Skip to content

Instantly share code, notes, and snippets.

@surfaceflinger
Last active March 11, 2021 10:24
Show Gist options
  • Save surfaceflinger/cadf92a1cb5dcf60c04cc5d9d8695950 to your computer and use it in GitHub Desktop.
Save surfaceflinger/cadf92a1cb5dcf60c04cc5d9d8695950 to your computer and use it in GitHub Desktop.
#!/usr/sbin/nft -f
# Hook order is: ingress -> prerouting -> input/output/forward -> postrouting
# Start by flushing all the rules.
flush ruleset
# Defining variables is easy in nftables scripts.
define wan = eth0
define vpn = wg0
define vpn_net = 192.168.69.0/24
define vpn_address = 192.168.69.1
# Setting up a table, simple firewalls will only need one table but there can be multiple.
# The "inet" say that this table will handle both ipv4 (ip) and ipv6 (ip6).
# The name is "firewall" you can name it anything you like.
table inet firewall {
# Sets are dictionaries and maps of ports, addresses etc.
# These can then easily be used in the rules.
# Sets can be named whatever you like.
# TCP ports to allow, here we add ssh, http and https.
set tcp_accepted {
# The "inet_service" are for tcp/udp ports and "flags interval" allows to set intervals, see the mosh ports below.
type inet_service; flags interval;
elements = {
80,443,1337,6969,25565
}
}
# UDP ports to allow, here we add ports for WireGuard and mosh.
set udp_accepted {
type inet_service; flags interval;
elements = {
443,6969,46124,51820
}
}
# The first chain, can be named anything you like.
chain incoming {
# This line set what traffic the chain will handle, the priority and default policy.
# The priority comes in when you in another table have a chain set to "hook input" and want to specify in what order they should run.
# Use a semicolon to separate multiple commands on one row.
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
# OBS! Rules with "limit" need to be put before rules accepting "established" connections.
# Allow all incmming established and related traffic.
ct state established,related accept
# Allow loopback.
# Interfaces can by set with "iif" or "iifname" (oif/oifname). If the interface can come and go use "iifname", otherwise use "iif" since it performs better.
iif lo accept
# Allow certain inbound ICMP types (ping, traceroute).
# With these allowed you are a good network citizen.
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
# Allow WireGuard clients to access DNS and services.
iifname $vpn udp dport 53 ct state new accept
iifname $vpn tcp dport @tcp_accepted ct state new accept
iifname $vpn udp dport @udp_accepted ct state new 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 peers to access other VPN peers.
iifname $vpn oifname $vpn ct state new accept
# Allow WAN to access internal VPN network through port forwarding.
# iifname $wan oifname $vpn ct state new accept
}
chain outgoing {
type filter hook output priority 0; policy drop;
# I believe settings "policy accept" would be the same but I prefer explicit rules.
# 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;
# dnat example to use for port forwarding wan -> vpn peer
# it's better to use specialized reverse proxies if possible.
# iifname $wan tcp dport 6969 dnat 192.168.69.3
}
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
# snat example to use for port forwarding wan -> vpn peer
# it's better to use specialized reverse proxies if possible.
# oifname $vpn ip daddr 192.168.69.3 tcp dport 6969 snat $vpn_address
}
}
# 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 = {
203.0.113.100,203.0.113.101
}
}
chain ingress {
# For some reason the interface must be hardcoded here, variable do not work.
type filter hook ingress device eth0 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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment