Прозрачное туннелирование трафика с маршрутизацией на основе геолокации IP-адресов

от автора

В этой статье попробую рассказать как в домашней сети создать еще один шлюз по умолчанию и настроить на нем на выборочную маршрутизацию на основе списка подсетей. Используя в качестве такого списка базу данных геолокации IP-адресов, можно перенаправлять трафик в зависимости от страны назначения.

В моем случае все манипуляции проводились на файловом сервере и свелись к следующим шагам: создаем виртуальный интерфейс и список подсетей, настраиваем маршрутизацию, используем этот интерфейс как шлюз по умолчанию для устройств в домашней сети.

Эту статью сложно назвать полноценной инструкцией, но надеюсь не упустил ничего важного.

Шаг 1. Создаем macvlan интерфейс

Сервер доступен по адресу 192.168.0.5/24 через интерфейс enp8s0.

$> ip address enp8s0: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000     link/ether 18:c0:4d:65:87:3a brd ff:ff:ff:ff:ff:ff     inet 192.168.0.5/24 metric 100 brd 192.168.0.255 scope global dynamic enp8s0  $> ip route default via 192.168.0.1 dev enp8s0 proto dhcp src 192.168.0.5 192.168.0.0/24 dev enp8s0 proto kernel scope link src 192.168.0.5

При помощи macvlan поверх физического интерфейсаenp8s0 создадим виртуальный интерфейс mc0, который будет доступен в том же широковещательном домене, сетевой адрес 192.168.0.3/24 будет назначаться DHCP сервером. Добавим флаг UseRoutes=false, маршрут по умолчанию в таблице main для этого интерфейса не нужен.

/etc/systemd/network/20-wired-mc0.netdev
[NetDev] Name=mc0 Kind=macvlan  [MACVLAN] Mode=bridge

/etc/systemd/network/20-wired-mc0.network
[Match] Name=mc0  [Network] DHCP=ipv4  [DHCP] UseMTU=true UseRoutes=false

В файле настройки интерфейса enp8s0 в секции [Network] добавляем ссылку на новый интерфейс.

/etc/systemd/network/10-wired-enp8s0.network
[Match] Name=enp8s0  [Network] DHCP=ipv4 MACVLAN=mc0  [DHCP] UseMTU=true

$> ip link enp8s0: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000     link/ether 18:c0:4d:65:87:3a brd ff:ff:ff:ff:ff:ff mc0@enp8s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000     link/ether 4a:1a:9c:13:73:ec brd ff:ff:ff:ff:ff:ff

Сейчас для других узлов в локальной сети интерфейсы enp8s0 и mc0 могут быть не отличимы друг от друга, учитывая что маршрутизация пакетов в дальнейшем будет настраиваться в том числе на основе интерфейсов, это может привести к большим проблемам, подробно про причины такого поведения можно прочитать тут.

Если посмотреть на ARP-таблицу на соседнем узле, то можно заметить, что ответ на ARP-запрос приходит от двух интерфейсов, в этом случае возможно состояние гонки.

$> arp  Address                  HWtype  HWaddress           Flags Mask  192.168.0.3              ether   18:c0:4d:65:87:3a   C                     wlan1 192.168.0.5              ether   18:c0:4d:65:87:3a   C                     wlan1  $> tcpdump -l -i wlan1 arp | grep '192.168.0.3' 08:27:10.498966 ARP, Request who-has 192.168.0.3 tell 192.168.0.15 08:27:10.500022 ARP, Reply 192.168.0.3 is-at 18:c0:4d:65:87:3a 08:27:10.500238 ARP, Reply 192.168.0.3 is-at 4a:1a:9c:13:73:ec

Чтобы это исправить изменяем параметры ядра для всех интерфейсов наarp_ignore=1 и arp_announce=2, описание параметров можно найти тут.

