Refactor admin socket, export request/response structs, remove types package

This commit is contained in:
Neil Alexander 2021-05-16 19:51:09 +01:00
parent dfca87ba80
commit 2d01386d6e
13 changed files with 290 additions and 218 deletions

View File

@ -1,7 +1,6 @@
package admin
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@ -9,17 +8,12 @@ import (
"net/url"
"os"
//"strconv"
"strings"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
//"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
//"github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
@ -34,16 +28,26 @@ type AdminSocket struct {
started bool
}
// Info refers to information that is returned to the admin socket handler.
type Info map[string]interface{}
type AdminSocketResponse struct {
Status string `json:"status"`
Request struct {
Name string `json:"request"`
KeepAlive bool `json:"keepalive"`
} `json:"request"`
Response interface{} `json:"response"`
}
type handler struct {
args []string // List of human-readable argument names
handler func(Info) (Info, error) // First is input map, second is output
handler func(json.RawMessage) (interface{}, error) // First is input map, second is output
}
type ListResponse struct {
List map[string][]string `json:"list"`
}
// 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 {
func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(json.RawMessage) (interface{}, error)) error {
if _, ok := a.handlers[strings.ToLower(name)]; ok {
return errors.New("handler already exists")
}
@ -61,70 +65,60 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.
a.handlers = make(map[string]handler)
current := state.GetCurrent()
a.listenaddr = current.AdminListen
a.AddHandler("list", []string{}, func(in Info) (Info, error) {
handlers := make(map[string]interface{})
for handlername, handler := range a.handlers {
handlers[handlername] = Info{"fields": handler.args}
_ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) {
res := &ListResponse{}
for name, handler := range a.handlers {
res.List[name] = handler.args
}
return Info{"list": handlers}, nil
return res, nil
})
return nil
}
func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) {
a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) {
ip := a.core.Address().String()
subnet := a.core.Subnet()
self := a.core.GetSelf()
return Info{
"self": Info{
ip: Info{
// TODO"box_pub_key": a.core.EncryptionPublicKey(),
"build_name": version.BuildName(),
"build_version": version.BuildVersion(),
"key": hex.EncodeToString(self.Key[:]),
"coords": fmt.Sprintf("%v", self.Coords),
"subnet": subnet.String(),
},
},
}, nil
_ = 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 Info) (Info, error) {
peers := make(Info)
for _, p := range a.core.GetPeers() {
addr := address.AddrForKey(p.Key)
so := net.IP(addr[:]).String()
peers[so] = Info{
"key": hex.EncodeToString(p.Key[:]),
"port": p.Port,
"coords": fmt.Sprintf("%v", p.Coords),
_ = 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 Info{"peers": peers}, nil
return res, nil
})
a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) {
dht := make(Info)
for _, d := range a.core.GetDHT() {
addr := address.AddrForKey(d.Key)
so := net.IP(addr[:]).String()
dht[so] = Info{
"key": hex.EncodeToString(d.Key[:]),
"port": fmt.Sprintf("%v", d.Port),
"next": fmt.Sprintf("%v", d.Next),
_ = 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 Info{"dht": dht}, nil
return res, nil
})
a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) {
sessions := make(Info)
for _, s := range a.core.GetSessions() {
addr := address.AddrForKey(s.Key)
so := net.IP(addr[:]).String()
sessions[so] = Info{
"key": hex.EncodeToString(s.Key[:]),
_ = 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 Info{"sessions": sessions}, nil
return res, nil
})
}
@ -209,20 +203,20 @@ func (a *AdminSocket) listen() {
// handleRequest calls the request handler for each request sent to the admin API.
func (a *AdminSocket) handleRequest(conn net.Conn) {
decoder := json.NewDecoder(conn)
decoder.DisallowUnknownFields()
encoder := json.NewEncoder(conn)
encoder.SetIndent("", " ")
recv := make(Info)
send := make(Info)
defer conn.Close()
defer func() {
r := recover()
if r != nil {
send = Info{
"status": "error",
"error": "Check your syntax and input types",
}
a.log.Debugln("Admin socket error:", r)
if err := encoder.Encode(&send); err != nil {
if err := encoder.Encode(&ErrorResponse{
Error: "Check your syntax and input types",
}); err != nil {
a.log.Debugln("Admin socket JSON encode error:", err)
}
conn.Close()
@ -230,83 +224,40 @@ func (a *AdminSocket) handleRequest(conn net.Conn) {
}()
for {
// Start with a clean slate on each request
recv = Info{}
send = Info{}
// Decode the input
if err := decoder.Decode(&recv); err != nil {
a.log.Debugln("Admin socket JSON decode error:", err)
return
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 = &ErrorResponse{
Error: "No request specified",
}
// Send the request back with the response, and default to "error"
// unless the status is changed below by one of the handlers
send["request"] = recv
send["status"] = "error"
n := strings.ToLower(recv["request"].(string))
if _, ok := recv["request"]; !ok {
send["error"] = "No request sent"
goto respond
}
if h, ok := a.handlers[n]; ok {
// 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,
}
goto respond
}
}
// By this point we should have all the fields we need, so call
// the handler
response, err := h.handler(recv)
} else if h, ok := a.handlers[strings.ToLower(resp.Request.Name)]; ok {
resp.Response, err = h.handler(buf)
if err != nil {
send["error"] = err.Error()
if response != nil {
send["response"] = response
goto respond
}
} else {
send["status"] = "success"
if response != nil {
send["response"] = response
goto respond
resp.Status = "error"
resp.Response = &ErrorResponse{
Error: err.Error(),
}
}
} 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)),
resp.Status = "error"
resp.Response = &ErrorResponse{
Error: fmt.Sprintf("Unknown action '%s', try 'list' for help", resp.Request.Name),
}
goto respond
}
// Send the response back
respond:
if err := encoder.Encode(&send); err != nil {
return
}
// If "keepalive" isn't true then close the connection
if keepalive, ok := recv["keepalive"]; !ok || !keepalive.(bool) {
conn.Close()
j, _ := json.Marshal(resp)
if err = encoder.Encode(resp); err != nil {
a.log.Debugln("Encode error:", err)
}
if !resp.Request.KeepAlive {
break
} else {
continue
}
}
}

5
src/admin/error.go Normal file
View File

@ -0,0 +1,5 @@
package admin
type ErrorResponse struct {
Error string `json:"error"`
}

34
src/admin/getdht.go Normal file
View File

@ -0,0 +1,34 @@
package admin
import (
"encoding/hex"
"net"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
type GetDHTRequest struct{}
type GetDHTResponse struct {
DHT map[string]DHTEntry `json:"dht"`
}
type DHTEntry struct {
PublicKey string `json:"key"`
Port uint64 `json:"port"`
Next uint64 `json:"next"`
}
func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) error {
res.DHT = map[string]DHTEntry{}
for _, d := range a.core.GetDHT() {
addr := address.AddrForKey(d.Key)
so := net.IP(addr[:]).String()
res.DHT[so] = DHTEntry{
PublicKey: hex.EncodeToString(d.Key[:]),
Port: d.Port,
Next: d.Next,
}
}
return nil
}

35
src/admin/getpeers.go Normal file
View File

@ -0,0 +1,35 @@
package admin
import (
"encoding/hex"
"net"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
type GetPeersRequest struct {
}
type GetPeersResponse struct {
Peers map[string]PeerEntry `json:"peers"`
}
type PeerEntry struct {
PublicKey string `json:"key"`
Port uint64 `json:"port"`
Coords []uint64 `json:"coords"`
}
func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersResponse) error {
res.Peers = map[string]PeerEntry{}
for _, p := range a.core.GetPeers() {
addr := address.AddrForKey(p.Key)
so := net.IP(addr[:]).String()
res.Peers[so] = PeerEntry{
PublicKey: hex.EncodeToString(p.Key),
Port: p.Port,
Coords: p.Coords,
}
}
return nil
}

31
src/admin/getself.go Normal file
View File

@ -0,0 +1,31 @@
package admin
import (
"crypto/ed25519"
"encoding/hex"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
)
type GetSelfRequest struct{}
type GetSelfResponse struct {
BuildName string `json:"build_name"`
BuildVersion string `json:"build_version"`
PublicKey string `json:"key"`
Coords []uint64 `json:"coords"`
IPAddress string `json:"address"`
Subnet string `json:"subnet"`
}
func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error {
res.BuildName = version.BuildName()
res.BuildVersion = version.BuildVersion()
public := a.core.PrivateKey().Public().(ed25519.PublicKey)
res.PublicKey = hex.EncodeToString(public[:])
res.IPAddress = a.core.Address().String()
snet := a.core.Subnet()
res.Subnet = snet.String()
// TODO: res.coords
return nil
}

30
src/admin/getsessions.go Normal file
View File

@ -0,0 +1,30 @@
package admin
import (
"encoding/hex"
"net"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
type GetSessionsRequest struct{}
type GetSessionsResponse struct {
Sessions map[string]SessionEntry `json:"sessions"`
}
type SessionEntry struct {
PublicKey string `json:"key"`
}
func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessionsResponse) error {
res.Sessions = map[string]SessionEntry{}
for _, s := range a.core.GetSessions() {
addr := address.AddrForKey(s.Key)
so := net.IP(addr[:]).String()
res.Sessions[so] = SessionEntry{
PublicKey: hex.EncodeToString(s.Key[:]),
}
}
return nil
}

View File

@ -22,11 +22,8 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/types"
)
type MTU = types.MTU
// NodeState represents the active and previous configuration of an Yggdrasil
// node. A NodeState object is returned when starting an Yggdrasil node. Note
// that this structure and related functions are likely to disappear soon.
@ -72,7 +69,7 @@ type NodeConfig struct {
PrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"`
LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."`
IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
IfMTU MTU `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
IfMTU uint16 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."`
NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`

View File

@ -1,7 +1,5 @@
package defaults
import "github.com/yggdrasil-network/yggdrasil-go/src/types"
// Defines which parameters are expected by default for configuration on a
// specific platform. These values are populated in the relevant defaults_*.go
// for the platform being targeted. They must be set.
@ -16,7 +14,7 @@ type platformDefaultParameters struct {
DefaultMulticastInterfaces []string
// TUN/TAP
MaximumIfMTU types.MTU
DefaultIfMTU types.MTU
MaximumIfMTU uint16
DefaultIfMTU uint16
DefaultIfName string
}

View File

@ -1,13 +1,34 @@
package multicast
import "github.com/yggdrasil-network/yggdrasil-go/src/admin"
import (
"encoding/json"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
)
type GetMulticastInterfacesRequest struct{}
type GetMulticastInterfacesResponse struct {
Interfaces []string `json:"multicast_interfaces"`
}
func (m *Multicast) getMulticastInterfacesHandler(req *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error {
res.Interfaces = []string{}
for _, v := range m.Interfaces() {
res.Interfaces = append(res.Interfaces, v.Name)
}
return nil
}
func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) {
a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) {
var intfs []string
for _, v := range m.Interfaces() {
intfs = append(intfs, v.Name)
_ = 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
}
return admin.Info{"multicast_interfaces": intfs}, nil
if err := m.getMulticastInterfacesHandler(req, res); err != nil {
return nil, err
}
return res, nil
})
}

View File

@ -1,57 +1,31 @@
package tuntap
import (
//"encoding/hex"
//"errors"
//"fmt"
//"net"
"encoding/json"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
)
func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) {
a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) {
defer func() {
if err := recover(); err != nil {
r = admin.Info{"none": admin.Info{}}
e = nil
}
}()
type GetTUNRequest struct{}
type GetTUNResponse map[string]uint16
return admin.Info{
t.Name(): admin.Info{
"mtu": t.mtu,
},
}, nil
})
/*
// TODO: rewrite this as I'm fairly sure it doesn't work right on many
// platforms anyway, but it may require changes to Water
a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) {
// Set sane defaults
iftapmode := defaults.GetDefaults().DefaultIfTAPMode
ifmtu := defaults.GetDefaults().DefaultIfMTU
// Has TAP mode been specified?
if tap, ok := in["tap_mode"]; ok {
iftapmode = tap.(bool)
func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error {
res = &GetTUNResponse{
t.Name(): t.MTU(),
}
// Check we have enough params for MTU
if mtu, ok := in["mtu"]; ok {
if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU {
ifmtu = int(in["mtu"].(float64))
}
}
// Start the TUN adapter
if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil {
return Info{}, errors.New("Failed to configure adapter")
} else {
return Info{
a.core.router.tun.iface.Name(): Info{
"tap_mode": a.core.router.tun.iface.IsTAP(),
"mtu": ifmtu,
},
}, nil
}
})
*/
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
})
}

View File

@ -23,11 +23,10 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/types"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
type MTU = types.MTU
type MTU uint16
// TunAdapter represents a running TUN interface and extends the
// yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you
@ -40,7 +39,7 @@ type TunAdapter struct {
log *log.Logger
addr address.Address
subnet address.Subnet
mtu MTU
mtu uint16
iface tun.Device
phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
//mutex sync.RWMutex // Protects the below
@ -56,7 +55,7 @@ func (tun *TunAdapter) SetSessionGatekeeper(gatekeeper func(pubkey ed25519.Publi
// Gets the maximum supported MTU for the platform based on the defaults in
// defaults.GetDefaults().
func getSupportedMTU(mtu MTU) MTU {
func getSupportedMTU(mtu uint16) uint16 {
if mtu < 1280 {
return 1280
}
@ -78,7 +77,7 @@ func (tun *TunAdapter) Name() string {
// MTU gets the adapter's MTU. This can range between 1280 and 65535, although
// the maximum value is determined by your platform. The returned value will
// never exceed that of MaximumMTU().
func (tun *TunAdapter) MTU() MTU {
func (tun *TunAdapter) MTU() uint16 {
return getSupportedMTU(tun.mtu)
}
@ -89,14 +88,14 @@ func DefaultName() string {
// DefaultMTU gets the default TUN interface MTU for your platform. This can
// be as high as MaximumMTU(), depending on platform, but is never lower than 1280.
func DefaultMTU() MTU {
func DefaultMTU() uint16 {
return defaults.GetDefaults().DefaultIfMTU
}
// MaximumMTU returns the maximum supported TUN interface MTU for your
// platform. This can be as high as 65535, depending on platform, but is never
// lower than 1280.
func MaximumMTU() MTU {
func MaximumMTU() uint16 {
return defaults.GetDefaults().MaximumIfMTU
}
@ -151,7 +150,7 @@ func (tun *TunAdapter) _start() error {
}
mtu := current.IfMTU
if tun.core.MTU() < uint64(mtu) {
mtu = MTU(tun.core.MTU())
mtu = uint16(tun.core.MTU())
}
if err := tun.setup(current.IfName, addr, mtu); err != nil {
return err

View File

@ -16,7 +16,7 @@ import (
)
// Configures the "utun" adapter with the correct IPv6 address and MTU.
func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error {
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint16) error {
if ifname == "auto" {
ifname = "utun"
}
@ -25,8 +25,8 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error {
panic(err)
}
tun.iface = iface
if mtu, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(MTU(mtu))
if m, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(uint16(m))
} else {
tun.mtu = 0
}

View File

@ -1,3 +0,0 @@
package types
type MTU uint16