22
33from __future__ import annotations
44
5+ import asyncio
6+ import ipaddress
57import logging
68from typing import Any
79
@@ -66,7 +68,7 @@ async def async_step_user(
6668
6769 try :
6870 await self ._test_connection (host , port , token )
69- except aiohttp .ClientError :
71+ except ( aiohttp .ClientError , asyncio . TimeoutError , TimeoutError ) :
7072 errors ["base" ] = "cannot_connect"
7173 except Exception : # noqa: BLE001
7274 _LOGGER .exception ("Unexpected error during config flow" )
@@ -84,11 +86,41 @@ async def async_step_user(
8486 errors = errors ,
8587 )
8688
89+ @staticmethod
90+ def _pick_best_ip (discovery_info : ZeroconfServiceInfo ) -> str :
91+ """Choose the best IP from discovery info.
92+
93+ Prefers RFC 1918 private addresses (192.168.x, 10.x, 172.16-31.x)
94+ over VPN/tunnel IPs (Tailscale 100.x, WireGuard, etc.). Falls back
95+ to whatever is available.
96+ """
97+ # discovery_info may expose ip_address (single) and ip_addresses (list).
98+ candidates : list [str ] = []
99+ if hasattr (discovery_info , "ip_addresses" ) and discovery_info .ip_addresses :
100+ candidates = [str (a ) for a in discovery_info .ip_addresses ]
101+ elif discovery_info .ip_address :
102+ candidates = [str (discovery_info .ip_address )]
103+
104+ if not candidates :
105+ return str (discovery_info .ip_address )
106+
107+ # Prefer private (RFC 1918) addresses over anything else.
108+ for ip_str in candidates :
109+ try :
110+ addr = ipaddress .ip_address (ip_str )
111+ if addr .is_private and not ip_str .startswith ("100." ):
112+ return ip_str
113+ except ValueError :
114+ continue
115+
116+ # No private IP found — return the first candidate.
117+ return candidates [0 ]
118+
87119 async def async_step_zeroconf (
88120 self , discovery_info : ZeroconfServiceInfo
89121 ) -> ConfigFlowResult :
90122 """Handle discovery via mDNS/Zeroconf."""
91- host = str (discovery_info . ip_address )
123+ host = self . _pick_best_ip (discovery_info )
92124 port = discovery_info .port
93125 properties = discovery_info .properties
94126
@@ -126,7 +158,7 @@ async def async_step_zeroconf_confirm(
126158 await self ._test_connection (
127159 self ._discovery_host , self ._discovery_port , token
128160 )
129- except aiohttp .ClientError :
161+ except ( aiohttp .ClientError , asyncio . TimeoutError , TimeoutError ) :
130162 errors ["base" ] = "cannot_connect"
131163 except Exception : # noqa: BLE001
132164 _LOGGER .exception ("Unexpected error during zeroconf confirm" )
@@ -202,7 +234,7 @@ async def async_step_init(
202234 url , headers = headers , timeout = aiohttp .ClientTimeout (total = 10 )
203235 ) as resp :
204236 resp .raise_for_status ()
205- except aiohttp .ClientError :
237+ except ( aiohttp .ClientError , asyncio . TimeoutError , TimeoutError ) :
206238 errors ["base" ] = "cannot_connect"
207239 except Exception : # noqa: BLE001
208240 _LOGGER .exception ("Unexpected error in options flow" )
0 commit comments