Skip to content

Commit 595550a

Browse files
committed
gluon-nftables-limit-arp: limit each client device separately
The migration collapsed the per-device rate limit into a single limit statement, which keeps one bucket for the whole rule - all client devices shared it, so a single noisy device used up the budget of every other one. The package documents a limit per device. ebtables kept a bucket per rule, which is why the daemon added one rule per address. The rules are static here, so use a dynamic set keyed on the source address instead: every address gets its own bucket. The node-wide limit stays a plain limit statement, as it was a single rule before too. The limited address is the ethernet source, as in the ebtables ruleset, not the ARP sender hardware address. Requests for a target in the DAT cache now return early instead of being excluded in every rule, which keeps them out of the counting as documented. The interface matches that were commented out inside the chain are gone - the rule jumping into it already matches on them.
1 parent 31f1d73 commit 595550a

2 files changed

Lines changed: 44 additions & 50 deletions

File tree

nftables-migration-notes.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,27 @@ Worst case recovery latency is one poll interval (30s), during which the rate
139139
limit does not apply to already-known addresses. `gluon-radv-filterd` has the
140140
same problem and handles it the same way, see below.
141141

142+
### Per-device ARP rate limiting
143+
144+
ebtables keeps a separate rate limit bucket per rule, so `gluon-arp-limiter`
145+
could give every client device its own by adding one rule per address to
146+
ARP_LIMIT_TLCHECK. A generated nftables ruleset cannot do that - the rules are
147+
static and only the sets are driven by the daemon - and a plain `limit`
148+
statement keeps a single bucket for the whole rule, which all devices would
149+
then share.
150+
151+
The per-device limit is therefore a dynamic set keyed on the source address
152+
(`add @arplimit_rate { ether saddr limit rate over 6/minute burst 50 packets }`),
153+
which gives each address its own bucket. The node-wide 1/second limit stays a
154+
plain `limit` statement, as it was a single ebtables rule too.
155+
156+
Both limits apply, as before and as the package documentation describes: a
157+
known device passes at most 6 requests per minute, and the node as a whole at
158+
most one per second.
159+
160+
The limited address is the ethernet source (ebtables' `--source`), not the ARP
161+
sender hardware address the migration had used.
162+
142163
### Router advertisement filtering
143164

144165
`gluon-radv-filterd` drove an ebtables chain with a DROP policy: it flushed
@@ -193,9 +214,6 @@ knowing about when reviewing the rest:
193214

194215
## Open points
195216

196-
- The per-MAC ARP rate limit is still a single chain-wide bucket, and the
197-
interface matches in `limit_arp_chain.nft` are commented out, so the chain
198-
matches more broadly than the ebtables original did.
199217
- fw4's set of valid include positions could not be verified against its
200218
source, which is fetched at build time rather than checked in. `chain-pre`,
201219
`chain-post`, `table-post` and `ruleset-pre` are used on the strength of the
Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Addresses of client devices, taken from the batman-adv translation table,
2+
# and target addresses present in the batman-adv DAT cache. Both are
3+
# maintained by gluon-arp-limiter.
14
set limitmac {
25
type ether_addr
36
}
@@ -6,56 +9,29 @@ set datips {
69
type ipv4_addr
710
}
811

9-
# Rewrite arp packet target hardware address if target protocol address matches a given address.
10-
# input meta iifname enp2s0 arp ptype 0x0800 arp htype 1 arp hlen 6 arp plen 4 @nh,192,32 0xc0a88f10 @nh,144,48 set 0x112233445566 accept
12+
# One rate limit per client device. A plain limit statement keeps a single
13+
# bucket for the whole rule, which all devices would then share; keying a
14+
# dynamic set on the source address gives each of them its own.
15+
set arplimit_rate {
16+
type ether_addr
17+
size 1024
18+
flags dynamic,timeout
19+
timeout 5m
20+
}
1121

12-
# chain('ARP_LIMIT', 'DROP')
1322
chain arplimit {
14-
# obrname "br-client" \
15-
# oifname "bat0" \
16-
# arp operation request \
17-
# counter
23+
# Requests for a target in the DAT cache are answered locally and never
24+
# reach the mesh, so they are neither counted nor limited.
25+
arp ptype 0x0800 arp plen 4 arp daddr ip @datips return
1826

19-
# match everything which will land on bridge br-client
20-
# protocol type: ipv4
21-
# hardware type: ethernet
22-
# hardware address length: 6 byte mac
23-
# protocol address length: 4 byte ipv4
24-
# arp request
25-
# source address is mac to be limited
26-
# target address is not in DAT
27-
# we're over the limit
28-
# count
29-
# obrname "br-client" \
30-
# oifname "bat0" \
31-
arp ptype 0x0800 \
32-
arp htype 1 \
33-
arp hlen 6 \
34-
arp plen 4 \
35-
arp operation request \
36-
arp saddr ether @limitmac \
37-
arp daddr ip != @datips \
38-
limit rate over 6/minute burst 50 packets \
39-
counter \
40-
drop
27+
# Known client devices are allowed six requests per minute each.
28+
ether saddr @limitmac \
29+
add @arplimit_rate { ether saddr limit rate over 6/minute burst 50 packets } \
30+
counter \
31+
drop
4132

42-
# obrname "br-client" \
43-
# oifname "bat0" \
44-
arp ptype 0x0800 \
45-
arp htype 1 \
46-
arp hlen 6 \
47-
arp plen 4 \
48-
arp operation request \
49-
arp saddr ether != @limitmac \
50-
arp daddr ip != @datips \
33+
# On top of that, the node as a whole passes one request per second.
5134
limit rate over 1/second burst 50 packets \
52-
counter \
53-
drop
35+
counter \
36+
drop
5437
}
55-
56-
# chain('ARP_LIMIT_DATCHECK', 'RETURN')
57-
# %s ARP_LIMIT_DATCHECK -p ARP --arp-ip-dst %s -j mark --mark-or 0x2 --mark-target RETURN
58-
59-
# chain('ARP_LIMIT_TLCHECK', 'RETURN')
60-
# %s ARP_LIMIT_TLCHECK --source %s --limit 6/min --limit-burst 50 -j RETURN"
61-
# %s ARP_LIMIT_TLCHECK (add ? "2" : "") --source %s -j DROP

0 commit comments

Comments
 (0)