Home Assistant Unofficial Reference 2024.12.1
network.py
Go to the documentation of this file.
1 """Network utilities."""
2 
3 from __future__ import annotations
4 
5 from ipaddress import IPv4Address, IPv6Address, ip_address, ip_network
6 import re
7 
8 import yarl
9 
10 # RFC6890 - IP addresses of loopback interfaces
11 IPV6_IPV4_LOOPBACK = ip_network("::ffff:127.0.0.0/104")
12 
13 LOOPBACK_NETWORKS = (
14  ip_network("127.0.0.0/8"),
15  ip_network("::1/128"),
16  IPV6_IPV4_LOOPBACK,
17 )
18 
19 # RFC6890 - Address allocation for Private Internets
20 PRIVATE_NETWORKS = (
21  ip_network("10.0.0.0/8"),
22  ip_network("172.16.0.0/12"),
23  ip_network("192.168.0.0/16"),
24  ip_network("fd00::/8"),
25  ip_network("::ffff:10.0.0.0/104"),
26  ip_network("::ffff:172.16.0.0/108"),
27  ip_network("::ffff:192.168.0.0/112"),
28 )
29 
30 # RFC6890 - Link local ranges
31 LINK_LOCAL_NETWORKS = (
32  ip_network("169.254.0.0/16"),
33  ip_network("fe80::/10"),
34  ip_network("::ffff:169.254.0.0/112"),
35 )
36 
37 
38 def is_loopback(address: IPv4Address | IPv6Address) -> bool:
39  """Check if an address is a loopback address."""
40  return address.is_loopback or address in IPV6_IPV4_LOOPBACK
41 
42 
43 def is_private(address: IPv4Address | IPv6Address) -> bool:
44  """Check if an address is a unique local non-loopback address."""
45  return any(address in network for network in PRIVATE_NETWORKS)
46 
47 
48 def is_link_local(address: IPv4Address | IPv6Address) -> bool:
49  """Check if an address is link-local (local but not necessarily unique)."""
50  return address.is_link_local
51 
52 
53 def is_local(address: IPv4Address | IPv6Address) -> bool:
54  """Check if an address is on a local network."""
55  return is_loopback(address) or is_private(address) or is_link_local(address)
56 
57 
58 def is_invalid(address: IPv4Address | IPv6Address) -> bool:
59  """Check if an address is invalid."""
60  return address.is_unspecified
61 
62 
63 def is_ip_address(address: str) -> bool:
64  """Check if a given string is an IP address."""
65  try:
66  ip_address(address)
67  except ValueError:
68  return False
69 
70  return True
71 
72 
73 def is_ipv4_address(address: str) -> bool:
74  """Check if a given string is an IPv4 address."""
75  try:
76  IPv4Address(address)
77  except ValueError:
78  return False
79 
80  return True
81 
82 
83 def is_ipv6_address(address: str) -> bool:
84  """Check if a given string is an IPv6 address."""
85  try:
86  IPv6Address(address)
87  except ValueError:
88  return False
89 
90  return True
91 
92 
93 def is_host_valid(host: str) -> bool:
94  """Check if a given string is an IP address or valid hostname."""
95  if is_ip_address(host):
96  return True
97  if len(host) > 255:
98  return False
99  if re.match(r"^[0-9\.]+$", host): # reject invalid IPv4
100  return False
101  if host.endswith("."): # dot at the end is correct
102  host = host[:-1]
103  allowed = re.compile(r"(?!-)[A-Z\d\-]{1,63}(?<!-)$", re.IGNORECASE)
104  return all(allowed.match(x) for x in host.split("."))
105 
106 
107 def normalize_url(address: str) -> str:
108  """Normalize a given URL."""
109  url = yarl.URL(address.rstrip("/"))
110  if url.is_absolute() and url.is_default_port():
111  return str(url.with_port(None))
112  return str(url)
bool is_loopback(IPv4Address|IPv6Address address)
Definition: network.py:38
bool is_host_valid(str host)
Definition: network.py:93
bool is_private(IPv4Address|IPv6Address address)
Definition: network.py:43
str normalize_url(str address)
Definition: network.py:107
bool is_ipv6_address(str address)
Definition: network.py:83
bool is_link_local(IPv4Address|IPv6Address address)
Definition: network.py:48
bool is_ip_address(str address)
Definition: network.py:63
bool is_local(IPv4Address|IPv6Address address)
Definition: network.py:53
bool is_ipv4_address(str address)
Definition: network.py:73
bool is_invalid(IPv4Address|IPv6Address address)
Definition: network.py:58