#!/usr/bin/perl =pod =head1 NAME mib2zabbix.pl - SNMP MIB to Zabbix Template =head1 SYNOPSIS mib2zabbix.pl -o [OPTIONS]... Export loaded SNMP MIB OIDs to Zabbix Template XML -f, --filename=PATH output filename (default: stdout) -N, --name=STRING template name (default: OID label) -G, --group=STRING template group (default: 'Templates') -e, --enable-items enable all template items (default: disabled) -o, --oid=STRING OID tree root to export -v, --snmpver=1|2|3 SNMP version (default: 2) -p, --port=PORT SNMP UDP port number (default: 161) SNMP Version 1 or 2c specific -c, --community=STRING SNMP community string (default: 'public') SNMP Version 3 specific -L, --level=LEVEL security level (noAuthNoPriv|authNoPriv|authPriv) -n, --context=CONTEXT context name -u, --username=USERNAME security name -a, --auth=PROTOCOL authentication protocol (MD5|SHA) -A, --authpass=PASSPHRASE authentication protocol passphrase -x, --privacy=PROTOCOL privacy protocol (DES|AES) -X, --privpass=PASSPHRASE privacy passphrase Zabbix template version -z, --zabbix_ver=2|3 Zabbix Template Schema Version (default: 3) Zabbix item configuration --check-delay=SECONDS check interval in seconds (default: 60) --disc-delay=SECONDS discovery interval in seconds (default: 3600) --history=DAYS history retention in days (default: 7) --trends=DAYS trends retention in days (default: 365) Help -h, --help print this message =head1 DESCRIPTION B will export a loaded MIB tree into a Zabbix Template starting from the OID root specified. Requires: Zabbix v2.4 or 3, Perl v5, Pod::Usage, XML::Simple 2.20, Net-SNMP =head1 AUTHOR Ryan Armstrong Steven Yu -- Zabbix v2.4 support =head1 SEE ALSO Guidelines for Authors and Reviewers of MIB Documents https://www.ietf.org/rfc/rfc4181.txt Next Generation Structure of Management Information (SMIng) Mappings to the Simple Network Management Protocol (SNMP) https://tools.ietf.org/html/rfc3781 SNMP Table Basics http://www.webnms.com/snmp/help/snmpapi/snmpv3/table_handling/snmptables_basics.html Zabbix Template Schema v2.4 - https://www.zabbix.com/documentation/2.4/manual/xml_export_import/hosts v3 - https://www.zabbix.com/documentation/3.0/manual/xml_export_import/hosts =head1 SUBROUTINES =cut use strict; #use warnings; use Cwd 'abs_path'; use Data::Dumper; use Date::Format; use Encode qw(decode encode); use File::Basename; use Pod::Usage; use Getopt::Long; use SNMP; use XML::Simple; # Get path info as constants use constant SCRIPT_NAME => basename($0); use constant BASE_PATH => dirname( abs_path($0) ); use constant ZBX_SERVER_CONF => '/etc/zabbix/zabbix_server.conf'; use constant ZBX_WEB_CONF => '/etc/zabbix/web/zabbix.conf.php'; # For Zabbix type constants see: # https://www.zabbix.com/documentation/3.0/manual/api/reference/item/object # Zabbix Item status use constant ZBX_ITEM_ENABLED => 0; use constant ZBX_ITEM_DISABLED => 1; # Zabbix Item Type IDs use constant ZBX_ITEM_TYPE_SNMPV1 => 1; use constant ZBX_ITEM_TYPE_SNMPV2 => 4; use constant ZBX_ITEM_TYPE_SNMPV3 => 6; use constant ZBX_ITEM_TYPE_SNMPTRAP => 17; # Zabbix Item Value Type IDs use constant ZBX_VAL_TYPE_FLOAT => 0; use constant ZBX_VAL_TYPE_CHAR => 1; use constant ZBX_VAL_TYPE_LOG => 2; use constant ZBX_VAL_TYPE_UINT => 3; use constant ZBX_VAL_TYPE_TEXT => 4; # Zabbix Item Storage types (delta) use constant ZBX_ITEM_STORE_ASIS => 0; # Store value as is use constant ZBX_ITEM_STORE_SPEED => 1; # Delta, speed per second use constant ZBX_ITEM_STORE_CHANGE => 2; # Delta, simple change # Zabbix Item SNMPv3 constants use constant ZBX_V3_PRIV_DES => 0; use constant ZBX_V3_PRIV_AES => 1; use constant ZBX_V3_AUTH_MD5 => 0; use constant ZBX_V3_AUTH_SHA => 1; use constant ZBX_V3_SEC_NOAUTHNOPRIV => 0; use constant ZBX_V3_SEC_AUTHNOPRIV => 1; use constant ZBX_V3_SEC_AUTHPRIV => 2; # SNMP Type -> Zabbix type mapping my $type_map = { 'BITS' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type 'COUNTER' => ZBX_VAL_TYPE_UINT , # Zabbix 'Numeric Unsigned' value type for an unsigned integer 'COUNTER32' => ZBX_VAL_TYPE_UINT , # Zabbix 'Numeric Unsigned' value type for an unsigned integer 'COUNTER64' => ZBX_VAL_TYPE_UINT , # Zabbix 'Numeric Unsigned' value type for an unsigned integer 'GAUGE' => ZBX_VAL_TYPE_UINT , # Zabbix 'Numeric Unsigned' value type for an unsigned integer 'GAUGE32' => ZBX_VAL_TYPE_UINT , # Zabbix 'Numeric Unsigned' value type for an unsigned integer 'INTEGER' => ZBX_VAL_TYPE_FLOAT , # Zabbix 'Numeric Float' value type for an signed integer 'INTEGER32' => ZBX_VAL_TYPE_FLOAT , # Zabbix 'Numeric Float' value type for an signed 32 bit integer 'IPADDR' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type for an IP address 'NETADDDR' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type for a network address 'NOTIF' => ZBX_ITEM_TYPE_SNMPTRAP, # Zabbix 'SNMP Trap' item type 'TRAP' => ZBX_ITEM_TYPE_SNMPTRAP, # Zabbix 'SNMP Trap' item type 'OBJECTID' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type for an OID 'OCTETSTR' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type 'OPAQUE' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type 'TICKS' => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' for a Module 232 timestamp 'UNSIGNED32' => ZBX_VAL_TYPE_UINT # Zabbix 'Numeric Unsigned' value type for an unsigned 32bit integer }; # SNMP Version -> Zabbix item type mapping my $snmpver_map = { 1 => ZBX_ITEM_TYPE_SNMPV1, # Zabbix SNMPv1 Agent type for SNMPv1 2 => ZBX_ITEM_TYPE_SNMPV2, # Zabbix SNMPv2 Agent type for SNMPv2 3 => ZBX_ITEM_TYPE_SNMPV3 # Zabbix SNMPv3 Agent type for SNMPv3 }; # SNMP Auth config -> Zabbix item auth config my $snmpv3_auth_level_map = { 'noAuthNoPriv' => ZBX_V3_SEC_NOAUTHNOPRIV, 'authNoPriv' => ZBX_V3_SEC_AUTHNOPRIV, 'authPriv' => ZBX_V3_SEC_AUTHPRIV }; my $snmpv3_auth_protocol_map = { 'md5' => ZBX_V3_AUTH_MD5, 'sha' => ZBX_V3_AUTH_SHA }; my $snmpv3_sec_protocol_map = { 'des' => ZBX_V3_PRIV_DES, 'aes' => ZBX_V3_PRIV_AES }; # Default command line options my $opts = { delay => 60, # 1 minute check interval disc_delay => 3600, # Hourly discovery enableitems => 0, # Disable items group => 'Templates', history => 7, trends => 365, list => 0, maxdepth => -1, oid => '.1', use_macros => 0, snmpcomm => 'public', snmpport => 161, snmpver => 2, v3auth_level => 'noAuthNoPriv', v3context => '', v3user => '', v3auth_protocol => 'md5', v3auth_pass => '', v3sec_protocol => 'des', v3sec_pass => '', zabbix_ver => 3 }; # Capture calling args my $cmd = basename($0) . " @ARGV"; # Get command line options Getopt::Long::Configure( "posix_default", "bundling" ); GetOptions( 'f|filename=s' => \$opts->{filename}, # Filename to output 'N|name=s' => \$opts->{name}, # Template name 'G|group=s' => \$opts->{group}, # Template group 'o|oid=s' => \$opts->{oid}, # Root OID to export 'e|enable-items' => \$opts->{enableitems}, # Enable template items 'v|snmpver=i' => \$opts->{snmpver}, # SNMP Version 'p|port=i' => \$opts->{snmpport}, # SNMP Port 'c|community=s' => \$opts->{snmpcomm}, # SNMP Community string 'L|level=s' => \$opts->{v3auth_level}, # SNMPv3 Authentication level 'n|context=s' => \$opts->{v3context}, # SNMPv3 Security Context 'u|username=s' => \$opts->{v3user}, # SNMPv3 Authentication username 'a|auth=s' => \$opts->{v3auth_protocol}, # SNMPv3 Authentication protocol 'A|authpass=s' => \$opts->{v3auth_pass}, # SNMPv3 Authentication passphrase 'x|privacy=s' => \$opts->{v3sec_protocol}, # SNMPv3 Privacy protocol 'X|privpass=s' => \$opts->{v2sec_pass}, # SNMPv3 Privacy passphrase 'z|zabbix_ver=i' => \$opts->{zabbix_ver}, # Zabbix version 'check-delay=i' => \$opts->{delay}, # Update interval in seconds 'disc-delay=i' => \$opts->{disc_delay}, # Update interval in seconds 'history=i' => \$opts->{history}, # History retention in days 'trends=i' => \$opts->{trends}, # Trends retention in days 'h|help' => \$opts->{help} ) || pod2usage(); # Print usage if requested pod2usage( { -exitval => 0 } ) if ( $opts->{help} ); # Validate SNMPv3 settings if ( $opts->{snmpver} == 3 ) { $opts->{snmpcomm} = ''; die("Unknown authentication level '$opts->{ v3auth_level }'") unless defined( $snmpv3_auth_level_map->{ $opts->{v3auth_level} } ); $opts->{v3auth_level} = $snmpv3_auth_level_map->{ $opts->{v3auth_level} }; die("Unknown authentical protocol '$opts->{ v3auth_protocol }'") unless defined( $snmpv3_auth_protocol_map->{ $opts->{v3auth_protocol} } ); $opts->{v3auth_protocol} = $snmpv3_auth_protocol_map->{ $opts->{v3auth_protocol} }; die("Unknown privacy protocol '$opts->{ v3sec_protocol }'") unless defined( $snmpv3_sec_protocol_map->{ $opts->{v3sec_protocol} } ); $opts->{v3sec_protocol} = $snmpv3_sec_protocol_map->{ $opts->{v3sec_protocol} }; } # Base template for Template Items, Discovery Rules and Item Prototypes # See: https://www.zabbix.com/documentation/2.2/manual/api/reference/item/object my %item_base_template = ( allowed_hosts => '', applications => [], authtype => '0', delay_flex => '', ipmi_sensor => '', params => '', password => '', port => '{$SNMP_PORT}', # Use macro for SNMP UDP Port privatekey => '', publickey => '', snmp_community => $opts->{snmpver} < 3 ? '{$SNMP_COMMUNITY}' : '', # Use macro for SNMP Community string snmpv3_authpassphrase => $opts->{snmpver} == 3 ? '{$SNMP_AUTHPASS}' : '', # Use macro for SNMPv3 Authentication passphrase snmpv3_authprotocol => $opts->{snmpver} == 3 ? $opts->{v3auth_protocol} : '0', snmpv3_contextname => $opts->{snmpver} == 3 ? '{$SNMP_CONTEXT}' : '', # Use macro for SNMPv3 context name snmpv3_privpassphrase => $opts->{snmpver} == 3 ? '{$SNMP_PRIVPASS}' : '', # Use macro for SNMPv3 Privacy passphrase snmpv3_privprotocol => $opts->{snmpver} == 3 ? $opts->{v3sec_protocol} : '0', snmpv3_securitylevel => $opts->{snmpver} == 3 ? $opts->{v3auth_level} : '0', snmpv3_securityname => $opts->{snmpver} == 3 ? '{$SNMP_USER}' : '', # Use macro for SNMPv3 Username status => ( $opts->{enableitems} ? ZBX_ITEM_ENABLED : ZBX_ITEM_DISABLED ) , # Enabled (0) | Disabled (1) username => '', ); my %item_template; # Item template for standard Template items for Zabbix v3 my %item_template_v3 = ( data_type => '0', delay => $opts->{delay}, # Update internal seconds delta => '0', # Change delta formula => '1', # Multiplier factor history => $opts->{history}, # History retention in days inventory_link => '0', multiplier => '0', # Enable multiplier trends => $opts->{trends}, # Trends retention in days units => '', valuemap => '', logtimefmt => '', ); # Item template for standard Template items for Zabbix v2 my %item_template_v2 = ( data_type => '0', delay => $opts->{delay}, # Update internal seconds delta => '0', # Change delta formula => '1', # Multiplier factor history => $opts->{history}, # History retention in days inventory_link => '0', multiplier => '0', # Enable multiplier trends => $opts->{trends}, # Trends retention in days valuemap => '', units => '', ); if ( $opts->{zabbix_ver} == 3 ) { %item_template = ( %item_base_template, %item_template_v3 ); } else { %item_template = ( %item_base_template, %item_template_v2 ); } # Discovery rule template my %disc_rule_template = ( delay => $opts->{disc_delay}, lifetime => '30', filter => { evaltype => 0, formula => undef, conditions => undef }, # The following items must be created as unique refs for each item host_prototypes => [], item_prototypes => [], graph_prototypes => [], trigger_prototypes => [] ); %disc_rule_template = ( %item_base_template, %disc_rule_template ); # SNMP Trap template for Zabbix v3 my %trap_template_v3 = ( allowed_hosts => '', applications => [], authtype => 0, data_type => 0, delay => '0', delay_flex => '', delta => 0, description => '', formula => 1, history => $opts->{history}, inventory_link => 0, ipmi_sensor => '', logtimefmt => 'hh:mm:ss dd/MM/yyyy', multiplier => '0', params => '', password => '', port => '', privatekey => '', publickey => '', snmp_community => '', snmp_oid => '', snmpv3_authpassphrase => '', snmpv3_authprotocol => 0, snmpv3_contextname => '', snmpv3_privpassphrase => '', snmpv3_privprotocol => 0, snmpv3_securitylevel => 0, snmpv3_securityname => '', status => ( $opts->{enableitems} ? ZBX_ITEM_ENABLED : ZBX_ITEM_DISABLED ), trends => $opts->{trends}, type => ZBX_ITEM_TYPE_SNMPTRAP, units => '', username => '', value_type => ZBX_VAL_TYPE_LOG, valuemap => '' ); # SNMP Trap template for Zabbix v2 my %trap_template_v2 = ( allowed_hosts => '', applications => [], authtype => 0, data_type => 0, delay => '0', delay_flex => '', delta => 0, description => '', formula => 1, history => $opts->{history}, inventory_link => 0, ipmi_sensor => '', logtimefmt => 'hh:mm:ss dd/MM/yyyy', multiplier => '0', params => '', password => '', port => '', privatekey => '', publickey => '', snmp_community => '', snmp_oid => '', snmpv3_authpassphrase => '', snmpv3_authprotocol => 0, snmpv3_contextname => '', snmpv3_privpassphrase => '', snmpv3_privprotocol => 0, snmpv3_securitylevel => 0, snmpv3_securityname => '', status => ( $opts->{enableitems} ? ZBX_ITEM_ENABLED : ZBX_ITEM_DISABLED ), trends => $opts->{trends}, type => ZBX_ITEM_TYPE_SNMPTRAP, units => '', username => '', value_type => ZBX_VAL_TYPE_LOG ); # Item prototype template my %item_proto_template = ( application_prototypes => undef, ); %item_proto_template = ( %item_template, %item_proto_template ); # Global value maps array my $valuemaps = {}; =head2 utf8_santize Parameters : (string) $malformed_utf8 Returns : (string) $wellformed_utf8 Description : Returns a sanitized UTF8 string, removing incompatable characters =cut sub utf8_sanitize { my ($malformed_utf8) = @_; my $octets = decode( 'UTF-8', $malformed_utf8, Encode::FB_DEFAULT ); return encode( 'UTF-8', $octets, Encode::FB_CROAK ); } =head2 oid_path Parameters : SNMP::MIB::Node $oid Returns : (String) $oid_path Description : Returns the fully qualified textual path of a MIB node by traversing the node's parents. =cut sub oid_path { my ($oid) = @_; my $path = $oid->{label}; my $node = $oid; while ( $node = $node->{parent} ) { $path = "$node->{ label }.$path"; } return $path; } =head2 node_to_item Parameters : SNMP::MIB::Node $node (Hash) $template Returns : (Hash) $item Description : Returns a Zabbix Item hash derived from the specified MIB OID =cut sub node_to_item { my ( $node, $template ) = @_; $template = $template || \%item_template; # Create item hash my $item = { %{$template} }; $item->{name} = $node->{label}; $item->{snmp_oid} = $node->{objectID}; if ( $node->{units} ) { # Convert unit to Zabbix postfix # See 'Units' section of https://www.zabbix.com/documentation/3.0/manual/config/items/item if ( $node->{units} =~ /^seconds$/ ) { $item->{units} = 's'; } elsif ( $node->{units} =~ /^(hundreds of seconds)$/i ) { $item->{units} = 's'; $item->{multiplier} = '1'; $item->{formula} = '100'; } elsif ( $node->{units} =~ /^(milliseconds|milli-seconds)$/i ) { $item->{units} = 's'; $item->{multiplier} = '1'; $item->{formula} = '.001'; } elsif ( $node->{units} =~ /^microseconds$/i ) { $item->{units} = 's'; $item->{multiplier} = '1'; $item->{formula} = '.000001'; } elsif ( $node->{units} =~ /^(octets|bytes)$/i ) { $item->{units} = 'B'; } elsif ( $node->{units} =~ /^(k-octets|kbytes|kb)$/i ) { $item->{units} = 'B'; $item->{multiplier} = '1'; $item->{formula} = '.001'; } elsif ( $node->{units} =~ /^(bits per second)$/i ) { $item->{units} = 'b'; } elsif ( $node->{units} =~ /^(kbps|kilobits per second)$/i ) { $item->{units} = 'b'; $item->{multiplier} = '1'; $item->{formula} = '.001'; } elsif ( $node->{units} =~ /^percent$/i ) { $item->{units} = '%'; } elsif ( $node->{units} =~ /\/s$/i ) { # truncate /s (/sec will be added later) $item->{units} = substr( $node->{units}, 0, -2 ) . "/sec"; } else { # default to original $item->{units} = $node->{units}; } } # Merge in item defaults %{$item} = ( %{$template}, %{$item} ); # Create SNMP Agent item $item->{type} = $snmpver_map->{ $opts->{snmpver} }; # Item key $item->{key} = "$node->{ moduleID }.$node->{ label }"; # Map value type (Ignore for OID Table Entry Rows) if ( $node->{type} ) { $item->{value_type} = $type_map->{ $node->{type} }; if ( !defined( $item->{value_type} ) ) { print STDERR "No type mapping found for type $node->{ type } in $node->{ objectID }\n"; } } # Set storage type to Delta for MIB counter types if ( $node->{type} ~~ [ 'COUNTER', 'COUNTER32', 'COUNTER64' ] ) { $item->{delta} = ZBX_ITEM_STORE_SPEED; if ( $item->{units} =~ /^s$/ ) { $item->{units} = '/sec'; } elsif ( $item->{units} =~ /^b$/i ) { $item->{units} .= 'ps'; } else { $item->{units} .= '/sec'; } } # Translate SNMP Ticks if ( $node->{type} eq 'TICKS' ) { $item->{multiplier} = '1'; $item->{formula} = '.01'; $item->{units} = 'uptime'; } # Parse item desciption $item->{description} = utf8_sanitize( $node->{description} ); if ( $item->{description} ) { $item->{description} =~ s/^\s+|\s+$|\n//g; # Trim left/right whitespace and newlines $item->{description} =~ s/\s{2,}/ /g; # Remove padding } # Process value maps if ( $opts->{zabbix_ver} == 3 ) { if ( scalar keys %{ $node->{enums} } ) { my $map_name = "$node->{ moduleID }::$node->{ label }"; # If the map_name is longer than 64 characters truncate to 64 characters # to match maximum database field length. if ( length($map_name) > 64 ) { $map_name = substr( $map_name, 0, 61 ) . "..."; } # add template value map for Zabbix version 3 $valuemaps->{$map_name}->{'mappings'} = []; foreach ( keys %{ $node->{enums} } ) { push( @{ $valuemaps->{$map_name}->{'mappings'} }, { 'value' => $node->{enums}->{$_}, 'newvalue' => $_ } ); } # Assign value map to item $item->{valuemap} = { name => $map_name }; } else { my $map_name = "$node->{ moduleID }::$node->{ label }"; # Assign value map to item $item->{valuemap} = { name => $map_name }; } } return $item; } =head2 node_to_trapitem Parameters : SNMP::MIB::Node $node (Hash) $template Returns : (Hash) $item Description : Returns a Zabbix SNMP Trap Item hash derived from the specified MIB OID =cut sub node_to_trapitem { my ( $node, $template ) = @_; if ( $opts->{zabbix_ver} == 3 ) { $template = $template || \%trap_template_v3; } else { $template = $template || \%trap_template_v2; } # Create item hash my $item = { %{$template} }; $item->{name} = "SNMP Trap: $node->{ moduleID }::$node->{ label }"; # Merge in item defaults %{$item} = ( %{$template}, %{$item} ); # Create trap key my $oid = $node->{objectID}; $oid =~ s/\./\\./g; $item->{key} = "snmptrap[\"\\s$oid\\s\"]"; # Parse item desciption my $desc = ''; if ( $node->{description} ) { $desc = $node->{description}; $desc =~ s/^\s+|\s+$|\n//g; # Trim left/right whitespace and newlines $desc =~ s/\s{2,}/ /g; # Remove padding } # Append varbinds to description if ( defined( $node->{varbinds} ) && scalar @{ $node->{varbinds} } ) { my $varcount = scalar @{ $node->{varbinds} }; if ( $desc ne '' ) { $desc .= "\n\n"; } $desc .= "Varbinds:\n"; for ( my $i = 0 ; $i < $varcount ; $i++ ) { my $varbind_label = $node->{varbinds}[$i]; $desc .= "$i. $varbind_label"; # Try to find OID for each varbind my $varbind_path = "$node->{ moduleID }::$varbind_label"; my $varbind = $SNMP::MIB{$varbind_path}; if ( defined($varbind) ) { $desc .= " ($varbind->{ type })\n" if $varbind->{type}; if ( $varbind->{description} ) { my $vbdesc = $varbind->{description}; $vbdesc =~ s/[ \t]+/ /g; # Replace long whitespace with single space $vbdesc =~ s/^ ?/ /mg; # Prepend indent to each description line $desc .= "$vbdesc\n\n"; } } else { $desc .= "\n"; } } } $item->{description} = $desc; return $item; } =head2 node_is_current Parameters : SNMP::MIB::Node $node Returns : (int) 0|1 Description : Returns true if the specified OID is not obsolete =cut sub node_is_current { my ($node) = @_; return ( node_is_valid_trap($node) || ( defined( $node->{status} ) && $node->{status} ne 'Obsolete' ) ); } =head2 node_is_valid_scalar Parameters : SNMP::MIB::Node $node Returns : (int) 0|1 Description : Returns true if the specified OID is current, readable and defines a valid value type. =cut sub node_is_valid_scalar { my ($node) = @_; return ( node_is_current($node) && $node->{type} && ( $node->{type} eq 'NOTIF' || $node->{type} eq 'TRAP' || ( $node->{access} eq 'ReadOnly' || $node->{access} eq 'ReadWrite' ) ) ); } =head2 node_is_valid_trap Parameters : SNMP::MIB::Node $node Returns : (int) 0|1 Description : Returns true if the specified OID is an SNMP Trap =cut sub node_is_valid_trap { my ($node) = @_; return ( defined( $node->{type} ) && ( $node->{type} eq 'NOTIF' || $node->{type} eq 'TRAP' ) ); } =head2 node_is_valid_table Parameters : SNMP::MIB::Node $node Returns : (int) 0|1 Description : Returns true if the specified OID is a valid table which is current, readable and contains a single child (row definition) =cut sub node_is_valid_table { my ($node) = @_; # The MIB will define a 'SEQUENCE OF' attribute for tables but # SNMP::MIB::NODE does not expose this value. Instead, a table # node must be 'NoAccess' and have a single 'NoAccess' child return ( node_is_current($node) # Table is NoAccess && $node->{access} eq 'NoAccess' # Table has one child (row definition) && ( scalar @{ $node->{children} } ) == 1 # Table row is NoAccess && $node->{children}[0]->{access} eq 'NoAccess' # Table row defines atleast one index && ( scalar @{ $node->{children}[0]->{indexes} } ) ); } =head2 build_template Parameters : (hash) $template SNMP::MIB::NODE $node Returns : (void) Description : Traverses a loaded MIB tree from the specified OID node a populates a Zabbix Template hash with items, discovery rules, item prototypes, groups and macros. =cut sub build_template { my ( $template, $node ) = @_; # Ignore obsolete OIDs if ( node_is_current($node) ) { # Create an Item Application name for this node my $appname = "$node->{ moduleID }::$node->{ parent }->{ label }"; # Is this a scalar value OID? if ( node_is_valid_trap($node) ) { # Convert the SNMP::MIB::Node to a Zabbix Template SNMP Trap Item my $item = node_to_trapitem($node); # Add item applications to template application list $item->{applications} = [ { name => $appname } ]; $template->{apptags}->{$appname} = 1; # Add item to template push( @{ $template->{items} }, $item ); # If the snmptrap has children. foreach ( @{ $node->{children} } ) { # Convert the SNMP::MIB::Node to a Zabbix Template SNMP Trap Item my $item = node_to_trapitem($_); # Add item applications to template application list $item->{applications} = [ { name => $appname } ]; $template->{apptags}->{$appname} = 1; # Add item to template push( @{ $template->{items} }, $item ); } } elsif ( node_is_valid_scalar($node) ) { # Convert the SNMP::MIB::Node to a Zabbix Template Item hash my $item = node_to_item($node); # Append '.0' to normal SNMP OIDS $item->{snmp_oid} = "$item->{ snmp_oid }.0"; # Add item applications to template application list $item->{applications} = [ { name => $appname } ]; $template->{apptags}->{$appname} = 1; # Add item to template push( @{ $template->{items} }, $item ); } elsif ( node_is_valid_table($node) ) { # Get row OID my $table = $node; my $row = $node->{children}[0]; # Validate naming standard if ( $table->{label} !~ /Table/ ) { print STDERR "Warning: $table->{ moduleID }:: $table->{ label } appears to be a table but does not have the 'Table' suffix\n"; } if ( $row->{label} !~ /Entry/ ) { print STDERR "Warning: $row->{ moduleID }:: $row->{ label } appears to be a table entry but does not have the 'Entry; suffix\n"; } # This is a table. Build a discovery rule my $disc_rule = {}; $disc_rule = node_to_item( $row, \%disc_rule_template ); # Update discovery rule name $disc_rule->{name} = "$disc_rule->{ name } Discovery"; $disc_rule->{snmp_oid} = "discovery["; # find any *Descr column my $index = '{#SNMPINDEX}'; foreach my $column ( @{ $row->{children} } ) { if ( node_is_valid_scalar($column) ) { if ( $column->{label} =~ m/Descr$/ ) { $disc_rule->{snmp_oid} .= "{#SNMPVALUE},$column->{ objectID },"; $index = '{#SNMPVALUE}'; } } } # Define macros in discovery key up to 255 chars # See: https://www.zabbix.com/documentation/3.0/manual/discovery/low_level_discovery#discovery_of_snmp_oids foreach my $column ( @{ $row->{children} } ) { if ( node_is_valid_scalar($column) ) { my $new_snmp_oid = $disc_rule->{snmp_oid} . "{#" . uc( $column->{label} ) . "}," . $column->{objectID} . ","; if ( length($new_snmp_oid) <= 255 ) { $disc_rule->{snmp_oid} = $new_snmp_oid; } } } $disc_rule->{snmp_oid} = substr( $disc_rule->{snmp_oid}, 0, -1 ) . "]"; # Fetch an arbitrary column OID for Zabbix to use for discovery my $index_oid = $row->{children}[0]; if ( !defined($index_oid) ) { print STDERR "No index found for table $table->{ moduleID}::$table->{ label } ($table->{ objectID })\n"; } else { # Remove unrequired fields delete( $disc_rule->{applications} ); delete( $disc_rule->{data_type} ); # Create new array for prototypes $disc_rule->{item_prototypes} = []; # Add prototypes for each row column foreach my $column ( @{ $row->{children} } ) { if ( node_is_valid_scalar($column) ) { if ( my $proto = node_to_item( $column, \%item_proto_template ) ) { $proto->{name} = "$proto->{ name } for $index"; $proto->{key} = "$column->{ label }\[$index]"; $proto->{snmp_oid} = "$proto->{ snmp_oid }.{#SNMPINDEX}"; # Add item applications to template application list $proto->{applications} = [ { name => $appname } ]; $template->{apptags}->{$appname} = 1; push( @{ $disc_rule->{item_prototypes} }, $proto ); } } } # Add discovery rule to template push( @{ $template->{discovery_rules} }, $disc_rule ); } } } else { # Parse children foreach ( @{ $node->{children} } ) { build_template( $template, $_ ); } } } # Initialize net-snmp $SNMP::save_descriptions = 1; SNMP::initMib(); # Verify the specified OID exists if ( $opts->{oid} !~ m/^\./ ) { $opts->{oid} = "." . $opts->{oid}; } my $oid_root = $SNMP::MIB{ $opts->{oid} }; if ( !$oid_root || $oid_root->{objectID} ne $opts->{oid} ) { print STDERR "OID $opts->{ oid } not found in MIB tree.\n"; exit 1; # Build a Zabbix template } else { my $suffix = $opts->{snmpver} > 2 ? " v$opts->{ snmpver }" : ''; my $template_name = $opts->{name} || "Template $oid_root->{ moduleID } - $oid_root->{ label }$suffix"; my $template = { name => $template_name, template => $template_name, description => "Generated by mib2zabbix", apptags => {}, applications => [], discovery_rules => [], groups => [ { name => $opts->{group} } ], items => [], macros => [ { macro => '{$MIB2ZABBIX_CMD}', value => $cmd }, { macro => '{$OID}', value => "$oid_root->{ objectID }" }, { macro => '{$OID_PATH}', value => oid_path($oid_root) }, { macro => '{$OID_MOD}', value => $oid_root->{moduleID} }, { macro => '{$SNMP_PORT}', value => $opts->{snmpport} } ] }; # Add SNMP connection macros if ( $opts->{snmpver} < 3 ) { push( @{ $template->{macros} }, { macro => '{$SNMP_COMMUNITY}', value => $opts->{snmpcomm} } ); } elsif ( $opts->{snmpver} == 3 ) { push( @{ $template->{macros} }, { macro => '{$SNMP_USER}', value => $opts->{v3user} } ); push( @{ $template->{macros} }, { macro => '{$SNMP_CONTEXT}', value => $opts->{v3context} } ); push( @{ $template->{macros} }, { macro => '{$SNMP_AUTHPASS}', value => $opts->{v3auth_pass} } ); push( @{ $template->{macros} }, { macro => '{$SNMP_PRIVPASS}', value => $opts->{v3sec_pass} } ); } build_template( $template, $oid_root, 0 ); # Convert applications hash to array @{ $template->{applications} } = map { { name => $_ } } keys %{ $template->{apptags} }; delete( $template->{apptags} ); # Build XML document my $time = time(); # version3 my $output_v3 = { version => '3.0', date => time2str( "%Y-%m-%dT%H:%M:%SZ", $time ), groups => $template->{groups}, templates => [$template], triggers => [], graphs => [], value_maps => [$valuemaps] }; # version 2 my $output_v2 = { version => '2.0', date => time2str( "%Y-%m-%dT%H:%M:%SZ", $time ), groups => $template->{groups}, templates => [$template], triggers => [], graphs => [] }; # Output stream my $fh = *STDOUT; if ( $opts->{filename} ) { open( $fh, ">$opts->{ filename }" ) or die "$!"; } # Output XML if ( $opts->{zabbix_ver} == 3 ) { XMLout( $output_v3, OutputFile => \$fh, XMLDecl => "", RootName => 'zabbix_export', NoAttr => 1, SuppressEmpty => undef, GroupTags => { 'applications' => 'application', 'groups' => 'group', 'templates' => 'template', 'items' => 'item', 'macros' => 'macro', 'discovery_rules' => 'discovery_rule', 'item_prototypes' => 'item_prototype', 'trigger_prototypes' => 'trigger_prototype', 'graph_prototypes' => 'graph_prototype', 'host_prototypes' => 'host_prototype', 'value_maps' => %{$valuemaps} ? 'value_map' : undef, 'mappings' => 'mapping' } ); } else { XMLout( $output_v2, OutputFile => \$fh, XMLDecl => "", RootName => 'zabbix_export', NoAttr => 1, SuppressEmpty => undef, GroupTags => { 'applications' => 'application', 'groups' => 'group', 'templates' => 'template', 'items' => 'item', 'macros' => 'macro', 'discovery_rules' => 'discovery_rule', 'item_prototypes' => 'item_prototype', 'trigger_prototypes' => 'trigger_prototype', 'graph_prototypes' => 'graph_prototype', 'host_prototypes' => 'host_prototype' } ); } if ( $opts->{filename} ) { close $fh; } }