Skip to content

Commit

Permalink
First limited version of MpReachNlriBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
DRiKE committed Aug 21, 2023
1 parent f1416bc commit d693fd7
Show file tree
Hide file tree
Showing 2 changed files with 266 additions and 13 deletions.
91 changes: 87 additions & 4 deletions src/bgp/message/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,8 @@ impl<Octs: Octets> UpdateMessage<Octs> {
).into(),
pa.value().as_ref()[2].into()
));
}

}
None
}

Expand Down Expand Up @@ -2027,7 +2027,10 @@ impl LargeCommunity {
use octseq::{FreezeBuilder, OctetsBuilder, ShortBuf};
use crate::bgp::message::attr_change_set::AttrChangeSet;
use crate::bgp::message::MsgType;
use crate::bgp::message::update_builder::MpUnreachNlriBuilder;
use crate::bgp::message::update_builder::{
MpReachNlriBuilder,
MpUnreachNlriBuilder
};

//use rotonda_fsm::bgp::session::AgreedConfig;

Expand Down Expand Up @@ -2060,6 +2063,7 @@ pub struct UpdateBuilder<Target> {
// MP_(UN)REACH_NLRI path attribute.
// Question is: can one also put (v4, unicast) in an MP_* attribute, and,
// then also in the conventional part (at the end of the PDU)?
mp_reach_nlri_builder: Option<MpReachNlriBuilder>,
mp_unreach_nlri_builder: Option<MpUnreachNlriBuilder>,
}

Expand Down Expand Up @@ -2088,6 +2092,7 @@ impl<Target: OctetsBuilder> UpdateBuilder<Target> {

attributes_len: 0,
total_pdu_len: 19 + 2 + 2,
mp_reach_nlri_builder: None,
mp_unreach_nlri_builder: None,
})
}
Expand Down Expand Up @@ -2292,8 +2297,56 @@ impl<Target: OctetsBuilder> UpdateBuilder<Target> {

} else {
// Nlri::Unicast only holds IPv4 and IPv6, so this must be
// IPv6.
todo!() // TODO use MpReachNlriBuilder once we have that.
// IPv6 unicast.

let new_bytes_num = match self.mp_reach_nlri_builder {
None => {
let nexthop = NextHop::Ipv6(0.into());
let builder = MpReachNlriBuilder::new(
AFI::Ipv6, SAFI::Unicast,
nexthop,
b.is_addpath()
);
let res = builder.compose_len(announcement);
self.mp_reach_nlri_builder = Some(builder);
res

}
Some(ref builder) => {
//TODO we should allow multicast here, but then we
//should also check whether a prefix is_multicast.
//Similarly, should we check for is_unicast?
//Or should we handle anything other than unicast
//in the outer match anyway, as that is never a
//conventional announcement anyway?

if builder.afi_safi() != (AFI::Ipv6, SAFI::Unicast)
|| builder.addpath_enabled() != b.is_addpath() {
// We are already constructing a
// MP_REACH_NLRI but for a different
// AFI,SAFI than the prefix in `announcement`,
// or we are mixing addpath with non-addpath.
return Err(ComposeError::IllegalCombination);
}
builder.compose_len(announcement)
}
};

println!("new_bytes_num: {new_bytes_num}");
let new_total = self.total_pdu_len + new_bytes_num;

if new_total > Self::MAX_PDU {
return Err(ComposeError::PduTooLarge(new_total));
}

if let Some(ref mut builder) = self.mp_reach_nlri_builder {
builder.add_announcement(announcement)?;
self.attributes_len += new_bytes_num;
self.total_pdu_len = new_total;
} else {
// We always have Some builder at this point.
unreachable!()
}
}
}
_ => todo!() // TODO use MpReachNlriBuilder once we have that.
Expand Down Expand Up @@ -2613,6 +2666,10 @@ where
nexthop.compose(&mut self.target)?
}

