#!/usr/bin/perl -w
# -*- perl -*-

=head1 NAME

snmp__if_combined - SNMP card plugin to monitor the network interfaces of any networked equipment.

=head1 APPLICABLE SYSTEMS

Any SNMP capable networked computer equipment.  Using a command such
as "munin-node-configure --snmp switch.langfeldt.net --snmpversion 2c
--snmpcommunity public | sh -x" should auto-detect all applicable
interfaces.  On a typical switch you will get one plugin pr. ethernet
port.  On a router you might get one plugin per VLAN interface.

=head1 CONFIGURATION

As a rule SNMP plugins need site specific configuration.  The default
configuration (shown here) will only work on insecure sites/devices:

   [snmp_*]
	env.version 2
        env.community public
        env.ifTypeOnly ethernetCsmacd

In general SNMP is not very secure at all unless you use SNMP version
3 which supports authentication and privacy (encryption).  But in any
case the community string for your devices should not be "public".

Please see 'perldoc Munin::Plugin::SNMP' for further configuration
information.

The ifTypeOnly is a space separated list of media types to show. To
see what interface types your switch, router, or other net thing uses
you can use this command:

  snmpwalk -c public -v 2c switch 1.3.6.1.2.1.2.2.1.3

It may show something like this:

  IF-MIB::ifType.1 = INTEGER: ethernetCsmacd(6)
  IF-MIB::ifType.4 = INTEGER: mplsTunnel(150)
  IF-MIB::ifType.5 = INTEGER: other(1)
  IF-MIB::ifType.6 = INTEGER: softwareLoopback(24)
  IF-MIB::ifType.8 = INTEGER: tunnel(131)
  IF-MIB::ifType.13 = INTEGER: propVirtual(53)
  IF-MIB::ifType.123 = INTEGER: l2vlan(135)

propVirtual or l2vlan is usually used for VLAN interfaces.  Tunnel
would normaly be for VPNs.  A minor horde of different interface types
are supposted, please see IANAifType-MIB (on your system or find with
Google) for a full list.

=head1 INTERPRETATION

A single graph is generated with all the interfaces overlaid one over
the other: incoming traffic is received on the interface from the
connected device, outgoing is sent to it instead.

Sub-graphs are created, one per interface, akin to snmp__if_multi
plugin.

=head1 MIB INFORMATION

This plugin requires the IF-MIB the standard IETF MIB for network
interfaces.

It reports the contents of the
IF-MIB::ifHCInOctets/IF-MIB::ifHCOutOctets if available,
IF-MIB::ifInOctets/IF-MIB::ifOutOctets if not.  The former are 64 bit
counters only available with SNMP 2 and later.  The later are 32 bit
counters (see FEATURES below).

=head1 MAGIC MARKERS

  #%# family=snmpauto
  #%# capabilities=snmpconf

=head1 VERSION

  $Id: snmp__if_.in 1818 2009-01-03 19:29:30Z janl $

=head1 BUGS

Should support indexing by

  - ifIndex
  - ifName
  - ifDescr
  - ifAlias
  - mac-address

(and anything else MRTG can index by) in addition to OID-index as now.

Pulling in a user definable set of ifName/ifDescr/ifAlias for textual
description and even graph_title would also be nice.

IFF we get a patch to support the .oldname attribute then we may use
that to let the operator change the indexing dynamically without data
loss.

=head1 FEATURES

You may get strange results if you use SNMPv1, or SNMPv2 on
switches that do not support 64 bit byte counters.  If the interface
traffic exceeds about 50Mbps a 32 bit byte counter will wrap around in
less than 5 minutes making the graph for the interface show random
results.

If you have a switch/device that supports 64 bit byte counters this
plugin will use them and the graph will be fine.  The graph
information will inform about this.  You must use SNMPv2c or SNMPv3 to
be able to use 64 bit counters - if the device supports them.

This problem is a feature of the device SNMP implementation or your
usage of it, it is nothing the plugin can fix.  In the future Munin
may be able to run the plugin more often than the counter wraps
around.

=head1 AUTHOR

Copyright (C) 2012 Diego Elio Pettenò.

Original snmp__if_multi: Copyright (C) 2004-2010 Jimmy Olsen, Dagfinn
Ilmari Mannsaaker, Nicolai Langfeldt, Redpill Linpro AS and others.

