Sockets, Denkfehler und Serverabsturz



  • Hallo alle zusammen _

    ich bin im Moment dabei meinen 2. Instant Messenger zu schreiben 😃
    Ich schreibe das Programm in C++ und nutze wxWidgets. Kompiliert wird es sowohl auf Linux (Ubuntu 13.04, Code::Blocks) als auch auf Windows (Windows 8, Visual Studio 2012).

    Ich habe schon einmal einen Instant Messenger geschrieben, da war die Art und Weise wie die Daten übertragen wurden etwas primitiv. Jetzt wollte ich mit Headern arbeiten, doch da zeigen sich einige Lücken in meinen Gedanken ^^

    Alte Version:
    Ich hab einfach alle benötigten Daten die ich brauche, in ein vordefiniertes Packet (Struktur) gepackt, (bsp [int][char 1024][char 1024][char4096]). Ich hatte 2 Parameterfelder, ein Feld für größere Informationen und eine ID, um was es sich hierbei handelt.
    Vorteil: Es ist simpel. die Struktur kann man über reinterpret_cast ganz einfach in ein char* verwandeln und über die Leitung senden.
    Nachteil: Selbst bei kleinen Informationen werden (relativ) gigantische Packete rausgesendet. Das gesamte System ist nicht dynamisch.

    Neue Version:
    Ich teile das Paket in Header und Databody. Die eigentlichen Informationen sind dann im Databody. Dabei kann es sich um Zahlen, Namen oder Binärdateien handeln (Updates oder Filetransfer). Der Header ist eine Struktur, die dann eine ID und die Größe des Databodys enthällt.
    Vorteil: Es ist dynamisch. Es ist möglich bei ganz einachen Anfragen den Databody leer zu lassen und die Anfrage über die ID zu identifizieren. Es ist was anderes __
    Nachteil: Höherer Aufwand (Nehm' ich gern zu kauf), siehe Problem.

    Aktuell:
    Ich hab es soweit zu (virtuellem) Papier gebracht und es funktioniert einwandfrei. Dadurch, dass ich vor dem senden Header und Databody in ein stringstream klatsche, wird alles anhängend gesendet und 2 Pakete überschneiden sich nicht. Wird ein Paket empfangen, so passiert folgendes:
    Es wird zuerst mit

    socket->Read(reinterpret_cast<char*>(&header), sizeof(Header));
    

    den Header gelesen (Header ist die Struktur, header eine "Instanz", nennt mans so bei einer Struktur? :p ), dann ein buffer vom Typ char mit der Größe header.size erstellt und gefüttert. Danach wird über die ID geguckt, was mit dem Databody passiert 😃

    Problem:
    Ich habe mir gedacht, was passiert, wenn ich jetzt ein Zeichen über PuTTY an meinen Server sende. Es Passiert folgendes: sollten die Zeichen über die Bytes laufen, die die Größe beschreiben, aber nicht länger sein, als der Header, so verbleibt er beim Lesen des Databodys, und sobald jemand dann vom richtigen Client auf den Server zugreift, überlappt das sich und der Server schafft nur noch Chaos. Mit anderen Worten: Sobald ein undefiniertes Paket, wie eine Eingabe durch Putty, einem Programm, das auf die falsche Portnummer zugreift oder ein unvollständiges Paket ankommt, so killt der Server sich selbst.

    Weiß jemand wie ich dieses Problem lösen kann?
    Ich bedanke mich schon mal im voraus 😃

    Mit freundlichen Grüßen,
    MatStorm



  • Kurz: Du willst erkennen, wenn ein Client sich nicht an dein "definiertes" Protokoll haelt?

    Da du Binaerdaten versendest, beispielsweise Laenge etc. kann am Binaermuster nicht unterschieden werden, um welche Daten es sich handelt. D.h. du musst Zusatzinformationen zu den Daten mitsenden.



  • Jein, ich will, dass der Server erkennt, ob er da jetzt einen richtigen Header gelesen hat, oder ob das was er gelesen hat, garnicht der Header ist.
    Das Problem ist ja, wenn ein Client falsche Informationen sendet (beispielsweise, wenn man sich mit einem Terminal Programm verbindet und einfach irgendwelche zeichen sendet) verschieben sich alle Dateien und kommen durcheinander.

    Bei den Binärdaten bin ich noch nicht. Da habe ich ebenfalls vor, dass eben im Databody zuerst ein Header mit den Informationen zur Datei und danach die Datei selbst folgt.

    Ich versuche mal das Problem grafisch darzustellen, hoffe dass es bischen zum Verständnis hilft 😛

    (H = Header | I = Information | X = Fehlinformation | U = Undefiniert)

    Ohne Problem:
    Client sendet: HHHH IIIIIIIIII
    Server empfängt: HHHH
    in HHHH steht, I ist 10 zeichen lang
    Server empfängt IIIIIIIIII
    alles ok

    Mit Problem
    Angreifer sendet: XXX
    Client sendet: HHHH IIIIIIIIII
    Server empfängt: XXXH
    in XXXH steht, I ist 5 zeichen lang (bspw. das 3. 😵
    Server empfängt: HHHII
    Server empfängt: IIII
    in IIII steht, I ist 15 zeichen lang
    Server empfängt: IIIIUUUUUUUUUUU
    ein richtig gesendetes Paket. 2 falsch empfangene Pakete

    Hoffe das ist etwas verständlicher ^^



  • Wie kann es sein, dass der Angreifer in der gleichen Verbindung hängt wie der richtige Client? Es gibt sowas wie TCP-Sequenznummern, das geht eigentlich nicht. Oder wie funktioniert dein Protokoll, dass so etwas möglich ist?

    Ansonsten denke ich, es führt zu nichts, herausfinden zu wollen, was ein "echter" Header ist und was nicht. Geh einfach davon aus, dass alles echt ist und sorg dafür, dass dein Server dabei nicht abstürzt.



  • Hoffe das ist etwas verständlicher

    Nein. Beispiel: Bedeutet HHH das 3 Header gesendet werden?



  • Okay langsam glaube ich, dass die Ursache, die ich vermute, falsch ist 😕
    Seh ich das richtig: Egal wie die Verbindungen geknüpft sind, die empfangenen Daten werden in einen Speicher, quasi einer virtuellen Datei, gespeichert, die ich dann mit Read auslese. Dann habe ich gedacht die Ursache ist, dass falsche Bytes reinrutschen, und beispielsweise die Zeichen an Stelle 8 auf Stelle 12 rutschen. Aber das scheint ja nicht zu sein. Ich muss mal mit den Speichern bisschen rumexperimentieren^^

    Fakt ist: Sobald ich Daten über PuTTY einschleuse, stürtzt der Server ab, und das ist bei einem Server nun wirklich nicht witzig 😛

    Vll kann mir jemand von den Code-Schnipseln sagen, woran es liegen könnte.

    Es wird im moment nur eingeloggt, wobei der Databody das md5-verschlüsselte Passwort beinhaltet.

    Header:

    struct Header
    {
        char username[32];
        unsigned short id;
        unsigned int size;
        unsigned short reserved;
    };
    

    Senden des Logins(Client):

    Header header;
        strcpy(header.username, iUsername->GetValue().mb_str());
        header.id = S_LoginRequest;
        header.size = password.length()+1;
        header.reserved = 0x0000;
    
        stringstream stream;
        stream.write(reinterpret_cast<char*>(&header), sizeof(Header));
        stream.write(datablock, header.size);
        char checkbyte = CHECKBYTE;
        stream.write(&checkbyte, 1);
    
        socket->Write(stream.str().c_str(), sizeof(Header) + header.size + 1);
    

    Empfangen der Daten(Server):

    Header header;
        socket->Read(reinterpret_cast<char*>(&header), sizeof(Header));
        char *data = new char[header.size+1];
        char checkbyte;
        socket->Read(data, header.size);
        socket->Read(&checkbyte, 1);
    


  • Warum schreibst du nicht mittels socket->write genauso wie du mit socket->read liesst? Warum der Umweg ueber stringstream? Warum verwendest du strcpy?



  • Ich hab mir gedacht, dass dadurch die Daten in einem Rutsch durch die Leitung geht und dadurch die Wahrscheinlichkeit auf Fehler sinkt. Liege ich damit Falsch? Wenn ja, werde ich das abändern^^

    header.username ist ja ein C-String. iUsername ist eine Instanz vom Typ wxTextCtrl, das Eingabefelt. Mit ->GetValue() bekomme ich ein wxString zurück, und mit ->mb_str() den C-String. Mit strcpy kopiere ich den einen String in den anderen. Aber das funktioniert doch?



  • Fehler? Beim Übertragen? Schau dir doch mal die Wikipedia-Seite vom TCP Protokoll an (davon ausgehend, dass du TCP benutzt, falls nicht: Wieso sollten dadurch weniger Fehler auftreten?).



  • Alles mit 1x send abschicken ist schon gut (zumindest so lange "alles" nicht extrem gross wird).
    send-send-recv ist so ziemlich die schlechteste Abfolge die man haben kann.

    Dabei geht's aber um Performance und nicht um Sicherheit.



  • @-Infected-
    Ich benutze TCP, UDP währ für einen Instant Messenger wohl die falsche Wahl. Mit Write werden die Daten doch direkt ausgesendet oder? Wenn ich jetzt den Header und den Datenteil mit zwei Write-Methoden aussende, so werden die ja getrennt voneinander gesendet oder? Ich weiß, dass TCP dafür sorgt, dass die Pakete in der richtigen Reihenfolge und auch Vollständig ankommen, aber wenn 2 Clients gleichzeitig senden hatte ich die Befürchtung, dass zuerst der Header von Person A, dann der Header von Person B, dann der Datenteil von Person A und dann erst der Datenteil von Person B kommt, und dachte dies so vorgebeugt zu haben. Oder wird das erst in einen Buffer geschrieben und dann direkt ausgesendet?

    @hustbaer
    Ich benutz zum Login-Test den Benutzername Test und das Passwort hallo. Das Paket ist dann 77 Byte groß. Sobald es zum Dateitransfer geht (wovon ich noch weit weg bin) werde ich warscheinlich den reserved-Teil benutzen um das Paket als Write oder Append zu beschreiben und dann nur Bruchteile senden.

    Ich glaube aber wir kommen vom Problem ab. Ich hab zwar im Code herumgespielt aber nichts dabei erreicht. Wenn ich in Putty eindach irgendwelche Daten sende, hängt sich der Server auf, samt grafischer Oberfläche...
    -EDIT-
    Nochmal kurz: Die ganze Anwendung funktioniert. Nur wenn sich ein Skriptkiddy denkt, er müsse mal den Server zur Leistungsverweigerung anstiften, braucht er nichts weiter, als Putty oder ein vergleichbares Programm, das Daten an den gewählten Port senden, zu nutzen und der Server hängt.

    ---EDIT---
    PROBLEM GELÖßT!
    Habe das Problem gelöst. Es funktioniert jetzt alles. Der Thread kann gelschlossen werden.

    Wen es interessiert: ich habe dem Server-Socket die Eigenschaft wxSOCKET_NOWAIT beigefügt. Danke euch allen. 😃



  • OK.
    Warum guckst du nicht nach wo der Server hängt statt rumzuraten bzw. uns darum zu bitten für dich zu raten?

    Um die Antwort auf die nächste Frage vorwegzunehmen: Du startest das Programm im Debugger, machst dass es hängenbleibt und drückst dann auf "Pause".

    ps: ich behaupte mal dass wir die wesentlichen Teile deines Servers her nicht sehen. Nämlich die die dafür sorgen dass mehrere Verbindungen gleichzeitig "bedient" werden können.



  • MatStorm schrieb:

    Habe das Problem gelöst. Es funktioniert jetzt alles. Der Thread kann gelschlossen werden.

    Was passiert denn jetzt wenn ich dir einen Header schicke, der als Größe riesige oder von deinem Programm als negativ interpretierte Zahlen enthält? Allokierst du dann gigabyteweise Speicher?

    Ganz abgesehen davon, dass dein direktes Einlesen von Netzwerkdaten in eine C-Struktur ziemlich verrückt ist. Das wird doch niemals über verschiedene Systeme (mit anderen Compilern, Alignmentanforderungen, usw/) hinweg funktionieren.



  • Vergiss den negativ-Halbsatz.


Anmelden zum Antworten