mod constants;
pub mod attribute;
pub mod capability;
pub mod decode;
pub mod message;
pub use capability::{BgpCapability, BgpOptParam, BGP_OPT_PARAM_CAPABILITIES};
pub use constants::*;
pub use message::{BgpNotification, BgpOpen, BgpRouteRefresh, BgpUpdate};
use crate::packet::{Layer, LayerContext};
use crate::protocols::transport::common::{impl_layer_div, impl_layer_object};
use crate::Result;
use self::attribute::{BgpAttrValue, BgpPathAttribute, BgpPrefix};
use self::message::{notification_name, BgpHeader};
fn message_type_name(message_type: u8) -> &'static str {
match message_type {
BGP_TYPE_OPEN => "OPEN",
BGP_TYPE_UPDATE => "UPDATE",
BGP_TYPE_NOTIFICATION => "NOTIFICATION",
BGP_TYPE_KEEPALIVE => "KEEPALIVE",
BGP_TYPE_ROUTE_REFRESH => "ROUTE-REFRESH",
_ => "UNKNOWN",
}
}
fn afi_name(afi: u16) -> String {
match afi {
AFI_IPV4 => "ipv4".to_string(),
AFI_IPV6 => "ipv6".to_string(),
_ => format!("afi-{afi}"),
}
}
fn safi_name(safi: u8) -> String {
match safi {
SAFI_UNICAST => "unicast".to_string(),
SAFI_MULTICAST => "multicast".to_string(),
_ => format!("safi-{safi}"),
}
}
fn capability_name(capability: &BgpCapability) -> String {
match capability.code {
CAP_MULTIPROTOCOL => match capability.multiprotocol_afi_safi() {
Ok((afi, safi)) => format!("mp-{}-{}", afi_name(afi), safi_name(safi)),
Err(_) => "multiprotocol".to_string(),
},
CAP_ROUTE_REFRESH => "route-refresh".to_string(),
CAP_GRACEFUL_RESTART => "graceful-restart".to_string(),
CAP_FOUR_OCTET_AS => "4-octet-as".to_string(),
CAP_ADD_PATH => "add-path".to_string(),
CAP_ENHANCED_ROUTE_REFRESH => "enhanced-route-refresh".to_string(),
CAP_ROUTE_REFRESH_OLD => "route-refresh-old".to_string(),
code => format!("cap-{code}"),
}
}
fn capability_list_summary(capabilities: &[BgpCapability]) -> String {
let names = capabilities
.iter()
.map(capability_name)
.collect::<Vec<_>>()
.join(", ");
format!("[{names}]")
}
fn prefix_summary(prefix: &BgpPrefix) -> String {
if prefix.length <= 32 && prefix.prefix.len() <= 4 {
let mut octets = [0; 4];
octets[..prefix.prefix.len()].copy_from_slice(&prefix.prefix);
return format!("{}/{}", std::net::Ipv4Addr::from(octets), prefix.length);
}
format!("prefix-{}({}b)", prefix.length, prefix.prefix.len())
}
fn prefix_list_summary(prefixes: &[BgpPrefix]) -> String {
let values = prefixes
.iter()
.map(prefix_summary)
.collect::<Vec<_>>()
.join(", ");
format!("[{values}]")
}
fn attribute_summary(attribute: &BgpPathAttribute) -> String {
match &attribute.value {
BgpAttrValue::Origin(_)
| BgpAttrValue::AsPath { .. }
| BgpAttrValue::As4Path(_)
| BgpAttrValue::NextHop(_)
| BgpAttrValue::MultiExitDisc(_)
| BgpAttrValue::LocalPref(_)
| BgpAttrValue::AtomicAggregate
| BgpAttrValue::Aggregator { .. }
| BgpAttrValue::As4Aggregator { .. }
| BgpAttrValue::Communities(_)
| BgpAttrValue::ExtendedCommunities(_)
| BgpAttrValue::LargeCommunities(_)
| BgpAttrValue::MpReachNlri { .. }
| BgpAttrValue::MpUnreachNlri { .. } => attribute.summary(),
BgpAttrValue::Unknown(value) => format!("attr-{}({}b)", attribute.type_code, value.len()),
}
}
fn attribute_list_summary(attributes: &[BgpPathAttribute]) -> String {
let values = attributes
.iter()
.map(attribute_summary)
.collect::<Vec<_>>()
.join(", ");
format!("[{values}]")
}
#[derive(Debug, Clone)]
pub(crate) enum BgpBody {
Open(BgpOpen),
Notification(BgpNotification),
Update(BgpUpdate),
RouteRefresh(BgpRouteRefresh),
Keepalive,
#[allow(dead_code)]
Unknown {
type_code: u8,
body: Vec<u8>,
},
}
impl BgpBody {
fn encoded_len(&self) -> usize {
match self {
BgpBody::Open(open) => open.body_len(),
BgpBody::Notification(notification) => notification.body_len(),
BgpBody::Update(update) => update.encoded_len(),
BgpBody::RouteRefresh(route_refresh) => route_refresh.body_len(),
BgpBody::Keepalive => 0,
BgpBody::Unknown { body, .. } => body.len(),
}
}
fn write_body(&self, out: &mut Vec<u8>) {
match self {
BgpBody::Open(open) => open.write_body(out),
BgpBody::Notification(notification) => notification.write_body(out),
BgpBody::Update(update) => update.write_body(out),
BgpBody::RouteRefresh(route_refresh) => route_refresh.write_body(out),
BgpBody::Keepalive => {}
BgpBody::Unknown { body, .. } => out.extend_from_slice(body),
}
}
}
#[derive(Debug, Clone)]
pub struct Bgp {
header: BgpHeader,
body: BgpBody,
}
impl Bgp {
pub fn open() -> Self {
Self {
header: BgpHeader::new(BGP_TYPE_OPEN),
body: BgpBody::Open(BgpOpen::new()),
}
}
pub fn keepalive() -> Self {
Self {
header: BgpHeader::new(BGP_TYPE_KEEPALIVE),
body: BgpBody::Keepalive,
}
}
pub fn notification(error_code: u8, error_subcode: u8) -> Self {
Self {
header: BgpHeader::new(BGP_TYPE_NOTIFICATION),
body: BgpBody::Notification(BgpNotification::new(error_code, error_subcode)),
}
}
pub fn update() -> Self {
Self {
header: BgpHeader::new(BGP_TYPE_UPDATE),
body: BgpBody::Update(BgpUpdate::new()),
}
}
pub fn cease() -> Self {
Self::notification(NOTIFY_CEASE, 0)
}
pub fn route_refresh(afi: u16, safi: u8) -> Self {
Self {
header: BgpHeader::new(BGP_TYPE_ROUTE_REFRESH),
body: BgpBody::RouteRefresh(BgpRouteRefresh::new(afi, safi)),
}
}
pub fn marker(mut self, marker: [u8; BGP_MARKER_LEN]) -> Self {
self.header.set_marker(marker);
self
}
pub fn length(mut self, length: u16) -> Self {
self.header.set_length(length);
self
}
pub fn version(mut self, version: u8) -> Self {
if let BgpBody::Open(open) = &mut self.body {
open.version.set_user(version);
}
self
}
pub fn my_as(mut self, my_as: u16) -> Self {
if let BgpBody::Open(open) = &mut self.body {
open.my_as.set_user(my_as);
}
self
}
pub fn hold_time(mut self, hold_time: u16) -> Self {
if let BgpBody::Open(open) = &mut self.body {
open.hold_time.set_user(hold_time);
}
self
}
pub fn bgp_id(mut self, bgp_id: impl Into<std::net::Ipv4Addr>) -> Self {
if let BgpBody::Open(open) = &mut self.body {
open.bgp_id.set_user(bgp_id.into());
}
self
}
pub fn opt_params_len(mut self, opt_params_len: u8) -> Self {
if let BgpBody::Open(open) = &mut self.body {
open.opt_params_len.set_user(opt_params_len);
}
self
}
pub fn push_param(mut self, param: BgpOptParam) -> Self {
if let BgpBody::Open(open) = &mut self.body {
open.push_param(param);
}
self
}
pub fn raw_param(mut self, param_type: u8, value: Vec<u8>) -> Self {
if let BgpBody::Open(open) = &mut self.body {
open.raw_param(param_type, value);
}
self
}
pub fn capabilities(mut self, capabilities: impl IntoIterator<Item = BgpCapability>) -> Self {
if let BgpBody::Open(open) = &mut self.body {
open.capabilities(capabilities);
}
self
}
pub fn data(mut self, data: Vec<u8>) -> Self {
if let BgpBody::Notification(notification) = &mut self.body {
notification.data = data;
}
self
}
pub fn withdraw(mut self, prefix: BgpPrefix) -> Self {
if let BgpBody::Update(update) = &mut self.body {
update.withdrawn.push(prefix);
}
self
}
pub fn withdrawn_len(mut self, withdrawn_len: u16) -> Self {
if let BgpBody::Update(update) = &mut self.body {
update.withdrawn_len.set_user(withdrawn_len);
}
self
}
pub fn attribute(mut self, attribute: BgpPathAttribute) -> Self {
if let BgpBody::Update(update) = &mut self.body {
update.attributes.push(attribute);
}
self
}
pub fn attr_len(mut self, attr_len: u16) -> Self {
if let BgpBody::Update(update) = &mut self.body {
update.attr_len.set_user(attr_len);
}
self
}
pub fn nlri(mut self, prefix: BgpPrefix) -> Self {
if let BgpBody::Update(update) = &mut self.body {
update.nlri.push(prefix);
}
self
}
pub fn subtype(mut self, subtype: u8) -> Self {
if let BgpBody::RouteRefresh(route_refresh) = &mut self.body {
route_refresh.subtype.set_user(subtype);
}
self
}
pub(crate) fn from_decoded_parts(
marker: [u8; BGP_MARKER_LEN],
length: u16,
message_type: u8,
body: BgpBody,
) -> Self {
Self {
header: BgpHeader::from_decoded_parts(marker, length, message_type),
body,
}
}
}
impl Layer for Bgp {
fn name(&self) -> &'static str {
"BGP"
}
fn summary(&self) -> String {
let len = self.header.effective_length(self.body.encoded_len());
match &self.body {
BgpBody::Open(open) => format!(
"BGP {} version={} as={} hold={} id={} caps={}",
message_type_name(BGP_TYPE_OPEN),
open.version.value().copied().unwrap_or(BGP_VERSION),
open.my_as.value().copied().unwrap_or(0),
open.hold_time.value().copied().unwrap_or(0),
open.bgp_id
.value()
.copied()
.unwrap_or(std::net::Ipv4Addr::UNSPECIFIED),
capability_list_summary(&open.capabilities)
),
BgpBody::Keepalive => {
format!("BGP {} len={len}", message_type_name(BGP_TYPE_KEEPALIVE))
}
BgpBody::Notification(notification) => {
let code = notification.error_code.value().copied().unwrap_or(0);
let subcode = notification.error_subcode.value().copied().unwrap_or(0);
format!(
"BGP {} {} data={} bytes",
message_type_name(BGP_TYPE_NOTIFICATION),
notification_name(code, subcode),
notification.data.len()
)
}
BgpBody::Update(update) => format!(
"BGP {} nlri={} withdrawn={} attrs={}",
message_type_name(BGP_TYPE_UPDATE),
prefix_list_summary(&update.nlri),
prefix_list_summary(&update.withdrawn),
attribute_list_summary(&update.attributes)
),
BgpBody::RouteRefresh(route_refresh) => format!(
"BGP {} afi={} safi={}",
message_type_name(BGP_TYPE_ROUTE_REFRESH),
route_refresh.afi.value().copied().unwrap_or(0),
route_refresh.safi.value().copied().unwrap_or(0)
),
BgpBody::Unknown { type_code, .. } => {
format!("BGP {} len={len}", message_type_name(*type_code))
}
}
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let marker = if self.header.effective_marker() == [0xFF; BGP_MARKER_LEN] {
"all-ones".to_string()
} else {
"modified".to_string()
};
let len = self.header.effective_length(self.body.encoded_len());
let mut fields = vec![
("marker", marker),
("length", len.to_string()),
(
"type",
message_type_name(self.header.effective_type()).to_string(),
),
];
if let BgpBody::Open(open) = &self.body {
fields.push((
"version",
open.version
.value()
.copied()
.unwrap_or(BGP_VERSION)
.to_string(),
));
fields.push((
"my_as",
open.my_as.value().copied().unwrap_or(0).to_string(),
));
fields.push((
"hold_time",
open.hold_time.value().copied().unwrap_or(0).to_string(),
));
fields.push((
"bgp_id",
open.bgp_id
.value()
.copied()
.unwrap_or(std::net::Ipv4Addr::UNSPECIFIED)
.to_string(),
));
for capability in &open.capabilities {
fields.push(("capability", capability_name(capability)));
}
}
if let BgpBody::Notification(notification) = &self.body {
let code = notification.error_code.value().copied().unwrap_or(0);
let subcode = notification.error_subcode.value().copied().unwrap_or(0);
fields.push(("error_code", code.to_string()));
fields.push(("error_subcode", subcode.to_string()));
fields.push(("data_length", notification.data.len().to_string()));
fields.push(("error", notification_name(code, subcode)));
}
if let BgpBody::Update(update) = &self.body {
fields.push(("withdrawn_routes", update.withdrawn.len().to_string()));
fields.push(("withdrawn_length", update.withdrawn_len().to_string()));
for prefix in &update.withdrawn {
fields.push(("withdrawn_prefix", prefix_summary(prefix)));
}
fields.push(("path_attributes", update.attributes.len().to_string()));
fields.push(("path_attribute_length", update.attributes_len().to_string()));
for attribute in &update.attributes {
fields.push(("path_attribute", attribute_summary(attribute)));
}
fields.push(("nlri", update.nlri.len().to_string()));
for prefix in &update.nlri {
fields.push(("nlri_prefix", prefix_summary(prefix)));
}
}
if let BgpBody::RouteRefresh(route_refresh) = &self.body {
fields.push((
"afi",
route_refresh.afi.value().copied().unwrap_or(0).to_string(),
));
fields.push((
"subtype",
route_refresh
.subtype
.value()
.copied()
.unwrap_or(0)
.to_string(),
));
fields.push((
"safi",
route_refresh.safi.value().copied().unwrap_or(0).to_string(),
));
}
fields
}
fn encoded_len(&self) -> usize {
BGP_HEADER_LEN + self.body.encoded_len()
}
fn compile(&self, _ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
let body_len = self.body.encoded_len();
self.header.write_header(body_len, out);
self.body.write_body(out);
Ok(())
}
impl_layer_object!(Bgp);
}
impl_layer_div!(Bgp);
#[allow(dead_code)]
pub struct BgpKeepalive;
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::Packet;
#[test]
fn bgp_prelude_resolves() {
let _ = core::mem::size_of::<crate::prelude::Bgp>();
}
#[test]
fn keepalive_defaults_to_type_four() {
let bgp = Bgp::keepalive();
assert_eq!(bgp.name(), "BGP");
assert!(matches!(bgp.body, BgpBody::Keepalive));
assert_eq!(bgp.encoded_len(), BGP_HEADER_LEN);
}
#[test]
fn open_builder_stores_values() {
let bgp = Bgp::open()
.version(4)
.my_as(65000)
.hold_time(90)
.bgp_id([192, 0, 2, 1]);
assert_eq!(bgp.header.effective_type(), BGP_TYPE_OPEN);
match bgp.body {
BgpBody::Open(open) => {
assert_eq!(open.version.value(), Some(&BGP_VERSION));
assert!(open.version.is_user_set());
assert_eq!(open.my_as.value(), Some(&65000));
assert!(open.my_as.is_user_set());
assert_eq!(open.hold_time.value(), Some(&90));
assert!(open.hold_time.is_user_set());
assert_eq!(
open.bgp_id.value(),
Some(&std::net::Ipv4Addr::new(192, 0, 2, 1))
);
assert!(open.bgp_id.is_user_set());
assert_eq!(open.opt_params_len.value(), None);
assert!(open.params.is_empty());
assert_eq!(open.body_len(), 10);
}
other => panic!("expected OPEN body, got {other:?}"),
}
}
#[test]
fn message_type_name_maps_known_and_unknown_codes() {
assert_eq!(message_type_name(BGP_TYPE_OPEN), "OPEN");
assert_eq!(message_type_name(BGP_TYPE_UPDATE), "UPDATE");
assert_eq!(message_type_name(BGP_TYPE_NOTIFICATION), "NOTIFICATION");
assert_eq!(message_type_name(BGP_TYPE_KEEPALIVE), "KEEPALIVE");
assert_eq!(message_type_name(BGP_TYPE_ROUTE_REFRESH), "ROUTE-REFRESH");
assert_eq!(message_type_name(0), "UNKNOWN");
assert_eq!(message_type_name(99), "UNKNOWN");
}
#[test]
fn keepalive_summary_reports_type_and_length() {
let summary = Bgp::keepalive().summary();
assert_eq!(summary, "BGP KEEPALIVE len=19");
}
#[test]
fn open_summary_reports_as_and_capability_names() {
let summary = Bgp::open()
.my_as(65000)
.hold_time(180)
.bgp_id([192, 0, 2, 1])
.capabilities([
BgpCapability::ipv4_unicast(),
BgpCapability::four_octet_as(65000),
BgpCapability::route_refresh(),
])
.summary();
assert!(summary.contains("BGP OPEN"), "summary was: {summary}");
assert!(summary.contains("as=65000"), "summary was: {summary}");
assert!(
summary.contains("mp-ipv4-unicast"),
"summary was: {summary}"
);
assert!(summary.contains("4-octet-as"), "summary was: {summary}");
assert!(summary.contains("route-refresh"), "summary was: {summary}");
}
#[test]
fn open_inspection_fields_report_body_and_capabilities() {
let fields = Bgp::open()
.my_as(65000)
.hold_time(180)
.bgp_id([192, 0, 2, 1])
.capabilities([
BgpCapability::route_refresh(),
BgpCapability::raw(200, Vec::new()),
])
.inspection_fields();
assert!(fields.contains(&("type", "OPEN".to_string())));
assert!(fields.contains(&("version", "4".to_string())));
assert!(fields.contains(&("my_as", "65000".to_string())));
assert!(fields.contains(&("hold_time", "180".to_string())));
assert!(fields.contains(&("bgp_id", "192.0.2.1".to_string())));
assert!(fields.contains(&("capability", "route-refresh".to_string())));
assert!(fields.contains(&("capability", "cap-200".to_string())));
}
#[test]
fn keepalive_show_contains_keepalive() {
let shown = Packet::from_layer(Bgp::keepalive()).show();
assert!(
shown.contains("KEEPALIVE"),
"show() should name the KEEPALIVE message, got:\n{shown}"
);
assert!(shown.contains("marker: all-ones"));
assert!(shown.contains("length: 19"));
}
#[test]
fn keepalive_compiles_to_nineteen_bytes() {
let packet = Packet::from_layer(Bgp::keepalive());
let bytes = packet.compile().unwrap();
assert_eq!(bytes.len(), 19);
assert_eq!(&bytes[..BGP_MARKER_LEN], &[0xFF; BGP_MARKER_LEN]);
assert_eq!(
&bytes[BGP_MARKER_LEN..BGP_MARKER_LEN + 2],
&(BGP_HEADER_LEN as u16).to_be_bytes()
);
assert_eq!(bytes[BGP_MARKER_LEN + 2], BGP_TYPE_KEEPALIVE);
}
#[test]
fn cease_notification_compiles_to_twenty_one_bytes() {
let packet = Packet::from_layer(Bgp::cease());
let bytes = packet.compile().unwrap();
assert_eq!(bytes.len(), 21);
assert_eq!(&bytes[..BGP_MARKER_LEN], &[0xFF; BGP_MARKER_LEN]);
assert_eq!(
&bytes[BGP_MARKER_LEN..BGP_MARKER_LEN + 2],
&(21u16).to_be_bytes()
);
assert_eq!(bytes[BGP_MARKER_LEN + 2], BGP_TYPE_NOTIFICATION);
assert_eq!(&bytes[BGP_HEADER_LEN..], &[NOTIFY_CEASE, 0]);
}
#[test]
fn empty_update_compiles_to_twenty_three_bytes() {
let packet = Packet::from_layer(Bgp::update());
let bytes = packet.compile().unwrap();
assert_eq!(bytes.len(), 23);
assert_eq!(&bytes[..BGP_MARKER_LEN], &[0xFF; BGP_MARKER_LEN]);
assert_eq!(
&bytes[BGP_MARKER_LEN..BGP_MARKER_LEN + 2],
&(23u16).to_be_bytes()
);
assert_eq!(bytes[BGP_MARKER_LEN + 2], BGP_TYPE_UPDATE);
assert_eq!(&bytes[BGP_HEADER_LEN..], &[0, 0, 0, 0]);
}
#[test]
fn update_announcement_compiles_with_attribute_lengths_and_order() {
let origin = super::attribute::BgpPathAttribute::origin(super::attribute::BGP_ORIGIN_IGP);
let as_path = super::attribute::BgpPathAttribute::as_sequence(&[65000]);
let next_hop =
super::attribute::BgpPathAttribute::next_hop(std::net::Ipv4Addr::new(192, 0, 2, 1));
let nlri =
super::attribute::BgpPrefix::from_ipv4(std::net::Ipv4Addr::new(203, 0, 113, 0), 24)
.expect("valid documentation prefix");
let mut attr_bytes = Vec::new();
origin.encode(&mut attr_bytes);
as_path.encode(&mut attr_bytes);
next_hop.encode(&mut attr_bytes);
assert_eq!(
attr_bytes,
[
0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x04, 0x02, 0x01, 0xfd, 0xe8, 0x40, 0x03, 0x04,
0xc0, 0x00, 0x02, 0x01,
]
);
let mut nlri_bytes = Vec::new();
nlri.encode_prefix(&mut nlri_bytes);
assert_eq!(nlri_bytes, [0x18, 0xcb, 0x00, 0x71]);
let bgp = Bgp::update()
.attribute(origin)
.attribute(as_path)
.attribute(next_hop)
.nlri(nlri);
let expected_body_len = 2 + 2 + attr_bytes.len() + nlri_bytes.len();
let expected_total_len = BGP_HEADER_LEN + expected_body_len;
match &bgp.body {
BgpBody::Update(update) => {
assert_eq!(update.withdrawn_len(), 0);
assert_eq!(update.attributes_len(), attr_bytes.len());
assert_eq!(update.nlri_len(), nlri_bytes.len());
assert_eq!(update.encoded_len(), expected_body_len);
}
other => panic!("expected UPDATE body, got {other:?}"),
}
assert_eq!(bgp.encoded_len(), expected_total_len);
let bytes = Packet::from_layer(bgp).compile().unwrap();
assert_eq!(bytes.len(), expected_total_len);
assert_eq!(
&bytes[BGP_MARKER_LEN..BGP_MARKER_LEN + 2],
&(expected_total_len as u16).to_be_bytes()
);
assert_eq!(bytes[BGP_MARKER_LEN + 2], BGP_TYPE_UPDATE);
let body = &bytes[BGP_HEADER_LEN..];
assert_eq!(&body[..2], &0u16.to_be_bytes());
assert_eq!(&body[2..4], &(attr_bytes.len() as u16).to_be_bytes());
assert_eq!(&body[4..4 + attr_bytes.len()], attr_bytes.as_slice());
assert_eq!(&body[4 + attr_bytes.len()..], nlri_bytes.as_slice());
}
#[test]
fn update_summary_reports_prefixes_and_attributes() {
let bgp = Bgp::update()
.attribute(super::attribute::BgpPathAttribute::origin(
super::attribute::BGP_ORIGIN_IGP,
))
.attribute(super::attribute::BgpPathAttribute::as_sequence(&[65000]))
.attribute(super::attribute::BgpPathAttribute::next_hop(
std::net::Ipv4Addr::new(192, 0, 2, 1),
))
.nlri(
super::attribute::BgpPrefix::from_ipv4(std::net::Ipv4Addr::new(203, 0, 113, 0), 24)
.expect("valid documentation prefix"),
);
let summary = bgp.summary();
assert!(summary.contains("203.0.113.0/24"), "summary was: {summary}");
assert!(summary.contains("AS_PATH"), "summary was: {summary}");
assert!(
summary.contains("NEXT_HOP=192.0.2.1"),
"summary was: {summary}"
);
let fields = bgp.inspection_fields();
assert!(fields.contains(&("nlri", "1".to_string())));
assert!(fields.contains(&("path_attributes", "3".to_string())));
assert!(fields.contains(&("nlri_prefix", "203.0.113.0/24".to_string())));
assert!(fields.contains(&("path_attribute", "AS_PATH=65000".to_string())));
}
#[test]
fn update_internal_length_overrides_are_preserved() {
let packet = Packet::from_layer(Bgp::update().withdrawn_len(7).attr_len(9));
let bytes = packet.compile().unwrap();
assert_eq!(bytes.len(), 23);
assert_eq!(&bytes[BGP_HEADER_LEN..], &[0, 7, 0, 9]);
}
#[test]
fn minimal_open_compiles_with_auto_lengths() {
let packet = Packet::from_layer(
Bgp::open()
.version(4)
.my_as(65000)
.hold_time(180)
.bgp_id([192, 0, 2, 1]),
);
let bytes = packet.compile().unwrap();
assert_eq!(bytes.len(), 29);
assert_eq!(&bytes[..BGP_MARKER_LEN], &[0xFF; BGP_MARKER_LEN]);
assert_eq!(&bytes[BGP_MARKER_LEN..BGP_MARKER_LEN + 2], &[0x00, 0x1d]);
assert_eq!(bytes[BGP_MARKER_LEN + 2], BGP_TYPE_OPEN);
assert_eq!(
&bytes[BGP_HEADER_LEN..],
&[4, 0xfd, 0xe8, 0x00, 0xb4, 192, 0, 2, 1, 0]
);
assert_eq!(bytes[BGP_HEADER_LEN + 9], 0);
}
#[test]
fn open_optional_parameters_length_override_is_preserved() {
let packet = Packet::from_layer(
Bgp::open()
.my_as(65000)
.hold_time(180)
.bgp_id([192, 0, 2, 1])
.opt_params_len(7),
);
let bytes = packet.compile().unwrap();
assert_eq!(bytes[BGP_HEADER_LEN + 9], 7);
}
#[test]
fn open_raw_optional_parameter_compiles_with_framing() {
let packet = Packet::from_layer(
Bgp::open()
.my_as(65000)
.hold_time(180)
.bgp_id([192, 0, 2, 1])
.raw_param(99, vec![0xaa, 0xbb, 0xcc]),
);
let bytes = packet.compile().unwrap();
assert_eq!(bytes[BGP_HEADER_LEN + 9], 5);
assert_eq!(&bytes[BGP_HEADER_LEN + 10..], &[99, 3, 0xaa, 0xbb, 0xcc]);
}
}