Nytro Posted January 28, 2011 Report Share Posted January 28, 2011 [Perl] DDOS Acceptddos_accept.pl -- tool for mitigating attacks against httpd's accept loop #!/usr/bin/perl## ddos_accept.pl -- tool for mitigating attacks against httpd's accept loop## Usage: ddos_accept.pl [-vd] [-t timeout] [-q queuelen] hostname port [proxy]## Will listen on hostname's port and proxy connections to proxy's port (default localhost);# designed to ignore idiots who connect and immediately disconnect, since that is# the nature of the ddos.## Even with -d (daemonize) set and -v (verbose) unset, this script will spew occasional# warnings to stderr. Be sure to close stderr on the command line if you are absolutely# uninterested in any loggable output.#use strict;use warnings;no warnings 'uninitialized';use IO::Socket::INET;use IO::Select;use Errno ":POSIX";use POSIX "setsid";use Getopt::Std;my $USAGE = "Usage: ddos_accept.pl [-vd] [-t timeout] [-q queuelen] hostname port [proxy]\n";getopts "vdt:q:" => \ my %opts or die $USAGE;@ARGV >= 2 or die $USAGE;my $VERBOSE = $opts{v};my $QUEUE = $opts{q} || 512;my $TIMEOUT = $opts{t} || 60;my $HOST = shift;my $PORT = shift;my $PROXY = shift || "localhost";my $listener = IO::Socket::INET->new( Listen => $QUEUE, LocalAddr => $HOST, LocalPort => $PORT, Proto => "tcp", ReuseAddr => 1, Blocking => 0,) or die "Can't create listener on $HOST\:$PORT\: $!";my $readers = IO::Select->new;my $listeners = IO::Select->new;$readers->add($listener);$listeners->add($listener);my $writers = IO::Select->new;if ($opts{d}) { # daemonize open STDIN, "</dev/null" or die "can't read /dev/null: $!"; open STDOUT, ">/dev/null" or die "can't write /dev/null: $!"; defined (my $pid = fork) or die "can't fork: $!"; exit if $pid; setsid or die "can't create new session: $!";}my (%client, %proxy, %client_read_buffer, %client_spoke, %proxy_read_buffer, %client_read_closed, %proxy_read_closed);# The %client and %proxy hashes are named to describe their keys, not their values.# The values of those hashes are exactly the opposite of their names.# The %client_read_buffer and the %proxy_read_buffer hashes describe the internal memory locations# of where the recv() results are stored and are appropriately named.# The %client_read_closed and %proxy_read_closed hashes are flags which indicate whether# the read-side of the socket has shut down.# The %client_spoke hash tags the last time the client socket was active.$SIG{PIPE} = sub { warn "SIG$_[0] detected" };my $last_garbage_collection = time;MAIN_LOOP:while (1) { warn "READ = ", $readers->count, "\tWRITE = ", $writers->count, "\tCLIENT = " . keys %client, "\tPROXY = " . keys %proxy, "\n" if $VERBOSE; READER: for my $r ($readers->can_read(0.25)) { if ($r == $listener) { my $i = 0; do { my $c = $listener->accept or (warn("accept failed: $!"), last); $c->blocking(0); $readers->add($c); $writers->add($c); $client{$c} = undef; $client_spoke{$c} = [time, $c]; warn("listen backlog full"), last if ++$i == $QUEUE; } while ($listeners->can_read(0)); } elsif (exists $client{$r}) { # this handle is a readable client-side socket $client_spoke{$r}->[0] = time; unless (defined $client{$r}) { # this client-side socket has no existing associated proxy connection recv $r, $client_read_buffer{$r}, 8192, 0; my $bytes_read = length $client_read_buffer{$r}; warn "recv 8192 ($bytes_read)" if $VERBOSE; if (!$bytes_read) { next READER if $! == EAGAIN; warn "closing ddos connection: " . $r->peerhost; $readers->remove($r); $writers->remove($r); delete $client{$r}; delete $client_read_buffer{$r}; delete $client_spoke{$r}; close $r; } else { my $proxy = IO::Socket::INET->new( PeerAddr => $PROXY, PeerPort => $PORT, Proto => "tcp", Blocking => 0, ); if ($proxy) { warn "proxy for " . $r->peerhost . " created" if $VERBOSE; $client{$r} = $proxy; $proxy{$proxy} = $r; $proxy_read_buffer{$proxy} = ""; $readers->add($proxy); $writers->add($proxy); goto READ_CLIENT; } else { warn "couldn't create proxy socket: $!"; $readers->remove($r); $writers->remove($r); delete $client_read_buffer{$r}; delete $client{$r}; delete $client_spoke{$r}; close $r; } } } else { # this is a client-side readable socket with an existing proxy connection READ_CLIENT: while (1) { recv $r, my $buf, 8192, 0; my $bytes_read = length $buf; warn "recv 8192 ($bytes_read)" if $VERBOSE; if (!$bytes_read) { next READER if $! == EAGAIN; $readers->remove($r); $client_read_closed{$r} = 1; last; } else { $client_read_buffer{$r} .= $buf; } last if length $client_read_buffer{$r} > 65536; } } } elsif (exists $proxy{$r}) { # this handle is a proxy-side readable socket with an existing client connection (obviously) READ_PROXY: while (1) { recv $r, my $buf, 65536, 0; my $bytes_read = length $buf; warn "recv 65536 ($bytes_read)" if $VERBOSE; if (!$bytes_read) { next READER if $! == EAGAIN; $readers->remove($r); $proxy_read_closed{$r} = 1; last; } else { $proxy_read_buffer{$r} .= $buf; } last if length $proxy_read_buffer{$r} > 65536; } } } WRITER: for my $w ($writers->can_write(0)) { if (defined $client{$w} and (length $proxy_read_buffer{$client{$w}} or $proxy_read_closed{$client{$w}})) { # this handle is a client-side writable socket with pending proxy data my $bytes_written = send($w, $proxy_read_buffer{$client{$w}}, MSG_NOSIGNAL) || 0; warn "send " . length($proxy_read_buffer{$client{$w}}) . " ($bytes_written)" if $VERBOSE; next WRITER if $bytes_written == 0 and $! == EAGAIN; substr $proxy_read_buffer{$client{$w}}, 0, $bytes_written, ""; $client_spoke{$w}->[0] = time; if (!$bytes_written or length($proxy_read_buffer{$client{$w}}) == 0 and $proxy_read_closed{$client{$w}}) { # all done: time to clean up warn "done with " . $w->peerhost if $VERBOSE; my $proxy = $client{$w}; $readers->remove($w, $proxy); $writers->remove($w, $proxy); delete $client{$w}; delete $client_read_buffer{$w}; delete $client_read_closed{$w}; delete $client_spoke{$w}; delete $proxy{$proxy}; delete $proxy_read_buffer{$proxy}; delete $proxy_read_closed{$proxy}; close $w; close $proxy; } } elsif (defined $proxy{$w} and (length $client_read_buffer{$proxy{$w}} or $client_read_closed{$proxy{$w}})) { # this handle is a proxy-side writable socket with pending client data my $bytes_written = send($w, $client_read_buffer{$proxy{$w}}, MSG_NOSIGNAL) || 0; warn "send " . length($client_read_buffer{$proxy{$w}}) . " ($bytes_written)" if $VERBOSE; next WRITER if $bytes_written == 0 and $! == EAGAIN; substr $client_read_buffer{$proxy{$w}}, 0, $bytes_written, ""; if (!$bytes_written or length($client_read_buffer{$proxy{$w}}) == 0 and $client_read_closed{$proxy{$w}}) { shutdown $w, 1; $writers->remove($w); } } } GARBAGE: my $now = time; next MAIN_LOOP if $now == $last_garbage_collection; $last_garbage_collection = $now; for my $c (map $client_spoke{$_}->[1], grep $now - $client_spoke{$_}->[0] > $TIMEOUT, keys %client_spoke) { # client connections idle for more than $TIMEOUT seconds are terminated warn "timed out " . $c->peerhost if $VERBOSE; if (my $proxy = $client{$c}) { $readers->remove($proxy); $writers->remove($proxy); delete $proxy{$proxy}; delete $proxy_read_buffer{$proxy}; delete $proxy_read_closed{$proxy}; close $proxy; } $readers->remove($c); $writers->remove($c); delete $client{$c}; delete $client_read_buffer{$c}; delete $client_read_closed{$c}; delete $client_spoke{$c}; close $c; }}=head1 COPYRIGHTLicensed to the Apache Software Foundation (ASF) under oneor more contributor license agreements. See the NOTICE filedistributed with this work for additional informationregarding copyright ownership. The ASF licenses this fileto you under the Apache License, Version 2.0 (the"License"); you may not use this file except in compliancewith the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing,software distributed under the License is distributed on an"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANYKIND, either express or implied. See the License for thespecific language governing permissions and limitationsunder the License.Sursa: ddos_accept.pl -- tool for mitigating attacks against httpd's accept loop - r00tsecurity Quote Link to comment Share on other sites More sharing options...