diff --git a/doc/development/format_of_snmp_json_db_file.txt b/doc/development/format_of_snmp_json_db_file.txt new file mode 100644 index 0000000000000000000000000000000000000000..e5387ea48ffb910dbef04ab37f686009f5613080 --- /dev/null +++ b/doc/development/format_of_snmp_json_db_file.txt @@ -0,0 +1,53 @@ + +statistics table: + +a measurement value +per rule, +for each measurement time slot, + +e.g., + +{ + "_per_rule": { + "243": [ + {"ts": "2023-09-28T13:00:02.107721", "value": {"packets": 0, "bytes": 0 }, + {"ts": "2023-09-28T13:00:07.107721", "value": {"packets": 10, "bytes": 4545 } }, + {"ts": "2023-09-28T13:00:12.107721", "value": 1, + {"ts": "2023-09-28T13:00:17.107721", "value": {"packets": 30, "bytes": 8539 } }, + {"ts": "2023-09-28T13:00:22.107721", "value": {"packets": 80, "bytes": 18234 } }, + {"ts": "2023-09-28T13:00:27.107721", "value": {"packets": 80, "bytes": 18234 } }, + {"ts": "2023-09-28T13:00:32.107721", "value": {"packets": 80, "bytes": 18234 } }, + {"ts": "2023-09-28T13:00:33.107721", "value": 0 }, + {"ts": "2023-09-30T14:00:11.107721", "value": {"packets": 0, "bytes": 0 }, + {"ts": "2023-09-30T14:00:12.107721", "value": {"packets": 15, "bytes": 3691 } }, + {"ts": "2023-09-30T14:00:17.107721", "value": {"packets": 25, "bytes": 18539 } }, + {"ts": "2023-09-30T14:00:22.107721", "value": {"packets": 120, "bytes": 28934 } }, + {"ts": "2023-09-30T14:00:27.107721", "value": {"packets": 120, "bytes": 28934 } }, + {"ts": "2023-09-30T14:00:32.107721", "value": {"packets": 120, "bytes": 28934 } }, + {"ts": "2023-09-30T14:00:37.107721", "value": {"packets": 120, "bytes": 28934 } }, + {"ts": "2023-09-30T14:00:42.107721", "value": {"packets": 120, "bytes": 28934 } }, + {"ts": "2023-09-30T14:00:47.107721", "value": {"packets": 120, "bytes": 28934 } }, + {"ts": "2023-09-28T14:00:49.107721", "value": 0 }, + ], + ... + }, +} + +measurement value per time slot is either: + +zero measurement: = { bytes=>0, packets=>0, } : at start of measurement = activation of the rule on the router + +or + +real measurement (example): = { bytes=>1414, packets=>5, } : cumulative (=absolue values from start of measurement = from last activation of the rule) bytes/packet counters + +or + +missing_value: =1 : no measurements from none of the routers queried answered values for the specific rule in the specific scheduling time slot (while rule is activated) + +null_value: =0 : used when rule is deactivated = measurement should stop on the router + +when rule is re-activated again on the router after some duration of having been deactivated it is started again with a zero measurement in the table and absolute values for the future real measurement are starting from zero again + + + diff --git a/flowspec/snmpstats.py b/flowspec/snmpstats.py index b47a831150c2c18c4f78e23f5c7743d831803168..5cda2cb8c4944d8fe91bcf350436fd688cecad7c 100644 --- a/flowspec/snmpstats.py +++ b/flowspec/snmpstats.py @@ -23,6 +23,7 @@ from datetime import datetime, timedelta import json import os import time +import re from flowspec.models import Route from flowspec.junos import create_junos_name @@ -36,17 +37,19 @@ identoffset = len(settings.SNMP_CNTPACKETS) + 1 # noinspection PyUnusedLocal,PyUnusedLocal def snmpCallback(snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorIndex, varBindTable, cbCtx): + try: (authData, transportTarget, results) = cbCtx + logger.error('snmpCallback(): called') # debug - which router replies: #print('%s via %s' % (authData, transportTarget)) # CNTPACKETS and CNTBYTES are of the same length if errorIndication: - logger.error('Bad errorIndication.') + logger.error('snmpCallback(): Bad errorIndication.') return 0 elif errorStatus: - logger.error('Bad errorStatus.') + logger.error('snmpCallback(): Bad errorStatus.') return 0 for varBindRow in varBindTable: for name, val in varBindRow: @@ -56,7 +59,7 @@ def snmpCallback(snmpEngine, sendRequestHandle, errorIndication, elif name.startswith(settings.SNMP_CNTBYTES): counter = "bytes" else: - logger.debug('Finished {}.'.format(transportTarget)) + logger.debug('snmpCallback(): Finished {}.'.format(transportTarget)) return 0 ident = name[identoffset:] @@ -70,28 +73,38 @@ def snmpCallback(snmpEngine, sendRequestHandle, errorIndication, len2 = ordvals[len1] + 1 routename = "".join([chr(i) for i in ordvals[len1 + 1:len1 + len2]]) + logger.error("routename="+str(routename)) + xtype='counter' + if re.match(r'^[0-9]+[Mk]_', routename): + ary=re.split(r'_', routename, maxsplit=1) + xtype=ary[0] + routename=ary[1] + logger.error("=> routename="+str(routename)+" xtype="+str(xtype)) + # add value into dict if routename in results: - if counter in results[routename]: - results[routename][counter] = results[routename][counter] + int(val) + if counter in results[routename][xtype]: + results[routename][xtype][counter] = results[routename][counter][xtype] + int(val) else: - results[routename][counter] = int(val) + results[routename][xtype][counter] = int(val) else: - results[routename] = {counter: int(val)} + results[routename] = { xtype: {counter: int(val)} } logger.debug("%s %s %s %s = %s" %(transportTarget, counter, tablename, routename, int(val))) - - return 1 # continue table retrieval + + except Exception as e: + logger.error("snmpCallback(): got exception "+str(e), exc_info=True) + return 1 # continue table retrieval def get_snmp_stats(): - """Return dict() of the sum of counters (bytes, packets) from all selected routes, where - route identifier is the key in dict. The sum is counted over all routers. - - Example output with one rule: {'77.72.72.1,0/0,proto=1': {'bytes': 13892216, 'packets': 165387}} + """Return dict() of the sum of counters (bytes, packets) from all selected routes, where + route identifier is the key in dict. The sum is counted over all routers. - This function uses SNMP_IP list, SNMP_COMMUNITY, SNMP_CNTPACKETS and - SNMP_RULESFILTER list, all defined in settings.""" + Example output with one rule: {'77.72.72.1,0/0,proto=1': {'bytes': 13892216, 'packets': 165387}} + This function uses SNMP_IP list, SNMP_COMMUNITY, SNMP_CNTPACKETS and + SNMP_RULESFILTER list, all defined in settings.""" + try: if not isinstance(settings.SNMP_IP, list): settings.SNMP_IP = [settings.SNMP_IP] @@ -132,6 +145,8 @@ def get_snmp_stats(): snmpEngine.transportDispatcher.runDispatcher() return results + except Exception as e: + logger.error("get_snmp_stats(): got exception "+str(e), exc_info=True) def lock_history_file(wait=1, reason=""): first=1 @@ -272,12 +287,17 @@ def poll_snmp_statistics(): # proper update history samplecount = settings.SNMP_MAX_SAMPLECOUNT for rule in newdata: - counter = {"ts": nowstr, "value": newdata[rule]} - if rule in history: - history[rule].insert(0, counter) - history[rule] = history[rule][:samplecount] - else: - history[rule] = [counter] + for xtype in newdata[rule]: + key = "value" + if xtype!="counter": + key = "value-"+str(xtype) + #counter = {"ts": nowstr, "value": newdata[rule]['counter']} + counter = {"ts": nowstr, key: newdata[rule][xtype]} + if rule in history: + history[rule].insert(0, counter) + history[rule] = history[rule][:samplecount] + else: + history[rule] = [counter] # check for old rules and remove them toremove = [] @@ -310,7 +330,15 @@ def poll_snmp_statistics(): for ruleobj in queryset: rule_id = str(ruleobj.id) rule_status = str(ruleobj.status).upper() - logger.debug("snmpstats: STATISTICS_PER_RULE rule_id="+str(rule_id)+" rule_status="+str(rule_status)) + + xtype='counter' + limit_rate = None + for thenaction in ruleobj.then.all(): + if thenaction.action and thenaction.action=='rate-limit': + limit_rate=thenaction.action_value + xtype=str(limit_rate) + + logger.debug("snmpstats: STATISTICS_PER_RULE rule_id="+str(rule_id)+" rule_status="+str(rule_status)+" limit_rate="+str(limit_rate)) #rule_last_updated = str(ruleobj.last_updated) # e.g. 2018-06-21 08:03:21+00:00 #rule_last_updated = datetime.strptime(str(ruleobj.last_updated), '%Y-%m-%d %H:%M:%S+00:00') # TODO TZ offset assumed to be 00:00 rule_last_updated = helper_rule_ts_parse(str(ruleobj.last_updated)) @@ -326,7 +354,7 @@ def poll_snmp_statistics(): if rule_status=="ACTIVE": try: - counter = {"ts": nowstr, "value": newdata[flowspec_params_str]} + counter = {"ts": nowstr, "value": newdata[flowspec_params_str][xtype]} counter_is_null = False except Exception as e: logger.info("poll_snmp_statistics(): 1 STATISTICS_PER_RULE: exception: rule_id="+str(rule_id)+" newdata for flowspec_params_str='"+str(flowspec_params_str)+"' missing : "+str(e)) diff --git a/flowspec/tasks.py b/flowspec/tasks.py index e2ce3f72601cd38772f436c3c1293aa38ff11709..d503e907308b3fa0d70d65562b2e24b5d4c091c1 100644 --- a/flowspec/tasks.py +++ b/flowspec/tasks.py @@ -76,6 +76,8 @@ def edit(routepk, callback=None): commit, response = applier.apply(operation="replace") if commit: route.status = "ACTIVE" + route.response = response + route.save() # save() has to be called before snmp_add_initial_zero_value, as last_updated DB filed is updated now on every call of save() and last db_measurement time must become >= this new last_updated value try: #snmp_add_initial_zero_value.delay(str(route.id), True) snmp_add_initial_zero_value(str(route.id), True) @@ -86,10 +88,14 @@ def edit(routepk, callback=None): # repeat the action raise TimeoutError() route.status = "ERROR" + route.response = response + route.save() else: route.status = "ERROR" - route.response = response - route.save() + route.response = response + route.save() + #route.response = response + #route.save() announce("[%s] Rule edit: %s - Result: %s" % (route.applier_username_nice, route.name_visible, response), route.applier, route) @shared_task(ignore_result=True, autoretry_for=(TimeoutError, TimeLimitExceeded, SoftTimeLimitExceeded), retry_backoff=True, retry_kwargs={'max_retries': settings.NETCONF_MAX_RETRY_BEFORE_ERROR}) diff --git a/vnet_router/snmp/pass_persisttest_bgpflowspec b/vnet_router/snmp/pass_persisttest_bgpflowspec index d95210cb08f1cc7e63177d4846343a356e3c38d0..7adde9e8bb4add2b1fccd7e31a8599c359c5513a 100755 --- a/vnet_router/snmp/pass_persisttest_bgpflowspec +++ b/vnet_router/snmp/pass_persisttest_bgpflowspec @@ -105,6 +105,10 @@ sub get_snmp_rulename $src =~ s/^0.0.0.0\/0$/0\/0/; $dst =~ s/^0.0.0.0\/0$/0\/0/; + # https://github.com/librenms/librenms/blob/master/mibs/junos/JUNIPER-FIREWALL-MIB + + my $counter_type_value = 2; # default is normal counter + my $rulename = $dst.",".$src; my $proto = $protocol_num; @@ -144,9 +148,17 @@ sub get_snmp_rulename $rulename .= ",frag".join(",", (map { $frag_types->{$_}; } split(/,/, $fragment_options))); } + if (defined($thenaction)) { + if ($thenaction eq 'discard') { + } elsif ($thenaction =~ /^rate-limit-([0-9]+([Mk]?))$/) { + $rulename = $1."_".$rulename; + $counter_type_value = 3; # policer + } + } + print STDERR "rulename=$rulename$/" if ($debug); - return $rulename; + return ($rulename, $counter_type_value); } sub get_state($) @@ -163,11 +175,11 @@ sub get_state($) my ($cookie, $typex, $byte_ctr, $pkt_ctr, $rulename, @rule_params) = split(/\s+/, $line); next if ($typex eq 'default'); - my $snmp_rulename = get_snmp_rulename(@rule_params); + my ($snmp_rulename, $counter_type_value) = get_snmp_rulename(@rule_params); if ($snmp_rulename ne '') { my $prefix1 = string_to_oid_list($snmp_rulename); - my $snmp_oid_counter_pkts = $place_pkts.$rulesfilter_oidlist.$prefix1.".2"; - my $snmp_oid_counter_bytes = $place_bytes.$rulesfilter_oidlist.$prefix1.".2"; + my $snmp_oid_counter_pkts = $place_pkts.$rulesfilter_oidlist.$prefix1.".".$counter_type_value; + my $snmp_oid_counter_bytes = $place_bytes.$rulesfilter_oidlist.$prefix1.".".$counter_type_value; $state_hash->{$snmp_oid_counter_bytes} = $byte_ctr; $state_hash->{$snmp_oid_counter_pkts} = $pkt_ctr; }