feat(packet): add ICMP/IP packet build and parse helpers

This commit is contained in:
lohhiiccc 2026-01-25 01:44:00 +01:00
parent 01e66a34fb
commit d453ef8bed
10 changed files with 491 additions and 0 deletions

View file

@ -0,0 +1,38 @@
#ifndef ICMP_PACKET_H
#define ICMP_PACKET_H
#include <stdint.h>
/* ICMP Header Structure (RFC 792) */
struct icmp_header {
uint8_t type;
uint8_t code;
uint16_t checksum;
union {
struct {
uint16_t id;
uint16_t seq;
} echo;
uint32_t gateway;
struct {
uint16_t unused;
uint16_t mtu;
} frag;
} un;
} __attribute__((packed));
/* IP Header Structure */
struct ip_header {
uint8_t version_ihl;
uint8_t tos;
uint16_t total_len;
uint16_t id;
uint16_t frag_off;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
uint32_t saddr;
uint32_t daddr;
} __attribute__((packed));
#endif

View file

@ -0,0 +1,20 @@
#ifndef ICMP_PACKET_INTERNAL_H
#define ICMP_PACKET_INTERNAL_H
#include <stdint.h>
#include <stddef.h>
#include <netinet/in.h>
int icmp_build_packet(void *buffer, size_t buffer_len, uint8_t type,
uint8_t code, uint16_t id, uint16_t seq,
const void *payload, size_t payload_len);
int icmp_parse_ip_header(const void *buffer, size_t buffer_len,
uint8_t *ttl, struct in_addr *src_addr,
size_t *ip_hdr_len);
int icmp_parse_icmp_payload(const void *buffer, size_t buffer_len,
size_t ip_hdr_len, uint8_t *type, uint8_t *code,
const void **payload, size_t *payload_len);
#endif

View file

61
src/packet/build.c Normal file
View file

@ -0,0 +1,61 @@
#include "internal/icmp_packet_internal.h"
#include "internal/icmp_packet.h"
#include "internal/icmp_utils.h"
#include <stddef.h>
#include <string.h>
/* Forward declarations */
static void write_icmp_header(struct icmp_header *hdr, uint8_t type,
uint8_t code, uint16_t id, uint16_t seq);
static void copy_payload(void *dest, const void *src, size_t len);
static void compute_and_set_checksum(struct icmp_header *hdr, size_t total_len);
/* -------------------- */
int
icmp_build_packet(void *buffer, size_t buffer_len, uint8_t type, uint8_t code,
uint16_t id, uint16_t seq, const void *payload,
size_t payload_len)
{
const size_t required_len = sizeof(struct icmp_header) + payload_len;
if (buffer_len < required_len)
return -1;
struct icmp_header *h = (struct icmp_header *)buffer;
write_icmp_header(h, type, code, id, seq);
if (payload_len > 0)
{
copy_payload((unsigned char*)buffer + sizeof(struct icmp_header),
payload, payload_len);
}
compute_and_set_checksum(h, required_len);
return (int)required_len;
}
static void
write_icmp_header(struct icmp_header *hdr, uint8_t type, uint8_t code,
uint16_t id, uint16_t seq)
{
hdr->type = type;
hdr->code = code;
hdr->checksum = 0; // will be computed later
hdr->un.echo.id = id;
hdr->un.echo.seq = seq;
}
static void
copy_payload(void *dest, const void *src, size_t len)
{
if (0 == len)
return;
memcpy(dest, src, len);
}
static void
compute_and_set_checksum(struct icmp_header *hdr, size_t total_len)
{
hdr->checksum = icmp_checksum(hdr, total_len);
}

54
src/packet/parse_icmp.c Normal file
View file

@ -0,0 +1,54 @@
#include "internal/icmp_packet_internal.h"
#include "internal/icmp_packet.h"
#include <stdint.h>
/* ICMP header size */
#define ICMP_HEADER_SIZE 8
/* Forward declarations */
static int validate_icmp_size(size_t buffer_len, size_t ip_hdr_len);
static const struct icmp_header *get_icmp_header(const void *buffer,
size_t ip_hdr_len);
static void extract_icmp_fields(const struct icmp_header *hdr, uint8_t *type,
uint8_t *code);
/* -------------------- */
int
icmp_parse_icmp_payload(const void *buffer, size_t buffer_len,
size_t ip_hdr_len, uint8_t *type, uint8_t *code,
const void **payload, size_t *payload_len)
{
if (validate_icmp_size(buffer_len, ip_hdr_len) != 0)
return -1;
const struct icmp_header *hdr = get_icmp_header(buffer, ip_hdr_len);
extract_icmp_fields(hdr, type, code);
size_t payload_offset = ip_hdr_len + ICMP_HEADER_SIZE;
if (buffer_len < payload_offset)
return -1;
*payload_len = buffer_len - payload_offset;
*payload = (const uint8_t *)buffer + payload_offset;
return 0;
}
static int
validate_icmp_size(size_t buffer_len, size_t ip_hdr_len)
{
return (buffer_len >= (ip_hdr_len + ICMP_HEADER_SIZE)) ? 0 : -1;
}
static const struct icmp_header *
get_icmp_header(const void *buffer, size_t ip_hdr_len)
{
return (const struct icmp_header *)((const uint8_t *)buffer + ip_hdr_len);
}
static void
extract_icmp_fields(const struct icmp_header *hdr, uint8_t *type,
uint8_t *code)
{
*type = hdr->type;
*code = hdr->code;
}

60
src/packet/parse_ip.c Normal file
View file

@ -0,0 +1,60 @@
#include "internal/icmp_packet_internal.h"
#include "internal/icmp_packet.h"
#include <netinet/in.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
/* Minimum IP header size */
#define MIN_IP_HEADER_SIZE 20
/* Forward declarations */
static int validate_ip_header(const struct ip_header *hdr, size_t buffer_len);
static size_t extract_ip_header_length(uint8_t version_ihl);
/* -------------------- */
int
icmp_parse_ip_header(const void *buffer, size_t buffer_len, uint8_t *ttl,
struct in_addr *src_addr, size_t *ip_hdr_len)
{
if (buffer_len < MIN_IP_HEADER_SIZE)
return -1;
const struct ip_header *h = (const struct ip_header *)buffer;
size_t ihl_bytes = extract_ip_header_length(h->version_ihl);
if (0 != validate_ip_header(h, buffer_len))
return -1;
if (ttl)
*ttl = h->ttl;
if (src_addr)
src_addr->s_addr = h->saddr;
if (ip_hdr_len)
*ip_hdr_len = ihl_bytes;
return 0;
}
static int
validate_ip_header(const struct ip_header *hdr, size_t buffer_len)
{
if (4 != (hdr->version_ihl >> 4))
return -1;
uint8_t ihl = hdr->version_ihl & 0x0F;
if (ihl < 5)
return -1;
size_t header_len = ihl << 2;
if (header_len > buffer_len)
return -1;
return 0;
}
static size_t
extract_ip_header_length(uint8_t version_ihl)
{
return (version_ihl & 0x0F) << 2;
}

View file

73
tests/packet/test_build.c Normal file
View file

@ -0,0 +1,73 @@
#include <criterion/criterion.h>
#include <string.h>
#include "internal/icmp_packet_internal.h"
#include "internal/icmp_packet.h"
#include "internal/icmp_utils.h"
Test(packet_build, echo_request_no_payload)
{
uint8_t buffer[8] = {0};
uint8_t type = 8, code = 0;
uint16_t id = 0x1234;
uint16_t seq = 0x0001;
int ret = icmp_build_packet(buffer, sizeof(buffer), type, code, id, seq,
NULL, 0);
cr_assert_eq(ret, 8, "Expected packet size 8, got %d", ret);
struct icmp_header *hdr = (struct icmp_header *)buffer;
cr_assert_eq(hdr->type, 8, "Expected type 8, got %u", hdr->type);
cr_assert_eq(hdr->code, 0, "Expected code 0, got %u", hdr->code);
cr_assert_eq(hdr->un.echo.id, id, "Expected id 0x%04x, got 0x%04x", id,
hdr->un.echo.id);
cr_assert_eq(hdr->un.echo.seq, seq, "Expected seq 0x%04x, got 0x%04x", seq,
hdr->un.echo.seq);
cr_assert_neq(hdr->checksum, 0, "Checksum should not be zero");
}
Test(packet_build, echo_request_with_payload)
{
const char payload[] = "HELLO";
size_t payload_len = sizeof(payload) - 1;
uint8_t buffer[8 + 5] = {0};
uint8_t type = 8, code = 0;
uint16_t id = 0xabcd, seq = 0x1234;
int ret = icmp_build_packet(buffer, sizeof(buffer), type, code, id, seq,
payload, payload_len);
cr_assert_eq(ret, 13, "Expected packet size 13, got %d", ret);
cr_assert(memcmp(buffer + 8, payload, 5) == 0,
"Payload not copied correctly");
}
Test(packet_build, checksum_correct)
{
uint8_t buffer[16] = {0};
uint8_t type = 8, code = 0;
uint16_t id = 0x77aa, seq = 0x0033;
const char payload[] = "ABC";
size_t payload_len = 3;
int ret = icmp_build_packet(buffer, sizeof(buffer), type, code, id, seq,
payload, payload_len);
cr_assert_eq(ret, 11, "Expected packet size 11, got %d", ret);
struct icmp_header *hdr = (struct icmp_header *)buffer;
uint16_t old_checksum = hdr->checksum;
hdr->checksum = 0;
uint16_t checksum = icmp_checksum(buffer, ret);
hdr->checksum = old_checksum;
cr_assert_eq(old_checksum, checksum,
"Checksum in packet (%04x) does not match recomputed one (%04x)",
old_checksum, checksum);
}
Test(packet_build, buffer_too_small)
{
uint8_t buf[4];
const char payload[8] = "ABCDEFG";
int ret = icmp_build_packet(buf, sizeof(buf), 8, 0, 0x1111, 0x2222,
payload, 8);
cr_assert_eq(ret, -1, "Expected -1 for too small buffer, got %d", ret);
}

View file

@ -0,0 +1,89 @@
#include <criterion/criterion.h>
#include <string.h>
#include "internal/icmp_packet_internal.h"
#include "internal/icmp_packet.h"
Test(packet_parse_icmp, echo_reply)
{
uint8_t buffer[20 + 8 + 4] = {0}; // 20 bytes IP header, 8 ICMP, 4 payload
size_t ip_hdr_len = 20;
// fake ICMP header: type=0 (echo reply), code=0
struct icmp_header *icmp = (struct icmp_header *)(buffer + ip_hdr_len);
icmp->type = 0;
icmp->code = 0;
// payload: "PONG"
memcpy(buffer + ip_hdr_len + 8, "PONG", 4);
uint8_t type = 255, code = 255;
const void *payload = NULL;
size_t payload_len = 0;
int ret = icmp_parse_icmp_payload(buffer, sizeof(buffer),
ip_hdr_len, &type, &code, &payload, &payload_len);
cr_assert_eq(ret, 0, "Should succeed for valid ICMP echo reply");
cr_assert_eq(type, 0, "Type should be 0 (echo reply)");
cr_assert_eq(code, 0, "Code should be 0");
cr_assert_eq(payload_len, 4, "Payload length should be 4");
cr_assert(memcmp(payload, "PONG", 4) == 0, "Payload should be 'PONG'");
}
Test(packet_parse_icmp, no_payload)
{
// Only IP header + ICMP header (20+8=28)
uint8_t buffer[28] = {0};
size_t ip_hdr_len = 20;
struct icmp_header *icmp = (struct icmp_header *)(buffer + ip_hdr_len);
icmp->type = 8; // Echo request
icmp->code = 1;
uint8_t type = 0, code = 0;
const void *payload = NULL;
size_t payload_len = 123;
int ret = icmp_parse_icmp_payload(buffer, sizeof(buffer),
ip_hdr_len, &type, &code, &payload, &payload_len);
cr_assert_eq(ret, 0, "Should succeed for ICMP with no payload");
cr_assert_eq(payload_len, 0, "Payload length should be 0");
}
Test(packet_parse_icmp, extract_type_code)
{
// Build with type=11 (time exceeded), code=0
uint8_t buffer[20 + 8] = {0};
size_t ip_hdr_len = 20;
struct icmp_header *icmp = (struct icmp_header *)(buffer + ip_hdr_len);
icmp->type = 11;
icmp->code = 0;
uint8_t type = 0, code = 100;
const void *payload = NULL;
size_t payload_len = 0;
int ret = icmp_parse_icmp_payload(buffer, sizeof(buffer),
ip_hdr_len, &type, &code, &payload, &payload_len);
cr_assert_eq(ret, 0, "Should succeed for ICMP Time Exceeded");
cr_assert_eq(type, 11, "Type should be 11");
cr_assert_eq(code, 0, "Code should be 0");
}
Test(packet_parse_icmp, buffer_too_small)
{
// Only enough for IP header + 4 bytes of ICMP header (need 8)
uint8_t buffer[20 + 4] = {0};
size_t ip_hdr_len = 20;
uint8_t type = 0, code = 0;
const void *payload = NULL;
size_t payload_len = 0;
int ret = icmp_parse_icmp_payload(buffer, sizeof(buffer),
ip_hdr_len, &type, &code, &payload, &payload_len);
cr_assert_eq(ret, -1,
"Should fail when buffer is too small for ICMP header");
}

View file

@ -0,0 +1,96 @@
#include <criterion/criterion.h>
#include <string.h>
#include <arpa/inet.h>
#include "internal/icmp_packet_internal.h"
#include "internal/icmp_packet.h"
Test(packet_parse_ip, valid_header)
{
uint8_t buffer[20] = {0};
struct ip_header *hdr = (struct ip_header*)buffer;
hdr->version_ihl = 0x45; // version 4, IHL = 5*4 = 20 bytes
hdr->ttl = 64;
hdr->saddr = inet_addr("192.168.1.1");
uint8_t ttl = 0;
struct in_addr src_addr = {0};
size_t ip_hdr_len = 0;
int ret = icmp_parse_ip_header(buffer, sizeof(buffer), &ttl, &src_addr,
&ip_hdr_len);
cr_assert_eq(ret, 0, "Should return 0 for valid IP header");
cr_assert_eq(ttl, 64, "TTL should be 64");
cr_assert_eq(src_addr.s_addr, inet_addr("192.168.1.1"),
"Source address mismatch");
cr_assert_eq(ip_hdr_len, 20, "Header length should be 20 bytes");
}
Test(packet_parse_ip, extract_ttl)
{
uint8_t buffer[20] = {0};
struct ip_header *hdr = (struct ip_header*)buffer;
hdr->version_ihl = 0x45;
hdr->ttl = 128;
hdr->saddr = inet_addr("10.0.0.1");
uint8_t ttl = 0;
struct in_addr src_addr = {0};
size_t ip_hdr_len = 0;
int ret = icmp_parse_ip_header(buffer, sizeof(buffer), &ttl, &src_addr,
&ip_hdr_len);
cr_assert_eq(ret, 0, "Should return 0 for valid header");
cr_assert_eq(ttl, 128, "TTL should be 128");
}
Test(packet_parse_ip, extract_source_addr)
{
uint8_t buffer[20] = {0};
struct ip_header *hdr = (struct ip_header*)buffer;
hdr->version_ihl = 0x45;
hdr->ttl = 5;
hdr->saddr = inet_addr("8.8.8.8");
uint8_t ttl = 0;
struct in_addr src_addr = {0};
size_t ip_hdr_len = 0;
int ret = icmp_parse_ip_header(buffer, sizeof(buffer), &ttl, &src_addr,
&ip_hdr_len);
cr_assert_eq(ret, 0, "Should return 0 for valid header");
cr_assert_eq(src_addr.s_addr, inet_addr("8.8.8.8"),
"Source address should match 8.8.8.8");
}
Test(packet_parse_ip, buffer_too_small)
{
uint8_t buffer[10] = {0}; // < 20
uint8_t ttl = 0;
struct in_addr src_addr = {0};
size_t ip_hdr_len = 0;
int ret = icmp_parse_ip_header(buffer, sizeof(buffer), &ttl, &src_addr,
&ip_hdr_len);
cr_assert_eq(ret, -1, "Should return -1 when buffer is too small");
}
Test(packet_parse_ip, invalid_version)
{
uint8_t buffer[20] = {0};
struct ip_header *hdr = (struct ip_header*)buffer;
hdr->version_ihl = 0x65; // version = 6, IHL = 5
hdr->ttl = 99;
hdr->saddr = inet_addr("1.2.3.4");
uint8_t ttl = 0;
struct in_addr src_addr = {0};
size_t ip_hdr_len = 0;
int ret = icmp_parse_ip_header(buffer, sizeof(buffer), &ttl, &src_addr,
&ip_hdr_len);
cr_assert_eq(ret, -1, "Should return -1 for invalid IP version");
}