#!/usr/bin/perl # Copyright (C) 2011 Glen Pitt-Pladdy # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # # See: http://www.pitt-pladdy.com/blog/_20111004-103120_0100_ICMP_Connectivity_Monitoring_with_Cacti/ use strict; use warnings; # under Debian based systems you will need: libproc-daemon-perl libnet-pcap-perl # where to keep the pid file my $PID = '/var/run/icmpmond.pid'; # sanity check arguments # should be: if ( $#ARGV != 3 or $ARGV[0] !~ /^\w+$/ or $ARGV[1] !~ /^(true|false)$/ or $ARGV[2] !~ /^\d+$/ ) { die "Usage: $0 \n"; } # daemonise use Proc::Daemon; my $pid = Proc::Daemon::Init (); if ( $pid > 0 ) { # we are the parent exit 0; } # get some signal handlers in place $SIG{INT} = 'cleanexit'; $SIG{TERM} = 'cleanexit'; $SIG{QUIT} = 'cleanexit'; # write pid file open my $pidh, '>', $PID or die "FATAL - can't write \"$PID\": $!\n"; print $pidh "$$\n"; close $pidh; my %stats; # read in previous data if available if ( -f $ARGV[3] and open my $fh, '<', $ARGV[3] ) { while ( defined ( my $line = <$fh> ) ) { chomp $line; if ( $line =~ /^(icmp6?):(\d+):(\w+)=(\d+)$/ ) { if ( $3 eq 'total' ) { $stats{"$1tot"}{$2} = $4; } else { $stats{$1}{$2}{$3} = $4; } } } close $fh; } # get the capture online use Net::Pcap; my $dev = $ARGV[0]; my $snaplen = 256; my $promisc = ($ARGV[1] eq 'true')?1:0; my $to_ms = 100; my $err; my $capture = Net::Pcap::open_live($dev, $snaplen, $promisc, $to_ms, \$err); my $filter_compiled; Net::Pcap::compile($capture, \$filter_compiled, "icmp or icmp6", 1, 0 ); Net::Pcap::setfilter($capture, $filter_compiled); $stats{'statstime'} = $ARGV[2]; $stats{'filename'} = $ARGV[3]; Net::Pcap::loop ( $capture, 0, \&processpacket, \%stats ); Net::Pcap::close ( $capture ); # remove pid file unlink $PID or die "FATAL - can't remove \"$PID\": $!\n"; sub processpacket { my ( $stats, $header, $packet ) = @_; my ( $ethdsth, $ethdstl, $ethsrch, $ethsrcl, $ethproto, $ippacket ) = unpack 'NnNnna*', $packet; if ( $ethproto == 0x800 ) { # IPv4 my ( $ipverhdrlen, $ipdiffsrv, $iptotlen, $ipident, $ipflagsfragoff, $ipttl, $ipproto, $iphdrcsum, $ipsrc, $ipdst, $protopacket ) = unpack 'CCnnnCCnNNa*', $ippacket; my ( $icmptype, $icmpcode, $icmpcsum, $icmpident, $icmpseq, $data ) = unpack 'CCnnna*', $protopacket; if ( ! exists $$stats{'icmptot'}{$icmptype} ) { $$stats{'icmptot'}{$icmptype} = 0; } ++$$stats{'icmptot'}{$icmptype}; if ( ! exists $$stats{'icmp'}{$icmptype}{$icmpcode} ) { $$stats{'icmp'}{$icmptype}{$icmpcode} = 0; } ++$$stats{'icmp'}{$icmptype}{$icmpcode}; } elsif ( $ethproto == 0x86dd ) { # IPv6 my ( $ipvertcfl, $ippayloadlen, $ipproto, $iphoplim, $ipsrc0, $ipsrc1, $ipsrc2, $ipsrc3, $ipdst0, $ipdst1, $ipdst2, $ipdst3, $protopacket ) = unpack 'NnCCNNNNNNNNa*', $ippacket; my ( $icmptype, $icmpcode, $icmpcsum, $icmpident, $icmpseq, $data ) = unpack 'CCnnna*', $protopacket; if ( ! exists $$stats{'icmp6tot'}{$icmptype} ) { $$stats{'icmp6tot'}{$icmptype} = 0; } ++$$stats{'icmp6tot'}{$icmptype}; if ( ! exists $$stats{'icmp6'}{$icmptype}{$icmpcode} ) { $$stats{'icmp6'}{$icmptype}{$icmpcode} = 0; } ++$$stats{'icmp6'}{$icmptype}{$icmpcode}; } # sort stats file updates if needed if ( ! exists $$stats{'lasttime'} ) { $$stats{'lasttime'} = $^T; $$stats{'packets'} = 0; $$stats{'writepackets'} = 1; } ++$$stats{'packets'}; my $timediff = time() - $$stats{'lasttime'}; if ( $timediff >= $$stats{'statstime'} ) { $$stats{'lasttime'} += $$stats{'statstime'}; # write stats #print "\n$timediff\n"; open my $fh, '>', $$stats{'filename'}.".TMP" or die "FATAL - can't write stats: $!\n"; foreach my $type (keys %{$stats{'icmp'}}) { printf $fh "icmp:%u:total=%d\n", $type, $$stats{'icmptot'}{$type}; foreach my $code (keys %{$stats{'icmp'}{$type}}) { printf $fh "icmp:%u:%u=%d\n", $type, $code, $$stats{'icmp'}{$type}{$code}; } } foreach my $type (keys %{$stats{'icmp6'}}) { printf $fh "icmp6:%u:total=%d\n", $type, $$stats{'icmp6tot'}{$type}; foreach my $code (keys %{$stats{'icmp6'}{$type}}) { printf $fh "icmp6:%u:%u=%d\n", $type, $code, $$stats{'icmp6'}{$type}{$code}; } } close $fh; rename $$stats{'filename'}.".TMP", $$stats{'filename'}; } } sub cleanexit { if ( defined $capture ) { Net::Pcap::breakloop( $capture ); } }