From b67c313f449427845b46da123f4767683fdf83b3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 12:22:38 +0100 Subject: [PATCH] Admin socket and `yggdrasilctl` improvements This refactors the request parsing, as well as improving the output for some request types. It also tweaks `yggdrasilctl` output, which should help with #947. --- cmd/yggdrasilctl/main.go | 35 ++++++- src/admin/admin.go | 194 +++++++++++++++++++++------------------ src/core/api.go | 22 ++++- src/multicast/admin.go | 25 ++--- src/tuntap/admin.go | 40 ++++---- 5 files changed, 193 insertions(+), 123 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 78700a4..0ec0ccf 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -16,6 +16,8 @@ import ( "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/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) @@ -135,6 +137,7 @@ func run() int { table.SetBorder(false) table.SetTablePadding("\t") // pad with tabs table.SetNoWhiteSpace(true) + table.SetAutoWrapText(false) switch strings.ToLower(recv.Request.Name) { case "list": @@ -142,9 +145,12 @@ func run() int { if err := json.Unmarshal(recv.Response, &resp); err != nil { panic(err) } - table.SetHeader([]string{"Command", "Arguments"}) + table.SetHeader([]string{"Command", "Arguments", "Description"}) for _, entry := range resp.List { - table.Append([]string{entry.Command, strings.Join(entry.Fields, ", ")}) + for i := range entry.Fields { + entry.Fields[i] = entry.Fields[i] + "=..." + } + table.Append([]string{entry.Command, strings.Join(entry.Fields, ", "), entry.Description}) } table.Render() @@ -238,8 +244,31 @@ func run() int { 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 tuntap.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() + default: - panic("unknown response type: " + recv.Request.Name) + fmt.Println(string(recv.Response)) } return 0 diff --git a/src/admin/admin.go b/src/admin/admin.go index 4e98c89..376f79a 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -43,6 +43,7 @@ type AdminSocketResponse struct { } type handler struct { + desc string // What does the endpoint do? args []string // List of human-readable argument names handler core.AddHandlerFunc // First is input map, second is output } @@ -52,16 +53,18 @@ type ListResponse struct { } type ListEntry struct { - Command string `json:"command"` - Fields []string `json:"fields,omitempty"` + Command string `json:"command"` + Description string `json:"description"` + Fields []string `json:"fields,omitempty"` } // AddHandler is called for each admin function to add the handler and help documentation to the API. -func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc core.AddHandlerFunc) error { +func (a *AdminSocket) AddHandler(name, desc string, args []string, handlerfunc core.AddHandlerFunc) error { if _, ok := a.handlers[strings.ToLower(name)]; ok { return errors.New("handler already exists") } a.handlers[strings.ToLower(name)] = handler{ + desc: desc, args: args, handler: handlerfunc, } @@ -81,12 +84,13 @@ func New(c *core.Core, log util.Logger, opts ...SetupOption) (*AdminSocket, erro if a.config.listenaddr == "none" || a.config.listenaddr == "" { return nil, nil } - _ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) { + _ = a.AddHandler("list", "List available commands", []string{}, func(_ json.RawMessage) (interface{}, error) { res := &ListResponse{} for name, handler := range a.handlers { res.List = append(res.List, ListEntry{ - Command: name, - Fields: handler.args, + Command: name, + Description: handler.desc, + Fields: handler.args, }) } sort.SliceStable(res.List, func(i, j int) bool { @@ -100,61 +104,76 @@ func New(c *core.Core, log util.Logger, opts ...SetupOption) (*AdminSocket, erro } func (a *AdminSocket) SetupAdminHandlers() { - _ = a.AddHandler("getSelf", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetSelfRequest{} - res := &GetSelfResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := a.getSelfHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) - _ = a.AddHandler("getPeers", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetPeersRequest{} - res := &GetPeersResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := a.getPeersHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) - _ = a.AddHandler("getDHT", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetDHTRequest{} - res := &GetDHTResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := a.getDHTHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) - _ = a.AddHandler("getPaths", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetPathsRequest{} - res := &GetPathsResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := a.getPathsHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) - _ = a.AddHandler("getSessions", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetSessionsRequest{} - res := &GetSessionsResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := a.getSessionsHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) + _ = a.AddHandler( + "getSelf", "Show details about this node", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetSelfRequest{} + res := &GetSelfResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getSelfHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) + _ = a.AddHandler( + "getPeers", "Show directly connected peers", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetPeersRequest{} + res := &GetPeersResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getPeersHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) + _ = a.AddHandler( + "getDHT", "Show known DHT entries", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetDHTRequest{} + res := &GetDHTResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getDHTHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) + _ = a.AddHandler( + "getPaths", "Show established paths through this node", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetPathsRequest{} + res := &GetPathsResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getPathsHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) + _ = a.AddHandler( + "getSessions", "Show established traffic sessions with remote nodes", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetSessionsRequest{} + res := &GetSessionsResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getSessionsHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) //_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) //_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler) //_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler) @@ -279,35 +298,34 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { for { var err error var buf json.RawMessage - _ = decoder.Decode(&buf) var resp AdminSocketResponse - resp.Status = "success" - if err = json.Unmarshal(buf, &resp.Request); err == nil { - if resp.Request.Name == "" { - resp.Status = "error" - resp.Response, _ = json.Marshal(ErrorResponse{ - Error: "No request specified", - }) - } else if h, ok := a.handlers[strings.ToLower(resp.Request.Name)]; ok { - res, err := h.handler(buf) - if err != nil { - resp.Status = "error" - resp.Response, _ = json.Marshal(ErrorResponse{ - Error: err.Error(), - }) - } - if resp.Response, err = json.Marshal(res); err != nil { - resp.Status = "error" - resp.Response, _ = json.Marshal(ErrorResponse{ - Error: err.Error(), - }) - } - } else { - resp.Status = "error" - resp.Response, _ = json.Marshal(ErrorResponse{ - Error: fmt.Sprintf("Unknown action '%s', try 'list' for help", resp.Request.Name), - }) + if err := func() error { + if err = decoder.Decode(&buf); err != nil { + return fmt.Errorf("Failed to find request") } + if err = json.Unmarshal(buf, &resp.Request); err != nil { + return fmt.Errorf("Failed to unmarshal request") + } + if resp.Request.Name == "" { + return fmt.Errorf("No request specified") + } + reqname := strings.ToLower(resp.Request.Name) + handler, ok := a.handlers[reqname] + if !ok { + return fmt.Errorf("Unknown action '%s', try 'list' for help", reqname) + } + res, err := handler.handler(buf) + if err != nil { + return fmt.Errorf("Handler returned error: %w", err) + } + if resp.Response, err = json.Marshal(res); err != nil { + return fmt.Errorf("Failed to marshal response: %w", err) + } + resp.Status = "success" + return nil + }(); err != nil { + resp.Status = "error" + resp.Error = err.Error() } if err = encoder.Encode(resp); err != nil { a.log.Debugln("Encode error:", err) diff --git a/src/core/api.go b/src/core/api.go index 657b551..a67ecf3 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -273,7 +273,7 @@ func (c *Core) PublicKey() ed25519.PublicKey { // Hack to get the admin stuff working, TODO something cleaner type AddHandler interface { - AddHandler(name string, args []string, handlerfunc AddHandlerFunc) error + AddHandler(name, desc string, args []string, handlerfunc AddHandlerFunc) error } type AddHandlerFunc func(json.RawMessage) (interface{}, error) @@ -281,16 +281,28 @@ type AddHandlerFunc func(json.RawMessage) (interface{}, error) // SetAdmin must be called after Init and before Start. // It sets the admin handler for NodeInfo and the Debug admin functions. func (c *Core) SetAdmin(a AddHandler) error { - if err := a.AddHandler("getNodeInfo", []string{"key"}, c.proto.nodeinfo.nodeInfoAdminHandler); err != nil { + if err := a.AddHandler( + "getNodeInfo", "Request nodeinfo from a remote node by its public key", []string{"key"}, + c.proto.nodeinfo.nodeInfoAdminHandler, + ); err != nil { return err } - if err := a.AddHandler("debug_remoteGetSelf", []string{"key"}, c.proto.getSelfHandler); err != nil { + if err := a.AddHandler( + "debug_remoteGetSelf", "Debug use only", []string{"key"}, + c.proto.getSelfHandler, + ); err != nil { return err } - if err := a.AddHandler("debug_remoteGetPeers", []string{"key"}, c.proto.getPeersHandler); err != nil { + if err := a.AddHandler( + "debug_remoteGetPeers", "Debug use only", []string{"key"}, + c.proto.getPeersHandler, + ); err != nil { return err } - if err := a.AddHandler("debug_remoteGetDHT", []string{"key"}, c.proto.getDHTHandler); err != nil { + if err := a.AddHandler( + "debug_remoteGetDHT", "Debug use only", []string{"key"}, + c.proto.getDHTHandler, + ); err != nil { return err } return nil diff --git a/src/multicast/admin.go b/src/multicast/admin.go index 2ae6ec0..9bcc257 100644 --- a/src/multicast/admin.go +++ b/src/multicast/admin.go @@ -20,15 +20,18 @@ func (m *Multicast) getMulticastInterfacesHandler(req *GetMulticastInterfacesReq } func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { - _ = a.AddHandler("getMulticastInterfaces", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetMulticastInterfacesRequest{} - res := &GetMulticastInterfacesResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := m.getMulticastInterfacesHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) + _ = a.AddHandler( + "getMulticastInterfaces", "Show which interfaces multicast is enabled on", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetMulticastInterfacesRequest{} + res := &GetMulticastInterfacesResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := m.getMulticastInterfacesHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) } diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 862a3c6..24521fe 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -7,31 +7,39 @@ import ( ) type GetTUNRequest struct{} -type GetTUNResponse map[string]TUNEntry +type GetTUNResponse struct { + Enabled bool `json:"enabled"` + Name string `json:"name,omitempty"` + MTU uint64 `json:"mtu,omitempty"` +} type TUNEntry struct { MTU uint64 `json:"mtu"` } func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error { - *res = GetTUNResponse{ - t.Name(): TUNEntry{ - MTU: t.MTU(), - }, + res.Enabled = t.isEnabled + if !t.isEnabled { + return nil } + res.Name = t.Name() + res.MTU = t.MTU() return nil } func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { - _ = a.AddHandler("getTunTap", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetTUNRequest{} - res := &GetTUNResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := t.getTUNHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) + _ = a.AddHandler( + "getTun", "Show information about the node's TUN interface", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetTUNRequest{} + res := &GetTUNResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := t.getTUNHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) }