#!/usr/sbin/nft -f

#--- Hook order is: ingress -> prerouting -> input/output/forward -> postrouting

#--- Flush previous rules
flush ruleset

#--- Definitions
define wan = eth0
define vpn = wg0
define vpn_net = 10.16.76.1/22 #---VPN_NETWORK
#define ipsec_remote = 10.0.0.0/24

#--- "inet" say that this table will handle both ipv4 (ip) and ipv6 (ip6).
table inet firewall {
  #--- TCP ports to allow (ssh, http and https).
  set tcp_accepted {
    #--- "inet_service" are for tcp/udp ports, "flags interval" allows to set intervals.
    type inet_service; flags interval;
    elements = {80,443,10760} #---OpenTCPports/
  }

  #--- UDP ports to allow WireGuard.
  set udp_accepted {
    type inet_service; flags interval;
    elements = {53,500,4500,25237} #---OpenUDPports
  }

  chain incoming {
    type filter hook input priority 0; policy drop;

    # Drop invalid packets.
    ct state invalid drop

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

    # Limit ping requests.
    ip protocol icmp icmp type echo-request limit rate over 1/second burst 5 packets drop
    ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate over 1/second burst 5 packets drop

    # Allow all incmming established and related traffic.
    ct state established,related accept

    # Allow loopback.
    iif lo accept

    # Allow certain inbound ICMP types (ping, traceroute).
    ip protocol icmp icmp type { destination-unreachable, echo-reply, echo-request, source-quench, time-exceeded } accept
    # Without the nd-* ones ipv6 will not work.
    ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, echo-reply, echo-request, nd-neighbor-solicit,  nd-router-advert, nd-neighbor-advert, packet-too-big, parameter-problem, time-exceeded } accept

    # Allow needed tcp and udp ports.
    iifname $wan tcp dport @tcp_accepted ct state new accept
    iifname $wan udp dport @udp_accepted ct state new accept
    #iifname $vpn tcp dport @tcp_accepted ct state new accept
    #iifname $vpn udp dport @udp_accepted ct state new accept
   
    # Allow all incoming traffic from vpn
    iifname $vpn ct state new accept

    # Allow WireGuard clients to access DNS and services.
    iifname $vpn udp dport 53 ct state new accept
    
    # Allow VPN clients to communicate with each other.
    iifname $vpn oifname $vpn ct state new accept

    # Allows IPSEC StrongSwan trafic.
    ip protocol { ah, esp } accept

    meta ipsec exists accept
    #ipsec in ip saddr $ipsec_remote accept


  }

  chain forwarding {
    type filter hook forward priority 0; policy drop;

    # Drop invalid packets.
    ct state invalid drop

    # Forward all established and related traffic.
    ct state established,related accept

    # Forward WireGuard traffic.
    # Allow WireGuard traffic to access the internet via wan.
    iifname $vpn oifname $wan ct state new accept

    # Allow VPN clients to communicate with each other.
    iifname $vpn oifname $vpn accept

  }

  chain outgoing {
    type filter hook output priority 0; policy drop;

    # Drop invalid packets.
    ct state invalid drop

    # Allow all other outgoing traffic.
    # For some reason ipv6 ICMP needs to be explicitly allowed here.
    ip6 nexthdr ipv6-icmp accept
    ct state new,established,related accept
  }
}

# Separate table for hook pre- and postrouting.
# If using kernel 5.2 or later you can replace "ip" with "inet" to also filter IPv6 traffic.
table ip router {
  # With kernel 4.17 or earlier both need to be set even when one is empty.
  chain prerouting {
    type nat hook prerouting priority -100;
  }

  chain postrouting {
    type nat hook postrouting priority 100;

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

# Separate table for hook ingress to filter bad packets early.
table netdev filter {
  # List of ipv4 addresses to block.
  set blocklist_v4 {
    # The "ipv4_addr" are for ipv4 addresses and "flags interval" allows to set intervals.
    type ipv4_addr; flags interval;
    elements = {172.16.254.1,172.16.254.2} #---BloquedIPs
  }

  chain ingress {
    # For some reason the interface must be hardcoded here, variable do not work.
    type filter hook ingress device $wan priority -500;

    # Drop all fragments.
    ip frag-off & 0x1fff != 0 counter drop

    # Drop bad addresses.
    ip saddr @blocklist_v4 counter drop

    # Drop XMAS packets.
    tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg counter drop

    # Drop NULL packets.
    tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop

    # Drop uncommon MSS values.
    tcp flags syn tcp option maxseg size 1-535 counter drop
  }
}

