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 (
|
2019-05-20 18:51:44 +00:00
|
|
|
"encoding/hex"
|
2018-06-12 22:50:08 +00:00
|
|
|
"encoding/json"
|
2019-05-19 21:02:04 +00:00
|
|
|
"errors"
|
2018-06-12 22:50:08 +00:00
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
2019-05-20 18:51:44 +00:00
|
|
|
"strconv"
|
2018-06-12 22:50:08 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2018-07-07 11:08:52 +00:00
|
|
|
|
2019-05-19 16:27:48 +00:00
|
|
|
"github.com/gologme/log"
|
|
|
|
|
2018-12-15 02:49:18 +00:00
|
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
2019-05-19 16:27:48 +00:00
|
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
2018-12-15 02:49:18 +00:00
|
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
2019-05-19 16:27:48 +00:00
|
|
|
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
|
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 {
|
|
|
|
core *yggdrasil.Core
|
|
|
|
log *log.Logger
|
2018-12-30 12:04:42 +00:00
|
|
|
reconfigure chan chan error
|
2018-12-29 18:51:51 +00:00
|
|
|
listenaddr string
|
|
|
|
listener net.Listener
|
2019-05-19 21:02:04 +00:00
|
|
|
handlers map[string]handler
|
2018-01-30 00:48:14 +00:00
|
|
|
}
|
|
|
|
|
2019-05-19 21:02:04 +00:00
|
|
|
// Info refers to information that is returned to the admin socket handler.
|
|
|
|
type Info map[string]interface{}
|
2018-05-20 16:21:14 +00:00
|
|
|
|
2019-05-19 21:02:04 +00:00
|
|
|
type handler struct {
|
|
|
|
args []string // List of human-readable argument names
|
|
|
|
handler func(Info) (Info, error) // First is input map, second is output
|
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.
|
|
|
|
func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(Info) (Info, error)) error {
|
|
|
|
if _, ok := a.handlers[strings.ToLower(name)]; ok {
|
|
|
|
return errors.New("handler already exists")
|
|
|
|
}
|
|
|
|
a.handlers[strings.ToLower(name)] = handler{
|
|
|
|
args: args,
|
|
|
|
handler: handlerfunc,
|
|
|
|
}
|
|
|
|
return nil
|
2018-01-21 00:17:15 +00:00
|
|
|
}
|
|
|
|
|
2018-06-10 23:03:28 +00:00
|
|
|
// init runs the initial admin setup.
|
2019-05-19 16:27:48 +00:00
|
|
|
func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) {
|
2018-01-21 00:17:15 +00:00
|
|
|
a.core = c
|
2019-05-19 16:27:48 +00:00
|
|
|
a.log = log
|
2018-12-30 12:04:42 +00:00
|
|
|
a.reconfigure = make(chan chan error, 1)
|
2019-05-19 21:02:04 +00:00
|
|
|
a.handlers = make(map[string]handler)
|
2018-12-29 18:51:51 +00:00
|
|
|
go func() {
|
|
|
|
for {
|
2019-01-15 08:51:19 +00:00
|
|
|
e := <-a.reconfigure
|
2019-07-27 14:00:09 +00:00
|
|
|
current, previous := state.GetCurrent(), state.GetPrevious()
|
2019-03-28 00:30:25 +00:00
|
|
|
if current.AdminListen != previous.AdminListen {
|
|
|
|
a.listenaddr = current.AdminListen
|
2019-05-19 16:27:48 +00:00
|
|
|
a.Stop()
|
|
|
|
a.Start()
|
2018-12-29 18:51:51 +00:00
|
|
|
}
|
2019-01-15 08:51:19 +00:00
|
|
|
e <- nil
|
2018-12-29 18:51:51 +00:00
|
|
|
}
|
|
|
|
}()
|
2019-07-27 14:00:09 +00:00
|
|
|
current := state.GetCurrent()
|
2019-03-28 00:30:25 +00:00
|
|
|
a.listenaddr = current.AdminListen
|
2019-05-19 21:02:04 +00:00
|
|
|
a.AddHandler("list", []string{}, func(in Info) (Info, error) {
|
2018-05-21 12:54:51 +00:00
|
|
|
handlers := make(map[string]interface{})
|
2019-05-19 21:02:04 +00:00
|
|
|
for handlername, handler := range a.handlers {
|
|
|
|
handlers[handlername] = Info{"fields": handler.args}
|
2018-01-30 00:48:14 +00:00
|
|
|
}
|
2019-05-19 21:02:04 +00:00
|
|
|
return Info{"list": handlers}, nil
|
2018-01-30 00:48:14 +00:00
|
|
|
})
|
2019-05-19 21:02:04 +00:00
|
|
|
/*
|
|
|
|
a.AddHandler("dot", []string{}, func(in Info) (Info, error) {
|
|
|
|
return Info{"dot": string(a.getResponse_dot())}, nil
|
|
|
|
})
|
|
|
|
*/
|
|
|
|
a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) {
|
2019-05-19 16:27:48 +00:00
|
|
|
ip := c.Address().String()
|
2019-05-19 21:02:04 +00:00
|
|
|
return Info{
|
|
|
|
"self": Info{
|
|
|
|
ip: Info{
|
2019-05-19 16:27:48 +00:00
|
|
|
"box_pub_key": c.BoxPubKey(),
|
|
|
|
"build_name": yggdrasil.BuildName(),
|
|
|
|
"build_version": yggdrasil.BuildVersion(),
|
|
|
|
"coords": fmt.Sprintf("%v", c.Coords()),
|
|
|
|
"subnet": c.Subnet().String(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, nil
|
2018-02-07 23:48:30 +00:00
|
|
|
})
|
2019-05-19 21:02:04 +00:00
|
|
|
a.AddHandler("getPeers", []string{}, func(in Info) (Info, error) {
|
|
|
|
peers := make(Info)
|
2019-05-19 16:27:48 +00:00
|
|
|
for _, p := range a.core.GetPeers() {
|
|
|
|
addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey))
|
|
|
|
so := net.IP(addr[:]).String()
|
2019-05-19 21:02:04 +00:00
|
|
|
peers[so] = Info{
|
2019-05-19 16:27:48 +00:00
|
|
|
"port": p.Port,
|
|
|
|
"uptime": p.Uptime.Seconds(),
|
|
|
|
"bytes_sent": p.BytesSent,
|
|
|
|
"bytes_recvd": p.BytesRecvd,
|
|
|
|
"proto": p.Protocol,
|
|
|
|
"endpoint": p.Endpoint,
|
|
|
|
"box_pub_key": p.PublicKey,
|
|
|
|
}
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
2019-05-19 21:02:04 +00:00
|
|
|
return Info{"peers": peers}, nil
|
2018-01-30 00:48:14 +00:00
|
|
|
})
|
2019-05-19 21:02:04 +00:00
|
|
|
a.AddHandler("getSwitchPeers", []string{}, func(in Info) (Info, error) {
|
|
|
|
switchpeers := make(Info)
|
2019-05-19 16:27:48 +00:00
|
|
|
for _, s := range a.core.GetSwitchPeers() {
|
|
|
|
addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey))
|
|
|
|
so := fmt.Sprint(s.Port)
|
2019-05-19 21:02:04 +00:00
|
|
|
switchpeers[so] = Info{
|
2019-05-19 16:27:48 +00:00
|
|
|
"ip": net.IP(addr[:]).String(),
|
|
|
|
"coords": fmt.Sprintf("%v", s.Coords),
|
|
|
|
"port": s.Port,
|
|
|
|
"bytes_sent": s.BytesSent,
|
|
|
|
"bytes_recvd": s.BytesRecvd,
|
|
|
|
"proto": s.Protocol,
|
|
|
|
"endpoint": s.Endpoint,
|
|
|
|
"box_pub_key": s.PublicKey,
|
|
|
|
}
|
2018-02-28 13:43:06 +00:00
|
|
|
}
|
2019-05-19 21:02:04 +00:00
|
|
|
return Info{"switchpeers": switchpeers}, nil
|
2018-02-28 13:43:06 +00:00
|
|
|
})
|
2019-05-19 21:02:04 +00:00
|
|
|
/*
|
|
|
|
a.AddHandler("getSwitchQueues", []string{}, func(in Info) (Info, error) {
|
|
|
|
queues := a.core.GetSwitchQueues()
|
|
|
|
return Info{"switchqueues": queues.asMap()}, nil
|
|
|
|
})
|
|
|
|
*/
|
|
|
|
a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) {
|
|
|
|
dht := make(Info)
|
2019-05-19 16:27:48 +00:00
|
|
|
for _, d := range a.core.GetDHT() {
|
|
|
|
addr := *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey))
|
|
|
|
so := net.IP(addr[:]).String()
|
2019-05-19 21:02:04 +00:00
|
|
|
dht[so] = Info{
|
2019-05-19 16:27:48 +00:00
|
|
|
"coords": fmt.Sprintf("%v", d.Coords),
|
|
|
|
"last_seen": d.LastSeen.Seconds(),
|
|
|
|
"box_pub_key": d.PublicKey,
|
|
|
|
}
|
2018-03-16 23:24:28 +00:00
|
|
|
}
|
2019-05-19 21:02:04 +00:00
|
|
|
return Info{"dht": dht}, nil
|
2018-03-16 23:24:28 +00:00
|
|
|
})
|
2019-05-19 21:02:04 +00:00
|
|
|
a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) {
|
|
|
|
sessions := make(Info)
|
2019-05-19 16:27:48 +00:00
|
|
|
for _, s := range a.core.GetSessions() {
|
|
|
|
addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey))
|
|
|
|
so := net.IP(addr[:]).String()
|
2019-05-19 21:02:04 +00:00
|
|
|
sessions[so] = Info{
|
2019-05-19 16:27:48 +00:00
|
|
|
"coords": fmt.Sprintf("%v", s.Coords),
|
|
|
|
"bytes_sent": s.BytesSent,
|
|
|
|
"bytes_recvd": s.BytesRecvd,
|
|
|
|
"mtu": s.MTU,
|
2019-05-29 11:59:36 +00:00
|
|
|
"uptime": s.Uptime.Seconds(),
|
2019-05-19 16:27:48 +00:00
|
|
|
"was_mtu_fixed": s.WasMTUFixed,
|
|
|
|
"box_pub_key": s.PublicKey,
|
|
|
|
}
|
2018-05-10 08:48:12 +00:00
|
|
|
}
|
2019-05-19 21:02:04 +00:00
|
|
|
return Info{"sessions": sessions}, nil
|
2018-05-20 18:23:43 +00:00
|
|
|
})
|
2019-05-20 18:51:44 +00:00
|
|
|
a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) {
|
|
|
|
// Set sane defaults
|
|
|
|
intf := ""
|
|
|
|
// Has interface been specified?
|
|
|
|
if itf, ok := in["interface"]; ok {
|
|
|
|
intf = itf.(string)
|
|
|
|
}
|
|
|
|
if a.core.AddPeer(in["uri"].(string), intf) == nil {
|
|
|
|
return Info{
|
|
|
|
"added": []string{
|
|
|
|
in["uri"].(string),
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
} else {
|
|
|
|
return Info{
|
|
|
|
"not_added": []string{
|
|
|
|
in["uri"].(string),
|
|
|
|
},
|
|
|
|
}, errors.New("Failed to add peer")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) {
|
|
|
|
port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return Info{}, err
|
|
|
|
}
|
|
|
|
if a.core.DisconnectPeer(uint64(port)) == nil {
|
|
|
|
return Info{
|
|
|
|
"removed": []string{
|
|
|
|
fmt.Sprint(port),
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
} else {
|
|
|
|
return Info{
|
|
|
|
"not_removed": []string{
|
|
|
|
fmt.Sprint(port),
|
|
|
|
},
|
|
|
|
}, errors.New("Failed to remove peer")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) {
|
|
|
|
return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil
|
|
|
|
})
|
|
|
|
a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) {
|
|
|
|
if a.core.AddAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
|
|
|
|
return Info{
|
|
|
|
"added": []string{
|
|
|
|
in["box_pub_key"].(string),
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
} else {
|
|
|
|
return Info{
|
|
|
|
"not_added": []string{
|
|
|
|
in["box_pub_key"].(string),
|
|
|
|
},
|
|
|
|
}, errors.New("Failed to add allowed key")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) {
|
|
|
|
if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
|
|
|
|
return Info{
|
|
|
|
"removed": []string{
|
|
|
|
in["box_pub_key"].(string),
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
} else {
|
|
|
|
return Info{
|
|
|
|
"not_removed": []string{
|
|
|
|
in["box_pub_key"].(string),
|
|
|
|
},
|
|
|
|
}, errors.New("Failed to remove allowed key")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) {
|
|
|
|
if in["target"] == nil {
|
|
|
|
in["target"] = "none"
|
|
|
|
}
|
|
|
|
result, err := a.core.DHTPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string))
|
|
|
|
if err == nil {
|
|
|
|
infos := make(map[string]map[string]string, len(result.Infos))
|
|
|
|
for _, dinfo := range result.Infos {
|
|
|
|
info := map[string]string{
|
|
|
|
"box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]),
|
|
|
|
"coords": fmt.Sprintf("%v", dinfo.Coords),
|
2018-11-21 06:10:20 +00:00
|
|
|
}
|
2019-05-20 18:51:44 +00:00
|
|
|
addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String()
|
|
|
|
infos[addr] = info
|
2018-11-25 22:10:32 +00:00
|
|
|
}
|
2019-05-20 18:51:44 +00:00
|
|
|
return Info{"nodes": infos}, nil
|
|
|
|
} else {
|
|
|
|
return Info{}, err
|
|
|
|
}
|
|
|
|
})
|
|
|
|
a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) {
|
|
|
|
var nocache bool
|
|
|
|
if in["nocache"] != nil {
|
|
|
|
nocache = in["nocache"].(string) == "true"
|
|
|
|
}
|
|
|
|
var box_pub_key, coords string
|
|
|
|
if in["box_pub_key"] == nil && in["coords"] == nil {
|
|
|
|
nodeinfo := a.core.MyNodeInfo()
|
|
|
|
var jsoninfo interface{}
|
|
|
|
if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil {
|
|
|
|
return Info{}, err
|
2018-12-31 11:48:50 +00:00
|
|
|
} else {
|
2019-05-20 18:51:44 +00:00
|
|
|
return Info{"nodeinfo": jsoninfo}, nil
|
2018-12-31 11:48:50 +00:00
|
|
|
}
|
2019-05-20 18:51:44 +00:00
|
|
|
} else if in["box_pub_key"] == nil || in["coords"] == nil {
|
|
|
|
return Info{}, errors.New("Expecting both box_pub_key and coords")
|
|
|
|
} else {
|
|
|
|
box_pub_key = in["box_pub_key"].(string)
|
|
|
|
coords = in["coords"].(string)
|
|
|
|
}
|
|
|
|
result, err := a.core.GetNodeInfo(box_pub_key, coords, nocache)
|
|
|
|
if err == nil {
|
|
|
|
var m map[string]interface{}
|
|
|
|
if err = json.Unmarshal(result, &m); err == nil {
|
|
|
|
return Info{"nodeinfo": m}, nil
|
2018-12-15 10:56:46 +00:00
|
|
|
} else {
|
2019-05-19 21:02:04 +00:00
|
|
|
return Info{}, err
|
2018-12-15 10:56:46 +00:00
|
|
|
}
|
2019-05-20 18:51:44 +00:00
|
|
|
} else {
|
|
|
|
return Info{}, err
|
|
|
|
}
|
|
|
|
})
|
2018-05-27 21:13:37 +00:00
|
|
|
}
|
|
|
|
|
2018-06-10 23:03:28 +00:00
|
|
|
// start runs the admin API socket to listen for / respond to admin API calls.
|
2019-05-19 16:27:48 +00:00
|
|
|
func (a *AdminSocket) Start() error {
|
2018-12-09 17:53:31 +00:00
|
|
|
if a.listenaddr != "none" && a.listenaddr != "" {
|
2018-12-09 17:46:48 +00:00
|
|
|
go a.listen()
|
|
|
|
}
|
2018-05-27 21:13:37 +00:00
|
|
|
return nil
|
2018-01-21 00:17:15 +00:00
|
|
|
}
|
|
|
|
|
2018-07-07 11:34:10 +00:00
|
|
|
// cleans up when stopping
|
2019-05-19 16:27:48 +00:00
|
|
|
func (a *AdminSocket) Stop() error {
|
2019-02-05 23:39:59 +00:00
|
|
|
if a.listener != nil {
|
|
|
|
return a.listener.Close()
|
|
|
|
} else {
|
|
|
|
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() {
|
2018-07-07 10:22:49 +00:00
|
|
|
u, err := url.Parse(a.listenaddr)
|
|
|
|
if err == nil {
|
|
|
|
switch strings.ToLower(u.Scheme) {
|
|
|
|
case "unix":
|
2018-12-10 00:19:21 +00:00
|
|
|
if _, err := os.Stat(a.listenaddr[7:]); err == nil {
|
2019-05-19 16:27:48 +00:00
|
|
|
a.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up")
|
2019-03-03 19:32:36 +00:00
|
|
|
if _, err := net.DialTimeout("unix", a.listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() {
|
2019-05-19 16:27:48 +00:00
|
|
|
a.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process")
|
2019-03-03 14:09:54 +00:00
|
|
|
os.Exit(1)
|
|
|
|
} else {
|
|
|
|
if err := os.Remove(a.listenaddr[7:]); err == nil {
|
2019-05-19 16:27:48 +00:00
|
|
|
a.log.Debugln(a.listenaddr[7:], "was cleaned up")
|
2019-03-03 14:09:54 +00:00
|
|
|
} else {
|
2019-05-19 16:27:48 +00:00
|
|
|
a.log.Errorln(a.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
|
|
|
}
|
2018-07-07 19:04:11 +00:00
|
|
|
a.listener, err = net.Listen("unix", a.listenaddr[7:])
|
2018-12-10 00:00:23 +00:00
|
|
|
if err == nil {
|
2018-12-10 00:26:12 +00:00
|
|
|
switch a.listenaddr[7:8] {
|
|
|
|
case "@": // maybe abstract namespace
|
|
|
|
default:
|
|
|
|
if err := os.Chmod(a.listenaddr[7:], 0660); err != nil {
|
2019-05-19 16:27:48 +00:00
|
|
|
a.log.Warnln("WARNING:", a.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))
|
|
|
|
a.listener, err = net.Listen("tcp", a.listenaddr)
|
2018-07-07 10:22:49 +00:00
|
|
|
}
|
|
|
|
} else {
|
2018-07-07 19:04:11 +00:00
|
|
|
a.listener, err = net.Listen("tcp", a.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)
|
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)
|
|
|
|
encoder := json.NewEncoder(conn)
|
|
|
|
encoder.SetIndent("", " ")
|
2019-05-19 21:02:04 +00:00
|
|
|
recv := make(Info)
|
|
|
|
send := make(Info)
|
2018-05-20 16:21:14 +00:00
|
|
|
|
2018-05-20 20:44:30 +00:00
|
|
|
defer func() {
|
|
|
|
r := recover()
|
|
|
|
if r != nil {
|
2019-05-19 21:02:04 +00:00
|
|
|
send = Info{
|
2018-05-20 20:44:30 +00:00
|
|
|
"status": "error",
|
2019-07-07 18:41:53 +00:00
|
|
|
"error": "Check your syntax and input types",
|
2018-05-20 20:44:30 +00:00
|
|
|
}
|
2019-07-07 18:41:53 +00:00
|
|
|
a.log.Debugln("Admin socket error:", r)
|
2018-05-20 20:44:30 +00:00
|
|
|
if err := encoder.Encode(&send); 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 {
|
2018-05-20 20:44:30 +00:00
|
|
|
// Start with a clean slate on each request
|
2019-05-19 21:02:04 +00:00
|
|
|
recv = Info{}
|
|
|
|
send = Info{}
|
2018-05-20 20:44:30 +00:00
|
|
|
|
|
|
|
// Decode the input
|
2018-05-20 16:21:14 +00:00
|
|
|
if err := decoder.Decode(&recv); err != nil {
|
2019-05-19 21:03:20 +00:00
|
|
|
a.log.Debugln("Admin socket JSON decode error:", err)
|
2018-05-20 16:21:14 +00:00
|
|
|
return
|
2018-01-21 12:57:54 +00:00
|
|
|
}
|
|
|
|
|
2018-05-20 20:44:30 +00:00
|
|
|
// Send the request back with the response, and default to "error"
|
|
|
|
// unless the status is changed below by one of the handlers
|
2018-05-20 18:23:43 +00:00
|
|
|
send["request"] = recv
|
|
|
|
send["status"] = "error"
|
|
|
|
|
2019-07-07 18:41:53 +00:00
|
|
|
n := strings.ToLower(recv["request"].(string))
|
|
|
|
|
2019-05-19 21:02:04 +00:00
|
|
|
if _, ok := recv["request"]; !ok {
|
|
|
|
send["error"] = "No request sent"
|
2019-07-07 18:41:53 +00:00
|
|
|
goto respond
|
2019-05-19 21:02:04 +00:00
|
|
|
}
|
2018-05-20 16:21:14 +00:00
|
|
|
|
2019-07-07 18:41:53 +00:00
|
|
|
if h, ok := a.handlers[n]; ok {
|
2019-05-19 21:02:04 +00:00
|
|
|
// Check that we have all the required arguments
|
|
|
|
for _, arg := range h.args {
|
|
|
|
// An argument in [square brackets] is optional and not required,
|
|
|
|
// so we can safely ignore those
|
|
|
|
if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Check if the field is missing
|
|
|
|
if _, ok := recv[arg]; !ok {
|
|
|
|
send = Info{
|
|
|
|
"status": "error",
|
|
|
|
"error": "Expected field missing: " + arg,
|
|
|
|
"expecting": arg,
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
2019-07-07 18:41:53 +00:00
|
|
|
goto respond
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
2019-05-19 21:02:04 +00:00
|
|
|
}
|
2018-05-20 20:44:30 +00:00
|
|
|
|
2019-05-19 21:02:04 +00:00
|
|
|
// By this point we should have all the fields we need, so call
|
|
|
|
// the handler
|
|
|
|
response, err := h.handler(recv)
|
|
|
|
if err != nil {
|
|
|
|
send["error"] = err.Error()
|
|
|
|
if response != nil {
|
|
|
|
send["response"] = response
|
2019-07-07 18:41:53 +00:00
|
|
|
goto respond
|
2019-05-19 21:02:04 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
send["status"] = "success"
|
|
|
|
if response != nil {
|
|
|
|
send["response"] = response
|
2019-07-07 18:41:53 +00:00
|
|
|
goto respond
|
2019-05-19 21:02:04 +00:00
|
|
|
}
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
2019-07-07 18:41:53 +00:00
|
|
|
} else {
|
|
|
|
// Start with a clean response on each request, which defaults to an error
|
|
|
|
// state. If a handler is found below then this will be overwritten
|
|
|
|
send = Info{
|
|
|
|
"request": recv,
|
|
|
|
"status": "error",
|
|
|
|
"error": fmt.Sprintf("Unknown action '%s', try 'list' for help", recv["request"].(string)),
|
|
|
|
}
|
|
|
|
goto respond
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
|
|
|
|
2018-05-20 20:44:30 +00:00
|
|
|
// Send the response back
|
2019-07-07 18:41:53 +00:00
|
|
|
respond:
|
2018-05-20 16:21:14 +00:00
|
|
|
if err := encoder.Encode(&send); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-20 20:44:30 +00:00
|
|
|
// If "keepalive" isn't true then close the connection
|
|
|
|
if keepalive, ok := recv["keepalive"]; !ok || !keepalive.(bool) {
|
|
|
|
conn.Close()
|
|
|
|
}
|
2018-05-20 16:21:14 +00:00
|
|
|
}
|
2018-01-30 00:48:14 +00:00
|
|
|
}
|
2018-01-21 18:55:45 +00:00
|
|
|
|
2019-05-19 21:02:04 +00:00
|
|
|
// getResponse_dot returns a response for a graphviz dot formatted
|
|
|
|
// representation of the known parts of the network. This is color-coded and
|
|
|
|
// labeled, and includes the self node, switch peers, nodes known to the DHT,
|
|
|
|
// and nodes with open sessions. The graph is structured as a tree with directed
|
|
|
|
// links leading away from the root.
|
|
|
|
/*
|
2019-05-19 16:27:48 +00:00
|
|
|
func (a *AdminSocket) getResponse_dot() []byte {
|
2019-05-19 21:02:04 +00:00
|
|
|
//self := a.getData_getSelf()
|
|
|
|
peers := a.core.GetSwitchPeers()
|
|
|
|
dht := a.core.GetDHT()
|
|
|
|
sessions := a.core.GetSessions()
|
2018-01-30 00:48:14 +00:00
|
|
|
// Start building a tree from all known nodes
|
|
|
|
type nodeInfo struct {
|
2018-06-01 01:28:09 +00:00
|
|
|
name string
|
|
|
|
key string
|
|
|
|
parent string
|
2019-05-19 21:02:04 +00:00
|
|
|
port uint64
|
2018-06-01 01:28:09 +00:00
|
|
|
options string
|
2018-01-30 00:48:14 +00:00
|
|
|
}
|
|
|
|
infos := make(map[string]nodeInfo)
|
2018-07-21 04:02:25 +00:00
|
|
|
// Get coords as a slice of strings, FIXME? this looks very fragile
|
|
|
|
coordSlice := func(coords string) []string {
|
|
|
|
tmp := strings.Replace(coords, "[", "", -1)
|
|
|
|
tmp = strings.Replace(tmp, "]", "", -1)
|
|
|
|
return strings.Split(tmp, " ")
|
|
|
|
}
|
2018-01-30 00:48:14 +00:00
|
|
|
// First fill the tree with all known nodes, no parents
|
2018-06-01 22:23:24 +00:00
|
|
|
addInfo := func(nodes []admin_nodeInfo, options string, tag string) {
|
2018-06-01 01:28:09 +00:00
|
|
|
for _, node := range nodes {
|
|
|
|
n := node.asMap()
|
|
|
|
info := nodeInfo{
|
|
|
|
key: n["coords"].(string),
|
|
|
|
options: options,
|
|
|
|
}
|
2018-06-01 22:23:24 +00:00
|
|
|
if len(tag) > 0 {
|
|
|
|
info.name = fmt.Sprintf("%s\n%s", n["ip"].(string), tag)
|
|
|
|
} else {
|
|
|
|
info.name = n["ip"].(string)
|
|
|
|
}
|
2018-07-21 04:02:25 +00:00
|
|
|
coordsSplit := coordSlice(info.key)
|
|
|
|
if len(coordsSplit) != 0 {
|
|
|
|
portStr := coordsSplit[len(coordsSplit)-1]
|
|
|
|
portUint, err := strconv.ParseUint(portStr, 10, 64)
|
|
|
|
if err == nil {
|
2019-05-19 21:02:04 +00:00
|
|
|
info.port = portUint
|
2018-07-21 04:02:25 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-01 01:28:09 +00:00
|
|
|
infos[info.key] = info
|
2018-01-21 22:19:39 +00:00
|
|
|
}
|
2018-01-30 00:48:14 +00:00
|
|
|
}
|
2018-06-02 20:21:05 +00:00
|
|
|
addInfo(dht, "fillcolor=\"#ffffff\" style=filled fontname=\"sans serif\"", "Known in DHT") // white
|
|
|
|
addInfo(sessions, "fillcolor=\"#acf3fd\" style=filled fontname=\"sans serif\"", "Open session") // blue
|
|
|
|
addInfo(peers, "fillcolor=\"#ffffb5\" style=filled fontname=\"sans serif\"", "Connected peer") // yellow
|
2018-06-02 13:24:06 +00:00
|
|
|
addInfo(append([]admin_nodeInfo(nil), *self), "fillcolor=\"#a5ff8a\" style=filled fontname=\"sans serif\"", "This node") // green
|
2018-01-30 00:48:14 +00:00
|
|
|
// Now go through and create placeholders for any missing nodes
|
|
|
|
for _, info := range infos {
|
|
|
|
// This is ugly string manipulation
|
|
|
|
coordsSplit := coordSlice(info.key)
|
|
|
|
for idx := range coordsSplit {
|
|
|
|
key := fmt.Sprintf("[%v]", strings.Join(coordsSplit[:idx], " "))
|
|
|
|
newInfo, isIn := infos[key]
|
|
|
|
if isIn {
|
|
|
|
continue
|
2018-01-21 22:19:39 +00:00
|
|
|
}
|
2018-01-30 00:48:14 +00:00
|
|
|
newInfo.name = "?"
|
|
|
|
newInfo.key = key
|
2018-06-02 13:24:06 +00:00
|
|
|
newInfo.options = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
|
2018-09-05 00:30:07 +00:00
|
|
|
|
|
|
|
coordsSplit := coordSlice(newInfo.key)
|
|
|
|
if len(coordsSplit) != 0 {
|
|
|
|
portStr := coordsSplit[len(coordsSplit)-1]
|
|
|
|
portUint, err := strconv.ParseUint(portStr, 10, 64)
|
|
|
|
if err == nil {
|
2019-05-19 21:02:04 +00:00
|
|
|
newInfo.port = portUint
|
2018-09-05 00:30:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-30 00:48:14 +00:00
|
|
|
infos[key] = newInfo
|
2018-01-21 22:19:39 +00:00
|
|
|
}
|
2018-01-30 00:48:14 +00:00
|
|
|
}
|
|
|
|
// Now go through and attach parents
|
|
|
|
for _, info := range infos {
|
|
|
|
pSplit := coordSlice(info.key)
|
|
|
|
if len(pSplit) > 0 {
|
|
|
|
pSplit = pSplit[:len(pSplit)-1]
|
2018-01-21 20:58:54 +00:00
|
|
|
}
|
2018-01-30 00:48:14 +00:00
|
|
|
info.parent = fmt.Sprintf("[%v]", strings.Join(pSplit, " "))
|
|
|
|
infos[info.key] = info
|
|
|
|
}
|
|
|
|
// Finally, get a sorted list of keys, which we use to organize the output
|
|
|
|
var keys []string
|
|
|
|
for _, info := range infos {
|
|
|
|
keys = append(keys, info.key)
|
|
|
|
}
|
2018-06-12 22:50:08 +00:00
|
|
|
// sort
|
2018-07-21 04:02:25 +00:00
|
|
|
sort.SliceStable(keys, func(i, j int) bool {
|
2018-01-30 00:48:14 +00:00
|
|
|
return keys[i] < keys[j]
|
2018-07-21 04:02:25 +00:00
|
|
|
})
|
|
|
|
sort.SliceStable(keys, func(i, j int) bool {
|
|
|
|
return infos[keys[i]].port < infos[keys[j]].port
|
|
|
|
})
|
2018-01-30 00:48:14 +00:00
|
|
|
// Now print it all out
|
|
|
|
var out []byte
|
|
|
|
put := func(s string) {
|
|
|
|
out = append(out, []byte(s)...)
|
|
|
|
}
|
|
|
|
put("digraph {\n")
|
|
|
|
// First set the labels
|
|
|
|
for _, key := range keys {
|
|
|
|
info := infos[key]
|
2018-06-01 01:28:09 +00:00
|
|
|
put(fmt.Sprintf("\"%v\" [ label = \"%v\" %v ];\n", info.key, info.name, info.options))
|
2018-01-21 00:17:15 +00:00
|
|
|
}
|
2018-01-30 00:48:14 +00:00
|
|
|
// Then print the tree structure
|
|
|
|
for _, key := range keys {
|
|
|
|
info := infos[key]
|
|
|
|
if info.key == info.parent {
|
|
|
|
continue
|
|
|
|
} // happens for the root, skip it
|
2018-07-21 04:02:25 +00:00
|
|
|
port := fmt.Sprint(info.port)
|
2018-06-02 13:24:06 +00:00
|
|
|
style := "fontname=\"sans serif\""
|
|
|
|
if infos[info.parent].name == "?" || infos[info.key].name == "?" {
|
|
|
|
style = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
|
|
|
|
}
|
|
|
|
put(fmt.Sprintf(" \"%+v\" -> \"%+v\" [ label = \"%v\" %s ];\n", info.parent, info.key, port, style))
|
2018-01-21 00:17:15 +00:00
|
|
|
}
|
2018-01-30 00:48:14 +00:00
|
|
|
put("}\n")
|
|
|
|
return out
|
2018-01-21 00:17:15 +00:00
|
|
|
}
|
2019-05-19 16:27:48 +00:00
|
|
|
*/
|