Skip to main content

bgpkit_parser/
error.rs

1/*!
2error module defines the error types used in bgpkit-parser.
3*/
4use crate::models::{Afi, AttrType, Bgp4MpType, BgpState, EntryType, Safi, TableDumpV2Type};
5use num_enum::TryFromPrimitiveError;
6#[cfg(feature = "oneio")]
7use oneio::OneIoError;
8use std::fmt::{Display, Formatter};
9use std::io::ErrorKind;
10use std::{error::Error, fmt, io};
11
12#[derive(Debug)]
13pub enum ParserError {
14    IoError(io::Error),
15    EofError(io::Error),
16    #[cfg(feature = "oneio")]
17    OneIoError(OneIoError),
18    EofExpected,
19    ParseError(String),
20    TruncatedMsg(String),
21    Unsupported(String),
22    FilterError(String),
23    /// NLRI length field is inconsistent with content
24    /// (total_bits < minimum required, or underflow in prefix calculation)
25    InvalidLabeledNlriLength,
26    /// Input ended before completing NLRI structure
27    TruncatedLabeledNlri,
28    /// Input ended in the middle of prefix data
29    TruncatedPrefix,
30    /// Exceeded configured max_labels without finding Bottom-of-Stack bit
31    MaxLabelStackDepthExceeded,
32    /// Exceeded peer-advertised max labels (Multiple Labels Capability)
33    /// Per RFC 8277 §2.1, this should be treated as a withdrawal
34    PeerMaxLabelsExceeded,
35    /// Invalid prefix in NLRI
36    InvalidPrefix,
37}
38
39impl Error for ParserError {}
40
41/// Errors that can occur when encoding BGP/MRT messages to wire format.
42///
43/// These arise when in-memory data structures contain values that cannot be
44/// represented in their wire-format fields (e.g. an AS_PATH segment with more
45/// than 255 ASes, or an attribute value exceeding 65535 bytes). All such
46/// conditions were previously handled by silently truncating — see issue #313.
47#[derive(Debug, Clone, PartialEq, Eq)]
48#[non_exhaustive]
49pub enum EncodingError {
50    /// A value exceeded the maximum size that fits in its wire-format field.
51    ///
52    /// `field` names the wire field (e.g. `"AS_PATH segment count"`,
53    /// `"BGP attribute value length"`). `actual` is the byte/element count
54    /// that overflowed; `max` is the field's capacity.
55    ValueTooLarge {
56        field: &'static str,
57        actual: usize,
58        max: usize,
59    },
60    /// The value cannot be represented in wire format at all (e.g. ATTR_SET
61    /// encoding is not implemented, a labeled NLRI has an empty label stack,
62    /// or a BGP OPEN optional parameter uses the reserved type 255).
63    Unencodable { field: &'static str, reason: String },
64}
65
66impl EncodingError {
67    pub(crate) fn too_large(field: &'static str, actual: usize, max: usize) -> Self {
68        EncodingError::ValueTooLarge { field, actual, max }
69    }
70
71    pub(crate) fn unencodable(field: &'static str, reason: impl Into<String>) -> Self {
72        EncodingError::Unencodable {
73            field,
74            reason: reason.into(),
75        }
76    }
77}
78
79/// Check that `actual` fits within `max`, returning `actual` unchanged.
80///
81/// This is the single place where wire-format capacity checks happen, so
82/// non-power-of-two bounds (e.g. the 12-bit FlowSpec NLRI length) are an
83/// explicit, greppable decision at the call site.
84pub(crate) fn check_max(
85    field: &'static str,
86    actual: usize,
87    max: usize,
88) -> Result<usize, EncodingError> {
89    if actual > max {
90        return Err(EncodingError::too_large(field, actual, max));
91    }
92    Ok(actual)
93}
94
95impl Display for EncodingError {
96    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
97        match self {
98            EncodingError::ValueTooLarge { field, actual, max } => write!(
99                f,
100                "encoding error: {field} ({actual}) exceeds maximum ({max})"
101            ),
102            EncodingError::Unencodable { field, reason } => {
103                write!(f, "encoding error: {field} cannot be encoded: {reason}")
104            }
105        }
106    }
107}
108
109impl Error for EncodingError {}
110
111#[derive(Debug)]
112pub struct ParserErrorWithBytes {
113    pub error: ParserError,
114    pub bytes: Option<Vec<u8>>,
115}
116
117impl Display for ParserErrorWithBytes {
118    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
119        write!(f, "{}", self.error)
120    }
121}
122
123impl Error for ParserErrorWithBytes {}
124
125/// implement Display trait for Error which satistifies the std::error::Error
126/// trait's requirement (must implement Display and Debug traits, Debug already derived)
127impl Display for ParserError {
128    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
129        match self {
130            ParserError::IoError(e) => write!(f, "Error: {e}"),
131            ParserError::EofError(e) => write!(f, "Error: {e}"),
132            ParserError::ParseError(s) => write!(f, "Error: {s}"),
133            ParserError::TruncatedMsg(s) => write!(f, "Error: {s}"),
134            ParserError::Unsupported(s) => write!(f, "Error: {s}"),
135            ParserError::EofExpected => write!(f, "Error: reach end of file"),
136            #[cfg(feature = "oneio")]
137            ParserError::OneIoError(e) => write!(f, "Error: {e}"),
138            ParserError::FilterError(e) => write!(f, "Error: {e}"),
139            ParserError::InvalidLabeledNlriLength => {
140                write!(f, "Error: invalid labeled NLRI length field")
141            }
142            ParserError::TruncatedLabeledNlri => write!(f, "Error: truncated labeled NLRI"),
143            ParserError::TruncatedPrefix => write!(f, "Error: truncated prefix in NLRI"),
144            ParserError::MaxLabelStackDepthExceeded => write!(
145                f,
146                "Error: max label stack depth exceeded without finding BoS bit"
147            ),
148            ParserError::PeerMaxLabelsExceeded => write!(
149                f,
150                "Error: received more labels than peer advertised maximum"
151            ),
152            ParserError::InvalidPrefix => write!(f, "Error: invalid prefix in NLRI"),
153        }
154    }
155}
156
157#[cfg(feature = "oneio")]
158impl From<OneIoError> for ParserErrorWithBytes {
159    fn from(error: OneIoError) -> Self {
160        ParserErrorWithBytes {
161            error: ParserError::OneIoError(error),
162            bytes: None,
163        }
164    }
165}
166
167#[cfg(feature = "oneio")]
168impl From<OneIoError> for ParserError {
169    fn from(error: OneIoError) -> Self {
170        ParserError::OneIoError(error)
171    }
172}
173
174impl From<ParserError> for ParserErrorWithBytes {
175    fn from(error: ParserError) -> Self {
176        ParserErrorWithBytes { error, bytes: None }
177    }
178}
179
180impl From<io::Error> for ParserError {
181    fn from(io_error: io::Error) -> Self {
182        match io_error.kind() {
183            ErrorKind::UnexpectedEof => ParserError::EofError(io_error),
184            _ => ParserError::IoError(io_error),
185        }
186    }
187}
188
189impl From<TryFromPrimitiveError<Bgp4MpType>> for ParserError {
190    fn from(value: TryFromPrimitiveError<Bgp4MpType>) -> Self {
191        ParserError::ParseError(format!("cannot parse bgp4mp subtype: {}", value.number))
192    }
193}
194
195impl From<TryFromPrimitiveError<BgpState>> for ParserError {
196    fn from(value: TryFromPrimitiveError<BgpState>) -> Self {
197        ParserError::ParseError(format!("cannot parse bgp4mp state: {}", value.number))
198    }
199}
200
201impl From<TryFromPrimitiveError<TableDumpV2Type>> for ParserError {
202    fn from(value: TryFromPrimitiveError<TableDumpV2Type>) -> Self {
203        ParserError::ParseError(format!("cannot parse table dump v2 type: {}", value.number))
204    }
205}
206
207impl From<TryFromPrimitiveError<EntryType>> for ParserError {
208    fn from(value: TryFromPrimitiveError<EntryType>) -> Self {
209        ParserError::ParseError(format!("cannot parse entry type: {}", value.number))
210    }
211}
212
213impl From<TryFromPrimitiveError<Afi>> for ParserError {
214    fn from(value: TryFromPrimitiveError<Afi>) -> Self {
215        ParserError::ParseError(format!("Unknown AFI type: {}", value.number))
216    }
217}
218
219impl From<TryFromPrimitiveError<Safi>> for ParserError {
220    fn from(value: TryFromPrimitiveError<Safi>) -> Self {
221        ParserError::ParseError(format!("Unknown SAFI type: {}", value.number))
222    }
223}
224
225/// BGP validation warnings for RFC 7606 compliant error handling.
226/// These represent non-fatal validation issues that don't prevent parsing.
227///
228/// This enum is `#[non_exhaustive]` so that new warning variants can be added
229/// in minor releases without breaking exhaustive matches.
230#[derive(Debug, Clone, PartialEq, Eq)]
231#[non_exhaustive]
232pub enum BgpValidationWarning {
233    /// Attribute flags conflict with attribute type code (RFC 4271 Section 6.3)
234    AttributeFlagsError {
235        attr_type: AttrType,
236        expected_flags: u8,
237        actual_flags: u8,
238    },
239    /// Attribute length conflicts with expected length (RFC 4271 Section 6.3)
240    AttributeLengthError {
241        attr_type: AttrType,
242        expected_length: Option<usize>,
243        actual_length: usize,
244    },
245    /// Missing well-known mandatory attribute (RFC 4271 Section 6.3)
246    MissingWellKnownAttribute { attr_type: AttrType },
247    /// Unrecognized well-known attribute (RFC 4271 Section 6.3)
248    UnrecognizedWellKnownAttribute { attr_type_code: u8 },
249    /// Invalid origin attribute value (RFC 4271 Section 6.3)
250    InvalidOriginAttribute { value: u8 },
251    /// Invalid next hop attribute (RFC 4271 Section 6.3)
252    InvalidNextHopAttribute { reason: String },
253    /// Malformed AS_PATH attribute (RFC 4271 Section 6.3)
254    MalformedAsPath { reason: String },
255    /// Optional attribute error (RFC 4271 Section 6.3)
256    OptionalAttributeError { attr_type: AttrType, reason: String },
257    /// Attribute appears more than once (RFC 4271 Section 6.3)
258    DuplicateAttribute { attr_type: AttrType },
259    /// Invalid network field in NLRI (RFC 4271 Section 6.3)
260    InvalidNetworkField { reason: String },
261    /// Malformed attribute list (RFC 4271 Section 6.3)
262    MalformedAttributeList { reason: String },
263    /// Partial attribute with errors (RFC 7606)
264    PartialAttributeError { attr_type: AttrType, reason: String },
265    /// Malformed NLRI field (RFC 7606 §5.3). The UPDATE message was parseable
266    /// up to the NLRI section, but the NLRI itself contained syntactic errors.
267    /// Per RFC 7606, the recommended action is "treat-as-withdrawal": all
268    /// routes carried in the NLRI section should be withdrawn.
269    ///
270    /// `nlri_type` distinguishes between the standard (IPv4 unicast) NLRI
271    /// field and the multiprotocol NLRI carried inside MP_REACH/MP_UNREACH
272    /// path attributes. `raw_bytes` preserves the offending NLRI bytes so
273    /// callers can inspect or export them without re-encoding.
274    MalformedNlri {
275        /// "withdrawn", "announced", "mp_reach", or "mp_unreach"
276        nlri_type: &'static str,
277        /// Human-readable description of the parse error
278        reason: String,
279        /// Raw NLRI bytes as they appeared in the UPDATE message
280        raw_bytes: Vec<u8>,
281    },
282    /// ROUTE-REFRESH message subtype other than 0, 1, or 2 (RFC 7313
283    /// Section 5). A live speaker MUST ignore such a message and SHOULD log
284    /// an error; the parser retains it raw and reports this warning.
285    UnknownRouteRefreshSubtype { subtype: u8 },
286    /// BoRR/EoRR ROUTE-REFRESH message (RFC 7313 subtype 1 or 2) whose body
287    /// is not exactly 4 bytes. On the wire this is a fatal "Invalid Message
288    /// Length" NOTIFICATION when the Enhanced Route Refresh capability was
289    /// negotiated; MRT data carries no session context, so the parser
290    /// retains the message and reports this warning instead.
291    InvalidRouteRefreshLength {
292        subtype: u8,
293        /// Message body length excluding the 19-byte BGP header
294        length: usize,
295    },
296}
297
298impl Display for BgpValidationWarning {
299    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
300        match self {
301            BgpValidationWarning::AttributeFlagsError { attr_type, expected_flags, actual_flags } => {
302                write!(f, "Attribute flags error for {attr_type:?}: expected 0x{expected_flags:02x}, got 0x{actual_flags:02x}")
303            }
304            BgpValidationWarning::AttributeLengthError { attr_type, expected_length, actual_length } => {
305                match expected_length {
306                    Some(expected) => write!(f, "Attribute length error for {attr_type:?}: expected {expected}, got {actual_length}"),
307                    None => write!(f, "Attribute length error for {attr_type:?}: invalid length {actual_length}"),
308                }
309            }
310            BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
311                write!(f, "Missing well-known mandatory attribute: {attr_type:?}")
312            }
313            BgpValidationWarning::UnrecognizedWellKnownAttribute { attr_type_code } => {
314                write!(f, "Unrecognized well-known attribute: type code {attr_type_code}")
315            }
316            BgpValidationWarning::InvalidOriginAttribute { value } => {
317                write!(f, "Invalid origin attribute value: {value}")
318            }
319            BgpValidationWarning::InvalidNextHopAttribute { reason } => {
320                write!(f, "Invalid next hop attribute: {reason}")
321            }
322            BgpValidationWarning::MalformedAsPath { reason } => {
323                write!(f, "Malformed AS_PATH: {reason}")
324            }
325            BgpValidationWarning::OptionalAttributeError { attr_type, reason } => {
326                write!(f, "Optional attribute error for {attr_type:?}: {reason}")
327            }
328            BgpValidationWarning::DuplicateAttribute { attr_type } => {
329                write!(f, "Duplicate attribute: {attr_type:?}")
330            }
331            BgpValidationWarning::InvalidNetworkField { reason } => {
332                write!(f, "Invalid network field: {reason}")
333            }
334            BgpValidationWarning::MalformedAttributeList { reason } => {
335                write!(f, "Malformed attribute list: {reason}")
336            }
337            BgpValidationWarning::PartialAttributeError { attr_type, reason } => {
338                write!(f, "Partial attribute error for {attr_type:?}: {reason}")
339            }
340            BgpValidationWarning::MalformedNlri { nlri_type, reason, .. } => {
341                write!(f, "Malformed NLRI ({nlri_type}): {reason}")
342            }
343            BgpValidationWarning::UnknownRouteRefreshSubtype { subtype } => {
344                write!(f, "Unknown ROUTE-REFRESH message subtype: {subtype}")
345            }
346            BgpValidationWarning::InvalidRouteRefreshLength { subtype, length } => {
347                write!(f, "Invalid ROUTE-REFRESH message length for subtype {subtype}: body is {length} bytes, expected 4")
348            }
349        }
350    }
351}
352
353/// Result type for BGP attribute parsing that includes validation warnings
354#[derive(Debug, Clone)]
355pub struct BgpValidationResult<T> {
356    pub value: T,
357    pub warnings: Vec<BgpValidationWarning>,
358}
359
360impl<T> BgpValidationResult<T> {
361    pub fn new(value: T) -> Self {
362        Self {
363            value,
364            warnings: Vec::new(),
365        }
366    }
367
368    pub fn with_warnings(value: T, warnings: Vec<BgpValidationWarning>) -> Self {
369        Self { value, warnings }
370    }
371
372    pub fn add_warning(&mut self, warning: BgpValidationWarning) {
373        self.warnings.push(warning);
374    }
375
376    pub fn has_warnings(&self) -> bool {
377        !self.warnings.is_empty()
378    }
379}