История о том, как сисадмин борется с конторой с бюджетом в 60 млрд.
В продолжение комментария.

Ето я
Ето я

Введение

Есть грандиозный проект, и для его существования нужны 3 вещи:
- сервера
- люди
- vpn

Компания зарубежная, и мы с удовольствием используем AWS.
Требования регуляторов привели нас к OpenVPN.
А компания любит дешёвую рабочую силу, поэтому часть сотрудников живёт в РФ.

Получилась простая схема, на которой хотелось бы жить вечно.

client -> openvpn://bastion.example.com:1194
client -> openvpn://bastion.example.com:1194


Казалось бы, что может пойти не так? Именно тут появляется наш великолепный цензор в виде РКН и ставит палки в колёса.

КВН
КВН

Глава 1. Первые блокировки, obfs4proxy

В какой-то момент, клиенты в РФ начали отваливаться. Анализ быстро привёл нас к пониманию, что происходит обрыв tcp соединения на этапе подключения. Но только для OpenVPN, остальной траффик ходил как положено.
Значит нам нужно замаскировать траффик, и сделать это:
- максимально удобно для сотрудников, далеко не все кулхацкеры
- безопасно для инфры, лишных дырок появиться не должно

Так у нас появился obfs4proxy c связке с Viscosity

client -> obfs://127.0.0.1:1080 -> obfs://bastion.example.com:1195 -> openvpn://bastion.example.com:1194
client -> obfs://127.0.0.1:1080 -> obfs://bastion.example.com:1195 -> openvpn://bastion.example.com:1194

Конечно же никто не мог тогда предвидеть, на какую интересную дорожку мы встали.

Первые опьяняющие победы
Первые опьяняющие победы

Глава 2. Вынос части инфры из AWS.

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

client -> obfs://127.0.0.1:1080 -> obfs://tunnel.example.com:1195 -> openvpn://bastion.example.com:1194
client -> obfs://127.0.0.1:1080 -> obfs://tunnel.example.com:1195 -> openvpn://bastion.example.com:1194

Логика была банальна:
- поставщики виртуалок не обязаны ставить ТСПУ
- у хороших вендоров есть свой канал
- серваки светятся как отечественные в россии, но имеют зарубежный IP

Такое должно прожить долго и лавочку не прикроют, верно ведь?

Всё ещё просто и весело
Всё ещё просто и весело

Глава 3. Попытка попасть в белые списки.

Играть в кошки-мышки с государством интересно, но хотелось иногда просто поработать.
Поэтому следующим этапом была попытка попасть в белые списки. Мы легальные, аудионаркотики не продаём, неужели не мы можем работать как белые люди?
Спустя несколько месяцев ответ был получен, но результата это не дало.

Успешное добавление в белые списки не гарантирует работоспособность
Успешное добавление в белые списки не гарантирует работоспособность

Глава 4. Появление cloak/trojan/hysteria/vless

В какой-то момент часть сотрудников начала жаловаться на недоступность. Снова.
Ещё одна пачка анализов и приходим к пониманию, что траффик режется. Симпомы идентичны обычному использованию OpenVPN, всё намекает на то, что протокол не столь надёжен, как хотелось бы.
Начинаем искать альтернативы. Так в течении пологода у нас заводятся shadowsocks/amnezia/ipsec/cloak/trojan/hysteria/vless/vkturnproxy.

client -> ck-client://127.0.0.1:1080 -> ck-server://tunnel.example.com:443 -> openvpn://bastion.example.com:1194
client -> ck-client://127.0.0.1:1080 -> ck-server://tunnel.example.com:443 -> openvpn://bastion.example.com:1194

Решения которые прекрасно работали для меня одного, начинают отваливаться в разных регионах страны, на разных провайдерах, в разных случаях. И в этом чёрном ящике абсолютно не понятно:
- то ли накатывают белые списки
- то ли РКН сломал часть интернета
- то ли их DPI научился детектить протокол
- то ли мы провинились
- то ли кривые руки сотрудников

Всё приходит к тому, что у нас поднята пачка методов обхода блокировок (уже 10 в документации), а на сотрудниках я тестирую.

Безмерная любовь к нашим органам
Безмерная любовь к нашим органам

Глава 5. Белые списки, поднятие серверов в РФ

Научились обходить блокировки? Вот вам задачка со звёздочкой.
Видимо так решило моё любимое министерство цифрового отрицательного развития.
Появилась нужда резко найти IP из белых списков и использовать их. Решение пришло также быстро - обратный король мидас отечественного IT. cloud.vk.com позвляет быстро поднять VPS, а на их серваках внезапно доступны instagram/youtube/etc.

client -> ck-client://127.0.0.1:1080 -> ck-server://subway.example.com:443 -> openvpn://bastion.example.com:1194
client -> ck-client://127.0.0.1:1080 -> ck-server://subway.example.com:443 -> openvpn://bastion.example.com:1194

Появилось ложное ощущение, что вот он конец, решение всех проблем, один сервер чтобы обходить их все. Сотрудники получили отличную скорость, я получил спокойствие.

Любовь проявляется в разных формах
Любовь проявляется в разных формах

Глава 6. Отрыв IP, резервирование

В какой-то момент работа остановилась, и пришлось вспоминать основы high availability - наличие запасного сервера.

Рака яичек им пожелал, но это почему-то не решило проблему
Рака яичек им пожелал, но это почему-то не решило проблему

Сложно сказать, что послужило причиной блокровки:
- кто-то из сотрудников сидел в max с рабочего VPN'a
- DPI действительно научился палить vless
- мои кривые руки в конфигурации сервера
- активные пробы прошлись по всем портам и нашли лишнее

Причины были уже не важны, главное стало понимание, что нужно что-то делать. И самым простым оказалось поднять ещё чуть-чуть виртуалок.

client -> ck-client://127.0.0.1:1080 -> ck-server://{subway|underground}.example.com:443 -> openvpn://bastion.example.com:1194
client -> ck-client://127.0.0.1:1080 -> ck-server://{subway|underground}.example.com:443 -> openvpn://bastion.example.com:1194

Я в своем познании настолько преисполнился, что я как будто бы уже сто триллионов миллиардов лет обхожу блокировки.

Желание возлюбить
Желание возлюбить

Глава 7. Multi-hop

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

client -> ck-client://127.0.0.1:1080 -> gost://subway.example.com:443 -> ck-server://tunnel.example.com:443 -> openvpn://bastion.example.com:1194
client -> ck-client://127.0.0.1:1080 -> gost://subway.example.com:443 -> ck-server://tunnel.example.com:443 -> openvpn://bastion.example.com:1194

Latency слегка подрос, зато наши сотрудники живут в стабильных условиях (надолго ли?).

Техносовпротивление
Техносовпротивление

Конфиги

Для настройки всего этого счастья в любом случае придётся прильнуть к официанльной документации, но небольшую шапарглку оставлю.

Скрытый текст

gost.yaml

services:
  - name: cloak-tcp
    addr: :443
    handler:
      type: tcp
    listener:
      type: tcp
    forwarder:
      nodes:
        - name: cloak-tcp
          addr: {{ gost_target }}:443

obfs4proxy.conf

TOR_PT_MANAGED_TRANSPORT_VER=1
TOR_PT_STATE_LOCATION=/var/lib/obfs4
TOR_PT_SERVER_TRANSPORTS=obfs4
TOR_PT_SERVER_BINDADDR=obfs4-0.0.0.0:{{ obfs4_port }}
TOR_PT_ORPORT={{ obfs4_target }}

ck-server.json

{
  "ProxyBook": {
    "openvpn": [
      "tcp",
      "{{ cloak_remote }}"
    ]
  },
  "BindAddr": [
    ":443"
  ],
  "BypassUID": [
    {{ cloak_uid }}
  ],
  "RedirAddr": "www.example.com",
  "PrivateKey": "{{ cloak_private }}"
}

ck-client.json

{
  "RemoteHost": "subway.example.com",
  "Transport": "direct",
  "ProxyMethod": "openvpn",
  "EncryptionMethod": "plain",
  "UID": "{{ cloak_uid }}",
  "PublicKey": "{{ cloak_public }}",
  "ServerName": "www.example.com",
  "NumConn": 4,
  "BrowserSig": "chrome",
  "StreamTimeout": 300
}

xray-server.json

{
  "log": {
    "loglevel": "info"
  },
  "routing": {
    "rules": [],
    "domainStrategy": "AsIs"
  },
  "inbounds": [
    {
      "listen": "0.0.0.0",
      "port": {{ xray_port }},
      "protocol": "vless",
      "tag": "vless_tls",
      "settings": {
        "clients": [
          {
            "id": "{{ xray_client }}",
            "email": "{{ xray_email }}",
            "flow": "xtls-rprx-vision"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "show": false,
          "dest": "www.example.com:443",
          "xver": 0,
          "serverNames": [
            "www.example.com",
            "example.com"
          ],
          "mldsa65Seed": "",
          "privateKey": "{{ xray_private }}",
          "minClientVer": "",
          "maxClientVer": "",
          "maxTimeDiff": 0,
          "shortIds": [
            {{ xray_short }}
          ]
        }
      },
      "sniffing": {
        "enabled": true,
        "destOverride": [
          "http",
          "tls"
        ]
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom",
      "tag": "direct"
    },
    {
      "protocol": "blackhole",
      "tag": "block"
    }
  ]
}

xray-client.json

{
    "log": {
        "level": "info"  
    },      
    "inbounds": [        
        {   
            "listen": "127.0.0.1",  
            "port": 1080,
            "protocol": "socks",    
            "settings": {
                "udp": true         
            },           
            "sniffing": {
                "enabled": true,    
                "destOverride": [   
                    "http",         
                    "tls"
                ]        
            }            
        }   
    ],      
    "outbounds": [       
        {   
            "domain_strategy": "ipv4_only",                                   
            "flow": "xtls-rprx-vision",                                       
            "packet_encoding": "xudp",                                        
            "server": "subway.example.com",                                   
            "server_port": 8443,    
            "tag": "proxy",         
            "tls": {     
                "alpn": [
                    "h2" 
                ],       
                "enabled": true,    
                "insecure": true,   
                "reality": {        
                    "enabled": true,
                    "public_key": "{{ xray_public }}", 
                    "short_id": "{{ xray_short }}"                            
                },       
                "server_name": "www.example.com",                             
                "utls": {
                    "enabled": true,
                    "fingerprint": "chrome"
                }
            },
            "type": "vless",
            "uuid": "{{ xray_uuid }}"
        },
        {
            "tag": "direct",
            "protocol": "freedom"
        },
        {
            "tag": "block",
            "protocol": "blackhole"
        }
    ]
}

ss-server.json

{
    "server": "0.0.0.0",
    "server_port": {{ ss_port }},
    "password": "{{ ss_pass }}",
    "method": "aes-256-gcm",
    "mode":"tcp_and_udp",
    "fast_open":false
}

ss-client.json

{
    "server": "tunnel.example.com",
    "server_port": {{ ss_port }},
    "password": "{{ ss_pass }}",
    "method": "aes-256-gcm",
    "local_address": "127.0.0.1",
    "local_port": 1080
}

hysteria-server.yaml

listen: :{{ hysteria_port }}

tls:
  cert: /etc/ssl/wildcard.crt
  key: /etc/ssl/wildcard.key

auth:
  type: userpass
  userpass: 
    {{ hysteria_client }}: {{ hysteria_pass }}

masquerade:
  type: proxy
  proxy:
    url: https://www.example.com
    rewriteHost: true

obfs:
  type: salamander
  salamander:
    password: {{ hysteria_obfs }}

resolver:
  type: udp
  tcp:
    addr: 8.8.8.8:53
    timeout: 4s
  udp:
    addr: 8.8.4.4:53
    timeout: 4s
  tls:
    addr: 1.1.1.1:853
    timeout: 10s
    sni: cloudflare-dns.com
    insecure: false
  https:
    addr: 1.1.1.1:443
    timeout: 10s
    sni: cloudflare-dns.com
    insecure: false

hysteria-client.yaml

server: tunnel.example.com:1984
auth: {{ hysteria_client }}: {{ hysteria_pass }}
obfs:
  type: salamander
  salamander:
	password: {{ hysteria_obfs }}
http:
  listen: 127.0.0.1:8080
socks5:
  listen: 127.0.0.1:1080

trojan-server.json

{
  "run_type": "server",
  "local_addr": "0.0.0.0",
  "local_port": {{ trojan_port }},
  "remote_addr": "{{ trojan_remote }}",
  "remote_port": 443,
  "password": ["{{ trojan_password }}"],
  "ssl": {
    "cert": "/etc/ssl/wildcard.crt",
    "key": "/etc/ssl/wildcard.key",
    "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384",
    "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384",
    "prefer_server_cipher": true,
    "alpn": [
      "http/1.1"
    ],
    "alpn_port_override": {
      "h2": 81
    },
    "reuse_session": true,
    "session_ticket": false,
    "session_timeout": 600,
    "plain_http_response": "",
    "curves": "",
    "dhparam": ""
  },
  "tcp": {
      "prefer_ipv4": false,
      "no_delay": true,
      "keep_alive": true,
      "reuse_port": false,
      "fast_open": false,
      "fast_open_qlen": 20
  },
  "mysql": {
      "enabled": false,
      "server_addr": "127.0.0.1",
      "server_port": 3306,
      "database": "trojan",
      "username": "trojan",
      "password": "",
      "key": "",
      "cert": "",
      "ca": ""
  }
}

trojan-client.json

{
  "run_type": "client",
  "local_addr": "127.0.0.1",
  "local_port": 1080,
  "remote_addr": "{{ trojan_remote }}",
  "remote_port": {{ trojan_port }},
  "password": [
      "{{ trojan_password }}"
  ],
  "log_level": 1,
  "ssl": {
      "verify": true,
      "verify_hostname": true,
      "cert": "",
      "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA",
      "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384",
      "sni": "www.example.com",
      "alpn": [
          "h2",
          "http/1.1"
      ],
      "reuse_session": true,
      "session_ticket": false,
      "curves": ""
  },
  "tcp": {
      "no_delay": true,
      "keep_alive": true,
      "reuse_port": false,
      "fast_open": false,
      "fast_open_qlen": 20
  }
}

Вывод

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

Особое внимание:
- ТСПУ начали поставлять на сети межрегиональных провайдеров, желательно настраивать multihop, чтобы траффик из РФ выходил и шифрованным, и обфусцированным
client -> vps rf -> vps eu -> vpn
- желательно не использовать обход блокировок на мобилках одновременно с небезопасными приложениями (макс/яндекс/банки), активно сливают, можно поймать бан на следующий день
- не забываем настраивать split-tunnel, чтобы траффик для отечественных сервисов шёл внутри страны
- РКН чувствителен к портам, для белых списков только 80/443, для обхода блокировок не использовать 1337/1984/3128/8080
- РКН быстро добавляет IP в блэклист сервера за рубежом, и рубит машину в РФ, если спалит

На данный момент самым живым является схема, когда:
- есть защита от активных проб
- есть валидные tls сертификаты и настоящий веб-сервер, который отвечает
- траффик шифруется лишь 1 раз
- траффик обфусцируется и очень похож на https
- входной и выходной адреса отличаются
- есть резервирование на несколько машин внутри страны

Комментарии (8)


  1. atwok
    26.04.2026 01:05

    Странно, что у одних изымают IP адреса в VK Cloud, а у других там всё работает и они публично распространяют ссылки MTproxy для подключения к Телеграм с обходом белых списков. Или Телеграм это другое и не считается обходом блокировок?


  1. edik-petrof
    26.04.2026 01:05

    не использовать ... 1984

    Почему? За данным портом не закреплено никакой широко используемой сетевой службы или программы. Или просто само сочетание цифр выглядит сомнительно?) По такой логике и порт 1917 можно запретить тогда.


    1. lex899
      26.04.2026 01:05

      Почему?

      Как раз потому что:

      За данным портом не закреплено никакой широко используемой сетевой службы или программы.

      Любой непонятный трафик на непонятных портах рискует попасть в блокировку "на всякий случай".


    1. Kenya-West
      26.04.2026 01:05

      У России три союзника: порты 22, 80 и 443. Все остальные для РКН рано или поздно станут как красный флаг для быка. Я давно уже свыкся с этой мыслёй...


  1. tuxi
    26.04.2026 01:05

    gost вроде бы тоже умеет в obfs


  1. microtheft
    26.04.2026 01:05

    Ко́микс (от англ. comic — «смешной») — рисованная история, рассказ в картинках.


  1. GeorgSokolov96
    26.04.2026 01:05

    Молодец лягушонок! Так их!


  1. aleksandr55575
    26.04.2026 01:05

    Мне теперь надо оплачивать 2 впс, чтоб обойти тспу, купленную на мои налоги. Как то не правильно это работает...