Архив статей

Все статьи из текущего раздела

DNS-туннелинг или халявный dialup

Сейчас многие провайдеры предоставляют так называемые free-аккаунты, необходимые для пополнения счета через Web или просто для посещения провайдерского сайта. Доступ в интернет через такие аккаунты конечно же перекрыт фаерволом, поэтому они никому не нужны и малоинтересны.

Но, как известно, у медали две стороны. В наш бурный век протоколы расширяются до неимоверных пределов. DNS не исключение, так как именно через него стало возможным создание полноценного IP-туннелинга. Конечно, реализация непростая. Тут и обмен по UDP вместо надежного TCP, требование полной синхронизации данных, а также фрагментации пакетов, по той причине, что DNS-запрос может состоять лишь из 512 байт. Казалось бы, все не в пользу программиста, но и они, в свою очередь, не лыком шиты и вполне способны сделать из мухи слона =).

Первое рождение - NSTX

Итак, после долгих мучений родилась первая версия сервера и клиента NSTX (что в расшифровке означает "NameServer Transfer Protocol). Эта программа позволяет построить туннель между двумя UNIX-серверами посредством интерфейса Ethertap. Чтобы воспользоваться этой программой, нам также нужен будет собственный домен, а точнее, доступ к зонам на каком-либо dns-сервере. Механизм передачи будет примерно следующим: клиент, запущенный локально с параметром домена и dns-сервера, будет передавать зашифрованный трафик через Ethertap. Dns-сервер вернет серваку прова поле NS, тот, конечно же, обратится на эту тачку, на которой будет стоять nstx-daemon, возвращающий трафик по ethertap-интерфейсу, стоящему на системе конечной точки. В итоге получаем полноценный обмен трафиком.

Переходим от теории к практике. Для удачного эксперимента тебе понадобится домашний сервер на пингвине, который ты, надеюсь, ставил не один раз =), шелл в забугорье без установленного named`а на нем, а также собственный домен (второе и третье ты можешь попросить у кого-нибудь за символическое пиво). Далее, создаешь домен третьего уровня с полем NS, значение которого будет адресом тачки с установленным nstx.

Установка

Топаем на эту самую тачку, устанавливаем nstx и поднимаем ethertap (непосредственно через него и будет проходить весь трафик). Для начала создадим блочный файл /dev/tap0, командой "mknod /dev/tap0 c 36 16". Затем подгружаем модуль ethertap.o (locate ethertap.o, insmod /path/to/ethertap.o) и, наконец, поднимаем интерфейс:

ifconfig tap0 up 192.168.0.1, присваивая тем самым адрес.

Напоследок создаем роутинг: route add -host 192.168.0.2 gw 192.168.0.1, чтобы сервер мог видеть клиента =). И запускаем nstxd с параметром созданного домена третьего уровня.


На домашнем компьютере проделываем в точности такую же операцию, только запускаем клиент nstxcd с двумя параметрами: домен и dns-сервер, на котором прописаны зоны к этому домену, логически завершая туннелинг. Ehtertap`у клиента присваиваем ip-адрес 192.168.0.2. Затем пробуем пингануть 192.168.0.1. Если пинги пойдут, значит, тоннель работает. В моем случае все заработало со второго раза, и то из-за невнимательности :). Но так как шелл стоял очень далеко, да и коннект у прова был паршивый, такой интернет разочаровал своей медлительностью, и я забил на эту идею. Но ненадолго.

Спаситель UDP

Мне пришла в голову следующая мысль: а что, если провайдер позволяет светить 53-ий порт куда угодно, заботливо оставляя его для DNS-обмена. Решив проверить эту фичу, я написал простенькую систему клиент-сервер, передающую данные и записывающую их в логфайл. Оставалось проверить успешную передачу и радоваться жизни. Однако не стоит сильно обнадеживаться. Может оказаться так, что провайдер закроет все порты, даже 53-ий UDP. Но вернемся к нашей утилите. Я опишу работу сервера, а клиент ты без труда напишешь и сам (по аналогии). Для кодинга я выбрал язык Perl, так как мог свободно пользовать Windows-клиент и *nix-сервер для работы с udp-сокетом.

#!/usr/bin/perl

## Checker for open udp ports

use IO::Socket;

$sock=IO::Socket::INET->new(LocalPort=>53,Proto=>`udp`,Reuse=>1) || die "cant create socket $!\n";

while($sock->recv($data,10000,0)) {

open(TF,">accept");

print TF "GOT msg $data\n";

close(TF);

}

Этот сервер устанавливаем на нашем шелле. Он, как ты, наверное, уже догадался, будет принимать данные на 53-ий udp-порт. Аналогичный клиент пошлет запрос на сервер, и если он успешно дойдет, то будет записан в файл "accept". Пролистав его, ты поймешь, светится ли порт. Если порт будет открыт, возможно написать свой сервер, который следит за udp-подключениями, создает tcp-соединение и перенаправляет tcp в udp и наоборот. То есть осуществить туннелинг по следующей схеме: локальный клиент, следящий tcp-порт и перенаправляющий все данные с него на udp-порт удаленного сервера, который, в свою очередь, порождает tcp-соединение с возвратом всех данных посредством udp-датаграмм.

Несмотря на кажущуюся сложность схемы, все довольно просто. Пишется сервер, который будет находиться на шелле с хорошим каналом в инет, и клиент, который будет запускаться непосредственно с домашней машины. Для реализации этой схемы нам потребуется:

a) ActivePerl, если у тебя Windows (в случае с NTSX, юзание Windows недопустимо).

б) Shell в России или в зарубежье.

в) Доступ к Proxy-серверу с доступным CONNECT методом (необязательно).

