Net-Connection-ncnetstat/Net-Connection-ncnetstat/lib/Net/Connection/ncnetstat.pm

527 lines
12 KiB
Perl

package Net::Connection::ncnetstat;
use 5.006;
use strict;
use warnings;
use Net::Connection;
use Net::Connection::Match;
use Net::Connection::Sort;
use Term::ANSIColor;
use Proc::ProcessTable;
use Text::ANSITable;
# use Net::Connection::FreeBSD_sockstat if possible
use if $^O =~ /freebsd/, 'Net::Connection::FreeBSD_sockstat';
use if $^O !~ /freebsd/, 'Net::Connection::lsof';
=head1 NAME
Net::Connection::ncnetstat - The backend for ncnetstat, the colorized and enhanced netstat like tool.
=head1 VERSION
Version 0.6.2
=cut
our $VERSION = '0.6.2';
=head1 SYNOPSIS
use Net::Connection::ncnetstat;
# Net::Connection::Match filters
my @filters=(
{
type=>'States',
invert=>1,
args=>{
states=>['LISTEN']
}
}
);
my $ncnetstat=Net::Connection::ncnetstat->new(
{
ptr=>1,
command=>1,
command_long=>0,
wchan=>0,
pct_show=>1,
sorter=>{
invert=>0,
type=>'host_lf',
},
match=>{
checks=>\@filters,
}
}
);
print $ncnetstat->run;
=head1 METHODS
=head2 new
This initiates the object.
my $ncnetstat=Net::Connection::ncnetstat->new( \%args );
=head3 args hash ref
=head4 command
If set to true, it will show the command for the PID.
=head4 command_long
If set to true, the full command is shown.
This requires command also being true.
=head4 match
This is the hash to pass to L<Net::Connection::Match>.
By default this is undef and that module won't be used.
=head4 no_pid_user
Don't show the PID or UID/user colomn.
=head4 sorter
This is what is to be passed to L<Net::Connection::Sorter>.
The default is as below.
{
type=>'host_fl',
invert=>0,
}
=cut
sub new {
my %args;
if ( defined( $_[1] ) ) {
%args = %{ $_[1] };
}
if ( !defined( $args{sorter} ) ) {
$args{sorter} = {
type => 'host_fl',
invert => 0,
};
}
my $self = {
invert => 0,
sorter => Net::Connection::Sort->new( $args{sorter} ),
ptr => 1,
command => 0,
command_long => 0,
wchan => 0,
pct => 0,
no_pid_user => 0,
};
bless $self;
if ( defined( $args{match} ) ) {
$self->{match} = Net::Connection::Match->new( $args{match} );
}
if ( defined( $args{ptr} ) ) {
$self->{ptr} = $args{ptr};
}
if ( defined( $args{command} ) ) {
$self->{command} = $args{command};
}
if ( defined( $args{pct} ) ) {
$self->{pct} = $args{pct};
}
if ( defined( $args{wchan} ) ) {
$self->{wchan} = $args{wchan};
}
if ( defined( $args{command_long} ) ) {
$self->{command_long} = $args{command_long};
}
if ( defined( $args{no_pid_user} ) ) {
$self->{no_pid_user} = $args{no_pid_user};
}
return $self;
}
=head2 run
This runs it and returns a string.
print $ncnetstat->run;
=cut
sub run {
my $self = $_[0];
my @objects;
if ( $^O !~ /freebsd/ ) {
@objects = &lsof_to_nc_objects;
}
else {
@objects = sockstat_to_nc_objects;
}
my @found;
if ( defined( $self->{match} ) ) {
foreach my $conn (@objects) {
if ( $self->{match}->match($conn) ) {
push( @found, $conn );
}
}
}
else {
@found = @objects;
}
@found = $self->{sorter}->sorter( \@found );
my $tb = Text::ANSITable->new;
$tb->border_style('ASCII::None');
$tb->color_theme('NoColor');
my @headers;
my $header_int = 0;
push( @headers, 'Proto' );
$tb->set_column_style( $header_int, pad => 0 );
$header_int++;
if ( !$self->{no_pid_user} ) {
push( @headers, 'User' );
$tb->set_column_style( $header_int, pad => 1 );
$header_int++;
push( @headers, 'PID' );
$tb->set_column_style( $header_int, pad => 0 );
$header_int++;
}
push( @headers, 'Local Host' );
$tb->set_column_style( $header_int, pad => 1, formats => [ [ wrap => { ansi => 1, mb => 1 } ] ] );
$header_int++;
push( @headers, 'L Port' );
$tb->set_column_style( $header_int, pad => 0 );
$header_int++;
push( @headers, 'Remote Host' );
$tb->set_column_style( $header_int, pad => 1, formats => [ [ wrap => { ansi => 1, mb => 1 } ] ] );
$header_int++;
push( @headers, 'R Port' );
$tb->set_column_style( $header_int, pad => 0 );
$header_int++;
push( @headers, 'State' );
$tb->set_column_style( $header_int, pad => 1 );
$header_int++;
my $padding = 0;
if ( $self->{wchan} ) {
if ( ( $header_int % 2 ) != 0 ) {
$padding = 1;
}
push( @headers, 'WChan' );
$tb->set_column_style( $header_int, pad => $padding );
$header_int++;
}
if ( $self->{pct} ) {
if ( ( $header_int % 2 ) != 0 ) {
$padding = 1;
}
else {
$padding = 0;
}
push( @headers, 'CPU%' );
$tb->set_column_style( $header_int, pad => $padding );
$header_int++;
if ( ( $header_int % 2 ) != 0 ) {
$padding = 1;
}
else {
$padding = 0;
}
push( @headers, 'Mem%' );
$tb->set_column_style( $header_int, pad => $padding );
$header_int++;
}
if ( $self->{command} ) {
if ( ( $header_int % 2 ) != 0 ) {
$padding = 1;
}
else {
$padding = 0;
}
push( @headers, 'Command' );
$tb->set_column_style( $header_int, pad => $padding, formats => [ [ wrap => { ansi => 1, mb => 1 } ] ] );
$header_int++;
}
$tb->set_column_style( 4, pad => 0 );
$tb->set_column_style( 5, pad => 1, formats => [ [ wrap => { ansi => 1, mb => 1 } ] ] );
$tb->set_column_style( 6, pad => 0 );
$tb->set_column_style( 7, pad => 1 );
$tb->set_column_style( 8, pad => 0 );
$tb->set_column_style( 9, pad => 1 );
$tb->set_column_style( 10, pad => 0 );
$tb->set_column_style( 11, pad => 1, formats => [ [ wrap => { ansi => 1, mb => 1 } ] ] );
$tb->set_column_style( 12, pad => 0 );
$tb->set_column_style( 13, pad => 1 );
$tb->columns( \@headers );
# process table stuff if needed
my $ppt;
my $proctable;
my %cmd_cache;
if ( $self->{command} ) {
$ppt = Proc::ProcessTable->new;
$proctable = $ppt->table;
}
my @td;
foreach my $conn (@found) {
my @new_line = ( color('bright_yellow') . $conn->proto . color('reset'), );
# don't add the PID or user if requested to
if ( !$self->{no_pid_user} ) {
# handle adding the username or UID if we have one
if ( defined( $conn->username ) ) {
push( @new_line, color('bright_cyan') . $conn->username . color('reset') );
}
else {
if ( defined( $conn->uid ) ) {
push( @new_line, color('bright_cyan') . $conn->uid . color('reset') );
}
else {
push( @new_line, '' );
}
}
# handle adding the PID if we have one
if ( defined( $conn->pid ) ) {
push( @new_line, color('bright_red') . $conn->pid . color('reset') );
$conn->pid;
}
else {
push( @new_line, '' );
}
}
# Figure out what we are using for the local host
my $local;
if ( defined( $conn->local_ptr ) && $self->{ptr} ) {
$local = $conn->local_ptr;
}
else {
$local = $conn->local_host;
}
# Figure out what we are using for the foriegn host
my $foreign;
if ( defined( $conn->foreign_ptr ) && $self->{ptr} ) {
$foreign = $conn->foreign_ptr;
}
else {
$foreign = $conn->foreign_host;
}
# Figure out what we are using for the local port
my $lport;
if ( defined( $conn->local_port_name ) ) {
$lport = $conn->local_port_name;
}
else {
$lport = $conn->local_port;
}
# Figure out what we are using for the foreign port
my $fport;
if ( defined( $conn->foreign_port_name ) ) {
$fport = $conn->foreign_port_name;
}
else {
$fport = $conn->foreign_port;
}
push( @new_line,
color('bright_green') . $local . color('reset'),
color('green') . $lport . color('reset'),
color('bright_magenta') . $foreign . color('reset'),
color('magenta') . $fport . color('reset'),
color('bright_blue') . $conn->state . color('reset'),
);
# handle the wchan bit if needed
if ( $self->{wchan}
&& defined( $conn->wchan ) )
{
push( @new_line, color('bright_yellow') . $conn->wchan . color('reset') );
}
# handle the percent stuff if needed
if ( $self->{pct}
&& defined( $conn->pctcpu ) )
{
push( @new_line, color('bright_cyan') . sprintf( '%.2f', $conn->pctcpu ) . color('reset') );
}
# handle the percent stuff if needed
if ( $self->{pct}
&& defined( $conn->pctmem ) )
{
push( @new_line, color('bright_green') . sprintf( '%.2f', $conn->pctmem ) . color('reset') );
}
# handle the command portion if needed
if ( defined( $conn->pid )
&& $self->{command} )
{
my $loop = 1;
my $proc = 0;
while ( defined( $proctable->[$proc] )
&& $loop )
{
my $command;
if ( defined( $cmd_cache{ $conn->pid } ) ) {
push( @new_line, color('bright_red') . $cmd_cache{ $conn->pid } . color('reset') );
$loop = 0;
}
elsif ( defined( $conn->proc ) ) {
my $command = $conn->proc;
if ( !$self->{command_long} ) {
$command =~ s/\ .*//;
}
$cmd_cache{ $conn->pid } = $command;
push( @new_line, color('bright_red') . $cmd_cache{ $conn->pid } . color('reset') );
$loop = 0,;
}
elsif ( $proctable->[$proc]->pid eq $conn->pid ) {
if ( $proctable->[$proc]->{'cmndline'} =~ /^$/ ) {
#kernel process
$cmd_cache{ $conn->pid }
= color('bright_red') . '[' . $proctable->[$proc]->{'fname'} . ']' . color('reset');
}
elsif ( $self->{command_long} ) {
$cmd_cache{ $conn->pid }
= color('bright_red') . $proctable->[$proc]->{'cmndline'} . color('reset');
}
elsif ( $proctable->[$proc]->{'cmndline'} =~ /^\// ) {
# something ran with a complete path
$cmd_cache{ $conn->pid }
= color('bright_red') . $proctable->[$proc]->{'fname'} . color('reset');
}
else {
# likely a thread or the like... such as dovecot/auth
# just trunkcat everything after the space
my $cmd = $proctable->[$proc]->{'cmndline'};
$cmd =~ s/\ +.*//g;
$cmd_cache{ $conn->pid } = color('bright_red') . $cmd . color('reset');
}
push( @new_line, $cmd_cache{ $conn->pid } );
$loop = 0;
}
$proc++;
}
}
elsif ( ( !defined( $conn->pid ) )
&& $self->{command} )
{
push( @new_line, '' );
}
$tb->add_row( \@new_line );
}
return $tb->draw;
}
=head1 TODO
* Add support for more collection methods than L<Net::Connection::lsof>
* Support color selection and column ordering.
=head1 AUTHOR
Zane C. Bowers-Hadley, C<< <vvelox at vvelox.net> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-net-connection-ncnetstat at rt.cpan.org>, or through
the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-Connection-ncnetstat>. I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Net::Connection::ncnetstat
You can also look for information at:
=over 4
=item * RT: CPAN's request tracker (report bugs here)
L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-Connection-ncnetstat>
=item * AnnoCPAN: Annotated CPAN documentation
L<http://annocpan.org/dist/Net-Connection-ncnetstat>
=item * CPAN Ratings
L<https://cpanratings.perl.org/d/Net-Connection-ncnetstat>
=item * Search CPAN
L<https://metacpan.org/release/Net-Connection-ncnetstat>
=item * Repository
L<https://github.com/VVelox/Net-Connection-ncnetstat>
=back
=head1 ACKNOWLEDGEMENTS
=head1 LICENSE AND COPYRIGHT
This software is Copyright (c) 2019 by Zane C. Bowers-Hadley.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
=cut
1; # End of Net::Connection::ncnetstat