PDA

Просмотр полной версии : Socks-сервер WinSock


Faost
13.06.2010, 20:59
Т.к. в моей пред.теме о socks никто ничем помочь не смог, создаю еще одну.

Проблема заключается в некорректности работы сокс-сервера через соксификаторы.

Для наглядности, чтоб было максимально понятно написал простейший socks4 сервер с подробнейшими комментариями (Delphi, WinSock + WinApi).

Качаем соксификатор (http://www.freecap.ru/?p=download), настраиваем в нем socks4 (Сервер: 127.0.0.1, порт: 8080).


Качаем исходник socks4-сервера (http://www.sendspace.com/file/ehy83w) (залил для большей наглядности)

Компилируем, запускаем, заходим на mail.ru - страница грузится очень долго (конца загрузки дождаться мне так и не удалось), хотя тот же гугл, да и вконтакт (по крайней мере на "Поиске людей") работают норм.

Так вот, нужна подсказка, из-за чего это происходит и как это исправить.

Код:
program Socks4;
uses
Windows,
WinSock;

const IpToListen = '127.0.0.1';


function OurBrowserListening(p: pointer): DWORD; stdcall;
var BSock: TSocket;
vn, cd, UserID: byte;
answer: array[1..8] of byte;
buf: pointer;
addr: sockaddr_in;
TargetSock: TSocket;
fset: tfdset;
i: integer;
avail_bytes: integer;
time_out: timeval;
label 2, 3;
begin
BSock:= Cardinal(p); //BSock - браузер (подключеный клиент)


TargetSock:= socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//TargetSock - таргет (цель, конечный хост)
if TargetSock = INVALID_SOCKET then ExitThread(0);
ZeroMemory(@addr, sizeof(addr));
addr.sin_family:= PF_INET;

//Теперь нужно принять данные от браузера (клиента):
// +----+----+----+----+----+----+----+----+----+----+....+----+
// | VN | CD | DSTPORT | DSTIP | USERID |NULL|
// +----+----+----+----+----+----+----+----+----+----+....+----+
// 1 1 2 4 variable 1

recv(BSock, vn, sizeof(vn), 0); //принимаем версию socks - 4
recv(BSock, cd, sizeof(cd), 0); //принимаем команду - 1 (connect)
if cd <> $01 then goto 2; //если не connect, то перейти к метке 2
recv(BSock, addr.sin_port, 2, 0); //принимаем порт таргета (цели)
recv(BSock, addr.sin_addr.S_addr, 4, 0); //принимаем ip таргета (цели)
repeat
recv(BSock, UserID, sizeof(UserID), 0);
until UserID = $00; //принимаем UserID, оканчивающийся NULL-байтом


//пытаемся соединиться с таргетом
if connect(TargetSock, addr, sizeof(addr)) <> 0 then
begin //если не удалось, то:
2:ZeroMemory(@answer, sizeof(answer));
answer[1]:= $00; //должен быть 0
answer[2]:= $5B; //$5B - ошибка
send(BSock, answer, sizeof(answer), 0); //посылаем ответ браузеру (клиенту)
goto 3;
end;


//Теперь нужно послать ответ браузеру (клиенту)
// +----+----+----+----+----+----+----+----+
// | VN | CD | DSTPORT | DSTIP |
// +----+----+----+----+----+----+----+----+
// 1 1 2 4
ZeroMemory(@answer, sizeof(answer));
answer[1]:= $00; //должен быть 0
answer[2]:= $5A; //$5A - коннект к таргету успешен
send(BSock, answer, sizeof(answer), 0); //посылаем ответ браузеру (клиенту)


//Теперь следует непосредственно перенаправление данных от браузера к
//таргету и наоборот
while true do
begin //while true do

FD_ZERO(fset);
FD_SET(BSock, fset);
FD_SET(TargetSock, fset);
time_out.tv_sec:= 8; //установим таймаут на select в 8 секунд
time_out.tv_usec:= 0;


if select(0, @fset, nil, nil, @time_out) <= 0 then
goto 3;
//если есть данные от браузера и/или таргета, то:




if FD_IsSet(BSock, fset) then //если есть данные от браузера, то:
begin
avail_bytes:= 0;
ioctlsocket(BSock, FIONREAD, avail_bytes); //читаем кол-во пришедших байт
buf:= VirtualAlloc(nil, avail_bytes, MEM_COMMIT, PAGE_READWRITE); //освобождаем под них память
i:= recv(BSock, buf^, avail_bytes, 0); //принимаем их от браузера
if i <= 0 then //если ошибка/соединение закрыто, то:
begin
VirtualFree(buf, 0, MEM_RELEASE);
goto 3;
end;
send(TargetSock, buf^, i, 0); //передаем клиенту
VirtualFree(buf, 0, MEM_RELEASE); //освобождаем память
end;


if FD_IsSet(TargetSock, fset) then //если есть данные от таргета, то:
begin
avail_bytes:= 0;
ioctlsocket(TargetSock, FIONREAD, avail_bytes); //читаем кол-во пришедших байт
buf:= VirtualAlloc(nil, avail_bytes, MEM_COMMIT, PAGE_READWRITE); //освобождаем под них память
i:= recv(TargetSock, buf^, avail_bytes, 0); //принимаем их от таргета
if i <= 0 then //если ошибка/соединение закрыто, то:
begin
VirtualFree(buf, 0, MEM_RELEASE);
goto 3;
end;
send(BSock, buf^, i, 0);
VirtualFree(buf, 0, MEM_RELEASE); //освобождаем память
end;



end; //while true do


3:
CloseSocket(BSock); //закрываем сокет браузера
CloseSocket(TargetSock); //закрываем сокет таргета

Result:= 0;
ExitThread(0); //выходим из потока
end;




var sock, ClientSock: TSocket;
wdata: TWSAData;
saddr: sockaddr_in;
id: DWORD;
label 1;
begin
if WSAStartup(MAKEWORD(1, 1), wdata) <> 0 then ExitProcess(0);
sock:= socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if sock = INVALID_SOCKET then ExitProcess(0);

ZeroMemory(@saddr, sizeof(saddr));
saddr.sin_family:= PF_INET;
saddr.sin_port:= htons(8080); //будем слушать на 8080 порту
saddr.sin_addr.S_addr:= inet_addr(IpToListen); //127.0.0.1
if bind(sock, saddr, sizeof(sockaddr_in)) <> 0 then ExitProcess(0);
if listen(sock, SOMAXCONN) <> 0 then ExitProcess(0);

1:
ClientSock:= accept(sock, nil, nil);
CreateThread(nil, 0, @OurBrowserListening, pointer(ClientSock), 0, id);
goto 1; //Если подключился, ждем подключения снова.

WSACleanup();



end.


Я так думаю, что проблема зарыта где-то уже на этапе перенаправления данных от браузера к таргету и наоборот, но где именно - не могу найти никак, скоро на стену полезу. Надеюсь на вашу помощь.

Тому, кто поможет, скромное вознаграждение размером в 20$. Очень надеюсь на вас, а то я уже сломал мозг.

Faost
14.06.2010, 18:37
Неужели на ачате совсем не осталось знающих людей? Может тогда посоветуете подходящий форум?

Ra$cal
14.06.2010, 18:56
попробуй на васме спросить. здесь мало кто таким программингом занимается. В основм хттпвебреквест, на делфе сделать проверку 20 радиобатонов через ифы, итп.

fluffylion
14.06.2010, 19:10
Faost, как вариант можно разбить обработчик сессии на 2 потока, в одном передавать данные от браузера к таргету, в другом от таргета к браузеру

Faost
14.06.2010, 19:23
Ra$cal, спасибо, попробую. Только не побьют ли меня там за делфийский код...)
А то билдер долго качать и устанавливать, чтоб проверить, так ли переписал на C.
fluffylion, я и так пробовал, проблема аналогичная.

Ra$cal
14.06.2010, 19:35
не побьют, но помогать будут медленнее =) накидай алгоритм словесный примерный лучше. я вот тоже даже не пытался смотретоь на код, ибо делфи.

bons
14.06.2010, 21:43
компилить и отлаживать код не пытался, так что просто опишу что я заметил, просматривая это:
- когда принимаешь заголовок socks не проверяешь количество принятых байт
- неправльная работа с select. Проблемы с mail.ru полагаю из-за этого. Ты не проверяешь возможность того что селект возвратил управление по таймауту. Ты не обновляешь набор сокетов перед использованием селекта. Короче, читай маны...
- зачем-то использованы одновременно потоки и select
- VirtualAlloc для выделения буфера памяти каждый раз при приеме данных очень круто и оптимально... вообще выделяемая этой функцией память округляется до страницы(64 кб). Почему бы не использовать буфер в стеке?
- когда таргет закрывает соединение, надо сначала отправить все данные в его буфере клиенту, а потом уже корректно завершить соединение с помощью shutdown а не обрывать его.

стиль:
- зачем goto? тут можно было бы и без него
- нет отступов адекватных


дополнительные советы:
если будут еще проблемы, попробуй использовать сниффер для отладки...

PS за такой код на васме точно побьют ;)

slesh
14.06.2010, 22:00
дажа разбиратсья влом, глядя на этот код.
Оформление нулевое и ужасный код.

Faost
14.06.2010, 22:14
- когда принимаешь заголовок socks не проверяешь количество принятых байт
Ну это само собой, что нужно проверять, это все отлажено - тут проблем не возникает, поэтому для большей минимальности убрал проверку.
- неправльная работа с select. Проблемы с mail.ru полагаю из-за этого. Ты не проверяешь возможность того что селект возвратил управление по таймауту. Ты не обновляешь набор сокетов перед использованием селекта.
Насчет таймаута - я ставил даже на "бесконечное" ожидание - select(0, @fset, nil, nil, nil), проблема абсолютно идентична. Кроме того, маил.ру ну полюбасу ответит в течении 8сек на 1 запрос.
Ты не обновляешь набор сокетов перед использованием селекта
FD_ZERO(fset);
FD_SET(BSock, fset);
FD_SET(TargetSock, fset);
зачем-то использованы одновременно потоки и select
В главном потоке сервер слушает подключившихся клиентов, причем слушать след.клиента надо сразу после подключения первого.
VirtualAlloc для выделения буфера памяти каждый раз при приеме данных очень круто и оптимально... вообще выделяемая этой функцией память округляется до страницы(64 кб). Почему бы не использовать буфер в стеке?
Учту.
когда таргет закрывает соединение, надо сначала отправить все данные в его буфере клиенту, а потом уже корректно завершить соединение с помощью shutdown а не обрывать его.
А какие данные то отправлять, если они передаются ему по мере поступления от таргета? Как только таргет отправил данные, они отправились клиенту (браузеру). Если таргет вместо того, чтобы отправить данные, разрывает соединение, то что еще остается передать браузеру? Насчет shutdown - пробовал, результат тот же(

зачем goto? тут можно было бы и без него
можно было. Мне кажется, для форумчан использование goto очень-очень наглядно. Иначе, с множеством begin;end было бы жесть.
Оформление нулевое и ужасный код.
Насчет оформления - да тут и оформлять то собственно нечего, в коде нет и 200 строк. Насчет кода - поправь меня, скажи где что КРИТИЧЕСКИ криво (не учитывая goto, проверку того, в чем я уверен и что ошибочным в ходе ПРАВИЛЬНОГО теста быть не может - например, заголовков сокс. Также не считаю критически кривым кучу ExitProcess в начале, т.к. мне кажется, что это поспособствовало бы лучшему пониманию, чем множество begin;end). Ребята, вы же понимаете, что это не исходник на продажу/не для всеобщего использования в повседневной жизни, поэтому о каких программерских тонкостях типа goto может идти речь?
P.S.
Да, я злой и грубый, а что остается еще делать видя этот кривой код и радостные лица тех, кто его написал.
Лицо у меня далеко не радостное :(