Add top level message parser
This commit is contained in:
1 parent
5ae8f6e249
commit
b26160ff40
2 files changed
+198
-142
No files matched your search
+25
-140
@@ -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<u8> for MsgHeaderSubcode {
|
||||
type Error = ();
|
||||
fn try_from(v: u8) -> Result<Self, ()> {
|
||||
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<u8> for OpenMsgSubcode {
|
||||
type Error = ();
|
||||
fn try_from(v: u8) -> Result<Self, ()> {
|
||||
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<u8> for UpdateMsgSubcode {
|
||||
type Error = ();
|
||||
fn try_from(v: u8) -> Result<Self, ()> {
|
||||
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<u8> for FsmSubcode {
|
||||
type Error = ();
|
||||
fn try_from(v: u8) -> Result<Self, ()> {
|
||||
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<u8> for CeaseSubcode {
|
||||
type Error = ();
|
||||
fn try_from(v: u8) -> Result<Self, ()> {
|
||||
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<u8> for RouteRefreshSubcode {
|
||||
type Error = ();
|
||||
fn try_from(v: u8) -> Result<Self, ()> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -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<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.
|
||||
#[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());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user