if let Some(builder) = self.mp_reach_nlri_builder {
builder.compose(&mut self.target)?
}

if let Some(builder) = self.mp_unreach_nlri_builder {
builder.compose(&mut self.target)?
}
Expand Down Expand Up @@ -3669,6 +3726,32 @@ mod tests {
//print_pcap(pdu);
}

#[test]
fn build_announcements_mp() {
use crate::bgp::aspath::HopPath;

let mut builder = UpdateBuilder::new_vec();
let prefixes = [
"2001:db8:1::/48",
"2001:db8:2::/48",
"2001:db8:3::/48",
].map(|p| Nlri::unicast_from_str(p).unwrap());
let mut iter = prefixes.into_iter().peekable();
builder.announcements_from_iter(&mut iter).unwrap();
builder.set_origin(OriginType::Igp).unwrap();
//builder.set_nexthop("fe80:1:2;3::".parse().unwrap()).unwrap();
let path = HopPath::from([
Asn::from_u32(100),
Asn::from_u32(200),
Asn::from_u32(300),
]);
builder.set_aspath::<Vec<u8>>(path.to_as_path().unwrap()).unwrap();

let raw = builder.finish().unwrap();
print_pcap(&raw);

}



#[test]
Expand Down
188 changes: 179 additions & 9 deletions src/bgp/message/update_builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use octseq::OctetsBuilder;

use crate::bgp::message::nlri::Nlri;
use crate::bgp::message::update::{AFI, SAFI};
use crate::bgp::message::update::{AFI, SAFI, NextHop};

use super::update::ComposeError;

Expand Down Expand Up @@ -103,6 +103,176 @@ pub mod new_pas {

}


//------------ MpReachNlriBuilder --------------------------------------------
// See notes at MpUnreachNlriBuilder, these also apply here.
//
// Additionally, the MpReachNlri attribute contains the nexthop information.
// The nexthop semantics can not always be derived from the AFI/SAFI tuple,
// i.e. for IPv6 unicast the nexthop might contain two addresses (one global
// and one link-local). The global address will always be there, the
// link-local is optional.
//
// Now whether or not the additional link-local is necessary might not be
// known at the time we build the UPDATE PDU. Therefore we reserve 16 bytes
// for a LL address, to prevent ending up with a PDU larger than the max pdu
// size allowed on the BGP session.


#[derive(Debug)]
pub struct MpReachNlriBuilder {
announcements: Vec<Nlri<Vec<u8>>>,
len: usize, // size of value, excluding path attribute flags+typecode+len
extended: bool,
afi: AFI,
safi: SAFI,
nexthop: NextHop,
addpath_enabled: bool,
}

