Skip to content

Commit ab5d60e

Browse files
davidrapanbdraco
andauthored
Make DHCP discovery aware of the network integration (#144767)
Co-authored-by: J. Nick Koston <nick@koston.org>
1 parent 31847d8 commit ab5d60e

5 files changed

Lines changed: 78 additions & 2 deletions

File tree

homeassistant/components/dhcp/__init__.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from datetime import timedelta
88
from fnmatch import translate
99
from functools import lru_cache, partial
10+
from ipaddress import IPv4Address
1011
import itertools
1112
import logging
1213
import re
@@ -22,6 +23,7 @@
2223
from cached_ipaddress import cached_ip_addresses
2324

2425
from homeassistant import config_entries
26+
from homeassistant.components import network
2527
from homeassistant.components.device_tracker import (
2628
ATTR_HOST_NAME,
2729
ATTR_IP,
@@ -421,9 +423,33 @@ def _async_process_dhcp_request(self, response: aiodhcpwatcher.DHCPRequest) -> N
421423
response.ip_address, response.hostname, response.mac_address
422424
)
423425

426+
async def async_get_adapter_indexes(self) -> list[int] | None:
427+
"""Get the adapter indexes."""
428+
adapters = await network.async_get_adapters(self.hass)
429+
if network.async_only_default_interface_enabled(adapters):
430+
return None
431+
return [
432+
adapter["index"]
433+
for adapter in adapters
434+
if (
435+
adapter["enabled"]
436+
and adapter["index"] is not None
437+
and adapter["ipv4"]
438+
and (
439+
addresses := [IPv4Address(ip["address"]) for ip in adapter["ipv4"]]
440+
)
441+
and any(
442+
ip for ip in addresses if not ip.is_loopback and not ip.is_global
443+
)
444+
)
445+
]
446+
424447
async def async_start(self) -> None:
425448
"""Start watching for dhcp packets."""
426-
self._unsub = await aiodhcpwatcher.async_start(self._async_process_dhcp_request)
449+
self._unsub = await aiodhcpwatcher.async_start(
450+
self._async_process_dhcp_request,
451+
await self.async_get_adapter_indexes(),
452+
)
427453

428454

429455
class RediscoveryWatcher(WatcherBase):

homeassistant/components/dhcp/manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"domain": "dhcp",
33
"name": "DHCP Discovery",
44
"codeowners": ["@bdraco"],
5+
"dependencies": ["network"],
56
"documentation": "https://www.home-assistant.io/integrations/dhcp",
67
"integration_type": "system",
78
"iot_class": "local_push",

tests/components/dhcp/test_init.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ async def _async_get_handle_dhcp_packet(
157157
hass,
158158
DHCPData(integration_matchers, set(), address_data),
159159
)
160+
160161
with patch("aiodhcpwatcher.async_start"):
161162
await dhcp_watcher.async_start()
162163

@@ -171,6 +172,53 @@ async def _async_handle_dhcp_packet(packet):
171172
return cast("Callable[[Any], Awaitable[None]]", _async_handle_dhcp_packet)
172173

173174

175+
async def test_dhcp_start_using_multiple_interfaces(
176+
hass: HomeAssistant,
177+
) -> None:
178+
"""Test start using multiple interfaces."""
179+
180+
def _generate_mock_adapters():
181+
return [
182+
{
183+
"index": 1,
184+
"auto": False,
185+
"default": False,
186+
"enabled": True,
187+
"ipv4": [{"address": "192.168.0.1", "network_prefix": 24}],
188+
"ipv6": [],
189+
"name": "eth0",
190+
},
191+
{
192+
"index": 2,
193+
"auto": True,
194+
"default": True,
195+
"enabled": True,
196+
"ipv4": [{"address": "192.168.1.1", "network_prefix": 24}],
197+
"ipv6": [],
198+
"name": "eth1",
199+
},
200+
]
201+
202+
integration_matchers = dhcp.async_index_integration_matchers(
203+
[{"domain": "mock-domain", "hostname": "connect", "macaddress": "B8B7F1*"}]
204+
)
205+
dhcp_watcher = dhcp.DHCPWatcher(
206+
hass,
207+
DHCPData(integration_matchers, set(), {}),
208+
)
209+
210+
with (
211+
patch("aiodhcpwatcher.async_start") as mock_start,
212+
patch(
213+
"homeassistant.components.dhcp.network.async_get_adapters",
214+
return_value=_generate_mock_adapters(),
215+
),
216+
):
217+
await dhcp_watcher.async_start()
218+
219+
mock_start.assert_called_with(dhcp_watcher._async_process_dhcp_request, [1, 2])
220+
221+
174222
async def test_dhcp_match_hostname_and_macaddress(hass: HomeAssistant) -> None:
175223
"""Test matching based on hostname and macaddress."""
176224
integration_matchers = dhcp.async_index_integration_matchers(

tests/components/dhcp/test_websocket_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ async def test_subscribe_discovery(
2222

2323
async def mock_start(
2424
callback: Callable[[aiodhcpwatcher.DHCPRequest], None],
25+
if_indexes: list[int] | None = None,
2526
) -> None:
2627
"""Mock start."""
2728
nonlocal saved_callback

tests/test_requirements.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,5 +655,5 @@ async def test_discovery_requirements_dhcp(hass: HomeAssistant) -> None:
655655
) as mock_process:
656656
await async_get_integration_with_requirements(hass, "comp")
657657

658-
assert len(mock_process.mock_calls) == 1 # dhcp does not depend on http
658+
assert len(mock_process.mock_calls) == 2 # dhcp does not depend on http
659659
assert mock_process.mock_calls[0][1][1] == dhcp.requirements

0 commit comments

Comments
 (0)