#!/usr/bin/perl # @(#)throttle.pl v2.0 10 SEP 2000 Rob Thomas robt@cymru.com # # Monitor top SYN senders. Choke them off with SAM if the SYN rate exceeds # level. This script requires tcpdump. # # Usage: throttle.pl # # Where is measured in seconds and is measured in SYN # packets from a given host. # # The throttle.pl program will block SYN floods through the fw sam facility. # Any action taken by throttle.pl is logged through the syslog facility. # The $FACILITY and $LEVEL variables should be modified to suit the local # logging requirements. The throttle.pl program should be used in place of # SYN defender, which performs poorly under any moderate to intense attack. # # The throttle.pl program will wait (waitid()) until tcpdump actually returns # some data, e.g. data destined for TCP port . When throttle.pl sends # the block request through fw sam, it will also remove all the current # entries in the state table for the now-blocked IP address. # # To unblock the IPs that have been blocked, stop throttle, issue the # fw sam -D command on the firewall, then restart throttle. To monitor the # entries in the SAM cache, issue the fw tab -t sam_blocked_ips command, # which will present the IP addresses in hex. # # v1.0 17 FEB 2000 - Early prototype that watched and cached the top SYN # senders. # v2.0 10 SEP 2000 - Added fw sam and syslog functionality, cleaned up # signal handler piece, filtered out SYN/ACK packets. # # CREDITS: Thanks to Steve Gill (gillsr@us.ibm.com) for version 1.0 brain- # storming and design assistance. # use Sys::Syslog; $SAM="/etc/fw/bin/fw sam"; $FACILITY="local3"; $LEVEL="info"; $SIG{ALRM} = \&catchSignal; ($NIC, $PORT, $IVAL, $RATE) = @ARGV; if (! -f "/bin/sun") { $TCPDUMP="/usr/sbin/tcpdump -I"; } else { $TCPDUMP="/usr/local/sbin/tcpdump"; } die "throttle.pl: usage: throttle.pl " if (1 > $#ARGV); die "throttle.pl: can not execute tcpdump!" if (! -x $TCPDUMP); openlog("throttle", "ndelay", "$FACILITY"); if (0 == $RATE) { $RATE = 75; } if (0 == $IVAL) { $IVAL = 10; } # Sighandler sub catchSignal() { my $signame = shift; if ($signame eq "ALRM") { # Uncomment the following line if your system suffers from SysV unreliable # signals. # $SIG{ALRM} = \&catchSignal; foreach $shost (keys %hostdb) { delete $hostdb{$shost}; } if (%tophosts != NULL) { @sortedtalkers = sort sortBySocks keys(%tophosts); foreach (@sortedtalkers) { if ($blockip{$_} > 0) { syslog("debug", "$_ already blocked"); } else { ++$blockip{$_}; syslog("debug","$_ had $tophosts{$_} out of $numpkts sockets"); syslog("$LEVEL", "Blocking $_ from TCP port $PORT"); open (CHOKE, "$SAM -v -t 0 -I src $_ dst Any $PORT tcp|") or die "Can not run fw sam $!"; while () { syslog("debug", "$_"); } close(CHOKE); } } foreach $shost (keys %tophosts) { delete $tophosts{$shost}; } } close(DUMPFH); $numpkts = 0; } } # Sort the top SYN senders sub sortBySocks () { return $tophosts{$b} <=> $tophosts{$a}; } # MAIN syslog("$LEVEL","throttle starting, using $IVAL interval for rate of $RATE"); while(1) { alarm $IVAL; open (DUMPFH, "$TCPDUMP -n -i $NIC dst port $PORT |") or die "Can not run tcpdump: $!"; while () { ($tstamp, $shostport, $junk, $thost, $ipflags, $ack) = split; if (("S" eq $ipflags) && ("ack" ne $ack)) { @soct = split(/\./, $shostport); ++$numpkts; $shost=$soct[0] . "." . $soct[1] . "." . $soct[2] . "." . $soct[3]; ++$hostdb{$shost}; if ($hostdb{$shost} > $RATE) { $tophosts{$shost} = $hostdb{$shost}; } } } close(DUMPFH); } exit(0);