mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 22:57:17 +00:00
Avoid enabling ipv6 dual stack for zeroconf on unsupported platforms (#56584)
This commit is contained in:
parent
f74291ccb6
commit
26f73779cc
@ -5,9 +5,10 @@ import asyncio
|
|||||||
from collections.abc import Coroutine
|
from collections.abc import Coroutine
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from ipaddress import IPv6Address, ip_address
|
from ipaddress import IPv4Address, IPv6Address, ip_address
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
import sys
|
||||||
from typing import Any, TypedDict, cast
|
from typing import Any, TypedDict, cast
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -131,18 +132,31 @@ async def _async_get_instance(hass: HomeAssistant, **zcargs: Any) -> HaAsyncZero
|
|||||||
return aio_zc
|
return aio_zc
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_zc_has_functional_dual_stack() -> bool:
|
||||||
|
"""Return true for platforms that not support IP_ADD_MEMBERSHIP on an AF_INET6 socket.
|
||||||
|
|
||||||
|
Zeroconf only supports a single listen socket at this time.
|
||||||
|
"""
|
||||||
|
return not sys.platform.startswith("freebsd") and not sys.platform.startswith(
|
||||||
|
"darwin"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up Zeroconf and make Home Assistant discoverable."""
|
"""Set up Zeroconf and make Home Assistant discoverable."""
|
||||||
zc_args: dict = {}
|
zc_args: dict = {"ip_version": IPVersion.V4Only}
|
||||||
|
|
||||||
adapters = await network.async_get_adapters(hass)
|
adapters = await network.async_get_adapters(hass)
|
||||||
|
|
||||||
ipv6 = True
|
ipv6 = False
|
||||||
if not any(adapter["enabled"] and adapter["ipv6"] for adapter in adapters):
|
if _async_zc_has_functional_dual_stack():
|
||||||
ipv6 = False
|
if any(adapter["enabled"] and adapter["ipv6"] for adapter in adapters):
|
||||||
zc_args["ip_version"] = IPVersion.V4Only
|
ipv6 = True
|
||||||
else:
|
zc_args["ip_version"] = IPVersion.All
|
||||||
zc_args["ip_version"] = IPVersion.All
|
elif not any(adapter["enabled"] and adapter["ipv4"] for adapter in adapters):
|
||||||
|
zc_args["ip_version"] = IPVersion.V6Only
|
||||||
|
ipv6 = True
|
||||||
|
|
||||||
if not ipv6 and network.async_only_default_interface_enabled(adapters):
|
if not ipv6 and network.async_only_default_interface_enabled(adapters):
|
||||||
zc_args["interfaces"] = InterfaceChoice.Default
|
zc_args["interfaces"] = InterfaceChoice.Default
|
||||||
@ -152,6 +166,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
for source_ip in await network.async_get_enabled_source_ips(hass)
|
for source_ip in await network.async_get_enabled_source_ips(hass)
|
||||||
if not source_ip.is_loopback
|
if not source_ip.is_loopback
|
||||||
and not (isinstance(source_ip, IPv6Address) and source_ip.is_global)
|
and not (isinstance(source_ip, IPv6Address) and source_ip.is_global)
|
||||||
|
and not (
|
||||||
|
isinstance(source_ip, IPv6Address)
|
||||||
|
and zc_args["ip_version"] == IPVersion.V4Only
|
||||||
|
)
|
||||||
|
and not (
|
||||||
|
isinstance(source_ip, IPv4Address)
|
||||||
|
and zc_args["ip_version"] == IPVersion.V6Only
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
aio_zc = await _async_get_instance(hass, **zc_args)
|
aio_zc = await _async_get_instance(hass, **zc_args)
|
||||||
|
@ -779,11 +779,13 @@ _ADAPTERS_WITH_MANUAL_CONFIG = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async def test_async_detect_interfaces_setting_empty_route(hass, mock_async_zeroconf):
|
async def test_async_detect_interfaces_setting_empty_route_linux(
|
||||||
"""Test without default interface config and the route returns nothing."""
|
hass, mock_async_zeroconf
|
||||||
with patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, patch.object(
|
):
|
||||||
hass.config_entries.flow, "async_init"
|
"""Test without default interface config and the route returns nothing on linux."""
|
||||||
), patch.object(
|
with patch("homeassistant.components.zeroconf.sys.platform", "linux"), patch(
|
||||||
|
"homeassistant.components.zeroconf.HaZeroconf"
|
||||||
|
) as mock_zc, patch.object(hass.config_entries.flow, "async_init"), patch.object(
|
||||||
zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
|
zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.zeroconf.network.async_get_adapters",
|
"homeassistant.components.zeroconf.network.async_get_adapters",
|
||||||
@ -807,6 +809,33 @@ async def test_async_detect_interfaces_setting_empty_route(hass, mock_async_zero
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_detect_interfaces_setting_empty_route_freebsd(
|
||||||
|
hass, mock_async_zeroconf
|
||||||
|
):
|
||||||
|
"""Test without default interface config and the route returns nothing on freebsd."""
|
||||||
|
with patch("homeassistant.components.zeroconf.sys.platform", "freebsd"), patch(
|
||||||
|
"homeassistant.components.zeroconf.HaZeroconf"
|
||||||
|
) as mock_zc, patch.object(hass.config_entries.flow, "async_init"), patch.object(
|
||||||
|
zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.zeroconf.network.async_get_adapters",
|
||||||
|
return_value=_ADAPTERS_WITH_MANUAL_CONFIG,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.zeroconf.AsyncServiceInfo",
|
||||||
|
side_effect=get_service_info_mock,
|
||||||
|
):
|
||||||
|
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert mock_zc.mock_calls[0] == call(
|
||||||
|
interfaces=[
|
||||||
|
"192.168.1.5",
|
||||||
|
"172.16.1.5",
|
||||||
|
],
|
||||||
|
ip_version=IPVersion.V4Only,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_get_announced_addresses(hass, mock_async_zeroconf):
|
async def test_get_announced_addresses(hass, mock_async_zeroconf):
|
||||||
"""Test addresses for mDNS announcement."""
|
"""Test addresses for mDNS announcement."""
|
||||||
expected = {
|
expected = {
|
||||||
@ -848,11 +877,13 @@ _ADAPTER_WITH_DEFAULT_ENABLED_AND_IPV6 = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async def test_async_detect_interfaces_explicitly_set_ipv6(hass, mock_async_zeroconf):
|
async def test_async_detect_interfaces_explicitly_set_ipv6_linux(
|
||||||
"""Test interfaces are explicitly set when IPv6 is present."""
|
hass, mock_async_zeroconf
|
||||||
with patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, patch.object(
|
):
|
||||||
hass.config_entries.flow, "async_init"
|
"""Test interfaces are explicitly set when IPv6 is present on linux."""
|
||||||
), patch.object(
|
with patch("homeassistant.components.zeroconf.sys.platform", "linux"), patch(
|
||||||
|
"homeassistant.components.zeroconf.HaZeroconf"
|
||||||
|
) as mock_zc, patch.object(hass.config_entries.flow, "async_init"), patch.object(
|
||||||
zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
|
zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.zeroconf.network.async_get_adapters",
|
"homeassistant.components.zeroconf.network.async_get_adapters",
|
||||||
@ -871,6 +902,31 @@ async def test_async_detect_interfaces_explicitly_set_ipv6(hass, mock_async_zero
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_detect_interfaces_explicitly_set_ipv6_freebsd(
|
||||||
|
hass, mock_async_zeroconf
|
||||||
|
):
|
||||||
|
"""Test interfaces are explicitly set when IPv6 is present on freebsd."""
|
||||||
|
with patch("homeassistant.components.zeroconf.sys.platform", "freebsd"), patch(
|
||||||
|
"homeassistant.components.zeroconf.HaZeroconf"
|
||||||
|
) as mock_zc, patch.object(hass.config_entries.flow, "async_init"), patch.object(
|
||||||
|
zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.zeroconf.network.async_get_adapters",
|
||||||
|
return_value=_ADAPTER_WITH_DEFAULT_ENABLED_AND_IPV6,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.zeroconf.AsyncServiceInfo",
|
||||||
|
side_effect=get_service_info_mock,
|
||||||
|
):
|
||||||
|
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_zc.mock_calls[0] == call(
|
||||||
|
interfaces=InterfaceChoice.Default,
|
||||||
|
ip_version=IPVersion.V4Only,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_no_name(hass, mock_async_zeroconf):
|
async def test_no_name(hass, mock_async_zeroconf):
|
||||||
"""Test fallback to Home for mDNS announcement if the name is missing."""
|
"""Test fallback to Home for mDNS announcement if the name is missing."""
|
||||||
hass.config.location_name = ""
|
hass.config.location_name = ""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user