VPN через I2P: wireguard & i2pd

Дисклеймер. Автор статьи никоим образом не призывает к чему-либо, что противоречит законодательству РФ и других благословенных государств. Юзкейс этой статьи родился в быту: был я, значит, в командировке в одной из стран ближайшего (дружественного) зарубежья, а там wireguard почему-то плохо работал, а без него связь с офисом в Москве встала — удалёнка не работает. Вот и пришлось изобретать…

О чём речь

В этой статье вы узнаете как подключиться к VPN-серверу wireguard через I2P, завернув в VPN весь нужный трафик устройства, при этом приложение I2P (i2pd) будет работать штатно через домашнего интернет-провайдера. Представленная конфигурация описана для Linux-систем, в частности для дистрибутивов, основанных на Debian (с другими принципиальных проблем тоже быть не должно).

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

Сервер

Команды приведены от рута (без sudo).

Устанавливаем wireguard:

apt install wireguard

Конфигурируем wireguard в качестве принимающего сервера. Чтобы не заниматься консольными танцами, можно воспользоваться онлайн-генератором конфигов. Обратите внимание на CIDR (адрес сети), чтобы он не конфликтовал с другими вашими сетями. В примере я использую 10.20.25.0/24. Серверную часть ложим в /etc/wireguard/wg0.conf (строки PostUp и PostDown выбрасываем, это лишнее). Также не забудьте сохранить клиентский конфиг.

В моем случае серверный конфиг рассчитан на одного клиента и выглядит примерно так (можете использовать, если сеть не конфликтует с другими вашими сетями, и не забудьте подставить ключи):

[Interface]
Address = 10.20.25.1/24
ListenPort = 51820
PrivateKey = ***

[Peer]
PublicKey = ***
AllowedIPs = 10.20.25.2/32

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

Поднимаем интерфейс wireguard и добавляем его в автозагрузку:

systemctl start wg-quick@wg0
systemctl enable wg-quick@wg0

Включаем форвардинг трафика. В файле /etc/sysctl.conf раскомментируем строку net.ipv4.ip_forward = 1 (если такой строки нет, просто добавьте ее в начало файла). Применяем изменения командой

sysctl -p

Если видите ошибку о том, что команда не найдена, воспользуйтесь whereis sysctl и вызовите утилиту по полному пути /usr/sbin/sysctl.

Чтобы система умела не только пересылать пакеты, но и делать это корректно между разными сетями, включаем маскарадинг (на Debian 12 в качестве фаервола по умолчанию используется nftables). В конец файла /etc/nftables.conf добавляем

table ip nat {
    chain postrouting {
        type nat hook postrouting priority 100; policy accept;
        iifname "wg0" masquerade
    }
}

Обратите внимание, что эта конфигурация включает маскарадинг только для запросов, которые приходят через интерфейс wireguard (wg0). Чтобы включить маскарадинг во всех направлениях, оставьте в строке только слово masquerade.

На свежей системе nftables.conf примет такой вид:

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
        chain input {
                type filter hook input priority filter;
        }
        chain forward {
                type filter hook forward priority filter;
        }
        chain output {
                type filter hook output priority filter;
        }
}

table ip nat {
    chain postrouting {
        type nat hook postrouting priority 100; policy accept;
        iifname "wg0" masquerade
    }
}

После изменения конфига, перезапускам nftables:

systemtl restart nftables

Базовая настройка системы в качестве VPN-сервера завершена. Теперь переходим к экзотической части.

Устанавливаем i2pd по гайду из доки:

apt-get install apt-transport-https gpg
wget -q -O - https://repo.i2pd.xyz/.help/add_repo | bash -s -
apt-get update
apt-get install i2pd

Создаем серверный туннель для приема подключений через сеть I2P. Файл /etc/i2pd/tunnels.conf очищаем от лишнего и приводим к следующему виду:

[WG-TUNNEL]
type = udpserver
address = 127.0.0.1
host = 127.0.0.1
port = 51820
inport = 51820
inbound.length = 0
inbound.quantity = 1
outbound.length = 0
outbound.quantity = 1
keys = wg-tun.dat

Обратите внимание: приведена конфигурация с туннелями нулевой длины. Это значит, что любой человек или бот, который знает I2P-адрес VPN-туннеля, с легкостью определит сервер, на котором он хостится, т.к. концы входящего и исходящего туннели будут иметь один постоянный IP-адрес.

Если по каким-то причинам вам надо скрыть сервер от вымышленного наблюдателя, установите длину туннелей в один транзитный узел и увеличьте их «ширину», используя эти значения:

inbound.length = 1
inbound.quantity = 16
outbound.length = 1
outbound.quantity = 16

Подробнее про туннели I2P можете почитать тут.

Перезапускаем i2pd и на всякий случай дублируем автозапуск сервиса, который при установке уже был добавлен туда автоматически:

systemctl restart i2pd
systemctl enable i2pd

Последнее, что нам требуется на сервере — узнать I2P-адрес нашего туннеля. Самый простой способ — зайти в веб-интерфейс i2pd. Я покажу как это делается через консольный веб-браузер:

apt install lynx
lynx 127.0.0.1:7070

71bf0afca1fac4ad337c365249ec7439.png

Навигация стрелками на клавиатуре. Выбираем пункт «I2P tunnels» и оттуда копируем адрес *.b32.i2p под заголовком из конфига (WG-TUNNEL в примере выше).

Клиент

Сначала устанавливаем i2pd и wireguard аналогично тому, как это было сделано на сервере. Конфиги пока что не трогаем.

Если сейчас запустить VPN с маршрутом 0.0.0.0/0, то есть в качестве сетевого шлюза системы, i2pd вместе с другими приложениями будет ходить через туннель wireguard. Нюанс в том, что нам надо завернуть трафик wireguard в i2pd, а остальные приложения пускать через WG. Вопрос!

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

Попытки разрешить через фаервол какие-то отдельные порты или адреса для прямого выхода i2pd через домашнего провайдера упрутся в тупик: I2P-роутер общается с огромным количеством случайных адресов и по разным портам. Даже протоколы разные: полноценно используются TCP и UDP.

Решение в том, чтобы организовать изолированное сетевое пространство имен, создать там виртуальный сетевой интерфейс, который будет ходить через физический сетевой адаптер в интернет через домашнего провайдера без всяких VPN. Через специальную виртуальную сеть мы вытащим туннель i2pd в основное сетевое пространство ОС и подключим к нему клиент wireguard. Чтобы вся система была максимально секьюрна, в основном сетевом пространстве имен на сетевом интерфейсе приколотим статический локальный адрес и намеренно не зададим настройки шлюза. Матёрый killswitch получится!

Схема VPN через I2P на Linux

Схема VPN через I2P на Linux

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

Обратите внимание, что в переменной IP_ADDRESS надо указать свободный адрес вашей локальной сети — с ним будет создан изолированный сетевой интерфейс в пространстве имен i2pd, который станет равноценным участником вашей локалки помимо основного интерфейса.

#!/bin/bash
# acetone, 2024

# Set your default gateway settings
INTERFACE="eth0"
IP_ADDRESS="192.168.0.99/24"
GATEWAY="192.168.0.1"

# Nothing below this line should be changed unless you know what you are doing!

# Create i2pd network namespace
ip netns add i2pd_ns
ip netns exec i2pd_ns ip link set lo up

# Create macvlan interface (gateway for i2pd_ns)
ip link add macvlan0 link $INTERFACE type macvlan mode bridge
ip link set macvlan0 netns i2pd_ns

# Activate the macvlan interface in the i2pd namespace
ip netns exec i2pd_ns ip link set macvlan0 up

# Configuring the IP address and route for i2pd_ns
ip netns exec i2pd_ns ip addr add $IP_ADDRESS dev macvlan0
ip netns exec i2pd_ns ip route add default via $GATEWAY dev macvlan0

# Create virtual interfaces for i2pd to communicate with the main system
ip link add bri2pd_external type veth peer name bri2pd_internal
ip link set bri2pd_external up
ip link set bri2pd_internal netns i2pd_ns up
ip addr add 10.10.10.1/30 dev bri2pd_external
ip netns exec i2pd_ns ip addr add 10.10.10.2/30 dev bri2pd_internal

Запускаем скрипт, чтобы создать в системе изолированную сеть и прочие прелести (от рута).

chmod +x ./setup_network.sh
./setup_network.sh

Я использую systemd-сервис, чтобы скрипт отрабатывал всегда при старте системы и без моего участия. Если хотите также, создайте файл /etc/systemd/system/setup-network-ns.service со следующим содержимым (исправьте строку ExecStart):

[Unit]
Description=Setup Network Namespace
After=network.target
Wants=network.target

[Service]
Type=oneshot
User=root
ExecStart=/path/to/your/setup_network.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Когда файл сервиса создан, добавляем его в автозагрузку:

systemctl enable setup-network-ns

Сетевое пространство имен создано. Чтобы мы смогли без прав суперпользователя и ввода пароля запускать в нем приложения, поставим утилиту netns-exec:

apt install git build-essential
git clone --recursive https://github.com/freeacetone/netns-exec
cd netns-exec
make
make install

Финишная прямая! Конфигурируем i2pd и wireguard.

В /etc/i2pd/tunnels.conf убираем лишнее и создаем туннель до сервера (замените значение в строке destination):

[WG-CLIENT]
type = udpclient
address = 10.10.10.2
host = 10.10.10.2
port = 51820
destination = ***.b32.i2p
destinationport = 51820
inbound.length = 1
inbound.quantity = 16
outbound.length = 1
outbound.quantity = 16
keys = transient-vpn

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

inbound.length = 0
inbound.quantity = 1
outbound.length = 0
outbound.quantity = 1

Теперь нужно внести небольшое изменение в файл сервиса i2pd, чтобы i2pd запускался в изолированном сетевом пространстве. В файле /lib/systemd/system/i2pd.service исправляем строку ExecStart, добавляя в ее начало /usr/local/bin/netns-exec i2pd_ns

a6e5f6b5e7ac4e86d3bd3440a3543481.png

systemctl daemon-reload
systemctl restart i2pd

Если все было сделано верно, i2pd работает в отдельной сети и уже предоставляет туннель до VPN сервера. Работу в отдельной сети легко проверить через консольный браузер:

apt install lynx
lynx 127.0.0.1:7070

c4687ae306b33c18107bf76e4ac2aec1.png

Ошибка — это правильно! i2pd работает в изолированном пространстве имен. Если запустить браузер в том же пространстве (i2pd_ns), все будет ок:

0ac3cb5ad83d293b8a462743bee9fa39.png

Наконец, конфигурируем туннель wireguard. Создаем файл /etc/wireguard/wg0.conf, вставляем в него клиентский конфиг (подставьте ключи и замените значение Address в зависимости от конфига на сервере):

[Interface]
PrivateKey = ***
Address = 10.20.25.2/24
DNS = 94.140.14.14

[Peer]
PublicKey = ***
Endpoint = 10.10.10.2:51820
AllowedIPs = 0.0.0.0/0, ::/0

В AllowedIPs важно вставлять IPv6 маршрут по умолчанию (::/0) даже если сервер VPN его не поддерживает — страховка от того, что IPv6 потечет мимо VPN.

Директива «DNS» не будет работать, если в системе не установлен resolvconf. Установить его просто:

apt install resolvconf 

Готово! Поднимаем туннель wireguard и добавляем его в автозагрузку:

systemctl start wg-quick@wg0
systemctl enable wg-quick@wg0

Важно: чтобы настроить киллсвитч (предотвращение утечки пакетов при отключенном VPN), после завершения всех действий, когда убедитесь, что VPN работает, задайте статические настройки вашего основного сетевого интерфейса, убрав gateway.

9e51650c007fc56c612f4f03ae1e45c1.png

Экспириенс

ce4527e09eabcf4f6e06e3394041cb11.jpg

Саспенс — это слово почему-то просится быть первым после «экспириенс» в данном контексте.

Несколько замеров на загрузку большого файла через wget на кабеле 100Мб/сек показали, что при туннелях нулевой длины скорость падает примерно в два-четыре раза с редкими возможными скачками до 80% от реальной пропускной способности канала (верю в светлое будущее, где новые релизы i2pd улучшат показатель). При туннелях с ненулевой длиной стабильный замер практически невозможен: промежуточные узлы — кот в мешке. Видео грузятся и ладно. Стабильным такое подключение назвать нельзя, но и нерабочим — тоже, и это главное. VPN через I2P, Карл!

Возможны ли случайные утечки трафика? При приведенной конфигурации — нет. Сетевое пространство имен для i2pd изолировано от основной системы на уровне ядра ОС, при этом в доступном для пользователя пространстве нет маршрутов по умолчанию кроме VPN-туннеля. Если wireguard по каким-то причинам упадет, ПК останется оффлайн даже при том, что i2pd продолжит штатную работу.

Высокотехнологичный костыль красивее родной ноги.

© Habrahabr.ru