/opt/splice-src/agent/main.go
package main
import (
"bufio"
"bytes"
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"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 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"`
Error string `json:"error"`
}
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 MeshSnapshot struct {
Self Peer `json:"self"`
Peers []Peer `json:"peers"`
Hub *HubInfo `json:"hub"`
HubAllowedIPs []string `json:"hub_allowed_ips"`
Epoch int64 `json:"epoch"`
Error string `json:"error"`
}
func hostname() string {
h, _ := os.Hostname()
if h == "" {
return "node"
}
return h
}
func fatal(err error) { fmt.Fprintln(os.Stderr, "error:", err); os.Exit(1) }
func newJoinKey() string {
b := make([]byte, 16)
rand.Read(b)
return "spl_" + base64.RawURLEncoding.EncodeToString(b)
}
func genWGKeys() (priv, pub string, err error) {
k, err := ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
return "", "", err
}
return base64.StdEncoding.EncodeToString(k.Bytes()),
base64.StdEncoding.EncodeToString(k.PublicKey().Bytes()), nil
}
func interactiveKey() string {
r := bufio.NewReader(os.Stdin)
for {
fmt.Print("No network key given. [G]enerate a new network, or [P]aste an existing key? ")
line, _ := r.ReadString('\n')
switch strings.ToLower(strings.TrimSpace(line)) {
case "g":
k := newJoinKey()
fmt.Printf("\nYour new network key (share it to add machines):\n\n %s\n\n", k)
return k
case "p":
fmt.Print("Paste network key: ")
kl, _ := r.ReadString('\n')
if k := strings.TrimSpace(kl); k != "" {
return k
}
}
}
}
func register(base string, req RegisterRequest) (RegisterResponse, error) {
body, _ := json.Marshal(req)
resp, err := http.Post(strings.TrimRight(base, "/")+"/register", "application/json", bytes.NewReader(body))
if err != nil {
return RegisterResponse{}, err
}
defer resp.Body.Close()
var rr RegisterResponse
json.NewDecoder(resp.Body).Decode(&rr)
return rr, nil
}
func poll(base, key, pub string) (MeshSnapshot, int, error) {
u := fmt.Sprintf("%s/poll?key=%s&pubkey=%s", strings.TrimRight(base, "/"), url.QueryEscape(key), url.QueryEscape(pub))
resp, err := http.Get(u)
if err != nil {
return MeshSnapshot{}, 0, err
}
defer resp.Body.Close()
var s MeshSnapshot
json.NewDecoder(resp.Body).Decode(&s)
return s, resp.StatusCode, nil
}
func renderConfig(priv, overlayIP string, hub *HubInfo) (string, error) {
if hub == nil || hub.PublicKey == "" {
return "", fmt.Errorf("waiting for hub")
}
var b strings.Builder
fmt.Fprintf(&b, "[Interface]\nPrivateKey = %s\nAddress = %s\n\n", priv, overlayIP)
fmt.Fprintf(&b, "[Peer]\nPublicKey = %s\nEndpoint = %s\nAllowedIPs = %s\nPersistentKeepalive = 25\n",
hub.PublicKey, hub.Endpoint, hub.AllowedIPs)
return b.String(), nil
}
func main() {
coord := flag.String("coordinator", "https://splicevpn.com.au/api", "coordinator base URL")
name := flag.String("name", hostname(), "node name")
key := flag.String("key", "", "network join key")
gen := flag.Bool("generate", false, "generate a new network key")
advertise := flag.String("advertise", "", "advertise a local subnet (CIDR)")
profile := flag.String("profile", "", "profile")
iface := flag.String("iface", "splice0", "WireGuard interface name")
port := flag.Int("port", 51820, "local WireGuard listen port (registration hint)")
flag.Parse()
joinKey := *key
if *gen {
joinKey = newJoinKey()
fmt.Printf("Your new network key (share it with machines that should join):\n\n %s\n\n", joinKey)
}
if joinKey == "" {
joinKey = interactiveKey()
}
priv, pub, err := genWGKeys()
if err != nil {
fatal(err)
}
req := RegisterRequest{JoinKey: joinKey, Name: *name, PublicKey: pub, ListenPort: *port, Profile: *profile, Advertise: *advertise}
reg, err := register(*coord, req)
if err != nil {
fatal(err)
}
if reg.Error != "" {
fmt.Printf("\n X Couldn't join: %s\n\n", reg.Error)
os.Exit(1)
}
overlay := reg.OverlayIP
fmt.Printf("OK joined network %s as %s (%s)\n", reg.Network, *name, overlay)
var last string
for {
snap, code, err := poll(*coord, joinKey, pub)
if err != nil {
fmt.Println("poll error:", err)
time.Sleep(5 * time.Second)
continue
}
if code == 404 {
fmt.Println("coordinator lost our registration - re-registering")
if rr, e := register(*coord, req); e == nil && rr.Error == "" {
overlay = rr.OverlayIP
last = ""
}
time.Sleep(2 * time.Second)
continue
}
cfg, err := renderConfig(priv, overlay, snap.Hub)
if err != nil {
time.Sleep(5 * time.Second)
continue
}
if cfg != last {
if err := applyConfig(*iface, cfg); err != nil {
fmt.Println("apply error:", err)
} else {
fmt.Printf("mesh updated: %d peer(s) reachable via hub\n", len(snap.Peers))
last = cfg
}
}
time.Sleep(5 * time.Second)
}
}