From fdb732142a7181676ffc8a74c78d7ace9063c2e5 Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Tue, 5 Apr 2022 12:01:13 +0000 Subject: [PATCH] Add bridgedomain settings. Bridges can be created with default settings, with specific settings, and they can be sync'd at runtime with all of the settings in this change. Notably missing are two features: - unknown unicast flooding into specific interfaces (as opposed to on/off on the bridge) - learn-limit, which does not have an API getter, only a setter. --- config/bridgedomain.py | 32 ++++++++++++++++++ config/schema.py | 10 ++++++ config/test_bridgedomain.py | 16 +++++++++ docs/config-guide.md | 30 +++++++++++++---- intest/hippo9.yaml | 3 ++ schema.yaml | 10 ++++++ unittest/test_bridgedomain.yaml | 6 +++- vpp/reconciler.py | 57 +++++++++++++++++++++++++++++++-- 8 files changed, 155 insertions(+), 9 deletions(-) diff --git a/config/bridgedomain.py b/config/bridgedomain.py index a85e0ac..deada3b 100644 --- a/config/bridgedomain.py +++ b/config/bridgedomain.py @@ -81,6 +81,38 @@ def bvi_unique(yaml, bviname): return n<2 +def get_settings(yaml, ifname): + ifname, iface = get_by_name(yaml, ifname) + if not iface: + return None + + settings = { + 'learn': True, + 'unicast-flood': True, + 'unknown-unicast-flood': True, + 'unicast-forward': True, + 'arp-termination': False, + 'arp-unicast-forward': False, + 'mac-age-minutes': 0, ## 0 means disabled + } + if 'settings' in iface: + if 'learn' in iface['settings']: + settings['learn'] = iface['settings']['learn'] + if 'unicast-flood' in iface['settings']: + settings['unicast-flood'] = iface['settings']['unicast-flood'] + if 'unknown-unicast-flood' in iface['settings']: + settings['unknown-unicast-flood'] = iface['settings']['unknown-unicast-flood'] + if 'unicast-forward' in iface['settings']: + settings['unicast-forward'] = iface['settings']['unicast-forward'] + if 'arp-termination' in iface['settings']: + settings['arp-termination'] = iface['settings']['arp-termination'] + if 'arp-unicast-forward' in iface['settings']: + settings['arp-unicast-forward'] = iface['settings']['arp-unicast-forward'] + if 'mac-age-minutes' in iface['settings']: + settings['mac-age-minutes'] = int(iface['settings']['mac-age-minutes']) + return settings + + def validate_bridgedomains(yaml): result = True msgs = [] diff --git a/config/schema.py b/config/schema.py index 4bd8477..1cb87a6 100644 --- a/config/schema.py +++ b/config/schema.py @@ -32,6 +32,16 @@ bridgedomain: mtu: int(min=128,max=9216,required=False) bvi: str(matches='loop[0-9]+',required=False) interfaces: list(str(),required=False) + settings: include('bridgedomain-settings',required=False) +--- +bridgedomain-settings: + learn: bool(required=False) + unicast-flood: bool(required=False) + unknown-unicast-flood: bool(required=False) + unicast-forward: bool(required=False) + arp-termination: bool(required=False) + arp-unicast-forward: bool(required=False) + mac-age-minutes: int(min=0,max=255,required=False) --- loopback: description: str(exclude='\'"',len=64,required=False) diff --git a/config/test_bridgedomain.py b/config/test_bridgedomain.py index d32e28e..f699d3e 100644 --- a/config/test_bridgedomain.py +++ b/config/test_bridgedomain.py @@ -49,3 +49,19 @@ class TestBridgeDomainMethods(unittest.TestCase): def test_get_bridgedomains(self): ifs = bridgedomain.get_bridgedomains(self.cfg) self.assertEqual(len(ifs), 6) + + def test_get_settings(self): + settings = bridgedomain.get_settings(self.cfg, "bd1") + self.assertIsNone(settings) + + settings = bridgedomain.get_settings(self.cfg, "bd10") + self.assertTrue(settings['learn']) + self.assertTrue(settings['unknown-unicast-flood']) + self.assertTrue(settings['unicast-flood']) + self.assertEqual(settings['mac-age-minutes'], 0) + + settings = bridgedomain.get_settings(self.cfg, "bd11") + self.assertTrue(settings['learn']) + self.assertFalse(settings['unknown-unicast-flood']) + self.assertFalse(settings['unicast-flood']) + self.assertEqual(settings['mac-age-minutes'], 10) diff --git a/docs/config-guide.md b/docs/config-guide.md index 1fde7f3..a7c8a6d 100644 --- a/docs/config-guide.md +++ b/docs/config-guide.md @@ -105,17 +105,24 @@ BridgeDomains are required to be named `bdN` where N in [1, 16777216). Note that * ***interfaces***: A list of zero or more interfaces or sub-interfaces that are bridge members. If the bridge has a `BVI`, it MUST NOT appear in this list. Bridges are allowed to exist with no member interfaces. +* ***settings***: A map of bridge-domain settings to further manipulate its behavior: + * ***learn***: A boolean that turns learning on/off. Default True. + * ***unicast-flood***: A boolean that turns unicast flooding on/off. Default True. + * ***unknown-unicast-flood***: A boolean that turns unknown unicast flooding on/off. + Default True. + * ***unicast-forward***: A boolean that turns unicast forwarding on/off. Default True. + * ***arp-termination***: A boolean that turns termination and response of ARP Requests + on/off. Default False. + * ***arp-unicast-forward***: A boolean that turns L2 arp-unicast forwarding on/off. + Default False. + * ***mac-age-minutes***: An integer between [0,256) that drives the ARP timeout on the + bridge in minutes, where 0 means do not age out, which is the default. Any member sub-interfaces that are added, will automatically be configured to tag-rewrite the number of tags they have, so a simple dot1q sub-interface will be configured as `pop 1`, while a QinQ or QinAD sub-interface will be configured as `pop 2`. Conversely, when interfaces are removed from the bridge, their tag-rewriting will be disabled. -*Caveat*: Currently, bridgedomains are always created with their default attributes in VPP, that -is to say with learning and unicast forwarding turned on, unknown-unicast flooding enabled, -and ARP terminating and aging turned off. In a future release, `vppcfg` will give more -configuration options. - Examples: ``` bridgedomains: @@ -124,9 +131,20 @@ bridgedomains: bvi: loop1 interfaces: [ BondEthernet0.500, HundredGigabitEthernet12/0/1, vxlan_tunnel1 ] bd11: - description: "No member interfaces, default 1500 byte MTU" + description: "No members, default 1500 byte MTU, with (default) settings" + settings: + learn: True + unicast-flood: True + unknown-unicast-flood: True + unicast-forward: True + arp-termination: False + arp-unicast-forward: False + mac-age-minutes: 0 ``` +*Caveat*: The flooding of unknown-unicast can be turned on or off, but flooding to a specific interface +(as opposed to all interfaces which is the default), is not supported. + ### BondEthernets BondEthernets are required to be named `BondEthernetN` (note the camelcase) where N in diff --git a/intest/hippo9.yaml b/intest/hippo9.yaml index 49a18da..0cd71a5 100644 --- a/intest/hippo9.yaml +++ b/intest/hippo9.yaml @@ -75,6 +75,9 @@ bridgedomains: mtu: 2000 bvi: loop2 interfaces: [ BondEthernet0.500, BondEthernet0.501 ] + settings: + mac-age-minutes: 10 + learn: False bd11: mtu: 1500 diff --git a/schema.yaml b/schema.yaml index de920b5..d2858d7 100644 --- a/schema.yaml +++ b/schema.yaml @@ -18,6 +18,16 @@ bridgedomain: mtu: int(min=128,max=9216,required=False) bvi: str(matches='loop[0-9]+',required=False) interfaces: list(str(),required=False) + settings: include('bridgedomain-settings',required=False) +--- +bridgedomain-settings: + learn: bool(required=False) + unicast-flood: bool(required=False) + unknown-unicast-flood: bool(required=False) + unicast-forward: bool(required=False) + arp-termination: bool(required=False) + arp-unicast-forward: bool(required=False) + mac-age-minutes: int(min=0,max=255,required=False) --- loopback: description: str(exclude='\'"',len=64,required=False) diff --git a/unittest/test_bridgedomain.yaml b/unittest/test_bridgedomain.yaml index 84e2493..939c177 100644 --- a/unittest/test_bridgedomain.yaml +++ b/unittest/test_bridgedomain.yaml @@ -51,9 +51,13 @@ bridgedomains: bvi: loop0 interfaces: [ GigabitEthernet1/0/0, GigabitEthernet1/0/1, BondEthernet0 ] bd11: - description: "Bridge Domain 11, with sub-interfaces" + description: "Bridge Domain 11, with sub-interfaces and settings" mtu: 2000 interfaces: [ GigabitEthernet2/0/0.100, GigabitEthernet2/0/1.100, BondEthernet0.100 ] + settings: + mac-age-minutes: 10 + unicast-flood: False + unknown-unicast-flood: False bd12: description: "Bridge Domain 12, invalid because it has Gi1/0/0 as well" mtu: 9000 diff --git a/vpp/reconciler.py b/vpp/reconciler.py index 630b3cf..0e92e00 100644 --- a/vpp/reconciler.py +++ b/vpp/reconciler.py @@ -623,9 +623,24 @@ class Reconciler(): for ifname in bridgedomain.get_bridgedomains(self.cfg): ifname, iface = bridgedomain.get_by_name(self.cfg, ifname) instance = int(ifname[2:]) + settings = bridgedomain.get_settings(self.cfg, ifname) if instance in self.vpp.cache['bridgedomains']: continue cli="create bridge-domain %s" % (instance) + if not settings['learn']: + cli += " learn 0" + if not settings['unicast-flood']: + cli += " flood 0" + if not settings['unknown-unicast-flood']: + cli += " uu-flood 0" + if not settings['unicast-forward']: + cli += " forward 0" + if settings['arp-termination']: + cli += " arp-term 1" + if settings['arp-unicast-forward']: + cli += " arp-ufwd 1" + if settings['mac-age-minutes'] > 0: + cli += " mac-age %d" % settings['mac-age-minutes'] self.cli['create'].append(cli); return True @@ -727,12 +742,48 @@ class Reconciler(): bridge_members = [self.vpp.cache['interfaces'][x].interface_name for x in bridge_sw_if_index_list if x in self.vpp.cache['interfaces']] else: ## New BridgeDomain + vpp_bridge = None bvi_sw_if_index = -1 bridge_members = [] config_bridge_ifname, config_bridge_iface = bridgedomain.get_by_name(self.cfg, "bd%d"%instance) - if not 'interfaces' in config_bridge_iface: - continue + if vpp_bridge: + # Sync settings on existing bridge. create_bridgedomain() will have set them for new bridges. + settings = bridgedomain.get_settings(self.cfg, config_bridge_ifname) + if settings['learn'] != vpp_bridge.learn: + cli="set bridge-domain learn %d" % (instance) + if not settings['learn']: + cli += " disable" + self.cli['sync'].append(cli); + if settings['unicast-forward'] != vpp_bridge.forward: + cli="set bridge-domain forward %d" % (instance) + if not settings['unicast-forward']: + cli += " disable" + self.cli['sync'].append(cli); + if settings['unicast-flood'] != vpp_bridge.flood: + cli="set bridge-domain flood %d" % (instance) + if not settings['unicast-flood']: + cli += " disable" + self.cli['sync'].append(cli); + if settings['unknown-unicast-flood'] != vpp_bridge.uu_flood: + cli="set bridge-domain uu-flood %d" % (instance) + if not settings['unknown-unicast-flood']: + cli += " disable" + self.cli['sync'].append(cli); + if settings['arp-termination'] != vpp_bridge.arp_term: + cli="set bridge-domain arp term %d" % (instance) + if not settings['arp-termination']: + cli += " disable" + self.cli['sync'].append(cli); + if settings['arp-unicast-forward'] != vpp_bridge.arp_ufwd: + cli="set bridge-domain arp-ufwd %d" % (instance) + if not settings['arp-unicast-forward']: + cli += " disable" + self.cli['sync'].append(cli); + if settings['mac-age-minutes'] != vpp_bridge.mac_age: + cli="set bridge-domain mac-age %d %d" % (instance, settings['mac-age-minutes']) + self.cli['sync'].append(cli); + if 'bvi' in config_bridge_iface: bviname = config_bridge_iface['bvi'] if bviname in self.vpp.cache['interface_names'] and self.vpp.cache['interface_names'][bviname].sw_if_index == bvi_sw_if_index: @@ -740,6 +791,8 @@ class Reconciler(): cli="set interface l2 bridge %s %d bvi" % (bviname, instance) self.cli['sync'].append(cli); + if not 'interfaces' in config_bridge_iface: + continue for member_ifname in config_bridge_iface['interfaces']: member_ifname, member_iface = interface.get_by_name(self.cfg, member_ifname) if not member_ifname in bridge_members: