XRay (с VLESS/XTLS): проброс портов, реверс-прокси, и псевдо-VPN
Я уже написал тут много статей на тему установки и настройки прокси-серверов XRay с недетектируемыми протоколами Shadowsocks-2022, VLESS (с XTLS), и т.п. И один из очень часто поднимаемых в комментариях вопросов звучит так: можно ли с использованием XRay как-то организовать проброс портов или получать доступ к внутренностям корпоративной сети? Можно, и сейчас я расскажу как.
XRay поддерживает механизм под названием «reverse proxy», что в купе с богатыми возможностями настройки правил маршрутизации позволяет сделать довольно много интересных схем. Механизм в документации упомянут как »…in the testing phase» и «may have some issues», но я попробовал, и все работает.
Традиционная нейрокартинка для отвлечения внимания
Итак, что же можно сделать с помощью реверс-проксирования?
Можно получать доступ к каким-либо сервисам на хосте за NAT’ом или строгим фаерволом, и даже более того — можно получать доступ к сервисам на других устройствах в локальной сети, к которой имеет доступ этот самый хост за NAT’ом или файерволом.
Можно маршрутизировать весь (или некоторый в зависимости от настроенных правил) трафик на хост за NAT’ом или фаерволом и выпускать его оттуда в Интернет.
Например, вы проживаете за границей, хотите оплачивать счета за ЖКХ вашей недвижимости оставшейся России, но сервис оплаты не пускает вас с забугорных IP и не пускает вас с IP-адресов даже российских VPS-хостеров. Тогда можно поставить у кого-нибудь из друзей или родственников в РФ преднастроенный роутер или одноплатник типа Raspberry Pi, который подключится к вашему прокси-серверу, а вы, в свою очередь, через прокси-сервер сможете достучаться до этого роутера/р-пишки и выйти через него во внешний интернет как обычный пользователь, находящийся в России — и всем ресурсам будет виден IP-адрес российского домашнего интернет-провайдера.Можно выборочно пробрасывать порты, например, все подключения на 80 порт прокси-сервера будут переадресовываться на 80 (или любой другой) порт «изолированного» хоста или еще куда-то дальше.
Можно даже теоретически попробовать соорудить псевдо-VPN, чтобы подключенные клиенты прокси-сервера могли коммуницировать друг с другом.
Итак, поехали. Суть механизма reverse-proxy проста. Есть у нас прокси-сервер на каком-нибудь VPS, доступный для всего интернета. Нам через него надо попасть на какой-нибудь хост, который не доступен из всего интернета — например, он находится за NAT’ом без своего белого IP.
Решение простое: нужно сделать так, чтобы не сервер пытался достучаться до хоста (это невозможно), а хост первым подключился к серверу, а потом по уже установленному подключению запросы будут бегать в противоположном направлении. Это и есть reverse proxy.
Далее нужно разобраться с терминологией, которая используется в документации и конфигах XRay, потому что там все немного не очевидно и можно запутаться. Нужно знать и понимать три слова:
«client» — это клиент (логично, да), которому надо получить доступ куда-то или выйти в Интернет;
»portal» — это ваш прокси-сервер на VPS, связующее звено между client’ом и bridge’м;
»bridge» — это другой xray-клиент, изолированный за NAT’ом и фаерволом, к которому надо получить доступ.
-
Слово «bridge» может запутать, но скорее всего такое название разработчики решили использовать, потому что bridge является точкой соприкосновения внешнего интернета и ресурсов внутри сети — то есть финальной точкой назначения до которой мы хотим достучаться, может быть не сам bridge, а какие-то хосты в его локалке.
А теперь давайте разбираться, как это все настроить. Конфигурация reverse proxy описана в документации XRay: https://xtls.github.io/en/config/reverse.html, но описание довольно куцее. Еще есть гит-репа с примерами настройки реверс-прокси: https://github.com/XTLS/Xray-examples/blob/main/ReverseProxy/README.ENG.md
В качестве транспортного протокола может использоваться любой, который поддерживается XRay’ем. Я буду использовать в примерах для простоты обычный Shadowsocks, но ровно то же самое можно сделать и с VLESS, и с VLESS с Reality, и с VLESS через вебсокеты и CDN, и вообще как угодно — см. мои предыдущие статьи.
Начнем с настройки сервера («portal»)
{
"reverse": {
"portals": [
{
"tag": "portal",
"domain": "reverse.hellohabr.com"
}
]
},
"inbounds": [
{
"port": 5555,
"tag": "incoming",
"protocol": "shadowsocks",
"settings": {
"method": "2022-blake3-aes-128-gcm",
"password": "...",
"network": "tcp,udp"
}
}],
"outbounds": [
{
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"tag": "block"
}],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": ["incoming"],
"outboundTag": "portal"
}]
}
}
И давайте разбираться, что все это значит. В inbound у нас все как обычно — описание параметров для входящих соединений, простой shadowsocks. А вот дальше начинается интересное.
В начале конфига в секции «reverse» и объявляем один объект в массиве «portals», назначая ему тег «portal» и виртуальный домен «reverse.hellohabr.com». Этот домен именно виртуальный — он может не существовать, а правильнее сказать, он не должен быть каким-то из реально существующих доменов. Он используется только для того, чтобы XRay понял, что конкретно вот этот входящий запрос (с таким доменом) — не что-то, что нужно обработать как обычно и выпустить в интернет, а специальный запрос на установление соединения от bridge’а для того чтобы поднять reverse proxy.
Самое важное тут в routing — rules. Правило маршрутизации говорит о том, что все запросы, которые приходят от клиентов, нужно переадресовывать на reverse-proxy (тег «portal»).
Естественно, если у нас другие пожелания, можно это правило маршрутизации немножко усложнить — например, переадресовывать на portal только запросы к определенным IP-адресам (диапазонам IP-адресов) или хостнеймам, а все остальное отправлять сразу в интернет (outbound «freedom»), или не пускать никуда (outbound «block»). Про правила маршрутизации можно почитать в документации XRay: https://xtls.github.io/en/config/routing.html#ruleobject, плюс я еще немного касался этой темы в своем недавнем FAQ.
Дальше настраиваем «изолированного клиента» («bridge»)
{
"reverse": {
"bridges": [
{
"tag": "bridge",
"domain": "reverse.hellohabr.com"
}]
},
"outbounds": [
{
"protocol": "shadowsocks",
"settings": {
"servers": [{
"address": "....",
"port": 5555,
"method": "2022-blake3-aes-128-gcm",
"password": "...",
}]
},
"tag": "outgoing"
},
{
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"tag": "block"
}],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": ["bridge"],
"domain": ["full:reverse.hellohabr.com"],
"outboundTag": "outgoing"
},
{
"type": "field",
"inboundTag": ["bridge"],
"outboundTag": "direct"
}]
}
}
В outbounds у нас тоже все просто — настройки подключения к серверу на VPS, который мы настроили в предыдущем шаге. Тоже есть секция «reverse», но на этот раз там объявлен объект «bridge» с тегом «bridge», и тем же виртуальным служебным хостнеймом, что мы задали для portal’а на предыдущем шаге — они должны совпадать.
А вот в routing-rules у нас все чуть-чуть сложнее. Первое правило определяет, как именно мы должны подключаться к нашему прокси на VPS (порталу) — мы говорим, что все подключения от bridge с виртуальным служебным адресом должны уходить на прокси через outbound, который мы описали чуть выше в этом же конфиге.
А второе правило определяет, что мы должны сделать с запросами, которые пришли к нам с прокси-сервера (с портала). В данном случае мы сразу выпускаем их всех наружу. То есть если пунктом назначения в запросе будет IP-адрес какого-либо хоста в локальной сети, к которому подключен bridge, подключение будет установлено к этому хосту в локальной сети. Если пунктом назначения в запросе будет какой-нибудь ресурс в публичности интернете — бридж установит соединение с ним. И т.д. Также как и в прошлом пункте, эти правила можно кастомизировать под себя — например, выпускать на outbound «direct» только запросы к определенным IP-адресам или хостнеймам, а все остальное отправлять в block, если мы не хотим, чтобы через нас лазили в интернет, а могли достучаться только до туда, до куда мы разрешили.
Готово!
В принципе, на этом настройка закончена. Когда вы запустите Xray на bridge, благодаря объявленному объекту «bridge» в конфиге, он инициирует и будет поддерживать специальное служебное подключение к прокси-серверу.
Прокси-сервер на вашем VPS, благодаря объекту «bridge» в конфиге, поймет, что это подключение — не обычно, а специальное служебное для reverse-проксирования, и обработает его как надо.
Когда вы своим обычным клиентом (например, с мобильного телефона или десктопа) подключитесь к прокси-серверу и попробуете подключиться через него к какому-нибудь ресурсу, прокси (portal) следуя правилам, переадресует этот запрос на подключение на bridge, а bridge, в свою очередь, следуя своим правилам, выпустит этот вопрос в свою локальную сеть или в интернет.
Как я уже сказал ранее, вы можете настроить правила так, как надо вам — например, на вашем прокси на VPS отправлять на bridge только запросы к определенным IP-адресам или доменам, а все остальное выпускать сразу в интернет и блокировать. Можно настроить несколько разных inbound’s или несколько разных пользовательских ID в рамках одного inbound’а, и применять разные правила для разных пользователей. На самом bridge (хосте за NAT’ом) тоже можно применять разные правила — например, пропускать только запросы на определенные IP, а все остальное блокировать.
Про правила маршрутизации можно почитать в документации XRay: https://xtls.github.io/en/config/routing.html#ruleobject, плюс я еще немного касался этой темы в своем недавнем FAQ.
Из того, что еще может оказаться полезным: в настройках outbound’а с протоколом «freedom» можно указывать конкретный сетевой интерфейс или IP-адрес для исходящих подключений, и даже routing mark в Linux, чтобы разруливать доступ к локалке так, как нужно вам, см. документацию Xray для подробностей.
А если нужен проброс портов?
На базе описанных выше конфигов можно строить разные схемы — например, пробрасывать порты.
Допустим, нам нужно, чтобы все подключения к 80 порту portal’а переадресовывались на 80 порт bridge’а.
Добавим на portal’е специальный inbound и правило для него:
"inbounds":
[
....,
{
"tag": "web_server_forward",
"port": 80,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1",
"port": 80,
"network": "tcp"
}
}
]
....
"routing": {
"rules": [
....,
{
"type": "field",
"inboundTag": ["web_server_forward"],
"outboundTag": "portal"
}
]
}
а и добавим соответствующие директивы на bridge:
"outbounds":
[
...,
{
"tag": "web_server_local",
"protocol": "freedom",
"settings": {
"redirect": "127.0.0.1:80"
}
}
]
...
"routing": {
"rules": [
...,
{
"type": "field",
"inboundTag": ["bridge"],
"outboundTag": "web_server_local"
}
]
}
После этого все запросы, пришедшие на 80 порт portal’а, будут переадресованы на 80 порт bridge’а через reverse-прокси подключение.
Если ничего не работает
Если вы пытаетесь получить доступ к локальной сети через reverse proxy, но ничего не работает, и даже на сервере в логах не видно запросов, проверьте настройки клиента. Во многих клиентах по умолчанию запросы на «geoip: private» (то есть как минимум локальные IP-адреса классов A, B и C) отправляются в block.
Из того, с чем столкнулся я: после настройки на bridge XRay запускался, но почему-то не поднимал подключение к portal’у, и в логах была тишина.
На portal же, при попытке сходить куда-то через bridge, в логах были сообщение
v2ray.com/core/app/reverse: failed to process reverse connection > v2ray.com/core/app/reverse: empty worker list
означавшее, что portal-то и не против сходить куда-нибудь через bridge, вот только ни одного подходящего bridge’а к нему не подключилось. Я так и не понял, в чем было дело, в какой-то момент я просто снес конфиг и переписал его заново более внимательно и все заработало.
При правильной настройке при попытке подключиться, на стороне portal в логах будет что-то типа такого:
xray[1856216]: 11.11.11.11:10457 accepted 22.22.22.22:443 [incoming -> portal]
xray[1856216]: proxy/shadowsocks_2022: tunnelling request to tcp:reverse.hellohabr.com:0
xray[1856216]: app/dispatcher: taking detour [portal] for [tcp:reverse.hellohabr.com:0]
xray[1856216]: common/mux: dispatching request to udp:reverse.internal.v2fly.org:0
xray[1856216]: [33.33.33.33]:63786 accepted reverse.hellohabr.com:0 [incoming -> portal]
а на bridge:
xray[648852]: [Info] app/dispatcher: taking detour [outgoing] for [tcp:reverse.hellohabr.com:0]
xray[648852]: [Info] proxy/shadowsocks_2022: tunneling request to tcp:reverse.hellohabr.com:0 via 11.11.11.11:5555
xray[648852]: [Info] transport/internet/tcp: dialing TCP to tcp:[11.11.11.11]:5555
xray[648852]: [Info] common/mux: received request for udp:reverse.internal.v2fly.org:0
Псевдо-VPN?
Сообразительный читатель наверное догадается, что в теории таким образом можно даже сделать какое-то подобие VPN. Если на каждом клиенте настроить и outbound до прокси и bridge, а на прокси для каждого клиента создать portal и соответствующие правила (например, выдумав набор виртуальных IP-адресов и routing rules для них, типа подключения к 10.0.0.1 отправляй на этот портал, а к 10.0.0.2 на другой), то можно соорудить схему, когда клиенты смогут подключаться через прокси друг к другу. Скажу честно — я не пробовал. Может заработает, а может и нет. Если кто-то попробует и получится — расскажите. Если не получится — тоже расскажите. Из явных минусов: конфиг будет страшный, и отлаживать все это дело будет непросто. Плюс на мобильных клиентах (да и в простых десктопных) bridge-функционал просто так не настроить.
Поэтому в случае необходимости подобного, я бы предложил все-таки использовать классические VPN-протоколы, например, AmneziaWG, или OpenVPN завернутый в Cloak, или что-то из TLS VPN, о которых я расскажу в следующей статье.