Skip to main content

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 local_addr_map;
26mod ndp;
27mod tcp;
28mod udp;
29
30mod unix;
31mod windows;
32
33/// Standard DNS port number.
34const DNS_PORT: u16 = 53;
35
36/// Default per-connection TCP ring buffer bounds: start small and autotune up
37/// to a few MiB so idle/short connections stay cheap while bulk flows ramp.
38const DEFAULT_TCP_BUFFER_BOUNDS: TcpBufferBounds = TcpBufferBounds {
39    initial: 16 * 1024,
40    max: 4 * 1024 * 1024,
41};
42
43pub use dns_resolver::StaticDnsRecord;
44pub use dns_resolver::StaticDnsRecordError;
45use inspect::Inspect;
46use inspect::InspectMut;
47use pal_async::driver::Driver;
48use smoltcp::phy::Checksum;
49use smoltcp::phy::ChecksumCapabilities;
50use smoltcp::wire::DhcpMessageType;
51use smoltcp::wire::EthernetAddress;
52use smoltcp::wire::EthernetFrame;
53use smoltcp::wire::EthernetProtocol;
54use smoltcp::wire::EthernetRepr;
55use smoltcp::wire::IPV4_HEADER_LEN;
56use smoltcp::wire::Icmpv6Message;
57use smoltcp::wire::Icmpv6Packet;
58use smoltcp::wire::IpAddress;
59use smoltcp::wire::IpProtocol;
60pub use smoltcp::wire::IpVersion;
61use smoltcp::wire::Ipv4Address;
62use smoltcp::wire::Ipv4Packet;
63use smoltcp::wire::Ipv6Address;
64use smoltcp::wire::Ipv6Packet;
65use std::net::IpAddr;
66use std::net::Ipv4Addr;
67use std::net::SocketAddr;
68use std::net::SocketAddrV4;
69use std::net::SocketAddrV6;
70use std::task::Context;
71use std::time::Duration;
72use thiserror::Error;
73
74#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
75struct FourTuple {
76    src: SocketAddr,
77    dst: SocketAddr,
78}
79
80impl core::fmt::Display for FourTuple {
81    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
82        write!(f, "{}-{}", self.src, self.dst)
83    }
84}
85
86#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
87struct PortForwardKey {
88    family: IpVersion,
89    guest_port: u16,
90}
91
92impl PortForwardKey {
93    fn new(family: IpVersion, guest_port: u16) -> Self {
94        Self { family, guest_port }
95    }
96
97    fn from_socket_addr(addr: SocketAddr, guest_port: u16) -> Self {
98        let family = match addr {
99            SocketAddr::V4(_) => IpVersion::Ipv4,
100            SocketAddr::V6(_) => IpVersion::Ipv6,
101        };
102        Self::new(family, guest_port)
103    }
104}
105
106impl core::fmt::Display for PortForwardKey {
107    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
108        write!(f, "{}:{}", self.family, self.guest_port)
109    }
110}
111
112/// A consomme instance.
113#[derive(InspectMut)]
114pub struct Consomme {
115    state: ConsommeState,
116    #[inspect(mut)]
117    tcp: tcp::Tcp,
118    #[inspect(mut)]
119    udp: udp::Udp,
120    icmp: icmp::Icmp,
121    dns: dns_resolver::DnsResolver,
122    host_has_ipv6: bool,
123}
124
125#[derive(Inspect)]
126struct ConsommeState {
127    params: ConsommeParams,
128    #[inspect(skip)]
129    buffer: Box<[u8]>,
130    local_addr_map: local_addr_map::LocalAddrMap,
131}
132
133/// Dynamic networking properties of a consomme endpoint.
134#[derive(Inspect, Clone)]
135pub struct ConsommeParams {
136    /// Current IPv4 network mask.
137    #[inspect(display)]
138    pub net_mask: Ipv4Address,
139    /// Current Ipv4 gateway address.
140    #[inspect(display)]
141    pub gateway_ip: Ipv4Address,
142    /// Current Ipv4 gateway MAC address.
143    #[inspect(display)]
144    pub gateway_mac: EthernetAddress,
145    /// Current Ipv4 address assigned to endpoint.
146    #[inspect(display)]
147    pub client_ip: Ipv4Address,
148    /// Current client MAC address.
149    #[inspect(display)]
150    pub client_mac: EthernetAddress,
151    /// Current list of DNS resolvers.
152    #[inspect(with = "|x| inspect::iter_by_index(x).map_value(inspect::AsDisplay)")]
153    pub nameservers: Vec<IpAddress>,
154    /// Current IPv6 network mask (if any).
155    pub prefix_len_ipv6: u8,
156    /// If true, advertise an autonomous IPv6 prefix so guests create a
157    /// routable IPv6 address with SLAAC.
158    pub advertise_routable_ipv6: bool,
159    /// Current IPv6 gateway MAC address (if any).
160    #[inspect(display)]
161    pub gateway_mac_ipv6: EthernetAddress,
162    /// Gateway's link-local IPv6 address (derived from gateway_mac_ipv6).
163    ///
164    /// This is the address used as the source for NDP Router Advertisements
165    /// and as the target for Neighbor Solicitations.
166    #[inspect(display)]
167    pub gateway_link_local_ipv6: Ipv6Address,
168    /// Current IPv6 address learned from guest via SLAAC (if any).
169    ///
170    /// With SLAAC (Stateless Address Autoconfiguration), the guest generates
171    /// its own IPv6 address using the advertised prefix and its interface identifier.
172    /// This field is learned from incoming IPv6 traffic from the guest.
173    #[inspect(with = "|x| x.map(inspect::AsDisplay)")]
174    pub client_ip_ipv6: Option<Ipv6Address>,
175    /// Current routable IPv6 address learned from guest via SLAAC (if any).
176    /// The guest will typically have two addresses: a local and a routable one.
177    /// This field is learned from incoming IPv6 traffic from the guest.
178    #[inspect(with = "|x| x.map(inspect::AsDisplay)")]
179    pub client_ip_ipv6_routable: Option<Ipv6Address>,
180    /// Idle timeout for UDP connections.
181    pub udp_timeout: Duration,
182    /// Inactivity timeout for TCP connections waiting for a close handshake
183    /// to make progress, including closes initiated before the initial
184    /// handshake completes. ACKs and newly accepted data restart the timeout.
185    /// The same duration is used as the `TimeWait` interval.
186    pub tcp_close_timeout: Duration,
187    /// If true, skip checks for host IPv6 support and assume the host has a
188    /// routable IPv6 address.
189    pub skip_ipv6_checks: bool,
190    /// If true, allow guest traffic destined for host-local addresses
191    /// (loopback, unspecified, link-local).
192    pub allow_host_local_access: bool,
193    /// Per-connection TCP receive ring buffer bounds (guest-to-host).
194    pub tcp_rx_buffer: TcpBufferBounds,
195    /// Per-connection TCP transmit ring buffer bounds (host-to-guest).
196    pub tcp_tx_buffer: TcpBufferBounds,
197}
198
199/// Bounds for a per-connection TCP ring buffer.
200///
201/// The buffer is allocated at `initial` and may grow up to `max` as demand
202/// warrants. Both values are clamped to `[16 KiB, 4 MiB]` and rounded up to a
203/// power of two, then `initial` is clamped to be no greater than `max`. The
204/// advertised TCP window scale is derived from `max`, so `max` is fixed for
205/// the connection's lifetime.
206///
207/// Note that if the peer does not negotiate window scaling, the effective
208/// receive window (and thus the useful rx capacity) is capped at `u16::MAX`
209/// regardless of `max`.
210#[derive(Inspect, Clone, Copy, Debug, PartialEq, Eq)]
211pub struct TcpBufferBounds {
212    /// Starting capacity, allocated up front.
213    pub initial: usize,
214    /// Ceiling for autotuned growth.
215    pub max: usize,
216}
217
218impl TcpBufferBounds {
219    /// Use the same value for `initial` and `max`. Disables autotune growth.
220    pub const fn fixed(n: usize) -> Self {
221        Self { initial: n, max: n }
222    }
223}
224
225/// An error indicating that the CIDR is invalid.
226#[derive(Debug, Error)]
227#[error("invalid CIDR")]
228pub struct InvalidCidr;
229
230impl ConsommeParams {
231    /// Create default dynamic network state. The default state is
232    ///     IP address: 10.0.0.2 / 24
233    ///     gateway: 10.0.0.1 with MAC address 52-55-10-0-0-1
234    ///     IPv6 address: is not assigned by us, we expect the guest to assign it via SLAAC
235    ///     gateway IPv6 link-local address: fe80::5055:aff:fe00:102 (EUI-64 derived from
236    ///     gateway MAC address 52-55-0A-00-01-02)
237    pub fn new() -> Result<Self, Error> {
238        let nameservers = dns::nameservers()?;
239        let gateway_mac_ipv6 = EthernetAddress([0x52, 0x55, 0x0A, 0x00, 0x01, 0x02]);
240
241        Ok(Self {
242            gateway_ip: Ipv4Address::new(10, 0, 0, 1),
243            gateway_mac: EthernetAddress([0x52, 0x55, 10, 0, 0, 1]),
244            client_ip: Ipv4Address::new(10, 0, 0, 2),
245            client_mac: EthernetAddress([0x0, 0x0, 0x0, 0x0, 0x1, 0x0]),
246            net_mask: Ipv4Address::new(255, 255, 255, 0),
247            nameservers,
248            prefix_len_ipv6: 64,
249            advertise_routable_ipv6: true,
250            gateway_mac_ipv6,
251            gateway_link_local_ipv6: Self::compute_link_local_address(gateway_mac_ipv6),
252            client_ip_ipv6: None,
253            client_ip_ipv6_routable: None,
254            // Per RFC 4787, UDP NAT bindings, by default, should timeout after 5 minutes, but can be configured.
255            udp_timeout: Duration::from_secs(300),
256            // Defaults to 2*MSL per RFC 9293 for the `TimeWait` case.
257            tcp_close_timeout: Duration::from_secs(60),
258            skip_ipv6_checks: false,
259            allow_host_local_access: false,
260            tcp_rx_buffer: DEFAULT_TCP_BUFFER_BOUNDS,
261            tcp_tx_buffer: DEFAULT_TCP_BUFFER_BOUNDS,
262        })
263    }
264
265    /// Sets the cidr for the network.
266    ///
267    /// Setting, for example, 192.168.0.0/24 will set the gateway to
268    /// 192.168.0.1 and the client IP to 192.168.0.2.
269    pub fn set_cidr(&mut self, cidr: &str) -> Result<(), InvalidCidr> {
270        let cidr: smoltcp::wire::Ipv4Cidr = cidr.parse().map_err(|()| InvalidCidr)?;
271        let base_address = cidr.network().address();
272        let mut gateway_octets = base_address.octets();
273        gateway_octets[3] += 1;
274        self.gateway_ip = Ipv4Address::from(gateway_octets);
275        let mut client_octets = base_address.octets();
276        client_octets[3] += 2;
277        self.client_ip = Ipv4Address::from(client_octets);
278        self.net_mask = cidr.netmask();
279        Ok(())
280    }
281
282    /// Compute a link-local IPv6 address from a MAC address using EUI-64 format.
283    ///
284    /// RFC 4291 Section 2.5.6: Link-local addresses are formed by combining
285    /// the link-local prefix (fe80::/64) with an interface identifier derived
286    /// from the MAC address using the EUI-64 format.
287    ///
288    /// EUI-64 format (RFC 2464 Section 4):
289    /// - Insert 0xFFFE in the middle of the 48-bit MAC address
290    /// - Invert the universal/local bit (bit 6 of the first byte)
291    pub fn compute_link_local_address(mac: EthernetAddress) -> Ipv6Address {
292        const LINK_LOCAL_PREFIX: [u8; 8] = [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
293
294        let mut addr = [0u8; 16];
295
296        // Set link-local prefix (fe80::/64)
297        addr[0..8].copy_from_slice(&LINK_LOCAL_PREFIX);
298
299        // Create EUI-64 interface identifier from MAC address
300        // MAC: AB:CD:EF:11:22:33
301        // EUI-64: AB:CD:EF:FF:FE:11:22:33 with universal/local bit flipped
302        addr[8] = mac.0[0] ^ 0x02; // Flip the universal/local bit
303        addr[9] = mac.0[1];
304        addr[10] = mac.0[2];
305        addr[11] = 0xFF;
306        addr[12] = 0xFE;
307        addr[13] = mac.0[3];
308        addr[14] = mac.0[4];
309        addr[15] = mac.0[5];
310
311        Ipv6Address::from_octets(addr)
312    }
313
314    /// Infer the guest's link-local IPv6 address from a learned routable SLAAC address.
315    ///
316    /// If the routable address uses an EUI-64 address derived from the guest MAC, we can infer a
317    /// matching link-local address.
318    ///
319    /// If the routable address uses a different interface identifier (for example,
320    /// privacy or stable-secret addressing), this leaves the link-local address
321    /// unknown and waits to learn it from traffic or NDP instead.
322    pub(crate) fn infer_client_link_local_from_routable(
323        &mut self,
324        routable: Ipv6Address,
325        source: &'static str,
326    ) {
327        // Link local address is already known.
328        if self.client_ip_ipv6.is_some() {
329            return;
330        }
331
332        let link_local = Self::compute_link_local_address(self.client_mac);
333        if routable.octets()[8..] != link_local.octets()[8..] {
334            return;
335        }
336
337        tracing::debug!(
338            client_ipv6 = %link_local,
339            client_ipv6_routable = %routable,
340            source,
341            "inferred client link-local IPv6 address from routable SLAAC address"
342        );
343        self.client_ip_ipv6 = Some(link_local);
344    }
345
346    /// Returns the list of IPv6 nameservers suitable for advertisement to
347    /// guests via NDP RDNSS or DHCPv6.
348    ///
349    /// Filters out addresses that are not useful as DNS servers in a
350    /// guest-facing context: unspecified, loopback, multicast, unique-local
351    /// (fc00::/7), and deprecated site-local (fec0::/10) addresses.
352    pub fn filtered_ipv6_nameservers(&self) -> Vec<Ipv6Address> {
353        self.nameservers
354            .iter()
355            .filter_map(|ip| match ip {
356                IpAddress::Ipv6(addr) => Some(*addr),
357                _ => None,
358            })
359            .filter(|addr| {
360                let octets = addr.octets();
361                !(addr.is_unspecified()
362                    || addr.is_loopback()
363                    || addr.is_multicast()
364                    || matches!(octets[0], 0xfc | 0xfd) // unique local address
365                    || octets.starts_with(&[0xfe, 0xc0])) // deprecated site-local
366            })
367            .collect()
368    }
369
370    /// Returns the default internal nameserver list for use when the DNS
371    /// resolver is active. Includes the IPv6 gateway only when the host
372    /// has a routable IPv6 address.
373    fn internal_nameservers(&self, host_has_ipv6: bool) -> Vec<IpAddress> {
374        let mut ns = vec![self.gateway_ip.into()];
375        if host_has_ipv6 {
376            ns.push(self.gateway_link_local_ipv6.into());
377        }
378        ns
379    }
380
381    fn is_local_address(&self, addr: &SocketAddr) -> bool {
382        match addr {
383            SocketAddr::V4(v4) => v4.ip().is_loopback() || v4.ip() == &self.client_ip,
384            SocketAddr::V6(v6) => {
385                v6.ip().is_loopback()
386                    || self.client_ip_ipv6.is_some_and(|ip| v6.ip() == &ip)
387                    || self
388                        .client_ip_ipv6_routable
389                        .is_some_and(|ip| v6.ip() == &ip)
390            }
391        }
392    }
393}
394
395impl ConsommeState {
396    fn try_ft_from_remote_address(
397        &mut self,
398        remote_addr: &SocketAddr,
399        dst_port: u16,
400    ) -> Option<FourTuple> {
401        Self::translate_remote_address(
402            &self.params,
403            &mut self.local_addr_map,
404            remote_addr,
405            dst_port,
406        )
407    }
408
409    fn translate_remote_address(
410        params: &ConsommeParams,
411        local_addr_map: &mut local_addr_map::LocalAddrMap,
412        remote_addr: &SocketAddr,
413        dst_port: u16,
414    ) -> Option<FourTuple> {
415        // Pick the best destination (guest) address based on the origination of the remote packet.
416        let dst = match remote_addr {
417            SocketAddr::V4(_) => SocketAddr::V4(SocketAddrV4::new(params.client_ip, dst_port)),
418            SocketAddr::V6(v6) => {
419                let client_ipv6 = if !v6.ip().is_unicast_link_local()
420                    && let Some(routable) = params.client_ip_ipv6_routable
421                {
422                    routable
423                } else if let Some(ipv6) = params.client_ip_ipv6 {
424                    ipv6
425                } else if let Some(routable) = params.client_ip_ipv6_routable {
426                    routable
427                } else {
428                    tracelimit::warn_ratelimited!(addr = %remote_addr, "Client IPv6 address is not known, dropping packet");
429                    return None;
430                };
431
432                SocketAddr::V6(SocketAddrV6::new(client_ipv6, dst_port, 0, 0))
433            }
434        };
435
436        // If the remote IP is loopback or matches the client IP address, replace
437        // it with a unique virtual address so that the guest routes the reply
438        // back through the virtual adapter and we can reverse-translate it on the
439        // outgoing path.
440        //
441        // This is skipped for endpoints that are themselves dedicated to
442        // loopback traffic (their `client_ip` is a loopback address, as used by
443        // WSL's VirtioProxy localhost forwarding). The guest routes those
444        // replies back through this adapter based on the loopback source, so
445        // rewriting the source out of the loopback range would break the return
446        // path.
447        let is_loopback_adapter = params.client_ip.is_loopback();
448        let src = match remote_addr {
449            SocketAddr::V4(v4) if params.is_local_address(remote_addr) && !is_loopback_adapter => {
450                let subnet_base =
451                    Ipv4Addr::from(u32::from(params.gateway_ip) & u32::from(params.net_mask));
452                let virtual_ip = local_addr_map.get_or_allocate_v4(
453                    *v4.ip(),
454                    subnet_base,
455                    params.net_mask,
456                    params.gateway_ip,
457                    params.client_ip,
458                );
459                match virtual_ip {
460                    Some(ip) => SocketAddr::V4(SocketAddrV4::new(ip, v4.port())),
461                    None => {
462                        // Pool exhausted, fall back to gateway IP.
463                        SocketAddr::V4(SocketAddrV4::new(params.gateway_ip, v4.port()))
464                    }
465                }
466            }
467            SocketAddr::V6(v6) if params.is_local_address(remote_addr) && !is_loopback_adapter => {
468                let virtual_ip = local_addr_map.get_or_allocate_v6(
469                    *v6.ip(),
470                    params.gateway_link_local_ipv6,
471                    params.client_ip_ipv6,
472                    params.client_ip_ipv6_routable,
473                );
474                match virtual_ip {
475                    Some(ip) => SocketAddr::V6(SocketAddrV6::new(ip, v6.port(), 0, 0)),
476                    None => {
477                        // Pool exhausted, fall back to gateway link-local.
478                        SocketAddr::V6(SocketAddrV6::new(
479                            params.gateway_link_local_ipv6,
480                            v6.port(),
481                            0,
482                            0,
483                        ))
484                    }
485                }
486            }
487            SocketAddr::V6(v6) => {
488                // Remove flow info and scope id from the source address.
489                SocketAddr::V6(SocketAddrV6::new(*v6.ip(), v6.port(), 0, 0))
490            }
491            _ => *remote_addr,
492        };
493
494        Some(FourTuple { src, dst })
495    }
496
497    /// Resolve a destination address that the guest is sending to. If it is a
498    /// virtual mapped address, return the real host address. Otherwise return
499    /// the address unchanged.
500    fn resolve_destination(&self, addr: &SocketAddr) -> SocketAddr {
501        let ip = addr.ip();
502        if let Some(real_ip) = self.local_addr_map.resolve_virtual(&ip) {
503            SocketAddr::new(real_ip, addr.port())
504        } else {
505            *addr
506        }
507    }
508}
509
510/// An accessor for consomme.
511pub struct Access<'a, T> {
512    inner: &'a mut Consomme,
513    client: &'a mut T,
514}
515
516/// A consomme client.
517pub trait Client {
518    /// Gets the driver to use for handling new connections.
519    ///
520    /// TODO: generalize connection creation to allow pluggable model (not just
521    /// OS sockets) and remove this.
522    fn driver(&self) -> &dyn Driver;
523
524    /// Transmits a packet to the client.
525    ///
526    /// If `checksum.ipv4`, `checksum.tcp`, or `checksum.udp` are set, then the
527    /// packet contains an IPv4 header, TCP header, and/or UDP header with a
528    /// valid checksum.
529    ///
530    /// TODO: support >MTU sized packets (RSC/LRO/GRO).
531    fn recv(&mut self, data: &[u8], checksum: &ChecksumState);
532
533    /// Transmits a packet whose bytes are provided as multiple discontiguous
534    /// segments, delivered in order as a single logical packet.
535    ///
536    /// This lets callers avoid linearizing a frame into a scratch buffer when
537    /// its payload is not contiguous (for example, a header segment followed by
538    /// TCP window bytes that wrap a ring buffer). The `checksum` argument has
539    /// the same meaning as for [`Client::recv`].
540    ///
541    /// The default implementation copies the segments into a contiguous buffer
542    /// and forwards to [`Client::recv`]. Clients that can write discontiguous
543    /// data directly to the guest should override this to eliminate the copy.
544    fn recv_segments(&mut self, segments: &[&[u8]], checksum: &ChecksumState) {
545        if let [segment] = segments {
546            self.recv(segment, checksum);
547            return;
548        }
549        let total = segments.iter().map(|s| s.len()).sum();
550        let mut data = Vec::with_capacity(total);
551        for segment in segments {
552            data.extend_from_slice(segment);
553        }
554        self.recv(&data, checksum);
555    }
556
557    /// Specifies the maximum size for the next call to `recv`.
558    ///
559    /// This is the MTU including the Ethernet frame header. This must be at
560    /// least [`MIN_MTU`].
561    ///
562    /// Return 0 to indicate that there are no buffers available for receiving
563    /// data.
564    fn rx_mtu(&mut self) -> usize;
565}
566
567/// Specifies the checksum state for a packet being transmitted.
568#[derive(Debug, Copy, Clone)]
569pub struct ChecksumState {
570    /// On receive, the data has a valid IPv4 header checksum. On send, the
571    /// checksum should be ignored.
572    pub ipv4: bool,
573    /// On receive, the data has a valid TCP checksum. On send, the checksum
574    /// should be ignored.
575    pub tcp: bool,
576    /// On receive, the data has a valid UDP checksum. On send, the checksum
577    /// should be ignored.
578    pub udp: bool,
579    /// The data consists of multiple TCP segments, each with the provided
580    /// segment size.
581    ///
582    /// The IP header's length field may be invalid and should be ignored.
583    pub tso: Option<u16>,
584    /// The data is a large UDP payload that should be sent with OS-level UDP
585    /// GSO, splitting into UDP datagrams of this segment size.
586    pub gso: Option<u16>,
587}
588
589impl ChecksumState {
590    const NONE: Self = Self {
591        ipv4: false,
592        tcp: false,
593        udp: false,
594        tso: None,
595        gso: None,
596    };
597    const IPV4_ONLY: Self = Self {
598        ipv4: true,
599        tcp: false,
600        udp: false,
601        tso: None,
602        gso: None,
603    };
604    const TCP4: Self = Self {
605        ipv4: true,
606        tcp: true,
607        udp: false,
608        tso: None,
609        gso: None,
610    };
611    const UDP4: Self = Self {
612        ipv4: true,
613        tcp: false,
614        udp: true,
615        tso: None,
616        gso: None,
617    };
618    const TCP6: Self = Self {
619        ipv4: false,
620        tcp: true,
621        udp: false,
622        tso: None,
623        gso: None,
624    };
625
626    fn caps(&self) -> ChecksumCapabilities {
627        let mut caps = ChecksumCapabilities::default();
628        if self.ipv4 {
629            caps.ipv4 = Checksum::None;
630        }
631        if self.tcp {
632            caps.tcp = Checksum::None;
633        }
634        if self.udp {
635            caps.udp = Checksum::None;
636        }
637        caps
638    }
639}
640
641/// The minimum MTU for receives supported by Consomme (including the Ethernet
642/// frame).
643pub const MIN_MTU: usize = 1514;
644
645/// The reason a packet was dropped without being handled.
646#[derive(Debug, Error)]
647pub enum DropReason {
648    /// The packet could not be parsed.
649    #[error("packet parsing error")]
650    Packet(#[from] smoltcp::wire::Error),
651    /// The ethertype is unknown.
652    #[error("unsupported ethertype {0}")]
653    UnsupportedEthertype(EthernetProtocol),
654    /// The ethertype is unknown.
655    #[error("unsupported ip protocol {0}")]
656    UnsupportedIpProtocol(IpProtocol),
657    /// The ICMPv6 message type is unsupported.
658    #[error("unsupported icmpv6 message type {0}")]
659    UnsupportedIcmpv6(Icmpv6Message),
660    /// The ARP type is unsupported.
661    #[error("unsupported dhcp message type {0:?}")]
662    UnsupportedDhcp(DhcpMessageType),
663    /// The ARP type is unsupported.
664    #[error("unsupported arp type")]
665    UnsupportedArp,
666    /// The IPv4 checksum was invalid.
667    #[error("ipv4 checksum failure")]
668    Ipv4Checksum,
669    /// The send buffer is invalid.
670    #[error("send buffer full")]
671    SendBufferFull,
672    /// There was an IO error.
673    #[error("io error")]
674    Io(#[source] std::io::Error),
675    /// The TCP state is invalid.
676    #[error("bad tcp state")]
677    BadTcpState(#[from] tcp::TcpError),
678    /// The DHCPv6 message type is unsupported.
679    #[error("unsupported dhcpv6 message type {0:?}")]
680    UnsupportedDhcpv6(dhcpv6::MessageType),
681    /// The NDP message type is unsupported.
682    #[error("unsupported ndp message type {0:?}")]
683    UnsupportedNdp(ndp::NdpMessageType),
684    /// An incoming packet was recognized but was self-contradictory.
685    /// E.g. a TCP packet with both SYN and FIN flags set.
686    #[error("packet is malformed")]
687    MalformedPacket,
688    /// The IP total-length field does not match the buffer size.
689    #[error("ip length mismatch")]
690    IpLengthMismatch,
691    /// An incoming IP packet has been split into several IP fragments and was dropped,
692    /// since IP reassembly is not supported.
693    #[error("packet fragmentation is not supported")]
694    FragmentedPacket,
695    /// The destination address is not allowed (e.g., loopback, unspecified,
696    /// or link-local when host-local access is disabled).
697    #[error("destination address not allowed")]
698    DestinationNotAllowed,
699}
700
701/// An error from a port bind or unbind operation.
702#[derive(Debug, Error)]
703pub enum BindError {
704    /// The specified port is already bound.
705    #[error("port {0} is already bound")]
706    PortAlreadyBound(u16),
707    /// The specified port is not bound.
708    #[error("port is not bound")]
709    PortNotBound,
710    /// An IO error occurred during binding.
711    #[error(transparent)]
712    Io(std::io::Error),
713}
714
715/// An error to create a consomme instance.
716#[derive(Debug, Error)]
717pub enum Error {
718    /// Could not get DNS nameserver information.
719    #[error("failed to initialize nameservers")]
720    Dns(#[from] dns::Error),
721}
722
723#[derive(Debug)]
724struct Ipv4Addresses {
725    src_addr: Ipv4Address,
726    dst_addr: Ipv4Address,
727}
728
729#[derive(Debug)]
730struct Ipv6Addresses {
731    src_addr: Ipv6Address,
732    dst_addr: Ipv6Address,
733}
734
735#[derive(Debug)]
736enum IpAddresses {
737    V4(Ipv4Addresses),
738    V6(Ipv6Addresses),
739}
740
741impl IpAddresses {
742    fn src_addr(&self) -> IpAddress {
743        match self {
744            IpAddresses::V4(addrs) => IpAddress::Ipv4(addrs.src_addr),
745            IpAddresses::V6(addrs) => IpAddress::Ipv6(addrs.src_addr),
746        }
747    }
748
749    fn dst_addr(&self) -> IpAddress {
750        match self {
751            IpAddresses::V4(addrs) => IpAddress::Ipv4(addrs.dst_addr),
752            IpAddresses::V6(addrs) => IpAddress::Ipv6(addrs.dst_addr),
753        }
754    }
755}
756
757/// Returns `true` if the given IPv4 destination is host-local
758/// (loopback, unspecified, or link-local) and should be blocked
759/// when `allow_host_local_access` is disabled.
760fn is_blocked_host_local_ipv4(addr: Ipv4Address) -> bool {
761    addr.is_loopback() || addr.is_unspecified() || addr.is_link_local()
762}
763
764/// Returns `true` if the given IPv6 destination is host-local
765/// (loopback, unspecified, or link-local) and should be blocked
766/// when `allow_host_local_access` is disabled.
767fn is_blocked_host_local_ipv6(addr: Ipv6Address) -> bool {
768    addr.is_loopback() || addr.is_unspecified() || addr.is_unicast_link_local()
769}
770
771/// Returns `true` if two IPv4 addresses are in the same subnet given a mask.
772pub(crate) fn is_same_ipv4_subnet(
773    addr1: Ipv4Address,
774    addr2: Ipv4Address,
775    subnet_mask: Ipv4Address,
776) -> bool {
777    let subnet_mask = subnet_mask.to_bits();
778    (addr1.to_bits() & subnet_mask) == (addr2.to_bits() & subnet_mask)
779}
780
781/// Returns `true` if two IPv6 addresses share the same prefix of the given length.
782pub(crate) fn is_same_ipv6_subnet(addr1: Ipv6Address, addr2: Ipv6Address, prefix_len: u8) -> bool {
783    if prefix_len == 0 {
784        return true;
785    }
786    if prefix_len >= 128 {
787        return addr1 == addr2;
788    }
789    let mask = u128::MAX << (128 - prefix_len);
790    (addr1.to_bits() & mask) == (addr2.to_bits() & mask)
791}
792
793/// Returns `true` if the given IPv6 address is a globally routable unicast
794/// address (i.e., not loopback, unspecified, or link-local).
795fn is_routable_ipv6(addr: &std::net::Ipv6Addr) -> bool {
796    !addr.is_loopback() && !addr.is_unspecified() && !addr.is_unicast_link_local()
797}
798
799impl Consomme {
800    /// Creates a new consomme instance with specified state.
801    pub fn new(mut params: ConsommeParams) -> Self {
802        let host_has_ipv6 = if params.skip_ipv6_checks {
803            true
804        } else {
805            #[cfg(windows)]
806            let host_has_ipv6_result = windows::host_has_ipv6_address().map_err(|e| e.to_string());
807            #[cfg(unix)]
808            let host_has_ipv6_result = unix::host_has_ipv6_address().map_err(|e| e.to_string());
809
810            match host_has_ipv6_result {
811                Ok(has_ipv6) => has_ipv6,
812                Err(e) => {
813                    tracelimit::warn_ratelimited!(
814                        "failed to check for host IPv6 address, assuming no IPv6 support: {e}"
815                    );
816                    false
817                }
818            }
819        };
820        let dns =
821            match dns_resolver::DnsResolver::new(dns_resolver::DEFAULT_MAX_PENDING_DNS_REQUESTS) {
822                Ok(dns) => {
823                    // When the DNS resolver is available, use the default internal nameserver.
824                    params.nameservers = params.internal_nameservers(host_has_ipv6);
825                    dns
826                }
827                Err(_) => {
828                    tracelimit::warn_ratelimited!(
829                        "failed to initialize DNS resolver, falling back to using host DNS settings"
830                    );
831                    dns_resolver::DnsResolver::without_backend(
832                        dns_resolver::DEFAULT_MAX_PENDING_DNS_REQUESTS,
833                    )
834                }
835            };
836        let tcp_rx_buffer = params.tcp_rx_buffer;
837        let tcp_tx_buffer = params.tcp_tx_buffer;
838        let udp_timeout = params.udp_timeout;
839        Self {
840            state: ConsommeState {
841                params,
842                buffer: Box::new([0; 65536]),
843                local_addr_map: local_addr_map::LocalAddrMap::new(),
844            },
845            tcp: tcp::Tcp::new(tcp_rx_buffer, tcp_tx_buffer),
846            udp: udp::Udp::new(udp_timeout),
847            icmp: icmp::Icmp::new(),
848            dns,
849            host_has_ipv6,
850        }
851    }
852
853    /// Get access to the parameters to be updated.
854    ///
855    /// FUTURE: add support for updating only the parameters that can be safely
856    /// changed at runtime.
857    pub fn params_mut(&mut self) -> &mut ConsommeParams {
858        &mut self.state.params
859    }
860
861    /// Clears the local address mapping table. Call this after changing the
862    /// network configuration (e.g., via [`ConsommeParams::set_cidr`]) to avoid
863    /// stale or conflicting virtual address mappings.
864    ///
865    /// Some in-flight packets may be lost during the transition; this is
866    /// acceptable.
867    pub fn clear_local_addr_map(&mut self) {
868        self.state.local_addr_map.clear();
869    }
870
871    /// Adds a static DNS record that will be returned directly
872    /// if the guest sends a matching query.
873    pub fn add_dns_record(
874        &mut self,
875        record: StaticDnsRecord,
876        name: &str,
877    ) -> Result<(), StaticDnsRecordError> {
878        self.dns.add_static_record(record, name)
879    }
880
881    /// Allocates a virtual address within this endpoint's subnet and routes
882    /// guest traffic sent to it to `destination` on the host.
883    /// Returns `None` if the subnet's virtual address pool is exhausted.
884    pub fn create_virtual_address(&mut self, destination: IpAddr) -> Option<IpAddr> {
885        match destination {
886            IpAddr::V4(destination) => {
887                let net_mask = self.state.params.net_mask;
888                let gateway_ip = self.state.params.gateway_ip;
889                let client_ip = self.state.params.client_ip;
890                let subnet_base = Ipv4Addr::from(u32::from(gateway_ip) & u32::from(net_mask));
891                self.state
892                    .local_addr_map
893                    .get_or_allocate_v4(destination, subnet_base, net_mask, gateway_ip, client_ip)
894                    .map(IpAddr::V4)
895            }
896            IpAddr::V6(destination) => {
897                let gateway_ll = self.state.params.gateway_link_local_ipv6;
898                let client_ll = self.state.params.client_ip_ipv6;
899                let client_routable = self.state.params.client_ip_ipv6_routable;
900                self.state
901                    .local_addr_map
902                    .get_or_allocate_v6(destination, gateway_ll, client_ll, client_routable)
903                    .map(IpAddr::V6)
904            }
905        }
906    }
907
908    /// Pairs the client with this instance to operate on the consomme instance.
909    pub fn access<'a, T: Client>(&'a mut self, client: &'a mut T) -> Access<'a, T> {
910        Access {
911            inner: self,
912            client,
913        }
914    }
915}
916
917impl<T: Client> Access<'_, T> {
918    /// Gets the inner consomme object.
919    pub fn get(&self) -> &Consomme {
920        self.inner
921    }
922
923    /// Gets the inner consomme object.
924    pub fn get_mut(&mut self) -> &mut Consomme {
925        self.inner
926    }
927
928    /// Polls for work, transmitting any ready packets to the client.
929    pub fn poll(&mut self, cx: &mut Context<'_>) {
930        self.poll_udp(cx);
931        self.poll_tcp(cx);
932        self.poll_icmp(cx);
933    }
934
935    /// Update all sockets to use the new client's IO driver. This must be
936    /// called if the previous driver is no longer usable or if the client
937    /// otherwise wants existing connections to be polled on a new IO driver.
938    pub fn refresh_driver(&mut self) {
939        self.refresh_tcp_driver();
940        self.refresh_udp_driver();
941    }
942
943    /// Sends an Ethernet frame to the network.
944    ///
945    /// If `checksum.ipv4`, `checksum.tcp`, or `checksum.udp` are set, then
946    /// skips validating the IPv4, TCP, and UDP checksums. Otherwise, these
947    /// checksums are validated as normal and packets with invalid checksums are
948    /// dropped.
949    ///
950    /// If `checksum.tso.is_some()`, then perform TCP segmentation offset on the
951    /// frame. Practically speaking, this means that the frame contains a TCP
952    /// packet with these caveats:
953    ///
954    ///   * The IP header length may be invalid and will be ignored. The TCP
955    ///     packet payload is assumed to end at the end of `data`.
956    ///   * The TCP segment's payload size may be larger than the advertized TCP
957    ///     MSS value.
958    ///
959    /// This allows for sending TCP data that is much larger than the MSS size
960    /// via a single call.
961    ///
962    /// TODO:
963    ///
964    ///   1. allow for discontiguous packets
965    ///   2. allow for packets in guest memory (including lifetime model, if
966    ///      necessary--currently TCP transmits only happen in `poll`, but this
967    ///      may not be necessary. If the underlying socket implementation
968    ///      performs a copy (as the standard kernel socket APIs do), then no
969    ///      lifetime model is necessary, but if an implementation wants
970    ///      zerocopy support then some mechanism to allow the guest memory to
971    ///      be released later will be necessary.
972    pub fn send(&mut self, data: &[u8], checksum: &ChecksumState) -> Result<(), DropReason> {
973        let frame_packet = EthernetFrame::new_unchecked(data);
974        let frame = EthernetRepr::parse(&frame_packet)?;
975        match frame.ethertype {
976            EthernetProtocol::Ipv4 => self.handle_ipv4(&frame, frame_packet.payload(), checksum)?,
977            EthernetProtocol::Ipv6 => {
978                if self.inner.host_has_ipv6 {
979                    self.handle_ipv6(&frame, frame_packet.payload(), checksum)?
980                }
981            }
982            EthernetProtocol::Arp => self.handle_arp(&frame, frame_packet.payload())?,
983            _ => return Err(DropReason::UnsupportedEthertype(frame.ethertype)),
984        }
985        Ok(())
986    }
987
988    fn handle_ipv4(
989        &mut self,
990        frame: &EthernetRepr,
991        payload: &[u8],
992        checksum: &ChecksumState,
993    ) -> Result<(), DropReason> {
994        let ipv4 = Ipv4Packet::new_unchecked(payload);
995        if payload.len() < IPV4_HEADER_LEN
996            || ipv4.version() != 4
997            || payload.len() < ipv4.header_len().into()
998        {
999            return Err(DropReason::MalformedPacket);
1000        }
1001
1002        // For segmentation offload (TSO/USO) the IP total_length field may
1003        // not reflect the actual buffer size (it can hold only one segment's
1004        // worth or wrap for payloads > 64 KiB). Use the real buffer length
1005        // instead.
1006        let segmentation_offload = checksum.tso.is_some() || checksum.gso.is_some();
1007        if !segmentation_offload && payload.len() < ipv4.total_len().into() {
1008            return Err(DropReason::IpLengthMismatch);
1009        }
1010
1011        let total_len = if segmentation_offload {
1012            payload.len()
1013        } else {
1014            ipv4.total_len().into()
1015        };
1016        if total_len < ipv4.header_len().into() {
1017            return Err(DropReason::MalformedPacket);
1018        }
1019
1020        if ipv4.more_frags() || ipv4.frag_offset() != 0 {
1021            return Err(DropReason::FragmentedPacket);
1022        }
1023
1024        if !checksum.ipv4 && !ipv4.verify_checksum() {
1025            return Err(DropReason::Ipv4Checksum);
1026        }
1027
1028        // Reject guest traffic to host-local-only destinations.
1029        if !self.inner.state.params.allow_host_local_access
1030            && is_blocked_host_local_ipv4(ipv4.dst_addr())
1031        {
1032            return Err(DropReason::DestinationNotAllowed);
1033        }
1034
1035        let addresses = Ipv4Addresses {
1036            src_addr: ipv4.src_addr(),
1037            dst_addr: ipv4.dst_addr(),
1038        };
1039
1040        let inner = &payload[ipv4.header_len().into()..total_len];
1041
1042        match ipv4.next_header() {
1043            IpProtocol::Tcp => self.handle_tcp(&IpAddresses::V4(addresses), inner, checksum)?,
1044            IpProtocol::Udp => {
1045                self.handle_udp(frame, &IpAddresses::V4(addresses), inner, checksum)?
1046            }
1047            IpProtocol::Icmp => {
1048                self.handle_icmp(frame, &addresses, inner, checksum, ipv4.hop_limit())?
1049            }
1050            p => return Err(DropReason::UnsupportedIpProtocol(p)),
1051        };
1052        Ok(())
1053    }
1054
1055    fn handle_ipv6(
1056        &mut self,
1057        frame: &EthernetRepr,
1058        payload: &[u8],
1059        checksum: &ChecksumState,
1060    ) -> Result<(), DropReason> {
1061        let ipv6 = Ipv6Packet::new_unchecked(payload);
1062        if payload.len() < smoltcp::wire::IPV6_HEADER_LEN || ipv6.version() != 6 {
1063            return Err(DropReason::MalformedPacket);
1064        }
1065
1066        // For segmentation offload (TSO/USO) the IPv6 payload_length field
1067        // may not reflect the actual buffer size. Skip the length validation
1068        // and use the full buffer.
1069        let segmentation_offload = checksum.tso.is_some() || checksum.gso.is_some();
1070        if !segmentation_offload {
1071            let required_len = smoltcp::wire::IPV6_HEADER_LEN + ipv6.payload_len() as usize;
1072            if payload.len() < required_len {
1073                return Err(DropReason::MalformedPacket);
1074            }
1075        }
1076
1077        // Reject guest traffic to host-local-only destinations.
1078        if !self.inner.state.params.allow_host_local_access
1079            && is_blocked_host_local_ipv6(ipv6.dst_addr())
1080        {
1081            return Err(DropReason::DestinationNotAllowed);
1082        }
1083
1084        let next_header = ipv6.next_header();
1085        let src_addr = ipv6.src_addr();
1086        let inner = &payload[smoltcp::wire::IPV6_HEADER_LEN..];
1087        let addresses = Ipv6Addresses {
1088            src_addr,
1089            dst_addr: ipv6.dst_addr(),
1090        };
1091
1092        // Learn the client's link-local IPv6 address from outgoing traffic.
1093        // This covers clients that do not perform DAD before using the address.
1094        if src_addr.is_unicast_link_local()
1095            && self.inner.state.params.client_ip_ipv6 != Some(src_addr)
1096        {
1097            tracing::debug!(
1098                client_ipv6 = %src_addr,
1099                "learned client link-local IPv6 address from outgoing traffic"
1100            );
1101            self.inner.state.params.client_ip_ipv6 = Some(src_addr);
1102        }
1103
1104        // Learn the client's routable IPv6 address from outgoing traffic.
1105        // This is more reliable than relying solely on DAD Neighbor
1106        // Solicitations, which some clients skip on private virtual links.
1107        if !src_addr.is_unspecified()
1108            && !src_addr.is_multicast()
1109            && !src_addr.is_unicast_link_local()
1110            && self.inner.state.params.client_ip_ipv6_routable != Some(src_addr)
1111        {
1112            tracing::debug!(
1113                client_ipv6_routable = %src_addr,
1114                "learned client routable IPv6 address from outgoing traffic"
1115            );
1116            self.inner.state.params.client_ip_ipv6_routable = Some(src_addr);
1117            self.inner
1118                .state
1119                .params
1120                .infer_client_link_local_from_routable(src_addr, "outgoing traffic");
1121        }
1122
1123        match next_header {
1124            IpProtocol::Udp => {
1125                self.handle_udp(frame, &IpAddresses::V6(addresses), inner, checksum)?
1126            }
1127            IpProtocol::Tcp => self.handle_tcp(&IpAddresses::V6(addresses), inner, checksum)?,
1128            IpProtocol::Icmpv6 => {
1129                // Check if this is an NDP packet
1130                let icmpv6_packet = Icmpv6Packet::new_unchecked(inner);
1131                let msg_type = icmpv6_packet.msg_type();
1132
1133                if msg_type.is_ndisc() {
1134                    self.handle_ndp(frame, inner, ipv6.src_addr())?;
1135                } else {
1136                    tracing::trace!(
1137                        icmpv6_type = %msg_type,
1138                        src_addr = %src_addr,
1139                        dst_addr = %addresses.dst_addr,
1140                        "unsupported ICMPv6 message"
1141                    );
1142                    return Err(DropReason::UnsupportedIcmpv6(msg_type));
1143                }
1144            }
1145
1146            p => return Err(DropReason::UnsupportedIpProtocol(p)),
1147        };
1148        Ok(())
1149    }
1150
1151    /// Updates the DNS nameservers based on the current consomme parameters.
1152    pub fn update_dns_nameservers(&mut self) {
1153        if self.inner.dns.is_available() {
1154            self.inner.state.params.nameservers = self
1155                .inner
1156                .state
1157                .params
1158                .internal_nameservers(self.inner.host_has_ipv6);
1159        }
1160    }
1161}
1162
1163#[cfg(test)]
1164mod tests;