diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 226edf7d2c2..7534ad39541 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -9,6 +9,7 @@ from zeroconf import ( DNSPointer, DNSRecord, InterfaceChoice, + IPVersion, NonUniqueNameException, ServiceBrowser, ServiceInfo, @@ -42,7 +43,9 @@ ZEROCONF_TYPE = "_home-assistant._tcp.local." HOMEKIT_TYPE = "_hap._tcp.local." CONF_DEFAULT_INTERFACE = "default_interface" +CONF_IPV6 = "ipv6" DEFAULT_DEFAULT_INTERFACE = False +DEFAULT_IPV6 = True HOMEKIT_PROPERTIES = "properties" HOMEKIT_PAIRED_STATUS_FLAG = "sf" @@ -54,7 +57,8 @@ CONFIG_SCHEMA = vol.Schema( { vol.Optional( CONF_DEFAULT_INTERFACE, default=DEFAULT_DEFAULT_INTERFACE - ): cv.boolean + ): cv.boolean, + vol.Optional(CONF_IPV6, default=DEFAULT_IPV6): cv.boolean, } ) }, @@ -68,11 +72,17 @@ async def async_get_instance(hass): return await hass.async_add_executor_job(_get_instance, hass) -def _get_instance(hass, default_interface=False): +def _get_instance(hass, default_interface=False, ipv6=True): """Create an instance.""" logging.getLogger("zeroconf").setLevel(logging.NOTSET) - args = [InterfaceChoice.Default] if default_interface else [] - zeroconf = HaZeroconf(*args) + + zc_args = {} + if default_interface: + zc_args["interfaces"] = InterfaceChoice.Default + if not ipv6: + zc_args["ip_version"] = IPVersion.V4Only + + zeroconf = HaZeroconf(**zc_args) def stop_zeroconf(_): """Stop Zeroconf.""" @@ -115,8 +125,13 @@ class HaZeroconf(Zeroconf): def setup(hass, config): """Set up Zeroconf and make Home Assistant discoverable.""" + zc_config = config.get(DOMAIN, {}) zeroconf = hass.data[DOMAIN] = _get_instance( - hass, config.get(DOMAIN, {}).get(CONF_DEFAULT_INTERFACE) + hass, + default_interface=zc_config.get( + CONF_DEFAULT_INTERFACE, DEFAULT_DEFAULT_INTERFACE + ), + ipv6=zc_config.get(CONF_IPV6, DEFAULT_IPV6), ) # Get instance UUID diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index dcde788e050..412e1f5f3f5 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -1,9 +1,9 @@ """Test Zeroconf component setup process.""" import pytest -from zeroconf import InterfaceChoice, ServiceInfo, ServiceStateChange +from zeroconf import InterfaceChoice, IPVersion, ServiceInfo, ServiceStateChange from homeassistant.components import zeroconf -from homeassistant.components.zeroconf import CONF_DEFAULT_INTERFACE +from homeassistant.components.zeroconf import CONF_DEFAULT_INTERFACE, CONF_IPV6 from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.generated import zeroconf as zc_gen from homeassistant.setup import async_setup_component @@ -114,6 +114,43 @@ async def test_setup_without_default_interface(hass, mock_zeroconf): assert mock_zeroconf.called_with() +async def test_setup_without_ipv6(hass, mock_zeroconf): + """Test without ipv6.""" + with patch.object(hass.config_entries.flow, "async_init"), patch.object( + zeroconf, "HaServiceBrowser", side_effect=service_update_mock + ): + mock_zeroconf.get_service_info.side_effect = get_service_info_mock + assert await async_setup_component( + hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {CONF_IPV6: False}} + ) + + assert mock_zeroconf.called_with(ip_version=IPVersion.V4Only) + + +async def test_setup_with_ipv6(hass, mock_zeroconf): + """Test without ipv6.""" + with patch.object(hass.config_entries.flow, "async_init"), patch.object( + zeroconf, "HaServiceBrowser", side_effect=service_update_mock + ): + mock_zeroconf.get_service_info.side_effect = get_service_info_mock + assert await async_setup_component( + hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {CONF_IPV6: True}} + ) + + assert mock_zeroconf.called_with() + + +async def test_setup_with_ipv6_default(hass, mock_zeroconf): + """Test without ipv6 as default.""" + with patch.object(hass.config_entries.flow, "async_init"), patch.object( + zeroconf, "HaServiceBrowser", side_effect=service_update_mock + ): + mock_zeroconf.get_service_info.side_effect = get_service_info_mock + assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) + + assert mock_zeroconf.called_with() + + async def test_homekit_match_partial_space(hass, mock_zeroconf): """Test configured options for a device are loaded via config entry.""" with patch.dict(