Igalia / pflua

Packet filtering in Lua
Other
313 stars 39 forks source link

New language: Pfmatch #229

Closed wingo closed 9 years ago

wingo commented 9 years ago

Sometimes you want more than just a "yes/no" answer on matching a packet. Often you want multiple different ways to say "yes" or "no", and those ways might want access to data in the packet. This isn't possible in pflang but we can define other related languages. Here is one proposal, the "pfmatch" language.

Program := Captures => Consequent
Captures := '(' [ Identifier' [',' Identifier]... ] ')'
Consequent := Call | '{' Clause... '}'
Call := Identifier '(' [ ArithmeticExpression [ ',' ArithmeticExpression ] ] ')'
Clause := Pflang '=>' Consequent

Example:

(udp, tcp, ip6) => {
  ip => {
    udp => udp(&udp[0])
    tcp => tcp(&tcp[0])
  }
  ip6 => ip6(&ip6[0])
}

See #228 for more on the & operator.

You would use it like this:

local function handle_udp(packet, len, payload_addr) ... end
local function handle_tcp(packet, len, payload_addr) ... end
local function handle_ip6(packet, len, payload_addr) ... end
local match = pf.compile_pfmatch("(udp, tcp, ip6) => ...")(
   handle_udp, handle_tcp, handle_ip6)

...
for p, len in packets do match(p, len) end

The matcher would return nil if no clause matches. If one clause fails to match, the next clause is tried. Likewise if evaluating the arithmetic expressions for a call fails. If a clause matches, the captured procedure is tail-called, and the result of the match is whatever the callee returns.

wingo commented 9 years ago

The example is probably less confusing when written as

(handle_udp, handle_tcp, handle_ip6) => {
  ip => {
    udp => handle_udp(&udp[0])
    tcp => handle_tcp(&tcp[0])
  }
  ip6 => handle_ip6(&ip6[0])
}

They are equivalent though.

kbara commented 9 years ago

Interesting. A feature like this would be nice, as would one to bind subclauses to names and only evaluate them once. If we go in this direction, do we actually want to keep pflang compatibility, given things like the oddness with which it handles vlans? Also, we have to be very careful not to make this Turing-complete.

wingo commented 9 years ago

This language is not turing-complete -- there's no way to go back and try a previous clause.

Regarding names: this doesn't help in binding names to arithmetic expressions. The names are just for the handlers. Naming arithmetic expressions is not an issue for optimizability or other criteria here, though it could make some things in pflang more convenient. I wouldn't couple that to this though. (Mostly I'm just looking for a way to match on packets nicely without littering apps with offsets and conditionals.)

wingo commented 9 years ago

Also I think it's best to stay close to pflang, as that's what users are used to. We can bill this as an enhancement and not an alternative.

kbara commented 9 years ago

I know this one isn't; I mean "if we introduce a bunch of extensions, we need to make sure they don't add Turing-completeness, alone or in combination".

I was thinking of naming in the context of http://snellman.net/blog/archive/2015-05-18-whats-wrong-with-pcap-filters , since this proposal has a fair bit of overlap.

For the use case you're mentioning, this sounds positive.

wingo commented 9 years ago

Snabb app using dispatch language: https://github.com/Igalia/snabbswitch/compare/basicnat...Igalia:basicnat-dispatch

kbara commented 9 years ago

How do you want to handle IPv6 packets with extension headers? Both libpcap and pflua will not match udp packets with filters like "ip6 and udp" in the presence of an extension header; the same is true for tcp and other protocols. The match language is more useful if it can do better than pflang in this regard.

wingo commented 9 years ago

Dunno. Do you have a proposal?

wingo commented 9 years ago

Initial implementation in #236, docs in pfmatch.md.