Skip to content

Commit

Permalink
vxlan: T5668: add CLI knob to enable ARP/ND suppression
Browse files Browse the repository at this point in the history
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
  • Loading branch information
c-po committed Oct 28, 2023
1 parent 0e129df commit f3039d2
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 0 deletions.
6 changes: 6 additions & 0 deletions interface-definitions/interfaces-vxlan.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
<valueless/>
</properties>
</leafNode>
<leafNode name="neighbor-suppress">
<properties>
<help>Enable neighbor discovery (ARP and ND) suppression</help>
<valueless/>
</properties>
</leafNode>
</children>
</node>
#include <include/port-number.xml.i>
Expand Down
23 changes: 23 additions & 0 deletions python/vyos/ifconfig/vxlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}',
},
'vlan_tunnel': {
'validate': lambda v: assert_list(v, ['on', 'off']),
'shellcmd': 'bridge link set dev {ifname} vlan_tunnel {value}',
Expand Down Expand Up @@ -113,6 +117,19 @@ def _create(self):
'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.
Expand Down Expand Up @@ -163,3 +180,9 @@ def update(self, config):
# 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, 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')
37 changes: 37 additions & 0 deletions smoketest/scripts/cli/test_interfaces_vxlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,48 @@ def test_vxlan_vlan_vni_mapping(self):

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'])

# 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.cli_delete(['interfaces', 'bridge', bridge])

if __name__ == '__main__':
unittest.main(verbosity=2)
6 changes: 6 additions & 0 deletions src/conf_mode/interfaces-vxlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
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()
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit f3039d2

Please sign in to comment.