mib2zabbix/mib2zabbix.pl
Sven Putteneers 581a205121 Make ordering of mappings and application stable
Perl 5.8.1 added hash randomisation (see
https://metacpan.org/release/JHI/perl-5.8.1/view/pod/perldelta.pod#Hash-Randomisation).

The effect is that multiple invocations of `keys %hash` return the hash keys in
different ordering, even between separate runs of the script with the same
inputs.

As a result, running the script multiple times, yielded non-identical XML
output, since the order of the mappings and application lists is different
every time.

By first sorting the hash keys for the mapping and application hashes before
iterating over them, multiple runs of the script with the same inputs result in
identical output.
2022-07-15 11:51:34 +02:00

926 lines
33 KiB
Perl
Executable file

#!/usr/bin/perl
=pod
=head1 NAME
mib2zabbix.pl - SNMP MIB to Zabbix Template
=head1 SYNOPSIS
mib2zabbix.pl -o <OID> [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 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)
-h, --help print this message
=head1 DESCRIPTION
B<mib2zabbix.pl> will export a loaded MIB tree into a Zabbix Template starting
from the OID root specified.
Requires: Zabbix v3, Perl v5, Pod::Usage, XML::Simple, Net-SNMP
=head1 AUTHOR
Ryan Armstrong <ryan@cavaliercoder.com>
=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
=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 a signed integer
'INTEGER32' => ZBX_VAL_TYPE_FLOAT, # Zabbix 'Numeric Float' value type for a 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 => ''
};
# 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
'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 } = '';
if (defined $snmpv3_auth_level_map->{ lc($opts->{ v3auth_level }) }) {
$opts->{ v3auth_level } = $snmpv3_auth_level_map->{ lc($opts->{ v3auth_level }) }
}
else {
die("Unknown authentication level '$opts->{ v3auth_level }'");
}
if (defined $snmpv3_auth_protocol_map->{ lc($opts->{ v3auth_protocol }) }) {
$opts->{ v3auth_protocol } = $snmpv3_auth_protocol_map->{ lc($opts->{ v3auth_protocol }) }
}
else {
die("Unknown authentication protocol '$opts->{ v3auth_protocol }'");
}
if (defined $snmpv3_sec_protocol_map->{ lc($opts->{ v3sec_protocol }) }) {
$opts->{ v3sec_protocol } = $snmpv3_sec_protocol_map->{ lc($opts->{ v3sec_protocol }) }
}
else {
die("Unknown privacy protocol '$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 => '',
);
# Item template for standard Template items
my %item_template = (
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 = (%item_base_template, %item_template);
# 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
my %trap_template = (
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 => ''
);
# 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 (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
$valuemaps->{ $map_name }->{ 'mappings' } = [];
foreach(sort keys %{ $node->{ enums } }) {
push(@{ $valuemaps->{ $map_name }->{ 'mappings' } }, {
'value' => $node->{ enums }->{ $_ },
'newvalue' => $_
});
}
# 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) = @_;
$template = $template || \%trap_template;
# 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 SNMP $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 => $_ } } sort keys %{ $template->{ apptags } };
delete($template->{ apptags });
# Build XML document
my $time = time();
my $output = {
version => '3.0',
date => time2str("%Y-%m-%dT%H:%M:%SZ", $time),
groups => $template->{ groups },
templates => [$template],
triggers => [],
graphs => [],
value_maps => [$valuemaps]
};
# Output stream
my $fh = *STDOUT;
if ($opts->{ filename }) {
open($fh, ">$opts->{ filename }") or die "$!";
}
# Output XML
XMLout($output,
OutputFile => \$fh,
XMLDecl => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
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'
}
);
if ($opts->{ filename }) {
close $fh;
}
}