Add top level message parser

This commit is contained in:
Rayhaan Jaufeerally committed 2026-04-18 13:13:21 +02:00
1 parent 5ae8f6e249
commit b26160ff40
2 files changed
+198 -142

No files matched your search

+25 -140
View File
@@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::FromRepr;
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
@@ -46,19 +47,20 @@ impl BgpError {
pub fn code_subcode(&self) -> (u8, u8) { pub fn code_subcode(&self) -> (u8, u8) {
match self { match self {
Self::MsgHeader(s) => (1, u8::from(s)), Self::MsgHeader(s) => (1, *s as u8),
Self::OpenMsg(s) => (2, u8::from(s)), Self::OpenMsg(s) => (2, *s as u8),
Self::UpdateMsg(s) => (3, u8::from(s)), Self::UpdateMsg(s) => (3, *s as u8),
Self::HoldTimer => (4, 0), Self::HoldTimer => (4, 0),
Self::FsmError(s) => (5, u8::from(s)), Self::FsmError(s) => (5, *s as u8),
Self::Cease(s) => (6, u8::from(s)), Self::Cease(s) => (6, *s as u8),
Self::RouteRefresh(s) => (7, u8::from(s)), Self::RouteRefresh(s) => (7, *s as u8),
Self::SendHoldTimer => (8, 0), 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 { pub enum MsgHeaderSubcode {
#[error("Connection not synchronized")] #[error("Connection not synchronized")]
ConnNotSynchronized = 1, ConnNotSynchronized = 1,
@@ -73,26 +75,12 @@ pub enum MsgHeaderSubcode {
impl TryFrom<u8> for MsgHeaderSubcode { impl TryFrom<u8> for MsgHeaderSubcode {
type Error = (); type Error = ();
fn try_from(v: u8) -> Result<Self, ()> { fn try_from(v: u8) -> Result<Self, ()> {
match v { Self::from_repr(v).ok_or(())
1 => Ok(Self::ConnNotSynchronized),
2 => Ok(Self::BadMessageLength),
3 => Ok(Self::BadMessagType),
_ => Err(()),
}
} }
} }
impl From<&MsgHeaderSubcode> for u8 { #[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
fn from(s: &MsgHeaderSubcode) -> u8 { #[repr(u8)]
match s {
MsgHeaderSubcode::ConnNotSynchronized => 1,
MsgHeaderSubcode::BadMessageLength => 2,
MsgHeaderSubcode::BadMessagType => 3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
pub enum OpenMsgSubcode { pub enum OpenMsgSubcode {
#[error("Unsupported BGP version number")] #[error("Unsupported BGP version number")]
UnsupportedVersion = 1, UnsupportedVersion = 1,
@@ -119,34 +107,12 @@ pub enum OpenMsgSubcode {
impl TryFrom<u8> for OpenMsgSubcode { impl TryFrom<u8> for OpenMsgSubcode {
type Error = (); type Error = ();
fn try_from(v: u8) -> Result<Self, ()> { fn try_from(v: u8) -> Result<Self, ()> {
match v { Self::from_repr(v).ok_or(())
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(()),
}
} }
} }
impl From<&OpenMsgSubcode> for u8 { #[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
fn from(s: &OpenMsgSubcode) -> u8 { #[repr(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)]
pub enum UpdateMsgSubcode { pub enum UpdateMsgSubcode {
#[error("Malformed attribute list")] #[error("Malformed attribute list")]
MalforedAttrs = 1, MalforedAttrs = 1,
@@ -182,40 +148,12 @@ pub enum UpdateMsgSubcode {
impl TryFrom<u8> for UpdateMsgSubcode { impl TryFrom<u8> for UpdateMsgSubcode {
type Error = (); type Error = ();
fn try_from(v: u8) -> Result<Self, ()> { fn try_from(v: u8) -> Result<Self, ()> {
match v { Self::from_repr(v).ok_or(())
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(()),
}
} }
} }
impl From<&UpdateMsgSubcode> for u8 { #[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
fn from(s: &UpdateMsgSubcode) -> u8 { #[repr(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)]
pub enum FsmSubcode { pub enum FsmSubcode {
#[error("Received an unexpected message in OpenSent state")] #[error("Received an unexpected message in OpenSent state")]
UnexpectedOpenSent = 1, UnexpectedOpenSent = 1,
@@ -230,26 +168,12 @@ pub enum FsmSubcode {
impl TryFrom<u8> for FsmSubcode { impl TryFrom<u8> for FsmSubcode {
type Error = (); type Error = ();
fn try_from(v: u8) -> Result<Self, ()> { fn try_from(v: u8) -> Result<Self, ()> {
match v { Self::from_repr(v).ok_or(())
1 => Ok(Self::UnexpectedOpenSent),
2 => Ok(Self::UnexpectedOpenConfirm),
3 => Ok(Self::UnexpectedEstablished),
_ => Err(()),
}
} }
} }
impl From<&FsmSubcode> for u8 { #[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
fn from(s: &FsmSubcode) -> u8 { #[repr(u8)]
match s {
FsmSubcode::UnexpectedOpenSent => 1,
FsmSubcode::UnexpectedOpenConfirm => 2,
FsmSubcode::UnexpectedEstablished => 3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
pub enum CeaseSubcode { pub enum CeaseSubcode {
#[error("Maximum number of prefixes reached")] #[error("Maximum number of prefixes reached")]
MaxPrefixes = 1, MaxPrefixes = 1,
@@ -285,40 +209,12 @@ pub enum CeaseSubcode {
impl TryFrom<u8> for CeaseSubcode { impl TryFrom<u8> for CeaseSubcode {
type Error = (); type Error = ();
fn try_from(v: u8) -> Result<Self, ()> { fn try_from(v: u8) -> Result<Self, ()> {
match v { Self::from_repr(v).ok_or(())
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(()),
}
} }
} }
impl From<&CeaseSubcode> for u8 { #[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
fn from(s: &CeaseSubcode) -> u8 { #[repr(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)]
pub enum RouteRefreshSubcode { pub enum RouteRefreshSubcode {
#[error("Invalid message length")] #[error("Invalid message length")]
InvalidLength = 1, InvalidLength = 1,
@@ -327,17 +223,6 @@ pub enum RouteRefreshSubcode {
impl TryFrom<u8> for RouteRefreshSubcode { impl TryFrom<u8> for RouteRefreshSubcode {
type Error = (); type Error = ();
fn try_from(v: u8) -> Result<Self, ()> { fn try_from(v: u8) -> Result<Self, ()> {
match v { Self::from_repr(v).ok_or(())
1 => Ok(Self::InvalidLength),
_ => Err(()),
}
}
}
impl From<&RouteRefreshSubcode> for u8 {
fn from(s: &RouteRefreshSubcode) -> u8 {
match s {
RouteRefreshSubcode::InvalidLength => 1,
}
} }
} }
+173 -2
View File
@@ -34,6 +34,14 @@ pub const AS_TRANS: u16 = 23456;
/// Version number used in the BGP4 protocol. /// Version number used in the BGP4 protocol.
pub const BGP4_VERSION: u8 = 4; 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. /// Message represents the top-level messages in the BGP protocol.
#[derive(Debug, Serialize, Deserialize, EnumDiscriminants)] #[derive(Debug, Serialize, Deserialize, EnumDiscriminants)]
#[repr(u8)] #[repr(u8)]
@@ -46,7 +54,112 @@ pub enum Message {
RouteRefresh = 5, 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<u16> {
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. /// Represents a BGP Update message.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -1367,7 +1480,7 @@ mod tests {
use crate::message::{ use crate::message::{
AggregatorPathAttribute, AsPathAttribute, AsPathSegment, AtomicAggregatePathAttribute, AggregatorPathAttribute, AsPathAttribute, AsPathSegment, AtomicAggregatePathAttribute,
CommunitiesPathAttribute, ExtendedCommunitiesPathAttribute, ExtendedCommunity, CommunitiesPathAttribute, ExtendedCommunitiesPathAttribute, ExtendedCommunity,
LargeCommunitiesPathAttribute, LargeCommunity, LocalPrefPathAttribute, LargeCommunitiesPathAttribute, LargeCommunity, LocalPrefPathAttribute, Message,
MpReachNlriPathAttribute, MpUnreachNlriPathAttribute, MultiExitDiscPathAttribute, MpReachNlriPathAttribute, MpUnreachNlriPathAttribute, MultiExitDiscPathAttribute,
NextHopPathAttribute, NlriNextHop, NotificationMessage, OriginPathAttribute, PathAttribute, NextHopPathAttribute, NlriNextHop, NotificationMessage, OriginPathAttribute, PathAttribute,
PathAttributeFlags, UpdateMessage, PathAttributeFlags, UpdateMessage,
@@ -1813,4 +1926,62 @@ mod tests {
assert_eq!(parsed, msg); assert_eq!(parsed, msg);
Ok(()) 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());
}
} }