Skip to main content

bgpkit_parser/parser/mrt/messages/
bgp4mp.rs

1use crate::error::{EncodingError, ParserError};
2use crate::models::*;
3use crate::parser::bgp::messages::parse_bgp_message;
4use crate::parser::{encode_asn, encode_ipaddr, ReadUtils};
5use bytes::{Buf, BufMut, Bytes, BytesMut};
6use std::convert::TryFrom;
7use std::net::{IpAddr, Ipv4Addr};
8
9pub(crate) fn is_short_zebra_open(data: &Bytes, asn_len: &AsnLength) -> bool {
10    if !matches!(asn_len, AsnLength::Bits16) {
11        return false;
12    }
13
14    let asn_bytes = asn_len.bytes() * 2;
15    data.len() >= asn_bytes + 19
16        && data[asn_bytes..asn_bytes + 16] == [0xff; 16]
17        && data[asn_bytes + 18] == BgpMessageType::OPEN as u8
18}
19
20pub(crate) fn uses_zebra_compat(sub_type: u16, data: &Bytes) -> bool {
21    match Bgp4MpType::try_from(sub_type) {
22        Ok(Bgp4MpType::StateChange) => data.len() == 8,
23        Ok(
24            Bgp4MpType::Message
25            | Bgp4MpType::MessageLocal
26            | Bgp4MpType::MessageAddpath
27            | Bgp4MpType::MessageLocalAddpath,
28        ) => is_short_zebra_open(data, &AsnLength::Bits16),
29        _ => false,
30    }
31}
32
33/// Parse MRT BGP4MP type
34///
35/// RFC: <https://www.rfc-editor.org/rfc/rfc6396#section-4.4>
36///
37pub fn parse_bgp4mp(sub_type: u16, input: Bytes) -> Result<Bgp4MpEnum, ParserError> {
38    let bgp4mp_type: Bgp4MpType = Bgp4MpType::try_from(sub_type)?;
39    let msg: Bgp4MpEnum = match bgp4mp_type {
40        Bgp4MpType::StateChange => Bgp4MpEnum::StateChange(parse_bgp4mp_state_change(
41            input,
42            AsnLength::Bits16,
43            &bgp4mp_type,
44        )?),
45        Bgp4MpType::StateChangeAs4 => Bgp4MpEnum::StateChange(parse_bgp4mp_state_change(
46            input,
47            AsnLength::Bits32,
48            &bgp4mp_type,
49        )?),
50        Bgp4MpType::Message | Bgp4MpType::MessageLocal => Bgp4MpEnum::Message(
51            parse_bgp4mp_message(input, false, AsnLength::Bits16, &bgp4mp_type)?,
52        ),
53        Bgp4MpType::MessageAs4 | Bgp4MpType::MessageAs4Local => Bgp4MpEnum::Message(
54            parse_bgp4mp_message(input, false, AsnLength::Bits32, &bgp4mp_type)?,
55        ),
56        Bgp4MpType::MessageAddpath | Bgp4MpType::MessageLocalAddpath => Bgp4MpEnum::Message(
57            parse_bgp4mp_message(input, true, AsnLength::Bits16, &bgp4mp_type)?,
58        ),
59        Bgp4MpType::MessageAs4Addpath | Bgp4MpType::MessageLocalAs4Addpath => Bgp4MpEnum::Message(
60            parse_bgp4mp_message(input, true, AsnLength::Bits32, &bgp4mp_type)?,
61        ),
62    };
63
64    Ok(msg)
65}
66
67/// Return the embedded BGP message length in a BGP4MP message body.
68///
69/// The BGP4MP envelope is defined by RFC 6396 Section 4.4.2 for 16-bit ASNs
70/// and Section 4.4.3 for AS4 variants:
71/// <https://www.rfc-editor.org/rfc/rfc6396#section-4.4.2>
72/// <https://www.rfc-editor.org/rfc/rfc6396#section-4.4.3>
73///
74/// RFC 8050 Section 3 defines the ADDPATH BGP4MP subtypes that reuse the same
75/// envelope before the encapsulated BGP message:
76/// <https://www.rfc-editor.org/rfc/rfc8050#section-3>
77///
78/// `total_size` is the MRT message body length. Subtracting the peer/local ASNs,
79/// interface index, AFI, and peer/local IP addresses leaves the encapsulated BGP
80/// message length.
81/*
824.4.2.  BGP4MP_MESSAGE Subtype:
83   0                   1                   2                   3
84   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
85  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
86  |         Peer AS Number        |        Local AS Number        |
87  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
88  |        Interface Index        |        Address Family         |
89  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
90  |                      Peer IP Address (variable)               |
91  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
92  |                      Local IP Address (variable)              |
93  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
94  |                    BGP Message... (variable)
95  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
96
974.4.3.  BGP4MP_MESSAGE_AS4 Subtype
98  0                   1                   2                   3
99   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
100  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
101  |                         Peer AS Number                        |
102  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
103  |                         Local AS Number                       |
104  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
105  |        Interface Index        |        Address Family         |
106  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
107  |                      Peer IP Address (variable)               |
108  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
109  |                      Local IP Address (variable)              |
110  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
111  |                    BGP Message... (variable)
112  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
113*/
114pub(crate) fn validate_bgp4mp_afi(afi: &Afi) -> Result<(), ParserError> {
115    match afi {
116        Afi::Ipv4 | Afi::Ipv6 => Ok(()),
117        Afi::LinkState => Err(ParserError::ParseError(
118            "Link-State AFI is invalid in a BGP4MP envelope".to_string(),
119        )),
120    }
121}
122
123pub(crate) fn bgp4mp_message_payload_len(
124    afi: &Afi,
125    asn_len: &AsnLength,
126    total_size: usize,
127) -> Result<usize, ParserError> {
128    validate_bgp4mp_afi(afi)?;
129    let ip_size = if matches!(afi, Afi::Ipv4) {
130        4 * 2
131    } else {
132        16 * 2
133    };
134    let asn_size = match asn_len {
135        AsnLength::Bits16 => 2 * 2,
136        AsnLength::Bits32 => 2 * 4,
137    };
138    // Saturating: on a truncated/inconsistent record the caller compares the
139    // result against `data.remaining()` and rejects the mismatch, so a
140    // saturated 0 simply fails that check instead of underflowing.
141    Ok(total_size
142        .saturating_sub(asn_size)
143        .saturating_sub(2)
144        .saturating_sub(2)
145        .saturating_sub(ip_size))
146}
147/*
148   0                   1                   2                   3
149   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
150  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
151  |         Peer AS Number        |        Local AS Number        |
152  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
153  |        Interface Index        |        Address Family         |
154  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
155  |                      Peer IP Address (variable)               |
156  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
157  |                      Local IP Address (variable)              |
158  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
159  |                    BGP Message... (variable)
160  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
161*/
162pub fn parse_bgp4mp_message(
163    mut data: Bytes,
164    add_path: bool,
165    asn_len: AsnLength,
166    msg_type: &Bgp4MpType,
167) -> Result<Bgp4MpMessage, ParserError> {
168    let total_size = data.len();
169    let is_short_zebra_open = is_short_zebra_open(&data, &asn_len);
170
171    let peer_asn: Asn = data.read_asn(asn_len)?;
172    let local_asn: Asn = data.read_asn(asn_len)?;
173
174    // Old Zebra versions omitted the interface index, AFI, and peer/local IP
175    // addresses from some BGP OPEN records. The BGP marker therefore follows
176    // the two 16-bit ASNs immediately. Limit this compatibility path to that
177    // exact signature so other malformed envelopes still fail normally.
178    if is_short_zebra_open {
179        let bgp_message = parse_bgp_message(&mut data, add_path, &asn_len)?;
180        return Ok(Bgp4MpMessage {
181            msg_type: *msg_type,
182            peer_asn,
183            local_asn,
184            interface_index: 0,
185            peer_ip: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
186            local_ip: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
187            bgp_message,
188        });
189    }
190
191    let interface_index: u16 = data.read_u16()?;
192    let afi: Afi = data.read_afi()?;
193    let should_read = bgp4mp_message_payload_len(&afi, &asn_len, total_size)?;
194    let peer_ip = data.read_address(&afi)?;
195    let local_ip = data.read_address(&afi)?;
196
197    if should_read != data.remaining() {
198        return Err(ParserError::TruncatedMsg(format!(
199            "truncated bgp4mp message: should read {} bytes, have {} bytes available",
200            should_read,
201            data.remaining()
202        )));
203    }
204    let bgp_message: BgpMessage = parse_bgp_message(&mut data, add_path, &asn_len)?;
205
206    Ok(Bgp4MpMessage {
207        msg_type: *msg_type,
208        peer_asn,
209        local_asn,
210        interface_index,
211        peer_ip,
212        local_ip,
213        bgp_message,
214    })
215}
216
217impl Bgp4MpMessage {
218    pub fn encode(&self, asn_len: AsnLength) -> Result<Bytes, EncodingError> {
219        let mut bytes = BytesMut::new();
220        bytes.extend(encode_asn(&self.peer_asn, &asn_len));
221        bytes.extend(encode_asn(&self.local_asn, &asn_len));
222        bytes.put_u16(self.interface_index);
223        bytes.put_u16(address_family(&self.peer_ip));
224        bytes.extend(encode_ipaddr(&self.peer_ip));
225        bytes.extend(encode_ipaddr(&self.local_ip));
226        bytes.put_slice(&self.bgp_message.encode(asn_len)?);
227        Ok(bytes.freeze())
228    }
229}
230
231/*
232   0                   1                   2                   3
233   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
234  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
235  |         Peer AS Number        |        Local AS Number        |
236  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
237  |        Interface Index        |        Address Family         |
238  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
239  |                      Peer IP Address (variable)               |
240  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
241  |                      Local IP Address (variable)              |
242  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
243  |            Old State          |          New State            |
244  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
245
246   0                   1                   2                   3
247   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
248  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
249  |                         Peer AS Number                        |
250  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
251  |                         Local AS Number                       |
252  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
253  |        Interface Index        |        Address Family         |
254  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
255  |                      Peer IP Address (variable)               |
256  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
257  |                      Local IP Address (variable)              |
258  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
259  |            Old State          |          New State            |
260  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
261*/
262pub fn parse_bgp4mp_state_change(
263    mut input: Bytes,
264    asn_len: AsnLength,
265    msg_type: &Bgp4MpType,
266) -> Result<Bgp4MpStateChange, ParserError> {
267    let is_short_zebra_state_change = matches!(asn_len, AsnLength::Bits16) && input.len() == 8;
268    let peer_asn: Asn = input.read_asn(asn_len)?;
269    let local_asn: Asn = input.read_asn(asn_len)?;
270
271    // Work around a historical Zebra corruption where an 8-byte state-change
272    // record contains only the two ASNs and the old/new FSM states.
273    if is_short_zebra_state_change {
274        let old_state = BgpState::try_from(input.read_u16()?)?;
275        let new_state = BgpState::try_from(input.read_u16()?)?;
276        return Ok(Bgp4MpStateChange {
277            msg_type: *msg_type,
278            peer_asn,
279            local_asn,
280            interface_index: 0,
281            peer_ip: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
282            local_addr: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
283            old_state,
284            new_state,
285        });
286    }
287
288    let interface_index: u16 = input.read_u16()?;
289    let address_family: Afi = input.read_afi()?;
290    validate_bgp4mp_afi(&address_family)?;
291    let peer_ip = input.read_address(&address_family)?;
292    let local_addr = input.read_address(&address_family)?;
293    let old_state = BgpState::try_from(input.read_u16()?)?;
294    let new_state = BgpState::try_from(input.read_u16()?)?;
295    Ok(Bgp4MpStateChange {
296        msg_type: *msg_type,
297        peer_asn,
298        local_asn,
299        interface_index,
300        peer_ip,
301        local_addr,
302        old_state,
303        new_state,
304    })
305}
306
307impl Bgp4MpStateChange {
308    pub fn encode(&self, asn_len: AsnLength) -> Bytes {
309        let mut bytes = BytesMut::new();
310        bytes.extend(encode_asn(&self.peer_asn, &asn_len));
311        bytes.extend(encode_asn(&self.local_asn, &asn_len));
312        bytes.put_u16(self.interface_index);
313        bytes.put_u16(address_family(&self.peer_ip));
314        bytes.extend(encode_ipaddr(&self.peer_ip));
315        bytes.extend(encode_ipaddr(&self.local_addr));
316        bytes.put_u16(self.old_state as u16);
317        bytes.put_u16(self.new_state as u16);
318        bytes.freeze()
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325    use std::net::IpAddr;
326    use std::str::FromStr;
327
328    #[test]
329    fn test_bgp4mp_message_encode_uses_subtype_asn_width() {
330        let message = Bgp4MpMessage {
331            msg_type: Bgp4MpType::Message,
332            peer_asn: Asn::new_32bit(65000),
333            local_asn: Asn::new_32bit(65001),
334            interface_index: 1,
335            peer_ip: IpAddr::from_str("10.0.0.1").unwrap(),
336            local_ip: IpAddr::from_str("10.0.0.2").unwrap(),
337            bgp_message: BgpMessage::KeepAlive,
338        };
339
340        let encoded = message.encode(AsnLength::Bits16).unwrap();
341        let parsed = parse_bgp4mp(Bgp4MpType::Message as u16, encoded).unwrap();
342
343        match parsed {
344            Bgp4MpEnum::Message(parsed) => {
345                assert_eq!(parsed.peer_asn, Asn::new_16bit(65000));
346                assert_eq!(parsed.local_asn, Asn::new_16bit(65001));
347                assert_eq!(parsed.interface_index, 1);
348                assert_eq!(parsed.peer_ip, message.peer_ip);
349                assert_eq!(parsed.local_ip, message.local_ip);
350                assert_eq!(parsed.bgp_message, BgpMessage::KeepAlive);
351            }
352            other => panic!("unexpected BGP4MP message: {other:?}"),
353        }
354    }
355
356    #[test]
357    fn test_bgp4mp_message_rejects_link_state_envelope_afi() {
358        let mut data = BytesMut::new();
359        data.put_u16(65000);
360        data.put_u16(65001);
361        data.put_u16(0);
362        data.put_u16(Afi::LinkState as u16);
363        data.put_slice(&BgpMessage::KeepAlive.encode(AsnLength::Bits16).unwrap());
364
365        let error = match parse_bgp4mp(Bgp4MpType::Message as u16, data.freeze()) {
366            Err(error) => error,
367            Ok(message) => panic!("unexpectedly parsed BGP4MP message: {message:?}"),
368        };
369        assert!(matches!(
370            error,
371            ParserError::ParseError(message)
372                if message == "Link-State AFI is invalid in a BGP4MP envelope"
373        ));
374    }
375
376    #[test]
377    fn test_bgp4mp_state_change_rejects_link_state_envelope_afi() {
378        let mut data = BytesMut::new();
379        data.put_u16(65000);
380        data.put_u16(65001);
381        data.put_u16(0);
382        data.put_u16(Afi::LinkState as u16);
383        data.put_u16(BgpState::Idle as u16);
384        data.put_u16(BgpState::Connect as u16);
385
386        let error = match parse_bgp4mp(Bgp4MpType::StateChange as u16, data.freeze()) {
387            Err(error) => error,
388            Ok(message) => panic!("unexpectedly parsed BGP4MP state change: {message:?}"),
389        };
390        assert!(matches!(
391            error,
392            ParserError::ParseError(message)
393                if message == "Link-State AFI is invalid in a BGP4MP envelope"
394        ));
395    }
396}