Skip to content

Enable IPv6 ULA, SLAAC Router Advertisements, NAT66, and E2E validation - #3104

Open
sferrogoo wants to merge 3 commits into
google:mainfrom
sferrogoo:cuttlefish-ipv6-enablement
Open

Enable IPv6 ULA, SLAAC Router Advertisements, NAT66, and E2E validation#3104
sferrogoo wants to merge 3 commits into
google:mainfrom
sferrogoo:cuttlefish-ipv6-enablement

Conversation

@sferrogoo

Copy link
Copy Markdown
Contributor

Description

This change adds complete IPv6 support for Cuttlefish instances on the host and validates guest dual-stack and pure IPv6-only operation.

  1. IPv6 Unique Local Addresses (ULA):

    • Configures default subnets fd00:cf:22::/64 on cvd-wbr and fd00:cf:24::/64 on cvd-ebr.
    • Adds configuration toggles in /etc/default/cuttlefish-host-resources.
  2. Router Advertisements (SLAAC):

    • Configures dnsmasq to broadcast SLAAC Router Advertisements for autonomous in-guest address assignment.
  3. NAT66 & Forwarding:

    • Implements native nftables masquerading and forwarding rules in cuttlefish-host-resources.init.
    • Adds nftables package dependency to cuttlefish-base in debian/control.
  4. Automated E2E Integration Testing:

    • Adds //cvd/network_tests:network_tests verifying guest SLAAC acquisition, guest-to-host dual-stack communication, and 100% pure IPv6-only functionality after flushing IPv4.

@sferrogoo
sferrogoo force-pushed the cuttlefish-ipv6-enablement branch from eee6c24 to c758369 Compare August 28, 2026 15:43
@sferrogoo

Copy link
Copy Markdown
Contributor Author

@google/android-cuttlefish @adelva1984 @jemoreira PTAL

… and E2E validation

* Add IPv6 Unique Local Address (ULA) subnets fd00:cf:22::/64 (cvd-wbr) and fd00:cf:24::/64 (cvd-ebr) to cuttlefish-host-resources defaults and init script.
* Configure dnsmasq to broadcast IPv6 Router Advertisements for guest SLAAC autoconfiguration.
* Implement native nftables masquerading (NAT66) and forwarding rules in cuttlefish-host-resources.init.
* Add nftables dependency in base/debian/control.
* Add E2E network integration test in //cvd/network_tests:network_tests verifying SLAAC IP acquisition, guest-to-host dual-stack communication, and pure IPv6-only operation.
@sferrogoo
sferrogoo force-pushed the cuttlefish-ipv6-enablement branch from cd09095 to 4f5ccc3 Compare September 1, 2026 00:06
@rmuthiah
rmuthiah requested a review from dxapd September 1, 2026 04:03
@0405ysj 0405ysj added the kokoro:force-run Trigger a presubmit build unconditionally. label Sep 1, 2026
@GoogleCuttlefishTesterBot GoogleCuttlefishTesterBot removed the kokoro:force-run Trigger a presubmit build unconditionally. label Sep 1, 2026
Comment thread e2etests/cvd/network_tests/main_test.go Outdated
t.Fatalf("Timed out waiting for ADB device connection: %v", adbErr)
}

// 1. Verify SLAAC IPv6 assignment on in-guest network interface

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

factorize or split up the test further instead of doing this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Split the test into two separate flows: TestIPv6DualStackConnectivity and TestIPv6OnlyMode. Extracted the address discovery logic into a standalone pollIPv6Address() helper.

