2019-05-19 16:27:48 +00:00
|
|
|
package admin
|
2018-01-21 00:17:15 +00:00
|
|
|
|
2018-06-12 22:50:08 +00:00
|
|
|
import (
|
|
|
|
"encoding/json"
|
2019-05-19 21:02:04 +00:00
|
|
|
"errors"
|
2018-06-12 22:50:08 +00:00
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
2022-09-03 09:50:43 +00:00
|
|
|
"sort"
|
2021-05-10 21:47:28 +00:00
|
|
|
|
2018-06-12 22:50:08 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2018-07-07 11:08:52 +00:00
|
|
|
|
2021-05-23 19:42:26 +00:00
|
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
2022-09-03 10:54:46 +00:00
|
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/util"
|
2018-06-12 22:50:08 +00:00
|
|
|
)
|
2018-01-21 00:17:15 +00:00
|
|
|
|
2018-01-21 12:57:54 +00:00
|
|
|
// TODO: Add authentication
|
2018-01-21 00:17:15 +00:00
|
|
|
|
2019-05-19 16:27:48 +00:00
|
|
|
type AdminSocket struct {
|
2022-09-03 10:54:46 +00:00
|
|
|
core *core.Core
|
|
|
|
log util.Logger
|
|
|
|
listener net.Listener
|
|
|
|
handlers map[string]handler
|
|
|
|
done chan struct{}
|
|
|
|
config struct {
|
|
|
|
listenaddr ListenAddress
|
|
|
|
}
|
2018-01-30 00:48:14 +00:00
|
|
|
}
|
|
|
|
|
2022-09-03 09:50:43 +00:00
|
|
|
type AdminSocketRequest struct {
|
|
|
|
Name string `json:"request"`
|
|
|
|
Arguments map[string]string `json:"arguments,omitempty"`
|
|
|
|
KeepAlive bool `json:"keepalive,omitempty"`
|
|
|
|
}
|
|
|
|
|
2021-05-16 18:51:09 +00:00
|
|
|
type AdminSocketResponse struct {
|
2022-09-03 09:50:43 +00:00
|
|
|
Status string `json:"status"`
|
|
|
|
Error string `json:"error,omitempty"`
|
|
|
|
Request AdminSocketRequest `json:"request"`
|
|
|
|
Response json.RawMessage `json:"response"`
|
2021-05-16 18:51:09 +00:00
|
|
|
}
|
2018-05-20 16:21:14 +00:00
|
|
|
|
2019-05-19 21:02:04 +00:00
|
|
|
type handler struct {
|
2022-09-24 11:22:38 +00:00
|
|
|
desc string // What does the endpoint do?
|
2021-09-01 01:16:57 +00:00
|
|
|
args []string // List of human-readable argument names
|
|
|
|
handler core.AddHandlerFunc // First is input map, second is output
|
2021-05-16 18:51:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ListResponse struct {
|
2022-09-03 09:50:43 +00:00
|
|
|
List []ListEntry `json:"list"`
|
2021-05-16 19:53:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ListEntry struct {
|
2022-09-24 11:22:38 +00:00
|
|
|
Command string `json:"command"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
Fields []string `json:"fields,omitempty"`
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
|
|
|
|
2019-05-19 21:02:04 +00:00
|
|
|
// AddHandler is called for each admin function to add the handler and help documentation to the API.
|
2022-09-24 11:22:38 +00:00
|
|
|
func (a *AdminSocket) AddHandler(name, desc string, args []string, handlerfunc core.AddHandlerFunc) error {
|
2019-05-19 21:02:04 +00:00
|
|
|
if _, ok := a.handlers[strings.ToLower(name)]; ok {
|
|
|
|
return errors.New("handler already exists")
|
|
|
|
}
|
|
|
|
a.handlers[strings.ToLower(name)] = handler{
|
2022-09-24 11:22:38 +00:00
|
|
|
desc: desc,
|
2019-05-19 21:02:04 +00:00
|
|
|
args: args,
|
|
|
|
handler: handlerfunc,
|
|
|
|
}
|
|
|
|
return nil
|
2018-01-21 00:17:15 +00:00
|
|
|
}
|
|
|
|
|
2020-09-27 13:42:46 +00:00
|
|
|
// Init runs the initial admin setup.
|
2022-09-03 10:54:46 +00:00
|
|
|
func New(c *core.Core, log util.Logger, opts ...SetupOption) (*AdminSocket, error) {
|
|
|
|
a := &AdminSocket{
|
|
|
|
core: c,
|
|
|
|
log: log,
|
|
|
|
handlers: make(map[string]handler),
|
|
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
|
|
a._applyOption(opt)
|
|
|
|
}
|
|
|
|
if a.config.listenaddr == "none" || a.config.listenaddr == "" {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2022-09-24 11:22:38 +00:00
|
|
|
_ = a.AddHandler("list", "List available commands", []string{}, func(_ json.RawMessage) (interface{}, error) {
|
2022-09-03 09:50:43 +00:00
|
|
|
res := &ListResponse{}
|
2021-05-16 18:51:09 +00:00
|
|
|
for name, handler := range a.handlers {
|
2022-09-03 09:50:43 +00:00
|
|
|
res.List = append(res.List, ListEntry{
|
2022-09-24 11:22:38 +00:00
|
|
|
Command: name,
|
|
|
|
Description: handler.desc,
|
|
|
|
Fields: handler.args,
|
2022-09-03 09:50:43 +00:00
|
|
|
})
|
2019-10-23 10:12:51 +00:00
|
|
|
}
|
2022-09-03 09:50:43 +00:00
|
|
|
sort.SliceStable(res.List, func(i, j int) bool {
|
|
|
|
return strings.Compare(res.List[i].Command, res.List[j].Command) < 0
|
|
|
|
})
|
2021-05-16 18:51:09 +00:00
|
|
|
return res, nil
|
2019-10-23 10:12:51 +00:00
|
|
|
})
|
2022-09-03 10:54:46 +00:00
|
|
|
a.done = make(chan struct{})
|
|
|
|
go a.listen()
|
|
|
|
return a, a.core.SetAdmin(a)
|
2019-10-23 09:44:58 +00:00
|
|
|
}
|
|
|
|
|
2022-08-29 19:40:19 +00:00
|
|
|
func (a *AdminSocket) SetupAdminHandlers() {
|
2022-09-24 11:22:38 +00:00
|
|
|
_ = 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
|
|
|
|
},
|
|
|
|
)
|
2021-06-13 10:43:03 +00:00
|
|
|
//_ = 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)
|
|
|
|
//_ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler)
|
2018-05-27 21:13:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-23 09:44:58 +00:00
|
|
|
// IsStarted returns true if the module has been started.
|
|
|
|
func (a *AdminSocket) IsStarted() bool {
|
2021-05-30 02:37:13 +00:00
|
|
|
select {
|
|
|
|
case <-a.done:
|
|
|
|
// Not blocking, so we're not currently running
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
// Blocked, so we must have started
|
|
|
|
return true
|
|
|
|
}
|
2019-10-23 09:44:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stop will stop the admin API and close the socket.
|
2019-05-19 16:27:48 +00:00
|
|
|
func (a *AdminSocket) Stop() error {
|
2022-09-17 19:07:00 +00:00
|
|
|
if a == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2019-02-05 23:39:59 +00:00
|
|
|
if a.listener != nil {
|
2021-05-30 02:37:13 +00:00
|
|
|
select {
|
|
|
|
case <-a.done:
|
|
|
|
default:
|
|
|
|
close(a.done)
|
|
|
|
}
|
2019-02-05 23:39:59 +00:00
|
|
|
return a.listener.Close()
|
|
|
|
}
|
2020-09-27 13:42:46 +00:00
|
|
|
return nil
|
2018-07-07 11:34:10 +00:00
|
|
|
}
|
|
|
|
|
2018-06-10 23:03:28 +00:00
|
|
|
// listen is run by start and manages API connections.
|
2019-05-19 16:27:48 +00:00
|
|
|
func (a *AdminSocket) listen() {
|
2022-09-03 10:54:46 +00:00
|
|
|
listenaddr := string(a.config.listenaddr)
|
|
|
|
u, err := url.Parse(listenaddr)
|
2018-07-07 10:22:49 +00:00
|
|
|
if err == nil {
|
|
|
|
switch strings.ToLower(u.Scheme) {
|
|
|
|
case "unix":
|
2022-09-03 10:54:46 +00:00
|
|
|
if _, err := os.Stat(listenaddr[7:]); err == nil {
|
|
|
|
a.log.Debugln("Admin socket", listenaddr[7:], "already exists, trying to clean up")
|
|
|
|
if _, err := net.DialTimeout("unix", listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() {
|
|
|
|
a.log.Errorln("Admin socket", listenaddr[7:], "already exists and is in use by another process")
|
2019-03-03 14:09:54 +00:00
|
|
|
os.Exit(1)
|
|
|
|
} else {
|
2022-09-03 10:54:46 +00:00
|
|
|
if err := os.Remove(listenaddr[7:]); err == nil {
|
|
|
|
a.log.Debugln(listenaddr[7:], "was cleaned up")
|
2019-03-03 14:09:54 +00:00
|
|
|
} else {
|
2022-09-03 10:54:46 +00:00
|
|
|
a.log.Errorln(listenaddr[7:], "already exists and was not cleaned up:", err)
|
2019-03-03 14:09:54 +00:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
2018-12-10 00:19:21 +00:00
|
|
|
}
|
2022-09-03 10:54:46 +00:00
|
|
|
a.listener, err = net.Listen("unix", listenaddr[7:])
|
2018-12-10 00:00:23 +00:00
|
|
|
if err == nil {
|
2022-09-03 10:54:46 +00:00
|
|
|
switch listenaddr[7:8] {
|
2018-12-10 00:26:12 +00:00
|
|
|
case "@": // maybe abstract namespace
|
|
|
|
default:
|
2022-09-03 10:54:46 +00:00
|
|
|
if err := os.Chmod(listenaddr[7:], 0660); err != nil {
|
|
|
|
a.log.Warnln("WARNING:", listenaddr[:7], "may have unsafe permissions!")
|
2018-12-10 00:26:12 +00:00
|
|
|
}
|
2018-12-10 00:19:21 +00:00
|
|
|
}
|
2018-12-10 00:00:23 +00:00
|
|
|
}
|
2018-07-07 10:22:49 +00:00
|
|
|
case "tcp":
|
2018-07-07 19:04:11 +00:00
|
|
|
a.listener, err = net.Listen("tcp", u.Host)
|
2018-07-07 10:22:49 +00:00
|
|
|
default:
|
2018-07-08 09:37:20 +00:00
|
|
|
// err = errors.New(fmt.Sprint("protocol not supported: ", u.Scheme))
|
2022-09-03 10:54:46 +00:00
|
|
|
a.listener, err = net.Listen("tcp", listenaddr)
|
2018-07-07 10:22:49 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-09-03 10:54:46 +00:00
|
|
|
a.listener, err = net.Listen("tcp", listenaddr)
|
2018-07-07 10:22:49 +00:00
|
|
|
}
|
2018-01-21 00:17:15 +00:00
|
|
|
if err != nil {
|
2019-05-19 16:27:48 +00:00
|
|
|
a.log.Errorf("Admin socket failed to listen: %v", err)
|
2018-01-21 00:17:15 +00:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
2019-05-19 16:27:48 +00:00
|
|
|
a.log.Infof("%s admin socket listening on %s",
|
2018-07-07 19:04:11 +00:00
|
|
|
strings.ToUpper(a.listener.Addr().Network()),
|
|
|
|
a.listener.Addr().String())
|
|
|
|
defer a.listener.Close()
|
2018-01-21 00:17:15 +00:00
|
|
|
for {
|
2018-07-07 19:04:11 +00:00
|
|
|
conn, err := a.listener.Accept()
|
2018-01-21 00:17:15 +00:00
|
|
|
if err == nil {
|
2018-12-17 19:06:52 +00:00
|
|
|
go a.handleRequest(conn)
|
2021-05-30 02:37:13 +00:00
|
|
|
} else {
|
|
|
|
select {
|
|
|
|
case <-a.done:
|
|
|
|
// Not blocked, so we havent started or already stopped
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
// Blocked, so we're supposed to keep running
|
|
|
|
}
|
2018-01-21 00:17:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-10 23:03:28 +00:00
|
|
|
// handleRequest calls the request handler for each request sent to the admin API.
|
2019-05-19 16:27:48 +00:00
|
|
|
func (a *AdminSocket) handleRequest(conn net.Conn) {
|
2018-05-20 16:21:14 +00:00
|
|
|
decoder := json.NewDecoder(conn)
|
2021-05-16 18:51:09 +00:00
|
|
|
decoder.DisallowUnknownFields()
|
|
|
|
|
2018-05-20 16:21:14 +00:00
|
|
|
encoder := json.NewEncoder(conn)
|
|
|
|
encoder.SetIndent("", " ")
|
2021-05-16 18:51:09 +00:00
|
|
|
|
|
|
|
defer conn.Close()
|
2018-05-20 16:21:14 +00:00
|
|
|
|
2018-05-20 20:44:30 +00:00
|
|
|
defer func() {
|
|
|
|
r := recover()
|
|
|
|
if r != nil {
|
2019-07-07 18:41:53 +00:00
|
|
|
a.log.Debugln("Admin socket error:", r)
|
2021-05-16 18:51:09 +00:00
|
|
|
if err := encoder.Encode(&ErrorResponse{
|
|
|
|
Error: "Check your syntax and input types",
|
|
|
|
}); err != nil {
|
2019-07-07 18:41:53 +00:00
|
|
|
a.log.Debugln("Admin socket JSON encode error:", err)
|
2018-05-20 20:44:30 +00:00
|
|
|
}
|
|
|
|
conn.Close()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2018-05-20 16:21:14 +00:00
|
|
|
for {
|
2021-05-16 18:51:09 +00:00
|
|
|
var err error
|
|
|
|
var buf json.RawMessage
|
|
|
|
var resp AdminSocketResponse
|
2022-09-24 11:22:38 +00:00
|
|
|
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")
|
|
|
|
}
|
2021-05-16 18:51:09 +00:00
|
|
|
if resp.Request.Name == "" {
|
2022-09-24 11:22:38 +00:00
|
|
|
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)
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
2022-09-24 11:22:38 +00:00
|
|
|
resp.Status = "success"
|
|
|
|
return nil
|
|
|
|
}(); err != nil {
|
|
|
|
resp.Status = "error"
|
|
|
|
resp.Error = err.Error()
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
2021-05-16 18:51:09 +00:00
|
|
|
if err = encoder.Encode(resp); err != nil {
|
|
|
|
a.log.Debugln("Encode error:", err)
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
2021-05-16 18:51:09 +00:00
|
|
|
if !resp.Request.KeepAlive {
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
continue
|
2018-05-20 20:44:30 +00:00
|
|
|
}
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
2018-01-30 00:48:14 +00:00
|
|
|
}
|
2022-09-03 09:50:43 +00:00
|
|
|
|
|
|
|
type DataUnit uint64
|
|
|
|
|
|
|
|
func (d DataUnit) String() string {
|
|
|
|
switch {
|
2022-09-17 19:07:00 +00:00
|
|
|
case d > 1024*1024*1024*1024:
|
|
|
|
return fmt.Sprintf("%2.ftb", float64(d)/1024/1024/1024/1024)
|
2022-09-03 09:50:43 +00:00
|
|
|
case d > 1024*1024*1024:
|
|
|
|
return fmt.Sprintf("%2.fgb", float64(d)/1024/1024/1024)
|
|
|
|
case d > 1024*1024:
|
|
|
|
return fmt.Sprintf("%2.fmb", float64(d)/1024/1024)
|
|
|
|
default:
|
|
|
|
return fmt.Sprintf("%2.fkb", float64(d)/1024)
|
|
|
|
}
|
|
|
|
}
|