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.


Log in to reply