Files
bgp/bgpd/src/server/peer.rs
Rayhaan Jaufeerally 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

1340 lines
52 KiB
Rust

// 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::bgp_packet::capabilities::{
BGPCapability, BGPCapabilityTypeValues, BGPCapabilityValue, BGPOpenOptionTypeValues,
FourByteASNCapability, MultiprotocolCapability, OpenOption, OpenOptionCapabilities,
OpenOptions,
};
use crate::bgp_packet::constants::{
AddressFamilyIdentifier, SubsequentAddressFamilyIdentifier, AS_TRANS,
};
use crate::bgp_packet::messages::BGPMessage;
use crate::bgp_packet::messages::BGPMessageTypeValues;
use crate::bgp_packet::messages::BGPMessageTypeValues::OPEN_MESSAGE;
use crate::bgp_packet::messages::BGPMessageTypeValues::UPDATE_MESSAGE;
use crate::bgp_packet::messages::BGPSubmessage;
use crate::bgp_packet::messages::Codec;
use crate::bgp_packet::messages::KeepaliveMessage;
use crate::bgp_packet::messages::NotificationMessage;
use crate::bgp_packet::messages::OpenMessage;
use crate::bgp_packet::messages::UpdateMessage;
use crate::bgp_packet::nlri::NLRI;
use crate::bgp_packet::path_attributes::ASPathAttribute;
use crate::bgp_packet::path_attributes::NextHopPathAttribute;
use crate::bgp_packet::path_attributes::OriginPathAttribute;
use crate::bgp_packet::path_attributes::PathAttribute;
use crate::bgp_packet::path_attributes::{
LargeCommunitiesPathAttribute, LargeCommunitiesPayload, MPReachNLRIPathAttribute,
};
use crate::bgp_packet::traits::ParserContext;
use crate::server::config::PrefixAnnouncement;
use crate::server::config::{PeerConfig, ServerConfig};
use crate::server::data_structures::RouteAnnounce;
use crate::server::data_structures::RouteWithdraw;
use crate::server::data_structures::{RouteInfo, RouteUpdate};
use crate::server::rib_manager::RouteManagerCommands;
use bytes::BytesMut;
use ip_network_table_deps_treebitmap::address::Address;
use ip_network_table_deps_treebitmap::IpLookupTable;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::net::IpAddr;
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::RwLock;
use std::time::Duration;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use tokio::net::tcp;
use tokio::net::TcpStream;
use tokio::sync::broadcast;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use tokio_util::codec::{Decoder, Encoder};
use tokio_util::sync::CancellationToken;
use tracing::{info, trace, warn};
type PeerInterface = mpsc::UnboundedSender<PeerCommands>;
// Note on the threading model: Messages must be processed in order
// from the BGP peer, so we constrain PeerStateMachine to be called
// with updaates on a single thread only. Updating the state should
// not be expensive, and other tasks such as picking the best route
// will be done in a different threading model.
/// PeerStatus contians the current state of the PSM for monitoring
/// and debugging.
#[derive(Clone, Debug)]
pub struct PeerStatus {
pub name: String,
pub config: PeerConfig,
pub state: BGPState,
}
/// BGPState represents which state of the BGP state machine the peer
/// is currently in.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum BGPState {
/// Idle represents the configuration existing but not trying to
/// establish connections to or accept connections from the peer.
Idle,
/// Active represents a state where we are trying to establish a
/// connection to the peer.
Active,
/// Connect represents a state where we have intiiated a TCP
/// connection to the peer.
Connect,
/// OpenSent represents a state where we have sent a BGP OPEN
/// message to the peer and are waiting for the corresponding
/// OPEN message back.
OpenSent,
/// OpenConfirm represents a state where we have sent a
/// KEEPALIVE message to the peer after the exachange of OPEN
/// messages, and are waiting for the corresponding KEEPALIVE.
OpenConfirm,
/// Established represents the steady state of an ongoing
/// BGP session where routes are being exchanged.
Established,
}
// PeerStateMachine has two interfaces, one to the PeerConnector and
// another to the RIBManager.
#[derive(Debug)]
pub enum PeerCommands {
// NewConnection is used to pass a fresh inbound connection
// to this instance.
NewConnection(TcpStream),
// ConnectionClosed indicates that the connection to the peer
// has been lost, and state cleanup should be triggered.
ConnectionClosed(),
SendNotification(NotificationMessage),
// Send an UPDATE message to the peer.
Announce(RouteUpdate),
// Internal events for the PeerStateMachine itself
MessageFromPeer(BGPSubmessage),
TimerEvent(PeerTimerEvent),
// Adds a community to all announcements.
AddLargeCommunity((u32, u32), oneshot::Sender<String>),
RemoveLargeCommunity((u32, u32), oneshot::Sender<String>),
// GetStatus is a crude hack to get a status string out of the PSM for debugging.
GetStatus(oneshot::Sender<PeerStatus>),
}
#[derive(Copy, Clone, Debug)]
pub enum PeerTimerEvent {
ConnectTimerExpire(),
HoldTimerExpire(),
KeepaliveTimerExpire(),
}
async fn run_timer(
cancel_token: CancellationToken,
iface: PeerInterface,
event: PeerTimerEvent,
after: tokio::time::Duration,
) {
loop {
tokio::select! {
_ = cancel_token.cancelled() => {
info!("run_timer was cancelled");
return;
},
_ = tokio::time::sleep(after) => {
info!("Sending timer event: {:?}", event);
match iface.send(PeerCommands::TimerEvent(event)) {
Ok(_) => {}
Err(e) => {
warn!("Failed to send timer message to PeerStateMachine: {}, abort run_timer", e);
return;
}
}
}
}
}
}
// check_hold_timer tries to poll the last_msg_time every second
// to see if the time is past the hold time.
async fn check_hold_timer(
cancel_token: CancellationToken,
iface: PeerInterface,
last_msg_time: Arc<RwLock<std::time::SystemTime>>,
hold_time: std::time::Duration,
) {
loop {
tokio::select! {
_ = cancel_token.cancelled() => {
info!("check_hold_timer was cancelled");
return;
}
_ = tokio::time::sleep(std::time::Duration::from_secs(1)) => {
let last = last_msg_time.read().unwrap();
let elapsed_time = std::time::SystemTime::now().duration_since(*last);
match elapsed_time {
Ok(duration) => {
if duration > hold_time {
match iface.send(PeerCommands::TimerEvent(PeerTimerEvent::HoldTimerExpire())) {
Ok(()) => {},
Err(e) => {
warn!("Failed to send HoldTimerExpire message: {}", e);
}
}
// Exit the hold timer task since it's expired already and is not needed anymore.
return;
}
}
Err(e) => {
warn!("Failed to check duration since last message: {}", e);
}
}
}
}
}
}
// parse_incoming_msgs reads messages from a TCP socket and dispatches the parsed
// BGP messages to the PeerInterface.
async fn parse_incoming_msgs(
cancel_token: CancellationToken,
conn: &mut tcp::OwnedReadHalf,
iface: PeerInterface,
codec: &mut Arc<Mutex<Codec>>,
) -> Result<(), std::io::Error> {
let mut buf = BytesMut::new();
loop {
tokio::select! {
_ = cancel_token.cancelled() => {
info!("check_hold_timer was cancelled");
return Ok(());
}
len_res = conn.read_buf(&mut buf) => {
match len_res {
Err(e) => {
warn!("Failed to read from buf: {}", e);
// Send a message that the connection has been closed.
iface
.send(PeerCommands::ConnectionClosed())
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
return Err(e);
}
Ok(len) => {
if len == 0 {
while let Some(frame) = codec.lock().await.decode_eof(&mut buf)? {
iface
.send(PeerCommands::MessageFromPeer(frame.payload))
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
}
iface
.send(PeerCommands::ConnectionClosed())
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
info!("Exiting handler due to connection close");
return Ok(());
}
while let Some(frame) = codec.lock().await.decode(&mut buf)? {
iface
.send(PeerCommands::MessageFromPeer(frame.payload))
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
}
}
}
}
}
}
}
/// generate_open_message creates an open message for the provided peer.
fn generate_open_message(server_config: &ServerConfig, peer_config: &PeerConfig) -> OpenMessage {
let mut res = OpenMessage {
version: 4,
asn: AS_TRANS,
hold_time: server_config.hold_time,
identifier: server_config.identifier,
options: Vec::new(),
};
// Four byte ASN.
let asn_cap = FourByteASNCapability {
asn: server_config.asn,
};
// Multiprotocol.
let multiprotocol_cap = MultiprotocolCapability {
afi: peer_config.afi,
safi: peer_config.safi,
};
res.options.push(OpenOption {
option_type: BGPOpenOptionTypeValues::CAPABILITIES,
oval: OpenOptions::Capabilities(OpenOptionCapabilities {
caps: vec![
BGPCapability {
cap_type: BGPCapabilityTypeValues::FOUR_BYTE_ASN,
val: BGPCapabilityValue::FourByteASN(asn_cap),
},
BGPCapability {
cap_type: BGPCapabilityTypeValues::MULTPROTOCOL_BGP4,
val: BGPCapabilityValue::Multiprotocol(multiprotocol_cap),
},
],
}),
});
res
}
/// PeerStateMachine encapsulates the state of a particular peer.
/// Type parameter A refers to the type of address this peer is
/// tracking, can be Ipv4Addr or Ipv6Addr as those are the types
/// supported by treebitmap.
pub struct PeerStateMachine<A: Address> {
// server_config is the server wide config that we use here for
// reading global options.
server_config: ServerConfig,
/// The current configuration for this peer.
// To apply a new configuration the peer must be shutdown and
// restarted so that the new configuration can take effect.
config: PeerConfig,
// Store the peer's open message so we can reference it.
peer_open_msg: Option<OpenMessage>,
/// Current state of this peer.
pub state: BGPState,
tcp_stream: Option<tcp::OwnedWriteHalf>,
codec: Arc<Mutex<Codec>>,
/// ADJ-RIB for the peer.
/// The RouteInfo object contians information as to whether the
/// prefix was actually accepted and
/// the whole structure represents ADJ-RIB-IN.
prefixes_in: IpLookupTable<A, RouteInfo<A>>,
// prefixes_out contains the routes we want to export to the peer.
// TODO: Use this.
//prefixes_out: IpLookupTable<A, RouteUpdate>,
// Interface to this state machine
pub iface_rx: mpsc::UnboundedReceiver<PeerCommands>,
pub iface_tx: mpsc::UnboundedSender<PeerCommands>,
// Interfaces to the rest of the daemon.
/// rib_in is a channel to the route processor, all accepted
/// updates from the peer go to rib_in.
route_manager: mpsc::UnboundedSender<RouteManagerCommands<A>>,
// Keep track of the time of the last message to efficiently implement
// the hold timer.
last_msg_time: Arc<RwLock<std::time::SystemTime>>,
// Timers and cancellation token to spawned tasks
connect_timer: Option<(JoinHandle<()>, CancellationToken)>,
hold_timer: Option<(JoinHandle<()>, CancellationToken)>,
keepalive_timer: Option<(JoinHandle<()>, CancellationToken)>,
read_cancel_token: Option<CancellationToken>,
shutdown: broadcast::Receiver<()>,
}
impl<A: Address> PeerStateMachine<A>
where
NLRI: TryInto<A>,
<NLRI as TryInto<A>>::Error: ToString,
A: std::fmt::Debug,
{
pub fn new(
server_config: ServerConfig,
config: PeerConfig,
iface_rx: mpsc::UnboundedReceiver<PeerCommands>,
iface_tx: mpsc::UnboundedSender<PeerCommands>,
route_manager: mpsc::UnboundedSender<RouteManagerCommands<A>>,
shutdown: broadcast::Receiver<()>,
) -> PeerStateMachine<A> {
let afi = config.afi;
PeerStateMachine {
server_config,
config,
peer_open_msg: None,
state: BGPState::Active,
tcp_stream: None,
codec: Arc::new(Mutex::new(Codec {
ctx: ParserContext {
four_octet_asn: None,
nlri_mode: Some(afi),
},
})),
prefixes_in: IpLookupTable::new(),
iface_rx,
iface_tx,
route_manager,
last_msg_time: Arc::new(RwLock::new(std::time::SystemTime::UNIX_EPOCH)),
connect_timer: None,
hold_timer: None,
keepalive_timer: None,
read_cancel_token: None,
shutdown,
}
}
// run implements the main loop of the peer state machine and drives the
// events relating to this particular peer.
pub async fn run(&mut self) {
// TODO: Wire up other spawned tasks into the shutdown signal.
// Initialize connect timer.
{
let token = CancellationToken::new();
let token_copy = token.clone();
let chan = self.iface_tx.clone();
let connect_timer = tokio::spawn(async move {
run_timer(
token_copy,
chan,
PeerTimerEvent::ConnectTimerExpire(),
std::time::Duration::from_secs(5),
)
.await;
});
self.connect_timer = Some((connect_timer, token));
}
loop {
let next = tokio::select! {
cmd = self.iface_rx.recv() => cmd,
_ = self.shutdown.recv() => {
warn!("PSM shutting down due to shutdown signal.");
return;
},
};
match next {
Some(msg) => match self.handle_chan_msg(msg).await {
Ok(_) => {}
Err(e) => {
warn!(
"Failed to handle message on peer state machine channel: {}",
e
);
}
},
None => {
warn!("PeerStateMachine channel broken!");
return;
}
}
}
}
async fn handle_chan_msg(&mut self, c: PeerCommands) -> Result<(), std::io::Error> {
match c {
PeerCommands::NewConnection(mut conn) => {
let peer_addr = conn.peer_addr()?;
info!("Handling connection from peer: {}", peer_addr);
// Check that the state machine is in the right state for accepting
// a connection.
if self.state != BGPState::Active && self.state != BGPState::Connect {
info!(
"Dropping connection from peer because PSM is in state: {:?}",
self.state
);
// Just let conn be dropped here, that closes it.
return Ok(());
};
// Disable connect timer
match &self.connect_timer {
Some((_join_handle, cancel_token)) => {
cancel_token.cancel();
self.connect_timer = None;
}
None => {}
}
// Generate the OPEN message and send it to the peer.
let open_msg = generate_open_message(&self.server_config, &self.config);
let bgp_message = BGPMessage {
msg_type: OPEN_MESSAGE,
payload: BGPSubmessage::OpenMessage(open_msg),
};
let mut buf = BytesMut::new();
self.codec.lock().await.encode(bgp_message, &mut buf)?;
conn.write(&buf).await?;
// Update state
self.state = BGPState::OpenSent;
// Split the TCP connection into onwed read and write halves.
let (mut read_half, write_half) = conn.into_split();
self.tcp_stream = Some(write_half);
// Spawn a task to listen
let chan = self.iface_tx.clone();
let mut codec = self.codec.clone();
let peer_name = self.config.name.clone();
// Spawn a worker task to receive messages from the peer.
// If the connection gets closed, then a ConnectionClosed message is sent
// on chan so handle_chan_msg can clean up the state.
let read_cancel_token = CancellationToken::new();
self.read_cancel_token = Some(read_cancel_token.clone());
tokio::spawn(async move {
match parse_incoming_msgs(read_cancel_token, &mut read_half, chan, &mut codec)
.await
{
Ok(_) => info!("reader task shutdown for peer: {}", peer_name),
Err(e) => warn!(
"reader task for peer {} exited with error: {}",
peer_name, e
),
}
});
}
// When the connection is lost, we need to reset the state of the PSM,
// and clear the connection related variables out. Note that we do not
// remove any routes because that should only be done when the hold timer
// expires.
PeerCommands::ConnectionClosed() => {
self.connection_closed().await?;
}
PeerCommands::SendNotification(notification) => {
self.send_notification(notification).await?
}
PeerCommands::Announce(_) => {
todo!();
}
PeerCommands::AddLargeCommunity(c, sender) => {
for mut a in self.config.announcements.iter_mut() {
if let Some(lcs) = a.large_communities.as_mut() {
lcs.push(format!("{}:{}:{}", self.config.asn, c.0, c.1));
} else {
a.large_communities =
Some(vec![format!("{}:{}:{}", self.config.asn, c.0, c.1)]);
}
}
for a in &self.config.announcements.clone() {
if let Err(e) = self.announce_static(&a).await {
if let Err(se) = sender.send(e) {
warn!("Failed to send to sender: {}", se);
}
return Ok(());
}
}
if let Err(se) = sender.send("Ok".to_string()) {
warn!("Failed to send to sender: {}", se);
}
}
PeerCommands::RemoveLargeCommunity(c, sender) => {
let communities_str = format!("{}:{}:{}", self.config.asn, c.0, c.1);
for a in self.config.announcements.iter_mut() {
if let Some(lcs) = a.large_communities.as_mut() {
lcs.retain(|e| *e != communities_str);
}
}
for a in &self.config.announcements.clone() {
if let Err(e) = self.announce_static(&a).await {
if let Err(se) = sender.send(e) {
warn!("Failed to send to sender: {}", se);
}
return Ok(());
}
}
if let Err(se) = sender.send("Ok".to_string()) {
warn!("Failed to send to sender: {}", se);
}
}
PeerCommands::MessageFromPeer(msg) => match self.handle_msg(msg).await {
Ok(_) => {
// Update the last time counter
// We call unwrap here because it indicates that some other thread which
// was accessing the lock had a panic.
// TODO: This should be handled more gracefully, maybe by shutting down the
// peer and starting it up again.
let mut last_time_lock = (*self.last_msg_time).write().unwrap();
*last_time_lock = std::time::SystemTime::now();
}
Err(e) => {
return Err(std::io::Error::new(std::io::ErrorKind::Other, e));
}
},
PeerCommands::TimerEvent(timer_event) => match timer_event {
// When the connect timer expires we want to try and initiate
// a new connection to the peer.
PeerTimerEvent::ConnectTimerExpire() => {
info!("Connect timer expired");
match self.try_connect(Duration::from_secs(3)).await {
Ok(conn) => {
info!("Successfully connected to {}", self.config.ip);
self.iface_tx
.send(PeerCommands::NewConnection(conn))
.map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to send message on channel",
)
})?;
// Disable connect timer.
match &self.connect_timer {
Some((_join_handle, cancel_token)) => {
cancel_token.cancel();
self.connect_timer = None;
}
None => {}
}
}
Err(e) => {
warn!(
"Connection attempt to peer {} failed: {}",
self.config.ip, e
)
}
}
}
PeerTimerEvent::HoldTimerExpire() => {
trace!("Hold timer expired");
self.hold_timer_expired().await?;
}
PeerTimerEvent::KeepaliveTimerExpire() => {
trace!("Keepalive timer expired");
self.send_keepalive().await?;
}
},
PeerCommands::GetStatus(sender) => {
let state = PeerStatus {
name: self.config.name.clone(),
config: self.config.clone(),
state: self.state,
};
match sender.send(state) {
Ok(()) => {}
Err(e) => {
warn!(
"PeerCommands::GetStatus: Failed to send state back to requester: {:?}",
e
)
}
}
}
}
Ok(())
}
async fn send_notification(
&mut self,
notification: NotificationMessage,
) -> Result<(), std::io::Error> {
let mut buf = BytesMut::new();
let bgp_msg = BGPMessage {
msg_type: BGPMessageTypeValues::NOTIFICATION_MESSAGE,
payload: BGPSubmessage::NotificationMessage(notification),
};
self.codec.lock().await.encode(bgp_msg, &mut buf)?;
match self.tcp_stream.as_mut() {
Some(stream) => {
stream.write(&buf).await?;
}
None => warn!("Dropped notification message to peer"),
}
Ok(())
}
// connection_closed handles the case where the peer connection has been terminated.
// 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> {
info!("Connection closed on peer {}", self.config.name);
// Cancel keepalive timer.
match &self.keepalive_timer {
Some((_join_handle, cancel_token)) => {
cancel_token.cancel();
}
None => {}
}
// Cancel the reading task.
if let Some(cancel_token) = &self.read_cancel_token {
cancel_token.cancel();
}
// Close the TCP stream.
if let Some(stream) = self.tcp_stream.as_mut() {
match stream.shutdown().await {
Ok(_) => info!("Closed TCP stream with peer: {}", self.config.name),
Err(e) => warn!(
"Failed to close TCP stream with peer {}: {}",
self.config.name,
e.to_string()
),
}
}
// 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(),
prefixes: vec![],
};
for prefix in self.prefixes_in.iter_mut() {
route_withdraw.prefixes.push(prefix.2.nlri.clone());
}
self.route_manager
.send(RouteManagerCommands::Update(RouteUpdate::Withdraw(
route_withdraw,
)))
.map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e.to_string()))?;
// Clear prefixes_in.
self.prefixes_in = IpLookupTable::new();
// Set the state machine back to the expected.
self.state = BGPState::Active;
// Restart the connect timer to try and connect periodically.
{
let token = CancellationToken::new();
let token_copy = token.clone();
let chan = self.iface_tx.clone();
let connect_timer = tokio::spawn(async move {
run_timer(
token_copy,
chan,
PeerTimerEvent::ConnectTimerExpire(),
std::time::Duration::from_secs(10),
)
.await;
});
self.connect_timer = Some((connect_timer, token));
}
Ok(())
}
/// 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> {
let mut route_withdraw = RouteWithdraw {
peer: self.config.name.clone(),
prefixes: vec![],
};
for nlri in withdrawals {
let addr: A = nlri.clone().try_into().map_err(|e| e.to_string())?;
// remove from prefixes if present.
self.prefixes_in.remove(addr, nlri.prefixlen.into());
route_withdraw.prefixes.push(nlri);
}
if route_withdraw.prefixes.len() > 0 {
self.route_manager
.send(RouteManagerCommands::Update(RouteUpdate::Withdraw(
route_withdraw,
)))
.map_err(|e| e.to_string())?;
}
Ok(())
}
/// process_announcements creates a RouteUpdate from the announced NLRIs and path attributes
/// and sends them to the rib_in channel to be consumed by the route processor.
fn process_announcements(
&mut self,
nexthop: Vec<u8>,
announcements: Vec<NLRI>,
path_attributes: Vec<PathAttribute>,
) -> Result<(), String> {
let mut as_path: Vec<u32> = vec![];
let mut med: u32 = 0;
for attr in &path_attributes {
match attr {
PathAttribute::ASPathAttribute(aspa) => {
for segment in &aspa.segments {
for asn in &segment.path {
as_path.push(*asn);
}
}
}
PathAttribute::MultiExitDiscPathAttribute(med_attr) => {
med = med_attr.0;
}
_ => {}
}
}
let mut route_update = RouteAnnounce {
local_pref: self.config.local_pref,
med,
nexthop,
as_path,
path_attributes,
peer: self.config.name.clone(),
prefixes: vec![],
};
for announcement in announcements {
let addr: A = announcement.clone().try_into().map_err(|e| e.to_string())?;
// Should we accept this prefix?
let accepted: bool = self.decide_accept_prefix(addr, announcement.prefixlen);
let rejection_reason: Option<String> = match accepted {
true => Some("Filtered by policy".to_owned()),
false => None,
};
// Note that this logic assumes accepted routes remain accepted and the converse.
// If this is to support live updates of filters the assumptions will need to be
// revisited.
match self
.prefixes_in
.exact_match(addr, announcement.prefixlen.into())
{
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.updated = std::time::SystemTime::now();
self.prefixes_in
.insert(addr, announcement.prefixlen.into(), new_route_info);
}
None => {
// Insert new RouteInfo
let route_info = RouteInfo::<A> {
prefix: addr,
prefixlen: announcement.prefixlen,
nlri: announcement.clone(),
accepted,
rejection_reason,
learned: std::time::SystemTime::now(),
updated: std::time::SystemTime::now(),
path_attributes: route_update.path_attributes.clone(),
};
self.prefixes_in
.insert(addr, announcement.prefixlen.into(), route_info);
}
}
if accepted {
route_update.prefixes.push(announcement);
}
}
if !route_update.prefixes.is_empty() {
self.route_manager
.send(RouteManagerCommands::Update(RouteUpdate::Announce(
route_update,
)))
.map_err(|e| e.to_string())?;
}
Ok(())
}
fn decide_accept_prefix(&mut self, _: A, _: u8) -> bool {
// TODO: Implement filtering of prefixes.
true
}
fn decide_accept_message(&mut self, _: &[PathAttribute]) -> bool {
// TODO: Implement filtering of Update messages.
// TODO: Section 9.1.2 of RFC 4271:
// * Reject the message if the next hop is not resolvable
// * Reject the message if there is an AS loop
true
}
/// try_connect attempts to connect to a remote TCP endpoint with a given timeout.
async fn try_connect(&mut self, timeout: Duration) -> Result<TcpStream, std::io::Error> {
let addr = self.config.ip;
let port = self.config.port.unwrap_or(179);
let sockaddr = SocketAddr::new(addr, port);
let std_stream = std::net::TcpStream::connect_timeout(&sockaddr, timeout)?;
std_stream.set_nonblocking(true)?;
Ok(TcpStream::from_std(std_stream)?)
}
/// send_keepalive checks if the peer connection is still established and sends a
/// keepalive message.
/// Takes a lock on the peer object.
async fn send_keepalive(&mut self) -> Result<(), std::io::Error> {
info!("Sending keepalive");
match self.tcp_stream.as_mut() {
Some(conn) => {
let keepalive = BGPMessage {
msg_type: BGPMessageTypeValues::KEEPALIVE_MESSAGE,
payload: BGPSubmessage::KeepaliveMessage(KeepaliveMessage {}),
};
let mut buf = BytesMut::new();
self.codec.lock().await.encode(keepalive, &mut buf)?;
conn.write(buf.as_ref()).await?;
Ok(())
}
None => Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Called send_keepalive with no connection set",
)),
}
}
/// handle_msg processes incoming messages and updates the state in PeerStateMachine.
async fn handle_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
match &self.state {
BGPState::Idle => self.handle_idle_msg().await,
BGPState::Active => self.handle_active_msg(msg).await,
BGPState::Connect => self.handle_connect_msg(msg).await,
BGPState::OpenSent => self.handle_opensent_msg(msg).await,
BGPState::OpenConfirm => self.handle_openconfirm_msg(msg).await,
BGPState::Established => self.handle_established_msg(msg).await,
}
}
async fn handle_idle_msg(&mut self) -> Result<(), String> {
Err("Peer cannot process messages when in the Idle state".to_string())
}
async fn handle_active_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
// 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
));
}
async fn handle_connect_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
// 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
));
}
// 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> {
info!("Handling message in OpenSent state: {:?}", msg);
match msg {
BGPSubmessage::OpenMessage(o) => {
// Check that the peer has the right ASN set
if u32::from(o.asn) != self.config.asn && o.asn != AS_TRANS {
warn!(
"peer {} did not use AS_TRANS or actual ASN: {}, closing conn",
self.config.name, o.asn
);
self.state = BGPState::Active;
if let Some(stream) = self.tcp_stream.as_mut() {
stream.shutdown().await.map_err(|e| e.to_string())?;
}
}
// Unpack ASN option and assert correctness.
let mut as4_cap: Option<FourByteASNCapability> = None;
for option in &o.options {
match &option.oval {
OpenOptions::Capabilities(caps) => {
for cap in &caps.caps {
if let BGPCapabilityValue::FourByteASN(v) = &cap.val {
as4_cap = Some(v.clone());
}
}
}
}
}
fn notify_error_close(
error_code: u8,
error_subcode: u8,
iface_tx: &mut mpsc::UnboundedSender<PeerCommands>,
) -> Result<(), String> {
let notification = NotificationMessage {
error_code,
error_subcode,
data: vec![],
};
iface_tx
.send(PeerCommands::SendNotification(notification))
.map_err(|e| e.to_string())?;
iface_tx
.send(PeerCommands::ConnectionClosed())
.map_err(|e| e.to_string())?;
Ok(())
}
match as4_cap {
Some(cap) => {
// We have to set the AS4 option on the BGP message parser.
self.codec.lock().await.ctx.four_octet_asn = Some(true);
if cap.asn != self.config.asn {
warn!(
"Got non-matching ASN from peer: {} want: {}",
cap.asn, self.config.asn
);
notify_error_close(2, 2, &mut self.iface_tx)?;
}
}
None => {
// Reject connection by sending notification then queue a close.
notify_error_close(2, 4, &mut self.iface_tx)?;
}
}
// Assert that the right MultiProtocol options are set
// TODO: Handle the case where there is more than one multiprotocol cap set.
let mut mp_cap: Option<MultiprotocolCapability> = None;
for option in &o.options {
match &option.oval {
OpenOptions::Capabilities(caps) => {
for cap in &caps.caps {
if let BGPCapabilityValue::Multiprotocol(mp) = &cap.val {
mp_cap = Some(mp.clone());
}
}
}
}
}
match mp_cap {
Some(cap) => {
if cap.afi != self.config.afi {
warn!(
"Mismatched multiprotocol AFI, got: {}, want: {}",
cap.afi, self.config.afi
);
return notify_error_close(2, 4, &mut self.iface_tx);
}
if cap.safi != self.config.safi {
warn!(
"Mismatched multiprotocol SAFI, got: {}, want: {}",
cap.safi, self.config.safi
);
return notify_error_close(2, 4, &mut self.iface_tx);
}
}
None => {
warn!("No multiptotocol capability found, closing conn");
return notify_error_close(2, 4, &mut self.iface_tx);
}
}
// Ensure that the hold time is set to an acceptable value accoring to
// https://datatracker.ietf.org/doc/html/rfc4271#section-6.2
match o.hold_time {
1 | 2 => {
return notify_error_close(2, 6, &mut self.iface_tx);
}
_ => {}
}
// Store the open message for reference / debugging.
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.state = BGPState::OpenConfirm;
Ok(())
}
_ => Err("Got non-open message in state opensent".to_string()),
}
}
// In the openconfirm state we are waiting for a KEEPALIVE from the peer.
async fn handle_openconfirm_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
// 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(),
);
}
};
match msg {
BGPSubmessage::KeepaliveMessage(_) => {
// Switch the state from OpenConfirm to ESTABLISHED.
self.state = BGPState::Established;
if hold_time > 0 {
// Set keepalive timer.
let keepalive_duration = hold_time / 3;
info!(
"Using keepalive duration of {} for peer {}",
keepalive_duration, self.config.name
);
{
let token = CancellationToken::new();
let token_copy = token.clone();
let chan = self.iface_tx.clone();
let keepalive_timer = tokio::spawn(async move {
run_timer(
token_copy,
chan,
PeerTimerEvent::KeepaliveTimerExpire(),
std::time::Duration::from_secs(keepalive_duration.into()),
)
.await;
});
self.keepalive_timer = Some((keepalive_timer, token));
}
// Set hold timer.
{
let token = CancellationToken::new();
let token_copy = token.clone();
let chan = self.iface_tx.clone();
let last_msg_time = self.last_msg_time.clone();
let hold_timer = tokio::spawn(async move {
check_hold_timer(
token_copy,
chan,
last_msg_time,
std::time::Duration::from_secs(hold_time.into()),
)
.await
});
self.hold_timer = Some((hold_timer, token));
}
};
// TODO: Should not have to clone here?
let announcements: Vec<PrefixAnnouncement> = self.config.announcements.clone();
for announcement in announcements {
self.announce_static(&announcement).await?;
}
Ok(())
}
_ => Err(format!(
"Got unsupported message type in handle_openconfirm_msg: {:?}",
msg
)),
}
}
async fn hold_timer_expired(&mut self) -> Result<(), std::io::Error> {
let notification = NotificationMessage {
error_code: 4,
error_subcode: 0,
data: vec![],
};
self.send_notification(notification).await?;
self.connection_closed().await?;
Ok(())
}
async fn announce_static(&mut self, announcement: &PrefixAnnouncement) -> Result<(), String> {
let mut bgp_update_msg = UpdateMessage {
withdrawn_nlri: vec![],
announced_nlri: vec![],
path_attributes: vec![],
};
// Origin, TODO: configure this based on i/eBGP
bgp_update_msg
.path_attributes
.push(PathAttribute::OriginPathAttribute(OriginPathAttribute(1)));
bgp_update_msg
.path_attributes
.push(ASPathAttribute::from_asns(vec![self.server_config.asn]));
match self.config.afi {
AddressFamilyIdentifier::Ipv4 => {
match announcement.nexthop {
IpAddr::V4(nh) => {
bgp_update_msg
.path_attributes
.push(PathAttribute::NextHopPathAttribute(NextHopPathAttribute(
nh,
)))
}
_ => return Err("Found non IPv4 nexthop in announcement".to_string()),
}
let nlri = NLRI::try_from(announcement.prefix.clone())?;
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());
}
};
let nlri = NLRI::try_from(announcement.prefix.clone())?;
let mp_reach = MPReachNLRIPathAttribute {
afi: AddressFamilyIdentifier::Ipv6,
safi: SubsequentAddressFamilyIdentifier::Unicast,
nexthop: nexthop_octets,
nlris: vec![nlri],
};
bgp_update_msg
.path_attributes
.push(PathAttribute::MPReachNLRIPathAttribute(mp_reach));
}
}
if let Some(large_communities) = &announcement.large_communities {
let mut large_communities_attr = LargeCommunitiesPathAttribute { values: vec![] };
for large_community in large_communities {
let parts: Vec<u32> = large_community
.split(':')
.flat_map(|x| x.parse::<u32>())
.collect();
if parts.len() != 3 {
warn!("Failed to parse large community: {}", large_community);
}
let payload = LargeCommunitiesPayload {
global_admin: parts[0],
ld1: parts[1],
ld2: parts[2],
};
large_communities_attr.values.push(payload);
}
bgp_update_msg
.path_attributes
.push(PathAttribute::LargeCommunitiesPathAttribute(
large_communities_attr,
));
}
let bgp_message = BGPMessage {
msg_type: UPDATE_MESSAGE,
payload: BGPSubmessage::UpdateMessage(bgp_update_msg),
};
info!("Sending static announcement to peer: {:?}", bgp_message);
let mut buf = BytesMut::new();
self.codec
.lock()
.await
.encode(bgp_message, &mut buf)
.map_err(|e| format!("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))?;
}
Ok(())
}
// In the established state we accept Update, Keepalive and Notification messages.
async fn handle_established_msg(&mut self, msg: BGPSubmessage) -> Result<(), String> {
match msg {
BGPSubmessage::UpdateMessage(u) => {
if !self.decide_accept_message(&u.path_attributes) {
info!(
"Rejected message due to path attributes: {:?}",
u.path_attributes
);
}
// Have a seperate path for calling Multiprotocol NLRI processing.
for attr in &u.path_attributes {
match attr {
PathAttribute::MPReachNLRIPathAttribute(nlri) => {
// TODO: Determine which AFI/SAFI this update corresponds to.
let nexthop_res = nlri.clone().nexthop_to_v6();
// TODO: How do we pick whether to use the global or LLNH?
if let Some((global, _llnh_opt)) = nexthop_res {
self.process_announcements(
global.octets().to_vec(),
nlri.nlris.clone(),
u.path_attributes.clone(),
)?;
}
}
PathAttribute::MPUnreachNLRIPathAttribute(nlri) => {
// TODO: Determine which AFI/SAFI this update corresponds to.
self.process_withdrawals(nlri.nlris.clone())?;
}
_ => {}
}
}
if !u.withdrawn_nlri.is_empty() {
self.process_withdrawals(u.withdrawn_nlri)?;
}
if !u.announced_nlri.is_empty() {
let mut nexthop_option: Option<NextHopPathAttribute> = None;
for attr in &u.path_attributes {
if let PathAttribute::NextHopPathAttribute(nh_attr) = attr {
nexthop_option = Some(nh_attr.clone());
}
}
match nexthop_option {
Some(nexthop) => {
self.process_announcements(
nexthop.0.octets().to_vec(),
u.announced_nlri,
u.path_attributes,
)?;
}
None => {
warn!(
"Got announced NLRI from peer {} without any nexthop",
self.config.name
);
// TODO: Send a notification to the peer in this case.
}
}
}
Ok(())
}
BGPSubmessage::NotificationMessage(n) => {
info!(
"Got notification message from peer {}: {}",
self.config.name, n
);
Ok(())
}
BGPSubmessage::KeepaliveMessage(_) => Ok(()),
_ => Err(format!("Got unexpected message from peer: {:?}", msg)),
}
}
}