Пишем PROXY-SERVER

advertisement
Пишем PROXY-SERVER
[ Хакерство :: Прочее ]
Было время, когда мне нужно было написать простейшей одноконнектовый прокси, даже без интерфейса, но состоящий из двух
половинок, которые соединяются протоколом SPX, а не TCP. Я столкнулся с тем, что в том небольшом количестве примеров работы с
WinSock, что у меня были, было столько ненужного мне мусора, что это затрудняло понимание самого принципа. А примеров
организации многоконнектовости у меня вообще не было. Поэтому в данной статье я постараюсь как можно проще объяснить принцип
работы прокси, но я не буду объяснять все с нуля. Если вы хотите понять принцип работы асинхронных неблокирующих сокетов в
Windows и их отличия от стандартных синхронных, для начала прочтите документ "Синхронные и асинхронные сокеты в Windows". А
если вы вообще не знакомы с сетевым программированием, отложите не надолго эту статью и постигните основы. Здесь же я
расскажу только о том, что действительно может быть непонятным читателю. В качестве примера рассмотрим программу,
организующую прослушивание сокета и осуществляющую перенаправление данных на указанный IP:PORT. Правильнее было бы
назвать это чем-то вроде "port map" или "port redirect".
самая лучшая документация для программиста - это исходный текст программы
Для начала определимся с константами.
Какой локальный порт будем прослушивать:
#define IN_PORT 3128
Удаленный IP адрес.
#define OUT_IP "192.168.0.1"
Порт к которому будем подключаться.
#define OUT_PORT 3128
Решим, какое максимальное количество соединений мы будем поддерживать.
#define MAXCONN 1000
Объявим глобальные переменные.
Это будет буфер для принятых данных.
char buf[MAX_DATA];
Слушающий сокет, на который будут коннектиться клиенты.
SOCKET hListenSockTCP;
Массив дескрипторов сокетов, полученных при соединении нашей
программой с удаленным сервисом в ответ на подключение со стороны
клиента. Дескриптор сокета с клиентской стороны и будет индексом.
Например: к нашей программе подключается клиент. После выполнения
строки "currentsock = accept(hListenSockTCP,NULL,NULL);" в переменной
currentsock типа SOCKET будет возращен дескриптор сокета. Он,
например, может быть числом 5,6 и т.д., поэтому, сам дескриптор можно
использовать в качестве индекса в массиве. Теперь в ответ на "пятое"
соединение (в случае, когда currentsock=5) соединяемся с прокси, на
который мы делаем перенаправление, и полученный дескриптор сохраняем
sockets[5]. Это равносильно строке "sockets[5]=connect
(sockets[nofsock], ". Как вы должны понимать, это не самый лучший
метод. Но зато он самый простой и нам пока подойдет.
SOCKET sockets[MAXCONN];
Начнем.
* Инициализация среды перед использованием WinSock:
WSADATA stWSADataTCPIP;
if(WSAStartup(0x0101, &stWSADataTCPIP)) MessageBox(hwndMain,
"WSAStartup error !","NET ERROR!!!",0);
* Заполним массив дескрипторов сокетов нулями (на всякий случай).
ZeroMemory(sockets,sizeof(sockets));
* Зарегистрируем класс и создадим окно. Получим hwndMain - дескриптор
окна.
* Создадим сокет.
hListenSockTCP = socket (AF_INET,SOCK_STREAM,0);
* Заполним структуру SOCKADDR_IN, указав тип протокола(family) и порт,
к которому будем "биндиться", и "привязываем" сокет.
SOCKADDR_IN myaddrTCP;
myaddrTCP.sin_family = AF_INET;
myaddrTCP.sin_addr.s_addr = htonl (INADDR_ANY);
myaddrTCP.sin_port = htons (IN_PORT);
bind( hListenSockTCP,(LPSOCKADDR)&myaddrTCP, sizeof(struct sockaddr) );
* Запускаем сокет "на прослушку".
listen (hListenSockTCP, SOMAXCONN));
* Привязываем события FD_ACCEPT, FD_READ, FD_CLOSE сокета к главному
окну программы.
WSAAsyncSelect (hListenSockTCP,hwndMain,WM_ASYNC_CLIENTEVENT,
FD_ACCEPT|FD_READ|FD_CLOSE);
Это значит, что при попытке клиента подключиться к прослушиваемому
сокету окну с дескриптором hwndMain будет передаваться сообщение
WM_ASYNC_CLIENTEVENT. Напомню, что функция обработки сообщений окна
выглядит так - "LRESULT CALLBACK MainWndProc(HWND hwnd,UINT
msg,WPARAM wParam,LPARAM lParam)". В переменной wParam будет передан
дескриптор сокета, в котором произошло событие. А какое именно узнаем из lParam. Но кроме кода события в lParam еще находится код
ошибки. Для извлечения их этого 4-х байтного числа (DWORD) двух слов
(WORD) существуют два макроопределения - WSAGETSELECTERROR(lParam) и
WSAGETSELECTEVENT(lParam).
* Процедура обработки сообщений.
.............
case WM_ASYNC_CLIENTEVENT:
Сообщения о событиях подключенных к клиенту сокетов...
currentsock = wParam;
именно так узнаем, какое событие с сокетом произошло
WSAEvent = WSAGETSELECTEVENT (lParam);
switch (WSAEvent)
{
Это сообщение приходит тогда, когда к нам хотят подключиться.
case FD_ACCEPT:
Разрешаем подключение клиента, и пытаемся теперь подключиться к
нашему удаленному прокси.
ConnectToProxy(accept(hListenSockTCP,NULL,NULL));
Если это не удалось, закрываем соединение, которое только что мы
позволили установить с нами клиенту. Второй параметр - SD_SEND (у
меня - просто единица). Этим мы позволяем соединению спокойно
закрыться. После этой команды с сокетом произойдет событие
"FD_CLOSE".
shutdown(currentsock,1);
return 0;
case FD_CLOSE :
Клиент по какой-либо причине хочет прервать соединение. Глушим
соединение с уд. прокси, которое мы установили в ответ на это
соединение.
shutdown(sockets[currentsock],1);
и закрываем сокет.
closesocket(currentsock);
return 0;
case FD_READ:
На сокет пришли данные. Берем от клиента, посылаем на сервер.
i=recv(currentsock, buf, MAX_DATA, 0);
send(sockets[currentsock], buf, i, 0);
и отправляем...
return 0;
}
break;
Так же поступаем и с обработкой событий на сокетах, когда сам наш прокси является клиентом другого прокси, на который мы делаем
перенаправление. Проще говоря, у нас в программе будут две группы сокетов.
1. Со стороны клиентов. Есть главный сокет - hListenSockTCP. К нему могут подключаться клиенты, каждый раз создавая новые
виртуальные каналы, каждому из которых назначается свой дескриптор.
2. Со стороны оконечного прокси сервера (или сервиса). Наша программа будет каждый раз при необходимости создавать сокет и
коннектиться на заданный IP:PORT. Каждый открытый виртуальный канал будет ответом на подсоединение со стороны клиентов.
case WM_ASYNC_PROXYEVENT:
Найдем соответствующий дескриптор в массиве.
for (i=0;iMAXCONN)
{
shutdown(hsocket);close(hsocket);}4. Еще раз внимательно посмотрите на код, и, возможно, вы найдете еще что-то, что я упустил.
2
3
Download