Probleme mit Socks5 Proxyserver



  • Hab einen Socks5 Proxyserver geschrieben, hat ziemlich lange gedauert und jetzt funktioniert er nicht richtig. Er läuft vieleicht par Minuten und dann wird keine Seite mehr angezeigt.
    Erstes Problem ist schonmal wie ich erkennen soll ob das was vom Client kommt eine HTTP request ist oder Socks5. Ich hab das jetzt so gemacht dass ich Prüfe ob im ersten Feld des empfangenen Arrays der Wert 5 ist, sollte im HTTP request nicht der Fall sein. Ich kann hier unmöglich den gesamten Code posten weil das viel zu viel wär, aber hier sind die Funktionen der Socks5Socket Klasse:

    http://pastebin.com/MfFdLBt9

    Das läuft alles über asynchrone Sockets, ich verarbeite alles in der Window Proc. Hier werden die Funktionen aufgerufen:

    Socks5Socket* s=(Socks5Socket*)findSocket(clients, MAX_CLIENTS, wParam);
    					if(s==NULL)
    						{
    							cout<<"Socket für FD_READ nicht gefunden."<<br;
    							return -1;
    						}
    					char buffer[BUF_SIZE];
    					int nread=0;
    
    					if((nread=s->recv(buffer,BUF_SIZE-1))<=0)
    					{
    						s->close();
    						return -1;
    					}
    
    					if(buffer[0]==5 && nread==buffer[1]+2)
    					{
    
    						s->socks5auth(buffer, nread);	//auf authentifizierungsmethode einigen				
    					}
    					else if(buffer[0]==5 && buffer[2]==0)
    					{
    
    						s->socks5connect(buffer,nread, hWnd);//verbindungsanfrage annehmen und verbinden
    					}
    					else 
    					{
    
    						s->socks5recv(buffer, nread);	//daten austauschen
    					}
    

    Das entsprechende Socks5Socket Objekt aus dem Array mit findSocket rauszusuchen passiert auch bei FD_CLOSE. Aber manchmal wird da nichts gefunden. Die Funktion geht nur alle Objekte im Array durch und schaut ob der SOCKET member == wParam also der SOCKET ist um den es bei dieser Nachricht geht. Wenn im array nichts gefunden wird wird einfach trotzdem versucht den SOCKET in wParam mit closesocket() zu schließen.

    Sieht irgendwer da ein Problem?



  • Ähm ... wenn du einen Socks Proxy schreibst, brauchst du doch nicht auf HTTP-Inhalte prüfen, oder verstehe ich da was falsch?

    Ich habe bisher immer nur einen Client-Support für Socks entwickelt, und meine daher, dass eine Proxy-Verbindung zwischen 2 Modi unterscheiden muss:
    A) Anfrage- und Authentifizierung
    😎 Datenweiterleitung

    Wenn du mit A beginnst, ist alles, was du vom Client bekommst reines Socks-Protokoll. So teilst du deinem Server ja mit, wohin er die folgenden Daten hinschicken muss. Nun kannst du einen zweiten Socket zur gewünschten anderen Seite aufbauen.
    Im Modus B werden alle Bytes die du auf einer Seite empfängst an die jeweils andere weitergeschickt... es soll dich gar nicht interessieren, welches native Protokoll der ursprüngliche Client und der Ziel-Server spricht.

    Wenn du einen Puffer von BUF_SIZE bytes anlegst, darfst du ruhig auch N Bytes mit recv lesen und nicht nur BUF_SIZE - 1. Auf was du aber achten solltest sind böse Clients, die ihre Anfrage Byte-weise schicken. Angenommen dein recv liest nur 1 Byte in den Puffer, so wird dein folgender Code

    if(buffer[0]==5 && nread==buffer[1]+2)
    

    undefiniertes Verhalten liefern, weil ja nur das erste Byte im Puffer wirklich vom Socket stammt, der Rest ist Speichermüll.

    Das entsprechende Socks5Socket Objekt aus dem Array mit findSocket rauszusuchen passiert auch bei FD_CLOSE

    Die korrekte Verwaltung der Socket-Klassen musst du umsetzen. Es kann natürlich vorkommen, dass entweder der Client oder aber der Ziel-Server einfach die Verbindung schließt. Dann musst du die entsprechende andere Verbindung auch schließen.
    Genauer: Es ist der Write-Shutdown, den du empfängst (recv => 0 bytes), den du weiterleitest, indem du den anderen Socket auch mit shutdown(SD_SEND) zum Teil schließt. Bedenke, dass solche halb-offenen Sockets manchmal vom Protokoll gewünscht sind. (HTTP-Server machen das beim Header "Connection: Close" gerne!)
    Erst wenn beide Seiten ihre Sockets für weiteres Schreiben heruntergefahren haben, solltest du beide Sockets schließen und löschen.

    Aber manchmal wird da nichts gefunden.

    Da hast du offenbar vergessen für einen Socket eine deiner Socks5Socket Objekte anzulegen, oder? 😉

    cu



  • ich dachte erstmal dass man für jede Seite einen neuen Socket erstellt, aber der Client schickt auch nachdem man schon zu einem Webserver verbunden und die Seite geholt hat einfach eine neue Anfrage mit Authentifizierung und Verbindungsdaten. Deswegen prüfe ich den Inhalt.
    Deinen Punkt A hab ich in 2 Funktionen geteilt weil das non-blocking ist und ich nicht weitere Daten einfach empfangen kann in der Funktion. Das muss alles bei FD_READ gemacht werden.
    Die -1 ist dafür da dass ich ne 0 am Ende einfügen kann um den buffer auszugeben. Meistens sind das nur unlesbare Zeichen, zum Beispiel sendet das Ziel ein array zurück das mit 23,3,1,0 anfängt, das sind keine Buchstaben und so sieht soweit ich weis kein HTTP header aus.
    Das ganze ist auch ziemlich langsam, ich weis nicht genau wieso.



  • Hallo nochmal

    Ich finde es immer gut, wenn sich die Leute direkt mit Netzwerken befassen und HTTP-Proxy oder Socks-Support einbauen, weil es Orte gibt, wo Routing nicht gestattet ist und viele Programme einfach nicht mehr funktionieren, weil sie nicht mit Proxies reden können. 👍 😉

    Du arbeitest wie es aussieht mit WSAAsyncSelect, damit habe ich eigentlich nie gearbeitet, sondern immer mit select(), welches in einem eigenen Thread ausgeführt wird.

    Nur das wir uns verstehen:
    Wenn ein Client sich zu deinem Proxy-Server verbindet, teilt er ihm über das SOCKS Protokoll mit, wohin der Proxy-Server die zweite ausgehende Verbindung aufbauen soll. Sobald dieser Vorgang abgeschlossen ist, leitet der Proxy nur noch Bytes zwischen den beiden Sockets hin und her ohne zu wissen, was die Daten bedeuten.
    Daher der Gedanke mit Modus A und B. Bist du in B, brauchst du nie wieder in deinen Puffer hineinschauen, sondern nur seinen Inhalt über den jeweils anderen Socket senden.

    Folgende Punkte kann ich dir bei der Entwicklung empfehlen:

    • Wenn du Daten liest, schreibe sie in einem Puffer, der mit deinem Socket assoziert ist. Teste, ob die Daten im Puffer ausreichen, um das SOCKS-Protokoll auszuparsen.
      Dass nämlich so ein SOCKS-Request in einem Stück bei dir ankommt ist Zufall. Er kann auch in lauter einzelnen Bytes eintreffen.
      Deshalb: Immer alles Puffern und weiterlesen, falls die Daten im Puffer nicht ausreichen um den Inhalt zu interpretieren.
    • Puffere auch zwischen den beiden Sockets:
      Es kann sein, dass du auf einem Socket 4 KBytes als Block empfängst, aber auf dem anderen Socket im Moment nur 1 KByte versenden kannst.
      Der Sendevorgang muss daher in mehrere Stücke aufgeteilt werden. send() teilt dir ja mit, wieviel es tatsächlich versenden konnte und was nicht.
      Gehen dir hier Bytes verloren, korrumpierst du das Protokoll.
    • Und nun zur etwas komplexeren shutdown-Geschichte:
      Sockets werden in manchen Protokollen nicht einfach mit "closesocket()" geschlossen, sondern der erste Endpunkt, der nichts mehr senden
      will ruft shutdown(SD_SEND) auf. Der zweite Endpunkt wird, nachdem alle Daten gelesen wurden, recv() mit "0" zurückkehren lassen und dann wissen, dass er nie wieder etwas empfangen wird.
      Nun kann auch der zweite Endpunkt, wenn er auch nichts mehr zu senden hat, ebenfalls shutdown(SD_SEND) aufrufen, damit auch der erste Endpunkt per recv() mit "0" erfährt, dass keine Daten mehr empfangen werden.
      Erst jetzt schließen beide Seiten mit closesocket() ihre Verbindungen endgültig.

    So wie es in der Doku steht, erhälst du ein FD_CLOSE genau dann, wenn recv() nichts weiteres mehr empfangen kann und "0" zurückgeben würde.

    Client (shutdown) -> Server (FD_CLOSE)
    Server (shutdown) -> Client (FD_CLOSE)

    Ein dummer HTTP Client (der kein Connection-Keep-Alive und Content-Length kennt - ja das gibt es wirklich) würde folgendes machen:

    1. Verbindung herstellen "connect()"
    2. HTTP-Request senden "send()"
    3. Sende-Seite herunterfahren "shutdown(SD_SEND)"

    der HTTP Server geht so vor

    1. Verbindung annehmen "accept()"
    2. Alle Daten lesen bis keine mehr kommen (also "recv()" == 0)
    3. HTTP-Antwort samt Content an den Client senden "send()"
    4. Socket schließen "closesocket()", da nichts mehr gelesen und auch nichts mehr gesendet werden wird.

    der HTTP Client macht dann:

    1. Alles vom Server lesen, bis nichts mehr kommt ("recv()" == "0")
    2. Socket schließen "closesocket()", da nichts mehr gelesen und auch nichts mehr gesendet werden wird.

    Und ein Proxy sollte dieses Verhalten zuverlässlich nachstellen können, oder es kommt zu Protokoll-Problemen.
    Wenn du nämlich beim ersten recv() == 0 oder FD_CLOSE beide Sockets schließt, hast du eventuell noch nicht ausgelieferte Daten der Gegenseite verworfen. Machst du das bei einem HTTP-Transfer, wird der Browser nicht erfreut darüber sein und eine weitere Verbindung aufmachen, oder fehlschlagen.
    ... und das könnte deine Probleme erklären.

    Client (shutdown) -> ProxyServerSocket (FD_CLOSE) -> ProxyClientSocket (shutdown) -> Server (FD_CLOSE)
    Server (shutdown) -> ProxyClientSocket (FD_CLOSE) -> ProxyServerSocket (shutdown) -> Client (FD_CLOSE)

    cu


Log in to reply