Today I’m taking a look at my MONO Gateway - but with my own software twist. You may know I am not a fan of the OpenWRT project for end users (mostly due to their very poor and inconsistent documentation), and usually recommend OPNsense, but this time I want to build something DECLARATIVE! So I’m of course going back to just plain Debian.

I’m using systemd-networkd for this, so all of the configs are in systemd’s ini-style. I rather like this syntax.

Networkd Configs

Here are my example configs

Backup Interface

/etc/systemd/network/06_backup.network

# 'backup' network interface
# This does not depend on anything else, so we can always
# use it to SSH in via gateway.local
[Match]
Name=eth1

[Network]
IPv6AcceptRA=yes
IPv6SendRA=yes
MulticastDNS=yes

# RA options
[IPv6SendRA]
Managed=no
OtherInformation=no
#Not a router!
RouterLifetimeSec=0

# ula prefix (for internet-outage connectivity)
[IPv6Prefix]
Prefix=fdf9:ff03:0f91:0001::/64
Assign=true

Vlan Parent Interface

This interface just has VLAN slaves. /etc/systemd/network/10_eth0_vlans.network:

# Systemd Network unit for interface which is just a
# parent for VLAN interfaces
[Match]
Name=eth0

[Network]
DHCP=no
IPv6AcceptRA=no
#Carry VLANs on this physical interface
VLAN=eth0.20
VLAN=eth0.21

WAN Interface

This interface actually does the WAN upstream. Some notes in DHCPv6 are provider-specific. /etc/systemd/network/11_wan.network

# Systemd Network unit for WAN interface
# Replace name with your WAN interface name
[Match]
Name=eth2

[Network]
Description=WAN
#DHCP=both means * force ipv6 *
#Normally you would set DHCP=ipv4, meaning ipv6 depends on router advertisement M-flag
DHCP=both
IPv6AcceptRA=yes

[DHCPv4]
UseRoutes=yes
RouteMetric=100

[IPv6AcceptRA]
RouteMetric=100
DHCPv6Client=always

[DHCPv6]
#Force request even if nothing else indicates it should request PD (should not be needed)
ForceDHCPv6PDOtherInformation=yes
#Normal values here are /56 and /60
PrefixDelegationHint=::/59
#This means do not request an address at all
#UseAddress=no

LAN Interface

This is a basic LAN interface: /etc/systemd/network/31_lan20.network

# LAN interface on vlan 20
[Match]
Name=eth0.20

[Network]
Address=10.10.20.1/24
IPv6SendRA=yes
IPv6AcceptRA=no
DHCPServer=yes
DHCPPrefixDelegation=yes
IPMasquerade=ipv6

# DHCP server pool options
[DHCPServer]
PoolOffset=100
PoolSize=100
EmitDNS=yes
DNS=192.168.0.3

# RA options
[IPv6SendRA]
Managed=no
OtherInformation=no
RouterLifetimeSec=1800


# ula prefix (not required)
[IPv6Prefix]
Prefix=fdf9:ff03:0f91:0001::/64
Assign=true

#static prefixes (this can be copied as many times as you want)
#[IPv6Prefix]
#Prefix=2001:db8:10::/64
#Assign=true

#dynamic prefixes
[DHCPPrefixDelegation]
UplinkInterface=eth2
SubnetId=0x2
Announce=yes
Assign=yes
#Token can be used under any Prefix or PrefixDelegation
#This means a forced suffix
#Not needed, but some people like it
Token=::1

VLAN Device

# /etc/systemd/network/31_vlanXX.netdev
# Network Device for VLAN xx
[NetDev]
Name=eth0.20
Kind=vlan

[VLAN]
Id=20

Nftables.conf

This goes in /etc/nftables.conf - you may need a systemd unit to initialize it

#!/usr/sbin/nft -f

flush ruleset

#
# Local Variables
#
define WAN = eth2
define LAN20 = eth0.20
define LANS = { "eth0.20" }
define LAN_RANGES = "10.10.0.0/16"


#
# IPv4/IPv6 stateful firewall
#

table inet filter {

    # INPUT chain is sessions who terminate at this box
    # this is for services running on this box, not routed through
    # just to be clear
    chain input {
        type filter hook input priority filter;
        policy drop;

        # Loopback
        iifname "lo" accept

        # Backup interface
        iifname "eth1" accept

        iifname $WAN accept

        # Established/related connections
        ct state established,related accept

        # Invalid packets
        ct state invalid drop

        #
        # ICMP
        #
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept
        ip6 nexthdr 41 accept

        #
        # DHCP client
        #
        # IPv4 DHCP: client -> server
        iifname $WAN udp sport 68 udp dport 67 accept

        # IPv6 DHCP: client -> server
        iifname $WAN udp sport 546 udp dport 547 accept

        #
        # DHCP server for LAN
        #
        iifname $LANS udp sport 68 udp dport 67 accept
        iifname $LANS udp sport 546 udp dport 547 accept

        #
        # Router administration from LAN (SSH)
        #
        iifname $LAN20 tcp dport 22 accept
    }

    # FORWARD chain is where your normal firewall rules go
    # this is stuff that is ROUTED
    chain forward {
        type filter hook forward priority filter;
        policy drop;

        # Established/related connections
        ct state established,related accept

        # Invalid packets
        ct state invalid drop

        #
        # LAN -> WAN
        #
        iifname $LAN20 oifname $WAN accept

        iifname "tun_waw" accept

        #
        # IPv4 port forward
        # At the firewall stage, daddr+dport have been NATed
        #
        iifname $WAN oifname $LAN20 \
            ip daddr 10.10.20.150 \
            tcp dport 80 \
            ct state new accept

        #
        # IPv6: allow TCP/443 to the known EUI-64 host
        #
        iifname $WAN oifname $LAN20 \
            ip6 daddr & 0:0:0:0:ffff:ffff:ffff:ffff == ::be24:11ff:fe71:a41f \
            tcp dport 80 \
            ct state new accept
    }

    # OUTPUT chain is sessions who are initiated by the box
    chain output {
        type filter hook output priority filter;
        policy accept;
    }
}


#
# IPv4 NAT
#

table ip nat {

    # IPv4 Port Forwards go in PREROUTING
    chain prerouting {
        type nat hook prerouting priority dstnat;
        policy accept;

        # $WAN TCP/8080 -> LAN 10.10.20.150:80
        iifname $WAN \
            tcp dport 8080 \
            dnat to 10.10.20.150:80
    }

    # MASQUERADE goes in POSTROUTING
    chain postrouting {
        type nat hook postrouting priority srcnat;
        policy accept;

        # IPv4 LAN Internet access
        oifname $WAN \
            masquerade
    }
}