Три года назад я написал статью “Интернет-цензура и обход блокировок: не время расслабляться”. Перечитывая ее сейчас, спустя три года, не могу избавиться от двоякого чувства: с одной стороны я почти во всем оказался прав касательно того, как будут развиваться события, усиливаться блокировки, и регулироваться законодательство. С другой стороны, кое в чем я тогда серьезно ошибся.

А именно: слишком уж я надеялся на то, что доблестные фильтраторы интернета будут заботиться об уменьшении побочных эффектов и так называемого collateral damage, и поэтому будут вынуждены изобретать более сложные технологии детектирования, продолжая увлекательную игру в кошки-мышки. Реальность же оказалась гораздо прозаичнее: зачем думать, когда в руке кувалда? В результате чего имеем следующее: часть детектирования откровенно тупая (например, блок нескольких параллельных подключений к одному IP, что решается использованием gRPC-транспорта для VLESS или XHTTP-транспорта с правильными настройками мультиплексирования), а часть - еще тупее, а именно, никакого детектирования, а тупо бан популярных хостеров и облачных провайдеров целыми подсетями /16 и даже /8 - в результате чего или нельзя подключиться по TLS вообще, или можно, но соединение фризится после передачи примерно 16 килобайт данных, или же блок срабатывает на целую подсеть после случайного обращения к какому-нибудь “плохому” IP-адресу. За анализ огромное спасибо товарищу @0ka и его статьям “РКН создали белый список для 72 AS, но пострадали 391 AS (>225 млн IP адресов)”  и “РКН создали список подозрительных айпи адресов (динамическая блокировка 47 AS)”.

Поскольку это по сути дела ни что иное, как белые списки (не те что включаются в некоторых регионах во время воздушной тревоги, а белые списки SNI (доменов) в рамках заблокированных подсетей), то и обходить такие ограничение разумно все теми же методами: либо использовать старый-добрый Reality с подменой домена, либо… либо разместиться в такой подсети, которая наврядли целиком попадет под бан.

Про Reality

На самом деле, что самое смешное, XTLS-Reality в российских реалиях до сих пор избыточен. РКН так и не проводит active probing, поэтому точно такого же эффекта можно достичь, настроив обычный VLESS с простым самоподписанным сертификатом с нужным доменом - никто и не заметит. Кстати, такая штука прокатывает даже с некоторыми CDN, позволяющими загружать свои сертификаты. Think about it.

Давайте пойдем по второму пути. 

Сразу говорю: для всего описанного дальше нужна будет иностранная банковская карта и возможность подтвердить резидентство в какой-нибудь другой стране. Поэтому первым шагом необходимо обзавестись другом или родственником где-нибудь не в России, который все зарегистрирует и оплатит для вас. 

Мелких и средних хостеров блокируют, большие CDN блокируют, а кого не заблокируют, или хотя бы заблокируют самыми последними? Коллега мне тут шепчет на ухо: Azure, адреса которого располагаются в той же AS, где и почти все обычные ресурсы Microsoft, в том числе те, через которые работает Office 365 и с которых Windows получает метаданные для обновлений безопасности. В принципе, вариант не самый плохой, благо low-end виртуалки у них довольно недорогие. У меня почему-то не получилось стукнуться на такую виртуалку по TLS c SNI с доменами *.microsoft.com и подобными (хотя google.com пролетел исправно), то ли у них фильтр на фаерволе от таких хитрозадых, то ли я рукожоп - но вот чистый HTTP с такими доменами работает отлично, что позволяет использовать его с VLESS Enc (да, в VLESS появилось шифрование - и более того, в него же засунули Reality Seed, который предназначен для защиты от детектирования по размерам пакетов - изучите документацию на гитхабе XRay, там много нового и интересного).

Но Azure - это скучно. Что насчет Google? Берем Google Cloud, создаем там виртуалку в Compute Engine. Можно поиграться с параметрами чтобы сделать ее как можно дешевле - взять инстанс типа e2-micro (самый low-end, 0.25 vCPU, 1Gb RAM), поменять storage на standard disk, no backups, прощелкать локации чтобы найти где дешевле (разница может быть несколько долларов в месяц), не забыть отметить галочками Allow HTTP и Allow HTTPS traffic, и выбрать Ephemeral IPv4 address. По умолчанию оно создается как IPv4-only, чтобы было еще и IPv6 надо отдельно потанцевать с бубном в настройках сети перед созданием виртуалки, но не будем на этот тратить время.

Виртуалка создается, устанавливаем туда XRay, и… все работает. Мы стучимся к домену google.com который располагается в настоящей AS GOOGLE. 

А с другой стороны, если нам оно нужно только как запасной вариант на черный день - когда все остальное отвалится, чтобы была возможно попасть в свободный интернет и починить все как надо - то нет смысла постоянно держать включенной виртуалку и жечь деньги. Может, поднимем просто контейнер?

Идем в Google Cloud Run -> Services, и выбираем Deploy web service:

В качестве пути к образу контейнера задаем официальный docker-образ XRay: docker.io/teddysun/xray 

Выбираем регион подешевле (Tier 1) и поближе к нам. 

Дальше пройдемся по дополнительным опциям. Container port - 8080 (должен совпадать с тем, что мы укажем в конфиге XRay), memory 128 mb (для пары пользователей вполне достаточно если увидите, что не хватает, потом можно добавить будет), 1 CPU. 