Comment thread e2etests/cvd/network_tests/main_test.go Outdated
var ip6Output e2etests.CommandOutput
var activeDev string
var gatewayIP string
for attempt := 1; attempt <= 15; attempt++ {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a retry mechanism to workaround several second delay after interface creation to process the host Router Advertisement and complete Duplicate Address Detection (DAD). I refactored however now to be much cleaner - Replaced the ad-hoc retry loop with a standard deadline-based pollIPv6Address() helper with an explicit 30s timeout.

Comment thread e2etests/cvd/network_tests/main_test.go Outdated
var activeDev string
var gatewayIP string
for attempt := 1; attempt <= 15; attempt++ {
// Check all potential network interfaces (wireless and ethernet bridges)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following block is hard to reason about and this comment doesn't help.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to make it more clear: Logic now is: Depending on the device configuration (e.g. WiFi vs Ethernet bridge), the active interface may be eth1 or wlan0. Rather than iterating hardcoded device names, pollIPv6Address() now runs a single ip -6 -o addr show scope global and dynamically selects whichever interface received the Cuttlefish ULA prefix (fd00:cf:...).

Comment thread e2etests/cvd/network_tests/main_test.go Outdated
// 2. Verify guest-to-host IPv6 connectivity
t.Logf("Verifying dual-stack guest-to-host IPv6 connectivity (gateway %s on %s)...", gatewayIP, activeDev)
// Query detected router link-local address from neighbor table
neighOut, _ := c.RunCmd("adb", "shell", fmt.Sprintf("ip -6 neigh show dev %s", activeDev))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

factorize

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Removed the raw neighbor table query and consolidated route installation into configureGuestIPv6Routes(), targeting the gateway discovered during SLAAC.

Let me know if you prefer more factorizing - like splitting out explicit test methods etc, happy to do so if it helps!

Comment thread e2etests/cvd/network_tests/main_test.go Outdated
}
t.Logf("Guest ULA: %s, Router LL: %s, Gateway ULA: %s", guestIP, routerLL, gatewayIP)

connCmd := fmt.Sprintf("su 0 toybox ping -6 -c 3 -I %s %s || su 0 toybox ping -6 -c 3 -I %s %s || su 0 toybox ping -6 -c 3 %s", guestIP, gatewayIP, activeDev, routerLL, gatewayIP)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

factorize

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned this ip a bit - replaced the chained ping fallback string with pingIPv6Target(), which manages the ping command more cleanly.

Comment thread e2etests/cvd/network_tests/main_test.go Outdated
}
t.Logf("Dual-stack IPv6 connectivity successful:\n%s", connOut.Stdout)

// 3. Uninstall / Flush IPv4 (Simulate IPv6-Only environment)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deserves a different test; you are testing too many variables within the one test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, Split the IPv4 flush and pure IPv6 routing assertions into a distinct TestIPv6OnlyMode test.

Comment thread e2etests/cvd/network_tests/main_test.go Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this test depend on the test host actually having the defaults updated?

How can you run the test on a v4-only host?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right it would fail in ipv4 only. I tweaked a bit - Added checkHostIPv6Support(), which runs before fetching or launching the CVD. It checks whether the host cuttlefish bridge (cvd-ebr or cvd-wbr) has an assigned ULA prefix. If the test host is IPv4-only, it calls t.Skip() so CI runs on older/unsupported runners do not fail.

Comment thread e2etests/cvd/network_tests/main_test.go Outdated
@@ -0,0 +1,159 @@
// Copyright (C) 2026 The Android Open Source Project

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to have been added in another directory "network_tests" at the same level as the "networking_tests" directory. Perhaps this would be better with either:

  • a different directory name, if the goal of these tests is different from the goal of tests in networking_tests/
  • rolled into networking_tests/ as a new test file or a new test case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Removed network_tests/ and rolled the IPv6 tests directly into e2etests/cvd/networking_tests/ipv6_test.go so they share the existing test framework and Bazel targets.

Comment thread base/debian/control Outdated
iptables,
jq,
less,
nftables,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a duplicate entry.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

- Remove duplicate nftables dependency from debian/control.
- Roll IPv6 integration tests into existing networking_tests suite.
- Factorize IPv6 address polling and connectivity assertions with timeouts.
- Separate dual-stack connectivity from IPv6-only flush tests.
- Add test host IPv6 bridge check to skip gracefully on IPv4-only hosts.
@sferrogoo

Copy link
Copy Markdown
Contributor Author

Pushed commit 457a75c addressing all review comments:

  1. Removed duplicate nftables dependency in debian/control.
  2. Moved tests to e2etests/cvd/networking_tests/ipv6_test.go and integrated with Bazel.
  3. Factorized polling and ping logic into reusable helpers with explicit timeouts.
  4. Split into two tests: TestIPv6DualStackConnectivity and TestIPv6OnlyMode.
  5. Added host bridge check to skip gracefully on IPv4-only test hosts.

@sferrogoo

Copy link
Copy Markdown
Contributor Author

@dxapd @3405691582 PTAL, addressed all feedback in commit 457a75c.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants