# Configuring VLANs This document describes the device data model parameters one should consider when creating a VLAN configuration template. For a wider picture, please see [contributing new devices](../devices.md) document. This document assumes you're using an Ansible task list that is able to deploy device configuration from a template. If you plan to use Ansible modules to build device configuration, you'll find some guidance in [Using Ansible Configuration Modules](ospf-ansible-config) section of OSPF implementation guide. VLAN node- and interface data model contains enough information to implement VLANs on a wide variety of platforms, including: * Switch-like platforms that use a VLAN database, VLAN trunks on physical interfaces, and VLAN/SVI interfaces (example: Dell OS10). Set **vlan.model** attribute to **switch**. * Router-like platforms that use bridge groups, VLAN subinterfaces on physical interfaces, and BVI/IRB interfaces (example: Cisco IOSv, Nokia SR Linux, Mikrotik RouterOS). Set **vlan.model** attribute to **router**. * Switch-like platforms that support both routed and switched ports, or a hybrid approach where some ports can be routed using VLAN subinterfaces, while other ports could be connected to the internal L2/L3 switch (example: Arista EOS, VyOS). Set **vlan.model** attribute to **l3-switch**. The following blog posts might help if the above bullets managed to totally confuse you: * [Router Interfaces and Switch Ports](https://blog.ipspace.net/2022/09/interfaces-ports.html) * [How Routers Became Bridges](https://blog.ipspace.net/2022/09/routers-bridges-crb-irb.html) * [VLAN Interfaces and Subinterfaces](https://blog.ipspace.net/2022/09/vlan-interfaces.html) Finally, it's highly recommended to run all relevant VLAN integration test cases in `tests/integration/vlan` directory to test your implementation. **Notes:** * The device configuration template (in Jinja2 format) should be stored in `netsim/templates/vlan/.j2` with **nos** being the value of **netlab_device_type** or **ansible_network_os** variable (see [Using Your Devices with Ansible Playbooks](../devices.md#using-your-device-with-ansible-playbooks) for more details). * Most of the data model attributes are optional. Use `if sth is defined`, `sth|default(value)` or `if 'sth' in ifdata` in your Jinja2 templates to check for presence of optional attributes. Try to be consistent ;) ```eval_rst .. contents:: Table of Contents :depth: 2 :local: :backlinks: none ``` ## VLAN Principles Data model: * VLANs are defined in node-level **vlans** dictionary. Use this dictionary to get VLAN ID (802.1q tag), bridge group ID, and VNI. * Interface VLAN parameters are specified in interface **vlan** dictionary. Do not use any other parameter (for example, **vlan_name**). * VLAN interfaces or routed VLAN subinterface are created as needed on platforms that support them. These interfaces have **virtual_interface** set to *True*; you can further classify them with the **type** attribute: * VLAN/SVI/BVI interface have **type** set to *svi* * Routed VLAN subinterfaces have **type** set to ‌*vlan_member* VLAN-related interfaces are included in the **node.interfaces** list and are thus best configured in *initial* configuration template[^FC]. The other VLAN-related parameters should be configured in `netsim/templates/vlan/.j2` template (or corresponding Ansible task list). [^FC]: You might have to include further VLAN-related configuration in that configuration template to make them work. ## Device Features You have to specify VLAN-related capabilities of your device in `devices..features.vlan` dictionary in `topology-defaults.yml`. You can set the following parameters: * **model** -- describes the way the device implements VLAN interfaces. Valid values are **router**, **switch** or **l3-switch**. See the introductory section of this document for more details. * **svi_interface_name** -- a template for the VLAN/SVI/BVI interface name. You can use `{vlan}` or `{bvi}` within this string to set the interface name based on VLAN ID or bridge group. * **subif_name** -- name of VLAN subinterfaces for **router** platforms or routed VLAN subinterface name for **l3-switch** platforms. Use `{ifname}` to get the parent interface name, `{subif_index}` to get subinterface ID[^SID], and `{vlan.access_id}` to get the VLAN tag[^SUBIF]. * **first_subif_id** -- subinterface ID of the first subinterface in case your platform uses unusual subinterface names. Defaults to 1. * **mixed_trunk** -- set to *True* when a platform supports a mix of bridged and routed VLANs on a trunk interface, regardless of the platform type -- applicable to **router** and **l3-switch** platforms. Set this flag only if your platform passes the `vlan-mixed-trunk.yml` integration test case. * **native_routed** -- set to *True* when a platform supports routed native VLAN on a routed interface -- applicable to **router** and **l3-switch** platforms. Set this flag only if your platform passes the `vlan-routed-native.yml` integration test case. [^SID]: A counter starting at **first_subif_id**. [^SUBIF]: You can also use any other attribute from the parent interface, or attributes from the current interface (like `vlan.access_id`) that are not defined on the parent interface. The following VLAN features have been defined for Cisco IOSv, Arista EOS, VyOS, Mikrotik RouterOS and Dell OS10: ``` devices: iosv: features: vlan: model: router svi_interface_name: BVI{bvi} subif_name: "{ifname}.{subif_index}" mixed_trunk: True native_routed: True eos: features: vlan: model: l3-switch svi_interface_name: Vlan{vlan} subif_name: "{ifname}.{subif_index}" srlinux: features: vlan: model: router svi_interface_name: "vlan{vlan}" subif_name: "{ifname}.{subif_index}" mixed_trunk: True vyos: features: vlan: model: l3-switch svi_interface_name: "br0.{vlan}" subif_name: "{ifname}.{vlan.access_id}" dellos10: features: vlan: model: switch svi_interface_name: vlan{vlan} routeros: features: vlan: model: router svi_interface_name: bridge{vlan} subif_name: "{ifname}-{vlan.access_id}" ``` **Notes:** * Cisco IOSv is a router and uses BVI (bridge group) interface and per-VLAN subinterfaces. * Arista EOS is a switch and uses VLAN interfaces. It also supports routed VLAN subinterfaces. * VyOS uses a VLAN-aware Linux bridge and creates VLAN interfaces by appending VLAN ID to bridge name - so it behaves like a switch. It also supports routed VLAN subinterfaces. * Dell OS10 is a switch and uses VLAN interfaces. As specified above, it does not support routed VLAN subinterfaces. * Mikrotik RouterOS is a router and uses bridge interface and per-VLAN subinterfaces. ## Interface Configuration It's easiest to create VLAN interfaces and subinterfaces in the `netsim/templates/initial/.j2` configuration template. Use **virtual_interface** attribute to skip configuration parameters that apply only to physical interfaces. For example, you cannot configure **switchport** or **mac-address** on an Arista EOS VLAN interface. Arista EOS interface configuration template: ``` {% for l in interfaces|default([]) %} interface {{ l.ifname }} no shutdown {% if l.virtual_interface is not defined %} no switchport mac-address {{ '52dc.cafe.%02d%02d' % ( id,l.ifindex ) }} {% endif %} {% endfor %} ``` Some operating systems (example: Cisco IOS) don't want to create VLAN or BVI interface unless you have already configured the corresponding VLAN or bridge-group. Cisco IOS initial configuration template thus checks for presence of **vlans** dictionary: ``` {% if vlans is defined %} {% include 'ios.vlan.j2' %} {% endif %} ``` Cisco IOS initial VLAN configuration creates the necessary bridge groups. VLANs are assigned to bridge groups during the VLAN configuration process. ``` bridge irb ! {% for vname,vdata in vlans.items() %} bridge {{ vdata.bridge_group }} protocol ieee bridge {{ vdata.bridge_group }} route ip ! {% endfor +%} ``` ## VLAN Database Configuration Use the **vlans** node dictionary to create a list of VLANs used by a device. Each VLAN within the **vlans** dictionary has at least these attributes * **id** -- 802.1q tag * **vni** -- VXLAN VNI * **bridge_group** -- VLAN-specific bridge group within the node. Switch-like devices usually need a VLAN database. The following template configures it on Arista EOS: ``` {% if vlans is defined %} {% for vname,vdata in vlans.items() %} vlan {{ vdata.id }} name {{ vname }} ! {% endfor +%} {% endif %} ``` ## Interface VLAN Parameters Based on the capabilities of your platform, you might have to configure: * VLAN access interfaces * VLAN trunks * Native VLAN * VLAN encapsulation on a VLAN- or routed subinterface[^RIF] [^RIF]: A routed (sub)interface is an interface that receives and routes IPv4/IPv6 packets sent to the device MAC address. It does not bridge other packets but drops them. You can use the following interface VLAN parameters to configure VLANs: * A (switched) trunk VLAN interface has **vlan.trunk_id** list that contains the list of VLANs on that trunk. A pure routed physical interface will not have a **vlan.trunk_id** list -- the VLANs terminated on the interface are configured as subinterfaces. * A trunk VLAN interface might have a native VLAN. Its 802.1q tag is specified in **vlan.access_id** attribute, its name in **vlan.native** attribute. The native VLAN 802.1q tag is also included in **vlan.trunk_id**. A routed interface will not have **vlan.native** or **vlan.access_id** attribute -- there is no need for extra configuration when all you want to do is to route untagged IP packets. * An access VLAN interface has **vlan.access_id** attribute that contains the 802.1 VLAN tag. You can also use **vlan.access** attribute to get VLAN name. A routed interface will not have **vlan.access** or **vlan.access_id** attribute because it's routing untagged IP packets. * A routed VLAN subinterface has VLAN 802.1q tag in **vlan.access_id** attribute. There is no **vlan.access** or **vlan.native** attribute. * Use **type** to differentiate between a VLAN access physical interface and a routed VLAN subinterface -- routed subinterfaces have **type** set to *vlan_member* ### Switch-Like Platforms Use the following logic to configure interface VLANs parameters on platforms that look like switches: * Is **vlan.trunk_id** defined? ➜ trunk interface * Set interface mode to trunk * Set list of allowed VLANs from **vlan.trunk_id** * If **vlan.native** is defined, set native VLAN from **vlan.access_id** * Else: Is **vlan.access_id** defined? ➜ access or routed subinterface * Is **type** equal to *vlan_member*? ➜ routed VLAN subinterface, set VLAN tag * Else: Access VLAN interface: * Set interface mode to access * Set access VLAN 802.1q tag from **vlan.access_id** The following template implements that logic for Arista EOS: ``` {% for ifdata in interfaces if ifdata.vlan is defined %} ! interface {{ ifdata.ifname }} {% if ifdata.vlan.trunk_id is defined %} switchport switchport mode trunk switchport trunk allowed vlan {{ ifdata.vlan.trunk_id|sort|join(",") }} {% if ifdata.vlan.native is defined %} switchport trunk native vlan {{ ifdata.vlan.access_id }} {% endif %} {% elif ifdata.vlan.access_id is defined %} {% if ifdata.type == 'vlan_member' %} encapsulation dot1q vlan {{ ifdata.vlan.access_id }} {% else %} switchport switchport access vlan {{ ifdata.vlan.access_id }} {% endif %} {% endif %} {% endfor +%} ``` Alternatively, you could check for: * **vlan.trunk_id** ➜ trunk VLAN interface * **vlan.access** ➜ access VLAN interface * **vlan.access_id** but no **vlan.access** ➜ routed subinterface * Anything else ➜ regular IP interface ### Router-Like Platforms Router-like platforms implement VLANs by connecting physical interfaces or VLAN subinterfaces to bridge domains (groups). VLAN subinterfaces are created for every VLAN in a VLAN trunk; interface **type** for VLAN subinterfaces is set to *vlan_member* regardless of whether the VLAN is routed or bridged. Configuring VLAN interface parameters on a router-like platform is even easier than doing it on a switch-like platform: * If the VLAN name is specified (**vlan.access** or **vlan.native**), find the **bridge_group** from the VLAN database. Configure bridge group on the interface to connect the VLAN subinterface or access interface to the VLAN-specific bridge group. * When configuring a VLAN subinterface (**type** == *vlan_member*), configure interface VLAN encapsulation using **vlan.access_id** parameter. The following template configures VLAN interface parameters on Cisco IOS: ``` {% for ifdata in interfaces if ifdata.vlan is defined %} ! interface {{ ifdata.ifname }} {% if ifdata.type == 'vlan_member' and ifdata.vlan.access_id is defined %} encapsulation dot1Q {{ ifdata.vlan.access_id }} {% endif %} {% if ifdata.vlan.access is defined %} bridge-group {{ vlans[ifdata.vlan.access].bridge_group }} {% endif %} {% if ifdata.vlan.native is defined %} bridge-group {{ vlans[ifdata.vlan.native].bridge_group }} {% endif %} {% endfor %} ```