Skip to content

Commit d90d2ff

Browse files
committed
feat: Add opt-in IPv6 for containers
Configurable and w/ migrations between IPv4-Only and Dual-Stack Signed-off-by: David Rapan <[email protected]>
1 parent 42f8855 commit d90d2ff

File tree

17 files changed

+376
-84
lines changed

17 files changed

+376
-84
lines changed

supervisor/api/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from aiohttp import hdrs, web
1010

11-
from ..const import AddonState
11+
from ..const import SUPERVISOR_DOCKER_NAME, AddonState
1212
from ..coresys import CoreSys, CoreSysAttributes
1313
from ..exceptions import APIAddonNotInstalled, HostNotSupportedError
1414
from ..utils.sentry import async_capture_exception
@@ -426,7 +426,7 @@ def _register_supervisor(self) -> None:
426426
async def get_supervisor_logs(*args, **kwargs):
427427
try:
428428
return await self._api_host.advanced_logs_handler(
429-
*args, identifier="hassio_supervisor", **kwargs
429+
*args, identifier=SUPERVISOR_DOCKER_NAME, **kwargs
430430
)
431431
except Exception as err: # pylint: disable=broad-exception-caught
432432
# Supervisor logs are critical, so catch everything, log the exception
@@ -789,6 +789,7 @@ def _register_docker(self) -> None:
789789
self.webapp.add_routes(
790790
[
791791
web.get("/docker/info", api_docker.info),
792+
web.post("/docker/options", api_docker.options),
792793
web.get("/docker/registries", api_docker.registries),
793794
web.post("/docker/registries", api_docker.create_registry),
794795
web.delete("/docker/registries/{hostname}", api_docker.remove_registry),

supervisor/api/docker.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import voluptuous as vol
88

99
from ..const import (
10+
ATTR_ENABLE_IPV6,
1011
ATTR_HOSTNAME,
1112
ATTR_LOGGING,
1213
ATTR_PASSWORD,
@@ -30,10 +31,39 @@
3031
}
3132
)
3233

34+
# pylint: disable=no-value-for-parameter
35+
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_ENABLE_IPV6): vol.Boolean()})
36+
3337

3438
class APIDocker(CoreSysAttributes):
3539
"""Handle RESTful API for Docker configuration."""
3640

41+
@api_process
42+
async def info(self, request: web.Request):
43+
"""Get docker info."""
44+
data_registries = {}
45+
for hostname, registry in self.sys_docker.config.registries.items():
46+
data_registries[hostname] = {
47+
ATTR_USERNAME: registry[ATTR_USERNAME],
48+
}
49+
return {
50+
ATTR_VERSION: self.sys_docker.info.version,
51+
ATTR_ENABLE_IPV6: self.sys_docker.config.enable_ipv6,
52+
ATTR_STORAGE: self.sys_docker.info.storage,
53+
ATTR_LOGGING: self.sys_docker.info.logging,
54+
ATTR_REGISTRIES: data_registries,
55+
}
56+
57+
@api_process
58+
async def options(self, request: web.Request) -> None:
59+
"""Set docker options."""
60+
body = await api_validate(SCHEMA_OPTIONS, request)
61+
62+
if ATTR_ENABLE_IPV6 in body:
63+
self.sys_docker.config.enable_ipv6 = body[ATTR_ENABLE_IPV6]
64+
65+
await self.sys_docker.config.save_data()
66+
3767
@api_process
3868
async def registries(self, request) -> dict[str, Any]:
3969
"""Return the list of registries."""
@@ -64,18 +94,3 @@ async def remove_registry(self, request: web.Request):
6494

6595
del self.sys_docker.config.registries[hostname]
6696
await self.sys_docker.config.save_data()
67-
68-
@api_process
69-
async def info(self, request: web.Request):
70-
"""Get docker info."""
71-
data_registries = {}
72-
for hostname, registry in self.sys_docker.config.registries.items():
73-
data_registries[hostname] = {
74-
ATTR_USERNAME: registry[ATTR_USERNAME],
75-
}
76-
return {
77-
ATTR_VERSION: self.sys_docker.info.version,
78-
ATTR_STORAGE: self.sys_docker.info.storage,
79-
ATTR_LOGGING: self.sys_docker.info.logging,
80-
ATTR_REGISTRIES: data_registries,
81-
}

supervisor/api/network.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
ATTR_TYPE,
4141
ATTR_VLAN,
4242
ATTR_WIFI,
43+
DOCKER_IPV4_NETWORK_MASK,
4344
DOCKER_NETWORK,
44-
DOCKER_NETWORK_MASK,
4545
)
4646
from ..coresys import CoreSysAttributes
4747
from ..exceptions import APIError, APINotFound, HostNetworkNotFound
@@ -203,7 +203,7 @@ async def info(self, request: web.Request) -> dict[str, Any]:
203203
],
204204
ATTR_DOCKER: {
205205
ATTR_INTERFACE: DOCKER_NETWORK,
206-
ATTR_ADDRESS: str(DOCKER_NETWORK_MASK),
206+
ATTR_ADDRESS: str(DOCKER_IPV4_NETWORK_MASK),
207207
ATTR_GATEWAY: str(self.sys_docker.network.gateway),
208208
ATTR_DNS: str(self.sys_docker.network.dns),
209209
},

