Заметка о том как создать mesh-сеть Yggdrasil over TLS и убрать публичный пир за Nginx. Кратко, что такое Yggdrasil? Это протокол для создания зашифрованной overlay IPv6 mesh сети поверх локальных и публичных сетей. Нам не надо знать как маршрутизируется трафик, через что подключены узлы сети (wi-fi, ethernet, bluetooth), достаточно чтобы пиры как-то могли подключиться друг к другу.

Зачем использовать TLS и 443 порт? Это будет примитивная маскировка под обычный TLS трафик с указанием в SNI-заголовке левого домена. Этого должно хватить для обхода простых блокировок.

В моём случае я не хочу, чтобы пиры были участниками глобальной Yggdrasil-сети, а образовали мою закрытую mesh-сеть. Один узел будет доступен через интернет и иметь белый IP, остальные будут подключаться как придётся. В идеале, если внешних пиров несколько, их можно выключать и перезагружать не нарушая доступность/связности mesh-сети.

Если хотите стать участником общей сети yggdrasil, со своими сервисами и сайтами (такой интернет в интернете) - укажите хотя бы один публичный пир в настройках ваших клиентов.

Установка

Сначала установим необходимое ПО, для Ubuntu:

sudo apt install yggdrasil
sudo systemctl enable yggdrasil.service

для Arch:

sudo pacman -S yggdrasil
sudo systemctl enable yggdrasil.service

Настройка внешнего пира

Создаем конфиг:

yggdrasil -genconf | sudo tee /etc/yggdrasil/yggdrasil.conf

Меняем Listen в /etc/yggdrasil/yggdrasil.conf на что-то подобное (порт любой):

Listen: ["tls://127.0.0.1:19657"]

Запускаем сервис:

sudo systemctl start yggdrasil.service

Настройка Nginx

На моем сервере помимо yggdrasil работает vless и несколько сайтов, поэтому будем использовать nginx модуль stream и SNI для маршрутизации трафика между сервисами.

В /etc/nginx/nginx.conf добавляем

stream {
    include /etc/nginx/stream-enabled/*;
}

В /etc/nginx/stream-enabled/proxy

map $ssl_preread_server_name $sni_name {
    hostnames;

    www.twitch.tv       yggdrasil;

    # my local services
    example.ru              www;
    *.example.ru            www;

    # xray
    default                 xray;
}

upstream www {
    server 127.0.0.1:7443;
}

upstream xray {
    server 127.0.0.1:8443;
}


upstream yggdrasil {
    server 127.0.0.1:19657;
}


server {
    listen          443;
    proxy_pass      $sni_name;
    ssl_preread     on;
}

Порт 19657 в примере можно заменить на любой другой, но он должен совпадать в Nginx и конфиге Yggdrasil.

Домен www.twitch.tv тоже любой, далее будет использоваться другими пирами в SNI-заголовках для подключению к этому пиру.

Остальные пиры

Также устанавливаем Yggdrasil и создаем конфиг:

sudo apt install yggdrasil
sudo systemctl enable yggdrasil.service
yggdrasil -genconf | sudo tee /etc/yggdrasil/yggdrasil.conf

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

Peers: ["tls://123.4.5.6:443?sni=www.twitch.tv"]

Указываем IP по которому доступен пир. Значение sni должно совпадать с доменом, указанным в map-блоке Nginx на внешнем сервере.

Запускаем сервис:

sudo systemctl start yggdrasil.service

Безопасность

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

Чтобы получить публичный ключ выполняем на каждом узле:

sudo yggdrasilctl getSelf

Build name:             yggdrasil
Build version:          0.5.12
IPv6 address:           202:xxxx:yyyy:zzz:becc:f050:3ac4:c32
IPv6 subnet:            302:xxxx:yyyy:zzz::/64
Routing table size:     2
Public key:             2a28e5ad7e304asdasdsdf74rguydrfga8adc97c8ffc9ed8e0ffsdfsdf8934rfsduyjh

Берем Public key's и добавляем к конфиг каждого пира в массив AllowedPublicKeys, например:

AllowedPublicKeys: [ "key1", "key2", "key3" ]

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

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

$ sudo yggdrasilctl getPeers
         URI            State   Dir                   IP Address                Uptime    RTT     RX      TX    Down    Up      Pr
Cost    Last Error
tls://123.4.5.6:443     Up      Out     202:xxxx:yyyy:zzz:becc:f050:3ac4:2370   15m28s  43.27ms 351.6KB 326.9KB -       -       0
83      -

Статус пиров должен быть Up.

И пингуем узлы сети по IPv6 адресам:

$ ping 202:xxxx:yyyy:zzz:becc:f050:3ac4:2370
PING 202:xxxx:yyyy:zzz:becc:f050:3ac4:2370 (202:xxxx:yyyy:zzz:becc:f050:3ac4:2370) 56 data bytes
64 bytes from 202:xxxx:yyyy:zzz:becc:f050:3ac4:2370: icmp_seq=1 ttl=64 time=89.5 ms
64 bytes from 202:xxxx:yyyy:zzz:becc:f050:3ac4:2370: icmp_seq=2 ttl=64 time=69.8 ms
64 bytes from 202:xxxx:yyyy:zzz:becc:f050:3ac4:2370: icmp_seq=3 ttl=64 time=103 ms
64 bytes from 202:xxxx:yyyy:zzz:becc:f050:3ac4:2370: icmp_seq=4 ttl=64 time=79.7 ms
^C
--- 202:xxxx:yyyy:zzz:becc:f050:3ac4:2370 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3001ms
rtt min/avg/max/mdev = 69.802/85.529/103.108/12.306 ms

В итоге у вас должна получиться overlay сеть, где все пиры имеют IPv6 адреса и видят друг друга, где бы физически они не находились. Теперь, находясь с ноутбуком где-нибудь в кафе с публичным wi-fi, вы сможете прозрачно иметь доступ к любому узлу вашей mesh-сети как если бы находились с ними в одной локалке.