Page MenuHomeFreeBSD

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

Authored by kp on Jun 10 2017, 5:26 PM.


Group Reviewers

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 <>

Diff Detail

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

Event Timeline

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


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


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


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


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


Please put empty line after declarations.


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 (
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.