Linux kernel, Linux system development, Networking, sk_buff, Software

Printing sk_buff data

Sometimes when working with network packets inside the Linux kernel it might be very useful to print packet contents to see what actually going on.
Here I’m describing how to print packet from sk_buff structure and analyze this data with Wireshark.
In this short note I will not describe capturing of the packets inside the kernel but only show how to print contents of the sk_buff.

Struct sk_buff it’s a famous Linux kernel structure which hold network packet (with all headers) during travel through the Linux network stack.

As you probably know sk_buff contains few pointers which represents different regions in the one memory which contains all data of the packet.
Pointers ‘data‘ and ‘tail‘ may be changed on different layers of the network stack.

Image credits: kernel.org

Let’s describe reception of the packet. In the initial state ‘data‘ points directly to the beginning of the packet, on the Ethernet header.
It’s L2 layer of the network stack. On the L3 layer ‘data’ pointer incremented by the size of the Ethernet header and points on the IP header. And so on.
But we still can access Start of the packet and Ethernet header because data is still here.
Linux kernel provides set of functions to get access to the different layers headers and sk_buff pointers manipulation. It’s highly recommended to use this functions instead of direct pointers access. Please refer include/linux/skbuff.h

If we want to print full packet with network header we need to reach pointer to the mac header.
Function skb_mac_header() can help us.
Let’s check how this function implemented

static inline unsigned char *skb_mac_header(const struct sk_buff *skb)
{
    return skb->head + skb->mac_header;
}

As you can see this function is very simple. Result is just offset from the sk_buff memory start (head) by some value from mac_header variable. This variable initialized during packet reception in driver (or in the stack during packet generation and transmission).

You can see function skb_reset_mac_header() which set mac_header to the position of the ‘data’ pointer. This might be useful during initial construction of the packet inside sk_buff.

Also you my know function eth_hdr()
This function is just a simple wrapper around skb_mac_header() with type casting.

static inline struct ethhdr *eth_hdr(const struct sk_buff *skb)
{
    return (struct ethhdr *)skb_mac_header(skb);
}

Now we can get pointer to the whole packet so it’s time to print some data.

We can print Ethernet header with source/destination addresses and protocol number if it’s required.

struct ethhdr *ether = eth_hdr(skb);

printk("Source: %x:%x:%x:%x:%x:%x\n", ether->h_source[0], ether->h_source[1], ether->h_source[2], ether->h_source[3], ether->h_source[4], ether->h_source[5]);
printk("Destination: %x:%x:%x:%x:%x:%x\n", ether->h_dest[0], ether->h_dest[1], ether->h_dest[2], ether->h_dest[3], ether->h_dest[4], ether->h_dest[5]);
printk("Protocol: %d\n", ether->h_proto);

Please note that protocol number is in network byte order.

Typically network packets printed as hex string by 16 bytes in one line and with line numbering.
Something like this:

000000 FF FF FF FF FF FF 20 CF 30 38 56 A1 08 06 00 01
000010 08 00 06 04 00 01 20 CF 30 38 56 A1 C0 A8 01 02
000020 00 00 00 00 00 00 C0 A8 01 05 00 00 00 00 00 00 
000030 00 00 00 00 00 00 00 00 00 00 00 00

To get such output we can write simple function

void pkt_hex_dump(struct sk_buff *skb)
{
    size_t len;
    int rowsize = 16;
    int i, l, linelen, remaining;
    int li = 0;
    uint8_t *data, ch; 

    printk("Packet hex dump:\n");
    data = (uint8_t *) skb_mac_header(skb);

    if (skb_is_nonlinear(skb)) {
        len = skb->data_len;
    } else {
        len = skb->len;
    }

    remaining = len;
    for (i = 0; i < len; i += rowsize) {
        printk("%06d\t", li);

        linelen = min(remaining, rowsize);
        remaining -= rowsize;

        for (l = 0; l < linelen; l++) {
            ch = data[l];
            printk(KERN_CONT "%02X ", (uint32_t) ch);
        }

        data += linelen;
        li += 10; 

        printk(KERN_CONT "\n");
    }
}

KERN_CONT in printk allows us to add data in to message buffer without flushing and without printing module name (and other information) in the beginning of every string. Except time 🙂

After executing this function you can find in dmesg something like this:

[ 1869.042384] 000000   FF FF FF FF FF FF 20 CF 30 38 56 A1 08 06 00 01
[ 1869.042424] 000010   08 00 06 04 00 01 20 CF 30 38 56 A1 C0 A8 01 02 
[ 1869.042463] 000020   00 00 00 00 00 00 C0 A8 01 05 00 00 00 00 00 00 
[ 1869.042502] 000030   00 00 00 00 00 00 00 00 00 00 00 00

To analyze this data with Wireshark (which is very handy) we need to copy this text in some text file (for example packet_dump.txt), remove time stamps and convert this text in to binary pcap format.

We need text2pcap utility which can be found in most Linux distros.

Run the following command:

cat packet_dump.txt | awk '{$1=""; print $0}' | text2pcap - hex_dump.pcap

Now open resulting hex_dump.pcap with Wireshark.

I can recommend to use this method to print and analyze only small and medium size packets. Large packets may hang or slow down you system during printing.

Thanks for reading!

Tagged , , ,

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.