WIP moving IP-specific checks from tuntap to core

This commit is contained in:
Arceliar 2021-06-13 04:22:21 -05:00
parent bb66851c2b
commit 1147ee1934
9 changed files with 339 additions and 220 deletions

View file

@ -1,157 +0,0 @@
package tuntap
import (
"crypto/ed25519"
"sync"
"time"
iwt "github.com/Arceliar/ironwood/types"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
const keyStoreTimeout = 2 * time.Minute
type keyStore struct {
tun *TunAdapter
mutex sync.Mutex
keyToInfo map[keyArray]*keyInfo
addrToInfo map[address.Address]*keyInfo
addrBuffer map[address.Address]*buffer
subnetToInfo map[address.Subnet]*keyInfo
subnetBuffer map[address.Subnet]*buffer
}
type keyArray [ed25519.PublicKeySize]byte
type keyInfo struct {
key keyArray
address address.Address
subnet address.Subnet
timeout *time.Timer // From calling a time.AfterFunc to do cleanup
}
type buffer struct {
packets [][]byte
timeout *time.Timer
}
func (k *keyStore) init(tun *TunAdapter) {
k.tun = tun
k.keyToInfo = make(map[keyArray]*keyInfo)
k.addrToInfo = make(map[address.Address]*keyInfo)
k.addrBuffer = make(map[address.Address]*buffer)
k.subnetToInfo = make(map[address.Subnet]*keyInfo)
k.subnetBuffer = make(map[address.Subnet]*buffer)
}
func (k *keyStore) sendToAddress(addr address.Address, bs []byte) {
k.mutex.Lock()
if info := k.addrToInfo[addr]; info != nil {
k.resetTimeout(info)
k.mutex.Unlock()
_, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:]))
} else {
var buf *buffer
if buf = k.addrBuffer[addr]; buf == nil {
buf = new(buffer)
k.addrBuffer[addr] = buf
}
msg := append([]byte(nil), bs...)
buf.packets = append(buf.packets, msg)
if buf.timeout != nil {
buf.timeout.Stop()
}
buf.timeout = time.AfterFunc(keyStoreTimeout, func() {
k.mutex.Lock()
defer k.mutex.Unlock()
if nbuf := k.addrBuffer[addr]; nbuf == buf {
delete(k.addrBuffer, addr)
}
})
k.mutex.Unlock()
k.tun.sendKeyLookup(addr.GetKey())
}
}
func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) {
k.mutex.Lock()
if info := k.subnetToInfo[subnet]; info != nil {
k.resetTimeout(info)
k.mutex.Unlock()
_, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:]))
} else {
var buf *buffer
if buf = k.subnetBuffer[subnet]; buf == nil {
buf = new(buffer)
k.subnetBuffer[subnet] = buf
}
msg := append([]byte(nil), bs...)
buf.packets = append(buf.packets, msg)
if buf.timeout != nil {
buf.timeout.Stop()
}
buf.timeout = time.AfterFunc(keyStoreTimeout, func() {
k.mutex.Lock()
defer k.mutex.Unlock()
if nbuf := k.subnetBuffer[subnet]; nbuf == buf {
delete(k.subnetBuffer, subnet)
}
})
k.mutex.Unlock()
k.tun.sendKeyLookup(subnet.GetKey())
}
}
func (k *keyStore) update(key ed25519.PublicKey) *keyInfo {
k.mutex.Lock()
var kArray keyArray
copy(kArray[:], key)
var info *keyInfo
if info = k.keyToInfo[kArray]; info == nil {
info = new(keyInfo)
info.key = kArray
info.address = *address.AddrForKey(ed25519.PublicKey(info.key[:]))
info.subnet = *address.SubnetForKey(ed25519.PublicKey(info.key[:]))
k.keyToInfo[info.key] = info
k.addrToInfo[info.address] = info
k.subnetToInfo[info.subnet] = info
k.resetTimeout(info)
k.mutex.Unlock()
if buf := k.addrBuffer[info.address]; buf != nil {
for _, bs := range buf.packets {
_, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:]))
}
delete(k.addrBuffer, info.address)
}
if buf := k.subnetBuffer[info.subnet]; buf != nil {
for _, bs := range buf.packets {
_, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:]))
}
delete(k.subnetBuffer, info.subnet)
}
} else {
k.resetTimeout(info)
k.mutex.Unlock()
}
return info
}
func (k *keyStore) resetTimeout(info *keyInfo) {
if info.timeout != nil {
info.timeout.Stop()
}
info.timeout = time.AfterFunc(keyStoreTimeout, func() {
k.mutex.Lock()
defer k.mutex.Unlock()
if nfo := k.keyToInfo[info.key]; nfo == info {
delete(k.keyToInfo, info.key)
}
if nfo := k.addrToInfo[info.address]; nfo == info {
delete(k.addrToInfo, info.address)
}
if nfo := k.subnetToInfo[info.subnet]; nfo == info {
delete(k.subnetToInfo, info.subnet)
}
})
}

