#!/usr/sbin/nft -f + nftables scripts need this shebang with the -f option to run
# script variable definitions
+
* Device definitions can be found using ip a
* IP address should also be able to be found using ip a
.
* Where services are on separate server, virtual, container, etc., the ip a
may need to be run in on these servers.
* Another option would be to use sudo nmap -sn 192.168.1.0/24
to find all the related active ip address ip range.
define wan = eno1 #definition of wan device label, setup as ppp1 for pppoe
define modem = eno1 #definition of modem device label
define lan = br0 #definition of lan device label
define lan_ip4 = 192.168.1.0/24 #definition of lan IPV4 range
define router_ip4 = 192.168.1.1 #definition of router IPv4 address
define modem_ip = 192.168.5.2 #definition of modem IPv4 address
+ The $modem router interface has been assigned the IP address 192.168.5.2, as the modem device IP address is 192.168.5.1, See network interface.
define modem_ip4 = 192.168.5.0/24 #definition of modem IPv4 address range
+ The $modem router interface has been assigned the IP address 192.168.5.2, as the modem device IP address is 192.168.5.1, See network interface.
define http_server = 192.168.1.12 #definition of http server IPv4 address
define mail_server = 192.168.1.18 #definition of mail server IPv4 address
define wan_ip4 = 112.213.222.38 #definition of wan IPv4 address
define vpn = wg0 #definition of wireguard vpn device label
define vpn_ip = 192.168.6.0/24 #definition of wireguard IPv4 address range
define vpn_ip4 = 192.168.6.1 #definition of wireguard IPv4 server address
define vpn_port = 51914 #definition of wireguard server port
#define he_tunnel = he-ipv6 #
# Clean out the current ruleset
flush ruleset
+Clears the previous ruleset. This flushes out all the tables, chains and rules so we can start clean. This is not done automatically so without this, previously added rules would still be in effect. See nft man page Ruleset for more information.
table inet firewall {
+This defines a table. A table is a container for chains (and sets). This line defines a table with the family inet
and name firewall
. inet
is a family that encompasses both IPv4 and IPv6 internet protocols. I wanted a shared configuration between both so I chose this one, alternatively I could have restricted it to either by using ip or ip6. See nft man page Tables for more information.
# The set controllist is used to dynamically add IP address(es) to a timed drop list for LAN access control
+ This has been separately described in detail at the following link: NFTables IP Control, also see wiki.nftables on Names Sets & nft man page Sets.
set controllist {
type ipv4_addr
flags interval
flags timeout
}
# Remember that the input and output chains only refer to this machine.
# The forward and nat chains refer to routing.
# open ports refers to ports to open for input to this machine.
# ssh is port 22 for SSH on both TCP and UDP, but we only use TCP!
# bootps, port 67 and bootpc, port 68 are for DHCP on both TCP and UDP, but we only use UDP!
# domain is port 53 for DNS requests on TCP & UDP.
# openvpn is port 1194 on both TCP and UDP, but we will not be using this antiquated protocol.
set tcp_open_ports {
+ This creates a set of type inet_service (port number or range). The flags interval directive enables port ranges to be used. After that, you just set elements to add members to the set. Having a set is a clean and efficient way to later reference all of these values in the rules. We can use port numbers, service names and port ranges.
Sets provide a performant way to use generic sets of data, that can be dynamically manipulated, so they are very suitable for tasks like IP blocking. More about sets on the nftables wiki page Names Sets & nft man page Sets.
type inet_service; flags interval;
elements = {
ssh,
domain
}
}
set udp_open_ports {
+ This creates a set of type inet_service (port number or range). The flags interval directive enables port ranges to be used. After that, you just set elements to add members to the set. Having a set is a clean and efficient way to later reference all of these values in the rules. We can use port numbers, service names and port ranges.
Sets provide a performant way to use generic sets of data, that can be dynamically manipulated, so they are very suitable for tasks like IP blocking. More about sets on the nftables wiki page Names Sets & nft man page Sets.
type inet_service; flags interval;
elements = {
domain,
$vpn_port, # Wireguard port
60000-61000 # mosh port interval
}
}
chain tcp_cek {
+ A chain is a container for rules. This a regular (non-base) chain. As a regular chain does not define a type or hook it can only be called from another chain with a jump (or goto) statement. See wiki.nftables Configuring Chains & nft man page Chainsfor more information
# bad tcp → avoid network scanning:
tcp flags & (fin|syn) == (fin|syn) counter drop
tcp flags & (syn|rst) == (syn|rst) counter drop
tcp flags & (fin|syn|rst|psh|ack|urg) < (fin) counter drop
tcp flags & (fin|syn|rst|psh|ack|urg) == (fin|psh|urg) counter drop
}
chain icmp_cek {
+ A chain is a container for rules. This a regular (non-base) chain. As a regular chain does not define a type or hook it can only be called from another chain with a jump (or goto) statement. See wiki.nftables Configuring Chains for more information.
More information on icmp can be seen at nftables man ICMP Header Expression.
Our table type is inet which means IPv4 or IPv6. However, ICMP is different to ICMPv6. This means that we have to do our checks with version specific directives. The ip and ip6 directives do that. After more version specific checks, we match the version specific types.
# no ping floods
ip protocol icmp counter
ip protocol icmp limit rate over 2/second burst 5 packets counter drop + This limits the incoming rate of icmp packets handled
ip6 nexthdr ipv6-icmp counter
ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate over 2/second burst 5 packets counter drop + This limits the incoming rate of icmpv6 packets handled
# allow icmp
ip protocol icmp icmp type { + This allows the icmp packets to be handled
echo-request, echo-reply, time-exceeded,
parameter-problem, destination-unreachable
} counter accept
ip6 nexthdr icmpv6 icmpv6 type { + This allows the icmpv6 packets to be handled
echo-request, echo-reply, time-exceeded,
parameter-problem, destination-unreachable,
packet-too-big, nd-router-advert, nd-router-solicit,
nd-neighbor-solicit, nd-neighbor-advert,
mld-listener-query
} counter accept
}
chain input {
+ See wiki.nftables Configuring Chains for more information. The name does not actually matter, but was chosen to align with iptables. This is a base chain, hence base chain type and base chain hook must be defined.
type filter hook input priority 0; policy drop;
+ This statement defines the the base chain's: type (filter), hook (input), priority (0) and policy (drop), any packet that makes it way through all the table chain is at the end drop (nftable default policy is accept, hence it is necessary to set so). See wiki.nftables Configuring Chains for more information.
# drop connections from blacklisted addresses
# ip saddr @blacklist counter drop
# Cek valid tcp packet
ip protocol tcp jump tcp_cek + We use the jump statement as we want to return and continue to process chains after this statement.
# Cek valid icmp communications
jump icmp_cek + We use the jump statement as we want to return and continue to process chains after this statement.
#count all the wan SSH attemps
#iifname $wan tcp dport ssh counter
# avoid brute force on ssh: (if using external ssh)
#tcp dport ssh limit rate over 8/minute counter drop
+ Rate limit and count WAN ssh attempts. As I only allow ssh locally on LAN or via VPN I drop all such incoming traffic. (Also probably would not work without NAT prerouting dnat.)
# In general input filter drops and limits list should be placed above here here
+ Position of rules is important in chains. Once a chain rule accepts, drops or rejects a packet subsequent chain rules are no longer processed!)
# accept established connections
+ This rule is to allow already established or related connections through. If the connection has already been established, it probably means it was already allowed by us earlier and we can just continue allowing it.
ct
is used to tap into the connection tracking entry associated with the packet. In this case, we are accessing the state of the connection, and checking if it is in the set {established, related}
. If it is in the set, accept the packet, otherwise, continue to the next rule.
ct state established, related counter accept
# drop invalid state packets
+ This is similar to the previous line, but this time, instead of checking if the state is within a set, we only check if the connection state is invalid
and if so, we drop the packet. That is, we just ignore the packet as if it never came in. Otherwise continue on to the next rule.
ct state invalid counter drop
# accept input from loopback interface
+ It is generally accepted practice to accept all packets from the loopback interface.
iif lo counter accept
# accept input from LAN
+ Filtering of LAN incoming packets. It can be done by port or port sets, however in general all the LAN interface inputs can be accepted. Filtering by port probably does improve security, but any applications using ports not in the filter will be broken until added.
iifname $lan counter accept # By port is a pain in the arse!
# iifname $lan tcp dport { ssh, domain, 667, 953 } counter accept + This is used to accept specific LAN tcp ports, required if not accepting all.
# iifname $lan udp dport { domain, bootps, bootpc, 667, 953} counter accept + This is used to accept specific LAN tcp ports, required if not accepting all.
# accept input from VPN
+ Filtering of VPN incoming packets. It can be done by port or port sets, however in general the all the VPN interface inputs can be accepted. Filtering by port probably does improve security, but any applications using ports not in the filter will be broken until added.
iifname $vpn counter accept # By port is a pain in the arse!
ip daddr $vpn_ip4 udp dport $vpn_port counter accept
+ As the VPN is hosted on this local machine this chain allows packets that have been preroute dnat to the VPN IP address to be accepted.
# iifname $wan udp dport $vpn_port ct state new counter accept
# everything else reject port unreachable
ct state new counter reject
+ Any other new packets coming to the filter input are unknown and should be dropped or rejected. Dropped means the packet is simply ignored and not further processed. Reject means the packet is ignored and not further processed and a ICMP(v4 or v6) host-unreachable message is returned to the source. See wiki.nftables Rejecting traffic for more information.
}
chain forward {
+ See wiki.nftables Configuring Chains for more information. The name does not actually matter, but was chosen to align with iptables. This is a base chain, hence base chain type and base chain hook must be defined.
type filter hook forward priority 0; policy accept;
+ This statement defines the the base chain's: type (filter), hook (forward), priority (0) and policy (accept), any packet that makes it way through all the table chain is at the end drop (nftable default policy is accept, hence it is necessary to set so). See wiki.nftables Configuring Chains for more information.
#drop all IP addresses in the control list both source and destination
+ This has been separately described in detail at the following link: NFTables IP Control, also see wiki.nftables on Names Sets & nft man page Sets.
ip daddr @controllist counter drop
ip saddr @controllist counter drop
#limit the IP address range bandwidth using WAN
+ This is a simple form of traffic control. My DHCP / DNS place critical IP address in the 192.168.1.1 to 99 range, other equipment is automatically assigned in the 100-200 range. The chains below then limit traffic speed into / out of these IP addresses. This has been separately described in detail at the following link: Limit Rate on IP Address Range
iifname $wan ip daddr { 192.168.1.100-192.168.1.200 } limit rate over 1300kbytes/second burst 3000kbytes counter drop
oifname $wan ip saddr { 192.168.1.100-192.168.1.200 } limit rate over 750kbytes/second burst 1200kbytes counter drop
oifname $wan tcp flags syn tcp option maxseg size set rt mtu # This automatically set the tcp mtu
#oifname $wan tcp flags syn tcp option maxseg size set 1452 counter # This set the tcp mtu directly to a maximum value
# In general forward filter drops and limits list should be placed above here here
+ Position of rules is important in chains. Once a chain rule accepts, drops or rejects a packet subsequent chain rules are no longer processed!)
# accept established connections
+ This rule is to allow already established or related connections through. If the connection has already been established, it probably means it was already allowed by us earlier and we can just continue allowing it.
ct
is used to tap into the connection tracking entry associated with the packet. In this case, we are accessing the state of the connection, and checking if it is in the set {established, related}
. If it is in the set, accept the packet, otherwise, continue to the next rule.
ct state established, related counter accept
# drop invalid state packets
+ This is similar to the previous line, but this time, instead of checking if the state is within a set, we only check if the connection state is invalid
and if so, we drop the connection. That is, we just ignore the packet as if it never came in. Otherwise continue on to the next rule.
ct state invalid counter drop
#All traffic from lan is accepted
iifname $lan counter accept # accept anything from the LAN, keep it simple.
# Allow traffic coming from the vpn
iifname $vpn counter accept # accept anything from the VPN, keep it simple.
# Allow new connections to internal servers, but only those that have had dnat performed.
# The server names can be used where host DNS resolves to IP address
ip daddr $http_server ct status dnat counter accept
+ The incoming WAN packets that have been preroute dnat to the http server IP address to be accepted.
ip daddr $mail_server ct status dnat counter accept
+ The incoming WAN packets that have been preroute dnat to the mail server IP address to be accepted.
#ip daddr $vpn_ip4 ct status dnat counter accept # required if VPN service is not on main router IP
# other option is perhaps more verbose, e.g.
#ip daddr $http_server tcp dport {http, https} counter accept
#ip daddr $mail_server tcp dport {http, https, pop3s, imaps, smtp} counter accept
#ip daddr $vpn_ip4 udp dport $vpn_port counter accept
#Hurricane Electric IPv6 tunnel
#iifname $he_tunnel counter jump base_checks # not required as we do this for all connections above
#iifname $lan oifname $he_tunnel counter accept
# drop and count everything else not accounted for,
# probably does not have much use on forward
counter drop
}
chain output {
# We allow everything out
type filter hook output priority 0; policy accept;
#counters for different devices, ip address, port, etc. place first
ip saddr $vpn_ip4 counter
# In general output filter drops and limits list should be placed above here here
+ Position of rules is important in chains. Once a chain rule accepts, drops or rejects a packet subsequent chain rules are no longer processed!)
# Allow packets with existing state & count
ct state established,related counter accept
# Allow all new packets on Router output & count
ct state new counter accept
# Drop all invalid packet and count
ct state invalid counter drop
}
}
table ip nat {
#map tcp_nat_map {
# type inet_service : ipv4_addr
# elements = { http : 192.168.1.16, https : 192.168.1.16}
#}
#map udp_nat_map {
# type inet_service : ipv4_addr
#}
#chain wan_in {
# tcp dport { http, https} counter dnat to $http_server
# tcp dport { pop3s, imaps, smtp} counter dnat to $mail_server
# udp dport $vpn_port counter dnat to 192.168.6.1
# dnat tcp dport map @tcp_nat_map
# dnat udp dport map @udp_nat_map
#}
chain prerouting {
+ If we decalre postrouting nat we must also decalare prerouting nat, even if we do not need any rules in the prerouting chain.
type nat hook prerouting priority 0; policy accept;
#iifname $wan jump wan_in
# dnat - direct allowed by port number wan incoming services to correct lan server ip.
ip daddr $wan_ip4 tcp dport {http, https} counter dnat to $http_server
+ Incoming WAN packets to the http or https ports are preroute dnat to the webserver IP address.
ip daddr $wan_ip4 tcp dport { pop3s, imaps, imap2, smtp, submission, submissions } counter dnat to $mail_server
+ Incoming WAN packets to the mail ports, pop3s, imaps or smtp ports are preroute dnat to the mail server IP address.
ip daddr $wan_ip4 udp dport $vpn_port counter dnat to $vpn_ip4
+ Incoming WAN packets to the VPN port are preroute dnat to the VPN IP address.
}
chain postrouting {
type nat hook postrouting priority 0; policy accept;
#Allow internal clients to correctly see external address “hairpin dnat”
+ Hairpin nat is dicussed in greater death at hairpin nat.
ip saddr $lan_ip4 ip daddr $http_server tcp dport { http, https } counter snat $router_ip4
ip saddr $lan_ip4 ip daddr $mail_server tcp dport { http, https, pop3s, imap2, imaps, smtp, submission, submissions } counter snat $router_ip4
#Standard postrouting nat
+ The examples below show different levels of granularity in control.
#oifname $modem counter masquerade #needed with dynamic wan ip address
ip saddr $lan_ip4 ip daddr $modem_ip4 counter snat $modem_ip
ip saddr $vpn_ip ip daddr $modem_ip4 counter snat $modem_ip
#oifname $wan counter masquerade #needed with dynamic wan ip address
ip saddr $lan_ip4 oifname $wan counter snat $wan_ip4
ip saddr $vpn_ip oifname $wan counter snat $wan_ip4
#ip saddr { $lan_ip4, $vpn_ip4 }oifname $wan counter snat $wan_ip4 #with ip set
#oifname { $wan, $modem } counter masquerade #with device set
}
}