mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-04-28 06:05:06 +03:00

The CLI is simple, but parses config files and communicates over the network with arbitrary endpoints. Limit system operations to that is needed before doing anything and drop all priviledges after config file and socket handling is done, i.e. do parse and speak over the network completely unprivileged.
313 lines
8 KiB
Go
313 lines
8 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"suah.dev/protect"
|
|
|
|
"github.com/olekukonko/tablewriter"
|
|
"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 Yggdrasil 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
|
|
}
|
|
|
|
table := tablewriter.NewWriter(os.Stdout)
|
|
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
table.SetAutoFormatHeaders(false)
|
|
table.SetCenterSeparator("")
|
|
table.SetColumnSeparator("")
|
|
table.SetRowSeparator("")
|
|
table.SetHeaderLine(false)
|
|
table.SetBorder(false)
|
|
table.SetTablePadding("\t") // pad with tabs
|
|
table.SetNoWhiteSpace(true)
|
|
table.SetAutoWrapText(false)
|
|
|
|
switch strings.ToLower(send.Name) {
|
|
case "list":
|
|
var resp admin.ListResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
table.SetHeader([]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.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Pr", "Cost", "Last Error"})
|
|
for _, peer := range resp.Peers {
|
|
state, lasterr, dir, rtt := "Up", "-", "Out", "-"
|
|
if !peer.Up {
|
|
state, lasterr = "Down", 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"
|
|
}
|
|
uristring := peer.URI
|
|
if uri, err := url.Parse(peer.URI); err == nil {
|
|
uri.RawQuery = ""
|
|
uristring = uri.String()
|
|
}
|
|
table.Append([]string{
|
|
uristring,
|
|
state,
|
|
dir,
|
|
peer.IPAddress,
|
|
(time.Duration(peer.Uptime) * time.Second).String(),
|
|
rtt,
|
|
peer.RXBytes.String(),
|
|
peer.TXBytes.String(),
|
|
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.SetHeader([]string{"Public Key", "IP Address", "Port", "Rest"})
|
|
table.SetHeader([]string{"Public Key", "IP Address", "Parent", "Sequence"})
|
|
for _, tree := range resp.Tree {
|
|
table.Append([]string{
|
|
tree.PublicKey,
|
|
tree.IPAddress,
|
|
tree.Parent,
|
|
fmt.Sprintf("%d", tree.Sequence),
|
|
//fmt.Sprintf("%d", dht.Port),
|
|
//fmt.Sprintf("%d", dht.Rest),
|
|
})
|
|
}
|
|
table.Render()
|
|
|
|
case "getpaths":
|
|
var resp admin.GetPathsResponse
|
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
|
panic(err)
|
|
}
|
|
table.SetHeader([]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.SetHeader([]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)
|
|
}
|
|
table.SetHeader([]string{"Interface"})
|
|
for _, p := range resp.Interfaces {
|
|
table.Append([]string{p})
|
|
}
|
|
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":
|
|
|
|
default:
|
|
fmt.Println(string(recv.Response))
|
|
}
|
|
|
|
return 0
|
|
}
|