impl MpReachNlriBuilder {
pub fn new(
afi: AFI,
safi: SAFI,
nexthop: NextHop,
addpath_enabled: bool,
) -> Self {
// For now, we only do v4/v6 unicast and multicast.
if !matches!(
(afi, safi),
(AFI::Ipv4 | AFI::Ipv6, SAFI::Unicast | SAFI::Multicast)
)
{
unimplemented!()
}

MpReachNlriBuilder {
announcements: vec![],
// 3 bytes for AFI+SAFI, nexthop len, reserved byte
len: 3 + nexthop.compose_len() + 1,
extended: false,
afi,
safi,
nexthop,
addpath_enabled
}
}

pub fn afi_safi(&self) -> (AFI, SAFI) {
(self.afi, self.safi)
}

pub fn addpath_enabled(&self) -> bool {
self.addpath_enabled
}

pub fn set_nexthop(&mut self, nexthop: NextHop) {
self.nexthop = nexthop;
}

pub fn add_announcement(&mut self, announcement: &Nlri<Vec<u8>>)
-> Result<(), ComposeError>
{
let announcement_len = announcement.compose_len();
if !self.extended && self.len + announcement_len > 255 {
self.extended = true;
}
self.len += announcement_len;
self.announcements.push(announcement.clone());
Ok(())
}

pub fn compose_len(&self, announcement: &Nlri<Vec<u8>>) -> usize {
let announcement_len = announcement.compose_len();
if self.announcements.is_empty() {
// First announcement to be added, so the total number of
// required octets includes the path attribute flags and
// length, the AFI/SAFI, the nexthop field, and the reserved byte.
let nh_len = self.nexthop.compose_len();
return announcement_len + 3 + 3 + nh_len + 1;
}

if !self.extended && self.len + announcement_len > 255 {
// Adding this announcement would make the path attribute exceed
// 255 and thus require the Extended Length bit to be set.
// This adds a second byte to the path attribute length field,
// so we need to account for that.
return announcement_len + 1;
}
announcement_len
}

pub fn compose<Target: OctetsBuilder>(&self, target: &mut Target)
-> Result<(), Target::AppendError>
{
let len = self.len.to_be_bytes();

if self.extended {
// FIXME this assumes usize is 64bits
target.append_slice(&[0b1001_0000, 14, len[6], len[7]])
} else {
target.append_slice(&[0b1000_0000, 14, len[7]])
}?;

target.append_slice(&u16::from(self.afi).to_be_bytes())?;
target.append_slice(&[self.safi.into()])?;
self.nexthop.compose(target)?;

// Reserved byte:
target.append_slice(&[0x00])?;

for w in &self.announcements {
match w {
Nlri::Unicast(b) => {
if !b.is_v4() {
b.compose(target)?;
} else {
unreachable!();
}
}
_ => unreachable!()
}
}
Ok(())
}
}

// **NB:** This is bgp::message::update::NextHop, _not_ new_pas::NextHop
impl NextHop {
fn compose_len(&self) -> usize {
// 1 byte for the length, plus:
1 + match *self {
NextHop::Ipv4(_) => 4,
NextHop::Ipv6(_) => 16,
NextHop::Ipv6LL(_, _) => 32,
_ => unimplemented!()
//NextHop::Ipv4MplsVpnUnicast(RouteDistinguisher, Ipv4Addr),
//NextHop::Ipv6MplsVpnUnicast(RouteDistinguisher, Ipv6Addr),
//NextHop::Empty, // FlowSpec
//NextHop::Unimplemented(AFI, SAFI),
}
}

pub fn compose<Target: OctetsBuilder>(&self, target: &mut Target)
-> Result<(), Target::AppendError>
{
target.append_slice(&[u8::try_from(self.compose_len()).unwrap() - 1])?;
match *self {
NextHop::Ipv4(a) => target.append_slice(&a.octets())?,
NextHop::Ipv6(a) => target.append_slice(&a.octets())?,
NextHop::Ipv6LL(a, ll) => {
target.append_slice(&a.octets())?;
target.append_slice(&ll.octets())?;
}
_ => unimplemented!()
}

Ok(())
}

}


//------------ MpUnreachNlriBuilder ------------------------------------------

// Note that all to-be-withdrawn NLRI should either have no PathID, or have a
Expand Down Expand Up @@ -148,15 +318,15 @@ impl MpUnreachNlriBuilder {

pub fn add_withdrawal(&mut self, withdrawal: &Nlri<Vec<u8>>)
-> Result<(), ComposeError>
{
let withdrawal_len = withdrawal.compose_len();
if !self.extended && self.len + withdrawal_len > 255 {
self.extended = true;
}
self.len += withdrawal_len;
self.withdrawals.push(withdrawal.clone());
Ok(())
{
let withdrawal_len = withdrawal.compose_len();
if !self.extended && self.len + withdrawal_len > 255 {
self.extended = true;
}
self.len += withdrawal_len;
self.withdrawals.push(withdrawal.clone());
Ok(())
}

pub fn compose_len(&self, withdrawal: &Nlri<Vec<u8>>) -> usize {
let withdrawal_len = withdrawal.compose_len();
Expand Down

0 comments on commit d693fd7

Please sign in to comment.