Privacy Tools Guide

Network Address Translation (NAT) has been a fundamental part of internet infrastructure since the IPv4 address shortage began. When you connect to a VPN, NAT traversal becomes one of the most challenging technical hurdles your connection must overcome. This article explains how VPN NAT traversal works, particularly in environments using Carrier-Grade NAT (CGNAT), and provides practical insights for developers and power users.

Understanding NAT and Its Impact on VPNs

NAT allows multiple devices to share a single public IP address. When a device behind NAT sends a packet, the NAT device records the mapping between the internal IP:port and the public IP:port, then rewrites the packet headers. Incoming packets are routed back using this mapping.

For VPNs, this creates a fundamental problem: a VPN tunnel requires the remote server to initiate or maintain a connection to your device. Traditional NAT only supports outbound-initiated connections. When both peers are behind NAT, neither can initiate a direct connection to the other.

Types of NAT Behavior

NAT devices implement different behaviors that affect VPN connectivity:

Full Cone NAT maps an internal port to a public port and accepts incoming packets from any external IP:port. Once mapped, any external host can send data to that port.

Address-Restricted Cone NAT accepts incoming packets from any port on a specific external IP, but not from other IP addresses.

Port-Restricted Cone NAT accepts incoming packets only from a specific external IP:port combination—the most restrictive of the cone NAT types.

Symmetric NAT maps each unique destination to a different public port. This is the most restrictive and problematic type for peer-to-peer VPN connections.

You can discover your NAT type using a STUN server:

import stun

# Discover NAT type and public IP
nat_type, external_ip, external_port = stun.get_ip_info()
print(f"NAT Type: {nat_type}")
print(f"External: {external_ip}:{external_port}")

Carrier-Grade NAT Explained

Carrier-Grade NAT extends NAT to ISP level. Instead of NAT happening at your router, the ISP’s infrastructure handles address translation for entire subscriber groups. This means:

  1. Your public IP is shared among dozens or hundreds of customers
  2. Inbound connections are impossible without port forwarding
  3. Your actual public IP differs from what websites see

CGNAT typically uses symmetric NAT behavior, making peer-to-peer VPN connections extremely difficult. Many mobile networks and some residential ISPs now deploy CGNAT exclusively.

To check if you’re behind CGNAT:

# Compare your public IP with what your router reports
curl -s ifconfig.me # Your apparent public IP
curl -s ipinfo.io/ip # Alternative check
# If these differ from your router's WAN IP, you're likely behind CGNAT

NAT Traversal Techniques for VPNs

Modern VPNs employ several techniques to establish connections through NAT.

STUN (Session Traversal Utilities for NAT)

STUN servers allow clients to discover their public IP address and port mappings. The client sends a request to the STUN server, which responds with the external address and port the NAT assigned.

import stun

# Simple STUN query
nat_type, external_ip, external_port = stun.get_ip_info(
 stun_host='stun.l.google.com',
 stun_port=19302
)
print(f"Public endpoint: {external_ip}:{external_port}")

STUN works well for cone NAT but fails with symmetric NAT or when both peers are behind CGNAT.

TURN (Traversal Using Relays around NAT)

TURN relays all traffic through an intermediate server. When direct peer-to-peer connection is impossible, both clients connect to a TURN server which forwards traffic between them.

// Example TURN configuration for WebRTC
const iceServers = {
 iceServers: [
 { urls: 'stun:stun.l.google.com:19302' },
 {
 urls: 'turn:turn.example.com:3478',
 username: 'user',
 credential: 'password'
 }
 ]
};

const pc = new RTCPeerConnection(iceServers);

TURN guarantees connectivity but introduces latency and bandwidth costs since all traffic flows through the relay server.

ICE (Interactive Connectivity Establishment)

ICE combines STUN and TURN, attempting the most efficient connection method first. It tests multiple candidate pairs and falls back to relay connections when direct connectivity fails.

The candidate gathering process:

  1. Host candidates: Local IP addresses and ports
  2. Server-reflexive (srflx) candidates: Public endpoints from STUN
  3. Relayed candidates: TURN server allocations as fallback
# Simplified ICE candidate gathering concept
candidates = []

# Gather host candidates
for iface in get_network_interfaces():
 candidates.append(f"candidate:1 1 UDP {iface.ip} {iface.port} typ host")

# Gather srflx candidates via STUN
stun_response = query_stun_server(public_stun_server)
candidates.append(f"candidate:2 1 UDP {stun_response.ip} {stun_response.port} typ srflx")

# Gather relayed candidates via TURN
turn_allocation = request_turn_allocation(turn_server)
candidates.append(f"candidate:3 1 UDP {turn_allocation.ip} {turn_allocation.port} typ relay")

UDP Hole Punching

The most elegant solution for NAT traversal, UDP hole punching exploits how NAT devices handle outbound connections:

  1. Both peers send UDP packets to a well-known server (the rendezvous server)
  2. The NAT creates mappings for the outbound traffic
  3. The server shares each peer’s public endpoint with the other
  4. Both peers send packets to each other’s public endpoints
  5. The NAT devices, expecting return traffic, allow the packets through

This technique fails with symmetric NAT, which assigns different public ports for each destination.

Practical VPN Implementation

Most modern VPN protocols handle NAT traversal automatically. WireGuard, for instance, includes keepalive packets that maintain NAT mappings:

# WireGuard configuration with persistent keepalive
# This ensures NAT mappings stay open
[Peer]
PublicKey = <server-public-key>
Endpoint = vpn.example.com:51820
PersistentKeepalive = 25 # Send keepalive every 25 seconds
AllowedIPs = 0.0.0.0/0

OpenVPN can be configured to use NAT traversal techniques:

# OpenVPN NAT traversal options
# Force NAT type detection
--nat-detect

# Use UDP with NAT traversal
--proto udp

# Keepalive for NAT mappings
--keepalive 10 60

For peer-to-peer VPNs like Tailscale or ZeroTier, the control plane handles NAT traversal automatically using a combination of DERP (Dangerously Exit Relay Proxy) servers and direct STUN/TURN fallback.

Troubleshooting NAT Traversal Issues

When VPN connections fail due to NAT, diagnostic steps include:

  1. Verify NAT type: Use online tools or the stun library to determine your NAT behavior
  2. Check CGNAT status: Compare your apparent public IP with your router’s WAN IP
  3. Test UDP connectivity: Many VPN protocols require UDP; verifyUDP port 500/4500 (IPsec) or 51820 (WireGuard) are not blocked
  4. Enable port forwarding: If your router supports it, forward the necessary ports to your VPN client
  5. Use relay fallback: Configure your VPN to use TURN or similar relay services as fallback
# Test UDP port reachability
nc -zvu vpn.example.com 51820

# Check if your ISP blocks common VPN ports
nmap -p 500,4500,1701,51820 -sU your-isp-gateway

Built by theluckystrike — More at zovo.one