Nftables Router Tutorial

From Alteeve Wiki
Revision as of 04:44, 28 March 2024 by Digimer (talk | contribs)
Jump to navigation Jump to search

 AN!Wiki :: How To :: Nftables Router Tutorial

Warning: This is incomplete and untrustworthy! Do not expect anything to be useful or accurate before this warning is removed.

This tutorial is meant to show how to use nftables to build a router suitable for a home or boat.

This tutorial is written for RHEL 9 or distros based on it, like AlmaLinux 9 and Rocky Linux 9.

Setup

Before we configure nftables, we need to setup the machine first.

Enabling ipv4 Forwarding

Make sure that ip_forward is enabled in the kernel.

sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 0

This shows that it's disabled. To enable it, and make sure it's set when the system reboots, edit (or create) the file "/etc/sysctl.d/99-custom.conf" and add (or update) the lines;

# Added for router function support
net.ipv4.conf.all.forwarding = 1

Now reload the config;

sysctl --system
* Applying /usr/lib/sysctl.d/10-default-yama-scope.conf ...
* Applying /usr/lib/sysctl.d/50-coredump.conf ...
* Applying /usr/lib/sysctl.d/50-default.conf ...
* Applying /usr/lib/sysctl.d/50-libkcapi-optmem_max.conf ...
* Applying /usr/lib/sysctl.d/50-pid-max.conf ...
* Applying /usr/lib/sysctl.d/50-redhat.conf ...
* Applying /etc/sysctl.d/99-custom.conf ...
* Applying /etc/sysctl.d/99-sysctl.conf ...
* Applying /etc/sysctl.conf ...
kernel.yama.ptrace_scope = 0
kernel.core_pattern = |/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h
...<snip>...
net.ipv4.conf.lo.rp_filter = 1
net.ipv4.conf.wlp58s0.rp_filter = 1
net.ipv4.conf.all.forwarding = 1

Now we can verify that forwarding is enabled;

sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 1

Now ip_forward is enabled!

Disable firewalld

The nftables tool is an alternative to firewalld, so we need to disable it.

systemctl disable --now firewalld.service
Removed "/etc/systemd/system/multi-user.target.wants/firewalld.service".
Removed "/etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service".

Configuring nftables

Note: We're using the script name alteeve.nft, but you can use whatever you like.

We're going to create an nft executable script to configure our router. We'll do this in the file /etc/nftables/alteeve.nft.

touch /etc/nftables/alteeve.nft
chmod 755 /etc/nftables/alteeve.nft 
chown root:root /etc/nftables/alteeve.nft
ls -lah /etc/nftables/alteeve.nft
-rwxr-xr-x. 1 root root 0 Mar 27 21:59 /etc/nftables/alteeve.nft

This empty file is now executable, so lets write our script.

TODO: Explain all this better
#!/usr/sbin/nft -f
# 
# Written by Madison Kelly - Mar. 27, 2024
# - Alteeve's Niche!
# - mkelly@alteeve.com
# - Released under the GPL v3
#
### Setup variables.
## Network devices
# Wired Internet interface (ISP router connection)
define IF_INET = eno1
# Wireless Internet interface (tethering cellphones, etc)
define IF_WLAN = wlp58s0 
# Network connecting to the Internet-Facing Network (as used in Anvil! systems,
# more generally, the main network with most devices)
define IF_IFN1 = enp0s20f0u1
# Network connecting to the Back-Channel Network (as used in the Anvil! system,
# more generally, the network with infrastructure / secure devices)
# NOTE: Not currently used
#define IF_BCN1 = 

# Define our networks. For now, there's only one.
define NET_IFN1 = 10.255.0.0/16

define DNS_SERVERS = { 8.8.8.8, 8.8.4.4 }

flush ruleset

### NOTES:
## Address families;
# ip:     Matches only IPv4 packets. This is the default if you do not specify
#         an address family.
# ip6:    Matches only IPv6 packets.
# inet:   Matches both IPv4 and IPv6 packets.
# arp:    Matches IPv4 address resolution protocol (ARP) packets.
# bridge: Matches packets that pass through a bridge device.
# netdev: Matches packets from ingress.

## Hooks;
#

## Chain Types
# filter: Standard chain type
# - All address families 
# - All hooks 
# nat:    Chains of this type perform native address translation based on 
#         connection tracking entries. Only the first packet traverses this 
#         chain type.
# - AFs:   ip, ip6, and inet
# - Hooks: prerouting, input, output, and postrouting
# route:  Accepted packets that traverse this chain type cause a new route 
#         lookup if relevant parts of the IP header have changed.
# - AFs:   ip and ip6
# - Hooks: output

## Chain Priorities
# +----------+----------+------------------+-------------+
# | Textual  | Numberic | Address Families | Hooks       |
# |  Value   |  Value   |                  |             |
# +----------+----------+------------------+-------------+
# | raw      | -300     | ip, ip6, inet    | all         |
# +----------+----------+------------------+-------------+
# | mangle   | -150     | ip, ip6, inet    | all         |
# +----------+----------+------------------+-------------+
# | dstnat   | -100     | ip, ip6, inet    | prerouting  |
# |          | -300     | bridge           | prerouting  |
# +----------+----------+------------------+-------------+
# | filter   |    0     | ip, ip6, inet,   | all         |
# |          |          | arp, netdev      |             |
# |          | -200     | bridge           | all         |
# +----------+----------+------------------+-------------+
# | security |   50     | ip, ip6, inet    | all         |
# +----------+----------+------------------+-------------+
# | srcnat   |  100     | ip, ip6, inet    | postrouting |
# |          |  300     | bridge           | postrouting |
# +----------+----------+------------------+-------------+
# | out      |  100     |  bridge          | output      |
# +----------+----------+------------------+-------------+

## Chain Policies
# accept (default)
# drop  

## table format example;
# table <table_address_family> <table_name> {
#   chain <chain_name> {
#     type <type> hook <hook> priority <priority> ; policy <policy> ;
#       <rule>
#   }
# }

# Create the 'global' table for all packets (use 'ip' for IPv4 only, or 'ip6' 
# for IPv6 only).
table inet global {
	# Chain to manage connections coming into the router from the outside
	# world
	chain inbound_wan {
		# Allow incoming pings, but only up to 5 pings per second.
		icmp type echo-request limit rate 5/second accept
		
		# Allow inbound ssh connections. If you want to limit this, add
		# 'ip saddr <ip>...'. For now, accept from anywhere, so we can
		# remote in while traveling (though really we should have a VPN
		# for that...).
		#ip protocol . th dport ssh accept
	}
	# Chain to manage connections coming in from the IFN 1.
	chain inbound_ifn1 {
		icmp type echo-request limit rate 5/second accept
		
		### TODO: Explain '.' and 'th'
		# Allow DNS, DHCP, and SSH
		ip protocol . th dport vmap { tcp . 22 : accept, udp . 53 : accept, tcp . 53 : accept, udp . 67 : accept}
	}
	# Chain to manage other inbound
	chain inbound {
		type filter hook input priority 0; policy drop;
		
		# Allow traffic from established and related packets, drop invalid
	        ct state vmap { established : accept, related : accept, invalid : drop }

		# allow loopback traffic, anything else jump to chain for further evaluation
	        iifname vmap { lo : accept, $IF_INET : jump inbound_wan, $IF_IFN1 : jump inbound_ifn1 }

        	# the rest is dropped by the above policy
	}
	
	# 
	chain forward {
		type filter hook forward priority 0; policy drop;

	        # Allow traffic from established and related packets, drop invalid
        	ct state vmap { established : accept, related : accept, invalid : drop }

	        # connections from the internal net to the internet or to other
	        # internal nets are allowed
        	iifname $IF_IFN1 accept

	        # the rest is dropped by the above policy
	}

	chain postrouting {
		type nat hook postrouting priority 100; policy accept;
		
		# masquerade private IP addresses
		ip saddr $NET_IFN1 oifname $IF_IFN1 masquerade
	}
}

Now run it:

/etc/nftables/alteeve.nft

If it worked, you should be able to setup a machine on the 10.255.0.0/24 network, set the IP of the router as the default gateway (10.255.255.254 in this case), and connect to the internet!

Setting Up DHCP Server

We need to be able to give out IP addresses to our network now.

Install the DHCP server

dnf install dhcp-server
Last metadata expiration check: 0:55:35 ago on Wed Mar 27 23:29:35 2024.
Dependencies resolved.
==============================================================================================================================
 Package                       Architecture             Version                                Repository                Size
==============================================================================================================================
Installing:
 dhcp-server                   x86_64                   12:4.4.2-19.b1.el9                     baseos                   1.2 M
Installing dependencies:
 dhcp-common                   noarch                   12:4.4.2-19.b1.el9                     baseos                   128 k

Transaction Summary
==============================================================================================================================
Install  2 Packages

Total download size: 1.3 M
Installed size: 4.2 M
Is this ok [y/N]: y
Downloading Packages:
(1/2): dhcp-common-4.4.2-19.b1.el9.noarch.rpm                                                 495 kB/s | 128 kB     00:00    
(2/2): dhcp-server-4.4.2-19.b1.el9.x86_64.rpm                                                 3.6 MB/s | 1.2 MB     00:00    
------------------------------------------------------------------------------------------------------------------------------
Total                                                                                         1.8 MB/s | 1.3 MB     00:00     
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                                                                      1/1 
  Installing       : dhcp-common-12:4.4.2-19.b1.el9.noarch                                                                1/2 
  Running scriptlet: dhcp-server-12:4.4.2-19.b1.el9.x86_64                                                                2/2 
  Installing       : dhcp-server-12:4.4.2-19.b1.el9.x86_64                                                                2/2 
  Running scriptlet: dhcp-server-12:4.4.2-19.b1.el9.x86_64                                                                2/2 
  Verifying        : dhcp-common-12:4.4.2-19.b1.el9.noarch                                                                1/2 
  Verifying        : dhcp-server-12:4.4.2-19.b1.el9.x86_64                                                                2/2 

Installed:
  dhcp-common-12:4.4.2-19.b1.el9.noarch                         dhcp-server-12:4.4.2-19.b1.el9.x86_64                        

Complete!

Configure the DHCP Service

Note: This is setting up IPv4 only.

We need to copy the original services file into /etc/systemd/system/, and then edit it to configure the daemon to only listen to our internal interfaces. Yes, this is clunky. No, I don't know why this can't be done in a config file.

Don't edit /usr/lib/systemd/system/dhcpd.service directly, changes will be lost on future updates.

cp /usr/lib/systemd/system/dhcpd.service /etc/systemd/system/

Now edit /etc/systemd/system/dhcpd.service to tell it to listed to our internal-facing interface (enp0s20f0u1 in my case).

vim /etc/systemd/system/dhcpd.service
[Unit]
Description=DHCPv4 Server Daemon
Documentation=man:dhcpd(8) man:dhcpd.conf(5)
Wants=network-online.target
After=network-online.target
After=time-sync.target

[Service]
Type=notify
EnvironmentFile=-/etc/sysconfig/dhcpd
ExecStart=/usr/sbin/dhcpd -f -cf /etc/dhcp/dhcpd.conf -user dhcpd -group dhcpd --no-pid $DHCPDARGS enp0s20f0u1 
StandardError=null

[Install]
WantedBy=multi-user.target

The difference is;

diff -U0 /usr/lib/systemd/system/dhcpd.service /etc/systemd/system/dhcpd.service
--- /usr/lib/systemd/system/dhcpd.service	2023-09-26 22:47:07.000000000 -0400
+++ /etc/systemd/system/dhcpd.service	2024-03-28 00:42:51.271385340 -0400
@@ -11 +11 @@
-ExecStart=/usr/sbin/dhcpd -f -cf /etc/dhcp/dhcpd.conf -user dhcpd -group dhcpd --no-pid $DHCPDARGS
+ExecStart=/usr/sbin/dhcpd -f -cf /etc/dhcp/dhcpd.conf -user dhcpd -group dhcpd --no-pid $DHCPDARGS enp0s20f0u1

Configure the DHCP Server

Back up the original config file.

cp /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.original


References

 

Any questions, feedback, advice, complaints or meanderings are welcome.
Alteeve's Niche! Enterprise Support:
Alteeve Support
Community Support
© Alteeve's Niche! Inc. 1997-2024   Anvil! "Intelligent Availability®" Platform
legal stuff: All info is provided "As-Is". Do not use anything here unless you are willing and able to take responsibility for your own actions.