Compare commits

...

32 Commits

Author SHA1 Message Date
1d3c6aa977 Start of major rewrite/cleanup.
Some checks are pending
Rust / build (push) Waiting to run
2025-04-19 18:19:37 +02:00
3b41e64650 Start cleanup
Some checks failed
Rust / build (push) Has been cancelled
2025-01-01 13:03:02 +01:00
b0f2995ed8 Cleanup clippy warnings.
Some checks failed
Rust / build (push) Has been cancelled
2024-12-08 22:09:00 +00:00
cc3842b384 Add skeleton of announce_to_peer function
Some checks failed
Rust / build (push) Has been cancelled
2024-10-07 10:47:24 +00:00
f55066c1fb Fix build: remove call to removed function 2024-10-07 10:27:56 +00:00
8c371648d0 Update result type in some files.
Some checks are pending
Rust / build (push) Waiting to run
2024-10-07 10:23:07 +00:00
173487b411 Implemented route manager redistributing routes to peer handlers (not yet announcing to peers)
Some checks failed
Rust / build (push) Has been cancelled
2024-08-18 18:42:50 +00:00
0e45b96440 Misc cleanups
Some checks are pending
Rust / build (push) Waiting to run
2024-08-18 13:59:28 +00:00
82d052ca33 Fix route_del bug: routes are added without protocol, so delete them without protocol too
Some checks are pending
Rust / build (push) Waiting to run
2024-08-18 10:50:27 +00:00
661946b2f8 Add utility binary for testing
Some checks are pending
Rust / build (push) Waiting to run
2024-08-18 10:42:42 +00:00
f937d2e526 Missed returning error on exit
Some checks are pending
Rust / build (push) Waiting to run
2024-08-18 10:16:51 +00:00
f6c6747345 Fix route delete to fail on error instead of just warn
Some checks are pending
Rust / build (push) Waiting to run
2024-08-18 10:10:10 +00:00
a47c1271aa Added additional debug prints
Some checks are pending
Rust / build (push) Waiting to run
2024-08-18 08:53:43 +00:00
9b44ab782d Add EnvLogger and start of trait fn for dumping routes
Some checks are pending
Rust / build (push) Waiting to run
2024-08-17 14:16:46 +00:00
4a7e346153 Change route client bails into warnings.
Some checks failed
Rust / build (push) Has been cancelled
2024-08-08 21:19:19 +00:00
9c9d6beede Fix bug where connection close aborts the peer handler if it's not in Established state.
Some checks are pending
Rust / build (push) Waiting to run
2024-08-08 21:09:14 +00:00
5130177bf4 Assorted cleanups.
Some checks are pending
Rust / build (push) Waiting to run
2024-08-08 20:46:41 +00:00
0cd3a120d0 Cleanups and filtering.
Some checks failed
Rust / build (push) Has been cancelled
2024-07-25 09:57:05 +00:00
8546c37dac Remove large community editing support via RPC for now.
Some checks are pending
Rust / build (push) Waiting to run
2024-07-24 18:59:11 +00:00
e03df6176b Implemented basic input filtering.
Some checks are pending
Rust / build (push) Waiting to run
2024-07-20 16:58:19 +00:00
993d18e873 Remove another SystemTime reference 2024-07-17 17:00:42 +00:00
77919edd15 Add cross connection test and cleanup rpc server a bit 2024-07-16 23:21:29 +00:00
7a99fda7a5 Switch to table_id function of netlink_packet_route 2024-07-16 19:03:02 +00:00
a7b17e99c0 Merge branch 'main' of https://github.com/net-control-plane/bgp 2024-07-16 18:59:57 +00:00
9dfa39b99d Clean up cancellation token 2024-07-16 18:58:27 +00:00
6380ce31b2 Update Cargo.toml 2024-07-16 20:57:20 +02:00
9be6b1d59d Refactored client binary. 2024-07-07 22:34:00 +02:00
75dbfc319a Upgrade crates and fix build 2024-03-04 22:37:08 +01:00
a38ff34634 Fix outdated deps and temporarily comment out route_server. 2023-05-29 16:43:22 +02:00
2101ee02e0 Create rust.yml 2023-02-23 01:10:29 +01:00
5818ec4956 Add capability from rfc8950.
Just the basic parser, still needs to be integrated into the rest of the
code for the OPEN message parsing, and then the rest of the MP
Reach/Unreach NLRI parsing needs to be added.
2023-02-23 01:07:46 +01:00
ff3211d1fc Re-import repository. 2023-01-27 01:10:15 +01:00
58 changed files with 11556 additions and 2 deletions

22
.github/workflows/rust.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Rust
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

12
.gitignore vendored
View File

@ -1 +1,11 @@
/target
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

View File

@ -27,3 +27,4 @@ serde_json = "1.0.140"
serde_repr = "0.1.20"
thiserror = "2.0.12"
tokio = { version = "1.44.2", features = ["full"] }
>>>>>>> ferrix/master

202
LICENSE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -9,3 +9,4 @@ The aim of this project is to provide a modern, high performance, secure and pro
The project is currently being rewritten from scratch to be more modular and maintainable, as well as to fix deeper issues with the previous implementation.
Warning: This is currently a work in progress and not ready for use.
>>>>>>> ferrix/master

1
bgpd_overview.drawio Normal file
View File

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2022-03-19T21:52:43.200Z" agent="5.0 (X11)" version="16.6.4" etag="HDYYftdqbxB7Xw-OJYvK" type="github"><diagram id="dM1C7iNFgguTdmpZPzfk">UzV2zq1wL0osyPDNT0nNUTV2VTV2LsrPL4GwciucU3NyVI0MMlNUjV1UjYwMgFjVyA2HrCFY1qAgsSg1rwSLBiADYTaQg2Y1AA==</diagram></mxfile>

40
bin/Cargo.toml Normal file
View File

@ -0,0 +1,40 @@
[package]
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "bgpd_bin"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
axum = "0.7.7"
bgp_packet.workspace = true
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"
[[bin]]
name = "util"
path = "src/util/main.rs"
[[bin]]
name = "api_server"
path = "src/api_server/main.rs"

124
bin/src/api_server/main.rs Normal file
View File

@ -0,0 +1,124 @@
use std::sync::Arc;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::routing::post;
use axum::Router;
use bgp_packet::nlri::NLRI;
use clap::{Parser, Subcommand};
use eyre::{bail, Result};
use route_client::connector::Connector;
use tokio::sync::Mutex;
use tracing::info;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
#[derive(Parser)]
#[clap(
author = "Rayhaan Jaufeerally <rayhaan@rayhaan.ch>",
version = "0.1",
about = "API Server"
)]
struct Cli {
#[clap(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// Run runs the API server connected to a given gRPC backend.
Run {
/// The route server gRPC backend.
backend: String,
},
}
struct AppState {
pub connector: Connector,
}
async fn handle_announce(
State(state): State<Arc<Mutex<AppState>>>,
Path((prefix, prefixlen)): Path<(String, u8)>,
) -> Result<(StatusCode, String), (StatusCode, String)> {
state
.lock()
.await
.connector
.send_announce(
"pr01_rue_rayhaan_net".to_owned(),
NLRI::try_from(format!("{}/{}", prefix, prefixlen).as_str()).map_err(|e| {
(
StatusCode::BAD_REQUEST,
format!("failed to parse NLRI: {}", e),
)
})?,
)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("failed to make RPC to backend: {}", e),
)
})?;
Ok((StatusCode::OK, "Success".to_owned()))
}
async fn handle_withdraw(
State(state): State<Arc<Mutex<AppState>>>,
Path((prefix, prefixlen)): Path<(String, u8)>,
) -> Result<(StatusCode, String), (StatusCode, String)> {
state
.lock()
.await
.connector
.send_withdraw(
"pr01_rue_rayhaan_net".to_owned(),
NLRI::try_from(format!("{}/{}", prefix, prefixlen).as_str()).map_err(|e| {
(
StatusCode::BAD_REQUEST,
format!("failed to parse NLRI: {}", e),
)
})?,
)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("failed to make RPC to backend: {}", e),
)
})?;
Ok((StatusCode::OK, "Success".to_owned()))
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Cli::parse();
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(EnvFilter::from_default_env())
.init();
info!("Starting API Server");
match args.command {
Some(Commands::Run { backend }) => {
let connector = Connector::new(backend).await?;
let app = Router::new()
.route("/announce/:prefix/:prefixlen", post(handle_announce))
.route("/withdraw/:prefix/:prefixlen", post(handle_withdraw))
.with_state(Arc::new(Mutex::new(AppState { connector })));
let listener = tokio::net::TcpListener::bind("localhost:8179")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
None => bail!("A subcommand must be specified."),
};
Ok(())
}

View File

@ -0,0 +1,92 @@
// 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 clap::Parser;
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 tracing::info;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use std::fs::File;
use std::io::BufReader;
use std::sync::Arc;
use bgp_server::bgp_server::Server;
use bgp_server::config::ServerConfig;
#[derive(Parser)]
#[command(author = "Rayhaan Jaufeerally <rayhaan@rayhaan.ch>", version = "0.1")]
struct Cli {
#[arg(short = 'c', long = "config")]
config_path: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(EnvFilter::from_default_env())
.init();
let args = Cli::parse();
info!("Starting BGP Daemon!");
let config_file = File::open(args.config_path).unwrap();
let reader = BufReader::new(config_file);
let server_config: ServerConfig = serde_json::from_reader(reader).unwrap();
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(())
}

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

@ -0,0 +1,101 @@
use bgp_packet::constants::AddressFamilyIdentifier;
use clap::{Parser, Subcommand};
use eyre::{bail, Result};
use route_client::southbound_interface::{DummyVerifier, SouthboundInterface};
use tracing::{info, warn};
use route_client::netlink::NetlinkConnector;
use route_client::{run_connector_v4, run_connector_v6};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
#[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 {
/// route_server is the gRPC endpoint to connect to for streaming routes from.
#[clap(long = "route_server")]
route_server: String,
#[clap(subcommand)]
command: Option<Commands>,
#[clap(long = "af")]
address_family: Vec<AddressFamilyIdentifier>,
}
#[derive(Subcommand)]
enum Commands {
/// InstallKernel installs the routes received into the kernel routing table.
InstallKernel {
#[arg(default_value_t = 201)]
rt_table: u32,
#[arg(default_value_t = false)]
dry_run: bool,
},
/// Verify performs consistency checks on the inbound stream of routes to ensure
/// that there are no spurious removals or duplicate entries.
Verify,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Cli::parse();
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(EnvFilter::from_default_env())
.init();
info!("Starting route client");
match args.command {
Some(Commands::InstallKernel { rt_table, dry_run }) => {
let southbound = NetlinkConnector::new(Some(rt_table)).await?;
run_connector::<NetlinkConnector>(args.route_server, dry_run, southbound).await
}
Some(Commands::Verify) => {
let southbound = DummyVerifier::default();
run_connector::<DummyVerifier>(args.route_server, false, southbound).await
}
None => bail!("A subcommand must be specified."),
};
Ok(())
}
async fn run_connector<S: SouthboundInterface + Clone + Send + Sync + 'static>(
server_addr: String,
dry_run: bool,
southbound: S,
) {
let v4_joinhandle = {
let server_addr = server_addr.clone();
let southbound = southbound.clone();
tokio::task::spawn(async move {
run_connector_v4::<S>(server_addr.clone(), dry_run, southbound)
.await
.unwrap();
})
};
let v6_joinhandle = {
let server_addr = server_addr.clone();
tokio::task::spawn(async move {
run_connector_v6::<S>(server_addr, dry_run, southbound)
.await
.unwrap();
})
};
tokio::select! {
_ = v4_joinhandle => {
warn!("Unexpected exit of IPv4 connector");
},
_ = v6_joinhandle => {
warn!("Unexpected exit of IPv6 connector");
}
}
}

107
bin/src/util/main.rs Normal file
View File

@ -0,0 +1,107 @@
use std::net::IpAddr;
use bgp_packet::constants::AddressFamilyIdentifier;
use bgp_packet::nlri::NLRI;
use clap::{Parser, Subcommand};
use eyre::{bail, Result};
use route_client::netlink::NetlinkConnector;
use route_client::southbound_interface::SouthboundInterface;
use tracing::info;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
#[derive(Parser)]
#[clap(
author = "Rayhaan Jaufeerally <rayhaan@rayhaan.ch>",
version = "0.1",
about = "Misc routing utilities"
)]
struct Cli {
#[clap(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// AddRoute installs the routes received into the kernel routing table.
AddRoute {
/// The destination IP prefix.
prefix: String,
/// The next hop for sending traffic to the prefix.
nexthop: String,
/// Table to add route into.
#[arg(default_value_t = 201)]
rt_table: u32,
},
/// DelRoute installs the routes received into the kernel routing table.
DelRoute {
/// The destination IP prefix.
prefix: String,
/// The next hop for sending traffic to the prefix.
nexthop: String,
/// Table to add route into.
#[arg(default_value_t = 201)]
rt_table: u32,
},
/// DumpRoutes prints all routes from the given routing table ID.
DumpRoutes {
/// Selects which table to print routes from.
#[arg(default_value_t = 201)]
rt_table: u32,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Cli::parse();
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(EnvFilter::from_default_env())
.init();
info!("Starting route client");
match args.command {
Some(Commands::AddRoute {
prefix,
nexthop,
rt_table,
}) => {
let mut handle = NetlinkConnector::new(Some(rt_table)).await?;
let prefix: NLRI = NLRI::try_from(prefix.as_str())?;
let nexthop: IpAddr = nexthop.parse()?;
handle.route_add(prefix.afi, prefix, nexthop).await?;
}
Some(Commands::DelRoute {
prefix,
nexthop,
rt_table,
}) => {
let mut handle = NetlinkConnector::new(Some(rt_table)).await?;
let prefix: NLRI = NLRI::try_from(prefix.as_str())?;
let nexthop: IpAddr = nexthop.parse()?;
handle.route_del(prefix, nexthop).await?;
}
Some(Commands::DumpRoutes { rt_table }) => {}
None => bail!("A subcommand must be specified."),
};
Ok(())
}
/// Implements the route dump CLI functionality.
async fn dump_routes(table: u16) -> Result<()> {
let connector = NetlinkConnector::new(Some(table)).await?;
let routes = connector.dump_routes(AddressFamilyIdentifier::Ipv6).await?;
for route in routes {
print_route_message(route);
}
}
fn print_route_message(msg: RouteMessage) {
// Parse the prefix out
}

1000
configs/bgplab.json Normal file

File diff suppressed because it is too large Load Diff

85
configs/pr01.home.json Normal file
View File

@ -0,0 +1,85 @@
{
"identifier": "193.36.105.1",
"asn": 210036,
"hold_time": 180,
"listen_addrs": [
"[::]:179"
],
"peers": [
{
"name": "iway_rs1_ipv6",
"ip": "2001:8e0:9ff:2000::1",
"asn": 8758,
"afi": 2,
"safi": 1,
"local_pref": 100,
"announcements": [
{
"prefix": "2a0d:d740:105::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
}
]
},
{
"name": "iway_rs2_ipv6",
"ip": "2001:8e0:ffff:3::42",
"asn": 8758,
"afi": 2,
"safi": 1,
"local_pref": 200,
"announcements": [
{
"prefix": "2a0d:d740::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
},
{
"prefix": "2a0d:d740:105::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
}
]
},
{
"name": "iway_rs3_ipv6",
"ip": "2001:8e0:ffff:3::72",
"asn": 8758,
"afi": 2,
"safi": 1,
"local_pref": 200,
"announcements": [
{
"prefix": "2a0d:d740::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
},
{
"prefix": "2a0d:d740:105::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
}
]
},
{
"name": "iway_rs1_ipv4",
"ip": "::ffff:83.150.40.2",
"asn": 8758,
"afi": 1,
"safi": 1,
"local_pref": 100,
"announcements": [
{
"prefix": "193.36.104.0/24",
"nexthop": "83.150.43.65",
"large_communities": ["210036:10:1"]
},
{
"prefix": "193.36.105.0/24",
"nexthop": "83.150.43.65",
"large_communities": ["210036:10:1"]
}
]
}
]
}

90
configs/pr01.man.json Normal file
View File

@ -0,0 +1,90 @@
{
"identifier": "193.36.105.1",
"asn": 210036,
"hold_time": 180,
"listen_addrs": [
"[::]:179"
],
"peers": [
{
"name": "iway_rs1_ipv6",
"ip": "2001:8e0:9ff:2000::1",
"asn": 8758,
"afi": 2,
"safi": 1,
"local_pref": 100,
"announcements": [
{
"prefix": "2a0d:d740::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
},
{
"prefix": "2a0d:d740:105::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
}
]
},
{
"name": "iway_rs2_ipv6",
"ip": "2001:8e0:ffff:3::42",
"asn": 8758,
"afi": 2,
"safi": 1,
"local_pref": 200,
"announcements": [
{
"prefix": "2a0d:d740::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
},
{
"prefix": "2a0d:d740:105::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
}
]
},
{
"name": "iway_rs3_ipv6",
"ip": "2001:8e0:ffff:3::72",
"asn": 8758,
"afi": 2,
"safi": 1,
"local_pref": 200,
"announcements": [
{
"prefix": "2a0d:d740::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
},
{
"prefix": "2a0d:d740:105::/48",
"nexthop": "2001:8e0:9ff:2000::5396:2b41",
"large_communities": ["210036:10:1"]
}
]
},
{
"name": "iway_rs1_ipv4",
"ip": "::ffff:83.150.40.2",
"asn": 8758,
"afi": 1,
"safi": 1,
"local_pref": 100,
"announcements": [
{
"prefix": "193.36.104.0/24",
"nexthop": "83.150.43.65",
"large_communities": ["210036:10:1"]
},
{
"prefix": "193.36.105.0/24",
"nexthop": "83.150.43.65",
"large_communities": ["210036:10:1"]
}
]
}
]
}

25
configs/test.json Normal file
View File

@ -0,0 +1,25 @@
{
"identifier": "193.36.105.196",
"asn": 210036,
"hold_time": 180,
"http_addr": "[::]:9179",
"listen_addrs": [
"[::]:1790"
],
"peers": [
{
"name": "sample_peer",
"ip": "2a0d:d740:105:0:af9d:7b05:a881:2e91",
"asn": 210036,
"afi": "Ipv4",
"safi": "Unicast",
"local_pref": 100,
"announcements": [
{
"prefix": "2001:db8::/32",
"nexthop": "2a0d:d740:105:0:af9d:7b05:a881:2e91"
}
]
}
]
}

View File

@ -0,0 +1,24 @@
[package]
name = "bgp_packet"
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[lints]
workspace = true
[dependencies]
byteorder = "1.4.3"
bytes.workspace = true
clap.workspace = true
eyre.workspace = true
netlink-packet-route.workspace = true
nom = "7.1"
rtnetlink.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio-util = { version = "0.7.10", features = ["codec"] }

View File

@ -0,0 +1,737 @@
// 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 crate::constants::AddressFamilyIdentifier;
use crate::constants::SubsequentAddressFamilyIdentifier;
use crate::traits::BGPParserError;
use crate::traits::ParserContext;
use crate::traits::ReadablePacket;
use crate::traits::WritablePacket;
use byteorder::{ByteOrder, NetworkEndian};
use nom::number::complete::{be_u16, be_u8};
use nom::Err::Failure;
use nom::IResult;
use std::fmt;
use std::fmt::Display;
/// BGPOpenOptionType represents the option types in the Open message.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)]
pub struct BGPOpenOptionType(pub u8);
impl BGPOpenOptionType {
pub fn new(val: u8) -> BGPOpenOptionType {
BGPOpenOptionType(val)
}
}
impl From<BGPOpenOptionType> for u8 {
fn from(val: BGPOpenOptionType) -> Self {
val.0
}
}
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
pub mod BGPOpenOptionTypeValues {
use super::BGPOpenOptionType;
pub const CAPABILITIES: BGPOpenOptionType = BGPOpenOptionType(2);
}
#[derive(Debug, PartialEq)]
pub struct OpenOption {
pub option_type: BGPOpenOptionType,
pub oval: OpenOptions,
}
impl ReadablePacket for OpenOption {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], OpenOption, BGPParserError<&'a [u8]>> {
let (buf, typ) = nom::combinator::complete(be_u8)(buf)?;
let (buf, val) = match BGPOpenOptionType(typ) {
BGPOpenOptionTypeValues::CAPABILITIES => {
let (b, cap) = OpenOptionCapabilities::from_wire(ctx, buf)?;
(b, OpenOptions::Capabilities(cap))
}
_ => {
// TODO: This should gracefully degrrrrade and not fail the parser.
return Err(Failure(BGPParserError::CustomText(
"Unknown BGP OPEN option".to_string(),
)));
}
};
IResult::Ok((
buf,
OpenOption {
option_type: BGPOpenOptionType(typ),
oval: val,
},
))
}
}
impl WritablePacket for OpenOption {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf = Vec::new();
match &self.oval {
OpenOptions::Capabilities(c) => {
buf.push(BGPOpenOptionTypeValues::CAPABILITIES.into());
buf.append(&mut c.to_wire(ctx)?);
}
}
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
match &self.oval {
OpenOptions::Capabilities(c) => Ok(2 + c.wire_len(ctx)?),
}
}
}
impl Display for OpenOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "OpenOption: {}", self.oval)
}
}
#[derive(Debug, PartialEq)]
pub enum OpenOptions {
Capabilities(OpenOptionCapabilities),
}
impl Display for OpenOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
OpenOptions::Capabilities(c) => write!(f, "Capabilities: {}", c),
}
}
}
/// CapabilityList represents a list of capabilities which can be present in an OpenOption.
#[derive(Debug, PartialEq)]
pub struct OpenOptionCapabilities {
pub caps: Vec<BGPCapability>,
}
impl ReadablePacket for OpenOptionCapabilities {
// from wire reads the length and value of the TLV.
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], OpenOptionCapabilities, BGPParserError<&'a [u8]>> {
let (buf, caps): (_, Vec<BGPCapability>) = nom::multi::length_value(
be_u8,
nom::multi::many0(|i| BGPCapability::from_wire(ctx, i)),
)(buf)?;
IResult::Ok((buf, OpenOptionCapabilities { caps }))
}
}
impl WritablePacket for OpenOptionCapabilities {
// to_wire writes the length and value of the TLV.
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf: Vec<u8> = Vec::new();
buf.push(self.wire_len(ctx).unwrap() as u8);
for cap in &self.caps {
let mut result: Vec<u8> = (*cap).to_wire(ctx)?;
buf.append(&mut result);
}
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
let mut ttl: u16 = 0;
for cap in &self.caps {
ttl += (*cap).wire_len(ctx)?;
}
Ok(ttl)
}
}
impl Display for OpenOptionCapabilities {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Capabilities: [")?;
for cap in &self.caps {
std::fmt::Display::fmt(cap, f)?;
}
write!(f, "]")
}
}
/// BGP Capabilities.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)]
pub struct BGPCapabilityType(pub u8);
impl BGPCapabilityType {
pub fn new(val: u8) -> BGPCapabilityType {
BGPCapabilityType(val)
}
}
impl From<BGPCapabilityType> for u8 {
fn from(val: BGPCapabilityType) -> Self {
val.0
}
}
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
pub mod BGPCapabilityTypeValues {
use super::BGPCapabilityType;
/// Multiprotocol Extensions for BGP-4 [RFC2858]
pub const MULTPROTOCOL_BGP4: BGPCapabilityType = BGPCapabilityType(1);
/// Route Refresh Capability for BGP-4 [RFC2918]
pub const ROUTE_REFRESH_BGP4: BGPCapabilityType = BGPCapabilityType(2);
/// Outbound Route Filtering Capability [RFC5291]
pub const OUTBOUND_ROUTE_FILTERING: BGPCapabilityType = BGPCapabilityType(3);
/// Extended Next Hop Encoding [RFC8950]
pub const EXTENDED_NEXT_HOP: BGPCapabilityType = BGPCapabilityType(5);
/// BGP Extended Message [RFC8654]
pub const EXTENDED_MESSAGE: BGPCapabilityType = BGPCapabilityType(6);
/// BGPsec Capability [RFC8205]
pub const BGPSEC: BGPCapabilityType = BGPCapabilityType(7);
/// Multiple Labels Capability [RFC8277]
pub const MULTILABEL_COMPAT: BGPCapabilityType = BGPCapabilityType(8);
/// Graceful Restart Capability [RFC4724]
pub const GRACEFUL_RESTART: BGPCapabilityType = BGPCapabilityType(64);
/// Support for 4-octet AS number capability [RFC6793]
pub const FOUR_BYTE_ASN: BGPCapabilityType = BGPCapabilityType(65);
/// ADD-PATH Capability [RFC7911]
pub const ADD_PATH: BGPCapabilityType = BGPCapabilityType(69);
/// Enhanced Route Refresh Capability [RFC7313]
pub const ENHANCED_ROUTE_REFRESH: BGPCapabilityType = BGPCapabilityType(70);
}
#[derive(Debug, PartialEq)]
pub struct BGPCapability {
pub cap_type: BGPCapabilityType,
pub val: BGPCapabilityValue,
}
impl ReadablePacket for BGPCapability {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], BGPCapability, BGPParserError<&'a [u8]>> {
let (buf, cap_type) = nom::combinator::peek(be_u8)(buf)?; // Peek the type, if we know it, consume.
let (buf, val): (_, BGPCapabilityValue) =
match BGPCapabilityType(cap_type) {
BGPCapabilityTypeValues::FOUR_BYTE_ASN => {
let (buf, _) = be_u8(buf)?; // Consume type
let (buf, cap) = nom::multi::length_value(be_u8, |i| {
FourByteASNCapability::from_wire(ctx, i)
})(buf)?;
(buf, BGPCapabilityValue::FourByteASN(cap))
}
BGPCapabilityTypeValues::MULTPROTOCOL_BGP4 => {
let (buf, _) = be_u8(buf)?;
let (buf, cap) = nom::multi::length_value(be_u8, |i| {
MultiprotocolCapability::from_wire(ctx, i)
})(buf)?;
(buf, BGPCapabilityValue::Multiprotocol(cap))
}
// TODO: Add extended next hop.
BGPCapabilityTypeValues::ROUTE_REFRESH_BGP4 => {
let (buf, _) = be_u8(buf)?;
let (buf, cap) = nom::multi::length_value(be_u8, |i| {
RouteRefreshCapability::from_wire(ctx, i)
})(buf)?;
(buf, BGPCapabilityValue::RouteRefresh(cap))
}
BGPCapabilityTypeValues::GRACEFUL_RESTART => {
let (buf, _) = be_u8(buf)?;
let (buf, cap) = nom::multi::length_value(be_u8, |i| {
GracefulRestartCapability::from_wire(ctx, i)
})(buf)?;
(buf, BGPCapabilityValue::GracefulRestart(cap))
}
_ => {
// If we do not know what this is, then put the bytes in an UnknownCapability.
let (buf, cap) = UnknownCapability::from_wire(ctx, buf)?;
(buf, BGPCapabilityValue::UnknownCapability(cap))
}
};
IResult::Ok((
buf,
BGPCapability {
cap_type: BGPCapabilityType(cap_type),
val,
},
))
}
}
impl WritablePacket for BGPCapability {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf: Vec<u8> = vec![];
buf.push(self.cap_type.into());
match &self.val {
BGPCapabilityValue::FourByteASN(v) => {
buf.push(v.wire_len(ctx)? as u8);
buf.extend_from_slice(&v.to_wire(ctx)?);
}
BGPCapabilityValue::Multiprotocol(v) => {
buf.push(v.wire_len(ctx)? as u8);
buf.extend_from_slice(&v.to_wire(ctx)?);
}
BGPCapabilityValue::RouteRefresh(v) => {
buf.push(v.wire_len(ctx)? as u8);
buf.extend_from_slice(&v.to_wire(ctx)?);
}
BGPCapabilityValue::GracefulRestart(v) => {
buf.push(v.wire_len(ctx)? as u8);
buf.extend_from_slice(&v.to_wire(ctx)?);
}
BGPCapabilityValue::UnknownCapability(v) => {
buf.push(v.wire_len(ctx)? as u8);
buf.extend_from_slice(&v.to_wire(ctx)?);
}
};
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
// BGPCapabilityType(u8) + cap_len(u8) + val
match &self.val {
BGPCapabilityValue::FourByteASN(v) => Ok(2 + v.wire_len(ctx)?),
BGPCapabilityValue::Multiprotocol(v) => Ok(2 + v.wire_len(ctx)?),
BGPCapabilityValue::RouteRefresh(v) => Ok(2 + v.wire_len(ctx)?),
BGPCapabilityValue::GracefulRestart(v) => Ok(2 + v.wire_len(ctx)?),
BGPCapabilityValue::UnknownCapability(v) => Ok(2 + v.wire_len(ctx)?),
}
}
}
impl Display for BGPCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::Display::fmt(&self.val, f)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum BGPCapabilityValue {
FourByteASN(FourByteASNCapability),
Multiprotocol(MultiprotocolCapability),
RouteRefresh(RouteRefreshCapability),
GracefulRestart(GracefulRestartCapability),
UnknownCapability(UnknownCapability),
}
impl Display for BGPCapabilityValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
BGPCapabilityValue::FourByteASN(v) => std::fmt::Display::fmt(v, f),
BGPCapabilityValue::Multiprotocol(v) => std::fmt::Display::fmt(v, f),
BGPCapabilityValue::RouteRefresh(v) => std::fmt::Display::fmt(v, f),
BGPCapabilityValue::GracefulRestart(v) => std::fmt::Display::fmt(v, f),
BGPCapabilityValue::UnknownCapability(v) => std::fmt::Display::fmt(v, f),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct UnknownCapability {
cap_code: u8,
payload: Vec<u8>,
}
impl ReadablePacket for UnknownCapability {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, typ) = be_u8(buf)?;
let (buf, len) = be_u8(buf)?;
let (buf, payload) = nom::bytes::complete::take(len)(buf)?;
Ok((
buf,
UnknownCapability {
cap_code: typ,
payload: payload.to_vec(),
},
))
}
}
impl WritablePacket for UnknownCapability {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf = vec![];
// No need to push the type or length on as that's done at a higher level.
buf.extend(self.payload.to_owned());
Ok(buf)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(self.payload.len() as u16)
}
}
impl Display for UnknownCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "UnknownCapability type: {}", self.cap_code)
}
}
/// FourByteASNCapability represents the four byte BGP Capability value.
#[derive(Clone, Debug, PartialEq)]
pub struct FourByteASNCapability {
pub asn: u32,
}
impl FourByteASNCapability {
fn new(asn: u32) -> FourByteASNCapability {
FourByteASNCapability { asn }
}
}
impl ReadablePacket for FourByteASNCapability {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, asn) = nom::combinator::complete(nom::number::complete::be_u32)(buf)?;
IResult::Ok((buf, FourByteASNCapability::new(asn)))
}
}
impl WritablePacket for FourByteASNCapability {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf: Vec<u8> = vec![0; 4];
byteorder::NetworkEndian::write_u32(&mut buf, self.asn);
Ok(buf)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(4)
}
}
impl Display for FourByteASNCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FourByteASN: asn: {}", self.asn)
}
}
/// MultiprotocolExtCapability represents support for RFC 4760.
#[derive(Clone, Debug, PartialEq)]
pub struct MultiprotocolCapability {
pub afi: AddressFamilyIdentifier,
pub safi: SubsequentAddressFamilyIdentifier,
}
impl MultiprotocolCapability {
fn new(
afi: AddressFamilyIdentifier,
safi: SubsequentAddressFamilyIdentifier,
) -> MultiprotocolCapability {
MultiprotocolCapability { afi, safi }
}
}
impl ReadablePacket for MultiprotocolCapability {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], MultiprotocolCapability, BGPParserError<&'a [u8]>> {
let (buf, (afi, _, safi)) = nom::combinator::complete(nom::sequence::tuple((
|i| AddressFamilyIdentifier::from_wire(ctx, i),
nom::bytes::complete::take(1u8),
|i| SubsequentAddressFamilyIdentifier::from_wire(ctx, i),
)))(buf)?;
IResult::Ok((buf, MultiprotocolCapability::new(afi, safi)))
}
}
impl WritablePacket for MultiprotocolCapability {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
// [ AFI: uint16, 0: uint8, SAFI: uint8 ]
let mut res = [0u8; 4];
byteorder::NetworkEndian::write_u16(&mut res[..2], self.afi.into());
res[3] = self.safi.into();
Ok(res.to_vec())
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(4)
}
}
impl Display for MultiprotocolCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MultiprotocolCapbility: [ {} {} ]", self.afi, self.safi,)
}
}
// Route refresh capability
#[derive(Clone, Debug, PartialEq)]
pub struct RouteRefreshCapability {}
impl WritablePacket for RouteRefreshCapability {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
Ok(vec![])
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(0)
}
}
impl ReadablePacket for RouteRefreshCapability {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], RouteRefreshCapability, BGPParserError<&'a [u8]>> {
IResult::Ok((buf, RouteRefreshCapability {}))
}
}
impl Display for RouteRefreshCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RouteRefreshCapability")
}
}
// Graceful restart capability
#[derive(Clone, Debug, PartialEq)]
pub struct GracefulRestartCapability {
pub restart_state: bool, // 4 bits total, most sig bit here, rest reserved.
pub restart_time_sec: u16, // 12 bits.
pub payloads: Vec<GracefulRestartPayload>,
}
// GracefulRestartPayload represents the contents of the graceful restart cap.
#[derive(Clone, Debug, PartialEq)]
pub struct GracefulRestartPayload {
pub afi: AddressFamilyIdentifier,
pub safi: SubsequentAddressFamilyIdentifier,
pub af_flags: bool, // 8 bits total, most significant bit used here.
}
impl ReadablePacket for GracefulRestartPayload {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], GracefulRestartPayload, BGPParserError<&'a [u8]>> {
let (buf, (afi, safi, flags)) = nom::combinator::complete(nom::sequence::tuple((
|i| AddressFamilyIdentifier::from_wire(ctx, i),
|i| SubsequentAddressFamilyIdentifier::from_wire(ctx, i),
be_u8,
)))(buf)?;
IResult::Ok((
buf,
GracefulRestartPayload {
afi,
safi,
af_flags: (0x80 & flags) != 0,
},
))
}
}
impl WritablePacket for GracefulRestartPayload {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
let afi: u16 = self.afi.into();
let mut res = vec![0u8; 2];
byteorder::NetworkEndian::write_u16(res.as_mut(), afi);
res.push(self.safi.into());
res.push(if self.af_flags { 0x80 } else { 0 });
Ok(res)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(4)
}
}
impl Display for GracefulRestartPayload {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"GracefulRestartPayload: [afi:{} safi:{} af_flags:{}]",
self.afi, self.safi, self.af_flags
)
}
}
impl ReadablePacket for GracefulRestartCapability {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, state_rt) = nom::combinator::complete(be_u16)(buf)?;
let (buf, payloads): (_, Vec<GracefulRestartPayload>) =
nom::multi::many0(|i| GracefulRestartPayload::from_wire(ctx, i))(buf)?;
let restart_time_sec: u16 = 0x0fff & state_rt; // Lower 14 bits.
let restart_state: bool = (0x8000 & state_rt) != 0; // highest bit
IResult::Ok((
buf,
GracefulRestartCapability {
restart_state,
restart_time_sec,
payloads,
},
))
}
}
impl WritablePacket for GracefulRestartCapability {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf: Vec<u8> = vec![0u8; 2];
let state_rt: u16 = ((self.restart_state as u16) << 15) | (0xfff & self.restart_time_sec);
NetworkEndian::write_u16(&mut buf, state_rt);
for item in &self.payloads {
buf.append(&mut item.to_wire(ctx)?);
}
Ok(buf)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok((2 + self.payloads.len() * 4) as u16)
}
}
impl Display for GracefulRestartCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GracefulRestartCapability: [")?;
for value in &self.payloads {
fmt::Display::fmt(value, f)?;
}
write!(f, " ]")
}
}
// RFC8950 - Advertising IPv4 NLRI with IPv6 next hop.
// GracefulRestartPayload represents the contents of the graceful restart cap.
#[derive(Clone, Debug, PartialEq)]
pub struct ExtendedNextHopEncodingCapability {
pub afi_safi_nhafi: Vec<(
AddressFamilyIdentifier,
SubsequentAddressFamilyIdentifier,
AddressFamilyIdentifier,
)>,
}
impl WritablePacket for ExtendedNextHopEncodingCapability {
fn to_wire(&self, _ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
Ok(self
.afi_safi_nhafi
.iter()
.flat_map(|e| {
Into::<Vec<u8>>::into(e.0)
.into_iter()
.chain(vec![0x00, Into::<u8>::into(e.1)])
.chain(Into::<Vec<u8>>::into(e.2))
.collect::<Vec<u8>>()
})
.collect::<Vec<u8>>())
}
fn wire_len(&self, _ctx: &ParserContext) -> Result<u16, &'static str> {
Ok((self.afi_safi_nhafi.len() * 6) as u16)
}
}
impl ReadablePacket for ExtendedNextHopEncodingCapability {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>>
where
Self: Sized,
{
let (buf, tuples) = nom::combinator::complete(nom::multi::many0(nom::sequence::tuple((
|i| AddressFamilyIdentifier::from_wire(ctx, i),
|i| {
let (buf, _) = be_u8(i)?; // Eat the 0 byte.
SubsequentAddressFamilyIdentifier::from_wire(ctx, buf)
},
|i| AddressFamilyIdentifier::from_wire(ctx, i),
))))(buf)?;
IResult::Ok((
buf,
Self {
afi_safi_nhafi: tuples,
},
))
}
}
impl Display for ExtendedNextHopEncodingCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ExtendednextHopEncodingCapability [")?;
for entry in &self.afi_safi_nhafi {
write!(f, "afi: {}, safi: {}, nhafi: {}", entry.0, entry.1, entry.2)?;
}
write!(f, "]")
}
}
#[cfg(test)]
mod tests {
use super::BGPCapability;
use super::BGPCapabilityTypeValues;
use super::BGPCapabilityValue;
use super::ExtendedNextHopEncodingCapability;
use super::FourByteASNCapability;
use super::OpenOption;
use crate::constants::AddressFamilyIdentifier::Ipv6;
use crate::traits::ParserContext;
use crate::traits::ReadablePacket;
#[test]
fn test_four_byte_asn_capability() {
let bytes: &[u8] = &[0x41, 0x04, 0x00, 0x00, 0x00, 0x2a];
let ctx = &ParserContext::default()
.four_octet_asn(true)
.nlri_mode(Ipv6);
let (buf, result) = BGPCapability::from_wire(ctx, bytes).unwrap();
assert_eq!(
result,
BGPCapability {
cap_type: BGPCapabilityTypeValues::FOUR_BYTE_ASN,
val: BGPCapabilityValue::FourByteASN(FourByteASNCapability { asn: 42 })
}
);
assert_eq!(buf.len(), 0);
}
#[test]
fn test_open_options<'a>() {
let option_bytes: &[u8] = &[
0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, 0x02, 0x02, 0x80, 0x00, 0x02, 0x02,
0x02, 0x00, 0x02, 0x02, 0x46, 0x00, 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x00, 0x2a,
];
let ctx = &ParserContext::default()
.four_octet_asn(true)
.nlri_mode(Ipv6);
let (_buf, result) =
nom::multi::many0(|buf: &'a [u8]| OpenOption::from_wire(ctx, buf))(option_bytes)
.unwrap();
let expected_str = "[OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(1), val: Multiprotocol(MultiprotocolCapability { afi: Ipv4, safi: Unicast }) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(128), val: UnknownCapability(UnknownCapability { cap_code: 128, payload: [] }) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(2), val: RouteRefresh(RouteRefreshCapability) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(70), val: UnknownCapability(UnknownCapability { cap_code: 70, payload: [] }) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(65), val: FourByteASN(FourByteASNCapability { asn: 42 }) }] }) }]";
assert_eq!(format!("{:?}", result), expected_str);
}
#[test]
fn test_extended_next_hop_encoding_capability() {
let bytes: Vec<u8> = vec![0x00, 0x01, 0x00, 0x01, 0x00, 0x02];
let ctx = &ParserContext::default()
.four_octet_asn(true)
.nlri_mode(Ipv6);
let (_, cap) = ExtendedNextHopEncodingCapability::from_wire(ctx, &bytes).unwrap();
let expected_str =
"ExtendednextHopEncodingCapability [afi: Ipv4, safi: Unicast, nhafi: Ipv6]";
assert_eq!(expected_str, cap.to_string());
}
}

View File

@ -0,0 +1,172 @@
// 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 nom::IResult;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::io::ErrorKind;
use super::traits::{BGPParserError, ParserContext, ReadablePacket};
// Address Family Identifiers as per
// https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml
#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize, Hash, clap::ValueEnum)]
pub enum AddressFamilyIdentifier {
Ipv4,
Ipv6,
}
impl From<AddressFamilyIdentifier> for u16 {
fn from(val: AddressFamilyIdentifier) -> Self {
match val {
AddressFamilyIdentifier::Ipv4 => 1,
AddressFamilyIdentifier::Ipv6 => 2,
}
}
}
impl TryFrom<u16> for AddressFamilyIdentifier {
type Error = std::io::Error;
fn try_from(i: u16) -> Result<Self, Self::Error> {
match i {
1 => Ok(Self::Ipv4),
2 => Ok(Self::Ipv6),
_ => Err(std::io::Error::new(
ErrorKind::InvalidInput,
format!("Unknown AFI: {}", i),
)),
}
}
}
impl From<AddressFamilyIdentifier> for Vec<u8> {
fn from(val: AddressFamilyIdentifier) -> Self {
match val {
AddressFamilyIdentifier::Ipv4 => 1_u16.to_be_bytes().to_vec(),
AddressFamilyIdentifier::Ipv6 => 2_u16.to_be_bytes().to_vec(),
}
}
}
/// Convenience functions to convert AddressFamilyIdentifier into those used by netlink.
impl From<AddressFamilyIdentifier> for netlink_packet_route::AddressFamily {
fn from(val: AddressFamilyIdentifier) -> Self {
match val {
AddressFamilyIdentifier::Ipv4 => netlink_packet_route::AddressFamily::Inet,
AddressFamilyIdentifier::Ipv6 => netlink_packet_route::AddressFamily::Inet6,
}
}
}
impl From<AddressFamilyIdentifier> for rtnetlink::IpVersion {
fn from(val: AddressFamilyIdentifier) -> Self {
match val {
AddressFamilyIdentifier::Ipv4 => rtnetlink::IpVersion::V4,
AddressFamilyIdentifier::Ipv6 => rtnetlink::IpVersion::V6,
}
}
}
/// This parser for AFI makes it easier to write the other message parsers.
impl ReadablePacket for AddressFamilyIdentifier {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], AddressFamilyIdentifier, BGPParserError<&'a [u8]>> {
let (buf, afi_raw) = nom::number::complete::be_u16(buf)?;
let afi = AddressFamilyIdentifier::try_from(afi_raw)
.map_err(|e| nom::Err::Error(BGPParserError::CustomText(e.to_string())))?;
IResult::Ok((buf, afi))
}
}
impl fmt::Display for AddressFamilyIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ipv4 => write!(f, "Ipv4"),
Self::Ipv6 => write!(f, "Ipv6"),
}
}
}
// Subsequent Address Family Identifiers as per
// https://www.iana.org/assignments/safi-namespace/safi-namespace.xhtml
#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize)]
pub enum SubsequentAddressFamilyIdentifier {
Unicast,
Multicast,
NlriWithMpls,
MplsLabeledVPN,
MulticastMplsVpn,
}
impl From<SubsequentAddressFamilyIdentifier> for u8 {
fn from(val: SubsequentAddressFamilyIdentifier) -> Self {
match val {
SubsequentAddressFamilyIdentifier::Unicast => 1,
SubsequentAddressFamilyIdentifier::Multicast => 2,
SubsequentAddressFamilyIdentifier::NlriWithMpls => 4,
SubsequentAddressFamilyIdentifier::MplsLabeledVPN => 128,
SubsequentAddressFamilyIdentifier::MulticastMplsVpn => 129,
}
}
}
impl TryFrom<u8> for SubsequentAddressFamilyIdentifier {
type Error = std::io::Error;
fn try_from(i: u8) -> Result<Self, Self::Error> {
match i {
1 => Ok(Self::Unicast),
2 => Ok(Self::Multicast),
4 => Ok(Self::NlriWithMpls),
128 => Ok(Self::MplsLabeledVPN),
129 => Ok(Self::MulticastMplsVpn),
_ => Err(std::io::Error::new(
ErrorKind::InvalidInput,
format!("Unknown SAFI value: {} ", i),
)),
}
}
}
/// This parser for SAFI makes it easier to write the other message parsers.
impl ReadablePacket for SubsequentAddressFamilyIdentifier {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], SubsequentAddressFamilyIdentifier, BGPParserError<&'a [u8]>> {
let (buf, safi_raw) = nom::number::complete::be_u8(buf)?;
let safi = SubsequentAddressFamilyIdentifier::try_from(safi_raw)
.map_err(|e| nom::Err::Error(BGPParserError::CustomText(e.to_string())))?;
IResult::Ok((buf, safi))
}
}
impl fmt::Display for SubsequentAddressFamilyIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unicast => write!(f, "Unicast"),
Self::Multicast => write!(f, "Multicast"),
Self::NlriWithMpls => write!(f, "NlriWithMpls"),
Self::MulticastMplsVpn => write!(f, "MulticastMplsVpn"),
Self::MplsLabeledVPN => write!(f, "MplsLabeledVpn"),
}
}
}
pub const AS_TRANS: u16 = 23456;

View File

@ -0,0 +1,26 @@
// 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.
//! Implements parsers / serializers for the Border Gateway Protocol wire format.
//! RFC4271
// Meta
pub mod constants;
pub mod traits;
// Parsers
pub mod capabilities;
pub mod messages;
pub mod nlri;
pub mod path_attributes;

View File

@ -0,0 +1,719 @@
// 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 crate::capabilities::OpenOption;
use crate::constants::AddressFamilyIdentifier;
use crate::constants::SubsequentAddressFamilyIdentifier;
use crate::nlri::NLRI;
use crate::path_attributes::PathAttribute;
use crate::traits::BGPParserError;
use crate::traits::ParserContext;
use crate::traits::ReadablePacket;
use crate::traits::WritablePacket;
use byteorder::{ByteOrder, NetworkEndian};
use bytes::Buf;
use bytes::BufMut;
use bytes::BytesMut;
use nom::number::complete::{be_u16, be_u32, be_u8};
use nom::Err::Failure;
use nom::IResult;
use std::convert::TryInto;
use std::fmt;
use std::fmt::Display;
use std::net::Ipv4Addr;
use tokio_util::codec::{Decoder, Encoder};
/// BGPMessageType represents the type of the top level BGP message.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)]
pub struct BGPMessageType(pub u8);
impl BGPMessageType {
pub fn new(val: u8) -> BGPMessageType {
BGPMessageType(val)
}
}
impl From<BGPMessageType> for u8 {
fn from(val: BGPMessageType) -> Self {
val.0
}
}
impl From<u8> for BGPMessageType {
fn from(i: u8) -> BGPMessageType {
BGPMessageType(i)
}
}
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
pub mod BGPMessageTypeValues {
use super::BGPMessageType;
pub const OPEN_MESSAGE: BGPMessageType = BGPMessageType(1);
pub const UPDATE_MESSAGE: BGPMessageType = BGPMessageType(2);
pub const NOTIFICATION_MESSAGE: BGPMessageType = BGPMessageType(3);
pub const KEEPALIVE_MESSAGE: BGPMessageType = BGPMessageType(4);
pub const REFRESH_MESSAGE: BGPMessageType = BGPMessageType(5);
}
#[derive(Debug, PartialEq)]
pub enum BGPSubmessage {
OpenMessage(OpenMessage),
UpdateMessage(UpdateMessage),
NotificationMessage(NotificationMessage),
KeepaliveMessage(KeepaliveMessage),
}
impl WritablePacket for BGPSubmessage {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
match &self {
BGPSubmessage::OpenMessage(m) => m.to_wire(ctx),
BGPSubmessage::UpdateMessage(m) => m.to_wire(ctx),
BGPSubmessage::NotificationMessage(m) => m.to_wire(ctx),
BGPSubmessage::KeepaliveMessage(m) => m.to_wire(ctx),
}
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
match &self {
BGPSubmessage::OpenMessage(m) => m.wire_len(ctx),
BGPSubmessage::UpdateMessage(m) => m.wire_len(ctx),
BGPSubmessage::NotificationMessage(m) => m.wire_len(ctx),
BGPSubmessage::KeepaliveMessage(m) => m.wire_len(ctx),
}
}
}
/// KeepaliveMessage implements the KEEPALIVE message as defined in RFC4271.
#[derive(Debug, PartialEq)]
pub struct KeepaliveMessage {}
impl ReadablePacket for KeepaliveMessage {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
Ok((buf, KeepaliveMessage {}))
}
}
impl WritablePacket for KeepaliveMessage {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
Ok(vec![])
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(0)
}
}
impl Display for KeepaliveMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "KeepaliveMessage")
}
}
/// NotificationMessage implements the NOTIFICATION message type as defined in RFC4271.
#[derive(Debug, PartialEq)]
pub struct NotificationMessage {
pub error_code: u8,
pub error_subcode: u8,
pub data: Vec<u8>,
}
impl ReadablePacket for NotificationMessage {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, ec) = be_u8(buf)?;
let (buf, esc) = be_u8(buf)?;
let data = &buf;
Ok((
&[0u8; 0],
NotificationMessage {
error_code: ec,
error_subcode: esc,
data: data.to_vec(),
},
))
}
}
impl WritablePacket for NotificationMessage {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf = vec![];
buf.push(self.error_code);
buf.push(self.error_subcode);
buf.extend(self.data.to_owned());
Ok(buf)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(2 + self.data.len() as u16)
}
}
impl Display for NotificationMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"NotificationMessage error_code: {}, error_subcode: {}",
self.error_code, self.error_subcode
)
}
}
#[derive(Debug, PartialEq)]
pub struct RouteRefreshMessage {
pub afi: AddressFamilyIdentifier,
pub safi: SubsequentAddressFamilyIdentifier,
}
impl WritablePacket for RouteRefreshMessage {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut res = [0u8; 4];
byteorder::NetworkEndian::write_u16(&mut res[..2], self.afi.into());
res[3] = self.safi.into();
Ok(res.to_vec())
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(4)
}
}
impl ReadablePacket for RouteRefreshMessage {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, (afi, _, safi)) = nom::combinator::complete(nom::sequence::tuple((
|i| AddressFamilyIdentifier::from_wire(ctx, i),
nom::bytes::complete::take(1u8),
|i| SubsequentAddressFamilyIdentifier::from_wire(ctx, i),
)))(buf)?;
IResult::Ok((buf, RouteRefreshMessage { afi, safi }))
}
}
impl Display for RouteRefreshMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RouteRefresh [afi: {}, safi: {}]", self.afi, self.safi)
}
}
/// BGPMessage is the top level message which is transmitted over the wire.
#[derive(Debug, PartialEq)]
pub struct BGPMessage {
pub msg_type: BGPMessageType,
pub payload: BGPSubmessage,
}
/// Codec is a helper for serializing and deserializing BGP messages.
pub struct Codec {
pub ctx: ParserContext,
}
impl Encoder<BGPMessage> for Codec {
type Error = std::io::Error;
fn encode(
&mut self,
msg: BGPMessage,
buf: &mut BytesMut,
) -> Result<(), <Self as Encoder<BGPMessage>>::Error> {
let result = msg.to_wire(&self.ctx);
match result {
Ok(bytes) => {
// XXX: Copying here because the whole write path needs to be updated
// to take a refrence to BytesMut and write to that directly.
let tmp: BytesMut = bytes.as_slice().into();
buf.put(tmp);
Ok(())
}
Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
}
}
}
impl Decoder for Codec {
type Item = BGPMessage;
type Error = std::io::Error;
fn decode(
&mut self,
buf: &mut BytesMut,
) -> Result<std::option::Option<<Self as Decoder>::Item>, <Self as Decoder>::Error> {
// We first check to see if the frame contains the full BGP message before invoking
// the parser on it.
// Expected contents: 16x 0xff, u16 of length.
// The length contains the header length, so we just check that the buf len matches.
if buf.len() < 19 {
// Minimum size is 19 for header + length + type.
return Ok(None);
}
// Read the length
let len: u16 = byteorder::BigEndian::read_u16(&buf[16..18]);
if buf.len() < len.into() {
// Not enough data to read this frame.
Ok(None)
} else if buf.len() == len as usize {
// Exactly one message here, parse and clear buf.
let parse_result = BGPMessage::from_wire(&self.ctx, buf.as_ref());
match parse_result {
Ok(msg) => {
let result = msg.1;
buf.clear();
Ok(Some(result))
}
Err(e) => Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to parse message: {:?}", e),
)),
}
} else {
// More than one message here, parse and advance buf.
let parse_result = BGPMessage::from_wire(&self.ctx, buf.as_ref());
match parse_result {
Ok(msg) => {
let result = msg.1;
buf.advance(len as usize);
Ok(Some(result))
}
Err(e) => Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to parse message: {:?}", e),
)),
}
}
}
}
impl WritablePacket for BGPMessage {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf: Vec<u8> = Vec::new();
// 16 bytes of 0xff according to Section 4.1 of RFC4271.
buf.append(&mut vec![0xff; 16]);
// Length.
{
let mut tmp: [u8; 2] = [0u8; 2];
NetworkEndian::write_u16(&mut tmp, self.wire_len(ctx)?);
buf.extend_from_slice(&tmp);
}
// Type
buf.push(self.msg_type.into());
let mut result: Vec<u8> = self.payload.to_wire(ctx)?;
buf.append(&mut result);
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
Ok(16 + 2 + 1 + self.payload.wire_len(ctx)?)
}
}
impl ReadablePacket for BGPMessage {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, _) = nom::combinator::complete(nom::bytes::complete::tag(&[0xff; 16]))(buf)?;
let (buf, len) = nom::combinator::complete(be_u16)(buf)?;
let (buf, typ) = nom::combinator::complete(be_u8)(buf)?;
let payload_len = len - 19;
let (buf, payload_bytes) = nom::bytes::complete::take(payload_len)(buf)?;
let (_, payload) = match typ.into() {
BGPMessageTypeValues::OPEN_MESSAGE => {
let (b, omsg) = OpenMessage::from_wire(ctx, payload_bytes)?;
(b, BGPSubmessage::OpenMessage(omsg))
}
BGPMessageTypeValues::UPDATE_MESSAGE => {
let (b, umsg) = UpdateMessage::from_wire(ctx, payload_bytes)?;
(b, BGPSubmessage::UpdateMessage(umsg))
}
BGPMessageTypeValues::NOTIFICATION_MESSAGE => {
let (b, nmsg) = NotificationMessage::from_wire(ctx, payload_bytes)?;
(b, BGPSubmessage::NotificationMessage(nmsg))
}
BGPMessageTypeValues::KEEPALIVE_MESSAGE => {
let (b, kmsg) = KeepaliveMessage::from_wire(ctx, payload_bytes)?;
(b, BGPSubmessage::KeepaliveMessage(kmsg))
}
_ => {
return Err(Failure(BGPParserError::CustomText(
"Unknown BGP message type".to_string(),
)));
}
};
Ok((
buf,
BGPMessage {
msg_type: BGPMessageType(typ),
payload,
},
))
}
}
impl Display for BGPMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.payload {
BGPSubmessage::OpenMessage(m) => fmt::Display::fmt(&m, f),
BGPSubmessage::UpdateMessage(m) => fmt::Display::fmt(&m, f),
BGPSubmessage::KeepaliveMessage(m) => fmt::Display::fmt(&m, f),
BGPSubmessage::NotificationMessage(m) => fmt::Display::fmt(&m, f),
}
}
}
#[derive(Debug, PartialEq)]
pub struct OpenMessage {
pub version: u8,
pub asn: u16,
pub hold_time: u16,
pub identifier: Ipv4Addr,
pub options: Vec<OpenOption>,
}
impl ReadablePacket for OpenMessage {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], OpenMessage, BGPParserError<&'a [u8]>> {
let (buf, (version, asn, hold_time, identifier)) =
nom::combinator::complete(nom::sequence::tuple((be_u8, be_u16, be_u16, be_u32)))(buf)?;
// oplen, [ [OpenOption] ... ]
// OpenOption = [T, L, V]
let (buf, opts): (_, Vec<OpenOption>) = nom::multi::length_value(
be_u8,
nom::multi::many0(|b| OpenOption::from_wire(ctx, b)),
)(buf)?;
Ok((
buf,
OpenMessage {
version,
asn,
hold_time,
identifier: Ipv4Addr::from(identifier),
options: opts,
},
))
}
}
impl WritablePacket for OpenMessage {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf: Vec<u8> = vec![0; 10];
buf[0] = self.version;
NetworkEndian::write_u16(&mut buf.as_mut_slice()[1..3], self.asn);
NetworkEndian::write_u16(&mut buf.as_mut_slice()[3..5], self.hold_time);
buf[5..9].clone_from_slice(&self.identifier.octets());
let mut oplen: u8 = 0;
for opt in &self.options {
buf.append(&mut (*opt).to_wire(ctx)?);
oplen += ((*opt).wire_len(ctx)?) as u8;
}
buf[9] = oplen;
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
let mut count: usize = 10;
for opt in &self.options {
count += (*opt).to_wire(ctx)?.len();
}
count
.try_into()
.map_err(|_| "overflow in wire_len in OpenMessage")
}
}
impl Display for OpenMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"OpenMessage: [version: {}, asn: {}, hold_time: {}, identifier: {}, options: [",
self.version, self.asn, self.hold_time, self.identifier
)?;
for option in &self.options {
fmt::Display::fmt(option, f)?;
}
write!(f, "]]")
}
}
/// UPDATE message and subtypes.
#[derive(Debug, PartialEq, Default)]
pub struct UpdateMessage {
pub withdrawn_nlri: Vec<NLRI>,
pub path_attributes: Vec<PathAttribute>,
pub announced_nlri: Vec<NLRI>,
}
impl ReadablePacket for UpdateMessage {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, wd_nlris): (_, Vec<NLRI>) = nom::multi::length_value(
be_u16,
nom::multi::many0(|i| NLRI::from_wire(&ctx.clone(), i)),
)(buf)?;
let (buf, pattrs): (_, Vec<PathAttribute>) = nom::multi::length_value(
be_u16,
nom::multi::many0(|i| PathAttribute::from_wire(ctx, i)),
)(buf)?;
let (buf, ann_nlri): (_, Vec<NLRI>) =
nom::multi::many0(|i| NLRI::from_wire(&ctx.clone(), i))(buf)?;
Ok((
buf,
UpdateMessage {
withdrawn_nlri: wd_nlris,
path_attributes: pattrs,
announced_nlri: ann_nlri,
},
))
}
}
impl WritablePacket for UpdateMessage {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf: Vec<u8> = Vec::new();
let tmp: &mut [u8] = &mut [0u8; 2];
let mut wd_len: u16 = 0;
for wd in &self.withdrawn_nlri {
wd_len += wd.wire_len(ctx)?;
}
NetworkEndian::write_u16(tmp, wd_len);
buf.append(&mut tmp.to_vec());
for wd in &self.withdrawn_nlri {
buf.extend(wd.to_wire(ctx)?);
}
let mut pattr_len: u16 = 0;
for pattr in &self.path_attributes {
pattr_len += pattr.wire_len(ctx)?;
}
NetworkEndian::write_u16(tmp, pattr_len);
buf.extend(tmp.to_vec());
for pattr in &self.path_attributes {
buf.extend(pattr.to_wire(ctx)?);
}
for ann in &self.announced_nlri {
buf.extend(ann.to_wire(ctx)?);
}
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
let mut ctr: u16 = 0;
ctr += 2;
for wd in &self.withdrawn_nlri {
ctr += wd.wire_len(ctx)?;
}
ctr += 2;
for pa in &self.path_attributes {
ctr += pa.wire_len(ctx)?;
}
for ann in &self.announced_nlri {
ctr += ann.wire_len(ctx)?;
}
Ok(ctr)
}
}
impl Display for UpdateMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "UpdateMessage [ withdrawn: ")?;
for withdrawn_nlri in &self.withdrawn_nlri {
fmt::Display::fmt(withdrawn_nlri, f)?;
}
write!(f, " announced: ")?;
for announced_nlri in &self.announced_nlri {
fmt::Display::fmt(announced_nlri, f)?;
}
write!(f, " path attributes: ")?;
for path_attr in &self.path_attributes {
fmt::Display::fmt(path_attr, f)?;
}
write!(f, " ]")
}
}
#[cfg(test)]
mod tests {
use super::BGPMessage;
use super::Codec;
use crate::constants::AddressFamilyIdentifier::Ipv6;
use crate::messages::AddressFamilyIdentifier::Ipv4;
use crate::traits::ParserContext;
use crate::traits::ReadablePacket;
use crate::traits::WritablePacket;
use bytes::BufMut;
use tokio_util::codec::{Decoder, Encoder};
#[test]
fn test_open_msg() {
let open_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x39, 0x01, 0x04, 0x00, 0x2a, 0x00, 0xb4, 0xd4, 0x19, 0x16, 0x26,
0x1c, 0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, 0x02, 0x02, 0x80, 0x00, 0x02,
0x02, 0x02, 0x00, 0x02, 0x02, 0x46, 0x00, 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x00,
0x2a,
];
let ctx = &ParserContext::default()
.four_octet_asn(true)
.nlri_mode(Ipv4);
let (buf, result) = BGPMessage::from_wire(ctx, open_msg_bytes).unwrap();
assert_eq!(buf.len(), 0);
let want_str = "OpenMessage: [version: 4, asn: 42, hold_time: 180, identifier: 212.25.22.38, options: [OpenOption: Capabilities: Capabilities: [MultiprotocolCapbility: [ Ipv4 Unicast ]]OpenOption: Capabilities: Capabilities: [UnknownCapability type: 128]OpenOption: Capabilities: Capabilities: [RouteRefreshCapability]OpenOption: Capabilities: Capabilities: [UnknownCapability type: 70]OpenOption: Capabilities: Capabilities: [FourByteASN: asn: 42]]]";
assert_eq!(format!("{}", result), want_str);
let wire: Vec<u8> = result.to_wire(ctx).unwrap();
assert_eq!(wire, open_msg_bytes);
}
#[test]
fn test_open_msg_ipv6() {
let open_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x35, 0x01, 0x04, 0x22, 0x36, 0x00, 0xb4, 0xd4, 0x19, 0x1b, 0x2d,
0x18, 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02,
0x02, 0x80, 0x00, 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x22, 0x36,
];
let ctx = &ParserContext::default()
.four_octet_asn(true)
.nlri_mode(Ipv4);
let (buf, result) = BGPMessage::from_wire(ctx, open_msg_bytes).unwrap();
assert_eq!(buf.len(), 0);
let want_str = "OpenMessage: [version: 4, asn: 8758, hold_time: 180, identifier: 212.25.27.45, options: [OpenOption: Capabilities: Capabilities: [MultiprotocolCapbility: [ Ipv6 Unicast ]]OpenOption: Capabilities: Capabilities: [RouteRefreshCapability]OpenOption: Capabilities: Capabilities: [UnknownCapability type: 128]OpenOption: Capabilities: Capabilities: [FourByteASN: asn: 8758]]]";
assert_eq!(format!("{}", result), want_str);
let wire: Vec<u8> = result.to_wire(ctx).unwrap();
assert_eq!(wire, open_msg_bytes);
}
#[test]
fn test_update_msg_simple() {
let update_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x79, 0x02, 0x00, 0x00, 0x00, 0x5e, 0x40, 0x01, 0x01, 0x02, 0x40,
0x02, 0x16, 0x02, 0x05, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00,
0x73, 0xfb, 0x00, 0x00, 0x05, 0x13, 0x00, 0x00, 0x12, 0x83, 0x40, 0x03, 0x04, 0xb9,
0x5f, 0xdb, 0x24, 0xc0, 0x08, 0x1c, 0x05, 0x13, 0x88, 0xb8, 0x73, 0xfb, 0x0f, 0xa0,
0x73, 0xfb, 0x0f, 0xb5, 0x9a, 0x74, 0x0f, 0xa0, 0x9a, 0x74, 0x0f, 0xaa, 0xdf, 0x1e,
0x07, 0xd0, 0xdf, 0x1e, 0x07, 0xda, 0xc0, 0x20, 0x18, 0x00, 0x00, 0xdf, 0x1e, 0x00,
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00,
0x14, 0x00, 0x00, 0x00, 0x0a, 0x18, 0xcb, 0x01, 0x4e,
];
let ctx = &ParserContext::default()
.four_octet_asn(true)
.nlri_mode(Ipv4);
let (buf, result) = BGPMessage::from_wire(ctx, update_msg_bytes).unwrap();
assert_eq!(buf.len(), 0);
let want_str = "UpdateMessage [ withdrawn: announced: 203.1.78.0/24 path attributes: OriginPathAttribute::INCOMPLETEAS Path: { Segment [ Type: AS_SEGMENT 39540 57118 29691 1299 4739 ]] }NextHop: 185.95.219.36Communities: [ 1299:35000, 29691:4000, 29691:4021, 39540:4000, 39540:4010, 57118:2000, 57118:2010, ] LargeCommunities: [ 57118:20:0, 57118:20:10, ] ]";
assert_eq!(format!("{}", result), want_str);
let reencoded = result.to_wire(ctx).unwrap();
assert_eq!(&reencoded, update_msg_bytes);
}
#[test]
fn test_insufficient_decode() {
let update_msg_bytes: &[u8] = &[0xff, 0xff, 0xff, 0xff, 0xff];
let codec = &mut Codec {
ctx: ParserContext {
four_octet_asn: Some(true),
nlri_mode: Some(Ipv6),
},
};
let mut buf = bytes::BytesMut::from(update_msg_bytes);
let result = codec.decode(&mut buf);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
assert_eq!(buf.len(), 5);
}
#[test]
fn test_exact_decode_encode() {
let update_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x79, 0x02, 0x00, 0x00, 0x00, 0x5e, 0x40, 0x01, 0x01, 0x02, 0x40,
0x02, 0x16, 0x02, 0x05, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00,
0x73, 0xfb, 0x00, 0x00, 0x05, 0x13, 0x00, 0x00, 0x12, 0x83, 0x40, 0x03, 0x04, 0xb9,
0x5f, 0xdb, 0x24, 0xc0, 0x08, 0x1c, 0x05, 0x13, 0x88, 0xb8, 0x73, 0xfb, 0x0f, 0xa0,
0x73, 0xfb, 0x0f, 0xb5, 0x9a, 0x74, 0x0f, 0xa0, 0x9a, 0x74, 0x0f, 0xaa, 0xdf, 0x1e,
0x07, 0xd0, 0xdf, 0x1e, 0x07, 0xda, 0xc0, 0x20, 0x18, 0x00, 0x00, 0xdf, 0x1e, 0x00,
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00,
0x14, 0x00, 0x00, 0x00, 0x0a, 0x18, 0xcb, 0x01, 0x4e,
];
let codec = &mut Codec {
ctx: ParserContext {
four_octet_asn: Some(true),
nlri_mode: Some(Ipv6),
},
};
let mut buf = bytes::BytesMut::from(update_msg_bytes);
let result = codec.decode(&mut buf).unwrap();
assert!(result.is_some());
assert_eq!(buf.len(), 0);
codec.encode(result.unwrap(), &mut buf).unwrap();
print!("Output bytes: ");
for b in &buf {
print!("0x{:02x}, ", b);
}
assert_eq!(buf.as_ref(), update_msg_bytes);
}
#[test]
fn test_multi_msg_codec_decode() {
let update_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x79, 0x02, 0x00, 0x00, 0x00, 0x5e, 0x40, 0x01, 0x01, 0x02, 0x40,
0x02, 0x16, 0x02, 0x05, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00,
0x73, 0xfb, 0x00, 0x00, 0x05, 0x13, 0x00, 0x00, 0x12, 0x83, 0x40, 0x03, 0x04, 0xb9,
0x5f, 0xdb, 0x24, 0xc0, 0x08, 0x1c, 0x05, 0x13, 0x88, 0xb8, 0x73, 0xfb, 0x0f, 0xa0,
0x73, 0xfb, 0x0f, 0xb5, 0x9a, 0x74, 0x0f, 0xa0, 0x9a, 0x74, 0x0f, 0xaa, 0xdf, 0x1e,
0x07, 0xd0, 0xdf, 0x1e, 0x07, 0xda, 0xc0, 0x20, 0x18, 0x00, 0x00, 0xdf, 0x1e, 0x00,
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00,
0x14, 0x00, 0x00, 0x00, 0x0a, 0x18, 0xcb, 0x01, 0x4e,
// Add part of a second message which is incomplete
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00,
];
let codec = &mut Codec {
ctx: ParserContext {
four_octet_asn: Some(true),
nlri_mode: Some(Ipv6),
},
};
let mut buf = bytes::BytesMut::from(update_msg_bytes);
let result = codec.decode(&mut buf);
assert!(result.is_ok());
assert!(result.unwrap().is_some());
assert_eq!(buf.len(), 17);
// Add the rest of the message into buf.
buf.put_slice(&[
0x79, 0x02, 0x00, 0x00, 0x00, 0x5e, 0x40, 0x01, 0x01, 0x02, 0x40, 0x02, 0x16, 0x02,
0x05, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x73, 0xfb, 0x00,
0x00, 0x05, 0x13, 0x00, 0x00, 0x12, 0x83, 0x40, 0x03, 0x04, 0xb9, 0x5f, 0xdb, 0x24,
0xc0, 0x08, 0x1c, 0x05, 0x13, 0x88, 0xb8, 0x73, 0xfb, 0x0f, 0xa0, 0x73, 0xfb, 0x0f,
0xb5, 0x9a, 0x74, 0x0f, 0xa0, 0x9a, 0x74, 0x0f, 0xaa, 0xdf, 0x1e, 0x07, 0xd0, 0xdf,
0x1e, 0x07, 0xda, 0xc0, 0x20, 0x18, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00, 0x14,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
0x00, 0x0a, 0x18, 0xcb, 0x01, 0x4e,
]);
let result2 = codec.decode(&mut buf);
assert!(result2.is_ok());
assert!(result2.unwrap().is_some());
assert_eq!(buf.len(), 0);
}
}

View File

@ -0,0 +1,376 @@
// 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 crate::constants::AddressFamilyIdentifier;
use crate::traits::BGPParserError;
use crate::traits::ParserContext;
use crate::traits::ReadablePacket;
use crate::traits::WritablePacket;
use eyre::{bail, eyre};
use nom::bytes::complete::take;
use nom::number::complete::be_u8;
use nom::Err::Failure;
use nom::IResult;
use serde::de;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::convert::TryInto;
use std::fmt;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
// NLRI here is the Neighbor Link Reachability Information from RFC 4271.
// Other NLRIs such as MP Reach NLRI are implemented as path attributes.
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct NLRI {
pub afi: AddressFamilyIdentifier,
pub prefixlen: u8,
pub prefix: Vec<u8>,
}
impl NLRI {
pub fn from_bytes(
afi: AddressFamilyIdentifier,
prefix: Vec<u8>,
prefixlen: u8,
) -> Result<Self, String> {
// Check that the vector has enough bytes to represent the prefix.
if prefix.len() < ((prefixlen + 7) / 8).into() {
return Err(format!(
"Prefix: {:?}/{} does not have enough bytes in prefix for given prefixlen",
prefix, prefixlen
));
}
Ok(NLRI {
afi,
prefixlen,
prefix,
})
}
}
impl ReadablePacket for NLRI {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
// plen is the length in bits of the address.
let (buf, prefixlen) = be_u8(buf)?;
let octet_len = (prefixlen + 7) / 8;
let (buf, prefix) = take(octet_len)(buf)?;
match ctx.nlri_mode {
None => Err(Failure(BGPParserError::CustomText(
"nlri_mode not set in the context for NLRI::from_wire".to_string(),
))),
Some(afi) => Ok((
buf,
NLRI {
afi,
prefixlen,
prefix: prefix.to_vec(),
},
)),
}
}
}
impl WritablePacket for NLRI {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf: Vec<u8> = Vec::new();
buf.push(self.prefixlen);
buf.extend(self.prefix.as_slice());
Ok(buf)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(1 + self.prefix.len() as u16)
}
}
impl Serialize for NLRI {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for NLRI {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Self::try_from(String::deserialize(deserializer)?.as_str()).map_err(de::Error::custom)
}
}
impl TryFrom<NLRI> for Ipv6Addr {
type Error = eyre::ErrReport;
fn try_from(value: NLRI) -> Result<Self, Self::Error> {
match value.afi {
AddressFamilyIdentifier::Ipv6 => {
let mut v: [u8; 16] = [0u8; 16];
if value.prefix.len() > v.len() {
bail!(
"prefix length {} greater than IPv6 address length 16",
value.prefix.len()
);
}
for (pos, e) in value.prefix.iter().enumerate() {
v[pos] = *e;
}
let ip6: Ipv6Addr = v.into();
Ok(ip6)
}
other => bail!("Unsupported AddressFamily type {}", other),
}
}
}
impl TryFrom<NLRI> for Ipv4Addr {
type Error = eyre::Report;
fn try_from(value: NLRI) -> Result<Self, Self::Error> {
match value.afi {
AddressFamilyIdentifier::Ipv4 => {
let mut v: [u8; 4] = [0u8; 4];
if value.prefix.len() > v.len() {
bail!(
"prefix length {} greater than IPv4 address length 4",
value.prefix.len()
);
}
for (pos, e) in value.prefix.iter().enumerate() {
v[pos] = *e;
}
let ip4 = Ipv4Addr::new(v[0], v[1], v[2], v[3]);
Ok(ip4)
}
other => bail!("Unsupported AddressFamily type: {}", other),
}
}
}
impl TryInto<IpAddr> for NLRI {
type Error = eyre::ErrReport;
fn try_into(self) -> Result<IpAddr, Self::Error> {
match self.afi {
AddressFamilyIdentifier::Ipv4 => {
let mut v: [u8; 4] = [0u8; 4];
if self.prefix.len() > v.len() {
bail!(
"prefix length {} greater than IPv4 address length 4",
self.prefix.len()
);
}
for (pos, e) in self.prefix.iter().enumerate() {
v[pos] = *e;
}
let ip4 = Ipv4Addr::new(v[0], v[1], v[2], v[3]);
Ok(IpAddr::V4(ip4))
}
AddressFamilyIdentifier::Ipv6 => {
let mut v: [u8; 16] = [0u8; 16];
if self.prefix.len() > v.len() {
bail!(
"prefix length {} greater than IPv6 address length 16",
self.prefix.len()
);
}
for (pos, e) in self.prefix.iter().enumerate() {
v[pos] = *e;
}
let ip6: Ipv6Addr = v.into();
Ok(IpAddr::V6(ip6))
}
}
}
}
impl TryFrom<&str> for NLRI {
type Error = eyre::ErrReport;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let parts: Vec<&str> = value.split('/').collect();
if parts.len() != 2 {
bail!("Expected ip_addr/prefixlen but got: {}", value);
}
let prefixlen: u8 =
u8::from_str(parts[1]).map_err(|_| eyre!("failed to parse prefixlen"))?;
let mut octets: Vec<u8>;
let afi: AddressFamilyIdentifier;
if parts[0].contains(':') {
afi = AddressFamilyIdentifier::Ipv6;
let addr: Ipv6Addr = Ipv6Addr::from_str(parts[0]).map_err(|e| eyre!(e))?;
octets = addr.octets().to_vec();
} else if parts[0].contains('.') {
afi = AddressFamilyIdentifier::Ipv4;
let addr: Ipv4Addr = Ipv4Addr::from_str(parts[0]).map_err(|e| eyre!(e))?;
octets = addr.octets().to_vec();
} else {
bail!("Could not detect IP address type: {}", parts[0]);
}
// Truncate octets to prefixlen
if prefixlen % 8 == 0 {
// Cleanly truncate.
octets.truncate((prefixlen / 8).into());
} else {
let num_bytes = (prefixlen / 8) + 1;
let mask = u8::MAX << (8 - (prefixlen % 8));
octets.truncate(num_bytes.into());
if !octets.is_empty() {
let last_pos = octets.len() - 1;
octets[last_pos] &= mask;
}
}
Ok(NLRI {
afi,
prefixlen,
prefix: octets,
})
}
}
impl fmt::Display for NLRI {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.afi {
AddressFamilyIdentifier::Ipv4 => {
let bytes = &mut self.prefix.clone();
if bytes.len() < 4 {
bytes.extend(std::iter::repeat(0).take(4 - bytes.len()));
}
let four_bytes: [u8; 4] = bytes.as_slice().try_into().map_err(|_| fmt::Error {})?;
let ipv4_addr = Ipv4Addr::from(four_bytes);
write!(f, "{}/{}", ipv4_addr, self.prefixlen)
}
AddressFamilyIdentifier::Ipv6 => {
let bytes = &mut self.prefix.clone();
if bytes.len() < 16 {
bytes.extend(std::iter::repeat(0).take(16 - bytes.len()));
}
let sixteen_bytes: [u8; 16] =
bytes.as_slice().try_into().map_err(|_| fmt::Error {})?;
let ipv6_addr = Ipv6Addr::from(sixteen_bytes);
write!(f, "{}/{}", ipv6_addr, self.prefixlen)
}
}
}
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use super::NLRI;
use crate::constants::AddressFamilyIdentifier::{Ipv4, Ipv6};
use crate::traits::ParserContext;
use crate::traits::ReadablePacket;
use crate::traits::WritablePacket;
#[test]
fn test_basic_nlri_v6() {
let nlri_bytes: &[u8] = &[0x20, 0x20, 0x01, 0xdb, 0x8];
let ctx = &ParserContext::default()
.four_octet_asn(true)
.nlri_mode(Ipv6);
let nlri_res: (&[u8], NLRI) = NLRI::from_wire(ctx, nlri_bytes).unwrap();
assert_eq!(nlri_res.1.afi, Ipv6);
assert_eq!(nlri_res.1.prefixlen, 32);
assert_eq!(nlri_res.1.prefix, vec![0x20, 0x01, 0xdb, 0x8]);
assert_eq!(nlri_res.0.len(), 0);
let wire: Vec<u8> = nlri_res.1.to_wire(ctx).unwrap();
assert_eq!(wire.as_slice(), nlri_bytes);
assert_eq!(nlri_res.1.wire_len(ctx).unwrap() as usize, wire.len());
}
#[test]
fn test_basic_nlri_v4() {
let nlri_bytes: &[u8] = &[0x18, 192, 168, 1];
let ctx = &ParserContext::default()
.four_octet_asn(true)
.nlri_mode(Ipv4);
let nlri_res: (&[u8], NLRI) = NLRI::from_wire(ctx, nlri_bytes).unwrap();
assert_eq!(nlri_res.1.afi, Ipv4);
assert_eq!(nlri_res.1.prefixlen, 24);
assert_eq!(nlri_res.1.prefix, vec![192, 168, 1]);
assert_eq!(nlri_res.0.len(), 0);
let wire: Vec<u8> = nlri_res.1.to_wire(ctx).unwrap();
assert_eq!(wire.as_slice(), nlri_bytes);
assert_eq!(nlri_res.1.wire_len(ctx).unwrap() as usize, wire.len());
}
#[test]
fn test_string_roundtrip() {
let cases: Vec<(String, Vec<u8>, u8, String)> = vec![
(
"2001:db8::/32".into(),
vec![0x20, 0x01, 0xd, 0xb8],
32,
"2001:db8::/32".into(),
),
(
"2001:db8::1/16".into(),
vec![0x20, 0x01],
16,
"2001::/16".into(),
),
(
"2001:db8::/64".into(),
vec![0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0],
64,
"2001:db8::/64".into(),
),
(
"2001:db8::/24".into(),
vec![0x20, 0x01, 0xd],
24,
"2001:d00::/24".into(),
),
("2001:db8::/0".into(), vec![], 0, "::/0".into()),
("::/0".into(), vec![], 0, "::/0".into()),
("10.0.0.0/8".into(), vec![10], 8, "10.0.0.0/8".into()),
];
for (i, case) in cases.iter().enumerate() {
let parsed_nlri = NLRI::try_from(case.0.as_str()).unwrap();
assert_eq!(parsed_nlri.prefix, case.1, "Check prefix match ({})", i);
assert_eq!(
parsed_nlri.prefixlen, case.2,
"Check prefixlen match ({})",
i
);
assert_eq!(
case.3,
format!("{}", parsed_nlri),
"Check std::fmt::Display match ({})",
i
);
// Check that roundtripping via JSON serialize / deserialize is correct.
let json_encoded = serde_json::to_string(&parsed_nlri).unwrap();
assert_eq!(json_encoded[1..json_encoded.len() - 1], case.3);
let reparsed: NLRI = serde_json::from_str(&json_encoded).unwrap();
assert_eq!(reparsed, parsed_nlri);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,74 @@
// 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.
//! Implements high level abstractions for use in the BGP parser.
use crate::constants::AddressFamilyIdentifier;
use nom::error::ErrorKind;
use nom::error::ParseError;
use nom::IResult;
// ParserContext contains information pertinent to configurations which affect
// how message parsing is to be handled.
#[derive(Debug, Default, PartialEq, Clone)]
pub struct ParserContext {
// Whether the peer is RFC6793 compliant.
pub four_octet_asn: Option<bool>,
// nlri_mode specifies if a parsed NLRI prefix should be a IPv4 or IPv6 address.
pub nlri_mode: Option<AddressFamilyIdentifier>,
}
impl ParserContext {
pub fn four_octet_asn(mut self, v: bool) -> Self {
self.four_octet_asn = Some(v);
self
}
pub fn nlri_mode(mut self, v: AddressFamilyIdentifier) -> Self {
self.nlri_mode = Some(v);
self
}
}
// Custom error type for the parser.
#[derive(Debug, PartialEq)]
pub enum BGPParserError<I> {
CustomText(String),
Nom(I, ErrorKind),
}
impl<I> ParseError<I> for BGPParserError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
BGPParserError::Nom(input, kind)
}
fn append(_: I, _: ErrorKind, other: Self) -> Self {
other
}
}
pub trait WritablePacket {
/// to_wire serializes the packet to the wire format bytes.
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str>;
/// wire_len is the length of the message in bytes as would be on the wire.
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str>;
}
pub trait ReadablePacket {
fn from_wire<'a>(
ctx: &ParserContext,
i: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>>
where
Self: Sized;
}

View File

@ -0,0 +1,38 @@
[package]
name = "route_client"
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[lints]
workspace = true
[dependencies]
async-trait.workspace = true
bgp_packet.workspace = true
byteorder = "1.4.3"
bytes.workspace = true
eyre.workspace = true
futures.workspace = true
ip_network_table-deps-treebitmap.workspace = true
log.workspace = true
netlink-packet-route.workspace = true
netlink-packet-utils.workspace = true
nom = "7.1"
prost.workspace = true
rtnetlink.workspace = true
serde.workspace = true
tokio-stream = "0.1.14"
tokio-util = { version = "0.7.10", features = ["codec"] }
tokio.workspace = true
tonic.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
warp.workspace = true
[build-dependencies]
tonic-build = { version = "0.5.1", features = ["compression", "prost"] }

View File

@ -0,0 +1,19 @@
// 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.
fn main() {
tonic_build::configure()
.compile(&["proto/route_service.proto"], &["proto"])
.unwrap();
}

View File

@ -0,0 +1,98 @@
// 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.
syntax = "proto3";
package bgpd.grpc;
enum AddressFamily {
UNKNOWN = 0;
IPv4 = 1;
IPv6 = 2;
}
message Prefix {
bytes ip_prefix = 1;
int32 prefix_len = 2;
AddressFamily address_family = 3;
}
// Path represents the metadata associated with the route to a particular
// prefix.
message Path {
bytes nexthop = 1;
bytes peer_id = 2;
uint32 local_pref = 3;
uint32 med = 4;
repeated uint32 as_path = 5;
// TODO: Path attributes. Not yet supported because we need to generate proto
// definitions for all of them.
}
message PathSet {
uint64 epoch = 1;
Prefix prefix = 2;
repeated Path paths = 3;
}
message StreamPathsRequest { AddressFamily address_family = 1; }
message DumpPathsRequest { AddressFamily address_family = 1; };
message DumpPathsResponse {
uint64 epoch = 1;
repeated PathSet path_sets = 2;
};
service RouteService {
// DumpPaths returns all the paths currently in the RIB.
rpc DumpPaths(DumpPathsRequest) returns (DumpPathsResponse);
// StreamPaths dumps the existing routes and starts streaming updates to the
// RIB.
rpc StreamPaths(StreamPathsRequest) returns (stream PathSet);
}
message PeerStatusRequest {}
message PeerStatus {
string peer_name = 1;
bytes peer_id = 2;
string state = 3;
optional uint64 session_established_time = 4;
optional uint64 last_messaage_time = 5;
optional uint64 route_updates_in = 6;
optional uint64 route_updates_out = 7;
}
message PeerStatusResponse { repeated PeerStatus peer_status = 1; }
message AnnouncementRequest {
string peer_name = 1;
Prefix prefix = 2;
repeated string large_communities = 3;
// When set to true inserts the route, when set to false withdraws the route.
bool add = 4;
}
message AnnouncementResponse {}
// BGPServerAdminService implements an administrative interface to
// view the status and control the operation of this BGP server.
service BGPServerAdminService {
// Get the status of a specific peer.
rpc PeerStatus(PeerStatusRequest) returns (PeerStatusResponse);
// Make a BGP announcement to a specific peer.
rpc AnnounceToPeer(AnnouncementRequest) returns (AnnouncementResponse);
}

View File

@ -0,0 +1,66 @@
use std::str::FromStr;
use std::time::Duration;
use bgp_packet::nlri::NLRI;
use eyre::Result;
use tonic::transport::{Channel, Endpoint, Uri};
use crate::proto::bgp_server_admin_service_client::BgpServerAdminServiceClient;
use crate::proto::{AnnouncementRequest, Prefix};
pub struct Connector {
client: BgpServerAdminServiceClient<Channel>,
}
impl Connector {
pub async fn new(addr: String) -> Result<Self> {
let uri = Uri::from_str(addr.as_str()).unwrap();
let endpoint = Endpoint::from(uri).keep_alive_timeout(Duration::from_secs(10));
let client = BgpServerAdminServiceClient::connect(endpoint).await?;
Ok(Self { client })
}
pub async fn send_announce(&mut self, peer_name: String, prefix: NLRI) -> Result<()> {
let request = AnnouncementRequest {
peer_name,
prefix: Some(Prefix {
ip_prefix: prefix.prefix,
prefix_len: prefix.prefixlen as i32,
address_family: match prefix.afi {
bgp_packet::constants::AddressFamilyIdentifier::Ipv4 => {
crate::proto::AddressFamily::IPv4.into()
}
bgp_packet::constants::AddressFamilyIdentifier::Ipv6 => {
crate::proto::AddressFamily::IPv6.into()
}
},
}),
large_communities: vec![],
add: true,
};
self.client.announce_to_peer(request).await?;
Ok(())
}
pub async fn send_withdraw(&mut self, peer_name: String, prefix: NLRI) -> Result<()> {
let request = AnnouncementRequest {
peer_name,
prefix: Some(Prefix {
ip_prefix: prefix.prefix,
prefix_len: prefix.prefixlen as i32,
address_family: match prefix.afi {
bgp_packet::constants::AddressFamilyIdentifier::Ipv4 => {
crate::proto::AddressFamily::IPv4.into()
}
bgp_packet::constants::AddressFamilyIdentifier::Ipv6 => {
crate::proto::AddressFamily::IPv6.into()
}
},
}),
large_communities: vec![],
add: false,
};
self.client.announce_to_peer(request).await?;
Ok(())
}
}

View File

