From d453ef8bed4f30a0c410c8b9c52854a8afd3c16e Mon Sep 17 00:00:00 2001 From: lohhiiccc <96543753+lohhiiccc@users.noreply.github.com> Date: Sun, 25 Jan 2026 01:44:00 +0100 Subject: [PATCH] feat(packet): add ICMP/IP packet build and parse helpers --- includes/internal/icmp_packet.h | 38 ++++++++++ includes/internal/icmp_packet_internal.h | 20 +++++ src/packet/.gitkeep | 0 src/packet/build.c | 61 +++++++++++++++ src/packet/parse_icmp.c | 54 +++++++++++++ src/packet/parse_ip.c | 60 +++++++++++++++ tests/packet/.gitkeep | 0 tests/packet/test_build.c | 73 ++++++++++++++++++ tests/packet/test_parse_icmp.c | 89 ++++++++++++++++++++++ tests/packet/test_parse_ip.c | 96 ++++++++++++++++++++++++ 10 files changed, 491 insertions(+) create mode 100644 includes/internal/icmp_packet.h create mode 100644 includes/internal/icmp_packet_internal.h delete mode 100644 src/packet/.gitkeep create mode 100644 src/packet/build.c create mode 100644 src/packet/parse_icmp.c create mode 100644 src/packet/parse_ip.c delete mode 100644 tests/packet/.gitkeep create mode 100644 tests/packet/test_build.c create mode 100644 tests/packet/test_parse_icmp.c create mode 100644 tests/packet/test_parse_ip.c diff --git a/includes/internal/icmp_packet.h b/includes/internal/icmp_packet.h new file mode 100644 index 0000000..7cbbfad --- /dev/null +++ b/includes/internal/icmp_packet.h @@ -0,0 +1,38 @@ +#ifndef ICMP_PACKET_H +#define ICMP_PACKET_H + +#include + +/* 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 diff --git a/includes/internal/icmp_packet_internal.h b/includes/internal/icmp_packet_internal.h new file mode 100644 index 0000000..fca26ef --- /dev/null +++ b/includes/internal/icmp_packet_internal.h @@ -0,0 +1,20 @@ +#ifndef ICMP_PACKET_INTERNAL_H +#define ICMP_PACKET_INTERNAL_H + +#include +#include +#include + +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 diff --git a/src/packet/.gitkeep b/src/packet/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/packet/build.c b/src/packet/build.c new file mode 100644 index 0000000..7a52dd5 --- /dev/null +++ b/src/packet/build.c @@ -0,0 +1,61 @@ +#include "internal/icmp_packet_internal.h" +#include "internal/icmp_packet.h" +#include "internal/icmp_utils.h" +#include +#include + +/* 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); +} diff --git a/src/packet/parse_icmp.c b/src/packet/parse_icmp.c new file mode 100644 index 0000000..4c3b5eb --- /dev/null +++ b/src/packet/parse_icmp.c @@ -0,0 +1,54 @@ +#include "internal/icmp_packet_internal.h" +#include "internal/icmp_packet.h" +#include + +/* 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; +} diff --git a/src/packet/parse_ip.c b/src/packet/parse_ip.c new file mode 100644 index 0000000..0a1c49d --- /dev/null +++ b/src/packet/parse_ip.c @@ -0,0 +1,60 @@ +#include "internal/icmp_packet_internal.h" +#include "internal/icmp_packet.h" +#include +#include +#include +#include + +/* 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; +} diff --git a/tests/packet/.gitkeep b/tests/packet/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/packet/test_build.c b/tests/packet/test_build.c new file mode 100644 index 0000000..e0ff715 --- /dev/null +++ b/tests/packet/test_build.c @@ -0,0 +1,73 @@ +#include +#include +#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); +} diff --git a/tests/packet/test_parse_icmp.c b/tests/packet/test_parse_icmp.c new file mode 100644 index 0000000..1e8191f --- /dev/null +++ b/tests/packet/test_parse_icmp.c @@ -0,0 +1,89 @@ +#include +#include +#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"); +} diff --git a/tests/packet/test_parse_ip.c b/tests/packet/test_parse_ip.c new file mode 100644 index 0000000..f225ef1 --- /dev/null +++ b/tests/packet/test_parse_ip.c @@ -0,0 +1,96 @@ +#include +#include +#include +#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"); +}