How to Parse a Raw UDP Header in C
Parsing a raw UDP header in C involves capturing a raw network packet buffer, identifying the byte offset where the UDP segment begins, and mapping that memory directly onto a standard UDP header structure. This guide explains the layout of a UDP header, how to map binary packet data into C structures, handle endianness conversions, and access fields such as source and destination ports, payload length, and checksums.
The UDP Header Structure
The User Datagram Protocol (UDP) header is fixed at 8 bytes (64 bits) in length and consists of four 16-bit fields:
- Source Port (16 bits): Port number of the sender.
- Destination Port (16 bits): Port number of the receiver.
- Length (16 bits): Total length of the UDP header plus payload in bytes (minimum 8 bytes).
- Checksum (16 bits): Error-checking field covering the header, payload, and an IP pseudo-header.
Defining the UDP Header in C
Standard Linux and Unix environments provide predefined structures in
<netinet/udp.h>. The standard structure is
struct udphdr:
#include <netinet/udp.h>
/*
struct udphdr {
u_int16_t uh_sport; // Source port
u_int16_t uh_dport; // Destination port
u_int16_t uh_ulen; // UDP length
u_int16_t uh_sum; // Checksum
};
*/Alternatively, you can define a custom packed structure:
#include <stdint.h>
struct custom_udphdr {
uint16_t source_port;
uint16_t dest_port;
uint16_t length;
uint16_t checksum;
} __attribute__((packed));Parsing the Header from a Raw Buffer
When receiving packets via raw sockets
(AF_INET, SOCK_RAW, IPPROTO_UDP or AF_PACKET),
the raw buffer contains lower-layer protocol headers (like Ethernet and
IPv4) preceding the UDP header.
To reach the UDP header: 1. Extract the IP header to determine its
length (using the ihl field). 2. Offset the buffer pointer
by the IP header size. 3. Cast the resulting pointer to
struct udphdr *. 4. Convert 16-bit fields from Network Byte
Order (Big Endian) to Host Byte Order using ntohs().
Complete Implementation
#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
void parse_udp_packet(const unsigned char *buffer, size_t buffer_len) {
if (buffer_len < sizeof(struct iphdr)) {
printf("Packet too short to contain an IP header.\n");
return;
}
// 1. Parse the IPv4 Header
const struct iphdr *ip_header = (const struct iphdr *)buffer;
size_t ip_header_len = ip_header->ihl * 4;
if (buffer_len < ip_header_len + sizeof(struct udphdr)) {
printf("Packet too short to contain a UDP header.\n");
return;
}
// Ensure the protocol is UDP
if (ip_header->protocol != IPPROTO_UDP) {
printf("Not a UDP packet.\n");
return;
}
// 2. Locate and Map the UDP Header
const struct udphdr *udp_header = (const struct udphdr *)(buffer + ip_header_len);
// 3. Convert Byte Order and Extract Fields
uint16_t src_port = ntohs(udp_header->uh_sport);
uint16_t dst_port = ntohs(udp_header->uh_dport);
uint16_t udp_len = ntohs(udp_header->uh_ulen);
uint16_t checksum = ntohs(udp_header->uh_sum);
// 4. Access the Payload
const unsigned char *payload = buffer + ip_header_len + sizeof(struct udphdr);
size_t payload_len = udp_len - sizeof(struct udphdr);
// Display Header Information
printf("--- UDP Header ---\n");
printf("Source Port: %u\n", src_port);
printf("Destination Port: %u\n", dst_port);
printf("Length: %u bytes\n", udp_len);
printf("Checksum: 0x%04X\n", checksum);
printf("Payload Size: %zu bytes\n", payload_len);
}
int main() {
// Simulated raw IP+UDP packet (IPv4 header + UDP header + 4-byte payload)
unsigned char raw_packet[] = {
// IPv4 Header (20 bytes)
0x45, 0x00, 0x00, 0x20, 0x00, 0x01, 0x00, 0x00,
0x40, 0x11, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x01,
0x7F, 0x00, 0x00, 0x01,
// UDP Header (8 bytes): SrcPort=8080, DstPort=9090, Len=12, Checksum=0x0000
0x1F, 0x90, 0x23, 0x82, 0x00, 0x0C, 0x00, 0x00,
// Payload (4 bytes: "TEST")
0x54, 0x45, 0x53, 0x54
};
parse_udp_packet(raw_packet, sizeof(raw_packet));
return 0;
}Key Considerations
- Byte Ordering: Network protocols transmit
multi-byte integers in Big Endian format. Always use
ntohs()for 16-bit integers andntohl()for 32-bit integers to prevent data corruption on Little Endian architectures (like x86/x64). - Buffer Bounds Checking: Always validate that the buffer size is large enough to contain both the IP header and the UDP header before dereferencing pointers to avoid segmentation faults or buffer over-reads.
- Payload Offset: The UDP payload immediately follows
the 8-byte UDP header. Calculate payload size by subtracting
sizeof(struct udphdr)fromudp_header->uh_ulen.