def initialize_variation_modules(variation_modules, mode): log.info('Initializing variation modules...') for name, modules_contexts in variation_modules.items(): body = modules_contexts[0] for handler in ('init', 'variate', 'shutdown'): if handler not in body: log.error('missing "%s" handler at variation module ' '"%s"' % (handler, name)) return 1 try: body['init'](options=body['args'], mode=mode) except Exception as exc: log.error( 'Variation module "%s" from "%s" load FAILED: ' '%s' % (body['alias'], body['path'], exc)) else: log.info( 'Variation module "%s" from "%s" ' 'loaded OK' % (body['alias'], body['path']))
def flush(self): """Dump accumulated metrics into a JSON file. Reset all counters upon success. """ if not self._metrics: return now = int(time.time()) if self._next_dump > now: return self._next_dump = now + self.REPORTING_PERIOD self._metrics['format'] = self.REPORTING_FORMAT self._metrics['version'] = self.REPORTING_VERSION self._metrics['producer'] = self.PRODUCER_UUID dump_path = os.path.join(self._reports_dir, '%s.json' % now) log.debug('Dumping JSON metrics to %s' % dump_path) try: json_doc = json.dumps(self._metrics, indent=2) with tempfile.NamedTemporaryFile(delete=False) as fl: fl.write(json_doc.encode('utf-8')) os.rename(fl.name, dump_path) except Exception as exc: log.error('Failure while dumping metrics into ' '%s: %s' % (dump_path, exc)) self._metrics.clear()
mod = os.path.join(variationModulesDir, variationModuleName + '.py') if not os.path.exists(mod): log.info('Variation module "%s" not found' % mod) continue ctx = {'path': mod, 'moduleContext': {}} try: if sys.version_info[0] > 2: exec(compile(open(mod).read(), mod, 'exec'), ctx) else: execfile(mod, ctx) except Exception: log.error('Variation module "%s" execution failure: %s' % (mod, sys.exc_info()[1])) sys.exit(-1) else: variationModule = ctx log.info('Variation module "%s" loaded' % variationModuleName) break else: log.error('variation module "%s" not found' % variationModuleName) sys.exit(-1) # SNMP configuration snmpEngine = engine.SnmpEngine() if snmpVersion == 3:
def main(): 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.') 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( '--v2c-arch', action='store_true', help='Use lightweight, legacy SNMP architecture capable to support ' 'v1/v2c versions of SNMP') 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('--data-dir', type=str, action='append', metavar='<DIR>', dest='data_dirs', help='SNMP simulation data recordings directory.') endpoint_group = parser.add_mutually_exclusive_group(required=True) endpoint_group.add_argument( '--agent-udpv4-endpoint', type=str, action='append', metavar='<[X.X.X.X]:NNNNN>', dest='agent_udpv4_endpoints', default=[], help='SNMP agent UDP/IPv4 address to listen on (name:port)') endpoint_group.add_argument( '--agent-udpv6-endpoint', type=str, action='append', metavar='<[X:X:..X]:NNNNN>', dest='agent_udpv6_endpoints', default=[], help='SNMP agent UDP/IPv6 address to listen on ([name]:port)') args = parser.parse_args() 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) 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) parser.print_usage(sys.stderr) return 1 try: ReportingManager.configure(*args.reporting_method) except SnmpsimError as exc: sys.stderr.write('%s\r\n' % exc) parser.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) parser.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, )) contexts[univ.OctetString(community_name)] = mib_instrum data_index_instrum_controller.add_data_file( full_path, community_name) log.msg.dec_ident() del _mib_instrums del _data_files def get_bulk_handler(req_var_binds, non_repeaters, max_repetitions, read_next_vars): """Only v2c arch GETBULK handler""" N = min(int(non_repeaters), len(req_var_binds)) M = int(max_repetitions) R = max(len(req_var_binds) - N, 0) if R: M = min(M, int(args.max_var_binds / R)) if N: rsp_var_binds = read_next_vars(req_var_binds[:N]) else: rsp_var_binds = [] var_binds = req_var_binds[-R:] while M and R: rsp_var_binds.extend(read_next_vars(var_binds)) var_binds = rsp_var_binds[-R:] M -= 1 return rsp_var_binds def commandResponderCbFun(transport_dispatcher, transport_domain, transport_address, whole_msg): """v2c arch command responder request handling callback""" while whole_msg: msg_ver = api.decodeMessageVersion(whole_msg) if msg_ver in api.protoModules: p_mod = api.protoModules[msg_ver] else: log.error('Unsupported SNMP version %s' % (msg_ver, )) return req_msg, whole_msg = decoder.decode(whole_msg, asn1Spec=p_mod.Message()) community_name = req_msg.getComponentByPosition(1) for candidate in datafile.probe_context( transport_domain, transport_address, context_engine_id=datafile.SELF_LABEL, context_name=community_name): if candidate in contexts: log.info( 'Using %s selected by candidate %s; transport ID %s, ' 'source address %s, context engine ID <empty>, ' 'community name ' '"%s"' % (contexts[candidate], candidate, univ.ObjectIdentifier(transport_domain), transport_address[0], community_name)) community_name = candidate break else: log.error('No data file selected for transport ID %s, source ' 'address %s, community name ' '"%s"' % (univ.ObjectIdentifier(transport_domain), transport_address[0], community_name)) return whole_msg rsp_msg = p_mod.apiMessage.getResponse(req_msg) rsp_pdu = p_mod.apiMessage.getPDU(rsp_msg) req_pdu = p_mod.apiMessage.getPDU(req_msg) if req_pdu.isSameTypeWith(p_mod.GetRequestPDU()): backend_fun = contexts[community_name].readVars elif req_pdu.isSameTypeWith(p_mod.SetRequestPDU()): backend_fun = contexts[community_name].writeVars elif req_pdu.isSameTypeWith(p_mod.GetNextRequestPDU()): backend_fun = contexts[community_name].readNextVars elif (hasattr(p_mod, 'GetBulkRequestPDU') and req_pdu.isSameTypeWith(p_mod.GetBulkRequestPDU())): if not msg_ver: log.info('GETBULK over SNMPv1 from %s:%s' % (transport_domain, transport_address)) return whole_msg def backend_fun(var_binds): return get_bulk_handler( var_binds, p_mod.apiBulkPDU.getNonRepeaters(req_pdu), p_mod.apiBulkPDU.getMaxRepetitions(req_pdu), contexts[community_name].readNextVars) else: log.error('Unsupported PDU type %s from ' '%s:%s' % (req_pdu.__class__.__name__, transport_domain, transport_address)) return whole_msg try: var_binds = backend_fun(p_mod.apiPDU.getVarBinds(req_pdu)) except NoDataNotification: return whole_msg except Exception as exc: log.error('Ignoring SNMP engine failure: %s' % exc) return whole_msg if not msg_ver: for idx in range(len(var_binds)): oid, val = var_binds[idx] if val.tagSet in SNMP_2TO1_ERROR_MAP: var_binds = p_mod.apiPDU.getVarBinds(req_pdu) p_mod.apiPDU.setErrorStatus( rsp_pdu, SNMP_2TO1_ERROR_MAP[val.tagSet]) p_mod.apiPDU.setErrorIndex(rsp_pdu, idx + 1) break p_mod.apiPDU.setVarBinds(rsp_pdu, var_binds) transport_dispatcher.sendMessage(encoder.encode(rsp_msg), transport_domain, transport_address) return whole_msg # Configure access to data index log.info('Maximum number of variable bindings in SNMP ' 'response: %s' % args.max_var_binds) data_index_instrum_controller = controller.DataIndexInstrumController() contexts = {univ.OctetString('index'): data_index_instrum_controller} with daemon.PrivilegesOf(args.process_user, args.process_group): configure_managed_objects(args.data_dirs or confdir.data, data_index_instrum_controller) contexts['index'] = data_index_instrum_controller # Configure socket server transport_dispatcher = AsyncoreDispatcher() transport_index = args.transport_id_offset for agent_udpv4_endpoint in args.agent_udpv4_endpoints: transport_domain = udp.domainName + (transport_index, ) transport_index += 1 agent_udpv4_endpoint = ( endpoints.IPv4TransportEndpoints().add(agent_udpv4_endpoint)) transport_dispatcher.registerTransport(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]))) transport_index = args.transport_id_offset for agent_udpv6_endpoint in args.agent_udpv6_endpoints: transport_domain = udp6.domainName + (transport_index, ) transport_index += 1 agent_udpv6_endpoint = ( endpoints.IPv4TransportEndpoints().add(agent_udpv6_endpoint)) transport_dispatcher.registerTransport(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]))) transport_dispatcher.registerRecvCbFun(commandResponderCbFun) 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 commandResponderCbFun(transport_dispatcher, transport_domain, transport_address, whole_msg): """v2c arch command responder request handling callback""" while whole_msg: msg_ver = api.decodeMessageVersion(whole_msg) if msg_ver in api.protoModules: p_mod = api.protoModules[msg_ver] else: log.error('Unsupported SNMP version %s' % (msg_ver, )) return req_msg, whole_msg = decoder.decode(whole_msg, asn1Spec=p_mod.Message()) community_name = req_msg.getComponentByPosition(1) for candidate in datafile.probe_context( transport_domain, transport_address, context_engine_id=datafile.SELF_LABEL, context_name=community_name): if candidate in contexts: log.info( 'Using %s selected by candidate %s; transport ID %s, ' 'source address %s, context engine ID <empty>, ' 'community name ' '"%s"' % (contexts[candidate], candidate, univ.ObjectIdentifier(transport_domain), transport_address[0], community_name)) community_name = candidate break else: log.error('No data file selected for transport ID %s, source ' 'address %s, community name ' '"%s"' % (univ.ObjectIdentifier(transport_domain), transport_address[0], community_name)) return whole_msg rsp_msg = p_mod.apiMessage.getResponse(req_msg) rsp_pdu = p_mod.apiMessage.getPDU(rsp_msg) req_pdu = p_mod.apiMessage.getPDU(req_msg) if req_pdu.isSameTypeWith(p_mod.GetRequestPDU()): backend_fun = contexts[community_name].readVars elif req_pdu.isSameTypeWith(p_mod.SetRequestPDU()): backend_fun = contexts[community_name].writeVars elif req_pdu.isSameTypeWith(p_mod.GetNextRequestPDU()): backend_fun = contexts[community_name].readNextVars elif (hasattr(p_mod, 'GetBulkRequestPDU') and req_pdu.isSameTypeWith(p_mod.GetBulkRequestPDU())): if not msg_ver: log.info('GETBULK over SNMPv1 from %s:%s' % (transport_domain, transport_address)) return whole_msg def backend_fun(var_binds): return get_bulk_handler( var_binds, p_mod.apiBulkPDU.getNonRepeaters(req_pdu), p_mod.apiBulkPDU.getMaxRepetitions(req_pdu), contexts[community_name].readNextVars) else: log.error('Unsupported PDU type %s from ' '%s:%s' % (req_pdu.__class__.__name__, transport_domain, transport_address)) return whole_msg try: var_binds = backend_fun(p_mod.apiPDU.getVarBinds(req_pdu)) except NoDataNotification: return whole_msg except Exception as exc: log.error('Ignoring SNMP engine failure: %s' % exc) return whole_msg if not msg_ver: for idx in range(len(var_binds)): oid, val = var_binds[idx] if val.tagSet in SNMP_2TO1_ERROR_MAP: var_binds = p_mod.apiPDU.getVarBinds(req_pdu) p_mod.apiPDU.setErrorStatus( rsp_pdu, SNMP_2TO1_ERROR_MAP[val.tagSet]) p_mod.apiPDU.setErrorIndex(rsp_pdu, idx + 1) break p_mod.apiPDU.setVarBinds(rsp_pdu, var_binds) transport_dispatcher.sendMessage(encoder.encode(rsp_msg), transport_domain, transport_address) return whole_msg
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, )) contexts[univ.OctetString(community_name)] = mib_instrum data_index_instrum_controller.add_data_file( full_path, community_name) log.msg.dec_ident() del _mib_instrums del _data_files
mod = os.path.join(variationModulesDir, variationModuleName + '.py') if not os.path.exists(mod): log.info('Variation module "%s" not found' % mod) continue ctx = {'path': mod, 'moduleContext': {}} try: if sys.version_info[0] > 2: exec(compile(open(mod).read(), mod, 'exec'), ctx) else: execfile(mod, ctx) except Exception: log.error('Variation module "%s" execution ' 'failure: %s' % (mod, sys.exc_info()[1])) sys.exit(-1) else: variationModule = ctx log.info('Variation module "%s" loaded' % variationModuleName) break else: log.error('variation module "%s" not found' % variationModuleName) sys.exit(-1) # Variation module initialization if variationModule: log.info('Initializing variation module...')
def process_var_binds(self, var_binds, **context): rsp_var_binds = [] if context.get('nextFlag'): error_status = exval.endOfMib else: error_status = exval.noSuchInstance try: text, db = self.get_handles() except SnmpsimError as exc: log.error('Problem with data file or its index: %s' % exc) ReportingManager.update_metrics(data_file=self._text_file, datafile_failure_count=1, transport_call_count=1, **context) return [(vb[0], error_status) for vb in var_binds] vars_remaining = vars_total = len(var_binds) err_total = 0 log.info( 'Request var-binds: %s, flags: %s, ' '%s' % (', '.join( ['%s=<%s>' % (vb[0], vb[1].prettyPrint()) for vb in var_binds]), context.get('nextFlag') and 'NEXT' or 'EXACT', context.get('setFlag') and 'SET' or 'GET')) for oid, val in var_binds: text_oid = str(univ.OctetString('.'.join(['%s' % x for x in oid]))) try: line = self._record_index.lookup( str(univ.OctetString('.'.join(['%s' % x for x in oid])))) except KeyError: offset = search_record_by_oid(oid, text, self._text_parser) subtree_flag = exact_match = False else: offset, subtree_flag, prev_offset = line.split( str2octs(','), 2) subtree_flag, exact_match = int(subtree_flag), True offset = int(offset) text.seek(offset) vars_remaining -= 1 line, _, _ = get_record(text) # matched line while True: if exact_match: if context.get('nextFlag') and not subtree_flag: _next_line, _, _ = get_record(text) # next line if _next_line: _next_oid, _ = self._text_parser.evaluate( _next_line, oidOnly=True) try: _, subtree_flag, _ = self._record_index.lookup( str(_next_oid)).split(str2octs(','), 2) except KeyError: log.error('data error for %s at %s, index ' 'broken?' % (self, _next_oid)) line = '' # fatal error else: subtree_flag = int(subtree_flag) line = _next_line else: line = _next_line else: # search function above always rounds up to the next OID if line: _oid, _ = self._text_parser.evaluate(line, oidOnly=True) else: # eom _oid = 'last' try: _, _, _prev_offset = self._record_index.lookup( str(_oid)).split(str2octs(','), 2) except KeyError: log.error('data error for %s at %s, index ' 'broken?' % (self, _oid)) line = '' # fatal error else: _prev_offset = int(_prev_offset) # previous line serves a subtree? if _prev_offset >= 0: text.seek(_prev_offset) _prev_line, _, _ = get_record(text) _prev_oid, _ = self._text_parser.evaluate( _prev_line, oidOnly=True) if _prev_oid.isPrefixOf(oid): # use previous line to the matched one line = _prev_line subtree_flag = True if not line: _oid = oid _val = error_status break call_context = context.copy() call_context.update((), origOid=oid, origValue=val, dataFile=self._text_file, subtreeFlag=subtree_flag, exactMatch=exact_match, errorStatus=error_status, varsTotal=vars_total, varsRemaining=vars_remaining, variationModules=self._variation_modules) try: _oid, _val = self._text_parser.evaluate( line, **call_context) if _val is exval.endOfMib: exact_match = True subtree_flag = False continue except NoDataNotification: raise except MibOperationError: raise except Exception as exc: _oid = oid _val = error_status err_total += 1 log.error('data error at %s for %s: %s' % (self, text_oid, exc)) break rsp_var_binds.append((_oid, _val)) log.info('Response var-binds: %s' % (', '.join( ['%s=<%s>' % (vb[0], vb[1].prettyPrint()) for vb in rsp_var_binds]))) ReportingManager.update_metrics(data_file=self._text_file, varbind_count=vars_total, datafile_call_count=1, datafile_failure_count=err_total, transport_call_count=1, **context) return rsp_var_binds
mod = os.path.join(variationModulesDir, variationModuleName + '.py') if not os.path.exists(mod): log.info('Variation module "%s" not found' % mod) continue ctx = {'path': mod, 'moduleContext': {}} try: if sys.version_info[0] > 2: exec(compile(open(mod).read(), mod, 'exec'), ctx) else: execfile(mod, ctx) except Exception: log.error('Variation module "%s" execution failure: ' '%s' % (mod, sys.exc_info()[1])) sys.exit(-1) else: variationModule = ctx log.info('Variation module "%s" loaded' % variationModuleName) break else: log.error('variation module "%s" not found' % variationModuleName) sys.exit(-1) # SNMP configuration snmpEngine = engine.SnmpEngine()
def probe_hash_context(responder, snmp_engine): """v3arch SNMP context name searcher""" execCtx = snmp_engine.observer.getExecutionContext( 'rfc3412.receiveMessage:request') (transport_domain, transport_address, context_engine_id, context_name) = ( execCtx['transportDomain'], execCtx['transportAddress'], execCtx['contextEngineId'], execCtx['contextName'].prettyPrint() ) if context_engine_id == snmp_engine.snmpEngineID: context_engine_id = datafile.SELF_LABEL else: context_engine_id = context_engine_id.prettyPrint() for candidate in datafile.probe_context( transport_domain, transport_address, context_engine_id, context_name): if len(candidate) > 32: probed_context_name = md5(candidate).hexdigest() else: probed_context_name = candidate try: mib_instrum = responder.snmpContext.getMibInstrum( probed_context_name) except error.PySnmpError: pass else: log.info( 'Using %s selected by candidate %s; transport ID %s, ' 'source address %s, context engine ID %s, ' 'community name ' '"%s"' % (mib_instrum, candidate, univ.ObjectIdentifier(transport_domain), transport_address[0], context_engine_id, probed_context_name)) context_name = probed_context_name break else: mib_instrum = responder.snmpContext.getMibInstrum(context_name) log.info( 'Using %s selected by contextName "%s", transport ID %s, ' 'source address %s' % (mib_instrum, context_name, univ.ObjectIdentifier(transport_domain), transport_address[0])) if not isinstance(mib_instrum, ( controller.MibInstrumController, controller.DataIndexInstrumController)): log.error( 'LCD access denied (contextName does not match any data file)') raise NoDataNotification() return context_name
def load_variation_modules(search_path, modules_options): variation_modules = {} modules_options = modules_options.copy() for variation_modules_dir in search_path: 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 for d_file in os.listdir(variation_modules_dir): if d_file[-3:] != '.py': continue _to_load = [] mod_name = os.path.splitext(os.path.basename(d_file))[0] if mod_name in modules_options: while modules_options[mod_name]: alias, params = modules_options[mod_name].pop() _to_load.append((alias, params)) del modules_options[mod_name] else: _to_load.append((mod_name, '')) mod = os.path.abspath(os.path.join(variation_modules_dir, d_file)) for alias, params in _to_load: if alias in variation_modules: log.error( 'ignoring duplicate variation module "%s" at ' '"%s"' % (alias, mod)) continue ctx = { 'path': mod, 'alias': alias, 'args': params, '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 # moduleContext, agentContexts, recordContexts variation_modules[alias] = ctx, {}, {} log.info('A total of %s modules found in ' '%s' % (len(variation_modules), variation_modules_dir)) if modules_options: log.info('WARNING: unused options for variation modules: ' '%s' % ', '.join(modules_options)) return variation_modules
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(): 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
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
def cbFun(snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorIndex, varBindTable, cbCtx): if errorIndication and not cbCtx['retries']: cbCtx['errors'] += 1 log.error('SNMP Engine error: %s' % errorIndication) return # SNMPv1 response may contain noSuchName error *and* SNMPv2c exception, # so we ignore noSuchName error here if errorStatus and errorStatus != 2 or errorIndication: log.error('Remote SNMP error %s' % (errorIndication or errorStatus.prettyPrint())) if cbCtx['retries']: try: nextOID = varBindTable[-1][0][0] except IndexError: nextOID = cbCtx['lastOID'] else: log.error('Failed OID: %s' % nextOID) # fuzzy logic of walking a broken OID if len(nextOID) < 4: pass elif (continueOnErrors - cbCtx['retries']) * 10 / continueOnErrors > 5: nextOID = nextOID[:-2] + (nextOID[-2] + 1, ) elif nextOID[-1]: nextOID = nextOID[:-1] + (nextOID[-1] + 1, ) else: nextOID = nextOID[:-2] + (nextOID[-2] + 1, 0) cbCtx['retries'] -= 1 cbCtx['lastOID'] = nextOID log.info('Retrying with OID %s (%s retries left)...' % (nextOID, cbCtx['retries'])) # initiate another SNMP walk iteration if getBulkFlag: cmdGen.sendVarBinds(snmpEngine, 'tgt', v3ContextEngineId, v3Context, 0, getBulkRepetitions, [(nextOID, None)], cbFun, cbCtx) else: cmdGen.sendVarBinds(snmpEngine, 'tgt', v3ContextEngineId, v3Context, [(nextOID, None)], cbFun, cbCtx) cbCtx['errors'] += 1 return if continueOnErrors != cbCtx['retries']: cbCtx['retries'] += 1 if varBindTable and varBindTable[-1] and varBindTable[-1][0]: cbCtx['lastOID'] = varBindTable[-1][0][0] stopFlag = False # Walk var-binds for varBindRow in varBindTable: for oid, value in varBindRow: # EOM if stopOID and oid >= stopOID: stopFlag = True # stop on out of range condition elif (value is None or value.tagSet in (rfc1905.NoSuchObject.tagSet, rfc1905.NoSuchInstance.tagSet, rfc1905.EndOfMibView.tagSet)): stopFlag = 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': cbCtx['count'], 'total': cbCtx['total'], 'iteration': cbCtx['iteration'], 'reqTime': cbCtx['reqTime'], 'startOID': startOID, 'stopOID': stopOID, 'stopFlag': stopFlag, 'variationModule': variationModule } try: line = dataFileHandler.format(oid, value, **context) except error.MoreDataNotification: cbCtx['count'] = 0 cbCtx['iteration'] += 1 moreDataNotification = sys.exc_info()[1] if 'period' in moreDataNotification: log.info('%s OIDs dumped, waiting %.2f sec(s)...' % (cbCtx['total'], moreDataNotification['period'])) time.sleep(moreDataNotification['period']) # initiate another SNMP walk iteration if getBulkFlag: cmdGen.sendVarBinds(snmpEngine, 'tgt', v3ContextEngineId, v3Context, 0, getBulkRepetitions, [(startOID, None)], cbFun, cbCtx) else: cmdGen.sendVarBinds(snmpEngine, 'tgt', v3ContextEngineId, v3Context, [(startOID, None)], cbFun, cbCtx) stopFlag = True # stop current iteration except error.NoDataNotification: pass except error.SnmpsimError: log.error((sys.exc_info()[1], )) continue else: outputFile.write(line) cbCtx['count'] += 1 cbCtx['total'] += 1 if cbCtx['count'] % 100 == 0: log.error('OIDs dumped: %s/%s' % (cbCtx['iteration'], cbCtx['count'])) # Next request time cbCtx['reqTime'] = time.time() # Continue walking return not stopFlag
def cbFun(snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorIndex, varBindTable, cbCtx): if errorIndication and not cbCtx['retries']: cbCtx['errors'] += 1 log.error('SNMP Engine error: %s' % errorIndication) return # SNMPv1 response may contain noSuchName error *and* SNMPv2c exception, # so we ignore noSuchName error here if errorStatus and errorStatus != 2 or errorIndication: log.error( 'Remote SNMP error %s' % ( errorIndication or errorStatus.prettyPrint())) if cbCtx['retries']: try: nextOID = varBindTable[-1][0][0] except IndexError: nextOID = cbCtx['lastOID'] else: log.error('Failed OID: %s' % nextOID) # fuzzy logic of walking a broken OID if len(nextOID) < 4: pass elif (continueOnErrors - cbCtx['retries']) * 10 / continueOnErrors > 5: nextOID = nextOID[:-2] + (nextOID[-2] + 1,) elif nextOID[-1]: nextOID = nextOID[:-1] + (nextOID[-1] + 1,) else: nextOID = nextOID[:-2] + (nextOID[-2] + 1, 0) cbCtx['retries'] -= 1 cbCtx['lastOID'] = nextOID log.info( 'Retrying with OID %s (%s retries left)' '...' % (nextOID, cbCtx['retries'])) # initiate another SNMP walk iteration if getBulkFlag: cmdGen.sendVarBinds( snmpEngine, 'tgt', v3ContextEngineId, v3Context, 0, getBulkRepetitions, [(nextOID, None)], cbFun, cbCtx) else: cmdGen.sendVarBinds( snmpEngine, 'tgt', v3ContextEngineId, v3Context, [(nextOID, None)], cbFun, cbCtx) cbCtx['errors'] += 1 return if continueOnErrors != cbCtx['retries']: cbCtx['retries'] += 1 if varBindTable and varBindTable[-1] and varBindTable[-1][0]: cbCtx['lastOID'] = varBindTable[-1][0][0] stopFlag = False # Walk var-binds for varBindRow in varBindTable: for oid, value in varBindRow: # EOM if stopOID and oid >= stopOID: stopFlag = True # stop on out of range condition elif (value is None or value.tagSet in (rfc1905.NoSuchObject.tagSet, rfc1905.NoSuchInstance.tagSet, rfc1905.EndOfMibView.tagSet)): stopFlag = 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': cbCtx['count'], 'total': cbCtx['total'], 'iteration': cbCtx['iteration'], 'reqTime': cbCtx['reqTime'], 'startOID': startOID, 'stopOID': stopOID, 'stopFlag': stopFlag, 'variationModule': variationModule } try: line = dataFileHandler.format(oid, value, **context) except error.MoreDataNotification: cbCtx['count'] = 0 cbCtx['iteration'] += 1 moreDataNotification = sys.exc_info()[1] if 'period' in moreDataNotification: log.info( '%s OIDs dumped, waiting %.2f sec(s)' '...' % (cbCtx['total'], moreDataNotification['period'])) time.sleep(moreDataNotification['period']) # initiate another SNMP walk iteration if getBulkFlag: cmdGen.sendVarBinds( snmpEngine, 'tgt', v3ContextEngineId, v3Context, 0, getBulkRepetitions, [(startOID, None)], cbFun, cbCtx) else: cmdGen.sendVarBinds( snmpEngine, 'tgt', v3ContextEngineId, v3Context, [(startOID, None)], cbFun, cbCtx) stopFlag = True # stop current iteration except error.NoDataNotification: pass except error.SnmpsimError: log.error((sys.exc_info()[1],)) continue else: outputFile.write(line) cbCtx['count'] += 1 cbCtx['total'] += 1 if cbCtx['count'] % 100 == 0: log.info('OIDs dumped: %s/%s' % ( cbCtx['iteration'], cbCtx['count'])) # Next request time cbCtx['reqTime'] = time.time() # Continue walking return not stopFlag
mod = os.path.join(variationModulesDir, variationModuleName + '.py') if not os.path.exists(mod): log.info('Variation module "%s" not found' % mod) continue ctx = {'path': mod, 'moduleContext': {}} try: if sys.version_info[0] > 2: exec(compile(open(mod).read(), mod, 'exec'), ctx) else: execfile(mod, ctx) except Exception: log.error('Variation module "%s" execution failure: %s' % (mod, sys.exc_info()[1])) sys.exit(-1) else: variationModule = ctx log.info('Variation module "%s" loaded' % variationModuleName) break else: log.error('variation module "%s" not found' % variationModuleName) sys.exit(-1) # Variation module initialization if variationModule: log.info('Initializing variation module...') for x in ('init', 'record', 'shutdown'):
def main(): variation_module = None endpoints = {} contexts = {} stats = { 'UDP packets': 0, 'IP packets': 0, 'bad packets': 0, 'empty packets': 0, 'unknown L2 protocol': 0, 'SNMP errors': 0, 'SNMP exceptions': 0, 'agents seen': 0, 'contexts seen': 0, 'snapshots taken': 0, 'Response PDUs seen': 0, 'OIDs seen': 0 } 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.') 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=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') variation_group = parser.add_argument_group( 'Simulation data variation options') variation_group.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') parser.add_argument( '--output-dir', metavar='<FILE>', type=str, default='.', help='SNMP simulation data directory to place captured traffic in ' 'form of simulation records. File names reflect traffic sources ' 'on the network.') variation_group.add_argument( '--transport-id-offset', type=int, default=0, help='When arranging simulation data files, start enumerating ' 'receiving transport endpoints from this number.') traffic_group = parser.add_argument_group('Traffic capturing options') traffic_group.add_argument( '--packet-filter', type=str, default='udp and src port 161', help='Traffic filter (in tcpdump syntax) to use for picking SNMP ' 'packets out of the rest of the traffic.') variation_group.add_argument('--listen-interface', type=str, help='Listen on this network interface.') parser.add_argument( '--promiscuous-mode', action='store_true', help='Attempt to switch NIC to promiscuous mode. Depending on the ' 'network, this may make traffic of surrounding machines visible. ' 'Might require superuser privileges.') parser.add_argument( '--capture-file', metavar='<FILE>', type=str, help='PCAP file with SNMP simulation data file to read from ' 'instead of listening on a NIC.') args = parser.parse_args() if not pcap: sys.stderr.write( 'ERROR: pylibpcap package is missing!\r\nGet it by running ' '`pip install ' 'https://downloads.sourceforge.net/project/pylibpcap/pylibpcap' '/0.6.4/pylibpcap-0.6.4.tar.gz`' '\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%s\r\n' % exc) parser.print_usage(sys.stderr) sys.exit(1) if (isinstance(args.start_object, rfc1902.ObjectIdentity) or isinstance(args.stop_object, rfc1902.ObjectIdentity)): mib_builder = builder.MibBuilder() mib_view_controller = view.MibViewController(mib_builder) compiler.addMibCompiler(mib_builder, sources=args.mib_sources) try: if isinstance(args.start_object, rfc1902.ObjectIdentity): args.start_object.resolveWithMib(mib_view_controller) if isinstance(args.stop_object, rfc1902.ObjectIdentity): args.stop_object.resolveWithMib(mib_view_controller) except PySnmpError as exc: sys.stderr.write('ERROR: %s\r\n' % exc) return 1 # 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 # Variation module initialization if variation_module: log.info('Initializing variation module...') for handler in ('init', 'record', 'shutdown'): if handler not in variation_module: log.error('missing "%s" handler at variation module ' '"%s"' % (handler, args.variation_module)) return 1 handler = variation_module['init'] try: handler(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) pcap_obj = pcap.pcapObject() if args.listen_interface: if not args.quiet: log.info('Listening on interface %s in %spromiscuous ' 'mode' % (args.listen_interface, '' if args.promiscuous_mode else 'non-')) try: pcap_obj.open_live(args.listen_interface, 65536, args.promiscuous_mode, 1000) except Exception as exc: log.error('Error opening interface %s for snooping: ' '%s' % (args.listen_interface, exc)) return 1 elif args.capture_file: if not args.quiet: log.info('Opening capture file %s' % args.capture_file) try: pcap_obj.open_offline(args.capture_file) except Exception as exc: log.error('Error opening capture file %s for reading: ' '%s' % (args.capture_file, exc)) return 1 else: sys.stderr.write( 'ERROR: no capture file or live interface specified\r\n') parser.print_usage(sys.stderr) return 1 if args.packet_filter: if not args.quiet: log.info('Applying packet filter \"%s\"' % args.packet_filter) pcap_obj.setfilter(args.packet_filter, 0, 0) if not args.quiet: log.info('Processing records from %still ' '%s' % ('the beginning ' if args.start_object else args.start_object, args.stop_object if args.stop_object else 'the end')) def parse_packet(raw): pkt = {} # http://www.tcpdump.org/linktypes.html ll_headers = {0: 4, 1: 14, 108: 4, 228: 0} if pcap_obj.datalink() in ll_headers: raw = raw[ll_headers[pcap_obj.datalink()]:] else: stats['unknown L2 protocol'] += 1 pkt['version'] = (ord(raw[0]) & 0xf0) >> 4 pkt['header_len'] = ord(raw[0]) & 0x0f pkt['tos'] = ord(raw[1]) pkt['total_len'] = socket.ntohs(struct.unpack('H', raw[2:4])[0]) pkt['id'] = socket.ntohs(struct.unpack('H', raw[4:6])[0]) pkt['flags'] = (ord(raw[6]) & 0xe0) >> 5 pkt['fragment_offset'] = socket.ntohs( struct.unpack('H', raw[6:8])[0] & 0x1f) pkt['ttl'] = ord(raw[8]) pkt['protocol'] = ord(raw[9]) pkt['checksum'] = socket.ntohs(struct.unpack('H', raw[10:12])[0]) pkt['source_address'] = pcap.ntoa(struct.unpack('i', raw[12:16])[0]) pkt['destination_address'] = pcap.ntoa( struct.unpack('i', raw[16:20])[0]) if pkt['header_len'] > 5: pkt['options'] = raw[20:4 * (pkt['header_len'] - 5)] else: pkt['options'] = None raw = raw[4 * pkt['header_len']:] if pkt['protocol'] == 17: pkt['source_port'] = socket.ntohs(struct.unpack('H', raw[0:2])[0]) pkt['destination_port'] = socket.ntohs( struct.unpack('H', raw[2:4])[0]) raw = raw[8:] stats['UDP packets'] += 1 pkt['data'] = raw stats['IP packets'] += 1 return pkt def handle_snmp_message(d, t, private={}): msg_ver = api.decodeMessageVersion(d['data']) if msg_ver in api.protoModules: p_mod = api.protoModules[msg_ver] else: stats['bad packets'] += 1 return try: rsp_msg, whole_msg = decoder.decode(d['data'], asn1Spec=p_mod.Message()) except PyAsn1Error: stats['bad packets'] += 1 return if rsp_msg['data'].getName() == 'response': rsp_pdu = p_mod.apiMessage.getPDU(rsp_msg) error_status = p_mod.apiPDU.getErrorStatus(rsp_pdu) if error_status: stats['SNMP errors'] += 1 else: endpoint = d['source_address'], d['source_port'] if endpoint not in endpoints: endpoints[endpoint] = udp.domainName + ( args.transport_id_offset + len(endpoints), ) stats['agents seen'] += 1 context = '%s/%s' % (p_mod.ObjectIdentifier( endpoints[endpoint]), p_mod.apiMessage.getCommunity(rsp_msg)) if context not in contexts: contexts[context] = {} stats['contexts seen'] += 1 context = '%s/%s' % (p_mod.ObjectIdentifier( endpoints[endpoint]), p_mod.apiMessage.getCommunity(rsp_msg)) stats['Response PDUs seen'] += 1 if 'basetime' not in private: private['basetime'] = t for oid, value in p_mod.apiPDU.getVarBinds(rsp_pdu): if oid < args.start_object: continue if args.stop_object and oid >= args.stop_object: continue if oid in contexts[context]: if value != contexts[context][oid]: stats['snapshots taken'] += 1 else: contexts[context][oid] = [], [] contexts[context][oid][0].append(t - private['basetime']) contexts[context][oid][1].append(value) stats['OIDs seen'] += 1 def handle_packet(pktlen, data, timestamp): if not data: stats['empty packets'] += 1 return handle_snmp_message(parse_packet(data), timestamp) try: if args.listen_interface: log.info('Listening on interface "%s", kill me when you ' 'are done.' % args.listen_interface) while True: pcap_obj.dispatch(1, handle_packet) elif args.capture_file: log.info('Processing capture file "%s"....' % args.capture_file) args = pcap_obj.next() while args: handle_packet(*args) args = pcap_obj.next() except (TypeError, KeyboardInterrupt): log.info('Shutting down process...') finally: data_file_handler = SnmprecRecord() for context in contexts: ext = os.path.extsep ext += RECORD_TYPES[args.destination_record_type].ext filename = os.path.join(args.output_dir, context + ext) if not args.quiet: log.info('Creating simulation context %s at ' '%s' % (context, filename)) try: os.mkdir(os.path.dirname(filename)) except OSError: pass record = RECORD_TYPES[args.destination_record_type] try: output_file = record.open(filename, 'wb') except IOError as exc: log.error('writing %s: %s' % (filename, exc)) return 1 count = total = iteration = 0 time_offset = 0 req_time = time.time() oids = sorted(contexts[context]) oids.append(oids[-1]) # duplicate last OID to trigger stopFlag while True: for oid in oids: timeline, values = contexts[context][oid] value = values[min( len(values) - 1, bisect.bisect_left(timeline, time_offset))] if value.tagSet in (rfc1905.NoSuchObject.tagSet, rfc1905.NoSuchInstance.tagSet, rfc1905.EndOfMibView.tagSet): stats['SNMP exceptions'] += 1 continue # remove value enumeration if value.tagSet == Integer32.tagSet: value = Integer32(value) if value.tagSet == Unsigned32.tagSet: value = Unsigned32(value) if value.tagSet == Bits.tagSet: value = OctetString(value) # Build .snmprec record ctx = { 'origOid': oid, 'origValue': value, 'count': count, 'total': total, 'iteration': iteration, 'reqTime': req_time, 'startOID': args.start_object, 'stopOID': args.stop_object, 'stopFlag': oids.index(oid) == len(oids) - 1, 'variationModule': variation_module } try: line = data_file_handler.format(oid, value, **ctx) except error.MoreDataNotification as exc: count = 0 iteration += 1 moreDataNotification = exc if 'period' in moreDataNotification: time_offset += moreDataNotification['period'] log.info( '%s OIDs dumped, advancing time window to ' '%.2f sec(s)...' % (total, time_offset)) break except error.NoDataNotification: pass except error.SnmpsimError as exc: log.error(exc) continue else: output_file.write(line) count += 1 total += 1 else: break output_file.flush() output_file.close() if variation_module: log.info('Shutting down variation module ' '"%s"...' % args.variation_module) handler = variation_module['shutdown'] try: handler(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) log.info("""\ PCap statistics: packets snooped: %s packets dropped: %s packets dropped: by interface %s\ """ % pcap_obj.stats()) log.info("""\ SNMP statistics: %s\ """ % ' '.join(['%s: %s\r\n' % kv for kv in stats.items()])) return 0