mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2024-12-19 14:37:34 +00:00
2d587740c1
Restrict system operations of CLI tools with https://man.openbsd.org/pledge.2. https://pkg.go.dev/suah.dev/protect abstracts the OS specific code, i.e. is a NOOP on non-OpenBSD systems. This PR is to gauge upstream interest in this direction; my OpenBSD port of yggdrasil already pledges the daemon, resulting in minimal runtime privileges, but there are still a few rough edges: https://github.com/jasperla/openbsd-wip/blob/master/net/yggdrasil/patches/patch-cmd_yggdrasil_main_go#L80 --------- Co-authored-by: Neil <git@neilalexander.dev>
334 lines
8.4 KiB
Go
334 lines
8.4 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", "Down", "Up", "Pr", "Cost", "Last Error"})
|
|
for _, peer := range resp.Peers {
|
|
state, lasterr, dir, rtt, rxr, txr := "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()
|
|
}
|
|
if peer.RXRate > 0 {
|
|
rxr = peer.RXRate.String() + "/s"
|
|
}
|
|
if peer.TXRate > 0 {
|
|
txr = peer.TXRate.String() + "/s"
|
|
}
|
|
table.Append([]string{
|
|
uristring,
|
|
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.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)
|
|
}
|
|
fmtBool := func(b bool) string {
|
|
if b {
|
|
return "Yes"
|
|
}
|
|
return "-"
|
|
}
|
|
table.SetHeader([]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":
|
|
|
|
default:
|
|
fmt.Println(string(recv.Response))
|
|
}
|
|
|
|
return 0
|
|
}
|