Refactored client binary.

This commit is contained in:
Rayhaan Jaufeerally
2024-07-07 22:34:00 +02:00
parent 75dbfc319a
commit 9be6b1d59d
34 changed files with 452 additions and 296 deletions

30
bin/Cargo.toml Normal file
View File

@ -0,0 +1,30 @@
[package]
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "bgpd_bin"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
bgp_server.workspace = true
clap.workspace = true
eyre.workspace = true
libc.workspace = true
log.workspace = true
route_client.workspace = true
serde_json.workspace = true
signal-hook = { version = "0.3.17", features = ["extended-siginfo"] }
signal-hook-tokio = "0.3.0"
tokio.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
[[bin]]
name = "bgp_server"
path = "src/bgp_server/main.rs"
[[bin]]
name = "client"
path = "src/client/main.rs"

View File

@ -0,0 +1,93 @@
// Copyright 2021 Rayhaan Jaufeerally.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use bgp_server::bgp_server::Server;
use bgp_server::config::ServerConfig;
use clap::{App, Arg};
use core::sync::atomic::AtomicBool;
use libc::SIGUSR1;
use signal_hook::consts::signal::*;
use signal_hook::consts::TERM_SIGNALS;
use signal_hook::flag;
use signal_hook::iterator::exfiltrator::WithOrigin;
use signal_hook::iterator::SignalsInfo;
use std::fs::File;
use std::io::BufReader;
use std::process::exit;
use std::sync::Arc;
use tracing::info;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let subscriber = tracing_subscriber::fmt().with_env_filter("bgpd=info");
match subscriber.try_init() {
Ok(()) => {}
Err(e) => {
eprintln!("Failed to initialize logger: {:?}", e);
exit(1);
}
}
let argv_matches = App::new("bgpd")
.author("Rayhaan Jaufeerally <rayhaan@rayhaan.ch>")
.version("0.1")
.about("net-control-plane BGP daemon")
.arg(Arg::with_name("config").takes_value(true))
.get_matches();
info!("Starting BGP Daemon!");
let config_file = File::open(argv_matches.value_of("config").unwrap_or("config.json")).unwrap();
let reader = BufReader::new(config_file);
let server_config: ServerConfig = serde_json::from_reader(reader).unwrap();
info!("Parsed server config");
let mut bgp_server = Server::new(server_config);
bgp_server.start(true).await.unwrap();
// The following signal handling code is from:
// https://docs.rs/signal-hook/0.3.10/signal_hook/
// Comments removed for brevity.
let term_now = Arc::new(AtomicBool::new(false));
for sig in TERM_SIGNALS {
flag::register_conditional_shutdown(*sig, 1, Arc::clone(&term_now))?;
flag::register(*sig, Arc::clone(&term_now))?;
}
let mut sigs = vec![SIGHUP, SIGUSR1];
sigs.extend(TERM_SIGNALS);
let mut signals = SignalsInfo::<WithOrigin>::new(&sigs)?;
for info in &mut signals {
match info.signal {
// TODO: Implement something on receiving SIGHUP / SIGUSR1.
SIGHUP => {
println!("Caught SIGHUP, not doing anything");
}
SIGUSR1 => {
println!("Caught SIGUSR1, not doing anything");
}
_term_sig => {
eprintln!("Shutting down app");
break;
}
}
}
bgp_server.shutdown().await;
Ok(())
}

77
bin/src/client/main.rs Normal file
View File

@ -0,0 +1,77 @@
use clap::Parser;
use eyre::Result;
use tracing::instrument::WithSubscriber;
use tracing::log::LevelFilter;
use tracing::{info, warn};
use tracing_subscriber::filter;
use tracing_subscriber::prelude::*;
use route_client::netlink::NetlinkConnector;
use route_client::{run_connector_v4, run_connector_v6};
#[derive(Parser)]
#[clap(
author = "Rayhaan Jaufeerally <rayhaan@rayhaan.ch>",
version = "0.1",
about = "Installs routes from a BGP speaker via streaming RPC to the forwarding plane"
)]
struct Cli {
#[clap(long = "route_server")]
route_server: String,
#[clap(long = "rt_table")]
rt_table: Option<u32>,
dry_run: bool,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Cli::parse();
tracing_subscriber::fmt().pretty().init();
info!("Starting route client");
let rt_table = match args.rt_table {
Some(table) => table,
None => 201,
};
let v4_joinhandle = {
let server_addr = args.route_server.clone();
tokio::task::spawn(async move {
run_connector_v4::<NetlinkConnector>(
server_addr.clone(),
rt_table,
args.dry_run,
NetlinkConnector::new(Some(rt_table)).await.unwrap(),
)
.await
.unwrap();
})
};
let v6_joinhandle = {
let server_addr = args.route_server.clone();
tokio::task::spawn(async move {
run_connector_v6::<NetlinkConnector>(
server_addr,
rt_table,
args.dry_run,
NetlinkConnector::new(Some(rt_table)).await.unwrap(),
)
.await
.unwrap();
})
};
tokio::select! {
_ = v4_joinhandle => {
warn!("Unexpected exit of IPv4 connector");
},
_ = v6_joinhandle => {
warn!("Unexpected exit of IPv6 connector");
}
}
Ok(())
}

View File

@ -0,0 +1,164 @@
// Copyright 2021 Rayhaan Jaufeerally.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use bgpd::bgp_packet::constants::AddressFamilyIdentifier;
use bgpd::bgp_packet::nlri::NLRI;
use bgpd::server::route_server::route_server::route_service_client::RouteServiceClient;
use bgpd::server::route_server::route_server::DumpPathsRequest;
use bgpd::server::route_server::route_server::PathSet;
use bgpd::server::route_server::route_server::StreamPathsRequest;
use clap::Parser;
use std::process::exit;
use std::time::Duration;
use tokio::task::JoinHandle;
use tonic::transport::Endpoint;
use tracing::{info, warn};
extern crate clap;
#[derive(clap::Parser)]
#[clap(
author = "Rayhaan Jaufeerally <rayhaan@rayhaan.ch>",
version = "0.1",
about = "A utility for dumping routes from the bgp_server."
)]
struct Cli {
server_address: String,
}
#[tokio::main]
async fn main() -> Result<(), String> {
let subscriber = tracing_subscriber::fmt();
match subscriber.try_init() {
Ok(()) => {}
Err(e) => {
eprintln!("Failed to initialize logger: {:?}", e);
exit(1);
}
}
let cli = Cli::parse();
info!("Starting client");
let grpc_endpoint = cli.server_address;
let endpoint = Endpoint::from_shared(grpc_endpoint)
.map_err(|e| e.to_string())?
.keep_alive_timeout(Duration::from_secs(10));
let mut client = RouteServiceClient::connect(endpoint)
.await
.map_err(|e| e.to_string())?;
info!("Connected");
// 1. First subscribe to the route feed and put these into an unbounded channel.
let (stream_tx, mut stream_rx) = tokio::sync::mpsc::unbounded_channel::<PathSet>();
let request = StreamPathsRequest {
address_family: 2_i32,
};
let mut client_copy = client.clone();
let _recv_handle: JoinHandle<Result<(), String>> = tokio::spawn(async move {
let mut rpc_stream = client_copy
.stream_paths(request)
.await
.map_err(|e| e.to_string())?
.into_inner();
while let Some(route) = rpc_stream.message().await.map_err(|e| e.to_string())? {
stream_tx.send(route).map_err(|e| e.to_string())?;
}
Err("Stream closed".to_string())
});
// 2. Dump the whole RIB
let dump_request = DumpPathsRequest {
address_family: 2_i32,
};
let dump_response = client.dump_paths(dump_request).await.unwrap().into_inner();
let dump_epoch = dump_response.epoch;
info!("Dump epoch was: {}", dump_epoch);
let overrun_slot: Option<PathSet>;
loop {
let item = stream_rx.recv().await;
match &item {
Some(pathset) => {
if pathset.epoch >= dump_epoch {
overrun_slot = Some(pathset.clone());
break;
} else {
info!("Skipping already-dumped epoch: {}", pathset.epoch);
}
}
None => {
return Err("Stream unexpectedly closed".to_owned());
}
}
}
// Replay all the pathsets from dump_response
for pathset in dump_response.path_sets {
info!("Got pathset: {:?}", pathset);
// Parse an NLRI from the pathset
if let Some(prefix) = &pathset.prefix {
let nlri = NLRI::from_bytes(
AddressFamilyIdentifier::Ipv6,
prefix.ip_prefix.clone(),
prefix.prefix_len as u8,
)
.unwrap();
info!("Parsed NLRI: {}", nlri.to_string());
}
}
// Replay the overrun slot
if let Some(pathset) = overrun_slot {
if let Some(prefix) = &pathset.prefix {
let nlri = NLRI::from_bytes(
AddressFamilyIdentifier::Ipv6,
prefix.ip_prefix.clone(),
prefix.prefix_len as u8,
)
.unwrap();
info!("Parsed NLRI: {}", nlri.to_string());
}
}
loop {
let item = stream_rx.recv().await;
match &item {
Some(pathset) => {
info!("Got pathset: {:?}", pathset);
// Parse an NLRI from the pathset
if let Some(prefix) = &pathset.prefix {
let nlri = NLRI::from_bytes(
AddressFamilyIdentifier::Ipv6,
prefix.ip_prefix.clone(),
prefix.prefix_len as u8,
)
.unwrap();
info!("Parsed NLRI: {}", nlri.to_string());
}
}
None => {
warn!("stream_rx closed");
break;
}
}
}
Err("Program exited unexpectedly.".to_owned())
}