def __init__(self, host, port, raw_mibs, compiled_mibs): self.oid_mapping = {} self.databus_mediator = DatabusMediator(self.oid_mapping) # mapping between OID and databus keys # Create SNMP engine self.snmpEngine = engine.SnmpEngine() # Configure SNMP compiler mib_builder = self.snmpEngine.getMibBuilder() addMibCompiler(mib_builder, destination=compiled_mibs) mib_builder.getMibCompiler().addSources(FileReader(raw_mibs)) mib_builder.getMibCompiler().addSources( FtpReader("ftp.cisco.com", "/pub/mibs/v2/@mib@", 80)) # Transport setup udp_sock = gevent.socket.socket(gevent.socket.AF_INET, gevent.socket.SOCK_DGRAM) udp_sock.setsockopt(gevent.socket.SOL_SOCKET, gevent.socket.SO_BROADCAST, 1) udp_sock.bind((host, port)) self.server_port = udp_sock.getsockname()[1] # UDP over IPv4 self.addSocketTransport(self.snmpEngine, udp.domainName, udp_sock) # SNMPv1 config.addV1System(self.snmpEngine, "public-read", "public") # SNMPv3/USM setup # user: usr-md5-des, auth: MD5, priv DES config.addV3User( self.snmpEngine, "usr-md5-des", config.usmHMACMD5AuthProtocol, "authkey1", config.usmDESPrivProtocol, "privkey1", ) # user: usr-sha-none, auth: SHA, priv NONE config.addV3User(self.snmpEngine, "usr-sha-none", config.usmHMACSHAAuthProtocol, "authkey1") # user: usr-sha-aes128, auth: SHA, priv AES/128 config.addV3User( self.snmpEngine, "usr-sha-aes128", config.usmHMACSHAAuthProtocol, "authkey1", config.usmAesCfb128Protocol, "privkey1", ) # Allow full MIB access for each user at VACM config.addVacmUser( self.snmpEngine, 1, "public-read", "noAuthNoPriv", readSubTree=(1, 3, 6, 1, 2, 1), writeSubTree=(1, 3, 6, 1, 2, 1), ) config.addVacmUser( self.snmpEngine, 2, "public-read", "noAuthNoPriv", readSubTree=(1, 3, 6, 1, 2, 1), writeSubTree=(1, 3, 6, 1, 2, 1), ) config.addVacmUser( self.snmpEngine, 3, "usr-md5-des", "authPriv", readSubTree=(1, 3, 6, 1, 2, 1), writeSubTree=(1, 3, 6, 1, 2, 1), ) config.addVacmUser( self.snmpEngine, 3, "usr-sha-none", "authNoPriv", readSubTree=(1, 3, 6, 1, 2, 1), writeSubTree=(1, 3, 6, 1, 2, 1), ) config.addVacmUser( self.snmpEngine, 3, "usr-sha-aes128", "authPriv", readSubTree=(1, 3, 6, 1, 2, 1), writeSubTree=(1, 3, 6, 1, 2, 1), ) # Get default SNMP context this SNMP engine serves snmpContext = context.SnmpContext(self.snmpEngine) # Register SNMP Applications at the SNMP engine for particular SNMP context self.resp_app_get = conpot_cmdrsp.c_GetCommandResponder( self.snmpEngine, snmpContext, self.databus_mediator, host, port) self.resp_app_set = conpot_cmdrsp.c_SetCommandResponder( self.snmpEngine, snmpContext, self.databus_mediator, host, port) self.resp_app_next = conpot_cmdrsp.c_NextCommandResponder( self.snmpEngine, snmpContext, self.databus_mediator, host, port) self.resp_app_bulk = conpot_cmdrsp.c_BulkCommandResponder( self.snmpEngine, snmpContext, self.databus_mediator, host, port)
def main(): variation_module = None parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument( '-v', '--version', action='version', version=utils.TITLE) parser.add_argument( '--quiet', action='store_true', help='Do not print out informational messages') parser.add_argument( '--debug', choices=pysnmp_debug.flagMap, action='append', type=str, default=[], help='Enable one or more categories of SNMP debugging.') parser.add_argument( '--debug-asn1', choices=pyasn1_debug.FLAG_MAP, action='append', type=str, default=[], help='Enable one or more categories of ASN.1 debugging.') parser.add_argument( '--logging-method', type=lambda x: x.split(':'), metavar='=<%s[:args]>]' % '|'.join(log.METHODS_MAP), default='stderr', help='Logging method.') parser.add_argument( '--log-level', choices=log.LEVELS_MAP, type=str, default='info', help='Logging level.') v1arch_group = parser.add_argument_group('SNMPv1/v2c parameters') v1arch_group.add_argument( '--protocol-version', choices=['1', '2c'], default='2c', help='SNMPv1/v2c protocol version') v1arch_group.add_argument( '--community', type=str, default='public', help='SNMP community name') v3arch_group = parser.add_argument_group('SNMPv3 parameters') v3arch_group.add_argument( '--v3-user', metavar='<STRING>', type=functools.partial(_parse_sized_string, min_length=1), help='SNMPv3 USM user (security) name') v3arch_group.add_argument( '--v3-auth-key', type=_parse_sized_string, help='SNMPv3 USM authentication key (must be > 8 chars)') v3arch_group.add_argument( '--v3-auth-proto', choices=AUTH_PROTOCOLS, type=lambda x: x.upper(), default='NONE', help='SNMPv3 USM authentication protocol') v3arch_group.add_argument( '--v3-priv-key', type=_parse_sized_string, help='SNMPv3 USM privacy (encryption) key (must be > 8 chars)') v3arch_group.add_argument( '--v3-priv-proto', choices=PRIV_PROTOCOLS, type=lambda x: x.upper(), default='NONE', help='SNMPv3 USM privacy (encryption) protocol') v3arch_group.add_argument( '--v3-context-engine-id', type=lambda x: univ.OctetString(hexValue=x[2:]), help='SNMPv3 context engine ID') v3arch_group.add_argument( '--v3-context-name', type=str, default='', help='SNMPv3 context engine ID') parser.add_argument( '--use-getbulk', action='store_true', help='Use SNMP GETBULK PDU for mass SNMP managed objects retrieval') parser.add_argument( '--getbulk-repetitions', type=int, default=25, help='Use SNMP GETBULK PDU for mass SNMP managed objects retrieval') endpoint_group = parser.add_mutually_exclusive_group(required=True) endpoint_group.add_argument( '--agent-udpv4-endpoint', type=endpoints.parse_endpoint, metavar='<[X.X.X.X]:NNNNN>', help='SNMP agent UDP/IPv4 address to pull simulation data ' 'from (name:port)') endpoint_group.add_argument( '--agent-udpv6-endpoint', type=functools.partial(endpoints.parse_endpoint, ipv6=True), metavar='<[X:X:..X]:NNNNN>', help='SNMP agent UDP/IPv6 address to pull simulation data ' 'from ([name]:port)') parser.add_argument( '--timeout', type=int, default=3, help='SNMP command response timeout (in seconds)') parser.add_argument( '--retries', type=int, default=3, help='SNMP command retries') parser.add_argument( '--start-object', metavar='<MIB::Object|OID>', type=_parse_mib_object, default=univ.ObjectIdentifier('1.3.6'), help='Drop all simulation data records prior to this OID specified ' 'as MIB object (MIB::Object) or OID (1.3.6.)') parser.add_argument( '--stop-object', metavar='<MIB::Object|OID>', type=functools.partial(_parse_mib_object, last=True), help='Drop all simulation data records after this OID specified ' 'as MIB object (MIB::Object) or OID (1.3.6.)') parser.add_argument( '--mib-source', dest='mib_sources', metavar='<URI|PATH>', action='append', type=str, default=['http://mibs.snmplabs.com/asn1/@mib@'], help='One or more URIs pointing to a collection of ASN.1 MIB files.' 'Optional "@mib@" token gets replaced with desired MIB module ' 'name during MIB search.') parser.add_argument( '--destination-record-type', choices=variation.RECORD_TYPES, default='snmprec', help='Produce simulation data with record of this type') parser.add_argument( '--output-file', metavar='<FILE>', type=str, help='SNMP simulation data file to write records to') parser.add_argument( '--continue-on-errors', metavar='<tolerance-level>', type=int, default=0, help='Keep on pulling SNMP data even if intermittent errors occur') variation_group = parser.add_argument_group( 'Simulation data variation options') parser.add_argument( '--variation-modules-dir', action='append', type=str, help='Search variation module by this path') variation_group.add_argument( '--variation-module', type=str, help='Pass gathered simulation data through this variation module') variation_group.add_argument( '--variation-module-options', type=str, default='', help='Variation module options') args = parser.parse_args() if args.debug: pysnmp_debug.setLogger(pysnmp_debug.Debug(*args.debug)) if args.debug_asn1: pyasn1_debug.setLogger(pyasn1_debug.Debug(*args.debug_asn1)) if args.output_file: ext = os.path.extsep ext += variation.RECORD_TYPES[args.destination_record_type].ext if not args.output_file.endswith(ext): args.output_file += ext record = variation.RECORD_TYPES[args.destination_record_type] args.output_file = record.open(args.output_file, 'wb') else: args.output_file = sys.stdout if sys.version_info >= (3, 0, 0): # binary mode write args.output_file = sys.stdout.buffer elif sys.platform == "win32": import msvcrt msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) # Catch missing params if args.protocol_version == '3': if not args.v3_user: sys.stderr.write('ERROR: --v3-user is missing\r\n') parser.print_usage(sys.stderr) return 1 if args.v3_priv_key and not args.v3_auth_key: sys.stderr.write('ERROR: --v3-auth-key is missing\r\n') parser.print_usage(sys.stderr) return 1 if AUTH_PROTOCOLS[args.v3_auth_proto] == config.usmNoAuthProtocol: if args.v3_auth_key: args.v3_auth_proto = 'MD5' else: if not args.v3_auth_key: sys.stderr.write('ERROR: --v3-auth-key is missing\r\n') parser.print_usage(sys.stderr) return 1 if PRIV_PROTOCOLS[args.v3_priv_proto] == config.usmNoPrivProtocol: if args.v3_priv_key: args.v3_priv_proto = 'DES' else: if not args.v3_priv_key: sys.stderr.write('ERROR: --v3-priv-key is missing\r\n') parser.print_usage(sys.stderr) return 1 proc_name = os.path.basename(sys.argv[0]) try: log.set_logger(proc_name, *args.logging_method, force=True) if args.log_level: log.set_level(args.log_level) except error.SnmpsimError as exc: sys.stderr.write('%s\r\n' % exc) parser.print_usage(sys.stderr) return 1 if args.use_getbulk and args.protocol_version == '1': log.info('will be using GETNEXT with SNMPv1!') args.use_getbulk = False # Load variation module if args.variation_module: for variation_modules_dir in ( args.variation_modules_dir or confdir.variation): log.info( 'Scanning "%s" directory for variation ' 'modules...' % variation_modules_dir) if not os.path.exists(variation_modules_dir): log.info('Directory "%s" does not exist' % variation_modules_dir) continue mod = os.path.join(variation_modules_dir, args.variation_module + '.py') if not os.path.exists(mod): log.info('Variation module "%s" not found' % mod) continue ctx = {'path': mod, 'moduleContext': {}} try: with open(mod) as fl: exec (compile(fl.read(), mod, 'exec'), ctx) except Exception as exc: log.error('Variation module "%s" execution failure: ' '%s' % (mod, exc)) return 1 variation_module = ctx log.info('Variation module "%s" loaded' % args.variation_module) break else: log.error('variation module "%s" not found' % args.variation_module) return 1 # SNMP configuration snmp_engine = engine.SnmpEngine() if args.protocol_version == '3': if args.v3_priv_key is None and args.v3_auth_key is None: secLevel = 'noAuthNoPriv' elif args.v3_priv_key is None: secLevel = 'authNoPriv' else: secLevel = 'authPriv' config.addV3User( snmp_engine, args.v3_user, AUTH_PROTOCOLS[args.v3_auth_proto], args.v3_auth_key, PRIV_PROTOCOLS[args.v3_priv_proto], args.v3_priv_key) log.info( 'SNMP version 3, Context EngineID: %s Context name: %s, SecurityName: %s, ' 'SecurityLevel: %s, Authentication key/protocol: %s/%s, Encryption ' '(privacy) key/protocol: ' '%s/%s' % ( args.v3_context_engine_id and args.v3_context_engine_id.prettyPrint() or '<default>', args.v3_context_name and args.v3_context_name.prettyPrint() or '<default>', args.v3_user, secLevel, args.v3_auth_key is None and '<NONE>' or args.v3_auth_key, args.v3_auth_proto, args.v3_priv_key is None and '<NONE>' or args.v3_priv_key, args.v3_priv_proto)) else: args.v3_user = '******' secLevel = 'noAuthNoPriv' config.addV1System(snmp_engine, args.v3_user, args.community) log.info( 'SNMP version %s, Community name: ' '%s' % (args.protocol_version, args.community)) config.addTargetParams( snmp_engine, 'pms', args.v3_user, secLevel, VERSION_MAP[args.protocol_version]) if args.agent_udpv6_endpoint: config.addSocketTransport( snmp_engine, udp6.domainName, udp6.Udp6SocketTransport().openClientMode()) config.addTargetAddr( snmp_engine, 'tgt', udp6.domainName, args.agent_udpv6_endpoint, 'pms', args.timeout * 100, args.retries) log.info('Querying UDP/IPv6 agent at [%s]:%s' % args.agent_udpv6_endpoint) elif args.agent_udpv4_endpoint: config.addSocketTransport( snmp_engine, udp.domainName, udp.UdpSocketTransport().openClientMode()) config.addTargetAddr( snmp_engine, 'tgt', udp.domainName, args.agent_udpv4_endpoint, 'pms', args.timeout * 100, args.retries) log.info('Querying UDP/IPv4 agent at %s:%s' % args.agent_udpv4_endpoint) log.info('Agent response timeout: %d secs, retries: ' '%s' % (args.timeout, args.retries)) if (isinstance(args.start_object, ObjectIdentity) or isinstance(args.stop_object, ObjectIdentity)): compiler.addMibCompiler( snmp_engine.getMibBuilder(), sources=args.mib_sources) mib_view_controller = view.MibViewController( snmp_engine.getMibBuilder()) try: if isinstance(args.start_object, ObjectIdentity): args.start_object.resolveWithMib(mib_view_controller) if isinstance(args.stop_object, ObjectIdentity): args.stop_object.resolveWithMib(mib_view_controller) except PySnmpError as exc: sys.stderr.write('ERROR: %s\r\n' % exc) return 1 # Variation module initialization if variation_module: log.info('Initializing variation module...') for x in ('init', 'record', 'shutdown'): if x not in variation_module: log.error('missing "%s" handler at variation module ' '"%s"' % (x, args.variation_module)) return 1 try: handler = variation_module['init'] handler(snmpEngine=snmp_engine, options=args.variation_module_options, mode='recording', startOID=args.start_object, stopOID=args.stop_object) except Exception as exc: log.error( 'Variation module "%s" initialization FAILED: ' '%s' % (args.variation_module, exc)) else: log.info( 'Variation module "%s" initialization OK' % args.variation_module) data_file_handler = variation.RECORD_TYPES[args.destination_record_type] # SNMP worker def cbFun(snmp_engine, send_request_handle, error_indication, error_status, error_index, var_bind_table, cb_ctx): if error_indication and not cb_ctx['retries']: cb_ctx['errors'] += 1 log.error('SNMP Engine error: %s' % error_indication) return # SNMPv1 response may contain noSuchName error *and* SNMPv2c exception, # so we ignore noSuchName error here if error_status and error_status != 2 or error_indication: log.error( 'Remote SNMP error %s' % ( error_indication or error_status.prettyPrint())) if cb_ctx['retries']: try: next_oid = var_bind_table[-1][0][0] except IndexError: next_oid = cb_ctx['lastOID'] else: log.error('Failed OID: %s' % next_oid) # fuzzy logic of walking a broken OID if len(next_oid) < 4: pass elif (args.continue_on_errors - cb_ctx['retries']) * 10 / args.continue_on_errors > 5: next_oid = next_oid[:-2] + (next_oid[-2] + 1,) elif next_oid[-1]: next_oid = next_oid[:-1] + (next_oid[-1] + 1,) else: next_oid = next_oid[:-2] + (next_oid[-2] + 1, 0) cb_ctx['retries'] -= 1 cb_ctx['lastOID'] = next_oid log.info( 'Retrying with OID %s (%s retries left)' '...' % (next_oid, cb_ctx['retries'])) # initiate another SNMP walk iteration if args.use_getbulk: cmd_gen.sendVarBinds( snmp_engine, 'tgt', args.v3_context_engine_id, args.v3_context_name, 0, args.getbulk_repetitions, [(next_oid, None)], cbFun, cb_ctx) else: cmd_gen.sendVarBinds( snmp_engine, 'tgt', args.v3_context_engine_id, args.v3_context_name, [(next_oid, None)], cbFun, cb_ctx) cb_ctx['errors'] += 1 return if args.continue_on_errors != cb_ctx['retries']: cb_ctx['retries'] += 1 if var_bind_table and var_bind_table[-1] and var_bind_table[-1][0]: cb_ctx['lastOID'] = var_bind_table[-1][0][0] stop_flag = False # Walk var-binds for var_bind_row in var_bind_table: for oid, value in var_bind_row: # EOM if args.stop_object and oid >= args.stop_object: stop_flag = True # stop on out of range condition elif (value is None or value.tagSet in (rfc1905.NoSuchObject.tagSet, rfc1905.NoSuchInstance.tagSet, rfc1905.EndOfMibView.tagSet)): stop_flag = True # remove value enumeration if value.tagSet == rfc1902.Integer32.tagSet: value = rfc1902.Integer32(value) if value.tagSet == rfc1902.Unsigned32.tagSet: value = rfc1902.Unsigned32(value) if value.tagSet == rfc1902.Bits.tagSet: value = rfc1902.OctetString(value) # Build .snmprec record context = { 'origOid': oid, 'origValue': value, 'count': cb_ctx['count'], 'total': cb_ctx['total'], 'iteration': cb_ctx['iteration'], 'reqTime': cb_ctx['reqTime'], 'args.start_object': args.start_object, 'stopOID': args.stop_object, 'stopFlag': stop_flag, 'variationModule': variation_module } try: line = data_file_handler.format(oid, value, **context) except error.MoreDataNotification as exc: cb_ctx['count'] = 0 cb_ctx['iteration'] += 1 more_data_notification = exc if 'period' in more_data_notification: log.info( '%s OIDs dumped, waiting %.2f sec(s)' '...' % (cb_ctx['total'], more_data_notification['period'])) time.sleep(more_data_notification['period']) # initiate another SNMP walk iteration if args.use_getbulk: cmd_gen.sendVarBinds( snmp_engine, 'tgt', args.v3_context_engine_id, args.v3_context_name, 0, args.getbulk_repetitions, [(args.start_object, None)], cbFun, cb_ctx) else: cmd_gen.sendVarBinds( snmp_engine, 'tgt', args.v3_context_engine_id, args.v3_context_name, [(args.start_object, None)], cbFun, cb_ctx) stop_flag = True # stop current iteration except error.NoDataNotification: pass except error.SnmpsimError as exc: log.error(exc) continue else: args.output_file.write(line) cb_ctx['count'] += 1 cb_ctx['total'] += 1 if cb_ctx['count'] % 100 == 0: log.info('OIDs dumped: %s/%s' % ( cb_ctx['iteration'], cb_ctx['count'])) # Next request time cb_ctx['reqTime'] = time.time() # Continue walking return not stop_flag cb_ctx = { 'total': 0, 'count': 0, 'errors': 0, 'iteration': 0, 'reqTime': time.time(), 'retries': args.continue_on_errors, 'lastOID': args.start_object } if args.use_getbulk: cmd_gen = cmdgen.BulkCommandGenerator() cmd_gen.sendVarBinds( snmp_engine, 'tgt', args.v3_context_engine_id, args.v3_context_name, 0, args.getbulk_repetitions, [(args.start_object, rfc1902.Null(''))], cbFun, cb_ctx) else: cmd_gen = cmdgen.NextCommandGenerator() cmd_gen.sendVarBinds( snmp_engine, 'tgt', args.v3_context_engine_id, args.v3_context_name, [(args.start_object, rfc1902.Null(''))], cbFun, cb_ctx) log.info( 'Sending initial %s request for %s (stop at %s)' '....' % (args.use_getbulk and 'GETBULK' or 'GETNEXT', args.start_object, args.stop_object or '<end-of-mib>')) started = time.time() try: snmp_engine.transportDispatcher.runDispatcher() except KeyboardInterrupt: log.info('Shutting down process...') finally: if variation_module: log.info('Shutting down variation module ' '%s...' % args.variation_module) try: handler = variation_module['shutdown'] handler(snmpEngine=snmp_engine, options=args.variation_module_options, mode='recording') except Exception as exc: log.error( 'Variation module %s shutdown FAILED: ' '%s' % (args.variation_module, exc)) else: log.info( 'Variation module %s shutdown OK' % args.variation_module) snmp_engine.transportDispatcher.closeDispatcher() started = time.time() - started cb_ctx['total'] += cb_ctx['count'] log.info( 'OIDs dumped: %s, elapsed: %.2f sec, rate: %.2f OIDs/sec, errors: ' '%d' % (cb_ctx['total'], started, started and cb_ctx['count'] // started or 0, cb_ctx['errors'])) args.output_file.flush() args.output_file.close() return cb_ctx.get('errors', 0) and 1 or 0
* include managed object information 1.3.6.1.2.1.1.5.0 = 'system name' Functionally similar to: | $ snmptrap -v3 -l authPriv -u usr-md5-des -A authkey1 -X privkey1 -e 8000000001020304 demo.snmplabs.com 0 1.3.6.1.6.3.1.1.5.1 1.3.6.1.2.1.1.1.0 s "my system" """# from pysnmp.entity import engine, config from pysnmp.carrier.asyncore.dgram import udp from pysnmp.entity.rfc3413 import ntforg from pysnmp.proto.api import v2c # Create SNMP engine instance with specific (and locally unique) # SnmpEngineId -- it must also be known to the receiving party # and configured at its VACM users table. snmpEngine = engine.SnmpEngine(snmpEngineID=v2c.OctetString( hexValue='8000000001020304')) # Add USM user config.addV3User(snmpEngine, 'usr-md5-des', config.USM_AUTH_HMAC96_MD5, 'authkey1', config.USM_PRIV_CBC56_DES, 'privkey1') config.addTargetParams(snmpEngine, 'my-creds', 'usr-md5-des', 'authPriv') # Setup transport endpoint and bind it with security settings yielding # a target name config.addTransport(snmpEngine, udp.DOMAIN_NAME, udp.UdpSocketTransport().openClientMode()) config.addTargetAddr(snmpEngine, 'my-nms', udp.DOMAIN_NAME, ('104.236.166.95', 162),
(pluginId, sys.exc_info()[1])) sys.exit(-1) for configEntryPath in cfgTree.getPathsToAttr('snmp-credentials-id'): credId = cfgTree.getAttrValue('snmp-credentials-id', *configEntryPath) configKey = [] log.msg('configuring snmp-credentials %s (at %s)...' % (credId, '.'.join(configEntryPath))) engineId = cfgTree.getAttrValue('snmp-engine-id', *configEntryPath) if engineId in engineIdMap: snmpEngine, snmpContext, snmpEngineMap = engineIdMap[engineId] log.msg('using engine-id %s' % snmpEngine.snmpEngineID.prettyPrint()) else: snmpEngine = engine.SnmpEngine(snmpEngineID=engineId) snmpContext = context.SnmpContext(snmpEngine) snmpEngineMap = {'transportDomain': {}, 'securityName': {}} snmpEngine.observer.registerObserver(requestObserver, 'rfc3412.receiveMessage:request', cbCtx=gCurrentRequestContext) CommandResponder(snmpEngine, snmpContext) engineIdMap[engineId] = snmpEngine, snmpContext, snmpEngineMap log.msg('new engine-id %s' % snmpEngine.snmpEngineID.prettyPrint()) configKey.append(str(snmpEngine.snmpEngineID))
def main(): class LogString(LazyLogString): GROUPINGS = [ ['callflow-id'], ['trunk-id'], [ 'server-snmp-engine-id', 'server-snmp-transport-domain', 'server-snmp-peer-address', 'server-snmp-peer-port', 'server-snmp-bind-address', 'server-snmp-bind-port', 'server-snmp-security-model', 'server-snmp-security-level', 'server-snmp-security-name', 'server-snmp-context-engine-id', 'server-snmp-context-name', 'server-snmp-pdu', 'server-snmp-entity-id' ], [ 'server-snmp-credentials-id', 'server-snmp-context-id', 'server-snmp-content-id', 'server-snmp-peer-id', 'server-classification-id' ], [ 'snmp-peer-id', 'snmp-bind-address', 'snmp-bind-port', 'snmp-peer-address', 'snmp-peer-port', 'snmp-context-engine-id', 'snmp-context-name', 'snmp-pdu' ], ] FORMATTERS = { 'server-snmp-pdu': LazyLogString.prettyVarBinds, 'snmp-pdu': LazyLogString.prettyVarBinds, } def snmpCbFun(snmpEngine, sendRequestHandle, errorIndication, rspPDU, cbCtx): trunkId, msgId, trunkReq, pluginIdList, reqCtx = cbCtx trunkRsp = { 'callflow-id': trunkReq['callflow-id'], 'snmp-pdu': rspPDU, } logCtx = LogString(trunkRsp) if errorIndication: log.info('received SNMP error-indication "%s"' % errorIndication, ctx=logCtx) trunkRsp['error-indication'] = errorIndication if rspPDU: reqPdu = trunkReq['server-snmp-pdu'] for pluginId in pluginIdList: if reqPdu.tagSet in rfc3411.notificationClassPDUs: st, rspPDU = pluginManager.processNotificationResponse( pluginId, snmpEngine, rspPDU, trunkReq, reqCtx) elif reqPdu.tagSet not in rfc3411.unconfirmedClassPDUs: st, rspPDU = pluginManager.processCommandResponse( pluginId, snmpEngine, rspPDU, trunkReq, reqCtx) else: log.error('ignoring unsupported PDU', ctx=logCtx) break if st == status.BREAK: log.debug('plugin %s inhibits other plugins' % pluginId, ctx=logCtx) break elif st == status.DROP: log.debug( 'received SNMP %s, plugin %s muted response' % (errorIndication and 'error' or 'response', pluginId), ctx=logCtx) trunkRsp['snmp-pdu'] = None break try: trunkingManager.sendRsp(trunkId, msgId, trunkRsp) except SnmpfwdError: log.error('received SNMP %s message, trunk message not sent "%s"' % (msgId, sys.exc_info()[1]), ctx=logCtx) return log.debug('received SNMP %s message, forwarded as trunk message #%s' % (errorIndication and 'error' or 'response', msgId), ctx=logCtx) # # The following needs proper support in pysnmp. Meanwhile - monkey patching! # def makeTargetAddrOverride(targetAddr): endpoints = [] def getTargetAddr(snmpEngine, snmpTargetAddrName): addrInfo = list(targetAddr(snmpEngine, snmpTargetAddrName)) if endpoints: peerAddr, bindAddr = endpoints.pop(), endpoints.pop() try: addrInfo[1] = addrInfo[1].__class__( peerAddr).setLocalAddress(bindAddr) except Exception: raise PySnmpError( 'failure replacing bind address %s -> %s for transport ' 'domain %s' % (addrInfo[1], bindAddr, addrInfo[0])) return addrInfo def updateEndpoints(bindAddr, peerAddr): endpoints.extend((bindAddr, peerAddr)) return getTargetAddr, updateEndpoints lcd.getTargetAddr, updateEndpoints = makeTargetAddrOverride( lcd.getTargetAddr) def trunkCbFun(trunkId, msgId, trunkReq): for key in tuple(trunkReq): if key != 'callflow-id': trunkReq['server-' + key] = trunkReq[key] del trunkReq[key] trunkReq['trunk-id'] = trunkId k = [ str(x) for x in (trunkReq['server-snmp-engine-id'], trunkReq['server-snmp-transport-domain'], trunkReq['server-snmp-peer-address'] + ':' + str(trunkReq['server-snmp-peer-port']), trunkReq['server-snmp-bind-address'] + ':' + str(trunkReq['server-snmp-bind-port']), trunkReq['server-snmp-security-model'], trunkReq['server-snmp-security-level'], trunkReq['server-snmp-security-name'], trunkReq['server-snmp-context-engine-id'], trunkReq['server-snmp-context-name']) ] k.append(snmpPduTypesMap.get(trunkReq['server-snmp-pdu'].tagSet, '?')) k.append('|'.join([ str(x[0]) for x in v2c.apiPDU.getVarBinds(trunkReq['server-snmp-pdu']) ])) k = '#'.join(k) for x, y in origCredIdList: if y.match(k): origPeerId = trunkReq[ 'server-snmp-entity-id'] = macro.expandMacro(x, trunkReq) break else: origPeerId = None k = [ str(x) for x in (trunkReq['server-snmp-credentials-id'], trunkReq['server-snmp-context-id'], trunkReq['server-snmp-content-id'], trunkReq['server-snmp-peer-id']) ] k = '#'.join(k) for x, y in srvClassIdList: if y.match(k): srvClassId = trunkReq[ 'server-classification-id'] = macro.expandMacro( x, trunkReq) break else: srvClassId = None logCtx = LogString(trunkReq) errorIndication = None peerIdList = routingMap.get( (origPeerId, srvClassId, macro.expandMacro(trunkId, trunkReq))) if not peerIdList: log.error('unroutable trunk message #%s' % msgId, ctx=logCtx) errorIndication = 'no route to SNMP peer configured' cbCtx = trunkId, msgId, trunkReq, (), {} if errorIndication: snmpCbFun(None, None, errorIndication, None, cbCtx) return pluginIdList = pluginIdMap.get( (origPeerId, srvClassId, macro.expandMacro(trunkId, trunkReq))) for peerId in peerIdList: peerId = macro.expandMacro(peerId, trunkReq) trunkReqCopy = trunkReq.copy() (snmpEngine, contextEngineId, contextName, bindAddr, bindAddrMacro, peerAddr, peerAddrMacro) = peerIdMap[peerId] if bindAddrMacro: bindAddr = macro.expandMacro(bindAddrMacro, trunkReqCopy), 0 if peerAddrMacro: peerAddr = macro.expandMacro(peerAddrMacro, trunkReqCopy), 161 if bindAddr and peerAddr: updateEndpoints(bindAddr, peerAddr) trunkReqCopy['snmp-peer-id'] = peerId trunkReqCopy['snmp-context-engine-id'] = contextEngineId trunkReqCopy['snmp-context-name'] = contextName trunkReqCopy['snmp-bind-address'], trunkReqCopy[ 'snmp-bind-port'] = bindAddr trunkReqCopy['snmp-peer-address'], trunkReqCopy[ 'snmp-peer-port'] = peerAddr logCtx.update(trunkReqCopy) pdu = trunkReqCopy['server-snmp-pdu'] if pluginIdList: reqCtx = {} cbCtx = trunkId, msgId, trunkReqCopy, pluginIdList, reqCtx for pluginNum, pluginId in enumerate(pluginIdList): if pdu.tagSet in rfc3411.notificationClassPDUs: st, pdu = pluginManager.processNotificationRequest( pluginId, snmpEngine, pdu, trunkReqCopy, reqCtx) elif pdu.tagSet not in rfc3411.unconfirmedClassPDUs: st, pdu = pluginManager.processCommandRequest( pluginId, snmpEngine, pdu, trunkReqCopy, reqCtx) else: log.error('ignoring unsupported PDU', ctx=logCtx) break if st == status.BREAK: log.debug('plugin %s inhibits other plugins' % pluginId, ctx=logCtx) cbCtx = trunkId, msgId, trunkReqCopy, pluginIdList[: pluginNum], reqCtx break elif st == status.DROP: log.debug( 'received trunk message #%s, plugin %s muted request' % (msgId, pluginId), ctx=logCtx) snmpCbFun(snmpEngine, None, None, None, cbCtx) return elif st == status.RESPOND: log.debug( 'received trunk message #%s, plugin %s forced immediate response' % (msgId, pluginId), ctx=logCtx) snmpCbFun(snmpEngine, None, None, pdu, cbCtx) return snmpMessageSent = False if pdu.tagSet in rfc3411.notificationClassPDUs: if pdu.tagSet in rfc3411.unconfirmedClassPDUs: try: notificationOriginator.sendPdu( snmpEngine, peerId, macro.expandMacro(contextEngineId, trunkReq), macro.expandMacro(contextName, trunkReq), pdu) snmpMessageSent = True except PySnmpError: errorIndication = 'failure sending SNMP notification' log.error('trunk message #%s, SNMP error: %s' % (msgId, sys.exc_info()[1]), ctx=logCtx) else: errorIndication = None # respond to trunk right away snmpCbFun(snmpEngine, None, errorIndication, None, cbCtx) else: try: notificationOriginator.sendPdu( snmpEngine, peerId, macro.expandMacro(contextEngineId, trunkReq), macro.expandMacro(contextName, trunkReq), pdu, snmpCbFun, cbCtx) snmpMessageSent = True except PySnmpError: log.error('trunk message #%s, SNMP error: %s' % (msgId, sys.exc_info()[1]), ctx=logCtx) elif pdu.tagSet not in rfc3411.unconfirmedClassPDUs: try: commandGenerator.sendPdu( snmpEngine, peerId, macro.expandMacro(contextEngineId, trunkReq), macro.expandMacro(contextName, trunkReq), pdu, snmpCbFun, cbCtx) snmpMessageSent = True except PySnmpError: errorIndication = 'failure sending SNMP command' log.error('trunk message #%s, SNMP error: %s' % (msgId, sys.exc_info()[1]), ctx=logCtx) # respond to trunk right away snmpCbFun(snmpEngine, None, errorIndication, None, cbCtx) else: log.error('ignoring unsupported PDU', ctx=logCtx) if snmpMessageSent: log.debug( 'received trunk message #%s, forwarded as SNMP message' % msgId, ctx=logCtx) # # Main script body starts here # helpMessage = """\ Usage: %s [--help] [--version ] [--debug-snmp=<%s>] [--debug-asn1=<%s>] [--daemonize] [--process-user=<uname>] [--process-group=<gname>] [--pid-file=<file>] [--logging-method=<%s[:args>]>] [--log-level=<%s>] [--config-file=<file>]""" % (sys.argv[0], '|'.join([ x for x in getattr(pysnmp_debug, 'FLAG_MAP', getattr(pysnmp_debug, 'flagMap', ())) if x != 'mibview' ]), '|'.join([ x for x in getattr(pyasn1_debug, 'FLAG_MAP', getattr(pyasn1_debug, 'flagMap', ())) ]), '|'.join(log.methodsMap), '|'.join(log.levelsMap)) try: opts, params = getopt.getopt(sys.argv[1:], 'hv', [ 'help', 'version', 'debug=', 'debug-snmp=', 'debug-asn1=', 'daemonize', 'process-user='******'process-group=', 'pid-file=', 'logging-method=', 'log-level=', 'config-file=' ]) except Exception: sys.stderr.write('ERROR: %s\r\n%s\r\n' % (sys.exc_info()[1], helpMessage)) return if params: sys.stderr.write('ERROR: extra arguments supplied %s\r\n%s\r\n' % (params, helpMessage)) return pidFile = '' cfgFile = CONFIG_FILE foregroundFlag = True procUser = procGroup = None loggingMethod = ['stderr'] loggingLevel = None for opt in opts: if opt[0] == '-h' or opt[0] == '--help': sys.stderr.write("""\ Synopsis: SNMP Proxy Forwarder: client part. Receives SNMP PDUs via one or many encrypted trunks established with the Forwarder's Agent part(s) running elsewhere and routes PDUs to built-in SNMP Managers for further transmission towards SNMP Agents. Can implement complex routing and protocol conversion logic through analyzing parts of SNMP messages and matching them against proxying rules. Documentation: http://snmplabs.com/snmpfwd/ %s """ % helpMessage) return if opt[0] == '-v' or opt[0] == '--version': import snmpfwd import pysnmp import pyasn1 sys.stderr.write("""\ SNMP Proxy Forwarder version %s, written by Ilya Etingof <*****@*****.**> Using foundation libraries: pysnmp %s, pyasn1 %s. Python interpreter: %s Software documentation and support at http://snmplabs.com/snmpfwd/ %s """ % (snmpfwd.__version__, hasattr(pysnmp, '__version__') and pysnmp.__version__ or 'unknown', hasattr(pyasn1, '__version__') and pyasn1.__version__ or 'unknown', sys.version, helpMessage)) return elif opt[0] == '--debug-snmp': pysnmp_debug.setLogger( pysnmp_debug.Debug(*opt[1].split(','), **dict(loggerName=PROGRAM_NAME + '.pysnmp'))) elif opt[0] == '--debug-asn1': pyasn1_debug.setLogger( pyasn1_debug.Debug(*opt[1].split(','), **dict(loggerName=PROGRAM_NAME + '.pyasn1'))) elif opt[0] == '--daemonize': foregroundFlag = False elif opt[0] == '--process-user': procUser = opt[1] elif opt[0] == '--process-group': procGroup = opt[1] elif opt[0] == '--pid-file': pidFile = opt[1] elif opt[0] == '--logging-method': loggingMethod = opt[1].split(':') elif opt[0] == '--log-level': loggingLevel = opt[1] elif opt[0] == '--config-file': cfgFile = opt[1] with daemon.PrivilegesOf(procUser, procGroup): try: log.setLogger(PROGRAM_NAME, *loggingMethod, **dict(force=True)) if loggingLevel: log.setLevel(loggingLevel) except SnmpfwdError: sys.stderr.write('%s\r\n%s\r\n' % (sys.exc_info()[1], helpMessage)) return try: cfgTree = cparser.Config().load(cfgFile) except SnmpfwdError: log.error('configuration parsing error: %s' % sys.exc_info()[1]) return if cfgTree.getAttrValue('program-name', '', default=None) != PROGRAM_NAME: log.error('config file %s does not match program name %s' % (cfgFile, PROGRAM_NAME)) return if cfgTree.getAttrValue('config-version', '', default=None) != CONFIG_VERSION: log.error( 'config file %s version is not compatible with program version %s' % (cfgFile, CONFIG_VERSION)) return random.seed() # # SNMPv3 CommandGenerator & NotificationOriginator implementation # origCredIdList = [] srvClassIdList = [] peerIdMap = {} pluginIdMap = {} routingMap = {} engineIdMap = {} commandGenerator = cmdgen.CommandGenerator() notificationOriginator = ntforg.NotificationOriginator() transportDispatcher = AsynsockDispatcher() transportDispatcher.registerRoutingCbFun(lambda td, t, d: td) transportDispatcher.setSocketMap() # use global asyncore socket map pluginManager = PluginManager(macro.expandMacros( cfgTree.getAttrValue('plugin-modules-path-list', '', default=[], vector=True), {'config-dir': os.path.dirname(cfgFile)}), progId=PROGRAM_NAME, apiVer=PLUGIN_API_VERSION) for pluginCfgPath in cfgTree.getPathsToAttr('plugin-id'): pluginId = cfgTree.getAttrValue('plugin-id', *pluginCfgPath) pluginMod = cfgTree.getAttrValue('plugin-module', *pluginCfgPath) pluginOptions = macro.expandMacros( cfgTree.getAttrValue('plugin-options', *pluginCfgPath, **dict(default=[], vector=True)), {'config-dir': os.path.dirname(cfgFile)}) log.info( 'configuring plugin ID %s (at %s) from module %s with options %s...' % (pluginId, '.'.join(pluginCfgPath), pluginMod, ', '.join(pluginOptions) or '<none>')) with daemon.PrivilegesOf(procUser, procGroup): try: pluginManager.loadPlugin(pluginId, pluginMod, pluginOptions) except SnmpfwdError: log.error('plugin %s not loaded: %s' % (pluginId, sys.exc_info()[1])) return for peerEntryPath in cfgTree.getPathsToAttr('snmp-peer-id'): peerId = cfgTree.getAttrValue('snmp-peer-id', *peerEntryPath) if peerId in peerIdMap: log.error('duplicate snmp-peer-id=%s at %s' % (peerId, '.'.join(peerEntryPath))) return log.info('configuring SNMP peer %s (at %s)...' % (peerId, '.'.join(peerEntryPath))) engineId = cfgTree.getAttrValue('snmp-engine-id', *peerEntryPath) if engineId in engineIdMap: snmpEngine, snmpContext, snmpEngineMap = engineIdMap[engineId] log.info('using engine-id: %s' % snmpEngine.snmpEngineID.prettyPrint()) else: snmpEngine = engine.SnmpEngine(snmpEngineID=engineId) snmpContext = context.SnmpContext(snmpEngine) snmpEngineMap = { 'transportDomain': {}, 'securityName': {}, 'credIds': set() } engineIdMap[engineId] = snmpEngine, snmpContext, snmpEngineMap log.info('new engine-id %s' % snmpEngine.snmpEngineID.prettyPrint()) transportDomain = cfgTree.getAttrValue('snmp-transport-domain', *peerEntryPath) transportDomain = rfc1902.ObjectName(str(transportDomain)) if (transportDomain[:len(udp.domainName)] != udp.domainName and udp6 and transportDomain[:len(udp6.domainName)] != udp6.domainName): log.error('unknown transport domain %s' % (transportDomain, )) return transportOptions = cfgTree.getAttrValue( 'snmp-transport-options', *peerEntryPath, **dict(default=[], vector=True)) bindAddr = cfgTree.getAttrValue('snmp-bind-address', *peerEntryPath) try: bindAddr, bindAddrMacro = endpoint.parseTransportAddress( transportDomain, bindAddr, transportOptions) except SnmpfwdError: log.error('bad snmp-bind-address specification %s at %s' % (bindAddr, '.'.join(peerEntryPath))) return if transportDomain in snmpEngineMap['transportDomain']: log.info('using transport endpoint with transport ID %s' % (transportDomain, )) else: if transportDomain[:len(udp.domainName)] == udp.domainName: transport = udp.UdpTransport() else: transport = udp6.Udp6Transport() snmpEngine.registerTransportDispatcher(transportDispatcher, transportDomain) t = transport.openClientMode(bindAddr) if 'transparent-proxy' in transportOptions: t.enablePktInfo() t.enableTransparent() elif 'virtual-interface' in transportOptions: t.enablePktInfo() config.addSocketTransport(snmpEngine, transportDomain, t) snmpEngineMap['transportDomain'][transportDomain] = bindAddr[ 0], bindAddr[1], transportDomain log.info( 'new transport endpoint at bind address [%s]:%s, options %s, transport ID %s' % (bindAddr[0], bindAddr[1], transportOptions and '/'.join(transportOptions) or '<none>', transportDomain)) securityModel = cfgTree.getAttrValue('snmp-security-model', *peerEntryPath) securityModel = rfc1902.Integer(securityModel) securityLevel = cfgTree.getAttrValue('snmp-security-level', *peerEntryPath) securityLevel = rfc1902.Integer(securityLevel) securityName = cfgTree.getAttrValue('snmp-security-name', *peerEntryPath) contextEngineId = cfgTree.getAttrValue('snmp-context-engine-id', *peerEntryPath, **dict(default=None)) contextName = cfgTree.getAttrValue('snmp-context-name', *peerEntryPath, **dict(default='')) if securityModel in (1, 2): if securityName in snmpEngineMap['securityName']: if snmpEngineMap['securityName'][ securityName] == securityModel: log.info('using security-name %s' % securityName) else: log.error( 'security-name %s already in use at security-model %s' % (securityName, securityModel)) return else: communityName = cfgTree.getAttrValue('snmp-community-name', *peerEntryPath) config.addV1System(snmpEngine, securityName, communityName, securityName=securityName) log.info( 'new community-name %s, security-model %s, security-name %s, security-level %s' % (communityName, securityModel, securityName, securityLevel)) snmpEngineMap['securityName'][securityName] = securityModel elif securityModel == 3: if securityName in snmpEngineMap['securityName']: if snmpEngineMap['securityName'][ securityName] == securityModel: log.info('using USM security-name: %s' % securityName) else: raise SnmpfwdError( 'security-name %s already in use at security-model %s' % (securityName, securityModel)) else: usmUser = cfgTree.getAttrValue('snmp-usm-user', *peerEntryPath) securityEngineId = cfgTree.getAttrValue( 'snmp-security-engine-id', *peerEntryPath, **dict(default=None)) if securityEngineId: securityEngineId = rfc1902.OctetString(securityEngineId) log.info( 'new USM user %s, security-model %s, security-level %s, ' 'security-name %s, security-engine-id %s' % (usmUser, securityModel, securityLevel, securityName, securityEngineId and securityEngineId.prettyPrint() or '<none>')) if securityLevel in (2, 3): usmAuthProto = cfgTree.getAttrValue( 'snmp-usm-auth-protocol', *peerEntryPath, **dict(default=config.usmHMACMD5AuthProtocol)) try: usmAuthProto = authProtocols[usmAuthProto.upper()] except KeyError: pass usmAuthProto = rfc1902.ObjectName(usmAuthProto) usmAuthKey = cfgTree.getAttrValue('snmp-usm-auth-key', *peerEntryPath) log.info( 'new USM authentication key: %s, authentication protocol: %s' % (usmAuthKey, usmAuthProto)) if securityLevel == 3: usmPrivProto = cfgTree.getAttrValue( 'snmp-usm-priv-protocol', *peerEntryPath, **dict(default=config.usmDESPrivProtocol)) try: usmPrivProto = privProtocols[usmPrivProto.upper()] except KeyError: pass usmPrivProto = rfc1902.ObjectName(usmPrivProto) usmPrivKey = cfgTree.getAttrValue( 'snmp-usm-priv-key', *peerEntryPath, **dict(default=None)) log.info( 'new USM encryption key: %s, encryption protocol: %s' % (usmPrivKey, usmPrivProto)) config.addV3User( snmpEngine, usmUser, usmAuthProto, usmAuthKey, usmPrivProto, usmPrivKey, ) else: config.addV3User(snmpEngine, usmUser, usmAuthProto, usmAuthKey, securityEngineId=securityEngineId) else: config.addV3User(snmpEngine, usmUser, securityEngineId=securityEngineId) snmpEngineMap['securityName'][securityName] = securityModel else: log.error('unknown security-model: %s' % securityModel) sys.exit(1) credId = '/'.join( [str(x) for x in (securityName, securityLevel, securityModel)]) if credId in snmpEngineMap['credIds']: log.info('using credentials ID %s...' % credId) else: config.addTargetParams( snmpEngine, credId, securityName, securityLevel, securityModel == 3 and 3 or securityModel - 1) log.info( 'new credentials %s, security-name %s, security-level %s, security-model %s' % (credId, securityName, securityLevel, securityModel)) snmpEngineMap['credIds'].add(credId) peerAddr = cfgTree.getAttrValue('snmp-peer-address', *peerEntryPath) try: peerAddr, peerAddrMacro = endpoint.parseTransportAddress( transportDomain, peerAddr, transportOptions, defaultPort=161) except SnmpfwdError: log.error('bad snmp-peer-address specification %s at %s' % (peerAddr, '.'.join(peerEntryPath))) return timeout = cfgTree.getAttrValue('snmp-peer-timeout', *peerEntryPath) retries = cfgTree.getAttrValue('snmp-peer-retries', *peerEntryPath) config.addTargetAddr(snmpEngine, peerId, transportDomain, peerAddr, credId, timeout, retries) peerIdMap[ peerId] = snmpEngine, contextEngineId, contextName, bindAddr, bindAddrMacro, peerAddr, peerAddrMacro log.info( 'new peer ID %s, bind address %s, peer address %s, timeout %s*0.01 secs, retries %s, credentials ID %s' % (peerId, bindAddrMacro or '<default>', peerAddrMacro or '%s:%s' % peerAddr, timeout, retries, credId)) duplicates = {} # TODO: rename orig-* into server-* and orig-snmp-peer-id into server-snmp-entity-id for origCredCfgPath in cfgTree.getPathsToAttr('orig-snmp-peer-id'): origCredId = cfgTree.getAttrValue('orig-snmp-peer-id', *origCredCfgPath) if origCredId in duplicates: log.error('duplicate orig-snmp-peer-id=%s at %s and %s' % (origCredId, '.'.join(origCredCfgPath), '.'.join( duplicates[origCredId]))) return duplicates[origCredId] = origCredCfgPath k = '#'.join( (cfgTree.getAttrValue('orig-snmp-engine-id-pattern', *origCredCfgPath), cfgTree.getAttrValue('orig-snmp-transport-domain-pattern', *origCredCfgPath), cfgTree.getAttrValue('orig-snmp-peer-address-pattern', *origCredCfgPath), cfgTree.getAttrValue('orig-snmp-bind-address-pattern', *origCredCfgPath), cfgTree.getAttrValue('orig-snmp-security-model-pattern', *origCredCfgPath), cfgTree.getAttrValue('orig-snmp-security-level-pattern', *origCredCfgPath), cfgTree.getAttrValue('orig-snmp-security-name-pattern', *origCredCfgPath), cfgTree.getAttrValue('orig-snmp-context-engine-id-pattern', *origCredCfgPath), cfgTree.getAttrValue('orig-snmp-context-name-pattern', *origCredCfgPath), cfgTree.getAttrValue('orig-snmp-pdu-type-pattern', *origCredCfgPath), cfgTree.getAttrValue('orig-snmp-oid-prefix-pattern', *origCredCfgPath))) log.info( 'configuring original SNMP peer ID %s (at %s), composite key: %s' % (origCredId, '.'.join(origCredCfgPath), k)) origCredIdList.append((origCredId, re.compile(k))) duplicates = {} for srvClassCfgPath in cfgTree.getPathsToAttr('server-classification-id'): srvClassId = cfgTree.getAttrValue('server-classification-id', *srvClassCfgPath) if srvClassId in duplicates: log.error('duplicate server-classification-id=%s at %s and %s' % (srvClassId, '.'.join(srvClassCfgPath), '.'.join( duplicates[srvClassId]))) return duplicates[srvClassId] = srvClassCfgPath k = '#'.join( (cfgTree.getAttrValue('server-snmp-credentials-id-pattern', *srvClassCfgPath), cfgTree.getAttrValue('server-snmp-context-id-pattern', *srvClassCfgPath), cfgTree.getAttrValue('server-snmp-content-id-pattern', *srvClassCfgPath), cfgTree.getAttrValue('server-snmp-peer-id-pattern', *srvClassCfgPath))) log.info( 'configuring server classification ID %s (at %s), composite key: %s' % (srvClassId, '.'.join(srvClassCfgPath), k)) srvClassIdList.append((srvClassId, re.compile(k))) del duplicates for pluginCfgPath in cfgTree.getPathsToAttr('using-plugin-id-list'): pluginIdList = cfgTree.getAttrValue('using-plugin-id-list', *pluginCfgPath, **dict(vector=True)) log.info('configuring plugin ID(s) %s (at %s)...' % (','.join(pluginIdList), '.'.join(pluginCfgPath))) for credId in cfgTree.getAttrValue('matching-orig-snmp-peer-id-list', *pluginCfgPath, **dict(vector=True)): for srvClassId in cfgTree.getAttrValue( 'matching-server-classification-id-list', *pluginCfgPath, **dict(vector=True)): for trunkId in cfgTree.getAttrValue('matching-trunk-id-list', *pluginCfgPath, **dict(vector=True)): k = credId, srvClassId, trunkId if k in pluginIdMap: log.error( 'duplicate snmp-credentials-id=%s and server-classification-id=%s and trunk-id=%s at plugin-id %s' % (credId, srvClassId, trunkId, ','.join(pluginIdList))) return else: log.info( 'configuring plugin(s) %s (at %s), composite key: %s' % (','.join(pluginIdList), '.'.join(pluginCfgPath), '/'.join(k))) for pluginId in pluginIdList: if not pluginManager.hasPlugin(pluginId): log.error( 'undefined plugin ID %s referenced at %s' % (pluginId, '.'.join(pluginCfgPath))) return pluginIdMap[k] = pluginIdList for routeCfgPath in cfgTree.getPathsToAttr('using-snmp-peer-id-list'): peerIdList = cfgTree.getAttrValue('using-snmp-peer-id-list', *routeCfgPath, **dict(vector=True)) log.info('configuring routing entry with peer IDs %s (at %s)...' % (','.join(peerIdList), '.'.join(routeCfgPath))) for credId in cfgTree.getAttrValue('matching-orig-snmp-peer-id-list', *routeCfgPath, **dict(vector=True)): for srvClassId in cfgTree.getAttrValue( 'matching-server-classification-id-list', *routeCfgPath, **dict(vector=True)): for trunkId in cfgTree.getAttrValue('matching-trunk-id-list', *routeCfgPath, **dict(vector=True)): k = credId, srvClassId, trunkId if k in routingMap: log.error( 'duplicate snmp-credentials-id=%s and server-classification-id=%s and trunk-id=%s at snmp-peer-id %s' % (credId, srvClassId, trunkId, ','.join(peerIdList))) return else: for peerId in peerIdList: if peerId not in peerIdMap: log.error('missing peer-id %s at %s' % (peerId, '.'.join(routeCfgPath))) return routingMap[k] = peerIdList trunkingManager = TrunkingManager(trunkCbFun) for trunkCfgPath in cfgTree.getPathsToAttr('trunk-id'): trunkId = cfgTree.getAttrValue('trunk-id', *trunkCfgPath) secret = cfgTree.getAttrValue('trunk-crypto-key', *trunkCfgPath, **dict(default='')) secret = secret and (secret * ((16 // len(secret)) + 1))[:16] log.info('configuring trunk ID %s (at %s)...' % (trunkId, '.'.join(trunkCfgPath))) connectionMode = cfgTree.getAttrValue('trunk-connection-mode', *trunkCfgPath) if connectionMode == 'client': trunkingManager.addClient( trunkId, parseTrunkEndpoint( cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath)), parseTrunkEndpoint( cfgTree.getAttrValue('trunk-peer-address', *trunkCfgPath), 30201), cfgTree.getAttrValue('trunk-ping-period', *trunkCfgPath, default=0, expect=int), secret) log.info( 'new trunking client from %s to %s' % (cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath), cfgTree.getAttrValue('trunk-peer-address', *trunkCfgPath))) if connectionMode == 'server': trunkingManager.addServer( parseTrunkEndpoint( cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath), 30201), cfgTree.getAttrValue('trunk-ping-period', *trunkCfgPath, default=0, expect=int), secret) log.info( 'new trunking server at %s' % (cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath))) transportDispatcher.registerTimerCbFun(trunkingManager.setupTrunks, random.randrange(1, 5)) transportDispatcher.registerTimerCbFun(trunkingManager.monitorTrunks, random.randrange(1, 5)) if not foregroundFlag: try: daemon.daemonize(pidFile) except Exception: log.error('can not daemonize process: %s' % sys.exc_info()[1]) return # Run mainloop log.info('starting I/O engine...') transportDispatcher.jobStarted(1) # server job would never finish # Python 2.4 does not support the "finally" clause with daemon.PrivilegesOf(procUser, procGroup, final=True): while True: try: transportDispatcher.runDispatcher() except (PySnmpError, SnmpfwdError, socket.error): log.error(str(sys.exc_info()[1])) continue except Exception: transportDispatcher.closeDispatcher() raise
def start_listener(self, callback, address=None, port=1162, community='public', timeout=TIMEOUT): ''' Start a TRAP v1/v2c/v3 notification receiver with predefined users. @param callback: Takes these args snmpEngine, stateReference, contextEngineId, contextName, varBinds, cbCtx @param address: The address to listen to @param port: The port to listen to @param community: The community name for v2c Predefined users: usr-md5-des usr-md5-none usr-sha-aes128 Auth: authkey1 Priv: privkey1 ''' if not address: address = get_local_ip(self.host) if timeout < 0: timeout = 10 ** 10 # Create SNMP engine with auto-generated engineID and pre-bound # to socket transport dispatcher snmpEngine = engine.SnmpEngine() # Transport setup if IPAddress(address).version == 4: # UDP over IPv4 domain_oid = udp.domainName transport = udp.UdpTransport() else: # UDP over IPv6 domain_oid = udp6.domainName transport = udp6.Udp6Transport() # Waiting up to TIMEOUT seconds for the port to be released LOG.debug('Waiting for port %s:%d to become available...', address, port) transport = wait(lambda: transport.openServerMode((address, port)), timeout=TIMEOUT, interval=1) LOG.info('Listening for traps on %s:%d...', address, port) config.addSocketTransport(snmpEngine, domain_oid, transport) # Terrible monkey patching!! # But there's no other way to cause the dispatcher loop to end if we # don't get what we expect in a given amount of time. For now that time # is limited to TIMEOUT seconds. end = time.time() + timeout def jobsArePending(self): if self._AbstractTransportDispatcher__jobs and time.time() < end: return 1 else: return 0 snmpEngine.transportDispatcher.__class__.jobsArePending = jobsArePending # SNMPv1/2 setup config.addV1System(snmpEngine, 'test-agent', community) # SNMPv3/USM setup # user: usr-md5-des, auth: MD5, priv DES config.addV3User( snmpEngine, 'usr-md5-des', config.usmHMACMD5AuthProtocol, 'authkey1', config.usmDESPrivProtocol, 'privkey1' ) # user: usr-md5-des, auth: MD5, priv DES, contextEngineId: 8000000001020304 # this USM entry is used for TRAP receiving purposes config.addV3User( snmpEngine, 'usr-md5-des', config.usmHMACMD5AuthProtocol, 'authkey1', config.usmDESPrivProtocol, 'privkey1', contextEngineId=v2c.OctetString(hexValue='8000000001020304') ) # user: usr-md5-none, auth: MD5, priv NONE config.addV3User( snmpEngine, 'usr-md5-none', config.usmHMACMD5AuthProtocol, 'authkey1' ) # user: usr-md5-none, auth: MD5, priv NONE, contextEngineId: 8000000001020304 # this USM entry is used for TRAP receiving purposes config.addV3User( snmpEngine, 'usr-md5-none', config.usmHMACMD5AuthProtocol, 'authkey1', contextEngineId=v2c.OctetString(hexValue='8000000001020304') ) # user: usr-sha-aes128, auth: SHA, priv AES config.addV3User( snmpEngine, 'usr-sha-aes128', config.usmHMACSHAAuthProtocol, 'authkey1', config.usmAesCfb128Protocol, 'privkey1' ) # user: usr-sha-aes128, auth: SHA, priv AES, contextEngineId: 8000000001020304 # this USM entry is used for TRAP receiving purposes config.addV3User( snmpEngine, 'usr-sha-aes128', config.usmHMACSHAAuthProtocol, 'authkey1', config.usmAesCfb128Protocol, 'privkey1', contextEngineId=v2c.OctetString(hexValue='8000000001020304') ) # def sample_callback(snmpEngine, stateReference, contextEngineId, contextName, # varBinds, cbCtx): # print('Notification received, ContextEngineId "%s", ContextName "%s"' % ( # contextEngineId.prettyPrint(), contextName.prettyPrint()) # ) # for name, val in varBinds: # print('%s = %s' % (name.prettyPrint(), val.prettyPrint())) # print # If callback() returns True we'll stop the loop def callback_wrapper(*args, **kwargs): if callback(*args, **kwargs): snmpEngine.transportDispatcher.jobFinished(DEFAULT_JOB) # Register SNMP Application at the SNMP engine ntfrcv.NotificationReceiver(snmpEngine, callback_wrapper) #return address, port t = TrapListener(snmpEngine) t.start() return address, port, t
def __init__(self, host, port, mibpaths): self.oid_mapping = {} self.databus_mediator = DatabusMediator(self.oid_mapping) # mapping between OID and databus keys # Create SNMP engine self.snmpEngine = engine.SnmpEngine() # path to custom mibs mibBuilder = self.snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder mibSources = mibBuilder.getMibSources() for mibpath in mibpaths: mibSources += (builder.DirMibSource(mibpath), ) mibBuilder.setMibSources(*mibSources) # Transport setup udp_sock = gevent.socket.socket(gevent.socket.AF_INET, gevent.socket.SOCK_DGRAM) udp_sock.setsockopt(gevent.socket.SOL_SOCKET, gevent.socket.SO_BROADCAST, 1) udp_sock.bind((host, port)) self.server_port = udp_sock.getsockname()[1] # UDP over IPv4 self.addSocketTransport(self.snmpEngine, udp.domainName, udp_sock) # SNMPv1 config.addV1System(self.snmpEngine, 'public-read', 'public') # SNMPv3/USM setup # user: usr-md5-des, auth: MD5, priv DES config.addV3User(self.snmpEngine, 'usr-md5-des', config.usmHMACMD5AuthProtocol, 'authkey1', config.usmDESPrivProtocol, 'privkey1') # user: usr-sha-none, auth: SHA, priv NONE config.addV3User(self.snmpEngine, 'usr-sha-none', config.usmHMACSHAAuthProtocol, 'authkey1') # user: usr-sha-aes128, auth: SHA, priv AES/128 config.addV3User(self.snmpEngine, 'usr-sha-aes128', config.usmHMACSHAAuthProtocol, 'authkey1', config.usmAesCfb128Protocol, 'privkey1') # Allow full MIB access for each user at VACM config.addVacmUser(self.snmpEngine, 1, 'public-read', 'noAuthNoPriv', readSubTree=(1, 3, 6, 1, 2, 1), writeSubTree=(1, 3, 6, 1, 2, 1)) config.addVacmUser(self.snmpEngine, 2, 'public-read', 'noAuthNoPriv', readSubTree=(1, 3, 6, 1, 2, 1), writeSubTree=(1, 3, 6, 1, 2, 1)) config.addVacmUser(self.snmpEngine, 3, 'usr-md5-des', 'authPriv', readSubTree=(1, 3, 6, 1, 2, 1), writeSubTree=(1, 3, 6, 1, 2, 1)) config.addVacmUser(self.snmpEngine, 3, 'usr-sha-none', 'authNoPriv', readSubTree=(1, 3, 6, 1, 2, 1), writeSubTree=(1, 3, 6, 1, 2, 1)) config.addVacmUser(self.snmpEngine, 3, 'usr-sha-aes128', 'authPriv', readSubTree=(1, 3, 6, 1, 2, 1), writeSubTree=(1, 3, 6, 1, 2, 1)) # Get default SNMP context this SNMP engine serves snmpContext = context.SnmpContext(self.snmpEngine) # Register SNMP Applications at the SNMP engine for particular SNMP context self.resp_app_get = conpot_cmdrsp.c_GetCommandResponder( self.snmpEngine, snmpContext, self.databus_mediator, host, port) self.resp_app_set = conpot_cmdrsp.c_SetCommandResponder( self.snmpEngine, snmpContext, self.databus_mediator, host, port) self.resp_app_next = conpot_cmdrsp.c_NextCommandResponder( self.snmpEngine, snmpContext, self.databus_mediator, host, port) self.resp_app_bulk = conpot_cmdrsp.c_BulkCommandResponder( self.snmpEngine, snmpContext, self.databus_mediator, host, port)
def main(): class MibTreeProxyMixIn(object): MIB_INTRUMENTATION_CALL = None def _getMgmtFun(self, contextName): return self._routeToMibTree def _routeToMibTree(self, *varBinds, **context): cbFun = context['cbFun'] mibTreeReq = gCurrentRequestContext.copy() pdu = mibTreeReq['snmp-pdu'] pluginIdList = mibTreeReq['plugins-list'] logCtx = LogString(mibTreeReq) reqCtx = {} for pluginNum, pluginId in enumerate(pluginIdList): st, pdu = pluginManager.processCommandRequest( pluginId, snmpEngine, pdu, mibTreeReq, reqCtx) if st == status.BREAK: log.debug('plugin %s inhibits other plugins' % pluginId, ctx=logCtx) pluginIdList = pluginIdList[:pluginNum] break elif st == status.DROP: log.debug( 'received SNMP message, plugin %s muted request' % pluginId, ctx=logCtx) # TODO: need to report some special error to drop request cbFun(varBinds, **context) return elif st == status.RESPOND: log.debug( 'received SNMP message, plugin %s forced immediate response' % pluginId, ctx=logCtx) # TODO: should we respond something other than request? cbFun(varBinds, **context) return # Apply PDU to MIB(s) mibTreeId = mibTreeReq['mib-tree-id'] if not mibTreeId: log.error('no matching MIB tree route for the request', ctx=logCtx) cbFun(varBinds, **dict(context, error=smi_error.GenError())) return mibInstrum = mibTreeIdMap.get(mibTreeId) if not mibInstrum: log.error('MIB tree ID %s does not exist' % mibTreeId, ctx=logCtx) cbFun(varBinds, **dict(context, error=smi_error.GenError())) return log.debug('received SNMP message, applied on mib-tree-id %s' % mibTreeId, ctx=logCtx) cbCtx = pluginIdList, mibTreeId, mibTreeReq, snmpEngine, reqCtx, context[ 'cbFun'] mgmtFun = getattr(mibInstrum, self.MIB_INTRUMENTATION_CALL) mgmtFun( *varBinds, **dict(context, cbFun=self._mibTreeCbFun, cbCtx=cbCtx, acFun=None)) # TODO: it just occurred to me that `*varBinds` would look more consistent def _mibTreeCbFun(self, varBinds, **context): pluginIdList, mibTreeId, mibTreeReq, snmpEngine, reqCtx, cbFun = context[ 'cbCtx'] logCtx = LogString(mibTreeReq) err = context.get('error') if err: log.info('MIB operation resulted in error: %s' % err, ctx=logCtx) cbFun(varBinds, **dict(context, cbFun=cbFun)) # plugins need to work at var-binds level # # for key in tuple(mibTreeRsp): # pdu = mibTreeRsp['client-snmp-pdu'] # # for pluginId in pluginIdList: # st, pdu = pluginManager.processCommandResponse( # pluginId, snmpEngine, pdu, mibTreeReq, reqCtx # ) # # if st == status.BREAK: # log.debug('plugin %s inhibits other plugins' % pluginId, ctx=logCtx) # break # elif st == status.DROP: # log.debug('plugin %s muted response' % pluginId, ctx=logCtx) # self.releaseStateInformation(stateReference) # return # # try: # self.sendPdu(snmpEngine, stateReference, pdu) # # except PySnmpError: # log.error('mibTree message #%s, SNMP response error: %s' % (msgId, sys.exc_info()[1]), # ctx=logCtx) # # else: # log.debug('received mibTree message #%s, forwarded as SNMP message' % msgId, ctx=logCtx) class GetCommandResponder(MibTreeProxyMixIn, cmdrsp.GetCommandResponder): MIB_INTRUMENTATION_CALL = 'readMibObjects' class GetNextCommandResponder(MibTreeProxyMixIn, cmdrsp.NextCommandResponder): MIB_INTRUMENTATION_CALL = 'readNextMibObjects' class GetBulkCommandResponder(MibTreeProxyMixIn, cmdrsp.BulkCommandResponder): MIB_INTRUMENTATION_CALL = 'readNextMibObjects' class SetCommandResponder(MibTreeProxyMixIn, cmdrsp.SetCommandResponder): MIB_INTRUMENTATION_CALL = 'writeMibObjects' class LogString(LazyLogString): GROUPINGS = [ ['callflow-id'], [ 'snmp-engine-id', 'snmp-transport-domain', 'snmp-bind-address', 'snmp-bind-port', 'snmp-security-model', 'snmp-security-level', 'snmp-security-name', 'snmp-credentials-id' ], ['snmp-context-engine-id', 'snmp-context-name', 'snmp-context-id'], ['snmp-pdu', 'snmp-content-id'], ['snmp-peer-address', 'snmp-peer-port', 'snmp-peer-id'], ['mib-tree-id'], ['client-snmp-pdu'], ] FORMATTERS = { 'client-snmp-pdu': LazyLogString.prettyVarBinds, 'snmp-pdu': LazyLogString.prettyVarBinds, } def securityAuditObserver(snmpEngine, execpoint, variables, cbCtx): securityModel = variables.get('securityModel', 0) logMsg = 'SNMPv%s auth failure' % securityModel logMsg += ' at %s:%s' % variables['transportAddress'].getLocalAddress() logMsg += ' from %s:%s' % variables['transportAddress'] statusInformation = variables.get('statusInformation', {}) if securityModel in (1, 2): logMsg += ' using snmp-community-name "%s"' % statusInformation.get( 'communityName', '?') elif securityModel == 3: logMsg += ' using snmp-usm-user "%s"' % statusInformation.get( 'msgUserName', '?') try: logMsg += ': %s' % statusInformation['errorIndication'] except KeyError: pass log.error(logMsg) def usmRequestObserver(snmpEngine, execpoint, variables, cbCtx): mibTreeReq = {'snmp-security-engine-id': variables['securityEngineId']} cbCtx.clear() cbCtx.update(mibTreeReq) def requestObserver(snmpEngine, execpoint, variables, cbCtx): mibTreeReq = { 'callflow-id': '%10.10x' % random.randint(0, 0xffffffffff), 'snmp-engine-id': snmpEngine.snmpEngineID, 'snmp-transport-domain': variables['transportDomain'], 'snmp-peer-address': variables['transportAddress'][0], 'snmp-peer-port': variables['transportAddress'][1], 'snmp-bind-address': variables['transportAddress'].getLocalAddress()[0], 'snmp-bind-port': variables['transportAddress'].getLocalAddress()[1], 'snmp-security-model': variables['securityModel'], 'snmp-security-level': variables['securityLevel'], 'snmp-security-name': variables['securityName'], 'snmp-context-engine-id': variables['contextEngineId'], 'snmp-context-name': variables['contextName'], } try: mibTreeReq['snmp-security-engine-id'] = cbCtx.pop( 'snmp-security-engine-id') except KeyError: # SNMPv1/v2c mibTreeReq['snmp-security-engine-id'] = mibTreeReq[ 'snmp-engine-id'] mibTreeReq['snmp-credentials-id'] = macro.expandMacro( credIdMap.get( (str(snmpEngine.snmpEngineID), variables['transportDomain'], variables['securityModel'], variables['securityLevel'], str(variables['securityName']))), mibTreeReq) k = '#'.join([ str(x) for x in (variables['contextEngineId'], variables['contextName']) ]) for x, y in contextIdList: if y.match(k): mibTreeReq['snmp-context-id'] = macro.expandMacro( x, mibTreeReq) break else: mibTreeReq['snmp-context-id'] = None addr = '%s:%s#%s:%s' % ( variables['transportAddress'][0], variables['transportAddress'][1], variables['transportAddress'].getLocalAddress()[0], variables['transportAddress'].getLocalAddress()[1]) for pat, peerId in peerIdMap.get(str(variables['transportDomain']), ()): if pat.match(addr): mibTreeReq['snmp-peer-id'] = macro.expandMacro( peerId, mibTreeReq) break else: mibTreeReq['snmp-peer-id'] = None pdu = variables['pdu'] k = '#'.join([ snmpPduTypesMap.get(variables['pdu'].tagSet, '?'), '|'.join([str(x[0]) for x in v2c.apiPDU.getVarBinds(pdu)]) ]) for x, y in contentIdList: if y.match(k): mibTreeReq['snmp-content-id'] = macro.expandMacro( x, mibTreeReq) break else: mibTreeReq['snmp-content-id'] = None mibTreeReq['plugins-list'] = pluginIdMap.get( (mibTreeReq['snmp-credentials-id'], mibTreeReq['snmp-context-id'], mibTreeReq['snmp-peer-id'], mibTreeReq['snmp-content-id']), []) mibTreeReq['mib-tree-id'] = routingMap.get( (mibTreeReq['snmp-credentials-id'], mibTreeReq['snmp-context-id'], mibTreeReq['snmp-peer-id'], mibTreeReq['snmp-content-id'])) mibTreeReq['snmp-pdu'] = pdu cbCtx.clear() cbCtx.update(mibTreeReq) # # main script starts here # helpMessage = """\ Usage: %s [--help] [--version ] [--debug-snmp=<%s>] [--debug-asn1=<%s>] [--daemonize] [--process-user=<uname>] [--process-group=<gname>] [--pid-file=<file>] [--logging-method=<%s[:args>]>] [--log-level=<%s>] [--config-file=<file>]""" % (sys.argv[0], '|'.join([ x for x in getattr(pysnmp_debug, 'FLAG_MAP', getattr(pysnmp_debug, 'flagMap', ())) if x != 'mibview' ]), '|'.join([ x for x in getattr(pyasn1_debug, 'FLAG_MAP', getattr(pyasn1_debug, 'flagMap', ())) ]), '|'.join(log.methodsMap), '|'.join(log.levelsMap)) try: opts, params = getopt.getopt(sys.argv[1:], 'hv', [ 'help', 'version', 'debug=', 'debug-snmp=', 'debug-asn1=', 'daemonize', 'process-user='******'process-group=', 'pid-file=', 'logging-method=', 'log-level=', 'config-file=' ]) except Exception: sys.stderr.write('ERROR: %s\r\n%s\r\n' % (sys.exc_info()[1], helpMessage)) return if params: sys.stderr.write('ERROR: extra arguments supplied %s\r\n%s\r\n' % (params, helpMessage)) return pidFile = '' cfgFile = CONFIG_FILE foregroundFlag = True procUser = procGroup = None loggingMethod = ['stderr'] loggingLevel = None for opt in opts: if opt[0] == '-h' or opt[0] == '--help': sys.stderr.write("""\ Synopsis: SNMP Command Responder. Runs one or more SNMP command responders (agents) and one or more trees of MIB objects representing SNMP-managed entities. The tool applies received messages onto one of the MIB trees chosen by tool's configuration. Documentation: http://snmplabs.com/snmpresponder/ %s """ % helpMessage) return if opt[0] == '-v' or opt[0] == '--version': import snmpresponder import pysnmp import pyasn1 sys.stderr.write("""\ SNMP Command Responder version %s, written by Ilya Etingof <*****@*****.**> Using foundation libraries: pysnmp %s, pyasn1 %s. Python interpreter: %s Software documentation and support at http://snmplabs.com/snmpresponder/ %s """ % (snmpresponder.__version__, hasattr(pysnmp, '__version__') and pysnmp.__version__ or 'unknown', hasattr(pyasn1, '__version__') and pyasn1.__version__ or 'unknown', sys.version, helpMessage)) return elif opt[0] == '--debug-snmp': pysnmp_debug.setLogger( pysnmp_debug.Debug(*opt[1].split(','), loggerName=PROGRAM_NAME + '.pysnmp')) elif opt[0] == '--debug-asn1': pyasn1_debug.setLogger( pyasn1_debug.Debug(*opt[1].split(','), loggerName=PROGRAM_NAME + '.pyasn1')) elif opt[0] == '--daemonize': foregroundFlag = False elif opt[0] == '--process-user': procUser = opt[1] elif opt[0] == '--process-group': procGroup = opt[1] elif opt[0] == '--pid-file': pidFile = opt[1] elif opt[0] == '--logging-method': loggingMethod = opt[1].split(':') elif opt[0] == '--log-level': loggingLevel = opt[1] elif opt[0] == '--config-file': cfgFile = opt[1] with daemon.PrivilegesOf(procUser, procGroup): try: log.setLogger(PROGRAM_NAME, *loggingMethod, force=True) if loggingLevel: log.setLevel(loggingLevel) except SnmpResponderError: sys.stderr.write('%s\r\n%s\r\n' % (sys.exc_info()[1], helpMessage)) return try: cfgTree = cparser.Config().load(cfgFile) except SnmpResponderError: log.error('configuration parsing error: %s' % sys.exc_info()[1]) return if cfgTree.getAttrValue('program-name', '', default=None) != PROGRAM_NAME: log.error('config file %s does not match program name %s' % (cfgFile, PROGRAM_NAME)) return if cfgTree.getAttrValue('config-version', '', default=None) != CONFIG_VERSION: log.error( 'config file %s version is not compatible with program version %s' % (cfgFile, CONFIG_VERSION)) return random.seed() gCurrentRequestContext = {} credIdMap = {} peerIdMap = {} contextIdList = [] contentIdList = [] pluginIdMap = {} routingMap = {} mibTreeIdMap = {} engineIdMap = {} transportDispatcher = AsyncoreDispatcher() transportDispatcher.registerRoutingCbFun(lambda td, t, d: td) transportDispatcher.setSocketMap() # use global asyncore socket map # # Initialize plugin modules # pluginManager = PluginManager(macro.expandMacros( cfgTree.getAttrValue('plugin-modules-path-list', '', default=[], vector=True), {'config-dir': os.path.dirname(cfgFile)}), progId=PROGRAM_NAME, apiVer=PLUGIN_API_VERSION) for pluginCfgPath in cfgTree.getPathsToAttr('plugin-id'): pluginId = cfgTree.getAttrValue('plugin-id', *pluginCfgPath) pluginMod = cfgTree.getAttrValue('plugin-module', *pluginCfgPath) pluginOptions = macro.expandMacros( cfgTree.getAttrValue('plugin-options', *pluginCfgPath, default=[], vector=True), {'config-dir': os.path.dirname(cfgFile)}) log.info( 'configuring plugin ID %s (at %s) from module %s with options %s...' % (pluginId, '.'.join(pluginCfgPath), pluginMod, ', '.join(pluginOptions) or '<none>')) with daemon.PrivilegesOf(procUser, procGroup): try: pluginManager.loadPlugin(pluginId, pluginMod, pluginOptions) except SnmpResponderError: log.error('plugin %s not loaded: %s' % (pluginId, sys.exc_info()[1])) return for configEntryPath in cfgTree.getPathsToAttr('snmp-credentials-id'): credId = cfgTree.getAttrValue('snmp-credentials-id', *configEntryPath) configKey = [] log.info('configuring snmp-credentials %s (at %s)...' % (credId, '.'.join(configEntryPath))) engineId = cfgTree.getAttrValue('snmp-engine-id', *configEntryPath) if engineId in engineIdMap: snmpEngine, snmpContext, snmpEngineMap = engineIdMap[engineId] log.info('using engine-id %s' % snmpEngine.snmpEngineID.prettyPrint()) else: snmpEngine = engine.SnmpEngine(snmpEngineID=engineId) snmpContext = context.SnmpContext(snmpEngine) snmpEngineMap = {'transportDomain': {}, 'securityName': {}} snmpEngine.observer.registerObserver( securityAuditObserver, 'rfc2576.prepareDataElements:sm-failure', 'rfc3412.prepareDataElements:sm-failure', cbCtx=gCurrentRequestContext) snmpEngine.observer.registerObserver( requestObserver, 'rfc3412.receiveMessage:request', cbCtx=gCurrentRequestContext) snmpEngine.observer.registerObserver(usmRequestObserver, 'rfc3414.processIncomingMsg', cbCtx=gCurrentRequestContext) GetCommandResponder(snmpEngine, snmpContext) GetNextCommandResponder(snmpEngine, snmpContext) GetBulkCommandResponder(snmpEngine, snmpContext) SetCommandResponder(snmpEngine, snmpContext) engineIdMap[engineId] = snmpEngine, snmpContext, snmpEngineMap log.info('new engine-id %s' % snmpEngine.snmpEngineID.prettyPrint()) configKey.append(str(snmpEngine.snmpEngineID)) transportDomain = cfgTree.getAttrValue('snmp-transport-domain', *configEntryPath) transportDomain = rfc1902.ObjectName(transportDomain) if (transportDomain[:len(udp.DOMAIN_NAME)] != udp.DOMAIN_NAME and udp6 and transportDomain[:len(udp6.DOMAIN_NAME)] != udp6.DOMAIN_NAME): log.error('unknown transport domain %s' % (transportDomain, )) return if transportDomain in snmpEngineMap['transportDomain']: bindAddr, transportDomain = snmpEngineMap['transportDomain'][ transportDomain] log.info('using transport endpoint [%s]:%s, transport ID %s' % (bindAddr[0], bindAddr[1], transportDomain)) else: bindAddr = cfgTree.getAttrValue('snmp-bind-address', *configEntryPath) transportOptions = cfgTree.getAttrValue('snmp-transport-options', *configEntryPath, default=[], vector=True) try: bindAddr, bindAddrMacro = endpoint.parseTransportAddress( transportDomain, bindAddr, transportOptions) except SnmpResponderError: log.error('bad snmp-bind-address specification %s at %s' % (bindAddr, '.'.join(configEntryPath))) return if transportDomain[:len(udp.DOMAIN_NAME)] == udp.DOMAIN_NAME: transport = udp.UdpTransport() else: transport = udp6.Udp6Transport() t = transport.openServerMode(bindAddr) if 'transparent-proxy' in transportOptions: t.enablePktInfo() t.enableTransparent() elif 'virtual-interface' in transportOptions: t.enablePktInfo() snmpEngine.registerTransportDispatcher(transportDispatcher, transportDomain) config.addSocketTransport(snmpEngine, transportDomain, t) snmpEngineMap['transportDomain'][ transportDomain] = bindAddr, transportDomain log.info( 'new transport endpoint [%s]:%s, options %s, transport ID %s' % (bindAddr[0], bindAddr[1], transportOptions and '/'.join(transportOptions) or '<none>', transportDomain)) configKey.append(transportDomain) securityModel = cfgTree.getAttrValue('snmp-security-model', *configEntryPath) securityModel = rfc1902.Integer(securityModel) securityLevel = cfgTree.getAttrValue('snmp-security-level', *configEntryPath) securityLevel = rfc1902.Integer(securityLevel) securityName = cfgTree.getAttrValue('snmp-security-name', *configEntryPath) if securityModel in (1, 2): if securityName in snmpEngineMap['securityName']: if snmpEngineMap['securityName'][ securityModel] == securityModel: log.info('using security-name %s' % securityName) else: raise SnmpResponderError( 'snmp-security-name %s already in use at snmp-security-model %s' % (securityName, securityModel)) else: communityName = cfgTree.getAttrValue('snmp-community-name', *configEntryPath) config.addV1System(snmpEngine, securityName, communityName, securityName=securityName) log.info( 'new community-name %s, security-model %s, security-name %s, security-level %s' % (communityName, securityModel, securityName, securityLevel)) snmpEngineMap['securityName'][securityName] = securityModel configKey.append(securityModel) configKey.append(securityLevel) configKey.append(securityName) elif securityModel == 3: if securityName in snmpEngineMap['securityName']: log.info('using USM security-name: %s' % securityName) else: usmUser = cfgTree.getAttrValue('snmp-usm-user', *configEntryPath) securityEngineId = cfgTree.getAttrValue( 'snmp-security-engine-id', *configEntryPath, default=None) if securityEngineId: securityEngineId = rfc1902.OctetString(securityEngineId) log.info( 'new USM user %s, security-model %s, security-level %s, ' 'security-name %s, security-engine-id %s' % (usmUser, securityModel, securityLevel, securityName, securityEngineId and securityEngineId.prettyPrint() or '<none>')) if securityLevel in (2, 3): usmAuthProto = cfgTree.getAttrValue( 'snmp-usm-auth-protocol', *configEntryPath, default=config.USM_AUTH_HMAC96_MD5) try: usmAuthProto = authProtocols[usmAuthProto.upper()] except KeyError: pass usmAuthProto = rfc1902.ObjectName(usmAuthProto) usmAuthKey = cfgTree.getAttrValue('snmp-usm-auth-key', *configEntryPath) log.info( 'new USM authentication key: %s, authentication protocol: %s' % (usmAuthKey, usmAuthProto)) if securityLevel == 3: usmPrivProto = cfgTree.getAttrValue( 'snmp-usm-priv-protocol', *configEntryPath, default=config.USM_PRIV_CBC56_DES) try: usmPrivProto = privProtocols[usmPrivProto.upper()] except KeyError: pass usmPrivProto = rfc1902.ObjectName(usmPrivProto) usmPrivKey = cfgTree.getAttrValue('snmp-usm-priv-key', *configEntryPath, default=None) log.info( 'new USM encryption key: %s, encryption protocol: %s' % (usmPrivKey, usmPrivProto)) config.addV3User(snmpEngine, usmUser, usmAuthProto, usmAuthKey, usmPrivProto, usmPrivKey, securityEngineId=securityEngineId) else: config.addV3User(snmpEngine, usmUser, usmAuthProto, usmAuthKey, securityEngineId=securityEngineId) else: config.addV3User(snmpEngine, usmUser, securityEngineId=securityEngineId) snmpEngineMap['securityName'][securityName] = securityModel configKey.append(securityModel) configKey.append(securityLevel) configKey.append(securityName) else: raise SnmpResponderError('unknown snmp-security-model: %s' % securityModel) configKey = tuple(configKey) if configKey in credIdMap: log.error( 'ambiguous configuration for key snmp-credentials-id=%s at %s' % (credId, '.'.join(configEntryPath))) return credIdMap[configKey] = credId duplicates = {} for peerCfgPath in cfgTree.getPathsToAttr('snmp-peer-id'): peerId = cfgTree.getAttrValue('snmp-peer-id', *peerCfgPath) if peerId in duplicates: log.error( 'duplicate snmp-peer-id=%s at %s and %s' % (peerId, '.'.join(peerCfgPath), '.'.join(duplicates[peerId]))) return duplicates[peerId] = peerCfgPath log.info('configuring peer ID %s (at %s)...' % (peerId, '.'.join(peerCfgPath))) transportDomain = cfgTree.getAttrValue('snmp-transport-domain', *peerCfgPath) if transportDomain not in peerIdMap: peerIdMap[transportDomain] = [] for peerAddress in cfgTree.getAttrValue( 'snmp-peer-address-pattern-list', *peerCfgPath, vector=True): for bindAddress in cfgTree.getAttrValue( 'snmp-bind-address-pattern-list', *peerCfgPath, vector=True): peerIdMap[transportDomain].append( (re.compile(peerAddress + '#' + bindAddress), peerId)) duplicates = {} for contextCfgPath in cfgTree.getPathsToAttr('snmp-context-id'): contextId = cfgTree.getAttrValue('snmp-context-id', *contextCfgPath) if contextId in duplicates: log.error('duplicate snmp-context-id=%s at %s and %s' % (contextId, '.'.join(contextCfgPath), '.'.join( duplicates[contextId]))) return duplicates[contextId] = contextCfgPath k = '#'.join((cfgTree.getAttrValue('snmp-context-engine-id-pattern', *contextCfgPath), cfgTree.getAttrValue('snmp-context-name-pattern', *contextCfgPath))) log.info('configuring context ID %s (at %s), composite key: %s' % (contextId, '.'.join(contextCfgPath), k)) contextIdList.append((contextId, re.compile(k))) duplicates = {} for contentCfgPath in cfgTree.getPathsToAttr('snmp-content-id'): contentId = cfgTree.getAttrValue('snmp-content-id', *contentCfgPath) if contentId in duplicates: log.error('duplicate snmp-content-id=%s at %s and %s' % (contentId, '.'.join(contentCfgPath), '.'.join( duplicates[contentId]))) return duplicates[contentId] = contentCfgPath for x in cfgTree.getAttrValue('snmp-pdu-oid-prefix-pattern-list', *contentCfgPath, vector=True): k = '#'.join([ cfgTree.getAttrValue('snmp-pdu-type-pattern', *contentCfgPath), x ]) log.info('configuring content ID %s (at %s), composite key: %s' % (contentId, '.'.join(contentCfgPath), k)) contentIdList.append((contentId, re.compile(k))) del duplicates for pluginCfgPath in cfgTree.getPathsToAttr('using-plugin-id-list'): pluginIdList = cfgTree.getAttrValue('using-plugin-id-list', *pluginCfgPath, vector=True) log.info('configuring plugin ID(s) %s (at %s)...' % (','.join(pluginIdList), '.'.join(pluginCfgPath))) for credId in cfgTree.getAttrValue('matching-snmp-credentials-id-list', *pluginCfgPath, vector=True): for peerId in cfgTree.getAttrValue('matching-snmp-peer-id-list', *pluginCfgPath, vector=True): for contextId in cfgTree.getAttrValue( 'matching-snmp-context-id-list', *pluginCfgPath, vector=True): for contentId in cfgTree.getAttrValue( 'matching-snmp-content-id-list', *pluginCfgPath, vector=True): k = credId, contextId, peerId, contentId if k in pluginIdMap: log.error( 'duplicate snmp-credentials-id %s, snmp-context-id %s, snmp-peer-id %s, snmp-content-id %s at plugin-id(s) %s' % (credId, contextId, peerId, contentId, ','.join(pluginIdList))) return else: log.info( 'configuring plugin(s) %s (at %s), composite key: %s' % (','.join(pluginIdList), '.'.join(pluginCfgPath), '/'.join(k))) for pluginId in pluginIdList: if not pluginManager.hasPlugin(pluginId): log.error( 'undefined plugin ID %s referenced at %s' % (pluginId, '.'.join(pluginCfgPath))) return pluginIdMap[k] = pluginIdList for routeCfgPath in cfgTree.getPathsToAttr('using-mib-tree-id'): mibTreeId = cfgTree.getAttrValue('using-mib-tree-id', *routeCfgPath) log.info('configuring destination MIB tree ID(s) %s (at %s)...' % (mibTreeId, '.'.join(routeCfgPath))) for credId in cfgTree.getAttrValue('matching-snmp-credentials-id-list', *routeCfgPath, vector=True): for peerId in cfgTree.getAttrValue('matching-snmp-peer-id-list', *routeCfgPath, vector=True): for contextId in cfgTree.getAttrValue( 'matching-snmp-context-id-list', *routeCfgPath, vector=True): for contentId in cfgTree.getAttrValue( 'matching-snmp-content-id-list', *routeCfgPath, vector=True): k = credId, contextId, peerId, contentId if k in routingMap: log.error( 'duplicate snmp-credentials-id %s, snmp-context-id %s, snmp-peer-id %s, snmp-content-id %s at mib-tree-id(s) %s' % (credId, contextId, peerId, contentId, ','.join(mibTreeIdList))) return else: routingMap[k] = mibTreeId log.info( 'configuring MIB tree routing to %s (at %s), composite key: %s' % (mibTreeId, '.'.join(routeCfgPath), '/'.join(k))) for mibTreeCfgPath in cfgTree.getPathsToAttr('mib-tree-id'): mibTreeId = cfgTree.getAttrValue('mib-tree-id', *mibTreeCfgPath) log.info('configuring MIB tree ID %s (at %s)...' % (mibTreeId, '.'.join(mibTreeCfgPath))) mibTextPaths = cfgTree.getAttrValue('mib-text-search-path-list', *mibTreeCfgPath, default=[], vector=True) mibCodePatternPaths = macro.expandMacros( cfgTree.getAttrValue('mib-code-modules-pattern-list', *mibTreeCfgPath, default=[], vector=True), {'config-dir': os.path.dirname(cfgFile)}) mibBuilder = builder.MibBuilder() compiler.addMibCompiler(mibBuilder, sources=mibTextPaths) for topDir in mibCodePatternPaths: filenameRegExp = re.compile(os.path.basename(topDir)) topDir = os.path.dirname(topDir) for root, dirs, files in os.walk(topDir): if not files or root.endswith('__pycache__'): continue mibBuilder.setMibSources(builder.DirMibSource(root), *mibBuilder.getMibSources()) for filename in files: if not filenameRegExp.match(filename): log.debug( 'skipping non-matching file %s while loading ' 'MIB tree ID %s' % (filename, mibTreeId)) continue module, _ = os.path.splitext(filename) try: mibBuilder.loadModule(module) except PySnmpError as ex: log.error('fail to load MIB implementation from file ' '%s into MIB tree ID %s' % (os.path.join(root, filename), mibTreeId)) raise SnmpResponderError(str(ex)) log.info('loaded MIB implementation file %s into MIB tree ' 'ID %s' % (os.path.join(root, filename), mibTreeId)) mibCodePackages = macro.expandMacros( cfgTree.getAttrValue('mib-code-packages-pattern-list', *mibTreeCfgPath, default=[], vector=True), {'config-dir': os.path.dirname(cfgFile)}) for mibCodePackage in mibCodePackages: mibCodePackageRegExp = re.compile(mibCodePackage) for entryPoint in pkg_resources.iter_entry_points( 'snmpresponder.mibs'): log.debug('found extension entry point %s' % entryPoint.name) mibPackage = entryPoint.load() root = os.path.dirname(mibPackage.__file__) mibPathSet = False for filename in os.listdir(root): if filename.startswith('__init__'): continue if not os.path.isfile(os.path.join(root, filename)): continue mibPath = '.'.join((entryPoint.name, filename)) if not mibCodePackageRegExp.match(mibPath): log.debug( 'extension MIB %s from %s is NOT configured, ' 'skipping' % (mibPath, entryPoint.name)) continue if not mibPathSet: mibBuilder.setMibSources(builder.DirMibSource(root), *mibBuilder.getMibSources()) mibPathSet = True log.debug('loading extension MIB %s from %s into MIB tree ' 'ID %s' % (mibPath, entryPoint.name, mibTreeId)) module, _ = os.path.splitext(filename) try: mibBuilder.loadModule(module) except PySnmpError as ex: log.error('fail to load MIB implementation %s from ' '%s into MIB tree ID %s' % (mibPath, entryPoint.name, mibTreeId)) raise SnmpResponderError(str(ex)) log.info( 'loaded MIB implementation %s from %s into MIB tree ' 'ID %s' % (mibPath, entryPoint.name, mibTreeId)) mibTreeIdMap[mibTreeId] = instrum.MibInstrumController(mibBuilder) log.info('loaded new MIB tree ID %s' % mibTreeId) if not foregroundFlag: try: daemon.daemonize(pidFile) except Exception: log.error('can not daemonize process: %s' % sys.exc_info()[1]) return # Run mainloop log.info('starting I/O engine...') transportDispatcher.jobStarted(1) # server job would never finish with daemon.PrivilegesOf(procUser, procGroup, final=True): while True: try: transportDispatcher.runDispatcher() except (PySnmpError, SnmpResponderError, socket.error): log.error(str(sys.exc_info()[1])) continue except Exception: transportDispatcher.closeDispatcher() raise
def __init__(self, udpIp, udpPort): # Create SNMP engine with autogenernated engineID and pre-bound # to socket transport dispatcher self.snmpEngine = engine.SnmpEngine() self.mibBuilder = self.snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder mibPath = self.mibBuilder.getMibPath() + ('.', ) self.mibBuilder.setMibPath(*mibPath) # Setup UDP over IPv4 transport endpoint config.addSocketTransport( self.snmpEngine, udp.domainName, udp.UdpSocketTransport().openServerMode((udpIp, udpPort))) print 'Publishing readings via SNMP' print 'Agent address {}:{}'.format(udpIp, udpPort) print 'Community name public' # v1/2 setup config.addV1System(self.snmpEngine, 'test-agent', 'public') # v3 setup config.addV3User(self.snmpEngine, 'test-user') # VACM setup config.addContext(self.snmpEngine, '') config.addRwUser(self.snmpEngine, 1, 'test-agent', 'noAuthNoPriv', (1, 3, 6)) # v1 config.addRwUser(self.snmpEngine, 2, 'test-agent', 'noAuthNoPriv', (1, 3, 6)) # v2c config.addRwUser(self.snmpEngine, 3, 'test-user', 'noAuthNoPriv', (1, 3, 6)) # v3 # SNMP context snmpContext = context.SnmpContext(self.snmpEngine) # Apps registration cmdrsp.GetCommandResponder(self.snmpEngine, snmpContext) cmdrsp.SetCommandResponder(self.snmpEngine, snmpContext) cmdrsp.NextCommandResponder(self.snmpEngine, snmpContext) cmdrsp.BulkCommandResponder(self.snmpEngine, snmpContext) MibScalarInstance, = self.mibBuilder.importSymbols( 'SNMPv2-SMI', 'MibScalarInstance') class ScalarFromCallback(MibScalarInstance): def __init__(self, sensorId, valueGetter, typeName, instId, syntax): MibScalarInstance.__init__(self, typeName, instId, syntax) self.valueGetter = valueGetter def readTest(self, name, val, idx, (acFun, acCtx)): if not self.valueGetter(): raise error.NoAccessError(idx=idx, name=name) def readGet(self, name, val, idx, (acFun, acCtx)): value = self.valueGetter() if not value: raise error.NoAccessError(idx=idx, name=name) else: return name, self.syntax.clone(value)
def __init__(self, mibObjects): """ mibObjects - a list of MibObject tuples that this agent will serve """ #each SNMP-based application has an engine self._snmpEngine = engine.SnmpEngine() #open a UDP socket to listen for snmp requests config.addSocketTransport(self._snmpEngine, udp.domainName, udp.UdpTransport().openServerMode(('', 161))) #add a v2 user with the community string public config.addV1System(self._snmpEngine, "agent", "public") config.addV1System(self._snmpEngine, 'write-area', 'private') #let anyone accessing 'public' read anything in the subtree below, #which is the enterprises subtree that we defined our MIB to be in config.addVacmUser(self._snmpEngine, 2, "agent", "noAuthNoPriv", readSubTree=(1,3,6,1,4,1)) config.addVacmUser(self._snmpEngine, 2, 'write-area', 'noAuthNoPriv', readSubTree=(1,3,6,1,4,1), writeSubTree=(1,3,6,1,4,1)) #each app has one or more contexts self._snmpContext = context.SnmpContext(self._snmpEngine) #the builder is used to load mibs. tell it to look in the #current directory for our new MIB. We'll also use it to #export our symbols later mibBuilder = self._snmpContext.getMibInstrum().getMibBuilder() mibSources = mibBuilder.getMibSources() + (builder.DirMibSource('.'),) mibBuilder.setMibSources(*mibSources) Integer, ObjectIdentifier, OctetString = mibBuilder.importSymbols("ASN1", "Integer", "ObjectIdentifier", "OctetString") MibScalar, MibScalarInstance = mibBuilder.importSymbols( 'SNMPv2-SMI', 'MibScalar', 'MibScalarInstance' ) NamedValues, = mibBuilder.importSymbols("ASN1-ENUMERATION", "NamedValues") ConstraintsUnion, SingleValueConstraint, ConstraintsIntersection, ValueSizeConstraint, ValueRangeConstraint = mibBuilder.importSymbols("ASN1-REFINEMENT", "ConstraintsUnion", "SingleValueConstraint", "ConstraintsIntersection", "ValueSizeConstraint", "ValueRangeConstraint") NotificationGroup, ModuleCompliance, ObjectGroup = mibBuilder.importSymbols("SNMPv2-CONF", "NotificationGroup", "ModuleCompliance", "ObjectGroup") Integer32, MibScalar, MibTable, MibTableRow, MibTableColumn, NotificationType, MibIdentifier, IpAddress, TimeTicks, Counter64, Unsigned32, enterprises, iso, Gauge32, ModuleIdentity, ObjectIdentity, Bits, Counter32 = mibBuilder.importSymbols("SNMPv2-SMI", "Integer32", "MibScalar", "MibTable", "MibTableRow", "MibTableColumn", "NotificationType", "MibIdentifier", "IpAddress", "TimeTicks", "Counter64", "Unsigned32", "enterprises", "iso", "Gauge32", "ModuleIdentity", "ObjectIdentity", "Bits", "Counter32") DisplayString, TextualConvention = mibBuilder.importSymbols("SNMPv2-TC", "DisplayString", "TextualConvention") dockerRoot = ObjectIdentity((1, 3, 6, 1, 4, 1, 12345)) if mibBuilder.loadTexts: dockerRoot.setStatus('current') dockerDaemon = MibIdentifier((1, 3, 6, 1, 4, 1, 12345, 1)) dockerDaemonUptime = MibScalar((1, 3, 6, 1, 4, 1, 12345, 1, 1), TimeTicks()).setMaxAccess("readonly") dockerDaemonRestart = MibScalar((1, 3, 6, 1, 4, 1, 12345, 1, 2), Integer32().subtype(subtypeSpec=SingleValueConstraint(0, 1)).clone(namedValues=NamedValues(("notRestarting", 0), ("restaring", 1)))).setMaxAccess("readwrite") if mibBuilder.loadTexts: dockerDaemonRestart.setStatus('current') dockerDaemonMandatoryImplementations = ObjectGroup((1, 3, 6, 1, 4, 1, 12345, 1, 3)).setObjects(("ANDRE-GLOBAL-REG", "dockerDaemonUptime"), ("ANDRE-GLOBAL-REG", "dockerDaemonRestart")) if getattr(mibBuilder, 'version', (0, 0, 0)) > (4, 4, 0): dockerDaemonMandatoryImplementations = dockerDaemonMandatoryImplementations.setStatus('current') dockerContainers = MibIdentifier((1, 3, 6, 1, 4, 1, 12345, 2)) containeListTable = MibTable((1, 3, 6, 1, 4, 1, 12345, 2, 1), ) if mibBuilder.loadTexts: containeListTable.setStatus('current') containeListEntry = MibTableRow((1, 3, 6, 1, 4, 1, 12345, 2, 1, 1), ).setIndexNames((0, "ANDRE-GLOBAL-REG", "containeListHashIdentifier"), (0, "ANDRE-GLOBAL-REG", "containeListUptime"), (0, "ANDRE-GLOBAL-REG", "containeListName"), (0, "ANDRE-GLOBAL-REG", "containeListStatus")) if mibBuilder.loadTexts: containeListEntry.setStatus('current') containeListHashIdentifier = MibTableColumn((1, 3, 6, 1, 4, 1, 12345, 2, 1, 1, 1), OctetString()).setMaxAccess("readonly") if mibBuilder.loadTexts: containeListHashIdentifier.setStatus('current') containeListImageID = MibTableColumn((1, 3, 6, 1, 4, 1, 12345, 2, 1, 1, 2), OctetString()).setMaxAccess("readonly") if mibBuilder.loadTexts: containeListUptime.setStatus('current') containeListName = MibTableColumn((1, 3, 6, 1, 4, 1, 12345, 2, 1, 1, 3), OctetString()).setMaxAccess("readonly") if mibBuilder.loadTexts: containeListName.setStatus('current') containeListStatus = MibTableColumn((1, 3, 6, 1, 4, 1, 12345, 2, 1, 1, 4), OctetString()).setMaxAccess("readonly") if mibBuilder.loadTexts: containeListStatus.setStatus('current') mibBuilder.exportSymbols("ANDRE-GLOBAL-REG", dockerContainers=dockerContainers, containeListName=containeListName, dockerRoot=dockerRoot, containeListEntry=containeListEntry, containeListImageID=containeListImageID, dockerDaemonUptime=dockerDaemonUptime, dockerDaemonRestart=dockerDaemonRestart, containeListStatus=containeListStatus, dockerDaemon=dockerDaemon, dockerDaemonMandatoryImplementations=dockerDaemonMandatoryImplementations, containeListHashIdentifier=containeListHashIdentifier, containeListTable=containeListTable) container_info = getDockerProcesses() docker_informations = loads(check_output(['curl','--unix-socket','/var/run/docker.sock', 'http://localhost/containers/json'])) print docker_informations[0] class DockerDaemonUpTimeMibScalarInstance(MibScalarInstance): def getValue(self, name, idx): out = check_output(['ps', 'aux']) res = [line for line in out.split('\n') if 'MacOS/Docker' in line][0].split()[1] print res if res == [] or res == '': result = 0 return self.getSyntax().clone(result) out = check_output(['ps', '-o', 'etime=','-p',res]) print out try: try: t=datetime.datetime.strptime(out,'%H:%M:%S\n') except: t=datetime.datetime.strptime(out,'%M:%S\n') except: result = 0 return self.getSyntax().clone(result) result = (((t.hour * 60) + t.minute) * 60 + t.second) * 100 return self.getSyntax().clone(result) dockerDaemonRestart class DockerDaemonRestartMibScalarInstance(MibScalarInstance): MaxAccess = "readwrite" def getValue(self, name, idx): return self.getSyntax().clone(0) def setValue(self, value, name, idx): if value == 1: for i in range(len(container_info) - 1): print i Popen(["curl", "-d", "''", "--unix-socket", "/var/run/docker.sock", "http://localhost/containers/" + docker_informations[i]["Id"] + "/restart"]) if hasattr(self.syntax, 'setValue'): return self.syntax.setValue(value) else: return self.syntax.clone(value) class ContaineListHashIdentifierStateInstance(MibScalarInstance): def readGet(self, name, val, *args): try: docker_informations = loads(check_output(['curl','--unix-socket','/var/run/docker.sock', 'http://localhost/containers/json'])) return self.name, self.syntax.clone(docker_informations[name[-1] - 1]["Id"]) except: MibScalarInstance.readGet(self, name, val, *args) class ContaineListNameStateInstance(MibScalarInstance): def readGet(self, name, val, *args): docker_informations = loads(check_output(['curl','--unix-socket','/var/run/docker.sock', 'http://localhost/containers/json'])) return self.name, self.syntax.clone(docker_informations[name[-1] - 1]["Image"]) class ContaineListStatusStateInstance(MibScalarInstance): def readGet(self, name, val, *args): docker_informations = loads(check_output(['curl','--unix-socket','/var/run/docker.sock', 'http://localhost/containers/json'])) return self.name, self.syntax.clone(docker_informations[name[-1] - 1]["Status"]) class ContaineListImageIDStateInstance(MibScalarInstance): def readGet(self, name, val, *args): docker_informations = loads(check_output(['curl','--unix-socket','/var/run/docker.sock', 'http://localhost/containers/json'])) return self.name, self.syntax.clone(docker_informations[name[-1] - 1]["ImageID"]) mibBuilder.exportSymbols("ANDRE-GLOBAL-REG",dockerDaemonUptime, DockerDaemonUpTimeMibScalarInstance((1, 3, 6, 1, 4, 1, 12345, 1, 1),(0,),TimeTicks())) mibBuilder.exportSymbols("ANDRE-GLOBAL-REG",dockerDaemonRestart, DockerDaemonRestartMibScalarInstance(dockerDaemonRestart.getName(),(0,),dockerDaemonRestart.getSyntax()).setMaxAccess("readwrite")) for i in range(len(docker_informations)): mibBuilder.exportSymbols("ANDRE-GLOBAL-REG", ContaineListNameStateInstance(containeListName.getName(), (i+1,), containeListName.getSyntax()), ContaineListHashIdentifierStateInstance(containeListHashIdentifier.getName(), (i+1,), containeListHashIdentifier.getSyntax()), ContaineListStatusStateInstance(containeListStatus.getName(), (i+1,), containeListStatus.getSyntax()), ContaineListImageIDStateInstance(containeListImageID.getName(), (i+1,), containeListImageID.getSyntax()), ) #Export Test Table #our variables will subclass this since we only have scalar types #can't load this type directly, need to import it MibScalarInstance, = mibBuilder.importSymbols('SNMPv2-SMI', 'MibScalarInstance') # tell pysnmp to respotd to get, getnext, and getbulk cmdrsp.GetCommandResponder(self._snmpEngine, self._snmpContext) cmdrsp.SetCommandResponder(self._snmpEngine, self._snmpContext) cmdrsp.NextCommandResponder(self._snmpEngine, self._snmpContext) cmdrsp.BulkCommandResponder(self._snmpEngine, self._snmpContext)
def main(): class CommandResponder(cmdrsp.CommandResponderBase): pduTypes = (rfc1905.SetRequestPDU.tagSet, rfc1905.GetRequestPDU.tagSet, rfc1905.GetNextRequestPDU.tagSet, rfc1905.GetBulkRequestPDU.tagSet) def handleMgmtOperation(self, snmpEngine, stateReference, contextName, pdu, acInfo): trunkReq = gCurrentRequestContext.copy() trunkReq['snmp-pdu'] = pdu pluginIdList = trunkReq['plugins-list'] logCtx = LogString(trunkReq) reqCtx = {} for pluginNum, pluginId in enumerate(pluginIdList): st, pdu = pluginManager.processCommandRequest( pluginId, snmpEngine, pdu, trunkReq, reqCtx) if st == status.BREAK: log.debug('plugin %s inhibits other plugins' % pluginId, ctx=logCtx) pluginIdList = pluginIdList[:pluginNum] break elif st == status.DROP: log.debug( 'received SNMP message, plugin %s muted request' % pluginId, ctx=logCtx) self.releaseStateInformation(stateReference) return elif st == status.RESPOND: log.debug( 'received SNMP message, plugin %s forced immediate response' % pluginId, ctx=logCtx) try: self.sendPdu(snmpEngine, stateReference, pdu) except PySnmpError: log.error('failure sending SNMP response: %s' % sys.exc_info()[1], ctx=logCtx) else: self.releaseStateInformation(stateReference) return # pass query to trunk trunkIdList = trunkReq['trunk-id-list'] if trunkIdList is None: log.error('no route configured', ctx=logCtx) self.releaseStateInformation(stateReference) return for trunkId in trunkIdList: cbCtx = pluginIdList, trunkId, trunkReq, snmpEngine, stateReference, reqCtx try: msgId = trunkingManager.sendReq(trunkId, trunkReq, self.trunkCbFun, cbCtx) except SnmpfwdError: log.error( 'received SNMP message, message not sent to trunk "%s"' % sys.exc_info()[1], ctx=logCtx) return log.debug( 'received SNMP message, forwarded as trunk message #%s' % msgId, ctx=logCtx) def trunkCbFun(self, msgId, trunkRsp, cbCtx): pluginIdList, trunkId, trunkReq, snmpEngine, stateReference, reqCtx = cbCtx for key in tuple(trunkRsp): if key != 'callflow-id': trunkRsp['client-' + key] = trunkRsp[key] del trunkRsp[key] trunkRsp['callflow-id'] = trunkReq['callflow-id'] logCtx = LogString(trunkRsp) if trunkRsp['client-error-indication']: log.info( 'received trunk message #%s, remote end reported error-indication "%s", NOT responding' % (msgId, trunkRsp['client-error-indication']), ctx=logCtx) elif 'client-snmp-pdu' not in trunkRsp: log.info( 'received trunk message #%s, remote end does not send SNMP PDU, NOT responding' % msgId, ctx=logCtx) else: pdu = trunkRsp['client-snmp-pdu'] for pluginId in pluginIdList: st, pdu = pluginManager.processCommandResponse( pluginId, snmpEngine, pdu, trunkReq, reqCtx) if st == status.BREAK: log.debug('plugin %s inhibits other plugins' % pluginId, ctx=logCtx) break elif st == status.DROP: log.debug('plugin %s muted response' % pluginId, ctx=logCtx) self.releaseStateInformation(stateReference) return try: self.sendPdu(snmpEngine, stateReference, pdu) except PySnmpError: log.error('trunk message #%s, SNMP response error: %s' % (msgId, sys.exc_info()[1]), ctx=logCtx) else: log.debug( 'received trunk message #%s, forwarded as SNMP message' % msgId, ctx=logCtx) self.releaseStateInformation(stateReference) # # SNMPv3 NotificationReceiver implementation # class NotificationReceiver(ntfrcv.NotificationReceiver): pduTypes = (rfc1157.TrapPDU.tagSet, rfc1905.SNMPv2TrapPDU.tagSet) def processPdu(self, snmpEngine, messageProcessingModel, securityModel, securityName, securityLevel, contextEngineId, contextName, pduVersion, pdu, maxSizeResponseScopedPDU, stateReference): trunkReq = gCurrentRequestContext.copy() if messageProcessingModel == 0: pdu = rfc2576.v1ToV2(pdu) trunkReq['snmp-pdu'] = pdu pluginIdList = trunkReq['plugins-list'] logCtx = LogString(trunkReq) reqCtx = {} for pluginNum, pluginId in enumerate(pluginIdList): st, pdu = pluginManager.processNotificationRequest( pluginId, snmpEngine, pdu, trunkReq, reqCtx) if st == status.BREAK: log.debug('plugin %s inhibits other plugins' % pluginId, ctx=logCtx) pluginIdList = pluginIdList[:pluginNum] break elif st == status.DROP: log.debug('plugin %s muted request' % pluginId, ctx=logCtx) return elif st == status.RESPOND: log.debug('plugin %s NOT forced immediate response' % pluginId, ctx=logCtx) # TODO: implement immediate response for confirmed-class PDU return # pass query to trunk trunkIdList = trunkReq['trunk-id-list'] if trunkIdList is None: log.error('no route configured', ctx=logCtx) return for trunkId in trunkIdList: # TODO: pass messageProcessingModel to respond cbCtx = pluginIdList, trunkId, trunkReq, snmpEngine, stateReference, reqCtx try: msgId = trunkingManager.sendReq(trunkId, trunkReq, self.trunkCbFun, cbCtx) except SnmpfwdError: log.error( 'received SNMP message, message not sent to trunk "%s" %s' % (trunkId, sys.exc_info()[1]), ctx=logCtx) return log.debug( 'received SNMP message, forwarded as trunk message #%s' % msgId, ctx=logCtx) def trunkCbFun(self, msgId, trunkRsp, cbCtx): pluginIdList, trunkId, trunkReq, snmpEngine, stateReference, reqCtx = cbCtx for key in tuple(trunkRsp): if key != 'callflow-id': trunkRsp['client-' + key] = trunkRsp[key] del trunkRsp[key] trunkRsp['callflow-id'] = trunkReq['callflow-id'] logCtx = LazyLogString(trunkReq, trunkRsp) if trunkRsp['client-error-indication']: log.info( 'received trunk message #%s, remote end reported error-indication "%s", NOT responding' % (msgId, trunkRsp['client-error-indication']), ctx=logCtx) else: if 'client-snmp-pdu' not in trunkRsp: log.debug( 'received trunk message #%s -- unconfirmed SNMP message' % msgId, ctx=logCtx) return pdu = trunkRsp['client-snmp-pdu'] for pluginId in pluginIdList: st, pdu = pluginManager.processNotificationResponse( pluginId, snmpEngine, pdu, trunkReq, reqCtx) if st == status.BREAK: log.debug('plugin %s inhibits other plugins' % pluginId, ctx=logCtx) break elif st == status.DROP: log.debug( 'received trunk message #%s, plugin %s muted response' % (msgId, pluginId), ctx=logCtx) return log.debug( 'received trunk message #%s, forwarded as SNMP message' % msgId, ctx=logCtx) # TODO: implement response part # # Agent-side API complies with SMIv2 # if messageProcessingModel == 0: # PDU = rfc2576.v2ToV1(PDU, origPdu) # # statusInformation = {} # # # 3.4.3 # try: # snmpEngine.msgAndPduDsp.returnResponsePdu( # snmpEngine, messageProcessingModel, securityModel, # securityName, securityLevel, contextEngineId, # contextName, pduVersion, rspPDU, maxSizeResponseScopedPDU, # stateReference, statusInformation) # # except error.StatusInformation: # log.error('processPdu: stateReference %s, statusInformation %s' % (stateReference, sys.exc_info()[1])) class LogString(LazyLogString): GROUPINGS = [ ['callflow-id'], [ 'snmp-engine-id', 'snmp-transport-domain', 'snmp-bind-address', 'snmp-bind-port', 'snmp-security-model', 'snmp-security-level', 'snmp-security-name', 'snmp-credentials-id' ], ['snmp-context-engine-id', 'snmp-context-name', 'snmp-context-id'], ['snmp-pdu', 'snmp-content-id'], ['snmp-peer-address', 'snmp-peer-port', 'snmp-peer-id'], ['trunk-id'], ['client-snmp-pdu'], ] FORMATTERS = { 'client-snmp-pdu': LazyLogString.prettyVarBinds, 'snmp-pdu': LazyLogString.prettyVarBinds, } def securityAuditObserver(snmpEngine, execpoint, variables, cbCtx): securityModel = variables.get('securityModel', 0) logMsg = 'SNMPv%s auth failure' % securityModel logMsg += ' at %s:%s' % variables['transportAddress'].getLocalAddress() logMsg += ' from %s:%s' % variables['transportAddress'] statusInformation = variables.get('statusInformation', {}) if securityModel in (1, 2): logMsg += ' using snmp-community-name "%s"' % statusInformation.get( 'communityName', '?') elif securityModel == 3: logMsg += ' using snmp-usm-user "%s"' % statusInformation.get( 'msgUserName', '?') try: logMsg += ': %s' % statusInformation['errorIndication'] except KeyError: pass log.error(logMsg) def usmRequestObserver(snmpEngine, execpoint, variables, cbCtx): trunkReq = {'snmp-security-engine-id': variables['securityEngineId']} cbCtx.clear() cbCtx.update(trunkReq) def requestObserver(snmpEngine, execpoint, variables, cbCtx): trunkReq = { 'callflow-id': '%10.10x' % random.randint(0, 0xffffffffff), 'snmp-engine-id': snmpEngine.snmpEngineID, 'snmp-transport-domain': variables['transportDomain'], 'snmp-peer-address': variables['transportAddress'][0], 'snmp-peer-port': variables['transportAddress'][1], 'snmp-bind-address': variables['transportAddress'].getLocalAddress()[0], 'snmp-bind-port': variables['transportAddress'].getLocalAddress()[1], 'snmp-security-model': variables['securityModel'], 'snmp-security-level': variables['securityLevel'], 'snmp-security-name': variables['securityName'], 'snmp-context-engine-id': variables['contextEngineId'], 'snmp-context-name': variables['contextName'], } try: trunkReq['snmp-security-engine-id'] = cbCtx.pop( 'snmp-security-engine-id') except KeyError: # SNMPv1/v2c trunkReq['snmp-security-engine-id'] = trunkReq['snmp-engine-id'] trunkReq['snmp-credentials-id'] = macro.expandMacro( credIdMap.get( (str(snmpEngine.snmpEngineID), variables['transportDomain'], variables['securityModel'], variables['securityLevel'], str(variables['securityName']))), trunkReq) k = '#'.join([ str(x) for x in (variables['contextEngineId'], variables['contextName']) ]) for x, y in contextIdList: if y.match(k): trunkReq['snmp-context-id'] = macro.expandMacro(x, trunkReq) break else: trunkReq['snmp-context-id'] = None addr = '%s:%s#%s:%s' % ( variables['transportAddress'][0], variables['transportAddress'][1], variables['transportAddress'].getLocalAddress()[0], variables['transportAddress'].getLocalAddress()[1]) for pat, peerId in peerIdMap.get(str(variables['transportDomain']), ()): if pat.match(addr): trunkReq['snmp-peer-id'] = macro.expandMacro(peerId, trunkReq) break else: trunkReq['snmp-peer-id'] = None pdu = variables['pdu'] if pdu.tagSet == v1.TrapPDU.tagSet: pdu = rfc2576.v1ToV2(pdu) v2c.apiTrapPDU.setDefaults(pdu) k = '#'.join([ snmpPduTypesMap.get(variables['pdu'].tagSet, '?'), '|'.join([str(x[0]) for x in v2c.apiTrapPDU.getVarBinds(pdu)]) ]) for x, y in contentIdList: if y.match(k): trunkReq['snmp-content-id'] = macro.expandMacro(x, trunkReq) break else: trunkReq['snmp-content-id'] = None trunkReq['plugins-list'] = pluginIdMap.get( (trunkReq['snmp-credentials-id'], trunkReq['snmp-context-id'], trunkReq['snmp-peer-id'], trunkReq['snmp-content-id']), []) trunkReq['trunk-id-list'] = trunkIdMap.get( (trunkReq['snmp-credentials-id'], trunkReq['snmp-context-id'], trunkReq['snmp-peer-id'], trunkReq['snmp-content-id'])) cbCtx.clear() cbCtx.update(trunkReq) # # main script starts here # helpMessage = """\ Usage: %s [--help] [--version ] [--debug-snmp=<%s>] [--debug-asn1=<%s>] [--daemonize] [--process-user=<uname>] [--process-group=<gname>] [--pid-file=<file>] [--logging-method=<%s[:args>]>] [--log-level=<%s>] [--config-file=<file>]""" % (sys.argv[0], '|'.join([ x for x in getattr(pysnmp_debug, 'FLAG_MAP', getattr(pysnmp_debug, 'flagMap', ())) if x != 'mibview' ]), '|'.join([ x for x in getattr(pyasn1_debug, 'FLAG_MAP', getattr(pyasn1_debug, 'flagMap', ())) ]), '|'.join(log.methodsMap.keys()), '|'.join(log.levelsMap)) try: opts, params = getopt.getopt(sys.argv[1:], 'hv', [ 'help', 'version', 'debug=', 'debug-snmp=', 'debug-asn1=', 'daemonize', 'process-user='******'process-group=', 'pid-file=', 'logging-method=', 'log-level=', 'config-file=' ]) except Exception: sys.stderr.write('ERROR: %s\r\n%s\r\n' % (sys.exc_info()[1], helpMessage)) return if params: sys.stderr.write('ERROR: extra arguments supplied %s\r\n%s\r\n' % (params, helpMessage)) return pidFile = '' cfgFile = CONFIG_FILE foregroundFlag = True procUser = procGroup = None loggingMethod = ['stderr'] loggingLevel = None for opt in opts: if opt[0] == '-h' or opt[0] == '--help': sys.stderr.write("""\ Synopsis: SNMP Proxy Forwarder: server part. Receives SNMP requests at one or many built-in SNMP Agents and routes them to encrypted trunks established with Forwarder's Manager part(s) running elsewhere. Can implement complex routing logic through analyzing parts of SNMP messages and matching them against proxy rules. Documentation: http://snmplabs.com/snmpfwd/ %s """ % helpMessage) return if opt[0] == '-v' or opt[0] == '--version': import snmpfwd import pysnmp import pyasn1 sys.stderr.write("""\ SNMP Proxy Forwarder version %s, written by Ilya Etingof <*****@*****.**> Using foundation libraries: pysnmp %s, pyasn1 %s. Python interpreter: %s Software documentation and support at http://snmplabs.com/snmpfwd/ %s """ % (snmpfwd.__version__, hasattr(pysnmp, '__version__') and pysnmp.__version__ or 'unknown', hasattr(pyasn1, '__version__') and pyasn1.__version__ or 'unknown', sys.version, helpMessage)) return elif opt[0] == '--debug-snmp': pysnmp_debug.setLogger( pysnmp_debug.Debug(*opt[1].split(','), **dict(loggerName=PROGRAM_NAME + '.pysnmp'))) elif opt[0] == '--debug-asn1': pyasn1_debug.setLogger( pyasn1_debug.Debug(*opt[1].split(','), **dict(loggerName=PROGRAM_NAME + '.pyasn1'))) elif opt[0] == '--daemonize': foregroundFlag = False elif opt[0] == '--process-user': procUser = opt[1] elif opt[0] == '--process-group': procGroup = opt[1] elif opt[0] == '--pid-file': pidFile = opt[1] elif opt[0] == '--logging-method': loggingMethod = opt[1].split(':') elif opt[0] == '--log-level': loggingLevel = opt[1] elif opt[0] == '--config-file': cfgFile = opt[1] with daemon.PrivilegesOf(procUser, procGroup): try: log.setLogger(PROGRAM_NAME, *loggingMethod, **dict(force=True)) if loggingLevel: log.setLevel(loggingLevel) except SnmpfwdError: sys.stderr.write('%s\r\n%s\r\n' % (sys.exc_info()[1], helpMessage)) return try: cfgTree = cparser.Config().load(cfgFile) except SnmpfwdError: log.error('configuration parsing error: %s' % sys.exc_info()[1]) return if cfgTree.getAttrValue('program-name', '', default=None) != PROGRAM_NAME: log.error('config file %s does not match program name %s' % (cfgFile, PROGRAM_NAME)) return if cfgTree.getAttrValue('config-version', '', default=None) != CONFIG_VERSION: log.error( 'config file %s version is not compatible with program version %s' % (cfgFile, CONFIG_VERSION)) return random.seed() gCurrentRequestContext = {} credIdMap = {} peerIdMap = {} contextIdList = [] contentIdList = [] pluginIdMap = {} trunkIdMap = {} engineIdMap = {} transportDispatcher = AsynsockDispatcher() transportDispatcher.registerRoutingCbFun(lambda td, t, d: td) transportDispatcher.setSocketMap() # use global asyncore socket map # # Initialize plugin modules # pluginManager = PluginManager(macro.expandMacros( cfgTree.getAttrValue('plugin-modules-path-list', '', default=[], vector=True), {'config-dir': os.path.dirname(cfgFile)}), progId=PROGRAM_NAME, apiVer=PLUGIN_API_VERSION) for pluginCfgPath in cfgTree.getPathsToAttr('plugin-id'): pluginId = cfgTree.getAttrValue('plugin-id', *pluginCfgPath) pluginMod = cfgTree.getAttrValue('plugin-module', *pluginCfgPath) pluginOptions = macro.expandMacros( cfgTree.getAttrValue('plugin-options', *pluginCfgPath, **dict(default=[], vector=True)), {'config-dir': os.path.dirname(cfgFile)}) log.info( 'configuring plugin ID %s (at %s) from module %s with options %s...' % (pluginId, '.'.join(pluginCfgPath), pluginMod, ', '.join(pluginOptions) or '<none>')) with daemon.PrivilegesOf(procUser, procGroup): try: pluginManager.loadPlugin(pluginId, pluginMod, pluginOptions) except SnmpfwdError: log.error('plugin %s not loaded: %s' % (pluginId, sys.exc_info()[1])) return for configEntryPath in cfgTree.getPathsToAttr('snmp-credentials-id'): credId = cfgTree.getAttrValue('snmp-credentials-id', *configEntryPath) configKey = [] log.info('configuring snmp-credentials %s (at %s)...' % (credId, '.'.join(configEntryPath))) engineId = cfgTree.getAttrValue('snmp-engine-id', *configEntryPath) if engineId in engineIdMap: snmpEngine, snmpContext, snmpEngineMap = engineIdMap[engineId] log.info('using engine-id %s' % snmpEngine.snmpEngineID.prettyPrint()) else: snmpEngine = engine.SnmpEngine(snmpEngineID=engineId) snmpContext = context.SnmpContext(snmpEngine) snmpEngineMap = {'transportDomain': {}, 'securityName': {}} snmpEngine.observer.registerObserver( securityAuditObserver, 'rfc2576.prepareDataElements:sm-failure', 'rfc3412.prepareDataElements:sm-failure', cbCtx=gCurrentRequestContext) snmpEngine.observer.registerObserver( requestObserver, 'rfc3412.receiveMessage:request', cbCtx=gCurrentRequestContext) snmpEngine.observer.registerObserver(usmRequestObserver, 'rfc3414.processIncomingMsg', cbCtx=gCurrentRequestContext) CommandResponder(snmpEngine, snmpContext) NotificationReceiver(snmpEngine, None) engineIdMap[engineId] = snmpEngine, snmpContext, snmpEngineMap log.info('new engine-id %s' % snmpEngine.snmpEngineID.prettyPrint()) configKey.append(str(snmpEngine.snmpEngineID)) transportDomain = cfgTree.getAttrValue('snmp-transport-domain', *configEntryPath) transportDomain = rfc1902.ObjectName(transportDomain) if (transportDomain[:len(udp.domainName)] != udp.domainName and udp6 and transportDomain[:len(udp6.domainName)] != udp6.domainName): log.error('unknown transport domain %s' % (transportDomain, )) return if transportDomain in snmpEngineMap['transportDomain']: bindAddr, transportDomain = snmpEngineMap['transportDomain'][ transportDomain] log.info('using transport endpoint [%s]:%s, transport ID %s' % (bindAddr[0], bindAddr[1], transportDomain)) else: bindAddr = cfgTree.getAttrValue('snmp-bind-address', *configEntryPath) transportOptions = cfgTree.getAttrValue( 'snmp-transport-options', *configEntryPath, **dict(default=[], vector=True)) try: bindAddr, bindAddrMacro = endpoint.parseTransportAddress( transportDomain, bindAddr, transportOptions) except SnmpfwdError: log.error('bad snmp-bind-address specification %s at %s' % (bindAddr, '.'.join(configEntryPath))) return if transportDomain[:len(udp.domainName)] == udp.domainName: transport = udp.UdpTransport() else: transport = udp6.Udp6Transport() t = transport.openServerMode(bindAddr) if 'transparent-proxy' in transportOptions: t.enablePktInfo() t.enableTransparent() elif 'virtual-interface' in transportOptions: t.enablePktInfo() snmpEngine.registerTransportDispatcher(transportDispatcher, transportDomain) config.addSocketTransport(snmpEngine, transportDomain, t) snmpEngineMap['transportDomain'][ transportDomain] = bindAddr, transportDomain log.info( 'new transport endpoint [%s]:%s, options %s, transport ID %s' % (bindAddr[0], bindAddr[1], transportOptions and '/'.join(transportOptions) or '<none>', transportDomain)) configKey.append(transportDomain) securityModel = cfgTree.getAttrValue('snmp-security-model', *configEntryPath) securityModel = rfc1902.Integer(securityModel) securityLevel = cfgTree.getAttrValue('snmp-security-level', *configEntryPath) securityLevel = rfc1902.Integer(securityLevel) securityName = cfgTree.getAttrValue('snmp-security-name', *configEntryPath) if securityModel in (1, 2): if securityName in snmpEngineMap['securityName']: if snmpEngineMap['securityName'][ securityModel] == securityModel: log.info('using security-name %s' % securityName) else: raise SnmpfwdError( 'snmp-security-name %s already in use at snmp-security-model %s' % (securityName, securityModel)) else: communityName = cfgTree.getAttrValue('snmp-community-name', *configEntryPath) config.addV1System(snmpEngine, securityName, communityName, securityName=securityName) log.info( 'new community-name %s, security-model %s, security-name %s, security-level %s' % (communityName, securityModel, securityName, securityLevel)) snmpEngineMap['securityName'][securityName] = securityModel configKey.append(securityModel) configKey.append(securityLevel) configKey.append(securityName) elif securityModel == 3: if securityName in snmpEngineMap['securityName']: log.info('using USM security-name: %s' % securityName) else: usmUser = cfgTree.getAttrValue('snmp-usm-user', *configEntryPath) securityEngineId = cfgTree.getAttrValue( 'snmp-security-engine-id', *configEntryPath, **dict(default=None)) if securityEngineId: securityEngineId = rfc1902.OctetString(securityEngineId) log.info( 'new USM user %s, security-model %s, security-level %s, ' 'security-name %s, security-engine-id %s' % (usmUser, securityModel, securityLevel, securityName, securityEngineId and securityEngineId.prettyPrint() or '<none>')) if securityLevel in (2, 3): usmAuthProto = cfgTree.getAttrValue( 'snmp-usm-auth-protocol', *configEntryPath, **dict(default=config.usmHMACMD5AuthProtocol)) try: usmAuthProto = authProtocols[usmAuthProto.upper()] except KeyError: pass usmAuthProto = rfc1902.ObjectName(usmAuthProto) usmAuthKey = cfgTree.getAttrValue('snmp-usm-auth-key', *configEntryPath) log.info( 'new USM authentication key: %s, authentication protocol: %s' % (usmAuthKey, usmAuthProto)) if securityLevel == 3: usmPrivProto = cfgTree.getAttrValue( 'snmp-usm-priv-protocol', *configEntryPath, **dict(default=config.usmDESPrivProtocol)) try: usmPrivProto = privProtocols[usmPrivProto.upper()] except KeyError: pass usmPrivProto = rfc1902.ObjectName(usmPrivProto) usmPrivKey = cfgTree.getAttrValue( 'snmp-usm-priv-key', *configEntryPath, **dict(default=None)) log.info( 'new USM encryption key: %s, encryption protocol: %s' % (usmPrivKey, usmPrivProto)) config.addV3User(snmpEngine, usmUser, usmAuthProto, usmAuthKey, usmPrivProto, usmPrivKey, securityEngineId=securityEngineId) else: config.addV3User(snmpEngine, usmUser, usmAuthProto, usmAuthKey, securityEngineId=securityEngineId) else: config.addV3User(snmpEngine, usmUser, securityEngineId=securityEngineId) snmpEngineMap['securityName'][securityName] = securityModel configKey.append(securityModel) configKey.append(securityLevel) configKey.append(securityName) else: raise SnmpfwdError('unknown snmp-security-model: %s' % securityModel) configKey = tuple(configKey) if configKey in credIdMap: log.error( 'ambiguous configuration for key snmp-credentials-id=%s at %s' % (credId, '.'.join(configEntryPath))) return credIdMap[configKey] = credId duplicates = {} for peerCfgPath in cfgTree.getPathsToAttr('snmp-peer-id'): peerId = cfgTree.getAttrValue('snmp-peer-id', *peerCfgPath) if peerId in duplicates: log.error( 'duplicate snmp-peer-id=%s at %s and %s' % (peerId, '.'.join(peerCfgPath), '.'.join(duplicates[peerId]))) return duplicates[peerId] = peerCfgPath log.info('configuring peer ID %s (at %s)...' % (peerId, '.'.join(peerCfgPath))) transportDomain = cfgTree.getAttrValue('snmp-transport-domain', *peerCfgPath) if transportDomain not in peerIdMap: peerIdMap[transportDomain] = [] for peerAddress in cfgTree.getAttrValue( 'snmp-peer-address-pattern-list', *peerCfgPath, **dict(vector=True)): for bindAddress in cfgTree.getAttrValue( 'snmp-bind-address-pattern-list', *peerCfgPath, **dict(vector=True)): peerIdMap[transportDomain].append( (re.compile(peerAddress + '#' + bindAddress), peerId)) duplicates = {} for contextCfgPath in cfgTree.getPathsToAttr('snmp-context-id'): contextId = cfgTree.getAttrValue('snmp-context-id', *contextCfgPath) if contextId in duplicates: log.error('duplicate snmp-context-id=%s at %s and %s' % (contextId, '.'.join(contextCfgPath), '.'.join( duplicates[contextId]))) return duplicates[contextId] = contextCfgPath k = '#'.join((cfgTree.getAttrValue('snmp-context-engine-id-pattern', *contextCfgPath), cfgTree.getAttrValue('snmp-context-name-pattern', *contextCfgPath))) log.info('configuring context ID %s (at %s), composite key: %s' % (contextId, '.'.join(contextCfgPath), k)) contextIdList.append((contextId, re.compile(k))) duplicates = {} for contentCfgPath in cfgTree.getPathsToAttr('snmp-content-id'): contentId = cfgTree.getAttrValue('snmp-content-id', *contentCfgPath) if contentId in duplicates: log.error('duplicate snmp-content-id=%s at %s and %s' % (contentId, '.'.join(contentCfgPath), '.'.join( duplicates[contentId]))) return duplicates[contentId] = contentCfgPath for x in cfgTree.getAttrValue('snmp-pdu-oid-prefix-pattern-list', *contentCfgPath, **dict(vector=True)): k = '#'.join([ cfgTree.getAttrValue('snmp-pdu-type-pattern', *contentCfgPath), x ]) log.info('configuring content ID %s (at %s), composite key: %s' % (contentId, '.'.join(contentCfgPath), k)) contentIdList.append((contentId, re.compile(k))) del duplicates for pluginCfgPath in cfgTree.getPathsToAttr('using-plugin-id-list'): pluginIdList = cfgTree.getAttrValue('using-plugin-id-list', *pluginCfgPath, **dict(vector=True)) log.info('configuring plugin ID(s) %s (at %s)...' % (','.join(pluginIdList), '.'.join(pluginCfgPath))) for credId in cfgTree.getAttrValue('matching-snmp-credentials-id-list', *pluginCfgPath, **dict(vector=True)): for peerId in cfgTree.getAttrValue('matching-snmp-peer-id-list', *pluginCfgPath, **dict(vector=True)): for contextId in cfgTree.getAttrValue( 'matching-snmp-context-id-list', *pluginCfgPath, **dict(vector=True)): for contentId in cfgTree.getAttrValue( 'matching-snmp-content-id-list', *pluginCfgPath, **dict(vector=True)): k = credId, contextId, peerId, contentId if k in pluginIdMap: log.error( 'duplicate snmp-credentials-id %s, snmp-context-id %s, snmp-peer-id %s, snmp-content-id %s at plugin-id(s) %s' % (credId, contextId, peerId, contentId, ','.join(pluginIdList))) return else: log.info( 'configuring plugin(s) %s (at %s), composite key: %s' % (','.join(pluginIdList), '.'.join(pluginCfgPath), '/'.join(k))) for pluginId in pluginIdList: if not pluginManager.hasPlugin(pluginId): log.error( 'undefined plugin ID %s referenced at %s' % (pluginId, '.'.join(pluginCfgPath))) return pluginIdMap[k] = pluginIdList for routeCfgPath in cfgTree.getPathsToAttr('using-trunk-id-list'): trunkIdList = cfgTree.getAttrValue('using-trunk-id-list', *routeCfgPath, **dict(vector=True)) log.info('configuring destination trunk ID(s) %s (at %s)...' % (','.join(trunkIdList), '.'.join(routeCfgPath))) for credId in cfgTree.getAttrValue('matching-snmp-credentials-id-list', *routeCfgPath, **dict(vector=True)): for peerId in cfgTree.getAttrValue('matching-snmp-peer-id-list', *routeCfgPath, **dict(vector=True)): for contextId in cfgTree.getAttrValue( 'matching-snmp-context-id-list', *routeCfgPath, **dict(vector=True)): for contentId in cfgTree.getAttrValue( 'matching-snmp-content-id-list', *routeCfgPath, **dict(vector=True)): k = credId, contextId, peerId, contentId if k in trunkIdMap: log.error( 'duplicate snmp-credentials-id %s, snmp-context-id %s, snmp-peer-id %s, snmp-content-id %s at trunk-id(s) %s' % (credId, contextId, peerId, contentId, ','.join(trunkIdList))) return else: trunkIdMap[k] = trunkIdList log.info( 'configuring trunk routing to %s (at %s), composite key: %s' % (','.join(trunkIdList), '.'.join(routeCfgPath), '/'.join(k))) def dataCbFun(trunkId, msgId, msg): log.debug('message ID %s received from trunk %s' % (msgId, trunkId)) trunkingManager = TrunkingManager(dataCbFun) for trunkCfgPath in cfgTree.getPathsToAttr('trunk-id'): trunkId = cfgTree.getAttrValue('trunk-id', *trunkCfgPath) secret = cfgTree.getAttrValue('trunk-crypto-key', *trunkCfgPath, **dict(default='')) secret = secret and (secret * ((16 // len(secret)) + 1))[:16] log.info('configuring trunk ID %s (at %s)...' % (trunkId, '.'.join(trunkCfgPath))) connectionMode = cfgTree.getAttrValue('trunk-connection-mode', *trunkCfgPath) if connectionMode == 'client': trunkingManager.addClient( trunkId, parseTrunkEndpoint( cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath)), parseTrunkEndpoint( cfgTree.getAttrValue('trunk-peer-address', *trunkCfgPath), 30201), cfgTree.getAttrValue('trunk-ping-period', *trunkCfgPath, default=0, expect=int), secret) log.info( 'new trunking client from %s to %s' % (cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath), cfgTree.getAttrValue('trunk-peer-address', *trunkCfgPath))) if connectionMode == 'server': trunkingManager.addServer( parseTrunkEndpoint( cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath), 30201), cfgTree.getAttrValue('trunk-ping-period', *trunkCfgPath, default=0, expect=int), secret) log.info( 'new trunking server at %s' % (cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath))) transportDispatcher.registerTimerCbFun(trunkingManager.setupTrunks, random.randrange(1, 5)) transportDispatcher.registerTimerCbFun(trunkingManager.monitorTrunks, random.randrange(1, 5)) if not foregroundFlag: try: daemon.daemonize(pidFile) except Exception: log.error('can not daemonize process: %s' % sys.exc_info()[1]) return # Run mainloop log.info('starting I/O engine...') transportDispatcher.jobStarted(1) # server job would never finish # Python 2.4 does not support the "finally" clause with daemon.PrivilegesOf(procUser, procGroup, final=True): while True: try: transportDispatcher.runDispatcher() except (PySnmpError, SnmpfwdError, socket.error): log.error(str(sys.exc_info()[1])) continue except Exception: transportDispatcher.closeDispatcher() raise
def _setup(self, q, port): """Setup a new agent in a separate process. The port the agent is listening too will be returned using the provided queue. """ snmpEngine = engine.SnmpEngine() if self.ipv6: config.addSocketTransport( snmpEngine, udp6.domainName, udp6.Udp6Transport().openServerMode(('::1', port))) else: config.addSocketTransport( snmpEngine, udp.domainName, udp.UdpTransport().openServerMode(('127.0.0.1', port))) # Community is public and MIB is writable config.addV1System(snmpEngine, 'read-write', self.community) config.addVacmUser(snmpEngine, 1, 'read-write', 'noAuthNoPriv', (1, 3, 6), (1, 3, 6)) config.addVacmUser(snmpEngine, 2, 'read-write', 'noAuthNoPriv', (1, 3, 6), (1, 3, 6)) config.addV3User( snmpEngine, 'read-write', config.usmHMACMD5AuthProtocol, self.authpass, config.usmAesCfb128Protocol, self.privpass) config.addVacmUser(snmpEngine, 3, 'read-write', 'authPriv', (1, 3, 6), (1, 3, 6)) # Build MIB def stringToOid(string): return [ord(x) for x in string] def flatten(*args): result = [] for el in args: if isinstance(el, (list, tuple)): for sub in el: result.append(sub) else: result.append(el) return tuple(result) snmpContext = context.SnmpContext(snmpEngine) mibBuilder = snmpContext.getMibInstrum().getMibBuilder() (MibTable, MibTableRow, MibTableColumn, MibScalar, MibScalarInstance) = mibBuilder.importSymbols( 'SNMPv2-SMI', 'MibTable', 'MibTableRow', 'MibTableColumn', 'MibScalar', 'MibScalarInstance') class RandomMibScalarInstance(MibScalarInstance): previous_value = 0 def getValue(self, name, idx): self.previous_value += random.randint(1, 2000) return self.getSyntax().clone(self.previous_value) mibBuilder.exportSymbols( '__MY_SNMPv2_MIB', # SNMPv2-MIB::sysDescr MibScalar((1, 3, 6, 1, 2, 1, 1, 1), v2c.OctetString()), MibScalarInstance((1, 3, 6, 1, 2, 1, 1, 1), (0,), v2c.OctetString( "Snimpy Test Agent {0}".format( self.community))), # SNMPv2-MIB::sysObjectID MibScalar((1, 3, 6, 1, 2, 1, 1, 2), v2c.ObjectIdentifier()), MibScalarInstance((1, 3, 6, 1, 2, 1, 1, 2), (0,), v2c.ObjectIdentifier((1, 3, 6, 1, 4, 1, 9, 1, 1208)))) mibBuilder.exportSymbols( '__MY_IF_MIB', # IF-MIB::ifNumber MibScalar((1, 3, 6, 1, 2, 1, 2, 1), v2c.Integer()), MibScalarInstance((1, 3, 6, 1, 2, 1, 2, 1), (0,), v2c.Integer(3)), # IF-MIB::ifTable MibTable((1, 3, 6, 1, 2, 1, 2, 2)), MibTableRow((1, 3, 6, 1, 2, 1, 2, 2, 1)).setIndexNames( (0, '__MY_IF_MIB', 'ifIndex')), # IF-MIB::ifIndex MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 1), (1,), v2c.Integer(1)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 1), (2,), v2c.Integer(2)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 1), (3,), v2c.Integer(3)), # IF-MIB::ifDescr MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 2), v2c.OctetString()), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 2), (1,), v2c.OctetString("lo")), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 2), (2,), v2c.OctetString("eth0")), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 2), (3,), v2c.OctetString("eth1")), # IF-MIB::ifType MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 3), v2c.Integer()), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 3), (1,), v2c.Integer(24)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 3), (2,), v2c.Integer(6)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 3), (3,), v2c.Integer(6)), # IF-MIB::ifInOctets MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 10), v2c.Integer()), RandomMibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 10), (1,), v2c.Gauge32()), RandomMibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 10), (2,), v2c.Gauge32()), RandomMibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 10), (3,), v2c.Gauge32()), # IF-MIB::ifIndex ifIndex=MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 1), v2c.Integer())) args = ( '__MY_SNIMPY-MIB', # SNIMPY-MIB::snimpyIpAddress MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 1), v2c.OctetString()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 1), (0,), v2c.OctetString("AAAA")), # SNIMPY-MIB::snimpyString MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 2), v2c.OctetString()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 2), (0,), v2c.OctetString("bye")), # SNIMPY-MIB::snimpyInteger MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 3), v2c.Integer()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 3), (0,), v2c.Integer(19)), # SNIMPY-MIB::snimpyEnum MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 4), v2c.Integer()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 4), (0,), v2c.Integer(2)), # SNIMPY-MIB::snimpyObjectId MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 5), v2c.ObjectIdentifier()).setMaxAccess("readwrite"), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 5), ( 0,), v2c.ObjectIdentifier((1, 3, 6, 4454, 0, 0))), # SNIMPY-MIB::snimpyBoolean MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 6), v2c.Integer()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 6), (0,), v2c.Integer(1)), # SNIMPY-MIB::snimpyCounter MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 7), v2c.Counter32()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 7), (0,), v2c.Counter32(47)), # SNIMPY-MIB::snimpyGauge MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 8), v2c.Gauge32()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 8), (0,), v2c.Gauge32(18)), # SNIMPY-MIB::snimpyTimeticks MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 9), v2c.TimeTicks()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 9), (0,), v2c.TimeTicks(12111100)), # SNIMPY-MIB::snimpyCounter64 MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 10), v2c.Counter64()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 10), (0,), v2c.Counter64(2 ** 48 + 3)), # SNIMPY-MIB::snimpyBits MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 11), v2c.OctetString()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 11), (0,), v2c.OctetString(b"\xa0")), # SNIMPY-MIB::snimpyMacAddress MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 15), v2c.OctetString()).setMaxAccess("readwrite"), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 15), ( 0,), v2c.OctetString(b"\x11\x12\x13\x14\x15\x16")), # SNIMPY-MIB::snimpyMacAddressInvalid MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 16), v2c.OctetString()).setMaxAccess("readwrite"), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 16), ( 0,), v2c.OctetString(b"\xf1\x12\x13\x14\x15\x16")), # SNIMPY-MIB::snimpyIndexTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 3)), MibTableRow( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1)).setIndexNames( (0, "__MY_SNIMPY-MIB", "snimpyIndexVarLen"), (0, "__MY_SNIMPY-MIB", "snimpyIndexOidVarLen"), (0, "__MY_SNIMPY-MIB", "snimpyIndexFixedLen"), (1, "__MY_SNIMPY-MIB", "snimpyIndexImplied")), # SNIMPY-MIB::snimpyIndexVarLen MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 1), flatten(4, stringToOid('row1'), 3, 1, 2, 3, stringToOid('alpha5'), stringToOid('end of row1')), v2c.OctetString(b"row1")), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 1), flatten(4, stringToOid('row2'), 4, 1, 0, 2, 3, stringToOid('beta32'), stringToOid('end of row2')), v2c.OctetString(b"row2")), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 1), flatten(4, stringToOid('row3'), 4, 120, 1, 2, 3, stringToOid('gamma7'), stringToOid('end of row3')), v2c.OctetString(b"row3")), # SNIMPY-MIB::snimpyIndexInt MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6), flatten(4, stringToOid('row1'), 3, 1, 2, 3, stringToOid('alpha5'), stringToOid('end of row1')), v2c.Integer(4571)), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6), flatten(4, stringToOid('row2'), 4, 1, 0, 2, 3, stringToOid('beta32'), stringToOid('end of row2')), v2c.Integer(78741)), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6), flatten(4, stringToOid('row3'), 4, 120, 1, 2, 3, stringToOid('gamma7'), stringToOid('end of row3')), v2c.Integer(4110)), # SNIMPY-MIB::snimpyInvalidTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 5)), MibTableRow( (1, 3, 6, 1, 2, 1, 45121, 2, 5, 1)).setIndexNames( (0, "__MY_SNIMPY-MIB", "snimpyInvalidIndex")), # SNIMPY-MIB::snimpyInvalidDescr MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 2), (1,), v2c.OctetString(b"Hello")), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 2), (2,), v2c.OctetString(b"\xf1\x12\x13\x14\x15\x16"))) if self.emptyTable: args += ( # SNIMPY-MIB::snimpyEmptyTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 6)), MibTableRow( (1, 3, 6, 1, 2, 1, 45121, 2, 6, 1)).setIndexNames( (0, "__MY_SNIMPY-MIB", "snimpyEmptyIndex"))) kwargs = dict( # Indexes snimpyIndexVarLen=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 1), v2c.OctetString( )), snimpyIndexIntIndex=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 2), v2c.Integer( )).setMaxAccess( "noaccess"), snimpyIndexOidVarLen=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 3), v2c.ObjectIdentifier( )).setMaxAccess( "noaccess"), snimpyIndexFixedLen=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 4), v2c.OctetString( ).setFixedLength( 6)).setMaxAccess( "noaccess"), snimpyIndexImplied=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 5), v2c.OctetString( )).setMaxAccess("noaccess"), snimpyIndexInt=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6), v2c.Integer()).setMaxAccess("readwrite"), snimpyInvalidIndex=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 1), v2c.Integer()).setMaxAccess("noaccess"), snimpyInvalidDescr=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 2), v2c.OctetString()).setMaxAccess("readwrite") ) if self.emptyTable: kwargs.update(dict( snimpyEmptyIndex=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 6, 1, 1), v2c.Integer()).setMaxAccess("noaccess"), snimpyEmptyDescr=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 6, 1, 2), v2c.OctetString()).setMaxAccess("readwrite"))) mibBuilder.exportSymbols(*args, **kwargs) # Start agent cmdrsp.GetCommandResponder(snmpEngine, snmpContext) cmdrsp.SetCommandResponder(snmpEngine, snmpContext) cmdrsp.NextCommandResponder(snmpEngine, snmpContext) cmdrsp.BulkCommandResponder(snmpEngine, snmpContext) q.put(port) snmpEngine.transportDispatcher.jobStarted(1) snmpEngine.transportDispatcher.runDispatcher()
def trigger_trap(temp): # Create SNMP engine instance snmpEngine = engine.SnmpEngine() # SecurityName <-> CommunityName mapping config.addV1System(snmpEngine, 'my-area', 'federated') # Specify security settings per SecurityName (SNMPv2c -> 1) config.addTargetParams(snmpEngine, 'my-creds', 'my-area', 'noAuthNoPriv', 1) # Setup transport endpoint and bind it with security settings yielding # a target name config.addSocketTransport( snmpEngine, udp.domainName, udp.UdpSocketTransport().openClientMode() ) config.addTargetAddr( snmpEngine, 'my-nms', udp.domainName, ('34.215.95.184', 162), 'my-creds', tagList='all-my-managers' ) # Specify what kind of notification should be sent (TRAP or INFORM), # to what targets (chosen by tag) and what filter should apply to # the set of targets (selected by tag) config.addNotificationTarget( snmpEngine, 'my-notification', 'my-filter', 'all-my-managers', 'trap' ) # Allow NOTIFY access to Agent's MIB by this SNMP model (2), securityLevel # and SecurityName config.addContext(snmpEngine, '') config.addVacmUser(snmpEngine, 2, 'my-area', 'noAuthNoPriv', (), (), (1,3,6)) # *** SNMP engine configuration is complete by this line *** # Create default SNMP context where contextEngineId == SnmpEngineId snmpContext = context.SnmpContext(snmpEngine) # Create Notification Originator App instance. ntfOrg = ntforg.NotificationOriginator(snmpContext) # Build and submit notification message to dispatcher ntfOrg.sendNotification( snmpEngine, # Notification targets 'my-notification', # Trap OID (SNMPv2-MIB::coldStart) #(1,3,6,1,6,3,1,1,5,1), (1,3,6,1,4,1,8072,2,7,1,1,1,1,3,1), # ( (oid, value), ... ) ( ((1,3,6,1,4,1,8072,2,7,1,1,1,1,3,1), v2c.OctetString(temp)), ((1,3,6,1,2,1,1,5,0), v2c.OctetString('Reason: Admin brought down')) ) ) print('Notification is scheduled to be sent') # Run I/O dispatcher which would send pending message and process response snmpEngine.transportDispatcher.runDispatcher()
import sys from pysnmp.entity import engine, config from pysnmp.entity.rfc3413 import cmdrsp, context from pysnmp.carrier.asyncore.dgram import udp from pysnmp.proto.api import v2c # Create SNMP engine snmpEngine = engine.SnmpEngine() # Transport setup # UDP over IPv4 config.addTransport(snmpEngine, udp.domainName, udp.UdpTransport().openServerMode(('127.0.0.1', 1610))) # SNMPv2c setup # SecurityName <-> CommunityName mapping. config.addV1System(snmpEngine, 'my-area', 'public') # Allow read MIB access for this user / securityModels at VACM config.addVacmUser(snmpEngine, 2, 'my-area', 'noAuthNoPriv', (1, 3, 6, 1, 4, 1)) # Create an SNMP context snmpContext = context.SnmpContext(snmpEngine) # --- create custom Managed Object Instance --- mibBuilder = snmpContext.getMibInstrum().getMibBuilder()
* with USM user 'usr-md5-des', auth: MD5, priv DES * allow access to SNMPv2-MIB objects (1.3.6.1.2.1) * over IPv4/UDP, listening at 127.0.0.1:161 The following Net-SNMP command will walk this Agent: | $ snmpwalk -v3 -u usr-md5-des -l authPriv -A authkey1 -X privkey1 -e 8000000004030201 localhost .1.3.6 """# from pysnmp.entity import engine, config from pysnmp.entity.rfc3413 import cmdrsp, context from pysnmp.carrier.asyncore.dgram import udp from pysnmp.proto import rfc1902 # Create SNMP engine snmpEngine = engine.SnmpEngine( rfc1902.OctetString(hexValue='8000000004030201')) # Transport setup # UDP over IPv4 config.addTransport(snmpEngine, udp.DOMAIN_NAME, udp.UdpTransport().openServerMode(('127.0.0.1', 161))) # SNMPv3/USM setup # user: usr-md5-des, auth: MD5, priv DES config.addV3User(snmpEngine, 'usr-md5-des', config.USM_AUTH_HMAC96_MD5, 'authkey1', config.USM_PRIV_CBC56_DES, 'privkey1') # Allow full MIB access for each user at VACM config.addVacmUser(snmpEngine, 3, 'usr-md5-des', 'authPriv',
def main(): parser = argparse.ArgumentParser(add_help=False) parser.add_argument( '-v', '--version', action='version', version=utils.TITLE) parser.add_argument( '-h', action='store_true', dest='usage', help='Brief usage message') parser.add_argument( '--help', action='store_true', help='Detailed help message') parser.add_argument( '--quiet', action='store_true', help='Do not print out informational messages') parser.add_argument( '--debug', choices=pysnmp_debug.flagMap, action='append', type=str, default=[], help='Enable one or more categories of SNMP debugging.') parser.add_argument( '--debug-asn1', choices=pyasn1_debug.FLAG_MAP, action='append', type=str, default=[], help='Enable one or more categories of ASN.1 debugging.') parser.add_argument( '--logging-method', type=lambda x: x.split(':'), metavar='=<%s[:args]>]' % '|'.join(log.METHODS_MAP), default='stderr', help='Logging method.') parser.add_argument( '--log-level', choices=log.LEVELS_MAP, type=str, default='info', help='Logging level.') parser.add_argument( '--reporting-method', type=lambda x: x.split(':'), metavar='=<%s[:args]>]' % '|'.join(ReportingManager.REPORTERS), default='null', help='Activity metrics reporting method.') parser.add_argument( '--daemonize', action='store_true', help='Disengage from controlling terminal and become a daemon') parser.add_argument( '--process-user', type=str, help='If run as root, switch simulator daemon to this user right ' 'upon binding privileged ports') parser.add_argument( '--process-group', type=str, help='If run as root, switch simulator daemon to this group right ' 'upon binding privileged ports') parser.add_argument( '--pid-file', metavar='<FILE>', type=str, default='/var/run/%s/%s.pid' % (__name__, os.getpid()), help='SNMP simulation data file to write records to') parser.add_argument( '--cache-dir', metavar='<DIR>', type=str, help='Location for SNMP simulation data file indices to create') parser.add_argument( '--force-index-rebuild', action='store_true', help='Rebuild simulation data files indices even if they seem ' 'up to date') parser.add_argument( '--validate-data', action='store_true', help='Validate simulation data files on daemon start-up') parser.add_argument( '--variation-modules-dir', metavar='<DIR>', type=str, action='append', default=[], help='Variation modules search path(s)') parser.add_argument( '--variation-module-options', metavar='<module[=alias][:args]>', type=str, action='append', default=[], help='Options for a specific variation module') parser.add_argument( '--v3-only', action='store_true', help='Trip legacy SNMP v1/v2c support to gain a little lesser memory ' 'footprint') parser.add_argument( '--transport-id-offset', type=int, default=0, help='Start numbering the last sub-OID of transport endpoint OIDs ' 'starting from this ID') parser.add_argument( '--max-var-binds', type=int, default=64, help='Maximum number of variable bindings to include in a single ' 'response') parser.add_argument( '--args-from-file', metavar='<FILE>', type=str, help='Read SNMP engine(s) command-line configuration from this ' 'file. Can be useful when command-line is too long') # We do not parse SNMP params with argparse, but we want its -h/--help snmp_helper = argparse.ArgumentParser( description=DESCRIPTION, add_help=False, parents=[parser]) v3_usage = """\ Configure one or more independent SNMP engines. Each SNMP engine has a distinct engine ID, its own set of SNMP USM users, one or more network transport endpoints to listen on and its own simulation data directory. Each SNMP engine configuration starts with `--v3-engine-id <arg>` parameter followed by other configuration options up to the next `--v3-engine-id` option or end of command line Example ------- $ snmp-command-responder \\ --v3-engine-id auto \\ --data-dir ./data --agent-udpv4-endpoint=127.0.0.1:1024 \\ --v3-engine-id auto \\ --data-dir ./data --agent-udpv4-endpoint=127.0.0.1:1025 \\ --data-dir ./data --agent-udpv4-endpoint=127.0.0.1:1026 Besides network endpoints, simulated agents can be addressed by SNMPv1/v2c community name or SNMPv3 context engine ID/name. These parameters are configured automatically based on simulation data file paths relative to `--data-dir`. """ v3_group = snmp_helper.add_argument_group(v3_usage) v3_group.add_argument( '--v3-engine-id', type=str, metavar='<HEX|auto>', default='auto', help='SNMPv3 engine ID') v3_group.add_argument( '--v3-user', metavar='<STRING>', type=functools.partial(_parse_sized_string, min_length=1), help='SNMPv3 USM user (security) name') v3_group.add_argument( '--v3-auth-key', type=_parse_sized_string, help='SNMPv3 USM authentication key (must be > 8 chars)') v3_group.add_argument( '--v3-auth-proto', choices=AUTH_PROTOCOLS, type=lambda x: x.upper(), default='NONE', help='SNMPv3 USM authentication protocol') v3_group.add_argument( '--v3-priv-key', type=_parse_sized_string, help='SNMPv3 USM privacy (encryption) key (must be > 8 chars)') v3_group.add_argument( '--v3-priv-proto', choices=PRIV_PROTOCOLS, type=lambda x: x.upper(), default='NONE', help='SNMPv3 USM privacy (encryption) protocol') v3_group.add_argument( '--v3-context-engine-id', type=lambda x: univ.OctetString(hexValue=x[2:]), help='SNMPv3 context engine ID') v3_group.add_argument( '--v3-context-name', type=str, default='', help='SNMPv3 context engine ID') v3_group.add_argument( '--agent-udpv4-endpoint', type=endpoints.parse_endpoint, metavar='<[X.X.X.X]:NNNNN>', help='SNMP agent UDP/IPv4 address to listen on (name:port)') v3_group.add_argument( '--agent-udpv6-endpoint', type=functools.partial(endpoints.parse_endpoint, ipv6=True), metavar='<[X:X:..X]:NNNNN>', help='SNMP agent UDP/IPv6 address to listen on ([name]:port)') v3_group.add_argument( '--data-dir', type=str, metavar='<DIR>', help='SNMP simulation data recordings directory.') args, unparsed_args = parser.parse_known_args() if args.usage: snmp_helper.print_usage(sys.stderr) return 1 if args.help: snmp_helper.print_help(sys.stderr) return 1 _, unknown_args = snmp_helper.parse_known_args(unparsed_args) if unknown_args: sys.stderr.write( 'ERROR: Unknown command-line parameter(s) ' '%s\r\n' % ' '.join(unknown_args)) snmp_helper.print_usage(sys.stderr) return 1 # Reformat unparsed args into a list of (option, value) tuples snmp_args = [] name = None for opt in unparsed_args: if '=' in opt: snmp_args.append(opt.split('=')) elif name: snmp_args.append((name, opt)) name = None else: name = opt if name: sys.stderr.write( 'ERROR: Non-paired command-line key-value parameter ' '%s\r\n' % name) snmp_helper.print_usage(sys.stderr) return 1 if args.cache_dir: confdir.cache = args.cache_dir if args.variation_modules_dir: confdir.variation = args.variation_modules_dir variation_modules_options = variation.parse_modules_options( args.variation_module_options) if args.args_from_file: try: with open(args.args_from_file) as fl: snmp_args.extend([handler.split('=', 1) for handler in fl.read().split()]) except Exception as exc: sys.stderr.write( 'ERROR: file %s opening failure: ' '%s\r\n' % (args.args_from_file, exc)) snmp_helper.print_usage(sys.stderr) return 1 with daemon.PrivilegesOf(args.process_user, args.process_group): proc_name = os.path.basename(sys.argv[0]) try: log.set_logger(proc_name, *args.logging_method, force=True) if args.log_level: log.set_level(args.log_level) except SnmpsimError as exc: sys.stderr.write('%s\r\n' % exc) snmp_helper.print_usage(sys.stderr) return 1 try: ReportingManager.configure(*args.reporting_method) except SnmpsimError as exc: sys.stderr.write('%s\r\n' % exc) snmp_helper.print_usage(sys.stderr) return 1 if args.daemonize: try: daemon.daemonize(args.pid_file) except Exception as exc: sys.stderr.write( 'ERROR: cant daemonize process: %s\r\n' % exc) snmp_helper.print_usage(sys.stderr) return 1 if not os.path.exists(confdir.cache): try: with daemon.PrivilegesOf(args.process_user, args.process_group): os.makedirs(confdir.cache) except OSError as exc: log.error('failed to create cache directory "%s": ' '%s' % (confdir.cache, exc)) return 1 else: log.info('Cache directory "%s" created' % confdir.cache) variation_modules = variation.load_variation_modules( confdir.variation, variation_modules_options) with daemon.PrivilegesOf(args.process_user, args.process_group): variation.initialize_variation_modules( variation_modules, mode='variating') def configure_managed_objects( data_dirs, data_index_instrum_controller, snmp_engine=None, snmp_context=None): """Build pysnmp Managed Objects base from data files information""" _mib_instrums = {} _data_files = {} for dataDir in data_dirs: log.info( 'Scanning "%s" directory for %s data ' 'files...' % (dataDir, ','.join( [' *%s%s' % (os.path.extsep, x.ext) for x in variation.RECORD_TYPES.values()]))) if not os.path.exists(dataDir): log.info('Directory "%s" does not exist' % dataDir) continue log.msg.inc_ident() for (full_path, text_parser, community_name) in datafile.get_data_files(dataDir): if community_name in _data_files: log.error( 'ignoring duplicate Community/ContextName "%s" for data ' 'file %s (%s already loaded)' % (community_name, full_path, _data_files[community_name])) continue elif full_path in _mib_instrums: mib_instrum = _mib_instrums[full_path] log.info('Configuring *shared* %s' % (mib_instrum,)) else: data_file = datafile.DataFile( full_path, text_parser, variation_modules) data_file.index_text(args.force_index_rebuild, args.validate_data) MibController = controller.MIB_CONTROLLERS[data_file.layout] mib_instrum = MibController(data_file) _mib_instrums[full_path] = mib_instrum _data_files[community_name] = full_path log.info('Configuring %s' % (mib_instrum,)) log.info('SNMPv1/2c community name: %s' % (community_name,)) agent_name = md5( univ.OctetString(community_name).asOctets()).hexdigest() context_name = agent_name if not args.v3_only: # snmpCommunityTable::snmpCommunityIndex can't be > 32 config.addV1System( snmp_engine, agent_name, community_name, contextName=context_name) snmp_context.registerContextName(context_name, mib_instrum) if len(community_name) <= 32: snmp_context.registerContextName(community_name, mib_instrum) data_index_instrum_controller.add_data_file( full_path, community_name, context_name) log.info( 'SNMPv3 Context Name: %s' '%s' % (context_name, len(community_name) <= 32 and ' or %s' % community_name or '')) log.msg.dec_ident() del _mib_instrums del _data_files # Bind transport endpoints for idx, opt in enumerate(snmp_args): if opt[0] == '--agent-udpv4-endpoint': snmp_args[idx] = ( opt[0], endpoints.IPv4TransportEndpoints().add(opt[1])) elif opt[0] == '--agent-udpv6-endpoint': snmp_args[idx] = ( opt[0], endpoints.IPv6TransportEndpoints().add(opt[1])) # Start configuring SNMP engine(s) transport_dispatcher = AsyncoreDispatcher() transport_dispatcher.registerRoutingCbFun(lambda td, t, d: td) if not snmp_args or snmp_args[0][0] != '--v3-engine-id': snmp_args.insert(0, ('--v3-engine-id', 'auto')) if snmp_args and snmp_args[-1][0] != 'end-of-options': snmp_args.append(('end-of-options', '')) snmp_engine = None transport_index = { 'udpv4': args.transport_id_offset, 'udpv6': args.transport_id_offset, } for opt in snmp_args: if opt[0] in ('--v3-engine-id', 'end-of-options'): if snmp_engine: log.info('--- SNMP Engine configuration') log.info( 'SNMPv3 EngineID: ' '%s' % (hasattr(snmp_engine, 'snmpEngineID') and snmp_engine.snmpEngineID.prettyPrint() or '<unknown>',)) if not v3_context_engine_ids: v3_context_engine_ids.append((None, [])) log.msg.inc_ident() log.info('--- Simulation data recordings configuration') for v3_context_engine_id, ctx_data_dirs in v3_context_engine_ids: snmp_context = context.SnmpContext(snmp_engine, v3_context_engine_id) # unregister default context snmp_context.unregisterContextName(null) log.info( 'SNMPv3 Context Engine ID: ' '%s' % snmp_context.contextEngineId.prettyPrint()) data_index_instrum_controller = controller.DataIndexInstrumController() with daemon.PrivilegesOf(args.process_user, args.process_group): configure_managed_objects( ctx_data_dirs or data_dirs or confdir.data, data_index_instrum_controller, snmp_engine, snmp_context ) # Configure access to data index config.addV1System(snmp_engine, 'index', 'index', contextName='index') log.info('--- SNMPv3 USM configuration') if not v3_users: v3_users = ['simulator'] v3_auth_keys[v3_users[0]] = 'auctoritas' v3_auth_protos[v3_users[0]] = 'MD5' v3_priv_keys[v3_users[0]] = 'privatus' v3_priv_protos[v3_users[0]] = 'DES' for v3User in v3_users: if v3User in v3_auth_keys: if v3User not in v3_auth_protos: v3_auth_protos[v3User] = 'MD5' elif v3User in v3_auth_protos: log.error( 'auth protocol configured without key for user ' '%s' % v3User) return 1 else: v3_auth_keys[v3User] = None v3_auth_protos[v3User] = 'NONE' if v3User in v3_priv_keys: if v3User not in v3_priv_protos: v3_priv_protos[v3User] = 'DES' elif v3User in v3_priv_protos: log.error( 'privacy protocol configured without key for user ' '%s' % v3User) return 1 else: v3_priv_keys[v3User] = None v3_priv_protos[v3User] = 'NONE' if (AUTH_PROTOCOLS[v3_auth_protos[v3User]] == config.usmNoAuthProtocol and PRIV_PROTOCOLS[v3_priv_protos[v3User]] != config.usmNoPrivProtocol): log.error( 'privacy impossible without authentication for USM user ' '%s' % v3User) return 1 try: config.addV3User( snmp_engine, v3User, AUTH_PROTOCOLS[v3_auth_protos[v3User]], v3_auth_keys[v3User], PRIV_PROTOCOLS[v3_priv_protos[v3User]], v3_priv_keys[v3User]) except error.PySnmpError as exc: log.error( 'bad USM values for user %s: ' '%s' % (v3User, exc)) return 1 log.info('SNMPv3 USM SecurityName: %s' % v3User) if AUTH_PROTOCOLS[v3_auth_protos[v3User]] != config.usmNoAuthProtocol: log.info( 'SNMPv3 USM authentication key: %s, ' 'authentication protocol: ' '%s' % (v3_auth_keys[v3User], v3_auth_protos[v3User])) if PRIV_PROTOCOLS[v3_priv_protos[v3User]] != config.usmNoPrivProtocol: log.info( 'SNMPv3 USM encryption (privacy) key: %s, ' 'encryption protocol: ' '%s' % (v3_priv_keys[v3User], v3_priv_protos[v3User])) snmp_context.registerContextName('index', data_index_instrum_controller) log.info( 'Maximum number of variable bindings in SNMP response: ' '%s' % local_max_var_binds) log.info('--- Transport configuration') if not agent_udpv4_endpoints and not agent_udpv6_endpoints: log.error( 'agent endpoint address(es) not specified for SNMP ' 'engine ID %s' % v3_engine_id) return 1 for agent_udpv4_endpoint in agent_udpv4_endpoints: transport_domain = udp.domainName + (transport_index['udpv4'],) transport_index['udpv4'] += 1 snmp_engine.registerTransportDispatcher( transport_dispatcher, transport_domain) config.addSocketTransport( snmp_engine, transport_domain, agent_udpv4_endpoint[0]) log.info( 'Listening at UDP/IPv4 endpoint %s, transport ID ' '%s' % (agent_udpv4_endpoint[1], '.'.join([str(handler) for handler in transport_domain]))) for agent_udpv6_endpoint in agent_udpv6_endpoints: transport_domain = udp6.domainName + (transport_index['udpv6'],) transport_index['udpv6'] += 1 snmp_engine.registerTransportDispatcher( transport_dispatcher, transport_domain) config.addSocketTransport( snmp_engine, transport_domain, agent_udpv6_endpoint[0]) log.info( 'Listening at UDP/IPv6 endpoint %s, transport ID ' '%s' % (agent_udpv6_endpoint[1], '.'.join([str(handler) for handler in transport_domain]))) # SNMP applications GetCommandResponder(snmp_engine, snmp_context) SetCommandResponder(snmp_engine, snmp_context) NextCommandResponder(snmp_engine, snmp_context) BulkCommandResponder( snmp_engine, snmp_context).maxVarBinds = local_max_var_binds log.msg.dec_ident() if opt[0] == 'end-of-options': # Load up the rest of MIBs while running privileged (snmp_engine .msgAndPduDsp .mibInstrumController .mibBuilder.loadModules()) break # Prepare for next engine ID configuration v3_context_engine_ids = [] data_dirs = [] local_max_var_binds = args.max_var_binds v3_users = [] v3_auth_keys = {} v3_auth_protos = {} v3_priv_keys = {} v3_priv_protos = {} agent_udpv4_endpoints = [] agent_udpv6_endpoints = [] try: v3_engine_id = opt[1] if not v3_engine_id or v3_engine_id.lower() == 'auto': snmp_engine = engine.SnmpEngine() else: snmp_engine = engine.SnmpEngine( snmpEngineID=univ.OctetString(hexValue=v3_engine_id)) except Exception as exc: log.error( 'SNMPv3 Engine initialization failed, EngineID "%s": ' '%s' % (v3_engine_id, exc)) return 1 config.addContext(snmp_engine, '') elif opt[0] == '--v3-context-engine-id': v3_context_engine_ids.append((univ.OctetString(hexValue=opt[1]), [])) elif opt[0] == '--data-dir': if v3_context_engine_ids: v3_context_engine_ids[-1][1].append(opt[1]) else: data_dirs.append(opt[1]) elif opt[0] == '--max-varbinds': local_max_var_binds = opt[1] elif opt[0] == '--v3-user': v3_users.append(opt[1]) elif opt[0] == '--v3-auth-key': if not v3_users: log.error('--v3-user should precede %s' % opt[0]) return 1 if v3_users[-1] in v3_auth_keys: log.error( 'repetitive %s option for user %s' % (opt[0], v3_users[-1])) return 1 v3_auth_keys[v3_users[-1]] = opt[1] elif opt[0] == '--v3-auth-proto': if opt[1].upper() not in AUTH_PROTOCOLS: log.error('bad v3 auth protocol %s' % opt[1]) return 1 else: if not v3_users: log.error('--v3-user should precede %s' % opt[0]) return 1 if v3_users[-1] in v3_auth_protos: log.error( 'repetitive %s option for user %s' % (opt[0], v3_users[-1])) return 1 v3_auth_protos[v3_users[-1]] = opt[1].upper() elif opt[0] == '--v3-priv-key': if not v3_users: log.error('--v3-user should precede %s' % opt[0]) return 1 if v3_users[-1] in v3_priv_keys: log.error( 'repetitive %s option for user %s' % (opt[0], v3_users[-1])) return 1 v3_priv_keys[v3_users[-1]] = opt[1] elif opt[0] == '--v3-priv-proto': if opt[1].upper() not in PRIV_PROTOCOLS: log.error('bad v3 privacy protocol %s' % opt[1]) return 1 else: if not v3_users: log.error('--v3-user should precede %s' % opt[0]) return 1 if v3_users[-1] in v3_priv_protos: log.error( 'repetitive %s option for user %s' % (opt[0], v3_users[-1])) return 1 v3_priv_protos[v3_users[-1]] = opt[1].upper() elif opt[0] == '--agent-udpv4-endpoint': agent_udpv4_endpoints.append(opt[1]) elif opt[0] == '--agent-udpv6-endpoint': agent_udpv6_endpoints.append(opt[1]) transport_dispatcher.jobStarted(1) # server job would never finish with daemon.PrivilegesOf(args.process_user, args.process_group, final=True): try: transport_dispatcher.runDispatcher() except KeyboardInterrupt: log.info('Shutting down process...') finally: if variation_modules: log.info('Shutting down variation modules:') for name, contexts in variation_modules.items(): body = contexts[0] try: body['shutdown'](options=body['args'], mode='variation') except Exception as exc: log.error( 'Variation module "%s" shutdown FAILED: ' '%s' % (name, exc)) else: log.info('Variation module "%s" shutdown OK' % name) transport_dispatcher.closeDispatcher() log.info('Process terminated') return 0
def main(): # Get the event loop for this thread loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) # Create SNMP engine with autogenernated engineID and pre-bound # to socket transport dispatcher snmpEngine = engine.SnmpEngine() # UDP over IPv4, first listening interface/port config.addTransport( snmpEngine, udp.domainName, udp.UdpTransport().openServerMode(("0.0.0.0", 2162)), ) with open(CONFIG_PATH, encoding="utf-8") as file: config_base = yaml.safe_load(file) idx = 0 if "communities" in config_base: if "2c" in config_base["communities"]: for community in config_base["communities"]["2c"]: idx += 1 config.addV1System(snmpEngine, idx, community) if "usernameSecrets" in config_base: for secret in config_base["usernameSecrets"]: location = os.path.join("secrets/snmpv3", secret) userName = get_secret_value(location, "userName", required=True, default=None) authKey = get_secret_value(location, "authKey", required=False) privKey = get_secret_value(location, "privKey", required=False) authProtocol = get_secret_value(location, "authProtocol", required=False) logging.debug(f"authProtocol: {authProtocol}") authProtocol = AuthProtocolMap.get(authProtocol.upper(), "NONE") privProtocol = get_secret_value(location, "privProtocol", required=False, default="NONE") logging.debug(f"privProtocol: {privProtocol}") privProtocol = PrivProtocolMap.get(privProtocol.upper(), "NONE") config.addV3User( snmpEngine, userName=userName, authProtocol=authProtocol, authKey=authKey, privProtocol=privProtocol, privKey=privKey, securityEngineId=v2c.OctetString(hexValue=SECURITY_ENGINE_ID), ) logging.debug( f"V3 users: {userName} auth {authProtocol} authkey {authKey} privprotocol {privProtocol} " f"privkey {privKey} securityEngineId {SECURITY_ENGINE_ID}") # Register SNMP Application at the SNMP engine ntfrcv.NotificationReceiver(snmpEngine, cbFun) # Run asyncio main loop loop.run_forever()
#!/usr/bin/env python # -*- coding:utf-8 -*- #@Time : 2019/12/1 18:56 #@Author: max liu #@File : get_v3.py from pysnmp.entity import engine, config from pysnmp.carrier.asynsock.dgram import udp from pysnmp.entity.rfc3413 import cmdgen import sys from io import StringIO # Create SNMP engine instance snmpEngine = engine.SnmpEngine() # 添加SNMP引擎实例 # Setup transport endpoint and bind it with security settings yielding # a target name (choose one entry depending of the transport needed). # UDP/IPv4 config.addSocketTransport(snmpEngine, udp.domainName, udp.UdpSocketTransport().openClientMode()) # Error/response reciever def cbFun(sendRequestHandle, errorIndication, errorStatus, errorIndex, varBindTable, cbCtx): # 接收信息并处理 global oid_list # 全局清单 oid_list = [] # 创建oid_list全局清单 if errorIndication: # 错误打印 print(errorIndication) elif errorStatus: # 错误打印 print('%s at %s' %
# Configuration parameters for each of SNMP Engines snmpEngineInfo = ( ('0102030405060708', udp.DOMAIN_NAME + (0,), ('127.0.0.1', 161)), ('0807060504030201', udp.DOMAIN_NAME + (1,), ('127.0.0.2', 161)) ) # Instantiate the single transport dispatcher object transportDispatcher = AsyncoreDispatcher() # Setup a custom data routing function to select snmpEngine by transportDomain transportDispatcher.registerRoutingCbFun(lambda td, t, d: td) # Instantiate and configure SNMP Engines for snmpEngineId, transportDomain, transportAddress in snmpEngineInfo: # Create SNMP engine with specific engineID snmpEngine = engine.SnmpEngine(rfc1902.OctetString(hexValue=snmpEngineId)) # Register SNMP Engine object with transport dispatcher. Request incoming # data from specific transport endpoint to be funneled to this SNMP Engine. snmpEngine.registerTransportDispatcher(transportDispatcher, transportDomain) # Transport setup # UDP over IPv4 config.addTransport( snmpEngine, transportDomain, udp.UdpTransport().openServerMode(transportAddress) ) # SNMPv3/USM setup