diff --git a/crates/packet/src/errors.rs b/crates/packet/src/errors.rs index 5f2dc7f..246e997 100644 --- a/crates/packet/src/errors.rs +++ b/crates/packet/src/errors.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use strum::FromRepr; use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)] @@ -46,19 +47,20 @@ impl BgpError { pub fn code_subcode(&self) -> (u8, u8) { match self { - Self::MsgHeader(s) => (1, u8::from(s)), - Self::OpenMsg(s) => (2, u8::from(s)), - Self::UpdateMsg(s) => (3, u8::from(s)), + Self::MsgHeader(s) => (1, *s as u8), + Self::OpenMsg(s) => (2, *s as u8), + Self::UpdateMsg(s) => (3, *s as u8), Self::HoldTimer => (4, 0), - Self::FsmError(s) => (5, u8::from(s)), - Self::Cease(s) => (6, u8::from(s)), - Self::RouteRefresh(s) => (7, u8::from(s)), + Self::FsmError(s) => (5, *s as u8), + Self::Cease(s) => (6, *s as u8), + Self::RouteRefresh(s) => (7, *s as u8), Self::SendHoldTimer => (8, 0), } } } -#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)] +#[repr(u8)] pub enum MsgHeaderSubcode { #[error("Connection not synchronized")] ConnNotSynchronized = 1, @@ -73,26 +75,12 @@ pub enum MsgHeaderSubcode { impl TryFrom for MsgHeaderSubcode { type Error = (); fn try_from(v: u8) -> Result { - match v { - 1 => Ok(Self::ConnNotSynchronized), - 2 => Ok(Self::BadMessageLength), - 3 => Ok(Self::BadMessagType), - _ => Err(()), - } + Self::from_repr(v).ok_or(()) } } -impl From<&MsgHeaderSubcode> for u8 { - fn from(s: &MsgHeaderSubcode) -> u8 { - match s { - MsgHeaderSubcode::ConnNotSynchronized => 1, - MsgHeaderSubcode::BadMessageLength => 2, - MsgHeaderSubcode::BadMessagType => 3, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)] +#[repr(u8)] pub enum OpenMsgSubcode { #[error("Unsupported BGP version number")] UnsupportedVersion = 1, @@ -119,34 +107,12 @@ pub enum OpenMsgSubcode { impl TryFrom for OpenMsgSubcode { type Error = (); fn try_from(v: u8) -> Result { - match v { - 1 => Ok(Self::UnsupportedVersion), - 2 => Ok(Self::BadPeerAs), - 3 => Ok(Self::BadBgpId), - 4 => Ok(Self::UnsupportedOptionalParam), - 6 => Ok(Self::UnacceptableHoldTime), - 7 => Ok(Self::UnsupportedCapability), - 8 => Ok(Self::RoleMismatch), - _ => Err(()), - } + Self::from_repr(v).ok_or(()) } } -impl From<&OpenMsgSubcode> for u8 { - fn from(s: &OpenMsgSubcode) -> u8 { - match s { - OpenMsgSubcode::UnsupportedVersion => 1, - OpenMsgSubcode::BadPeerAs => 2, - OpenMsgSubcode::BadBgpId => 3, - OpenMsgSubcode::UnsupportedOptionalParam => 4, - OpenMsgSubcode::UnacceptableHoldTime => 6, - OpenMsgSubcode::UnsupportedCapability => 7, - OpenMsgSubcode::RoleMismatch => 8, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)] +#[repr(u8)] pub enum UpdateMsgSubcode { #[error("Malformed attribute list")] MalforedAttrs = 1, @@ -182,40 +148,12 @@ pub enum UpdateMsgSubcode { impl TryFrom for UpdateMsgSubcode { type Error = (); fn try_from(v: u8) -> Result { - match v { - 1 => Ok(Self::MalforedAttrs), - 2 => Ok(Self::UnrecognizedWellKnownAttr), - 3 => Ok(Self::MissingWellKnown), - 4 => Ok(Self::AttributeFlags), - 5 => Ok(Self::AttributeLength), - 6 => Ok(Self::InvalidOrigin), - 8 => Ok(Self::InvalidNextHop), - 9 => Ok(Self::OptionalAttribute), - 10 => Ok(Self::InvalidNetworkField), - 11 => Ok(Self::MalformedAsPath), - _ => Err(()), - } + Self::from_repr(v).ok_or(()) } } -impl From<&UpdateMsgSubcode> for u8 { - fn from(s: &UpdateMsgSubcode) -> u8 { - match s { - UpdateMsgSubcode::MalforedAttrs => 1, - UpdateMsgSubcode::UnrecognizedWellKnownAttr => 2, - UpdateMsgSubcode::MissingWellKnown => 3, - UpdateMsgSubcode::AttributeFlags => 4, - UpdateMsgSubcode::AttributeLength => 5, - UpdateMsgSubcode::InvalidOrigin => 6, - UpdateMsgSubcode::InvalidNextHop => 8, - UpdateMsgSubcode::OptionalAttribute => 9, - UpdateMsgSubcode::InvalidNetworkField => 10, - UpdateMsgSubcode::MalformedAsPath => 11, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)] +#[repr(u8)] pub enum FsmSubcode { #[error("Received an unexpected message in OpenSent state")] UnexpectedOpenSent = 1, @@ -230,26 +168,12 @@ pub enum FsmSubcode { impl TryFrom for FsmSubcode { type Error = (); fn try_from(v: u8) -> Result { - match v { - 1 => Ok(Self::UnexpectedOpenSent), - 2 => Ok(Self::UnexpectedOpenConfirm), - 3 => Ok(Self::UnexpectedEstablished), - _ => Err(()), - } + Self::from_repr(v).ok_or(()) } } -impl From<&FsmSubcode> for u8 { - fn from(s: &FsmSubcode) -> u8 { - match s { - FsmSubcode::UnexpectedOpenSent => 1, - FsmSubcode::UnexpectedOpenConfirm => 2, - FsmSubcode::UnexpectedEstablished => 3, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)] +#[repr(u8)] pub enum CeaseSubcode { #[error("Maximum number of prefixes reached")] MaxPrefixes = 1, @@ -285,40 +209,12 @@ pub enum CeaseSubcode { impl TryFrom for CeaseSubcode { type Error = (); fn try_from(v: u8) -> Result { - match v { - 1 => Ok(Self::MaxPrefixes), - 2 => Ok(Self::AdminShutdown), - 3 => Ok(Self::PeerDeconf), - 4 => Ok(Self::AdminReset), - 5 => Ok(Self::ConnRejected), - 6 => Ok(Self::ConfChange), - 7 => Ok(Self::ConnCollisionResolution), - 8 => Ok(Self::OutOfResources), - 9 => Ok(Self::HardReset), - 10 => Ok(Self::BfdDown), - _ => Err(()), - } + Self::from_repr(v).ok_or(()) } } -impl From<&CeaseSubcode> for u8 { - fn from(s: &CeaseSubcode) -> u8 { - match s { - CeaseSubcode::MaxPrefixes => 1, - CeaseSubcode::AdminShutdown => 2, - CeaseSubcode::PeerDeconf => 3, - CeaseSubcode::AdminReset => 4, - CeaseSubcode::ConnRejected => 5, - CeaseSubcode::ConfChange => 6, - CeaseSubcode::ConnCollisionResolution => 7, - CeaseSubcode::OutOfResources => 8, - CeaseSubcode::HardReset => 9, - CeaseSubcode::BfdDown => 10, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)] +#[repr(u8)] pub enum RouteRefreshSubcode { #[error("Invalid message length")] InvalidLength = 1, @@ -327,17 +223,6 @@ pub enum RouteRefreshSubcode { impl TryFrom for RouteRefreshSubcode { type Error = (); fn try_from(v: u8) -> Result { - match v { - 1 => Ok(Self::InvalidLength), - _ => Err(()), - } - } -} - -impl From<&RouteRefreshSubcode> for u8 { - fn from(s: &RouteRefreshSubcode) -> u8 { - match s { - RouteRefreshSubcode::InvalidLength => 1, - } + Self::from_repr(v).ok_or(()) } } diff --git a/crates/packet/src/message.rs b/crates/packet/src/message.rs index 16f8df3..8e0bf93 100644 --- a/crates/packet/src/message.rs +++ b/crates/packet/src/message.rs @@ -34,6 +34,14 @@ pub const AS_TRANS: u16 = 23456; /// Version number used in the BGP4 protocol. pub const BGP4_VERSION: u8 = 4; +/// Total length of the BGP message header (marker + length + type). +pub const BGP_HEADER_LEN: u16 = 19; + +/// Maximum BGP message length per RFC 4271 ยง4.1. +pub const BGP_MAX_MSG_LEN: u16 = 4096; + +const BGP_MARKER: [u8; 16] = [0xFF; 16]; + /// Message represents the top-level messages in the BGP protocol. #[derive(Debug, Serialize, Deserialize, EnumDiscriminants)] #[repr(u8)] @@ -46,7 +54,112 @@ pub enum Message { RouteRefresh = 5, } -impl Message {} +impl Message { + /// Parses a complete BGP message including the 19-byte header from `buf`. + /// Returns the unparsed remainder and the decoded `Message`. + pub fn from_wire<'a>( + ctx: &ParserContext, + buf: &'a [u8], + ) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> { + let (buf, marker) = nom::bytes::take(16usize).parse(buf)?; + if marker != BGP_MARKER { + return Err(Failure(BgpParserError::Eyre(eyre!( + "Invalid BGP marker: expected 16 x 0xFF" + )))); + } + + let (buf, length) = be_u16(buf)?; + if length < BGP_HEADER_LEN { + return Err(Failure(BgpParserError::Eyre(eyre!( + "BGP message length {length} is below minimum {BGP_HEADER_LEN}" + )))); + } + if length > BGP_MAX_MSG_LEN { + return Err(Failure(BgpParserError::Eyre(eyre!( + "BGP message length {length} exceeds maximum {BGP_MAX_MSG_LEN}" + )))); + } + + let (buf, msg_type) = be_u8(buf)?; + let body_len = length - BGP_HEADER_LEN; + let (buf, body) = nom::bytes::take(body_len as usize).parse(buf)?; + + let msg = match msg_type { + 1 => { + let (_, open) = OpenMessage::from_wire(ctx, body)?; + Self::Open(open) + } + 2 => { + let (_, update) = UpdateMessage::from_wire(ctx, body)?; + Self::Update(update) + } + 3 => { + let (_, notif) = NotificationMessage::from_wire(ctx, body)?; + Self::Notification(notif) + } + 4 => { + if !body.is_empty() { + return Err(Failure(BgpParserError::Eyre(eyre!( + "KEEPALIVE body must be empty, got {} bytes", + body.len() + )))); + } + Self::KeepAlive + } + 5 => Self::RouteRefresh, + t => { + return Err(Failure(BgpParserError::Eyre(eyre!( + "Unknown BGP message type {t}" + )))); + } + }; + + Ok((buf, msg)) + } + + /// Serializes the message (header + body) into `out`. + pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<()> { + // Serialize the body first so we can compute the total length. + let mut body = BytesMut::new(); + match self { + Self::Open(m) => m.to_wire(ctx, &mut body)?, + Self::Update(m) => m.to_wire(ctx, &mut body)?, + Self::Notification(m) => m.to_wire(ctx, &mut body)?, + Self::KeepAlive | Self::RouteRefresh => {} + } + + let total_len = BGP_HEADER_LEN + .checked_add(body.len() as u16) + .filter(|&l| l <= BGP_MAX_MSG_LEN) + .ok_or_else(|| eyre!("BGP message body too large to serialize"))?; + + out.put_slice(&BGP_MARKER); + out.put_u16(total_len); + out.put_u8(self.type_code()); + out.put(body); + Ok(()) + } + + pub fn wire_len(&self, ctx: &ParserContext) -> Result { + let body_len: u16 = match self { + Self::Open(m) => m.wire_len(ctx)?, + Self::Update(m) => m.wire_len(ctx)?, + Self::Notification(m) => m.wire_len(), + Self::KeepAlive | Self::RouteRefresh => 0, + }; + Ok(BGP_HEADER_LEN + body_len) + } + + fn type_code(&self) -> u8 { + match self { + Self::Open(_) => 1, + Self::Update(_) => 2, + Self::Notification(_) => 3, + Self::KeepAlive => 4, + Self::RouteRefresh => 5, + } + } +} /// Represents a BGP Update message. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -1367,7 +1480,7 @@ mod tests { use crate::message::{ AggregatorPathAttribute, AsPathAttribute, AsPathSegment, AtomicAggregatePathAttribute, CommunitiesPathAttribute, ExtendedCommunitiesPathAttribute, ExtendedCommunity, - LargeCommunitiesPathAttribute, LargeCommunity, LocalPrefPathAttribute, + LargeCommunitiesPathAttribute, LargeCommunity, LocalPrefPathAttribute, Message, MpReachNlriPathAttribute, MpUnreachNlriPathAttribute, MultiExitDiscPathAttribute, NextHopPathAttribute, NlriNextHop, NotificationMessage, OriginPathAttribute, PathAttribute, PathAttributeFlags, UpdateMessage, @@ -1813,4 +1926,62 @@ mod tests { assert_eq!(parsed, msg); Ok(()) } + + #[test] + fn test_message_keepalive_roundtrip() -> Result<()> { + let ctx = &default_v6_context(); + let mut buf = BytesMut::new(); + Message::KeepAlive.to_wire(ctx, &mut buf)?; + + // 16-byte marker + 2-byte length (19) + 1-byte type (4) + assert_eq!(buf.len(), 19); + assert!(buf[..16].iter().all(|&b| b == 0xFF)); + assert_eq!(&buf[16..19], &[0x00, 0x13, 0x04]); + + let (rem, parsed) = Message::from_wire(ctx, &buf).unwrap(); + assert!(rem.is_empty()); + assert!(matches!(parsed, Message::KeepAlive)); + Ok(()) + } + + #[test] + fn test_message_notification_roundtrip() -> Result<()> { + use crate::errors::{BgpError, CeaseSubcode}; + + let ctx = &default_v6_context(); + let inner = NotificationMessage { + error: BgpError::Cease(CeaseSubcode::AdminShutdown), + data: vec![], + }; + let msg = Message::Notification(inner); + + let mut buf = BytesMut::new(); + msg.to_wire(ctx, &mut buf)?; + assert_eq!(buf.len(), msg.wire_len(ctx)? as usize); + + let (rem, parsed) = Message::from_wire(ctx, &buf).unwrap(); + assert!(rem.is_empty()); + assert!(matches!(parsed, Message::Notification(_))); + Ok(()) + } + + #[test] + fn test_message_rejects_bad_marker() { + let ctx = default_v6_context(); + let mut buf = vec![0u8; 19]; + buf[16] = 0x00; + buf[17] = 0x13; + buf[18] = 0x04; + assert!(Message::from_wire(&ctx, &buf).is_err()); + } + + #[test] + fn test_message_rejects_short_length() { + let ctx = default_v6_context(); + let mut buf = vec![0xFF; 19]; + buf[16] = 0x00; + buf[17] = 0x12; // length = 18, below minimum 19 + buf[18] = 0x04; + assert!(Message::from_wire(&ctx, &buf).is_err()); + } }