4

I have a code written in C/C++ that look like this:

    while(1)
{
    //Accept
    struct sockaddr_in client_addr;
    int client_fd = this->w_accept(&client_addr);
    char client_ip[64];
    int client_port = ntohs(client_addr.sin_port);
    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));

    //Listen first string
    char firststring[512];
    memset(firststring,0,512);
    if(this->recvtimeout(client_fd,firststring,sizeof(firststring),u->timeoutlogin) < 0){
        close(client_fd);
    }

    if(strcmp(firststring,"firststr")!=0)
    {
        cout << "Disconnected!" << endl;
        close(client_fd);
        continue;
    }

    //Send OK first string
    send(client_fd, "OK", 2, 0);


    //Listen second string
    char secondstring[512];
    memset(secondstring,0,512);
    if(this->recvtimeout(client_fd,secondstring,sizeof(secondstring),u->timeoutlogin) < 0){
        close(client_fd);
    }

    if(strcmp(secondstring,"secondstr")!=0)
    {
        cout << "Disconnected!!!" << endl;
        close(client_fd);
        continue;
    }

    //Send OK second string
    send(client_fd, "OK", 2, 0);


}
    }

So, it's dossable. I've write a very simple dos script in perl that takedown the server.

#Evildos.pl
use strict;
use Socket;
use IO::Handle;
sub dosfunction
{
my $host = shift || '192.168.4.21';
my $port = 1234;
my $firststr = 'firststr';
my $secondstr = 'secondstr';
my $protocol = getprotobyname('tcp');
$host = inet_aton($host) or die "$host: unknown host";
socket(SOCK, AF_INET, SOCK_STREAM, $protocol) or die "socket() failed: $!";
my $dest_addr = sockaddr_in($port,$host);
connect(SOCK,$dest_addr) or die "connect() failed: $!";
SOCK->autoflush(1);
print SOCK $firststr;
#sleep(1);
print SOCK $secondstr;
#sleep(1);
close SOCK;
}

my $i;
for($i=0; $i<30;$i++)
{
&dosfunction;
}

With a loop of 30 times, the server goes down.

The question is: is there a method, a system, a solution that can avoid this type of attack?

EDIT: recvtimeout

int recvtimeout(int s, char *buf, int len, int timeout)
     {


fd_set fds;
int n;
struct timeval tv;
// set up the file descriptor set
FD_ZERO(&fds);
FD_SET(s, &fds);
// set up the struct timeval for the timeout
tv.tv_sec = timeout;
tv.tv_usec = 0;
// wait until timeout or data received
n = select(s+1, &fds, NULL, NULL, &tv);
if (n == 0){
    return -2; // timeout!
}
if (n == -1){
    return -1; // error
}
// data must be here, so do a normal recv()
return recv(s, buf, len, 0);
    }
user840718
  • 1,563
  • 6
  • 29
  • 54
  • Is there a reason you aren't closing `client_fd` at the end? also your error control flow is off. and I expect that your `recvtimeout` may also return `0` – Hasturkun May 28 '12 at 15:43
  • 1
    If your code can't handle more than 30 connections, then it's time to use a different server architecture paradigm. – Mahmoud Al-Qudsi May 28 '12 at 15:50
  • I've posted recvtimeout, that is to say the same of Beejnet guide. All is ok in this server, but goes down randomicaly in my lan. Maybe with more hops this can't be possible. – user840718 May 28 '12 at 15:53

5 Answers5

4

I don't think there is any 100% effective software solution to DOS attacks in general; no matter what you do, someone could always throw more packets at your network interface than it can handle.

In this particular case, though, it looks like your program can only handle one connection at a time -- that is, incoming connection #2 won't be processed until connection #1 has completed its transaction (or timed out). So that's an obvious choke point -- all an attacker has to do is connect to your server and then do nothing, and your server is effectively disabled for (however long your timeout period is).

To avoid that you would need to rewrite the server code to handle multiple TCP connections at once. You could either do that by switching to non-blocking I/O (by passing O_NONBLOCK flag to fcntl()), and using select() or poll() or etc to wait for I/O on multiple sockets at once, or by spawning multiple threads or sub-processes to handle incoming connections in parallel, or by using async I/O. (I personally prefer the first solution, but all can work to varying degrees). In the first approach it is also practical to do things like forcibly closing any existing sockets from a given IP address before accepting a new socket from that IP address, which means that any given attacking computer could only tie up a maximum of one socket on your server at a time, which would make it harder for that person to DOS your machine unless he had access to a number of client machines.

