Page MenuHomeFreeBSD

PF: implement RFC 4787 REQ 1 and 3 (full cone NAT)
Needs ReviewPublic

Authored by kp on Jun 10 2017, 5:26 PM.
Tags
None
Referenced Files
Unknown Object (File)
Wed, Mar 13, 10:05 AM
Unknown Object (File)
Tue, Mar 12, 11:40 AM
Unknown Object (File)
Mon, Mar 4, 11:57 AM
Unknown Object (File)
Mon, Mar 4, 11:57 AM
Unknown Object (File)
Mon, Mar 4, 11:57 AM
Unknown Object (File)
Mon, Mar 4, 11:57 AM
Unknown Object (File)
Mon, Mar 4, 11:41 AM
Unknown Object (File)
Feb 27 2024, 6:00 PM

Details

Reviewers
None
Group Reviewers
network
Summary

This patch implements RFC 4787 requirements 1 and 3, changing PF's allocation of NAT mappings for UDP from the current "symmetric" NAT to a "endpoint-independent mapping" NAT, a.k.a "full cone" NAT. All UDP packets from the internal IP:port X:x go through the same external Y:y no matter the Z:z, and nothing but X:x uses Y:y.

Internal External
X:x -----> NAT Y:y ----> Z:z

The implementation is relatively straightforward. pf_state for UDP connections now reference a pf_udp_mapping, which is reference counted, and kept alive as long as at least 1 pf_state is referencing it. Every new NAT mapping that gets created tries to find a pf_udp_mapping by its source X:x endpoint and reuses its external Y:y, failing which, it creates a new one through an unused Y:y. Only allocation of NAT mappings is changed. Each X:x <-> Z:z still has its own distinct connection state (struct pf_state) and behaves the same as before.

Currently, only if a Z:z was previously transmitted to by X:x, can it transmit back to X:x through Y:y, i.e it behaves as a "port-restricted cone" NAT (or endpoint-independent mapping NAT with address- and port-dependent filtering, as per RFC 4787).

This provides application with a NAT hole punching capability. Unlike in a symmetric NAT, in any cone-type NAT, an internal UDP application can negotiate to receive packets from a known peer, by using STUN to create a external IP:port for its UDP socket and discover what they are, communicating them to its peer and learning what external IP:port its peer is using, and even if it's behind the most restrictive "ported-restricted cone" NAT, it can just send 1 packet to its peer's IP:port to create a connection and allow that peer to send packets back.

This works even if both peers are NATed, as long as at least 1 (the server) is not a symmetric NAT.

Submitted By: Damjan Jovanovic <damjan.jov@gmail.com>

Diff Detail

Repository
rS FreeBSD src repository - subversion
Lint
Lint Skipped
Unit
Tests Skipped
Build Status
Buildable 10045

Event Timeline

Did a style review, now working on understanding the gist of changes.

sys/net/pfvar.h
696

Please use new C standard uintXX_t types instead of historic u_intXX_t in new code.

sys/netpfil/pf/pf.c
454

Please use standard C keyword "inline" instead of "__inline" gcc-ism.

1402

Please follow style(9), do not initialize variables in declaration. They rule may be violated sometimes, but calling function in initializer is too much!

1414

Please put return values into braces, to follow style(9). This refers to all new returns in this patch.

1465

Please put empty line after declarations.

sys/netpfil/pf/pf_lb.c
224

Empty line below.

Damjan has updated the patch to fix the style issues and address a panic with non-udp traffic.

I've tested this patch on a setup where this feature is usefull: Using Mediation (NAT hole punching) feature of Strongswan for connecting 2 sites with IPSec, each site behind a NAT box (https://bsdrp.net/documentation/examples/strongswan_ipsec_mediation_feature).
Previously I had to use nat keyword "static-port" into pf line configuration for "helping" pf to keept original UDP source port, then allowing this feature to work.
With this patch, a standard pf nat (without any option) allow to create NAT hole: I don't think it's a "security downgrade" problem, because NAT was never desing for security.

For concluding: I like the feature brings by this patch :-)

Any news about pushing this change to head ?

Any news about pushing this change to head ?

I'm afraid not. I've not had the time or energy to review the patch, or examine the implications in sufficient detail. Considering all of the other issues I'm also struggling to find the time for I really can't promise anything.

TWIMC some users, some of whom seem willingly to invest time & work into this, are discussing this in the FreeBSD Forum; i.e. the pros & cons & whether it's worth it at all. Please chime in if you have some valuable arguments.

Tailscale has a good blog on NAT traversal that explains the value of allowing NAT hole punching https://tailscale.com/blog/how-nat-traversal-works/

Tailscale has a good blog on NAT traversal that explains the value of allowing NAT hole punching https://tailscale.com/blog/how-nat-traversal-works/

Is the assertion that this currently does not work with pf and will work with this patch?

I've been following discussions here and on the forums for literally years and I have yet to hear an explanation of why this is needed. Frankly, it's more than a little frustrating at this point.

My ask remains the same: explain why this is needed (and at this point: rebase the patch, add test cases and commit to supporting any fallout from it).

IMO RFC 4787 and the Tailscale blog provide reasonable justification for preferring endpoint-independent mapping. I guess one challenge in making the case is that functionality and failure modes fall on a spectrum, and applications implement fallbacks to work around the inability to transit NATs, to varying levels of success. One example is the inability of Nintendo Switch peer-to-peer communication to work between various "NAT types." (It doesn't help that Nintendo doesn't document what the "NAT Types" actually mean.) Various guides for Switch online gaming include suggestions like giving the Switch a static internal IP and forwarding ports 1-65535 to it to work around "NAT problems". Or, another example is Tailscale trying to use probabilistic port guessing to establish a connection or falling back to a TCP connection to a relay server if nothing else works.

I think it's uncontroversial that there are cases where EIM NAT provides a better user experience. Some applications can make use of UPnP, some users can research and configure port forwarding, but you're still left with cases where applications "just work" with "easy" NATs and do not with "hard" NATs. Look at the number of reddit posts, blogs etc. written about getting a Nintendo Switch to work with FreeBSD or pfSense, compared to the relative lack of issues with common consumer CPE.

All of that is separate from discussions of NAT security properties, opt-in/opt-out controls and default behaviors, tests, and the specific details of this patch. Those are all things that indeed need to be addressed as part of an effort to bring it in.

Rebased and added a test.

Since the work is already done and sitting on my computer, I thought it was worth pushing anyway.

I spent some time coming up with concrete use-cases to demonstrate this patch's usefulness:

  1. Here is a demonstration showing a 3x performance uplift in a file transfer over the Tailscale VPN from applying this patch on a router, enabling a peer-to-peer connection rather than using their relay servers: https://gist.github.com/tendstofortytwo/5bc7b158239b1216e338c45b56e6b9b1
  1. When I try to play Mario Kart 7 online on my Nintendo 2DS behind a FreeBSD router running pf NAT, I get error code 006-0612: https://files.catbox.moe/zt067x.jpg. While I can't test applying this patch to that router (I don't have admin privileges over that router), Nintendo's support page indicates that a less restrictive NAT type would solve this issue: https://en-americas-support.nintendo.com/app/answers/detail/a_id/25881/~/error-code%3A-006-0612