Есть сервер (далее — локальный) с WireGuard-сервером (wg0), к которому подключён клиент (телефон, ноутбук). Есть промежуточный прокси-сервер, доступный из туннеля. Есть внешний прокси-сервер, через который нужно выпускать трафик клиента в интернет.

Задача: завернуть весь TCP-трафик клиента (порты 80 и 443) через цепочку прокси, не затрагивая существующий туннель между серверами.

Схема

Клиент (10.X.X.3)
    │ WireGuard
    ▼
Локальный сервер (10.X.X.2)
    │ REDIRECT tcp 80/443 → :12345
    │ DROP udp 443 (QUIC)
    ▼
3proxy tcppm :12345
    │ parent → 10.X.X.1:3128
    ▼
Промежуточный прокси (10.X.X.1:3128)
    │ parent → 10.Y.Y.2:3128
    ▼
Внешний прокси (10.Y.Y.2:3128)
    │ NAT
    ▼
Интернет

Предварительные требования

  1. WireGuard установлен, wg0 поднят, клиент подключён. В wg0.conf есть peer клиента с AllowedIPs = 10.X.X.3/32.
  2. 3proxy собран из master-ветки (в релизе 1.0.0 из .deb команды transparent нет — её добавили в 1.0.1+). Проверить: 3proxy -? 2>&1 | grep -i transparent. Если пусто — версия старая, transparent не заработает.
  3. ip_forward = 1: sysctl net.ipv4.ip_forward должен вернуть 1.
  4. Известны IP всех участников в туннеле:
    • 10.X.X.3 — клиент
    • 10.X.X.2 — локальный сервер
    • 10.X.X.1 — промежуточный прокси
    • 10.Y.Y.2 — внешний прокси
  5. NAT для подсети туннеля (10.X.X.0/24) настроен на выход через физический интерфейс (enp3s0).

Пошаговая настройка

Шаг 1: Конфиг 3proxy (/etc/3proxy/3proxy.cfg)

nserver 8.8.8.8
nscache 65536
timeouts 1 5 30 60 180 1800 15 60
log /var/log/3proxy/3proxy.log D
rotate 30

auth iponly
allow * 10.X.X.3,192.168.0.0/24,127.0.0.1
parent 1000 connect 10.X.X.1 3128
deny *

proxy -p3128 -a
socks -p1080

transparent
tcppm -e10.X.X.2 12345 127.0.0.1 11111
notransparent

Ключевые моменты:

  • parent 1000 connect 10.X.X.1 3128 — все запросы уходят на промежуточный прокси.
  • transparent + tcppm — приём перенаправленного трафика на порт 12345.
  • -e10.X.X.2 — source IP для исходящих соединений к parent. Должен быть IP локального сервера в туннеле, а не физический (иначе пакеты уходят не туда).
  • proxy -p3128 — обычный HTTP-прокси (для ПК в локальной сети).
  • socks -p1080 — SOCKS5 (опционально).

Шаг 2: Перезапуск 3proxy

systemctl restart 3proxy
systemctl is-active 3proxy
ss -tlnp | grep -E '3128|1080|12345'

Должны слушаться все три порта.

Шаг 3: nftables — REDIRECT (table ip transparent)

table ip transparent {
    chain prerouting {
        type nat hook prerouting priority -100; policy accept;
        iifname "wg0" ip saddr 10.X.X.3 
            ip daddr != { 10.0.0.0/8, 127.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } 
            tcp dport { 80, 443 } counter redirect to :12345
        iifname "wg0" ip saddr 10.X.X.3 udp dport 443 counter drop
    }
}

Ключевые моменты:

  • iifname "wg0" — только трафик из туннеля.
  • ip saddr 10.X.X.3 — только от клиента.
  • ip daddr != { ... } — исключить локальные сети (иначе петля).
  • tcp dport { 80, 443 } — только веб-трафик.
  • redirect to :12345 — в transparent-прокси 3proxy.
  • udp dport 443 drop — блокировка QUIC. Без этого важные сервисы и программы ходят через UDP и игнорируют REDIRECT.

Шаг 4: nftables — NAT (table ip nat)

table ip nat {
    chain postrouting {
        type nat hook postrouting priority 100; policy accept;
        ip saddr 10.X.X.0/24 oifname "enp3s0" masquerade
    }
}

Ключевые моменты:

  • ip saddr 10.X.X.0/24 — вся подсеть туннеля.
  • oifname "enp3s0" — физический интерфейс (заменить на свой).
  • masquerade — подмена source IP на IP сервера.

Шаг 5: UFW

ufw allow in on wg0 to any port 12345 proto tcp
ufw allow in on wg0 to any port 3128 proto tcp
ufw route allow in on wg0
ufw reload

Ключевой момент: после REDIRECT пакеты попадают в INPUT с iifname = wg0 и dport = 12345. UFW должен их пропустить.

Шаг 6: ip_forward

sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf

Почему не работают другие подходы

ПодходПочему не подходит
TPROXY3proxy не ставит флаг IP_TRANSPARENT на сокет. TPROXY — для xray, sing-box.
REDIRECT через iptablesВ Ubuntu 24.04 iptables — обёртка над nftables. Правила уходят в «несовместимую» таблицу.
DNAT на 127.0.0.1:31283proxy на 3128 — обычный прокси, ждёт HTTP-запрос, а не transparent-соединение.
tcppm ... 0 03proxy падает с code 40. Нужны заглушки 127.0.0.1 11111.
Без DROP UDP 443многие важные программы используют QUIC (UDP 443), игнорируя REDIRECT для TCP.

Диагностика

Проверка REDIRECT

nft -a list table ip transparent

Счётчик packets должен расти при трафике от клиента.

Проверка 3proxy

netstat -anp | grep 12345

Должны быть ESTABLISHED от IP клиента.

Проверка parent

curl -x http://10.X.X.1:3128 -s ifconfig.me

Должен вернуть IP внешнего прокси.

Проверка цепочки через локальный 3proxy

curl -x http://127.0.0.1:3128 -s ifconfig.me

Должен вернуть IP внешнего прокси.

Проверка логов 3proxy

tail -20 /var/log/3proxy/3proxy.log*

В логе должны быть строки с байтами в обе стороны (не 0 0):

10.X.X.3:PORT → 10.X.X.1:3128  1234  5678  1  destination_ip

Если видите 0 0 1 — parent недоступен или -e указан неверно.

Проверка QUIC

nft -a list table ip transparent
tcpdump -i wg0 udp port 443 -n

Счётчик у правила с udp dport 443 drop должен расти.

Проверка маршрутизации source IP

ip route get 10.X.X.1 from 192.168.0.200   # покажет, что маршрут идёт не через wg0
ip route get 10.X.X.1 from 10.X.X.2        # покажет, что маршрут идёт через wg0

Автозагрузка

Не через nftables.service (сохраняет весь ruleset и ломает UFW). А через PostUp в wg0.conf:

[Interface]
PrivateKey = ...
Address = 10.X.X.2/32
ListenPort = 51820
PostUp = nft add table ip transparent
PostUp = nft add chain ip transparent prerouting { type nat hook prerouting priority -100 \; policy accept \; }
PostUp = nft add rule ip transparent prerouting iifname "wg0" ip saddr 10.X.X.3 ip daddr != { 10.0.0.0/8, 127.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } tcp dport { 80, 443 } redirect to :12345
PostUp = nft add rule ip transparent prerouting iifname "wg0" ip saddr 10.X.X.3 udp dport 443 drop
PostUp = nft add table ip nat
PostUp = nft add chain ip nat postrouting { type nat hook postrouting priority 100 \; policy accept \; }
PostUp = nft add rule ip nat postrouting ip saddr 10.X.X.0/24 oifname "enp3s0" masquerade
PostDown = nft delete table ip transparent
PostDown = nft delete table ip nat

Тогда при подъёме wg0 правила создаются автоматически.

Ограничения решения

  • Только TCP 80/443. Другие порты (8080, 8443, игры) не редиректятся.
  • Только IPv4. IPv6 не обрабатывается.
  • QUIC отключён. Важные сервисы работают через TCP — чуть медленнее, но работает.
  • Приложения без TCP (некоторые игры, VoIP) — не заработают.
  • DNS не заворачивается. Если нужен — добавить правило для UDP/TCP 53.

Откат

nft delete table ip transparent
nft delete table ip nat
systemctl restart 3proxy

Что проверить в первую очередь, если отвалилось

  1. wg show — поднят ли wg0.
  2. systemctl is-active 3proxy — работает ли 3proxy.
  3. nft list tables — есть ли таблицы transparent и nat.

Если таблиц нет — systemctl restart wg-quick@wg0 (правила создадутся через PostUp).
Если 3proxy упал — systemctl restart 3proxy.
Если ничего не помогает — откатить (nft delete table ip transparent; nft delete table ip nat) и настроить заново по шагам.

Итог

Рабочая конфигурация:

  • 3proxy с transparent + tcppm на отдельном порту (12345)
  • REDIRECT в nftables для TCP 80/443 от клиента
  • DROP UDP 443 (QUIC)
  • NAT masquerade для подсети туннеля
  • UFW разрешает входящие на порты 12345 и 3128
  • ip_forward = 1

Существующий туннель между серверами не затрагивается.