This commit is contained in:
asolana 2022-10-07 11:36:39 +02:00
parent 74910f96da
commit 82dd30ee5c
61 changed files with 2197 additions and 2030 deletions

12
src/admin/addpeer.go Normal file
View file

@ -0,0 +1,12 @@
package admin
type AddPeerRequest struct {
Uri string `json:"uri"`
Sintf string `json:"interface,omitempty"`
}
type AddPeerResponse struct{}
func (a *AdminSocket) addPeerHandler(req *AddPeerRequest, res *AddPeerResponse) error {
return a.core.AddPeer(req.Uri, req.Sintf)
}

View file

@ -7,55 +7,63 @@ import (
"net"
"net/url"
"os"
"sort"
"strings"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/core"
)
// TODO: Add authentication
type AdminSocket struct {
core *core.Core
log *log.Logger
listenaddr string
listener net.Listener
handlers map[string]handler
done chan struct{}
core *core.Core
log core.Logger
listener net.Listener
handlers map[string]handler
done chan struct{}
config struct {
listenaddr ListenAddress
}
}
type AdminSocketRequest struct {
Name string `json:"request"`
Arguments json.RawMessage `json:"arguments,omitempty"`
KeepAlive bool `json:"keepalive,omitempty"`
}
type AdminSocketResponse struct {
Status string `json:"status"`
Request struct {
Name string `json:"request"`
KeepAlive bool `json:"keepalive"`
} `json:"request"`
Response interface{} `json:"response"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
Request json.RawMessage `json:"request"`
Response json.RawMessage `json:"response"`
}
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
}
type ListResponse struct {
List map[string]ListEntry `json:"list"`
List []ListEntry `json:"list"`
}
type ListEntry struct {
Fields []string `json:"fields"`
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,
}
@ -63,100 +71,142 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc core.Ad
}
// Init runs the initial admin setup.
func (a *AdminSocket) Init(c *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error {
a.core = c
a.log = log
a.handlers = make(map[string]handler)
nc.RLock()
a.listenaddr = nc.AdminListen
nc.RUnlock()
a.done = make(chan struct{})
close(a.done) // Start in a done / not-started state
_ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) {
res := &ListResponse{
List: map[string]ListEntry{},
}
func New(c *core.Core, log core.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
}
_ = a.AddHandler("list", "List available commands", []string{}, func(_ json.RawMessage) (interface{}, error) {
res := &ListResponse{}
for name, handler := range a.handlers {
res.List[name] = ListEntry{
Fields: handler.args,
}
res.List = append(res.List, ListEntry{
Command: name,
Description: handler.desc,
Fields: handler.args,
})
}
sort.SliceStable(res.List, func(i, j int) bool {
return strings.Compare(res.List[i].Command, res.List[j].Command) < 0
})
return res, nil
})
return a.core.SetAdmin(a)
a.done = make(chan struct{})
go a.listen()
return a, a.core.SetAdmin(a)
}
func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) {
_ = 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
})
func (a *AdminSocket) SetupAdminHandlers() {
_ = 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(
"addPeer", "Add a peer to the peer list", []string{"uri", "interface"},
func(in json.RawMessage) (interface{}, error) {
req := &AddPeerRequest{}
res := &AddPeerResponse{}
if err := json.Unmarshal(in, &req); err != nil {
return nil, err
}
if err := a.addPeerHandler(req, res); err != nil {
return nil, err
}
return res, nil
},
)
_ = a.AddHandler(
"removePeer", "Remove a peer from the peer list", []string{"uri", "interface"},
func(in json.RawMessage) (interface{}, error) {
req := &RemovePeerRequest{}
res := &RemovePeerResponse{}
if err := json.Unmarshal(in, &req); err != nil {
return nil, err
}
if err := a.removePeerHandler(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)
//_ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler)
}
// Start runs the admin API socket to listen for / respond to admin API calls.
func (a *AdminSocket) Start() error {
if a.listenaddr != "none" && a.listenaddr != "" {
a.done = make(chan struct{})
go a.listen()
}
return nil
}
// IsStarted returns true if the module has been started.
func (a *AdminSocket) IsStarted() bool {
select {
@ -171,6 +221,9 @@ func (a *AdminSocket) IsStarted() bool {
// Stop will stop the admin API and close the socket.
func (a *AdminSocket) Stop() error {
if a == nil {
return nil
}
if a.listener != nil {
select {
case <-a.done:
@ -184,31 +237,32 @@ func (a *AdminSocket) Stop() error {
// listen is run by start and manages API connections.
func (a *AdminSocket) listen() {
u, err := url.Parse(a.listenaddr)
listenaddr := string(a.config.listenaddr)
u, err := url.Parse(listenaddr)
if err == nil {
switch strings.ToLower(u.Scheme) {
case "unix":
if _, err := os.Stat(a.listenaddr[7:]); err == nil {
a.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up")
if _, err := net.DialTimeout("unix", a.listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() {
a.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process")
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")
os.Exit(1)
} else {
if err := os.Remove(a.listenaddr[7:]); err == nil {
a.log.Debugln(a.listenaddr[7:], "was cleaned up")
if err := os.Remove(listenaddr[7:]); err == nil {
a.log.Debugln(listenaddr[7:], "was cleaned up")
} else {
a.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err)
a.log.Errorln(listenaddr[7:], "already exists and was not cleaned up:", err)
os.Exit(1)
}
}
}
a.listener, err = net.Listen("unix", a.listenaddr[7:])
a.listener, err = net.Listen("unix", listenaddr[7:])
if err == nil {
switch a.listenaddr[7:8] {
switch listenaddr[7:8] {
case "@": // maybe abstract namespace
default:
if err := os.Chmod(a.listenaddr[7:], 0660); err != nil {
a.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!")
if err := os.Chmod(listenaddr[7:], 0660); err != nil {
a.log.Warnln("WARNING:", listenaddr[:7], "may have unsafe permissions!")
}
}
}
@ -216,10 +270,10 @@ func (a *AdminSocket) listen() {
a.listener, err = net.Listen("tcp", u.Host)
default:
// err = errors.New(fmt.Sprint("protocol not supported: ", u.Scheme))
a.listener, err = net.Listen("tcp", a.listenaddr)
a.listener, err = net.Listen("tcp", listenaddr)
}
} else {
a.listener, err = net.Listen("tcp", a.listenaddr)
a.listener, err = net.Listen("tcp", listenaddr)
}
if err != nil {
a.log.Errorf("Admin socket failed to listen: %v", err)
@ -271,37 +325,58 @@ func (a *AdminSocket) handleRequest(conn net.Conn) {
for {
var err error
var buf json.RawMessage
_ = decoder.Decode(&buf)
var req AdminSocketRequest
var resp AdminSocketResponse
resp.Status = "success"
if err = json.Unmarshal(buf, &resp.Request); err == nil {
if resp.Request.Name == "" {
resp.Status = "error"
resp.Response = &ErrorResponse{
Error: "No request specified",
}
} else if h, ok := a.handlers[strings.ToLower(resp.Request.Name)]; ok {
resp.Response, err = h.handler(buf)
if err != nil {
resp.Status = "error"
resp.Response = &ErrorResponse{
Error: err.Error(),
}
}
} else {
resp.Status = "error"
resp.Response = &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, &req); err != nil {
return fmt.Errorf("Failed to unmarshal request")
}
if req.Name == "" {
return fmt.Errorf("No request specified")
}
reqname := strings.ToLower(req.Name)
handler, ok := a.handlers[reqname]
if !ok {
return fmt.Errorf("Unknown action '%s', try 'list' for help", reqname)
}
res, err := handler.handler(req.Arguments)
if err != nil {
return 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)
}
if !resp.Request.KeepAlive {
if !req.KeepAlive {
break
} else {
continue
}
}
}
type DataUnit uint64
func (d DataUnit) String() string {
switch {
case d > 1024*1024*1024*1024:
return fmt.Sprintf("%2.ftb", float64(d)/1024/1024/1024/1024)
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)
}
}

View file

@ -3,6 +3,8 @@ package admin
import (
"encoding/hex"
"net"
"sort"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
@ -10,25 +12,30 @@ import (
type GetDHTRequest struct{}
type GetDHTResponse struct {
DHT map[string]DHTEntry `json:"dht"`
DHT []DHTEntry `json:"dht"`
}
type DHTEntry struct {
IPAddress string `json:"address"`
PublicKey string `json:"key"`
Port uint64 `json:"port"`
Rest uint64 `json:"rest"`
}
func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) error {
res.DHT = map[string]DHTEntry{}
for _, d := range a.core.GetDHT() {
dht := a.core.GetDHT()
res.DHT = make([]DHTEntry, 0, len(dht))
for _, d := range dht {
addr := address.AddrForKey(d.Key)
so := net.IP(addr[:]).String()
res.DHT[so] = DHTEntry{
res.DHT = append(res.DHT, DHTEntry{
IPAddress: net.IP(addr[:]).String(),
PublicKey: hex.EncodeToString(d.Key[:]),
Port: d.Port,
Rest: d.Rest,
}
})
}
sort.SliceStable(res.DHT, func(i, j int) bool {
return strings.Compare(res.DHT[i].PublicKey, res.DHT[j].PublicKey) < 0
})
return nil
}

View file

@ -3,6 +3,8 @@ package admin
import (
"encoding/hex"
"net"
"sort"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
@ -11,23 +13,28 @@ type GetPathsRequest struct {
}
type GetPathsResponse struct {
Paths map[string]PathEntry `json:"paths"`
Paths []PathEntry `json:"paths"`
}
type PathEntry struct {
IPAddress string `json:"address"`
PublicKey string `json:"key"`
Path []uint64 `json:"path"`
}
func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsResponse) error {
res.Paths = map[string]PathEntry{}
for _, p := range a.core.GetPaths() {
paths := a.core.GetPaths()
res.Paths = make([]PathEntry, 0, len(paths))
for _, p := range paths {
addr := address.AddrForKey(p.Key)
so := net.IP(addr[:]).String()
res.Paths[so] = PathEntry{
res.Paths = append(res.Paths, PathEntry{
IPAddress: net.IP(addr[:]).String(),
PublicKey: hex.EncodeToString(p.Key),
Path: p.Path,
}
})
}
sort.SliceStable(res.Paths, func(i, j int) bool {
return strings.Compare(res.Paths[i].PublicKey, res.Paths[j].PublicKey) < 0
})
return nil
}

View file

@ -3,6 +3,7 @@ package admin
import (
"encoding/hex"
"net"
"sort"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
@ -11,33 +12,38 @@ type GetPeersRequest struct {
}
type GetPeersResponse struct {
Peers map[string]PeerEntry `json:"peers"`
Peers []PeerEntry `json:"peers"`
}
type PeerEntry struct {
IPAddress string `json:"address"`
PublicKey string `json:"key"`
Port uint64 `json:"port"`
Coords []uint64 `json:"coords"`
Remote string `json:"remote"`
RXBytes uint64 `json:"bytes_recvd"`
TXBytes uint64 `json:"bytes_sent"`
RXBytes DataUnit `json:"bytes_recvd"`
TXBytes DataUnit `json:"bytes_sent"`
Uptime float64 `json:"uptime"`
}
func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersResponse) error {
res.Peers = map[string]PeerEntry{}
for _, p := range a.core.GetPeers() {
peers := a.core.GetPeers()
res.Peers = make([]PeerEntry, 0, len(peers))
for _, p := range peers {
addr := address.AddrForKey(p.Key)
so := net.IP(addr[:]).String()
res.Peers[so] = PeerEntry{
res.Peers = append(res.Peers, PeerEntry{
IPAddress: net.IP(addr[:]).String(),
PublicKey: hex.EncodeToString(p.Key),
Port: p.Port,
Coords: p.Coords,
Remote: p.Remote,
RXBytes: p.RXBytes,
TXBytes: p.TXBytes,
RXBytes: DataUnit(p.RXBytes),
TXBytes: DataUnit(p.TXBytes),
Uptime: p.Uptime.Seconds(),
}
})
}
sort.Slice(res.Peers, func(i, j int) bool {
return res.Peers[i].Port < res.Peers[j].Port
})
return nil
}

View file

@ -9,28 +9,22 @@ import (
type GetSelfRequest struct{}
type GetSelfResponse struct {
Self map[string]SelfEntry `json:"self"`
}
type SelfEntry struct {
BuildName string `json:"build_name"`
BuildVersion string `json:"build_version"`
PublicKey string `json:"key"`
IPAddress string `json:"address"`
Coords []uint64 `json:"coords"`
Subnet string `json:"subnet"`
}
func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error {
res.Self = make(map[string]SelfEntry)
self := a.core.GetSelf()
addr := a.core.Address().String()
snet := a.core.Subnet()
res.Self[addr] = SelfEntry{
BuildName: version.BuildName(),
BuildVersion: version.BuildVersion(),
PublicKey: hex.EncodeToString(self.Key[:]),
Subnet: snet.String(),
Coords: self.Coords,
}
res.BuildName = version.BuildName()
res.BuildVersion = version.BuildVersion()
res.PublicKey = hex.EncodeToString(self.Key[:])
res.IPAddress = a.core.Address().String()
res.Subnet = snet.String()
res.Coords = self.Coords
return nil
}

View file

@ -3,6 +3,8 @@ package admin
import (
"encoding/hex"
"net"
"sort"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
@ -10,21 +12,32 @@ import (
type GetSessionsRequest struct{}
type GetSessionsResponse struct {
Sessions map[string]SessionEntry `json:"sessions"`
Sessions []SessionEntry `json:"sessions"`
}
type SessionEntry struct {
PublicKey string `json:"key"`
IPAddress string `json:"address"`
PublicKey string `json:"key"`
RXBytes DataUnit `json:"bytes_recvd"`
TXBytes DataUnit `json:"bytes_sent"`
Uptime float64 `json:"uptime"`
}
func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessionsResponse) error {
res.Sessions = map[string]SessionEntry{}
for _, s := range a.core.GetSessions() {
sessions := a.core.GetSessions()
res.Sessions = make([]SessionEntry, 0, len(sessions))
for _, s := range sessions {
addr := address.AddrForKey(s.Key)
so := net.IP(addr[:]).String()
res.Sessions[so] = SessionEntry{
res.Sessions = append(res.Sessions, SessionEntry{
IPAddress: net.IP(addr[:]).String(),
PublicKey: hex.EncodeToString(s.Key[:]),
}
RXBytes: DataUnit(s.RXBytes),
TXBytes: DataUnit(s.TXBytes),
Uptime: s.Uptime.Seconds(),
})
}
sort.SliceStable(res.Sessions, func(i, j int) bool {
return strings.Compare(res.Sessions[i].PublicKey, res.Sessions[j].PublicKey) < 0
})
return nil
}

16
src/admin/options.go Normal file
View file

@ -0,0 +1,16 @@
package admin
func (c *AdminSocket) _applyOption(opt SetupOption) {
switch v := opt.(type) {
case ListenAddress:
c.config.listenaddr = v
}
}
type SetupOption interface {
isSetupOption()
}
type ListenAddress string
func (a ListenAddress) isSetupOption() {}

12
src/admin/removepeer.go Normal file
View file

@ -0,0 +1,12 @@
package admin
type RemovePeerRequest struct {
Uri string `json:"uri"`
Sintf string `json:"interface,omitempty"`
}
type RemovePeerResponse struct{}
func (a *AdminSocket) removePeerHandler(req *RemovePeerRequest, res *RemovePeerResponse) error {
return a.core.RemovePeer(req.Uri, req.Sintf)
}