fix(fc): slot-driven guest IPv6 config; default to IPv4-only to eliminate Happy Eyeballs latency - #3587
fix(fc): slot-driven guest IPv6 config; default to IPv4-only to eliminate Happy Eyeballs latency#3587AdaAibaby wants to merge 4 commits into
Conversation
…ency The tap interface is configured IPv4-only: process.go builds the ip= kernel parameter with NamespaceIP/TapIP/TapMask but no IPv6 router or prefix is advertised on the host side of the tap. With ipv6.disable=0 and ipv6.autoconf=1 the guest kernel enables SLAAC, which at best yields an unroutable fe80:: link-local address and at worst waits for an RA that never arrives. The practical consequence is a ~250 ms Happy Eyeballs penalty (RFC 8305) on every outbound connection to a dual-stack host: the kernel prefers the AAAA address, the attempt fails with EHOSTUNREACH or a silent NDP timeout, then falls back to IPv4. pip install, npm install, and any HTTP API call to a major service (OpenAI, GitHub, PyPI, GCP) all have AAAA records, so every sandbox pays this cost on every first TCP connection per destination. Fix: set ipv6.disable=1 and drop the now-redundant ipv6.autoconf=1. This is a one-line kernel cmdline change with no other code impact. Full IPv6 support can be re-enabled once the host networking layer gains a complete stack (RA, prefix delegation, ip6tables). Fixes: e2b-dev#3585
Previously the guest kernel always booted with ipv6.disable=1 (hardcoded in kernel_args.go). This fix keeps the same safe default while making the flag slot-driven, so the codebase is ready for a future host-side IPv6 stack without a scattered search-and-replace. Changes: - Slot.HasIPv6Router() bool — the single source of truth for whether the host tap has an IPv6 router configured. Returns false today (no RA on the tap); flip to true (or derive from real IPv6 state) when the host gains IPv6. - ProcessOptions.IPv6RouterConfigured bool — carries the slot's answer into buildKernelArgs without coupling kernel_args.go to the network package. Set from Slot.HasIPv6Router() in Process.Create, one line before the call. - buildKernelArgs selects ipv6.disable=0 or =1 based on the flag instead of a string literal. The default (false) preserves the existing behaviour: guests without an IPv6 router on the host tap boot with ipv6.disable=1. - kernel_args_test.go adds an "ipv6 router configured" case that asserts ipv6.disable=0 when IPv6RouterConfigured=true. Fixes e2b-dev#3585.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 926e6ed210
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Tested this on a bare-metal server (Debian 13/trixie, kernel 6.12) while trying to run the Reproduced the bug on Cherry-picked this PR onto the same environment, rebuilt the orchestrator, and confirmed a clean build with no IPv6 attempts – |
Fixes #3585.
Problem
kernel_args.gohardcodedipv6.disable=0andipv6.autoconf=1, enabling IPv6 inside every Firecracker guest. However the host-side network setup only configures IPv4 — no router advertisement is sent on the host tap interface, so SLAAC produces only an unroutablefe80::link-local address.With IPv6 nominally enabled but unroutable, the Linux kernel's address selection (RFC 6724) prefers AAAA records. On every outbound connection to a dual-stack host the kernel tries IPv6 first, hits EHOSTUNREACH or waits for a silent NDP timeout, then falls back to IPv4
under the Happy Eyeballs ~250 ms delay (RFC 8305).
Every sandbox is affected. pip, npm, cargo, apt, and any HTTP API call to a major service (OpenAI, GitHub, PyPI, GCP, AWS) has AAAA records. A package install that opens 20 TCP connections silently loses 5+ seconds.
Fix
Rather than hardcoding
ipv6.disable=1, the guest's IPv6 state is now derived from whether the host slot actually has an IPv6 router configured:Slot.HasIPv6Router() bool— single source of truth. Returnsfalsetoday (no RA on the tap); flip totrue(or read from real IPv6 slot state) when the host gains a full IPv6 stack.ProcessOptions.IPv6RouterConfigured bool— carries the slot's answer intobuildKernelArgswithout coupling the kernel-args layer to the network package. Set fromSlot.HasIPv6Router()inProcess.Create.buildKernelArgs— selectsipv6.disable=0or=1based on the flag. The zero-value default (false) preserves safe behavior: guests without an IPv6 router boot IPv4-only.This means today's sandboxes get
ipv6.disable=1(fixing the Happy Eyeballs regression), and future dual-stack support requires only flippingHasIPv6Router()— no scattered code changes.Changes
network/slot.goHasIPv6Router() bool— documented extension point, returnsfalsefc/process.goProcessOptions.IPv6RouterConfigured; set fromslot.HasIPv6Router()beforebuildKernelArgsfc/kernel_args.goipv6.disablederived fromoptions.IPv6RouterConfiguredinstead of hardcodedfc/kernel_args_test.goipv6.disable=1); addedipv6 router configuredcase (expectipv6.disable=0)docs/ARCHITECTURE.md