CrackedRuby CrackedRuby

Overview

Port numbers are 16-bit unsigned integers (0-65535) that identify specific processes or services on a networked computer. Combined with an IP address, a port number forms a socket address that uniquely identifies a network endpoint. When data arrives at a computer, the operating system uses the destination port number to route packets to the correct application or service.

The port number system solves a fundamental networking problem: a single computer runs multiple network services simultaneously, each requiring its own stream of network traffic. Without port numbers, the operating system cannot distinguish between data intended for a web server, an SSH session, or an email client. Port numbers create logical channels within a single network interface, multiplexing many communication streams over one physical connection.

Port numbers fall into three ranges defined by IANA (Internet Assigned Numbers Authority):

Well-Known Ports (0-1023): Reserved for system services and common protocols. Opening these ports requires administrative privileges on Unix-like systems. Examples include HTTP (80), HTTPS (443), SSH (22), and FTP (21).

Registered Ports (1024-49151): Available for registration with IANA for specific services. Applications can use these ports without special permissions. Database servers commonly use registered ports: PostgreSQL (5432), MySQL (3306), MongoDB (27017).

Dynamic or Ephemeral Ports (49152-65535): Automatically assigned by the operating system for client-side connections. When a web browser connects to a server, the OS assigns an ephemeral port for that connection's source port.

Port numbers work at the transport layer (TCP and UDP) of the networking stack. A TCP connection requires four pieces of information: source IP, source port, destination IP, and destination port. This four-tuple uniquely identifies each connection, allowing a server to handle thousands of simultaneous connections to the same port number from different clients.

require 'socket'

# Server binds to port 8080
server = TCPServer.new(8080)
puts "Server listening on port #{server.local_address.ip_port}"
# => Server listening on port 8080

# Accept a connection
client = server.accept
puts "Client connected from #{client.peeraddr[3]}:#{client.peeraddr[1]}"
# => Client connected from 127.0.0.1:52341 (ephemeral port)

Key Principles

Transport Layer Multiplexing: Port numbers operate at OSI Layer 4 (Transport Layer), above the network layer (IP addresses) and below the application layer (HTTP, FTP, etc.). The transport layer protocol (TCP or UDP) includes the port number in each packet header. When the network interface card receives a packet, the operating system examines the destination port and forwards the packet to the appropriate socket buffer.

Socket Binding: Before a server can receive connections on a port, it must bind to that port. Binding associates a socket with a specific port number and IP address combination. Only one process can bind to a specific port/address pair at any time on most systems. The SO_REUSEADDR socket option modifies this behavior for TCP, allowing faster rebinding during development.

Client vs Server Ports: Servers bind to well-known or registered ports that clients know in advance. Clients use ephemeral ports assigned automatically by the operating system. The client's ephemeral port identifies the specific connection but has no semantic meaning. Multiple connections from the same client machine use different ephemeral ports.

Port Exhaustion: Each client connection consumes an ephemeral port. On a busy client making many outbound connections, the operating system can exhaust available ephemeral ports. The default ephemeral range varies by operating system: Linux typically uses 32768-60999, while Windows uses 49152-65535. Applications making frequent short-lived connections must implement connection pooling to avoid port exhaustion.

Stateful vs Stateless Protocols: TCP ports maintain connection state between endpoints. The operating system tracks each TCP connection with a control block containing sequence numbers, window sizes, and connection state. UDP ports are stateless—each datagram stands alone. This affects how applications handle port numbers: TCP servers can identify clients by connection, while UDP servers must include client identification in application data.

Port Security Model: The distinction between privileged (0-1023) and unprivileged (1024+) ports creates a security boundary. On Unix systems, only processes running as root can bind to privileged ports. This restriction prevents unprivileged users from impersonating system services. An unprivileged user cannot start a rogue SSH server on port 22.

Network Address Translation (NAT): NAT devices rewrite port numbers as packets traverse network boundaries. Home routers use NAT to share a single public IP among multiple devices. The NAT device maintains a mapping table between internal (IP:port) pairs and external (IP:port) pairs. This complicates server applications behind NAT, which must use techniques like port forwarding or UPnP.

Dual-Stack Networking: Modern systems support both IPv4 and IPv6. A server binding to a port must decide whether to accept IPv4, IPv6, or both. Ruby's socket library handles this through address family specifications. A socket bound to :: (IPv6 any address) may or may not accept IPv4 connections depending on the system's IPV6_V6ONLY socket option.

require 'socket'

# Bind to IPv4 only
ipv4_server = TCPServer.new('0.0.0.0', 9000)

# Bind to IPv6 (may also accept IPv4 on some systems)
ipv6_server = TCPServer.new('::', 9001)

# Bind to specific interface
localhost_server = TCPServer.new('127.0.0.1', 9002)

Ruby Implementation

Ruby provides comprehensive networking support through the Socket library, part of the standard library. The library offers both low-level socket primitives and high-level abstractions for common protocols.

TCPServer and TCPSocket: The most common approach for TCP networking in Ruby uses TCPServer for servers and TCPSocket for clients. TCPServer.new(port) binds to all interfaces on the specified port. TCPServer.new(hostname, port) binds to a specific interface.

require 'socket'

# Simple echo server on port 3000
server = TCPServer.new(3000)

loop do
  client = server.accept
  
  # Read data from client
  data = client.gets
  puts "Received: #{data}"
  
  # Echo back to client
  client.puts "Echo: #{data}"
  client.close
end

Client Connection with Ephemeral Ports: When creating a client connection, Ruby automatically assigns an ephemeral port. The client does not specify its source port in normal usage.

require 'socket'

# Connect to server - Ruby assigns ephemeral port automatically
client = TCPSocket.new('localhost', 3000)

# Check the locally assigned port
local_port = client.local_address.ip_port
puts "Connected from local port: #{local_port}"
# => Connected from local port: 54321

client.puts "Hello Server"
response = client.gets
puts response
client.close

UDPSocket for Datagram Communication: UDP sockets work differently—no connection establishment occurs. The server binds to a port and receives datagrams from any source.

require 'socket'

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

data, sender = udp_server.recvfrom(1024)
puts "Received from #{sender[3]}:#{sender[1]}: #{data}"

# UDP client
udp_client = UDPSocket.new
udp_client.send("Hello UDP", 0, 'localhost', 4000)
udp_client.close

Socket Options and Port Reuse: The SO_REUSEADDR option allows rebinding to a port in TIME_WAIT state, essential during development when frequently restarting servers.

require 'socket'

socket = Socket.new(:INET, :STREAM)
socket.setsockopt(:SOCKET, :REUSEADDR, true)
socket.bind(Socket.sockaddr_in(5000, '0.0.0.0'))
socket.listen(5)

# Now the port can be reused immediately after the server stops

Port Detection and Dynamic Port Assignment: Binding to port 0 tells the OS to assign an available port automatically. This technique works well for services that advertise their port through service discovery.

require 'socket'

# Let OS choose an available port
server = TCPServer.new(0)
actual_port = server.local_address.ip_port
puts "Server started on dynamic port: #{actual_port}"
# => Server started on dynamic port: 58472

Non-blocking I/O and Port Management: Ruby supports non-blocking socket operations, important for servers handling many connections without spawning threads for each client.

require 'socket'

server = TCPServer.new(6000)
server.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, true)

clients = []

loop do
  # Non-blocking accept
  begin
    clients << server.accept_nonblock
  rescue IO::WaitReadable
    # No pending connection
  end
  
  # Handle existing clients
  clients.each do |client|
    begin
      data = client.read_nonblock(1024)
      client.write("Received: #{data}")
    rescue IO::WaitReadable
      # No data available
    rescue EOFError
      client.close
      clients.delete(client)
    end
  end
  
  sleep 0.01 # Prevent busy waiting
end

Port Forwarding and Tunneling: Ruby applications can implement port forwarding, receiving connections on one port and forwarding traffic to another destination.

require 'socket'

# Port forwarder: listen on 7000, forward to 8000
forwarder = TCPServer.new(7000)

loop do
  client = forwarder.accept
  
  Thread.new(client) do |client_conn|
    target = TCPSocket.new('localhost', 8000)
    
    # Bidirectional forwarding
    [
      Thread.new { IO.copy_stream(client_conn, target) },
      Thread.new { IO.copy_stream(target, client_conn) }
    ].each(&:join)
    
    client_conn.close
    target.close
  end
end

Practical Examples

HTTP Server on Standard Port: Building a minimal HTTP server demonstrates port binding for web services. Standard HTTP uses port 80, but development servers typically use ports above 1024 to avoid privilege requirements.

require 'socket'

server = TCPServer.new(8080)
puts "HTTP server running on port 8080"

loop do
  client = server.accept
  request = client.readpartial(2048)
  
  # Parse request line
  method, path, version = request.lines.first.split
  
  response = "HTTP/1.1 200 OK\r\n"
  response += "Content-Type: text/html\r\n"
  response += "\r\n"
  response += "<html><body><h1>Hello from port 8080</h1></body></html>"
  
  client.write(response)
  client.close
end

Multi-Port Service: Some applications listen on multiple ports simultaneously, each serving a different purpose. Administrative interfaces often run on separate ports from main application traffic.

require 'socket'

# Main application port
app_server = TCPServer.new(3000)

# Admin interface port
admin_server = TCPServer.new(3001)

loop do
  # Use select to monitor both ports
  ready = IO.select([app_server, admin_server], nil, nil, 1)
  
  next unless ready
  
  ready[0].each do |server|
    client = server.accept
    
    if server == app_server
      client.puts "Application response"
      puts "App request from #{client.peeraddr[3]}:#{client.peeraddr[1]}"
    else
      client.puts "Admin interface - restricted access"
      puts "Admin request from #{client.peeraddr[3]}:#{client.peeraddr[1]}"
    end
    
    client.close
  end
end

Database Connection Pool: Database clients maintain connection pools to reuse connections and prevent port exhaustion. Each connection consumes an ephemeral port.

require 'socket'

class ConnectionPool
  def initialize(host, port, max_connections)
    @host = host
    @port = port
    @available = []
    @in_use = []
    @max_connections = max_connections
  end
  
  def acquire
    connection = @available.pop || create_connection
    @in_use << connection
    connection
  end
  
  def release(connection)
    @in_use.delete(connection)
    @available << connection
  end
  
  private
  
  def create_connection
    if @available.size + @in_use.size >= @max_connections
      raise "Connection pool exhausted"
    end
    
    conn = TCPSocket.new(@host, @port)
    puts "Created connection on local port #{conn.local_address.ip_port}"
    conn
  end
end

# Usage
pool = ConnectionPool.new('localhost', 5432, 10)
conn = pool.acquire
# Use connection
pool.release(conn)

Port Scanner Implementation: Port scanning tests which ports accept connections on a target host. This example shows TCP connect scanning, the most reliable method.

require 'socket'
require 'timeout'

def scan_port(host, port, timeout_sec = 1)
  begin
    Timeout.timeout(timeout_sec) do
      socket = TCPSocket.new(host, port)
      socket.close
      return true
    end
  rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Timeout::Error
    return false
  end
end

# Scan common web ports
host = 'localhost'
[80, 443, 8000, 8080, 8443].each do |port|
  if scan_port(host, port)
    puts "Port #{port} is open"
  else
    puts "Port #{port} is closed"
  end
end

Common Patterns

Well-Known Port Assignment: Server applications bind to standardized ports that clients know in advance. HTTP servers use port 80 (HTTP) or 443 (HTTPS). Deviating from standard ports requires clients to specify the port explicitly in URLs or configuration.

require 'socket'

# Standard port - no port in URL needed
web_server = TCPServer.new(80) # Requires root/admin privileges

# Non-standard port - must specify in URL (http://example.com:3000)
dev_server = TCPServer.new(3000)

Ephemeral Port Allocation: Client applications let the operating system choose source ports automatically. Manual port specification on clients serves no purpose except for specific scenarios like source port filtering or connection tracking.

Port Range Configuration: Applications that require multiple ports often use consecutive port ranges. Load balancers distribute across multiple backend ports, monitoring systems use adjacent ports for different protocols.

require 'socket'

# Service cluster on ports 9000-9003
cluster = (9000..9003).map do |port|
  TCPServer.new(port)
end

puts "Cluster running on ports 9000-9003"

Service Port Discovery: Rather than hardcoding ports, applications can implement service discovery. A central registry maps service names to host:port pairs. Clients query the registry to locate services.

# Service registry
SERVICES = {
  'auth' => { host: 'localhost', port: 5001 },
  'api' => { host: 'localhost', port: 5002 },
  'cache' => { host: 'localhost', port: 6379 }
}

def connect_to_service(service_name)
  config = SERVICES[service_name]
  raise "Unknown service: #{service_name}" unless config
  
  TCPSocket.new(config[:host], config[:port])
end

# Client code
auth_conn = connect_to_service('auth')

Zero-Configuration Port Selection: For services that don't require fixed ports, binding to port 0 enables automatic port assignment. The application then advertises its actual port through other means.

require 'socket'

server = TCPServer.new(0) # OS chooses port
actual_port = server.local_address.ip_port

# Write port to file for other processes to discover
File.write('service.port', actual_port)

puts "Service running on port #{actual_port}"

Port Multiplexing: Running multiple services on a single port requires application-layer protocol multiplexing. HTTP servers use the Host header, TLS uses SNI (Server Name Indication). The application examines initial data to route to the correct handler.

require 'socket'

server = TCPServer.new(9000)

loop do
  client = server.accept
  request = client.readpartial(1024)
  
  # Route based on HTTP path
  if request.include?('GET /api')
    client.puts "HTTP/1.1 200 OK\r\n\r\nAPI Response"
  elsif request.include?('GET /admin')
    client.puts "HTTP/1.1 200 OK\r\n\r\nAdmin Interface"
  else
    client.puts "HTTP/1.1 404 Not Found\r\n\r\n"
  end
  
  client.close
end

Error Handling & Edge Cases

Address Already in Use (EADDRINUSE): Attempting to bind to a port already in use raises Errno::EADDRINUSE. This occurs when another process holds the port, or when the port remains in TIME_WAIT state after a recent server shutdown. The SO_REUSEADDR socket option mitigates the TIME_WAIT issue but cannot override active bindings.

require 'socket'

begin
  server = TCPServer.new(8080)
rescue Errno::EADDRINUSE
  puts "Port 8080 already in use"
  
  # Retry with SO_REUSEADDR
  socket = Socket.new(:INET, :STREAM)
  socket.setsockopt(:SOCKET, :REUSEADDR, true)
  socket.bind(Socket.sockaddr_in(8080, '0.0.0.0'))
  socket.listen(5)
  
  server = TCPServer.for_fd(socket.fileno)
end

Permission Denied (EACCES): Binding to ports below 1024 requires elevated privileges on Unix-like systems. Non-root processes receive Errno::EACCES when attempting to bind to privileged ports.

require 'socket'

begin
  server = TCPServer.new(80)
rescue Errno::EACCES
  puts "Cannot bind to port 80 without root privileges"
  puts "Starting on unprivileged port 8080 instead"
  server = TCPServer.new(8080)
end

Port Exhaustion: Systems have a finite number of ephemeral ports. Applications making many concurrent outbound connections can exhaust the ephemeral port range. The kernel returns EADDRNOTAVAIL when no ports remain available.

require 'socket'

connections = []

begin
  1000.times do |i|
    connections << TCPSocket.new('localhost', 8080)
    puts "Created connection #{i + 1}" if (i + 1) % 100 == 0
  end
rescue Errno::EADDRNOTAVAIL
  puts "Exhausted ephemeral ports after #{connections.size} connections"
ensure
  connections.each(&:close)
end

Connection Refused (ECONNREFUSED): Attempting to connect to a port with no listening service results in Errno::ECONNREFUSED. This indicates the host is reachable but no process accepts connections on that port.

require 'socket'

begin
  client = TCPSocket.new('localhost', 9999)
rescue Errno::ECONNREFUSED
  puts "No service listening on port 9999"
rescue Errno::ETIMEDOUT
  puts "Connection timed out - host unreachable or port filtered"
end

IPv4 vs IPv6 Binding Conflicts: Binding to the IPv6 wildcard address :: may implicitly bind to IPv4 0.0.0.0 on some systems. This causes conflicts when attempting to bind both explicitly.

require 'socket'

# This may bind to both IPv4 and IPv6
ipv6_server = TCPServer.new('::', 7000)

begin
  # This may fail if IPv6 socket already bound IPv4
  ipv4_server = TCPServer.new('0.0.0.0', 7000)
rescue Errno::EADDRINUSE
  puts "IPv6 socket already bound this port for IPv4 traffic"
end

File Descriptor Limits: Each socket consumes a file descriptor. Systems impose per-process file descriptor limits. Exceeding this limit causes EMFILE errors.

require 'socket'

servers = []

begin
  10000.times do |i|
    servers << TCPServer.new(0) # Dynamic port for each
  end
rescue Errno::EMFILE
  puts "Reached file descriptor limit at #{servers.size} sockets"
ensure
  servers.each(&:close)
end

TIME_WAIT State: TCP connections entering TIME_WAIT state hold the port for a system-defined period (typically 2 * MSL, or 60-120 seconds). Servers that restart frequently encounter this when rebinding.

require 'socket'

# Handle TIME_WAIT gracefully
def create_server_with_reuse(port)
  socket = Socket.new(:INET, :STREAM)
  socket.setsockopt(:SOCKET, :REUSEADDR, true)
  
  # Linux-specific: reuse TIME_WAIT ports
  begin
    socket.setsockopt(:SOCKET, :REUSEPORT, true)
  rescue
    # REUSEPORT not available on all systems
  end
  
  socket.bind(Socket.sockaddr_in(port, '0.0.0.0'))
  socket.listen(5)
  TCPServer.for_fd(socket.fileno)
end

server = create_server_with_reuse(8080)

Security Implications

Port Exposure and Attack Surface: Every open port represents a potential entry point for attackers. Minimize exposed ports by running only necessary services. Use firewalls to restrict port access to authorized networks.

Privileged Port Restrictions: The Unix privileged port system (0-1023) provides basic security by preventing non-root users from impersonating system services. However, this protection only prevents local user impersonation, not network-based attacks. After binding to a privileged port, applications should drop root privileges.

require 'socket'
require 'etc'

# Bind to privileged port as root
server = TCPServer.new(80)

# Drop privileges after binding
Process::GID.change_privilege(Etc.getgrnam('www-data').gid)
Process::UID.change_privilege(Etc.getpwnam('www-data').uid)

puts "Running as user #{Etc.getpwuid(Process.uid).name}"

# Continue serving as non-root
loop do
  client = server.accept
  # Handle request
  client.close
end

Port Scanning Detection: Attackers scan port ranges to identify running services. Implement rate limiting and intrusion detection to identify scanning activity. Logging connection attempts helps identify reconnaissance.

require 'socket'

connection_tracker = Hash.new { |h, k| h[k] = [] }

server = TCPServer.new(8080)

loop do
  client = server.accept
  client_ip = client.peeraddr[3]
  
  # Track connection attempts
  connection_tracker[client_ip] << Time.now
  
  # Remove old entries
  connection_tracker[client_ip].select! { |t| Time.now - t < 60 }
  
  # Detect scanning
  if connection_tracker[client_ip].size > 10
    puts "WARNING: Potential port scan from #{client_ip}"
    client.close
    next
  end
  
  # Handle legitimate connection
  client.puts "Welcome"
  client.close
end

Firewall Configuration: Firewalls control which ports accept connections from specific networks. Default-deny policies close all ports except those explicitly allowed. Application deployment requires coordinating firewall rules with required ports.

Man-in-the-Middle Attacks: Unencrypted protocols on any port are vulnerable to interception. Use TLS/SSL for sensitive data transmission regardless of port number. Port 443 conventionally indicates HTTPS, but any port can carry encrypted traffic.

require 'socket'
require 'openssl'

# TLS server on non-standard port
tcp_server = TCPServer.new(9443)

ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.cert = OpenSSL::X509::Certificate.new(File.read('cert.pem'))
ssl_context.key = OpenSSL::PKey::RSA.new(File.read('key.pem'))

ssl_server = OpenSSL::SSL::SSLServer.new(tcp_server, ssl_context)

