PF set condition for not equal to a list of ports for MacOS

Solution 1:

One should clearly understand what Pf's "lists" are. They aren't a part of ruleset that gets loaded into kernel in fact, but macros instead. It means they're expanded during preprocessing phase of rules loading — contrary to tables. Keeping that in mind saves one from "shooting in own foot".

Let's now see what you're trying to do:

block out quick proto { tcp, udp } from any to ! <my_table> port != { 66 80 } 
  • Block immediately If
    • it's TCP or it's UDP
    • AND it's destined to
      • IPs that aren't in my_table
      • AND ports, that aren't in the list

As I've told you lists' items would get expanded into a separate rule each. And this would break the logic I've just explained: only the first port of the list would be treated right, if you block immediately (quick) it obviously means no second checking — "not allowed port, ok, blocking it".

Mastering firewall ruleset you'd better keep things as simple as possible. Well, actually it's not only for firewalls — it's general and programming common sense. Double negations, exception of exceptions aren't that simple to follow.

So what are your options then? — You can take a look at the different angle: what and when do you want to pass?

# If it's destined to IP in <my_table> -- pass:
pass  out quick proto { tcp, udp } from any to <my_table>

# ElseIf it's to allowed ports -- pass:
pass  out quick proto { tcp, udp } to port { 66 80 }

# And this point would be reached only if it wasn't to <my_table>
# and to some ports other than allowed ones:
block out quick proto { tcp, udp }

This ruleset is way more readable indeed. Moreover — macro expansion doesn't spoil its logic.

Of course, Pf has some other means for solving this very task but describing them all properly would make this answer way too lengthy.

Solution 2:

You can make the inverse command logic.

First Block all, then open just the ones you want:

The result is the same you tried by negating all except those chosen to be opened. :)

set skip on lo
port_pass = "{ 80 66 53 22 }"
block all
pass out on en0 proto { tcp, udp } to any port $port_pass keep state

Solution 3:

Figured it out:

Is by negating each individual port, inside the delimiter { }

 block out quick proto { tcp, udp } from any to ! <my_table> port { != 66, != 80 } 

I found the 'op-list' section for specifying port here: https://man.openbsd.org/pf.conf.5

This shows you how to apply the logic of adding more ports