Skip to main content

smoltcp/iface/interface/
ipv6.rs

1use super::*;
2
3use crate::iface::Route;
4
5/// Enum used for the process_hopbyhop function. In some cases, when discarding a packet, an ICMP
6/// parameter problem message needs to be transmitted to the source of the address. In other cases,
7/// the processing of the IP packet can continue.
8#[allow(clippy::large_enum_variant)]
9enum HopByHopResponse<'frame> {
10    /// Continue processing the IPv6 packet.
11    Continue((IpProtocol, &'frame [u8])),
12    /// Discard the packet and maybe send back an ICMPv6 packet.
13    Discard(Option<Packet<'frame>>),
14}
15
16// We implement `Default` such that we can use the check! macro.
17impl Default for HopByHopResponse<'_> {
18    fn default() -> Self {
19        Self::Discard(None)
20    }
21}
22
23impl InterfaceInner {
24    /// Return the IPv6 address that is a candidate source address for the given destination
25    /// address, based on RFC 6724.
26    ///
27    /// # Panics
28    /// This function panics if the destination address is unspecified.
29    #[allow(unused)]
30    pub(crate) fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Ipv6Address {
31        assert!(!dst_addr.is_unspecified());
32
33        // See RFC 6724 Section 4: Candidate source address
34        fn is_candidate_source_address(dst_addr: &Ipv6Address, src_addr: &Ipv6Address) -> bool {
35            // For all multicast and link-local destination addresses, the candidate address MUST
36            // only be an address from the same link.
37            if dst_addr.is_link_local() && !src_addr.is_link_local() {
38                return false;
39            }
40
41            if dst_addr.is_multicast()
42                && matches!(dst_addr.x_multicast_scope(), Ipv6MulticastScope::LinkLocal)
43                && src_addr.is_multicast()
44                && !matches!(src_addr.x_multicast_scope(), Ipv6MulticastScope::LinkLocal)
45            {
46                return false;
47            }
48
49            // Unspecified addresses and multicast address can not be in the candidate source address
50            // list. Except when the destination multicast address has a link-local scope, then the
51            // source address can also be link-local multicast.
52            if src_addr.is_unspecified() || src_addr.is_multicast() {
53                return false;
54            }
55
56            true
57        }
58
59        // See RFC 6724 Section 2.2: Common Prefix Length
60        fn common_prefix_length(dst_addr: &Ipv6Cidr, src_addr: &Ipv6Address) -> usize {
61            let addr = dst_addr.address();
62            let mut bits = 0;
63            for (l, r) in addr.octets().iter().zip(src_addr.octets().iter()) {
64                if l == r {
65                    bits += 8;
66                } else {
67                    bits += (l ^ r).leading_zeros();
68                    break;
69                }
70            }
71
72            bits = bits.min(dst_addr.prefix_len() as u32);
73
74            bits as usize
75        }
76
77        // If the destination address is a loopback address, or when there are no IPv6 addresses in
78        // the interface, then the loopback address is the only candidate source address.
79        if dst_addr.is_loopback()
80            || self
81                .ip_addrs
82                .iter()
83                .filter(|a| matches!(a, IpCidr::Ipv6(_)))
84                .count()
85                == 0
86        {
87            return Ipv6Address::LOCALHOST;
88        }
89
90        let mut candidate = self
91            .ip_addrs
92            .iter()
93            .find_map(|a| match a {
94                #[cfg(feature = "proto-ipv4")]
95                IpCidr::Ipv4(_) => None,
96                IpCidr::Ipv6(a) => Some(a),
97            })
98            .unwrap(); // NOTE: we check above that there is at least one IPv6 address.
99
100        for addr in self.ip_addrs.iter().filter_map(|a| match a {
101            #[cfg(feature = "proto-ipv4")]
102            IpCidr::Ipv4(_) => None,
103            #[cfg(feature = "proto-ipv6")]
104            IpCidr::Ipv6(a) => Some(a),
105        }) {
106            if !is_candidate_source_address(dst_addr, &addr.address()) {
107                continue;
108            }
109
110            // Rule 1: prefer the address that is the same as the output destination address.
111            if candidate.address() != *dst_addr && addr.address() == *dst_addr {
112                candidate = addr;
113            }
114
115            // Rule 2: prefer appropriate scope.
116            if (candidate.address().x_multicast_scope() as u8)
117                < (addr.address().x_multicast_scope() as u8)
118            {
119                if (candidate.address().x_multicast_scope() as u8)
120                    < (dst_addr.x_multicast_scope() as u8)
121                {
122                    candidate = addr;
123                }
124            } else if (addr.address().x_multicast_scope() as u8)
125                > (dst_addr.x_multicast_scope() as u8)
126            {
127                candidate = addr;
128            }
129
130            // Rule 3: avoid deprecated addresses (TODO)
131            // Rule 4: prefer home addresses (TODO)
132            // Rule 5: prefer outgoing interfaces (TODO)
133            // Rule 5.5: prefer addresses in a prefix advertises by the next-hop (TODO).
134            // Rule 6: prefer matching label (TODO)
135            // Rule 7: prefer temporary addresses (TODO)
136            // Rule 8: use longest matching prefix
137            if common_prefix_length(candidate, dst_addr) < common_prefix_length(addr, dst_addr) {
138                candidate = addr;
139            }
140        }
141
142        candidate.address()
143    }
144
145    /// Determine if the given `Ipv6Address` is the solicited node
146    /// multicast address for a IPv6 addresses assigned to the interface.
147    /// See [RFC 4291 § 2.7.1] for more details.
148    ///
149    /// [RFC 4291 § 2.7.1]: https://tools.ietf.org/html/rfc4291#section-2.7.1
150    pub fn has_solicited_node(&self, addr: Ipv6Address) -> bool {
151        self.ip_addrs.iter().any(|cidr| {
152            match *cidr {
153                IpCidr::Ipv6(cidr) if cidr.address() != Ipv6Address::LOCALHOST => {
154                    // Take the lower order 24 bits of the IPv6 address and
155                    // append those bits to FF02:0:0:0:0:1:FF00::/104.
156                    addr.octets()[14..] == cidr.address().octets()[14..]
157                }
158                _ => false,
159            }
160        })
161    }
162
163    /// Get the first IPv6 address if present.
164    pub fn ipv6_addr(&self) -> Option<Ipv6Address> {
165        self.ip_addrs.iter().find_map(|addr| match *addr {
166            IpCidr::Ipv6(cidr) => Some(cidr.address()),
167            #[allow(unreachable_patterns)]
168            _ => None,
169        })
170    }
171
172    /// Get the first link-local IPv6 address of the interface, if present.
173    fn link_local_ipv6_address(&self) -> Option<Ipv6Address> {
174        self.ip_addrs.iter().find_map(|addr| match *addr {
175            #[cfg(feature = "proto-ipv4")]
176            IpCidr::Ipv4(_) => None,
177            #[cfg(feature = "proto-ipv6")]
178            IpCidr::Ipv6(cidr) => {
179                let addr = cidr.address();
180                if addr.is_link_local() {
181                    Some(addr)
182                } else {
183                    None
184                }
185            }
186        })
187    }
188
189    pub(super) fn process_ipv6<'frame>(
190        &mut self,
191        sockets: &mut SocketSet,
192        meta: PacketMeta,
193        source_hardware_addr: HardwareAddress,
194        ipv6_packet: &Ipv6Packet<&'frame [u8]>,
195    ) -> Option<Packet<'frame>> {
196        let ipv6_repr = check!(Ipv6Repr::parse(ipv6_packet));
197
198        if !ipv6_repr.src_addr.x_is_unicast() {
199            // Discard packets with non-unicast source addresses.
200            net_debug!("non-unicast source address");
201            return None;
202        }
203
204        let (next_header, ip_payload) = if ipv6_repr.next_header == IpProtocol::HopByHop {
205            match self.process_hopbyhop(ipv6_repr, ipv6_packet.payload()) {
206                HopByHopResponse::Discard(e) => return e,
207                HopByHopResponse::Continue(next) => next,
208            }
209        } else {
210            (ipv6_repr.next_header, ipv6_packet.payload())
211        };
212
213        if !self.has_ip_addr(ipv6_repr.dst_addr)
214            && !self.has_multicast_group(ipv6_repr.dst_addr)
215            && !ipv6_repr.dst_addr.is_loopback()
216        {
217            if !ipv6_repr.dst_addr.x_is_unicast() {
218                net_trace!(
219                    "Rejecting IPv6 packet; {} is not a unicast address",
220                    ipv6_repr.dst_addr
221                );
222                return None;
223            }
224
225            if self
226                .routes
227                .lookup(&IpAddress::Ipv6(ipv6_repr.dst_addr), self.now)
228                .is_none_or(|router_addr| !self.has_ip_addr(router_addr))
229            {
230                net_trace!("Rejecting IPv6 packet; no matching routes");
231
232                return None;
233            }
234
235            net_trace!("Rejecting IPv6 packet; no assigned address");
236            return None;
237        }
238
239        #[cfg(feature = "socket-raw")]
240        let handled_by_raw_socket = self.raw_socket_filter(sockets, &ipv6_repr.into(), ip_payload);
241        #[cfg(not(feature = "socket-raw"))]
242        let handled_by_raw_socket = false;
243
244        #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
245        if ipv6_repr.dst_addr.x_is_unicast() {
246            self.neighbor_cache.reset_expiry_if_existing(
247                IpAddress::Ipv6(ipv6_repr.src_addr),
248                source_hardware_addr,
249                self.now,
250            );
251        }
252
253        self.process_nxt_hdr(
254            sockets,
255            meta,
256            ipv6_repr,
257            next_header,
258            handled_by_raw_socket,
259            ip_payload,
260        )
261    }
262
263    fn process_hopbyhop<'frame>(
264        &mut self,
265        ipv6_repr: Ipv6Repr,
266        ip_payload: &'frame [u8],
267    ) -> HopByHopResponse<'frame> {
268        let param_problem = || {
269            let payload_len =
270                icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len());
271            self.icmpv6_reply(
272                ipv6_repr,
273                Icmpv6Repr::ParamProblem {
274                    reason: Icmpv6ParamProblem::UnrecognizedOption,
275                    pointer: ipv6_repr.buffer_len() as u32,
276                    header: ipv6_repr,
277                    data: &ip_payload[0..payload_len],
278                },
279            )
280        };
281
282        let ext_hdr = check!(Ipv6ExtHeader::new_checked(ip_payload));
283        let ext_repr = check!(Ipv6ExtHeaderRepr::parse(&ext_hdr));
284        let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ext_repr.data));
285        let hbh_repr = check!(Ipv6HopByHopRepr::parse(&hbh_hdr));
286
287        for opt_repr in &hbh_repr.options {
288            match opt_repr {
289                Ipv6OptionRepr::Pad1 | Ipv6OptionRepr::PadN(_) | Ipv6OptionRepr::RouterAlert(_) => {
290                }
291                #[cfg(feature = "proto-rpl")]
292                Ipv6OptionRepr::Rpl(_) => {}
293
294                Ipv6OptionRepr::Unknown { type_, .. } => {
295                    match Ipv6OptionFailureType::from(*type_) {
296                        Ipv6OptionFailureType::Skip => (),
297                        Ipv6OptionFailureType::Discard => {
298                            return HopByHopResponse::Discard(None);
299                        }
300                        Ipv6OptionFailureType::DiscardSendAll => {
301                            return HopByHopResponse::Discard(param_problem());
302                        }
303                        Ipv6OptionFailureType::DiscardSendUnicast => {
304                            if !ipv6_repr.dst_addr.is_multicast() {
305                                return HopByHopResponse::Discard(param_problem());
306                            } else {
307                                return HopByHopResponse::Discard(None);
308                            }
309                        }
310                    }
311                }
312            }
313        }
314
315        HopByHopResponse::Continue((
316            ext_repr.next_header,
317            &ip_payload[ext_repr.header_len() + ext_repr.data.len()..],
318        ))
319    }
320
321    /// Given the next header value forward the payload onto the correct process
322    /// function.
323    fn process_nxt_hdr<'frame>(
324        &mut self,
325        sockets: &mut SocketSet,
326        meta: PacketMeta,
327        ipv6_repr: Ipv6Repr,
328        nxt_hdr: IpProtocol,
329        handled_by_raw_socket: bool,
330        ip_payload: &'frame [u8],
331    ) -> Option<Packet<'frame>> {
332        match nxt_hdr {
333            IpProtocol::Icmpv6 => self.process_icmpv6(sockets, ipv6_repr, ip_payload),
334
335            #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
336            IpProtocol::Udp => self.process_udp(
337                sockets,
338                meta,
339                handled_by_raw_socket,
340                ipv6_repr.into(),
341                ip_payload,
342            ),
343
344            #[cfg(feature = "socket-tcp")]
345            IpProtocol::Tcp => {
346                self.process_tcp(sockets, handled_by_raw_socket, ipv6_repr.into(), ip_payload)
347            }
348
349            #[cfg(feature = "socket-raw")]
350            _ if handled_by_raw_socket => None,
351
352            _ => {
353                // Send back as much of the original payload as we can.
354                let payload_len =
355                    icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len());
356                let icmp_reply_repr = Icmpv6Repr::ParamProblem {
357                    reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
358                    // The offending packet is after the IPv6 header.
359                    pointer: ipv6_repr.buffer_len() as u32,
360                    header: ipv6_repr,
361                    data: &ip_payload[0..payload_len],
362                };
363                self.icmpv6_reply(ipv6_repr, icmp_reply_repr)
364            }
365        }
366    }
367
368    pub(super) fn process_icmpv6<'frame>(
369        &mut self,
370        _sockets: &mut SocketSet,
371        ip_repr: Ipv6Repr,
372        ip_payload: &'frame [u8],
373    ) -> Option<Packet<'frame>> {
374        let icmp_packet = check!(Icmpv6Packet::new_checked(ip_payload));
375        let icmp_repr = check!(Icmpv6Repr::parse(
376            &ip_repr.src_addr,
377            &ip_repr.dst_addr,
378            &icmp_packet,
379            &self.caps.checksum,
380        ));
381
382        #[cfg(feature = "socket-icmp")]
383        let mut handled_by_icmp_socket = false;
384
385        #[cfg(feature = "socket-icmp")]
386        {
387            use crate::socket::icmp::Socket as IcmpSocket;
388            for icmp_socket in _sockets
389                .items_mut()
390                .filter_map(|i| IcmpSocket::downcast_mut(&mut i.socket))
391            {
392                if icmp_socket.accepts_v6(self, &ip_repr, &icmp_repr) {
393                    icmp_socket.process_v6(self, &ip_repr, &icmp_repr);
394                    handled_by_icmp_socket = true;
395                }
396            }
397        }
398
399        match icmp_repr {
400            // Respond to echo requests.
401            #[cfg(feature = "auto-icmp-echo-reply")]
402            Icmpv6Repr::EchoRequest {
403                ident,
404                seq_no,
405                data,
406            } => {
407                let icmp_reply_repr = Icmpv6Repr::EchoReply {
408                    ident,
409                    seq_no,
410                    data,
411                };
412                self.icmpv6_reply(ip_repr, icmp_reply_repr)
413            }
414
415            // Ignore any echo replies.
416            Icmpv6Repr::EchoReply { .. } => None,
417
418            // Forward any NDISC packets to the ndisc packet handler
419            #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
420            Icmpv6Repr::Ndisc(repr) if ip_repr.hop_limit == 0xff => match self.caps.medium {
421                #[cfg(feature = "medium-ethernet")]
422                Medium::Ethernet => self.process_ndisc(ip_repr, repr),
423                #[cfg(feature = "medium-ieee802154")]
424                Medium::Ieee802154 => self.process_ndisc(ip_repr, repr),
425                #[cfg(feature = "medium-ip")]
426                Medium::Ip => None,
427            },
428            #[cfg(feature = "multicast")]
429            Icmpv6Repr::Mld(repr) => match repr {
430                // [RFC 3810 § 6.2], reception checks
431                MldRepr::Query { .. }
432                    if ip_repr.hop_limit == 1 && ip_repr.src_addr.is_link_local() =>
433                {
434                    self.process_mldv2(ip_repr, repr)
435                }
436                _ => None,
437            },
438
439            // Don't report an error if a packet with unknown type
440            // has been handled by an ICMP socket
441            #[cfg(feature = "socket-icmp")]
442            _ if handled_by_icmp_socket => None,
443
444            // FIXME: do something correct here?
445            // By doing nothing, this arm handles the case when auto echo replies are disabled.
446            _ => None,
447        }
448    }
449
450    #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
451    pub(super) fn process_ndisc<'frame>(
452        &mut self,
453        ip_repr: Ipv6Repr,
454        repr: NdiscRepr<'frame>,
455    ) -> Option<Packet<'frame>> {
456        match repr {
457            NdiscRepr::NeighborAdvert {
458                lladdr,
459                target_addr,
460                flags,
461            } => {
462                let ip_addr = ip_repr.src_addr.into();
463                if let Some(lladdr) = lladdr {
464                    let lladdr = check!(lladdr.parse(self.caps.medium));
465                    if !lladdr.is_unicast() || !target_addr.x_is_unicast() {
466                        return None;
467                    }
468                    if flags.contains(NdiscNeighborFlags::OVERRIDE)
469                        || !self.neighbor_cache.lookup(&ip_addr, self.now).found()
470                    {
471                        self.neighbor_cache.fill(ip_addr, lladdr, self.now)
472                    }
473                }
474                None
475            }
476            NdiscRepr::NeighborSolicit {
477                target_addr,
478                lladdr,
479                ..
480            } => {
481                if let Some(lladdr) = lladdr {
482                    let lladdr = check!(lladdr.parse(self.caps.medium));
483                    if !lladdr.is_unicast() || !target_addr.x_is_unicast() {
484                        return None;
485                    }
486                    self.neighbor_cache
487                        .fill(ip_repr.src_addr.into(), lladdr, self.now);
488                }
489
490                if self.has_solicited_node(ip_repr.dst_addr) && self.has_ip_addr(target_addr) {
491                    let advert = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
492                        flags: NdiscNeighborFlags::SOLICITED,
493                        target_addr,
494                        #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
495                        lladdr: Some(self.hardware_addr.into()),
496                    });
497                    let ip_repr = Ipv6Repr {
498                        src_addr: target_addr,
499                        dst_addr: ip_repr.src_addr,
500                        next_header: IpProtocol::Icmpv6,
501                        hop_limit: 0xff,
502                        payload_len: advert.buffer_len(),
503                    };
504                    Some(Packet::new_ipv6(ip_repr, IpPayload::Icmpv6(advert)))
505                } else {
506                    None
507                }
508            }
509            #[cfg(feature = "proto-ipv6-slaac")]
510            NdiscRepr::RouterAdvert {
511                hop_limit: _,
512                flags: _,
513                router_lifetime,
514                reachable_time: _,
515                retrans_time: _,
516                lladdr: _,
517                mtu: _,
518                prefix_info,
519            } if self.slaac_enabled => {
520                if ip_repr.src_addr.is_link_local()
521                    && (ip_repr.dst_addr == IPV6_LINK_LOCAL_ALL_NODES
522                        || ip_repr.dst_addr.is_link_local())
523                    && ip_repr.hop_limit == 255
524                {
525                    self.slaac.process_advertisement(
526                        &ip_repr.src_addr,
527                        router_lifetime,
528                        prefix_info,
529                        self.now,
530                    )
531                }
532                None
533            }
534            _ => None,
535        }
536    }
537
538    pub(super) fn icmpv6_reply<'frame, 'icmp: 'frame>(
539        &self,
540        ipv6_repr: Ipv6Repr,
541        icmp_repr: Icmpv6Repr<'icmp>,
542    ) -> Option<Packet<'frame>> {
543        let src_addr = ipv6_repr.dst_addr;
544        let dst_addr = ipv6_repr.src_addr;
545
546        let src_addr = if src_addr.x_is_unicast() {
547            src_addr
548        } else {
549            self.get_source_address_ipv6(&dst_addr)
550        };
551
552        let ipv6_reply_repr = Ipv6Repr {
553            src_addr,
554            dst_addr,
555            next_header: IpProtocol::Icmpv6,
556            payload_len: icmp_repr.buffer_len(),
557            hop_limit: 64,
558        };
559        Some(Packet::new_ipv6(
560            ipv6_reply_repr,
561            IpPayload::Icmpv6(icmp_repr),
562        ))
563    }
564
565    pub(super) fn mldv2_report_packet<'any>(
566        &self,
567        records: &'any [MldAddressRecordRepr<'any>],
568    ) -> Option<Packet<'any>> {
569        // Per [RFC 3810 § 5.2.13], source addresses must be link-local, falling
570        // back to the unspecified address if we haven't acquired one.
571        // [RFC 3810 § 5.2.13]: https://tools.ietf.org/html/rfc3810#section-5.2.13
572        let src_addr = self
573            .link_local_ipv6_address()
574            .unwrap_or(Ipv6Address::UNSPECIFIED);
575
576        // Per [RFC 3810 § 5.2.14], all MLDv2 reports are sent to ff02::16.
577        // [RFC 3810 § 5.2.14]: https://tools.ietf.org/html/rfc3810#section-5.2.14
578        let dst_addr = IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS;
579
580        // Create a dummy IPv6 extension header so we can calculate the total length of the packet.
581        // The actual extension header will be created later by Packet::emit_payload().
582        let dummy_ext_hdr = Ipv6ExtHeaderRepr {
583            next_header: IpProtocol::Unknown(0),
584            length: 0,
585            data: &[],
586        };
587
588        let mut hbh_repr = Ipv6HopByHopRepr::mldv2_router_alert();
589        hbh_repr.push_padn_option(0);
590
591        let mld_repr = MldRepr::ReportRecordReprs(records);
592        let records_len = records
593            .iter()
594            .map(MldAddressRecordRepr::buffer_len)
595            .sum::<usize>();
596
597        // All MLDv2 messages must be sent with an IPv6 Hop limit of 1.
598        Some(Packet::new_ipv6(
599            Ipv6Repr {
600                src_addr,
601                dst_addr,
602                next_header: IpProtocol::HopByHop,
603                payload_len: dummy_ext_hdr.header_len()
604                    + hbh_repr.buffer_len()
605                    + mld_repr.buffer_len()
606                    + records_len,
607                hop_limit: 1,
608            },
609            IpPayload::HopByHopIcmpv6(hbh_repr, Icmpv6Repr::Mld(mld_repr)),
610        ))
611    }
612}
613
614impl Interface {
615    /// Synchronize the slaac address and router state with the interface state.
616    #[cfg(feature = "proto-ipv6-slaac")]
617    pub(super) fn sync_slaac_state(&mut self, timestamp: Instant) {
618        let required_addresses: Vec<_, IFACE_MAX_PREFIX_COUNT> = self
619            .inner
620            .slaac
621            .prefix()
622            .iter()
623            .filter_map(|(prefix, prefixinfo)| {
624                if prefixinfo.is_valid(timestamp) {
625                    Ipv6Cidr::from_link_prefix(prefix, self.inner.hardware_addr())
626                } else {
627                    None
628                }
629            })
630            .collect();
631        let removed_addresses: Vec<_, IFACE_MAX_PREFIX_COUNT> = self
632            .inner
633            .slaac
634            .prefix()
635            .iter()
636            .filter_map(|(prefix, prefixinfo)| {
637                if !prefixinfo.is_valid(timestamp) {
638                    Ipv6Cidr::from_link_prefix(prefix, self.inner.hardware_addr())
639                } else {
640                    None
641                }
642            })
643            .collect();
644
645        self.update_ip_addrs(|addresses| {
646            for address in required_addresses {
647                if !addresses.contains(&IpCidr::Ipv6(address)) {
648                    let _ = addresses.push(IpCidr::Ipv6(address));
649                }
650            }
651            addresses.retain(|address| {
652                if let IpCidr::Ipv6(address) = address {
653                    !removed_addresses.contains(address)
654                } else {
655                    true
656                }
657            });
658        });
659
660        {
661            let required_routes = self
662                .inner
663                .slaac
664                .routes()
665                .into_iter()
666                .filter(|required| required.is_valid(timestamp));
667
668            let removed_routes = self
669                .inner
670                .slaac
671                .routes()
672                .into_iter()
673                .filter(|r| !r.is_valid(timestamp));
674
675            self.inner.routes.update(|routes| {
676                routes.retain(|r| match (&r.cidr, &r.via_router) {
677                    (IpCidr::Ipv6(cidr), IpAddress::Ipv6(via_router)) => !removed_routes
678                        .clone()
679                        .any(|f| f.same_route(cidr, via_router)),
680                    _ => true,
681                });
682
683                for route in required_routes {
684                    if routes.iter().all(|r| match (&r.cidr, &r.via_router) {
685                        (IpCidr::Ipv6(cidr), IpAddress::Ipv6(via_router)) => {
686                            !route.same_route(cidr, via_router)
687                        }
688                        _ => false,
689                    }) {
690                        let _ = routes.push(Route {
691                            cidr: route.cidr.into(),
692                            via_router: route.via_router.into(),
693                            preferred_until: None,
694                            expires_at: None,
695                        });
696                    }
697                }
698            });
699        }
700        self.inner.slaac_updated = timestamp;
701        self.inner.slaac.update_slaac_state(timestamp);
702    }
703
704    /// Retrieve the timestamp at which the slaac state was last updated.
705    #[cfg(feature = "proto-ipv6-slaac")]
706    pub fn slaac_updated_at(&self) -> Instant {
707        self.inner.slaac_updated
708    }
709
710    /// Emit a router solicitation when required by the interface's slaac state machine.
711    #[cfg(feature = "proto-ipv6-slaac")]
712    pub(super) fn ndisc_rs_egress(&mut self, device: &mut (impl Device + ?Sized)) {
713        if !self.inner.slaac.rs_required(self.inner.now) {
714            return;
715        }
716        let rs_repr = Icmpv6Repr::Ndisc(NdiscRepr::RouterSolicit {
717            lladdr: Some(self.hardware_addr().into()),
718        });
719        let ipv6_repr = Ipv6Repr {
720            src_addr: self.inner.link_local_ipv6_address().unwrap(),
721            dst_addr: IPV6_LINK_LOCAL_ALL_ROUTERS,
722            next_header: IpProtocol::Icmpv6,
723            payload_len: rs_repr.buffer_len(),
724            hop_limit: 255,
725        };
726        let packet = Packet::new_ipv6(ipv6_repr, IpPayload::Icmpv6(rs_repr));
727        let Some(tx_token) = device.transmit(self.inner.now) else {
728            return;
729        };
730        // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
731        self.inner
732            .dispatch_ip(
733                tx_token,
734                PacketMeta::default(),
735                packet,
736                &mut self.fragmenter,
737            )
738            .unwrap();
739        self.inner.slaac.rs_sent(self.inner.now);
740    }
741}