├── README.md └── mib2zabbix.pl /README.md: -------------------------------------------------------------------------------- 1 | # mib2zabbix 2 | 3 | This Perl script will generate a Zabbix v3 Template in XML format from an OID 4 | tree in a SNMP MIB file. 5 | 6 | ### Usage 7 | 8 | mib2zabbix.pl -o [OPTIONS]... 9 | 10 | Export loaded SNMP MIB OIDs to Zabbix Template XML 11 | 12 | -f, --filename=PATH output filename (default: stdout) 13 | 14 | -N, --name=STRING template name (default: OID label) 15 | -G, --group=STRING template group (default: 'Templates') 16 | -e, --enable-items enable all template items (default: disabled) 17 | 18 | -o, --oid=STRING OID tree root to export (must start with '.') 19 | 20 | -v, --snmpver=1|2|3 SNMP version (default: 2) 21 | -p, --port=PORT SNMP UDP port number (default: 161) 22 | 23 | SNMP Version 1 or 2c specific 24 | 25 | -c, --community=STRING SNMP community string (default: 'public') 26 | 27 | SNMP Version 3 specific 28 | 29 | -L, --level=LEVEL security level (noAuthNoPriv|authNoPriv|authPriv) 30 | -n, --context=CONTEXT context name 31 | -u, --username=USERNAME security name 32 | -a, --auth=PROTOCOL authentication protocol (MD5|SHA) 33 | -A, --authpass=PASSPHRASE authentication protocol passphrase 34 | -x, --privacy=PROTOCOL privacy protocol (DES|AES) 35 | -X, --privpass=PASSPHRASE privacy passphrase 36 | 37 | Zabbix item configuration 38 | 39 | --check-delay=SECONDS check interval in seconds (default: 60) 40 | --disc-delay=SECONDS discovery interval in seconds (default: 3600) 41 | --history=DAYS history retention in days (default: 7) 42 | --trends=DAYS trends retention in days (default: 365) 43 | 44 | -h, --help print this message 45 | 46 | ### Requirements 47 | 48 | * Zabbix v3+ 49 | * Perl v5 50 | * Pod::Usage 51 | * XML::Simple 52 | * Net-SNMP 53 | * Correctly configured [MIB files](http://net-snmp.sourceforge.net/tutorial/tutorial-5/commands/mib-options.html) 54 | 55 | ### Installation 56 | 57 | Install from GitHub: 58 | 59 | # install prerequisites on Ubuntu: 60 | apt-get install perl libxml-simple-perl libsnmp-perl 61 | 62 | # install prerequisites on RHEL family: 63 | yum install "perl(SNMP)" "perl(XML::Simple)" 64 | 65 | # assumes ~/bin exists and is in $PATH, so adjust accordingly! 66 | curl -sL -o ~/bin/mib2zabbix https://raw.githubusercontent.com/cavaliercoder/mib2zabbix/master/mib2zabbix.pl 67 | chmod +x ~/bin/mib2zabbix 68 | 69 | 70 | ### Translations 71 | 72 | This table describes how MIB elements are translated into Zabbix template 73 | elements: 74 | 75 | * Scalar OID -> Zabbix SNMP Item 76 | * Table OID -> Zabbix SNMP Discovery Rule 77 | * Table Column OID -> Zabbix Discovery Prototype 78 | * Trap/Notification OID -> Zabbix SNMP Trap Item 79 | * OID Enums -> Zabbix Value Maps 80 | 81 | ### License 82 | 83 | mib2zabbix - SNMP Template Generator for Zabbix 84 | Copyright (C) 2016 - Ryan Armstrong 85 | 86 | This program is free software; you can redistribute it and/or modify 87 | it under the terms of the GNU General Public License as published by 88 | the Free Software Foundation; either version 2 of the License, or 89 | (at your option) any later version. 90 | 91 | This program is distributed in the hope that it will be useful, 92 | but WITHOUT ANY WARRANTY; without even the implied warranty of 93 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 94 | GNU General Public License for more details. 95 | 96 | You should have received a copy of the GNU General Public License 97 | along with this program; if not, write to the Free Software 98 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 99 | -------------------------------------------------------------------------------- /mib2zabbix.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | =pod 4 | 5 | =head1 NAME 6 | 7 | mib2zabbix.pl - SNMP MIB to Zabbix Template 8 | 9 | =head1 SYNOPSIS 10 | 11 | mib2zabbix.pl -o [OPTIONS]... 12 | 13 | Export loaded SNMP MIB OIDs to Zabbix Template XML 14 | 15 | -f, --filename=PATH output filename (default: stdout) 16 | 17 | -N, --name=STRING template name (default: OID label) 18 | -G, --group=STRING template group (default: 'Templates') 19 | -e, --enable-items enable all template items (default: disabled) 20 | 21 | -o, --oid=STRING OID tree root to export 22 | 23 | -v, --snmpver=1|2|3 SNMP version (default: 2) 24 | -p, --port=PORT SNMP UDP port number (default: 161) 25 | 26 | SNMP Version 1 or 2c specific 27 | 28 | -c, --community=STRING SNMP community string (default: 'public') 29 | 30 | SNMP Version 3 specific 31 | 32 | -L, --level=LEVEL security level (noAuthNoPriv|authNoPriv|authPriv) 33 | -n, --context=CONTEXT context name 34 | -u, --username=USERNAME security name 35 | -a, --auth=PROTOCOL authentication protocol (MD5|SHA) 36 | -A, --authpass=PASSPHRASE authentication protocol passphrase 37 | -x, --privacy=PROTOCOL privacy protocol (DES|AES) 38 | -X, --privpass=PASSPHRASE privacy passphrase 39 | 40 | Zabbix item configuration 41 | 42 | --check-delay=SECONDS check interval in seconds (default: 60) 43 | --disc-delay=SECONDS discovery interval in seconds (default: 3600) 44 | --history=DAYS history retention in days (default: 7) 45 | --trends=DAYS trends retention in days (default: 365) 46 | 47 | -h, --help print this message 48 | 49 | =head1 DESCRIPTION 50 | 51 | B will export a loaded MIB tree into a Zabbix Template starting 52 | from the OID root specified. 53 | 54 | Requires: Zabbix v3, Perl v5, Pod::Usage, XML::Simple, Net-SNMP 55 | 56 | =head1 AUTHOR 57 | 58 | Ryan Armstrong 59 | 60 | =head1 SEE ALSO 61 | 62 | Guidelines for Authors and Reviewers of MIB Documents 63 | https://www.ietf.org/rfc/rfc4181.txt 64 | 65 | Next Generation Structure of Management Information (SMIng) Mappings to the 66 | Simple Network Management Protocol (SNMP) 67 | https://tools.ietf.org/html/rfc3781 68 | 69 | SNMP Table Basics 70 | http://www.webnms.com/snmp/help/snmpapi/snmpv3/table_handling/snmptables_basics.html 71 | 72 | =head1 SUBROUTINES 73 | 74 | =cut 75 | 76 | use strict; 77 | #use warnings; 78 | 79 | use Cwd 'abs_path'; 80 | use Data::Dumper; 81 | use Date::Format; 82 | use Encode qw(decode encode); 83 | use File::Basename; 84 | use Pod::Usage; 85 | use Getopt::Long; 86 | use SNMP; 87 | use XML::Simple; 88 | 89 | # Get path info as constants 90 | use constant SCRIPT_NAME => basename($0); 91 | use constant BASE_PATH => dirname(abs_path($0)); 92 | 93 | use constant ZBX_SERVER_CONF => '/etc/zabbix/zabbix_server.conf'; 94 | use constant ZBX_WEB_CONF => '/etc/zabbix/web/zabbix.conf.php'; 95 | 96 | # For Zabbix type constants see: 97 | # https://www.zabbix.com/documentation/3.0/manual/api/reference/item/object 98 | # Zabbix Item status 99 | use constant ZBX_ITEM_ENABLED => 0; 100 | use constant ZBX_ITEM_DISABLED => 1; 101 | 102 | # Zabbix Item Type IDs 103 | use constant ZBX_ITEM_TYPE_SNMPV1 => 1; 104 | use constant ZBX_ITEM_TYPE_SNMPV2 => 4; 105 | use constant ZBX_ITEM_TYPE_SNMPV3 => 6; 106 | use constant ZBX_ITEM_TYPE_SNMPTRAP => 17; 107 | 108 | # Zabbix Item Value Type IDs 109 | use constant ZBX_VAL_TYPE_FLOAT => 0; 110 | use constant ZBX_VAL_TYPE_CHAR => 1; 111 | use constant ZBX_VAL_TYPE_LOG => 2; 112 | use constant ZBX_VAL_TYPE_UINT => 3; 113 | use constant ZBX_VAL_TYPE_TEXT => 4; 114 | 115 | # Zabbix Item Storage types (delta) 116 | use constant ZBX_ITEM_STORE_ASIS => 0; # Store value as is 117 | use constant ZBX_ITEM_STORE_SPEED => 1; # Delta, speed per second 118 | use constant ZBX_ITEM_STORE_CHANGE => 2; # Delta, simple change 119 | 120 | # Zabbix Item SNMPv3 constants 121 | use constant ZBX_V3_PRIV_DES => 0; 122 | use constant ZBX_V3_PRIV_AES => 1; 123 | use constant ZBX_V3_AUTH_MD5 => 0; 124 | use constant ZBX_V3_AUTH_SHA => 1; 125 | use constant ZBX_V3_SEC_NOAUTHNOPRIV => 0; 126 | use constant ZBX_V3_SEC_AUTHNOPRIV => 1; 127 | use constant ZBX_V3_SEC_AUTHPRIV => 2; 128 | 129 | # SNMP Type -> Zabbix type mapping 130 | my $type_map = { 131 | 'BITS' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type 132 | 'COUNTER' => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' value type for an unsigned integer 133 | 'COUNTER32' => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' value type for an unsigned integer 134 | 'COUNTER64' => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' value type for an unsigned integer 135 | 'GAUGE' => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' value type for an unsigned integer 136 | 'GAUGE32' => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' value type for an unsigned integer 137 | 'INTEGER' => ZBX_VAL_TYPE_FLOAT, # Zabbix 'Numeric Float' value type for a signed integer 138 | 'INTEGER32' => ZBX_VAL_TYPE_FLOAT, # Zabbix 'Numeric Float' value type for a signed 32 bit integer 139 | 'IPADDR' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type for an IP address 140 | 'NETADDDR' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type for a network address 141 | 'NOTIF' => ZBX_ITEM_TYPE_SNMPTRAP, # Zabbix 'SNMP Trap' item type 142 | 'TRAP' => ZBX_ITEM_TYPE_SNMPTRAP, # Zabbix 'SNMP Trap' item type 143 | 'OBJECTID' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type for an OID 144 | 'OCTETSTR' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type 145 | 'OPAQUE' => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type 146 | 'TICKS' => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' for a Module 232 timestamp 147 | 'UNSIGNED32' => ZBX_VAL_TYPE_UINT # Zabbix 'Numeric Unsigned' value type for an unsigned 32bit integer 148 | }; 149 | 150 | # SNMP Version -> Zabbix item type mapping 151 | my $snmpver_map = { 152 | 1 => ZBX_ITEM_TYPE_SNMPV1, # Zabbix SNMPv1 Agent type for SNMPv1 153 | 2 => ZBX_ITEM_TYPE_SNMPV2, # Zabbix SNMPv2 Agent type for SNMPv2 154 | 3 => ZBX_ITEM_TYPE_SNMPV3 # Zabbix SNMPv3 Agent type for SNMPv3 155 | }; 156 | 157 | # SNMP Auth config -> Zabbix item auth config 158 | my $snmpv3_auth_level_map = { 159 | 'noauthnopriv' => ZBX_V3_SEC_NOAUTHNOPRIV, 160 | 'authnopriv' => ZBX_V3_SEC_AUTHNOPRIV, 161 | 'authpriv' => ZBX_V3_SEC_AUTHPRIV 162 | }; 163 | 164 | my $snmpv3_auth_protocol_map = { 165 | 'md5' => ZBX_V3_AUTH_MD5, 166 | 'sha' => ZBX_V3_AUTH_SHA 167 | }; 168 | 169 | my $snmpv3_sec_protocol_map = { 170 | 'des' => ZBX_V3_PRIV_DES, 171 | 'aes' => ZBX_V3_PRIV_AES 172 | }; 173 | 174 | # Default command line options 175 | my $opts = { 176 | delay => 60, # 1 minute check interval 177 | disc_delay => 3600, # Hourly discovery 178 | enableitems => 0, # Disable items 179 | group => 'Templates', 180 | history => 7, 181 | trends => 365, 182 | list => 0, 183 | maxdepth => -1, 184 | oid => '.1', 185 | use_macros => 0, 186 | snmpcomm => 'public', 187 | snmpport => 161, 188 | snmpver => 2, 189 | v3auth_level => 'noAuthNoPriv', 190 | v3context => '', 191 | v3user => '', 192 | v3auth_protocol => 'md5', 193 | v3auth_pass => '', 194 | v3sec_protocol => 'des', 195 | v3sec_pass => '' 196 | }; 197 | 198 | # Capture calling args 199 | my $cmd = basename($0) . " @ARGV"; 200 | 201 | # Get command line options 202 | Getopt::Long::Configure ("posix_default", "bundling"); 203 | GetOptions( 204 | 'f|filename=s' => \$opts->{ filename }, # Filename to output 205 | 206 | 'N|name=s' => \$opts->{ name }, # Template name 207 | 'G|group=s' => \$opts->{ group }, # Template group 208 | 'o|oid=s' => \$opts->{ oid }, # Root OID to export 209 | 210 | 'e|enable-items' => \$opts->{ enableitems }, # Enable template items 211 | 212 | 'v|snmpver=i' => \$opts->{ snmpver }, # SNMP Version 213 | 'p|port=i' => \$opts->{ snmpport }, # SNMP Port 214 | 215 | 'c|community=s' => \$opts->{ snmpcomm }, # SNMP Community string 216 | 217 | 'L|level=s' => \$opts->{ v3auth_level }, # SNMPv3 Authentication level 218 | 'n|context=s' => \$opts->{ v3context }, # SNMPv3 Security Context 219 | 'u|username=s' => \$opts->{ v3user }, # SNMPv3 Authentication username 220 | 'a|auth=s' => \$opts->{ v3auth_protocol }, # SNMPv3 Authentication protocol 221 | 'A|authpass=s' => \$opts->{ v3auth_pass }, # SNMPv3 Authentication passphrase 222 | 'x|privacy=s' => \$opts->{ v3sec_protocol }, # SNMPv3 Privacy protocol 223 | 'X|privpass=s' => \$opts->{ v2sec_pass}, # SNMPv3 Privacy passphrase 224 | 225 | 'check-delay=i' => \$opts->{ delay }, # Update interval in seconds 226 | 'disc-delay=i' => \$opts->{ disc_delay }, # Update interval in seconds 227 | 'history=i' => \$opts->{ history }, # History retention in days 228 | 'trends=i' => \$opts->{ trends }, # Trends retention in days 229 | 230 | 'h|help' => \$opts->{ help } 231 | ) || pod2usage(); 232 | 233 | # Print usage if requested 234 | pod2usage({ -exitval => 0 }) if ($opts->{ help }); 235 | 236 | # Validate SNMPv3 settings 237 | if ($opts->{ snmpver } == 3) { 238 | $opts->{ snmpcomm } = ''; 239 | if (defined $snmpv3_auth_level_map->{ lc($opts->{ v3auth_level }) }) { 240 | $opts->{ v3auth_level } = $snmpv3_auth_level_map->{ lc($opts->{ v3auth_level }) } 241 | } 242 | else { 243 | die("Unknown authentication level '$opts->{ v3auth_level }'"); 244 | } 245 | if (defined $snmpv3_auth_protocol_map->{ lc($opts->{ v3auth_protocol }) }) { 246 | $opts->{ v3auth_protocol } = $snmpv3_auth_protocol_map->{ lc($opts->{ v3auth_protocol }) } 247 | } 248 | else { 249 | die("Unknown authentication protocol '$opts->{ v3auth_protocol }'"); 250 | } 251 | if (defined $snmpv3_sec_protocol_map->{ lc($opts->{ v3sec_protocol }) }) { 252 | $opts->{ v3sec_protocol } = $snmpv3_sec_protocol_map->{ lc($opts->{ v3sec_protocol }) } 253 | } 254 | else { 255 | die("Unknown privacy protocol '$opts->{ v3sec_protocol }'"); 256 | } 257 | } 258 | 259 | # Base template for Template Items, Discovery Rules and Item Prototypes 260 | # See: https://www.zabbix.com/documentation/2.2/manual/api/reference/item/object 261 | my %item_base_template = ( 262 | allowed_hosts => '', 263 | applications => [], 264 | authtype => '0', 265 | delay_flex => '', 266 | ipmi_sensor => '', 267 | params => '', 268 | password => '', 269 | port => '{$SNMP_PORT}', # Use macro for SNMP UDP Port 270 | privatekey => '', 271 | publickey => '', 272 | snmp_community => $opts->{ snmpver } < 3 ? '{$SNMP_COMMUNITY}' : '', # Use macro for SNMP Community string 273 | snmpv3_authpassphrase => $opts->{ snmpver } == 3 ? '{$SNMP_AUTHPASS}' : '', # Use macro for SNMPv3 Authentication passphrase 274 | snmpv3_authprotocol => $opts->{ snmpver } == 3 ? $opts->{ v3auth_protocol } : '0', 275 | snmpv3_contextname => $opts->{ snmpver } == 3 ? '{$SNMP_CONTEXT}' : '', # Use macro for SNMPv3 context name 276 | snmpv3_privpassphrase => $opts->{ snmpver } == 3 ? '{$SNMP_PRIVPASS}' : '', # Use macro for SNMPv3 Privacy passphrase 277 | snmpv3_privprotocol => $opts->{ snmpver } == 3 ? $opts->{ v3sec_protocol } : '0', 278 | snmpv3_securitylevel => $opts->{ snmpver } == 3 ? $opts->{ v3auth_level } : '0', 279 | snmpv3_securityname => $opts->{ snmpver } == 3 ? '{$SNMP_USER}' : '', # Use macro for SNMPv3 Username 280 | status => ($opts->{ enableitems } ? ZBX_ITEM_ENABLED : ZBX_ITEM_DISABLED), # Enabled (0) | Disabled (1) 281 | username => '', 282 | ); 283 | 284 | # Item template for standard Template items 285 | my %item_template = ( 286 | data_type => '0', 287 | delay => $opts->{ delay }, # Update internal seconds 288 | delta => '0', # Change delta 289 | formula => '1', # Multiplier factor 290 | history => $opts->{ history }, # History retention in days 291 | inventory_link => '0', 292 | multiplier => '0', # Enable multiplier 293 | trends => $opts->{ trends }, # Trends retention in days 294 | units => '', 295 | valuemap => '', 296 | logtimefmt => '', 297 | ); 298 | %item_template = (%item_base_template, %item_template); 299 | 300 | # Discovery rule template 301 | my %disc_rule_template = ( 302 | delay => $opts->{ disc_delay }, 303 | lifetime => '30', 304 | filter => { 305 | evaltype => 0, 306 | formula => undef, 307 | conditions => undef 308 | }, 309 | 310 | # The following items must be created as unique refs for each item 311 | host_prototypes => [], 312 | item_prototypes => [], 313 | graph_prototypes => [], 314 | trigger_prototypes => [] 315 | ); 316 | %disc_rule_template = (%item_base_template, %disc_rule_template); 317 | 318 | # SNMP Trap template 319 | my %trap_template = ( 320 | allowed_hosts => '', 321 | applications => [], 322 | authtype => 0, 323 | data_type => 0, 324 | delay => '0', 325 | delay_flex => '', 326 | delta => 0, 327 | description => '', 328 | formula => 1, 329 | history => $opts->{ history }, 330 | inventory_link => 0, 331 | ipmi_sensor => '', 332 | logtimefmt => 'hh:mm:ss dd/MM/yyyy', 333 | multiplier => '0', 334 | params => '', 335 | password => '', 336 | port => '', 337 | privatekey => '', 338 | publickey => '', 339 | snmp_community => '', 340 | snmp_oid => '', 341 | snmpv3_authpassphrase => '', 342 | snmpv3_authprotocol => 0, 343 | snmpv3_contextname => '', 344 | snmpv3_privpassphrase => '', 345 | snmpv3_privprotocol => 0, 346 | snmpv3_securitylevel => 0, 347 | snmpv3_securityname => '', 348 | status => ($opts->{ enableitems } ? ZBX_ITEM_ENABLED : ZBX_ITEM_DISABLED), 349 | trends => $opts->{ trends }, 350 | type => ZBX_ITEM_TYPE_SNMPTRAP, 351 | units => '', 352 | username => '', 353 | value_type => ZBX_VAL_TYPE_LOG, 354 | valuemap => '' 355 | ); 356 | 357 | # Item prototype template 358 | my %item_proto_template = ( 359 | application_prototypes => undef, 360 | ); 361 | %item_proto_template = (%item_template, %item_proto_template); 362 | 363 | # Global value maps array 364 | my $valuemaps = {}; 365 | 366 | =head2 utf8_santize 367 | 368 | Parameters : (string) $malformed_utf8 369 | Returns : (string) $wellformed_utf8 370 | Description : Returns a sanitized UTF8 string, removing incompatable characters 371 | 372 | =cut 373 | sub utf8_sanitize { 374 | my ($malformed_utf8) = @_; 375 | 376 | my $octets = decode('UTF-8', $malformed_utf8, Encode::FB_DEFAULT); 377 | return encode('UTF-8', $octets, Encode::FB_CROAK); 378 | } 379 | 380 | =head2 oid_path 381 | 382 | Parameters : SNMP::MIB::Node $oid 383 | Returns : (String) $oid_path 384 | Description : Returns the fully qualified textual path of a MIB node by 385 | traversing the node's parents. 386 | 387 | =cut 388 | sub oid_path { 389 | my ($oid) = @_; 390 | 391 | my $path = $oid->{ label }; 392 | my $node = $oid; 393 | while ($node = $node->{ parent }) { 394 | $path = "$node->{ label }.$path"; 395 | } 396 | 397 | return $path; 398 | } 399 | 400 | =head2 node_to_item 401 | 402 | Parameters : SNMP::MIB::Node $node 403 | (Hash) $template 404 | Returns : (Hash) $item 405 | Description : Returns a Zabbix Item hash derived from the specified MIB OID 406 | 407 | =cut 408 | sub node_to_item { 409 | my ($node, $template) = @_; 410 | $template = $template || \%item_template; 411 | 412 | # Create item hash 413 | my $item = { %{ $template } }; 414 | 415 | $item->{ name } = $node->{ label }; 416 | $item->{ snmp_oid } = $node->{ objectID }; 417 | if ($node->{ units }) { 418 | # Convert unit to Zabbix postfix 419 | # See 'Units' section of https://www.zabbix.com/documentation/3.0/manual/config/items/item 420 | if ($node->{ units } =~ /^seconds$/) { 421 | $item->{ units } = 's'; 422 | } elsif ($node->{ units } =~ /^(hundreds of seconds)$/i) { 423 | $item->{ units } = 's'; 424 | $item->{ multiplier } = '1'; 425 | $item->{ formula } = '100'; 426 | } elsif ($node->{ units } =~ /^(milliseconds|milli-seconds)$/i) { 427 | $item->{ units } = 's'; 428 | $item->{ multiplier } = '1'; 429 | $item->{ formula } = '.001'; 430 | } elsif ($node->{ units } =~ /^microseconds$/i) { 431 | $item->{ units } = 's'; 432 | $item->{ multiplier } = '1'; 433 | $item->{ formula } = '.000001'; 434 | } elsif ($node->{ units } =~ /^(octets|bytes)$/i) { 435 | $item->{ units } = 'B'; 436 | } elsif ($node->{ units } =~ /^(k-octets|kbytes|kb)$/i) { 437 | $item->{ units } = 'B'; 438 | $item->{ multiplier } = '1'; 439 | $item->{ formula } = '.001'; 440 | } elsif ($node->{ units } =~ /^(bits per second)$/i) { 441 | $item->{ units } = 'b'; 442 | } elsif ($node->{ units } =~ /^(kbps|kilobits per second)$/i) { 443 | $item->{ units } = 'b'; 444 | $item->{ multiplier } = '1'; 445 | $item->{ formula } = '.001'; 446 | } elsif ($node->{ units } =~ /^percent$/i) { 447 | $item->{ units } = '%'; 448 | } elsif ($node->{ units } =~ /\/s$/i) { 449 | # truncate /s (/sec will be added later) 450 | $item->{ units } = substr($node->{ units }, 0, -2) . "/sec"; 451 | } else { 452 | # default to original 453 | $item->{ units } = $node->{ units }; 454 | } 455 | } 456 | 457 | # Merge in item defaults 458 | %{ $item } = (%{ $template }, %{ $item } ); 459 | 460 | # Create SNMP Agent item 461 | $item->{ type } = $snmpver_map->{ $opts->{ snmpver } }; 462 | 463 | # Item key 464 | $item->{ key } = "$node->{ moduleID }.$node->{ label }"; 465 | 466 | # Map value type (Ignore for OID Table Entry Rows) 467 | if ($node->{ type }) { 468 | $item->{ value_type } = $type_map->{ $node->{ type } }; 469 | if (!defined($item->{ value_type })) { 470 | print STDERR "No type mapping found for type $node->{ type } in $node->{ objectID }\n"; 471 | } 472 | } 473 | 474 | # Set storage type to Delta for MIB counter types 475 | if ( $node->{ type } ~~ ['COUNTER', 'COUNTER32', 'COUNTER64']) { 476 | $item->{ delta } = ZBX_ITEM_STORE_SPEED; 477 | 478 | if ($item->{ units } =~ /^s$/) { 479 | $item->{ units } = '/sec'; 480 | } elsif ($item->{ units } =~ /^b$/i) { 481 | $item->{ units } .= 'ps'; 482 | } else { 483 | $item->{ units } .= '/sec'; 484 | } 485 | } 486 | 487 | # Translate SNMP Ticks 488 | if ($node->{ type } eq 'TICKS') { 489 | $item->{ multiplier } = '1'; 490 | $item->{ formula } = '.01'; 491 | $item->{ units } = 'uptime'; 492 | } 493 | 494 | # Parse item desciption 495 | $item->{ description } = utf8_sanitize($node->{ description }); 496 | if ($item->{ description }) { 497 | $item->{ description } =~ s/^\s+|\s+$|\n//g; # Trim left/right whitespace and newlines 498 | $item->{ description } =~ s/\s{2,}/ /g; # Remove padding 499 | } 500 | 501 | # Process value maps 502 | if (scalar keys % {$node->{ enums } }) { 503 | my $map_name = "$node->{ moduleID }::$node->{ label }"; 504 | 505 | # If the map_name is longer than 64 characters truncate to 64 characters 506 | # to match maximum database field length. 507 | if (length($map_name) > 64) { 508 | $map_name = substr($map_name,0,61) . "..."; 509 | } 510 | 511 | # add template value map 512 | $valuemaps->{ $map_name }->{ 'mappings' } = []; 513 | foreach(keys %{ $node->{ enums } }) { 514 | push(@{ $valuemaps->{ $map_name }->{ 'mappings' } }, { 515 | 'value' => $node->{ enums }->{ $_ }, 516 | 'newvalue' => $_ 517 | }); 518 | } 519 | 520 | # Assign value map to item 521 | $item->{ valuemap } = { name => $map_name }; 522 | } 523 | 524 | return $item; 525 | } 526 | 527 | =head2 node_to_trapitem 528 | 529 | Parameters : SNMP::MIB::Node $node 530 | (Hash) $template 531 | Returns : (Hash) $item 532 | Description : Returns a Zabbix SNMP Trap Item hash derived from the 533 | specified MIB OID 534 | 535 | =cut 536 | sub node_to_trapitem { 537 | my ($node, $template) = @_; 538 | $template = $template || \%trap_template; 539 | 540 | # Create item hash 541 | my $item = { %{ $template } }; 542 | 543 | $item->{ name } = "SNMP Trap: $node->{ moduleID }::$node->{ label }"; 544 | 545 | # Merge in item defaults 546 | %{ $item } = (%{ $template }, %{ $item } ); 547 | 548 | # Create trap key 549 | my $oid = $node->{ objectID }; 550 | $oid =~ s/\./\\./g; 551 | $item->{ key } = "snmptrap[\"\\s$oid\\s\"]"; 552 | 553 | # Parse item desciption 554 | my $desc = ''; 555 | if ($node->{ description }) { 556 | $desc = $node->{ description }; 557 | $desc =~ s/^\s+|\s+$|\n//g; # Trim left/right whitespace and newlines 558 | $desc =~ s/\s{2,}/ /g; # Remove padding 559 | } 560 | 561 | # Append varbinds to description 562 | if (defined($node->{ varbinds }) && scalar @{ $node->{ varbinds } }) { 563 | my $varcount = scalar @{ $node->{ varbinds } }; 564 | 565 | if ($desc ne '') { 566 | $desc .= "\n\n"; 567 | } 568 | 569 | $desc .= "Varbinds:\n"; 570 | 571 | for(my $i = 0; $i < $varcount; $i++) { 572 | my $varbind_label = $node->{ varbinds }[$i]; 573 | $desc .= "$i. $varbind_label"; 574 | 575 | # Try to find OID for each varbind 576 | my $varbind_path = "$node->{ moduleID }::$varbind_label"; 577 | my $varbind = $SNMP::MIB{ $varbind_path }; 578 | 579 | if (defined($varbind)) { 580 | $desc .= " ($varbind->{ type })\n" if $varbind->{ type }; 581 | 582 | if ($varbind->{ description }) { 583 | my $vbdesc = $varbind->{ description }; 584 | $vbdesc =~ s/[ \t]+/ /g; # Replace long whitespace with single space 585 | $vbdesc =~ s/^ ?/ /mg; # Prepend indent to each description line 586 | $desc .= "$vbdesc\n\n"; 587 | } 588 | } else { 589 | $desc .= "\n"; 590 | } 591 | } 592 | } 593 | $item->{ description } = $desc; 594 | 595 | return $item; 596 | } 597 | 598 | =head2 node_is_current 599 | 600 | Parameters : SNMP::MIB::Node $node 601 | Returns : (int) 0|1 602 | Description : Returns true if the specified OID is not obsolete 603 | 604 | =cut 605 | sub node_is_current { 606 | my ($node) = @_; 607 | 608 | return ( 609 | node_is_valid_trap($node) 610 | || (defined($node->{ status }) && $node->{ status } ne 'Obsolete') 611 | ); 612 | } 613 | 614 | =head2 node_is_valid_scalar 615 | 616 | Parameters : SNMP::MIB::Node $node 617 | Returns : (int) 0|1 618 | Description : Returns true if the specified OID is current, readable and 619 | defines a valid value type. 620 | 621 | =cut 622 | sub node_is_valid_scalar { 623 | my ($node) = @_; 624 | 625 | return ( 626 | node_is_current($node) 627 | && $node->{ type } 628 | && ( 629 | $node->{ type } eq 'NOTIF' || $node->{ type } eq 'TRAP' 630 | || ($node->{ access } eq 'ReadOnly' || $node->{ access } eq 'ReadWrite') 631 | ) 632 | 633 | ); 634 | } 635 | 636 | =head2 node_is_valid_trap 637 | 638 | Parameters : SNMP::MIB::Node $node 639 | Returns : (int) 0|1 640 | Description : Returns true if the specified OID is an SNMP Trap 641 | 642 | =cut 643 | sub node_is_valid_trap { 644 | my ($node) = @_; 645 | 646 | return ( 647 | defined($node->{ type }) && ($node->{ type } eq 'NOTIF' || $node->{ type } eq 'TRAP') 648 | ); 649 | } 650 | 651 | =head2 node_is_valid_table 652 | 653 | Parameters : SNMP::MIB::Node $node 654 | Returns : (int) 0|1 655 | Description : Returns true if the specified OID is a valid table which is 656 | current, readable and contains a single child (row 657 | definition) 658 | 659 | =cut 660 | sub node_is_valid_table { 661 | my ($node) = @_; 662 | 663 | # The MIB will define a 'SEQUENCE OF' attribute for tables but 664 | # SNMP::MIB::NODE does not expose this value. Instead, a table 665 | # node must be 'NoAccess' and have a single 'NoAccess' child 666 | return ( 667 | node_is_current($node) 668 | 669 | # Table is NoAccess 670 | && $node->{ access } eq 'NoAccess' 671 | 672 | # Table has one child (row definition) 673 | && (scalar @{ $node->{ children } }) == 1 674 | 675 | # Table row is NoAccess 676 | && $node->{ children }[0]->{ access } eq 'NoAccess' 677 | 678 | # Table row defines atleast one index 679 | && (scalar @{ $node->{ children }[0]->{ indexes } }) 680 | ); 681 | } 682 | 683 | =head2 build_template 684 | 685 | Parameters : (hash) $template 686 | SNMP::MIB::NODE $node 687 | Returns : (void) 688 | Description : Traverses a loaded MIB tree from the specified OID node 689 | a populates a Zabbix Template hash with items, discovery 690 | rules, item prototypes, groups and macros. 691 | 692 | =cut 693 | sub build_template { 694 | my ($template, $node) = @_; 695 | 696 | # Ignore obsolete OIDs 697 | if (node_is_current($node)) { 698 | # Create an Item Application name for this node 699 | my $appname = "$node->{ moduleID }::$node->{ parent }->{ label }"; 700 | 701 | # Is this a scalar value OID? 702 | if (node_is_valid_trap($node)) { 703 | # Convert the SNMP::MIB::Node to a Zabbix Template SNMP Trap Item 704 | my $item = node_to_trapitem($node); 705 | 706 | # Add item applications to template application list 707 | $item->{ applications } = [{ name => $appname }]; 708 | $template->{ apptags }->{ $appname } = 1; 709 | 710 | # Add item to template 711 | push(@{ $template->{ items } }, $item ); 712 | 713 | # If the snmptrap has children. 714 | foreach(@{ $node->{ children } }) { 715 | 716 | # Convert the SNMP::MIB::Node to a Zabbix Template SNMP Trap Item 717 | my $item = node_to_trapitem($_); 718 | 719 | # Add item applications to template application list 720 | $item->{ applications } = [{ name => $appname }]; 721 | $template->{ apptags }->{ $appname } = 1; 722 | 723 | # Add item to template 724 | push(@{ $template->{ items } }, $item ); 725 | 726 | } 727 | 728 | } elsif (node_is_valid_scalar($node)) { 729 | 730 | # Convert the SNMP::MIB::Node to a Zabbix Template Item hash 731 | my $item = node_to_item($node); 732 | 733 | # Append '.0' to normal SNMP OIDS 734 | $item->{ snmp_oid } = "$item->{ snmp_oid }.0"; 735 | 736 | # Add item applications to template application list 737 | $item->{ applications } = [{ name => $appname }]; 738 | $template->{ apptags }->{ $appname } = 1; 739 | 740 | # Add item to template 741 | push(@{ $template->{ items } }, $item ); 742 | 743 | } elsif (node_is_valid_table($node)) { 744 | # Get row OID 745 | my $table = $node; 746 | my $row = $node->{ children }[0]; 747 | 748 | # Validate naming standard 749 | if ($table->{ label } !~ /Table/) { 750 | print STDERR "Warning: $table->{ moduleID }:: $table->{ label } appears to be a table but does not have the 'Table' suffix\n"; 751 | } 752 | 753 | if ($row->{ label } !~ /Entry/) { 754 | print STDERR "Warning: $row->{ moduleID }:: $row->{ label } appears to be a table entry but does not have the 'Entry; suffix\n"; 755 | } 756 | 757 | # This is a table. Build a discovery rule 758 | my $disc_rule = {}; 759 | $disc_rule = node_to_item($row, \%disc_rule_template); 760 | 761 | # Update discovery rule name 762 | $disc_rule->{ name } = "$disc_rule->{ name } Discovery"; 763 | $disc_rule->{ snmp_oid } = "discovery["; 764 | 765 | # find any *Descr column 766 | my $index = '{#SNMPINDEX}'; 767 | foreach my $column(@{ $row->{ children } }) { 768 | if (node_is_valid_scalar($column)) { 769 | if($column->{ label } =~ m/Descr$/) { 770 | $disc_rule->{ snmp_oid } .= "{#SNMPVALUE},$column->{ objectID },"; 771 | $index = '{#SNMPVALUE}'; 772 | } 773 | } 774 | } 775 | 776 | # Define macros in discovery key up to 255 chars 777 | # See: https://www.zabbix.com/documentation/3.0/manual/discovery/low_level_discovery#discovery_of_snmp_oids 778 | foreach my $column(@{ $row->{ children } }) { 779 | if (node_is_valid_scalar($column)) { 780 | my $new_snmp_oid = $disc_rule->{ snmp_oid } . "{#" . uc($column->{ label }) . "}," . $column->{ objectID } . ","; 781 | if (length($new_snmp_oid) <= 255) { 782 | $disc_rule->{ snmp_oid } = $new_snmp_oid; 783 | } 784 | } 785 | } 786 | $disc_rule->{ snmp_oid } = substr($disc_rule->{ snmp_oid }, 0, -1) . "]"; 787 | 788 | # Fetch an arbitrary column OID for Zabbix to use for discovery 789 | my $index_oid = $row->{ children }[0]; 790 | if (!defined($index_oid)) { 791 | print STDERR "No index found for table $table->{ moduleID}::$table->{ label } ($table->{ objectID })\n"; 792 | } else { 793 | # Remove unrequired fields 794 | delete($disc_rule->{ applications }); 795 | delete($disc_rule->{ data_type }); 796 | 797 | # Create new array for prototypes 798 | $disc_rule->{ item_prototypes } = []; 799 | 800 | # Add prototypes for each row column 801 | foreach my $column(@{ $row->{ children } }) { 802 | if (node_is_valid_scalar($column)) { 803 | if (my $proto = node_to_item($column, \%item_proto_template)) { 804 | $proto->{ name } = "$proto->{ name } for $index"; 805 | $proto->{ key } = "$column->{ label }\[$index]"; 806 | $proto->{ snmp_oid } = "$proto->{ snmp_oid }.{#SNMPINDEX}"; 807 | 808 | # Add item applications to template application list 809 | $proto->{ applications } = [{ name => $appname }]; 810 | $template->{ apptags }->{ $appname } = 1; 811 | 812 | push(@{ $disc_rule->{ item_prototypes } }, $proto); 813 | } 814 | } 815 | } 816 | 817 | # Add discovery rule to template 818 | push(@{ $template->{ discovery_rules } }, $disc_rule); 819 | } 820 | } 821 | } else { 822 | # Parse children 823 | foreach(@{ $node->{ children } }) { 824 | build_template($template, $_); 825 | } 826 | } 827 | } 828 | 829 | # Initialize net-snmp 830 | $SNMP::save_descriptions = 1; 831 | SNMP::initMib(); 832 | 833 | # Verify the specified OID exists 834 | if ($opts->{ oid } !~ m/^\./) { 835 | $opts->{ oid } = "." . $opts->{ oid } 836 | } 837 | 838 | my $oid_root = $SNMP::MIB{ $opts->{ oid } }; 839 | if (!$oid_root || $oid_root->{ objectID } ne $opts->{ oid }) { 840 | print STDERR "OID $opts->{ oid } not found in MIB tree.\n"; 841 | exit 1; 842 | 843 | # Build a Zabbix template 844 | } else { 845 | my $suffix = $opts->{ snmpver } > 2 ? " v$opts->{ snmpver }" : ''; 846 | my $template_name = $opts->{ name } || "Template SNMP $oid_root->{ moduleID } - $oid_root->{ label }$suffix"; 847 | my $template = { 848 | name => $template_name, 849 | template => $template_name, 850 | description => "Generated by mib2zabbix", 851 | apptags => {}, 852 | applications => [], 853 | discovery_rules => [], 854 | groups => [{ 855 | name => $opts->{ group } 856 | }], 857 | items => [], 858 | macros => [ 859 | { macro => '{$MIB2ZABBIX_CMD}', value => $cmd }, 860 | { macro => '{$OID}', value => "$oid_root->{ objectID }" }, 861 | { macro => '{$OID_PATH}', value => oid_path($oid_root) }, 862 | { macro => '{$OID_MOD}', value => $oid_root->{ moduleID } }, 863 | { macro => '{$SNMP_PORT}', value => $opts->{ snmpport } } 864 | ] 865 | }; 866 | 867 | # Add SNMP connection macros 868 | if($opts->{ snmpver } < 3) { 869 | push(@{ $template->{ macros } }, { macro => '{$SNMP_COMMUNITY}', value => $opts->{ snmpcomm } }); 870 | } elsif($opts->{ snmpver } == 3) { 871 | push(@{ $template->{ macros } }, { macro => '{$SNMP_USER}', value => $opts->{ v3user } }); 872 | push(@{ $template->{ macros } }, { macro => '{$SNMP_CONTEXT}', value => $opts->{ v3context } }); 873 | push(@{ $template->{ macros } }, { macro => '{$SNMP_AUTHPASS}', value => $opts->{ v3auth_pass } }); 874 | push(@{ $template->{ macros } }, { macro => '{$SNMP_PRIVPASS}', value => $opts->{ v3sec_pass } }); 875 | }; 876 | build_template($template, $oid_root, 0); 877 | 878 | # Convert applications hash to array 879 | @{ $template->{ applications } } = map { { name => $_ } } keys %{ $template->{ apptags } }; 880 | delete($template->{ apptags }); 881 | 882 | # Build XML document 883 | my $time = time(); 884 | my $output = { 885 | version => '3.0', 886 | date => time2str("%Y-%m-%dT%H:%M:%SZ", $time), 887 | groups => $template->{ groups }, 888 | templates => [$template], 889 | triggers => [], 890 | graphs => [], 891 | value_maps => [$valuemaps] 892 | }; 893 | 894 | # Output stream 895 | my $fh = *STDOUT; 896 | if ($opts->{ filename }) { 897 | open($fh, ">$opts->{ filename }") or die "$!"; 898 | } 899 | 900 | # Output XML 901 | XMLout($output, 902 | OutputFile => \$fh, 903 | XMLDecl => "", 904 | RootName => 'zabbix_export', 905 | NoAttr => 1, 906 | SuppressEmpty => undef, 907 | GroupTags => { 908 | 'applications' => 'application', 909 | 'groups' => 'group', 910 | 'templates' => 'template', 911 | 'items' => 'item', 912 | 'macros' => 'macro', 913 | 'discovery_rules' => 'discovery_rule', 914 | 'item_prototypes' => 'item_prototype', 915 | 'trigger_prototypes' => 'trigger_prototype', 916 | 'graph_prototypes' => 'graph_prototype', 917 | 'host_prototypes' => 'host_prototype', 918 | 'value_maps' => %{ $valuemaps } ? 'value_map' : undef, 919 | 'mappings' => 'mapping' 920 | } 921 | ); 922 | 923 | if ($opts->{ filename }) { 924 | close $fh; 925 | } 926 | } 927 | --------------------------------------------------------------------------------