http://qs321.pair.com?node_id=572712
Category: Networking Code
Author/Contact Info grinder @ perlmonks
Description:

I've had this lying around for some time. It just takes the output of netstat and summarises the information à la vmstat, iostat. I have used it for keeping an eye on large daemons that have shown a tendency to go mad.

I don't see that there's anything else to add to it, but I'd be interested in seeing if anyone has suggestions and ways it could be improved.

update: now with Win32 support! thanks Discipulus for the suggestion

#! /usr/bin/perl -w
#
# socksumm -- socket usage summary
#
# Copyright (C) 2005-2006 David Landgren

use strict;
use Getopt::Long;

my $VERSION = '1.1';

my $ok = GetOptions(
    'help',         \my $help,
    'localport=s',  \my $watch_local_port,
    'remoteport=s', \my $watch_remote_port,
    'port=s',       \my $watch_port,
    'sleep=i',      \my $sleep,
    'version',      \my $version,
    'num=i',        \my $num,
);

if( not $ok or $help ) {
    eval "use Pod::Usage";
    if( $@ ) {
        print <<HELP;
$0 [-localport=?] [-remoteport=?] [-port=?] [-sleep=n] [-num=n]
(install Pod::Usage for detailed help)
HELP
    }
    else {
        pod2usage(1);
    }
    exit;
}

$sleep ||= 60;

if( $version ) {
    eval "use File::Basename";
    print +($@ ? $0 : basename($0)), " v$VERSION\n";
    exit 0;
}

my $netstat_re;
my $args;

# platform-specific settings;
{
    my $ip_re = '\d+(?:\.\d+){3}';

    if( $^O eq 'freebsd' ) {
        $args = '-nf inet';
        $netstat_re = qr/^\S+(?:\s+\d+){2}\s+($ip_re)[:.](\d+)\s+($ip_
+re)[:.](\d+)\s+(\S+)\s*$/;
    }
    elsif( $^O eq 'linux' ) {
        $args = '-n --inet';
        $netstat_re = qr/^\S+(?:\s+\d+){2}\s+($ip_re)[:.](\d+)\s+($ip_
+re)[:.](\d+)\s+(\S+)\s*$/;
    }
    elsif( $^O eq 'solaris' ) {
        $args = '-nf inet';
        $netstat_re = qr/^($ip_re)\.(\d+)\s+($ip_re)\.(\d+)(?:\s+\d+){
+4}\s+(\S+)\s*$/;
    }
    elsif( $^O eq 'MSWin32' ) {
        $args = '-n';
        $netstat_re = qr/^\s+TCP\s+($ip_re):(\d+)\s+($ip_re):(\d+)\s+(
+\S+)\s*$/;
    }
    else {
        die "Don't know how to decode netstat on $^O\n" unless defined
+ $args;
    }
}

# resolve service names if we can
defined $_ and /\D/ and $_ = getservbyname($_,'tcp') || $_
    for ($watch_local_port, $watch_remote_port, $watch_port);

my %state;
my @col = qw(
    ESTABLISHED CLOSE_WAIT TIME_WAIT FIN_WAIT_1 FIN_WAIT_2 SYN_SENT SY
+N_RECV LAST_ACK
);
print "estab close twait finw1 finw2 syntx synrx lastk total\n";

while( 1 ) {
    my $total    = 0;
    @state{@col} = (0) x @col;

    open my $in, "netstat $args |"
        or die "Cannot open pipe from netstat: $!\n";
    while( <$in> ) {
        chomp;
        next unless my($local_host, $local_port, $remote_host, $remote
+_port, $state)
            = /$netstat_re/;
        next if $watch_port
            and $local_port  != $watch_port
            and $remote_port != $watch_port
        ;
        next if $watch_local_port  and $local_port  != $watch_local_po
+rt;
        next if $watch_remote_port and $remote_port != $watch_remote_p
+ort;

        $state =~ s/^(FIN_WAIT)(\d+)$/$1_$2/; # munge Linux variant
        ++$state{$state};
        ++$total;
    }
    close $in;

    # display one line of data
    my $timestamp = sprintf( '%02d:%02d:%02d', (localtime)[2,1,0] );
    printf "%5d %5d %5d %5d %5d %5d %5d %5d %5d %s",
        @state{@col}, $total, $timestamp;
    delete @state{@col};

    # deal with unknown or don't-care socket states
    if( %state ) {
        print ' ', join( ' ', map {"$_=$state{$_}"} sort keys %state )
+;
        %state = ();
    }
    print "\n";

    last if defined $num and --$num <= 0;
    sleep $sleep;
}

exit 0;

__END__

=head1 NAME

socksumm - Display a summary of open sockets

=head1 SYNOPSIS

B<socksumm> [B<-l>,B<-localport>] [B<-r>,B<-remoteport>] [B<-p>,B<-por
+t>] [B<-s>,B<-sleep>] [B<-n>,B<-num>] [B<-version>]

=head1 DESCRIPTION

Parse the output of the C<netstat(1)> command and produce a summary
of the socket connections on a port.

=head1 OPTIONS

=over 5

=item B<-l>,B<-localport>

Summarise socket connections on this local port. Numeric or symbolic n
+ames
(for example 389 or C<ldap>) are recognised. In otherwords, use this t
+o
monitor inbound connections.

=item B<-r>,B<-remoteport>

Summarise socket connections on this remote port. Numeric or symbolic 
+names
are recognised. Use this to monitor outbound connections.

=item B<-p>,B<-port>

Summarise socket connections on this port. Numeric or symbolic names
are recognised. Use this to monitor eitherbound connections.

=item B<-s>,B<-sleep>

Time to sleep between invocations of C<netstat>. A sixty (60) second s
+leep
time is assumed if this switch is omitted.

=item B<-n>,B<-number>

Produce this many summaries of C<netstat> and then exit.

=back

=head1 EXAMPLES

C<socksumm -l=ldap -s=10>

Summarise the inbound connections to the LDAP listener port. Will prod
+uce
output that looks similar to the following:

 estab close twait finw1 finw2 syntx synrx lastk total
   519     0     4     0     0     0     0     0   523 12:42:50
   524     0     2     0     0     0     0     1   527 12:43:00
   516     0     3     0     0     0     0     0   519 12:43:11

C<socksumm -r=22 -s=3600>

See how many outbound C<ssh> connections are open every hour.

=head1 BUGS

Assumes that C<netstat> can be found on the PATH. If you are running
C<netstat> on a platform other than FreeBSD, Linux or Solaris the
script will die. Please mail me the output and I'll endeavour to
incorporate it (or, better yet, send me patches).

=head1 COPYRIGHT

Copyright 2005-2006 David Landgren.

This script is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=head1 AUTHOR

David Landgren
join chr(0x40) => reverse qw[landgren.net david]

=cut