def _get_internal_url( hass: HomeAssistant, *, allow_ip: bool = True, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get internal URL of this instance.""" if hass.config.internal_url: internal_url = yarl.URL(hass.config.internal_url) if ( (not require_current_request or internal_url.host == _get_request_host()) and (not require_ssl or internal_url.scheme == "https") and (not require_standard_port or internal_url.is_default_port()) and (allow_ip or not is_ip_address(str(internal_url.host))) ): return normalize_url(str(internal_url)) # Fallback to detected local IP if allow_ip and not ( require_ssl or hass.config.api is None or hass.config.api.use_ssl ): ip_url = yarl.URL.build( scheme="http", host=hass.config.api.local_ip, port=hass.config.api.port ) if ( not is_loopback(ip_address(ip_url.host)) and (not require_current_request or ip_url.host == _get_request_host()) and (not require_standard_port or ip_url.is_default_port()) ): return normalize_url(str(ip_url)) raise NoURLAvailableError
def process_client(self, ip_address, hostname, mac_address): """Process a client.""" made_ip_address = make_ip_address(ip_address) if ( is_link_local(made_ip_address) or is_loopback(made_ip_address) or is_invalid(made_ip_address) ): # Ignore self assigned addresses, loopback, invalid return data = self._address_data.get(ip_address) if ( data and data[MAC_ADDRESS] == mac_address and data[HOSTNAME].startswith(hostname) ): # If the address data is the same no need # to process it return self._address_data[ip_address] = {MAC_ADDRESS: mac_address, HOSTNAME: hostname} self.process_updated_address_data(ip_address, self._address_data[ip_address])
def _get_deprecated_base_url( hass: HomeAssistant, *, internal: bool = False, allow_ip: bool = True, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Work with the deprecated `base_url`, used as fallback.""" if hass.config.api is None or not hass.config.api.deprecated_base_url: raise NoURLAvailableError base_url = yarl.URL(hass.config.api.deprecated_base_url) # Rules that apply to both internal and external if ((allow_ip or not is_ip_address(str(base_url.host))) and (not require_current_request or base_url.host == _get_request_host()) and (not require_ssl or base_url.scheme == "https") and (not require_standard_port or base_url.is_default_port())): # Check to ensure an internal URL if internal and (str(base_url.host).endswith(".local") or (is_ip_address(str(base_url.host)) and not is_loopback(ip_address(base_url.host)) and is_private(ip_address(base_url.host)))): return normalize_url(str(base_url)) # Check to ensure an external URL (a little) if (not internal and not str(base_url.host).endswith(".local") and not (is_ip_address(str(base_url.host)) and is_local(ip_address(str(base_url.host))))): return normalize_url(str(base_url)) raise NoURLAvailableError
def test_is_loopback(): """Test loopback addresses.""" assert network_util.is_loopback(ip_address("127.0.0.2")) assert network_util.is_loopback(ip_address("127.0.0.1")) assert network_util.is_loopback(ip_address("::1")) assert network_util.is_loopback(ip_address("::ffff:127.0.0.0")) assert network_util.is_loopback(ip_address("0:0:0:0:0:0:0:1")) assert network_util.is_loopback(ip_address("0:0:0:0:0:ffff:7f00:1")) assert not network_util.is_loopback(ip_address("104.26.5.238")) assert not network_util.is_loopback(ip_address("2600:1404:400:1a4::356e"))
def host_ip() -> str | None: if hass.config.api is None or is_loopback( ip_address(hass.config.api.local_ip)): return None return str( yarl.URL.build(scheme="http", host=hass.config.api.local_ip, port=hass.config.api.port))
def async_process_client(self, ip_address, hostname, mac_address): """Process a client.""" made_ip_address = make_ip_address(ip_address) if ( is_link_local(made_ip_address) or is_loopback(made_ip_address) or is_invalid(made_ip_address) ): # Ignore self assigned addresses, loopback, invalid return data = self._address_data.get(ip_address) if ( data and data[MAC_ADDRESS] == mac_address and data[HOSTNAME].startswith(hostname) ): # If the address data is the same no need # to process it return data = {MAC_ADDRESS: mac_address, HOSTNAME: hostname} self._address_data[ip_address] = data lowercase_hostname = data[HOSTNAME].lower() uppercase_mac = data[MAC_ADDRESS].upper() _LOGGER.debug( "Processing updated address data for %s: mac=%s hostname=%s", ip_address, uppercase_mac, lowercase_hostname, ) for entry in self._integration_matchers: if MAC_ADDRESS in entry and not fnmatch.fnmatch( uppercase_mac, entry[MAC_ADDRESS] ): continue if HOSTNAME in entry and not fnmatch.fnmatch( lowercase_hostname, entry[HOSTNAME] ): continue _LOGGER.debug("Matched %s against %s", data, entry) discovery_flow.async_create_flow( self.hass, entry["domain"], {"source": config_entries.SOURCE_DHCP}, DhcpServiceInfo( ip=ip_address, hostname=lowercase_hostname, macaddress=data[MAC_ADDRESS], ), )
def async_process_client(self, ip_address: str, hostname: str, mac_address: str) -> None: """Process a client.""" made_ip_address = make_ip_address(ip_address) if (is_link_local(made_ip_address) or is_loopback(made_ip_address) or is_invalid(made_ip_address)): # Ignore self assigned addresses, loopback, invalid return data = self._address_data.get(ip_address) if (data and data[MAC_ADDRESS] == mac_address and data[HOSTNAME].startswith(hostname)): # If the address data is the same no need # to process it return data = {MAC_ADDRESS: mac_address, HOSTNAME: hostname} self._address_data[ip_address] = data lowercase_hostname = hostname.lower() uppercase_mac = mac_address.upper() _LOGGER.debug( "Processing updated address data for %s: mac=%s hostname=%s", ip_address, uppercase_mac, lowercase_hostname, ) matched_domains = set() device_domains = set() dev_reg: DeviceRegistry = async_get(self.hass) if device := dev_reg.async_get_device(identifiers=set(), connections={ (CONNECTION_NETWORK_MAC, uppercase_mac) }): for entry_id in device.config_entries: if entry := self.hass.config_entries.async_get_entry(entry_id): device_domains.add(entry.domain)
def get_url( hass: HomeAssistant, *, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, allow_internal: bool = True, allow_external: bool = True, allow_cloud: bool = True, allow_ip: bool = True, prefer_external: bool = False, prefer_cloud: bool = False, ) -> str: """Get a URL to this instance.""" if require_current_request and http.current_request.get() is None: raise NoURLAvailableError order = [TYPE_URL_INTERNAL, TYPE_URL_EXTERNAL] if prefer_external: order.reverse() # Try finding an URL in the order specified for url_type in order: if allow_internal and url_type == TYPE_URL_INTERNAL: try: return _get_internal_url( hass, allow_ip=allow_ip, require_current_request=require_current_request, require_ssl=require_ssl, require_standard_port=require_standard_port, ) except NoURLAvailableError: pass if allow_external and url_type == TYPE_URL_EXTERNAL: try: return _get_external_url( hass, allow_cloud=allow_cloud, allow_ip=allow_ip, prefer_cloud=prefer_cloud, require_current_request=require_current_request, require_ssl=require_ssl, require_standard_port=require_standard_port, ) except NoURLAvailableError: pass # For current request, we accept loopback interfaces (e.g., 127.0.0.1), # the Supervisor hostname and localhost transparently request_host = _get_request_host() if ( require_current_request and request_host is not None and hass.config.api is not None ): scheme = "https" if hass.config.api.use_ssl else "http" current_url = yarl.URL.build( scheme=scheme, host=request_host, port=hass.config.api.port ) known_hostnames = ["localhost"] if hass.components.hassio.is_hassio(): host_info = hass.components.hassio.get_host_info() known_hostnames.extend( [host_info["hostname"], f"{host_info['hostname']}.local"] ) if ( ( ( allow_ip and is_ip_address(request_host) and is_loopback(ip_address(request_host)) ) or request_host in known_hostnames ) and (not require_ssl or current_url.scheme == "https") and (not require_standard_port or current_url.is_default_port()) ): return normalize_url(str(current_url)) # We have to be honest now, we have no viable option available raise NoURLAvailableError
routes = await hass.async_add_executor_job(_get_ip_route, MDNS_TARGET_IP) except Exception as ex: # pylint: disable=broad-except _LOGGER.debug( "The system could not auto detect routing data on your operating system; Zeroconf will broadcast on all interfaces", exc_info=ex, ) return InterfaceChoice.All if not (first_ip := _first_ip_nexthop_from_route(routes)): _LOGGER.debug( "The system could not auto detect the nexthop for %s on your operating system; Zeroconf will broadcast on all interfaces", MDNS_TARGET_IP, ) return InterfaceChoice.All if is_loopback(ip_address(first_ip)): _LOGGER.debug( "The next hop for %s is %s; Zeroconf will broadcast on all interfaces", MDNS_TARGET_IP, first_ip, ) return InterfaceChoice.All return InterfaceChoice.Default async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up Zeroconf and make Home Assistant discoverable.""" zc_config = config.get(DOMAIN, {}) zc_args: dict = {}