Если у тебя есть все три составляющие, то ты можешь использовать мой проект, позволяющий делать туннель для IRC. Конечно, это неполноценно, но халявный IRC меня вполне устраивает, и потеря пакетов там незначительная. Сам сервер и клиент выполнены несколько по-разному. Тут я попытаюсь рассказать основной принцип работы сервера и клиента с небольшими фрагментами кода.

Сервер: udpserver.pl

Прежде чем писать сервер, я тщательно обдумал, что от него требуется. А требовалось от него:

1) Многопоточность.

2) Гибкое закрытие сокета при обрыве его клиентом.

3) Проверка всех клиентов на живучесть с помощью keep-alive мессаг.

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

%ports - хеш, хранящий список локальных портов (по ним происходит сравнение сокетов).

%locate - хеш, хранящий полный адрес сокета (необходим для точной отправки udp-датаграммы).

@sockets - массив, хранящий идентификаторы сокетов.

@pings - массив, необходимый для процедуры обработки пинга сокета.

$timeout - таймаут, по умолчанию три минуты (значение * 3, в дальнейшем я расскажу про эту переменную).

$sockets - подсчет количества открытых сокетов.

$proxy – прокси-сервер, для tcp-соединения.


После объявления этих важных идентификаторов, мы создадим udp-сокет, который будет ловить пакеты на 53-ем порту, а также добавим его в объект модуля IO::Select, чтобы следить за данными в этом сокете. Затем порождаем бесконечный цикл, в котором читаем данные с udp-сокета следующей конструкцией:


foreach $n ($udp->can_read) { ## Смотрим все идентификаторы сокетов, в которых есть данные

if ($n eq $sockudp) { ## Если идентификатор - $sockudp, то есть наш сокет

$sockudp->recv($msg,1024); ## Получаем из него сообщение

my($port,$addr)=sockaddr_in($sockudp->peername); ## Запоминаем порт и адрес

$flag=0,$i=0,$nsock=0; ## Обнуляем временные переменные

foreach $in (values %ports) {

$i++;

if ($in ne -1) {

if ($in eq $port) {

$flag=1,$nsock=$i; ## Ищем этот порт в хеше портов, если он есть, устанавливаем $flag 1, иначе - 0

}

}

}

unless ($flag) {

$threads++;

$res=tcpsock($port,$threads); ## Если порт новый, создаем новый IRC-сокет

..............

} else {

## Оперируем над старыми udp-датаграммами

}

Обзор утилиты

В этой части сервера все просто. Следим за приходящими на udp-порт пакетами. Как известно, этот пакет может быть доставлен с любого ip-адреса и порта, так как фактического соединения с подтверждением не происходит (такой уж он - udp). Если в хеше %ports нет локального порта, откуда пришел этот пакет, считаем его новым, и делаем для него туннель через tcp-сокет. В противном случае переходим в малоинтересную часть кода - прием сообщений со всех порожденных (в данный момент времени) tcp-сокетов, с последующим возвратом пакетов клиенту через udp. Интереснее будет рассказать про процедуру отключения сокета по таймауту (при завершении сессии весь туннель должен быть корректно завершенным, с отключением tcp-соединения для него). Это делается с помощью сигнала ALRM, посылаемому серверу через определенный промежуток времени (этот промежуток и есть $timeout). Так как при передаче пакетов через udp возможна их потеря, контролировать которую крайне медленно и неудобно, мы обходимся тройной передачей сообщения "KEEP" клиенту. Если клиент хотя бы один раз возвращает "ALIVE", значит он все еще с нами =), и мы не убиваем его. Иначе закрываем сокет и присваиваем идентификатору значение "-1", впоследствии считая его мертвым. Они все могут быть "оживлены" заново при новом подключении. Таким образом количество элементов массива сводится к минимальному, что существенно улучшает производительность скрипта.

Хотелось бы вернуться к третьему пункту требований для пользования udp-tcp туннелера в IRC. Я отметил, что прокси использовать необязательно (в этом случае можно напрямую вписать ip ирц-сервера в переменную $proxy, таким образом, сервер будет соединять тебя лишь с одним IRC-серваком). С прокси же все по-другому. Через него ты можешь соединиться по HTTPS-методу с любым IRC-серваком на твой выбор, что значительно улучшает гибкость скрипта. Клиент я разбирать не буду, так как его реализация намного проще серверной, и разобраться в нем может практически каждый. Врубив удаленно сервер, соединяйся с провайдером на тестовом аккаунте и врубай клиент с параметром tcp-порта, который будет слушаться на твоей машине. Затем трави mIRC (или другой клиент) на прокси 127.0.0.1 с портом, тем, что ты указал в клиенте. Если все прошло успешно, ты увидишь туннель в действии.

Никто не запрещает тебе расширить возможности этого туннелера, сделав его пригодным для веба или аси. Основу скрипта я расписал, а под нее можно пристроить все что угодно. Благодаря многопоточности, ты можешь делиться халявным интернетом со своими друзьями за пиво :). И поспорить с законом тут можно - что не запрещено, то разрешено, а оставлять доступным DNS-сервер, а также его порты, не твоя забота...


Все проекты, описанные в этой статье, ты можешь скачать по следующим ссылкам:

NSTX: http://nstx.dereference.de/nstx.tar.gz

IRC: http://kamensk.net.ru/forb/1/x/udp-irc.tar.gz

Это отнюдь не единственный вариант туннелинга. Существуют также другие программы, яркий пример - iproxy, в которой есть возможность создавать туннель по udp. В результате тестировании наблюдался провал, поэтому описывать ее в этой статье я не стал. Скачать и оценить ее возможности можно отсюда:

http://www.vergenet.net/linux/iproxy/code/iproxy-0.0.0.tar.gz