- scripts/in-namespace.sh set CARGO_TARGET_DIR to the empty string when it was unset, which cargo refuses, and dropped RUSTFLAGS, so the run in the namespace rebuilt what kernel-tests.sh had just built. Each is passed on only where set. - The README's list of unsupported features still had route reflection and graceful restart, both of which exist; the command-line reference lacked SIGUSR1 and exit status 75; the HTTP API reference did not say what errors look like. - The event stream said "missed 0 events" for the best-routes stream, which does not count what it missed; it leaves the count out now. - CI lints bgp-bench with its count-memory feature, which nothing built. - bgp-bench had two unused dev-dependencies; bgp-integration-tests had three dependencies that only its tests and examples use. - install.sh updates apt before installing; two-routers.sh loses an unused variable; small things in the web crate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
142 lines
4.8 KiB
Rust
142 lines
4.8 KiB
Rust
//! HTTP surface of the daemon: a read-only JSON API for inspecting state, a
|
|
//! status page, a server-sent event stream, and Prometheus metrics.
|
|
//!
|
|
//! Everything here is a thin layer over handles owned by the BGP server
|
|
//! ([`SessionRegistry`], [`LocalRib`], [`RibTable`]); no routing state lives
|
|
//! in this crate.
|
|
|
|
mod api;
|
|
mod events;
|
|
mod metrics;
|
|
|
|
use std::net::Ipv4Addr;
|
|
use std::sync::Arc;
|
|
use std::time::{Instant, SystemTime};
|
|
|
|
use axum::Router;
|
|
use axum::response::{Html, Redirect};
|
|
use axum::routing::get;
|
|
use bgp_fib::Fib;
|
|
use bgp_rib::LocalRib;
|
|
use bgp_rib::table::RibTable;
|
|
use bgp_rpki::Rpki;
|
|
use bgp_server::refusals::RefusalLog;
|
|
use bgp_server::restart::Restarts;
|
|
use bgp_server::session::SessionRegistry;
|
|
use tokio::net::TcpListener;
|
|
use tracing::info;
|
|
|
|
pub use api::{
|
|
AttrSummary, ConfigView, KernelView, LocalRouteView, PeerView, ReceivedRouteView, RefusalsView,
|
|
RefusedRouteView, RoutesPage, RpkiView, StatusView,
|
|
};
|
|
|
|
/// Static facts about the router shown in status output.
|
|
#[derive(Debug, Clone)]
|
|
pub struct RouterInfo {
|
|
pub router_id: Ipv4Addr,
|
|
pub local_asn: u32,
|
|
pub version: String,
|
|
pub started: Instant,
|
|
/// Wall-clock start time, for display.
|
|
pub started_at: SystemTime,
|
|
/// Name of the machine we run on, for display.
|
|
pub hostname: String,
|
|
/// The configuration file as read at startup (path and raw text), shown
|
|
/// on the status page. `None` when the server was configured in code.
|
|
pub config: Option<(String, String)>,
|
|
/// Whether large communities of our AS ask for things here
|
|
/// (`control_communities`), so that the status page can say what.
|
|
pub control_communities: bool,
|
|
}
|
|
|
|
impl RouterInfo {
|
|
pub fn new(router_id: Ipv4Addr, local_asn: u32, version: impl Into<String>) -> Self {
|
|
Self {
|
|
router_id,
|
|
local_asn,
|
|
version: version.into(),
|
|
started: Instant::now(),
|
|
started_at: SystemTime::now(),
|
|
hostname: hostname(),
|
|
config: None,
|
|
control_communities: false,
|
|
}
|
|
}
|
|
|
|
/// Records whether control communities are acted on.
|
|
pub fn with_control_communities(mut self, on: bool) -> Self {
|
|
self.control_communities = on;
|
|
self
|
|
}
|
|
|
|
/// Records the configuration file so the status page can show it.
|
|
pub fn with_config(mut self, path: impl Into<String>, text: impl Into<String>) -> Self {
|
|
self.config = Some((path.into(), text.into()));
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Best-effort host name without pulling in a dependency: ask the `hostname`
|
|
/// utility, which exists on every platform we run on.
|
|
fn hostname() -> String {
|
|
std::process::Command::new("hostname")
|
|
.output()
|
|
.ok()
|
|
.filter(|o| o.status.success())
|
|
.and_then(|o| String::from_utf8(o.stdout).ok())
|
|
.map(|s| s.trim().to_string())
|
|
.filter(|s| !s.is_empty())
|
|
.unwrap_or_else(|| "unknown host".into())
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct AppState {
|
|
pub info: Arc<RouterInfo>,
|
|
pub registry: SessionRegistry,
|
|
pub local_rib: LocalRib,
|
|
/// The routing table: routes learned from peers and the best of them.
|
|
pub rib: Arc<RibTable>,
|
|
/// What peers announced that was not taken.
|
|
pub refusals: RefusalLog,
|
|
/// The data for route origin validation, where that is configured.
|
|
pub rpki: Option<Rpki>,
|
|
/// What keeps the best routes in the kernel, where that is configured.
|
|
pub kernel: Option<Fib>,
|
|
/// Graceful restart: the routes kept of restarting peers.
|
|
pub restarts: Arc<Restarts>,
|
|
}
|
|
|
|
/// The status page, embedded so the binary is self-contained.
|
|
const STATUSZ_HTML: &str = include_str!("../static/statusz.html");
|
|
|
|
async fn statusz() -> Html<&'static str> {
|
|
Html(STATUSZ_HTML)
|
|
}
|
|
|
|
/// Builds the router. Exposed so tests can drive it without a socket.
|
|
pub fn router(state: AppState) -> Router {
|
|
Router::new()
|
|
.route("/", get(|| async { Redirect::temporary("/statusz") }))
|
|
.route("/statusz", get(statusz))
|
|
.route("/api/status", get(api::status))
|
|
.route("/api/config", get(api::config))
|
|
.route("/api/peers", get(api::peers))
|
|
.route("/api/routes/local", get(api::local_routes))
|
|
.route("/api/routes/received", get(api::received_routes))
|
|
.route("/api/routes/rejected", get(api::rejected_routes))
|
|
.route("/api/rpki", get(api::rpki))
|
|
.route("/api/kernel", get(api::kernel))
|
|
.route("/api/events", get(events::stream))
|
|
.route("/metricz", get(metrics::metricz))
|
|
.with_state(state)
|
|
}
|
|
|
|
/// Serves the API on `listener` until the task is dropped. The caller
|
|
/// binds it, and so finds out before anything else starts if it cannot.
|
|
pub async fn serve_on(state: AppState, listener: TcpListener) -> std::io::Result<()> {
|
|
info!(addr = %listener.local_addr()?, "HTTP API listening");
|
|
axum::serve(listener, router(state)).await?;
|
|
Ok(())
|
|
}
|