From ec9a95502daa88b9632af12524e7cefebf86bab6 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sat, 28 Oct 2023 20:57:38 +0200 Subject: vxlan: T5668: add CLI knob to enable ARP/ND suppression In order to minimize the flooding of ARP and ND messages in the VXLAN network, EVPN includes provisions [1] that allow participating VTEPs to suppress such messages in case they know the MAC-IP binding and can reply on behalf of the remote host. In Linux, the above is implemented in the bridge driver using a per-port option called "neigh_suppress" that was added in kernel version 4.15. [1] https://www.rfc-editor.org/rfc/rfc7432#section-10 --- interface-definitions/interfaces-vxlan.xml.in | 6 ++++ python/vyos/ifconfig/vxlan.py | 23 +++++++++++++++ smoketest/scripts/cli/test_interfaces_vxlan.py | 39 ++++++++++++++++++++++++++ src/conf_mode/interfaces-vxlan.py | 6 ++++ 4 files changed, 74 insertions(+) diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 3fae17178..911c14f0c 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -89,6 +89,12 @@ + + + Enable neighbor discovery (ARP and ND) suppression + + + #include diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 1fe5db7cd..ddb2263ab 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -56,6 +56,10 @@ class VXLANIf(Interface): } _command_set = {**Interface._command_set, **{ + 'neigh_suppress': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'shellcmd': 'bridge link set dev {ifname} neigh_suppress {value} learning off', + }, 'vlan_tunnel': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'shellcmd': 'bridge link set dev {ifname} vlan_tunnel {value}', @@ -113,6 +117,19 @@ class VXLANIf(Interface): 'port {port} dev {ifname}' self._cmd(cmd.format(**self.config)) + def set_neigh_suppress(self, state): + """ + Controls whether neigh discovery (arp and nd) proxy and suppression + is enabled on the port. By default this flag is off. + """ + + # Determine current OS Kernel neigh_suppress setting - only adjust when needed + tmp = get_interface_config(self.ifname) + cur_state = 'on' if dict_search(f'linkinfo.info_slave_data.neigh_suppress', tmp) == True else 'off' + new_state = 'on' if state else 'off' + if cur_state != new_state: + self.set_interface('neigh_suppress', state) + def set_vlan_vni_mapping(self, state): """ Controls whether vlan to tunnel mapping is enabled on the port. @@ -163,3 +180,9 @@ class VXLANIf(Interface): # Enable/Disable VLAN tunnel mapping # This is only possible after the interface was assigned to the bridge self.set_vlan_vni_mapping(dict_search('vlan_to_vni', config) != None) + + # Enable/Disable neighbor suppression and learning, there is no need to + # explicitly "disable" it, as VXLAN interface will be recreated if anything + # under "parameters" changes. + if dict_search('parameters.neighbor_suppress', config) != None: + self.set_neigh_suppress('on') diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index e9c9e68fd..e35733fdc 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -177,11 +177,50 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): tmp = get_interface_config(interface) self.assertEqual(tmp['master'], bridge) + self.assertFalse(tmp['linkinfo']['info_slave_data']['neigh_suppress']) tmp = get_vxlan_vlan_tunnels('vxlan0') self.assertEqual(tmp, list(vlan_to_vni)) self.cli_delete(['interfaces', 'bridge', bridge]) + def test_vxlan_neighbor_suppress(self): + bridge = 'br555' + interface = 'vxlan555' + source_interface = 'eth0' + + self.cli_set(self._base_path + [interface, 'external']) + self.cli_set(self._base_path + [interface, 'source-interface', source_interface]) + + self.cli_set(self._base_path + [interface, 'parameters', 'neighbor-suppress']) + + # This must fail as this VXLAN interface is not associated with any bridge + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', interface]) + + # commit configuration + self.cli_commit() + + self.assertTrue(interface_exists(bridge)) + self.assertTrue(interface_exists(interface)) + + tmp = get_interface_config(interface) + self.assertEqual(tmp['master'], bridge) + self.assertTrue(tmp['linkinfo']['info_slave_data']['neigh_suppress']) + self.assertFalse(tmp['linkinfo']['info_slave_data']['learning']) + + # Remove neighbor suppress configuration and re-test + self.cli_delete(self._base_path + [interface, 'parameters', 'neighbor-suppress']) + # commit configuration + self.cli_commit() + + tmp = get_interface_config(interface) + self.assertEqual(tmp['master'], bridge) + self.assertFalse(tmp['linkinfo']['info_slave_data']['neigh_suppress']) + self.assertTrue(tmp['linkinfo']['info_slave_data']['learning']) + + self.cli_delete(['interfaces', 'bridge', bridge]) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index ff8144e74..7bd3fa692 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -34,6 +34,7 @@ from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import Interface from vyos.ifconfig import VXLANIf from vyos.template import is_ipv6 +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag airbag.enable() @@ -164,6 +165,11 @@ def verify(vxlan): raise ConfigError(f'VNI "{vni}" is already assigned to a different VLAN!') vnis_used.append(vni) + if dict_search('parameters.neighbor_suppress', vxlan): + if 'is_bridge_member' not in vxlan: + raise ConfigError('Neighbor suppression requires that VXLAN interface '\ + 'is member of a bridge interface!') + verify_mtu_ipv6(vxlan) verify_address(vxlan) verify_bond_bridge_member(vxlan) -- cgit v1.2.3