diff --git a/src/error.rs b/src/error.rs index b005396..52b1403 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,3 +27,10 @@ pub struct ConvertbgpMessageToBytesError { #[from] source: anyhow::Error, } + +#[derive(Error, Debug)] +#[error(transparent)] +pub struct ConstructIpv4NetworkError { + #[from] + source: anyhow::Error, +} diff --git a/src/packets/update.rs b/src/packets/update.rs index edcbd3c..15c7f5a 100644 --- a/src/packets/update.rs +++ b/src/packets/update.rs @@ -5,6 +5,7 @@ use crate::bgp_type::AutonomousSystemNumber; use crate::error::ConvertBytesToBgpMessageError; use crate::path_attribute::{AsPath, Origin, PathAttribute}; use crate::routing::Ipv4Network; +use anyhow::Context; use bytes::{BufMut, BytesMut}; use super::header::{Header, MessageType}; @@ -90,7 +91,42 @@ impl TryFrom for UpdateMessage { type Error = ConvertBytesToBgpMessageError; fn try_from(bytes: BytesMut) -> Result { - todo!(); + let header = Header::try_from(BytesMut::from(&bytes[0..19]))?; + + let withdrawn_routes_length: u16 = u16::from_be_bytes(bytes[19..21].try_into().context( + format!("cannot convert to withdrawn_routes_length: {:?}", &bytes), + )?); + let withdrawn_routes_end_index = 21 + withdrawn_routes_length as usize; + let withdrawn_routes_bytes = &bytes[21..withdrawn_routes_end_index]; + let withdrawn_routes = Ipv4Network::from_u8_slice(withdrawn_routes_bytes)?; + + let path_attributes_start_index = withdrawn_routes_end_index + 2; + let total_path_attribute_length = u16::from_be_bytes( + bytes[withdrawn_routes_end_index..path_attributes_start_index] + .try_into() + .context(format!( + "cannot convert to total_path_attribute_length: {:?}", + &bytes + ))?, + ); + + let path_attributes_bytes = &bytes[path_attributes_start_index + ..path_attributes_start_index + total_path_attribute_length as usize]; + let path_attributes = Arc::new(PathAttribute::from_u8_slice(path_attributes_bytes)?); + let network_layer_reachability_information_start_index = + path_attributes_start_index + total_path_attribute_length as usize; + let network_layer_reachability_information = Ipv4Network::from_u8_slice( + &bytes[network_layer_reachability_information_start_index..], + )?; + + Ok(Self { + header, + withdrawn_routes, + withdrawn_routes_length, + path_attributes, + path_attributes_length: total_path_attribute_length, + network_layer_reachability_information, + }) } } @@ -114,7 +150,7 @@ mod tests { let update_message = UpdateMessage::new( update_message_path_attributes, - vec!["10.100.220.0.24".parse().unwrap()], + vec!["10.100.220.0/24".parse().unwrap()], vec![], ); diff --git a/src/path_attribute.rs b/src/path_attribute.rs index 36548df..3969f71 100644 --- a/src/path_attribute.rs +++ b/src/path_attribute.rs @@ -1,6 +1,8 @@ +use anyhow::Context; use bytes::{BufMut, BytesMut}; use crate::bgp_type::AutonomousSystemNumber; +use crate::error::ConvertBytesToBgpMessageError; use std::collections::BTreeSet; use std::net::Ipv4Addr; @@ -61,6 +63,50 @@ impl From<&AsPath> for BytesMut { } } +impl TryFrom for Origin { + type Error = anyhow::Error; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Origin::Igp), + 1 => Ok(Origin::Egp), + 2 => Ok(Origin::Incomplete), + _ => Err(anyhow::anyhow!("cannot convert to Origin: {:?}", value)), + } + } +} + +impl TryFrom<&[u8]> for AsPath { + type Error = anyhow::Error; + + fn try_from(value: &[u8]) -> Result { + match value[0] { + 1 => { + let mut ases = BTreeSet::new(); + let mut i = 2; + while i < value.len() { + ases.insert(u16::from_be_bytes(value[i..i + 2].try_into()?).into()); + i += 2; + } + Ok(AsPath::AsSet(ases)) + } + 2 => { + let mut ases = Vec::new(); + let mut i = 2; + while i < value.len() { + ases.push(u16::from_be_bytes(value[i..i + 2].try_into()?).into()); + i += 2; + } + Ok(AsPath::AsSequence(ases)) + } + _ => Err(anyhow::anyhow!(format!( + "cannot convert to AsPath: {:?}", + value + ))), + } + } +} + impl PathAttribute { pub fn bytes_len(&self) -> usize { let path_attribute_value_length = match self { @@ -78,6 +124,51 @@ impl PathAttribute { length + 1 } } + + pub fn from_u8_slice( + bytes: &[u8], + ) -> Result, ConvertBytesToBgpMessageError> { + let mut path_attributes = vec![]; + let mut i = 0; + while bytes.len() > i { + let attribute_flag = bytes[i]; + let attribute_length_octets = ((attribute_flag & 0b0001_0000) >> 4) + 1; + let attribute_type_code = bytes[i + 1]; + let attribute_length = if attribute_length_octets == 1 { + bytes[i + 2] as usize + } else { + u16::from_be_bytes( + bytes[i + 2..i + 4] + .try_into() + .context("cannot convert to attribute_length")?, + ) as usize + }; + + let attribute_start_index = i + 1 + attribute_length_octets as usize + 1; + let attribute_end_index = attribute_start_index + attribute_length; + let path_attribute = match attribute_type_code { + 1 => PathAttribute::Origin(Origin::try_from(bytes[attribute_start_index])?), + 2 => PathAttribute::AsPath(AsPath::try_from( + &bytes[attribute_start_index..attribute_end_index], + )?), + 3 => { + let addr = Ipv4Addr::new( + bytes[attribute_start_index], + bytes[attribute_start_index + 1], + bytes[attribute_start_index + 2], + bytes[attribute_start_index + 3], + ); + + PathAttribute::NextHop(addr) + } + _ => PathAttribute::DontKnow(bytes[i..attribute_end_index].to_owned()), + }; + path_attributes.push(path_attribute); + i = attribute_end_index; + } + + Ok(path_attributes) + } } impl From<&PathAttribute> for BytesMut { diff --git a/src/routing.rs b/src/routing.rs index 1f46c8a..c059a9d 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -1,5 +1,5 @@ use std::{ - net::IpAddr, + net::{IpAddr, Ipv4Addr}, ops::{Deref, DerefMut}, str::FromStr, }; @@ -10,7 +10,10 @@ use futures::TryStreamExt; use rtnetlink::{new_connection, IpVersion}; use tracing::info; -use crate::{config::Config, error::ConfigParseError}; +use crate::{ + config::Config, + error::{ConfigParseError, ConstructIpv4NetworkError, ConvertBytesToBgpMessageError}, +}; #[derive(Debug, PartialEq, Eq, Clone)] struct LocRib; @@ -74,6 +77,70 @@ impl From<&Ipv4Network> for BytesMut { } impl Ipv4Network { + pub fn new(addr: Ipv4Addr, prefix: u8) -> Result { + let network = ipnetwork::Ipv4Network::new(addr, prefix).context(format!( + "cannot create Ipv4Network: {:?}, {:?}", + addr, prefix + ))?; + + Ok(Self(network)) + } + + pub fn from_u8_slice(bytes: &[u8]) -> Result, ConvertBytesToBgpMessageError> { + let mut networks = vec![]; + let mut i = 0; + while bytes.len() > i { + let prefix = bytes[i]; + i += 1; + match prefix { + 0 => { + networks.push(Ipv4Network::new(Ipv4Addr::new(0, 0, 0, 0), prefix).context("")?); + } + 1..=8 => { + networks.push( + Ipv4Network::new(Ipv4Addr::new(bytes[i], 0, 0, 0), prefix).context("")?, + ); + i += 1; + } + 9..=16 => { + networks.push( + Ipv4Network::new(Ipv4Addr::new(bytes[i], bytes[i + 1], 0, 0), prefix) + .context("")?, + ); + i += 2; + } + 17..=24 => { + networks.push( + Ipv4Network::new( + Ipv4Addr::new(bytes[i], bytes[i + 1], bytes[i + 2], 0), + prefix, + ) + .context("")?, + ); + i += 3; + } + 25..=32 => { + networks.push( + Ipv4Network::new( + Ipv4Addr::new(bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]), + prefix, + ) + .context("")?, + ); + i += 4; + } + _ => { + return Err(ConvertBytesToBgpMessageError::from(anyhow::anyhow!( + "Invalid prefix length: {:?}", + prefix + ))); + } + } + } + + Ok(networks) + } + pub fn bytes_len(&self) -> usize { match self.prefix() { 0 => 1,