Learn › The Network Stack › What Moves on the Wire
tshark - a hands-on Linux lab on a real virtual machine.
tshark and tcpdump let you see every packet that crosses your interface. This is where networking becomes real.
Every page load, every ssh session and every download is carried by packets: small chunks of data that cross a network interface one at a time. Engineers say the word packet constantly. Almost nobody has actually watched one go by.
You are about to. This VM contains a private network with two machines and a web server, built so you have traffic worth reading. By the end of this lesson you will have read live packets one line at a time, opened one up layer by layer, printed just the fields you care about, and handed the kernel a compiled filter that decides, packet by packet, what reaches your screen.
The two tools that do this are called tshark and tcpdump. Between them they have been watching the world's wires since 1988.
The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The real VM comes at the end, under Ready to practice. Your progress in the real lab is tracked automatically, so type commands naturally and watch the checkmarks appear.
A packet is a chunk of data small enough to travel a network in one piece. Big things, like a web page, are cut into many packets and reassembled on the far side. At the front of every packet ride its headers: structured fields that say where it came from, where it is going, and what kind of data is inside, like the address block on an envelope. Capture tools read headers and print them for you.
To give you packets, this lab builds a tiny network out of network namespaces. A network namespace is a separate networking world inside one Linux kernel: its own interfaces, its own addresses, its own routes. Two namespaces, named client and server, are joined by a veth pair, a virtual patch cable with an interface at each end.
[client] 10.0.1.10 <----veth----> 10.0.1.20 [server]
MAC: 02:00:0a:00:01:0a MAC: 02:00:0a:00:01:14
The server side answers web requests on port 80. The addresses are fixed, so the screens in this lesson show the same machines you will capture from in the lab. You met interfaces and MAC addresses in Module 1; here they finally do something.
You stand outside both namespaces, in the VM itself. Three helper commands are installed for you: send-ping and send-curl put traffic on the wire, and cap captures it. You will open cap up shortly and see there is no magic in it.
Tab completion works on command names. Type send- and press Tab twice, and the shell lists both traffic generators.
tcpdump came first. Van Jacobson's group at Lawrence Berkeley National Laboratory wrote it in 1988 to see why the young Internet kept choking, and its capture engine was later split out as libpcap, the library nearly every capture tool on earth now uses. When engineers say pcap, that lineage is what they mean.
tshark is the terminal face of Wireshark, the most widely used protocol analyzer in the world. Wireshark started life in 1998 under the name Ethereal. When its author changed employers in 2006, the old company kept the trademark, so the project renamed itself overnight and kept moving. Same code, new name.
The split that matters day to day: tcpdump is on nearly every Linux box and is the fast first look. tshark carries Wireshark's full protocol dissectors, several thousand of them, so it can decode almost anything and hand you any field by name. Underneath, both drink from the same libpcap machinery. You will use both in the next ten minutes.
send-ping works by running the ping command you know from Foundations: it sends an ICMP echo request to 10.0.1.20 once per second. Before you capture anything, commit to a number.
>>> Two. A ping is a round trip: an echo request from client to server, then an echo reply coming back. If you picked one, you had half the story, and it is the less useful half: the proof of life is the reply. If you picked ninety-eight, hold on to that number anyway. It is about to appear on your screen meaning something else entirely.
Time to look at the wire. Two commands, one after the other.
First, put traffic on it. send-ping 30 & starts thirty pings, and the & puts them in the background so you get your prompt back. The shell prints a job line like [1] 812; that is the shell talking, not the network. The generator itself prints nothing, which keeps your capture screen clean.
Then capture four packets on the client interface. -c 4 means stop after a count of four:
cap -c 4
prompt: student@capture-lab:~$ answer: cap -c 4 ||| cap -c4 ||| sudo ip netns exec client tshark -i veth-c -c 4 output: Running as user "root" and group "root". This could be dangerous. Capturing on 'veth-c' 1 0.000000000 10.0.1.10 → 10.0.1.20 ICMP 98 Echo (ping) request id=0x1c07, seq=1/256, ttl=64 2 0.000058123 10.0.1.20 → 10.0.1.10 ICMP 98 Echo (ping) reply id=0x1c07, seq=1/256, ttl=64 (request in 1) 3 1.023415225 10.0.1.10 → 10.0.1.20 ICMP 98 Echo (ping) request id=0x1c07, seq=2/512, ttl=64 4 1.023473480 10.0.1.20 → 10.0.1.10 ICMP 98 Echo (ping) reply id=0x1c07, seq=2/512, ttl=64 (request in 2) 4 packets captured hint: The capture shortcut with a count of four: cap -c 4
That is live network traffic, one line per packet. Read line 1 left to right: a frame number, a timestamp in seconds since the capture started, source address, an arrow, destination address, the protocol, a size, and a summary.
The size says 98, and the predict question pays off here. A default ping carries 56 bytes of payload. ICMP wraps it with an 8 byte header, IP wraps that with 20 more, and Ethernet wraps everything with 14. 56 plus 8 plus 20 plus 14 is 98. You are watching the envelopes stack up inside a single number.
ttl=64 is the packet's Time to Live, a counter every router decreases by one. Linux starts it at 64, Windows at 128, and some network gear at 255, so an arriving ttl whispers what sent the packet and how far away it is: a reply landing with ttl=59 came from a Linux box five routers away. Yours reads exactly 64 because these two machines share a cable with no routers on it.
Notice (request in 1) on the replies. tshark remembered packet 1 and matched packet 2 to it. It keeps that kind of state across a whole capture, which is something tcpdump never does.
Two lines you did not ask for: the root warning at the top, and Capturing on 'veth-c'. The warning deserves a whole step. That is next.
A wrapper you cannot see inside is a trap: you end up learning the lab instead of Linux. Open it.
cat /usr/local/bin/cap
prompt: student@capture-lab:~$ answer: cat /usr/local/bin/cap ||| cat $(which cap) output: #!/bin/bash
sudo ip netns exec client tshark -i veth-c "$@" hint: It is a file like any other: cat /usr/local/bin/cap
One real line. sudo ip netns exec client COMMAND runs COMMAND inside the client namespace, and tshark -i veth-c says capture on the interface named veth-c. -i chooses the interface, and "$@" passes along whatever you handed to cap. From here on, cap is typing relief, and you know the full spell.
Now the root warning. On its own, tshark does not capture packets. It launches a small helper called dumpcap that does the privileged work of opening the interface, while tshark itself only parses what dumpcap hands over. That split exists because protocol dissectors are millions of lines of code parsing hostile bytes, and they have had real security holes over the years. The design keeps the parsing out of root. Debian can grant capture rights through a wireshark group so nobody runs dissectors as root; this lab keeps things simple with sudo instead, and tshark grumbles about it every single time. Now you know exactly what it is grumbling about.
You are about to run tcpdump for the first time. One of these two capture tools has a habit you should know about before you trust a capture.
>>> tcpdump. By default it tries to turn every IP address it prints into a hostname, and each attempt is a reverse DNS query: real packets, sent while you capture, sometimes captured by the very capture you are reading. The -n flag turns resolving off, and experienced engineers type it by reflex. If you picked tshark: by default it resolves only MAC address vendor prefixes, from a local file, no packets sent. If you picked neither, that is the trap: a listening tool can still transmit, and a tcpdump stalled on a dead DNS server is a classic mystery.
Same wire, other tool, and this time no wrapper: the full command, with -n for no name lookups. Your pings may still be running; if the wire has gone quiet, start another send-ping 30 & first.
sudo ip netns exec client tcpdump -ni veth-c -c 4
prompt: student@capture-lab:~$ answer: sudo ip netns exec client tcpdump -ni veth-c -c 4 ||| sudo ip netns exec client tcpdump -i veth-c -c 4 -n output: tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on veth-c, link-type EN10MB (Ethernet), snapshot length 262144 bytes 06:12:04.881712 IP 10.0.1.10 > 10.0.1.20: ICMP echo request, id 7207, seq 5, length 64 06:12:04.881766 IP 10.0.1.20 > 10.0.1.10: ICMP echo reply, id 7207, seq 5, length 64 06:12:05.905671 IP 10.0.1.10 > 10.0.1.20: ICMP echo request, id 7207, seq 6, length 64 06:12:05.905732 IP 10.0.1.20 > 10.0.1.10: ICMP echo reply, id 7207, seq 6, length 64 4 packets captured 4 packets received by filter 0 packets dropped by kernel hint: The full spell: sudo ip netns exec client tcpdump -ni veth-c -c 4
Same traffic, different accent. tcpdump stamps wall clock time instead of seconds since start, writes the direction as > instead of an arrow, and says length 64 where tshark said 98. Neither is wrong: 64 is the ICMP message alone, 8 bytes of header plus 56 of payload, while tshark counted the whole 98 byte frame. Knowing which layer a tool is measuring is half of reading it.
The lines around the packets teach just as much. snapshot length 262144 bytes is the snaplen: how many bytes of each packet get copied to the tool. 262144 is the modern grab-everything default; for years the default was a stingy 68 bytes, headers only, a relic of slow disks that still bites people reading old capture files. -s changes it.
The exit summary is the part professionals read first. received by filter counts what the kernel saw for you. dropped by kernel counts packets that arrived faster than your tool could take delivery, when the buffer between kernel and userspace overflowed. If that number is not zero, your capture has holes in it, and any conclusion you draw is missing packets. Check it before you trust anything else.
A question has been hiding under every screen so far. An interface receives frames addressed to other machines all day. Why does it give any of them to you?
Normally it does not. A network interface reads the destination address at the front of every arriving frame and drops the ones that do not name it, in hardware, before the kernel is involved. That filter is why an ordinary machine is not buried under its neighbors' traffic.
A capture tool switches the filter off. It puts the interface into promiscuous mode, and from that moment every frame the interface sees travels up the stack for you to read. libpcap does this for you by default, which is why nobody ever mentions it. An interface in that state carries the flag PROMISC in ip link show. And -p is the flag that tells tcpdump not to enable it, the polite way to capture on a machine you do not own.
Then a switch takes the gift back. A switch forwards a frame only out the port that owns the destination address, so on a switched network promiscuous mode buys you your own traffic plus broadcasts and very little else. The frames you wanted never arrive at your port to be let in. That is why real capture work means a mirror port (also called SPAN), a network tap, or standing on one of the two machines actually talking.
On this lab's virtual cable both frames of every exchange involve the client, so the filter has nothing to hide from you and every capture you run is complete.
Count what just happened. You have captured live traffic with the two tools every Linux engineer reaches for, read both of their dialects, and picked up the reflexes around them: -n before trusting names, the drop counter before trusting an analysis.
One more piece, and it is the one that wins arguments. The kernel clones packets to your capture tool at the edge of its network stack: arriving packets are copied to you before the firewall judges them, and outgoing packets are copied after the firewall has already let them through, on their way to the driver. So when tcpdump shows a request arriving but the application swears nothing came, the packet died between those two points, and the firewall is your first suspect. A capture proves arrival on the wire. It never proves delivery to the program.
So far every packet has been one summary line. Next: all of it.
The -V flag tells tshark to print the full dissection: every layer, every field, every value it understands. For one ping packet that is about sixty lines. The screen below keeps the headline of each layer plus the lines this step reads; run it and scroll through your own. Make sure ping traffic is flowing first.
Before you press Enter, commit to a number. One ping packet is about to be taken apart. How many named layers will tshark print before it reaches the ping message itself?
cap -c 1 -V
prompt: student@capture-lab:~$ answer: cap -c 1 -V ||| cap -V -c 1 ||| sudo ip netns exec client tshark -i veth-c -c 1 -V output: Running as user "root" and group "root". This could be dangerous. Capturing on 'veth-c' Frame 1: 98 bytes on wire (784 bits), 98 bytes captured (784 bits) on interface veth-c, id 0 Frame Length: 98 bytes (784 bits) [Protocols in frame: eth:ethertype:ip:icmp:data] Ethernet II, Src: 02:00:0a:00:01:0a (02:00:0a:00:01:0a), Dst: 02:00:0a:00:01:14 (02:00:0a:00:01:14) Type: IPv4 (0x0800) Internet Protocol Version 4, Src: 10.0.1.10, Dst: 10.0.1.20 Time to Live: 64 Protocol: ICMP (1) Internet Control Message Protocol Type: 8 (Echo (ping) request) Code: 0 1 packet captured hint: The dissection flag on a single packet: cap -c 1 -V
Read the headlines top to bottom: Frame, then Ethernet II, then Internet Protocol, then ICMP. Each layer is an envelope around the next. Ethernet carries the MAC addresses and a Type field naming what is inside. IP carries the addresses that cross networks, the Time to Live you met earlier, and a protocol number naming its own contents. ICMP is the message itself. This nesting is the shape of every packet you will ever capture, whatever the protocols are called that day.
One subtlety that separates readers from experts: the Frame section at the top is not bytes from the wire. It is the capture tool's own bookkeeping, which interface, which timestamp, how many bytes, attached on your machine. The wire's first real byte is the first byte of the Ethernet destination address.
The next lesson walks this entire dissection line by line until every field is yours. Today you have the shape.
Someday you will capture your own machine's outgoing TCP and see every packet flagged with a bad checksum. That is almost never corruption. With checksum offload, the kernel hands the packet to the capture tap before the network card fills the checksum in, so the capture sees an unfinished packet while the wire got a correct one. Bad checksums on your own outbound traffic are an artifact of where the copy happens, not damage.
A full dissection is for staring at one packet. Across many packets you want columns. -T fields switches the output to chosen fields only, and each -e names one, using the same field names the dissection just showed you.
Four -e flags are coming. Commit to the shape of the answer first. How many columns land on each line, and which of them change between line 1 and line 2?
With ping traffic flowing:
cap -c 3 -T fields -e eth.src -e eth.dst -e ip.src -e ip.dst
prompt: student@capture-lab:~$ answer: cap -c 3 -T fields -e eth.src -e eth.dst -e ip.src -e ip.dst ||| cap -T fields -e eth.src -e eth.dst -e ip.src -e ip.dst -c 3 ||| sudo ip netns exec client tshark -i veth-c -c 3 -T fields -e eth.src -e eth.dst -e ip.src -e ip.dst output: Running as user "root" and group "root". This could be dangerous. Capturing on 'veth-c' 02:00:0a:00:01:0a 02:00:0a:00:01:14 10.0.1.10 10.0.1.20 02:00:0a:00:01:14 02:00:0a:00:01:0a 10.0.1.20 10.0.1.10 02:00:0a:00:01:0a 02:00:0a:00:01:14 10.0.1.10 10.0.1.20 3 packets captured hint: Fields mode with four -e flags: cap -c 3 -T fields -e eth.src -e eth.dst -e ip.src -e ip.dst
Four columns: source MAC, destination MAC, source IP, destination IP, separated by tabs. Line 1 is a request, line 2 is the reply, and every address swaps sides. You can read direction without a single summary word.
The names you fed -e come from Wireshark's field namespace, the same names the dissection prints, and there are over two hundred thousand of them registered across its dissectors. Any field you can see in a dissection, you can extract as a column, which is why tshark output feeds scripts, spreadsheets and monitoring pipelines so well.
| Field | What it holds |
|---|---|
eth.src, eth.dst | MAC addresses in the Ethernet header |
eth.type | EtherType: 0x0800 is IPv4, 0x0806 is ARP |
ip.src, ip.dst | IP addresses |
ip.ttl | Time to Live |
tcp.srcport, tcp.dstport | TCP port numbers |
Both tools accept a capture filter: an expression at the end of the command, like icmp or tcp port 80, that decides which packets you receive at all. Before you use one, commit to a mental model.
>>> Inside the kernel. The filter text is compiled into a tiny program, and the kernel runs that program against every packet at the capture point, copying across only the ones that pass. Rejected packets never reach your tool, and that copy is exactly what makes unfiltered captures expens
Practice Watching Traffic in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.