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/policy/route-common-rule.xml.i | 34 ++++++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
(limited to 'interface-definitions/include/policy/route-common-rule.xml.i')
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
+
+
+
+
--
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 'interface-definitions/include/policy/route-common-rule.xml.i')
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 'interface-definitions/include/policy/route-common-rule.xml.i')
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