summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOleksandr Kuchmystyi <o.kuchmystyi@vyos.io>2026-04-02 15:31:32 +0300
committerOleksandr Kuchmystyi <o.kuchmystyi@vyos.io>2026-04-06 15:18:12 +0300
commit227699e62146c71c3a0d3923650edd247b20ef16 (patch)
tree07b395551d6132d27ff395803b61886a2e2a56f3
parent0792a6b2b7ab32006ff45832a35de4536547cbd3 (diff)
downloadvyos-1x-227699e62146c71c3a0d3923650edd247b20ef16.tar.gz
vyos-1x-227699e62146c71c3a0d3923650edd247b20ef16.zip
firewall: T8281: Normalize network prefixes in rule configuration
Convert non-canonical IPv4 and IPv6 prefixes in 'source/destination address' fields to proper network addresses (e.g. 10.10.10.1/30 -> 10.10.10.0/30). This prevents invalid configurations and ensures consistent prefix handling.
-rw-r--r--smoketest/configs/assert/firewall-rules-address26
-rw-r--r--smoketest/configs/firewall-rules-address88
-rw-r--r--src/migration-scripts/firewall/10-to-1169
3 files changed, 183 insertions, 0 deletions
diff --git a/smoketest/configs/assert/firewall-rules-address b/smoketest/configs/assert/firewall-rules-address
new file mode 100644
index 000000000..59627040c
--- /dev/null
+++ b/smoketest/configs/assert/firewall-rules-address
@@ -0,0 +1,26 @@
+set firewall ipv4 name TEST1 default-action 'drop'
+set firewall ipv4 name TEST1 rule 10 action 'return'
+set firewall ipv4 name TEST1 rule 10 destination address '10.10.11.2'
+set firewall ipv4 name TEST1 rule 10 source address '10.10.10.0/30'
+set firewall ipv4 name TEST1 rule 20 action 'return'
+set firewall ipv4 name TEST1 rule 20 destination address '10.10.13.1-10.10.13.16'
+set firewall ipv4 name TEST1 rule 20 source address '10.10.12.0/30'
+set firewall ipv4 name TEST1 rule 30 action 'return'
+set firewall ipv4 name TEST1 rule 30 destination address '!10.10.15.0/30'
+set firewall ipv4 name TEST1 rule 30 source address '10.10.14.1'
+set firewall ipv6 name TEST2 default-action 'drop'
+set firewall ipv6 name TEST2 rule 10 action 'return'
+set firewall ipv6 name TEST2 rule 10 destination address '2001:db8:10:11::2'
+set firewall ipv6 name TEST2 rule 10 source address '2001:db8:10:10::/126'
+set firewall ipv6 name TEST2 rule 20 action 'return'
+set firewall ipv6 name TEST2 rule 20 destination address '2001:db8:10:13::1-2001:db8:10:13::16'
+set firewall ipv6 name TEST2 rule 20 source address '2001:db8:10:12::/126'
+set firewall ipv6 name TEST2 rule 30 action 'return'
+set firewall ipv6 name TEST2 rule 30 destination address '!2001:db8:10:15::/126'
+set firewall ipv6 name TEST2 rule 30 source address '2001:db8:10:14::1'
+set interfaces ethernet eth0
+set interfaces loopback lo
+set system console device ttyS0 speed '115200'
+set system host-name 'vyos'
+set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
+set system login user vyos authentication plaintext-password '' \ No newline at end of file
diff --git a/smoketest/configs/firewall-rules-address b/smoketest/configs/firewall-rules-address
new file mode 100644
index 000000000..44dd81342
--- /dev/null
+++ b/smoketest/configs/firewall-rules-address
@@ -0,0 +1,88 @@
+firewall {
+ name TEST1 {
+ default-action drop
+ rule 10 {
+ action accept
+ destination {
+ address 10.10.11.2
+ }
+ source {
+ address 10.10.10.1/30
+ }
+ }
+ rule 20 {
+ action accept
+ destination {
+ address 10.10.13.1-10.10.13.16
+ }
+ source {
+ address 10.10.12.0/30
+ }
+ }
+ rule 30 {
+ action accept
+ destination {
+ address !10.10.15.1/30
+ }
+ source {
+ address 10.10.14.1
+ }
+ }
+ }
+ ipv6-name TEST2 {
+ default-action drop
+ rule 10 {
+ action accept
+ destination {
+ address 2001:db8:10:11::2
+ }
+ source {
+ address 2001:db8:10:10::1/126
+ }
+ }
+ rule 20 {
+ action accept
+ destination {
+ address 2001:db8:10:13::1-2001:db8:10:13::16
+ }
+ source {
+ address 2001:db8:10:12::/126
+ }
+ }
+ rule 30 {
+ action accept
+ destination {
+ address !2001:db8:10:15::1/126
+ }
+ source {
+ address 2001:db8:10:14::1
+ }
+ }
+ }
+}
+interfaces {
+ ethernet eth0 {
+ }
+ loopback lo {
+ }
+}
+system {
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ host-name "vyos"
+ login {
+ user vyos {
+ authentication {
+ encrypted-password "$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0"
+ plaintext-password ""
+ }
+ }
+ }
+}
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@23:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1"
+// Release version: 1.3.8 \ No newline at end of file
diff --git a/src/migration-scripts/firewall/10-to-11 b/src/migration-scripts/firewall/10-to-11
index 50d3eb89b..f608c200d 100644
--- a/src/migration-scripts/firewall/10-to-11
+++ b/src/migration-scripts/firewall/10-to-11
@@ -36,10 +36,57 @@
# set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> action jump
# set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> jump-target <name>
+# T8281: Normalize firewall rule network prefixes for source and destination address
+#
+# Fixes invalid/non-canonical prefixes like:
+# 10.10.10.1/30 -> 10.10.10.0/30
+# 2001:db8::1/64 -> 2001:db8::/64
+#
+# Only "source address" and "destination address" are rewritten.
+
+from ipaddress import ip_network
from vyos.configtree import ConfigTree
base = ['firewall']
+
+def normalize_address(config: ConfigTree, path: list[str]):
+ if not config.exists(path):
+ return
+
+ def normalize(value: str) -> str | None:
+ if '/' not in value:
+ return None
+
+ is_except = value.startswith('!')
+ if is_except:
+ value = value.lstrip('!')
+
+ try:
+ new_value = str(ip_network(value, strict=False))
+ except ValueError:
+ return None
+
+ return f'!{new_value}' if is_except else new_value
+
+ value = config.return_value(path)
+ if value:
+ normalized = normalize(value)
+ if normalized and normalized != value:
+ config.set(path, value=normalized)
+
+
+def migrate_ruleset(config: ConfigTree, ruleset_base: list[str]):
+ if not config.exists(ruleset_base + ['rule']):
+ return
+
+ for rule_id in config.list_nodes(ruleset_base + ['rule']):
+ rule_base = ruleset_base + ['rule', rule_id]
+
+ for direction in ['source', 'destination']:
+ normalize_address(config, rule_base + [direction, 'address'])
+
+
def migrate(config: ConfigTree) -> None:
if not config.exists(base):
# Nothing to do
@@ -185,3 +232,25 @@ def migrate(config: ConfigTree) -> None:
inp_ipv6_rule = inp_ipv6_rule + 5
config.delete(base + ['interface'])
+
+ ### Normalize/fix firewall rule network prefixes for source and destination address (T8281)
+ for family in ['ipv4', 'ipv6', 'bridge']:
+ # 1) Named rulesets:
+ # set firewall ipv4|ipv6|bridge name <ruleset> rule <id> ...
+ ruleset_base = base + [family, 'name']
+ if config.exists(ruleset_base):
+ for ruleset in config.list_nodes(ruleset_base):
+ migrate_ruleset(config, ruleset_base + [ruleset])
+
+ # 2) Hook-based rulesets:
+ # set firewall ipv4|ipv6|bridge input|output|forward filter|raw rule <id> ...
+ # set firewall ipv4|ipv6 prerouting raw rule <id> ...
+ hook_rulesets = {
+ 'input': ['filter', 'raw'],
+ 'output': ['filter', 'raw'],
+ 'forward': ['filter', 'raw'],
+ 'prerouting': ['raw'],
+ }
+ for hook, rulesets in hook_rulesets.items():
+ for ruleset in rulesets:
+ migrate_ruleset(config, base + [family, hook, ruleset])