loop do
  connection = ssl_server.accept
  # Encrypted communication
  connection.puts "Secure connection on port 9443"
  connection.close
end

Port-Based Service Identification: Attackers identify services by port number and target known vulnerabilities. Running services on non-standard ports provides minimal security—real protection comes from service hardening and patching.

Localhost Binding for Security: Binding to 127.0.0.1 restricts connections to the local machine only. Use localhost binding for services that should not accept external connections.

require 'socket'

# Accessible only from local machine
local_only = TCPServer.new('127.0.0.1', 6379)

# Accessible from network
network_accessible = TCPServer.new('0.0.0.0', 8080)

# Specific interface only
internal_only = TCPServer.new('10.0.1.5', 3000)

Reference

Port Range Categories

Range Name Description Usage
0-1023 Well-Known Ports System ports requiring privileges HTTP (80), HTTPS (443), SSH (22), FTP (21)
1024-49151 Registered Ports IANA registered services PostgreSQL (5432), MySQL (3306), Redis (6379)
49152-65535 Dynamic/Ephemeral OS-assigned client ports Automatic client source ports

Common Service Ports

Service Port Protocol Description
HTTP 80 TCP Unencrypted web traffic
HTTPS 443 TCP Encrypted web traffic
SSH 22 TCP Secure shell remote access
FTP 21 TCP File transfer (control)
SMTP 25 TCP Email transmission
DNS 53 TCP/UDP Domain name resolution
DHCP 67/68 UDP Dynamic IP configuration
Telnet 23 TCP Unencrypted remote access
IMAP 143 TCP Email retrieval
POP3 110 TCP Email retrieval
PostgreSQL 5432 TCP Database server
MySQL 3306 TCP Database server
Redis 6379 TCP Cache/data structure server
MongoDB 27017 TCP Document database
RabbitMQ 5672 TCP Message queue

Ruby Socket Methods

Method Purpose Example
TCPServer.new(port) Create TCP server on port TCPServer.new(8080)
TCPSocket.new(host, port) Connect TCP client to port TCPSocket.new('localhost', 80)
UDPSocket.new Create UDP socket socket.bind('0.0.0.0', 5000)
Socket.new Create low-level socket Socket.new(:INET, :STREAM)
socket.bind(sockaddr) Bind socket to address/port socket.bind(Socket.sockaddr_in(port, addr))
socket.listen(backlog) Listen for connections socket.listen(5)
server.accept Accept incoming connection client = server.accept
socket.local_address Get local socket address socket.local_address.ip_port
socket.remote_address Get remote socket address socket.remote_address.ip_address
socket.setsockopt Set socket options socket.setsockopt(:SOCKET, :REUSEADDR, true)

Socket Options for Port Management

Option Level Effect
SO_REUSEADDR SOL_SOCKET Allow binding to TIME_WAIT ports
SO_REUSEPORT SOL_SOCKET Allow multiple binds to same port (Linux)
IPV6_V6ONLY IPPROTO_IPV6 Disable IPv4-mapped IPv6 addresses
SO_LINGER SOL_SOCKET Control connection close behavior
TCP_NODELAY IPPROTO_TCP Disable Nagle algorithm

Common Error Codes

Error Errno Meaning Common Cause
EADDRINUSE 48/98 Address already in use Port bound by another process
EACCES 13 Permission denied Privileged port without root
EADDRNOTAVAIL 49/99 Address not available Port exhaustion, invalid address
ECONNREFUSED 61/111 Connection refused No listener on port
ETIMEDOUT 60/110 Connection timed out Host unreachable, firewall
EMFILE 24 Too many open files File descriptor limit reached

Port State Checklist

State Description Action Required
LISTEN Socket bound and listening Normal server state
ESTABLISHED Active connection Normal connection state
TIME_WAIT Connection closed, waiting Use SO_REUSEADDR to rebind
CLOSE_WAIT Remote closed, local pending Application must close socket
Port in use Bind fails with EADDRINUSE Check for running processes, wait for TIME_WAIT
Port accessible Firewall allows connections Verify firewall rules
Port exhausted No ephemeral ports available Implement connection pooling