$> echo "net.ipv4.conf.all.arp_ignore=1" >> /etc/sysctl.conf $> echo "net.ipv4.conf.all.arp_announce=2" >> /etc/sysctl.conf
$> ip -s -s neigh flush all $> ping 192.168.0.3 OK $> ping 192.168.0.5 OK $> arp -n Address                  HWtype  HWaddress           Flags Mask  192.168.0.3              ether   4a:1a:9c:13:73:ec   C                     wlan1 192.168.0.5              ether   18:c0:4d:65:87:3a   C                     wlan1 … $> tcpdump -l -i wlan1 arp | grep '192.168.0.3' 08:27:46.448933 ARP, Request who-has 192.168.0.3 tell 192.168.0.15 08:27:46.449974 ARP, Reply 192.168.0.3 is-at 4a:1a:9c:13:73:ec 

Совсем другое дело, теперь можно создать VPN-туннель и перейти к маршрутизации.

Шаг 2. Создаем VPN-туннель

В моем случае это WireGuard, про него написано достаточно много. Приведу лишь пример конфигурационных файлов для networkd, шлюз по умолчанию на этом интерфейсе 192.168.2.1/24.

$> ip address wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000     link/none      inet 192.168.2.6/24 scope global wg0
/etc/systemd/network/30-proxy-wg0.netdev
[NetDev] Name=wg0 Kind=wireguard Description=WireGuard tunnel (wg0)  [WireGuard] ListenPort=<listen port> PrivateKey=<private key>  [WireGuardPeer] Endpoint=<host>:<port> PublicKey=<public key> PresharedKey=<preshared key> AllowedIPs=0.0.0.0/0

/etc/systemd/network/30-proxy-wg0.network
[Match] Name=wg0  [Network] Address=192.168.2.6/24 DNS=1.1.1.1

UPD: Выбор VPN-протокола зависит от условий использования, некоторыми провайдерами WireGuard может блокироваться.

Шаг 3. Генерируем список подсетей

Для маршрутизации трафика нужно создать ipset хеш, в моем случае с российскими подсетями, для них маршрутизация меняться не будет, а весь остальной трафик будет перенаправлен в VPN-туннель.

Воспользуемся скриптом из этого комментария, уберем пару строк и получим готовый хеш c нужными подсетями. Чтобы создать новый хеш скрипт необходимо запустить один раз, затем можно сохранить настройки в файл.

/etc/ipset/create-ipset.sh
#!/usr/bin/env bash  # Description:  Create IPSET to filter full countries for all ports and protocols # Syntax:       create-ipset.sh countrycode [countrycode] ...... #               Use the standard locale country codes to get the proper IP list. eg. #               create-ipset.sh cn ru ro # Note:         To get a sorted list of the inserted IPSet IPs for example China list(cn) run the command: #               ipset list cn | sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4 # #############################################################################  # Defining some defaults tempdir="/tmp" sourceURL="http://www.ipdeny.com/ipblocks/data/countries/" # # Verifying that the program 'ipset' is installed if ! (dpkg -l | grep '^ii  ipset' &>/dev/null); then     echo "ERROR: 'ipset' package is not installed and required."     echo "Please install it with the command 'apt-get install ipset' and start this script again"     exit 1 fi [ -e /sbin/ipset ] && ipset="/sbin/ipset" || ipset="/usr/sbin/ipset" # # Verifying the number of arguments if [ $# -lt 1 ]; then     echo "ERROR: wrong number of arguments. Must be at least one."     echo "countries_block.bash countrycode [countrycode] ......"     echo "Use the standard locale country codes to get the proper IP list. eg."     echo "countries_block.bash cn ru ro"     exit 2 fi # # Now load the rules for blocking each given countries and insert them into IPSet tables for country; do     # Read each line of the list and create the IPSet rules     # Making sure only the valid country codes and lists are loaded     if wget -q -P $tempdir ${sourceURL}${country}.zone; then         # Destroy the IPSet list if it exists         $ipset flush $country &>/dev/null         # Create the IPSet list name         echo "Creating and filling the IPSet country list: $country"         $ipset create $country hash:net &>/dev/null         (for IP in $(cat $tempdir/${country}.zone); do             # Create the IPSet rule from each IP in the list             echo -n "$ipset add $country $IP --exist - "             $ipset add $country $IP -exist && echo "OK" || echo "FAILED"         done) >$tempdir/IPSet-rules.${country}.txt         # Delete the temporary downloaded counties IP lists         rm $tempdir/${country}.zone     else         echo "Argument $country is invalid or not available as country IP list. Skipping"     fi done # Dispaly the number of IP ranges entered in the IPset lists echo "--------------------------------------" for country; do     echo "Number of ip ranges entered in IPset list '$country' : $($ipset list $country | wc -l)" done echo "======================================" # #eof

