Как мы пробивали Великий Китайский Фаервол (ч.2)

Привет!

С вами снова Никита — системный инженер из компании SЕMrush. И этой статьей я продолжаю историю про то, как мы придумывали решение обхода Китайского Фаервола для нашего сервиса semrush.com.

В предыдущей части я рассказал:


  • какие появляются проблемы после того, как принимается реешние «Нам нужно сделать так, чтобы наш сервис работал в Китае»
  • какие проблемы есть у китайского интернета
  • зачем нужна ICP-лицензия
  • как и почему мы решили тестировать наши тестовые стенды с помощью Catchpoint
  • какой результат дал наш первый вариант решения, базирующийся на Cloudflare China Network
  • как мы нашли баг в DNS Cloudflare


c8e0wt5ps6j4bkc8glimogfgtxg.png

Эта часть — самая итересная, на мой счет, потому что сосредоточена на конкретных технических реализациях стейджингов. И начнем мы, а точнее продолжим, с Alibaba Cloud.


Alibaba Cloud

Alibaba Cloud — довольно большой облачный провайдер, в котором есть все сервисы, позволяющие ему честно величать себя cloud provider. Хорошо, что у них есть возможность зарегаться иностранным пользователям, и что большая часть сайта переведена на английский (для Китая это роскошь). В данном облаке можно работать со множеством регионов мира, материкового Китая, а также Океанической Азии (Гонконг, Тайвань, и т.д.).


IPSEC

Начали с географии. Так как тестовый сайт у нас находился в Google Cloud, нам необходимо было «связать» Alibaba Cloud c GCP, поэтому открыли список локаций, в которых присутствует Google. В тот момент у них еще не было своего датацентра в Гонконге.
Ближайшим регионом оказался asia-east1 (Тайвань). У Ali наиболее близким регионом континентального Китая к Тайваню оказался cn-shenzhen (Шеньчжень).

С помощью terraform описали и подняли всю инфраструктуру в GCP и Ali. Туннель 100 мбит/с между облаками поднялся практически моментально. На стороне Шеньчженя и Тайваня подняли проксирующие виртуалки. В Шеньчжене пользовательский трафик терминируется, проксируется через туннель в Тайвань, а оттуда уже идет напрямую на внешний IP нашего сервиса в us-east (Восточное побережье США). Пинг между виртуалками по туннелю 24ms, что не так плохо.

Одновременно мы разместили тестовую зону в Alibaba Cloud DNS. После делегирования зоны на NS Ali, время резолвинга снизилось с 470 ms до 50 ms. До этого зона тоже была на Cloudlfare.

Параллельно с туннелем до asia-east1 подняли еще один туннель из Шеньчженя прямо в us-east4. Там создали еще проксирующих виртуалок и начали мерить оба решения, маршрутизируя тестовый трафик с помощью Cookies или DNS. Схематично тестовый стенд описан на следующем рисунке:


r8c4gvhj7_joemea0hznmvo5gxk.png

Latency для туннелей получилась следующей:
Ali cn-shenzhen <--> GCP asia-east1 — 24ms
Ali cn-shenzhen <--> GCP us-east4 — 200ms

Браузерные тесты Catchpoint рапортовали об отличном улучшении показателей.


e0kuamjpkkcbj737036tyuirbuk.png

Сравните результаты тестов для двух решений:

Это данные решения, использующего IPSEC-туннель через asia-east1. Через us-east4 результаты были хуже, да и ошибок было больше, поэтому результаты приводить не буду.

По результатам данного теста двух туннелей, один из которых терминируется в ближайшем регионе к Китаю, а другой в финальном пункте назначения, стало понятно, что важно как можно быстрее «выныривать» из-под китайского фаервола, а дальше использовать быстрые сети (CDN-провайдеров, облачных провайдеров и т.д.). Не надо пытаться одним махом пройти фаервол и попасть в пункт назначения. Это не самый быстрый путь.

В целом, результаты неплохи, однако, у semrush.com медиана 8.8s, а 75 Percentile 9.4s (на том же тесте).
И прежде чем двигаться дальше, я хотел бы сделать небольшое лирическое отступление.


Лирическое отступление

После того, как пользователь заходит на сайт www.semrushchina.cn, который резолвится через «быстрые» китайские DNS-серверы, HTTP-запрос идет через наше быстрое решение. Ответ возвращается по тому же пути, но во всех JS-скриптах, HTML-страницах и прочих элементах вэб-страницы указан домен semrush.com для дополнительных ресурсов, которые должны быть загружены при отрисовке страницы. То есть клиент резолвит «главную» А-запись www.semrushchina.cn и идет в быстрый туннель, быстро получает response — HTML-страницу, в которой указано:


  • скачай такой-то js с sso.semrush.com,
  • CSS файлы забери с cdn.semrush.com,
  • и еще возьми картинок с dab.semrush.com
  • и так далее.

Браузер начинает идти во «внешний» интернет за этими ресурсами, проходя каждый раз через пожирающий время ответа фаервол.

Но на в предыдущем тесте представлены результаты, когда на странице нет ресурсов semrush.com, только semrushchina.cn, а *.semrushchina.cn резволвится в адрес виртуалки в Шеньчжене, чтобы попасть потом в туннель.

Только так, по максимуму закидывая весь возможный трафик через свое решение быстрого прохода китайского фаервола, можно получить приемлемые скорости и показатели доступности сайта, а также честные результаты тестов решений.
Мы сделали это без единой правки кода на стороне продуктов команд.


Subfilter

Решение родилось практически сразу после того, как всплыла данная проблема. Нам был нужен PoC (Proof of Concept), что наши решения прохода фаервола действительно работают хорошо. Для этого нужно по максимуму заворачивать весь трафик сайта в это решение. И мы применили subfilter в nginx.

Subfilter — это довольно простой модуль в nginx, позволяющий менять одну строку в теле ответа на другую строку. Вот мы и поменяли все вхождения semrush.com на semrushchina.cn во всех ответах.

И… это не сработало, потому что от бэкэндов мы получали сжатый контент, соответственно subfilter не находил нужную строку. Пришлось добавить еще один локальный server в nginx, который разжимал ответ и передавал его следующему локальному серверу, который уже занимался подменой строки, сжатием и отдачей ее следующему по цепочке прокси-серверу.


pb6mjdouldlxlm6hr1ilz9dpck0.png

В итоге, где клиент бы получил .semrush.com, он получал .semrushchina.cn и послушно шел через наше решение.

Однако недостаточно просто поменять домен в одну сторону, ведь бэкэнды все также ожидают semrush.com в последующих запросах от клиента. Соответственно, на том же сервере, где делается замена в одну сторону, с помощью простенького регулярного выражения мы получаем поддомен из запроса, а дальше делаем proxy_pass с переменной $host, выставленной в $subdomain.semrush.com. Может показаться запутанно, но это работает. И работает хорошо. Для отдельных доменов, требующих другой логики, просто создаются свои server-блоки и делается отдельная конфигурация. Ниже представлены укороченные nginx конфиги для наглядности и демонстрации этой схемы.

Следующий конфиг обрабатывает все запросы из Китая на .semrushchina.cn:

    listen 80;

    server_name ~^(?[\w\-]+)\.semrushchina.cn$;

    sub_filter '.semrush.com' '.semrushchina.cn';
    sub_filter_last_modified on;
    sub_filter_once off;
    sub_filter_types *;

    gzip on;
    gzip_proxied any;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;

    location / {
        proxy_pass http://127.0.0.1:8083;
        proxy_set_header Accept-Encoding "";
        proxy_set_header Host $subdomain.semrush.com;
        proxy_set_header X-Accept-Encoding $http_accept_encoding;
    }
}

Данный конфиг проксирует на localhost на 83 порт, а там поджидает следующий конфиг:

    listen 127.0.0.1:8083;

    server_name *.semrush.com;

    location / {
        resolver 8.8.8.8 ipv6=off;
        gunzip on;
        proxy_pass https://$host;
        proxy_set_header Accept-Encoding gzip;
    }
}

Повторюсь, это обрезанные конфиги.

Примерно так. Может выглядеть сложным, но это на словах. На деле все проще пареной репы :)


Конец лирического отступления

Какое-то время мы были счастливы, потому что миф о падающих IPSEC-туннелях не подтвердился. Но потом туннели начали падать. Несколько раз в сутки по несколько минут. Немного, но нас это не устраивало. Так как оба туннеля терминировались на стороне Ali на одном роутере, мы решили, что, возможно, это региональная проблема и нужно поднять бэкапный регион.

Подняли. Туннели начали падать в разное время, но у нас прекрасно отрабатывал фейловер на уровне upstream в nginx. Но потом туннели начали падать примерно одновременно :) И снова начались 502 и 504. Uptime стал ухудшаться, поэтому мы стали прорабатывать вариант с Alibaba CEN (Cloud Enterprise Network).


CEN

CEN — это связность двух VPC из разных регионов внутри Alibaba Cloud, то есть можно соединить приватные сети любых регионов внутри облака между собой. И что самое главное: у данного канала есть довольно строгий SLA. Он очень стабилен как по скорости, так и по uptime. Но никогда не бывает всё так просто:


  • его ОЧЕНЬ непросто получить, если вы не китайские граждане или юрлица,
  • платить нужно за каждый мегабит пропускной способности канала.

Получив возможность соединить Mainland China и Overseas, мы создали CEN между двумя регионами Ali: cn-shenzhen и us-east-1 (наиболее близкой точкой к us-east4). В Ali us-east-1 подняли еще одну виртуалку, чтобы был еще один hop.

Получилось так:


s50sqsjxzjx0r1fqyx-cjsjkuvq.png

Результаты браузерных тестов ниже:


9bp0oqtuludsve8g5tlnqfsa75k.png

Показатели немного лучше, чем у IPSEC. Но через IPSEC потенциально можно качать со скоростью 100 мбит/c, а через CEN только со скоростью 5 мбит/c и дороже.

Напрашивается гибрид, да? Соединить скорость IPSEC и стабильность CEN.

Так мы и поступили, пустив трафик как через IPSEC, так и через CEN в случае падения IPSEC-туннеля. Uptime стал намного более высоким, но скорость загрузки сайта пока оставляла желать лучшего. Тогда я нарисовал все схемы, которые мы уже использовали и тестировали, и решил попробовать добавить в эту схему еще немного GCP, а именно GLB.


GLB

GLB — это Global Load Balancer (или Google Cloud Load Balancer). Он обладает важным для нас преимуществом: в контексте CDN у него anycast IP, что позволяет роутить трафик в ближайший к клиенту датацентр, благодаря чему трафик быстрее попадает в быструю сеть Google и меньше идет по «обычному» интернету.

Недолго думая, мы подняли HTTP/HTTPS LB в GCP и бэкэндом поставили наши виртуалки с subfilter.

Было несколько схем:


  • Использовать Cloudflare China Network, но в этот раз Origin«ом указать глобальный IP GLB.
  • Терминировать клиентов в cn-shenzhen, а оттуда проксировать трафик сразу в GLB.
  • Идти сразу из Китая в GLB.
  • Терминировать клиентов в cn-shenzhen, оттуда проксировать в asia-east1 через IPSEC (в us-east4 через CEN), оттуда уже идти в GLB (спокойно, внизу будет картинка и объяснение)

Мы протестировали все эти варианты и еще несколько гибридных:


  • Cloudflare + GLB


aa-z5cpge5r_2qazfvcsvne5loi.png

Эта схема не устроила нас по uptime и DNS-ошибкам. Но тест проводили до фикса бага со стороны CF, возможно, сейчас стало лучше (однако, это не исключает HTTP-таймауты).


mmefid6fj_5fwsbvtjwealb6wnu.png

Такая схема также не устроила нас по uptime, так как GLB часто выпадал из апстрима из-за невозможности коннекта в приемлемое время или таймаута, ведь для сервера внутри Китая адрес GLB так и остается снаружи, а значит, за китайским фаерволом. Магии не случилось.


ygl9jcr4ugsqil_ry6zf4kj4dhy.png

Вариант, похожий на предыдущий, только в нем не использовались серверы в самом Китае: трафик шел сразу в GLB (поменяли DNS-записи). Соответственно, результаты не устроили, так как у обычных китайских клиентов, пользующихся услугами обычных интернет-провайдеров, ситуация с прохождением фаервола намного хуже, чем у Ali Cloud.


  • Shenzhen → (CEN/IPSEC) → Proxy → GLB


9fv_lwec8yi4-rmuubqypun5mas.png

Здесь мы решили использовать самое лучшее от всех решений:


  • стабильность и гарантированный SLA от CEN
  • высокую скорость от IPSEC
  • «быструю» сеть гугла и его anycast.

Схема выглядит примерно так: трафик пользователей терминируются на виртуалке в ch-shenzhen. Там настроены апстримы nginx, часть из которых ссылается на частные IP-серверов, находящихся на другом конце IPSEC-туннеля, а часть апстримов — на частные адреса серверов на другой стороне CEN. IPSEC настраивался до региона asia-east1 в GCP (был ближайший регион к Китаю на момент создания решения. Сейчас у GCP есть также присутствие в Гонконге). CEN — до региона us-east1 в Ali Cloud.

Далее трафик из обоих концов направлялся на anycast IP GLB, то есть в ближайшую точку присутствия гугла, и уходил по его сетям в регион us-east4 в GCP, в котором стояли подменяющие виртуалки (с subfilter в nginx).

Это гибридное решение, как мы и ожидали, позволило воспользоваться преимуществами каждой технологии. В целом, трафик идет через быстрый IPSEC, но если начинаются проблемы, мы быстро и на несколько минут выкидываем эти сервера из апстримов и шлем трафик только через CEN, пока туннель не стабилизируется.

Внедрив 4-е решение из списка выше, мы достигли того, чего хотели, и чего требовал от нас бизнес на тот момент времени.

Результаты браузерных тестов для нового решения в сравнении с предыдущими:


CDN

Во внедренном нами решении все хорошо, только нет CDN, который мог бы акселерировать трафик на уровне регионов и даже городов. По идее, это должно ускорить работу сайта для конечных пользователей за счет использования быстрых каналов связи CDN-провайдера. И мы все время об этом думали. И вот, наступило время следующей итерации по проекту: поиск и тестирование CDN-провайдеров в Китае.

И об этом я расскажу вам в следующей, заключительной части :)

© Habrahabr.ru