Source — /opt/splice-src/agent/main.go

/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 =[REDACTED]