mirror of
				https://github.com/yggdrasil-network/yggdrasil-go.git
				synced 2025-11-04 11:15:07 +03:00 
			
		
		
		
	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>
		
			
				
	
	
		
			333 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			333 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
 | 
						|
}
 |