#!@@PERL@@ # -*- perl -*- =head1 NAME port_ - Munin wildcard plugin to monitor network port usage. =head1 CONFIGURATION To monitor a port link port_ to this file. E.g. ln -s /usr/share/munin/plugins/port_ /etc/munin/plugins/port_ssh will monitor ssh connections. Services are those from /etc/services or a numeric port number. =head1 MAGIC MARKERS #%# family=manual #%# capabilities=autoconf suggest =head1 BUGS The plugin doesnt count the tcp6 connections if tcp and tcp6 is used. =head1 AUTHOR Unknown =head1 LICENSE GPLv2 =cut use Munin::Plugin; use constant { ST_ESTABLISHED => 1, ST_LISTEN => 10, }; sub cache_open { my ($fd, $file) = @_; my $cache_dir = "$ENV{MUNIN_PLUGSTATE}"; my $cache = $file; $cache =~ s:/:_:g; $cache = "$cache_dir/$cache"; my $ttl = $ENV{'cache_ttl'}; $ttl = 60 unless (defined $ttl); # The user can disable caching by setting cache_ttl to something # other than a positive integer. if ($ttl !~ /^\d+$/ || $ttl == 0) { return open($fd, $file); } if (-e $cache && -M _ < $ttl / 86400) { return open($fd, $cache); } unless (-d $cache_dir) { mkdir($cache_dir) || die "$cache_dir: mkdir: $!\n"; } my $retval; $retval = open(CACHE_INPUT, $file); return $retval unless ($retval); my $tmpfile = "$cache.$$"; open(CACHE_OUTPUT, ">$tmpfile") || die "$tmpfile: open: $!\n"; while () { print CACHE_OUTPUT; } close(CACHE_OUTPUT) || die "$tmpfile: close: $!\n"; close(CACHE_INPUT); rename($tmpfile, $cache) || die "$cache: rename: $!\n"; # Okay, we have a fresh copy in our cache. return open($fd, $cache); } if ($ARGV[0] and $ARGV[0] =~ /^\s*autoconf\s*$/i) { if (-r "/proc/net/tcp" or -r "/proc/net/tcp6") { print "yes\n"; exit 0; } else { print "no\n"; exit 0; } } sub count_tcp_ports { my ($state, $ports) = @_; cache_open("NETSTAT", "/proc/net/tcp") or exit 1; # Skip header line, else hex($curstate) will complain my $header = ; # 32/4 + 1 corresponds to the IP address in hex + ":" my $local_port_offset = 32/4 + 1; while () { my ($linenr, $local, $remote, $curstate) = split; next unless (hex($curstate) == $state); ++$ports->{hex(substr($local, $local_port_offset, 4))}; } close NETSTAT; } sub count_tcp6_ports { my ($state, $ports) = @_; cache_open("NETSTAT", "/proc/net/tcp6") or exit 1; # Skip header line, else hex($curstate) will complain my $header = ; # 128/4 + 1 corresponds to the IP address in hex + ":" my $local_port_offset = 128/4 + 1; while () { my ($linenr, $local, $remote, $curstate) = split; next unless (hex($curstate) == $state); ++$ports->{hex(substr($local, $local_port_offset, 4))}; } close(NETSTAT); } if ($ARGV[0] and $ARGV[0] =~ /^\s*suggest\s*$/i) { if (-r "/proc/net/tcp" or -r "/proc/net/tcp6") { my %ports = (); count_tcp_ports(ST_LISTEN, \%ports) if (-r "/proc/net/tcp"); count_tcp6_ports(ST_LISTEN, \%ports) if (-r "/proc/net/tcp6"); foreach my $port (keys %ports) { my $p = (getservbyport($port, "tcp"))[0]; print $p || $port, "\n"; } exit 0; } exit 1; } my ($name, $port); if ($0 =~ /port_(.+)*$/) { $name = $port = $1; if ($port =~ /\D/) { $port = (getservbyname ($name, "tcp"))[2]; } } else { exit 2; } if ($ARGV[0] and $ARGV[0] =~ /^\s*config\s*$/i) { if (int($name)) { print "graph_title TCP port $name connection count\n"; } else { print "graph_title $name connection count\n"; } print "graph_args --base 1000 -l 0\n"; print "graph_vlabel concurrent connections\n"; print "graph_category network\n"; print "count.label $name\n"; print_thresholds("count"); exit 0; } my %ports = (); count_tcp_ports(ST_ESTABLISHED, \%ports) if (-r "/proc/net/tcp"); count_tcp6_ports(ST_ESTABLISHED, \%ports) if (-r "/proc/net/tcp6"); # If there aint any the right answer is 0, not blank or U. $ports{$port} = 0 if (!exists $ports{$port}) or ($ports{$port} eq ''); print "count.value ", $ports{$port}, "\n"; # vim:syntax=perl