#! /usr/bin/nft -f

# Empty ruleset
flush ruleset

# some definitions
define pub_iface={{publicnetwork.iface}}
# a set is expensive if there's only one (ok for now)
define priv_iface={ {{privatenetworks|map(attribute='iface')|join(', ') }} }
define allifs={ {{publicnetwork.iface}}, {{privatenetworks|map(attribute='iface')|join(', ') }} }
define rfc_1918={ 192.168.0.0/16,
		  172.16.0.0/12,
		  10.0.0.0/8 }

# icmp_v6 -> allow some
define icmpv6_ok  = { nd-neighbor-solicit,
                      nd-neighbor-advert,
                      nd-router-advert,
                      destination-unreachable,
                      packet-too-big,
                      mld-listener-query,
                      mld-listener-report,
                      mld-listener-reduction,
                      echo-request,
                      echo-reply }


table inet filter {
	# conntrack valitity
	chain base_checks {
		# allow established/related connections
		ct state {established, related} accept
		# early drop of invalid connections
		ct state invalid drop
	}

	chain input {
		# We don't allow any type of access to the VR,
		# even if it's a container
		type filter hook input priority 0; policy drop;
		iif lo accept
		# verify related
		jump base_checks
		# allow arp on both
		iif $allifs ether type arp accept
		# allow IPv6 'L2' stuff
		ip6 daddr & :: == :: icmpv6 type $icmpv6_ok accept
		tcp dport { http, https } accept
		# no connections from the CS to this VR without verification
		iif $priv_iface jump private
		# for good measure ;-)
		counter drop
	}
	chain forward {
		type filter hook forward priority 0;
		# When we're having 4.12 and nft 0.8, we'll do notrack in raw for inter-priv
		# For now, just forward
		iif $priv_iface oif $priv_iface accept
		# verify related
		jump base_checks
		# conntracked and related out->in allow
		oif $priv_iface ct status dnat accept
		# For the rest, drop (that is this mainly for IPv6 forwarding)
		iif $pub_iface oif $priv_iface drop
		# for the rest, _if_ a vr would have another CS, forwarding can happen
		# NOTE, maybe good to envision some sort of filtering is CSes get connected
		# to one another; i.e.
		# ip saddr $host_in_CS1 ip daddr $host_in_CS2 tcp dport 3306 accept
		# and drop afterwards
	}
	chain output {
		type filter hook output priority 0;
		# don't output rfc_1918 on pub (IF you're double-natting, disable that one)
		# oif $pub_iface ip saddr $rfc_1918 drop

		# do we want smth else here?
	}
	chain private {
		# of course dhcp and udp dns is allowed
		# do we want tcp dns ?
		udp dport { domain, bootps } accept
		tcp dport domain accept
		counter drop
	}
}

table nat {
	chain prerouting {
		type nat hook prerouting priority -150;
		# DNAT magic happens here
		iif $pub_iface jump do_dnat
	}
	chain postrouting {
		type nat hook postrouting priority -150;
		# standard masquerade
		oif $pub_iface jump do_snat
	}
	chain do_dnat {
		# placeholder
	}
	chain do_snat {
		# placeholder
	}
}

# table raw {
# 	chain prerouting {
# 		type filter hook prerouting priority -300 \;
# 	}
# }

# dnat rules
{% for fw in portforwards -%}
{% for protocol in fw.protocols -%}
add rule ip nat do_dnat ip daddr {{fw.srcip}} {{protocol}} dport {{fw.srcport}} dnat {{fw.dstip}}:{{fw.dstport}}
{% endfor -%}
{% endfor -%}
add rule ip nat do_dnat return

# snat rest as one specific ip
{% for privatenetwork in privatenetworks %}
add rule ip nat do_snat ip saddr {{privatenetwork.subnet}} snat {{publicnetwork.ipaddress}} fully-random
{% endfor %}
