diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index f0de561c76a..d33c6159888 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -7,6 +7,7 @@ import logging import os import threading +from scapy.arch.common import compile_filter from scapy.config import conf from scapy.error import Scapy_Exception from scapy.layers.dhcp import DHCP @@ -217,6 +218,15 @@ class DHCPWatcher(WatcherBase): ) return + try: + await _async_verify_working_pcap(self.hass, FILTER) + except (Scapy_Exception, ImportError) as ex: + _LOGGER.error( + "Cannot watch for dhcp packets without a functional packet filter: %s", + ex, + ) + return + self._sniffer = AsyncSniffer( filter=FILTER, started_callback=self._started.set, @@ -285,3 +295,12 @@ def _verify_l2socket_creation_permission(): # disable scapy promiscuous mode as we do not need it conf.sniff_promisc = 0 conf.L2socket() + + +async def _async_verify_working_pcap(hass, cap_filter): + """Verify we can create a packet filter. + + If we cannot create a filter we will be listening for + all traffic which is too intensive. + """ + await hass.async_add_executor_job(compile_filter, cap_filter) diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index 049128248a7..fc24c8201e2 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -280,7 +280,11 @@ async def test_setup_and_stop(hass): ) await hass.async_block_till_done() - with patch("homeassistant.components.dhcp.AsyncSniffer.start") as start_call: + with patch("homeassistant.components.dhcp.AsyncSniffer.start") as start_call, patch( + "homeassistant.components.dhcp._verify_l2socket_creation_permission", + ), patch( + "homeassistant.components.dhcp.compile_filter", + ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -325,21 +329,49 @@ async def test_setup_fails_non_root(hass, caplog): ) await hass.async_block_till_done() - wait_event = threading.Event() - with patch("os.geteuid", return_value=10), patch( "homeassistant.components.dhcp._verify_l2socket_creation_permission", side_effect=Scapy_Exception, ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - wait_event.set() assert "Cannot watch for dhcp packets without root or CAP_NET_RAW" in caplog.text +async def test_setup_fails_with_broken_libpcap(hass, caplog): + """Test we abort if libpcap is missing or broken.""" + + assert await async_setup_component( + hass, + dhcp.DOMAIN, + {}, + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.dhcp._verify_l2socket_creation_permission", + ), patch( + "homeassistant.components.dhcp.compile_filter", + side_effect=ImportError, + ) as compile_filter, patch( + "homeassistant.components.dhcp.AsyncSniffer", + ) as async_sniffer: + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + + assert compile_filter.called + assert not async_sniffer.called + assert ( + "Cannot watch for dhcp packets without a functional packet filter" + in caplog.text + ) + + async def test_device_tracker_hostname_and_macaddress_exists_before_start(hass): """Test matching based on hostname and macaddress before start.""" hass.states.async_set(