feat: Phase 4 hardening — retry/backoff, loop detection, sync status UI + tests
Phase 4.1 — Retry/backoff for failed permission pushes: - _compute_next_retry(): capped exponential backoff with jitter (60s–1h) - _record_push_result(): tracks push_attempts and next_retry_at per link - replay_pending_pushes(): skips links still in backoff window, logs deferred count - _load() migration: adds push_attempts/next_retry_at to existing records Phase 4.2 — Loop detection (A→B→A routing cycle): - set_peer_route_via(): returns 409 if target cell already routes peers through us - apply_remote_permissions(): soft warning when accepting exit-relay that would cycle Phase 4.3 — Sync staleness indicator in Cell Network UI: - SyncBadge component: green (synced), amber (pending/failed), gray (never) - Shows relativeTime of last sync + error message + next retry estimate - Injected into CellPanel header alongside tunnel online/handshake status Tests (54 new): - TestCheckInviteConflicts: subnet overlap, domain conflict, exclude_cell (9 tests) - TestPushInviteToRemote: success, 4xx, no endpoint, subprocess errors (7 tests) - TestAcceptInviteNew: new cell, idempotent, healing dns/subnet changes (16 tests) - TestAddConnectionMutualPairing: push-invite call, non-fatal failure (5 tests) - TestPeerSyncAcceptInvite endpoint: happy path, field validation, error propagation (16 tests) - Fixed 2 existing replay tests to clear backoff gate (simulates elapsed window) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -237,6 +237,13 @@ def set_peer_route_via(peer_name):
|
||||
)
|
||||
if not link:
|
||||
return jsonify({'error': f"Cell {via_cell!r} not connected"}), 404
|
||||
if link.get('remote_exit_relay_active'):
|
||||
return jsonify({
|
||||
'error': (
|
||||
f"Cannot route via '{via_cell}': it is already routing peers "
|
||||
f"through this cell — enabling both directions would create a loop"
|
||||
)
|
||||
}), 409
|
||||
wireguard_manager.update_cell_peer_allowed_ips(
|
||||
link['public_key'], link['vpn_subnet'], add_default_route=True)
|
||||
wireguard_manager.apply_peer_route_via(peer_ip, via_wg_ip=link['dns_ip'])
|
||||
|
||||
Reference in New Issue
Block a user