diff --git a/bin/cfgmaker b/bin/cfgmaker index 703791d..e022bd1 100755 --- a/bin/cfgmaker +++ b/bin/cfgmaker @@ -44,7 +44,7 @@ BEGIN { use FindBin; use lib "${FindBin::Bin}"; -use lib "${FindBin::Bin}${main::SL}..${main::SL}lib${main::SL}mrtg2"; +use lib "${FindBin::Bin}${main::SL}..${main::SL}@@lib@@${main::SL}mrtg2"; use MRTG_lib "2.100015"; use Getopt::Long; diff --git a/bin/cfgmaker.lib64 b/bin/cfgmaker.lib64 new file mode 100755 index 0000000..703791d --- /dev/null +++ b/bin/cfgmaker.lib64 @@ -0,0 +1,2892 @@ +#! /usr/bin/perl -w +# -*- mode: Perl -*- +################################################################## +# MRTG 2.17.7 -- Config file creator +################################################################## +# Created by Tobias Oetiker +# this produces an mrtg config file for one router or more routers +# by pulling info off the router via snmp +################################################################## +# Distributed under the GNU copyleft +# Copyright 2000 by Tobias Oetiker +################################################################## + +# DEBUG TARGETS +# base - basic program flow +# snpo - SNMP Polling +# snpd - SNMP Detail +#@main::DEBUG=qw(base); +@main::DEBUG=qw(base snpo snpd); + +require 5.005; +use strict; + +BEGIN { + # Automatic OS detection ... do NOT touch + if ( $^O =~ /^(?:(ms)?(dos|win(32|nt)?))/i ) { + $main::OS = 'NT'; + $main::SL = '\\'; + $main::PS = ';'; + } elsif ( $^O =~ /^NetWare$/i ) { + $main::OS = 'NW'; + $main::SL = '/'; + $main::PS = ';'; + } elsif ( $^O =~ /^VMS$/i ) { + $main::OS = 'VMS'; + $main::SL = '.'; + $main::PS = ':'; + } else { + $main::OS = 'UNIX'; + $main::SL = '/'; + $main::PS = ':'; + } +} + +use FindBin; +use lib "${FindBin::Bin}"; +use lib "${FindBin::Bin}${main::SL}..${main::SL}lib${main::SL}mrtg2"; + +use MRTG_lib "2.100015"; +use Getopt::Long; +use Pod::Usage; +use Socket; + + +sub main { + + # assign defaults + my %opt = ( + 'enable-ipv6' => 0, + 'use-16bit' => 0, + 'community' => 'public', + 'interfaces' => 1, + 'enablesnmpv3' => 0, +# 'snmp-options' => ':::::2' + ); + my %routers; + my %confcache; + my $ipv4only; + my %v3opt; + $opt{fullcmd} = + "$0 ".(join " ", + map {$_ =~ /[ \[\]\*\{\}\;\>\<\&]/ ? qq{"$_"} : $_ } @ARGV); + options(\%opt,\%routers); + + # Check for IPv6 libraries if IPv6 is enabled. + # If the check fails, IPv6 is disabled. + $ipv4only = 1; + if ($opt{'enable-ipv6'} == 1) { + if ((eval {local $SIG{__DIE__};require Socket6;}) && (eval {local $SIG{__DIE__};require IO::Socket::INET6;})) { + debug ('base',"IPv6 libraries found, IPv6 enabled."); + $ipv4only = 0; + } else { + warn "WARNING: IPv6 libraries not found, IPv6 disabled.\n"; + $opt{'enable-ipv6'} = 0; + } + } + + if ($opt{'use-16bit'} == 1) { + warn "WARNING: Using 16bit RequestIDs\n"; + no warnings; + $SNMP_Session::default_use_16bit_request_ids=1; + } + # Check for SNMP V3 + # + if (exists($opt{username}) or lc($opt{enablesnmpv3}) eq "yes" ) { + if (eval {local $SIG{__DIE__};require Net_SNMP_util;}) { + import Net_SNMP_util; + debug('base', "SNMP V3 libraries found, SNMP V3 enabled."); + $opt{enablesnmpv3} = "yes"; + push @{$opt{global}}, "enablesnmpv3: yes"; + } else { + warn "WARNING: SNMP V3 libraries not found, SNMP V3 disabled. Falling back to V2c.\n"; + require SNMP_util; + import SNMP_util; + $opt{enablesnmpv3} = "revert"; + } + } + else { + require SNMP_util; + import SNMP_util; + $opt{enablesnmpv3} = "no"; + } + + init(); + + foreach my $router + (sort + {($routers{$a}{noofrouter}) <=> ($routers{$b}{noofrouter})} + keys %routers) + { + my @snmpopt = split(/:/,$routers{$router}{'snmp-options'}); + if ($snmpopt[5] and $snmpopt[5] == 3) { + if ($opt{enablesnmpv3} eq "revert") { + $snmpopt[5] = 2; + warn "reverting to snmpV2c for router $router\n"; + $routers{$router}{'snmp-options'} = join(":",@snmpopt); + $routers{$router}{snmpopt_current} = $routers{$router}{'snmp-options'}; + } else { + die "SNMP V3 requires a --username parameter as part of the User Security Model for router $routers{$router}{routerkey}" if $opt{enablesnmpv3} eq "no"; + $routers{$router}{enablesnmpv3} = $opt{enablesnmpv3}; + %v3opt = parsev3(\%opt); + } + } else { + debug('base',"snmpv3 available, but using v1/v2c for $routers{$router}{routerkey}") if $opt{enablesnmpv3} eq "yes"; + } + + + # pod2usage(-verbose=>1,-message=>"ERROR: Could not Parse $router\n") + # unless $router =~ /.*\@.*/; + debug('base',"Get Device Info on $router"); + $routers{$router}{ipv4only} = $ipv4only; + if ( my $devinfo = DeviceInfo($router,\%routers,\%v3opt) ) { + $routers{$router}{deviceinfo} = $devinfo; + } else { + warn "WARNING: Skipping $router as no info could be retrieved\n\n"; + sleep 5; + next; + } + + if ($opt{interfaces}) { + debug('base',"Populating confcache"); + populateconfcache(\%confcache,$router,$routers{$router}{ipv4only},1,\%v3opt); + debug('base',"Get Interface Info"); + InterfaceInfo(\%confcache,\%routers,$router,\%opt,\%v3opt); + } + } + GenConf(\%opt,\%routers,\%confcache,\%v3opt); +} # end main + +main; +exit 0; + +sub InterfaceInfo($$$$$) { + my $confcache = shift; + my $routers = shift; + my $router = shift; + my $opt = shift; + my $v3opt = shift; + my @Variables = qw (ifIndex ifType ifAdminStatus ifOperStatus ifMtu); + + my $snmphost = v4onlyifnecessary($router, $routers->{$router}{ipv4only}); + + if ($routers->{$router}{deviceinfo}{Vendor} eq 'cisco' && + $routers->{$router}{deviceinfo}{sysDescr} =~ m/Version\s+(\d+\.\d+)/) { + push @Variables, ($1 > 11.0 or $1 < 10.0 ) ? "ifAlias" : "CiscolocIfDescr"; + if ($1 > 11.2) {push @Variables, "vmVlan";}; + if ($1 > 11.3) {push @Variables, "vlanTrunkPortDynamicStatus";}; + } elsif ( $routers->{$router}{deviceinfo}{Vendor} =~ /(?:hp|juniper|dlink|wwp|foundry|dellLan|force10|3com|extremenetworks|openBSD|arista|enterasys|zyxel|vyatta|dcn|brocade)/i) { + push @Variables, "ifAlias"; + } + + my $descr = $routers->{$router}{deviceinfo}{sysDescr}; + if ($routers->{$router}{deviceinfo}{Vendor} eq 'cisco' && + $descr =~ m/Catalyst\sOperating\sSystem|Cisco\sSystems\sWS-C2900/ ) { + push @Variables, "CiscoCatalystPortName"; + push @Variables, "vmVlan"; + } + if ($routers->{$router}{deviceinfo}{Vendor} eq 'cisco' && + $descr =~ m/Catalyst/ ) { + push @Variables, "vlanTrunkPortDynamicStatus"; + } + if ($descr =~ m/Passport-8610/ || $descr =~ m/MERS-8610/ ) { + push @Variables, "ppPortName"; + } + + foreach my $var (@Variables) { + debug('base',"Walking $var"); + foreach my $tuple (snmpwalk($snmphost,$v3opt, $var)){ + my($if,$value) = split /:/, $tuple, 2; + $value =~ s/[\0- ]+$//; # no trailing space + $routers->{$router}{$if}{$var} = $value; + debug('snpd'," $router -> $if -> $var = $value"); + } + } + + # interface speed var depends on snmp version + + my $snmp_version = (split(':', $router, 6))[5] || 1; + if ( $snmp_version =~ /[23]/ ) { + debug('base',"Walking ifSpeed"); + my @ifSpeed = snmpwalk($snmphost, $v3opt,'ifSpeed'); + debug('snpd',"\@ifSpeed = @ifSpeed\n"); + debug('base',"Walking ifHighSpeed"); + my @ifHighSpeed = snmpwalk($snmphost,$v3opt, 'ifHighSpeed'); + for ( my $i=0; $i<=$#ifSpeed; $i++ ) { + my ($if,$value) = split /:/, $ifSpeed[$i], 2; +# the mib entry on ifSpeed says +# "An estimate of the interface's current bandwidth in bits +# per second. For interfaces which do not vary in bandwidth +# or for those where no accurate estimation can be made, this +# object should contain the nominal bandwidth. If the +# bandwidth of the interface is greater than the maximum value +# reportable by this object then this object should report its +# maximum value (4,294,967,295) and ifHighSpeed must be used +# to report the interace's speed. For a sub-layer which has +# no concept of bandwidth, this object should be zero." + if ( (not defined $value) || ($value == 2**32-1) ) { + ($if, $value) = split /:/, $ifHighSpeed[$i], 2; + $value = $value * 1000000; # highSpeed = contador * 10^6 + debug('base',"Speed: $if - $value"); + } + if ( ($descr =~ m/MERS-8610/ ) && (defined $ifHighSpeed[$i]) ) { + ($if, $value) = split /:/, $ifHighSpeed[$i], 2; + $value = $value * 1000000; # highSpeed = contador * 10^6 + debug('base',"Speed: $if - $value"); + + } + $routers->{$router}{$if}{'ifSpeed'} = $value; + } + } else { + debug('base',"Walking ifSpeed"); + foreach my $tuple (snmpwalk($snmphost,$v3opt, 'ifSpeed')){ + my($if,$value) = split /:/, $tuple, 2; + $routers->{$router}{$if}{'ifSpeed'} = $value; + debug('snpd'," $router -> $if -> ifSpeed = $value"); + } + } + + # magic speed determination for portmaster IFs + + if ($routers->{$router}{deviceinfo}{Vendor} eq 'portmaster') { + # We can only approximate speeds + # + # so we think that ppp can do 76800 bit/s, and slip 38400. + # (actualy, slip is a bit faster, but usualy users with newer modems + # use ppp). Alternatively, you can set async speed to 115200 or + # 230400 (the maximum speed supported by portmaster). + # + # But if the interface is ptpW (sync), max speed is 128000 + # change it to your needs. On various Portmasters there are + # various numbers of sync interfaces, so modify it. + # + # The most commonly used PM-2ER has only one sync. + # + # Paul Makeev (mac@redline.ru) + # + foreach my $if (keys %{$routers->{$router}}) { + next unless $if =~ /^\d+$/; + my $ift = $routers->{$router}{$if}{ifType}; + my $ifd = $routers->{$router}{$if}{Descr}; + if ($ift == 23) { + if ($ifd eq 'ptpW1') { + $routers->{$router}{$if}{ifSpeed} = 128000; + } else { + $routers->{$router}{$if}{ifSpeed} = 76800; + } + } elsif ($ift == 28) { + $routers->{$router}{$if}{ifSpeed} = 38400; + } elsif ($ift == 6) { + $routers->{$router}{$if}{ifSpeed} = 10000000; + } + } + } + + # match confcache info into tree + my $cachekey = cleanhostkey $router; + + foreach my $method (keys %{$$confcache{$cachekey}}) { + foreach my $key (keys %{$$confcache{$cachekey}{$method}}) { + my $if = readfromcache($confcache,$router,$method,$key); + next unless $if =~ /^\d+$/; + $routers->{$router}{$if}{$method} = $key; + for ($method) { + #fix special chars in ifdescr + # no need for this we fix if references later on + # /^Descr|Name$/ && do { + # $routers->{$router}{$if}{"R$method"} = $routers->{$router}{$if}{$method}; + # $routers->{$router}{$if}{$method} =~ s/(:)/\\$1/g; + # next; + # }; + + #find hostname of IF + !$$opt{noreversedns} && /^Ip$/ and do { + my $name = + gethostbyaddr( + pack('C4', + split(/\./, + $routers->{$router}{$if}{$method})), + AF_INET); + $routers->{$router}{$if}{DNSName} = ($name or ""); + next; + }; + } + } + } +} # end InterfaceInfo + +sub GenConf ($$$$) { + my $opt = shift; + my $routers = shift; + my $confcache = shift; + my $v3opt = shift; + my $conf = "# Created by \n# $$opt{fullcmd}\n\n"; + + # print global options + $conf .= < ($$routers{$b}{noofrouter})} + keys %$routers ) { + my $router_ref = $$routers{$router}; + my $router_opt = $$router_ref{opt}; + my $router_dev = $$router_ref{deviceinfo}; + + # Did any global options appear on the command line + # before this router? If so, include them into the + # configuration file. + if (defined $$router_opt{global}) { + foreach my $key (@{$$router_opt{global}}) { + $conf .= "$key\n"; + } + } + + # If IPv6 is enabled, add IPv4Only target directive for targets + # that do not support SNMP over IPv6. + my $ipv4only_directive; + my $router_ipv4only = ($$opt{'enable-ipv6'} == 1) && $$router_ref{ipv4only}; + + my $syscontact = $$router_dev{sysContact}; + my $html_syscontact = html_escape($syscontact); + my $syslocation = $$router_dev{sysLocation}; + my $html_syslocation = html_escape($syslocation); + my $sysname = $$router_dev{sysName}; + my $sysdescr = $$router_dev{sysDescr}; + my $comment_sysdescr = $sysdescr; + # make sure embeded newlines do not harm us here + $comment_sysdescr =~ s/[\n\r]+/\n\# /g; + my $community = $$router_ref{community}; + $community =~ s/([@ ])/\\$1/g; + my $router_connect = "$community\@$$router_ref{routername}$$router_ref{snmpopt_current}"; + my @v3options; + foreach my $v3op (keys %$v3opt) { + push @v3options, $v3op."=>'".$$v3opt{$v3op}."'"; + } + my $v3options = join(",",@v3options) if $$router_ref{'snmp-options'} =~ /(?::[^:]*){4}:3/ ; + my $html_sysdescr = html_escape($sysdescr); + my $router_name = + ($$router_ref{routername} + . (($$router_ref{'dns-domain'})?'.':'') + . $$router_ref{'dns-domain'}); + + # James Overbeck 2001/09/20 + # Moved $directory_name definition from within the interface + # foreach loop to here. In its previous location, $directory_name + # was not accessible to host templates. $directory_name is not + # changed per-interface so it might as well be here instead of + # where it was. + + my $directory_name = ""; + + if (defined $$router_opt{subdirs}) { + $directory_name = $$router_opt{subdirs}; + $directory_name =~ s/HOSTNAME/$router_name/g; + $directory_name =~ s/SNMPNAME/$$router_dev{sysName}/g; + } + + + my $target_lines = ""; + my $problem_lines = ""; + my $head_lines = " +###################################################################### +# System: $sysname +# Description: $comment_sysdescr +# Contact: $syscontact +# Location: $syslocation +###################################################################### +"; + + my $separator_lines = "\n\n"; + + # Host specific config lines generation code starts HERE + + if(defined $$router_opt{'host-template'}) { + # First test if the file exists and is readable, die if not. + die "File $$router_opt{'host-template'} didn't exist.\n" + unless (-e $$router_opt{'host-template'} + and -r $$router_opt{'host-template'}); + # Open the file (or die). + open IF_TEMPLATE, $$router_opt{'host-template'} + or die "File $$router_opt{'host-template'} couldn't be opened.\n"; + + my @template_lines = readline *IF_TEMPLATE; + close IF_TEMPLATE; + $@ = undef; + eval ('local $SIG{__DIE__};'.join("", @template_lines)); + + die "ERROR Evaluation of the contents in the file \n\n". + "$$router_opt{'host-template'}\ngave the error \n\n\"$@\"\n\nExiting cfgmaker\n" if $@; + } + + $conf .= ($head_lines + . $problem_lines + . $target_lines + . $separator_lines); + + # Host specific config lines generation code ends HERE + + + if ($$router_opt{'interfaces'}) { + foreach my $ifindex (sort {int($a) <=> int($b)} grep /^\d+$/, keys %$router_ref) { + my $i = $$router_ref{$ifindex}; + + # Now define a number of variables used for this interface. + # Some variables are just used internally by cfgmaker to + # process the interface, others are provided for usage in + # host and interface templates and for interface filters. + + my $if_index = $ifindex; + my $if_eth = $$i{Eth} || 'No Ethernet Id'; + + # does it make sense to look at the interface ? + my @prob; + my $default_ifstate = 1; # State assumed up. + my $default_iftype = 1; # iftype assumed ok. + my $if_ok = 1; # + + my $if_admin = (($$i{ifAdminStatus} || 0) == 1); + my $if_oper = (($$i{ifOperStatus} || 0) == 1); + + my $if_type = $$i{ifType} || -1; + my $if_is_ethernet = 0 < scalar(grep {$_ == $if_type;} + (6,7,26,62,69,117)); + my $if_is_isdn = (0 < scalar (grep {$_ == $if_type;} + (20,21,63,75,76,77))); + my $if_is_dialup = $if_is_isdn || + (0 < scalar (grep {$_ == $if_type;} + (23,81,82,108))); + my $if_is_atm = (0 < scalar(grep {$_ == $if_type;} + (37,49,107,105,106,114,134))); + + my $if_is_wan = 0 < scalar(grep {$_ == $if_type;} + (22,30,32,39,44,46)); + + my $if_is_lan = $if_is_ethernet || + (0 < scalar (grep {$_ == $if_type;} + (8,9,11,15,26,55,59,60,115))); + my $if_is_dsl = (0 < scalar(grep {$_ == $if_type;} + (94,95,96,97))); + my $if_is_loopback = $if_type == 24; + my $if_is_ciscovlan = + ($$router_dev{Vendor} eq 'cisco' + and $$i{Descr} =~ /^(unrouted )?[- ]?VLAN[- ]?\d*$/i); + + my $if_ip = $$i{Ip} || 'No Ip'; + my $if_snmp_descr = $$i{Descr} || 'No Description'; + $if_snmp_descr =~ s/\n$//; # no you don't want to know who does this + # ok ... dell 3524 + $if_snmp_descr =~ s/ /-/g; + my $if_type_num = $$i{ifType} || 'Unknown Type'; + $$i{ifType} ||= -1; + my $if_snmp_name = $$i{Name} || 'No Name'; + my $if_snmp_alias = $$i{ifAlias} || ''; + my $if_cisco_descr = $$i{CiscolocIfDescr} || ''; + my $if_dns_name = $$i{DNSName} || 'No DNS name'; + my $if_vlan_id = $$i{vmVlan} || 'No Vlan'; + my $if_cisco_trunk = ($$i{vlanTrunkPortDynamicStatus} || 0 == 1); + my $if_MTU = $$i{ifMtu} || 'No MTU'; + + # For Nokia IPSO, find non-ethernet interfaces with IP addresses + # and add missing MAC address and Port Speed information to + # to the LOGICAL and LOGICAL+VLAN interfaces. + if ( $$router_dev{Vendor} eq 'nokiaipsofw' ) { + if ($$i{ifType} ne "6" and + $$router_dev{sysDescr} =~ / IPSO / && + $$i{Ip} =~ /^\d+/ and + (not $$i{Eth} or + not $$i{ifSpeed} or + $$i{ifSpeed} < 10 ) + ) { + my $logical_if_name = $$i{Name}; + + # Split the LOGICAL interface name in attempt + # to match with base PHYSICAL interface detail. + my ($logical_if_HEAD, $logical_if_TAIL) = + $logical_if_name =~ /^(.*)(c\d+)$/; + + foreach my $ifindexTMP (sort {int($a) <=> int($b)} + grep /^\d+$/, keys %$router_ref) { + next unless $ifindexTMP =~ /^\d+$/; + + my $physical_if_name = $$router_ref{$ifindexTMP}; + + if ($$physical_if_name{ifType} == 6 && + $logical_if_HEAD eq $$physical_if_name{Name} ) { + $$i{Eth} ||= $$physical_if_name{Eth}; + $$i{ifSpeed} = $$physical_if_name{ifSpeed} + if ( not $$i{ifSpeed} or $$i{ifSpeed} < 10 ); + } + } + } + } + + # First investigate the state of the interface. + if (not defined $$router_opt{'no-down'}) { + if (($$i{ifAdminStatus} || 0 )== 2) { + push @prob, "it is administratively DOWN"; + $default_ifstate = 0; + } elsif (($$i{ifAdminStatus} || 0 ) == 3) { + push @prob, "it is in administrative TEST mode"; + $default_ifstate = 0; + } + + if (not defined $$router_opt{'show-op-down'}) { + if (($$i{ifOperStatus} || 0 ) == 2) { + push @prob, "it is operationally DOWN"; + $default_ifstate = 0; + } elsif (($$i{ifOperStatus} || 0 ) == 3) { + push @prob, "it is in operational TEST mode"; + $default_ifstate = 0; + } + } + } + + + # Investigate the type of the interface. + if ($$router_dev{Vendor} eq 'cisco' && $$i{ifType} == 18) { # by fwo@obsidian.co.za + push @prob, "it is a DS1 controllers"; + $default_iftype = 0; + } elsif ($$router_dev{Vendor} eq 'cisco' && $$i{ifType} == 19) { # by fwo@obsidian.co.za + push @prob, "it is a E1 controllers"; + $default_iftype = 0; + } elsif ($$i{ifType} == 24) { + push @prob, "it is a Software Loopback interface" ; + $default_iftype = 0; + } elsif ($$router_dev{Vendor} eq 'cisco' && $$i{ifType} == 30) { # by blube@floridadigital.net + push @prob, "it is a DS3 controller"; + $default_iftype = 0; + } elsif ($$router_dev{Vendor} eq 'cisco' && $$i{ifType} == 102) { # by dan.mcdonald@austinenergy.com + push @prob, "it is a Voice controller"; + $default_iftype = 0; + } elsif ($$router_dev{Vendor} eq 'cisco' && $$i{ifType} == 103) { # by dan.mcdonald@austinenergy.com + push @prob, "it is a Voice dial peer"; + $default_iftype = 0; + } elsif ($$i{ifType} == 162) { + push @prob, "it is a CEF Sub-interface"; # John Begley + } elsif ($$router_dev{Vendor} eq 'cisco' + and $$i{Descr} eq 'Null0') { + push @prob, "it is a cisco Null0 interface"; + $default_iftype = 0; + } + my $default = $default_iftype && $default_ifstate; + + # Do some futher investigation if the interface makes + # sense to collect on + + # I debated whether to insert the zero-speed check before + # or after the "will always be zero" sections below. + # I settled on before since I'll assume the user knows + # what speed the zero-speed interfaces should be better + # than the simple logic below. + if (not $$i{ifSpeed} and $$router_opt{'zero-speed'}) { + # Set all interfaces with zero speed to the value specified + # by the --zero-speed= command line option. + + # Be sure the value specified is a valid integer. + # It seems like this could be done once when + # $$router_opt is set, but I didn't see any example + # of input validation in that part of cfgmaker, + # so it gets done here, more times than are + # really necessary. ;-) + unless ($$router_opt{'zero-speed'} =~ /^\d+$/) { + die "ERROR: zero-speed specified with non-integer speed: $$router_opt{'zero-speed'}"; + } + $$i{ifSpeed} = $$router_opt{'zero-speed'}; + } + + if (not $$i{ifSpeed} and $$router_dev{Vendor} eq 'foundry' and $$i{ifType} and $$i{ifType} == 194) { + # foundry ATM subinterfaces always report 0 speed, make it 155Mbps instead. + $$i{ifSpeed} = 155000000; + } elsif (not $$i{ifSpeed} and $$router_dev{Vendor} eq 'foundry' and $$i{ifType} and $$i{ifType} == 135) { + # Foundry virtual Ethernet interfaces always report 0 speed, make it 1GB instead. + $$i{ifSpeed} = 1000000000; + } elsif (not $$i{ifSpeed} and $$router_dev{Vendor} eq 'cisco' and $$i{sysDescr} and $$i{sysDescr} =~ /FWSM-Firewall / ) { + # Cisco PIX Firewall Switch Modules have effective backplane speed of 600 Megs + $$i{ifSpeed} = 600000000; + } elsif (not $$i{ifSpeed} and $$router_dev{Vendor} eq '3com' and $$i{Descr} and $$i{Descr} =~ /RMON VLAN (\d+)/ ) { + $$i{ifSpeed} = 100000000; + $if_vlan_id = $1; + } elsif (not $$i{ifSpeed}) { + push @prob, "has no ifSpeed property"; + $$i{ifSpeed} = 0; + $if_ok = 0; + } + + my $message; + my $nohc =0; + if ($message = IsCounterBroken($ifindex, $router_ref,$v3opt)) { + # set snmpopt_current to working snmp options + if ($message eq '1') { + $nohc = 1; + } else { + push @prob, "got '$message' from interface when trying to query"; + $if_ok = 0; + } + } + + my $community = $$router_ref{community}; + $community =~ s/([@ ])/\\$1/g; + my $router_connect = "$community\@$$router_ref{routername}$$router_ref{snmpopt_current}"; + + my $v3options = join(",",@v3options) if $$router_ref{snmpopt_current} =~ /(?::[^:]*){4}:3/ ; + + # determine interface reference + my $if_ref; + if (defined $$router_opt{ifref}) { + foreach (split /,/,$$router_opt{ifref}) { + /^ip$/ && do { if($$i{Ip} ){ $if_ref = "/".$$i{Ip}; last;} next}; + /^eth$/ && do { if($$i{Eth} ){ $if_ref = "!".$$i{Eth}; last;} next}; + /^descr?$/&& do { if($$i{Descr}){ $if_ref = "\\".$$i{Descr};last;} next}; + /^name$/ && do { if($$i{Name} ){ $if_ref = "#".$$i{Name}; last;} next}; + /^type$/ && do { if($$i{Type} ){ $if_ref = "%".$$i{Type}; last;} next}; + /^nr$/ && do { $if_ref = $ifindex; last }; + die "ERROR: Invalid value for --ifref: $$router_opt{ifref} ($_)\n"; + } + if (not defined $if_ref) { + push @prob, "--ifref=$$router_opt{ifref} is not unique for this interface"; + $if_ref = $ifindex; + $if_ok = 0; + } + } else { + $if_ref = $ifindex; + } + + # generate Target name + my $trim_if_ref = $if_ref; + $trim_if_ref =~ s/[\#!\/\\:\s\@%]+/_/g; + $trim_if_ref =~ s/^_*(.+?)_*$/$1/; + my $target_name = "${router_name}_$trim_if_ref"; + my $if_title_desc = $if_ref; + $if_title_desc =~ s/^[^\d]//; + my $if_speed = int($$i{ifSpeed} / 8); + my $if_speed_str = fmi($if_speed,$$router_ref{flags}); + my $if_type_desc = IfType($$i{ifType}); + my $html_if_type_desc = html_escape($if_type_desc); + my $desc_prefix = 'Traffic Analysis for '; + + my $port_dot = $$i{Name} || 'Unknown'; + $port_dot =~ s/\//./g; + my $if_port_name = $$router_ref{$port_dot}{CiscoCatalystPortName}; + my $if_pp_port_name = $$router_ref{$ifindex}{ppPortName}; + + if (defined $$router_opt{ifdesc}) { + $desc_prefix = ''; + foreach (split /,/,$$router_opt{ifdesc}) { + /^ip$/ && do { if($$i{Ip}) { $if_title_desc = $$i{Ip}; last } next }; + /^eth$/ && do { if($$i{Eth}) { $if_title_desc = $$i{Eth}; last } next }; + /^descr?$/ && do { if($$i{Descr}){ $if_title_desc = $if_snmp_descr; last } next }; + /^alias$/ && do { if($$i{ifAlias}){ $if_title_desc = "$if_snmp_descr $if_snmp_alias $if_cisco_descr"; last } next }; + /^name$/ && do { if($$i{Name}) {$if_title_desc = "#".$$i{Name}; last } next }; + /^catname$/ && do {$if_title_desc = $if_port_name; last }; + /^ppname$/ && do {$if_title_desc = $if_pp_port_name; last}; + /^type$/ && do { if($$i{Type}) { $if_title_desc = "%".$$i{Type}; last } next }; + /^nr$/ && do {$if_title_desc = "Interface $ifindex"; last}; + /^$/ && do {$if_title_desc = $if_type_desc || $if_snmp_descr; last }; + die "ERROR: Invalid value for --ifdesc: '$$router_opt{ifdesc} ($_)'\n"; + } + } + + # Now setup a large number of variables needed for the + # generation of the configuration lines. + + $if_title_desc =~ s/\\([:@\\\/\# ])/$1/g; # unescape + $if_title_desc = $if_snmp_name if not $if_title_desc; + my $html_if_title_desc = html_escape($if_title_desc); + my $html_desc_prefix = html_escape($desc_prefix); + + my $html_if_snmp_descr = html_escape($if_snmp_descr); + my $html_if_snmp_name = html_escape($if_snmp_name); + my $html_if_snmp_alias = html_escape($if_snmp_alias); + my $html_if_cisco_descr = html_escape($if_cisco_descr); + my $if_description = "$if_snmp_descr $if_snmp_alias $if_cisco_descr"; + my $html_if_description = html_escape($if_description); + my $if_title = "$desc_prefix$if_title_desc -- $sysname"; + my $html_if_title = html_escape($if_title); + + my $head_lines = "### Interface $ifindex >> Descr: '$if_snmp_descr' |". + " Name: '$if_snmp_name' | Ip: '$if_ip' | Eth: '$if_eth' ###\n"; + + my $target_lines = ""; + my $separator_lines = "\n\n"; + + # escape the if reference + $if_ref =~ s/([& :])/\\$1/g; + my $default_target_directive = "Target[$target_name]: $if_ref:$router_connect"; + $default_target_directive .= "\nSnmpOptions[$target_name]: $v3options" if $$router_ref{snmpopt_current} =~ /(?::[^:]*){4}:3/ ; + $default_target_directive .= "\nnoHC[$target_name]: yes" if $nohc == 1; + my $if_snmp_descr_save = $if_snmp_descr; + $if_snmp_descr_save =~ s/"/'/g; + my $default_setenv_directive = "SetEnv[$target_name]: MRTG_INT_IP=\"$if_ip\" MRTG_INT_DESCR=\"$if_snmp_descr_save\""; + my $default_directory_directive = ($directory_name ? "Directory[$target_name]: $directory_name" : ""); + my $default_maxbytes_directive = "MaxBytes[$target_name]: $if_speed"; + + $ipv4only_directive = $router_ipv4only ? "IPv4Only[$target_name]: yes" : ""; + + my $default_title_directive = "Title[$target_name]: $html_desc_prefix$html_if_title_desc -- $sysname"; + my $default_pagetop_directive = + "PageTop[$target_name]:

$html_desc_prefix$html_if_title_desc -- $sysname

+
+ + + + + + + + + + + + + + + + + + + + + "; + $default_pagetop_directive .= " + + + + " if defined $if_port_name; + $default_pagetop_directive .= " + + + + " if defined $if_pp_port_name; + $default_pagetop_directive .= " + + + + "; + $default_pagetop_directive .= " + + + + " if $if_ip; + $default_pagetop_directive .= " +
System:$sysname in $html_syslocation
Maintainer:$html_syscontact
Description:$html_if_description
ifType:$html_if_type_desc ($if_type_num)
ifName:$html_if_snmp_name
Port Name:$if_port_name
Port Name:$if_pp_port_name
Max Speed:$if_speed_str
Ip:$if_ip ($if_dns_name)
+
"; + + my $default_target_lines = + ("\n" + . $default_target_directive . "\n" + . $default_setenv_directive . "\n" + . ($default_directory_directive + ? ($default_directory_directive . "\n") + : "") + . $default_maxbytes_directive . "\n" + . ($ipv4only_directive + ? ($ipv4only_directive . "\n") + : "") + . $default_title_directive . "\n" + . $default_pagetop_directive . "\n"); + + + # If --if-filter is provided, evalutat that. If it + # returns true, clear @prob. If it returns false, + # instead add a complaint to @prob. + + if (defined $$router_opt{'if-filter'}) { + $@ = undef; + if (eval('local $SIG{__DIE__};'.$$router_opt{'if-filter'})) { + @prob = (); + } else { + push @prob, "filter specified by --if-filter rejected the interface"; + $if_ok = 0; + } + die "ERROR: with if-filter $$router_opt{'if-filter'}: $@" if $@; + } + + + # issue problem report + + my $problem_lines = ""; + + if (@prob) { + $problem_lines .= "### The following interface is commented out because:\n"; + map {$problem_lines .= "### * $_\n"} @prob; + $if_ok = 0; + } + + # The target config generation code starts HERE. + + + if (defined $$router_opt{'if-template'}) { + # First test if the file exists and is readable, + # die if not. + die "File $$router_opt{'if-template'} didn't exist.\n" unless (-e $$router_opt{'if-template'} + and -r $$router_opt{'if-template'}); + # Open the file (or die). + open IF_TEMPLATE, $$router_opt{'if-template'} + or die "File $$router_opt{'if-template'} couldn't be opened.\n"; + my @template_lines = readline *IF_TEMPLATE; + + $@ = undef; + eval ('local $SIG{__DIE__};'.join( "", @template_lines)); + die "Evaluation of the contents in the file \n\n$$router_opt{'if-template'}\n". + "gave the error \n\n\"$@\"\n\nExiting cfgmaker\n" if $@; + } else { + $target_lines = $default_target_lines; + } + + + if ($target_lines && not $if_ok) { # comment out the target lines if needed + $target_lines =~ s/^/\# /gm; + } + + $conf .= ($head_lines + . $problem_lines + . $target_lines + . $separator_lines); + + } + # Target generation code ends HERE. + } + } + + # print any global options which might have + # appeared on the command line after the last + # router. + if (defined $$opt{global}) { + foreach my $key (@{$$opt{global}}) { + $conf .= "$key\n"; + } + } + + if ($$opt{output}) { + debug ('base', "Writing $$opt{output}"); + open X, ">$$opt{output}" or die "ERROR: creating $$opt{output}: $!\n"; + print X $conf; + close X; + } else { + print $conf; + } +} # end GenConf + +sub IsCounterBroken ($$$) { + my $if = shift; + my $router_ref = shift; + my $v3opt = shift; + my $router = $$router_ref{routerkey}; + my $fallback = 0; + local $SNMP_Session::suppress_warnings = 3; + local $Net_SNMP_util::suppress_warnings = 3; + + my $ipv4only = $$router_ref{ipv4only}; + my $snmphost = v4onlyifnecessary($router, $ipv4only); + + if ($router =~ /:[\d.]*:[\d.]*:[\d.]*:[23]/) { + my $speed = (snmpget($snmphost, $v3opt, 'ifHighSpeed.'.$if))[0] || 'unknown'; + debug('base',"snmpget $snmphost for ifHighSpeed.$if -> $speed Mb/s"); + $SNMP_Session::errmsg = undef; + $Net_SNMP_util::ErrorMessage = undef; + my $counter = (snmpget($snmphost,$v3opt, 'ifHCInOctets.'.$if))[0] || 'unknown'; + debug('base',"snmpget $snmphost for ifHCInOctets.$if -> $counter"); + if( $speed eq 'unknown' or $counter !~ /^\d+$/ or $SNMP_Session::errmsg or $Net_SNMP_util::ErrorMessage){ + $SNMP_Session::errmsg = undef; + $Net_SNMP_util::ErrorMessage = undef; + $fallback = 1; + debug('base',"check for HighspeedCounters failed ... Dropping back to V1"); + } else { + return 0; + } + } + if ( $fallback == 1 or $$router_ref{snmpopt_current} !~ /:[\d.]*:[\d.]*:[\d.]*:[23]/) { + my $counter = (snmpget($snmphost, 'ifInOctets.'.$if))[0]; + if (defined $SNMP_Session::errmsg) { + my $error = $SNMP_Session::errmsg; + $SNMP_Session::errmsg = undef; + $error =~ s/\n/\n### /g; + return $error; + } elsif (defined $Net_SNMP_util::ErrorMessage and $Net_SNMP_util::ErrorMessage =~ /\w/) { + my $error = $Net_SNMP_util::ErrorMessage; + $Net_SNMP_util::ErrorMessage = undef; + $error =~ s/\n/\n### /g; + return $error; + } elsif (not defined $counter or $counter eq '' or $counter =~ /\D/) { + return "No counter exists for $if"; + } + } + return $fallback; +} # end IsCounterBroken + +# DeviceInfo does fallback between IPv6 and IPv4: if an IPv6 snmpwalk returns +# undef values (= an error) and the target is a hostname, then it repeats the +# query using IPv4 in case the target does not support SNMP over IPv6. +# If DeviceInfo falls back to IPv4, it sets the ipv4only field for the target +# in the routers hash. +sub DeviceInfo ($$$) { + my $router=shift; + my $routers=shift; + my $v3opt=shift; + my %DevInfo; + my $ipv4only = $$routers{$router}{ipv4only}; + + my @variables = snmpwalk(v4onlyifnecessary($router, $ipv4only),$v3opt,'1.3.6.1.2.1.1'); # walk system + if (!(defined $variables[0])) { + # Do we need to fall back to IPv4? + my ($commmunity, $host) = ($1, $2) if ($router =~ /^(.*)@([^@]+)$/); + if ( ( ! $ipv4only ) && ( $host !~ /^\[(.*)\]/) ) { + # Not using IPv4, not an IPv6 address, so a hostname + debug ('base',"No response using IPv6 for $router, trying again using IPv4"); + $$routers{$router}{ipv4only} = 1; + @variables = snmpwalk(v4onlyifnecessary($router, 1),$v3opt, '1.3.6.1.2.1.1'); + } + } + if ( defined $variables[0] ) { + my (%DevInfo, %revOIDS); + + if ($$routers{$router}{enablesnmpv3} || '' eq "yes") { + no warnings; + %revOIDS = reverse %Net_SNMP_util::OIDS; + } + else { + no warnings; + %revOIDS = reverse %SNMP_util::OIDS; + } + foreach my $variable ( @variables ) { + my ($oid, $value) = split ( ':', $variable, 2); + if ($revOIDS{'1.3.6.1.2.1.1.'.$oid}){ + $DevInfo{ $revOIDS{'1.3.6.1.2.1.1.'.$oid} } = $value; + } + } + # vendor identification + my %vendorIDs = ( + # Add your vendor here + # sysObjectID Vendora + '1.3.6.1.4.1.11863.' => 'tplink', + '1.3.6.1.4.1.43.' => '3com', + '1.3.6.1.4.1.11.' => 'hp', + '1.3.6.1.4.1.9.' => 'cisco', + '1.3.6.1.4.1.171.' => 'dlink', + '1.3.6.1.4.1.6141.' => 'wwp', + '1.3.6.1.4.1.674.10895.' => 'dellLan', + '1.3.6.1.4.1.1916.' => 'extremenetworks', + '1.3.6.1.4.1.1991.' => 'foundry', + '1.3.6.1.4.1.6027.' => 'force10', + '1.3.6.1.4.1.2636.' => 'juniper', + '1.3.6.1.4.1.94.' => 'nokiaipsofw', + '1.3.6.1.4.1.307.' => 'portmaster', + '1.3.6.1.4.1.890.' => 'zyxel', + '1.3.6.1.4.1.2272.30' => 'nortel', + '1.3.6.1.4.1.6339' => 'DCN', + '1.3.6.1.4.1.30155.' => 'openBSD', + '1.3.6.1.4.1.30065.' => 'arista', + '1.3.6.1.4.1.5624.' => 'enterasys', + '1.3.6.1.4.1.30803.' => 'Vyatta', + '1.3.6.1.4.1.3955.' => 'LinkSys', + '1.3.6.1.4.1.1588.' => 'brocade' + ); + $DevInfo{Vendor} = 'Unknown Vendor - '.$DevInfo{sysObjectID}; + foreach (keys %vendorIDs) { + $DevInfo{Vendor} = $vendorIDs{$_} if ($DevInfo{sysObjectID} =~ /\Q$_\E/); + } + debug('base',"Vendor Id: $DevInfo{Vendor}"); + return \%DevInfo; + } else { + # we just die because the snmp module has already complained + return undef; + } +} # end DeviceInfo + + +sub fmi ($$) { + my $number = shift; + my $flags = shift; + my(@short); + if ($$flags{bits}){ + $number*=8; + @short = ("bits/s","kbits/s","Mbits/s","Gbits/s"); + } else { + @short = ("Bytes/s","kBytes/s","MBytes/s","GBytes/s"); + } + my $digits=length("".$number); + my $divm=0; + while ($digits-$divm*3 > 4) { $divm++; } + my $divnum = $number/10**($divm*3); + return sprintf("%1.1f %s",$divnum,$short[$divm]); +} # end fmi + + +sub IfType ($) { + my $id = shift; + my $ift = { + '1'=>'Other', + '2'=>'regular1822', + '3'=>'hdh1822', + '4'=>'ddnX25', + '5'=>'rfc877x25', + '6'=>'ethernetCsmacd', + '7'=>'iso88023Csmacd', + '8'=>'iso88024TokenBus', + '9'=>'iso88025TokenRing', + '10'=>'iso88026Man', + '11'=>'starLan', + '12'=>'proteon10Mbit', + '13'=>'proteon80Mbit', + '14'=>'hyperchannel', + '15'=>'fddi', + '16'=>'lapb', + '17'=>'sdlc', + '18'=>'ds1', + '19'=>'e1', + '20'=>'basicISDN', + '21'=>'primaryISDN', + '22'=>'propPointToPointSerial', + '23'=>'ppp', + '24'=>'softwareLoopback', + '25'=>'eon', + '26'=>'ethernet-3Mbit', + '27'=>'nsip', + '28'=>'slip', + '29'=>'ultra', + '30'=>'ds3', + '31'=>'sip', + '32'=>'frame-relay', + '33'=>'rs232', + '34'=>'para', + '35'=>'arcnet', + '36'=>'arcnetPlus', + '37'=>'atm', + '38'=>'miox25', + '39'=>'sonet', + '40'=>'x25ple', + '41'=>'iso88022llc', + '42'=>'localTalk', + '43'=>'smdsDxi', + '44'=>'frameRelayService', + '45'=>'v35', + '46'=>'hssi', + '47'=>'hippi', + '48'=>'modem', + '49'=>'aal5', + '50'=>'sonetPath', + '51'=>'sonetVT', + '52'=>'smdsIcip', + '53'=>'propVirtual', + '54'=>'propMultiplexor', + '55'=>'100BaseVG', + '56'=>'Fibre Channel', + '57'=>'HIPPI Interface', + '58'=>'Obsolete for FrameRelay', + '59'=>'ATM Emulation of 802.3 LAN', + '60'=>'ATM Emulation of 802.5 LAN', + '61'=>'ATM Emulation of a Circuit', + '62'=>'FastEthernet (100BaseT)', + '63'=>'ISDN & X.25', + '64'=>'CCITT V.11/X.21', + '65'=>'CCITT V.36', + '66'=>'CCITT G703 at 64Kbps', + '67'=>'Obsolete G702 see DS1-MIB', + '68'=>'SNA QLLC', + '69'=>'Full Duplex Fast Ethernet (100BaseFX)', + '70'=>'Channel', + '71'=>'Radio Spread Spectrum (802.11)', + '72'=>'IBM System 360/370 OEMI Channel', + '73'=>'IBM Enterprise Systems Connection', + '74'=>'Data Link Switching', + '75'=>'ISDN S/T Interface', + '76'=>'ISDN U Interface', + '77'=>'Link Access Protocol D (LAPD)', + '78'=>'IP Switching Opjects', + '79'=>'Remote Source Route Bridging', + '80'=>'ATM Logical Port', + '81'=>'AT&T DS0 Point (64 Kbps)', + '82'=>'AT&T Group of DS0 on a single DS1', + '83'=>'BiSync Protocol (BSC)', + '84'=>'Asynchronous Protocol', + '85'=>'Combat Net Radio', + '86'=>'ISO 802.5r DTR', + '87'=>'Ext Pos Loc Report Sys', + '88'=>'Apple Talk Remote Access Protocol', + '89'=>'Proprietary Connectionless Protocol', + '90'=>'CCITT-ITU X.29 PAD Protocol', + '91'=>'CCITT-ITU X.3 PAD Facility', + '92'=>'MultiProtocol Connection over Frame/Relay', + '93'=>'CCITT-ITU X213', + '94'=>'Asymetric Digitial Subscriber Loop (ADSL)', + '95'=>'Rate-Adapt Digital Subscriber Loop (RDSL)', + '96'=>'Symetric Digitial Subscriber Loop (SDSL)', + '97'=>'Very High Speed Digitial Subscriber Loop (HDSL)', + '98'=>'ISO 802.5 CRFP', + '99'=>'Myricom Myrinet', + '100'=>'Voice recEive and transMit (voiceEM)', + '101'=>'Voice Foreign eXchange Office (voiceFXO)', + '102'=>'Voice Foreign eXchange Station (voiceFXS)', + '103'=>'Voice Encapulation', + '104'=>'Voice Over IP Encapulation', + '105'=>'ATM DXI', + '106'=>'ATM FUNI', + '107'=>'ATM IMA', + '108'=>'PPP Multilink Bundle', + '109'=>'IBM IP over CDLC', + '110'=>'IBM Common Link Access to Workstation', + '111'=>'IBM Stack to Stack', + '112'=>'IBM Virtual IP Address (VIPA)', + '113'=>'IBM Multi-Protocol Channel Support', + '114'=>'IBM IP over ATM', + '115'=>'ISO 802.5j Fiber Token Ring', + '116'=>'IBM Twinaxial Data Link Control (TDLC)', + '117'=>'Gigabit Ethernet', + '118'=>'Higher Data Link Control (HDLC)', + '119'=>'Link Access Protocol F (LAPF)', + '120'=>'CCITT V.37', + '121'=>'CCITT X.25 Multi-Link Protocol', + '122'=>'CCITT X.25 Hunt Group', + '123'=>'Transp HDLC', + '124'=>'Interleave Channel', + '125'=>'Fast Channel', + '126'=>'IP (for APPN HPR in IP Networks)', + '127'=>'CATV MAC Layer', + '128'=>'CATV Downstream Interface', + '129'=>'CATV Upstream Interface', + '130'=>'Avalon Parallel Processor', + '131'=>'Encapsulation Interface', + '132'=>'Coffee Pot', + '133'=>'Circuit Emulation Service', + '134'=>'ATM Sub Interface', + '135'=>'Layer 2 Virtual LAN using 802.1Q', + '136'=>'Layer 3 Virtual LAN using IP', + '137'=>'Layer 3 Virtual LAN using IPX', + '138'=>'IP Over Power Lines', + '139'=>'Multi-Media Mail over IP', + '140'=>'Dynamic synchronous Transfer Mode (DTM)', + '141'=>'Data Communications Network', + '142'=>'IP Forwarding Interface', + '144'=>'IEEE1394 High Performance Serial Bus', + '150'=>'MPLS Tunnel Virtual Interface', + '161'=>'IEEE 802.3ad Link Aggregate', + '162'=>'Cisco Express Forwarding Interface', + '166'=>'MPLS', + '238'=>'Asymmetric Digital Subscriber Loop', + '246'=>'Pseudowire interface type', + '251'=>'Very high speed digital subscriber line', + }; + return ($ift->{$id} || "Uknown Interface Type"); +} # end IfType + + + + + +sub options ($$) { + my $opt = shift; + my $routers = shift; + + my $noofrouter = 0; # How many routers we've seen on cmdline. + + # The $flags hash stores what we've seen in Options[_], + # Options[^] and Options[$] so far. + # A cmdline arg like --global 'Options[_]: bits' will insert + # the element $$flags{default}{bits}="set". + # Similarly --global 'Options[$]:' will delete all elements + # in $$flags{append} + # + # This was originally created to manipulate the "bits" flag + # so fmi should know when to use "bits" or "bytes". It might + # be overkill to use such a comples solution but it makes life + # easier if cfgmaker in the future has to be extended to be + # aware of other Options[] settings like gauge, growright etc. + + my %flags; + { + my $def = {}; + my $pre = {}; + my $app = {}; + %flags = (default => $def, + prepend => $pre, + append => $app); + } + + my $addrouter_ornf = addrouter($opt, + $routers, + \$noofrouter, + \%flags); + + Getopt::Long::Configure("permute"); + GetOptions( $opt, + 'help|?', + 'man', + 'subdirs=s', + 'no-down', + 'show-op-down', + 'noreversedns', + 'ifref=s', + 'ifdesc=s', + 'if-filter=s', + 'if-template=s', + 'interfaces!', + 'host-template=s', + 'community=s', + 'username=s', + 'authkey=s', + 'authpassword=s', + 'authprotocol=s', + 'contextengineid=s', + 'contextname=s', + 'privkey=s', + 'privpassword=s', + 'privprotocol=s', + 'snmp-options=s', + 'dns-domain=s', + 'version', + 'output=s', + 'global=s@', + 'enable-ipv6', + 'enablesnmpv3', + 'use-16bit', + 'zero-speed=s', + '<>', $addrouter_ornf) or pod2usage(2); + + die("cfgmaker for mrtg-2.17.7\n") if $$opt{version}; + pod2usage(-exitval => 0, -verbose => 2) if $$opt{man}; + pod2usage(-verbose => 1) if not keys %$routers; +} # end options + +# The callback routine used by GetOptions to process "non-option +# strings" (routers) among the arguments is given only ONE argument. +# However, I want it to be able to specify both the %options hash +# (for read access) and the %routers hash (for modifying) as well +# as the router's name. This makes for three arguments. +# +# The solution is to use a closure. addrouter takes a opt hash, a +# routers hash, an index to the current number of routers and a flags +# hash and then returns a function which "remembers" these +# values (the closure) but also takes an argument (the router name). + +sub addrouter() { + my $opt = shift; + my $routers = shift; + my $noofrouter = shift; + my $flags = shift; + + return sub { + my $rawname = shift; + + $$noofrouter++; # First increase the number of routers seen. + + my ($community,$routername,$routerkey,$snmpopt,$dnsdomain,$tmpname,@tmpsnmp); + + # Now make sure that the router is defined with the + # proper community, domainname and SNMP options. + # Dissect the rawname to find out what it contains. + + # First check for community: + if ($rawname =~ /^(.+)\@([^@]+)$/) { + # Community was given explicitly! + $community = $1; + $tmpname = $2 + } else { + $community = $$opt{community}; + $tmpname = $rawname; + } + # Now separate the router name from the options. We + # can't just split on the : character because a numeric + # IPv6 address contains a variable number of :'s + if( ($tmpname =~ /^(\[.*\]):(.*)$/) || ($tmpname =~ /^(\[.*\])$/) ){ + # Numeric IPv6 address between [] + ($routername, $snmpopt) = ($1, $2); + } else { + # Hostname or numeric IPv4 address + ($routername, $snmpopt) = split(':', $tmpname, 2); + } + @tmpsnmp = split(':', $snmpopt || ''); + + $routername =~ s/\.$//; # if the hostname ends in a '.' remove it + # it seems to cause trouble in some other + # parts of mrtg + + # Now setup the SNMP options. + if (not defined $$opt{'snmp-options'}) { + $snmpopt = ':' . (join ':', @tmpsnmp); # No merge needed. + } else { + my ($t,$o,@s); + my @optsnmp = split ':',$$opt{'snmp-options'}; + + # Trim first element as the SNMP options start + # with a colon and thus the first element is a + # dummy "" string not corresponding to any SNMP option + # (or rather, corresponding to a router, if there had + # been one...) + shift @optsnmp; + + while ((scalar @tmpsnmp > 0) + or (scalar @optsnmp > 0)) { + $t = shift @tmpsnmp; + $o = shift @optsnmp; + + if(not defined $t) {$t = "";} + if(not defined $o) {$o = "";} + + if($t ne "") + { + push @s, $t; + } else { + push @s, $o; + } + } + + $snmpopt = ':' . (join ':', @s); + } + + my $newopt={}; # Perhaps unecessary initialization but... + + foreach my $o (keys %$opt) { + my $ovalue = $$opt{$o}; + + $$newopt{$o} = $ovalue + unless + ($o =~ /^fullcmd$/ or + $o =~ /^community$/ or + $o =~ /^snmp-options$/ or + $o =~ /^global$/ or + $o =~ /^output$/ + ); + + # Ok, copy the --globals array from $$opt so we know + # that which global(s) to print out into the config. + push @{$$newopt{$o}}, @{$$opt{$o}} if ($o =~ /^global$/); + + # Go through these --global statements one by one. + # If anyone of them contains Options[] for any of the + # targets [_], [^] or [_], process those statements + # tenderly and populate the $$flags{}{} hashes accordingly. + for my $g (@{$$opt{"global"}}) { + if ($g =~ /^options\[([_^\$])\]:\s*(.*)$/i){ + my ($t,$fs); + $t = $1; + $fs = $2; + $t =~ s/_/default/; + $t =~ s/\^/prepend/; + $t =~ s/\$/append/; + + # If a line like "options[X]:" is found clear + # all flags for that category and then go to next + # --global 'Options[..' line if any. + if ($fs =~ /^\s*$/) { + $$flags{$t} = {}; + next; + } else { + for my $f (split /\s*,\s*/,$fs) { + $$flags{$t}{$f} = "set"; + } + } + } + } + $$opt{$o} = [] if ($o =~ /^global$/); + } + + # Now let this router get it's own copy of + # the "currently effective" flags. + # Note, Options[_] should only be considered + # if Options[^] and Options[$] both are absent. + + my $newflags = {}; + + if((0 == keys %{$$flags{prepend}}) + and (0== keys %{$$flags{append}})) { + for my $f (keys %{$$flags{default}}) { + $$newflags{$f}="set"; + } + } else { + for my $f (keys %{$$flags{prepend}}, + keys %{$$flags{append}}) { + $$newflags{$f}="set"; + } + } + + if(defined $$opt{'dns-domain'}) { + $dnsdomain=$$opt{'dns-domain'}; + } else { + $dnsdomain=""; + } + + $routerkey = + "${community}\@${routername}" + . (($dnsdomain eq "")?"":".") + . "${dnsdomain}${snmpopt}"; + + $$routers{$routerkey}= { + # rawname is the unprocessed string from the + # command line. + rawname => $rawname, + + # opt is the commandline options which are + # in effect for THIS particular router. + opt => $newopt, + + # noofrouter is the unique number for the + # router. The first router on the command + # line is given number 1, the second number 2 + # and so on. + noofrouter => $$noofrouter, + + # flags contains which --global 'Options[^_$]: flags' + # are effective for THIS particular router. + flags => $newflags, + + # community is the SNMP community used for the router + community => $community, + + # snmpopt is the SNMP options on the form + # [port[:timeout[:retries[:backoff[:version]]]]] + # The empty string simply means that no + # specific SNMP options has been given. + 'snmp-options' => $snmpopt, + snmpopt_current => $snmpopt, + + # dns-domain is a domain which should be added + # to the routers hostname. + # e.g if dns-domain is place.xyz and host is router + # the host "router.place.xyz" will be polled. + # If host is "router.dept" the poll will be against + # "router.dept.place.xyz". + 'dns-domain' => $dnsdomain, + + # routername is the routers name as given on the + # command line but with SNMP community (if given) + # and SNMP options (if given) stripped. + # + # (Yes, routername COULD be on the form + # "host.domain" or "host.subdomain.domain") + routername => $routername, + + # routerkey is the same as the has key used for the + # router, which is the router name with everything + # taken into account: community, dns-domain and + # snmp-options. This is the value used when doing + # SNMP communication with the router. + routerkey => $routerkey, + }; + } +} # end addrouter + +sub html_escape ($) { + my $s = shift || ''; + $s =~ s/&/&/g; + $s =~ s//>/g; + $s =~ s/[\n\r]+([^\n\r])/
\n $1/g; + return $s; +} # end html_escape + +sub oid_pick ($$$;@){ + my $router = shift; + my $v3opt = shift; + my @oids = @_; + local $SNMP_Session::suppress_warnings = 3; + foreach my $oid (@oids){ + local $SNMP_Session::errmsg = undef; + my $counter = snmpget($router,$v3opt,$oid); + if (not defined $SNMP_Session::errmsg and defined $counter and $counter ne '' ) { + debug('base',"oid_pick - found $oid to work for $router"); + return $oid; + } + } + debug('base',"oid_pick - none of ".(join ",",@oids)." seem to work for $router"); + return undef; +} + + +sub parsev3 ($) { + my $opt = shift; + my %v3opt; + if (!exists ($$opt{username})) { + die "SMNP V3 requires a --username paramter as part of the User Security Model"; + } else { + $v3opt{username} = $$opt{username}; + } + $v3opt{contextname} = $$opt{contextname} if exists($$opt{contextname}); + if (exists ($$opt{authkey})) { + die "Can't use both an --authkey and --authpassword in the User Security Model" if exists($$opt{authpassword}); + $v3opt{authkey} = $$opt{authkey}; + } + if (exists ($$opt{authpassword})) { + die "Use of --authpassword requires --contextengineid" if !exists($$opt{contextengineid}); + $v3opt{authpassword} = $$opt{authpassword}; + } + if (exists ($$opt{authprotocol})) { + die "Only sha and md5 are defined for --authprotocol" if $$opt{authprotocol} !~ /^(md5|sha)$/i; + die "--authprotocol can only be used with --authpassword or --authkey" if ! exists($$opt{authpassword}) and ! exists($$opt{authkey}); + ($v3opt{authprotocol}) = (lc($$opt{authprotocol}) =~ /^(md5|sha)$/); + } + if (exists ($$opt{privkey})) { + die "Can't use both an --privkey and --privpassword in the User Security Model" if exists($$opt{privpassword}); + die "Can't have privacy parameters without authentication in the User security Model" if ! exists($$opt{authpassword}) and ! exists($$opt{authkey}); + $v3opt{privkey} = $$opt{privkey}; + } + if (exists ($$opt{privpassword})) { + die "Use of --privpassword requires --contextengineid" if !exists($$opt{contextengineid}); + die "Can't have privacy parameters without authentication in the User security Model" if ! exists($$opt{authpassword}) and ! exists($$opt{authkey}); + $v3opt{privpassword} = $$opt{privpassword}; + } + if (exists ($$opt{privprotocol})) { + die "Only des, 3des, 3desede, aes, aes128 are defined for --privprotocol" if $$opt{privprotocol} !~ /^(?:3?des(?:ede)?|aes(?:128)?)$/; + die "--privprotocol can only be used with --privpassword or --privkey" if ! exists($$opt{privpassword}) and ! exists($$opt{privkey}); + $v3opt{privprotocol} = lc($$opt{privprotocol}); + } + return %v3opt; +} + + +sub init () { + snmpmapOID('sysObjectID' => '1.3.6.1.2.1.1.2.0', + 'CiscolocIfDescr' => '1.3.6.1.4.1.9.2.2.1.1.28', + 'CiscoCatalystPortName' => '1.3.6.1.4.1.9.5.1.4.1.1.4', + 'ppPortName' => '1.3.6.1.4.1.2272.1.4.10.1.1.35', + 'vlanTrunkPortDynamicStatus'=> '1.3.6.1.4.1.9.9.46.1.6.1.1.14', + 'vmVlan' => '1.3.6.1.4.1.9.9.68.1.2.2.1.2', + 'ifAlias' => '1.3.6.1.2.1.31.1.1.1.18'); +} # end init + +__END__ + +=pod + +=head1 NAME + +cfgmaker - Creates mrtg.cfg files (for mrtg-2.17.7) + +=head1 SYNOPSIS + +cfgmaker [options] [community@]router [[options] [community@]router ...] + +=head1 OPTIONS + + --ifref=nr interface references by Interface Number (default) + --ifref=ip ... by Ip Address + --ifref=eth ... by Ethernet Number + --ifref=descr ... by Interface Description + --ifref=name ... by Interface Name + --ifref=type ... by Interface Type + You may also use multiple options separated by commas, + in which case the first available one is used: + e.g. --ifref=ip,name,nr + + --ifdesc=nr interface description uses Interface Number (default) + --ifdesc=ip ... uses Ip Address + --ifdesc=eth ... uses Ethernet Number + --ifdesc=descr ... uses Interface Description + --ifdesc=name ... uses Interface Name + --ifdesc=catname ... uses CatOS Interface Name + --ifdesc=ppname ... uses Passport Port Name + --ifdesc=alias ... uses Interface Alias + --ifdesc=type ... uses Interface Type + You may also use multiple options separated by commas, + in which case the first available one is used: + e.g. --ifdesc=catname,ppname,descr,alias,ip,name,nr + + --if-filter=f Test every interface against filter f to decide wether + or not to include that interface into the collection. + Currently f is being evaluated as a Perl expression + and it's truth value is used to reject or accept the + interface. + (Experimental, under development, might change) + + --if-template=templatefile + Replace the normal target entries for the interfaces + with an entry as specified by the contents in the file + templatefile. The file is supposed to contain Perl + code to be executed to generate the lines for the + target in the configuration file. + (Experimental, under development, might change) + + --host-template=templatefile + In addition to creating targets for a host's interfaces + do also create targets for the host itself as specified + by the contents in the file templatefile. The file is + supposed to contain Perl code to be executed to generate + the lines for the host related targets (such as CPU, + ping response time measurements etc.) in the config- + uration file. + (Experimental, under development, might change) + + --global "x: a" add global config entries + + --no-down do not look at admin or opr status of interfaces + + --show-op-down show interfaces which are operatively down + + --zero-speed=spd use this speed in bits-per-second as the interface + speed for all interfaces that return a speed of 0 + via ifSpeed/ifHighSpeed. 100Mbps = 100000000 + + --subdirs=format give each router its own subdirectory, naming each per + "format", in which HOSTNAME and SNMPNAME will be + replaced by the values of those items -- for instance, + --subdirs=HOSTNAME or --subdirs="HOSTNAME (SNMPNAME)" + + --noreversedns do not reverse lookup ip numbers + + --community=cmty Set the default community string to "cmty" instead of + "public". + + --enable-ipv6 Enable IPv6 support, if the required libraries are + present. Numeric IPv6 addresses must be enclosed + in square brackets, e.g. public@[2001:760:4::1]:161 + + --use-16bit Use 16bit SNMP request IDs to query all routers. + + --snmp-options=:[][:[][:[][:[][:]]]] + + Specify default SNMP options to be appended to all + routers following. Individual fields can be empty. + Routers following might override some or all of the + options given to --snmp-options. + + --dns-domain=domain + Specifies a domain to append to the name of all + routers following. + + --nointerfaces Don't do generate any configuration lines for interfaces, + skip the step of gathering interface information and + don't run any interface template code. + + --interfaces Generate configuration lines for interfaces (this is the + default). The main purpose of this option is to negate + an --nointerfaces appearing earlier on the command line. + + --help brief help message + --man full documentation + --version print the version of cfgmaker + + --output=file output filename default is STDOUT + +=head1 DESCRIPTION + +B creates MRTG configuration files based on information +pulled from a router or another SNMP manageable device. + +[IB<@>]I + +I is the community name of the device you want to create a +configuration for. If not specified, it defaults to 'B'; you might +want to try this first if you do not know the community name of a +device. If you are using the wrong community name you will get no +response from the device. + +I is the DNS name or the IP number of an SNMP-managable device. +Following the name you can specify 6 further options separated by +colons. The full syntax looks like this: + +B[:[B][:[B][:[B][:[B][:B]]]]] + +Of special interest may be the last parameter, B. If you set this to +'2' then your device will be queried with SNMP version 2 requests. This +allows you to poll the 64 bit traffic counters in the device and will thus work +much better with fast interfaces (no more counter overrun). Note that the +order in which the routers are specified on the command line do matter as +the same order is used when the configuration file is generated. The first +specified router has it's configuration lines genrated first, followed by +the lines belonging to the next router and so on. + +Note that the first line of the generated cfg file will contain all the +commandline options you used for generating it. This is to allow for the +easy 'regeneration' in case you want to add newhosts or make some other +global change. + +=head2 Configuration + +Except for the B<--output> and B<--global> options, all options affect +only the routers following them on the command line. If an option +specified earlier on the command line reappears later on the command +line with another value, the new value overrides the old value as far as +remaining routers are concerned. This way options might be tailored for +groups of routers or for individual routers. + +See B<--output> and B<--global> for how their behaviour is affected by +where or how many times they appear on the command line. + +See the B below on how to set an option differently for +multiple routers. + +=over + +=item B<--help> + +Print a brief help message and exit. + +=item B<--man> + +Prints the manual page and exits. + +=item B<--version> + +Print the version of cfgmaker. This should match the version of MRTG +for which config files are being created. + +=item B<--ifref> B|B|B|B|B + +Select the interface identification method. Default is B which +identifies the router interfaces by their number. Unfortunately the +interface numbering scheme in an SNMP tree can change. Some routers +change their numbering when new interfaces are added, others change +thier numbering every full moon just for fun. + +To work around this sad problem MRTG can identify interfaces by 4 +other properties. None of these works for all interfaces, but you +should be able to find one which does fine for you. Note that +especially ethernet addrsses can be problematic as some routers have +the same ethernet address on most of their interface cards. + +Select B to identify the interface by its IP number. Use B to +use the ethernet address for identification. Use B to use +the Interface description. Or use B to use the Interface name. + +You can specify multiple properties if you wish, separated by commas. +In this case, cfgmaker will use the first item in the list which +can provide unique identification. This allows you to specify, for +example, to use IP address and to use ifName if this is not defined: + --ifref ip,name + +If your chosen method does not allow unique interface identification on +the device you are querying, B will tell you about it. + +=item B<--ifdesc> B|B|B|B|B|B|B + +Select what to use as the description of the interface. The description +appears in the C property for the target as well as the text header +in the HTML code defined in the target's C. Default is to use +B which is just the interface number which isn't always useful +to the viewer of the graphs. + +There are 6 other properties which could be used. Use B if you want +to use the interface's IP-address. Use B if you want to use the +interface's ethernet address. If you want a better description, you can +use either B, B or B. Exactly what each of these do +varies between different equipment so you might need to experiment. For +instance, for a serial interface on a Cisco router running IOS using B +might result in C<"S0"> being the interface description , B might result +in C<"Serial0"> and B might result in C<"Link to HQ"> (provided that is +what is used as the interface's C in the router's configuration). + +Finally, if you want to describe the interface by it's Btype +(i.e C<"ethernetCSMA">, C<"propPointtoPoint"> etc) you can use B. + +You can specify multiple properties if you wish, separated by commas. +In this case, cfgmaker will use the first item in the list which +is available for this interface. This allows you to specify, for +example, to use any of the different aliases in order of preference. + +=item B<--if-filter> 'B' + +First of all, this is under some development and is experimental. + +Use this if you want to have better control over what interfaces gets +included into the configuration. The B is evaluated +as a piece of Perl code and is expected +to return a truth value. If true, include the interface and if false, +exclude the interface. + +For a further discussion on how these filters work, see the section +L
below. + +=item B<--if-template> B + +First of all, this is under some development and is experimental. + +Use this if you want to control what the line for each target should look +like in the configuration file. The contents of the file B +will be evaluated as a Perl program which generates the lines using certain +variables for input and output. + +For a further discussion on how these templates work, see the section +L
below. + +=item B<--host-template> B + +First of all, this is under some development and is experimental. + +Use this if you want to have some extra targets related to the host itself +such as CPU utilization, ping response time to the host, number of busy +modems etc. The contents of the file B will be evaluated +once per host as a Perl program which generates the lines using certain +variables for input and output. + +For a further discussion on how these templates work, see the section +L
below. + +=item B<--community> B + +Use this to set the community for the routers following on the command +line to B. Individual routers might overrride this +community string by using the syntax BB<@>B. + +=item B<--enable-ipv6> + +This option enables IPv6 support. It requires the appropriate perl +modules; if they are not found then IPv6 is disabled (see the ipv6 +documentation). + +cfgmaker will use IPv6 or IPv4 depending on the target. If the target +is a numeric address, the protocol depends on the type of address. If the +target is a hostname, cfgmaker will try to resolve the name first to an +IPv6 address then to an IPv4 address. + +IPv6 numeric addresses must be specified between square braces. + +For example: + + cfgmaker --enable-ipv6 [2001:760:4::1]:165:::2 + +If the target has both an IPv6 address and an IPv4 address with the same +hostname, cfgmaker first queries the target using IPv6 and falls back to +IPv4 if it fails. This is useful for targets which don't support SNMP +over IPv6. + +=item B<--use-16bit> + +This option forces the use of 16bit SNMP request IDs. Some broken SNMP +agents do not accept 32bit request IDs. Try to avoid this option as much +as possible, complain to your agent vendor instead. + +=item B<--snmp-options> :[B][:[B][:[B][:[B][:B]]]] + +Use this to set the default SNMP options for all routers following on the +command line. Individual values might be omitted as well as trailing +colons. Note that routers might override individual (or all) values +specified by B<--snmp-options> by using the syntax + +B[:[B][:[B][:[B][:[B][:B]]]]] + +=item B<--global> B<">IB<"> + +Use this to add global options to the generated config file. +You can call B<--global> several times to add multiple options. +The line will appear in the configuration just before the config for +the next router appearing on the command line. + + --global "workdir: /home/mrtg" + +If you want some default Options you might want to put + + --global "options[_]: growright,bits" + +Specifying B<--global> after the last router on the command line will +create a line in the configuration file which will appear after all the +routers. + +=item B<--noreversedns> + +Do not try to reverse lookup IP numbers ... a must for DNS free environments. + +=item B<--no-down> + +Normally cfgmaker will not include interfaces which are marked +anything but administratively and operationally UP. With this +switch you get them all. + +=item B<--show-op-down> + +Include interfaces which are operatively down. + +=item B<--zero-speed> I + +Assign this speed in bits-per-second to all interfaces which return 0 +for ifSpeed and ifHighSpeed. Some switches, notably Foundry equipment, +return a speed of zero for some interfaces. For example, to have +all interfaces reporting zero set to 100Mbps, use +--zero-speed=100000000. + +=item B<--subdirs> I + +Give each router its own subdirectory for the HTML and graphics (or +.rrd) files. The directory name is the given I string with a +couple of pattern replacements. The string "HOSTNAME" will be +replaced by the hostname of the router (however you specified it on +the B commandline -- it may be an actual hostname or just an +IP address), and "SNMPNAME" will be replaced with the device's idea of +its own name (the same name that appears on the right side of the +"Title" lines). For instance, a call like: + + cfgmaker --subdirs=HOSTNAME__SNMPNAME public@10.10.0.18 + +would result in the generation of lines looking something like: + + Directory[10.10.0.18_1]: 10.10.0.18__fp2200-bothrip-1.3 + +=item B<--output> I + +Write the output from B into the file I. The default +is to use C. B<--output> is expected to appear only once on the +command line. If used multiple times, the file specified by the last +B<--output> will be used. + +=item B<--nointerfaces> + +Don't generate configuration lines for interfaces. + +This makes cfgmaker skip all steps related to interfaces which means +it will not do any polling of the router to retrieve interface +information which speeds up the execution of cfgmaker and it will +neither run any interface templates. + +=item B<--interfaces> + +This makes cfgmaker generate configuration lines for interfaces (the +default behaviour). + +The main usage of this option is to negate an --nointerfaces appearing +earlier on the command line. + + +=back + +=head2 SNMP V3 Options + +B supports SNMP V3 using the B perl module. There are optional +parameters affecting SNMP operation. + +=over + +=item --enablesnmpv3 {yes|no} + +The B<--enablesnmpv3> option is an optional flag to check for the presence of +the B libraries. B will try to determine whether this flag is +required and will set the values automatically. + +=back + +=head3 SNMPv3 Arguments + +A SNMP context is a collection of management information accessible by a SNMP +entity. An item of management information may exist in more than one context +and a SNMP entity potentially has access to many contexts. The combination of +a contextEngineID and a contextName unambiguously identifies a context within +an administrative domain. In a SNMPv3 message, the contextEngineID and +contextName are included as part of the scopedPDU. All methods that generate +a SNMP message optionally take a B<--contextengineid> and B<--contextname> +argument to configure these fields. + +=over + +=item Context Engine ID + +The B<--contextengineid> argument expects a hexadecimal string representing +the desired contextEngineID. The string must be 10 to 64 characters (5 to +32 octets) long and can be prefixed with an optional "0x". Once the +B<--contextengineid> is specified it stays with the object until it is changed +again or reset to default by passing in the undefined value. By default, the +contextEngineID is set to match the authoritativeEngineID of the authoritative +SNMP engine. + +=item Context Name + +The contextName is passed as a string which must be 0 to 32 octets in length +using the B<--contextname> argument. The contextName stays with the object +until it is changed. The contextName defaults to an empty string which +represents the "default" context. + +=back + +=head3 User-based Security Model Arguments + +The User-based Security Model (USM) used by SNMPv3 requires that a securityName +be specified using the B<--username> argument. The creation of a Net::SNMP +object with the version set to SNMPv3 will fail if the B<--username> argument +is not present. The B<--username> argument expects a string 1 to 32 octets +in length. + +Different levels of security are allowed by the User-based Security Model which +address authentication and privacy concerns. A SNMPv3 target will +derive the security level (securityLevel) based on which of the following +arguments are specified. + +By default a securityLevel of 'noAuthNoPriv' is assumed. If the B<--authkey> +or B<--authpassword> arguments are specified, the securityLevel becomes +'authNoPriv'. The B<--authpassword> argument expects a string which is at +least 1 octet in length. Optionally, the B<--authkey> argument can be used so +that a plain text password does not have to be specified in a script. The +B<--authkey> argument expects a hexadecimal string produced by localizing the +password with the authoritativeEngineID for the specific destination device. +The C utility included with the Net::SNMP distribution can be used to create +the hexadecimal string (see L). + +Two different hash algorithms are defined by SNMPv3 which can be used by the +Security Model for authentication. These algorithms are HMAC-MD5-96 "MD5" +(RFC 1321) and HMAC-SHA-96 "SHA-1" (NIST FIPS PUB 180-1). The default +algorithm used by the module is HMAC-MD5-96. This behavior can be changed by +using the B<--authprotocol> argument. This argument expects either the string +'md5' or 'sha' to be passed to modify the hash algorithm. + +By specifying the arguments B<--privkey> or B<--privpassword> the securityLevel +associated with the object becomes 'authPriv'. According to SNMPv3, privacy +requires the use of authentication. Therefore, if either of these two +arguments are present and the B<--authkey> or B<--authpassword> arguments are +missing, the creation of the object fails. The B<--privkey> and +B<--privpassword> arguments expect the same input as the B<--authkey> and +B<--authpassword> arguments respectively. + +The User-based Security Model described in RFC 3414 defines a single encryption +protocol to be used for privacy. This protocol, CBC-DES "DES" (NIST FIPS PUB +46-1), is used by default or if the string 'des' is passed to the +B<--privprotocol> argument. By working with the Extended Security Options +Consortium http://www.snmp.com/eso/, the module also supports additional +protocols which have been defined in draft specifications. The draft +http://www.snmp.com/eso/draft-reeder-snmpv3-usm-3desede-00.txt +defines the support of CBC-3DES-EDE "Triple-DES" (NIST FIPS 46-3) in the +User-based Security Model. This protocol can be selected using the +B<--privprotocol> argument with the string '3desede'. The draft +http://www.snmp.com/eso/draft-blumenthal-aes-usm-04.txt +describes the use of CFB128-AES-128/192/256 "AES" (NIST FIPS PUB 197) in the +USM. The three AES encryption protocols, differentiated by their key sizes, +can be selected by passing 'aescfb128', 'aescfb192', or 'aescfb256' to the +B<-privprotocol> argument. + +=head2 Details on Filters + +The purpose of the filters is to decide which interfaces to accept and +which interfaces to reject. This decision is done for each interface by +evaluating the filter expression as a piece of Perl code and investigating +the result of the evaluation. If true, accept the interface otherwise +reject it. + +When working with filters, remember that Perl has it's own idea of what truth +and false is. The empty string "" and the string "0" are false, all other +strings are true. This further imples that any integer value of 0 is +false as well as any undef value. It also implies that all references +are considered true. + +As the filter is evaluated as a Perl expression, several useful constructs +in Perl are worth mentioning: + +Expressions might be grouped by using parentheses "()". Expressions might +be combined using boolean operators such as the following: + +=over + +=item "B" (equivalent with "B<&&>") + +Boolean "and" of the two expressions, is only true if both expressions are +true. Example: I B I + +=item "B" (equivalent with "B<||>") + +Boolean "or" of the two expressions, is true if either or both expressions +are true. Example: I B I + +=item "B" (equivalent with "B") + +Boolean negation of a single expression. Example: B I . +Yet another example: BI + +=back + +(For more details on this I recommend a book on Perl) + +=head3 Predefined Filter Variables + +To facilitate, there are a number of predefined values available to use +in the filter. Note that these variables are also available when templates +interfaces are evaluated (but not host templates). + +Caveat: All these variables' names begin with a dollar sign ($), which +is a syntactic requirement for scalar variables in Perl. The danger here +is that the dollar sign in many shells is an active character (often +used for shell variables exactly as in Perl variables) so it is important +to ensure that the Perl expression isn't evaluated by the command line +shell as shell code before being passed to cfgmaker as command line +arguments. In shells like Bourne shell, ksh shell or bash shell, placing +the entire expression within single qoutes will avoid such accidental +evaluation: + + '--if-filter=($default_iftype && $if_admin)' + +=over + +=item B<$if_type> + +This is an integer specifying the interface type as +per the SNMP standards and as reported by the polled device. A complete list +of interface types would be impractical for this document , but there are +a number predefined varables below. Normally, cfgmaker puts in the target's +PageTop this iftype value within paranthesis after the name of the interface +type. (e.g "propPointToPointSerial (22)"). + +Here's a list of some of the most common interface types by number: + + 6 ethernetCsmacd + 7 iso88023Csmacd + 9 iso88025TokenRing + 15 fddi + 19 E1 + 20 basicISDN + 21 primaryISDN + 22 propPointToPointSerial + 23 ppp + 24 softwareLoopback + 30 ds3 + 32 frame-relay + 33 rs232 + 37 atm + 39 sonet + 44 frameRelayService + 46 hssi + 49 aal5 + 53 propVirtual + 62 Fast Ethernet (100BaseT) + 63 ISDN & X.25 + 69 Full Duplex Fast Ethernet (100BaseFX) + 94 Asymetric Digital Subscriber Loop (ADSL) + 117 Gigabit Ethernet + 134 ATM Sub Interface + +=item B<$default> + +True if and only if cfgmaker normally should +accepted the interface based on the interfaces administrative and +operational state (taking the flags B<--no-down> and B<--show-op-down> into +account) and it's type (and a few other things). + +=item B<$default_ifstate> + +True if and only if cfgmaker would have accepted the +interface based on it's operational and administrative states (also taking +into account the presence of the flags B<--no-down> and B<--show-op-down>). + +=item B<$default_iftype> + +True if and only if cfgmaker would have accepted the +interface based on it's type (and a few type specific details in addition). + +=item B<$if_admin> + +True if and only if the interface is in an adminstrative up +state. + +=item B<$if_oper> + +True if and only if the interface is in an operational up +state. + +=back + +A number of variables are also predefined to easily decide if an interface +belong to a certain cathegory or not. Below is all those variables listed +together with which if_type numbers each variable will be true for. Note +that some variables refer to other variables as well. + +=over + +=item B<$if_is_ethernet> + +True for ethernet interfaces (nr 6, 7, 26, 62, 69 and 117). + +=item B<$if_is_isdn> + +True for various ISDN interface types (nr 20, 21, 63, 75, 76 and 77) + +=item B<$if_is_dialup> + +True for dial-up interfaces such as PPP as well +as ISDN. (nr 23, 81, 82 and 108 in addition to the numbers of +B<$if_is_isdn>). + +=item B<$if_is_atm> + +True for miscellaneous ATM related interface types (nr 37, 49, 107, 105, +106, 114 and 134). + +=item B<$if_is_wan> + +True for WAN interfaces point to point, Frame Relay and High Speed Serial ( 22,32,44,46) + +=item B<$if_is_lan> + +True for LAN interfaces (8, 9, 11, 15, 26, 55, 59, 60 and 115 in addition +to the numbers of B<$if_is_ethernet>). + +=item B<$if_is_dsl> + +True for ADSL, RDSL, HDSL and SDSL (nr 94, 95, 96, 97) + +=item B<$if_is_loopback> + +True for software loopback interfaces (nr 24) + +=item B<$if_is_ciscovlan> + +True for Cisco VLAN interfaces (interfaces with the +word Vlan or VLAN in their ifdescs) + +=item B<$if_vlan_id> + +Returns the vlan id associated with a specific port +on Cisco Catalyst switches under both Catalyst OS +and IOS, and 3Com switches. If it is not a vlan interface, will return undef. + +=item B<$if_cisco_trunk> + +Returns the trunking state of a specific port +on Cisco Catalyst switches under both Catalyst OS +and IOS. Returns "1" if the interface is a trunk, undef otherwise. + +=item B<$if_MTU> + +Returns the Maximum Transfer Unit associated with a specific port. + +=back + +Besides that, you can also use the variables defined for templates below. +Further, all the variables available in cfgmaker is at the scripts disposal +even if the use of such features is discouraged. More "shortcuts" in the +form of variables and functions will be made available in the future instead. + +=head3 Examples on Filters + +The following filter will not affect which interfaces get's included or +excluded, it will make cfgmaker behave as normally. + + '--if-filter=$default' + +The following filter will make cfgmaker exclude PPP (23) interfaces: + + '--if-filter=$default && $if_type!=23' + +The following filter will make cfgmaker behave as usual except that it will +consider the operational state of an interface irrelevant but still reject +all interfaces which are administratively down. + + '--if-filter=$if_admin && $default_iftype' + +=head2 Details on Templates + +The contents of the template files are evaluated as a Perl program. A +number or Perl variables are available for the program to read and others +are used to be written to. + +As quite a few of the predefined variables has values which are are supposed +to be used in HTML code some of them have an "HTML-escaped" variant, e.g +$html_syslocation is the HTML escaped variant of $syslocation. The HTML +escaping means that the chars "<", ">" and "&" are replaced by "<", +">" and "&" and that newlines embedded in the string are prepended +with "
" and appended with a space character (if a newline is last in the +string it is not touched). + +=head3 Writable Template Variables + +These are the variables available to store the configuration lines in. +Some of them are initialized prior to the evaluation of the template but +such content normally is comments for inclusion in the final configuration +file so those variables might be reset to the empty string in the template +code to eliminate the comments. The other way around is also possible, the +contents of these variables might be extended with further information +for various reasons such as debugging etc. + +Once the template has been evaluated, the following happens: if the +template is a interface template and the actual interface for some reason +is rejected and thus needs to be commented out, all the lines in the +variable B<$target_lines> are turned into comments by adding a hash mark +("#") at their beginning. Then all the variables B<$head_lines>, +B<$problem_lines> , B<$target_lines> and B<$separator_lines> +are concatenated together to form the lines to add to the configuration file. + +=over + +=item B<$target_lines> + +This variable is the placeholder for the configuration lines created +by the template. B<$target_lines> is predefined to be empty when +the template code is evaluated. + +=item B<$head_lines> + +This variable is intended to be the placeholder for the comment line +appearing just before the target in the configuration file. It is +initialized with that comment line before the evaluation of the template +code and if the template doesn't modify B<$head_lines> during evaluation, +the comment will look like usual in the config file. + +=item B<$problem_lines> + +This variable is intended to be the placholder for the comment lines +describing any problems which might have been encountered when trying +to add the target into the configuration. For host templates it's +normally not used and for those it's predefined as the empty string. +For interface templates B<$problem_lines> is predefined with +the error description comments which cfgmaker normally would use for +rejected interfaces or as the empty string for accepted interfaces. + +It is possible to test against B<$problem_lines> to find out if +an interface will be included or rejected but this is not recommended. +Test against B<$if_ok> instead. + +=item B<$separator_lines> + +This variable is the placeholder for the string to use as the separator +between the code for individual targets. The contents of this variable +is put after each target (so the lines will appear after the end of the +last target in the config as well). + +=back + +=head3 Predefined Template Variables + +All the variables below are available for interface templates to use. +For host templates, only those listed under L +are available. + +For interface templates the variables listed under +L are also available. + +=head3 Host and System Variables + +=over + +=item B<$router_name> + +This is the fully qualified name for the router. It is affected by the +following items on the command line: the router name itself and +B<--dns-domain>. + +=item B<$router_connect> + +This is the reference string for the router being polled. It is on the +form community@router possibly followed by some snmp options. It is +affected by the following items on the command line: the router name +itself, B<--community>, B<--snmp-options> and B<--dns-domain>. +(There's no HTML escaped variant available) + +=item B<$directory_name> + +This variable should contain the directory name as cfgmaker normally would +use as the value for the "Directory[]" directive. The value is determined +by the B<--subdirs> command line option. If B<--subdirs> isn't specified +B<$directory_name> will be the empty string. (There's no HTML escaped +variant available) + +=item B<$syscontact> + +This variable is the router's SNMP sysContact value. (HTML escaped +variant: B<$html_syscontact>) + +=item B<$sysname> + +This variable is the router's SNMP sysName value. (No HTML escaped +variant available) + +=item B<$syslocation> + +This variable is the router's SNMP sysLocation value. (HTML escaped +variant: B<$html_syslocation>) + +=item B<$sysdescr> + +This variable is the router's SNMP sysDescr value. It is normally not used +by cfgmaker but might be useful in a template. (HTML escaped variant: +B<$html_sysdescr>) + +=back + +=head3 Interface Target Related Variables + +=over + +=item B<$target_name> + +This is what cfgmaker normally would use as the the name of the target. +The target name is what is found within the square brackets, "[]", for target +directives. (There's no HTML escaped variant available) + +=item B<$if_ref> + +This the reference string for the interface. It is expected to be used +in the "Target[xyz]" directive to distinguish what interface to use. The +value of this variable is affected by the B<--ifref> command line option. +It is normally used together with B<$router_connect>. +(There's no HTML escaped variant available) + +=item B<$if_ok> + +This variable is true if the interface is going to be included into the +configuration file, otherwise false. Don't test against other variables +such as B<$problem_lines> to find out if an interface will be rejected +or not, use this B<$if_ok> instead. + +=item B<$default_target_lines> + +This variable contains all the target lines which cfgmaker by default outputs +for this interface. It's useful if you want to have the "standard target" +but want to add some extra lines to it by using a template. + +=back + +By default cfgmaker uses the following directives for each target it +generates: Target[], SetEnv[], MaxBytes[], Title[], PageTop[] and if +there is any directory specified also the Directory[] directive. + +To facilitate the creation of templates which generates target configs +which are similar to the default one, each of the above mentioned +directive lines have a corresponding variable containing the line as +cfgmaker would have output it by default. + +Note that none of these have a HTML escaped variant, text in them is +HTML escaped where needed. Also note that they do not have any newline +at the end. + +=over + +=item B<$default_target_directive> + +This variable contains the default string for the Target[] directive line. + +=item B<$default_setenv_directive> + +This variable contains the default string for the SetEnv[] directive line. + +=item B<$default_directory_directive> + +This variable contains the default string for the Directory[] directive line +which means it is an empty string (with no newline) if there's no directory. + +=item B<$default_maxbytes_directive> + +This variable contains the default string for the MaxBytes[] directive line. + +=item B<$default_title_directive> + +This variable contains the default string for the Title[] directive line. + +=item B<$default_pagetop_directive> + +This variable contains the default string for the PageTop[] directive lines. + +=back + +=head3 Interface Network Configuration Variables + +=over + +=item B<$if_ip> + +This variable should contain the IP-address of the interface, if any has +been assigned to it. (There's no HTML escaped variant available) + +=item B<$ifindex> + +This variable is the SNMP ifIndex for the interface which per definition +always is an integer. (There's no HTML escaped variant available) + +=item B<$if_index> + +Equivalent with B<$ifindex>. + +=item B<$if_eth> + +Contains the ethernet address of the interface, if any. (There's no HTML +escaped variant available) + +=item B<$if_speed> + +This variable is the speed in bytes/second (with prefixes). (There's no +HTML escaped variant available) + +=item B<$if_speed_str> + +This variable is a cooked speed description which is either in bits or +bytes depending on wether or not the bits option is active and also with +the proper prefix for the speed (k, M, G etc). (No HTML escaped variant +available) + +=item B<$if_type_desc> + +This variable is a textual description of the interface type. (HTML +escaped variant: B<$html_if_type_desc>) + +=item B<$if_type_num> + +This variable the integer value corresponding to the interface type (for a +listing for the value for the more common interface types, see the section +DETAILS ON FILTERS above). (No HTML escaped variant available) + +=item B<$if_dns_name> + +This is the DNS name for the interface. (No HTML escaped variant available) + +=back + +=head3 Interface Name, Description and Alias Variables + +It might seem confusing with both I, I and I in +this context and to some extent it is. I and I are +usually supported on most equipment but how they are used varies, both +between manufacturers as well as between different cathegories of equipment +from the same manufacturer. The I is at least supported by Cisco +IOS, and that variable contains whatever is used in the IOS statement +called "description" for the interface (not to be confused with the SNMP +variables for I). + +For better control from the command line consider B<$if_title_desc> which contents +are controlled by the B<--if-descr> command line option. + +=over + +=item B<$if_snmp_descr> + +This variable should contain the "raw" description of the interface as +determined by the SNMP polling of the router. (HTML escaped variant: +B<$html_if_snmp_descr>) + +=item B<$if_snmp_name> + +The "raw" name for the interface as provided by SNMP polling. (HTML escaped +variant: B<$html_if_snmp_name>) + +=item B<$if_snmp_alias> + +The "raw" ifAlias for the interface as provided by SNMP polling. (HTML +escaped variant: B<$html_if_snmp_alias>) + +=item B<$if_cisco_descr> + +The "raw" CiscolocIfDescr for the interface as provided by SNMP polling. +(HTML escaped variant: B<$html_if_cisco_descr>) + +=item B<$if_description> + +This is the "cooked" description string for the interface, taking into account +the SNMP values found for the interface's RDescr, ifAlias and +CiscolocIfDescr. (HTML escaped variant: B<$html_if_description>) + +=item B<$if_title> + +The full string cfgmaker by default would have used for the Title[] directive +in the configuration as well as the content of the topmost H1 tag in the +PageTop[]. Is composed by the contents of B<$desc_prefix>, +B<$if_title_desc> and B<$sysname>. + +As B<$if_title> depends on B<$if_title_desc>, it is possible to indirectly +control B<$if_title> by using the command line option B<--if-descr>. + +(HTML escaped variant: B<$html_if_title>) + +=item B<$if_port_name> + +If the host is a Cisco Catalyst LAN switch, this variable is the name of +that port. (No HTML escaped variant available) + +=item B<$if_pp_port_name> + +If the host is a Nortel Passport LAN switch, this variable is the name of +that port. (No HTML escaped variant available) + +=item B<$desc_prefix> + +This variable is a prefix of the description of what the target is to use in +the "Title[]" directive and in the H1 section of the "PageTop[]". Default is +"Traffic analysis for ". (HTML escaped variant: B<$html_desc_prefix>) + +=item B<$if_title_desc> + +This is the description of the interface normally used by cfgmaker as part +of the variable B<$if_title>. The latter is used as the full string in the +"Title[]" directove and the H1 section in the PageTop[]. + +B<$if_title_desc> is controlled by the command line option B<--if-descr> +which indirectly controls the contents of B<$if_title> + +(HTML escaped variant: B<$html_if_title_desc>) + +=back + +=head3 Help Functions for Templates + +The following functions exists to facilitate the writing of host and +interface templates. + +=over + +=item B)> + +B takes a string as an argument and returns a new string +where the following substitutions has been done: the chars "<", ">" and +"&" are replaced by "<", ">" and "&" and that newlines embedded +in the string are prepended with "
" and appended with a space character +(newlines at the end of the string are not touched). + +=item B + +This function will try to poll each of the oids specified until +it is successful or has run out of oids. It will return the name of the +first oid that worked or undef if it is not successful + +=back + +=head3 Example Template Files + +=head4 Template Example 1: Eliminating Rejected Targets From Appearing + +This template file generates exactly the same configuration code per +interface as cfgmaker does by default, with the exception that it eliminates +all lines (comments as well as config code) for an interface if the +interface happens to be rejected. + + if(not $problem_lines) + { + $target_lines .= <$html_desc_prefix$html_if_title_desc -- $sysname +
+ + + + + + + + + + + + + + + + + + + + + + ECHO + + $target_lines .= < + + + + ECHO + + $target_lines .= < + + + + ECHO + + $target_lines .= < + + + + ECHO + + $target_lines .= < + + + + ECHO + + $target_lines .= < + + ECHO + } else { + $head_lines=""; + $problem_lines=""; + $target_lines=""; + $separator_lines=""; + } + +=head3 Template Example 2: Simplier Version of Example 1 + +Example 1 was partly intended to demonstrate how to customize the generation +of interface targets but also to provide a hint of how the variables are +used in the "default" template which one could consider that cfgmaker +normally uses. + +If you're only intrested in the easiest way of entirely eliminating those +reject interfaces, the template below would do the job as well by using +B<$default_target_lines>. + + if($if_ok) { + $target_lines = $default_target_lines; + } else { + $head_lines=""; + $problem_lines=""; + $target_lines=""; + $separator_lines=""; + } + +=head3 Template Example 3: Creating CPU Targets for Hosts + +Below is an example of a host template. + + $head_lines .= <$router_name CPU load +
+
System:$sysname in $html_syslocation
Maintainer:$html_syscontact
Description:$html_if_description
ifType:$html_if_type_desc ($if_type_num)
ifName:$html_if_snmp_name
Port Name:$if_port_name
Port Name:$if_pp_port_name
Max Speed:$if_speed_str
Ip:$if_ip ($if_dns_name)
+ + + + + + + + + + + + + + + + +
System:$router_name in $html_syslocation
Maintainer:$html_syscontact
Description:$html_sysdescr
Resource:CPU.
+
+ ECHO + + +=head1 EXAMPLES + +The first example creates a config file for I: the router +has the community name I. Interfaces get identified by their +IP number. Two global options get added to the config file. The +config file gets redirected to I. The '\' signs at the end +of the line mean that this command should be written on a single line. + + cfgmaker --global "WorkDir: /home/tobi" \ + --global "Options[_]: growright,bits" \ + --ifref=ip \ + public@router.place.xyz > mrtg.cfg + +Note: if cfgmaker is not in your path, but you are in the directory where +cfgmaker is stored, you can start it with ./cfgmaker + +The next example creates a config file for four devices: +I, I, I and +I all with the community I. + +The two routers will have B<--ifref> set to B whilst the two +switches will use B<--ifref> set to B. Further the routers will +use B<--ifdesc> set to B and I will use +B<--ifdesc> set to B whilst I use B instead. + +Finally, there will be two Options lines inserted in the configuration: +One will be in the beginning, whilst the other will be inserted after +the lines related to the two routers but before those lines related +to the switches. + + cfgmaker --global "WorkDir: /home/tobi" \ + --global "Options[_]: growright,bits" \ + --ifref=descr \ + --ifdesc=alias \ + public@router1.place.xyz \ + public@router2.place.xyz \ + --global "Options[_]: growright" \ + --ifref=name \ + --ifdesc=descr \ + public@switch1.place.xyz \ + --ifdesc=name \ + public@switch2.place.xyz > mrtg.cfg + + +The next example demonstrates how to use the B<--community>, +B<--snmp-options> and B<--dns-domain> to make the command line +simpler. All the equipment will use the community I, except for +the ppp-server which use community I. All equipment uses these +SNMP options: B<1s timeout>, B<1 retry> and B (B and +B is unspecified which means they use the default values). +The exception again is the ppp-server which uses B. +Finally, all the equipment is part of the domain I, except +for the ppp-server which is part of the domain I. +Note that the latter is achieved simply by specifying the name +of the ppp-server to be I> . + + cfgmaker --global "WorkDir: /home/tobi" \ + --global "Options[_]: growright,bits" \ + --dns-domain=place.xyz \ + --community=hidden \ + --snmp-options=::1:1::2 \ + router1 \ + router2 \ + router3 \ + router4 \ + router5 \ + switch1 \ + switch2 \ + switch3 \ + switch4 \ + switch5 \ + switch6 \ + switch7 \ + access@ppp-server.remote:::::1 > mrtg.cfg + + +=head1 SEE ALSO + +L + +=head1 AUTHOR + +Tobias Oetiker Etobi@oetiker.chE and +Jakob Ilves Ejakob.ilves@oracle.comE + + +=head1 LICENSE + +GNU General Public License + +=head1 COPYRIGHT + +Cfgmaker is Copyright 2000 by Tobias Oetiker Etobi@oetiker.chE + +=cut diff --git a/bin/indexmaker b/bin/indexmaker index 4c29c99..6d5a303 100755 --- a/bin/indexmaker +++ b/bin/indexmaker @@ -48,7 +48,7 @@ BEGIN { use FindBin; use lib "${FindBin::Bin}"; -use lib "${FindBin::Bin}${main::SL}..${main::SL}lib${main::SL}mrtg2"; +use lib "${FindBin::Bin}${main::SL}..${main::SL}@@lib@@${main::SL}mrtg2"; use MRTG_lib "2.100016"; use Getopt::Long; diff --git a/bin/indexmaker.lib64 b/bin/indexmaker.lib64 new file mode 100755 index 0000000..4c29c99 --- /dev/null +++ b/bin/indexmaker.lib64 @@ -0,0 +1,859 @@ +#! /usr/bin/perl -w +# -*- mode: Perl -*- +################################################################## +# MRTG 2.17.7 --- Index Generator +################################################################## +# +# This reads a mrtg.cfg file form std input or cmdline argument +# and it takes a regexp on the cmdline to specify which +# targets to look at. +# +# from this info it produces a router index on stdout or +# on the filename specified by the --output option +# +################################################################## +# Distributed under the GNU General Public License +# Copyright 2000 by Tobias Oetiker +################################################################## + +$main::GRAPHFMT="png"; + +require 5.005; +use strict; + +# DEBUG TARGETS +# base - basic program flow +#@main::DEBUG=qw(base); + +BEGIN { + # Automatic OS detection ... do NOT touch + if ( $^O =~ /^(?:(ms)?(dos|win(32|nt)?))/i ) { + $main::OS = 'NT'; + $main::SL = '\\'; + $main::PS = ';'; + } elsif ( $^O =~ /^NetWare$/i ) { + $main::OS = 'NW'; + $main::SL = '/'; + $main::PS = ';'; + } elsif ( $^O =~ /^VMS$/i ) { + $main::OS = 'VMS'; + $main::SL = '.'; + $main::PS = ':'; + } else { + $main::OS = 'UNIX'; + $main::SL = '/'; + $main::PS = ':'; + } +} + +use FindBin; +use lib "${FindBin::Bin}"; +use lib "${FindBin::Bin}${main::SL}..${main::SL}lib${main::SL}mrtg2"; + +use MRTG_lib "2.100016"; +use Getopt::Long; +use Pod::Usage; + +#---------------------------------------------------------------------- +sub decimal_sort($$) { + my $a = $_[0]; + my $b = $_[1]; + + $a =~ s/([0-9]+)/sprintf("%09u",${1})/ge; + $b =~ s/([0-9]+)/sprintf("%09u",${1})/ge; + return $a cmp $b; + } +#---------------------------------------------------------------------- + +my @argv = @ARGV; +my $argz = "$0"; +foreach my $ar (@argv) { + if ($ar =~ /[ |()]/ ) { + $ar = sprintf qq{"%s"}, $ar; + } + $argz .= " $ar"; +} + + +main(); +exit 0; + +sub main { + # default options + my %opt = ( + sort => 'original', + show => 'day', + section => 'h1', + columns => 2, + addhead => '', + bodyopt => 'bgcolor="#ffffff" text="#000000" '. + 'link="#000000" vlink="#000000" alink="#000000"', + title => 'MRTG Index Page', + headlevel => 1, + pagetopend => '', + pagetop => '', + pageend => '', + prefix => '', + rrdviewer => '/cgi-bin/14all.cgi', + optlog => 1, + bold => 1, + boldon => '', + boldoff => '', + div => 'DIV', + imgborder => 1, + cellspacing => 10, + nolegend => 0, + ); + $opt{headon} = ""; + $opt{headoff} = ""; + # load real options + options(\%opt); + + #adapt some defaults to the current options + die "ERROR: --autoprefix requires --output" + if ($opt{autoprefix} and !defined $opt{output}); + $opt{pageend} = $opt{pagetopend} if (defined $opt{pagetopend} and not $opt{pageend}); + $opt{pagetop} = $opt{pagetopend} if (defined $opt{pagetopend} and not $opt{pagetop}); + $opt{boldon} = $opt{boldoff} = "" if (!$opt{bold}); + $opt{picfirst} = (defined $opt{picfirst}?1:0); + if ($opt{compact}) { + $opt{imgborder} = 0; + $opt{cellspacing} = 0; + $opt{headon} = $opt{boldon}; + $opt{headoff} = $opt{boldoff}; + } + if ($opt{sidebyside}) { + $opt{div} = 'td'; + } + + # slurp config files + my %rcfg; + my %cfg; + my @target; + my @routers; + my $cfgfile; + my %files; + while (@ARGV) { + $cfgfile = shift @ARGV; + readcfg($cfgfile,\@routers,\%cfg,\%rcfg); + if (($opt{sectionhost}) or ($opt{perhost})) { + #We need to cache the "hostname" as appeared in cfgfile, + #since it does change in cfgcheck (for ex. if multiple + #overlapping cfgfiles are specified) + for my $targ (@routers) { + if ( !defined $rcfg{host}{$targ} and + !($rcfg{target}{$targ} =~ m/(?{$targ} = $pref + if (! defined $rcfg{prefixes}->{$targ}); + } + } + } + # generate index page + genindex(\@routers, \%cfg, \%rcfg, \%opt, \%files); +} + +sub cleanurl ($) { + my $url = shift; + $url =~ s|([^/.][^/.]*/)\.\./\1|$1|g; + return $url; +} + +#Take a path the mrtg (usually the mrtg output directory) and the overview +#file, find the relative path from the overview to the directory +sub subpath ($$) { + my $sub = shift; + my $out = shift; + my @s=split /$main::SL/,$sub; + my @o=split /$main::SL/,$out; + pop @o; #Last is a filename; + for my $i (0..$#s) { #cut common dirs + if (defined $s[0] and + defined $o[0] and + $s[0] eq $o[0] ) { + shift @s; + shift @o; + } + } + my $ret = join $main::SL,@s; + for my $i (0..$#o) { + $ret = "..$main::SL$ret"; # ".." == "Directory below this one" for + # dos, windows, unix. What about VMS ? + # Is this correct ? HEH + } + $ret .= $main::SL; #Possibly this should be "/" in order not + #to break on platforms !unix, since it will be + #used for generating urls ? + #Don't degenerate in "/" when really no prefix is needed. + $ret = "" if ($ret eq $main::SL); + return $ret; +} + +sub genindex ($$$$) { + my $routers = shift; + my $cfg = shift; + my $rcfg = shift; + my $opt = shift; + my $cfgfile = shift; + my $index; + my $metaCmdLine; + # ----------------------------------------------------------- + # keep only the items our users want (--filter) + # ----------------------------------------------------------- + my @filtered; + ITEM: foreach my $item (@{$routers}) { + foreach my $filter (@{$$opt{filter}}) { + if ($filter =~ /(.+)([=!]~)(.+)/) { + my ($area,$comp,$regex) = ($1,$2,$3); + my $value; + for ($area) { + /^title|pagetop$/ && + do { $value = $$rcfg{$area}{$item}; last }; + + /^name$/ && + do { $value = $item; last }; + + die "ERROR: unknown filter area $_\n"; + }; + for ($comp) { + /^=~$/ && + do { next ITEM unless $value =~ /$regex/; last }; + /^!~$/ && + do { next ITEM unless $value !~ /$regex/; last }; + die "ERROR: unknown comparison operator $_\n"; + }; + } else { + die "ERROR: invalid filter expression $filter\n"; + } + } + push @filtered, $item; + }; + + # ----------------------------------------------------------- + # get items into proper order (--sort) + # ----------------------------------------------------------- + my @order; + for ($$opt{sort}) { + /^original$/ && do {@order = @filtered; last}; + /^name$/ && do { @order = sort { decimal_sort($a,$b); } @filtered; last}; + /^title$/ && do { @order = + sort { decimal_sort($$rcfg{title}{$a}, $$rcfg{title}{$b}) || $a cmp $b } + @filtered; + last; + }; + /^descr(iption)?$/ && + do { + @order = + sort { + $$rcfg{pagetop}{$a} =~ + m[Description:\s*(?:\S+\s+)?(.+?)]i; + my $aval = lc $1; + $$rcfg{pagetop}{$b} =~ + m[Description:\s*(?:\S+\s+)?(.+?)]i; + my $bval = lc $1; + $aval cmp $bval; + } @filtered; + last; + }; + die "ERROR: unknown sort order '$$opt{sort}'\n"; + } + + die "ERROR: did not find any matching data in cfg file\n" unless @order; + + # ----------------------------------------------------------- + # issue page top + # ----------------------------------------------------------- + my $interval =$$cfg{'interval'} ? $$cfg{'interval'} : 5; + my $expiration = &expistr($interval); + my $refresh = $$cfg{'refresh'} ? $$cfg{'refresh'} : 300; + for ($$opt{show}) { + $refresh = /^week$/ && 1800 + || /^month$/ && 7200 + || /^year$/ && 86400 + || $refresh ; + } + + my $gifPath = ''; + + if ($$cfg{icondir} || $$opt{icondir}) { + $gifPath = $$opt{icondir} || $$cfg{icondir}; + #lets make sure there is a trailing path separator + $gifPath =~ s|/*$|/|; + } else { + $gifPath = "$$cfg{imagehtml}"; + } + + +if ($$opt{optlog}) { + $metaCmdLine = $argz; +} else { + $metaCmdLine = ""; +} + +$metaCmdLine =~ s/&/&/g; # Must be first, otherwise it will affect the following changes +$metaCmdLine =~ s/"/"/g; +$metaCmdLine =~ s//>/g; + my $headeradd = $$opt{headeradd} || ""; + $index = < + + + $$opt{title} + + + + + + + + + + $headeradd +ECHO + + $index .= < +/* commandline was: $argz */ +/* sorry, no style, just abusing this to place the commandline and pass validation */ + +ECHO + + $index .= < + + +ECHO + $index .= <$$opt{subtitle}

+ECHO + + $index .= < + +ECHO + + # ----------------------------------------------------------- + # print the graph items + # ----------------------------------------------------------- + my $itemnr = 0; + my $first = $order[0]; + my $host = $$rcfg{host}{$first}; + if ($host){ + $index .= "$$opt{headon}Interfaces of $host $$opt{headoff}" if $$opt{perhost}; + } else { + $index .= "$$opt{headon}Special Items$$opt{headoff}" if $$opt{perhost}; + } + foreach my $item (@order) { + + if ($$opt{perhost}) { + my $newhost = $$rcfg{host}{$item} || 'unspecified host'; + if (!($host eq $newhost)) { + $host = $newhost; + if ($host){ + $index .= "$$opt{headon}Interfaces of $host $$opt{headoff}\n"; + } else { + $index .= "$$opt{headon}Special Items$$opt{headoff}\n"; + } + $index .= "\n"; + $itemnr=0; + } + } + $$opt{prefix} = $$rcfg{prefixes}->{$item} if ($$opt{autoprefix}); + $itemnr++; + $index .= ""; + my $dirrel = "../" x ($$rcfg{'directory_web'}{$item} =~ tr|/|/|); + + # --- produce graph section title --- + my $section; + for ($$opt{section}) { + /^h1$/ && + do{ + if ($$rcfg{pagetop}{$item} =~ m[+]*>(.+?)Description:\s*\Q$section\E\s*([^< ][^<]+?),i + and $section = $1; + last; + }; + /^portname$/ && + do{ + $section = "No Portname for $item"; + $$rcfg{pagetop}{$item} =~ m,Port Name:\s*(.*?),i + and $section = $1; + last; + }; + /^ifname$/ && + do{ + $section = "No Portname for $item"; + $$rcfg{pagetop}{$item} =~ m,ifname:\s*(.*?),i + and $section = $1; + last; + }; + die "ERROR: unknown sectioning type $_\n"; + }; + if (defined $$rcfg{host}{$item} and + !($section =~ m/\b\Q$$rcfg{host}{$item}\E\b/i)) { + $section = ucfirst $$rcfg{host}{$item} . ": $section"; + } + + # --- write the actual graph ---- + die "ERROR: Unknown show type $$opt{show}\n" + unless $$opt{show} =~ /^day|week|month|year|none$/; + + my $image = "$item-$$opt{show}.${main::GRAPHFMT}" + if $$opt{show} ne 'none'; + + $index .= "<$$opt{div}>" if (!$$opt{sidebyside}); + + if (not $image) { + if ($$cfg{logformat} eq 'rrdtool') { + my $sep = $$opt{rrdviewer} =~ /\?/ ? '&' : '?'; + $index .= + "". + "$$opt{boldon}". + "$section$$opt{boldoff}"; + } else { + $index .= "$$opt{boldon}". + "". + "$section$$opt{boldoff}\n<$$opt{div}>"; + } + } else { + #loop used for reversing (text,images) to (images,text) if req. + for my $picfirstloop (1,0) { + if ( $picfirstloop^$$opt{picfirst} ) { + $index .= "$$opt{boldon}$itemnr. $$opt{boldoff}" + if $$opt{enumerate}; + if ($$opt{clicktext}) { + $index .= "$$opt{boldon}"; + $index .= $section; + $index .= "$$opt{boldoff}"; + } else { + $index .= "$$opt{boldon}$section$$opt{boldoff}"; + } + $index .= "\n<$$opt{div}>" if $picfirstloop; + } + + if ( !($picfirstloop^$$opt{picfirst}) ) { + # figure show name for rrd viewer + if ($$cfg{logformat} eq 'rrdtool') { + my $sep = $$opt{rrdviewer} =~ /\?/ ? '&' : '?'; + $index .= "". + "\"$item" + } else { + $index .= ""; + $index .= "\"$item"; + } + $index .= "\n<$$opt{div}>" if $picfirstloop; + } + } + } + + $index .= "" if (!$$opt{sidebyside}); + $index .= "\n"; + + # --- new table column if necessary ---- + if (($itemnr) % $$opt{columns} == 0) { + $index .= "\n\n"; + } + } + # ----------------------------------------------------------- + # print page end + # ----------------------------------------------------------- + + + my $VERSION = "2.17.7"; + $index .= < + + +ECHO + + $index .= < + + + + + + +
MRTGMulti Router Traffic Grapher
+ + + + + +
+ version $VERSION + Tobias Oetiker + <tobi\@oetiker.ch> + and Dave Rand <dlr\@bungi.com> +
+ECHO + $index .= < + +ECHO + + # ----------------------------------------------------------- + # write out the index page + # ----------------------------------------------------------- + if ($$opt{output}) { + debug ('base', "Writing $$opt{output}"); + open X, ">$$opt{output}" or die "ERROR: creating $$opt{output}: $!\n"; + print X $index; + close X; + } else { + print $index; + } + +} + +sub options ($) { + my $opt = shift; + my @options = ( + 'help|?', + 'man', + 'output=s', + 'filter=s@', + 'addhead=s', + 'title=s', + 'subtitle=s', + 'bodyopt=s', + 'pagetopend=s', + 'pagetop=s', + 'pageend=s', + 'columns=i', + 'perhost!', + 'sort=s', + 'enumerate', + 'width=i', + 'height=i', + 'show=s', + 'section=s', + 'version', + 'prefix=s', + 'headeradd=s', + 'clicktext!', + 'optlog!', + 'compact!', + 'headlevel=i', + 'bold!', + 'picfirst!', + 'sidebyside!', + 'nolegend', + 'autoprefix!', + 'sectionhost!', + 'icondir=s', + 'rrdviewer=s'); + + #generate --option-file from --option + for ( grep /=s$/,@options ) { + my $fileopt = $_; + $fileopt =~ s/=s$/-file=s/; + push @options, $fileopt; + } + + GetOptions( $opt, @options ) or pod2usage(-verbose => 1); + + if ($$opt{prefix}){ + $$opt{prefix} .= '/'; + $$opt{prefix} =~ s|/+$|/|; + } + die ("Indexmaker for mrtg-2.17.7\n") if $$opt{version}; + pod2usage(-exitval => 1, -verbose => 2) if $$opt{man}; + pod2usage(-verbose => 1) if not @ARGV; + + #take care of --fileoption --> --option + for my $fileopt ( grep /-file$/, keys %{$opt} ) { + my $orgopt = $fileopt; + $orgopt =~ s/-file$//; + $$opt{$orgopt} = &readfile($$opt{$fileopt}); + } +} + +#return the contents of a file +sub readfile($) { + my $file = shift; + open F,"<$file" or die "ERROR: can\'t open $file for read, $!"; + my $sl = $/; + $/ = undef; + my $string = ; + $/ = $sl; + close F; + return $string; +} + +__END__ + +=pod + +=head1 NAME + +indexmaker - Creates index files for mrtg web sites (mrtg-2.17.7) + +=head1 SYNOPSIS + +indexmaker [options] mrtg.cfg [other.cfg ...] + +=head1 OPTIONS + + --output=filename set output filename (default: stdout) + + --filter title=~regexp select targets by matching regexp against titles + --filter pagetop=~regexp select targets by matching regexp against pagetop + --filter name=~regexp select targets by matchin regexp against name + + --addhead=text insert this text between and + --title=text set title of generated index file + --subtitle=text add a subtitle to the generated index file + --bodyopt=text set body tag options + --headlevel=number use at top of page (default: 1) + --pagetop=text insert this text between and

...

+ --pageend=text insert this text after the main body + --pagetopend=text use this text for pagetop or pageend if undefined + --nolegend do not add the Mrtg legend at the end of the page + + --columns=number show graphs in a table with x columns (default: 2) + --perhost show graphs of the same host on a row + --compact try to make a vertically more compact page + --optlog log the used command line in the page (default: log) + + --sort=title sort graphs by title + --sort=name sort graphs by their name + --sort=descr sort graphs by their description + --sort=original leave as is (default) + + --enumerate add a sequence number to the title of each graph + + --picfirst place pictures before text (default: text first) + --width=number set width of graphs (default: not set) + --height=number + --sidebyside place text / pictures side by side (default: above/below) + --bold use bold text (default: bold) + --clicktext make the text link to the inner page (like the image) + + --show=day pick which graph to show in the index (default) + --show=week + --show=month + --show=year + --show=none + + --section=h1 h1 tag from pagetop as section heading (default) + --section=title title as section headings for graphs + --section=name graph name as section heading + --section=descr graph description as section heading + --section=ifname interface name (ifName) as section heading + --section=portname port name entry in pagetop as section heading + --sectionhost Try to prepend the host to the section heading if missing + + --rrdviewer=path path to rrdviewer (default: /cgi-bin/14all.cgi) + --icondir=path path to icondir + --prefix=path path from the location of the index.html to the graphs + --headeradd=string add string to the html page header + --autoprefix try to set prefix automatically + + ---file=file read string argument for option from file + +=head1 DESCRIPTION + +B can create web pages which display the status of an +array of mrtg interface status pages. + +=over + +=item B<--output> I + +set output filename (default: stdout) + +=item B<--filter> (B|B<pagetop>|B<name>)(B<=~>|B<!~>)I<regexp> + +Several filters may get set. Each filter can match agains the contents +of a specific section of the mrtg config file. B<Name> refers to the +bit in square brackets (option[name]: bla). + +Depending on the match operator chosen (B<=~> or B<!~>) the match will be +positive or negative. + +Note that some shells consider B<!> a special character. It may be +necessary to type B<\!~> instead. + +=item B<--title> I<text> + +Set title of generated index file (default: regexp) + +=item B<--bodyopt> I<text> + +The value of this argument gets appended to +the E<lt>BODYE<gt> tag. This allows you to set document colors. +By default this option is set to + + bgcolor="#ffffff" text="#000000" link="#000000" vlink="#000000" alink="#000000" + +=item B<--columns> I<number> + +Display graphs in a table with I<number> columns (default: 2) + +=item B<--sort> B<title>|B<name>|B<descr>|B<original> + +Sort the graphs in the page either by B<title>, by B<name>, by interface +B<descr>iption, or leave them as is. + +=item B<--enumerate> + +Add a sequence number to the title of each graph + +=item B<--width> I<number> + +Set width of graphs + +=item B<--height> I<number> + +Set the height of the graphs + +=item B<--show> B<day>|B<week>|B<month>|B<year>|B<none> + +Select which graph to show in the index page. You can supress images +completely with B<--show=none>. + +=item B<--section> B<h1>|B<title>|B<name>|B<description>|B<portname> + +Select what to use as the title for each graph in the page. B<h1> is +the H1 section from pagetop, B<title> is the graph title, B<name> is +the bit in square brackets (option[name]: bla), and B<descr> or +B<description> is the text from the Description field of the PageTop +(the Cisco description text if it's available, otherwise just the +interface description). B<portname> is the C<Port Name:> from pagetop. + +=item B<--sectionhost> + +Extract the hostname from the target line (this does not work if the +target is a mathematial expression). Prepend the hostname (and a colon) +to the section if not already present. + +=item B<--rrdviewer> I<path> + +If you have set the B<LogFormat: rrdtool> property in the mrtg.cfg +file, the index will take this into account. The only thing you must +tell it is the path to your grapher cgi. (default: /cgi-bin/14all.cgi) + +=item B<--prefix> I<path> + +By default we assume that the file generated by indexmaker is stored in +I<WorkDir>. If you want to store it somewhere else, specify how to reach +I<WorkDir> from the place where the Index is stored. Note that you have to +use '/' as path separator as this will be used in urls. Speaking of which, +you can even enter a whole url. + +=item B<--autoprefix> I<path> + +Requires --output. +Try to generate the prefix automatically by comparision of the path to the +output file set with --output and the Htmldir set in the configuration files. +Particulary useful when multiple configuration files are specified, with +different Htmldir settings. + +=item B<--optlog> + +Default is logging in the generated page the command line, suppress with +--nooptlog . Useful if the commandline contains a complex --pagetop=string +which could confuse simple browsers. + +=item B<--someoption-file> I<filename> + +For any someoption which takes a I<string> as parameter you can read the +string from a file by adding <-file> to the option keyword. The whole +content of the file will be read and used as the I<string>. The file must +exist. + +=back + +=head1 AUTHOR + +Tobias Oetiker E<lt>tobi@oetiker.chE<gt> + +=head1 LICENSE + +GNU General Public License + +=head1 COPYRIGHT + +2000-2001 Tobias Oetiker E<lt>tobi@oetiker.chE<gt> + +=cut diff --git a/bin/mrtg b/bin/mrtg index bc9c316..a6aeb47 100755 --- a/bin/mrtg +++ b/bin/mrtg @@ -81,7 +81,7 @@ ERR use FindBin; use lib "${FindBin::Bin}"; -use lib "${FindBin::Bin}${main::SL}..${main::SL}lib${main::SL}mrtg2"; +use lib "${FindBin::Bin}${main::SL}..${main::SL}@@lib@@${main::SL}mrtg2"; use Getopt::Long; use Math::BigFloat; diff --git a/bin/mrtg-traffic-sum b/bin/mrtg-traffic-sum index f57e3a9..864f65f 100755 --- a/bin/mrtg-traffic-sum +++ b/bin/mrtg-traffic-sum @@ -33,7 +33,7 @@ BEGIN { use FindBin; use lib "${FindBin::Bin}"; -use lib "${FindBin::Bin}${main::SL}..${main::SL}lib${main::SL}mrtg2"; +use lib "${FindBin::Bin}${main::SL}..${main::SL}@@lib@@${main::SL}mrtg2"; use MRTG_lib "2.090017"; use POSIX qw(mktime); diff --git a/bin/mrtg-traffic-sum.lib64 b/bin/mrtg-traffic-sum.lib64 new file mode 100755 index 0000000..f57e3a9 --- /dev/null +++ b/bin/mrtg-traffic-sum.lib64 @@ -0,0 +1,255 @@ +#!/usr/bin/perl -w + +# Changes in revision 1.9 + +# Added an option for --range, which may be "current" for month-to-date or "previous" for last month +# Changed default date range to 00:00:00 on 1st of month to 23:59:59 at end of month +# Changed the default units to MB, more relevant to non-datacentre users :-) +# Added the option for units=GB, to allow for previous behaviour of this script +# Changed default minimum to 0, to report any info that is available by default +# Updated help section with the above changes, fixed a couple of typos + + +use strict; +use Getopt::Long; +use Pod::Usage; + +BEGIN { + # Automatic OS detection ... do NOT touch + if ( $^O =~ /^(?:(ms)?(dos|win(32|nt)?))/i ) { + $main::OS = 'NT'; + $main::SL = '\\'; + $main::PS = ';'; + } elsif ( $^O =~ /^VMS$/i ) { + $main::OS = 'VMS'; + $main::SL = '.'; + $main::PS = ':'; + } else { + $main::OS = 'UNIX'; + $main::SL = '/'; + $main::PS = ':'; + } +} + +use FindBin; +use lib "${FindBin::Bin}"; +use lib "${FindBin::Bin}${main::SL}..${main::SL}lib${main::SL}mrtg2"; +use MRTG_lib "2.090017"; +use POSIX qw(mktime); + +'$Revision: 1.9 $ ' =~ /Revision: (\S*)/; +my $Revision = $1; + +my $sendmail = "/usr/lib/sendmail"; +$sendmail = "/usr/sbin/sendmail" if -x "/usr/sbin/sendmail"; + +# main loop +my %opt = ( + 'catch' => '(?:description|port name):</td>\s*<td>\s*([^<\s]+[^<]*?)\s*</td', +# 'catch' => 'td>\s*([^<\s]+COLO[^<]*?)\s*</td', + 'min' => 0, + 'range' => "previous", + 'units' => "MB" + ); +sub walklog ($$$$$); +sub sumlog($$$); +sub mailout($$); + +sub main() +{ + # parse options + GetOptions(\%opt, 'min=i','help|h', 'catch=s', 'email=s','version','range=s','units=s') or exit(1); + if($opt{help}) { pod2usage(1) } + if($opt{man}) { pod2usage(-exitstatus => 0, -verbose => 2) } + if($opt{version}) { print "mrtg-traffic-sum $Revision\n"; exit(0) } + my $cfgfile = shift @ARGV; + pod2usage(1) unless $cfgfile and -r $cfgfile; + my %rcfg; + my %cfg; + my @routers; + my @target; + my $report = "Subject: Traffic total for '$cfgfile' ($Revision) "; + readcfg($cfgfile,\@routers,\%cfg,\%rcfg); + cfgcheck(\@routers, \%cfg, \%rcfg, \@target); + walklog \@routers, \%cfg, \%rcfg,\$report,\%opt; + if ($opt{email}) { + mailout \$report,$opt{email}; + } else { + print $report; + } +} + +main; + +sub walklog ($$$$$){ + my $routers = shift; + my $cfg = shift; + my $rcfg = shift; + my $report = shift; + my $opt = shift; + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime(time); + my $start = mktime(0, 0, 0, 1, $mon-1, $year, 0, 0, 0); + my $end = mktime(0, 0, 0, 1, $mon, $year, 0, 0, 0)-1; + if ($opt{range} eq "previous") { + $start = mktime(0, 0, 0, 1, $mon-1, $year, 0, 0, 0); + $end = mktime(0, 0, 0, 1, $mon, $year, 0, 0, 0)-1; + } + if ($opt{range} eq "current") { + $start = mktime(0, 0, 0, 1, $mon, $year, 0, 0, 0); + $end = mktime(0, 0, 0, 1, $mon+1, $year, 0, 0, 0)-1; + } + + my($m,$y) = (localtime($start))[4,5]; + $y += 1900; + $m ++; + $$report .= sprintf "%04d/%02d\n\n", $y,$m; + + $$report .= "Start: ".localtime($start)."\n"; + $$report .= "End: ".localtime($end)."\n"; +if ($opt{'units'} eq "GB") { + $$report .= sprintf "%-40s %15s\n","Interface","In+Out in GB"; +} else { + $$report .= sprintf "%-40s %15s\n","Interface","In+Out in MB"; +} + $$report .= ("-" x 78)."\n"; + foreach my $r (@$routers) { + my $label; + if ($rcfg->{pagetop}{$r} =~ /$opt{catch}/i ) { + $label = $1; + $label .= " $2" if $2; + my ($in,$out) = sumlog $cfg->{logdir}.$rcfg->{directory}{$r}."$r.log",$start,$end; + my $sum = ($in+$out)/(10**6); +if ($opt{'units'} eq "GB") { + $sum = ($in+$out)/(10**9); +} else { + $sum = ($in+$out)/(10**6); +} + next if $sum < $opt{'min'}; + $$report .= sprintf "%-40s %15.0f\n",$label,$sum; + } + } +} + +sub sumlog ($$$) { + my $log = shift; + my $start = shift; + my $end = shift; + my $in = 0.0; + my $out = 0.0; + if (open L, "<",$log){ + my @this=($end+1,0,0); + my @prev; + while (<L>) { + chomp; + @prev = @this; + @this = (split /\s+/)[0,1,2]; + + if ($prev[0] <= $end) { + my $diff = $prev[0] - $this[0]; + $in += $diff * $prev[1]; + $out += $diff * $prev[2]; + } + last if $this[0] <= $start; + } + close L; + } else { + warn "WARN Skipping $log\n"; + } + return $in,$out; +} + +sub mailout ($$) { + my $data = shift; + my $rec = shift; + open S, "|-" or exec $sendmail,'-f',$rec,$rec; + print S <<MESSAGE; +From: $rec +To: $rec +$$data +MESSAGE + close S; + +} +1 + +__END__ + +=head1 NAME + +mrtg-traffic-sum - Builds monthly traffic summary from mrtg log files + +=head1 SYNOPSIS + +B<mrtg-traffic-sum> [I<options>...] B<config-file> + + --man show man-page and exit + -h, --help display this help and exit + --version output version information and exit + --catch=regexp filter out things that match this in PageTop + --email=address email the result + --min=gigabytes minimal number of gigabites required for report + --range=<when> Specify "current" for month-to-date, "previous" for last complete month + --units=[GB|MB] Display results in gigabytes (default is MB) + +=head1 DESCRIPTION + +The mrtg-traffic-sum goes through the mrtg logs for the targets in the +the config file specified and builds the traffic total for the last +month in Giga Bytes. (Note in communications Giga is 10^9). + +The result of this analysis can be sent via email to an address of +your choice using the B<--email> option. + +With the B<--catch> option you can specify a regular expression which +will be matched against the contents of the PageTop settings. The +regular expression should return a value into $1 and possibly into +$2. This will then be used as description for the appropriate traffic +sum. By default the catch is set to: + + (?:description|port name):</td>\s*<td>\s*([^< ]*?[^<]*?)\s*</td + +By default, all traffic which is over one megabyte gets reported. Use the +B<--min> switch to change this number. + +=head1 COPYRIGHT + +Copyright (c) 2002 by Tobias Oetiker. All rights reserved. + +=head1 LICENSE + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +=head1 AUTHOR + +Tobias Oetiker E<lt>tobi@oetiker.chE<gt>, +Keith Dunnett E<lt>keith@dunnett.nameE<gt> + +=head1 HISTORY + + 2002-07-13 to Initial Version + 2009-12-38 kd New features + +=cut + +# Emacs Configuration +# +# Local Variables: +# mode: cperl +# eval: (cperl-set-style "PerlStyle") +# mode: flyspell +# mode: flyspell-prog +# End: +# +# vi: sw=4 et diff --git a/bin/mrtg.lib64 b/bin/mrtg.lib64 new file mode 100755 index 0000000..bc9c316 --- /dev/null +++ b/bin/mrtg.lib64 @@ -0,0 +1,2844 @@ +#! /usr/bin/perl -w +# -*- mode: cperl -*- + +################################################################### +# MRTG 2.17.7 Multi Router Traffic Grapher +################################################################### +# Created by Tobias Oetiker <tobi@oetiker.ch> +# and Dave Rand <dlr@bungi.com> +# +# For individual Contributers check the CHANGES file +# +################################################################### +# +# Distributed under the GNU General Public License +# +################################################################### +my @STARTARGS=($0,@ARGV); + +@main::DEBUG=qw(); +# DEBUG TARGETS +# cfg - watch the config file reading +# dir - directory mangeling +# base - basic program flow +# tarp - target parser +# snpo - snmp polling +# snpo2 - more snmp debug +# coca - confcache operations +# repo - track confcache repopulation +# fork - forking view +# time - some timing info +# log - logging of data via rateup or rrdtool +# eval - trace eval experssions +# prof - add timeing info some interesting bits of code + +$main::GRAPHFMT="png"; +# There older perls tend to behave peculiar with +# large integers ... +require 5.005; + +use strict; +# addon jpt +BEGIN { + # Automatic OS detection ... do NOT touch + if ( $^O =~ /^(ms)?(dos|win(32|nt)?)/i ) { + $main::OS = 'NT'; + $main::SL = '\\'; + $main::PS = ';'; + } elsif ( $^O =~ /^NetWare$/i ) { + $main::OS = 'NW'; + $main::SL = '/'; + $main::PS = ';'; + } elsif ( $^O =~ /^VMS$/i ) { + $main::OS = 'VMS'; + $main::SL = '.'; + $main::PS = ':'; + } elsif ( $^O =~ /^os2$/i ) { + $main::OS = 'OS2'; + $main::SL = '/'; + $main::PS = ';'; + } else { + $main::OS = 'UNIX'; + $main::SL = '/'; + $main::PS = ':'; + } + if ( $ENV{LANG} and $ENV{LANG} =~ /UTF.*8/i ){ + my $args = join " ", map { /\s/ ? "\"$_\"" : $_ } @ARGV; + $args ||= ""; + print <<ERR; +----------------------------------------------------------------------- +ERROR: Mrtg will most likely not work properly when the environment + variable LANG is set to UTF-8. Please run mrtg in an environment + where this is not the case. Try the following command to start: + + env LANG=C ${0} $args +----------------------------------------------------------------------- +ERR + exit 0; + } +} + + +use FindBin; +use lib "${FindBin::Bin}"; +use lib "${FindBin::Bin}${main::SL}..${main::SL}lib${main::SL}mrtg2"; +use Getopt::Long; +use Math::BigFloat; + +# search for binaries in the bin and bin/../lib directory +use MRTG_lib "2.100016"; + +my $NOW = timestamp; + +my $graphiteObj; + +# $SNMP_Session::suppress_warnings = 2; +use locales_mrtg "0.07"; + +# Do not Flash Console Windows for the forked rateup process +BEGIN { + if ($^O eq 'MSWin32'){ + eval {local $SIG{__DIE__};require Win32; Win32::SetChildShowWindow(0)}; + warn "WARNING: $@\n" if $@; + } +} + +$main::STARTTIME = time; + +%main::verified_rrd = (); + +if ($MRTG_lib::OS eq 'OS2') { +# in daemon mode we will pause 3 seconds to be sure that parent died + require OS2::Process; + if (OS2::Process::my_type() eq 'DETACH') {sleep(3);} +} + +if ($MRTG_lib::OS eq 'UNIX') { + $SIG{INT} = $SIG{TERM} = + sub { unlink ${main::Cleanfile} + if defined $main::Cleanfile; + unlink ${main::Cleanfile2} + if defined $main::Cleanfile2; + unlink ${main::Cleanfile3} + if defined $main::Cleanfile3; + warn "$NOW: ERROR: Bailout after SIG $_[0]\n"; + exit 1; + }; + $SIG{HUP} = sub { + unlink ${main::Cleanfile} + if defined $main::Cleanfile; + unlink ${main::Cleanfile2} + if defined $main::Cleanfile2; + unlink ${main::Cleanfile3} + if defined $main::Cleanfile3; + die "$NOW: ERROR: Bailout after SIG $_[0]\n"; + }; +} + + +END { + local($?, $!); + unlink ${main::Cleanfile} if defined $main::Cleanfile; + unlink ${main::Cleanfile2} if defined $main::Cleanfile2; +} + +&main; + +exit(0); + +#### Functions ################################################ + +sub main { + + + # read in the config file + my @routers; + my %cfg; + my %rcfg; + my %opts; + my $EXITCODE = 0; + + GetOptions(\%opts, 'user=s', 'group=s', 'lock-file=s','confcache-file=s','logging=s', 'check', 'fhs', 'daemon', 'pid-file=s','debug=s', 'log-only') or die "Please use valid Options\n"; + + if (defined $opts{debug}){ + @main::DEBUG = split /\s*,\s*/, $opts{debug}; + if (defined $SNMP_util::Debug){ + $SNMP_util::Debug = 1 if grep /^snpo2$/, @main::DEBUG; + } + } + if (grep /^prof$/, @main::DEBUG){ + require Time::HiRes; + eval "sub gettimeofday() {return Time::HiRes::time()}"; + # note this will crash if the module is missing + # so only use the --debug=prof if you have Time::HiRes installed + } else { + eval "sub gettimeofday() {return time()}"; + } + debug 'time', "prog start ".localtime(time); + + my $uid = $<; + my $gid = $(; + + if (defined $opts{group}) { + $gid = getgrnam($opts{group}); + die "$NOW: ERROR: Unknown Group: $opts{group})\n" if not defined $gid; + } + + if (defined $opts{user}) { + $uid = getpwnam($opts{user}); + die "$NOW: ERROR: Unknown User: $opts{user})\n" if not defined $uid; + } + + # If we've specified using FHS (http://www.pathname.com/fhs/) on the command line, + # use the relevant path definitions (can be overridden later): + + my $confcachefile; + my $pidfile; + my $lockfile; + my $templock; + my $logfile; + + if (defined $opts{"fhs"}) { + $confcachefile = "/var/cache/mrtg/mrtg.ok"; + $pidfile = "/var/run/mrtg.pid"; + $lockfile = "/var/cache/mrtg/mrtg.lck"; + $templock = "/var/cache/mrtg/mrtg.lck.$$"; + $logfile = "/var/log/mrtg.log"; + } + + my $cfgfile = shift @ARGV; + + if ( !defined $cfgfile and -r "/etc/mrtg.cfg" ) { $cfgfile = "/etc/mrtg.cfg"; } + + printusage() unless defined $cfgfile; + + # PID file code, used later if daemonizing... + if ( !defined($pidfile) ) { + $pidfile = $cfgfile; + $pidfile =~ s/\.[^.\/]+$//; + $pidfile .= '.pid'; + } + $pidfile = $opts{"pid-file"} || $pidfile; + + # Run as a daemon, specified on command line (required for FHS compliant daemon) + if (defined $opts{"daemon"}) { + # Create a pidfile, then chown it so we can use it once we change user + &create_pid($pidfile); + chown $uid, $gid, $pidfile; + } + + ($(,$)) = ($gid,$gid) ; + ($<,$>) = ($uid,$uid) ; + die "$NOW: ERROR failed to set UID to $uid\n" unless ($< == $uid and $> == $uid); + + $logfile = $opts{logging} || $logfile; + if (defined $logfile){ + setup_loghandlers $logfile; + warn "Started mrtg with config \'$cfgfile\'\n"; + } + + # lets make sure that there are not two mrtgs running in parallel. + # so we lock on the cfg file. Nothing fancy, just a lockfile + + $lockfile = $opts{"lock-file"} || $lockfile; + + if (! defined $lockfile) { + $lockfile = $cfgfile."_l"; + } + if (! defined $templock) { + $templock = $lockfile."_" . $$ ; + } + + debug('base', "Creating Lockfiles $lockfile,$templock"); + &lockit($lockfile,$templock); + + debug('base', "Reading Config File: $cfgfile"); + my $cfgfile_age = -M $cfgfile; + readcfg($cfgfile,\@routers,\%cfg,\%rcfg); + + imggen($cfg{icondir} || $cfg{imagedir} || $cfg{workdir}); + + # Enable or disable snmpv3 + if(defined $cfg{enablesnmpv3}) { + $cfg{enablesnmpv3} = lc($cfg{enablesnmpv3}); + } else { + $cfg{enablesnmpv3} = 'no'; + } + + if ($cfg{threshmailserver}) { + if (eval {local $SIG{__DIE__};require Net::SMTP;}) { + import Net::SMTP; + debug('base', "Loaded Net::SMTP module for ThreshMail."); + } else { + die "$NOW: WARNING: Can't load Net::SMTP module. This is required for ThreshMail."; + } + } + # Check we have the necessary libraries for IPv6 support + if ($cfg{enablesnmpv3} eq 'yes') { + if (eval {local $SIG{__DIE__};require Net_SNMP_util;}) { + import Net_SNMP_util; + debug('base', "SNMP V3 libraries found, SNMP V3 enabled."); + } else { + warn "$NOW: WARNING: SNMP V3 libraries not found, SNMP V3 disabled.\n"; + $cfg{enablesnmpv3} = 'no'; + require SNMP_util; + import SNMP_util; + } + } + else { # load V1/V2 libraries + require SNMP_util; + import SNMP_util; + } + + + # Enable or disable IPv6 + if(defined $cfg{enableipv6}) { + $cfg{enableipv6} = lc($cfg{enableipv6}); + } else { + $cfg{enableipv6} = 'no'; + } + + # Check we have the necessary libraries for IPv6 support + if ($cfg{enableipv6} eq 'yes') { + if ( eval {local $SIG{__DIE__};require Socket; require Socket6; require IO::Socket::INET6;}) { + import Socket; + import Socket6; + debug('base', "IPv6 libraries found, IPv6 enabled."); + } else { + warn "$NOW: WARNING: IPv6 libraries not found, IPv6 disabled.\n"; + $cfg{enableipv6} = 'no'; + } + } + + # Module invocation for sending a copy of the time series data to graphite. + if(defined $cfg{sendtographite}) { + require Net::Graphite; + } + + # from our last run we kept some info about + # the configuration of our devices around + debug('base', "Reading Interface Config cache"); + $confcachefile = $opts{"confcache-file"} || $confcachefile; + if ( !defined($confcachefile) ) { + $confcachefile = $cfgfile; + $confcachefile =~ s/\.[^.\/]+$//; + $confcachefile .= ".ok"; + } + my $confcache = readconfcache($confcachefile); + + # Check the config and create the target object + debug('base', "Checking Config File"); + my @target; + cfgcheck(\@routers, \%cfg, \%rcfg, \@target, \%opts); + + # exit here if we only check the config file + # in case of an error, cfgcheck() already exited + if (defined $opts{check}) { + debug('base', "Remove Lock Files"); + close LOCK; unlink ($templock, $lockfile); + debug('base', "Exit after successful config file check"); + exit 0; + } + + # postload rrdtool support + if ($cfg{logformat} eq 'rrdtool'){ + debug('base', "Loading RRD support"); + require 'RRDs.pm'; + } + + # set the locale + my $LOC; + if ( $cfg{'language'} and defined($lang2tran::LOCALE{"\L$cfg{'language'}\E"})) { + debug('base', "Loading Locale for ".$cfg{'language'}); + $LOC=$lang2tran::LOCALE{"\L$cfg{'language'}\E"}; + } else { + debug('base', "Loading default Locale"); + $LOC=$lang2tran::LOCALE{'default'}; + } + + # Daemon Code + my $last_time=0; + my $curent_time; + my $sleep_time; + if (defined $opts{"daemon"}) { $cfg{'runasdaemon'} = "yes"; } + &demonize_me($pidfile,$cfgfile) if defined $cfg{'runasdaemon'} and $cfg{'runasdaemon'} =~ /y/i and $MRTG_lib::OS ne 'VMS' + and not (defined $cfg{'nodetach'} and $cfg{'nodetach'} =~ /y/i); + # auto restart on die if running as demon + + $SIG{__DIE__} = sub { + warn $_[0]; + warn "*** Restarting after 10 seconds in an attempt to recover from the error above\n"; + sleep 10; + exec @STARTARGS; + } if $cfg{'runasdaemon'}; + + debug('base', "Starting main Loop"); + do { # Do this loop once for native mode and forever in daemon mode + my $router; + $NOW = timestamp; # get the time + debug 'time', "loop start ".localtime(time); + + #if we run as daemon, we sleep in between collection cycles + $sleep_time= (int($cfg{interval}*60))-(time-$last_time); + if ($sleep_time > 0 ) { #If greater than 0 the sleep that amount of time + debug('time', "Sleep time $sleep_time seconds"); + sleep ($sleep_time); + } elsif ($last_time > 0) { + warn "$NOW: WARNING: data collection did not complete within interval!\n"; + } + $last_time=time; + + # set meta expires if there is an index file + # 2000/05/03 Bill McGonigle <bill@zettabyte.net> + if (defined $cfg{'writeexpires'}) { + my $exp = &expistr($cfg{'interval'}); + my $fil; + $fil = "$cfg{'htmldir'}index.html" if -e "$cfg{'htmldir'}index.html"; + $fil = "$cfg{'htmldir'}index.htm" if -e "$cfg{'htmldir'}index.htm"; + if (defined $fil) { + open(META, ">$fil.meta"); + print META "Expires: $exp\n"; + close(META); + } + } + + + # Use SNMP to populate the target object + debug('base', "Populate Target object by polling SNMP and". + " external Datasources"); + debug 'time', "snmp read start ".localtime(time); + readtargets($confcache,\@target, \%cfg); + + $NOW = timestamp; # get the time + # collect data for each router or pseudo target (`executable`) + debug 'time', "target loop start ".localtime(time); + foreach $router (@routers) { + debug('base', "Act on Router/Target $router"); + if (defined $rcfg{'setenv'}{$router}) { + my $line = $rcfg{'setenv'}{$router}; + while ( $line =~ s/([^=]+)=\"([^\"]*)\"\s*// ) # " - unconfuse the highliter + { + $ENV{$1}=$2; + } + } + my($savetz) = $ENV{'TZ'}; + if (defined $rcfg{'timezone'}{$router}) { + $ENV{'TZ'} = $rcfg{'timezone'}{$router}; + if ( $main::OS eq 'UNIX' ){ + require 'POSIX.pm'; + POSIX::tzset(); + } + } + + my ($inlast, $outlast, $uptime, $name, $time) = + getcurrent(\@target, $router, \%rcfg, \%cfg); + + if ( defined($inlast) and defined($outlast)) { + $EXITCODE = $EXITCODE | 1; + } + else { + $EXITCODE = $EXITCODE | 2; + } + + debug('base', "Get Current values: in:".( defined $inlast ? $inlast : "undef").", out:". + ( defined $outlast? $outlast : "undef").", up:". + ( defined $uptime ? $uptime : "undef").", name:". + ( defined $name ? $name : "undef").", time:". + ( defined $time ? $time : "undef")); + + #abort, if the router is not responding. + if ($cfg{'logformat'} ne 'rrdtool') { + # undefined values are ok for rrdtool ! + #if ( not defined $inlast or not defined $outlast){ + # warn "$NOW: WARNING: Skipping Update of $router, inlast is not defined\n" + # unless defined $inlast; + # warn "$NOW: WARNING: Skipping Update of $router, outlast is not defined\n" + # unless defined $outlast; + # next; + #} + + if (defined $inlast and $inlast < 0) { + $inlast += 2**31; + # this is likely to be a broken snmp counter ... lets compensate + } + if (defined $outlast and $outlast < 0) { + $outlast += 2**31; + # this is likely to be a broken snmp counter ... lets compensate + } + } + + my ($maxin, $maxout, $maxpercent, $avin, $avout, $avpercent,$avmxin, $avmxout, + $cuin, $cuout, $cupercent); + debug('base', "Create Graphics"); + if ($rcfg{'options'}{'dorelpercent'}{$router}) { + ($maxin, $maxout, $maxpercent, $avin, $avout, $avpercent, + $cuin, $cuout, $cupercent, $avmxin, $avmxout) = + writegraphics($router, \%cfg, \%rcfg, $inlast, $outlast, $time,$LOC, \%opts); + } else { + ($maxin, $maxout ,$avin, $avout, $cuin, $cuout, $avmxin, $avmxout) = + writegraphics($router, \%cfg, \%rcfg, $inlast, $outlast, $time,$LOC, \%opts); + } + # skip this update if we did not get anything usefull out of + # writegraphics + next if not defined $maxin; + + debug('base', "Check for Thresholds"); + threshcheck(\%cfg,\%rcfg,$cfgfile,$router,$cuin,$cuout); + + if (defined $opts{'log-only'}){ + debug('base', "Disable Graph and HTML generation"); + } + + if ($cfg{logformat} eq 'rateup' and not defined $opts{'log-only'} ){ + debug('base', "Check for Write HTML Pages"); + writehtml($router, \%cfg, \%rcfg, + $maxin, $maxout, $maxpercent, $avin, $avout, $avmxin, $avmxout, $avpercent, + $cuin, $cuout, $cupercent, $uptime, $name, $LOC) + } + + # + clonedirectory($router,\%cfg, \%rcfg); + # + + #put TZ things back in shape ... + if ($savetz) { + $ENV{'TZ'} = $savetz; + } else { + delete $ENV{'TZ'}; + } + if ( $main::OS eq 'UNIX' ){ + require 'POSIX.pm'; + POSIX::tzset(); + }; + } + # Has the cfg file been modified since we started? if so, reload it. + if ( -M $cfgfile < $cfgfile_age and + $cfg{'runasdaemon'} and $cfg{'runasdaemon'} =~ /y/i ) { + # reload the configuration + $cfgfile_age = -M $cfgfile; + debug('base', "Re-reading Config File: $cfgfile"); + @routers = (); %cfg = (); %rcfg = (); + readcfg($cfgfile,\@routers,\%cfg,\%rcfg); + cfgcheck(\@routers, \%cfg, \%rcfg, \@target, \%opts); + } + debug('base', "End of main Loop"); + } while ($cfg{'runasdaemon'} and $cfg{'runasdaemon'} =~ /y/i ); #In daemon mode run forever + debug('base', "Exit main Loop"); + # OK we are done, remove the lock files ... + + debug('base', "Remove Lock Files"); + close LOCK; unlink ($templock, $lockfile); + + debug('base', "Store Interface Config Cache"); + delete $$confcache{___updated} if exists $$confcache{___updated}; # make sure everything gets written out not only the updated entries + writeconfcache($confcache,$confcachefile); + + if ( ! $cfg{'runasdaemon'} or $cfg{'runasdaemon'} !~ /y/i ) { + if ( ($EXITCODE & 1) and ($EXITCODE & 2) ) { + # At least one target was sucessful + exit 91; + } + elsif ( not ($EXITCODE & 1) and ($EXITCODE & 2) ) { + # All targets failed + exit 92; + } + } +} + +# ( $inlast, $outlast, $uptime, $name, $time ) = +# &getcurrent( $target, $rou, $rcfg, $cfg ) +# Calculate monitored data for device $rou based on information in @$target +# and referring to configuration data in %$rcfg and %$cfg. In the returned +# list, $inlast and $outlast are the input and output monitored data values, +# $uptime is the device uptime, $name is the device name, and $time is the +# current time when the calculation was performed. +sub getcurrent { + my( $target, $rou, $rcfg, $cfg ) = @_; + # Hash indexed by $mode for conveniently saving $inlast and $outlast + my %last; + # Initialize uptime, device name, and data collection time to empty strings + my $uptime = ''; + my $name = ''; + my $time = ''; + + # Calculate input and output monitored data + foreach my $mode( qw( _IN_ _OUT_ ) ) { + # Initialize monitored data, warning message, and death message + # to empty strings + my $data; + my $warning; + my $death; + { + # Code block used to calculate monitoring data + # Localize warning and death exception handlers to capture + # error message less any leading and trailing white space + local $SIG{ __WARN__ } = + sub { $_[0] =~ /^\s*(.+?)\s*$/; $warning = $1; }; + local $SIG{ __DIE__ } = + sub { $_[0] =~ /^\s*(.+?)\s*$/; $death = $1; }; + # Calculate monitoring data. $rcfg->{ target }{ $rou } contains + # a Perl expression for the calculation. + $data = eval "$rcfg->{target}{$rou}"; + } + # Test for various exceptions occurring in the calculation + if( $warning ) { + warn "$NOW: ERROR: Target[$rou][$mode] '$$rcfg{target}{$rou}' (warn): $warning\n"; + $data = undef; + } elsif( $death ) { + warn "$NOW: ERROR: Target[$rou][$mode] '$$rcfg{target}{$rou}' (kill): $death\n"; + $data = undef; + } elsif( $@ ) { + warn "$NOW: ERROR: Target[$rou][$mode] '$$rcfg{target}{$rou}' (eval): $@\n"; + $data = undef; + } elsif( not defined $data ) { + warn "$NOW: ERROR: Target[$rou][$mode] '$$rcfg{target}{$rou}' did not eval into defined data\n"; + $data = undef; + } elsif( $data and $data !~ /^[-+]?\d+(\.\d*)?([eE][+-]?[0-9]+)?$/ ) { + warn "$NOW: ERROR: Target[$rou][$mode] '$$rcfg{target}{$rou}' evaluated to '$data' instead of a number\n"; + $data = undef; + } elsif( length( $data ) > 190 ) { + warn "$NOW: ERROR: $mode value: '$data' is way to long ...\n"; + $data = undef; + } else { + # At this point data is considered valid. Round to an integer + # unless RRDTool is in use and this is a gauge + if (not ( $cfg->{ logformat } eq 'rrdtool' + and defined $rcfg->{ options }{ gauge }{ $rou })){ + if (ref $data and ref $data eq 'Math::BigFloat') { + $data->ffround( 0 ) + } else { + $data = sprintf "%.0f", $data; + } + } + # Remove any leading plus sign + $data =~ s/^\+//; + } + $last{ $mode } = $data; + } + + # Set $u to the unique index of the @$target array referred to in the + # monitored data calculation for the current device. $u will be set to + # -1 if that index is not unique. + my $u = $rcfg->{ uniqueTarget }{ $rou }; + + # Get the uptime, device name, and data collection time from the @$target + # array if the monitored data calculation refers only to one target. + # Otherwise it doesn't make sense to do this. + if( $u >= 0 ) { + $uptime = $target->[ $u ]{ _UPTIME_ }; + $name = $target->[ $u ]{ _NAME_ }; + $time = $target->[ $u ]{ _TIME_ }; + + if ($time =~ /^([-0-9.]+)$/) { + $time = $1; + } + + } + + # Set the time to the current time if it was not set above + $time = time unless $time; + + # Cache uptime location for reading name + my( $uploc ); + + # Get the uptime and device name from the alternate location (community@host or + # (OID:community@host or OID) that may have been specified with the RouterUptime + # target keyword + if( defined $rcfg->{ routeruptime }{ $rou } ) { + my( $noid, $nloc ) = split( /:/, $rcfg->{ routeruptime }{ $rou }, 2 ); + # If only location (community@host) was specified then + # move the location details into the right place + if( $noid =~ /@/ ) { + $nloc = $noid; + $noid = undef; + } + # If no OID (community@host) was specified use the hardcoded default + if( not $noid ) { + $noid = 'sysUptime'; + } + # If no location (community@host) was specified use values from the + # unique target referred to in the monitored data calculation + if( not $nloc ){ + if ($u >= 0) { + my $comm = $target->[ $u ]{ Community }; + my $host = $target->[ $u ]{ Host }; + my $opt = $target->[ $u ]{ SnmpOpt }; + $nloc = "$comm\@$host$opt"; + } else { + die "$NOW: ERROR: You must specify the location part of the RouterUptime oid for non unique targets! ($rou)\n"; + } + } + + $uploc = $nloc; + # Get the device uptime if $noid(OID) and $nloc (community@host) have been specified + # one way or the other + debug('base', "Fetching sysUptime and sysName from: $noid:$nloc"); + ( $uptime, $name ) = snmpget( $uploc, $rcfg->{ snmpoptions }{ $rou }, $noid, 'sysName'); + } + + # Get the device name from the alternate location (OID or + # OID:community@host) that may have been specified with the RouterName + # target keyword + if( defined $rcfg->{ routername }{ $rou } ) { + my( $noid, $nloc ) = split( /:/, $rcfg->{ routername }{ $rou }, 2 ); + # If no location (community@host) was specified use values from the + # unique target referred to in the monitored data calculation + if( $u >= 0 and not $nloc ) { + my $comm = $target->[ $u ]{ Community }; + my $host = $target->[ $u ]{ Host }; + my $opt = $target->[ $u ]{ SnmpOpt }; + $nloc = "$comm\@$host$opt"; + } + # Get the location from the RouterUptime keyword if that is defined + # and $nloc has not otherwise been specified + $nloc = $uploc if $uploc and not $nloc; + # Get the device name if $nloc (community@host) has been specified + # one way or the other + debug('base', "Fetching sysName from: $noid:$nloc"); + ( $name ) = snmpget( $nloc, $$rcfg{snmpoptions}{ $rou }, $noid ) if $nloc; + } + + return ( $last{ _IN_ }, $last{ _OUT_ }, $uptime, $name, $time ); +} + +sub rateupcheck ($) { + my $router = shift; + if ($?) { + my $value = $?; + my $signal = $? & 127; #ignore the most significant bit + #as it is always one when it is a returning + #child says dave ... + if (($MRTG_lib::OS ne 'UNIX') || ($signal != 127)) { + my $exitval = $? >> 8; + warn "$NOW: WARNING: rateup died from Signal $signal\n". + " with Exit Value $exitval when doing router '$router'\n". + " Signal was $signal, Returncode was $exitval\n" + } + } +} + +sub clonedirectory { + my($router,$cfg, $rcfg) = @_; + require File::Copy; + import File::Copy; + + return unless ( $$rcfg{'clonedirectory'}{$router} ); + + my ($clonedstdir, $clonedsttarget ,$srcname, $dstname); + + ($clonedstdir, $clonedsttarget ) = split (/,|\s+/, $$rcfg{'clonedirectory'}{$router}) if ( $$rcfg{'clonedirectory'}{$router} =~ /,|\S\s+\S/ ); + + if ( defined $clonedsttarget ) { + $clonedsttarget =~ s/\s+//; + $clonedsttarget = lc($clonedsttarget); + } else { + $clonedstdir = $$rcfg{'clonedirectory'}{$router}; + } + if ( $$rcfg{'directory'}{$router} ne $clonedstdir) { + $clonedstdir =~ s/\s+$//; + $clonedstdir .= "/" unless ($clonedstdir =~ /\/$/); + my $fullpathsrcdir = "$$cfg{'logdir'}$$rcfg{'directory'}{$router}"; + my $fullpathdstdir = "$$cfg{'logdir'}$clonedstdir"; + + die "$NOW: ERROR: Destination dir: $fullpathdstdir not found for cloning process\n" unless ( -e $fullpathdstdir ); + die "$NOW: ERROR: Destination dir: $fullpathdstdir is not a directory destination for cloning process\n" unless ( -d $fullpathdstdir ); + die "$NOW: ERROR: Destination dir: $fullpathdstdir is not writeable for cloning process\n" unless ( -w $fullpathdstdir ); + + if ( defined $clonedsttarget ) { + debug('base', "Clone directory $fullpathsrcdir to $fullpathdstdir " . + "renaming target $router to $clonedsttarget"); + } else { + debug('base', "Clone directory $fullpathsrcdir to $fullpathdstdir"); + } + + foreach my $srcfile (<$fullpathsrcdir$router\[.-\]*>) { + debug('base', "copying $srcfile $fullpathdstdir"); + copy("$srcfile","$fullpathdstdir") or warn "$NOW: WARNING: Cloning $srcfile to $fullpathdstdir unsuccessful; $!\n"; + if ($srcfile =~ /\.html/i) { + debug('base', "altering $fullpathdstdir/$router.$$rcfg{'extension'}{$router}"); + # + my $dirrel = "../" x ($$rcfg{'clonedirectory'}{$router} =~ tr|/|/|); + # + debug('base', "dirrel $dirrel $clonedstdir"); + open(HTML,"$fullpathdstdir/$router.$$rcfg{'extension'}{$router}"); + my @CLONEHTML = <HTML>; + close(HTML); + foreach ( @CLONEHTML ) { + if ( defined $clonedsttarget and /$router/ ) { + debug('base', "altering $router to $clonedsttarget in html file"); + s/$router/$clonedsttarget/i; + } + if ( /SRC=/i and /$$rcfg{'directory'}{$router}/ ) { + debug('base', "altering from $_"); + s|(\.\./)+|$dirrel|; + s|$$rcfg{'directory'}{$router}|$clonedstdir|; + debug('base', "altering to $_"); + } + } + open(HTML,">$fullpathdstdir/$router.$$rcfg{'extension'}{$router}"); + print HTML $_ for ( @CLONEHTML ); + close(HTML); + } + if ( defined $clonedsttarget ) { + $srcfile =~ /.+\/(.+)?$/; + $srcname = $1; + $dstname = $srcname; + $dstname =~ s/$router/$clonedsttarget/; + debug('base', "Clone renaming $srcname to $dstname at $fullpathdstdir"); + rename("$fullpathdstdir/$srcname","$fullpathdstdir/$dstname") or + warn "$NOW: WARNING: Renaming $fullpathdstdir/$srcname to $fullpathdstdir/$dstname unsuccessful; $!\n"; + } + } + } else { + warn "$NOW: WARNING: Cloning to the same place suspended. ; $!\n"; + } +} + +sub writegraphics { + my($router, $cfg, $rcfg, $inlast, $outlast, $time,$LOC, $opts) = @_; + + my($absmax,$maxv, $maxvi, $maxvo, $i, $period, $res); + my(@exec, @mxvls, @metas); + my(%maxin, %maxout, %maxpercent, %avin, %avout, %avmxin, %avmxout, %avpercent, %cuin, %cuout, %cupercent); + my($rrdinfo); + + @metas = (); + $maxvi = $$rcfg{'maxbytes1'}{$router}; + $maxvo = $$rcfg{'maxbytes2'}{$router}; + if ($maxvi > $maxvo) { + $maxv = $maxvi; + } else { + $maxv = $maxvo; + } + $absmax = $$rcfg{'absmax'}{$router}; + $absmax = $maxv unless defined $absmax; + if ($absmax < $maxv) { + die "$NOW: ERROR: AbsMax: $absmax is smaller than MaxBytes: $maxv\n"; + } + + + # select whether the datasource gives relative or absolute return values. + my $up_abs="u"; + $up_abs='m' if defined $$rcfg{'options'}{'perminute'}{$router}; + $up_abs='h' if defined $$rcfg{'options'}{'perhour'}{$router}; + $up_abs='d' if defined $$rcfg{'options'}{'derive'}{$router}; + $up_abs='a' if defined $$rcfg{'options'}{'absolute'}{$router}; + $up_abs='g' if defined $$rcfg{'options'}{'gauge'}{$router}; + + my $dotrrd = "$$cfg{'logdir'}$$rcfg{'directory'}{$router}$router.rrd"; + my $dotlog = "$$cfg{'logdir'}$$rcfg{'directory'}{$router}$router.log"; + my $reallog = $$cfg{logformat} eq 'rrdtool' ? $dotrrd : $dotlog; + + if (defined $$cfg{maxage} and -e $reallog and time()-$$cfg{maxage} > (stat($reallog))[9]){ + warn "$NOW: ERROR: skipping update of $router. As $reallog is older than MaxAge ($$cfg{maxage} s)\n"; + return undef; + } + + if ($$cfg{logformat} eq 'rrdtool') { + debug('base',"start RRDtool section"); + # make sure we got some sane default here + my %dstype = qw/u COUNTER a ABSOLUTE g GAUGE h COUNTER m COUNTER d DERIVE/; + $up_abs = $dstype{$up_abs}; + # update the database. + + # set minimum/maximum values. use 'U' if we cannot get good values + # the lower bound is hardcoded to 0 + my $absi = $maxvi; + my $abso = $maxvo; + $absi = $abso = $$rcfg{'absmax'}{$router} + if defined $$rcfg{'absmax'}{$router}; + debug('base',"maxi:$absi, maxo:$abso"); + $absi = 'U' if $absi == 0; + $abso = 'U' if $abso == 0; + # check to see if we already have an RRD file or have to create it + # maybe we can convert an .log file to the new rrd format + if( $RRDs::VERSION >= 1.4 and $$cfg{rrdcached} and $$cfg{rrdcached} !~ /^unix:/ ) { + # rrdcached in network mode. No log conversion possible. + # In this mode, we cannot use absolute paths. So, we strip logdir from the name. + $dotrrd = "$$rcfg{'directory'}{$router}$router.rrd"; + if( $RRDs::VERSION < 1.49 ) { + # This version of RRD doesnt support info, create and tune + debug('base',"Unable to verify RRD file with this version of rrdcached"); + if( !$main::verified_rrd{$dotrrd} ) { + warn "WARN: Unable to verify $dotrrd with this version of RRDTool\n"; + $main::verified_rrd{$dotrrd} = 1; + } + } elsif( !$main::verified_rrd{$dotrrd} ) { + # Test to see if it exists + debug('base',"Attempting to verify RRD file via rrdcached"); + $rrdinfo = RRDs::info($dotrrd,'--daemon',$$cfg{rrdcached},'--noflush'); + if(!$rrdinfo) { # doesnt exist, or cannot be accessed + my $e = RRDs::error(); + warn "$NOW: Cannot access $dotrrd; will attempt to (re)create it: $e\n" if $e; + + # don't fail if interval is not set + my $interval = $$cfg{interval}; + my $minhb = int($$cfg{interval} * 60)*2; + $minhb = 600 if ($minhb <600); + my $rows = $$rcfg{'rrdrowcount'}{$router} || int( 4000 / $interval); + my $rows30m = $$rcfg{'rrdrowcount30m'}{$router} || 800; + my $rows2h = $$rcfg{'rrdrowcount2h'}{$router} || 800; + my $rows1d = $$rcfg{'rrdrowcount1d'}{$router} || 800; + my @args = ($dotrrd, '-b', $time-10, '-s', int($interval * 60), + "DS:ds0:$up_abs:$minhb:0:$absi", + "DS:ds1:$up_abs:$minhb:0:$abso", + "RRA:AVERAGE:0.5:1:$rows", + ( $interval < 30 ? ("RRA:AVERAGE:0.5:".int(30/$interval).":".$rows30m):()), + "RRA:AVERAGE:0.5:".int(120/$interval).":".$rows2h, + "RRA:AVERAGE:0.5:".int(1440/$interval).":".$rows1d, + "RRA:MAX:0.5:1:$rows", + ( $interval < 30 ? ("RRA:MAX:0.5:".int(30/$interval).":".$rows30m):()), + "RRA:MAX:0.5:".int(120/$interval).":".$rows2h, + "RRA:MAX:0.5:".int(1440/$interval).":".$rows1d); + # do we have holt winters rras defined here ? + if (defined $$rcfg{'rrdhwrras'} and defined $$rcfg{'rrdhwrras'}{$router}){ + push @args, split(/\s+/, $$rcfg{'rrdhwrras'}{$router}); + } + push @args,"--daemon", $$cfg{rrdcached}; + + debug('base',"create $dotrrd via rrdcached"); + debug('log', "RRDs::create(".join(',',@args).")"); + RRDs::create(@args); + $e = RRDs::error(); + die "$NOW: ERROR: Cannot create RRD ".join(',',@args)."- $e\n" if $e; + } else { + # Does the RRD file need to be tuned? + if( + ($rrdinfo->{"ds[ds0].max"} != $absi) + ||($rrdinfo->{"ds[ds1].max"} != $abso) + ||($rrdinfo->{"ds[ds0].type"} ne $up_abs) + ||($rrdinfo->{"ds[ds1].type"} ne $up_abs) + ) { + debug('base',"RRD file needs to be tuned"); + warn "$NOW: RRDFile $dotrrd needs to be tuned but cannot do this remotely.\n"; + } + } + $main::verified_rrd{$dotrrd} = 1; + } else { + debug('base',"No need to verify this file again"); + } + } elsif (-e $dotlog and not -e $dotrrd) { + debug('base',"converting $dotlog to RRD format"); + if(defined $RRDs::VERSION and $RRDs::VERSION < 1.000271){ + die "$NOW: ERROR: RRDtool version 1.0.27 or later required to perform log2rrd conversion\n"; + } + log2rrd($router,$cfg,$rcfg); + } elsif (! -e $dotrrd) { + #nope it seems we have to create a new one + debug('base',"create $dotrrd"); + # create the rrd if it doesn't exist + # don't fail if interval is not set + my $interval = $$cfg{interval}; + my $minhb = int($$cfg{interval} * 60)*2; + $minhb = 600 if ($minhb <600); + my $rows = $$rcfg{'rrdrowcount'}{$router} || int( 4000 / $interval); + my $rows30m = $$rcfg{'rrdrowcount30m'}{$router} || 800; + my $rows2h = $$rcfg{'rrdrowcount2h'}{$router} || 800; + my $rows1d = $$rcfg{'rrdrowcount1d'}{$router} || 800; + my @args = ($dotrrd, '-b', $time-10, '-s', int($interval * 60), + "DS:ds0:$up_abs:$minhb:0:$absi", + "DS:ds1:$up_abs:$minhb:0:$abso", + "RRA:AVERAGE:0.5:1:$rows", + ( $interval < 30 ? ("RRA:AVERAGE:0.5:".int(30/$interval).":".$rows30m):()), + "RRA:AVERAGE:0.5:".int(120/$interval).":".$rows2h, + "RRA:AVERAGE:0.5:".int(1440/$interval).":".$rows1d, + "RRA:MAX:0.5:1:$rows", + ( $interval < 30 ? ("RRA:MAX:0.5:".int(30/$interval).":".$rows30m):()), + "RRA:MAX:0.5:".int(120/$interval).":".$rows2h, + "RRA:MAX:0.5:".int(1440/$interval).":".$rows1d); + # do we have holt winters rras defined here ? + if (defined $$rcfg{'rrdhwrras'} and defined $$rcfg{'rrdhwrras'}{$router}){ + push @args, split(/\s+/, $$rcfg{'rrdhwrras'}{$router}); + } + + debug('log', "RRDs::create(".join(',',@args).")"); + RRDs::create(@args); + my $e = RRDs::error(); + die "$NOW: ERROR: Cannot create RRD ".join(',',@args)."- $e\n" if $e; + } elsif ( -M $dotrrd > 0 ) { + # update the minimum/maximum according to maxbytes/absmax + # and (re)set the data-source-type to reflect cfg changes + # cost: 1 read/write cycle, but update will reuse the buffered data + # in daemon mode this will only happen in the first round + my @args = ($dotrrd, '-a', "ds0:$absi", '-a', "ds1:$abso", + '-d', "ds0:$up_abs", '-d', "ds1:$up_abs"); + debug('log', "RRDs::tune(@args)"); + my $start = gettimeofday(); + RRDs::tune(@args); + debug('prof',sprintf("RRDs::tune $dotrrd - %.3fs",gettimeofday()-$start)); + my $e = RRDs::error(); + warn "$NOW: ERROR: Cannot tune logfile: $e\n" if $e; + } + # update the rrd + $inlast = 'U' unless defined $inlast and $inlast =~ /\S/ and $inlast ne '##UNDEF##'; + $outlast = 'U' unless defined $outlast and $outlast =~ /\S/ and $outlast ne '##UNDEF##'; + debug('log', "RRDs::update($dotrrd, '$time:$inlast:$outlast')"); + my $start = gettimeofday(); + my $rrddata = 0; + if ( $RRDs::VERSION >= 1.4 and $$cfg{rrdcached} ){ + RRDs::update($dotrrd, '--daemon', $$cfg{rrdcached}, "$time:$inlast:$outlast"); + debug('prof',sprintf("RRDs::update $dotrrd (rrdcached) - %.3fs",gettimeofday()-$start)); + $rrddata = \{ dummy => "" }; + } elsif ( $RRDs::VERSION >= 1.2 ){ + $rrddata=RRDs::updatev($dotrrd, "$time:$inlast:$outlast"); + debug('prof',sprintf("RRDs::updatev $dotrrd - %.3fs",gettimeofday()-$start)); + } else { + RRDs::update($dotrrd, "$time:$inlast:$outlast"); + debug('prof',sprintf("RRDs::update $dotrrd - %.3fs",gettimeofday()-$start)); + } + my $e = RRDs::error(); + warn "$NOW: ERROR: Cannot update $dotrrd with '$time:$inlast:$outlast' $e\n" if ($e); + + + # Send a copy of the time series data to graphite + if(defined $$cfg{sendtographite}) { + my @a = split ",",$$cfg{sendtographite}; + if (not $graphiteObj) { + $graphiteObj = Net::Graphite->new( + host => $a[0], + port => $a[1], + trace => 0, + proto => "tcp", + timeout => 1, + fire_and_forget => 1, + return_connect_error => 0 + ); + if($graphiteObj->connect) { + debug('log',"successfully opened graphite socket ($graphiteObj)\n"); + } else { + debug('log',"graphite connect error: $!\n"); + } + } + + # make a copy of the router name for use in the graphite namespace + my $graphiterouter = $router; + # periods are delimiters in the graphite namespace so change them to underscores + $graphiterouter =~ s/_/\./g; + # commas are not allowed + $graphiterouter =~ s/,//g; #remove commas + #set up MRTG's 'in' var naming for graphite + my $graphitein; + if ($$rcfg{'legendi'}{$router}) { + $graphitein = $$rcfg{'legendi'}{$router}; + # if the 'in' var contains an underscore, change to a period so graphite will delimit on it + $graphitein =~ s/_/\./g; + } else { + # if legendi is not specified, just use 'in' + $graphitein = "in"; + } + + #set up MRTG's 'out' var naming for graphite + my $graphiteout; + if ($$rcfg{'legendo'}{$router}) { + $graphiteout = $$rcfg{'legendo'}{$router}; + # if the 'out' var contains an underscore, change to a period so graphite will delimit on it + $graphiteout =~ s/_/\./g; + } else { + # if legendo is not specified, just use 'out' + $graphiteout = "out"; + } + + my $graphitensprefix="m2g"; + + # verify that the 'in' value is numeric (ints, floats, +, -) , and send to graphite + if($inlast =~ /^[+-]?([0-9]*[.])?[0-9]+$/) { + # send the 'in' var data to graphite, and log it appropriately + debug('log', "graphite->send($graphitensprefix.$graphiterouter.$graphitein,$inlast,$time)\n"); + $graphiteObj->send( + path => "$graphitensprefix.$graphiterouter.$graphitein", + value => "$inlast", + time => $time, + ); + } else { + debug('log', "WARNING - skipped graphite->send($graphitensprefix.$graphiterouter.$graphitein,$inlast,$time) because value ($inlast) is non-numeric\n"); + } + + # verify that the 'out' value is numeric (ints, floats, +, -) , and send to graphite + if($outlast =~ /^[+-]?([0-9]*[.])?[0-9]+$/) { + # send the 'out' var data to graphite, and log it appropriately + debug('log', "graphite->send($graphitensprefix.$graphiterouter.$graphiteout,$outlast,$time)\n"); + $graphiteObj->send( + path => "$graphitensprefix.$graphiterouter.$graphiteout", + value => "$outlast", + time => $time, + ); + } else { + debug('log', "WARNING - skipped graphite->send($graphitensprefix.$graphiterouter.$graphiteout,$outlast,$time) because value ($outlast) is non-numeric\n"); + } + } + # done sending to graphite for this iteration + + + + if ( $RRDs::VERSION < 1.2 ){ + # get the rrdtool-processed values back from rrdtool + # for the threshold checks (we cannot use the fetched data) + $start = gettimeofday(); + my $info = RRDs::info($dotrrd); + debug('prof',sprintf("RRDs::info $dotrrd - %.3fs",gettimeofday()-$start)); + my $lasttime = $info->{last_update} - $info->{last_update} % $info->{step}; + debug('log', "RRDs::info($dotrrd)"); + $e = RRDs::error(); + warn "$NOW: ERROR: Cannot 'info' $dotrrd: $e\n" if ($e); + $start = gettimeofday(); + my $fetch = (RRDs::fetch($dotrrd,'AVERAGE','-s',$lasttime-1,'-e',$lasttime))[3]; + debug('prof',sprintf("RRDs::fetch $dotrrd - %.3fs",gettimeofday()-$start)); + $e = RRDs::error(); + warn "$NOW: ERROR: Cannot 'fetch' $dotrrd: $e\n" if ($e); + debug('log', "RRDs::fetch($dotrrd,'AVERAGE','-s',$lasttime,'-e',$lasttime)"); + $cuin{d}{$router} = $fetch->[0][0]; + $cuout{d}{$router} = $fetch->[0][1]; + } elsif ( $RRDs::VERSION >= 1.4 and $$cfg{rrdcached} ){ + # Cannot check thresholds + } elsif($rrddata) { + my $utime = $time - ($time % int($cfg->{interval}*60)); + $cuin{d}{$router} = $rrddata->{"[$utime]RRA[AVERAGE][1]DS[ds0]"}; + $cuout{d}{$router} = $rrddata->{"[$utime]RRA[AVERAGE][1]DS[ds1]"}; + $cuin{d_hwfail}{$router} = $rrddata->{"[$utime]RRA[FAILURES][1]DS[ds0]"}; + $cuout{d_hwfail}{$router} = $rrddata->{"[$utime]RRA[FAILURES][1]DS[ds1]"}; + } + my $in = defined $cuin{d}{$router} ? $cuin{d}{$router} : "???" ; + my $out = defined $cuout{d}{$router} ? $cuout{d}{$router} : "???" ; + debug('log', " got: $in/$out"); + # the html pages and the graphics are created at "call time" so that's it! + # (the returned hashes are empty, it's just to minimize the changes to mrtg) + if ($$rcfg{'options'}{'dorelpercent'}{$router}) { + return (\%maxin, \%maxout, \%maxpercent, \%avin, \%avout, \%avpercent, \%cuin, \%cuout, \%cupercent, \%avmxin, \%avmxout); + } + return (\%maxin, \%maxout, \%avin, \%avout, \%cuin, \%cuout, \%avmxin, \%avmxout ); + + } + ########## rrdtool users have left here ############### + + ((($MRTG_lib::OS eq 'NT' or $MRTG_lib::OS eq 'OS2') and (-e "${FindBin::Bin}${MRTG_lib::SL}rateup.exe")) or + (($MRTG_lib::OS eq 'NW') and (-e "SYS:/Mrtg/bin/rateup.nlm")) or + (-x "${FindBin::Bin}${MRTG_lib::SL}rateup")) or + die "$NOW: ERROR: Can't Execute '${FindBin::Bin}${MRTG_lib::SL}rateup'\n"; + + # rateup does not know about undef so we make inlast and outlast ready for rateup + #warn "$NOW: ERROR: inlast is undefined. Skipping $router\n" unless defined $inlast; + #warn "$NOW: ERROR: outlast is undefined. Skipping $router\n" unless defined $outlast; + #return undef unless defined $inlast and defined $outlast; + + + # set values to -1 to tell rateup about unknown values + $inlast = -1 unless defined $inlast; + $outlast = -1 unless defined $outlast; + + # untaint in and out + if ($inlast =~ /^([-0-9.]+)$/) { + $inlast = $1; + } + if ($outlast =~ /^([-0-9.]+)$/) { + $outlast = $1; + } + + if ($$rcfg{'options'}{'dorelpercent'}{$router}) { + @exec = ("${FindBin::Bin}${MRTG_lib::SL}rateup", + "$$cfg{'logdir'}$$rcfg{'directory'}{$router}","$router", + $time, $$rcfg{'options'}{'unknaszero'}{$router} ? '-z':'-Z', + "$up_abs"."p", $inlast, $outlast, $absmax, + "C", $$rcfg{'rgb1'}{$router},$$rcfg{'rgb2'}{$router}, + $$rcfg{'rgb3'}{$router},$$rcfg{'rgb4'}{$router}, + $$rcfg{'rgb5'}{$router}); + } else { + + @exec = ("${FindBin::Bin}${MRTG_lib::SL}rateup", + "$$cfg{'logdir'}$$rcfg{'directory'}{$router}","$router", + $time, $$rcfg{'options'}{'unknaszero'}{$router} ? '-z':'-Z', + "$up_abs", $inlast, $outlast, $absmax, + "c", $$rcfg{'rgb1'}{$router},$$rcfg{'rgb2'}{$router}, + $$rcfg{'rgb3'}{$router},$$rcfg{'rgb4'}{$router}); + } + + # If this list grows anymore would it be more efficient to have an + # array to look up the command line option to send to rateup rather + # than have a long list to check? + push (@exec, '-t') if defined $$rcfg{'options'}{'transparent'}{$router}; + push (@exec, '-0') if defined $$rcfg{'options'}{'withzeroes'}{$router}; + push (@exec, '-b') if defined $$rcfg{'options'}{'noborder'}{$router}; + push (@exec, '-a') if defined $$rcfg{'options'}{'noarrow'}{$router}; + push (@exec, '-i') if defined $$rcfg{'options'}{'noi'}{$router}; + push (@exec, '-o') if defined $$rcfg{'options'}{'noo'}{$router}; + push (@exec, '-l') if defined $$rcfg{'options'}{'logscale'}{$router}; + push (@exec, '-x') if defined $$rcfg{'options'}{'expscale'}{$router}; + push (@exec, '-m') if defined $$rcfg{'options'}{'secondmean'}{$router}; + push (@exec, '-p') if defined $$rcfg{'options'}{'printrouter'}{$router}; + + my $maxx = $$rcfg{'xsize'}{$router}; + my $maxy = $$rcfg{'ysize'}{$router}; + my $xscale = $$rcfg{'xscale'}{$router}; + my $yscale = $$rcfg{'yscale'}{$router}; + my $growright = 0+($$rcfg{'options'}{'growright'}{$router} or 0); + my $bits = 0+($$rcfg{'options'}{'bits'}{$router} or 0); + my $integer = 0+($$rcfg{'options'}{'integer'}{$router} or 0); + my $step = 5*60; + my $rop; + my $ytics = $$rcfg{'ytics'}{$router}; + my $yticsf= $$rcfg{'yticsfactor'}{$router}; + my $timestrfmt = $$rcfg{'timestrfmt'}{$router}; + my $timestrpos = ${MRTG_lib::timestrpospattern}{uc $$rcfg{'timestrpos'}{$router}}; + + if (not defined $$rcfg{'ylegend'}{$router}){ + if ($bits){ + $$rcfg{'ylegend'}{$router} = &$LOC("Bits per minute") + if defined $$rcfg{'options'}{'perminute'}{$router}; + $$rcfg{'ylegend'}{$router} = &$LOC("Bits per hour") + if defined $$rcfg{'options'}{'perhour'}{$router}; + } else { + $$rcfg{'ylegend'}{$router} = &$LOC("Bytes per minute") + if defined $$rcfg{'options'}{'perminute'}{$router}; + $$rcfg{'ylegend'}{$router} = &$LOC("Bytes per hour") + if defined $$rcfg{'options'}{'perhour'}{$router}; + } + } + + if ($$rcfg{'ylegend'}{$router}) { + push (@exec, "l", "[$$rcfg{'ylegend'}{$router}]"); + } + my $sign = ($$rcfg{'unscaled'}{$router} and $$rcfg{'unscaled'}{$router} =~ /d/) ? 1 : -1; + + if ($$rcfg{'pngtitle'}{$router}) { + push (@exec, "T", "[$$rcfg{'pngtitle'}{$router}]"); + } + + if ($$rcfg{'timezone'}{$router}) { + push (@exec, "Z", "$$rcfg{'timezone'}{$router}"); + } + + if ($$rcfg{'kilo'}{$router}) { + push (@exec, "k", $$rcfg{'kilo'}{$router}); + } + if ($$rcfg{'kmg'}{$router}) { + push (@exec, "K", $$rcfg{'kmg'}{$router}); + } + if ($$rcfg{'weekformat'}{$router}) { + push (@exec, "W", $$rcfg{'weekformat'}{$router}); + } + my $SAGE = (time - $main::STARTTIME) / 3600 / 24; # current script age + if (not defined $$opts{'log-only'}){ + if (not defined $$rcfg{'suppress'}{$router} or $$rcfg{'suppress'}{$router} !~ /d/) { + # VMS: should work for both now + push (@exec, "i", "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-day.${main::GRAPHFMT}", + $sign*$maxvi, $sign*$maxvo, $maxx, $maxy, ,$xscale, $yscale, $growright, $step, $bits, $ytics, $yticsf, $timestrfmt, $timestrpos); + @mxvls = ("d"); + push (@metas, "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-day.${main::GRAPHFMT}", + $$cfg{'interval'}); + } + + + if (((not -e "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-week.${main::GRAPHFMT}") or + ((-M "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-week.${main::GRAPHFMT}") + $SAGE >= 0.5/24)) and + (not defined $$rcfg{'suppress'}{$router} or $$rcfg{'suppress'}{$router} !~/w/) + ) { + $step=30*60; + $sign = (defined $$rcfg{'unscaled'}{$router} and $$rcfg{'unscaled'}{$router} =~ /w/) ? 1 : -1; + push (@mxvls , "w"); + $rop =(defined $$rcfg{'withpeak'}{$router} and $$rcfg{'withpeak'}{$router} =~ /w/) ? "p" : "i"; + push (@exec, $rop ,"$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-week.${main::GRAPHFMT}", + $sign*$maxvi, $sign*$maxvo, $maxx, $maxy, $xscale, $yscale, $growright, $step, $bits, $ytics, $yticsf, $timestrfmt, $timestrpos); + push (@metas, "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-week.${main::GRAPHFMT}", 30); + } + + + if (((not -e "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-month.${main::GRAPHFMT}") or + (( -M "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-month.${main::GRAPHFMT}") + $SAGE >= 2/24)) and + (not defined $$rcfg{'suppress'}{$router} or $$rcfg{'suppress'}{$router} !~ /m/)) { + $step=2*60*60; + $sign = (defined $$rcfg{'unscaled'}{$router} and $$rcfg{'unscaled'}{$router} =~ /m/) ? 1 : -1; + push (@mxvls , "m"); + $rop =(defined $$rcfg{'withpeak'}{$router} and $$rcfg{'withpeak'}{$router} =~ /m/) ? "p" : "i"; + push (@exec, $rop ,"$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-month.${main::GRAPHFMT}", + $sign*$maxvi, $sign*$maxvo, $maxx, $maxy, $xscale, $yscale, $growright, $step, $bits, $ytics, $yticsf, $timestrfmt, $timestrpos); + push (@metas, "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-month.${main::GRAPHFMT}", 120); + } + + if (((not -e "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-year.${main::GRAPHFMT}") or + (( -M "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-year.${main::GRAPHFMT}") + $SAGE >= 1)) and + (not defined $$rcfg{'suppress'}{$router} or $$rcfg{'suppress'}{$router} !~/y/)) { + $step=24*60*60; + $sign = (defined $$rcfg{'unscaled'}{$router} and $$rcfg{'unscaled'}{$router} =~ /y/) ? 1 : -1; + push (@mxvls , "y"); + $rop =(defined $$rcfg{'withpeak'}{$router} and $$rcfg{'withpeak'}{$router} =~ /y/) ? "p" : "i"; + push (@exec, $rop, "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-year.${main::GRAPHFMT}", + $sign*$maxvi, $sign*$maxvo, $maxx, $maxy, $xscale, $yscale, $growright, $step, $bits, $ytics, $yticsf, $timestrfmt, $timestrpos); + push (@metas, "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-year.${main::GRAPHFMT}", 1440); + } + } + + # VMS: this might work now ... or does VMS NOT know about pipes? + # NT doesn't have fork() so an open(xxx,"-|") won't work + # OS2 fork() have bug with socket handles. In RunAsDaemon mode it fail + # after first loop (with "socket operation on non socket" message. + + if ($MRTG_lib::OS eq 'VMS' or $MRTG_lib::OS eq 'NT' or $MRTG_lib::OS eq 'OS2'){ + map { s/"/\\"/; $_ = '"'.$_.'"' if /\s/ } @exec; + open (RATEUP, join (" ", @exec)."|") or + do { + warn "$NOW: WARNING: rateup (".(join " ", @exec ). + ") did not work: $!\n"; + return; + } + } elsif ($MRTG_lib::OS eq 'NW'){ + map { s/"/\\"/; $_ = '"'.$_.'"' if /\s/ } @exec; + + # Stuff around because of Perl problems. + + open (NWPARMS, ">"."$$cfg{'imagedir'}$router.dat") or + do { + warn "$NOW: WARNING: Rateup parameters [$$cfg{'imagedir'}$router.dat] [open] failed.\n"; + + return; + }; + print NWPARMS join (" ", @exec); + close NWPARMS; + + # Now run Rateup with path to Parameters. + + open (RATEUP, "SYS:/Mrtg/bin/rateup -f $$cfg{'imagedir'}$router.dat"."|") or + do { + warn "$NOW: WARNING: SYS:/Mrtg/bin/rateup -f $$cfg{'imagedir'}$router.dat did NOT work.\n"; + + return; + } + } else { + $! = undef; + open (RATEUP,"-|") or + do { exec @exec or + warn "$NOW: WARNING: rateup (".(join " ", @exec ). + ") did not work: $!\n"; + }; + + } + + debug('log', join(" ", @exec)); + + + if (open (HTML,"<$$cfg{'htmldir'}$$rcfg{'directory'}{$router}$router.$$rcfg{'extension'}{$router}")) { + for ($i=0 ; $i<200 ; $i++) { + last if eof(HTML); + $_= <HTML>; + if (/<!-- maxin ([dwmy]) (\d*)/) { + $maxin{$1}{$router}=$2 || 0; + } + + if (/<!-- maxout ([dwmy]) (\d*)/) { + $maxout{$1}{$router}=$2 || 0; + } + + if (/<!-- maxpercent ([dwmy]) (\d*)/) { + $maxpercent{$1}{$router}=$2 || 0; + } + + if (/<!-- avin ([dwmy]) (\d*)/) { + $avin{$1}{$router}=$2 || 0; + } + + if (/<!-- avout ([dwmy]) (\d*)/) { + $avout{$1}{$router}=$2 || 0; + } + + if (/<!-- avpercent ([dwmy]) (\d*)/) { + $avpercent{$1}{$router}=$2 || 0; + } + + if (/<!-- cuin ([dwmy]) (\d*)/) { + $cuin{$1}{$router}=$2 || 0; + } + + if (/<!-- cuout ([dwmy]) (\d+)/) { + $cuout{$1}{$router}=$2 || 0; + } + + if (/<!-- cupercent ([dwmy]) (\d+)/) { + $cupercent{$1}{$router}=$2 || 0; + } + if (/<!-- avmxin ([dwmy]) (\d*)/) { + $avmxin{$1}{$router}=$2 || 0; + } + + if (/<!-- avmxout ([dwmy]) (\d*)/) { + $avmxout{$1}{$router}=$2 || 0; + } + } + close HTML; + } + + foreach $period (@mxvls) { + $res = <RATEUP>; + if (not defined $res and eof(RATEUP)){ + warn "$NOW: ERROR: Skipping webupdates because rateup did not return anything sensible\n"; + close RATEUP; + rateupcheck $router; + return; + }; + chomp $res; + $maxin{$period}{$router}=sprintf("%.0f",$res || 0); + chomp($res = <RATEUP>); + $maxout{$period}{$router}=sprintf("%.0f",$res || 0); + + if ($$rcfg{'options'}{'dorelpercent'}{$router}) { + chomp($res = <RATEUP>); + $maxpercent{$period}{$router}=sprintf("%.0f",$res || 0); + } + + chomp($res = <RATEUP>); + $avin{$period}{$router}=sprintf("%.0f",$res || 0); + chomp($res = <RATEUP>); + $avout{$period}{$router}=sprintf("%.0f",$res || 0); + + if ($$rcfg{'options'}{'dorelpercent'}{$router}) { + chomp($res = <RATEUP>); + $avpercent{$period}{$router}=sprintf("%.0f",$res || 0); + } + + chomp($res = <RATEUP>); + $cuin{$period}{$router}=sprintf("%.0f",$res || 0); + chomp($res = <RATEUP>); + $cuout{$period}{$router}=sprintf("%.0f",$res || 0); + + if ($$rcfg{'options'}{'dorelpercent'}{$router}) { + chomp($res = <RATEUP>); + $cupercent{$period}{$router}=sprintf("%.0f",$res || 0); + } + + chomp($res = <RATEUP>); + debug('avmx',"avmxin $res"); + $avmxin{$period}{$router}=sprintf("%.0f",$res || 0); + chomp($res = <RATEUP>); + debug('avmx',"avmxout $res"); + $avmxout{$period}{$router}=sprintf("%.0f",$res || 0); + + } + close(RATEUP); + rateupcheck $router; + if ( defined $$cfg{'writeexpires'} and $$cfg{'writeexpires'} =~ /^y/i ) { + my($fil,$exp); + while ( $fil = shift(@metas) ) { + $exp = &expistr(shift(@metas)); + open(META, ">$fil.meta"); + print META "Expires: $exp\n"; + close(META); + } + } + + if ($$rcfg{'options'}{'dorelpercent'}{$router}) { + return (\%maxin, \%maxout, \%maxpercent, \%avin, \%avout, \%avpercent, \%cuin, \%cuout, \%cupercent, \%avmxin, \%avmxout); + } else { + return (\%maxin, \%maxout, \%avin, \%avout, \%cuin, \%cuout, \%avmxin, \%avmxout); + } +} + +#format 10*$kilo to 10 kB/s +sub fmi { + my($number, $maxbytes, $router, @foo) = @_; + return "?????" unless defined $number; + my($rcfg,$LOC)=@foo; + my @short= (); + my $mul = 1; + if ($$rcfg{'kmg'}{$router}) { + my($i); + if (defined $$rcfg{'shortlegend'}{$router}) { + foreach $i (split(/,/, $$rcfg{'kmg'}{$router})) { + $short[$#short+1] = "$i"."$$rcfg{'shortlegend'}{$router}"; + } + } + elsif ($$rcfg{'options'}{'bits'}{$router}) { + foreach $i (split(/,/, $$rcfg{'kmg'}{$router})) { + if ($$rcfg{'options'}{'perminute'}{$router}) { + $short[$#short+1] = "$i".&$LOC("b/min"); + } elsif ($$rcfg{'options'}{'perhour'}{$router}) { + $short[$#short+1] = "$i".&$LOC("b/h"); + } else { + $short[$#short+1] = "$i".&$LOC("b/s"); + } + } + $mul= 8; + } else { + foreach $i (split(/,/, $$rcfg{'kmg'}{$router})) { + if ($$rcfg{'options'}{'perminute'}{$router}) { + $short[$#short+1] = "$i".&$LOC ("B/min"); + } elsif ($$rcfg{'options'}{'perhour'}{$router}) { + $short[$#short+1] = "$i".&$LOC("B/h"); + } else { + $short[$#short+1] = "$i".&$LOC("B/s"); + } + } + $mul= 1; + } + } else { + if (defined $$rcfg{'options'}{'bits'}{$router}) { + if ($$rcfg{'options'}{'perminute'}{$router}) { + @short = (&$LOC("b/min"),&$LOC("kb/min"),&$LOC("Mb/min"),&$LOC("Gb/min")); + } elsif (defined $$rcfg{'options'}{'perhour'}{$router}) { + @short = (&$LOC("b/h"),&$LOC("kb/h"),&$LOC("Mb/h"),&$LOC("Gb/h")); + } else { + @short = (&$LOC("b/s"),&$LOC("kb/s"),&$LOC("Mb/s"),&$LOC("Gb/s")); + } + $mul= 8; + } else { + if ($$rcfg{'options'}{'perminute'}{$router}) { + @short = (&$LOC("B/min"),&$LOC("kB/min"),&$LOC("MB/min"),&$LOC("GB/min")); + } elsif ($$rcfg{'options'}{'perhour'}{$router}) { + @short = (&$LOC("B/h"),&$LOC("kB/h"),&$LOC("MB/h"),&$LOC("GB/h")); + } else { + @short = (&$LOC("B/s"),&$LOC("kB/s"),&$LOC("MB/s"),&$LOC("GB/s")); + } + $mul= 1; + } + if ($$rcfg{'shortlegend'}{$router}) { + @short = ("$$rcfg{'shortlegend'}{$router}", + "k$$rcfg{'shortlegend'}{$router}", + "M$$rcfg{'shortlegend'}{$router}", + "G$$rcfg{'shortlegend'}{$router}"); + } + } + my $digits=length("".$number*$mul); + my $divm=0; + # + # while ($digits-$divm*3 > 4) { $divm++; } + # my $divnum = $number*$mul/10**($divm*3); + my $divnum=$number*$mul*$$rcfg{'factor'}{$router}; + # while ($divnum/$$rcfg{'kilo'}{$router} >= 10*$$rcfg{'kilo'}{$router} and $divnum<$#short) { + while (($divnum >= 10*$$rcfg{'kilo'}{$router} or $short[$divm] =~ /^-/) and + $divm<$#short) { + $divm++; + $divnum /= $$rcfg{'kilo'}{$router}; + } + my $perc; + if ($number == 0 || $maxbytes == 0) { + $perc = 0; + } else { + $perc = 100/$maxbytes*$number; + } + if (defined $$rcfg{'options'}{'integer'}{$router}) { + if ($$rcfg{'options'}{'nopercent'}{$router}) { + return sprintf("%.0f %s",$divnum,$short[$divm]); + } else { + return sprintf("%.0f %s (%2.1f%%)",$divnum,$short[$divm],$perc); + } + } else { + if (defined $$rcfg{'options'}{'nopercent'}{$router}) { + return sprintf("%.1f %s",$divnum,$short[$divm]); # Added: FvW + } else { + return sprintf("%.1f %s (%2.1f%%)",$divnum,$short[$divm],$perc); + } + return sprintf("%.1f %s (%2.1f%%)",$divnum,$short[$divm],$perc); + } +} + + +sub writehtml { + my($router, $cfg, $rcfg, $maxin, $maxout, $maxpercent, + $avin, $avout, $avmxin, $avmxout, $avpercent, + $cuin, $cuout, $cupercent, $uptime, $name, $LOC) = @_; + + my($VERSION,$Today,$peri); + + my($persec); + + if (defined $$rcfg{'options'}{'bits'}{$router}) { + $persec = &$LOC("Bits"); + } else { + $persec = &$LOC("Bytes"); + } + + # Work out the Colour legend + my($leg1, $leg2, $leg3, $leg4, $leg5); + if ($$rcfg{'legend1'}{$router}) { + $leg1 = $$rcfg{'legend1'}{$router}; + } else { + if ($$rcfg{'options'}{'perminute'}{$router}) { + $leg1=&$LOC("Incoming Traffic in $persec per Minute"); + } elsif ($$rcfg{'options'}{'perhour'}{$router}) { + $leg1=&$LOC("Incoming Traffic in $persec per Hour"); + } else { + $leg1=&$LOC("Incoming Traffic in $persec per Second"); + } + } + if ($$rcfg{'legend2'}{$router}) { + $leg2 = $$rcfg{'legend2'}{$router}; + } else { + if ($$rcfg{'options'}{'perminute'}{$router}) { + $leg2=&$LOC("Outgoing Traffic in $persec per Minute"); + } elsif ($$rcfg{'options'}{'perhour'}{$router}) { + $leg2=&$LOC("Outgoing Traffic in $persec per Hour"); + } else { + $leg2=&$LOC("Outgoing Traffic in $persec per Second"); + } + } + if ($$rcfg{'legend3'}{$router}) { + $leg3 = $$rcfg{'legend3'}{$router}; + } else { + $leg3 = &$LOC("Maximal 5 Minute Incoming Traffic"); + } + if ($$rcfg{'legend4'}{$router}) { + $leg4 = $$rcfg{'legend4'}{$router}; + } else { + $leg4 = &$LOC("Maximal 5 Minute Outgoing Traffic"); + } + if ($$rcfg{'legend5'}{$router}) { + $leg5 = $$rcfg{'legend5'}{$router}; + } else { + $leg5 = "(($leg1)/($leg2))*100"; + } + # Translate the color names + $$rcfg{'col1'}{$router}=&$LOC($$rcfg{'col1'}{$router}); + $$rcfg{'col2'}{$router}=&$LOC($$rcfg{'col2'}{$router}); + $$rcfg{'col3'}{$router}=&$LOC($$rcfg{'col3'}{$router}); + $$rcfg{'col4'}{$router}=&$LOC($$rcfg{'col4'}{$router}); + $$rcfg{'col5'}{$router}=&$LOC($$rcfg{'col5'}{$router}); + + my $dirrel = "../" x ($$rcfg{'directory_web'}{$router} =~ tr|/|/|); + + $Today=&$LOC(datestr(time)); + $VERSION = "2.17.7"; + open (HTML,">$$cfg{'htmldir'}$$rcfg{'directory'}{$router}$router.$$rcfg{'extension'}{$router}") || + do { warn ("$NOW: WARNING: Writing $router.$$rcfg{'extension'}{$router}: $!"); + next }; + # this unforutnately confuses old IE greatly ... so we have to comment + # it out for now ... :-( + # print HTML '<?xml version="1.0" encoding="' . &$LOC('UTF-8') . '"?>' . "\n"; + print HTML '<!DOCTYPE html>' . "\n"; + print HTML '<html lang="en">' . "\n"; + my $interval =$$cfg{'interval'}; + my $expiration = &expistr($interval); + my $refresh = defined $$cfg{'refresh'} ? $$cfg{'refresh'} : 300; + my $namestring = &$LOC("the device"); + print HTML '<!-- MRTG ' . "$VERSION" . ' -->' . "\n"; + print HTML '<!-- Begin Head -->' . "\n"; + print HTML "\t" . '<head>' . "\n"; + print HTML "\t\t" . '<meta charset="' . &$LOC('UTF-8') . '" />' . "\n"; + print HTML "\t\t" . '<meta http-equiv="refresh" content="' . "$refresh" . '" />' . "\n"; +# Not functional in HTML file in modern web browsers - use HTTP header instead +# <meta http-equiv="pragma" content="no-cache" /> +# <meta http-equiv="cache-control" content="no-cache" /> +# <meta http-equiv="expires" content="$expiration" /> +# <meta http-equiv="generator" content="MRTG $VERSION" /> +# <meta http-equiv="date" content="$expiration" /> + print HTML "\t\t" . '<title>' . "$$rcfg{'title'}{$router}" . '' . "\n"; + foreach $peri (qw(d w m y)) { + print HTML <<"TEXT"; + + +TEXT + if ($$rcfg{'options'}{'dorelpercent'}{$router} and defined $$maxpercent{$peri}{$router}) { + print HTML <<"TEXT"; + +TEXT + } + print HTML <<"TEXT"; + + +TEXT + if ($$rcfg{'options'}{'dorelpercent'}{$router}) { + print HTML <<"TEXT"; + +TEXT + } + print HTML "\n" + if defined $$cuin{$peri}{$router}; + print HTML "\n" + if defined $$cuout{$peri}{$router}; + + if ($$rcfg{'options'}{'dorelpercent'}{$router} and $$cupercent{$peri}{$router} ) { + print HTML <<"TEXT"; + +TEXT + } + print HTML <<"TEXT" if $$avmxin{$peri}{$router} and $$avmxout{$peri}{$router}; + + +TEXT + + } + + $namestring = "'$name'" if $name; + + defined $$rcfg{backgc}{$router} or $$rcfg{backgc}{$router} = "#fff"; + + $$rcfg{'rgb1'}{$router} = "" unless defined $$rcfg{'rgb1'}{$router}; + $$rcfg{'rgb2'}{$router} = "" unless defined $$rcfg{'rgb2'}{$router}; + $$rcfg{'rgb3'}{$router} = "" unless defined $$rcfg{'rgb3'}{$router}; + $$rcfg{'rgb4'}{$router} = "" unless defined $$rcfg{'rgb4'}{$router}; + $$rcfg{'rgb5'}{$router} = "" unless defined $$rcfg{'rgb5'}{$router}; + $$rcfg{'rgb6'}{$router} = "" unless defined $$rcfg{'rgb6'}{$router}; + + print HTML " "; + + # allow for \n in addhead + defined $$rcfg{addhead}{$router} or $$rcfg{addhead}{$router} = ""; + defined $$rcfg{pagetop}{$router} or $$rcfg{pagetop}{$router} = ""; + + if (defined $$rcfg{bodytag}{$router}) { + if ($$rcfg{bodytag}{$router} !~ / +"; + print HTML " +$$rcfg{bodytag}{$router} + $$rcfg{'pagetop'}{$router} + "; + print HTML "

"; + if (defined $$rcfg{'timezone'}{$router}){ + print HTML + &$LOC("The statistics were last updated $Today $$rcfg{'timezone'}{$router}"); + } else { + print HTML + &$LOC("The statistics were last updated $Today"); + } + if ($uptime and ! $$rcfg{options}{noinfo}{$router}) { + print HTML + ",
\n". + &$LOC("at which time $namestring had been up for $uptime.") + } + print HTML "

"; + my %sample= ('d' => "`Daily' Graph (".$interval.' Minute', + 'w' => "`Weekly' Graph (30 Minute", + 'm' => "`Monthly' Graph (2 Hour", + 'y' => "`Yearly' Graph (1 Day"); + + my %full = ('d' => 'day', + 'w' => 'week', + 'm' => 'month', + 'y' => 'year'); + + my $InCo; + if (!(defined $$rcfg{'options'}{'noi'}{$router})) { + if (exists $$rcfg{'legendi'}{$router}) { + if ($$rcfg{'legendi'}{$router} ne "") { + $InCo=$$rcfg{'legendi'}{$router}; + } + } else { + $InCo=&$LOC("In"); + } + } + + my $OutCo; + if (!(defined $$rcfg{'options'}{'noo'}{$router})) { + if (exists $$rcfg{'legendo'}{$router}) { + if ($$rcfg{'legendo'}{$router} ne "") { + $OutCo=$$rcfg{'legendo'}{$router}; + } + } else { + $OutCo=&$LOC("Out"); + } + } + my $PercentCo; + if (defined $$rcfg{'legend5'}{$router}) { + if ($$rcfg{'legend5'}{$router} ne "") { + $PercentCo=$$rcfg{'legend5'}{$router}; + } + } else { + $PercentCo=&$LOC("Percentage"); + } + + foreach $peri (qw(d w m y)) { + next if defined $$rcfg{'suppress'}{$router} and $$rcfg{'suppress'}{$router} =~/$peri/; + my $gifw; + if ($$rcfg{'options'}{'dorelpercent'}{$router}) { + $gifw=sprintf("%.0f",($$rcfg{'xsize'}{$router}*$$rcfg{'xscale'}{$router}+ + +100+30) *$$rcfg{'xzoom'}{$router}); + } else { + $gifw=sprintf("%.0f",($$rcfg{'xsize'}{$router}*$$rcfg{'xscale'}{$router} + +100) *$$rcfg{'xzoom'}{$router}); + } + my $gifh=sprintf("%.0f",($$rcfg{'ysize'}{$router}*$$rcfg{'yscale'}{$router}+35) + *$$rcfg{'yzoom'}{$router}); + + # take the image directory away from the html directory to give us relative links + + + my $imagepath = ( $cfg->{htmldir} ne $cfg->{imagedir} ) ? "$dirrel$$cfg{imagehtml}$$rcfg{directory_web}{$router}" : ""; + print HTML " + +
+

".&$LOC("$sample{$peri}").&$LOC(' Average)')."

+ \"$full{$peri}\" + + + + + + + "; + my(@foo)=($rcfg,$LOC); + print HTML " + + + + + + " if $InCo; + print HTML " + + + + + + " if $OutCo; + print HTML " + + + + + + " if ($$rcfg{'options'}{'dorelpercent'}{$router} and $PercentCo); +print HTML " + + + " if ($$rcfg{'options'}{'avgpeak'}{$router} and $InCo and $OutCo); + print HTML " +
" . &$LOC("Max") . "" . &$LOC("Average") . "" . &$LOC("Current") . "
" . $InCo . "".&fmi($$maxin{$peri}{$router}, $$rcfg{'maxbytes1'}{$router}, $router, @foo)."".&fmi($$avin{$peri}{$router}, $$rcfg{'maxbytes1'}{$router}, $router, @foo)." ".&fmi($$cuin{$peri}{$router}, $$rcfg{'maxbytes1'}{$router}, $router, @foo)."
" . $OutCo . "".&fmi($$maxout{$peri}{$router}, $$rcfg{'maxbytes2'}{$router}, $router, @foo)." ".&fmi($$avout{$peri}{$router}, $$rcfg{'maxbytes2'}{$router}, $router, @foo)." ".&fmi($$cuout{$peri}{$router}, $$rcfg{'maxbytes2'}{$router}, $router, @foo)."
" . $PercentCo . "".sprintf("%0.1f %%",($$maxpercent{$peri}{$router} || 0))." ".sprintf("%0.1f %%",($$avpercent{$peri}{$router} || 0 ))." ".sprintf("%0.1f %%",($$cupercent{$peri}{$router} || 0 ))."
+ " . &$LOC("Average max 5 min values for $sample{$peri} interval):") . " + $InCo " . &fmi($$avmxin{$peri}{$router}, $$rcfg{'maxbytes1'}{$router}, $router, @foo) . "/ + $OutCo " . &fmi($$avmxout{$peri}{$router}, $$rcfg{'maxbytes2'}{$router}, $router, @foo) . " +
+
+\n"; +} + + if (!(defined $$rcfg{'options'}{'nolegend'}{$router})) { + print HTML " + +
+ "; + print HTML " + + + + " if $InCo; + print HTML " + + + + " if $OutCo; + if ($$rcfg{'withpeak'}{$router}) { + print HTML " + + + + " if $InCo; + print HTML " + + + + " if $OutCo; + } + if ($$rcfg{'options'}{'dorelpercent'}{$router}) { + print HTML " + + + + "; + } + print HTML " +
$$rcfg{'col1'}{$router} ###$leg1
$$rcfg{'col2'}{$router} ###$leg2
$$rcfg{'col3'}{$router} ###$leg3
$$rcfg{'col4'}{$router} ###$leg4
$$rcfg{'col5'}{$router} ###$leg5
+
+ +"; + } + + if (!(defined $$rcfg{'options'}{'nobanner'}{$router})) { + my $gifPath; + + if (defined $$cfg{icondir}) { + $gifPath = $$cfg{icondir}; + #lets make sure there is a trailing path separator + $gifPath =~ s|/*$|/|; + } else { + $gifPath = "$dirrel$$cfg{imagehtml}"; + } + + print HTML< +
+ +TEXT + } + + print HTML $$rcfg{'pagefoot'}{$router} if defined $$rcfg{'pagefoot'}{$router}; + print HTML < + + +TEXT + close HTML; + + if (defined $$cfg{'writeexpires'} and $$cfg{'writeexpires'} =~ /^y/i) { + open(HTMLG, ">$$cfg{'htmldir'}$$rcfg{'directory'}{$router}$router.". + "$$rcfg{'extension'}{$router}.meta") || + do { + warn "$NOW: WARNING: Writing $$cfg{'htmldir'}$$rcfg{'directory'}{$router}$router.". + "$$rcfg{'extension'}{$router}.meta: $!\n"; + next + }; + + print HTMLG "Expires: $expiration\n"; + close(HTMLG); + } +} + + +sub printusage { + print < + +mrtg-2.17.7 - Multi Router Traffic Grapher + +Copyright 1995-2006 by Tobias Oetiker +Licensed under the Gnu GPL. + +If you want to know more about this tool, you might want +to read the docs. You can find everything on the +mrtg website: + +http://oss.oetiker.ch/mrtg/ + +USAGEDESC + exit(1); +} + + +sub lockit { + my ($lockfile,$templock) = @_; + if ($MRTG_lib::OS eq 'VMS' or $MRTG_lib::OS eq 'NT' or $MRTG_lib::OS eq 'OS2') { + # too sad NT and VMS can't do links we'll do the diletants lock + if (-e $lockfile and not unlink $lockfile) { + my($lockage) = time()-(stat($lockfile))[9]; + die "$NOW: ERROR: I guess another mrtg is running. A lockfile ($lockfile)\n". + " aged $lockage seconds is hanging around and I can't remove\n". + " it because another process is still using it."; + } + + open (LOCK, ">$lockfile") or + die "$NOW: ERROR: Creating lockfile $lockfile: $!\n"; + print LOCK "$$\n"; + close LOCK; + open (LOCK, "<$lockfile") or + die "$NOW: ERROR: Reading lockfile $lockfile for owner check: $!\n"; + my($read)=; + chomp($read); + die "$NOW: ERROR: Someone else just got the lockfile $lockfile\n" + unless $$ == $read; + } else { + # now, lets do it the UNIX way ... Daves work ... + open(LOCK,">$templock") or die "$NOW: ERROR: Creating templock $templock: $!"; + $main::Cleanfile = $templock; + if (!link($templock,$lockfile)) { # Lock file exists - deal with it. + my($nlink,$lockage) = (stat($lockfile))[3,9]; + $lockage = time() - $lockage; + if ($nlink < 2 or $lockage > 30*60) { #lockfile is alone and old + unlink($lockfile) + || do{ unlink $templock; + die "$NOW: ERROR: Can't unlink stale lockfile ($lockfile). Permissions?\n"}; + link($templock,$lockfile) + || do{ unlink $templock; + die "$NOW: ERROR: Can't create lockfile ($lockfile).\n". + "Permission problem or another mrtg locking succesfully?\n"}; + } else { + unlink $templock; + die "$NOW: ERROR: It looks as if you are running two copies of mrtg in parallel on\n". + " the same config file. There is a lockfile ($lockfile) and it is\n". + " is only $lockage seconds old ... Check your crontab.\n". + " (/etc/crontab and /var/spool/cron/root) \n" + if $lockage < 4; + + die "$NOW: ERROR: I guess another mrtg is running. A lockfile ($lockfile) aged\n". + "$lockage seconds is hanging around. If you are sure that no other mrtg\n". + "is running you can remove the lockfile\n"; + + } + + } + } +} + +sub threshmail ($$$$){ + my $server = shift; + my $from = shift; + my $to = shift; + my $message = shift; + debug('base',"sending threshmail from $from to $to"); + my $smtp = Net::SMTP->new([split /\s*,\s*/, $server],Timeout=>5) or + do { warn "$NOW: ERROR: could not send thresholdmail to $to"; return }; + $smtp->mail($from); + $smtp->to(split(/\s*,\s*/, $to)); + $smtp->data(); + $smtp->datasend($message); + $smtp->dataend(); + $smtp->quit; +} + +sub threshcheck { + # threshold checking by Tom Muggli + # ... fsck'd up but fixed by Juha Laine + my ($cfg,$rcfg,$cfgfile,$router,$cuin,$cuout) = @_; + my $threshfile; + my %cu = ( i=> $cuin, o=>$cuout ); + # are we going to keep state ? + if (defined $$cfg{'threshdir'}){ + ensureSL(\$$cfg{'threshdir'}); + $threshfile = $$cfg{'threshdir'}.(split /\Q$MRTG_lib::SL\E/, $cfgfile)[-1].".$router"; + } + + # setup environment for external scripts + if (defined $rcfg->{'threshdesc'}{$router}) { + $ENV{THRESH_DESC}=$rcfg->{'threshdesc'}{$router}; + } else { + delete $ENV{THRESH_DESC}; + } + if (defined $rcfg->{'hwthreshdesc'}{$router}) { + $ENV{HWTHRESH_DESC}=$rcfg->{'hwthreshdesc'}{$router}; + } else { + delete $ENV{HWTHRESH_DESC}; + } + + for my $dir (qw(i o)){ # in and out + my %thresh = ( + thresh => $cu{$dir}{d}{$router}, + + # if we are looking at an rrd with holtwinters RRAs + # we get a failures count. + hwthresh => $cu{$dir}{d_hwfail}{$router} + ); + for my $type (keys %thresh){ + for my $bound (qw(min max)){ + # it gets set inside the loop because threshval gets modified further + # down in the code + my $threshval = $thresh{$type}; + next if not defined $threshval; + + my $boundval = $rcfg->{$type.$bound.$dir}{$router}; + next unless defined $boundval; + + my $realval = ""; + my $realthresh = ""; + + if ($boundval =~ s/%$//) { # defined in % of maxbytes + # 2 decimals in % + $realval = "% ($threshval)"; + $realthresh = "% (".sprintf("%.1f",($rcfg->{"maxbytes".($dir eq 'i' ? 1 : 2)}{$router} * $boundval / 100)).")"; + $threshval = sprintf "%.1f", ($threshval / $rcfg->{"maxbytes".($dir eq 'i' ? 1 : 2)}{$router} * 100); # the new code + } + my $msghead = ""; + $msghead = "From: $cfg->{threshmailsender}\nTo: $rcfg->{${type}.'mailaddress'}{$router}" + if $rcfg->{${type}.'mailaddress'}{$router} and $cfg->{threshmailsender}; + my $pagetop = $rcfg->{pagetop}{$router} || ''; + $pagetop =~ s|

.*?

||; + $pagetop =~ s|\s*\s*(.*?)\s*(.*?)\s*\s*|$1 $2\n|g; + $pagetop =~ s|\s*<.+?>\s*\n?||g; + + my $msgbody = <{title}{$router} + + Target: $router + Type: $type +Direction: $dir + Bound: $bound +Threshold: $boundval$realthresh + Current: $threshval$realval + +$pagetop + +MESSAGE + $msgbody .= "\n$rcfg->{$type.'desc'}{$router}\n" if $rcfg->{$type.'desc'}{$router}; + + + if (($bound eq 'min' and $boundval > $threshval) or + ($bound eq 'max' and $boundval < $threshval)) { + # threshold was broken... + my $message = <{$type.'prog'.$dir}{$router}, $router, + $rcfg->{$type.$bound.$dir}{$router}, $threshval,($rcfg->{$type.'desc'}{$router} ||"No Description")); + + # Check if we use the status file or not... + if ( defined $threshfile ) { + if ( not -e $threshfile.".".$type.$bound.uc($dir) ) { + # Create a file to indicate a threshold problem for the time after the problem + open THRESHTOUCH, ">".$threshfile.".".$type.$bound.uc($dir) + or warn "$NOW: WARNING: Creating $threshfile.".$bound.uc($dir).": $!\n"; + close THRESHTOUCH; + if (defined $rcfg->{$type.'prog'.$dir}{$router}){ + debug('base',"run threshprog$dir: ".(join ",",@exec)); + system @exec; + } + threshmail $cfg->{threshmailserver},$cfg->{threshmailsender},$rcfg->{$type.'mailaddress'}{$router},$message + if $rcfg->{$type.'mailaddress'}{$router} + } + else { + debug('base',"NOT acting on BROKEN threshold since $threshfile.$type$bound$dir exists"); + } + } elsif ( not defined $cfg->{$type.'hyst'} or + ($bound eq 'min' and $boundval - $cfg->{$type.'hyst'}* $boundval < $threshval) or + ($bound eq 'max' and $boundval + $cfg->{$type.'hyst'}* $boundval > $threshval) + ) { + # no threshold dir so run on every 'break' + if (defined $rcfg->{$type.'prog'.$dir}{$router}){ + debug('base',"run ${type}prog$dir: ".(join ",",@exec)); + system @exec; + } + threshmail $cfg->{threshmailserver},$cfg->{threshmailsender},$rcfg->{$type.'mailaddress'}{$router},$message + if $rcfg->{$type.'mailaddress'}{$router}; + } + } else { + # no threshold broken ... + my @exec = ( $rcfg->{$type.'progok'.$dir}{$router}, $router, + $rcfg->{$type.$bound.$dir}{$router}, $threshval); + my $message = <{$type.$bound.$dir}{$router} vs $threshval) + +Threshold UN-BROKEN +------------------- +$msgbody +MESSAGE + + # Check if we use the status file or not... + if ( defined $threshfile ) { + if ( -e $threshfile.".".$type.$bound.uc($dir) ){ + unlink "$threshfile.".$type.$bound.uc($dir); + if (defined $rcfg->{$type.'progok'.$dir}{$router}){ + debug('base',"run ${type}progok$dir: ".(join ",",@exec)); + system @exec; + } + threshmail $cfg->{threshmailserver},$cfg->{threshmailsender},$rcfg->{$type.'mailaddress'}{$router},$message + if $rcfg->{$type.'mailaddress'}{$router}; + } + } + } + } # for my $bound ... + } # for my $type + } # for my $dir +} + +sub getexternal ($) { + my $command = shift; + my $in=undef; + my $out=undef; + my $uptime="unknown"; + my $name="unknown"; + + open (EXTERNAL , $command."|") + or warn "$NOW: WARNING: Running '$command': $!\n"; + + warn "$NOW: WARNING: Could not get any data from external command ". + "'".$command. + "'\nMaybe the external command did not even start. ($!)\n\n" if eof EXTERNAL; + + chomp( $in=) unless eof EXTERNAL; + chomp( $out=) unless eof EXTERNAL; + chomp( $uptime=) unless eof EXTERNAL; + chomp( $name=) unless eof EXTERNAL; + + close EXTERNAL; + + # strip returned date + $uptime =~ s/^\s*(.*?)\s*/$1/; + $name =~ s/^\s*(.*?)\s*/$1/; + + # do we have numbers in the external programs answer ? + if ( not defined $in ) { + warn "$NOW: WARNING: Problem with External get '$command':\n". + " Expected a Number for 'in' but nothing'\n\n"; + } elsif ( $in eq 'UNKNOWN' ) { + $in = undef; + } elsif ( $in !~ /([-+]?\d+(.\d+)?)/ ) { + warn "$NOW: WARNING: Problem with External get '$command':\n". + " Expected a Number for 'in' but got '$in'\n\n"; + $in = undef; + } else { + $in = $1; + } + + if ( not defined $out ) { + warn "$NOW: WARNING: Problem with External get '$command':\n". + " Expected a Number for 'out' but nothing'\n\n"; + } elsif ( $out eq 'UNKNOWN' ) { + $out = undef; + } elsif ( $out !~ /([-+]?\d+(.\d+)?)/ ) { + warn "$NOW: WARNING: Problem with External get '$command':\n". + " Expected a Number for 'out' but got '$out'\n\n"; + $out = undef; + } else { + $out = $1; + } + debug('snpo',"External result:".($in||"undef")." out:".($out||"undef")." uptime:".($uptime||"undef")." name:".($name||"undef")); + return ($in,$out,time,$uptime,$name); +} + +sub getsnmparg ($$$$){ + my $confcache = shift; + my $target = shift; + my $cfg = shift; + my $populated = shift; + my $retry = 0; + + my $hostname = $$target{Host}; + my $hostkey = "$$target{Community}\@$$target{Host}$$target{SnmpOpt}"; + + if ($$target{ipv4only}) { + if (not ( $hostname =~ /^\d+\.\d+\.\d+\.\d+$/ or gethostbyname $hostname) ){ + warn "$NOW: WARNING: Skipping host $hostname as it does not resolve to an IPv4 address\n"; + return 'DEADHOST'; + } + } else { + if($hostname =~ /^\[(.*)\]$/) { + # Numeric IPv6 address. Check that it's valid + $hostname = substr($hostname, 1); + chop $hostname; + if(! inet_pton(AF_INET6(), $hostname)) { + warn "$NOW: WARNING: Skipping host $hostname: invalid IPv6 address\n"; + return 'DEADHOST'; + } + } else { + # Hostname. Look it up + my @res; + my ($too,$port,$otheropts) = split(':', $$target{SnmpOpt}, 3); + $port = 161 unless defined $port; + @res = getaddrinfo($hostname, $port, Socket::AF_UNSPEC(), Socket::SOCK_DGRAM()); + if (scalar (@res) < 5) { + warn "$NOW: WARNING: Skipping host $hostname as it does not resolve to an IPv4 or IPv6 address\n"; + return 'DEADHOST'; + } + } + } + RETRY: + my @ifnum = (); + my @OID = (); + # Find apropriate Interface to poll from + for my $i (0..1) { + if ($$target{IfSel}[$i] eq 'If') { + $ifnum[$i] = ".".$$target{Key}[$i]; + debug('snpo',"simple If: $ifnum[$i]"); + } elsif($$target{IfSel}[$i] eq 'None') { + $ifnum[$i] = ""; + } else { + $$target{Key}[$i] =~ s/\s+$//; # no trainling whitespace in keys ... + + if (not defined readfromcache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i])) { + debug('snpo',"($i) Populate ConfCache for $$target{Host}$$target{SnmpOpt}"); + populateconfcache($confcache,"$$target{Community}\@$$target{Host}$$target{SnmpOpt}",$$target{ipv4only},1,$$target{snmpoptions}); + $$populated{$hostname} = 1; # set cache population to true for this cycle and host + } + if (not defined readfromcache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i])) { + warn "$NOW: WARNING: Could not match host:'$$target{Community}\@$$target{Host}$$target{SnmpOpt}' ref:'$$target{IfSel}[$i]' key:'$$target{Key}[$i]'\n"; + return 'NOMATCH'; + } else { + $ifnum[$i] = ".".readfromcache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i]); + debug('snpo',"($i) Confcache Match $$target{Key}[$i] -> $ifnum[$i]"); + } + } + if ($ifnum[$i] !~ /^$|^\.\d+$/) { + warn "$NOW: WARNING: Can not determine". + " ifNumber for $$target{Community}\@$$target{Host}$$target{SnmpOpt} \tref: '$$target{IfSel}[$i]' \tkey: '$$target{Key}[$i]'\n"; + return 'NOMATCH'; + } + } + for my $i (0..1) { + # add ifget methodes call for a cross check; + for ($$target{IfSel}[$i]) { + /^Eth$/ && do { + push @OID, "ifPhysAddress".$ifnum[$i]; last + }; + /^Ip$/ && do { + push @OID, "ipAdEntIfIndex".".".$$target{Key}[$i];last + }; + /^Descr$/ && do { + push @OID, "ifDescr".$ifnum[$i]; last + }; + /^Type$/ && do { + push @OID, "ifType".$ifnum[$i]; last + }; + /^Name$/ && do { + push @OID, "ifName".$ifnum[$i]; last + }; + } + push @OID ,$$target{OID}[$i].$ifnum[$i]; + } + # we also want to know uptime and system name unless we are + if ( not defined $$cfg{nomib2} and $$cfg{logformat} ne 'rrdtool' ) { + if ( $OID[0] !~ /^cache.+$/ and + $OID[0] !~ /^\Q1.3.6.1.4.1.3495.1\E/ ) { + push @OID, qw(sysUptime sysName); + } else { + push @OID, qw(cacheUptime cacheSoftware cacheVersionId) + } + } + + # pull that data + debug('snpo',"SNMPGet from $$target{Community}\@$$target{Host}$$target{SnmpOpt} -- ".(join ",", @OID)); + my @ret; + + # make sure we have no error messages hanging round. + + $SNMP_Session::errmsg = undef; + $Net_SNMP_util::ErrorMessage = undef; + + my $targtemp = $$target{Community}.'@'.$$target{Host}.$$target{SnmpOpt}; + $targtemp = v4onlyifnecessary($targtemp, $$target{ipv4only}); + + my @snmpoids = grep !/^(Pseudo|WaLK|GeTNEXT|CnTWaLK)|IndexPOS/, @OID; + + if (defined $$cfg{singlerequest}){ +#LH local $BER::pretty_print_timeticks = 0; + foreach my $oid (@snmpoids){ + push @ret, snmpget($targtemp,$$target{snmpoptions},$oid); + } + } else { + @ret = snmpget($targtemp,$$target{snmpoptions},@snmpoids); + } + my @newret; + for (@OID) { + /^PseudoZero$/ && do { push @newret, 0; next; }; + /^PseudoOne$/ && do { push @newret, 1; next; }; + s/^WaLK(\d*)// && do { my $idx = $1 || 0; my $oid=$_;push @newret, (split /:/, (snmpwalk($targtemp,$$target{snmpoptions},$oid))[$idx],2)[1]; + debug('snpo',"snmpwalk '$oid' -> ".($newret[-1]||'UNDEF'));next}; + s/^GeTNEXT// && do { my $oid=$_;push @newret, (split /:/, snmpgetnext($targtemp,$$target{snmpoptions},$oid),2)[1]; + debug('snpo',"snmpgetnext '$oid' -> ".($newret[-1]||'UNDEF'));next}; + s/^CnTWaLK// && do { my $oid=$_;my @insts= (snmpwalk($targtemp,$$target{snmpoptions},$_)); + undef @insts if( $insts[1] || '') =~/no/i; push @newret, scalar @insts; + debug('snpo',"snmpCountwalk '$oid' -> ".($newret[-1]||'UNDEF'));next}; + /IndexPOS.*\.(\d*)/ && do { my $idx=$1; s/IndexPOS/$idx/; s/\.\d*$//; push @newret, snmpget($targtemp,$$target{snmpoptions},$_); + debug('snpo', "snmpget of oid '$_' after replacement of IndexPOS"); next}; + push @newret, shift @ret; + } + @ret = @newret; + debug('snpo',"SNMPfound -- ".(join ", ", map {"'".($_||"undef")."'"} @ret)); + $ret[-2] = $ret[-2].' '.$ret[-1] if $OID[-1] and $OID[-1] eq 'cacheVersionId'; + my $time = time; + my @final; + # lets do some reality check + for my $i (0..1) { + # some ifget methodes call for a cross check; + for ($$target{IfSel}[$i]) { + /^Eth$/ && do { + my $bin = shift @ret || 0xff; + my $eth = unpack 'H*', $bin; + my @eth; + while ($eth =~ s/^..//){ + push @eth, $&; + } + my $phys=join '-', @eth; + if ($phys ne $$target{Key}[$i]) { + debug('snpo', "($i) eth if crosscheck got $phys expected $$target{Key}[$i]"); + if (not $retry) { + $retry=1; + # remove broken entry + storeincache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i],undef); + debug('repo',"($i) goto RETRY force if cache repopulation"); + goto RETRY; + } else { + warn "$NOW: WARNING: could not match&get". + " $$target{Host}$$target{SnmpOpt}/$$target{OID}[$i] for Eth $$target{Key}[$i]\n"; + return 'NOMATCH'; + } + }; + debug ('snpo',"($i) Eth crosscheck OK"); + }; + /^Ip$/ && do { + my $if = shift @ret || 'none'; + if ($ifnum[$i] ne '.'.$if) { + debug('repo', "($i) IP if crosscheck got .$if expected $ifnum[$i]"); + if (not $retry) { + $retry=1; + # remove broken entry + storeincache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i],undef); + debug('repo',"($i) goto RETRY force if cache repopulation"); + goto RETRY; + } else { + warn "$NOW: WARNING: could not match&get". + " $$target{Host}$$target{SnmpOpt}/$$target{OID}[$i] for IP $$target{Key}[$i]\n"; + return 'NOMATCH'; + } + } + debug ('snpo',"($i) IP crosscheck OK"); + }; + /^(Descr|Name|Type)$/ && do { + my $descr = shift @ret || 'Empty'; + $descr =~ s/[\0- ]+$//; # remove excess spaces and stuff + if ($descr ne $$target{Key}[$i]) { + debug('repo', "($i) $_ if crosscheck got $descr expected $$target{Key}[$i]"); + if (not $retry) { + $retry=1; + # remove broken entry + storeincache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i],undef); + debug('repo',"($i) goto RETRY force if cache repopulation"); + goto RETRY; + } else { + warn "$NOW: WARNING: could not match&get". + " $$target{Host}$$target{SnmpOpt}/$$target{OID}[$i] for $_ '$$target{Key}[$i]'\n"; + return 'NOMATCH'; + } + } + debug ('snpo',"($i) $_ crosscheck OK"); + }; + } + # no sense continuing here ... if there is no data ... + if (defined $SNMP_Session::errmsg and $SNMP_Session::errmsg =~ /no response received/){ + $SNMP_Session::errmsg = undef; + warn "$NOW: WARNING: skipping because at least the query for $OID[0] on $$target{Host} did not succeed\n"; + return 'DEADHOST'; + } + if (defined $Net_SNMP_util::ErrorMessage and $Net_SNMP_util::ErrorMessage =~ /No response from remote/){ + $Net_SNMP_util::ErrorMessage = undef; + warn "$NOW: WARNING: skipping because at least the query for $OID[0] on $$target{Host} did not succeed\n"; + return 'DEADHOST'; + } + if ($$target{OID}[$i] =~ /if(Admin|Oper)Hack/) { + push @final, ((shift @ret) == 1) ? 1:0; + } else { + push @final, shift @ret; + } + } + + my @res = ( @final,$time, @ret); + + # Convert in and out values to integers with a user-defined subroutine + # specified by the Conversion target key + if( $target->{ Conversion } ) { + foreach my $ri( 0..1 ) { + next unless defined $res[ $ri ]; + local $SIG{__DIE__}; + my $exp = "&MRTGConversion::$target->{ Conversion }( \$res[\$ri] )"; + $res[ $ri ] = eval $exp; + warn "$NOW: WARNING: evaluation of \"$exp\" failed\n$@\n" if $@; + } + } + + # have some cleanup first, it seems that some agents + # are adding newlines to what they return + map{ $_ =~ s/\n|\r//g if defined $_ } @res; + map{ $_ =~ s/^\s+//g if defined $_ } @res; + map{ $_ =~ s/\s+$//g if defined $_ } @res; + + # in and out should be numbers only + for my $ri (0..1){ + # for folks using rrdtool I am allowing numbers + # with decimals here + if ( defined $res[$ri] and $res[$ri] !~ /^[-+]?\d+(.\d+)?$/ ) { + warn "$NOW: WARNING: Expected a number but got '$res[$ri]'\n"; + $res[$ri] = undef; + + } + } + return @res; + } + + +# read target function ... +sub readtargets ($$$) { + my ($confcache,$target,$cfg) = @_; + my $forks = $$cfg{forks}; + my $trgnum = $#{$target}+1; + if (defined $forks and $forks > 1 and $trgnum > 1){ + $forks = $trgnum if $forks > $trgnum; + my $split = int($trgnum / $forks) + 1; + my @hand; + # get them forks to work ... + for (my $i = 0; $i < $forks;$i++) { + local *D; + my $sleep_count=0; + my $pid; + do { + $pid = open(D, "-|"); + unless (defined $pid) { + warn "$NOW: WARNING cannot fork: $!\n"; + die "$NOW: ERROR bailing out after 6 failed forkattempts" + if $sleep_count++ > 6; + sleep 10; + } + } until defined $pid; + if ($pid) { # parent + $hand[$i] = *D; # funky file handle magic ... + debug ('fork',"Parent $$ after fork of child $i"); + } else { # child + debug ('fork',"Child $i ($$) after fork"); + my $res = ""; + my %deadhost; + my %populated; + for (my $ii = $i * $split; + $ii < ($i+1) * $split and $ii < $trgnum; + $ii++){ + my $targ = $$target[$ii]; + my @res; + if ($$targ{Methode} eq 'EXEC') { + @res = getexternal($$targ{Command}); + } else { # parent + if (not $deadhost{$$targ{Community}.$$targ{Host}}) { + @res = getsnmparg($confcache,$targ,$cfg,\%populated); + if ( $res[0] and $res[0] eq 'DEADHOST') { + # guess we got a blank here + @res = ( undef,undef,time,undef,undef); + $deadhost{$$targ{Community}.$$targ{Host}} = 1; + warn "$NOW: WARNING: no data for $$targ{OID}[0]&$$targ{OID}[1]:$$targ{Community}\@$$targ{Host}. Skipping further queries for Host $$targ{Host} in this round.\n" + } elsif ($res[0] and $res[0] eq 'NOMATCH'){ + @res = (undef,undef,time,undef,undef); + } + } else { + @res = ( undef,undef,time,undef,undef); + } + } + + for (my $iii=0;$iii<5;$iii++){ + if (defined $res[$iii]){ + $res .= "$res[$iii]\n"; + } else { + $res .= "##UNDEF##\n"; + } + } + } + debug ('fork',"Child $i ($$) waiting to deliver"); + print $res; # we only talk after the work has been done to + # otherwhise we might get blocked + # return updated hosts from confcache + writeconfcache($confcache,'&STDOUT') + if defined $$confcache{___updated}; + exit 0; + } + + } + # happy reaping ... + my $vin =''; # vector of pipe file-descriptors from children + for (my $i = 0; $i < $forks;$i++) { + vec($vin, fileno($hand[$i]), 1) = 1; + } + my $left = $forks; + while ($left) { + my $rout = $vin; # read vector + my $eout = $vin; # exception vector + my $nfound = select($rout, undef, $eout, undef); # no timeout + if (1 > $nfound) { + die sprintf("$NOW: ERROR: select returned %d: $!\n", $nfound); + } + for (my $i = 0; $i < $forks; $i++) { + next unless defined $hand[$i] and defined fileno($hand[$i]); +# this does not seem to work reliably +# if (vec($eout, fileno($hand[$i]), 1)) { +# die "$NOW: ERROR: fork $i has died ahead of time?\n"; +# } + next unless vec($rout, fileno($hand[$i]), 1); + + vec($vin, fileno($hand[$i]), 1) = 0; # remove this child fd + + debug ('fork',"Parent reading child $i"); + my $h = $hand[$i]; + for (my $ii = $i * $split; + $ii < ($i+1) * $split and $ii < $trgnum; + $ii++){ + my $targ = $$target[$ii]; + my @res; + for (0..4){ + my $line = <$h>; # must be a simple scalar here else it wont work + die "$NOW: ERROR: fork $i has died ahead of time ...\n" if not defined $line; + chomp $line; + # debug ('fork',"reading for $ii $line"); + $line = undef if $line eq "##UNDEF##"; + push @res,$line; + }; + + ($$targ{_IN_}, + $$targ{_OUT_}, + $$targ{_TIME_}, + $$targ{_UPTIME_}, + $$targ{_NAME_}) = @res; + if ($] >= 5.0061){ + $$targ{_IN_} = Math::BigFloat->new($$targ{_IN_}) if $$targ{_IN_}; + $$targ{_OUT_} = Math::BigFloat->new($$targ{_OUT_}) if $$targ{_OUT_}; + } + } + # feed confcache entries + my $lasthost =""; + while (<$h>){ + chomp; + my ($host,$method,$key,$value) = split (/\t/, $_); + if ($host ne $lasthost){ + debug ('fork',"start clearing confcache on first entry for target $host"); + clearfromcache($confcache,$host); + debug ('fork',"finished clearing confcache"); + } + $lasthost = $host; + storeincache($confcache,$host,$method,$key,$value); + } + close $h; + --$left; + } + } + + } else { + my %deadhost; + my %populated; + foreach my $targ (@$target) { + if ($$targ{Methode} eq 'EXEC') { + debug('snpo', "run external $$targ{Command}"); + ($$targ{_IN_}, + $$targ{_OUT_}, + $$targ{_TIME_}, + $$targ{_UPTIME_}, + $$targ{_NAME_}) = getexternal($$targ{Command}); + } elsif ($$targ{Methode} eq 'SNMP' and not $deadhost{$$targ{Host}}) { + debug('snpo', "run snmpget from $$targ{OID}[0]&$$targ{OID}[1]:$$targ{Community}\@$$targ{Host}"); + ($$targ{_IN_}, + $$targ{_OUT_}, + $$targ{_TIME_}, + $$targ{_UPTIME_}, + $$targ{_NAME_}) = getsnmparg($confcache,$targ,$cfg,\%populated); + if ( $$targ{_IN_} and $$targ{_IN_} eq 'DEADHOST') { + $$targ{_IN_} = undef; + $$targ{_TIME_} =time; + # guess we got a blank here + $deadhost{$$targ{Host}} = 1; + warn "$NOW: WARNING: no data for $$targ{OID}[0]&$$targ{OID}[1]:$$targ{Community}\@$$targ{Host}. Skipping further queries for Host $$targ{Host} in this round.\n" + } + if ( $$targ{_IN_} and $$targ{_IN_} eq 'NOMATCH') { + $$targ{_IN_} = undef; + $$targ{_TIME_} =time; + } + + } else { + $$targ{_IN_} = undef; + $$targ{_OUT_} = undef; + $$targ{_TIME_} = time; + $$targ{_UPTIME_} = undef; + $$targ{_NAME_} = undef; + } + if ($] >= 5.008 ){ + $$targ{_IN_} = new Math::BigFloat "$$targ{_IN_}" if $$targ{_IN_}; + $$targ{_OUT_} = new Math::BigFloat "$$targ{_OUT_}" if $$targ{_OUT_}; + } + + } + } + +} + +sub imggen ($) { + my $dir = shift; + if ( ! -r "$dir${main::SL}mrtg-l.png" and open W, ">$dir${main::SL}mrtg-l.png" ){ + binmode W; + print W unpack ('u', <<'UUENC'); +MB5!.1PT*&@H -24A$4@ #\ 9! , !TA.O' &%!,5$5]9GTT +M79A?8HB(9WFR;6G_=DWM=%35<5R_M[A2 6)+1T0'%F&(ZP 1%)1$%4 +M>-JMDKUOPD ,Q:E4]CH?S0PMG8-.$6L1A*YMJ,DQ%5?[_ON>$Y (2$R>= +MY;-^OF=;GNCM\SFY#_#GW$IU0WMP/#O5HSGNW8"9R)/92$NQ\Z:GUDG/@.@! +M)CP#4H\ >LT>)NB!A0]8,"]&0.(#WY92V<\$=FM4WHV? J +M=$58=%-I9VYA='5R90!D,C(W8S$dir${main::SL}mrtg-m.png" ){ + binmode W; + print W unpack ('u', <<'UUENC'); +MB5!.1PT*&@H -24A$4@ !D 9! , VQYA0 &%!,5$5]9GTT +M79A588QN9(*7:73_=DWL=%31-IC"$4"!0P$>"E&2B:I,%Z((!"(I$)X88H@GJ :A)<$Y@@*I8)YAA">("N( +M%PYD"#L+"IJ#Y4!FN(86N4',# )J"4TO*R\O!_$"@::'@HTEQ /K _&$0+P +M(.T:6@A2@62?H#B*6TRQN#,C9\ "IT15AT4VEG;F%T +M=7)E #1E,S8X-S$P,38Q-S)A96%B.3,Y8SEA,F5D-31B86(U@DWZ,@ ET +M15AT1&5L87D ,C4P(RC.$P YT15AT4&%G90 R-7@R-2LP*S"#D2 ? +) $E%3D2N0F"" +UUENC +close W; + } + if ( ! -r "$dir${main::SL}mrtg-r.png" and open W, ">$dir${main::SL}mrtg-r.png" ){ + binmode W; + print W unpack ('u', <<'UUENC'); +MB5!.1PT*&@H -24A$4@ 80 9! , ##'$3) &%!,5$4T79@\ +M8YQ5=JAPC;>:K\S\_?[5WNFZR-PB%CO! 6)+1T0'%F&(ZP !=5)1$%4 +M>-KM6$ESVE@0;BW 58: K_)"YDH(.%=YL/"511)7O""N3AS!WY_^NM^3Q)+, +M4C55DZJ1*2&]U]O7Z\,4T"]__0_A;US.O\7$$#H*PR=R[_'@#? A9X3KLW,K +MNRX_*L=MGK^#PQ)[$USW+J@'U)E,!JIGS9"+RG0^B!(NDM,ZMB'G,M_?6 +MF@^[_#7D[U)*!V_N!#+4$)>M@+Z(.J/1)PNAI4]=5K# 0_,)'Z(4UXH*A<#/ +M2SQ\3+?[=!F4Q&M?Z!8@2-?4X+O*L^Q\$PPQ;MA.R= 65RRWR/:[U&#PTVR_ +MR4*JI#3?9#E-$U)#W)5*GE&3[Y&!T%P+?QR2)U;UUOB(0@A0"-Z*.AOVC)=^ +MY=L&-T/L"$G !,KBW"V-3PU[.\8W@5N7J)W(?L'&WB5LP6_*X10OP+2L21G/ +MH7OA=#:1&M)F-1FI\^A+8B&H %;A+\U>LP8A)HN^RZOC9^BZY#=#_&10NA8" +M.5E !^R7H/2R=0G!6PH%1\%-!W@<"GU+PN4Q.Z2D6-K!2-;D]-=J"-2H?&C> +M7"B$ABCW."HV-PX@%"6$YHS<3:@.K!$K2;N"4-0A\)X'.R[?%R4$MXQ"TT9, +M7*Y:IP)!8N0L=Z0IVUJH(9!51H%B1)9A]$%,S3QB[_P0 EA;B\ W*KM/%?%Q +M%*@8'$*0K?Y@54'0*+ !XZ@&P;C'2N$W?SU&\BX"Z"YKH08ATBB,^WCHW[U5 +MCK6U$!U'H3$WA9<<1\%+2I8R"H:]@ZT=Q8%9LK1%Z!1AA< XH902XAM@X#<; +MA?;J) HL-F[!KJ*U%KC:DDD +M_NQ\.9M$RD9?TE!AX\HJ8E,+IJE^'Q6+.H1X\K#A;V\FI.B6F=)&B$+E>'9T +M51>-EPD:$VVE;#C>'CJ2:=.FJ?9>1_'20-CQ'_ESAN"5Y?Q4LT'=SFS?^.7* +MYFU%;!(IFTP>!K!Q%=8AL.)7D@8/AL;K:#3A#LW#*>0H> <0(-"]YF!:*>X2 +MUG&GD;D08Z0] ,((4XWG0O;)0-@2UTPW8@V^G0NG46BO^FNB,@I.7A$?EO.W +MK+1)V<4R&H<$E]MR-K7@GD!HB;DL!4G%N>VP;?YJCPE?\''#<:4C!?S7?-IH +MXG%'2N"B.&2Y/^M(*ZE!6PM.]L..5#4997R@5W:5"SWE* JV([U5 +M*6W8H>EBM=_O>9B8CF2C0)M:.1L)@-*(!$_\^+C?S8TF.FJJO;6?& AP20*A +MB6DR[*FS':G[7)5<_,&2F1-N>:+LBJ2^+#Z1Q1?WV02(3PR2/'*IX +M=,_1!P+ %2,A]5Q'TBSR49QN_$P5\?$9Z>2 P=;DXN8B..I(+ 2/'76SBU,P +MC9=6BD28R\^O)=+1&2E2"#/C%CX7M++)J&#SFMP*GD^F,_SCC+.'42'H+;&) +M0LH]YTU8>HM#"!3_GI@4.:R%*_3\U\EC:M+I8_I]-,T&9*3H0:4U/RYG:-+9 +M):7)9D,JX$_9*=,TW>)74"X0F@ Y50B83W?\'H!$"]80*XF;"P2P>%NUJ&2_ +M?-51V7N3)7)?E!]B_$V:OMMJ^,+CZMYPMK??9;'SXBG"J;4C%PBPVX5^T]1" +M6U-M/;K_[#>U>Q4<$?_I=?&SS>NP_(5\X=Z<FT="M4^ 0W1%6'13;V9T=V%R90! *",I26UA9V5-86=I8VL@ +M-"XR+CD@.3DO,#DO,#$@8W)IC9 +M\ "IT15AT4VEG;F%T=7)E #$R,3