1use crate::server::bgp::wire;
5use anyhow::{bail, ensure, Context, Result};
6use netgauze_bgp_pkt::BgpMessage;
7use serde_json::{json, Value as Json};
8use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
9
10pub const VERSION: u8 = 3;
11pub const HEADER_LEN: usize = 6;
12pub const PEER_HEADER_LEN: usize = 42;
13pub const MAX_MESSAGE: usize = 256 * 1024;
16pub const MAX_ITEMS: usize = 1024;
18
19pub const ROUTE_MONITORING: u8 = 0;
20pub const STATISTICS: u8 = 1;
21pub const PEER_DOWN: u8 = 2;
22pub const PEER_UP: u8 = 3;
23pub const INITIATION: u8 = 4;
24pub const TERMINATION: u8 = 5;
25pub const ROUTE_MIRRORING: u8 = 6;
26
27pub const FLAG_V6: u8 = 0x80;
28pub const FLAG_POST_POLICY: u8 = 0x40;
29pub const FLAG_LEGACY_AS: u8 = 0x20;
30pub const FLAG_ADJ_RIB_OUT: u8 = 0x10;
31
32pub fn message_len(buf: &[u8]) -> Result<Option<usize>> {
34 if buf.len() < HEADER_LEN {
35 return Ok(None);
36 }
37 ensure!(
38 buf[0] == VERSION,
39 "BMP version {} is not supported (only 3)",
40 buf[0]
41 );
42 let len = u32::from_be_bytes(buf[1..5].try_into()?) as usize;
43 ensure!(
44 len >= HEADER_LEN,
45 "BMP message length {len} is below the header"
46 );
47 ensure!(
48 len <= MAX_MESSAGE,
49 "BMP message of {len} bytes exceeds the {MAX_MESSAGE}-byte bound"
50 );
51 Ok(Some(len))
52}
53
54pub fn frame(kind: u8, body: &[u8]) -> Vec<u8> {
56 let mut out = Vec::with_capacity(HEADER_LEN + body.len());
57 out.push(VERSION);
58 out.extend(((HEADER_LEN + body.len()) as u32).to_be_bytes());
59 out.push(kind);
60 out.extend(body);
61 out
62}
63
64#[derive(Clone, Debug, PartialEq)]
65pub struct PeerHeader {
66 pub peer_type: u8,
67 pub flags: u8,
68 pub distinguisher: [u8; 8],
69 pub address: IpAddr,
70 pub asn: u32,
71 pub bgp_id: Ipv4Addr,
72 pub seconds: u32,
73 pub micros: u32,
74}
75
76fn address(b: &[u8], v6: bool) -> IpAddr {
77 let raw: [u8; 16] = b[..16].try_into().unwrap_or([0; 16]);
78 if v6 {
79 IpAddr::V6(Ipv6Addr::from(raw))
80 } else {
81 IpAddr::V4(Ipv4Addr::new(raw[12], raw[13], raw[14], raw[15]))
82 }
83}
84
85fn put_address(out: &mut Vec<u8>, a: IpAddr) {
86 match a {
87 IpAddr::V4(v4) => {
88 out.extend([0u8; 12]);
89 out.extend(v4.octets());
90 }
91 IpAddr::V6(v6) => out.extend(v6.octets()),
92 }
93}
94
95impl PeerHeader {
96 pub fn read(b: &[u8]) -> Result<Self> {
97 ensure!(
98 b.len() >= PEER_HEADER_LEN,
99 "the per-peer header is truncated"
100 );
101 let flags = b[1];
102 Ok(Self {
103 peer_type: b[0],
104 flags,
105 distinguisher: b[2..10].try_into()?,
106 address: address(&b[10..26], flags & FLAG_V6 != 0),
107 asn: u32::from_be_bytes(b[26..30].try_into()?),
108 bgp_id: Ipv4Addr::from(<[u8; 4]>::try_from(&b[30..34])?),
109 seconds: u32::from_be_bytes(b[34..38].try_into()?),
110 micros: u32::from_be_bytes(b[38..42].try_into()?),
111 })
112 }
113
114 pub fn write(&self, out: &mut Vec<u8>) {
115 out.push(self.peer_type);
116 let v6 = if self.address.is_ipv6() { FLAG_V6 } else { 0 };
117 out.push((self.flags & !FLAG_V6) | v6);
118 out.extend(self.distinguisher);
119 put_address(out, self.address);
120 out.extend(self.asn.to_be_bytes());
121 out.extend(self.bgp_id.octets());
122 out.extend(self.seconds.to_be_bytes());
123 out.extend(self.micros.to_be_bytes());
124 }
125
126 pub fn four_octet_as(&self) -> bool {
127 self.flags & FLAG_LEGACY_AS == 0
128 }
129
130 pub fn to_json(&self) -> Json {
131 let timestamp = (self.seconds != 0)
132 .then(|| {
133 chrono::DateTime::from_timestamp(
134 self.seconds as i64,
135 self.micros.min(999_999) * 1000,
136 )
137 .map(|t| t.to_rfc3339_opts(chrono::SecondsFormat::Micros, true))
138 })
139 .flatten();
140 json!({
141 "type": peer_type_name(self.peer_type),
142 "address": self.address.to_string(),
143 "asn": self.asn,
144 "bgp_id": self.bgp_id.to_string(),
145 "distinguisher": distinguisher(&self.distinguisher),
146 "post_policy": self.flags & FLAG_POST_POLICY != 0,
147 "adj_rib_out": self.flags & FLAG_ADJ_RIB_OUT != 0,
148 "four_octet_as": self.four_octet_as(),
149 "timestamp": timestamp,
150 })
151 }
152}
153
154pub fn peer_type_name(t: u8) -> String {
155 match t {
156 0 => "global".into(),
157 1 => "rd_instance".into(),
158 2 => "local_instance".into(),
159 3 => "loc_rib".into(),
160 n => format!("type_{n}"),
161 }
162}
163
164pub fn peer_type_code(name: &str) -> Option<u8> {
165 Some(match name {
166 "global" => 0,
167 "rd_instance" => 1,
168 "local_instance" => 2,
169 "loc_rib" => 3,
170 _ => return None,
171 })
172}
173
174pub fn distinguisher(d: &[u8; 8]) -> String {
176 let t = u16::from_be_bytes([d[0], d[1]]);
177 match t {
178 0 => format!(
179 "{}:{}",
180 u16::from_be_bytes([d[2], d[3]]),
181 u32::from_be_bytes([d[4], d[5], d[6], d[7]])
182 ),
183 1 => format!(
184 "{}:{}",
185 Ipv4Addr::new(d[2], d[3], d[4], d[5]),
186 u16::from_be_bytes([d[6], d[7]])
187 ),
188 2 => format!(
189 "{}:{}",
190 u32::from_be_bytes([d[2], d[3], d[4], d[5]]),
191 u16::from_be_bytes([d[6], d[7]])
192 ),
193 _ => format!("type{t}:{}", hex::encode(&d[2..])),
194 }
195}
196
197pub fn parse_distinguisher(s: &str) -> Result<[u8; 8]> {
198 let (a, n) = s
199 .rsplit_once(':')
200 .context("a route distinguisher is admin:assigned, e.g. 65000:1 or 192.0.2.1:7")?;
201 let mut d = [0u8; 8];
202 if let Ok(ip) = a.parse::<Ipv4Addr>() {
203 d[1] = 1;
204 d[2..6].copy_from_slice(&ip.octets());
205 d[6..8].copy_from_slice(&n.parse::<u16>()?.to_be_bytes());
206 } else {
207 let asn: u32 = a.parse().context("the distinguisher's admin part")?;
208 match u16::try_from(asn) {
209 Ok(a2) => {
210 d[2..4].copy_from_slice(&a2.to_be_bytes());
211 d[4..8].copy_from_slice(&n.parse::<u32>()?.to_be_bytes());
212 }
213 Err(_) => {
214 d[1] = 2;
215 d[2..6].copy_from_slice(&asn.to_be_bytes());
216 d[6..8].copy_from_slice(&n.parse::<u16>()?.to_be_bytes());
217 }
218 }
219 }
220 Ok(d)
221}
222
223pub fn tlvs(mut b: &[u8]) -> Result<Vec<(u16, &[u8])>> {
225 let mut out = Vec::new();
226 while !b.is_empty() {
227 ensure!(b.len() >= 4, "a TLV header is truncated");
228 ensure!(out.len() < MAX_ITEMS, "more than {MAX_ITEMS} TLVs");
229 let t = u16::from_be_bytes([b[0], b[1]]);
230 let l = u16::from_be_bytes([b[2], b[3]]) as usize;
231 ensure!(b.len() >= 4 + l, "a TLV value is truncated");
232 out.push((t, &b[4..4 + l]));
233 b = &b[4 + l..];
234 }
235 Ok(out)
236}
237
238pub fn put_tlv(out: &mut Vec<u8>, t: u16, v: &[u8]) -> Result<()> {
239 let l = u16::try_from(v.len()).context("a TLV value is over 65535 bytes")?;
240 out.extend(t.to_be_bytes());
241 out.extend(l.to_be_bytes());
242 out.extend(v);
243 Ok(())
244}
245
246fn text(v: &[u8]) -> String {
247 String::from_utf8_lossy(v).into_owned()
248}
249
250pub fn bgp_pdu(b: &[u8]) -> Result<(&[u8], &[u8])> {
252 ensure!(b.len() >= wire::BGP_HEADER_LEN, "the BGP PDU is truncated");
253 ensure!(
254 b[..16] == wire::BGP_MARKER,
255 "the BGP PDU's marker is not all ones"
256 );
257 let len = u16::from_be_bytes([b[16], b[17]]) as usize;
258 ensure!(
259 len >= wire::BGP_HEADER_LEN && len <= b.len(),
260 "the BGP PDU's length {len} does not fit the BMP message"
261 );
262 Ok((&b[..len], &b[len..]))
263}
264
265fn open_json(pdu: &[u8]) -> Json {
266 match wire::decode(pdu, true) {
267 Ok(BgpMessage::Open(o)) => json!({
268 "asn": o.my_asn4(),
269 "hold_time": o.hold_time(),
270 "bgp_id": o.bgp_id().to_string(),
271 "capabilities": wire::capabilities_to_json(&o),
272 }),
273 Ok(_) => json!({"decode_error": "not an OPEN message"}),
274 Err(e) => json!({"decode_error": e.to_string()}),
275 }
276}
277
278fn notification_json(pdu: &[u8]) -> Json {
279 if pdu.len() >= wire::BGP_HEADER_LEN + 2 && pdu[18] == wire::MSG_NOTIFICATION {
280 let (code, sub) = (pdu[19], pdu[20]);
281 json!({"code": code, "subcode": sub, "name": wire::error_name(code), "subcode_name": wire::error_subcode_name(code, sub)})
282 } else {
283 json!({"decode_error": "not a NOTIFICATION message"})
284 }
285}
286
287pub fn termination_reason(code: u16) -> &'static str {
288 match code {
289 0 => "administratively_closed",
290 1 => "unspecified",
291 2 => "out_of_resources",
292 3 => "redundant_connection",
293 4 => "permanently_administratively_closed",
294 _ => "unknown",
295 }
296}
297
298pub fn termination_code(name: &str) -> Option<u16> {
299 (0..=4).find(|c| termination_reason(*c) == name)
300}
301
302pub fn peer_down_reason(code: u8) -> &'static str {
303 match code {
304 1 => "local_notification",
305 2 => "local_no_notification",
306 3 => "remote_notification",
307 4 => "remote_no_data",
308 5 => "peer_deconfigured",
309 6 => "local_system_closed",
310 _ => "unknown",
311 }
312}
313
314pub fn peer_down_code(name: &str) -> Option<u8> {
315 (1..=6).find(|c| peer_down_reason(*c) == name)
316}
317
318pub fn stat_name(t: u16) -> String {
320 let name = match t {
321 0 => "rejected_prefixes",
322 1 => "duplicate_prefix_advertisements",
323 2 => "duplicate_withdraws",
324 3 => "invalidated_cluster_list_loop",
325 4 => "invalidated_as_path_loop",
326 5 => "invalidated_originator_id",
327 6 => "invalidated_as_confed_loop",
328 7 => "adj_rib_in_routes",
329 8 => "loc_rib_routes",
330 9 => "adj_rib_in_routes_per_afi_safi",
331 10 => "loc_rib_routes_per_afi_safi",
332 11 => "updates_treated_as_withdraw",
333 12 => "prefixes_treated_as_withdraw",
334 13 => "duplicate_update_messages",
335 14 => "adj_rib_out_pre_policy_routes",
336 15 => "adj_rib_out_post_policy_routes",
337 16 => "adj_rib_out_pre_policy_routes_per_afi_safi",
338 17 => "adj_rib_out_post_policy_routes_per_afi_safi",
339 n => return format!("type_{n}"),
340 };
341 name.into()
342}
343
344pub fn stat_code(name: &str) -> Option<u16> {
345 (0..=17).find(|c| stat_name(*c) == name)
346}
347
348pub fn stat_shape(t: u16) -> (bool, bool) {
351 match t {
352 7 | 8 | 14 | 15 => (true, false),
353 9 | 10 | 16 | 17 => (true, true),
354 _ => (false, false),
355 }
356}
357
358pub fn describe(kind: u8, body: &[u8]) -> Result<Option<(&'static str, Json)>> {
360 Ok(Some(match kind {
361 INITIATION | TERMINATION => {
362 let mut data = json!({"strings": []});
363 for (t, v) in tlvs(body)? {
364 match (kind, t) {
365 (_, 0) => data["strings"].as_array_mut().unwrap().push(json!(text(v))),
366 (INITIATION, 1) => data["sys_descr"] = json!(text(v)),
367 (INITIATION, 2) => data["sys_name"] = json!(text(v)),
368 (TERMINATION, 1) => {
369 ensure!(v.len() == 2, "a termination reason is two bytes");
370 let code = u16::from_be_bytes([v[0], v[1]]);
371 data["reason"] = json!(termination_reason(code));
372 data["reason_code"] = json!(code);
373 }
374 _ => {}
375 }
376 }
377 let id = if kind == INITIATION {
378 "bmp_initiation"
379 } else {
380 "bmp_termination"
381 };
382 (id, data)
383 }
384 ROUTE_MONITORING | STATISTICS | PEER_DOWN | PEER_UP | ROUTE_MIRRORING => {
385 let peer = PeerHeader::read(body)?;
386 let rest = &body[PEER_HEADER_LEN..];
387 let mut data = json!({"peer": peer.to_json()});
388 let id = match kind {
389 ROUTE_MONITORING => {
390 let (pdu, _) = bgp_pdu(rest)?;
391 match wire::decode(pdu, peer.four_octet_as()) {
392 Ok(BgpMessage::Update(u)) => data["update"] = wire::update_to_json(&u),
393 Ok(_) => data["decode_error"] = json!("the BGP PDU is not an UPDATE"),
394 Err(e) => data["decode_error"] = json!(e.to_string()),
395 }
396 "bmp_route_monitoring"
397 }
398 STATISTICS => {
399 ensure!(rest.len() >= 4, "the statistics count is truncated");
400 let mut counters = Vec::new();
401 for (t, v) in tlvs(&rest[4..])? {
402 let mut c = json!({"type": stat_name(t), "type_code": t});
403 match v.len() {
404 4 => c["value"] = json!(u32::from_be_bytes(v.try_into()?)),
405 8 => c["value"] = json!(u64::from_be_bytes(v.try_into()?)),
406 11 => {
407 c["afi"] = json!(u16::from_be_bytes([v[0], v[1]]));
408 c["safi"] = json!(v[2]);
409 c["value"] = json!(u64::from_be_bytes(v[3..11].try_into()?));
410 }
411 n => c["value_length"] = json!(n),
412 }
413 counters.push(c);
414 }
415 data["counters"] = json!(counters);
416 "bmp_statistics"
417 }
418 PEER_DOWN => {
419 ensure!(!rest.is_empty(), "the peer down reason is missing");
420 data["reason"] = json!(peer_down_reason(rest[0]));
421 data["reason_code"] = json!(rest[0]);
422 let tail = &rest[1..];
423 match rest[0] {
424 1 | 3 => data["notification"] = notification_json(bgp_pdu(tail)?.0),
425 2 => {
426 ensure!(tail.len() >= 2, "the FSM event code is truncated");
427 data["fsm_event"] = json!(u16::from_be_bytes([tail[0], tail[1]]));
428 }
429 _ => {}
430 }
431 "bmp_peer_down"
432 }
433 PEER_UP => {
434 ensure!(rest.len() >= 20, "the peer up header is truncated");
435 data["local_address"] =
436 json!(address(&rest[..16], peer.flags & FLAG_V6 != 0).to_string());
437 data["local_port"] = json!(u16::from_be_bytes([rest[16], rest[17]]));
438 data["remote_port"] = json!(u16::from_be_bytes([rest[18], rest[19]]));
439 let (sent, after) = bgp_pdu(&rest[20..])?;
440 let (received, info) = bgp_pdu(after)?;
441 data["sent_open"] = open_json(sent);
442 data["received_open"] = open_json(received);
443 data["information"] = json!(tlvs(info)?
444 .into_iter()
445 .map(|(t, v)| json!({"type": t, "value": text(v)}))
446 .collect::<Vec<_>>());
447 "bmp_peer_up"
448 }
449 _ => {
450 let mut messages = Vec::new();
451 for (t, v) in tlvs(rest)? {
452 match t {
453 0 => messages.push(
454 match wire::decode(bgp_pdu(v)?.0, peer.four_octet_as()) {
455 Ok(BgpMessage::Update(u)) => {
456 json!({"update": wire::update_to_json(&u)})
457 }
458 Ok(m) => json!({"bgp_message": format!("{:?}", m.get_type())}),
459 Err(e) => json!({"decode_error": e.to_string()}),
460 },
461 ),
462 1 if v.len() == 2 => messages.push(
463 json!({"information": match u16::from_be_bytes([v[0], v[1]]) {
464 0 => "errored_pdu".to_string(),
465 1 => "messages_lost".to_string(),
466 n => format!("code_{n}"),
467 }}),
468 ),
469 _ => {}
470 }
471 }
472 data["messages"] = json!(messages);
473 "bmp_route_mirroring"
474 }
475 };
476 (id, data)
477 }
478 _ => return Ok(None),
479 }))
480}
481
482pub fn initiation(sys_name: &str, sys_descr: &str, strings: &[String]) -> Result<Vec<u8>> {
484 let mut b = Vec::new();
485 put_tlv(&mut b, 2, sys_name.as_bytes())?;
486 put_tlv(&mut b, 1, sys_descr.as_bytes())?;
487 for s in strings {
488 put_tlv(&mut b, 0, s.as_bytes())?;
489 }
490 Ok(frame(INITIATION, &b))
491}
492
493pub fn termination(reason: u16, message: Option<&str>) -> Result<Vec<u8>> {
494 let mut b = Vec::new();
495 if let Some(m) = message {
496 put_tlv(&mut b, 0, m.as_bytes())?;
497 }
498 put_tlv(&mut b, 1, &reason.to_be_bytes())?;
499 Ok(frame(TERMINATION, &b))
500}
501
502pub fn with_peer(kind: u8, peer: &PeerHeader, rest: &[u8]) -> Result<Vec<u8>> {
503 let mut b = Vec::with_capacity(PEER_HEADER_LEN + rest.len());
504 peer.write(&mut b);
505 b.extend(rest);
506 if b.len() + HEADER_LEN > MAX_MESSAGE {
507 bail!("the BMP message would exceed {MAX_MESSAGE} bytes");
508 }
509 Ok(frame(kind, &b))
510}