Skip to content

(18) policer: rate limiting with RFC 2697/2698 three color markers - #1907

Draft
daniel-noland wants to merge 8 commits into
pr/daniel-noland/lldp-lifecyclefrom
pr/daniel-noland/rate-limit
Draft

daniel-noland wants to merge 8 commits into
pr/daniel-noland/lldp-lifecyclefrom
pr/daniel-noland/rate-limit

Conversation

@daniel-noland

@daniel-noland daniel-noland commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on (17g) #1925, the top of the LLDP stack ((17a) #1906 – (17g) #1925).

A rate limiting network function. Each rule matches traffic, to start with by source VNI and inner destination prefix, and limits packets per second and/or bytes per second. Every packet is colored by an RFC 2697 (srTCM) or RFC 2698 (trTCM) meter, and each color has an action: pass, remark DSCP, or drop.

Why policing, not shaping

At 100 Gb/s anything that queues is a liability. The three color markers are meters: each packet is colored when it arrives and never held, for a few comparisons a packet. What is hard at line rate is the workers, not the algorithm. RSS spreads one rule's traffic over every worker, and a bucket updated per packet would bounce one cache line between all of them.

Commits

  1. policer
    • Profiles for both markers.
    • Limits on bytes, packets, or both.
    • An exact single-owner Meter.
    • SharedMeter/LocalMeter: each worker leases tokens in batches, so the shared lock is taken roughly once per lease, not once per packet. A refused worker stays off the shared state until the bucket could have refilled.
    • The departure from the RFC model is bounded: a burst can stretch by one lease per worker, and the long-term rate is unchanged.
    • Tested against a literal tick-by-tick transcription of both RFCs, with a property test over worker interleavings and a shuttle/loom model. RFC 2697/2698 are vendored and cited with duvet.
  2. refactor(acl): the first-match classifier table (rte_acl or reference) moves out of acl-filter into acl::table, so any MatchKey can use it.
  3. config: rate limits in the overlay model.
    • Each rule has a name, a VPC, a destination prefix as the VPC addresses it (before NAT), limits, and an action per color.
    • Validation refuses an unknown VPC, a repeated name, and two rules on the same VPC and destination. Nested destinations are allowed; the longest prefix wins.
  4. net: DoneReason::RateLimited, and Ipv6::payload_length.
  5. policer: Display for limits.
  6. rate-limit: the RateLimiter<C: Classify> stage.
    • It is generic over the classifier, so it can police on any key at any point in the pipeline. VpcDestination is the first classifier.
    • A rule that survives a config change with the same name and limits keeps its meter and its counts.
    • Counts are kept per worker, so counting never contends.
  7. dataplane:
    • the stage sits after the ACLs (denied packets are not charged) and before NAT;
    • mgmt applies the rules;
    • read-only show rate-limits displays them;
    • rate-limited drops are never punted.

Tests

Passing: policer, rate-limit, config, net, just test dataplane (87) and just test mgmt (22). The mgmt sample-config test now applies a rate limit through rte_acl and checks that it reaches the workers. rte_acl is also checked against the reference backend.

Not yet done

  • The k8s/gateway API conversion. Rate limits exist in the internal model only.
  • Real-hardware throughput measurement.
  • Caching the rule on the flow, to skip classifying packets of known flows.
  • Overlay-algebra generation of rate limits.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@codecov

codecov Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

daniel-noland and others added 8 commits October 16, 2026 02:02
…rkers

A policer colors each packet on arrival and queues nothing, so unlike a
shaper it costs a few comparisons a packet at any rate. This adds the
meters a rate limiting stage will build on:

- profiles for the single rate (RFC 2697) and two rate (RFC 2698)
  markers, refusing the configurations the RFCs rule out;
- limits on bytes, packets, or both: a packet takes the best color both
  can pay for, and pays it in both;
- `Meter`, the exact single-owner meter, with exact integer refill;
- `SharedMeter`/`LocalMeter`, for traffic RSS spreads over workers. Each
  worker leases tokens in batches (100 us of rate, at most a quarter of
  the bucket split among the workers), so workers do not bounce a cache
  line per packet. A refused worker does not ask again until the bucket
  could have earned what it asked for, so traffic over the limit makes no
  shared writes. Leases can stretch a burst by a lease per worker, and
  can strand tokens briefly; neither changes the long term rate.

The meters are tested against a literal, tick by tick transcription of
both RFCs. A single worker leasing only what it needs must match it
exactly, any interleaving of workers must stay within what the buckets
earn, and a model-checked (shuttle/loom) test shows concurrent workers
spend each token once. RFC 2697 and 2698 are vendored for duvet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`AnyTable`, its rule rows and `build_table` were private to acl-filter,
but nothing in them is about ACLs: they build a first-match table for any
`MatchKey`, on rte_acl or the reference backend, and keep the rules for
display. They move to `acl::table` as `Table`, so the rate limiter can
classify with the same machinery. The reference backend is now gated on
the acl crate's `reference` feature rather than acl-filter's `cfg(test)`;
acl-filter's dev-dependency already enables it, so production still links
only rte_acl. `Table` also implements `lookup::Lookup`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A rate limit names a VPC and a destination prefix (as the VPC addresses
it, before NAT), the limits to meter that traffic against as one
aggregate (`policer::Limits`: bytes and/or packets, RFC 2697 or 2698),
and what happens to each color: pass, remark DSCP, or drop. By default
green and yellow pass and red drops.

Validation resolves the VPC to its VNI and refuses an unknown VPC, a
repeated name, and two rate limits on the same VPC and destination. One
packet is metered by one rate limit, the one with the longest matching
destination, so nested destinations are fine but equal ones would be
ambiguous.

The overlay algebra does not generate rate limits yet; the completeness
record says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`DoneReason::RateLimited` marks a packet a rate limit dropped; the bridge
drops it like every other policy drop. `Ipv6::payload_length` reads the
header field, so a meter can count the bytes of an IPv6 packet as RFC
2697 and 2698 do: the whole IP packet, header included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`bytes: trTCM cir 1000/s cbs 1500 pir 2000/s pbs 3000; packets: ...`,
for the CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`RateLimiter<C>` asks its `Classify` which rule meters a packet, colors
the packet with that rule's `policer::SharedMeter`, and passes, remarks
the DSCP of (refreshing checksums), or drops it (`RateLimited`) as the
rule says for the color. The stage knows nothing of what rules match, so
the same stage can police on any key at any point of the pipeline.

`VpcDestination` is the classifier for the configured rate limits: the
source VNI and the destination prefix, longest prefix first, on the
`acl::table` machinery (rte_acl in production). rte_acl wants a one byte
first field, so the key carries the protocol, wildcarded for now.

Rules reach the workers through a slot, as ACLs do. A rule that survives
a new configuration with the same name and limits keeps its meter, so its
buckets do not refill on every config change, and keeps its counts. Each
worker keeps its own counts per rule and writes only those, so counting
never contends; the CLI sums them.

Tests cover classification (longest prefix, VPC, v4/v6), each color's
action and the counts, packets the stage must leave alone, rules carried
over and restarted across configurations, rendering, and rte_acl
agreeing with the reference backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    show rate-limits

The rate limiter runs after the ACLs, so denied packets are not charged
to a limit, and before NAT, so destinations are matched as the source VPC
addresses them. Each worker's stage reads the rules from one writer,
sized for the number of workers so that leases scale with them.

Applying a config builds the rules on rte_acl after the ACL tables. The
CLI only reads: per rule, what it matches, its limits, and what each
color did, in packets and bytes, summed over the workers. A rate limited
drop is never punted to the control plane.

The mgmt sample config now carries a rate limit, and its test checks that
applying the config hands the rule to the workers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a third profile, CommittedPlusExcess (CIR, CBS, EIR, EBS). Unlike
RFC 2698's peak rate, the excess rate adds to the committed rate: green
packets pay only the committed bucket and yellow packets only the excess
bucket, so in-profile traffic is never refused because the second bucket
is small or drained by yellow traffic.

RFC 4115 colors packets as RFC 2697 does and refills as RFC 2698 does,
so the internals are renamed after what they describe: Shape::Separate
and Shape::Nested say which buckets each color pays from,
Buckets::Cascading and Buckets::Independent how the buckets refill. The
shared meter sizes leases on the excess rate.

Where RFC 4115's text (Tc(t) - B > 0) and its Figure 1 (B <= Tc(t-))
disagree at equality, the meter follows the figure, as RFC 2697 and
2698 do; the citation records why. Measuring rates in bits and sizing
buckets above the largest packet are left to the configuration, which
knows both; they are recorded as exceptions.

The literal tick by tick model and the fuzzed equivalence and leasing
tests cover the new profile, with unit tests for the additive rates, the
independence of green from the excess bucket, and the equality case. RFC
4115 is vendored for duvet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@daniel-noland
daniel-noland force-pushed the pr/daniel-noland/rate-limit branch from b3bba1a to e8733e0 Compare October 16, 2026 08:43
@daniel-noland
daniel-noland force-pushed the pr/daniel-noland/lldp-lifecycle branch from 1d2adae to 5934986 Compare October 16, 2026 08:43
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.

1 participant