mirror of
https://github.com/vvzvlad/trickster-vpn.git
synced 2024-12-26 02:41:00 +03:00
final fixs article and add pics
This commit is contained in:
parent
509bcba1f8
commit
1b8055f985
@ -1,21 +1,3 @@
|
|||||||
Разные IP
|
|
||||||
|
|
||||||
https://www.reg.ru/web-tools/myip
|
|
||||||
https://2ip.ru/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Разные реализации WG для macos:
|
|
||||||
https://github.com/cloudflare/boringtun
|
|
||||||
https://git.zx2c4.com/wireguard-go/about/
|
|
||||||
https://tunsafe.com/user-guide/linux
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---------
|
|
||||||
|
|
||||||
# Разбираемся с WireGuard и делаем свой умный VPN
|
# Разбираемся с WireGuard и делаем свой умный VPN
|
||||||
![](1.png)
|
![](1.png)
|
||||||
|
|
||||||
@ -28,36 +10,42 @@ https://tunsafe.com/user-guide/linux
|
|||||||
|
|
||||||
Поэтому мы сейчас займемся тем, что будем "включать чуть-чуть VPN".
|
Поэтому мы сейчас займемся тем, что будем "включать чуть-чуть VPN".
|
||||||
|
|
||||||
Примечание душнилы: я прекрасно понимаю, что при таком использовании это скорее прокси, а не VPN, но с языком ничего не поделать — если все называют программы для обхода блокировок VPN, то лучшее, что можно тут сделать — постараться привыкнуть и получать удовольствие.
|
|
||||||
Заодно чуть улучшим качество связи с локальными ресурсами: необходимость таскать трафик сначала до VPN вне страны, а потом обратно до сервера внутри ее драматично сказывается если не на скорости, то на задержке точно: даже на проводном интернете пинг в 4мс до яндекса легко превращается в 190мс, а на мобильном интернете — из 80мс в 240. Дополнительный хоп чуть ухудшит ситуацию, но далеко не так драматично.
|
|
||||||
|
|
||||||
Делать мы это будем на основе WireGuard — это относительно новая (разрабатыавется с 2016 года в отличии от OpenVPN и IPsec — первый это двухтысячные, а второй еще раньше) технология VPN, которая была создана, по сути, одним человеком — zx2c4, которого в миру зовут Джейсоном Доненфельдом. Плюсы WG — скорость (особенно для Linux, где он может работать как модуль ядра начиная с Kernel 5.6 и Windows, где модуль для ядра выпустили порядка недели назад), низкие задержки, современная криптография, и простое использование конечным юзером.
|
>Примечание душнилы: я прекрасно понимаю, что при таком использовании это скорее прокси, а не VPN, но с языком ничего не поделать — если все называют программы для обхода блокировок VPN, то лучшее, что можно тут сделать — постараться привыкнуть и получать удовольствие.
|
||||||
|
|
||||||
|
Заодно чуть улучшим качество связи с локальными ресурсами: необходимость таскать трафик сначала до VPN вне страны, а потом обратно до сервера внутри ее драматично сказывается если не на скорости, то на задержке точно: даже на проводном интернете пинг в 4мс до яндекса легко превращается в 190мс, а на мобильном интернете — из 80мс в 240мс. Дополнительный хоп чуть ухудшит ситуацию, но далеко не так драматично.
|
||||||
|
|
||||||
|
Делать мы это будем на основе WireGuard — это относительно новая (разрабатывается с 2016 года в отличии от OpenVPN и IPsec — первый это двухтысячные, а второй еще раньше) технология VPN, которая была создана, по сути, одним человеком — zx2c4, которого в миру зовут Джейсоном Доненфельдом. Плюсы WG — скорость (особенно для Linux, где он может работать как модуль ядра начиная с Kernel 5.6 и Windows, где модуль для ядра выпустили порядка недели назад), низкие задержки, современная криптография, и простое использование конечным юзером.
|
||||||
Ах да, еще UDP. UDP для туннелей это хорошо, потому что у TCP уже есть механизмы, которые позволяют ему работать на неидеальных соединениях, а UDP представляет из себя именно такое соединение. А когда вы засовываете TCP в TCP, то отказываетесь от большей части этих механизмов (инкапсулированный TCP-пакет будет гарантированно доставлен другой стороне, хотя протокол допускает недоставку), но все еще несете весь оверхед вида "хендшейк соединения для отправки хендшейка".
|
Ах да, еще UDP. UDP для туннелей это хорошо, потому что у TCP уже есть механизмы, которые позволяют ему работать на неидеальных соединениях, а UDP представляет из себя именно такое соединение. А когда вы засовываете TCP в TCP, то отказываетесь от большей части этих механизмов (инкапсулированный TCP-пакет будет гарантированно доставлен другой стороне, хотя протокол допускает недоставку), но все еще несете весь оверхед вида "хендшейк соединения для отправки хендшейка".
|
||||||
Не говоря уж о том, что инкапсулировать UDP в TCP — ничуть не лучшая идея, потому что сразу рушит все предположеия всяких скайпов о том, что лучше пропустить пару пакетов, чем уменьшить задержку: каждый UDP пакет в этом случае будет обязательно доставлен и доставлен корретно, не считаясь с тем, сколько это займет времени.
|
Не говоря уж о том, что инкапсулировать UDP в TCP — ничуть не лучшая идея, потому что сразу рушит все предположения всяких скайпов о том, что лучше пропустить пару пакетов, чем уменьшить задержку: каждый UDP пакет в этом случае будет принудительно перепослан и доставлен корректно, не считаясь с тем, сколько это займет времени.
|
||||||
|
|
||||||
Особенно для одинокого пользователя-хакера приятна работа с шифрованием: нет ни необходимости в сертификатах и удостоверяющих центрах, ни в логинах-паролях, все, что нужно — это с тем пиром, с которым хотите установить соедиение, передать друг другу публичные ключи вашего интерфейса WG. Для больших компаний, это, конечно, будет скорее минусом, как и то, что WG — это только базовая часть полноценной большой инфраструктуры VPN.
|
Особенно для одинокого пользователя-хакера приятна работа с шифрованием: нет ни необходимости в сертификатах и удостоверяющих центрах, ни в логинах-паролях, все, что нужно — это с тем пиром, с которым хотите установить соединение, передать друг другу публичные ключи вашего интерфейса WG. Для больших компаний, это, конечно, будет скорее минусом, как и то, что WG — это только базовая часть полноценной большой инфраструктуры VPN.
|
||||||
Но, например, именно WireGuard использовали в Cloudflare для своего WARP (https://blog.cloudflare.com/announcing-warp-plus/, https://blog.cloudflare.com/warp-technical-challenges/), правда, написав его собственную реализацию — boringtun.
|
Но, например, именно WireGuard использовали в Cloudflare для своего WARP (https://blog.cloudflare.com/announcing-warp-plus/, https://blog.cloudflare.com/warp-technical-challenges/), правда, написав его собственную реализацию — boringtun.
|
||||||
|
|
||||||
Еще одним минусом WG является то, что трафик не обфусцирован — DPI может обнаружить трафик WireGuard, так что его можно довольно легко заблокировать (не говоря уж о блокировке UDP совсем, что почти не мешает вебу, но гарантированно ломает работу WireGuard). Для скрытия трафика рекомендуется использовать специализированное ПО — Cloak, Obfsproxy, Shadowsocks, Stunnel, SoftEther, SSTP, или, в конце-концов, простой SSH.
|
Еще одним минусом WG является то, что трафик не обфусцирован — DPI может обнаружить трафик WireGuard, так что его можно довольно легко заблокировать (не говоря уж о блокировке UDP совсем, что почти не мешает вебу, но гарантированно ломает работу WireGuard). Для скрытия трафика рекомендуется использовать специализированное ПО — Cloak, Obfsproxy, Shadowsocks, Stunnel, SoftEther, SSTP, или, в конце-концов, простой SSH.
|
||||||
|
|
||||||
|
|
||||||
Если очень упрощать, ключи работают следующим образом: у нас есть закрытый (приватный) ключ, из которого можно сгенерировать открытый, или публичный. Наоборот — нельзя, из открытого ключа мы получить закрытый никак не можем. После чего, мы можем зашифровать с помощью закрытого ключая какую-то строку, а при помощи открытого — расшифровать ее и тем самым убедиться, что у собеседника точно есть закрытый ключ, а значит, он тот, за кого себя выдает. Таким образом, мы можем без проблем публиковать открытый ключ — он всего лишь позволяет проверить подлинность автора, но не притвориться им. Это как в SSH — публичный ключ лежит на сервере, где его потеря небольшая беда: все что сможет сделать с ним злоумышленник это положить его на свой сервер, чтобы вы к нему могли подключиться с помощью закрытого ключа.
|
Если очень упрощать, ключи работают следующим образом: у нас есть закрытый (приватный) ключ, из которого можно сгенерировать открытый, или публичный. Наоборот — нельзя, из открытого ключа мы получить закрытый никак не можем. После чего, мы можем зашифровать с помощью закрытого ключа какую-то строку, а при помощи открытого — расшифровать ее и тем самым убедиться, что у собеседника точно есть закрытый ключ, а значит, он тот, за кого себя выдает. Таким образом, мы можем без проблем передавать открытый ключ — он всего лишь позволяет проверить подлинность автора, но не притвориться им. Это как в SSH — публичный ключ лежит на сервере, где его потеря небольшая беда: все что сможет сделать с ним злоумышленник это положить его на свой сервер, чтобы вы к нему могли подключиться с помощью закрытого ключа.
|
||||||
Так вот, в WG первый этап подключения заключается в том, что каждая сторона с помощью зашифрованного приватным ключом сообщения доказывает собеседнику, что она именно она: это проверяется публичным ключом.
|
Так вот, в WG первый этап подключения заключается в том, что каждая сторона с помощью зашифрованного приватным ключом сообщения доказывает собеседнику, что она именно она: это проверяется публичным ключом.
|
||||||
Второй этап — это создание симметричных ключей для шифрования самого трафика.
|
Второй этап — это создание с помощью этих ключей и матана симметричных ключей для шифрования самого трафика. Благодаря тому, что расшифровать зашифрованное публичным ключом нельзя без приватного, мы сможем создать ключ для симметричного шифрования и отправить его по защищенному каналу. Этот шаг необходим потому, что симметричное шифрование — это гораздо менее ресурсоемкая операция, и минус у нее только один: необходимость синхронизации ключа у обоих сторон, при том, что перехват ключа третьей стороной ведет к возможности расшифровки трафика. Но эта проблема решается с помощью ассиметричной схемы. Это называется "Протокол Диффи — Хеллмана" — способ защищенного получения общего секретного ключа. В WG используется ECDH — вариация диффи-хеллмана на эллиптических кривых. Первые два этапа в терминах WG называется рукопожатием.
|
||||||
|
После всего этого эти симметричные ключи используются уже для шифрования трафика. Раз в 2 минуты происходит новое рукопожатие, и сессионные ключи меняются.
|
||||||
|
Разумеется, все немного сложнее — например, отправляются не сами ключи, а сгенерированные на основе их эферемные ключи, которые удаляются сразу после операции, и так далее. Заинтересовавшихся отправляю к [краткому описанию криптографии](https://www.wireguard.com/protocol/).
|
||||||
|
А мы же перейдем к более практическим действиям.
|
||||||
|
|
||||||
## Шаг первый: создаем и настраиваем два сервера.
|
## Шаг первый: создаем и настраиваем два сервера.
|
||||||
Один внутри страны — через него будет идти трафик на локальные ресурсы, а второй за границей. Далее я их буду называть **local** и **external**.
|
Один сервер будет внутри страны — через него будет идти трафик на локальные ресурсы, а второй за границей. Далее я их буду называть **local** и **external**.
|
||||||
Идеально, если **local** будет в вашей домашней сети, потому что при этом трафик на локальные ресурсы не будет отличаться от вашего домашнего трафика. Но для этого нужен какой-то хост дома, белый IP и возможность пробросить порт. У меня это виртуалка на домашнем сервер, но навереное, подойдет и малина (не пробовал, ей придется маршрутизировать весь трафик с устройств и держать в памяти ~11к маршрутов). Если дома хоста нет, то можно взять любой сервер у VDS-хостера (vdsina, ruvds), но могут быть проблемы у ресурсов-параноиков, которые блокируют подсети хостеров, полагая, что серверам их ресурс не нужен: на vdsina я такое ловил.
|
Идеально, если **local** будет в вашей домашней сети, потому что при этом трафик на локальные ресурсы не будет отличаться от вашего домашнего трафика. Но для этого нужен какой-то хост дома, белый IP и возможность пробросить порт. У меня это виртуалка на домашнем сервер, но наверное, подойдет и малина (не пробовал, ей придется маршрутизировать весь трафик с устройств и держать в памяти ~11к маршрутов). Если дома хоста нет, то можно взять любой сервер у VDS-хостера (vdsina, ruvds), но могут быть проблемы у ресурсов-параноиков, которые блокируют подсети хостеров, полагая, что серверам их ресурс не нужен: на vdsina я такое ловил.
|
||||||
Внешний сервер можно взять у тех же хостеров VDS, что и выше: у них есть зарубежные площажки, а можно выбрать иностранного хостера. Например, у меня 1984.hosting.
|
Внешний сервер можно взять у тех же хостеров VDS, что и выше: у них есть зарубежные площадки, а можно выбрать иностранного хостера. Например, у меня 1984.hosting.
|
||||||
Считаем, что на обоих серверах у нас Debian 11.
|
Считаем, что на обоих серверах у нас Debian 11.
|
||||||
|
|
||||||
Ставим нужные нам пакеты:
|
Ставим нужные нам пакеты:
|
||||||
```apt update && apt install wireguard iptables ipcalc qrencode curl jq traceroute dnsutils ufw -y```
|
```bash
|
||||||
|
apt update && apt install -y wireguard iptables ipcalc qrencode curl jq traceroute dnsutils ufw
|
||||||
|
```
|
||||||
|
|
||||||
Включаем перенаправление трафика: в этом случае сервер, получив пакет, который предназначается ни одному из его адресов, не отбросит его, а попытается перенаправить в соответствии со своими маршрутами.
|
Включаем перенаправление трафика: в этом случае сервер, получив пакет, который предназначается ни одному из его адресов, не отбросит его, а попытается перенаправить в соответствии со своими маршрутами.
|
||||||
```bash
|
```bash
|
||||||
echo "net.ipv4.ip_forward=1" > > /etc/sysctl.conf```
|
echo "net.ipv4.ip_forward=1" > > /etc/sysctl.conf
|
||||||
echo "net.ipv4.conf.all.forwarding=1" > > /etc/sysctl.conf
|
echo "net.ipv4.conf.all.forwarding=1" > > /etc/sysctl.conf
|
||||||
sysctl -p /etc/sysctl.conf
|
sysctl -p /etc/sysctl.conf
|
||||||
```
|
```
|
||||||
@ -78,10 +66,9 @@ root@trikster-internal:~# wg genkey
|
|||||||
6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=
|
6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=
|
||||||
```
|
```
|
||||||
|
|
||||||
Утилита wg genkey не делает никакой магии, это просто аналог чего-то типа "```echo $RANDOM | md5sum | head -c 32 | base64```", только наверняка более криптостойкое: мы просто генерируем 32 байта случайных значений и представляем их в виде base64.
|
Утилита wg genkey не делает никакой магии, это просто аналог чего-то типа ```echo $RANDOM | md5sum | head -c 32 | base64```, только наверняка более случайное: мы просто генерируем 32 байта случайных значений и представляем их в виде base64.
|
||||||
|
|
||||||
Создаем два конфига:
|
Создаем два конфига. Один на **internal**:
|
||||||
На **internal**:
|
|
||||||
```/etc/wireguard/wg-internal.conf```
|
```/etc/wireguard/wg-internal.conf```
|
||||||
```ini
|
```ini
|
||||||
[Interface]
|
[Interface]
|
||||||
@ -94,7 +81,7 @@ PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $
|
|||||||
PostDown = ip rule del from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table main
|
PostDown = ip rule del from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table main
|
||||||
```
|
```
|
||||||
|
|
||||||
На **external**:
|
Второй на **external**:
|
||||||
```/etc/wireguard/wg-external.conf```
|
```/etc/wireguard/wg-external.conf```
|
||||||
```ini
|
```ini
|
||||||
[Interface]
|
[Interface]
|
||||||
@ -104,7 +91,7 @@ PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5;
|
|||||||
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||||
```
|
```
|
||||||
|
|
||||||
Секция Interface — это настройки конкретного сетевого интерфейса Wireguard, того, что будет виден в ```ip a```. Название интерфейса берется из название текущего файла конфигурации. У одного интерфейса всегда одна ключевая пара: у пиров этого интерфейса одинаковый публичный ключ.
|
Секция **[Interface]** — это настройки конкретного сетевого интерфейса Wireguard, того, что будет виден в ```ip a```. Название интерфейса берется из название текущего файла конфигурации. У одного интерфейса всегда одна ключевая пара: у пиров этого интерфейса одинаковый публичный ключ.
|
||||||
Но никто не мешает, если хочется, сделать под каждого пира отдельный конфиг-файл, и отдельный интерфейс (правда, на сотнях клиентов это будет неудобно).
|
Но никто не мешает, если хочется, сделать под каждого пира отдельный конфиг-файл, и отдельный интерфейс (правда, на сотнях клиентов это будет неудобно).
|
||||||
|
|
||||||
Управляются интерфейсы обычно при помощи утилиты **wg-quick**:
|
Управляются интерфейсы обычно при помощи утилиты **wg-quick**:
|
||||||
@ -115,11 +102,12 @@ PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $
|
|||||||
Именно этим занимается **wg-quick**. Ну еще и настройкой DNS, указанных в конфиге и установкой MTU. Но ничего сложного в этом нет, достаточно сделать "```cat /usr/bin/wg-quick```", чтобы посмотреть на эту логику, и если надо, сделать тоже самое руками.
|
Именно этим занимается **wg-quick**. Ну еще и настройкой DNS, указанных в конфиге и установкой MTU. Но ничего сложного в этом нет, достаточно сделать "```cat /usr/bin/wg-quick```", чтобы посмотреть на эту логику, и если надо, сделать тоже самое руками.
|
||||||
|
|
||||||
|
|
||||||
**Interface-Address** — это IP текущего пира. Вся адерсация в WG статическая. С одной стороны, это упрощает настройку и бутстрап, с другой стороны, усложняет работу, если у вас очень много клиентов.
|
**Interface-Address** — это IP текущего пира. Вся адресация в WG статическая. С одной стороны, это упрощает настройку и бутстрап, с другой стороны, усложняет работу, если у вас очень много клиентов.
|
||||||
**ListenPort** — это UDP-порт для подключения извне. Если не указать, будет прослушивать 51820. Если этот пир будет только подключаться к другим клиентам, можно и не использовать.
|
**ListenPort** — это UDP-порт для подключения извне. Если не указать, будет прослушивать 51820.
|
||||||
**Interface-PostUp** и **PostDown** — это скрипты, выполняющиеся после поднятия и после остановки интерфейса. Есть еще **PreUP** и **PreDown**.
|
**Interface-PostUp** и **PostDown** — это скрипты, выполняющиеся после поднятия и после остановки интерфейса. Есть еще **PreUP** и **PreDown**.
|
||||||
|
|
||||||
Кроме публичных и приватных ключей есть еще опция **PresharedKey**, которая обеспечивает дополнительное шифрование симметричным шифром. Ключ генерируется командой ```wg genpsk``` и кладется в **PresharedKey** в секциях **Peer** на обоих пирах. Неиспользование этой опции не снижает нагрузку по шифровке-расшифровке: если ключ не указан, используется нулевое значение ключа. Для действительного обеспечения пост-квантовой безопасности (невозможности расшифровки данных квантовыми компьютерами) разработчики рекомендуют дополнительный внешний квантово-устойчивый механизм хендшека (например, Microsoft SIDH, который они пиарят именно в таком контексте), чей найденный общий ключ можно использовать в качестве **PresharedKey**.
|
Кроме публичных и приватных ключей есть еще опция **PresharedKey**, которая обеспечивает дополнительное шифрование симметричным шифром. Ключ генерируется командой ```wg genpsk``` и кладется в **PresharedKey** в секциях **Peer** на обоих пирах. Неиспользование этой опции не снижает нагрузку по шифровке-расшифровке: если ключ не указан, используется нулевое значение ключа.
|
||||||
|
Для действительного обеспечения пост-квантовой безопасности (невозможности расшифровки данных квантовыми компьютерами) разработчики рекомендуют дополнительный внешний квантово-устойчивый механизм хендшейка (например, Microsoft SIDH, который они пиарят именно в таком контексте), чей найденный общий ключ можно использовать в качестве **PresharedKey**.
|
||||||
|
|
||||||
Заклинания в PostUp достаточно просты. ````ip route | awk '/default/ {print $5; exit}'```` — это команда для подстановки имени сетевого интерфейса, куда по-умолчанию выполняется маршрутизация: как правило, это тот интерфейс, в который воткнут провайдер или роутер.
|
Заклинания в PostUp достаточно просты. ````ip route | awk '/default/ {print $5; exit}'```` — это команда для подстановки имени сетевого интерфейса, куда по-умолчанию выполняется маршрутизация: как правило, это тот интерфейс, в который воткнут провайдер или роутер.
|
||||||
Таким образом страшная команда превращается просто в ```iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE```, которая представляет собой включение NAT в режиме маскарада: сервер будет отправлять пришедшие ему пакеты пакеты во внешнюю сеть, подменяя в них адрес отправителя на свой, чтобы ответы на эти пакеты тоже приходили ему, а не исходному отправителю.
|
Таким образом страшная команда превращается просто в ```iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE```, которая представляет собой включение NAT в режиме маскарада: сервер будет отправлять пришедшие ему пакеты пакеты во внешнюю сеть, подменяя в них адрес отправителя на свой, чтобы ответы на эти пакеты тоже приходили ему, а не исходному отправителю.
|
||||||
@ -140,10 +128,10 @@ root@:~# ip route | awk '/default/ { print $5 }'
|
|||||||
inet 192.168.88.70/24 brd 192.168.88.255 scope global dynamic enp1s0
|
inet 192.168.88.70/24 brd 192.168.88.255 scope global dynamic enp1s0
|
||||||
```
|
```
|
||||||
И дальше вытаскиваем оттуда адрес, в данном случае 192.168.88.70.
|
И дальше вытаскиваем оттуда адрес, в данном случае 192.168.88.70.
|
||||||
И команда превращается в ```ip rule add from 95.93.219.123 table main``` — это необходимо для сервера **internal**, потому что иначе при активации маршрута 0.0.0.0/0 он начинает пересылать ответы на пакеты, приходящие ему на внешние адреса через туннель WG. Сервер на том конце, конечно, пересылает их по назначению, но тут уже не готов отправитель пакета: он присылает что-то на внешний адрес сервера **internal**, а ответ ему приходит с **external**. Естественно, при включенном rp_filter пакет отбрасывается. В этом случае сервер перестает быть доступен, например, по SSH снаружи, к нему надо коннектиться только по внутреннему IP wireguard-а. Отключать rp_filter у сервера это из пушки по воробьям, а вот дополнительное правило исправляет ситуацию.
|
И команда превращается в ```ip rule add from 95.93.219.123 table main``` — это необходимо для сервера **internal**, потому что иначе при активации маршрута 0.0.0.0/0 он начинает пересылать ответы на пакеты, приходящие ему на внешние адреса через туннель WG. Сервер на том конце, конечно, пересылает их по назначению, но тут уже не готов отправитель пакета: он присылает что-то на внешний адрес сервера **internal**, а ответ ему приходит с **external**. Естественно, при включенном rp_filter пакет отбрасывается. В этом случае сервер перестает быть доступен, например, по SSH снаружи, к нему надо коннектиться только по внутреннему IP wireguard-а. Отключать rp_filter это из пушки по воробьям, а вот дополнительное правило исправляет ситуацию.
|
||||||
|
|
||||||
Продолжим писать конфиг: в него надо добавить секцию **Peer**, чтобы связать их с друг-другом.
|
Продолжим писать конфиг: в него надо добавить секцию **Peer**, чтобы связать их с друг-другом.
|
||||||
Я намеренно не привожу сразу готовые конфиги, потому что хочу показать механизм создания конфигов в ручном режиме — в свое время у меня были проблемы с тем, что я генерировал конфиги утилитами типа ```easy-wg-quick```, которые спрашивают тебя о названии клиента и красиво показывают QR-код прям в консоли, но отнюдь не способствуют пониманию того, как работает WG на самом деле.
|
Я намеренно не привожу сразу готовые конфиги, потому что хочу показать механизм создания конфигов в ручном режиме — в свое время у меня были проблемы с тем, что я генерировал конфиги утилитами типа ```easy-wg-quick``` или веб-интерфейсами, которые спрашивают тебя о названии клиента и красиво показывают QR-код, но отнюдь не способствуют пониманию того, как работает WG на самом деле.
|
||||||
|
|
||||||
Итак, добавляем в каждый по секции **Peer**, для чего генерируем из приватного ключа публичный (вот в pubkey как раз происходит крипто-магия):
|
Итак, добавляем в каждый по секции **Peer**, для чего генерируем из приватного ключа публичный (вот в pubkey как раз происходит крипто-магия):
|
||||||
```bash
|
```bash
|
||||||
@ -200,11 +188,6 @@ PostDown = ip rule del from `ip addr show $(ip route | awk '/default/ { print $5
|
|||||||
[Peer]
|
[Peer]
|
||||||
PublicKey = FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
|
PublicKey = FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
|
||||||
AllowedIPs = 10.20.30.2/32, 0.0.0.0/0
|
AllowedIPs = 10.20.30.2/32, 0.0.0.0/0
|
||||||
|
|
||||||
#mobile-client node
|
|
||||||
[Peer]
|
|
||||||
PublicKey = 26Vhud00ag/bdB9molvSxfBzZTlzdO+aZgrX3ZDncSg=
|
|
||||||
AllowedIPs = 10.20.30.3/32
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -257,7 +240,7 @@ peer: MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Если видим "latest handshake: ... seconds ago" и байты и в **received** и в **sent**, значит, все ок. Если, байты только в **send**, без хендшейка и полученных данных, где-то в ошибка в конфиге или сервера недоступны друг для друга.
|
Если видим "latest handshake: ... seconds ago" и байты и в **received** и в **sent**, значит, все ок. Если байты только в **send**, без хендшейка и полученных данных, где-то в ошибка в конфиге или сервера недоступны друг для друга.
|
||||||
|
|
||||||
Если что-то пошло не так, и отвалился ssh, то достаточно перезагрузить сервер — активные туннели сбросятся.
|
Если что-то пошло не так, и отвалился ssh, то достаточно перезагрузить сервер — активные туннели сбросятся.
|
||||||
Если все хорошо, и доступ к серверам сохранился, ставим туннели в автозапуск:
|
Если все хорошо, и доступ к серверам сохранился, ставим туннели в автозапуск:
|
||||||
@ -292,6 +275,9 @@ HOST: trikster-internal.local Loss% Snt Last Avg Best Wrst StDev
|
|||||||
|
|
||||||
Все хорошо, трафик идет через внешний сервер — сначала на 10.20.30.2, который у нас выходная нода, а потом через его маршрутизаторы.
|
Все хорошо, трафик идет через внешний сервер — сначала на 10.20.30.2, который у нас выходная нода, а потом через его маршрутизаторы.
|
||||||
|
|
||||||
|
У нас получилась примерно такая схема:
|
||||||
|
![](net_scheme_1.png)
|
||||||
|
|
||||||
|
|
||||||
## Шаг третий: добавляем конфиг клиента
|
## Шаг третий: добавляем конфиг клиента
|
||||||
Создаем конфиг клиента, конечного устройства-пользователя VPN. За основу берем ```wg-external.conf```, потому что он такой же точно клиент, который подключается к **internal**, разница только в том, что **external** получает пакеты, а наш клиент будет отправлять.
|
Создаем конфиг клиента, конечного устройства-пользователя VPN. За основу берем ```wg-external.conf```, потому что он такой же точно клиент, который подключается к **internal**, разница только в том, что **external** получает пакеты, а наш клиент будет отправлять.
|
||||||
@ -321,7 +307,7 @@ PersistentKeepalive = 25
|
|||||||
```
|
```
|
||||||
|
|
||||||
Тут у нас добавилась опция **PersistentKeepalive**. Дело в том, что роутеры в цепочке между двумя пирами ничего не знают о сессии WG, а знают только о потоке UDP-пакетов. Для маршрутизации UDP-пакетов за NAT они создают у себя табличку, в которой записывают, кто куда и на какой порт отправил пакет. И если с destination-адреса/порта приходит UPD-пакет, то они определяют, куда его отправить по это таблице, делая вывод, что если сервер B недавно отправил пакет серверу А, то ответ от сервера А на этот же адрес и порт скорее всего надо переслать серверу B.
|
Тут у нас добавилась опция **PersistentKeepalive**. Дело в том, что роутеры в цепочке между двумя пирами ничего не знают о сессии WG, а знают только о потоке UDP-пакетов. Для маршрутизации UDP-пакетов за NAT они создают у себя табличку, в которой записывают, кто куда и на какой порт отправил пакет. И если с destination-адреса/порта приходит UPD-пакет, то они определяют, куда его отправить по это таблице, делая вывод, что если сервер B недавно отправил пакет серверу А, то ответ от сервера А на этот же адрес и порт скорее всего надо переслать серверу B.
|
||||||
А в отличии от TCP в UDP нет никаких договоренностей о поддержании сессии, т.к. нет и самого понятия сессии. WG же построен таким образом, что при отсуствии трафика, попадающего в туннель, не будет и трафика между пирами, только хедшейки раз в две минуты. Опция **PersistentKeepalive** заставлет его посылать пустые пакеты каждые 25 секунд, предовращая потерю маршрута на промежуточных роутерах, потому что иначе возможна ситуация, когда мы будем раз за разом отправить пакеты, а до второго пира они доходить не будут, а он об этом и не будет знать.
|
А в отличии от TCP в UDP нет никаких договоренностей о поддержании сессии, т.к. нет и самого понятия сессии. WG же построен таким образом, что при отсутствии трафика, попадающего в туннель, не будет и трафика между пирами, только хедшейки раз в две минуты. Опция **PersistentKeepalive** заставляет его посылать пустые пакеты каждые 25 секунд, предотвращая потерю маршрута на промежуточных роутерах, потому что иначе возможна ситуация, когда мы будем раз за разом отправить пакеты, а до второго пира они доходить не будут, а он об этом и не будет знать.
|
||||||
|
|
||||||
|
|
||||||
Дальше мы для нашего клиента добавляем еще одну секцию peer в конфиг на **internal**:
|
Дальше мы для нашего клиента добавляем еще одну секцию peer в конфиг на **internal**:
|
||||||
@ -338,8 +324,13 @@ AllowedIPs = 10.20.30.3/32
|
|||||||
Таким же образом создаем конфиги для других клиентов. Если это мобильные устройства, то удобнее показать им QR. Он делается следующим образом: создаем в текущей папке конфиг как обычно, конечно, с новыми ключами и другим IP, какой-нибудь ```wg-moblie-client.conf``` и дальше командой ```qrencode -t ansiutf8 < wg-moblie-client.conf``` показываем прям в консоли QR, который сканируем с телефона.
|
Таким же образом создаем конфиги для других клиентов. Если это мобильные устройства, то удобнее показать им QR. Он делается следующим образом: создаем в текущей папке конфиг как обычно, конечно, с новыми ключами и другим IP, какой-нибудь ```wg-moblie-client.conf``` и дальше командой ```qrencode -t ansiutf8 < wg-moblie-client.conf``` показываем прям в консоли QR, который сканируем с телефона.
|
||||||
Это удобнее копирования файлов, но вам так же никто не мешает скинуть ```wg-moblie-client.conf``` на телефон или вообще ввести значения 7 полей вручную.
|
Это удобнее копирования файлов, но вам так же никто не мешает скинуть ```wg-moblie-client.conf``` на телефон или вообще ввести значения 7 полей вручную.
|
||||||
|
|
||||||
|
Теперь наша схема выглядит следующим образом:
|
||||||
|
![](net_scheme_2.png)
|
||||||
|
|
||||||
В целом, готово: мы только что сделали очень странный двуххоповый VPN. Желаем это отметить и заказать себе пива, открываем сбермаркет..
|
В целом, готово: мы только что сделали очень странный двуххоповый VPN. Желаем это отметить и заказать себе пива, открываем сбермаркет..
|
||||||
"СберМаркет не открывается. Если у вас работает VPN, отключите его". Ах да, мы же с этой проблемой и собирались бороться... Неловко.
|
![](sber.png)
|
||||||
|
|
||||||
|
Ах да, мы же с этой проблемой и собирались бороться... Неловко.
|
||||||
Давайте доделаем.
|
Давайте доделаем.
|
||||||
|
|
||||||
## Шаг четвертый: добавляем регион-зависимую маршрутизацию.
|
## Шаг четвертый: добавляем регион-зависимую маршрутизацию.
|
||||||
@ -412,14 +403,14 @@ root@trikster-internal:~# ipcalc 195.85.234.0-195.85.236.255 |grep -v "deaggrega
|
|||||||
Скрипт на баше выглядит как-то так (я не удержался и добавил туда еще и прогрессбар):
|
Скрипт на баше выглядит как-то так (я не удержался и добавил туда еще и прогрессбар):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/bin/bash
|
|
||||||
function ProgressBar {
|
function ProgressBar {
|
||||||
let _progress=(${1}*100/${2}*100)/100
|
let _progress=(${1}*100/${2}*100)/100
|
||||||
let _done=(${_progress}*4)/10
|
let _done=(${_progress}*4)/10
|
||||||
let _left=40-$_done
|
let _left=40-$_done
|
||||||
_fill=$(printf "%${_done}s")
|
_fill=$(printf "%${_done}s")
|
||||||
_empty=$(printf "%${_left}s")
|
_empty=$(printf "%${_left}s")
|
||||||
printf "\rAddind routes : [${_fill// /#}${_empty// /-}] ${_progress}%%"
|
printf "\rAdd routes to route table (${1}/${2}): [${_fill// /#}${_empty// /-}] ${_progress}%%"
|
||||||
}
|
}
|
||||||
|
|
||||||
#Variables
|
#Variables
|
||||||
@ -439,11 +430,13 @@ cat $file_raw |grep "-" > $file_for_calc
|
|||||||
cat $file_raw |grep -v "-" > $file_processed
|
cat $file_raw |grep -v "-" > $file_processed
|
||||||
for line in $(cat $file_for_calc); do ipcalc $line |grep -v "deaggregate" >> $file_processed; done
|
for line in $(cat $file_for_calc); do ipcalc $line |grep -v "deaggregate" >> $file_processed; done
|
||||||
|
|
||||||
if [ -e $file_user ]; then echo "Add user subnets..."; cat $file_user >> $file_processed; fi
|
if [ -e $file_user ]; then echo "Add user subnets..."; cat $file_user |grep -v "#" >> $file_processed; fi
|
||||||
|
|
||||||
#Flush route table
|
#Flush route table
|
||||||
echo "Flush route table (down and up interface)..."
|
echo "Flush route table (down interface $interface)..."
|
||||||
ifdown $interface && ifup $interface
|
ifdown $interface > /dev/null 2>&1
|
||||||
|
echo "Up interface $interface..."
|
||||||
|
ifup $interface > /dev/null 2>&1
|
||||||
|
|
||||||
#Add route
|
#Add route
|
||||||
routes_count_in_file=`wc -l $file_processed`
|
routes_count_in_file=`wc -l $file_processed`
|
||||||
@ -465,9 +458,33 @@ echo "Routes in routing table: $routes_count"
|
|||||||
0 3 * * mon bash /root/update_ru_routes.sh > /root/update_routes_log.txt 2>&1
|
0 3 * * mon bash /root/update_ru_routes.sh > /root/update_routes_log.txt 2>&1
|
||||||
```
|
```
|
||||||
|
|
||||||
Если вам принудительно надо маршрутизовать какую-то сеть через **internal**, то можно рядом со скриптом создать файлик ```subnets_user_list.txt``` в который поместить список подсетей, тогда они каждый раз будут добавляться к общему списку при обновлении, в bash-скрипте это есть.
|
Если вам принудительно надо маршрутизировать какую-то сеть через **internal**, то можно рядом со скриптом создать файлик ```subnets_user_list.txt``` в который поместить список подсетей, тогда они каждый раз будут добавляться к общему списку при обновлении, в bash-скрипте это есть. Мой, например, выглядит так:
|
||||||
|
```
|
||||||
|
#avito
|
||||||
|
146.158.48.0/21
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#telegram
|
||||||
|
91.108.4.0/22
|
||||||
|
91.108.8.0/22
|
||||||
|
91.108.58.0/23
|
||||||
|
95.161.64.0/20
|
||||||
|
149.154.160.0/21
|
||||||
|
```
|
||||||
|
Первая подсеть — это что-то для приложения авито, которой почему-то не было в RIPE. Дальше подсети для TG, чтобы хоть немного ускорить загрузку фото и видео.
|
||||||
|
|
||||||
|
Проверяем:
|
||||||
|
![](ip.png)
|
||||||
|
Оп, и два разных сервиса показывают нам разные адреса: потому что один хостится где-то внутри россии, а другой — снаружи. Работает!
|
||||||
|
|
||||||
|
Кстати, если у вас **internal** находится в домашней сети, бонусом вы получаете доступ к домашней сети из любого места, где находится устройство со включенным VPN: маршрут 0.0.0.0/0 на устройстве отправляет в VPN весь трафик, а **internal**, замечая трафик в ту подсеть, в которой он находится, отправляет ее в локальный порт, а не в туннель до **external**. Очень удобно: у меня в домашней сети работает сервер с докерами web2rss, ownCloud, navidrome, freshrss, rss-bridge, homeassistant, и мне для получения к ним доступа совершенно не надо замерачиваться с пробросом портов, авторизацией каждого сервера и https.
|
||||||
|
|
||||||
|
Окончательная схема выглядит так:
|
||||||
|
![](net_scheme_3.png)
|
||||||
|
|
||||||
|
|
||||||
## Шаг пятый: настраиваем фаервол
|
## Шаг пятый: настраиваем фаервол
|
||||||
|
Хорошей привычкой и тоном будет закрытие всего ненужного на серверах.
|
||||||
Для начала на обоих серверах редактируем файл ```/etc/default/ufw```, изменяя значение "**DEFAULT_FORWARD_POLICY**" на **ACCEPT**.
|
Для начала на обоих серверах редактируем файл ```/etc/default/ufw```, изменяя значение "**DEFAULT_FORWARD_POLICY**" на **ACCEPT**.
|
||||||
|
|
||||||
Теперь выполняем следующие команды на **internal**:
|
Теперь выполняем следующие команды на **internal**:
|
||||||
@ -498,7 +515,7 @@ ufw enable
|
|||||||
|
|
||||||
## Шаг шестой, бонусный и необязательный: кеширующий защищенный DNS over HTTPS
|
## Шаг шестой, бонусный и необязательный: кеширующий защищенный DNS over HTTPS
|
||||||
Теперь нам нужна еще одна вещь: DNS. Можно, конечно, жить с DNS 1.1.1.1, но надо учитывать две вещи:
|
Теперь нам нужна еще одна вещь: DNS. Можно, конечно, жить с DNS 1.1.1.1, но надо учитывать две вещи:
|
||||||
Трафик на него пойдет через **external**, что автоматически означает задержку порядка 100мс при каждом запросе. Можно, конечно, добавить 1.1.1.1/32 в ```subnets_user_list.txt```, и тогда трафик пойдет через локальную ноду и локальный сервер 1.1.1.1, что уменьшит задержку до 10-20мс, но ваши DNS-запросы будет доступны вашему провайдеру, что в случае локальной ноды может быть для кого-то неприемлимо. Несколькими командами можно легко сделать кеширующий DNS, который еще и будет работать с DNS over HTTPS, а значит, провайдеру будет доступен только сам факт использовани DoH, но не сами запросы. Но это, конечно, не обязательно: у меня **internal** находится в домашней сети, и я просто использую DNS микротика, который находится в той же сети. Но если у вас **internal** сервер это VPS, то можно сделать там и DNS сервер. Использовать будем ```cloudflared```.
|
Трафик на него пойдет через **external**, что автоматически означает задержку порядка 100мс при каждом запросе. Можно, конечно, добавить 1.1.1.1/32 в ```subnets_user_list.txt```, и тогда трафик пойдет через локальную ноду и локальный сервер 1.1.1.1, что уменьшит задержку до 10-20мс, но ваши DNS-запросы будет доступны вашему провайдеру, что в случае локальной ноды может быть для кого-то неприемлемо. Несколькими командами можно легко сделать кеширующий DNS, который еще и будет работать с DNS over HTTPS, а значит, провайдеру будет доступен только сам факт использовании DoH, но не сами запросы. Но это, конечно, не обязательно: у меня **internal** находится в домашней сети, и я просто использую DNS микротика, который находится в той же сети. Но если у вас **internal** сервер это VPS, то можно сделать там и DNS сервер. Использовать будем ```cloudflared```.
|
||||||
|
|
||||||
Добавляем репозитарий:
|
Добавляем репозитарий:
|
||||||
```bash
|
```bash
|
||||||
@ -587,6 +604,16 @@ P.S. особые параноики могут запустить cloudflared
|
|||||||
|
|
||||||
Кстати, в качестве альтернативы можно поставить рядом на сервер [pi-hole](https://github.com/pi-hole/pi-hole/), который делает примерно тоже самое, но еще блокирует рекламу и показывает красивую статистику.
|
Кстати, в качестве альтернативы можно поставить рядом на сервер [pi-hole](https://github.com/pi-hole/pi-hole/), который делает примерно тоже самое, но еще блокирует рекламу и показывает красивую статистику.
|
||||||
|
|
||||||
Пары ключей в статье — действующие, так что вы можете, ничего не исправляя в конфигах (только IP и имена адаптеров), залить их на два своих сервера и клиент и поиграться. Но для боевого применения ключи надо перегенерить, конечно.
|
## Заключение
|
||||||
|
Пары ключей в статье — действующие, так что вы можете, ничего не исправляя в конфигах (только IP **internal**-сервера), залить их на свои сервера и клиент и поиграться. Но для боевого применения ключи надо перегенерить, конечно.
|
||||||
|
Благо, сделать это просто, даже если вам лень разбираться с ручной генерацией ключей — я сделал небольшой bash-скрипт для этого. Достаточно сделать так на **internal**-сервере:
|
||||||
|
|
||||||
Если кто-то захочет это все красиво обернуть в два докера и прикрутить к этому веб-интерфейс (потому что конфиги клиентов все же удобнее создавать в нем) — добро пожаловать в issues на гитхабе: [Trickster VPN](https://github.com/vvzvlad/trickster-vpn).
|
```bash
|
||||||
|
apt install -y git
|
||||||
|
git clone https://github.com/vvzvlad/trickster-vpn.git
|
||||||
|
cd trickster-vpn/config_generator
|
||||||
|
bash generate_cfgs.sh
|
||||||
|
```
|
||||||
|
После чего в папке ```trickster-vpn/config_generator/configs``` появятся конфиги с только что сгенерированными ключами, и останется только скопировать ```wg-internal.conf``` в ```/etc/wireguard/wg-internal.conf```, ```wg-external.conf``` унести на другой сервер, а ```wg-mobile-client.conf``` использовать для ноутбука или телефона. Ну и не забыть о пробросе порта, если **internal** у вас за NAT.
|
||||||
|
|
||||||
|
Если кто-то захочет это все красиво обернуть в два докера и прикрутить к этому один из веб-интерфейсов для WG (потому что конфиги клиентов все же удобнее создавать в нем) — добро пожаловать в issues на гитхабе: [Trickster VPN](https://github.com/vvzvlad/trickster-vpn).
|
||||||
|
BIN
article/ip.png
Normal file
BIN
article/ip.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 625 KiB |
BIN
article/net_scheme_1.png
Normal file
BIN
article/net_scheme_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
article/net_scheme_2.png
Normal file
BIN
article/net_scheme_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
BIN
article/net_scheme_3.png
Normal file
BIN
article/net_scheme_3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
BIN
article/sber.png
Normal file
BIN
article/sber.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 269 KiB |
Loading…
Reference in New Issue
Block a user