1use anyhow::{anyhow, bail, Context, Result};
34use bytes::BytesMut;
35use std::net::Ipv4Addr;
36use tokio_util::codec::{Decoder, Encoder};
37
38use netgauze_bgp_pkt::{
39 capabilities::{BgpCapability, FourOctetAsCapability},
40 codec::BgpCodec,
41 nlri::{Ipv4Unicast, Ipv4UnicastAddress},
42 open::{BgpOpenMessage, BgpOpenMessageParameter},
43 path_attribute::{
44 As2PathSegment, As4Path, As4PathSegment, AsPath, AsPathSegmentType, LocalPreference,
45 MultiExitDiscriminator, NextHop, Origin, PathAttribute, PathAttributeValue,
46 },
47 update::BgpUpdateMessage,
48 BgpMessage,
49};
50
51pub const BGP_MARKER: [u8; 16] = [0xff; 16];
53
54pub const BGP_HEADER_LEN: usize = 19;
56
57pub const BGP_MAX_MESSAGE_LEN: usize = 4096;
61
62pub const AS_TRANS: u16 = 23456;
65
66pub const MSG_OPEN: u8 = 1;
68pub const MSG_UPDATE: u8 = 2;
69pub const MSG_NOTIFICATION: u8 = 3;
70pub const MSG_KEEPALIVE: u8 = 4;
71pub const MSG_ROUTE_REFRESH: u8 = 5;
72
73pub type NotifyCode = (u8, u8);
75
76pub const ERR_HEADER: u8 = 1;
78pub const SUB_CONNECTION_NOT_SYNCHRONIZED: u8 = 1;
79pub const SUB_BAD_MESSAGE_LENGTH: u8 = 2;
80pub const SUB_BAD_MESSAGE_TYPE: u8 = 3;
81pub const ERR_OPEN: u8 = 2;
83pub const SUB_UNSUPPORTED_VERSION: u8 = 1;
84pub const SUB_BAD_PEER_AS: u8 = 2;
85pub const SUB_BAD_BGP_IDENTIFIER: u8 = 3;
86pub const SUB_UNACCEPTABLE_HOLD_TIME: u8 = 6;
87pub const ERR_UPDATE: u8 = 3;
89pub const ERR_HOLD_TIMER_EXPIRED: u8 = 4;
91pub const ERR_FSM: u8 = 5;
93pub const SUB_FSM_OPENSENT: u8 = 1;
94pub const SUB_FSM_OPENCONFIRM: u8 = 2;
95pub const SUB_FSM_ESTABLISHED: u8 = 3;
96pub const ERR_CEASE: u8 = 6;
98pub const SUB_CEASE_ADMIN_SHUTDOWN: u8 = 2;
99pub const SUB_CEASE_CONNECTION_REJECTED: u8 = 5;
102pub const SUB_CEASE_OUT_OF_RESOURCES: u8 = 8;
106
107#[derive(Debug, Clone, PartialEq, Eq)]
109pub enum HeaderError {
110 BadMarker,
112 BadLength(u16),
114}
115
116impl HeaderError {
117 pub fn notify_code(&self) -> NotifyCode {
118 match self {
119 Self::BadMarker => (ERR_HEADER, SUB_CONNECTION_NOT_SYNCHRONIZED),
120 Self::BadLength(_) => (ERR_HEADER, SUB_BAD_MESSAGE_LENGTH),
121 }
122 }
123
124 pub fn notify_data(&self) -> Vec<u8> {
126 match self {
127 Self::BadMarker => Vec::new(),
128 Self::BadLength(len) => len.to_be_bytes().to_vec(),
129 }
130 }
131}
132
133pub fn parse_header(header: &[u8; BGP_HEADER_LEN]) -> Result<(usize, u8), HeaderError> {
143 if header[..16] != BGP_MARKER {
144 return Err(HeaderError::BadMarker);
145 }
146 let len = u16::from_be_bytes([header[16], header[17]]);
147 let msg_type = header[18];
148
149 if (len as usize) < BGP_HEADER_LEN || (len as usize) > BGP_MAX_MESSAGE_LEN {
150 return Err(HeaderError::BadLength(len));
151 }
152 let min = match msg_type {
153 MSG_OPEN => 29,
154 MSG_UPDATE => 23,
155 MSG_NOTIFICATION => 21,
156 MSG_KEEPALIVE => 19,
157 MSG_ROUTE_REFRESH => 23,
160 _ => BGP_HEADER_LEN as u16,
161 };
162 if len < min {
163 return Err(HeaderError::BadLength(len));
164 }
165 Ok((len as usize, msg_type))
166}
167
168pub fn decode(bytes: &[u8], asn4: bool) -> std::result::Result<BgpMessage, DecodeError> {
177 let mut codec = BgpCodec::new(asn4);
178 let mut buf = BytesMut::from(bytes);
179 match codec.decode(&mut buf) {
180 Ok(Some((msg, _ignored))) => Ok(msg),
181 Ok(None) => Err(DecodeError {
184 notify: (ERR_HEADER, SUB_BAD_MESSAGE_LENGTH),
185 detail: "message body shorter than its length field".to_string(),
186 }),
187 Err(e) => Err(DecodeError::from_codec(e)),
188 }
189}
190
191#[derive(Debug, Clone)]
193pub struct DecodeError {
194 pub notify: NotifyCode,
195 pub detail: String,
196}
197
198impl std::fmt::Display for DecodeError {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 write!(
201 f,
202 "{} (NOTIFICATION {}/{})",
203 self.detail, self.notify.0, self.notify.1
204 )
205 }
206}
207
208impl DecodeError {
209 fn from_codec(e: netgauze_bgp_pkt::codec::BgpCodecDecoderError) -> Self {
210 use netgauze_bgp_pkt::codec::BgpCodecDecoderError as E;
211 use netgauze_bgp_pkt::wire::deserializer::open::BgpOpenMessageParsingError as OpenErr;
212 use netgauze_bgp_pkt::wire::deserializer::BgpMessageParsingError as MsgErr;
213
214 let detail = format!("{e:?}");
215 let notify = match &e {
216 E::IoError(_) => (ERR_CEASE, 0),
217 E::Incomplete(_) => (ERR_HEADER, SUB_BAD_MESSAGE_LENGTH),
218 E::BgpMessageParsingError(inner) => match inner {
219 MsgErr::ConnectionNotSynchronized(_) => {
220 (ERR_HEADER, SUB_CONNECTION_NOT_SYNCHRONIZED)
221 }
222 MsgErr::BadMessageLength(_) => (ERR_HEADER, SUB_BAD_MESSAGE_LENGTH),
223 MsgErr::UndefinedBgpMessageType(_) => (ERR_HEADER, SUB_BAD_MESSAGE_TYPE),
224 MsgErr::BgpOpenMessageParsingError(open) => match open {
225 OpenErr::UnsupportedVersionNumber(_) => (ERR_OPEN, SUB_UNSUPPORTED_VERSION),
226 OpenErr::UnacceptableHoldTime(_) => (ERR_OPEN, SUB_UNACCEPTABLE_HOLD_TIME),
227 OpenErr::InvalidBgpId(_) => (ERR_OPEN, SUB_BAD_BGP_IDENTIFIER),
228 _ => (ERR_OPEN, 0),
229 },
230 MsgErr::BgpUpdateMessageParsingError(_) => (ERR_UPDATE, 0),
231 _ => (ERR_CEASE, 0),
234 },
235 };
236 Self { notify, detail }
237 }
238}
239
240pub fn encode(msg: BgpMessage) -> Result<Vec<u8>> {
246 let mut codec = BgpCodec::default();
247 let mut out = BytesMut::new();
248 codec
249 .encode(msg, &mut out)
250 .map_err(|e| anyhow!("BGP encoding failed: {e:?}"))?;
251 if out.len() > BGP_MAX_MESSAGE_LEN {
252 bail!(
253 "BGP message is {} bytes, over the RFC 4271 maximum of {}",
254 out.len(),
255 BGP_MAX_MESSAGE_LEN
256 );
257 }
258 Ok(out.to_vec())
259}
260
261pub fn encode_keepalive() -> Vec<u8> {
263 let mut msg = Vec::with_capacity(BGP_HEADER_LEN);
264 msg.extend_from_slice(&BGP_MARKER);
265 msg.extend_from_slice(&(BGP_HEADER_LEN as u16).to_be_bytes());
266 msg.push(MSG_KEEPALIVE);
267 msg
268}
269
270pub fn encode_notification(error_code: u8, error_subcode: u8, data: &[u8]) -> Result<Vec<u8>> {
274 let total = BGP_HEADER_LEN + 2 + data.len();
275 if total > BGP_MAX_MESSAGE_LEN {
276 bail!(
277 "BGP NOTIFICATION with {} data bytes exceeds the {}-byte maximum",
278 data.len(),
279 BGP_MAX_MESSAGE_LEN
280 );
281 }
282 let mut msg = Vec::with_capacity(total);
283 msg.extend_from_slice(&BGP_MARKER);
284 msg.extend_from_slice(&(total as u16).to_be_bytes());
285 msg.push(MSG_NOTIFICATION);
286 msg.push(error_code);
287 msg.push(error_subcode);
288 msg.extend_from_slice(data);
289 Ok(msg)
290}
291
292pub fn build_open(local_as: u32, hold_time: u16, router_id: Ipv4Addr) -> BgpMessage {
302 let my_as = u16::try_from(local_as).unwrap_or(AS_TRANS);
303 BgpMessage::Open(BgpOpenMessage::new(
304 my_as,
305 hold_time,
306 router_id,
307 vec![BgpOpenMessageParameter::Capabilities(vec![
308 BgpCapability::FourOctetAs(FourOctetAsCapability::new(local_as)),
309 ])],
310 ))
311}
312
313pub fn ipv4_unicast(prefix: &str) -> Result<Ipv4Unicast> {
322 let (addr_str, len_str) = prefix
323 .split_once('/')
324 .with_context(|| format!("BGP prefix {prefix:?} must be in CIDR form, e.g. 10.0.0.0/24"))?;
325 let addr: Ipv4Addr = addr_str
326 .parse()
327 .with_context(|| format!("BGP prefix {prefix:?} has an invalid IPv4 address"))?;
328 let len: u8 = len_str
329 .parse()
330 .with_context(|| format!("BGP prefix {prefix:?} has an invalid prefix length"))?;
331 if len > 32 {
332 bail!("BGP prefix {prefix:?} has prefix length {len}, which exceeds 32");
333 }
334 if addr.is_multicast() || addr.is_broadcast() {
335 bail!("BGP prefix {prefix:?} is not a unicast address");
336 }
337
338 let mask: u32 = if len == 0 {
339 0
340 } else {
341 u32::MAX << (32 - u32::from(len))
342 };
343 let network = Ipv4Addr::from(u32::from(addr) & mask);
344
345 serde_json::from_value::<Ipv4Unicast>(serde_json::Value::String(format!("{network}/{len}")))
346 .with_context(|| format!("BGP prefix {prefix:?} is not a valid IPv4 network"))
347}
348
349#[derive(Debug, Clone)]
351pub struct UpdateIntent {
352 pub withdrawn: Vec<String>,
353 pub nlri: Vec<String>,
354 pub next_hop: Option<Ipv4Addr>,
355 pub as_path: Vec<u32>,
356 pub origin: Origin,
357 pub med: Option<u32>,
358 pub local_pref: Option<u32>,
359}
360
361pub fn build_update(intent: &UpdateIntent, peer_asn4: bool) -> Result<BgpMessage> {
374 let withdrawn = intent
375 .withdrawn
376 .iter()
377 .map(|p| ipv4_unicast(p).map(Ipv4UnicastAddress::new_no_path_id))
378 .collect::<Result<Vec<_>>>()?;
379 let nlri = intent
380 .nlri
381 .iter()
382 .map(|p| ipv4_unicast(p).map(Ipv4UnicastAddress::new_no_path_id))
383 .collect::<Result<Vec<_>>>()?;
384
385 if withdrawn.is_empty() && nlri.is_empty() {
386 bail!("send_bgp_update needs at least one prefix in nlri or withdrawn_routes");
387 }
388
389 let mut attrs: Vec<PathAttribute> = Vec::new();
390
391 if !nlri.is_empty() {
392 let next_hop = intent
393 .next_hop
394 .context("send_bgp_update with nlri requires next_hop (RFC 4271 makes NEXT_HOP mandatory for an announcement)")?;
395
396 attrs.push(well_known(PathAttributeValue::Origin(intent.origin))?);
397
398 if peer_asn4 {
399 let segments = if intent.as_path.is_empty() {
400 vec![]
401 } else {
402 vec![As4PathSegment::new(
403 AsPathSegmentType::AsSequence,
404 intent.as_path.clone(),
405 )]
406 };
407 attrs.push(well_known(PathAttributeValue::AsPath(
408 AsPath::As4PathSegments(segments),
409 ))?);
410 } else {
411 let needs_as4 = intent.as_path.iter().any(|&asn| asn > u32::from(u16::MAX));
412 let segments = if intent.as_path.is_empty() {
413 vec![]
414 } else {
415 vec![As2PathSegment::new(
416 AsPathSegmentType::AsSequence,
417 intent
418 .as_path
419 .iter()
420 .map(|&asn| u16::try_from(asn).unwrap_or(AS_TRANS))
421 .collect(),
422 )]
423 };
424 attrs.push(well_known(PathAttributeValue::AsPath(
425 AsPath::As2PathSegments(segments),
426 ))?);
427 if needs_as4 {
428 attrs.push(
430 PathAttribute::from(
431 true,
432 true,
433 false,
434 false,
435 PathAttributeValue::As4Path(As4Path::new(vec![As4PathSegment::new(
436 AsPathSegmentType::AsSequence,
437 intent.as_path.clone(),
438 )])),
439 )
440 .map_err(|(_, e)| anyhow!("invalid AS4_PATH attribute: {e:?}"))?,
441 );
442 }
443 }
444
445 attrs.push(well_known(PathAttributeValue::NextHop(NextHop::new(
446 next_hop,
447 )))?);
448
449 if let Some(local_pref) = intent.local_pref {
450 attrs.push(well_known(PathAttributeValue::LocalPreference(
451 LocalPreference::new(local_pref),
452 ))?);
453 }
454 if let Some(med) = intent.med {
455 attrs.push(
457 PathAttribute::from(
458 true,
459 false,
460 false,
461 false,
462 PathAttributeValue::MultiExitDiscriminator(MultiExitDiscriminator::new(med)),
463 )
464 .map_err(|(_, e)| anyhow!("invalid MULTI_EXIT_DISC attribute: {e:?}"))?,
465 );
466 }
467 }
468
469 Ok(BgpMessage::Update(BgpUpdateMessage::new(
470 withdrawn, attrs, nlri,
471 )))
472}
473
474fn well_known(value: PathAttributeValue) -> Result<PathAttribute> {
476 PathAttribute::from(false, true, false, false, value)
477 .map_err(|(_, e)| anyhow!("invalid well-known path attribute: {e:?}"))
478}
479
480pub fn update_to_json(update: &BgpUpdateMessage) -> serde_json::Value {
486 let withdrawn: Vec<String> = update
487 .withdraw_routes()
488 .iter()
489 .map(|r| r.network().to_string())
490 .collect();
491 let nlri: Vec<String> = update
492 .nlri()
493 .iter()
494 .map(|r| r.network().to_string())
495 .collect();
496
497 let attributes: Vec<serde_json::Value> = update
498 .path_attributes()
499 .iter()
500 .map(path_attribute_to_json)
501 .collect();
502
503 let mut origin = serde_json::Value::Null;
506 let mut next_hop = serde_json::Value::Null;
507 let mut as_path = serde_json::Value::Null;
508 for attr in update.path_attributes() {
509 match attr.value() {
510 PathAttributeValue::Origin(o) => origin = serde_json::json!(format!("{o:?}")),
511 PathAttributeValue::NextHop(n) => {
512 next_hop = serde_json::json!(n.next_hop().to_string())
513 }
514 PathAttributeValue::AsPath(p) => {
515 as_path = serde_json::json!(Vec::<u32>::from(p.clone()))
516 }
517 _ => {}
518 }
519 }
520
521 serde_json::json!({
522 "withdrawn_routes": withdrawn,
523 "nlri": nlri,
524 "origin": origin,
525 "next_hop": next_hop,
526 "as_path": as_path,
527 "path_attributes": attributes,
528 "end_of_rib": update.end_of_rib().is_some(),
529 })
530}
531
532fn path_attribute_to_json(attr: &PathAttribute) -> serde_json::Value {
533 let type_name = match attr.path_attribute_type() {
534 Ok(t) => format!("{t:?}"),
535 Err(code) => format!("UNKNOWN({code})"),
536 };
537 let mut out = serde_json::json!({
538 "type_name": type_name,
539 "optional": attr.optional(),
540 "transitive": attr.transitive(),
541 });
542
543 match attr.value() {
544 PathAttributeValue::Origin(o) => out["origin"] = serde_json::json!(format!("{o:?}")),
545 PathAttributeValue::AsPath(p) => {
546 out["as_path"] = serde_json::json!(Vec::<u32>::from(p.clone()));
547 out["four_octet"] = serde_json::json!(matches!(p, AsPath::As4PathSegments(_)));
548 }
549 PathAttributeValue::As4Path(p) => {
550 let asns: Vec<u32> = p
551 .segments()
552 .iter()
553 .flat_map(|s| s.as_numbers().iter().copied())
554 .collect();
555 out["as4_path"] = serde_json::json!(asns);
556 }
557 PathAttributeValue::NextHop(n) => {
558 out["next_hop"] = serde_json::json!(n.next_hop().to_string())
559 }
560 PathAttributeValue::MultiExitDiscriminator(m) => out["med"] = serde_json::json!(m.metric()),
561 PathAttributeValue::LocalPreference(l) => out["local_pref"] = serde_json::json!(l.metric()),
562 PathAttributeValue::AtomicAggregate(_) => out["atomic_aggregate"] = serde_json::json!(true),
563 PathAttributeValue::Communities(c) => {
564 let communities: Vec<String> = c
565 .communities()
566 .iter()
567 .map(|c| format!("{}:{}", c.collection_asn(), c.collection_value()))
568 .collect();
569 out["communities"] = serde_json::json!(communities);
570 }
571 _ => {}
575 }
576 out
577}
578
579pub fn capabilities_to_json(open: &BgpOpenMessage) -> serde_json::Value {
581 let names: Vec<String> = open
582 .capabilities()
583 .into_iter()
584 .map(|c| match c {
585 BgpCapability::FourOctetAs(a) => format!("four_octet_as({})", a.asn4()),
586 other => format!("{other:?}")
587 .split_whitespace()
588 .next()
589 .unwrap_or("unknown")
590 .trim_end_matches('(')
591 .to_string(),
592 })
593 .collect();
594 serde_json::json!(names)
595}
596
597pub fn error_name(code: u8) -> &'static str {
599 match code {
600 1 => "Message Header Error",
601 2 => "OPEN Message Error",
602 3 => "UPDATE Message Error",
603 4 => "Hold Timer Expired",
604 5 => "Finite State Machine Error",
605 6 => "Cease",
606 _ => "Unknown",
607 }
608}
609
610pub fn error_subcode_name(code: u8, subcode: u8) -> &'static str {
611 match (code, subcode) {
612 (1, 1) => "Connection Not Synchronized",
613 (1, 2) => "Bad Message Length",
614 (1, 3) => "Bad Message Type",
615 (2, 1) => "Unsupported Version Number",
616 (2, 2) => "Bad Peer AS",
617 (2, 3) => "Bad BGP Identifier",
618 (2, 4) => "Unsupported Optional Parameter",
619 (2, 6) => "Unacceptable Hold Time",
620 (2, 7) => "Unsupported Capability",
621 (3, 1) => "Malformed Attribute List",
622 (3, 2) => "Unrecognized Well-known Attribute",
623 (3, 3) => "Missing Well-known Attribute",
624 (3, 4) => "Attribute Flags Error",
625 (3, 5) => "Attribute Length Error",
626 (3, 6) => "Invalid ORIGIN Attribute",
627 (3, 8) => "Invalid NEXT_HOP Attribute",
628 (3, 9) => "Optional Attribute Error",
629 (3, 10) => "Invalid Network Field",
630 (3, 11) => "Malformed AS_PATH",
631 (5, 1) => "Unexpected Message in OpenSent State",
632 (5, 2) => "Unexpected Message in OpenConfirm State",
633 (5, 3) => "Unexpected Message in Established State",
634 (6, 1) => "Maximum Number of Prefixes Reached",
635 (6, 2) => "Administrative Shutdown",
636 (6, 3) => "Peer De-configured",
637 (6, 4) => "Administrative Reset",
638 (6, 5) => "Connection Rejected",
639 (6, 6) => "Other Configuration Change",
640 (6, 7) => "Connection Collision Resolution",
641 (6, 8) => "Out of Resources",
642 _ => "Unspecified",
643 }
644}
645
646pub fn encode_intent(intent: &serde_json::Value, peer_asn4: bool) -> Result<Vec<u8>> {
654 let kind = intent
655 .get("kind")
656 .and_then(|v| v.as_str())
657 .context("BGP action intent is missing its 'kind'")?;
658
659 match kind {
660 "open" => {
661 let my_as = intent
662 .get("my_as")
663 .and_then(|v| v.as_u64())
664 .context("BGP open intent is missing my_as")? as u32;
665 let hold_time = intent
666 .get("hold_time")
667 .and_then(|v| v.as_u64())
668 .context("BGP open intent is missing hold_time")?
669 as u16;
670 let router_id: Ipv4Addr = intent
671 .get("router_id")
672 .and_then(|v| v.as_str())
673 .context("BGP open intent is missing router_id")?
674 .parse()
675 .context("BGP open intent has an invalid router_id")?;
676 encode(build_open(my_as, hold_time, router_id))
677 }
678 "keepalive" => Ok(encode_keepalive()),
679 "notification" => {
680 let code = intent
681 .get("error_code")
682 .and_then(|v| v.as_u64())
683 .context("BGP notification intent is missing error_code")?
684 as u8;
685 let subcode = intent
686 .get("error_subcode")
687 .and_then(|v| v.as_u64())
688 .unwrap_or(0) as u8;
689 let data = match intent.get("data").and_then(|v| v.as_str()) {
690 Some(hex_str) if !hex_str.is_empty() => {
691 hex::decode(hex_str).context("BGP notification intent has non-hex data")?
692 }
693 _ => Vec::new(),
694 };
695 encode_notification(code, subcode, &data)
696 }
697 "update" => {
698 let strings = |key: &str| -> Vec<String> {
699 intent
700 .get(key)
701 .and_then(|v| v.as_array())
702 .map(|a| {
703 a.iter()
704 .filter_map(|v| v.as_str().map(str::to_string))
705 .collect()
706 })
707 .unwrap_or_default()
708 };
709 let next_hop = match intent.get("next_hop").and_then(|v| v.as_str()) {
710 Some(s) => Some(
711 s.parse::<Ipv4Addr>()
712 .context("BGP update intent has an invalid next_hop")?,
713 ),
714 None => None,
715 };
716 let origin = match intent.get("origin").and_then(|v| v.as_str()) {
717 Some("EGP") => Origin::EGP,
718 Some("INCOMPLETE") => Origin::Incomplete,
719 _ => Origin::IGP,
720 };
721 let intent = UpdateIntent {
722 withdrawn: strings("withdrawn_routes"),
723 nlri: strings("nlri"),
724 next_hop,
725 as_path: intent
726 .get("as_path")
727 .and_then(|v| v.as_array())
728 .map(|a| {
729 a.iter()
730 .filter_map(|v| v.as_u64())
731 .map(|n| n as u32)
732 .collect()
733 })
734 .unwrap_or_default(),
735 origin,
736 med: intent.get("med").and_then(|v| v.as_u64()).map(|n| n as u32),
737 local_pref: intent
738 .get("local_pref")
739 .and_then(|v| v.as_u64())
740 .map(|n| n as u32),
741 };
742 encode(build_update(&intent, peer_asn4)?)
743 }
744 other => bail!("unknown BGP action intent kind {other:?}"),
745 }
746}