#!/usr/bin/perl require 5.008; # # ifinfo v0.90 by Joseph Landman (landman@scalableinformatics.com) # copyright 2002,2003 by Scalable Informatics LLC # # License: GPL 2.0 (see the enclosed LICENSE file) # # Changelog: # # v0.25 # Get the basic idea down and working. Add a little option parsing so as # to get the information back in the needed format # v0.40 # Add the mask conversion stuff, fix some formatting issues, and try to # fix the spurious "something or the other not defined" bug in comparisons. # (the -w and strict options are wonderful!) # v0.50 # Fixed a bug. Incorrectly defined Gigabyte. Correct # definition now in place. # v0.60 # Added name service query option (--ns) to report the name(s) of the # interface(s) # # v0.75 # Added routing bits # # v0.85 # Added hardware MAC address, irq, etc # # v0.90 # fixed insecure path, device sorting, and headers # # todo: # 1) make portable (e.g. abstract data gathering) to all *nix # 2) creeping featuritis ... nah # 3) test corner cases (multiple odd aliases, things not well represented in the # current set of probed files/structures) use strict; use Net::netent qw(:FIELDS); use Net::hostent; use Socket; use IO::File; use Carp; use Data::Dumper; use Getopt::Long; # # constants # use constant kilobyte => 1024; use constant megabyte => 1024 * kilobyte; use constant gigabyte => 1024 * megabyte; use constant true => (1==1); use constant false => (1==0); # # Program paths and options: change these as needed # use constant netstat_binary => '/bin/netstat -nre'; use constant ifconfig_binary => '/sbin/ifconfig '; # # variables and run time setup # # # fix the path so that it is not wide open ... delete un-needed environment bits # $ENV{'PATH'} = '/bin:/usr/bin:/sbin'; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; my $fh = new IO::File; my $net; my @proc = qw( rx_bytes rx_packets rx_errors rx_drop rx_fifo rx_frame rx_compressed rx_multicast tx_bytes tx_packets tx_errors tx_drop tx_fifo tx_frame tx_compressed tx_multicast HWaddr name ); my @include = qw(Link_encap inet_addr Broadcast Mask MTU Metric dev); my @dir_service = qw(name); my @options = (@proc,@include,@dir_service); my (%route,$run_netstat,$tmp_y,@_info); my (@info,%ifname,@bytes,@lines,$long,$key,$value,$ns,$h,$name); my ($s,%status,@vector,$mask,$addr_and_mask,$field_size,$route); my $sep = ","; my ($format,$device); my (@ints,@all,$interfaces,$request,$state,$help,@params,$paramstring); my ($mac,$run_ifconfig,$irq,$tmp,$no_headers,$macnc,$kid_ps); $mac=false; $macnc=false; $no_headers=false; # # Parse command line options # my $result = GetOptions ( "info=s" => \$request, "ifname=s" =>\$interfaces, "sep=s"=>\$sep, "format=s" =>\$format, "mask=s" => \$mask, "help" => \$help, "ns" => \$ns, "mac" => \$mac, "irq" => \$irq, "route"=> \$route, "noheaders"=> \$no_headers ); if (defined($help)) { &print_help_and_exit() ; } if (defined($format)) { $format .= "\n"; } # append a new-line on fixed formats if (defined($request) && ($request =~ /name/)) { $ns = 1; } # make sure name service is on # if we request info by name # # start # # open the /proc/net/dev file and read it all in ... if (!($fh->open("< /proc/net/dev"))) { croak "Could not open /proc/net/dev for reading\n"; } @all=$fh->getlines(); $fh->close; # ... drop first two lines, and remove \n from ends # of all of the lines. shift @all; shift @all; chomp @all; # grab routing table if requested... &get_routing if ($route); if (defined($interfaces)) { @ints=split(/[:,\,,\s+]/,$interfaces); # allow : , and whitespace to # seperate the names } # # check info parameters against allowable parameters # if (defined($request)) { @params=split(/[:,\,,\s+]/,$request); # allow : , and whitespace to # seperate the parameters # if no format string has been defined, build one out # of the parameters and a seperator. Assume all parameters # are strings (not really, but this can work for the moment) if (!defined($format)) { my $tmpfmt="%s".$sep; $format=$tmpfmt x ($#params); $format .="%s\n"; } } # parse the remaining lines ... foreach my $line (@all) { $state=1; $line =~ s/^\s+//g; # ... trim leading white space $line =~ s/:/ /g; # ... turn ":" into white space $line =~ s/\s+/\ /g;# ... turn multiple white spaces # into a single white space @info = split (/[:,\s]/,$line); # split on : or white space #print "info: ",join(":",@info),"\n"; map($state &&= ($info[0] ne $_),@ints); # "AND" the comparisons together #print "state,if=>",$state," ",$info[0],"\n"; if (@ints) {next if ($state);} # skip if the interface wasnt on the command line $ifname{$info[0]} = { 'rx_bytes' => $info[1], 'rx_packets' => $info[2], 'rx_errors' => $info[3], 'rx_drop' => $info[4], 'rx_fifo' => $info[5], 'rx_frame' => $info[6], 'rx_compressed' => $info[7], 'rx_multicast' => $info[8], 'tx_bytes' => $info[9], 'tx_packets' => $info[10], 'tx_errors' => $info[11], 'tx_drop' => $info[12], 'tx_fifo' => $info[13], 'tx_frame' => $info[14], 'tx_compressed' => $info[15], 'tx_multicast' => $info[16], 'dev' => $info[0] }; # now grab the information from ifconfig if possible $info[0] =~ /(\w+)/; $device=$1; $run_ifconfig = ifconfig_binary . " " . $device ." |"; $ENV{'PATH'} = '/bin:/usr/bin:/sbin'; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV' }; open ($kid_ps, "$run_ifconfig"); $long=""; #open $kid_ps, ifconfig_binary , $device , "-|" or die $!; while ( <$kid_ps>) { $long .= $_ } close($kid_ps); $long =~ s/\n/ /g; # remove newlines if ($long =~ /HWaddr\s(\S+)\s+/) { $ifname{$info[0]}->{'HWaddr'}=$1; } if ($long =~ /Interrupt:(\S+)\s+/) { $ifname{$info[0]}->{'irq'}=$1; } foreach my $elt (split(/\s{2,}/,$long)) # split on 2 or more white spaces { next if ($elt eq $info[0]); #skip the interface name if (!($elt =~ m/HWaddr/)) { ($key,$value)=split(":",$elt,2); $key =~ s/\s/\_/g; } else { ($key,$value)=split(" ",$elt,2); } if (map(($key =~ m/$_/),@include)) # only add the keys in the include array { $ifname{$info[0]}->{$key}=$value; } } } if (!$no_headers) { if ( (!$format) && (!$ns) && (!$route) && (!$mac) ) { printf "device:\taddress/netmask\t\t\tMTU\t Tx (MB)\t Rx (MB)\n"; } if ( (!$format) && (!$ns) && (!$route) && ($mac) ) { printf "device:\tMAC\t\taddress/netmask\t\t\tMTU\t Tx (MB)\t Rx (MB)\n"; } if ( (!$format) && (!$ns) && ($route) ) { printf "device:\taddress/netmask\t\t\t destination/gw\n"; } if ( (!$format) && ($ns) && (!$route) && (!$mac) ) { printf "device:\tname from resolver\t\tMTU\t Tx (MB)\t Rx (MB)\n"; } if ( (!$format) && ($ns) && (!$route) && ($mac) ) { printf "device:\tMAC\t\tname from resolver\t\tMTU\t Tx (MB)\t Rx (MB)\n"; } } foreach my $interface (sort { lc($a) cmp lc($b) } keys %ifname) { %status=%{$ifname{$interface}}; $field_size=31; #print Dumper(\%status),"\n"; if ((!exists($status{'tx_bytes'})) || (!($status{'tx_bytes'}))) { $status{'tx_bytes'} = 0; } if ((!exists($status{'HWaddr'})) || (!($status{'HWaddr'}))) { $status{'HWaddr'} = "00:00:00:00:00:00"; } if ((!exists($status{'irq'})) || (!($status{'irq'}))) { $status{'irq'} = "-"; } if ((!exists($status{'rx_bytes'})) ||(!($status{'rx_bytes'}))) { $status{'rx_bytes'} = 0; } if ((!exists($status{'inet_addr'})) ||(!($status{'inet_addr'}))) { $status{'inet_addr'} = 'addr not set'; $status{'name'} = 'name not set'; } else { if ((defined($ns)) || (defined($status{'name'}))) { if ($h=gethost($status{'inet_addr'})) { $status{'name'}=$h->name; } else { $status{'name'}="Unknown"; } } } if ((!exists($status{'Mask'})) ||(!($status{'Mask'}))) { $status{'Mask'} = 'mask not set'; } elsif ((defined($mask)) && ($mask eq "hex")) { $tmp=&quad_to_hex($status{'Mask'}); $status{'Mask'}="0x".$tmp;$field_size=31; } elsif ((defined($mask)) && ($mask eq "range")) { $tmp=&count_bits($status{'Mask'}); $status{'Mask'}=$tmp;$field_size=31; } elsif ((defined($mask)) && ($mask eq "bits")) { my $tmp=&quad_to_bits($status{'Mask'}); $status{'Mask'}=$tmp; $field_size=47; } if (!($format)) { $addr_and_mask = sprintf "%s/%s", $status{'inet_addr'}, $status{'Mask'}; $addr_and_mask .= " " x $field_size; if (!$ns) { if (!defined($route)) { if (!($mac)) { printf "%s:\t%s\t%i\t%10.3f\t%10.3f\n", $interface, substr($addr_and_mask,0,$field_size), $status{'MTU'}, $status{'tx_bytes'}/megabyte, $status{'rx_bytes'}/megabyte; } else { $status{'HWaddr'} =~ s/://g; printf "%s:\t%s\t%s\t%i\t%10.3f\t%10.3f\n", $interface,$status{'HWaddr'}, substr($addr_and_mask,0,$field_size), $status{'MTU'}, $status{'tx_bytes'}/megabyte, $status{'rx_bytes'}/megabyte; } } else { my @routes; #printf "-----P\nDump=%s\n",Dumper(\%route); map { if (($route{$_}->{'dev'}) eq $interface) { $tmp_y = ""; $tmp_y = sprintf '%s/%s', $_, $route{$_}->{'genmask'}; $tmp_y .= " " x 32; push @routes, sprintf '%s -> %s', substr($tmp_y, 0, 32), $route{$_}->{'gateway'}; } } keys %route; my $paths=join("\n\t",@routes); printf "%s:\t%s\n", $interface,$paths; } } else { my $space=" " x 80; if (!($mac)) { printf "%s:\t%s\t%i\t%10.3f\t%10.3f\n", $interface, substr($status{'name'}.$space,0,$field_size), $status{'MTU'}, $status{'tx_bytes'}/megabyte, $status{'rx_bytes'}/megabyte; } else { $status{'HWaddr'} =~ s/://g; printf "%s:\t%s\t%s\t%i\t%10.3f\t%10.3f\n", $interface,$status{'HWaddr'}, substr($status{'name'}.$space,0,$field_size), $status{'MTU'}, $status{'tx_bytes'}/megabyte, $status{'rx_bytes'}/megabyte; } } } if ($format) { printf $format,map($status{$_} || "_not_set_",@params); } } sub print_help_and_exit { exec "man ifinfo"; exit; } sub quad_to_hex { my $quad=shift; my @y=split(/\./,$quad); my $sc=256; my $s=1; my $sum = 0; foreach my $elt (@y) { $sum += $s*$elt; $s *= $sc ;} $s=unpack("H*",pack("L",$sum)); return $s; } sub quad_to_bits { my $quad=shift; my @y=split(/\./,$quad); my $sc=256; my $s=1; my $sum = 0; foreach my $elt (@y) { $sum += $s*$elt; $s *= $sc ;} $s=unpack("B*",pack("L",$sum)); return $s; } sub count_bits { my $quad=shift; my $bits=&quad_to_bits($quad); my @exploded=split(/ */,$bits); #print join(":",@exploded),"\n"; my $sum=0; foreach my $s (@exploded) { $sum++ if $s; } return $sum; } sub get_routing { my @lines; $run_netstat = netstat_binary . " |"; return if (!(open($fh,$run_netstat) )); @lines=<$fh>; # grab all lines in file close($fh); shift @lines; shift @lines; # drop first two lines of text foreach my $line (@lines) # loop over routing table { $line =~ m/^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/; #$line =~ m/^((\S+)\s+){8}$/; $route{$1}->{'gateway'}=$2; $route{$1}->{'genmask'}=$3; $route{$1}->{'flags'}=$4; $route{$1}->{'metric'}=$5; $route{$1}->{'ref'}=$6; $route{$1}->{'use'}=$7; $route{$1}->{'dev'}=$8; } } sub parse { my $line=shift; my $in=shift; my %ret; if ($line =~ /Link encap:(\w+)\s+/) { $ret{'Link_encap'}=$1; } if ($line =~ /HWaddr\s+(\S+)\s+/) { $ret{'HWaddr'}=$1; } if ($line =~ /HWaddr\s+(\S+)\s+/) { $ret{'HWaddr'}=$1; } } __END__ =head1 NAME ifinfo - a tool to get network interface information =head1 SYNOPSIS B =head1 DESCRIPTION B is a simple commandline network inquiry/formatting tool specifically designed to output some useful information about your network connection. It works by querying the relevant tables in I as well as the output of various Unix commands. Its entire purpose in life is to tell you what you want to know, hopefully in a format that is useful and compact. =head1 OPTIONS =over 4 =item B<--ifname=>I[I<,interface_2>][I<,interface_3>]... Choose which interfaces to display. Defaults to all interfaces. You may use as many interfaces as you wish. Repeated interface names will not generate multiple output lines. Missing interfaces will be silently ignored. =item B<--info=>I[I<,parameter_2>][I<,parameter_3>]... Choose which parameters to display. Defaults to a summary of all interfaces with IP address/netmask, maximum transmit unit, bytes transmitted and received. The column headers in the below example below are not part of the output. Ifname IP/netmask MTU Tx (MB) Rx (MB) lo: 127.0.0.1/255.0.0.0 16436 210.524 MB 210.524 MB eth0: 192.168.1.16/255.255.255.0 1500 1787.516 MB 186.974 MB The parameters you may use for this are as follows: =over 2 =item B: number of received bytes =item B: number of received packets =item B: number of receive errors =item B: number of receive packets dropped =item B: =item B: =item B: =item B: number of received multicast packets =item B: number of transmitted bytes =item B: number of transmitted packets =item B: number of transmit errors =item B: number of transmit packets dropped =item B: =item B: =item B: =item B: number of transmitted multicast packets =item B: the hardware MAC address of the interface =item B: the name of this interface according to the resolver =item B: the interrupt associated with the interface. A returned value of "-" means that no interrupt has been assigned to the interface. =back Note: current versions of the Linux kernel have a network byte counter overflow at 4 GB, so that if you are transfering more than 4 GB of data, you will find that the counters overflow and wrap around. =item B<--sep=>I This is the character that is used to separate fields in the output. ifinfo --sep="," --ifname=eth0,lo --info=dev,HWaddr,irq,inet_addr,name lo,00:00:00:00:00:00,-,127.0.0.1,localhost.localdomain eth0,00:40:05:0B:9F:01,5,192.168.1.16,squash.scalableinformatics.com =item B<--format=>"I" This gives much finer grain control over the output of the program. You may use this to encode an arbitrary format string. It is possible to confuse the program with an incorrect format specifier. ifinfo --format="[%s],%s+%s" --ifname=eth0,lo --info=dev,HWaddr,inet_addr [lo],00:00:00:00:00:00+127.0.0.1 [eth0],00:40:05:0B:9F:01+192.168.1.16 ifinfo --format="%s%s%s" \ --ifname=eth0,lo --info=dev,HWaddr,inet_addr lo00:00:00:00:00:00127.0.0.1 eth000:40:05:0B:9F:01192.168.1.16 We recommend simply using B<%s> as the format specifier for a particular field. Wrap the field with the text you need. The above example shows how this could be used as part of a status display for a web based machine. =item B<--mask=>[I|I|I] This option controls how the netmask is printed. The usual method is in terms of the integer quads I. The I option allows you to change that. ifinfo --mask=hex --ifname=eth0,lo lo: 127.0.0.1/0xff000000 16436 219.678 MB 219.678 MB eth0: 192.168.1.16/0xffffff00 1500 1793.610 MB 189.103 MB ifinfo --mask=range --ifname=eth0,lo lo: 127.0.0.1/8 16436 219.685 MB 219.685 MB eth0: 192.168.1.16/24 1500 1793.614 MB 189.108 MB ifinfo --mask=bits --ifname=eth0,lo lo: 127.0.0.1/11111111000000000000000000000000 16436 219.691 MB 219.691 MB eth0: 192.168.1.16/11111111111111111111111100000000 1500 1793.620 MB 189.114 MB =item B<--help> The man page. =item B<--ns> This option queries your default resolver service to return the name of the particular interface. It is a synonym for the parameter I which can be used in the I<--info=...> option. ifinfo --ifname=eth0,lo --ns lo: localhost.localdomain 16436 220.802 MB 220.802 MB eth0: squash.scalableinformatics.com 1500 1794.392 MB 189.379 MB ifinfo --ifname=eth0,lo --info=dev,name lo,localhost.localdomain eth0,squash.scalableinformatics.com =item B<--irq> This option reports the interrupt assigned to the interface. It is a synonym for the parameter I which can be used in the I<--info=...> option. A minus sign (B<->) indicates that this interface does not have an interrupt assigned. This might be the case for various network interfaces which do not have hardware drivers attached to them. ifinfo --ifname=eth0,lo --info=dev,name,irq lo,localhost.localdomain,- eth0,squash.scalableinformatics.com,5 =item B<--route> This option returns a simple representation of the current routing tables. ifinfo --ifname=eth0,lo --route lo: 127.0.0.0/255.0.0.0 -> 0.0.0.0 eth0: 0.0.0.0/0.0.0.0 -> 192.168.1.254 192.168.1.0/255.255.255.0 -> 0.0.0.0 169.254.0.0/255.255.0.0 -> 0.0.0.0 The route I<0.0.0.0/0.0.0.0> address pointing to a particular IP address represents the default route. The masking bits above do not currently work with this option. =head1 FILES =over 4 /usr/local/bin/ifinfo =back =head1 DIAGNOSTICS B will emit warning messages for incorrect parameters. =head1 REQUIRES Perl 5.6.0 or higher, Getopt::Long, POSIX. =head1 SEE ALSO ifconfig(8), route(8), netstat(8) =head1 LICENSE This code is licensed under GPL version 2.0. See http://www.gnu.org/copyleft/gpl.html#SEC1 for specific details. =head1 AUTHOR Joe Landman landman@scalableinformatics.com L =cut