From 844c50d8b9b473b3daebdfe357ead3f904db9721 Mon Sep 17 00:00:00 2001 From: "Ladue, David (dl3158)" Date: Wed, 15 Aug 2018 18:11:46 -0400 Subject: adding snmpV3 support Change-Id: I6250e30fa1aa2516a16c4906628be8cc904fbc71 Issue-ID: DCAEGEN2-630 Signed-off-by: Ladue, David (dl3158) --- snmptrap/snmptrapd.py | 199 +++++++++++++++++++++++++++++++------------------- 1 file changed, 124 insertions(+), 75 deletions(-) (limited to 'snmptrap/snmptrapd.py') diff --git a/snmptrap/snmptrapd.py b/snmptrap/snmptrapd.py index f435c30..cbf004b 100644 --- a/snmptrap/snmptrapd.py +++ b/snmptrap/snmptrapd.py @@ -28,7 +28,7 @@ As traps arrive they are decomposed and transformed into a JSON message which is published to a dmaap instance that has been defined by controller. :Parameters: - usage: snmptrapd.py [-v] + usage: snmptrapd.py :Keywords: onap dcae snmp trap publish dmaap """ @@ -65,14 +65,15 @@ from pysnmp.carrier.asyncio.dgram import udp, udp6 # from pysnmp.carrier.asyncore.dgram import udp from pysnmp.entity.rfc3413 import ntfrcv from pysnmp.proto.api import v2c +from pysnmp import debug # dcae_snmptrap import trapd_settings as tds from trapd_runtime_pid import save_pid, rm_pid from trapd_get_cbs_config import get_cbs_config from trapd_exit import cleanup_and_exit -from trapd_http_session import init_session_obj - +from trapd_http_session import init_session_obj, close_session_obj, reset_session_obj +from trapd_snmpv3 import init, load_snmpv3_credentials from trapd_io import roll_all_logs, open_eelf_logs, roll_file, open_file, close_file, ecomp_logger, stdout_logger prog_name = os.path.basename(__file__) @@ -95,7 +96,7 @@ def usage_err(): """ print('Incorrect usage invoked. Correct usage:') - print(' %s [-v]' % prog_name) + print(' %s' % prog_name) cleanup_and_exit(1, "undefined") @@ -127,36 +128,34 @@ def load_all_configs(_signum, _frame): ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) - # Initialize dmaap requests session object. Close existing session - # if applicable. - if tds.http_requ_session is not None: - tds.http_requ_session.close() - - tds.http_requ_session = init_session_obj() - if tds.http_requ_session is None: - msg = "Unable to create new http session - FATAL ERROR, exiting" - ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_FATAL, tds.CODE_GENERAL, msg) - stdout_logger(msg) - cleanup_and_exit(1, tds.pid_file_name) - - # re-request config from config binding service - # (either broker, or json file override) + # re-request config (from broker, or local json file + # if broker not present) if not get_cbs_config(): - msg = "error (re)loading CBS config - FATAL ERROR, exiting" + msg = "Error (re)loading CBS config - FATAL ERROR, exiting" stdout_logger(msg) cleanup_and_exit(1, tds.pid_file_name) else: - current_runtime_config_file_name = tds.c_config['files.runtime_base_dir'] + \ + current_runtime_config_file_name = tds.c_config['files']['runtime_base_dir'] + \ "/tmp/current_config.json" - - msg = "current config logged to : %s" % current_runtime_config_file_name + if int(_signum) != 0: + msg = "updated config logged to : %s" % current_runtime_config_file_name + else: + msg = "current config logged to : %s" % current_runtime_config_file_name ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) with open(current_runtime_config_file_name, 'w') as outfile: json.dump(tds.c_config, outfile) - # if here, config re-read successfully - return True + # reset http session based on latest config + tds.http_requ_session = reset_session_obj(tds.http_requ_session) + + # FMDL: add with stormWatch + # reload sw participating entries, reset counter dictionary + # sw.interval_in_seconds, sw.participant_oid_dict = load_sw_participant_dict(tds.c_config['trap_config']) + # sw.counter_dict = init_counter_dict() + + # if here, config re-read successfully + return True # # # # # # # # # # # # # # fx: log_all_arriving_traps @@ -166,12 +165,12 @@ def load_all_configs(_signum, _frame): def log_all_arriving_traps(): # roll logs as needed/defined in files.roll_frequency - if tds.c_config['files.roll_frequency'] == "minute": + if tds.c_config['files']['roll_frequency'] == "minute": curr_minute = datetime.datetime.now().minute if curr_minute != tds.last_minute: roll_all_logs() tds.last_minute = curr_minute - elif tds.c_config['files.roll_frequency'] == "hour": + elif tds.c_config['files']['roll_frequency'] == "hour": curr_hour = datetime.datetime.now().hour if curr_hour != tds.last_hour: roll_all_logs() @@ -183,7 +182,7 @@ def log_all_arriving_traps(): roll_all_logs() tds.last_day = curr_day - # now log latest arriving trap + # always log arriving trap try: # going for: # 1520971776 Tue Mar 13 16:09:36 2018; 1520971776 2018-03-13 16:09:36 DCAE-COLLECTOR-UCSNMP 15209717760049 .1.3.6.1.4.1.2636.4.1.6 gfpmt5pcs10.oss.att.com 135.91.10.139 12.123.1.240 12.123.1.240 2 varbinds: [0] .1.3.6.1.2.1.1.3.0 {10} 1212058366 140 days, 6:49:43.66 [1] .1.3.6.1.6.3.1.1.4.1.0 {6} .1.3.6.1.4.1.2636.4.1.6 [2] .1.3.6.1.4.1.2636.3.1.15.1.1.2.4.0.0 {2} 2 [3] .1.3.6.1.4.1.2636.3.1.15.1.2.2.4.0.0 {2} 4 [4] .1.3.6.1.4.1.2636.3.1.15.1.3.2.4.0.0 {2} 0 [5] .1.3.6.1.4.1.2636.3.1.15.1.4.2.4.0.0 {2} 0 [6] .1.3.6.1.4.1.2636.3.1.15.1.5.2.4.0.0 {4} PEM 3 [7] .1.3.6.1.4.1.2636.3.1.15.1.6.2.4.0.0 {2} 7 [8] .1.3.6.1.4.1.2636.3.1.15.1.7.2.4.0.0 {2} 4 [9] .1.3.6.1.6.3.18.1.3.0 {7} 12.123.1.240 @@ -273,7 +272,7 @@ def post_dmaap(): k = 0 dmaap_pub_success = False - while not dmaap_pub_success and k < (int(tds.c_config['publisher.http_retries'])): + while not dmaap_pub_success and k < (int(tds.c_config['publisher']['http_retries'])): try: if tds.c_config['streams_publishes']['sec_fault_unsecure']['aaf_username'] == "" or tds.c_config['streams_publishes']['sec_fault_unsecure']['aaf_username'] is None: msg = "%d trap(s) : %s - attempt %d (unsecure)" % ( @@ -323,7 +322,7 @@ def post_dmaap(): k += 1 - if k < tds.c_config['publisher.http_retries']: + if k < tds.c_config['publisher']['http_retries']: msg = "sleeping %.4f seconds and retrying" % ( tds.seconds_between_retries) ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, @@ -380,9 +379,40 @@ def snmp_engine_observer_cb(snmp_engine, execpoint, variables, cbCtx): :Variables: """ + # All sorts of goodies available: + # print('Execution point: %s' % execpoint) + # print('* transportDomain: %s' % '.'.join([str(x) for x in variables['transportDomain']])) + # print('* transportAddress: %s' % '@'.join([str(x) for x in variables['transportAddress']])) + # print('* securityModel: %s' % variables['securityModel']) + # print('* securityName: %s' % variables['securityName']) + # print('* securityLevel: %s' % variables['securityLevel']) + # print('* contextEngineId: %s' % variables['contextEngineId'].prettyPrint()) + # print('* contextName: %s' % variables['contextName'].prettyPrint()) + # print('* PDU: %s' % variables['pdu'].prettyPrint()) + # V1 only: + # print('* enterprise: %s' % variables['pdu']['enterprise'].prettyPrint()) + # V1 name (e.g. coldstart, warmstart): + # print('* generic: %s' % variables['pdu']['generic-trap'].prettyPrint()) + # print('* generic: %d' % variables['pdu']['generic-trap']) + # print('* specific: %s' % variables['pdu']['specific-trap'].prettyPrint()) + # print('* specific: %d' % variables['pdu']['specific-trap']) + # init dictionary on new trap tds.trap_dict = {} + # FMDL.CHECK_WITH_DOWNSTREAM_CONSUMERS: get rid of round for millisecond val + # epoch_second = int(round(time.time())) + epoch_msecond = time.time() + epoch_second = int(round(epoch_msecond)) + if epoch_second == tds.last_epoch_second: + tds.traps_in_epoch += 1 + else: + tds.traps_in_epoch = 0 + tds.last_epoch_second = epoch_second + traps_in_epoch_04d = format(tds.traps_in_epoch, '04d') + tds.trap_dict['epoch_serno'] = int( + (str(epoch_second) + str(traps_in_epoch_04d))) + # assign uuid to trap tds.trap_dict["uuid"] = str(uuid_mod.uuid1()) @@ -413,7 +443,7 @@ def snmp_engine_observer_cb(snmp_engine, execpoint, variables, cbCtx): tds.dns_cache_ip_to_name[ip_addr_str] = agent_fqdn tds.dns_cache_ip_expires[ip_addr_str] = ( - time.time() + tds.c_config['cache.dns_cache_ttl_seconds']) + time.time() + tds.c_config['cache']['dns_cache_ttl_seconds']) msg = "cache for %s (%s) updated - set to expire at %d" % \ (agent_fqdn, ip_addr_str, tds.dns_cache_ip_expires[ip_addr_str]) ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, @@ -424,40 +454,35 @@ def snmp_engine_observer_cb(snmp_engine, execpoint, variables, cbCtx): tds.trap_dict["community"] = "" tds.trap_dict["community len"] = 0 - # FMDL.CHECK_WITH_DOWNSTREAM_CONSUMERS: get rid of round for millisecond val - # epoch_second = int(round(time.time())) - epoch_msecond = time.time() - epoch_second = int(round(epoch_msecond)) - if epoch_second == tds.last_epoch_second: - tds.traps_in_epoch += 1 - else: - tds.traps_in_epoch = 0 - tds.last_epoch_second = epoch_second - traps_in_epoch_04d = format(tds.traps_in_epoch, '04d') - tds.trap_dict['epoch_serno'] = int( - (str(epoch_second) + str(traps_in_epoch_04d))) - snmp_version = variables['securityModel'] if snmp_version == 1: tds.trap_dict["protocol version"] = "v1" + enterprise = variables['pdu']['enterprise'].prettyPrint() + generic_trap = variables['pdu']['generic-trap'] + specific_trap = variables['pdu']['specific-trap'] + if generic_trap < 6: + tds.trap_dict["notify OID"] = str(enterprise) + "." + str(specific_trap) + else: + tds.trap_dict["notify OID"] = str(enterprise) + ".0." + str(specific_trap) + tds.trap_dict["notify OID len"] = (tds.trap_dict["notify OID"].count('.') + 1) + tds.trap_dict["sysUptime"] = variables['pdu']['time-stamp'].prettyPrint() else: if snmp_version == 2: tds.trap_dict["protocol version"] = "v2c" else: if snmp_version == 3: tds.trap_dict["protocol version"] = "v3" + tds.trap_dict["security level"] = str(variables['securityLevel']) + tds.trap_dict["context name"] = str( + variables['contextName'].prettyPrint()) + tds.trap_dict["security name"] = str(variables['securityName']) + tds.trap_dict["security engine"] = str( + variables['contextEngineId'].prettyPrint()) else: tds.trap_dict["protocol version"] = "unknown" - if snmp_version == 3: - tds.trap_dict["protocol version"] = "v3" - tds.trap_dict["security level"] = str(variables['securityLevel']) - tds.trap_dict["context name"] = str( - variables['contextName'].prettyPrint()) - tds.trap_dict["security name"] = str(variables['securityName']) - tds.trap_dict["security engine"] = str( - variables['contextEngineId'].prettyPrint()) - tds.trap_dict['time received'] = epoch_msecond + # tds.trap_dict['time received'] = epoch_msecond + tds.trap_dict['time received'] = epoch_second tds.trap_dict['trap category'] = ( tds.c_config['streams_publishes']['sec_fault_unsecure']['dmaap_info']['topic_url']).split('/')[-1] @@ -486,7 +511,7 @@ def add_varbind_to_json(vb_idx, vb_oid, vb_type, vb_val): _individual_vb_dict = {} - if tds.trap_dict["protocol version"] == "v2c": + if tds.trap_dict["protocol version"] == "v2c" or tds.trap_dict["protocol version"] == "v3": # if v2c and first 2 varbinds, special handling required - e.g. put # in trap_dict, not vb_json_str if vb_idx == 0: @@ -537,10 +562,18 @@ def notif_receiver_cb(snmp_engine, stateReference, contextEngineId, contextName, callback trap arrival :Variables: """ - msg = "processing varbinds for %s" % (tds.trap_dict["uuid"]) ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) + # help(snmp_engine) + # print(snmp_engine) + # help(varBinds) + # print(varBinds) + # help(cbCtx) + # print(cbCtx) + # for key, val in cbCtx: + # print(key, val) + # FMDL update reset location when batching publishes vb_idx = 0 @@ -573,6 +606,9 @@ def notif_receiver_cb(snmp_engine, stateReference, contextEngineId, contextName, msg = "trap %s : %s" % (tds.trap_dict["uuid"], curr_trap_json_str) ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) + # always log arriving traps + log_all_arriving_traps() + # now have a complete json message for this trap in "curr_trap_json_str" tds.traps_since_last_publish += 1 milliseconds_since_last_publish = (time.time() - tds.last_pub_time) * 1000 @@ -588,17 +624,14 @@ def notif_receiver_cb(snmp_engine, stateReference, contextEngineId, contextName, ', ' + tds.trap_dict["uuid"] tds.all_traps_str = tds.all_traps_str + ', ' + curr_trap_json_str - # always log arriving traps - log_all_arriving_traps() - # publish to dmaap after last varbind is processed - if tds.traps_since_last_publish >= tds.c_config['publisher.max_traps_between_publishes']: + if tds.traps_since_last_publish >= tds.c_config['publisher']['max_traps_between_publishes']: msg = "num traps since last publish (%d) exceeds threshold (%d) - publish traps" % ( - tds.traps_since_last_publish, tds.c_config['publisher.max_traps_between_publishes']) + tds.traps_since_last_publish, tds.c_config['publisher']['max_traps_between_publishes']) ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) post_dmaap() - elif milliseconds_since_last_publish >= tds.c_config['publisher.max_milliseconds_between_publishes']: + elif milliseconds_since_last_publish >= tds.c_config['publisher']['max_milliseconds_between_publishes']: msg = "num milliseconds since last publish (%.0f) exceeds threshold - publish traps" % milliseconds_since_last_publish ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) @@ -633,13 +666,17 @@ if __name__ == "__main__": # init vars tds.init() + # FMDL: add with stormWatch + # init sw vars + # sw.init() + # Set initial startup hour for rolling logfile tds.last_hour = datetime.datetime.now().hour # get config binding service (CBS) values (either broker, or json file override) load_all_configs(0, 0) msg = "%s : %s version %s starting" % ( - prog_name, tds.c_config['snmptrap.title'], tds.c_config['snmptrap.version']) + prog_name, tds.c_config['snmptrapd']['title'], tds.c_config['snmptrapd']['version']) stdout_logger(msg) # open various ecomp logs @@ -650,18 +687,21 @@ if __name__ == "__main__": msg = "WARNING: '-v' argument present. All diagnostic messages will be logged. This can slow things down, use only when needed." tds.minimum_severity_to_log = 0 stdout_logger(msg) + # use specific flags or 'all' for full debugging + help(debug.setLogger) + debug.setLogger(debug.Debug('dsp', 'msgproc')) # name and open arriving trap log - tds.arriving_traps_filename = tds.c_config['files.runtime_base_dir'] + "/" + \ - tds.c_config['files.log_dir'] + "/" + \ - (tds.c_config['files.arriving_traps_log']) + tds.arriving_traps_filename = tds.c_config['files']['runtime_base_dir'] + "/" + \ + tds.c_config['files']['log_dir'] + "/" + \ + (tds.c_config['files']['arriving_traps_log']) tds.arriving_traps_fd = open_file(tds.arriving_traps_filename) msg = ("arriving traps logged to: %s" % tds.arriving_traps_filename) stdout_logger(msg) ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) # name and open json trap log - tds.json_traps_filename = tds.c_config['files.runtime_base_dir'] + "/" + tds.c_config['files.log_dir'] + "/" + "DMAAP_" + ( + tds.json_traps_filename = tds.c_config['files']['runtime_base_dir'] + "/" + tds.c_config['files']['log_dir'] + "/" + "DMAAP_" + ( tds.c_config['streams_publishes']['sec_fault_unsecure']['dmaap_info']['topic_url'].split('/')[-1]) + ".json" tds.json_traps_fd = open_file(tds.json_traps_filename) msg = ("published traps logged to: %s" % tds.json_traps_filename) @@ -672,8 +712,8 @@ if __name__ == "__main__": signal.signal(signal.SIGUSR1, load_all_configs) # save current PID for future/external reference - tds.pid_file_name = tds.c_config['files.runtime_base_dir'] + \ - '/' + tds.c_config['files.pid_dir'] + '/' + prog_name + ".pid" + tds.pid_file_name = tds.c_config['files']['runtime_base_dir'] + \ + '/' + tds.c_config['files']['pid_dir'] + '/' + prog_name + ".pid" msg = "Runtime PID file: %s" % tds.pid_file_name ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) rc = save_pid(tds.pid_file_name) @@ -681,7 +721,7 @@ if __name__ == "__main__": # Get the event loop for this thread loop = asyncio.get_event_loop() - # Create SNMP engine with autogenernated engineID pre-bound + # Create SNMP engine with autogenerated engineID pre-bound # to socket transport dispatcher snmp_engine = engine.SnmpEngine() @@ -690,12 +730,17 @@ if __name__ == "__main__": # # # # # # # # # # # # # UDP over IPv4 - # FMDL: add check for presense of ipv4_interface prior to attempting add OR just put entire thing in try/except clause try: - ipv4_interface = tds.c_config['protocols.ipv4_interface'] - ipv4_port = tds.c_config['protocols.ipv4_port'] + ipv4_interface = tds.c_config['protocols']['ipv4_interface'] + ipv4_port = tds.c_config['protocols']['ipv4_port'] try: + # FIXME: this doesn't appear to throw an exception even if + # the userID is unable (perms) to bind to port + # + # We may need to open raw port using other + # means to confirm proper privileges (then + # close it and reopen w/ pysnmp api) config.addTransport( snmp_engine, udp.domainName + (1,), @@ -716,10 +761,9 @@ if __name__ == "__main__": # UDP over IPv6 - # FMDL: add check for presense of ipv6_interface prior to attempting add OR just put entire thing in try/except clause try: - ipv6_interface = tds.c_config['protocols.ipv6_interface'] - ipv6_port = tds.c_config['protocols.ipv6_port'] + ipv6_interface = tds.c_config['protocols']['ipv6_interface'] + ipv6_port = tds.c_config['protocols']['ipv6_port'] try: config.addTransport( @@ -755,7 +799,12 @@ if __name__ == "__main__": comm_string_rewrite_observer, 'rfc2576.processIncomingMsg:writable' ) - + + # # # # # # # # # # # # + # SNMPv3 setup + # # # # # # # # # # # # + config, snmp_engine=load_snmpv3_credentials(config, snmp_engine, tds.c_config) + # register snmp_engine_observer_cb for message arrival snmp_engine.observer.registerObserver( snmp_engine_observer_cb, -- cgit 1.2.3-korg