netsnmpagent-0.6.0/0000755000175000001440000000000013071262631014323 5ustar piefusers00000000000000netsnmpagent-0.6.0/netsnmpapi.py0000644000175000001440000003362312722265166017072 0ustar piefusers00000000000000# # python-netsnmpagent module # Copyright (c) 2013-2016 Pieter Hollants # Licensed under the GNU Lesser Public License (LGPL) version 3 # # net-snmp C API abstraction module # import ctypes, ctypes.util c_sizet_p = ctypes.POINTER(ctypes.c_size_t) # Make libnetsnmpagent available via Python's ctypes module. We do this globally # so we can define C function prototypes # Workaround for net-snmp 5.4.x that has a bug with unresolved dependencies # in its libraries (http://sf.net/p/net-snmp/bugs/2107): load netsnmphelpers # first try: libnsh = ctypes.cdll.LoadLibrary(ctypes.util.find_library("netsnmphelpers")) except: raise Exception("Could not load libnetsnmphelpers! Is net-snmp installed?") try: libnsa = ctypes.cdll.LoadLibrary(ctypes.util.find_library("netsnmpagent")) except: raise Exception("Could not load libnetsnmpagent! Is net-snmp installed?") # net-snmp <5.6.x had various functions in libnetsnmphelpers.so that were moved # to libnetsnmpagent.so in later versions. Use netsnmp_create_watcher_info as # a test and define a libnsX handle to abstract from the actually used library # version. try: libnsa.netsnmp_create_watcher_info libnsX = libnsa except AttributeError: libnsX = libnsh # include/net-snmp/library/callback.h # Callback major types SNMP_CALLBACK_LIBRARY = 0 SNMP_CALLBACK_APPLICATION = 1 # SNMP_CALLBACK_LIBRARY minor types SNMP_CALLBACK_LOGGING = 4 SNMPCallback = ctypes.CFUNCTYPE( ctypes.c_int, # result type ctypes.c_int, # int majorID ctypes.c_int, # int minorID ctypes.c_void_p, # void *serverarg ctypes.c_void_p # void *clientarg ) for f in [ libnsa.snmp_register_callback ]: f.argtypes = [ ctypes.c_int, # int major ctypes.c_int, # int minor SNMPCallback, # SNMPCallback *new_callback ctypes.c_void_p # void *arg ] f.restype = int # include/net-snmp/agent/agent_callbacks.h SNMPD_CALLBACK_INDEX_STOP = 11 # include/net-snmp/library/snmp_logging.h LOG_EMERG = 0 # system is unusable LOG_ALERT = 1 # action must be taken immediately LOG_CRIT = 2 # critical conditions LOG_ERR = 3 # error conditions LOG_WARNING = 4 # warning conditions LOG_NOTICE = 5 # normal but significant condition LOG_INFO = 6 # informational LOG_DEBUG = 7 # debug-level messages class snmp_log_message(ctypes.Structure): pass snmp_log_message_p = ctypes.POINTER(snmp_log_message) snmp_log_message._fields_ = [ ("priority", ctypes.c_int), ("msg", ctypes.c_char_p) ] # include/net-snmp/library/snmp_api.h SNMPERR_SUCCESS = 0 # include/net-snmp/library/default_store.h NETSNMP_DS_LIBRARY_ID = 0 NETSNMP_DS_APPLICATION_ID = 1 NETSNMP_DS_LIB_PERSISTENT_DIR = 8 for f in [ libnsa.netsnmp_ds_set_boolean ]: f.argtypes = [ ctypes.c_int, # int storeid ctypes.c_int, # int which ctypes.c_int # int value ] f.restype = ctypes.c_int for f in [ libnsa.netsnmp_ds_set_string ]: f.argtypes = [ ctypes.c_int, # int storeid ctypes.c_int, # int which ctypes.c_char_p # const char *value ] f.restype = ctypes.c_int # include/net-snmp/agent/ds_agent.h NETSNMP_DS_AGENT_ROLE = 1 NETSNMP_DS_AGENT_X_SOCKET = 1 # include/net-snmp/library/snmp.h SNMP_ERR_NOERROR = 0 for f in [ libnsa.init_snmp ]: f.argtypes = [ ctypes.c_char_p # const char *type ] f.restype = None for f in [ libnsa.snmp_shutdown ]: f.argtypes = [ ctypes.c_char_p # const char *type ] f.restype = None # include/net-snmp/library/oid.h c_oid = ctypes.c_ulong c_oid_p = ctypes.POINTER(c_oid) # include/net-snmp/types.h MAX_OID_LEN = 128 # include/net-snmp/agent/snmp_vars.h for f in [ libnsa.init_agent ]: f.argtypes = [ ctypes.c_char_p # const char *app ] f.restype = ctypes.c_int for f in [ libnsa.shutdown_agent ]: f.argtypes = None f.restype = ctypes.c_int # include/net-snmp/library/parse.h class tree(ctypes.Structure): pass # include/net-snmp/mib_api.h for f in [ libnsa.netsnmp_init_mib ]: f.argtypes = None f.restype = None for f in [ libnsa.read_mib ]: f.argtypes = [ ctypes.c_char_p # const char *filename ] f.restype = ctypes.POINTER(tree) for f in [ libnsa.read_objid ]: f.argtypes = [ ctypes.c_char_p, # const char *input c_oid_p, # oid *output c_sizet_p # size_t *out_len ] f.restype = ctypes.c_int # include/net-snmp/agent/agent_handler.h HANDLER_CAN_GETANDGETNEXT = 0x01 HANDLER_CAN_SET = 0x02 HANDLER_CAN_RONLY = HANDLER_CAN_GETANDGETNEXT HANDLER_CAN_RWRITE = (HANDLER_CAN_GETANDGETNEXT | \ HANDLER_CAN_SET) class netsnmp_mib_handler(ctypes.Structure): pass netsnmp_mib_handler_p = ctypes.POINTER(netsnmp_mib_handler) class netsnmp_handler_registration(ctypes.Structure): pass netsnmp_handler_registration_p = ctypes.POINTER(netsnmp_handler_registration) netsnmp_handler_registration._fields_ = [ ("handlerName", ctypes.c_char_p), ("contextName", ctypes.c_char_p), ("rootoid", c_oid_p), ("rootoid_len", ctypes.c_size_t), ("handler", netsnmp_mib_handler_p), ("modes", ctypes.c_int), ("priority", ctypes.c_int), ("range_subid", ctypes.c_int), ("range_ubound", c_oid), ("timeout", ctypes.c_int), ("global_cacheid", ctypes.c_int), ("my_reg_void", ctypes.c_void_p) ] for f in [ libnsa.netsnmp_create_handler_registration ]: f.argtypes = [ ctypes.c_char_p, # const char *name ctypes.c_void_p, # Netsnmp_Node_Handler *handler_access_method c_oid_p, # const oid *reg_oid ctypes.c_size_t, # size_t reg_oid_len ctypes.c_int # int modes ] f.restype = netsnmp_handler_registration_p # include/net-snmp/library/asn1.h ASN_INTEGER = 0x02 ASN_OCTET_STR = 0x04 ASN_APPLICATION = 0x40 # counter64 requires some extra work because it can't be reliably represented # by a single C data type class counter64(ctypes.Structure): @property def value(self): return self.high << 32 | self.low @value.setter def value(self, val): self.high = val >> 32 self.low = val & 0xFFFFFFFF def __init__(self, initval=0): ctypes.Structure.__init__(self, 0, 0) self.value = initval counter64_p = ctypes.POINTER(counter64) counter64._fields_ = [ ("high", ctypes.c_ulong), ("low", ctypes.c_ulong) ] # include/net-snmp/library/snmp_impl.h ASN_IPADDRESS = ASN_APPLICATION | 0 ASN_COUNTER = ASN_APPLICATION | 1 ASN_UNSIGNED = ASN_APPLICATION | 2 ASN_TIMETICKS = ASN_APPLICATION | 3 ASN_COUNTER64 = ASN_APPLICATION | 6 # include/net-snmp/agent/watcher.h WATCHER_FIXED_SIZE = 0x01 WATCHER_MAX_SIZE = 0x02 class netsnmp_watcher_info(ctypes.Structure): pass netsnmp_watcher_info_p = ctypes.POINTER(netsnmp_watcher_info) netsnmp_watcher_info._fields_ = [ ("data", ctypes.c_void_p), ("data_size", ctypes.c_size_t), ("max_size", ctypes.c_size_t), ("type", ctypes.c_ubyte), ("flags", ctypes.c_int) # net-snmp 5.7.x knows data_size_p here as well but we ignore it for # backwards compatibility with net-snmp 5.4.x. ] for f in [ libnsX.netsnmp_create_watcher_info ]: f.argtypes = [ ctypes.c_void_p, # void *data ctypes.c_size_t, # size_t size ctypes.c_ubyte, # u_char type ctypes.c_int # int flags ] f.restype = netsnmp_watcher_info_p for f in [ libnsX.netsnmp_register_watched_instance ]: f.argtypes = [ netsnmp_handler_registration_p, # netsnmp_handler_registration *reginfo netsnmp_watcher_info_p # netsnmp_watcher_info *winfo ] f.restype = ctypes.c_int for f in [ libnsX.netsnmp_register_watched_scalar ]: f.argtypes = [ netsnmp_handler_registration_p, # netsnmp_handler_registration *reginfo netsnmp_watcher_info_p # netsnmp_watcher_info *winfo ] f.restype = ctypes.c_int # include/net-snmp/types.h class netsnmp_variable_list(ctypes.Structure): pass netsnmp_variable_list_p = ctypes.POINTER(netsnmp_variable_list) netsnmp_variable_list_p_p = ctypes.POINTER(netsnmp_variable_list_p) # include/net-snmp/varbind_api.h for f in [ libnsa.snmp_varlist_add_variable ]: f.argtypes = [ netsnmp_variable_list_p_p, # netsnmp_variable_list **varlist c_oid_p, # const oid *name ctypes.c_size_t, # size_t name_length ctypes.c_ubyte, # u_char type ctypes.c_void_p, # const void *value ctypes.c_size_t # size_t len ] f.restype = netsnmp_variable_list_p # include/net-snmp/agent/table_data.h class netsnmp_table_row(ctypes.Structure): pass netsnmp_table_row_p = ctypes.POINTER(netsnmp_table_row) netsnmp_table_row._fields_ = [ ("indexes", netsnmp_variable_list_p), ("index_oid", c_oid_p), ("index_oid_len", ctypes.c_size_t), ("data", ctypes.c_void_p), ("next", netsnmp_table_row_p), ("prev", netsnmp_table_row_p) ] class netsnmp_table_data(ctypes.Structure): pass netsnmp_table_data_p = ctypes.POINTER(netsnmp_table_data) netsnmp_table_data._fields_ = [ ("indexes_template", netsnmp_variable_list_p), ("name", ctypes.c_char_p), ("flags", ctypes.c_int), ("store_indexes", ctypes.c_int), ("first_row", netsnmp_table_row_p), ("last_row", netsnmp_table_row_p) ] # include/net-snmp/agent/table_dataset.h class netsnmp_table_data_set_storage_udata(ctypes.Union): pass netsnmp_table_data_set_storage_udata._fields_ = [ ("voidp", ctypes.c_void_p), ("integer", ctypes.POINTER(ctypes.c_long)), ("string", ctypes.c_char_p), ("objid", c_oid_p), ("bitstring", ctypes.POINTER(ctypes.c_ubyte)), ("counter64", ctypes.POINTER(counter64)), ("floatVal", ctypes.POINTER(ctypes.c_float)), ("doubleVal", ctypes.POINTER(ctypes.c_double)) ] class netsnmp_table_data_set_storage(ctypes.Structure): pass netsnmp_table_data_set_storage_p = ctypes.POINTER(netsnmp_table_data_set_storage) netsnmp_table_data_set_storage._fields_ = [ ("column", ctypes.c_uint), ("writable", ctypes.c_byte), ("change_ok_fn", ctypes.c_void_p), ("my_change_data", ctypes.c_void_p), ("type", ctypes.c_ubyte), ("data", netsnmp_table_data_set_storage_udata), ("data_len", ctypes.c_ulong), ("next", netsnmp_table_data_set_storage_p) ] class netsnmp_table_data_set(ctypes.Structure): pass netsnmp_table_data_set_p = ctypes.POINTER(netsnmp_table_data_set) netsnmp_table_data_set._fields_ = [ ("table", netsnmp_table_data_p), ("default_row", netsnmp_table_data_set_storage_p), ("allow_creation", ctypes.c_int), ("rowstatus_column", ctypes.c_uint) ] for f in [ libnsX.netsnmp_create_table_data_set ]: f.argtypes = [ ctypes.c_char_p # const char *table_name ] f.restype = netsnmp_table_data_set_p for f in [ libnsX.netsnmp_table_dataset_add_row ]: f.argtypes = [ netsnmp_table_data_set_p, # netsnmp_table_data_set *table netsnmp_table_row_p, # netsnmp_table_row *row ] f.restype = None for f in [ libnsX.netsnmp_table_data_set_create_row_from_defaults ]: f.argtypes = [ netsnmp_table_data_set_storage_p # netsnmp_table_data_set_storage *defrow ] f.restype = netsnmp_table_row_p for f in [ libnsX.netsnmp_table_set_add_default_row ]: f.argtypes = [ netsnmp_table_data_set_p, # netsnmp_table_data_set *table_set ctypes.c_uint, # unsigned int column ctypes.c_int, # int type ctypes.c_int, # int writable ctypes.c_void_p, # void *default_value ctypes.c_size_t # size_t default_value_len ] f.restype = ctypes.c_int for f in [ libnsX.netsnmp_register_table_data_set ]: f.argtypes = [ netsnmp_handler_registration_p, # netsnmp_handler_registration *reginfo netsnmp_table_data_set_p, # netsnmp_table_data_set *data_set ctypes.c_void_p # netsnmp_table_registration_info *table_info ] f.restype = ctypes.c_int for f in [ libnsX.netsnmp_set_row_column ]: f.argtypes = [ netsnmp_table_row_p, # netsnmp_table_row *row ctypes.c_uint, # unsigned int column ctypes.c_int, # int type ctypes.c_void_p, # const void *value ctypes.c_size_t # size_t value_len ] f.restype = ctypes.c_int for f in [ libnsX.netsnmp_table_dataset_add_index ]: f.argtypes = [ netsnmp_table_data_set_p, # netsnmp_table_data_set *table ctypes.c_ubyte # u_char type ] f.restype = None for f in [ libnsX.netsnmp_table_dataset_remove_and_delete_row ]: f.argtypes = [ netsnmp_table_data_set_p, # netsnmp_table_data_set *table netsnmp_table_row_p # netsnmp_table_row *row ] # include/net-snmp/agent/snmp_agent.h for f in [ libnsa.agent_check_and_process ]: f.argtypes = [ ctypes.c_int # int block ] f.restype = ctypes.c_int netsnmpagent-0.6.0/examples/0000755000175000001440000000000013071262631016141 5ustar piefusers00000000000000netsnmpagent-0.6.0/examples/SIMPLE-MIB.txt0000644000175000001440000002735113070717326020315 0ustar piefusers00000000000000SIMPLE-MIB DEFINITIONS ::= BEGIN ------------------------------------------------------------------------ -- Simple example MIB for python-netsnmpagent -- Copyright (c) 2012-2016 Pieter Hollants -- Licensed under the GNU Lesser Public License (LGPL) version 3 ------------------------------------------------------------------------ -- Imports IMPORTS MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, Integer32, Unsigned32, Counter32, Counter64, TimeTicks, IpAddress, enterprises FROM SNMPv2-SMI TEXTUAL-CONVENTION, DisplayString FROM SNMPv2-TC MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP FROM SNMPv2-CONF agentxObjects FROM AGENTX-MIB; -- Description and update information simpleMIB MODULE-IDENTITY LAST-UPDATED "201307070000Z" ORGANIZATION "N/A" CONTACT-INFO "Editor: Pieter Hollants EMail: " DESCRIPTION "Simple example MIB for python-netsnmpagent" REVISION "201307070000Z" DESCRIPTION "A simple example MIB for python-netsnmpagent's simple.py." ::= { agentxObjects 30187 } -- Definition of a generic SimpleNotificationStatus type SimpleNotificationStatus ::= TEXTUAL-CONVENTION STATUS current DESCRIPTION "Indicates the enabling or disabling of a particular class of notifications." SYNTAX INTEGER { disabled (0), -- This class of notifications is disabled enabled (1) -- This class of notifications is enabled } -- Definition of MIB's root nodes simpleMIBObjects OBJECT IDENTIFIER ::= { simpleMIB 1 } simpleMIBNotifications OBJECT IDENTIFIER ::= { simpleMIB 2 } simpleMIBConformance OBJECT IDENTIFIER ::= { simpleMIB 3 } simpleScalars OBJECT IDENTIFIER ::= { simpleMIBObjects 1 } simpleTables OBJECT IDENTIFIER ::= { simpleMIBObjects 2 } ------------------------------------------------------------------------ -- Scalars ------------------------------------------------------------------------ simpleUnsigned OBJECT-TYPE SYNTAX Unsigned32 MAX-ACCESS read-write STATUS current DESCRIPTION "A read-write, unsigned, 32-bits integer value." ::= { simpleScalars 1 } simpleUnsignedRO OBJECT-TYPE SYNTAX Unsigned32 MAX-ACCESS read-only STATUS current DESCRIPTION "A read-only, unsigned, 32-bits integer value." ::= { simpleScalars 2 } simpleInteger OBJECT-TYPE SYNTAX Integer32 MAX-ACCESS read-write STATUS current DESCRIPTION "A read-write, signed, 32-bits integer value." ::= { simpleScalars 3 } simpleIntegerRO OBJECT-TYPE SYNTAX Integer32 MAX-ACCESS read-only STATUS current DESCRIPTION "A read-only, signed, 32-bits integer value." ::= { simpleScalars 4 } simpleCounter32 OBJECT-TYPE SYNTAX Counter32 MAX-ACCESS read-only STATUS current DESCRIPTION "A read-only, unsigned, 32-bits counter value." ::= { simpleScalars 5 } simpleCounter64 OBJECT-TYPE SYNTAX Counter64 MAX-ACCESS read-only STATUS current DESCRIPTION "A read-only, unsigned, 64-bits counter value." ::= { simpleScalars 6 } simpleTimeTicks OBJECT-TYPE SYNTAX TimeTicks MAX-ACCESS read-write STATUS current DESCRIPTION "A read-only, unsigned, 32-bits TimeTicks value." ::= { simpleScalars 7 } simpleIpAddress OBJECT-TYPE SYNTAX IpAddress MAX-ACCESS read-write STATUS current DESCRIPTION "A 32-bits IPv4 address." ::= { simpleScalars 8 } SimpleOctetString ::= TEXTUAL-CONVENTION DISPLAY-HINT "1024t" STATUS current DESCRIPTION "An octet string containing characters in UTF-8 encoding." SYNTAX OCTET STRING (SIZE (1..1024)) simpleOctetString OBJECT-TYPE SYNTAX SimpleOctetString MAX-ACCESS read-only STATUS current DESCRIPTION "An UTF-8 encoded string value." ::= { simpleScalars 9 } simpleDisplayString OBJECT-TYPE SYNTAX DisplayString MAX-ACCESS read-only STATUS current DESCRIPTION "An ASCII string value." ::= { simpleScalars 10 } ------------------------------------------------------------------------ -- Tables ------------------------------------------------------------------------ firstTableNumber OBJECT-TYPE SYNTAX Unsigned32 MAX-ACCESS read-only STATUS current DESCRIPTION "The number of entries in firstTable." ::= { simpleTables 1 } firstTable OBJECT-TYPE SYNTAX SEQUENCE OF FirstTableEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "The first simple table" ::= { simpleTables 2 } FirstTableEntry ::= SEQUENCE { firstTableEntryIndex DisplayString, firstTableEntryDesc DisplayString, firstTableEntryValue Integer32 } firstTableEntry OBJECT-TYPE SYNTAX FirstTableEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A particular firstTable row." INDEX { firstTableEntryIndex } ::= { firstTable 1 } firstTableEntryIndex OBJECT-TYPE SYNTAX DisplayString MAX-ACCESS not-accessible STATUS current DESCRIPTION "The index column used to generate string indices into firstTable." ::= { firstTableEntry 1 } firstTableEntryDesc OBJECT-TYPE SYNTAX DisplayString MAX-ACCESS read-only STATUS current DESCRIPTION "A firstTableEntry's description." ::= { firstTableEntry 2 } firstTableEntryValue OBJECT-TYPE SYNTAX Integer32 MAX-ACCESS read-only STATUS current DESCRIPTION "A firstTableEntry's value." ::= { firstTableEntry 3 } secondTableNumber OBJECT-TYPE SYNTAX Unsigned32 MAX-ACCESS read-only STATUS current DESCRIPTION "The number of entries in secondTable." ::= { simpleTables 3 } secondTable OBJECT-TYPE SYNTAX SEQUENCE OF SecondTableEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "The second simple table" ::= { simpleTables 4 } SecondTableEntry ::= SEQUENCE { secondTableEntryIndex Integer32, secondTableEntryDesc DisplayString, secondTableEntryValue Unsigned32 } secondTableEntry OBJECT-TYPE SYNTAX SecondTableEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A particular secondTable row." INDEX { secondTableEntryIndex } ::= { secondTable 1 } secondTableEntryIndex OBJECT-TYPE SYNTAX Integer32 (0..2147483647) MAX-ACCESS not-accessible STATUS current DESCRIPTION "The index column used to generate numerical indices into secondTable." ::= { secondTableEntry 1 } secondTableEntryDesc OBJECT-TYPE SYNTAX DisplayString MAX-ACCESS read-only STATUS current DESCRIPTION "An secondTableEntry's description." ::= { secondTableEntry 2 } secondTableEntryValue OBJECT-TYPE SYNTAX Unsigned32 MAX-ACCESS read-only STATUS current DESCRIPTION "An secondTableEntry's value." ::= { secondTableEntry 3 } thirdTableNumber OBJECT-TYPE SYNTAX Unsigned32 MAX-ACCESS read-only STATUS current DESCRIPTION "The number of entries in thirdTable." ::= { simpleTables 5 } thirdTable OBJECT-TYPE SYNTAX SEQUENCE OF FirstTableEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "The third simple table" ::= { simpleTables 6 } FirstTableEntry ::= SEQUENCE { thirdTableEntryIndex IpAddress, thirdTableEntryDesc DisplayString, thirdTableEntryValue Integer32 } thirdTableEntry OBJECT-TYPE SYNTAX FirstTableEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A particular thirdTable row." INDEX { thirdTableEntryIndex } ::= { thirdTable 1 } thirdTableEntryIndex OBJECT-TYPE SYNTAX IpAddress MAX-ACCESS not-accessible STATUS current DESCRIPTION "The index column used to generate IpAddress indices into thirdTable." ::= { thirdTableEntry 1 } thirdTableEntryDesc OBJECT-TYPE SYNTAX DisplayString MAX-ACCESS read-only STATUS current DESCRIPTION "A thirdTableEntry's description." ::= { thirdTableEntry 2 } thirdTableEntryValue OBJECT-TYPE SYNTAX IpAddress MAX-ACCESS read-only STATUS current DESCRIPTION "A thirdTableEntry's value." ::= { thirdTableEntry 3 } ------------------------------------------------------------------------ -- Notifications ------------------------------------------------------------------------ events OBJECT IDENTIFIER ::= { simpleMIBNotifications 0 } operation OBJECT IDENTIFIER ::= { simpleMIBNotifications 1 } simpleUnsignedROChange NOTIFICATION-TYPE OBJECTS { simpleUnsignedRO } STATUS current DESCRIPTION "A simpleScalarsChange notification signifies that there has been a change to the value of simpleUnsignedRO." ::= { events 1 } firstTableChange NOTIFICATION-TYPE OBJECTS { firstTableEntryDesc, firstTableEntryValue } STATUS current DESCRIPTION "A firstTableChange notification signifies that there has been a change to an firstTableEntry." ::= { events 2 } simpleUnsignedROChangeNotificationsEnabled OBJECT-TYPE SYNTAX SimpleNotificationStatus MAX-ACCESS read-write STATUS current DESCRIPTION "Controls whether simpleUnsignedROChange notifications are enabled or disabled." ::= { operation 1 } firstTableChangeNotificationsEnabled OBJECT-TYPE SYNTAX SimpleNotificationStatus MAX-ACCESS read-write STATUS current DESCRIPTION "Controls whether firstTableChange notifications are enabled or disabled." ::= { operation 2 } ------------------------------------------------------------------------ -- Conformance ------------------------------------------------------------------------ simpleMIBGroups OBJECT IDENTIFIER ::= { simpleMIBConformance 1 } simpleMIBCompliances OBJECT IDENTIFIER ::= { simpleMIBConformance 2 } simpleMIBScalarsGroup OBJECT-GROUP OBJECTS { simpleInteger, simpleIntegerRO, simpleUnsigned, simpleUnsignedRO, simpleCounter32, simpleCounter64, simpleTimeTicks, simpleIpAddress, simpleOctetString, simpleDisplayString, simpleUnsignedROChangeNotificationsEnabled } STATUS current DESCRIPTION "A collection of objects related to simpleScalars." ::= { simpleMIBGroups 1 } simpleMIBTablesGroup OBJECT-GROUP OBJECTS { firstTableNumber, firstTableEntryDesc, firstTableEntryValue, firstTableChangeNotificationsEnabled, secondTableNumber, secondTableEntryDesc, secondTableEntryValue } STATUS current DESCRIPTION "A collection of objects related to simpleTables." ::= { simpleMIBGroups 2 } simpleMIBScalarsNotificationsGroup NOTIFICATION-GROUP NOTIFICATIONS { simpleUnsignedROChange } STATUS current DESCRIPTION "The notifications which indicate specific changes in simpleMIBScalars." ::= { simpleMIBGroups 3 } simpleMIBTablesNotificationsGroup NOTIFICATION-GROUP NOTIFICATIONS { firstTableChange } STATUS current DESCRIPTION "The notifications which indicate specific changes in simpleTables." ::= { simpleMIBGroups 4 } END netsnmpagent-0.6.0/examples/simple_agent.py0000755000175000001440000002025613071257716021202 0ustar piefusers00000000000000#!/usr/bin/env python # # python-netsnmpagent simple example agent # # Copyright (c) 2013-2016 Pieter Hollants # Licensed under the GNU Lesser Public License (LGPL) version 3 # # # This is an example of a simple SNMP sub-agent using the AgentX protocol # to connect to a master agent (snmpd), extending its MIB with the # information from the included SIMPLE-MIB.txt. # # Use the included script run_simple_agent.sh to test this example. # # Alternatively, if you want to test with your system-wide snmpd instance, # it must have as minimal configuration: # # rocommunity 127.0.0.1 # master agentx # # snmpd must be started first, then this agent must be started as root # (because of the AgentX socket under /var/run/agentx/master), eg. via "sudo". # # Then, from a separate console and from inside the python-netsnmpagent # directory, you can run eg.: # # snmpwalk -v 2c -c -M+. localhost SIMPLE-MIB::simpleMIB # # If you wish to test setting values as well, your snmpd.conf needs a # line like this: # # rwcommunity 127.0.0.1 # # Then you can try something like: # # snmpset -v 2c -c -M+. localhost \ # SIMPLE-MIB::simpleInteger i 0 # import sys, os, signal import optparse import pprint # Make sure we use the local copy, not a system-wide one sys.path.insert(0, os.path.dirname(os.getcwd())) import netsnmpagent prgname = sys.argv[0] # Process command line arguments parser = optparse.OptionParser() parser.add_option( "-m", "--mastersocket", dest="mastersocket", help="Sets the transport specification for the master agent's AgentX socket", default="/var/run/agentx/master" ) parser.add_option( "-p", "--persistencedir", dest="persistencedir", help="Sets the path to the persistence directory", default="/var/lib/net-snmp" ) (options, args) = parser.parse_args() # Get terminal width for usage with pprint rows, columns = os.popen("stty size", "r").read().split() # First, create an instance of the netsnmpAgent class. We specify the # fully-qualified path to SIMPLE-MIB.txt ourselves here, so that you # don't have to copy the MIB to /usr/share/snmp/mibs. try: agent = netsnmpagent.netsnmpAgent( AgentName = "SimpleAgent", MasterSocket = options.mastersocket, PersistenceDir = options.persistencedir, MIBFiles = [ os.path.abspath(os.path.dirname(sys.argv[0])) + "/SIMPLE-MIB.txt" ] ) except netsnmpagent.netsnmpAgentException as e: print("{0}: {1}".format(prgname, e)) sys.exit(1) # Then we create all SNMP scalar variables we're willing to serve. simpleInteger = agent.Integer32( oidstr = "SIMPLE-MIB::simpleInteger" ) simpleIntegerContext1 = agent.Integer32( oidstr = "SIMPLE-MIB::simpleInteger", context = "context1", initval = 200, ) simpleIntegerRO = agent.Integer32( oidstr = "SIMPLE-MIB::simpleIntegerRO", writable = False ) simpleUnsigned = agent.Unsigned32( oidstr = "SIMPLE-MIB::simpleUnsigned" ) simpleUnsignedRO = agent.Unsigned32( oidstr = "SIMPLE-MIB::simpleUnsignedRO", writable = False ) simpleCounter32 = agent.Counter32( oidstr = "SIMPLE-MIB::simpleCounter32" ) simpleCounter32Context2 = agent.Counter32( oidstr = "SIMPLE-MIB::simpleCounter32", context = "context2", initval = pow(2,32) - 10, # To rule out endianness bugs ) simpleCounter64 = agent.Counter64( oidstr = "SIMPLE-MIB::simpleCounter64" ) simpleCounter64Context2 = agent.Counter64( oidstr = "SIMPLE-MIB::simpleCounter64", context = "context2", initval = pow(2,64) - 10, # To rule out endianness bugs ) simpleTimeTicks = agent.TimeTicks( oidstr = "SIMPLE-MIB::simpleTimeTicks" ) simpleIpAddress = agent.IpAddress( oidstr = "SIMPLE-MIB::simpleIpAddress", initval = "127.0.0.1" ) simpleOctetString = agent.OctetString( oidstr = "SIMPLE-MIB::simpleOctetString", initval = "Hello World" ) simpleDisplayString = agent.DisplayString( oidstr = "SIMPLE-MIB::simpleDisplayString", initval = "Nice to meet you" ) # Create the first table firstTable = agent.Table( oidstr = "SIMPLE-MIB::firstTable", indexes = [ agent.DisplayString() ], columns = [ (2, agent.DisplayString("Unknown place")), (3, agent.Integer32(0)) ], counterobj = agent.Unsigned32( oidstr = "SIMPLE-MIB::firstTableNumber" ) ) # Add the first table row firstTableRow1 = firstTable.addRow([agent.DisplayString("aa")]) firstTableRow1.setRowCell(2, agent.DisplayString("Prague")) firstTableRow1.setRowCell(3, agent.Integer32(20)) # Add the second table row firstTableRow2 = firstTable.addRow([agent.DisplayString("ab")]) firstTableRow2.setRowCell(2, agent.DisplayString("Barcelona")) firstTableRow2.setRowCell(3, agent.Integer32(28)) # Add the third table row firstTableRow3 = firstTable.addRow([agent.DisplayString("bb")]) firstTableRow3.setRowCell(3, agent.Integer32(18)) # Create the second table secondTable = agent.Table( oidstr = "SIMPLE-MIB::secondTable", indexes = [ agent.Integer32() ], columns = [ (2, agent.DisplayString("Unknown interface")), (3, agent.Unsigned32()) ], counterobj = agent.Unsigned32( oidstr = "SIMPLE-MIB::secondTableNumber" ) ) # Add the first table row secondTableRow1 = secondTable.addRow([agent.Integer32(1)]) secondTableRow1.setRowCell(2, agent.DisplayString("foo0")) secondTableRow1.setRowCell(3, agent.Unsigned32(5030)) # Add the second table row secondTableRow2 = secondTable.addRow([agent.Integer32(2)]) secondTableRow2.setRowCell(2, agent.DisplayString("foo1")) secondTableRow2.setRowCell(3, agent.Unsigned32(12842)) # Create the third table thirdTable = agent.Table( oidstr = "SIMPLE-MIB::thirdTable", indexes = [ agent.IpAddress() ], columns = [ (2, agent.DisplayString("Broadcast")), (3, agent.IpAddress("192.168.0.255")) ], counterobj = agent.Unsigned32( oidstr = "SIMPLE-MIB::thirdTableNumber" ) ) # Add the first table row thirdTableRow1 = thirdTable.addRow([agent.IpAddress("192.168.0.1")]) thirdTableRow1.setRowCell(2, agent.DisplayString("Host 1")) thirdTableRow1.setRowCell(3, agent.IpAddress("192.168.0.1")) # Add the second table row thirdTableRow2 = thirdTable.addRow([agent.IpAddress("192.168.0.2")]) thirdTableRow2.setRowCell(2, agent.DisplayString("Host 2")) thirdTableRow2.setRowCell(3, agent.IpAddress("192.168.0.2")) # Add the third table row thirdTableRow3 = thirdTable.addRow([agent.IpAddress("192.168.0.3")]) # Finally, we tell the agent to "start". This actually connects the # agent to the master agent. try: agent.start() except netsnmpagent.netsnmpAgentException as e: print("{0}: {1}".format(prgname, e)) sys.exit(1) print("{0}: AgentX connection to snmpd established.".format(prgname)) # Helper function that dumps the state of all registered SNMP variables def DumpRegistered(): for context in agent.getContexts(): print("{0}: Registered SNMP objects in Context \"{1}\": ".format(prgname, context)) vars = agent.getRegistered(context) pprint.pprint(vars, width=columns) print DumpRegistered() # Install a signal handler that terminates our simple agent when # CTRL-C is pressed or a KILL signal is received def TermHandler(signum, frame): global loop loop = False signal.signal(signal.SIGINT, TermHandler) signal.signal(signal.SIGTERM, TermHandler) # Install a signal handler that dumps the state of all registered values # when SIGHUP is received def HupHandler(signum, frame): DumpRegistered() signal.signal(signal.SIGHUP, HupHandler) # The simple agent's main loop. We loop endlessly until our signal # handler above changes the "loop" variable. print("{0}: Serving SNMP requests, send SIGHUP to dump SNMP object state, press ^C to terminate...".format(prgname)) loop = True while (loop): # Block and process SNMP requests, if available agent.check_and_process() # Since we didn't give simpleCounter, simpleCounter64 and simpleTimeTicks # a real meaning in the SIMPLE-MIB, we can basically do with them whatever # we want. Here, we just increase them, although in different manners. simpleCounter32.update(simpleCounter32.value() + 2) simpleCounter64.update(simpleCounter64.value() + 4294967294) simpleTimeTicks.update(simpleTimeTicks.value() + 1) # With counters, you can also call increment() on them simpleCounter32Context2.increment() # By 1 simpleCounter64Context2.increment(5) # By 5 print("{0}: Terminating.".format(prgname)) agent.shutdown() netsnmpagent-0.6.0/examples/run_simple_agent_over_tcpsocket.sh0000755000175000001440000000505212722265166025157 0ustar piefusers00000000000000# # python-netsnmpagent simple example agent # # Copyright (c) 2013-2016 Pieter Hollants # Licensed under the GNU Lesser Public License (LGPL) version 3 # # # This script makes running simple_agent.py easier for you because it takes # care of setting everything up so that the example agent can be run # successfully. # set -u set -e # Find path to snmpd executable SNMPD_BIN="" for DIR in /usr/local/sbin /usr/sbin do if [ -x $DIR/snmpd ] ; then SNMPD_BIN=$DIR/snmpd break fi done if [ -z "$SNMPD_BIN" ] ; then echo "snmpd executable not found -- net-snmp not installed?" exit 1 fi # Make sure we leave a clean system upon exit cleanup() { if [ -n "$TMPDIR" -a -d "$TMPDIR" ] ; then # Terminate snmpd, if running if [ -n "$SNMPD_PIDFILE" -a -e "$SNMPD_PIDFILE" ] ; then PID="$(cat $SNMPD_PIDFILE)" if [ -n "$PID" ] ; then kill -TERM "$PID" fi fi echo "* Cleaning up..." # Clean up temporary directory rm -rf "$TMPDIR" fi # Make sure echo is back on stty echo } trap cleanup EXIT QUIT TERM INT HUP echo "* Preparing snmpd environment..." # Create a temporary directory TMPDIR="$(mktemp --directory --tmpdir simple_agent.XXXXXXXXXX)" SNMPD_CONFFILE=$TMPDIR/snmpd.conf SNMPD_PIDFILE=$TMPDIR/snmpd.pid # Create a minimal snmpd configuration for our purposes cat <>$SNMPD_CONFFILE [snmpd] rocommunity public 127.0.0.1 rwcommunity simple 127.0.0.1 agentaddress localhost:5555 informsink localhost:5556 smuxsocket localhost:5557 master agentx agentXSocket tcp:localhost:5558 [snmp] persistentDir $TMPDIR/state EOF touch $TMPDIR/mib_indexes # Start a snmpd instance for testing purposes, run as the current user and # and independent from any other running snmpd instance $SNMPD_BIN -r -LE warning -C -c$SNMPD_CONFFILE -p$SNMPD_PIDFILE # Give the user guidance echo "* Our snmpd instance is now listening on localhost, port 5555." echo " From a second console, use the net-snmp command line utilities like this:" echo "" echo " cd `pwd`" echo " snmpwalk -v 2c -c public -M+. localhost:5555 SIMPLE-MIB::simpleMIB" echo " snmptable -v 2c -c public -M+. -Ci localhost:5555 SIMPLE-MIB::firstTable" echo " snmpget -v 2c -c public -M+. localhost:5555 SIMPLE-MIB::simpleInteger.0" echo " snmpset -v 2c -c simple -M+. localhost:5555 SIMPLE-MIB::simpleInteger.0 i 123" echo "" # Workaround to have CTRL-C not generate any visual feedback (we don't do any # input anyway) stty -echo # Now start the simple example agent echo "* Starting the simple example agent..." python simple_agent.py -m tcp:localhost:5558 -p $TMPDIR/ netsnmpagent-0.6.0/examples/run_threading_agent.sh0000755000175000001440000000463512722265166022527 0ustar piefusers00000000000000# # python-netsnmpagent example agent with threading # # Copyright (c) 2013-2016 Pieter Hollants # Licensed under the GNU Lesser Public License (LGPL) version 3 # # # This script makes running threading_agent.py easier for you because it takes # care of setting everything up so that the example agent can be run # successfully. # set -u set -e # Find path to snmpd executable SNMPD_BIN="" for DIR in /usr/local/sbin /usr/sbin do if [ -x $DIR/snmpd ] ; then SNMPD_BIN=$DIR/snmpd break fi done if [ -z "$SNMPD_BIN" ] ; then echo "snmpd executable not found -- net-snmp not installed?" exit 1 fi # Make sure we leave a clean system upon exit cleanup() { if [ -n "$TMPDIR" -a -d "$TMPDIR" ] ; then # Terminate snmpd, if running if [ -n "$SNMPD_PIDFILE" -a -e "$SNMPD_PIDFILE" ] ; then PID="$(cat $SNMPD_PIDFILE)" if [ -n "$PID" ] ; then kill -TERM "$PID" fi fi echo "* Cleaning up..." # Clean up temporary directory rm -rf "$TMPDIR" fi # Make sure echo is back on stty echo } trap cleanup EXIT QUIT TERM INT HUP echo "* Preparing snmpd environment..." # Create a temporary directory TMPDIR="$(mktemp --directory --tmpdir threading_agent.XXXXXXXXXX)" SNMPD_CONFFILE=$TMPDIR/snmpd.conf SNMPD_PIDFILE=$TMPDIR/snmpd.pid # Create a minimal snmpd configuration for our purposes cat <>$SNMPD_CONFFILE [snmpd] rocommunity public 127.0.0.1 rwcommunity simple 127.0.0.1 agentaddress localhost:5555 informsink localhost:5556 smuxsocket localhost:5557 master agentx agentXSocket $TMPDIR/snmpd-agentx.sock [snmp] persistentDir $TMPDIR/state EOF touch $TMPDIR/mib_indexes # Start a snmpd instance for testing purposes, run as the current user and # and independent from any other running snmpd instance $SNMPD_BIN -r -LE warning -C -c$SNMPD_CONFFILE -p$SNMPD_PIDFILE # Give the user guidance echo "* Our snmpd instance is now listening on localhost, port 5555." echo " From a second console, use the net-snmp command line utilities like this:" echo "" echo " cd `pwd`" echo " snmpwalk -v 2c -c public -M+. localhost:5555 THREADING-MIB::threadingMIB" echo " snmpget -v 2c -c public -M+. localhost:5555 THREADING-MIB::threadingString.0" echo "" # Workaround to have CTRL-C not generate any visual feedback (we don't do any # input anyway) stty -echo # Now start the threading agent echo "* Starting the threading agent..." python threading_agent.py -m $TMPDIR/snmpd-agentx.sock -p $TMPDIR/ netsnmpagent-0.6.0/examples/threading_agent.py0000755000175000001440000001612313071257716021654 0ustar piefusers00000000000000#!/usr/bin/env python # # python-netsnmpagent example agent with threading # # Copyright (c) 2013-2016 Pieter Hollants # Licensed under the GNU Lesser Public License (LGPL) version 3 # # # simple_agent.py demonstrates registering the various SNMP object types quite # nicely but uses an inferior control flow logic: the main loop blocks in # net-snmp's check_and_process() call until some event happens (eg. SNMP # requests need processing). Only then will data be updated, not inbetween. And # on the other hand, SNMP requests can not be handled while data is being # updated, which might take longer periods of time. # # This example agent uses a more real life-suitable approach by outsourcing the # data update process into a separate thread that gets woken up through an # SIGALRM handler at an configurable interval. This does only ensure periodic # data updates, it also makes sure that SNMP requests will always be replied to # in time. # # Note that this implementation does not address possible locking issues: if # a SNMP client's requests are processed while the data update thread is in the # midst of refreshing the SNMP objects, the client might receive partially # inconsistent data. # # Use the included script run_threading_agent.sh to test this example. # # Alternatively, see the comment block in the head of simple_agent.py for # adaptable instructions how to run this example against a system-wide snmpd # instance. # import sys, os, signal, time import optparse, threading, subprocess # Make sure we use the local copy, not a system-wide one sys.path.insert(0, os.path.dirname(os.getcwd())) import netsnmpagent prgname = sys.argv[0] # Process command line arguments parser = optparse.OptionParser() parser.add_option( "-i", "--interval", dest="interval", help="Set interval in seconds between data updates", default=30 ) parser.add_option( "-m", "--mastersocket", dest="mastersocket", help="Sets the transport specification for the master agent's AgentX socket", default="/var/run/agentx/master" ) parser.add_option( "-p", "--persistencedir", dest="persistencedir", help="Sets the path to the persistence directory", default="/var/lib/net-snmp" ) (options, args) = parser.parse_args() headerlogged = 0 def LogMsg(msg): """ Writes a formatted log message with a timestamp to stdout. """ global headerlogged if headerlogged == 0: print("{0:<8} {1:<90} {2}".format( "Time", "MainThread", "UpdateSNMPObjsThread" )) print("{0:-^120}".format("-")) headerlogged = 1 threadname = threading.currentThread().name funcname = sys._getframe(1).f_code.co_name if funcname == "": funcname = "Main code path" elif funcname == "LogNetSnmpMsg": funcname = "net-snmp code" else: funcname = "{0}()".format(funcname) if threadname == "MainThread": logmsg = "{0} {1:<112.112}".format( time.strftime("%T", time.localtime(time.time())), "{0}: {1}".format(funcname, msg) ) else: logmsg = "{0} {1:>112.112}".format( time.strftime("%T", time.localtime(time.time())), "{0}: {1}".format(funcname, msg) ) print(logmsg) def LogNetSnmpMsg(priority, msg): """ Log handler for log messages generated by net-snmp code. """ LogMsg("[{0}] {1}.".format(priority, msg)) # Create an instance of the netsnmpAgent class try: agent = netsnmpagent.netsnmpAgent( AgentName = "ThreadingAgent", MasterSocket = options.mastersocket, PersistenceDir = options.persistencedir, MIBFiles = [ os.path.abspath(os.path.dirname(sys.argv[0])) + "/THREADING-MIB.txt" ], LogHandler = LogNetSnmpMsg, ) except netsnmpagent.netsnmpAgentException as e: print("{0}: {1}".format(prgname, e)) sys.exit(1) # Register the only SNMP object we server, a DisplayString threadingString = agent.DisplayString( oidstr = "THREADING-MIB::threadingString", initval = "" ) def UpdateSNMPObjs(): """ Function that does the actual data update. """ global threadingString LogMsg("Beginning data update.") data = "" # Obtain the data by calling an external command. We don't use # subprocess.check_output() here for compatibility with Python versions # older than 2.7. LogMsg("Calling external command \"sleep 5; date\".") proc = subprocess.Popen( "sleep 5; date", shell=True, env={ "LANG": "C" }, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) output = proc.communicate()[0].splitlines()[0] rc = proc.poll() if rc != 0: LogMsg("An error occured executing the command: {0}".format(output)) return msg = "Updating \"threadingString\" object with data \"{0}\"." LogMsg(msg.format(output)) threadingString.update(output) LogMsg("Data update done, exiting thread.") def UpdateSNMPObjsAsync(): """ Starts UpdateSNMPObjs() in a separate thread. """ # UpdateSNMPObjs() will be executed in a separate thread so that the main # thread can continue looping and processing SNMP requests while the data # update is still in progress. However we'll make sure only one update # thread is run at any time, even if the data update interval has been set # too low. if threading.active_count() == 1: LogMsg("Creating thread for UpdateSNMPObjs().") t = threading.Thread(target=UpdateSNMPObjs, name="UpdateSNMPObjsThread") t.daemon = True t.start() else: LogMsg("Data update still active, data update interval too low?") # Start the agent (eg. connect to the master agent). try: agent.start() except netsnmpagent.netsnmpAgentException as e: LogMsg("{0}: {1}".format(prgname, e)) sys.exit(1) # Trigger initial data update. LogMsg("Doing initial call to UpdateSNMPObjsAsync().") UpdateSNMPObjsAsync() # Install a signal handler that terminates our threading agent when CTRL-C is # pressed or a KILL signal is received def TermHandler(signum, frame): global loop loop = False signal.signal(signal.SIGINT, TermHandler) signal.signal(signal.SIGTERM, TermHandler) # Define a signal handler that takes care of updating the data periodically def AlarmHandler(signum, frame): global loop, timer_triggered LogMsg("Got triggered by SIGALRM.") if loop: timer_triggered = True UpdateSNMPObjsAsync() signal.signal(signal.SIGALRM, AlarmHandler) signal.setitimer(signal.ITIMER_REAL, float(options.interval)) msg = "Installing SIGALRM handler triggered every {0} seconds." msg = msg.format(options.interval) LogMsg(msg) signal.signal(signal.SIGALRM, AlarmHandler) signal.setitimer(signal.ITIMER_REAL, float(options.interval)) # The threading agent's main loop. We loop endlessly until our signal # handler above changes the "loop" variable. LogMsg("Now serving SNMP requests, press ^C to terminate.") loop = True while loop: # Block until something happened (signal arrived, SNMP packets processed) timer_triggered = False res = agent.check_and_process() if res == -1 and not timer_triggered and loop: loop = False LogMsg("Error {0} in SNMP packet processing!".format(res)) elif loop and timer_triggered: LogMsg("net-snmp's check_and_process() returned due to SIGALRM (res={0}), doing another loop.".format(res)) elif loop: LogMsg("net-snmp's check_and_process() returned (res={0}), doing another loop.".format(res)) LogMsg("Terminating.") agent.shutdown() netsnmpagent-0.6.0/examples/run_simple_agent.sh0000755000175000001440000000507013006124072022027 0ustar piefusers00000000000000# # python-netsnmpagent simple example agent # # Copyright (c) 2013-2016 Pieter Hollants # Licensed under the GNU Lesser Public License (LGPL) version 3 # # # This script makes running simple_agent.py easier for you because it takes # care of setting everything up so that the example agent can be run # successfully. # set -u set -e # Find path to snmpd executable SNMPD_BIN="" for DIR in /usr/local/sbin /usr/sbin do if [ -x $DIR/snmpd ] ; then SNMPD_BIN=$DIR/snmpd break fi done if [ -z "$SNMPD_BIN" ] ; then echo "snmpd executable not found -- net-snmp not installed?" exit 1 fi # Make sure we leave a clean system upon exit cleanup() { if [ -n "$TMPDIR" -a -d "$TMPDIR" ] ; then # Terminate snmpd, if running if [ -n "$SNMPD_PIDFILE" -a -e "$SNMPD_PIDFILE" ] ; then PID="$(cat $SNMPD_PIDFILE)" if [ -n "$PID" ] ; then kill -TERM "$PID" fi fi echo "* Cleaning up..." # Clean up temporary directory rm -rf "$TMPDIR" fi # Make sure echo is back on stty echo } trap cleanup EXIT QUIT TERM INT HUP echo "* Preparing snmpd environment..." # Create a temporary directory TMPDIR="$(mktemp --directory --tmpdir simple_agent.XXXXXXXXXX)" SNMPD_CONFFILE=$TMPDIR/snmpd.conf SNMPD_PIDFILE=$TMPDIR/snmpd.pid # Create a minimal snmpd configuration for our purposes cat <>$SNMPD_CONFFILE [snmpd] rocommunity public 127.0.0.1 rwcommunity simple 127.0.0.1 agentaddress localhost:5555 informsink localhost:5556 smuxsocket localhost:5557 master agentx agentXSocket $TMPDIR/snmpd-agentx.sock [snmp] persistentDir $TMPDIR/state EOF touch $TMPDIR/mib_indexes # Start a snmpd instance for testing purposes, run as the current user and # and independent from any other running snmpd instance $SNMPD_BIN -r -LE warning -C -c$SNMPD_CONFFILE -p$SNMPD_PIDFILE # Give the user guidance echo "* Our snmpd instance is now listening on localhost, port 5555." echo " From a second console, use the net-snmp command line utilities like this:" echo "" echo " cd `pwd`" echo " snmpwalk -v 2c -c public -M+. localhost:5555 SIMPLE-MIB::simpleMIB" echo " snmptable -v 2c -c public -M+. -Ci localhost:5555 SIMPLE-MIB::firstTable" echo " snmpget -v 2c -c public -M+. localhost:5555 SIMPLE-MIB::simpleInteger.0" echo " snmpset -v 2c -c simple -M+. localhost:5555 SIMPLE-MIB::simpleInteger.0 i 123" echo "" # Workaround to have CTRL-C not generate any visual feedback (we don't do any # input anyway) stty -echo # Now start the simple example agent echo "* Starting the simple example agent..." python simple_agent.py -m $TMPDIR/snmpd-agentx.sock -p $TMPDIR/ netsnmpagent-0.6.0/examples/THREADING-MIB.txt0000644000175000001440000000760512722265166020634 0ustar piefusers00000000000000THREADING-MIB DEFINITIONS ::= BEGIN ------------------------------------------------------------------------ -- MIB for python-netsnmpagent's example threading_agent.py -- Copyright (c) 2012-2016 Pieter Hollants -- Licensed under the GNU Lesser Public License (LGPL) version 3 ------------------------------------------------------------------------ -- Imports IMPORTS MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, Integer32, Unsigned32, Counter32, Counter64, TimeTicks, IpAddress, enterprises FROM SNMPv2-SMI TEXTUAL-CONVENTION, DisplayString FROM SNMPv2-TC MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP FROM SNMPv2-CONF agentxObjects FROM AGENTX-MIB; -- Description and update information threadingMIB MODULE-IDENTITY LAST-UPDATED "201307070000Z" ORGANIZATION "N/A" CONTACT-INFO "Editor: Pieter Hollants EMail: " DESCRIPTION "A MIB for python-netsnmpagent's example threading_agent.py" REVISION "201307070000Z" DESCRIPTION "First version." ::= { agentxObjects 101 } -- Definition of a generic ThreadingNotificationStatus type ThreadingNotificationStatus ::= TEXTUAL-CONVENTION STATUS current DESCRIPTION "Indicates the enabling or disabling of a particular class of notifications." SYNTAX INTEGER { disabled (0), -- This class of notifications is disabled enabled (1) -- This class of notifications is enabled } -- Definition of MIB's root nodes threadingMIBObjects OBJECT IDENTIFIER ::= { threadingMIB 1 } threadingMIBNotifications OBJECT IDENTIFIER ::= { threadingMIB 2 } threadingMIBConformance OBJECT IDENTIFIER ::= { threadingMIB 3 } threadingScalars OBJECT IDENTIFIER ::= { threadingMIBObjects 1 } ------------------------------------------------------------------------ -- Scalars ------------------------------------------------------------------------ threadingString OBJECT-TYPE SYNTAX DisplayString MAX-ACCESS read-only STATUS current DESCRIPTION "A string. Curious about its contents?" ::= { threadingScalars 1 } ------------------------------------------------------------------------ -- Notifications ------------------------------------------------------------------------ events OBJECT IDENTIFIER ::= { threadingMIBNotifications 0 } operation OBJECT IDENTIFIER ::= { threadingMIBNotifications 1 } threadingStringChange NOTIFICATION-TYPE OBJECTS { threadingString } STATUS current DESCRIPTION "A threadingStringChange notification signifies that there has been a change to the value of threadingString." ::= { events 1 } threadingStringChangeNotificationsEnabled OBJECT-TYPE SYNTAX ThreadingNotificationStatus MAX-ACCESS read-write STATUS current DESCRIPTION "Controls whether threadingStringChange notifications are enabled or disabled." ::= { operation 1 } ------------------------------------------------------------------------ -- Conformance ------------------------------------------------------------------------ threadingMIBGroups OBJECT IDENTIFIER ::= { threadingMIBConformance 1 } threadingMIBScalarsGroup OBJECT-GROUP OBJECTS { threadingString, threadingStringChangeNotificationsEnabled } STATUS current DESCRIPTION "A collection of objects related to threadingScalars." ::= { threadingMIBGroups 1 } threadingMIBScalarsNotificationsGroup NOTIFICATION-GROUP NOTIFICATIONS { threadingStringChange } STATUS current DESCRIPTION "The notifications which indicate specific changes in threadingScalars." ::= { threadingMIBGroups 2 } END netsnmpagent-0.6.0/examples/README0000644000175000001440000000330412233477550017030 0ustar piefusers00000000000000This directory contains two example agents together with associated MIBs: - "simple_agent.py" is the first example you should look at. It demonstrates registering the various supported SNMP object types including tables. For those interested in SNMP v3 it also shows registering variables in contexts. The simple agent uses net-snmp's check_and_process() call to wait for SNMP requests in a blocking manner. Data is only updated inbetween SNMP requests. For a different, more real-life usable approach see the next example. - "threading_agent.py" should be looked at next. It registers just a single DisplayString SNMP variable but demonstrates how to use Python's threading module to separate the data update process from the SNMP request processing so that both can execute independently. For both examples, shell scripts are included that should make it a waltz to play with the agents. The scripts take care of everything required to set up a custom, minimal snmpd instance that runs under your ordinary user account and separate from any system-wide running snmpd: - "run_simple_agent.sh" does exactly that. Run it and it will even tell you example SNMP commands that can be used to test that everything works. - "run_simple_agent_over_tcpsocket.sh" runs the same example, "simple_agent.py", but uses a TCP port instead of a Unix domain socket for the AgentX communication. - "run_threading_agent.py" runs "threading_agent.py" (doh!). If instead you want to run the example agents (or later on your own agents) against your system-wide snmpd instance, your /etc/snmp/snmpd.conf (the path may vary) must be configured appropriately. See the comments at the beginning of simple_agent.py for more info. netsnmpagent-0.6.0/PKG-INFO0000644000175000001440000000252013071262631015417 0ustar piefusers00000000000000Metadata-Version: 1.1 Name: netsnmpagent Version: 0.6.0 Summary: Facilitates writing Net-SNMP (AgentX) subagents in Python Home-page: https://github.com/pief/python-netsnmpagent Author: Pieter Hollants Author-email: pieter@hollants.com License: LGPL-3.0 Description: python-netsnmpagent is a Python module that facilitates writing Net-SNMP subagents in Python. Subagents connect to a locally running Master agent (snmpd) over a Unix domain socket (eg. "/var/run/agentx/master") and using the AgentX protocol (RFC2741). They implement custom Management Information Base (MIB) modules that extend the local node's MIB tree. Usually, this requires writing a MIB as well, ie. a text file that specifies the structure, names and data types of the information within the MIB module. Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Software Development :: Libraries netsnmpagent-0.6.0/README0000644000175000001440000001110313071257716015207 0ustar piefusers00000000000000 python-netsnmpagent Module Copyright (c) 2013-2016 Pieter Hollants Licensed under the GNU Lesser Public License (LGPL) version 3 WHAT IS IT? python-netsnmpagent is a Python module that facilitates writing Net-SNMP subagents in Python. Subagents connect to a locally running Master agent (snmpd) over a Unix domain socket (eg. "/var/run/agentx/master") and using the AgentX protocol (RFC2741). They implement custom Management Information Base (MIB) modules that extend the local node's MIB tree. Usually, this requires writing a MIB as well, ie. a text file that specifies the structure, names and data types of the information within the MIB module. python-netsnmpagent was written after a lot of frustration trying to fix the python-agentx module hosted on Sourceforge. I fixed some smaller issues there but when it came to making sure that you could actually walk the whole MIB, not just the variables registered, I realized that it would not make much sense to reimplement the complete behavior of a proper SNMP agent when the NetSNMP API actually can handle those things for you. Also, to be honest, I don't get python-agentx's design :-) python-netsnmpagent, by contrast, in the first line focusses on making the net-snmp C agent API accessible from Python. Though I will try not to just pass through every library call 1:1 but wrap them in a useful manner. net-snmp itself also comes with a Python module (see the "python" subdirectory in the source distribution), however it seems to offer bindings that implement the client side only, not the agent side. REQUIREMENTS python-netsnmpagent requires the net-snmp libraries to be installed. The runtime libraries are enough, development files are not necessary. Tested versions include 5.4.2 (included with SUSE Linux Enterprise Server 11 SP2), 5.4.3 (included with Ubuntu 12.04 LTS) and net-snmp 5.7.3 (included with openSUSE 12.3). While the intent is to support both net-snmp 5.4.x and 5.7.x versions at least for the near future, I want to avoid double code paths whereever possible. I therefore have to stick to the net-snmp 5.4.x way of things at certain places indicated by code comments. python-netsnmpagent has been tested and is compatible with: - Python 2.6 - Python 2.7 - Python 3.5 (since November 1st, 2016) LICENSE Version older than May 28th, 2016 were licensed under the GNU General Public License (GPL), version 3. Since May 28th, 2016, python-netsnmpagent is being licensed under the GNU Lesser General Public License (LGPL), version 3. See http://www.gnu.org/licenses/lgpl-3.0.txt or the included file LICENSE. DOWNLOAD The PyPI page with distribution archives is available at http://pypi.python.org/pypi/netsnmpagent Binary RPMs for SuSE distributions are available via search.opensuse.org and the Open Build Service Project page at https://build.opensuse.org/package/show?package=python-netsnmpagent&project=home%3Apfhllnts If you wish to follow development more closely clone the GitHub repo: https://github.com/pief/python-netsnmpagent.git INSTALLATION If you do not use a ready package for your Linux distribution, you can install directly from source using python setup.py install This will most probably require appropriate rights (eg. being "root"). EXAMPLE USAGE The "examples" subdirectory contains well-commented example agents that should give you a quickstart on how to use python-netsnmpagent. Read the "README" file contained there for a short introduction on the different examples. API The Python API has not really been documented yet. For now, please see the example agents' source code for info on how to use the python-netsnmpagent module. TESTS python-netsnmpagent now comes with integration tests to test the net-snmp integration. Type "make tests" to run them. See tests/README for the reasons why "nosetests" can't be run directly. TODO - notifications/traps - more tests CREDITS python-netsnmpagent was inspired by python-agentx, courtesy of Bozhin Zafirov . I blatantly ripped some ideas and some lines of code, shame on me. SNMP contexts support and various other additions were implemented by Steven Hiscocks . The bug that DisplayStrings/OctetStrings got truncated when trying to update them with a string larger that the original InitVal was hunted down and fixed by Max "mk23" Kalika . Tobias Deiminger contributed fixes related to using IpAddress objects as table indices. Additional smaller bugfixes were done by Anton Todorov . netsnmpagent-0.6.0/setup.py0000744000175000001440000000324213071262530016035 0ustar piefusers00000000000000#!/usr/bin/env python # # python-netsnmpagent module # Copyright (c) 2013-2016 Pieter Hollants # Licensed under the GNU Lesser Public License (LGPL) version 3 # # Distutils setup script # from distutils.core import setup try: import ctypes except: print("netsnmpagent requires the ctypes Python module!") import sys sys.exit(1) setup( name = "netsnmpagent", version = "0.6.0", description = "Facilitates writing Net-SNMP (AgentX) subagents in Python", long_description = """ python-netsnmpagent is a Python module that facilitates writing Net-SNMP subagents in Python. Subagents connect to a locally running Master agent (snmpd) over a Unix domain socket (eg. "/var/run/agentx/master") and using the AgentX protocol (RFC2741). They implement custom Management Information Base (MIB) modules that extend the local node's MIB tree. Usually, this requires writing a MIB as well, ie. a text file that specifies the structure, names and data types of the information within the MIB module.""", author = "Pieter Hollants", author_email = "pieter@hollants.com", py_modules = [ "netsnmpagent", "netsnmpapi" ], license = "LGPL-3.0", url = "https://github.com/pief/python-netsnmpagent", classifiers = [ 'Intended Audience :: Developers', 'Natural Language :: English', 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', 'Operating System :: POSIX', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries' ], ) netsnmpagent-0.6.0/ChangeLog0000644000175000001440000025672313071262530016112 0ustar piefusers00000000000000 Changes from v0.5.3 to v0.6.0 ============================= * Wed Apr 5 22:15:18 2017 +0100 - Radu - Eosif Mihailescu Fix packaging of examples - the proper destination is %{_defaultdocdir}/%{name}-%{version} ... - ... but why bother when %doc can magically take care of it? (Git commit ab4657b276f804552399681634f5e024555b76c9) * Sun Mar 12 15:51:38 2017 +0100 - Pieter Hollants Correct RFC number that describes the AgentX protocol Fixes #29. (Git commit 101cc3d88d48d77b8b5260cc7479a3a238628431) * Tue Nov 1 20:37:40 2016 +0100 - Pieter Hollants netsnmpagent: Update module docstring Hooray, we're not under heavy development anymore! (Git commit f543df2195867662dc72e61989e25798fcad1765) * Tue Nov 1 20:35:59 2016 +0100 - Pieter Hollants Update README and setup.py.in to indicate Python 3 compatibility (Git commit 3c662434549de36cffbdaff0f6de97a7a52ad0e3) * Tue Nov 1 14:54:28 2016 +0100 - Pieter Hollants netsnmpagent: Code formatting (Git commit 639619802d880b90d22b0492a93a51dd8bcb2f64) * Tue Nov 1 14:54:11 2016 +0100 - Pieter Hollants netsnmpagent: Correct some comments (Git commit 9e22370ba002b134543d24c4d28b007e8d828c9b) * Tue Nov 1 14:50:41 2016 +0100 - Pieter Hollants netsnmpagent: Remove superfluous double assignment of self._cvar.value We already assigned it a few lines above and also didn't modify it inbetween. (Git commit cb9af33107b21db6f75514e9ba13a7b0991a2a35) * Tue Nov 1 14:06:10 2016 +0100 - Pieter Hollants netsnmpagent: Convert between byte and unicode strings if necessary This change addresses the major issue that prevented python-netsnmpagent from running under Python 3 so far. Under Python 2.x, a string that gets declared is of the "str" type, which is implemented as a byte string. Unicode strings have to be explicitly declared: >>> a = "Metallica" >>> type(a) >>> isinstance(a, bytes) True >>> b = u"Motörhead" >>> type(b) We were prettily using "str" strings everywhere, which, being byte strings, perfectly aligned with the C-style strings exposed through the ctypes module. Now under Python 3.x, however, a string that gets declared is an instance of a class that is still called "str" but is implemented as a collection of Unicode code points. There is no »u"Foo"« notation anymore, instead byte strings have to be explicitly declared with a "b" prefix: >>> a = "Motörhead" >>> type(a) >>> isinstance(a, bytes) False >>> b = b"Metallica" >>> type(b) Following the paradigma of enforcing a stricter distinction between Unicode and byte strings in code, the ctypes module in Python 3.x removed automatic conversions. Thus we now need to explicitly encode Unicode strings to byte strings before passing them to net-snmp. Likewise we need to decode received strings before returning them to Python code. In both cases we use the encoding retrieved from locale.getpreferredencoding(). Note that this changed behavior affects running under Python 3.x ONLY, in an attempt to provide the "natural" behavior expected under the Python version used. Under Python 2.x, the interface to your Python code remains UNCHANGED, expecting and returning "str" byte strings. This also means that if your agent code is running under Python 2.x and you want to pass or receive Unicode strings to python-netsnmpagent and net-snmp, you will have to keep encoding/decoding them yourself. (Git commit 003fcb2724cfbaf22b876ab604b1914303a7e10d) * Tue Nov 1 15:27:55 2016 +0100 - Pieter Hollants netsnmpagent: Use long() under Python 2.x and int() otherwise Python 3.x does not need the "long" type anymore because "int" numbers have no limit anymore. (Git commit 79b3a35c7f06f250fd447b2bd8b1a5465b135870) * Tue Nov 1 15:17:14 2016 +0100 - Pieter Hollants netsnmpagent: Always return numbers through int(), ignoring sys.maxint Actually I'm not sure what I tried to achieve with the sys.maxint check as we can safely rely on int() under Python 2.x to automatically return a "long" typed number if "int" doesn't suffice. Under Python 3.x "int" doesn't have a limit anymore, anyway. (Git commit 919c47dc53048204b11b22597954bafc3f5f2065) * Tue Nov 1 19:34:30 2016 +0100 - Pieter Hollants netsnmptestenv: Decode snmpcmd output to Unicode if necessary Python 3 strings are Unicode strings, not byte strings. (Git commit f6b988ac08a4afba112eec0c84368ec8c5d9b540) * Tue Nov 1 20:16:25 2016 +0100 - Pieter Hollants netsnmptestenv: Hardcode temporary directory name As the tests are now no longer run through the nosetests wrapper script, sys.argv[0] doesn't contain reasonable data anymore. (Git commit 80f9a698504f346f77a80be3e303b550e90d27f4) * Tue Nov 1 19:28:33 2016 +0100 - Pieter Hollants Makefile: Run tests with both Python 2 and 3 This makes "make tests" run all tests with both Python 2 and Python 3, thereby allowing to identify code that behaves differently under either Python version. Because (at least on my system) there are no "nosetests2" and "nosetests3" links but "python2" and "python3" links do exist, we now run the tests via 'python2 -c "import nose; nose.main()' statements (likewise for python3). (Git commit a9cd9bdc71d667d491a5829a7f25f4399933db02) * Tue Nov 1 20:14:36 2016 +0100 - Pieter Hollants netsnmptestenv: Call shutdown() via atexit instead of __del__() Lesson learned: don't do cleanup stuff in __del__() or you'll get exceptions printed on stdout and possibly incomplete cleanup when running under Python 3. (Git commit 93cd55a97f0db3b407454e1cd90143decd744fc6) * Tue Nov 1 15:34:24 2016 +0100 - Pieter Hollants test_02_netsnmpagent_init: Drop unused imports (Git commit 8ffc1c76ad4cb6be00fe8575bc89ebd9bd71411f) * Tue Nov 1 20:19:10 2016 +0100 - Pieter Hollants netsnmptestenv: Add/revise some comments (Git commit dd7fd8046d2b3d0c33f64c32d0464b4873dfbdd9) * Mon Oct 31 19:16:27 2016 +0100 - Pieter Hollants Code formatting fixes (Git commit 9975c3a539a9cf6b278cd8904ad0efb3346c7e4e) * Thu Oct 27 21:11:11 2016 +0200 - Pieter Hollants Python 3 support: Use items() when iteritems() is not available (Git commit a6f837e06f2d68cbc1cf74fc075d2fe1403ab140) * Thu Oct 27 21:06:46 2016 +0200 - Pieter Hollants Python 3 support: Use print(foo) instead of print foo (Git commit a670331c7e126cf4d39ef6a73b61cd92686f2b41) * Mon Aug 29 23:06:24 2016 +0200 - Pieter Hollants Update README to give credit where credit is due (Git commit 5122583d4d0f7a1a90a9127f76550b5f37ec2f56) * Mon Aug 29 23:01:07 2016 +0200 - Pieter Hollants Trivial description fixes in SIMPLE-MIB.txt (Git commit d26c73a028af9e3df8e3c74653e6f280d3988a01) * Fri Aug 19 08:53:19 2016 +0200 - Tobias Deiminger Fixes for using IpAddress objects as table indices This incorporates a number of fixes for using IpAddress objects as table indices: 1. Add missing attributes to IpAddress class' __init__() method 2. Due to an unfixed Net-SNMP issue related to byte order (see https://sourceforge.net/p/net-snmp/bugs/2136/), we have to pass IpAddress values in host byte order when used as table indices and in network byte order otherwise. 3. Improve string representation of IpAddress in Table.value(). Note: In IpAddress.cref() we don't store a reference to _cidx in "self". This is only safe if _cidx is consumed by a Net-SNMP function that copies the value (eg. snmp_varlist_add_variable). If it were used by Net-SNMP as raw pointer, Pythons reference counting could delete the object while Net-SNMP still wants to access it. These changes have been tested on x86_64 (little endian) and mips64 (big endian) architecture. (Git commit 519b0e8f3b500c10e5ec57dd3eadaebf09329146) * Sat May 28 11:45:51 2016 +0200 - Pieter Hollants Bump copyright year (Git commit 9a5b4e6f94874d3797fba5c21a1401684e0bc1fa) * Sat May 28 11:35:53 2016 +0200 - Pieter Hollants Relicense under the Lesser GNU Public License (LGPL), version 3. I've decided to relicense python-netsnmpagent under the LGPL instead of the GPL to facilitate integration with LGPL-licensed as well as proprietary software. Ie. using python-netsnmpagent in your proprietary subagent does not mean that you have to license your subagent under python-netsnmpagent's license as well. (Git commit d19b91131353c19cf408f2208bdf5384941a47d6) * Wed May 27 14:00:30 2015 +0300 - Anton Todorov netsnmpagent: Fix Table's value() cutting off ASN_COUNTER64 table values When returning a table row's data, we accessed ASN_COUNTER64 values via a 32-bit pointer, effectively cutting off the other 32 bits. Using the "data" union's "counter64" pointer fixes this. (Git commit 1fb1abb33a5eccc172b0279b099172a5230e4118) * Fri Apr 17 10:47:54 2015 +0200 - Pieter Hollants Update README with extended credits (Git commit 9f7ad1cb2563c4d91948265cf85015bd67c1dea3) * Wed Apr 15 15:19:54 2015 +0300 - Anton Todorov Fix format string in threading_agent example's logging Python 2.6 requires positional argument specifiers, omitting them only works on Python 2.7 and later. Cf. https://docs.python.org/2/library/string.html#formatstrings (Git commit d547d9fc162c64db35b46ef75593534002cf5c6a) * Wed Apr 15 15:25:55 2015 +0300 - Anton Todorov examples/run_* scripts: Trap additional signals for cleanup In some cases the tempdirs were left because we seem to have missed some signals. (Git commit 6bc2e8753f38e872ab8815bc4186b314869993eb) * Sun Mar 22 13:49:46 2015 +0100 - Pieter Hollants netsnmpagent: Drop special string handling in Table's init()/setRowCell() Because of issues with strings inside tables, we used to implement some special handling for (what we thought were) the trailing zero byte in C strings in Table's init() and setRowCell() methods, passing size+1 to netsnmp_set_row_column(). However, while this seemed to work on older net-snmp versions, with newer net-snmp versions we suddently had different issues, with irregular dot chars appearing at the end of strings. Turns out, we had a bug, but fixed it at the wrong place: right here, passing size (not size+1) appears to be correct. a8181ddb fixes the real culprit in Table's value() method. (Git commit 74fac74547eb9a344852c06fc7ca6c78476a4cbd) * Sun Mar 22 13:35:58 2015 +0100 - Pieter Hollants netsnmpagent: Make Table's value() method regard string lengths When creating the dictionary returned by the Table class's value() method, we so far assumed that the strings referenced through the "data" field in netsnmp_table_data_set_storage structures would be zero byte-terminated, as all C strings. However, this does not seem to be the case, as eg. netsnmp_set_row_column() uses memcpy(), not strcpy(), ie. when setting a string it is NOT zero byte-terminated. We thus need to regard "data_len" when returning data to the Python world. Strangely, this does not seem to be necessary with the simple scalar types such as OctetString and DisplayString which are basically ctypes.c_char_p objects as well... (Git commit a8181ddbd1c2ba633b0e6bd651b32612ae42b117) * Wed Nov 12 18:05:00 2014 +0100 - Pieter Hollants Add test cases for OctetString scalar type (Git commit e01291200f16460f08af994866113e548db234cf) * Wed Nov 12 18:00:59 2014 +0100 - Pieter Hollants netsnmptestenv: Handle strings without datatype and remove double quotes Sometimes, snmpget might return data without an explicit datatype. We'll handle it as strings, then, and remove any heading and trailing double quotes. (Git commit 8b8681e7f3c2c24bd3233b360c2454dc458a493c) * Wed Nov 12 13:38:22 2014 +0100 - Pieter Hollants Add test cases for TimeTicks scalar type (Git commit dd1be2cb264957cc45bceb568bd820f5244be38d) * Wed Nov 12 13:36:12 2014 +0100 - Pieter Hollants netsnmptestenv: Fix splitting in snmpget() for output with multiple ":" chars (Git commit ceb3c12327eedccfe2896182def2725dd5e4dc95) * Wed Nov 12 13:28:37 2014 +0100 - Pieter Hollants Smaller fixes for Counter32/Counter64 integration tests "Oops". (Git commit eb48301e194dd3f03000cd8e5e9a75c457275d6b) * Wed Nov 12 11:20:53 2014 +0100 - Pieter Hollants Add test cases for Counter32 and Counter64 scalar types (Git commit 68a7a7263f565e533d9caa6a2bc0bd539b78ce55) * Wed Nov 12 11:09:09 2014 +0100 - Pieter Hollants Smaller fixes for Unsigned32 integration tests (Git commit 13924941b9932401be2434a3937a6d174292a44a) * Tue Nov 11 20:47:38 2014 +0100 - Pieter Hollants Add test cases for Integer32 variable type and rework those for Unsigned32 (Git commit 52c9e82f493da197e1d31e343eb419347595b480) * Tue Nov 11 15:51:53 2014 +0100 - Pieter Hollants Just some small fixes to unify source code style (Git commit fb5265611ef6ce94890678a9c432d719aa1fafb2) * Tue Nov 11 15:05:47 2014 +0100 - Pieter Hollants Construct packager email from user- and hostname if not set through git config The included RPM .spec file always needs a packager name or email address, otherwise rpmbuild will complain. In properly configured git repos, the email address configured with "git config user.email" would have been used so far, but it should be possible to issue a "make rpms" without this requirement. Thus we now fall back to constructing a most probably bogus email address by combining the username and the hostname. This will allow to build RPMs for personal purposes, however, for redistribution purposes one should always explicitly configure a proper email address through "git config" beforehand. (Git commit 2bef55cd908e8cf05bac02e65cb705294607f39d) * Thu Jul 31 16:10:34 2014 +0200 - Jacobo de Vera Rename as suggested in review (Git commit bdbb299c428d1f936433cbff9086490dc37f39b6) * Mon Jul 21 14:25:40 2014 +0200 - Jacobo de Vera Make the use of MIB files completely optional (Git commit b2baaa3b3097546603c8b082395d3c27cceb4467) * Sat May 17 00:28:57 2014 +0200 - Pieter Hollants Update README (Git commit 9f9de13efffe9be0b1b9ea24a91046a96e383b40) * Sat May 17 00:27:48 2014 +0200 - Pieter Hollants Add "tests" target to Makefile and README explaining why to use it net-snmp's broken shutdown semantics force use to wrap nosetests so that it spawns a new Python process for each test case file. (Git commit 5dd4001379e676d0772ca6f7bfe37458b1fadc15) * Sat May 17 00:27:06 2014 +0200 - Pieter Hollants Rename test case files to guarantee a meaningful execution order (Git commit 4126fbba217b76babc9b41dba3038dcda5600ac7) * Fri May 16 23:41:43 2014 +0200 - Pieter Hollants Adapt test_netsnmptestenv.py to the snmpget() changes from 63414520 (Git commit bc4fff3aa72d19ef097c5d6ae740eb5f6fa92faa) * Fri May 16 23:28:16 2014 +0200 - Pieter Hollants Do not call net-snmp's shutdown_agent() anymore Unfortunately, the situation is even worse than described in 9c6c5560 so that we'll have to revert that change. Calling shutdown_agent() will cause trouble if SNMP objects have been registered (double free()s). (Git commit ea4796fadff7f7d3c2f3481e112423bdb0f0681e) * Fri May 16 23:07:07 2014 +0200 - Pieter Hollants Add first test cases for netsnmpagent SNMP objects (Unsigned32) This first set of test cases tests - SNMPGET on a read/write Unsigned32 object without initval returns 0 - SNMPSET of that object to 42 raises no exception - SNMPGET on that object then returns 42 - SNMPGET on a Unsigned32 object with initval 0 returns 0 - SNMPGET on a Unsigned32 object with initval -1 returns 4294967295 - SNMPGET on a Unsigned32 object with initval 4294967295 returns 4294967295 - SNMPGET on a read-only Unsigned32 object without initval returns 0 - SNMPSET on that object raises a NotWritableError exception (Git commit d8de7ee1f5ee38c0b8c602efa8bd0ae5d890e83b) * Fri May 16 22:24:59 2014 +0200 - Pieter Hollants netsnmptestenv: Add snmpset() method to set values (Git commit f5323fe486cf907281084f5adacf9f205595a5f6) * Fri May 16 21:36:21 2014 +0200 - Pieter Hollants netsnmptestenv: Make snmpget() extract and return data and datatype Calling functions will have no interest in extracting the data themselves. (Git commit 63414520f6278ff59d477cd35e312173828d6769) * Fri May 16 15:54:52 2014 +0200 - Pieter Hollants Update TEST-MIB.txt to offer OIDs for more diverse testing (Git commit b3b2572ed91f7369cfa67f63fa34526a8f2af7e4) * Fri May 16 15:50:36 2014 +0200 - Pieter Hollants Re-import os and time once more in netsnmptestenv's shutdown() method Especially "os" may have been __del__'d when shutdown() gets called a second time by netsnmptestenv's own __del__() method. (Git commit 10f1c011f13aedca2db48ba1f2807fe56fa66092) * Fri May 16 15:19:40 2014 +0200 - Pieter Hollants Use __file__ instead of sys.argv[0] to find path to TEST-MIB If run through the "nosetests" script, sys.argv will be /usr/bin/nosetests, not the path of test_netsnmpagent_init.py. We have to use __file__ instead. (Git commit 377dc15e5d2efbc7103e1dc0cb5a0ff7bc4a6dc2) * Fri May 16 15:10:53 2014 +0200 - Pieter Hollants Rename test_netsnmpagent.py to test_netsnmpagent_init.py We can only test the init behavior of the netsnmpagent module in this file as, among other things, we explicitly test that calling agent.start() without registering any SNMP objects does not make them available. So we can't at the same time register and test SNMP objects -- calling shutdown() and instantiating a new netsnmpAgent instance wouldn't work for the reasons laid down in 9c6c5560. (Git commit f352558d5f57bc34abef297ec3f359fdac528c52) * Fri May 16 00:45:27 2014 +0200 - Pieter Hollants Add first test cases for netsnmpagent module itself These test cases so far yet deal with rather low level issues of the module only, such as the expected behavior after instance creation, whether a connection to a master snmpd agent inside a net-snmp test environment set up with the netsnmptestenv module could be successfully established etc. More important tests such as the behavior of the different types of SNMP objects are yet to follow. For this reason, the included new TEST-MIB.txt so far gets used only indirectly (to test when it becomes available to the test environment's snmpd). (Git commit 12a7097def7ddc3963a683b0dc0b873c19265ca3) * Thu May 15 20:47:30 2014 +0200 - Pieter Hollants Add test cases for snmpcmd() exceptions to test_netsnmptestenv.py These test cases cover the changes done in 706ecb06. (Git commit 931b27f2cdf3489ba94dd3f59e94b8c1d364e632) * Thu May 15 20:42:51 2014 +0200 - Pieter Hollants netsnmptestenv: Fix snmpcmd() error detection/exceptions For one thing, we used re.match() to search for matches at the end of the output, thus exceptions were never raid. Second, we only caught the case where a MIB (and thus the OID) was basically known, but not available (ie. because there was no subagent connected serving it) and reported it with the wrong exception name. Third, we didn't catch the case when an unknown OID was specified (eg. an unknown MIB or a known MIB but an invalid OID within that MIB). (Git commit 73f63d0b6d1168853525059b1ff650601f110734) * Thu May 15 19:29:38 2014 +0200 - Pieter Hollants Reorder imports in test_netsnmptestenv to be more logical (Git commit 706ecb06e56b39758696bb11c1f338c32732b511) * Thu May 15 16:36:12 2014 +0200 - Pieter Hollants Use slightly more intelligent process killing in netsnmptestenv module Instead of hammering the process to be killed with signals continously, sleep small amounts of time and check procfs. This is obviously Linux-specific, but so far I have not heard of anyone trying to use python-netsnmpagent with net-snmp on different platforms. (Git commit d638fe963a2372cfc4b9abcf2b72619a91079248) * Thu May 15 15:32:35 2014 +0200 - Pieter Hollants Add more tests for complete and clean shutdown() in test_netsnmptestenv.py Tests that - check that the snmpd has really exited by killing it's remembered PID - check that the tmpdir used by the testenv has been removed (Git commit a97ef42a87633ad65c509b6006ef2e20176b74ca) * Thu May 15 15:28:38 2014 +0200 - Pieter Hollants Test that snmpget really fails again after netsnmptestenv shutdown (Git commit 194d87f50a733e871ce9fd17a91ff141f3cd4558) * Thu May 15 15:26:06 2014 +0200 - Pieter Hollants Make use of nicer/more effective nose tools in test_netsnmptestenv.py (Git commit a5aff2547ed35f4f062f685c208cbb8585b40526) * Thu May 15 15:15:05 2014 +0200 - Pieter Hollants Use @timed decorator to test the time test_netsnmptestenv functions use (Git commit eecf982a941339416b6481829a40a16712342c90) * Thu May 15 15:12:37 2014 +0200 - Pieter Hollants Have test_netsnmptestenv.py also test shutdown(), not just call it (Git commit a4ccbb60f93604122131e1652cbef0af33e5398a) * Thu May 15 15:09:49 2014 +0200 - Pieter Hollants Add docstrings to test cases in test_netsnmptestenv.py (Git commit 8e73e9dc40f6306931d5cd7ffea3ed4bb7f53d5d) * Wed May 14 14:42:42 2014 +0200 - Pieter Hollants Use nose as testing framework The advantages over unittest are quite well-known (eg. not having to create test classes, test discovery etc.), so I won't elaborate in much detail here. (Git commit 66dad7b2493237e754547797619a801fe5a5ac1a) * Wed May 14 13:28:41 2014 +0200 - Pieter Hollants netsnmptestenv: Have snmpcmd() raise exceptions in timeout/"no such OID" cases The SNMP commands executed might hit a timeout (no running snmpd, wrong community string etc.) or a "no such OID" condition (no subagent running). These cases must be detectable by tests without having to check the output. (Git commit a0d17da836b6a5c84c187857fc5bd53bd81318e4) * Wed May 14 13:52:42 2014 +0200 - Pieter Hollants netsnmptestenv: Move shutdown()'s process killing into own function Sooner or later our net-snmp test environment will consist of more than snmpd (eg. snmptrapd), so it makes sense to generalize the process killing. (Git commit cb426775a957cfb1eaffb1fec684707909e82cf3) * Wed May 14 13:45:37 2014 +0200 - Pieter Hollants netsnmptestenv: Rename __del__ to shutdown As done for netsnmpagent in e7d9144c and for the same reasons. (Git commit 2b94e8c9359942eeff404f3b10a99ac92ca5290d) * Wed May 14 13:26:55 2014 +0200 - Pieter Hollants netsnmptestenv: Always strip() SNMP command's output immediately (Git commit 38c3945dfeea2fd4545f77277a8d63ad6149ac11) * Wed May 14 13:19:41 2014 +0200 - Pieter Hollants netsnmptestenv: Explicitly remember used TCP ports in instance variables This way test_netsnmptestenv.py can inspect in a future-safe manner whether the net-snmp daemons are still running and occupy the TCP ports or have exited properly. Currently the ports are still hard-coded but might become dynamic in a future commit. (Git commit 129778cc3b7fa165ce0f63f7487569822570b72e) * Fri May 2 01:22:20 2014 +0200 - Pieter Hollants netsnmpagent: Call net-snmp's shutdown_agent() in shutdown() method Properly written agents should call shutdown_agent() in addition to snmp_shutdown() as we did a init_agent() before the init_snmp() call. Note: with ALL net-snmp versions up to and including the current 5.7.3 beta this will still NOT allow for proper cleanup in such a way that you could "del" the netsnmpAgent instance, create a new one and have that one connect to the master agent successfully. Even with 5.7.x's code cleanups and the subsequent removal of "#ifdef SHUTDOWN_AGENT_CLEANLY" in snmpd.c, it still seems to be necessary to exit the process (or thread) and have the OS do additional cleanup work if a substantially different netsnmpAgent instance (eg. with a different MasterSocket) is to be created. Unfortunately this also affects test cases where one wants to run each test in a defined, clean and reproducible environment. (Git commit 9c6c556005838f67232a0f70a6a299968713e31a) * Fri May 2 01:19:11 2014 +0200 - Pieter Hollants netsnmpagent: Rename __del__ to shutdown() I ran into the common "In Python __del__ is no destructor" trap. The cleanup code called there was possibly never executed at all. Rename the method to "shutdown" to give agents a chance to explicitly call it at their own shutdown. (Git commit e7d9144c87199fb5f245020c8c99b3a886fb8901) * Thu May 1 13:07:51 2014 +0200 - Pieter Hollants netsnmpapi: define netsnmp_init_mib() instead of unused init_mib() In netsnmpapi we defined init_mib() but in netsnmpagent we called netsnmp_init_mib() which was undefined in netsnmpapi. That didn't cause trouble because a.) ctypes implicitly treated it as being "void netsnmp_init_mib(void)" and b.) by coincidence that matched the function signature. The confusion stemmed from a function rename in the net-snmp project, see https://www.mail-archive.com/net-snmp-coders%40lists.sourceforge.net/msg08417.html (Git commit fa383b70495dac6977c83cc9857f5aa41c8ecc59) * Wed Apr 30 22:56:19 2014 +0200 - Pieter Hollants Add netsnmptestenv Python module python-netsnmpagent needs test cases, integration tests with net-snmp to be precise. These tests will need the same temporary net-snmp test environments as the example agents, so it makes sense to unify such code into a new Python module "netsnmptestenv". Initially this module will be used by netsnmpagent tests, later on we'll modify the example agents so that they can use it, too, obsoleting the need for shell script wrappers. For this reason and because the example agents end up in /usr/share/doc/packages/python-netsnmpagent/examples/ in binary distributions, netsnmptestenv will be installed into the system along with the two existing Python modules netsnmpapi and netsnmpagent. For now, only one test environment can be created at the same time because we hardcode the TCP ports used by net-snmp. We'll replace this with random port assignment/port in-use detection code later. And of course the new module gets test cases itself, too :-) (Git commit 3a6a90f99aa3cca2a912a6fab6dfde59c8fc3acd) Changes from v0.5.2 to v0.5.3 ============================= * Mon Aug 29 23:06:24 2016 +0200 - Pieter Hollants Update README to give credit where credit is due (Git commit de4d76adbc5e54f0ae4536e0a661eb466251484d) * Mon Aug 29 23:01:07 2016 +0200 - Pieter Hollants Trivial description fixes in SIMPLE-MIB.txt (Git commit d2797ef699590b4601f584db3168c23e046b2b4e) * Fri Aug 19 08:53:19 2016 +0200 - Tobias Deiminger Fixes for using IpAddress objects as table indices This incorporates a number of fixes for using IpAddress objects as table indices: 1. Add missing attributes to IpAddress class' __init__() method 2. Due to an unfixed Net-SNMP issue related to byte order (see https://sourceforge.net/p/net-snmp/bugs/2136/), we have to pass IpAddress values in host byte order when used as table indices and in network byte order otherwise. 3. Improve string representation of IpAddress in Table.value(). Note: In IpAddress.cref() we don't store a reference to _cidx in "self". This is only safe if _cidx is consumed by a Net-SNMP function that copies the value (eg. snmp_varlist_add_variable). If it were used by Net-SNMP as raw pointer, Pythons reference counting could delete the object while Net-SNMP still wants to access it. These changes have been tested on x86_64 (little endian) and mips64 (big endian) architecture. (Git commit d0941efe26cccd3adf48c337ada9b7eb1a78802b) Changes from v0.5.1 to v0.5.2 ============================= * Sat May 28 11:45:51 2016 +0200 - Pieter Hollants Bump copyright year (Git commit d6edbe4db74c886bc6e69af7452982d143c9f186) * Sat May 28 11:35:53 2016 +0200 - Pieter Hollants Relicense under the Lesser GNU Public License (LGPL), version 3. I've decided to relicense python-netsnmpagent under the LGPL instead of the GPL to facilitate integration with LGPL-licensed as well as proprietary software. Ie. using python-netsnmpagent in your proprietary subagent does not mean that you have to license your subagent under python-netsnmpagent's license as well. (Git commit f815239c4d76d82d7c620ace1398240f49ad6f83) Changes from v0.5.0 to v0.5.1 ============================= * Wed May 27 14:00:30 2015 +0300 - Anton Todorov netsnmpagent: Fix Table's value() cutting off ASN_COUNTER64 table values When returning a table row's data, we accessed ASN_COUNTER64 values via a 32-bit pointer, effectively cutting off the other 32 bits. Using the "data" union's "counter64" pointer fixes this. (Git commit 3863552a9b9b4d6ef3b1fe79d3659a42a4311b5f) * Fri Apr 17 10:47:54 2015 +0200 - Pieter Hollants Update README with extended credits (Git commit 21fd9866f6b4ffedda5234e3d3fedfe46694a907) * Wed Apr 15 15:19:54 2015 +0300 - Anton Todorov Fix format string in threading_agent example's logging Python 2.6 requires positional argument specifiers, omitting them only works on Python 2.7 and later. Cf. https://docs.python.org/2/library/string.html#formatstrings (Git commit 5e3b6a01b149af160025c932ab91d39f6bb664e6) * Wed Apr 15 15:25:55 2015 +0300 - Anton Todorov examples/run_* scripts: Trap additional signals for cleanup In some cases the tempdirs were left because we seem to have missed some signals. (Git commit 41efac1224b073e3dfbe892da943febbce8cd0ca) * Sun Mar 22 13:49:46 2015 +0100 - Pieter Hollants netsnmpagent: Drop special string handling in Table's init()/setRowCell() Because of issues with strings inside tables, we used to implement some special handling for (what we thought were) the trailing zero byte in C strings in Table's init() and setRowCell() methods, passing size+1 to netsnmp_set_row_column(). However, while this seemed to work on older net-snmp versions, with newer net-snmp versions we suddently had different issues, with irregular dot chars appearing at the end of strings. Turns out, we had a bug, but fixed it at the wrong place: right here, passing size (not size+1) appears to be correct. a8181ddb fixes the real culprit in Table's value() method. (Git commit 84de336dbd3f27814c698659d8bc4c0106d2ce97) * Sun Mar 22 13:35:58 2015 +0100 - Pieter Hollants netsnmpagent: Make Table's value() method regard string lengths When creating the dictionary returned by the Table class's value() method, we so far assumed that the strings referenced through the "data" field in netsnmp_table_data_set_storage structures would be zero byte-terminated, as all C strings. However, this does not seem to be the case, as eg. netsnmp_set_row_column() uses memcpy(), not strcpy(), ie. when setting a string it is NOT zero byte-terminated. We thus need to regard "data_len" when returning data to the Python world. Strangely, this does not seem to be necessary with the simple scalar types such as OctetString and DisplayString which are basically ctypes.c_char_p objects as well... (Git commit b70d6cd3cc9ecee55cdb4bed0a246d6782fa7c42) * Tue Nov 11 15:51:53 2014 +0100 - Pieter Hollants Just some small fixes to unify source code style (Git commit 24e9b89cb5148f52ba032c5b5b13e1de05aecb15) * Thu Jul 31 16:10:34 2014 +0200 - Jacobo de Vera Rename as suggested in review (Git commit ccc443fb68fd927ca235c5021716ec4aed158fc3) * Mon Jul 21 14:25:40 2014 +0200 - Jacobo de Vera Make the use of MIB files completely optional (Git commit a760a4325512771da65f1c6030e9a17ee9ac1d84) * Mon Jun 1 22:31:45 2015 +0200 - Pieter Hollants Update README (Git commit d3dfa854e79cbe7ae2c7c05a21c2aa9eed5e3877) * Fri May 16 23:28:16 2014 +0200 - Pieter Hollants Do not call net-snmp's shutdown_agent() anymore Unfortunately, the situation is even worse than described in 9c6c5560 so that we'll have to revert that change. Calling shutdown_agent() will cause trouble if SNMP objects have been registered (double free()s). (Git commit 9618ab334f56dbc1fd5d523862bb8355a2a72d8a) * Fri May 2 01:22:20 2014 +0200 - Pieter Hollants netsnmpagent: Call net-snmp's shutdown_agent() in shutdown() method Properly written agents should call shutdown_agent() in addition to snmp_shutdown() as we did a init_agent() before the init_snmp() call. Note: with ALL net-snmp versions up to and including the current 5.7.3 beta this will still NOT allow for proper cleanup in such a way that you could "del" the netsnmpAgent instance, create a new one and have that one connect to the master agent successfully. Even with 5.7.x's code cleanups and the subsequent removal of "#ifdef SHUTDOWN_AGENT_CLEANLY" in snmpd.c, it still seems to be necessary to exit the process (or thread) and have the OS do additional cleanup work if a substantially different netsnmpAgent instance (eg. with a different MasterSocket) is to be created. Unfortunately this also affects test cases where one wants to run each test in a defined, clean and reproducible environment. (Git commit b579a0212f619d76b2e6d2ddbfa06db58f6db27a) * Fri May 2 01:19:11 2014 +0200 - Pieter Hollants netsnmpagent: Rename __del__ to shutdown() I ran into the common "In Python __del__ is no destructor" trap. The cleanup code called there was possibly never executed at all. Rename the method to "shutdown" to give agents a chance to explicitly call it at their own shutdown. (Git commit 7bfac319adc259394c117974a8b539fcc2c80170) * Thu May 1 13:07:51 2014 +0200 - Pieter Hollants netsnmpapi: define netsnmp_init_mib() instead of unused init_mib() In netsnmpapi we defined init_mib() but in netsnmpagent we called netsnmp_init_mib() which was undefined in netsnmpapi. That didn't cause trouble because a.) ctypes implicitly treated it as being "void netsnmp_init_mib(void)" and b.) by coincidence that matched the function signature. The confusion stemmed from a function rename in the net-snmp project, see https://www.mail-archive.com/net-snmp-coders%40lists.sourceforge.net/msg08417.html (Git commit 301a0e8a4b9f319dcf4ed63c9c90ee6064db64ba) * Sat Apr 19 11:24:05 2014 +0200 - Pieter Hollants Cosmetics (Git commit 5845f8adc9b21d3497156b71e26ae120364c8a9f) * Fri Apr 18 14:20:24 2014 +0200 - Pieter Hollants Revert c77f52aaa and c20a48f89 (do not define __version__ ourselves) c77f52aaa aimed at making a __version__ attribute available inside the netsnmpagent module by using pkg_resources to retrieve our version number. c20a48f89 consequently added a RPM spec file dependency on python-setuptools. This was clearly wrong, not only because it made running the examples from within a git clone or a source distribution impossible until "make install" got executed. It is just not the netsnmpagent module's job to have any knowledge about its (externally assigned) version number. Instead of using pkg_resources ourselves, production quality agents that use our module can (and should) use pkg_resources if they require a specific python-netsnmpagent version. After all, they need a installed copy of python-netsnmpagent anyway, so pkg_resources DOES know its version. This could look like this: [...] Finally, the example agents in the source distribution are always designed for the corrosponding version of the module, so no need to express explicit version requirements here that are implicit. (Git commit 5715e77f2f8e792a0732b1934e9eebefc2e8ee6a) * Fri Apr 18 00:10:36 2014 +0200 - Pieter Hollants Fix indenting style in setup.py.in Yes, one could discuss Tabs and Space usage and Tab size and the meaning of life and all, but for the time being the code style should at least be consistent. (Git commit fd42a511844afb0b7c1cb47276b4be547c824508) * Sat Feb 22 14:32:22 2014 +0100 - Pieter Hollants Add slides from the FOSDEM 2014 lightning talk on python-netsnmpagent (Git commit 7574563d5d86361032522299fef492277a6023d4) * Sun Feb 2 10:24:30 2014 +0100 - Daniele Sluijters setup: Add classifiers, link to Github repo. (Git commit 0d6452e4fd57f5ad0e7caa3760117f9da121c1c5) Changes from v0.4.6 to v0.5.0 ============================= * Mon Oct 28 15:14:22 2013 +0100 - Pieter Hollants Add RPM spec file dependency on python-setuptools At least in SLES 11 SP2, the pkg_resources module is only available if the python-setuptools RPM has been installed. In newer openSUSE versions, installation python-distribute will satisfy this dependency as well. (Git commit c20a48f8978fa4cfee9b0a3e0acc9e9ce8a4c705) * Mon Oct 28 14:33:01 2013 +0100 - Pieter Hollants Fix Makefile's "install" target (Git commit a8dad4a36c8358ccff92a38641bc5a5f8bd6db17) * Sun Oct 27 18:01:43 2013 +0100 - Pieter Hollants Export module's version as obtained from setuptools to enable version checks Seeing that python-netsnmpagent keeps gaining features in newer versions, I thought that it makes sense to make its version number available for version checks in agent code. Especially since some changes such as those to logging behavior in 61e9c4aa can not really be tested for. (Git commit c77f52aaa00fa41352406f9410e61813eefc0183) * Sun Oct 27 17:44:06 2013 +0100 - Pieter Hollants Distinguish connect failure between first attempt and reconnecting On the one hand, it does not make sense to BOTH raise a netsnmpAgentException for connection failure that will undoubtedly be printed to stderr or logged in some kind of way by agents AND pass on net-snmp's "Warning" log message to be printed/logged as well. On the other hand, we can not unconditionally intercept and surpress the "Warning" log message as there is not only the scenario of connection failure at startup but also when we got disconnected by the AgentX master. In the latter case we would get and print/log an "Info" log message that we were disconnected and net-snmp will retry in, say, 15 seconds and another "Info" when we'd be connected again, but see no log message for ongoing connection failure. Thus, this change reworks the netsnmpAgentStatus cases to move away from a generic DISCONNECTED status to two distinguished FIRSTCONNECT and RECONNECTING cases: the connection failure log message will be surpressed and an exception be raised in the FIRSTCONNECT state only. And while we're at it, ECONNECT was inspired by , of course, but as I can't imagine other EWHATEVER conditions, I felt CONNECTFAILED to be a more self-explainatory name. (Git commit 61e9c4aa81e583d8d21a1d6dbb71c70ac8da0f46) * Sun Oct 27 13:59:18 2013 +0100 - Pieter Hollants Explicitly document the possible "msgprio" strings As client code might opt to not just print/log the priority but apply filtering to it. (Git commit b9ec11a51a7f89e5cde301a11514f55c2ed894d5) * Sun Oct 27 13:56:59 2013 +0100 - Pieter Hollants Write out priority levels LOG_EMERG as "Emergency" and LOG_CRIT as "Critical" Since we most probably write these to stderr or a logfile, they should be verbose and unambiguous enough for the user. (Git commit 1fdcf5637cbab1c2ea548b9377edc7c548cdc24d) * Sun Oct 27 12:48:58 2013 +0100 - Pieter Hollants Add support for custom log handlers Change b68a9775 implemented detection of connection establishment and failure by accessing the log messages net-snmp generates. Log messages were still printed to stderr. This change allows agents using python-netsnmpagent to define their own custom log handler function by setting the new "LogHandler" property when creating the "netsnmpAgent" object. The function will then be called whenever the net-snmp code generates a log message with two parameters: 1. a string describing the message's priority 2. the text of the message itself If "LogHandler" is None, error message will continue to be printed to stderr, as can be seen in simple_agent.py. Whereas threading_agent.py, being a step towards a more real-life agent, has been updated to use a custom "LogHandler" to integrate net-snmp messages in its output more nicely. In your own agents, you will instead most likely define a custom log handler to write the messages to your agent's logfile. (Git commit f14b919664b7418ed56e0cd3fdf86531501c7c64) * Fri Oct 25 18:31:58 2013 +0200 - Pieter Hollants Support detecting connection establishment/failure So far we just relied on net-snmp resp. its init_snmp() function to establish the AgentX connection to the master snmpd instance and neither knew nor cared about whether a connection could be established at all. A mistake in the "MasterSocket" option would only cause a warning on stderr, emitted by net-snmp itself, only. Likewise, indication of a successful connection was through log messages on stderr only, too. This change introduces support for explicit detection of connection establishment and failure. To this end, the netsnmpAgent class' internal "_started" flag was replaced with a "_status" property that can take on the values defined in the new "netsnmpAgentStatus" enumeration. Your code should however NOT access this property -- just make sure to catch the new netsnmpAgentExceptions (implemented in the example agents in cd4ee23b). The change is quite large due to the fact that net-snmp itself in neither the 5.4.x nor 5.7.x versions actually returns status information: init_snmp() is defined as "void", lending due to the fact that it will actually trigger a sequence of function calls including callback functions that return "void" themselves. Unfortunately there is also no dedicated callback mechanism for client code such as python-netsnmpagent that gets called on connection status changes. So we had to invent it on our own, using two callback functions as more or less ugly workarounds: a custom log handler and, for net-snmp 5.4.x support, another callback function that abuses a callback hook originally intended for different purposes. See the sources for details, they are well commented. In this version, our custom log handler writes all log messages it receives to stderr, resembling previous behavior with the difference that we also log a message's priority level as defined by net-snmp, eg. "[Info] Foo.". (Git commit b68a977562d3afa05d38044fd12e5e12390c8c64) * Thu Oct 24 18:05:54 2013 +0200 - Pieter Hollants Add support for catching netsnmpAgentException in example agents Up to now netsnmpAgent objects were created and their start() methods called without any error handling. But an upcoming change will introduce a new netsnmpAgentException that will be raised for error conditions, so the example agents need to know about them. (Git commit 436d754db8afbc4e728e5903b155cdefa38ddb6e) * Thu Oct 24 15:17:01 2013 +0200 - Pieter Hollants Explicitly advertise simple_agent.py's support for state dumping with SIGHUP Sending simple_agent.py a SIGHUP signal causes its current variable state to be dumped again. This is worth a more explicit advertising at agent startup. (Git commit 26f971fe48a50b629a5cad57a6f0a8f47811e20d) * Fri Oct 25 19:23:05 2013 +0200 - Pieter Hollants Fix netsnmpagent.py's docstring (forgot a word) (Git commit 5cdd84c153449781d17d92e6c38d41cec2a8550f) Changes from v0.4.5 to v0.4.6 ============================= * Thu Oct 17 22:02:12 2013 +0200 - Pieter Hollants Advertise support for alternative AgentX transports more explicitly We did already support alternative transports for the AgentX communication between subagents and master snmpd instances, that is, not just UNIX domain sockets but eg. also TCP ports. These are specified through transport specifications that follow the format described in the "LISTENING ADDRESSES" section in the snmpd(8) manpage. This change modifies wording that would suggest we supported alternative UNIX domain socket paths only. It also adds a new script to run simple_agent.py against a snmpd instance over a TCP port, surprisingly named "run_simple_agent_over_tcpsocket.sh". (Git commit 1cc011ee12ba78dab6eeec25890e15767c55ea07) * Thu Oct 17 21:43:50 2013 +0200 - Pieter Hollants Move documentation on examples into own README in the examples directory To put the information closer to where you would be looking for it. (Git commit 3da5411e9cf7e9233d4ca0443dc1c529c94c0d7e) * Tue Oct 8 17:55:31 2013 +0200 - Pieter Hollants Update README with new credits (Git commit 3a0bd17d0934dd6620f30f177a286d821bfd51fc) * Tue Oct 8 17:53:52 2013 +0200 - Pieter Hollants Update README with net-snmp compatibility remarks (Git commit 9b4f81efc05c23f11f7689fcb7e71c39a9fcb4a9) * Tue Oct 8 17:42:05 2013 +0200 - Pieter Hollants Use WATCHER_MAX_SIZE instead of WATCHER_SIZE_STRLEN for 5.4.x compatibility We used to rely on net-snmp >= 5.5 behavior by setting a WATCHER_SIZE_STRLEN flag that didn't exist in net-snmp 5.4.x yet. For backwards compatibility we now use WATCHER_MAX_SIZE and update the used netsnmp_watcher_info structure's "data_size" field ourselves. Reported by Max "mk23" Kalika . (Git commit b15066fb9cd481c24ece736a68b2e22c2e483a67) * Tue Oct 8 17:27:35 2013 +0200 - Pieter Hollants Fix comment added in 60d51c93 (Git commit 06fa1ded69269d5cc5798ec7f4057aacfa6c58b4) * Tue Oct 8 17:24:22 2013 +0200 - Pieter Hollants Deref watcher before trying to its max_size property The netsnmp_watcher_info structure returned by netsnmp_create_watcher_info is a pointer, so it must be de-referenced with ctypes' "contents" attribute before any of its properties can be set. Reported by Max "mk23" Kalika . (Git commit f2078fdabb022aeac48fd676433a0e381047c004) * Tue Oct 8 16:30:17 2013 +0200 - Pieter Hollants Add missing netsnmp_watcher_info fields definition to netsnmpapi Basically we do not need fields definitions for net-snmp structures that we never modify ourselves. However in this case we actually tried setting netsnmp_watcher_info's "max_size" field which resulted in a no-op. Reported by Max "mk23" Kalika . (Git commit 464d058c24008623fe7e6b4c0d9043fe19ac8151) * Tue Oct 8 16:21:52 2013 +0200 - Pieter Hollants Explicitly comment why we set watcher_info struct's max_size manually (Git commit 60d51c9324e1aae8964cb7d46cb1136e0e772d22) * Fri Jul 19 18:20:24 2013 +0200 - Pieter Hollants Adapt the Makefile's srcdist and rpms targets to the new examples dir (Git commit e96fd75fe905d2c922da4dacdf110082f50ba4bb) * Thu Jul 18 17:16:02 2013 +0200 - Pieter Hollants Undo part of fa6e2d9723 (much longer string in simple_agent.py) That line was intended for debugging only. (Git commit e194a756b6ca368d0984b2bbea8e420dccb91f14) * Thu Jul 18 17:13:16 2013 +0200 - Pieter Hollants Use local copy of netsnmpagent module for example agents So you don't have to "make install" first. (Git commit fa6e2d9723de724bf619713ad41097b79616d41b) * Wed Jul 17 23:41:31 2013 +0200 - Pieter Hollants Fix more overseen references to "example" in run_simple_agent.sh (Git commit 29f77605c12e839a755c122445789bc1f0706823) * Fri Jul 12 00:05:19 2013 +0200 - Pieter Hollants simple_agent.py: Remove the caret that shouldn't be Narf... (Git commit 0e4aa6163a4555e846a0dc6b2a0221782a98d59f) * Fri Jul 12 00:01:55 2013 +0200 - Pieter Hollants Fix overseen references to "example" that should be "simple" Thanks to mjs7231 for reporting. (Git commit 3cb813f430a6d3ba4b3f3508f8f7801957b777de) * Sun Jul 7 22:38:48 2013 +0200 - Pieter Hollants Small update to the README - Phrase missing API stability less dramatically - Add TODO of SNMP notifications/traps (Git commit ca1f9ce7994a8db6a66efbd4729e8c814d6177f2) * Sun Jul 7 22:36:51 2013 +0200 - Pieter Hollants New example threading_agent.py demonstrating asynchronous data updates The default simple_agent.py always blocked until certain events occured, eg. SNMP requests had to be processed, and only updated its SNMP objects inbetween. Also SNMP requests couldn't be handled while data was in the process of being updated. Thus here's a more real life-usable example that outsources data updating into a separate thread. It does however not address possible locking issues. (Git commit c8d08523d707a62d85205b5a0f0260340d38d9ea) * Sun Jul 7 20:03:43 2013 +0200 - Pieter Hollants Non-functional improvements to the simple agent simple_agent.py's heading comment block: - Add note on running the run_simple_agent.sh script - Add note on using sudo when starting the agent directly - Encourage user to use own instead of "public" simple_agent.py's counters code: - Prettify sort order of code blocks - Explain initialization value - Document that increment() is a counter object's method more explicitly run_simple_agent.sh: - Mention snmpget as well (Git commit dff681469369bb61145d0a814e6921d507eec33c) * Sun Jul 7 14:59:13 2013 +0200 - Pieter Hollants Rename example agent to simple agent We're going to have other example agents in the examples/ subdirectory as well, therefore the name "example agent" would be ambiguous. The MIB and its contents have been renamed, too. (Git commit 4fadc259ded94736bfd8dd665577fa5877d00faf) * Sun Jul 7 14:49:05 2013 +0200 - Pieter Hollants Move example agent into new subdir examples/ (Git commit 53f47da9ae4fe1a7b462e57ddb7b6e36abd72931) Changes from v0.4.4 to v0.4.5 ============================= * Mon Apr 29 15:05:26 2013 +0200 - Pieter Hollants Makefile/RPM spec file: Cosmetics and smaller cleanups (Git commit b3e7d35531f2f5c983798e15f237a18b59a5233b) * Mon Apr 29 12:57:57 2013 +0200 - Pieter Hollants Use $@ instead of explicit filename references in ChangeLog Makefile target (Git commit 01abbd6e525a8f05fd7bdc2881be8be750c6ef42) Changes from v0.4.3 to v0.4.4 ============================= * Sun Apr 28 00:51:19 2013 +0200 - Pieter Hollants Use more accurate variable name $PKGREMAIL when building RPM spec file's %changelog section The one who uses the .spec file is not necessarily the $AUTHOR of python-netsnmpagent. (Git commit 1fc4d6c743dd5827592c594882d264a754753681) * Sun Apr 28 00:49:42 2013 +0200 - Pieter Hollants Use C locale to generate dates for RPM spec file's %changelog section RPM might otherwise complain about an "invalid date format" when building the package.. (Git commit 3e2623d78cd326b1ec87a3d768e871c245b9a8e7) * Sat Apr 27 19:16:50 2013 +0200 - Pieter Hollants Automatically generate %changelog section for RPM spec file The spec file uses a different changelog style than the ChangeLog file, which would be too verbose for this purpose. Usually package maintainers note changes to the spec file itself here as well and often give a more high-level summary of changes of the software being packaged. Our automatically generated changelog will not be of the same quality as we only indicate the timestamps a version was tagged and an "Update to version x.y.z"-style message. But until now we didn't use the ChangeLog section at all. And package maintainers can still replace it with a more detailed, manually maintained version. (Git commit e4a21d8fe50e1a86c558101a5f5e1e55015c58e8) * Sat Apr 27 16:37:25 2013 +0200 - Pieter Hollants Collect RPMs built in dist/ and clean up RPM build dirs (Git commit d184c128c339846fe2188f36fc94b338745b31d1) * Sat Apr 27 16:34:41 2013 +0200 - Pieter Hollants Generate RPM spec file along with source distribution archive Utility of the spec file is increased if it has python-netsnmpagent's version number already specified -- the old way of requiring a RPM "--define" only worked when the Git repo was available. Now one can take the generated dist/python-netsnmpagent.spec along with the source archive and simply copy it some build host and build. (Git commit 5a8f0e5afa12e1bb67df45531dd51b47ab33935c) Changes from v0.4.2 to v0.4.3 ============================= * Wed Apr 24 11:57:29 2013 +0200 - Pieter Hollants Clear counter variable also when a Table object is cleared (Git commit b4e19f42fa587e135c74785a39090df7a7c7bf83) Changes from v0.4.1 to v0.4.2 ============================= * Tue Mar 26 19:16:13 2013 +0100 - Pieter Hollants Fix specfile so it builds on SLES11 and SLES11SP1, too (Git commit 8e186e646b6fb8cfe53882b930f3a3a5b028f138) Changes from v0.4 to v0.4.1 =========================== * Tue Mar 26 15:55:24 2013 +0100 - Pieter Hollants Fixes to the spec file so it builds for openSUSE, SLES11 and RHEL6 (Git commit b1b1cfe4b746b32142c36fd159b2bbabe0454d18) Changes from v0.3 to v0.4 ========================= * Sat Mar 23 14:30:04 2013 +0100 - Pieter Hollants Add missing prototypes for init_mib() and netsnmp_table_dataset_remove_and_delete_row() (Git commit 5c4618526f4def25479503853a67c77d72dfc8ae) * Sat Mar 23 14:16:47 2013 +0100 - Pieter Hollants Add prototype for netsnmp_table_dataset_remove_and_delete_row (Git commit 8ec11c314dde071ad073dd8ae08c5b67883b2298) * Sat Mar 23 14:15:23 2013 +0100 - Pieter Hollants Abstract netsnmp_table_dataset_remove_and_delete_row from actual library as well Another function that used to be in libnetsnmphelpers in net-snmp <5.6 and now is part of libnetsnmpagent. (Git commit c068cace7f3aba096705cdc923ca99442bfb1e4e) * Tue Mar 5 23:30:31 2013 +0100 - Pieter Hollants Update README to reflect commit activity This is more and more getting fun :-) (Git commit a6777f189618f413c991d456fb27fe4ba8fbdbb1) * Tue Mar 5 23:29:38 2013 +0100 - Pieter Hollants Formatting for consistent code style (Git commit 92385a159bc7249c0c508fc068292b088c652541) * Tue Mar 5 22:03:37 2013 +0000 - Steven Hiscocks Add increment method to asntypes COUNTER and COUNTER64 (Git commit 7d3d7058c01d09086b8ecc85f443cd709f1b1ec3) * Wed Feb 27 00:50:56 2013 +0100 - Pieter Hollants Fix ChangeLog generation for the case when we're on a tag The mechanism erroneously tried to generate a changelog "between vX.Y and vX.Y" when we're on that very tag. (Git commit da08641c0ccfe1b43038f3bd840bbc3b2d5253f5) * Wed Feb 27 00:46:13 2013 +0100 - Pieter Hollants Include documentation and example files in built RPM Seeing that the module will be installed on many plattforms in form of an RPM, it is wise to have the documentation files and also the example agent and its files included. (Git commit ccf0a05b60ae7a4efa7468e1a3ae7fd2b7cb00ed) * Tue Mar 5 18:21:15 2013 +0000 - Steven Hiscocks Add optional argument to check_and_process as to whether to block (Git commit eb2d9b8822a4aed6bf10be55b510429049c94122) Changes from v0.2 to v0.3 ========================= * Wed Feb 27 00:50:56 2013 +0100 - Pieter Hollants Fix ChangeLog generation for the case when we're on a tag The mechanism erroneously tried to generate a changelog "between vX.Y and vX.Y" when we're on that very tag. (Git commit 78ebb0940c6de00a008e2e2321e9bcf362a90514) * Wed Feb 27 00:46:13 2013 +0100 - Pieter Hollants Include documentation and example files in built RPM Seeing that the module will be installed on many plattforms in form of an RPM, it is wise to have the documentation files and also the example agent and its files included. (Git commit c3c9456cd330c99f6bcc6d69c7e1ad0c433fd4a1) * Tue Feb 26 11:51:08 2013 +0100 - Pieter Hollants Automatically generate ChangeLog from git commits We now have a ChangeLog automatically generated from the Git history, so users who do not follow the proceedings in the Git repository itself can see what has changed between each version. The ChangeLog is built whenever a source distribution archive is generated. It can also be explicitly built by using "make ChangeLog". (Git commit c54fc95c4f7ba3f26c776871d418df54abe704d7) * Tue Feb 26 11:33:39 2013 +0100 - Pieter Hollants Add MANIFEST.in file so source distributions include necessary files distutils by default expects LICENSE.txt, not LICENSE. It also needs to be explicitly told about our example agent and associated files. (Git commit b394bdeaf55c2ad5903c3fbd90409cd2017780bf) * Mon Feb 25 20:30:20 2013 +0100 - Pieter Hollants Replace hardcoded version number with latest Git tag With this change, the version number is no longer hard-coded inside setup.py. Instead, the version number is derived from the latest git tag using "git describe". The Makefile (which is now more than a pure convenience Makefile) will generate setup.py from setup.py.in (which has been renamed accordingly), so that a source distribution (.tar.gz) will have the correct version number inside. As a special rule, whenever the Git checkout is not at a tag, "git describe" will generate a string similar to "0.2-21-g0e10fca", indicating that there have been 21 commits since the last tag "0.2" and the latest commit is g0e10fca. We translate this to "0.2_next21_g0e10fca" so a.) RPM does not complain about the dashes (it doesn't allow them in its "Version" field) and b.) the "next" indicates that this is a forthcoming version. (Git commit 7e920bb4e372f29cc3e0167224689e15bed14104) * Mon Feb 25 18:52:22 2013 +0100 - Pieter Hollants API change: swap "oidstr" and "initval" arguments for scalar variables The function signature for create_vartype_class() has changed and as such the constructor method signature for all classes using the @VarTypeClass decorator, that is, all classes representing SNMP scalar variables, eg. Integer32(), Unsigned32() etc. "initval" now comes first so that in scenarios where we just a SNMP variable without the intent of registering it we can simply write agent.Integer32(20) instead of agent.Integer32(initval=20) which, of course, implies "oidstr" == None. The former construct is of much more use when being interpreted as "initval" instead of "oidstr" as in agent.Integer32("EXAMPLE-MIB::exampleInteger32") which continues requiring an "oidstr=" prefix to work. This means that if you have already written agents using python-netsnmpagent and you did not follow the style in example_agent.py with explicit "initval=" and "oidstr=" qualifiers, you WILL have to adapt your code. (Git commit 0e10fca0ce6c34cd6730d3fc73158fe40386e14d) * Mon Feb 25 18:51:50 2013 +0100 - Pieter Hollants Fix typo in README (SNMP contents, eh?...) (Git commit 77dce9d7c43fe2ad68fcdd44917f0e2af73ce56f) * Fri Feb 22 19:58:43 2013 +0000 - Steven Hiscocks Fix counter64 incorrectly setting low and high in c structure (Git commit 809193cb1d4549f9849164aa7126972894b99e76) * Fri Feb 22 18:18:48 2013 +0000 - Steven Hiscocks Handle wrap of 32bit and 64bit counters (Git commit 9a2c4430e8011bdd6f2662bef90d63909c6d8003) * Fri Feb 22 14:06:47 2013 +0100 - Pieter Hollants Distinguish libraries when errors in library loading occur (Git commit 5c61efa9ef337641bfd26dc98e8600a8afba74d4) * Fri Feb 22 13:54:16 2013 +0100 - Pieter Hollants Update README with credits (Git commit 7b5fd128712073343a5c69025c6972dde53f1e02) * Fri Feb 22 13:52:43 2013 +0100 - Pieter Hollants Fix example agent's working with SNMP contexts (Git commit 1d9b0bbc92f02d99c86b2106c5a00598250ba73e) * Fri Feb 22 13:12:04 2013 +0100 - Pieter Hollants Abstract certain functions from actual library for compatibility with net-snmp <5.6 From net-snmp 5.5 to 5.6, all code from libnetsnmphelpers.so got integrated into libnetsnmpagent.so. While this doesn't break C programs since ld-linux.so will take care of dynamically resolving referenced symbols, with ctypes all prototype definitions and functions calls always depend on a particular libX handle. Therefore we now test using netsnmp_create_watcher_info whether that function is available in libnetsnmpagent and fall back to libnetsnmphelpers otherwise. The remaining code now uses libnsX to abstract from the actual library used. (Git commit 7f8ec59e26cd2ada809410b053d29a59f0efb3e3) * Fri Feb 22 13:05:03 2013 +0100 - Pieter Hollants Make comment about net-snmp 5.4.x workaround more verbose (Git commit d9388eeae15119ff4c5bd3e87578e4ff83b682e3) * Fri Feb 22 12:15:51 2013 +0100 - Pieter Hollants Work around unresolved dependencies bug in net-snmp 5.4.x libraries (Git commit 792977b5657e200a13794257bd12622b8d127021) * Thu Feb 21 23:43:24 2013 +0000 - Steven Hiscocks Added context argument to getRegistered and getContexts method (Git commit 830835b82bb7b976d8e57a28b2e0cbb1157aa2ff) * Fri Feb 15 13:37:45 2013 +0100 - Pieter Hollants Throw out SuSE-specific python macros from RPM specfile First of all, this module is noarch by definition. Second, SuSE macros won't be available on RHEL et al. (Git commit 9c0409d27624decaa309c998e56787f45ae29b51) * Fri Feb 15 13:20:50 2013 +0100 - Pieter Hollants Create BUILD directory during RPM build (required for SLES11SP2) (Git commit 6e66f11ff16e44ae686803980f00bf9121a60f78) * Thu Feb 14 10:42:48 2013 +0100 - Pieter Hollants Initialize variable so we can detect missing snmpd correctly (Git commit 53960d8547fdb648c86e5948a2a4db27d2470394) * Sun Feb 10 12:00:37 2013 +0000 - Steven Hiscocks Added ability to set context name for SNMP variables (Git commit ef457b1ea98cdf1a09bfaf705d7f7b5c6f7a6ffd) * Mon Jan 21 14:41:16 2013 +0100 - Pieter Hollants Rename "PersistentDir" option to "PersistenceDir" and streamline options in example agent (Git commit fe0d6b44964b8973f0dc0dc6f6b723d5630b3557) * Mon Jan 21 13:33:29 2013 +0100 - Pieter Hollants Bump year in copyright notices (Git commit f3d99ec7b4c36f145cfcc28cbf3bc5d163462f64) * Mon Jan 21 12:14:28 2013 +0100 - Pieter Hollants Fix Makefile targets and RPM building (Git commit 8b25ca9ced0997626a6eb25851c209e4e0f479b7) Changes from v0.1.1 to v0.2 =========================== * Mon Jan 21 12:13:06 2013 +0100 - Pieter Hollants Increase version number to 0.2 (Git commit 1ba10cab7be6389d65c208df7a5c52c0bebb4f51) * Mon Jan 21 12:12:32 2013 +0100 - Pieter Hollants Add local .spec file and Makefile target for RPM building (Git commit 116f4b4a9f184764bf3fea0dfc8cc4b9c2b6d668) * Mon Jan 21 11:02:06 2013 +0100 - Pieter Hollants Synchronize Makefile style with my other projects (Git commit 446a70cfa71457b463d70e834241600f58e9965c) * Mon Jan 21 10:55:49 2013 +0100 - Pieter Hollants Fix cleanup of *.pyc files (Git commit fc66e3ef6ba1b0374c81d9060cfa8a8bd47cee4d) * Mon Jan 21 10:30:10 2013 +0100 - Pieter Hollants Remove duplicate error handling statements set -e = set -o errexit (Git commit cde7b4908fca46ade88eed199d0d9f05c06d7a4d) * Mon Jan 21 10:29:39 2013 +0100 - Pieter Hollants Synchronize heading comment block style with my other projects (Git commit 32045ecd31ef39accdebaa46d0b620b82fe61e1e) * Fri Jan 18 18:49:36 2013 +0100 - Pieter Hollants Add missing .0 index for scalar variables Scalar variables must be registered with register_watched_scalar(), which automatically adds the ".0" suffix required by the SNMP standards. (Git commit 0dedf78627ccd9fd6f32361dd1fc82150e5b6a20) * Wed Jan 16 14:13:50 2013 +0100 - Pieter Hollants Add a function to clear the table (ie. remove all table rows) (Git commit fc2ba0976ec85bc92cd29c71abd348976270d7d8) * Mon Jan 14 14:39:25 2013 +0100 - Pieter Hollants Support auto-update of row counter variable for tables A SNMP variable of an integer type can be created and passed in to the Table() constructor in the "counterobj" property. netsnmpagent will then auto-increase this variable's value whenever a new row is added with addRow(). Naturally the variable should be exposed read-only. (Git commit f607142140200a0acb16bb442332e8d3013fd74c) * Mon Jan 14 14:38:27 2013 +0100 - Pieter Hollants Make the return value of integer variables types ordinary integers unless absolutely necessary (Git commit 8489ea8998b653af3f6df3367f4aa59ca9c38ddb) * Mon Jan 14 14:13:43 2013 +0100 - Pieter Hollants Rename poll() to check_and_process() for consistency with net-snmp API The name "poll" wasn't really intuitive, anyway. (Git commit f2558fee86bd1711893f93f429bb6ea642d21dfb) * Fri Jan 11 14:54:57 2013 +0100 - Pieter Hollants Update README (Git commit a2ae83446fb90d2d50080ac2d6f9b1a30bf41940) * Fri Jan 11 14:50:53 2013 +0100 - Pieter Hollants Make Makefile clean up *.pyc files as well (Git commit 7f9763d3cc835c56005a204f3a6c3ac4946a2a04) * Fri Jan 11 14:49:36 2013 +0100 - Pieter Hollants Correct dist target in convenience Makefile (Git commit 49524931c09337b0d8bc279d20e480d46e4421a9) * Fri Jan 11 13:44:03 2013 +0100 - Pieter Hollants Add dist directory generated by distutils to .gitignore (Git commit 1e6610b72a81fc291cb7e9e52d69e660cb919ac7) * Fri Jan 11 13:43:41 2013 +0100 - Pieter Hollants Add MANIFEST generated by distutils to .gitignore (Git commit 8e21655794de6f466b3d7152ae70903a12d4ad04) Initial version 0.1.1 ===================== * Fri Jan 11 13:36:35 2013 +0100 - Pieter Hollants Bump version number to 0.1.1 (Git commit 34158a2751b42764559930a03e6e4a1328f03709) * Fri Jan 11 13:35:43 2013 +0100 - Pieter Hollants Modify License specification to conform with openSUSE guidelines (Git commit 87f978986c9e466cae82e9bb6c4e2231c27888f0) * Fri Jan 11 13:34:30 2013 +0100 - Pieter Hollants Remove the she-bang from netsnmpagent.py and netsnmpapi.py These are Python modules not supposed to be called directly. (Git commit 2a482be4b5f2b4b58867660f20f98e13086ac020) * Fri Jan 11 13:26:10 2013 +0100 - Pieter Hollants Clean up dist directory as well (Git commit b0ea63d4658eeca4a69051e5cae63678e2af6024) * Fri Jan 11 11:22:02 2013 +0100 - Pieter Hollants Add convenience Makefile (Git commit d0ac759c8f1bee0dbf5044023d1b1fd526e59189) * Fri Jan 11 11:21:16 2013 +0100 - Pieter Hollants Remove python- prefix from package name (Git commit 8f4fb7377abf39669245506d3b0020e7bfffdee8) * Thu Jan 10 17:19:10 2013 +0100 - Pieter Hollants Update README (Git commit 37be34d9cf4cd95ae08c084c9b0c91dd8aa9b202) * Thu Jan 10 17:08:42 2013 +0100 - Pieter Hollants Add distutils-style setup.py script (Git commit b8f015747f6bcd9413bd1094e9dd88628825e011) * Thu Jan 10 16:50:39 2013 +0100 - Pieter Hollants Add own class for IpAddress to offer more natural Python interface IP addresses are stored as unsigned integers, but the Python interface should use strings so that IP addresses can be given in their natural form, ie. 127.0.0.1. (Git commit 86cc5a3bed54991eafa171becf30175c1efb451f) * Thu Jan 10 15:42:54 2013 +0100 - Pieter Hollants Make Table value() method return integer-style values for integers, not strings (Git commit 0419cb46d68ab0358babb5af2fbcfed87947fe7c) * Thu Jan 10 15:36:27 2013 +0100 - Pieter Hollants Let pprint module do the variable dumping in example agent (Git commit 6af9e9677e243e7601fba0a6e3ac0c40607e83d9) * Thu Jan 10 15:23:54 2013 +0100 - Pieter Hollants Improve Table value() output Row indices were returned incorrectly. Also make the returned object a dictionary containing dictionaries that match the value() output for Integer32 etc. (Git commit db79e071de38623ef4acb01e96fdf7293ed526cd) * Thu Jan 10 15:22:24 2013 +0100 - Pieter Hollants Of course one should use existing data types first... (Git commit c0fab40bb4c934fada5821c9952e9113274f70d2) * Thu Jan 10 15:21:03 2013 +0100 - Pieter Hollants Add a second example table to demonstrate different index types (Git commit 628cb7b9a5d4dce83e9938806b28947a2dc62380) * Thu Jan 10 15:09:08 2013 +0100 - Pieter Hollants Cosmetics in example agent's DumpRegistered() output (Git commit 305d0240414f8e3b8dac35b9132a8ce5e5cc68e5) * Mon Jan 7 11:51:38 2013 +0100 - Pieter Hollants Make run_example_agent.sh find snmpd within /usr/local as well (Git commit a5cbdc7bde9a5f323cb28c4678efd78cced77ce1) * Fri Jan 4 14:54:35 2013 +0100 - Pieter Hollants Cosmetics (Git commit b5eb7c4bdca5957f1e863953730541a2060a79ed) * Fri Jan 4 14:49:39 2013 +0100 - Pieter Hollants Add first implementation of value() method for tables (Git commit 2e204946984db2b3ad65b662229d82e4e84b955e) * Fri Jan 4 14:47:09 2013 +0100 - Pieter Hollants Add more error handling for netsnmp_ds_set_* calls (Git commit 7e6f8eddfb8b71f2d60690136df0b52a709e7a62) * Thu Jan 3 16:18:31 2013 +0100 - Pieter Hollants Add support for Counter64 data type This one requires actually some help since inheriting from ctypes.Structure alone does not work. net-snmp uses a C structure of two long variables internally to store the Counter64 value, so we need to provide wrapper functions for that. (Git commit a49b3a6a60e54aa632fca390a3d3afe16534adfd) * Wed Dec 26 15:08:18 2012 +0100 - Pieter Hollants Update README (Git commit cca2b90091e1bbf533c7a6cb34d6dbeacef24e74) * Wed Dec 26 14:58:45 2012 +0100 - Pieter Hollants Fix typos in README (Git commit f515bafa980673a3353308c2dc69402bcbf49157) * Wed Dec 26 14:46:25 2012 +0100 - Pieter Hollants First implementation of table support Among the existing scalar variables, netsnmpagent now supports SNMP tables. Table objects are defined in terms of the types of the row indexes, the number of columns, their types and their default row values and whether they are extendable or not (ie. whether new rows can be added through snmpset). After creation (and registration), rows can be added and individual cell contents set. This implementation does not support the value() method for tables yet. (Git commit e3c2b42e344544145b2e1335d12327247bf03544) * Wed Dec 26 14:40:47 2012 +0100 - Pieter Hollants Use a string index type for the exampleTable In preparation for the upcoming table support, the index type for exampleTable (exampleTableIndex) was changed to a string type since it'll make for a more advanced example than an integer type. (Git commit f696b7fe11e60345130b5e53068c3206691f8c01) * Wed Dec 26 00:07:04 2012 +0100 - Pieter Hollants Move net-snmp API stuff into own file and define C function prototypes Everything ctypes and libnetsnmpagent-related has been moved into the new file netsnmpapi.py that gets imported by netsnmpagent.py. All netsnmp functions called are now also defined in terms of argument and result types, similar to C prototypes. To achieve this, the libnetsnmpagent handle is now defined globally instead of being part of the netsnmpAgent class. (Git commit f9114e0cc22665afd37abf224ab9f10861e5c03e) * Sun Dec 23 22:34:06 2012 +0100 - Pieter Hollants Add DISPLAY-HINT for exampleOctetString so UTF-8 encoded strings can be set properly (Git commit 44713eb7aba8a80a5747d73775bd427e64a3bd61) * Sun Dec 23 22:27:21 2012 +0100 - Pieter Hollants Correct max_size calculation for scalar SNMP objects (Git commit 77b9fb048e2ed477b654475247f952acdef6b439) * Sun Dec 23 22:24:03 2012 +0100 - Pieter Hollants Cosmetical optimization of SNMP object's cref() method (Git commit a25799afe631c2689e775e7c167263a69a7b6a2f) * Sun Dec 23 22:20:59 2012 +0100 - Pieter Hollants Reunify SNMP object creation and registration again It turned out to be unnecessarily complex to maintain consistency with the seperation of SNMP object creation and registration as introduced in 0f7a4cdbf4677ebae52e0abee2acacd148c50491. Yet, that change is not literally undone: - Registration of an SNMP object must now be specified at object creation time by passing an "oidstr", if desired. If left off, the return object will remain unbound to the MIB and can be used mostly for object property inspection. As a consequence, register() was removed again. The new "writable" parameter to the object's creation method replaces register()'s "allow_set". - Previously, objects were created and if register() was called on them, it called a defined callback method. Now, the object's creation method calls the renamed _prepareRegistration() method instead and incorporates _registerWatcher() functionality. - The variable type's props array no longer needs a "register_func" member. (Git commit cb0bb0d44eb4be5c6f9e98920f269020bae2e145) * Sun Dec 23 21:48:05 2012 +0100 - Pieter Hollants Improve comment for DisplayString method (Git commit 5a85195fa56046f50d1114ba2ef5ab5e537b6fb7) * Sun Dec 23 21:39:38 2012 +0100 - Pieter Hollants example_agent.py cosmetics: move agent.start() call before handler definition (Git commit 5270e9653eaa5fcc002429b4402aa1fbe197d1c5) * Sun Dec 23 21:28:14 2012 +0100 - Pieter Hollants Add Python shebangs for netsnmpagent.py and example_agent.py (Git commit 7172cd2edc3aa38be50c5f4c4fe689ae1d4d8031) * Thu Dec 20 12:22:32 2012 +0100 - Pieter Hollants Fix wrong table column number in EXAMPLE-MIB.txt (Git commit f12e5c9ff298493ffab58a27f9702485a4b6e4e1) * Tue Dec 18 14:50:00 2012 +0100 - Pieter Hollants Generalize for SNMP tables, separate SNMP object creation and registration This change generalizes netsnmpagent in that we now deal with "SNMP objects" instead of just SNMP variables. This is in preparation of the upcoming support of SNMP tables. Because of the semantics associated with how we will wrap tables, SNMP object creation and registration under a certain OID are now separate tasks. SNMP objects now must be explicitly registered (see example_agent.py). Further details of the changes: - Because registering a certain SNMP object is object-dependent, the VarTypeClass() decorator now expects a new member "register_func" that specifies the callback function that will be called by the new generic register() method. - register() incorporates the former oidstr2oid() method - The VarTypeClass() decorator's variable registration code went into register() and the new callback function _registerWatcher() as well. - Variable type class instances now possess most of the atttributes from their props[] array such as "asntype" and "flags" - _data_size and _max_size were not always set correctly - Variable type class instances now possess a new cref() method which always returns the correct pointer to the enclosed _cvar, depending on the variable type (Git commit 0f7a4cdbf4677ebae52e0abee2acacd148c50491) * Wed Nov 28 13:30:04 2012 +0100 - Pieter Hollants Add run_example_agent.sh script to make running example_agent.py easier example_agent.py has now learned two command line options: -m/--master-socket allows specifying the Unix domain socket that python-netsnmpagent will use to connect to the master snmpd agent and speak AgentX. -p/--persistent-dir is passed on the net-snmp library and sets the directory the library will use to store persistance information. Both parameters are used by the new run_example_agent.sh script to be able to setup a net-snmp instance running under the current user and independent from a eventually running system-level snmpd service. They will also be used by upcoming automatic tests. run_example_agent takes care of - creating a temporary directory - creating a minimal snmpd configuration that especially places all system file/directory references into the temporary directory instead - running snmpd with additional necessary parameters - giving the user guidance as to the right net-snmp CLI utility commands - running example_agent.py with suitable parameters - cleanup of the temporary dir upon exit (Git commit c54e1a67f32fcc086aa0ee4ab8b567bab2391db3) * Wed Nov 28 12:55:57 2012 +0100 - Pieter Hollants Rename example.py to example_agent.py (Git commit 6db1ac747835f512aaa1783b1a5a7350429a99c9) * Wed Nov 28 11:58:25 2012 +0100 - Pieter Hollants Add support to modify persistance directory (Git commit 92388737919b9eb2b6bdc170c41e1c13c06fc21b) * Wed Nov 28 00:45:06 2012 +0100 - Pieter Hollants Minor cosmetics (Git commit ee5245d45ff319e30b8f2b8ed4c63463e1b14b62) * Wed Nov 28 00:41:37 2012 +0100 - Pieter Hollants Fix and expand EXAMPLE-MIB - Rename IPAddress to IpAddress - Replace exampleString with exampleOctetString and exampleDisplayString - Add missing IMPORTs for Counter32, TimeTicks and IpAddress to IpAddress - Add exampleCounter, exampleTimeTicks, exampleIpAddress, exampleOctetString and exampleDisplayString to exampleMIBScalarsGroup conformance group EXAMPLE-MIB.txt should always be validated with smilint. (Git commit 3798704e9b4633ebf4a398251e89881b3986dffd) * Wed Nov 28 00:29:12 2012 +0100 - Pieter Hollants Use single C variable reference and mutable string buffer String handling was broken. We must use ctypes.create_string_buffer with a defined MAX_STR_SIZE instead of ctypes.c_char_p because the latter creates a buffer which a.) Python considered immutable while "snmpset" would happily write into it and b.) had an implicit maximum size depending on the initial creation string, causing possible memory corruption when "snmpset" would be used with a larger string. It also didn't make much sense to re-create a new ctypes.* instance upon each SNMP variable update from within the Python agent, because the integer types were mutable before and strings have become so. We fill out netsnmp_watcher_info's "max_size" properly now. Required for string types. Also discarded the special handling for strings, special handling now depends on props["flags"] only. Reordered things in define_and_register() and accordingly renamed it to register_and_define(). Added more verbose comments about the process of SNMP variable registration. (Git commit 65ba9c1223f347eaa5d347c334aa646e61102326) * Wed Nov 28 00:00:38 2012 +0100 - Pieter Hollants Make example.py dump vars when SIGHUP is received Also give exampleString a friendlier default value. (Git commit 98d567461f4ae3f4479bbbabfd4b3e02431ae05a) * Tue Nov 27 12:45:11 2012 +0100 - Pieter Hollants Implement special handling for c_char_p variable types more explicitly We need a special case distinction for c_char_p (char *) variables during variable creation anyway (doesn't use ctypes.byref()), so no need for props["flags"], which was different for c_char_p only. Also fix watcher info's max_size as 0 as it is only used if the WATCHER_MAX_SIZE flag is set which we don't use. (Git commit 9b4297f4c87b3c16b1e8beaa4aa9010b96985316) * Mon Nov 26 14:30:54 2012 +0100 - Pieter Hollants Fix check that vars can't be added after agent start (Git commit ade8000db69ed9520676af6fa6f886ec76ed4f61) * Mon Nov 26 14:29:38 2012 +0100 - Pieter Hollants Cosmetics to make line lengths <= 80 chars (Git commit 14f8afa4e82c9adb2f52eaf240385453b8606993) * Mon Nov 26 14:20:31 2012 +0100 - Pieter Hollants Cosmetics (Git commit f328fbe1459bf9d8609bc4bdb84a5a580de794c6) * Mon Nov 26 14:18:52 2012 +0100 - Pieter Hollants Streamline SNMP variable class definition Before, there was a netsnmpVariable class and Integer32, Unsigned32, DisplayString32 etc. classes deriving from it (and an intermediate class netsnmpIntegerVariable). The variables carried the SNMP registration intelligence. In addition, netsnmpAgent had an addVar() method and proxy methods named liked the classes, taking care of creating variable instance and adding them to an agent's internal variable registry. Now, netsnmpAgent features a VarTypeClass decorator method that gets applied to methods Integer32, Unsigned32, DisplayString32 etc. which do nothing but return a dictionary with variable type properties. The decorator transforms these functions so that they a.) define the suitable class b.) create an instance and c.) register that instance with net-snmp. The classes themselves are now nothing but wrappers for the ctypes variables. So the registration intelligence (and oidstr2oid()) is back in netsnmpAgent itself again.- (Git commit b2e4c4d50c53e070e6635e2051873f1e2104516d) * Mon Nov 26 13:50:36 2012 +0100 - Pieter Hollants Let example.py print the registered vars for debugging and ease of use (Git commit 3aa9ac50fd3255fbf5695a1aaab61fd1b818c300) * Sun Nov 25 23:49:19 2012 +0100 - Pieter Hollants Major rewrite of SNMP variable handling and exposed API Before, the code using the module (eg. example.py) had to own ctypes-style variables and deal with conversions etc. netsnmpAgent just provided a registration function. Now, netsnmpAgent offers methods that return classes inheriting from a base class, netsnmpVariable, one class for each variable type. Class creation requires specification of the OID to register for and indication whether the variable should be writable using SNMP requests. Each class obtained this way offers value() and update() methods, called by the client at its decretion. See example.py how much shorter and nicer this looks. The interface is not quite polished yet, currently netsnmpVariable and deriving classes share the same namespace as netsnmpAgent and require an "agentlib" argument to get access to netsnmpAgent's libnetsnmpagent handle. This is currently provided by an addVar method for which there are in turn wrapper functions named after each variable type. This could need some improvement, probably using a factory method and/or decorators. In other news: - Moved net-snmp constants to the module level to avoid accessibility problems from within the new netsnmpVariable class and inheriting classes. - Removed debug-style print statement that spit out the name of each loaded MIB upon agent startup - Simplified library loading code since it looks like libnetsnmpagent alone will suffice. (Git commit e4fc68bfdf93998fd622d96d326443ef87a124d6) * Sat Nov 24 02:05:54 2012 +0100 - Pieter Hollants Add support for IPAddress variables (Git commit 4ffe76d0e967332994ea1da1d5b0c6ee6237bae3) * Sat Nov 24 00:56:22 2012 +0100 - Pieter Hollants Add support for TimeTicks variables (Git commit fbe5e01239396c7924816bf831e8e78dabc826e2) * Fri Nov 23 23:42:53 2012 +0100 - Pieter Hollants Cosmectic fix for correct size of Unsigned32 variables (Git commit 1e6973be62966a0cb4ae6703d9a612d40d863e47) * Fri Nov 23 23:32:46 2012 +0100 - Pieter Hollants Add support for Counter32 variables (Git commit 7563a6c16491efc86484426218b355ef9236fcf0) * Fri Nov 23 17:51:54 2012 +0100 - Pieter Hollants Added TODOs (Git commit e1c7400bbfe951523f15139e6c4c3f85e0942410) * Fri Nov 23 17:41:00 2012 +0100 - Pieter Hollants Add LICENSE (Git commit 2eb7fb848c1cede41018a340b656feadcd131fbf) * Fri Nov 23 17:40:15 2012 +0100 - Pieter Hollants Add a first version of a README (Git commit 609ee8edc197914ff0fb1b7d6b955f273ac080db) * Fri Nov 23 16:28:32 2012 +0100 - Pieter Hollants Added .gitignore to ignore *.pyc files (Git commit 62574958cf3ff02497bfb07bd9e10a17709947fb) * Fri Nov 23 16:10:31 2012 +0100 - Pieter Hollants Cosmetics (Git commit c06ba86f1df7d515b0567559e5ff6483cb302a3b) * Fri Nov 23 16:10:31 2012 +0100 - Pieter Hollants Add read-only variables support (Git commit f2def554a8cb54b0ea593da42ffec4daddb97a25) * Thu Nov 22 22:58:24 2012 +0100 - Pieter Hollants Initial commit (Git commit ff76a9d3fb6148ed4a6d588358840984eee14e24) netsnmpagent-0.6.0/netsnmpagent.py0000644000175000001440000010761613071257716017423 0ustar piefusers00000000000000# # python-netsnmpagent module # Copyright (c) 2013-2016 Pieter Hollants # Licensed under the GNU Lesser Public License (LGPL) version 3 # # Main module # """ Allows to write net-snmp subagents in Python. The Python bindings that ship with net-snmp support client operations only. I fixed a couple of issues in the existing python-agentx module but eventually decided to write a new module from scratch due to design issues. For example, it implemented its own handler for registered SNMP variables, which requires re-doing a lot of stuff which net-snmp actually takes care of in its API's helpers. This module, by contrast, concentrates on wrapping the net-snmp C API for SNMP subagents in an easy manner. """ import sys, os, socket, struct, re, locale from collections import defaultdict from netsnmpapi import * # Maximum string size supported by python-netsnmpagent MAX_STRING_SIZE = 1024 # Helper function courtesy of Alec Thomas and taken from # http://stackoverflow.com/questions/36932/how-can-i-represent-an-enum-in-python def enum(*sequential, **named): enums = dict(zip(sequential, range(len(sequential))), **named) try: # Python 2.x enums_iterator = enums.iteritems() except AttributeError: # Python 3.x enums_iterator = enums.items() enums["Names"] = dict((value,key) for key, value in enums_iterator) return type("Enum", (), enums) # Helper functions to deal with converting between byte strings (required by # ctypes) and Unicode strings (possibly used by the Python version in use) def b(s): """ Encodes Unicode strings to byte strings, if necessary. """ return s if isinstance(s, bytes) else s.encode(locale.getpreferredencoding()) def u(s): """ Decodes byte strings to Unicode strings, if necessary. """ return s if isinstance("Test", bytes) else s.decode(locale.getpreferredencoding()) # Indicates the status of a netsnmpAgent object netsnmpAgentStatus = enum( "REGISTRATION", # Unconnected, SNMP object registrations possible "FIRSTCONNECT", # No more registrations, first connection attempt "CONNECTFAILED", # Error connecting to snmpd "CONNECTED", # Connected to a running snmpd instance "RECONNECTING", # Got disconnected, trying to reconnect ) # Helper function to determine if "x" is a num def isnum(x): try: x + 1 return True except TypeError: return False class netsnmpAgent(object): """ Implements an SNMP agent using the net-snmp libraries. """ def __init__(self, **args): """Initializes a new netsnmpAgent instance. "args" is a dictionary that can contain the following optional parameters: - AgentName : The agent's name used for registration with net-snmp. - MasterSocket : The transport specification of the AgentX socket of the running snmpd instance to connect to (see the "LISTENING ADDRESSES" section in the snmpd(8) manpage). Change this if you want to use eg. a TCP transport or access a custom snmpd instance, eg. as shown in run_simple_agent.sh, or for automatic testing. - PersistenceDir: The directory to use to store persistence information. Change this if you want to use a custom snmpd instance, eg. for automatic testing. - MIBFiles : A list of filenames of MIBs to be loaded. Required if the OIDs, for which variables will be registered, do not belong to standard MIBs and the custom MIBs are not located in net-snmp's default MIB path (/usr/share/snmp/mibs). - UseMIBFiles : Whether to use MIB files at all or not. When False, the parser for MIB files will not be initialized, so neither system-wide MIB files nor the ones provided in the MIBFiles argument will be in use. - LogHandler : An optional Python function that will be registered with net-snmp as a custom log handler. If specified, this function will be called for every log message net-snmp itself generates, with parameters as follows: 1. a string indicating the message's priority: one of "Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Info" or "Debug". 2. the actual log message. Note that heading strings such as "Warning: " and "Error: " will be stripped off since the priority level is explicitly known and can be used to prefix the log message, if desired. Trailing linefeeds will also have been stripped off. If undefined, log messages will be written to stderr instead. """ # Default settings defaults = { "AgentName" : os.path.splitext(os.path.basename(sys.argv[0]))[0], "MasterSocket" : None, "PersistenceDir": None, "UseMIBFiles" : True, "MIBFiles" : None, "LogHandler" : None, } for key in defaults: setattr(self, key, args.get(key, defaults[key])) if self.UseMIBFiles and self.MIBFiles is not None and type(self.MIBFiles) not in (list, tuple): self.MIBFiles = (self.MIBFiles,) # Initialize status attribute -- until start() is called we will accept # SNMP object registrations self._status = netsnmpAgentStatus.REGISTRATION # Unfortunately net-snmp does not give callers of init_snmp() (used # in the start() method) any feedback about success or failure of # connection establishment. But for AgentX clients this information is # quite essential, thus we need to implement some more or less ugly # workarounds. # For net-snmp 5.7.x, we can derive success and failure from the log # messages it generates. Normally these go to stderr, in the absence # of other so-called log handlers. Alas we define a callback function # that we will register with net-snmp as a custom log handler later on, # hereby effectively gaining access to the desired information. def _py_log_handler(majorID, minorID, serverarg, clientarg): # "majorID" and "minorID" are the callback IDs with which this # callback function was registered. They are useful if the same # callback was registered multiple times. # Both "serverarg" and "clientarg" are pointers that can be used to # convey information from the calling context to the callback # function: "serverarg" gets passed individually to every call of # snmp_call_callbacks() while "clientarg" was initially passed to # snmp_register_callback(). # In this case, "majorID" and "minorID" are always the same (see the # registration code below). "serverarg" needs to be cast back to # become a pointer to a "snmp_log_message" C structure (passed by # net-snmp's log_handler_callback() in snmplib/snmp_logging.c) while # "clientarg" will be None (see the registration code below). logmsg = ctypes.cast(serverarg, snmp_log_message_p) # Generate textual description of priority level priorities = { LOG_EMERG: "Emergency", LOG_ALERT: "Alert", LOG_CRIT: "Critical", LOG_ERR: "Error", LOG_WARNING: "Warning", LOG_NOTICE: "Notice", LOG_INFO: "Info", LOG_DEBUG: "Debug" } msgprio = priorities[logmsg.contents.priority] # Strip trailing linefeeds and in addition "Warning: " and "Error: " # from msgtext as these conditions are already indicated through # msgprio msgtext = re.sub( "^(Warning|Error): *", "", u(logmsg.contents.msg.rstrip(b"\n")) ) # Intercept log messages related to connection establishment and # failure to update the status of this netsnmpAgent object. This is # really an ugly hack, introducing a dependency on the particular # text of log messages -- hopefully the net-snmp guys won't # translate them one day. if msgprio == "Warning" \ or msgprio == "Error" \ and re.match("Failed to .* the agentx master agent.*", msgtext): # If this was the first connection attempt, we consider the # condition fatal: it is more likely that an invalid # "MasterSocket" was specified than that we've got concurrency # issues with our agent being erroneously started before snmpd. if self._status == netsnmpAgentStatus.FIRSTCONNECT: self._status = netsnmpAgentStatus.CONNECTFAILED # No need to log this message -- we'll generate our own when # throwing a netsnmpAgentException as consequence of the # ECONNECT return 0 # Otherwise we'll stay at status RECONNECTING and log net-snmp's # message like any other. net-snmp code will keep retrying to # connect. elif msgprio == "Info" \ and re.match("AgentX subagent connected", msgtext): self._status = netsnmpAgentStatus.CONNECTED elif msgprio == "Info" \ and re.match("AgentX master disconnected us.*", msgtext): self._status = netsnmpAgentStatus.RECONNECTING # If "LogHandler" was defined, call it to take care of logging. # Otherwise print all log messages to stderr to resemble net-snmp # standard behavior (but add log message's associated priority in # plain text as well) if self.LogHandler: self.LogHandler(msgprio, msgtext) else: print("[{0}] {1}".format(msgprio, msgtext)) return 0 # We defined a Python function that needs a ctypes conversion so it can # be called by C code such as net-snmp. That's what SNMPCallback() is # used for. However we also need to store the reference in "self" as it # will otherwise be lost at the exit of this function so that net-snmp's # attempt to call it would end in nirvana... self._log_handler = SNMPCallback(_py_log_handler) # Now register our custom log handler with majorID SNMP_CALLBACK_LIBRARY # and minorID SNMP_CALLBACK_LOGGING. if libnsa.snmp_register_callback( SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_LOGGING, self._log_handler, None ) != SNMPERR_SUCCESS: raise netsnmpAgentException( "snmp_register_callback() failed for _netsnmp_log_handler!" ) # Finally the net-snmp logging system needs to be told to enable # logging through callback functions. This will actually register a # NETSNMP_LOGHANDLER_CALLBACK log handler that will call out to any # callback functions with the majorID and minorID shown above, such as # ours. libnsa.snmp_enable_calllog() # Unfortunately our custom log handler above is still not enough: in # net-snmp 5.4.x there were no "AgentX master disconnected" log # messages yet. So we need another workaround to be able to detect # disconnects for this release. Both net-snmp 5.4.x and 5.7.x support # a callback mechanism using the "majorID" SNMP_CALLBACK_APPLICATION and # the "minorID" SNMPD_CALLBACK_INDEX_STOP, which we can abuse for our # purposes. Again, we start by defining a callback function. def _py_index_stop_callback(majorID, minorID, serverarg, clientarg): # For "majorID" and "minorID" see our log handler above. # "serverarg" is a disguised pointer to a "netsnmp_session" # structure (passed by net-snmp's subagent_open_master_session() and # agentx_check_session() in agent/mibgroup/agentx/subagent.c). We # can ignore it here since we have a single session only anyway. # "clientarg" will be None again (see the registration code below). # We only care about SNMPD_CALLBACK_INDEX_STOP as our custom log # handler above already took care of all other events. if minorID == SNMPD_CALLBACK_INDEX_STOP: self._status = netsnmpAgentStatus.RECONNECTING return 0 # Convert it to a C callable function and store its reference self._index_stop_callback = SNMPCallback(_py_index_stop_callback) # Register it with net-snmp if libnsa.snmp_register_callback( SNMP_CALLBACK_APPLICATION, SNMPD_CALLBACK_INDEX_STOP, self._index_stop_callback, None ) != SNMPERR_SUCCESS: raise netsnmpAgentException( "snmp_register_callback() failed for _netsnmp_index_callback!" ) # No enabling necessary here # Make us an AgentX client if libnsa.netsnmp_ds_set_boolean( NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 1 ) != SNMPERR_SUCCESS: raise netsnmpAgentException( "netsnmp_ds_set_boolean() failed for NETSNMP_DS_AGENT_ROLE!" ) # Use an alternative transport specification to connect to the master? # Defaults to "/var/run/agentx/master". # (See the "LISTENING ADDRESSES" section in the snmpd(8) manpage) if self.MasterSocket: if libnsa.netsnmp_ds_set_string( NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCKET, b(self.MasterSocket) ) != SNMPERR_SUCCESS: raise netsnmpAgentException( "netsnmp_ds_set_string() failed for NETSNMP_DS_AGENT_X_SOCKET!" ) # Use an alternative persistence directory? if self.PersistenceDir: if libnsa.netsnmp_ds_set_string( NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PERSISTENT_DIR, b(self.PersistenceDir) ) != SNMPERR_SUCCESS: raise netsnmpAgentException( "netsnmp_ds_set_string() failed for NETSNMP_DS_LIB_PERSISTENT_DIR!" ) # Initialize net-snmp library (see netsnmp_agent_api(3)) if libnsa.init_agent(b(self.AgentName)) != 0: raise netsnmpAgentException("init_agent() failed!") # Initialize MIB parser if self.UseMIBFiles: libnsa.netsnmp_init_mib() # If MIBFiles were specified (ie. MIBs that can not be found in # net-snmp's default MIB directory /usr/share/snmp/mibs), read # them in so we can translate OID strings to net-snmp's internal OID # format. if self.UseMIBFiles and self.MIBFiles: for mib in self.MIBFiles: if libnsa.read_mib(b(mib)) == 0: raise netsnmpAgentException("netsnmp_read_module({0}) " + "failed!".format(mib)) # Initialize our SNMP object registry self._objs = defaultdict(dict) def _prepareRegistration(self, oidstr, writable = True): """ Prepares the registration of an SNMP object. "oidstr" is the OID to register the object at. "writable" indicates whether "snmpset" is allowed. """ # Make sure the agent has not been start()ed yet if self._status != netsnmpAgentStatus.REGISTRATION: raise netsnmpAgentException("Attempt to register SNMP object " \ "after agent has been started!") if self.UseMIBFiles: # We can't know the length of the internal OID representation # beforehand, so we use a MAX_OID_LEN sized buffer for the call to # read_objid() below oid = (c_oid * MAX_OID_LEN)() oid_len = ctypes.c_size_t(MAX_OID_LEN) # Let libsnmpagent parse the OID if libnsa.read_objid( b(oidstr), ctypes.cast(ctypes.byref(oid), c_oid_p), ctypes.byref(oid_len) ) == 0: raise netsnmpAgentException("read_objid({0}) failed!".format(oidstr)) else: # Interpret the given oidstr as the oid itself. try: parts = [c_oid(long(x) if sys.version_info <= (3,) else int(x)) for x in oidstr.split('.')] except ValueError: raise netsnmpAgentException("Invalid OID (not using MIB): {0}".format(oidstr)) oid = (c_oid * len(parts))(*parts) oid_len = ctypes.c_size_t(len(parts)) # Do we allow SNMP SETting to this OID? handler_modes = HANDLER_CAN_RWRITE if writable \ else HANDLER_CAN_RONLY # Create the netsnmp_handler_registration structure. It notifies # net-snmp that we will be responsible for anything below the given # OID. We use this for leaf nodes only, processing of subtrees will be # left to net-snmp. handler_reginfo = libnsa.netsnmp_create_handler_registration( b(oidstr), None, oid, oid_len, handler_modes ) return handler_reginfo def VarTypeClass(property_func): """ Decorator that transforms a simple property_func into a class factory returning instances of a class for the particular SNMP variable type. property_func is supposed to return a dictionary with the following elements: - "ctype" : A reference to the ctypes constructor method yielding the appropriate C representation of the SNMP variable, eg. ctypes.c_long or ctypes.create_string_buffer. - "flags" : A net-snmp constant describing the C data type's storage behavior, currently either WATCHER_FIXED_SIZE or WATCHER_MAX_SIZE. - "max_size" : The maximum allowed string size if "flags" has been set to WATCHER_MAX_SIZE. - "initval" : The value to initialize the C data type with, eg. 0 or "". - "asntype" : A constant defining the SNMP variable type from an ASN.1 perspective, eg. ASN_INTEGER. - "context" : A string defining the context name for the SNMP variable The class instance returned will have no association with net-snmp yet. Use the Register() method to associate it with an OID. """ # This is the replacement function, the "decoration" def create_vartype_class(self, initval = None, oidstr = None, writable = True, context = ""): agent = self # Call the original property_func to retrieve this variable type's # properties. Passing "initval" to property_func may seem pretty # useless as it won't have any effect and we use it ourselves below. # However we must supply it nevertheless since it's part of # property_func's function signature which THIS function shares. # That's how Python's decorators work. props = property_func(self, initval) # Use variable type's default initval if we weren't given one if initval == None: initval = props["initval"] # Create a class to wrap ctypes' access semantics and enable # Register() to do class-specific registration work. # # Since the part behind the "class" keyword can't be a variable, we # use the proxy name "cls" and overwrite its __name__ property # after class creation. class cls(object): def __init__(self): for prop in ["flags", "asntype"]: setattr(self, "_{0}".format(prop), props[prop]) # Create the ctypes class instance representing the variable # to be handled by the net-snmp C API. If this variable type # has no fixed size, pass the maximum size as second # argument to the constructor. if props["flags"] == WATCHER_FIXED_SIZE: self._cvar = props["ctype"](initval if isnum(initval) else b(initval)) self._data_size = ctypes.sizeof(self._cvar) self._max_size = self._data_size else: self._cvar = props["ctype"](initval if isnum(initval) else b(initval), props["max_size"]) self._data_size = len(self._cvar.value) self._max_size = max(self._data_size, props["max_size"]) if oidstr: # Prepare the netsnmp_handler_registration structure. handler_reginfo = agent._prepareRegistration(oidstr, writable) handler_reginfo.contents.contextName = b(context) # Create the netsnmp_watcher_info structure. self._watcher = libnsX.netsnmp_create_watcher_info( self.cref(), self._data_size, self._asntype, self._flags ) # Explicitly set netsnmp_watcher_info structure's # max_size parameter. netsnmp_create_watcher_info6 would # have done that for us but that function was not yet # available in net-snmp 5.4.x. self._watcher.contents.max_size = self._max_size # Register handler and watcher with net-snmp. result = libnsX.netsnmp_register_watched_scalar( handler_reginfo, self._watcher ) if result != 0: raise netsnmpAgentException("Error registering variable with net-snmp!") # Finally, we keep track of all registered SNMP objects for the # getRegistered() method. agent._objs[context][oidstr] = self def value(self): val = self._cvar.value if isnum(val): # Python 2.x will automatically switch from the "int" # type to the "long" type, if necessary. Python 3.x # has no limits on the "int" type anymore. val = int(val) else: val = u(val) return val def cref(self, **kwargs): return ctypes.byref(self._cvar) if self._flags == WATCHER_FIXED_SIZE \ else self._cvar def update(self, val): if self._asntype == ASN_COUNTER and val >> 32: val = val & 0xFFFFFFFF if self._asntype == ASN_COUNTER64 and val >> 64: val = val & 0xFFFFFFFFFFFFFFFF self._cvar.value = val if props["flags"] == WATCHER_MAX_SIZE: if len(val) > self._max_size: raise netsnmpAgentException( "Value passed to update() truncated: {0} > {1} " "bytes!".format(len(val), self._max_size) ) self._data_size = self._watcher.contents.data_size = len(val) if props["asntype"] in [ASN_COUNTER, ASN_COUNTER64]: def increment(self, count=1): self.update(self.value() + count) cls.__name__ = property_func.__name__ # Return an instance of the just-defined class to the agent return cls() return create_vartype_class @VarTypeClass def Integer32(self, initval = None, oidstr = None, writable = True, context = ""): return { "ctype" : ctypes.c_long, "flags" : WATCHER_FIXED_SIZE, "initval" : 0, "asntype" : ASN_INTEGER } @VarTypeClass def Unsigned32(self, initval = None, oidstr = None, writable = True, context = ""): return { "ctype" : ctypes.c_ulong, "flags" : WATCHER_FIXED_SIZE, "initval" : 0, "asntype" : ASN_UNSIGNED } @VarTypeClass def Counter32(self, initval = None, oidstr = None, writable = True, context = ""): return { "ctype" : ctypes.c_ulong, "flags" : WATCHER_FIXED_SIZE, "initval" : 0, "asntype" : ASN_COUNTER } @VarTypeClass def Counter64(self, initval = None, oidstr = None, writable = True, context = ""): return { "ctype" : counter64, "flags" : WATCHER_FIXED_SIZE, "initval" : 0, "asntype" : ASN_COUNTER64 } @VarTypeClass def TimeTicks(self, initval = None, oidstr = None, writable = True, context = ""): return { "ctype" : ctypes.c_ulong, "flags" : WATCHER_FIXED_SIZE, "initval" : 0, "asntype" : ASN_TIMETICKS } # Note we can't use ctypes.c_char_p here since that creates an immutable # type and net-snmp _can_ modify the buffer (unless writable is False). # Also note that while net-snmp 5.5 introduced a WATCHER_SIZE_STRLEN flag, # we have to stick to WATCHER_MAX_SIZE for now to support net-snmp 5.4.x # (used eg. in SLES 11 SP2 and Ubuntu 12.04 LTS). @VarTypeClass def OctetString(self, initval = None, oidstr = None, writable = True, context = ""): return { "ctype" : ctypes.create_string_buffer, "flags" : WATCHER_MAX_SIZE, "max_size" : MAX_STRING_SIZE, "initval" : "", "asntype" : ASN_OCTET_STR } # Whereas an OctetString can contain UTF-8 encoded characters, a # DisplayString is restricted to ASCII characters only. @VarTypeClass def DisplayString(self, initval = None, oidstr = None, writable = True, context = ""): return { "ctype" : ctypes.create_string_buffer, "flags" : WATCHER_MAX_SIZE, "max_size" : MAX_STRING_SIZE, "initval" : "", "asntype" : ASN_OCTET_STR } # IP addresses are stored as unsigned integers, but the Python interface # should use strings. So we need a special class. def IpAddress(self, initval = "0.0.0.0", oidstr = None, writable = True, context = ""): agent = self class IpAddress(object): def __init__(self): self._flags = WATCHER_FIXED_SIZE self._asntype = ASN_IPADDRESS self._cvar = ctypes.c_uint(0) self._data_size = ctypes.sizeof(self._cvar) self._max_size = self._data_size self.update(initval) if oidstr: # Prepare the netsnmp_handler_registration structure. handler_reginfo = agent._prepareRegistration(oidstr, writable) handler_reginfo.contents.contextName = b(context) # Create the netsnmp_watcher_info structure. watcher = libnsX.netsnmp_create_watcher_info( self.cref(), ctypes.sizeof(self._cvar), ASN_IPADDRESS, WATCHER_FIXED_SIZE ) watcher._maxsize = ctypes.sizeof(self._cvar) # Register handler and watcher with net-snmp. result = libnsX.netsnmp_register_watched_instance( handler_reginfo, watcher ) if result != 0: raise netsnmpAgentException("Error registering variable with net-snmp!") # Finally, we keep track of all registered SNMP objects for the # getRegistered() method. agent._objs[context][oidstr] = self def value(self): # Get string representation of IP address. return socket.inet_ntoa( struct.pack("I", self._cvar.value) ) def cref(self, **kwargs): # Due to an unfixed Net-SNMP issue (see # https://sourceforge.net/p/net-snmp/bugs/2136/) we have # to convert the value to host byte order if it shall be # used as table index. if kwargs.get("is_table_index", False) == False: return ctypes.byref(self._cvar) else: _cidx = ctypes.c_uint(0) _cidx.value = struct.unpack("I", struct.pack("!I", self._cvar.value))[0] return ctypes.byref(_cidx) def update(self, val): # Convert dotted decimal IP address string to ctypes # unsigned int in network byte order. self._cvar.value = struct.unpack( "I", socket.inet_aton(val) )[0] # Return an instance of the just-defined class to the agent return IpAddress() def Table(self, oidstr, indexes, columns, counterobj = None, extendable = False, context = ""): agent = self # Define a Python class to provide access to the table. class Table(object): def __init__(self, oidstr, idxobjs, coldefs, counterobj, extendable, context): # Create a netsnmp_table_data_set structure, representing both # the table definition and the data stored inside it. We use the # oidstr as table name. self._dataset = libnsX.netsnmp_create_table_data_set( ctypes.c_char_p(b(oidstr)) ) # Define the table row's indexes for idxobj in idxobjs: libnsX.netsnmp_table_dataset_add_index( self._dataset, idxobj._asntype ) # Define the table's columns and their default values for coldef in coldefs: colno = coldef[0] defobj = coldef[1] writable = coldef[2] if len(coldef) > 2 \ else 0 result = libnsX.netsnmp_table_set_add_default_row( self._dataset, colno, defobj._asntype, writable, defobj.cref(), defobj._data_size ) if result != SNMPERR_SUCCESS: raise netsnmpAgentException( "netsnmp_table_set_add_default_row() failed with " "error code {0}!".format(result) ) # Register handler and table_data_set with net-snmp. self._handler_reginfo = agent._prepareRegistration( oidstr, extendable ) self._handler_reginfo.contents.contextName = b(context) result = libnsX.netsnmp_register_table_data_set( self._handler_reginfo, self._dataset, None ) if result != SNMP_ERR_NOERROR: raise netsnmpAgentException( "Error code {0} while registering table with " "net-snmp!".format(result) ) # Finally, we keep track of all registered SNMP objects for the # getRegistered() method. agent._objs[context][oidstr] = self # If "counterobj" was specified, use it to track the number # of table rows if counterobj: counterobj.update(0) self._counterobj = counterobj def addRow(self, idxobjs): dataset = self._dataset # Define a Python class to provide access to the table row. class TableRow(object): def __init__(self, idxobjs): # Create the netsnmp_table_set_storage structure for # this row. self._table_row = libnsX.netsnmp_table_data_set_create_row_from_defaults( dataset.contents.default_row ) # Add the indexes for idxobj in idxobjs: result = libnsa.snmp_varlist_add_variable( ctypes.pointer(self._table_row.contents.indexes), None, 0, idxobj._asntype, idxobj.cref(is_table_index=True), idxobj._data_size ) if result == None: raise netsnmpAgentException("snmp_varlist_add_variable() failed!") def setRowCell(self, column, snmpobj): result = libnsX.netsnmp_set_row_column( self._table_row, column, snmpobj._asntype, snmpobj.cref(), snmpobj._data_size ) if result != SNMPERR_SUCCESS: raise netsnmpAgentException("netsnmp_set_row_column() failed with error code {0}!".format(result)) row = TableRow(idxobjs) libnsX.netsnmp_table_dataset_add_row( dataset, # *table row._table_row # row ) if self._counterobj: self._counterobj.update(self._counterobj.value() + 1) return row def value(self): # Because tables are more complex than scalar variables, we # return a dictionary representing the table's structure and # contents instead of a simple string. retdict = {} # The first entry will contain the defined columns, their types # and their defaults, if set. We use array index 0 since it's # impossible for SNMP tables to have a row with that index. retdict[0] = {} col = self._dataset.contents.default_row while bool(col): retdict[0][int(col.contents.column)] = {} asntypes = { ASN_INTEGER: "Integer", ASN_OCTET_STR: "OctetString", ASN_IPADDRESS: "IPAddress", ASN_COUNTER: "Counter32", ASN_COUNTER64: "Counter64", ASN_UNSIGNED: "Unsigned32", ASN_TIMETICKS: "TimeTicks" } retdict[0][int(col.contents.column)]["type"] = asntypes[col.contents.type] if bool(col.contents.data): if col.contents.type == ASN_OCTET_STR: retdict[0][int(col.contents.column)]["value"] = u(ctypes.string_at(col.contents.data.string, col.contents.data_len)) elif col.contents.type == ASN_IPADDRESS: uint_value = ctypes.cast( (ctypes.c_int*1)(col.contents.data.integer.contents.value), ctypes.POINTER(ctypes.c_uint) ).contents.value retdict[0][int(col.contents.column)]["value"] = socket.inet_ntoa(struct.pack("I", uint_value)) else: retdict[0][int(col.contents.column)]["value"] = col.contents.data.integer.contents.value col = col.contents.next # Next we iterate over the table's rows, creating a dictionary # entry for each row after that row's index. row = self._dataset.contents.table.contents.first_row while bool(row): # We want to return the row index in the same way it is # shown when using "snmptable", eg. "aa" instead of 2.97.97. # This conversion is actually quite complicated (see # net-snmp's sprint_realloc_objid() in snmplib/mib.c and # get*_table_entries() in apps/snmptable.c for details). # All code below assumes eg. that the OID output format was # not changed. # snprint_objid() below requires a _full_ OID whereas the # table row contains only the current row's identifer. # Unfortunately, net-snmp does not have a ready function to # get the full OID. The following code was modelled after # similar code in netsnmp_table_data_build_result(). fulloid = ctypes.cast( ctypes.create_string_buffer( MAX_OID_LEN * ctypes.sizeof(c_oid) ), c_oid_p ) # Registered OID rootoidlen = self._handler_reginfo.contents.rootoid_len for i in range(0, rootoidlen): fulloid[i] = self._handler_reginfo.contents.rootoid[i] # Entry fulloid[rootoidlen] = 1 # Fake the column number. Unlike the table_data and # table_data_set handlers, we do not have one here. No # biggie, using a fixed value will do for our purposes as # we'll do away with anything left of the first dot below. fulloid[rootoidlen + 1] = 2 # Index data indexoidlen = row.contents.index_oid_len for i in range(0, indexoidlen): fulloid[rootoidlen + 2 + i] = row.contents.index_oid[i] # Convert the full OID to its string representation oidcstr = ctypes.create_string_buffer(MAX_OID_LEN) libnsa.snprint_objid( oidcstr, MAX_OID_LEN, fulloid, rootoidlen + 2 + indexoidlen ) # And finally do away with anything left of the first dot # so we keep the row index only indices = oidcstr.value.split(b".", 1)[1] # If it's a string, remove the double quotes. If it's a # string containing an integer, make it one try: indices = int(indices) except ValueError: indices = u(indices.replace(b'"', b'')) # Finally, iterate over all columns for this row and add # stored data, if present retdict[indices] = {} data = ctypes.cast(row.contents.data, ctypes.POINTER(netsnmp_table_data_set_storage)) while bool(data): if bool(data.contents.data): if data.contents.type == ASN_OCTET_STR: retdict[indices][int(data.contents.column)] = u(ctypes.string_at(data.contents.data.string, data.contents.data_len)) elif data.contents.type == ASN_COUNTER64: retdict[indices][int(data.contents.column)] = data.contents.data.counter64.contents.value elif data.contents.type == ASN_IPADDRESS: uint_value = ctypes.cast((ctypes.c_int*1)( data.contents.data.integer.contents.value), ctypes.POINTER(ctypes.c_uint) ).contents.value retdict[indices][int(data.contents.column)] = socket.inet_ntoa(struct.pack("I", uint_value)) else: retdict[indices][int(data.contents.column)] = data.contents.data.integer.contents.value else: retdict[indices] += {} data = data.contents.next row = row.contents.next return retdict def clear(self): row = self._dataset.contents.table.contents.first_row while bool(row): nextrow = row.contents.next libnsX.netsnmp_table_dataset_remove_and_delete_row( self._dataset, row ) row = nextrow if self._counterobj: self._counterobj.update(0) # Return an instance of the just-defined class to the agent return Table(oidstr, indexes, columns, counterobj, extendable, context) def getContexts(self): """ Returns the defined contexts. """ return self._objs.keys() def getRegistered(self, context = ""): """ Returns a dictionary with the currently registered SNMP objects. Returned is a dictionary objects for the specified "context", which defaults to the default context. """ myobjs = {} try: # Python 2.x objs_iterator = self._objs[context].iteritems() except AttributeError: # Python 3.x objs_iterator = self._objs[context].items() for oidstr, snmpobj in objs_iterator: myobjs[oidstr] = { "type": type(snmpobj).__name__, "value": snmpobj.value() } return dict(myobjs) def start(self): """ Starts the agent. Among other things, this means connecting to the master agent, if configured that way. """ if self._status != netsnmpAgentStatus.CONNECTED \ and self._status != netsnmpAgentStatus.RECONNECTING: self._status = netsnmpAgentStatus.FIRSTCONNECT libnsa.init_snmp(b(self.AgentName)) if self._status == netsnmpAgentStatus.CONNECTFAILED: msg = "Error connecting to snmpd instance at \"{0}\" -- " \ "incorrect \"MasterSocket\" or snmpd not running?" msg = msg.format(self.MasterSocket) raise netsnmpAgentException(msg) def check_and_process(self, block=True): """ Processes incoming SNMP requests. If optional "block" argument is True (default), the function will block until a SNMP packet is received. """ return libnsa.agent_check_and_process(int(bool(block))) def shutdown(self): libnsa.snmp_shutdown(b(self.AgentName)) # Unfortunately we can't safely call shutdown_agent() for the time # being. All net-snmp versions up to and including 5.7.3 are unable # to do proper cleanup and cause issues such as double free()s so that # one effectively has to rely on the OS to release resources. #libnsa.shutdown_agent() class netsnmpAgentException(Exception): pass netsnmpagent-0.6.0/LICENSE0000644000175000001440000001674312722265100015337 0ustar piefusers00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.