#!/usr/bin/perl # # Module: vyatta-show-interfaces.pl # # **** License **** # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # This code was originally developed by Vyatta, Inc. # Portions created by Vyatta are Copyright (C) 2007 Vyatta, Inc. # All Rights Reserved. # # Author: Stig Thormodsrud # Date: February 2008 # Description: Script to display interface information # # **** End License **** # use lib "/opt/vyatta/share/perl5/"; use Vyatta::Interface; use Vyatta::Misc; use Getopt::Long; use POSIX; use NetAddr::IP; use strict; use warnings; # # valid actions # my %action_hash = ( 'show' => \&run_show_intf, 'show-brief' => \&run_show_intf_brief, 'show-count' => \&run_show_counters, 'clear' => \&run_clear_intf, 'reset' => \&run_reset_intf, ); my $clear_stats_dir = '/var/run/vyatta'; my $clear_file_magic = 'XYZZYX'; my @rx_stat_vars = qw/rx_bytes rx_packets rx_errors rx_dropped rx_over_errors multicast/; my @tx_stat_vars = qw/tx_bytes tx_packets tx_errors tx_dropped tx_carrier_errors collisions/; sub get_intf_description { my $name = shift; my $description = interface_description($name); return "" unless $description; return $description; } sub get_intf_stats { my $intf = shift; my %stats = (); foreach my $var (@rx_stat_vars, @tx_stat_vars) { $stats{$var} = get_sysfs_value($intf, "statistics/$var"); } return %stats; } sub get_intf_statsfile { my $intf = shift; return "$clear_stats_dir/$intf.stats"; } sub get_clear_stats { my $intf = shift; my %stats = (); foreach my $var (@rx_stat_vars, @tx_stat_vars) { $stats{$var} = 0; } my $filename = get_intf_statsfile($intf); open (my $f, '<', $filename) or return %stats; my $magic = <$f>; chomp $magic; if ($magic ne $clear_file_magic) { print "bad magic [$intf]\n"; return %stats; } my $timestamp = <$f>; chomp $timestamp; $stats{'timestamp'} = $timestamp; while (<$f>) { chop; my ($var, $val) = split(/,/); $stats{$var} = $val; } close($f); return %stats; } sub get_ipaddr { my $name = shift; # Skip IPV6 default Link-local return grep { !/^fe80/ } Vyatta::Misc::getIP($name); } sub get_state_link { my $intf = shift; my $state; my $link = 'down'; my $flags = get_sysfs_value($intf, 'flags'); my $hex_flags = hex($flags); if ($hex_flags & 0x1) { # IFF_UP $state = 'up'; my $carrier = get_sysfs_value($intf, 'carrier'); if ($carrier eq '1') { $link = "up"; } } else { $state = "admin down"; } return ($state, $link); } sub is_valid_intf { my $name = shift; return unless $name; my $intf = new Vyatta::Interface($name); return unless $intf; return $intf->exists(); } sub get_intf_for_type { my $type = shift; my @interfaces = getInterfaces(); my @list = (); foreach my $name (@interfaces) { if ($type) { my $intf = new Vyatta::Interface($name); next unless $intf; # unknown type next if ($type ne $intf->type()); } push @list, $name; } return @list; } # This function has to deal with both 32 and 64 bit counters sub get_counter_val { my ($clear, $now) = @_; return $now if $clear == 0; # device is using 64 bit values assume they never wrap my $value = $now - $clear; return $value if ($now >> 32) != 0; # The counter has rolled. If the counter has rolled # multiple times since the clear value, then this math # is meaningless. $value = (4294967296 - $clear) + $now if ($value < 0); return $value; } # # The "action" routines # sub run_show_intf { my @intfs = @_; foreach my $intf (@intfs) { my %clear = get_clear_stats($intf); my $description = get_intf_description($intf); my $timestamp = $clear{'timestamp'}; my $line = `ip addr show $intf | sed 's/^[0-9]*: //'`; chomp $line; if ($line =~ /link\/tunnel6/) { my $estat = `ip -6 tun show $intf | sed 's/.*encap/encap/'`; $line =~ s% link/tunnel6% $estat$&%; } print "$line\n"; if (defined $timestamp and $timestamp ne "") { my $time_str = strftime("%a %b %d %R:%S %Z %Y", localtime($timestamp)); print " Last clear: $time_str\n"; } if (defined $description and $description ne "") { print " Description: $description\n"; } print "\n"; my %stats = get_intf_stats($intf); my $fmt = " %10s %10s %10s %10s %10s %10s\n"; printf($fmt, "RX: bytes", "packets", "errors", "dropped", "overrun", "mcast"); printf($fmt, map { get_counter_val($clear{$_}, $stats{$_}) } @rx_stat_vars); printf($fmt, "TX: bytes", "packets", "errors", "dropped", "carrier", "collisions"); printf($fmt, map { get_counter_val($clear{$_}, $stats{$_}) } @tx_stat_vars); } } sub conv_brief_code { my $state = pop(@_); $state = 'u' if ($state eq 'up'); $state = 'D' if ($state eq 'down'); $state = 'A' if ($state eq 'admin down'); return $state; } sub conv_descriptions { my $description = pop @_; my @descriptions; my $line = ''; foreach my $elem (split(' ', $description)){ if ((length($line) + length($elem)) >= 24){ push(@descriptions, $line); $line = "$elem "; } else { $line .= "$elem "; } } push(@descriptions, $line); return @descriptions; } sub run_show_intf_brief { my @intfs = @_; my $format = "%-16s %-33s %-4s %s\n"; my $format2 = "%-16s %s\n"; print "Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down\n"; printf($format, "Interface","IP Address","S/L","Description"); printf($format, "---------","----------","---","-----------"); foreach my $intf (@intfs) { next if ($intf =~ /gre0/); next if ($intf =~ /sit0/); next if ($intf =~ /tunl0/); next if ($intf =~ /ip6tnl0/); my @ip_addr = get_ipaddr($intf); my ($state, $link) = get_state_link($intf); $state = conv_brief_code($state); $link = conv_brief_code($link); my $description = get_intf_description($intf); my @descriptions = conv_descriptions($description); if (scalar(@ip_addr) == 0) { my $desc = ''; $desc = shift @descriptions if (scalar(@descriptions) > 0 ); printf($format, $intf, "-", "$state/$link", $desc); foreach my $descrip (@descriptions){ printf($format, '', '', '', $descrip); } } else { my $tmpip = shift(@ip_addr); my $desc = ''; if (length($tmpip) < 33){ $desc = shift @descriptions if (scalar(@descriptions) > 0 ); printf($format, $intf, $tmpip , "$state/$link", $desc); foreach my $descrip (@descriptions){ printf($format, '', '', '', $descrip); } foreach my $ip (@ip_addr) { printf($format2, '', $ip) if (defined $ip); } } else { $desc = shift @descriptions if (scalar(@descriptions) > 0 ); printf($format2, $intf, $tmpip); my $printed_desc = 0; foreach my $ip (@ip_addr) { if (length($ip) >= 33) { printf($format2, '', $ip) if (defined $ip); } else { if (!$printed_desc){ printf($format, '', $ip, "$state/$link", $desc); $printed_desc = 1; foreach my $descrip (@descriptions){ printf($format, '', '', '', $descrip); } } else { printf($format2, '', $ip); } } } if (!$printed_desc){ printf($format, '', '', "$state/$link", $desc); foreach my $descrip (@descriptions){ printf($format, '', '', '', $descrip); } } } } } } sub run_show_counters { my @intfs = @_; printf("%-12s %10s %10s %10s %10s\n", "Interface","Rx Packets","Rx Bytes","Tx Packets","Tx Bytes"); foreach my $intf (@intfs) { my ($state, $link) = get_state_link($intf); next if $state ne 'up'; my %clear = get_clear_stats($intf); my %stats = get_intf_stats($intf); printf("%-12s %10s %10s %10s %10s\n", $intf, get_counter_val($clear{rx_packets}, $stats{rx_packets}), get_counter_val($clear{rx_bytes}, $stats{rx_bytes}), get_counter_val($clear{tx_packets}, $stats{tx_packets}), get_counter_val($clear{tx_bytes}, $stats{tx_bytes}) ); } } sub run_clear_intf { my @intfs = @_; foreach my $intf (@intfs) { my %stats = get_intf_stats($intf); my $filename = get_intf_statsfile($intf); mkdir $clear_stats_dir unless ( -d $clear_stats_dir ); open(my $f, '>', $filename) or die "Couldn't open $filename [$!]\n"; print "Clearing $intf\n"; print $f $clear_file_magic, "\n", time(), "\n"; while (my ($var, $val) = each (%stats)) { print $f $var, ",", $val, "\n"; } close($f); } } sub run_reset_intf { my @intfs = @_; foreach my $intf (@intfs) { my $filename = get_intf_stats($intf); system("rm -f $filename"); } } sub alphanum_split { my ($str) = @_; my @list = split m/(?=(?<=\D)\d|(?<=\d)\D)/, $str; return @list; } sub natural_order { my ($a, $b) = @_; my @a = alphanum_split($a); my @b = alphanum_split($b); while (@a && @b) { my $a_seg = shift @a; my $b_seg = shift @b; my $val; if (($a_seg =~ /\d/) && ($b_seg =~ /\d/)) { $val = $a_seg <=> $b_seg; } else { $val = $a_seg cmp $b_seg; } if ($val != 0) { return $val; } } return @a <=> @b; } sub intf_sort { my @a = @_; my @new_a = sort { natural_order($a,$b) } @a; return @new_a; } sub usage { print "Usage: $0 [intf=NAME|intf-type=TYPE] action=ACTION\n"; print " NAME = ", join(' | ', get_intf_for_type()), "\n"; print " TYPE = ", join(' | ', Vyatta::Interface::interface_types()), "\n"; print " ACTION = ", join(' | ', keys %action_hash), "\n"; exit 1; } # # main # my @intf_list = (); my ($intf_type, $intf); my $action = 'show'; GetOptions("intf-type=s" => \$intf_type, "intf=s" => \$intf, "action=s" => \$action, ) or usage(); if ($intf) { die "Invalid interface [$intf]\n" unless is_valid_intf($intf); push @intf_list, $intf; } elsif ($intf_type) { @intf_list = get_intf_for_type($intf_type); } else { # get all interfaces @intf_list = get_intf_for_type(); } @intf_list = intf_sort(@intf_list); my $func; if (defined $action_hash{$action}) { $func = $action_hash{$action}; } else { print "Invalid action [$action]\n"; usage(); } # # make it so... # &$func(@intf_list);