One of my favorite features of Fedora 22 is systemd-networkd and all of the new features that came with it in recent systemd versions. The configuration files are easy to read, bridging is simple, and tunnels are resilient.
I’ve recently started using a small Linux server at home again as a network router and firewall. However, I used systemd-networkd this time and had some great results. Let’s get started!
Overview
Our example router in this example has two network interfaces:
- eth0: public internet connectivity
- eth1: private LAN (192.168.3.1/24)
We want machines on the private LAN to route their traffic through the router to the public internet via NAT. Also, we want clients on the LAN to get their IP addresses assigned automatically.
Network configuration
All of the systemd-networkd configuration files live within /etc/systemd/network and we need to create that directory:
mkdir /etc/systemd/network
We need to write a network configuration file for our public interface that systemd-networkd can read. Open up /etc/systemd/network/eth0.network and write these lines:
[Match] Name=eth0 [Network] Address=PUBLIC_IP_ADDRESS/CIDR Gateway=GATEWAY DNS=8.8.8.8 DNS=8.8.4.4 IPForward=yes
If we break this configuration file down, we’re telling systemd-networkd to apply this configuration to any devices that are called eth0. Also, we’re specifying a public IP address and CIDR mask (like /24 or /22) so that the interface can be configured. The gateway address will be added to the routing table. We’ve also provided DNS servers to use with systemd-resolved (more on that later).
I added IPForward=yes so that systemd-networkd will automatically enable forwarding for the interface via sysctl. (That always seems to be the step I forget when I build a Linux router.)
Let’s do the same for our LAN interface. Create this configuration file and store it as /etc/systemd/network/eth1.network:
[Match] Name=eth1 [Network] Address=192.168.3.1/24 IPForward=yes
We don’t need to specify a gateway address here because this interface will be the gateway for the LAN.
Prepare the services
If we’re planning to use systemd-networkd, we need to ensure that it runs instead of traditional network scripts or NetworkManager:
systemctl disable network systemctl disable NetworkManager systemctl enable systemd-networkd
Also, let’s be sure to use systemd-resolved to handle our /etc/resolv.conf:
systemctl enable systemd-resolved systemctl start systemd-resolved rm -f /etc/resolv.conf ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
Reboot
We’re now set to reboot! It’s possible to bring up systemd-networkd without rebooting but I’d rather verify with a reboot now than get goosed with a broken network after a reboot later.
Once your router is back up, run networkctl and verify that you have routable in the output for both interfaces:
[root@router ~]# networkctl IDX LINK TYPE OPERATIONAL SETUP 1 lo loopback carrier unmanaged 2 eth0 ether routable configured 3 eth1 ether routable configured
DHCP
Now that both network interfaces are online, we need something to tell our clients about the IP configuration they should be using. There are plenty of good options here, but I prefer dnsmasq. It has served me well over the years and it provides some handy features along with DHCP, such as DNS caching, TFTP and IPv6 router announcements.
Let’s install dnsmasq and enable it at boot:
dnf -y install dnsmasq systemctl enable dnsmasq
Open /etc/dnsmasq.conf in your favorite text editor and edit a few lines:
- Uncomment
dhcp-authoritative - This tells dnsmasq that it’s the exclusive DHCP server on the network and that it should answer all requests
- Uncomment
interface=and addeth1on the end (should look likeinterface=eth1when you’re done) - Most ISP’s filter DHCP replies on their public networks, but we don’t want to take chances here. We need to restrict DHCP to our public interface only.
- Look for the
dhcp-rangeline and change it todhcp-range=192.168.3.50,192.168.3.150,12h - We’re giving clients 12 hour leases on 192.168.3.0/24
Save the file and start dnsmasq:
systemctl start dnsmasq
Firewall
We’re almost done! Now it’s time to tell iptables to masquerade any packets from our LAN to the internet. But wait, it’s 2015 and we have tools like firewall-cmd to do that for us in Fedora.
Let’s enable masquerading, allow DNS, and allow DHCP traffic. We can then make the state permanent:
firewall-cmd --add-masquerade firewall-cmd --add-service=dns --add-service=dhcp firewall-cmd --runtime-to-permanent
Testing
Put a client machine on your LAN network and you should be able to ping some public sites from the client:
[root@client ~]# ping -c 4 icanhazip.com PING icanhazip.com (104.238.141.75) 56(84) bytes of data. 64 bytes from lax.icanhazip.com (104.238.141.75): icmp_seq=1 ttl=52 time=69.8 ms 64 bytes from lax.icanhazip.com (104.238.141.75): icmp_seq=2 ttl=52 time=69.7 ms 64 bytes from lax.icanhazip.com (104.238.141.75): icmp_seq=3 ttl=52 time=69.6 ms 64 bytes from lax.icanhazip.com (104.238.141.75): icmp_seq=4 ttl=52 time=69.7 ms --- icanhazip.com ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3005ms rtt min/avg/max/mdev = 69.659/69.758/69.874/0.203 ms
Extras
If you need to adjust your network configuration, just run systemctl restart systemd-networkd afterwards. I’ve found that it’s quite intelligent about the network devices and it won’t reconfigure anything that hasn’t changed.
The networkctl command is very powerful. Check out the status and lldp functions to get more information about your network devices and the networks they’re connected to.
When something goes wrong, look in your systemd journal:
[root@router ~]# journalctl -u systemd-networkd -- Logs begin at Fri 2015-07-31 01:22:38 UTC, end at Fri 2015-07-31 02:11:24 UTC. -- Jul 31 01:46:14 router systemd[1]: Starting Network Service... Jul 31 01:46:14 router systemd-networkd[286]: Enumeration completed Jul 31 01:46:14 router systemd[1]: Started Network Service. Jul 31 01:46:15 router systemd-networkd[286]: eth1 : link configured Jul 31 01:46:15 router systemd-networkd[286]: eth0 : gained carrier Jul 31 01:46:15 router systemd-networkd[286]: eth0 : link configured Jul 31 01:46:16 router systemd-networkd[286]: eth1 : gained carrier
August 25, 2015 at 11:37
Great article! I think that Fedora router will be my next project.
August 26, 2015 at 21:08
Thanks, Kostic! Making a Linux router is fun. π
February 18, 2016 at 17:32
Major Hayden, I ran through your procedures and it went flawless. Great article. However, I did notice that once I ran through your steps (I used 192.168.1.1 as my test interal nic), I could not change that IP once I took the router to its operational location. If I change that IP to, say 192.168.1.233, it no longer NATs for the client computers. Any thought?
August 25, 2015 at 17:47
Nice guide, can this be extended to support multi ISP’s ?
and have some balancing and fail-over options ?
Thanks,
August 26, 2015 at 21:06
Rabin — You could do multiple uplinks via some scripts. For example, you could try pinging the gateway addresses on both links and run scripts when one of them stops pinging for a certain amount of time. I’m not aware of anything built into systemd-networkd to do that, though.
September 3, 2015 at 13:09
Forgot to close HTML tag in previous reply:
systemd.netdevhas it listed under “Supported kinds of virtual network devices” and I believe it is just what he needs.
August 26, 2015 at 00:45
How did you get a network interface with the old naming style, i.e. eth0? Does it persist across reboots?
August 26, 2015 at 21:07
Anon — In this particular case, I was using a virtual machine. That’s why the network devices showed up as eth0/eth1. To learn more about predictable network names, read my post on that subject:
https://major.io/2015/08/21/understanding-systemds-predictable-network-device-names/
August 26, 2015 at 06:58
Great write up! It’d be awesome to see a follow up with multiple ISP links (having one internal network load balanced across multiple WANs, or multiple internal networks using PBR between multiple providers).
Excellent primer on configuring a basic home router and firewall!
August 26, 2015 at 21:07
Thanks, Steven! I’ll add that to my list. π
August 26, 2015 at 08:50
Bookmarked!
Thanks, very helpful
August 26, 2015 at 21:08
You’re welcome, Sonny!
August 26, 2015 at 16:38
This would be su interesting project with the Banana Pl router. Are VLANs also supported here?
August 26, 2015 at 21:09
Roger — You can do VLAN’s and other complicated network stuff, like bonded interfaces and vxlan. I wrote a post on this topic here:
https://major.io/2015/08/21/using-systemd-networkd-with-bonding-on-rackspaces-onmetal-servers/
August 26, 2015 at 21:33
A user over on the G+ posting for this post asked an interesting question too — is there a webGUI availble to control systemd-networkd?
https://plus.google.com/+fedora/posts/7mkPcgj6UPS
August 26, 2015 at 21:38
I’m not aware of any GUI’s of any sort for systemd-networkd. There’s always room for one! π
August 30, 2015 at 09:29
Great article, thanks! I’ve tinkered with pfSense, IPFire and others in the past and am currently speccing an AM1 (Kabini) build as a firewall/router appliance for our home network (300Mbps WAN). Judging by your article setting up a router on Fedora is now even easier than setting up pfSense with a GUI! It would be fairly trivial to then set up openvpn and iptables to also have the box function as a VPN gateway (with network split across LAN interfaces if necessary). Nice! Thanks for giving me something else to tinker with. π
September 23, 2015 at 03:12
Nice article. I really like how Fedora Magazine adds useful tutorials alongside news. Please keep them coming! π
November 7, 2015 at 22:10
Great article, I have just tried to to follow this on the newly released clean install of fc23.
I however run into an issue where, /etc/resolv.conf pointing to /run/systemd/resolve/resolv.conf would create a permission issue for dnsmasq.
Many-thanks.
It would also be great to have a guide on how to do IPv6 routing in the full systemd world.
November 9, 2015 at 09:00
Hey Cameron,
There’s a problem with the latest SELinux policies and they don’t allow dnsmasq to read /etc/resolv.conf (which is linked to /run/systemd/resolve/resolv.conf). I’m planning to get a bug open today to get it fixed.
Major
November 9, 2015 at 14:54
I’m seeing this too. It seems dnsmasq wants to write to /etc/resolv.conf. It also wants to set an inotify which may take execute permissions. I’m not sure of that. This is FC 22.
November 9, 2015 at 14:56
If I start dnsmasq from the command prompt: dnsmasq -q -d
Then it works. I’ve confirmed by watching a DHCPREQUEST work.
February 20, 2016 at 10:14
Check your /lib/systemd/system/dnsmasq.service file and change
After=network.target
to:
After=systemd-resolved.service network.target
It helped in my case.
If works – perhaps main guide could be updated to include this change?
November 25, 2015 at 08:00
Hi and thanks for the great Post.
Anyway, I ran into an issue here. After setting up the services and reboot the machine, theres only an ems3 interface and I cant see eth0-eth2 anywhere. I am running F23 server in a virtual machine.
Do you have any advice on that one?
Thanks,
Tony
November 25, 2015 at 12:09
@TonyBoston: You’ll want to add another network device to the virtual machine using the manager, so you can experiment with two network interfaces. The naming is due to the virtual hardware being detected. You should be able to use the same steps and substitute the names provided on your VM.
December 1, 2015 at 16:55
You might need to tweak your startup order. I added to dnsmasq.service:
[Unit]
After=network.target
So far I haven’t had any more problems with dnsmasq starting up before the resolve.conf file is written.
February 5, 2016 at 07:03
Thanks for a great write up. I used it for an Arch Wifi Router project and your systemd-networkd instructions are more accurate than Arch’s own Router and Software Access Point documentation. I even setup webmin for remote administration.
AWESOME
February 6, 2016 at 16:12
For those of you having trouble with the resolve.conf file, I found a solution that works great.
Open up your /etc/dnsmasq.conf file, and file and find the following line:
#server=/localnet/192.168.0.1
Below that line, add the following values, of course adjusting the IP addresses to your needs. I will be using google dns.
server=8.8.8.8
server=8.8.4.4
Simply save the file, and reboot your router, and you should not have any troubles. I’ve left my resolve.conf file with those settings as well, but in the event there are issues with dnsmasq looking for that file, you’ve now hard coded the dns entries in the dnsmasq daemon.
Cheers
February 9, 2016 at 20:04
Any ideas where you’re suppose to stash policy routing rules that you’d typically create with ip rule add … in systemd and/or systemd-networkd world?
February 10, 2016 at 18:11
I have a modem configured in bridged mode by my ISP, how can I configure fedora to use my public static IP?
April 3, 2016 at 00:18
Is there a way to do this while having DHCP on a different server?