[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: pf and statesfull filtering on a bridge



Several people have asked about stateful filtering with pf for bridges,
and I wasn't sure myself about what's possible until I actually tried it
today :)

Assume we want to build a stealth ethernet bridge that does stateful packet
filtering using pf. First set up the bridge and verify it's working correctly:

  /etc/hostname.rl0
  up

  /etc/hostname.rl1
  up

  /etc/bridgename.bridge0
  add rl0 add rl1 up

Now enable pf with an empty rule set (passing all packets by default), and
observe how everything is still working.

To better understand how pf sees the packets, we add these four rules:

  pass in  log on rl0 inet proto icmp
  pass out log on rl0 inet proto icmp
  pass in  log on rl1 inet proto icmp
  pass out log on rl1 inet proto icmp

We send an echo-request (ping) from a host on the rl0 side of the bridge
to a host on the rl1 side, and an echo-reply comes back. We see the following
log entries:

  rule 0, pass in on rl0, icmp echo-request
  rule 3, pass out on rl1, icmp echo-request
  rule 2, pass in on rl1, icmp echo-reply
  rule 1, pass out on rl0, icmp echo-reply

As you can see, each packet goes through pf twice. It comes in on one
interface and goes out through the other.

With pf (unlike ipf), we can actually address each of these cases, both
directions on both interfaces.

If we filter statefully, there's one thing to keep in mind: the states in
the state table are sorted by a key which consists of the two address and
port pairs of the connection. The order of these two pairs is relevant.
If an outgoing packet from A to B creates state, pf will let pass outgoing
packets from A to B and incoming packets from B to A. But it will still
block outgoing packets from B to A and incoming packets from A to B. In
a non-bridging case this is perfectly clear and obvious.

But in our bridging case, the same packet goes through pf twice, once through
each interface. And the order of the key pairs is different in both cases.

There are two solutions. The first one is to create two states per
connection, one for each direction, using two 'keep state' rules.
I don't think anyone will want to do that. Especially since the second
solution is very simple and elegant:

>From pf perspective, packets go through our bridge twice. If you look at
either interface, you see exactly the same traffic, only the direction
is reversed. Hence, we can ignore one interface and do all the filtering
on the other one.

Here's an example:

  # we want to filter on rl0, hence just pass anything on rl1.
  pass in  quick on rl1 all
  pass out quick on rl1 all

  # as an example, we block eveything by default, and let pass only
  # icmp echo requests related replies (both directions, statefully).
  block in  on rl0 all
  block out on rl0 all
  pass in  on rl0 inet proto icmp all icmp-type echoreq keep state
  pass out on rl0 inet proto icmp all icmp-type echoreq keep state

This works equally well for more complex rules. The general recipe is
to pick one interface you want to filter on. Start with two rules
that let pass anything on the other interface (quick). After that,
filter only on the interface you picked.

Regards,
Daniel