Original snmp__if_ plugin: Copyright (C) 2004-2009 Jimmy Olsen, Dagfinn
Ilmari Mannsaaker.

Initial SNMPv3 support by "Confusedhacker".

Documentation, porting to Munin::Plugin::SNMP and
further grooming by Nicolai Langfeldt.

Reworked to snmp__if_multi by Nicolai Langfeldt.

Reworked to snmp__if_combined by Diego Elio Pettenò.

=head1 LICENSE

GPLv2

=cut

use strict;
use Munin::Plugin;
use Munin::Plugin::SNMP;

my $response;
my $iface;

# This is the snmpwalk:
# .1.3.6.1.2.1.2.1.0 = INTEGER: 2
# .1.3.6.1.2.1.2.2.1.1.1 = INTEGER: 1
# .1.3.6.1.2.1.2.2.1.1.65539 = INTEGER: 65539
# .1.3.6.1.2.1.2.2.1.2.1 = STRING: MS TCP Loopback interface
# .1.3.6.1.2.1.2.2.1.2.65539 = STRING: Broadcom NetXtreme Gigabit Ethernet
# .1.3.6.1.2.1.2.2.1.3.1 = INTEGER: softwareLoopback(24)
# .1.3.6.1.2.1.2.2.1.3.65539 = INTEGER: ethernetCsmacd(6)
# .1.3.6.1.2.1.2.2.1.4.1 = INTEGER: 1520
# .1.3.6.1.2.1.2.2.1.4.65539 = INTEGER: 1500
# .1.3.6.1.2.1.2.2.1.5.1 = Gauge32: 10000000
# .1.3.6.1.2.1.2.2.1.5.65539 = Gauge32: 1000000000
# .1.3.6.1.2.1.2.2.1.6.1 = STRING:
# .1.3.6.1.2.1.2.2.1.6.65539 = STRING: 0:30:48:75:65:5e
# .1.3.6.1.2.1.2.2.1.7.1 = INTEGER: up(1)
# .1.3.6.1.2.1.2.2.1.7.65539 = INTEGER: up(1)
# .1.3.6.1.2.1.2.2.1.8.1 = INTEGER: up(1)
# .1.3.6.1.2.1.2.2.1.8.65539 = INTEGER: up(1)
#
# 64 bit counters:
# .1.3.6.1.2.1.31.1.1.1.6.   Counter64 ifHCInOctets
# .1.3.6.1.2.1.31.1.1.1.10.  Counter64 ifHCOutOctets

if (defined $ARGV[0] and $ARGV[0] eq "snmpconf") {
	print "require 1.3.6.1.2.1.2.2.1.1.\n";
	exit 0;
}

my $sysDescr           = '1.3.6.1.2.1.1.1.0';
my $sysLocation        = '1.3.6.1.2.1.1.6.0';
my $sysContact         = '1.3.6.1.2.1.1.4.0';
my $sysName            = '1.3.6.1.2.1.1.5.0';

my $ifOIDBase          = "1.3.6.1.2.1.2.2.1";		# ifEntry
my $ifv2OIDBase        = "1.3.6.1.2.1.31.1.1.1";	# ifXEntry

my %ifTypes = (
    other => 1,
    regular1822 => 2,
    hdh1822 => 3,
    ddnX25 => 4,
    rfc877x25 => 5,
    ethernetCsmacd => 6,
    iso88023Csmacd => 7,
    iso88024TokenBus => 8,
    iso88025TokenRing => 9,
    iso88026Man => 10,
    starLan => 11,
    proteon10Mbit => 12,
    proteon80Mbit => 13,
    hyperchannel => 14,
    fddi => 15,
    lapb => 16,
    sdlc => 17,
    ds1 => 18,
    e1 => 19,
    basicISDN => 20,
    primaryISDN => 21,
    propPointToPointSerial => 22,
    ppp => 23,
    softwareLoopback => 24,
    eon => 25,
    ethernet3Mbit => 26,
    nsip => 27,
    slip => 28,
    ultra => 29,
    ds3 => 30,
    sip => 31,
    frameRelay => 32,
    rs232 => 33,
    para => 34,
    arcnet => 35,
    arcnetPlus => 36,
    atm => 37,
    miox25 => 38,
    sonet => 39,
    x25ple => 40,
    iso88022llc => 41,
    localTalk => 42,
    smdsDxi => 43,
    frameRelayService => 44,
    v35 => 45,
    hssi => 46,
    hippi => 47,
    modem => 48,
    aal5 => 49,
    sonetPath => 50,
    sonetVT => 51,
    smdsIcip => 52,
    propVirtual => 53,
    propMultiplexor => 54,
    ieee80212 => 55,
    fibreChannel => 56,
    hippiInterface => 57,
    frameRelayInterconnect => 58,
    aflane8023 => 59,
    aflane8025 => 60,
    cctEmul => 61,
    fastEther => 62,
    isdn => 63,
    v11 => 64,
    v36 => 65,
    g703at64k => 66,
    g703at2mb => 67,
    qllc => 68,
    fastEtherFX => 69,
    channel => 70,
    ieee80211 => 71,
    ibm370parChan => 72,
    escon => 73,
    dlsw => 74,
    isdns => 75,
    isdnu => 76,
    lapd => 77,
    ipSwitch => 78,
    rsrb => 79,
    atmLogical => 80,
    ds0 => 81,
    ds0Bundle => 82,
    bsc => 83,
    async => 84,
    cnr => 85,
    iso88025Dtr => 86,
    eplrs => 87,
    arap => 88,
    propCnls => 89,
    hostPad => 90,
    termPad => 91,
    frameRelayMPI => 92,
    x213 => 93,
    adsl => 94,
    radsl => 95,
    sdsl => 96,
    vdsl => 97,
    iso88025CRFPInt => 98,
    myrinet => 99,
    voiceEM => 100,
    voiceFXO => 101,
    voiceFXS => 102,
    voiceEncap => 103,
    voiceOverIp => 104,
    atmDxi => 105,
    atmFuni => 106,
    atmIma => 107,
    pppMultilinkBundle => 108,
    ipOverCdlc => 109,
    ipOverClaw => 110,
    stackToStack => 111,
    virtualIpAddress => 112,
    mpc => 113,
    ipOverAtm => 114,
    iso88025Fiber => 115,
    tdlc => 116,
    gigabitEthernet => 117,
    hdlc => 118,
    lapf => 119,
    v37 => 120,
    x25mlp => 121,
    x25huntGroup => 122,
    trasnpHdlc => 123,
    interleave => 124,
    fast => 125,
    ip => 126,
    docsCableMaclayer => 127,
    docsCableDownstream => 128,
    docsCableUpstream => 129,
    a12MppSwitch => 130,
    tunnel => 131,
    coffee => 132,
    ces => 133,
    atmSubInterface => 134,
    l2vlan => 135,
    l3ipvlan => 136,
    l3ipxvlan => 137,
    digitalPowerline => 138,
    mediaMailOverIp => 139,
    dtm => 140,
    dcn => 141,
    ipForward => 142,
    msdsl => 143,
    ieee1394 => 144,
    'if-gsn' => 145,
    dvbRccMacLayer => 146,
    dvbRccDownstream => 147,
    dvbRccUpstream => 148,
    atmVirtual => 149,
    mplsTunnel => 150,
    srp => 151,
    voiceOverAtm => 152,
    voiceOverFrameRelay => 153,
    idsl => 154,
    compositeLink => 155,
    ss7SigLink => 156,
    propWirelessP2P => 157,
    frForward => 158,
    rfc1483 => 159,
    usb => 160,
    ieee8023adLag => 161,
    bgppolicyaccounting => 162,
    frf16MfrBundle => 163,
    h323Gatekeeper => 164,
    h323Proxy => 165,
    mpls => 166,
    mfSigLink => 167,
    hdsl2 => 168,
    shdsl => 169,
    ds1FDL => 170,
    pos => 171,
    dvbAsiIn => 172,
    dvbAsiOut => 173,
    plc => 174,
    nfas => 175,
    tr008 => 176,
    gr303RDT => 177,
    gr303IDT => 178,
    isup => 179,
    propDocsWirelessMaclayer => 180,
    propDocsWirelessDownstream => 181,
    propDocsWirelessUpstream => 182,
    hiperlan2 => 183,
    propBWAp2Mp => 184,
    sonetOverheadChannel => 185,
    digitalWrapperOverheadChannel => 186,
    aal2 => 187,
    radioMAC => 188,
    atmRadio => 189,
    imt => 190,
    mvl => 191,
    reachDSL => 192,
    frDlciEndPt => 193,
    atmVciEndPt => 194,
    opticalChannel => 195,
    opticalTransport => 196,
    propAtm => 197,
    voiceOverCable => 198,
    infiniband => 199,
    teLink => 200,
    q2931 => 201,
    virtualTg => 202,
    sipTg => 203,
    sipSig => 204,
    docsCableUpstreamChannel => 205,
    econet => 206,
    pon155 => 207,
    pon622 => 208,
    bridge => 209,
    linegroup => 210,
    voiceEMFGD => 211,
    voiceFGDEANA => 212,
    voiceDID => 213,
    mpegTransport => 214,
    sixToFour => 215,
    gtp => 216,
    pdnEtherLoop1 => 217,
    pdnEtherLoop2 => 218,
    opticalChannelGroup => 219,
    homepna => 220,
    gfp => 221,
    ciscoISLvlan => 222,
    actelisMetaLOOP => 223,
    fcipLink => 224,
    rpr => 225,
    qam => 226,
    lmp => 227,
    cblVectaStar => 228,
    docsCableMCmtsDownstream => 229,
    adsl2 => 230,
    macSecControlledIF => 231,
    macSecUncontrolledIF => 232,
    aviciOpticalEther => 233,
    atmbond => 234,
    voiceFGDOS => 235,
    mocaVersion1 => 236,
    ieee80216WMAN => 237,
    adsl2plus => 238,
    dvbRcsMacLayer => 239,
    dvbTdm => 240,
    dvbRcsTdma => 241,
    x86Laps => 242,
    wwanPP => 243,
    wwanPP2 => 244
    );

my %ifTypeByNum = map { $ifTypes{$_} => $_; } keys %ifTypes;

# Needed as globals
my $snmpinfo;
my $snmpinfoX;

sub do_collect {
    # Collect information from SNMP agent

    my $session = Munin::Plugin::SNMP->session();

    $snmpinfo = $session->get_hash(
	-baseoid => $ifOIDBase,
	-cols    =>
	{ 2  => 'ifDescr',
	  # Type: See above
	  3  => 'ifType',
	  5  => 'ifSpeed',
	  # Oper: 1) up 2) down 3) testing
	  # 4) unknown, 5) dormant 6) not present
	  # 7) lowerLayerDown
	  8  => 'ifOperStatus',
	  10 => 'ifInOctets',
	  13 => 'ifInDiscards',
	  14 => 'ifInErrors',
	  16 => 'ifOutOctets',
	  19 => 'ifOutDiscards',
	  20 => 'ifOutErrors',
	});

    # ifXEntry - SNMP v2 and up only - on some devices
    $snmpinfoX = $session->get_hash(
	-baseoid => $ifv2OIDBase,
	-cols    =>
	{ 6  => 'ifHCInOctets',
	  10 => 'ifHCOutOctets',
	  15 => 'ifHighSpeed',
	  18 => 'ifAlias',
	});
}



sub do_preprocess_if {
    my ($mediatype, $if) = @_;

    my $response = $snmpinfo->{$if}->{ifType} || 1;

    if (defined($mediatype)) {
	if (defined($mediatype->{$response})) {
	    # This is one of the interesting media types
	} else {
	    # This media type is not interesting.  Remove.
	    delete $snmpinfo->{$if} if exists $snmpinfo->{$if};
	    delete $snmpinfoX->{$if} if exists $snmpinfoX->{$if};
	    return;
	}
    }

    if (defined ($response = $snmpinfo->{$if}->{ifOperStatus})) {
	# 1 = up,  2 = down,  7 = lowerLayerDown
	if ($response == 2 or $response == 7) {
	    # Fold recognized down states into one.
	    $response = $snmpinfo->{$if}->{ifOperStatus} = 2;
	} elsif ($response != 1) {
	    # This interface is fishy, remove and forget.
	    delete $snmpinfo->{$if} if exists $snmpinfo->{$if};
	    delete $snmpinfoX->{$if} if exists $snmpinfoX->{$if};
	    return;
	}
    }
}


sub do_preprocess {
    my $mediatypes = 'ALL';

    if (exists( $ENV{'ifTypeOnly'} )) {
	$mediatypes = $ENV{'ifTypeOnly'};
    }

    my @mediatypes = split(/[ ,]+/,$mediatypes);

    # Hash of numerical media types the user finds interesting.
    my $mediatype={};

    if ($mediatypes eq 'ALL') {
	$mediatype = undef;
    } else {
	foreach my $type (@mediatypes) {
	    if (exists($ifTypes{$type})) {
		$mediatype->{$ifTypes{$type}} = 1;
	    } else {
		die "Unknown media type '$type' specified in ifTypeOnly\n";
	    }
	}
    }

    foreach my $if (keys %{$snmpinfo}) {
	do_preprocess_if($mediatype, $if);
    }
}


sub do_config_root {
    my ($host, $version) = @_;

    my $extrainfo="";

    if (defined ($snmpinfoX->{0}->{ifHCInOctets})) {
	# If we get an answer at the 64 bit OID then this switch
	# supports the extended MIB

	$extrainfo .= " This switch supports 64 bit byte counters and these are used by this plugin.";
    } else {
	# If not we only have a 32 bit counter and are lost.
	$extrainfo .= " NOTE! This switch supports only 32 bit byte counters which makes the plugin unreliable and unsuitable for most 100Mb (or faster) interfaces, where bursts are expected to exceed 50Mbps.  This means that for interfaces where much traffic is sent this plugin will report false thruputs and cannot be trusted.";

	# unless perhaps the operator can get us snmp version 2c or 3?
	$extrainfo .= " I notice that you use SNMP version 1 which does not support 64 bit quantities.  You may get better results if you switch to SNMP version 2c or 3.  Please refer to the plugin documentation."
	  if $version == 1;
    }

    print <<END;

multigraph snmp_if_combined
graph_title $host interfaces traffic
graph_args --base 1000
graph_vlabel bits in (-) / out (+) per \${graph_period}
graph_category network
graph_info This graph shows the total traffic for $host.$extrainfo
END

    print "graph_order";
    foreach my $if (sort {$a <=> $b} keys %{$snmpinfo}) {
      print " recv$if=snmp_if_combined.$if.recv send$if=snmp_if_combined.$if.send";
    }
    print "\n";

    foreach my $if (sort {$a <=> $b} keys %{$snmpinfo}) {
      my $alias = $snmpinfo->{$if}->{ifDescr} || "Interface $if";

      if (! ($alias =~ /\d+/) ) {
	# If there are no numbers in the $alias add the if index
	$alias .=" (if $if)";
      }

      print <<END;
recv$if.label $alias
recv$if.graph no
send$if.label $alias
send$if.negative recv$if
END
    }

    print <<END;

multigraph snmp_if_combined_err
graph_title $host interfaces errors
graph_args --base 1000
graph_vlabel errors in (-) / out (+) per \${graph_period}
graph_category network
END

    print "graph_order";
    foreach my $if (sort {$a <=> $b} keys %{$snmpinfo}) {
      print " recv$if=snmp_if_combined_err.$if.total_in send$if=snmp_if_combined_err.$if.total_out";
    }
    print "\n";

    foreach my $if (sort {$a <=> $b} keys %{$snmpinfo}) {
      my $alias = $snmpinfoX->{$if}->{ifAlias} || $snmpinfo->{$if}->{ifDescr} || "Interface $if";

      if (! ($alias =~ /\d+/) ) {
	# If there are no numbers in the $alias add the if index
	$alias .=" (if $if)";
      }

      print <<END;
recv$if.label $alias
recv$if.graph no
send$if.label $alias
send$if.negative recv$if
END
    }
}

sub do_config_if {
    my ($host,$version,$if) = @_;

    my $alias = $snmpinfoX->{$if}->{ifAlias} || $snmpinfo->{$if}->{ifDescr} || "Interface $if";

    if (! ($alias =~ /\d+/) ) {
	# If there are no numbers in the $alias add the if index
	$alias .=" (if $if)";
    }

    my $warn = undef;
    my $speed = 0;
    my $extrainfo = "";

    if (defined ($speed = $snmpinfoX->{$if}->{ifHighSpeed}) and $speed) {
      # Speed in 1,000,000 bits per second
      $speed = $speed * 1000000;
      $warn = $speed / 75 * 100;

      my $textspeed = scaleNumber($speed,'bps','',
				  'The interface speed is %.1f%s%s.');

      $extrainfo .= ' '.$textspeed if $textspeed;
    } elsif (defined ($speed = $snmpinfo->{$if}->{ifSpeed}) and $speed) {
        # Speed in bits pr. second
	$warn = $speed*100/75;

	my $textspeed = scaleNumber($speed,'bps','',
				    'The interface speed is %.1f%s%s.');

	$extrainfo .= " ".$textspeed if $textspeed;
    }

    $response = $snmpinfo->{$if}->{ifType};

    print <<END;
multigraph snmp_if_combined.$if
graph_title $alias traffic
graph_args --base 1000
graph_vlabel bits in (-) / out (+) per \${graph_period}
graph_category network

recv.label bps
recv.type DERIVE
recv.graph no
recv.min 0
send.label bps
send.type DERIVE
send.negative recv
send.min 0
END

    if ( defined($speed) ) {
      printf("recv.max %s\nsend.max %s\n", $speed, $speed);
    }

    if ( defined($warn) ) {
      printf("recv.warning %s\nsend.warning %s\n", $warn, $warn);
    }

    print <<END;
multigraph snmp_if_combined_err.$if
graph_title $alias errors
graph_args --base 1000
graph_vlabel errors in (-) / out (+) per \${graph_period}
graph_category network

errors_in.label $alias
errors_in.graph no
errors_in.label recv
errors_in.type DERIVE
errors_in.min 0
errors_out.label Errors
errors_out.negative errors_in
errors_out.type DERIVE
errors_out.min 0

discards_in.label $alias
discards_in.graph no
discards_in.type DERIVE
discards_in.min 0
discards_out.label Discards
discards_out.negative discards_in
discards_out.type DERIVE
discards_out.min 0

total_in.label $alias
total_in.graph no
total_in.type DERIVE
total_in.min 0
total_out.label $alias
total_out.graph no
total_out.type DERIVE
total_out.min 0
END
}

sub do_fetch_if {
    my($if) = @_;

    my $status = $snmpinfo->{$if}->{ifOperStatus} || 1;

    my $response;

    # 1 means up
    # 2 means set to down
    # Everything else we ignore.

    print "multigraph snmp_if_combined.$if\n";

    if ($status == 2) {
	# Interface is down
	print "recv.value U\n";
	print "send.value U\n";
	print "send.extinfo This interface is currently down.\n";
	goto if_errors;
    }

    $response = $snmpinfoX->{$if}->{ifHCInOctets} || $snmpinfo->{$if}->{ifInOctets};
    printf("recv.value %s\n", defined($response) ? ($response * 8) : "U");

    $response = $snmpinfoX->{$if}->{ifHCOutOctets} || $snmpinfo->{$if}->{ifOutOctets};
    printf("send.value %s\n", defined($response) ? ($response * 8) : "U");

  if_errors:

    print "multigraph snmp_if_combined_err.$if\n";

    if ($status == 2) {
	print <<END;
errors_in.value U
errors_out.value U
discards_in.value U
discards_out.value U
total_in.value U
total_out.value U
errors_out.extinfo This interface is currently down
END
	return;
    }

    my $errors = $snmpinfo->{$if}->{ifInErrors};
    my $discards = $snmpinfo->{$if}->{ifInDiscards};
    printf("errors_in %s\n".
	   "discards_in %s\n".
	   "total_in %s\n",
	   $errors || "U",
	   $discards || "U",
	   ($errors || 0) + ($discards || 0)
	  );

    $errors = $snmpinfo->{$if}->{ifOutErrors};
    $discards = $snmpinfo->{$if}->{ifOutDiscards};
    printf("errors_out %s\n".
	   "discards_out %s\n".
	   "total_out %s\n",
	   $errors || "U",
	   $discards || "U",
	   ($errors || 0) + ($discards || 0)
	  );
}

sub do_config {

    my ($host,undef,$version) = Munin::Plugin::SNMP->config_session();

    print "host_name $host\n" unless $host eq 'localhost';

    foreach my $if (sort {$a <=> $b} keys %{$snmpinfo}) {
	do_config_if($host,$version,$if);
    }

    do_config_root($host,$version);
}

# ############################## MAIN ################################

do_collect;
do_preprocess;

if ($ARGV[0] and $ARGV[0] eq "config") {
    do_config();
    exit 0;
}

foreach my $if (sort {$a <=> $b} keys %{$snmpinfo}) {
    do_fetch_if($if);
}
