Skip to content

Commit

Permalink
Merge pull request vyos#3834 from c-po/interface-vrf-move
Browse files Browse the repository at this point in the history
interfaces: T6592: moving an interface between VRF instances failed
  • Loading branch information
c-po authored Jul 20, 2024
2 parents 458a724 + 36f3791 commit da3d941
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 22 deletions.
28 changes: 19 additions & 9 deletions python/vyos/ifconfig/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
from vyos.utils.network import get_interface_namespace
from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import is_netns_interface
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import run
Expand Down Expand Up @@ -402,7 +403,7 @@ def _delete(self):
if netns: cmd = f'ip netns exec {netns} {cmd}'
return self._cmd(cmd)

def _set_vrf_ct_zone(self, vrf):
def _set_vrf_ct_zone(self, vrf, old_vrf_tableid=None):
"""
Add/Remove rules in nftables to associate traffic in VRF to an
individual conntack zone
Expand All @@ -411,20 +412,27 @@ def _set_vrf_ct_zone(self, vrf):
if 'netns' in self.config:
return None

def nft_check_and_run(nft_command):
# Check if deleting is possible first to avoid raising errors
_, err = self._popen(f'nft --check {nft_command}')
if not err:
# Remove map element
self._cmd(f'nft {nft_command}')

if vrf:
# Get routing table ID for VRF
vrf_table_id = get_interface_config(vrf).get('linkinfo', {}).get(
'info_data', {}).get('table')
vrf_table_id = get_vrf_tableid(vrf)
# Add map element with interface and zone ID
if vrf_table_id:
# delete old table ID from nftables if it has changed, e.g. interface moved to a different VRF
if old_vrf_tableid and old_vrf_tableid != int(vrf_table_id):
nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}'
nft_check_and_run(nft_del_element)

self._cmd(f'nft add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}')
else:
nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}'
# Check if deleting is possible first to avoid raising errors
_, err = self._popen(f'nft --check {nft_del_element}')
if not err:
# Remove map element
self._cmd(f'nft {nft_del_element}')
nft_check_and_run(nft_del_element)

def get_min_mtu(self):
"""
Expand Down Expand Up @@ -601,8 +609,10 @@ def set_vrf(self, vrf: str) -> bool:
if tmp == vrf:
return False

# Get current VRF table ID
old_vrf_tableid = get_vrf_tableid(self.ifname)
self.set_interface('vrf', vrf)
self._set_vrf_ct_zone(vrf)
self._set_vrf_ct_zone(vrf, old_vrf_tableid)
return True

def set_arp_cache_tmo(self, tmo):
Expand Down
23 changes: 18 additions & 5 deletions python/vyos/utils/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@ def get_interface_vrf(interface):
return tmp['master']
return 'default'

def get_vrf_tableid(interface: str):
""" Return VRF table ID for given interface name or None """
from vyos.utils.dict import dict_search
table = None
tmp = get_interface_config(interface)
# Check if we are "the" VRF interface
if dict_search('linkinfo.info_kind', tmp) == 'vrf':
table = tmp['linkinfo']['info_data']['table']
# or an interface bound to a VRF
elif dict_search('linkinfo.info_slave_kind', tmp) == 'vrf':
table = tmp['linkinfo']['info_slave_data']['table']
return table

