On 2020/10/19 19:53, David Gwynne wrote:
> On Mon, Oct 19, 2020 at 09:34:31AM +0100, Stuart Henderson wrote:
> > On 2020/10/19 15:35, David Gwynne wrote:
> > > every few years i try and use route-to in pf, and every time it
> > > goes badly. i tried it again last week in a slightly different
> > > setting, and actually tried to understand the sharp edges i hit
> > > this time instead of giving up. it turns out there are 2 or 3
> > > different things together that have cause me trouble, which is why
> > > the diff below is so big.
> > 
> > I used to route-to/reply-to quite a lot at places with poor internet
> > connections to split traffic between lines (mostly those have better
> > connections now so I don't need it as often). It worked as I expected -
> > but I only ever used it with the interface specified.
> 
> cool. did it work beyond the first packet in a connection?

It must have done. The webcams would have utterly broken the rest of
traffic if it hadn't :)

> > I mostly used it with pppoe interfaces so the peer address was unknown
> > at ruleset load time. (I was lucky and had static IPs my side, but the
> > ISP side was variable). I relied on the fact that once packets are
> > directed at a point-point interface there's only one place for them to
> > go. I didn't notice that ":peer" might be useful here (and the syntax
> > 'route-to pppoe1:peer@pppoe1' is pretty awkward so I probably wouldn't
> > have come up with it), I had 0.0.0.1@pppoe1, 0.0.0.2@pppoe2 etc
> > (though actually I think it works with $any_random_address@pppoeX).
> 
> yes. i was trying to use it with peers over ethernet, and always
> struggled with the syntax.
> 
> > > the first and i would argue most fundamental problem is a semantic
> > > problem. if you ask a random person who has some clue about networks
> > > and routing what they would expect the "argument" to route-to or
> > > reply-to to be, they would say "a nexthop address" or "a gateway
> > > address". eg, say i want to force packets to a specific backend
> > > server without using NAT, i would write a rule like this:
> > > 
> > >   n_servers="192.0.2.128/27"
> > >   pass out on $if_internal to $n_servers route-to 192.168.0.1
> > > 
> > > pfctl will happily parse this, shove it into the kernel, let you read
> > > the rules back out again with pfctl -sr, and it all looks plausible, but
> > > it turns out that it's using the argument to route-to as an interface
> > > name. because rulesets can refer to interfaces that don't exist yet, pf
> > > just passes the IP address around as a string, hoping i'll plug in an
> > > interface with a driver name that looks like an ip address. i spent
> > > literally a day trying to figure out why a rule like this wasn't
> > > working.
> > 
> > I don't think I tried this, but the pf.conf(5) BNF syntax suggests it's
> > supposed to work. So either doc or implementation bug there.
> 
> im leaning toward implementation bug.
> 
> >      route          = ( "route-to" | "reply-to" | "dup-to" )
> >                       ( routehost | "{" routehost-list "}" )
> >                       [ pooltype ]
> > 
> >      routehost-list = routehost [ [ "," ] routehost-list ]
> > 
> >      routehost      = host | host "@" interface-name |
> >                       "(" interface-name [ address [ "/" mask-bits ] ] ")"
> > 
> > > the second problem is that the pf_route calls from pfsync don't
> > > have all the information it is supposed to have. more specifically,
> > > an ifp pointer isn't set which leads to a segfault. the ifp pointer
> > > isn't set because pfsync doesnt track which interface a packet is
> > > going out, it assumes the ip layer will get it right again later, or a
> > > rule provided something usable.
> > > 
> > > the third problem is that pf_route relies on information from rules to
> > > work correctly. this is a problem in a pfsync environment because you
> > > cannot have the same ruleset on both firewalls 100% of the time, which
> > > means you cannot have route-to/reply-to behave consistently on a pair of
> > > firwalls 100% of the time.
> > 
> > I didn't run into this because pppoe(4) and pfsync/carp don't really
> > go well together, but ouch!
> > 
> > > all of this together makes things work pretty obviously and smoothly.
> > > in my opinion anyway. route-to now works more like rdr-to, it just
> > > feels like it changes the address used for the route lookup rather
> > > than changing the actual IP address in the packet. it also works
> > > predictably in a pfsync pair, which is great from the point of view of
> > > high availability.
> > > 
> > > the main caveat is that it's not backward compatible. if you're already
> > > using route-to, you will need to tweak your rules to have them parse.
> > > however, i doubt anyone is using this stuff because it feels very broken
> > > to me.
> > 
> > Do you expect this to work with a bracketed "address" to defer lookup
> > until rule evaluation time? i.e.
> > 
> > pass out proto tcp to any port 22 route-to (pppoe1:peer)
> 
> in my opinion route-to should be able to take whatever rdr-to accepts.
> however, i just tried it and it doesnt currently work, but i'm sure i
> can figure it out.
> 
> > I think that will be all that's needed to allow converting the pppoe
> > use case. I don't have a multiple pppoe setup handy but I can probably
> > hack together some sort of test.
> > 
> > I've also used route-to with squid "transparent" proxying (shown in
> > the pkg-readme), I don't do that any more but I can put a squid test
> > together easily enough.
> 
> it looks doable.
> 
> my  changes will break route-to with "no state" rules though. should
> i try and keep those working too?

I don't think I ever used that myself. aja@ added the "no state" in
squid's pkg-readme (to avoid state problems when hairpinning the
intercepted packets back to a machine on a subnet alongside the client,
squid replying to the client directly), no idea if he's still using that,
I would guess probably not.

If I understand the problem correctly I think this case could use "keep
state (sloppy)" instead anyway.


> > 
> > > @@ -1842,37 +1833,18 @@ pfrule            : action dir logquick interface 
> > >                   decide_address_family($7.src.host, &r.af);
> > >                   decide_address_family($7.dst.host, &r.af);
> > >  
> > > -                 if ($8.route.rt) {
> > > +                 if ($8.rt) {
> > > +                         if ($8.rt != PF_DUPTO && !r.direction) {
> > > +                                 yyerror("direction must be explicit"
> > > +                                     " with rules that specify routing");
> > > +                                 YYERROR;
> > > +                         }
> > >                           if (!r.direction) {
> > >                                   yyerror("direction must be explicit "
> > >                                       "with rules that specify routing");
> > >                                   YYERROR;
> > >                           }
> > 
> > this stood out on reading the diff - the added if block doesn't change
> > any outcome, should it have actually been like this?
> 
> possibly :)
> 
> > 
> > -                           if (!r.direction) {
> > +                           if ($8.rt != PF_DUPTO && !r.direction) {
> >                                     yyerror("direction must be explicit"
> >                                         " with rules that specify routing");
> >                                     YYERROR;
> >                             }
> > 
> 

Reply via email to