This commit is contained in:
@ -15,7 +15,7 @@
|
||||
use bgp_packet::{
|
||||
constants::{AddressFamilyIdentifier, SubsequentAddressFamilyIdentifier},
|
||||
nlri::NLRI,
|
||||
path_attributes::{LargeCommunitiesPathAttribute, LargeCommunitiesPayload},
|
||||
path_attributes::LargeCommunitiesPayload,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
@ -12,11 +12,15 @@
|
||||
// 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::rib_manager::PathData;
|
||||
|
||||
/// RouteInfo encapsulates information received about a particular BGP route.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RouteInfo<A> {
|
||||
@ -43,25 +47,12 @@ pub struct RouteInfo<A> {
|
||||
/// RouteUpdate is a type which encapsulates a newly learned, modified, or removed set of prefixes.
|
||||
#[derive(Debug)]
|
||||
pub enum RouteUpdate {
|
||||
Announce(RouteAnnounce),
|
||||
Announce((Vec<NLRI>, Arc<PathData>)),
|
||||
Withdraw(RouteWithdraw),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RouteAnnounce {
|
||||
pub peer: String,
|
||||
pub prefixes: Vec<NLRI>,
|
||||
|
||||
pub local_pref: u32,
|
||||
pub med: u32,
|
||||
pub as_path: Vec<u32>,
|
||||
pub nexthop: Vec<u8>,
|
||||
|
||||
pub path_attributes: Vec<PathAttribute>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RouteWithdraw {
|
||||
pub peer: String,
|
||||
pub peer_id: Ipv4Addr,
|
||||
pub prefixes: Vec<NLRI>,
|
||||
}
|
||||
|
||||
@ -14,11 +14,10 @@
|
||||
|
||||
use crate::config::PrefixAnnouncement;
|
||||
use crate::config::{PeerConfig, ServerConfig};
|
||||
use crate::data_structures::RouteAnnounce;
|
||||
use crate::data_structures::RouteWithdraw;
|
||||
use crate::data_structures::{RouteInfo, RouteUpdate};
|
||||
use crate::filter_eval::FilterEvaluator;
|
||||
use crate::rib_manager::RouteManagerCommands;
|
||||
use crate::rib_manager::{PathData, PathSource, RouteManagerCommands};
|
||||
use crate::route_server::route_server::PeerStatus;
|
||||
use bgp_packet::capabilities::{
|
||||
BGPCapability, BGPCapabilityTypeValues, BGPCapabilityValue, BGPOpenOptionTypeValues,
|
||||
@ -576,6 +575,10 @@ where
|
||||
PeerCommands::GetStatus(sender) => {
|
||||
let state = PeerStatus {
|
||||
peer_name: self.config.name.clone(),
|
||||
peer_id: match &self.peer_open_msg {
|
||||
Some(peer_open_msg) => peer_open_msg.identifier.octets().to_vec(),
|
||||
None => vec![],
|
||||
},
|
||||
state: format!("{:?}", self.state),
|
||||
session_established_time: self.established_time.map(|t| t.timestamp() as u64),
|
||||
last_messaage_time: Some(self.last_msg_time.read().unwrap().timestamp() as u64),
|
||||
@ -619,7 +622,7 @@ where
|
||||
// It deallocates the resources in this peer, unsets the TCP connection, removes the
|
||||
// routes from the inner structure as well as the routes that were propagated into the
|
||||
// RIB.
|
||||
async fn connection_closed(&mut self) -> Result<(), std::io::Error> {
|
||||
async fn connection_closed(&mut self) -> eyre::Result<()> {
|
||||
info!("Connection closed on peer {}", self.config.name);
|
||||
|
||||
// Cancel keepalive timer.
|
||||
@ -647,10 +650,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let peer_id = match &self.peer_open_msg {
|
||||
Some(peer_open_msg) => peer_open_msg.identifier,
|
||||
None => bail!("Missing peer open msg"),
|
||||
};
|
||||
|
||||
// Iterate over every route that we've announced to the route manager
|
||||
// and withdraw it.
|
||||
let mut route_withdraw = RouteWithdraw {
|
||||
peer: self.config.name.clone(),
|
||||
peer_id,
|
||||
prefixes: vec![],
|
||||
};
|
||||
|
||||
@ -694,13 +702,18 @@ where
|
||||
|
||||
/// process_withdrawals creates a RouteUpdate from withdrawal announcments and sends
|
||||
/// them to the rib_in channel to be consumed by the route processor.
|
||||
fn process_withdrawals(&mut self, withdrawals: Vec<NLRI>) -> Result<(), String> {
|
||||
fn process_withdrawals(&mut self, withdrawals: Vec<NLRI>) -> eyre::Result<()> {
|
||||
let peer_id = match &self.peer_open_msg {
|
||||
Some(peer_open_msg) => peer_open_msg.identifier,
|
||||
None => bail!("Missing peer open msg"),
|
||||
};
|
||||
|
||||
let mut route_withdraw = RouteWithdraw {
|
||||
peer: self.config.name.clone(),
|
||||
peer_id,
|
||||
prefixes: vec![],
|
||||
};
|
||||
for nlri in withdrawals {
|
||||
let addr: A = nlri.clone().try_into().map_err(|e| e.to_string())?;
|
||||
let addr: A = nlri.clone().try_into().map_err(|e| eyre!(e.to_string()))?;
|
||||
|
||||
// remove from prefixes if present.
|
||||
self.prefixes_in.remove(addr, nlri.prefixlen.into());
|
||||
@ -713,7 +726,7 @@ where
|
||||
.send(RouteManagerCommands::Update(RouteUpdate::Withdraw(
|
||||
route_withdraw,
|
||||
)))
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(|e| eyre!(e.to_string()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -726,7 +739,7 @@ where
|
||||
nexthop: Vec<u8>,
|
||||
announcements: Vec<NLRI>,
|
||||
path_attributes: Vec<PathAttribute>,
|
||||
) -> Result<(), String> {
|
||||
) -> eyre::Result<()> {
|
||||
// Extract the as_path and med from the attributes.
|
||||
let mut as_path: Vec<u32> = vec![];
|
||||
let mut med: u32 = 0;
|
||||
@ -746,22 +759,33 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let mut route_update = RouteAnnounce {
|
||||
local_pref: self.config.local_pref,
|
||||
med,
|
||||
nexthop,
|
||||
as_path,
|
||||
path_attributes,
|
||||
peer: self.config.name.clone(),
|
||||
prefixes: vec![],
|
||||
let peer_id = match &self.peer_open_msg {
|
||||
Some(peer_open_msg) => peer_open_msg.identifier,
|
||||
None => bail!("missing peer open msg"),
|
||||
};
|
||||
|
||||
let mut path_data = PathData {
|
||||
origin: OriginPathAttribute::EGP,
|
||||
nexthop,
|
||||
path_source: PathSource::BGPPeer(peer_id),
|
||||
local_pref: self.config.local_pref,
|
||||
med,
|
||||
as_path,
|
||||
path_attributes,
|
||||
learn_time: Utc::now(),
|
||||
};
|
||||
|
||||
let mut accepted_nlris = vec![];
|
||||
|
||||
for announcement in announcements {
|
||||
let addr: A = announcement.clone().try_into().map_err(|e| e.to_string())?;
|
||||
let addr: A = announcement
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(|e| eyre!(e.to_string()))?;
|
||||
// Should we accept this prefix?
|
||||
let accepted = self.filter_evaluator.evaluate_in(
|
||||
&mut route_update.path_attributes,
|
||||
&route_update.as_path,
|
||||
&mut path_data.path_attributes,
|
||||
&path_data.as_path,
|
||||
&announcement,
|
||||
);
|
||||
let rejection_reason: Option<String> = match accepted {
|
||||
@ -779,22 +803,23 @@ where
|
||||
Some(route_info) => {
|
||||
// Update the route_info, we need to clone it then reassign.
|
||||
let mut new_route_info: RouteInfo<A> = route_info.clone();
|
||||
new_route_info.path_attributes = route_update.path_attributes.clone();
|
||||
new_route_info.path_attributes = path_data.path_attributes.clone();
|
||||
new_route_info.updated = Utc::now();
|
||||
self.prefixes_in
|
||||
.insert(addr, announcement.prefixlen.into(), new_route_info);
|
||||
}
|
||||
None => {
|
||||
// Insert new RouteInfo
|
||||
// TODO: Maybe RouteInfo should be replaced with PathData after adding an accepted/rejected to it.
|
||||
let route_info = RouteInfo::<A> {
|
||||
prefix: addr,
|
||||
prefixlen: announcement.prefixlen,
|
||||
nlri: announcement.clone(),
|
||||
accepted,
|
||||
rejection_reason,
|
||||
learned: Utc::now(),
|
||||
updated: Utc::now(),
|
||||
path_attributes: route_update.path_attributes.clone(),
|
||||
learned: path_data.learn_time,
|
||||
updated: path_data.learn_time,
|
||||
path_attributes: path_data.path_attributes.clone(),
|
||||
};
|
||||
self.prefixes_in
|
||||
.insert(addr, announcement.prefixlen.into(), route_info);
|
||||
@ -802,16 +827,17 @@ where
|
||||
}
|
||||
|
||||
if accepted {
|
||||
route_update.prefixes.push(announcement);
|
||||
accepted_nlris.push(announcement);
|
||||
}
|
||||
}
|
||||
|
||||
if !route_update.prefixes.is_empty() {
|
||||
if !accepted_nlris.is_empty() {
|
||||
self.route_manager
|
||||
.send(RouteManagerCommands::Update(RouteUpdate::Announce(
|
||||
route_update,
|
||||
)))
|
||||
.map_err(|e| e.to_string())?;
|
||||
.send(RouteManagerCommands::Update(RouteUpdate::Announce((
|
||||
accepted_nlris,
|
||||
Arc::new(path_data),
|
||||
))))
|
||||
.map_err(|e| eyre!(e.to_string()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -861,7 +887,7 @@ where
|
||||
}
|
||||
|
||||
/// handle_msg processes incoming messages and updates the state in PeerStateMachine.
|
||||
async fn handle_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
|
||||
async fn handle_msg(&mut self, msg: BGPSubmessage) -> eyre::Result<()> {
|
||||
match &self.state {
|
||||
BGPState::Idle => self.handle_idle_msg().await,
|
||||
BGPState::Active => self.handle_active_msg(msg).await,
|
||||
@ -872,32 +898,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_idle_msg(&mut self) -> Result<(), String> {
|
||||
Err("Peer cannot process messages when in the Idle state".to_string())
|
||||
async fn handle_idle_msg(&mut self) -> eyre::Result<()> {
|
||||
bail!("Peer cannot process messages when in the Idle state")
|
||||
}
|
||||
|
||||
async fn handle_active_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
|
||||
async fn handle_active_msg(&mut self, msg: BGPSubmessage) -> eyre::Result<()> {
|
||||
// In the active state a new connection should come in via the NewConnection
|
||||
// message on the PSM channel, or if we establish a connection out, then that
|
||||
// logic should handle the messages until OpenSent.
|
||||
return Err(format!(
|
||||
"Discarding message received in ACTIVE state: {:?}",
|
||||
msg
|
||||
));
|
||||
bail!("Discarding message received in ACTIVE state: {:?}", msg)
|
||||
}
|
||||
|
||||
async fn handle_connect_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
|
||||
async fn handle_connect_msg(&mut self, msg: BGPSubmessage) -> eyre::Result<()> {
|
||||
// In the connect state a new connection should come in via the NewConnection
|
||||
// message on the PSM channel, or if we establish a connection out, then that
|
||||
// logic should handle the messages until OpenSent.
|
||||
return Err(format!(
|
||||
"Discarding message received in CONNECT state: {:?}",
|
||||
msg
|
||||
));
|
||||
bail!("Discarding message received in CONNECT state: {:?}", msg)
|
||||
}
|
||||
|
||||
// In the opensent state we still need to get the OPEN message from the peer
|
||||
async fn handle_opensent_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
|
||||
async fn handle_opensent_msg(&mut self, msg: BGPSubmessage) -> eyre::Result<()> {
|
||||
info!("Handling message in OpenSent state: {:?}", msg);
|
||||
match msg {
|
||||
BGPSubmessage::OpenMessage(o) => {
|
||||
@ -910,7 +930,7 @@ where
|
||||
self.state = BGPState::Active;
|
||||
self.established_time = None;
|
||||
if let Some(stream) = self.tcp_stream.as_mut() {
|
||||
stream.shutdown().await.map_err(|e| e.to_string())?;
|
||||
stream.shutdown().await.map_err(|e| eyre!(e.to_string()))?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -932,7 +952,7 @@ where
|
||||
error_code: u8,
|
||||
error_subcode: u8,
|
||||
iface_tx: &mut mpsc::UnboundedSender<PeerCommands>,
|
||||
) -> Result<(), String> {
|
||||
) -> eyre::Result<()> {
|
||||
let notification = NotificationMessage {
|
||||
error_code,
|
||||
error_subcode,
|
||||
@ -940,10 +960,10 @@ where
|
||||
};
|
||||
iface_tx
|
||||
.send(PeerCommands::SendNotification(notification))
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(|e| eyre!(e.to_string()))?;
|
||||
iface_tx
|
||||
.send(PeerCommands::ConnectionClosed())
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(|e| eyre!(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1016,27 +1036,26 @@ where
|
||||
self.peer_open_msg = Some(o);
|
||||
|
||||
// Send the Keepalive message and transition to OpenConfirm.
|
||||
self.send_keepalive().await.map_err(|e| e.to_string())?;
|
||||
self.send_keepalive()
|
||||
.await
|
||||
.map_err(|e| eyre!(e.to_string()))?;
|
||||
self.state = BGPState::OpenConfirm;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err("Got non-open message in state opensent".to_string()),
|
||||
_ => bail!("Got non-open message in state opensent"),
|
||||
}
|
||||
}
|
||||
|
||||
// In the openconfirm state we are waiting for a KEEPALIVE from the peer.
|
||||
async fn handle_openconfirm_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
|
||||
async fn handle_openconfirm_msg(&mut self, msg: BGPSubmessage) -> eyre::Result<()> {
|
||||
// In the openconfirm state we wait for a keepalive message from the peer.
|
||||
// We also compute the timer expiry time for the keepalive timer.
|
||||
// Hold time of 0 means no keepalive and hold timer.
|
||||
let hold_time = match &self.peer_open_msg {
|
||||
Some(o) => o.hold_time,
|
||||
None => {
|
||||
return Err(
|
||||
"Logic error: reached handle_openconfirm without a open message set"
|
||||
.to_string(),
|
||||
);
|
||||
bail!("Logic error: reached handle_openconfirm without a open message set");
|
||||
}
|
||||
};
|
||||
match msg {
|
||||
@ -1097,14 +1116,14 @@ where
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(format!(
|
||||
_ => bail!(
|
||||
"Got unsupported message type in handle_openconfirm_msg: {:?}",
|
||||
msg
|
||||
)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn hold_timer_expired(&mut self) -> Result<(), std::io::Error> {
|
||||
async fn hold_timer_expired(&mut self) -> eyre::Result<()> {
|
||||
let notification = NotificationMessage {
|
||||
error_code: 4,
|
||||
error_subcode: 0,
|
||||
@ -1117,7 +1136,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn announce_static(&mut self, announcement: &PrefixAnnouncement) -> Result<(), String> {
|
||||
async fn announce_static(&mut self, announcement: &PrefixAnnouncement) -> eyre::Result<()> {
|
||||
let mut bgp_update_msg = UpdateMessage {
|
||||
withdrawn_nlri: vec![],
|
||||
announced_nlri: vec![],
|
||||
@ -1127,7 +1146,7 @@ where
|
||||
// Origin, TODO: configure this based on i/eBGP
|
||||
bgp_update_msg
|
||||
.path_attributes
|
||||
.push(PathAttribute::OriginPathAttribute(OriginPathAttribute(1)));
|
||||
.push(PathAttribute::OriginPathAttribute(OriginPathAttribute::EGP));
|
||||
|
||||
bgp_update_msg
|
||||
.path_attributes
|
||||
@ -1143,20 +1162,22 @@ where
|
||||
nh,
|
||||
)))
|
||||
}
|
||||
_ => return Err("Found non IPv4 nexthop in announcement".to_string()),
|
||||
_ => bail!("Found non IPv4 nexthop in announcement"),
|
||||
}
|
||||
|
||||
let nlri = NLRI::try_from(announcement.prefix.as_str())?;
|
||||
let nlri = NLRI::try_from(announcement.prefix.as_str())
|
||||
.map_err(|e| eyre!(e.to_string()))?;
|
||||
bgp_update_msg.announced_nlri.push(nlri);
|
||||
}
|
||||
AddressFamilyIdentifier::Ipv6 => {
|
||||
let nexthop_octets = match announcement.nexthop {
|
||||
IpAddr::V6(nh) => nh.octets().to_vec(),
|
||||
_ => {
|
||||
return Err("Found non IPv6 nexthop in announcement".to_string());
|
||||
bail!("Found non IPv6 nexthop in announcement");
|
||||
}
|
||||
};
|
||||
let nlri = NLRI::try_from(announcement.prefix.as_str())?;
|
||||
let nlri = NLRI::try_from(announcement.prefix.as_str())
|
||||
.map_err(|e| eyre!(e.to_string()))?;
|
||||
let mp_reach = MPReachNLRIPathAttribute {
|
||||
afi: AddressFamilyIdentifier::Ipv6,
|
||||
safi: SubsequentAddressFamilyIdentifier::Unicast,
|
||||
@ -1205,19 +1226,19 @@ where
|
||||
.lock()
|
||||
.await
|
||||
.encode(bgp_message, &mut buf)
|
||||
.map_err(|e| format!("failed to encode BGP message: {}", e))?;
|
||||
.map_err(|e| eyre!("failed to encode BGP message: {}", e))?;
|
||||
|
||||
if let Some(stream) = self.tcp_stream.as_mut() {
|
||||
stream
|
||||
.write(&buf)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write msg to peer: {}", e))?;
|
||||
.map_err(|e| eyre!("Failed to write msg to peer: {}", e))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// In the established state we accept Update, Keepalive and Notification messages.
|
||||
async fn handle_established_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
|
||||
async fn handle_established_msg(&mut self, msg: BGPSubmessage) -> eyre::Result<()> {
|
||||
match msg {
|
||||
BGPSubmessage::UpdateMessage(u) => {
|
||||
if !self.decide_accept_message(&u.path_attributes) {
|
||||
@ -1289,7 +1310,7 @@ where
|
||||
}
|
||||
|
||||
BGPSubmessage::KeepaliveMessage(_) => Ok(()),
|
||||
_ => Err(format!("Got unexpected message from peer: {:?}", msg)),
|
||||
_ => bail!("Got unexpected message from peer: {:?}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::config::PeerConfig;
|
||||
use crate::data_structures::RouteAnnounce;
|
||||
use crate::data_structures::RouteUpdate;
|
||||
use crate::peer::PeerCommands;
|
||||
|
||||
@ -21,10 +20,15 @@ use std::cmp::Eq;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use bgp_packet::nlri::NLRI;
|
||||
use bgp_packet::path_attributes::OriginPathAttribute;
|
||||
use bgp_packet::path_attributes::PathAttribute;
|
||||
use chrono::{DateTime, Utc};
|
||||
use eyre::{bail, eyre};
|
||||
use ip_network_table_deps_treebitmap::address::Address;
|
||||
use serde::Serialize;
|
||||
use tokio::sync::broadcast;
|
||||
@ -37,53 +41,84 @@ use super::data_structures::RouteWithdraw;
|
||||
|
||||
type PeerInterface = mpsc::UnboundedSender<PeerCommands>;
|
||||
|
||||
/// Path is a structure to contain a specific route via one nexthop.
|
||||
#[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),
|
||||
}
|
||||
|
||||
/// 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 Path {
|
||||
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>,
|
||||
pub peer_name: String,
|
||||
/// 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 PartialEq for Path {
|
||||
fn eq(&self, other: &Path) -> bool {
|
||||
// Local pref
|
||||
impl PartialEq for PathData {
|
||||
fn eq(&self, other: &PathData) -> bool {
|
||||
// Local pref.
|
||||
if self.local_pref > other.local_pref {
|
||||
return true;
|
||||
}
|
||||
// AS path length
|
||||
|
||||
// Prefer paths that are locally originated.
|
||||
if matches!(self.path_source, PathSource::LocallyConfigured) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// AS path length.
|
||||
if self.as_path.len() < other.as_path.len() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Origin
|
||||
|
||||
// MED lower is better
|
||||
if self.med < other.med {
|
||||
// IGP < EGP < INCOMPLETE
|
||||
if (self.origin as u8) < (other.origin as u8) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use peer name as discriminator of last resort
|
||||
self.peer_name < other.peer_name
|
||||
// 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 true;
|
||||
}
|
||||
}
|
||||
|
||||
// Pick the oldest path to prefer more stable ones.
|
||||
self.learn_time < other.learn_time
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Path {}
|
||||
impl Eq for PathData {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct PathSet<A> {
|
||||
pub addr: A,
|
||||
pub prefixlen: u8,
|
||||
pub nlri: NLRI,
|
||||
// paths is stored in a BTreeMap which is sorted and allows us to efficiently
|
||||
// find the best path.
|
||||
pub paths: BTreeMap<String, Path>,
|
||||
/// Sorted map keyed by the BGP Identifier of the peer that sent the route.
|
||||
pub paths: BTreeMap<Ipv4Addr, Arc<PathData>>,
|
||||
}
|
||||
|
||||
/// RibSnapshot contians a version number and the dump of all the routes.
|
||||
@ -141,7 +176,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<(), String> {
|
||||
pub async fn run(&mut self) -> eyre::Result<()> {
|
||||
loop {
|
||||
let next = tokio::select! {
|
||||
cmd = self.mgr_rx.recv() => cmd,
|
||||
@ -162,7 +197,7 @@ where
|
||||
},
|
||||
None => {
|
||||
warn!("All senders of the manager channel have been dropped, manager exiting!");
|
||||
return Err("Manager exited due to channel closure".to_string());
|
||||
bail!("Manager exited due to channel closure");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -206,26 +241,30 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, update: RouteUpdate) -> Result<(), String> {
|
||||
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: RouteAnnounce) -> Result<(), String> {
|
||||
let peer_name = update.peer.clone();
|
||||
let nexthop = update.nexthop;
|
||||
for nlri in update.prefixes {
|
||||
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| e.to_string())?;
|
||||
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();
|
||||
// There is already this prefix in the RIB, check if this is a
|
||||
// reannouncement or fresh announcement.
|
||||
match path_set.paths.get_mut(&update.peer) {
|
||||
match path_set.paths.get_mut(&peer_router_id) {
|
||||
// Peer already announced this route before.
|
||||
Some(existing) => {
|
||||
trace!(
|
||||
@ -233,20 +272,11 @@ where
|
||||
addr,
|
||||
prefixlen
|
||||
);
|
||||
existing.nexthop = nexthop.clone();
|
||||
existing.path_attributes = update.path_attributes.clone();
|
||||
*existing = update.1.clone();
|
||||
}
|
||||
// First time that this peer is announcing the route.
|
||||
None => {
|
||||
let path = Path {
|
||||
nexthop: nexthop.clone(),
|
||||
peer_name: peer_name.clone(),
|
||||
local_pref: update.local_pref,
|
||||
med: update.med,
|
||||
as_path: update.as_path.clone(),
|
||||
path_attributes: update.path_attributes.clone(),
|
||||
};
|
||||
path_set.paths.insert(update.peer.clone(), path);
|
||||
path_set.paths.insert(peer_router_id, update.1.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,15 +295,7 @@ where
|
||||
nlri,
|
||||
paths: BTreeMap::new(),
|
||||
};
|
||||
let path = Path {
|
||||
nexthop: nexthop.clone(),
|
||||
peer_name: peer_name.clone(),
|
||||
local_pref: update.local_pref,
|
||||
med: update.med,
|
||||
as_path: update.as_path.clone(),
|
||||
path_attributes: update.path_attributes.clone(),
|
||||
};
|
||||
path_set.paths.insert(peer_name.clone(), path);
|
||||
path_set.paths.insert(peer_router_id, update.1.clone());
|
||||
self.rib
|
||||
.insert(addr, prefixlen.into(), Mutex::new(path_set.clone()));
|
||||
|
||||
@ -285,18 +307,18 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_withdraw(&mut self, update: RouteWithdraw) -> Result<(), String> {
|
||||
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| e.to_string())?;
|
||||
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.paths.remove(&update.peer);
|
||||
let removed = path_set.paths.remove(&update.peer_id);
|
||||
if removed.is_none() {
|
||||
warn!(
|
||||
"Got a withdrawal for route {} from {}, which was not in RIB",
|
||||
nlri, update.peer
|
||||
nlri, update.peer_id
|
||||
);
|
||||
}
|
||||
// Ignore errors sending due to no active receivers on the channel.
|
||||
@ -309,7 +331,7 @@ where
|
||||
} else {
|
||||
warn!(
|
||||
"Got a withdrawal for route {} from {}, which was not in RIB",
|
||||
nlri, update.peer
|
||||
nlri, update.peer_id
|
||||
);
|
||||
}
|
||||
if pathset_empty {
|
||||
@ -329,16 +351,20 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::rib_manager::PathData;
|
||||
use crate::rib_manager::PathSource;
|
||||
use crate::rib_manager::RibManager;
|
||||
use crate::rib_manager::RouteAnnounce;
|
||||
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;
|
||||
|
||||
@ -351,32 +377,37 @@ mod tests {
|
||||
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 announce = RouteAnnounce {
|
||||
peer: "Some peer".to_string(),
|
||||
prefixes: vec![NLRI {
|
||||
afi: AddressFamilyIdentifier::Ipv6,
|
||||
prefixlen: 32,
|
||||
prefix: vec![0x20, 0x01, 0xd, 0xb8],
|
||||
}],
|
||||
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_eq!(
|
||||
rib_manager.handle_update(RouteUpdate::Announce(announce)),
|
||||
Ok(())
|
||||
);
|
||||
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.paths.len(), 1);
|
||||
let path_result = lookup_result.paths.get("Some peer").unwrap();
|
||||
let path_result = lookup_result
|
||||
.paths
|
||||
.get(&"1.2.3.4".parse().unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(path_result.nexthop, nexthop.octets().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
|
||||
use crate::peer::PeerCommands;
|
||||
use crate::rib_manager;
|
||||
use crate::rib_manager::PathSource;
|
||||
use crate::rib_manager::RibSnapshot;
|
||||
use crate::rib_manager::RouteManagerCommands;
|
||||
use crate::route_server::route_server::bgp_server_admin_service_server::BgpServerAdminService;
|
||||
@ -90,11 +91,14 @@ impl RouteServer {
|
||||
};
|
||||
for (_, path) in mgr_ps.1.paths {
|
||||
let proto_path = Path {
|
||||
as_path: path.as_path,
|
||||
as_path: path.as_path.clone(),
|
||||
local_pref: path.local_pref,
|
||||
med: path.med,
|
||||
nexthop: path.nexthop,
|
||||
peer_name: path.peer_name,
|
||||
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);
|
||||
}
|
||||
@ -164,11 +168,14 @@ impl RouteService for RouteServer {
|
||||
};
|
||||
for (_, path) in pathset.paths {
|
||||
let proto_path = Path {
|
||||
as_path: path.as_path,
|
||||
as_path: path.as_path.clone(),
|
||||
local_pref: path.local_pref,
|
||||
med: path.med,
|
||||
nexthop: path.nexthop,
|
||||
peer_name: path.peer_name,
|
||||
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);
|
||||
}
|
||||
@ -208,11 +215,14 @@ impl RouteService for RouteServer {
|
||||
};
|
||||
for (_, path) in pathset.paths {
|
||||
let proto_path = Path {
|
||||
as_path: path.as_path,
|
||||
as_path: path.as_path.clone(),
|
||||
local_pref: path.local_pref,
|
||||
med: path.med,
|
||||
nexthop: path.nexthop,
|
||||
peer_name: path.peer_name,
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user