Статья о том, как мне удалось запустить VPN-сервер за NAT'ом домашнего провайдера (без белого IP-адреса). Сразу оговорюсь: что работоспособность данной реализация напрямую зависит от типа NAT используемого Вашим провайдером, а также роутером.
Итак, возникла у меня необходимость подключаться со своего Android-смартфона к домашнему компьютеру, оба девайса подключены к Интернету через провайдерские NAT'ы, плюсом компьютер подключен через домашний роутер, который тоже NAT'ил соединения.
Классическая схема с использованием арендованного VPS/VDS с белым IP-адресом, а также аренда белого IP-адреса у провайдера не рассматривалась по нескольким причинам.
С учетом опыта прошлых статей, проведя несколько опытов со STUNами и NAT'ами провайдеров. Решился на небольшой эксперимент: выполнив команду на домашнем роутере работающем на прошивке OpenWRT:
$ stun stun.sipnet.ru
получил результат:
STUN client version 0.97
Primary: Independent Mapping, Independent Filter, random port, will hairpin
Return value is 0x000002
Дословный перевод:
Independent Mapping — независимое отображение
Independent Filter — независимый фильтр
random port — случайный порт
will hairpin — будет шпилька
Выполнив аналогичную команду на своем ПК, получил:
STUN client version 0.97
Primary: Independent Mapping, Port Dependent Filter, random port, will hairpin
Return value is 0x000006
Port Dependent Filter — порт зависимый фильтр
Разница в результатах вывода команд, говорила, о том, что домашний роутер вносил «свою лепту» в процесс трансляции пакетов из Интернета, это проявлялось в том, что при выполнении команды на компьютере:
stun stun.sipnet.ru -p 11111 -v
я получал результат:
…
MappedAddress = XX.1XX.1X4.2XX:4398
...
в это момент на некоторое время открывалась UDP-сессия, если в этот момент послать UDP-запрос (например: netcat XX.1XX.1X4.2XX 4398 -u), то запрос приходил на домашний роутер, что подтвердил TCPDump запущенный на нем, но запрос не доходил до компьютера — IPtables в качестве NAT-транслятора на роутере дропал его.
Но сам факт прохождения UDP запроса через провайдерский NAT давал надежду на успех. Так как роутер находится в моей юрисдикции, решил проблему перенаправлением UDP/11111 порта на компьютер. Тем самым получил возможность инициировать UDP-сессию и получать запросы из Интернета с любого IP-адреса. В этот момент запустил OpenVPN-server (предварительно сконфигурировав) слушая UDP/11111 порт, указал на смартфоне внешний IP-адрес и порт (XX.1XX.1X4.2XX:4398) и успешно подключился со смартфона к компьютеру. Но в данной реализации возникла проблема, нужно было как-то поддерживать UDP-сессию до момента подключения OpenVPN-клиента к серверу, вариант с периодическим запуском STUN-клиента мне не понравился — не хотелось впусту нагружать STUN-серверы.
Так же обратил внимание на запись "will hairpin — будет шпилька", данный режим
Hairpinning позволяет одной машине в локальной сети за NAT получить доступ к другой машине в той же сети по внешнему адресу маршрутизатора.
В итоге проблему поддержания UDP-сессии решил просто — запустил клиента на этом же компьютере с сервером.
Работало это так:
- запускал STUN-клианта с локальным портом 11111
- получал ответ с внешним IP-адресом и портом XX.1XX.1X4.2XX:4398
- отправлял данные с внешним IP-адресом и портом на почту (возможен любой другой сервис), настроенную на смартфоне
- запускал OpenVPN-сервер на компьютере с прослушкой UDP/11111 порта
- запускал OpenVPN-клиента на компьютере с указанием XX.1XX.1X4.2XX:4398 для подключения
- в любое время запускал OpenVPN-клиента на смартфоне с указанием IP-адреса и порта (в моем случае IP-адрес не менялся) для подключения
Таким образом я получил возможность подключаться к своему компьютеру со смартфона. Данная реализация позволяет подключать любого OpenVPN-клиента.
Практика
Понадобится:
# apt install openvpn stun-client sendemail
Написав пару скриптов, пару конфигурационных файлов, сгенерировав необходимые сертификаты (так как клиент на смартфоне работает только сертификатам) получилась обычная реализация OpenVPN-сервера.
Основной скрипт на компьютере:
# cat vpn11.sh
#!/bin/bash
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
ABSOLUTE_FILENAME=`readlink -f "$0"`
DIR=`dirname "$ABSOLUTE_FILENAME"`
localport=11111
until [[ $a ]]; do
address=`stun stun.sipnet.ru -v -p $localport 2>&1 | grep "MappedAddress" | sort | uniq | head -n 1 | sed 's/:/ /g' | awk '{print $3" "$4}'`
ip=`echo "$address" | awk {'print $1'}`
port=`echo "$address" | awk {'print $2'}`
srv="openvpn --config $DIR/server.conf --port $localport --daemon"
$srv
echo "$(date) Сервер запущен с внешним адресом $ip:$port"
$DIR/sendemail.sh "OpenVPN-Server" "$ip:$port"
sleep 1
openvpn --config $DIR/client.conf --remote $ip --port $port
echo "$(date) Cоединение клиента с сервером разорвано"
for i in `ps xa | grep "$srv" | grep -v grep | awk '{print $1}'`; do
kill $i && echo "$(date) Завершен процесс сервера $i ($srv)"
done
echo "Жду 15 сек"
sleep 15
done
Скрипт отправки данных на почту:
# cat sendemail.sh
#!/bin/bash
from="От кого"
pass="Пароль"
to="Кому"
theme="$1"
message="$2"
server="smtp.yandex.ru:587"
sendEmail -o tls=yes -f "$from" -t "$to" -s "$server" -xu "$from" -xp "$pass" -u "$theme" -m "$message"
конфигурационный файл сервера
# cat server.conf
proto udp
dev tun
ca /home/vpn11-srv/ca.crt
cert /home/vpn11-srv/server.crt
key /home/vpn11-srv/server.key
dh /home/vpn11-srv/dh2048.pem
server 10.2.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
tls-server
tls-auth /home/vpn11-srv/ta.key 0
tls-timeout 60
auth SHA256
cipher AES-256-CBC
client-to-client
keepalive 10 30
comp-lzo
max-clients 10
user nobody
group nogroup
persist-key
persist-tun
log /var/log/vpn11-server.log
verb 3
mute 20
Конфигурационный файл клиента:
# cat client.conf
client
dev tun
proto udp
ca "/home/vpn11-srv/ca.crt"
cert "/home/vpn11-srv/client1.crt"
key "/home/vpn11-srv/client1.key"
tls-client
tls-auth "/home/vpn11-srv/ta.key" 1
auth SHA256
cipher AES-256-CBC
auth-nocache
comp-lzo
user nobody
group nogroup
persist-key
persist-tun
log /var/log/vpn11-clent.log
verb 3
mute 20
ping 10
ping-exit 30
Генерация сертификатов производилась по этой статье.
Запуск скрипта:
# ./vpn11.sh
Предварительно сделав его исполняемым
# chmod+x vpn11.sh
На стороне смартфона
Установив приложение OpenVPN для Android, скопировав конфигурационный файл, сертификаты и настроив его, получилось так:
В процессе написания статьи я перенес конфигурацию с компьютера на Raspberry Pi 3 и попробовал запустить всё это дело на LTE модеме, но не получилось! Результат команды
# stun stun.ekiga.net -p 11111
STUN client version 0.97
Primary: Independent Mapping, Port Dependent Filter, random port, will hairpin
Return value is 0x000006
значение Port Dependent Filter не позволило запуститься системе.
Но домашний провайдер без проблем дал запуститься системе на Raspberry Pi 3.
В связке с вэб-камерой, сVLC для
$ cvlc v4l2:///dev/video0:chroma=h264 :input-slave=alsa://hw:1,0 --sout '#transcode{vcodec=x264,venc=x264{preset=ultrafast,profile=baseline,level=31},vb=2048,fps=12,scale=1,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}:rtp{sdp=rtsp://10.2.0.1:8554/}' --no-sout-all --sout-keep
и VLC на смартфоне для просмотра (поток rtsp://10.2.0.1:8554/), получилась не плохая система видеонаблюдения на расстоянии, так же можно поднять Samba, маршрутизировать трафик через VPN, удаленно управлять компьютером и много чего еще…
Вывод
Как показала практика, то для оргинизации VPN-сервера можно обойтись и без внешнего IP-адреса за который нужно платить, так же как и за арендуемый VPS/VDS. Но всё зависит от провайдера.
Автор: Dmitry