From 83bb095514b2af944f201f3fba0e8de2c519f77e Mon Sep 17 00:00:00 2001 From: Ivan Pepelnjak Date: Sat, 10 Sep 2022 10:42:55 +0200 Subject: [PATCH] Create VLAN links from a VLAN trunk before handling access/native VLAN (fixes #421) * VLAN trunk is created before changing any link attributes based on access/native VLAN * On routed access/native VLANs, set vlan.mode on all interfaces, not just on link (because it won't be copied into interface attributes) * Check whether routed native VLAN is supported by the platform (implements feature flag discussed in #415) * Cisco IOSv is the only device so far known to support routed native VLANs. FRR/Cumulus should be able to do that as well, but that needs to be verified. * Add 'routed native VLAN' test case --- docs/dev/config/vlan.md | 1 + netsim/modules/vlan.py | 63 +++- netsim/topology-defaults.yml | 1 + .../topology/expected/vlan-native-routed.yml | 345 ++++++++++++++++++ tests/topology/input/vlan-native-routed.yml | 21 ++ 5 files changed, 417 insertions(+), 14 deletions(-) create mode 100644 tests/topology/expected/vlan-native-routed.yml create mode 100644 tests/topology/input/vlan-native-routed.yml diff --git a/docs/dev/config/vlan.md b/docs/dev/config/vlan.md index 1a87012bd..50ecc69bd 100644 --- a/docs/dev/config/vlan.md +++ b/docs/dev/config/vlan.md @@ -48,6 +48,7 @@ You have to specify VLAN-related capabilities of your device in `devices. bool: def_vlan = get_from_box(topology,f'vlans.{vlan}.mode') def_global = get_from_box(topology,'vlan.mode') or 'irb' - #print(f'RAV: {link}') - #print(f'RAV: vlan {vlan} def_mode {def_vlan}') + if common.debug_active('vlan'): + print(f'routed_access_vlan: {link}') + print(f'... vlan {vlan} def_mode {def_vlan}') for intf in link.interfaces: mode = get_from_box(intf,'vlan.mode') or \ def_link or \ @@ -103,7 +104,8 @@ def routed_access_vlan(link: Box, topology: Box, vlan: str) -> bool: if mode != 'route': return False - #print(f'RAV: routed VLAN') + if common.debug_active('vlan'): + print(f'... VLAN is routed (returning True)') return True # @@ -355,6 +357,31 @@ def validate_link_vlan_attributes(link: Box,v_attr: Box,topology: Box) -> bool: intf[af] = False # ... make sure it's not connected to the native VLAN subnet return link_ok +""" +check_native_routed_vlan: verify that all devices attached to a link can support native routed VLAN + +This function is called when we're dealing with a link with routed non-tagged VLAN. It needs to check +whether we're dealing with a native VLAN (as opposed to access VLAN) and if so, iterate through +the attached nodes to validate whether they support this setup. +""" + +def check_native_routed_vlan(link: Box, v_attr: Box, topology: Box) -> None: + if not 'native' in v_attr: # No vlan.native attribute, we're good + return + + if not v_attr.native.set: # Native VLAN set is empty, we're good + return + + native_vlan = list(v_attr.native.set)[0] + for intf in link.interfaces: # Check only nodes VLAN attributes that have native VLAN in their trunk + if 'vlan' in intf and native_vlan in intf.vlan.get('trunk',{}): + features = devices.get_device_features(topology.nodes[intf.node],topology.defaults) + if not features.vlan.native_routed: + common.error( + f'Node {intf.node} does not support routed native VLANs\n... link {link}', + common.IncorrectValue, + 'vlan') + """ copy_vlan_attributes: copy prefix and link type from vlan to link """ @@ -429,7 +456,11 @@ def set_link_vlan_prefix(link: Box, v_attr: Box, topology: Box) -> None: create_vlan_links: Create virtual links for every VLAN in the VLAN trunk """ def create_vlan_links(link: Box, v_attr: Box, topology: Box) -> None: - native_vlan = link.get('vlan_name',None) + if common.debug_active('vlan'): + print('create VLAN links') + print(f'... link {link}') + print(f'... v_attr {v_attr}') + native_vlan = v_attr.native.list[0] if 'native' in v_attr else None for vname in sorted(v_attr.trunk.set): if vname != native_vlan: # Skip native VLAN link_data = Box(link.vlan.trunk[vname] or {},default_box=True,box_dots=True) @@ -902,6 +933,16 @@ def link_pre_transform(self, link: Box, topology: Box) -> None: if not validate_link_vlan_attributes(link,v_attr,topology): return + # Merge link VLAN attributes into interface VLAN attributes to make subsequent steps easier + if 'vlan' in link: + for intf in link.interfaces: # Iterate over all interfaces attached to the link + intf_node = topology.nodes[intf.node] + if 'vlan' in intf_node.get('module',[]): # ... is the node a VLAN-aware node? + intf.vlan = link.vlan + intf.vlan # ... merge link VLAN attributes with interface attributes + + if 'trunk' in v_attr: + create_vlan_links(link,v_attr,topology) + svi_skipattr = topology.defaults.vlan.vlan_no_propagate or [] # VLAN attributes not copied into link data link_vlan = get_link_access_vlan(v_attr) routed_vlan = False @@ -919,17 +960,14 @@ def link_pre_transform(self, link: Box, topology: Box) -> None: link[k] = vlan_data[k] + link[k] if routed_vlan: + check_native_routed_vlan(link,v_attr,topology) link.vlan.mode = 'route' + for intf in link.interfaces: + if 'vlan' in intf: + intf.vlan.mode = 'route' else: set_link_vlan_prefix(link,v_attr,topology) - # Merge link VLAN attributes into interface VLAN attributes to make subsequent steps easier - if 'vlan' in link: - for intf in link.interfaces: # Iterate over all interfaces attached to the link - intf_node = topology.nodes[intf.node] - if 'vlan' in intf_node.get('module',[]): # ... is the node a VLAN-aware node? - intf.vlan = link.vlan + intf.vlan # ... merge link VLAN attributes with interface attributes - # Disable IP addressing on access VLAN ports on bridged VLANs if link_vlan: # Are we dealing with an access VLAN? for intf in link.interfaces: # Iterate over all interfaces attached to the link @@ -939,9 +977,6 @@ def link_pre_transform(self, link: Box, topology: Box) -> None: intf.ipv4 = False # ... if so, disable addressing on this interface intf.ipv6 = False - if 'trunk' in v_attr: - create_vlan_links(link,v_attr,topology) - def module_post_transform(self, topology: Box) -> None: for n in topology.nodes.values(): if 'vlan' in n.get('module',[]): diff --git a/netsim/topology-defaults.yml b/netsim/topology-defaults.yml index 36fcb0033..620a7b1d1 100644 --- a/netsim/topology-defaults.yml +++ b/netsim/topology-defaults.yml @@ -319,6 +319,7 @@ devices: svi_interface_name: BVI{bvi} subif_name: "{ifname}.{subif_index}" mixed_trunk: True + native_routed: True vrf: loopback_interface_name: Loopback{vrfidx} external: diff --git a/tests/topology/expected/vlan-native-routed.yml b/tests/topology/expected/vlan-native-routed.yml new file mode 100644 index 000000000..571380a3c --- /dev/null +++ b/tests/topology/expected/vlan-native-routed.yml @@ -0,0 +1,345 @@ +input: +- topology/input/vlan-native-routed.yml +- package:topology-defaults.yml +links: +- bridge: input_1 + gateway: + ipv4: 172.16.3.1/24 + interfaces: + - ifindex: 1 + ipv4: 172.16.3.1/24 + node: r1 + vlan: + mode: route + native: pxeboot + trunk: + blue: {} + pxeboot: {} + red: {} + - ifindex: 1 + ipv4: 172.16.3.2/24 + node: r2 + vlan: + mode: route + native: pxeboot + trunk: + blue: {} + pxeboot: {} + red: {} + - ifindex: 1 + ipv4: 172.16.3.3/24 + node: h1 + linkindex: 1 + node_count: 3 + prefix: + ipv4: 172.16.3.0/24 + type: lan + vlan: + mode: route + native: pxeboot + trunk: + blue: {} + pxeboot: {} + red: {} +module: +- vlan +name: input +nodes: + h1: + af: + ipv4: true + box: generic/ubuntu2004 + device: linux + id: 3 + interfaces: + - bridge: input_1 + gateway: + ipv4: 172.16.3.1/24 + ifindex: 1 + ifname: eth1 + ipv4: 172.16.3.3/24 + linkindex: 1 + name: h1 -> [r1,r2] + neighbors: + - ifname: GigabitEthernet0/1 + ipv4: 172.16.3.1/24 + node: r1 + - ifname: GigabitEthernet0/1 + ipv4: 172.16.3.2/24 + node: r2 + type: lan + mgmt: + ifname: eth0 + ipv4: 192.168.121.103 + mac: 08-4F-A9-00-00-03 + module: [] + name: h1 + role: host + r1: + af: + ipv4: true + box: cisco/iosv + device: iosv + id: 1 + interfaces: + - bridge: input_1 + bridge_group: 1 + ifindex: 1 + ifname: GigabitEthernet0/1 + ipv4: 172.16.3.1/24 + linkindex: 1 + name: r1 -> [r2,h1] + neighbors: + - ifname: GigabitEthernet0/1 + ipv4: 172.16.3.2/24 + node: r2 + - ifname: eth1 + ipv4: 172.16.3.3/24 + node: h1 + subif_index: 2 + type: lan + vlan: + mode: route + trunk: + blue: {} + pxeboot: {} + red: {} + trunk_id: + - 1002 + - ifindex: 2 + ifname: GigabitEthernet0/1.1 + parent_ifindex: 1 + parent_ifname: GigabitEthernet0/1 + type: vlan_member + virtual_interface: true + vlan: + access: blue + access_id: 1001 + - ifindex: 3 + ifname: GigabitEthernet0/1.2 + parent_ifindex: 1 + parent_ifname: GigabitEthernet0/1 + type: vlan_member + virtual_interface: true + vlan: + access: red + access_id: 1000 + - bridge_group: 2 + ifindex: 4 + ifname: BVI2 + ipv4: 172.16.1.1/24 + name: VLAN blue (1001) -> [r2] + neighbors: + - ifname: BVI2 + ipv4: 172.16.1.2/24 + node: r2 + type: svi + virtual_interface: true + vlan: + mode: irb + - bridge_group: 3 + ifindex: 5 + ifname: BVI3 + ipv4: 172.16.0.1/24 + name: VLAN red (1000) -> [r2] + neighbors: + - ifname: BVI3 + ipv4: 172.16.0.2/24 + node: r2 + type: svi + virtual_interface: true + vlan: + mode: irb + loopback: + ipv4: 10.0.0.1/32 + mgmt: + ifname: GigabitEthernet0/0 + ipv4: 192.168.121.101 + mac: 08-4F-A9-00-00-01 + module: + - vlan + name: r1 + vlan: + max_bridge_group: 3 + mode: irb + vlans: + blue: + bridge_group: 2 + id: 1001 + mode: irb + prefix: + ipv4: 172.16.1.0/24 + vni: 101001 + pxeboot: + bridge_group: 1 + id: 1002 + mode: route + prefix: + ipv4: 172.16.2.0/24 + vni: 101002 + red: + bridge_group: 3 + id: 1000 + mode: irb + prefix: + ipv4: 172.16.0.0/24 + vni: 101000 + r2: + af: + ipv4: true + box: cisco/iosv + device: iosv + id: 2 + interfaces: + - bridge: input_1 + bridge_group: 1 + ifindex: 1 + ifname: GigabitEthernet0/1 + ipv4: 172.16.3.2/24 + linkindex: 1 + name: r2 -> [r1,h1] + neighbors: + - ifname: GigabitEthernet0/1 + ipv4: 172.16.3.1/24 + node: r1 + - ifname: eth1 + ipv4: 172.16.3.3/24 + node: h1 + subif_index: 2 + type: lan + vlan: + mode: route + trunk: + blue: {} + pxeboot: {} + red: {} + trunk_id: + - 1002 + - ifindex: 2 + ifname: GigabitEthernet0/1.1 + parent_ifindex: 1 + parent_ifname: GigabitEthernet0/1 + type: vlan_member + virtual_interface: true + vlan: + access: blue + access_id: 1001 + - ifindex: 3 + ifname: GigabitEthernet0/1.2 + parent_ifindex: 1 + parent_ifname: GigabitEthernet0/1 + type: vlan_member + virtual_interface: true + vlan: + access: red + access_id: 1000 + - bridge_group: 2 + ifindex: 4 + ifname: BVI2 + ipv4: 172.16.1.2/24 + name: VLAN blue (1001) -> [r1] + neighbors: + - ifname: BVI2 + ipv4: 172.16.1.1/24 + node: r1 + type: svi + virtual_interface: true + vlan: + mode: irb + - bridge_group: 3 + ifindex: 5 + ifname: BVI3 + ipv4: 172.16.0.2/24 + name: VLAN red (1000) -> [r1] + neighbors: + - ifname: BVI3 + ipv4: 172.16.0.1/24 + node: r1 + type: svi + virtual_interface: true + vlan: + mode: irb + loopback: + ipv4: 10.0.0.2/32 + mgmt: + ifname: GigabitEthernet0/0 + ipv4: 192.168.121.102 + mac: 08-4F-A9-00-00-02 + module: + - vlan + name: r2 + vlan: + max_bridge_group: 3 + mode: irb + vlans: + blue: + bridge_group: 2 + id: 1001 + mode: irb + neighbors: + - ifname: BVI2 + ipv4: 172.16.1.2/24 + node: r2 + - ifname: BVI2 + ipv4: 172.16.1.1/24 + node: r1 + prefix: + ipv4: 172.16.1.0/24 + vni: 101001 + pxeboot: + bridge_group: 1 + id: 1002 + mode: route + prefix: + ipv4: 172.16.2.0/24 + vni: 101002 + red: + bridge_group: 3 + id: 1000 + mode: irb + neighbors: + - ifname: BVI3 + ipv4: 172.16.0.2/24 + node: r2 + - ifname: BVI3 + ipv4: 172.16.0.1/24 + node: r1 + prefix: + ipv4: 172.16.0.0/24 + vni: 101000 +provider: libvirt +vlan: + mode: irb +vlans: + blue: + host_count: 0 + id: 1001 + neighbors: + - ifname: BVI2 + ipv4: 172.16.1.2/24 + node: r2 + - ifname: BVI2 + ipv4: 172.16.1.1/24 + node: r1 + prefix: + ipv4: 172.16.1.0/24 + vni: 101001 + pxeboot: + id: 1002 + mode: route + prefix: + ipv4: 172.16.2.0/24 + vni: 101002 + red: + host_count: 0 + id: 1000 + neighbors: + - ifname: BVI3 + ipv4: 172.16.0.2/24 + node: r2 + - ifname: BVI3 + ipv4: 172.16.0.1/24 + node: r1 + prefix: + ipv4: 172.16.0.0/24 + vni: 101000 diff --git a/tests/topology/input/vlan-native-routed.yml b/tests/topology/input/vlan-native-routed.yml new file mode 100644 index 000000000..2e1e6dae3 --- /dev/null +++ b/tests/topology/input/vlan-native-routed.yml @@ -0,0 +1,21 @@ +defaults.device: iosv +module: [ vlan ] +vlans: + red: + blue: + pxeboot: + mode: route + +nodes: + r1: + r2: + h1: + device: linux + module: [] + +links: +- r1: + r2: + h1: + vlan.trunk: [ red, blue, pxeboot ] + vlan.native: pxeboot