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>
67 lines
2.1 KiB
TOML
67 lines
2.1 KiB
TOML
[workspace]
|
|
default-members = ["bin/bgp"]
|
|
members = ["bin/*", "crates/*"]
|
|
# Fuzz targets need a nightly toolchain; see fuzz/README.md.
|
|
exclude = ["fuzz"]
|
|
resolver = "3"
|
|
|
|
[workspace.package]
|
|
authors = ["Rayhaan Jaufeerally <rayhaan+git@rayhaan.ch>"]
|
|
description = "A Border Gateway Protocol implementation"
|
|
edition = "2024"
|
|
homepage = "https://rayhaan.ch"
|
|
license = "Apache-2.0"
|
|
repository = "https://git.rayhaan.net/rayhaan/ferrix"
|
|
version = "0.0.2"
|
|
|
|
# Every crate opts in with `[lints] workspace = true`. CI turns warnings into
|
|
# errors, so a lint added here is enforced.
|
|
[workspace.lints.rust]
|
|
unsafe_code = "forbid"
|
|
|
|
[workspace.lints.clippy]
|
|
all = { level = "warn", priority = -1 }
|
|
dbg_macro = "warn"
|
|
todo = "warn"
|
|
unimplemented = "warn"
|
|
|
|
[workspace.dependencies]
|
|
# --- Our crates ---
|
|
bgp-fib = { path = "crates/fib" }
|
|
bgp-packet = { path = "crates/packet" }
|
|
bgp-policy = { path = "crates/policy" }
|
|
bgp-primitives = { path = "crates/primitives" }
|
|
bgp-rib = { path = "crates/rib" }
|
|
bgp-rpki = { path = "crates/rpki" }
|
|
bgp-server = { path = "crates/server" }
|
|
bgp-web = { path = "crates/web" }
|
|
bgp-integration-tests = { path = "crates/integration-tests" }
|
|
bgp-bench = { path = "crates/bench" }
|
|
|
|
# --- General ---
|
|
bitfield = "0.19.0"
|
|
bytes = "1.10.1"
|
|
eyre = "0.6.12"
|
|
nom = "8.0.0"
|
|
serde = { version = "1.0.219", features = ["derive"] }
|
|
nix = { version = "0.31", default-features = false, features = ["net"] }
|
|
socket2 = "0.6"
|
|
serde_json = "1.0.140"
|
|
serde_repr = "0.1.20"
|
|
thiserror = "2.0.12"
|
|
tokio = { version = "1.44.2", features = ["full"] }
|
|
strum = { version = "0.27.1", features = ["derive"] }
|
|
tracing = "0.1.44"
|
|
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
|
prometheus = "0.14.0"
|
|
proptest = "1.7"
|
|
protobuf = { package = "protobuf", version = "3.7.2" }
|
|
protobuf-codegen = { package = "protobuf-codegen", version = "3.7.2" }
|
|
clap = { version = "4.5.53", features = ["derive"] }
|
|
foldhash = "0.2"
|
|
futures = "0.3"
|
|
hashbrown = "0.17"
|
|
axum = "0.8.9"
|
|
tokio-stream = { version = "0.1.19", features = ["sync"] }
|
|
reqwest = { version = "0.13.5", default-features = false, features = ["json"] }
|