🌐Networking
Foundations · Lesson 1/8

The OSI & TCP/IP Models — How Data Travels

40 min 1 quiz Graded labs: 3

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 Lab

Build 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
Visible tests (5)
  • 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
Starter code
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

Try in Code Lab

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 Lab

Write 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.)

Visible tests (6)
  • 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
Starter code
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

Try in Code Lab

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 Lab

Parse 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 is 33 or 01); otherwise add nothing.
  • Type names: 0x0800 IPv4, 0x0806 ARP, 0x86dd IPv6, anything else unknown.
  • payload is 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 print error: 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
Visible tests (6)
  • 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
Starter code
#![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

Try in Code Lab

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:

  1. Layer 1: is the cable plugged in, is Wi-Fi switched on, is there a link light?
  2. Layer 2: are you connected to the right Wi-Fi network or switch port, and can you reach your neighbour (the router)?
  3. Layer 3: do you have a valid IP address, and can you ping the default gateway and then a public IP such as 1.1.1.1?
  4. Layer 4: is the destination port open, or does a firewall drop it?
  5. 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.

Quick quiz

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