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.
This commit is contained in:
Neil Alexander 2022-09-24 12:22:38 +01:00
parent 5ef61faeff
commit b67c313f44
No known key found for this signature in database
GPG Key ID: A02A2019A2BB0944
5 changed files with 193 additions and 123 deletions

View File

@ -16,6 +16,8 @@ import (
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/core" "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" "github.com/yggdrasil-network/yggdrasil-go/src/version"
) )
@ -135,6 +137,7 @@ func run() int {
table.SetBorder(false) table.SetBorder(false)
table.SetTablePadding("\t") // pad with tabs table.SetTablePadding("\t") // pad with tabs
table.SetNoWhiteSpace(true) table.SetNoWhiteSpace(true)
table.SetAutoWrapText(false)
switch strings.ToLower(recv.Request.Name) { switch strings.ToLower(recv.Request.Name) {
case "list": case "list":
@ -142,9 +145,12 @@ func run() int {
if err := json.Unmarshal(recv.Response, &resp); err != nil { if err := json.Unmarshal(recv.Response, &resp); err != nil {
panic(err) panic(err)
} }
table.SetHeader([]string{"Command", "Arguments"}) table.SetHeader([]string{"Command", "Arguments", "Description"})
for _, entry := range resp.List { 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() table.Render()
@ -238,8 +244,31 @@ func run() int {
break 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: default:
panic("unknown response type: " + recv.Request.Name) fmt.Println(string(recv.Response))
} }
return 0 return 0

View File

@ -43,6 +43,7 @@ type AdminSocketResponse struct {
} }
type handler struct { type handler struct {
desc string // What does the endpoint do?
args []string // List of human-readable argument names args []string // List of human-readable argument names
handler core.AddHandlerFunc // First is input map, second is output handler core.AddHandlerFunc // First is input map, second is output
} }
@ -53,15 +54,17 @@ type ListResponse struct {
type ListEntry struct { type ListEntry struct {
Command string `json:"command"` Command string `json:"command"`
Description string `json:"description"`
Fields []string `json:"fields,omitempty"` Fields []string `json:"fields,omitempty"`
} }
// AddHandler is called for each admin function to add the handler and help documentation to the API. // 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 { if _, ok := a.handlers[strings.ToLower(name)]; ok {
return errors.New("handler already exists") return errors.New("handler already exists")
} }
a.handlers[strings.ToLower(name)] = handler{ a.handlers[strings.ToLower(name)] = handler{
desc: desc,
args: args, args: args,
handler: handlerfunc, handler: handlerfunc,
} }
@ -81,11 +84,12 @@ func New(c *core.Core, log util.Logger, opts ...SetupOption) (*AdminSocket, erro
if a.config.listenaddr == "none" || a.config.listenaddr == "" { if a.config.listenaddr == "none" || a.config.listenaddr == "" {
return nil, nil 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{} res := &ListResponse{}
for name, handler := range a.handlers { for name, handler := range a.handlers {
res.List = append(res.List, ListEntry{ res.List = append(res.List, ListEntry{
Command: name, Command: name,
Description: handler.desc,
Fields: handler.args, Fields: handler.args,
}) })
} }
@ -100,7 +104,9 @@ func New(c *core.Core, log util.Logger, opts ...SetupOption) (*AdminSocket, erro
} }
func (a *AdminSocket) SetupAdminHandlers() { func (a *AdminSocket) SetupAdminHandlers() {
_ = a.AddHandler("getSelf", []string{}, func(in json.RawMessage) (interface{}, error) { _ = a.AddHandler(
"getSelf", "Show details about this node", []string{},
func(in json.RawMessage) (interface{}, error) {
req := &GetSelfRequest{} req := &GetSelfRequest{}
res := &GetSelfResponse{} res := &GetSelfResponse{}
if err := json.Unmarshal(in, &req); err != nil { if err := json.Unmarshal(in, &req); err != nil {
@ -110,8 +116,11 @@ func (a *AdminSocket) SetupAdminHandlers() {
return nil, err return nil, err
} }
return res, nil return res, nil
}) },
_ = a.AddHandler("getPeers", []string{}, func(in json.RawMessage) (interface{}, error) { )
_ = a.AddHandler(
"getPeers", "Show directly connected peers", []string{},
func(in json.RawMessage) (interface{}, error) {
req := &GetPeersRequest{} req := &GetPeersRequest{}
res := &GetPeersResponse{} res := &GetPeersResponse{}
if err := json.Unmarshal(in, &req); err != nil { if err := json.Unmarshal(in, &req); err != nil {
@ -121,8 +130,11 @@ func (a *AdminSocket) SetupAdminHandlers() {
return nil, err return nil, err
} }
return res, nil return res, nil
}) },
_ = a.AddHandler("getDHT", []string{}, func(in json.RawMessage) (interface{}, error) { )
_ = a.AddHandler(
"getDHT", "Show known DHT entries", []string{},
func(in json.RawMessage) (interface{}, error) {
req := &GetDHTRequest{} req := &GetDHTRequest{}
res := &GetDHTResponse{} res := &GetDHTResponse{}
if err := json.Unmarshal(in, &req); err != nil { if err := json.Unmarshal(in, &req); err != nil {
@ -132,8 +144,11 @@ func (a *AdminSocket) SetupAdminHandlers() {
return nil, err return nil, err
} }
return res, nil return res, nil
}) },
_ = a.AddHandler("getPaths", []string{}, func(in json.RawMessage) (interface{}, error) { )
_ = a.AddHandler(
"getPaths", "Show established paths through this node", []string{},
func(in json.RawMessage) (interface{}, error) {
req := &GetPathsRequest{} req := &GetPathsRequest{}
res := &GetPathsResponse{} res := &GetPathsResponse{}
if err := json.Unmarshal(in, &req); err != nil { if err := json.Unmarshal(in, &req); err != nil {
@ -143,8 +158,11 @@ func (a *AdminSocket) SetupAdminHandlers() {
return nil, err return nil, err
} }
return res, nil return res, nil
}) },
_ = a.AddHandler("getSessions", []string{}, func(in json.RawMessage) (interface{}, error) { )
_ = a.AddHandler(
"getSessions", "Show established traffic sessions with remote nodes", []string{},
func(in json.RawMessage) (interface{}, error) {
req := &GetSessionsRequest{} req := &GetSessionsRequest{}
res := &GetSessionsResponse{} res := &GetSessionsResponse{}
if err := json.Unmarshal(in, &req); err != nil { if err := json.Unmarshal(in, &req); err != nil {
@ -154,7 +172,8 @@ func (a *AdminSocket) SetupAdminHandlers() {
return nil, err return nil, err
} }
return res, nil return res, nil
}) },
)
//_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) //_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler)
//_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler) //_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler)
//_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler) //_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler)
@ -279,35 +298,34 @@ func (a *AdminSocket) handleRequest(conn net.Conn) {
for { for {
var err error var err error
var buf json.RawMessage var buf json.RawMessage
_ = decoder.Decode(&buf)
var resp AdminSocketResponse var resp AdminSocketResponse
resp.Status = "success" if err := func() error {
if err = json.Unmarshal(buf, &resp.Request); err == nil { 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 == "" { if resp.Request.Name == "" {
resp.Status = "error" return fmt.Errorf("No request specified")
resp.Response, _ = json.Marshal(ErrorResponse{ }
Error: "No request specified", reqname := strings.ToLower(resp.Request.Name)
}) handler, ok := a.handlers[reqname]
} else if h, ok := a.handlers[strings.ToLower(resp.Request.Name)]; ok { if !ok {
res, err := h.handler(buf) return fmt.Errorf("Unknown action '%s', try 'list' for help", reqname)
}
res, err := handler.handler(buf)
if err != nil { if err != nil {
resp.Status = "error" return fmt.Errorf("Handler returned error: %w", err)
resp.Response, _ = json.Marshal(ErrorResponse{
Error: err.Error(),
})
} }
if resp.Response, err = json.Marshal(res); err != nil { if resp.Response, err = json.Marshal(res); err != nil {
resp.Status = "error" return fmt.Errorf("Failed to marshal response: %w", err)
resp.Response, _ = json.Marshal(ErrorResponse{
Error: err.Error(),
})
} }
} else { resp.Status = "success"
return nil
}(); err != nil {
resp.Status = "error" resp.Status = "error"
resp.Response, _ = json.Marshal(ErrorResponse{ resp.Error = err.Error()
Error: fmt.Sprintf("Unknown action '%s', try 'list' for help", resp.Request.Name),
})
}
} }
if err = encoder.Encode(resp); err != nil { if err = encoder.Encode(resp); err != nil {
a.log.Debugln("Encode error:", err) a.log.Debugln("Encode error:", err)

View File

@ -273,7 +273,7 @@ func (c *Core) PublicKey() ed25519.PublicKey {
// Hack to get the admin stuff working, TODO something cleaner // Hack to get the admin stuff working, TODO something cleaner
type AddHandler interface { 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) 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. // SetAdmin must be called after Init and before Start.
// It sets the admin handler for NodeInfo and the Debug admin functions. // It sets the admin handler for NodeInfo and the Debug admin functions.
func (c *Core) SetAdmin(a AddHandler) error { 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 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 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 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 err
} }
return nil return nil

View File

@ -20,7 +20,9 @@ func (m *Multicast) getMulticastInterfacesHandler(req *GetMulticastInterfacesReq
} }
func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) {
_ = a.AddHandler("getMulticastInterfaces", []string{}, func(in json.RawMessage) (interface{}, error) { _ = a.AddHandler(
"getMulticastInterfaces", "Show which interfaces multicast is enabled on", []string{},
func(in json.RawMessage) (interface{}, error) {
req := &GetMulticastInterfacesRequest{} req := &GetMulticastInterfacesRequest{}
res := &GetMulticastInterfacesResponse{} res := &GetMulticastInterfacesResponse{}
if err := json.Unmarshal(in, &req); err != nil { if err := json.Unmarshal(in, &req); err != nil {
@ -30,5 +32,6 @@ func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) {
return nil, err return nil, err
} }
return res, nil return res, nil
}) },
)
} }

View File

@ -7,23 +7,30 @@ import (
) )
type GetTUNRequest struct{} 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 { type TUNEntry struct {
MTU uint64 `json:"mtu"` MTU uint64 `json:"mtu"`
} }
func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error { func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error {
*res = GetTUNResponse{ res.Enabled = t.isEnabled
t.Name(): TUNEntry{ if !t.isEnabled {
MTU: t.MTU(), return nil
},
} }
res.Name = t.Name()
res.MTU = t.MTU()
return nil return nil
} }
func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) {
_ = a.AddHandler("getTunTap", []string{}, func(in json.RawMessage) (interface{}, error) { _ = a.AddHandler(
"getTun", "Show information about the node's TUN interface", []string{},
func(in json.RawMessage) (interface{}, error) {
req := &GetTUNRequest{} req := &GetTUNRequest{}
res := &GetTUNResponse{} res := &GetTUNResponse{}
if err := json.Unmarshal(in, &req); err != nil { if err := json.Unmarshal(in, &req); err != nil {
@ -33,5 +40,6 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) {
return nil, err return nil, err
} }
return res, nil return res, nil
}) },
)
} }