Skip to content

Add isolated physical network access for apps - #6937

Draft
agners wants to merge 3 commits into
mainfrom
app-network-isolation
Draft

Add isolated physical network access for apps#6937
agners wants to merge 3 commits into
mainfrom
app-network-isolation

Conversation

@agners

@agners agners commented Jun 12, 2026

Copy link
Copy Markdown
Member

Proposed change

Implements the first phase of isolated physical network access: users can assign apps that use host networking an isolated endpoint on a physical network interface instead. The app then runs in bridge mode with its own IP and MAC address on the selected interface (Docker macvlan), providing full multicast/mDNS support and interface selection on multi-homed hosts while restoring the isolation that host networking gives up.

Key design points:

  • Dual attachment: the container stays attached to the internal hassio network, so ingress, API proxy, DNS plug-in and inter-app communication work unchanged. The macvlan endpoint provides the physical network presence and wins the default route via GwPriority.
  • Shared networks per interface: Docker allows only one macvlan network per parent interface, so networks (hassio-macvlan-<iface>) are a shared resource derived from the host interface configuration (subnet/gateway via NetworkManager), created on demand, recreated on drift and garbage collected when no app references them.
  • Static IPv4, stable MAC: the user assigns a static IP (validated against subnet, host/gateway addresses and other apps); the MAC is pinned with the IP-derived scheme older engines used, since Docker 28 assigns random MACs per endpoint creation. Both are exposed in the app info API so users can set up router/firewall rules before the first start.
  • IPv6 via SLAAC: external networks have IPv6 enabled (engine-allocated ULA satisfies the driver, real addressing comes from router advertisements). Endpoints set accept_ra=2 and accept_ra_rt_info_max_plen=64 so RFC 4191 routes announced by Thread border routers reach the container.
  • Graceful degradation: if the endpoint cannot be set up at start (interface gone, subnet changed), the app falls back to host networking and a network_isolation_failed issue is raised; a scheduled resolution check detects drift. The fallback is blocked when the Docker gateway is unprotected, since that protection does not apply to isolated apps (their traffic bypasses the host network namespace, which also lifts the security rating penalty for host networking).

The feature requires Docker >= 28 (for GwPriority and per-endpoint sysctls) and is gated accordingly; existing installations and apps are unaffected unless a user opts in per app.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New feature (which adds functionality to the supervisor)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • The code has been formatted using Ruff (ruff format supervisor tests)
  • Tests have been added to verify that the new code works.

If API endpoints or add-on configuration are added/changed:

@agners agners added the new-feature A new feature label Jun 12, 2026
@agners

agners commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

This is AI assisted based on the design and discussion in home-assistant/architecture#1034. Currently it allows Apps which use host network to isolate.

I've tested this with the ESPHome and Matter App. The Matter App required a small tweak in the startup script to not fetch the primary interface from Supervisor (rather use the ip route based fallback). With this tweak I was able to communicate to Thread devices behind a BR running on a different host, which means the Matter app's isolated network processed IPv6 ND RIO correctly 🎉 .

I think I'd prefer to make IPv4 completely optional. Currently the MAC address is derrived from the IPv4 address, which seems to be a design which Docker used previously. However, I think I'd rather prefer it to just assign a random MAC.

I am also not sure if we maybe should only support ipvlan. While macvlan is strictly more isolated, it seems not to work with WiFi really. If ipvlan gives all the isolation needs we have, maybe we can simply default to it instead 🤔

agners and others added 3 commits June 12, 2026 16:54
As discussed in home-assistant/architecture#1034, apps using host
networking get no isolation at all, while the default internal network
cannot serve use cases like custom multicast protocols or interface
selection on multi-homed hosts.

Allow users to assign apps with host networking an isolated endpoint on
a physical interface instead: the container runs in bridge mode,
attached to the internal hassio network (keeping ingress, API proxy and
DNS plug-in communication unchanged) plus a Supervisor-managed macvlan
network on the selected interface with a user-chosen static IPv4 and
its own MAC address. The macvlan endpoint gets default route priority
via GwPriority, which requires Docker 28; the feature is gated
accordingly.

Docker allows only one macvlan network per parent interface, so these
networks are a shared resource keyed by interface: created on demand
from the host interface configuration via NetworkManager, recreated
when that configuration changed and garbage collected once no installed
app references them anymore.

If the endpoint cannot be set up at app start (interface gone, subnet
changed), the app falls back to host networking and a resolution issue
is raised; a scheduled check detects such drift as well. Apps with an
isolated endpoint no longer run in the host network namespace, so the
host network security rating penalty and the unprotected Docker gateway
boot gate do not apply to them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Docker 28 assigns a random MAC address to macvlan endpoints on every
endpoint creation, while older engines derived it from the endpoint's
IP address. As app containers are recreated on every start, the
isolated endpoint would get a different MAC address on each restart,
making it impossible to reference the app in router or firewall rules.

Pin the MAC address in the endpoint configuration instead, using the
same derivation older engines used (02:42 followed by the static IPv4
octets). With the static IP enforced per app, the MAC address stays
stable across restarts and updates and only changes when the user
assigns a different IP address.

Since the MAC address is now fully determined by the configuration,
report it in the app info API from the configured IP rather than from
the running container's network metadata. This makes it available as
soon as isolation is configured, so users can set up router or
firewall rules before the first start.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The isolated endpoint is a real L2 presence on the physical network, so
the container kernel can autoconfigure IPv6 from the local network's
router advertisements without any address management on our side. This
serves the IPv6 use cases from home-assistant/architecture#1034 (Thread
border routers, Matter) that the NAT-ed internal network cannot.

Enable IPv6 on external networks without specifying a subnet: Docker
allocates a unique local address prefix to satisfy the macvlan driver,
while real addressing comes from SLAAC. Existing IPv4-only external
networks are recreated through the regular drift handling.

Two kernel defaults inside the container network namespace would break
the flagship use cases, so the endpoint sets per-interface sysctls via
the endpoint configuration (Docker API 1.47, covered by the existing
Docker 28 requirement): accept_ra=2 keeps accepting router
advertisements when the app enables IP forwarding (Thread border
routers), and accept_ra_rt_info_max_plen=64 enables RFC 4191 route
information option processing (off by default), which routes announced
by border routers need to reach the container at all.

Together with the pinned MAC address the SLAAC and link-local addresses
are stable across restarts (EUI-64 derivation from a stable MAC).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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

Adds the first phase of “isolated physical network access” for Supervisor-managed apps by allowing host-network apps to instead attach an isolated endpoint on a physical interface (Docker macvlan), while keeping the internal hassio network attachment for Supervisor ingress/DNS/inter-container connectivity.

Changes:

  • Introduces external physical network management (DockerExternalNetworks) and support for attaching extra network endpoints to containers at start.
  • Adds app-level network isolation configuration (schema + persistence), API support (options + info fields), and host-interface capability reporting.
  • Adds a new resolution issue/check (network_isolation_failed) plus test coverage across API, docker, apps, and resolution layers.

Reviewed changes

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

Show a summary per file
File Description
tests/resolution/check/test_check_app_network_isolation.py Adds tests for the new resolution check behavior.
tests/docker/test_external_network.py Adds unit tests for external network create/ensure/connect/gc logic.
tests/docker/test_app.py Adds integration-style tests for isolated run path and fallback behavior.
tests/conftest.py Wires docker.external_networks.coresys in the test CoreSys fixture.
tests/apps/test_network_isolation.py Tests schema validation, persistence round-trip, and security rating interaction.
tests/apps/test_app.py Adjusts mocks to avoid false-truthy persisted isolation config.
tests/api/test_apps_network_isolation.py Adds API tests for setting/clearing/validating isolation options.
supervisor/validate.py Adds ipv4_address() validator helper for schema normalization.
supervisor/resolution/const.py Adds IssueType.NETWORK_ISOLATION_FAILED.
supervisor/resolution/checks/app_network_isolation.py New check to detect drift/broken assigned isolation endpoints.
supervisor/exceptions.py Adds API error types for isolation validation failures.
supervisor/docker/manager.py Adds support for connecting extra (external) network endpoints during container start.
supervisor/docker/external_network.py New external network manager: ensure/recreate/connect/gc for per-interface macvlan networks.
supervisor/docker/const.py Adds ExternalNetworkDriver, NetworkIsolationConfig, and ExtraNetworkEndpoint types.
supervisor/docker/app.py Implements isolated endpoint setup + fallback logic and ensures internal IP reporting stays correct.
supervisor/const.py Adds new constants used by network isolation config/persistence.
supervisor/apps/validate.py Adds persisted user schema for network_isolation.
supervisor/apps/utils.py Adjusts security rating penalty so host-network apps with isolation don’t get penalized.
supervisor/apps/model.py Adds network_isolation property to the model interface (default None).
supervisor/apps/manager.py Updates boot gating so isolated apps are not blocked by Docker gateway firewall state.
supervisor/apps/app.py Adds persistence for isolation config, derived external MAC exposure, and gc on uninstall.
supervisor/api/network.py Exposes per-interface network_isolation_capable flag.
supervisor/api/const.py Adds API attribute constants for isolation info/availability/mac.
supervisor/api/apps.py Adds options validation + info fields for network isolation and triggers external network gc.

Comment thread supervisor/docker/app.py
Comment on lines +591 to +594
if config := self.app.network_isolation:
try:
network_name = await self.sys_docker.external_networks.ensure(config)
except (HostNetworkNotFound, DockerError) as err:
Comment on lines +575 to +581
# Connect additional (external) network endpoints. Unlike the
# internal network these are essential for the container's
# purpose, so failures abort the start.
for endpoint in extra_networks or []:
await self.external_networks.connect_container(
container.id, name, endpoint
)
Comment on lines +158 to +160
return current_ipam is not None and current_ipam.get(
"Gateway"
) == wanted_ipam.get("Gateway")
@github-actions

Copy link
Copy Markdown
Contributor

There hasn't been any activity on this pull request recently. This pull request has been automatically marked as stale because of that and will be closed if no further activity occurs within 7 days.
Thank you for your contributions.

@github-actions github-actions Bot added the stale label Jul 17, 2026
@github-actions github-actions Bot closed this Jul 24, 2026
@agners agners reopened this Jul 24, 2026
@github-actions github-actions Bot removed the stale label Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants