#!/usr/bin/env perl

# top 10 non-established tcp sessions without time_wait state:
# cat /var/log/tcpstat-per-ip/tcpstat-per-ip.2025 | grep -v 'IP' | sort -t $'\t' -k9,9nr | head -10 | perl -lpe '/^(\S+)/; chomp($host = qx(host $1 | grep -o "pointer .*" | cut -d" " -f2 | head -n1)); s/^(\S+)/$host/'

use Getopt::Long;
use Socket qw(AF_INET AF_INET6 pack_sockaddr_in pack_sockaddr_in6 inet_aton inet_pton getnameinfo);
use Data::Dumper;

# ss.c: sstate_name
my @STATES = qw(ESTAB SYN-SENT SYN-RECV FIN-WAIT-1 FIN-WAIT-2 TIME-WAIT UNCONN CLOSE-WAIT LAST-ACK LISTEN CLOSING NON-EST-WITHOUT-TW);

my $top_ip = 0;
my $resolve = 1;
GetOptions(
  'n|top-ip=i' => \$top_ip,
  'resolve!' => \$resolve,
);

if (! @ARGV[0]) {
    get_tcpstat();
} else {
    parse_tcpstat_log();
}

sub ip_to_str {
    my $straddr = $_[0];
    my $sockaddr;
    return $straddr if not $resolve;

    my ($err, $hostname, $servicename);
    eval {
        $sockaddr = ($straddr =~ /:/) ? pack_sockaddr_in6(0, inet_pton(AF_INET6, $straddr)) : pack_sockaddr_in(0, inet_aton($straddr));
        ($err, $hostname, $servicename) = getnameinfo($sockaddr);
    };
    return $straddr if $@ || $err;
 
    return $hostname;
}

sub parse_tcpstat_log {
    my %tcpstat;
    while (<>) {
        next if /IP/;
        my ($ip, @state_cnt) = split /\t/;
        for (my $i = 0; $i < scalar(@STATES); $i++) {
            $tcpstat{$STATES[$i]}{$ip} += $state_cnt[$i];
        }
    }

    for my $state (@STATES) {
        my @ips_per_state = sort { $tcpstat{$state}{$b} <=> $tcpstat{$state}{$a} } grep { $_ && $tcpstat{$state}{$_} } keys %{$tcpstat{$state}};
        my $limit = $top_ip <= $#ips_per_state && $top_ip > 0 ? $top_ip : $#ips_per_state;
        map { printf "%d $state %s\n", $tcpstat{$state}{$_}, ip_to_str($_) } @ips_per_state[1 .. $limit];
    }
    # print Dumper(\%tcpstat);
}

sub get_tcpstat {
    my %conns;
    open(my $fh, '-|', 'ss -atn') or die "$!";
    
    while (<$fh>) {
        chomp;
        next if /State|LISTEN/;
        my ($state, $rcvq, $sndq, $src, $dst) = split /\s+/;
    
        my ($dst_ip) = ($dst =~ /^(.*):\d+$/);
        if (not exists $conns{$dst_ip}) {
            $conns{$dst_ip} = { map { $_ => 0 } @STATES };
        }
    
        $conns{$dst_ip}{$state}++;
        if ($state ne "ESTAB" && $state ne "TIME-WAIT") {
            $conns{$dst_ip}{"NON-EST-WITHOUT-TW"}++;
        }
    }
    
    printf "%s\n", join("\t", ("IP", @STATES));
    for $ip (keys %conns) {
        printf "%s\n", join("\t", ($ip, map { $conns{$ip}{$_} } @STATES));
    }
}
