Skip to content

Настройка Wireguard VPN сервера с маршрутизацией

My Diagram

Проблематика:

Есть два VPS сервера:

  • в Швеции(1cpu, 1G, 10G): для доступа к ресурсам, заблокированным для пользователей РФ
  • в России(2cpu, 2G, 25G): для доступа к домашним ресурсам, потому что домашний сервер за NAT, а арендовать выделенный ip адрес у провайдера по цене vps - не логично

Если на каждом VPS поднимать wireguard сервер, будет крайне неудобно в плане переключения на ноутбуке когда находишься вне дома, когда нужно воспользоваться теми или иными ресурсами.

Задача:

Необходимо сделать так, чтобы при одном настроенном wireguard подключении на ноутбуке были доступны все внутренние ресурсы wireguard, а весь остальной трафик направлялся через заграничного провайдера.

Описание:

У нас есть ноутбук с запущенным клиентом WireGuard, Endpoint A, с которого мы хотим получить доступ в Internet(на схеме).

Чтобы WireGuard-трафик мог дойти от Endpoint A до Интернета в этом сценарии, ему нужно пройти два перехода: один через wg_hub, Host B; и второй через wg_gate Host C. Хотя мы и хотим использовать Host C в качестве шлюза в Internet для Endpoint A, мы этого не хотим для Host B.

ENDPOINT A

На Endpoint A, когда сеть WireGuard активна, мы хотим отправлять весь Интернет-трафик через Host B, поэтому мы настраиваем AllowedIPs = 0.0.0.0/0 для Host B в конфигурации WireGuard Endpoint A:

[Interface]
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
Address = 10.0.1.3/32
DNS = 10.100.1.2, 10.100.1.1

[Peer]
PublicKey = PublicKeyHost-B
AllowedIPs = 0.0.0.0/0
Endpoint = HOSTNAME-B:PORT-B
PersistentKeepalive = 20

Если мы хотим использовать DNS, который нам выдает например мобильный оператор - просто убираем строку DNS = ...

ENDPOINT B

Теперь нужно сделать так, чтобы:

  • весь трафик, который приходит на наш wireguard интерфейс wg0 отправлялся на host C
  • трафик предназначенный для 10.100.1.0.24 (Home-Lan) направлялся в host D

Поэтому нужно настроить

  • для хоста A: AllowedIPs = 10.0.1.3/32
  • для хоста C: AllowedIPs=0.0.0.0/0
  • для хоста D: AllowedIPs = 10.0.1.2/32, 10.100.1.0/24
    if item.wg_gate == true %}
       AllowedIPs=0.0.0.0/0
    

Поскольку мы не хотим, чтобы весь трафик хоста B отправлялся на хост C, а мы хотим отправлять только трафик, который forward через wireguard сеть - поэтому - сконфигурируем маршруты для этого wireguard интерфейса, для этого используем custom routing table (через указание в настройках Table=123 для wg интерфейса, Table = 123 в конфиге wg0.conf указывает что маршруты будут добавляться в таблицу маршрутизации с идентификатором 123 для этого интерфейса WireGuard). По умолчанию маршруты добавляются в таблицу по умолчанию main с идентификатором 0. Когда WireGuard интерфейс активируется, он автоматически добавляет маршруты на основе AllowedIPs https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8

  • используем PreUp команду добавления правила маршрутизации, которое указывает хосту использовать эту таблицу только для трафика приходящего из wireguard интерфейса ( ip rule add iif wg0 table 123 priority 456 )
  • если перед этим правилом не добавить PreUp = ip rule add to 172.31.0.0/16 table main priority 444, то изнутри контейнеров не будет доступа до 10.100.1.2 например, потому что без этого правила пакеты будут уходить через main а возвращаться на table 123 и уходить на 10.0.1.100, а не внутрь докер сети, поэтому мы и добавляем это правило перед ip rule add iif wg0 table 123 priority 456 Не забываем добавить для каждого PreUp - PostDown

Для того чтобы хост B стал доступен (пинг его или SSH например) через wireguard соединение с одной или нескольких точек wg, вместо того, чтобы просто перенаправлять трафик через него - также необходимо добавить один или несколько маршрутов в основную таблицу маршрутизации хоста B, которая позволяет ему знать, как маршрутизировать свой собственный трафик в сеть WireGuard. Потому что маршруты теперь добавляются в table 123, которая только для iff wg0, а в main их нет.

PostUp = ip route add 10.0.1.0/24 dev wg0
PostUp = ip route add 10.100.1.0/24 dev wg0
Но если хост В в пределах сети до которой нужно открыть маршрутом, то можно в интерфейсе wg расширить маску до 24 и оставить только маршрут PostUp = ip route add 10.100.1.0/24 dev wg0 то есть если Address = 10.0.1.1/32 то нужно указать PostUp = ip route add 10.0.1.0/24 dev wg0 а если Address =10.0.1.1/24 то не должен быть PostUp = ip route add 10.0.1.0/24 dev wg0 иначе будет ошибка

[Interface]
PrivateKey = {{ wg_server_privkey }}
Address = {{ wg_ip }}/{{ wg_mask_bits }}
ListenPort = {{ wg_server_port }}
{% if wg_hub %}
Table = 123

# IPv4 forwarding & routing
PreUp = sysctl -w net.ipv4.ip_forward=1
PreUp = ip rule add iif wg0 table 123 priority 456
PostDown = ip rule del iif wg0 table 123 priority 456
PostUp = ip route add 10.0.1.0/24 dev wg0
PostUp = ip route add 10.100.1.0/24 dev wg0
# PostDown = ip route del 10.100.1.0/24 dev wg0
{% endif %}
Т.к ранее был задан для хоста С AllowedIPs=0.0.0.0/0 то в таблице 123 создастся маршрут по умолчанию default dev wg0 scope link. Он указывает, что весь исходящий трафик должен направляться через интерфейс wg0 Т.е вспоминаем, на хосте А был задан маршрут по умолчанию, который повел весь трафик в wg, а на хосте B этот трафик принялся на входящем порту и далее был снова задан маршрут по умолчанию, который дальше повел весь трафик в wg(так как приоритет таблицы маршрутизации 123 есть 456 < 32766(для таблицы main), поэтому маршрут по умолчанию wg а не в таблице main )

Как же ядро выбирает, в какую таблицу отправлять пакеты? Все логично – для этого есть правила.

