From 1c304767b84a56ccedf8b87795b049a31a6a13e2 Mon Sep 17 00:00:00 2001 From: jdl Date: Tue, 9 Jun 2026 07:40:06 +0200 Subject: [PATCH] WIP --- peer/app.go | 1 + peer/device.go | 7 +++++++ peer/on_hub.go | 22 ++++++++++++++++++---- peer/on_ping.go | 4 +++- peer/on_tick.go | 24 +++++++++++++++++------- 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/peer/app.go b/peer/app.go index f23860b..4781eba 100644 --- a/peer/app.go +++ b/peer/app.go @@ -19,6 +19,7 @@ const ( ControlPort = 4561 PingInterval = 8 * time.Second TimeoutInterval = 30 * time.Second + probeTimeout = 30 * time.Second ) // HubPeer is a peer entry as reported by the hub poller. diff --git a/peer/device.go b/peer/device.go index 7aec48e..1a44cd8 100644 --- a/peer/device.go +++ b/peer/device.go @@ -47,6 +47,13 @@ func (a *App) devAddDirect(p *Peer, endpoint netip.AddrPort) { p.State = StateDirect } +func (a *App) devAddRelayed(p *Peer, relayVPNIP netip.Addr) { + port := ForwarderBasePort + uint16(p.VPNIP.As4()[3]) + endpoint := netip.AddrPortFrom(relayVPNIP, port) + devRetry(p.VPNIP, "AddRelayed", func() error { return a.dev.AddDirect(p.PubKey(), endpoint, p.VPNIP) }) + p.State = StateRelayed +} + func (a *App) devSetRelay(p *Peer, endpoint netip.AddrPort) { devRetry(p.VPNIP, "SetRelay", func() error { return a.dev.SetRelay(p.PubKey(), endpoint, a.vpnNet) }) } diff --git a/peer/on_hub.go b/peer/on_hub.go index eb04beb..dd2dd4b 100644 --- a/peer/on_hub.go +++ b/peer/on_hub.go @@ -37,7 +37,11 @@ func (a *App) onAddPeer(p HubPeer) { a.peersByIP[peer.VPNIP] = peer if !peer.IsPublic { - a.devAddPeer(peer) + if a.relay != nil { + a.devAddRelayed(peer, a.relay.VPNIP) + } else { + a.devAddPeer(peer) + } if a.forwarder != nil && endpoint.IsValid() { a.forwarder.SetEndpoint(peer.VPNIP.As4()[3], endpoint) } @@ -86,11 +90,21 @@ func (a *App) switchActiveRelay() { } if best == nil { log.Printf("no relay available") - return + } else { + a.devSetRelay(best, best.PreferredEndpoint()) + a.relay = best } - a.devSetRelay(best, best.PreferredEndpoint()) - a.relay = best + for _, p := range a.peersByKey { + if p.State != StateRelayed { + continue + } + if best != nil { + a.devAddRelayed(p, best.VPNIP) + } else { + a.devAddPeer(p) + } + } } func preferredEndpoint(v4, v6 netip.AddrPort) netip.AddrPort { diff --git a/peer/on_ping.go b/peer/on_ping.go index 2be8d0a..503677a 100644 --- a/peer/on_ping.go +++ b/peer/on_ping.go @@ -54,5 +54,7 @@ func (a *App) addProbe(peer *Peer, v4, v6 netip.AddrPort) { return } peer.UpdateEndpoints(v4, v6) - a.devAddProbe(peer, endpoint) + a.devAddDirect(peer, endpoint) + peer.State = StateProbing + peer.probeDeadline = time.Now().Add(probeTimeout) } diff --git a/peer/on_tick.go b/peer/on_tick.go index 45de25e..502c4f2 100644 --- a/peer/on_tick.go +++ b/peer/on_tick.go @@ -5,7 +5,6 @@ import ( "time" "vppn/peer/control" - "vppn/peer/wginterface" ) func (a *App) onTick() { @@ -29,16 +28,27 @@ func (a *App) onTick() { switch p.State { case StateProbing: - // Promote probing peers to direct once alive (direct path confirmed - // working). - if time.Since(p.LastHandshakeTime()) < wginterface.SessionTimeout { - a.devAddDirect(p, p.WGEndpoint()) + if p.Up() { + // Direct handshake succeeded; WG entry already correct. + p.State = StateDirect + } else if time.Now().After(p.probeDeadline) { + // Probe timed out — revert to relay. + if a.relay != nil { + a.devAddRelayed(p, a.relay.VPNIP) + } else { + a.devAddPeer(p) + } } case StateDirect: - // Demote stale non-public direct peers back to probing. + // Demotion skips StateProbing — only probe when there is positive + // evidence of a direct path (ping or multicast beacon). if !p.IsPublic && !p.Up() { - a.devAddProbe(p, p.WGEndpoint()) + if a.relay != nil { + a.devAddRelayed(p, a.relay.VPNIP) + } else { + a.devAddPeer(p) + } } } }