/opt/splice-src/coordinator/main.go
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net"
"net/http"
"os/exec"
"strings"
"sync"
"time"
)
type RegisterRequest struct {
JoinKey string `json:"join_key"`
Name string `json:"name"`
PublicKey string `json:"public_key"`
ListenPort int `json:"listen_port"`
Profile string `json:"profile"`
Advertise string `json:"advertise"`
Observed string `json:"observed"`
}
type Peer struct {
Name string `json:"name"`
PublicKey string `json:"public_key"`
OverlayIP string `json:"overlay_ip"`
Endpoints []string `json:"endpoints"`
AllowedIPs []string `json:"allowed_ips"`
}
type HubInfo struct {
PublicKey string `json:"public_key"`
Endpoint string `json:"endpoint"`
AllowedIPs string `json:"allowed_ips"`
}
type RegisterResponse struct {
OverlayIP string `json:"overlay_ip"`
Network string `json:"network"`
Hub *HubInfo `json:"hub,omitempty"`
Error string `json:"error,omitempty"`
}
type MeshSnapshot struct {
Self Peer `json:"self"`
Peers []Peer `json:"peers"`
Hub *HubInfo `json:"hub,omitempty"`
HubAllowedIPs []string `json:"hub_allowed_ips"`
Epoch int64 `json:"epoch"`
Error string `json:"error,omitempty"`
}
type node struct {
req RegisterRequest
overlay net.IP
advertise *net.IPNet
endpoint string
lastSeen time.Time
}
type network struct {
key string
cidr *net.IPNet
nextHost uint32
nodes map[string]*node
epoch int64
}
var (
mu sync.Mutex
networks = map[string]*network{}
nextNet = 0
reap = 90 * time.Second
hubIface string
hubEndpoint string
hubPub string
)
func getOrCreateNetwork(key string) *network {
if n, ok := networks[key]; ok {
return n
}
if nextNet >= 64 {
return nil
}
cidr := &net.IPNet{IP: net.IPv4(100, byte(64+nextNet), 0, 0).To4(), Mask: net.CIDRMask(16, 32)}
nextNet++
n := &network{key: key, cidr: cidr, nextHost: 2, nodes: map[string]*node{}}
networks[key] = n
return n
}
func (n *network) ipFor(off uint32) net.IP {
ip := make(net.IP, 4)
copy(ip, n.cidr.IP.To4())
ip[2] = byte(off >> 8)
ip[3] = byte(off & 0xff)
return ip
}
func overlap(a, b *net.IPNet) bool { return a.Contains(b.IP) || b.Contains(a.IP) }
func writeJSON(w http.ResponseWriter, code int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
_ = json.NewEncoder(w).Encode(v)
}
func hubInfoFor(n *network) *HubInfo {
if hubPub == "" {
return nil
}
return &HubInfo{PublicKey: hubPub, Endpoint: hubEndpoint, AllowedIPs: n.cidr.String()}
}
func hubAddPeer(pub, ipcidr string) {
if hubPub == "" {
return
}
if err := exec.Command("wg", "set", hubIface, "peer", pub, "allowed-ips", ipcidr).Run(); err != nil {
log.Printf("hub add peer: %v", err)
}
}
func hubDelPeer(pub string) {
if hubPub == "" {
return
}
_ = exec.Command("wg", "set", hubIface, "peer", pub, "remove").Run()
}
func peerOf(nd *node) Peer {
allowed := []string{nd.overlay.String() + "/32"}
if nd.advertise != nil {
allowed = append(allowed, nd.advertise.String())
}
var eps []string
if nd.endpoint != "" {
eps = []string{nd.endpoint}
}
return Peer{Name: nd.req.Name, PublicKey: nd.req.PublicKey, OverlayIP: nd.overlay.String() + "/32", Endpoints: eps, AllowedIPs: allowed}
}
func handleRegister(w http.ResponseWriter, r *http.Request) {
var req RegisterRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, 400, RegisterResponse{Error: "bad request"})
return
}
if strings.TrimSpace(req.JoinKey) == "" || strings.TrimSpace(req.PublicKey) == "" {
writeJSON(w, 401, RegisterResponse{Error: "join_key and public_key are required"})
return
}
var adv *net.IPNet
if req.Advertise != "" {
_, cidr, err := net.ParseCIDR(req.Advertise)
if err != nil {
writeJSON(w, 400, RegisterResponse{Error: "advertise is not a valid CIDR"})
return
}
adv = cidr
}
mu.Lock()
defer mu.Unlock()
n := getOrCreateNetwork(req.JoinKey)
if n == nil {
writeJSON(w, 503, RegisterResponse{Error: "coordinator at network capacity"})
return
}
if adv != nil {
if overlap(adv, n.cidr) {
writeJSON(w, 409, RegisterResponse{Error: fmt.Sprintf("advertised %s overlaps overlay range %s", req.Advertise, n.cidr)})
return
}
for _, other := range n.nodes {
if other.req.PublicKey == req.PublicKey || other.advertise == nil {
continue
}
if overlap(adv, other.advertise) {
writeJSON(w, 409, RegisterResponse{Error: fmt.Sprintf("can't bridge: %s already in use by %q - renumber one side, or drop -advertise", req.Advertise, other.req.Name)})
return
}
}
}
endpoint := req.Observed
if endpoint == "" {
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil && req.ListenPort > 0 {
endpoint = net.JoinHostPort(host, fmt.Sprintf("%d", req.ListenPort))
}
}
nd, exists := n.nodes[req.PublicKey]
if !exists {
nd = &node{overlay: n.ipFor(n.nextHost)}
n.nextHost++
n.nodes[req.PublicKey] = nd
}
nd.req, nd.advertise, nd.endpoint, nd.lastSeen = req, adv, endpoint, time.Now()
n.epoch = time.Now().Unix()
hubAddPeer(req.PublicKey, nd.overlay.String()+"/32")
disp := n.key
if len(disp) > 8 {
disp = disp[:8]
}
log.Printf("join: net %s (%s) <- %s -> %s", disp, n.cidr, req.Name, nd.overlay)
writeJSON(w, 200, RegisterResponse{OverlayIP: nd.overlay.String() + "/32", Network: n.cidr.String(), Hub: hubInfoFor(n)})
}
func handlePoll(w http.ResponseWriter, r *http.Request) {
key := r.URL.Query().Get("key")
pub := r.URL.Query().Get("pubkey")
mu.Lock()
defer mu.Unlock()
n, ok := networks[key]
if !ok {
writeJSON(w, 404, MeshSnapshot{Error: "unknown network"})
return
}
self, ok := n.nodes[pub]
if !ok {
writeJSON(w, 404, MeshSnapshot{Error: "not registered"})
return
}
self.lastSeen = time.Now()
hubAddPeer(self.req.PublicKey, self.overlay.String()+"/32")
snap := MeshSnapshot{Self: peerOf(self), Hub: hubInfoFor(n), HubAllowedIPs: []string{n.cidr.String()}, Epoch: n.epoch}
for _, nd := range n.nodes {
if nd.req.PublicKey == pub {
continue
}
snap.Peers = append(snap.Peers, peerOf(nd))
}
writeJSON(w, 200, snap)
}
func reaper() {
for range time.Tick(30 * time.Second) {
mu.Lock()
for _, n := range networks {
for pk, nd := range n.nodes {
if time.Since(nd.lastSeen) > reap {
hubDelPeer(pk)
delete(n.nodes, pk)
n.epoch = time.Now().Unix()
}
}
}
mu.Unlock()
}
}
func main() {
listen := flag.String("listen", "0.0.0.0:7777", "listen address")
hi := flag.String("hub-iface", "wgsplice", "hub WireGuard interface")
he := flag.String("hub-endpoint", "", "hub public endpoint host:port")
flag.Parse()
hubIface = *hi
hubEndpoint = *he
if out, err := exec.Command("wg", "show", hubIface, "public-key").Output(); err == nil {
hubPub = strings.TrimSpace(string(out))
log.Printf("hub %s pubkey=%s endpoint=%s", hubIface, hubPub, hubEndpoint)
} else {
log.Printf("WARNING: hub %q unavailable (%v); relay disabled", hubIface, err)
}
http.HandleFunc("/register", handleRegister)
http.HandleFunc("/poll", handlePoll)
http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("ok")) })
go reaper()
log.Printf("splice-coordinator listening on %s", *listen)
log.Fatal(http.ListenAndServe(*listen, nil))
}