Ай Лайн
Связаться
Отдел продаж +7 (812) 385-74-84
Время работы пн-пт с 9:00 до 18:00

Схема 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-объекта:

  1. Выделить адрес, например 10.0.0.3/32.
  2. Добавить peer в /etc/wireguard/wg-office.conf:
[Peer]
PublicKey = <NEW_OBJECT_PUBLIC_KEY>
PresharedKey = <NEW_PSK>
AllowedIPs = 10.0.0.3/32
  1. Применить live:
wg set wg-office peer <NEW_OBJECT_PUBLIC_KEY> \
  preshared-key <(printf '%s\n' '<NEW_PSK>') \
  allowed-ips 10.0.0.3/32
  1. На MikroTik объекта настроить WireGuard interface, address и peer на gate-02.example.com:55871.
  2. Добавить routing table и mangle-правила, какие клиенты должны идти через этот туннель.
  3. Добавить 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 и тестировать на одном клиенте.
Оставить заявку