$> create-ipset.sh ru $> ipset test ru ya.ru 213.180.193.56 is in set ru. $> ipset test ru google.com 64.233.165.102 is NOT in set ru.

После перезагрузки восстановлением настроек будет заниматься сервис ipset-persistent.

/etc/systemd/system/ipset-persistent.service
[Unit]  Description=runs ipset restore on boot ConditionFileIsExecutable=/etc/ipset/restore-ipset.sh After=network.target  [Service] Type=forking ExecStart=/etc/ipset/restore-ipset.sh TimeoutSec=0 RemainAfterExit=yes GuessMainPID=no  [Install] WantedBy=multi-user.target

/etc/ipset/restore-ipset.sh
#!/usr/bin/env bash  RULES="/etc/ipset/*.rules" for fname in $RULES; do    /usr/bin/flock /run/.ipset-restore /sbin/ipset restore -! < "$fname" done

Шаг 4. Маркировка и фильтрация трафика

В iptables придется поправить три цепочки: mangle, nat и filter.

$> cat /etc/iptables/00-iptables.rules *mangle -A PREROUTING -i mc0 -j MARK --set-xmark 0x32/0xffffffff -A PREROUTING -i wg0 -j MARK --set-xmark 0x64/0xffffffff -A PREROUTING ! -d 192.168.0.0/16 -i mc0 -m set ! --match-set ru dst -j MARK --set-xmark 0x64/0xffffffff COMMIT *filter -A INPUT -i wg0 ! -p icmp -j DROP -A FORWARD -i mc0 -j ACCEPT -A FORWARD -i wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT COMMIT *nat -A POSTROUTING -s 192.168.0.0/24 -o wg0 -j MASQUERADE COMMIT

Правило в цепочке nat, как можно догадаться, включает NAT на интерфейсе wg0 для пакетов, отправляемых из локальной сети, это позволит избежать настройки маршрутизации на VPN-сервере.

Правила в цепочке mangle каждому пакету добавляют fwmark метку, которую будем использовать для маршрутизации. Пакеты с меткой 0x32 будем направлять через шлюз по умолчанию, а с меткой 0x64 через VPN туннель, в скобках порядковый номер правила:

  • все, что приходит на интерфейс wg0, всегда помечается флагом 0x64(2);

  • пакеты, пришедшие на интерфейс mc0, по умолчанию помечаются как 0x32(1), если же адрес назначения находится за рубежом, то сработает следующее правило и метка маршрутизации будет изменена на 0x64(3), в данном случае порядок правил имеет значение.

Правила в цепочке filter разрешают маршрутизацию пакетов между интерфейсами mc0 и wg0, если политика FORWARD по умолчанию ACCEPT, то эти правила можно пропустить. Запретим входящий трафик непосредственно на интерфейсе wg0, оставим только протокол ICMP.

Важно убедиться, что маршрутизация IP-пакетов на уровне ядра разрешена.

$> sysctl net.ipv4.ip_forward net.ipv4.ip_forward = 1

После перезагрузки восстановлением настроек будет заниматься сервис iptables-persistent.

/etc/systemd/system/iptables-persistent.service
[Unit]  Description=runs iptables restore on boot ConditionFileIsExecutable=/etc/iptables/restore-iptables.sh After=network.target ipset-persistent.service  [Service] Type=forking ExecStart=/etc/iptables/restore-iptables.sh TimeoutSec=0 RemainAfterExit=yes GuessMainPID=no  [Install] WantedBy=multi-user.target

/etc/iptables/restore-iptables.sh
#!/usr/bin/env bash  RULES="/etc/iptables/*.rules" for fname in $RULES; do    /usr/bin/flock /run/.iptables-restore /sbin/iptables-restore -n < $RULES done /usr/bin/flock /run/.iptables-restore /etc/iptables/remove-duplicates.sh

/etc/iptables/remove-duplicates.sh
#!/usr/bin/env bash  RULES=$(mktemp) if [ -f "$RULES" ]; then   /sbin/iptables-save | awk '/^COMMIT$/ { delete x; }; !x[$0]++' > "$RULES"   /sbin/iptables-restore "$RULES"   rm -f "$RULES" fi

Шаг 5. Настройка маршрутизации

Перед тем как добавлять новые маршруты, создадим две таблицы: proxy и no-proxy. Номера IP-таблиц могут не совпадать с fwmark, но так удобнее.

/etc/iproute2/rt_tables
# # reserved values # 255local 254main 253default 0unspec # # local # #1inr.ruhep 50no-proxy 100proxy

/etc/systemd/networkd.conf
[Network] RouteTable=no-proxy:50 RouteTable=proxy:100

Добавляем новые маршруты в таблицы proxy и no-proxy:

/etc/systemd/network/20-wired-mc0.network
[Match] Name=mc0  [Network] DHCP=ipv4  [DHCP] UseMTU=true UseRoutes=false  [Route] Destination=192.168.0.0/24 Scope=link Table=proxy  [Route] Gateway=192.168.0.1 Table=no-proxy  [Route] Destination=192.168.0.0/24 Scope=link Table=no-proxy  [RoutingPolicyRule] FirewallMark=50 Table=no-proxy

/etc/systemd/network/30-proxy-wg0.network
[Match] Name=wg0  [Network] Address=192.168.2.6/24 DNS=1.1.1.1  [Route] Gateway=192.168.2.1 GatewayOnLink=yes Table=proxy  [Route] Destination=192.168.2.0/24 Scope=link Table=proxy  [RoutingPolicyRule] FirewallMark=100 Table=proxy

Теперь пакеты с меткой 0x32 должны использовать таблицу no-proxy, пакеты с меткой 0x64proxy. Проверяем содержимое таблиц маршрутизации:

$> ip rule 0:from all lookup local 32764:from all fwmark 0x64 lookup proxy proto static 32765:from all fwmark 0x32 lookup no-proxy proto static 32766:from all lookup main 32767:from all lookup default
$> ip route show table no-proxy  default via 192.168.0.1 dev mc0 proto static onlink  192.168.0.0/24 dev mc0 proto static scope link  
$> ip route show table proxy default via 192.168.2.1 dev wg0 proto static onlink  192.168.0.0/24 dev mc0 proto static scope link  192.168.2.0/24 dev wg0 proto static scope link

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

#> ip route default via 192.168.0.3 dev wlan1 192.168.0.0/24 dev wlan1 proto kernel scope link src 192.168.0.8  $> nping -c 1 --tcp ya.ru  SENT (0.0546s) TCP 192.168.0.8:55175 > 213.180.193.56:80 S ttl=64 id=65375 iplen=40  seq=1493994850 win=1480  RCVD (0.0698s) TCP 213.180.193.56:80 > 192.168.0.8:55175 SA ttl=55 id=0 iplen=44  seq=377826229 win=42300 <mss 1410> Max rtt: 15.046ms | Min rtt: 15.046ms | Avg rtt: 15.046ms  $> nping -c 1 --tcp google.com SENT (0.0307s) TCP 192.168.0.8:13236 > 64.233.163.100:80 S ttl=64 id=60742 iplen=40  seq=3567496319 win=1480  RCVD (0.1110s) TCP 64.233.163.100:80 > 192.168.0.8:13236 SA ttl=123 id=0 iplen=44  seq=1559691755 win=65535 <mss 1412> Max rtt: 80.170ms | Min rtt: 80.170ms | Avg rtt: 80.170ms

Теперь все готово. Спасибо!


ссылка на оригинал статьи https://habr.com/ru/articles/854112/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *