Skip to content

Commit 7130695

Browse files
committed
fixed IPv6 connection parsing / added separate IPv4/IPv6 connection totals
1 parent 9ba697a commit 7130695

3 files changed

Lines changed: 67 additions & 14 deletions

File tree

mktxp/collector/connection_collector.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ def collect(router_entry):
2727
if connection_records:
2828
connection_metrics = BaseCollector.gauge_collector('ip_connections_total', 'Number of IP connections', connection_records, 'count',)
2929
yield connection_metrics
30+
ipv4_connection_metrics = BaseCollector.gauge_collector('ipv4_connections_total', 'Number of IPv4 connections', connection_records, 'ipv4_count',)
31+
yield ipv4_connection_metrics
32+
ipv6_connection_metrics = BaseCollector.gauge_collector('ipv6_connections_total', 'Number of IPv6 connections', connection_records, 'ipv6_count',)
33+
yield ipv6_connection_metrics
3034

3135
if router_entry.config_entry.connection_stats:
3236
connection_stats_records = IPConnectionStatsDatasource.metric_records(router_entry)

mktxp/datasource/connection_ds.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,38 @@ def metric_records(router_entry, *, metric_labels = None):
2424
if metric_labels is None:
2525
metric_labels = []
2626
try:
27-
res = router_entry.api_connection.router_api().get_resource('/ip/firewall/connection/').call('print', {'count-only': ''})
28-
# result processing as described at: https://github.com/socialwifi/RouterOS-api/issues/79#issuecomment-2089744809
27+
api = router_entry.api_connection.router_api()
28+
ipv4_cnt_str = '0'
29+
ipv6_cnt_str = '0'
30+
31+
res = api.get_resource('/ip/firewall/connection/').call('print', {'count-only': ''})
2932
cnt_str = res.done_message.get('ret')
33+
if cnt_str is not None:
34+
ipv4_cnt_str = cnt_str
35+
36+
try:
37+
res_v6 = api.get_resource('/ipv6/firewall/connection/').call('print', {'count-only': ''})
38+
cnt_str_v6 = res_v6.done_message.get('ret')
39+
if cnt_str_v6 is not None:
40+
ipv6_cnt_str = cnt_str_v6
41+
except Exception:
42+
pass
43+
44+
try:
45+
ipv4_count = int(ipv4_cnt_str)
46+
except (ValueError, TypeError):
47+
ipv4_count = 0
48+
3049
try:
31-
count = int(cnt_str)
50+
ipv6_count = int(ipv6_cnt_str)
3251
except (ValueError, TypeError):
33-
cnt_str = '0'
34-
records = [{'count': cnt_str}]
52+
ipv6_count = 0
53+
54+
records = [{
55+
'count': str(ipv4_count + ipv6_count),
56+
'ipv4_count': str(ipv4_count),
57+
'ipv6_count': str(ipv6_count)
58+
}]
3559
return BaseDSProcessor.trimmed_records(router_entry, router_records = records, metric_labels = metric_labels)
3660
except Exception as exc:
3761
print(f'Error getting IP connection info from router {router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
@@ -41,6 +65,15 @@ def metric_records(router_entry, *, metric_labels = None):
4165
class IPConnectionStatsDatasource:
4266
''' IP connections stats data provider
4367
'''
68+
@staticmethod
69+
def strip_port(address):
70+
if address.startswith('['):
71+
return address[1:address.find(']')]
72+
colons = address.count(':')
73+
if colons == 1:
74+
return address.split(':')[0]
75+
return address
76+
4477
@staticmethod
4578
def metric_records(router_entry, *, metric_labels = None, add_router_id = True):
4679
if metric_labels is None:
@@ -56,12 +89,18 @@ def metric_records(router_entry, *, metric_labels = None, add_router_id = True):
5689
else:
5790
proplist = 'src-address'
5891

59-
connection_records = router_entry.api_connection.router_api().get_resource('/ip/firewall/connection/').call('print', \
60-
{'proplist': proplist})
92+
api = router_entry.api_connection.router_api()
93+
connection_records = api.get_resource('/ip/firewall/connection/').call('print', {'proplist': proplist})
94+
try:
95+
connection_records_v6 = api.get_resource('/ipv6/firewall/connection/').call('print', {'proplist': proplist})
96+
connection_records.extend(connection_records_v6)
97+
except Exception:
98+
pass
99+
61100
# calculate number of connections per src-address
62101
connections_per_src_address = {}
63102
for connection_record in connection_records:
64-
address = connection_record['src-address'].split(':')[0]
103+
address = IPConnectionStatsDatasource.strip_port(connection_record.get('src-address', ''))
65104

66105
count, destinations = 0, set()
67106
if connections_per_src_address.get(address):

tests/collector/test_connection_collector.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,14 @@ def api_call_router(*args, **kwargs):
5555
# Test the method of focus
5656
result = IPConnectionStatsDatasource.metric_records(mock_router_entry)
5757
if should_make_stats_call:
58-
# This one should have been called twice, once for count and once for the stats
59-
assert call_mock.call_count == 2
58+
# This one should have been called four times, twice for count (IPv4+IPv6) and twice for the stats (IPv4+IPv6)
59+
assert call_mock.call_count == 4
6060
assert result is not None
6161
assert len(result) > 0
6262
assert result[0]['src_address'] == '1.1.1.1'
6363
else:
64-
# And this just once for the count
65-
call_mock.assert_called_once()
64+
# And this twice for the count (IPv4+IPv6)
65+
assert call_mock.call_count == 2
6666
assert result == []
6767

6868
def test_ip_connection_stats_datasource_without_destinations():
@@ -100,9 +100,19 @@ def api_call_router(*args, **kwargs):
100100

101101
result = IPConnectionStatsDatasource.metric_records(mock_router_entry)
102102

103-
assert call_mock.call_count == 2
103+
assert call_mock.call_count == 4
104104
assert result is not None
105105
assert len(result) == 1
106106
assert result[0]['src_address'] == '1.1.1.1'
107-
assert result[0]['connection_count'] == 2
107+
assert result[0]['connection_count'] == 4
108108
assert result[0]['dst_addresses'] == ''
109+
110+
def test_strip_port_ipv4_and_ipv6():
111+
"""
112+
Verifies that IPConnectionStatsDatasource.strip_port correctly parses IPv4 and IPv6 addresses.
113+
"""
114+
assert IPConnectionStatsDatasource.strip_port('192.168.1.1:80') == '192.168.1.1'
115+
assert IPConnectionStatsDatasource.strip_port('192.168.1.1') == '192.168.1.1'
116+
assert IPConnectionStatsDatasource.strip_port('[2001:db8::1]:443') == '2001:db8::1'
117+
assert IPConnectionStatsDatasource.strip_port('2001:db8::1') == '2001:db8::1'
118+
assert IPConnectionStatsDatasource.strip_port('') == ''

0 commit comments

Comments
 (0)