Socks-сервер WinSock

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by Faost, 13 Jun 2010.

  1. Faost

    Faost New Member

    Joined:
    11 Jul 2009
    Messages:
    18
    Likes Received:
    3
    Reputations:
    0
    Т.к. в моей пред.теме о socks никто ничем помочь не смог, создаю еще одну.

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

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

    Качаем соксификатор, настраиваем в нем socks4 (Сервер: 127.0.0.1, порт: 8080).


    Качаем исходник socks4-сервера (залил для большей наглядности)

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

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

    Код:
    Code:
    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$. Очень надеюсь на вас, а то я уже сломал мозг.
     
  2. Faost

    Faost New Member

    Joined:
    11 Jul 2009
    Messages:
    18
    Likes Received:
    3
    Reputations:
    0
    Неужели на ачате совсем не осталось знающих людей? Может тогда посоветуете подходящий форум?
     
  3. Ra$cal

    Ra$cal Elder - Старейшина

    Joined:
    16 Aug 2006
    Messages:
    670
    Likes Received:
    185
    Reputations:
    78
    попробуй на васме спросить. здесь мало кто таким программингом занимается. В основм хттпвебреквест, на делфе сделать проверку 20 радиобатонов через ифы, итп.
     
  4. fluffylion

    fluffylion Member

    Joined:
    22 Feb 2010
    Messages:
    55
    Likes Received:
    10
    Reputations:
    0
    Faost, как вариант можно разбить обработчик сессии на 2 потока, в одном передавать данные от браузера к таргету, в другом от таргета к браузеру
     
  5. Faost

    Faost New Member

    Joined:
    11 Jul 2009
    Messages:
    18
    Likes Received:
    3
    Reputations:
    0
    Ra$cal, спасибо, попробую. Только не побьют ли меня там за делфийский код...)
    А то билдер долго качать и устанавливать, чтоб проверить, так ли переписал на C.
    fluffylion, я и так пробовал, проблема аналогичная.
     
  6. Ra$cal

    Ra$cal Elder - Старейшина

    Joined:
    16 Aug 2006
    Messages:
    670
    Likes Received:
    185
    Reputations:
    78
    не побьют, но помогать будут медленнее =) накидай алгоритм словесный примерный лучше. я вот тоже даже не пытался смотретоь на код, ибо делфи.
     
  7. bons

    bons Elder - Старейшина

    Joined:
    20 Dec 2007
    Messages:
    286
    Likes Received:
    121
    Reputations:
    21
    компилить и отлаживать код не пытался, так что просто опишу что я заметил, просматривая это:
    - когда принимаешь заголовок socks не проверяешь количество принятых байт
    - неправльная работа с select. Проблемы с mail.ru полагаю из-за этого. Ты не проверяешь возможность того что селект возвратил управление по таймауту. Ты не обновляешь набор сокетов перед использованием селекта. Короче, читай маны...
    - зачем-то использованы одновременно потоки и select
    - VirtualAlloc для выделения буфера памяти каждый раз при приеме данных очень круто и оптимально... вообще выделяемая этой функцией память округляется до страницы(64 кб). Почему бы не использовать буфер в стеке?
    - когда таргет закрывает соединение, надо сначала отправить все данные в его буфере клиенту, а потом уже корректно завершить соединение с помощью shutdown а не обрывать его.

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


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

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

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    дажа разбиратсья влом, глядя на этот код.
    Оформление нулевое и ужасный код.
     
  9. Faost

    Faost New Member

    Joined:
    11 Jul 2009
    Messages:
    18
    Likes Received:
    3
    Reputations:
    0
    Ну это само собой, что нужно проверять, это все отлажено - тут проблем не возникает, поэтому для большей минимальности убрал проверку.
    Насчет таймаута - я ставил даже на "бесконечное" ожидание - select(0, @fset, nil, nil, nil), проблема абсолютно идентична. Кроме того, маил.ру ну полюбасу ответит в течении 8сек на 1 запрос.
    FD_ZERO(fset);
    FD_SET(BSock, fset);
    FD_SET(TargetSock, fset);
    В главном потоке сервер слушает подключившихся клиентов, причем слушать след.клиента надо сразу после подключения первого.
    Учту.
    А какие данные то отправлять, если они передаются ему по мере поступления от таргета? Как только таргет отправил данные, они отправились клиенту (браузеру). Если таргет вместо того, чтобы отправить данные, разрывает соединение, то что еще остается передать браузеру? Насчет shutdown - пробовал, результат тот же(

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