DIY Linux Router Part 2: Interfaces, DHCP and VLAN

DIY Linux Router Part 2: Interfaces, DHCP and VLAN

The following is the second part of a multipart series describing how I build (software not hardware) my own Linux router from scratch, based on Debian 11.

The base system for my router is Debian 11. I'm a fan of systemd and of systemd-networkd (my Linux servers use it to configure their network interfaces) and wanted to see how much of the basic networking functions of a router I could configure with just systemd-networkd. Turns out, pretty much everything, the only exception being PPPOE.

Interfaces

Let's start by configuring the interfaces. I have four interfaces on my vault labeled WAN, LAN OPT1, and OPT2. The WAN port will be connected to my modem. The LAN port and OPT1 port will form a network bridge and the LAN port will be connected to my Switch and the OPT1 port will connect to the UniFi AP. OPT2 will be configured as a DHCP client, for when I need to put it behind my FRITZ!Box.

With systemd-networkd you normally have two files for each interface, both located inside /etc/systemd/network/. One .link file, which describes the physical interface and one .network file that configures the interface.

Network topology

Currently I have two networks, one for my own devices and one for my guests. My guests will only connect via Wi-Fi so it will only be available on the OPT1 interface connected to my UniFi AP. My own devices will connect via Wi-Fi and via ethernet, so my private network needs to be available on the LAN and OPT1 interface.

To achieve this, we need to employ two things: VLANs and Bridges. VLANs allow us to have multiple networks on the same port (OPT1) and a bridge is a software switch that allows us to connect multiple physical interfaces (LAN, OPT1) to the same network.

I decided on the follow IP ranges for my two networks:

  • Private: 192.168.144.0/24
  • Guests: 192.168.222.0/24

For now I decided to go with a IPv4 only network. This makes it easier to relate traffic to devices since it will always originate from the same IP address and reduces complexity. It is possible to configure IPv6 with systemd-network only and I will write about it at the end of this series.

WAN

The WAN port does not need to be configured, since the PPPOE daemon will create a separate ppp0 interfaces later and will be the only part the system using the plain WAN port, everything else will use the ppp0 interface. But to align the interface name with the label on the vault, I at least rename the port to wan.

[Match]
MACAddress=00:e0:67:27:67:f4
Type=ether

[Link]
Name=wan
/etc/systemd/network/10-wan.link

I also stop the interface from assigning itself an IPv6 link local address since we don't need it.

[Match]
Name=wan

[Network]
LinkLocalAddressing=no
/etc/systemd/network/10-wan.network

Bridge

To create a bridge with systemd-networkd you first create a virtual interface with a .netdev file and then connect the physical interfaces to this virtual interface.

The follow will create a virtual bridge interface named br0 and assign it an MAC address. For the MAC address I just used the physical address of my interfaces and changed the last character to an f.

[NetDev]
Name=br0
Kind=bridge
MACAddress=00:e0:67:27:67:ff
/etc/systemd/network/10-br0.netdev

We then name the LAN and OPT1 interface with the .link file and connect them to the bridge interface with the .network file. The physical interfaces also don't need a link local address, this is covered by the bridge itself.

[Match]
MACAddress=00:e0:67:27:67:f5
Type=ether

[Link]
Name=lan
/etc/systemd/network/10-lan.link
[Match]
Name=lan

[Network]
Bridge=br0
LinkLocalAddressing=no
/etc/systemd/network/10-lan.network
[Match]
MACAddress=00:e0:67:27:67:f6
Type=ether

[Link]
Name=opt1
/etc/systemd/network/10-opt1.link
[Match]
Name=opt1

[Network]
Bridge=br0
LinkLocalAddressing=no
/etc/systemd/network/10-opt1.network

Finally let's configure my chosen network and assign the first IP in the network to the router.

[Match]
Name=br0

[Network]
Address=192.168.144.1/24
/etc/systemd/network/10-br0.network

VLAN

To create a VLAN with systemd-networkd you again first create a virtual interface and then connect the physical interfaces to it.

The following creates a VLAN interface named vlan222 with the VLAN ID 222.

[NetDev]
Name=vlan222
Kind=vlan

[VLAN]
Id=222
/etc/systemd/network/10-vlan222.netdev

We then modify the OPT1 .network file and add the VLAN tagging to it.

[Match]
Name=opt1

[Network]
Bridge=br0
LinkLocalAddressing=no
VLAN=vlan222
/etc/systemd/network/10-opt1.network

Finally let's configure our chosen guest network and assign the first IP in the network to the router.

[Match]
Name=vlan222

[Network]
Address=192.168.222.1/24
/etc/systemd/network/10-vlan222.network

The interfaces will now operate like this:

  • A packet with a 192.168.222.0/24 as a target will leave the vlan222 interface through the physical OPT1 interface tagged with the VLAN ID 222. My UniFi AP will then send this to the guest Wi-Fi network (coming in a later blog post)
  • A packet with a 192.168.144.0/24 as a target will leave the br0 interface though either the LAN or OPT1 physical interfaces untagged.

OPT2

The OPT2 interface is a backup and configured as a DHCP client. I also marked it to not be required when determining whether the system is online.

[Match]
MACAddress=00:e0:67:27:67:f7
Type=ether

[Link]
Name=opt2
/etc/systemd/network/10-opt2.link
[Match]
Name=opt2

[Network]
DHCP=ipv4

[Link]
RequiredForOnline=no
/etc/systemd/network/10-opt2.network

DHCP Server

systemd-networkd includes a basic DHCP server. It's currently missing many features from other more powerful DHCP servers (Lease logging, Static leases [systemd 249+], and others) but I decided to still go with the included one. I don't really need these features, and this avoids installing additional software.

To activate the server, we modify the br0 and vlan222 .network files.

[Match]
Name=br0

[Network]
Address=192.168.144.1/24
DHCPServer=yes

[DHCPServer]
PoolOffset=20
PoolSize=150
DNS=192.168.144.1
/etc/systemd/network/10-br0.network
[Match]
Name=vlan222

[Network]
Address=192.168.222.1/24
DHCPServer=yes

[DHCPServer]
PoolOffset=20
PoolSize=150
DNS=192.168.222.1
/etc/systemd/network/10-vlan222.network

Both interfaces will know offer up to 150 IPv4 addresses to clients between 192,168.X.20 and 192.168.X.170. The router will also advertise itself as the DNS Server that we will configure in a later blog post.

After rebooting my interfaces now look like this:

Up next: Routing and PPPOE