Skip to content

icmp rate limiter#607

Open
FRIDM636 wants to merge 1 commit intoDPDK:mainfrom
FRIDM636:main
Open

icmp rate limiter#607
FRIDM636 wants to merge 1 commit intoDPDK:mainfrom
FRIDM636:main

Conversation

@FRIDM636
Copy link
Copy Markdown

@FRIDM636 FRIDM636 commented Apr 27, 2026

Hello,

I've been studying the grout codebase and I'm looking forward to contributing to it.
As a first step, I have implemented a basic ICMP rate limiter based on time-spacing.

Before extending it, I would like your feedback on:

  • Use token bucket instead of or alongside time-spacing, to support burst tolerance
  • Per-type granularity via type mask, allowing independent limits per ICMP message type
  • Any specifics to ICMPv6 rate limiting support

Thanks.

ICMP Rate Limiter Implementation

Implements a basic global ICMP rate limiter based on minimum time spacing between Echo Request responses.

Core Implementation

modules/ip/control/icmp_rl.h and modules/ip/control/icmp_rl.c

  • Introduces a time-spacing rate limiter with a static time_space structure tracking last_ts (microsecond timestamp) and interval_ms (millisecond interval).
  • icmp_rl_init(interval_ms) initializes the limiter with caller-provided interval and captures current time via gr_clock_us().
  • icmp_rl_allow() compares elapsed microseconds against the configured interval; rejects requests occurring before the interval elapses (returns false), updates timestamp on acceptance (returns true).

Control Plane Integration

modules/ip/api/gr_ip4.h

  • Adds GR_IP4_ICMP_RATE_LIMIT request type with struct gr_ip4_icmp_rl_req containing a uint32_t rate_limit field.

modules/ip/control/icmp.c

  • Registers icmp_rate_limit() API handler that receives the interval value and calls icmp_rl_init().

Data Path Integration

modules/ip/datapath/icmp_input.c

  • Adds RATE_LIMITED edge to the ICMP input node state machine.
  • Gates Echo Request handling with icmp_rl_allow() check.
  • Routes rate-limited packets to dedicated icmp_rate_limited drop path instead of echo reply output.

User Interface

modules/infra/cli/icmp.c

  • Adds icmp rate-limit (INTERVAL) CLI command accepting interval in milliseconds (1 to UINT32_MAX).
  • Parses argument and sends API request; returns error on parsing or API failure.

modules/ip/control/meson.build

  • Adds icmp_rl.c to build source list.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Warning

Rate limit exceeded

@FRIDM636 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 51 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7e126730-eff0-4671-832a-9e546f4a4ee1

📥 Commits

Reviewing files that changed from the base of the PR and between d3f1f87 and 7eb6397.

📒 Files selected for processing (7)
  • modules/infra/cli/icmp.c
  • modules/ip/api/gr_ip4.h
  • modules/ip/control/icmp.c
  • modules/ip/control/icmp_rl.c
  • modules/ip/control/icmp_rl.h
  • modules/ip/control/meson.build
  • modules/ip/datapath/icmp_input.c
📝 Walkthrough

Walkthrough

This pull request adds ICMP rate limiting functionality across the stack. Changes include a new CLI command handler that accepts an interval parameter, API definitions for the rate-limit request, a control-plane handler to initialize the rate limiter, a new rate-limiter implementation with initialization and permission-checking functions, and integration into the ICMP input datapath to gate Echo Request handling using the rate-limit checks.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@modules/ip/control/icmp_rl.c`:
- Around line 11-25: The global rate-limiter state (icmp_rl of type time_space)
is accessed concurrently by icmp_rl_init() and icmp_rl_allow(), causing races;
serialize access by protecting reads/writes to icmp_rl.last_ts and
icmp_rl.interval_ms with a concurrency primitive (e.g., a mutex/spinlock or
atomic CAS) so only one worker can update/check the timestamp at a time. Modify
icmp_rl_init() to acquire the lock before setting icmp_rl.interval_ms and
icmp_rl.last_ts and release it afterwards, and modify icmp_rl_allow() to
atomically read/compare/update icmp_rl.last_ts (or acquire the same lock around
the now - last_ts check and the assignment) to ensure the interval logic is
enforced across workers; keep identifiers icmp_rl, icmp_rl_init, icmp_rl_allow,
last_ts, interval_ms and time_space for locating the changes.
- Around line 13-25: icmp_rl_init currently sets icmp_rl.last_ts to now which
forces the first icmp_rl_allow() call to wait a full interval; instead subtract
the configured interval (in microseconds) from the current timestamp so the
first reply is allowed immediately, and when comparing/multiplying use 64-bit
arithmetic: convert icmp_rl.interval_ms to microseconds with a uint64_t cast
(e.g. (uint64_t)icmp_rl.interval_ms * 1000) and use that value both when
backdating icmp_rl.last_ts in icmp_rl_init and when comparing in icmp_rl_allow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e39c273e-63bf-413b-9e47-df4d6e32a9f7

📥 Commits

Reviewing files that changed from the base of the PR and between 6d9e2ef and d3f1f87.

📒 Files selected for processing (7)
  • modules/infra/cli/icmp.c
  • modules/ip/api/gr_ip4.h
  • modules/ip/control/icmp.c
  • modules/ip/control/icmp_rl.c
  • modules/ip/control/icmp_rl.h
  • modules/ip/control/meson.build
  • modules/ip/datapath/icmp_input.c

Comment on lines +11 to +25
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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Serialize the shared limiter state.

icmp_rl_allow() and icmp_rl_init() both read/write the same global icmp_rl object, but this code is exercised from the ICMP input node, which can run on multiple workers. As written, concurrent packets can race through the check/update and both be accepted, so the rate limit is not enforced reliably.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/ip/control/icmp_rl.c` around lines 11 - 25, The global rate-limiter
state (icmp_rl of type time_space) is accessed concurrently by icmp_rl_init()
and icmp_rl_allow(), causing races; serialize access by protecting reads/writes
to icmp_rl.last_ts and icmp_rl.interval_ms with a concurrency primitive (e.g., a
mutex/spinlock or atomic CAS) so only one worker can update/check the timestamp
at a time. Modify icmp_rl_init() to acquire the lock before setting
icmp_rl.interval_ms and icmp_rl.last_ts and release it afterwards, and modify
icmp_rl_allow() to atomically read/compare/update icmp_rl.last_ts (or acquire
the same lock around the now - last_ts check and the assignment) to ensure the
interval logic is enforced across workers; keep identifiers icmp_rl,
icmp_rl_init, icmp_rl_allow, last_ts, interval_ms and time_space for locating
the changes.

Comment on lines +13 to +25
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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't arm the limiter with the current timestamp.

icmp_rl_init() currently makes the first Echo Reply after configuration wait a full interval, and the interval_ms * 1000 conversion is still done in 32-bit arithmetic. Please backdate last_ts by the configured interval and widen the microsecond conversion to uint64_t.

Possible fix
 void icmp_rl_init(uint32_t interval_ms) {
+	uint64_t interval_us = (uint64_t)interval_ms * 1000ULL;
 	icmp_rl.interval_ms = interval_ms;
-	icmp_rl.last_ts = gr_clock_us();
+	icmp_rl.last_ts = gr_clock_us() - interval_us;
 }
 
 bool icmp_rl_allow() {
 	uint64_t now = gr_clock_us();
+	uint64_t interval_us = (uint64_t)icmp_rl.interval_ms * 1000ULL;
 
-	if ((now - icmp_rl.last_ts) < (icmp_rl.interval_ms * 1000))
+	if ((now - icmp_rl.last_ts) < interval_us)
 		return false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/ip/control/icmp_rl.c` around lines 13 - 25, icmp_rl_init currently
sets icmp_rl.last_ts to now which forces the first icmp_rl_allow() call to wait
a full interval; instead subtract the configured interval (in microseconds) from
the current timestamp so the first reply is allowed immediately, and when
comparing/multiplying use 64-bit arithmetic: convert icmp_rl.interval_ms to
microseconds with a uint64_t cast (e.g. (uint64_t)icmp_rl.interval_ms * 1000)
and use that value both when backdating icmp_rl.last_ts in icmp_rl_init and when
comparing in icmp_rl_allow.

Implement a basic global ICMP rate limiter based on a minimum time space
between responses.

Signed-off-by: Farid Mihoub <farid.mihoub@yandex.com>
@rjarry
Copy link
Copy Markdown
Collaborator

rjarry commented Apr 28, 2026

Hi @FRIDM636,

Thanks for the patch but this feature was already submitted in #576

I forgot to link #14 to it

@christophefontaine
Copy link
Copy Markdown
Collaborator

Hi, thanks a lot for your contribution !
It seems we have 2 PRs trying to achieve the same function, so how is this different from #576 ?

@FRIDM636
Copy link
Copy Markdown
Author

Hi @christophefontaine
I think @rjarry has already done the right work. As I mentioned I added a simple rate limiting based on minimum time space between packets. I intended to go for the token bucket with a proportional refill rate instead of the full used refill.

I guess I should close the current PR.

I would like to see if there is a specific subject I can work on.

@rjarry
Copy link
Copy Markdown
Collaborator

rjarry commented Apr 28, 2026

I would like to see if there is a specific subject I can work on.

There are many things which would require some work. They are more or less fast/easy to implement. Some of them require extensive design work. Is there anything you need for a use case or are you doing this for exploration purposes?

Here are a few random ideas:

  • Support for route metrics (multiple nexthops attached to the same route prefix but only one is installed in the FIB).
  • Consistent hash algorithm for ECMP.
  • Basic ACL/Firewall (will require some design work)
  • Use BGP flowspec as control plane for ACL/Firewall
  • Basic QoS (classify on RX, shaping on TX with hardware offload)

@FRIDM636
Copy link
Copy Markdown
Author

There are many things which would require some work. They are more or less fast/easy to implement. Some of them require extensive design work. Is there anything you need for a use case or are you doing this for exploration purposes?

I have no specific use case, just looking to make open-source contributions. Grout caught my eye for two reasons: it aligns with my work (DPI, networking, DPDK integration...) and will definitely help harden my skills, and I genuinely appreciate your commitment to clean code and software architecture. Such high-quality work!

@maxime-leroy
Copy link
Copy Markdown
Collaborator

@FRIDM636 it seems you have some experience on FRR, any help on this topic will be appreciate.

You can join your channel on stack: https://dpdkproject.slack.com/archives/C07NAFWE1MG

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants