8 minutes
Network Headers
Network Headers
While writing the part two of Building an Efficient Network Flow Monitoring Tool with eBPF, I felt the need to visualize and lay out a foundation on how packet headers are defined in the Linux kernel to make it easier to grasp the concepts there. Initially, I was embedding the contents of this blog there but I noticed it would turn into a pretty long or perhaps a boring read. Alas, I decided to make a separate and preliminary post, covering these topics.
For the sake of simplicity, I will refer to frames, packets and segments as packets here.
Ethernet Header
This is how the Ethernet header is defined:
struct ethhdr { /* Offset Size */
unsigned char h_dest[6]; /* 0 6 */
unsigned char h_source[6]; /* 6 6 */
__be16 h_proto; /* 12 2 */
};
That visually translates to:
0 8 16 24 31
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| EtherType | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
| Payload |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- The size of the Ethernet header is 14 bytes.
- Source and Destination address (MAC address) fields are each 48 bits or 6 bytes.
Ethertype
field is used to determine the higher level protocol,0x800
for IPv4 and0x86DD
for IPv6 packets and so on.Ethertype
field is 16 bits or 2 bytes and is Big Endian.
Link to Ethernet header source code.
IPv4 Header
This is how the IPv4 header is defined:
struct iphdr { /* Offset Size */
__u8 version:4; /* 0: 0 1 */
__u8 ihl:4; /* 0: 4 1 */
__u8 tos; /* 1 1 */
__be16 tot_len; /* 2 2 */
__be16 id; /* 4 2 */
__be16 frag_off; /* 6 2 */
__u8 ttl; /* 8 1 */
__u8 protocol; /* 9 1 */
__sum16 check; /* 10 2 */
union {
struct {
__be32 saddr; /* 12 4 */
__be32 daddr; /* 16 4 */
}; /* 12 8 */
struct {
__be32 saddr; /* 12 4 */
__be32 daddr; /* 16 4 */
} addrs; /* 12 8 */
}; /* 12 8 */
/* size: 20, cachelines: 1, members: 10 */
/* last cacheline: 20 bytes */
};
That visually translates to:
0 8 16 24 31
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- The size of the IPv4 header is 20 bytes.
- Options field is optional and variable in size. For instance it can add another 20 bytes if Maximum segment size, SACK permitted, Timestamps, No-Operation (NOP) and Window scale flags are set
- Source and destination IPv4 addresses are each 32 bits or 4 bytes.
- Source and destination addresses can be utilized in two ways:
// Named struct
struct iphdr ip_header;
ip_header.addrs.saddr = source_address;
ip_header.addrs.daddr = destination_address;
// Or anonymous struct
ip_header.saddr = source_address;
ip_header.daddr = destination_address;
Link to IPv4 header source code.
IPv6 Header
This is how the IPv6 header is defined:
struct ipv6hdr { /* Offset Size */
__u8 priority:4; /* 0: 0 1 */
__u8 version:4; /* 0: 4 1 */
__u8 flow_lbl[3]; /* 1 3 */
__be16 payload_len; /* 4 2 */
__u8 nexthdr; /* 6 1 */
__u8 hop_limit; /* 7 1 */
union {
struct {
struct in6_addr saddr; /* 8 16 */
struct in6_addr daddr; /* 24 16 */
}; /* 8 32 */
struct {
struct in6_addr saddr; /* 8 16 */
struct in6_addr daddr; /* 24 16 */
} addrs; /* 8 32 */
}; /* 8 32 */
/* size: 40, cachelines: 1, members: 7 */
/* last cacheline: 40 bytes */
};
That visually translates to:
0 8 16 24 31
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Traffic Class | Flow Label |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Length | Next Header | Hop Limit |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Source Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Destination Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- The size of the IPv6 header is 40 bytes.
- Source and destination IPv6 addresses are each 128 bits or 16 bytes.
- Similar to IPv4, source and destination address structs can be utilized in two ways, named or anonymously.
- The
version
field shows 4, this is the size of the field not to be confused with the actual IP version. - IPv6 does not utilize a checksum unlike IPv4 that checksums the IPv4 header.
Notice that saddr
and daddr
are inside a union
type. It gives us three ways to utilize it:
__u8 u6_addr8[16]
: By consuming sixteenu8
bits or 1 byte chunks.__be16 u6_addr16[8]
: By consuming eightu16
bits or 2 bytes chunks.__be32 u6_addr32[4]
: By consuming fouru32
bits or 4 bytes chunks.
Link to IPv6 header source code.
TCP Header
This is how the TCP header is defined:
struct tcphdr { /* Offset Size */
__be16 source; /* 0 2 */
__be16 dest; /* 2 2 */
__be32 seq; /* 4 4 */
__be32 ack_seq; /* 8 4 */
__u16 res1:4; /* 12: 0 2 */
__u16 doff:4; /* 12: 4 2 */
__u16 fin:1; /* 12: 8 2 */
__u16 syn:1; /* 12: 9 2 */
__u16 rst:1; /* 12:10 2 */
__u16 psh:1; /* 12:11 2 */
__u16 ack:1; /* 12:12 2 */
__u16 urg:1; /* 12:13 2 */
__u16 ece:1; /* 12:14 2 */
__u16 cwr:1; /* 12:15 2 */
__be16 window; /* 14 2 */
__sum16 check; /* 16 2 */
__be16 urg_ptr; /* 18 2 */
/* size: 20, cachelines: 1, members: 17 */
/* last cacheline: 20 bytes */
};
That visually translates to:
0 8 16 24 31
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Offset| Res. | Flags | Window |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- The size of the TCP header is 20 bytes.
- Options field can be between 0 to 40 bytes.
- Notice how the fields are defined as __be16 or Big Endian.
Link to TCP header source code.
UDP Header
This is how the UDP header is defined:
struct udphdr { /* Offset Size */
__be16 source; /* 0 2 */
__be16 dest; /* 2 2 */
__be16 len; /* 4 2 */
__sum16 check; /* 6 2 */
};
That visually translates to:
0 8 16 24 31
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- The size of the UDP header is 8 bytes.
- See how much simpler the UDP header is in comparison to TCP.
Link to UDP header source code.
ICMP Header
This is how the ICMP header is defined:
struct icmphdr { /* Offset Size */
__u8 type; /* 0 1 */
__u8 code; /* 1 1 */
__sum16 checksum; /* 2 2 */
union {
struct {
__be16 id; /* 4 2 */
__be16 sequence; /* 6 2 */
} echo; /* 4 4 */
__be32 gateway; /* 4 4 */
struct {
__be16 __unused; /* 4 2 */
__be16 mtu; /* 6 2 */
} frag; /* 4 4 */
__u8 reserved[4]; /* 4 4 */
} un; /* 4 4 */
/* size: 8, cachelines: 1, members: 4 */
/* last cacheline: 8 bytes */
};
That visually translates to:
0 8 16 24 31
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Message Body +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- The size of the ICMP header is 8 bytes.
- All ICMP codes are defined here.
Link to ICMP header source code.
ICMPv6 Header
This is how the ICMPv6 header is defined:
struct icmp6hdr { /* Offset Size */
__u8 icmp6_type; /* 0 1 */
__u8 icmp6_code; /* 1 1 */
__sum16 icmp6_cksum; /* 2 2 */
union {
__be32 un_data32[1]; /* 4 4 */
__be16 un_data16[2]; /* 4 4 */
__u8 un_data8[4]; /* 4 4 */
struct icmpv6_echo u_echo; /* 4 4 */
struct icmpv6_nd_advt u_nd_advt; /* 4 4 */
struct icmpv6_nd_ra u_nd_ra; /* 4 4 */
} icmp6_dataun; /* 4 4 */
/* size: 8, cachelines: 1, members: 4 */
/* last cacheline: 8 bytes */
};
That visually translates to:
0 8 16 24 31
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Message Body +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- The size of the ICMPv6 header is 8 bytes.
- All ICMPv6 codes are defined here.
Link to ICMP header source code.
What’s Next?
Stay tuned for the second part of Building an Efficient Network Flow Monitoring Tool with eBPF post!
Thanks for reading.
Resources
The ASCII art has been generated using protocol and protocolpro.