Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The Network Configuration integration."""
2 
3 from __future__ import annotations
4 
5 from ipaddress import IPv4Address, IPv6Address, ip_interface
6 import logging
7 
8 from homeassistant.core import HomeAssistant, callback
9 from homeassistant.exceptions import HomeAssistantError
10 from homeassistant.helpers import config_validation as cv
11 from homeassistant.helpers.typing import UNDEFINED, ConfigType, UndefinedType
12 from homeassistant.loader import bind_hass
13 
14 from . import util
15 from .const import (
16  DOMAIN,
17  IPV4_BROADCAST_ADDR,
18  LOOPBACK_TARGET_IP,
19  MDNS_TARGET_IP,
20  PUBLIC_TARGET_IP,
21 )
22 from .models import Adapter
23 from .network import Network, async_get_network
24 
25 _LOGGER = logging.getLogger(__name__)
26 
27 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
28 
29 
30 @bind_hass
31 async def async_get_adapters(hass: HomeAssistant) -> list[Adapter]:
32  """Get the network adapter configuration."""
33  network: Network = await async_get_network(hass)
34  return network.adapters
35 
36 
37 @bind_hass
39  hass: HomeAssistant, target_ip: str | UndefinedType = UNDEFINED
40 ) -> str:
41  """Get the source ip for a target ip."""
42  adapters = await async_get_adapters(hass)
43  all_ipv4s = []
44  for adapter in adapters:
45  if adapter["enabled"] and (ipv4s := adapter["ipv4"]):
46  all_ipv4s.extend([ipv4["address"] for ipv4 in ipv4s])
47 
48  if target_ip is UNDEFINED:
49  source_ip = (
50  util.async_get_source_ip(PUBLIC_TARGET_IP)
51  or util.async_get_source_ip(MDNS_TARGET_IP)
52  or util.async_get_source_ip(LOOPBACK_TARGET_IP)
53  )
54  else:
55  source_ip = util.async_get_source_ip(target_ip)
56 
57  if not all_ipv4s:
58  _LOGGER.warning(
59  "Because the system does not have any enabled IPv4 addresses, source"
60  " address detection may be inaccurate"
61  )
62  if source_ip is None:
63  raise HomeAssistantError(
64  "Could not determine source ip because the system does not have any"
65  " enabled IPv4 addresses and creating a socket failed"
66  )
67  return source_ip
68 
69  return source_ip if source_ip in all_ipv4s else all_ipv4s[0]
70 
71 
72 @bind_hass
74  hass: HomeAssistant,
75 ) -> list[IPv4Address | IPv6Address]:
76  """Build the list of enabled source ips."""
77  adapters = await async_get_adapters(hass)
78  sources: list[IPv4Address | IPv6Address] = []
79  for adapter in adapters:
80  if not adapter["enabled"]:
81  continue
82  if adapter["ipv4"]:
83  addrs_ipv4 = [IPv4Address(ipv4["address"]) for ipv4 in adapter["ipv4"]]
84  sources.extend(addrs_ipv4)
85  if adapter["ipv6"]:
86  addrs_ipv6 = [
87  IPv6Address(f"{ipv6['address']}%{ipv6['scope_id']}")
88  for ipv6 in adapter["ipv6"]
89  ]
90  sources.extend(addrs_ipv6)
91 
92  return sources
93 
94 
95 @callback
96 def async_only_default_interface_enabled(adapters: list[Adapter]) -> bool:
97  """Check to see if any non-default adapter is enabled."""
98  return not any(
99  adapter["enabled"] and not adapter["default"] for adapter in adapters
100  )
101 
102 
103 @bind_hass
104 async def async_get_ipv4_broadcast_addresses(hass: HomeAssistant) -> set[IPv4Address]:
105  """Return a set of broadcast addresses."""
106  broadcast_addresses: set[IPv4Address] = {IPv4Address(IPV4_BROADCAST_ADDR)}
107  adapters = await async_get_adapters(hass)
109  return broadcast_addresses
110  for adapter in adapters:
111  if not adapter["enabled"]:
112  continue
113  for ip_info in adapter["ipv4"]:
114  interface = ip_interface(
115  f"{ip_info['address']}/{ip_info['network_prefix']}"
116  )
117  broadcast_addresses.add(
118  IPv4Address(interface.network.broadcast_address.exploded)
119  )
120  return broadcast_addresses
121 
122 
123 async def async_get_announce_addresses(hass: HomeAssistant) -> list[str]:
124  """Return a list of IP addresses to announce/use via zeroconf/ssdp/etc.
125 
126  The default ip address is always returned first if available.
127  """
128  adapters = await async_get_adapters(hass)
129  addresses: list[str] = []
130  default_ip: str | None = None
131  for adapter in adapters:
132  if not adapter["enabled"]:
133  continue
134  addresses.extend(str(IPv4Address(ips["address"])) for ips in adapter["ipv4"])
135  addresses.extend(str(IPv6Address(ips["address"])) for ips in adapter["ipv6"])
136 
137  # Puts the default IPv4 address first in the list to preserve compatibility,
138  # because some mDNS implementations ignores anything but the first announced
139  # address.
140  if default_ip := await async_get_source_ip(hass, target_ip=MDNS_TARGET_IP):
141  if default_ip in addresses:
142  addresses.remove(default_ip)
143  return [default_ip, *addresses]
144  return list(addresses)
145 
146 
147 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
148  """Set up network for Home Assistant."""
149  # Avoid circular issue: http->network->websocket_api->http
150  from .websocket import ( # pylint: disable=import-outside-toplevel
151  async_register_websocket_commands,
152  )
153 
155  return True
Network async_get_network(HomeAssistant hass)
Definition: network.py:27
None async_register_websocket_commands(HomeAssistant hass)
Definition: websocket.py:20
bool async_only_default_interface_enabled(list[Adapter] adapters)
Definition: __init__.py:96
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:147
set[IPv4Address] async_get_ipv4_broadcast_addresses(HomeAssistant hass)
Definition: __init__.py:104
list[IPv4Address|IPv6Address] async_get_enabled_source_ips(HomeAssistant hass)
Definition: __init__.py:75
list[Adapter] async_get_adapters(HomeAssistant hass)
Definition: __init__.py:31
list[str] async_get_announce_addresses(HomeAssistant hass)
Definition: __init__.py:123
str async_get_source_ip(HomeAssistant hass, str|UndefinedType target_ip=UNDEFINED)
Definition: __init__.py:40