Provide workaround for missing/disabled/broken IPv6 (#37887)

pull/37888/head
J. Nick Koston 2020-07-15 08:26:40 -10:00 committed by GitHub
parent 53d6f4948e
commit abe3e3094e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 59 additions and 7 deletions

View File

@ -9,6 +9,7 @@ from zeroconf import (
DNSPointer, DNSPointer,
DNSRecord, DNSRecord,
InterfaceChoice, InterfaceChoice,
IPVersion,
NonUniqueNameException, NonUniqueNameException,
ServiceBrowser, ServiceBrowser,
ServiceInfo, ServiceInfo,
@ -42,7 +43,9 @@ ZEROCONF_TYPE = "_home-assistant._tcp.local."
HOMEKIT_TYPE = "_hap._tcp.local." HOMEKIT_TYPE = "_hap._tcp.local."
CONF_DEFAULT_INTERFACE = "default_interface" CONF_DEFAULT_INTERFACE = "default_interface"
CONF_IPV6 = "ipv6"
DEFAULT_DEFAULT_INTERFACE = False DEFAULT_DEFAULT_INTERFACE = False
DEFAULT_IPV6 = True
HOMEKIT_PROPERTIES = "properties" HOMEKIT_PROPERTIES = "properties"
HOMEKIT_PAIRED_STATUS_FLAG = "sf" HOMEKIT_PAIRED_STATUS_FLAG = "sf"
@ -54,7 +57,8 @@ CONFIG_SCHEMA = vol.Schema(
{ {
vol.Optional( vol.Optional(
CONF_DEFAULT_INTERFACE, default=DEFAULT_DEFAULT_INTERFACE 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) 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.""" """Create an instance."""
logging.getLogger("zeroconf").setLevel(logging.NOTSET) 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(_): def stop_zeroconf(_):
"""Stop Zeroconf.""" """Stop Zeroconf."""
@ -115,8 +125,13 @@ class HaZeroconf(Zeroconf):
def setup(hass, config): def setup(hass, config):
"""Set up Zeroconf and make Home Assistant discoverable.""" """Set up Zeroconf and make Home Assistant discoverable."""
zc_config = config.get(DOMAIN, {})
zeroconf = hass.data[DOMAIN] = _get_instance( 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 # Get instance UUID

View File

@ -1,9 +1,9 @@
"""Test Zeroconf component setup process.""" """Test Zeroconf component setup process."""
import pytest import pytest
from zeroconf import InterfaceChoice, ServiceInfo, ServiceStateChange from zeroconf import InterfaceChoice, IPVersion, ServiceInfo, ServiceStateChange
from homeassistant.components import zeroconf 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.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.generated import zeroconf as zc_gen from homeassistant.generated import zeroconf as zc_gen
from homeassistant.setup import async_setup_component 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() 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): async def test_homekit_match_partial_space(hass, mock_zeroconf):
"""Test configured options for a device are loaded via config entry.""" """Test configured options for a device are loaded via config entry."""
with patch.dict( with patch.dict(