411 lines
11 KiB
Go
411 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"suah.dev/protect"
|
|
|
|
"github.com/olekukonko/tablewriter"
|
|
"github.com/olekukonko/tablewriter/renderer"
|
|
"github.com/olekukonko/tablewriter/tw"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
|
)
|
|
|
|
func main() {
|
|
// read config, speak DNS/TCP and/or over a UNIX socket
|
|
if err := protect.Pledge("stdio rpath inet unix dns"); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// makes sure we can use defer and still return an error code to the OS
|
|
os.Exit(run())
|
|
}
|
|
|
|
func run() int {
|
|
logbuffer := &bytes.Buffer{}
|
|
logger := log.New(logbuffer, "", log.Flags())
|
|
|
|
defer func() int {
|
|
if r := recover(); r != nil {
|
|
logger.Println("Fatal error:", r)
|
|
fmt.Print(logbuffer)
|
|
return 1
|
|
}
|
|
return 0
|
|
}()
|
|
|
|
cmdLineEnv := newCmdLineEnv()
|
|
cmdLineEnv.parseFlagsAndArgs()
|
|
|
|
if cmdLineEnv.ver {
|
|
fmt.Println("Build name:", version.BuildName())
|
|
fmt.Println("Build version:", version.BuildVersion())
|
|
fmt.Println("To get the version number of the running Kaya node, run", os.Args[0], "getSelf")
|
|
return 0
|
|
}
|
|
|
|
if len(cmdLineEnv.args) == 0 {
|
|
flag.Usage()
|
|
return 0
|
|
}
|
|
|
|
cmdLineEnv.setEndpoint(logger)
|
|
|
|
var conn net.Conn
|
|
u, err := url.Parse(cmdLineEnv.endpoint)
|
|
if err == nil {
|
|
switch strings.ToLower(u.Scheme) {
|
|
case "unix":
|
|
logger.Println("Connecting to UNIX socket", cmdLineEnv.endpoint[7:])
|
|
conn, err = net.Dial("unix", cmdLineEnv.endpoint[7:])
|
|
case "tcp":
|
|
logger.Println("Connecting to TCP socket", u.Host)
|
|
conn, err = net.Dial("tcp", u.Host)
|
|
default:
|
|
logger.Println("Unknown protocol or malformed address - check your endpoint")
|
|
err = errors.New("protocol not supported")
|
|
}
|
|
} else {
|
|
logger.Println("Connecting to TCP socket", u.Host)
|
|
conn, err = net.Dial("tcp", cmdLineEnv.endpoint)
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// config and socket are done, work without unprivileges
|
|
if err := protect.Pledge("stdio"); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
logger.Println("Connected")
|
|
defer conn.Close()
|
|
|
|
decoder := json.NewDecoder(conn)
|
|
encoder := json.NewEncoder(conn)
|
|
send := &admin.AdminSocketRequest{}
|
|
recv := &admin.AdminSocketResponse{}
|
|
args := map[string]string{}
|
|
for c, a := range cmdLineEnv.args {
|
|
if c == 0 {
|
|
if strings.HasPrefix(a, "-") {
|
|
logger.Printf("Ignoring flag %s as it should be specified before other parameters\n", a)
|
|
continue
|
|
}
|
|
logger.Printf("Sending request: %v\n", a)
|
|
send.Name = a
|
|
continue
|
|
}
|
|
tokens := strings.SplitN(a, "=", 2)
|
|
switch {
|
|
case len(tokens) == 1:
|
|
logger.Println("Ignoring invalid argument:", a)
|
|
default:
|
|
args[tokens[0]] = tokens[1]
|
|
}
|
|
}
|
|
if send.Arguments, err = json.Marshal(args); err != nil {
|
|
panic(err)
|
|
}
|
|
if err := encoder.Encode(&send); err != nil {
|
|
panic(err)
|
|
}
|
|
logger.Printf("Request sent")
|
|
if err := decoder.Decode(&recv); err != nil {
|
|
panic(err)
|
|
}
|
|
if recv.Status == "error" {
|
|
if err := recv.Error; err != "" {
|
|
fmt.Println("Admin socket returned an error:", err)
|
|
} else {
|
|
fmt.Println("Admin socket returned an error but didn't specify any error text")
|
|
}
|
|
return 1
|
|
}
|
|
if cmdLineEnv.injson {
|
|
if json, err := json.MarshalIndent(recv.Response, "", " "); err == nil {
|
|
fmt.Println(string(json))
|
|
}
|
|
return 0
|
|
}
|
|
|
|
opts := []tablewriter.Option{
|
|
tablewriter.WithRowAlignment(tw.AlignLeft),
|
|
tablewriter.WithHeaderAlignment(tw.AlignCenter),
|
|
tablewriter.WithHeaderAutoFormat(tw.Off),
|
|
tablewriter.WithDebug(false),
|
|
}
|
|
if !cmdLineEnv.borders {
|
|
opts = append(opts, tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
|
|
Borders: tw.BorderNone,
|
|
Settings: tw.Settings{
|
|
Lines: tw.LinesNone,
|
|
Separators: tw.SeparatorsNone,
|
|
},
|
|
})))
|
|
}
|
|
table := tablewriter.NewTable(os.Stdout, opts...)
|
|
|
|
switch strings.ToLower(send.Name) {
|
|
case "list":
|
|
var resp admin.ListResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
table.Header([]string{"Command", "Arguments", "Description"})
|
|
for _, entry := range resp.List {
|
|
for i := range entry.Fields {
|
|
entry.Fields[i] = entry.Fields[i] + "=..."
|
|
}
|
|
_ = table.Append([]string{entry.Command, strings.Join(entry.Fields, ", "), entry.Description})
|
|
}
|
|
_ = table.Render()
|
|
|
|
case "getself":
|
|
var resp admin.GetSelfResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
_ = table.Append([]string{"Build name:", resp.BuildName})
|
|
_ = table.Append([]string{"Build version:", resp.BuildVersion})
|
|
_ = table.Append([]string{"IPv6 address:", resp.IPAddress})
|
|
_ = table.Append([]string{"IPv6 subnet:", resp.Subnet})
|
|
_ = table.Append([]string{"Routing table size:", fmt.Sprintf("%d", resp.RoutingEntries)})
|
|
_ = table.Append([]string{"Public key:", resp.PublicKey})
|
|
_ = table.Render()
|
|
|
|
case "getpeers":
|
|
var resp admin.GetPeersResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
table.Header([]string{"URI", "Remote", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Down", "Up", "Pr", "Cost", "Last Error"})
|
|
for _, peer := range resp.Peers {
|
|
state, lasterr, dir, rtt, rxr, txr := "Up", "-", "Out", "-", "-", "-"
|
|
if !peer.Up {
|
|
if state = "Down"; peer.LastError != "" {
|
|
lasterr = fmt.Sprintf("%s ago: %s", peer.LastErrorTime.Round(time.Second), peer.LastError)
|
|
}
|
|
} else if rttms := float64(peer.Latency.Microseconds()) / 1000; rttms > 0 {
|
|
rtt = fmt.Sprintf("%.02fms", rttms)
|
|
}
|
|
if peer.Inbound {
|
|
dir = "In"
|
|
}
|
|
parsedURI, parseErr := url.Parse(peer.URI)
|
|
uristring := peer.URI
|
|
if parseErr == nil {
|
|
parsedURI.RawQuery = ""
|
|
uristring = parsedURI.String()
|
|
}
|
|
if peer.RXRate > 0 {
|
|
rxr = peer.RXRate.String() + "/s"
|
|
}
|
|
if peer.TXRate > 0 {
|
|
txr = peer.TXRate.String() + "/s"
|
|
}
|
|
remote := peerRemoteHost(parsedURI, parseErr)
|
|
_ = table.Append([]string{
|
|
uristring,
|
|
remote,
|
|
state,
|
|
dir,
|
|
peer.IPAddress,
|
|
(time.Duration(peer.Uptime) * time.Second).String(),
|
|
rtt,
|
|
peer.RXBytes.String(),
|
|
peer.TXBytes.String(),
|
|
rxr,
|
|
txr,
|
|
fmt.Sprintf("%d", peer.Priority),
|
|
fmt.Sprintf("%d", peer.Cost),
|
|
lasterr,
|
|
})
|
|
}
|
|
_ = table.Render()
|
|
|
|
case "gettree":
|
|
var resp admin.GetTreeResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
table.Header([]string{"Tree", "IP Address", "Sequence"})
|
|
for _, line := range formatTreeRows(resp.Tree) {
|
|
_ = table.Append([]string{line.Branch, line.IPAddress, fmt.Sprintf("%d", line.Sequence)})
|
|
}
|
|
_ = table.Render()
|
|
|
|
case "getpaths":
|
|
var resp admin.GetPathsResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
table.Header([]string{"Public Key", "IP Address", "Path", "Seq"})
|
|
for _, p := range resp.Paths {
|
|
_ = table.Append([]string{
|
|
p.PublicKey,
|
|
p.IPAddress,
|
|
fmt.Sprintf("%v", p.Path),
|
|
fmt.Sprintf("%d", p.Sequence),
|
|
})
|
|
}
|
|
_ = table.Render()
|
|
|
|
case "getsessions":
|
|
var resp admin.GetSessionsResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
table.Header([]string{"Public Key", "IP Address", "Uptime", "RX", "TX"})
|
|
for _, p := range resp.Sessions {
|
|
_ = table.Append([]string{
|
|
p.PublicKey,
|
|
p.IPAddress,
|
|
(time.Duration(p.Uptime) * time.Second).String(),
|
|
p.RXBytes.String(),
|
|
p.TXBytes.String(),
|
|
})
|
|
}
|
|
_ = table.Render()
|
|
|
|
case "getnodeinfo":
|
|
var resp core.GetNodeInfoResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
for _, v := range resp {
|
|
fmt.Println(string(v))
|
|
break
|
|
}
|
|
|
|
case "getmulticastinterfaces":
|
|
var resp multicast.GetMulticastInterfacesResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
fmtBool := func(b bool) string {
|
|
if b {
|
|
return "Yes"
|
|
}
|
|
return "-"
|
|
}
|
|
table.Header([]string{"Name", "Listen Address", "Beacon", "Listen", "Password"})
|
|
for _, p := range resp.Interfaces {
|
|
_ = table.Append([]string{
|
|
p.Name,
|
|
p.Address,
|
|
fmtBool(p.Beacon),
|
|
fmtBool(p.Listen),
|
|
fmtBool(p.Password),
|
|
})
|
|
}
|
|
_ = table.Render()
|
|
|
|
case "gettun":
|
|
var resp tun.GetTUNResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
_ = table.Append([]string{"TUN enabled:", fmt.Sprintf("%#v", resp.Enabled)})
|
|
if resp.Enabled {
|
|
_ = table.Append([]string{"Interface name:", resp.Name})
|
|
_ = table.Append([]string{"Interface MTU:", fmt.Sprintf("%d", resp.MTU)})
|
|
}
|
|
_ = table.Render()
|
|
|
|
case "addpeer", "removepeer", "setpeertraffic":
|
|
|
|
default:
|
|
fmt.Println(string(recv.Response))
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func peerRemoteHost(parsedURI *url.URL, parseErr error) string {
|
|
if parseErr != nil || parsedURI == nil {
|
|
return "-"
|
|
}
|
|
host := parsedURI.Host
|
|
if splitHost, _, err := net.SplitHostPort(host); err == nil {
|
|
return splitHost
|
|
}
|
|
if host == "" {
|
|
return "-"
|
|
}
|
|
return host
|
|
}
|
|
|
|
type treeLine struct {
|
|
Branch string
|
|
IPAddress string
|
|
Sequence uint64
|
|
}
|
|
|
|
func formatTreeRows(entries []admin.TreeEntry) []treeLine {
|
|
if len(entries) == 0 {
|
|
return nil
|
|
}
|
|
children := make(map[string][]admin.TreeEntry, len(entries))
|
|
byKey := make(map[string]admin.TreeEntry, len(entries))
|
|
for _, entry := range entries {
|
|
byKey[entry.PublicKey] = entry
|
|
}
|
|
roots := make([]admin.TreeEntry, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if entry.Parent == "" || entry.Parent == entry.PublicKey || byKey[entry.Parent].PublicKey == "" {
|
|
roots = append(roots, entry)
|
|
continue
|
|
}
|
|
children[entry.Parent] = append(children[entry.Parent], entry)
|
|
}
|
|
sort.Slice(roots, func(i, j int) bool { return roots[i].PublicKey < roots[j].PublicKey })
|
|
for parent := range children {
|
|
sort.Slice(children[parent], func(i, j int) bool {
|
|
return children[parent][i].PublicKey < children[parent][j].PublicKey
|
|
})
|
|
}
|
|
rows := make([]treeLine, 0, len(entries))
|
|
var walk func(admin.TreeEntry, string, bool)
|
|
walk = func(node admin.TreeEntry, prefix string, last bool) {
|
|
branchPrefix := ""
|
|
nextPrefix := ""
|
|
if prefix != "" {
|
|
if last {
|
|
branchPrefix = prefix + "└─ "
|
|
nextPrefix = prefix + " "
|
|
} else {
|
|
branchPrefix = prefix + "├─ "
|
|
nextPrefix = prefix + "│ "
|
|
}
|
|
}
|
|
label := node.PublicKey
|
|
if len(label) > 16 {
|
|
label = label[:16] + "…"
|
|
}
|
|
rows = append(rows, treeLine{Branch: branchPrefix + label, IPAddress: node.IPAddress, Sequence: node.Sequence})
|
|
kids := children[node.PublicKey]
|
|
for i, child := range kids {
|
|
walk(child, nextPrefix, i == len(kids)-1)
|
|
}
|
|
}
|
|
for i, root := range roots {
|
|
walk(root, "", i == len(roots)-1)
|
|
}
|
|
return rows
|
|
}
|