Files
vppn/peer/on_hub.go

119 lines
2.8 KiB
Go

package peer
import (
"log"
"math"
"net/netip"
"time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"vppn/m"
"vppn/peer/control"
)
func (a *App) onAddPeer(p m.Peer) {
a.onRemovePeer(p.WGPubKey)
octets := a.vpnNet.Addr().As4()
octets[3] = p.PeerIP
vpnIP := netip.AddrFrom4(octets)
peer := &Peer{
wgPeer: wgtypes.Peer{PublicKey: p.WGPubKey},
VPNIP: vpnIP,
Name: p.Name,
IsRelay: p.Relay,
IsPublic: p.IsPublic(),
EndpointV4: p.Endpoint4(),
EndpointV6: p.Endpoint6(),
RTT: time.Duration(math.MaxInt64) * time.Nanosecond,
Role: roleFor(a.isPublic, a.vpnIP, p.IsPublic(), vpnIP),
SignPubKey: p.SignPubKey,
}
a.peersByKey[p.WGPubKey] = peer
a.peersByIP[peer.VPNIP] = peer
defer a.updateHosts()
if !peer.IsPublic {
if a.isPublic {
// Public nodes accept traffic from non-public peers as soon as they
// initiate a handshake. Set /32 AllowedIPs now; WireGuard learns the
// endpoint from the incoming handshake automatically.
a.devPromote(peer)
} else {
a.devAddRelayed(peer)
}
return
}
a.devAddDirect(peer, peer.PreferredEndpoint())
}
func (a *App) onRemovePeer(key wgtypes.Key) {
peer, exists := a.peersByKey[key]
if !exists {
return
}
a.devRemove(peer)
delete(a.peersByKey, key)
delete(a.peersByIP, peer.VPNIP)
a.updateHosts()
if peer == a.relay {
a.relay = nil
a.switchActiveRelay()
}
}
// switchActiveRelay selects the live relay with the lowest VPN IP as active.
//
// Choosing deterministically by IP (rather than by lowest RTT) makes every
// non-public peer converge on the same relay, so two non-public peers always
// share a relay and can reach each other. Failover walks up to the next-lowest
// live relay; failback returns to a lower-IP relay once it recovers. The call
// is idempotent: if the best relay is already active it does nothing, so it's
// safe to run every state tick.
func (a *App) switchActiveRelay() {
var best *Peer
for _, p := range a.peersByKey {
if !p.CanRelay() {
continue
}
if best == nil || p.VPNIP.Less(best.VPNIP) {
best = p
}
}
if best == a.relay {
return // Already on the best relay (or none available and none before).
}
if a.relay != nil {
// The old relay is public, so it goes back to being a direct peer -
// this converts its /24 back to a /32.
a.devAddDirect(a.relay, a.relay.PreferredEndpoint())
}
if best == nil {
log.Printf("no relay available")
a.relay = nil
return
}
a.devSetRelay(best, best.PreferredEndpoint())
a.relay = best
}
func roleFor(selfIsPublic bool, selfIP netip.Addr, peerIsPublic bool, peerVPNIP netip.Addr) control.Role {
if !selfIsPublic && peerIsPublic {
return control.Client
}
if selfIsPublic && !peerIsPublic {
return control.Server
}
return control.RoleFor(selfIP, peerVPNIP)
}