Skip to main content

bgpkit_parser/parser/mrt/messages/
bgp4mp.rs

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