From deb9bfa02863ea28104f36558ed4e90caba792e3 Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Sun, 9 Jan 2022 23:35:30 +0100
Subject: policy: T4155: Fix using incorrect table variable
---
python/vyos/firewall.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'python/vyos/firewall.py')
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 8b7402b7e..414ec89c1 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -209,7 +209,7 @@ def parse_policy_set(set_conf, def_suffix):
table = set_conf['table']
if table == 'main':
table = '254'
- mark = 0x7FFFFFFF - int(set_conf['table'])
+ mark = 0x7FFFFFFF - int(table)
out.append(f'meta mark set {mark}')
if 'tcp_mss' in set_conf:
mss = set_conf['tcp_mss']
--
cgit v1.2.3
From a132ba993e786994a3b129c72fb0024931339619 Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Wed, 12 Jan 2022 00:59:53 +0100
Subject: firewall: T4160: Fix support for inverse matches
---
python/vyos/firewall.py | 35 ++++++++++++++++++++++++++++-------
src/validators/port-multi | 2 ++
2 files changed, 30 insertions(+), 7 deletions(-)
(limited to 'python/vyos/firewall.py')
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 414ec89c1..66dc8bc40 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -45,13 +45,19 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'state' in rule_conf and rule_conf['state']:
states = ",".join([s for s, v in rule_conf['state'].items() if v == 'enable'])
- output.append(f'ct state {{{states}}}')
+
+ if states:
+ output.append(f'ct state {{{states}}}')
if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
proto = rule_conf['protocol']
+ operator = ''
+ if proto[0] == '!':
+ operator = '!='
+ proto = proto[1:]
if proto == 'tcp_udp':
proto = '{tcp, udp}'
- output.append('meta l4proto ' + proto)
+ output.append(f'meta l4proto {operator} {proto}')
for side in ['destination', 'source']:
if side in rule_conf:
@@ -59,7 +65,10 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
side_conf = rule_conf[side]
if 'address' in side_conf:
- output.append(f'{ip_name} {prefix}addr {side_conf["address"]}')
+ suffix = side_conf['address']
+ if suffix[0] == '!':
+ suffix = f'!= {suffix[1:]}'
+ output.append(f'{ip_name} {prefix}addr {suffix}')
if 'mac_address' in side_conf:
suffix = side_conf["mac_address"]
@@ -69,15 +78,27 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'port' in side_conf:
proto = rule_conf['protocol']
- port = side_conf["port"]
+ port = side_conf['port'].split(',')
- if isinstance(port, list):
- port = ",".join(port)
+ ports = []
+ negated_ports = []
+
+ for p in port:
+ if p[0] == '!':
+ negated_ports.append(p[1:])
+ else:
+ ports.append(p)
if proto == 'tcp_udp':
proto = 'th'
- output.append(f'{proto} {prefix}port {{{port}}}')
+ if ports:
+ ports_str = ','.join(ports)
+ output.append(f'{proto} {prefix}port {{{ports_str}}}')
+
+ if negated_ports:
+ negated_ports_str = ','.join(negated_ports)
+ output.append(f'{proto} {prefix}port != {{{negated_ports_str}}}')
if 'group' in side_conf:
group = side_conf['group']
diff --git a/src/validators/port-multi b/src/validators/port-multi
index 017ea78fb..cef371563 100755
--- a/src/validators/port-multi
+++ b/src/validators/port-multi
@@ -22,6 +22,8 @@ if __name__ == '__main__':
services = get_services()
for port in ports:
+ if port and port[0] == '!':
+ port = port[1:]
if re.match('^[0-9]{1,5}-[0-9]{1,5}$', port):
port_1, port_2 = port.split('-')
if int(port_1) not in range(1, 65536) or int(port_2) not in range(1, 65536):
--
cgit v1.2.3
From df5a862beb84145dfc8434efde7d7fee783199cf Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Thu, 13 Jan 2022 12:58:37 +0100
Subject: firewall: T4178: Use lowercase for TCP flags and add an validator
---
.../include/firewall/common-rule.xml.i | 34 ++++++++++++++++++++--
.../include/policy/route-common-rule-ipv6.xml.i | 34 ++++++++++++++++++++--
.../include/policy/route-common-rule.xml.i | 34 ++++++++++++++++++++--
python/vyos/firewall.py | 7 ++---
src/conf_mode/firewall.py | 3 ++
src/conf_mode/policy-route.py | 10 +++----
src/validators/tcp-flag | 19 ++++++++++++
7 files changed, 126 insertions(+), 15 deletions(-)
create mode 100755 src/validators/tcp-flag
(limited to 'python/vyos/firewall.py')
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index 92950cc68..6e8203c88 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -274,12 +274,42 @@
TCP flags to match
txt
- TCP flags to match
+ Multiple comma-separated flags
+
+
+ syn
+ Syncronise flag
+
+
+ ack
+ Acknowledge flag
+
+
+ fin
+ Finish flag
+
+
+ rst
+ Reset flag
+
+
+ urg
+ Urgent flag
+
+
+ psh
+ Push flag
- \n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset
+ \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset
+
+ syn ack fin rst urg psh
+
+
+
+
diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
index 2d6adcd1d..b8fee4b7b 100644
--- a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
+++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
@@ -330,12 +330,42 @@
TCP flags to match
txt
- TCP flags to match
+ Multiple comma-separated flags
+
+
+ syn
+ Syncronise flag
+
+
+ ack
+ Acknowledge flag
+
+
+ fin
+ Finish flag
+
+
+ rst
+ Reset flag
+
+
+ urg
+ Urgent flag
+
+
+ psh
+ Push flag
- \n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset
+ \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset
+
+ syn ack fin rst urg psh
+
+
+
+
diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i
index c4deefd2a..17b47474d 100644
--- a/interface-definitions/include/policy/route-common-rule.xml.i
+++ b/interface-definitions/include/policy/route-common-rule.xml.i
@@ -330,12 +330,42 @@
TCP flags to match
txt
- TCP flags to match
+ Multiple comma-separated flags
+
+
+ syn
+ Syncronise flag
+
+
+ ack
+ Acknowledge flag
+
+
+ fin
+ Finish flag
+
+
+ rst
+ Reset flag
+
+
+ urg
+ Urgent flag
+
+
+ psh
+ Push flag
- \n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset
+ \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset
+
+ syn ack fin rst urg psh
+
+
+
+
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 66dc8bc40..acde9f913 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -171,7 +171,6 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if tcp_flags:
output.append(parse_tcp_flags(tcp_flags))
-
output.append('counter')
if 'set' in rule_conf:
@@ -190,10 +189,10 @@ def parse_tcp_flags(flags):
include = []
for flag in flags.split(","):
if flag[0] == '!':
- flag = flag[1:]
+ flag = flag[1:].lower()
else:
- include.append(flag)
- all_flags.append(flag)
+ include.append(flag.lower())
+ all_flags.append(flag.lower())
return f'tcp flags & ({"|".join(all_flags)}) == {"|".join(include)}'
def parse_time(time):
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 7b491a325..853470fd8 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -142,6 +142,9 @@ def verify_rule(firewall, rule_conf, ipv6):
if not {'count', 'time'} <= set(rule_conf['recent']):
raise ConfigError('Recent "count" and "time" values must be defined')
+ if dict_search_args(rule_conf, 'tcp', 'flags') and dict_search_args(rule_conf, 'protocol') != 'tcp':
+ raise ConfigError('Protocol must be tcp when specifying tcp flags')
+
for side in ['destination', 'source']:
if side in rule_conf:
side_conf = rule_conf[side]
diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py
index c5904309f..30597ef4e 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -76,7 +76,7 @@ def get_config(config=None):
return policy
-def verify_rule(policy, rule_conf, ipv6):
+def verify_rule(policy, name, rule_conf, ipv6):
icmp = 'icmp' if not ipv6 else 'icmpv6'
if icmp in rule_conf:
icmp_defined = False
@@ -93,14 +93,14 @@ def verify_rule(policy, rule_conf, ipv6):
if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp:
raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP')
+
if 'set' in rule_conf:
if 'tcp_mss' in rule_conf['set']:
tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
if not tcp_flags or 'SYN' not in tcp_flags.split(","):
raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS')
- if 'tcp' in rule_conf:
- if 'flags' in rule_conf['tcp']:
- if 'protocol' not in rule_conf or rule_conf['protocol'] != 'tcp':
+
+ if dict_search_args(rule_conf, 'tcp', 'flags') and dict_search_args(rule_conf, 'protocol') != 'tcp':
raise ConfigError(f'{name} rule {rule_id}: TCP flags can only be set if protocol is set to TCP')
for side in ['destination', 'source']:
@@ -138,7 +138,7 @@ def verify(policy):
for name, pol_conf in policy[route].items():
if 'rule' in pol_conf:
for rule_id, rule_conf in pol_conf['rule'].items():
- verify_rule(policy, rule_conf, ipv6)
+ verify_rule(policy, name, rule_conf, ipv6)
for ifname, if_policy in policy['interfaces'].items():
name = dict_search_args(if_policy, 'route')
diff --git a/src/validators/tcp-flag b/src/validators/tcp-flag
new file mode 100755
index 000000000..86ebec189
--- /dev/null
+++ b/src/validators/tcp-flag
@@ -0,0 +1,19 @@
+#!/usr/bin/python3
+
+import sys
+import re
+
+if __name__ == '__main__':
+ if len(sys.argv)>1:
+ flags = sys.argv[1].split(",")
+
+ for flag in flags:
+ if flag and flag[0] == '!':
+ flag = flag[1:]
+ if flag.lower() not in ['syn', 'ack', 'rst', 'fin', 'urg', 'psh']:
+ print(f'Error: {flag} is not a valid TCP flag')
+ sys.exit(1)
+ else:
+ sys.exit(2)
+
+ sys.exit(0)
--
cgit v1.2.3
From 64668771d5f14fc4b68fff382d166238c164bdde Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Sat, 15 Jan 2022 12:48:48 +0100
Subject: firewall: policy: T4178: Migrate and refactor tcp flags
* Add support for ECN and CWR flags
---
.../include/firewall/common-rule.xml.i | 51 +--------
.../include/firewall/tcp-flags.xml.i | 119 +++++++++++++++++++++
.../include/policy/route-common-rule-ipv6.xml.i | 51 +--------
.../include/policy/route-common-rule.xml.i | 51 +--------
python/vyos/firewall.py | 10 +-
smoketest/configs/dialup-router-medium-vpn | 9 ++
smoketest/scripts/cli/test_firewall.py | 16 +--
smoketest/scripts/cli/test_policy_route.py | 6 +-
src/conf_mode/firewall.py | 12 ++-
src/conf_mode/policy-route.py | 14 ++-
src/migration-scripts/firewall/6-to-7 | 21 ++++
src/migration-scripts/policy/1-to-2 | 19 ++++
src/validators/tcp-flag | 14 ++-
13 files changed, 213 insertions(+), 180 deletions(-)
create mode 100644 interface-definitions/include/firewall/tcp-flags.xml.i
(limited to 'python/vyos/firewall.py')
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index 6e8203c88..5ffbd639c 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -264,56 +264,7 @@
-
-
- TCP flags to match
-
-
-
-
- TCP flags to match
-
- txt
- Multiple comma-separated flags
-
-
- syn
- Syncronise flag
-
-
- ack
- Acknowledge flag
-
-
- fin
- Finish flag
-
-
- rst
- Reset flag
-
-
- urg
- Urgent flag
-
-
- psh
- Push flag
-
-
-
- \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset
-
-
- syn ack fin rst urg psh
-
-
-
-
-
-
-
-
+#include
Time to match rule
diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i
new file mode 100644
index 000000000..b99896687
--- /dev/null
+++ b/interface-definitions/include/firewall/tcp-flags.xml.i
@@ -0,0 +1,119 @@
+
+
+
+ TCP flags to match
+
+
+
+
+ TCP flags to match
+
+
+
+
+ Synchronise flag
+
+
+
+
+
+ Acknowledge flag
+
+
+
+
+
+ Finish flag
+
+
+
+
+
+ Reset flag
+
+
+
+
+
+ Urgent flag
+
+
+
+
+
+ Push flag
+
+
+
+
+
+ Explicit Congestion Notification flag
+
+
+
+
+
+ Congestion Window Reduced flag
+
+
+
+
+
+ Match flags not set
+
+
+
+
+ Synchronise flag
+
+
+
+
+
+ Acknowledge flag
+
+
+
+
+
+ Finish flag
+
+
+
+
+
+ Reset flag
+
+
+
+
+
+ Urgent flag
+
+
+
+
+
+ Push flag
+
+
+
+
+
+ Explicit Congestion Notification flag
+
+
+
+
+
+ Congestion Window Reduced flag
+
+
+
+
+
+
+
+
+
+
diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
index b8fee4b7b..735edbd48 100644
--- a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
+++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
@@ -320,56 +320,7 @@
-
-
- TCP flags to match
-
-
-
-
- TCP flags to match
-
- txt
- Multiple comma-separated flags
-
-
- syn
- Syncronise flag
-
-
- ack
- Acknowledge flag
-
-
- fin
- Finish flag
-
-
- rst
- Reset flag
-
-
- urg
- Urgent flag
-
-
- psh
- Push flag
-
-
-
- \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset
-
-
- syn ack fin rst urg psh
-
-
-
-
-
-
-
-
+#include
Time to match rule
diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i
index 17b47474d..4452f78fc 100644
--- a/interface-definitions/include/policy/route-common-rule.xml.i
+++ b/interface-definitions/include/policy/route-common-rule.xml.i
@@ -320,56 +320,7 @@
-
-
- TCP flags to match
-
-
-
-
- TCP flags to match
-
- txt
- Multiple comma-separated flags
-
-
- syn
- Syncronise flag
-
-
- ack
- Acknowledge flag
-
-
- fin
- Finish flag
-
-
- rst
- Reset flag
-
-
- urg
- Urgent flag
-
-
- psh
- Push flag
-
-
-
- \n When specifying more than one flag, flags should be comma-separated.\n For example: value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset
-
-
- syn ack fin rst urg psh
-
-
-
-
-
-
-
-
+#include
Time to match rule
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index acde9f913..ad84393df 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -185,14 +185,8 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
return " ".join(output)
def parse_tcp_flags(flags):
- all_flags = []
- include = []
- for flag in flags.split(","):
- if flag[0] == '!':
- flag = flag[1:].lower()
- else:
- include.append(flag.lower())
- all_flags.append(flag.lower())
+ include = [flag for flag in flags if flag != 'not']
+ all_flags = include + [flag for flag in flags['not']] if 'not' in flags else []
return f'tcp flags & ({"|".join(all_flags)}) == {"|".join(include)}'
def parse_time(time):
diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn
index 7ca540b66..63d955738 100644
--- a/smoketest/configs/dialup-router-medium-vpn
+++ b/smoketest/configs/dialup-router-medium-vpn
@@ -6,6 +6,15 @@ firewall {
ipv6-src-route disable
ip-src-route disable
log-martians enable
+ name test_tcp_flags {
+ rule 1 {
+ action drop
+ protocol tcp
+ tcp {
+ flags SYN,ACK,!RST,!FIN
+ }
+ }
+ }
options {
interface vtun0 {
adjust-mss 1380
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 2b3b354ba..c70743a9f 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -53,7 +53,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
@@ -61,7 +61,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['iifname "eth0"', 'jump smoketest'],
- ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'tcp dport { 53, 123 }', 'return'],
+ ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'th dport { 53, 123 }', 'return'],
]
nftables_output = cmd('sudo nft list table ip filter')
@@ -72,7 +72,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
if all(item in line for item in search):
matched = True
break
- self.assertTrue(matched)
+ self.assertTrue(matched, msg=search)
def test_basic_rules(self):
self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
@@ -80,8 +80,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'reject'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'protocol', 'tcp_udp'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'protocol', 'tcp'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'destination', 'port', '8888'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
@@ -90,7 +92,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['iifname "eth0"', 'jump smoketest'],
['saddr 172.16.20.10', 'daddr 172.16.10.10', 'return'],
- ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'reject'],
+ ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'reject'],
['smoketest default-action', 'drop']
]
@@ -102,7 +104,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
if all(item in line for item in search):
matched = True
break
- self.assertTrue(matched)
+ self.assertTrue(matched, msg=search)
def test_basic_rules_ipv6(self):
self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'default-action', 'drop'])
@@ -132,7 +134,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
if all(item in line for item in search):
matched = True
break
- self.assertTrue(matched)
+ self.assertTrue(matched, msg=search)
def test_state_policy(self):
self.cli_set(['firewall', 'state-policy', 'established', 'action', 'accept'])
diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
index 4463a2255..9035f0832 100755
--- a/smoketest/scripts/cli/test_policy_route.py
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -63,8 +63,10 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.assertTrue(matched)
def test_pbr_table(self):
- self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'port', '8888'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'syn'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'not', 'ack'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'table', table_id])
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'protocol', 'tcp_udp'])
self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888'])
@@ -81,7 +83,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['iifname "eth0"', 'jump VYOS_PBR_smoketest'],
- ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex]
+ ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'meta mark set ' + mark_hex]
]
nftables_output = cmd('sudo nft list table ip mangle')
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 853470fd8..906d477b0 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -142,8 +142,16 @@ def verify_rule(firewall, rule_conf, ipv6):
if not {'count', 'time'} <= set(rule_conf['recent']):
raise ConfigError('Recent "count" and "time" values must be defined')
- if dict_search_args(rule_conf, 'tcp', 'flags') and dict_search_args(rule_conf, 'protocol') != 'tcp':
- raise ConfigError('Protocol must be tcp when specifying tcp flags')
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if tcp_flags:
+ if dict_search_args(rule_conf, 'protocol') != 'tcp':
+ raise ConfigError('Protocol must be tcp when specifying tcp flags')
+
+ not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not')
+ if not_flags:
+ duplicates = [flag for flag in tcp_flags if flag in not_flags]
+ if duplicates:
+ raise ConfigError(f'Cannot match a tcp flag as set and not set')
for side in ['destination', 'source']:
if side in rule_conf:
diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py
index 30597ef4e..eb13788dd 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -97,11 +97,19 @@ def verify_rule(policy, name, rule_conf, ipv6):
if 'set' in rule_conf:
if 'tcp_mss' in rule_conf['set']:
tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
- if not tcp_flags or 'SYN' not in tcp_flags.split(","):
+ if not tcp_flags or 'syn' not in tcp_flags:
raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS')
- if dict_search_args(rule_conf, 'tcp', 'flags') and dict_search_args(rule_conf, 'protocol') != 'tcp':
- raise ConfigError(f'{name} rule {rule_id}: TCP flags can only be set if protocol is set to TCP')
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if tcp_flags:
+ if dict_search_args(rule_conf, 'protocol') != 'tcp':
+ raise ConfigError('Protocol must be tcp when specifying tcp flags')
+
+ not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not')
+ if not_flags:
+ duplicates = [flag for flag in tcp_flags if flag in not_flags]
+ if duplicates:
+ raise ConfigError(f'Cannot match a tcp flag as set and not set')
for side in ['destination', 'source']:
if side in rule_conf:
diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7
index 4a4097d56..bc0b19325 100755
--- a/src/migration-scripts/firewall/6-to-7
+++ b/src/migration-scripts/firewall/6-to-7
@@ -17,6 +17,7 @@
# T2199: Remove unavailable nodes due to XML/Python implementation using nftables
# monthdays: nftables does not have a monthdays equivalent
# utc: nftables userspace uses localtime and calculates the UTC offset automatically
+# T4178: Update tcp flags to use multi value node
from sys import argv
from sys import exit
@@ -45,6 +46,7 @@ if config.exists(base + ['name']):
if config.exists(base + ['name', name, 'rule']):
for rule in config.list_nodes(base + ['name', name, 'rule']):
rule_time = base + ['name', name, 'rule', rule, 'time']
+ rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags']
if config.exists(rule_time + ['monthdays']):
config.delete(rule_time + ['monthdays'])
@@ -52,11 +54,21 @@ if config.exists(base + ['name']):
if config.exists(rule_time + ['utc']):
config.delete(rule_time + ['utc'])
+ if config.exists(rule_tcp_flags):
+ tmp = config.return_value(rule_tcp_flags)
+ config.delete(rule_tcp_flags)
+ for flag in tmp.split(","):
+ if flag[0] == '!':
+ config.set(rule_tcp_flags + ['not', flag[1:].lower()])
+ else:
+ config.set(rule_tcp_flags + [flag.lower()])
+
if config.exists(base + ['ipv6-name']):
for name in config.list_nodes(base + ['ipv6-name']):
if config.exists(base + ['ipv6-name', name, 'rule']):
for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']):
rule_time = base + ['ipv6-name', name, 'rule', rule, 'time']
+ rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags']
if config.exists(rule_time + ['monthdays']):
config.delete(rule_time + ['monthdays'])
@@ -64,6 +76,15 @@ if config.exists(base + ['ipv6-name']):
if config.exists(rule_time + ['utc']):
config.delete(rule_time + ['utc'])
+ if config.exists(rule_tcp_flags):
+ tmp = config.return_value(rule_tcp_flags)
+ config.delete(rule_tcp_flags)
+ for flag in tmp.split(","):
+ if flag[0] == '!':
+ config.set(rule_tcp_flags + ['not', flag[1:].lower()])
+ else:
+ config.set(rule_tcp_flags + [flag.lower()])
+
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2
index 7ffceef22..eebbf9d41 100755
--- a/src/migration-scripts/policy/1-to-2
+++ b/src/migration-scripts/policy/1-to-2
@@ -16,6 +16,7 @@
# T4170: rename "policy ipv6-route" to "policy route6" to match common
# IPv4/IPv6 schema
+# T4178: Update tcp flags to use multi value node
from sys import argv
from sys import exit
@@ -41,6 +42,24 @@ if not config.exists(base):
config.rename(base, 'route6')
config.set_tag(['policy', 'route6'])
+for route in ['route', 'route6']:
+ route_path = ['policy', route]
+ if config.exists(route_path):
+ for name in config.list_nodes(route_path):
+ if config.exists(route_path + [name, 'rule']):
+ for rule in config.list_nodes(route_path + [name, 'rule']):
+ rule_tcp_flags = route_path + [name, 'rule', rule, 'tcp', 'flags']
+
+ if config.exists(rule_tcp_flags):
+ tmp = config.return_value(rule_tcp_flags)
+ config.delete(rule_tcp_flags)
+ for flag in tmp.split(","):
+ for flag in tmp.split(","):
+ if flag[0] == '!':
+ config.set(rule_tcp_flags + ['not', flag[1:].lower()])
+ else:
+ config.set(rule_tcp_flags + [flag.lower()])
+
if config.exists(['interfaces']):
def if_policy_rename(config, path):
if config.exists(path + ['policy', 'ipv6-route']):
diff --git a/src/validators/tcp-flag b/src/validators/tcp-flag
index 86ebec189..1496b904a 100755
--- a/src/validators/tcp-flag
+++ b/src/validators/tcp-flag
@@ -5,14 +5,12 @@ import re
if __name__ == '__main__':
if len(sys.argv)>1:
- flags = sys.argv[1].split(",")
-
- for flag in flags:
- if flag and flag[0] == '!':
- flag = flag[1:]
- if flag.lower() not in ['syn', 'ack', 'rst', 'fin', 'urg', 'psh']:
- print(f'Error: {flag} is not a valid TCP flag')
- sys.exit(1)
+ flag = sys.argv[1]
+ if flag and flag[0] == '!':
+ flag = flag[1:]
+ if flag not in ['syn', 'ack', 'rst', 'fin', 'urg', 'psh', 'ecn', 'cwr']:
+ print(f'Error: {flag} is not a valid TCP flag')
+ sys.exit(1)
else:
sys.exit(2)
--
cgit v1.2.3
From 0a5a78621b2b28f06af1f40c10ee8bb880f860a0 Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Tue, 18 Jan 2022 15:29:03 +0100
Subject: firewall: T3560: Add support for MAC address groups
---
data/templates/firewall/nftables-defines.tmpl | 5 ++++
interface-definitions/firewall.xml.in | 21 +++++++++++++++++
.../include/firewall/common-rule.xml.i | 3 +++
.../include/firewall/mac-group.xml.i | 10 ++++++++
.../firewall/source-destination-group-ipv6.xml.i | 1 +
.../firewall/source-destination-group.xml.i | 1 +
.../include/policy/route-common-rule-ipv6.xml.i | 3 +++
.../include/policy/route-common-rule.xml.i | 3 +++
python/vyos/firewall.py | 3 +++
smoketest/scripts/cli/test_firewall.py | 4 ++++
src/op_mode/firewall.py | 2 ++
src/validators/mac-address-firewall | 27 ++++++++++++++++++++++
12 files changed, 83 insertions(+)
create mode 100644 interface-definitions/include/firewall/mac-group.xml.i
create mode 100755 src/validators/mac-address-firewall
(limited to 'python/vyos/firewall.py')
diff --git a/data/templates/firewall/nftables-defines.tmpl b/data/templates/firewall/nftables-defines.tmpl
index 3578a9dc5..d9eb7c199 100644
--- a/data/templates/firewall/nftables-defines.tmpl
+++ b/data/templates/firewall/nftables-defines.tmpl
@@ -9,6 +9,11 @@ define A_{{ group_name }} = { {{ group_conf.address | join(",") }} }
define A6_{{ group_name }} = { {{ group_conf.address | join(",") }} }
{% endfor %}
{% endif %}
+{% if group.mac_group is defined %}
+{% for group_name, group_conf in group.mac_group.items() %}
+define M_{{ group_name }} = { {{ group_conf.mac_address | join(",") }} }
+{% endfor %}
+{% endif %}
{% if group.network_group is defined %}
{% for group_name, group_conf in group.network_group.items() %}
define N_{{ group_name }} = { {{ group_conf.network | join(",") }} }
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index fd98ae138..987ccaca6 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -144,6 +144,27 @@
+
+
+ Firewall mac-group
+
+
+ #include
+
+
+ Mac-group member
+
+ <MAC address>
+ MAC address to match
+
+
+
+
+
+
+
+
+
Firewall network-group
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index 5ffbd639c..521fe54f2 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -176,6 +176,9 @@
!<MAC address>
Match everything except the specified MAC address
+
+
+
#include
diff --git a/interface-definitions/include/firewall/mac-group.xml.i b/interface-definitions/include/firewall/mac-group.xml.i
new file mode 100644
index 000000000..dbce3fc88
--- /dev/null
+++ b/interface-definitions/include/firewall/mac-group.xml.i
@@ -0,0 +1,10 @@
+
+
+
+ Group of MAC addresses
+
+ firewall group mac-group
+
+
+
+
\ No newline at end of file
diff --git a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
index 7815b78d4..c2cc7edb3 100644
--- a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
+++ b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
@@ -12,6 +12,7 @@
+ #include
Group of networks
diff --git a/interface-definitions/include/firewall/source-destination-group.xml.i b/interface-definitions/include/firewall/source-destination-group.xml.i
index 9a9bed0fe..ab11e89e9 100644
--- a/interface-definitions/include/firewall/source-destination-group.xml.i
+++ b/interface-definitions/include/firewall/source-destination-group.xml.i
@@ -12,6 +12,7 @@
+ #include
Group of networks
diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
index 735edbd48..406125e55 100644
--- a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
+++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
@@ -232,6 +232,9 @@
!<MAC address>
Match everything except the specified MAC address
+
+
+
#include
diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i
index 4452f78fc..33c4ba77c 100644
--- a/interface-definitions/include/policy/route-common-rule.xml.i
+++ b/interface-definitions/include/policy/route-common-rule.xml.i
@@ -232,6 +232,9 @@
!<MAC address>
Match everything except the specified MAC address
+
+
+
#include
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index ad84393df..2ab78ff18 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -108,6 +108,9 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
elif 'network_group' in group:
group_name = group['network_group']
output.append(f'{ip_name} {prefix}addr $N{def_suffix}_{group_name}')
+ if 'mac_group' in group:
+ group_name = group['mac_group']
+ output.append(f'ether {prefix}addr $M_{group_name}')
if 'port_group' in group:
proto = rule_conf['protocol']
group_name = group['port_group']
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index c70743a9f..6b74e6c92 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -46,6 +46,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
def test_groups(self):
+ self.cli_set(['firewall', 'group', 'mac-group', 'smoketest_mac', 'mac-address', '00:01:02:03:04:05'])
self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24'])
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53'])
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123'])
@@ -54,6 +55,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac'])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
@@ -62,6 +65,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['iifname "eth0"', 'jump smoketest'],
['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'th dport { 53, 123 }', 'return'],
+ ['ether saddr { 00:01:02:03:04:05 }', 'return']
]
nftables_output = cmd('sudo nft list table ip filter')
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index 030a9b19a..b6bb5b802 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -272,6 +272,8 @@ def show_firewall_group(name=None):
row.append("\n".join(sorted(group_conf['address'], key=ipaddress.ip_address)))
elif 'network' in group_conf:
row.append("\n".join(sorted(group_conf['network'], key=ipaddress.ip_network)))
+ elif 'mac_address' in group_conf:
+ row.append("\n".join(sorted(group_conf['mac_address'])))
elif 'port' in group_conf:
row.append("\n".join(sorted(group_conf['port'])))
else:
diff --git a/src/validators/mac-address-firewall b/src/validators/mac-address-firewall
new file mode 100755
index 000000000..70551f86d
--- /dev/null
+++ b/src/validators/mac-address-firewall
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import re
+import sys
+
+pattern = "^!?([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$"
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ sys.exit(1)
+ if not re.match(pattern, sys.argv[1]):
+ sys.exit(1)
+ sys.exit(0)
--
cgit v1.2.3
From d1d0150b6a40252700181530ca87c5699a4bd0b4 Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Thu, 20 Jan 2022 00:27:53 +0100
Subject: firewall: T2199: Add log prefix to match legacy perl behaviour
Example syslog: [FWNAME-default-D] ...
* Also clean-up firewall default-action
---
data/templates/firewall/nftables-policy.tmpl | 10 ++--------
data/templates/firewall/nftables.tmpl | 14 ++------------
python/vyos/firewall.py | 3 ++-
python/vyos/template.py | 13 +++++++++++++
4 files changed, 19 insertions(+), 21 deletions(-)
(limited to 'python/vyos/firewall.py')
diff --git a/data/templates/firewall/nftables-policy.tmpl b/data/templates/firewall/nftables-policy.tmpl
index 484b6f203..905ffcd09 100644
--- a/data/templates/firewall/nftables-policy.tmpl
+++ b/data/templates/firewall/nftables-policy.tmpl
@@ -25,11 +25,7 @@ table ip mangle {
{{ rule_conf | nft_rule(route_text, rule_id, 'ip') }}
{% endfor %}
{% endif %}
-{% if conf.default_action is defined %}
- counter {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}"
-{% else %}
- counter return
-{% endif %}
+ {{ conf | nft_default_rule(route_text) }}
}
{% endfor %}
{%- endif %}
@@ -52,9 +48,7 @@ table ip6 mangle {
{{ rule_conf | nft_rule(route_text, rule_id, 'ip6') }}
{% endfor %}
{% endif %}
-{% if conf.default_action is defined %}
- counter {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}"
-{% endif %}
+ {{ conf | nft_default_rule(route_text) }}
}
{% endfor %}
{% endif %}
diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl
index 81b2c0b98..33c821e84 100644
--- a/data/templates/firewall/nftables.tmpl
+++ b/data/templates/firewall/nftables.tmpl
@@ -32,18 +32,13 @@ table ip filter {
{% endif %}
{% if name is defined %}
{% for name_text, conf in name.items() %}
-{% set default_log = 'log' if 'enable_default_log' in conf else '' %}
chain {{ name_text }} {
{% if conf.rule is defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}
{{ rule_conf | nft_rule(name_text, rule_id) }}
{% endfor %}
{% endif %}
-{% if conf.default_action is defined %}
- counter {{ default_log }} {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}"
-{% else %}
- return
-{% endif %}
+ {{ conf | nft_default_rule(name_text) }}
}
{% endfor %}
{% endif %}
@@ -87,18 +82,13 @@ table ip6 filter {
{% endif %}
{% if ipv6_name is defined %}
{% for name_text, conf in ipv6_name.items() %}
-{% set default_log = 'log' if 'enable_default_log' in conf else '' %}
chain {{ name_text }} {
{% if conf.rule is defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}
{{ rule_conf | nft_rule(name_text, rule_id, 'ip6') }}
{% endfor %}
{% endif %}
-{% if conf.default_action is defined %}
- counter {{ default_log }} {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}"
-{% else %}
- return
-{% endif %}
+ {{ conf | nft_default_rule(name_text) }}
}
{% endfor %}
{% endif %}
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 2ab78ff18..808e90e38 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -121,7 +121,8 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
output.append(f'{proto} {prefix}port $P_{group_name}')
if 'log' in rule_conf and rule_conf['log'] == 'enable':
- output.append('log')
+ action = rule_conf['action'] if 'action' in rule_conf else 'accept'
+ output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}] "')
if 'hop_limit' in rule_conf:
operators = {'eq': '==', 'gt': '>', 'lt': '<'}
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 6f65c6c98..633b28ade 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -516,6 +516,19 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'):
from vyos.firewall import parse_rule
return parse_rule(rule_conf, fw_name, rule_id, ip_name)
+@register_filter('nft_default_rule')
+def nft_default_rule(fw_conf, fw_name):
+ output = ['counter']
+ default_action = fw_conf.get('default_action', 'accept')
+
+ if 'enable_default_log' in fw_conf:
+ action_suffix = default_action[:1].upper()
+ output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}] "')
+
+ output.append(nft_action(default_action))
+ output.append(f'comment "{fw_name} default-action {default_action}"')
+ return " ".join(output)
+
@register_filter('nft_state_policy')
def nft_state_policy(conf, state, ipv6=False):
out = [f'ct state {state}']
--
cgit v1.2.3
From dcabea5919e299cdee9db7469b451356743cc7ff Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Thu, 27 Jan 2022 13:12:39 +0100
Subject: firewall: T4178: Fix tcp flags output when `not` isn't used
---
python/vyos/firewall.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'python/vyos/firewall.py')
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 808e90e38..4993d855e 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -190,8 +190,8 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
def parse_tcp_flags(flags):
include = [flag for flag in flags if flag != 'not']
- all_flags = include + [flag for flag in flags['not']] if 'not' in flags else []
- return f'tcp flags & ({"|".join(all_flags)}) == {"|".join(include)}'
+ exclude = flags['not'].keys() if 'not' in flags else []
+ return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include)}'
def parse_time(time):
out = []
--
cgit v1.2.3
From 1c828cc5a1dcbad6c8d94142de64ba9a529c14a7 Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Sat, 29 Jan 2022 01:46:50 +0100
Subject: firewall: T4178: Fix dict_keys issue with tcp flags
---
python/vyos/firewall.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'python/vyos/firewall.py')
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 4993d855e..a2e133217 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -190,7 +190,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
def parse_tcp_flags(flags):
include = [flag for flag in flags if flag != 'not']
- exclude = flags['not'].keys() if 'not' in flags else []
+ exclude = list(flags['not']) if 'not' in flags else []
return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include)}'
def parse_time(time):
--
cgit v1.2.3
From 985a9e8536cb7f049e82dd1c7333ecced34563fa Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Sat, 29 Jan 2022 23:34:05 +0100
Subject: firewall: T4216: Add support for negated firewall groups
---
python/vyos/firewall.py | 25 +++++++++++++++++++++----
src/conf_mode/firewall.py | 4 ++++
2 files changed, 25 insertions(+), 4 deletions(-)
(limited to 'python/vyos/firewall.py')
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index a2e133217..a74fd922a 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -104,13 +104,25 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
group = side_conf['group']
if 'address_group' in group:
group_name = group['address_group']
- output.append(f'{ip_name} {prefix}addr $A{def_suffix}_{group_name}')
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_name} {prefix}addr {operator} $A{def_suffix}_{group_name}')
elif 'network_group' in group:
group_name = group['network_group']
- output.append(f'{ip_name} {prefix}addr $N{def_suffix}_{group_name}')
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_name} {prefix}addr {operator} $N{def_suffix}_{group_name}')
if 'mac_group' in group:
group_name = group['mac_group']
- output.append(f'ether {prefix}addr $M_{group_name}')
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'ether {prefix}addr {operator} $M_{group_name}')
if 'port_group' in group:
proto = rule_conf['protocol']
group_name = group['port_group']
@@ -118,7 +130,12 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if proto == 'tcp_udp':
proto = 'th'
- output.append(f'{proto} {prefix}port $P_{group_name}')
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+
+ output.append(f'{proto} {prefix}port {operator} $P_{group_name}')
if 'log' in rule_conf and rule_conf['log'] == 'enable':
action = rule_conf['action'] if 'action' in rule_conf else 'accept'
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 5b6c57d04..064b2d5a3 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -204,6 +204,10 @@ def verify_rule(firewall, rule_conf, ipv6):
for group in valid_groups:
if group in side_conf['group']:
group_name = side_conf['group'][group]
+
+ if group_name and group_name[0] == '!':
+ group_name = group_name[1:]
+
fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
error_group = fw_group.replace("_", "-")
group_obj = dict_search_args(firewall, 'group', fw_group, group_name)
--
cgit v1.2.3
From 9f7f1ebb15a2dce507693830517bc1c0c2b6815e Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Thu, 3 Feb 2022 00:30:52 +0100
Subject: firewall: T4178: Fix only inverse matching on tcp flags
---
python/vyos/firewall.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'python/vyos/firewall.py')
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index a74fd922a..c1217b420 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -208,7 +208,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
def parse_tcp_flags(flags):
include = [flag for flag in flags if flag != 'not']
exclude = list(flags['not']) if 'not' in flags else []
- return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include)}'
+ return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include) if include else "0x0"}'
def parse_time(time):
out = []
--
cgit v1.2.3
From 22f0794a9f195e69e277d48f031fe934febe9408 Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:58:36 +0100
Subject: firewall: T4209: Fix support for rule `recent` matches
---
data/templates/firewall/nftables.tmpl | 22 ++++++++++++++++++++++
.../include/firewall/common-rule.xml.i | 19 +++++++++++++++----
python/vyos/firewall.py | 4 +---
src/conf_mode/firewall.py | 6 +++++-
src/migration-scripts/firewall/6-to-7 | 20 ++++++++++++++++++++
5 files changed, 63 insertions(+), 8 deletions(-)
(limited to 'python/vyos/firewall.py')
diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl
index 468a5a32f..0cc977cf9 100644
--- a/data/templates/firewall/nftables.tmpl
+++ b/data/templates/firewall/nftables.tmpl
@@ -31,16 +31,27 @@ table ip filter {
}
{% endif %}
{% if name is defined %}
+{% set ns = namespace(sets=[]) %}
{% for name_text, conf in name.items() %}
chain NAME_{{ name_text }} {
{% if conf.rule is defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}
{{ rule_conf | nft_rule(name_text, rule_id) }}
+{% if rule_conf.recent is defined %}
+{% set ns.sets = ns.sets + [name_text + '_' + rule_id] %}
+{% endif %}
{% endfor %}
{% endif %}
{{ conf | nft_default_rule(name_text) }}
}
{% endfor %}
+{% for set_name in ns.sets %}
+ set RECENT_{{ set_name }} {
+ type ipv4_addr
+ size 65535
+ flags dynamic
+ }
+{% endfor %}
{% endif %}
{% if state_policy is defined %}
chain VYOS_STATE_POLICY {
@@ -81,16 +92,27 @@ table ip6 filter {
}
{% endif %}
{% if ipv6_name is defined %}
+{% set ns = namespace(sets=[]) %}
{% for name_text, conf in ipv6_name.items() %}
chain NAME6_{{ name_text }} {
{% if conf.rule is defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}
{{ rule_conf | nft_rule(name_text, rule_id, 'ip6') }}
+{% if rule_conf.recent is defined %}
+{% set ns.sets = ns.sets + [name_text + '_' + rule_id] %}
+{% endif %}
{% endfor %}
{% endif %}
{{ conf | nft_default_rule(name_text) }}
}
{% endfor %}
+{% for set_name in ns.sets %}
+ set RECENT6_{{ set_name }} {
+ type ipv6_addr
+ size 65535
+ flags dynamic
+ }
+{% endfor %}
{% endif %}
{% if state_policy is defined %}
chain VYOS_STATE_POLICY6 {
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index 521fe54f2..353804990 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -146,13 +146,24 @@
- Source addresses seen in the last N seconds
+ Source addresses seen in the last second/minute/hour
+
+ second minute hour
+
- u32:0-4294967295
- Source addresses seen in the last N seconds
+ second
+ Source addresses seen COUNT times in the last second
+
+
+ minute
+ Source addresses seen COUNT times in the last minute
+
+
+ hour
+ Source addresses seen COUNT times in the last hour
-
+ ^(second|minute|hour)$
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index c1217b420..55ce318e7 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -181,9 +181,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'recent' in rule_conf:
count = rule_conf['recent']['count']
time = rule_conf['recent']['time']
- # output.append(f'meter {fw_name}_{rule_id} {{ ip saddr and 255.255.255.255 limit rate over {count}/{time} burst {count} packets }}')
- # Waiting on input from nftables developers due to
- # bug with above line and atomic chain flushing.
+ output.append(f'add @RECENT{def_suffix}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}')
if 'time' in rule_conf:
output.append(parse_time(rule_conf['time']))
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 9dec2143e..41df1b84a 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -278,6 +278,7 @@ def cleanup_rule(table, jump_chain):
def cleanup_commands(firewall):
commands = []
+ commands_end = []
for table in ['ip filter', 'ip6 filter']:
state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6'
json_str = cmd(f'nft -j list table {table}')
@@ -308,7 +309,10 @@ def cleanup_commands(firewall):
chain = rule['chain']
handle = rule['handle']
commands.append(f'delete rule {table} {chain} handle {handle}')
- return commands
+ elif 'set' in item:
+ set_name = item['set']['name']
+ commands_end.append(f'delete set {table} {set_name}')
+ return commands + commands_end
def generate(firewall):
if not os.path.exists(nftables_conf):
diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7
index efc901530..5f4cff90d 100755
--- a/src/migration-scripts/firewall/6-to-7
+++ b/src/migration-scripts/firewall/6-to-7
@@ -104,6 +104,7 @@ if config.exists(base + ['name']):
continue
for rule in config.list_nodes(base + ['name', name, 'rule']):
+ rule_recent = base + ['name', name, 'rule', rule, 'recent']
rule_time = base + ['name', name, 'rule', rule, 'time']
rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags']
rule_icmp = base + ['name', name, 'rule', rule, 'icmp']
@@ -114,6 +115,15 @@ if config.exists(base + ['name']):
if config.exists(rule_time + ['utc']):
config.delete(rule_time + ['utc'])
+ if config.exists(rule_recent + ['time']):
+ tmp = int(config.return_value(rule_recent + ['time']))
+ unit = 'minute'
+ if tmp > 600:
+ unit = 'hour'
+ elif tmp < 10:
+ unit = 'second'
+ config.set(rule_recent + ['time'], value=unit)
+
if config.exists(rule_tcp_flags):
tmp = config.return_value(rule_tcp_flags)
config.delete(rule_tcp_flags)
@@ -148,6 +158,7 @@ if config.exists(base + ['ipv6-name']):
continue
for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']):
+ rule_recent = base + ['ipv6-name', name, 'rule', rule, 'recent']
rule_time = base + ['ipv6-name', name, 'rule', rule, 'time']
rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags']
rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6']
@@ -158,6 +169,15 @@ if config.exists(base + ['ipv6-name']):
if config.exists(rule_time + ['utc']):
config.delete(rule_time + ['utc'])
+ if config.exists(rule_recent + ['time']):
+ tmp = int(config.return_value(rule_recent + ['time']))
+ unit = 'minute'
+ if tmp > 600:
+ unit = 'hour'
+ elif tmp < 10:
+ unit = 'second'
+ config.set(rule_recent + ['time'], value=unit)
+
if config.exists(rule_tcp_flags):
tmp = config.return_value(rule_tcp_flags)
config.delete(rule_tcp_flags)
--
cgit v1.2.3
From c514cea0ad94a00838530cd07f87723be372ea8f Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Tue, 5 Apr 2022 20:40:45 +0200
Subject: firewall: T4345: Fix incorrect rule limit rate syntax
---
interface-definitions/include/firewall/common-rule.xml.i | 6 +++---
python/vyos/firewall.py | 2 +-
smoketest/configs/dialup-router-complex | 3 +++
smoketest/scripts/cli/test_firewall.py | 5 +++++
src/conf_mode/firewall.py | 6 ++++++
5 files changed, 18 insertions(+), 4 deletions(-)
(limited to 'python/vyos/firewall.py')
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index 353804990..cd80b7e28 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -66,11 +66,11 @@
Maximum average matching rate
- u32:0-4294967295
- Maximum average matching rate
+ txt
+ integer/unit (Example: 5/minute)
-
+ ^\d+/(second|minute|hour|day)$
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 55ce318e7..ff8623592 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -174,7 +174,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'limit' in rule_conf:
if 'rate' in rule_conf['limit']:
- output.append(f'limit rate {rule_conf["limit"]["rate"]}/second')
+ output.append(f'limit rate {rule_conf["limit"]["rate"]}')
if 'burst' in rule_conf['limit']:
output.append(f'burst {rule_conf["limit"]["burst"]} packets')
diff --git a/smoketest/configs/dialup-router-complex b/smoketest/configs/dialup-router-complex
index 1b62deb5c..ac5ff5e99 100644
--- a/smoketest/configs/dialup-router-complex
+++ b/smoketest/configs/dialup-router-complex
@@ -498,6 +498,9 @@ firewall {
destination {
port 110,995
}
+ limit {
+ rate "10/minute"
+ }
protocol tcp
}
rule 123 {
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index ecc0c29a0..16b020e07 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -88,6 +88,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'destination', 'port', '8888'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'destination', 'port', '22'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'limit', 'rate', '5/minute'])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
@@ -97,6 +101,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['iifname "eth0"', 'jump NAME_smoketest'],
['saddr 172.16.20.10', 'daddr 172.16.10.10', 'return'],
['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'reject'],
+ ['tcp dport { 22 }', 'limit rate 5/minute', 'return'],
['smoketest default-action', 'drop']
]
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 41df1b84a..f33198a49 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -171,6 +171,12 @@ def verify_rule(firewall, rule_conf, ipv6):
if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']):
raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"')
+ if 'limit' in rule_conf:
+ if 'rate' in rule_conf['limit']:
+ rate_int = re.sub(r'\D', '', rule_conf['limit']['rate'])
+ if int(rate_int) < 1:
+ raise ConfigError('Limit rate integer cannot be less than 1')
+
if 'ipsec' in rule_conf:
if {'match_ipsec', 'match_non_ipsec'} <= set(rule_conf['ipsec']):
raise ConfigError('Cannot specify both "match-ipsec" and "match-non-ipsec"')
--
cgit v1.2.3