Make DHCP discovery aware of the network integration (#144767)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
David Rapan
2025-05-14 07:59:48 +02:00
committed by GitHub
parent 31847d8cfb
commit ab5d60e33d
5 changed files with 78 additions and 2 deletions

View File

@ -7,6 +7,7 @@ from collections.abc import Callable
from datetime import timedelta
from fnmatch import translate
from functools import lru_cache, partial
from ipaddress import IPv4Address
import itertools
import logging
import re
@ -22,6 +23,7 @@ from aiodiscover.discovery import (
from cached_ipaddress import cached_ip_addresses
from homeassistant import config_entries
from homeassistant.components import network
from homeassistant.components.device_tracker import (
ATTR_HOST_NAME,
ATTR_IP,
@ -421,9 +423,33 @@ class DHCPWatcher(WatcherBase):
response.ip_address, response.hostname, response.mac_address
)
async def async_get_adapter_indexes(self) -> list[int] | None:
"""Get the adapter indexes."""
adapters = await network.async_get_adapters(self.hass)
if network.async_only_default_interface_enabled(adapters):
return None
return [
adapter["index"]
for adapter in adapters
if (
adapter["enabled"]
and adapter["index"] is not None
and adapter["ipv4"]
and (
addresses := [IPv4Address(ip["address"]) for ip in adapter["ipv4"]]
)
and any(
ip for ip in addresses if not ip.is_loopback and not ip.is_global
)
)
]
async def async_start(self) -> None:
"""Start watching for dhcp packets."""
self._unsub = await aiodhcpwatcher.async_start(self._async_process_dhcp_request)
self._unsub = await aiodhcpwatcher.async_start(
self._async_process_dhcp_request,
await self.async_get_adapter_indexes(),
)
class RediscoveryWatcher(WatcherBase):

View File

@ -2,6 +2,7 @@
"domain": "dhcp",
"name": "DHCP Discovery",
"codeowners": ["@bdraco"],
"dependencies": ["network"],
"documentation": "https://www.home-assistant.io/integrations/dhcp",
"integration_type": "system",
"iot_class": "local_push",

View File

@ -157,6 +157,7 @@ async def _async_get_handle_dhcp_packet(
hass,
DHCPData(integration_matchers, set(), address_data),
)
with patch("aiodhcpwatcher.async_start"):
await dhcp_watcher.async_start()
@ -171,6 +172,53 @@ async def _async_get_handle_dhcp_packet(
return cast("Callable[[Any], Awaitable[None]]", _async_handle_dhcp_packet)
async def test_dhcp_start_using_multiple_interfaces(
hass: HomeAssistant,
) -> None:
"""Test start using multiple interfaces."""
def _generate_mock_adapters():
return [
{
"index": 1,
"auto": False,
"default": False,
"enabled": True,
"ipv4": [{"address": "192.168.0.1", "network_prefix": 24}],
"ipv6": [],
"name": "eth0",
},
{
"index": 2,
"auto": True,
"default": True,
"enabled": True,
"ipv4": [{"address": "192.168.1.1", "network_prefix": 24}],
"ipv6": [],
"name": "eth1",
},
]
integration_matchers = dhcp.async_index_integration_matchers(
[{"domain": "mock-domain", "hostname": "connect", "macaddress": "B8B7F1*"}]
)
dhcp_watcher = dhcp.DHCPWatcher(
hass,
DHCPData(integration_matchers, set(), {}),
)
with (
patch("aiodhcpwatcher.async_start") as mock_start,
patch(
"homeassistant.components.dhcp.network.async_get_adapters",
return_value=_generate_mock_adapters(),
),
):
await dhcp_watcher.async_start()
mock_start.assert_called_with(dhcp_watcher._async_process_dhcp_request, [1, 2])
async def test_dhcp_match_hostname_and_macaddress(hass: HomeAssistant) -> None:
"""Test matching based on hostname and macaddress."""
integration_matchers = dhcp.async_index_integration_matchers(

View File

@ -22,6 +22,7 @@ async def test_subscribe_discovery(
async def mock_start(
callback: Callable[[aiodhcpwatcher.DHCPRequest], None],
if_indexes: list[int] | None = None,
) -> None:
"""Mock start."""
nonlocal saved_callback

View File

@ -655,5 +655,5 @@ async def test_discovery_requirements_dhcp(hass: HomeAssistant) -> None:
) as mock_process:
await async_get_integration_with_requirements(hass, "comp")
assert len(mock_process.mock_calls) == 1 # dhcp does not depend on http
assert len(mock_process.mock_calls) == 2 # dhcp does not depend on http
assert mock_process.mock_calls[0][1][1] == dhcp.requirements