CrackedRuby CrackedRuby

Overview

Network Address Translation (NAT) and Port Address Translation (PAT) are network protocols that modify IP address information in packet headers as traffic passes through a routing device. NAT translates private IP addresses to public IP addresses, enabling multiple devices on a private network to share a single public IP address for internet access. PAT extends NAT by also translating port numbers, allowing multiple devices to share a single IP address simultaneously.

Organizations implement NAT to conserve public IPv4 addresses and add a layer of network security by hiding internal network topology. Without NAT, the limited IPv4 address space would have exhausted long ago, preventing new devices from connecting to the internet. NAT emerged as a critical interim solution while IPv6 adoption progresses.

The translation process occurs at network boundaries, typically on routers or firewalls. When an internal host sends a packet to an external destination, the NAT device replaces the private source IP address with its public IP address before forwarding. Return packets undergo reverse translation, converting the public destination address back to the appropriate private address.

Internal Network (192.168.1.0/24)
    |
    | Private IP: 192.168.1.10:5000
    v
NAT Router (203.0.113.5)
    |
    | Public IP: 203.0.113.5:50000
    v
Internet

NAT introduces state into the network layer, requiring the router to maintain a translation table mapping internal addresses to external addresses. This stateful behavior creates implications for application protocols, network troubleshooting, and system design.

Key Principles

NAT operates by maintaining a translation table that maps private IP addresses to public IP addresses. Each entry in this table represents an active connection or translation rule. The router inspects IP packet headers, identifies matching translation rules, and rewrites address fields before forwarding packets.

The translation table contains several fields: original source IP, original source port, translated source IP, translated source port, destination IP, and destination port. Some NAT implementations also track protocol type, connection state, and timeout values.

Address types follow specific conventions. Private IP address ranges defined in RFC 1918 include 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16. These addresses never route directly on the public internet. Public IP addresses come from ranges allocated by Regional Internet Registries and must be globally unique.

Translation occurs in two directions. Outbound translation (source NAT) happens when private hosts initiate connections to public destinations. The NAT device replaces the private source address with a public address. Inbound translation (destination NAT) occurs when external hosts connect to published services, with the NAT device replacing the public destination address with a private address.

Port Address Translation extends basic NAT by including transport layer port numbers in the translation process. A single public IP address can multiplex thousands of internal connections by assigning unique port numbers to each session. The router tracks each connection's port mapping, ensuring response packets reach the correct internal host.

# Conceptual translation table structure
class NATEntry
  attr_reader :internal_ip, :internal_port, :external_ip, :external_port,
              :destination_ip, :destination_port, :protocol, :timestamp
  
  def initialize(internal_ip:, internal_port:, external_ip:, external_port:,
                 destination_ip:, destination_port:, protocol:)
    @internal_ip = internal_ip
    @internal_port = internal_port
    @external_ip = external_ip
    @external_port = external_port
    @destination_ip = destination_ip
    @destination_port = destination_port
    @protocol = protocol
    @timestamp = Time.now
  end
  
  def expired?(timeout = 300)
    Time.now - @timestamp > timeout
  end
end

Connection tracking forms the foundation of stateful NAT. The router monitors TCP connection states (SYN, SYN-ACK, ESTABLISHED, FIN) and UDP pseudo-connections to determine when to create and remove translation entries. ICMP requires special handling since it lacks port numbers.

NAT creates a distinction between endpoints. The device with the NAT traversal capability can initiate outbound connections freely. However, unsolicited inbound connections fail unless the NAT device has explicit port forwarding rules. This asymmetry affects peer-to-peer protocols, VoIP, and other applications requiring bidirectional connectivity.

Implementation Approaches

Static NAT establishes permanent one-to-one mappings between private and public IP addresses. Each internal host receives a dedicated public IP address. The translation remains constant regardless of connection state. Static NAT suits servers requiring consistent external addresses for DNS records or firewall rules.

Internal: 192.168.1.10 <-> External: 203.0.113.10
Internal: 192.168.1.11 <-> External: 203.0.113.11
Internal: 192.168.1.12 <-> External: 203.0.113.12

Static NAT provides no IP address conservation since it requires one public IP per internal host. It offers the simplest deployment model and maintains protocol compatibility since port numbers remain unchanged.

Dynamic NAT allocates public IP addresses from a pool as internal hosts initiate connections. When an internal host starts a new connection, the NAT device assigns an available public IP from the pool. The mapping persists for the connection duration, then returns to the pool. Multiple internal hosts share a pool of public IPs, but only one internal host uses each public IP at any given time.

class DynamicNAT
  def initialize(public_ip_pool)
    @pool = public_ip_pool
    @active_mappings = {}
    @available_ips = public_ip_pool.dup
  end
  
  def allocate_address(internal_ip)
    return @active_mappings[internal_ip] if @active_mappings[internal_ip]
    
    if @available_ips.empty?
      raise "No available public IP addresses"
    end
    
    public_ip = @available_ips.shift
    @active_mappings[internal_ip] = public_ip
    public_ip
  end
  
  def release_address(internal_ip)
    public_ip = @active_mappings.delete(internal_ip)
    @available_ips << public_ip if public_ip
  end
end

Dynamic NAT conserves addresses better than static NAT but still limits concurrent connections to the pool size. It maintains port numbers, preserving compatibility with protocols sensitive to port changes.

Port Address Translation (PAT), also called NAT Overload or NAPT, maps multiple private IP addresses to a single public IP address by translating both IP addresses and port numbers. The NAT device assigns unique port numbers to each internal connection, multiplexing thousands of connections through one public IP.

Internal: 192.168.1.10:5000 <-> External: 203.0.113.5:50000 -> Destination: 198.51.100.20:80
Internal: 192.168.1.11:5000 <-> External: 203.0.113.5:50001 -> Destination: 198.51.100.20:80
Internal: 192.168.1.12:6000 <-> External: 203.0.113.5:50002 -> Destination: 198.51.100.30:443

PAT achieves maximum address conservation, supporting thousands of internal hosts with a single public IP. Most consumer routers and many enterprise deployments use PAT as the default NAT mode. The tradeoff involves port exhaustion risks when extremely high connection counts exceed the 65,535 available ports, and protocol compatibility issues for applications embedding IP addresses in payload data.

Bidirectional NAT combines source and destination translation. Outbound traffic undergoes source NAT for internet access, while port forwarding rules implement destination NAT for published services. This approach enables a mixed deployment where some internal hosts act as clients and others act as servers.

Carrier-Grade NAT (CGN) applies NAT at the ISP level, placing multiple customers behind shared public IP addresses. CGN introduces a second layer of translation, with customer premises equipment performing one NAT translation and the ISP performing another. This double NAT arrangement creates significant complications for logging, troubleshooting, and protocols requiring end-to-end connectivity.

Ruby Implementation

Ruby applications interact with NAT through socket programming, requiring awareness of address translation effects on network communication. Direct NAT configuration typically requires system-level access, but Ruby can detect NAT presence, work within NAT constraints, and implement NAT traversal techniques.

Detecting NAT presence involves comparing the local socket address with the address seen by external peers. The Socket library provides local address information, while external services can report the public-facing address:

require 'socket'
require 'net/http'

class NATDetector
  def self.local_address
    # Get local IP address
    Socket.ip_address_list.find { |addr| 
      addr.ipv4? && !addr.ipv4_loopback? && !addr.ipv4_multicast?
    }&.ip_address
  end
  
  def self.public_address
    # Query external service for public IP
    uri = URI('https://api.ipify.org')
    response = Net::HTTP.get(uri)
    response.strip
  end
  
  def self.behind_nat?
    local = local_address
    public = public_address
    
    return false unless local && public
    
    # Check if local address is private
    private_ranges = [
      IPAddr.new('10.0.0.0/8'),
      IPAddr.new('172.16.0.0/12'),
      IPAddr.new('192.168.0.0/16')
    ]
    
    private_ranges.any? { |range| range.include?(local) } && local != public
  end
end

puts "Local IP: #{NATDetector.local_address}"
puts "Public IP: #{NATDetector.public_address}"
puts "Behind NAT: #{NATDetector.behind_nat?}"

Port forwarding detection and management require system-level tools. Ruby can execute system commands or use native extensions to interact with NAT configuration. The following example demonstrates UPnP-based port mapping, a protocol that allows applications to request port forwards automatically:

require 'socket'
require 'net/http'

class UPnPMapper
  SSDP_ADDR = '239.255.255.250'
  SSDP_PORT = 1900
  
  def discover_gateway
    search_msg = [
      'M-SEARCH * HTTP/1.1',
      "HOST: #{SSDP_ADDR}:#{SSDP_PORT}",
      'MAN: "ssdp:discover"',
      'MX: 2',
      'ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1',
      '', ''
    ].join("\r\n")
    
    socket = UDPSocket.new
    socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, 1)
    socket.send(search_msg, 0, SSDP_ADDR, SSDP_PORT)
    
    # Wait for response
    Timeout.timeout(3) do
      response, addr = socket.recvfrom(1024)
      parse_gateway_location(response)
    end
  rescue Timeout::Error
    nil
  ensure
    socket.close if socket
  end
  
  def parse_gateway_location(response)
    location_line = response.lines.find { |line| line =~ /^LOCATION:/i }
    location_line ? location_line.split(':', 2)[1].strip : nil
  end
  
  def add_port_mapping(external_port, internal_port, internal_ip, protocol = 'TCP')
    # Simplified - real implementation requires full UPnP SOAP interaction
    {
      external_port: external_port,
      internal_port: internal_port,
      internal_ip: internal_ip,
      protocol: protocol,
      description: 'Ruby Application'
    }
  end
end

TCP hole punching enables peer-to-peer connections through NAT by coordinating simultaneous connection attempts from both peers. A rendezvous server facilitates the exchange of connection information:

require 'socket'

class NATPunchThrough
  def initialize(rendezvous_server, rendezvous_port)
    @server = rendezvous_server
    @port = rendezvous_port
  end
  
  def register_peer(local_port)
    # Connect to rendezvous server to register and get peer info
    socket = TCPSocket.new(@server, @port)
    
    # Send registration
    socket.puts("REGISTER:#{local_port}")
    
    # Receive peer information
    response = socket.gets.chomp
    peer_ip, peer_port = response.split(':')
    
    socket.close
    
    { ip: peer_ip, port: peer_port.to_i }
  end
  
  def punch_hole(local_port, peer_info)
    # Create socket and bind to specific local port
    server = TCPServer.new('0.0.0.0', local_port)
    
    # Attempt outbound connection to peer (creates NAT mapping)
    Thread.new do
      begin
        TCPSocket.new(peer_info[:ip], peer_info[:port])
      rescue
        # Expected to fail initially, but creates NAT hole
      end
    end
    
    # Wait for peer's connection attempt
    client = server.accept
    client
  ensure
    server.close if server
  end
end

Application-level gateways require embedding address information in higher-layer protocols. FTP active mode, for example, sends IP addresses in the command channel. Ruby applications implementing such protocols must handle NAT-aware address substitution:

class FTPNATAwareClient
  def initialize(host, port)
    @control = TCPSocket.new(host, port)
    @public_ip = get_public_ip
  end
  
  def passive_mode
    # PASV mode avoids NAT issues by having server listen
    @control.puts("PASV")
    response = @control.gets
    
    # Parse server's IP and port from response
    match = response.match(/\((\d+),(\d+),(\d+),(\d+),(\d+),(\d+)\)/)
    return nil unless match
    
    ip = match[1..4].join('.')
    port = (match[5].to_i * 256) + match[6].to_i
    
    { ip: ip, port: port }
  end
  
  def active_mode
    # PORT mode problematic with NAT - must use public IP
    data_server = TCPServer.new('0.0.0.0', 0)
    data_port = data_server.addr[1]
    
    # Convert IP and port to FTP format
    ip_parts = @public_ip.split('.')
    port_high = data_port / 256
    port_low = data_port % 256
    
    port_command = "PORT #{ip_parts.join(',')},#{port_high},#{port_low}"
    @control.puts(port_command)
    
    data_server
  end
  
  private
  
  def get_public_ip
    # Query external service or use configured value
    NATDetector.public_address
  end
end

Session Traversal Utilities for NAT (STUN) enable clients to discover their public-facing addresses and port mappings. Ruby implementations can query STUN servers to determine NAT behavior:

require 'socket'

class STUNClient
  STUN_MAGIC_COOKIE = 0x2112A442
  
  def initialize(stun_server, stun_port = 3478)
    @server = stun_server
    @port = stun_port
  end
  
  def get_mapped_address
    socket = UDPSocket.new
    
    # Build STUN binding request
    transaction_id = Random.bytes(12)
    request = build_binding_request(transaction_id)
    
    socket.send(request, 0, @server, @port)
    
    # Receive response
    response, addr = socket.recvfrom(1024)
    parse_mapped_address(response)
  ensure
    socket.close if socket
  end
  
  private
  
  def build_binding_request(transaction_id)
    message_type = 0x0001  # Binding Request
    message_length = 0
    
    header = [message_type, message_length, STUN_MAGIC_COOKIE].pack('nnN')
    header + transaction_id
  end
  
  def parse_mapped_address(response)
    # Simplified parser - real implementation needs full STUN protocol
    # Response contains XOR-MAPPED-ADDRESS attribute with public IP/port
    {
      ip: '203.0.113.5',
      port: 50000,
      message: 'Parsed from STUN response'
    }
  end
end

Tools & Ecosystem

iptables provides the primary NAT implementation on Linux systems. The nat table contains chains for different translation types: PREROUTING for destination NAT, POSTROUTING for source NAT, and OUTPUT for locally generated packets. Ruby applications can execute iptables commands through system calls:

class IPTablesNAT
  def self.add_source_nat(internal_ip, external_ip)
    rule = "iptables -t nat -A POSTROUTING -s #{internal_ip} -j SNAT --to-source #{external_ip}"
    system(rule)
  end
  
  def self.add_masquerade(interface)
    # PAT/masquerade rule for dynamic IP addresses
    rule = "iptables -t nat -A POSTROUTING -o #{interface} -j MASQUERADE"
    system(rule)
  end
  
  def self.add_port_forward(external_port, internal_ip, internal_port, protocol = 'tcp')
    rule = "iptables -t nat -A PREROUTING -p #{protocol} --dport #{external_port} " \
           "-j DNAT --to-destination #{internal_ip}:#{internal_port}"
    system(rule)
  end
  
  def self.list_nat_rules
    `iptables -t nat -L -n -v`
  end
end

nftables supersedes iptables as the modern Linux packet filtering framework. It provides a more consistent syntax and improved performance. Ruby applications interact with nftables through command execution or the nftables Ruby gem:

class NFTablesNAT
  def self.create_nat_table
    commands = [
      "nft add table ip nat",
      "nft add chain ip nat prerouting { type nat hook prerouting priority -100 \\; }",
      "nft add chain ip nat postrouting { type nat hook postrouting priority 100 \\; }"
    ]
    commands.each { |cmd| system(cmd) }
  end
  
  def self.add_masquerade(interface)
    system("nft add rule ip nat postrouting oifname #{interface} masquerade")
  end
  
  def self.add_dnat_rule(external_port, internal_ip, internal_port)
    rule = "nft add rule ip nat prerouting tcp dport #{external_port} " \
           "dnat to #{internal_ip}:#{internal_port}"
    system(rule)
  end
end

pf (Packet Filter) serves as the firewall and NAT implementation on BSD systems and macOS. Configuration uses the pf.conf file format. Ruby applications can generate and load pf configurations:

class PFConfig
  def initialize
    @rules = []
  end
  
  def add_nat_rule(interface, internal_network)
    @rules << "nat on #{interface} from #{internal_network} to any -> (#{interface})"
  end
  
  def add_rdr_rule(interface, external_port, internal_ip, internal_port, protocol = 'tcp')
    @rules << "rdr on #{interface} proto #{protocol} from any to any " \
              "port #{external_port} -> #{internal_ip} port #{internal_port}"
  end
  
  def write_config(path = '/etc/pf.conf')
    File.write(path, @rules.join("\n"))
  end
  
  def reload
    system("pfctl -f /etc/pf.conf")
  end
end

miniupnpc provides a C library with Ruby bindings for UPnP NAT traversal. Applications use UPnP to automatically configure port forwards on compatible routers:

# Using miniupnpc gem (if available)
require 'miniupnpc'

upnp = UPnP::UPnP.new
upnp.discover

if upnp.valid?
  external_ip = upnp.external_ip_address
  
  # Add port mapping
  upnp.add_port_mapping(
    external_port: 8080,
    internal_port: 8080,
    internal_client: '192.168.1.100',
    protocol: 'TCP',
    description: 'Ruby Web Server'
  )
  
  # List existing mappings
  mappings = upnp.list_port_mappings
end

Netfilter connection tracking (conntrack) exposes NAT state information. The conntrack command and /proc/net/nf_conntrack file provide visibility into active translations:

class ConnTrack
  def self.list_connections
    File.readlines('/proc/net/nf_conntrack').map do |line|
      parse_conntrack_entry(line)
    end
  end
  
  def self.parse_conntrack_entry(line)
    # Parse conntrack entry format
    parts = line.split
    {
      protocol: parts[0],
      state: parts[3],
      src: parts[4].split('=')[1],
      dst: parts[5].split('=')[1],
      sport: parts[6].split('=')[1],
      dport: parts[7].split('=')[1]
    }
  end
  
  def self.count_connections_by_host(host)
    list_connections.count { |conn| conn[:src] == host || conn[:dst] == host }
  end
end

STUN and TURN servers facilitate NAT traversal for real-time communications. Ruby applications implementing WebRTC or VoIP use these protocols. The stun and turn Ruby gems provide client implementations.

tcpdump and Wireshark enable NAT troubleshooting by capturing and analyzing packet headers before and after translation. Ruby can execute tcpdump and parse output:

class NATDebug
  def self.capture_nat_traffic(interface, duration = 10)
    output_file = "/tmp/nat_capture_#{Time.now.to_i}.pcap"
    cmd = "timeout #{duration} tcpdump -i #{interface} -w #{output_file} " \
          "'tcp or udp or icmp'"
    
    system(cmd)
    output_file
  end
  
  def self.analyze_capture(pcap_file)
    # Use tshark to parse pcap file
    output = `tshark -r #{pcap_file} -T fields -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport`
    
    output.lines.map do |line|
      src_ip, dst_ip, src_port, dst_port = line.split
      {
        src: "#{src_ip}:#{src_port}",
        dst: "#{dst_ip}:#{dst_port}"
      }
    end
  end
end

Integration & Interoperability

HTTP and HTTPS protocols operate transparently through NAT since translation occurs at the network layer below the application layer. Web applications require no special NAT handling for standard client-server communication. However, applications embedding IP addresses in HTTP headers or cookies must account for NAT:

require 'sinatra/base'

class NATAwareWebApp < Sinatra::Base
  def client_ip
    # Check X-Forwarded-For header for real client IP behind proxies/NAT
    forwarded = request.env['HTTP_X_FORWARDED_FOR']
    return forwarded.split(',').first.strip if forwarded
    
    # Fall back to direct connection IP
    request.ip
  end
  
  get '/api/info' do
    content_type :json
    {
      client_ip: client_ip,
      server_sees: request.ip,
      headers: request.env.select { |k, v| k.start_with?('HTTP_') }
    }.to_json
  end
end

WebSocket connections maintain long-lived TCP connections through NAT. NAT timeout values affect WebSocket reliability. Applications implement keepalive mechanisms to prevent NAT session expiration:

require 'faye/websocket'

class NATAwareWebSocket
  KEEPALIVE_INTERVAL = 30 # seconds
  
  def initialize(url)
    @ws = Faye::WebSocket::Client.new(url)
    @keepalive_thread = nil
    
    @ws.on :open do |event|
      start_keepalive
    end
    
    @ws.on :message do |event|
      handle_message(event.data)
    end
    
    @ws.on :close do |event|
      stop_keepalive
    end
  end
  
  def start_keepalive
    @keepalive_thread = Thread.new do
      loop do
        sleep KEEPALIVE_INTERVAL
        @ws.ping('keepalive') rescue break
      end
    end
  end
  
  def stop_keepalive
    @keepalive_thread&.kill
  end
  
  def handle_message(data)
    # Process received messages
  end
end

DNS operates through NAT without modification since DNS queries and responses contain no IP address information requiring translation. However, DNS rebinding attacks exploit NAT by resolving domain names to internal IP addresses.

SIP (Session Initiation Protocol) used in VoIP embeds IP addresses in the SIP message body. SIP requires ALG (Application Layer Gateway) support in NAT devices or client-side NAT traversal techniques. Ruby SIP applications implement ICE (Interactive Connectivity Establishment):

class SIPNATHandler
  def initialize(local_ip, public_ip)
    @local_ip = local_ip
    @public_ip = public_ip
  end
  
  def rewrite_sdp(sdp_body)
    # Replace internal IP with public IP in SDP
    sdp_body.gsub(/c=IN IP4 #{Regexp.escape(@local_ip)}/, 
                  "c=IN IP4 #{@public_ip}")
  end
  
  def add_ice_candidates(sdp_body)
    # Add ICE candidates for NAT traversal
    candidates = [
      "a=candidate:1 1 UDP 2130706431 #{@local_ip} 5060 typ host",
      "a=candidate:2 1 UDP 1694498815 #{@public_ip} 5060 typ srflx"
    ]
    
    sdp_body + "\n" + candidates.join("\n")
  end
end

FTP active mode fails through NAT since the client sends its IP address to the server in the PORT command. FTP passive mode (PASV) solves this by having the server listen instead. Ruby FTP libraries default to passive mode:

require 'net/ftp'

ftp = Net::FTP.new
ftp.passive = true  # Essential for NAT traversal
ftp.connect('ftp.example.com')
ftp.login('user', 'password')

IPsec VPN protocols face NAT compatibility issues. IPsec ESP (Encapsulating Security Payload) encrypts the entire IP payload, preventing NAT from translating port numbers. NAT-T (NAT Traversal) encapsulates IPsec packets in UDP to enable NAT compatibility:

class IPSecNATT
  NAT_T_PORT = 4500
  
  def initialize(gateway_ip, psk)
    @gateway_ip = gateway_ip
    @psk = psk
  end
  
  def establish_tunnel
    # Detect NAT using IKE initial exchange
    nat_detected = probe_nat
    
    if nat_detected
      # Enable NAT-T by encapsulating in UDP
      {
        protocol: 'UDP',
        port: NAT_T_PORT,
        encapsulation: 'NAT-T'
      }
    else
      # Use standard IPsec
      {
        protocol: 'ESP',
        port: nil,
        encapsulation: 'none'
      }
    end
  end
  
  def probe_nat
    # Send IKE packets to detect NAT presence
    # Simplified - real implementation uses IKE protocol
    true
  end
end

SCTP (Stream Control Transmission Protocol) includes built-in NAT traversal through INIT and INIT-ACK chunk validation. Ruby applications using SCTP benefit from protocol-level NAT support.

REST APIs commonly use HTTP, inheriting NAT compatibility. API rate limiting and access control based on IP addresses must account for shared public IPs behind NAT:

class NATAwareRateLimiter
  def initialize
    @requests = Hash.new { |h, k| h[k] = [] }
  end
  
  def check_rate_limit(client_ip, user_id)
    # Use user_id for authenticated requests to avoid
    # false positives from shared NAT IP addresses
    key = user_id || client_ip
    
    @requests[key].reject! { |time| Time.now - time > 3600 }
    
    if @requests[key].size >= 1000
      return false
    end
    
    @requests[key] << Time.now
    true
  end
end

gRPC and HTTP/2 maintain multiplexed streams over single TCP connections, working transparently through NAT. Connection reuse reduces NAT state table pressure compared to HTTP/1.1.

Common Pitfalls

NAT breaks the end-to-end principle of internet architecture. Protocols assuming direct connectivity fail when intermediary devices modify packet headers. Applications must actively detect and handle NAT presence rather than assuming transparent network communication.

Connection timeout configurations in NAT devices cause session failures. TCP connections with infrequent traffic expire from NAT tables, leading to connection resets. Applications implement application-layer keepalives:

class TCPKeepalive
  def self.enable(socket)
    # Enable TCP keepalive at OS level
    socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
    
    # Linux-specific: configure keepalive parameters
    if RUBY_PLATFORM.include?('linux')
      socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_KEEPIDLE, 60)
      socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_KEEPINTVL, 10)
      socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_KEEPCNT, 5)
    end
  end
  
  def self.application_keepalive(socket, interval = 30)
    Thread.new do
      loop do
        sleep interval
        begin
          socket.write("\0")  # Send null byte as keepalive
        rescue
          break
        end
      end
    end
  end
end

Port exhaustion occurs when PAT depletes available port numbers. A single public IP supports approximately 65,000 simultaneous connections per destination. High-traffic applications encounter connection failures when reaching this limit:

class PortExhaustionMonitor
  def self.check_port_usage
    # Count connections in ESTABLISHED state
    connections = `netstat -ant | grep ESTABLISHED | wc -l`.to_i
    
    # Check system limits
    max_files = `ulimit -n`.to_i
    
    {
      active_connections: connections,
      max_file_descriptors: max_files,
      port_usage_percent: (connections.to_f / 65535 * 100).round(2)
    }
  end
  
  def self.warn_if_exhausted(threshold = 80)
    stats = check_port_usage
    
    if stats[:port_usage_percent] > threshold
      puts "WARNING: Port usage at #{stats[:port_usage_percent]}%"
      true
    else
      false
    end
  end
end

Hairpin NAT failures prevent internal hosts from accessing other internal hosts through their public IP address. When an internal client connects to its NAT gateway's public IP, the router must recognize the destination as another internal host and implement hairpin routing:

Internal Host A (192.168.1.10) -> 203.0.113.5:80 -> Internal Host B (192.168.1.20:80)

Many consumer routers lack hairpin NAT support, causing timeouts. Applications should use split-horizon DNS, resolving internal hostnames to private addresses for local clients and public addresses for external clients.

ICMP path MTU discovery breaks when NAT devices drop ICMP packets. TCP connections hang during data transfer when packet sizes exceed the path MTU but ICMP "Fragmentation Needed" messages never arrive:

class MTUDiscovery
  def self.test_path_mtu(destination, port)
    socket = TCPSocket.new(destination, port)
    
    # Disable fragmentation (DF flag)
    # Platform-specific implementation required
    
    mtu_sizes = [1500, 1400, 1300, 1200, 1100, 1000]
    
    mtu_sizes.each do |size|
      begin
        # Send packet of specific size
        data = 'x' * (size - 40)  # Account for IP+TCP headers
        socket.write(data)
        return size
      rescue Errno::EMSGSIZE
        next
      end
    end
    
    576  # Minimum MTU
  ensure
    socket.close if socket
  end
end

ALG (Application Layer Gateway) interference modifies application protocols unpredictably. SIP ALG implementations often corrupt SIP messages, causing registration and call failures. Applications should detect and work around ALG modifications:

class ALGDetector
  def self.detect_sip_alg(sip_server)
    # Send SIP REGISTER with known Via header
    original_via = "192.168.1.10:5060"
    
    # Actual implementation would send SIP message and
    # compare received Via header at server
    
    {
      alg_detected: true,
      original: original_via,
      modified: "203.0.113.5:5060",
      recommendation: "Disable SIP ALG on router"
    }
  end
end

Symmetric NAT assigns different port mappings for each unique destination. This behavior breaks many NAT traversal techniques that assume consistent external port mapping. STUN cannot determine external ports for symmetric NAT:

class NATTypeDetector
  def initialize(stun_servers)
    @stun_servers = stun_servers
  end
  
  def detect_nat_type
    # Query first STUN server
    result1 = query_stun(@stun_servers[0])
    
    # Query second STUN server
    result2 = query_stun(@stun_servers[1])
    
    if result1.nil? || result2.nil?
      return :blocked
    end
    
    if result1[:port] == result2[:port]
      :cone_nat
    else
      :symmetric_nat  # Most restrictive
    end
  end
  
  def query_stun(server)
    # Implementation would use STUN protocol
    { ip: '203.0.113.5', port: 50000 }
  end
end

Fragmentation issues arise when NAT modifies IP headers, potentially invalidating checksums in fragments. UDP applications sending large datagrams experience mysterious packet loss through NAT. Applications should implement path MTU discovery or limit payload sizes.

Multiple NAT layers (double NAT) occur in carrier-grade NAT deployments or cascaded router configurations. Each NAT layer adds state and potential failure points. Diagnostic tools must trace through multiple translation layers to identify issues.

Reference

NAT Operation Comparison

NAT Type Address Mapping Port Translation Connections per Public IP Use Case
Static NAT One-to-one permanent No 1 Servers requiring consistent external addresses
Dynamic NAT One-to-one from pool No 1 per allocation Moderate address conservation
PAT/NAPT Many-to-one Yes ~65,000 Maximum address conservation
Bidirectional Both source and dest Varies Depends on config Mixed client-server environments

Private IP Address Ranges

Range CIDR Number of Addresses Common Usage
10.0.0.0 - 10.255.255.255 10.0.0.0/8 16,777,216 Large enterprise networks
172.16.0.0 - 172.31.255.255 172.16.0.0/12 1,048,576 Medium enterprise networks
192.168.0.0 - 192.168.255.255 192.168.0.0/16 65,536 Home and small office networks

Protocol NAT Compatibility

Protocol NAT Compatible Requires Special Handling Notes
HTTP/HTTPS Yes No Operates at application layer
DNS Yes No Query-response pattern works through NAT
FTP Partial Yes Passive mode required, active mode fails
SIP No Yes Requires ALG or NAT traversal techniques
IPsec No Yes Needs NAT-T encapsulation
PPTP No Yes GRE protocol lacks port numbers
SSH Yes No Standard TCP connection
SMTP/IMAP Yes No Standard TCP connections
RTP/RTCP Partial Yes Requires coordination with signaling protocol
BitTorrent Partial Yes Inbound connections require port forwarding

NAT Table Entry Fields

Field Description Example Value
Internal IP Source address on private network 192.168.1.10
Internal Port Source port on private network 5000
External IP Translated public address 203.0.113.5
External Port Translated public port 50000
Destination IP Remote endpoint address 198.51.100.20
Destination Port Remote endpoint port 80
Protocol Transport protocol TCP, UDP, ICMP
State Connection state ESTABLISHED, SYN_SENT
Timeout Seconds until entry expiration 300

Common NAT Timeout Values

Connection Type Typical Timeout Implication
TCP Established 2 hours - 5 days Long-lived connections persist
TCP Transitory 5 minutes - 1 hour Short-lived connections cleaned quickly
UDP 30 seconds - 5 minutes Requires frequent keepalives
ICMP 30 seconds - 1 minute Brief window for ping replies

Ruby Socket Options for NAT

Socket Option Purpose Usage
SO_KEEPALIVE Enable TCP keepalive socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
TCP_KEEPIDLE Time before first keepalive socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_KEEPIDLE, 60)
TCP_KEEPINTVL Interval between keepalives socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_KEEPINTVL, 10)
SO_REUSEADDR Allow address reuse socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
IP_MTU_DISCOVER Control MTU discovery Platform-specific implementation

NAT Traversal Techniques

Technique Description Complexity Success Rate
UPnP/NAT-PMP Automatic port mapping Low 60-70% (depends on router support)
STUN Discover public address/port Low 80-85% (fails with symmetric NAT)
TURN Relay through server Medium 100% (always works, adds latency)
ICE Combines STUN and TURN High 100% (fallback to TURN)
TCP Hole Punching Simultaneous connection attempts Medium 60-70% (timing dependent)
ALG Router protocol awareness None (automatic) Variable (often causes issues)

Linux iptables NAT Commands

Command Purpose
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE Enable PAT on interface eth0
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to 192.168.1.10:8080 Forward port 80 to internal host
iptables -t nat -L -n -v List NAT rules with statistics
iptables -t nat -F Flush all NAT rules
iptables -t nat -D POSTROUTING 1 Delete rule number 1 from POSTROUTING chain

NAT Detection Methods

Method What It Reveals Implementation
Compare local vs public IP Whether behind NAT Query external service and compare to Socket.ip_address_list
STUN protocol External IP and port mapping Send STUN Binding Request, parse XOR-MAPPED-ADDRESS
Trace headers Proxies and NAT gateways Check X-Forwarded-For and Via headers
MTU probing Path characteristics Send progressively larger packets with DF flag
Connection behavior NAT type classification Multiple STUN queries to different servers