NAT Types Explained

View Source

Understanding NAT (Network Address Translation) types is essential for building P2P applications that work across different network environments. This guide explains the NAT classification model used in Macula based on the NATCracker methodology.


Why NAT Matters for P2P

When two peers behind NAT want to communicate directly:

Peer A (192.168.1.10)                    Peer B (192.168.2.20)
       |                                        |
   [NAT A]                                  [NAT B]
       |                                        |
  203.0.113.5:40000                      198.51.100.8:50000
       |                                        |
       +------------  Internet  ----------------+

The challenge: Neither peer can initiate a connection to the other because NATs block unsolicited incoming traffic.


NAT Policy Classification

Macula uses the NATCracker 3-policy classification model to characterize NAT behavior. Each NAT is described by three policies:

1. Mapping Policy (How NAT assigns external addresses)

PolicyCodeBehaviorPrevalence
Endpoint-IndependentEISame external addr for all destinations~52%
Host-DependentHDDifferent external addr per destination host~12%
Port-DependentPDDifferent external addr per destination host:port~36%

Example - Endpoint-Independent (EI):

Local: 192.168.1.10:5000
  -> Destination A: 8.8.8.8:53     => NAT maps to 203.0.113.5:40000
  -> Destination B: 1.1.1.1:53     => NAT maps to 203.0.113.5:40000  (same!)

Example - Port-Dependent (PD):

Local: 192.168.1.10:5000
  -> Destination A: 8.8.8.8:53     => NAT maps to 203.0.113.5:40000
  -> Destination B: 1.1.1.1:53     => NAT maps to 203.0.113.5:40001  (different!)

2. Filtering Policy (What incoming traffic NAT accepts)

PolicyCodeBehaviorSecurity
Endpoint-IndependentEIAccepts from any sourceLow
Host-DependentHDAccepts from hosts we've contactedMedium
Port-DependentPDAccepts from host:port we've contactedHigh

Example - Port-Dependent filtering:

Local 192.168.1.10:5000 sends to 8.8.8.8:53
NAT now accepts incoming on 203.0.113.5:40000 ONLY from 8.8.8.8:53
  -> 8.8.8.8:53     => ALLOWED
  -> 8.8.8.8:80     => BLOCKED (wrong port)
  -> 1.1.1.1:53     => BLOCKED (wrong host)

3. Allocation Policy (How NAT chooses external ports)

PolicyCodeBehaviorPredictability
Port-PreservationPPexternal_port = local_portHigh
Port-ContiguityPCexternal_port = last_port + deltaMedium
RandomRDNo predictable patternNone

Example - Port-Preservation (PP):

Local: 192.168.1.10:5000  => NAT: 203.0.113.5:5000  (same port!)

Example - Port-Contiguity (PC):

Local: 192.168.1.10:5000  => NAT: 203.0.113.5:40000
Local: 192.168.1.10:5001  => NAT: 203.0.113.5:40001  (delta = 1)

Common NAT Type Combinations

Based on NATCracker research across millions of NATs:

TypeMappingFilteringAllocationPrevalenceDirect P2P
Full ConeEIEIPP15%Yes
Restricted ConeEIHDPP37%With punch
Port RestrictedEIPDPP20%With punch
SymmetricPDPDRD12%No (relay)
CGNATvariesPDvaries16%Usually relay

Full Cone NAT (EI, EI, PP) - Best Case

                   Internet
                      |
                 [Full Cone NAT]
                      |
             203.0.113.5:5000
                      |
Any external host can send to this address
after ANY outbound packet from local peer

Direct P2P: YES - Any peer can connect directly

Restricted Cone NAT (EI, HD, PP) - Good

Local sends to Host A
  -> External: 203.0.113.5:5000

Now Host A (any port) can send back
Host B cannot send (never contacted)

Direct P2P: YES with hole punching

Symmetric NAT (PD, PD, RD) - Worst Case

Local sends to Host A:Port1 -> NAT: 203.0.113.5:40000
Local sends to Host B:Port2 -> NAT: 203.0.113.5:40001 (different!)

Each destination gets different external address
External port is random and unpredictable

Direct P2P: NO - must use relay

NAT Detection in Macula

Macula detects NAT type automatically using macula_nat_detector:

%% Get local NAT profile
{ok, Profile} = macula_nat_detector:get_local_profile().

%% Profile contains:
#{
    mapping => ei,           % Endpoint-Independent
    filtering => pd,         % Port-Dependent
    allocation => pp,        % Port-Preservation
    public_ip => {203,0,113,5},
    public_port => 5000,
    detected_at => 1700000000
}

Detection Algorithm

  1. Send NAT_PROBE to primary observer (gateway/public peer)

    • Receive reflexive address (your public IP:port as seen from outside)
  2. Send NAT_PROBE to secondary observer (different public peer)

    • Compare reflexive addresses
  3. Classification:

    • Same address for both observers -> EI mapping
    • Same IP, different port -> HD mapping
    • Different IP -> PD mapping (or multiple NATs)

Connection Strategy Decision Tree

Macula's macula_nat_coordinator uses this decision tree:

Start: Want to connect Peer A <-> Peer B
  |
  v
Either has public IP?
  |-- YES -> Direct connection to public peer
  |-- NO  -> Check NAT profiles
              |
              v
         Both have EI mapping?
           |-- YES -> Hole punching possible
           |          |
           |          v
           |     Any has EI filtering?
           |       |-- YES -> Simple hole punch
           |       |-- NO  -> Coordinated hole punch
           |
           |-- NO  -> Either has PD+PD+RD (symmetric)?
                        |-- YES -> Must use relay
                        |-- NO  -> Try hole punch with prediction

Hole Punching Explained

Hole punching creates NAT mappings that allow peers to communicate:

Time T0: Peer A and B have no mappings to each other

Time T1: Coordinator tells both peers to send packet
         Peer A sends to Peer B's predicted external addr
         Peer B sends to Peer A's predicted external addr

Time T2: Packets arrive at NATs
         NAT A: Creates mapping for B's address (outbound packet)
         NAT B: Creates mapping for A's address (outbound packet)

Time T3: Subsequent packets pass through created mappings
         Direct communication established!

Requirements for successful hole punch:

  • Both NATs have EI or HD mapping (predictable external address)
  • At least one has PP or PC allocation (predictable port)
  • Timing coordination within ~100ms

CGNAT (Carrier-Grade NAT)

ISPs increasingly use CGNAT, adding another NAT layer:

Your Device (192.168.1.10)
       |
   [Home Router NAT]
       |
   10.0.0.50 (ISP private)
       |
   [CGNAT]
       |
   203.0.113.5 (public)
       |
   Internet

CGNAT complications:

  • Multiple customers share same public IP
  • Often uses PD filtering (restrictive)
  • Hole punching success rate drops to ~40%
  • Relay fallback frequently needed

Best Practices

For Application Developers

  1. Always have relay fallback - Some NATs cannot be traversed
  2. Detect NAT type early - Cache profile at peer startup
  3. Prefer EI-mapping peers as coordinators - Better success rate

For Network Operators

  1. Use Full Cone or Restricted Cone NAT - Best P2P compatibility
  2. Enable UPnP/NAT-PMP - Allows applications to request mappings
  3. Avoid Symmetric NAT - Breaks most P2P protocols

Further Reading


See Also: