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? > 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? > > > @@ -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; > } >