Firewall Rule - Ordering, Best Practices & Other Questions

Network Info / Goals

4 Segments, all with their own VLAN, DHCP, WiFI etc:

VLAN 99 - CORE - 192.168.1.0/24 (This is the original one)
VLAN 100 - HOME - 192.168.100.0/24
VLAN 110 - GUEST - 192.168.110.0/24
VLAN 120 - PRIV - 192.168.1.120/24 (this happens to have all traffic routed via a Wireguard tunnel (WG list))

add interface=CORE list=LAN
add interface=HOME list=LAN
add interface=PRIV list=LAN
add interface=GUEST list=LAN

add interface=PRIV list=SAFE-MGMT
add interface=ether5 list=SAFE-MGMT

1 interface = 1 VLAN = 1 subnet in this config, and is the approach I would always take. The only exception is that I also have an off-bridge port, ether5 - 192.168.250.0/24, this is purely emergency management access.

Simple goals:

  • PRIV and ether5 to be able to reach the router for management access
  • LAN interface list to have Internet access
  • Allow specific services (only DNS and ICMP at the moment) on the Input chain from the LAN list
  • VLANs within the LAN list should not be able to communicate with eachother by default (I may add individual exceptions at a later date)
  • PRIV VLAN can talk to everyone, though
  • Implicit drop rule on all chains to catch any mistakes or errors (I prefer default deny, probably because I cut my teeth on Cisco ASAs)

I am a but unsure about the ordering of the Forward chain rules in particular; does the position of the Fasttrack rule matter in this context? is there a general best practice here? And what about the position of the rule for ‘established,related,untracked’?

I try to keep things clean and go with the approach of:

  • Use interface lists when I want all members of all those subnets to have the same setting (in my case this is mainly for Internet access)
  • Specific Interface-In when it’s just for that segment (as opposed to using a subnet/IP list)
  • Individual IP (or IP in general) only if it needs to be more granular
/ip firewall filter
;input chain
add action=drop chain=input comment="defconf: drop invalid" connection-state=invalid
add action=accept chain=input comment="Safe Management Access" in-interface-list=SAFE-MGMT
add action=accept chain=input comment="Allowed UDP Services to Router" dst-port=53 in-interface-list=LAN protocol=udp
add action=accept chain=input comment="Allowed TCP Services to Router" dst-port=53 in-interface-list=LAN protocol=tcp
add action=accept chain=input comment="defconf: accept established,related,untracked" connection-state=established,related,untracked
add action=accept chain=input comment="Allow ICMP to Input chain from LAN Segments" in-interface-list=LAN protocol=icmp
add action=drop chain=input comment="Implicit Drop Input Chain" log-prefix=INPUT-NOT-LAN

;forward chain
add action=drop chain=forward comment="defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat connection-state=new in-interface-list=WAN
add action=drop chain=forward comment="defconf: drop invalid" connection-state=invalid
add action=accept chain=forward comment="PRIV to other Segments" in-interface=PRIV out-interface-list=LAN
add action=accept chain=forward comment="Internet Access for All" connection-state=new in-interface-list=LAN out-interface-list=WAN
add action=accept chain=forward comment="Applicable access for Wireguard Tunnel" in-interface=GUEST out-interface-list=WG
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" connection-state=established,related hw-offload=yes
add action=accept chain=forward comment="defconf: accept established,related, untracked" connection-state=established,related,untracked
add action=drop chain=forward comment="Implicit Drop Forward Chain" log=yes log-prefix=IMPLICIT

Anything here I can tweak to make it more performant, logical or secure? I am all about least privilege.

As an extra question, what is the best practice approach for ip service and system users when it comes to the ‘available from’ setting? If you have already firewalled it off as above, do you leave that setting blank? I have still set it to match the subnets explicity, but not sure if it’s actually adding any value. I guess it is belt and braces.

With this part done, I have a nice, clean and segmented network on my hAP AX3 :slight_smile:

Thank you!

The very reason to implement tracking of connections and their state has been to make it possible to put a single “accept established or related” rule to the very top of each chain in firewall filter, because that way, most packets only ever hit this rule, and all subsequent rules basically only deal with packets that do not belong to any known connection and have a potential to create a new one.

Fasttracking goes a step further, as it lets most packets belonging to known connections skip not only filter but many other layers of firewall processing and IPsec policy matching, so the amount of CPU cycles spent on handling those packets is much lower than if they just hit the “accept established or related” rule.

But the thing is that the purpose if the action=fasttrack rule is not to handle every single packet this way - its purpose is to set the fasttrack flag in the context data of the connection so that subsequent packets belonging to that connection could go down the fast lane. So this rule must be placed before the “accept established or related” one, otherwise no packet would ever reach it.

And then come things like preventing of IPsec payload packets from ever hitting the action=fasttrack-connection rule, excluding some traffic from connection tracking using rules in raw etc.

Thanks Sindy,

So that makes sense, so I’ve rejigged things:

add action=fasttrack-connection chain=forward comment="defconf: fasttrack" connection-state=established,related hw-offload=yes
add action=accept chain=forward comment="defconf: accept established,related, untracked" connection-state=established,related,untracked
add action=drop chain=forward comment="defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat connection-state=new in-interface-list=WAN
add action=drop chain=forward comment="defconf: drop invalid" connection-state=invalid
add action=accept chain=forward comment="Internet Access for All" connection-state=new in-interface-list=LAN out-interface-list=WAN
add action=accept chain=forward comment="PRIV to other Segments" in-interface=PRIV out-interface-list=LAN
add action=accept chain=forward comment="Applicable access for Wireguard Tunnel" in-interface=GUEST out-interface-list=WG
add action=drop chain=forward comment="Implicit Drop Forward Chain" log=yes log-prefix=IMPLICIT

I read some different schools of thought on where to place the ‘invalid’ rule, some of which say it makes sense to make that number 1, and also lots of examples don’t mention the rule dropping traffic that is not dest-natted - this rule specifically never seems to show any hit count. Would removing it potentially allow something to pass that is not already covered by another rule?

As it happens you have been very helpful in the past with exactly the issue of ipsec and fasttrack and the associated workarounds for IPSEC, however this particular router has no current use case for such traffic, but of course I will keep it in mind in future if that requirement comes up.

Each packet has exactly one value of connection-state. So it doesn’t matter whether you put a rule matching on connection-state=invalid before or after a rule that matches on connection-state=established,related, as packets that match one of them do not match the other one.

In the default firewall configuration, the role of the “drop invalid” rule is to relieve all subsequent rules from having to match on connection-state=new, because in the default firewall configuration, the “accept established or related” rule accepts also “untracked” packets, i.e. those explicitly excluded from connection tracking using action=notrack rules in raw. So the connection-state of whatever gets past that rule can be only invalid or new, hence by placing the “drop invalid” right below/after “accept established,related,untracked”, all what is left are packets with connection-state=new, so there is no need to match on connection-state in the subsequent rules.

A separate point is the very occurrence of packets with connection-state=invalid. This state is normally assigned to packets belonging to protocols that do have an internal state that may be tracked and the contents of some packets may not match that internal state - from what I know only TCP and SCTP currently fit into this category. But by default, loose-tcp-tracking is enabled, so the connection tracking does not care about the TCP packet state. For example, if you remove a ssh connection from a connection list and one of the endpoints sends another packet within the existing TCP session, the connection tracking marks that packet as connection-state=new even though it does not contain any SYN flag so it would be incapable to actually initiate a new TCP session. So what remains of connection-state=inactive is the bug that marks GRE packets as invalid even if matching GRE packets in the opposite direction did pass through the firewall.

Love the detail, thank you.

Seems I’m in a good spot then, and it makes a lot more sense with all that extra conext.

add action=drop chain=forward comment=“defconf: drop all from WAN not DSTNATed” connection-nat-state=!dstnat connection-state=new in-interface-list=WAN

What is this rule REALLY DOING as it was intended in the default rule setup!
a. its blocking WAN to LAN traffic unless there is a corresponding dstnat rule that is triggered and thus needs to pass through.
b. it implicitly allows outgoing internet traffic…
BUT..… you have kept this default rule, not knowing its purpose
AND added a new rule…
add action=drop chain=forward comment=“Implicit Drop Forward Chain” log=yes log-prefix=IMPLICIT

Point1: Why do you log noise, certainly a waste on the input size as their are a gazillion bots hitting routers all the time… but in general, if its dropped, be happy dont worry about it.
There are times to log but this isnt one of them,
Point2. YOu effectively (tis a good security rule at the end of the forward chain) BLOCK ALL ELSE.

In other words you have inadvertently blocked all your outgoing internet traffic,
If you add the block all rule at the end, which is good, then you have to modify your rule set:

(default rules to keep)
add action=fasttrack-connection chain=forward connection-state=established,related hw-offload=yes
add action=accept chain=forward connection-state=established,related,untracked
add action=drop chain=forward connection-state=invalid
(admin rules)
add action=accept chain=forward comment=“internet traffic” in-interface-list=LAN out-interface-list=WAN
add action=accept chain=forward comment=“PRIV to other Segments” in-interface=PRIV out-interface-list=LAN
add action=accept chain=forward comment=“Applicable access for Wireguard Tunnel” in-interface=GUEST out-interface-list=WG
add action=accept chain=forward coment=“port forwarding” connection-nat-state=dstnat
add action=drop chain=forward comment=“Implicit Drop Forward Chain”

EDIT: Ahhh, I see you in fact did add the internet rule, very good. It should be probably the first admin rule, as that is probably executed the most.
So in Summary, the only change you need to make is get rid of the default rule for dstnat and replace it with the clear allow rule for dstnat.
The drop all rule at the end catches any other wan to lan traffic and thus you are getting rid of a partially redundant rule.

Thanks Anav,

So now I finally understand what the dst-nat rule is for. A big lightbulb moment. And now that you framed it like that, it is so obvious. It also confirms to me why I never see any hits; I do not do any port forwarding and never allow access to anything via my routers public IP. So, that rule is actually one I can simply remove. If I do need access externally (for example, homeassistant is the main one) then I will use Cloudflare Tunnel; I much prefer this to directly opening services on my router.

I guess the ! logic (why deny not, vs allowing when it is?) is part of the issue. And I see you have mentioned this elsewhere on the forums…

As for the logging, that forward chain rule will only be triggering for outbound traffic (owing also to the above point) that I have either forgot about or didn’t know about. It’s a handy diagnostic for the moment, and isn’t getting many hits at all. (I would not log the equivalent on the input chain however!)

Well between this and the other threads you, Sindy and others in the community have helepd me on…I am in a great spot for my home network. I learned heaps, and implemented wireguard for the first time (actually very straight forward after all…)

Thanks :smiley: