Директен VPN тунел помеѓу компјутерите преку NAT на провајдери (без VPS, користејќи STUN сервер и Yandex.disk)

Продолжување Член за тоа како успеав да организирам директен VPN тунел помеѓу два компјутери лоцирани зад NAT провајдерите. Претходниот напис го опиша процесот на организирање врска со помош на трета страна - посредник (изнајмен VPS кој делува како нешто како STUN сервер и јазол предавател на податоци за врската). Во оваа статија ќе ви кажам како се снајдов без VPS, но посредниците останаа и тоа беа STUN серверот и Yandex.Disk...
Директен VPN тунел помеѓу компјутерите преку NAT на провајдерите (без VPS, користејќи STUN сервер и Yandex.disk)

Вовед

Откако ги прочитав коментарите од претходниот пост, сфатив дека главниот недостаток на имплементацијата е употребата на посредник - трета страна (VPS) која ги означува тековните параметри на јазолот, каде и како да се поврзе. Имајќи ги предвид препораките за користење на овој STUN (од кои ги има многу) за одредување на тековните параметри за поврзување. Прво, решив да го користам TCPDump за да ја погледнам содржината на пакетите кога серверот STUN работеше со клиенти и добиваше целосно нечитлива содржина. Гуглувајќи го протоколот наидов напис кој го опишува протоколот. Сфатив дека не можам сам да имплементирам барање до серверот STUN и да ја ставам идејата во „далечна кутија“.

Теорија

Неодамна морав да инсталирам STUN сервер на Debian од пакетот

# apt install stun-server

и во зависностите го видов пакетот stun-client, но некако не му обрнав внимание. Но, подоцна се сетив на пакетот stun-client и решив да дознаам како функционира, откако прогуглав и барав во Yandex, добив:

# apt install stun-client
# stun stun.ekiga.net -p 21234 -v

Како одговор добив:

STUN клиент верзија 0.97
Отворена порта 21234 со fd 3
Отворена порта 21235 со fd 4
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 0

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 4

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 2

Ќе испратам порака од len 28 на 216.93.246.18:3478
Добиена зачудувачка порака: 92 бајти
MappedAddress = <Моја IP>:2885
Извор Адреса = 216.93.246.18:3478
Променета адреса = 216.93.246.17:3479
Непознат атрибут: 32800
ServerName = Vovida.org 0.98-CPC
Примена порака од типот 257 id=1
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 0

Ќе испратам порака од len 28 на 216.93.246.17:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 4

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 2

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 0

Ќе испратам порака од len 28 на <Моја IP>:2885
Добиена зачудувачка порака: 28 бајти
ChangeRequest = 0
Примена порака од типот 1 id=11
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 0

Ќе испратам порака од len 28 на 216.93.246.17:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 4

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 2

Ќе испратам порака од len 28 на 216.93.246.18:3478
Добиена зачудувачка порака: 92 бајти
MappedAddress = <Моја IP>:2885
Извор Адреса = 216.93.246.17:3479
Променета адреса = 216.93.246.18:3478
Непознат атрибут: 32800
ServerName = Vovida.org 0.98-CPC
Примена порака од типот 257 id=10
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 4

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 2

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 4

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 2

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 4

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 2

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 4

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 2

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 4

Ќе испратам порака од len 28 на 216.93.246.18:3478
Кодирачка порака за зашеметување:
Барање за промена на кодирање: 2

Ќе испратам порака од len 28 на 216.93.246.18:3478
тест I = 1
тест II = 0
тест III = 0
тест I(2) = 1
е nat = 1
мапирана IP иста = 1
фиба = 1
порта за зачувување = 0
Примарно: Независно мапирање, филтер зависен од пристаништето, случаен приклучок, фиба
Повратната вредност е 0x000006

Низа со вредност

MappedAddress = <Моја IP>:2885

само она што ви треба! Го прикажуваше моменталниот статус за врската на локалната UDP порта 21234. Но, ова е само половина од битката; се појави прашањето како да се пренесат овие податоци на оддалечениот домаќин и да се организира VPN врска. Користење на протоколот за пошта, или можеби Телеграма?! Има многу опции и решив да користам Yandex.disk, бидејќи наидов статија за работа на Curl преку WebDav со Yandex.disk. Откако размислував за имплементацијата, дојдов до следнава шема:

  1. Сигнал дека јазлите се подготвени да воспостават врска со присуство на одредена датотека со временски печат на Yandex.disk;
  2. Ако јазлите се подготвени, тогаш примајте тековни параметри од серверот STUN;
  3. Поставете ги тековните поставки на Yandex.disk;
  4. Проверете го присуството и прочитајте ги параметрите на оддалечен јазол од датотека на Yandex.disk;
  5. Воспоставување врска со далечински домаќин користејќи OpenVPN.

Пракса

Откако малку размислив, земајќи го предвид искуството од последната статија, брзо напишав сценарио. Ќе ни требаат:

# apt install openvpn stun-client curl 

Самото сценарио:

оригинална верзија

# cat vpn8.sh

#!/bin/bash
######################## Задаем цветной текст ###
WARN='33[37;1;41m'				#
END='33[0m'					#
RED='33[0;31m'         #  ${RED}		#
GREEN='33[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

За да функционира сценариото, потребни ви се:

  1. Копирајте во таблата со исечоци и залепете во уредувачот, на пример:
    # nano vpn8.sh 
  2. наведете го корисничкото име и лозинката за Yandex.disk.
  3. во полето „—ifconfig 10.45.54.(1 или 2) 255.255.255.252“ наведете ја внатрешната IP адреса на интерфејсот
  4. создаваат тајна.клуч команда:
    # openvpn --genkey --secret secret.key 
  5. направете го скриптата извршна:
    # chmod +x vpn8.sh
  6. извршете го сценариото:
    # ./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='33[37;1;41m'
END='33[0m'
RED='33[0;31m'
GREEN='33[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

За да функционира сценариото, потребни ви се:

  1. Копирајте во таблата со исечоци и залепете во уредувачот, на пример:
    # nano vpn10.sh 
  2. наведете го најавувањето (втора линија) и лозинката за Yandex.disk (2-та линија).
  3. наведете ја внатрешната IP адреса на тунелот (4-та линија).
  4. направете го скриптата извршна:
    # chmod +x vpn10.sh
  5. извршете го сценариото:
    # ./vpn10.sh nZbVGBuX5dtturD

    каде што nZbVGBuX5dtturD е генериран ID на врската тука

На оддалечениот јазол, сторете го истото, наведете ја соодветната внатрешна IP адреса на тунелот и ID на врската.

За автоматско вклучување на скриптата кога е вклучена, ја користам командата „nohup /< path to the script>/vpn10.sh nZbVGBuX5dtturD > /var/log/vpn10.log 2>/dev/null &“ содржана во датотеката /etc/ rc.локален

Заклучок

Скриптата работи, тестирана на Ubuntu (18.04, 19.10, 20.04) и Debian 9. Можете да користите која било друга услуга како предавател, но за искуство користев Yandex.disk.
За време на експериментите, беше откриено дека некои типови на NAT провајдери не дозволуваат воспоставување врска. Главно од мобилните оператори каде што се блокирани торентите.

Планирам да се подобрам во однос на:

  • Автоматско генерирање на secret.key секој пат кога ќе започнете, шифрирајте и копирајте на Yandex.disk за пренос на оддалечен јазол (Земајќи ја предвид ажурираната верзија)
  • Автоматско доделување на IP адреси на интерфејси
  • Шифрирање на податоците пред поставување на Yandex.disk
  • Оптимизација на кодот

Нека има IPv6 во секој дом!

Ажурирано! Најнови датотеки и DEB пакет овде - yandex.диск

Извор: www.habr.com

Додадете коментар