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: