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 and 0x86DD 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.
  • 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:

  1. __u8 u6_addr8[16]: By consuming sixteen u8 bits or 1 byte chunks.
  2. __be16 u6_addr16[8]: By consuming eight u16 bits or 2 bytes chunks.
  3. __be32 u6_addr32[4]: By consuming four u32 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.
  • Notice how the fields are defined as 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.
  • Notice 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.