Here’s my take: as with everything in RouterOS, it depends on what you’re doing - if your goal is maximum efficiency of rule processing.
Here an example where connection marks are good:
/ip firewall mangle
add chain=prerouting connection-mark=no-mark in-interface=ether1 dst-address=10.1.0.0/24 protocol=tcp dst-port=22 action=mark-connection new-connection-mark=test passthrough=yes
add chain=prerouting connection-mark=test action=mark-packet new-packet-mark=test
A whole bunch of properties on a rule is basically a giant AND condition. If (connection-mark=no-mark AND in-interface=ether1 AND …) can be short circuited on the first condition that fails - since everything is ANDed, you no longer have to check anything else.
When I took my MTCNA training we had a bit of a discussion about this. Steve Discher (excellent trainer) went to confirm with sergejs, and unless I am remembering wrong connection-marks are checked first. no-mark is a neat way to determine that a connection is new, then (though since it relies on conntrack I would think that connection-state=new would also work well, but that’s beside the point).
So the first packet of a connection is going to go through 5 checks in the first rule (what chain it is in of course doesn’t count), matches all of them, and the connection is marked. It is then passed on to the second rule, matches that one condition, and the packet is marked. Every subsequent packet is then marked after two checks: first the connection-mark is checked in the first rule, and that check fails (the connection has a mark, so it isn’t ‘no-mark’). Then the only check in the second rule matches and the packet is marked.
Compare that to this:
/ip firewall mangle
add chain=prerouting in-interface=ether1 dst-address=10.1.0.0/24 protocol=tcp dst-port=22 action=mark-packet new-packet-mark=test
Every packet has to match 4 checks to be marked - that’s twice as much work.
The moment you start throwing in source and destination addresses and interfaces, the second example also only marks one way - you’d need a second rule to mark return traffic. With a connection-mark you cover both ways.
Also, technically you can save yourself a little more resources by duplicating rules:
/ip firewall mangle
add chain=prerouting connection-mark=test action=mark-packet new-packet-mark=test
add chain=prerouting connection-mark=no-mark in-interface=ether1 dst-address=10.1.0.0/24 protocol=tcp dst-port=22 action=mark-connection new-connection-mark=test passthrough=yes
add chain=prerouting connection-mark=test action=mark-packet new-packet-mark=test
Now the first packet of a connection needs 7 checks (fail first rule, 5 checks match and action on second, passthrough, one more check on third rule), but all subsequent packets match with just one check.
Connection-marks are less efficient in certain situations, though. Here an example that marks every packet based on interface, with different marks for upload and download traffic:
/ip firewall mangle
add chain=postrouting out-interface=LAN1 action=mark-packet new-packet-mark=LAN1-download
add chain=prerouting in-interface=LAN1 action=mark-packet new-packet-mark=LAN1-upload
In that case you can make a single decision for all download packets (one check that matches on rule one), and two decisions for upload traffic (first the packet is checked in prerouting, doesn’t match, then goes to postrouting, and matches the only check).
That’s not possible to do with connection-marks, where the minimum amount of checks is three, or four if traffic can be initiated bi-directionally.
So basically when you have very few checks, not using connection-marks can save you resources. Whenever you’re checking more than two properties, though, connection-marks will probably save resources. When in doubt, count it all out.
Of course the above also relies on the assumption that connection-marks are checked first when multiple properties are being checked for - if they’re checked last, it all falls apart.
Hope that helps.