Arch Linux-based Home Router, Part II (systemd and sysctl/kernel configuration)

This is the second post in my Arch Linux-based Home Router series.  This article covers my systemd-networkd and sysctl/kernel configuration.

systemd-networkd configuration

Because systemd-networkd is included in systemd, I went with it instead of another network manager.  systemd-networkd is part of the systemd package (which itself is part of the base package), so you will have it installed even if you elect to use another network manager.  If you decide to use another network manager, you will need to adapt these instructions to it (I'm slowly moving away from other network managers on all of my Arch devices).  Be sure to heed the warning from the Arch wiki about running multiple network managers!  Only one should be active at a time, or at least should not manage the same connections if you decide to use multiple.  This sounds like a lot more work than it's worth, so I wouldn't do that.

First thing to do (you can do this during the Arch installation) is determine your network interface names.  The easiest way is to run the ip link command, which will show all of the network interfaces you have loaded:

# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 00:30:18:c5:b4:d5 brd ff:ff:ff:ff:ff:ff
3: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 00:30:18:c5:b4:d4 brd ff:ff:ff:ff:ff:ff
4: wlp2s0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 74:f0:6d:39:36:34 brd ff:ff:ff:ff:ff:ff
5: enp4s0.66@enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 00:30:18:c5:b4:d4 brd ff:ff:ff:ff:ff:ff
6: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/none

These are the new style interface names which should be stable.  en* designates hardware Ethernet devices, whereas wl* indicates wireless devices.  The p indicates that these are PCIe devices (which can be seen using the lspci command).  wlp2s0 is the Atheros 802.11n (mini-PCIe) card that I still have installed.  I don't use it anymore, but in Debian I used the Hostapd daemon to let the router act as a WiFi Access Point (AP).

You can see by the output that I have two physical Ethernet devices, enp3s0 and enp4s0.  They correspond to the physical PCIe bus connections (they're actually integrated with the NUC main board).  Yours might be different, but they should remain stable across reboots.  enp4s0.66 is a virtual interface, corresponding to my IoT VLAN which uses the VLAN tag 66, but is otherwise physically serviced by the enp4s0 Ethernet device.

I chose enp3s0 as my WAN interface.  To configure it, I created the file /etc/systemd/network/20-WAN.network:

# vim /etc/systemd/network/20-WAN.network
[Match]
Name=enp3s0

[Network]
DHCP=yes

[DHCPv4]
ClientIdentifier=duid
IAID=1987201769
SendOption=50:ipv4address:redacted

This configures the WAN interface on enp3s0, and it gets its IP address from my ISP's DHCP (Dynamic Host Control Protocol) service.  Under the [Network] section, I have DHCP=yes, which enables receiving both IPv4 and IPv6 addresses from my ISP.  The [DHCPv4] section has parameters that attempt to receive the same IPv4 address from my ISP.  The ClientIdentifier and IAID options are likely unnecessary (as they didn't work for receiving the same IP address by themselves).  The SendOption parameter sends DHCPv4 Option 50, and the redacted field has my desired IPv4 address.  In Debian this was unnecessary, as it automatically requested the same address from the ISP DHCP server.  The main thing this avoids is getting a new IP address with every reboot.  I got an IP address that was aesthetically pleasing to me, so I replaced redacted with that IP address.  You'll want to comment out this option the first time you set it up, as you may not know what subnets are available to you.  You can do the same for DHCPv6, but I'm not sure if Option 50 is the correct option, and IPv6 addresses are considerably more complicated.  If you're interested in IPv6 (it's possible your ISP no longer serves IPv4 addresses), you can read the IPv6 Arch wiki article.  

Once you have an IP address from your ISP, you can use the ip addr show dev enp3s0 command (replacing enp3s0 with your actual Ethernet interface name) to see what public IP address you receive.  Compare that closely with the private address spaces (see below), there's a possibility your ISP does not give you a public IP address out of the box.  That can make it difficult for some applications, as you have what's called a "double-NAT" situation.  You may need to configure your modem for "bridge" mode in this case, your ISP should have details on how to do this (they may not call it "bridge" mode, either).

I use IPv4 throughout the rest of this series.  The main difference between IPv4 and IPv6 is the address size.  IPv4 has 32-bit addresses, whereas IPv6 has 128-bit addresses.  I do get IPv6 addresses from my ISP, and they do (somehow) propagate to other devices on my network.  But I don't really use them that much.  I have a long way to go before I fully understand IPv6, and I've only just begun playing with it.

A note about Public versus Private IP address ranges

IPv4 has a few private address spaces, anything not included below are either public IP addresses routable on the global Internet, or are reserved for specific other uses by the IETF (Internet Engineering Task Force).  There are three basic private network prefixes (otherwise known as netmasks, these are specified in RFC-1918):

  • 10.x.y.z/8
  • 172.a.b.c/16
  • 192.168.m.n/24

The three above (in CIDR notation) are classically refered to as Class A (10.*/8), Class B (172.*/16), or Class C (192.168.*/24).  These have 8-, 16-, or 24-bit prefixes/netmasks, which indicate how many left-most bits are in the network address (the rest are the host address). IPv4 evolved to have so-called Classless address spaces, so the number of bits in the prefix/netmask can be arbitrary (between 1 and 32, but practically they are between 8 and 31 for private networks).  The more bits in the netmask, the fewer discrete IP addresses in the network/subnet.  The notation given above is called dotted decimal notation with CIDR (the /I where I is a decimal number between 1 and 32 is the number of bits in the netmask).  You may also see the netmask in dotted decimal notation (e.g. /24 is equivalent to 255.255.255.0).

The lowercase letters in the above are 8-bit fields, and in general can be between 0 and 255 (the highest decimal value for an unsigned 8-bit integer).  This is true for most of these lowercase letters, except a, which can only be between decimal 16 and 32 (otherwise it's not a private network).  

You can choose any private network you want for your LAN interface, with any subnet mask.  The larger your subnet mask (prefix or netmask), the fewer available IP addresses you will have on your network.  I chose netmasks of /24 for simplicity, which in dotted decimal notation is 255.255.255.0(which allows for 254 discrete IP addresses).  You can make a larger netmask if you intend to have fewer devices, just know that every bit you add to the right of the netmask cuts the number of discrete IP addresses on the network in half.  Thus a CIDR subnet of \25 is equivalent to 255.255.255.128, and only has 126 usable IP addresses.  Conversely, a smaller netmask allows for more IP addresses per network, so \23 / 255.255.254.0 allows for 510 usable IP addresses.

I arbitrarily chose the subnet 10.20.30.0/24 as my LAN subnet (as an homage to my grandfather who was born on 1930-10-20, or 10/20/30 in the US two-digit year format).  I chose this because it was easy for me to remember.  I chose 172.19.87.0/24 for my IoT subnet (as an homage to my wife who was born in 1987).  You can use any mnemonic you choose to remember your subnets.  You will commonly see commercial routers use something like 192.168.1.0/24 for their subnet, and I'm sure quite a few households neither know nor care about this.  Note that in all of these 24-bit subnets, the last/fourth octet is 0.  This is the network address, no host on these networks can have that IP address (by design of IPv4).  Also, the last octet cannot be 255 on these networks, as that is the broadcast address for the network.  

If you choose a different prefix value (e.g., not /24), it can become complicated to figure out what the network and broadcast addresses are.  For this purpose I use a tool known as sipcalc.  When given an IP address and a netmask (in CIDR or dotted decimal notation), it will print all of the relevant parameters of that IP address so you don't have to guess.  I use it at work a lot because many of my customers don't give us /24 blocks for our on-premises equipment, and some of our solutions architects only give the CIDR notation for the netmask in our internal documentation.

This is only an overview of IPv4 networking.  See the Wikipedia article on IPv4 for a more thorough introduction.  I have only begun investigating IPv6 on my network, so I don't understand it enough to write about it here.  Many of the concepts are similar (the prefix and host addresses directly transfer conceptually), but I don't know how or why the hosts on my LAN or IoT subnets have IPv6 addresses which they get from my router when it receives an IPv6 address from my ISP.

systemd-networkd (LAN configuration)

Now back to configuring the network with systemd-networkd.  As with the WAN interface, I created a separate .network file for enp4s0, /etc/systemd/network/21-ceti.network:

# vim /etc/systemd/network/21-ceti.network
[Match]
Name=enp4s0

[Network]
Address=10.20.30.254/24
DNS=10.20.30.254
VLAN=enp4s0.66

I chose the IP address of 10.20.30.254 arbitrarily for the gateway.  On off the shelf routers you'll quite commonly see the router/gateway have the lowest address in the network (e.g. for this network the lowest address is 10.20.30.1/24).  This is known as a static assignment because it does not receive its IPv4 configuration from any other device on the network (i.e., DHCP or BOOTP).  Since this is my router, it will act as a gateway for the rest of the LAN.

systemd-networkd (IoT configuration)

Since my NUC only has two physical Ethernet ports, I don't have a third interface with which to create a DMZ to keep these devices from accessing my LAN.  This is where virtual interfaces and VLANs come in.  VLANs, or Virtual LANs, are a layer-2 (data link layer) protocol that allows the network architect or administrator to segregate networks without separating them physically (as with physical network ports).  A VLAN-capable Ethernet switch can "tag" packets coming into and going out of specific network ports, and can ensure they don't route to other VLANs.

Here is my /etc/systemd/network/22-iot.netdev file, which defines the virtual enp4s0.66 interface tagged with VLAN ID 66:

# vim /etc/systemd/network/22-iot.netdev
[NetDev]
Name=enp4s0.66
Kind=vlan

[VLAN]
Id=66

Next, I created the /etc/systemd/network/22-iot.network file, which sets up the network on this router for VLAN 66:

# vim /etc/systemd/network/22-iot.network
[Match]
Name=enp4s0.66

[Network]
Address=172.19.87.254/24

[RoutingPolicyRule]
From=172.19.87.0/24
Table=iot

The [RoutingPolicyRule] ensures the IoT network 172.19.87.0/24 is set up on its own routing table, separate from the main routing table (see man ip-route for details).  

I have Ubiquiti UniFi switches, which are highly configurable.  I'm able to set up the VLANs appropriately.  You'll need to consult your Ethernet switches' manuals to see if they're VLAN capable.  Configuring the Ethernet switches is beyond the scope of this series.

systemd-resolved

systemd-resolved is installed by default, to help with local name resolution.  It doesn't have much bearing on providing DNS services to the local network, and /etc/resolv.conf on my router merely contains the local LAN address of my router, and my local domain name as a search parameter.  Thus even my router's local DNS queries are serviced by the local BIND service (more on that in a later article).  However, systemd-resolved is enabled and running on my router, so you may want to configure it.  The Arch wiki article linked above covers the topic very well.

Kernel configuration

One of the core features of any network router is forwarding packets from one network to another.  This is done by the net subsystem of the kernel, and is configured by the Sysctl facility.  You can set up forwarding to survive a reboot (anything done with the sysctl command as root, or writing parameters to files in the virtual /proc filesystem are runtime-only, they do not survive a reboot) by editing the following file in /etc/sysctl.d/20-ipv4_forwarding.conf:

# vim /etc/sysctl.d/20-ipv4_forwarding.conf
net.ipv4.ip_forward=1

You can set this parameter at runtime without rebooting by issuing one of the following commands:

# sysctl net.ipv4.ip_forward=1
# ## OR ##
# echo 1 > /proc/sys/net/ipv4/ip_forward

Failure to do this section may make your router nonfunctional!

NEXT STEPS

The following articles continue this series on setting up an Arch Linux-based Home Router:

  1. Arch Linux-based Home Router, Part I
  2. Arch Linux-based Home Router, Part II (systemd-networkd and sysctl/kernel) (This article)
  3. Arch Linux-based Home Router, Part III (firewalld configuration)
  4. Arch Linux-based Home Router, Part IV (dhcpd configuration)
  5. Arch Linux-based Home Router, Part V (bind)
  6. Arch Linux-based Home Router, Part VI (DDNS)