You might read this article for more discussion about handling many TCP connections at the same time.

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • I looked around for more solution but the best seems to be the "non-blocking". I have to use select, poll or epoll? For a Linux machine what's the best and easy to add in the code? – user840718 May 28 '12 at 16:41
  • +1 on non-blocking. I voted up on this answer since it was nearly equivalent to what I wrote. – selbie May 28 '12 at 17:01
  • @user840718 - Avoid using select() unless you are on windows. select() on unix has some limitations when you get beyond a certain limit of socket handles. epoll is linux only. poll() works on every unix platform. epoll has better scalability, but in reality only few types of servers need to scale to 10K connections. You'll notice in the code link I gave you in my answer, that I have poll and epoll abstracted out so that it uses either depending on what's being compiled for. Also, read this: http://sheddingbikes.com/posts/1280829388.html – selbie May 28 '12 at 17:07
  • Why do you prefer `select` or `poll` instead of multiple threads? Though select and poll will listen for multiple clients, processing has to be done in a "serialized" fashion which is less efficient for multiple legit clients. Or am I not understanding correctly? – m4l490n Apr 10 '20 at 03:00
  • 1
    @m4l490n It's not multiple threads that I'm against so much as blocking I/O. Whenever blocking I/O is used, every time you call `recv()` you place the calling thread at the mercy of the client, since `recv()` won't return until at least 1 byte of data is received from the client. So if your server uses blocking I/O, all an attacker has to do is make a TCP connection and then not send any data, and that thread is blocked for a long time, possibly forever. If the attacker keeps doing that, then sooner or later you will run out of threads and/or RAM :) – Jeremy Friesner Apr 10 '20 at 03:11
2

The main issue with DOS and DDOS attacks is that they play on your weakness: namely the fact that there is a limited memory / number of ports / processing resources that you can use to provide the service. Even if you have infinite scalability (or close) using something like the Amazon farms, you'll probably want to limit it to avoid the bill going through the roof.

At the server level, your main worry should be to avoid a crash, by imposing self-preservation limits. You can for example set a maximum number of connections that you know you can handle and simply refuse any other.

Full strategies will include specialized materials, like firewalls, but there is always a way to play them and you will have to live with that.

For example of nasty attacks, read about Slow Loris on wikipedia.

Slowloris tries to keep many connections to the target web server open and hold them open as long as possible. It accomplishes this by opening connections to the target web server and sending a partial request. Periodically, it will send subsequent HTTP headers, adding to—but never completing—the request. Affected servers will keep these connections open, filling their maximum concurrent connection pool, eventually denying additional connection attempts from clients.

There are many variants of DOS attacks, so a specific answer is quite difficult.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
1

Your code leaks a filehandle when it succeeds, this will eventually make you run out of fds to allocate, making accept() fail.

close() the socket when you're done with it.

Also, to directly answer your question, there is no solution for DOS caused by faulty code other than correcting it.

Hasturkun
  • 35,395
  • 6
  • 71
  • 104
1

This isn't a cure-all for DOS attacks, but using non-blocking sockets will definitely help for scalability. And if you can scale-up, you can mitigate many DOS attacks. This design changes includes setting both the listen socket used in accept calls and the client connection sockets to non-blocking.

Then instead of blocking on a recv(), send(), or an accept() call, you block on either a poll, epoll, or select call - then handle that event for that connection as much as you are able to. Use a reasonable timeout (e.g. 30 seconds) such that you can wake up from polling call to sweep and close any connections that don't seem to be progressing through your protocol chain.

This basically requires every socket to have it's own "connection" struct that keeps track of the state of that connection with respect to the protocol you implement. It likely also means keeping a (hash) table of all sockets so they can be mapped to their connection structure instance. It also means "sends" are non-blocking as well. Send and recv can return partial data amounts anyway.

You can look at an example of a non-blocking socket server on my project code here. (Look around line 360 for the start of the main loop in Run method).

An example of setting a socket into non-blocking state:

int SetNonBlocking(int sock)
{
    int result = -1;
    int flags = 0;

    flags = ::fcntl(sock, F_GETFL, 0);
    if (flags != -1)
    {
        flags |= O_NONBLOCK;
        result = fcntl(sock , F_SETFL , flags);
    }
    return result;
}
selbie
  • 100,020
  • 15
  • 103
  • 173
0

I would use boost::asio::async_connector from boost::asio functionality to create multiple connection handlers (works both on single and multi-threaded environment). In the single threaded case, you just need to run from time to time boost::asio::io_service::run in order to make sure communications have time to be processed

The reason why you want to use asio is because its very good at handling asynchronous communication logic, so it won't block (as in your case) if a connection gets blocked. You can even arrange how much processing you want to devote to opening new connections, while keep serving existing ones

lurscher
  • 25,930
  • 29
  • 122
  • 185