В нашем случае:
$ ip rule list
0:      from all lookup local
456:    from all iif wg0 lookup 123
32766:  from all lookup main
32767:  from all lookup default
- Число в начале строки – идентификатор правила, - from all – условие, означает пакеты с любых адресов, - iif – имя интерфейса, на который пришел пакет. - lookup указывает в какую таблицу направлять пакет. Если пакет подпадает под несколько правил, то он проходит их все по порядку возрастания идентификатора. Конечно, если пакет подпадет под какую-либо запись маршрутизации, то последующие записи маршрутизации и последующие правила он уже проходить не будет. Мы же применили создание собственных таблиц и правил маршрутизации это и есть policy-routing, он же PBR (policy based routing Просмотреть маршруты в таблице 123
$ ip route show table 123
default dev wg0 scope link
10.0.1.2 dev wg0 scope link
10.0.1.3 dev wg0 scope link
10.0.1.4 dev wg0 scope link
10.0.1.5 dev wg0 scope link
10.0.1.101 dev wg0 scope link
10.0.1.102 dev wg0 scope link
10.100.1.0/24 dev wg0 scope link
Просмотреть маршруты в таблице main
ip route show table main 
or
ip route show 
or
netstat -rn
ip rule add iif wg0 table 123 priority 456 - не создает таблицу 123, создает правило маршрутизации с приоритетом 456 Таблица создается когда туда помещается хотя бы один маршрут: Создаем таблицу с единственным маршрутом: ip route add default via 10.1.0.1 table 120

ENDPOINT C

На wg_gate (host C) Добавим PersistentKeepalive=25 , чтобы - пробивать отверстия через NAT-файрвол "pokes a hole through the NAT firewall" (некоторые NAT-файерволы могут закрывать соединения из-за отсутствия активности трафика. Отправка пустых пакетов (keepalive) помогает сохранять состояние открытого порта на NAT-файрволе, что позволяет данным узлам поддерживать активное соединение.) - eсли между двумя узлами нет активного обмена данными (например, если нет регулярной передачи реального трафика), keepalive-пакеты гарантируют, что соединение остается открытым и готовым к передаче данных в любое время

Весь трафик, который приходит на wireguard маркируем и потом в postrouting для не wg0 интерфейса делаем маскарадинг.

- name: Insert PostUp and PostDown in wg0.conf
  blockinfile:
    path: /etc/wireguard/wg0.conf
    insertafter: '^(DNS|ListenPort|Address).*'
    block: |
      {% if wg_gate == true %}

      # IPv4 forwarding
      PreUp = sysctl -w net.ipv4.ip_forward=1
      # IPv4 masquerading
      PreUp = iptables -t mangle -A PREROUTING -i wg0 -j MARK --set-mark 0x30
      PreUp = iptables -t nat -A POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE
      PostDown = iptables -t mangle -D PREROUTING -i wg0 -j MARK --set-mark 0x30
      PostDown = iptables -t nat -D POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE

      {% endif %}

Проверка

На хосте А запустим и увидим редирект

$ curl google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
...
еще можно проверить через
$ curl 2ip.ru

Debugging

The netstat-nat command display the natted connections on a Linux iptable firewall: netstat-nat -n

To display SNAT connections, run: netstat-nat -S

To display DNAT connections, type: netstat-nat -D

Please note that you may get the following message on the latest version of Linux:

Could not read info about connections from the kernel, make sure netfilter is enabled in kernel or by modules.

Then use the conntrack command: sudo conntrack -L # List/dump sudo conntrack -L -n # Filter source NAT connections sudo conntrack -L -g # Filter destination NAT connections sudo conntrack -L -j # Filter any NAT connection

Особенно важно в туннелях учитывать фрагментацию пакетов TCP при передаче данных через сети с разным MTU, таких как WireGuard, где дополнительные заголовки могут уменьшать эффективный MTU. Вот шаги для правильной настройки MSS:

1. Делаем ``tcpdump -i wg0``

После начальной успешной передачи, сервер продолжает отправлять данные, но клиент перестает отвечать на них.
Сервер многократно пытается отправить те же данные:
host > 10.100.1.3.51068: Flags [.], seq 278:1626, ack 160 host > 10.100.1.3.51068: Flags [.], seq 278:1626, ack 160 host > 10.100.1.3.51068: Flags [.], seq 278:1626, ack 160 ...
Поведение ``iperf3 -c 10.0.1.100``
Connecting to host 10.0.1.1, port 5201 [ 5] local 10.0.1.100 port 53678 connected to 10.0.1.1 port 5201 [ ID] Interval Transfer Bitrate Retr Cwnd [ 5] 0.00-1.00 sec 69.5 KBytes 568 Kbits/sec 2 1.34 KBytes [ 5] 1.00-2.00 sec 0.00 Bytes 0.00 bits/sec 0 1.34 KBytes [ 5] 2.00-3.00 sec 0.00 Bytes 0.00 bits/sec 1 1.34 KBytes

  1. Определить mtu

Linux ping -s 1300 -M do 10.0.1.100 MacOS ping -D -s 1300 10.0.1.100

При 1301 пинг уже не идет без фрагметации, поэтому MTU=1300+8+20=1328 Как определить оптимальный размер MTU

  1. Уменьшаем mtu на клиентах wg ip link set wg0 mtu 1328

  2. Можем посмотреть на пути уменьшает ли кто-то mtu

apt install iputils-tracepath && tracepath $IP