feat: extract_offending function

This commit is contained in:
lohhiiccc 2026-02-07 11:15:42 +01:00
parent d3daf711b2
commit 97a9f24fc3
4 changed files with 381 additions and 0 deletions

View file

@ -23,6 +23,27 @@ typedef struct icmp_reply {
size_t ip_payload_len;
} icmp_reply_t;
/* Offending packet structure for error messages */
typedef struct icmp_offending_packet {
struct in_addr src;
struct in_addr dst;
uint8_t protocol;
uint8_t icmp_type;
uint8_t icmp_code;
union {
struct {
uint16_t id;
uint16_t seq;
} echo;
uint32_t gateway;
struct {
uint16_t unused;
uint16_t mtu;
} frag;
uint8_t raw[4];
} rest;
} icmp_offending_packet_t;
/* Callback type for received packets */
typedef void (*icmp_callback_t)(const icmp_reply_t *reply, void *userdata);
@ -48,6 +69,10 @@ int icmp_process(icmp_handle_t *h, icmp_callback_t cb, void *userdata,
int icmp_reply_id_seq(const icmp_reply_t *reply, uint16_t *id,
uint16_t *seq);
/* Error message helpers */
int icmp_error_extract_offending(const icmp_reply_t *reply,
icmp_offending_packet_t *offending);
/* Error handling */
const char *icmp_strerror(const icmp_handle_t *h);
int icmp_should_retry(const icmp_handle_t *h);

View file

@ -29,6 +29,7 @@ SRCS += $(SRC_DIR)/recv/core/parse_packet.c
SRCS += $(SRC_DIR)/recv/core/process_single_packet.c
SRCS += $(SRC_DIR)/recv/api/process.c
SRCS += $(SRC_DIR)/recv/api/extract_id_seq.c
SRCS += $(SRC_DIR)/recv/api/extract_offending.c
TESTS_DIR = tests
TESTS += $(TESTS_DIR)/utils/test_checksum.c
@ -52,3 +53,4 @@ TESTS += $(TESTS_DIR)/recv/test_receive_packet.c
TESTS += $(TESTS_DIR)/recv/test_parse_packet.c
TESTS += $(TESTS_DIR)/recv/test_process.c
TESTS += $(TESTS_DIR)/recv/test_extract_id_seq.c
TESTS += $(TESTS_DIR)/recv/test_extract_offending.c

View file

@ -0,0 +1,99 @@
#include "icmp.h"
#include "icmp_types.h"
#include "internal/icmp_packet.h"
#include "internal/icmp_packet_internal.h"
#include <netinet/in.h>
#include <stdint.h>
#define MIN_ERROR_PAYLOAD_LEN 28
#define MIN_ICMP_HEADER_LEN 8
/* Forward declarations */
static int is_error_type(uint8_t type);
static int is_echo_type(uint8_t type);
static int is_frag_needed(uint8_t type, uint8_t code);
static int parse_embedded_ip(const icmp_reply_t *reply,
icmp_offending_packet_t *offending,
size_t *ip_hdr_len);
static const struct icmp_header *get_embedded_icmp(const icmp_reply_t *reply,
size_t ip_hdr_len);
static void extract_icmp_fields(const struct icmp_header *hdr,
icmp_offending_packet_t *offending);
/* -------------------- */
int
icmp_error_extract_offending(const icmp_reply_t *reply,
icmp_offending_packet_t *offending)
{
size_t ip_hdr_len;
const struct icmp_header *hdr;
if ((NULL == reply || NULL == offending) ||
(!is_error_type(reply->type)) ||
(reply->payload_len < MIN_ERROR_PAYLOAD_LEN) ||
(parse_embedded_ip(reply, offending, &ip_hdr_len) < 0) ||
(reply->payload_len < ip_hdr_len + MIN_ICMP_HEADER_LEN))
return -1;
hdr = get_embedded_icmp(reply, ip_hdr_len);
extract_icmp_fields(hdr, offending);
return 0;
}
static int
is_error_type(uint8_t type)
{
return ICMP_TYPE_DEST_UNREACHABLE == type ||
ICMP_TYPE_REDIRECT == type ||
ICMP_TYPE_TIME_EXCEEDED == type ||
ICMP_TYPE_PARAMETER_PROBLEM == type;
}
static int
is_echo_type(uint8_t type)
{
return ICMP_TYPE_ECHO_REPLY == type ||
ICMP_TYPE_ECHO_REQUEST == type ||
ICMP_TYPE_TIMESTAMP_REQUEST == type ||
ICMP_TYPE_TIMESTAMP_REPLY == type;
}
static int
is_frag_needed(uint8_t type, uint8_t code)
{
return ICMP_TYPE_DEST_UNREACHABLE == type &&
ICMP_CODE_FRAG_NEEDED == code;
}
static int
parse_embedded_ip(const icmp_reply_t *reply, icmp_offending_packet_t *offending,
size_t *ip_hdr_len)
{
return icmp_parse_ip_header(reply->payload, reply->payload_len,
NULL, &offending->src, ip_hdr_len,
&offending->dst, &offending->protocol);
}
static const struct icmp_header *
get_embedded_icmp(const icmp_reply_t *reply, size_t ip_hdr_len)
{
return (const struct icmp_header *)((const uint8_t *)reply->payload + ip_hdr_len);
}
static void
extract_icmp_fields(const struct icmp_header *hdr, icmp_offending_packet_t *offending)
{
offending->icmp_type = hdr->type;
offending->icmp_code = hdr->code;
offending->rest.gateway = hdr->un.gateway;
if (is_echo_type(hdr->type))
{
offending->rest.echo.id = ntohs(hdr->un.echo.id);
offending->rest.echo.seq = ntohs(hdr->un.echo.seq);
}
else if (is_frag_needed(hdr->type, hdr->code))
{
offending->rest.frag.mtu = ntohs(hdr->un.frag.mtu);
}
}

View file

@ -0,0 +1,255 @@
#include <criterion/criterion.h>
#include <string.h>
#include <arpa/inet.h>
#include "icmp.h"
#include "icmp_types.h"
#include "internal/icmp_packet.h"
/* Helper to build an error reply with embedded ICMP packet */
static icmp_reply_t
make_error_reply(uint8_t error_type, uint8_t *buffer, size_t buffer_size)
{
icmp_reply_t reply;
memset(&reply, 0, sizeof(reply));
memset(buffer, 0, buffer_size);
reply.type = error_type;
reply.code = 0;
reply.payload = buffer;
reply.payload_len = buffer_size;
return reply;
}
/* Helper to fill IP header in buffer */
static void
fill_ip_header(uint8_t *buffer, const char *src, const char *dst, uint8_t protocol)
{
struct ip_header *ip = (struct ip_header *)buffer;
ip->version_ihl = 0x45; /* version 4, IHL 5 (20 bytes) */
ip->ttl = 64;
ip->protocol = protocol;
ip->saddr = inet_addr(src);
ip->daddr = inet_addr(dst);
}
/* Helper to fill ICMP header after IP header */
static void
fill_icmp_header(uint8_t *buffer, uint8_t type, uint8_t code,
uint16_t id, uint16_t seq)
{
buffer[20] = type; /* After 20-byte IP header */
buffer[21] = code;
buffer[22] = 0; /* checksum high */
buffer[23] = 0; /* checksum low */
/* id and seq in network byte order */
*(uint16_t *)(buffer + 24) = htons(id);
*(uint16_t *)(buffer + 26) = htons(seq);
}
Test(extract_offending, time_exceeded_with_echo)
{
uint8_t buffer[28];
icmp_reply_t reply = make_error_reply(ICMP_TYPE_TIME_EXCEEDED, buffer, sizeof(buffer));
fill_ip_header(buffer, "192.168.1.100", "8.8.8.8", 1);
fill_icmp_header(buffer, ICMP_TYPE_ECHO_REQUEST, 0, 0x1234, 0x5678);
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, 0, "Should return 0 for valid Time Exceeded");
cr_assert_eq(offending.src.s_addr, inet_addr("192.168.1.100"),
"Source address should match");
cr_assert_eq(offending.dst.s_addr, inet_addr("8.8.8.8"),
"Destination address should match");
cr_assert_eq(offending.protocol, 1, "Protocol should be 1 (ICMP)");
cr_assert_eq(offending.icmp_type, ICMP_TYPE_ECHO_REQUEST,
"ICMP type should be Echo Request");
cr_assert_eq(offending.icmp_code, 0, "ICMP code should be 0");
cr_assert_eq(offending.rest.echo.id, 0x1234, "Echo ID should match");
cr_assert_eq(offending.rest.echo.seq, 0x5678, "Echo seq should match");
}
Test(extract_offending, dest_unreachable_with_timestamp)
{
uint8_t buffer[28];
icmp_reply_t reply = make_error_reply(ICMP_TYPE_DEST_UNREACHABLE, buffer, sizeof(buffer));
fill_ip_header(buffer, "10.0.0.1", "10.0.0.2", 1);
fill_icmp_header(buffer, ICMP_TYPE_TIMESTAMP_REQUEST, 0, 0xABCD, 0xEF01);
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, 0, "Should return 0 for valid Dest Unreachable");
cr_assert_eq(offending.protocol, 1, "Protocol should be ICMP");
cr_assert_eq(offending.icmp_type, ICMP_TYPE_TIMESTAMP_REQUEST);
cr_assert_eq(offending.rest.echo.id, 0xABCD);
cr_assert_eq(offending.rest.echo.seq, 0xEF01);
}
Test(extract_offending, redirect_with_gateway)
{
uint8_t buffer[28];
icmp_reply_t reply = make_error_reply(ICMP_TYPE_REDIRECT, buffer, sizeof(buffer));
fill_ip_header(buffer, "10.0.0.1", "10.0.0.2", 1);
/* For redirect, the 4 bytes after type/code/checksum are the gateway address */
buffer[20] = ICMP_TYPE_REDIRECT;
buffer[21] = 0;
buffer[22] = 0;
buffer[23] = 0;
*(uint32_t *)(buffer + 24) = inet_addr("192.168.1.1"); /* Gateway */
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, 0, "Should return 0 for valid Redirect");
cr_assert_eq(offending.icmp_type, ICMP_TYPE_REDIRECT);
cr_assert_eq(offending.rest.gateway, inet_addr("192.168.1.1"),
"Gateway address should match");
}
Test(extract_offending, parameter_problem)
{
uint8_t buffer[28];
icmp_reply_t reply = make_error_reply(ICMP_TYPE_PARAMETER_PROBLEM, buffer, sizeof(buffer));
fill_ip_header(buffer, "1.2.3.4", "5.6.7.8", 1);
fill_icmp_header(buffer, ICMP_TYPE_ECHO_REQUEST, 0, 100, 200);
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, 0, "Should return 0 for Parameter Problem");
cr_assert_eq(offending.rest.echo.id, 100);
cr_assert_eq(offending.rest.echo.seq, 200);
}
Test(extract_offending, non_icmp_protocol)
{
uint8_t buffer[28];
icmp_reply_t reply = make_error_reply(ICMP_TYPE_TIME_EXCEEDED, buffer, sizeof(buffer));
/* Embedded packet is UDP (protocol 17), not ICMP */
fill_ip_header(buffer, "10.0.0.1", "10.0.0.2", 17);
memset(buffer + 20, 0, 8); /* 8 bytes of UDP header */
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, 0, "Should return 0 even for non-ICMP");
cr_assert_eq(offending.protocol, 17, "Protocol should be 17 (UDP)");
/* icmp_type and icmp_code are set but not meaningful for UDP */
}
Test(extract_offending, non_error_type_returns_error)
{
uint8_t buffer[28];
icmp_reply_t reply = make_error_reply(ICMP_TYPE_ECHO_REPLY, buffer, sizeof(buffer));
fill_ip_header(buffer, "10.0.0.1", "10.0.0.2", 1);
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, -1, "Should return -1 for non-error type (Echo Reply)");
}
Test(extract_offending, null_reply_returns_error)
{
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(NULL, &offending);
cr_assert_eq(ret, -1, "Should return -1 for NULL reply");
}
Test(extract_offending, null_offending_returns_error)
{
uint8_t buffer[28];
icmp_reply_t reply = make_error_reply(ICMP_TYPE_TIME_EXCEEDED, buffer, sizeof(buffer));
int ret = icmp_error_extract_offending(&reply, NULL);
cr_assert_eq(ret, -1, "Should return -1 for NULL offending");
}
Test(extract_offending, payload_too_short_returns_error)
{
uint8_t buffer[27]; /* One byte short */
icmp_reply_t reply = make_error_reply(ICMP_TYPE_TIME_EXCEEDED, buffer, sizeof(buffer));
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, -1, "Should return -1 when payload < 28 bytes");
}
Test(extract_offending, ip_header_with_options)
{
uint8_t buffer[40]; /* 20 byte IP + options + 8 byte ICMP */
icmp_reply_t reply = make_error_reply(ICMP_TYPE_TIME_EXCEEDED, buffer, sizeof(buffer));
/* IP header with IHL=6 (24 bytes including 4 bytes of options) */
struct ip_header *ip = (struct ip_header *)buffer;
ip->version_ihl = 0x46; /* version 4, IHL 6 (24 bytes) */
ip->ttl = 64;
ip->protocol = 1;
ip->saddr = inet_addr("192.168.1.1");
ip->daddr = inet_addr("192.168.1.2");
/* ICMP header starts at offset 24 */
fill_icmp_header(buffer + 4, ICMP_TYPE_ECHO_REQUEST, 0, 0x9999, 0xAAAA);
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, 0, "Should handle IP headers with options");
cr_assert_eq(offending.protocol, 1);
cr_assert_eq(offending.icmp_type, ICMP_TYPE_ECHO_REQUEST);
cr_assert_eq(offending.rest.echo.id, 0x9999);
cr_assert_eq(offending.rest.echo.seq, 0xAAAA);
}
Test(extract_offending, embedded_frag_needed_mtu)
{
uint8_t buffer[28];
icmp_reply_t reply = make_error_reply(ICMP_TYPE_TIME_EXCEEDED, buffer, sizeof(buffer));
fill_ip_header(buffer, "10.0.0.1", "10.0.0.2", 1);
/* Embedded packet is itself a Dest Unreachable / Frag Needed */
buffer[20] = ICMP_TYPE_DEST_UNREACHABLE;
buffer[21] = ICMP_CODE_FRAG_NEEDED;
buffer[22] = 0;
buffer[23] = 0;
*(uint16_t *)(buffer + 24) = 0; /* unused */
*(uint16_t *)(buffer + 26) = htons(1400); /* mtu in network order */
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, 0);
cr_assert_eq(offending.icmp_type, ICMP_TYPE_DEST_UNREACHABLE);
cr_assert_eq(offending.icmp_code, ICMP_CODE_FRAG_NEEDED);
cr_assert_eq(offending.rest.frag.mtu, 1400, "MTU should be in host byte order");
}
Test(extract_offending, redirect_with_tcp_packet)
{
uint8_t buffer[48];
icmp_reply_t reply = make_error_reply(ICMP_TYPE_REDIRECT, buffer, sizeof(buffer));
fill_ip_header(buffer, "10.0.0.1", "10.0.0.2", 6); // Protocol 6 = TCP
/* TCP header after IP */
memset(buffer + 20, 0, 20);
buffer[20] = 0x50;
buffer[21] = 0x00;
icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, 0);
cr_assert_eq(offending.protocol, 6, "Should be TCP");
}