summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@vyos.io>2026-05-21 14:35:56 +0100
committerGitHub <noreply@github.com>2026-05-21 14:35:56 +0100
commit097573dda926df118532f7479e1244b7d7a83e08 (patch)
treeec8ade85127be6baa3372fc6fcd1b3c55295b69a
parenta367f7adc56ac01cd6ef7db93564d53b48bb2610 (diff)
parentfea16e41f9a3f4aef4251786b7d157e781368cb9 (diff)
downloadvyos-1x-097573dda926df118532f7479e1244b7d7a83e08.tar.gz
vyos-1x-097573dda926df118532f7479e1244b7d7a83e08.zip
Merge pull request #5175 from natali-rs1985/T8509
wireguard: T8509: Add fwmark ip rules for VRF-bound interfaces
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireguard.py57
-rwxr-xr-xsrc/conf_mode/interfaces_wireguard.py34
-rwxr-xr-xsrc/conf_mode/vrf.py6
3 files changed, 97 insertions, 0 deletions
diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py
index a2d6f043c..cf98deda1 100755
--- a/smoketest/scripts/cli/test_interfaces_wireguard.py
+++ b/smoketest/scripts/cli/test_interfaces_wireguard.py
@@ -253,5 +253,62 @@ class WireGuardInterfaceTest(BasicInterfaceTest.TestCase):
# Ensure the service is no longer running after WireGuard interface is deleted
self.assertFalse(is_systemd_service_running(domain_resolver))
+ def test_wireguard_vrf_fwmark(self):
+ # T8509 Check fwmark ip rule created for WireGuard interface with VRF
+ interface = 'wg0'
+ port = '12345'
+ privkey = '6ISOkASm6VhHOOSz/5iIxw+Q9adq9zA17iMM4X40dlc='
+ pubkey = 'n1CUsmR0M2LUUsyicBd6blZICwUqqWWHbu4ifZ2/9gk='
+ mark = '101'
+ vrf_table = '200'
+ vrf = 'testvrf'
+
+ base_interface_path = base_path + [interface]
+ self.cli_set(base_interface_path + ['address', '172.16.0.1/24'])
+ self.cli_set(base_interface_path + ['private-key', privkey])
+ self.cli_set(base_interface_path + ['port', port])
+
+ peer_base_path = base_interface_path + ['peer', 'VyOS']
+ self.cli_set(peer_base_path + ['port', port])
+ self.cli_set(peer_base_path + ['public-key', pubkey])
+ self.cli_set(peer_base_path + ['allowed-ips', '169.254.0.0/16'])
+ self.cli_set(peer_base_path + ['address', '192.0.2.1'])
+
+ self.cli_set(base_interface_path + ['fwmark', mark])
+ self.cli_set(base_interface_path + ['vrf', vrf])
+ self.cli_set(['vrf', 'name', vrf, 'table', vrf_table])
+
+ self.cli_commit()
+
+ hex_fwmark = hex(int(mark))
+
+ # Verify ip rule at priority 1998 routes fwmark-tagged packets into the VRF
+ tmp = cmd(f'ip rule show priority 1998')
+ self.assertIn(f'fwmark {hex_fwmark} lookup {vrf}', tmp)
+
+ # Remove VRF from the interface — ip rule must be cleaned up
+ self.cli_delete(base_interface_path + ['vrf'])
+ self.cli_commit()
+
+ tmp = cmd(f'ip rule show priority 1998')
+ self.assertNotIn(f'fwmark {hex_fwmark}', tmp)
+
+ # Re-add VRF — ip rule must be re-created
+ self.cli_set(base_interface_path + ['vrf', vrf])
+ self.cli_commit()
+
+ tmp = cmd(f'ip rule show priority 1998')
+ self.assertIn(f'fwmark {hex_fwmark} lookup {vrf}', tmp)
+
+ # Delete the interface entirely — ip rule must be removed
+ self.cli_delete(base_interface_path)
+ self.cli_commit()
+
+ tmp = cmd(f'ip rule show priority 1998')
+ self.assertNotIn(f'fwmark {hex_fwmark}', tmp)
+
+ self.cli_delete(['vrf', 'name', vrf])
+
+
if __name__ == '__main__':
unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())
diff --git a/src/conf_mode/interfaces_wireguard.py b/src/conf_mode/interfaces_wireguard.py
index b430fa94a..92e3a239d 100755
--- a/src/conf_mode/interfaces_wireguard.py
+++ b/src/conf_mode/interfaces_wireguard.py
@@ -34,6 +34,7 @@ from vyos.configverify import verify_bond_bridge_member
from vyos.ifconfig import WireGuardIf
from vyos.utils.kernel import check_kmod
from vyos.utils.network import check_port_availability
+from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import is_wireguard_key_pair
from vyos.utils.process import call
from vyos import ConfigError
@@ -75,6 +76,16 @@ def get_config(config=None):
else:
wireguard['is_source_interface'] = tmp
+ if is_node_changed(conf, base + [ifname, 'fwmark']) or is_node_changed(
+ conf, base + [ifname, 'vrf']
+ ):
+ wireguard['fwmark_vrf_changed'] = {}
+ prev = conf.get_config_dict(
+ base + [ifname], effective=True, key_mangling=('-', '_'), get_first_key=True
+ )
+ wireguard['prev_fwmark'] = prev.get('fwmark')
+ wireguard['prev_vrf'] = prev.get('vrf')
+
return wireguard
@@ -154,6 +165,29 @@ def apply(wireguard):
else:
wg.update(wireguard)
+ # delete old fwmark-based ip rule if fwmark or VRF was changed
+ if 'fwmark_vrf_changed' in wireguard or 'deleted' in wireguard:
+ prev_fwmark = wireguard.get('prev_fwmark')
+ prev_vrf = wireguard.get('prev_vrf')
+ if prev_fwmark is not None and prev_vrf is not None:
+ table_id = get_vrf_tableid(prev_vrf)
+ if table_id is not None:
+ for afi in ['-4', '-6']:
+ call(
+ f'ip {afi} rule del pref 1998 fwmark {prev_fwmark} table {table_id}'
+ )
+
+ # Add ip rule to route fwmark-marked WireGuard tunnel packets into the
+ # correct VRF routing table. This is required for VRF-bound WireGuard
+ # interfaces with fwmark set, so that outgoing encapsulated packets use the
+ # proper VRF routes (otherwise, they may be unroutable or use the main table).
+ if wireguard.get('fwmark', '0') != '0' and 'vrf' in wireguard:
+ table_id = get_vrf_tableid(wireguard['vrf'])
+ for afi in ['-4', '-6']:
+ call(
+ f'ip {afi} rule add pref 1998 fwmark {wireguard["fwmark"]} table {table_id}'
+ )
+
domain_resolver_usage = '/run/use-vyos-domain-resolver-interfaces-wireguard-' + wireguard['ifname']
## DOMAIN RESOLVER
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 92bb956ed..c307ae27e 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -265,6 +265,12 @@ def apply(vrf):
# Remove map element
cmd(f'nft {nft_del_element}')
+ # Remove all ip rules pointing to this VRF table
+ table_id = get_vrf_tableid(tmp)
+ for afi in ['-4', '-6']:
+ while call(f'ip {afi} rule del table {table_id}') == 0:
+ pass
+
# Delete the VRF Kernel interface
call(f'ip link delete dev {tmp}')