consomme/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! The Consomme user-mode TCP stack.
5//!
6//! This crate implements a user-mode TCP stack designed for use with
7//! virtualization. The guest operating system sends Ethernet frames, and this
8//! crate parses them and distributes the data streams to individual TCP and UDP
9//! sockets.
10//!
11//! The current implementation supports OS-backed TCP and UDP sockets,
12//! essentially causing this stack to act as a NAT implementation, providing
13//! guest OS networking by leveraging the host's network stack.
14//!
15//! This implementation includes a small DHCP server for address assignment.
16
17mod arp;
18mod dhcp;
19mod dhcpv6;
20#[cfg_attr(unix, path = "dns_unix.rs")]
21#[cfg_attr(windows, path = "dns_windows.rs")]
22mod dns;
23mod dns_resolver;
24mod icmp;
25mod ndp;
26mod tcp;
27mod udp;
28
29mod unix;
30mod windows;
31
32/// Standard DNS port number.
33const DNS_PORT: u16 = 53;
34
35use inspect::Inspect;
36use inspect::InspectMut;
37use pal_async::driver::Driver;
38use smoltcp::phy::Checksum;
39use smoltcp::phy::ChecksumCapabilities;
40use smoltcp::wire::DhcpMessageType;
41use smoltcp::wire::EthernetAddress;
42use smoltcp::wire::EthernetFrame;
43use smoltcp::wire::EthernetProtocol;
44use smoltcp::wire::EthernetRepr;
45use smoltcp::wire::IPV4_HEADER_LEN;
46use smoltcp::wire::Icmpv6Packet;
47use smoltcp::wire::IpAddress;
48use smoltcp::wire::IpProtocol;
49use smoltcp::wire::Ipv4Address;
50use smoltcp::wire::Ipv4Packet;
51use smoltcp::wire::Ipv6Address;
52use smoltcp::wire::Ipv6Packet;
53use std::task::Context;
54use std::time::Duration;
55use thiserror::Error;
56
57/// A consomme instance.
58#[derive(InspectMut)]
59pub struct Consomme {
60    state: ConsommeState,
61    #[inspect(mut)]
62    tcp: tcp::Tcp,
63    #[inspect(mut)]
64    udp: udp::Udp,
65    icmp: icmp::Icmp,
66    dns: Option<dns_resolver::DnsResolver>,
67    host_has_ipv6: bool,
68}
69
70#[derive(Inspect)]
71struct ConsommeState {
72    params: ConsommeParams,
73    #[inspect(skip)]
74    buffer: Box<[u8]>,
75}
76
77/// Dynamic networking properties of a consomme endpoint.
78#[derive(Inspect, Clone)]
79pub struct ConsommeParams {
80    /// Current IPv4 network mask.
81    #[inspect(display)]
82    pub net_mask: Ipv4Address,
83    /// Current Ipv4 gateway address.
84    #[inspect(display)]
85    pub gateway_ip: Ipv4Address,
86    /// Current Ipv4 gateway MAC address.
87    #[inspect(display)]
88    pub gateway_mac: EthernetAddress,
89    /// Current Ipv4 address assigned to endpoint.
90    #[inspect(display)]
91    pub client_ip: Ipv4Address,
92    /// Current client MAC address.
93    #[inspect(display)]
94    pub client_mac: EthernetAddress,
95    /// Current list of DNS resolvers.
96    #[inspect(with = "|x| inspect::iter_by_index(x).map_value(inspect::AsDisplay)")]
97    pub nameservers: Vec<IpAddress>,
98    /// Current IPv6 network mask (if any).
99    #[inspect(display)]
100    pub prefix_len_ipv6: u8,
101    /// Current IPv6 gateway MAC address (if any).
102    #[inspect(display)]
103    pub gateway_mac_ipv6: EthernetAddress,
104    /// Gateway's link-local IPv6 address (derived from gateway_mac_ipv6).
105    ///
106    /// This is the address used as the source for NDP Router Advertisements
107    /// and as the target for Neighbor Solicitations.
108    #[inspect(display)]
109    pub gateway_link_local_ipv6: Ipv6Address,
110    /// Current IPv6 address learned from guest via SLAAC (if any).
111    ///
112    /// With SLAAC (Stateless Address Autoconfiguration), the guest generates
113    /// its own IPv6 address using the advertised prefix and its interface identifier.
114    /// This field is learned from incoming IPv6 traffic from the guest.
115    #[inspect(with = "Option::is_some")]
116    pub client_ip_ipv6: Option<Ipv6Address>,
117    /// Idle timeout for UDP connections.
118    #[inspect(debug)]
119    pub udp_timeout: Duration,
120    /// If true, skip checks for host IPv6 support and assume the host has a
121    /// routable IPv6 address.
122    #[inspect(display)]
123    pub skip_ipv6_checks: bool,
124}
125
126/// An error indicating that the CIDR is invalid.
127#[derive(Debug, Error)]
128#[error("invalid CIDR")]
129pub struct InvalidCidr;
130
131impl ConsommeParams {
132    /// Create default dynamic network state. The default state is
133    ///     IP address: 10.0.0.2 / 24
134    ///     gateway: 10.0.0.1 with MAC address 52-55-10-0-0-1
135    ///     IPv6 address: is not assigned by us, we expect the guest to assign it via SLAAC
136    ///     gateway IPv6 link-local address: fe80::5055:aff:fe00:102 (EUI-64 derived from
137    ///     gateway MAC address 52-55-0A-00-01-02)
138    pub fn new() -> Result<Self, Error> {
139        let nameservers = dns::nameservers()?;
140        let gateway_mac_ipv6 = EthernetAddress([0x52, 0x55, 0x0A, 0x00, 0x01, 0x02]);
141
142        Ok(Self {
143            gateway_ip: Ipv4Address::new(10, 0, 0, 1),
144            gateway_mac: EthernetAddress([0x52, 0x55, 10, 0, 0, 1]),
145            client_ip: Ipv4Address::new(10, 0, 0, 2),
146            client_mac: EthernetAddress([0x0, 0x0, 0x0, 0x0, 0x1, 0x0]),
147            net_mask: Ipv4Address::new(255, 255, 255, 0),
148            nameservers,
149            prefix_len_ipv6: 64,
150            gateway_mac_ipv6,
151            gateway_link_local_ipv6: Self::compute_link_local_address(gateway_mac_ipv6),
152            client_ip_ipv6: None,
153            // Per RFC 4787, UDP NAT bindings, by default, should timeout after 5 minutes, but can be configured.
154            udp_timeout: Duration::from_secs(300),
155            skip_ipv6_checks: false,
156        })
157    }
158
159    /// Sets the cidr for the network.
160    ///
161    /// Setting, for example, 192.168.0.0/24 will set the gateway to
162    /// 192.168.0.1 and the client IP to 192.168.0.2.
163    pub fn set_cidr(&mut self, cidr: &str) -> Result<(), InvalidCidr> {
164        let cidr: smoltcp::wire::Ipv4Cidr = cidr.parse().map_err(|()| InvalidCidr)?;
165        let base_address = cidr.network().address();
166        let mut gateway_octets = base_address.octets();
167        gateway_octets[3] += 1;
168        self.gateway_ip = Ipv4Address::from(gateway_octets);
169        let mut client_octets = base_address.octets();
170        client_octets[3] += 2;
171        self.client_ip = Ipv4Address::from(client_octets);
172        self.net_mask = cidr.netmask();
173        Ok(())
174    }
175
176    /// Compute a link-local IPv6 address from a MAC address using EUI-64 format.
177    ///
178    /// RFC 4291 Section 2.5.6: Link-local addresses are formed by combining
179    /// the link-local prefix (fe80::/64) with an interface identifier derived
180    /// from the MAC address using the EUI-64 format.
181    ///
182    /// EUI-64 format (RFC 2464 Section 4):
183    /// - Insert 0xFFFE in the middle of the 48-bit MAC address
184    /// - Invert the universal/local bit (bit 6 of the first byte)
185    pub fn compute_link_local_address(mac: EthernetAddress) -> Ipv6Address {
186        const LINK_LOCAL_PREFIX: [u8; 8] = [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
187
188        let mut addr = [0u8; 16];
189
190        // Set link-local prefix (fe80::/64)
191        addr[0..8].copy_from_slice(&LINK_LOCAL_PREFIX);
192
193        // Create EUI-64 interface identifier from MAC address
194        // MAC: AB:CD:EF:11:22:33
195        // EUI-64: AB:CD:EF:FF:FE:11:22:33 with universal/local bit flipped
196        addr[8] = mac.0[0] ^ 0x02; // Flip the universal/local bit
197        addr[9] = mac.0[1];
198        addr[10] = mac.0[2];
199        addr[11] = 0xFF;
200        addr[12] = 0xFE;
201        addr[13] = mac.0[3];
202        addr[14] = mac.0[4];
203        addr[15] = mac.0[5];
204
205        Ipv6Address::from_octets(addr)
206    }
207
208    /// Returns the list of IPv6 nameservers suitable for advertisement to
209    /// guests via NDP RDNSS or DHCPv6.
210    ///
211    /// Filters out addresses that are not useful as DNS servers in a
212    /// guest-facing context: unspecified, loopback, multicast, unique-local
213    /// (fc00::/7), and deprecated site-local (fec0::/10) addresses.
214    pub fn filtered_ipv6_nameservers(&self) -> Vec<Ipv6Address> {
215        self.nameservers
216            .iter()
217            .filter_map(|ip| match ip {
218                IpAddress::Ipv6(addr) => Some(*addr),
219                _ => None,
220            })
221            .filter(|addr| {
222                let octets = addr.octets();
223                !(addr.is_unspecified()
224                    || addr.is_loopback()
225                    || addr.is_multicast()
226                    || matches!(octets[0], 0xfc | 0xfd) // unique local address
227                    || octets.starts_with(&[0xfe, 0xc0])) // deprecated site-local
228            })
229            .collect()
230    }
231
232    /// Returns the default internal nameserver list for use when the DNS
233    /// resolver is active. Includes the IPv6 gateway only when the host
234    /// has a routable IPv6 address.
235    fn internal_nameservers(&self, host_has_ipv6: bool) -> Vec<IpAddress> {
236        let mut ns = vec![self.gateway_ip.into()];
237        if host_has_ipv6 {
238            ns.push(self.gateway_link_local_ipv6.into());
239        }
240        ns
241    }
242}
243
244/// An accessor for consomme.
245pub struct Access<'a, T> {
246    inner: &'a mut Consomme,
247    client: &'a mut T,
248}
249
250/// A consomme client.
251pub trait Client {
252    /// Gets the driver to use for handling new connections.
253    ///
254    /// TODO: generalize connection creation to allow pluggable model (not just
255    /// OS sockets) and remove this.
256    fn driver(&self) -> &dyn Driver;
257
258    /// Transmits a packet to the client.
259    ///
260    /// If `checksum.ipv4`, `checksum.tcp`, or `checksum.udp` are set, then the
261    /// packet contains an IPv4 header, TCP header, and/or UDP header with a
262    /// valid checksum.
263    ///
264    /// TODO:
265    ///
266    /// 1. support >MTU sized packets (RSC/LRO/GRO)
267    /// 2. allow discontiguous data to eliminate the extra copy from the TCP
268    ///    window.
269    fn recv(&mut self, data: &[u8], checksum: &ChecksumState);
270
271    /// Specifies the maximum size for the next call to `recv`.
272    ///
273    /// This is the MTU including the Ethernet frame header. This must be at
274    /// least [`MIN_MTU`].
275    ///
276    /// Return 0 to indicate that there are no buffers available for receiving
277    /// data.
278    fn rx_mtu(&mut self) -> usize;
279}
280
281/// Specifies the checksum state for a packet being transmitted.
282#[derive(Debug, Copy, Clone)]
283pub struct ChecksumState {
284    /// On receive, the data has a valid IPv4 header checksum. On send, the
285    /// checksum should be ignored.
286    pub ipv4: bool,
287    /// On receive, the data has a valid TCP checksum. On send, the checksum
288    /// should be ignored.
289    pub tcp: bool,
290    /// On receive, the data has a valid UDP checksum. On send, the checksum
291    /// should be ignored.
292    pub udp: bool,
293    /// The data consists of multiple TCP segments, each with the provided
294    /// segment size.
295    ///
296    /// The IP header's length field may be invalid and should be ignored.
297    pub tso: Option<u16>,
298}
299
300impl ChecksumState {
301    const NONE: Self = Self {
302        ipv4: false,
303        tcp: false,
304        udp: false,
305        tso: None,
306    };
307    const IPV4_ONLY: Self = Self {
308        ipv4: true,
309        tcp: false,
310        udp: false,
311        tso: None,
312    };
313    const TCP4: Self = Self {
314        ipv4: true,
315        tcp: true,
316        udp: false,
317        tso: None,
318    };
319    const UDP4: Self = Self {
320        ipv4: true,
321        tcp: false,
322        udp: true,
323        tso: None,
324    };
325    const TCP6: Self = Self {
326        ipv4: false,
327        tcp: true,
328        udp: false,
329        tso: None,
330    };
331
332    fn caps(&self) -> ChecksumCapabilities {
333        let mut caps = ChecksumCapabilities::default();
334        if self.ipv4 {
335            caps.ipv4 = Checksum::None;
336        }
337        if self.tcp {
338            caps.tcp = Checksum::None;
339        }
340        if self.udp {
341            caps.udp = Checksum::None;
342        }
343        caps
344    }
345}
346
347/// The minimum MTU for receives supported by Consomme (including the Ethernet
348/// frame).
349pub const MIN_MTU: usize = 1514;
350
351/// The reason a packet was dropped without being handled.
352#[derive(Debug, Error)]
353pub enum DropReason {
354    /// The packet could not be parsed.
355    #[error("packet parsing error")]
356    Packet(#[from] smoltcp::wire::Error),
357    /// The ethertype is unknown.
358    #[error("unsupported ethertype {0}")]
359    UnsupportedEthertype(EthernetProtocol),
360    /// The ethertype is unknown.
361    #[error("unsupported ip protocol {0}")]
362    UnsupportedIpProtocol(IpProtocol),
363    /// The ARP type is unsupported.
364    #[error("unsupported dhcp message type {0:?}")]
365    UnsupportedDhcp(DhcpMessageType),
366    /// The ARP type is unsupported.
367    #[error("unsupported arp type")]
368    UnsupportedArp,
369    /// The IPv4 checksum was invalid.
370    #[error("ipv4 checksum failure")]
371    Ipv4Checksum,
372    /// The send buffer is invalid.
373    #[error("send buffer full")]
374    SendBufferFull,
375    /// There was an IO error.
376    #[error("io error")]
377    Io(#[source] std::io::Error),
378    /// The TCP state is invalid.
379    #[error("bad tcp state")]
380    BadTcpState(#[from] tcp::TcpError),
381    /// Specified port is not bound.
382    #[error("port is not bound")]
383    PortNotBound,
384    /// The DHCPv6 message type is unsupported.
385    #[error("unsupported dhcpv6 message type {0:?}")]
386    UnsupportedDhcpv6(dhcpv6::MessageType),
387    /// The NDP message type is unsupported.
388    #[error("unsupported ndp message type {0:?}")]
389    UnsupportedNdp(ndp::NdpMessageType),
390    /// An incoming packet was recognized but was self-contradictory.
391    /// E.g. a TCP packet with both SYN and FIN flags set.
392    #[error("packet is malformed")]
393    MalformedPacket,
394    /// An incoming IP packet has been split into several IP fragments and was dropped,
395    /// since IP reassembly is not supported.
396    #[error("packet fragmentation is not supported")]
397    FragmentedPacket,
398}
399
400/// An error to create a consomme instance.
401#[derive(Debug, Error)]
402pub enum Error {
403    /// Could not get DNS nameserver information.
404    #[error("failed to initialize nameservers")]
405    Dns(#[from] dns::Error),
406}
407
408#[derive(Debug)]
409struct Ipv4Addresses {
410    src_addr: Ipv4Address,
411    dst_addr: Ipv4Address,
412}
413
414#[derive(Debug)]
415struct Ipv6Addresses {
416    src_addr: Ipv6Address,
417    dst_addr: Ipv6Address,
418}
419
420#[derive(Debug)]
421enum IpAddresses {
422    V4(Ipv4Addresses),
423    V6(Ipv6Addresses),
424}
425
426impl IpAddresses {
427    fn src_addr(&self) -> IpAddress {
428        match self {
429            IpAddresses::V4(addrs) => IpAddress::Ipv4(addrs.src_addr),
430            IpAddresses::V6(addrs) => IpAddress::Ipv6(addrs.src_addr),
431        }
432    }
433
434    fn dst_addr(&self) -> IpAddress {
435        match self {
436            IpAddresses::V4(addrs) => IpAddress::Ipv4(addrs.dst_addr),
437            IpAddresses::V6(addrs) => IpAddress::Ipv6(addrs.dst_addr),
438        }
439    }
440}
441
442/// Returns `true` if the given IPv6 address is a globally routable unicast
443/// address (i.e., not loopback, unspecified, or link-local).
444fn is_routable_ipv6(addr: &std::net::Ipv6Addr) -> bool {
445    !addr.is_loopback() && !addr.is_unspecified() && !addr.is_unicast_link_local()
446}
447
448impl Consomme {
449    /// Creates a new consomme instance with specified state.
450    pub fn new(mut params: ConsommeParams) -> Self {
451        let host_has_ipv6 = if params.skip_ipv6_checks {
452            true
453        } else {
454            #[cfg(windows)]
455            let host_has_ipv6_result = windows::host_has_ipv6_address().map_err(|e| e.to_string());
456            #[cfg(unix)]
457            let host_has_ipv6_result = unix::host_has_ipv6_address().map_err(|e| e.to_string());
458
459            match host_has_ipv6_result {
460                Ok(has_ipv6) => has_ipv6,
461                Err(e) => {
462                    tracelimit::warn_ratelimited!(
463                        "failed to check for host IPv6 address, assuming no IPv6 support: {e}"
464                    );
465                    false
466                }
467            }
468        };
469        let dns =
470            match dns_resolver::DnsResolver::new(dns_resolver::DEFAULT_MAX_PENDING_DNS_REQUESTS) {
471                Ok(dns) => {
472                    // When the DNS resolver is available, use the default internal nameserver.
473                    params.nameservers = params.internal_nameservers(host_has_ipv6);
474                    Some(dns)
475                }
476                Err(_) => {
477                    tracelimit::warn_ratelimited!(
478                        "failed to initialize DNS resolver, falling back to using host DNS settings"
479                    );
480                    None
481                }
482            };
483        let timeout = params.udp_timeout;
484        Self {
485            state: ConsommeState {
486                params,
487                buffer: Box::new([0; 65536]),
488            },
489            tcp: tcp::Tcp::new(),
490            udp: udp::Udp::new(timeout),
491            icmp: icmp::Icmp::new(),
492            dns,
493            host_has_ipv6,
494        }
495    }
496
497    /// Get access to the parameters to be updated.
498    ///
499    /// FUTURE: add support for updating only the parameters that can be safely
500    /// changed at runtime.
501    pub fn params_mut(&mut self) -> &mut ConsommeParams {
502        &mut self.state.params
503    }
504
505    /// Pairs the client with this instance to operate on the consomme instance.
506    pub fn access<'a, T: Client>(&'a mut self, client: &'a mut T) -> Access<'a, T> {
507        Access {
508            inner: self,
509            client,
510        }
511    }
512}
513
514impl<T: Client> Access<'_, T> {
515    /// Gets the inner consomme object.
516    pub fn get(&self) -> &Consomme {
517        self.inner
518    }
519
520    /// Gets the inner consomme object.
521    pub fn get_mut(&mut self) -> &mut Consomme {
522        self.inner
523    }
524
525    /// Polls for work, transmitting any ready packets to the client.
526    pub fn poll(&mut self, cx: &mut Context<'_>) {
527        self.poll_udp(cx);
528        self.poll_tcp(cx);
529        self.poll_icmp(cx);
530    }
531
532    /// Update all sockets to use the new client's IO driver. This must be
533    /// called if the previous driver is no longer usable or if the client
534    /// otherwise wants existing connections to be polled on a new IO driver.
535    pub fn refresh_driver(&mut self) {
536        self.refresh_tcp_driver();
537        self.refresh_udp_driver();
538    }
539
540    /// Sends an Ethernet frame to the network.
541    ///
542    /// If `checksum.ipv4`, `checksum.tcp`, or `checksum.udp` are set, then
543    /// skips validating the IPv4, TCP, and UDP checksums. Otherwise, these
544    /// checksums are validated as normal and packets with invalid checksums are
545    /// dropped.
546    ///
547    /// If `checksum.tso.is_some()`, then perform TCP segmentation offset on the
548    /// frame. Practically speaking, this means that the frame contains a TCP
549    /// packet with these caveats:
550    ///
551    ///   * The IP header length may be invalid and will be ignored. The TCP
552    ///     packet payload is assumed to end at the end of `data`.
553    ///   * The TCP segment's payload size may be larger than the advertized TCP
554    ///     MSS value.
555    ///
556    /// This allows for sending TCP data that is much larger than the MSS size
557    /// via a single call.
558    ///
559    /// TODO:
560    ///
561    ///   1. allow for discontiguous packets
562    ///   2. allow for packets in guest memory (including lifetime model, if
563    ///      necessary--currently TCP transmits only happen in `poll`, but this
564    ///      may not be necessary. If the underlying socket implementation
565    ///      performs a copy (as the standard kernel socket APIs do), then no
566    ///      lifetime model is necessary, but if an implementation wants
567    ///      zerocopy support then some mechanism to allow the guest memory to
568    ///      be released later will be necessary.
569    pub fn send(&mut self, data: &[u8], checksum: &ChecksumState) -> Result<(), DropReason> {
570        let frame_packet = EthernetFrame::new_unchecked(data);
571        let frame = EthernetRepr::parse(&frame_packet)?;
572        match frame.ethertype {
573            EthernetProtocol::Ipv4 => self.handle_ipv4(&frame, frame_packet.payload(), checksum)?,
574            EthernetProtocol::Ipv6 => {
575                if self.inner.host_has_ipv6 {
576                    self.handle_ipv6(&frame, frame_packet.payload(), checksum)?
577                }
578            }
579            EthernetProtocol::Arp => self.handle_arp(&frame, frame_packet.payload())?,
580            _ => return Err(DropReason::UnsupportedEthertype(frame.ethertype)),
581        }
582        Ok(())
583    }
584
585    fn handle_ipv4(
586        &mut self,
587        frame: &EthernetRepr,
588        payload: &[u8],
589        checksum: &ChecksumState,
590    ) -> Result<(), DropReason> {
591        let ipv4 = Ipv4Packet::new_unchecked(payload);
592        if payload.len() < IPV4_HEADER_LEN
593            || ipv4.version() != 4
594            || payload.len() < ipv4.header_len().into()
595            || payload.len() < ipv4.total_len().into()
596        {
597            return Err(DropReason::MalformedPacket);
598        }
599
600        let total_len = if checksum.tso.is_some() {
601            payload.len()
602        } else {
603            ipv4.total_len().into()
604        };
605        if total_len < ipv4.header_len().into() {
606            return Err(DropReason::MalformedPacket);
607        }
608
609        if ipv4.more_frags() || ipv4.frag_offset() != 0 {
610            return Err(DropReason::FragmentedPacket);
611        }
612
613        if !checksum.ipv4 && !ipv4.verify_checksum() {
614            return Err(DropReason::Ipv4Checksum);
615        }
616
617        let addresses = Ipv4Addresses {
618            src_addr: ipv4.src_addr(),
619            dst_addr: ipv4.dst_addr(),
620        };
621
622        let inner = &payload[ipv4.header_len().into()..total_len];
623
624        match ipv4.next_header() {
625            IpProtocol::Tcp => self.handle_tcp(&IpAddresses::V4(addresses), inner, checksum)?,
626            IpProtocol::Udp => {
627                self.handle_udp(frame, &IpAddresses::V4(addresses), inner, checksum)?
628            }
629            IpProtocol::Icmp => {
630                self.handle_icmp(frame, &addresses, inner, checksum, ipv4.hop_limit())?
631            }
632            p => return Err(DropReason::UnsupportedIpProtocol(p)),
633        };
634        Ok(())
635    }
636
637    fn handle_ipv6(
638        &mut self,
639        frame: &EthernetRepr,
640        payload: &[u8],
641        checksum: &ChecksumState,
642    ) -> Result<(), DropReason> {
643        let ipv6 = Ipv6Packet::new_unchecked(payload);
644        if payload.len() < smoltcp::wire::IPV6_HEADER_LEN || ipv6.version() != 6 {
645            return Err(DropReason::MalformedPacket);
646        }
647
648        let required_len = smoltcp::wire::IPV6_HEADER_LEN + ipv6.payload_len() as usize;
649        if payload.len() < required_len {
650            return Err(DropReason::MalformedPacket);
651        }
652
653        let next_header = ipv6.next_header();
654        let inner = &payload[smoltcp::wire::IPV6_HEADER_LEN..];
655        let addresses = Ipv6Addresses {
656            src_addr: ipv6.src_addr(),
657            dst_addr: ipv6.dst_addr(),
658        };
659
660        match next_header {
661            IpProtocol::Udp => {
662                self.handle_udp(frame, &IpAddresses::V6(addresses), inner, checksum)?
663            }
664            IpProtocol::Tcp => self.handle_tcp(&IpAddresses::V6(addresses), inner, checksum)?,
665            IpProtocol::Icmpv6 => {
666                // Check if this is an NDP packet
667                let icmpv6_packet = Icmpv6Packet::new_unchecked(inner);
668                let msg_type = icmpv6_packet.msg_type();
669
670                if msg_type == smoltcp::wire::Icmpv6Message::NeighborSolicit
671                    || msg_type == smoltcp::wire::Icmpv6Message::NeighborAdvert
672                    || msg_type == smoltcp::wire::Icmpv6Message::RouterSolicit
673                    || msg_type == smoltcp::wire::Icmpv6Message::RouterAdvert
674                {
675                    self.handle_ndp(frame, inner, ipv6.src_addr())?;
676                } else {
677                    return Err(DropReason::UnsupportedIpProtocol(next_header));
678                }
679            }
680
681            p => return Err(DropReason::UnsupportedIpProtocol(p)),
682        };
683        Ok(())
684    }
685
686    /// Updates the DNS nameservers based on the current consomme parameters.
687    pub fn update_dns_nameservers(&mut self) {
688        if self.inner.dns.is_some() {
689            self.inner.state.params.nameservers = self
690                .inner
691                .state
692                .params
693                .internal_nameservers(self.inner.host_has_ipv6);
694        }
695    }
696}