Advanced Winsock: AcceptEx und DoS-Angriffe



  • Hallo!

    ich programmiere als Schulprojekt gerade einen einfachen TCP-Server mit Overlapped I/O und IoCompletion-Ports. Für das Eingehen neuer Verbindungen nutze ich AcceptEx() mit einem Receive-Buffer.

    Nun weiß ich, dass bei AcceptEx() erst die Completion eintritt (also wenn WSAOVERLAPPED::hEvent signaled ist), wenn eine Verbindung eingegangen wird und, sofern ein Buffer angegeben ist, die ersten Bytes gesendet werden. Nun habe ich von einem DoS-Problem gehört: Wenn eine Verbindung aufgenommen wird, dabei aber keine weiteres Bytes geschickt werden, returnt AcceptEx() nicht und es endet in DoS. Die Lösung soll irgendwie mit der Zeitmessung der aktiven Socket-Handels per SO_CONNECT_TIME und getsockopt() laufen, nur habe ich keine Idee, wie ich das einbauen soll. 😕

    Bisher lief das so ab:

    std::shared_ptr<per_connection_data> connection(new per_connection_data(accept_socket));
    connections.push_back(connection);
    
    if(accept_ex(listen_socket.get(), accept_socket.get(), accept_buffer, sizeof accept_buffer - ((sizeof(sockaddr_in) + 16) * 2), 
    					 sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, &bytes_received, &overlapped) == SOCKET_ERROR)
    		{
    		if(WSAGetLastError() != WSA_IO_PENDING)
    			throw std::runtime_error("Konnte eine neue Verbindung nicht eingehen!");
    	}
    
    for(;;) {
    	if(WSAWaitForMultipleEvents(1, event, FALSE, WSA_INFINITE, TRUE) == WSA_WAIT_FAILED)
    		throw std::runtime_error("Eine neue Verbindung konnte nicht eingegangen werden!");
    	if(WSAGetLastError() != WAIT_IO_COMPLETION)
    		if(running)
    			break;
    		else {
    			std::cout << "Server-Thread wird beendet...\n";
    			return 0;
    		}
    
    }
    

    WaitForMultipleEvents() blockiert ja dann endgültig, bis AcceptEx fertig ist. Wo kommt das nun alles hin mit dem getsockopt()?



  • Ad aCTa schrieb:

    Hallo!

    ich programmiere als Schulprojekt gerade einen einfachen TCP-Server mit Overlapped I/O und IoCompletion-Ports. Für das Eingehen neuer Verbindungen nutze ich AcceptEx() mit einem Receive-Buffer.

    Klassischer Fall von Premature Optimization



  • Bist du unter Drogen?
    Halts **** wenn du nix vernünftiges beizutragen hast du Oberpro :mad 👎



  • also cih verstehe dich acuh nicht...
    wenn es ein schulprojekt ist hat wohl kaum jemand daran interesse seine kraft dafür aufzubrauchen bei deinem prog ne denial of service attace durchzuführen...



  • Hi

    Und ausserdem leg ich dir dein Server'chen auch so lam !!
    Und da fählt mir nicht nur eine Methode ein

    l-o-w-b-y-t-e



  • Hi!

    Ich glaube, es gibt eine Möglichkeit, mit AcceptEx() noch keinen Empfang durchzuführen. Irgendwie ging das, aber mir fällt gerade nicht ein, wie, sorry 😞



  • > Klassischer Fall von Premature Optimization

    Was hat das damit zu tun? Irgendwie muss man ja anfangen asynchrone I/O zu programmieren, dass dabei kein 1337-Server rauskommt ist mir erst mal egal. Produktiv soll der Server sowieso nicht sein. Und btw.: Boost setzt auch darauf, also macht jeder, der boost::asio benutzt auch Premature Optimization?

    > hat wohl kaum jemand daran interesse seine kraft dafür aufzubrauchen bei deinem prog ne denial of service attace durchzuführen...

    Darum geht es mir doch gar nicht. Wenn ich aber zukünftig was damit anfangen will, sollte ich ja irgendwann mal lernen, wie man mit AcceptEx() sicher umgeht.

    > Und ausserdem leg ich dir dein Server'chen auch so lam !!
    > Und da fählt mir nicht nur eine Methode ein

    Echt? Ist der kurze Codeabschnitt schon so unsicher?

    > Ich glaube, es gibt eine Möglichkeit, mit AcceptEx() noch keinen Empfang durchzuführen.

    Ja, keinen Receive-Buffer angeben. 😃 Aber das kann nicht Sinn der Übung sein. Aber wieso müssen gleich fast alle wieder auf mir rumhacken, nur weil ich versuche, mir etwas über Server-Bau an zu eignen? 😞



  • Also.
    z.T. DOS.

    Eine SYN-Flood-Attacke kannst du soweit ich weiss auf Sockets Ebene gar nicht sinnvoll verhindern. Dafür hat man entweder eine Firewall die das kann, oder man hat eben einen Server der nicht vor SYN-Flood-Attacken geschützt ist. Meist auch kein Beinbruch - erst muss man es mal schaffen überhaupt ein interessantes Ziel zu sein.

    Eine einfachere Attacke, wo die Connections noch vollständig hergestellt werden, aber danach keine Daten gesendet, kannst du verhindern, indem du ein Timeout pro Connection einbaust.
    Wenn X Sekunden nix daherkommt -> Verbindung trennen.
    Wenn es eine "einfache" DOS Attacke ist, kann man sich die Client-IPs der letzten paar Connections merken die aus diesem Grund getrennt wurden. Wenn dann innerhalb der nächsten paar Minuten wieder eine Verbindung von dieser IP reinkommt, kann man die gleich (oder mit wesentlich verkürztem Timeout) trennen.

    Wenn du das Annehmen der Connection und das Lesen des ersten Datenblocks in zwei getrennten Schritten machst, ist das relativ einfach umzusetzen. Du bekommst sofort nach Annehmen der Connection einen neuen Socket-Descriptor. Und diesen Socket-Descriptor kannst du jederzeit einfach mit close() killen, wenn das Timeout abgelaufen ist. Dazu kannst du z.B. einfach alle paar Sekunden alle Connections durchgehen, und gucken bei welchen das Timeout abgelaufen ist.

    Wenn du mittels AcceptEx beides in einem Schritt machen willst, wird alles nur unnötig komplizierter. Wozu also den ganzen Tanz? Nur weil es mit AcceptEx um ein paar Takte schneller ist?

    Ja, keinen Receive-Buffer angeben. Aber das kann nicht Sinn der Übung sein. Aber wieso müssen gleich fast alle wieder auf mir rumhacken, nur weil ich versuche, mir etwas über Server-Bau an zu eignen?

    Doch, genau das ist der Sinn der Übung. Keinen Receive-Buffer angeben. Kannst du mir einen Grund nennen warum du unbedingt einen Receive-Buffer bei AcceptEx angeben willst? Einfach nur weil es geht? Weil du wo gelesen hast dass es cool ist? Weil ... ?



  • Hi

    @hustbaer

    Das ist nicht richtig !

    Eine SYN-Flood-Attacke kannst du soweit ich weiss auf Sockets Ebene gar nicht sinnvoll verhindern.

    Oh doch warum nicht ? Stichwort :SYN-Cookies
    Doch da musst du eine ebene tiefer arbeiten. Aber das ist dir glaube ich klar.

    lowbyte



  • lowbyte_ schrieb:

    Eine SYN-Flood-Attacke kannst du soweit ich weiss auf Sockets Ebene gar nicht sinnvoll verhindern.

    Oh doch warum nicht ? Stichwort :SYN-Cookies
    Doch da musst du eine ebene tiefer arbeiten. Aber das ist dir glaube ich klar.

    Klar kann man immer irgendwo irgendwie was machen. Nur eben nicht mit der klassischen Berkeley-Sockets-API.



  • > Nur eben nicht mit der klassischen Berkeley-Sockets-API.

    Die ich ja auch kaum benutze. Egal, wie obercool ist jetzt bin 🕶 würde ich trotzdem gerne wissen, wie ich zwischen AcceptEx() (mit Receive-Buffer) und WSAWaitForMultipleEvents() überprüfen kann, ob auf dem Socket schon Daten gesendet wurden. Ein Timeout wäre spitze, nur weiß ich nicht, wie ich das anstellen soll.
    Irgendwie muss es ja gehen.



  • Gib doch bei WSAWaitForMultipleEvents ein Timeout an und wenn dieser auftritt führst du die Prüfung durch. Wo siehst du denn genau das Problem?



  • > führst du die Prüfung durch.

    Es gibt ja eben keine. Wenn wirklich Daten da sind, ist das hEvent von overlapped signaled. Das zu überprüfen wäre Schwachsinn, da genau dann WSAWaitForMultipleEvents() returned. Außerdem: Dann würde die Unendlichschleife ja durchlaufen.



  • Ich meine die getsockopt mit SO_CONNECT_TIME Prüfung.



  • Und was soll das bringen?

    while(running) {
       //...
       AcceptEx(...);
       //...
       WSAWaitForMultipleEvents(...); // warte eben nur 1s
       //...
       if(getsockopt(...)) { ... } // Socket nicht verbunden, gibt -1 zurück
       //...
    }
    

    Das bringt doch exakt garnichts?! Nur, dass die CPU etwas mehr blutet, da die Server-Hauptschleife jetzt jede Sekunde durchlaufen wird. getsockopt() gibt mir einfach -1 zurück, wenn AcceptEx() nicht completed ist.



  • Ja aber dann kannst du doch die Verbindung trennen wenn die Verbindung besteht aber nach einer bestimmten Zeit noch keine Daten angekommen sind.



  • Das getsockopt wird dir die Millisekunden zurückgeben die der Client bereits verbunden ist auch wenn noch keine Daten empfangen wurden.



  • Ups, sind wohl Sekunden.

    Das getsockopt wird dir nicht erst nach der AcceptEx Completion einen posiiven Wert liefern.



  • Laut Doku bekommt man nen Wert nachdem die Connection aufgebaut wurde (auch bevor Daten angekommen sind). Davor nen Fehler.

    Ich finde das allerdings trotzdem alles viel zu kompliziert.

    Vor allem muss man parallel Unmengen von AcceptEx Aufrufen pending haben wenn man nicht möchte dass langsame Clients den Server ausbremsen. Auch mit solchen Abfragen um extrem langsame Clients rauszuwerfen.

    Ohne Receive-Buffer bzw. mit dem guten alten accept() ist das nicht nötig.



  • > Ohne Receive-Buffer bzw. mit dem guten alten accept() ist das nicht nötig.

    Dafür ist accept() blockierend, nicht asynchron/overlapped und langsam.

    > Vor allem muss man parallel Unmengen von AcceptEx Aufrufen pending haben wenn man nicht möchte dass langsame Clients den Server ausbremsen.

    Ja, das Feature füge ich zu meinem Server jetzt mal hinzu. Neben dem Socket-Pool (dann spar ich mir die teure Erstellung von neuen Sockets beim AcceptEx()).

    > Ich finde das allerdings trotzdem alles viel zu kompliziert.

    Niemand hat behauptet, dass es einfach ist. 😉 Beste Performance hat seinen Preis.

    Ich habe btw. einen Weg gefunden, den ich aber erst morgen teste. Wenn ich den listen-Socket mit einem Event assoziiere (per WSAEventSelect()), das bei FD_ACCEPT signaled ist, dann weiß ich schon vor dem Aufruf von AcceptEx(), dass ein Client im Anmarsch (in der Listen-Queue) ist. Nach dem Ereignis gehe ich alle Sockets durch, die gerade in einer AcceptEx()-Operation stecken und prüfe mit getsockopt, ob und wie lange sie schon verbunden sind. Das könnte glatt funktionieren.

    Nun habe ich ein anderes Problem: Der Kernel-Mode belegt beim Aufruf von AcceptEx() Ressourcen, die (aus Performance-Gründen) nicht freigegeben werden, sollte die Overlapped-Operation abgebrochen werden. Auf Deutsch: Ich kann nicht einfach DisconnectEx() oder closesocket() auf einen Socket setzten, der im AcceptEx()-Pending steckt, aber noch keine Daten gesendet hat, sonst bekomme ich Memory-Leaks. Was kann ich da tun?


Log in to reply