Policy scripts: the operator's own rules for routes, in Rhai

A new crate, bgp-policy, runs functions of a script on routes: on their
way in, after the general checks of a peer's import and before the
checks that can change (RPKI, next hop reachability); and on their way
out to a peer. A function is given the route and the peer, may refuse
the route, and may change its communities, large communities,
LOCAL_PREF and MED, and on the way out prepend our AS:

    fn from_peer(route, peer) {
        if route.has_community("65535:666") { return false; }
        route.add_community(LEARNED_FROM[peer.role]);
        route.local_pref = PREFERENCE[peer.role];
        true
    }

Configuration: policy { path | source, import_function, export_function }
and, for a peer, import { function } and export { function }. The script
is compiled at startup; `bgp --check` reads it and says which function
is whose. A function that fails on a route has refused it. Scripts have
no files, network or clock, and are stopped after 200,000 steps.

Route selection now goes by a LOCAL_PREF on a route from an external
peer: the decoder never lets a peer's own through, so one that is there
was set by policy.

What it costs was measured before it was built on (benches/policy.rs):
a call that tests a few communities and changes two takes 1 to 3
microseconds, some two hundred times what Rust would. So a function is
asked once for the routes of an UPDATE, which share their attributes,
unless it asks for the prefix. One peer sending 1.23 M routes over a
session: 0.37 s without a function, 0.80 s with one that marks and
ranks every route, 3.4 s if it also looks at the prefix.

Export applies to what is announced, which today is what the daemon
originates; learned routes are not passed on yet.

Tested by unit tests of the crate, sessions end to end, a fuzz target on
the example script (testdata/policy.rhai), and a lab phase in which a
script changes which of FRR's and BIRD's route is chosen and what each
of them is told.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-02 21:09:39 +02:00
1 parent f1d80c66df
commit cbf0df1eef
52 files changed
+3467 -43

No files matched your search

+10
View File
@@ -176,6 +176,12 @@ pub enum ConfigError {
HttpPort,
#[error("rpki: no caches are listed")]
RpkiNoCaches,
#[error("policy: give the script's path or its source, and not both")]
PolicySource,
#[error("policy: cannot read {path}: {error}")]
PolicyFile { path: String, error: String },
#[error("policy: {0}")]
Policy(#[from] bgp_policy::PolicyError),
#[error("rpki: the port of cache {0:?} is required")]
RpkiCachePort(String),
#[error("peer {peer:?}: {problem}")]
@@ -192,6 +198,10 @@ pub enum ConfigError {
pub enum PeerProblem {
#[error("endpoint is missing")]
MissingEndpoint,
#[error("{0}")]
Policy(#[from] bgp_policy::PolicyError),
#[error("names the policy function {0:?}, and there is no policy script")]
NoPolicyScript(String),
#[error("endpoint is invalid: {0}")]
Endpoint(Box<ConfigError>),
#[error("remote_asn must be non-zero")]