Implementing VLANs with systemd-networkd on an active physical interface
Published: 2022-12-23Setting 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
.netdev
file. - The untagged network interface gets matched to the VLANs in a single 'base'
.network
file. - 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'
.network
file. - Actual configuration of the tagged interfaces is done through a higher level
.network
file (again one per VLAN interface). - Number your
.netdev
and.network
files 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
The .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. eth0.100
here.
# 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 .network
files 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' lounge
and 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 [Network]
section:
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 [Address]
and [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
And with ip
:
# 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
The ip
output is truncated, with functional IPv6 you will have routable IPv6 addresses as well.