2727_STATE_CACHE_TTL_S = 2.0
2828_ARTI_PROOF_CACHE : dict [str , Any ] = {"port" : 0 , "ok" : False , "ts" : 0.0 }
2929_ARTI_PROOF_CACHE_TTL_S = 30.0
30+ _ARTI_STATUS_CACHE : dict [str , Any ] = {"port" : 0 , "ready" : False , "ts" : 0.0 }
31+ _ARTI_STATUS_FAIL_TTL_S = 4.0
32+ _ARTI_PROBE_LOCK = threading .Lock ()
33+ _ARTI_SOCKS_FAILURES = 0
34+ _ARTI_LAST_TOR_RECOVERY_TS = 0.0
35+ _ARTI_TOR_RECOVERY_COOLDOWN_S = 45.0
36+ _ARTI_SOCKS_CONNECT_TIMEOUT_S = 5.0
3037_PRIVATE_CLEARNET_FALLBACK_WINDOW_S = 300.0
3138
3239BACKEND_DIR = Path (__file__ ).resolve ().parent .parent
7077 "PRIVACY_CORE_MIN_VERSION" ,
7178}
7279
73- def _check_arti_ready () -> bool :
74- from services .config import get_settings
80+ def invalidate_arti_ready_cache () -> None :
81+ _ARTI_PROOF_CACHE .update ({"port" : 0 , "ok" : False , "ts" : 0.0 })
82+ _ARTI_STATUS_CACHE .update ({"port" : 0 , "ready" : False , "ts" : 0.0 })
7583
76- settings = get_settings ()
77- if not bool (settings .MESH_ARTI_ENABLED ):
78- return False
79- socks_port = int (settings .MESH_ARTI_SOCKS_PORT or 9050 )
84+
85+ def _maybe_recover_tor_socks_transport (socks_port : int ) -> None :
86+ global _ARTI_SOCKS_FAILURES , _ARTI_LAST_TOR_RECOVERY_TS
87+
88+ _ARTI_SOCKS_FAILURES += 1
89+ if _ARTI_SOCKS_FAILURES < 3 :
90+ return
91+ now = time .time ()
92+ if (now - _ARTI_LAST_TOR_RECOVERY_TS ) < _ARTI_TOR_RECOVERY_COOLDOWN_S :
93+ return
94+ _ARTI_LAST_TOR_RECOVERY_TS = now
95+ _ARTI_SOCKS_FAILURES = 0
8096 try :
81- with socket .create_connection ((WORMHOLE_HOST , socks_port ), timeout = 2.0 ) as sock :
82- # SOCKS5 greeting: version 5, 1 auth method, no-auth.
97+ from services .tor_hidden_service import tor_service
98+
99+ logger .warning (
100+ "Tor SOCKS on port %s is wedged — recycling Tor hidden service" ,
101+ socks_port ,
102+ )
103+ tor_service .stop ()
104+ tor_service .start (target_port = 8000 )
105+ invalidate_arti_ready_cache ()
106+ except Exception as exc :
107+ logger .warning ("Tor SOCKS recovery failed: %s" , exc )
108+
109+
110+ def _probe_arti_socks_ready (socks_port : int ) -> bool :
111+ try :
112+ with socket .create_connection (
113+ (WORMHOLE_HOST , socks_port ),
114+ timeout = _ARTI_SOCKS_CONNECT_TIMEOUT_S ,
115+ ) as sock :
116+ sock .settimeout (_ARTI_SOCKS_CONNECT_TIMEOUT_S )
83117 sock .sendall (b"\x05 \x01 \x00 " )
84118 response = sock .recv (2 )
85119 if response != b"\x05 \x00 " :
@@ -88,6 +122,53 @@ def _check_arti_ready() -> bool:
88122 except Exception as exc :
89123 logger .warning ("Arti SOCKS check failed on port %s: %s" , socks_port , exc )
90124 return False
125+ return True
126+
127+
128+ def _check_arti_ready (* , force : bool = False ) -> bool :
129+ from services .config import get_settings
130+
131+ settings = get_settings ()
132+ if not bool (settings .MESH_ARTI_ENABLED ):
133+ return False
134+ socks_port = int (settings .MESH_ARTI_SOCKS_PORT or 9050 )
135+ now = time .time ()
136+ if not force :
137+ if (
138+ int (_ARTI_STATUS_CACHE .get ("port" , 0 ) or 0 ) == socks_port
139+ and (now - float (_ARTI_STATUS_CACHE .get ("ts" , 0.0 ) or 0.0 )) < _ARTI_STATUS_FAIL_TTL_S
140+ ):
141+ return bool (_ARTI_STATUS_CACHE .get ("ready" ))
142+ if (
143+ int (_ARTI_PROOF_CACHE .get ("port" , 0 ) or 0 ) == socks_port
144+ and bool (_ARTI_PROOF_CACHE .get ("ok" ))
145+ and (now - float (_ARTI_PROOF_CACHE .get ("ts" , 0.0 ) or 0.0 )) < _ARTI_PROOF_CACHE_TTL_S
146+ ):
147+ return True
148+
149+ with _ARTI_PROBE_LOCK :
150+ now = time .time ()
151+ if not force :
152+ if (
153+ int (_ARTI_STATUS_CACHE .get ("port" , 0 ) or 0 ) == socks_port
154+ and (now - float (_ARTI_STATUS_CACHE .get ("ts" , 0.0 ) or 0.0 )) < _ARTI_STATUS_FAIL_TTL_S
155+ ):
156+ return bool (_ARTI_STATUS_CACHE .get ("ready" ))
157+ if (
158+ int (_ARTI_PROOF_CACHE .get ("port" , 0 ) or 0 ) == socks_port
159+ and bool (_ARTI_PROOF_CACHE .get ("ok" ))
160+ and (now - float (_ARTI_PROOF_CACHE .get ("ts" , 0.0 ) or 0.0 )) < _ARTI_PROOF_CACHE_TTL_S
161+ ):
162+ return True
163+
164+ if not _probe_arti_socks_ready (socks_port ):
165+ _ARTI_STATUS_CACHE .update ({"port" : socks_port , "ready" : False , "ts" : now })
166+ _maybe_recover_tor_socks_transport (socks_port )
167+ return False
168+
169+ global _ARTI_SOCKS_FAILURES
170+ _ARTI_SOCKS_FAILURES = 0
171+ _ARTI_STATUS_CACHE .update ({"port" : socks_port , "ready" : True , "ts" : now })
91172
92173 now = time .time ()
93174 if (
@@ -110,12 +191,13 @@ def _check_arti_ready() -> bool:
110191 is_tor = bool (payload .get ("IsTor" )) or bool (payload .get ("is_tor" ))
111192 if not (response .ok and is_tor ):
112193 logger .warning (
113- "Arti Tor proof failed (status=%s is_tor=%s) — SOCKS is up, using Arti anyway " ,
194+ "Arti Tor proof failed (status=%s is_tor=%s)" ,
114195 getattr (response , "status_code" , "unknown" ),
115196 payload .get ("IsTor" , payload .get ("is_tor" )),
116197 )
117- _ARTI_PROOF_CACHE .update ({"port" : socks_port , "ok" : True , "ts" : now })
118- return True
198+ _ARTI_PROOF_CACHE .update ({"port" : socks_port , "ok" : False , "ts" : now })
199+ _ARTI_STATUS_CACHE .update ({"port" : socks_port , "ready" : False , "ts" : now })
200+ return False
119201 _ARTI_PROOF_CACHE .update ({"port" : socks_port , "ok" : True , "ts" : now })
120202 return True
121203 except Exception as exc :
0 commit comments