From 2e45e970c64cfbca6536964e2eddafb2a48c0d74 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 May 2021 13:52:52 -0500 Subject: [PATCH] work-in-progress adding nodeinfo --- src/tuntap/iface.go | 3 + src/tuntap/nodeinfo.go | 200 +++++++++++++++++++++++++++++++++++++++++ src/tuntap/tun.go | 15 ++-- src/tuntap/types.go | 2 + 4 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 src/tuntap/nodeinfo.go diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index ff91cf17..87129f2b 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -74,6 +74,9 @@ func (tun *TunAdapter) write() { switch bs[0] { case typeSessionTraffic: // This is what we want to handle here + if !tun.isEnabled { + continue // Drop traffic if the tun is disabled + } default: continue } diff --git a/src/tuntap/nodeinfo.go b/src/tuntap/nodeinfo.go new file mode 100644 index 00000000..a88916ca --- /dev/null +++ b/src/tuntap/nodeinfo.go @@ -0,0 +1,200 @@ +package tuntap + +import ( + "encoding/json" + "errors" + "runtime" + "strings" + "time" + + "github.com/Arceliar/phony" + //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/version" + + iwt "github.com/Arceliar/ironwood/types" +) + +// NodeInfoPayload represents a RequestNodeInfo response, in bytes. +type NodeInfoPayload []byte + +type nodeinfo struct { + phony.Inbox + tun *TunAdapter + myNodeInfo NodeInfoPayload + callbacks map[keyArray]nodeinfoCallback + cache map[keyArray]nodeinfoCached + //table *lookupTable +} + +type nodeinfoCached struct { + payload NodeInfoPayload + created time.Time +} + +type nodeinfoCallback struct { + call func(nodeinfo *NodeInfoPayload) + created time.Time +} + +// Represents a session nodeinfo packet. +type nodeinfoReqRes struct { + Key keyArray // Sender's permanent key + IsResponse bool + NodeInfo NodeInfoPayload +} + +// Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep +// the cache/callback maps clean of stale entries +func (m *nodeinfo) init(tun *TunAdapter) { + m.Act(nil, func() { + m._init(tun) + }) +} + +func (m *nodeinfo) _init(tun *TunAdapter) { + m.tun = tun + m.callbacks = make(map[keyArray]nodeinfoCallback) + m.cache = make(map[keyArray]nodeinfoCached) + + m._cleanup() +} + +func (m *nodeinfo) _cleanup() { + for boxPubKey, callback := range m.callbacks { + if time.Since(callback.created) > time.Minute { + delete(m.callbacks, boxPubKey) + } + } + for boxPubKey, cache := range m.cache { + if time.Since(cache.created) > time.Hour { + delete(m.cache, boxPubKey) + } + } + time.AfterFunc(time.Second*30, func() { + m.Act(nil, m._cleanup) + }) +} + +// Add a callback for a nodeinfo lookup +func (m *nodeinfo) addCallback(sender keyArray, call func(nodeinfo *NodeInfoPayload)) { + m.Act(nil, func() { + m._addCallback(sender, call) + }) +} + +func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo *NodeInfoPayload)) { + m.callbacks[sender] = nodeinfoCallback{ + created: time.Now(), + call: call, + } +} + +// Handles the callback, if there is one +func (m *nodeinfo) _callback(sender keyArray, nodeinfo NodeInfoPayload) { + if callback, ok := m.callbacks[sender]; ok { + callback.call(&nodeinfo) + delete(m.callbacks, sender) + } +} + +// Get the current node's nodeinfo +func (m *nodeinfo) getNodeInfo() (p NodeInfoPayload) { + phony.Block(m, func() { + p = m._getNodeInfo() + }) + return +} + +func (m *nodeinfo) _getNodeInfo() NodeInfoPayload { + return m.myNodeInfo +} + +// Set the current node's nodeinfo +func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) (err error) { + phony.Block(m, func() { + err = m._setNodeInfo(given, privacy) + }) + return +} + +func (m *nodeinfo) _setNodeInfo(given interface{}, privacy bool) error { + defaults := map[string]interface{}{ + "buildname": version.BuildName(), + "buildversion": version.BuildVersion(), + "buildplatform": runtime.GOOS, + "buildarch": runtime.GOARCH, + } + newnodeinfo := make(map[string]interface{}) + if !privacy { + for k, v := range defaults { + newnodeinfo[k] = v + } + } + if nodeinfomap, ok := given.(map[string]interface{}); ok { + for key, value := range nodeinfomap { + if _, ok := defaults[key]; ok { + if strvalue, strok := value.(string); strok && strings.EqualFold(strvalue, "null") || value == nil { + delete(newnodeinfo, key) + } + continue + } + newnodeinfo[key] = value + } + } + newjson, err := json.Marshal(newnodeinfo) + if err == nil { + if len(newjson) > 16384 { + return errors.New("NodeInfo exceeds max length of 16384 bytes") + } + m.myNodeInfo = newjson + return nil + } + return err +} + +// Add nodeinfo into the cache for a node +func (m *nodeinfo) _addCachedNodeInfo(key keyArray, payload NodeInfoPayload) { + m.cache[key] = nodeinfoCached{ + created: time.Now(), + payload: payload, + } +} + +// Get a nodeinfo entry from the cache +func (m *nodeinfo) _getCachedNodeInfo(key keyArray) (NodeInfoPayload, error) { + if nodeinfo, ok := m.cache[key]; ok { + return nodeinfo.payload, nil + } + return NodeInfoPayload{}, errors.New("No cache entry found") +} + +func (m *nodeinfo) sendReq(from phony.Actor, key keyArray, callback func(nodeinfo *NodeInfoPayload)) { + m.Act(from, func() { + m._sendReq(key, callback) + }) +} + +func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo *NodeInfoPayload)) { + if callback != nil { + m._addCallback(key, callback) + } + m.tun.core.WriteTo([]byte{typeSessionNodeInfoRequest}, iwt.Addr(key[:])) +} + +func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) { + m.Act(from, func() { + m._sendRes(key) + }) +} + +func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayload) { + m.Act(from, func() { + m._callback(key, info) + m._addCachedNodeInfo(key, info) + }) +} + +func (m *nodeinfo) _sendRes(key keyArray) { + bs := append([]byte{typeSessionNodeInfoResponse}, m._getNodeInfo()...) + m.tun.core.WriteTo(bs, iwt.Addr(key[:])) +} diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 4492784d..f5d19433 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -45,7 +45,9 @@ type TunAdapter struct { 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 isOpen bool + isEnabled bool // Used by the writer to drop sessionTraffic if not enabled gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool + nodeinfo nodeinfo } func (tun *TunAdapter) SetSessionGatekeeper(gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool) { @@ -107,6 +109,7 @@ func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log tun.store.init(tun) tun.config = config tun.log = log + tun.nodeinfo.init(tun) if err := tun.core.SetOutOfBandHandler(tun.oobHandler); err != nil { return fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) } @@ -138,15 +141,8 @@ func (tun *TunAdapter) _start() error { addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if current.IfName == "none" || current.IfName == "dummy" { tun.log.Debugln("Not starting TUN as ifname is none or dummy") - go func() { - bs := make([]byte, tun.core.PacketConn.MTU()) - for { - // Dump traffic to nowhere - if _, _, err := tun.core.PacketConn.ReadFrom(bs); err != nil { - return - } - } - }() + tun.isEnabled = false + go tun.write() return nil } mtu := current.IfMTU @@ -160,6 +156,7 @@ func (tun *TunAdapter) _start() error { tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU()) } tun.isOpen = true + tun.isEnabled = true go tun.read() go tun.write() return nil diff --git a/src/tuntap/types.go b/src/tuntap/types.go index b503e647..f1267a92 100644 --- a/src/tuntap/types.go +++ b/src/tuntap/types.go @@ -11,4 +11,6 @@ const ( const ( typeSessionDummy = iota typeSessionTraffic + typeSessionNodeInfoRequest + typeSessionNodeInfoResponse )