Support custom DNS configuration on auto

So far, when the network setting is set to auto, we don't allow a
custom DNS configuration. However, it is a perfectly valid use case to
provide custom DNS configuration while using automatic IP configuration
(e.g. DHCP). Especially when the DHCP server doesn't provide a DNS
server. In this case, systemd-resolved (at least on Home Assistant OS)
falls back to Cloudflare. This might not what the user wants. Customze
the DNS setting allows to set a custom fallback DNS server or override
a potential non-working DHCP provided one.
This commit is contained in:
Stefan Agner 2024-08-20 16:32:40 +00:00
parent 8ab396d77c
commit 1e9a3d7d0f
No known key found for this signature in database
GPG Key ID: 22D95D15D3A36E95
6 changed files with 76 additions and 28 deletions

View File

@ -29,6 +29,7 @@ from ..const import (
ATTR_METHOD,
ATTR_MODE,
ATTR_NAMESERVERS,
ATTR_NAMESERVERS_AUTO,
ATTR_PARENT,
ATTR_PRIMARY,
ATTR_PSK,
@ -90,6 +91,7 @@ def ipconfig_struct(config: IpConfig) -> dict[str, Any]:
return {
ATTR_METHOD: config.method,
ATTR_ADDRESS: [address.with_prefixlen for address in config.address],
ATTR_NAMESERVERS_AUTO: config.nameservers_auto,
ATTR_NAMESERVERS: [str(address) for address in config.nameservers],
ATTR_GATEWAY: str(config.gateway) if config.gateway else None,
ATTR_READY: config.ready,
@ -200,15 +202,19 @@ class APINetwork(CoreSysAttributes):
if key == ATTR_IPV4:
interface.ipv4 = replace(
interface.ipv4
or IpConfig(InterfaceMethod.STATIC, [], None, [], None),
or IpConfig(InterfaceMethod.STATIC, [], None, False, [], None),
**config,
)
if interface.ipv4.method == InterfaceMethod.AUTO:
interface.ipv4.nameservers_auto = ATTR_NAMESERVERS not in config
elif key == ATTR_IPV6:
interface.ipv6 = replace(
interface.ipv6
or IpConfig(InterfaceMethod.STATIC, [], None, [], None),
or IpConfig(InterfaceMethod.STATIC, [], None, False, [], None),
**config,
)
if interface.ipv6.method == InterfaceMethod.AUTO:
interface.ipv6.nameservers_auto = ATTR_NAMESERVERS not in config
elif key == ATTR_WIFI:
interface.wifi = replace(
interface.wifi

View File

@ -244,6 +244,7 @@ ATTR_MODE = "mode"
ATTR_MULTICAST = "multicast"
ATTR_NAME = "name"
ATTR_NAMESERVERS = "nameservers"
ATTR_NAMESERVERS_AUTO = "nameservers_auto"
ATTR_NETWORK = "network"
ATTR_NETWORK_DESCRIPTION = "network_description"
ATTR_NETWORK_RX = "network_rx"

View File

@ -64,6 +64,7 @@ class IpProperties:
"""IP properties object for Network Manager."""
method: str | None
dns: list[str] | None
@dataclass(slots=True)

View File

@ -6,7 +6,7 @@ from typing import Any
from dbus_fast import Variant
from dbus_fast.aio.message_bus import MessageBus
from ....const import ATTR_METHOD, ATTR_MODE, ATTR_PSK, ATTR_SSID
from ....const import ATTR_DNS, ATTR_METHOD, ATTR_MODE, ATTR_PSK, ATTR_SSID
from ...const import DBUS_NAME_NM
from ...interface import DBusInterface
from ...utils import dbus_connected
@ -75,7 +75,7 @@ def _merge_settings_attribute(
class NetworkSetting(DBusInterface):
"""Network connection setting object for Network Manager.
https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.Settings.Connection.html
https://networkmanager.dev/docs/api/1.48.0/gdbus-org.freedesktop.NetworkManager.Settings.Connection.html
"""
bus_name: str = DBUS_NAME_NM
@ -229,11 +229,13 @@ class NetworkSetting(DBusInterface):
if CONF_ATTR_IPV4 in data:
self._ipv4 = IpProperties(
data[CONF_ATTR_IPV4].get(ATTR_METHOD),
data[CONF_ATTR_IPV4].get(ATTR_DNS),
)
if CONF_ATTR_IPV6 in data:
self._ipv6 = IpProperties(
data[CONF_ATTR_IPV6].get(ATTR_METHOD),
data[CONF_ATTR_IPV6].get(ATTR_DNS),
)
if CONF_ATTR_MATCH in data:

View File

@ -76,13 +76,6 @@ def get_connection_from_interface(
ipv4["method"] = Variant("s", "disabled")
else:
ipv4["method"] = Variant("s", "manual")
ipv4["dns"] = Variant(
"au",
[
socket.htonl(int(ip_address))
for ip_address in interface.ipv4.nameservers
],
)
adressdata = []
for address in interface.ipv4.address:
@ -96,6 +89,18 @@ def get_connection_from_interface(
ipv4["address-data"] = Variant("aa{sv}", adressdata)
ipv4["gateway"] = Variant("s", str(interface.ipv4.gateway))
if (
interface.ipv4.method == InterfaceMethod.AUTO
and not interface.ipv4.nameservers_auto
) or interface.ipv4.method == InterfaceMethod.STATIC:
ipv4["dns"] = Variant(
"au",
[
socket.htonl(int(ip_address))
for ip_address in interface.ipv4.nameservers
],
)
conn[CONF_ATTR_IPV4] = ipv4
ipv6 = {}
@ -105,9 +110,6 @@ def get_connection_from_interface(
ipv6["method"] = Variant("s", "link-local")
else:
ipv6["method"] = Variant("s", "manual")
ipv6["dns"] = Variant(
"aay", [ip_address.packed for ip_address in interface.ipv6.nameservers]
)
adressdata = []
for address in interface.ipv6.address:
@ -121,6 +123,14 @@ def get_connection_from_interface(
ipv6["address-data"] = Variant("aa{sv}", adressdata)
ipv6["gateway"] = Variant("s", str(interface.ipv6.gateway))
if (
interface.ipv6.method == InterfaceMethod.AUTO
and not interface.ipv6.nameservers_auto
) or interface.ipv6.method == InterfaceMethod.STATIC:
ipv6["dns"] = Variant(
"aay", [ip_address.packed for ip_address in interface.ipv6.nameservers]
)
conn[CONF_ATTR_IPV6] = ipv6
if interface.type == InterfaceType.ETHERNET:

View File

@ -2,6 +2,7 @@
from dataclasses import dataclass
from ipaddress import IPv4Address, IPv4Interface, IPv6Address, IPv6Interface
import socket
from ..dbus.const import (
ConnectionStateFlags,
@ -32,6 +33,7 @@ class IpConfig:
method: InterfaceMethod
address: list[IPv4Interface | IPv6Interface]
gateway: IPv4Address | IPv6Address | None
nameservers_auto: bool
nameservers: list[IPv4Address | IPv6Address]
ready: bool | None
@ -102,6 +104,30 @@ class Interface:
bool(inet.connection)
and ConnectionStateFlags.IP6_READY in inet.connection.state_flags
)
# Use Nameserver from configuration if present (means manual DNS override)
if inet.settings.ipv4.dns:
ipv4_nameservers_auto = False
ipv4_nameservers = [
IPv4Address(socket.ntohl(ip)) for ip in inet.settings.ipv4.dns
]
elif inet.connection.ipv4.nameservers:
ipv4_nameservers_auto = True
ipv4_nameservers = inet.connection.ipv4.nameservers
else:
ipv4_nameservers_auto = True
ipv4_nameservers = []
if inet.settings.ipv6.dns:
ipv6_nameservers_auto = False
ipv6_nameservers = [IPv6Address(bytes(ip)) for ip in inet.settings.ipv6.dns]
elif inet.connection.ipv6.nameservers:
ipv6_nameservers_auto = True
ipv6_nameservers = inet.connection.ipv6.nameservers
else:
ipv6_nameservers_auto = True
ipv6_nameservers = []
return Interface(
inet.name,
inet.hw_address,
@ -111,27 +137,29 @@ class Interface:
inet.primary,
Interface._map_nm_type(inet.type),
IpConfig(
ipv4_method,
inet.connection.ipv4.address if inet.connection.ipv4.address else [],
inet.connection.ipv4.gateway,
inet.connection.ipv4.nameservers
if inet.connection.ipv4.nameservers
method=ipv4_method,
address=inet.connection.ipv4.address
if inet.connection.ipv4.address
else [],
ipv4_ready,
gateway=inet.connection.ipv4.gateway,
nameservers_auto=ipv4_nameservers_auto,
nameservers=ipv4_nameservers,
ready=ipv4_ready,
)
if inet.connection and inet.connection.ipv4
else IpConfig(ipv4_method, [], None, [], ipv4_ready),
else IpConfig(ipv4_method, [], None, True, [], ipv4_ready),
IpConfig(
ipv6_method,
inet.connection.ipv6.address if inet.connection.ipv6.address else [],
inet.connection.ipv6.gateway,
inet.connection.ipv6.nameservers
if inet.connection.ipv6.nameservers
method=ipv6_method,
address=inet.connection.ipv6.address
if inet.connection.ipv6.address
else [],
ipv6_ready,
gateway=inet.connection.ipv6.gateway,
nameservers_auto=ipv6_nameservers_auto,
nameservers=ipv6_nameservers,
ready=ipv6_ready,
)
if inet.connection and inet.connection.ipv6
else IpConfig(ipv6_method, [], None, [], ipv6_ready),
else IpConfig(ipv6_method, [], None, True, [], ipv6_ready),
Interface._map_nm_wifi(inet),
Interface._map_nm_vlan(inet),
)