package peer import ( "net/netip" "os" "os/signal" "syscall" "time" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "vppn/m" "vppn/peer/control" "vppn/peer/multicast" "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 ) // scratchSize is large enough for the biggest buffer either the ping or the // multicast path serializes through the shared App scratch. const scratchSize = max(control.Size, multicast.SignedPacketSize) type PingEvent struct { srcVPNIP netip.Addr ping control.Ping } // 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 localDomain string // Infrastructure dev WGDevice controlConn ControlConn // 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 // Reusable serialization scratch for outgoing pings and multicast signature // verification. Only touched from the Run goroutine. scratch []byte // Event channels fed by background goroutines hubAddCh <-chan m.Peer hubRemoveCh <-chan wgtypes.Key pingCh <-chan PingEvent multicastCh <-chan multicast.Packet } // Run is the main event loop. It runs until SIGTERM/SIGINT. func (a *App) Run() error { // Establish a clean hosts section before the first poll lands, clearing // any stale entries left by a prior run (e.g. crash, or peers removed // while we were down). a.updateHosts() 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()) }