Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- nftables is the successor to iptables and it is time for OpenWRT to make the switch. Before we can start implementing firewall4, we need to get a feel for how nftables works, possibilities etc. The set of commands below represents my attempt at understanding nftables, and how we initially can leverage at least some of the new features. My commands are a manual conversion of an iptables filter-table corresponding to the configuration at the bottom of this document. I have not tested my nftables configuration very thorougly, but at least the base functionality works.
- nftables has gotten rid of the default tables and chains from iptables, and leaves the configuration completely up to a user. A table is just a collection of chains, and base chains are given a hook and a priority. In other words, a user has completely control over when a chain is hit and the order in which chains are hit. base chains roughly works in the same way as the default iptables chains, and more information about hooks and priorities can be found here: https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains
- My nftables-configuration closely matches the structure of the iptables rules. There are three base chains - filter_input, filter_forward and filter_output - that all have the filter priority and run on the correct hooks. On the machine I used for testing there were two firewall zones - wan and lan - so the rest of the chains are the normal "zone_lan_input", "zone_lan_forward", etc. The only difference is that all the chains are prefixed with "filter_".
- The biggest difference from iptables, if you ask me, is that there is one table and one set of chains for both ipv4 and ipv6. nftables supports dual-configuration via the "inet"-family, and not having to main two set of rules greatly reduces size of ruleset and complexity if you ask me. Some rules only apply to either family, but instead of creating a complex structure with new tables and sets of chains, I have instead just added a match on l3-protocol. Another approach would be to keep ipv4/ipv6-specific rules in separate chains, and only jump to this chain if the l3-protcol matches.
- Another big difference is that the use of sets eliminates or at least reduces the size of the old zone_lan_src_ACCEPT, zone_wan_dest_ACCEPT, etc. chains. By adding the interfaces belonging to a chain to a set, we can match on this set and for example accept right away or jump to a much smaller version of zone_wan_dest_ACCEPT.
- I originally planned to use maps (which is built on top of sets) plus a feature called concatination for the traffic rules as well. Concatination enables set elements to consist of multiple selectors, so it is possible to create for example tuples. However, I chose not to do this as it is for example not possible to do wildcard-matching or cidr-matching in a set. Thus, we would have to create a large number of different types of set. While it would be possible to for example create the different set types on demand, this might quicly grow complex. I think we should stick with traffic rules as they are today, and then monitor/contribute to upstream. Sets can probably still be used for DNAT/SNAT, as well as rules for ICMP (already done in my example commands).
- Based on this experience, forking firewall3 and adding initial nftables support should not be that hard. There are some things that are not available in nftables (like date), but it seems like all features we need are already present. We need to decide on an overall structure, but once those are out of the way then the amount of work should not be overwhelming. I hope my set of rules and description can serve as useful input to OpenWRT + nftables.
- ## Rules
- #C&P to a file, then load the filter with nft -f <filename>
- #Flush all nftables rules
- flush ruleset
- #Create table, it will be used for both ipv4 and ipv6
- add table inet firewall4
- #Create base filter chains
- add chain inet firewall4 filter_input {type filter hook input priority 0; policy accept;}
- add chain inet firewall4 filter_forward {type filter hook forward priority 0; policy drop;}
- add chain inet firewall4 filter_output {type filter hook output priority 0; policy accept;}
- #Create output/forward/input zone rules
- add chain inet firewall4 filter_zone_lan_input
- add chain inet firewall4 filter_zone_lan_forward
- add chain inet firewall4 filter_zone_lan_output
- add chain inet firewall4 filter_zone_wan_input
- add chain inet firewall4 filter_zone_wan_forward
- add chain inet firewall4 filter_zone_wan_output
- #Create chains for user controlled rules
- add chain inet firewall4 filter_input_rule
- add chain inet firewall4 filter_forwarding_rule
- add chain inet firewall4 filter_output_rule
- #Create lan zone user chains
- add chain inet firewall4 filter_input_lan_rule
- add chain inet firewall4 filter_forwarding_lan_rule
- add chain inet firewall4 filter_output_lan_rule
- #Create wan zone user chains
- add chain inet firewall4 filter_input_wan_rule
- add chain inet firewall4 filter_forwarding_wan_rule
- add chain inet firewall4 filter_output_wan_rule
- #Create and populate reject chain. The reason for not keeping the reject rules
- #in a map, is that we have no guarantee the tcp match is evalued before the any
- #match. Reason for the strange chain name is that reject is a
- add chain inet firewall4 filter_reject_c
- add rule inet firewall4 filter_reject_c ip protocol tcp reject with tcp reset
- add rule inet firewall4 filter_reject_c reject #Default reject action is port-unreachable
- #Create and populate zone interface sets
- add set inet firewall4 zone_lan_if { type ifname; }
- add set inet firewall4 zone_wan_if { type ifname; }
- add element inet firewall4 zone_lan_if { eth2, eth2.245 }
- add element inet firewall4 zone_wan_if { eth0, eth1 }
- #Populate input
- add rule inet firewall4 filter_input iif lo accept
- add rule inet firewall4 filter_input jump filter_input_rule
- add rule inet firewall4 filter_input ct state { related, established } accept
- add rule inet firewall4 filter_input tcp flags & (fin|syn|rst|ack) == syn limit rate over 25/second burst 50 packets drop
- add rule inet firewall4 filter_input iifname @zone_lan_if jump filter_zone_lan_input
- add rule inet firewall4 filter_input iifname @zone_wan_if jump filter_zone_wan_input
- #Populate forwarding
- add rule inet firewall4 filter_forward jump filter_forwarding_rule
- add rule inet firewall4 filter_forward ct state { related, established } accept
- add rule inet firewall4 filter_forward iifname @zone_lan_if jump filter_zone_lan_forward
- add rule inet firewall4 filter_forward iifname @zone_wan_if jump filter_zone_wan_forward
- add rule inet firewall4 filter_forward jump filter_reject_c
- #Populate output
- add rule inet firewall4 filter_output oif lo accept
- add rule inet firewall4 filter_output jump filter_output_rule
- add rule inet firewall4 filter_output ct state { related, established } accept
- add rule inet firewall4 filter_output iifname @zone_lan_if jump filter_zone_lan_output
- add rule inet firewall4 filter_output iifname @zone_wan_if jump filter_zone_wan_output
- #zone_wan_dest_ACCEPT, no need to check for interface since we use the set as a match before jumping here
- add chain inet firewall4 filter_zone_wan_dest_ACCEPT
- add rule inet firewall4 filter_zone_wan_dest_ACCEPT ct state invalid drop
- add rule inet firewall4 filter_zone_wan_dest_ACCEPT accept
- #zone_lan_forward
- add rule inet firewall4 filter_zone_lan_forward jump filter_forwarding_lan_rule
- add rule inet firewall4 filter_zone_lan_forward oifname @zone_wan_if jump filter_zone_wan_dest_ACCEPT
- add rule inet firewall4 filter_zone_lan_forward ip version 4 ct status dnat accept
- add rule inet firewall4 filter_zone_lan_forward oifname @zone_lan_if accept
- #zone_lan_input
- add rule inet firewall4 filter_zone_lan_input jump filter_input_lan_rule
- add rule inet firewall4 filter_zone_lan_input ct status dnat accept
- add rule inet firewall4 filter_zone_lan_input iifname @zone_lan_if ct state { new, untracked } accept
- #zone_lan_output
- add rule inet firewall4 filter_zone_lan_output jump filter_output_lan_rule
- add rule inet firewall4 filter_zone_lan_output oifname @zone_lan_if accept
- #zone_wan_output
- add rule inet firewall4 filter_zone_wan_output jump filter_output_wan_rule
- add rule inet firewall4 filter_zone_wan_output oifname @zone_wan_if jump filter_zone_wan_dest_ACCEPT
- #zone_wan_forward
- add rule inet firewall4 filter_zone_wan_forward jump filter_forwarding_wan_rule
- add rule inet firewall4 filter_zone_wan_forward icmpv6 type { echo-request, echo-reply, destination-unreachable, packet-too-big, time-exceeded } limit rate 1000/second burst 5 packets accept
- add rule inet firewall4 filter_zone_wan_forward icmpv6 type parameter-problem icmpv6 code {0,1} limit rate 1000/second burst 5 packets accept
- add rule inet firewall4 filter_zone_wan_forward meta l4proto esp oifname @zone_lan_if accept
- add rule inet firewall4 filter_zone_wan_forward udp dport 500 oifname @zone_lan_if accept
- add rule inet firewall4 filter_zone_wan_forward tcp sport . tcp dport { 1234 . 5678 } oifname @zone_lan_if jump filter_reject_c
- add rule inet firewall4 filter_zone_wan_forward meta l4proto {tcp, udp} @th,16,16 9997 oifname @zone_lan_if drop
- add rule inet firewall4 filter_zone_wan_forward meta protocol ip ct status dnat accept
- add rule inet firewall4 filter_zone_wan_forward oifname @zone_wan_if jump filter_reject_c
- #zone_wan_input
- add rule inet firewall4 filter_zone_wan_input jump filter_input_wan_rule
- add rule inet firewall4 filter_zone_wan_input meta protocol ip udp dport 68 accept
- add rule inet firewall4 filter_zone_wan_input ip6 saddr fc00::/6 udp dport 546 accept
- add rule inet firewall4 filter_zone_wan_input meta protocol ip meta l4proto igmp accept
- add rule inet firewall4 filter_zone_wan_input icmp type echo-request accept
- add rule inet firewall4 filter_zone_wan_input ip6 saddr fe80::/10 icmpv6 type { mld-listener-query, mld-listener-report, mld-listener-reduction } icmpv6 code 0 accept
- add rule inet firewall4 filter_zone_wan_forward icmpv6 type { echo-request, echo-reply, destination-unreachable, packet-too-big, time-exceeded, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } limit rate 1000/second burst 5 packets accept
- add rule inet firewall4 filter_zone_wan_forward icmpv6 type parameter-problem icmpv6 code {0,1} limit rate 1000/second burst 5 packets accept
- add rule inet firewall4 filter_zone_wan_input meta protocol ip ct status dnat accept
- add rule inet firewall4 filter_zone_wan_input iifname @zone_wan_if drop
- ## Firewall configuration
- config defaults
- option syn_flood 1
- option input ACCEPT
- option output ACCEPT
- option forward REJECT
- # Uncomment this line to disable ipv6 rules
- # option disable_ipv6 1
- config zone
- option name lan
- list network 'lan'
- list network 'lan2'
- option input ACCEPT
- option output ACCEPT
- option forward ACCEPT
- config zone
- option name wan
- list network 'wan'
- list network 'wan6'
- list network 'wan2'
- option input DROP
- option output ACCEPT
- option forward REJECT
- option masq 1
- option mtu_fix 1
- config forwarding
- option src lan
- option dest wan
- # We need to accept udp packets on port 68,
- # see https://dev.openwrt.org/ticket/4108
- config rule
- option name Allow-DHCP-Renew
- option src wan
- option proto udp
- option dest_port 68
- option target ACCEPT
- option family ipv4
- # Allow IPv4 ping
- config rule
- option name Allow-Ping
- option src wan
- option proto icmp
- option icmp_type echo-request
- option family ipv4
- option target ACCEPT
- config rule
- option name Allow-IGMP
- option src wan
- option proto igmp
- option family ipv4
- option target ACCEPT
- # Allow DHCPv6 replies
- # see https://dev.openwrt.org/ticket/10381
- config rule
- option name Allow-DHCPv6
- option src wan
- option proto udp
- option src_ip fc00::/6
- option dest_ip fc00::/6
- option dest_port 546
- option family ipv6
- option target ACCEPT
- config rule
- option name Allow-MLD
- option src wan
- option proto icmp
- option src_ip fe80::/10
- list icmp_type '130/0'
- list icmp_type '131/0'
- list icmp_type '132/0'
- list icmp_type '143/0'
- option family ipv6
- option target ACCEPT
- # Allow essential incoming IPv6 ICMP traffic
- config rule
- option name Allow-ICMPv6-Input
- option src wan
- option proto icmp
- list icmp_type echo-request
- list icmp_type echo-reply
- list icmp_type destination-unreachable
- list icmp_type packet-too-big
- list icmp_type time-exceeded
- list icmp_type bad-header
- list icmp_type unknown-header-type
- list icmp_type router-solicitation
- list icmp_type neighbour-solicitation
- list icmp_type router-advertisement
- list icmp_type neighbour-advertisement
- option limit 1000/sec
- option family ipv6
- option target ACCEPT
- # Allow essential forwarded IPv6 ICMP traffic
- config rule
- option name Allow-ICMPv6-Forward
- option src wan
- option dest *
- option proto icmp
- list icmp_type echo-request
- list icmp_type echo-reply
- list icmp_type destination-unreachable
- list icmp_type packet-too-big
- list icmp_type time-exceeded
- list icmp_type bad-header
- list icmp_type unknown-header-type
- option limit 1000/sec
- option family ipv6
- option target ACCEPT
- config rule
- option name Allow-IPSec-ESP
- option src wan
- option dest lan
- option proto esp
- option target ACCEPT
- config rule
- option name Allow-ISAKMP
- option src wan
- option dest lan
- option dest_port 500
- option proto udp
- option target ACCEPT
- config rule
- option name test-1
- option src wan
- option dest lan
- option src_port 1234
- option dest_port 5678
- option proto tcp
- option target REJECT
- config rule
- option name test-2
- option src wan
- option dest lan
- option dest_port 9997
- option proto 'tcpudp'
- option target DROP
- # include a file with users custom iptables rules
- config include
- option path /etc/firewall.user
- ## Network configuration
- config interface 'loopback'
- option ifname 'lo'
- option proto 'static'
- option ipaddr '127.0.0.1'
- option netmask '255.0.0.0'
- config globals 'globals'
- option ula_prefix 'fdc2:5b52:f3c6::/48'
- config interface 'lan'
- option ifname 'eth2'
- option proto 'static'
- option ipaddr '192.168.1.1'
- option netmask '255.255.255.0'
- option ip6assign '60'
- config interface 'lan2'
- option ifname 'eth2.245'
- option proto 'static'
- option ipaddr '192.168.2.1'
- option netmask '255.255.255.0'
- config interface 'wan'
- option ifname 'eth0'
- option proto 'dhcp'
- option ip4table '1000'
- config interface 'wan2'
- option ifname 'eth1'
- option proto 'dhcp'
- option ip4table '1001'
- config interface 'wan6'
- option ifname 'eth0'
- option proto 'dhcpv6'
Advertisement
Add Comment
Please, Sign In to add comment