Implementing VLANs with systemd-networkd on an active physical interfacePublished: 2022-12-23
Setting up a VLAN with networkd is unfortunately not as straightforward as with Debian's
ifupdown framework, where you can just append the VLAN to the interface name. Networkd has a more complex approach that boils down to this:
- Each single VLAN is defined in its own
- The untagged network interface gets matched to the VLANs in a single 'base'
- If you are only using tagged interfaces, you do not need to specify any IP settings (ie no DHCP or static lease, DNS or gateway) in that 'base'
- Actual configuration of the tagged interfaces is done through a higher level
.networkfile (again one per VLAN interface).
- Number your
.networkfiles hierarchically, so they get parsed in the right order.
File naming can be whatever you like to make things clearer - especially the fact that systemd needs at least two .network files to set this up can be confusing, so you need to be able to tell them apart easily.
Note: I had to load the
8021q module manually first. You can automate this by inserting the module in
/etc/modules, so after a reboot it gets autoloaded.
# echo 8021q >> /etc/modules
Netdev file: basic VLAN definition
.netdev file needs two sections:
NetDev to define the name and network device type, and
VLAN to define the VLAN ID. Note you can name the VLAN device in the traditional
$INTERFACE.$VLAN way, but you do not need to. In the example below I have specified a simple human readable name that indicates what the VLAN will be used for. If you feel more comfortable, just use e.g.
# cat /etc/systemd/network/10-guest_vlan.netdev [NetDev] Name=guest Kind=vlan [VLAN] Id=100
Create a file for every single VLAN you want to set up. Make sure to number them lower than any
.networkfiles to follow - the
.netdev files may share the same number, since they are self-contained.
Lower level network file: physical interface configuration
The lower level network file has two purposes: link all VLANs by name to the physical interface, and (if desired) configure the untagged physical interface. While confusing, that's how systemd seems to handle it. In the example below the physical, untagged interface gets set up with static IPv4 and DHCPv6. The 'ghost'
vip VLANs are added as an example; you simply list them all one after another if you are setting up multiple.
Note the higher number so it gets parsed after the .netdev files.
# cat /etc/systemd/network/11-lan_untagged.network [Match] Name=eth0 Type=ether [Network] Description=Unconfigured physical Ethernet device VLAN=guest VLAN=lounge VLAN=vip DHCP=ipv6 # IPv4 untagged Address=192.168.1.10/24 Gateway=192.168.1.1 DNS=192.168.1.3
If you leave the lower physical interface unconfigured, drop everything from the
[Network] section after the
VLAN= definitions. Make sure any
[Address] sections are removed too, and add the following to the
LinkLocalAddressing=no LLDP=no EmitLLDP=no IPv6AcceptRA=no IPv6SendRA=no
This will prevent the untagged interface from setting up a link-local address or acquiring an IPv6 lease through DHCPv6.
Upper level network file: tagged VLAN configuration
Just like the .netdev file, you set up each tagged VLAN in a separate .network file. The example below sets up the guest network. Note that, when specifying multiple addresses, you need separate
[Route] sections for each, as you can see below.
# cat /etc/systemd/network/12-guest_vlan.network [Match] Name=guest Type=vlan [Network] Description=Guest network DNS DHCP=ipv6 DNS=192.168.100.3 # Link-local is necessary for IPv6 LinkLocalAddressing=ipv6 LLDP=false EmitLLDP=false IPv6AcceptRA=true IPv6SendRA=false [Address] Address=192.168.100.10/24 [Address] Address=dead:beef:badc:ab1e::10/64 [Route] Gateway=192.168.100.1 [IPv6AcceptRA] DHCPv6Client=true
Now that everything is set up, reload
networkd and inspect the interfaces:
$ sudo networkctl reload $ sudo networkctl list IDX LINK TYPE OPERATIONAL SETUP 1 lo loopback carrier unmanaged 2 eth0 ether routable configured 3 guest vlan routable configured
That's looking good - our guest network interface is up. Now check whether the VLANs exist:
$ cat /proc/net/vlan/config VLAN Dev name | VLAN ID Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD guest | 100 | eth0
# ip a s guest 3: guest@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff inet 192.168.100.10/24 brd 192.168.100.255 scope global guest valid_lft forever preferred_lft forever inet6 dead:beef:badc:ab1e::10/64 scope global mngtmpaddr noprefixroute valid_lft forever preferred_lft forever
ip output is truncated, with functional IPv6 you will have routable IPv6 addresses as well.