#!/sbin/nft -f

# --------------------------------------------------------------------------------- #

# References:
# https://wiki.nftables.org/wiki-nftables/index.php/Main_Page
# https://wiki.gentoo.org/wiki/Nftables/Examples
# https://wiki.archlinux.org/title/Nftables
# https://github.com/krabelize/nftables-firewall-config/blob/master/nftables.conf
# https://github.com/atweiden/archvault/blob/master/resources/etc/nftables.conf
# https://xdeb.org/post/2019/09/26/setting-up-a-server-firewall-with-nftables-that-support-wireguard-vpn/

# Libvirt:
# https://libvirt.org/firewall.html

# Notes:
# - "limit" rules need to be put before "established" connections
# - use sets for groups of things (eg. IP, ports, ...)
# - explicitly allow IPv6 ICMP if do not have "policy accept" on the outgoing chain
# - hook order: ingress -> prerouting -> input/output/forward -> postrouting
# - "iif" should be use when possible (for persistent interfaces) since it is faster than "iifname"

# --------------------------------------------------------------------------------- #

flush ruleset

# TCP ports to accept (both IPv4 and IPv6)
#define ACCEPT_TCP_PORTS = { 80, 443, 1965 }

# UDP ports to accept (both IPv4 and IPv6)
#define ACCEPT_UDP_PORTS = { 51820 }

# Wireguard (use for the 'server' only)
define vpn = wg0
define wan = eth0
define vpn_net = 10.2.0.0/24

table inet filter {
	chain libvirt_input {
		iifname "virbr0" udp dport 53 counter accept
		iifname "virbr0" tcp dport 53 counter accept
		iifname "virbr0" udp dport 67 counter accept
		iifname "virbr0" tcp dport 67 counter accept
	}

	chain libvirt_forward {
		oifname "virbr0" ip daddr 192.168.122.0/24 ct state { established, related } counter accept
		iifname "virbr0" ip saddr 192.168.122.0/24 counter accept
		iifname "virbr0" oifname "virbr0" counter accept
		oifname "virbr0" counter reject with icmpx type port-unreachable
		iifname "virbr0" counter reject with icmpx type port-unreachable
	}

    chain wireguard_input {
        # Allow WireGuard clients to access DNS
        iifname $vpn udp dport 53 ct state new counter accept

        # Allow VPN clients to communicate with each other
        iifname $vpn oifname $vpn ct state new accept
    }

    chain wireguard_forward {
        iifname $vpn oifname $wan ct state new accept
    }

	# Default to drop all inbound traffic, unless they meet the criteria
	chain input {
		type filter hook input priority 0; policy drop;

		# Drop invalid packets early
		ct state invalid counter drop

		# Drop none SYN packets
		#tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop

		# Reject AUTH to make it fail fast
		tcp dport 113 reject with icmpx type port-unreachable

		# Rate limit on SSH port
		#tcp dport ssh ct state new limit rate 6/minute accept

		# Mitigate ping floods
		ip protocol icmp icmp type { echo-reply, echo-request } limit rate over 1/second burst 4 packets drop
		ip6 nexthdr icmpv6 icmpv6 type { echo-reply, echo-request } limit rate over 1/second burst 4 packets drop

		ct state { established, related } counter accept
		ct status dnat accept

		# Allow loopback from host
		iif lo accept
		iif != lo ip daddr 127.0.0.1/8 counter drop
		iif != lo ip6 daddr ::1/128 counter drop

		counter jump libvirt_input
        counter jump wireguard_input

		# Accept user-defined ports
		#tcp dport $ACCEPT_TCP_PORTS ct state new counter accept
		#udp dport $ACCEPT_UDP_PORTS ct state new counter accept

		# Accept ICMPv4
		ip protocol icmp icmp type {
			echo-reply,
			echo-request,
			destination-unreachable,
			time-exceeded,
			parameter-problem,
			router-advertisement,
			router-solicitation
		} counter accept

		# Accept basic ICMPv6 functionality
		ip6 nexthdr icmpv6 icmpv6 type {
			echo-reply,
			echo-request,
			destination-unreachable,
			packet-too-big,
			time-exceeded,
			parameter-problem
		} counter accept

		# Allow ICMPv6 SLAAC
		ip6 nexthdr icmpv6 icmpv6 type {
			nd-router-solicit,
			nd-router-advert,
			nd-neighbor-solicit,
			nd-neighbor-advert,
		} ip6 hoplimit 255 counter accept

		# Allow ICMPv6 multicast listener discovery on link-local
		ip6 nexthdr icmpv6 icmpv6 type {
			mld-listener-query,
			mld-listener-report,
			mld-listener-reduction,
			mld2-listener-report,
		} ip6 saddr fe80::/10 counter accept

		counter comment "Count dropped packets"
		#log prefix "[nftables] Inbound Denied: " flags all counter drop
	}

	# Route your own packets! I'm not your router.
	# Can be enabled while using VPN (for tunneling)
	chain forward {
		type filter hook forward priority 0; policy drop;

		ct state { related, established } accept
		ct status dnat accept

		oif lo accept
		oif != lo ip daddr 127.0.0.1/8 counter drop
		oif != lo ip6 daddr ::1/128 counter drop

		counter jump libvirt_forward
        counter jump wireguard_forward

		counter comment "Count dropped packets"
		#log prefix "[nftables] Forward Denied: " flags all counter drop
	}

	# Accept all outbound traffic
	chain output {
		type filter hook output priority 0; policy accept;
		counter comment "Count accepted packets"
		#log prefix "[nftables] Outbound Accepted: " flags all counter accept
	}
}

table inet nat {
	chain libvirt_postrouting {
		ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter masquerade
	}

    chain wireguard_postrouting {
        # Masquerade WireGuard traffic
        # All WireGuard traffic will look like it comes from the servers IP address
        oifname $wan ip saddr $vpn_net counter masquerade
    }

	chain postrouting {
		type nat hook postrouting priority 100; policy accept;
		counter jump libvirt_postrouting
        counter jump wireguard_postrouting
	}
}
