Skip to content

Conversation

@SkohTV
Copy link
Contributor

@SkohTV SkohTV commented Oct 8, 2025

Fix #215
Currently a WIP POC

I've started getting into adding this features.
So far, I've managed to add the ratelimit [int]/[time_unit] keyword to the cli and put it's value into a bf_map.

However I'm not sure about implementing a "real" rate limiting as it seems to require EMIT (and that look scary).
Added (empty for now) elfstub to handle rate limiting

The idea of the implementation so far is pretty naive (only allow the first X requests to pass through), mostly because it was easier to implement and I hadn't strong feelings on which direction to go.

I'll keep this as a draft for now since it's still in its early stages.

@meta-cla meta-cla bot added the cla signed label Oct 8, 2025
@SkohTV SkohTV force-pushed the rate-limit branch 2 times, most recently from 3d56c6e to 63e5923 Compare October 8, 2025 20:45
@SkohTV SkohTV changed the title cli: add ratelimit keyword feature: rate limit Oct 10, 2025
Copy link
Contributor

@qdeslandes qdeslandes left a comment

Choose a reason for hiding this comment

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

I went for an early review, mostly for the parsing part. On the BPF side, the map will contain the runtime values for a given rule's rate limit (current burst/allowance, limit, last update time...).

char *tmp = in;
uint32_t limit = atoi(tmp);

printf("Got %d\n", limit);
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove

{
_cleanup_free_ char *in = $2;
char *tmp = in;
uint32_t limit = atoi(tmp);
Copy link
Contributor

Choose a reason for hiding this comment

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

strtoul with overflow check would be better, see https://github.com/facebook/bpfilter/blob/main/src/libbpfilter/matcher.c#L283.

@SkohTV SkohTV force-pushed the rate-limit branch 3 times, most recently from 5021faf to df768e9 Compare January 23, 2026 21:26
@SkohTV
Copy link
Contributor Author

SkohTV commented Jan 23, 2026

Here is a working POC
Not going to far, to make it easier to change/correct things if required

Things done:

  • Added ratelimit XX/XX (ex: ratelimit 5/sec) to bfcli
  • Created a map to store current (packets that passed in this unit of time) and last_time (last packet timestamp)
  • Added an elfstub (and a bunch of emits) to allow only X packets to be ACCEPTED, packets over the rate limit will be DROPPED

Things remaining:

  • Make the unit (sec) actually be used (atm we only do it per second)
  • Handle cli syntax better (errors on negative or wrong syntax)

Testing:

$ sudo ./result/sbin/bpfilter --transient --verbose=bpf

$ sudo ./result/sbin/bfcli chain set --from-str \
	'chain my_chain BF_HOOK_XDP{ifindex=2} ACCEPT
	rule ip4.proto icmp log internet ratelimit 5/sec ACCEPT'

$ ping 8.8.8.8 -c 10 -i 0.02
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=120 time=24.7 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=120 time=18.9 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=120 time=14.4 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=120 time=17.1 ms
64 bytes from 8.8.8.8: icmp_seq=5 ttl=120 time=13.5 ms

--- 8.8.8.8 ping statistics ---
10 packets transmitted, 5 received, 50% packet loss, time 184ms
rtt min/avg/max/mdev = 13.462/17.706/24.692/3.982 ms, pipe 2

There's a bunch of doc to do and leftover comments to remove, but that should be enough to swi(t)ch this PR from draft to "ready-for-review" now that the feature is working !

@SkohTV SkohTV marked this pull request as ready for review January 23, 2026 21:49
@SkohTV SkohTV requested a review from qdeslandes January 31, 2026 01:08
BF_RULE_OPTION_MARK = 1 << 2,
BF_RULE_OPTION_LOG = 1 << 0,
BF_RULE_OPTION_COUNTER = 1 << 1,
BF_RULE_OPTION_RATELIMIT = 1 << 2,
Copy link
Contributor

Choose a reason for hiding this comment

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

New options should be added at the end, otherwise it breaks the ABI.

bf_parse_err("ratelimit should be positive");

errno = 0;
uint32_t limit = strtoul(strtok_r(tmp, "/", &saveptr), NULL, 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Variables should be defined at the beginning of the scope.

Comment on lines +326 to +334
$$ = (struct bf_rule_options){
.ratelimit = limit,
.flags = BF_RULE_OPTION_RATELIMIT,
};

$$ = (struct bf_rule_options){
.ratelimit = limit,
.flags = BF_RULE_OPTION_RATELIMIT,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Duplicate.


if (current_time != ratelimit->last_time) {
ratelimit->current = 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

No braces needed.

Comment on lines +313 to +318
case BF_MAP_TYPE_RATELIMIT: // I have no clue what I'm doing here
btf__add_int(kbtf, "u64", 8, 0);
btf->key_type_id = btf__add_int(kbtf, "u32", 4, 0);
btf->value_type_id = btf__add_struct(kbtf, "bf_", 16);
btf__add_field(kbtf, "limit", 1, 0, 0);
break;
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for this yet.

*/
BF_ELFSTUB_LOG,

// Return 0 on ACCEPT, 1 on DROP
Copy link
Contributor

Choose a reason for hiding this comment

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

Document the full elfstub.

EMIT(program, BPF_MOV32_IMM(BPF_REG_3, rule->ratelimit));
EMIT_FIXUP_ELFSTUB(program, BF_ELFSTUB_RATELIMIT);

_clean_bf_jmpctx_ struct bf_jmpctx ctx =
Copy link
Contributor

Choose a reason for hiding this comment

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

Variables are defined at the beginning of the scope.

Comment on lines +642 to +645
EMIT(program,
BPF_MOV64_IMM(BPF_REG_0,
program->runtime.ops->get_verdict(BF_VERDICT_DROP)));
EMIT(program, BPF_EXIT_INSN());
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to think this through. With the current implementation: if the rate limit is reached, all extra packet matching the rule are dropped. What if the rule already drops the packet? Then you drop all the matching packets, meaning the rate limit actually matches because the outcome is the same.

Currently, actions (mark, log, counter) don't have any impact on whether the rule matches or not. This one does. Would this make more sense as a matcher instead? It would allow for negative matches:

  • rule meta.ratelimit 4pkt/s ACCEPT: if the packet is within the limit, accept it
  • rule meta.ratelimit not 4pkt/s DROP: packets within the limit are ignored, while packets above are matched and dropped (not or another keyword that fits better).

Seems like this would be more in line with nftables. What do you think? I'm open to alternatives here.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Traffic rate limiting

2 participants