Skip to content

feat: SOCKS/proxy detection + tunnel auto-reconnect, uptime, copy command#12

Closed
rekurt wants to merge 4 commits into
masterfrom
claude/ecstatic-cerf-1xeap0
Closed

feat: SOCKS/proxy detection + tunnel auto-reconnect, uptime, copy command#12
rekurt wants to merge 4 commits into
masterfrom
claude/ecstatic-cerf-1xeap0

Conversation

@rekurt

@rekurt rekurt commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Три фичи вокруг SSH-туннелей / SOCKS5. Туннельная инфраструктура (forward.rs, ssh_tunnel.rs, views/tunnels.rs, авто-старт из конфига) уже существовала — здесь закрыты конкретные пробелы.

Сознательно не входит: «проксирование сканера prt» (prt не делает исходящих сетевых соединений — читает только /proc и lsof, проксировать нечего) и per-tunnel трафик (требует дорогого per-PID опроса).

1. Детект локальных SOCKS/proxy-листенеров (prt-core)

Новая эвристика ProxyListening в suspicious.rs: процесс, слушающий на well-known SOCKS/proxy-порту (1080, 1081, 3128, 9050, 9150). 8080/8443 намеренно исключены, чтобы не плодить ложные срабатывания на http-alt. Новая причина встраивается в существующий конвейер — листенер автоматически получает magenta-подсветку + бейдж [!] в Connections и попадает под фильтр suspicious. Плюс имена socks/tor-socks в known_ports.

2. Доработка UI туннелей (дёшевый набор, без новых соединений)

  • Uptime — новая колонка (компактный формат 45s/12m/3h04m/2d05h), сбрасывается при рестарте.
  • Health-check листенера — поверх статуса Alive: живой ssh-процесс без реального LISTEN на своём порту (типичный признак сломанного -D/-L) показывается жёлтым no listener вместо обманчивого зелёного alive. Переиспользует уже отсканированные данные — никаких новых соединений.
  • Copy ssh-команды (клавиша c) — копирует полную ssh-команду туннеля в буфер через существующий copy_to_clipboard.

3. Авто-reconnect упавших туннелей (prt)

Туннели, упавшие сами по себе, автоматически перезапускаются с экспоненциальным backoff (2s → ×2 → потолок 60s), чтобы не долбить недоступный хост. reconnect_failed() вызывается каждый цикл после cleanup(); гейтинг по next_retry_at, успешный рестарт сбрасывает backoff. Ручной kill удаляет туннель целиком — реконнектятся только самопроизвольные падения.

i18n

Новые строки (tunnel_col_uptime, tunnel_health_no_listener, hint_copy_tunnel) добавлены во все три языка (en/ru/zh) — compile-time completeness-check проходит.

Тесты

  • Новые юнит-тесты suspicious.rs: 1080/9050 LISTEN → flagged, 8080 → нет, ESTABLISHED → нет.
  • Юнит-тест fmt_uptime.
  • cargo test --workspace — зелёный (197+ тестов), clippy и fmt --check чисты.

Ручная проверка

  • Детект: ssh -D 1080 localhost (или nc -l 1080) → строка порта 1080 в Connections magenta + [!], ловится фильтром suspicious.
  • UI: поднять туннель (n) → растёт Uptime; c → ssh-команда в буфере; health = alive при живом листенере.
  • Reconnect: туннель к недоступному хосту → failed, затем повторы с растущим backoff; при восстановлении хоста сам переходит в alive.

https://claude.ai/code/session_013YMo3DYYxbDAr6coo72M9W


Generated by Claude Code

claude added 3 commits June 12, 2026 16:53
Add a ProxyListening heuristic that flags processes listening on
well-known SOCKS/proxy ports (1080, 1081, 3128, 9050, 9150). 8080/8443
are deliberately excluded to avoid false positives on http-alt servers.
The new reason plugs into the existing enrichment pipeline, so proxy
listeners get the magenta + [!] treatment and match the 'suspicious'
filter automatically. Also adds tor-socks/socks names to known_ports.
- Add an Uptime column (compact 45s/12m/3h04m/2d05h formatting), reset
  on restart.
- Layer a listener health check over the Alive status: an alive ssh
  child with no local listener on its port (broken -D/-L bind) now shows
  a yellow 'no listener' warning instead of a misleading green 'alive'.
  Reuses already-scanned LISTEN entries — opens no new connections.
- Add 'c' key to copy the tunnel's full ssh command to the clipboard,
  reusing the existing copy_to_clipboard helper.
- New i18n strings (uptime column, no-listener, copy hint) in en/ru/zh.
Tunnels that die on their own are now automatically restarted with
exponential backoff (2s doubling up to 60s) so an unreachable host
isn't hammered. reconnect_failed() runs each loop iteration right after
cleanup(); gating is by next_retry_at rather than the tick, and a
successful restart resets the backoff. Manual kill removes a tunnel
entirely, so only self-inflicted failures reconnect.
@rekurt rekurt marked this pull request as ready for review June 12, 2026 17:40
…t, model health

Auto-reconnect (#1,#2,#3,#8):
- Backoff now actually grows for unreachable hosts. The reset was tied to
  a 'successful' 150ms spawn, which an unreachable host passes (it blocks
  on TCP timeout), so the delay never increased. Growth now happens on
  every attempt and is reset only after a tunnel stays Alive for
  STABLE_THRESHOLD (30s), via refresh_health.
- reconnect uses a new non-blocking restart_async() (no 150ms sleep in the
  render thread); failures are caught on the next tick. Manual restart()
  stays blocking for immediate feedback.
- After MAX_RETRIES attempts without recovery a tunnel is marked permanently
  failed (auto_reconnect=false); drop_failed() now prunes only those, so
  'save' no longer discards a tunnel that's mid-reconnect. auto_reconnect
  is now a meaningful flag.

Listener health in the model (#4,#5):
- New TunnelStatus::Unhealthy (process alive, local port not listening).
  Decision extracted to a pure, unit-tested decide_status(); a HEALTH_GRACE
  window and a scan_usable flag suppress false 'no listener' right after
  start and while auto-refresh is paused. cleanup() now takes the listening
  port set; the view no longer reaches into the scan.

Cleanups:
- command_string() shell-quotes args via shlex (#6).
- uptime column reuses prt-core format_duration (#7).
- private from_parts() constructor removes spawn duplication (#9).
- cross-reference comments between PROXY_PORTS and known_ports (#10).

New unit tests: decide_status, next_backoff, command_string quoting.

rekurt commented Jun 12, 2026

Copy link
Copy Markdown
Owner Author

Closing as superseded by #13 (ba0aad2), which landed an equivalent implementation of the same work — SOCKS/proxy listener detection, tunnel uptime, copy-command, and the same auto-reconnect review fixes (stability-gated backoff, non-blocking reconnect, retry cap + drop_failed keeping reconnecting tunnels, listener health) — directly on master.

That overlap is what made this branch conflict in every shared file. master's version is at parity or better on a few points (listener health matches the ssh child by PID rather than port only; format_uptime keeps minute/hour precision; copy-command quoting is hand-rolled with no extra dependency), so rebasing this PR would either produce an empty diff or revert those improvements. Nothing of net value to carry over — closing.


Generated by Claude Code

@rekurt rekurt closed this Jun 12, 2026
@rekurt rekurt deleted the claude/ecstatic-cerf-1xeap0 branch June 12, 2026 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants