Home Assistant Unofficial Reference 2024.12.1
util.py
Go to the documentation of this file.
1 """Network helper class for the network integration."""
2 
3 from __future__ import annotations
4 
5 from ipaddress import IPv4Address, IPv6Address, ip_address
6 import logging
7 import socket
8 from typing import cast
9 
10 import ifaddr
11 
12 from homeassistant.core import callback
13 
14 from .const import MDNS_TARGET_IP
15 from .models import Adapter, IPv4ConfiguredAddress, IPv6ConfiguredAddress
16 
17 _LOGGER = logging.getLogger(__name__)
18 
19 
20 async def async_load_adapters() -> list[Adapter]:
21  """Load adapters."""
22  source_ip = async_get_source_ip(MDNS_TARGET_IP)
23  source_ip_address = ip_address(source_ip) if source_ip else None
24 
25  ha_adapters: list[Adapter] = [
26  _ifaddr_adapter_to_ha(adapter, source_ip_address)
27  for adapter in ifaddr.get_adapters()
28  ]
29 
30  if not any(adapter["default"] and adapter["auto"] for adapter in ha_adapters):
31  for adapter in ha_adapters:
33  adapter["auto"] = True
34 
35  return ha_adapters
36 
37 
38 def enable_adapters(adapters: list[Adapter], enabled_interfaces: list[str]) -> bool:
39  """Enable configured adapters."""
40  _reset_enabled_adapters(adapters)
41 
42  if not enabled_interfaces:
43  return False
44 
45  found_adapter = False
46  for adapter in adapters:
47  if adapter["name"] in enabled_interfaces:
48  adapter["enabled"] = True
49  found_adapter = True
50 
51  return found_adapter
52 
53 
54 def enable_auto_detected_adapters(adapters: list[Adapter]) -> None:
55  """Enable auto detected adapters."""
57  adapters, [adapter["name"] for adapter in adapters if adapter["auto"]]
58  )
59 
60 
61 def _adapter_has_external_address(adapter: Adapter) -> bool:
62  """Adapter has a non-loopback and non-link-local address."""
63  return any(
64  _has_external_address(v4_config["address"]) for v4_config in adapter["ipv4"]
65  ) or any(
66  _has_external_address(v6_config["address"]) for v6_config in adapter["ipv6"]
67  )
68 
69 
70 def _has_external_address(ip_str: str) -> bool:
71  return _ip_address_is_external(ip_address(ip_str))
72 
73 
74 def _ip_address_is_external(ip_addr: IPv4Address | IPv6Address) -> bool:
75  return (
76  not ip_addr.is_multicast
77  and not ip_addr.is_loopback
78  and not ip_addr.is_link_local
79  )
80 
81 
82 def _reset_enabled_adapters(adapters: list[Adapter]) -> None:
83  for adapter in adapters:
84  adapter["enabled"] = False
85 
86 
88  adapter: ifaddr.Adapter, next_hop_address: IPv4Address | IPv6Address | None
89 ) -> Adapter:
90  """Convert an ifaddr adapter to ha."""
91  ip_v4s: list[IPv4ConfiguredAddress] = []
92  ip_v6s: list[IPv6ConfiguredAddress] = []
93  default = False
94  auto = False
95 
96  for ip_config in adapter.ips:
97  if ip_config.is_IPv6:
98  ip_addr = ip_address(ip_config.ip[0])
99  ip_v6s.append(_ip_v6_from_adapter(ip_config))
100  else:
101  assert not isinstance(ip_config.ip, tuple)
102  ip_addr = ip_address(ip_config.ip)
103  ip_v4s.append(_ip_v4_from_adapter(ip_config))
104 
105  if ip_addr == next_hop_address:
106  default = True
107  if _ip_address_is_external(ip_addr):
108  auto = True
109 
110  return {
111  "name": adapter.nice_name,
112  "index": adapter.index,
113  "enabled": False,
114  "auto": auto,
115  "default": default,
116  "ipv4": ip_v4s,
117  "ipv6": ip_v6s,
118  }
119 
120 
121 def _ip_v6_from_adapter(ip_config: ifaddr.IP) -> IPv6ConfiguredAddress:
122  assert isinstance(ip_config.ip, tuple)
123  return {
124  "address": ip_config.ip[0],
125  "flowinfo": ip_config.ip[1],
126  "scope_id": ip_config.ip[2],
127  "network_prefix": ip_config.network_prefix,
128  }
129 
130 
131 def _ip_v4_from_adapter(ip_config: ifaddr.IP) -> IPv4ConfiguredAddress:
132  assert not isinstance(ip_config.ip, tuple)
133  return {
134  "address": ip_config.ip,
135  "network_prefix": ip_config.network_prefix,
136  }
137 
138 
139 @callback
140 def async_get_source_ip(target_ip: str) -> str | None:
141  """Return the source ip that will reach target_ip."""
142  test_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
143  test_sock.setblocking(False) # must be non-blocking for async
144  try:
145  test_sock.connect((target_ip, 1))
146  return cast(str, test_sock.getsockname()[0])
147  except Exception: # noqa: BLE001
148  _LOGGER.debug(
149  (
150  "The system could not auto detect the source ip for %s on your"
151  " operating system"
152  ),
153  target_ip,
154  )
155  return None
156  finally:
157  test_sock.close()
list[Adapter] async_load_adapters()
Definition: util.py:20
None _reset_enabled_adapters(list[Adapter] adapters)
Definition: util.py:82
bool _adapter_has_external_address(Adapter adapter)
Definition: util.py:61
str|None async_get_source_ip(str target_ip)
Definition: util.py:140
IPv4ConfiguredAddress _ip_v4_from_adapter(ifaddr.IP ip_config)
Definition: util.py:131
bool enable_adapters(list[Adapter] adapters, list[str] enabled_interfaces)
Definition: util.py:38
bool _has_external_address(str ip_str)
Definition: util.py:70
Adapter _ifaddr_adapter_to_ha(ifaddr.Adapter adapter, IPv4Address|IPv6Address|None next_hop_address)
Definition: util.py:89
None enable_auto_detected_adapters(list[Adapter] adapters)
Definition: util.py:54
bool _ip_address_is_external(IPv4Address|IPv6Address ip_addr)
Definition: util.py:74
IPv6ConfiguredAddress _ip_v6_from_adapter(ifaddr.IP ip_config)
Definition: util.py:121