package peer import ( "net/netip" "os" "os/signal" "syscall" "time" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "vppn/peer/control" "vppn/peer/wginterface" ) var _ WGDevice = (*wginterface.Device)(nil) // compile-time check: Device satisfies WGDevice const ( ControlPort = 4561 PingInterval = 8 * time.Second TimeoutInterval = 30 * time.Second ) // HubPeer is a peer entry as reported by the hub poller. type HubPeer struct { PubKey wgtypes.Key VPNIP netip.Addr IsRelay bool IsPublic bool EndpointV4 netip.AddrPort // zero if none EndpointV6 netip.AddrPort // zero if none SignPubKey [32]byte } type PingEvent struct { srcVPNIP netip.Addr ping control.Ping } // MulticastEvent carries a raw signed beacon for verification in the event loop. type MulticastEvent struct { signed []byte // nacl/sign signed beacon (64-byte sig || 35-byte payload) src netip.Addr // physical LAN source address } // App is the peer application. All mutable state lives here and is // accessed only from the Run goroutine. type App struct { // Identity vpnIP netip.Addr vpnNet netip.Prefix privKey wgtypes.Key pubKey wgtypes.Key isRelay bool isPublic bool // Infrastructure dev WGDevice controlConn ControlConn // Relay-side forwarder; non-nil only when isRelay. forwarder *Forwarder // Peer state relay *Peer peersByKey map[wgtypes.Key]*Peer peersByIP map[netip.Addr]*Peer // Our own external endpoints, learned from Dst fields in incoming pings selfV4 netip.AddrPort selfV6 netip.AddrPort // Event channels fed by background goroutines hubAddCh <-chan HubPeer hubRemoveCh <-chan wgtypes.Key pingCh <-chan PingEvent multicastCh <-chan MulticastEvent } // Run is the main event loop. It runs until SIGTERM/SIGINT. func (a *App) Run() error { ticker := time.NewTicker(PingInterval) defer ticker.Stop() sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT) defer signal.Stop(sig) for { select { case p := <-a.hubAddCh: a.onAddPeer(p) case key := <-a.hubRemoveCh: a.onRemovePeer(key) case e := <-a.pingCh: a.onPing(e) case e := <-a.multicastCh: a.onMulticastDiscovery(e) case <-ticker.C: a.onTick() case <-sig: return a.onShutdown() } } } func (a *App) onShutdown() error { return wginterface.Delete(a.dev.Name()) }