Продолжение статьи о том, как мне удалось организовать прямой VPN-туннель между двумя компьютерами находящимися за NAT'ами провайдеров. В прошлой статье описывался процесс организации соединения с помощью третьей стороны — посредника (арендованный VPS выполняющий роль, что-то типа STUN-сервера и передатчика данных узлов для соединения). В этой статье я расскажу как обошелся без VPS, но посредники остались и ими были STUN-сервер и Яндекс.Диск…

Прочитав комментарии прошлого поста я понял, что главным недостатком реализации было использование посредника — третьей стороны (VPS) которая указывала текущие параметры узла, куда и как подключаться. Учитывая рекомендации использовать настоящий STUN (коих очень много) для определения текущих параметров подключения. Первым делом я решил посмотреть при помощи TCPDump содержимое пакетов при работе STUN-сервера с клиентами и получил совершенно нечитабельное содержимое. Погуглив протокол наткнулся на статью с описанием протокола. Я понял что самостоятельно реализовать запрос к STUN-серверу я не могу и убрал задумку в «далекий ящик».
Недавно мне пришлось устанавливать STUN-сервер на Debian из пакета
В ответ я получил:
Строка со значением
как раз то, что надо! Она отображала текущее состояние для соединения на локальном UDP порту 21234. Но это всего лишь пол дела, встал вопрос как передать эти данные удаленному узлу и организовать VPN-соединение. Использование почтового протокола, а может Telegram?! Вариантов много и решил использовать Яндекс.диск, так как попадалась мне статья о работе Curl через WebDav с Яндекс.диском. Подумав над реализацией я пришел к такой схеме:
Немного подумав, с учетом опыта прошлой статьи, написал скрипт на скорую руку. Нам понадобится:
Собственно сам скрипт:
Обновленный вариант (для корректной работы время должно быть сихронизировано):
Для работы скрипта нужно:
На удаленном узле произвести всё тоже самое, указать соответствующий внутренний IP-адрес туннеля и ID-соединения.
Для автозапуска скрипта при включении я использую команду «nohup /<путь до скрипта>/vpn10.sh nZbVGBuX5dtturD > /var/log/vpn10.log 2>/dev/null &» содержащиюся в файле /etc/rc.local
Скрипт работает, проверен на Ubuntu (18.04, 19.10, 20.04) и Debian 9. В качестве передатчика можно использовать любой другой сервис, но для опыта я использовал Яндекс.диск.
В процессе экспериментов было обнаружено, что некоторые типы NAT провайдеров не позволяют организовать соединение. В основном у сотовых операторов, где заблокированы торренты.
Планирую доработать в плане:
Да будет IPv6 в каждом доме!
Обновлено! Последние файлы и DEB-пакет тут — yandex.disk

Введение
Прочитав комментарии прошлого поста я понял, что главным недостатком реализации было использование посредника — третьей стороны (VPS) которая указывала текущие параметры узла, куда и как подключаться. Учитывая рекомендации использовать настоящий STUN (коих очень много) для определения текущих параметров подключения. Первым делом я решил посмотреть при помощи TCPDump содержимое пакетов при работе STUN-сервера с клиентами и получил совершенно нечитабельное содержимое. Погуглив протокол наткнулся на статью с описанием протокола. Я понял что самостоятельно реализовать запрос к STUN-серверу я не могу и убрал задумку в «далекий ящик».
Теория
Недавно мне пришлось устанавливать STUN-сервер на Debian из пакета
и в зависимостях я увидел пакет stun-client, но как-то не придал этому значения. Но позже я вспомнил про пакет stun-client и решил разобраться как он работает, погуглив и пояндексив я получил:# apt install stun-server
# apt install stun-client # stun stun.ekiga.net -p 21234 -v
В ответ я получил:
STUN client version 0.97
Opened port 21234 with fd 3
Opened port 21235 with fd 4
Encoding stun message:
Encoding ChangeRequest: 0
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 4
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
About to send msg of len 28 to 216.93.246.18:3478
Received stun message: 92 bytes
MappedAddress = <Мой IP>:2885
SourceAddress = 216.93.246.18:3478
ChangedAddress = 216.93.246.17:3479
Unknown attribute: 32800
ServerName = Vovida.org 0.98-CPC
Received message of type 257 id=1
Encoding stun message:
Encoding ChangeRequest: 0
About to send msg of len 28 to 216.93.246.17:3478
Encoding stun message:
Encoding ChangeRequest: 4
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 0
About to send msg of len 28 to <Мой IP>:2885
Received stun message: 28 bytes
ChangeRequest = 0
Received message of type 1 id=11
Encoding stun message:
Encoding ChangeRequest: 0
About to send msg of len 28 to 216.93.246.17:3478
Encoding stun message:
Encoding ChangeRequest: 4
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
About to send msg of len 28 to 216.93.246.18:3478
Received stun message: 92 bytes
MappedAddress = <Мой IP>:2885
SourceAddress = 216.93.246.17:3479
ChangedAddress = 216.93.246.18:3478
Unknown attribute: 32800
ServerName = Vovida.org 0.98-CPC
Received message of type 257 id=10
Encoding stun message:
Encoding ChangeRequest: 4
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 4
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 4
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 4
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 4
About to send msg of len 28 to 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
About to send msg of len 28 to 216.93.246.18:3478
test I = 1
test II = 0
test III = 0
test I(2) = 1
is nat = 1
mapped IP same = 1
hairpin = 1
preserver port = 0
Primary: Independent Mapping, Port Dependent Filter, random port, will hairpin
Return value is 0x000006
Строка со значением
MappedAddress = <Мой IP>:2885
как раз то, что надо! Она отображала текущее состояние для соединения на локальном UDP порту 21234. Но это всего лишь пол дела, встал вопрос как передать эти данные удаленному узлу и организовать VPN-соединение. Использование почтового протокола, а может Telegram?! Вариантов много и решил использовать Яндекс.диск, так как попадалась мне статья о работе Curl через WebDav с Яндекс.диском. Подумав над реализацией я пришел к такой схеме:
- Сигнализировать о готовности узлов к установке соединения наличием определенного файла с временной меткой на Яндекс.диске;
- Если узлы готовы, то получать текущие параметры от STUN-сервера;
- Выгружать текущие параметры на Яндекс.диск;
- Проверять наличие и считывать параметры удаленного узла из файла на Яндекс.диске;
- Установка соединения с удаленным узлом с помощью OpenVPN.
Практика
Немного подумав, с учетом опыта прошлой статьи, написал скрипт на скорую руку. Нам понадобится:
# apt install openvpn stun-client curl
Собственно сам скрипт:
Первоначальный вариант
Для работы скрипта нужно:
На удаленном узле произвести всё тоже самое за исключением генерации secret.key и ID-соединения, они должны быть идентичными.
# cat vpn8.sh
#!/bin/bash ######################## Задаем цветной текст ### WARN='\033[37;1;41m' # END='\033[0m' # RED='\033[0;31m' # ${RED} # GREEN='\033[0;32m' # ${GREEN} # ################################################# ####################### Проверяем наличие необходымих приложений ######################################################### al="echo readlink dirname grep awk md5sum shuf nc curl sleep openvpn cat stun" ch=0 for i in $al; do which $i > /dev/null || echo -e "${WARN}Для работы необходим $i ${END}"; which $i > /dev/null || ch=1; done if (( $ch > 0 )); then echo -e "${WARN}Ой, отсутствуют необходимые для корректной работы приложения${END}"; exit; fi ####################################################################################################################### if [[ $1 == '' ]]; then echo -e "${WARN}Введите идентификатор соединения (любое уникальное слово, должно быть одинаковое с двух сторон!) ${END} \t ${GREEN}Для запуска в автоматическом режиме при включении компьютера можно прописать в /etc/rc.local строку nohup /<путь к файлу>/vpn8.sh > /var/log/vpn8.log 2>/dev/hull & ${END}"; exit; fi ABSOLUTE_FILENAME=`readlink -f "$0"` # полный путь до скрипта DIR=`dirname "$ABSOLUTE_FILENAME"` # каталог в котором лежит скрипт ############################### Проверка наличия секретного ключа ################################## key="$DIR/secret.key" if [ ! -f "$key" ]; then echo -e "${WARN}Секретный ключ VPN-соединения не найден, для генерации ключа выполните: \ openvpn --genkey --secret secret.key Внимание: ключ используется для авторизации и должен \ быть одинаковым с двух сторон!!!${END} # ls -l secret.key -rw------- 1 root root 637 ноя 27 11:12 secret.key # chmod 600 secret.key"; exit; fi ######################################################################################################################## ABSOLUTE_FILENAME=`readlink -f "$0"` # полный путь до скрипта DIR=`dirname "$ABSOLUTE_FILENAME"` # каталог в котором лежит скрипт name=$(uname -n | md5sum | awk '{print $1}') vpn=$(echo $1 | md5sum | awk '{print $1}') stun="stun.ekiga.net" # STUN сервер username="Yandex" # Логин от Яндекс.диска password="Password" # Пароль от Яндекс.диска localport=`shuf -i 20000-65000 -n 1` # генерация локального порта echo "$(date) Создаю папку на Яндекс.диске" curl -X MKCOL --user "${username}:${password}" https://webdav.yandex.ru/vpn-$vpn echo "$(date) Очищаю папку от всякого мусора" for i in `curl --silent --user "$username:$password" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></\n/g' | grep "d:displayname" | sed 's/d:displayname//g' | sed 's/>//g' | sed 's/<//' | sed 's/\///g' | grep -v $(date +%Y-%m-%d-%H-%M)`; do echo "$(date) Delete: $i" curl -X DELETE --user "${username}:${password}" https://webdav.yandex.ru/vpn-$vpn/$i done until [ $c ];do until [[ $b ]]; do echo "$(date) Проверяю папку" date=`date +%Y-%m-%d-%H-%M` mydata=`curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></>\n</g' | grep $name | grep $date | grep "d:displayname"` if [[ -z $mydata ]]; then echo "$(date) Файл готовности создан" echo "$date" > "/tmp/$date-$name-ready.txt" curl -T "/tmp/$date-$name-ready.txt" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$date-$name-ready.txt else echo "$(date) Файл готовности уже существует - $date" fi remote=`curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></>\n</g' | grep -v $name | grep $date | grep "d:displayname"` if [[ -z $remote ]]; then echo -e "$(date) ${RED} Удаленный узел не готов ${END}" echo "$(date) Жду" sleep 20 else echo -e "$(date) ${GREEN} Удаленный узел готов ${END}" b=1 a='' fi done until [ $a ]; do echo "$(date) Подключение и получение данных от STUN сервера: $stun" mydata=`stun $stun -p $localport -v 2>&1 | grep MappedAddress | sort | uniq` echo -e "$(date) ${GREEN}Мои данные соединения: $mydata${END}" echo "$mydata" > "$DIR/mydata" echo "$(date) Загрузка данных на Яндекс.диск" curl -T "$DIR/mydata" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$name.txt echo "$(date) Получение файла данных удаленного узла" filename=$(curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></\n/g' | grep "d:displayname>" | grep "txt" | grep -v "$name" | grep -v "ready" | sed 's|.*d:displayname>||' | sed 's/</ /g' | awk '{print $1}') echo "$(date) Чтение файла данных удаленного узла: $filename" address=$(curl --silent --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$filename | sort | uniq | head -n1 | sed 's/:/ /g') echo "$(date) Определение IP-адреса и порта" ip=$(echo "$address" | awk '{print $3}') port=$(echo "$address" | awk '{print $4}') if [[ -n "$ip" && -n "$port" ]]; then echo -e "$(date) ${GREEN} Соединение $ip $port ${END}" openvpn --remote $ip --rport $port --lport $localport \ --proto udp --dev tap --float --auth-nocache --verb 3 --mute 20 \ --ifconfig 10.45.54.2 255.255.255.252 \ --secret "$DIR/secret.key" \ --auth SHA256 --cipher AES-256-CBC \ --ncp-disable --ping 10 --ping-exit 30 \ --comp-lzo yes echo -e "$(date) ${WARN} Соединение разорвано${END}" a=1 b='' else a=1 b='' fi done done
Для работы скрипта нужно:
- Скопировать в буфер и вставить в редактор, например:
# nano vpn8.sh - указать логин и пароль от Яндекс.диска.
- в поле "--ifconfig 10.45.54.(1 или 2) 255.255.255.252" указать внутренний IP-адрес интерфейса
- cоздать secret.key командой:
# openvpn --genkey --secret secret.key - сделать скрипт исполняемым:
# chmod +x vpn8.sh - запустить скрипт:
# ./vpn8.sh nZbVGBuX5dtturD
где nZbVGBuX5dtturD — ID-соединения сгенерированный тут
На удаленном узле произвести всё тоже самое за исключением генерации secret.key и ID-соединения, они должны быть идентичными.
Обновленный вариант (для корректной работы время должно быть сихронизировано):
cat vpn10.sh
#!/bin/bash stuns="stun.sipnet.ru stun.ekiga.net" # Список STUN серверов через пробел username=" Login " # Логин от Яндекс.диска password=" Password " # Пароль от Яндекс.диска intip="10.23.22.1" # IP-адрес внутреннего интерфейса WARN='\033[37;1;41m' END='\033[0m' RED='\033[0;31m' GREEN='\033[0;32m' al="ip echo readlink dirname grep awk md5sum openssl sha256sum shuf curl sleep openvpn cat stun" ch=0 for i in $al; do which $i > /dev/null || echo -e "${WARN}Для работы необходим $i ${END}"; which $i > /dev/null || ch=1; done if (( $ch > 0 )); then echo -e "${WARN}Ой, отсутствуют необходимые для корректной работы приложения${END}"; exit; fi if [[ $1 == '' ]]; then echo -e "${WARN}Введите идентификатор соединения (любое уникальное слово, должно быть одинаковое с двух сторон!) ${END} \t ${GREEN}Для запуска в автоматическом режиме при включении компьютера можно прописать в /etc/rc.local строку nohup /<путь к файлу>/vpn10.sh > /var/log/vpn10.log 2>/dev/hull & ${END}" exit fi ABSOLUTE_FILENAME=`readlink -f "$0"` # полный путь до скрипта DIR=`dirname "$ABSOLUTE_FILENAME"` # каталог в котором лежит скрипт key="$DIR/secret.key" until [[ -n "$iftosrv" ]] do echo "$(date) Определяю сетевой интерфейс"; iftosrv=`ip route get 8.8.8.8 | head -n 1 | sed 's|.*dev ||' | awk '{print $1}'` sleep 5 done timedatectl name=$(uname -n | md5sum | awk '{print $1}') vpn=$(echo $1 | md5sum | awk '{print $1}') echo "$(date) Создаю папку на Яндекс.диске" curl -X MKCOL --user "${username}:${password}" https://webdav.yandex.ru/vpn-$vpn echo "$(date) ID на диске: $vpn" until [ $c ];do echo "$(date) Очищаю папку от всякого мусора" for i in `curl --silent --user "$username:$password" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></\n/g' | grep "d:displayname" | sed 's/d:displayname//g' | sed 's/>//g' | sed 's/<//' | sed 's/\///g' | grep -v $(date +%Y-%m-%d-%H-%M)` do echo -e "$(date)${RED} Удаляю старый файл: $i${END}" curl -X DELETE --user "${username}:${password}" https://webdav.yandex.ru/vpn-$vpn/$i done echo "$(date) ID на диске: $vpn" openvpn --genkey --secret "$key" passwd=`echo "$vpn-tt" | sha256sum | awk '{print $1}'` openssl AES-256-CBC -e -in "$key" -out "$DIR/file.enc" -k "$passwd" -base64 curl -T "$DIR/file.enc" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/key.enc rm "$DIR"/file.enc echo -e "$(date) ${GREEN}Фаза 1 - Получение готовности удаленного узла${END}" go=3 localport=`shuf -i 20000-65000 -n 1` # генерация локального порта start='' remote='' timeout1='' nextcheck='' timestart='' until [[ $b ]] do echo "$(date) Проверяю папку" date=`date +%s` timeout1=60 echo "$(date) Создание файла готовности $date" echo "$date" > "/tmp/ready-$date-$name.txt" curl -T "/tmp/ready-$date-$name.txt" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/ready-$name.txt readyfile=`curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></>\n</g' | grep -v $name | grep "ready" | grep "d:displayname" | sed 's/<d:displayname>//g' | sed 's/<\/d:displayname>//g'` if [[ -z $readyfile ]] then echo -e "$(date) ${RED} Удаленный узел не готов ${END}" echo "$(date) Жду 60 секунд" sleep $timeout1 else remote=$(curl --silent --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$readyfile) echo -e "$(date) ${GREEN} Удаленный узел готов ${END}" start=`curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></>\n</g' | grep "start" | grep "d:displayname" | sed 's/-/ /g' | awk '{print $2}'` if [[ -z $start ]] then let nextcheck=$timeout1-$date+$remote let timestart=$date+$timeout1-$nextcheck go=$nextcheck echo "$timestart" > "/tmp/start-$date-$name.txt" curl -T "/tmp/start-$date-$name.txt" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/start-$date-$name.txt else echo "$(date) жду $go секунд" sleep $go b=1 a='' fi fi done echo -e "$(date) ${GREEN}Фаза 2 - Обмен данными и установка соединения${END}" mydata='' filename='' address='' myip='' ip='' port='' ex=0 until [ $a ]; do until [[ -n "$mydata" ]]; do k=`echo "$stuns" | wc -w` x=1 z=`shuf -i 1-$k -n 1` for st in $stuns; do if [[ $x == $z ]]; then stun=$st; fi; (( x++ )); done echo "$(date) Подключение и получение данных от STUN сервера: $stun" sleep 5 && for pid in $(ps xa | grep "stun "$stun" 1 -p "$localport" -v" | grep -v grep | awk '{print $1}'); do kill $pid; done & mydata=`stun "$stun" 1 -p "$localport" -v 2>&1 | grep "MappedAddress" | sort | uniq` done echo -e "$(date) ${GREEN}Мои данные соединения: $mydata${END}" echo "$(date) Загрузка данных на Яндекс.диск" echo "$mydata" > "$DIR/mydata" echo "IntIP $intip" >> "$DIR/mydata" curl -T "$DIR/mydata" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$name-ipport.txt rm "$DIR/mydata" sleep 5 echo "$(date) Получение файла данных удаленного узла" filename=$(curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></\n/g' | grep "d:displayname>" | grep "ipport" | grep -v "$name" | sed 's|.*d:displayname>||' | sed 's/</ /g' | awk '{print $1}') if [[ -n "$filename" ]] then echo "$(date) Чтение файла данных удаленного узла: $filename" address=$(curl --silent --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$filename | grep "MappedAddress" | head -n1 | sed 's/:/ /g') intip2=$(curl --silent --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$filename | grep "IntIP" | head -n1 | awk '{print $2}') echo "$(date) Определение IP-адреса и порта: $address $sesid2 $tunid2" ip=$(echo "$address" | awk '{print $3}') port=$(echo "$address" | awk '{print $4}') myip=`ip route get "$ip" | head -n 1 | sed 's|.*src ||' | awk '{print $1}'` if [[ -n "$ip" && -n "$port" && -n "$myip" && -n "$localport" ]]; then echo -e "$(date) ${GREEN} Соединение $ip $port ${END}" echo -e "`date` ${GREEN} $myip:$localport -> $ip:$port ${END}" curl --silent --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/key.enc > "$DIR/secret.enc" openssl AES-256-CBC -d -in "$DIR/secret.enc" -out "$key" -k "$passwd" -base64 chmod 600 "$key" rm "$DIR/secret.enc" openvpn --remote $ip --rport $port --lport $localport \ --proto udp --dev tun --float --auth-nocache --verb 3 --mute 20 \ --ifconfig "$intip" "$intip2" \ --secret "$key" \ --auth SHA256 --cipher AES-256-CBC \ --ncp-disable --ping 10 --ping-exit 20 \ --comp-lzo yes a=1 b='' fi else if (( $ex >= 5 )) then echo "$(date) Сброс" a=1 b='' fi (( ex++ )) sleep 5 fi done done
Для работы скрипта нужно:
- Скопировать в буфер и вставить в редактор, например:
# nano vpn10.sh - указать логин (2я строка) и пароль от Яндекс.диска (3я строка).
- указать внутренний IP-адрес туннеля (4я строка).
- сделать скрипт исполняемым:
# chmod +x vpn10.sh - запустить скрипт:
# ./vpn10.sh nZbVGBuX5dtturD
где nZbVGBuX5dtturD — ID-соединения сгенерированный тут
На удаленном узле произвести всё тоже самое, указать соответствующий внутренний IP-адрес туннеля и ID-соединения.
Для автозапуска скрипта при включении я использую команду «nohup /<путь до скрипта>/vpn10.sh nZbVGBuX5dtturD > /var/log/vpn10.log 2>/dev/null &» содержащиюся в файле /etc/rc.local
Заключение
Скрипт работает, проверен на Ubuntu (18.04, 19.10, 20.04) и Debian 9. В качестве передатчика можно использовать любой другой сервис, но для опыта я использовал Яндекс.диск.
В процессе экспериментов было обнаружено, что некоторые типы NAT провайдеров не позволяют организовать соединение. В основном у сотовых операторов, где заблокированы торренты.
Планирую доработать в плане:
- Автоматической генерации secret.key каждый раз при старте, шифровании и копирования на Яндекс.диск для передачи на удаленный узел (Учтено в обновленном варианте)
- Автоматического назначения IP-адресов интерфейсов
- Шифрования данных перед выгрузкой на Яндекс.диск
- Оптимизация кода
Да будет IPv6 в каждом доме!
Обновлено! Последние файлы и DEB-пакет тут — yandex.disk
