feat: add -M (dont-fragment) option

- Add FLAG_DONT_FRAGMENT flag and cli_handle_dont_fragment handler
- Register -M/--dont-fragment option in the X-macro option table
- Apply DF bit via icmp_set_dont_fragment() before ping loop
- Expand error output: table-driven ICMP error messages and dedicated
  frag-needed output with next-hop MTU
- Add STATIC_ARRAY_FOREACH / COUNT_OF macros to compiler.h
This commit is contained in:
lohhiiccc 2026-03-13 03:22:14 +01:00
parent 5a8671bdde
commit 770265ee80
9 changed files with 107 additions and 17 deletions

View file

@ -3,4 +3,9 @@
#define __unused __attribute__((unused)) #define __unused __attribute__((unused))
#define COUNT_OF(arr) (sizeof(arr) / sizeof((arr)[0]))
#define STATIC_ARRAY_FOREACH(arr, ptr) \
for ((ptr) = (arr); (ptr) < (arr) + COUNT_OF(arr); (ptr)++)
#endif #endif

View file

@ -91,6 +91,14 @@
cli_handle_flood, \ cli_handle_flood, \
OPT_ARG_NONE, \ OPT_ARG_NONE, \
"Flood mode" \ "Flood mode" \
) \
X( \
'M', \
"dont-fragment", \
no_argument, \
cli_handle_dont_fragment, \
OPT_ARG_NONE, \
"Set the Don't Fragment bit" \
) )
#undef X #undef X

View file

@ -6,6 +6,7 @@
extern const struct option_descriptor g_options[]; extern const struct option_descriptor g_options[];
int cli_handle_count(const char *arg, void *config); int cli_handle_count(const char *arg, void *config);
int cli_handle_dont_fragment(const char *arg, void *config);
int cli_handle_deadline(const char *arg, void *config); int cli_handle_deadline(const char *arg, void *config);
int cli_handle_flood(const char *arg, void *config); int cli_handle_flood(const char *arg, void *config);
int cli_handle_help(const char *arg, void *config); int cli_handle_help(const char *arg, void *config);

View file

@ -1,9 +1,10 @@
#ifndef FT_PING_FLAGS #ifndef FT_PING_FLAGS
#define FT_PING_FLAGS #define FT_PING_FLAGS
#define FLAG_VERBOSE (1 << 0) #define FLAG_VERBOSE (1 << 0)
#define FLAG_QUIET (1 << 1) #define FLAG_QUIET (1 << 1)
#define FLAG_FLOOD (1 << 2) #define FLAG_FLOOD (1 << 2)
#define FLAG_DONT_FRAGMENT (1 << 3)
#define HAS_FLAG(flags, flag) ((flags) & (flag)) #define HAS_FLAG(flags, flag) ((flags) & (flag))
#define SET_FLAG(flags, flag) ((flags) |= (flag)) #define SET_FLAG(flags, flag) ((flags) |= (flag))

View file

@ -6,6 +6,7 @@ PING_SRCS = $(CLI_SRCS) \
$(PING_SRC_DIR)/main.c \ $(PING_SRC_DIR)/main.c \
$(PING_SRC_DIR)/cli/parse.c \ $(PING_SRC_DIR)/cli/parse.c \
$(PING_SRC_DIR)/cli/handlers/handle_count.c \ $(PING_SRC_DIR)/cli/handlers/handle_count.c \
$(PING_SRC_DIR)/cli/handlers/handle_dont_fragment.c \
$(PING_SRC_DIR)/cli/handlers/handle_deadline.c \ $(PING_SRC_DIR)/cli/handlers/handle_deadline.c \
$(PING_SRC_DIR)/cli/handlers/handle_flood.c \ $(PING_SRC_DIR)/cli/handlers/handle_flood.c \
$(PING_SRC_DIR)/cli/handlers/handle_help.c \ $(PING_SRC_DIR)/cli/handlers/handle_help.c \

View file

@ -96,7 +96,8 @@ static void
build_long_options(struct option *long_opts, size_t nb_opts, build_long_options(struct option *long_opts, size_t nb_opts,
const struct option_descriptor *src) const struct option_descriptor *src)
{ {
for (size_t i = 0; i < nb_opts; ++i) { for (size_t i = 0; i < nb_opts; ++i)
{
long_opts[i].name = src[i].long_opt; long_opts[i].name = src[i].long_opt;
long_opts[i].has_arg = src[i].has_arg; long_opts[i].has_arg = src[i].has_arg;
long_opts[i].flag = NULL; long_opts[i].flag = NULL;

View file

@ -0,0 +1,11 @@
#include "compiler.h"
#include "ping/cli.h"
#include "ping/ft_ping_flags.h"
int
cli_handle_dont_fragment(__unused const char *arg, void *config_void)
{
struct ping_config *config = (struct ping_config *)config_void;
SET_FLAG(config->flags, FLAG_DONT_FRAGMENT);
return CLI_SUCCESS;
}

View file

@ -25,22 +25,28 @@ int
ping_run(const struct ping_config *config) ping_run(const struct ping_config *config)
{ {
icmp_handle_t *handle; icmp_handle_t *handle;
int ret; int ret = 0 ;
size_t i; size_t i = 0 ;
if (0 == config->nb_destinations) if (0 == config->nb_destinations)
return 1; return 1;
handle = icmp_create(); handle = icmp_create();
if (NULL == handle) if (NULL == handle)
return 1; return 1;
ret = 0; if (HAS_FLAG(config->flags, FLAG_DONT_FRAGMENT)
i = 0; && 0 != icmp_set_dont_fragment(handle))
{
ret = 1;
goto cleanup;
}
while (i < config->nb_destinations) while (i < config->nb_destinations)
{ {
if (0 != ping_one(config, config->destinations[i], handle)) if (0 != ping_one(config, config->destinations[i], handle))
ret = 1; ret = 1;
i++; i++;
} }
cleanup:
icmp_destroy(handle); icmp_destroy(handle);
return ret; return ret;
} }

View file

@ -1,4 +1,5 @@
#include <stdio.h> #include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include "compiler.h" #include "compiler.h"
@ -7,27 +8,82 @@
#include "icmp_types.h" #include "icmp_types.h"
#include "internal/ping/output.h" #include "internal/ping/output.h"
// TODO: CLEAN THIS FILE !
/* Forward declarations */ /* Forward declarations */
static const char *error_msg_for(const icmp_reply_t *reply); static void output_frag_needed(const char *from, uint16_t seq,
uint16_t next_mtu);
static const char *error_msg_for(const icmp_reply_t *reply);
/* -------------------- */ /* -------------------- */
void void
ping_output_error(const icmp_reply_t *reply, ping_output_error(const icmp_reply_t *reply,
__unused const icmp_offending_packet_t *offending, const icmp_offending_packet_t *offending,
uint16_t seq, __unused const struct ping_config *config) uint16_t seq, __unused const struct ping_config *config)
{ {
char from_str[INET_ADDRSTRLEN]; char from_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &reply->from, from_str, sizeof(from_str)); inet_ntop(AF_INET, &reply->from, from_str, sizeof(from_str));
fprintf(stderr, "From %s: icmp_seq=%u %s\n", if (ICMP_TYPE_DEST_UNREACHABLE == reply->type
from_str, (unsigned int)seq, error_msg_for(reply)); && ICMP_CODE_FRAG_NEEDED == reply->code)
output_frag_needed(from_str, seq, offending->next_mtu);
else
dprintf(STDERR_FILENO, "From %s: icmp_seq=%u %s\n",
from_str, (unsigned int)seq, error_msg_for(reply));
}
static void
output_frag_needed(const char *from, uint16_t seq, uint16_t next_mtu)
{
if (next_mtu > 0)
dprintf(STDERR_FILENO,
"From %s: icmp_seq=%u Frag needed and DF set (mtu = %u)\n",
from, (unsigned int)seq, (unsigned int)next_mtu);
else
dprintf(STDERR_FILENO,
"From %s: icmp_seq=%u Frag needed and DF set (mtu unknown)\n",
from, (unsigned int)seq);
} }
static const char * static const char *
error_msg_for(const icmp_reply_t *reply) error_msg_for(const icmp_reply_t *reply)
{ {
if (ICMP_TYPE_TIME_EXCEEDED == reply->type) static const struct s_error_entry
return ("Time to live exceeded"); {
if (ICMP_CODE_HOST_UNREACHABLE == reply->code) uint8_t type;
return ("Destination Host Unreachable"); uint8_t code;
return ("Destination Net Unreachable"); const char *msg;
} error_table[] = {
{
ICMP_TYPE_TIME_EXCEEDED, ICMP_CODE_TTL_EXCEEDED,
"Time to live exceeded"
},
{
ICMP_TYPE_TIME_EXCEEDED, ICMP_CODE_FRAG_REASM_EXCEEDED,
"Frag reassembly time exceeded"
},
{
ICMP_TYPE_DEST_UNREACHABLE, ICMP_CODE_NET_UNREACHABLE,
"Destination Net Unreachable"
},
{
ICMP_TYPE_DEST_UNREACHABLE, ICMP_CODE_HOST_UNREACHABLE,
"Destination Host Unreachable"
},
{
ICMP_TYPE_DEST_UNREACHABLE, ICMP_CODE_PROTOCOL_UNREACHABLE,
"Destination Protocol Unreachable"
},
{
ICMP_TYPE_DEST_UNREACHABLE, ICMP_CODE_PORT_UNREACHABLE,
"Destination Port Unreachable"
},
};
const struct s_error_entry *entry;
STATIC_ARRAY_FOREACH(error_table, entry)
{
if (entry->type == reply->type && entry->code == reply->code)
return (entry->msg);
}
return ("Unknown ICMP error");
} }