# Consomme

Consomme is a user-mode NAT that provides guest VM networking through
ordinary host sockets — no driver installation and no system-wide
network configuration. Because all traffic flows through standard
socket APIs, host networking policies (firewalls, VPN routing, proxy
settings) apply naturally, just as they would for any other application.

The guest sees a normal network: it gets an IP address via DHCP, has a
default gateway, and can resolve DNS names. Behind the scenes, Consomme
intercepts the raw Ethernet frames from the virtual NIC, parses them,
and translates them into socket calls on the host.

```mermaid
flowchart TB
    subgraph VM ["Guest VM"]
        App["Application"] --> GuestStack["TCP/IP Stack"] --> VNIC["Virtual NIC"]
    end

    subgraph UserMode ["Host User Mode"]
        VNIC -- "Ethernet frames" --> Parse["Parse & Dispatch"]
        Parse --> TCPProxy["TCP proxy"]
        Parse --> UDPProxy["UDP proxy"]
        Parse --> ICMPFwd["ICMP forwarder"]
        Parse --> DNSFwd["DNS forwarder"]
        Parse --> DHCPSrv["DHCP / ARP / NDP"]
        TCPProxy & UDPProxy & ICMPFwd --> Sockets["Host sockets"]
        DNSFwd -- "DNS API call" --> HostDNS["Platform DNS resolver"]
        DHCPSrv -. "replies directly to guest" .-> VNIC
    end

    subgraph Kernel ["Host Kernel"]
        Sockets --> HostStack["Host TCP/IP stack + firewall + VPN"]
        HostDNS --> HostStack
        HostStack --> NIC["Physical NIC"]
    end

    NIC --> Internet((Internet))
```

## Default network topology

| Role | IPv4 Address |
|------|-------------|
| Guest | `10.0.0.2` |
| Gateway | `10.0.0.1` |
| Subnet mask | `255.255.255.0` |

The subnet is configurable via the `--net consomme:<cidr>` CLI option.
The gateway is always `.1` and the guest is always `.2`.

## Port forwarding

By default, Consomme only provides outbound connectivity from the guest.
To accept inbound connections, use the `hostfwd=` option to forward
host ports into the guest:

```bash
--net consomme:hostfwd=tcp::2222-:22
```

This binds host port 2222 and forwards incoming TCP connections to
guest port 22. The general syntax is:

```text
hostfwd=<proto>:[<hostaddr>]:<hostport>-[<guestaddr>]:<guestport>
```

| Field | Description |
|-------|-------------|
| `proto` | `tcp` or `udp` |
| `hostaddr` | Host IP to bind (default: all IPv4 interfaces, `0.0.0.0`). Use `[addr]` brackets for IPv6 addresses |
| `hostport` | Host port to listen on. Use `0` for any available port |
| `guestaddr` | Currently ignored — traffic always forwards to the guest IP |
| `guestport` | Guest port to forward to |

Multiple forwards can be specified with commas:

```bash
--net consomme:hostfwd=tcp::2222-:22,hostfwd=tcp::3389-:3389
```

Port forwards can also be combined with a CIDR:

```bash
--net consomme:10.0.0.0/24,hostfwd=tcp::2222-:22
```

Examples:

```bash
# Forward host port 8080 to guest port 80
--net consomme:hostfwd=tcp::8080-:80

# Bind only to localhost
--net consomme:hostfwd=tcp:127.0.0.1:8080-:80

# IPv6 localhost
--net consomme:hostfwd=tcp:[::1]:8080-:80

# UDP forwarding
--net consomme:hostfwd=udp::5353-:53
```

IPv6 is enabled when the host has a routable IPv6 address. Consomme
advertises a prefix via SLAAC and the guest auto-configures its own
address. IPv6 DNS servers are advertised via RDNSS (in Router
Advertisements) and DHCPv6 Information-Request.

## Protocol handling

### TCP

Consomme maintains a full TCP state machine on the guest-facing side
and opens a real host socket per connection. Data flows through ring
buffers between the two sides. This is a split-TCP design: there are
two independent TCP connections (guest↔Consomme and host↔remote server)
stitched together.

Key implications of the split-TCP design:

- **Congestion control is disconnected from the real network.** The
  guest sees a virtual link with zero packet loss and sub-microsecond
  RTT. Its congestion window opens wide almost immediately. The host
  kernel's TCP stack handles all real congestion and retransmission.
- **Another TCP stack to debug.** When a connection stalls, there are
  three TCP implementations in play: the guest's, Consomme's, and the
  host kernel's. Diagnosing which one is misbehaving requires visibility
  into Consomme's internal state.

Consomme supports TCP Segmentation Offload (TSO) from the guest NIC,
window scaling, and MSS negotiation.

The per-connection ring buffers autotune. Each connection starts with a
small buffer (16 KiB by default) and grows on demand — doubling under
sustained throughput pressure up to a ceiling (4 MiB by default) — so
idle and short-lived connections stay cheap while bulk transfers can
ramp up to a large window. Embedders can override the bounds via the
`tcp_rx_buffer` and `tcp_tx_buffer` fields on `ConsommeParams` (each a
`TcpBufferBounds { initial, max }`); the values are clamped to
`[16 KiB, 4 MiB]` and rounded up to a power of two. Setting
`initial == max` pins a fixed size and disables growth.

Inbound connections require explicit port forwarding — OpenVMM can
programmatically bind host ports and forward them into the guest.

### UDP

Each unique guest source socket (guest IP + source port) gets a
host-side UDP socket bound to an ephemeral port. Datagrams are
forwarded in both directions. Idle bindings are cleaned up after a
configurable timeout (5 minutes by default, per RFC 4787).

UDP packets to the gateway on well-known ports are intercepted:

- **Port 67** — handled by the built-in DHCP server
- **Port 53** — handled by the DNS forwarder

UDP segmentation offload and receive coalescing are not currently
supported. Each datagram requires a separate socket call, which may
matter for QUIC/HTTP/3 performance.

### DNS

The guest is told (via DHCP) to use the gateway as its DNS server.
Queries to the gateway on port 53 are intercepted and forwarded to the
host's DNS resolver. On Windows, this uses the `DnsQueryRaw` API, which
forwards the raw DNS packet without parsing — the host resolver handles
all the logic and returns a raw response. On Unix-like hosts, Consomme
uses the system resolver configuration (for example `/etc/resolv.conf`)
and forwards queries via host UDP/TCP sockets. This means guest DNS
resolution matches the host's behavior, including corporate VPNs and
split-tunnel DNS configurations.

DNS over TCP is also supported for large responses.

### ICMP

Echo requests (ping) are forwarded through a host-side ICMP socket.
The guest's TTL is preserved, so ping works and traceroute partially
works (the echo reply comes back, but intermediate TTL Exceeded hops
are not relayed — see limitations).

### ARP and NDP

- **ARP** — Responds to requests for the gateway MAC address. All other
  ARP traffic is dropped.
- **NDP** — Responds to Router Solicitations (advertising the IPv6
  prefix and DNS servers) and Neighbor Solicitations for the gateway's
  link-local address. DAD is silently ignored.

### DHCP

Built-in DHCPv4 server assigns the guest its IP address, gateway, and
DNS servers. Lease duration is 24 hours. DHCPv6 Information-Request is
also handled (stateless — provides DNS servers to SLAAC-configured
guests).

## Checksum and segmentation offload

Consomme supports the following offloads from the guest NIC:

- IPv4 header checksum offload
- TCP checksum offload
- UDP checksum offload
- TCP Segmentation Offload (TSO)

## Limitations

### No multicast or broadcast

Beyond ARP, DHCP, and the NDP multicast messages required for IPv6,
Consomme only processes unicast traffic. Protocols that rely on
multicast discovery do not work:

- **mDNS / Bonjour** — printer, AirPlay, Chromecast discovery
- **SSDP / UPnP** — device discovery, NAT-PMP port mapping
- **LLMNR** — Windows local name resolution fallback
- **NetBIOS** — legacy Windows browsing

### No ICMP error relay

ICMP error messages (Destination Unreachable, TTL Exceeded, etc.) are
not forwarded to the guest:

- **TCP** — unreachable hosts produce a TCP RST instead of an ICMP
  error. The connection fails, but the guest can't distinguish "host
  unreachable" from "connection refused."
- **UDP** — Port Unreachable errors are silently swallowed.
- **Traceroute** — TTL Exceeded replies from intermediate routers are
  not relayed, so the hop-by-hop path is not visible.

### No IP fragment reassembly

Fragmented IP packets from the guest are silently dropped. This only
affects UDP datagrams larger than ~1472 bytes. TCP is never affected
because the guest kernel segments at the TCP layer. On the host side,
fragmentation is handled transparently by the kernel socket layer.

### No jumbo frames

MTU is fixed at 1500 bytes.

### No ICMPv6 Echo

IPv6 ping does not work. IPv6 TCP and UDP work normally.

### No non-TCP/UDP/ICMP protocols

GRE, ESP, SCTP, and other IP protocols are silently dropped. This
means VPNs that terminate inside the guest and use IPsec (ESP) or
PPTP (GRE) will not work. UDP-based VPNs like WireGuard work fine.
IKEv2 with NAT-T (UDP-encapsulated ESP on port 4500) also works.

VPNs running on the host work transparently — Consomme's traffic flows
through the host network stack like any other application.

### IPv6 depends on host

IPv6 is only enabled if the host has a routable IPv6 address at VM
start time.

## Crate structure

| Crate | Path | Purpose |
|-------|------|---------|
| `consomme` | `vm/devices/net/net_consomme/consomme/` | Core user-mode TCP/IP stack |
| `net_consomme` | `vm/devices/net/net_consomme/` | [`net_backend::Endpoint`](https://openvmm.dev/rustdoc/linux/net_backend/trait.Endpoint.html) integration |
