From 7eb6397a5c55fe41536e5a3194ea41f46e18cdbf Mon Sep 17 00:00:00 2001 From: Farid Mihoub Date: Sun, 26 Apr 2026 22:58:19 +0200 Subject: [PATCH] icmp: add icmp rate limiter Implement a basic global ICMP rate limiter based on a minimum time space between responses. Signed-off-by: Farid Mihoub --- modules/infra/cli/icmp.c | 26 ++++++++++++++++++++++++++ modules/ip/api/gr_ip4.h | 7 +++++++ modules/ip/control/icmp.c | 10 ++++++++++ modules/ip/control/icmp_rl.c | 26 ++++++++++++++++++++++++++ modules/ip/control/icmp_rl.h | 16 ++++++++++++++++ modules/ip/control/meson.build | 1 + modules/ip/datapath/icmp_input.c | 8 ++++++++ 7 files changed, 94 insertions(+) create mode 100644 modules/ip/control/icmp_rl.c create mode 100644 modules/ip/control/icmp_rl.h diff --git a/modules/infra/cli/icmp.c b/modules/infra/cli/icmp.c index 403cdf4ee..685607585 100644 --- a/modules/infra/cli/icmp.c +++ b/modules/infra/cli/icmp.c @@ -6,6 +6,7 @@ #include "cli_l3.h" #include +#include #include #include @@ -39,6 +40,18 @@ static cmd_status_t ping(struct gr_api_client *c, const struct ec_pnode *p) { return CMD_ERROR; } +static cmd_status_t icmp_rate_limit(struct gr_api_client *c, const struct ec_pnode *p) { + struct gr_ip4_icmp_rl_req req; + + if (arg_u32(p, "INTERVAL", &req.rate_limit)) + return CMD_ERROR; + + if (gr_api_client_send_recv(c, GR_IP4_ICMP_RATE_LIMIT, sizeof(req), &req, NULL) < 0) + return CMD_ERROR; + + return CMD_SUCCESS; +} + static cmd_status_t traceroute(struct gr_api_client *c, const struct ec_pnode *p) { struct cli_icmp_ops *ops; uint8_t ip[16]; @@ -94,6 +107,19 @@ static int ctx_init(struct ec_node *root) { ec_node_uint("IDENT", 1, UINT16_MAX, 10) ) ); + if (ret < 0) + return ret; + + ret = CLI_COMMAND( + CLI_CONTEXT(root, CTX_ARG("icmp", "Icmp rate limit config")), + "rate-limit (INTERVAL)", + icmp_rate_limit, + "Set the icmp rate limiter", + with_help( + "The time space interval in milliseconds", + ec_node_uint("INTERVAL", 1, UINT32_MAX, 10) + ) + ); return ret; } diff --git a/modules/ip/api/gr_ip4.h b/modules/ip/api/gr_ip4.h index c150863e3..5de966672 100644 --- a/modules/ip/api/gr_ip4.h +++ b/modules/ip/api/gr_ip4.h @@ -39,6 +39,7 @@ enum gr_ip4_requests : uint32_t { GR_IP4_ICMP_RECV, GR_IP4_FIB_DEFAULT_SET, GR_IP4_FIB_INFO_LIST, + GR_IP4_ICMP_RATE_LIMIT, }; // routes ////////////////////////////////////////////////////////////////////// @@ -148,6 +149,12 @@ struct gr_ip4_icmp_recv_resp { GR_REQ(GR_IP4_ICMP_RECV, struct gr_ip4_icmp_recv_req, struct gr_ip4_icmp_recv_resp); +struct gr_ip4_icmp_rl_req { + uint32_t rate_limit; +}; + +GR_REQ(GR_IP4_ICMP_RATE_LIMIT, struct gr_ip4_icmp_rl_req, struct gr_empty); + // fib info //////////////////////////////////////////////////////////////////// // FIB status for a VRF. diff --git a/modules/ip/control/icmp.c b/modules/ip/control/icmp.c index c07712e5d..fa00d4d29 100644 --- a/modules/ip/control/icmp.c +++ b/modules/ip/control/icmp.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2024 Christophe Fontaine +#include "icmp_rl.h" #include "ip4.h" #include "ip4_datapath.h" #include "log.h" @@ -87,6 +88,13 @@ static struct rte_mbuf *get_icmp_response(uint16_t ident, uint16_t seq_num, cloc return errno_set_null(ENOENT); } +static struct api_out icmp_rate_limit(const void *request, struct api_ctx *) { + const struct gr_ip4_icmp_rl_req *rl = request; + icmp_rl_init(rl->rate_limit); + + return api_out(0, 0, NULL); +} + static struct api_out icmp_send(const void *request, struct api_ctx *) { const struct gr_ip4_icmp_send_req *req = request; const struct nexthop *nh; @@ -167,6 +175,7 @@ static void icmp_init(struct event_base *) { SOCKET_ID_ANY, 0 // flags ); + if (pool == NULL) ABORT("rte_mempool_create(icmp_queue) failed"); } @@ -191,6 +200,7 @@ RTE_INIT(icmp_module_init) { module_register(&icmp_module); api_handler(GR_IP4_ICMP_SEND, icmp_send); api_handler(GR_IP4_ICMP_RECV, icmp_recv); + api_handler(GR_IP4_ICMP_RATE_LIMIT, icmp_rate_limit); icmp_input_register_callback(RTE_ICMP_TYPE_DEST_UNREACHABLE, icmp_input_cb); icmp_input_register_callback(RTE_ICMP_TYPE_TTL_EXCEEDED, icmp_input_cb); icmp_input_register_callback(RTE_ICMP_TYPE_ECHO_REPLY, icmp_input_cb); diff --git a/modules/ip/control/icmp_rl.c b/modules/ip/control/icmp_rl.c new file mode 100644 index 000000000..57fe8731c --- /dev/null +++ b/modules/ip/control/icmp_rl.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026 Farid Mihoub + +#include "icmp_rl.h" + +#include + +#include +#include + +static struct time_space icmp_rl = {0}; + +void icmp_rl_init(uint32_t interval_ms) { + icmp_rl.interval_ms = interval_ms; + icmp_rl.last_ts = gr_clock_us(); +} + +bool icmp_rl_allow() { + uint64_t now = gr_clock_us(); + + if ((now - icmp_rl.last_ts) < (icmp_rl.interval_ms * 1000)) + return false; + + icmp_rl.last_ts = now; + return true; +} diff --git a/modules/ip/control/icmp_rl.h b/modules/ip/control/icmp_rl.h new file mode 100644 index 000000000..30d83cb13 --- /dev/null +++ b/modules/ip/control/icmp_rl.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026 Farid Mihoub + +#pragma once + +#include +#include +#include + +struct time_space { + uint64_t last_ts; + uint32_t interval_ms; +}; + +void icmp_rl_init(uint32_t interval_ms); +bool icmp_rl_allow(); diff --git a/modules/ip/control/meson.build b/modules/ip/control/meson.build index 78458d91d..07cb9f5bd 100644 --- a/modules/ip/control/meson.build +++ b/modules/ip/control/meson.build @@ -6,5 +6,6 @@ src += files( 'icmp.c', 'nexthop.c', 'route.c', + 'icmp_rl.c', ) inc += include_directories('.') diff --git a/modules/ip/datapath/icmp_input.c b/modules/ip/datapath/icmp_input.c index 167cc9d79..0b81c3134 100644 --- a/modules/ip/datapath/icmp_input.c +++ b/modules/ip/datapath/icmp_input.c @@ -3,6 +3,7 @@ #include "control_output.h" #include "graph.h" +#include "icmp_rl.h" #include "ip4_datapath.h" #include "log.h" #include "mbuf.h" @@ -17,6 +18,7 @@ enum { CONTROL, INVALID, UNSUPPORTED, + RATE_LIMITED, EDGE_COUNT, }; @@ -49,6 +51,10 @@ icmp_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, edge = INVALID; goto next; } + if (!icmp_rl_allow()) { + edge = RATE_LIMITED; + goto next; + } icmp->icmp_type = RTE_ICMP_TYPE_ECHO_REPLY; ip = ip_data->dst; ip_data->dst = ip_data->src; @@ -95,6 +101,7 @@ static struct rte_node_register icmp_input_node = { [CONTROL] = "control_output", [INVALID] = "icmp_input_invalid", [UNSUPPORTED] = "icmp_input_unsupported", + [RATE_LIMITED] = "icmp_rate_limited", }, }; @@ -109,3 +116,4 @@ GR_NODE_REGISTER(icmp_input_info); GR_DROP_REGISTER(icmp_input_invalid); GR_DROP_REGISTER(icmp_input_unsupported); +GR_DROP_REGISTER(icmp_rate_limited);