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.

NAT Types Overview


Why NAT Matters for P2P

The challenge with P2P: Neither peer can initiate a connection to the other because NATs block unsolicited incoming traffic. Macula solves this by detecting NAT characteristics and choosing optimal traversal strategies.


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

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

Only hosts we've contacted can send back (but from any port). Direct P2P: YES with hole punching.

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

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

The diagram at the top of this document shows the connection strategy matrix. Macula's macula_nat_coordinator follows this logic:

  1. Either has public IP? → Direct connection to public peer
  2. Both have EI mapping? → Hole punching possible
    • EI filtering → Simple hole punch
    • PD filtering → Coordinated hole punch
  3. Either has PD+PD+RD (symmetric)? → Must use relay
  4. Otherwise → Try hole punch with port prediction

Hole Punching Explained

Hole punching creates NAT mappings that allow peers to communicate:

TimeEventResult
T0Initial statePeer A and B have no mappings to each other
T1Coordinator signalBoth peers simultaneously send packets to each other's predicted external address
T2NAT processingBoth NATs create outbound mappings for the other peer's address
T3Connection establishedSubsequent packets pass through created mappings

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:

LayerAddressRole
Your Device192.168.1.10Local network address
Home Router NAT→ 10.0.0.50ISP private address (RFC 1918)
CGNAT→ 203.0.113.5Public address (shared with other customers)
InternetGlobal routing

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: