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 |