diff --git a/src/config/config.go b/src/config/config.go index a4cd5c3..b5a1f89 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -19,7 +19,7 @@ type NodeConfig struct { 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."` TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."` - Metadata map[string]interface{} `comment:"Optional node metadata. 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."` + 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."` //Net NetConfig `comment:"Extended options for connecting to peers over other networks."` } diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 5def7e0..f3b5998 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -322,16 +322,16 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{}, err } }) - a.addHandler("getMetadata", []string{"box_pub_key", "coords", "[nocache]"}, func(in admin_info) (admin_info, error) { + a.addHandler("getNodeInfo", []string{"box_pub_key", "coords", "[nocache]"}, func(in admin_info) (admin_info, error) { var nocache bool if in["nocache"] != nil { nocache = in["nocache"].(string) == "true" } - result, err := a.admin_getMetadata(in["box_pub_key"].(string), in["coords"].(string), nocache) + result, err := a.admin_getNodeInfo(in["box_pub_key"].(string), in["coords"].(string), nocache) if err == nil { var m map[string]interface{} if err = json.Unmarshal(result, &m); err == nil { - return admin_info{"metadata": m}, nil + return admin_info{"nodeinfo": m}, nil } else { return admin_info{}, err } @@ -817,15 +817,15 @@ func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtR return dhtRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString)) } -func (a *admin) admin_getMetadata(keyString, coordString string, nocache bool) (metadataPayload, error) { +func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) { var key boxPubKey if keyBytes, err := hex.DecodeString(keyString); err != nil { - return metadataPayload{}, err + return nodeinfoPayload{}, err } else { copy(key[:], keyBytes) } if !nocache { - if response, err := a.core.metadata.getCachedMetadata(key); err == nil { + if response, err := a.core.nodeinfo.getCachedNodeInfo(key); err == nil { return response, nil } } @@ -836,23 +836,23 @@ func (a *admin) admin_getMetadata(keyString, coordString string, nocache bool) ( continue } if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil { - return metadataPayload{}, err + return nodeinfoPayload{}, err } else { coords = append(coords, uint8(u64)) } } - response := make(chan *metadataPayload, 1) - sendMetaRequest := func() { - a.core.metadata.addCallback(key, func(meta *metadataPayload) { + response := make(chan *nodeinfoPayload, 1) + sendNodeInfoRequest := func() { + a.core.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) { defer func() { recover() }() select { - case response <- meta: + case response <- nodeinfo: default: } }) - a.core.metadata.sendMetadata(key, coords, false) + a.core.nodeinfo.sendNodeInfo(key, coords, false) } - a.core.router.doAdmin(sendMetaRequest) + a.core.router.doAdmin(sendNodeInfoRequest) go func() { time.Sleep(6 * time.Second) close(response) @@ -860,7 +860,7 @@ func (a *admin) admin_getMetadata(keyString, coordString string, nocache bool) ( for res := range response { return *res, nil } - return metadataPayload{}, errors.New(fmt.Sprintf("getMetadata timeout: %s", keyString)) + return nodeinfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString)) } // getResponse_dot returns a response for a graphviz dot formatted representation of the known parts of the network. diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 05d528a..66c6964 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -31,7 +31,7 @@ type Core struct { admin admin searches searches multicast multicast - metadata metadata + nodeinfo nodeinfo tcp tcpInterface log *log.Logger ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this @@ -123,8 +123,8 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) c.admin.init(c, nc.AdminListen) - c.metadata.init(c) - c.metadata.setMetadata(nc.Metadata) + c.nodeinfo.init(c) + c.nodeinfo.setNodeInfo(nc.NodeInfo) if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil { c.log.Println("Failed to start TCP interface") @@ -241,14 +241,14 @@ func (c *Core) GetSubnet() *net.IPNet { return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } -// Gets the node metadata. -func (c *Core) GetMetadata() metadataPayload { - return c.metadata.getMetadata() +// Gets the nodeinfo. +func (c *Core) GetNodeInfo() nodeinfoPayload { + return c.nodeinfo.getNodeInfo() } -// Sets the node metadata. -func (c *Core) SetMetadata(meta interface{}) { - c.metadata.setMetadata(meta) +// Sets the nodeinfo. +func (c *Core) SetNodeInfo(nodeinfo interface{}) { + c.nodeinfo.setNodeInfo(nodeinfo) } // Sets the output logger of the Yggdrasil node after startup. This may be diff --git a/src/yggdrasil/metadata.go b/src/yggdrasil/metadata.go deleted file mode 100644 index 67bab4a..0000000 --- a/src/yggdrasil/metadata.go +++ /dev/null @@ -1,167 +0,0 @@ -package yggdrasil - -import ( - "encoding/json" - "errors" - "runtime" - "sync" - "time" -) - -type metadata struct { - core *Core - myMetadata metadataPayload - myMetadataMutex sync.RWMutex - callbacks map[boxPubKey]metadataCallback - callbacksMutex sync.Mutex - cache map[boxPubKey]metadataCached - cacheMutex sync.RWMutex -} - -type metadataPayload []byte - -type metadataCached struct { - payload metadataPayload - created time.Time -} - -type metadataCallback struct { - call func(meta *metadataPayload) - created time.Time -} - -// Initialises the metadata cache/callback maps, and starts a goroutine to keep -// the cache/callback maps clean of stale entries -func (m *metadata) init(core *Core) { - m.core = core - m.callbacks = make(map[boxPubKey]metadataCallback) - m.cache = make(map[boxPubKey]metadataCached) - - go func() { - for { - m.callbacksMutex.Lock() - for boxPubKey, callback := range m.callbacks { - if time.Since(callback.created) > time.Minute { - delete(m.callbacks, boxPubKey) - } - } - m.callbacksMutex.Unlock() - m.cacheMutex.Lock() - for boxPubKey, cache := range m.cache { - if time.Since(cache.created) > time.Hour { - delete(m.cache, boxPubKey) - } - } - m.cacheMutex.Unlock() - time.Sleep(time.Second * 30) - } - }() -} - -// Add a callback for a metadata lookup -func (m *metadata) addCallback(sender boxPubKey, call func(meta *metadataPayload)) { - m.callbacksMutex.Lock() - defer m.callbacksMutex.Unlock() - m.callbacks[sender] = metadataCallback{ - created: time.Now(), - call: call, - } -} - -// Handles the callback, if there is one -func (m *metadata) callback(sender boxPubKey, meta metadataPayload) { - m.callbacksMutex.Lock() - defer m.callbacksMutex.Unlock() - if callback, ok := m.callbacks[sender]; ok { - callback.call(&meta) - delete(m.callbacks, sender) - } -} - -// Get the current node's metadata -func (m *metadata) getMetadata() metadataPayload { - m.myMetadataMutex.RLock() - defer m.myMetadataMutex.RUnlock() - return m.myMetadata -} - -// Set the current node's metadata -func (m *metadata) setMetadata(given interface{}) error { - m.myMetadataMutex.Lock() - defer m.myMetadataMutex.Unlock() - newmeta := map[string]interface{}{ - "buildname": GetBuildName(), - "buildversion": GetBuildVersion(), - "buildplatform": runtime.GOOS, - "buildarch": runtime.GOARCH, - } - if metamap, ok := given.(map[string]interface{}); ok { - for key, value := range metamap { - if _, ok := newmeta[key]; ok { - continue - } - newmeta[key] = value - } - } - if newjson, err := json.Marshal(newmeta); err == nil { - if len(newjson) > 16384 { - return errors.New("Metadata exceeds max length of 16384 bytes") - } - m.myMetadata = newjson - return nil - } else { - return err - } -} - -// Add metadata into the cache for a node -func (m *metadata) addCachedMetadata(key boxPubKey, payload metadataPayload) { - m.cacheMutex.Lock() - defer m.cacheMutex.Unlock() - m.cache[key] = metadataCached{ - created: time.Now(), - payload: payload, - } -} - -// Get a metadata entry from the cache -func (m *metadata) getCachedMetadata(key boxPubKey) (metadataPayload, error) { - m.cacheMutex.RLock() - defer m.cacheMutex.RUnlock() - if meta, ok := m.cache[key]; ok { - return meta.payload, nil - } - return metadataPayload{}, errors.New("No cache entry found") -} - -// Handles a meta request/response - called from the router -func (m *metadata) handleMetadata(meta *sessionMeta) { - if meta.IsResponse { - m.callback(meta.SendPermPub, meta.Metadata) - m.addCachedMetadata(meta.SendPermPub, meta.Metadata) - } else { - m.sendMetadata(meta.SendPermPub, meta.SendCoords, true) - } -} - -// Send metadata request or response - called from the router -func (m *metadata) sendMetadata(key boxPubKey, coords []byte, isResponse bool) { - table := m.core.switchTable.table.Load().(lookupTable) - meta := sessionMeta{ - SendCoords: table.self.getCoords(), - IsResponse: isResponse, - Metadata: m.core.metadata.getMetadata(), - } - bs := meta.encode() - shared := m.core.sessions.getSharedKey(&m.core.boxPriv, &key) - payload, nonce := boxSeal(shared, bs, nil) - p := wire_protoTrafficPacket{ - Coords: coords, - ToKey: key, - FromKey: m.core.boxPub, - Nonce: *nonce, - Payload: payload, - } - packet := p.encode() - m.core.router.out(packet) -} diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go new file mode 100644 index 0000000..2146b27 --- /dev/null +++ b/src/yggdrasil/nodeinfo.go @@ -0,0 +1,175 @@ +package yggdrasil + +import ( + "encoding/json" + "errors" + "runtime" + "sync" + "time" +) + +type nodeinfo struct { + core *Core + myNodeInfo nodeinfoPayload + myNodeInfoMutex sync.RWMutex + callbacks map[boxPubKey]nodeinfoCallback + callbacksMutex sync.Mutex + cache map[boxPubKey]nodeinfoCached + cacheMutex sync.RWMutex +} + +type nodeinfoPayload []byte + +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 { + SendPermPub boxPubKey // Sender's permanent key + SendCoords []byte // Sender's coords + 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(core *Core) { + m.core = core + m.callbacks = make(map[boxPubKey]nodeinfoCallback) + m.cache = make(map[boxPubKey]nodeinfoCached) + + go func() { + for { + m.callbacksMutex.Lock() + for boxPubKey, callback := range m.callbacks { + if time.Since(callback.created) > time.Minute { + delete(m.callbacks, boxPubKey) + } + } + m.callbacksMutex.Unlock() + m.cacheMutex.Lock() + for boxPubKey, cache := range m.cache { + if time.Since(cache.created) > time.Hour { + delete(m.cache, boxPubKey) + } + } + m.cacheMutex.Unlock() + time.Sleep(time.Second * 30) + } + }() +} + +// Add a callback for a nodeinfo lookup +func (m *nodeinfo) addCallback(sender boxPubKey, call func(nodeinfo *nodeinfoPayload)) { + m.callbacksMutex.Lock() + defer m.callbacksMutex.Unlock() + m.callbacks[sender] = nodeinfoCallback{ + created: time.Now(), + call: call, + } +} + +// Handles the callback, if there is one +func (m *nodeinfo) callback(sender boxPubKey, nodeinfo nodeinfoPayload) { + m.callbacksMutex.Lock() + defer m.callbacksMutex.Unlock() + if callback, ok := m.callbacks[sender]; ok { + callback.call(&nodeinfo) + delete(m.callbacks, sender) + } +} + +// Get the current node's nodeinfo +func (m *nodeinfo) getNodeInfo() nodeinfoPayload { + m.myNodeInfoMutex.RLock() + defer m.myNodeInfoMutex.RUnlock() + return m.myNodeInfo +} + +// Set the current node's nodeinfo +func (m *nodeinfo) setNodeInfo(given interface{}) error { + m.myNodeInfoMutex.Lock() + defer m.myNodeInfoMutex.Unlock() + newnodeinfo := map[string]interface{}{ + "buildname": GetBuildName(), + "buildversion": GetBuildVersion(), + "buildplatform": runtime.GOOS, + "buildarch": runtime.GOARCH, + } + if nodeinfomap, ok := given.(map[string]interface{}); ok { + for key, value := range nodeinfomap { + if _, ok := newnodeinfo[key]; ok { + continue + } + newnodeinfo[key] = value + } + } + if newjson, err := json.Marshal(newnodeinfo); err == nil { + if len(newjson) > 16384 { + return errors.New("NodeInfo exceeds max length of 16384 bytes") + } + m.myNodeInfo = newjson + return nil + } else { + return err + } +} + +// Add nodeinfo into the cache for a node +func (m *nodeinfo) addCachedNodeInfo(key boxPubKey, payload nodeinfoPayload) { + m.cacheMutex.Lock() + defer m.cacheMutex.Unlock() + m.cache[key] = nodeinfoCached{ + created: time.Now(), + payload: payload, + } +} + +// Get a nodeinfo entry from the cache +func (m *nodeinfo) getCachedNodeInfo(key boxPubKey) (nodeinfoPayload, error) { + m.cacheMutex.RLock() + defer m.cacheMutex.RUnlock() + if nodeinfo, ok := m.cache[key]; ok { + return nodeinfo.payload, nil + } + return nodeinfoPayload{}, errors.New("No cache entry found") +} + +// Handles a nodeinfo request/response - called from the router +func (m *nodeinfo) handleNodeInfo(nodeinfo *nodeinfoReqRes) { + if nodeinfo.IsResponse { + m.callback(nodeinfo.SendPermPub, nodeinfo.NodeInfo) + m.addCachedNodeInfo(nodeinfo.SendPermPub, nodeinfo.NodeInfo) + } else { + m.sendNodeInfo(nodeinfo.SendPermPub, nodeinfo.SendCoords, true) + } +} + +// Send nodeinfo request or response - called from the router +func (m *nodeinfo) sendNodeInfo(key boxPubKey, coords []byte, isResponse bool) { + table := m.core.switchTable.table.Load().(lookupTable) + nodeinfo := nodeinfoReqRes{ + SendCoords: table.self.getCoords(), + IsResponse: isResponse, + NodeInfo: m.core.nodeinfo.getNodeInfo(), + } + bs := nodeinfo.encode() + shared := m.core.sessions.getSharedKey(&m.core.boxPriv, &key) + payload, nonce := boxSeal(shared, bs, nil) + p := wire_protoTrafficPacket{ + Coords: coords, + ToKey: key, + FromKey: m.core.boxPub, + Nonce: *nonce, + Payload: payload, + } + packet := p.encode() + m.core.router.out(packet) +} diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 14e089f..1067067 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -409,10 +409,10 @@ func (r *router) handleProto(packet []byte) { r.handlePing(bs, &p.FromKey) case wire_SessionPong: r.handlePong(bs, &p.FromKey) - case wire_SessionMetaRequest: + case wire_NodeInfoRequest: fallthrough - case wire_SessionMetaResponse: - r.handleMetadata(bs, &p.FromKey) + case wire_NodeInfoResponse: + r.handleNodeInfo(bs, &p.FromKey) case wire_DHTLookupRequest: r.handleDHTReq(bs, &p.FromKey) case wire_DHTLookupResponse: @@ -457,14 +457,14 @@ func (r *router) handleDHTRes(bs []byte, fromKey *boxPubKey) { r.core.dht.handleRes(&res) } -// Decodes meta request -func (r *router) handleMetadata(bs []byte, fromKey *boxPubKey) { - req := sessionMeta{} +// Decodes nodeinfo request +func (r *router) handleNodeInfo(bs []byte, fromKey *boxPubKey) { + req := nodeinfoReqRes{} if !req.decode(bs) { return } req.SendPermPub = *fromKey - r.core.metadata.handleMetadata(&req) + r.core.nodeinfo.handleNodeInfo(&req) } // Passed a function to call. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index dbd273f..4f2bedf 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -54,14 +54,6 @@ type sessionPing struct { MTU uint16 } -// Represents a session metadata packet. -type sessionMeta struct { - SendPermPub boxPubKey // Sender's permanent key - SendCoords []byte // Sender's coords - IsResponse bool - Metadata metadataPayload -} - // Updates session info in response to a ping, after checking that the ping is OK. // Returns true if the session was updated, or false otherwise. func (s *sessionInfo) update(p *sessionPing) bool { diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index d46994d..5e87784 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -16,8 +16,8 @@ const ( wire_SessionPong // inside protocol traffic header wire_DHTLookupRequest // inside protocol traffic header wire_DHTLookupResponse // inside protocol traffic header - wire_SessionMetaRequest // inside protocol traffic header - wire_SessionMetaResponse // inside protocol traffic header + wire_NodeInfoRequest // inside protocol traffic header + wire_NodeInfoResponse // inside protocol traffic header ) // Calls wire_put_uint64 on a nil slice. @@ -355,39 +355,39 @@ func (p *sessionPing) decode(bs []byte) bool { //////////////////////////////////////////////////////////////////////////////// -// Encodes a sessionMeta into its wire format. -func (p *sessionMeta) encode() []byte { +// Encodes a nodeinfoReqRes into its wire format. +func (p *nodeinfoReqRes) encode() []byte { var pTypeVal uint64 if p.IsResponse { - pTypeVal = wire_SessionMetaResponse + pTypeVal = wire_NodeInfoResponse } else { - pTypeVal = wire_SessionMetaRequest + pTypeVal = wire_NodeInfoRequest } bs := wire_encode_uint64(pTypeVal) bs = wire_put_coords(p.SendCoords, bs) - if pTypeVal == wire_SessionMetaResponse { - bs = append(bs, p.Metadata...) + if pTypeVal == wire_NodeInfoResponse { + bs = append(bs, p.NodeInfo...) } return bs } -// Decodes an encoded sessionMeta into the struct, returning true if successful. -func (p *sessionMeta) decode(bs []byte) bool { +// Decodes an encoded nodeinfoReqRes into the struct, returning true if successful. +func (p *nodeinfoReqRes) decode(bs []byte) bool { var pType uint64 switch { case !wire_chop_uint64(&pType, &bs): return false - case pType != wire_SessionMetaRequest && pType != wire_SessionMetaResponse: + case pType != wire_NodeInfoRequest && pType != wire_NodeInfoResponse: return false case !wire_chop_coords(&p.SendCoords, &bs): return false } - if p.IsResponse = pType == wire_SessionMetaResponse; p.IsResponse { + if p.IsResponse = pType == wire_NodeInfoResponse; p.IsResponse { if len(bs) == 0 { return false } - p.Metadata = make(metadataPayload, len(bs)) - if !wire_chop_slice(p.Metadata[:], &bs) { + p.NodeInfo = make(nodeinfoPayload, len(bs)) + if !wire_chop_slice(p.NodeInfo[:], &bs) { return false } }