The OSI Model
The OSI (Open Systems Interconnection) model divides network communication into 7 abstract layers. Each layer uses the services of the layer below and provides services to the layer above. While real-world protocols don't map perfectly to OSI, it's an invaluable conceptual framework for understanding and troubleshooting network issues.
| # | Layer | Unit | Protocols / Examples | Key Concern |
|---|---|---|---|---|
| 7 | Application | Data | HTTP, HTTPS, DNS, SMTP, SSH, FTP | Application logic |
| 6 | Presentation | Data | TLS/SSL, JPEG, Unicode encoding | Data format & encryption |
| 5 | Session | Data | NetBIOS, RPC, TLS session management | Connection state |
| 4 | Transport | Segment | TCP, UDP, SCTP | End-to-end delivery, ports |
| 3 | Network | Packet | IPv4, IPv6, ICMP, OSPF, BGP | Routing between networks |
| 2 | Data Link | Frame | Ethernet, WiFi (802.11), ARP | Delivery within a network segment |
| 1 | Physical | Bit | RJ45, fiber optic, radio waves, USB | Physical signal transmission |
The TCP/IP Stack
The real-world Internet uses the TCP/IP model (4 layers), which maps to the OSI model but consolidates some layers. When you open a web page, each layer adds its header as the data travels down the stack (encapsulation), and each layer strips its header as the data travels up on the receiving side.
flowchart TD
subgraph Sending["Sending Side — Encapsulation"]
App["Application Layer\nHTTP: GET / HTTP/1.1\nHost: example.com"]
Trans["Transport Layer\nTCP Segment: Src Port 54321 → Dst Port 443\n+ Seq/Ack numbers, flags"]
Net["Network Layer\nIP Packet: Src 192.168.1.10 → Dst 93.184.216.34\n+ TTL, protocol=TCP"]
Link["Data Link Layer\nEthernet Frame: Src MAC → Dst MAC\n+ EtherType=IPv4"]
Phy["Physical Layer\nElectrical/optical signal"]
end
App -->|"Add TCP header"| Trans
Trans -->|"Add IP header"| Net
Net -->|"Add Ethernet header+trailer"| Link
Link -->|"Convert to bits"| Phy
style App fill:#3B9797,color:#fff
style Trans fill:#16476A,color:#fff
style Net fill:#132440,color:#fff
style Link fill:#BF092F,color:#fff
IP Addressing
An IPv4 address is a 32-bit number written as 4 octets in dotted decimal: 192.168.1.10. An IPv6 address is 128 bits written as 8 groups of 4 hex digits: 2001:0db8:85a3::8a2e:0370:7334.
Every IP address has two parts: the network prefix (identifies the network) and the host portion (identifies the device within that network). The division is determined by the subnet mask.
CIDR Notation & Subnetting
CIDR (Classless Inter-Domain Routing) notation appends a prefix length after the address: 192.168.1.0/24 means the first 24 bits are the network prefix (leaves 8 bits for hosts = 256 addresses, 254 usable).
# ipcalc — show subnet details (install: apt install ipcalc)
ipcalc 192.168.1.0/24 2>/dev/null || echo "ipcalc not installed, use python3:"
# Python subnet calculator
python3 -c "
import ipaddress
# Analyse a network
net = ipaddress.IPv4Network('192.168.1.0/24')
print(f'Network: {net.network_address}')
print(f'Broadcast: {net.broadcast_address}')
print(f'Netmask: {net.netmask}')
print(f'Hosts: {net.num_addresses - 2}')
print(f'First host: {list(net.hosts())[0]}')
print(f'Last host: {list(net.hosts())[-1]}')
print()
# Check if an IP is in a network
ip = ipaddress.IPv4Address('192.168.1.100')
print(f'192.168.1.100 in 192.168.1.0/24: {ip in net}')
print(f'192.168.2.100 in 192.168.1.0/24: {ipaddress.IPv4Address(\"192.168.2.100\") in net}')
"
# Common subnet sizes
python3 -c "
import ipaddress
subnets = ['/8', '/16', '/24', '/25', '/26', '/28', '/30']
print(f'{'CIDR':>8} {'Mask':>15} {'Hosts':>8}')
for prefix in subnets:
n = ipaddress.IPv4Network(f'10.0.0.0{prefix}')
print(f'{prefix:>8} {str(n.netmask):>15} {n.num_addresses - 2:>8}')
"
Private Address Ranges
| Range | CIDR | Addresses | Use |
|---|---|---|---|
| 10.0.0.0 – 10.255.255.255 | 10.0.0.0/8 | 16.7M | Large corp/cloud VPCs (AWS default VPC: 172.31.0.0/16) |
| 172.16.0.0 – 172.31.255.255 | 172.16.0.0/12 | 1.05M | Docker default bridge: 172.17.0.0/16 |
| 192.168.0.0 – 192.168.255.255 | 192.168.0.0/16 | 65,536 | Home routers |
| 127.0.0.0/8 | 127.0.0.0/8 | 16.7M | Loopback (localhost) |
| 169.254.0.0/16 | — | 65,536 | Link-local (APIPA — no DHCP) |
ARP — Address Resolution Protocol
IP addresses identify devices logically (network layer). But to actually send a frame on Ethernet, you need the physical MAC address of the next hop. ARP resolves IP→MAC within a local network segment.
sequenceDiagram
participant A as Host A (192.168.1.10)
participant BC as Everyone on LAN (broadcast)
participant B as Host B (192.168.1.20)
A->>BC: ARP Request (broadcast): "Who has 192.168.1.20? Tell 192.168.1.10"
Note over BC: All hosts receive this frame
B->>A: ARP Reply (unicast): "192.168.1.20 is at AA:BB:CC:DD:EE:FF"
A->>A: Cache IP→MAC in ARP table (TTL ~20 min)
A->>B: Can now send Ethernet frames directly to B's MAC
# View the ARP cache
arp -n # Traditional arp command
ip neigh show # Modern ip command (preferred)
ip neigh show dev eth0 # For a specific interface
# ARP cache entries:
# 192.168.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE
# 192.168.1.20 dev eth0 lladdr ... STALE (entry aging out)
# Status: REACHABLE/STALE/FAILED/DELAY/PROBE
# Send ARP request manually (requires root or CAP_NET_RAW)
arping -c 3 192.168.1.1 2>/dev/null || echo "arping not available"
# Clear ARP cache
sudo ip neigh flush all 2>/dev/null || echo "requires root"
The Linux Network Stack
When a packet arrives at a network interface card (NIC), it travels up the Linux network stack. When you call send(), it travels down. This stack is where netfilter (the basis for iptables and nftables) hooks in to inspect and potentially modify or drop packets.
# View network stack statistics
cat /proc/net/dev # Per-interface RX/TX stats (bytes, packets, errors)
netstat -s 2>/dev/null | head -20 # Or: ss statistics
cat /proc/net/sockstat # Socket usage summary
# View kernel networking tunables
sysctl net.core.somaxconn # Max listen backlog (default: 4096)
sysctl net.ipv4.tcp_max_syn_backlog # SYN backlog
sysctl net.ipv4.ip_forward # IP forwarding (needed for routing/containers)
# Network buffer sizes (important for high-throughput)
sysctl net.core.rmem_max # Max receive buffer
sysctl net.core.wmem_max # Max send buffer
sysctl net.ipv4.tcp_rmem # TCP receive buffer (min/default/max)
sysctl net.ipv4.tcp_wmem # TCP send buffer (min/default/max)
Network Interfaces
# === ip command (modern, preferred over ifconfig) ===
# List all interfaces
ip link show
ip -br link show # Brief format: name, state, MAC
# View IP addresses
ip addr show
ip -br addr show # Brief: name, state, IPs
# Bring interface up/down
sudo ip link set eth0 up
sudo ip link set eth0 down
# Add/remove an IP address (temporary — lost on reboot)
sudo ip addr add 192.168.1.50/24 dev eth0
sudo ip addr del 192.168.1.50/24 dev eth0
# View routing table
ip route show
ip route get 8.8.8.8 # Show how a specific address would be reached
ip route get 8.8.8.8 | grep "via\|src" # Gateway and source IP
# Common virtual interfaces
# lo — loopback (127.0.0.1)
# eth0/ens3 — physical ethernet
# wlan0 — WiFi
# docker0 — Docker bridge network
# veth* — Virtual ethernet pairs (used by containers)
# tun0/tap0 — VPN/tunnel interfaces
# br-* — Linux bridge interfaces
# Interface statistics
ip -s link show eth0 # RX/TX bytes, errors, drops
ethtool eth0 2>/dev/null | grep -E "Speed|Duplex|Link" # Physical link info
Essential Networking Tools
# === ping — ICMP echo test ===
ping 8.8.8.8 -c 4 # 4 pings to Google DNS
ping -c 3 -W 1 192.168.1.1 # Timeout 1s per ping
ping6 ::1 # Ping IPv6 loopback
# === traceroute — hop-by-hop path tracing ===
traceroute 8.8.8.8 # Shows each router hop + latency
traceroute -T 8.8.8.8 # TCP traceroute (bypasses ICMP blocks)
tracepath 8.8.8.8 # Similar, no root needed
# === dig — DNS queries (covered in Part 12) ===
dig google.com # A record
dig +short google.com A # Just the IP address
# === curl — HTTP requests ===
curl -v https://example.com # Verbose (shows TLS, headers, body)
curl -I https://example.com # Headers only
curl -s -o /dev/null -w "%{http_code}" https://example.com # Just status code
curl --connect-timeout 5 https://example.com # Timeout
# === wget — download files ===
wget -q https://example.com/file.tar.gz -O /tmp/file.tar.gz
wget -c https://example.com/large-file.tar.gz # Resume interrupted download
# === nc (netcat) — Swiss army knife ===
nc -zv 192.168.1.1 22 # TCP port check (z=scan, v=verbose)
nc -zv -w 3 google.com 443 # HTTPS port with 3s timeout
echo -e "HEAD / HTTP/1.0\r\n\r\n" | nc google.com 80 # Raw HTTP request
nc -l -p 9999 > received.txt & # Listen on port 9999, save to file
# === ss — socket statistics (modern netstat) ===
ss -tlnp # TCP listening sockets + process names
ss -tulnp # TCP+UDP listening
ss -tnp state established # Established TCP connections
ss -tnp 'dst 8.8.8.8' # Connections to specific IP
# Bandwidth monitoring
iftop -n 2>/dev/null & # Real-time bandwidth per connection
watch -n 1 "cat /proc/net/dev | grep -v lo | awk '{print \$1, \$2, \$10}'"
How Docker Container Networking Works at the L2/L3 Level
When Docker creates a container: (1) It creates a veth pair — two virtual ethernet interfaces connected at the kernel level; (2) One end (vethXXXXXX) stays on the host, attached to the Docker bridge (docker0); (3) The other end (renamed to eth0) goes inside the container's network namespace; (4) Docker assigns a private IP (e.g., 172.17.0.5/16) from the bridge subnet; (5) ARP and IP routing work normally within this virtual network. The container sees a normal ethernet interface; the host sees virtual interfaces on a bridge. Network namespaces (Part 21) are what make the container's network stack completely isolated from the host's.
Exercises
# Exercise 1: Network inspection
ip addr show # List all interfaces and IPs
ip route show # View routing table
ip neigh show # View ARP cache
# Exercise 2: Subnetting practice
python3 -c "
import ipaddress
# Divide 10.0.0.0/16 into 4 equal subnets
network = ipaddress.IPv4Network('10.0.0.0/16')
subnets = list(network.subnets(prefixlen_diff=2)) # /18 subnets
for i, subnet in enumerate(subnets):
print(f'Subnet {i+1}: {subnet} — {subnet.num_addresses-2} hosts')
"
# Exercise 3: Port scanning
for port in 22 80 443 3306 5432; do
nc -zv -w 1 localhost $port 2>&1 | grep -E "open|refused|succeeded"
done
# Exercise 4: Trace a packet route
traceroute -m 10 8.8.8.8 2>/dev/null | head -10
# Exercise 5: Show listening services
ss -tlnp | grep LISTEN
# Which ports are open on your machine?
Conclusion & Next Steps
The OSI and TCP/IP models give you a vocabulary and mental framework for every network problem. IP addressing and CIDR subnetting are fundamental to cloud VPC design, Kubernetes networking, and firewall rules. ARP bridges the logical (IP) and physical (MAC) worlds. The Linux network stack is where all of this lives — and the tools in this article let you observe it in real time.