The OSI & TCP/IP Models — How Data Travels
What you'll learn
- Why networks are built in layers, and what each layer is responsible for
- The 7-layer OSI model and the 4-layer TCP/IP model, and how they line up
- Encapsulation: how your data is wrapped in headers on the way down and unwrapped on the way up
- What changes (and what does not) when a packet crosses a router
- You will build three small tools: a layer lookup, a frame-overhead calculator and an Ethernet header parser
The postal system analogy 📬
Imagine you want to send a birthday card to a friend in another country. You do not drive it there yourself. You write the card (the message), put it in an envelope with the friend's name (who it is for), and hand it over. The post office puts that envelope in a bag labelled with a city. The bag goes on a truck labelled with the next sorting centre. Each step only cares about its own label, and nobody on the truck reads your card.
Networks work the same way. Sending a web page involves dozens of small jobs: turning bits into electrical or radio signals, finding the next device, finding the right computer anywhere on Earth, making sure nothing is lost, and finally speaking the language of the application. Instead of one giant program that does everything, we split the work into layers. Each layer does one job, talks only to the layer directly above and below it, and can be replaced without breaking the others. That is why your phone can use Wi-Fi at home and 5G outside, and the same apps keep working.
The seven layers of OSI
The OSI model (Open Systems Interconnection) is a reference model: a shared vocabulary for talking about networks. Engineers say "that is a layer 2 problem" all day, so you need to know the numbers.
7 Application what the user's program speaks HTTP, DNS, SMTP, SSH
6 Presentation data format, encoding, encryption
5 Session starting, keeping, ending dialogues
4 Transport program to program, ports TCP, UDP
3 Network host to host across networks IP, ICMP (router)
2 Data Link neighbour to neighbour Ethernet, Wi-Fi (switch, MAC)
1 Physical bits as signals on a medium cable, radio, fibre (hub)
A classic memory trick, from layer 7 down to 1: All People Seem To Need Data Processing. Or from 1 up: Please Do Not Throw Sausage Pizza Away.
Each layer has its own address and its own name for a chunk of data (the PDU, protocol data unit):
- Layer 2 uses MAC addresses (48 bits, like
02:00:00:aa:00:01). Chunk name: frame. - Layer 3 uses IP addresses (32 bits for IPv4, 128 for IPv6). Chunk name: packet.
- Layer 4 uses port numbers (16 bits, 0 to 65535). Chunk name: segment for TCP, datagram for UDP.
- Layers 5-7 just call it data or a message.
And each layer has typical devices: a hub repeats bits to every port (layer 1), a switch forwards frames using MAC addresses (layer 2), a router forwards packets using IP addresses (layer 3).
The TCP/IP model: what the Internet actually uses
The Internet was built with the simpler TCP/IP model, which has four layers (some books split the bottom one in two and show five):
TCP/IP OSI equivalent
Application 7 + 6 + 5 (HTTP, DNS, TLS, SSH ...)
Transport 4 (TCP, UDP)
Internet 3 (IP, ICMP)
Link 2 + 1 (Ethernet, Wi-Fi)
The OSI model is the one used for discussion and exams; TCP/IP is the one implemented in your operating system. Real protocols do not always fit neatly: TLS sits somewhere between OSI layers 4 and 7 depending on who you ask, and ARP is usually placed between 2 and 3. Do not argue about it in an interview; explain what the protocol does and say where you would place it and why.
Lab exercise 1
PythonGraded in Code LabBuild a layer lookup. Each input line is the name of a protocol or device. Print the OSI layer it works at, using this table (case-insensitive):
- Layer 7 (Application):
http,https,dns,ssh,smtp - Layer 4 (Transport):
tcp,udp - Layer 3 (Network):
ip,icmp,router - Layer 2 (Data Link):
ethernet,switch - Layer 1 (Physical):
hub,repeater
Layer names are: 7 Application, 6 Presentation, 5 Session, 4 Transport, 3 Network, 2 Data Link, 1 Physical.
Output format: one line per input line, <name as typed> -> <number> <layer name>. Names not in the table print <name as typed> -> unknown. Skip blank lines.
Example
Input:
TCP
router
laser
Output:
TCP -> 4 Transport
router -> 3 Network
laser -> unknown
- Application-layer protocols
- Devices and lower layers
- Transport and network protocols
- Unknown names and blank lines
- Empty input prints nothing
Example run
- Input
HTTP dns SSH
- Expected output
HTTP -> 7 Application dns -> 7 Application SSH -> 7 Application
import sys
LAYER_NAMES = {
7: "Application",
6: "Presentation",
5: "Session",
4: "Transport",
3: "Network",
2: "Data Link",
1: "Physical",
}
More lines in Code Lab: 10
Hints and instant feedback are in Code Lab. Optional practice: it does not block finishing the lesson.
JavaScript and Python also run in your browser without an account; other languages use the secure server runner and need a free account.
Encapsulation: envelopes inside envelopes
When your browser sends a request, the data travels down the stack. At each layer the sender adds a header (and the link layer also adds a trailer) that the same layer on the receiving side will read. This wrapping is called encapsulation; the receiver reverses it, which is decapsulation.
Application [ HTTP request ]
Transport [ TCP header | HTTP request ] segment
Network [ IP header | TCP header | HTTP request ] packet
Link [ Eth header | IP header | TCP header | HTTP request | FCS ] frame
Try it. This demo wraps a short message the way the stack does, using documentation-reserved addresses (they are guaranteed never to belong to a real host).
Read the output from the inside out: the HTTP request never changes, each layer just adds a labelled envelope around it. The receiver removes the envelopes in the opposite order. Change the ports or addresses and run it again.
message = "GET / HTTP/1.1"
def wrap(layer, header, payload):
return "[" + layer + " " + header + " | " + payload + "]"
# Going DOWN the stack: each layer adds its own header.
segment = wrap("TCP", "src_port=51514 dst_port=80", message)
packet = wrap("IP", "src=198.51.100.10 dst=203.0.113.80", segment)
frame = wrap("Ethernet", "src=02:00:00:aa:00:01 dst=02:00:00:aa:00:02", packet)
print("segment:", segment)
print("packet: ", packet)
print("frame: ", frame)
# Going UP the stack: the receiver peels the layers off in reverse order.
inner = frame
for layer in ["Ethernet", "IP", "TCP"]:
inner = inner[inner.index("|") + 2 : -1]
print("after", layer, "->", inner)
Headers cost bytes: MTU and MSS
Every layer's header is overhead. A typical Ethernet II frame carries:
- 14-byte header: destination MAC (6) + source MAC (6) + EtherType (2), which says what the payload is (
0x0800= IPv4,0x0806= ARP,0x86DD= IPv6) - the payload, at most 1500 bytes; this limit is the MTU (maximum transmission unit)
- a 4-byte FCS (frame check sequence), a CRC that lets the receiver detect corruption
A frame is never shorter than 64 bytes (header, payload and FCS together). If your payload is tiny, the sender pads it. The maximum is therefore 14 + 1500 + 4 = 1518 bytes. (The preamble and the gap between frames also use the wire, but they are not counted in the frame.)
Inside the 1500-byte MTU sits an IP packet: 20-byte IPv4 header (without options) + the transport segment. For TCP, add a 20-byte TCP header (without options). That leaves 1500 - 20 - 20 = 1460 bytes of your data per frame. This number is the MSS (maximum segment size) you will see in packet captures. To send 100 KB the sender needs many frames, and every frame pays the header tax again.
Now you compute that tax yourself.
Lab exercise 2
JavaScript (Node.js)Graded in Code LabWrite a frame overhead calculator. Each input line is the number of bytes of application data sent over TCP (a non-negative integer). Split the data into the fewest frames possible and report the cost.
Rules (the same as the lesson):
- each frame carries at most 1460 bytes of data (MSS)
- every frame has Ethernet header 14 + IP header 20 + TCP header 20 + FCS 4 = 58 bytes of overhead
- a frame is never smaller than 64 bytes in total; smaller ones are padded up to 64
- 0 bytes of data means 0 frames
Output: for each input line print frames=<n> wire=<total bytes on the wire> overhead=<wire minus data>.
Example
Input:
1460
1461
Output:
frames=1 wire=1518 overhead=58
frames=2 wire=1582 overhead=121
(1461 bytes = one full frame of 1518 bytes + one frame with 1 byte of data: 59 bytes padded to 64.)
- Exactly one full frame
- One byte over: second frame is padded
- Tiny payload is padded to 64 bytes
- Zero bytes needs no frames
- Several inputs, including 6 bytes (exactly 64 on the wire)
- A 100,000-byte transfer
Example run
- Input
1460
- Expected output
frames=1 wire=1518 overhead=58
const MSS = 1460; // max data bytes per frame
const OVERHEAD = 58; // Ethernet 14 + IP 20 + TCP 20 + FCS 4
const MIN_FRAME = 64; // smaller frames are padded to this size
const lines = require("fs").readFileSync(0, "utf8").split("\n");
for (const line of lines) {
if (line.trim() === "") continue;
const data = Number(line.trim());
// TODO: work out how many frames are needed, the bytes on the wire
// (remember the 64-byte minimum per frame) and the overhead,
// then print: frames=<n> wire=<bytes> overhead=<bytes>More lines in Code Lab: 1
Hints and instant feedback are in Code Lab. Optional practice: it does not block finishing the lesson.
JavaScript and Python also run in your browser without an account; other languages use the secure server runner and need a free account.
What changes at each hop?
A packet from your laptop to a server crosses several networks. Two kinds of addresses are involved, and they behave very differently:
- The IP addresses (layer 3) say where the packet is ultimately going. They stay the same from end to end. (NAT, which you will meet later, is the main exception.) Only the TTL (time to live) field changes: every router subtracts 1, and if it reaches 0 the packet is dropped. That stops packets from looping forever.
- The MAC addresses (layer 2) say who should receive the frame on this one link. Each router removes the old frame, looks at the IP packet, picks the next hop, and builds a new frame with new MAC addresses for the next link.
Run the simulation and watch which fields change.
The MAC pair changes on every link, the IP pair never does, and the TTL drops by one per router. This is why a switch (which only looks at MACs) cannot send traffic between networks, and a router can.
// A packet from a laptop to a server, crossing two routers.
// Addresses come from the ranges reserved for documentation.
const packet = { srcIp: "198.51.100.10", dstIp: "203.0.113.80", ttl: 64 };
const links = [
{ name: "laptop -> router A", srcMac: "02:00:00:00:00:01", dstMac: "02:00:00:00:00:0a" },
{ name: "router A -> router B", srcMac: "02:00:00:00:00:0b", dstMac: "02:00:00:00:00:0c" },
{ name: "router B -> server", srcMac: "02:00:00:00:00:0d", dstMac: "02:00:00:00:00:99" },
];
links.forEach(function (link, i) {
if (i > 0) packet.ttl -= 1; // each router decrements TTL before forwarding
console.log("Link " + (i + 1) + ": " + link.name);
console.log(" frame: " + link.srcMac + " -> " + link.dstMac + " (new on every link)");
console.log(" packet: " + packet.srcIp + " -> " + packet.dstIp + " ttl=" + packet.ttl);
});
Reading a raw Ethernet frame
Wireshark and tcpdump show you frames as hexadecimal bytes. Two hex digits are one byte. The first 14 bytes are always the Ethernet header, so you can read it by eye:
ffffffffffff 525400123456 0806 0001080006040001...
dst MAC src MAC type payload (here an ARP request)
Everything after the 14th byte is the payload. Time to write the parser that Wireshark has built in: for a hex string, print the MAC addresses, name the EtherType, and handle bad input. It is written in Rust, a systems language that is increasingly used for network tools.
Lab exercise 3
RustGraded in Code LabParse an Ethernet II frame. The input is one line of hexadecimal digits (no spaces, upper or lower case) containing a whole frame. Print:
dst=<MAC>[ (broadcast)| (multicast)]
src=<MAC>
type=<name> (0x<4 hex digits>)
payload=<n> bytes
- MAC addresses are printed as six lowercase hex pairs separated by colons.
- The destination is broadcast if it is
ff:ff:ff:ff:ff:ff; otherwise it is multicast if the lowest bit of its first byte is 1 (for example the first byte is33or01); otherwise add nothing. - Type names:
0x0800IPv4,0x0806ARP,0x86ddIPv6, anything elseunknown. payloadis the number of bytes after the 14-byte header.- If the hex is invalid (odd length or a non-hex character) print
error: invalid hex. If it decodes to fewer than 14 bytes printerror: frame too short.
Example
Input:
0a00000000025254001234560800450000341c46400040060000c0000232cb007150
Output:
dst=0a:00:00:00:00:02
src=52:54:00:12:34:56
type=IPv4 (0x0800)
payload=20 bytes
- IPv4 frame to a unicast address
- ARP request sent to broadcast
- LLDP frame sent to a multicast address
- Header only (upper-case hex): payload is 0 bytes
- A frame shorter than 14 bytes is rejected
- Invalid hex is rejected
Example run
- Input
0a00000000025254001234560800450000341c46400040060000c0000232cb007150
- Expected output
dst=0a:00:00:00:00:02 src=52:54:00:12:34:56 type=IPv4 (0x0800) payload=20 bytes
#![allow(dead_code, unused_variables)]
use std::io;
/// Turns 6 bytes into "aa:bb:cc:dd:ee:ff".
fn format_mac(bytes: &[u8]) -> String {
// TODO: format every byte as two lowercase hex digits ({:02x}) and join them with ":"
String::new()
}
/// Names the EtherType value.
fn ether_type_name(t: u16) -> &'static str {More lines in Code Lab: 28
Hints and instant feedback are in Code Lab. Optional practice: it does not block finishing the lesson.
JavaScript and Python also run in your browser without an account; other languages use the secure server runner and need a free account.
Using layers to troubleshoot
The model is most useful as a checklist. When "the internet is broken", walk up the layers and stop at the first one that fails:
- Layer 1: is the cable plugged in, is Wi-Fi switched on, is there a link light?
- Layer 2: are you connected to the right Wi-Fi network or switch port, and can you reach your neighbour (the router)?
- Layer 3: do you have a valid IP address, and can you
pingthe default gateway and then a public IP such as1.1.1.1? - Layer 4: is the destination port open, or does a firewall drop it?
- Layer 7: does the name resolve (DNS) and does the application answer correctly?
A classic example: ping 1.1.1.1 works but ping example.com fails. Layers 1-3 are fine, so the problem is name resolution, which is an application-layer service (DNS). Without the layered view you might spend an hour replacing cables.
Question 1 of 5
At which OSI layer does a router make its forwarding decisions?
Question 2 of 5
A packet crosses three routers on its way to a server. What happens to its addresses?
Question 3 of 5
Which description of encapsulation is correct for a web request sent over TCP and Ethernet?
Question 4 of 5
Ethernet's MTU is 1500 bytes. Why can a TCP sender put only 1460 bytes of data in each frame?
Question 5 of 5
You can ping 1.1.1.1 successfully, but pinging example.com says "unknown host". Which layer is the most likely culprit?
Recap
- Layers split a hard problem into small jobs: OSI has 7, TCP/IP has 4.
- Going down, each layer encapsulates the data with its own header; the receiver decapsulates.
- Data units: frame (2), packet (3), segment/datagram (4).
- MAC addresses change on every link, IP addresses stay end to end, ports pick the program.
- An Ethernet frame is 64 to 1518 bytes; TCP over IPv4 carries at most 1460 data bytes per frame.
Practice next: open Wireshark (or run tcpdump -e -n) on your own computer, load a web page, and find the Ethernet, IP and TCP headers of one packet. Then move on to IP addressing and subnetting.
Practice
Reading is not learning until you run something. Pick one.
Graded lab exercises
- Lab exercise 1PythonNot passed yet Try in Code Lab
- Lab exercise 2JavaScript (Node.js)Not passed yet Try in Code Lab
- Lab exercise 3RustNot passed yet Try in Code Lab