Не поддавайтесь искушению поставить <1 CPU - (там можно задавать аж 0.08 и 0.25 CPU) - Google Cloud в этом случае запретит вам обработку более 1 параллельного подключения вашим контейнером, не знаю уж чем объясняется такое требование, поэтому ставим 1 CPU.

Идем дальше. Allow public access, billing - request-based, Auto Scaling от 0 инстансов (ничего не запущено когда нет подключений) до 1 инстанса (а нам больше и не надо). Ingress - all.

В заключение ставим 3600 секунд таймаут, и сотню или больше параллельных запросов.

Теперь вопрос: а как нам скормить конфиг XRay контейнеру? Перед тем как создавать контейнер, идем в Secret Manager -> Create secret,  задаем любое имя, и загружаем туда наш config.json или копипастим его содержимое туда.

После этого в настройках контейнера на вкладке Volumes можно подмонтировать этот секрет как /etc/xray/config.json файл в файловой системе:

Конфиг XRay сделаем максимально простым, типа такого (sniffing отключен для экономии ресурсов):

{
  "log": {
    "loglevel": "info"
  },
  "inbounds": [
    {
      "port": 8080,
      "protocol": "vless",
      "tag": "pxy",
      "settings": {
        "clients": [
          {
            "id": "4c3fe585-ac09-41df-b284-70d3fbe27773",
            "email": "user1"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "xhttp",
        "xhttpSettings": {
          "path": "/secreturlxx"
        }
      },
      "sniffing": {
        "enabled": false
       }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom",
      "tag": "direct"
    }
  ]
}

Тут используется XHTTP - балансировщику Google важно, чтобы использовался какой-нибудь стандартный и ему известный протокол.

Деплоим контейнер, смотрим в Logs что он запустился и чувствует себя хорошо, там же в свойствах контейнера получаем URL типа xxxxxxxxx.europe-west4.run.app для доступа к нему, настраиваем XRay-клиент соответствующе (не забудьте XHTTP XMUX с maxConnections=1), пробуем подключиться и убеждаемся, что все работает. Даже если не запущено ни одного инстанса, стартует (в отличие от тех же контейнеров в Azure) оно очень быстро, буквально 1-2 секунды задержки на первый запрос. Балансировщик у Google хороший, и позволяет использовать XHTTP даже в самом быстром режиме stream-one

Еще раз радуемся что все работает, заходим в настройки, и осознаем, что мы ошиблись: меняя конфигурацию со старой, использовавшей google.com как фейковый домен, мы указали наш новый домен *run.app в поле Host, но забыли поменять его в строке адреса сервера и в строке SNI - там остался google.com 
И все равно все работает. WTF?

Разгадка проста. Google Cloud - это почти та же самая инфраструктура, что Google использует для своих собственных сервисов. И устроена она так, что вы можете подключиться почти к любому серверу Google - да-да, к тем, к которым подключаются еще тысячи ваших соседей, указав в SNI домен любого сервиса Google, при подключении получить правильный сертификат… а потом передать серверу в поле Host домен вашего контейнера в облаке, и запрос будет смаршрутизирован через инфраструктуру Google к вашему контейнеру. 

Этот трюк не новый, он называется domain fronting, я уже писал статью на эту тему пару лет назад: https://habr.com/ru/articles/778134/ Удивительно только то, что почти все популярные CDN его уже давно строжайше запретили, а Google и не парится.

Еще раз: это не обманка типа XTLS-Reality, когда мы передаете SNI google.com и прикрываетесь его сертификатом, подключаясь на самом деле к какому-то левому серверу.

Нет. В данном случае вы резолвите google.com, подключаетесь к IP-адресу настоящего google.com, но запрос волшебным образом попадает к вам в контейнер. Если это не идеальная маскировка под гугл, то я не знаю, что назвать идеальной маскировкой. Работает оно и по HTTPS, и через QUIC - как больше нравится. И чтобы забанить такое, нужно целиком и полностью забанить Google.

Конфиг клиента выглядит примерно так:

В чем подводные камни? Кроме необходимости наличия иностранной карты и подтверждения адреса вне РФ, есть еще существенный недостаток: это очень дорого. Поэтому подойдет только как On-demand штука для экстренных случаев, либо наоборот - нужно шарить контейнер с как можно большим количеством других людей, чтобы вышло дешевле в пересчете на одного человека. Да, будет все равно дорого, но тут ничего не поделаешь - цена за “жить как будто ничего не случилось” растет с каждым днем сильнее и сильнее.
Подслащу пилюлю: Google Cloud при регистрации дает trial на три месяца с 200 долларами на счете (но карту привязать все равно надо).

А если вы уперлись в предел вычислительных мощностей контейнера, и у вас есть какой-нибудь другой VPS за бугром, то вместо контейнера XRay можно задеплоить praveenkarunarathne/Google-Cloud-Run-Proxy. Передаете контейнеру env V2RAY_SERVER_IP=1.2.3.4, и он будет тупо пересылать все TCP-подключения со входа контейнера на заданный IP-адрес (на том конце должен быть обычный HTTP inbound, т.к. TLS уже терминируется на инфраструктуре Google). Он написан на Go и очень эффективный по производительности. Не забудьте поставить звездочку на гитхабе - автору будет приятно.

Такие дела.