DMYTRO SHYTYI

KD-Project_2: kernel module sk_buff send packet

Linux kernel would be huge without module support. With modules, we are able to add a code that runs in kernel space to extend the functionality. In this post will present the simple Loadable Kernel Module (LKM) that sends ethernet link layer frame (sent on broadcast address) which carries IP packet with UDP payload. We are currently running the 4.4.0-116-generic kernel. The full code you may find in the github repo: OPEN_ME.

The figure below shows the result of the networking LKM hello world: the packet, captured by wireshark contains the “hello world” message 🙂 

We are using VM with Ubuntu, thus to update we use the next command:

sudo apt update

We need linux headers to build kernel code, thus we will install them

sudo apt install linux-headers-$(uname -r)

The Makefile is presented below. It will compile your lkm.c file to get kernel object.

obj-m+=lkm.o all:         
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules clean:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean

Init function that is executed after LKM is loaded is presented below. We are setting the dst link layer address to broadcast and we send on the enp0s3 interface specific to ubuntu.
You could find your interfaces by typing “ip a sh” command. Also, we set the IP protocol to be used when building the packet. Finally, the init function calls “send_my” func that is responsible for the building of the packet and in the end prints “hello” message to the “/var/log/kernel.log”.

static int __init helloLKM_init(void){
         uint16_t proto;
         static char addr[ETH_ALEN] = {0xff,0xff,0xff,0xff,0xff,0xff};         uint8_t dest_addr[ETH_ALEN];
         struct net_device *enp0s3;
         enp0s3 = dev_get_by_name(&init_net,"enp0s3");
         memcpy (dest_addr, addr,ETH_ALEN);
         proto = ETH_P_IP;
         send_my(enp0s3,dest_addr,proto);
         printk(KERN_INFO "Hello from the  LKM!\n" );
         return 0; 
}

At the beginning of the “send_my” function we set up the src/dst IP addresses and filling the UDP payload. We define the lengths of UDP header, IP header, UDP payload and IP payload.

int send_my(struct net_device* dev, uint8_t dest_addr [ETH_ALEN] , uint16_t proto){
int ret;
unsigned char* data;char *srcIP = "192.168.0.1";
char *dstIP = "192.168.0.2";
char *hello_world = ">>> KERNEL sk_buff Hello World <<< by Dmytro Shytyi";
int data_len = 51;
int udp_header_len = 8;
int udp_payload_len = data_len;
int udp_total_len = udp_header_len+udp_payload_len;
int ip_header_len = 20;
int ip_payload_len = udp_total_len;
int ip_total_len = ip_header_len + ip_payload_len;

The next code snippet allocates a network buffer with Ethernet header + IP header + UDP header + UDP payload. We set the device in the skb_buff and define the packet type. Also we are allocating some space in the HEAD room for the ETH+IP+UDP headers. skb_reserve increase the headroom of skb by reducing the tail room.

struct sk_buff* skb = alloc_skb(ETH_HLEN+ip_total_len, GFP_ATOMIC);//allocate a network buffer
skb->dev = dev;
skb->pkt_type = PACKET_OUTGOING;
skb_reserve(skb, ETH_HLEN+ip_header_len+udp_header_len);//adjust headroom

This snippet of the code is actually put “hello world” in the DATA room of the sk_buff:

data = skb_put(skb,udp_payload_len);
memcpy(data, hello_world, data_len);

The next step is to add UDP header. To do that we  adjust the HEAD/DATA room with skb_push function. Here we set the len: UDP header+payload and give the ports:

struct udphdr* uh = (struct udphdr*)skb_push(skb,udp_header_len);  
uh->len = htons(udp_total_len);
uh->source = htons(15934);
uh->dest = htons(15904);

Further, we add IP header where we describe the version, IP header+IP payload length (i.e. UDPh+UDPpayload). We set the Time To Live, source, destination addresses and protocol as a payload in the IP.

struct iphdr* iph = (struct iphdr*)skb_push(skb,ip_header_len);   iph->ihl = ip_header_len/4;//4*5=20 ip_header_len
iph->version = 4; // IPv4u
iph->tos = 0;
iph->tot_len=htons(ip_total_len);
iph->frag_off = 0;
iph->ttl = 64; // Set a TTL.
iph->protocol = IPPROTO_UDP; //  protocol.
iph->check = 0;
iph->saddr = inet_addr(srcIP);
iph->daddr = inet_addr(dstIP);

The pre-final steps are:

  • set src/dest link layer addresses:
  • set the IP in the skb->protocol;
  • disable frame checksum
  • set the type of the packet

And finally, when we have built the skb buffer, we send it to the queue of the device with dev_queue_xmit.

/* changing Mac address */   
struct ethhdr* eth = (struct ethhdr*)skb_push(skb, sizeof (struct ethhdr));//add data to the start of a buffer
skb->protocol = eth->h_proto = htons(proto);
skb->no_fcs = 1;
memcpy(eth->h_source, dev->dev_addr, ETH_ALEN);
memcpy(eth->h_dest, dest_addr, ETH_ALEN); /* set packet type and send the packet. */
skb->pkt_type = PACKET_OUTGOING;
ret = dev_queue_xmit(skb);
return 1;