Chapter 8 · Hands-on labs
Six short labs that turn the slides into commands you actually run — list live sockets, watch a TCP three-way handshake and teardown packet-by-packet, read sequence & window fields, and compare TCP against UDP head-to-head. Each maps to a Chapter 8 topic and ends by tying what you saw back to the concept.
ss/netstat, tcpdump, curl, nc, iperf3. Missing one? sudo apt install -y iproute2 tcpdump netcat-openbsd iperf3. The ❯ is just the prompt — Copy grabs only the command.List active TCP connections with their local & remote sockets:
ss -tan
State Local Address:Port Peer Address:Port ESTAB 192.168.1.23:51000 142.250.74.36:443 LISTEN 0.0.0.0:22 0.0.0.0:*
Each ESTAB line is one conversation: local IP:port ↔ peer IP:port — the socket pair. The :443 is HTTPS; your side (:51000) is an ephemeral port.
Generate a connection in Terminal B, then re-run the socket list and find it:
curl -s https://example.com > /dev/null & ss -tan | grep :443
Which process owns which port? (the classic security check):
sudo ss -tanp # older equivalent: sudo netstat -tanp
ss lists the transport layer’s live conversations. The unique key on every row is the socket pair — the exact multiplexing the slides describe.ss, how does your machine tell the two connections apart if the destination IP:port is identical? (answer below)What is your machine listening on?
ss -tlnp
Look up any port number → service in the system’s port database:
grep -wE "22|53|80|443|67|161" /etc/services | head
ssh 22/tcp domain 53/tcp domain 53/udp http 80/tcp https 443/tcp
Scan a host you own to see which well-known ports are open:
nmap -F 127.0.0.1
/etc/services is the well-known-ports table from the slide, and an open port is a server application bound to that number — exactly “a service assigned to a port is open.”Sniff a single web connection in Terminal A (flags show as [S], [S.], [.], [F.]):
sudo tcpdump -i any -n "tcp port 80 and host example.com"
Make the connection in Terminal B:
curl -s http://example.com > /dev/null
IP you.51001 > example.80: Flags [S] seq 1000 <- SYN IP example.80 > you.51001: Flags [S.] seq 9000 ack 1001 <- SYN, ACK IP you.51001 > example.80: Flags [.] ack 9001 <- ACK ... data ... IP you.51001 > example.80: Flags [F.] ... <- FIN (teardown)
Read the flags: [S] = SYN, [S.] = SYN+ACK, [.] = ACK, [F.] = FIN+ACK. That’s the exact three-way handshake from the slides, live.
Capture with absolute sequence numbers and verbose TCP options:
sudo tcpdump -i any -nvS "tcp port 443 and host example.com"
In Terminal B, trigger it and watch seq, ack, win and mss appear:
curl -s https://example.com > /dev/null
Flags [S], seq 305419896, win 64240, options [mss 1460,sackOK,...] Flags [S.], seq 87654321, ack 305419897, win 65535, options [mss 1460,...] Flags [.], ack 1, win 502
seq/ack track every byte (reliability); win is the receiver’s advertised window (flow control); mss 1460 is the max segment size negotiated in the SYN.
Where does 1460 come from? Check your link MTU:
ip link show | grep -oE "mtu [0-9]+"
Watch a UDP exchange — a DNS query is pure UDP (Terminal A):
sudo tcpdump -i any -n udp port 53
dig kali.org
IP you.51512 > 192.168.1.1.53: 12345+ A? kali.org. IP 192.168.1.1.53 > you.51512: 12345 1/0/0 A 192.124.249.13
Compare with Lab 3: no handshake, no acknowledgments, no FIN — the datagram just goes and (maybe) comes back. That’s connectionless, best-effort UDP.
Send raw UDP yourself — listener in A, sender in B:
# Terminal A — UDP listener
nc -u -lvp 9999
# Terminal B — UDP sender (type a line, Enter)
nc -u 127.0.0.1 9999
nc -u just starts firing datagrams. No session, no confirmation. That tiny 8-byte header is the whole point: speed over guarantees.Start the server in Terminal A (serves both TCP and UDP):
iperf3 -s
Test TCP in Terminal B — note the Retr (retransmit) column, proof of TCP’s reliability:
iperf3 -c 127.0.0.1
Test UDP — note the Lost/Total Datagrams and Jitter, which only UDP reports:
iperf3 -c 127.0.0.1 -u -b 100M
[TCP] ... Bitrate 44.8 Gbits/sec Retr 0 [UDP] ... Bitrate 100 Mbits/sec Jitter 0.01 ms 0/85000 (0%)
-b 1G). Does loss appear? Why would that same loss be invisible over TCP? (answer below)Lab 1. The two connections have different source ports (ephemeral) on your machine. Same dest IP:port, different source port → different socket pair → the OS keeps them apart.
Lab 2. Your outbound source ports fall in the dynamic / ephemeral range (49152–65535) — the OS picks them for the client side.
Lab 3. The handshake is 3 packets (SYN, SYN-ACK, ACK); the teardown is 4 (FIN, ACK, FIN, ACK) because each direction closes independently.
Lab 4. You expect MSS 1460: 1500 MTU − 20 (IPv4 header) − 20 (TCP header). The full 1500 can’t be data — the headers have to fit inside the frame too.
Lab 5. UDP showed just 2 packets (query + reply) versus TCP’s handshake. Missing: SYN/SYN-ACK/ACK setup, acknowledgments, and the FIN teardown — UDP has none of them.
Lab 6. At high bitrates UDP loses datagrams (and never resends them). Over TCP that same loss is invisible to the application because TCP retransmits the missing bytes — you’d see it only as lower throughput / higher Retr.
| This lab’s tool | Connects to… |
|---|---|
| ss / netstat sockets (Lab 1–2) | Chapter 7 — the ports DNS (53) & DHCP (67/68) use |
| tcpdump handshake (Lab 3) | Chapter 9 — how these segments get routed hop-by-hop |
| seq / window / MSS (Lab 4) | Chapter 4 & 10 — the IP/IPv6 headers MSS is derived from |
| iperf3 TCP vs UDP (Lab 6) | Chapter 1 — bandwidth, throughput & goodput revisited |