feat: add icmp_set_dont_fragment and next_mtu extraction

- Add icmp_set_dont_fragment() using IP_MTU_DISCOVER/IP_PMTUDISC_DO
- Add next_mtu field to icmp_offending_packet_t, populated from the
  outer ICMP header on type=3/code=4 (frag needed) replies
- Fix extract_offending: read MTU from outer ICMP, not embedded packet
- Update test to match corrected extraction semantics
- Clean up checksum odd-byte handling with a static zero sentinel
This commit is contained in:
lohhiiccc 2026-03-13 03:21:35 +01:00
parent f85b342f8c
commit ad7698d530
6 changed files with 69 additions and 24 deletions

View file

@ -30,6 +30,7 @@ typedef struct icmp_offending_packet {
uint8_t protocol; uint8_t protocol;
uint8_t icmp_type; uint8_t icmp_type;
uint8_t icmp_code; uint8_t icmp_code;
uint16_t next_mtu; /* MTU from outer ICMP (type=3, code=4) */
union { union {
struct { struct {
uint16_t id; uint16_t id;
@ -74,6 +75,9 @@ int icmp_reply_id_seq(const icmp_reply_t *reply, uint16_t *id,
int icmp_error_extract_offending(const icmp_reply_t *reply, int icmp_error_extract_offending(const icmp_reply_t *reply,
icmp_offending_packet_t *offending); icmp_offending_packet_t *offending);
/* Socket options */
int icmp_set_dont_fragment(icmp_handle_t *h);
/* Error handling */ /* Error handling */
const char *icmp_strerror(const icmp_handle_t *h); const char *icmp_strerror(const icmp_handle_t *h);
int icmp_should_retry(const icmp_handle_t *h); int icmp_should_retry(const icmp_handle_t *h);

View file

@ -8,6 +8,7 @@ SRCS += $(SRC_DIR)/error/strerror.c
SRCS += $(SRC_DIR)/error/should_retry.c SRCS += $(SRC_DIR)/error/should_retry.c
SRCS += $(SRC_DIR)/socket/create.c SRCS += $(SRC_DIR)/socket/create.c
SRCS += $(SRC_DIR)/socket/configure.c SRCS += $(SRC_DIR)/socket/configure.c
SRCS += $(SRC_DIR)/socket/set_dont_fragment.c
SRCS += $(SRC_DIR)/handle/create.c SRCS += $(SRC_DIR)/handle/create.c
SRCS += $(SRC_DIR)/handle/destroy.c SRCS += $(SRC_DIR)/handle/destroy.c
SRCS += $(SRC_DIR)/handle/get_fd.c SRCS += $(SRC_DIR)/handle/get_fd.c

View file

@ -37,6 +37,12 @@ icmp_error_extract_offending(const icmp_reply_t *reply,
hdr = get_embedded_icmp(reply, ip_hdr_len); hdr = get_embedded_icmp(reply, ip_hdr_len);
extract_icmp_fields(hdr, offending); extract_icmp_fields(hdr, offending);
offending->next_mtu = 0;
if (is_frag_needed(reply->type, reply->code) && NULL != reply->ip_payload) {
const struct icmp_header *outer;
outer = (const struct icmp_header *)reply->ip_payload;
offending->next_mtu = ntohs(outer->un.frag.mtu);
}
return 0; return 0;
} }
@ -92,8 +98,4 @@ extract_icmp_fields(const struct icmp_header *hdr, icmp_offending_packet_t *offe
offending->rest.echo.id = ntohs(hdr->un.echo.id); offending->rest.echo.id = ntohs(hdr->un.echo.id);
offending->rest.echo.seq = ntohs(hdr->un.echo.seq); 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,26 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include "internal/icmp_internal.h"
int
icmp_set_dont_fragment(struct icmp_handle *h)
{
int val;
int saved_errno;
if (NULL == h)
return -1;
val = IP_PMTUDISC_DO;
if (0 > setsockopt(h->fd, IPPROTO_IP,
IP_MTU_DISCOVER, &val, sizeof(val)))
{
saved_errno = errno;
icmp_set_error_fmt(h, ICMP_ERR_SOCKET,
"Failed to set DF bit: %s", strerror(saved_errno));
return -1;
}
return 0;
}

View file

@ -41,8 +41,10 @@ sum_words(const uint8_t *data, size_t len)
} }
} }
/* Handle odd byte if present */ /* Handle odd byte if present: point to last byte or a zero sentinel */
sum += ((uint32_t)(*ptr << 8)) & (uint32_t)(-(len & 1)); static const uint8_t zero = 0;
ptr = (len & 1) ? ptr : &zero;
sum += (uint32_t)(*ptr << 8);
return sum; return sum;
} }

View file

@ -213,27 +213,37 @@ Test(extract_offending, ip_header_with_options)
cr_assert_eq(offending.rest.echo.seq, 0xAAAA); cr_assert_eq(offending.rest.echo.seq, 0xAAAA);
} }
Test(extract_offending, embedded_frag_needed_mtu) Test(extract_offending, frag_needed_mtu_from_outer_icmp)
{ {
uint8_t buffer[28]; /* outer ICMP header (8 bytes): type=3, code=4, unused=0, mtu=1400 */
icmp_reply_t reply = make_error_reply(ICMP_TYPE_TIME_EXCEEDED, buffer, sizeof(buffer)); uint8_t outer[8];
/* embedded IP (20 bytes) + embedded ICMP echo request (8 bytes) */
uint8_t embedded[28];
icmp_reply_t reply;
fill_ip_header(buffer, "10.0.0.1", "10.0.0.2", 1); memset(outer, 0, sizeof(outer));
/* Embedded packet is itself a Dest Unreachable / Frag Needed */ outer[0] = ICMP_TYPE_DEST_UNREACHABLE;
buffer[20] = ICMP_TYPE_DEST_UNREACHABLE; outer[1] = ICMP_CODE_FRAG_NEEDED;
buffer[21] = ICMP_CODE_FRAG_NEEDED; *(uint16_t *)(outer + 6) = htons(1400); /* next-hop MTU */
buffer[22] = 0;
buffer[23] = 0; fill_ip_header(embedded, "10.0.0.1", "10.0.0.2", 1);
*(uint16_t *)(buffer + 24) = 0; /* unused */ fill_icmp_header(embedded, ICMP_TYPE_ECHO_REQUEST, 0, 0x1234, 0x0001);
*(uint16_t *)(buffer + 26) = htons(1400); /* mtu in network order */
memset(&reply, 0, sizeof(reply));
reply.type = ICMP_TYPE_DEST_UNREACHABLE;
reply.code = ICMP_CODE_FRAG_NEEDED;
reply.payload = embedded;
reply.payload_len = sizeof(embedded);
reply.ip_payload = outer;
icmp_offending_packet_t offending; icmp_offending_packet_t offending;
int ret = icmp_error_extract_offending(&reply, &offending); int ret = icmp_error_extract_offending(&reply, &offending);
cr_assert_eq(ret, 0); cr_assert_eq(ret, 0);
cr_assert_eq(offending.icmp_type, ICMP_TYPE_DEST_UNREACHABLE); cr_assert_eq(offending.icmp_type, ICMP_TYPE_ECHO_REQUEST);
cr_assert_eq(offending.icmp_code, ICMP_CODE_FRAG_NEEDED); cr_assert_eq(offending.rest.echo.id, 0x1234);
cr_assert_eq(offending.rest.frag.mtu, 1400, "MTU should be in host byte order"); cr_assert_eq(offending.rest.echo.seq, 0x0001);
cr_assert_eq(offending.next_mtu, 1400, "MTU should be in host byte order");
} }
Test(extract_offending, redirect_with_tcp_packet) Test(extract_offending, redirect_with_tcp_packet)