WIP
This commit is contained in:
149
peer/hub_poller.go
Normal file
149
peer/hub_poller.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
const hubPollInterval = 64 * time.Second
|
||||
|
||||
type HubPoller struct {
|
||||
selfVPNIP netip.Addr
|
||||
vpnNet netip.Prefix
|
||||
hubURL string
|
||||
apiKey string
|
||||
addCh chan<- HubPeer
|
||||
removeCh chan<- wgtypes.Key
|
||||
known map[wgtypes.Key]int64 // pubKey → last seen version
|
||||
}
|
||||
|
||||
func NewHubPoller(
|
||||
selfVPNIP netip.Addr,
|
||||
vpnNet netip.Prefix,
|
||||
hubURL, apiKey string,
|
||||
addCh chan<- HubPeer,
|
||||
removeCh chan<- wgtypes.Key,
|
||||
) (*HubPoller, error) {
|
||||
u, err := url.Parse(hubURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Path = "/peer/fetch-state/"
|
||||
|
||||
return &HubPoller{
|
||||
selfVPNIP: selfVPNIP,
|
||||
vpnNet: vpnNet,
|
||||
hubURL: u.String(),
|
||||
apiKey: apiKey,
|
||||
addCh: addCh,
|
||||
removeCh: removeCh,
|
||||
known: make(map[wgtypes.Key]int64),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (hp *HubPoller) Run() {
|
||||
hp.poll()
|
||||
for range time.Tick(hubPollInterval) {
|
||||
hp.poll()
|
||||
}
|
||||
}
|
||||
|
||||
func (hp *HubPoller) poll() {
|
||||
req, err := http.NewRequest(http.MethodGet, hp.hubURL, nil)
|
||||
if err != nil {
|
||||
log.Printf("[HubPoller] build request: %v", err)
|
||||
return
|
||||
}
|
||||
req.SetBasicAuth("", hp.apiKey)
|
||||
|
||||
client := &http.Client{Timeout: 32 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[HubPoller] fetch: %v", err)
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
if err != nil {
|
||||
log.Printf("[HubPoller] read body: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var state m.NetworkState
|
||||
if err := json.Unmarshal(body, &state); err != nil {
|
||||
log.Printf("[HubPoller] unmarshal: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
hp.apply(state)
|
||||
}
|
||||
|
||||
func (hp *HubPoller) apply(state m.NetworkState) {
|
||||
seen := make(map[wgtypes.Key]struct{}, len(hp.known))
|
||||
|
||||
netAddr := hp.vpnNet.Addr().As4()
|
||||
|
||||
for _, p := range state.Peers {
|
||||
if p == nil || len(p.WGPubKey) != wgtypes.KeyLen {
|
||||
continue
|
||||
}
|
||||
|
||||
pubKey, err := wgtypes.NewKey(p.WGPubKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
octets := netAddr
|
||||
octets[3] = p.PeerIP
|
||||
vpnIP := netip.AddrFrom4(octets)
|
||||
if vpnIP == hp.selfVPNIP {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[pubKey] = struct{}{}
|
||||
|
||||
if v, ok := hp.known[pubKey]; ok && v == p.Version {
|
||||
continue
|
||||
}
|
||||
hp.known[pubKey] = p.Version
|
||||
hp.addCh <- hubPeerFrom(pubKey, vpnIP, p)
|
||||
}
|
||||
|
||||
for key := range hp.known {
|
||||
if _, ok := seen[key]; !ok {
|
||||
delete(hp.known, key)
|
||||
hp.removeCh <- key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hubPeerFrom(pubKey wgtypes.Key, vpnIP netip.Addr, p *m.Peer) HubPeer {
|
||||
var ep4, ep6 netip.AddrPort
|
||||
if len(p.Addr4) > 0 {
|
||||
if addr, ok := netip.AddrFromSlice(p.Addr4); ok {
|
||||
ep4 = netip.AddrPortFrom(addr.Unmap(), p.Port4)
|
||||
}
|
||||
}
|
||||
if len(p.Addr6) > 0 {
|
||||
if addr, ok := netip.AddrFromSlice(p.Addr6); ok {
|
||||
ep6 = netip.AddrPortFrom(addr, p.Port6)
|
||||
}
|
||||
}
|
||||
return HubPeer{
|
||||
PubKey: pubKey,
|
||||
VPNIP: vpnIP,
|
||||
IsRelay: p.Relay,
|
||||
IsPublic: ep4.IsValid() || ep6.IsValid(),
|
||||
EndpointV4: ep4,
|
||||
EndpointV6: ep6,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user