Kea, Raw Sockets, and ICMP

Posted on May 4, 2024

The other day I was taking a look at kea, an open-source DHCP server from ISC for my home network.

I installed it on my gateway, made the minimal initial configurations, and fired it up.

Then I checked my laptop and confirmed that it had successfully been assigned an IP address from the range I had allocated.

So far so good.

I was about to proceed to checking out the other parts of the configuration and the documentation.

However, I had a sudden realization:

“Wait a minute, I haven’t enabled port 67 on my gateway’s firewall. How is it accepting DHCP requests?”

All of my machines are configured so that it drops any ingress traffic by default.

My gateway is no exception to this. Sure enough port 67 had not been enabled when I confirmed my firewall rules with iptables.

What gives?

Then I remembered reading something about raw sockets in the config file.

From kea-dhcp4.conf:

// Kea DHCPv4 server by default listens using raw sockets. This ensures
// all packets, including those sent by directly connected clients
// that don't have IPv4 address yet, are received. However, if your
// traffic is always relayed, it is often better to use regular
// UDP sockets. If you want to do that, uncomment this line:
// "dhcp-socket-type": "udp"

Raw Sockets

Specifically, kea uses a raw socket with the address family set to AF_PACKET.

Packet sockets allow a process to send and receive layer 2 frames without the kernel’s intervention.

That is, when data is received on the network interface it is handed to the user space process without being processed by the kernel.

This explains why kea had been able to respond to DHCP requests despite it being disabled in iptables; the packets simply by-pass the kernel where the packet headers would be inspected.

In order to demonstrate this idea, I wrote an ICMP “echo server”1 which listens for ICMP Echo Requests and replies to them.

The following is an excerpt from the code. The full source code can be found here.

#include <stdio.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/ethernet.h>

#include "packet.h"
#include "error.h"
#include "utils.h"

int 
main(void)
{
	int			sockfd;
	packetbuf_t	recvbuf, sendbuf;

	if ((sockfd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL))) == -1)
		ERR_EXIT("socket failed");

	init_packetbuf(&recvbuf);
	init_packetbuf(&sendbuf);

	while (1) {
		receive_packet(sockfd, &recvbuf);
		craft_packet(&sendbuf, &recvbuf);
		send_packet(sockfd, &sendbuf);
	}

	exit(EXIT_SUCCESS);
}

The following is a short demo:

Interesting ICMP Behaviors

Here are some interesting things I encountered while working on the above.

Truncated

This happened when I tried to send an icmp packet with no data (only the IP header and ICMP header).

[vilr0i@cyberia ~]$ ping 192.168.122.141
PING 192.168.122.141 (192.168.122.141) 56(84) bytes of data.
8 bytes from 192.168.122.141: icmp_seq=1 ttl=64 (truncated)
8 bytes from 192.168.122.141: icmp_seq=2 ttl=64 (truncated)
8 bytes from 192.168.122.141: icmp_seq=3 ttl=64 (truncated)
8 bytes from 192.168.122.141: icmp_seq=4 ttl=64 (truncated)
8 bytes from 192.168.122.141: icmp_seq=5 ttl=64 (truncated)
^C

Wrong Data Byte

The following occurred when the data portion for the icmp message had been filled with zeros.

[vilr0i@cyberia ~]$ ping 192.168.122.141
PING 192.168.122.141 (192.168.122.141) 56(84) bytes of data.
64 bytes from 192.168.122.141: icmp_seq=1 ttl=64 time=1714850526716 ms
wrong data byte #16 should be 0x10 but was 0x0
#16     0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
#48     0 0 0 0 0 0 0 0 
64 bytes from 192.168.122.141: icmp_seq=2 ttl=64 time=1714850527717 ms
wrong data byte #16 should be 0x10 but was 0x0
#16     0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
#48     0 0 0 0 0 0 0 0 

Resources and Further Reading


  1. Yes, the notion of an ICMP Echo Server doesn’t make much sense. ICMP is usually handled in layer 3 (the kernel, etc), so if a device receives an ICMP Echo request, a Reply is made independent to any user-space processes. In fact, if you run the program I wrote on a machine where ICMP is not disabled, it results in the client receiving a duplicate Reply. ↩︎