Some checks failed
Windows project source guards / test (push) Has been cancelled
- Ожидание освобождения TUN-адаптера ждало любой адаптер xftun*, а Wintun убитого ядра остаётся в системе выключенным: после первой же TUN-сессии каждое отключение и переключение в TUN стояло полные 10 с. Теперь ждём только адаптер своей сессии, пока он не перестанет быть Up. - sing-box останавливается сразу kill(): terminate() консольный sing-box на Windows не обрабатывает, 500 мс «мягкой» остановки истекали всегда. - «Осиротевшие» ядра и winws2 ищутся снимком WinAPI вместо PowerShell+WMI (~0,2 с на каждый запуск ядра, у Zapret — дважды); PowerShell — запасной. - Готовность портов ядер, relay и Clash API сначала смотрится по таблице TCP: подключение к ещё закрытому порту Windows держит повторами SYN, и ранняя проба всегда выжидала свой таймаут (~0,35–0,5 с). - sing-box check — только после неудачного запуска, ради понятной ошибки: параллельно с работающим ядром она для TUN-конфига мешает туннелю. - cryptography грузится только при пароле данных и ссылках Happ. Проверка готовности TUN оставлена прежней: при более ранней готовности закрепление сервера через Clash API в первые ~0,4 с ломало TCP туннеля в «родном» режиме sing-box. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
3 KiB
Python
81 lines
3 KiB
Python
"""Loopback relay readiness — the shared seam between sing-box and each core."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import socket
|
|
import time
|
|
from typing import Callable
|
|
|
|
from ...application.async_steps import TransitionSteps, run_in_worker, sleep_ms
|
|
from ...constants import PROXY_HOST
|
|
from ...network.proxy_readiness import LISTENER_POLL_MS, ports_listening
|
|
from ...platform.windows.subprocess_utils import sleep_with_events
|
|
|
|
|
|
def probe_loopback_relay(port: int, *, host: str = PROXY_HOST, connect_timeout: float = 0.15) -> bool:
|
|
"""One blocking accept probe (worker-only)."""
|
|
try:
|
|
with socket.create_connection((host, int(port)), timeout=connect_timeout):
|
|
return True
|
|
except OSError:
|
|
return False
|
|
|
|
|
|
def wait_for_loopback_relay_steps(
|
|
port: int,
|
|
*,
|
|
host: str = PROXY_HOST,
|
|
timeout: float = 10.0,
|
|
should_continue: Callable[[], bool] | None = None,
|
|
connect_timeout: float = 0.15,
|
|
step: float = 0.05,
|
|
) -> TransitionSteps:
|
|
"""Step version of :func:`wait_for_loopback_relay` for transitions.
|
|
|
|
Every connect attempt runs in the worker pool; ``should_continue`` is
|
|
evaluated on the GUI thread between attempts, so it may read QProcess
|
|
and controller state.
|
|
"""
|
|
deadline = time.monotonic() + max(0.0, timeout)
|
|
while time.monotonic() < deadline:
|
|
if should_continue is not None and not should_continue():
|
|
return False
|
|
# Порт ещё не открыт (таблица TCP) — подключаться рано: на Windows
|
|
# такая попытка выжидает весь connect_timeout.
|
|
ready = yield run_in_worker(
|
|
lambda: None if ports_listening((port,)) is False
|
|
else probe_loopback_relay(port, host=host, connect_timeout=connect_timeout)
|
|
)
|
|
if ready:
|
|
return True
|
|
yield sleep_ms(LISTENER_POLL_MS if ready is None else int(step * 1000))
|
|
return False
|
|
|
|
|
|
def wait_for_loopback_relay(
|
|
port: int,
|
|
*,
|
|
host: str = PROXY_HOST,
|
|
timeout: float = 10.0,
|
|
should_continue: Callable[[], bool] | None = None,
|
|
connect_timeout: float = 0.15,
|
|
step: float = 0.05,
|
|
) -> bool:
|
|
"""Wait until a core's local SOCKS relay accepts a loopback TCP connection.
|
|
|
|
Returns ``True`` as soon as a connection succeeds, ``False`` if the deadline
|
|
passes or ``should_continue`` reports the attempt should be abandoned (e.g.
|
|
the owned process died or the transition was superseded). No payload is
|
|
sent — this only proves the listener is bound and accepting, which is the
|
|
precondition the sing-box front relies on before dialing the relay.
|
|
"""
|
|
deadline = time.monotonic() + max(0.0, timeout)
|
|
while time.monotonic() < deadline:
|
|
if should_continue is not None and not should_continue():
|
|
return False
|
|
try:
|
|
with socket.create_connection((host, int(port)), timeout=connect_timeout):
|
|
return True
|
|
except OSError:
|
|
sleep_with_events(step)
|
|
return False
|