Skip to main content

rustbgpd_wire/
validate.rs

1use std::collections::HashSet;
2use std::net::IpAddr;
3
4use crate::attribute::{AsPath, AsPathSegment, PathAttribute, attr_error_data};
5use crate::capability::Safi;
6use crate::constants::{attr_flags, attr_type};
7use crate::notification::update_subcode;
8
9/// Error produced by UPDATE attribute validation.
10///
11/// Contains the NOTIFICATION subcode and data bytes per RFC 4271 §6.3.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct UpdateError {
14    /// NOTIFICATION subcode for this validation error.
15    pub subcode: u8,
16    /// Raw bytes for the NOTIFICATION data field.
17    pub data: Vec<u8>,
18}
19
20/// Well-known attribute type codes that MUST be present when NLRI is advertised.
21const MANDATORY_ATTRS: &[u8] = &[attr_type::ORIGIN, attr_type::AS_PATH];
22
23/// Validate the semantic correctness of a set of path attributes.
24///
25/// This is separate from decode (which is structural — "can I read these bytes?").
26/// Validation checks whether the attribute set is RFC-compliant for this UPDATE.
27///
28/// `has_nlri` — true if the UPDATE carries announced prefixes (body or MP).
29/// `has_body_nlri` — true if the UPDATE carries IPv4 NLRI in the body fields.
30/// `is_ebgp` — true if the session is external BGP.
31///
32/// # Errors
33///
34/// Returns an `UpdateError` with the appropriate subcode and data.
35pub fn validate_update_attributes(
36    attrs: &[PathAttribute],
37    has_nlri: bool,
38    has_body_nlri: bool,
39    is_ebgp: bool,
40) -> Result<(), UpdateError> {
41    check_duplicate_types(attrs)?;
42    check_unrecognized_wellknown(attrs)?;
43
44    if has_nlri {
45        check_mandatory_present(attrs, has_body_nlri, is_ebgp)?;
46    }
47
48    for attr in attrs {
49        match attr {
50            PathAttribute::NextHop(addr) => check_next_hop(*addr)?,
51            PathAttribute::AsPath(path) => check_as_path(path)?,
52            // RFC 8955 §6.1: for FlowSpec (SAFI 133), the NEXT_HOP
53            // attribute value is "irrelevant" and is recommended
54            // to be 0 when advertising. The on-wire NH-Len for
55            // FlowSpec is 0 and the decoder fills `mp.next_hop`
56            // with 0.0.0.0; running the standard NEXT_HOP validator
57            // (which rejects 0.0.0.0) on a FlowSpec MP_REACH causes
58            // us to send NOTIFICATION 3/8 and tear the session
59            // against any RFC-compliant peer. Skip validation for
60            // FlowSpec; the rule contents travel in
61            // `flowspec_announced`, not next_hop.
62            PathAttribute::MpReachNlri(mp) if mp.safi != Safi::FlowSpec => {
63                check_mp_reach_next_hop(mp.next_hop)?;
64            }
65            _ => {}
66        }
67    }
68
69    Ok(())
70}
71
72/// (3,1) Duplicate attribute type codes.
73fn check_duplicate_types(attrs: &[PathAttribute]) -> Result<(), UpdateError> {
74    let mut seen = HashSet::new();
75    for attr in attrs {
76        let tc = attr.type_code();
77        if !seen.insert(tc) {
78            return Err(UpdateError {
79                subcode: update_subcode::MALFORMED_ATTRIBUTE_LIST,
80                data: vec![],
81            });
82        }
83    }
84    Ok(())
85}
86
87/// (3,2) Unrecognized well-known attribute: Optional=0 and type code unknown.
88fn check_unrecognized_wellknown(attrs: &[PathAttribute]) -> Result<(), UpdateError> {
89    for attr in attrs {
90        if let PathAttribute::Unknown(raw) = attr {
91            // If Optional bit is NOT set, it claims to be well-known
92            if (raw.flags & attr_flags::OPTIONAL) == 0 {
93                return Err(UpdateError {
94                    subcode: update_subcode::UNRECOGNIZED_WELLKNOWN,
95                    data: attr_error_data(raw.flags, raw.type_code, &raw.data),
96                });
97            }
98        }
99    }
100    Ok(())
101}
102
103/// (3,3) Missing mandatory well-known attributes.
104///
105/// `has_body_nlri` — true if the UPDATE carries IPv4 NLRI in the body fields.
106/// When only `MP_REACH_NLRI` is present (no body NLRI), `NEXT_HOP` is carried
107/// inside the MP attribute (RFC 4760 §3) and not required as a separate attribute.
108/// Mixed UPDATEs (body NLRI + `MP_REACH_NLRI`) still require body `NEXT_HOP`.
109fn check_mandatory_present(
110    attrs: &[PathAttribute],
111    has_body_nlri: bool,
112    is_ebgp: bool,
113) -> Result<(), UpdateError> {
114    let present: HashSet<u8> = attrs.iter().map(PathAttribute::type_code).collect();
115
116    for &tc in MANDATORY_ATTRS {
117        if !present.contains(&tc) {
118            return Err(UpdateError {
119                subcode: update_subcode::MISSING_WELLKNOWN,
120                data: vec![tc],
121            });
122        }
123    }
124
125    // NEXT_HOP mandatory for eBGP when body NLRI is present. When only MP_REACH
126    // carries NLRI, the next-hop is inside the MP attribute (RFC 4760 §3).
127    if is_ebgp && has_body_nlri && !present.contains(&attr_type::NEXT_HOP) {
128        return Err(UpdateError {
129            subcode: update_subcode::MISSING_WELLKNOWN,
130            data: vec![attr_type::NEXT_HOP],
131        });
132    }
133
134    Ok(())
135}
136
137/// (3,8) Invalid `NEXT_HOP` address.
138fn check_next_hop(addr: std::net::Ipv4Addr) -> Result<(), UpdateError> {
139    let octets = addr.octets();
140
141    // 0.0.0.0
142    if addr.is_unspecified() {
143        return Err(UpdateError {
144            subcode: update_subcode::INVALID_NEXT_HOP,
145            data: octets.to_vec(),
146        });
147    }
148
149    // 127.0.0.0/8
150    if addr.is_loopback() {
151        return Err(UpdateError {
152            subcode: update_subcode::INVALID_NEXT_HOP,
153            data: octets.to_vec(),
154        });
155    }
156
157    // 224.0.0.0/4 (multicast)
158    if addr.is_multicast() {
159        return Err(UpdateError {
160            subcode: update_subcode::INVALID_NEXT_HOP,
161            data: octets.to_vec(),
162        });
163    }
164
165    // 255.255.255.255
166    if addr.is_broadcast() {
167        return Err(UpdateError {
168            subcode: update_subcode::INVALID_NEXT_HOP,
169            data: octets.to_vec(),
170        });
171    }
172
173    Ok(())
174}
175
176/// Validate `MP_REACH_NLRI` next-hop address.
177fn check_mp_reach_next_hop(addr: IpAddr) -> Result<(), UpdateError> {
178    match addr {
179        IpAddr::V4(v4) => check_next_hop(v4)?,
180        IpAddr::V6(v6) => {
181            if !is_valid_ipv6_nexthop(&v6) {
182                return Err(UpdateError {
183                    subcode: update_subcode::INVALID_NEXT_HOP,
184                    data: v6.octets().to_vec(),
185                });
186            }
187        }
188    }
189    Ok(())
190}
191
192/// Check if an IPv6 address is link-local (`fe80::/10`).
193fn is_ipv6_link_local(addr: &std::net::Ipv6Addr) -> bool {
194    (addr.segments()[0] & 0xffc0) == 0xfe80
195}
196
197/// Returns `true` if `addr` is a valid IPv6 next-hop for BGP advertisements.
198///
199/// Rejects unspecified (`::`), loopback (`::1`), multicast (`ff00::/8`),
200/// and link-local (`fe80::/10`) addresses.
201#[must_use]
202pub fn is_valid_ipv6_nexthop(addr: &std::net::Ipv6Addr) -> bool {
203    !addr.is_unspecified()
204        && !addr.is_loopback()
205        && !addr.is_multicast()
206        && !is_ipv6_link_local(addr)
207}
208
209/// (3,11) Malformed `AS_PATH`.
210fn check_as_path(path: &AsPath) -> Result<(), UpdateError> {
211    for segment in &path.segments {
212        let asns = match segment {
213            AsPathSegment::AsSet(asns) | AsPathSegment::AsSequence(asns) => asns,
214        };
215        if asns.is_empty() {
216            return Err(UpdateError {
217                subcode: update_subcode::MALFORMED_AS_PATH,
218                data: vec![],
219            });
220        }
221    }
222    Ok(())
223}
224
225#[cfg(test)]
226mod tests {
227    use std::net::Ipv4Addr;
228
229    use bytes::Bytes;
230
231    use super::*;
232    use crate::attribute::{Origin, RawAttribute};
233
234    fn basic_attrs(next_hop: Ipv4Addr) -> Vec<PathAttribute> {
235        vec![
236            PathAttribute::Origin(Origin::Igp),
237            PathAttribute::AsPath(AsPath {
238                segments: vec![AsPathSegment::AsSequence(vec![65001])],
239            }),
240            PathAttribute::NextHop(next_hop),
241        ]
242    }
243
244    #[test]
245    fn valid_ebgp_update() {
246        let attrs = basic_attrs(Ipv4Addr::new(10, 0, 0, 1));
247        assert!(validate_update_attributes(&attrs, true, true, true).is_ok());
248    }
249
250    #[test]
251    fn valid_ibgp_update_no_next_hop() {
252        // iBGP doesn't require NEXT_HOP (it's optional based on the peer)
253        let attrs = vec![
254            PathAttribute::Origin(Origin::Igp),
255            PathAttribute::AsPath(AsPath {
256                segments: vec![AsPathSegment::AsSequence(vec![65001])],
257            }),
258        ];
259        assert!(validate_update_attributes(&attrs, true, true, false).is_ok());
260    }
261
262    #[test]
263    fn withdrawal_only_no_attrs_ok() {
264        // No NLRI → no mandatory attributes required
265        assert!(validate_update_attributes(&[], false, false, true).is_ok());
266    }
267
268    #[test]
269    fn reject_duplicate_type() {
270        let attrs = vec![
271            PathAttribute::Origin(Origin::Igp),
272            PathAttribute::Origin(Origin::Egp),
273        ];
274        let err = validate_update_attributes(&attrs, false, false, true).unwrap_err();
275        assert_eq!(err.subcode, update_subcode::MALFORMED_ATTRIBUTE_LIST);
276    }
277
278    #[test]
279    fn reject_missing_origin() {
280        let attrs = vec![
281            PathAttribute::AsPath(AsPath {
282                segments: vec![AsPathSegment::AsSequence(vec![65001])],
283            }),
284            PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
285        ];
286        let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
287        assert_eq!(err.subcode, update_subcode::MISSING_WELLKNOWN);
288    }
289
290    #[test]
291    fn reject_missing_as_path() {
292        let attrs = vec![
293            PathAttribute::Origin(Origin::Igp),
294            PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
295        ];
296        let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
297        assert_eq!(err.subcode, update_subcode::MISSING_WELLKNOWN);
298    }
299
300    #[test]
301    fn reject_missing_next_hop_ebgp() {
302        let attrs = vec![
303            PathAttribute::Origin(Origin::Igp),
304            PathAttribute::AsPath(AsPath {
305                segments: vec![AsPathSegment::AsSequence(vec![65001])],
306            }),
307        ];
308        let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
309        assert_eq!(err.subcode, update_subcode::MISSING_WELLKNOWN);
310        assert_eq!(err.data, vec![attr_type::NEXT_HOP]);
311    }
312
313    #[test]
314    fn reject_next_hop_unspecified() {
315        let attrs = basic_attrs(Ipv4Addr::UNSPECIFIED);
316        let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
317        assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
318    }
319
320    #[test]
321    fn reject_next_hop_loopback() {
322        let attrs = basic_attrs(Ipv4Addr::LOCALHOST);
323        let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
324        assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
325    }
326
327    #[test]
328    fn reject_next_hop_multicast() {
329        let attrs = basic_attrs(Ipv4Addr::new(224, 0, 0, 1));
330        let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
331        assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
332    }
333
334    #[test]
335    fn reject_next_hop_broadcast() {
336        let attrs = basic_attrs(Ipv4Addr::BROADCAST);
337        let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
338        assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
339    }
340
341    #[test]
342    fn reject_empty_as_path_segment() {
343        let attrs = vec![
344            PathAttribute::Origin(Origin::Igp),
345            PathAttribute::AsPath(AsPath {
346                segments: vec![AsPathSegment::AsSequence(vec![])],
347            }),
348            PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
349        ];
350        let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
351        assert_eq!(err.subcode, update_subcode::MALFORMED_AS_PATH);
352    }
353
354    #[test]
355    fn reject_unrecognized_wellknown() {
356        let attrs = vec![PathAttribute::Unknown(RawAttribute {
357            flags: attr_flags::TRANSITIVE, // Optional=0 → claims well-known
358            type_code: 99,
359            data: Bytes::from_static(&[1, 2, 3]),
360        })];
361        let err = validate_update_attributes(&attrs, false, false, true).unwrap_err();
362        assert_eq!(err.subcode, update_subcode::UNRECOGNIZED_WELLKNOWN);
363    }
364
365    #[test]
366    fn optional_unknown_attribute_ok() {
367        let attrs = vec![PathAttribute::Unknown(RawAttribute {
368            flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
369            type_code: 99,
370            data: Bytes::from_static(&[1, 2, 3]),
371        })];
372        assert!(validate_update_attributes(&attrs, false, false, true).is_ok());
373    }
374
375    // --- MP_REACH_NLRI validation tests ---
376
377    #[test]
378    fn mp_reach_nlri_no_body_next_hop_required_for_ebgp() {
379        use crate::attribute::MpReachNlri;
380        use crate::capability::{Afi, Safi};
381        use crate::nlri::{Ipv6Prefix, NlriEntry, Prefix};
382
383        // eBGP UPDATE with MP_REACH_NLRI only (no body NLRI): NEXT_HOP not required
384        let attrs = vec![
385            PathAttribute::Origin(Origin::Igp),
386            PathAttribute::AsPath(AsPath {
387                segments: vec![AsPathSegment::AsSequence(vec![65001])],
388            }),
389            PathAttribute::MpReachNlri(MpReachNlri {
390                afi: Afi::Ipv6,
391                safi: Safi::Unicast,
392                next_hop: std::net::IpAddr::V6("2001:db8::1".parse().unwrap()),
393                link_local_next_hop: None,
394                announced: vec![NlriEntry {
395                    path_id: 0,
396                    prefix: Prefix::V6(Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32)),
397                }],
398                flowspec_announced: vec![],
399                evpn_announced: vec![],
400            }),
401        ];
402        // has_nlri=true, has_body_nlri=false (only MP NLRI), is_ebgp=true
403        assert!(validate_update_attributes(&attrs, true, false, true).is_ok());
404    }
405
406    #[test]
407    fn mixed_update_requires_body_next_hop_for_ebgp() {
408        use crate::attribute::MpReachNlri;
409        use crate::capability::{Afi, Safi};
410        use crate::nlri::{Ipv6Prefix, NlriEntry, Prefix};
411
412        // eBGP UPDATE with BOTH body NLRI and MP_REACH_NLRI but no NEXT_HOP attr
413        let attrs = vec![
414            PathAttribute::Origin(Origin::Igp),
415            PathAttribute::AsPath(AsPath {
416                segments: vec![AsPathSegment::AsSequence(vec![65001])],
417            }),
418            PathAttribute::MpReachNlri(MpReachNlri {
419                afi: Afi::Ipv6,
420                safi: Safi::Unicast,
421                next_hop: std::net::IpAddr::V6("2001:db8::1".parse().unwrap()),
422                link_local_next_hop: None,
423                announced: vec![NlriEntry {
424                    path_id: 0,
425                    prefix: Prefix::V6(Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32)),
426                }],
427                flowspec_announced: vec![],
428                evpn_announced: vec![],
429            }),
430        ];
431        // has_nlri=true, has_body_nlri=true (body IPv4 NLRI present), is_ebgp=true
432        // → should require NEXT_HOP for the body NLRI
433        let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
434        assert_eq!(err.subcode, update_subcode::MISSING_WELLKNOWN);
435        assert_eq!(err.data, vec![attr_type::NEXT_HOP]);
436    }
437
438    #[test]
439    fn mp_reach_nlri_reject_unspecified_v6_next_hop() {
440        use crate::attribute::MpReachNlri;
441        use crate::capability::{Afi, Safi};
442
443        let attrs = vec![
444            PathAttribute::Origin(Origin::Igp),
445            PathAttribute::AsPath(AsPath {
446                segments: vec![AsPathSegment::AsSequence(vec![65001])],
447            }),
448            PathAttribute::MpReachNlri(MpReachNlri {
449                afi: Afi::Ipv6,
450                safi: Safi::Unicast,
451                next_hop: std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED),
452                link_local_next_hop: None,
453                announced: vec![],
454                flowspec_announced: vec![],
455                evpn_announced: vec![],
456            }),
457        ];
458        let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
459        assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
460    }
461
462    #[test]
463    fn mp_reach_nlri_reject_link_local_v6_next_hop() {
464        use crate::attribute::MpReachNlri;
465        use crate::capability::{Afi, Safi};
466
467        let attrs = vec![
468            PathAttribute::Origin(Origin::Igp),
469            PathAttribute::AsPath(AsPath {
470                segments: vec![AsPathSegment::AsSequence(vec![65001])],
471            }),
472            PathAttribute::MpReachNlri(MpReachNlri {
473                afi: Afi::Ipv6,
474                safi: Safi::Unicast,
475                next_hop: std::net::IpAddr::V6("fe80::1".parse().unwrap()),
476                link_local_next_hop: None,
477                announced: vec![],
478                flowspec_announced: vec![],
479                evpn_announced: vec![],
480            }),
481        ];
482        let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
483        assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
484    }
485
486    #[test]
487    fn mp_reach_nlri_reject_loopback_v6_next_hop() {
488        use crate::attribute::MpReachNlri;
489        use crate::capability::{Afi, Safi};
490
491        let attrs = vec![
492            PathAttribute::Origin(Origin::Igp),
493            PathAttribute::AsPath(AsPath {
494                segments: vec![AsPathSegment::AsSequence(vec![65001])],
495            }),
496            PathAttribute::MpReachNlri(MpReachNlri {
497                afi: Afi::Ipv6,
498                safi: Safi::Unicast,
499                next_hop: std::net::IpAddr::V6(std::net::Ipv6Addr::LOCALHOST),
500                link_local_next_hop: None,
501                announced: vec![],
502                flowspec_announced: vec![],
503                evpn_announced: vec![],
504            }),
505        ];
506        let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
507        assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
508    }
509
510    #[test]
511    fn is_valid_ipv6_nexthop_accepts_global() {
512        assert!(super::is_valid_ipv6_nexthop(
513            &"2001:db8::1".parse().unwrap()
514        ));
515    }
516
517    #[test]
518    fn is_valid_ipv6_nexthop_rejects_unspecified() {
519        assert!(!super::is_valid_ipv6_nexthop(
520            &std::net::Ipv6Addr::UNSPECIFIED
521        ));
522    }
523
524    #[test]
525    fn is_valid_ipv6_nexthop_rejects_loopback() {
526        assert!(!super::is_valid_ipv6_nexthop(
527            &std::net::Ipv6Addr::LOCALHOST
528        ));
529    }
530
531    #[test]
532    fn is_valid_ipv6_nexthop_rejects_link_local() {
533        assert!(!super::is_valid_ipv6_nexthop(&"fe80::1".parse().unwrap()));
534    }
535
536    #[test]
537    fn is_valid_ipv6_nexthop_rejects_multicast() {
538        assert!(!super::is_valid_ipv6_nexthop(&"ff02::1".parse().unwrap()));
539    }
540
541    #[test]
542    fn mp_reach_nlri_reject_multicast_v6_next_hop() {
543        use crate::attribute::MpReachNlri;
544        use crate::capability::{Afi, Safi};
545
546        let attrs = vec![
547            PathAttribute::Origin(Origin::Igp),
548            PathAttribute::AsPath(AsPath {
549                segments: vec![AsPathSegment::AsSequence(vec![65001])],
550            }),
551            PathAttribute::MpReachNlri(MpReachNlri {
552                afi: Afi::Ipv6,
553                safi: Safi::Unicast,
554                // ff02::1 is multicast
555                next_hop: std::net::IpAddr::V6("ff02::1".parse().unwrap()),
556                link_local_next_hop: None,
557                announced: vec![],
558                flowspec_announced: vec![],
559                evpn_announced: vec![],
560            }),
561        ];
562        let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
563        assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
564    }
565
566    /// Regression: an `MP_REACH` for `FlowSpec` (SAFI 133) with
567    /// the recommended-by-RFC-8955-§6.1 next-hop value of 0.0.0.0
568    /// must NOT trip `NEXT_HOP` validation. Before the `FlowSpec`
569    /// guard, validate ran the standard `check_next_hop` against
570    /// every `MP_REACH` next-hop and rejected 0.0.0.0 with subcode
571    /// 8 (Invalid `NEXT_HOP`), causing rustbgpd to send
572    /// `NOTIFICATION` 3/8 and tear the session against any
573    /// RFC-compliant `FlowSpec` peer (FRR, `GoBGP`). M22 was
574    /// masking this with long display-path waits that hid the
575    /// resulting flap-and-recover cycle.
576    #[test]
577    fn mp_reach_flowspec_unspecified_next_hop_is_valid() {
578        use crate::attribute::MpReachNlri;
579        use crate::capability::{Afi, Safi};
580
581        let attrs = vec![
582            PathAttribute::Origin(Origin::Igp),
583            PathAttribute::AsPath(AsPath {
584                segments: vec![AsPathSegment::AsSequence(vec![65001])],
585            }),
586            PathAttribute::MpReachNlri(MpReachNlri {
587                afi: Afi::Ipv4,
588                safi: Safi::FlowSpec,
589                // RFC 8955 §6.1 recommends 0.0.0.0 for FlowSpec
590                // advertisements; on the wire NH-Len is 0 and the
591                // decoder defaults this to 0.0.0.0.
592                next_hop: std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
593                link_local_next_hop: None,
594                announced: vec![],
595                flowspec_announced: vec![],
596                evpn_announced: vec![],
597            }),
598        ];
599        // Empty announced + empty body — FlowSpec EoR-equivalent
600        // shape FRR sends post-handshake. Must pass validation.
601        assert!(
602            validate_update_attributes(&attrs, false, false, true).is_ok(),
603            "FlowSpec MP_REACH with 0.0.0.0 next-hop must pass — RFC 8955 §6.1 \
604             specifies the next-hop value is irrelevant for FlowSpec and \
605             recommends 0. The pre-fix path tore sessions against every \
606             RFC-compliant FlowSpec peer."
607        );
608    }
609}