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 announced: vec![NlriEntry {
381 path_id: 0,
382 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32)),
383 }],
384 flowspec_announced: vec![],
385 evpn_announced: vec![],
386 }),
387 ];
388 assert!(validate_update_attributes(&attrs, true, false, true).is_ok());
390 }
391
392 #[test]
393 fn mixed_update_requires_body_next_hop_for_ebgp() {
394 use crate::attribute::MpReachNlri;
395 use crate::capability::{Afi, Safi};
396 use crate::nlri::{Ipv6Prefix, NlriEntry, Prefix};
397
398 let attrs = vec![
400 PathAttribute::Origin(Origin::Igp),
401 PathAttribute::AsPath(AsPath {
402 segments: vec![AsPathSegment::AsSequence(vec![65001])],
403 }),
404 PathAttribute::MpReachNlri(MpReachNlri {
405 afi: Afi::Ipv6,
406 safi: Safi::Unicast,
407 next_hop: std::net::IpAddr::V6("2001:db8::1".parse().unwrap()),
408 announced: vec![NlriEntry {
409 path_id: 0,
410 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32)),
411 }],
412 flowspec_announced: vec![],
413 evpn_announced: vec![],
414 }),
415 ];
416 let err = validate_update_attributes(&attrs, true, true, true).unwrap_err();
419 assert_eq!(err.subcode, update_subcode::MISSING_WELLKNOWN);
420 assert_eq!(err.data, vec![attr_type::NEXT_HOP]);
421 }
422
423 #[test]
424 fn mp_reach_nlri_reject_unspecified_v6_next_hop() {
425 use crate::attribute::MpReachNlri;
426 use crate::capability::{Afi, Safi};
427
428 let attrs = vec![
429 PathAttribute::Origin(Origin::Igp),
430 PathAttribute::AsPath(AsPath {
431 segments: vec![AsPathSegment::AsSequence(vec![65001])],
432 }),
433 PathAttribute::MpReachNlri(MpReachNlri {
434 afi: Afi::Ipv6,
435 safi: Safi::Unicast,
436 next_hop: std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED),
437 announced: vec![],
438 flowspec_announced: vec![],
439 evpn_announced: vec![],
440 }),
441 ];
442 let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
443 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
444 }
445
446 #[test]
447 fn mp_reach_nlri_reject_link_local_v6_next_hop() {
448 use crate::attribute::MpReachNlri;
449 use crate::capability::{Afi, Safi};
450
451 let attrs = vec![
452 PathAttribute::Origin(Origin::Igp),
453 PathAttribute::AsPath(AsPath {
454 segments: vec![AsPathSegment::AsSequence(vec![65001])],
455 }),
456 PathAttribute::MpReachNlri(MpReachNlri {
457 afi: Afi::Ipv6,
458 safi: Safi::Unicast,
459 next_hop: std::net::IpAddr::V6("fe80::1".parse().unwrap()),
460 announced: vec![],
461 flowspec_announced: vec![],
462 evpn_announced: vec![],
463 }),
464 ];
465 let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
466 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
467 }
468
469 #[test]
470 fn mp_reach_nlri_reject_loopback_v6_next_hop() {
471 use crate::attribute::MpReachNlri;
472 use crate::capability::{Afi, Safi};
473
474 let attrs = vec![
475 PathAttribute::Origin(Origin::Igp),
476 PathAttribute::AsPath(AsPath {
477 segments: vec![AsPathSegment::AsSequence(vec![65001])],
478 }),
479 PathAttribute::MpReachNlri(MpReachNlri {
480 afi: Afi::Ipv6,
481 safi: Safi::Unicast,
482 next_hop: std::net::IpAddr::V6(std::net::Ipv6Addr::LOCALHOST),
483 announced: vec![],
484 flowspec_announced: vec![],
485 evpn_announced: vec![],
486 }),
487 ];
488 let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
489 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
490 }
491
492 #[test]
493 fn is_valid_ipv6_nexthop_accepts_global() {
494 assert!(super::is_valid_ipv6_nexthop(
495 &"2001:db8::1".parse().unwrap()
496 ));
497 }
498
499 #[test]
500 fn is_valid_ipv6_nexthop_rejects_unspecified() {
501 assert!(!super::is_valid_ipv6_nexthop(
502 &std::net::Ipv6Addr::UNSPECIFIED
503 ));
504 }
505
506 #[test]
507 fn is_valid_ipv6_nexthop_rejects_loopback() {
508 assert!(!super::is_valid_ipv6_nexthop(
509 &std::net::Ipv6Addr::LOCALHOST
510 ));
511 }
512
513 #[test]
514 fn is_valid_ipv6_nexthop_rejects_link_local() {
515 assert!(!super::is_valid_ipv6_nexthop(&"fe80::1".parse().unwrap()));
516 }
517
518 #[test]
519 fn is_valid_ipv6_nexthop_rejects_multicast() {
520 assert!(!super::is_valid_ipv6_nexthop(&"ff02::1".parse().unwrap()));
521 }
522
523 #[test]
524 fn mp_reach_nlri_reject_multicast_v6_next_hop() {
525 use crate::attribute::MpReachNlri;
526 use crate::capability::{Afi, Safi};
527
528 let attrs = vec![
529 PathAttribute::Origin(Origin::Igp),
530 PathAttribute::AsPath(AsPath {
531 segments: vec![AsPathSegment::AsSequence(vec![65001])],
532 }),
533 PathAttribute::MpReachNlri(MpReachNlri {
534 afi: Afi::Ipv6,
535 safi: Safi::Unicast,
536 next_hop: std::net::IpAddr::V6("ff02::1".parse().unwrap()),
538 announced: vec![],
539 flowspec_announced: vec![],
540 evpn_announced: vec![],
541 }),
542 ];
543 let err = validate_update_attributes(&attrs, true, false, true).unwrap_err();
544 assert_eq!(err.subcode, update_subcode::INVALID_NEXT_HOP);
545 }
546}