Firewall rules on return traffic from established connections

So I’m have some experience in administrating firewalls like Palo Alto, FortiGate and currently using OPNsense at home. But since I started to playing around with the Mikrotik Firewall on my hEX I came across something I couldnt get my head around therefore I seek some advise if I understand this correctly.

Since the Mikrotik does not have a Implicit Deny, I added these manually for the forward and input chain in the default config. After that also the return traffic from already established connection were dropped from the WAN interface. At first I thought the Mikrotik firewall works like a simple packet filter.
But the logs and documentation showed me otherwise.
So I learn that I had to allow established and related to the incoming WAN interface.

Since I never came across something like this on other firewalls like OPNsense, do other firewall vendors implicitly allow this established or related traffic?

Is it the “correct” or safe to allow established/related traffic connections for the return packets from the WAN interface?

This is my current configuration:

/ip firewall filter
add action=accept chain=input connection-state=established,related,new,untracked src-address=192.168.88.0/24
add action=drop chain=input comment="defconf: drop invalid" connection-state=invalid
add action=accept chain=input comment="defconf: accept ICMP" protocol=icmp
add action=accept chain=input connection-state=established dst-port="" log=yes log-prefix="dns input" protocol=udp src-port=53
add action=drop chain=input comment="defconf: drop all not coming from LAN" in-interface-list=!LAN log=yes
add action=drop chain=input log=yes log-prefix=drop_Input
add action=accept chain=forward connection-state=established,related in-interface=ether1
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" connection-state=established,related hw-offload=yes
add action=accept chain=forward connection-state=established,related,new,untracked log=yes src-address=192.168.88.0/24
add action=drop chain=forward comment="defconf: drop invalid" connection-state=invalid
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 log=yes log-prefix=drop_forward

Thanks for your input!

you need to mark allowed connection in firewall mangle first and then make fordward rule accept based on establish connection

First of all, directly to your issue: adding the drop rule at the end of the chains does drop everything that was not matched previous to that rule, as you wanted. BUT the default firewall relies on the “default policy” of accept, and of course simply adding it will not result in a properly working firewall config.

As an example, the second to last rule:

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

as the comment says, drop everything “from WAN”. If you follow this up with “drop everything”, well… not surprisingly it drops everything else as well, even from the LAN (as the rule says.) If you want to convert this to a version with a “drop all” rule while conserving the bahvior, you’d have to replace it with:

  1. drop everything from WAN
  2. allow everything from LAN
  3. drop everything

No. 2 is what you’re missing.

And yes, you’re instincts (or experience?) regard this is spot on - I also like input/forward chains with a “drop all” at the end. It’s just that some other rules have to be reformulated or added for this to work correctly.

I personally think that Mikrotik’s default rules are a bit “too cleverly” formulated, and you’re not the first person finding them a bit hard to understand. I have to emphasize that they are correct, just (in my opinion) not as clear as they could be.

As to your more general points. Mikrotik engineers didn’t come up with this firewall scheme after one too many shots, but this is the Linux iptables firewall in (very thin) mascara. Yes, this is exactly a connection tracking (stateful) L3/L4 packet filter. Last I read, about 80+% of the servers on the internet use it. If you understood the documentation to say something else, you’ve misunderstood each other :slight_smile:

Regarding established/related: yes, this rule is implicit in many other systems. That’s why basically every filter input and forward chain starts with allowing these as the first rule. For you the problem was not that this rule didn’t work, but that - as explained above - you dropped even the outgoing packets. Packets that are dropped don’t create connections that could then be replied to and the reply allowed (duh!)

Nope this will be handled by this rule.

add action=accept chain=forward connection-state=established,related,new,untracked log=yes src-address=192.168.88.0/24

You must have misunderstood something. I was not talking about outgoing packets but rather incoming packets/return traffic from already established connections.

Thank you for pointing this out and the explanation on the established/related rules. I will dig more down into iptables to understand how the mikrotik works.
For now its more clear to me how it works and that is the intended way. Thank you :slight_smile:

I missed the added src-address=192.168.88.0./24 part. This changes what I should have written a bit, the correct thing to say would be that you are blocking both the incoming and outgoing packets. :slight_smile:

Other than me feeling smug, to be actually helpful we should deconstruct what packets the above rule actually matches (in some ways it’s not as intuitive as it could be).

But first let’s talk about connection tracking in the nf (netfilter) world: Upon entering the firewall, all tracked packets either already have a connection tracking entry, or are (initially on a temporary basis) allocated one upon encountering the first packet. Packets can have exactly one of these states: new, established, related or invalid. New is set for packets that don’t yet have an entry, packets that already have an entry that can be associated with them get one of the other three.

So your rule means:

  • the packet has a connection that can be associated with it in the established OR related state
  • AND* the packet’s source address is in 192.168.88.0/24

So its logically:
( connection-state == established || connection-state == related ) && src-address in 192…/24

Do outgoing packets that would create a connection tracking entry satisfy this condition? NO, because they do not yet have a conntrack entry, they are in the “new” state.

Do incoming packets for already established connections satisfy this condition? NO, because reply packets typically have source IP addresses outside of the local subnet. (Note that here it’s not the connection’s originator ip is matched, but the source ip extracted directly from the packet header.)

I get where you’re coming from, and on that level the rule makes sense to me, but the firewall has exact rules to apply. For it to understand what you want, you will probably have to reformulate things. I think mainly what you want is very much akin to what I suggested previously (only not matching on interfaces, but subnets):

  • allow established/related (not matching on src-address)
  • allow src-address in 192…/24

This allows:

  • all replies (even external) to already established connections
  • allow the internal host only to transmit packets successfully to the outside, as a side effect creating a conntrack entry that will then (via the previous rule) allow them to reply

The default firewall rules that come with ‘home products’ works on an implicit allow.
It blocks a few things and allows everything else.
Most folks with a bit of experience change it to an explicit deny concept by keeping some of the default rules and then focusing on NEEDED traffic (accept) and then a drop all else rule at the end of the input and forward chains.

1 Like

add action=accept chain=input connection-state=established,related,new,untracked src-address=192.168.88.0/24
add action=drop chain=input comment=“defconf: drop invalid” connection-state=invalid

You can simplify these rules (and similar ones in forward chain) by switching them around and removing the connection state check in the accept rule. Or if you want to keep this order, use “connection-state=!invalid” in the first rule.

add action=accept chain=forward connection-state=established,related,new,untracked log=yes src-address=192.168.88.0/24

Be careful with logging pretty much all connections. It will flood your logs in no time. Unlike some other firewalls, Linux/MikroTik firewall logs every single packet, not just connection.

add action=drop chain=input comment=“defconf: drop all not coming from LAN” in-interface-list=!LAN log=yes

This rule is redundant since the next one drops everything. Unless you want to log drops from LAN with a different prefix.

add action=accept chain=forward connection-state=established,related in-interface=ether1
add action=fasttrack-connection chain=forward comment=“defconf: fasttrack” connection-state=established,related hw-offload=yes
add action=accept chain=forward connection-state=established,related,new,untracked log=yes src-address=192.168.88.0/24

The top two rules effectively disable FastTrack for WAN. You need to switch them around. The fasttrack-connection action doesn’t actually FastTrack it right there but marks the connection as such. In your configuration once a connection is established, the first rule here accepts the packets, and they never reach the next rule. So they never get marked for FastTrack.
To be fair, the third rule here for traffic from LAN comes after FastTrack, so it will still get marked (since WAN->LAN or LAN->WAN is still the same connection for a given flow). It’s just somewhat convoluted doing it this way.
I would rewrite these 3 rules like so:

add action=fasttrack-connection chain=forward comment=“defconf: fasttrack” connection-state=established,related hw-offload=yes
add action=accept chain=forward connection-state=established,related
add action=accept chain=forward connection-state=new,untracked log=yes src-address=192.168.88.0/24

Another thing, you effectively block all return traffic from the Internet to the router itself except for DNS replies. I assume this is intentional. Keep in mind that it will break DDNS, fetching updates, NTP if you use any of that.

@anserk: I have to disagree with your analysis on this one. It’s not a matter of rewriting or simplifying rules. They are simply wrong. Qualifying a quite usual “accept established/related” entry with an additional src-address matcher shows a fundamental misunderstanding of how the stateful firewall works and what it does.

If we want to block traffic, why let a conntrack entry be created for it in the first place?

If the intention is to allow reply traffic to the router’s outgoing ones - which is the usual intent of this type of rule - why block anyone outside the local subnet from replying?

You are correct, the original configuration has inefficiencies and lack clarify. I just didn’t want to be too hard on the OP since everyone has their own way of thinking. While suboptimal, these rules probably get the job done.

I agree, if the intent is to block traffic originating from the router (with some exception), I would use the output chain instead of letting it out and then dropping replies.

So to summarize it in my own words and current understanding:

  1. In general - yes its normal to allow the return packets from an already established connections in the realm from mikrotik/iptables since this is how they work with their connection states. Other firewall vendors does this (presumably) implicitly for you.
  2. Because of No. 1, it would make sense to have some rules like the following on the top, to allow already established for faster processing inside the firewall.
    EDIT: This rule will also allow the return packets.
    Does it make sense to have these two rules also on the input chain?
add action=fasttrack-connection chain=forward comment=“defconf: fasttrack” connection-state=established,related hw-offload=yes
add action=accept chain=forward connection-state=established,related
  1. After these rules you allow all new/unrelated connections you want to go through the firewall. To the destination. Other return traffic or new packets from the same connections will go through the first two established connection rules since it was already tracked.

So the final firewall ruleset for forward chain (and can be adapted for input?) could look something like this:

add action=fasttrack-connection chain=forward comment=“defconf: fasttrack” connection-state=established,related hw-offload=yes
add action=accept chain=forward connection-state=established,related
add action=accept chain=forward connection-state=new,untracked log=yes src-address=192.168.88.0/24 out-interface-list=WAN
add action=accept chain=forward connection-state=new,untracked log=yes src-address=192.168.88.0/24 dst-address=192.168.90.10 dst-port=53
<more rules to different vlans/services>
add action=drop chain=forward comment="implicit deny"

Does it makes a (performance) difference if I filter for src-address or in-interface? Or will it go though any rule anyway?

Could you elaborate a bit on when it could be useful using output instead of input?
So I could filter on the output for services that the mikrotik itself uses/needs like NTP or updates?
And input filters services that the mikrotik offers for other devices like DNS, DHCP?

Not the fasttrack one, not applicable to input.

New connections won’t be matched by these rules, so they will continue down the list.

add action=accept chain=forward connection-state=new,untracked log=yes src-address=192.168.88.0/24 out-interface-list=WAN
add action=accept chain=forward connection-state=new,untracked log=yes src-address=192.168.88.0/24 dst-address=192.168.90.10 dst-port=53

I would remove connection state check, it’s redundant. But if you do that, you want to drop invalid connections before these as the third rule:

add action=drop chain=forward comment="drop invalid" connection-state=invalid

And then you don’t need to check the state on any other rules since you already accept all established,related and drop invalid. If a packet got past these rules, it’s either new or untracked that you already intended to allow. This is probably negligible for efficiency but easier to read and understand (in my opinion).

If a packet reached a rule, it will go through it. I remember reading somewhere that filtering on interface is a bit more efficient but don’t know that for sure. What I do know for sure is the more checks you have, the more CPU it takes. But I wouldn’t worry about it too much for new connections as the bulk of the traffic is established,related. This is where optimization really matters. Although with FastTrack in place it’s less important.

Output is for anything sourced from the router. If you don’t like it phoning home (which can be disabled elsewhere), then you can limit what the router is allowed to send out. By default there are no rules in the output chain, so everything is allowed.

Input is very critical especially for WAN interfaces. Some people like to limit stuff from LAN also. I personally allow everything from my private VLANs (I need everything anyway) but not from untrusted VLANs. If you do limit input, make sure to allow services you mentioned.

It’s always a good idea to use the Safe mode, especially when changing input rules. Otherwise you can lock yourself out.

There are many Linux netfilter flow diagrams online and of course MikroTik packet flow chart. But I often use this one for quick high-level overview without unnecessary details. It’s especially useful when doing NAT and mangle to see the order of processing. Just FYI, this is plain Linux iptables diagram, so terminology might differ somewhat from MikroTik.

What do you mean by redundant? Redundant in the case you have to configure it in any rule? Or configuration wise not necessary?

Yes I see the point on why to do this. But this then would “implicitly” allow new connections, where I would prefer a “explicit” allow on new connections and then implicitly deny any other packets which then should include invalid. Maybe this is not as efficient as it could be but then I can read the rules as they are and dont have to remember the implicit stuff.

Understood, cant currently not think of anything I want to block/allow in that case but I will think more about it.

Already learned that the hard way :smiley:

Thank you for this. I already try to consult the mikrotik documentation more and more but sometimes its hard to understand especially when you are not that deep into the linux world of networking. Maybe I also should use the iptable context more in my research.
I still dont understand many of the flow in the diagram like mangle or raw but I have not yet invested the time to understand.

The big problem that you had was qualifying the related/established rule with an src-address matcher. That’s now fixed. A big step in the right direction.

As to using a connection-state=new style matcher on the following rules: it’s usually not done, but it doesn’t hurt to do so. It’s mostly a matter of convention/taste. It is redundant because by the time these rules are consulted, packets with other connections states were already handled. (You do know that rules are consulted in strict order.)

Lots of people worry about the performance impact of firewall rules. Simply don’t. In a strict sense it’s true that having more rules results in additional cpu cycles. However this is such a miniscule part of packet processing that when I did an experiment where instead of 1, I included 50 additional rules that the packet had to go through, I couldn’t reliably measure the throughput difference even in a lab setting. Simply don’t worry about this, construct your rules so that they make sense and are easy to follow.

The Linux firewall has lot’s of functions and it can be bewildering. Even for more complicated networks these chains are usually enough to know about:

  • filter/input - packets where the router itself is the recipient
  • filter/forward - packets that go through the router (not generated or received by the router itself)
  • nat/srcnat - this is where translation from internal addresses to internet routable addresses happens

Usually the next one to come up is:

  • nat/dsnat - this is where what most routers call “port forwarding” happens

The other chains are used in specialized scenarios.

The packet flow diagram specifically constructed for Mikrotiks: The ultimate Mikrotik iptables flowchart

As to Mikrotik documentation. The parts where they go into theory is usually well written and absolutely worth a read. Many things, however, are only documented in a reference style - so it’s mostly useful for someone who is already familiar with the system is use. In relation to the firewall specifically it may be useful to read up on iptables-style firewalling.

EDIT: fixed link

Redundant = superfluous = excessive

Agree with @lurker888, it’s a matter of preference and the context. I personally prefer shorter rules with easy to follow logic. For example, accept all established,related → drop all invalid → accept all from LAN → accept from untrusted to WAN → drop everything else. If my private LAN is trusted, there is no need to drown in details about connection states there. When opening a particular port on the WAN side, this is where matching on “new” might be more useful depending in situation, although if my intent is to allow, say, WireGuard port, I don’t really care about the state either.

Where explicitness is very important is the explicit “deny all” rule that default configuration lacks.