WARNING: technical content ahead! There’s also a tonne of config files, which make this page look longer than it really is, but hopefully they’ll help other people who want to do similar work.
Back in 2017 I described how to build a home router based on CentOS 7.
C7 is now out of date, so I figured it was time to rebuild it, this time using Rocky Linux 9. That should give me another 8 years of security patches!
My requirements were mostly the same, but I wasn’t going to use WiFi on the machine; I’d already replaced that with Access Points. I also wasn’t so much with IPv6 any more; this is mostly because the Hurricane Electric tunnelbroker IP addresses were on a lot of “low reputation” lists, and I was getting annoyed with that. I still use IPv6 but only specific machines, with a static configuration, use it.
I figured it was also time to get a new machine. There are a LOT of cheap hand-sized computers, these days, including some dual port ones. I went for a CTone Mini PC; 8GB RAM, 256Gb SSD, dual 1Gbit NICs. Sounded perfect. Unfortunately it wasn’t. It uses RTL chipsets, which appear to be less performant than the existing machine with Intel NICs; it worked but speed test results were consistently 100Mbit/s slower. So I got a new SSD for my old machine and built fresh on that. If I failed to get it to work then I could just put the old SSD back in. The new slow machine isn’t wasted though; it’s now a backup just in case my primary ever fails.
Basic networking
Requirements
At home I have 3 VLANS; 10 (LAN), 11 (guestnet), 12 (IoT). Although I no longer need to (no wireless) I’m going to keep using bridges for everything and I’m going to keep using the old name.
So, to summarize, the result would look something like
Use | Bridge | Interface |
---|---|---|
Internet | br-wan | enp2s0 |
LAN | br-lan | enp3s0.10 |
Guest | br-guest | enp3s0.11 |
IoT | br-iot | enp3s0.12 |
For simplicity, I’m going to assign enp2s0
to $WAN
, and enp3s0
to $LAN
.
WAN
First we need to make sure there’s no existing connections defined from the
OS install. This can be done with the nmcli con show
command. If you
see anything like Wired connection 1
then you should delete them with
nmcli con del ...
.
You will lose network access doing this, so make sure you’re on the console!
Bridges are created with nmcli connection add type bridge ...
and interfaces
are associated with it with nmcli connection add type bridge-slave ...
commands.
So the WAN interface would be created with
nmcli device set $WAN autoconnect yes
nmcli connection add type bridge con-name br-wan ifname br-wan bridge.stp no ipv4.method auto ipv4.dns "10.0.0.1 10.0.0.5" ipv4.dns-search "spuddy.org" ipv6.method disabled
nmcli connection add type bridge-slave con-name $WAN ifname $WAN master br-wan
In this I am hard-coding my internal DNS servers (this machine and my backup DNS server) and DNS search path.
Woohoo! At this point the machine should(!) have internet access.
Internal VLANs
Similarly we can define the 3 VLANs and bridges. br-lan
will have a static
IPv6 address, but the rest just have IPv4.
nmcli device set $LAN autoconnect no
nmcli connection add type bridge con-name br-lan ifname br-lan bridge.stp no ip4 10.0.0.1/24 ipv6.method manual ipv6.address 2001:470:1f07:dc4::1/64
nmcli connection add type vlan con-name $LAN.10 ifname $LAN.10 vlan.parent $LAN vlan.id 10 slave-type bridge master br-lan
nmcli device set $LAN.10 autoconnect yes
nmcli connection add type bridge con-name br-guest ifname br-guest bridge.stp no ip4 10.100.100.1/24 ipv6.method disabled
nmcli connection add type vlan con-name $LAN.11 ifname $LAN.11 vlan.parent $LAN vlan.id 11 slave-type bridge master br-guest
nmcli device set $LAN.11 autoconnect yes
nmcli connection add type bridge con-name br-iot ifname br-iot bridge.stp no ip4 10.100.200.1/24 ipv6.method disabled
nmcli connection add type vlan con-name $LAN.12 ifname $LAN.12 vlan.parent $LAN vlan.id 12 slave-type bridge master br-iot
nmcli device set $LAN.12 autoconnect yes
We can now see how this looks:
# nmcli dev
DEVICE TYPE STATE CONNECTION
br-wan bridge connected br-wan
br-lan bridge connected br-lan
br-guest bridge connected br-guest
br-iot bridge connected br-iot
enp2s0 ethernet connected enp2s0
enp3s0.10 vlan connected enp3s0.10
enp3s0.11 vlan connected enp3s0.11
enp3s0.12 vlan connected enp3s0.12
lo loopback connected (externally) lo
enp3s0 ethernet disconnected --
enp1s0 ethernet unavailable --
# nmcli con
NAME UUID TYPE DEVICE
br-wan 0102b304-effc-4437-a70a-aa3360d4d3e0 bridge br-wan
br-lan acf41256-e68b-40a8-a85a-944eb95035a6 bridge br-lan
br-guest a3c962bb-e605-46aa-8d43-7cb9db4ebad1 bridge br-guest
br-iot f7715bbd-1a7c-41fb-bfb0-5ddc2900048a bridge br-iot
enp2s0 e966376e-39e5-4789-aaaa-5a2c0154323d ethernet enp2s0
enp3s0.10 bfd85eca-989c-499c-89d2-cb4f57d0cde9 vlan enp3s0.10
enp3s0.11 a63e60ce-e808-499e-bfb1-aedb5ee83541 vlan enp3s0.11
enp3s0.12 2d775836-ab3b-4ccb-a120-c32e2ec0efc7 vlan enp3s0.12
lo f63cce44-1aed-467f-bd27-96348e58c8f2 loopback lo
If we install the old bridge-utils
package (from EPEL) we can also see
very simply the bridge configurations
% brctl show
bridge name bridge id STP enabled interfaces
br-guest 8000.000db9439cce no enp3s0.11
br-iot 8000.000db9439cce no enp3s0.12
br-lan 8000.000db9439cce no enp3s0.10
br-wan 8000.000db9439ccd no enp2s0
And IP addresses can be seen with ip -4 a
. So far so good!
Config files.
NetworkManager is not meant to be manipulated via config files (I much prefer
the old /etc/sysconfig/network-scripts
) but there are files we can look at,
in /etc/NetworkManager/system-connections
. e.g.
% ls /etc/NetworkManager/system-connections/
br-guest.nmconnection br-wan.nmconnection enp3s0.11.nmconnection
br-iot.nmconnection enp2s0.nmconnection enp3s0.12.nmconnection
br-lan.nmconnection enp3s0.10.nmconnection
IPv6
Hurricane Electric IPv6 tunnels are very easy to set up. You need three values; the tunnelbroker endpoint IPv4 address, and the two IPv6 address associated with each end. These will probably be ….::2 for your end, and ….::1 for their end.
nmcli connect add type ip-tunnel ifname he-sit mode sit remote $ipv4address ipv4.method disabled ipv6.method manual ipv6.address $connection::2/64 ipv6.gateway $connection::1
This will show up under nmcli con
and nmcli dev
% nmcli dev | grep he-sit
he-sit iptunnel connected ip-tunnel-he-sit
% nmcli con | grep he-sit
ip-tunnel-he-sit 6840bf43-8275-4ccc-88a1-6ff16caabea2 ip-tunnel he-sit
The tunnel should come up (if your IPv4 address is correct on the HE end)
and we can see this in ip -6 a
output
24: he-sit@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 state UNKNOWN qlen 1000
inet6 _connection_::2/64 scope global noprefixroute
valid_lft forever preferred_lft forever
If all is going well, ping6 google.com
should work!
PING google.com(lga25s41-in-x0e.1e100.net (2607:f8b0:4006:80f::200e)) 56 data bytes
64 bytes from lga25s41-in-x0e.1e100.net (2607:f8b0:4006:80f::200e): icmp_seq=1 ttl=119 time=9.21 ms
...
Now HE uses your IPv4 address to validate the tunnel is allowed to be established. If you’re on a dynamic IP then a simple script that calls their API would work.
PASS=YOUR_PASSWORD
USERID=YOUR_USERNAME
GTUNID=YOUR_TUNNEL_ID
HOST=https://$USERID:$PASS@ipv4.tunnelbroker.net
newip=$1
# Check to see what the end point currently is
x=$(wget -q --no-check-certificate -O - $HOST/tunnelInfo.php?tid=$GTUNID | sed -n 's/^.*<clientv4>\(.*\)<.*$/\1/p')
if [ "x$x" == "x$newip" ]
then
echo Tunnel IP is already correct
exit
fi
wget -q --no-check-certificate -O - "$HOST/nic/update?hostname=$GTUNID&myip=$newip"
Firewalls and routing
That’s the low level plumbing done. Now to make it look like a router
Forwarding
Routers need to forward packets… This is done by setting sysctl
options.
They can be made to persist by putting them into a config file; e.g.
% cat /etc/sysctl.d/forward.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.ipv6.conf.all.forwarding = 1
At this point the 3 VLANs can see each other. Unrestricted and there’s no internet access, but we’re getting there!
firewalld
RL9 has two ways that can manipulate network table rules; the first is
firewalld
, and the second is to use nftables
directly.
I prefer nftables
, so we need to disable firewalld
and enable `nftables
# systemctl disable --now firewalld
# systemctl mask firewalld
# systemctl enable --now nftables
nftables
I’m going to look at this similarly to how iptables
did it. We have
standard rules (INPUT/OUTPUT/FORWARD) we have to look at them from the
perspective of the router. INPUT rules are those that impact packets that
are targeted at the router; OUTPUT rules are those that originate from
the router; FORWARD rules are those that pass through the router. This
gets important for NAT where the packet targets the router’s external
IP address. Similarly for NAT rules we have PREROUTING and POSTROUTING.
nftables
has a bit more “fluff” needed to create the necessary structures,
so that’s where we start
# Clear out all the rules
flush ruleset
# Create empty structures
add table ip filter
add table ip nat
add table ip6 filter
I’m also going to define some structures that we’ll use later.
add map nat fport { type inet_service : interval ipv4_addr . inet_service ; flag
s interval; }
add set ip nat reflect { type inet_service; }
add set ip nat this_host { type ipv4_addr; }
add set ip6 filter my_hosts { type ipv6_addr; }
IPv4
We can now start building the IPv4 rules. Let’s create the 3 standard chains and 2 more chains that will be used for logging of packets
add chain ip filter INPUT { type filter hook input priority 0; policy accept; }
add chain ip filter OUTPUT { type filter hook output priority 0; policy accept; }
add chain ip filter FORWARD { type filter hook forward priority 0; policy accept; }
# Log rules for input and forwarding
add chain ip filter LOGGER
add chain ip filter LOGGERF
The logging rules are pretty simple. We limit how many can be sent, to avoid DoSing ourselves with log messages!
# Logging rule for INPUT drops
add rule ip filter LOGGER limit rate 2/minute burst 5 packets counter log prefix "IPTables-Dropped: "
add rule ip filter LOGGER counter drop
# Logging rule for FORWARD drops
add rule ip filter LOGGERF limit rate 2/minute burst 5 packets counter log prefix "IPTables-Dropped-Forward: "
add rule ip filter LOGGERF counter drop
Now we can handle INPUT rules (remember, this is traffic meant for this machine, and it may come from any of the VLANs or internet). We want to allow all traffic from the LAN; guestnet and IoT have limited access. Everything else should be blocked. Remember, we’re only talking about traffic targeting the router; we’ll get to port forwarding later.
# Allow traffic for established connections, blocking invalid header format
add rule ip filter INPUT ct state related,established counter accept
add rule ip filter INPUT ct state invalid counter drop
# All localhost traffic allowed
add rule ip filter INPUT iifname "lo" counter accept
# The LAN can see this machine
add rule ip filter INPUT iifname "br-lan" counter accept
# Allow the world to ping this machine (amongst other things)
add rule ip filter INPUT ip protocol icmp counter accept
# Let guestnet and IoT see DNS servers on this machine (tcp+udp)
add rule ip filter INPUT iifname "br-guest" tcp dport 53 counter accept
add rule ip filter INPUT iifname "br-guest" udp dport 53 counter accept
add rule ip filter INPUT iifname "br-guest" tcp dport 853 counter accept
add rule ip filter INPUT iifname "br-guest" udp dport 853 counter accept
# IoT may also need NTP access
add rule ip filter INPUT iifname "br-iot" tcp dport 53 counter accept
add rule ip filter INPUT iifname "br-iot" udp dport 53 counter accept
add rule ip filter INPUT iifname "br-iot" udp dport 123 counter accept
# Log block traffic; this could be noisy, so only enable it if you
# want lots of logs!
## add rule ip filter INPUT counter jump LOGGER
# Block all other incoming traffic
add rule ip filter INPUT counter drop
OUTPUT rules are very simple. We allow everything, because this is just for traffic that originated on this machine.
add rule ip filter OUTPUT ct state related,established counter accept
add rule ip filter OUTPUT ct state invalid counter drop
Now we need to allow traffic from our internal networks to see the internet. I’m also going to add a special rule; if my mobile is on guestnet then it can see the whole of the WAN.
There are also some other special rules (eg allow guests to see my printer) that we need to handle
# Allow established connections to work, blocking invalid header format
add rule ip filter FORWARD ct state related,established counter accept
add rule ip filter FORWARD ct state invalid counter drop
# Allow the LAN to see the world
add rule ip filter FORWARD iifname "br-lan" counter accept
# Allow return traffic for NAT'd connections to work
add rule ip filter FORWARD iifname "br-wan" ct status dnat counter accept
# Allow my mobile devices to have access to everything
add rule ip filter FORWARD iifname "br-guest" ip saddr 10.100.100.8 counter accept
# Allow guestnet to see the printer
add rule ip filter FORWARD iifname "br-guest" ip daddr 10.0.0.10 counter accept
# Allow guestnet devices to see my MQTT server
add rule ip filter FORWARD iifname "br-guest" oifname "br-lan" tcp dport 1883 counter accept
# Allow guestnet to see the internet
add rule ip filter FORWARD iifname "br-guest" oifname "br-wan" counter accept
# Allow IoT devices to see my MQTT server
add rule ip filter FORWARD iifname "br-iot" oifname "br-lan" tcp dport 1883 counter accept
# Allow ESP8266 devices on IoT to be network updates
# (The process causes the ESP8266 to call back to a webserver on the host)
add rule ip filter FORWARD iifname "br-iot" oifname "br-lan" tcp dport 8266 counter accept
# Allow IoT devices to see the internet
add rule ip filter FORWARD iifname "br-iot" oifname "br-wan" counter accept
# Allow marked traffic (ie reflected traffic) to reach the destination
add rule ip filter FORWARD meta mark 100 counter accept
# Log and block all other forwarded traffic
add rule ip filter FORWARD counter jump LOGGERF
add rule ip filter FORWARD counter drop
NAT
OK! We have connectivity on the internal network, now we need to set up the NAT rules to allow the internal network to SNAT (source NAT) to reach the internet, and to allow the internet to DNAT (destination NAT) to allow it to reach specific internal services.
There’s also an odd edge-case. If you’re on the guest network and try to
reach an internal service via the external IP address (e.g. a web server)
then it all breaks. This is because the incoming traffic doesn’t arrive
via the br-wan
interface and so isn’t NAT’d. In iptables
days we used
a set of “reflection” rules (see the earlier blog entry about this). With
nftables
we can be smarter. We can set up some rules that use sets and
maps to hold the information.
So to define incoming port connections and those we want reflected:
# Port forwarding for incoming traffic
add element nat fport { 80 : 10.0.0.100 . 80 } # http
add element nat fport { 443 : 10.0.0.100 . 443 } # https
add element nat fport { 22 : 10.0.0.110 . 22 } # ssh
add element nat fport { 6881-6999 : 10.0.0.120 . 6881-6999 } # RL9 torrents
# These ports should be reflected. Basically the ports from fport that
# people might want to access
add element nat reflect { 80, 443, 22 }
We’re now in a position to define a set of rules
add chain ip nat INPUT { type nat hook input priority 100; policy accept; }
add chain ip nat OUTPUT { type nat hook output priority -100; policy accept; }
add chain ip nat PREROUTING { type nat hook prerouting priority -100; policy accept; }
add chain ip nat POSTROUTING { type nat hook postrouting priority 100; policy accept; }
# Log specific incoming traffic
add rule ip nat PREROUTING iifname "br-wan" tcp dport 22 limit rate 2/minute burst 5 packets counter log prefix "IPTables-New-SSH: "
# Tag internal reflection traffic
add rule ip nat PREROUTING ip saddr 10.0.0.0/8 ip daddr @this_host tcp dport @reflect mark set 100
# DNAT incoming traffic from the internet (or internal send to br-wan address)
add rule ip nat PREROUTING ip daddr @this_host ip protocol tcp dnat ip addr . port to tcp dport map @fport
# Do NAT on egress traffic to the internet
add rule ip nat POSTROUTING oifname "br-wan" counter masquerade
# Also NAT marked traffic from internal to external IP
add rule ip nat POSTROUTING meta mark 100 counter masquerade
@this_host
You might have noticed the @this_host
rules. This refers to the set we
defined earlier… but we haven’t populated it, yet!
There are two rules that work by have a PREROUTING
rule detect traffic
designed for reflection, and this sets a mark on it; the POSTROUTING
rule
detects this mark and ensures the traffic is masqueraded (SNAT’d).
The other rule using @this_host
is the general incoming forwarding rule,
which uses the fport
map we filled out earlier. This catches both internet
and VLAN initiated traffic that’s trying to hit this machine and ensures
they’re both forwarded (DNAT’d) to the right target.
However, this set needs to hold the external IP address of the machine. We can set it in two places.
dhcp hooks
Whenever the br-wan
interface gets assigned an address from DHCP we can
arrange for a script to be called. This can simply populate the external
IP address (passed as a variable) into the set:
% cat /etc/NetworkManager/dispatcher.d/nft_host
#!/bin/sh
if [ "$1" == "br-wan" -a -n "$DHCP4_IP_ADDRESS" ]
then
nft "flush set nat this_host;add element nat this_host { $DHCP4_IP_ADDRESS }"
echo `date`: Put $DHCP4_IP_ADDRESS into set
fi
cron job
Just in case the hook doesn’t fire properly, or if you reload the rules (and
so have an empty set) we can also run a cron job every 5 minutes that can
check the br-wan
IP address, and if it’s not in the set we add it. I call
this script reflect
.
#!/bin/sh
export PATH=/sbin:$PATH
export DHCP4_IP_ADDRESS=$(ip -4 address show dev br-wan | sed -n 's/ *inet \([0-9.][0-9.]*\)\/.*/\1/p')
# Check to see if this is in the NFT ruleset
if [ -z "$(nft list set ip nat this_host | grep "{ $DHCP4_IP_ADDRESS }")" ]
then
/etc/NetworkManager/dispatcher.d/nft_host br-wan dhcp4-change
fi
IPv6
Phew, that was a lot of work. IPv6 is a lot simpler; we allow any machine on the LAN to see the internet on IPv6 and we only allow a limited subset of external machines (machines I own) to be able to reach in from the internet.
We can specify the trusted machines by putting them in the my_hosts
set
# My IPv6 hosts
add element ip6 filter my_hosts { \
XXXX:XXXX:XXXX:XXXX:1, \
YYYY:YYYY::YYYY:YYYY:YYYY:YYYY, \
ZZZZ:ZZZ:ZZ:ZZ:ZZZZ:ZZZZ:ZZZZ:0 \
}
And now the IPv6 rules are pretty simple:
add chain ip6 filter INPUT { type filter hook input priority 0; policy accept; }
add chain ip6 filter OUTPUT { type filter hook output priority 0; policy accept;
}
add chain ip6 filter FORWARD { type filter hook forward priority 0; policy accept; }
add rule ip6 filter INPUT ct state related,established counter accept
add rule ip6 filter INPUT ct state invalid counter drop
add rule ip6 filter INPUT iifname "lo" counter accept
add rule ip6 filter INPUT iifname "br-lan" counter accept
add rule ip6 filter INPUT meta l4proto ipv6-icmp counter accept
add rule ip6 filter INPUT ip6 saddr @my_hosts counter accept
add rule ip6 filter INPUT counter drop
add rule ip6 filter OUTPUT ct state related,established counter accept
add rule ip6 filter OUTPUT ct state invalid counter drop
add rule ip6 filter FORWARD ct state related,established counter accept
add rule ip6 filter FORWARD ct state invalid counter drop
add rule ip6 filter FORWARD iifname "br-lan" counter accept
add rule ip6 filter FORWARD meta l4proto ipv6-icmp icmpv6 type destination-unreachable counter accept
add rule ip6 filter FORWARD meta l4proto ipv6-icmp icmpv6 type packet-too-big counter accept
add rule ip6 filter FORWARD meta l4proto ipv6-icmp icmpv6 type time-exceeded counter accept
add rule ip6 filter FORWARD meta l4proto ipv6-icmp icmpv6 type parameter-problem counter accept
add rule ip6 filter FORWARD ip6 saddr @my_hosts counter accept
add rule ip6 filter FORWARD counter drop
Get this to load on reboot
What I do is have a complete list of all my rules in a file; I’ll include
it below. We can then load this with the single command nft -f <filename>
Earlier we ran systemctl enable --now nftables
. This picks up
/etc/sysconfig/nftables.conf
so we can now save the new rules
nft list ruleset > /etc/sysconfig/nftables.conf
Of course after reloading the rules, @this_host
is empty so we also need
to run the reflect
command (or wait for the cron job).
We can do all of this in one line
nft -f nft.rules ; nft list ruleset >| /etc/sysconfig/nftables.conf ; reflect
The final nft ruleset
# Load this rules with
# nft -f <this file>
# Save the rules so they apply on reboot with
# nft list ruleset > /etc/sysconfig/nftables.conf
#
# In one command
#
# nft -f nft ; nft list ruleset >| /etc/sysconfig/nftables.conf ; reflect
#
# Remember to configure nftables to start at boot time, and to disable
# firewalld
#
# systemctl disable --now firewalld
# systemctl mask firewalld
# systemctl enable --now nftables
###############################################################################
# Clear out all the rules
flush ruleset
# Create empty structures
add table ip filter
add table ip nat
add table ip6 filter
add map nat fport { type inet_service : interval ipv4_addr . inet_service ; flags interval; }
add set ip nat reflect { type inet_service; }
add set ip nat this_host { type ipv4_addr; }
add set ip6 filter my_hosts { type ipv6_addr; }
###############################################################################
# This should be the only configuration needed
# Port forwarding for incoming traffic
add element nat fport { 80 : 10.0.0.100 . 80 } # http
add element nat fport { 443 : 10.0.0.100 . 443 } # https
add element nat fport { 22 : 10.0.0.110 . 22 } # ssh
add element nat fport { 6881-6999 : 10.0.0.120 . 6881-6999 } # RL9 torrents
# These ports should be reflected. Basically the ports from fport that
# people might want to access
add element nat reflect { 80, 443, 22 }
# My IPv6 hosts
add element ip6 filter my_hosts { \
XXXX:XXXX:XXXX:XXXX:1, \
YYYY:YYYY::YYYY:YYYY:YYYY:YYYY, \
ZZZZ:ZZZ:ZZ:ZZ:ZZZZ:ZZZZ:ZZZZ:0 \
}
###############################################################################
# This ruleset is split into two parts; ip and ip6
###############################################################################
#
# IPv4
#
###############################################################################
# Basic rules:
# LAN can see everything
# Guestnet and IoT network can see a few internal resources (eg DNS)
# and the internet
# Outside world can see a few exposed services
# Define the 3 standard filters:
# INPUT: Traffic targeting this machine
# OUTPUT: Traffic originating from this machine
# FORWARD: Traffic goin through this machine
add chain ip filter INPUT { type filter hook input priority 0; policy accept; }
add chain ip filter OUTPUT { type filter hook output priority 0; policy accept; }
add chain ip filter FORWARD { type filter hook forward priority 0; policy accept; }
# Log rules for input and forwarding
add chain ip filter LOGGER
add chain ip filter LOGGERF
######
###### Logging rules
######
# Logging rule for INPUT drops
add rule ip filter LOGGER limit rate 2/minute burst 5 packets counter log prefix "IPTables-Dropped: "
add rule ip filter LOGGER counter drop
# Logging rule for FORWARD drops
add rule ip filter LOGGERF limit rate 2/minute burst 5 packets counter log prefix "IPTables-Dropped-Forward: "
add rule ip filter LOGGERF counter drop
######
###### INPUT rules
######
# Allow traffic for established connections, blocking invalid header format
add rule ip filter INPUT ct state related,established counter accept
add rule ip filter INPUT ct state invalid counter drop
# All localhost traffic allowed
add rule ip filter INPUT iifname "lo" counter accept
# The LAN can see this machine
add rule ip filter INPUT iifname "br-lan" counter accept
# Allow the world to ping this machine (amongst other things)
add rule ip filter INPUT ip protocol icmp counter accept
# Let guestnet and IoT see DNS servers on this machine (tcp+udp)
add rule ip filter INPUT iifname "br-guest" tcp dport 53 counter accept
add rule ip filter INPUT iifname "br-guest" udp dport 53 counter accept
add rule ip filter INPUT iifname "br-guest" tcp dport 853 counter accept
add rule ip filter INPUT iifname "br-guest" udp dport 853 counter accept
add rule ip filter INPUT iifname "br-iot" tcp dport 53 counter accept
add rule ip filter INPUT iifname "br-iot" udp dport 53 counter accept
add rule ip filter INPUT iifname "br-iot" udp dport 123 counter accept
# Log block traffic; this could be noisy, so only enable it if you
# want lots of logs!
## add rule ip filter INPUT counter jump LOGGER
# Block all other incoming traffic
add rule ip filter INPUT counter drop
######
###### OUTPUT rules
######
# Output is easy; we let this machine talk to anything
add rule ip filter OUTPUT ct state related,established counter accept
add rule ip filter OUTPUT ct state invalid counter drop
######
###### FORWARD rules
######
# Allow established connections to work, blocking invalid header format
add rule ip filter FORWARD ct state related,established counter accept
add rule ip filter FORWARD ct state invalid counter drop
# Allow the LAN to see the world
add rule ip filter FORWARD iifname "br-lan" counter accept
# Allow the VPN to see everything
add rule ip filter FORWARD iifname "tun0" counter accept
# Allow return traffic for NAT'd connections to work
add rule ip filter FORWARD iifname "br-wan" ct status dnat counter accept
# Allow guestnet to see external DNS servers
add rule ip filter FORWARD iifname "br-guest" tcp dport 53 counter accept
add rule ip filter FORWARD iifname "br-guest" udp dport 53 counter accept
add rule ip filter FORWARD iifname "br-guest" tcp dport 853 counter accept
add rule ip filter FORWARD iifname "br-guest" udp dport 853 counter accept
add rule ip filter FORWARD iifname "br-iot" tcp dport 53 counter accept
add rule ip filter FORWARD iifname "br-iot" udp dport 53 counter accept
# Allow my mobile devices to have access to everything
add rule ip filter FORWARD iifname "br-guest" ip saddr 10.100.100.8 counter accept
# Allow guestnet to see the printer
add rule ip filter FORWARD iifname "br-guest" ip daddr 10.0.0.10 counter accept
# Allow guestnet devices to see my MQTT server
add rule ip filter FORWARD iifname "br-guest" oifname "br-lan" tcp dport 1883 counter accept
# Allow guestnet to see the internet
add rule ip filter FORWARD iifname "br-guest" oifname "br-wan" counter accept
# Allow IoT devices to see my MQTT server
add rule ip filter FORWARD iifname "br-iot" oifname "br-lan" tcp dport 1883 counter accept
# Allow ESP8266 devices on IoT to be network updates
# (The process causes the ESP8266 to call back to a webserver on the host)
add rule ip filter FORWARD iifname "br-iot" oifname "br-lan" tcp dport 8266 counter accept
# Allow IoT devices to see the internet
add rule ip filter FORWARD iifname "br-iot" oifname "br-wan" counter accept
# Allow marked traffic (ie reflected traffic) to reach the destination
add rule ip filter FORWARD meta mark 100 counter accept
# Log and block all other forwarded traffic
add rule ip filter FORWARD counter jump LOGGERF
add rule ip filter FORWARD counter drop
######
###### NAT rules
######
add chain ip nat INPUT { type nat hook input priority 100; policy accept; }
add chain ip nat OUTPUT { type nat hook output priority -100; policy accept; }
add chain ip nat PREROUTING { type nat hook prerouting priority -100; policy accept; }
add chain ip nat POSTROUTING { type nat hook postrouting priority 100; policy accept; }
# Log specific incoming traffic
add rule ip nat PREROUTING iifname "br-wan" tcp dport 22 limit rate 2/minute burst 5 packets counter log prefix "IPTables-New-SSH: "
# Tag internal reflection traffic
add rule ip nat PREROUTING ip saddr 10.0.0.0/8 ip daddr @this_host tcp dport @reflect mark set 100
# DNAT incoming traffic from the internet (or internal send to br-wan address)
add rule ip nat PREROUTING ip daddr @this_host ip protocol tcp dnat ip addr . port to tcp dport map @fport
# Do NAT on egress traffic to the internet
add rule ip nat POSTROUTING oifname "br-wan" counter masquerade
# Also NAT marked traffic from internal to external IP
add rule ip nat POSTROUTING meta mark 100 counter masquerade
###############################################################################
#
# IPv6
#
###############################################################################
# IPv6 is simpler; there's no NAT involved. We allow LAN to see the
# world and only allow trusted external machines to see in to LAN
add chain ip6 filter INPUT { type filter hook input priority 0; policy accept; }
add chain ip6 filter OUTPUT { type filter hook output priority 0; policy accept; }
add chain ip6 filter FORWARD { type filter hook forward priority 0; policy accept; }
add rule ip6 filter INPUT ct state related,established counter accept
add rule ip6 filter INPUT ct state invalid counter drop
add rule ip6 filter INPUT iifname "lo" counter accept
add rule ip6 filter INPUT iifname "br-lan" counter accept
add rule ip6 filter INPUT meta l4proto ipv6-icmp counter accept
add rule ip6 filter INPUT ip6 saddr @my_hosts counter accept
add rule ip6 filter INPUT counter drop
add rule ip6 filter OUTPUT ct state related,established counter accept
add rule ip6 filter OUTPUT ct state invalid counter drop
add rule ip6 filter FORWARD ct state related,established counter accept
add rule ip6 filter FORWARD ct state invalid counter drop
add rule ip6 filter FORWARD iifname "br-lan" counter accept
add rule ip6 filter FORWARD meta l4proto ipv6-icmp icmpv6 type destination-unreachable counter accept
add rule ip6 filter FORWARD meta l4proto ipv6-icmp icmpv6 type packet-too-big counter accept
add rule ip6 filter FORWARD meta l4proto ipv6-icmp icmpv6 type time-exceeded counter accept
add rule ip6 filter FORWARD meta l4proto ipv6-icmp icmpv6 type parameter-problem counter accept
add rule ip6 filter FORWARD ip6 saddr @my_hosts counter accept
add rule ip6 filter FORWARD counter drop