Skip to content

Commit 3454c72

Browse files
committed
plugin linux_netfilter: fixes for port-ranges and unset mac-address
1 parent d09ee86 commit 3454c72

File tree

4 files changed

+143
-157
lines changed

4 files changed

+143
-157
lines changed

src/firewall_test/plugins/system/firewall_netfilter.py

Lines changed: 90 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from simulator.packet import PacketIP, PacketTCPUDP, PacketICMP
66
from utils.logger import log_debug, log_warn
77

8+
# todo: add explicit match-tests
9+
810

911
# pylint: disable=R0912
1012
class RuleMatcherNetfilter(RuleMatcher):
@@ -16,95 +18,95 @@ def matches(self, packet: (PacketIP, PacketTCPUDP, PacketICMP), rule: Rule) -> R
1618
"""
1719
nf_rule: NftRule = rule.raw
1820

19-
if rule.action is None:
21+
if rule.action is None or \
22+
not issubclass(rule.action, (RuleActionKindTerminal, RuleActionKindToChain, RuleActionKindNAT)):
2023
return RuleMatchResult(False, None, None, None, None)
2124

22-
if issubclass(rule.action, (RuleActionKindTerminal, RuleActionKindToChain, RuleActionKindNAT)):
23-
results = []
24-
25-
if len(nf_rule.matches) == 0:
26-
return RuleMatchResult(
27-
matched=True,
28-
action=rule.action,
29-
target_chain_name=nf_rule.target_chain,
30-
target_nat_ip=nf_rule.target_nat_ip,
31-
target_nat_port=nf_rule.target_nat_port,
32-
)
33-
34-
for match in nf_rule.matches:
35-
single_l3_result = True # 'ip6 daddr != XXX' would drop any IPv4 traffic
36-
single_results = []
37-
# NETWORK INTERFACES
38-
if match.match_ni_in:
39-
single_results.append(packet.ni_in in match.value)
40-
41-
if match.match_ni_out:
42-
single_results.append(packet.ni_out in match.value)
43-
44-
# IP PROTOCOL
45-
if match.match_proto_l3:
46-
if match.value_proto_l3:
47-
single_l3_result = packet.proto_l3 == match.value_proto_l3
48-
49-
else:
50-
single_l3_result = packet.proto_l3 in match.value
51-
52-
# IP SOURCE AND DESTINATION
53-
if match.match_ip_saddr:
54-
single_results.append(any(
55-
packet.src in ip_net for ip_net in match.value
56-
))
57-
58-
if match.match_ip_daddr:
59-
single_results.append(any(
60-
packet.dst in ip_net for ip_net in match.value
61-
))
62-
63-
# TRANSPORT PROTOCOL
64-
if match.match_proto_l4:
65-
if match.value_proto_l4:
66-
single_results.append(packet.proto_l4 == match.value_proto_l4)
67-
68-
else:
69-
single_results.append(packet.proto_l4 in match.value)
70-
71-
if isinstance(packet, PacketTCPUDP):
72-
# PORTS
73-
if match.match_sport:
74-
single_results.append(packet.sport in match.value)
75-
76-
if match.match_dport:
77-
single_results.append(packet.dport in match.value)
78-
79-
# CONNECTION TRACKING STATE
80-
if match.match_ct:
81-
single_results.append(packet.ct in match.value)
82-
83-
# if we need to separate the L3-result from the actual condition as it can impact the match
84-
results.append(single_l3_result)
85-
86-
if len(single_results) > 0:
87-
if match.operator in [match.OP_EQ, match.OP_IN]:
88-
results.append(all(single_results))
89-
90-
elif match.operator in [match.OP_NE, match.OP_NOT]:
91-
results.append(not all(single_results))
92-
93-
else:
94-
log_warn('Firewall Plugin', f' > Unable to get results for operator "{match.operator}"')
95-
96-
if len(results) == 0:
97-
log_warn('Firewall Plugin', ' > Matches: Found not matches we could process - skipping rule')
98-
99-
else:
100-
log_debug('Firewall Plugin', f' > Match Results: {nf_rule.get_match_types()} => {results}')
101-
102-
return RuleMatchResult(
103-
matched=all(results),
104-
action=rule.action,
105-
target_chain_name=nf_rule.target_chain,
106-
target_nat_ip=nf_rule.target_nat_ip,
107-
target_nat_port=nf_rule.target_nat_port,
108-
)
25+
results = []
26+
27+
if len(nf_rule.matches) == 0:
28+
return RuleMatchResult(
29+
matched=True,
30+
action=rule.action,
31+
target_chain_name=nf_rule.target_chain,
32+
target_nat_ip=nf_rule.target_nat_ip,
33+
target_nat_port=nf_rule.target_nat_port,
34+
)
35+
36+
for match in nf_rule.matches:
37+
single_l3_result = True # 'ip6 daddr != XXX' would drop any IPv4 traffic
38+
single_results = []
39+
# NETWORK INTERFACES
40+
if match.match_ni_in:
41+
single_results.append(packet.ni_in in match.value)
42+
43+
if match.match_ni_out:
44+
single_results.append(packet.ni_out in match.value)
45+
46+
# IP PROTOCOL
47+
if match.match_proto_l3:
48+
if match.value_proto_l3:
49+
single_l3_result = packet.proto_l3 == match.value_proto_l3
50+
51+
else:
52+
single_l3_result = packet.proto_l3 in match.value
53+
54+
# IP SOURCE AND DESTINATION
55+
if match.match_ip_saddr:
56+
single_results.append(any(
57+
packet.src in ip_net for ip_net in match.value
58+
))
59+
60+
if match.match_ip_daddr:
61+
single_results.append(any(
62+
packet.dst in ip_net for ip_net in match.value
63+
))
64+
65+
# TRANSPORT PROTOCOL
66+
if match.match_proto_l4:
67+
if match.value_proto_l4:
68+
single_results.append(packet.proto_l4 == match.value_proto_l4)
69+
70+
else:
71+
single_results.append(packet.proto_l4 in match.value)
72+
73+
if isinstance(packet, PacketTCPUDP):
74+
# PORTS
75+
if match.match_sport:
76+
single_results.append(packet.sport in match.value)
77+
78+
if match.match_dport:
79+
single_results.append(packet.dport in match.value)
80+
81+
# CONNECTION TRACKING STATE
82+
if match.match_ct:
83+
single_results.append(packet.ct in match.value)
84+
85+
# if we need to separate the L3-result from the actual condition as it can impact the match
86+
results.append(single_l3_result)
87+
88+
if len(single_results) > 0:
89+
if match.operator in [match.OP_EQ, match.OP_IN]:
90+
results.append(all(single_results))
91+
92+
elif match.operator in [match.OP_NE, match.OP_NOT]:
93+
results.append(not all(single_results))
94+
95+
else:
96+
log_warn('Firewall Plugin', f' > Unable to get results for operator "{match.operator}"')
97+
98+
if len(results) == 0:
99+
log_warn('Firewall Plugin', ' > Matches: Found not matches we could process - skipping rule')
100+
101+
else:
102+
log_debug('Firewall Plugin', f' > Match Results: {nf_rule.get_matches()} => {results}')
103+
104+
return RuleMatchResult(
105+
matched=all(results),
106+
action=rule.action,
107+
target_chain_name=nf_rule.target_chain,
108+
target_nat_ip=nf_rule.target_nat_ip,
109+
target_nat_port=nf_rule.target_nat_port,
110+
)
109111

110112
return RuleMatchResult(False, None, None, None, None)

src/firewall_test/plugins/translate/linux.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def get(self) -> list[NetworkInterface]:
9999
def _parse_ni(raw: dict) -> dict:
100100
r = {
101101
'name': raw['ifname'],
102-
'mac': raw['address'],
102+
'mac': raw.get('address', None),
103103
ProtoL3IP4.N: [],
104104
ProtoL3IP6.N: [],
105105
'net4': [],

src/firewall_test/plugins/translate/netfilter/elements.py

Lines changed: 49 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from abc import ABC
2-
from ipaddress import ip_network, summarize_address_range, ip_address, IPv4Network, IPv6Network
2+
from ipaddress import ip_network, summarize_address_range, ip_address
33

44
from config import ProtoL3, ProtoL3IP4, ProtoL3IP6, MatchPort, ProtoL4ICMP, ProtoL4TCP, ProtoL4UDP, ProtoL3IP4IP6, \
5-
PROTO_L4_MAPPING, PROTO_L3_MAPPING, PROTOS_L3, PROTOS_L4
5+
PROTO_L4_MAPPING, PROTO_L3_MAPPING
66
from plugins.translate.netfilter.parts import RULE_ACTIONS, IGNORE_RULE_EXPRESSIONS, IGNORE_LEFT
77
from utils.logger import log_warn
88

@@ -191,14 +191,21 @@ def parse_right(self):
191191
return
192192

193193
if 'prefix' in self._right:
194+
# parsed in 'update_value_type'
195+
self.value = [self._right]
196+
197+
if 'range' in self._right:
198+
# parsed in 'update_value_type'
194199
self.value = [self._right]
195200

196201
if isinstance(self._right, (str, int)):
197202
self.value = [self._right]
198203

199204
if self.value is None:
200205
if 'range' not in self._right and 'fin' not in self._right:
201-
raise ValueError(self._right)
206+
log_warn('Firewall Plugin', f'Unable to parse match (right): "{self._right}"')
207+
return
208+
202209
self.value = self._right
203210

204211
def update_value_type(self):
@@ -220,11 +227,16 @@ def update_value_type(self):
220227
values.append(ip_network(f"{v['addr']}/{v['len']}"))
221228

222229
elif 'range' in v:
223-
range_nets = list(summarize_address_range(
224-
ip_address(v['range'][0]),
225-
ip_address(v['range'][1]),
226-
))
227-
values.extend(range_nets)
230+
if str(v['range'][0]).isnumeric():
231+
range_ports = range(v['range'][0], v['range'][1])
232+
values.extend(range_ports)
233+
234+
else:
235+
range_nets = list(summarize_address_range(
236+
ip_address(v['range'][0]),
237+
ip_address(v['range'][1]),
238+
))
239+
values.extend(range_nets)
228240

229241
else:
230242
raise ValueError(self.value)
@@ -258,86 +270,54 @@ def update_value_type(self):
258270

259271
self.value = values
260272

261-
def get_match_types(self) -> (list[str], None):
262-
match = []
273+
def get_matches(self) -> (dict, None):
274+
matches = {}
263275
if self.match_proto_l3:
264-
match.append('proto_l3')
276+
if self.value_proto_l3:
277+
matches['proto_l3'] = {self.OP_EQ: self.value_proto_l3.N}
278+
279+
else:
280+
matches['proto_l3'] = {self.operator: [v.N for v in self.value]}
265281

266282
if self.match_proto_l4:
267-
match.append('proto_l4')
283+
if self.value_proto_l4:
284+
matches['proto_l4'] = {self.OP_EQ: self.value_proto_l4.N}
285+
286+
else:
287+
matches['proto_l4'] = {self.operator: [v.N for v in self.value]}
268288

269289
if self.match_ip_saddr:
270-
match.append('ip_saddr')
290+
matches['ip_saddr'] = { self.operator: [str(v) for v in self.value]}
271291

272292
if self.match_ip_daddr:
273-
match.append('ip_daddr')
293+
matches['ip_daddr'] = {self.operator: [str(v) for v in self.value]}
274294

275295
if self.match_ni_in:
276-
match.append('ni_in')
296+
matches['ni_in'] = {self.operator: self.value}
277297

278298
if self.match_ni_out:
279-
match.append('ni_out')
299+
matches['ni_out'] = {self.operator: self.value}
280300

281301
if self.match_sport:
282-
match.append('src-port')
302+
matches['src_port'] = {self.operator: self.value}
283303

284304
if self.match_dport:
285-
match.append('dst-port')
305+
matches['dst_port'] = {self.operator: self.value}
286306

287307
if self.match_ct:
288-
match.append('ct')
308+
matches['ct'] = {self.operator: self.value}
289309

290-
if len(match) == 0:
291-
match = None
310+
if len(matches) == 0:
311+
return None
292312

293-
return match
313+
return matches
294314

295315
def __repr__(self) -> str:
296-
values = []
297-
for v in self.value:
298-
if isinstance(v, (IPv4Network, IPv6Network)):
299-
values.append(str(v))
300-
301-
elif v in PROTOS_L3 or v in PROTOS_L4:
302-
values.append(v.N)
303-
304-
else:
305-
values.append(v)
306-
307-
if self.value_proto_l3 is not None:
308-
values.append(self.value_proto_l3.N)
309-
310-
if self.value_proto_l4 is not None:
311-
values.append(self.value_proto_l4.N)
312-
313-
matches = self.get_match_types()
316+
matches = self.get_matches()
314317
if matches is None:
315318
return 'None (unsupported)'
316319

317-
if len(matches) == 1:
318-
return f"{matches[0]} {self.operator} {values}"
319-
320-
match_proto = None
321-
value_proto = None
322-
for proto_kind, proto_mapping in {
323-
'proto_l3': PROTO_L3_MAPPING,
324-
'proto_l4': PROTO_L4_MAPPING,
325-
}.items():
326-
if proto_kind in matches:
327-
match_proto = proto_kind
328-
for v in values:
329-
if v in proto_mapping:
330-
value_proto = v
331-
332-
if match_proto is not None and value_proto is not None:
333-
matches.remove(match_proto)
334-
values.remove(value_proto)
335-
if len(matches) == 1:
336-
matches = matches[0]
337-
338-
return f"{match_proto} {self.OP_EQ} {value_proto} & {matches} {self.operator} {values}"
339-
340-
return f"{matches} {self.operator} {values}"
320+
return f"{self.operator} {matches}"
341321

342322

343323
class NftRule(NftBase):
@@ -349,6 +329,7 @@ def __init__(self, table: NftTable, chain: NftChain, raw: dict, seq: int, sets:
349329

350330
self.comment = raw.get('comment', None)
351331
self.family = raw.get('family', None)
332+
self.handle = raw.get('handle', None)
352333

353334
self.action = None
354335
self.matches: list[NftMatch] = []
@@ -411,7 +392,7 @@ def _init_match(self, e: dict) -> bool:
411392
left=e['match']['left'],
412393
right=e['match']['right'],
413394
)
414-
if match.get_match_types() is None:
395+
if match.get_matches() is None:
415396
self.invalid_matches = True
416397

417398
else:
@@ -448,10 +429,10 @@ def _init_nat_xt(self, e: dict) -> bool:
448429

449430
return False
450431

451-
def get_match_types(self) -> list[str]:
452-
matches = []
432+
def get_matches(self) -> dict:
433+
matches = {}
453434
for m in self.matches:
454-
matches.extend(m.get_match_types())
435+
matches = {**matches, **m.get_matches()}
455436

456437
return matches
457438

0 commit comments

Comments
 (0)