The `pf_route()` functions bypass the normal routing mechanism thus need to re-implement some functionalities on their own, like fragmentation, ICMP signalling and testing the outbound firewall. Because of this for route-to targets functions depending on `pf_send_tcp()`, like syncookies, won't work and the dtrace probe for `pf_test()` is never called and packets delayed by dummynet need to be routed and emitted again. The current approach also skips things like ipsec.
This draft proposes the following solution: have the `pf_route()` and `pf_route6()` functions use the PACKET_TAG_IPFORWARD tag and let the FreeBSD IP stack handle the routing. This unfortunately does not work out of the box, see D41479. Furthermore the change breaks the ability to specify the interface for the redirection pools - redirection target must be a reachable address and a matching interface will be used. This patch does not deal with removing the interface specification from pf.conf and other structures like pfsync. OpenBSD has already removed the ability to force the interface and greatly simplified the redirection pool logic some years ago. I've already looked into that as a part of my NAT64 backport effort and when I have time to get back to it I'd like to port the better redirection pools and remove the interface from them anyway.
At the moment of writing this the patch breaks one test: `pf/route_to:icmp_nat`. After some investigation I must say that handling of post-NAT ICMP is broken in pf in general. A ruleset with no route-to, just plain forwarding and a NAT rule on an interface with small MTU will cause exactly the same issue. The function `icmp_error` eventually results in a call to `ip_output` which will only call `pfil_mbuf_out` which would only match the inbound pf state to the router. Maybe we should call `pfil_mbuf_in` from `icmp_send`?
Also I don't understand why the test uses `pass out route-to` instead of `pass in route-to`, as with such approach the gateway and possible the interface will be changed after a state is already created on another interface. With `set state-policy if-bound` such ruleset could not work anyway if the interface is changed.
Sponsored by InnoGames GmbH