TCP/IP Model — MTU Mismatch Causes 100% Packet Loss
When large TCP transfers time out but small pings succeed, suspect MTU mismatch from VPN.
20+ years shipping production systems from the metal up. Lessons pulled from things that broke in production.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- TCP/IP is the four-layer networking model: Application, Transport, Internet, Network Access.
- Data flows down the stack (encapsulation) and up the stack (decapsulation).
- TCP provides reliable, ordered delivery with a 3-way handshake; UDP is faster but unreliable.
- Performance: TCP adds ~1 RTT for connection setup; UDP has zero setup cost.
- Production trap: Packet fragmentation at the Network Access layer can silently degrade TCP throughput.
The TCP/IP model is the architectural blueprint of the internet, defining how data moves from one device to another across networks. Unlike the OSI model's seven layers, TCP/IP collapses this into four: Application, Transport, Internet, and Network Access.
It exists because the internet needed a practical, battle-tested protocol stack that could handle unreliable hardware and scale globally — not a theoretical ideal. Every HTTP request, DNS lookup, or SMTP email you send is shaped by this model's rules for addressing, routing, and reliable delivery.
At its core, TCP/IP solves two fundamental problems: getting packets to the right destination (IP's job) and ensuring they arrive complete and in order (TCP's job). The Transport layer gives you a choice — TCP for guaranteed delivery (used by web, email, file transfers) or UDP for speed over reliability (used by VoIP, video streaming, DNS).
The Internet layer handles IP addressing and routing across heterogeneous networks, while the Application layer speaks protocols like HTTP/HTTPS, DNS, and SMTP that your software actually uses.
Where this model shines is in its real-world pragmatism. TCP's three-way handshake (SYN, SYN-ACK, ACK) establishes connections before data flows, preventing the chaos of blind packet injection. Encapsulation wraps each layer's data with headers — application data gets a TCP header, then an IP header, then a frame header — creating nested envelopes that routers and switches peel back.
When you hit an MTU mismatch, this encapsulation chain breaks: a router fragments or drops packets exceeding the link's maximum transmission unit, causing 100% loss for oversized packets. Tools like ping -M do or traceroute expose this by probing path MTU.
Don't use TCP/IP for real-time control systems or sensor networks where latency is critical — those use specialized stacks like CAN bus or Zigbee. For anything that touches the public internet, TCP/IP is non-negotiable. The model's genius is its separation of concerns: you can swap out Ethernet for Wi-Fi at the Network Access layer without rewriting your HTTP server.
Understanding this stack is what separates devs who debug packet loss from those who blindly blame 'the network.'
Think of the TCP/IP model like a postal service: your data is a letter that gets put into envelopes (headers) at each step — first a transport envelope, then an internet envelope, then a physical envelope — before being mailed. If one envelope is too big for the mail truck (MTU mismatch), the letter gets thrown away instead of delivered, causing 100% packet loss even though small postcards (pings) get through fine.
The TCP/IP stack is the backbone of modern internet communication. Every HTTP request, every DNS query, every real-time video stream — they all rely on the four layers working correctly. Yet most developers only interact with the Application layer. When a connection drops, latency spikes, or packets get lost, understanding the layers below is what separates a senior engineer from someone who needs to escalate.
This article breaks down each layer, shows you how data actually moves, and highlights the production pitfalls that emerge when a layer misbehaves.
What the TCP/IP Model Actually Defines
The TCP/IP model is the architectural framework that governs how data traverses the internet. It defines four abstraction layers — Application, Transport, Internet, and Network Access — each with specific protocols and responsibilities. The core mechanic is encapsulation: each layer adds its own header to the payload from the layer above, creating a nested packet structure that enables end-to-end communication across heterogeneous networks.
In practice, the Internet layer (IP) handles addressing and routing, while the Transport layer (TCP/UDP) manages reliability and port multiplexing. The Network Access layer deals with the physical medium and link-level framing. A critical property: the Maximum Transmission Unit (MTU) at the Network Access layer imposes a hard limit on the size of the IP packet. If a TCP segment exceeds the path MTU, it must be fragmented — or dropped if the Don't Fragment flag is set. This is where silent failures occur.
You use the TCP/IP model every time you send data over a network. Its layered design allows independent evolution of protocols — you can swap Ethernet for Wi-Fi without rewriting TCP. But the abstraction hides real constraints: MTU mismatches between layers cause 100% packet loss for oversized packets, a failure mode that remains invisible until you measure throughput or observe connection timeouts.
tracepath or ping -M do -s <size> before debugging higher-layer timeouts.The Four Layers
The four layers form a strict hierarchy. Each layer on the sender adds its own header (encapsulation). The receiver strips headers in reverse order (decapsulation). This design allows each layer to operate independently — you can replace Ethernet with Wi-Fi without touching the IP layer.
# The TCP/IP stack — what happens when you make an HTTP request # You write: import requests response = requests.get('https://thecodeforge.io/python/nested-loops/') # What actually happens across the TCP/IP layers: # LAYER 4 — APPLICATION (HTTP) # Your code creates an HTTP GET request: # GET /python/nested-loops/ HTTP/1.1 # Host: thecodeforge.io # Accept: text/html # LAYER 3 — TRANSPORT (TCP) # TCP wraps the HTTP data: # Source port: 54321 (ephemeral) Dest port: 443 (HTTPS) # Sequence number, acknowledgement number, flags # TCP ensures the HTTP data arrives complete and in order # LAYER 2 — INTERNET (IP) # IP wraps the TCP segment: # Source IP: 192.168.1.100 Dest IP: 104.26.10.33 # IP handles routing — gets the packet to the right server # LAYER 1 — NETWORK ACCESS (Ethernet/Wi-Fi) # Ethernet wraps the IP packet: # Source MAC: aa:bb:cc:dd:ee:ff Dest MAC: router's MAC # Handles physical transmission to the next hop print('Each layer wraps the layer above — unwrapped in reverse at destination')
TCP Three-Way Handshake
Before any data is sent, TCP establishes a connection with a three-way handshake. This adds one round trip of latency — the cost of reliability.
# TCP three-way handshake: # Client → Server: SYN (I want to connect, my seq=100) # Server → Client: SYN-ACK (OK, your seq+1=101, my seq=300) # Client → Server: ACK (Got it, your seq+1=301) # → Connection established, data can flow # TLS adds two more round trips on top of TCP: # TCP 3-way handshake (1 RTT) # TLS ClientHello / ServerHello (1 RTT) # TLS Finished / Application data (1 RTT) # Total: 3 RTTs before first HTTP byte # Why HTTP/3 uses QUIC instead of TCP: import socket # TCP socket: 3-way handshake before any data tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_sock.connect(('example.com', 80)) # handshake happens here # UDP socket: no handshake — send immediately udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udp_sock.sendto(b'data', ('example.com', 53)) # DNS uses UDP # No connection, no guarantee of delivery or order
TCP vs UDP — When to Use Each
Choosing between TCP and UDP comes down to tolerance for loss vs need for ordering. TCP handles retransmission and congestion control automatically, but it can create head-of-line blocking. UDP shifts those responsibilities to the application, which is why HTTP/3 (QUIC) builds reliability on top of UDP.
# TCP: reliable, ordered, connection-oriented # Use for: HTTP, HTTPS, email, file transfer, databases # - Guarantees all data arrives in order # - Retransmits lost packets # - Flow control and congestion control built in # - Cost: 3-way handshake, higher latency # UDP: unreliable, connectionless, fast # Use for: DNS, video streaming, online gaming, VoIP # - No handshake — send immediately # - No retransmission of lost packets # - Lower latency, no head-of-line blocking # - Application must handle ordering/reliability if needed import socket # DNS lookup — UDP (one-shot request/response, loss is handled by retry) def dns_query(domain): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(2) # DNS query format (simplified) sock.sendto(b'\x00\x01' + domain.encode(), ('8.8.8.8', 53)) data, _ = sock.recvfrom(1024) return data # For streaming video: UDP — a dropped frame is preferable to stopping to retransmit # For file download: TCP — every byte must arrive correctly
Encapsulation and Decapsulation in Action
Encapsulation is the process of wrapping data from a higher layer with a header from the layer below. Decapsulation is the reverse — each layer strips its own header and passes the payload up. This is how a single HTTP request turns into multiple Ethernet frames.
# Simulating encapsulation with the io.thecodeforge.network library from io.thecodeforge.network import Layer, ProtocolStack # Build an HTTP GET request http_data = b'GET /api/users HTTP/1.1\r\nHost: example.com\r\n\r\n' # Wrap in TCP segment tcp_segment = ProtocolStack.wrap( data=http_data, layer=Layer.TRANSPORT, src_port=54321, dst_port=80 ) # Wrap in IP packet ip_packet = ProtocolStack.wrap( data=tcp_segment, layer=Layer.INTERNET, src_ip='192.168.1.100', dst_ip='93.184.216.34' ) # Wrap in Ethernet frame ethernet_frame = ProtocolStack.wrap( data=ip_packet, layer=Layer.NETWORK_ACCESS, src_mac='aa:bb:cc:dd:ee:ff', dst_mac='00:11:22:33:44:55' ) print(f'Frame size: {len(ethernet_frame)} bytes') # At the receiver, decapsulation happens in reverse order
Application Layer Protocols: HTTP, DNS, SMTP
The Application layer is where most developers live. Each protocol uses either TCP or UDP underneath, but the choice affects performance and reliability. HTTP/1.1 uses TCP with persistent connections; DNS uses UDP for queries and TCP for zone transfers. Understanding which protocol runs on which transport helps you diagnose slowness.
# DNS query over UDP with io.thecodeforge.network from io.thecodeforge.network import dns_resolve # Resolve a domain name (UDP, single request) result = dns_resolve('thecodeforge.io', record_type='A') print(f'IP address: {result}') # e.g., 104.26.10.33 # HTTP GET over TCP (with 3-way handshake) from io.thecodeforge.network import http_get response = http_get('https://thecodeforge.io/python/nested-loops/') print(f'Status: {response.status_code}, Body length: {len(response.content)}')
The Internet Layer — Where IP Earns Its Keep
The Internet layer is the backbone of routing. It takes packets from the Transport layer and figures out how to get them across potentially dozens of routers to the destination. Its job is simple: addressing, routing, and fragmentation. Nothing else.
IP is the star here. It adds a header with source and destination addresses. That header also carries a TTL field — a production trap where old packets loop forever if you don't decrement it. Once TTL hits zero, every router along the path drops the packet and sends back an ICMP "Time Exceeded" message. That's how traceroute works.
Fragmentation happens when a packet exceeds the MTU of the next hop. The IP layer splits it, and the receiver reassembles it. Do not rely on this for high-throughput workloads. Fragmentation kills performance. Modern systems use Path MTU Discovery and set the Don't Fragment (DF) flag to avoid it entirely.
// io.thecodeforge — cs-fundamentals tutorial import struct import socket def check_mtu(target_host: str, probe_size: int = 1500): """Check if packets get fragmented to target host.""" icmp_id = 12345 payload = b'A' * (probe_size - 28) # 28 bytes for IP + ICMP header header = struct.pack('!BBHHH', 8, 0, 0, icmp_id, 1) packet = header + payload sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) try: sock.sendto(packet, (target_host, 1)) sock.settimeout(2) data, addr = sock.recvfrom(65535) ip_header = data[0:20] flags_fragment = struct.unpack('!H', ip_header[6:8])[0] df_bit = (flags_fragment & 0x4000) != 0 fragment_offset = flags_fragment & 0x1FFF if df_bit: print(f"Fragmentation NOT allowed. Offset: {fragment_offset}") else: print(f"Fragmentation possible. Offset: {fragment_offset}") except socket.timeout: print("No response — packet likely filtered.") finally: sock.close() check_mtu("8.8.8.8", 1500)
ping -M do -s 1472 <gateway> to test actual path MTU.The Network Access Layer — The Forgotten Concrete
Most developers ignore this layer until a switch burns or a cable gets chewed by a rodent. The Network Access layer (also called Link layer) is where bits hit the wire. It handles MAC addressing, framing, and physical transport. Ethernet frames are the real unit of work here.
ARP (Address Resolution Protocol) lives here. When you ping a local IP, the kernel shouts "Who has 192.168.1.15?" via broadcast. The owner responds with its MAC address. ARP poisoning is how attackers man-in-the-middle on a LAN — they answer for everyone.
Every frame has an Ethernet header with source MAC, destination MAC, and EtherType. The EtherType tells the receiver which protocol is inside (0x0800 for IP, 0x0806 for ARP). This is why you can have multiple network protocols on the same wire without collision.
You don't touch this layer daily. But when you're debugging a flapping interface or a bad cable. Remember: CRC errors at this layer mean packet corruption downstream. That's not a TCP retransmit bug. That's a bad SFP transceiver.
// io.thecodeforge — cs-fundamentals tutorial import subprocess import json def get_arp_table() -> dict: """Return parsed ARP table from system.""" result = subprocess.run(['arp', '-a'], capture_output=True, text=True) entries = {} for line in result.stdout.split('\n'): if '(' in line and ')' in line: ip = line.split('(')[1].split(')')[0] parts = line.split() # Typical format: ? (192.168.1.1) at aa:bb:cc:dd:ee:ff [ether] on eth0 mac = parts[3] if len(parts) > 3 else 'incomplete' entries[ip] = mac return entries arp_data = get_arp_table() print(json.dumps(arp_data, indent=2)) # Quick check: do any IPs show 'incomplete'? for ip, mac in arp_data.items(): if mac == 'incomplete': print(f"[!] ARP resolution failed for {ip} — check physical link")
arp -a. An incomplete entry means the host isn't responding. That's faster than running a tcpdump and guessing.Why TCP/IP Won Over OSI — Real Talk
The OSI model has seven layers. It's beautiful, academic, and mostly unused in production. TCP/IP has four layers. It's pragmatic, battle-tested, and runs the entire Internet. The difference is not just complexity.
OSI was designed by committee. TCP/IP was built by engineers who needed shit to work. OSI's Presentation layer is supposed to handle encryption and encoding. In practice, TLS lives at the Application layer, and encryption is handled by libraries like OpenSSL. The Session layer? TCP already handles sessions with SYN/ACK. Nobody needs a separate layer for something TCP does.
What matters is that TCP/IP maps directly to how hardware works. The Network Access layer is your NIC and cable. The Internet layer is your router's routing table. The Transport layer is your kernel's TCP stack. The Application layer is your code. That's it.
OSI survives in textbooks because it's easier to teach. But when you're debugging a production outage, you think in TCP/IP. You check the link first (Layer 2), then routing (Layer 3), then the socket (Layer 4), then the app (Layer 7). Anything else is noise.
// io.thecodeforge — cs-fundamentals tutorial def tcpip_production_debug(symptom: str): """Production debugging flow: TCP/IP style.""" debug_steps = { "timeout reaching external host": [ "1. Ping local link (Layer 2)", "2. Ping default gateway (Layer 3)", "3. Traceroute to external IP (Layer 3)", "4. Curl with verbose (Layer 4 & 7)" ], "connection refused": [ "1. Check if port is listening: ss -tlnp", "2. Check firewall: iptables -L", "3. Check SELinux: audit.log", "4. Check application logs" ] } symptom = symptom.lower().replace(" connection", "").strip() if symptom in debug_steps: print("\n".join(debug_steps[symptom])) else: print("Unknown symptom. Start with ping, traceroute, then tcpdump.") tcpip_production_debug("timeout reaching external host")
Subnetting — Carving IP Networks Into Usable Blocks
IP addresses alone don't scale. Subnetting splits a network into smaller, manageable segments. Why? Efficiency, security, and traffic isolation. A subnet mask defines which part of an IP is network and which is host. For example, /24 means the first 24 bits are the network. Subnetting reduces broadcast domains — fewer devices see each other's noise. It also conserves IPs: instead of a /16 for 50 devices, you allocate a /26. The math: 2^(32 - mask bits) gives total addresses; subtract two for network and broadcast IDs to get usable hosts. When designing subnets, align with physical topology and team boundaries. Common trap: misapplying the — for example, thinking /30 gives 4 usable addresses (it gives 2). Subnetting directly fuels CIDR, VLSM, and every modern routing table. Master it and you control traffic, not the other way.
// io.thecodeforge — cs-fundamentals tutorial def usable_hosts(mask_bits): return 2 ** (32 - mask_bits) - 2 for prefix in [24, 26, 30]: print(f"/{prefix}: {usable_hosts(prefix)} usable hosts")
VLAN — Virtual Separation on a Single Switch
A VLAN (Virtual Local Area Network) lets one physical switch act like many. Why? Without VLANs, all ports share a single broadcast domain — ARP storms and security leaks. VLANs isolate traffic at Layer 2. Each VLAN has its own broadcast domain, its own IP subnet. Hosts in VLAN 10 can't speak to VLAN 20 unless a router (or Layer 3 switch) bridges them. Configuration is simple: assign ports to a VLAN ID (1–4094). Trunk ports carry multiple VLANs using 802.1Q tags — a 4-byte header inserted into Ethernet frames. Tagged frames leave VLAN membership intact across switches. Real-world: separate guest Wi-Fi, IoT, and corporate traffic on one cable plant. Common mistake: forgetting to prune unused VLANs from trunks — they leak broadcast traffic. VLANs are the backbone of network segmentation. No VLANs, no security.
// io.thecodeforge — cs-fundamentals tutorial
vlan_config = {
'VLAN10': '192.168.10.0/24',
'VLAN20': '192.168.20.0/24',
}
for vlan, subnet in vlan_config.items():
print(f"{vlan} -> {subnet} (isolated broadcast domain)")Spanning Tree Protocol — Preventing Layer 2 Loops
Ethernet loops kill networks — broadcast storms, MAC table thrash, total collapse. Spanning Tree Protocol (STP) prevents this. Why? Redundant links are needed for resilience, but without STP, frames circulate forever. STP elects a root bridge (lowest bridge ID wins), then computes shortest paths to it. Blocked ports are backup — they stay silent until a link fails. Rapid PVST+ (Per-VLAN Spanning Tree Plus) is the Cisco standard: one STP instance per VLAN, converging in under a second. Port roles: root, designated, alternate, backup. Key tunable: port priority and path cost — lower cost wins. Common mistake: neglecting UDLD (Unidirectional Link Detection). A unidirectional link doesn't break STP — it creates a forwarding black hole. Always enable BPDU guard on access ports to block rogue switches. STP is invisible until it saves your network.
// io.thecodeforge — cs-fundamentals tutorial class Port: def __init__(self, cost): self.cost = cost self.state = 'blocking' root_port = Port(4) print(f"Root path cost: {root_port.cost}, state: {root_port.state}")
Advantages of the TCP/IP Model
The TCP/IP model's primary advantage is its open, standards-based architecture, which ensures interoperability across diverse hardware and software. Unlike proprietary protocols, any vendor can implement TCP/IP without licensing fees, fostering the global internet. Its modular design separates concerns: the application layer handles data formatting (e.g., HTTP), while the transport layer (TCP/UDP) manages reliability or speed. This layering allows seamless upgrades—replacing Ethernet with Wi-Fi at the network access layer doesn't break TCP or IP. TCP/IP is also remarkably resilient; if a router fails, IP dynamically reroutes packets via alternative paths, as seen in BGP routing. The model's simplicity (four layers vs. OSI's seven) reduces overhead and debugging complexity. For example, diagnosing a slow web app often starts at the application layer with HTTP status codes, then drops to TCP retransmissions. This pragmatic approach made TCP/IP the backbone of the internet, supporting everything from email to streaming video with built-in error checking (TCP) or low-latency delivery (UDP). Its real-world testing at scale proves its durability; the core protocols have remained stable for decades while absorbing new layers like TLS for security.
// io.thecodeforge — cs-fundamentals tutorial import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("example.com", 80)) client.send(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") response = client.recv(4096).decode() print("TCP ensured reliability:", response[:100] if response else "failed") client.close()
Limitations of the TCP/IP Model
Despite its dominance, the TCP/IP model has notable limitations. First, it lacks a clear separation between the physical and data link layers, which the OSI model handles in layers 1 and 2. This conflation can blur troubleshooting—for instance, a packet collision at the Ethernet level (layer 2) is often misdiagnosed as an IP addressing issue (layer 3). Second, TCP/IP has weak built-in security; the original design assumed trusted networks, so protocols like IP, TCP, and UDP lack encryption. This forced bolt-on solutions like TLS, IPsec, and HTTPS, adding complexity and overhead. Third, the model struggles with real-time applications. TCP's retransmission logic introduces jitter for VoIP or gaming, while UDP offers no congestion control, potentially flooding networks. Fourth, header overhead is significant—a 40-byte TCP header per packet wastes bandwidth in IoT or microservice environments (e.g., MQTT over TCP). Fifth, the model doesn't natively support mobility; moving a device between networks often breaks active TCP connections (though Mobile IP works around this). Finally, the rigid 32-bit IP address space (IPv4) proved too small, requiring NAT and eventually IPv6, which added migration pain. These gaps show that TCP/IP traded elegance for practicality.
// io.thecodeforge — cs-fundamentals tutorial import socket, time udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for _ in range(5): start = time.time() udp.sendto(b"ping", ("8.8.8.8", 53)) print(f"UDP jitter: {(time.time() - start)*1000:.2f}ms") udp.close()
MTU Mismatch Causes 100% Packet Loss for Large Files
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360. Verified with ping -M do -s 1360.- Always verify path MTU when large transfers fail but small ones succeed.
- MTU mismatches are invisible to standard monitoring tools.
- Use
traceroute --mtuto discover the smallest MTU along the path.
tcpdump -nn -i eth0 host <target ip> to see if SYN packets are being retransmitted. Then check firewall logs and MTU path.netstat -s | grep retransmit. Use ss -ti to see TCP congestion window and RTT.dig @<dns server> <domain> to verify. Check if the DNS server is reachable over UDP on port 53.ss -tlnp | grep <port>curl -v telnet://<host>:<port>traceroute -n <target_ip>ping -c 10 <target_ip> # check jittersysctl net.ipv4.tcp_window_scaling # should be 1ss -ti | grep cwnd # see congestion windowsysctl -w net.core.rmem_max=26214400 net.core.wmem_max=26214400| File | Command / Code | Purpose |
|---|---|---|
| PacketFragmentationCheck.py | def check_mtu(target_host: str, probe_size: int = 1500): | The Internet Layer |
| ArpTableInspector.py | def get_arp_table() -> dict: | The Network Access Layer |
| OsiVsTcpIp.py | def tcpip_production_debug(symptom: str): | Why TCP/IP Won Over OSI |
| subnet_calc.py | def usable_hosts(mask_bits): | Subnetting |
| vlan_setup.py | vlan_config = { | VLAN |
| stp_sim.py | class Port: | Spanning Tree Protocol |
| tcp_advantage_demo.py | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | Advantages of the TCP/IP Model |
| tcp_jitter_demo.py | udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | Limitations of the TCP/IP Model |
Key takeaways
Common mistakes to avoid
3 patternsAssuming all network problems are code bugs
Misconfiguring TCP keepalive values
sysctl -w net.ipv4.tcp_keepalive_time=300 and enable keepalive in application code.Using UDP without application-level reliability
Interview Questions on This Topic
What are the four layers of the TCP/IP model?
What is the TCP three-way handshake?
When would you choose UDP over TCP?
Explain TCP head-of-line blocking and how QUIC solves it.
Frequently Asked Questions
TCP delivers data in order. If one packet is lost, subsequent packets — even if they arrived — must wait until the lost packet is retransmitted. This is head-of-line blocking. In HTTP/2 over TCP, multiple streams share one TCP connection, so one lost packet blocks all streams. HTTP/3 uses QUIC (over UDP) where each stream is independent — a lost packet blocks only that stream.
An IP address identifies a machine on the network. A port identifies a specific application on that machine. The combination (IP + port) is called a socket address. Port 80 is HTTP, 443 is HTTPS, 5432 is PostgreSQL, 3306 is MySQL. When you run a server on port 8000 and connect from a browser, the server gets connection from your IP + ephemeral port (e.g., 54321).
When an IP packet exceeds the Maximum Transmission Unit (MTU) of the next hop, the IP layer fragments it into smaller packets. Each fragment has its own IP header indicating the offset. The receiver reassembles them. Fragmentation increases overhead and is often a source of issues — it's better to use Path MTU Discovery to avoid fragmentation.
The Network Access layer handles the physical transmission of data over the medium (e.g., Ethernet, Wi-Fi, fiber). It deals with MAC addresses, framing, error detection (CRC), and media access control. It's the only layer that actually touches the wire.
20+ years shipping production systems from the metal up. Lessons pulled from things that broke in production.
That's Computer Networks. Mark it forged?
7 min read · try the examples if you haven't