From 2c7b22db920239ca4845158a82e1688192602c95 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 May 2021 13:01:54 -0500 Subject: [PATCH 1/3] allow for multiple traffic types inside the session at the tuntap level, only implement typeSessionTraffic for now --- src/tuntap/iface.go | 20 +++++++++++++++++--- src/tuntap/tun.go | 14 +++++++------- src/tuntap/types.go | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 src/tuntap/types.go diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index d2680d0d..ff91cf17 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -50,6 +50,8 @@ func (tun *TunAdapter) read() { if srcAddr != tun.addr && srcSubnet != tun.subnet { continue // Wrong soruce address } + bs = buf[begin-1 : end] + bs[0] = typeSessionTraffic if dstAddr.IsValid() { tun.store.sendToAddress(dstAddr, bs) } else if dstSubnet.IsValid() { @@ -61,12 +63,24 @@ func (tun *TunAdapter) read() { func (tun *TunAdapter) write() { var buf [TUN_OFFSET_BYTES + 65535]byte for { - bs := buf[TUN_OFFSET_BYTES:] + bs := buf[TUN_OFFSET_BYTES-1:] n, from, err := tun.core.ReadFrom(bs) if err != nil { return } - bs = bs[:n] + if n == 0 { + continue + } + switch bs[0] { + case typeSessionTraffic: + // This is what we want to handle here + default: + continue + } + bs = bs[1:n] + if len(bs) == 0 { + continue + } if bs[0]&0xf0 != 0x60 { continue // not IPv6 } @@ -99,7 +113,7 @@ func (tun *TunAdapter) write() { if srcAddr != info.address && srcSubnet != info.subnet { continue // bad remote address/subnet } - bs = buf[:TUN_OFFSET_BYTES+n] + bs = buf[:TUN_OFFSET_BYTES+len(bs)] n, err = tun.iface.Write(bs, TUN_OFFSET_BYTES) if err != nil { tun.Act(nil, func() { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 6d41f59b..4492784d 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -150,8 +150,8 @@ func (tun *TunAdapter) _start() error { return nil } mtu := current.IfMTU - if tun.core.MTU() < uint64(mtu) { - mtu = MTU(tun.core.MTU()) + if tun.maxSessionMTU() < mtu { + mtu = tun.maxSessionMTU() } if err := tun.setup(current.IfName, addr, mtu); err != nil { return err @@ -216,11 +216,6 @@ func (tun *TunAdapter) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) } } -const ( - typeKeyLookup = 1 - typeKeyResponse = 2 -) - func (tun *TunAdapter) sendKeyLookup(partial ed25519.PublicKey) { sig := ed25519.Sign(tun.core.PrivateKey(), partial[:]) bs := append([]byte{typeKeyLookup}, sig...) @@ -232,3 +227,8 @@ func (tun *TunAdapter) sendKeyResponse(dest ed25519.PublicKey) { bs := append([]byte{typeKeyResponse}, sig...) tun.core.SendOutOfBand(dest, bs) } + +func (tun *TunAdapter) maxSessionMTU() MTU { + const sessionTypeOverhead = 1 + return MTU(tun.core.MTU() - sessionTypeOverhead) +} diff --git a/src/tuntap/types.go b/src/tuntap/types.go new file mode 100644 index 00000000..b503e647 --- /dev/null +++ b/src/tuntap/types.go @@ -0,0 +1,14 @@ +package tuntap + +// Out-of-band packet types +const ( + typeKeyDummy = iota + typeKeyLookup + typeKeyResponse +) + +// In-band packet types +const ( + typeSessionDummy = iota + typeSessionTraffic +) From 2e45e970c64cfbca6536964e2eddafb2a48c0d74 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 May 2021 13:52:52 -0500 Subject: [PATCH 2/3] 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 ) From a6c254c87a5a59ed7cc1f15b730a40f671f63a35 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 May 2021 14:00:37 -0500 Subject: [PATCH 3/3] more nodeinfo WIP, still needs admin socket support --- src/tuntap/iface.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 87129f2b..a3275436 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -77,6 +77,17 @@ func (tun *TunAdapter) write() { if !tun.isEnabled { continue // Drop traffic if the tun is disabled } + case typeSessionNodeInfoRequest: + var key keyArray + copy(key[:], from.(iwt.Addr)) + tun.nodeinfo.handleReq(nil, key) + continue + case typeSessionNodeInfoResponse: + var key keyArray + copy(key[:], from.(iwt.Addr)) + res := append([]byte(nil), bs[1:n]...) + tun.nodeinfo.handleRes(nil, key, res) + continue default: continue }