В этой статье попробую рассказать как в домашней сети создать еще один шлюз по умолчанию и настроить на нем на выборочную маршрутизацию на основе списка подсетей. Используя в качестве такого списка базу данных геолокации 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
#
255 local
254 main
253 default
0 unspec
#
# local
#
#1 inr.ruhep
50 no-proxy
100 proxy
/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
, пакеты с меткой 0x64
— proxy
. Проверяем содержимое таблиц маршрутизации:
$> 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
Теперь все готово. Спасибо!
HomeMan
И достаточно много написано, что его блокируют "ис каропки". Зачем все эти телодвижения, если ничего работать не будет? Или тут снова ИИ подрабатывает?
dartraiden
Не везде.
Впрочем, достаточно мысленно заменить "WireGuard" на "WireGuard Amnezia" - и ваша претензия будет устранена. На маршрутизацию эта замена никак не повлияет, поскольку всё отличие там лишь в параметрах конфига WG, связанных с обфускацией трафика.
XtouRusX
Подтверждаю, Amnezia работает нормально. Отличия минимальны, но в когда подсовываешь conf файл с сервера клиенту, нужно снести в нем строку с ipv6 и сделать симлинк на resolve.
Правда у меня нет такой сложной маршрутизации. Файлопомойка стоит отдельным ПК в доме в углу. Подключена к роутеру. На нем настроил nat с eth0 на awg0, и указал "глупым" устройствам типа ТВ в качестве шлюза эту файлопомойку. Так же запустил простой прокси(tinyproxy) и в браузере включил расширение "обход блокировок" со своим прокси. В итоге обычный трафик идёт через роутер, а все что нужно через шлюз и на vds.
Осталось ещё торрент траффик с transmission пустить мимо ВПН и будет достаточно.
DK6v Автор
Выбор протокола зависит от условий использования, не так давно можно было взять tun2sock + *byedpi-прокси и все бы работало, но статься не про это.