feat(packet): add ICMP/IP packet build and parse helpers
This commit is contained in:
parent
01e66a34fb
commit
d453ef8bed
10 changed files with 491 additions and 0 deletions
38
includes/internal/icmp_packet.h
Normal file
38
includes/internal/icmp_packet.h
Normal 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
|
||||
20
includes/internal/icmp_packet_internal.h
Normal file
20
includes/internal/icmp_packet_internal.h
Normal 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
|
||||
61
src/packet/build.c
Normal file
61
src/packet/build.c
Normal 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
54
src/packet/parse_icmp.c
Normal 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
60
src/packet/parse_ip.c
Normal 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;
|
||||
}
|
||||
73
tests/packet/test_build.c
Normal file
73
tests/packet/test_build.c
Normal 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);
|
||||
}
|
||||
89
tests/packet/test_parse_icmp.c
Normal file
89
tests/packet/test_parse_icmp.c
Normal 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");
|
||||
}
|
||||
96
tests/packet/test_parse_ip.c
Normal file
96
tests/packet/test_parse_ip.c
Normal 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");
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue