Overview
The Open Systems Interconnection (OSI) Model standardizes network communication functions into seven distinct layers. Developed by the International Organization for Standardization (ISO) in 1984, the model provides a conceptual framework for understanding how data moves between applications on different computers across networks.
Each layer in the OSI Model performs specific functions and communicates with the layers immediately above and below it. The model separates the complex process of network communication into manageable components, allowing different vendors and developers to create interoperable networking products and protocols.
The seven layers, from lowest to highest, are:
- Physical Layer - Transmission of raw bits over physical medium
- Data Link Layer - Node-to-node data transfer and error detection
- Network Layer - Routing and logical addressing
- Transport Layer - End-to-end communication and reliability
- Session Layer - Managing connections between applications
- Presentation Layer - Data translation and encryption
- Application Layer - Network services to applications
The OSI Model differs from the TCP/IP model, which combines several OSI layers. While TCP/IP represents actual implementation, the OSI Model serves as a teaching tool and design reference. Network engineers and developers use the model to diagnose problems, understand protocol interactions, and design network architectures.
Data transmission follows an encapsulation process. At the sending computer, data moves down the stack from Layer 7 to Layer 1, with each layer adding header information. At the receiving computer, data moves up the stack, with each layer removing its corresponding header. This process ensures that each layer performs its designated function without needing knowledge of other layers' internal operations.
Application Data
↓
[L7: Application] → Add Application Header
↓
[L6: Presentation] → Add Presentation Header
↓
[L5: Session] → Add Session Header
↓
[L4: Transport] → Add Transport Header (Segment)
↓
[L3: Network] → Add Network Header (Packet)
↓
[L2: Data Link] → Add Frame Header/Trailer (Frame)
↓
[L1: Physical] → Transmit Bits
Key Principles
The OSI Model operates on several fundamental principles that define how layers interact and process data.
Layer Independence: Each layer performs specific functions independent of other layers. A layer communicates only with adjacent layers through defined interfaces. This independence allows modifications to one layer without affecting others, provided the interface contracts remain unchanged. The Network Layer routes packets without knowledge of how the Physical Layer transmits bits or how the Transport Layer ensures reliability.
Encapsulation: Each layer adds control information (headers or trailers) to the data received from the layer above. This process creates Protocol Data Units (PDUs) specific to each layer:
- Layer 7-5: Data
- Layer 4: Segment (TCP) or Datagram (UDP)
- Layer 3: Packet
- Layer 2: Frame
- Layer 1: Bits
Headers contain information required for that layer's functions. The Transport Layer header includes port numbers and sequence numbers. The Network Layer header includes source and destination IP addresses. The receiving system's corresponding layer reads and removes each header, processing the information before passing data to the next layer.
Service Primitives: Layers communicate through service primitives - abstract operations that define interactions between adjacent layers. Four types exist:
- REQUEST: Upper layer requests service from lower layer
- INDICATION: Lower layer notifies upper layer of event
- RESPONSE: Upper layer responds to indication from lower layer
- CONFIRM: Lower layer confirms request completion
Connection-Oriented vs Connectionless: Different layers can operate in connection-oriented or connectionless modes. Connection-oriented communication establishes a connection, transfers data, then terminates the connection. This mode provides reliable, ordered delivery with error checking. TCP at the Transport Layer exemplifies connection-oriented communication.
Connectionless communication sends data without establishing a connection. Each data unit contains complete addressing information and travels independently. This mode offers less overhead but no delivery guarantees. UDP at the Transport Layer and IP at the Network Layer demonstrate connectionless communication.
Error Detection and Correction: Multiple layers implement error detection and correction mechanisms. The Data Link Layer detects errors in transmitted frames using checksums or cyclic redundancy checks (CRC). The Transport Layer provides end-to-end error detection and correction through acknowledgments and retransmissions. The Presentation Layer may perform data integrity checking for encrypted data.
Flow Control: Flow control prevents fast senders from overwhelming slow receivers. The Data Link Layer implements flow control between adjacent nodes. The Transport Layer provides end-to-end flow control across the entire network path. Window-based flow control mechanisms allow senders to transmit multiple segments before requiring acknowledgment, balancing throughput and reliability.
Multiplexing and Demultiplexing: Upper layers multiplex data from multiple sources onto a single lower-layer connection. The Transport Layer uses port numbers to multiplex multiple application connections onto one network connection. The receiving side demultiplexes incoming data, directing each segment to the correct application based on port numbers.
Ruby Implementation
Ruby provides networking capabilities that map to various OSI layers, primarily focusing on Layers 3-7. The language includes standard library modules and third-party gems for network programming at different abstraction levels.
Transport Layer (Layer 4) - Socket Programming: Ruby's Socket class provides direct access to TCP and UDP protocols. The TCPSocket and UDPSocket classes offer simplified interfaces for common transport layer operations.
require 'socket'
# TCP server (connection-oriented)
server = TCPServer.new(8080)
puts "Server listening on port 8080"
client = server.accept
request = client.gets
puts "Received: #{request}"
client.puts "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello"
client.close
# => TCP connection established, data exchanged, connection closed
require 'socket'
# UDP socket (connectionless)
socket = UDPSocket.new
socket.bind('0.0.0.0', 9000)
data, addr = socket.recvfrom(1024)
puts "Received #{data.bytesize} bytes from #{addr[3]}:#{addr[1]}"
# => No connection establishment, direct datagram reception
The Socket class exposes socket options that correspond to Transport Layer features. Setting SO_KEEPALIVE enables TCP keepalive, maintaining long-lived connections. The TCP_NODELAY option disables Nagle's algorithm, affecting how the Transport Layer batches small segments.
require 'socket'
socket = TCPSocket.new('example.com', 80)
# Set socket options at Transport Layer
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
# Query socket state
linger = socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER)
puts "Linger timeout: #{linger.int} seconds"
# => Access to TCP layer configuration
Network Layer (Layer 3) - IP Operations: Ruby's IPAddr class handles IP address manipulation and subnet calculations. The Socket.getaddrinfo method performs DNS resolution, bridging the Application Layer and Network Layer.
require 'ipaddr'
require 'socket'
# IP address operations (Network Layer)
ip = IPAddr.new('192.168.1.100/24')
puts "Network: #{ip.to_range.first}"
puts "Broadcast: #{ip.to_range.last}"
puts "Netmask: #{ip.mask(24).to_s}"
# DNS resolution maps hostnames to IP addresses
addresses = Socket.getaddrinfo('example.com', nil, Socket::AF_INET)
addresses.each do |addr|
puts "IP: #{addr[3]}"
end
# => 93.184.216.34
Session and Presentation Layers (Layers 5-6) - Data Formatting: Ruby handles data encoding and serialization, functions typically associated with the Presentation Layer. The JSON and Marshal modules serialize data structures for network transmission.
require 'json'
require 'zlib'
# Presentation Layer - data encoding and compression
data = { user_id: 123, message: 'Network data' }
json_data = JSON.generate(data)
# Compress data before transmission
compressed = Zlib::Deflate.deflate(json_data)
puts "Original: #{json_data.bytesize} bytes"
puts "Compressed: #{compressed.bytesize} bytes"
# Decompress and decode at receiver
decompressed = Zlib::Inflate.inflate(compressed)
parsed = JSON.parse(decompressed)
# => Data representation transformed for efficient transmission
Application Layer (Layer 7) - Protocol Implementation: Ruby's standard library includes Application Layer protocol implementations. The Net::HTTP module implements HTTP, while Net::FTP handles file transfers.
require 'net/http'
require 'uri'
# Application Layer HTTP protocol
uri = URI('https://api.example.com/data')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['User-Agent'] = 'RubyApp/1.0'
response = http.request(request)
puts "Status: #{response.code}"
puts "Content-Type: #{response['content-type']}"
# => Complete HTTP transaction at Application Layer
Layer Interaction in Web Frameworks: Ruby web frameworks like Rack abstract multiple OSI layers. Rack provides a middleware interface that handles Session Layer concerns (connection management), Presentation Layer concerns (content negotiation, encoding), and Application Layer protocols (HTTP).
# Rack application demonstrating multi-layer concerns
app = Proc.new do |env|
# env contains headers (Application Layer)
# Rack handles HTTP parsing (Application Layer)
# Underlying TCP connection (Transport Layer) abstracted
content_type = env['HTTP_ACCEPT']
body = if content_type =~ /json/
'{"message": "Hello"}' # JSON encoding (Presentation Layer)
else
'Hello' # Plain text (Presentation Layer)
end
[200, {'Content-Type' => content_type}, [body]]
end
require 'rack'
Rack::Handler::WEBrick.run(app, Port: 3000)
# => Rack abstracts Layers 4-7 for application developer
Low-Level Network Inspection: The Socket class provides methods to inspect lower-layer information. The Socket.getifaddrs method retrieves network interface information, including hardware addresses (Layer 2) and IP addresses (Layer 3).
require 'socket'
Socket.getifaddrs.each do |ifaddr|
next unless ifaddr.addr
puts "Interface: #{ifaddr.name}"
if ifaddr.addr.ip?
puts " IP Address: #{ifaddr.addr.ip_address}" # Layer 3
end
if ifaddr.broadaddr
puts " Broadcast: #{ifaddr.broadaddr.ip_address}" # Layer 3
end
if ifaddr.netmask
puts " Netmask: #{ifaddr.netmask.ip_address}" # Layer 3
end
end
# => Inspect Network Layer configuration
Practical Examples
Example 1: Multi-Layer HTTP Request: An HTTP request demonstrates data flow through multiple layers. Starting at the Application Layer with an HTTP GET request, data descends through the stack, gaining headers at each layer.
require 'socket'
# Manually construct HTTP request (Application Layer)
host = 'example.com'
port = 80
# Transport Layer - establish TCP connection
socket = TCPSocket.new(host, port)
# Application Layer - send HTTP request
request = "GET / HTTP/1.1\r\n"
request += "Host: #{host}\r\n"
request += "Connection: close\r\n"
request += "\r\n"
socket.print(request)
# Application Layer - receive HTTP response
response = ""
while line = socket.gets
response += line
end
socket.close
# Parse response headers and body
headers, body = response.split("\r\n\r\n", 2)
puts "HTTP Response Headers:"
puts headers
# => HTTP/1.1 200 OK
# => Content-Type: text/html
# => (Transport Layer ensured reliable delivery)
In this example, the Application Layer constructs the HTTP message. The Transport Layer (TCP) establishes a connection, segments the data, and ensures reliable delivery through acknowledgments. The Network Layer (IP) routes packets across networks to reach the destination. The Data Link and Physical Layers handle frame transmission over the local network segment.
Example 2: UDP Broadcast for Service Discovery: UDP broadcasting demonstrates connectionless communication at the Transport Layer. Service discovery protocols often use broadcasts to locate services without maintaining connections.
require 'socket'
# Service announcement (server side)
def announce_service
socket = UDPSocket.new
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, 1)
loop do
message = "SERVICE:FileServer:PORT:8080"
socket.send(message, 0, '<broadcast>', 9999)
puts "Broadcast sent"
sleep 5
end
end
# Service discovery (client side)
def discover_services
socket = UDPSocket.new
socket.bind('0.0.0.0', 9999)
puts "Listening for service announcements..."
loop do
data, addr = socket.recvfrom(1024)
puts "Discovered service: #{data} from #{addr[3]}"
# => SERVICE:FileServer:PORT:8080 from 192.168.1.100
end
end
This connectionless protocol operates at the Transport Layer without establishing connections. The Network Layer handles broadcast addressing, delivering packets to all hosts on the local network. The Data Link Layer may use hardware broadcast addresses (FF:FF:FF:FF:FF:FF for Ethernet) to reach all devices.
Example 3: SSL/TLS Encryption Layer: SSL/TLS operates between the Transport Layer and Application Layer, sometimes considered part of the Presentation Layer. Ruby's OpenSSL integration demonstrates secure communication.
require 'socket'
require 'openssl'
# Create secure server
tcp_server = TCPServer.new(8443)
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)
client = ssl_server.accept
puts "Client connected: #{client.peer_cert.subject}"
# Data encrypted at Presentation Layer
data = client.gets
puts "Received encrypted: #{data}"
client.puts "Encrypted response"
client.close
# => TLS encrypts data before Transport Layer transmission
The TLS handshake occurs after TCP connection establishment (Transport Layer) but before application data exchange (Application Layer). TLS provides encryption (Presentation Layer function) while relying on TCP for reliable delivery (Transport Layer function).
Example 4: Protocol Tunneling: Tunneling encapsulates one protocol within another, demonstrating how layers can be stacked in non-standard ways. VPN protocols encapsulate Network Layer packets within Transport Layer protocols.
require 'socket'
require 'json'
# Tunnel application data through custom protocol
class ProtocolTunnel
def initialize(host, port)
@socket = TCPSocket.new(host, port)
end
def send_message(message)
# Application Layer message
app_data = { type: 'MESSAGE', content: message, timestamp: Time.now.to_i }
# Presentation Layer - serialize to JSON
json_data = JSON.generate(app_data)
# Custom Session Layer - add tunnel header
tunnel_header = [json_data.bytesize].pack('N')
# Transport Layer - send over TCP
@socket.write(tunnel_header + json_data)
end
def receive_message
# Read tunnel header (Session Layer)
header = @socket.read(4)
length = header.unpack1('N')
# Read payload
json_data = @socket.read(length)
# Presentation Layer - deserialize
JSON.parse(json_data)
end
end
tunnel = ProtocolTunnel.new('tunnel.example.com', 5000)
tunnel.send_message('Tunneled data')
response = tunnel.receive_message
# => Application data tunneled through custom protocol
Example 5: Socket Options and Layer Configuration: Socket options control behavior at different layers. Configuring these options demonstrates direct interaction with Transport and Network Layer features.
require 'socket'
socket = TCPSocket.new('example.com', 80)
# Transport Layer configuration
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
puts "TCP_NODELAY: Disables Nagle's algorithm"
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
puts "SO_KEEPALIVE: Enables keepalive probes"
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF, 65536)
puts "SO_RCVBUF: Set receive buffer to 64KB"
# Query current settings
nodelay = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY)
puts "Current TCP_NODELAY: #{nodelay.int}"
keepalive = socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE)
puts "Current SO_KEEPALIVE: #{keepalive.int}"
rcvbuf = socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF)
puts "Current receive buffer: #{rcvbuf.int} bytes"
# => Direct control over Transport Layer behavior
Integration & Interoperability
Layer integration defines how the OSI Model's components interact to provide end-to-end communication. Each layer depends on services from the layer below while providing services to the layer above.
Vertical Communication: Data flows vertically through the stack. An application generates data at Layer 7. Each layer adds its header, creating nested protocol data units. The Physical Layer transmits the resulting bit stream. At the receiver, each layer strips its corresponding header, reversing the encapsulation process.
# Demonstrate encapsulation concept
class LayerSimulator
def initialize(data)
@data = data
end
def application_layer
puts "L7 Application: Original data: #{@data}"
"APP:#{@data}"
end
def presentation_layer(data)
encoded = Base64.encode64(data).strip
puts "L6 Presentation: Encoded: #{encoded}"
"PRES:#{encoded}"
end
def session_layer(data)
session_id = rand(1000..9999)
puts "L5 Session: Added session ID: #{session_id}"
"SESS:#{session_id}:#{data}"
end
def transport_layer(data)
port = 8080
puts "L4 Transport: Added port: #{port}"
"TCP:#{port}:#{data}"
end
def network_layer(data)
src_ip = '192.168.1.100'
dst_ip = '192.168.1.200'
puts "L3 Network: Added IPs: #{src_ip} -> #{dst_ip}"
"IP:#{src_ip}:#{dst_ip}:#{data}"
end
def encapsulate
l7 = application_layer
l6 = presentation_layer(l7)
l5 = session_layer(l6)
l4 = transport_layer(l5)
l3 = network_layer(l4)
l3
end
end
simulator = LayerSimulator.new("Hello Network")
packet = simulator.encapsulate
puts "\nFinal packet: #{packet}"
# => Shows how each layer adds its header
Horizontal Communication: While data physically flows vertically, each layer conceptually communicates with its peer on the remote system. The Transport Layer on one system communicates with the Transport Layer on another system through the protocol defined in the Transport Layer header. This peer-to-peer communication occurs through vertical data flow.
Layer Interface Contracts: Each layer exposes a service interface to the layer above. The Transport Layer provides reliable or unreliable delivery services to the Session Layer. The Network Layer provides routing services to the Transport Layer. These interfaces remain stable even when internal implementations change.
# Abstract layer interfaces
module TransportLayerInterface
def send_segment(data, destination)
raise NotImplementedError
end
def receive_segment
raise NotImplementedError
end
end
class TCPImplementation
include TransportLayerInterface
def send_segment(data, destination)
# Establish connection
socket = TCPSocket.new(destination[:host], destination[:port])
# Reliable delivery with acknowledgment
socket.write(data)
ack = socket.read(3)
socket.close
ack == "ACK"
end
def receive_segment
# Implementation details
end
end
class UDPImplementation
include TransportLayerInterface
def send_segment(data, destination)
# Connectionless transmission
socket = UDPSocket.new
socket.send(data, 0, destination[:host], destination[:port])
socket.close
true # No acknowledgment in UDP
end
def receive_segment
# Implementation details
end
end
# => Different implementations satisfy same interface contract
Cross-Layer Optimization: While layers maintain independence, implementations sometimes violate strict layering for performance. TCP implementations may query lower layers about Maximum Transmission Unit (MTU) to avoid fragmentation. Application Layer protocols may implement compression (Presentation Layer function) for efficiency.
Protocol Translation: Gateways and proxies translate between different protocol stacks. A gateway might translate between the OSI Model and the TCP/IP model, or between IPv4 and IPv6. This translation occurs at specific layers while maintaining transparency to other layers.
require 'socket'
# Protocol translation proxy
class ProtocolProxy
def initialize(listen_port, target_host, target_port)
@server = TCPServer.new(listen_port)
@target_host = target_host
@target_port = target_port
end
def start
loop do
client = @server.accept
Thread.new(client) { |conn| handle_connection(conn) }
end
end
def handle_connection(client)
# Connect to target
target = TCPSocket.new(@target_host, @target_port)
# Bidirectional forwarding
threads = []
threads << Thread.new do
while data = client.recv(4096)
break if data.empty?
translated = translate_protocol(data)
target.send(translated, 0)
end
end
threads << Thread.new do
while data = target.recv(4096)
break if data.empty?
client.send(data, 0)
end
end
threads.each(&:join)
client.close
target.close
end
def translate_protocol(data)
# Translate between protocol versions or formats
# Operates at Application/Presentation Layer
data.gsub(/ProtocolV1/, 'ProtocolV2')
end
end
# => Gateway translates protocols while maintaining connectivity
Layer Independence and Substitution: Different protocols can substitute at the same layer without affecting other layers. The Network Layer can use IPv4 or IPv6 transparently to upper layers. The Transport Layer can switch between TCP and UDP based on application requirements. This substitution enables protocol evolution and optimization.
Tools & Ecosystem
Ruby's networking ecosystem includes tools and gems operating at different OSI layers, from low-level socket manipulation to high-level protocol implementations.
Transport and Network Layer Tools: The standard library's socket module provides the foundation. Third-party gems extend these capabilities with additional protocols and features.
The eventmachine gem provides event-driven I/O for scalable network servers. It abstracts Transport Layer complexity while maintaining low-level control:
require 'eventmachine'
# EventMachine operates at Transport Layer
module EchoServer
def receive_data(data)
puts "Received: #{data}"
send_data("Echo: #{data}")
end
def unbind
puts "Client disconnected"
end
end
EventMachine.run do
EventMachine.start_server('0.0.0.0', 8080, EchoServer)
puts "Server running on port 8080"
end
# => Asynchronous Transport Layer handling
The async gem provides modern concurrency primitives for network operations. It uses fibers and non-blocking I/O for efficient Transport Layer communication:
require 'async'
require 'async/io'
Async do
endpoint = Async::IO::Endpoint.tcp('example.com', 80)
endpoint.connect do |socket|
socket.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
response = socket.read
puts response
end
end
# => Fiber-based concurrency for network I/O
Application Layer Protocol Libraries: Ruby includes standard library modules for common Application Layer protocols. Net::HTTP implements HTTP/1.1, Net::FTP handles file transfers, and Net::SMTP manages email transmission.
The httparty gem simplifies HTTP operations with a cleaner API:
require 'httparty'
response = HTTParty.get('https://api.example.com/data',
headers: {
'Authorization' => 'Bearer token123',
'Accept' => 'application/json'
},
timeout: 10
)
puts "Status: #{response.code}"
puts "Body: #{response.body}"
# => Simplified HTTP (Application Layer)
The faraday gem provides middleware-based HTTP client architecture, separating concerns across layers:
require 'faraday'
conn = Faraday.new(url: 'https://api.example.com') do |f|
f.request :json # Presentation Layer - JSON encoding
f.response :json # Presentation Layer - JSON decoding
f.response :logger # Application Layer - logging
f.adapter Faraday.default_adapter # Transport Layer
end
response = conn.get('/data')
# => Middleware stack mirrors layered architecture
Presentation Layer Processing: Gems handle data encoding, compression, and encryption typically associated with the Presentation Layer.
The oj gem provides fast JSON parsing and generation:
require 'oj'
data = { user: 'alice', messages: 100 }
json = Oj.dump(data, mode: :compat)
# => Fast JSON serialization (Presentation Layer)
parsed = Oj.load(json)
# => Fast JSON deserialization (Presentation Layer)
The msgpack gem implements binary serialization for efficient data transmission:
require 'msgpack'
data = { type: 'event', id: 12345, payload: 'data' }
packed = MessagePack.pack(data)
puts "Packed size: #{packed.bytesize} bytes"
unpacked = MessagePack.unpack(packed)
# => Binary serialization reduces transmission size
Debugging and Network Analysis: Several gems assist in debugging network communication across layers.
The net-telnet gem, while deprecated for production, helps debug Application Layer protocols:
require 'net/telnet'
# Connect to service and observe raw protocol
telnet = Net::Telnet.new('Host' => 'example.com', 'Port' => 25)
telnet.cmd('String' => 'EHLO localhost', 'Match' => /250/) do |output|
puts "SMTP Response: #{output}"
end
# => Observe Application Layer protocol directly
The packetfu gem provides packet crafting and analysis capabilities, operating at Network and Transport Layers:
require 'packetfu'
# Craft custom TCP packet
packet = PacketFu::TCPPacket.new
packet.ip_saddr = '192.168.1.100'
packet.ip_daddr = '192.168.1.200'
packet.tcp_sport = 12345
packet.tcp_dport = 80
packet.tcp_flags.syn = 1
puts packet.inspect
# => Manipulate Network and Transport Layer headers
Web Framework Layer Abstraction: Web frameworks like Rails abstract layers 4-7, allowing developers to focus on application logic.
# Rails controller - operates at Application Layer
class UsersController < ApplicationController
def create
# Application Layer - business logic
user = User.create(user_params)
# Presentation Layer - content negotiation handled by Rails
respond_to do |format|
format.json { render json: user } # JSON encoding
format.xml { render xml: user } # XML encoding
end
# Session/Transport/Network Layers handled by Rack/Puma
end
end
Load Balancers and Proxies: Gems like rack-proxy operate at multiple layers, forwarding requests while modifying headers:
require 'rack/proxy'
class CustomProxy < Rack::Proxy
def rewrite_env(env)
# Modify Application Layer headers
env['HTTP_X_FORWARDED_FOR'] = env['REMOTE_ADDR']
# Change destination
env['HTTP_HOST'] = 'backend.example.com'
env
end
end
# => Proxy operates at Application and Transport Layers
Common Pitfalls
Confusing OSI Model with TCP/IP Model: The OSI Model contains seven layers while the TCP/IP model has four. Developers often incorrectly assume one-to-one correspondence. The TCP/IP Application Layer encompasses OSI Layers 5-7. The TCP/IP Internet Layer corresponds to OSI Layer 3. Understanding both models prevents architectural mistakes.
Assuming Layer Boundaries in Real Implementations: Real protocols do not always respect strict layer boundaries. SSL/TLS operates between Transport and Application Layers. HTTP/2 includes features traditionally associated with Session Layer (multiplexing) and Presentation Layer (header compression). Expecting pure layering in production systems leads to confusion.
require 'net/http'
require 'openssl'
# SSL/TLS does not fit cleanly into one layer
uri = URI('https://example.com')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true # Presentation Layer concern
# But TLS relies on TCP (Transport Layer)
http.verify_mode = OpenSSL::SSL::VERIFY_PEER # Security spans layers
# => TLS demonstrates that real protocols cross layer boundaries
Ignoring Layer 2 Implications: Developers working at higher layers often ignore Data Link Layer constraints. Maximum Transmission Unit (MTU) limits at Layer 2 affect upper layers. Large packets fragment, reducing performance. Applications sending large datagrams without considering MTU encounter performance degradation.
require 'socket'
# Sending large UDP datagram without considering MTU
socket = UDPSocket.new
large_data = 'X' * 65000
# This exceeds typical MTU (1500 bytes)
# Will fragment at IP layer, potentially causing issues
socket.send(large_data, 0, 'example.com', 9000)
# => Fragmentation reduces reliability and performance
Misunderstanding Connection State: TCP maintains connection state at the Transport Layer, but applications must handle application-level state. Developers sometimes assume TCP connection persistence guarantees application session persistence. Load balancers may break Transport Layer connections while maintaining application sessions through cookies or session IDs.
require 'socket'
# TCP connection does not guarantee application session
def perform_transaction
socket = TCPSocket.new('example.com', 8080)
# Send request
socket.puts 'BEGIN_TRANSACTION'
response = socket.gets
# Connection might close here
socket.close
# New connection required for next operation
socket = TCPSocket.new('example.com', 8080)
socket.puts 'CONTINUE_TRANSACTION'
# => Application must track session, not rely on TCP connection
end
Neglecting Error Handling Across Layers: Errors occur at different layers with different characteristics. Network Layer routing failures differ from Transport Layer connection timeouts which differ from Application Layer protocol errors. Treating all network errors identically prevents proper error recovery.
require 'socket'
require 'timeout'
begin
Timeout.timeout(5) do
socket = TCPSocket.new('example.com', 80)
socket.puts 'REQUEST'
response = socket.gets
end
rescue Errno::ECONNREFUSED
# Transport Layer - connection refused
puts "Server not accepting connections"
rescue Errno::EHOSTUNREACH
# Network Layer - host unreachable
puts "Cannot route to host"
rescue Errno::ETIMEDOUT
# Transport Layer - connection timeout
puts "Connection timed out"
rescue Timeout::Error
# Application Layer - operation timeout
puts "Operation took too long"
rescue SocketError => e
# Could be DNS resolution (Application Layer) or other socket error
puts "Socket error: #{e.message}"
end
# => Different errors require different recovery strategies
Assuming Reliable Delivery Without TCP: UDP provides no delivery guarantees. Applications using UDP must implement their own reliability mechanisms if needed. Developers sometimes use UDP expecting TCP-like reliability, leading to data loss.
require 'socket'
# UDP provides no guarantees
socket = UDPSocket.new
100.times do |i|
socket.send("Packet #{i}", 0, 'example.com', 9000)
end
# Some packets WILL be lost with no notification
# Application must implement acknowledgments if reliability required
Overlooking Flow Control: TCP implements flow control, but applications can still overwhelm receivers by generating data faster than the network can transmit. Understanding the relationship between application data rate and network capacity prevents buffer bloat and performance issues.
Misusing Broadcast and Multicast: Broadcast operates at Layer 2 (limited to local network) and Layer 3 (IP broadcast). Developers sometimes expect broadcasts to cross routers without understanding routing restrictions. Most networks block broadcasts to prevent network storms.
require 'socket'
socket = UDPSocket.new
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, 1)
# Broadcast limited to local network segment
socket.send('DISCOVERY', 0, '255.255.255.255', 9000)
# => Routers do not forward broadcasts
# Use multicast or unicast for cross-network communication
Ignoring Presentation Layer Security: Sending sensitive data without encryption exposes it at all layers below the Presentation Layer. Network analyzers capture unencrypted traffic. Applications must encrypt data before transmission when security matters.
Reference
OSI Model Layer Summary
| Layer | Name | PDU | Function | Example Protocols |
|---|---|---|---|---|
| 7 | Application | Data | Network services to applications | HTTP, FTP, SMTP, DNS |
| 6 | Presentation | Data | Data translation, encryption, compression | SSL/TLS, JPEG, ASCII |
| 5 | Session | Data | Session management, dialog control | NetBIOS, RPC |
| 4 | Transport | Segment/Datagram | End-to-end communication, reliability | TCP, UDP |
| 3 | Network | Packet | Routing, logical addressing | IP, ICMP, OSPF |
| 2 | Data Link | Frame | Node-to-node transfer, MAC addressing | Ethernet, PPP, ARP |
| 1 | Physical | Bits | Physical transmission medium | Ethernet cable, WiFi, Fiber |
Layer Functions and Characteristics
| Layer | Primary Functions | Key Characteristics | Ruby Access |
|---|---|---|---|
| Application | User interface, application services | Closest to end user | Net::HTTP, Net::FTP, Net::SMTP |
| Presentation | Data format conversion, encryption | Data representation | JSON, Marshal, OpenSSL |
| Session | Session establishment and management | Connection coordination | Rack session handling |
| Transport | Segmentation, reliability, flow control | End-to-end communication | Socket, TCPSocket, UDPSocket |
| Network | Routing, logical addressing | Path determination | IPAddr, Socket.getaddrinfo |
| Data Link | Framing, physical addressing | Node-to-node reliability | Socket.getifaddrs (limited) |
| Physical | Bit transmission | Physical medium | Not directly accessible |
Protocol Data Unit Encapsulation
| Layer | PDU Name | Header Contents | Size Overhead |
|---|---|---|---|
| Application | Data | Application protocol headers | Varies by protocol |
| Presentation | Data | Encoding, compression metadata | Varies by encoding |
| Session | Data | Session identifiers | Varies by protocol |
| Transport | Segment/Datagram | Port numbers, sequence numbers, flags | 20-60 bytes (TCP), 8 bytes (UDP) |
| Network | Packet | Source/destination IP, TTL, protocol | 20-60 bytes |
| Data Link | Frame | MAC addresses, frame type, CRC | 14-26 bytes |
| Physical | Bits | Preamble, sync bits | Varies by medium |
TCP vs UDP Characteristics
| Characteristic | TCP | UDP |
|---|---|---|
| Connection | Connection-oriented | Connectionless |
| Reliability | Guaranteed delivery | Best-effort delivery |
| Ordering | Ordered delivery | Unordered delivery |
| Speed | Slower (overhead) | Faster (minimal overhead) |
| Header Size | 20-60 bytes | 8 bytes |
| Flow Control | Yes | No |
| Error Checking | Yes | Yes (optional) |
| Use Cases | HTTP, FTP, SSH | DNS, streaming, gaming |
| Ruby Class | TCPSocket, TCPServer | UDPSocket |
Common Port Numbers by Layer 7 Protocol
| Protocol | Port | Transport | Purpose |
|---|---|---|---|
| HTTP | 80 | TCP | Web traffic |
| HTTPS | 443 | TCP | Secure web traffic |
| FTP | 20, 21 | TCP | File transfer |
| SSH | 22 | TCP | Secure shell |
| SMTP | 25 | TCP | Email transmission |
| DNS | 53 | UDP/TCP | Name resolution |
| DHCP | 67, 68 | UDP | IP address assignment |
| POP3 | 110 | TCP | Email retrieval |
| IMAP | 143 | TCP | Email access |
| SNMP | 161, 162 | UDP | Network management |
Socket Options by Layer
| Option | Layer | Constant | Purpose |
|---|---|---|---|
| SO_REUSEADDR | Transport | Socket::SO_REUSEADDR | Reuse address |
| SO_KEEPALIVE | Transport | Socket::SO_KEEPALIVE | Keep connection alive |
| TCP_NODELAY | Transport | Socket::TCP_NODELAY | Disable Nagle algorithm |
| SO_RCVBUF | Transport | Socket::SO_RCVBUF | Receive buffer size |
| SO_SNDBUF | Transport | Socket::SO_SNDBUF | Send buffer size |
| SO_BROADCAST | Network | Socket::SO_BROADCAST | Allow broadcast |
| IP_TTL | Network | Socket::IP_TTL | Time to live |
| IPV6_V6ONLY | Network | Socket::IPV6_V6ONLY | IPv6 only mode |
Ruby Networking Module Quick Reference
| Module/Class | Layer | Purpose | Common Methods |
|---|---|---|---|
| Socket | Transport/Network | Low-level socket operations | new, bind, listen, accept, connect |
| TCPSocket | Transport | TCP client connections | new, gets, puts, read, write |
| TCPServer | Transport | TCP server | new, accept, close |
| UDPSocket | Transport | UDP communication | new, bind, send, recvfrom |
| IPAddr | Network | IP address manipulation | new, mask, include? |
| Net::HTTP | Application | HTTP client | get, post, request |
| OpenSSL::SSL | Presentation | SSL/TLS encryption | SSLSocket, SSLContext |
Error Handling by Layer
| Error Class | Layer | Cause | Recovery Strategy |
|---|---|---|---|
| Errno::ECONNREFUSED | Transport | Connection refused | Verify server running, retry |
| Errno::ETIMEDOUT | Transport | Connection timeout | Check network, increase timeout |
| Errno::EHOSTUNREACH | Network | Host unreachable | Verify routing, check firewall |
| Errno::ENETUNREACH | Network | Network unreachable | Check network configuration |
| SocketError | Various | DNS or socket errors | Check hostname, network settings |
| OpenSSL::SSL::SSLError | Presentation | SSL/TLS errors | Verify certificates, cipher suites |
| Timeout::Error | Application | Operation timeout | Increase timeout, optimize operation |
Debugging Commands and Tools
| Tool | Layer | Purpose | Ruby Equivalent |
|---|---|---|---|
| ping | Network | Connectivity test | Net::Ping gem |
| traceroute | Network | Path discovery | None (use system command) |
| netstat | Transport | Connection status | Socket.getifaddrs |
| tcpdump | Data Link/Network | Packet capture | PacketFu gem |
| wireshark | Multiple | Protocol analysis | None (use external tool) |
| nslookup | Application | DNS resolution | Socket.getaddrinfo |
| telnet | Application | Protocol testing | Net::Telnet module |
Common Ruby Gems by Layer
| Gem | Layer | Purpose |
|---|---|---|
| httparty | Application | Simplified HTTP client |
| faraday | Application | HTTP middleware framework |
| rest-client | Application | REST API client |
| eventmachine | Transport | Event-driven I/O |
| async | Transport | Fiber-based concurrency |
| oj | Presentation | Fast JSON parser |
| msgpack | Presentation | Binary serialization |
| packetfu | Network/Transport | Packet crafting |
| net-ping | Network | ICMP ping implementation |