Customizing SNMP MIBs for Gets and Traps in SR Linux#
SR Linux version 24.10.1 introduces a customizable SNMP framework allowing you to define your own SNMP management information bases (MIBs) for gets and traps. This same framework powers SR Linux's built-in MIBs and traps, offering flexibility that customizes SNMP MIBs to the specific requirements for your network.
The framework defines:
Mapping files (YAML): To define MIB tables and object identifiers (OIDs).
Conversion scripts (uPython): To process data from the management server via gNMI and convert it for SNMP.
A simple list of supported OIDs for monitoring is in /etc/opt/srlinux/snmp/numbers.txt, and a detailed list with script information is in /etc/opt/srlinux/snmp/exportedOids when an / system snmp access-group is configured. These files are created at runtime when the SNMP server is started.
The table definition YAML file describes the framework components used to define a particular MIB table. Take the if_mib.yaml file for example, it maps interface-related data to standard MIB tables such as ifTable, ifXTable, and ifStackTable.
You can list the contents of this file with cat /opt/srlinux/snmp/scripts/if_mib.yaml and it is below for reference:
if_mib.yaml Definition File
#- This is the mapping for interfaces and subinterfaces. It defines MIB tables ifTable, ifXTable and ifStackTable.paths:-/interface/-/interface/ethernet-/interface/lag-/interface/statistics-/interface/transceiver-/interface/subinterface/-/interface/subinterface/statisticspython-script:if_mib.pyenabled:truedebug:falsetables:-name:ifTableenabled:trueoid:1.3.6.1.2.1.2.2indexes:-name:ifIndexoid:1.3.6.1.2.1.2.2.1.1syntax:integercolumns:-name:ifIndexoid:1.3.6.1.2.1.2.2.1.1syntax:integer-name:ifDescroid:1.3.6.1.2.1.2.2.1.2syntax:octet string-name:ifTypeoid:1.3.6.1.2.1.2.2.1.3syntax:integer-name:ifMtuoid:1.3.6.1.2.1.2.2.1.4syntax:integer-name:ifSpeedoid:1.3.6.1.2.1.2.2.1.5syntax:gauge32-name:ifPhysAddressoid:1.3.6.1.2.1.2.2.1.6syntax:octet stringbinary:true-name:ifAdminStatusoid:1.3.6.1.2.1.2.2.1.7syntax:integer-name:ifOperStatusoid:1.3.6.1.2.1.2.2.1.8syntax:integer-name:ifLastChangeoid:1.3.6.1.2.1.2.2.1.9syntax:timeticks-name:ifInOctetsoid:1.3.6.1.2.1.2.2.1.10syntax:counter32-name:ifInUcastPktsoid:1.3.6.1.2.1.2.2.1.11syntax:counter32-name:ifInNUcastPktsoid:1.3.6.1.2.1.2.2.1.12syntax:counter32-name:ifInDiscardsoid:1.3.6.1.2.1.2.2.1.13syntax:counter32-name:ifInErrorsoid:1.3.6.1.2.1.2.2.1.14syntax:counter32-name:ifInUnknownProtosoid:1.3.6.1.2.1.2.2.1.15syntax:counter32-name:ifOutOctetsoid:1.3.6.1.2.1.2.2.1.16syntax:counter32-name:ifOutUcastPktsoid:1.3.6.1.2.1.2.2.1.17syntax:counter32-name:ifOutNUcastPktsoid:1.3.6.1.2.1.2.2.1.18syntax:counter32-name:ifOutDiscardsoid:1.3.6.1.2.1.2.2.1.19syntax:counter32-name:ifOutErrorsoid:1.3.6.1.2.1.2.2.1.20syntax:counter32-name:ifOutQLenoid:1.3.6.1.2.1.2.2.1.21syntax:gauge32-name:ifSpecificoid:1.3.6.1.2.1.2.2.1.22syntax:object identifier-name:ifXTableenabled:trueoid:1.3.6.1.2.1.31.1.1augment:ifTablecolumns:-name:ifNameoid:1.3.6.1.2.1.31.1.1.1.1syntax:octet string-name:ifInMulticastPktsoid:1.3.6.1.2.1.31.1.1.1.2syntax:counter32-name:ifInBroadcastPktsoid:1.3.6.1.2.1.31.1.1.1.3syntax:counter32-name:ifOutMulticastPktsoid:1.3.6.1.2.1.31.1.1.1.4syntax:counter32-name:ifOutBroadcastPktsoid:1.3.6.1.2.1.31.1.1.1.5syntax:counter32-name:ifHcInOctetsoid:1.3.6.1.2.1.31.1.1.1.6syntax:counter64-name:ifHcInUcastPktsoid:1.3.6.1.2.1.31.1.1.1.7syntax:counter64-name:ifHcInMulticastPktsoid:1.3.6.1.2.1.31.1.1.1.8syntax:counter64-name:ifHcInBroadcastPktsoid:1.3.6.1.2.1.31.1.1.1.9syntax:counter64-name:ifHcOutOctetsoid:1.3.6.1.2.1.31.1.1.1.10syntax:counter64-name:ifHcOutUcastPktsoid:1.3.6.1.2.1.31.1.1.1.11syntax:counter64-name:ifHcOutMulticastPktsoid:1.3.6.1.2.1.31.1.1.1.12syntax:counter64-name:ifHcOutBroadcastPktsoid:1.3.6.1.2.1.31.1.1.1.13syntax:counter64-name:ifLinkUpDownTrapEnableoid:1.3.6.1.2.1.31.1.1.1.14syntax:integer-name:ifHighSpeedoid:1.3.6.1.2.1.31.1.1.1.15syntax:gauge32-name:ifPromiscuousModeoid:1.3.6.1.2.1.31.1.1.1.16syntax:integer-name:ifConnectorPresentoid:1.3.6.1.2.1.31.1.1.1.17syntax:integer-name:ifAliasoid:1.3.6.1.2.1.31.1.1.1.18syntax:octet string-name:ifCounterDiscontinuityTimeoid:1.3.6.1.2.1.31.1.1.1.19syntax:timeticks-name:ifStackTableenabled:trueoid:1.3.6.1.2.1.31.1.2indexes:-name:ifStackHigherLayeroid:1.3.6.1.2.1.31.1.2.1.1syntax:integer-name:ifStackLowerLayeroid:1.3.6.1.2.1.31.1.2.1.2syntax:integercolumns:-name:ifStackStatusoid:1.3.6.1.2.1.31.1.2.1.3syntax:integerscalars:-name:ifNumberenabled:trueoid:1.3.6.1.2.1.2.1syntax:integer-name:ifTableLastChangeenabled:trueoid:1.3.6.1.2.1.31.1.5syntax:timeticks
The table definition file has the following important top level fields:
paths: Specifies the gNMI paths for retrieving data.
python-script: References the Python script used for data conversion.
tables: Lists MIB tables, their structure, and their OIDs.
scalars: Defines scalar OIDs.
You can see the list of MIB table definitions in the tables list, where each table has the following structure:
name: Specifies the name of the SNMP table. This is used for identification and reference in the SNMP configuration.
enabled: Defines whether the table is active (true) or inactive (false).
oid: The base OID for the table. All rows and columns in the table are extensions of this base OID.
indexes: Indexes uniquely identify rows in the table. Each index maps a specific OID to a value that differentiates rows. A list of column definitions that serve as unique identifiers for rows.
name: The name of the index column.
oid: The OID for the index.
syntax: The data type of the index value.
columns: Columns represent attributes or properties for each row in the table. Each column is defined with an OID and a data type.
name: The name of the column.
oid: The OID for the column.
syntax: The data type of the column's value.
binary: (optional) Indicates if the value is base64-encoded.
enabled: (optional) Enables or disables the column.
The syntax field in SNMP table and scalar definitions specifies the data type of the OID value. Each data type maps to a specific ASN.1 type, defining how the data is represented and transmitted in SNMP operations. Below is a detailed explanation of the supported data types.
Data Types
octet string: Represents a sequence of octets (bytes). Commonly used for textual information (e.g., names, descriptions) or raw binary data. E.g: ifDescr.
integer / integer32: Represents a signed 32-bit integer. Used for numeric attributes like counters, states, or enumerations. E.g: ifType, ifAdminStatus, ifOperStatus.
unsigned / unsigned32: Represents an unsigned 32-bit integer. E.g: values that should not be negative like counts or identifiers.
counter / counter32: Represents a counter that increments over time and wraps back to 0 when it exceeds the maximum value (4,294,967,295). E.g: ifInOctets, ifOutOctets.
counter64: Represents a 64-bit counter for high-capacity devices or metrics with large values. E.g: ifHCInOctets, ifHCOutOctets.
gauge / gauge32: Represents a non-negative integer that can increase or decrease but cannot wrap. E.g ifSpeed.
timeticks: Represents time in hundredths of a second since a device was last initialized or restarted. E.g: ifLastChange.
ipaddress: Represents an IPv4 address as a 32-bit value. Stored and transmitted in network byte order (big-endian).
object identifier: Represents an OID as a series of numbers identifying objects or properties in the SNMP tree.
bits: Represents a sequence of bits, often used to define flags or multiple binary states.
You can create custom MIB definitions following these steps:
Define the mapping file: Specify paths, tables, scalars, and their structure in YAML.
Write the conversion script: Implement a snmp_main function in Python that processes the input JSON and generates SNMP objects.
Add the mapping file to the list of table definitions to /etc/opt/srlinux/snmp/snmp_files_config.yaml.
Location of Built-in and Custom SNMP Framework Files
The user-defined MIB definitions and files with the associated scripts are stored in /etc/opt/srlinux/snmp directory, while the built-in MIB definitions are stored in /opt/srlinux/snmp directory.
The SNMP framework is powered by the underlying SR Linux's gNMI infrastructure. The paths you define in the table mapping file will retrieve the data that the conversion script will use to create the SNMP MIB tables.
Note that the paths you define in the mapping file are non-recursive; this means that the returned data will be limited to the immediate children of the path you specify. To recursively retrieve data from a path, add ... to the end of the path, e.g. /interface/ethernet/....
The Python script receives data in JSON format, including global SNMP information and the gNMI query results. Here is an example of a payload the if_mib.py script receives.
The script entry point is a function called snmp_main that takes a JSON string as input and returns a JSON string.
defsnmp_main(in_json_str:str)->str:
Refer to the built-in scripts as examples. The /opt/srlinux/snmp/scripts/utilities.py script contains some useful helper functions to perform various checks and common type conversions.
Traps are defined with mapping files that look similar to the MIB files, but include additional parameters for triggers and variable bindings. As you have seen in the beginning of this document, the traps mapping files are listed in the global /opt/srlinux/snmp/snmp_files_config.yaml.
A list of OIDs available for traps is in /etc/opt/srlinux/snmp/installedTraps when a trap-group is configured. This file is created at runtime when the SNMP server is started.
The trap definition YAML file has exactly the same top level elements as the table definition file but instead of tables the file contains traps top-level list. Here is the contents of the /opt/srlinux/snmp/scripts/rfc3418_traps.yaml mapping file that defines the traps as per RFC 3418:
rfc3418_traps.yaml definition file
python-script:rfc3418_traps.pyenabled:truedebug:falsetraps:-name:coldStartenabled:trueoid:1.3.6.1.6.3.1.1.5.1startup:truetriggers:-/platform/chassis/last-booted-name:warmStartenabled:trueoid:1.3.6.1.6.3.1.1.5.2startup:truetriggers:-/platform/chassis/last-booted-name:linkDownenabled:trueoid:1.3.6.1.6.3.1.1.5.3triggers:-/interface/oper-state-/interface/subinterface/oper-statecontext:-/interface-/interface/subinterfacedata:-indexes:-name:ifIndexsyntax:integerobjects:-name:ifIndexoid:1.3.6.1.2.1.2.2.1.1syntax:integer-name:ifAdminStatusoid:1.3.6.1.2.1.2.2.1.7syntax:integer-name:ifOperStatusoid:1.3.6.1.2.1.2.2.1.8syntax:integer-name:ifName# non-standard, but usefulenabled:trueoid:1.3.6.1.2.1.31.1.1.1.1syntax:octet stringoptional:true-name:linkUpenabled:trueoid:1.3.6.1.6.3.1.1.5.4triggers:-/interface/oper-state-/interface/subinterface/oper-statecontext:-/interface-/interface/subinterfacedata:-indexes:-name:ifIndexsyntax:integerobjects:-name:ifIndexoid:1.3.6.1.2.1.2.2.1.1syntax:integer-name:ifAdminStatusoid:1.3.6.1.2.1.2.2.1.7syntax:integer-name:ifOperStatusoid:1.3.6.1.2.1.2.2.1.8syntax:integer-name:ifName# non-standard, but usefulenabled:trueoid:1.3.6.1.2.1.31.1.1.1.1syntax:octet stringoptional:true-name:authenticationFailureenabled:truehardcoded:trueoid:1.3.6.1.6.3.1.1.5.5
Besides the common name, enabled and oid fields, the traps object has the following fields:
triggers: Specifies paths that trigger the trap.
context: Additional paths to fetch data for the trap.
data: Defines variable bindings included in the trap.
The script entry point is a function called snmp_main that takes a JSON string as input and returns a JSON string.
defsnmp_main(in_json_str:str)->str:
Refer to the built-in scripts as examples. The /opt/srlinux/snmp/scripts/utilities.py script contains some useful helper functions to perform various checks and common type conversions.
Debug files are generated in /tmp/snmp_debug/$NETWORK_INSTANCE when debug: true is set in the YAML configuration file.
For MIBs: check /etc/opt/srlinux/snmp/exportedOids for your OIDs and make sure an / system snmp access-group is configured.
For traps: check /etc/opt/srlinux/snmp/installedTraps for your traps and make sure a / system snmp trap-group is configured.
Input/output logs: Check .json_input, .json_output, .console and .error files for debugging script execution. The .console files contain output printed by the scripts and the .error files contain mapping and scripts errors.
Path data: Inspect debug outputs for issues in path retrieval.
Add a new table definition to /etc/opt/srlinux/snmp/scripts/grpc_mib.yaml.
This MIB has a single index gRPCServerName and 6 columns; the gRPC server network instance, its admin and operational states, the number of accepted and rejected RPCs and the last time an RPC was accepted.
All of these fields can be mapped from leafs that are found under the XPath /system/grpc-server/...
The YAML file references a Python script called grpc_mib.py. It must be placed in the same directory as the grpc_mib.yaml file.
The script is fairly simple; it grabs the JSON input and sets some global SNMP information such as the system boot time (useful for calculating time ticks values). After that, it iterates over the list of gRPC servers in the input JSON and set each server's columns values (with the correct format) in the prepared output dict. Finally it returns the output dict as a JSON blob.
#!/usr/bin/python############################################################################ Description:## Copyright (c) 2024 Nokia###########################################################################importjsonimportutilitiesSERVER_ADMIN_STATUS_UP=1SERVER_ADMIN_STATUS_DOWN=2IF_OPER_STATUS_UP=1IF_OPER_STATUS_DOWN=2# maps the gNMI admin status value to its corresponding SNMP valuedefconvertAdminStatus(value:str):ifvalueisnotNone:ifvalue=="enable":returnSERVER_ADMIN_STATUS_UPelifvalue=="disable":returnSERVER_ADMIN_STATUS_DOWN# maps the gNMI oper status value to its corresponding SNMP valuedefconvertOperStatus(value:str):ifvalueisnotNone:ifvalue=="up":returnIF_OPER_STATUS_UPelifvalue=="down":returnIF_OPER_STATUS_DOWN## main routine#defsnmp_main(in_json_str:str)->str:in_json=json.loads(in_json_str)delin_json_str# read in general info from the snmp serversnmp_info=in_json.get("_snmp_info_")utilities.process_snmp_info(snmp_info)# prepare the output dictoutput={"tables":{"gRPCServerTable":[]}}# Iterate over all grpc-server instancesgrpc_servers=in_json.get("system",{}).get("grpc-server",[])forserveringrpc_servers:# Extract required fieldsname=server.get("name","")statistics=server.get("statistics",{})access_rejects=statistics.get("access-rejects",0)access_accepts=statistics.get("access-accepts",0)last_access_accept=0# Grab the last-access-accept timestamp_last_access=statistics.get("last-access-accept",False)if_last_access:ts=utilities.parse_rfc3339_date(_last_access)# Convert it to timeTicks from boot timelast_access_accept=utilities.convertUnixTimeStampInTimeticks(ts)# Append the object to the outputoutput["tables"]["gRPCServerTable"].append({"objects":{"gRPCServerName":name,"grpcServerNetworkInstance":server.get("network-instance",""),"grpcServerAdminState":convertAdminStatus(server.get("admin-state","")),"grpcServerOperState":convertOperStatus(server.get("oper-state")),"grpcServerAccessRejects":access_rejects,"grpcServerAccessAccepts":access_accepts,"grpcServerLastAccessAccept":last_access_accept,}})returnjson.dumps(output)
Restart the SNMP server process for it to load the new custom MIB definitions.
--{ running }--[ ]--A:srl1# /tools system app-management application snmp_server-mgmt restart/system/app-management/application[name=snmp_server-mgmt]: Application 'snmp_server-mgmt' was killed with signal9/system/app-management/application[name=snmp_server-mgmt]: Application 'snmp_server-mgmt' was restarted
Similar to the SNMP MIB, let's add custom SNMP traps to SR Linux at runtime, no feature requests, no software upgrades, by creating a gRPC server SNMP trap 🤪. Traps are independent from MIBs and do not need a corresponding MIB that is used for SNMP gets.
Add a new trap definitions to /etc/opt/srlinux/snmp/scripts/grpc_traps.yaml.
Two traps are defined:
gRPCServerDown: sent when a gRPC server goes down.
gRPCServerUp: sent when a gRPC server comes up, including at startup.
Both of these traps are triggered from the /system/grpc-server/oper-state XPath.
############################################################################ Description:## Copyright (c) 2024 Nokia############################################################################ yaml-language-server: $schema=./trap_definition_schema.jsonpython-script:grpc_traps.pyenabled:truedebug:truetraps:-name:gRPCServerDownenabled:truestartup:trueoid:1.3.6.1.4.1.6527.115.114.108.105.110.117.122enterprise:1.3.6.1.4.1.6527.1.20triggers:-/system/grpc-server/oper-statecontext:-/system/grpc-server/...data:-objects:# this object is a scalar, does not use an index-name:gRPCServerNameoid:1.3.6.1.4.1.6527.115.114.108.105.110.117.120.1.1syntax:octet string-name:gRPCServerUpenabled:truestartup:trueoid:1.3.6.1.4.1.6527.115.114.108.105.110.117.123enterprise:1.3.6.1.4.1.6527.1.20triggers:-/system/grpc-server/oper-statecontext:-/system/grpc-server/...data:-objects:# this object is a scalar, does not use an index-name:gRPCServerNameoid:1.3.6.1.4.1.6527.115.114.108.105.110.117.120.1.1syntax:octet string
The YAML file references a Python script called grpc_traps.py. It must be placed in the same directory as the grpc_mib.yaml file.
The script is fairly simple; it grabs the JSON input and looks for the gRPC server name to add as a variable binding. You can add additional variable bindings to traps that are relevant if you want, but in this case we only need one for the server name. Finally it returns the output dict as a JSON blob.
############################################################################ Description:## Copyright (c) 2025 Nokia###########################################################################importjsonfromcollectionsimportOrderedDictimportreimportutilities# list of traps that will be echoed back to the clienttraps_list_db:list=[]defparseGrpcServerKeys(xpath:str):# retrieve the name of the grpc-server from the xpathname_pattern=re.compile(r"[^\]]+\[([^\[\]=]+?)=([^\]]+)\]")matches=name_pattern.match(xpath)ifmatchesisnotNone:returnmatches.group(2)returnNonedefgRPCServerUpgRPCServerUpDownTrap(grpc_servers:list,trap:dict,value)->None:filter_key=parseGrpcServerKeys(trap.get("xpath",""))iffilter_keyisNone:raiseValueError(f"Can't look for a grpc-server without an xpath")# loop over all grpc-servers, filter the ones that match the xpathforserveringrpc_servers:server_name=server.get("name","")# keyifserver_name!=filter_key:continue# only report the grpc-servers that have the correct oper-state (unless force flag was used)ifnotutilities.is_forced_simulated_trap():oper_state=server.get("oper-state","")ifvalueisnotNoneandoper_state!=value:continuerow=OrderedDict()objects=OrderedDict()objects["gRPCServerName"]=server_namerow["trap"]=trap.get("name","")row["indexes"]=OrderedDict()# no indexes to reportrow["objects"]=objectstraps_list_db.append(row)## main routine#defsnmp_main(in_json_str:str)->str:globaltraps_list_dbin_json=json.loads(in_json_str)# read in general info from the snmp serversnmp_info=in_json.get("_snmp_info_")utilities.process_snmp_info(snmp_info)# read in info about the traps that will be triggered in this request (depending on the trigger)trap_info=in_json.get("_trap_info_",[])system=in_json.get("system",{})grpc_servers=system.get("grpc-server",[])# loop over all traps in this requestfortrapintrap_info:name=trap.get("name","")trigger=trap.get("trigger","")newValue=trap.get("new-value","")ifname=="gRPCServerDown":ifnewValue=="down"orutilities.is_forced_simulated_trap():gRPCServerUpgRPCServerUpDownTrap(grpc_servers,trap,newValue)elifname=="gRPCServerUp":ifnewValue=="up"orutilities.is_forced_simulated_trap():gRPCServerUpgRPCServerUpDownTrap(grpc_servers,trap,newValue)else:raiseValueError(f"Unknown trap {name} with trigger {trigger}")response:dict={}response["traps"]=traps_list_dbreturnjson.dumps(response)
Restart the SNMP server process for it to load the new custom traps.
--{ running }--[ ]--A:srl1# /tools system app-management application snmp_server-mgmt restart/system/app-management/application[name=snmp_server-mgmt]: Application 'snmp_server-mgmt' was killed with signal9/system/app-management/application[name=snmp_server-mgmt]: Application 'snmp_server-mgmt' was restarted
Test your new traps by sending them from SR Linux.
--{ running }--[ ]--A:srl1# /tools system snmp trap gRPCServerDown force trigger "/system/grpc-server[name=mgmt]/oper-state"/: Trap gRPCServerDown was sent--{ running }--[ ]--A:srl1# /tools system snmp trap gRPCServerUp force trigger "/system/grpc-server[name=mgmt]/oper-state"/: Trap gRPCServerUp was sent
You can see the traps being received on a Unix host using tools like snmptrapd.
// comments will be removed before sending to the python-script{"_snmp_info_":{"boottime":"2025-02-13T20:51:02Z","datetime":"2025-02-13T21:02:33Z","debug":true,"is-cold-boot":false,"network-instance":"mgmt","platform-type":"7220 IXR-D2L","script":"grpc_traps.yaml","sysobjectid":"1.3.6.1.4.1.6527.1.20.26","sysuptime":69100,"trigger":"/system/grpc-server[name=mgmt]","paths":["/system/grpc-server[name=mgmt]/..."]},"_trap_info_":[{"name":"gRPCServerDown","new-value":"up","old-value":"down","startup":true,"trigger":"/system/grpc-server/oper-state","xpath":"/system/grpc-server[name=mgmt]/oper-state"},{"name":"gRPCServerUp","new-value":"up","old-value":"down","startup":true,"trigger":"/system/grpc-server/oper-state","xpath":"/system/grpc-server[name=mgmt]/oper-state"}],"system":{"grpc-server":[{// Path: "/system/grpc-server[name=mgmt]""name":"mgmt","admin-state":"enable","default-tls-profile":false,"max-concurrent-streams":65535,"metadata-authentication":true,"network-instance":"mgmt","oper-state":"up","port":57400,"rate-limit":65000,"session-limit":20,"timeout":7200,"tls-profile":"clab-profile","yang-models":"native","gnmi":{"commit-confirmed-timeout":0,"commit-save":false,"include-defaults-in-config-only-responses":false},"services":["gnmi","gnoi","gnsi","gribi","p4rt"],"source-address":["::"],"trace-options":["request","response","common"],"unix-socket":{"admin-state":"enable","socket-path":"/opt/srlinux/var/run/sr_grpc_server_mgmt"}}]}}
The SR Linux customizable SNMP framework allows you to define your own SNMP MIBs for gets and traps that customize SNMP functionalities to your specific requirements at runtime, no feature request, no software upgrade needed.