Topic Overview

NAT & PAT

Understand Network Address Translation (NAT) and Port Address Translation (PAT) for connecting private networks to the internet and conserving IPv4 addresses.

NAT (Network Address Translation) allows private networks to use private IP addresses while accessing the internet through a public IP address. PAT (Port Address Translation), also called NAT overload, extends NAT to allow multiple devices to share a single public IP using different ports.


What is NAT?

NAT (Network Address Translation) translates private IP addresses to public IP addresses (and vice versa) at the network boundary.

Why needed:

  • IPv4 exhaustion: Limited public IP addresses
  • Private networks: Use RFC 1918 private addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
  • Security: Hide internal network structure
  • Cost: Reduce need for multiple public IPs

NAT Types

1. Static NAT

One-to-one mapping: One private IP → One public IP

Private Network          Public Network
192.168.1.10    →    203.0.113.10
192.168.1.20    →    203.0.113.20

Use case: Servers that need consistent public IP

2. Dynamic NAT

Many-to-many mapping: Multiple private IPs → Pool of public IPs

Private Network          Public IP Pool
192.168.1.10    →    203.0.113.10-15
192.168.1.20    →    (assigned dynamically)
192.168.1.30    →

Use case: Multiple devices sharing a pool of public IPs

3. PAT (Port Address Translation / NAT Overload)

Many-to-one mapping: Multiple private IPs → Single public IP (using ports)

Private Network          Public Network
192.168.1.10:5000  →  203.0.113.1:10000
192.168.1.20:5000  →  203.0.113.1:10001
192.168.1.30:5000  →  203.0.113.1:10002

Use case: Home networks, small offices (most common)


NAT Process

Outbound Traffic (Private → Public)

1. Device (192.168.1.10) sends packet to 8.8.8.8
   Source: 192.168.1.10:5000
   Dest: 8.8.8.8:53

2. NAT router receives packet
   - Checks NAT table
   - If no entry, creates translation
   - Translates source IP/port

3. NAT router forwards packet
   Source: 203.0.113.1:10000 (translated)
   Dest: 8.8.8.8:53

4. Response comes back
   Source: 8.8.8.8:53
   Dest: 203.0.113.1:10000

5. NAT router translates back
   Source: 8.8.8.8:53
   Dest: 192.168.1.10:5000 (original)

NAT Table

NAT router maintains a translation table:

Inside Local          Inside Global          Outside Global    Outside Local
192.168.1.10:5000  →  203.0.113.1:10000  ↔  8.8.8.8:53      →  8.8.8.8:53
192.168.1.20:5000  →  203.0.113.1:10001  ↔  93.184.216.34:80 → 93.184.216.34:80

Table entries expire after timeout (typically 30-60 seconds of inactivity)


PAT (Port Address Translation)

PAT allows multiple devices to share one public IP by using different ports.

PAT Example

Device A (192.168.1.10) → Public IP (203.0.113.1:10000)
Device B (192.168.1.20) → Public IP (203.0.113.1:10001)
Device C (192.168.1.30) → Public IP (203.0.113.1:10002)

How it works:

  1. Device sends packet with source port (e.g., 5000)
  2. NAT router assigns unique public port (e.g., 10000)
  3. Maps private IP:port → public IP:port
  4. Response uses same mapping to route back

Port range: Typically 1024-65535 (ephemeral ports)


Examples

NAT Configuration (Router)

# Static NAT (Cisco)
ip nat inside source static 192.168.1.10 203.0.113.10
interface GigabitEthernet0/0
  ip nat inside
interface GigabitEthernet0/1
  ip nat outside

# Dynamic NAT
ip nat pool PUBLIC_POOL 203.0.113.10 203.0.113.15 netmask 255.255.255.0
ip nat inside source list 1 pool PUBLIC_POOL
access-list 1 permit 192.168.1.0 0.0.0.255

# PAT (NAT Overload)
ip nat inside source list 1 interface GigabitEthernet0/1 overload

NAT Simulation (Python)

import socket
from collections import defaultdict
import time

class NATRouter:
    def __init__(self, public_ip):
        self.public_ip = public_ip
        self.nat_table = {}  # (private_ip, private_port) → (public_port, timestamp)
        self.port_counter = 10000
        self.timeout = 60  # seconds
    
    def translate_outbound(self, private_ip, private_port, dest_ip, dest_port):
        """Translate outbound packet (private → public)"""
        key = (private_ip, private_port)
        
        # Check if translation exists
        if key in self.nat_table:
            public_port, timestamp = self.nat_table[key]
            # Update timestamp
            self.nat_table[key] = (public_port, time.time())
            return self.public_ip, public_port
        
        # Create new translation
        public_port = self.port_counter
        self.port_counter += 1
        if self.port_counter > 65535:
            self.port_counter = 10000
        
        self.nat_table[key] = (public_port, time.time())
        
        print(f"NAT: {private_ip}:{private_port}{self.public_ip}:{public_port}")
        return self.public_ip, public_port
    
    def translate_inbound(self, public_port, dest_ip, dest_port):
        """Translate inbound packet (public → private)"""
        # Find private IP:port for this public port
        for (private_ip, private_port), (pub_port, timestamp) in self.nat_table.items():
            if pub_port == public_port:
                # Check timeout
                if time.time() - timestamp > self.timeout:
                    del self.nat_table[(private_ip, private_port)]
                    continue
                
                # Update timestamp
                self.nat_table[(private_ip, private_port)] = (pub_port, time.time())
                print(f"NAT: {self.public_ip}:{public_port}{private_ip}:{private_port}")
                return private_ip, private_port
        
        return None, None  # No translation found
    
    def cleanup_expired(self):
        """Remove expired NAT entries"""
        now = time.time()
        expired = [
            key for key, (port, timestamp) in self.nat_table.items()
            if now - timestamp > self.timeout
        ]
        for key in expired:
            del self.nat_table[key]

# Usage
nat = NATRouter("203.0.113.1")

# Outbound translation
public_ip, public_port = nat.translate_outbound("192.168.1.10", 5000, "8.8.8.8", 53)
print(f"Translated to: {public_ip}:{public_port}")

# Inbound translation
private_ip, private_port = nat.translate_inbound(public_port, "8.8.8.8", 53)
print(f"Translated back to: {private_ip}:{private_port}")

NAT Table Monitoring

def show_nat_table(nat_router):
    """Display NAT translation table"""
    print("NAT Translation Table:")
    print(f"{'Inside Local':<20} {'Inside Global':<20} {'Age':<10}")
    print("-" * 50)
    
    now = time.time()
    for (private_ip, private_port), (public_port, timestamp) in nat_router.nat_table.items():
        age = int(now - timestamp)
        inside_local = f"{private_ip}:{private_port}"
        inside_global = f"{nat_router.public_ip}:{public_port}"
        print(f"{inside_local:<20} {inside_global:<20} {age}s")

# Usage
show_nat_table(nat)

NAT Limitations

Problems with NAT

  1. End-to-end connectivity: Breaks end-to-end principle
  2. Peer-to-peer applications: Difficult for P2P (need port forwarding)
  3. IPsec: Can conflict with NAT (NAT traversal needed)
  4. Logging: Harder to track individual users
  5. Port exhaustion: PAT can run out of ports (65,535 limit)

NAT Traversal

NAT Traversal techniques for applications that need direct connections:

  1. STUN (Session Traversal Utilities for NAT): Discover public IP:port
  2. TURN (Traversal Using Relays around NAT): Relay server for NAT traversal
  3. ICE (Interactive Connectivity Establishment): Combines STUN and TURN
  4. UPnP (Universal Plug and Play): Automatic port forwarding

Common Pitfalls

  • Port exhaustion: Too many connections exhaust available ports. Fix: Increase port range, use connection pooling, reduce timeout
  • NAT table overflow: Too many translations consume memory. Fix: Reduce timeout, limit connections per device
  • Breaking applications: Some apps don't work through NAT. Fix: Use NAT traversal (STUN/TURN), port forwarding
  • Not understanding PAT: Confusing static NAT with PAT. Fix: Understand PAT uses ports, static NAT doesn't
  • Double NAT: NAT behind NAT causes issues. Fix: Avoid nested NAT, use bridge mode
  • Timeout too short: Connections drop prematurely. Fix: Increase NAT timeout for long-lived connections
  • Security through obscurity: Relying on NAT for security. Fix: Use proper firewalls, don't rely on NAT alone

Interview Questions

Beginner

Q: What is NAT and why is it used?

A:

NAT (Network Address Translation) translates private IP addresses to public IP addresses at the network boundary.

Why used:

  1. IPv4 exhaustion: Limited public IP addresses available
  2. Private networks: Use RFC 1918 private addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
  3. Security: Hide internal network structure from internet
  4. Cost: Reduce need for multiple public IP addresses
  5. Flexibility: Easy to change internal network without affecting external

How it works:

Private Network          NAT Router          Internet
192.168.1.10    →    203.0.113.1    →    8.8.8.8
(Private IP)         (Public IP)         (Internet)

Types:

  • Static NAT: One private IP → One public IP
  • Dynamic NAT: Multiple private IPs → Pool of public IPs
  • PAT (NAT Overload): Multiple private IPs → One public IP (using ports)

Intermediate

Q: Explain the difference between NAT and PAT. How does PAT allow multiple devices to share one public IP?

A:

NAT (Network Address Translation):

  • Translates IP addresses only
  • Requires one public IP per private IP (or pool)
  • Used for static or dynamic mapping

PAT (Port Address Translation / NAT Overload):

  • Translates both IP addresses and ports
  • Allows multiple private IPs to share one public IP
  • Uses different ports to distinguish connections

How PAT works:

Device A (192.168.1.10:5000) → Public (203.0.113.1:10000)
Device B (192.168.1.20:5000) → Public (203.0.113.1:10001)
Device C (192.168.1.30:5000) → Public (203.0.113.1:10002)

PAT Process:

  1. Device sends packet: Source 192.168.1.10:5000
  2. NAT router assigns unique public port: 10000
  3. Creates mapping: (192.168.1.10:5000) → (203.0.113.1:10000)
  4. Forwards packet with translated source
  5. Response comes to 203.0.113.1:10000
  6. NAT router looks up mapping, translates back to 192.168.1.10:5000

NAT Table:

Inside Local          Inside Global
192.168.1.10:5000  →  203.0.113.1:10000
192.168.1.20:5000  →  203.0.113.1:10001
192.168.1.30:5000  →  203.0.113.1:10002

Benefits of PAT:

  • Efficiency: One public IP for entire network
  • Cost: No need for multiple public IPs
  • Scalability: Support many devices (up to port limit)

Limitations:

  • Port exhaustion: Maximum 65,535 ports
  • Peer-to-peer: Difficult for P2P applications
  • Logging: Harder to track individual users

Senior

Q: Design a high-performance NAT system for a cloud provider that handles millions of concurrent connections. How do you handle port exhaustion, connection tracking, and ensure low latency?

A:

class HighPerformanceNAT {
  private natTable: DistributedNATTable;
  private portAllocator: PortAllocator;
  private connectionTracker: ConnectionTracker;
  private loadBalancer: LoadBalancer;
  
  constructor() {
    // Distributed NAT table (Redis cluster)
    this.natTable = new DistributedNATTable({
      backend: 'redis-cluster',
      replication: 3,
      sharding: 'consistent-hashing'
    });
    
    // Port allocator with port pools
    this.portAllocator = new PortAllocator({
      portRange: [10000, 65535],
      poolsPerIP: 1000
    });
    
    // Connection tracking
    this.connectionTracker = new ConnectionTracker({
      timeout: 60, // seconds
      maxConnections: 1000000
    });
  }
  
  // 1. Distributed NAT Translation
  async translateOutbound(
    privateIP: string,
    privatePort: number,
    destIP: string,
    destPort: number
  ): Promise<Translation> {
    const key = `${privateIP}:${privatePort}`;
    
    // Check existing translation
    const existing = await this.natTable.get(key);
    if (existing && !this.isExpired(existing)) {
      await this.natTable.updateTimestamp(key);
      return existing;
    }
    
    // Allocate new translation
    const publicIP = await this.loadBalancer.selectPublicIP();
    const publicPort = await this.portAllocator.allocate(publicIP);
    
    const translation = {
      privateIP,
      privatePort,
      publicIP,
      publicPort,
      destIP,
      destPort,
      timestamp: Date.now(),
      connectionId: this.generateConnectionId()
    };
    
    // Store in distributed table
    await this.natTable.set(key, translation, { ttl: 60 });
    
    // Track connection
    await this.connectionTracker.track(translation);
    
    return translation;
  }
  
  // 2. Port Allocation with Pools
  class PortAllocator {
    private portPools: Map<string, PortPool>;
    
    async allocate(publicIP: string): Promise<number> {
      let pool = this.portPools.get(publicIP);
      
      if (!pool || pool.isExhausted()) {
        pool = await this.createPortPool(publicIP);
        this.portPools.set(publicIP, pool);
      }
      
      return await pool.allocate();
    }
    
    async createPortPool(publicIP: string): Promise<PortPool> {
      // Allocate port range for this IP
      const startPort = this.getNextPortRange();
      const endPort = startPort + 1000; // 1000 ports per pool
      
      return new PortPool(publicIP, startPort, endPort);
    }
  }
  
  // 3. Connection Tracking
  class ConnectionTracker {
    private connections: Map<string, Connection>;
    
    async track(translation: Translation): Promise<void> {
      const connection = {
        id: translation.connectionId,
        translation,
        packets: 0,
        bytes: 0,
        startTime: Date.now(),
        lastActivity: Date.now()
      };
      
      this.connections.set(translation.connectionId, connection);
      
      // Set expiration
      setTimeout(() => {
        this.cleanup(translation.connectionId);
      }, 60000);
    }
    
    async updateActivity(connectionId: string, bytes: number): Promise<void> {
      const connection = this.connections.get(connectionId);
      if (connection) {
        connection.packets++;
        connection.bytes += bytes;
        connection.lastActivity = Date.now();
      }
    }
  }
  
  // 4. Port Exhaustion Handling
  async handlePortExhaustion(publicIP: string): Promise<void> {
    // Strategy 1: Reuse expired ports
    const expired = await this.natTable.getExpired(publicIP);
    if (expired.length > 0) {
      await this.portAllocator.release(expired.map(e => e.publicPort));
      return;
    }
    
    // Strategy 2: Use additional public IP
    const newPublicIP = await this.loadBalancer.addPublicIP();
    await this.portAllocator.createPool(newPublicIP);
    
    // Strategy 3: Increase timeout (aggressive cleanup)
    await this.natTable.cleanupExpired(publicIP, { aggressive: true });
  }
  
  // 5. Load Balancing Across Public IPs
  class LoadBalancer {
    private publicIPs: string[];
    private ipUsage: Map<string, number>;
    
    async selectPublicIP(): Promise<string> {
      // Select least used public IP
      let minUsage = Infinity;
      let selectedIP = this.publicIPs[0];
      
      for (const ip of this.publicIPs) {
        const usage = this.ipUsage.get(ip) || 0;
        if (usage < minUsage) {
          minUsage = usage;
          selectedIP = ip;
        }
      }
      
      this.ipUsage.set(selectedIP, (this.ipUsage.get(selectedIP) || 0) + 1);
      return selectedIP;
    }
  }
  
  // 6. Monitoring and Health Checks
  async getMetrics(): Promise<Metrics> {
    return {
      totalTranslations: await this.natTable.getCount(),
      activeConnections: await this.connectionTracker.getCount(),
      portUtilization: await this.portAllocator.getUtilization(),
      publicIPUsage: await this.loadBalancer.getUsage(),
      averageLatency: await this.getAverageLatency()
    };
  }
}

Features:

  1. Distributed NAT table: Redis cluster for scalability
  2. Port pools: Allocate port ranges per public IP
  3. Connection tracking: Monitor active connections
  4. Port exhaustion handling: Reuse expired ports, add IPs, aggressive cleanup
  5. Load balancing: Distribute across multiple public IPs
  6. Monitoring: Track metrics, detect issues

Key Takeaways

  • NAT: Translates private IPs to public IPs at network boundary
  • Types: Static (1:1), Dynamic (many:many), PAT (many:1 with ports)
  • PAT: Uses ports to allow multiple devices to share one public IP
  • NAT table: Tracks translations, entries expire after timeout
  • Process: Outbound (private→public), inbound (public→private) using table lookup
  • Limitations: Breaks end-to-end connectivity, P2P issues, port exhaustion
  • NAT traversal: STUN/TURN/ICE for applications needing direct connections
  • Best practices: Monitor port usage, use connection pooling, implement proper timeouts

About the author

InterviewCrafted helps you master system design with patience. We believe in curiosity-led engineering, reflective writing, and designing systems that make future changes feel calm.