1#![deny(missing_docs)]
2use heapless::{LinearMap, Vec};
3
4use crate::config::{IFACE_MAX_PREFIX_COUNT, IFACE_MAX_ROUTE_COUNT};
5use crate::time::{Duration, Instant};
6use crate::wire::NdiscPrefixInfoFlags;
7use crate::wire::{Ipv6Address, Ipv6Cidr, NdiscPrefixInformation, ipv6::AddressExt};
8
9const MAX_RTR_SOLICITATIONS: u8 = 3;
10const RTR_SOLICITATION_INTERVAL: Duration = Duration::from_secs(4);
11const IPV6_DEFAULT: Ipv6Cidr = Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 0);
12
13#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum Phase {
16 Start,
17 Discovering,
18 Maintaining,
19 None,
20}
21
22#[derive(Debug, Clone, Copy)]
24pub(crate) struct Route {
25 pub cidr: Ipv6Cidr,
27 pub via_router: Ipv6Address,
29 pub valid_until: Instant,
31}
32
33#[derive(Debug, Clone, Copy)]
35#[cfg_attr(feature = "defmt", derive(defmt::Format))]
36pub struct PrefixInfo {
37 preferred_until: Instant,
38 valid_until: Instant,
39}
40
41impl PrefixInfo {
42 fn new(preferred_until: Instant, valid_until: Instant) -> Self {
43 Self {
44 preferred_until,
45 valid_until,
46 }
47 }
48
49 pub(crate) fn from_prefix(prefix: &NdiscPrefixInformation, now: Instant) -> Self {
51 let preferred_until = now + prefix.preferred_lifetime;
52 let valid_until = now + prefix.valid_lifetime;
53
54 Self::new(preferred_until, valid_until)
55 }
56
57 pub(crate) fn is_valid(&self, now: Instant) -> bool {
59 self.valid_until > now
60 }
61}
62
63impl Route {
64 pub fn same_route(&self, cidr: &Ipv6Cidr, via_router: &Ipv6Address) -> bool {
66 self.cidr == *cidr && self.via_router == *via_router
67 }
68
69 pub fn is_valid(&self, now: Instant) -> bool {
71 self.valid_until > now
72 }
73}
74
75#[derive(Debug)]
82pub struct Slaac {
83 prefix: LinearMap<Ipv6Cidr, PrefixInfo, IFACE_MAX_PREFIX_COUNT>,
85 routes: Vec<Route, IFACE_MAX_ROUTE_COUNT>,
87 phase: Phase,
89 sync_required: bool,
91 retry_rs_at: Instant,
93 num_solicitations: u8,
95}
96
97impl Slaac {
98 pub(super) fn new() -> Self {
99 Self {
100 prefix: LinearMap::new(),
101 routes: Vec::new(),
102 phase: Phase::Start,
103 sync_required: false,
104 retry_rs_at: Instant::from_millis(0),
105 num_solicitations: MAX_RTR_SOLICITATIONS,
106 }
107 }
108
109 pub(crate) fn has_ra_update(&self) -> bool {
114 self.sync_required
115 }
116
117 pub(crate) fn prefix(&self) -> &LinearMap<Ipv6Cidr, PrefixInfo, IFACE_MAX_PREFIX_COUNT> {
119 &self.prefix
120 }
121
122 pub(crate) fn routes(&self) -> &Vec<Route, IFACE_MAX_ROUTE_COUNT> {
124 &self.routes
125 }
126
127 fn add_prefix(&mut self, cidr: &Ipv6Cidr, prefix: &NdiscPrefixInformation, now: Instant) {
128 if cidr.address().is_link_local() {
129 return;
130 }
131 let prefix_info = PrefixInfo::from_prefix(prefix, now);
132 if let Ok(old_info) = self.prefix.insert(*cidr, prefix_info)
133 && old_info.is_none()
134 {
135 self.sync_required = true;
136 }
137 }
138
139 fn expire_prefix(&mut self, cidr: &Ipv6Cidr) {
140 if let Some(info) = self.prefix.get_mut(cidr) {
141 info.valid_until = Instant::from_millis(0);
142 info.preferred_until = Instant::from_millis(0);
143 self.sync_required = true;
144 }
145 }
146
147 fn add_route(&mut self, cidr: &Ipv6Cidr, router: &Ipv6Address, valid_until: Instant) {
148 if let Some(route) = self.routes.iter_mut().find(|r| r.same_route(cidr, router)) {
149 route.valid_until = valid_until;
150 } else {
151 let _ = self.routes.push(Route {
152 cidr: *cidr,
153 via_router: *router,
154 valid_until,
155 });
156 self.sync_required = true;
157 }
158 }
159
160 fn expire_route(&mut self, cidr: &Ipv6Cidr, via_router: &Ipv6Address) {
161 for route in self.routes.iter_mut() {
162 if route.same_route(cidr, via_router) {
163 route.valid_until = Instant::from_millis(0);
164 self.sync_required = true;
165 }
166 }
167 }
168
169 fn process_prefix(&mut self, prefix: NdiscPrefixInformation, now: Instant) {
170 if !prefix.flags.contains(NdiscPrefixInfoFlags::ADDRCONF) {
171 return;
172 }
173
174 let cidr = Ipv6Cidr::new(prefix.prefix, prefix.prefix_len);
175
176 if prefix.valid_lifetime > Duration::ZERO {
177 self.add_prefix(&cidr, &prefix, now);
178 } else {
179 self.expire_prefix(&cidr);
180 }
181 }
182
183 pub(super) fn process_advertisement(
185 &mut self,
186 source: &Ipv6Address,
187 router_lifetime: Duration, prefix: Option<NdiscPrefixInformation>, now: Instant,
190 ) {
191 if let Some(prefix) = prefix
192 && prefix.is_valid_prefix_info()
193 {
194 self.process_prefix(prefix, now)
195 }
196
197 if router_lifetime > Duration::ZERO {
198 self.add_route(&IPV6_DEFAULT, source, now + router_lifetime);
199 } else {
200 self.expire_route(&IPV6_DEFAULT, source);
201 }
202
203 if self.phase == Phase::Discovering {
205 self.phase = Phase::Maintaining;
206 }
207 }
208
209 fn prefix_expire_sync_required(&self, now: Instant) -> bool {
210 self.prefix.values().any(|info| !info.is_valid(now))
211 }
212
213 fn route_expire_sync_required(&self, now: Instant) -> bool {
214 self.routes.iter().any(|r| !r.is_valid(now))
215 }
216
217 pub(crate) fn sync_required(&self, now: Instant) -> bool {
219 self.has_ra_update()
220 || self.prefix_expire_sync_required(now)
221 || self.route_expire_sync_required(now)
222 }
223
224 pub(crate) fn update_slaac_state(&mut self, now: Instant) {
226 let removals: Vec<Ipv6Cidr, IFACE_MAX_PREFIX_COUNT> = self
227 .prefix
228 .iter()
229 .filter_map(|(cidr, info)| {
230 if info.is_valid(now) {
231 None
232 } else {
233 Some(*cidr)
234 }
235 })
236 .collect();
237 for cidr in removals.iter() {
238 self.prefix.remove(cidr);
239 }
240 self.routes.retain(|r| r.is_valid(now));
241 self.sync_required = false;
242 }
243
244 pub(crate) fn rs_required(&self, now: Instant) -> bool {
246 match self.phase {
247 Phase::Start | Phase::Discovering
248 if self.retry_rs_at <= now && self.num_solicitations > 0 =>
249 {
250 true
251 }
252 _ => false,
253 }
254 }
255
256 pub(crate) fn rs_sent(&mut self, now: Instant) {
260 match self.phase {
261 Phase::Start | Phase::Discovering if self.retry_rs_at <= now => {
262 if self.num_solicitations == 0 {
263 self.phase = Phase::None;
264 } else {
265 self.num_solicitations -= 1;
266 self.phase = Phase::Discovering;
267 self.retry_rs_at = now + RTR_SOLICITATION_INTERVAL;
268 }
269 }
270 _ => (),
271 }
272 }
273
274 pub(crate) fn poll_at(&self, now: Instant) -> Option<Instant> {
276 match self.phase {
277 Phase::Discovering | Phase::Start => Some(self.retry_rs_at),
278 Phase::Maintaining => {
279 let prefix_at = self.prefix.values().filter_map(|prefix_info| {
280 if prefix_info.is_valid(now) {
281 Some(prefix_info.valid_until)
282 } else {
283 None
284 }
285 });
286 let routes_at = self.routes.iter().filter_map(|r| {
287 if r.is_valid(now) {
288 Some(r.valid_until)
289 } else {
290 None
291 }
292 });
293 prefix_at.chain(routes_at).min()
294 }
295 _ => None,
296 }
297 }
298}
299
300#[cfg(test)]
301mod test {
302 use super::*;
303 mod mock {
304 use super::super::*;
305 pub const SOURCE: Ipv6Address = Ipv6Address::new(0xfe80, 0xdb8, 0, 0, 0, 0, 0, 0);
306 pub const PREFIX: NdiscPrefixInformation = NdiscPrefixInformation {
307 prefix_len: 64,
308 flags: NdiscPrefixInfoFlags::ADDRCONF,
309 valid_lifetime: Duration::from_secs(700),
310 preferred_lifetime: Duration::from_secs(300),
311 prefix: Ipv6Address::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
312 };
313 pub const VALID: Duration = Duration::from_secs(600);
314
315 pub const ROUTE: Route = Route {
316 cidr: Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 0),
317 via_router: SOURCE,
318 valid_until: Instant::from_millis_const(100000),
319 };
320 }
321 use mock::*;
322
323 #[test]
324 fn test_route() {
325 assert!(ROUTE.same_route(&Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 0), &SOURCE));
326 assert!(!ROUTE.same_route(&Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 64), &SOURCE));
327 assert!(!ROUTE.same_route(
328 &Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 0),
329 &Ipv6Address::UNSPECIFIED
330 ));
331 assert!(!ROUTE.same_route(&Ipv6Cidr::new(SOURCE, 0), &Ipv6Address::UNSPECIFIED));
332 assert!(!ROUTE.same_route(&Ipv6Cidr::new(SOURCE, 64), &Ipv6Address::UNSPECIFIED));
333 }
334
335 #[test]
336 fn test_route_valid() {
337 assert!(ROUTE.is_valid(Instant::ZERO));
338 assert!(!ROUTE.is_valid(Instant::from_secs(200)));
339 }
340
341 #[test]
342 fn test_solicitation() {
343 let mut slaac = Slaac::new();
344 let now = Instant::from_millis(1);
345 assert!(slaac.rs_required(now));
346
347 slaac.rs_sent(now);
348 assert_eq!(slaac.num_solicitations, 2);
349 assert!(!slaac.rs_required(now));
350
351 let next_poll = slaac.poll_at(now).unwrap();
352 assert_eq!(next_poll, now + RTR_SOLICITATION_INTERVAL);
353
354 let now = next_poll;
355 assert!(slaac.rs_required(now));
356
357 slaac.num_solicitations = 0;
358 assert!(!slaac.rs_required(now));
359 slaac.rs_sent(now);
360 assert_eq!(slaac.phase, Phase::None);
361 assert!(slaac.poll_at(now).is_none());
362 }
363
364 #[test]
365 fn test_ra_state() {
366 let mut slaac = Slaac::new();
367 assert_eq!(slaac.phase, Phase::Start);
368 let now = Instant::from_millis(1);
369 assert!(!slaac.has_ra_update());
370
371 slaac.process_advertisement(&SOURCE, VALID, Some(PREFIX), now);
373 assert_eq!(slaac.phase, Phase::Start);
374 assert!(slaac.has_ra_update());
375
376 let now = Instant::from_secs(300);
377 slaac.rs_sent(now);
378 assert_eq!(slaac.phase, Phase::Discovering);
379
380 slaac.process_advertisement(&SOURCE, VALID, Some(PREFIX), now);
382 slaac.process_advertisement(&SOURCE, VALID, Some(PREFIX), now);
383 assert_eq!(slaac.phase, Phase::Maintaining);
384 let poll_at = slaac.poll_at(now).unwrap();
385 assert_eq!(poll_at, now + VALID);
386
387 for (prefix, info) in slaac.prefix() {
388 assert_eq!(prefix.address(), PREFIX.prefix);
389 assert_eq!(prefix.prefix_len(), PREFIX.prefix_len);
390 assert_eq!(info.valid_until, now + PREFIX.valid_lifetime);
391 assert_eq!(info.preferred_until, now + PREFIX.preferred_lifetime);
392 assert!(info.is_valid(now));
393 }
394
395 for route in slaac.routes() {
396 assert_eq!(route.cidr, Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 0));
397 assert_eq!(route.via_router, SOURCE);
398 assert_eq!(route.valid_until, now + VALID);
399 assert!(route.is_valid(now));
400 }
401 assert_eq!(slaac.prefix().len(), 1);
402 assert_eq!(slaac.routes().len(), 1);
403 assert!(slaac.sync_required(now));
404
405 slaac.update_slaac_state(now);
406 assert!(!slaac.sync_required(now));
407
408 let now = poll_at;
410 assert!(slaac.sync_required(now));
411 for (_prefix, info) in slaac.prefix() {
412 assert!(info.is_valid(now));
413 }
414 for route in slaac.routes() {
415 assert!(!route.is_valid(now));
416 }
417
418 slaac.update_slaac_state(now);
419 assert!(!slaac.sync_required(now));
420 assert_eq!(slaac.routes().len(), 0);
421
422 let poll_at = slaac.poll_at(now).unwrap();
424 let now = poll_at;
425 assert!(slaac.sync_required(now));
426 for (_prefix, info) in slaac.prefix() {
427 assert!(!info.is_valid(now));
428 }
429 assert!(slaac.poll_at(now).is_none());
431 slaac.update_slaac_state(now);
432 assert!(!slaac.sync_required(now));
433 assert_eq!(slaac.routes().len(), 0);
434 assert_eq!(slaac.prefix().len(), 0);
435
436 assert!(slaac.poll_at(now).is_none());
438 }
439
440 #[test]
441 fn test_ra_expire() {
442 let mut slaac = Slaac::new();
443 let now = Instant::from_millis(1);
444 slaac.rs_sent(now);
445 slaac.process_advertisement(&SOURCE, VALID, Some(PREFIX), now);
446
447 let now = Instant::from_secs(300);
448
449 assert!(slaac.sync_required(now));
450 for (_prefix, info) in slaac.prefix() {
451 assert!(info.is_valid(now));
452 }
453 for route in slaac.routes() {
454 assert!(route.is_valid(now));
455 }
456 slaac.update_slaac_state(now);
457
458 let mut expire_prefix = PREFIX;
459 expire_prefix.preferred_lifetime = Duration::ZERO;
460 expire_prefix.valid_lifetime = Duration::ZERO;
461
462 slaac.process_advertisement(&SOURCE, VALID, Some(expire_prefix), now);
464
465 assert!(slaac.sync_required(now));
466 for (_prefix, info) in slaac.prefix() {
467 assert!(!info.is_valid(now));
468 }
469 for route in slaac.routes() {
470 assert!(route.is_valid(now));
471 }
472 slaac.update_slaac_state(now);
473 assert_eq!(slaac.prefix().len(), 0);
474 assert_eq!(slaac.routes().len(), 1);
475
476 assert!(!slaac.sync_required(now));
477 slaac.process_advertisement(&SOURCE, Duration::ZERO, Some(expire_prefix), now);
479 assert!(slaac.sync_required(now));
480 for route in slaac.routes() {
481 assert!(!route.is_valid(now));
482 }
483 assert!(slaac.poll_at(now).is_none());
484
485 slaac.update_slaac_state(now);
486 assert_eq!(slaac.prefix().len(), 0);
487 assert_eq!(slaac.routes().len(), 0);
488 assert!(!slaac.sync_required(now));
489 assert!(slaac.poll_at(now).is_none());
491 }
492}