Category: Utilities
Author/Contact Info Garret D. Kelly, garret dot kelly at gmail dot com
Description: The aim of this program is to aid admins in watching for machines which are ARP storming.

This program, when invoked as root, will open a pipe from tcpdump and wait for ARP packets. It maintains a list of IPs who are sending ARP packets, and the number of ARP packets they have sent in the past monitoring period; if the number of packets a given IP has broadcast differs from the average number of broadcast packets by more than the standard deviance, that IP is displayed in a list of ARP offenders.
#!/usr/bin/perl -w
                                                                      
+                                                                     
+                                                         
# Garret D. Kelly, Oct 13th, 2004
# garret.kelly@gmail.com
# Do what you will.
                                                                      
+                                                                     
+                                                         
use strict;
use IPC::Shareable;
use POSIX qw(ceil);
use Term::ANSIColor qw(:constants);
                                                                      
+                                                                     
+                                                         
my %options = (destroy => 'yes', create => 'yes');
                                                                      
+                                                                     
+                                                         
my $purgedelay = 3600;
my $dispdelay = 5;
                                                                      
+                                                                     
+                                                         
# The shareable segment remains dirty because this program never
# exits clean. Teehee. We _must_ clean the shm here or we'll get
# last run's counts.
IPC::Shareable->clean_up;
                                                                      
+                                                                     
+                                                         
# Process command-line arguments here.
                                                                      
+                                                                     
+                                                         
sub watch_traffic {
        my %arpcounts;
        tie %arpcounts, 'IPC::Shareable', 'narp', {%options};
        %arpcounts = ();
        # For some reason this doesn't perform like you think
        # it would. I think it might be something to do with
        # the way tcpdump pukes its output to us.
        while(1) {
                open TD, "-|", "tcpdump -ni eth0";
                while(<TD>) {
                        if(/arp/) {
                                chomp;
                                my @dumpparts = split / /, $_;
                                my $addr = $dumpparts[$#dumpparts];
                                if(!$arpcounts{$addr}) {
                                        $arpcounts{$addr} = 1;
                                } else {
                                        $arpcounts{$addr} = $arpcounts
+{$addr} + 1;
                                }
                        }
                }
                close TD;
                print BOLD, RED, "Warning: ", RESET, "Pipe to tcpdump 
+broken\n";
        }
}
                                                                      
+                                                                     
+                                                         
                                                                      
+                                                                     
+                                                         
$|++;
                                                                      
+                                                                     
+                                                         
# Fork off a thread for watching for traffic.
if(fork() == 0) {
        watch_traffic();
}
                                                                      
+                                                                     
+                                                         
# We'll clear the arp counts every hour to make sure we
# don't get a huge backlog or anything like that.
if(fork() == 0) {
        my %ac;
        tie %ac, 'IPC::Shareable', 'narp', {%options};
        while(1) {
                sleep $purgedelay;
                %ac = ();
        }
}

# Compute stats and display abusers every so often;
while(1) {
        system('clear');
        my %arpcounts;
        tie %arpcounts, 'IPC::Shareable', 'narp';
        print BOLD, "arpsnitch", RESET, " v0.1a -- Garret D. Kelly -- 
+", BOLD, "Current Average: ", RESET;
        my ($avg, $avgdeviance, %deviants);
        for(keys %arpcounts) {
                $avg += $arpcounts{$_};
        }
        if((scalar keys %arpcounts) == 0) {
                print "N/A\n\nNo statistics have been accumulated so f
+ar.";
                sleep $dispdelay;
                next;
        }
        $avg /= scalar keys %arpcounts;
        print ceil($avg) . "\n";
        print BOLD, BLACK, "IP\t\t\tCOUNT\t\tDEVIANCE\n", RESET;
        for(keys %arpcounts) {
                $avgdeviance += $arpcounts{$_} - $avg;
        }
        $avgdeviance /= scalar keys %arpcounts;
        for(keys %arpcounts) {
                if($arpcounts{$_} - $avg > $avgdeviance) {
                        print BOLD, RED, "$_\t\t", RESET, "$arpcounts{
+$_}\t\t" . ceil($arpcounts{$_} - $avg) . "\n";
                }
        }
        sleep $dispdelay;
}