@ -0,0 +1,238 @@
// 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 eyre::{bail, Result};
use ip_network_table_deps_treebitmap::address::Address;
use ip_network_table_deps_treebitmap::IpLookupTable;
use std::convert::{TryFrom, TryInto};
use std::fmt::Formatter;
use std::net::Ipv6Addr;
use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::{info, trace, warn};
use bgp_packet::constants::AddressFamilyIdentifier;
use bgp_packet::nlri::NLRI;
use crate::southbound_interface::SouthboundInterface;
/// fib_state implements the logic to maintain forwarding routes in the FIB.
/// This for now means the Linux Kernel via Netlink, but in the future can
/// be extended to include other targets such as OpenFlow or even program
/// a router using BGP.
#[derive(Debug)]
pub struct FibEntry {
nexthop: IpAddr,
}
pub struct FibState<A: Address, S: SouthboundInterface> {
pub fib: IpLookupTable<A, Arc<Mutex<FibEntry>>>,
pub southbound: S,
pub af: AddressFamilyIdentifier,
}
impl<A: Address, S: SouthboundInterface> std::fmt::Debug for FibState<A, S> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "FibState af: {:?}", self.af)
}
}
/// to_octets provides an interface for accessing an address as a vector of bytes.
/// This is implemented for IPv4Addr and IPv6Addr to be able to use them interchangably
/// to send updates to the kernel.
pub trait ToOctets {
fn octets(&self) -> Vec<u8>;
}
impl ToOctets for Ipv4Addr {
fn octets(&self) -> Vec<u8> {
self.octets().into()
}
}
impl ToOctets for Ipv6Addr {
fn octets(&self) -> Vec<u8> {
self.octets().into()
}
}
impl<
A: Address
+ std::convert::TryFrom<NLRI>
+ ToOctets
+ std::cmp::PartialEq
+ std::fmt::Display
+ std::fmt::Debug,
S: SouthboundInterface,
> FibState<A, S>
where
eyre::ErrReport: From<<A as TryFrom<NLRI>>::Error>,
{
pub async fn get_routing_table(&mut self) -> Result<()> {
todo!();
}
/// route_add requests updating the nexthop to a particular path if it is not already
/// the best path.
pub async fn route_add(&mut self, nlri: &NLRI, nexthop: IpAddr) -> Result<()> {
trace!(af = ?self.af, %nlri, %nexthop);
// Lookup the path in the Fib, there are three possible outcomes:
// 1. The route is not yet known, we add it to the FibState and inject it into the kernel,
// 2. The route is known and has a prior nexthop that needs to be updated
// 3. The route is known and has the same nexthop: no-op.
let prefix_addr: A = nlri.clone().try_into()?;
match self
.fib
.exact_match(prefix_addr, nlri.prefixlen.into())
.as_mut()
{
Some(entry_wrapped) => {
let mut entry = entry_wrapped.lock().await;
if entry.nexthop == nexthop {
// Nothing to do, route already in kernel.
trace!("Skipping route that already exists in kernel");
} else {
// Remove old route
trace!("Remove old route: {:?}", entry);
if let Err(e) = self.southbound.route_del(nlri.clone(), entry.nexthop).await {
warn!(
"Southbound interface returned error when trying to remove route: {} via {}, error: {}",
nlri, entry.nexthop, e
);
bail!("Failed to delete route in update: {}", e);
}
// Add new route
trace!(
"Add new route: prefix: {:?}, nexthop: {}",
nlri.prefix,
nexthop
);
if let Err(e) = self
.southbound
.route_add(self.af, nlri.clone(), nexthop)
.await
{
warn!(
"Netlink returned error when trying to add route: {} via {}, error: {}",
nlri, nexthop, e
);
bail!("Failed to add route to netlink in update: {}", e);
}
entry.nexthop = nexthop;
}
}
None => {
// Need to insert a new entry for this route
let entry = FibEntry {
nexthop: nexthop.clone(),
};
if let Err(e) = self
.southbound
.route_add(self.af, nlri.clone(), nexthop)
.await
{
warn!(
"Netlink returned error when trying to add route: {} via {}, error: {}",
nlri, nexthop, e
);
bail!("Failed to add new route: {}", e);
}
let addr: A = nlri.clone().try_into()?;
self.fib
.insert(addr, nlri.prefixlen.into(), Arc::new(Mutex::new(entry)));
trace!(af = ?self.af, nlri = %nlri, nexthop = %nexthop, "Added to kernel");
}
};
Ok(())
}
/// route_del removes a route from the FibState and kernel.
pub async fn route_del(&mut self, nlri: NLRI) -> Result<()> {
let prefix_addr: A = nlri.clone().try_into()?;
if let Some(entry_wrapped) = self.fib.exact_match(prefix_addr, nlri.prefixlen.into()) {
{
let entry = entry_wrapped.lock().await;
info!(%nlri, %prefix_addr, %entry.nexthop, "route_del");
if let Err(e) = self.southbound.route_del(nlri.clone(), entry.nexthop).await {
bail!(
"Failed to apply route mutation to remove NLRI: {}, error: {}",
nlri,
e
);
}
}
self.fib.remove(prefix_addr, nlri.prefixlen.into());
info!(%nlri, "Successfully removed route from kernel");
} else {
bail!("Failed to find prefix to remove from FIB: {}", nlri);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::{
net::{IpAddr, Ipv6Addr},
str::FromStr,
};
use bgp_packet::nlri::NLRI;
use eyre::{eyre, Result};
use ip_network_table_deps_treebitmap::IpLookupTable;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
use crate::southbound_interface::DummyVerifier;
use super::FibState;
#[tokio::test]
async fn test_double_add() -> Result<()> {
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(EnvFilter::from_default_env())
.init();
let southbound = DummyVerifier::default();
let mut fib_state: FibState<Ipv6Addr, DummyVerifier> = FibState {
fib: IpLookupTable::default(),
southbound,
af: bgp_packet::constants::AddressFamilyIdentifier::Ipv6,
};
let nlri = NLRI::try_from("2602:feda:b8d::/48").map_err(|e| eyre!(e))?;
let nexthop = IpAddr::V6(Ipv6Addr::from_str("2001:db8:cafe::1")?);
fib_state
.route_add(&nlri, nexthop)
.await
.map_err(|e| eyre!(e))?;
let nexthop_2 = IpAddr::V6(Ipv6Addr::from_str("2001:db8:babe::1")?);
fib_state
.route_add(&nlri, nexthop_2)
.await
.map_err(|e| eyre!(e))?;
Ok(())
}
}

View File

@ -0,0 +1,198 @@
// 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.
pub mod connector;
pub mod fib_state;
pub mod netlink;
pub mod southbound_interface;
use std::convert::TryInto;
use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::str::FromStr;
use std::time::Duration;
use eyre::{bail, Result};
use ip_network_table_deps_treebitmap::IpLookupTable;
use tonic::transport::Endpoint;
use tonic::transport::Uri;
use tracing::trace;
use bgp_packet::constants::AddressFamilyIdentifier;
use bgp_packet::nlri::NLRI;
use crate::fib_state::FibState;
use crate::proto::route_service_client::RouteServiceClient;
use crate::southbound_interface::SouthboundInterface;
pub mod proto {
tonic::include_proto!("bgpd.grpc");
}
fn vec_to_array<T, const N: usize>(v: Vec<T>) -> Result<[T; N]> {
v.try_into()
.map_err(|_| eyre::Error::msg("Wrong size of Vec".to_string()))
}
/// Temporary hack to select the route to install to the FIB.
/// TODO: Implement proper route selection logic.
fn select_best_route(ps: &proto::PathSet) -> Option<proto::Path> {
let mut selected: Option<proto::Path> = None;
for path in &ps.paths {
if let Some(current) = selected.as_ref() {
if path.local_pref < current.local_pref {
selected = Some(path.clone());
}
} else {
selected = Some(path.clone());
}
}
selected
}
pub async fn run_connector_v4<S: SouthboundInterface>(
route_server: String,
dry_run: bool,
southbound: S,
) -> Result<()> {
// Create netlink socket.
let mut fib_state = FibState::<Ipv4Addr, S> {
fib: IpLookupTable::new(),
southbound,
af: AddressFamilyIdentifier::Ipv4,
};
let uri = Uri::from_str(route_server.as_str()).unwrap();
let endpoint = Endpoint::from(uri).keep_alive_timeout(Duration::from_secs(10));
let mut client = RouteServiceClient::connect(endpoint).await?;
let request = proto::StreamPathsRequest {
address_family: proto::AddressFamily::IPv4.into(),
};
let mut stream = client.stream_paths(request).await?.into_inner();
let mut msg_ctr: u64 = 0;
while let Some(route) = stream.message().await? {
let nlri = NLRI {
afi: AddressFamilyIdentifier::Ipv4,
prefixlen: route.prefix.as_ref().unwrap().prefix_len as u8,
prefix: route.prefix.as_ref().unwrap().ip_prefix.clone(),
};
trace!("IPv4 Update {} for: {} ", msg_ctr, nlri);
msg_ctr += 1;
if !dry_run {
if !route.paths.is_empty() {
if let Some(best) = select_best_route(&route) {
// Hack to convert the nexthop into a v4 addr
let nh_bytes: [u8; 4] = vec_to_array(best.nexthop.clone())?;
let nh_addr: Ipv4Addr = Ipv4Addr::from(nh_bytes);
if let Err(e) = fib_state.route_add(&nlri, IpAddr::V4(nh_addr)).await {
bail!("Failed to add route into kernel: {}: {}", nlri, e);
}
}
} else {
// No more paths, delete
if let Err(e) = fib_state.route_del(nlri).await {
bail!("Failed to delete route from kernel: {}", e);
}
}
}
trace!("Number of paths: {}", route.paths.len());
for path in &route.paths {
// TODO: have a proper error here not unwrap.
let nexthop_bytes: [u8; 4] = path.nexthop.clone().try_into().unwrap();
let nexthop: Ipv4Addr = nexthop_bytes.into();
trace!(
"nexthop: {}, peer_id: {:x?}, local_pref: {}, med: {}, as_path: {:?}",
nexthop,
path.peer_id,
path.local_pref,
path.med,
path.as_path
);
}
}
unreachable!()
}
pub async fn run_connector_v6<S: SouthboundInterface>(
route_server: String,
dry_run: bool,
southbound: S,
) -> Result<()> {
let mut fib_state = FibState::<Ipv6Addr, S> {
fib: IpLookupTable::new(),
southbound,
af: AddressFamilyIdentifier::Ipv6,
};
let uri = Uri::from_str(route_server.as_str()).unwrap();
let endpoint = Endpoint::from(uri).keep_alive_timeout(Duration::from_secs(10));
let mut client = RouteServiceClient::connect(endpoint).await?;
let request = proto::StreamPathsRequest {
address_family: proto::AddressFamily::IPv6.into(),
};
let mut stream = client.stream_paths(request).await?.into_inner();
let mut msg_ctr: u64 = 0;
while let Some(route) = stream.message().await? {
let nlri = NLRI {
afi: AddressFamilyIdentifier::Ipv6,
prefixlen: route.prefix.as_ref().unwrap().prefix_len as u8,
prefix: route.prefix.as_ref().unwrap().ip_prefix.clone(),
};
trace!("IPv6 Update {} for: {} ", msg_ctr, nlri);
msg_ctr += 1;
if !dry_run {
if !route.paths.is_empty() {
if let Some(best) = select_best_route(&route) {
// Hack to convert the nexthop into a v6 addr
let nh_bytes: [u8; 16] = vec_to_array(best.nexthop.clone())?;
let nh_addr: Ipv6Addr = Ipv6Addr::from(nh_bytes);
if let Err(e) = fib_state.route_add(&nlri, IpAddr::V6(nh_addr)).await {
bail!("Failed to add route into kernel: {}: {}", nlri, e);
}
}
} else {
// No more paths, delete
if let Err(e) = fib_state.route_del(nlri).await {
bail!("Failed to delete route from kernel: {}", e);
}
}
}
trace!("Number of paths: {}", route.paths.len());
for path in &route.paths {
// TODO: have a proper error here not unwrap.
let nexthop_bytes: [u8; 16] = path.nexthop.clone().try_into().unwrap();
let nexthop: Ipv6Addr = nexthop_bytes.into();
trace!(
"nexthop: {}, peer_id: {:x?}, local_pref: {}, med: {}, as_path: {:?}",
nexthop,
path.peer_id,
path.local_pref,
path.med,
path.as_path
);
}
}
unreachable!()
}

View File

@ -0,0 +1,154 @@
use async_trait::async_trait;
use bgp_packet::{constants::AddressFamilyIdentifier, nlri::NLRI};
use eyre::{bail, Result};
use futures::TryStreamExt;
use netlink_packet_route::route::RouteAddress;
use netlink_packet_route::route::RouteAttribute;
use netlink_packet_route::route::RouteHeader;
use netlink_packet_route::route::RouteMessage;
use netlink_packet_route::route::RouteType;
use netlink_packet_route::AddressFamily as NetlinkAddressFamily;
use rtnetlink::IpVersion;
use std::convert::TryInto;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use super::southbound_interface::SouthboundInterface;
/// NetlinkConnector implements methods to read/update Linux networking stuff including
/// routes and link level info.
#[derive(Clone)]
pub struct NetlinkConnector {
handle: rtnetlink::Handle,
table: Option<u32>,
}
#[async_trait]
impl SouthboundInterface for NetlinkConnector {
async fn get_all_routes(
&mut self,
address_family: AddressFamilyIdentifier,
) -> Result<Vec<(NLRI, IpAddr)>> {
let route = self.handle.route();
let mut get_request = route.get(address_family.into());
get_request.message_mut().header.table = 201;
todo!();
}
async fn route_add(
&mut self,
address_family: AddressFamilyIdentifier,
prefix: NLRI,
nexthop: IpAddr,
) -> Result<()> {
let route = self.handle.route();
match address_family {
AddressFamilyIdentifier::Ipv6 => {
let prefix_len = prefix.prefixlen;
let addr: Ipv6Addr = match prefix.try_into()? {
IpAddr::V6(addr) => addr,
_ => {
bail!("Got non IPv6 address from NLRI")
}
};
let gw_addr: Ipv6Addr = match nexthop.clone().try_into()? {
IpAddr::V6(addr) => addr,
_ => {
bail!("Got non IPv6 address from nexthop");
}
};
let mut mutation = route
.add()
.v6()
.destination_prefix(addr, prefix_len)
.gateway(gw_addr);
if let Some(table_id) = self.table {
mutation = mutation.table_id(table_id.try_into().unwrap());
}
mutation.execute().await.map_err(|e| eyre::Error::from(e))
}
AddressFamilyIdentifier::Ipv4 => {
let prefix_len = prefix.prefixlen;
let addr: Ipv4Addr = match prefix.clone().try_into()? {
IpAddr::V4(addr) => addr,
_ => {
bail!("Got non-IPv4 address from NLRI")
}
};
let gw_addr = match nexthop.clone().try_into()? {
IpAddr::V4(addr) => addr,
_ => {
bail!("Got non IPv4 address from nexthop");
}
};
let mut mutation = route
.add()
.v4()
.destination_prefix(addr, prefix_len)
.gateway(gw_addr);
if let Some(table_id) = self.table {
mutation = mutation.table_id(table_id.try_into().unwrap());
}
mutation.execute().await.map_err(|e| eyre::Error::from(e))
}
}
}
async fn route_del(&mut self, prefix: NLRI, nexthop: IpAddr) -> Result<()> {
let rt_handle = self.handle.route();
let destination = match prefix.afi {
AddressFamilyIdentifier::Ipv4 => RouteAddress::Inet(prefix.clone().try_into()?),
AddressFamilyIdentifier::Ipv6 => RouteAddress::Inet6(prefix.clone().try_into()?),
};
let nexthop = match nexthop {
IpAddr::V4(ipv4) => RouteAddress::Inet(ipv4),
IpAddr::V6(ipv6) => RouteAddress::Inet6(ipv6),
};
let header = RouteHeader {
address_family: match prefix.afi {
AddressFamilyIdentifier::Ipv4 => NetlinkAddressFamily::Inet,
AddressFamilyIdentifier::Ipv6 => NetlinkAddressFamily::Inet6,
},
destination_prefix_length: prefix.prefixlen,
table: self.table.unwrap_or(0) as u8,
kind: RouteType::Unicast,
..Default::default()
};
let mut rt_msg: RouteMessage = Default::default();
rt_msg.header = header;
rt_msg.attributes = vec![
RouteAttribute::Destination(destination),
RouteAttribute::Gateway(nexthop),
];
rt_handle
.del(rt_msg)
.execute()
.await
.map_err(|e| eyre::Error::from(e))
}
}
impl NetlinkConnector {
pub async fn new(table: Option<u32>) -> Result<Self> {
let (connection, handle, _) = rtnetlink::new_connection()?;
tokio::spawn(connection);
Ok(NetlinkConnector { handle, table })
}
pub async fn dump_routes(
&mut self,
address_family: AddressFamilyIdentifier,
) -> Result<Vec<RouteMessage>, rtnetlink::Error> {
let mut req = self.handle.route().get(match address_family {
AddressFamilyIdentifier::Ipv4 => IpVersion::V4,
AddressFamilyIdentifier::Ipv6 => IpVersion::V6,
});
if let Some(table_id) = self.table {
req.message_mut()
.attributes
.push(RouteAttribute::Table(table_id));
}
req.execute().try_collect().await
}
}

View File

@ -0,0 +1,107 @@
// 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 std::{collections::HashMap, net::IpAddr};
use async_trait::async_trait;
use eyre::{eyre, Result};
use log::info;
use bgp_packet::{constants::AddressFamilyIdentifier, nlri::NLRI};
/// SouthboundInterface provides a uniform API to network forwarding elements
/// These are devices or targets that perform packet routing and are the end
/// consumers of packet routing data.
#[async_trait]
pub trait SouthboundInterface {
/// route_add adds a route towards a particular address_family/NLRI via the given nexthop.
async fn route_add(
&mut self,
address_family: AddressFamilyIdentifier,
prefix: NLRI,
nexthop: IpAddr,
) -> Result<()>;
/// route_del removes the route towards a particular prefix via a given nexthop.
async fn route_del(&mut self, prefix: NLRI, nexthop: IpAddr) -> Result<()>;
/// get_all_routes returns all the routes for the given address family.
async fn get_all_routes(
&mut self,
address_family: AddressFamilyIdentifier,
) -> Result<Vec<(NLRI, IpAddr)>>;
}
/// DummyVerifier is a SouthboundInterface that checks that routes are not added more than
/// once and not removed when there are none.
#[derive(Clone, Default)]
pub struct DummyVerifier {
route_state: HashMap<NLRI, IpAddr>,
}
#[async_trait]
impl SouthboundInterface for DummyVerifier {
async fn get_all_routes(
&mut self,
_address_family: AddressFamilyIdentifier,
) -> Result<Vec<(NLRI, IpAddr)>> {
todo!();
}
async fn route_add(
&mut self,
_: AddressFamilyIdentifier,
prefix: NLRI,
nexthop: IpAddr,
) -> Result<()> {
// Check that the route is not already present.
match self.route_state.get(&prefix) {
Some(value) => {
return Err(eyre!(
"Prefix {} with nexthop {} already contained in route_state! when trying to add {} -> {}",
prefix, value, prefix, nexthop,
));
}
_ => {}
}
if self.route_state.get(&prefix).is_some() {}
// Insert route into in memory state.
self.route_state.insert(prefix, nexthop);
info!("Route add ok in verifier ({})", self.route_state.len());
Ok(())
}
async fn route_del(&mut self, prefix: NLRI, nexthop: IpAddr) -> Result<()> {
match self.route_state.remove(&prefix) {
Some(entry) => {
if entry != nexthop {
return Err(eyre!(
"Removed entry's nexthop did not match: {} vs requested {}",
entry,
nexthop
));
}
}
None => {
return Err(eyre!(
"Requested removal of route {} that was not in route_state",
prefix
));
}
}
info!("Route del ok in verifier ({})", self.route_state.len());
Ok(())
}
}

34
crates/server/Cargo.toml Normal file
View File

@ -0,0 +1,34 @@
[package]
name = "bgp_server"
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[lints]
workspace = true
[dependencies]
bgp_packet.workspace = true
byteorder = "1.4.3"
bytes.workspace = true
chrono = { version = "0.4.38", features = ["serde"] }
eyre.workspace = true
ip_network_table-deps-treebitmap.workspace = true
log.workspace = true
nom = "7.1"
prost.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio-stream = "0.1.14"
tokio-util = { version = "0.7.10", features = ["codec"] }
tokio.workspace = true
tonic.workspace = true
tracing.workspace = true
warp.workspace = true
[build-dependencies]
tonic-build = { version = "0.5.1", features = ["compression", "prost"] }

19
crates/server/build.rs Normal file
View File

@ -0,0 +1,19 @@
// 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.
fn main() {
tonic_build::configure()
.compile(&["proto/route_service.proto"], &["proto"])
.unwrap();
}

View File

@ -0,0 +1,98 @@
// 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.
syntax = "proto3";
package bgpd.grpc;
enum AddressFamily {
UNKNOWN = 0;
IPv4 = 1;
IPv6 = 2;
}
message Prefix {
bytes ip_prefix = 1;
int32 prefix_len = 2;
AddressFamily address_family = 3;
}
// Path represents the metadata associated with the route to a particular
// prefix.
message Path {
bytes nexthop = 1;
bytes peer_id = 2;
uint32 local_pref = 3;
uint32 med = 4;
repeated uint32 as_path = 5;
// TODO: Path attributes. Not yet supported because we need to generate proto
// definitions for all of them.
}
message PathSet {
uint64 epoch = 1;
Prefix prefix = 2;
repeated Path paths = 3;
}
message StreamPathsRequest { AddressFamily address_family = 1; }
message DumpPathsRequest { AddressFamily address_family = 1; };
message DumpPathsResponse {
uint64 epoch = 1;
repeated PathSet path_sets = 2;
};
service RouteService {
// DumpPaths returns all the paths currently in the RIB.
rpc DumpPaths(DumpPathsRequest) returns (DumpPathsResponse);
// StreamPaths dumps the existing routes and starts streaming updates to the
// RIB.
rpc StreamPaths(StreamPathsRequest) returns (stream PathSet);
}
message PeerStatusRequest {}
message PeerStatus {
string peer_name = 1;
bytes peer_id = 2;
string state = 3;
optional uint64 session_established_time = 4;
optional uint64 last_messaage_time = 5;
optional uint64 route_updates_in = 6;
optional uint64 route_updates_out = 7;
}
message PeerStatusResponse { repeated PeerStatus peer_status = 1; }
message AnnouncementRequest {
string peer_name = 1;
Prefix prefix = 2;
repeated string large_communities = 3;
// When set to true inserts the route, when set to false withdraws the route.
bool add = 4;
}
message AnnouncementResponse {}
// BGPServerAdminService implements an administrative interface to
// view the status and control the operation of this BGP server.
service BGPServerAdminService {
// Get the status of a specific peer.
rpc PeerStatus(PeerStatusRequest) returns (PeerStatusResponse);
// Make a BGP announcement to a specific peer.
rpc AnnounceToPeer(AnnouncementRequest) returns (AnnouncementResponse);
}

View File

@ -0,0 +1,456 @@
// 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 crate::config::PeerConfig;
use crate::config::ServerConfig;
use crate::peer::PeerCommands;
use crate::peer::PeerStateMachine;
use crate::rib_manager::RibManager;
use crate::rib_manager::RibSnapshot;
use crate::rib_manager::RouteManagerCommands;
use crate::route_server;
use crate::route_server::proto::bgp_server_admin_service_server::BgpServerAdminServiceServer;
use crate::route_server::proto::route_service_server::RouteServiceServer;
use bgp_packet::constants::AddressFamilyIdentifier;
use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::SocketAddr;
use tokio::net::TcpListener;
use tokio::net::TcpStream;
use tokio::sync::mpsc::unbounded_channel;
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::oneshot;
use tokio_util::sync::CancellationToken;
use tracing::{info, warn};
use warp::Filter;
use warp::Reply;
// socket_listener starts listening on the given address, and passes clients that have
// made an inbound connection to the provided stream. It also implements logic for
// recreating the listener in the event that it fails.
// Notifier is sent the restult of the first attempt to start the listener.
async fn socket_listener(
c: UnboundedSender<(TcpStream, SocketAddr)>,
listen_addr: String,
notifier: oneshot::Sender<Result<(), String>>,
shutdown: CancellationToken,
) {
info!("Starting to listen on addr: {}", listen_addr);
let listener_result = TcpListener::bind(&listen_addr).await;
if let Err(e) = listener_result {
warn!("Listener for {} failed: {}", listen_addr, e.to_string());
match notifier.send(Err(e.to_string())) {
Ok(_) => {}
Err(e) => warn!(?e, "Failed to send notification of channel error"),
}
return;
}
let listener = listener_result.unwrap();
match notifier.send(Ok(())) {
Ok(_) => {}
Err(e) => warn!("Failed to send notification of channel ready: {:?}", e),
}
info!(listen_addr, "Spawned listner");
loop {
let conn = tokio::select! {
res = listener.accept() => res,
_ = shutdown.cancelled() => {
info!("Shutting down listener");
return;
}
};
info!(?conn, "New inbound connection");
match conn {
Ok((stream, addr)) => {
info!("Accepted socket connection from {}", addr);
match c.send((stream, addr)) {
Ok(_) => {}
Err(e) => {
warn!(
"Dropped connection from {} due to mpsc::channel failure: {}",
addr, e
);
}
}
}
Err(e) => {
warn!("Failed to accept connection: {}, aborting listener", e);
break;
}
}
}
}
async fn start_http_server(
manager4: UnboundedSender<RouteManagerCommands<Ipv4Addr>>,
manager6: UnboundedSender<RouteManagerCommands<Ipv6Addr>>,
peers: HashMap<String, UnboundedSender<PeerCommands>>,
listen_addr: SocketAddr,
shutdown: CancellationToken,
) -> Result<tokio::task::JoinHandle<()>, String> {
async fn manager_get_routes_handler<T: serde::ser::Serialize>(
channel: UnboundedSender<RouteManagerCommands<T>>,
) -> Result<impl warp::Reply, warp::Rejection> {
let (tx, rx) = tokio::sync::oneshot::channel::<RibSnapshot<T>>();
if let Err(e) = channel.send(RouteManagerCommands::DumpRib(tx)) {
warn!("Failed to send DumpRib request: {}", e);
return Err(warp::reject());
}
match rx.await {
Ok(result) => Ok(warp::reply::json(&result)),
Err(e) => {
warn!("Failed to get RIB from manager: {}", e);
Err(warp::reject())
}
}
}
// reset_peer_connection causes the PSM to close the connection, flush state, and reconnect to the peer.
async fn reset_peer_connection(
peer_name: String,
peers: HashMap<String, UnboundedSender<PeerCommands>>,
) -> Result<impl warp::Reply, warp::Rejection> {
if let Some(peer_sender) = peers.get(&peer_name) {
if let Err(e) = peer_sender.send(PeerCommands::ConnectionClosed()) {
Ok(warp::reply::with_status(
format!("Something went wrong: {}", e),
warp::http::StatusCode::INTERNAL_SERVER_ERROR,
)
.into_response())
} else {
Ok(warp::reply::html(
"Sent restart request to PeerStateMachine. Something might happen.",
)
.into_response())
}
} else {
Ok(
warp::reply::with_status("No such peer found!", warp::http::StatusCode::NOT_FOUND)
.into_response(),
)
}
}
/// peerz is a debugging endpoint for PeerStateMachines on this server.
async fn get_peerz(
peers: HashMap<String, UnboundedSender<PeerCommands>>,
) -> Result<impl warp::Reply, warp::Rejection> {
let mut result: String = "<!DOCTYPE html><body>".to_string();
for (peer_name, sender) in peers {
result += &format!("<h2>{}</h2><br/>", peer_name);
let (tx, rx) = oneshot::channel();
match sender.send(PeerCommands::GetStatus(tx)) {
Ok(()) => {}
Err(e) => {
warn!("Failed to send request to PSM channel: {}", e);
return Ok(warp::reply::with_status(
"Something went wrong!",
warp::http::StatusCode::INTERNAL_SERVER_ERROR,
)
.into_response());
}
}
match rx.await {
Ok(resp) => {
result += &format!("Peer state: <b>{:?}</b><br/>", resp.state);
}
Err(e) => {
warn!("error on rx from peer channel: {}", e);
return Ok(warp::reply::with_status(
"Something went wrong!",
warp::http::StatusCode::INTERNAL_SERVER_ERROR,
)
.into_response());
}
}
}
result += "</body></html>";
Ok(warp::http::Response::builder().body(result).into_response())
}
// Start the web server that has access to the rib managers so that it can expose the state.
let v4_mgr_filter = warp::any().map(move || manager4.clone());
let warp_v4_routes = warp::get()
.and(warp::path("ipv4"))
.and(warp::path("routes"))
.and(warp::path::end())
.and(v4_mgr_filter)
.and_then(manager_get_routes_handler);
let v6_mgr_filter = warp::any().map(move || manager6.clone());
let warp_v6_routes = warp::get()
.and(warp::path("ipv6"))
.and(warp::path("routes"))
.and(warp::path::end())
.and(v6_mgr_filter)
.and_then(manager_get_routes_handler);
let peers_map_filter = warp::any().map(move || peers.clone());
let peerz_route = warp::get()
.and(warp::path("peerz"))
.and(warp::path::end())
.and(peers_map_filter.clone())
.and_then(get_peerz);
let peers_restart_route = warp::post()
.and(warp::path("peerz"))
.and(warp::path::param())
.and(warp::path("restart"))
.and(warp::path::end())
.and(peers_map_filter)
.and_then(reset_peer_connection);
let routes = warp_v4_routes
.or(warp_v6_routes)
.or(peerz_route)
.or(peers_restart_route);
let (_, server) = warp::serve(routes)
.try_bind_with_graceful_shutdown(listen_addr, async move {
shutdown.cancelled().await;
})
.map_err(|e| e.to_string())?;
Ok(tokio::task::spawn(server))
}
/// Server encapsulates the behavior of the BGP speaker.
pub struct Server {
config: ServerConfig,
// shutdown is a channel that a
shutdown: CancellationToken,
// worker_handles contains the JoinHandle of tasks spawned by the server so that
// we can wait on them for shutdown.
worker_handles: Vec<tokio::task::JoinHandle<()>>,
mgr_v6: Option<UnboundedSender<RouteManagerCommands<Ipv6Addr>>>,
mgr_v4: Option<UnboundedSender<RouteManagerCommands<Ipv4Addr>>>,
}
impl Server {
pub fn new(config: ServerConfig) -> Server {
let shutdown = CancellationToken::new();
Server {
config,
shutdown,
worker_handles: vec![],
mgr_v4: None,
mgr_v6: None,
}
}
// start kicks off the BGP server
// wait_startup controls whether this function waits for the listeners to come up healthy
// before returning. This is useful in tests and other situations where we want to wait
// and then probe the endpoints.
pub async fn start(&mut self, wait_startup: bool) -> Result<(), String> {
// TODO: the following code spawns a bunch of asynchronous tasks, and it would be
// good to have a handle on the status of these tasks so that we can restart them
// or alert if they crash.
// Channel for passing newly established TCP streams to the dispatcher.
let (tcp_in_tx, mut tcp_in_rx): (UnboundedSender<(TcpStream, SocketAddr)>, _) =
tokio::sync::mpsc::unbounded_channel();
// For every address we are meant to listen on, we spawn a task that will listen on
// that address. This is so that if the listening socket breaks somehow, we can
// periodically retry to listen again.
for listen_addr in self.config.clone().listen_addrs {
info!("Starting listener for {}", listen_addr.to_string());
let sender = tcp_in_tx.clone();
let (ready_tx, ready_rx) = oneshot::channel();
let listen_handle = tokio::spawn({
let shutdown = self.shutdown.clone();
async move {
socket_listener(sender, listen_addr.to_string(), ready_tx, shutdown).await;
}
});
self.worker_handles.push(listen_handle);
if wait_startup {
let statup_result = ready_rx.await;
match statup_result {
Ok(_) => {}
Err(err) => return Err(format!("Failed to startup listener: {}", err)),
}
}
}
// Start the route manager for IPv6 and IPv4.
let (rp6_tx, rp6_rx) = unbounded_channel::<RouteManagerCommands<Ipv6Addr>>();
self.mgr_v6 = Some(rp6_tx.clone());
let mut rib_manager6: RibManager<Ipv6Addr> =
RibManager::<Ipv6Addr>::new(rp6_rx, self.shutdown.clone()).unwrap();
tokio::spawn(async move {
match rib_manager6.run().await {
Ok(_) => {}
Err(e) => {
warn!("RIBManager exited: {}", e);
}
}
});
let (rp4_tx, rp4_rx) = unbounded_channel::<RouteManagerCommands<Ipv4Addr>>();
self.mgr_v4 = Some(rp4_tx.clone());
let mut rib_manager4: RibManager<Ipv4Addr> =
RibManager::<Ipv4Addr>::new(rp4_rx, self.shutdown.clone()).unwrap();
tokio::spawn(async move {
match rib_manager4.run().await {
Ok(_) => {}
Err(e) => {
warn!("RIBManager exited: {}", e);
}
}
});
// Start a PeerStateMachine for every peer that is configured and store its channel so that
// we can communicate with it.
let mut peer_statemachines: HashMap<String, (PeerConfig, UnboundedSender<PeerCommands>)> =
HashMap::new();
for peer_config in &self.config.peers {
let (psm_tx, psm_rx) = unbounded_channel::<PeerCommands>();
match peer_config.afi {
AddressFamilyIdentifier::Ipv6 => {
let mut psm = PeerStateMachine::<Ipv6Addr>::new(
self.config.clone(),
peer_config.clone(),
psm_rx,
psm_tx.clone(),
rp6_tx.clone(),
self.shutdown.clone(),
);
self.worker_handles.push(tokio::spawn(async move {
psm.run().await;
}));
}
AddressFamilyIdentifier::Ipv4 => {
let mut psm = PeerStateMachine::<Ipv4Addr>::new(
self.config.clone(),
peer_config.clone(),
psm_rx,
psm_tx.clone(),
rp4_tx.clone(),
self.shutdown.clone(),
);
self.worker_handles.push(tokio::spawn(async move {
psm.run().await;
}));
}
_ => panic!("Unsupported address family: {}", peer_config.afi),
}
peer_statemachines.insert(peer_config.name.clone(), (peer_config.clone(), psm_tx));
}
let mut peer_chan_map: HashMap<String, UnboundedSender<PeerCommands>> = HashMap::new();
for (k, v) in &peer_statemachines {
peer_chan_map.insert(k.to_string(), v.1.clone());
}
// Start the HTTP server for debugging access.
if let Some(http_addr) = &self.config.http_addr {
let addr = http_addr.parse().unwrap();
start_http_server(
rp4_tx.clone(),
rp6_tx.clone(),
peer_chan_map.clone(),
addr,
self.shutdown.clone(),
)
.await
.unwrap();
}
// Start the gRPC server for streaming the RIB.
if let Some(grpc_addr) = &self.config.grpc_addr {
let addr = grpc_addr.parse().unwrap();
info!("Running gRPC RouteService on {}", addr);
let rs = route_server::RouteServer {
ip4_manager: rp4_tx.clone(),
ip6_manager: rp6_tx.clone(),
peer_state_machines: peer_chan_map,
};
let rs_svc = RouteServiceServer::new(rs.clone());
let adm_svc = BgpServerAdminServiceServer::new(rs);
tokio::spawn(async move {
if let Err(e) = tonic::transport::Server::builder()
.add_service(rs_svc)
.add_service(adm_svc)
.serve(addr)
.await
{
warn!("Failed to run gRPC server: {}", e);
}
});
}
// Event loop for processing inbound connections.
let shutdown = self.shutdown.clone();
self.worker_handles.push(tokio::spawn(async move {
loop {
let next = tokio::select! {
cmd = tcp_in_rx.recv() => cmd,
_ = shutdown.cancelled() => {
warn!("Peer connection dispatcher shutting down due to shutdown signal.");
return;
}
};
match next {
Some((socket, addr)) => {
let mut psm_opt: Option<UnboundedSender<PeerCommands>> = None;
for (name, handle) in &peer_statemachines {
if handle.0.ip == addr.ip() {
info!("Got connection for peer: {}", name);
psm_opt = Some(handle.1.clone());
}
}
if let Some(psm) = psm_opt {
psm.send(PeerCommands::NewConnection(socket)).unwrap();
} else {
info!("Dropping unrecognized connection from {}", addr);
}
}
None => {
warn!("Failed to read incoming connections, exiting");
break;
}
}
}
}));
Ok(())
}
pub async fn shutdown(&mut self) {
self.shutdown.cancel();
for handle in &mut self.worker_handles {
match handle.await {
Ok(_) => {}
Err(e) => {
warn!("Failed to shutdown task: {}", e);
}
}
}
}
}

122
crates/server/src/config.rs Normal file
View File

@ -0,0 +1,122 @@
// 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_packet::{
constants::{AddressFamilyIdentifier, SubsequentAddressFamilyIdentifier},
nlri::NLRI,
path_attributes::LargeCommunitiesPayload,
};
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[derive(Clone, Serialize, Deserialize)]
pub struct ServerConfig {
pub identifier: Ipv4Addr,
pub asn: u32,
pub hold_time: u16,
// The address to listen on for control plane gRPC connections.
// If unset the gRPC server is not started.
pub grpc_addr: Option<String>,
// The address to listen on for the debugging HTTP server.
// If unset the HTTP server is not started.
pub http_addr: Option<String>,
// The addresses to listen on for BGP peers.
pub listen_addrs: Vec<String>,
pub peers: Vec<PeerConfig>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PeerConfig {
/// A unique name for this peer.
pub name: String,
pub ip: IpAddr,
/// Optional port number to communicate with this peer.
pub port: Option<u16>,
/// Autonomous system number of the peer.
pub asn: u32,
pub afi: AddressFamilyIdentifier,
pub safi: SubsequentAddressFamilyIdentifier,
pub local_pref: u32,
// Announcements is a hardcoded list of BGP updates to send
// to the peer.
pub announcements: Vec<PrefixAnnouncement>,
/// filter_in is applied to every announcement received by the peer before being accepted into Loc-RIB.
pub filter_in: Vec<(FilterMatcher, FilterAction)>,
/// filter_out is applied to every entry from Loc-RIB and if evaluation passes, will be sent to Adj-RIBs-Out
pub filter_out: Vec<(FilterMatcher, FilterAction)>,
}
/// All the fields in a FilterMatcher must be matched if present for the action to be executed.
// Implementation note, it's probably more efficient to JIT compile the filter into machine code
// so only the relevant fields need to be checked.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FilterMatcher {
pub nlri: Option<NLRI>,
pub origin_asn: Option<u32>,
pub large_community: Option<LargeCommunitiesPayload>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum FilterAction {
Accept,
Reject,
Update(UpdateAction),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum UpdateAction {
AttachLargeCommunity(LargeCommunitiesPayload),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PrefixAnnouncement {
pub prefix: String,
/// Nexthop to be announced for this prefix.
pub nexthop: IpAddr,
/// Linklocal nexthop to be used for IPv6 announcements.
pub llnh: Option<Ipv6Addr>,
/// Path attributes
pub local_pref: Option<u32>,
/// Multi exit discriminator
pub med: Option<u32>,
/// Legacy communities [RFC 1997]
pub communities: Option<Vec<String>>,
/// Large communities [RFC 8092]
pub large_communities: Option<Vec<String>>,
}
impl Default for PrefixAnnouncement {
fn default() -> Self {
Self {
prefix: "::/0".to_owned(),
nexthop: IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
llnh: Default::default(),
local_pref: Default::default(),
med: Default::default(),
communities: Default::default(),
large_communities: Default::default(),
}
}
}

View File

@ -0,0 +1,60 @@
// 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 std::{net::Ipv4Addr, sync::Arc};
use bgp_packet::nlri::NLRI;
use bgp_packet::path_attributes::PathAttribute;
use chrono::{DateTime, Utc};
use crate::path::path_data::PathData;
/// RouteInfo encapsulates information received about a particular BGP route.
#[derive(Clone, Debug)]
pub struct RouteInfo<A> {
pub prefix: A,
pub prefixlen: u8,
pub nlri: NLRI,
/// accepted is true if the route was accepted.
pub accepted: bool,
/// rejection_reason contains the reason why a particular route was dropped.
pub rejection_reason: Option<String>,
/// Time at which this path was learned from the peer.
pub learned: DateTime<Utc>,
/// Time at which this path was last updated by the peer.
pub updated: DateTime<Utc>,
/// The current path attributes from the UPDATE message where this path
/// was learned.
pub path_attributes: Vec<PathAttribute>,
}
/// RouteUpdate is a type which encapsulates a newly learned, modified, or removed set of prefixes.
#[derive(Debug)]
pub enum RouteUpdate {
Announce((Vec<NLRI>, Arc<PathData>)),
Withdraw(RouteWithdraw),
}
#[derive(Debug)]
pub struct RouteWithdraw {
/// The peer identifier of the peer that sent the withdrawal.
pub peer_id: Ipv4Addr,
/// The prefixes which have been withdrawn.
pub prefixes: Vec<NLRI>,
}

View File

@ -0,0 +1,205 @@
use bgp_packet::{
nlri::NLRI,
path_attributes::{LargeCommunitiesPathAttribute, PathAttribute},
};
use crate::config::{FilterAction, FilterMatcher, UpdateAction};
pub struct FilterEvaluator {
filter_in: Vec<(FilterMatcher, FilterAction)>,
filter_out: Vec<(FilterMatcher, FilterAction)>,
}
impl FilterEvaluator {
pub fn new(
filter_in: Vec<(FilterMatcher, FilterAction)>,
filter_out: Vec<(FilterMatcher, FilterAction)>,
) -> Self {
Self {
filter_in,
filter_out,
}
}
fn check_rule_match(
matcher: &FilterMatcher,
path_attributes: &Vec<PathAttribute>,
as_path: &Vec<u32>,
nlri: &NLRI,
) -> bool {
if let Some(matcher_nlri) = &matcher.nlri {
if nlri != matcher_nlri {
return false;
}
}
if let (Some(matcher_origin_asn), Some(origin_asn)) = (matcher.origin_asn, as_path.last()) {
if matcher_origin_asn != *origin_asn {
return false;
}
}
if let Some(matcher_large_community) = &matcher.large_community {
let mut found = false;
for attribute in path_attributes {
if let PathAttribute::LargeCommunitiesPathAttribute(lcs) = attribute {
if lcs.values.iter().any(|lc| lc == matcher_large_community) {
found = true;
break;
}
}
}
if !found {
return false;
}
}
return true;
}
fn apply_update(update_action: &UpdateAction, path_attributes: &mut Vec<PathAttribute>) {
match update_action {
UpdateAction::AttachLargeCommunity(large_community) => {
let mut added_existing = false;
for path_attribute in &mut *path_attributes {
if let PathAttribute::LargeCommunitiesPathAttribute(lc_attr) = path_attribute {
lc_attr.values.push(large_community.clone());
added_existing = true;
}
}
if !added_existing {
path_attributes.push(PathAttribute::LargeCommunitiesPathAttribute(
LargeCommunitiesPathAttribute {
values: vec![large_community.clone()],
},
))
}
}
}
}
fn evaluate(
rules: &Vec<(FilterMatcher, FilterAction)>,
path_attributes: &mut Vec<PathAttribute>,
as_path: &Vec<u32>,
nlri: &NLRI,
) -> bool {
for rule in rules {
if Self::check_rule_match(&rule.0, path_attributes, as_path, nlri) {
match &rule.1 {
FilterAction::Accept => return true,
FilterAction::Reject => return false,
FilterAction::Update(update_action) => {
Self::apply_update(update_action, path_attributes)
}
}
}
}
// Default behavior is to deny.
return false;
}
/// evaluate_in checks if an announced route is eligible to be accepted into the Loc-RIB.
/// Note that this may change the path_attributes if a FilterAction requests to do so.
pub fn evaluate_in(
&self,
path_attributes: &mut Vec<PathAttribute>,
as_path: &Vec<u32>,
nlri: &NLRI,
) -> bool {
Self::evaluate(&self.filter_in, path_attributes, as_path, nlri)
}
/// evaluate_out checks if a route from the Loc-RIB is to be announced to a peer.
/// Note that this may change the path_attributes if a FilterAction requests to do so.
pub fn evaluate_out(
&self,
path_attributes: &mut Vec<PathAttribute>,
as_path: &Vec<u32>,
nlri: &NLRI,
) -> bool {
Self::evaluate(&self.filter_out, path_attributes, as_path, nlri)
}
}
#[cfg(test)]
mod tests {
use bgp_packet::nlri::NLRI;
use crate::config::{FilterAction, FilterMatcher};
use super::FilterEvaluator;
#[test]
fn test_simple_match_nlri() {
let nlri = NLRI::try_from("2001:db8::/48").unwrap();
let matcher = FilterEvaluator::new(
vec![(
FilterMatcher {
nlri: Some(nlri.clone()),
origin_asn: None,
large_community: None,
},
FilterAction::Accept,
)],
vec![],
);
assert!(matcher.evaluate_in(&mut vec![], &vec![], &nlri));
}
#[test]
fn test_simple_match_origin_asn() {
let matcher = FilterEvaluator::new(
vec![(
FilterMatcher {
nlri: None,
origin_asn: Some(65000),
large_community: None,
},
FilterAction::Accept,
)],
vec![],
);
assert!(matcher.evaluate_in(
&mut vec![],
&vec![65000],
&NLRI::try_from("2001:db8::/48").unwrap()
));
}
#[test]
fn test_targeted_deny() {
let bad_nlri = NLRI::try_from("2001:db8:bad::/48").unwrap();
let matcher = FilterEvaluator::new(
vec![
// Reject a specific prefix 2001:db8:bad::/48
(
FilterMatcher {
nlri: Some(bad_nlri.clone()),
origin_asn: None,
large_community: None,
},
FilterAction::Reject,
),
// Accept everything else.
(
FilterMatcher {
nlri: None,
origin_asn: None,
large_community: None,
},
FilterAction::Accept,
),
],
vec![],
);
assert!(!matcher.evaluate_in(&mut vec![], &vec![], &bad_nlri));
assert!(matcher.evaluate_in(
&mut vec![],
&vec![],
&NLRI::try_from("2001:db8:1234::/48").unwrap()
));
}
}

22
crates/server/src/lib.rs Normal file
View File

@ -0,0 +1,22 @@
// 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.
pub mod bgp_server;
pub mod config;
pub mod data_structures;
pub mod filter_eval;
pub mod path;
pub mod peer;
pub mod rib_manager;
pub mod route_server;

View File

@ -0,0 +1,3 @@
/// Contains structures for working with BGP paths (data associated with a route to a prefix).
pub mod path_data;
pub mod path_set;

View File

@ -0,0 +1,95 @@
use std::cmp::Ordering;
use chrono::{DateTime, Utc};
use serde::Serialize;
use bgp_packet::path_attributes::{OriginPathAttribute, PathAttribute};
use super::path_set::PathSource;
/// PathData is a structure to contain a specific route via one nexthop.
/// Note that currently there is an assumption that there is only
/// one route per peer per prefix, but when ADD-PATH support is added
/// this will no longer hold true.
#[derive(Debug, Clone, Serialize)]
pub struct PathData {
/// The origin through which this path was learned. This is set to EGP when learned from
/// another peer, set to IGP when statically configured or from another control plane.
pub origin: OriginPathAttribute,
/// The nexthop that traffic can be sent to.
pub nexthop: Vec<u8>,
/// Where this path was learned from.
pub path_source: PathSource,
/// The local pref of this path.
pub local_pref: u32,
/// The multi exit discriminator of this path.
pub med: u32,
/// The path of autonomous systems to the destination along this path.
pub as_path: Vec<u32>,
/// Path attributes received from the peer.
pub path_attributes: Vec<PathAttribute>,
/// When the path was learned.
pub learn_time: DateTime<Utc>,
}
impl PartialOrd for PathData {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PathData {
fn cmp(&self, other: &Self) -> Ordering {
// Compare local_pref.
match self.local_pref.cmp(&other.local_pref) {
Ordering::Equal => {}
ord => return ord,
}
// Prefer paths that are locally configured.
if matches!(self.path_source, PathSource::LocallyConfigured)
&& !matches!(other.path_source, PathSource::LocallyConfigured)
{
return Ordering::Less;
}
// Compare path length.
match self.as_path.len().cmp(&other.as_path.len()) {
Ordering::Equal => {}
ord => return ord,
}
// IGP < EGP < INCOMPLETE
match (self.origin as u8).cmp(&(other.origin as u8)) {
Ordering::Equal => {}
ord => return ord,
}
// MED lower is better, only checked if the announcing ASN is the same.
if let (Some(announcing_as_self), Some(announcing_as_other)) =
(self.as_path.last(), other.as_path.last())
{
if announcing_as_self == announcing_as_other && self.med < other.med {
return Ordering::Less;
}
}
// As a discriminator of last resort, prefer older routes.
self.learn_time.cmp(&other.learn_time)
}
}
impl PartialEq for PathData {
fn eq(&self, other: &Self) -> bool {
self.origin == other.origin
&& self.nexthop == other.nexthop
&& self.path_source == other.path_source
&& self.local_pref == other.local_pref
&& self.med == other.med
&& self.as_path == other.as_path
&& self.path_attributes == other.path_attributes
&& self.learn_time == other.learn_time
}
}
impl Eq for PathData {}

View File

@ -0,0 +1,132 @@
use std::{
collections::{btree_set, BTreeMap, BTreeSet},
net::Ipv4Addr,
sync::Arc,
};
use bgp_packet::nlri::NLRI;
use eyre::{bail, Result};
use serde::Serialize;
use super::path_data::PathData;
#[derive(Debug, Clone, Serialize)]
pub enum PathSource {
LocallyConfigured,
/// BGPPeer represents a path that has been learned from a BGP peer,
/// and contains the Router ID of the peer.
BGPPeer(Ipv4Addr),
}
impl PartialEq for PathSource {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::BGPPeer(l0), Self::BGPPeer(r0)) => l0 == r0,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct PathSet<A> {
addr: A,
prefixlen: u8,
nlri: NLRI,
/// Sorted map keyed by the BGP Identifier of the peer that sent the route.
peer_paths: BTreeMap<Ipv4Addr, Arc<PathData>>,
paths: BTreeSet<Arc<PathData>>,
}
impl<A> PathSet<A> {
pub fn new(addr: A, prefixlen: u8, nlri: NLRI) -> Self {
Self {
addr,
prefixlen,
nlri,
peer_paths: Default::default(),
paths: Default::default(),
}
}
pub fn addr<'a>(&'a self) -> &'a A {
&self.addr
}
pub fn prefixlen(&self) -> u8 {
self.prefixlen
}
pub fn nlri<'a>(&'a self) -> &'a NLRI {
&self.nlri
}
pub fn is_empty(&self) -> bool {
self.paths.is_empty()
}
pub fn len(&self) -> usize {
self.paths.len()
}
pub fn get_by_announcer(&self, announcer: &Ipv4Addr) -> Option<Arc<PathData>> {
self.peer_paths.get(announcer).cloned()
}
/// Inserts a PathData from a given announcer, returning a PathData if the best
/// route has been updated.
pub fn insert_pathdata(
&mut self,
announcer: &Ipv4Addr,
path_data: &Arc<PathData>,
) -> Option<Arc<PathData>> {
let previous_best = self.paths.first().cloned();
if let Some(existing) = self.peer_paths.get_mut(announcer) {
// Path exists already so we must first remove it from self.paths.
self.paths.remove(existing);
// Add the new path to self.paths.
self.paths.insert(path_data.clone());
// Update it in the peer_paths map.
*existing = path_data.clone();
} else {
// Path does not yet exist so we just add it in both structures.
self.paths.insert(path_data.clone());
self.peer_paths.insert(*announcer, path_data.clone());
}
let next_best = self.paths.first().cloned();
// If the best path has changed, return the new best.
if previous_best != next_best {
return next_best;
}
// Update has not changed the best path.
return None;
}
/// Removes a path from the PathSet.
pub fn remove_pathdata(
&mut self,
announcer: &Ipv4Addr,
nlri: &NLRI,
) -> Result<Option<Arc<PathData>>> {
let previous_best = self.paths.first().cloned();
if self.peer_paths.contains_key(&announcer) {
self.peer_paths.remove(&announcer);
self.paths
.retain(|e| e.path_source != PathSource::BGPPeer(*announcer));
} else {
bail!("cannot remove pathdata for NLRI {} from {}, as it is not present in PathSet.peer_paths",
nlri, announcer);
}
let next_best = self.paths.first().cloned();
// If the best path has changed, return the new best.
if previous_best != next_best {
return Ok(next_best);
}
// Update has not changed the best path.
return Ok(None);
}
/// Iterator over the paths contained in this PathSet.
pub fn path_iter<'a>(&'a self) -> btree_set::Iter<'a, Arc<PathData>> {
self.paths.iter()
}
}

1452
crates/server/src/peer.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,350 @@
// 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 crate::config::PeerConfig;
use crate::data_structures::RouteUpdate;
use crate::path::path_data::PathData;
use crate::path::path_set::PathSet;
use crate::path::path_set::PathSource;
use crate::peer::PeerCommands;
use std::collections::HashMap;
use std::convert::TryInto;
use std::sync::Arc;
use std::sync::Mutex;
use bgp_packet::nlri::NLRI;
use eyre::{bail, eyre};
use ip_network_table_deps_treebitmap::address::Address;
use serde::Serialize;
use tokio::sync::broadcast;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
use tokio_util::sync::CancellationToken;
use tracing::{info, trace, warn};
use super::data_structures::RouteWithdraw;
type PeerInterface = mpsc::UnboundedSender<PeerCommands>;
/// RibSnapshot contians a version number and the dump of all the routes.
#[derive(Debug, Serialize)]
pub struct RibSnapshot<A> {
pub epoch: u64,
pub routes: Vec<PathSet<A>>,
}
pub enum RouteManagerCommands<A> {
Update(RouteUpdate),
/// DumpRib returns the view of the RIB at the current epoch.
DumpRib(oneshot::Sender<RibSnapshot<A>>),
/// StreamRib will send all the routes currently in the RIB then stream updates.
StreamRib(
mpsc::UnboundedSender<(u64, PathSet<A>)>,
oneshot::Sender<broadcast::Receiver<(u64, PathSet<A>)>>,
),
}
pub struct RibManager<A: Address> {
mgr_rx: mpsc::UnboundedReceiver<RouteManagerCommands<A>>,
/// Peers configured on this server instance.
peers: HashMap<String, (PeerConfig, PeerInterface)>,
rib: ip_network_table_deps_treebitmap::IpLookupTable<A, Mutex<PathSet<A>>>,
epoch: u64,
// Handle for streaming updates to PathSets in the RIB.
pathset_streaming_handle: broadcast::Sender<(u64, PathSet<A>)>,
shutdown: CancellationToken,
}
impl<A: Address> RibManager<A>
where
NLRI: TryInto<A>,
<NLRI as TryInto<A>>::Error: ToString,
A: std::fmt::Debug + std::fmt::Display,
{
pub fn new(
chan: mpsc::UnboundedReceiver<RouteManagerCommands<A>>,
shutdown: CancellationToken,
) -> Result<Self, std::io::Error> {
// TODO: Make this a flag that can be configured.
let (pathset_tx, _) = broadcast::channel(10_000_000);
Ok(RibManager::<A> {
mgr_rx: chan,
peers: HashMap::new(),
rib: ip_network_table_deps_treebitmap::IpLookupTable::new(),
epoch: 0,
pathset_streaming_handle: pathset_tx,
shutdown,
})
}
pub async fn run(&mut self) -> eyre::Result<()> {
loop {
let next = tokio::select! {
cmd = self.mgr_rx.recv() => cmd,
_ = self.shutdown.cancelled() => {
warn!("RIB manager shutting down.");
return Ok(());
}
};
match next {
Some(mgr_cmd) => match mgr_cmd {
RouteManagerCommands::Update(update) => self.handle_update(update)?,
RouteManagerCommands::DumpRib(sender) => {
self.dump_rib(sender);
}
RouteManagerCommands::StreamRib(dump_sender, stream_sender) => {
self.stream_rib(dump_sender, stream_sender);
}
},
None => {
warn!("All senders of the manager channel have been dropped, manager exiting!");
bail!("Manager exited due to channel closure");
}
}
}
}
// dump_rib returns an atomic snapshot of the RIB at the current epoch.
fn dump_rib(&mut self, sender: tokio::sync::oneshot::Sender<RibSnapshot<A>>) {
info!("Starting RIB dump");
let mut snapshot = RibSnapshot::<A> {
epoch: self.epoch,
routes: vec![],
};
for pathset in self.rib.iter() {
snapshot.routes.push(pathset.2.lock().unwrap().clone());
}
if let Err(e) = sender.send(snapshot) {
trace!("Failed to send snapshot of RIB: {:?}", e);
}
info!("Done RIB dump");
}
/// stream_rib sends the current routes in the RIB back via dump_chan then closes it,
/// and subsequently returns a broadcast::Receiver for streaming updates.
fn stream_rib(
&mut self,
dump_sender: mpsc::UnboundedSender<(u64, PathSet<A>)>,
stream_sender: oneshot::Sender<broadcast::Receiver<(u64, PathSet<A>)>>,
) {
// Send all the routes currently in the RIB.
for pathset in self.rib.iter() {
if let Err(e) = dump_sender.send((self.epoch, pathset.2.lock().unwrap().clone())) {
warn!("Failed to send dump to client: {}", e);
}
}
drop(dump_sender);
// Create a new subscriber and return that to the caller to be notified of updates.
let subscriber = self.pathset_streaming_handle.subscribe();
if let Err(e) = stream_sender.send(subscriber) {
warn!("Failed to send subscriber in stream_rib: {:?}", e);
}
}
fn handle_update(&mut self, update: RouteUpdate) -> eyre::Result<()> {
match update {
RouteUpdate::Announce(announce) => self.handle_announce(announce),
RouteUpdate::Withdraw(withdraw) => self.handle_withdraw(withdraw),
}
}
fn handle_announce(&mut self, update: (Vec<NLRI>, Arc<PathData>)) -> eyre::Result<()> {
let peer_router_id = match update.1.path_source {
PathSource::LocallyConfigured => {
bail!("handle_announce should not be called with a LocallyConfigured route")
}
PathSource::BGPPeer(peer_id) => peer_id,
};
for nlri in update.0 {
// Increment the epoch on every NLRI processed.
self.epoch += 1;
let addr: A = nlri.clone().try_into().map_err(|e| eyre!(e.to_string()))?;
let prefixlen = nlri.prefixlen;
if let Some(path_set_wrapped) = self.rib.exact_match(addr, prefixlen.into()) {
let mut path_set = path_set_wrapped.lock().unwrap();
if let Some(new_best) = path_set.insert_pathdata(&peer_router_id, &update.1) {
for (_config, peer) in self.peers.values() {
peer.send(PeerCommands::Announce(RouteUpdate::Announce((
vec![nlri.clone()],
new_best.clone(),
))))?;
}
}
// Ignore errors sending due to no active receivers on the channel.
let _ = self
.pathset_streaming_handle
.send((self.epoch, path_set.clone()));
} else {
// This prefix has never been seen before, so add a new PathSet for it.
let mut path_set = PathSet::<A>::new(addr, nlri.prefixlen, nlri.clone());
if let Some(new_best) = path_set.insert_pathdata(&peer_router_id, &update.1) {
for (_config, peer) in self.peers.values() {
peer.send(PeerCommands::Announce(RouteUpdate::Announce((
vec![nlri.clone()],
new_best.clone(),
))))?;
}
} else {
bail!("Inconsistent state, adding new pathdata but no new best path");
}
self.rib
.insert(addr, prefixlen.into(), Mutex::new(path_set.clone()));
// Ignore errors sending due to no active receivers on the channel.
let _ = self.pathset_streaming_handle.send((self.epoch, path_set));
}
}
Ok(())
}
fn handle_withdraw(&mut self, update: RouteWithdraw) -> eyre::Result<()> {
for nlri in update.prefixes {
self.epoch += 1;
let addr: A = nlri.clone().try_into().map_err(|e| eyre!(e.to_string()))?;
let mut pathset_empty = false;
if let Some(path_set_wrapped) = self.rib.exact_match(addr, nlri.prefixlen.into()) {
let mut path_set = path_set_wrapped.lock().unwrap();
let removed = path_set.remove_pathdata(&update.peer_id, &nlri);
match removed {
Err(_e) => {
warn!(
"Got a withdrawal for route {} from {}, which was not in RIB",
nlri, update.peer_id
);
}
Ok(Some(new_best)) => {
// Communicate new_best to all peers.
for (_config, peer) in self.peers.values() {
peer.send(PeerCommands::Announce(RouteUpdate::Announce((
vec![nlri.clone()],
new_best.clone(),
))))?;
}
}
Ok(None) => {
// Do nothing here since we check below if the path is withdrawn.
}
}
// Ignore errors sending due to no active receivers on the channel.
let _ = self
.pathset_streaming_handle
.send((self.epoch, path_set.clone()));
if path_set.is_empty() {
pathset_empty = true;
for (_config, peer) in self.peers.values() {
peer.send(PeerCommands::Announce(RouteUpdate::Withdraw(
RouteWithdraw {
peer_id: update.peer_id,
prefixes: vec![nlri.clone()],
},
)))?;
}
}
} else {
warn!(
"Got a withdrawal for route {} from {}, which was not in RIB",
nlri, update.peer_id
);
}
if pathset_empty {
self.rib.remove(addr, nlri.prefixlen.into());
}
}
Ok(())
}
pub fn lookup_path_exact(&self, addr: A, prefixlen: u32) -> Option<PathSet<A>> {
self.rib
.exact_match(addr, prefixlen)
.map(|path| path.lock().unwrap().clone())
}
}
#[cfg(test)]
mod tests {
use crate::rib_manager::PathData;
use crate::rib_manager::PathSource;
use crate::rib_manager::RibManager;
use crate::rib_manager::RouteManagerCommands;
use crate::rib_manager::RouteUpdate;
use bgp_packet::constants::AddressFamilyIdentifier;
use bgp_packet::nlri::NLRI;
use bgp_packet::path_attributes::OriginPathAttribute;
use chrono::Utc;
use std::net::Ipv6Addr;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
#[test]
fn test_manager_process_single() {
let (_, rp_rx) = mpsc::unbounded_channel::<RouteManagerCommands<Ipv6Addr>>();
let mut rib_manager: RibManager<Ipv6Addr> =
RibManager::<Ipv6Addr>::new(rp_rx, CancellationToken::new()).unwrap();
let nexthop = Ipv6Addr::new(0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0x1);
// Send an update to the manager and check that it adds it to the RIB.
let path_data = PathData {
as_path: vec![65536],
local_pref: 0,
med: 0,
nexthop: nexthop.octets().to_vec(),
path_attributes: vec![],
origin: OriginPathAttribute::EGP,
path_source: PathSource::BGPPeer("1.2.3.4".parse().unwrap()),
learn_time: Utc::now(),
};
let prefixes = vec![NLRI {
afi: AddressFamilyIdentifier::Ipv6,
prefixlen: 32,
prefix: vec![0x20, 0x01, 0xd, 0xb8],
}];
// Manually drive the manager instead of calling run to not deal with async in tests.
assert!(rib_manager
.handle_update(RouteUpdate::Announce((prefixes, Arc::new(path_data))))
.is_ok());
let addr = Ipv6Addr::from_str("2001:db8::").unwrap();
let prefixlen: u32 = 32;
let lookup_result = rib_manager.lookup_path_exact(addr, prefixlen).unwrap();
assert_eq!(lookup_result.len(), 1);
let path_result = lookup_result
.get_by_announcer(&"1.2.3.4".parse().unwrap())
.unwrap();
assert_eq!(path_result.nexthop, nexthop.octets().to_vec());
}
}

View File

@ -0,0 +1,427 @@
// 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 crate::data_structures::RouteUpdate;
use crate::data_structures::RouteWithdraw;
use crate::path::path_data::PathData;
use crate::path::path_set::PathSet;
use crate::path::path_set::PathSource;
use crate::peer::PeerCommands;
use crate::rib_manager::RibSnapshot;
use crate::rib_manager::RouteManagerCommands;
use crate::route_server::proto::bgp_server_admin_service_server::BgpServerAdminService;
use crate::route_server::proto::route_service_server::RouteService;
use crate::route_server::proto::AddressFamily;
use crate::route_server::proto::AnnouncementRequest;
use crate::route_server::proto::AnnouncementResponse;
use crate::route_server::proto::DumpPathsRequest;
use crate::route_server::proto::DumpPathsResponse;
use crate::route_server::proto::Path;
use crate::route_server::proto::Prefix;
use crate::route_server::proto::StreamPathsRequest;
use bgp_packet::constants::AddressFamilyIdentifier;
use bgp_packet::nlri::NLRI;
use bgp_packet::path_attributes::OriginPathAttribute;
use chrono::Utc;
use proto::PeerStatusRequest;
use proto::PeerStatusResponse;
use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::sync::Arc;
use tokio::sync::broadcast;
use tokio::sync::mpsc;
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::oneshot;
use tokio_stream::wrappers::ReceiverStream;
use tonic::Response;
use tonic::Status;
use tracing::{info, warn};
pub mod proto {
tonic::include_proto!("bgpd.grpc");
}
#[derive(Clone)]
pub struct RouteServer {
pub ip4_manager: UnboundedSender<RouteManagerCommands<Ipv4Addr>>,
pub ip6_manager: UnboundedSender<RouteManagerCommands<Ipv6Addr>>,
pub peer_state_machines: HashMap<String, UnboundedSender<PeerCommands>>,
}
impl RouteServer {
async fn get_streaming_receiver<A>(
&self,
manager: UnboundedSender<RouteManagerCommands<A>>,
// dump_tx is used to receive the current state before streaming starts.
dump_tx: UnboundedSender<(u64, PathSet<A>)>,
) -> Result<broadcast::Receiver<(u64, PathSet<A>)>, Status> {
let (stream_tx, stream_rx) = oneshot::channel::<broadcast::Receiver<(u64, PathSet<A>)>>();
if let Err(e) = manager.send(RouteManagerCommands::StreamRib(dump_tx, stream_tx)) {
warn!("Failed to send StreamRib command to route manager: {}", e);
return Err(tonic::Status::internal(
"failed to communicate with route manager".to_owned(),
));
}
stream_rx
.await
.map_err(|e| tonic::Status::internal(e.to_string()))
}
/// Converts a rib_manager::PathSet into the proto format PathSet using the
/// appropriate address family.
fn transform_pathset<A>(mgr_ps: (u64, PathSet<A>), address_family: i32) -> proto::PathSet {
let mut proto_pathset = proto::PathSet {
epoch: mgr_ps.0,
prefix: Some(Prefix {
ip_prefix: mgr_ps.1.nlri().prefix.clone(),
prefix_len: mgr_ps.1.nlri().prefixlen.into(),
address_family,
}),
paths: vec![],
};
for path in mgr_ps.1.path_iter() {
let proto_path = Path {
as_path: path.as_path.clone(),
local_pref: path.local_pref,
med: path.med,
nexthop: path.nexthop.clone(),
peer_id: match path.path_source {
PathSource::LocallyConfigured => vec![],
PathSource::BGPPeer(peer) => peer.octets().to_vec(),
},
};
proto_pathset.paths.push(proto_path);
}
proto_pathset
}
}
#[tonic::async_trait]
impl BgpServerAdminService for RouteServer {
async fn peer_status(
&self,
_request: tonic::Request<PeerStatusRequest>,
) -> Result<Response<PeerStatusResponse>, Status> {
let mut result = PeerStatusResponse::default();
// Store the pending futures in a JoinSet to parallelize fetching.
let mut join_set = tokio::task::JoinSet::new();
for peer in &self.peer_state_machines {
let (tx, rx) = oneshot::channel();
if let Err(_e) = peer.1.send(PeerCommands::GetStatus(tx)) {
warn!(
peer = peer.0,
"Failed to send GetStatus request to PeerStateMachine"
);
continue;
}
join_set.spawn(rx);
}
while let Some(next) = join_set.join_next().await {
match next {
Ok(Ok(peer_status)) => result.peer_status.push(peer_status),
Ok(Err(e)) => return Err(Status::internal(format!("{}", e))),
Err(e) => return Err(Status::internal(format!("{}", e))),
}
}
Ok(Response::new(result))
}
async fn announce_to_peer(
&self,
request: tonic::Request<AnnouncementRequest>,
) -> Result<tonic::Response<AnnouncementResponse>, Status> {
info!("Processing announce_to_peer: {:?}", request);
let request = request.get_ref();
if let Some(peer) = self.peer_state_machines.get(&request.peer_name) {
let prefix = request
.prefix
.as_ref()
.ok_or(Status::invalid_argument("Missing prefix"))?;
let nlri = NLRI {
afi: AddressFamilyIdentifier::Ipv6,
prefix: prefix.ip_prefix.clone(),
prefixlen: prefix.prefix_len as u8,
};
if request.add {
let path_data = PathData {
origin: OriginPathAttribute::IGP,
nexthop: Vec::default(),
path_source: PathSource::LocallyConfigured,
local_pref: 100,
med: 100,
as_path: vec![210036],
path_attributes: Vec::default(),
learn_time: Utc::now(),
};
if let Err(e) = peer.send(PeerCommands::Announce(RouteUpdate::Announce((
vec![nlri],
Arc::new(path_data),
)))) {
warn!("Failed to send announcement to peer: {}", e);
return Err(Status::internal(format!(
"Failed to send message to PeerStateMachine: {}",
e
)));
}
} else {
let update = PeerCommands::Announce(RouteUpdate::Withdraw(RouteWithdraw {
peer_id: Ipv4Addr::new(0, 0, 0, 0),
prefixes: vec![nlri],
}));
if let Err(e) = peer.send(update) {
warn!("Failed to send withdrawal to peer: {}", e);
return Err(Status::internal(format!(
"Failed to send message to PeerStateMachine: {}",
e
)));
}
}
} else {
return Err(Status::invalid_argument(format!(
"No such peer: {}",
&request.peer_name,
)));
}
Ok(Response::new(AnnouncementResponse::default()))
}
}
#[tonic::async_trait]
impl RouteService for RouteServer {
async fn dump_paths(
&self,
request: tonic::Request<DumpPathsRequest>,
) -> Result<Response<DumpPathsResponse>, Status> {
let mut response = DumpPathsResponse {
epoch: 0,
path_sets: vec![],
};
let afi = AddressFamilyIdentifier::try_from(request.get_ref().address_family as u16)
.map_err(|e| tonic::Status::internal(e.to_string()))?;
match afi {
AddressFamilyIdentifier::Ipv4 => {
let (tx, rx) = tokio::sync::oneshot::channel::<RibSnapshot<Ipv4Addr>>();
if let Err(e) = self.ip4_manager.send(RouteManagerCommands::DumpRib(tx)) {
warn!("Failed to send DumpRib command to route manager: {}", e);
return Err(tonic::Status::internal(
"failed to communicate with route manager",
));
}
match rx.await {
Ok(result) => {
response.epoch = result.epoch;
for pathset in result.routes {
let mut proto_pathset = proto::PathSet {
epoch: result.epoch,
prefix: Some(Prefix {
ip_prefix: pathset.nlri().prefix.clone(),
prefix_len: pathset.nlri().prefixlen.into(),
address_family: AddressFamily::IPv4.into(),
}),
paths: vec![],
};
for path in pathset.path_iter() {
let proto_path = Path {
as_path: path.as_path.clone(),
local_pref: path.local_pref,
med: path.med,
nexthop: path.nexthop.clone(),
peer_id: match path.path_source {
PathSource::LocallyConfigured => vec![],
PathSource::BGPPeer(peer) => peer.octets().to_vec(),
},
};
proto_pathset.paths.push(proto_path);
}
response.path_sets.push(proto_pathset);
}
Ok(tonic::Response::new(response))
}
Err(e) => {
warn!("Failed to get response from route manager: {}", e);
return Err(tonic::Status::internal(
"failed to get response from route manager",
));
}
}
}
AddressFamilyIdentifier::Ipv6 => {
let (tx, rx) = tokio::sync::oneshot::channel::<RibSnapshot<Ipv6Addr>>();
if let Err(e) = self.ip6_manager.send(RouteManagerCommands::DumpRib(tx)) {
warn!("Failed to send DumpRib command to route manager: {}", e);
return Err(tonic::Status::internal(
"failed to communicate with route manager",
));
}
match rx.await {
Ok(result) => {
response.epoch = result.epoch;
for pathset in result.routes {
let mut proto_pathset = proto::PathSet {
epoch: result.epoch,
prefix: Some(Prefix {
ip_prefix: pathset.nlri().prefix.clone(),
prefix_len: pathset.nlri().prefixlen.into(),
address_family: AddressFamily::IPv6.into(),
}),
paths: vec![],
};
for path in pathset.path_iter() {
let proto_path = Path {
as_path: path.as_path.clone(),
local_pref: path.local_pref,
med: path.med,
nexthop: path.nexthop.clone(),
peer_id: match path.path_source {
PathSource::LocallyConfigured => vec![],
PathSource::BGPPeer(peer) => peer.octets().to_vec(),
},
};
proto_pathset.paths.push(proto_path);
}
response.path_sets.push(proto_pathset);
}
Ok(tonic::Response::new(response))
}
Err(e) => {
warn!("Failed to get response from route manager: {}", e);
return Err(tonic::Status::internal(
"failed to get response from route manager",
));
}
}
}
}
}
type StreamPathsStream = ReceiverStream<Result<proto::PathSet, Status>>;
async fn stream_paths(
&self,
request: tonic::Request<StreamPathsRequest>,
) -> Result<Response<Self::StreamPathsStream>, Status> {
match request.get_ref().address_family {
1 => {
let (dump_tx, mut dump_rx) = mpsc::unbounded_channel();
let mut receiver = self
.get_streaming_receiver::<Ipv4Addr>(self.ip4_manager.clone(), dump_tx)
.await?;
let (tx, rx) = mpsc::channel(10_000);
// Spawn a task for receving values from the manager and send them to the peer.
tokio::spawn(async move {
// Consume the dump before moving to the streamed paths.
while let Some(next) = dump_rx.recv().await {
let pathset =
RouteServer::transform_pathset(next, AddressFamily::IPv4.into());
if let Err(e) = tx.send(Ok(pathset)).await {
warn!("Failed to send path to peer: {}", e);
return;
}
}
loop {
let next = receiver.recv().await;
if let Err(e) = next {
warn!("Failed to get next streaming route from manager: {}", e);
let _ = tx
.send(Err(tonic::Status::internal(format!(
"Failed to get next route from manager: {}",
e
))))
.await;
return;
}
let route = next.unwrap();
if let Err(e) = tx
.send(Ok(RouteServer::transform_pathset(
route,
AddressFamily::IPv4.into(),
)))
.await
{
warn!("Failed to send streaming route to peer: {}", e);
return;
}
}
});
return Ok(Response::new(ReceiverStream::new(rx)));
}
2 => {
let (dump_tx, mut dump_rx) = mpsc::unbounded_channel();
let mut receiver = self
.get_streaming_receiver::<Ipv6Addr>(self.ip6_manager.clone(), dump_tx)
.await?;
let (tx, rx) = mpsc::channel(10_000);
// Spawn a task for receving values from the manager and send them to the peer.
tokio::spawn(async move {
// Consume the dump before moving to the streamed paths.
while let Some(next) = dump_rx.recv().await {
let pathset =
RouteServer::transform_pathset(next, AddressFamily::IPv4.into());
if let Err(e) = tx.send(Ok(pathset)).await {
warn!("Failed to send path to peer: {}", e);
return;
}
}
loop {
let next = receiver.recv().await;
if let Err(e) = next {
warn!("Failed to get next streaming route from manager: {}", e);
let _ = tx
.send(Err(tonic::Status::internal(format!(
"Failed to get next route from manager: {}",
e
))))
.await;
return;
}
let route = next.unwrap();
if let Err(e) = tx
.send(Ok(RouteServer::transform_pathset(
route,
AddressFamily::IPv6.into(),
)))
.await
{
warn!("Failed to send streaming route to peer: {}", e);
return;
}
}
});
return Ok(Response::new(ReceiverStream::new(rx)));
}
_ => return Err(tonic::Status::internal("Unknown address family")),
};
}
}

11
netlink/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

15
netlink/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "netlink"
version = "0.1.0"
authors = ["rayhaan"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = "0.2"
bytes = "1"
byteorder = "1.4.3"
log = "0.4"
hex = "0.4.3"
neli = "0.6.2"

33
netlink/README.md Normal file
View File

@ -0,0 +1,33 @@
# Netlink
This project was created to have an easy way to manipulate routes in the Linux kernel using the Netlink protocol.
There are some other libraries which provide similar functionality, but which were not offering the exact API which was desired to quickly modify routing state from control plane routing protocol daemons.
The API that this crate provides is (currently) specifically only for mutating routes using the following function:
```rust
// Create a handle which opens up a socket to the kernel.
let nl_iface = NetlinkInterface::new().unwrap();
// Modify a route
let af: u8 = 2; // Address family 1 is IPv6.
let dst_prefix = vec![0x20, 0x01, 0xdb, 0x8]; // 2001:db8::.
let dst_prefix_len = 32; // Specifying the prefix length is 32 bits.
let gateway_addr = vec![ // Nexthop / gateway to send packets to.
0x2a, 0x0d, 0xd7, 0x40, 0x01, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];
let rt_table = 200; // Install this route into table 200.
self.nl_iface.mutate_route(
true, // Add a route, false would be for removing a route.
af,
dst_prefix,
dst_prefix_len,
gateway_addr,
Some(rt_table)).unwrap();
```
Internally `RouteMessage` is used to represent a [rtmsg](https://man7.org/linux/man-pages/man7/rtnetlink.7.html) to the kernel, with a set of `RouteAttributes` that's attached to a particular `rtmsg`.

BIN
netlink/netlink.pcap Normal file

Binary file not shown.

172
netlink/src/constants.rs Normal file
View File

@ -0,0 +1,172 @@
// 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.
// This is direcly from https://docs.rs/libc/0.2.98/src/libc/unix/linux_like/linux/mod.rs.html#2449
// because when we build with musl libc some of these values are missing.
// linux/rtnetlink.h
pub const TCA_UNSPEC: libc::c_ushort = 0;
pub const TCA_KIND: libc::c_ushort = 1;
pub const TCA_OPTIONS: libc::c_ushort = 2;
pub const TCA_STATS: libc::c_ushort = 3;
pub const TCA_XSTATS: libc::c_ushort = 4;
pub const TCA_RATE: libc::c_ushort = 5;
pub const TCA_FCNT: libc::c_ushort = 6;
pub const TCA_STATS2: libc::c_ushort = 7;
pub const TCA_STAB: libc::c_ushort = 8;
pub const RTM_NEWLINK: u16 = 16;
pub const RTM_DELLINK: u16 = 17;
pub const RTM_GETLINK: u16 = 18;
pub const RTM_SETLINK: u16 = 19;
pub const RTM_NEWADDR: u16 = 20;
pub const RTM_DELADDR: u16 = 21;
pub const RTM_GETADDR: u16 = 22;
pub const RTM_NEWROUTE: u16 = 24;
pub const RTM_DELROUTE: u16 = 25;
pub const RTM_GETROUTE: u16 = 26;
pub const RTM_NEWNEIGH: u16 = 28;
pub const RTM_DELNEIGH: u16 = 29;
pub const RTM_GETNEIGH: u16 = 30;
pub const RTM_NEWRULE: u16 = 32;
pub const RTM_DELRULE: u16 = 33;
pub const RTM_GETRULE: u16 = 34;
pub const RTM_NEWQDISC: u16 = 36;
pub const RTM_DELQDISC: u16 = 37;
pub const RTM_GETQDISC: u16 = 38;
pub const RTM_NEWTCLASS: u16 = 40;
pub const RTM_DELTCLASS: u16 = 41;
pub const RTM_GETTCLASS: u16 = 42;
pub const RTM_NEWTFILTER: u16 = 44;
pub const RTM_DELTFILTER: u16 = 45;
pub const RTM_GETTFILTER: u16 = 46;
pub const RTM_NEWACTION: u16 = 48;
pub const RTM_DELACTION: u16 = 49;
pub const RTM_GETACTION: u16 = 50;
pub const RTM_NEWPREFIX: u16 = 52;
pub const RTM_GETMULTICAST: u16 = 58;
pub const RTM_GETANYCAST: u16 = 62;
pub const RTM_NEWNEIGHTBL: u16 = 64;
pub const RTM_GETNEIGHTBL: u16 = 66;
pub const RTM_SETNEIGHTBL: u16 = 67;
pub const RTM_NEWNDUSEROPT: u16 = 68;
pub const RTM_NEWADDRLABEL: u16 = 72;
pub const RTM_DELADDRLABEL: u16 = 73;
pub const RTM_GETADDRLABEL: u16 = 74;
pub const RTM_GETDCB: u16 = 78;
pub const RTM_SETDCB: u16 = 79;
pub const RTM_NEWNETCONF: u16 = 80;
pub const RTM_GETNETCONF: u16 = 82;
pub const RTM_NEWMDB: u16 = 84;
pub const RTM_DELMDB: u16 = 85;
pub const RTM_GETMDB: u16 = 86;
pub const RTM_NEWNSID: u16 = 88;
pub const RTM_DELNSID: u16 = 89;
pub const RTM_GETNSID: u16 = 90;
pub const RTM_F_NOTIFY: libc::c_uint = 0x100;
pub const RTM_F_CLONED: libc::c_uint = 0x200;
pub const RTM_F_EQUALIZE: libc::c_uint = 0x400;
pub const RTM_F_PREFIX: libc::c_uint = 0x800;
pub const RTA_UNSPEC: libc::c_ushort = 0;
pub const RTA_DST: libc::c_ushort = 1;
pub const RTA_SRC: libc::c_ushort = 2;
pub const RTA_IIF: libc::c_ushort = 3;
pub const RTA_OIF: libc::c_ushort = 4;
pub const RTA_GATEWAY: libc::c_ushort = 5;
pub const RTA_PRIORITY: libc::c_ushort = 6;
pub const RTA_PREFSRC: libc::c_ushort = 7;
pub const RTA_METRICS: libc::c_ushort = 8;
pub const RTA_MULTIPATH: libc::c_ushort = 9;
pub const RTA_PROTOINFO: libc::c_ushort = 10; // No longer used
pub const RTA_FLOW: libc::c_ushort = 11;
pub const RTA_CACHEINFO: libc::c_ushort = 12;
pub const RTA_SESSION: libc::c_ushort = 13; // No longer used
pub const RTA_MP_ALGO: libc::c_ushort = 14; // No longer used
pub const RTA_TABLE: libc::c_ushort = 15;
pub const RTA_MARK: libc::c_ushort = 16;
pub const RTA_MFC_STATS: libc::c_ushort = 17;
pub const RTN_UNSPEC: libc::c_uchar = 0;
pub const RTN_UNICAST: libc::c_uchar = 1;
pub const RTN_LOCAL: libc::c_uchar = 2;
pub const RTN_BROADCAST: libc::c_uchar = 3;
pub const RTN_ANYCAST: libc::c_uchar = 4;
pub const RTN_MULTICAST: libc::c_uchar = 5;
pub const RTN_BLACKHOLE: libc::c_uchar = 6;
pub const RTN_UNREACHABLE: libc::c_uchar = 7;
pub const RTN_PROHIBIT: libc::c_uchar = 8;
pub const RTN_THROW: libc::c_uchar = 9;
pub const RTN_NAT: libc::c_uchar = 10;
pub const RTN_XRESOLVE: libc::c_uchar = 11;
pub const RTPROT_UNSPEC: libc::c_uchar = 0;
pub const RTPROT_REDIRECT: libc::c_uchar = 1;
pub const RTPROT_KERNEL: libc::c_uchar = 2;
pub const RTPROT_BOOT: libc::c_uchar = 3;
pub const RTPROT_STATIC: libc::c_uchar = 4;
pub const RT_SCOPE_UNIVERSE: libc::c_uchar = 0;
pub const RT_SCOPE_SITE: libc::c_uchar = 200;
pub const RT_SCOPE_LINK: libc::c_uchar = 253;
pub const RT_SCOPE_HOST: libc::c_uchar = 254;
pub const RT_SCOPE_NOWHERE: libc::c_uchar = 255;
pub const RT_TABLE_UNSPEC: libc::c_uchar = 0;
pub const RT_TABLE_COMPAT: libc::c_uchar = 252;
pub const RT_TABLE_DEFAULT: libc::c_uchar = 253;
pub const RT_TABLE_MAIN: libc::c_uchar = 254;
pub const RT_TABLE_LOCAL: libc::c_uchar = 255;
pub const RTMSG_OVERRUN: u32 = libc::NLMSG_OVERRUN as u32;
pub const RTMSG_NEWDEVICE: u32 = 0x11;
pub const RTMSG_DELDEVICE: u32 = 0x12;
pub const RTMSG_NEWROUTE: u32 = 0x21;
pub const RTMSG_DELROUTE: u32 = 0x22;
pub const RTMSG_NEWRULE: u32 = 0x31;
pub const RTMSG_DELRULE: u32 = 0x32;
pub const RTMSG_CONTROL: u32 = 0x40;
pub const RTMSG_AR_FAILED: u32 = 0x51;
pub const MAX_ADDR_LEN: usize = 7;
pub const ARPD_UPDATE: libc::c_ushort = 0x01;
pub const ARPD_LOOKUP: libc::c_ushort = 0x02;
pub const ARPD_FLUSH: libc::c_ushort = 0x03;
pub const ATF_MAGIC: libc::c_int = 0x80;
// From https://docs.rs/libc/0.2.98/src/libc/unix/linux_like/linux/gnu/mod.rs.html#938
// linux/rtnetlink.h
pub const TCA_PAD: libc::c_ushort = 9;
pub const TCA_DUMP_INVISIBLE: libc::c_ushort = 10;
pub const TCA_CHAIN: libc::c_ushort = 11;
pub const TCA_HW_OFFLOAD: libc::c_ushort = 12;
pub const RTM_DELNETCONF: u16 = 81;
pub const RTM_NEWSTATS: u16 = 92;
pub const RTM_GETSTATS: u16 = 94;
pub const RTM_NEWCACHEREPORT: u16 = 96;
pub const RTM_F_LOOKUP_TABLE: libc::c_uint = 0x1000;
pub const RTM_F_FIB_MATCH: libc::c_uint = 0x2000;
pub const RTA_VIA: libc::c_ushort = 18;
pub const RTA_NEWDST: libc::c_ushort = 19;
pub const RTA_PREF: libc::c_ushort = 20;
pub const RTA_ENCAP_TYPE: libc::c_ushort = 21;
pub const RTA_ENCAP: libc::c_ushort = 22;
pub const RTA_EXPIRES: libc::c_ushort = 23;
pub const RTA_PAD: libc::c_ushort = 24;
pub const RTA_UID: libc::c_ushort = 25;
pub const RTA_TTL_PROPAGATE: libc::c_ushort = 26;

18
netlink/src/lib.rs Normal file
View File

@ -0,0 +1,18 @@
// 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.
pub mod constants;
pub mod netlink_interface;
pub mod packet;
pub mod traits;

109
netlink/src/main.rs Normal file
View File

@ -0,0 +1,109 @@
// 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.
//! This is just a small test program for testing the netlink integration.
use bytes::BytesMut;
use libc::c_void;
use netlink::packet::parse_netlink_message;
use netlink::packet::RouteAttribute;
use netlink::traits::NetlinkAttribute;
use netlink::traits::Serializable;
use netlink::packet::NetlinkHeader;
use netlink::packet::RouteMessage;
use std::convert::TryInto;
fn main() {
println!("Starting netlink dump!");
let nl_fd: libc::c_int;
unsafe {
// Establish a Netlink socket to the kernel.
nl_fd = libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, libc::NETLINK_ROUTE);
if nl_fd < 0 {
println!("Failed to create netlink socket: {}", nl_fd);
std::process::exit(1);
}
let sockaddr = libc::sockaddr {
sa_family: libc::AF_NETLINK as u16,
sa_data: [0i8; 14],
};
let bind_result = libc::bind(
nl_fd,
&sockaddr,
std::mem::size_of::<libc::sockaddr>().try_into().unwrap(),
);
if bind_result < 0 {
println!("Failed to create netlink socket: {}", nl_fd);
std::process::exit(1);
}
}
// Build a route dump message and send it to the kernel.
let mut nl_hdr = NetlinkHeader {
nlmsg_type: libc::RTM_NEWROUTE,
nlmsg_flags: (libc::NLM_F_REQUEST) as u16,
nlmsg_seq: 0xcafe,
nlmsg_pid: 0,
nlmsg_len: 0,
};
println!("message type: {}", nl_hdr.nlmsg_type);
let rt_msg = RouteMessage {
af: libc::AF_INET6 as u8,
dst_len: 32,
..Default::default()
};
let dst_attr = RouteAttribute::Dst(vec![0x20, 0x01, 0xdb, 0x8]);
let gateway_addr = RouteAttribute::Gateway(vec![
0x2a, 0x0d, 0xd7, 0x40, 0x1, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01,
]);
let len = std::mem::size_of::<NetlinkHeader>()
+ std::mem::size_of::<RouteMessage>()
+ 4_usize
+ dst_attr.payload_len() as usize
+ 4_usize
+ gateway_addr.payload_len() as usize;
nl_hdr.nlmsg_len = len as u32;
println!("Length of netlink message: {}", len);
let mut buf = BytesMut::with_capacity(4096);
nl_hdr.to_wire(&mut buf).unwrap();
rt_msg.to_wire(&mut buf).unwrap();
dst_attr.to_wire(&mut buf).unwrap();
gateway_addr.to_wire(&mut buf).unwrap();
unsafe {
let bytes_written = libc::write(nl_fd, buf.as_ptr() as *const c_void, buf.len());
println!("bytes_written: {}", bytes_written);
}
let mut resp = BytesMut::with_capacity(4096);
unsafe {
let bytes_read = libc::read(nl_fd, resp.as_mut_ptr() as *mut c_void, 4096);
resp.set_len(bytes_read.try_into().unwrap());
};
println!("Read bytes from netlink: {:?}", resp);
while resp.len() > 3 {
let (header, response) = parse_netlink_message(&mut resp).unwrap();
println!("Header: {:?} response: {:?}", header, response);
}
}

View File

@ -0,0 +1,211 @@
// 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 crate::packet::parse_netlink_message;
use crate::packet::NetlinkHeader;
use crate::packet::NetlinkPayload;
use crate::packet::RouteAttribute;
use crate::packet::RouteMessage;
use crate::traits::NetlinkAttribute;
use crate::traits::Serializable;
use bytes::BytesMut;
use libc::c_void;
use log::info;
use std::convert::TryInto;
use std::fmt;
use std::fmt::Formatter;
use std::net::Ipv6Addr;
pub struct NetlinkInterface {
nl_fd: libc::c_int,
seqno: u32,
buf: BytesMut,
}
#[derive(Debug, Clone)]
pub struct NetlinkError {
reason: String,
}
impl NetlinkError {
fn new(reason: String) -> NetlinkError {
NetlinkError { reason }
}
}
impl fmt::Display for NetlinkError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}", self.reason)
}
}
impl std::error::Error for NetlinkError {}
impl NetlinkInterface {
/// # Safety
/// This function is unsafe as it manually creates a netlink socket with the socket
/// system call.
pub unsafe fn new() -> Result<NetlinkInterface, Box<dyn std::error::Error>> {
let nl_fd = libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, libc::NETLINK_ROUTE);
if nl_fd < 0 {
return Err(Box::new(NetlinkError::new(format!(
"Error creating netlink socket: {}",
nl_fd
))));
}
let sockaddr = libc::sockaddr {
sa_family: libc::AF_NETLINK as u16,
sa_data: [0i8; 14],
};
let bind_result = libc::bind(
nl_fd,
&sockaddr,
std::mem::size_of::<libc::sockaddr>().try_into()?,
);
if bind_result < 0 {
return Err(Box::new(NetlinkError::new(format!(
"Failed to bind to netlink socket: {}",
bind_result
))));
}
Ok(NetlinkInterface {
nl_fd,
seqno: 0,
buf: BytesMut::with_capacity(4096),
})
}
pub fn mutate_route(
&mut self,
add: bool,
address_family: u8,
dst_prefix: Vec<u8>,
prefix_len: u8,
gateway: Vec<u8>,
table: Option<u32>,
) -> Result<(), Box<dyn std::error::Error>> {
info!(
"Mutate route: {:x?}/{prefix_len} via {:x?}",
dst_prefix, gateway
);
// XXX: Fix this we should reuse the buffer instead of allocating a new one
// each time. But there's some bug with how the size is being manipulated
// below that causes the buffer to get exhausted.
self.buf = BytesMut::with_capacity(4096);
let msg_type = match add {
true => libc::RTM_NEWROUTE,
false => libc::RTM_DELROUTE,
};
self.seqno += 1;
let mut nl_hdr = NetlinkHeader {
nlmsg_type: msg_type,
nlmsg_flags: (libc::NLM_F_REQUEST | libc::NLM_F_ACK) as u16,
nlmsg_seq: self.seqno,
nlmsg_pid: 0,
nlmsg_len: 0, // Filled in later.
};
let rt_msg = RouteMessage {
af: address_family,
dst_len: prefix_len,
..Default::default()
};
let dst_attr = RouteAttribute::Dst(dst_prefix);
let gateway_addr = RouteAttribute::Gateway(gateway);
nl_hdr.nlmsg_len = std::mem::size_of::<NetlinkHeader>() as u32
+ std::mem::size_of::<RouteMessage>() as u32
+ 4 // Attribute header
+ dst_attr.payload_len() as u32
+ 4 // Attribute header
+ gateway_addr.payload_len() as u32;
let mut table_attr: Option<RouteAttribute> = None;
if let Some(table_id) = table {
table_attr = Some(RouteAttribute::Table(table_id));
nl_hdr.nlmsg_len += 4 + table_attr.as_ref().unwrap().payload_len() as u32;
}
// self.buf.clear();
nl_hdr.to_wire(&mut self.buf)?;
rt_msg.to_wire(&mut self.buf)?;
dst_attr.to_wire(&mut self.buf)?;
gateway_addr.to_wire(&mut self.buf)?;
if let Some(table_attr) = table_attr {
table_attr.to_wire(&mut self.buf)?;
}
unsafe {
let bytes_written = libc::write(
self.nl_fd,
self.buf.as_ptr() as *const c_void,
self.buf.len(),
);
if bytes_written < 0 {
return Err(Box::new(NetlinkError::new(format!(
"Failed to write to netlink: {}",
bytes_written
))));
}
if bytes_written != self.buf.len() as isize {
return Err(Box::new(NetlinkError::new(
"Failed to write full message to netlink".to_string(),
)));
}
}
// Read the response back from netlink, should be a ACK or Error.
self.buf.clear();
unsafe {
let bytes_read = libc::read(self.nl_fd, self.buf.as_mut_ptr() as *mut c_void, 4906);
if bytes_read < 0 {
return Err(Box::new(NetlinkError::new(format!(
"Failed to read from netlink: {}",
bytes_read
))));
}
println!(
"bytes_read: {} (usz) {}, cap: {}",
bytes_read,
(bytes_read as usize),
self.buf.capacity()
);
// let read_view = self.buf.clone();
self.buf.set_len(bytes_read as usize);
let (_header, response) = parse_netlink_message(&mut self.buf)?;
match response {
NetlinkPayload::Error(e) => {
if e.error == 0 {
// Successful ACK of the route add.
Ok(())
} else {
Err(Box::new(NetlinkError::new(format!(
"Got netlink error: {:?}",
e
))))
}
}
_ => Err(Box::new(NetlinkError::new(format!(
"Got unexpected netlink message: {:?}",
response
)))),
}
}
}
}

614
netlink/src/packet.rs Normal file
View File

@ -0,0 +1,614 @@
// 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 crate::constants;
use crate::traits::NetlinkAttribute;
use crate::traits::Serializable;
use byteorder::ByteOrder;
use byteorder::NativeEndian;
use byteorder::ReadBytesExt;
use byteorder::WriteBytesExt;
use bytes::Buf;
use bytes::BufMut;
use bytes::BytesMut;
use log::info;
use std::convert::TryInto;
use std::fmt::Display;
use std::fmt::Formatter;
use std::io::Read;
use std::io::Write;
// XXX: Hack to make libc:: constants the right type.
const CONST_NETLINK_ROUTE: u16 = libc::NETLINK_ROUTE as u16;
const CONST_NETLINK_NOOP: u16 = libc::NLMSG_NOOP as u16;
const CONST_NETLINK_ERR: u16 = libc::NLMSG_ERROR as u16;
macro_rules! check_vec_len {
($payload:expr, $len:expr) => {
if $payload.len() != $len {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"expected {} bytes of payload, instead got {}",
$len,
$payload.len()
),
));
}
};
}
#[derive(Debug)]
pub enum NetlinkPayload {
Route(RouteMessage, Vec<RouteAttribute>),
Error(NetlinkError),
Noop(),
Done(),
}
pub fn parse_netlink_message(
buf: &mut BytesMut,
) -> Result<(NetlinkHeader, NetlinkPayload), std::io::Error> {
let header = NetlinkHeader::from_wire(buf)?;
let payload_len = header.nlmsg_len - std::mem::size_of::<NetlinkHeader>() as u32;
if payload_len > buf.len().try_into().unwrap() {
return Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
format!(
"Requested payload_len > buffer len: {} > {}",
payload_len,
buf.len()
),
));
}
info!(
"Calling split_to with payload_len={}, buf.len()={}",
payload_len,
buf.len()
);
let payload: &mut BytesMut = &mut buf.split_to(payload_len as usize);
match header.nlmsg_type {
CONST_NETLINK_ERR => {
let error = NetlinkError::from_wire(payload)?;
Ok((header, NetlinkPayload::Error(error)))
}
CONST_NETLINK_NOOP => Ok((header, NetlinkPayload::Noop())),
CONST_NETLINK_ROUTE => {
let (rt_msg, attrs) = take_route_message(payload)?;
Ok((header, NetlinkPayload::Route(rt_msg, attrs)))
}
libc::RTM_NEWROUTE | libc::RTM_GETROUTE | libc::RTM_DELROUTE => {
let (rt_msg, attrs) = take_route_message(payload)?;
Ok((header, NetlinkPayload::Route(rt_msg, attrs)))
}
unknown => Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
format!("Unknown netlink message type: {}", unknown),
)),
}
}
/// take_route_messaage attemts to parse a route message and attributes from the
/// provided buffer. It expects the header is already removed and the buffer is
/// trimmed of any padding.
pub fn take_route_message(
buf: &mut BytesMut,
) -> Result<(RouteMessage, Vec<RouteAttribute>), std::io::Error> {
let rt_msg = RouteMessage::from_wire(buf)?;
let mut attributes = Vec::<RouteAttribute>::new();
while buf.len() > 3 {
let attr = RouteAttribute::from_wire(buf)?;
attributes.push(attr);
}
Ok((rt_msg, attributes))
}
// NetlinkHeader is equivalent to nlmsghdr from the kernel.
// https://man7.org/linux/man-pages/man7/netlink.7.html
#[repr(C)]
#[derive(Debug)]
pub struct NetlinkHeader {
pub nlmsg_len: u32,
pub nlmsg_type: u16,
pub nlmsg_flags: u16,
pub nlmsg_seq: u32,
pub nlmsg_pid: u32,
}
impl Serializable<NetlinkHeader> for NetlinkHeader {
fn to_wire(&self, buf: &mut BytesMut) -> Result<(), std::io::Error> {
let mut writer = buf.writer();
writer.write_u32::<NativeEndian>(self.nlmsg_len)?;
writer.write_u16::<NativeEndian>(self.nlmsg_type)?;
writer.write_u16::<NativeEndian>(self.nlmsg_flags)?;
writer.write_u32::<NativeEndian>(self.nlmsg_seq)?;
writer.write_u32::<NativeEndian>(self.nlmsg_pid)?;
Ok(())
}
fn from_wire(buf: &mut BytesMut) -> Result<NetlinkHeader, std::io::Error> {
let mut reader = buf.reader();
let nlmsg_len = reader.read_u32::<NativeEndian>()?;
let nlmsg_type = reader.read_u16::<NativeEndian>()?;
let nlmsg_flags = reader.read_u16::<NativeEndian>()?;
let nlmsg_seq = reader.read_u32::<NativeEndian>()?;
let nlmsg_pid = reader.read_u32::<NativeEndian>()?;
Ok(NetlinkHeader {
nlmsg_len,
nlmsg_type,
nlmsg_flags,
nlmsg_seq,
nlmsg_pid,
})
}
}
impl Display for NetlinkHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"NetlinkHeader [ len: {}, type: {}, flags: {}, seq: {}, pid: {} ]",
self.nlmsg_len, self.nlmsg_type, self.nlmsg_flags, self.nlmsg_seq, self.nlmsg_pid
)
}
}
#[repr(C)]
#[derive(Debug)]
pub struct NetlinkError {
pub error: i32,
pub msg: NetlinkHeader,
// Other attributes that we're not parsing right now.
pub payload: Vec<u8>,
}
impl Serializable<NetlinkError> for NetlinkError {
fn to_wire(&self, buf: &mut BytesMut) -> Result<(), std::io::Error> {
buf.writer().write_i32::<NativeEndian>(self.error)?;
self.msg.to_wire(buf)?;
buf.writer().write_all(&self.payload)?;
Ok(())
}
fn from_wire(buf: &mut BytesMut) -> Result<NetlinkError, std::io::Error> {
let mut reader = buf.reader();
let error = reader.read_i32::<NativeEndian>()?;
let msg = NetlinkHeader::from_wire(buf)?;
let payload: Vec<u8> = buf.to_owned().to_vec();
Ok(NetlinkError {
error,
msg,
payload,
})
}
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct RouteMessage {
// address family
pub af: u8,
pub dst_len: u8,
pub src_len: u8,
pub tos: u8,
pub table: u8,
pub protocol: u8,
pub scope: u8,
pub r#type: u8,
pub flags: u32,
}
impl Serializable<RouteMessage> for RouteMessage {
fn to_wire(&self, buf: &mut BytesMut) -> Result<(), std::io::Error> {
let mut writer = buf.writer();
writer.write_u8(self.af)?;
writer.write_u8(self.dst_len)?;
writer.write_u8(self.src_len)?;
writer.write_u8(self.tos)?;
writer.write_u8(self.table)?;
writer.write_u8(self.protocol)?;
writer.write_u8(self.scope)?;
writer.write_u8(self.r#type)?;
writer.write_u32::<NativeEndian>(self.flags)?;
Ok(())
}
fn from_wire(buf: &mut BytesMut) -> Result<RouteMessage, std::io::Error> {
// Check that the length is at least the size of a RouteMessage
if buf.len() < std::mem::size_of::<RouteMessage>() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Buffer not large enough to read RouteMessage".to_string(),
));
}
let mut reader = buf.reader();
let af = reader.read_u8()?;
let dst_len = reader.read_u8()?;
let src_len = reader.read_u8()?;
let tos = reader.read_u8()?;
let table = reader.read_u8()?;
let protocol = reader.read_u8()?;
let scope = reader.read_u8()?;
let r#type = reader.read_u8()?;
let flags = reader.read_u32::<NativeEndian>()?;
Ok(RouteMessage {
af,
dst_len,
src_len,
tos,
table,
protocol,
scope,
r#type,
flags,
})
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum RouteAttribute {
Dst(Vec<u8>),
Src(Vec<u8>),
Iif(u32),
Oif(u32),
Gateway(Vec<u8>),
Priority(u32),
Prefsrc(u32),
Metrics(u32),
// TODO: support multipath attribute properly
Multipath(Vec<u8>),
Flow(u32),
// TODO: support cacheinfo properly
CacheInfo(Vec<u8>),
Table(u32),
Mark(u32),
// TODO: support mfc_stats properly
MfcStats(Vec<u8>),
// TODO: support via properly
Via(Vec<u8>),
NewDst(Vec<u8>),
Pref(u8),
EnacpType(u16),
Encap(Vec<u8>),
}
impl NetlinkAttribute for RouteAttribute {
fn attr_type(&self) -> u16 {
match self {
RouteAttribute::Dst(_) => constants::RTA_DST,
RouteAttribute::Src(_) => constants::RTA_SRC,
RouteAttribute::Iif(_) => constants::RTA_IIF,
RouteAttribute::Oif(_) => constants::RTA_OIF,
RouteAttribute::Gateway(_) => constants::RTA_GATEWAY,
RouteAttribute::Priority(_) => constants::RTA_PRIORITY,
RouteAttribute::Prefsrc(_) => constants::RTA_PREFSRC,
RouteAttribute::Metrics(_) => constants::RTA_METRICS,
RouteAttribute::Multipath(_) => constants::RTA_MULTIPATH,
RouteAttribute::Flow(_) => constants::RTA_FLOW,
RouteAttribute::CacheInfo(_) => constants::RTA_CACHEINFO,
RouteAttribute::Table(_) => constants::RTA_TABLE,
RouteAttribute::Mark(_) => constants::RTA_MARK,
RouteAttribute::MfcStats(_) => constants::RTA_MFC_STATS,
RouteAttribute::Via(_) => constants::RTA_VIA,
RouteAttribute::NewDst(_) => constants::RTA_NEWDST,
RouteAttribute::Pref(_) => constants::RTA_PREF,
RouteAttribute::EnacpType(_) => constants::RTA_ENCAP_TYPE,
RouteAttribute::Encap(_) => constants::RTA_ENCAP,
}
}
fn payload_len(&self) -> u16 {
match self {
RouteAttribute::Dst(dst) => dst.len() as u16,
RouteAttribute::Src(src) => src.len() as u16,
RouteAttribute::Iif(_) => 4,
RouteAttribute::Oif(_) => 4,
RouteAttribute::Gateway(gateway) => gateway.len() as u16,
RouteAttribute::Priority(_) => 4,
RouteAttribute::Prefsrc(_) => 4,
RouteAttribute::Metrics(_) => 4,
RouteAttribute::Multipath(multipath) => multipath.len() as u16,
RouteAttribute::Flow(_) => 4,
RouteAttribute::CacheInfo(cacheinfo) => cacheinfo.len() as u16,
RouteAttribute::Table(_) => 4,
RouteAttribute::Mark(_) => 4,
RouteAttribute::MfcStats(stats) => stats.len() as u16,
RouteAttribute::Via(via) => via.len() as u16,
RouteAttribute::NewDst(newdst) => newdst.len() as u16,
RouteAttribute::Pref(_) => 1,
RouteAttribute::EnacpType(_) => 2,
RouteAttribute::Encap(encap) => encap.len() as u16,
}
}
fn write_payload(&self, buf: &mut BytesMut) -> Result<(), std::io::Error> {
let mut writer = buf.writer();
match self {
RouteAttribute::Dst(dst) => buf.put(dst.as_slice()),
RouteAttribute::Src(src) => buf.put(src.as_slice()),
RouteAttribute::Iif(iif) => writer.write_u32::<NativeEndian>(*iif)?,
RouteAttribute::Oif(oif) => writer.write_u32::<NativeEndian>(*oif)?,
RouteAttribute::Gateway(gateway) => buf.put(gateway.as_slice()),
RouteAttribute::Priority(priority) => writer.write_u32::<NativeEndian>(*priority)?,
RouteAttribute::Prefsrc(prefsrc) => writer.write_u32::<NativeEndian>(*prefsrc)?,
RouteAttribute::Metrics(metrics) => writer.write_u32::<NativeEndian>(*metrics)?,
RouteAttribute::Multipath(multipath) => buf.put(multipath.as_slice()),
RouteAttribute::Flow(flow) => writer.write_u32::<NativeEndian>(*flow)?,
RouteAttribute::CacheInfo(cacheinfo) => buf.put(cacheinfo.as_slice()),
RouteAttribute::Table(table) => writer.write_u32::<NativeEndian>(*table)?,
RouteAttribute::Mark(mark) => writer.write_u32::<NativeEndian>(*mark)?,
RouteAttribute::MfcStats(stats) => buf.put(stats.as_slice()),
RouteAttribute::Via(via) => buf.put(via.as_slice()),
RouteAttribute::NewDst(newdst) => buf.put(newdst.as_slice()),
RouteAttribute::Pref(pref) => buf.put_u8(*pref),
RouteAttribute::EnacpType(encaptype) => writer.write_u16::<NativeEndian>(*encaptype)?,
RouteAttribute::Encap(encap) => buf.put(encap.as_slice()),
};
Ok(())
}
}
impl Serializable<RouteAttribute> for RouteAttribute {
fn to_wire(&self, buf: &mut BytesMut) -> Result<(), std::io::Error> {
// Write Type, Length, Value then pad to 4 byte boundary.
let mut writer = buf.writer();
writer.write_u16::<NativeEndian>(self.payload_len() + 4)?;
writer.write_u16::<NativeEndian>(self.attr_type())?;
self.write_payload(buf)?;
// Align the attribute to a four byte boundary.
let padding = (4 + self.payload_len()) % 4;
buf.put(vec![0u8; padding.into()].as_slice());
Ok(())
}
fn from_wire(buf: &mut BytesMut) -> Result<RouteAttribute, std::io::Error> {
let mut reader = buf.reader();
let attr_len: u16 = reader.read_u16::<NativeEndian>()?;
let attr_type: u16 = reader.read_u16::<NativeEndian>()?;
let padding = attr_len % 4;
if attr_len < 4 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"route attr cannot have length < 4",
));
}
let payload_len = attr_len - 4;
if buf.remaining() < payload_len.into() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Route attribute length was {} but buf has {} remaining",
payload_len,
buf.remaining()
),
));
}
let mut payload: Vec<u8> = vec![0u8; payload_len.into()];
let bytes_read = buf.reader().read(&mut payload)?;
if bytes_read != payload_len.into() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Failed to read {} bytes of payload, instead got {}",
payload_len, bytes_read
),
));
}
// Move buf past padding bytes.
buf.advance(padding.into());
match attr_type {
constants::RTA_DST => Ok(RouteAttribute::Dst(payload)),
constants::RTA_SRC => Ok(RouteAttribute::Src(payload)),
constants::RTA_IIF => {
check_vec_len!(payload, 4);
Ok(RouteAttribute::Iif(NativeEndian::read_u32(&payload)))
}
constants::RTA_OIF => {
check_vec_len!(payload, 4);
Ok(RouteAttribute::Oif(NativeEndian::read_u32(&payload)))
}
constants::RTA_GATEWAY => Ok(RouteAttribute::Gateway(payload)),
constants::RTA_PRIORITY => {
check_vec_len!(payload, 4);
Ok(RouteAttribute::Priority(NativeEndian::read_u32(&payload)))
}
constants::RTA_PREFSRC => {
check_vec_len!(payload, 4);
Ok(RouteAttribute::Prefsrc(NativeEndian::read_u32(&payload)))
}
constants::RTA_METRICS => {
check_vec_len!(payload, 4);
Ok(RouteAttribute::Metrics(NativeEndian::read_u32(&payload)))
}
constants::RTA_MULTIPATH => Ok(RouteAttribute::Multipath(payload)),
constants::RTA_FLOW => {
check_vec_len!(payload, 4);
Ok(RouteAttribute::Flow(NativeEndian::read_u32(buf)))
}
constants::RTA_CACHEINFO => Ok(RouteAttribute::CacheInfo(payload)),
constants::RTA_TABLE => {
check_vec_len!(payload, 4);
Ok(RouteAttribute::Table(NativeEndian::read_u32(&payload)))
}
constants::RTA_MARK => {
check_vec_len!(payload, 4);
Ok(RouteAttribute::Mark(NativeEndian::read_u32(&payload)))
}
constants::RTA_MFC_STATS => Ok(RouteAttribute::MfcStats(payload)),
constants::RTA_VIA => Ok(RouteAttribute::CacheInfo(payload)),
constants::RTA_NEWDST => Ok(RouteAttribute::CacheInfo(payload)),
constants::RTA_PREF => {
check_vec_len!(payload, 1);
Ok(RouteAttribute::Pref(payload[0]))
}
constants::RTA_ENCAP_TYPE => {
check_vec_len!(payload, 2);
Ok(RouteAttribute::EnacpType(NativeEndian::read_u16(&payload)))
}
constants::RTA_ENCAP => Ok(RouteAttribute::Encap(payload)),
_ => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Unknown attribute type: {}", attr_type),
))
}
}
}
}
#[cfg(test)]
mod tests {
use super::RouteAttribute;
use crate::packet::parse_netlink_message;
use crate::traits::Serializable;
use bytes::BytesMut;
#[test]
fn routemessage_roundtrip() {
let _payload = &[
0x74, 0x00, 0x00, 0x00, 0x18, 0x00, 0x02, 0x00, 0x35, 0x86, 0x00, 0x00, 0x31, 0x2f,
0x05, 0x00, 0x0a, 0x80, 0x00, 0x00, 0xfe, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x0f, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x08, 0x00, 0x06, 0x00, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x01, 0x00,
0x00, 0x00, 0x24, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x14, 0x00,
0x00, 0x00, 0x00, 0x00,
];
}
#[test]
fn rta_table() {
let payload: &[u8] = &[0x08, 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x00];
let attr = RouteAttribute::from_wire(&mut BytesMut::from(payload));
assert_eq!(RouteAttribute::Table(0xff), attr.unwrap());
}
#[test]
fn rta_dst() {
let payload: &[u8] = &[
0x14, 0x00, 0x01, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let attr = RouteAttribute::from_wire(&mut BytesMut::from(payload));
assert_eq!(
RouteAttribute::Dst(vec![255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,]),
attr.unwrap()
);
}
#[test]
fn parse_netlink_example_error() {
let payload_str = "58000000020000000319000022311300edffffff410000001800050003190000000000000a280000000000000000000009000100200116b8170014000500200108e009ff2000000000000000000208000f00c90000000000";
let payload = hex::decode(payload_str).expect("Test data hex decode failed");
let mut buf = BytesMut::from(payload.as_slice());
let res = parse_netlink_message(&mut buf).unwrap();
println!("Parsed netlink message: {:?}", res);
// assert_eq!(initial_capacity, buf.capacity());
}
// TODO: Clean this up to test only rtnetlink messages.
// This blob contains link add meessages.
// #[test]
// fn parse_netlink_message_invariants() {
// let payload: &[u8] = &[
// /* Netlink route*/ 0xf4, 0x03, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x65, 0xbf,
// 0xe2, 0x61, 0xe8, 0x48, 0x85, 0xaa, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00,
// 0x43, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x77, 0x6c,
// 0x70, 0x30, 0x73, 0x32, 0x30, 0x66, 0x33, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d, 0x00,
// 0xe8, 0x03, 0x00, 0x00, 0x05, 0x00, 0x10, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00,
// 0x11, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x8c, 0x05, 0x00, 0x00,
// 0x08, 0x00, 0x32, 0x00, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, 0x33, 0x00, 0x00, 0x09,
// 0x00, 0x00, 0x08, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1e, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1f, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00,
// 0x28, 0x00, 0xff, 0xff, 0x00, 0x00, 0x08, 0x00, 0x29, 0x00, 0x00, 0x00, 0x01, 0x00,
// 0x08, 0x00, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x21, 0x00, 0x01, 0x00,
// 0x00, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x6e, 0x6f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x00,
// 0x08, 0x00, 0x23, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x08, 0x00, 0x2f, 0x00, 0x2f, 0x00,
// 0x00, 0x00, 0x08, 0x00, 0x30, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x05, 0x00, 0x27, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00,
// 0x01, 0x00, 0x1c, 0x99, 0x57, 0xd9, 0x60, 0xa2, 0x00, 0x00, 0x0a, 0x00, 0x02, 0x00,
// 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xc4, 0x00, 0x17, 0x00, 0x9a, 0x2d,
// 0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x62, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xb6, 0x31, 0x63, 0x29, 0x03, 0x00, 0x00, 0x00, 0x15, 0xff, 0x5f, 0x7e, 0x01, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x07, 0x00, 0x9a, 0x2d,
// 0xbd, 0x00, 0xc2, 0x62, 0x53, 0x00, 0xb6, 0x31, 0x63, 0x29, 0x15, 0xff, 0x5f, 0x7e,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x15, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x2b, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x0a, 0x00, 0x36, 0x00, 0x1c, 0x99, 0x57, 0xd9, 0x60, 0xa2, 0x00, 0x00,
// 0x90, 0x01, 0x1a, 0x00, 0x88, 0x00, 0x02, 0x00, 0x84, 0x00, 0x01, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
// 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x04, 0x01, 0x0a, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x14, 0x00,
// 0x05, 0x00, 0xff, 0xff, 0x00, 0x00, 0xf2, 0xea, 0xf6, 0x00, 0x44, 0x66, 0x00, 0x00,
// 0xe8, 0x03, 0x00, 0x00, 0xe4, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
// 0x00, 0x00, 0x8c, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
// 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x0f,
// 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3a, 0x09, 0x00,
// 0x80, 0x51, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x10, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
// 0x01, 0x00, 0x00, 0x00, 0x60, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xee,
// 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
// 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x11, 0x00, 0x38, 0x00, 0x30, 0x30,
// 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x31, 0x34, 0x2e, 0x33, 0x00, 0x00, 0x00, 0x00,
// 0x08, 0x00, 0x39, 0x00, 0x70, 0x63, 0x69, 0x00,
// ];
// let mut buf = BytesMut::from(payload);
// let initial_capacity = buf.capacity();
// let res = parse_netlink_message(&mut buf);
// println!("Parsed netlink message: {:?}", res);
// assert_eq!(initial_capacity, buf.capacity());
// }
}

26
netlink/src/traits.rs Normal file
View File

@ -0,0 +1,26 @@
// 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 bytes::BytesMut;
pub trait Serializable<A> {
fn to_wire(&self, buf: &mut BytesMut) -> Result<(), std::io::Error>;
fn from_wire(buf: &mut BytesMut) -> Result<A, std::io::Error>;
}
pub trait NetlinkAttribute {
fn attr_type(&self) -> u16;
fn payload_len(&self) -> u16;
fn write_payload(&self, buf: &mut BytesMut) -> Result<(), std::io::Error>;
}

View File

@ -0,0 +1,20 @@
[package]
authors = ["Rayhaan Jaufeerally <rayhaan@rayhaan.ch>"]
edition = "2018"
license = "Apache-2.0"
name = "integration_tests"
version = "0.1.0"
[dependencies]
bgp_packet.workspace = true
bgp_server.workspace = true
bytes = "1.*"
libc = "0.2.126"
route_client.workspace = true
tokio = { version = "1.6.1", features = ["full"] }
tokio-util = { version = "0.7.10", features = ["codec"] }
tracing = "0.1"
tracing-subscriber = "0.2"
[dev-dependencies]
serial_test = "0.5.1"

View File

@ -0,0 +1 @@
edition = "2018"

View File

@ -0,0 +1,640 @@
// 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 std::io::{Read, Write};
use std::mem::size_of;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::TcpListener;
use std::net::TcpStream;
use std::net::{IpAddr, SocketAddrV6};
use std::os::unix::io::AsRawFd;
use std::time::Duration;
use tokio_util::codec::Decoder;
use tracing::info;
use bgp_packet::constants::{AddressFamilyIdentifier, SubsequentAddressFamilyIdentifier};
use bgp_packet::messages::BGPSubmessage;
use bgp_packet::traits::ParserContext;
use bgp_server::bgp_server::Server;
use bgp_server::config::{PeerConfig, PrefixAnnouncement, ServerConfig};
use bgp_server::route_server::proto::bgp_server_admin_service_client::BgpServerAdminServiceClient;
use bgp_server::route_server::proto::PeerStatusRequest;
#[macro_use]
extern crate serial_test;
fn init() {
match tracing_subscriber::fmt()
// .with_env_filter("server=trace,tokio=trace,basic_startup=trace")
.try_init()
{
Ok(()) => {}
Err(e) => {
eprintln!("Failed to setup tracing: {}", e);
}
}
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn test_bgp_listener_simple() {
init();
let sc = ServerConfig {
asn: 65535,
hold_time: 10,
identifier: Ipv4Addr::new(127, 0, 0, 1),
grpc_addr: None,
http_addr: None,
listen_addrs: vec!["[::]:9179".to_owned()],
peers: vec![],
};
let mut bgp_server = Server::new(sc);
bgp_server.start(true).await.unwrap();
// Try to connect to localhost:9179 and it should connect.
assert!(TcpStream::connect("[::1]:9179").is_ok());
bgp_server.shutdown().await;
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn test_bgp_listener_unknown_peer() {
init();
let sc = ServerConfig {
asn: 65535,
hold_time: 10,
identifier: Ipv4Addr::new(127, 0, 0, 1),
grpc_addr: None,
http_addr: None,
listen_addrs: vec!["[::]:9179".to_owned()],
peers: vec![],
};
let mut bgp_server = Server::new(sc);
bgp_server.start(true).await.unwrap();
// Try to connect to localhost:9179 and it should connect.
let conn = TcpStream::connect_timeout(&"[::1]:9179".parse().unwrap(), Duration::from_secs(3));
assert!(conn.is_ok());
let open_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x39, 0x01, 0x04, 0x00, 0x2a, 0x00, 0xb4, 0xd4, 0x19, 0x16, 0x26, 0x1c, 0x02,
0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, 0x02, 0x02, 0x80, 0x00, 0x02, 0x02, 0x02, 0x00,
0x02, 0x02, 0x46, 0x00, 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x00, 0x2a,
];
assert!(conn.as_ref().unwrap().write(open_msg_bytes).is_ok());
let mut buf = Vec::with_capacity(256);
assert_eq!(conn.unwrap().read(&mut buf).unwrap(), 0);
bgp_server.shutdown().await;
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn test_bgp_listener_known_peer() {
init();
let v6_addr: Ipv6Addr = "::1".parse().unwrap();
let sc = ServerConfig {
asn: 65535,
hold_time: 10,
identifier: Ipv4Addr::new(127, 0, 0, 1),
grpc_addr: None,
http_addr: None,
listen_addrs: vec!["[::]:9179".to_owned()],
peers: vec![PeerConfig {
afi: AddressFamilyIdentifier::Ipv6,
safi: SubsequentAddressFamilyIdentifier::Unicast,
asn: 8758,
ip: IpAddr::V6(v6_addr),
announcements: vec![],
name: "local-test-peer".to_string(),
local_pref: 100,
port: None,
filter_in: Vec::default(),
filter_out: Vec::default(),
}],
};
let mut bgp_server = Server::new(sc);
bgp_server.start(true).await.unwrap();
// Try to connect to localhost:9179 and it should connect.
let mut conn =
TcpStream::connect_timeout(&"[::1]:9179".parse().unwrap(), Duration::from_secs(3)).unwrap();
// Make the stream blocking to be able to handle it easily in tests.
conn.set_nonblocking(false).unwrap();
conn.set_read_timeout(None).unwrap();
let open_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x35, 0x01, 0x04, 0x22, 0x36, 0x00, 0xb4, 0xd4, 0x19, 0x1b, 0x2d, 0x18, 0x02,
0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x80, 0x00,
0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x22, 0x36,
];
assert!(conn.write_all(open_msg_bytes).is_ok());
let mut open_buf = vec![0u8; 65536];
let _ = conn.read(&mut open_buf).unwrap();
let mut codec = bgp_packet::messages::Codec {
ctx: ParserContext {
four_octet_asn: None,
nlri_mode: None,
},
};
let response_open_msg = codec
.decode(&mut bytes::BytesMut::from(open_buf.as_slice()))
.unwrap();
info!("Response message is: {:?}", response_open_msg);
match response_open_msg.unwrap().payload {
BGPSubmessage::OpenMessage(_open) => {}
_ => {
unreachable!();
}
}
// Check that the server sends a keepalive after the open message.
let mut ka_buf = vec![0u8; 65536];
let _ = conn.read(&mut ka_buf).unwrap();
let response_ka_message = codec
.decode(&mut bytes::BytesMut::from(ka_buf.as_slice()))
.unwrap();
match response_ka_message.unwrap().payload {
BGPSubmessage::KeepaliveMessage(_ka) => {}
_ => {
unreachable!();
}
}
bgp_server.shutdown().await;
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn test_bgp_peer_statemachine_outbound_conn() {
init();
let v6_addr: Ipv6Addr = "::1".parse().unwrap();
// Listen on some arbitrary port and put that port into the config for the server to dial out to.
let listener = TcpListener::bind("[::1]:0".parse::<SocketAddrV6>().unwrap()).unwrap();
info!("Listener is listening on: {:?}", listener.local_addr());
let port: u16 = listener.local_addr().unwrap().port();
let sc = ServerConfig {
asn: 65535,
hold_time: 10,
identifier: Ipv4Addr::new(127, 0, 0, 1),
grpc_addr: None,
http_addr: None,
listen_addrs: vec!["[::]:9179".to_owned()],
peers: vec![PeerConfig {
afi: AddressFamilyIdentifier::Ipv6,
safi: SubsequentAddressFamilyIdentifier::Unicast,
asn: 8758,
ip: IpAddr::V6(v6_addr),
port: Some(port),
announcements: vec![],
name: "local-test-peer".to_string(),
local_pref: 100,
filter_in: Vec::default(),
filter_out: Vec::default(),
}],
};
let mut bgp_server = Server::new(sc);
bgp_server.start(true).await.unwrap();
// Wait for the connection from the bgp_server.
info!("Waiting for connection in test");
let (mut conn, _) = listener.accept().unwrap();
info!("Got a connection in test");
let open_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x35, 0x01, 0x04, 0x22, 0x36, 0x00, 0xb4, 0xd4, 0x19, 0x1b, 0x2d, 0x18, 0x02,
0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x80, 0x00,
0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x22, 0x36,
];
assert!(conn.write_all(open_msg_bytes).is_ok());
let mut open_buf = vec![0u8; 65536];
let _ = conn.read(&mut open_buf).unwrap();
let mut codec = bgp_packet::messages::Codec {
ctx: ParserContext {
four_octet_asn: None,
nlri_mode: None,
},
};
let response_open_msg = codec
.decode(&mut bytes::BytesMut::from(open_buf.as_slice()))
.unwrap();
info!("Response message is: {:?}", response_open_msg);
match response_open_msg.unwrap().payload {
BGPSubmessage::OpenMessage(_open) => {}
_ => {
unreachable!();
}
}
// Check that the server sends a keepalive after the open message.
let mut ka_buf = vec![0u8; 65536];
let _ = conn.read(&mut ka_buf).unwrap();
let response_ka_message = codec
.decode(&mut bytes::BytesMut::from(ka_buf.as_slice()))
.unwrap();
match response_ka_message.unwrap().payload {
BGPSubmessage::KeepaliveMessage(_ka) => {}
_ => {
unreachable!();
}
}
bgp_server.shutdown().await;
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
// Check that reconnecting to a connection that was previously established works.
async fn test_bgp_peer_statemachine_outbound_reconnection() {
init();
let v6_addr: Ipv6Addr = "::1".parse().unwrap();
// Listen on some arbitrary port and put that port into the config for the server to dial out to.
let listener = TcpListener::bind("[::1]:0".parse::<SocketAddrV6>().unwrap()).unwrap();
info!("Listener is listening on: {:?}", listener.local_addr());
let port: u16 = listener.local_addr().unwrap().port();
let sc = ServerConfig {
asn: 65535,
hold_time: 10,
identifier: Ipv4Addr::new(127, 0, 0, 1),
grpc_addr: None,
http_addr: None,
listen_addrs: vec!["[::]:9179".to_owned()],
peers: vec![PeerConfig {
afi: AddressFamilyIdentifier::Ipv6,
safi: SubsequentAddressFamilyIdentifier::Unicast,
asn: 8758,
ip: IpAddr::V6(v6_addr),
port: Some(port),
announcements: vec![],
name: "local-test-peer".to_string(),
local_pref: 100,
filter_in: Vec::default(),
filter_out: Vec::default(),
}],
};
let mut bgp_server = Server::new(sc);
bgp_server.start(true).await.unwrap();
// Wait for the connection from the bgp_server.
info!("Waiting for connection in test");
let (mut conn, _) = listener.accept().unwrap();
info!("Got a connection in test");
let open_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x35, 0x01, 0x04, 0x22, 0x36, 0x00, 0xb4, 0xd4, 0x19, 0x1b, 0x2d, 0x18, 0x02,
0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x80, 0x00,
0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x22, 0x36,
];
assert!(conn.write_all(open_msg_bytes).is_ok());
let mut open_buf = vec![0u8; 65536];
let _ = conn.read(&mut open_buf).unwrap();
let mut codec = bgp_packet::messages::Codec {
ctx: ParserContext {
four_octet_asn: None,
nlri_mode: None,
},
};
let response_open_msg = codec
.decode(&mut bytes::BytesMut::from(open_buf.as_slice()))
.unwrap();
info!("Response message is: {:?}", response_open_msg);
match response_open_msg.unwrap().payload {
BGPSubmessage::OpenMessage(_open) => {}
_ => {
unreachable!();
}
}
// Check that the server sends a keepalive after the open message.
let mut ka_buf = vec![0u8; 65536];
let _ = conn.read(&mut ka_buf).unwrap();
let response_ka_message = codec
.decode(&mut bytes::BytesMut::from(ka_buf.as_slice()))
.unwrap();
match response_ka_message.unwrap().payload {
BGPSubmessage::KeepaliveMessage(_ka) => {}
_ => {
unreachable!();
}
}
conn.shutdown(std::net::Shutdown::Both).unwrap();
// Expect that the other side reconnects to re-establish the connection.
info!("Waiting for re-connection in test");
let (mut conn, _) = listener.accept().unwrap();
info!("Got the re-connection in test");
let open_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x35, 0x01, 0x04, 0x22, 0x36, 0x00, 0xb4, 0xd4, 0x19, 0x1b, 0x2d, 0x18, 0x02,
0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x80, 0x00,
0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x22, 0x36,
];
assert!(conn.write_all(open_msg_bytes).is_ok());
let mut open_buf = vec![0u8; 65536];
let _ = conn.read(&mut open_buf).unwrap();
let mut codec = bgp_packet::messages::Codec {
ctx: ParserContext {
four_octet_asn: None,
nlri_mode: None,
},
};
let response_open_msg = codec
.decode(&mut bytes::BytesMut::from(open_buf.as_slice()))
.unwrap();
info!("Response message is: {:?}", response_open_msg);
match response_open_msg.unwrap().payload {
BGPSubmessage::OpenMessage(_open) => {}
_ => {
unreachable!();
}
}
// Check that the server sends a keepalive after the open message.
let mut ka_buf = vec![0u8; 65536];
let _ = conn.read(&mut ka_buf).unwrap();
let response_ka_message = codec
.decode(&mut bytes::BytesMut::from(ka_buf.as_slice()))
.unwrap();
match response_ka_message.unwrap().payload {
BGPSubmessage::KeepaliveMessage(_ka) => {}
_ => {
unreachable!();
}
}
bgp_server.shutdown().await;
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn test_bgp_listener_known_peer_inbound_reconnection() {
init();
let v6_addr: Ipv6Addr = "::1".parse().unwrap();
let sc = ServerConfig {
asn: 65535,
hold_time: 10,
identifier: Ipv4Addr::new(127, 0, 0, 1),
grpc_addr: None,
http_addr: None,
listen_addrs: vec!["[::]:9179".to_owned()],
peers: vec![PeerConfig {
afi: AddressFamilyIdentifier::Ipv6,
safi: SubsequentAddressFamilyIdentifier::Unicast,
asn: 8758,
ip: IpAddr::V6(v6_addr),
announcements: vec![],
name: "local-test-peer".to_string(),
local_pref: 100,
port: None,
filter_in: Vec::default(),
filter_out: Vec::default(),
}],
};
let mut bgp_server = Server::new(sc);
bgp_server.start(true).await.unwrap();
// Try to connect to localhost:9179 and it should connect.
let mut conn =
TcpStream::connect_timeout(&"[::1]:9179".parse().unwrap(), Duration::from_secs(3)).unwrap();
// Make the stream blocking to be able to handle it easily in tests.
conn.set_nonblocking(false).unwrap();
conn.set_read_timeout(None).unwrap();
// Unsafe set linger: simulate a broken TCP stream by setting linger with a deadline of 0.
// This causes a RST packet to be sent instead of a FIN, which means that the other side
// will exercise the error path.
unsafe {
let val: libc::linger = libc::linger {
l_onoff: 1,
l_linger: 0,
};
let ret_val = libc::setsockopt(
conn.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_LINGER,
&val as *const libc::linger as *const libc::c_void,
size_of::<libc::linger>() as libc::socklen_t,
);
assert!(ret_val == 0);
}
let open_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x35, 0x01, 0x04, 0x22, 0x36, 0x00, 0xb4, 0xd4, 0x19, 0x1b, 0x2d, 0x18, 0x02,
0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x80, 0x00,
0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x22, 0x36,
];
assert!(conn.write_all(open_msg_bytes).is_ok());
let mut open_buf = vec![0u8; 65536];
let _ = conn.read(&mut open_buf).unwrap();
let mut codec = bgp_packet::messages::Codec {
ctx: ParserContext {
four_octet_asn: None,
nlri_mode: None,
},
};
let response_open_msg = codec
.decode(&mut bytes::BytesMut::from(open_buf.as_slice()))
.unwrap();
info!("Response message is: {:?}", response_open_msg);
match response_open_msg.unwrap().payload {
BGPSubmessage::OpenMessage(_open) => {}
_ => {
unreachable!();
}
}
// Check that the server sends a keepalive after the open message.
let mut ka_buf = vec![0u8; 65536];
let _ = conn.read(&mut ka_buf).unwrap();
let response_ka_message = codec
.decode(&mut bytes::BytesMut::from(ka_buf.as_slice()))
.unwrap();
match response_ka_message.unwrap().payload {
BGPSubmessage::KeepaliveMessage(_ka) => {}
_ => {
unreachable!();
}
}
drop(conn);
// Try to connect to localhost:9179 and it should connect and send the OPEN message.
let mut conn =
TcpStream::connect_timeout(&"[::1]:9179".parse().unwrap(), Duration::from_secs(3)).unwrap();
let open_msg_bytes: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x35, 0x01, 0x04, 0x22, 0x36, 0x00, 0xb4, 0xd4, 0x19, 0x1b, 0x2d, 0x18, 0x02,
0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x80, 0x00,
0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x22, 0x36,
];
assert!(conn.write_all(open_msg_bytes).is_ok());
let mut open_buf = vec![0u8; 65536];
conn.set_read_timeout(Some(Duration::from_secs(3))).unwrap();
let _ = conn.read(&mut open_buf).unwrap();
let mut codec = bgp_packet::messages::Codec {
ctx: ParserContext {
four_octet_asn: None,
nlri_mode: None,
},
};
let response_open_msg = codec
.decode(&mut bytes::BytesMut::from(open_buf.as_slice()))
.unwrap();
info!("Response message is: {:?}", response_open_msg);
match response_open_msg.unwrap().payload {
BGPSubmessage::OpenMessage(_open) => {}
_ => {
unreachable!();
}
}
info!("Reconnection successful");
bgp_server.shutdown().await;
}
// Spawn two instances of the BGP server and make them peer with each other and exchange a route.
// We then check that the route is accepted by the receiving peer.
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn test_multi_instance_announce() {
init();
let v6_addr: Ipv6Addr = "::1".parse().unwrap();
let config_a = ServerConfig {
asn: 65535,
hold_time: 3,
identifier: Ipv4Addr::new(127, 0, 0, 1),
grpc_addr: Some("[::]:9181".to_owned()),
http_addr: None,
listen_addrs: vec!["[::]:9179".to_owned()],
peers: vec![PeerConfig {
afi: AddressFamilyIdentifier::Ipv6,
safi: SubsequentAddressFamilyIdentifier::Unicast,
asn: 65536,
ip: IpAddr::V6(v6_addr),
port: Some(9180),
announcements: vec![PrefixAnnouncement {
prefix: "2001:db8:babe::/48".to_owned(),
nexthop: "2001:db8::1".parse().unwrap(),
local_pref: Some(100),
med: Some(100),
..Default::default()
}],
name: "config-b-peer".to_string(),
local_pref: 100,
filter_in: Vec::default(),
filter_out: Vec::default(),
}],
};
let mut config_b = config_a.clone();
config_b.asn = 65536;
config_b.listen_addrs = vec!["[::]:9180".to_owned()];
config_b.grpc_addr = Some("[::]:9182".to_owned());
config_b.peers[0].asn = 65535;
config_b.peers[0].port = Some(9179);
config_b.peers[0].name = "config-a-peer".to_string();
let mut server_a = Server::new(config_a);
server_a.start(true).await.unwrap();
let mut server_b = Server::new(config_b);
server_b.start(true).await.unwrap();
// Connect to the grpc endpoint of server_b and check until the peer is healthy.
let mut stub = BgpServerAdminServiceClient::connect("http://[::1]:9182")
.await
.unwrap();
let mut established = false;
for _ in 1..10 {
let response = stub
.peer_status(PeerStatusRequest::default())
.await
.unwrap()
.into_inner();
info!(?response);
if response.peer_status[0].state == "Established" {
established = true;
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
assert!(established);
}