mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
Relocate async_get_announce_addresses from zeroconf to network (#94816)
This commit is contained in:
parent
c47543c9dd
commit
605c4db142
@ -119,6 +119,32 @@ async def async_get_ipv4_broadcast_addresses(hass: HomeAssistant) -> set[IPv4Add
|
|||||||
return broadcast_addresses
|
return broadcast_addresses
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_announce_addresses(hass: HomeAssistant) -> list[str]:
|
||||||
|
"""Return a list of IP addresses to announce/use via zeroconf/ssdp/etc.
|
||||||
|
|
||||||
|
The default ip address is always returned first if available.
|
||||||
|
"""
|
||||||
|
adapters = await async_get_adapters(hass)
|
||||||
|
addresses: list[str] = []
|
||||||
|
default_ip: str | None = None
|
||||||
|
for adapter in adapters:
|
||||||
|
if not adapter["enabled"]:
|
||||||
|
continue
|
||||||
|
for ips in adapter["ipv4"]:
|
||||||
|
addresses.append(str(IPv4Address(ips["address"])))
|
||||||
|
for ips in adapter["ipv6"]:
|
||||||
|
addresses.append(str(IPv6Address(ips["address"])))
|
||||||
|
|
||||||
|
# Puts the default IPv4 address first in the list to preserve compatibility,
|
||||||
|
# because some mDNS implementations ignores anything but the first announced
|
||||||
|
# address.
|
||||||
|
if default_ip := await async_get_source_ip(hass, target_ip=MDNS_TARGET_IP):
|
||||||
|
if default_ip in addresses:
|
||||||
|
addresses.remove(default_ip)
|
||||||
|
return [default_ip] + list(addresses)
|
||||||
|
return list(addresses)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up network for Home Assistant."""
|
"""Set up network for Home Assistant."""
|
||||||
# Avoid circular issue: http->network->websocket_api->http
|
# Avoid circular issue: http->network->websocket_api->http
|
||||||
|
@ -7,10 +7,9 @@ from contextlib import suppress
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from fnmatch import translate
|
from fnmatch import translate
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from ipaddress import IPv4Address, IPv6Address, ip_address
|
from ipaddress import IPv4Address, IPv6Address
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import socket
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Final, cast
|
from typing import Any, Final, cast
|
||||||
|
|
||||||
@ -25,8 +24,6 @@ from zeroconf.asyncio import AsyncServiceInfo
|
|||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components import network
|
from homeassistant.components import network
|
||||||
from homeassistant.components.network import MDNS_TARGET_IP, async_get_source_ip
|
|
||||||
from homeassistant.components.network.models import Adapter
|
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, __version__
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, __version__
|
||||||
from homeassistant.core import Event, HomeAssistant, callback
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import BaseServiceInfo
|
from homeassistant.data_entry_flow import BaseServiceInfo
|
||||||
@ -243,32 +240,6 @@ def _build_homekit_model_lookups(
|
|||||||
return homekit_model_lookup, homekit_model_matchers
|
return homekit_model_lookup, homekit_model_matchers
|
||||||
|
|
||||||
|
|
||||||
def _get_announced_addresses(
|
|
||||||
adapters: list[Adapter],
|
|
||||||
first_ip: bytes | None = None,
|
|
||||||
) -> list[bytes]:
|
|
||||||
"""Return a list of IP addresses to announce via zeroconf.
|
|
||||||
|
|
||||||
If first_ip is not None, it will be the first address in the list.
|
|
||||||
"""
|
|
||||||
addresses = {
|
|
||||||
addr.packed
|
|
||||||
for addr in [
|
|
||||||
ip_address(ip["address"])
|
|
||||||
for adapter in adapters
|
|
||||||
if adapter["enabled"]
|
|
||||||
for ip in cast(list, adapter["ipv6"]) + cast(list, adapter["ipv4"])
|
|
||||||
]
|
|
||||||
if not (addr.is_unspecified or addr.is_loopback)
|
|
||||||
}
|
|
||||||
if first_ip:
|
|
||||||
address_list = [first_ip]
|
|
||||||
address_list.extend(addresses - set({first_ip}))
|
|
||||||
else:
|
|
||||||
address_list = list(addresses)
|
|
||||||
return address_list
|
|
||||||
|
|
||||||
|
|
||||||
def _filter_disallowed_characters(name: str) -> str:
|
def _filter_disallowed_characters(name: str) -> str:
|
||||||
"""Filter disallowed characters from a string.
|
"""Filter disallowed characters from a string.
|
||||||
|
|
||||||
@ -307,24 +278,13 @@ async def _async_register_hass_zc_service(
|
|||||||
# Set old base URL based on external or internal
|
# Set old base URL based on external or internal
|
||||||
params["base_url"] = params["external_url"] or params["internal_url"]
|
params["base_url"] = params["external_url"] or params["internal_url"]
|
||||||
|
|
||||||
adapters = await network.async_get_adapters(hass)
|
|
||||||
|
|
||||||
# Puts the default IPv4 address first in the list to preserve compatibility,
|
|
||||||
# because some mDNS implementations ignores anything but the first announced
|
|
||||||
# address.
|
|
||||||
host_ip = await async_get_source_ip(hass, target_ip=MDNS_TARGET_IP)
|
|
||||||
host_ip_pton = None
|
|
||||||
if host_ip:
|
|
||||||
host_ip_pton = socket.inet_pton(socket.AF_INET, host_ip)
|
|
||||||
address_list = _get_announced_addresses(adapters, host_ip_pton)
|
|
||||||
|
|
||||||
_suppress_invalid_properties(params)
|
_suppress_invalid_properties(params)
|
||||||
|
|
||||||
info = AsyncServiceInfo(
|
info = AsyncServiceInfo(
|
||||||
ZEROCONF_TYPE,
|
ZEROCONF_TYPE,
|
||||||
name=f"{valid_location_name}.{ZEROCONF_TYPE}",
|
name=f"{valid_location_name}.{ZEROCONF_TYPE}",
|
||||||
server=f"{uuid}.local.",
|
server=f"{uuid}.local.",
|
||||||
addresses=address_list,
|
parsed_addresses=await network.async_get_announce_addresses(hass),
|
||||||
port=hass.http.server_port,
|
port=hass.http.server_port,
|
||||||
properties=params,
|
properties=params,
|
||||||
)
|
)
|
||||||
|
@ -3,8 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.local_ip import DOMAIN
|
from homeassistant.components.local_ip import DOMAIN
|
||||||
from homeassistant.components.network import async_get_source_ip
|
from homeassistant.components.network import MDNS_TARGET_IP, async_get_source_ip
|
||||||
from homeassistant.components.zeroconf import MDNS_TARGET_IP
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
@ -712,3 +712,120 @@ async def test_async_get_source_ip_no_ip_loopback(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert await network.async_get_source_ip(hass) == "127.0.0.1"
|
assert await network.async_get_source_ip(hass) == "127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
_ADAPTERS_WITH_MANUAL_CONFIG = [
|
||||||
|
{
|
||||||
|
"auto": True,
|
||||||
|
"index": 1,
|
||||||
|
"default": False,
|
||||||
|
"enabled": True,
|
||||||
|
"ipv4": [],
|
||||||
|
"ipv6": [
|
||||||
|
{
|
||||||
|
"address": "2001:db8::",
|
||||||
|
"network_prefix": 64,
|
||||||
|
"flowinfo": 1,
|
||||||
|
"scope_id": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "fe80::1234:5678:9abc:def0",
|
||||||
|
"network_prefix": 64,
|
||||||
|
"flowinfo": 1,
|
||||||
|
"scope_id": 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"name": "eth0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"auto": True,
|
||||||
|
"index": 2,
|
||||||
|
"default": False,
|
||||||
|
"enabled": True,
|
||||||
|
"ipv4": [{"address": "192.168.1.5", "network_prefix": 23}],
|
||||||
|
"ipv6": [],
|
||||||
|
"name": "eth1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"auto": True,
|
||||||
|
"index": 3,
|
||||||
|
"default": False,
|
||||||
|
"enabled": True,
|
||||||
|
"ipv4": [{"address": "172.16.1.5", "network_prefix": 23}],
|
||||||
|
"ipv6": [
|
||||||
|
{
|
||||||
|
"address": "fe80::dead:beef:dead:beef",
|
||||||
|
"network_prefix": 64,
|
||||||
|
"flowinfo": 1,
|
||||||
|
"scope_id": 3,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "eth2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"auto": False,
|
||||||
|
"index": 4,
|
||||||
|
"default": False,
|
||||||
|
"enabled": False,
|
||||||
|
"ipv4": [{"address": "169.254.3.2", "network_prefix": 16}],
|
||||||
|
"ipv6": [],
|
||||||
|
"name": "vtun0",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_get_announce_addresses(hass: HomeAssistant) -> None:
|
||||||
|
"""Test addresses for mDNS/etc announcement."""
|
||||||
|
first_ip = "172.16.1.5"
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.network.async_get_source_ip",
|
||||||
|
return_value=first_ip,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.network.async_get_adapters",
|
||||||
|
return_value=_ADAPTERS_WITH_MANUAL_CONFIG,
|
||||||
|
):
|
||||||
|
actual = await network.async_get_announce_addresses(hass)
|
||||||
|
assert actual[0] == first_ip and actual == [
|
||||||
|
first_ip,
|
||||||
|
"2001:db8::",
|
||||||
|
"fe80::1234:5678:9abc:def0",
|
||||||
|
"192.168.1.5",
|
||||||
|
"fe80::dead:beef:dead:beef",
|
||||||
|
]
|
||||||
|
|
||||||
|
first_ip = "192.168.1.5"
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.network.async_get_source_ip",
|
||||||
|
return_value=first_ip,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.network.async_get_adapters",
|
||||||
|
return_value=_ADAPTERS_WITH_MANUAL_CONFIG,
|
||||||
|
):
|
||||||
|
actual = await network.async_get_announce_addresses(hass)
|
||||||
|
|
||||||
|
assert actual[0] == first_ip and actual == [
|
||||||
|
first_ip,
|
||||||
|
"2001:db8::",
|
||||||
|
"fe80::1234:5678:9abc:def0",
|
||||||
|
"172.16.1.5",
|
||||||
|
"fe80::dead:beef:dead:beef",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_get_announce_addresses_no_source_ip(hass: HomeAssistant) -> None:
|
||||||
|
"""Test addresses for mDNS/etc announcement without source ip."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.network.async_get_source_ip",
|
||||||
|
return_value=None,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.network.async_get_adapters",
|
||||||
|
return_value=_ADAPTERS_WITH_MANUAL_CONFIG,
|
||||||
|
):
|
||||||
|
actual = await network.async_get_announce_addresses(hass)
|
||||||
|
assert actual == [
|
||||||
|
"2001:db8::",
|
||||||
|
"fe80::1234:5678:9abc:def0",
|
||||||
|
"192.168.1.5",
|
||||||
|
"172.16.1.5",
|
||||||
|
"fe80::dead:beef:dead:beef",
|
||||||
|
]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Test Zeroconf component setup process."""
|
"""Test Zeroconf component setup process."""
|
||||||
from ipaddress import ip_address
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import call, patch
|
from unittest.mock import call, patch
|
||||||
|
|
||||||
@ -13,11 +12,7 @@ from zeroconf import (
|
|||||||
from zeroconf.asyncio import AsyncServiceInfo
|
from zeroconf.asyncio import AsyncServiceInfo
|
||||||
|
|
||||||
from homeassistant.components import zeroconf
|
from homeassistant.components import zeroconf
|
||||||
from homeassistant.components.zeroconf import (
|
from homeassistant.components.zeroconf import CONF_DEFAULT_INTERFACE, CONF_IPV6
|
||||||
CONF_DEFAULT_INTERFACE,
|
|
||||||
CONF_IPV6,
|
|
||||||
_get_announced_addresses,
|
|
||||||
)
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_COMPONENT_LOADED,
|
EVENT_COMPONENT_LOADED,
|
||||||
EVENT_HOMEASSISTANT_START,
|
EVENT_HOMEASSISTANT_START,
|
||||||
@ -1202,29 +1197,6 @@ async def test_async_detect_interfaces_setting_empty_route_freebsd(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_get_announced_addresses(
|
|
||||||
hass: HomeAssistant, mock_async_zeroconf: None
|
|
||||||
) -> None:
|
|
||||||
"""Test addresses for mDNS announcement."""
|
|
||||||
expected = {
|
|
||||||
ip_address(ip).packed
|
|
||||||
for ip in [
|
|
||||||
"fe80::1234:5678:9abc:def0",
|
|
||||||
"2001:db8::",
|
|
||||||
"192.168.1.5",
|
|
||||||
"fe80::dead:beef:dead:beef",
|
|
||||||
"172.16.1.5",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
first_ip = ip_address("172.16.1.5").packed
|
|
||||||
actual = _get_announced_addresses(_ADAPTERS_WITH_MANUAL_CONFIG, first_ip)
|
|
||||||
assert actual[0] == first_ip and set(actual) == expected
|
|
||||||
|
|
||||||
first_ip = ip_address("192.168.1.5").packed
|
|
||||||
actual = _get_announced_addresses(_ADAPTERS_WITH_MANUAL_CONFIG, first_ip)
|
|
||||||
assert actual[0] == first_ip and set(actual) == expected
|
|
||||||
|
|
||||||
|
|
||||||
_ADAPTER_WITH_DEFAULT_ENABLED_AND_IPV6 = [
|
_ADAPTER_WITH_DEFAULT_ENABLED_AND_IPV6 = [
|
||||||
{
|
{
|
||||||
"auto": True,
|
"auto": True,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user