Skip to main content

smoltcp/iface/
slaac.rs

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/// Router solicitation state machine
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum Phase {
16    Start,
17    Discovering,
18    Maintaining,
19    None,
20}
21
22/// A prefix of addresses received via router advertisements
23#[derive(Debug, Clone, Copy)]
24pub(crate) struct Route {
25    /// IPv6 cidr to route
26    pub cidr: Ipv6Cidr,
27    /// Router, origin of the advertisement
28    pub via_router: Ipv6Address,
29    /// Valid lifetime of the route
30    pub valid_until: Instant,
31}
32
33/// Info associated with a prefix
34#[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    /// Derive the prefix information from the neighbor discovery option.
50    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    /// Get whether the prefix is still valid.
58    pub(crate) fn is_valid(&self, now: Instant) -> bool {
59        self.valid_until > now
60    }
61}
62
63impl Route {
64    /// Compare this route based on the prefix and the next hop router.
65    pub fn same_route(&self, cidr: &Ipv6Cidr, via_router: &Ipv6Address) -> bool {
66        self.cidr == *cidr && self.via_router == *via_router
67    }
68
69    /// Get whether the route is still valid.
70    pub fn is_valid(&self, now: Instant) -> bool {
71        self.valid_until > now
72    }
73}
74
75/// SLAAC runtime state
76///
77/// Tracks router solicitations and collects information from all received
78/// router advertisements.
79///
80/// State must be synchronized with the IP addresses and routes in the `Interface`.
81#[derive(Debug)]
82pub struct Slaac {
83    /// Set of prefixes received.
84    prefix: LinearMap<Ipv6Cidr, PrefixInfo, IFACE_MAX_PREFIX_COUNT>,
85    /// Set of routes received.
86    routes: Vec<Route, IFACE_MAX_ROUTE_COUNT>,
87    /// Router discovery phase.
88    phase: Phase,
89    /// Signal for address and route updates.
90    sync_required: bool,
91    /// Time to next router solicitation.
92    retry_rs_at: Instant,
93    /// Number of solicitations emitted.
94    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    /// Get whether router advertisement information is updated.
110    ///
111    /// This flags whether new prefixes or routes have been received, or current prefixes and
112    /// routes have expired.
113    pub(crate) fn has_ra_update(&self) -> bool {
114        self.sync_required
115    }
116
117    /// Get a reference to the map of prefixes stored.
118    pub(crate) fn prefix(&self) -> &LinearMap<Ipv6Cidr, PrefixInfo, IFACE_MAX_PREFIX_COUNT> {
119        &self.prefix
120    }
121
122    /// Get a reference to the set of routes stored.
123    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    /// Process a router advertisement's information.
184    pub(super) fn process_advertisement(
185        &mut self,
186        source: &Ipv6Address,
187        router_lifetime: Duration,              // default route lifetime
188        prefix: Option<NdiscPrefixInformation>, // prefix info
189        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        // Advertisement might be unsolicited
204        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    /// Get whether a route and prefix information must be synchronized with the interface.
218    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    /// Remove expired routes and prefixes.
225    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    /// Get whether a router solicitation must be emitted.
245    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    /// Update router solicitation tracking state
257    ///
258    /// Must be called after sending a router solicitation on the interface.
259    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    /// Get the next time the SLAAC state must be polled for updates.
275    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        // Unsolicited advertisement
372        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        // Solicited advertisement
381        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        // Skip time until the route expires
409        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        // Skip time until the prefix expires
423        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        // Should already return None
430        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        // No state remaining, nothing to wait on
437        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        // Invalidate the prefix, but not the route
463        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        // Invalidate also the route
478        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        // No state remaining, nothing to wait on
490        assert!(slaac.poll_at(now).is_none());
491    }
492}