Схема VPN-объекта через Xray и WireGuard с раздельной маршрутизацией
Статья относится к построению корпоративных VPN-каналов между офисами и удалёнными сотрудниками: доступ к внутренним ресурсам компании и служебные подключения инженеров к обслуживаемой инфраструктуре.
Документ описывает схему, где офисный MikroTik подключается к VM srv-vpn по kernel WireGuard, TCP-трафик прозрачно проходит через Xray для доменной маршрутизации, а UDP уходит напрямую через kernel WireGuard на сервер выхода srv-egress.
Итоговая схема
Клиенты офиса
-> gate-office
-> WireGuard wg-srv-vpn
-> srv-vpn: kernel wg-office
-> TCP: nft REDIRECT -> Xray dokodemo-door -> Xray routing/sniffing
-> UDP: nft mark -> kernel route -> wg-egress
-> srv-egress
-> internet
Роли:
gate-office- MikroTik объекта/офиса.srv-vpn- VM с 3x-ui/Xray, принимает объекты и применяет маршрутизацию.srv-egress- сервер выхода, WireGuard-сервер и выход в интернет.gate-nat- внешний NAT-шлюз передsrv-vpn.
Адресация в примере
srv-vpn LAN IP: 10.20.2.114
public/NAT endpoint: gate-02.example.com:55871
office WG network: 10.0.0.0/24
srv-vpn wg-office: 10.0.0.1/24
office MikroTik WG: 10.0.0.2/24
egress WG network: 10.20.3.0/24
srv-egress wg0: 10.20.3.254/24
srv-vpn wg-egress: 10.20.3.8/32
srv-egress endpoint: 203.0.113.60:555
Порты на srv-vpn:
55871/udp kernel WireGuard wg-office
12345/tcp Xray transparent TCP inbound
8443/tcp VLESS Reality inbound для пользователей, если нужен
2053/tcp 3x-ui web panel
2096/tcp 3x-ui subscriptions
Установка 3x-ui / Xray
На чистой VM обычно ставится 3x-ui, который сам кладет Xray runtime и управляет /usr/local/x-ui/bin/config.json.
Проверяйте актуальную команду установки в репозитории 3x-ui перед использованием. Типовой вариант:
bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/master/install.sh)
После установки:
systemctl status x-ui
ls -la /etc/x-ui /usr/local/x-ui /usr/local/x-ui/bin
/usr/local/x-ui/bin/xray-linux-amd64 version
Важные файлы:
/etc/x-ui/x-ui.db база 3x-ui
/usr/local/x-ui/bin/config.json сгенерированный Xray config
/etc/systemd/system/x-ui.service systemd service
Перед изменениями базы всегда делать backup:
cp -a /etc/x-ui/x-ui.db /etc/x-ui/x-ui.db.backup-before-change.$(date +%Y%m%d-%H%M%S)
Настройка сервера выхода srv-egress
srv-egress выступает WireGuard-сервером и NAT-ит клиентов 10.20.3.0/24 в интернет.
Установка:
apt-get update
apt-get install -y wireguard-tools
Пример /etc/wireguard/wg0.conf:
[Interface]
Address = 10.20.3.254/24
ListenPort = 555
PrivateKey = <EGRESS_PRIVATE_KEY>
MTU = 1420
SaveConfig = false
# srv-vpn wg-egress
[Peer]
PublicKey = <SRV_VPN_WG_EGRESS_PUBLIC_KEY>
PresharedKey = <PSK>
AllowedIPs = 10.20.3.8/32
Включить forwarding и NAT:
cat >/etc/sysctl.d/99-wg-forward.conf <<'EOF'
net.ipv4.ip_forward=1
EOF
sysctl --system
Пример nft NAT:
nft add table ip wg_nat
nft 'add chain ip wg_nat postrouting { type nat hook postrouting priority srcnat; policy accept; }'
nft add rule ip wg_nat postrouting ip saddr 10.20.3.0/24 oifname eth0 masquerade
Разрешить forward wg0 -> eth0 в firewall. Если используется UFW:
ufw route allow in on wg0 out on eth0
ufw allow 555/udp
Запуск:
systemctl enable --now wg-quick@wg0
wg show wg0
Подключение srv-vpn к серверу выхода
На srv-vpn:
apt-get update
apt-get install -y wireguard-tools nftables
Пример /etc/wireguard/wg-egress.conf:
[Interface]
Address = 10.20.3.8/32
PrivateKey = <SRV_VPN_WG_EGRESS_PRIVATE_KEY>
MTU = 1420
Table = off
PostUp = ip rule add fwmark 51820 table 51820 || true; ip route replace default dev %i table 51820
PostDown = ip rule del fwmark 51820 table 51820 || true; ip route del default dev %i table 51820 || true
[Peer]
PublicKey = <SRV_EGRESS_PUBLIC_KEY>
PresharedKey = <PSK>
Endpoint = 203.0.113.60:555
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Запуск:
systemctl enable --now wg-quick@wg-egress
wg show wg-egress
ip rule show
ip route show table 51820
Проверка внешнего выхода:
curl --interface wg-egress -4 https://ifconfig.me
Ожидаемо должен вернуться внешний IP srv-egress.
Xray outbound через wg-egress
В 3x-ui нужно заменить outbound, который раньше был VLESS до сервера выхода, на freedom outbound с sockopt.mark = 51820.
Пример outbound:
{
"tag": "VPN-EGRESS-srv-vpn-outbound",
"protocol": "freedom",
"settings": {
"domainStrategy": "AsIs",
"redirect": "",
"noises": []
},
"streamSettings": {
"sockopt": {
"mark": 51820
}
}
}
Linux policy route отправит sockets с mark 51820 через wg-egress.
Проверка:
grep -n "VPN-EGRESS" /usr/local/x-ui/bin/config.json
ip rule show
ip route show table 51820
tcpdump -i wg-egress -nn
Подключение объекта через kernel WireGuard
На srv-vpn объект принимается не Xray WireGuard inbound, а обычным kernel WireGuard.
Важно: если раньше объект был подключен к WG inbound в 3x-ui, можно переиспользовать те же ключи:
secretKeyиз 3x-ui WG inbound = private key сервера.publicKey, который видит MikroTik, должен совпадать сwg pubkey.- peer public key MikroTik остается прежним.
- PSK можно оставить прежним.
Пример /etc/wireguard/wg-office.conf:
[Interface]
Address = 10.0.0.1/24
ListenPort = 55871
PrivateKey = <SRV_VPN_OFFICE_PRIVATE_KEY>
MTU = 1420
Table = off
PostUp = sysctl -w net.ipv4.ip_forward=1 net.ipv4.conf.all.src_valid_mark=1 >/dev/null; nft -f /etc/nftables.d/xray-office-tproxy.nft; nft -f /etc/nftables.d/wg-office-udp-egress.nft
PostDown = nft delete table ip xray_tproxy || true; nft delete table ip wg_office_udp_egress || true; nft delete table ip xray_udp_tproxy || true; nft delete table inet xray_tproxy || true
[Peer]
PublicKey = <OFFICE_MIKROTIK_PUBLIC_KEY>
PresharedKey = <PSK>
AllowedIPs = 10.0.0.2/32
Запуск:
systemctl enable --now wg-quick@wg-office
wg show wg-office
На NAT-шлюзе перед srv-vpn должен быть проброс:
UDP 55871 -> 10.20.2.114:55871
MikroTik объекта
Пример интерфейса:
/interface/wireguard
add name=wg-srv-vpn mtu=1420 listen-port=13231 private-key="<OFFICE_PRIVATE_KEY>"
Адрес:
/ip/address
add address=10.0.0.2/24 interface=wg-srv-vpn
Peer:
/interface/wireguard/peers
add interface=wg-srv-vpn \
name="srv-vpn" \
public-key="<SRV_VPN_OFFICE_PUBLIC_KEY>" \
preshared-key="<PSK>" \
endpoint-address=gate-02.example.com \
endpoint-port=55871 \
allowed-address=0.0.0.0/0 \
persistent-keepalive=20s
Маршрутная таблица для трафика, который надо отправлять через srv-vpn:
/routing/table
add name=test_vless fib
/ip/route
add dst-address=0.0.0.0/0 gateway=10.0.0.1 routing-table=test_vless distance=1
Маркировка трафика клиентов:
/ip/firewall/mangle
add chain=prerouting src-address=10.1.0.0/24 dst-address-list=!LAN-EXCEPTION \
action=mark-routing new-routing-mark=test_vless passthrough=no \
comment="Office clients over srv-vpn"
MSS clamp для TCP:
/ip/firewall/mangle
add chain=forward protocol=tcp tcp-flags=syn out-interface=wg-srv-vpn \
action=change-mss new-mss=1380 passthrough=yes \
comment="Clamp MSS to WG MTU 1420"
add chain=forward protocol=tcp tcp-flags=syn in-interface=wg-srv-vpn \
action=change-mss new-mss=1380 passthrough=yes \
comment="Clamp MSS from WG MTU 1420"
Проверка:
/interface/wireguard/peers/print detail where interface=wg-srv-vpn
/ping address=10.0.0.1 interface=wg-srv-vpn count=3
TCP через Xray
TCP из wg-office перенаправляется в Xray через nft REDIRECT.
Файл /etc/nftables.d/xray-office-tproxy.nft:
table ip xray_tproxy {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
iifname "wg-office" ip protocol tcp counter redirect to :12345
}
}
Xray inbound в шаблоне 3x-ui:
{
"tag": "redirect-office",
"listen": "0.0.0.0",
"port": 12345,
"protocol": "dokodemo-door",
"settings": {
"network": "tcp",
"followRedirect": true
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"],
"metadataOnly": false,
"routeOnly": false
}
}
Так Xray видит TCP-потоки, умеет sniffing TLS SNI / HTTP Host и применяет routing rules.
Проверка:
ss -lntup | grep 12345
nft list table ip xray_tproxy
tcpdump -i wg-office -nn tcp
tcpdump -i wg-egress -nn tcp
UDP через kernel-маршрутизацию
UDP не идет в Xray. Он маркируется и отправляется напрямую через wg-egress, чтобы не ломать звонки и UDP-сервисы.
Файл /etc/nftables.d/wg-office-udp-egress.nft:
table ip wg_office_udp_egress {
chain prerouting {
type filter hook prerouting priority mangle; policy accept;
iifname "wg-office" ip protocol udp counter meta mark set 0x0000ca6c accept
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "wg-egress" ip saddr 10.0.0.0/24 ip protocol udp counter masquerade
}
}
0x0000ca6c = decimal 51820, тот же mark, который уже отправляется в route table 51820.
Проверка DNS/UDP:
tcpdump -i wg-office -nn 'udp and port 53'
tcpdump -i wg-egress -nn 'udp and port 53'
nft list table ip wg_office_udp_egress
Ожидаемо на wg-egress UDP будет виден уже от адреса 10.20.3.8, потому что включен masquerade.
Routing rules Xray
Пример правил:
{
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"inboundTag": ["api"],
"outboundTag": "api"
},
{
"type": "field",
"ip": ["10.0.0.0/8"],
"outboundTag": "direct"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": ["geoip:private"]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": ["bittorrent"]
},
{
"type": "field",
"outboundTag": "direct",
"domain": [
"ext:geosite_RU.dat:ru-available-only-inside",
"regexp:.*\\.su$",
"regexp:.*\\.ru$",
"regexp:.*\\.xn--p1ai$"
]
},
{
"type": "field",
"outboundTag": "direct",
"ip": ["ext:geoip_RU.dat:ru"]
},
{
"type": "field",
"network": "TCP,UDP",
"outboundTag": "VPN-EGRESS-srv-vpn-outbound"
}
]
}
На практике доменные правила работают для TCP, где Xray может sniff TLS/HTTP. UDP в текущей схеме идет напрямую через ядро и не использует доменные правила Xray.
Подключение нового объекта
Для нового MikroTik-объекта:
- Выделить адрес, например
10.0.0.3/32. - Добавить peer в
/etc/wireguard/wg-office.conf:
[Peer]
PublicKey = <NEW_OBJECT_PUBLIC_KEY>
PresharedKey = <NEW_PSK>
AllowedIPs = 10.0.0.3/32
- Применить live:
wg set wg-office peer <NEW_OBJECT_PUBLIC_KEY> \
preshared-key <(printf '%s\n' '<NEW_PSK>') \
allowed-ips 10.0.0.3/32
- На MikroTik объекта настроить WireGuard interface, address и peer на
gate-02.example.com:55871. - Добавить routing table и mangle-правила, какие клиенты должны идти через этот туннель.
- Добавить MSS clamp под MTU 1420:
MTU 1420 -> MSS 1380
Проверка после настройки
На srv-vpn:
systemctl is-active x-ui wg-quick@wg-office wg-quick@wg-egress
wg show wg-office
wg show wg-egress
ss -lntup | grep 12345
ss -lnuap | grep 55871
nft list table ip xray_tproxy
nft list table ip wg_office_udp_egress
ip rule show
ip route show table 51820
Тест TCP:
tcpdump -i wg-office -nn tcp
tcpdump -i wg-egress -nn tcp
С клиента/объекта:
git clone https://github.com/<org>/<repo>.git
ssh -v <host>
curl https://ifconfig.me
Тест UDP:
tcpdump -i wg-office -nn udp
tcpdump -i wg-egress -nn udp
На MikroTik:
/interface/wireguard/peers/print detail
/interface/monitor-traffic wg-srv-vpn once
/ip/firewall/mangle/print stats where action=change-mss
Диагностика MTU
Для MTU 1420 ожидаемый TCP MSS:
1420 - 20 IPv4 - 20 TCP = 1380
Проверка DF ping с MikroTik:
/ping address=10.0.0.1 interface=wg-srv-vpn size=1412 do-not-fragment count=2
/ping address=10.0.0.1 interface=wg-srv-vpn size=1432 do-not-fragment count=2
Ожидаемо:
size=1412 проходит
size=1432 не проходит
Потому что 1412 + 28 = 1440 для ICMP на MikroTik может отличаться по интерпретации размера, но практический потолок должен быть около MTU 1420.
Если есть проблемы:
- временно поставить MTU
1360; - MSS
1320; - повторить
git clone; - смотреть retransmits:
tshark -r capture.pcap -Y 'tcp.analysis.retransmission || tcp.analysis.duplicate_ack'
Откат
Отключить kernel office WG:
systemctl disable --now wg-quick@wg-office
Удалить nft rules:
nft delete table ip xray_tproxy || true
nft delete table ip wg_office_udp_egress || true
Вернуть базу 3x-ui:
systemctl stop x-ui
cp -a /etc/x-ui/x-ui.db.backup-before-kernel-wg-office-tproxy.<timestamp> /etc/x-ui/x-ui.db
systemctl start x-ui
После отката Xray снова будет управлять тем, что было в сохраненной базе.
Что важно помнить
- 3x-ui WireGuard inbound не равен kernel WireGuard. Даже если выключить
No-kernel TUN, Xray WG inbound может работать через gVisor/userspace. - Kernel WG быстрее и стабильнее под большими TCP-потоками.
- TCP через Xray сохраняет доменную маршрутизацию по TLS SNI / HTTP Host.
- UDP в текущей стабильной схеме идет напрямую через ядро, без Xray domain sniffing.
- Если понадобится именно UDP через Xray routing, нужно отдельно доводить UDP TProxy и тестировать на одном клиенте.