Bug 193568 - PF rdr rule with ipv6 does not work
Summary: PF rdr rule with ipv6 does not work
Status: Open
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: 9.3-RELEASE
Hardware: amd64 Any
: --- Affects Many People
Assignee: freebsd-pf (Nobody)
Depends on:
Reported: 2014-09-11 17:16 UTC by Daniel Ylitalo
Modified: 2018-01-23 15:51 UTC (History)
3 users (show)

See Also:

Fix RDR rules that redirect to ::1 (4.37 KB, patch)
2018-01-23 15:51 UTC, Alan Somers
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Daniel Ylitalo 2014-09-11 17:16:07 UTC
There is something wrong with ipv6 redirects in PF, it doesnt work at all.

This works fine in ipv4:
rdr pass on $fiber inet proto tcp from any to any port 80 -> port 8080

This does not work in ipv6:
rdr pass on $fiber inet6 proto tcp from any to any port 80 -> ::1 port 8080
Comment 1 j.david.lists 2015-03-26 01:29:01 UTC
This is a duplicate of 179392.

FreeBSD 9 + PF + IPv6 has been hopelessly broken for years and nobody cares.  A fix for this particular issue was eventually developed, but is currently not available available in any released version.

To resolve this issue, your choices are to downgrade to 8.4 or upgrade to 10-STABLE.  The fix should be in 10.2 in late 2015 / early 2016.
Comment 2 Alan Somers freebsd_committer 2018-01-17 21:25:18 UTC
This is NOT a duplicate of 179392.  It has nothing to do with checksums.  In fact, it technically isn't a bug at all.  The problem is that you're asking PF to do something that's illegal in IPv6.

IPv6 addresses have the concept of "scopes".  A scope is the domain in which a particular address is valid.  Localhost (::1) has local scope, link-local addresses (fe80:*) have link-local scope, site-local addresses (fec0:*) have site-local scope, and global addresses (everythign else) have global scope.  Since ::1 only has local scope, it's only valid for traffic that originates and ends on the local machine.  For that reason, it is specifically forbidden to assign ::1 to a real network interface.

Your PF rule redirects a packet to ::1, but doesn't change the receiving interface.  Thus, it violates scoping rules.  You can tell by running 'netstat -s -f inet6 | grep "violated scope"' before and after generating the traffic that you want to redirect.  The check is in in6_setscope().

The simple workaround is to change your rdr rule to redirect to your actual link-local, site-local, or global IPv6 address instead of ::1.
Comment 3 Richard Gallamore freebsd_committer 2018-01-17 21:45:50 UTC
(In reply to Alan Somers from comment #2)
Thanks very much for explaining this Alan. This is a problem I'v been wondering about for a long time. The work around I used was global IPv6 address which isn't so reliable when due to not being static. The link-local is a much better solution.

According to [1], site-local is deprecated. I'm not sure if this exists in FreeBSD, but suggesting this address probably should probably be retracted.

[1] https://tools.ietf.org/html/rfc3879
Comment 4 Alan Somers freebsd_committer 2018-01-17 21:56:50 UTC
Good find Richard.  I didn't know that site-local addresses were deprecated, though I hadn't ever noticed anybody using them.  I'll keep searching for a better solution, because I too would like to write a rule like Daniel's.
Comment 5 Alan Somers freebsd_committer 2018-01-23 15:51:54 UTC
Created attachment 190002 [details]
Fix RDR rules that redirect to ::1

"Fix" pf rdr rules with ::1 targets

Redirecting an IPv6 packet to ::1 is a violation of IPv6 scoping rules, because
::1 only has node-local scope.  It's supposed to be used only for traffic the
begins and ends on a single node.  Plus, it's a bad practice security-wise
(server processes are often bound to ::1 as a deliberate way to prevent them
from talking to the outside world), and it doesn't work on multihomed,
single-fib hosts (the reply packet doesn't know which interface to use if its
destination is non-local).  However, the same arguments apply to IPv4, and
FreeBSD already has hacks to make it work there.  So I'm extending those hacks
to IPv6.

Even with this "fix", rdr won't work for packets destined to a link-local
(ff80::*) address, because pf doesn't know how to set the embedded scope
identifier on the reply packets.

A better solution would be to configure pf to only redirect a packet's port,
not its address and port.  However, pf doesn't currently have that capability.

	In ip6_input, don't reject a packet with bad scope if the firewall has
	changed the destination address.  ip_input does something similar with
	the "dchg" variable.

	In ip6_output, don't fail a packet immediately if the scope check
	fails.  Instead, repeat the scope check after running it through the
	firewall, if the firewall changed either the src or dst address.
	ip_output simply does the scope checking after the firewall.  However,
	ip6_output can't do that, because at that point multicast packets will
	have inconsistent embedded scope identifiers.