(Answered) How do I use nftables to create a firewall in Linux and forward packets between private networks?

Trenix25

Member
Joined
Aug 15, 2017
Messages
71
Reaction score
18
Credits
550
I used iptables to create a firewall for my Linux system for a while, but my script and firewall rules were a mess. Then I wanted to move to nftables, which is intended to replace iptables, hoping to get something better. I had also wondered about how to forward traffic between different private network subnets, but could never get it to work. I asked Bing's AI about this and though it didn't entirely solve my problem it did help to point me in the right direction. I also managed to get traffic from my second WiFi access point, which connects to my computer's Ethernet port, to be passed to the Internet so I could use it as a WiFi relay to extend WiFi coverage.

If you'd like to create a firewall for your Linux system using nftables you can use this example script to help get you started.

This is nft-firewall:

Code:
#!/bin/bash
#
# This is an example bash script that shows
# how to build a firewall using /usr/sbin/nft.

# Define where to find the binary.

NFT="/usr/sbin/nft"

# Define our network devices and IP addresses.

MYWIFI4="wlan0"
MYIP4="Use your own static private IPv4 address here."

MYETH="eth0"
MYETHIP="Use your own static private IPv4 address here."

MYWIFI6="wlan0"
MYIP6="Use your own static IPv6 address here."

# VMware uses these.

VM1="vmnet1"
VM1IP="192.168.85.1"

VM8="vmnet8"
VM8IP="172.16.138.1"

# See if the user wants to flush everything and exit.

FLUSH_AND_EXIT=0

if [ ! -z "$1" ]; then

     if [ ! -z "$2" ]; then
          /usr/bin/echo "Usage: nft-firewall [-f]"
          exit 1
     fi

     if [ "$1" != "-f" ]; then
          /usr/bin/echo "Usage: nft-firewall [-f]"
          exit 1
     else
          FLUSH_AND_EXIT=1
     fi

fi

# Check to see if we have any rules at all.

RULES=$($NFT list ruleset)

if [ $FLUSH_AND_EXIT == 0 ]; then

     /usr/bin/echo "Setting up the firewalls."

     if [ -z "$RULES" ]; then
          /usr/bin/echo "Creating the filter tables."
          $NFT create table ip filter4
          $NFT create table ip6 filter6
          /usr/bin/echo "Creating the nat table."
          $NFT create table ip nat
     fi

fi

if [ ! -z "$RULES" ]; then

     # Remove any existing base chains.

     RESULT=$($NFT list chains | /usr/bin/grep "chain forward")

     if [ ! -z "$RESULT" ]; then
          /usr/bin/echo "Flushing and removing the forward base chain."
          $NFT flush chain filter4 forward
          $NFT delete chain ip filter4 forward
     fi

     RESULT=$($NFT list chains | /usr/bin/grep "chain input")

     if [ ! -z "$RESULT" ]; then
          /usr/bin/echo "Flushing and removing the input base chains."
          $NFT flush chain filter4 input
          $NFT delete chain ip filter4 input
          $NFT delete chain ip6 filter6 input
     fi

     RESULT=$($NFT list chains | /usr/bin/grep "chain output")

     if [ ! -z "$RESULT" ]; then
          /usr/bin/echo "Flushing and removing the output base chains."
          $NFT flush chain filter4 output
          $NFT delete chain ip filter4 output
          $NFT delete chain ip6 filter6 output
     fi

     RESULT=$($NFT list chains | /usr/bin/grep "chain translate")

     if [ ! -z "$RESULT" ]; then
          /usr/bin/echo "Flushing and removing the translate base chain."
          $NFT flush chain nat translate
          $NFT delete chain ip nat translate
     fi

     unset RESULT

     # Flush the firewall tables.

     /usr/bin/echo "Flushing firewall tables."

     $NFT flush table ip filter4
     $NFT flush table ip6 filter6
     $NFT flush table ip nat

fi

# Exit after flushing everything if the user requests it.

if [ $FLUSH_AND_EXIT == 1 ]; then

     if [ ! -z "$RULES" ]; then
          /usr/bin/echo "Removing the filter tables."
          $NFT delete table ip filter4
          $NFT delete table ip6 filter6
          /usr/bin/echo "Removing the nat table."
          $NFT delete table ip nat
     fi

     /usr/bin/echo "Exiting."
     exit 0

fi

# Add the forward base chain using a default policy of accept.

# This will be necessary if you use multiple subnets
# and you want them to be able to talk to each other.

/usr/bin/echo "Adding the forward base chain to the filter4 table."

$NFT add chain ip filter4 forward \{ type filter hook forward priority 0 \; policy accept \; \}

#
# Add the input and output base chains.
#

# Add the input base chain to the ip filter table using a default policy of drop.

/usr/bin/echo "Adding the input base chain to the filter4 table."

#$NFT add chain ip filter4 input \{ type filter hook input priority 0 \; policy accept \; \}
$NFT add chain ip filter4 input \{ type filter hook input priority 0 \; policy drop \; \}

# Add the input base chain to the ip6 filter table using a default policy of drop.

/usr/bin/echo "Adding the input base chain to the filter6 table."

#$NFT add chain ip6 filter6 input \{ type filter hook input priority 0 \; policy accept \; \}
$NFT add chain ip6 filter6 input \{ type filter hook input priority 0 \; policy drop \; \}

# Add the output base chain to the ip filter table using a default policy of accept.

/usr/bin/echo "Adding the output base chain to the filter4 table."

$NFT add chain ip filter4 output \{ type filter hook output priority 0 \; policy accept \; \}

# Add the output base chain to the ip6 filter table using a default policy of accept.

/usr/bin/echo "Adding the output base chain to the filter6 table."

$NFT add chain ip6 filter6 output \{ type filter hook output priority 0 \; policy accept \; \}

# Add the translate base chain to the nat filter table using a default policy of accept.

# This will be necessary if you want to allow traffic on one subnet to be forwarded to devices
# on another subnet, such as forwarding traffic from a second WiFi network to the Internet.
# You must still tell your Linux system to translate the addresses in the packet headers.

/usr/bin/echo "Adding the translate base chain to the nat table."

$NFT add chain ip nat translate \{ type nat hook postrouting priority 0 \; policy accept \; \}

#
# Define rules for the base chains.
#

# Block invalid input packets.

/usr/bin/echo "Blocking invalid input packets."

$NFT add rule ip filter4 input ct state invalid drop
$NFT add rule ip6 filter6 input ct state invalid drop

# Block ping requests.  Only include this if you want ICMP echo requests ignored.

/usr/bin/echo "Blocking IPv4 ping requests."

$NFT add rule ip filter4 input icmp type echo-request drop

/usr/bin/echo "Blocking IPv6 ping requests."

$NFT add rule ip6 filter6 input icmpv6 type echo-request drop

#
# Block based on IP address.
#

# Block SSDP packets.

/usr/bin/echo "Blocking SSDP packets."

$NFT add rule ip filter4 input ip daddr 239.255.255.250 drop
$NFT add rule ip filter4 input ip daddr 224.0.0.251 drop
$NFT add rule ip filter4 output ip daddr 239.255.255.250 drop
$NFT add rule ip filter4 output ip daddr 224.0.0.251 drop

#
# Block based on port number.
#

# Block Service Location Protocol (SLP) attacks.

/usr/bin/echo "Blocking SLP packets."

$NFT add rule ip filter4 input udp dport 427 drop
$NFT add rule ip6 filter6 input udp dport 427 drop
$NFT add rule ip filter4 output ip daddr ne $MYIP4 udp dport 427 drop
$NFT add rule ip6 filter6 output ip6 daddr ne $MYIP6 udp dport 427 drop

# llmnr is used by OpenVPN.
# Block outgoing llmnr packets.  The default input policy is drop.

/usr/bin/echo "Blocking outgoing llmnr packets."

$NFT add rule ip filter4 output ip daddr ne $MYIP4 tcp dport 5355 drop
$NFT add rule ip filter4 output ip daddr ne $MYIP4 udp dport 5355 drop
$NFT add rule ip6 filter6 output ip6 daddr ne $MYIP6 tcp dport 5355 drop
$NFT add rule ip6 filter6 output ip6 daddr ne $MYIP6 udp dport 5355 drop

# Enable localhost traffic.

/usr/bin/echo "Enabling localhost."

$NFT add rule ip filter4 input iif lo accept
$NFT add rule ip6 filter6 input iif lo accept

# Allow incoming traffic for established connections.

/usr/bin/echo "Allowing incoming traffic for established connections."

$NFT add rule ip filter4 input ct state established,related accept
$NFT add rule ip6 filter6 input ct state established,related accept

# Allow basic IPv6 connectivity traffic.

/usr/bin/echo "Allowing basic IPv6 connectivity traffic."

$NFT add rule ip6 filter6 input icmpv6 type \{ nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert \} accept
$NFT add rule ip6 filter6 output icmpv6 type \{ nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert \} accept

# Allow local access.  Without this we can't talk to ourself.

/usr/bin/echo "Allowing local access."

$NFT add rule ip filter4 input ip saddr $MYIP4 ip daddr $MYIP4 accept
$NFT add rule ip filter4 input ip saddr $MYETHIP ip daddr $MYETHIP accept
$NFT add rule ip filter4 input ip saddr $VM1IP/24 ip daddr $MYIP4 accept
$NFT add rule ip filter4 input ip saddr $VM8IP/24 ip daddr $MYIP4 accept
$NFT add rule ip6 filter6 input ip6 saddr $MYIP6 ip6 daddr $MYIP6 accept

#
# Allow access to specific services.
#

# Only allow access to services that you want others to have access to.
# Do not allow access to services that are not running on your system.

# Allow IPv4 access to the web server.  This is for example purposes.

/usr/bin/echo "Allowing access to the web server."

$NFT add rule ip filter4 input tcp dport 80 accept
$NFT add rule ip filter4 input tcp dport 443 accept

# Allow Network Time Protocol packets.  This is needed.

/usr/bin/echo "Allowing Network Time Protocol packets."

$NFT add rule ip filter4 input udp dport 123 accept
$NFT add rule ip6 filter6 input udp dport 123 accept
$NFT add rule ip filter4 output udp dport 123 accept
$NFT add rule ip6 filter6 output udp dport 123 accept

#
# And we're done.
#

/usr/bin/echo "Exiting."

exit 0

# EOF

If you'd like to forward traffic between eth0 and wlan0 then you can use this example script to help get you started with that.

This is nft-forward:

Code:
#!/bin/bash
#
# Enables or disables forwarding between wlan0 and eth0 using /usr/sbin/nft.

# Make sure you have the following line in /etc/sysctl.conf:
# net.ipv4.ip_forward=1
# to tell your Linux system to forward packets between subnets.

# You can use this command, as root, to enable this without rebooting:
# sysctl net.ipv4.ip_forward=1

# Define where to find the binary.

NFT="/usr/sbin/nft"

# Define our network devices and IP addresses.

MYWIFI="wlan0"

MYETH="eth0"

# Make sure we have one and only one command line argument.

if [ -z "$1" ] || [ ! -z "$2" ]; then
     /usr/bin/echo "Usage: nft-forward <on | off>"
     exit 1
fi

# Find out what the user wants to do.

FORWARD="UNDEFINED"

if [ "$1" == "on" ]; then
     FORWARD="ON"
elif [ "$1" == "off" ]; then
     FORWARD="OFF"
else
     /usr/bin/echo "Usage: nft-forward <on | off>"
     exit 1
fi

if [ $FORWARD == "ON" ]; then

     /usr/bin/echo "Enabling IPv4 forwarding between eth0 and wlan0."

     $NFT add rule ip filter4 forward iif $MYETH oif $MYWIFI accept
     $NFT add rule ip filter4 forward iif $MYWIFI oif $MYETH accept

else

     # I really couldn't find any other way to get this to work.
     # We must directly specify eth0 and wlan0 instead of $MYETH and $MYWIFI.

     /usr/bin/echo "Disabling IPv4 forwarding between eth0 and wlan0."

     # Find the correct handle number for forwarding from eth0 to wlan0.

     RESULT=$($NFT -a list ruleset | /usr/bin/grep 'iif \"eth0\" oif \"wlan0\" accept')

     if [ -z "$RESULT" ]; then
          /usr/bin/echo -n "Could not find the handle number "
          /usr/bin/echo "for forwarding from eth0 to wlan0."
          /usr/bin/echo "Exiting due to error."
          exit 1
     fi

     HANDLE=$(/usr/bin/echo "$RESULT" | /usr/bin/cut -d "#" -f 2 | /usr/bin/cut -d " " -f 3)

     $NFT delete rule ip filter4 forward handle "$HANDLE"

     # Find the correct handle number for forwarding from wlan0 to eth0.

     RESULT=$($NFT -a list ruleset | /usr/bin/grep 'iif \"wlan0\" oif \"eth0\" accept')

     if [ -z "$RESULT" ]; then
          /usr/bin/echo -n "Could not find the handle number "
          /usr/bin/echo "for forwarding from wlan0 to eth0."
          /usr/bin/echo "Exiting due to error."
          exit 1
     fi

     HANDLE=$(/usr/bin/echo "$RESULT" | /usr/bin/cut -d "#" -f 2 | /usr/bin/cut -d " " -f 3)

     $NFT delete rule ip filter4 forward handle "$HANDLE"

fi

exit 0

# EOF

When you do this any hosts coming in from a WiFi access point connected to eth0 will still use their actual IPv4 address, at least while the traffic is still in your Linux computer. If you use Network Address Translation (NAT) to send such traffic beyond your Linux computer to other hosts on another private local subnet, or to the Internet, the source address on the outgoing packets will be swapped with the IPv4 address used by your Linux computer's network device on the way out of your computer.

If you'd like to forward traffic from such devices to another private local subnet, if your Linux computer is connected to both subnets, or out to the Internet, then you'll need to use Network Address Translation (NAT) to make this work.

This is nft-translate:

Code:
#!/bin/bash
#
# Enables or disables network address translation from eth0 to wlan0 using /usr/sbin/nft.

# Define where to find the binary.

NFT="/usr/sbin/nft"

# Define our network devices and IP addresses.

MYWIFI="wlan0"

MYETH="eth0"

# Make sure we have one and only one command line argument.

if [ -z "$1" ] || [ ! -z "$2" ]; then
     /usr/bin/echo "Usage: nft-translate <on | off>"
     exit 1
fi

# Find out what the user wants to do.

FORWARD="UNDEFINED"

if [ "$1" == "on" ]; then
     FORWARD="ON"
elif [ "$1" == "off" ]; then
     FORWARD="OFF"
else
     /usr/bin/echo "Usage: nft-translate <on | off>"
     exit 1
fi

if [ $FORWARD == "ON" ]; then

     /usr/bin/echo "Enabling network address translation from eth0 to wlan0."
     $NFT add rule ip nat translate oif $MYWIFI masquerade

else

     # I really couldn't find any other way to get this to work.
     # We must directly specify eth0 and wlan0 instead of $MYETH and $MYWIFI.

     /usr/bin/echo "Disabling network address translation from eth0 to wlan0."

     # Find the correct handle number for performing network address translation.

     RESULT=$($NFT -a list ruleset | /usr/bin/grep 'oif \"wlan0\" masquerade')

     if [ -z "$RESULT" ]; then
          /usr/bin/echo -n "Could not find the handle number "
          /usr/bin/echo "for performing network address translation."
          /usr/bin/echo "Exiting due to error."
          exit 1
     fi

     HANDLE=$(/usr/bin/echo "$RESULT" | /usr/bin/cut -d "#" -f 2 | /usr/bin/cut -d " " -f 3)

     $NFT delete rule ip nat translate handle "$HANDLE"

fi

exit 0

# EOF

I do hope this information is helpful to people.

/Trenix25
 
Last edited:


Thank you for sharing, I'll add it to bookmarks and come back when I need forwarding setup.

Did you know you can develop your firewall by using the so called "atomic" operation?
Instead of shell script your write rules in *.nft script which is run directly by nft, ex:

Bash:
#!/usr/sbin/nft -f

flush ruleset

add table filter_4

add chain filter_4 input {
    type filter hook input priority filter; policy drop;
}

# etc...

Advantage of this approach is that it makes the code more readable and loading the firewall is atomic meaning entire ruleset is first loaded into memory and then at once replaced with current firewall resulting in faster replacement and execution.

Drawback is that you can't use bash scripting in the file but you can still create bash scripts and then call nft scripts from sh script if you need it.
 
Last edited:
Thank you for sharing, I'll add it to bookmarks and come back when I need forwarding setup.

Did you know you can develop your firewall by using the so called "atomic" operation?
Instead of shell script your write rules in *.nft script which is run directly by nft, ex:

Bash:
#!/usr/sbin/nft -f

flush ruleset

add table filter_4

add chain filter_4 input {
    type filter hook input priority filter; policy drop;
}

# etc...

Advantage of this approach is that it makes the code more readable and loading the firewall is atomic meaning entire ruleset is first loaded into memory and then at once replaced with current firewall resulting in faster replacement and execution.

Drawback is that you can't use bash scripting in the file but you can still create bash scripts and then call nft scripts from sh script if you need it.
Yes, I was aware, but I wanted to use those echo statements to help with debugging if anything went wrong. I've made changes over time. I wanted to deal with one issue at a time.

/Trenix25
 

Staff online

Members online


Top