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