- PVSM.RU - https://www.pvsm.ru -
В данной статье будет показан пример реализации раздельного туннелирования на домашнем маршрутизаторе. Основная идея заключается в том, чтоб часть трафика шла через удаленный VPS-сервер, на котором развернут Xray + xHTTP, а часть через провайдера. Примеры применения данной технологии оставляю на откуп фантазии читателя, от себя лишь скажу, что статья ни в коем случае не предназначена для обхода каких-либо блокировок и написана исключительно в лабораторных целях. Развертывание Xray на также не является предметом обсуждения в этой статье. Просто предположим, что он у вас есть. В целом будет достаточно даже одного ключа.
В качестве подопытного будет использоваться Keenetic Hopper KN-3810 — добротный гигабитный роутер на АРМ процессоре с прошивкой KeeneticOS 5 версии (да-да, даже не Openwrt). На подопытном будет использоваться Entware/opkg, через которые будет поднят x-ray core. В отличии от sing-box, он поддерживает транспорт xHTTP. Раздельное туннелирование буду делать по принципу DNS-based routing, т.к. у меня нет необходимости маршрутизировать конкретные IP.
Немного терминологии кинетика для тех, кто ранее ничем подобным не занимался:
opkg — это пакетный менеджер Openwrt:
Entware — своего рода репозиторий opkg, который можно накатить поверх прошивки для запуска доп.софта;
Xray core — vpn "клиент", поддерживающие известные протоколы;
Первое, чем нужно заняться, это подготовка флешки, на которую будут установлены Entware/OPKG. Минимальный объем от 2ГБ для базовой установки, но желательно выделить от 8 до 32, чтоб разместить репозиторий пакетов и обновлений без переполнения. Не забываем, что на нее же будут сыпаться кэш, логи и прочий мусор, а также флешка должна выдерживать частые записи/удаления без повреждений. Еще один немаловажный факт, который стоит учесть, что для работы OPKG флешку в роутере придется похоронить. Форматировать следует в FAT32 или EXT4. Keenetic рекомендует использовать формат EXT4, поэтому выбираю его:
Вставляем флешку, определяем командой fdisk -l; В данном случае /dev/sda - моя 8-гиговая флешка;
Размонтируем командой umount /dev/sda1;
Форматируем в EX4 командой mkfs.ext4 -L OPKG /dev/sda1;
С помощью команды fsck -f /dev/sda1 проверяем, что все ок, и извлекаем флешку;
Далее необходимо донастроить флешку и установить OPKG на самом Кинетике. Вставляем ее в USB‑порт, и, если всё сделано правильно, роутер сам распознаёт флешку.
Заходим на роутер через веб‑интерфейс и переходим в «Компоненты операционной системы». В случае с версией прошивки 5.0.7 необходимо найти и активировать чекбоксы следующих компонентов:
Поддержка открытых пакетов;
Файловая система Ext (для ext4);
Интерфейс USB;
Поддержка USB-накопителей;
Сервер SSH;
Block-mount;
Если не находится block-mount - это ок. В разных версиях он называется по-разному, но почти всегда этот компонент уже активирован по умолчанию. После обновляем KeeneticOS и ждем перезагрузку.
Для установки Entware переходим в раздел меню "OPKG", выбираем флешку в выпадающем списке, включаем доступ для пользователя и сохраняем;
Затем локально скачиваем wget https://bin.entware.net/mipselsf-k3.4/installer/mipsel-installer.tar.gz; В веб-морде переходим к Applications, выбираем флешку, создаем папку install, закидываем скаченный файл;
Дальше уже гарантированно потребуется иметь возможность реализовать SSH-подключение. Для этого заходим сперва через telnet, вводим креды, затем выполняем на роутере:
service ssh
system configuration save
После этого можно ходить по ssh admin@192.168.1.1;
Самое время активировать opkg, поэтому выполняем команды:
service opkg
system configuration save
После активации OPKG возвращаемся на веб-морду, снова выбираем флешку, жмем пользователя, жмем сохранить. После этого можно перейти в диагностику и проверить журнал логов на процесс установки.
Корректный лог должен выглядеть примерно так:
Возвращаемся к терминалу и переходим в BusyBox Shell с помощью команды exec /opt/bin/sh и скачиваем актуальные индексы пакетов командой /opt/bin/opkg update;
Затем потребуется установить базовый набор пакетов, который нужен для x-ray core:
/opt/bin/opkg install ca-certificates ca-bundle
/opt/bin/opkg install curl wget
/opt/bin/opkg install ip-full
/opt/bin/opkg install iptables
Для установки можно использовать данный скрипт:
cd /opt/tmp
/opt/bin/opkg install unzip
/opt/bin/curl -L -o xray.zip
https://github.com/XTLS/Xray-core/releases/download/v26.2.6/Xray-linux-mips32le.zip
rm -rf /opt/tmp/xray && mkdir -p /opt/tmp/xray
/opt/bin/unzip -o xray.zip -d /opt/tmp/xray
cp -f /opt/tmp/xray/xray /opt/bin/xray
chmod +x /opt/bin/xray
/opt/bin/xray version
Затем положим датники в рабочее место:
mkdir -p /opt/etc/xray
cp -f /opt/tmp/xray/geoip.dat /opt/etc/xray/geoip.dat
cp -f /opt/tmp/xray/geosite.dat /opt/etc/xray/geosite.dat
cp -f /opt/tmp/xray/geoip.dat /opt/bin/geoip.dat
cp -f /opt/tmp/xray/geosite.dat /opt/bin/geosite.dat
ls -lh /opt/bin/geoip.dat /opt/bin/geosite.dat
В данном случае они нужны нам только ради того, чтоб в будущем не словить обидную ошибку по датникам.
Теперь создадим конфиг. Ниже приложен шаблон моего конфига, при желании его можно скопировать, подставив свои значения из ключа.
mkdir -p /opt/etc/xray
cat > /opt/etc/xray/config.json <<'JSON'
{
"log": { "loglevel": "warning" },
"inbounds": [
{
"tag": "tun-in",
"protocol": "tun",
"settings": { "name": "xray0", "mtu": 1500 },
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls", "quic"],
"routeOnly": true
}
}
],
"outbounds": [
{ "tag": "direct", "protocol": "freedom" },
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "ваш домен",
"port": ваш порт xhttp,
"users": [
{ "id": "ваш UUID", "encryption": "none" }
]
}
]
},
"streamSettings": {
"network": "xhttp",
"security": "tls",
"tlsSettings": {
"serverName": "ваш домен",
"alpn": ["h2", "http/1.1"],
"fingerprint": "chrome"
},
"xhttpSettings": {
"host": "ваш домен",
"path": "/ваш домен",
"mode": "auto"
}
}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"ip": ["geoip:google"],
"outboundTag": "proxy"
},
{
"type": "field",
"domain": [
"domain:discord.com",
"domain:discord.gg",
"domain:discordapp.com",
"domain:discordapp.net",
"domain:discordcdn.com",
"domain:discord.media",
"domain:soundcloud.com",
"domain:sndcdn.com",
"domain:chatgpt.com",
"domain:openai.com",
"domain:oaistatic.com",
"domain:oaiusercontent.com",
"domain:auth.openai.com",
"domain:auth0.openai.com",
"domain:whatsapp.com",
"domain:whatsapp.net",
"domain:whatsapp-cdn.net",
"domain:static.whatsapp.net",
"domain:cdn.whatsapp.net",
"domain:youtube.com",
"domain:youtu.be",
"domain:googlevideo.com",
"domain:ytimg.com",
"domain:ggpht.com",
"domain:gvt1.com",
"domain:gvt2.com",
"domain:gvt3.com",
"domain:gvt6.com",
"domain:youtubei.googleapis.com",
"domain:youtube.googleapis.com",
"domain:gstatic.com",
"domain:googleapis.com",
"domain:googleusercontent.com",
"domain:accounts.google.com",
"domain:clients3.google.com",
"domain:clients4.google.com",
"domain:google.com",
"domain:android.com",
"domain:connectivitycheck.android.com",
"domain:mtalk.google.com",
"domain:x.com",
"domain:twitter.com",
"domain:t.co",
"domain:twimg.com",
"domain:facebook.com",
"domain:fb.com",
"domain:facebook.net",
"domain:fbcdn.net",
"domain:fbsbx.com",
"domain:instagram.com",
"domain:cdninstagram.com",
"domain:intel.com",
"domain:netflix.com",
"domain:netflix.net",
"domain:fast.com",
"domain:nflxext.com",
"domain:nflximg.com",
"domain:nflximg.net",
"domain:nflxsearch.net",
"domain:nflxso.net",
"domain:nflxvideo.net"
],
"outboundTag": "proxy"
}
]
}
}
Указание проксируемых доменов
Здесь стоит обратить внимания на директиву "domain", в нее записывается список доменных имен, которые будут проксироваться.
Зачем здесь tun inbound и что он делает
Tun инбаунд заставляет xray создать виртуальный TUN-интерфейс xray0. Это обычный линуксовый L3 интерфейс, ядро считает его сетевой картой, куда можно маршрутизировать пакеты. Дальше политика маршрутизации на роутере направит трафик клиентов в этот интерфейс. Таким образом xray становится своего рода прокладкой между LAN и WAN.
Зачем нужен Sniffing для раздельного туннелирования
Раздельное туннелирование по доменным именам невозможно на уровне L3/L4 (маршрутизация и iptables видят ip/port, но не видят домен). Поэтому мы заставляем xray снифать SNI в Client Hello, host- заголовок в HTTP и домены в QUIC (udp/443), когда это возможно.
Direct & proxy outbounds
У нас 2 выхода: "direct" через провайдера и "proxy" до
Geoip Google
Наличие правила "ip": ["geoip:google"] означает, что любой трафик к Google IP-пулам уходит в туннель даже если домен не снифается. Это может быть полезно для Google-приложений, например, на ТВ, когда мы не знаем, к каким апстримам обращается приложение под капотом.
Кроме того, он выполняет роль страховки от случаев, когда домен не получается определить и вместо срабатывания правила приоритета маршрутизации через провайдера, мы гарантированно отправляем Google IP-пулы в VPN. Да, это расширяет объем туннелированного трафика, но делает поведение google-сервисов менее плавающим.
Почему в списке лишние домены
Как упомянул ранее, для браузера обычно хватает доменного имени, но приложения ходят на api, в картинки и еще много куда. Если не включать CDN, то приложение может стартовать, но не отдавать корректно контент.
Как трафик попадает в xray
Конфиг регулирует только то, как xray делит трафик. Попадание трафика в xray регулируется рядом PBR, об этом более подробно будет описано ниже по тексту.
Первый запуск и проверка интерфейса:
mkdir -p /opt/var/log
killall xray 2>/dev/null
/opt/bin/xray run -c /opt/etc/xray/config.json >/opt/var/log/xray.log 2>&1 &
sleep 1
/opt/sbin/ip link show xray0
tail -n 80 /opt/var/log/xray.log
В случае успеха увидим что-то такое:
Проверяем маршрут по умолчанию:
/opt/sbin/ip -4 route show default
/opt/sbin/ip -4 route show proto kernel scope link
Проверяем, что xray0 в up:
/opt/sbin/ip link show xray0
/opt/sbin/ip -4 route show default
Если отдает какие-либо ошибки, например Device "xray0" does not exist, выполняем скрипт перезапуска:
killall xray 2>/dev/null
mkdir -p /opt/var/log
/opt/bin/xray run -c /opt/etc/xray/config.json >/opt/var/log/xray.log 2>&1 &
sleep 1
/opt/sbin/ip link show xray0 || true
tail -n 120 /opt/var/log/xray.log
Дальше начинается самое интересное - как заставить клиентский трафик из LAN проходить через xray0, но при этом не сломать управление роутером, не умереть из-за NAT и фаервола и не чинить руками после каждого рестарта xray.
Почему наивная маршрутизация ломает интернет
Самая частая ошибка в гайдах - это попытка маршрутизировать по source-подсети:
ip rule add from 192.168.1.0/24 table 100
ip route add default dev xray0 table 100
Это выглядит логично, но на Keenetic ломает все очень быстро. Причина банальна - сам роутер имеет адрес 192.168.1.1 и тоже попадает в 192.168.1.0/24. В итоге DNS/NTP/системные запросы роутера тоже начинают уходить в туннель. Появляется плавающая сеть, веб-морда зависает, SSH отваливается. Правильным подходом будет маркировать (fwmark) только форвардящийся трафик из LAN, а не трафик самого роутера.
Мы строим цепочку:
iptables mangle PREROUTING - помечаем трафик, пришедший из LAN (br0) и идущий не в локальную сеть, меткой 0x1;
ip rule - весь трафик с меткой 0x1 отправляем в таблицу маршрутизации 100;
table 100 - default dev xray0, плюс анти-петля до
Xray получает весь поток в TUN и внутри делит его по следующему принципу: домены из списка в xray, всё остальное в direct. Эта схема позволяет доменный сплит делать на L7 через sniff SNI/host, а L3/L4 оставить системе.
Далее пойдёт “эталонный” набор правил. Каждое из них решает конкретную проблему.
Таблица маршрутизации 100 + ip rule по fwmark
Сначала создаем table 100 и правило, которое будет направлять маркированный трафик в эту таблицу:
VPS_IP="ваш ip"
WAN_IF=$(/opt/sbin/ip -4 route show default | awk '{print $5; exit}')
WAN_GW=$(/opt/sbin/ip -4 route show default | awk '{print $3; exit}')
/opt/sbin/ip rule del priority 10000 2>/dev/null || true
/opt/sbin/ip route flush table 100 2>/dev/null || true
/opt/sbin/ip route replace default dev xray0 table 100
/opt/sbin/ip route replace ${VPS_IP}/32 via ${WAN_GW} dev ${WAN_IF} table 100
/opt/sbin/ip rule add fwmark 0x1 table 100 priority 10000
Почему нужен маршрут до
Это анти-петля, спасающая от того, что трафик до самого VPS-сервера может попытаться уйти в туннель, и получится самопожирающийся цикл.
Проверка:
/opt/sbin/ip route show table 100
/opt/sbin/ip rule | tail -n 20
Это тот нюанс, из-за которого многие решения работают “то да, то нет”, либо вообще не работают. На кинетике по умолчанию есть SNAT/MASQUERADE для LAN. Если не исключить выход в xray0, трафик внутри туннеля может получить не тот src, ответы не вернутся клиенту, и будет ощущение, что всё умерло.
Фиксится так:
/opt/sbin/iptables -t nat -D POSTROUTING -o xray0 -j RETURN 2>/dev/null || true
/opt/sbin/iptables -t nat -I POSTROUTING 1 -o xray0 -j RETURN
Данное правило должно быть первым в POSTROUTING. Проверка:
/opt/sbin/iptables -t nat -L POSTROUTING -n -v --line-numbers | head -n 10
Затем нужно разрешить форвардинг между br0 и xray0. KeeneticOS использует достаточно строгие ACL-цепочки NDM, трафик LAN - tun может быть просто отфильтрован. Поэтому добавляем явные допуски:
/opt/sbin/iptables -D FORWARD -i br0 -o xray0 -j ACCEPT 2>/dev/null || true
/opt/sbin/iptables -D FORWARD -i xray0 -o br0 -j ACCEPT 2>/dev/null || true
/opt/sbin/iptables -I FORWARD 1 -i br0 -o xray0 -j ACCEPT
/opt/sbin/iptables -I FORWARD 1 -i xray0 -o br0 -j ACCEPT
/opt/sbin/iptables -L FORWARD -n -v --line-numbers | head -n 20
Самый безопасный способ не уронить всю сеть - включить маркировку только для одного клиента. В случае каких-либо ошибок отвалится только одно устройство, а не всё, что подключено к вайфаю. Узнаем текущий LAN айпишник, в моем случае это 192.168.1.59, и запускаем скрипт:
LAN_NET="192.168.1.0/24"
CLIENT_IP="192.168.1.59"
/opt/sbin/iptables -t mangle -D PREROUTING -i br0 -s ${CLIENT_IP} ! -d ${LAN_NET} -j MARK --set-mark 0x1 2>/dev/null || true
/opt/sbin/iptables -t mangle -I PREROUTING 1 -i br0 -s ${CLIENT_IP} ! -d ${LAN_NET} -j MARK --set-mark 0x1
/opt/sbin/iptables -t mangle -L PREROUTING -n -v --line-numbers | head -n 15
Если все в порядке, то при хождении по сайтам счетчик pkts/bytes у MARK будет расти. Если не все в порядке, рекомендую пройти чеклист для локализации проблемы:
Проверка, что xray здравствует и tun есть:
pidof xray
/opt/sbin/ip link show xray0
Проверка правил:
/opt/sbin/ip rule | tail -n 30
/opt/sbin/ip route show table 100
Проверка MARK и NAT:
/opt/sbin/iptables -t mangle -L PREROUTING -n -v --line-numbers | head -n 10
/opt/sbin/iptables -t nat -L POSTROUTING -n -v --line-numbers | head -n 10
Если все проверки успешны (Xray жив, xray0 UP, table 100 содержит default dev xray0, MARK и RETURN присутствуют и их счетчики растут), значит транспортная часть схемы исправна и трафик 100% попадает в xray. В этом случае косяк нужно искать либо в самом xray конфиге, либо в доступности VPS-сервера, либо в особенностях конкретного приложения. Если хотя бы один пункт не выполняется - проблема на найденном слое и дальнейшие действия зависят от того, на каком этапе произошел факап.
Дальше - проще. Если все протестировано и один клиент работает, убираем -s CLIENT_IP и маркируем весь LAN:
LAN_NET="192.168.1.0/24"
CLIENT_IP="192.168.1.59"
/opt/sbin/iptables -t mangle -D PREROUTING -i br0 -s ${CLIENT_IP} ! -d ${LAN_NET} -j MARK --set-mark 0x1 2>/dev/null || true
/opt/sbin/iptables -t mangle -D PREROUTING -i br0 ! -d ${LAN_NET} -j MARK --set-mark 0x1 2>/dev/null || true
/opt/sbin/iptables -t mangle -I PREROUTING 1 -i br0 ! -d ${LAN_NET} -j MARK --set-mark 0x1
Поздравляю, на этом этапе раздельное туннелирование применяется ко всем устройствам в сети!
Вы могли заметить странную вещь: все работает... но до первого рестарта xray, после которого интересующие нас ресурсы перестают открываться. Причина не в xray и не в доменах. Причина в одной неочевидной детали работы линукса: xray при рестарте пересоздает интерфейс xray0, маршруты типа default dev xray0 в table 100 могут исчезать (интерфейс то удаляется, то создается заново), а в итоге ip rule и MARK остаются, но table 100 уже не знает куда отправлять трафик.
Именно поэтому мы делаем идемпотентный apply-скрипт + init.d сервис!
cat > /opt/bin/xray-pbr-apply.sh <<'SH'
#!/opt/bin/sh
set -eu
LAN_NET="192.168.1.0/24"
VPS_IP="ваш ip"
MARK="0x1"
TABLE="100"
PRIO="10000"
IP="/opt/sbin/ip"
IPT="/opt/sbin/iptables"
WAN_IF="$($IP -4 route show default | awk '{print $5; exit}')"
WAN_GW="$($IP -4 route show default | awk '{print $3; exit}')"
i=0
while ! $IP link show xray0 >/dev/null 2>&1; do
i=$((i+1))
[ "$i" -ge 20 ] && exit 1
sleep 1
done
if ! $IP rule | grep -q "fwmark $MARK.*lookup $TABLE"; then
$IP rule add fwmark $MARK table $TABLE priority $PRIO
fi
if ! $IP route show table $TABLE | grep -q "^default dev xray0"; then
$IP route replace default dev xray0 table $TABLE
fi
if ! $IP route show table $TABLE | grep -q "^$VPS_IP/32"; then
$IP route replace $VPS_IP/32 via "$WAN_GW" dev "$WAN_IF" table $TABLE
fi
if ! $IPT -t nat -C POSTROUTING -o xray0 -j RETURN 2>/dev/null; then
$IPT -t nat -I POSTROUTING 1 -o xray0 -j RETURN
fi
if ! $IPT -C FORWARD -i br0 -o xray0 -j ACCEPT 2>/dev/null; then
$IPT -I FORWARD 1 -i br0 -o xray0 -j ACCEPT
fi
if ! $IPT -C FORWARD -i xray0 -o br0 -j ACCEPT 2>/dev/null; then
$IPT -I FORWARD 1 -i xray0 -o br0 -j ACCEPT
fi
if ! $IPT -t mangle -C PREROUTING -i br0 ! -d "$LAN_NET" -j MARK --set-mark $MARK 2>/dev/null; then
$IPT -t mangle -I PREROUTING 1 -i br0 ! -d "$LAN_NET" -j MARK --set-mark $MARK
fi
exit 0
SH
chmod +x /opt/bin/xray-pbr-apply.sh
Скрипт проверяет наличие xray0, default dev xray0 в table 100, маршрут до
Затем создадим guard, который будет поднимать xray, если он умер, и запускает apply-скрипт, чтобы вернуть маршруты, если что-то сбросилось
cat > /opt/etc/init.d/S99xray <<'SH'
#!/opt/bin/sh
XRAY="/opt/bin/xray"
CFG="/opt/etc/xray/config.json"
LOG="/opt/var/log/xray.log"
PIDF="/opt/var/run/xray.pid"
GUARDPID="/opt/var/run/xray-guard.pid"
start_xray() {
mkdir -p /opt/var/log /opt/var/run
if [ -f "$PIDF" ] && kill -0 "$(cat "$PIDF")" 2>/dev/null; then
return 0
fi
: > "$LOG"
"$XRAY" run -c "$CFG" >>"$LOG" 2>&1 &
echo $! > "$PIDF"
}
stop_xray() {
if [ -f "$PIDF" ]; then
kill "$(cat "$PIDF")" 2>/dev/null || true
rm -f "$PIDF"
else
killall xray 2>/dev/null || true
fi
}
start_guard() {
if [ -f "$GUARDPID" ] && kill -0 "$(cat "$GUARDPID")" 2>/dev/null; then
return 0
fi
(
while true; do
if ! pidof xray >/dev/null 2>&1; then
start_xray
fi
/opt/bin/xray-pbr-apply.sh >/dev/null 2>&1 || true
sleep 15
done
) &
echo $! > "$GUARDPID"
}
stop_guard() {
if [ -f "$GUARDPID" ]; then
kill "$(cat "$GUARDPID")" 2>/dev/null || true
rm -f "$GUARDPID"
fi
}
case "${1:-}" in
start)
start_xray
/opt/bin/xray-pbr-apply.sh || true
start_guard
;;
stop)
stop_guard
stop_xray
;;
restart)
stop_guard
stop_xray
sleep 1
start_xray
/opt/bin/xray-pbr-apply.sh || true
start_guard
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
SH
chmod +x /opt/etc/init.d/S99xray
/opt/etc/init.d/S99xray start
Проверить работу гварда можно сымитировав проблему, например удалить default route из table 100 и посмотреть, восстановится ли он сам через 15-20 секунд.
/opt/sbin/ip route del default table 100
sleep 20
/opt/sbin/ip route show table 100
Если вдруг у вас в ходу IPv6, часть устройств может уйти по нему мимо
Единственное, чего не удалось победить - это whatsapp. Судя по всему, он использует еще ряд соединений, которые либо идут на голые ip без sni, либо на какие-то неочевидные домены и порты, и текущим доменным сплитом это надежно не закрыть. В остальном получилась вполне себе надежная система.
Автор: acid_yards
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/iptables/446685
Ссылки в тексте:
[1] VPS: https://www.reg.ru/?rlink=reflink-717
[2] Источник: https://habr.com/ru/articles/1010414/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1010414
Нажмите здесь для печати.