CrackedRuby CrackedRuby

Overview

The TCP/IP protocol stack defines a layered model for network communication that enables data transmission across interconnected networks. This four-layer architecture emerged from the ARPANET project in the 1970s and became the foundation for the modern Internet. Unlike the seven-layer OSI model, TCP/IP uses a practical, implementation-focused approach with four distinct layers: Link, Internet, Transport, and Application.

Each layer in the TCP/IP stack handles specific networking responsibilities. The Link layer manages physical network access, the Internet layer routes packets across networks, the Transport layer ensures reliable data delivery, and the Application layer provides services to end-user programs. Data passes down through these layers when sending and up through them when receiving, with each layer adding or removing protocol-specific headers.

The protocol stack operates through encapsulation. When an application sends data, each layer wraps the previous layer's packet with its own header information. A web request starts as HTTP data at the Application layer, gains TCP headers at the Transport layer, receives IP headers at the Internet layer, and gets frame headers at the Link layer before transmission. The receiving system reverses this process, stripping headers at each layer until the application data reaches its destination.

# Basic TCP/IP communication in Ruby
require 'socket'

# Create a TCP server on port 8080
server = TCPServer.new(8080)
puts "Server listening on port 8080"

# Accept incoming connection
client = server.accept
puts "Client connected from #{client.peeraddr[3]}"

# Receive data (Application layer data over TCP/IP)
request = client.gets
puts "Received: #{request}"

# Send response
client.puts "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello"
client.close

Ruby's Socket library provides direct access to TCP/IP networking primitives. Developers work primarily at the Transport and Application layers, while the operating system handles Internet and Link layer operations. This abstraction allows focus on application logic without managing low-level packet routing or frame construction.

Key Principles

The TCP/IP stack organizes network communication into four functional layers, each building upon the services of the layer below it. This layered architecture creates clear separation of concerns and allows protocols at each layer to evolve independently.

Link Layer handles the physical transmission of data over a specific network medium. This layer includes protocols like Ethernet, Wi-Fi, and PPP that manage MAC addresses, frame formatting, and direct hardware communication. The Link layer converts packets into electrical signals or radio waves and handles media access control for shared networks. Error detection occurs here through checksums, though error correction typically happens at higher layers.

Internet Layer routes packets across multiple networks using IP addresses. The Internet Protocol (IP) operates at this layer, providing logical addressing and packet forwarding. IP treats each packet independently, making no guarantees about delivery order or reliability. This layer fragments large packets when necessary, adds Time-To-Live values to prevent infinite routing loops, and determines the best path through intermediate routers. ICMP operates at this layer for diagnostic purposes, while ARP resolves IP addresses to MAC addresses.

Transport Layer establishes end-to-end communication between applications. TCP provides reliable, ordered, stream-oriented delivery with connection establishment, flow control, and congestion management. UDP offers unreliable, message-oriented delivery with minimal overhead. TCP maintains connection state, acknowledges received segments, and retransmits lost data. UDP simply sends datagrams without acknowledgment or ordering guarantees. Port numbers at this layer multiplex multiple applications over a single IP address.

Application Layer implements protocols that applications use directly. HTTP, FTP, SMTP, DNS, and SSH operate at this layer, each defining its own message formats and exchange patterns. Application protocols build on Transport layer services, choosing between TCP's reliability or UDP's speed based on application requirements. This layer handles data representation, encryption, compression, and application-specific error handling.

# Demonstrating layer interaction
require 'socket'
require 'resolv'

# Application Layer: DNS resolution
hostname = 'example.com'
ip_address = Resolv.getaddress(hostname)
puts "Resolved #{hostname} to #{ip_address}"  # Internet layer address

# Transport Layer: TCP connection with specific port
socket = TCPSocket.new(ip_address, 80)  # Port 80 = HTTP

# Application Layer: HTTP request
socket.puts "GET / HTTP/1.1\r\nHost: #{hostname}\r\n\r\n"

# Receive response
response = socket.read
puts "Received #{response.bytesize} bytes"
socket.close

The protocol stack implements the end-to-end principle: intelligence resides at the endpoints rather than in the network. Routers forward packets based on destination addresses without understanding application semantics. This design creates a simple, scalable network core while allowing complex functionality at the edges.

Flow control mechanisms prevent fast senders from overwhelming slow receivers. TCP uses a sliding window protocol where the receiver advertises how much buffer space remains available. The sender limits transmission to this window size, expanding or contracting based on receiver capacity and network conditions.

Congestion control prevents network overload by detecting packet loss as a congestion signal. TCP reduces transmission rate when timeouts occur and gradually increases speed when transmissions succeed. Algorithms like TCP Reno and CUBIC implement sophisticated mechanisms that balance throughput with network health.

The stack handles packet fragmentation when data exceeds the Maximum Transmission Unit (MTU) of the underlying network. IP fragments large packets at the Internet layer and reassembles them at the destination. Modern systems prefer path MTU discovery to avoid fragmentation, sending packets small enough for the entire path.

Ruby Implementation

Ruby provides socket programming through the Socket library, which exposes TCP/IP functionality through object-oriented interfaces. The library includes classes for different protocol levels and communication patterns, abstracting operating system socket APIs.

TCP Sockets offer stream-oriented, connection-based communication. TCPServer creates listening sockets that accept incoming connections, while TCPSocket initiates outbound connections. These classes build on the lower-level Socket class with convenient methods for common TCP operations.

require 'socket'

# Server using TCPServer
server = TCPServer.new('localhost', 9000)

loop do
  client = server.accept  # Blocks until connection arrives
  
  Thread.new(client) do |conn|
    # Each client handled in separate thread
    begin
      conn.puts "Connected to server at #{Time.now}"
      
      while line = conn.gets
        break if line.strip == 'quit'
        conn.puts "Echo: #{line}"
      end
    ensure
      conn.close
    end
  end
end

The Socket class provides lower-level access for specialized requirements. Creating raw sockets requires understanding socket domains (AF_INET for IPv4, AF_INET6 for IPv6), socket types (SOCK_STREAM for TCP, SOCK_DGRAM for UDP), and protocol numbers.

require 'socket'

# Low-level TCP socket creation
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(80, 'example.com')

begin
  socket.connect(sockaddr)
  
  request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
  socket.write(request)
  
  response = socket.read(1024)
  puts response
ensure
  socket.close
end

UDP Sockets provide message-oriented, connectionless communication. UDPSocket sends and receives individual datagrams without establishing connections. Each send operation specifies the destination address, and receive operations return both data and sender information.

require 'socket'

# UDP server
server = UDPSocket.new
server.bind('localhost', 9001)

Thread.new do
  loop do
    data, addr = server.recvfrom(1024)
    puts "Received from #{addr[3]}:#{addr[1]}: #{data}"
    server.send("ACK: #{data}", 0, addr[3], addr[1])
  end
end

# UDP client
client = UDPSocket.new
client.send("Hello UDP", 0, 'localhost', 9001)
response, _ = client.recvfrom(1024)
puts "Response: #{response}"

Ruby's socket library includes non-blocking I/O through the IO.select method and socket options. This allows handling multiple connections without threading overhead, critical for high-concurrency servers.

require 'socket'

server = TCPServer.new('localhost', 9002)
clients = []

loop do
  # Wait for activity on server or any client socket
  ready = IO.select([server] + clients, nil, nil, 1)
  
  next unless ready
  
  ready[0].each do |socket|
    if socket == server
      # New connection
      clients << server.accept
      puts "Client connected. Total: #{clients.size}"
    else
      # Data from existing client
      begin
        data = socket.read_nonblock(1024)
        socket.write("Received: #{data}")
      rescue EOFError, Errno::ECONNRESET
        clients.delete(socket)
        socket.close
        puts "Client disconnected. Total: #{clients.size}"
      rescue IO::WaitReadable
        # No data available yet
      end
    end
  end
end

Socket Options configure TCP/IP behavior. The setsockopt method adjusts parameters like SO_REUSEADDR for address reuse, TCP_NODELAY to disable Nagle's algorithm, and SO_KEEPALIVE for connection monitoring.

require 'socket'

server = TCPServer.new('localhost', 9003)

# Enable address reuse (avoids "Address already in use" after restart)
server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)

# Set TCP keep-alive
server.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)

client = server.accept

# Disable Nagle's algorithm for low-latency communication
client.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

# Set receive buffer size
client.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF, 65536)

The Resolv library handles DNS resolution, translating hostnames to IP addresses. This operates at the Application layer but provides addresses needed by Transport and Internet layer operations.

require 'resolv'

# Synchronous resolution
ip = Resolv.getaddress('github.com')
puts "GitHub IP: #{ip}"

# Multiple addresses
Resolv::DNS.new.each_address('google.com') do |addr|
  puts "Google address: #{addr}"
end

# Reverse DNS lookup
hostname = Resolv.getname('8.8.8.8')
puts "8.8.8.8 resolves to: #{hostname}"

Practical Examples

HTTP Client Implementation

Building an HTTP client demonstrates Application layer protocol implementation over TCP. This example handles request formatting, connection management, and response parsing.

require 'socket'
require 'uri'

class SimpleHTTPClient
  def self.get(url)
    uri = URI.parse(url)
    
    # Establish TCP connection (Transport layer)
    socket = TCPSocket.new(uri.host, uri.port || 80)
    
    # Format HTTP request (Application layer)
    request = [
      "GET #{uri.path.empty? ? '/' : uri.path} HTTP/1.1",
      "Host: #{uri.host}",
      "Connection: close",
      "User-Agent: SimpleHTTPClient/1.0",
      "",
      ""
    ].join("\r\n")
    
    # Send request
    socket.write(request)
    
    # Read response
    response = socket.read
    socket.close
    
    # Parse response
    headers, body = response.split("\r\n\r\n", 2)
    status_line = headers.lines.first
    
    {
      status: status_line.split(' ', 3)[1].to_i,
      headers: parse_headers(headers),
      body: body
    }
  end
  
  def self.parse_headers(header_text)
    headers = {}
    header_text.lines.drop(1).each do |line|
      key, value = line.strip.split(': ', 2)
      headers[key] = value if key
    end
    headers
  end
end

# Usage
result = SimpleHTTPClient.get('http://example.com')
puts "Status: #{result[:status]}"
puts "Content-Type: #{result[:headers]['Content-Type']}"
puts "Body length: #{result[:body].bytesize} bytes"

Port Scanner

A port scanner demonstrates Transport layer interaction, testing TCP connection establishment across multiple ports to identify listening services.

require 'socket'
require 'timeout'

class PortScanner
  def self.scan(host, port_range, timeout_seconds = 1)
    open_ports = []
    
    port_range.each do |port|
      begin
        Timeout::timeout(timeout_seconds) do
          socket = TCPSocket.new(host, port)
          open_ports << port
          socket.close
        end
      rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
        # Port closed or host unreachable
      rescue Timeout::Error
        # Connection timeout
      rescue => e
        puts "Error scanning port #{port}: #{e.message}"
      end
    end
    
    open_ports
  end
end

# Scan common ports
host = 'localhost'
common_ports = [21, 22, 23, 25, 80, 110, 143, 443, 3306, 5432, 8080]

puts "Scanning #{host}..."
open_ports = PortScanner.scan(host, common_ports, 0.5)

if open_ports.empty?
  puts "No open ports found"
else
  puts "Open ports: #{open_ports.join(', ')}"
end

Multi-Client Chat Server

A chat server demonstrates concurrent connection handling, message broadcasting, and proper socket cleanup with thread management.

require 'socket'

class ChatServer
  def initialize(port)
    @server = TCPServer.new(port)
    @clients = []
    @mutex = Mutex.new
  end
  
  def start
    puts "Chat server started on port #{@server.addr[1]}"
    
    loop do
      client = @server.accept
      
      Thread.new(client) do |conn|
        handle_client(conn)
      end
    end
  end
  
  private
  
  def handle_client(client)
    # Get nickname
    client.puts "Enter nickname:"
    nickname = client.gets.strip
    
    # Add to clients list
    @mutex.synchronize do
      @clients << { socket: client, nickname: nickname }
    end
    
    broadcast("#{nickname} joined the chat", client)
    
    # Handle messages
    loop do
      message = client.gets
      break unless message
      
      broadcast("#{nickname}: #{message.strip}", client)
    end
  rescue => e
    puts "Client error: #{e.message}"
  ensure
    # Remove client and notify others
    @mutex.synchronize do
      client_data = @clients.find { |c| c[:socket] == client }
      @clients.delete(client_data)
      
      if client_data
        broadcast("#{client_data[:nickname]} left the chat", nil)
      end
    end
    
    client.close
  end
  
  def broadcast(message, sender)
    @mutex.synchronize do
      @clients.each do |client_data|
        next if client_data[:socket] == sender
        
        begin
          client_data[:socket].puts(message)
        rescue
          # Client disconnected, will be cleaned up
        end
      end
    end
  end
end

# Start server
server = ChatServer.new(9004)
server.start

UDP Network Time Client

This example demonstrates UDP datagram communication, showing connectionless message exchange with the Network Time Protocol.

require 'socket'

class SimpleNTPClient
  NTP_SERVER = 'pool.ntp.org'
  NTP_PORT = 123
  NTP_EPOCH_OFFSET = 2208988800  # Seconds between 1900 and 1970
  
  def self.get_time
    socket = UDPSocket.new
    
    # NTP request packet (48 bytes)
    request = [0x1B] + [0] * 47
    packet = request.pack('C48')
    
    socket.send(packet, 0, NTP_SERVER, NTP_PORT)
    
    # Wait for response with timeout
    response, _ = socket.recvfrom(48)
    socket.close
    
    # Parse transmit timestamp (bytes 40-43 for seconds)
    seconds = response[40..43].unpack1('N')
    
    # Convert from NTP epoch (1900) to Unix epoch (1970)
    unix_timestamp = seconds - NTP_EPOCH_OFFSET
    
    Time.at(unix_timestamp)
  end
end

# Get network time
network_time = SimpleNTPClient.get_time
local_time = Time.now

puts "Network time: #{network_time}"
puts "Local time:   #{local_time}"
puts "Difference:   #{(network_time - local_time).round(2)} seconds"

Security Implications

TCP/IP protocols contain no built-in encryption or authentication. Data travels across networks in plaintext, vulnerable to interception, modification, and spoofing attacks. Security must come from higher-layer protocols or application-level implementations.

Man-in-the-Middle Attacks exploit the lack of endpoint verification. An attacker intercepts communication between two parties, reading or altering messages without detection. TCP's three-way handshake authenticates nothing beyond IP addresses, which attackers can spoof. Applications must implement their own authentication mechanisms, typically through TLS/SSL at the Transport layer or application-specific protocols.

SYN Flooding attacks exploit TCP's connection establishment process. Attackers send many SYN packets with spoofed source addresses, causing servers to allocate resources for half-open connections. The server waits for ACK packets that never arrive, exhausting connection tables and preventing legitimate connections. Defenses include SYN cookies, connection rate limiting, and firewall rules.

require 'socket'

# Vulnerable server without protection
def vulnerable_server(port)
  server = TCPServer.new(port)
  
  loop do
    # accept blocks, but each SYN allocates resources
    # Attacker sends many SYNs without completing handshake
    client = server.accept
    handle_client(client)
  end
end

# Hardened server with connection limits
def hardened_server(port, max_clients = 100)
  server = TCPServer.new(port)
  clients = []
  mutex = Mutex.new
  
  loop do
    # Check connection limit
    if clients.size >= max_clients
      sleep 0.1
      next
    end
    
    # Use non-blocking accept with timeout
    ready = IO.select([server], nil, nil, 1)
    next unless ready
    
    begin
      client = server.accept_nonblock
      
      mutex.synchronize do
        clients << client
      end
      
      Thread.new(client) do |conn|
        begin
          handle_client(conn)
        ensure
          mutex.synchronize do
            clients.delete(conn)
          end
          conn.close
        end
      end
    rescue IO::WaitReadable
      # No connection ready
    end
  end
end

IP Spoofing allows attackers to forge source addresses in packets. UDP particularly suffers from this since it lacks connection state. An attacker sends packets claiming to originate from a trusted source, bypassing IP-based access controls. Applications must not rely solely on source IP for authentication.

Port Scanning enables reconnaissance by probing which ports accept connections. While not an attack itself, port scanning identifies potential targets and running services. Defensive measures include closing unnecessary ports, using firewalls to restrict access, and implementing rate limiting to detect scanning attempts.

DNS Cache Poisoning corrupts DNS resolution by injecting false records into DNS caches. Attackers redirect traffic to malicious servers by making DNS resolvers believe false IP mappings. DNSSEC provides cryptographic verification of DNS responses, though adoption remains incomplete.

require 'socket'
require 'openssl'

# Insecure TCP communication
def insecure_connection(host, port)
  socket = TCPSocket.new(host, port)
  socket.puts "SECRET_DATA"  # Transmitted in plaintext
  response = socket.gets
  socket.close
  response
end

# Secure TCP communication with TLS
def secure_connection(host, port)
  tcp_socket = TCPSocket.new(host, port)
  
  ssl_context = OpenSSL::SSL::SSLContext.new
  ssl_context.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
  
  ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
  ssl_socket.connect
  
  # Verify certificate
  ssl_socket.post_connection_check(host)
  
  ssl_socket.puts "SECRET_DATA"  # Encrypted over TLS
  response = ssl_socket.gets
  
  ssl_socket.close
  tcp_socket.close
  
  response
end

TCP Session Hijacking exploits predictable sequence numbers to inject malicious packets into established connections. Modern TCP implementations use random initial sequence numbers to prevent this, but older systems remain vulnerable. Applications should implement their own session tokens rather than relying on TCP connection state for security.

Firewall Rules control which packets reach application sockets. Network-level firewalls filter based on IP addresses, ports, and protocol types. Application-level firewalls inspect packet contents for malicious patterns. Ruby applications cannot directly control system firewalls but should document required ports and protocols.

DDoS Mitigation requires multiple defensive layers. Application code should handle resource limits, implement request throttling, and fail gracefully under load. Rate limiting per source IP prevents single attackers from consuming all resources. Connection pooling and efficient socket handling reduce resource consumption per connection.

Tools & Ecosystem

Ruby's networking ecosystem includes libraries and frameworks that abstract TCP/IP complexities while providing protocol-specific functionality.

Net::HTTP provides HTTP client functionality built on TCP sockets. This standard library class handles connection pooling, SSL/TLS, redirects, and various HTTP methods. Most applications use this instead of raw sockets for HTTP communication.

require 'net/http'
require 'uri'

uri = URI('https://api.github.com/users/octocat')
response = Net::HTTP.get_response(uri)

puts "Status: #{response.code}"
puts "Content-Type: #{response['content-type']}"
puts "Body: #{response.body[0..100]}"

# POST with custom headers
uri = URI('https://httpbin.org/post')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri.path)
request['Content-Type'] = 'application/json'
request.body = '{"key": "value"}'

response = http.request(request)
puts response.body

EventMachine provides event-driven I/O for high-concurrency applications. This gem implements a reactor pattern that handles multiple connections efficiently without threading overhead. EventMachine manages the event loop and callbacks for socket operations.

Async offers fiber-based concurrency for network operations. This gem provides non-blocking I/O with synchronous-looking code through Ruby's fiber scheduler. Async simplifies concurrent socket programming compared to threads or callbacks.

Celluloid combines actor-based concurrency with socket handling. Each actor runs in its own thread, communicating through message passing. Celluloid::IO provides non-blocking socket operations integrated with the actor model.

Puma and Unicorn are production web servers implementing efficient TCP connection handling. Puma uses a thread pool for concurrent request processing, while Unicorn uses forked processes. Both handle HTTP over TCP with optimizations for web traffic patterns.

WEBrick ships with Ruby as a basic HTTP server implementation. While slower than production servers, WEBrick demonstrates pure-Ruby TCP/IP server implementation and serves well for development and testing.

require 'webrick'

server = WEBrick::HTTPServer.new(Port: 8000)

server.mount_proc '/' do |req, res|
  res.body = "Request from #{req.peeraddr[3]}\n"
  res.body << "Path: #{req.path}\n"
  res.body << "Query: #{req.query}\n"
end

trap('INT') { server.shutdown }
server.start

tcpdump and Wireshark capture and analyze TCP/IP packets at the Link and Internet layers. While not Ruby tools, these network analyzers help debug protocol issues and understand packet flow. Ruby applications generate traffic that these tools examine.

netstat and ss display active network connections and listening ports. These command-line tools show which ports Ruby applications bind to and track connection states. Monitoring these tools helps verify server startup and diagnose connection issues.

curl and wget test HTTP endpoints from the command line. These tools verify that Ruby servers correctly handle requests and responses. Both support various HTTP methods, headers, and authentication schemes.

nmap performs network discovery and port scanning. This tool identifies listening services and open ports on target systems. Understanding nmap output helps configure firewalls and verify that applications listen on intended ports.

Common Pitfalls

Forgetting to Close Sockets leads to resource leaks. Each open socket consumes file descriptors, and operating systems impose limits on descriptor counts. Long-running applications that fail to close sockets eventually exhaust available descriptors and cannot accept new connections.

# Problematic: Socket might not close on error
def connect_no_ensure(host, port)
  socket = TCPSocket.new(host, port)
  socket.puts "DATA"
  response = socket.gets
  socket.close  # Skipped if exception occurs
  response
end

# Correct: Always close socket
def connect_with_ensure(host, port)
  socket = TCPSocket.new(host, port)
  begin
    socket.puts "DATA"
    response = socket.gets
    response
  ensure
    socket.close if socket
  end
end

Blocking on Single Connections prevents servers from handling multiple clients. A server that calls gets or read without timeouts blocks indefinitely if clients never send data. This stalls the entire server or requires threading for each connection.

Ignoring Partial Reads causes data corruption. TCP provides a byte stream, not message boundaries. Calling read might return fewer bytes than requested if the network delivers data in chunks. Applications must loop until receiving the expected amount or implement length prefixes.

# Incorrect: Assumes single read gets all data
def read_message_incorrect(socket)
  socket.read(1024)  # Might return less than full message
end

# Correct: Read until delimiter or length
def read_message_correct(socket)
  # Read until newline delimiter
  socket.gets
end

def read_exact_bytes(socket, length)
  data = String.new(encoding: 'ASCII-8BIT')
  
  while data.bytesize < length
    chunk = socket.read(length - data.bytesize)
    break unless chunk
    data << chunk
  end
  
  data
end

Nagle's Algorithm Delays cause latency in interactive applications. TCP delays small packets to combine them for efficiency, but this hurts request-response protocols. Disabling Nagle's algorithm with TCP_NODELAY eliminates delays for time-sensitive communication.

Not Handling SIGPIPE crashes applications when writing to closed sockets. On Unix systems, writing to a closed TCP socket generates SIGPIPE, which terminates the process by default. Applications must trap this signal or handle socket write errors.

# Handle SIGPIPE to prevent crashes
trap('PIPE') { puts 'Broken pipe ignored' }

# Or handle EPIPE errors
begin
  socket.write(data)
rescue Errno::EPIPE
  puts 'Client disconnected'
end

Mixing Binary and Text Mode corrupts data. Ruby defaults to text mode, which may translate line endings or encoding. Binary protocols require explicit binary mode to prevent data corruption.

socket = TCPSocket.new('example.com', 80)
socket.binmode  # Disable text mode conversions

# Now safe to send binary data
binary_data = [0x89, 0x50, 0x4E, 0x47].pack('C*')
socket.write(binary_data)

Assuming Immediate Delivery breaks timing-sensitive code. TCP buffers data and may delay transmission for efficiency. Applications cannot assume write sends data immediately or that recipients receive data at specific times.

Hardcoding IPv4 excludes IPv6 networks. Modern systems require supporting both address families. Using Socket::AF_UNSPEC in getaddrinfo allows both protocols, or applications must explicitly handle both.

require 'socket'

# Bad: IPv4 only
def connect_ipv4_only(hostname, port)
  TCPSocket.new(hostname, port)  # Fails if only IPv6 available
end

# Good: IPv4 and IPv6
def connect_dual_stack(hostname, port)
  addrs = Socket.getaddrinfo(hostname, port, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
  
  addrs.each do |addr|
    begin
      socket = Socket.new(addr[4], addr[5], 0)
      sockaddr = Socket.sockaddr_in(port, addr[3])
      socket.connect(sockaddr)
      return socket
    rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
      socket.close if socket
      next
    end
  end
  
  raise "Could not connect to #{hostname}:#{port}"
end

Neglecting Timeouts hangs applications indefinitely. DNS lookups, connection attempts, and read operations all block without timeouts. Setting appropriate timeouts prevents resource exhaustion from stalled operations.

Trusting Source IPs for authentication fails with spoofing. UDP packets particularly suffer from forged source addresses. Applications must implement cryptographic authentication rather than relying on network-layer addressing.

Reference

Protocol Stack Layers

Layer Protocols Purpose Ruby Access
Application HTTP, FTP, SMTP, DNS End-user services Net::HTTP, Net::FTP, Resolv
Transport TCP, UDP End-to-end delivery TCPSocket, UDPSocket
Internet IP, ICMP, ARP Routing across networks Operating system handles
Link Ethernet, WiFi, PPP Physical transmission Operating system handles

Socket Classes

Class Protocol Connection Use Case
TCPSocket TCP Connection-oriented Client connections
TCPServer TCP Listening server Accept incoming connections
UDPSocket UDP Connectionless Datagram communication
Socket Various Low-level access Custom protocol implementation
BasicSocket Abstract Base class Socket hierarchy root

Common Socket Options

Option Level Purpose Values
SO_REUSEADDR SOL_SOCKET Reuse address after close 0 or 1
SO_KEEPALIVE SOL_SOCKET Send keepalive probes 0 or 1
TCP_NODELAY IPPROTO_TCP Disable Nagle algorithm 0 or 1
SO_RCVBUF SOL_SOCKET Receive buffer size Bytes
SO_SNDBUF SOL_SOCKET Send buffer size Bytes
SO_LINGER SOL_SOCKET Close behavior Struct with timeout

TCP Connection States

State Description Transitions
CLOSED No connection Initial state or fully closed
LISTEN Awaiting connection Server ready for SYN
SYN_SENT Connection requested Client sent SYN, waiting for SYN-ACK
SYN_RECEIVED Connection request received Server received SYN, sent SYN-ACK
ESTABLISHED Connection active Both sides ready for data
FIN_WAIT_1 Connection closing Sent FIN, waiting for ACK
FIN_WAIT_2 Waiting for peer close Received ACK, waiting for peer FIN
CLOSE_WAIT Peer closed connection Received FIN, sending remaining data
CLOSING Both sides closing Both sent FIN simultaneously
LAST_ACK Final acknowledgment Sent FIN, waiting for ACK
TIME_WAIT Connection closed Waiting to ensure ACK received

Socket Methods

Method Class Purpose Returns
new TCPSocket Create client socket Socket instance
new TCPServer Create server socket Server instance
accept TCPServer Accept connection Client socket
connect Socket Establish connection 0 on success
bind Socket Bind to address 0 on success
listen Socket Mark as server 0 on success
send Socket Send data Bytes sent
recv Socket Receive data Data string
close Socket Close connection nil
setsockopt Socket Set option 0 on success
getsockopt Socket Get option Option value

Address Families

Constant Value Protocol Description
AF_INET 2 IPv4 Internet Protocol version 4
AF_INET6 10 IPv6 Internet Protocol version 6
AF_UNIX 1 Local Unix domain sockets
AF_UNSPEC 0 Any Protocol-independent

Socket Types

Constant Protocol Characteristics
SOCK_STREAM TCP Reliable, ordered, connection-based
SOCK_DGRAM UDP Unreliable, message-oriented, connectionless
SOCK_RAW IP Direct access to IP layer

Error Handling

Exception Cause Recovery
Errno::ECONNREFUSED Port not listening Check server running, verify port
Errno::ETIMEDOUT Connection timeout Retry with backoff, check network
Errno::EHOSTUNREACH No route to host Verify address, check routing
EOFError Peer closed connection Handle graceful shutdown
Errno::EPIPE Write to closed socket Trap SIGPIPE, handle error
SocketError DNS or address error Validate hostname, check DNS