1 """Diagnostics support for Thread networks.
3 When triaging Matter and HomeKit issues you often need to check for problems with the Thread network.
5 This report helps spot and rule out:
7 * Is the users border router visible at all?
8 * Is the border router actually announcing any routes? The user could have a network boundary like
9 VLANs or WiFi isolation that is blocking the RA packets.
10 * Alternatively, if user isn't on HAOS they could have accept_ra_rt_info_max_plen set incorrectly.
11 * Are there any bogus routes that could be interfering. If routes don't expire they can build up.
12 When you have 10 routes and only 2 border routers something has gone wrong.
14 This does not do any connectivity checks. So user could have all their border routers visible, but
15 some of their thread accessories can't be pinged, but it's still a thread problem.
18 from __future__
import annotations
20 from typing
import TYPE_CHECKING, Any, TypedDict
22 from python_otbr_api.tlv_parser
import MeshcopTLVType
28 from .dataset_store
import async_get_store
29 from .discovery
import async_read_zeroconf_cache
32 from pyroute2
import NDB
36 """A neighbour cache entry (ip neigh)."""
44 """A route table entry (ip -6 route)."""
52 """A border router."""
56 neighbours: dict[str, Neighbour]
57 thread_version: str |
None
60 routes: dict[str, Route]
64 """A thread network."""
67 routers: dict[str, Router]
69 unexpected_routers: set[str]
74 ) -> tuple[dict[str, dict[str, Route]], dict[str, set[str]]]:
78 routes: dict[str, dict[str, Route]] = {}
79 reverse_routes: dict[str, set[str]] = {}
81 for record
in ndb.routes:
83 if record.family != 10:
86 if record.dst_len != 64:
89 if not record.gateway
and not record.nh_gateway:
91 gateway = record.gateway
or record.nh_gateway
92 route = routes.setdefault(gateway, {})
94 "metrics": record.metrics,
95 "priority": record.priority,
98 "is_nexthop": record.nh_gateway
is not None,
100 reverse_routes.setdefault(record.dst, set()).
add(gateway)
101 return routes, reverse_routes
106 neighbours: dict[str, Neighbour] = {
108 "lladdr": record.lladdr,
109 "state": record.state,
110 "probes": record.probes,
112 for record
in ndb.neighbours
118 """Get the routes and neighbours from pyroute2."""
120 from pyroute2
import (
128 return routes, reverse_routes, neighbours
132 hass: HomeAssistant, entry: ConfigEntry
134 """Return diagnostics for all known thread networks."""
135 networks: dict[str, Network] = {}
139 for record
in store.datasets.values():
140 if not record.extended_pan_id:
142 network = networks.setdefault(
143 record.extended_pan_id,
145 "name": record.network_name,
148 "unexpected_routers": set(),
151 if mlp_item := record.dataset.get(MeshcopTLVType.MESHLOCALPREFIX):
153 network[
"prefixes"].
add(f
"{mlp[0:4]}:{mlp[4:8]}:{mlp[8:12]}:{mlp[12:16]}")
159 routes, reverse_routes, neighbours = await hass.async_add_executor_job(
160 _get_routes_and_neighbors
163 aiozc = await zeroconf.async_get_async_instance(hass)
165 if not data.extended_pan_id:
168 network = networks.setdefault(
169 data.extended_pan_id,
171 "name": data.network_name,
174 "unexpected_routers": set(),
181 router = network[
"routers"][data.server] = {
182 "server": data.server,
183 "addresses": data.addresses
or [],
185 "thread_version": data.thread_version,
186 "model": data.model_name,
187 "vendor": data.vendor_name,
195 for address
in data.addresses:
196 if address
in routes:
197 router[
"routes"].
update(routes[address])
199 if address
in neighbours:
200 router[
"neighbours"][address] = neighbours[address]
202 network[
"prefixes"].
update(router[
"routes"].keys())
207 for network
in networks.values():
210 for router
in network[
"routers"].values():
211 routers.update(router[
"addresses"])
213 for prefix
in network[
"prefixes"]:
214 if prefix
not in reverse_routes:
216 if ghosts := reverse_routes[prefix] - routers:
217 network[
"unexpected_routers"] = ghosts
220 "networks": networks,
bool add(self, _T matcher)
IssData update(pyiss.ISS iss)
DatasetStore async_get_store(HomeAssistant hass)
def _get_routes_and_neighbors()
dict[str, Neighbour] _get_neighbours(NDB ndb)
dict[str, Any] async_get_config_entry_diagnostics(HomeAssistant hass, ConfigEntry entry)
tuple[dict[str, dict[str, Route]], dict[str, set[str]]] _get_possible_thread_routes(NDB ndb)
list[ThreadRouterDiscoveryData] async_read_zeroconf_cache(AsyncZeroconf aiozc)