View file

@ -1,189 +0,0 @@
package tuntap
import (
"encoding/hex"
"encoding/json"
"errors"
"net"
"runtime"
"strings"
"time"
iwt "github.com/Arceliar/ironwood/types"
"github.com/Arceliar/phony"
//"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
)
// NodeInfoPayload represents a RequestNodeInfo response, in bytes.
type NodeInfoPayload []byte
type nodeinfo struct {
phony.Inbox
proto *protoHandler
myNodeInfo NodeInfoPayload
callbacks map[keyArray]nodeinfoCallback
}
type nodeinfoCallback struct {
call func(nodeinfo NodeInfoPayload)
created time.Time
}
// Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep
// the cache/callback maps clean of stale entries
func (m *nodeinfo) init(proto *protoHandler) {
m.Act(nil, func() {
m._init(proto)
})
}
func (m *nodeinfo) _init(proto *protoHandler) {
m.proto = proto
m.callbacks = make(map[keyArray]nodeinfoCallback)
m._cleanup()
}
func (m *nodeinfo) _cleanup() {
for boxPubKey, callback := range m.callbacks {
if time.Since(callback.created) > time.Minute {
delete(m.callbacks, boxPubKey)
}
}
time.AfterFunc(time.Second*30, func() {
m.Act(nil, m._cleanup)
})
}
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)
}
}
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
}
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.proto.tun.core.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, 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)
})
}
func (m *nodeinfo) _sendRes(key keyArray) {
bs := append([]byte{typeSessionProto, typeProtoNodeInfoResponse}, m._getNodeInfo()...)
_, _ = m.proto.tun.core.WriteTo(bs, iwt.Addr(key[:]))
}
// Admin socket stuff
type GetNodeInfoRequest struct {
Key string `json:"key"`
}
type GetNodeInfoResponse map[string]interface{}
func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) {
var req GetNodeInfoRequest
if err := json.Unmarshal(in, &req); err != nil {
return nil, err
}
var key keyArray
var kbs []byte
var err error
if kbs, err = hex.DecodeString(req.Key); err != nil {
return nil, err
}
copy(key[:], kbs)
ch := make(chan []byte, 1)
m.sendReq(nil, key, func(info NodeInfoPayload) {
ch <- info
})
timer := time.NewTimer(6 * time.Second)
defer timer.Stop()
select {
case <-timer.C:
return nil, errors.New("timeout")
case info := <-ch:
var msg json.RawMessage
if err := msg.UnmarshalJSON(info); err != nil {
return nil, err
}
ip := net.IP(address.AddrForKey(kbs)[:])
res := GetNodeInfoResponse{ip.String(): msg}
return res, nil
}
}

View file

@ -1,349 +0,0 @@
package tuntap
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net"
"time"
iwt "github.com/Arceliar/ironwood/types"
"github.com/Arceliar/phony"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
const (
typeDebugDummy = iota
typeDebugGetSelfRequest
typeDebugGetSelfResponse
typeDebugGetPeersRequest
typeDebugGetPeersResponse
typeDebugGetDHTRequest
typeDebugGetDHTResponse
)
type reqInfo struct {
callback func([]byte)
timer *time.Timer // time.AfterFunc cleanup
}
type protoHandler struct {
phony.Inbox
tun *TunAdapter
nodeinfo nodeinfo
sreqs map[keyArray]*reqInfo
preqs map[keyArray]*reqInfo
dreqs map[keyArray]*reqInfo
}
func (p *protoHandler) init(tun *TunAdapter) {
p.tun = tun
p.nodeinfo.init(p)
p.sreqs = make(map[keyArray]*reqInfo)
p.preqs = make(map[keyArray]*reqInfo)
p.dreqs = make(map[keyArray]*reqInfo)
}
func (p *protoHandler) handleProto(from phony.Actor, key keyArray, bs []byte) {
if len(bs) == 0 {
return
}
switch bs[0] {
case typeProtoDummy:
case typeProtoNodeInfoRequest:
p.nodeinfo.handleReq(p, key)
case typeProtoNodeInfoResponse:
p.nodeinfo.handleRes(p, key, bs[1:])
case typeProtoDebug:
p._handleDebug(key, bs[1:])
}
}
func (p *protoHandler) _handleDebug(key keyArray, bs []byte) {
if len(bs) == 0 {
return
}
switch bs[0] {
case typeDebugDummy:
case typeDebugGetSelfRequest:
p._handleGetSelfRequest(key)
case typeDebugGetSelfResponse:
p._handleGetSelfResponse(key, bs[1:])
case typeDebugGetPeersRequest:
p._handleGetPeersRequest(key)
case typeDebugGetPeersResponse:
p._handleGetPeersResponse(key, bs[1:])
case typeDebugGetDHTRequest:
p._handleGetDHTRequest(key)
case typeDebugGetDHTResponse:
p._handleGetDHTResponse(key, bs[1:])
}
}
func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) {
p.Act(nil, func() {
if info := p.sreqs[key]; info != nil {
info.timer.Stop()
delete(p.sreqs, key)
}
info := new(reqInfo)
info.callback = callback
info.timer = time.AfterFunc(time.Minute, func() {
p.Act(nil, func() {
if p.sreqs[key] == info {
delete(p.sreqs, key)
}
})
})
p.sreqs[key] = info
p._sendDebug(key, typeDebugGetSelfRequest, nil)
})
}
func (p *protoHandler) _handleGetSelfRequest(key keyArray) {
self := p.tun.core.GetSelf()
res := map[string]string{
"key": hex.EncodeToString(self.Key[:]),
"coords": fmt.Sprintf("%v", self.Coords),
}
bs, err := json.Marshal(res) // FIXME this puts keys in base64, not hex
if err != nil {
return
}
p._sendDebug(key, typeDebugGetSelfResponse, bs)
}
func (p *protoHandler) _handleGetSelfResponse(key keyArray, bs []byte) {
if info := p.sreqs[key]; info != nil {
info.timer.Stop()
info.callback(bs)
delete(p.sreqs, key)
}
}
func (p *protoHandler) sendGetPeersRequest(key keyArray, callback func([]byte)) {
p.Act(nil, func() {
if info := p.preqs[key]; info != nil {
info.timer.Stop()
delete(p.preqs, key)
}
info := new(reqInfo)
info.callback = callback
info.timer = time.AfterFunc(time.Minute, func() {
p.Act(nil, func() {
if p.preqs[key] == info {
delete(p.preqs, key)
}
})
})
p.preqs[key] = info
p._sendDebug(key, typeDebugGetPeersRequest, nil)
})
}
func (p *protoHandler) _handleGetPeersRequest(key keyArray) {
peers := p.tun.core.GetPeers()
var bs []byte
for _, pinfo := range peers {
tmp := append(bs, pinfo.Key[:]...)
const responseOverhead = 2 // 1 debug type, 1 getpeers type
if uint64(len(tmp))+responseOverhead > p.tun.maxSessionMTU() {
break
}
bs = tmp
}
p._sendDebug(key, typeDebugGetPeersResponse, bs)
}
func (p *protoHandler) _handleGetPeersResponse(key keyArray, bs []byte) {
if info := p.preqs[key]; info != nil {
info.timer.Stop()
info.callback(bs)
delete(p.preqs, key)
}
}
func (p *protoHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) {
p.Act(nil, func() {
if info := p.dreqs[key]; info != nil {
info.timer.Stop()
delete(p.dreqs, key)
}
info := new(reqInfo)
info.callback = callback
info.timer = time.AfterFunc(time.Minute, func() {
p.Act(nil, func() {
if p.dreqs[key] == info {
delete(p.dreqs, key)
}
})
})
p.dreqs[key] = info
p._sendDebug(key, typeDebugGetDHTRequest, nil)
})
}
func (p *protoHandler) _handleGetDHTRequest(key keyArray) {
dinfos := p.tun.core.GetDHT()
var bs []byte
for _, dinfo := range dinfos {
tmp := append(bs, dinfo.Key[:]...)
const responseOverhead = 2 // 1 debug type, 1 getdht type
if uint64(len(tmp))+responseOverhead > p.tun.maxSessionMTU() {
break
}
bs = tmp
}
p._sendDebug(key, typeDebugGetDHTResponse, bs)
}
func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) {
if info := p.dreqs[key]; info != nil {
info.timer.Stop()
info.callback(bs)
delete(p.dreqs, key)
}
}
func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) {
bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...)
_, _ = p.tun.core.WriteTo(bs, iwt.Addr(key[:]))
}
// Admin socket stuff
type DebugGetSelfRequest struct {
Key string `json:"key"`
}
type DebugGetSelfResponse map[string]interface{}
func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) {
var req DebugGetSelfRequest
if err := json.Unmarshal(in, &req); err != nil {
return nil, err
}
var key keyArray
var kbs []byte
var err error
if kbs, err = hex.DecodeString(req.Key); err != nil {
return nil, err
}
copy(key[:], kbs)
ch := make(chan []byte, 1)
p.sendGetSelfRequest(key, func(info []byte) {
ch <- info
})
timer := time.NewTimer(6 * time.Second)
defer timer.Stop()
select {
case <-timer.C:
return nil, errors.New("timeout")
case info := <-ch:
var msg json.RawMessage
if err := msg.UnmarshalJSON(info); err != nil {
return nil, err
}
ip := net.IP(address.AddrForKey(kbs)[:])
res := DebugGetSelfResponse{ip.String(): msg}
return res, nil
}
}
type DebugGetPeersRequest struct {
Key string `json:"key"`
}
type DebugGetPeersResponse map[string]interface{}
func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error) {
var req DebugGetPeersRequest
if err := json.Unmarshal(in, &req); err != nil {
return nil, err
}
var key keyArray
var kbs []byte
var err error
if kbs, err = hex.DecodeString(req.Key); err != nil {
return nil, err
}
copy(key[:], kbs)
ch := make(chan []byte, 1)
p.sendGetPeersRequest(key, func(info []byte) {
ch <- info
})
timer := time.NewTimer(6 * time.Second)
defer timer.Stop()
select {
case <-timer.C:
return nil, errors.New("timeout")
case info := <-ch:
ks := make(map[string][]string)
bs := info
for len(bs) >= len(key) {
ks["keys"] = append(ks["keys"], hex.EncodeToString(bs[:len(key)]))
bs = bs[len(key):]
}
js, err := json.Marshal(ks)
if err != nil {
return nil, err
}
var msg json.RawMessage
if err := msg.UnmarshalJSON(js); err != nil {
return nil, err
}
ip := net.IP(address.AddrForKey(kbs)[:])
res := DebugGetPeersResponse{ip.String(): msg}
return res, nil
}
}
type DebugGetDHTRequest struct {
Key string `json:"key"`
}
type DebugGetDHTResponse map[string]interface{}
func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
var req DebugGetDHTRequest
if err := json.Unmarshal(in, &req); err != nil {
return nil, err
}
var key keyArray
var kbs []byte
var err error
if kbs, err = hex.DecodeString(req.Key); err != nil {
return nil, err
}
copy(key[:], kbs)
ch := make(chan []byte, 1)
p.sendGetDHTRequest(key, func(info []byte) {
ch <- info
})
timer := time.NewTimer(6 * time.Second)
defer timer.Stop()
select {
case <-timer.C:
return nil, errors.New("timeout")
case info := <-ch:
ks := make(map[string][]string)
bs := info
for len(bs) >= len(key) {
ks["keys"] = append(ks["keys"], hex.EncodeToString(bs[:len(key)]))
bs = bs[len(key):]
}
js, err := json.Marshal(ks)
if err != nil {
return nil, err
}
var msg json.RawMessage
if err := msg.UnmarshalJSON(js); err != nil {
return nil, err
}
ip := net.IP(address.AddrForKey(kbs)[:])
res := DebugGetDHTResponse{ip.String(): msg}
return res, nil
}
}

View file

@ -189,41 +189,3 @@ func (tun *TunAdapter) _stop() error {
return nil
}
func (tun *TunAdapter) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) {
if len(data) != 1+ed25519.SignatureSize {
return
}
sig := data[1:]
switch data[0] {
case typeKeyLookup:
snet := *address.SubnetForKey(toKey)
if snet == tun.subnet && ed25519.Verify(fromKey, toKey[:], sig) {
// This is looking for at least our subnet (possibly our address)
// Send a response
tun.sendKeyResponse(fromKey)
}
case typeKeyResponse:
// TODO keep a list of something to match against...
// Ignore the response if it doesn't match anything of interest...
if ed25519.Verify(fromKey, toKey[:], sig) {
tun.store.update(fromKey)
}
}
}
func (tun *TunAdapter) sendKeyLookup(partial ed25519.PublicKey) {
sig := ed25519.Sign(tun.core.PrivateKey(), partial[:])
bs := append([]byte{typeKeyLookup}, sig...)
_ = tun.core.SendOutOfBand(partial, bs)
}
func (tun *TunAdapter) sendKeyResponse(dest ed25519.PublicKey) {
sig := ed25519.Sign(tun.core.PrivateKey(), dest[:])
bs := append([]byte{typeKeyResponse}, sig...)
_ = tun.core.SendOutOfBand(dest, bs)
}
func (tun *TunAdapter) maxSessionMTU() uint64 {
const sessionTypeOverhead = 1
return tun.core.MTU() - sessionTypeOverhead
}

View file

@ -1,23 +0,0 @@
package tuntap
// Out-of-band packet types
const (
typeKeyDummy = iota // nolint:deadcode,varcheck
typeKeyLookup
typeKeyResponse
)
// In-band packet types
const (
typeSessionDummy = iota // nolint:deadcode,varcheck
typeSessionTraffic
typeSessionProto
)
// Protocol packet types
const (
typeProtoDummy = iota
typeProtoNodeInfoRequest
typeProtoNodeInfoResponse
typeProtoDebug = 255
)