|
DNS-туннелинг или халявный dialup Дмитрий Докучаев aka Forb Xakep, номер #051, стр. 051-058-3 %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", впоследствии считая его мертвым. Они все могут быть "оживлены" заново при новом подключении. Таким образом количество элементов массива сводится к минимальному, что существенно улучшает производительность скрипта. |