Jump to content
Nytro

[Perl] DDOS Accept

Recommended Posts

[Perl] DDOS Accept

ddos_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 COPYRIGHT

Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless 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 ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.

Sursa: ddos_accept.pl -- tool for mitigating attacks against httpd's accept loop - r00tsecurity

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...