Learn › The Network Stack › What Moves on the Wire
tshark -T fields - a hands-on Linux lab on a real virtual machine.
EtherType is the 2-byte tag in every Ethernet frame that tells the kernel which protocol is inside. One wire, multiple protocols, one tag to sort them.
In the last lesson you took one Ethernet frame apart byte by byte. The type field read Type: IPv4 (0x0800), and so did every other frame you captured.
That is not because this cable only carries IPv4. A second protocol crossed it while the lab was still booting, riding in exactly the same kind of frame, addressed to every machine on the segment at once. You never saw it. Capture right now and you still will not.
Two bytes are what separate the two protocols. This lesson finds those bytes, reads them off live traffic, and then forces the hidden protocol into the open so you can watch both share one wire.
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 are student on the machine ethertype-lab, and inside it live two namespaces joined by a virtual cable. 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 and id numbers in your own captures will differ from the printed screens. Every field this lesson is about will match.
The EtherType is a 2-byte (16-bit) field in the Ethernet frame header. It sits immediately after the source MAC address, and it answers one question: what protocol is inside this frame?
| Destination MAC | Source MAC | EtherType | Payload | CRC |
| 6 bytes | 6 bytes | 2 bytes | 46-1500 bytes| 4 B |
Think of it as the label on a shipping container. The container is the Ethernet frame. The cargo can be an IPv4 packet, an ARP message, or an IPv6 packet, and the container does not change shape for any of them. The label tells the dock workers, which here is your kernel, which department should unpack it.
Ethernet is a general-purpose delivery system. It does not care what is inside. That is the whole reason the label has to exist.
There are hundreds of registered EtherType values. Three of them account for nearly everything you will ever see:
| EtherType | Hex value | What it carries |
|---|---|---|
| IPv4 | 0x0800 | Internet Protocol version 4 packets |
| ARP | 0x0806 | Address Resolution Protocol messages |
| IPv6 | 0x86DD | Internet Protocol version 6 packets |
0x0800 is by far the most common. Nearly all regular traffic (web browsing, file transfers, API calls) is IPv4. 0x0806 appears when machines need to discover each other's MAC addresses. 0x86DD appears on networks that use IPv6.
The 0x prefix means the number is written in hexadecimal (base 16). 0x0800 is 2048 in decimal. You do not need to memorize the decimal values. Everyone uses the hex form, including every tool in this track.
This lab uses the same two-namespace network as the previous lessons:
[client] 10.0.1.10 <--------> 10.0.1.20 [server]
MAC: 02:00:0a:00:01:0a MAC: 02:00:0a:00:01:14
The server runs a simple web service on port 80. Four helpers do the plumbing for you: send-ping sends pings from client to server, send-curl fetches the web page, cap runs tshark inside the client namespace, and check-progress prints your score.
Every helper here tab-completes. Type send-p and press Tab and the shell finishes send-ping for you. Type che and Tab for check-progress. Build the habit now: it costs nothing and it removes a whole class of typo.
You already know how to pull single fields out of captured packets with tshark's -T fields option, naming each field with -e. The field name for the EtherType is eth.type.
A capture only shows traffic that flows while it is listening, so on the real VM you start the traffic first with send-ping &. The & puts it in the background so you get your prompt back. The practice terminal keeps ping traffic flowing for you, so here you type only the capture.
Before you press Enter, commit to a shape. Five packets, one field each: how many values come back, and how many different values among them?
cap -c 5 -T fields -e eth.type
prompt: student@ethertype-lab:~$ answer: cap -c 5 -T fields -e eth.type ||| cap -T fields -e eth.type -c 5 output: Running as user "root" and group "root". This could be dangerous. Capturing on 'veth-c' 0x0800 0x0800 0x0800 0x0800 0x0800 5 packets captured hint: Fields mode, one field, five packets: cap -c 5 -T fields -e eth.type
Two lines of preamble, then the field, then a count. The cap helper runs tshark as root inside the client namespace, so every capture in this lab opens with that warning and the name of the interface it is listening on. On the real VM the backgrounded send-ping also drops its own summary into the same terminal when it finishes, so expect the two to interleave.
Every packet shows 0x0800, and 0x0800 is IPv4. The ping command sends ICMP messages, and ICMP messages travel inside IPv4 packets. What tshark printed is the raw field: two bytes, four hex digits, exactly as they sit in the frame.
Extracting this field is the first of the five things your lab grades.
The -c 5 means "stop after capturing 5 packets", not "wait 5 seconds". send-ping sends three requests by default, six packets in all, and tshark needs a moment to start listening, so it can miss the first pair. If a capture ever sits there waiting, it is simply short of packets. Press Ctrl-C to stop it and print what it did catch, or send a longer burst with send-ping 10 &.
-e takes as many fields as you want to name, and tshark prints them as columns separated by tabs. Add the two MAC addresses in front of the tag and you have the entire Ethernet header, one frame per line.
cap -c 5 -T fields -e eth.src -e eth.dst -e eth.type
prompt: student@ethertype-lab:~$ answer: cap -c 5 -T fields -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' 02:00:0a:00:01:0a 02:00:0a:00:01:14 0x0800 02:00:0a:00:01:14 02:00:0a:00:01:0a 0x0800 02:00:0a:00:01:0a 02:00:0a:00:01:14 0x0800 02:00:0a:00:01:14 02:00:0a:00:01:0a 0x0800 02:00:0a:00:01:0a 02:00:0a:00:01:14 0x0800 5 packets captured hint: Same fields mode, three -e flags: cap -c 5 -T fields -e eth.src -e eth.dst -e eth.type
Three columns: source MAC, destination MAC, EtherType. Those three fields are the whole Ethernet header, everything in front of the payload. You are reading the frame header directly, with nothing decoded for you.
Watch which columns move. The two MAC columns swap on every second line, because each reply is a new frame addressed back the way the request came. The EtherType column does not move at all. It names the cargo, not the direction, and both directions are carrying IPv4.
This design has a name: protocol multiplexing. One wire carries multiple protocols, and the EtherType tells the receiver which one each frame belongs to.
When a frame arrives at your network interface, the kernel reads those two bytes and hands the payload to the matching handler:
0x0800 goes to the IPv4 stack0x0806 goes to the ARP handler0x86DD goes to the IPv6 stackWithout the tag the kernel would have no way to know whether the bytes after the header are an IP packet, an ARP message, or something else entirely. Guessing from the payload is not an option: an ARP message and an IP packet can start with the same byte values, and getting it wrong means handing hostile bytes to the wrong parser.
Switches and management tools use EtherTypes of their own. 0x88CC belongs to LLDP, the protocol switches use to announce themselves to their neighbors. A plain Linux box with no LLDP software running has no handler registered for it.
>>> It is dropped, quietly. The tag is the only routing decision the kernel makes at this layer, and an unrecognized tag has no destination. If you picked payload inspection, that is exactly what EtherType exists to avoid: the tag makes the decision cheap and unambiguous, and a parser only ever sees bytes that claim to be its own. If you picked a default handler, notice how dangerous that would be. Every stray frame on the segment would be fed to the IP parser as if it were a packet. One detail to carry forward: a capture tool still shows you the dropped frame, because tshark taps frames before that sorting step. What the kernel discards, a capture can still count.
The frame you are reading is called Ethernet II, from the second version of the specification that DEC, Intel and Xerox published in 1982. In their design, bytes 12 and 13 hold the EtherType.
A competing standard arrived from the IEEE at almost the same time. In 802.3 framing, those same two bytes are not a type at all. They are a length field, holding the size of the payload that follows.
Two standards, the same two bytes, two completely different meanings. Every receiver has to work out which dialect it is reading, and it gets exactly one clue: the value itself.
A frame arrives. You read bytes 12 and 13 out of the hex dump and they hold 05 dc, which is 1500 in decimal.
>>> It is a length. The rule is a clean split with no overlap. A value of 1536 (0x0600) or higher is an EtherType, which is modern Ethernet II. A value of 1500 (0x05DC) or lower is a length field, which is the older IEEE 802.3 format. Payloads stop at 1500 bytes, so no legal length can ever reach 1536, and that gap is exactly why every EtherType ever assigned starts at 0x0600. Those values below the line were reserved for the length reading. If you picked a new protocol, the number is the giveaway: no EtherType is allowed to live down there. If you picked corruption, nothing is wrong with this frame. It is simply the other dialect, and in practice you will almost never see it. Modern networks are Ethernet II from end to end.
You might see 0x8100 in some captures. That is the EtherType for VLAN-tagged frames (802.1Q). The VLAN tag inserts 4 extra bytes into the frame header, and the real EtherType follows after the tag. VLAN tagging gets its own lesson later in this track.
Fields mode gives you the raw value. The -V flag does the opposite: it prints every layer of a packet as a full tree, every field named and decoded. It is the reference view, and it is where you go when you want to know what a field is called.
Capture one packet with it and look for the Type: line inside the Ethernet section.
cap -c 1 -V
prompt: student@ethertype-lab:~$ answer: cap -c 1 -V output: Running as user "root" and group "root". This could be dangerous. Capturing on 'veth-c' ... (the Frame metadata block: capture time, length, protocol chain) ... Ethernet II, Src: 02:00:0a:00:01:0a, Dst: 02:00:0a:00:01:14 Destination: 02:00:0a:00:01:14 ... (address and bit-flag sub-lines) ... Source: 02:00:0a:00:01:0a ... (address and bit-flag sub-lines) ... Type: IPv4 (0x0800) ... (the IPv4 and ICMP trees continue for another screen) ... 1 packet captured hint: One packet, fully decoded: cap -c 1 -V
There it is: Type: IPv4 (0x0800). tshark decodes the raw hex value into a human-readable protocol name and prints both, the name for you and the number for the wire. This is the field telling the kernel that the payload is an IPv4 packet.
The lines marked as trimmed are real on your screen. -V expands the destination and source into their own address and bit-flag sub-lines, and it prints the frame, IPv4 and ICMP layers around the Ethernet one. Below the Ethernet section you will find the IPv4 header (source IP, destination IP, TTL, protocol) and then the ICMP message, which is the actual ping. Each layer peels open to reveal the next one inside.
A full dissection is the second of the five things your lab grades.
Every capture so far has shown 0x0800. There is another protocol on this network, and it uses EtherType 0x0806: ARP, the Address Resolution Protocol, the thing machines use to find each other's MAC addresses.
To see only the packets you care about, use a display filter with the -Y flag. A display filter is a test written against dissected fields, and tshark shows only the packets that pass it. arp is a shortcut filter that matches any packet with EtherType 0x0806.
So cap -c 5 -Y arp should show you the second protocol on this wire. Before you run it, take a position.
>>> No packet lines at all. That is not a broken command, and the filter is spelled correctly. It is how ARP works. The kernel keeps a small table of the IP-to-MAC answers it already has, called the neighbor cache, and it only sends an ARP request when the answer is missing. This lab pinged the server once while it was building itself, so the client already knows the server MAC and has no reason to ask again. If you picked the two lines, that is the right expectation for a cold machine and the wrong one for this warm one. If you picked an error, note the shape of what you get instead: a capture that displays nothing still captured five packets and still prints its count. Silence from a filter is data, not a fault.
To watch ARP you have to give the client a reason to ask. Clear the cached answer and the next ping has to go looking for it.
ip neigh is the neighbor cache. flush dev veth-c empties the entries learned on that interface. The interface lives inside the client namespace, and an interface exists in exactly one namespace at a time, so the command has to run in there.
Expect no output at all when it works.
sudo ip netns exec client ip neigh flush dev veth-c
prompt: student@ethertype-lab:~$ answer: sudo ip netns exec client ip neigh flush dev veth-c ||| sudo ip netns exec client ip n flush dev veth-c ||| sudo ip netns exec client ip neighbour flush dev veth-c output: hint: Run it inside the client namespace: sudo ip netns exec client ip neigh flush dev veth-c
Silence, and a prompt back. That is the flush working. The client has now forgotten which MAC address belongs to 10.0.1.20, and the next packet aimed at that address cannot be addressed until it asks.
This command is half of the fifth thing your lab grades. The other half is the capture you will build at the end of the lesson.
One detail decides whether this works. The ARP exchange happens in the first milliseconds of the ping, and tshark needs a moment to start listening. A ping launched at the same instant has already finished asking before the capture is running.
So on the real VM you hold the ping back two seconds: (sleep 2; send-ping) & starts the delay in the background, and you type the capture straight after. Here the practice terminal handles the timing, so you type only the capture.
Same command you predicted on, now against a cold cache.
cap -c 5 -Y arp
prompt: student@ethertype-lab:~$ answer: cap -c 5 -Y arp ||| cap -c 5 -Y "arp" ||| cap -c 5 -Y 'arp' ||| cap -c 5 -Y "eth.type == 0x0806" ||| cap -c 5 -Y 'eth.type == 0x0806' output: Running as user "root" and group "root". This could be dangerous. Capturing on 'veth-c' 1 0.000000000 02:00:0a:00:01:0a -> ff:ff:ff:ff:ff:ff ARP 42 Who has 10.0.1.20? Tell 10.0.1.10 2 0.000039804 02:00:0a:00:01:14 -> 02:00:0a:00:01:0a ARP 42 10.0.1.20 is at 02:00:0a:00:01:14 5 packets captured hint: The display filter flag with the arp shortcut: cap -c 5 -Y arp
Two lines out of the five packets captured, and the second protocol is finally visible. The timestamps are seconds since the capture started, so yours will not match these to the microsecond. The two lines themselves will.
Look at where the first packet is going: ff:ff:ff:ff:ff:ff. That is the broadcast MAC address, and it means the frame goes to every device on the network. The client does not know who owns 10.0.1.20, so it asks everybody: "Who has 10.0.1.20? Tell 10.0.1.10." The reply comes back to one address with the answer: "10.0.1.20 is at 02:00:0a:00:01:14."
You could have written this filter the long way as -Y "eth.type == 0x0806". Both do the same job, because the arp shortcut is nothing more than a name for that value. Filtering for ARP is the third of the five things your lab grades.
Flushing is needed every time you want to watch ARP again. One ping refills the cache, and the entry then survives for minutes. If a capture shows no ARP, that is your first suspect, not a mistake in the filter.
Now point the same flag at the other protocol. ip is the shortcut filter for IPv4, matching anything tagged 0x0800.
Before you run it, commit to a number: how many of the five captured packets pass this filter, given what you just saw pass the ARP one?
cap -c 5 -Y ip
prompt: student@ethertype-lab:~$ answer: cap -c 5 -Y ip ||| cap -c 5 -Y "ip" ||| cap -c 5 -Y 'ip' 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.000039804 10.0.1.20 -> 10.0.1.10 ICMP 98 Echo (ping) reply id=0x0521, seq=1/256, ttl=64 (request in 1) 3 1.023951120 10.0.1.10 -> 10.0.1.20 ICMP 98 Echo (ping) request id=0x0521, seq=2/512, ttl=64 4 1.023993516 10.0.1.20 -> 10.0.1.10 ICMP 98 Echo (ping) reply id=0x0521, seq=2/512, ttl=64 (request in 3) 5 2.048013207 10.0.1.10 -> 10.0.1.20 ICMP 98 Echo (ping) request id=0x0521, seq=3/768, ttl=64 5 packets captured hint: The display filter flag with the shortcut for IPv4: cap -c 5 -Y ip
Only ICMP ping packets, which ride inside IPv4. No ARP anywhere. Five captured, five displayed, because the cache was warm again by the time this capture ran.
Two details in the lines themselves. The id is picked by ping when it starts, so yours will be a different number. The sequence climbs by one per request, which
Practice EtherType in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.