supervisor/const.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass
44
from enum import StrEnum
5-
from ipaddress import IPv4Network
5+
from ipaddress import IPv4Network, IPv6Network
66
from pathlib import Path
77
from sys import version_info as systemversion
88
from typing import Self
@@ -12,6 +12,10 @@
1212
SUPERVISOR_VERSION = "9999.09.9.dev9999"
1313
SERVER_SOFTWARE = f"HomeAssistantSupervisor/{SUPERVISOR_VERSION} aiohttp/{aiohttpversion} Python/{systemversion[0]}.{systemversion[1]}"
1414

15+
DOCKER_PREFIX: str = "hassio"
16+
OBSERVER_DOCKER_NAME: str = f"{DOCKER_PREFIX}_observer"
17+
SUPERVISOR_DOCKER_NAME: str = f"{DOCKER_PREFIX}_supervisor"
18+
1519
URL_HASSIO_ADDONS = "https://github.com/home-assistant/addons"
1620
URL_HASSIO_APPARMOR = "https://version.home-assistant.io/apparmor_{channel}.txt"
1721
URL_HASSIO_VERSION = "https://version.home-assistant.io/{channel}.json"
@@ -41,8 +45,10 @@
4145
SYSTEMD_JOURNAL_VOLATILE = Path("/run/log/journal")
4246

4347
DOCKER_NETWORK = "hassio"
44-
DOCKER_NETWORK_MASK = IPv4Network("172.30.32.0/23")
45-
DOCKER_NETWORK_RANGE = IPv4Network("172.30.33.0/24")
48+
DOCKER_NETWORK_DRIVER = "bridge"
49+
DOCKER_IPV6_NETWORK_MASK = IPv6Network("fd0c:ac1e:2100::/48")
50+
DOCKER_IPV4_NETWORK_MASK = IPv4Network("172.30.32.0/23")
51+
DOCKER_IPV4_NETWORK_RANGE = IPv4Network("172.30.33.0/24")
4652

4753
# This needs to match the dockerd --cpu-rt-runtime= argument.
4854
DOCKER_CPU_RUNTIME_TOTAL = 950_000
@@ -172,6 +178,7 @@
172178
ATTR_DOCUMENTATION = "documentation"
173179
ATTR_DOMAINS = "domains"
174180
ATTR_ENABLE = "enable"
181+
ATTR_ENABLE_IPV6 = "enable_ipv6"
175182
ATTR_ENABLED = "enabled"
176183
ATTR_ENVIRONMENT = "environment"
177184
ATTR_EVENT = "event"

supervisor/dbus/network/connection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def ipv4(self) -> IpConfiguration | None:
9696

9797
@ipv4.setter
9898
def ipv4(self, ipv4: IpConfiguration | None) -> None:
99-
"""Set ipv4 configuration."""
99+
"""Set IPv4 configuration."""
100100
if self._ipv4 and self._ipv4 is not ipv4:
101101
self._ipv4.shutdown()
102102

@@ -109,7 +109,7 @@ def ipv6(self) -> IpConfiguration | None:
109109

110110
@ipv6.setter
111111
def ipv6(self, ipv6: IpConfiguration | None) -> None:
112-
"""Set ipv6 configuration."""
112+
"""Set IPv6 configuration."""
113113
if self._ipv6 and self._ipv6 is not ipv6:
114114
self._ipv6.shutdown()
115115

supervisor/dbus/network/setting/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,12 @@ def vlan(self) -> VlanProperties | None:
152152

153153
@property
154154
def ipv4(self) -> IpProperties | None:
155-
"""Return ipv4 properties if any."""
155+
"""Return IPv4 properties if any."""
156156
return self._ipv4
157157

158158
@property
159159
def ipv6(self) -> Ip6Properties | None:
160-
"""Return ipv6 properties if any."""
160+
"""Return IPv6 properties if any."""
161161
return self._ipv6
162162

163163
@property

supervisor/docker/manager.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import requests
2323

2424
from ..const import (
25+
ATTR_ENABLE_IPV6,
2526
ATTR_REGISTRIES,
2627
DNS_SUFFIX,
2728
DOCKER_NETWORK,
@@ -93,6 +94,16 @@ def __init__(self):
9394
"""Initialize the JSON configuration."""
9495
super().__init__(FILE_HASSIO_DOCKER, SCHEMA_DOCKER_CONFIG)
9596

97+
@property
98+
def enable_ipv6(self) -> bool:
99+
"""Return IPv6 configuration for docker network."""
100+
return self._data.get(ATTR_ENABLE_IPV6, False)
101+
102+
@enable_ipv6.setter
103+
def enable_ipv6(self, value: bool) -> None:
104+
"""Set IPv6 configuration for docker network."""
105+
self._data[ATTR_ENABLE_IPV6] = value
106+
96107
@property
97108
def registries(self) -> dict[str, Any]:
98109
"""Return credentials for docker registries."""
@@ -124,9 +135,11 @@ async def post_init(self) -> Self:
124135
timeout=900,
125136
),
126137
)
127-
self._network = DockerNetwork(self._docker)
128138
self._info = DockerInfo.new(self.docker.info())
129139
await self.config.read_data()
140+
self._network = await DockerNetwork(self.docker).post_init(
141+
self.config.enable_ipv6
142+
)
130143
return self
131144

132145
@property

0 commit comments

Comments
 (0)