feat: extract_offending function
This commit is contained in:
parent
d3daf711b2
commit
97a9f24fc3
4 changed files with 381 additions and 0 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
99
src/recv/api/extract_offending.c
Normal file
99
src/recv/api/extract_offending.c
Normal 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);
|
||||
}
|
||||
}
|
||||
255
tests/recv/test_extract_offending.c
Normal file
255
tests/recv/test_extract_offending.c
Normal 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");
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue