mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
// netbird-load launches N userspace NetBird nodes and has each one periodically
|
|
// TCP-dial a peer to measure connect latency through the wireguard overlay.
|
|
//
|
|
// Usage:
|
|
//
|
|
// NB_SETUP_KEY=nb-... go run . -count 10 -interval 5s -target 100.x.y.z:22
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math/rand/v2"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/netbirdio/netbird/client/embed"
|
|
)
|
|
|
|
func main() {
|
|
setupKey := flag.String("setupkey", os.Getenv("NB_SETUP_KEY"),
|
|
"NetBird reusable setup key (or set NB_SETUP_KEY).")
|
|
mgmt := flag.String("mgmt", os.Getenv("NB_MGMT_URL"),
|
|
"NetBird management URL (empty = embed default, i.e. NetBird Cloud).")
|
|
count := flag.Int("count", 10, "Number of embedded netbird nodes to launch.")
|
|
interval := flag.Duration("interval", 5*time.Second, "Interval between actions per node.")
|
|
hostnamePrefix := flag.String("hostname-prefix", "uncloud", "Hostname prefix for netbird nodes.")
|
|
stateRoot := flag.String("state-dir", "./state", "Directory to keep netbird state (empty = a fresh temp dir).")
|
|
target := flag.String("target", "", "TCP host:port to dial each tick (required).")
|
|
flag.Parse()
|
|
|
|
if *setupKey == "" {
|
|
log.Fatal("missing NetBird setup key: pass -setupkey or set NB_SETUP_KEY.")
|
|
}
|
|
if *target == "" {
|
|
log.Fatal("missing -target: provide a TCP host:port reachable through the netbird network.")
|
|
}
|
|
|
|
root := *stateRoot
|
|
if root == "" {
|
|
var err error
|
|
root, err = os.MkdirTemp("", "netbird-load-*")
|
|
if err != nil {
|
|
log.Fatalf("mktemp: %v", err)
|
|
}
|
|
defer os.RemoveAll(root)
|
|
} else if err := os.MkdirAll(root, 0o700); err != nil {
|
|
log.Fatalf("mkdir state dir: %v", err)
|
|
}
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < *count; i++ {
|
|
wg.Add(1)
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
runNode(ctx, id, *setupKey, *mgmt, *hostnamePrefix, root, *target, *interval)
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func runNode(ctx context.Context, id int, setupKey, mgmt, prefix, stateRoot, target string, interval time.Duration) {
|
|
logPrefix := fmt.Sprintf("[node-%02d] ", id)
|
|
logger := log.New(os.Stderr, logPrefix, log.LstdFlags|log.Lmsgprefix)
|
|
|
|
nodeDir := filepath.Join(stateRoot, fmt.Sprintf("node-%02d", id))
|
|
deviceName := fmt.Sprintf("%s-%02d", prefix, id)
|
|
|
|
client, err := embed.New(embed.Options{
|
|
DeviceName: deviceName,
|
|
SetupKey: setupKey,
|
|
ManagementURL: mgmt,
|
|
StatePath: nodeDir,
|
|
ConfigPath: filepath.Join(nodeDir, "config"),
|
|
// Silence per-node netbird chatter. Swap to os.Stderr to debug.
|
|
LogOutput: io.Discard,
|
|
//NoUserspace: true,
|
|
})
|
|
if err != nil {
|
|
logger.Printf("netbird new: %v", err)
|
|
return
|
|
}
|
|
defer func() {
|
|
stopCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
_ = client.Stop(stopCtx)
|
|
}()
|
|
|
|
upCtx, cancel := context.WithTimeout(ctx, 60*time.Second)
|
|
defer cancel()
|
|
if err := client.Start(upCtx); err != nil {
|
|
logger.Printf("netbird start: %v", err)
|
|
return
|
|
}
|
|
logger.Printf("up as %s", deviceName)
|
|
|
|
// Stagger ticks so all nodes don't fire in lockstep.
|
|
time.Sleep(time.Duration(rand.Int64N(int64(interval))))
|
|
|
|
tick := time.NewTicker(interval)
|
|
defer tick.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-tick.C:
|
|
doDial(ctx, client, target, logger)
|
|
}
|
|
}
|
|
}
|
|
|
|
func doDial(ctx context.Context, c *embed.Client, target string, logger *log.Logger) {
|
|
dctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
|
|
start := time.Now()
|
|
conn, err := c.Dial(dctx, "tcp", target)
|
|
if err != nil {
|
|
logger.Printf("dial %s: %v", target, err)
|
|
return
|
|
}
|
|
latency := time.Since(start)
|
|
_ = conn.Close()
|
|
logger.Printf("dial %s ok in %.1fms", target, float64(latency.Microseconds())/1000)
|
|
}
|