Packets.
Byte By Byte, No Wireshark Required.
A static reference for the four headers you end up hand-parsing most often: Ethernet, IPv4, TCP, and UDP. Each one is laid out here in wire order with byte offsets, so you can read a hex dump cold and know what you're looking at before you ever open a capture tool. Once you do open one, the same fields (tcp.flags, ip.ttl, and the rest) are what the Wireshark Display Filters cheatsheet filters on.
Ethernet Header
The first 14 bytes of any raw frame, fixed length, no exceptions. Everything else in this reference sits on top of it.
Ethernet II Header (14 bytes, fixed)
Frame offset 0: the very first byte captured ├── Destination MAC 6 bytes offset 0–5 ├── Source MAC 6 bytes offset 6–11 └── EtherType 2 bytes offset 12–13 // tells the next parser what sits inside the payload Common EtherType values ├── 0x0800 IPv4 ├── 0x0806 ARP └── 0x86DD IPv6 // there's no length field here: Ethernet doesn't say how big the payload is, IP's Total Length does that job instead
IP Header (IPv4)
Starts right where Ethernet ends, at offset 14. Minimum 20 bytes, longer only when options are present.
IPv4 Header (20 bytes minimum, up to 60 with options)
Frame offset 14: first byte right after the Ethernet header ├── Version / IHL 1 byte offset 14 │ └── high nibble = version (4 for IPv4), low nibble = IHL, header length in 4-byte words ├── Type of Service 1 byte offset 15 ├── Total Length 2 bytes offset 16–17 │ └── // whole IP packet, header plus payload, in bytes ├── Identification 2 bytes offset 18–19 ├── Flags / Fragment Offset 2 bytes offset 20–21 │ └── 3 flag bits (reserved, DF, MF) and a 13-bit fragment offset packed into the same two bytes ├── TTL 1 byte offset 22 ├── Protocol 1 byte offset 23 │ └── 6 = TCP, 17 = UDP, 1 = ICMP ├── Header Checksum 2 bytes offset 24–25 ├── Source IP 4 bytes offset 26–29 └── Destination IP 4 bytes offset 30–33 // IHL = 5 (no options) means this header is exactly 20 bytes, offsets 14 through 33
TCP Header
The connection-state header. Minimum 20 bytes, and the offsets below assume a 20-byte IP header ahead of it with no options.
TCP Header (20 bytes minimum, up to 60 with options)
Frame offset 34: right after a 20-byte IP header ├── Source Port 2 bytes offset 34–35 ├── Destination Port 2 bytes offset 36–37 ├── Sequence Number 4 bytes offset 38–41 ├── Acknowledgment Number 4 bytes offset 42–45 ├── Data Offset / Flags 2 bytes offset 46–47 │ └── high nibble = data offset, header length in 4-byte words; low bits carry the flags: │ SYN, ACK, FIN, RST, PSH, URG ├── Window 2 bytes offset 48–49 ├── Checksum 2 bytes offset 50–51 └── Urgent Pointer 2 bytes offset 52–53 // a bare SYN sets only that bit with ACK=0 and no data; a SYN/ACK sets both bits in the same flags byte
UDP Header
Eight bytes, always. No sequence numbers, no acknowledgment, no state to track between packets.
UDP Header (8 bytes, fixed, RFC 768)
Frame offset varies: same starting point TCP would occupy, right after the IP header ├── Source Port 2 bytes ├── Destination Port 2 bytes ├── Length 2 bytes │ └── // UDP header plus payload, in bytes, the same job IP's Total Length does one layer up └── Checksum 2 bytes No sequence number, no acknowledgment number, no flags // deliberately simpler than TCP: 8 bytes total, and nothing here tracks connection state
Reading It By Hand
The practical payoff of knowing this layout: finding a header in a raw hex dump with nothing but a byte counter.
Finding the TCP Header In A Hex Dump
14 (0x0E) every single time. From there the math depends on one nibble: read the byte sitting at offset 14, take its low nibble, that's the IHL, and multiply it by 4. That product is the IP header's real length in bytes. Add it to 14 and you land on the first byte of whatever transport header comes next. With no IP options, IHL is 5, the IP header runs exactly 20 bytes, and TCP or UDP starts at offset 34 (0x22). Any IP options push that starting offset further out, so checking the IHL nibble first is what keeps a hand count from landing you three bytes into someone else's field.