Learn › The Network Stack › The Application Layer
tshark -V - a hands-on Linux lab on a real virtual machine.
One curl request. Every layer visible from Linux. Frame, IP, TCP, TLS, HTTP, all in one capture.
This is the capstone. Every protocol you have learned in this track is about to appear in a single packet capture.
You will run one curl command to server.lab. That single request triggers a cascade: an ARP request (if needed), a DNS query (UDP port 53), a DNS response, a TCP three-way handshake (SYN, SYN-ACK, ACK), an HTTP GET request, an HTTP 200 response, and a TCP teardown (FIN, ACK). Every layer of the network stack, all visible from your terminal.
You have met every one of those on its own. Nobody has shown you them firing in order, from one keystroke, on one wire. That is what this lesson is.
The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The lab user is student and the machine is named fullstack-lab. Values that change on every run, such as packet numbers, timestamps and the port the client picks, are printed as labels rather than numbers, because your run will produce its own.
A protocol never travels alone. Every packet you have captured in this track carried several protocols at once, one wrapped inside the next, and the tools have been quietly unwrapping them for you the whole time.
Two different questions come out of that, and this lesson answers both:
The first is a vertical slice. The second is a timeline. An engineer who can read both can debug almost anything on a network, because every fault lands somewhere on one of those two pictures.
The split you are about to see in a dissection was a design decision, and an argued one. The original protocol behind the internet was a single thing called TCP, which did addressing and reliable delivery together.
In 1978 it was cut in two. IP took the addressing and the delivery attempt, TCP kept the reliability on top, and anything that did not want reliability could then use IP directly. That is why a dissection prints Internet Protocol Version 4 and Transmission Control Protocol as two separate blocks with their own fields. It is also why UDP and ICMP can sit at the same level as TCP rather than underneath it.
Layering is the reason a network is debuggable at all. Each layer has its own addresses, its own error messages and its own tools, so a fault can be located rather than merely observed. When something is broken, the useful question is never "is the network down" but "which layer stops working", and the whole of this lesson is practice at answering that.
This lab has the most complete network you have seen in this track, built as two network namespaces inside your one VM. A network namespace is a private copy of the network stack: its own interfaces, its own addresses, its own routing table.
[client] 10.0.1.10 <--------> 10.0.1.20 [server]
The server runs three services:
server.lab to 10.0.1.20Inside the client namespace, /etc/resolv.conf names the server as the resolver (nameserver 10.0.1.20). When you curl server.lab, the client first asks the server "what is the IP for server.lab?" before it can send the HTTP request.
That 10.0.1.0/24 network exists only inside the namespaces. To run a command in there, put sudo ip netns exec client in front of it. The send-curl, send-curl-https, send-ping and cap helpers already do that.
The helper names all share a prefix, so lean on tab completion. Type send- and press Tab twice and the shell lists every helper on this box. Type send-curl- and press Tab and it finishes the HTTPS one. That habit is worth more here than in any lesson so far, because these captures fail silently if a helper name is wrong.
Your progress is tracked automatically. Just type the commands naturally and you will see a checkmark appear when each task completes.
Every capture in this lesson follows the same shape: start tshark first, then let the traffic arrive while it is listening. A request is over in milliseconds, so a capture started afterwards sees nothing.
This part waits three seconds in the background while tshark gets going, then sends the request:
(sleep 3; send-curl) &
Now filter the capture down to the application layer. -a duration:8 stops the capture after eight seconds and hands your prompt back. Packet numbers and timestamps will be whatever your run produces.
cap -a duration:8 -Y http
prompt: student@fullstack-lab:~$ answer: cap -a duration:8 -Y http ||| cap -Y http -a duration:8 output: 1 0.005123 10.0.1.10 -> 10.0.1.20 HTTP GET / HTTP/1.1 2 0.006234 10.0.1.20 -> 10.0.1.10 HTTP HTTP/1.0 200 OK (text/html) hint: The cap helper runs tshark inside the client namespace, and the display filter keeps only HTTP: cap -a duration:8 -Y http
That is the application layer. The GET request and the 200 response. But there is much more happening underneath. To see it, you need to remove the filter and look at every packet.
Look at the first timestamp before you move on. The HTTP request did not go out at 0.000000. Something else had already been happening on this wire for five thousandths of a second before curl said a word. Catching this exchange is the first of the five things your lab grades.
You asked for HTTP and got two packets. The filter did not change what crossed the wire, only what tshark printed.
>>> Around ten. One curl command produces a DNS query and its response, a three-way handshake of three packets, the HTTP request and response, and a teardown of three more. Add an ARP exchange at the front if the client does not already know the server's MAC address. If you picked two, that is what the filtered screen shows and it is exactly the trap a display filter sets: it is a view, not the traffic. If you picked hundreds, the page here is 22 bytes, so it fits in one packet with room to spare. On a real page of a few hundred kilobytes you would see hundreds of data packets, and the ten around them would be identical to the ten you are about to capture.
-V prints every layer of a packet instead of a summary line. Pick the TCP SYN, the packet that opens the connection, with a display filter that matches a SYN without an ACK.
Start the traffic first, as always:
(sleep 3; send-curl) &
Do not reach for -c 1 here. In tshark, -c counts packets captured, not packets displayed. cap -c 1 -V stops after the very first packet on the wire, which in this lab is the ARP request or the DNS query, not the SYN. If that packet does not match your filter, you get nothing at all. Use a filter to choose the packet, and -a duration: to end the capture.
The dissection is laid out as shown below. The source port is different on every run.
cap -a duration:8 -V -Y "tcp.flags.syn == 1 and tcp.flags.ack == 0"
prompt: student@fullstack-lab:~$ answer: cap -a duration:8 -V -Y "tcp.flags.syn == 1 and tcp.flags.ack == 0" ||| cap -a duration:8 -Y "tcp.flags.syn == 1 and tcp.flags.ack == 0" -V output: Frame <n>: 74 bytes on wire (592 bits), 74 bytes captured [Protocols in frame: eth:ethertype:ip:tcp]
Ethernet II, Src: 02:00:0a:00:01:0a, Dst: 02:00:0a:00:01:14 Destination: 02:00:0a:00:01:14 Source: 02:00:0a:00:01:0a Type: IPv4 (0x0800)
Internet Protocol Version 4, Src: 10.0.1.10, Dst: 10.0.1.20 Time to Live: 64 Protocol: TCP (6)
Transmission Control Protocol, Src Port: <ephemeral port>, Dst Port: 80 Flags: 0x002 (SYN) Sequence Number: 0 (relative) hint: Add verbose mode to a capture and use a display filter that selects a SYN with no ACK: cap -a duration:8 -V -Y "tcp.flags.syn == 1 and tcp.flags.ack == 0"
Read that from top to bottom. Most of this track is right there in one packet:
One packet, four layers, six lessons.
The [Protocols in frame: eth:ethertype:ip:tcp] line at the very top is the same information in one line, written in the order the machine unwraps them. That is your vertical slice, and it is the second of the five things your lab grades.
This is where it all comes together. When you curl server.lab (not the IP), the client needs to resolve the name first. That means DNS before HTTP. Capture everything, with no display filter at all.
(sleep 3; send-curl) &
Your timestamps and port numbers will differ, and the packet count depends on whether the ARP cache was already warm.
cap -a duration:8
prompt: student@fullstack-lab:~$ answer: cap -a duration:8 output: 1 0.000000 10.0.1.10 -> 10.0.1.20 DNS Standard query A server.lab 2 0.000456 10.0.1.20 -> 10.0.1.10 DNS Standard query response A server.lab A 10.0.1.20 3 0.001234 10.0.1.10 -> 10.0.1.20 TCP 45678 -> 80 [SYN] 4 0.001345 10.0.1.20 -> 10.0.1.10 TCP 80 -> 45678 [SYN, ACK] 5 0.001456 10.0.1.10 -> 10.0.1.20 TCP 45678 -> 80 [ACK] 6 0.002345 10.0.1.10 -> 10.0.1.20 HTTP GET / HTTP/1.1 7 0.003456 10.0.1.20 -> 10.0.1.10 HTTP HTTP/1.0 200 OK 8 0.003567 10.0.1.20 -> 10.0.1.10 TCP 80 -> 45678 [FIN, ACK] 9 0.003678 10.0.1.10 -> 10.0.1.20 TCP 45678 -> 80 [FIN, ACK] 10 0.003789 10.0.1.20 -> 10.0.1.10 TCP 80 -> 45678 [ACK] hint: Same capture as before with the display filter left off entirely: cap -a duration:8
Walk through that line by line:
1. DNS query (packet 1) - "What is the IP for server.lab?" Sent over UDP port 53. 2. DNS response (packet 2) - "server.lab is 10.0.1.20." Now the client knows where to connect. 3. TCP SYN (packet 3) - The client starts the three-way handshake. 4. TCP SYN-ACK (packet 4) - The server agrees to the connection. 5. TCP ACK (packet 5) - Handshake complete. The connection is established. 6. HTTP GET (packet 6) - The client sends the actual request. 7. HTTP 200 (packet 7) - The server sends the response. 8. TCP FIN (packets 8-10) - The connection is torn down.
One curl command. Ten packets. DNS, TCP, HTTP, and TCP teardown. Every layer working together.
Now put this screen next to the filtered one. Packets 6 and 7 are the two lines -Y http showed you. The other eight were always there. Capturing the whole conversation is the third of the five things your lab grades.
You will also see ARP packets at the very beginning if the client does not already have the server's MAC address in its cache. ARP asks "who has 10.0.1.20?" and the server replies with its MAC address. That is Layer 2 doing its job before Layer 3 can send anything, and it happens before even the DNS query.
Stop and take that in. You have just read a complete network transaction end to end, in order, with nothing hidden and nothing summarised for you.
You did not learn the layers one at a time and then take somebody else's word for how they fit. You watched a name become an address, an address become a connection, a connection carry a request, and the connection close itself politely afterwards. That sequence is the same on a laptop, in a datacenter, and behind every website you have ever loaded.
Two things left: what changes when the payload is encrypted, and how to get a summary of everything a capture saw without reading a single packet by hand.
The server also runs HTTPS on port 443, and send-curl-https is the helper for it. With plain HTTP you just read the GET request and the 200 response in clear text.
>>> The two Hello messages, and no more. In TLS 1.3 the keys are agreed by the end of the Server Hello, so even the server's certificate travels encrypted, and everything after that message reads as Application Data. If you picked nothing, the first two messages have to be readable: the two sides have no shared secret yet, so there is nothing to encrypt with. If you picked everything except the content, that was true of TLS 1.2 and it is what most older material still shows. What does not change is everything below TLS. The DNS lookup, the ARP exchange and the TCP handshake are the same packets they were a moment ago, because encryption sits above them.
Start the HTTPS helper in the background this time, and filter the capture to TLS.
(sleep 3; send-curl-https) &
cap -a duration:8 -Y tls
prompt: student@fullstack-lab:~$ answer: cap -a duration:8 -Y tls ||| cap -Y tls -a duration:8 output: 1 <time> 10.0.1.10 -> 10.0.1.20 TLSv1.3 Client Hello 2 <time> 10.0.1.20 -> 10.0.1.10 TLSv1.3 Server Hello, Change Cipher Spec, Application Data 3 <time> 10.0.1.10 -> 10.0.1.20 TLSv1.3 Change Cipher Spec, Application Data 4 <time> 10.0.1.20 -> 10.0.1.10 TLSv1.3 Application Data hint: Same capture form as the HTTP one, with the TLS display filter and the HTTPS helper sending the traffic: cap -a duration:8 -Y tls
Compare this with the plain HTTP capture. With HTTP, you saw the GET request and the 200 response in clear text. With HTTPS, only the two Hello messages stay readable. Everything after the Server Hello reads as Application Data, including the rest of the handshake: in TLS 1.3 the keys are agreed by the end of the Server Hello, so even the server's certificate travels encrypted.
This is the difference TLS makes. Same application, same HTTP protocol underneath, but wrapped in encryption that makes the payload invisible to anyone watching the wire. Catching it is the fourth of the five things your lab grades.
tshark has a special mode that counts every protocol it saw in a capture and prints them as a tree, with each protocol indented under the one that carries it.
ip, alongside tcp and udp.eth, at the same level as ip, because ARP is not carried by IP at all.>>> Under eth, level with ip. ARP rides directly on Ethernet, which is exactly what makes it able to do its job: it exists to find a MAC address, and it has to work before IP can send anything anywhere. Putting it under IP would be a circular dependency drawn on a screen. If you picked the top level, ARP is very much part of the stack, and it has a frame, a source MAC and a destination MAC like everything else. It simply stops at Layer 2 instead of climbing higher. The tree indentation is not decoration, it is containment, and reading it as containment is the skill this step is for.
Start the capture, then push a mix of traffic at it. Three helpers in one background line, so the capture sees HTTP, TLS, DNS and ICMP:
(sleep 3; send-curl; send-curl-https; send-ping) &
The -qz io,phs flag tells tshark to be quiet (-q) and print a protocol hierarchy statistics table (-z io,phs) when the capture ends. Fifteen seconds is enough for all three helpers to finish, since send-ping alone takes three.
The table is a tree. The counts are whatever your capture saw.
cap -a duration:15 -qz io,phs
prompt: student@fullstack-lab:~$ answer: cap -a duration:15 -qz io,phs ||| cap -qz io,phs -a duration:15 output: =================================================================== Protocol Hierarchy Statistics Filter:
eth frames:<n> bytes:<n> ip frames:<n> bytes:<n> tcp frames:<n> bytes:<n> http frames:<n> bytes:<n> tls frames:<n> bytes:<n> udp frames:<n> bytes:<n> dns frames:<n> bytes:<n> icmp frames:<n> bytes:<n> arp frames:<n> bytes:<n> =================================================================== hint: Add the quiet flag and the protocol hierarchy statistic to a longer capture: cap -a duration:15 -qz io,phs
Read the indentation as containment. Every frame starts with eth (Ethernet). Most contain ip (IP). Inside IP sit tcp (which carries HTTP and TLS), udp (which carries DNS), and icmp (ping). The arp line only appears if the client had to ask for the server's MAC address during your capture, and when it does appear it is indented under eth, not under ip, because ARP is not carried by IP at all. It rides directly on Ethernet.
The frame counts add up the same way the indentation does. Everything nested under ip sums to the ip line, and ip plus anything else at that level sums to eth.
That tree is the network stack. You just built a map of every protocol in your capture, organized by layer, and it is the fifth and last of the things your lab grades.
Here is what you now understand. When you type curl http://server.lab/ in a terminal, this happens:
| Step | Protocol | Layer | What happens |
|---|---|---|---|
| 1 | DNS | Application | Client asks "what is server.lab?" Server answers "10.0.1.20" |
| 2 | ARP | Data Link | Client asks "who has 10.0.1.20?" Server answers with its MAC |
| 3 | TCP | Transport | Three-way handshake: SYN, SYN-ACK, ACK |
| 4 | HTTP | Application | Client sends GET /. Server sends 200 OK with the page |
| 5 | TCP | Transport | Connection teardown: FIN, ACK |
Every step except ARP rides on IP (Layer 3), which rides on Ethernet (Layer 2), which rides on the physical wire (Layer 1). ARP skips IP entirely and sits straight on Ethernet, which is exactly where the protocol hierarchy table put it.
If the reques
Practice The Full Stack in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.