Skip to content

feat(system-tests): support API boundary node playnets on the local backend - #11208

Merged
basvandijk merged 6 commits into
masterfrom
bas/local-backend-api-bn-playnet
Aug 21, 2026
Merged

feat(system-tests): support API boundary node playnets on the local backend#11208
basvandijk merged 6 commits into
masterfrom
bas/local-backend-api-bn-playnet

Conversation

@basvandijk

@basvandijk basvandijk commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

with_api_boundary_nodes_playnet gives API boundary nodes a domain name and a certificate for it that the nodes trust. Both halves came from Farm, so every system-test using it — and therefore every system-test with a cloud engine subnet, which requires it — was pinned to backend = "farm".

This stands up an equivalent locally.

DNS in the local backend

The group's dnsmasq becomes its DNS server. It already ran on the group bridge as an RA/DHCPv4 daemon with --port=0; dropping that and adding --no-resolv --no-hosts makes it a hermetic resolver — with no upstream left to forward to, anything it cannot answer is REFUSED. It answers from two sources: an --addn-hosts file, written by the new LocalBackend::add_dns_record and re-read on SIGHUP, and a --synth-domain that mirrors the public nip.io wildcard service (so canister_http_socks_test needs no DNS-specific code and stays identical on both backends).

GuestOS has no name-server knob and boots with IPv6AcceptRA=no, so rather than reconfiguring the guests, create_group assigns the four addresses GuestOS is hard-coded to query to the bridge. Inside the backend's own network namespace those addresses are free and no query can escape, so every node gets a working resolver without touching IC-OS. Those addresses are now a single IPV6_NAME_SERVERS constant, kept in sync with the networkd contents by a unit test.

A local playnet

InternetComputer::setup_api_bn_local_playnet issues an ephemeral CA plus a leaf covering the API boundary nodes' domains, registers those domains with the group's dnsmasq, and stores the material in a new LocalApiBoundaryNodesPlaynet attribute — deliberately separate from Playnet, which the IC gateway VM also reads and writes. bootstrap serves the leaf from ic-boundary through the existing ic_boundary_tls_cert mechanism, so that pipeline is unchanged.

The one production change

nns_delegation_manager built its root store from the compiled-in webpki_roots only, with no config, env var, feature or cfg hook — no test-issued certificate could ever satisfy it. It now also honours extra_api_boundary_node_trust_anchors_pem, a new dev-only GuestOSDevSettings field (config version 1.15.0 → 1.16.0). Production nodes get None and keep the public roots as their only trust anchors.

This is the part that needs a careful look from the delegation-manager owners.

Tests enabled

backend = "farm" dropped from canister_http_socks_test, cloud_engine_canister_sig_test, xnet_cloud_engine_isolation_test, nns_delegation_branch_nns_version_test and delete_subnet_test. The cpus of canister_http_socks_test was understated — its comment omitted the four cloud engine nodes.

Two tests stay on Farm for unrelated reasons, now recorded accurately in place of the old comment:

  • nns_delegation_mainnet_nns_version_test boots the mainnet GuestOS, whose replica predates the new field. It can be enabled once that has rolled out.
  • firewall_correctness_test asserts that port 8080 is closed between particular pairs of nodes, but on the local backend ic-prep always adds 8080 to the fd00::/8 rule the driver needs to reach the nodes at all — and the nodes themselves live in fd00::/8. Enabling it means narrowing that rule to the driver's own addresses, which changes shared local-backend behaviour for every local test and is left as a follow-up.

Verification

Run on the local backend, all passing: cloud_engine_canister_sig_test_local, xnet_cloud_engine_isolation_test_local, nns_delegation_branch_nns_version_test_local, delete_subnet_test_local. Their logs show a cloud engine node resolving apibn-0.ic.net through the group's dnsmasq, completing a TLS handshake against the ephemeral CA, and fetching its NNS delegation.

canister_http_socks_test_local is enabled but was not run: at 11 node VMs plus a UVM it needs ~48 GiB, more than the machine I verified on had.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR enables API boundary node “playnet” functionality (domain + trusted TLS) on the local system-test backend by adding a hermetic in-group DNS resolver and issuing an ephemeral CA/cert for API BNs, allowing several previously Farm-only tests (notably those requiring cloud-engine subnets) to run locally. It also introduces a production-facing change to allow nns_delegation_manager to optionally trust additional API BN roots via config (intended for system tests).

Changes:

  • Add local-backend DNS + per-group API BN domain/TLS issuance to support API BN playnets without Farm.
  • Plumb a new dev-only GuestOS setting/config field for extra API BN trust anchors, and use it in nns_delegation_manager.
  • Enable multiple system tests on the local backend and update firewall test logic to account for pre-seeded global firewall rules.

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
rs/tests/nns/BUILD.bazel Enables delete_subnet_test off Farm by removing the Farm-only backend constraint.
rs/tests/networking/firewall/firewall_correctness_test.rs Reads existing global firewall rules before proposing updates (local backend isn’t empty).
rs/tests/networking/firewall/BUILD.bazel Keeps firewall correctness test on Farm, updating rationale for why local backend differs.
rs/tests/networking/canister_http_socks_test.rs Clarifies why a hostname (nip.io-style) is required for SOCKS outcalls.
rs/tests/networking/BUILD.bazel Enables canister_http_socks_test locally and corrects CPU sizing/commentary; documents remaining Farm-only test.
rs/tests/message_routing/xnet/BUILD.bazel Enables xnet_cloud_engine_isolation_test off Farm by removing backend pin.
rs/tests/driver/src/util.rs Adds template variable for extra_api_boundary_node_trust_anchors_pem.
rs/tests/driver/src/driver/test_env_api.rs Adds TopologySnapshot::firewall_rules helper for reading registry firewall state.
rs/tests/driver/src/driver/local_backend.rs Expands dnsmasq role to RA/DHCPv4/DNS; adds local DNS record registration and nip.io synthesis.
rs/tests/driver/src/driver/ic.rs Implements local playnet setup: assign local domains, issue ephemeral CA/leaf, register DNS records.
rs/tests/driver/src/driver/bootstrap.rs Plumbs local playnet TLS material + extra trust anchors into GuestOS config generation.
rs/tests/driver/Cargo.toml Adds dependency on network crate to share GuestOS DNS server constants.
rs/tests/driver/BUILD.bazel Adds Bazel dep on //rs/ic_os/networking/network.
rs/tests/crypto/BUILD.bazel Enables cloud_engine_canister_sig_test off Farm by removing backend pin.
rs/orchestrator/src/firewall.rs Updates config rendering template inputs for the new trust-anchor field in tests.
rs/ic_os/networking/network/src/systemd.rs Introduces IPV6_NAME_SERVERS constant + unit test to keep it in sync with networkd contents.
rs/ic_os/networking/network/BUILD.bazel Adjusts crate visibility to allow system tests to depend on network.
rs/ic_os/config/types/src/lib.rs Bumps config version and adds dev-only extra_api_boundary_node_trust_anchors_pem field.
rs/ic_os/config/types/compatibility_tests/fixtures/hostos_v1.16.0.json Adds v1.16.0 fixture covering the new dev setting.
rs/ic_os/config/types/compatibility_tests/fixtures/guestos_v1.16.0.json Adds v1.16.0 fixture covering the new dev setting.
rs/ic_os/config/tool/templates/ic.json5.template Adds new http_handler config field to rendered IC config.
rs/ic_os/config/tool/src/guestos/generate_ic_config.rs JSON-encodes PEM into template var and wires it into config rendering.
rs/http_endpoints/nns_delegation_manager/src/nns_delegation_manager.rs Extends trust store with optional extra roots from config when contacting API BNs.
rs/config/src/http_handler.rs Adds new optional config field with default None.
Cargo.lock Records the new network dependency in the lockfile.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread rs/ic_os/config/types/compatibility_tests/fixtures/hostos_v1.16.0.json Outdated
Comment thread rs/ic_os/config/types/compatibility_tests/fixtures/guestos_v1.16.0.json Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 25 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (1)

rs/tests/networking/canister_http_socks_test.rs:162

  • This cross-reference still uses the old method name, but this PR renames it to LocalBackend::start_dnsmasq; as written, readers cannot find the referenced implementation.
    // `LocalBackend::start_ra_daemon`), so no external DNS is involved there.

@basvandijk

Copy link
Copy Markdown
Collaborator Author

Picking up the suppressed comment from the last Copilot review (canister_http_socks_test.rs:162): correct, that cross-reference pointed at LocalBackend::start_ra_daemon, which this PR renames to start_dnsmasq.

Fixed in 6c287ca, along with three comments in local_backend.rs that still called it "the RA daemon" — it now serves RA, DHCPv4 and DNS, which is the whole reason for the rename. No remaining references to the old names.

