Skip to content
Snippets Groups Projects
Commit 9ff905eb authored by David Schmitz's avatar David Schmitz
Browse files

fix/wrong_ratelimit_stats: corrected algorithm of SNMP statistics fetching for...

fix/wrong_ratelimit_stats: corrected algorithm of SNMP statistics fetching for mitigation rules using rate-limiting: using different SNMP table entries containing the rate_limit value in front of  the match specification (implemented by policiers internally in JUNOS)
parent 8190524d
No related branches found
No related tags found
No related merge requests found
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
......@@ -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))
......
......@@ -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})
......
......@@ -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;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment