smoltcp/iface/interface/
ipv6.rs

1use super::*;
2
3/// Enum used for the process_hopbyhop function. In some cases, when discarding a packet, an ICMP
4/// parameter problem message needs to be transmitted to the source of the address. In other cases,
5/// the processing of the IP packet can continue.
6#[allow(clippy::large_enum_variant)]
7enum HopByHopResponse<'frame> {
8    /// Continue processing the IPv6 packet.
9    Continue((IpProtocol, &'frame [u8])),
10    /// Discard the packet and maybe send back an ICMPv6 packet.
11    Discard(Option<Packet<'frame>>),
12}
13
14// We implement `Default` such that we can use the check! macro.
15impl Default for HopByHopResponse<'_> {
16    fn default() -> Self {
17        Self::Discard(None)
18    }
19}
20
21impl InterfaceInner {
22    /// Return the IPv6 address that is a candidate source address for the given destination
23    /// address, based on RFC 6724.
24    ///
25    /// # Panics
26    /// This function panics if the destination address is unspecified.
27    #[allow(unused)]
28    pub(crate) fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Ipv6Address {
29        assert!(!dst_addr.is_unspecified());
30
31        // See RFC 6724 Section 4: Candidate source address
32        fn is_candidate_source_address(dst_addr: &Ipv6Address, src_addr: &Ipv6Address) -> bool {
33            // For all multicast and link-local destination addresses, the candidate address MUST
34            // only be an address from the same link.
35            if dst_addr.is_link_local() && !src_addr.is_link_local() {
36                return false;
37            }
38
39            if dst_addr.is_multicast()
40                && matches!(dst_addr.x_multicast_scope(), Ipv6MulticastScope::LinkLocal)
41                && src_addr.is_multicast()
42                && !matches!(src_addr.x_multicast_scope(), Ipv6MulticastScope::LinkLocal)
43            {
44                return false;
45            }
46
47            // Unspecified addresses and multicast address can not be in the candidate source address
48            // list. Except when the destination multicast address has a link-local scope, then the
49            // source address can also be link-local multicast.
50            if src_addr.is_unspecified() || src_addr.is_multicast() {
51                return false;
52            }
53
54            true
55        }
56
57        // See RFC 6724 Section 2.2: Common Prefix Length
58        fn common_prefix_length(dst_addr: &Ipv6Cidr, src_addr: &Ipv6Address) -> usize {
59            let addr = dst_addr.address();
60            let mut bits = 0;
61            for (l, r) in addr.octets().iter().zip(src_addr.octets().iter()) {
62                if l == r {
63                    bits += 8;
64                } else {
65                    bits += (l ^ r).leading_zeros();
66                    break;
67                }
68            }
69
70            bits = bits.min(dst_addr.prefix_len() as u32);
71
72            bits as usize
73        }
74
75        // If the destination address is a loopback address, or when there are no IPv6 addresses in
76        // the interface, then the loopback address is the only candidate source address.
77        if dst_addr.is_loopback()
78            || self
79                .ip_addrs
80                .iter()
81                .filter(|a| matches!(a, IpCidr::Ipv6(_)))
82                .count()
83                == 0
84        {
85            return Ipv6Address::LOCALHOST;
86        }
87
88        let mut candidate = self
89            .ip_addrs
90            .iter()
91            .find_map(|a| match a {
92                #[cfg(feature = "proto-ipv4")]
93                IpCidr::Ipv4(_) => None,
94                IpCidr::Ipv6(a) => Some(a),
95            })
96            .unwrap(); // NOTE: we check above that there is at least one IPv6 address.
97
98        for addr in self.ip_addrs.iter().filter_map(|a| match a {
99            #[cfg(feature = "proto-ipv4")]
100            IpCidr::Ipv4(_) => None,
101            #[cfg(feature = "proto-ipv6")]
102            IpCidr::Ipv6(a) => Some(a),
103        }) {
104            if !is_candidate_source_address(dst_addr, &addr.address()) {
105                continue;
106            }
107
108            // Rule 1: prefer the address that is the same as the output destination address.
109            if candidate.address() != *dst_addr && addr.address() == *dst_addr {
110                candidate = addr;
111            }
112
113            // Rule 2: prefer appropriate scope.
114            if (candidate.address().x_multicast_scope() as u8)
115                < (addr.address().x_multicast_scope() as u8)
116            {
117                if (candidate.address().x_multicast_scope() as u8)
118                    < (dst_addr.x_multicast_scope() as u8)
119                {
120                    candidate = addr;
121                }
122            } else if (addr.address().x_multicast_scope() as u8)
123                > (dst_addr.x_multicast_scope() as u8)
124            {
125                candidate = addr;
126            }
127
128            // Rule 3: avoid deprecated addresses (TODO)
129            // Rule 4: prefer home addresses (TODO)
130            // Rule 5: prefer outgoing interfaces (TODO)
131            // Rule 5.5: prefer addresses in a prefix advertises by the next-hop (TODO).
132            // Rule 6: prefer matching label (TODO)
133            // Rule 7: prefer temporary addresses (TODO)
134            // Rule 8: use longest matching prefix
135            if common_prefix_length(candidate, dst_addr) < common_prefix_length(addr, dst_addr) {
136                candidate = addr;
137            }
138        }
139
140        candidate.address()
141    }
142
143    /// Determine if the given `Ipv6Address` is the solicited node
144    /// multicast address for a IPv6 addresses assigned to the interface.
145    /// See [RFC 4291 § 2.7.1] for more details.
146    ///
147    /// [RFC 4291 § 2.7.1]: https://tools.ietf.org/html/rfc4291#section-2.7.1
148    pub fn has_solicited_node(&self, addr: Ipv6Address) -> bool {
149        self.ip_addrs.iter().any(|cidr| {
150            match *cidr {
151                IpCidr::Ipv6(cidr) if cidr.address() != Ipv6Address::LOCALHOST => {
152                    // Take the lower order 24 bits of the IPv6 address and
153                    // append those bits to FF02:0:0:0:0:1:FF00::/104.
154                    addr.octets()[14..] == cidr.address().octets()[14..]
155                }
156                _ => false,
157            }
158        })
159    }
160
161    /// Get the first IPv6 address if present.
162    pub fn ipv6_addr(&self) -> Option<Ipv6Address> {
163        self.ip_addrs.iter().find_map(|addr| match *addr {
164            IpCidr::Ipv6(cidr) => Some(cidr.address()),
165            #[allow(unreachable_patterns)]
166            _ => None,
167        })
168    }
169
170    /// Get the first link-local IPv6 address of the interface, if present.
171    fn link_local_ipv6_address(&self) -> Option<Ipv6Address> {
172        self.ip_addrs.iter().find_map(|addr| match *addr {
173            #[cfg(feature = "proto-ipv4")]
174            IpCidr::Ipv4(_) => None,
175            #[cfg(feature = "proto-ipv6")]
176            IpCidr::Ipv6(cidr) => {
177                let addr = cidr.address();
178                if addr.is_link_local() {
179                    Some(addr)
180                } else {
181                    None
182                }
183            }
184        })
185    }
186
187    pub(super) fn process_ipv6<'frame>(
188        &mut self,
189        sockets: &mut SocketSet,
190        meta: PacketMeta,
191        source_hardware_addr: HardwareAddress,
192        ipv6_packet: &Ipv6Packet<&'frame [u8]>,
193    ) -> Option<Packet<'frame>> {
194        let ipv6_repr = check!(Ipv6Repr::parse(ipv6_packet));
195
196        if !ipv6_repr.src_addr.x_is_unicast() {
197            // Discard packets with non-unicast source addresses.
198            net_debug!("non-unicast source address");
199            return None;
200        }
201
202        let (next_header, ip_payload) = if ipv6_repr.next_header == IpProtocol::HopByHop {
203            match self.process_hopbyhop(ipv6_repr, ipv6_packet.payload()) {
204                HopByHopResponse::Discard(e) => return e,
205                HopByHopResponse::Continue(next) => next,
206            }
207        } else {
208            (ipv6_repr.next_header, ipv6_packet.payload())
209        };
210
211        if !self.has_ip_addr(ipv6_repr.dst_addr)
212            && !self.has_multicast_group(ipv6_repr.dst_addr)
213            && !ipv6_repr.dst_addr.is_loopback()
214        {
215            if !self.any_ip {
216                net_trace!("Rejecting IPv6 packet; any_ip=false");
217                return None;
218            }
219
220            if !ipv6_repr.dst_addr.x_is_unicast() {
221                net_trace!(
222                    "Rejecting IPv6 packet; {} is not a unicast address",
223                    ipv6_repr.dst_addr
224                );
225                return None;
226            }
227
228            if self
229                .routes
230                .lookup(&IpAddress::Ipv6(ipv6_repr.dst_addr), self.now)
231                .map_or(true, |router_addr| !self.has_ip_addr(router_addr))
232            {
233                net_trace!("Rejecting IPv6 packet; no matching routes");
234
235                return None;
236            }
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 => self.process_tcp(sockets, ipv6_repr.into(), ip_payload),
346
347            #[cfg(feature = "socket-raw")]
348            _ if handled_by_raw_socket => None,
349
350            _ => {
351                // Send back as much of the original payload as we can.
352                let payload_len =
353                    icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len());
354                let icmp_reply_repr = Icmpv6Repr::ParamProblem {
355                    reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
356                    // The offending packet is after the IPv6 header.
357                    pointer: ipv6_repr.buffer_len() as u32,
358                    header: ipv6_repr,
359                    data: &ip_payload[0..payload_len],
360                };
361                self.icmpv6_reply(ipv6_repr, icmp_reply_repr)
362            }
363        }
364    }
365
366    pub(super) fn process_icmpv6<'frame>(
367        &mut self,
368        _sockets: &mut SocketSet,
369        ip_repr: Ipv6Repr,
370        ip_payload: &'frame [u8],
371    ) -> Option<Packet<'frame>> {
372        let icmp_packet = check!(Icmpv6Packet::new_checked(ip_payload));
373        let icmp_repr = check!(Icmpv6Repr::parse(
374            &ip_repr.src_addr,
375            &ip_repr.dst_addr,
376            &icmp_packet,
377            &self.caps.checksum,
378        ));
379
380        #[cfg(feature = "socket-icmp")]
381        let mut handled_by_icmp_socket = false;
382
383        #[cfg(feature = "socket-icmp")]
384        {
385            use crate::socket::icmp::Socket as IcmpSocket;
386            for icmp_socket in _sockets
387                .items_mut()
388                .filter_map(|i| IcmpSocket::downcast_mut(&mut i.socket))
389            {
390                if icmp_socket.accepts_v6(self, &ip_repr, &icmp_repr) {
391                    icmp_socket.process_v6(self, &ip_repr, &icmp_repr);
392                    handled_by_icmp_socket = true;
393                }
394            }
395        }
396
397        match icmp_repr {
398            // Respond to echo requests.
399            Icmpv6Repr::EchoRequest {
400                ident,
401                seq_no,
402                data,
403            } => {
404                let icmp_reply_repr = Icmpv6Repr::EchoReply {
405                    ident,
406                    seq_no,
407                    data,
408                };
409                self.icmpv6_reply(ip_repr, icmp_reply_repr)
410            }
411
412            // Ignore any echo replies.
413            Icmpv6Repr::EchoReply { .. } => None,
414
415            // Forward any NDISC packets to the ndisc packet handler
416            #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
417            Icmpv6Repr::Ndisc(repr) if ip_repr.hop_limit == 0xff => match self.caps.medium {
418                #[cfg(feature = "medium-ethernet")]
419                Medium::Ethernet => self.process_ndisc(ip_repr, repr),
420                #[cfg(feature = "medium-ieee802154")]
421                Medium::Ieee802154 => self.process_ndisc(ip_repr, repr),
422                #[cfg(feature = "medium-ip")]
423                Medium::Ip => None,
424            },
425            #[cfg(feature = "multicast")]
426            Icmpv6Repr::Mld(repr) => match repr {
427                // [RFC 3810 § 6.2], reception checks
428                MldRepr::Query { .. }
429                    if ip_repr.hop_limit == 1 && ip_repr.src_addr.is_link_local() =>
430                {
431                    self.process_mldv2(ip_repr, repr)
432                }
433                _ => None,
434            },
435
436            // Don't report an error if a packet with unknown type
437            // has been handled by an ICMP socket
438            #[cfg(feature = "socket-icmp")]
439            _ if handled_by_icmp_socket => None,
440
441            // FIXME: do something correct here?
442            _ => None,
443        }
444    }
445
446    #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
447    pub(super) fn process_ndisc<'frame>(
448        &mut self,
449        ip_repr: Ipv6Repr,
450        repr: NdiscRepr<'frame>,
451    ) -> Option<Packet<'frame>> {
452        match repr {
453            NdiscRepr::NeighborAdvert {
454                lladdr,
455                target_addr,
456                flags,
457            } => {
458                let ip_addr = ip_repr.src_addr.into();
459                if let Some(lladdr) = lladdr {
460                    let lladdr = check!(lladdr.parse(self.caps.medium));
461                    if !lladdr.is_unicast() || !target_addr.x_is_unicast() {
462                        return None;
463                    }
464                    if flags.contains(NdiscNeighborFlags::OVERRIDE)
465                        || !self.neighbor_cache.lookup(&ip_addr, self.now).found()
466                    {
467                        self.neighbor_cache.fill(ip_addr, lladdr, self.now)
468                    }
469                }
470                None
471            }
472            NdiscRepr::NeighborSolicit {
473                target_addr,
474                lladdr,
475                ..
476            } => {
477                if let Some(lladdr) = lladdr {
478                    let lladdr = check!(lladdr.parse(self.caps.medium));
479                    if !lladdr.is_unicast() || !target_addr.x_is_unicast() {
480                        return None;
481                    }
482                    self.neighbor_cache
483                        .fill(ip_repr.src_addr.into(), lladdr, self.now);
484                }
485
486                if self.has_solicited_node(ip_repr.dst_addr) && self.has_ip_addr(target_addr) {
487                    let advert = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
488                        flags: NdiscNeighborFlags::SOLICITED,
489                        target_addr,
490                        #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
491                        lladdr: Some(self.hardware_addr.into()),
492                    });
493                    let ip_repr = Ipv6Repr {
494                        src_addr: target_addr,
495                        dst_addr: ip_repr.src_addr,
496                        next_header: IpProtocol::Icmpv6,
497                        hop_limit: 0xff,
498                        payload_len: advert.buffer_len(),
499                    };
500                    Some(Packet::new_ipv6(ip_repr, IpPayload::Icmpv6(advert)))
501                } else {
502                    None
503                }
504            }
505            _ => None,
506        }
507    }
508
509    pub(super) fn icmpv6_reply<'frame, 'icmp: 'frame>(
510        &self,
511        ipv6_repr: Ipv6Repr,
512        icmp_repr: Icmpv6Repr<'icmp>,
513    ) -> Option<Packet<'frame>> {
514        let src_addr = ipv6_repr.dst_addr;
515        let dst_addr = ipv6_repr.src_addr;
516
517        let src_addr = if src_addr.x_is_unicast() {
518            src_addr
519        } else {
520            self.get_source_address_ipv6(&dst_addr)
521        };
522
523        let ipv6_reply_repr = Ipv6Repr {
524            src_addr,
525            dst_addr,
526            next_header: IpProtocol::Icmpv6,
527            payload_len: icmp_repr.buffer_len(),
528            hop_limit: 64,
529        };
530        Some(Packet::new_ipv6(
531            ipv6_reply_repr,
532            IpPayload::Icmpv6(icmp_repr),
533        ))
534    }
535
536    pub(super) fn mldv2_report_packet<'any>(
537        &self,
538        records: &'any [MldAddressRecordRepr<'any>],
539    ) -> Option<Packet<'any>> {
540        // Per [RFC 3810 § 5.2.13], source addresses must be link-local, falling
541        // back to the unspecified address if we haven't acquired one.
542        // [RFC 3810 § 5.2.13]: https://tools.ietf.org/html/rfc3810#section-5.2.13
543        let src_addr = self
544            .link_local_ipv6_address()
545            .unwrap_or(Ipv6Address::UNSPECIFIED);
546
547        // Per [RFC 3810 § 5.2.14], all MLDv2 reports are sent to ff02::16.
548        // [RFC 3810 § 5.2.14]: https://tools.ietf.org/html/rfc3810#section-5.2.14
549        let dst_addr = IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS;
550
551        // Create a dummy IPv6 extension header so we can calculate the total length of the packet.
552        // The actual extension header will be created later by Packet::emit_payload().
553        let dummy_ext_hdr = Ipv6ExtHeaderRepr {
554            next_header: IpProtocol::Unknown(0),
555            length: 0,
556            data: &[],
557        };
558
559        let mut hbh_repr = Ipv6HopByHopRepr::mldv2_router_alert();
560        hbh_repr.push_padn_option(0);
561
562        let mld_repr = MldRepr::ReportRecordReprs(records);
563        let records_len = records
564            .iter()
565            .map(MldAddressRecordRepr::buffer_len)
566            .sum::<usize>();
567
568        // All MLDv2 messages must be sent with an IPv6 Hop limit of 1.
569        Some(Packet::new_ipv6(
570            Ipv6Repr {
571                src_addr,
572                dst_addr,
573                next_header: IpProtocol::HopByHop,
574                payload_len: dummy_ext_hdr.header_len()
575                    + hbh_repr.buffer_len()
576                    + mld_repr.buffer_len()
577                    + records_len,
578                hop_limit: 1,
579            },
580            IpPayload::HopByHopIcmpv6(hbh_repr, Icmpv6Repr::Mld(mld_repr)),
581        ))
582    }
583}