@basvandijk
basvandijk marked this pull request as ready for review August 19, 2026 00:11
@basvandijk
basvandijk requested review from a team as code owners August 19, 2026 00:11

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This pull request changes code owned by the Governance team. Therefore, make sure that
you have considered the following (for Governance-owned code):

  1. Update unreleased_changelog.md (if there are behavior changes, even if they are
    non-breaking).

  2. Are there BREAKING changes?

  3. Is a data migration needed?

  4. Security review?

How to Satisfy This Automatic Review

  1. Go to the bottom of the pull request page.

  2. Look for where it says this bot is requesting changes.

  3. Click the three dots to the right.

  4. Select "Dismiss review".

  5. In the text entry box, respond to each of the numbered items in the previous
    section, declare one of the following:

  • Done.

  • $REASON_WHY_NO_NEED. E.g. for unreleased_changelog.md, "No
    canister behavior changes.", or for item 2, "Existing APIs
    behave as before.".

Brief Guide to "Externally Visible" Changes

"Externally visible behavior change" is very often due to some NEW canister API.

Changes to EXISTING APIs are more likely to be "breaking".

If these changes are breaking, make sure that clients know how to migrate, how to
maintain their continuity of operations.

If your changes are behind a feature flag, then, do NOT add entrie(s) to
unreleased_changelog.md in this PR! But rather, add entrie(s) later, in the PR
that enables these changes in production.

Reference(s)

For a more comprehensive checklist, see here.

GOVERNANCE_CHECKLIST_REMINDER_DEDUP

@zeropath-ai

zeropath-ai Bot commented Aug 19, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 37395b0.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► rs/config/src/http_handler.rs
    Add extra_api_boundary_node_trust_anchors_pem field to Config and default initialization
► rs/http_endpoints/nns_delegation_manager/src/nns_delegation_manager.rs
    Introduce api_boundary_node_root_store function to incorporate extra trust anchors; pass config to connect; use extra anchors when available
► rs/ic_os/config/tool/src/guestos/generate_ic_config.rs
    Encode extra_api_boundary_node_trust_anchors_pem as JSON in IcConfigTemplate
► rs/ic_os/config/tool/templates/ic.json5.template
    Include extra_api_boundary_node_trust_anchors_pem in http_handler config template
Enhancement ► rs/ic_os/config/types/compatibility_tests/fixtures/guestos_v1.16.0.json
    Add extra_api_boundary_node_trust_anchors_pem field placeholder (null)
Enhancement ► rs/ic_os/config/types/compatibility_tests/fixtures/hostos_v1.16.0.json
    Add extra_api_boundary_node_trust_anchors_pem field placeholder (null)
Enhancement ► rs/ic_os/config/types/src/lib.rs
    Bump CONFIG_VERSION to 1.16.0 and add extra_api_boundary_node_trust_anchors_pem field documentation/serde default for GuestOSDevSettings
Enhancement ► rs/ic_os/networking/network/BUILD.bazel
    Expose network crate to additional targets (ic-os-pkg, system-tests-pkg)
Enhancement ► rs/ic_os/networking/network/src/systemd.rs
    Define IPV6_NAME_SERVERS constant and related tests; keep contents aligned with networkd contents
Enhancement ► rs/orchestrator/src/firewall.rs
    Update test fixture to include extra_api_boundary_node_trust_anchors_pem field
Enhancement ► rs/tests/driver/src/driver/bootstrap.rs
    Adapt API BN TLS/trust anchor handling when building config; pass api_bn_trust_anchors_pem through to config creation
Enhancement ► rs/tests/driver/src/driver/ic.rs
    Import LocalApiBoundaryNodesPlaynet type and adjust related code paths
Enhancement ► rs/tests/driver/src/driver/local_backend.rs
    Introduce NIC name server DNS setup and IPV6_NAME_SERVERS usage; incorporate DNS name server handling into group creation and dnsmasq setup
Enhancement ► rs/tests/driver/Cargo.toml
    Add dependency on network crate for tests
Enhancement ► rs/tests/driver/src/driver/ic.rs
    Refactor to accommodate LocalApiBoundaryNodesPlaynet and related attributes

@zeropath-ai

zeropath-ai Bot commented Aug 19, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 37395b0.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► rs/config/src/http_handler.rs
    Add extra_api_boundary_node_trust_anchors_pem field to Config and default initialization
► rs/http_endpoints/nns_delegation_manager/src/nns_delegation_manager.rs
    Introduce api_boundary_node_root_store function to incorporate extra trust anchors; pass config to connect; use extra anchors when available
► rs/ic_os/config/tool/src/guestos/generate_ic_config.rs
    Encode extra_api_boundary_node_trust_anchors_pem as JSON in IcConfigTemplate
► rs/ic_os/config/tool/templates/ic.json5.template
    Include extra_api_boundary_node_trust_anchors_pem in http_handler config template
Enhancement ► rs/ic_os/config/types/compatibility_tests/fixtures/guestos_v1.16.0.json
    Add extra_api_boundary_node_trust_anchors_pem field placeholder (null)
Enhancement ► rs/ic_os/config/types/compatibility_tests/fixtures/hostos_v1.16.0.json
    Add extra_api_boundary_node_trust_anchors_pem field placeholder (null)
Enhancement ► rs/ic_os/config/types/src/lib.rs
    Bump CONFIG_VERSION to 1.16.0 and add extra_api_boundary_node_trust_anchors_pem field documentation/serde default for GuestOSDevSettings
Enhancement ► rs/ic_os/networking/network/BUILD.bazel
    Expose network crate to additional targets (ic-os-pkg, system-tests-pkg)
Enhancement ► rs/ic_os/networking/network/src/systemd.rs
    Define IPV6_NAME_SERVERS constant and related tests; keep contents aligned with networkd contents
Enhancement ► rs/orchestrator/src/firewall.rs
    Update test fixture to include extra_api_boundary_node_trust_anchors_pem field
Enhancement ► rs/tests/driver/src/driver/bootstrap.rs
    Adapt API BN TLS/trust anchor handling when building config; pass api_bn_trust_anchors_pem through to config creation
Enhancement ► rs/tests/driver/src/driver/ic.rs
    Import LocalApiBoundaryNodesPlaynet type and adjust related code paths
Enhancement ► rs/tests/driver/src/driver/local_backend.rs
    Introduce NIC name server DNS setup and IPV6_NAME_SERVERS usage; incorporate DNS name server handling into group creation and dnsmasq setup
Enhancement ► rs/tests/driver/Cargo.toml
    Add dependency on network crate for tests
Enhancement ► rs/tests/driver/src/driver/ic.rs
    Refactor to accommodate LocalApiBoundaryNodesPlaynet and related attributes

Comment thread rs/ic_os/config/types/compatibility_tests/src/fixture.rs
Comment thread rs/tests/networking/firewall/firewall_correctness_test.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated no new comments.

@daniel-wong-dfinity-org-twin

daniel-wong-dfinity-org-twin commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

UPDATE: This PR of mine that adds a new system test has merged into master.

I think a PR of mine that adds a system test is going to go in first. If so,

here is another TODO for this PR, I think?

@basvandijk
basvandijk requested a review from a team as a code owner August 21, 2026 10:06
@basvandijk
basvandijk force-pushed the bas/local-backend-api-bn-playnet branch from cb57a27 to 8dcfd1b Compare August 21, 2026 10:39
basvandijk and others added 6 commits August 21, 2026 14:32
…ackend

`with_api_boundary_nodes_playnet` gives API boundary nodes a domain name and a
certificate for it that the nodes trust. Both halves came from Farm, so every
test using it — and therefore every test with a cloud engine subnet, which
requires it — was pinned to `backend = "farm"`.

Stand up an equivalent locally:

* Turn the group's `dnsmasq` into its DNS server. It already ran on the group
  bridge as an RA/DHCPv4 daemon with `--port=0`; dropping that and adding
  `--no-resolv --no-hosts` makes it a hermetic resolver that answers from an
  `--addn-hosts` file (new `LocalBackend::add_dns_record`) and from a
  `--synth-domain` mirroring the public `nip.io` wildcard service.

  GuestOS has no name-server knob and boots with `IPv6AcceptRA=no`, so rather
  than reconfiguring the guests, `create_group` assigns the four addresses
  GuestOS is hard-coded to query to the bridge. Inside the backend's own network
  namespace those addresses are free and no query can escape, so every node gets
  a working resolver without touching IC-OS.

* Add `InternetComputer::setup_api_bn_local_playnet`, which issues an ephemeral
  CA plus a leaf covering the API boundary nodes' domains and registers those
  domains with the group's `dnsmasq`. `bootstrap` serves the leaf from
  `ic-boundary` through the existing `ic_boundary_tls_cert` mechanism.

* Let the replica trust that CA. `nns_delegation_manager` built its root store
  from the compiled-in public roots only, which no test-issued certificate can
  satisfy. It now also honours `extra_api_boundary_node_trust_anchors_pem`, a
  new dev-only `GuestOSDevSettings` field that is unset in production, leaving
  the public roots as the only anchors there.

Drops `backend = "farm"` from `canister_http_socks_test`,
`cloud_engine_canister_sig_test`, `xnet_cloud_engine_isolation_test`,
`nns_delegation_branch_nns_version_test` and `delete_subnet_test`. The `cpus` of
`canister_http_socks_test` was understated: its comment omitted the four cloud
engine nodes.

Two tests stay on Farm for unrelated reasons, now recorded accurately:
`nns_delegation_mainnet_nns_version_test` runs the mainnet GuestOS, whose
replica predates the new field; and `firewall_correctness_test` asserts that
port 8080 is closed between certain nodes, which the local backend cannot
satisfy because `ic-prep` always adds 8080 to the `fd00::/8` rule the driver
needs to reach the nodes. That test did assume the global firewall rule set
starts out empty, which it does not on the local backend, so it now reads the
current rules through the new `TopologySnapshot::firewall_rules`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Url::parse` was handed the whole comma-separated list, which yields a single
`Url` whose host is `icp-api.io,https` and whose path is the remainder. That
re-serialises to `https://icp-api.io,https//icp0.io,https://ic0.app` — not a
valid URL, and not the three the fixture meant to carry.

Split it into three, and regenerate the v1.16.0 fixtures, which this branch
introduces. The older fixtures keep the malformed value: they are historical
records that must stay byte-for-byte as generated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…kend

`start_ra_daemon`/`stop_ra_daemon` became `start_dnsmasq`/`stop_dnsmasq` when
the daemon took on DNS, but a cross-reference in `canister_http_socks_test`
still pointed at the old name, and three comments still called it the RA daemon
even though it now serves RA, DHCPv4 and DNS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…es plumbing

`firewall_correctness_test` stays `backend = "farm"`, and on Farm the global
firewall scope starts out empty, so reading the current rules and passing them
as the proposal's `previous_rules` buys nothing there. It was a remnant of
trying to also enable the test on the local backend, where the backend seeds a
global rule so the driver can reach the nodes.

Move it, and the `TopologySnapshot::firewall_rules` accessor it needed, to the
follow-up that actually enables the test locally. Both files are byte-identical
to master again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… local upgrades

`assert_assigned_replica_version` allows a node 600s to come up on a new
replica version. On Farm that covers a whole GuestOS upgrade cycle; on the
local backend it does not. This test's 10 VMs ask for 60 vCPUs and 40 GiB of
guest RAM from a single host, and one cycle was measured at ~9 min there:
~90s to download the ~580 MiB update image, ~240s for `manageboot.sh` to
`tar`-unpack it into the guest's tmpfs `/tmp`, ~35s to `dd` it onto the
inactive slot and ~150s to reboot. Step 5's deadline expired 79s after the
orchestrator came back up on the new version, before any replica had bound
:8080, so the test panicked with "Replica did reboot, but never came back
online!" -- which is only what `assert_assigned_replica_version_with_time`
prints when its last poll errored.

Wait 20 min per node instead, and raise the per-test timeout from 30 to 50
min: `ImageUpgrader::execute_upgrade` deletes the update image after
installing and never checks whether the target version already sits on the
inactive slot, so the roll back in Step 7 and the roll forward in Step 9 each
pay for the full cycle again.

A local run now passes in 2300s, still well inside the
`test_timeout = "eternal"` hour the BUILD file gives the action.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@basvandijk
basvandijk force-pushed the bas/local-backend-api-bn-playnet branch from 664477c to 37395b0 Compare August 21, 2026 12:32
@basvandijk basvandijk removed the CI_ALL_BAZEL_TARGETS Runs all bazel targets label Aug 21, 2026
@basvandijk
basvandijk added this pull request to the merge queue Aug 21, 2026
Merged via the queue into master with commit 3f7ae28 Aug 21, 2026
40 checks passed
@basvandijk
basvandijk deleted the bas/local-backend-api-bn-playnet branch August 21, 2026 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants