1use std::collections::HashSet;
2use std::net::IpAddr;
3
4use crate::attribute::{AsPath, AsPathSegment, PathAttribute, attr_error_data};
5use crate::constants::{attr_flags, attr_type};
6use crate::notification::update_subcode;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct UpdateError {
13 pub subcode: u8,
15 pub data: Vec<u8>,
17}
18
19const MANDATORY_ATTRS: &[u8] = &[attr_type::ORIGIN, attr_type::AS_PATH];
21
22pub fn validate_update_attributes(
35 attrs: &[PathAttribute],
36 has_nlri: bool,
37 has_body_nlri: bool,
38 is_ebgp: bool,
39) -> Result<(), UpdateError> {
40 check_duplicate_types(attrs)?;
41 check_unrecognized_wellknown(attrs)?;
42
43 if has_nlri {
44 check_mandatory_present(attrs, has_body_nlri, is_ebgp)?;
45 }
46
47 for attr in attrs {
48 match attr {
49 PathAttribute::NextHop(addr) => check_next_hop(*addr)?,
50 PathAttribute::AsPath(path) => check_as_path(path)?,
51 PathAttribute::MpReachNlri(mp) => check_mp_reach_next_hop(mp.next_hop)?,
52 _ => {}
53 }
54 }
55
56 Ok(())
57}
58
59fn check_duplicate_types(attrs: &[PathAttribute]) -> Result<(), UpdateError> {
61 let mut seen = HashSet::new();
62 for attr in attrs {
63 let tc = attr.type_code();
64 if !seen.insert(tc) {
65 return Err(UpdateError {
66 subcode: update_subcode::MALFORMED_ATTRIBUTE_LIST,
67 data: vec![],
68 });
69 }
70 }
71 Ok(())
72}
73
74fn check_unrecognized_wellknown(attrs: &[PathAttribute]) -> Result<(), UpdateError> {
76 for attr in attrs {
77 if let PathAttribute::Unknown(raw) = attr {
78 if (raw.flags & attr_flags::OPTIONAL) == 0 {
80 return Err(UpdateError {
81 subcode: update_subcode::UNRECOGNIZED_WELLKNOWN,
82 data: attr_error_data(raw.flags, raw.type_code, &raw.data),
83 });
84 }
85 }
86 }
87 Ok(())
88}
89
90fn check_mandatory_present(
97 attrs: &[PathAttribute],
98 has_body_nlri: bool,
99 is_ebgp: bool,
100) -> Result<(), UpdateError> {
101 let present: HashSet<u8> = attrs.iter().map(PathAttribute::type_code).collect();
102
103 for &tc in MANDATORY_ATTRS {
104 if !present.contains(&tc) {
105 return Err(UpdateError {
106 subcode: update_subcode::MISSING_WELLKNOWN,
107 data: vec![tc],
108 });
109 }
110 }
111
112 if is_ebgp && has_body_nlri && !present.contains(&attr_type::NEXT_HOP) {
115 return Err(UpdateError {
116 subcode: update_subcode::MISSING_WELLKNOWN,
117 data: vec![attr_type::NEXT_HOP],
118 });
119 }
120
121 Ok(())
122}
123
124fn check_next_hop(addr: std::net::Ipv4Addr) -> Result<(), UpdateError> {
126 let octets = addr.octets();
127
128 if addr.is_unspecified() {
130 return Err(UpdateError {
131 subcode: update_subcode::INVALID_NEXT_HOP,
132 data: octets.to_vec(),
133 });
134 }
135
136 if addr.is_loopback() {
138 return Err(UpdateError {
139 subcode: update_subcode::INVALID_NEXT_HOP,
140 data: octets.to_vec(),
141 });
142 }
143
144 if addr.is_multicast() {
146 return Err(UpdateError {
147 subcode: update_subcode::INVALID_NEXT_HOP,
148 data: octets.to_vec(),
149 });
150 }
151
152 if addr.is_broadcast() {
154 return Err(UpdateError {
155 subcode: update_subcode::INVALID_NEXT_HOP,
156 data: octets.to_vec(),
157 });
158 }
159
160 Ok(())
161}
162
163fn check_mp_reach_next_hop(addr: IpAddr) -> Result<(), UpdateError> {
165 match addr {
166 IpAddr::V4(v4) => check_next_hop(v4)?,
167 IpAddr::V6(v6) => {
168 if !is_valid_ipv6_nexthop(&v6) {
169 return Err(UpdateError {
170 subcode: update_subcode::INVALID_NEXT_HOP,
171 data: v6.octets().to_vec(),
172 });
173 }
174 }
175 }
176 Ok(())
177}
178
179fn is_ipv6_link_local(addr: &std::net::Ipv6Addr) -> bool {
181 (addr.segments()[0] & 0xffc0) == 0xfe80
182}
183
184#[must_use]
189pub fn is_valid_ipv6_nexthop(addr: &std::net::Ipv6Addr) -> bool {
190 !addr.is_unspecified()
191 && !addr.is_loopback()
192 && !addr.is_multicast()
193 && !is_ipv6_link_local(addr)
194}
195
196fn check_as_path(path: &AsPath) -> Result<(), UpdateError> {
198 for segment in &path.segments {
199 let asns = match segment {
200 AsPathSegment::AsSet(asns) | AsPathSegment::AsSequence(asns) => asns,
201 };
202 if asns.is_empty() {
203 return Err(UpdateError {
204 subcode: update_subcode::MALFORMED_AS_PATH,
205 data: vec![],
206 });
207 }
208 }
209 Ok(())
210}
211
212#[cfg(test)]
213mod tests {
214 use std::net::Ipv4Addr;
215
216 use bytes::Bytes;
217
218 use super::*;
219 use crate::attribute::{Origin, RawAttribute};
220
221 fn basic_attrs(next_hop: Ipv4Addr) -> Vec<PathAttribute> {
222 vec![
223 PathAttribute::Origin(Origin::Igp),
224 PathAttribute::AsPath(AsPath {
225 segments: vec![AsPathSegment::AsSequence(vec![65001])],
226 }),
227 PathAttribute::NextHop(next_hop),
228 ]
229 }
230
231 #[test]
232 fn valid_ebgp_update() {
233 let attrs = basic_attrs(Ipv4Addr::new(10, 0, 0, 1));
234 assert!(validate_update_attributes(&attrs, true, true, true).is_ok());
235 }
236
237 #[test]
238 fn valid_ibgp_update_no_next_hop() {
239 let attrs = vec![
241 PathAttribute::Origin(Origin::Igp),
242 PathAttribute::AsPath(AsPath {
243 segments: vec![AsPathSegment::AsSequence(vec![65001])],
244 }),
245 ];
246 assert!(validate_update_attributes(&attrs, true, true, false).is_ok());
247 }
248
249 #[test]
250 fn withdrawal_only_no_attrs_ok() {
251 assert!(validate_update_attributes(&[], false, false, true).is_ok());
253 }
254
255 #[test]
256 fn reject_duplicate_type() {
257 let attrs = vec![
258 PathAttribute::Origin(Origin::Igp),
259 PathAttribute::Origin(Origin::Egp),
260 ];
261 let err = validate_update_attributes(&attrs, false, false, true).unwrap_err();
262 assert_eq!(err.subcode, update_subcode::MALFORMED_ATTRIBUTE_LIST);
263 }
264
265 #[test]
266 fn reject_missing_origin() {
267 let attrs = vec![
268 PathAttribute::AsPath(AsPath {
269 segments: vec![AsPathSegment::AsSequence(vec![65001])],
270 }),
271 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
272 ];
273 let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
274 assert_eq!(err.subcode, update_subcode::MISSING_WELLKNOWN);
275 }
276
277 #[test]
278 fn reject_missing_as_path() {
279 let attrs = vec![
280 PathAttribute::Origin(Origin::Igp),
281 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
282 ];
283 let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
284 assert_eq!(err.subcode, update_subcode::MISSING_WELLKNOWN);
285 }
286
287 #[test]
288 fn reject_missing_next_hop_ebgp() {
289 let attrs = vec![
290 PathAttribute::Origin(Origin::Igp),
291 PathAttribute::AsPath(AsPath {
292 segments: vec![AsPathSegment::AsSequence(vec![65001])],
293 }),
294 ];
295 let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
296 assert_eq!(err.subcode, update_subcode::MISSING_WELLKNOWN);
297 assert_eq!(err.data, vec![attr_type::NEXT_HOP]);
298 }
299
300 #[test]
301 fn reject_next_hop_unspecified() {
302 let attrs = basic_attrs(Ipv4Addr::UNSPECIFIED);
303 let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
304 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
305 }
306
307 #[test]
308 fn reject_next_hop_loopback() {
309 let attrs = basic_attrs(Ipv4Addr::LOCALHOST);
310 let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
311 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
312 }
313
314 #[test]
315 fn reject_next_hop_multicast() {
316 let attrs = basic_attrs(Ipv4Addr::new(224, 0, 0, 1));
317 let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
318 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
319 }
320
321 #[test]
322 fn reject_next_hop_broadcast() {
323 let attrs = basic_attrs(Ipv4Addr::BROADCAST);
324 let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
325 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
326 }
327
328 #[test]
329 fn reject_empty_as_path_segment() {
330 let attrs = vec![
331 PathAttribute::Origin(Origin::Igp),
332 PathAttribute::AsPath(AsPath {
333 segments: vec![AsPathSegment::AsSequence(vec![])],
334 }),
335 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
336 ];
337 let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
338 assert_eq!(err.subcode, update_subcode::MALFORMED_AS_PATH);
339 }
340
341 #[test]
342 fn reject_unrecognized_wellknown() {
343 let attrs = vec![PathAttribute::Unknown(RawAttribute {
344 flags: attr_flags::TRANSITIVE, type_code: 99,
346 data: Bytes::from_static(&[1, 2, 3]),
347 })];
348 let err = validate_update_attributes(&attrs, false, false, true).unwrap_err();
349 assert_eq!(err.subcode, update_subcode::UNRECOGNIZED_WELLKNOWN);
350 }
351
352 #[test]
353 fn optional_unknown_attribute_ok() {
354 let attrs = vec![PathAttribute::Unknown(RawAttribute {
355 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
356 type_code: 99,
357 data: Bytes::from_static(&[1, 2, 3]),
358 })];
359 assert!(validate_update_attributes(&attrs, false, false, true).is_ok());
360 }
361
362 #[test]
365 fn mp_reach_nlri_no_body_next_hop_required_for_ebgp() {
366 use crate::attribute::MpReachNlri;
367 use crate::capability::{Afi, Safi};
368 use crate::nlri::{Ipv6Prefix, NlriEntry, Prefix};
369
370 let attrs = vec![
372 PathAttribute::Origin(Origin::Igp),
373 PathAttribute::AsPath(AsPath {
374 segments: vec![AsPathSegment::AsSequence(vec![65001])],
375 }),
376 PathAttribute::MpReachNlri(MpReachNlri {
377 afi: Afi::Ipv6,
378 safi: Safi::Unicast,
379 next_hop: std::net::IpAddr::V6("2001:db8::1".parse().unwrap()),
380 link_local_next_hop: None,
381 announced: vec![NlriEntry {
382 path_id: 0,
383 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32)),
384 }],
385 flowspec_announced: vec![],
386 evpn_announced: vec![],
387 }),
388 ];
389 assert!(validate_update_attributes(&attrs, true, false, true).is_ok());
391 }
392
393 #[test]
394 fn mixed_update_requires_body_next_hop_for_ebgp() {
395 use crate::attribute::MpReachNlri;
396 use crate::capability::{Afi, Safi};
397 use crate::nlri::{Ipv6Prefix, NlriEntry, Prefix};
398
399 let attrs = vec![
401 PathAttribute::Origin(Origin::Igp),
402 PathAttribute::AsPath(AsPath {
403 segments: vec![AsPathSegment::AsSequence(vec![65001])],
404 }),
405 PathAttribute::MpReachNlri(MpReachNlri {
406 afi: Afi::Ipv6,
407 safi: Safi::Unicast,
408 next_hop: std::net::IpAddr::V6("2001:db8::1".parse().unwrap()),
409 link_local_next_hop: None,
410 announced: vec![NlriEntry {
411 path_id: 0,
412 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32)),
413 }],
414 flowspec_announced: vec![],
415 evpn_announced: vec![],
416 }),
417 ];
418 let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
421 assert_eq!(err.subcode, update_subcode::MISSING_WELLKNOWN);
422 assert_eq!(err.data, vec![attr_type::NEXT_HOP]);
423 }
424
425 #[test]
426 fn mp_reach_nlri_reject_unspecified_v6_next_hop() {
427 use crate::attribute::MpReachNlri;
428 use crate::capability::{Afi, Safi};
429
430 let attrs = vec![
431 PathAttribute::Origin(Origin::Igp),
432 PathAttribute::AsPath(AsPath {
433 segments: vec![AsPathSegment::AsSequence(vec![65001])],
434 }),
435 PathAttribute::MpReachNlri(MpReachNlri {
436 afi: Afi::Ipv6,
437 safi: Safi::Unicast,
438 next_hop: std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED),
439 link_local_next_hop: None,
440 announced: vec![],
441 flowspec_announced: vec![],
442 evpn_announced: vec![],
443 }),
444 ];
445 let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
446 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
447 }
448
449 #[test]
450 fn mp_reach_nlri_reject_link_local_v6_next_hop() {
451 use crate::attribute::MpReachNlri;
452 use crate::capability::{Afi, Safi};
453
454 let attrs = vec![
455 PathAttribute::Origin(Origin::Igp),
456 PathAttribute::AsPath(AsPath {
457 segments: vec![AsPathSegment::AsSequence(vec![65001])],
458 }),
459 PathAttribute::MpReachNlri(MpReachNlri {
460 afi: Afi::Ipv6,
461 safi: Safi::Unicast,
462 next_hop: std::net::IpAddr::V6("fe80::1".parse().unwrap()),
463 link_local_next_hop: None,
464 announced: vec![],
465 flowspec_announced: vec![],
466 evpn_announced: vec![],
467 }),
468 ];
469 let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
470 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
471 }
472
473 #[test]
474 fn mp_reach_nlri_reject_loopback_v6_next_hop() {
475 use crate::attribute::MpReachNlri;
476 use crate::capability::{Afi, Safi};
477
478 let attrs = vec![
479 PathAttribute::Origin(Origin::Igp),
480 PathAttribute::AsPath(AsPath {
481 segments: vec![AsPathSegment::AsSequence(vec![65001])],
482 }),
483 PathAttribute::MpReachNlri(MpReachNlri {
484 afi: Afi::Ipv6,
485 safi: Safi::Unicast,
486 next_hop: std::net::IpAddr::V6(std::net::Ipv6Addr::LOCALHOST),
487 link_local_next_hop: None,
488 announced: vec![],
489 flowspec_announced: vec![],
490 evpn_announced: vec![],
491 }),
492 ];
493 let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
494 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
495 }
496
497 #[test]
498 fn is_valid_ipv6_nexthop_accepts_global() {
499 assert!(super::is_valid_ipv6_nexthop(
500 &"2001:db8::1".parse().unwrap()
501 ));
502 }
503
504 #[test]
505 fn is_valid_ipv6_nexthop_rejects_unspecified() {
506 assert!(!super::is_valid_ipv6_nexthop(
507 &std::net::Ipv6Addr::UNSPECIFIED
508 ));
509 }
510
511 #[test]
512 fn is_valid_ipv6_nexthop_rejects_loopback() {
513 assert!(!super::is_valid_ipv6_nexthop(
514 &std::net::Ipv6Addr::LOCALHOST
515 ));
516 }
517
518 #[test]
519 fn is_valid_ipv6_nexthop_rejects_link_local() {
520 assert!(!super::is_valid_ipv6_nexthop(&"fe80::1".parse().unwrap()));
521 }
522
523 #[test]
524 fn is_valid_ipv6_nexthop_rejects_multicast() {
525 assert!(!super::is_valid_ipv6_nexthop(&"ff02::1".parse().unwrap()));
526 }
527
528 #[test]
529 fn mp_reach_nlri_reject_multicast_v6_next_hop() {
530 use crate::attribute::MpReachNlri;
531 use crate::capability::{Afi, Safi};
532
533 let attrs = vec![
534 PathAttribute::Origin(Origin::Igp),
535 PathAttribute::AsPath(AsPath {
536 segments: vec![AsPathSegment::AsSequence(vec![65001])],
537 }),
538 PathAttribute::MpReachNlri(MpReachNlri {
539 afi: Afi::Ipv6,
540 safi: Safi::Unicast,
541 next_hop: std::net::IpAddr::V6("ff02::1".parse().unwrap()),
543 link_local_next_hop: None,
544 announced: vec![],
545 flowspec_announced: vec![],
546 evpn_announced: vec![],
547 }),
548 ];
549 let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
550 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
551 }
552}