def get_interface_config(interface):
""" Returns the used encapsulation protocol for given interface.
If interface does not exist, None is returned.
Expand Down Expand Up @@ -537,21 +550,21 @@ def ipv6_prefix_length(low, high):
return None

xor = bytearray(a ^ b for a, b in zip(lo, hi))

plen = 0
while plen < 128 and xor[plen // 8] == 0:
plen += 8

if plen == 128:
return plen

for i in range((plen // 8) + 1, 16):
if xor[i] != 0:
return None

for i in range(8):
msk = ~xor[plen // 8] & 0xff

if msk == bytemasks[i]:
return plen + i + 1

Expand Down
46 changes: 46 additions & 0 deletions smoketest/scripts/cli/base_interfaces_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from vyos.utils.process import process_named_running
from vyos.utils.network import get_interface_config
from vyos.utils.network import get_interface_vrf
from vyos.utils.network import get_vrf_tableid
from vyos.utils.process import cmd
from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.network import is_ipv6_link_local
Expand Down Expand Up @@ -257,6 +258,51 @@ def test_dhcpv6_vrf(self):

self.cli_delete(['vrf', 'name', vrf_name])

def test_move_interface_between_vrf_instances(self):
if not self._test_vrf:
self.skipTest('not supported')

vrf1_name = 'smoketest_mgmt1'
vrf1_table = '5424'
vrf2_name = 'smoketest_mgmt2'
vrf2_table = '7412'

self.cli_set(['vrf', 'name', vrf1_name, 'table', vrf1_table])
self.cli_set(['vrf', 'name', vrf2_name, 'table', vrf2_table])

# move interface into first VRF
for interface in self._interfaces:
for option in self._options.get(interface, []):
self.cli_set(self._base_path + [interface] + option.split())
self.cli_set(self._base_path + [interface, 'vrf', vrf1_name])

self.cli_commit()

# check that interface belongs to proper VRF
for interface in self._interfaces:
tmp = get_interface_vrf(interface)
self.assertEqual(tmp, vrf1_name)

tmp = get_interface_config(vrf1_name)
self.assertEqual(int(vrf1_table), get_vrf_tableid(interface))

# move interface into second VRF
for interface in self._interfaces:
self.cli_set(self._base_path + [interface, 'vrf', vrf2_name])

self.cli_commit()

# check that interface belongs to proper VRF
for interface in self._interfaces:
tmp = get_interface_vrf(interface)
self.assertEqual(tmp, vrf2_name)

tmp = get_interface_config(vrf2_name)
self.assertEqual(int(vrf2_table), get_vrf_tableid(interface))

self.cli_delete(['vrf', 'name', vrf1_name])
self.cli_delete(['vrf', 'name', vrf2_name])

def test_span_mirror(self):
if not self._mirror_interfaces:
self.skipTest('not supported')
Expand Down
5 changes: 3 additions & 2 deletions smoketest/scripts/cli/test_protocols_static.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from vyos.configsession import ConfigSessionError
from vyos.template import is_ipv6
from vyos.utils.network import get_interface_config
from vyos.utils.network import get_vrf_tableid

base_path = ['protocols', 'static']
vrf_path = ['protocols', 'vrf']
Expand Down Expand Up @@ -421,7 +422,7 @@ def test_03_static_vrf(self):
tmp = get_interface_config(vrf)

# Compare VRF table ID
self.assertEqual(tmp['linkinfo']['info_data']['table'], int(vrf_config['table']))
self.assertEqual(get_vrf_tableid(vrf), int(vrf_config['table']))
self.assertEqual(tmp['linkinfo']['info_kind'], 'vrf')

# Verify FRR bgpd configuration
Expand Down Expand Up @@ -478,4 +479,4 @@ def test_03_static_vrf(self):
self.assertIn(tmp, frrconfig)

if __name__ == '__main__':
unittest.main(verbosity=2)
unittest.main(verbosity=2, failfast=True)
7 changes: 3 additions & 4 deletions smoketest/scripts/cli/test_vrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from vyos.ifconfig import Section
from vyos.utils.file import read_file
from vyos.utils.network import get_interface_config
from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.network import interface_exists
from vyos.utils.system import sysctl_read
Expand Down Expand Up @@ -111,8 +112,7 @@ def test_vrf_vni_and_table_id(self):
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)

tmp = get_interface_config(vrf)
self.assertEqual(int(table), tmp['linkinfo']['info_data']['table'])
self.assertEqual(int(table), get_vrf_tableid(vrf))

# Increment table ID for the next run
table = str(int(table) + 1)
Expand Down Expand Up @@ -266,8 +266,7 @@ def test_vrf_link_local_ip_addresses(self):
for address in addresses:
self.assertTrue(is_intf_addr_assigned(interface, address))
# Verify VRF table ID
tmp = get_interface_config(vrf)
self.assertEqual(int(table), tmp['linkinfo']['info_data']['table'])
self.assertEqual(int(table), get_vrf_tableid(vrf))

# Verify interface is assigned to VRF
tmp = get_interface_config(interface)
Expand Down
5 changes: 3 additions & 2 deletions src/conf_mode/vrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from vyos.template import render_to_string
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import get_vrf_members
from vyos.utils.network import interface_exists
from vyos.utils.process import call
Expand Down Expand Up @@ -160,8 +161,8 @@ def verify(vrf):

# routing table id can't be changed - OS restriction
if interface_exists(name):
tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name)))
if tmp and tmp != vrf_config['table']:
tmp = get_vrf_tableid(name)
if tmp and tmp != int(vrf_config['table']):
raise ConfigError(f'VRF "{name}" table id modification not possible!')

# VRF routing table ID must be unique on the system
Expand Down

0 comments on commit da3d941

Please sign in to comment.