Refactored client binary.
This commit is contained in:
30
bin/Cargo.toml
Normal file
30
bin/Cargo.toml
Normal 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"
|
||||
93
bin/src/bgp_server/main.rs
Normal file
93
bin/src/bgp_server/main.rs
Normal 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
77
bin/src/client/main.rs
Normal 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(())
|
||||
}
|
||||
164
bin/src/streamer_cli/main.rs
Normal file
164
bin/src/streamer_cli/main.rs
Normal 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())
|
||||
}
|
||||
Reference in New Issue
Block a user