Skip to content

Allow rules to match TCP and UDP ports - #211

Open
JohnCampionJr wants to merge 1 commit into
openai:mainfrom
JohnCampionJr:port-rules
Open

JohnCampionJr wants to merge 1 commit into
openai:mainfrom
JohnCampionJr:port-rules

Conversation

@JohnCampionJr

@JohnCampionJr JohnCampionJr commented Oct 7, 2026 •

Copy link
Copy Markdown

A rule's target can now end in :PORT or :FIRST-LAST, for example --allow=192.168.0.10/32:8443 or --block="out @host:22". It matches TCP and UDP traffic with that port on the target's side: the destination for outbound traffic, the source for inbound.

The use case: a VM whose private ranges are blocked but which still needs to reach one service on another host. Today the only option is to allow the whole host, which also exposes SSH and anything else listening on it.

A rule with ports takes precedence over rules without them, so one port of a host can be opened through a block of its network. Among rules with ports, the longest prefix wins and blocking wins a tie, as with the existing rules. Only the first fragment of a packet carries ports, so later fragments never match a port rule.

The same syntax works through the control channel. This picks up the "ports" item from #184.

A target may end in :PORT or :FIRST-LAST. Rules with ports take
precedence over rules without them, so a single port of a host can be
allowed through a block of its network.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JohnCampionJr

Copy link
Copy Markdown
Author

For context: I read through the syntax discussion in #184 before opening this. Ports came up there, with forms like out proto tcp to @host port 8080, but #188 shipped direction only, so this adds the smallest piece on top of it: an optional port on the existing target, matching both TCP and UDP.

If you'd rather go with the proto … to … port … form from #184, I'm happy to rework it that way. The matching and precedence logic wouldn't change, only the parser.

@edi-oai
edi-oai self-requested a review October 7, 2026 09:40

@edi-oai edi-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to separate the port-based rules from the regular ones?

Also, we should approach the rule syntax with more thought to accommodate different use-cases in the future. For example, one might want to:

  • specify a range of ports
  • a protocol
  • differentiate between the source or destination port

See pf.conf(5)'s for a good example for such extensibility (proto, from and to actions). There are other packet filters that allow this flexibility as well.

It would be nice if was an issue in the first place, where we would be able to discuss the shape required to support both (1) your use-case and (2) possible future ones, and only then jump into the implementation.

@edi-oai

edi-oai commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

If you'd rather go with the proto … to … port … form from #184, I'm happy to rework it that way. The matching and precedence logic wouldn't change, only the parser.

Sorry, I've totally missed this message.

Yes, I think we should prioritize extensibility from the outset to avoid ending up with overly complex syntax. Or worse: a syntax that cannot express certain rules at all.

That would be particularly unfortunate given how how many existing packet-filter implementations we can draw inspiration from.

My preference goes for pf.conf(5), but I'm happy to compare it with the other approaches as well.

@JohnCampionJr

Copy link
Copy Markdown
Author

Thanks, happy to go the pf.conf route. Proposed subset, so we can agree the shape before I rework the parser:

rule      = [direction] [proto] target
direction = "in" | "out"
proto     = "proto" ("tcp" | "udp" | "icmp")
target    = "from" endpoint ["to" endpoint] | "to" endpoint
endpoint  = (CIDR | "@host" | "any") ["port" port]
port      = N | N ":" M

Examples: out proto tcp to 192.168.0.10 port 8443, in proto tcp from @host to any port 22, out to 10.0.0.0/8. Existing rules keep working: a bare TARGET stays shorthand for to TARGET, and in/out behave as they do today. A port requires proto tcp or udp. Precedence stays longest-prefix with block winning ties, with a rule that names a port counting as more specific than one that doesn't.

If you'd prefer this as a separate issue, I'll open one.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants