156 lines
3.5 KiB
Go
156 lines
3.5 KiB
Go
package peer
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/netip"
|
|
"os"
|
|
"os/signal"
|
|
"sort"
|
|
"strings"
|
|
"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
|
|
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()
|
|
|
|
stateTicker := time.NewTicker(time.Second) // TODO: Const.
|
|
pingTicker := time.NewTicker(PingInterval)
|
|
|
|
sig := make(chan os.Signal, 1)
|
|
signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
|
|
defer signal.Stop(sig)
|
|
|
|
tickCount := 0
|
|
|
|
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 <-stateTicker.C:
|
|
a.onTick()
|
|
case <-pingTicker.C:
|
|
a.onPingTicker()
|
|
tickCount++
|
|
if tickCount == 0 {
|
|
a.logNetworkState()
|
|
}
|
|
|
|
case <-sig:
|
|
return a.onShutdown()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *App) onShutdown() error {
|
|
return wginterface.Delete(a.dev.Name())
|
|
}
|
|
|
|
func (a *App) logNetworkState() {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "Network state (self: %s public=%v):\n", a.vpnIP, a.isPublic)
|
|
fmt.Fprintf(&b, " Network: %v\n", a.vpnNet)
|
|
fmt.Fprintf(&b, " IPv4: %v\n", a.selfV4)
|
|
fmt.Fprintf(&b, " IPv6: %v\n", a.selfV6)
|
|
|
|
b.WriteString("Peers:\n")
|
|
//
|
|
peers := make([]*Peer, 0, len(a.peersByIP))
|
|
for _, p := range a.peersByIP {
|
|
peers = append(peers, p)
|
|
}
|
|
|
|
sort.Slice(peers, func(i, j int) bool {
|
|
return peers[i].VPNIP.As4()[3] < peers[j].VPNIP.As4()[3]
|
|
})
|
|
|
|
for _, p := range peers {
|
|
ip := p.VPNIP.As4()[3]
|
|
up := "DOWN"
|
|
if p.Up() {
|
|
up = "UP "
|
|
}
|
|
|
|
endpoint := p.WGEndpoint()
|
|
if endpoint.IsValid() {
|
|
fmt.Fprintf(&b, " %24s %03d %s %s seen=%s @ %s\n",
|
|
p.Name, ip, p.State, up, time.Since(p.LastPing).Round(time.Millisecond), endpoint)
|
|
} else {
|
|
fmt.Fprintf(&b, " %24s %03d %s %s seen=%s\n",
|
|
p.Name, ip, p.State, up, time.Since(p.LastPing).Round(time.Millisecond))
|
|
}
|
|
}
|
|
|
|
log.Print(b.String())
|
|
}
|