LearnThe Network StackWhat Moves on the Wire

The Ethernet Frame

tshark -V - a hands-on Linux lab on a real virtual machine.

One packet, fully dissected. Ethernet header wraps IP wraps ICMP. Envelopes inside envelopes.

The first line of ping is lying to you

In the last lesson you captured live traffic and read it line by line. Today you slow down and read one single frame, byte by byte, until nothing in it is mysterious.

Start with a lie. ping is about to tell you exactly how big its packets are, and its number does not match what your machine puts on the wire. Run it and read the first line carefully.

The black boxes below are a practice terminal: a safe sandbox that checks only the one command each step teaches. The real VM waits at the end. There you log in as the user account student on the machine frame-lab. Inside it live two namespaces joined by a virtual cable: client (10.0.1.10, MAC 02:00:0a:00:01:0a) and server (10.0.1.20, MAC 02:00:0a:00:01:14). A namespace is a private copy of the network stack, a separate computer as far as networking is concerned. Your progress is tracked automatically, so type commands naturally. Timestamps, checksums and id fields in your own captures will differ from the printed screens; every structural byte will match.

send-ping

prompt: student@frame-lab:~$ answer: send-ping output: PING 10.0.1.20 (10.0.1.20) 56(84) bytes of data. 64 bytes from 10.0.1.20: icmp_seq=1 ttl=64 time=0.062 ms 64 bytes from 10.0.1.20: icmp_seq=2 ttl=64 time=0.051 ms 64 bytes from 10.0.1.20: icmp_seq=3 ttl=64 time=0.048 ms

--- 10.0.1.20 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2039ms rtt min/avg/max/mdev = 0.048/0.053/0.062/0.005 ms hint: The helper that pings the server: send-ping

The first line says 56(84) bytes of data. Two numbers for one packet. 56 is the data ping puts inside. Add the 8-byte ICMP header and you get 64, which is why every reply line says 64 bytes from. Add the 20-byte IP header on top and you get 84, the number in parentheses. That is the complete IP packet.

Except it is not what left the machine. In the last lesson, these same ping packets showed up in your captures as 98 bytes. Somewhere below the IP layer, every packet gains exactly 14 bytes that ping refuses to count. Finding those 14 bytes is this whole lesson.

Catch the envelope in the act

Capture those pings and let the capture do the measuring. The cap helper from the last lesson runs tshark inside the client namespace. On the real VM you will start traffic first with send-ping & (the & runs it in the background) and then capture. The practice terminal keeps ping traffic flowing for you, so here you type only the capture.

cap -c 2

prompt: student@frame-lab:~$ answer: cap -c 2 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=0x0521, seq=1/256, ttl=64 2 0.000041318 10.0.1.20 -> 10.0.1.10 ICMP 98 Echo (ping) reply id=0x0521, seq=1/256, ttl=64 (request in 1) 2 packets captured hint: The capture helper with a count of two: cap -c 2

There it is: 98, not 84. The 14 missing bytes are real, and they sit in front of the IP packet. Before anything leaves a machine, the network interface wraps the IP packet in a frame, the Layer 2 envelope. The IP packet is the letter. The frame is the envelope, and its 14-byte header is the addressing written on the outside.

Two side notes. The Running as user "root" warning appears because namespace capture needs root, and Wireshark dislikes root: its decoders parse hostile bytes off the network and have needed real security fixes. On normal systems, capturing is delegated to a small privileged helper called dumpcap while all decoding runs unprivileged. And (request in 1) is not in any packet: tshark matched the reply to its request for you. Wire facts and tool analysis will mix on your screen all day; telling them apart is part of the craft.

Every helper on this VM tab-completes. Type send-p and press Tab to get send-ping, or check- and Tab for check-progress. Let the shell finish names for you.

Anatomy of the envelope

Every Ethernet II frame has the same shape. Fourteen bytes of header, a payload, and a 4-byte trailer.

+-----------------+-----------------+-----------+------------------+-----+
| Destination MAC | Source MAC      | EtherType | Payload          | FCS |
| 6 bytes         | 6 bytes         | 2 bytes   | 46 to 1500 bytes | 4 B |
+-----------------+-----------------+-----------+------------------+-----+
|<---------- the 14-byte header --------------->|

Destination MAC and Source MAC are hardware addresses: 6 bytes each, written as six hex pairs like 02:00:0a:00:01:14. They name the receiving and sending interfaces on this one cable segment. Where they come from is the next lesson. How a sender learns which one to write in the destination field is ARP, in Lesson 8.

EtherType is 2 bytes announcing what the payload is, so the receiver knows which parser to hand it to:

EtherTypePayload
0x0800IPv4 packet
0x0806ARP message
0x86DDIPv6 packet

There is a rule hiding in those values. Every EtherType ever assigned is at least 0x0600, which is 1536, because the oldest 802.3 framing used these same 2 bytes as a length field. Payloads max out at 1500, so a value of 1500 or less means length, and 1536 or more means type. One glance at bytes 12 and 13 tells a receiver which dialect of Ethernet it is reading. Lesson 7 digs into EtherTypes properly.

Payload is the cargo, 46 bytes minimum. Ship less and the sender pads it with zeros up to 46.

FCS, the Frame Check Sequence, is a 4-byte CRC (cyclic redundancy check): a checksum computed over the whole frame, used by the receiver to detect corruption.

So the minimum frame is 14 + 46 + 4 = 64 bytes, and the classic maximum is 14 + 1500 + 4 = 1518 bytes. The 64 is not arbitrary. Original Ethernet was one shared cable, everyone talking on the same wire, and a sender could only detect a collision while it was still transmitting. Sixty-four bytes is 512 bit times, long enough for the first bit to reach the far end of the largest legal cable run before the sender finished. Collisions died with shared cables; the 64-byte floor is still enforced on every port made today.

Ethernet started as a 1973 memo by Bob Metcalfe at Xerox PARC, built with David Boggs, and named after the luminiferous ether, the invisible medium physicists once believed carried light. The first version ran at 2.94 Mbps because it borrowed its clock from the Alto workstation it was built for. The frame you are dissecting is called Ethernet II because it comes from version two of the 1982 DEC, Intel and Xerox spec, and it has not changed since.

Commit: where did four bytes go?

Do the arithmetic for the frame you captured: 14 header + 20 IP + 8 ICMP + 56 data = 98. But the anatomy says a frame ends with a 4-byte FCS, so on a physical wire this frame is 102 bytes. Your capture said 98. Commit to an explanation before you move on.

Read every field of the header

-V tells tshark to print every layer of a packet as a full tree, every field decoded. This is the reference view you will come back to for the rest of the track. Capture one packet with it.

Commit to a count before you run it. The anatomy named three fields in the Ethernet header. Will tshark spend three lines on them, or more?

cap -c 1 -V

prompt: student@frame-lab:~$ answer: cap -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 ... (interface and timing details) ... [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) Destination: 02:00:0a:00:01:14 (02:00:0a:00:01:14) Address: 02:00:0a:00:01:14 (02:00:0a:00:01:14) .... ..1. .... .... .... .... = LG bit: Locally administered address (this is NOT the factory default) .... ...0 .... .... .... .... = IG bit: Individual address (unicast) Source: 02:00:0a:00:01:0a (02:00:0a:00:01:0a) Address: 02:00:0a:00:01:0a (02:00:0a:00:01:0a) .... ..1. .... .... .... .... = LG bit: Locally administered address (this is NOT the factory default) .... ...0 .... .... .... .... = IG bit: Individual address (unicast) Type: IPv4 (0x0800) Internet Protocol Version 4, Src: 10.0.1.10, Dst: 10.0.1.20 ... (the IP, ICMP and data trees continue for another screen) ... 1 packet captured hint: One packet, fully decoded: cap -c 1 -V

Read it top to bottom. Frame 1 is not a protocol: it is capture metadata, where and when this frame was caught. Anything in square brackets, like [Protocols in frame: eth:ethertype:ip:icmp:data], is analysis tshark computed rather than bytes from the wire. That bracketed chain reads outside-in: an Ethernet frame whose type field announces IP, which carries ICMP, which carries data.

Then the envelope itself, Ethernet II, holding exactly the three fields from the anatomy: destination, source, type. Nothing else. The most interesting lines are the bit breakdowns under each address. The LG bit line says these are locally administered addresses: the 02 at the front marks a MAC assigned by software, guaranteed never to collide with a factory-burned address. This lab set them deliberately so every member sees identical bytes. The IG bit says each address is an individual interface, not a group. Both stories continue in the MAC lesson.

Expand only the layer you care about

-V prints everything, and everything is a lot. Picture pasting a frame into a ticket for a colleague: the Ethernet header is the evidence, and sixty lines of IP and ICMP underneath it are noise that buries the point.

The -O flag (capital O) expands one protocol in full and collapses the rest to single summary lines. It takes the protocol name exactly as it appears in the bracketed chain: eth, ip, icmp, tcp, arp.

Commit to one thing before you run it. Do the collapsed layers still get dissected, or does -O skip them entirely?

cap -c 1 -O eth

prompt: student@frame-lab:~$ answer: cap -c 1 -O eth 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 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) Destination: 02:00:0a:00:01:14 (02:00:0a:00:01:14) Address: 02:00:0a:00:01:14 (02:00:0a:00:01:14) .... ..1. .... .... .... .... = LG bit: Locally administered address (this is NOT the factory default) .... ...0 .... .... .... .... = IG bit: Individual address (unicast) Source: 02:00:0a:00:01:0a (02:00:0a:00:01:0a) Address: 02:00:0a:00:01:0a (02:00:0a:00:01:0a) .... ..1. .... .... .... .... = LG bit: Locally administered address (this is NOT the factory default) .... ...0 .... .... .... .... = IG bit: Individual address (unicast) Type: IPv4 (0x0800) Internet Protocol Version 4, Src: 10.0.1.10, Dst: 10.0.1.20 Internet Control Message Protocol 1 packet captured hint: Expand one dissector by name: cap -c 1 -O eth

Same frame, and this time the whole screen fits. Frame, Internet Protocol and Internet Control Message Protocol each collapsed to one line while Ethernet stayed fully open.

Collapsed does not mean skipped. tshark still dissected every layer, and every field is still there for filters and extraction to use. -O changes only what gets printed. That distinction matters shortly, when you filter on a field of a layer you are not even displaying.

Ask the interface for its limits

The anatomy said payloads stop at 1500 bytes. That per-interface ceiling is the MTU, the Maximum Transmission Unit, and you can read it. The interface lives inside the client namespace, and an interface exists in exactly one namespace at a time, so the query has to run inside that namespace. This is the full form the cap helper has been wrapping for you:

sudo ip netns exec client ip link show veth-c

prompt: student@frame-lab:~$ answer: sudo ip netns exec client ip link show veth-c ||| sudo ip netns exec client ip link show dev veth-c output: 4: veth-c@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 02:00:0a:00:01:0a brd ff:ff:ff:ff:ff:ff link-netns server hint: Run ip link show veth-c inside the client namespace: sudo ip netns exec client ip link show veth-c

mtu 1500 is the payload budget. It does not include the header: the 14 bytes ride on top, so the biggest frame a capture can show on this link is 1514 bytes. Datacenter links often raise the MTU to 9000, called jumbo frames, but every interface and switch on the path must agree or oversized frames quietly vanish. That failure mode gets its own treatment later in the track.

Two more details pay rent. link/ether 02:00:0a:00:01:0a is the same source MAC your dissection showed, this time read from the interface itself. And veth-c@if3 plus link-netns server name the other end of the virtual cable: veth interfaces are born in pairs, and a frame pushed into one end appears at the other. The index digits at the far left may differ on your VM; the names will not.

Turn frames into numbers

Decoded trees are for reading. For scripts, graphs and reports you want bare values, and that is -T fields: switch the output format to fields mode, then name each field you want with -e. The total frame size lives in the field frame.len.

Commit before you run it. Three packets, one field named. How many lines of actual data come back, and does the usual summary line survive?

cap -c 3 -T fields -e frame.len

prompt: student@frame-lab:~$ answer: cap -c 3 -T fields -e frame.len output: Running as user "root" and group "root". This could be dangerous. Capturing on 'veth-c' 98 98 98 3 packets captured hint: Fields mode, one field: cap -c 3 -T fields -e frame.len

Three frames, three bare numbers, nothing to clean up. Fields mode replaces the summary line entirely: what you ask for is all you get, ready for awk, sort or a monitoring pipeline.

Field names come from the dissection tree you read with -V, lowercased and dotted. The frame layer has frame.len; the Ethernet layer has eth.src, eth.dst and eth.type. Every field tshark can display, it can also extract.

Commit: which column moves?

Next you will extract four fields at once and read the traffic as a table: length, source MAC, destination MAC and EtherType for four packets. Ping flows both ways. Commit before you run it.

Watch the addresses trade places

Now run it and check your commitment.

cap -c 4 -T fields -e frame.len -e eth.src -e eth.dst -e eth.type

prompt: student@frame-lab:~$ answer: cap -c 4 -T fields -e frame.len -e eth.src -e eth.dst -e eth.type output: Running as user "root" and group "root". This could be dangerous. Capturing on 'veth-c' 98 02:00:0a:00:01:0a 02:00:0a:00:01:14 0x0800 98 02:00:0a:00:01:14 02:00:0a:00:01:0a 0x0800 98 02:00:0a:00:01:0a 02:00:0a:00:01:14 0x0800 98 02:00:0a:00:01:14 02:00:0a:00:01:0a 0x0800 4 packets captured hint: Fields mode with four -e flags: cap -c 4 -T fields -e frame.len -e eth.src -e eth.dst -e eth.type

Exactly as committed: request, reply, request, reply, with the middle two columns mirrored on every second line and the outer two constant. The columns are tab-separated on purpose, the natural input for cut and awk.

You have now read the whole 14-byte header without seeing a single raw byte. Time to fix that.

Read the raw bytes

Decoders can mislabel things; bytes cannot. Hexadecimal is base 16, digits 0 to 9 then a to f, and one byte is exactly two hex digits. The -x flag prints each captured frame as a hex dump: every byte, sixteen per row, with an ASCII rendering on the right where printable bytes show as characters and everything else as dots.

cap -c 1 -x

prompt: student@frame-lab:~$ answer: cap -c 1 -x 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=0x0521, seq=1/256, ttl=64 0000 02 00 0a 00 01 14 02 00 0a 00 01 0a 08 00 45 00 ..............E. 0010 00 54 39 4a 40 00 40 01 eb 41 0a 00 01 0a 0a 00 .T9J@[email protected]...... 0020 01 14 08 00 84 0d 05 21 00 01 05 20 75 6a 00 00 .......!... uj.. 0030 00 00 31 73 04 00 00 00 00 00 10 11 12 13 14 15 ..1s............ 0040 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 .......... !"#$% 0050 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 &'()*+,-./012345 0060 36 37 67 1 packet captured hint: Hex and ASCII, one packet: cap -c 1 -x

The left column is the byte offset, and it is hex too: row 0010 starts at byte 16, not byte 10. Now read the first row, because the entire envelope fits in it with two bytes to spare.

BytesHexField
0-502 00 0a 00 01 14Destination MAC (the server)
6-1102 00 0a 00 01 0aSource MAC (the client)
12-1308 00EtherType 0x0800: IPv4
14-1545 00Already the IP packet

Byte 14 is where the payload begins, and its value 45 decodes in halves: the 4 is the IP version, and the 5 says the IP header is five 4-byte words, 20 bytes. Almost every IPv4 packet starts 45, and 0x45 is the letter E in ASCII. A lone capital E in the ASCII column is how engineers spot IPv4 in any hex dump, and y

Practice The Ethernet Frame in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.

More lessons in What Moves on the Wire