Files
NODEDC_MISSION_CORE/apps/node-agent/internal/node/tailscale.go
T

77 lines
2.0 KiB
Go

package node
import (
"bytes"
"context"
"encoding/json"
"errors"
"net/netip"
"os"
"os/exec"
"time"
)
type TailscaleStatus struct {
Installed bool `json:"installed"`
State string `json:"state"`
Online bool `json:"online"`
Addresses []string `json:"addresses"`
}
type boundedProviderOutput struct{ bytes.Buffer }
func (b *boundedProviderOutput) Write(data []byte) (int, error) {
if b.Len()+len(data) > 1024*1024 {
return 0, errors.New("provider status too large")
}
return b.Buffer.Write(data)
}
func ReadTailscale() TailscaleStatus {
value := TailscaleStatus{State: "not_installed", Addresses: []string{}}
if _, err := os.Stat("/usr/bin/tailscale"); err != nil {
return value
}
value.Installed = true
value.State = "unavailable"
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "/usr/bin/tailscale", "status", "--json", "--peers=false")
// Do not request peer inventory or expose auth URLs, user identities, keys,
// provider diagnostics or profile objects in the product API.
var output boundedProviderOutput
cmd.Stdout = &output
if err := cmd.Run(); err != nil {
return value
}
return parseTailscale(output.Bytes())
}
func parseTailscale(data []byte) TailscaleStatus {
value := TailscaleStatus{Installed: true, State: "unavailable", Addresses: []string{}}
if len(data) > 1024*1024 {
return value
}
var raw struct {
BackendState string
TailscaleIPs []string
Self *struct{ Online bool }
}
if json.Unmarshal(data, &raw) != nil {
return value
}
switch raw.BackendState {
case "Running", "Stopped", "NeedsLogin", "NeedsMachineAuth", "Starting", "NoState":
value.State = raw.BackendState
default:
return value
}
value.Online = raw.BackendState == "Running" && raw.Self != nil && raw.Self.Online
for _, address := range raw.TailscaleIPs {
if ip, err := netip.ParseAddr(address); err == nil {
value.Addresses = append(value.Addresses, ip.String())
}
}
return value
}