From 088d28a93b5437b259a981a5dde81accf2d8a21c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Oct 2018 18:04:02 +0100 Subject: [PATCH 01/18] Fix debug builds with friendly names --- src/yggdrasil/debug.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 892529b..74d4ae0 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -50,7 +50,7 @@ func StartProfiler(log *log.Logger) error { func (c *Core) Init() { bpub, bpriv := newBoxKeys() spub, spriv := newSigKeys() - c.init(bpub, bpriv, spub, spriv) + c.init(bpub, bpriv, spub, spriv, "(simulator)") c.switchTable.start() c.router.start() } @@ -84,7 +84,7 @@ func (c *Core) DEBUG_getPeers() *peers { func (ps *peers) DEBUG_newPeer(box boxPubKey, sig sigPubKey, link boxSharedKey) *peer { //in <-chan []byte, //out chan<- []byte) *peer { - return ps.newPeer(&box, &sig, &link) //, in, out) + return ps.newPeer(&box, &sig, &link, "(simulator)", "(simulator)") //, in, out) } /* @@ -358,7 +358,7 @@ func (c *Core) DEBUG_init(bpub []byte, copy(boxPriv[:], bpriv) copy(sigPub[:], spub) copy(sigPriv[:], spriv) - c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) + c.init(&boxPub, &boxPriv, &sigPub, &sigPriv, "(simulator)") if err := c.router.start(); err != nil { panic(err) From 4f435705e31dfd2c32e993b10d46dcfd4be88477 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Oct 2018 18:06:54 +0100 Subject: [PATCH 02/18] Fix getSelf in yggdrasilctl --- yggdrasilctl.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/yggdrasilctl.go b/yggdrasilctl.go index 2b5b79a..fd2d9b8 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -181,6 +181,13 @@ func main() { } case "getself": for k, v := range res["self"].(map[string]interface{}) { + if friendlyname, ok := v.(map[string]interface{})["friendly_name"].(string); ok { + if friendlyname == "" { + fmt.Println("Friendly name: (none)") + } else { + fmt.Println("Friendly name:", friendlyname) + } + } fmt.Println("IPv6 address:", k) if subnet, ok := v.(map[string]interface{})["subnet"].(string); ok { fmt.Println("IPv6 subnet:", subnet) From a1b72c16d8448e66f470df67ae916b47cb98394b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Oct 2018 22:58:27 +0100 Subject: [PATCH 03/18] Some attempt at exchanging session metadata over the wire (broken) --- src/yggdrasil/admin.go | 6 +- src/yggdrasil/config/config.go | 8 ++- src/yggdrasil/core.go | 55 +++++++------- src/yggdrasil/metadata.go | 7 ++ src/yggdrasil/peer.go | 46 ++++++------ src/yggdrasil/router.go | 17 ++++- src/yggdrasil/session.go | 126 +++++++++++++++++++++++++-------- src/yggdrasil/tcp.go | 2 +- src/yggdrasil/wire.go | 44 ++++++++++++ 9 files changed, 228 insertions(+), 83 deletions(-) create mode 100644 src/yggdrasil/metadata.go diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 630db17..aa73d3b 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -470,7 +470,9 @@ func (a *admin) getData_getSelf() *admin_nodeInfo { {"ip", a.core.GetAddress().String()}, {"subnet", a.core.GetSubnet().String()}, {"coords", fmt.Sprint(coords)}, - {"friendly_name", a.core.friendlyName}, + {"name", a.core.metadata.name}, + {"location", a.core.metadata.location}, + {"contact", a.core.metadata.contact}, } return &self } @@ -494,7 +496,6 @@ func (a *admin) getData_getPeers() []admin_nodeInfo { {"bytes_sent", atomic.LoadUint64(&p.bytesSent)}, {"bytes_recvd", atomic.LoadUint64(&p.bytesRecvd)}, {"endpoint", p.endpoint}, - {"friendly_name", p.friendlyName}, } peerInfos = append(peerInfos, info) } @@ -520,7 +521,6 @@ func (a *admin) getData_getSwitchPeers() []admin_nodeInfo { {"bytes_sent", atomic.LoadUint64(&peer.bytesSent)}, {"bytes_recvd", atomic.LoadUint64(&peer.bytesRecvd)}, {"endpoint", peer.endpoint}, - {"friendly_name", peer.friendlyName}, } peerInfos = append(peerInfos, info) } diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 530c18c..04c95c2 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -2,7 +2,7 @@ package config // NodeConfig defines all configuration values needed to run a signle yggdrasil node type NodeConfig struct { - FriendlyName string `comment:"Friendly name for this node. It is visible to direct peers."` + Metadata Metadata `comment:"Optional node metadata. Entirely optional but visible to all\npeers and nodes with open sessions."` Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` AdminListen string `comment:"Listen address for admin connections Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X."` Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` @@ -35,3 +35,9 @@ type SessionFirewall struct { WhitelistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always accepted,\nregardless of AllowFromDirect or AllowFromRemote."` BlacklistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always rejected,\nregardless of the whitelist, AllowFromDirect or AllowFromRemote."` } + +type Metadata struct { + Name string + Location string + Contact string +} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 8f61bf6..9be08ab 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -16,31 +16,31 @@ import ( // object for each Yggdrasil node you plan to run. type Core struct { // This is the main data structure that holds everything else for a node - boxPub boxPubKey - boxPriv boxPrivKey - sigPub sigPubKey - sigPriv sigPrivKey - friendlyName string - switchTable switchTable - peers peers - sigs sigManager - sessions sessions - router router - dht dht - tun tunDevice - admin admin - searches searches - multicast multicast - tcp tcpInterface - log *log.Logger - ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this + boxPub boxPubKey + boxPriv boxPrivKey + sigPub sigPubKey + sigPriv sigPrivKey + metadata metadata + switchTable switchTable + peers peers + sigs sigManager + sessions sessions + router router + dht dht + tun tunDevice + admin admin + searches searches + multicast multicast + tcp tcpInterface + log *log.Logger + ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this } func (c *Core) init(bpub *boxPubKey, bpriv *boxPrivKey, spub *sigPubKey, spriv *sigPrivKey, - friendlyname string) { + metadata metadata) { // TODO separate init and start functions // Init sets up structs // Start launches goroutines that depend on structs being set up @@ -51,7 +51,7 @@ func (c *Core) init(bpub *boxPubKey, } c.boxPub, c.boxPriv = *bpub, *bpriv c.sigPub, c.sigPriv = *spub, *spriv - c.friendlyName = friendlyname + c.metadata = metadata c.admin.core = c c.sigs.init() c.searches.init(c) @@ -65,11 +65,8 @@ func (c *Core) init(bpub *boxPubKey, } // Gets the friendly name of this node, as specified in the NodeConfig. -func (c *Core) GetFriendlyName() string { - if c.friendlyName == "" { - return "(none)" - } - return c.friendlyName +func (c *Core) GetMeta() metadata { + return c.metadata } // Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging @@ -105,7 +102,13 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { copy(sigPub[:], sigPubHex) copy(sigPriv[:], sigPrivHex) - c.init(&boxPub, &boxPriv, &sigPub, &sigPriv, nc.FriendlyName) + meta := metadata{ + name: nc.Metadata.Name, + location: nc.Metadata.Location, + contact: nc.Metadata.Contact, + } + + c.init(&boxPub, &boxPriv, &sigPub, &sigPriv, meta) c.admin.init(c, nc.AdminListen) if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil { diff --git a/src/yggdrasil/metadata.go b/src/yggdrasil/metadata.go new file mode 100644 index 0000000..c524308 --- /dev/null +++ b/src/yggdrasil/metadata.go @@ -0,0 +1,7 @@ +package yggdrasil + +type metadata struct { + name string + location string + contact string +} diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 4f79237..da807c9 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -79,34 +79,34 @@ type peer struct { bytesSent uint64 // To track bandwidth usage for getPeers bytesRecvd uint64 // To track bandwidth usage for getPeers // BUG: sync/atomic, 32 bit platforms need the above to be the first element - core *Core - port switchPort - box boxPubKey - sig sigPubKey - shared boxSharedKey - linkShared boxSharedKey - endpoint string - friendlyName string - firstSeen time.Time // To track uptime for getPeers - linkOut (chan []byte) // used for protocol traffic (to bypass queues) - doSend (chan struct{}) // tell the linkLoop to send a switchMsg - dinfo *dhtInfo // used to keep the DHT working - out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes - close func() // Called when a peer is removed, to close the underlying connection, or via admin api + core *Core + port switchPort + box boxPubKey + sig sigPubKey + shared boxSharedKey + linkShared boxSharedKey + endpoint string + metadata metadata + firstSeen time.Time // To track uptime for getPeers + linkOut (chan []byte) // used for protocol traffic (to bypass queues) + doSend (chan struct{}) // tell the linkLoop to send a switchMsg + dinfo *dhtInfo // used to keep the DHT working + out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes + close func() // Called when a peer is removed, to close the underlying connection, or via admin api } // Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unocupied port number. -func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey, endpoint string, friendlyname string) *peer { +func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey, endpoint string, metadata metadata) *peer { now := time.Now() p := peer{box: *box, - sig: *sig, - shared: *getSharedKey(&ps.core.boxPriv, box), - linkShared: *linkShared, - endpoint: endpoint, - friendlyName: friendlyname, - firstSeen: now, - doSend: make(chan struct{}, 1), - core: ps.core} + sig: *sig, + shared: *getSharedKey(&ps.core.boxPriv, box), + linkShared: *linkShared, + endpoint: endpoint, + metadata: metadata, + firstSeen: now, + doSend: make(chan struct{}, 1), + core: ps.core} ps.mutex.Lock() defer ps.mutex.Unlock() oldPorts := ps.getPorts() diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index dcc6a5c..bcec258 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -47,7 +47,7 @@ func (r *router) init(core *Core) { r.core = core r.addr = *address_addrForNodeID(&r.core.dht.nodeID) in := make(chan []byte, 32) // TODO something better than this... - p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)", r.core.GetFriendlyName()) + p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)", r.core.metadata) p.out = func(packet []byte) { // This is to make very sure it never blocks select { @@ -324,6 +324,10 @@ func (r *router) handleProto(packet []byte) { r.handlePing(bs, &p.FromKey) case wire_SessionPong: r.handlePong(bs, &p.FromKey) + case wire_SessionMetaRequest: + fallthrough + case wire_SessionMetaResponse: + r.handleMeta(bs, &p.FromKey) case wire_DHTLookupRequest: r.handleDHTReq(bs, &p.FromKey) case wire_DHTLookupResponse: @@ -368,6 +372,17 @@ func (r *router) handleDHTRes(bs []byte, fromKey *boxPubKey) { r.core.dht.handleRes(&res) } +// Decodes meta request +func (r *router) handleMeta(bs []byte, fromKey *boxPubKey) { + req := sessionMeta{} + if !req.decode(bs) { + return + } + req.SendPermPub = *fromKey + r.core.log.Printf("handleMeta: %+v\n", req) + r.core.sessions.handleMeta(&req) +} + // Passed a function to call. // This will send the function to r.admin and block until it finishes. // It's used by the admin socket to ask the router mainLoop goroutine about information in the session or dht structs, which cannot be read safely from outside that goroutine. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 0bc27a1..39ea4cb 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -13,34 +13,37 @@ import ( // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { - core *Core - theirAddr address - theirSubnet subnet - theirPermPub boxPubKey - theirSesPub boxPubKey - mySesPub boxPubKey - mySesPriv boxPrivKey - sharedSesKey boxSharedKey // derived from session keys - theirHandle handle - myHandle handle - theirNonce boxNonce - myNonce boxNonce - theirMTU uint16 - myMTU uint16 - wasMTUFixed bool // Was the MTU fixed by a receive error? - time time.Time // Time we last received a packet - coords []byte // coords of destination - packet []byte // a buffered packet, sent immediately on ping/pong - init bool // Reset if coords change - send chan []byte - recv chan *wire_trafficPacket - nonceMask uint64 - tstamp int64 // tstamp from their last session ping, replay attack mitigation - mtuTime time.Time // time myMTU was last changed - pingTime time.Time // time the first ping was sent since the last received packet - pingSend time.Time // time the last ping was sent - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session + core *Core + theirAddr address + theirSubnet subnet + theirPermPub boxPubKey + theirSesPub boxPubKey + mySesPub boxPubKey + mySesPriv boxPrivKey + sharedSesKey boxSharedKey // derived from session keys + theirHandle handle + myHandle handle + theirNonce boxNonce + myNonce boxNonce + metaReqTime time.Time + metaResTime time.Time + theirMetadata metadata + theirMTU uint16 + myMTU uint16 + wasMTUFixed bool // Was the MTU fixed by a receive error? + time time.Time // Time we last received a packet + coords []byte // coords of destination + packet []byte // a buffered packet, sent immediately on ping/pong + init bool // Reset if coords change + send chan []byte + recv chan *wire_trafficPacket + nonceMask uint64 + tstamp int64 // tstamp from their last session ping, replay attack mitigation + mtuTime time.Time // time myMTU was last changed + pingTime time.Time // time the first ping was sent since the last received packet + pingSend time.Time // time the last ping was sent + bytesSent uint64 // Bytes of real traffic sent in this session + bytesRecvd uint64 // Bytes of real traffic received in this session } // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -54,6 +57,13 @@ type sessionPing struct { MTU uint16 } +// Represents a session metadata packet. +type sessionMeta struct { + SendPermPub boxPubKey // Sender's permanent key + IsResponse bool + Metadata metadata +} + // 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 { @@ -431,6 +441,66 @@ func (ss *sessions) handlePing(ping *sessionPing) { bs, sinfo.packet = sinfo.packet, nil ss.core.router.sendPacket(bs) } + if time.Since(sinfo.metaResTime).Minutes() > 15 { + if time.Since(sinfo.metaReqTime).Minutes() > 1 { + ss.sendMeta(sinfo, false) + } + } +} + +func (ss *sessions) sendMeta(sinfo *sessionInfo, isResponse bool) { + meta := sessionMeta{ + IsResponse: isResponse, + Metadata: metadata{ + name: "some.name.com", //[]byte(ss.core.friendlyName)[0:len(ss.core.friendlyName):32], + location: "Some Place", + contact: "someone@somewhere.com", + }, + } + bs := meta.encode() + shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) + payload, nonce := boxSeal(shared, bs, nil) + p := wire_protoTrafficPacket{ + Coords: sinfo.coords, + ToKey: sinfo.theirPermPub, + FromKey: ss.core.boxPub, + Nonce: *nonce, + Payload: payload, + } + packet := p.encode() + ss.core.router.out(packet) + if isResponse { + ss.core.log.Println("Sent meta response to", sinfo.theirAddr) + } else { + ss.core.log.Println("Sent meta request to", sinfo.theirAddr) + sinfo.metaReqTime = time.Now() + } +} + +// Handles a meta request/response. +func (ss *sessions) handleMeta(meta *sessionMeta) { + // Get the corresponding session (or create a new session) + sinfo, isIn := ss.getByTheirPerm(&meta.SendPermPub) + // Check the session firewall + if !isIn && ss.sessionFirewallEnabled { + if !ss.isSessionAllowed(&meta.SendPermPub, false) { + return + } + } + if !isIn || sinfo.timedout() { + return + } + if meta.IsResponse { + ss.core.log.Println("Received meta response", string(meta.Metadata.name), "from", sinfo.theirAddr) + sinfo.theirMetadata = meta.Metadata + sinfo.metaResTime = time.Now() + ss.core.log.Println("- name:", meta.Metadata.name) + ss.core.log.Println("- contact:", meta.Metadata.contact) + ss.core.log.Println("- location:", meta.Metadata.location) + } else { + ss.core.log.Println("Received meta request", string(meta.Metadata.name), "from", sinfo.theirAddr) + ss.sendMeta(sinfo, true) + } } // Used to subtract one nonce from another, staying in the range +- 64. diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 58d9422..dc1e2b1 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -287,7 +287,7 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { }() // Note that multiple connections to the same node are allowed // E.g. over different interfaces - p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String(), "(none)") + p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String(), metadata{}) p.linkOut = make(chan []byte, 1) in := func(bs []byte) { p.handlePacket(bs) diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index d05624e..fd898a6 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -16,6 +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 ) // Calls wire_put_uint64 on a nil slice. @@ -353,6 +355,48 @@ func (p *sessionPing) decode(bs []byte) bool { //////////////////////////////////////////////////////////////////////////////// +// Encodes a sessionPing into its wire format. +func (p *sessionMeta) encode() []byte { + var pTypeVal uint64 + if p.IsResponse { + pTypeVal = wire_SessionMetaResponse + } else { + pTypeVal = wire_SessionMetaRequest + } + bs := wire_encode_uint64(pTypeVal) + if p.IsResponse { + bs = append(bs, p.Metadata.name...) + bs = append(bs, p.Metadata.location...) + bs = append(bs, p.Metadata.contact...) + } + return bs +} + +// Decodes an encoded sessionPing into the struct, returning true if successful. +func (p *sessionMeta) decode(bs []byte) bool { + var pType uint64 + switch { + case !wire_chop_uint64(&pType, &bs): + return false + case pType != wire_SessionMetaRequest && pType != wire_SessionMetaResponse: + return false + } + p.IsResponse = pType == wire_SessionMetaResponse + if p.IsResponse { + switch { + case !wire_chop_slice([]byte(p.Metadata.name), &bs): + return false + case !wire_chop_slice([]byte(p.Metadata.location), &bs): + return false + case !wire_chop_slice([]byte(p.Metadata.contact), &bs): + return false + } + } + return true +} + +//////////////////////////////////////////////////////////////////////////////// + // Encodes a dhtReq into its wire format. func (r *dhtReq) encode() []byte { coords := wire_encode_coords(r.Coords) From 97464feba9394d7403abdceb63e024ac8b26e675 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 12 Dec 2018 19:51:28 +0000 Subject: [PATCH 04/18] Working metadata exchange --- src/config/config.go | 9 +-------- src/yggdrasil/admin.go | 3 --- src/yggdrasil/core.go | 16 ++++------------ src/yggdrasil/metadata.go | 7 ------- src/yggdrasil/router.go | 3 +-- src/yggdrasil/session.go | 25 +++++++++++-------------- src/yggdrasil/wire.go | 17 +++++------------ 7 files changed, 22 insertions(+), 58 deletions(-) delete mode 100644 src/yggdrasil/metadata.go diff --git a/src/config/config.go b/src/config/config.go index 18950a4..66de668 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -2,7 +2,6 @@ package config // NodeConfig defines all configuration values needed to run a signle yggdrasil node type NodeConfig struct { - Metadata Metadata `comment:"Optional node metadata. Entirely optional but visible to all\npeers and nodes with open sessions."` Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."` Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` @@ -20,6 +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 interface{} `comment:"Optional node metadata. Entirely optional but visible to all\npeers and nodes with open sessions."` //Net NetConfig `comment:"Extended options for connecting to peers over other networks."` } @@ -52,10 +52,3 @@ type TunnelRouting struct { type SwitchOptions struct { MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."` } - -// Optional metadata - format subject to change -type Metadata struct { - Name string - Location string - Contact string -} diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 332fa1c..266d5a8 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -574,9 +574,6 @@ func (a *admin) getData_getSelf() *admin_nodeInfo { {"ip", a.core.GetAddress().String()}, {"subnet", a.core.GetSubnet().String()}, {"coords", fmt.Sprint(coords)}, - {"name", a.core.metadata.name}, - {"location", a.core.metadata.location}, - {"contact", a.core.metadata.contact}, } if name := GetBuildName(); name != "unknown" { self = append(self, admin_pair{"build_name", name}) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 06a3cb3..5a9267c 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -23,7 +23,6 @@ type Core struct { boxPriv boxPrivKey sigPub sigPubKey sigPriv sigPrivKey - metadata metadata switchTable switchTable peers peers sessions sessions @@ -41,8 +40,7 @@ type Core struct { func (c *Core) init(bpub *boxPubKey, bpriv *boxPrivKey, spub *sigPubKey, - spriv *sigPrivKey, - metadata metadata) { + spriv *sigPrivKey) { // TODO separate init and start functions // Init sets up structs // Start launches goroutines that depend on structs being set up @@ -53,7 +51,6 @@ func (c *Core) init(bpub *boxPubKey, } c.boxPub, c.boxPriv = *bpub, *bpriv c.sigPub, c.sigPriv = *spub, *spriv - c.metadata = metadata c.admin.core = c c.searches.init(c) c.dht.init(c) @@ -85,7 +82,7 @@ func GetBuildVersion() string { // Gets the friendly name of this node, as specified in the NodeConfig. func (c *Core) GetMeta() metadata { - return c.metadata + return c.sessions.myMetadata } // Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging @@ -129,13 +126,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { copy(sigPub[:], sigPubHex) copy(sigPriv[:], sigPrivHex) - meta := metadata{ - name: nc.Metadata.Name, - location: nc.Metadata.Location, - contact: nc.Metadata.Contact, - } - - c.init(&boxPub, &boxPriv, &sigPub, &sigPriv, meta) + c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) c.admin.init(c, nc.AdminListen) if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil { @@ -152,6 +143,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } + c.sessions.setMetadata(metadata("HIYA, THIS IS METADATA")) c.sessions.setSessionFirewallState(nc.SessionFirewall.Enable) c.sessions.setSessionFirewallDefaults( nc.SessionFirewall.AllowFromDirect, diff --git a/src/yggdrasil/metadata.go b/src/yggdrasil/metadata.go deleted file mode 100644 index c524308..0000000 --- a/src/yggdrasil/metadata.go +++ /dev/null @@ -1,7 +0,0 @@ -package yggdrasil - -type metadata struct { - name string - location string - contact string -} diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 2df359d..a40563b 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -58,7 +58,7 @@ func (r *router) init(core *Core) { r.addr = *address_addrForNodeID(&r.core.dht.nodeID) r.subnet = *address_subnetForNodeID(&r.core.dht.nodeID) in := make(chan []byte, 32) // TODO something better than this... - p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)", r.core.metadata) + p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)", r.core.sessions.myMetadata) p.out = func(packet []byte) { // This is to make very sure it never blocks select { @@ -483,7 +483,6 @@ func (r *router) handleMeta(bs []byte, fromKey *boxPubKey) { return } req.SendPermPub = *fromKey - r.core.log.Printf("handleMeta: %+v\n", req) r.core.sessions.handleMeta(&req) } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index e088726..690f603 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -64,6 +64,8 @@ type sessionMeta struct { Metadata metadata } +type metadata []byte + // 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 { @@ -125,6 +127,8 @@ type sessions struct { sessionFirewallAlwaysAllowsOutbound bool sessionFirewallWhitelist []string sessionFirewallBlacklist []string + // Metadata for this node + myMetadata metadata } // Initializes the session struct. @@ -139,6 +143,11 @@ func (ss *sessions) init(core *Core) { ss.lastCleanup = time.Now() } +// Enable or disable the session firewall +func (ss *sessions) setMetadata(meta metadata) { + ss.myMetadata = meta +} + // Enable or disable the session firewall func (ss *sessions) setSessionFirewallState(enabled bool) { ss.sessionFirewallEnabled = enabled @@ -486,11 +495,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { func (ss *sessions) sendMeta(sinfo *sessionInfo, isResponse bool) { meta := sessionMeta{ IsResponse: isResponse, - Metadata: metadata{ - name: "some.name.com", //[]byte(ss.core.friendlyName)[0:len(ss.core.friendlyName):32], - location: "Some Place", - contact: "someone@somewhere.com", - }, + Metadata: ss.myMetadata, } bs := meta.encode() shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) @@ -504,10 +509,7 @@ func (ss *sessions) sendMeta(sinfo *sessionInfo, isResponse bool) { } packet := p.encode() ss.core.router.out(packet) - if isResponse { - ss.core.log.Println("Sent meta response to", sinfo.theirAddr) - } else { - ss.core.log.Println("Sent meta request to", sinfo.theirAddr) + if !isResponse { sinfo.metaReqTime = time.Now() } } @@ -526,14 +528,9 @@ func (ss *sessions) handleMeta(meta *sessionMeta) { return } if meta.IsResponse { - ss.core.log.Println("Received meta response", string(meta.Metadata.name), "from", sinfo.theirAddr) sinfo.theirMetadata = meta.Metadata sinfo.metaResTime = time.Now() - ss.core.log.Println("- name:", meta.Metadata.name) - ss.core.log.Println("- contact:", meta.Metadata.contact) - ss.core.log.Println("- location:", meta.Metadata.location) } else { - ss.core.log.Println("Received meta request", string(meta.Metadata.name), "from", sinfo.theirAddr) ss.sendMeta(sinfo, true) } } diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index fd898a6..7f394d7 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -364,10 +364,8 @@ func (p *sessionMeta) encode() []byte { pTypeVal = wire_SessionMetaRequest } bs := wire_encode_uint64(pTypeVal) - if p.IsResponse { - bs = append(bs, p.Metadata.name...) - bs = append(bs, p.Metadata.location...) - bs = append(bs, p.Metadata.contact...) + if pTypeVal == wire_SessionMetaResponse { + bs = append(bs, p.Metadata...) } return bs } @@ -381,14 +379,9 @@ func (p *sessionMeta) decode(bs []byte) bool { case pType != wire_SessionMetaRequest && pType != wire_SessionMetaResponse: return false } - p.IsResponse = pType == wire_SessionMetaResponse - if p.IsResponse { - switch { - case !wire_chop_slice([]byte(p.Metadata.name), &bs): - return false - case !wire_chop_slice([]byte(p.Metadata.location), &bs): - return false - case !wire_chop_slice([]byte(p.Metadata.contact), &bs): + if p.IsResponse = pType == wire_SessionMetaResponse; p.IsResponse { + p.Metadata = make(metadata, len(bs)) + if !wire_chop_slice(p.Metadata[:], &bs) { return false } } From 042a3400fe08050ae0e30b8e453ae2f59bb5c50d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 12 Dec 2018 22:40:49 +0000 Subject: [PATCH 05/18] Wrap the metadata with a mutex to guarantee thread safety across core/router/sessions --- src/yggdrasil/core.go | 15 ++++++++++----- src/yggdrasil/router.go | 2 ++ src/yggdrasil/session.go | 30 +++++++++++++++++++++++------- src/yggdrasil/wire.go | 7 +++++-- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 5a9267c..72a11d7 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -80,11 +80,6 @@ func GetBuildVersion() string { return buildVersion } -// Gets the friendly name of this node, as specified in the NodeConfig. -func (c *Core) GetMeta() metadata { - return c.sessions.myMetadata -} - // Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging // through the provided log.Logger. The started stack will include TCP and UDP // sockets, a multicast discovery socket, an admin socket, router, switch and @@ -245,6 +240,16 @@ func (c *Core) GetSubnet() *net.IPNet { return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } +// Gets the node metadata. +func (c *Core) GetMetadata() metadata { + return c.sessions.getMetadata() +} + +// Sets the node metadata. +func (c *Core) SetMetadata(meta metadata) { + c.sessions.setMetadata(meta) +} + // Sets the output logger of the Yggdrasil node after startup. This may be // useful if you want to redirect the output later. func (c *Core) SetLogger(log *log.Logger) { diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index a40563b..8ac59d8 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -58,7 +58,9 @@ func (r *router) init(core *Core) { r.addr = *address_addrForNodeID(&r.core.dht.nodeID) r.subnet = *address_subnetForNodeID(&r.core.dht.nodeID) in := make(chan []byte, 32) // TODO something better than this... + r.core.sessions.myMetadataMutex.RLock() p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)", r.core.sessions.myMetadata) + r.core.sessions.myMetadataMutex.RUnlock() p.out = func(packet []byte) { // This is to make very sure it never blocks select { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 690f603..a7bed8a 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -7,6 +7,7 @@ package yggdrasil import ( "bytes" "encoding/hex" + "sync" "time" ) @@ -128,7 +129,8 @@ type sessions struct { sessionFirewallWhitelist []string sessionFirewallBlacklist []string // Metadata for this node - myMetadata metadata + myMetadata metadata + myMetadataMutex sync.RWMutex } // Initializes the session struct. @@ -143,8 +145,17 @@ func (ss *sessions) init(core *Core) { ss.lastCleanup = time.Now() } -// Enable or disable the session firewall +// Get the metadata +func (ss *sessions) getMetadata() metadata { + ss.myMetadataMutex.RLock() + defer ss.myMetadataMutex.RUnlock() + return ss.myMetadata +} + +// Set the metadata func (ss *sessions) setMetadata(meta metadata) { + ss.myMetadataMutex.Lock() + defer ss.myMetadataMutex.Unlock() ss.myMetadata = meta } @@ -485,18 +496,23 @@ func (ss *sessions) handlePing(ping *sessionPing) { bs, sinfo.packet = sinfo.packet, nil ss.core.router.sendPacket(bs) } - if time.Since(sinfo.metaResTime).Minutes() > 15 { - if time.Since(sinfo.metaReqTime).Minutes() > 1 { - ss.sendMeta(sinfo, false) - } - } + // This requests metadata from the remote side fairly quickly after + // establishing the session, and if other time constraints apply (no more + // often than 15 minutes since receiving the last metadata) + //if time.Since(sinfo.metaResTime).Minutes() > 15 { + // if time.Since(sinfo.metaReqTime).Minutes() > 1 { + // ss.sendMeta(sinfo, false) + // } + //} } func (ss *sessions) sendMeta(sinfo *sessionInfo, isResponse bool) { + ss.myMetadataMutex.RLock() meta := sessionMeta{ IsResponse: isResponse, Metadata: ss.myMetadata, } + ss.myMetadataMutex.RUnlock() bs := meta.encode() shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) payload, nonce := boxSeal(shared, bs, nil) diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index 7f394d7..2d87a37 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -355,7 +355,7 @@ func (p *sessionPing) decode(bs []byte) bool { //////////////////////////////////////////////////////////////////////////////// -// Encodes a sessionPing into its wire format. +// Encodes a sessionMeta into its wire format. func (p *sessionMeta) encode() []byte { var pTypeVal uint64 if p.IsResponse { @@ -370,7 +370,7 @@ func (p *sessionMeta) encode() []byte { return bs } -// Decodes an encoded sessionPing into the struct, returning true if successful. +// Decodes an encoded sessionMeta into the struct, returning true if successful. func (p *sessionMeta) decode(bs []byte) bool { var pType uint64 switch { @@ -380,6 +380,9 @@ func (p *sessionMeta) decode(bs []byte) bool { return false } if p.IsResponse = pType == wire_SessionMetaResponse; p.IsResponse { + if len(bs) == 0 { + return false + } p.Metadata = make(metadata, len(bs)) if !wire_chop_slice(p.Metadata[:], &bs) { return false From 74de8c9416f716a2d90f836b5878d2ac5aba0c41 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 12 Dec 2018 22:48:04 +0000 Subject: [PATCH 06/18] Consistent function naming for metadata --- src/yggdrasil/router.go | 6 +++--- src/yggdrasil/session.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 8ac59d8..b0655eb 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -433,7 +433,7 @@ func (r *router) handleProto(packet []byte) { case wire_SessionMetaRequest: fallthrough case wire_SessionMetaResponse: - r.handleMeta(bs, &p.FromKey) + r.handleMetadata(bs, &p.FromKey) case wire_DHTLookupRequest: r.handleDHTReq(bs, &p.FromKey) case wire_DHTLookupResponse: @@ -479,13 +479,13 @@ func (r *router) handleDHTRes(bs []byte, fromKey *boxPubKey) { } // Decodes meta request -func (r *router) handleMeta(bs []byte, fromKey *boxPubKey) { +func (r *router) handleMetadata(bs []byte, fromKey *boxPubKey) { req := sessionMeta{} if !req.decode(bs) { return } req.SendPermPub = *fromKey - r.core.sessions.handleMeta(&req) + r.core.sessions.handleMetadata(&req) } // Passed a function to call. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index a7bed8a..6a49d58 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -501,12 +501,12 @@ func (ss *sessions) handlePing(ping *sessionPing) { // often than 15 minutes since receiving the last metadata) //if time.Since(sinfo.metaResTime).Minutes() > 15 { // if time.Since(sinfo.metaReqTime).Minutes() > 1 { - // ss.sendMeta(sinfo, false) + // ss.sendMetadata(sinfo, false) // } //} } -func (ss *sessions) sendMeta(sinfo *sessionInfo, isResponse bool) { +func (ss *sessions) sendMetadata(sinfo *sessionInfo, isResponse bool) { ss.myMetadataMutex.RLock() meta := sessionMeta{ IsResponse: isResponse, @@ -531,7 +531,7 @@ func (ss *sessions) sendMeta(sinfo *sessionInfo, isResponse bool) { } // Handles a meta request/response. -func (ss *sessions) handleMeta(meta *sessionMeta) { +func (ss *sessions) handleMetadata(meta *sessionMeta) { // Get the corresponding session (or create a new session) sinfo, isIn := ss.getByTheirPerm(&meta.SendPermPub) // Check the session firewall @@ -547,7 +547,7 @@ func (ss *sessions) handleMeta(meta *sessionMeta) { sinfo.theirMetadata = meta.Metadata sinfo.metaResTime = time.Now() } else { - ss.sendMeta(sinfo, true) + ss.sendMetadata(sinfo, true) } } From 2056e75ad58d400dbff995722e3f840e23fe24d5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 12 Dec 2018 22:51:00 +0000 Subject: [PATCH 07/18] Remove friendlyname fields from yggdrasilctl --- cmd/yggdrasilctl/main.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index f09e5d8..b37c256 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -256,13 +256,6 @@ func main() { if buildversion, ok := v.(map[string]interface{})["build_version"].(string); ok && buildversion != "unknown" { fmt.Println("Build version:", buildversion) } - if friendlyname, ok := v.(map[string]interface{})["friendly_name"].(string); ok { - if friendlyname == "" { - fmt.Println("Friendly name: (none)") - } else { - fmt.Println("Friendly name:", friendlyname) - } - } fmt.Println("IPv6 address:", k) if subnet, ok := v.(map[string]interface{})["subnet"].(string); ok { fmt.Println("IPv6 subnet:", subnet) From 64060a447c5301e3ac0602f6730a9b46909fdc08 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 12 Dec 2018 22:58:16 +0000 Subject: [PATCH 08/18] Remove metadata-peer association until we have some sensible way to cache it --- src/yggdrasil/peer.go | 4 +--- src/yggdrasil/router.go | 4 +--- src/yggdrasil/session.go | 1 + src/yggdrasil/tcp.go | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 4a81cb8..abdfa0c 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -86,7 +86,6 @@ type peer struct { shared boxSharedKey linkShared boxSharedKey endpoint string - metadata metadata firstSeen time.Time // To track uptime for getPeers linkOut (chan []byte) // used for protocol traffic (to bypass queues) doSend (chan struct{}) // tell the linkLoop to send a switchMsg @@ -96,14 +95,13 @@ type peer struct { } // Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unocupied port number. -func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey, endpoint string, metadata metadata) *peer { +func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey, endpoint string) *peer { now := time.Now() p := peer{box: *box, sig: *sig, shared: *getSharedKey(&ps.core.boxPriv, box), linkShared: *linkShared, endpoint: endpoint, - metadata: metadata, firstSeen: now, doSend: make(chan struct{}, 1), core: ps.core} diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index b0655eb..4914a6b 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -58,9 +58,7 @@ func (r *router) init(core *Core) { r.addr = *address_addrForNodeID(&r.core.dht.nodeID) r.subnet = *address_subnetForNodeID(&r.core.dht.nodeID) in := make(chan []byte, 32) // TODO something better than this... - r.core.sessions.myMetadataMutex.RLock() - p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)", r.core.sessions.myMetadata) - r.core.sessions.myMetadataMutex.RUnlock() + p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)") p.out = func(packet []byte) { // This is to make very sure it never blocks select { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 6a49d58..31fe375 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -546,6 +546,7 @@ func (ss *sessions) handleMetadata(meta *sessionMeta) { if meta.IsResponse { sinfo.theirMetadata = meta.Metadata sinfo.metaResTime = time.Now() + } else { ss.sendMetadata(sinfo, true) } diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index dc1e2b1..5ca6630 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -287,7 +287,7 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { }() // Note that multiple connections to the same node are allowed // E.g. over different interfaces - p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String(), metadata{}) + p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String()) p.linkOut = make(chan []byte, 1) in := func(bs []byte) { p.handlePacket(bs) From a9907a78788b8698d80d053f390d415f3c58047d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 12 Dec 2018 22:59:55 +0000 Subject: [PATCH 09/18] Fix debug builds after 64060a4 --- src/yggdrasil/debug.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 9a56e28..e463518 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -50,7 +50,7 @@ func StartProfiler(log *log.Logger) error { func (c *Core) Init() { bpub, bpriv := newBoxKeys() spub, spriv := newSigKeys() - c.init(bpub, bpriv, spub, spriv, metadata{}) + c.init(bpub, bpriv, spub, spriv) c.switchTable.start() c.router.start() } @@ -84,7 +84,7 @@ func (c *Core) DEBUG_getPeers() *peers { func (ps *peers) DEBUG_newPeer(box boxPubKey, sig sigPubKey, link boxSharedKey) *peer { //in <-chan []byte, //out chan<- []byte) *peer { - return ps.newPeer(&box, &sig, &link, "(simulator)", metadata{}) //, in, out) + return ps.newPeer(&box, &sig, &link, "(simulator)") //, in, out) } /* @@ -356,7 +356,7 @@ func (c *Core) DEBUG_init(bpub []byte, copy(boxPriv[:], bpriv) copy(sigPub[:], spub) copy(sigPriv[:], spriv) - c.init(&boxPub, &boxPriv, &sigPub, &sigPriv, metadata{}) + c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) if err := c.router.start(); err != nil { panic(err) From d5031a5cb610cd755039fbf68d013418f77fcb74 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 00:48:27 +0000 Subject: [PATCH 10/18] Metadata exchange without sessions --- src/yggdrasil/admin.go | 52 +++++++++++++++ src/yggdrasil/core.go | 13 ++-- src/yggdrasil/metadata.go | 92 ++++++++++++++++++++++++++ src/yggdrasil/router.go | 2 +- src/yggdrasil/session.go | 136 +++++++++----------------------------- src/yggdrasil/wire.go | 5 +- 6 files changed, 187 insertions(+), 113 deletions(-) create mode 100644 src/yggdrasil/metadata.go diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 266d5a8..1bf6af0 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -322,6 +322,14 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{}, err } }) + a.addHandler("getMeta", []string{"box_pub_key", "coords"}, func(in admin_info) (admin_info, error) { + result, err := a.admin_getMeta(in["box_pub_key"].(string), in["coords"].(string)) + if err == nil { + return admin_info{"metadata": string(result)}, nil + } else { + return admin_info{}, err + } + }) } // start runs the admin API socket to listen for / respond to admin API calls. @@ -806,6 +814,50 @@ 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_getMeta(keyString, coordString string) (metadataPayload, error) { + var key boxPubKey + if keyBytes, err := hex.DecodeString(keyString); err != nil { + return metadataPayload{}, err + } else { + copy(key[:], keyBytes) + } + var coords []byte + for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") { + if cstr == "" { + // Special case, happens if trimmed is the empty string, e.g. this is the root + continue + } + if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil { + return metadataPayload{}, err + } else { + coords = append(coords, uint8(u64)) + } + } + response := make(chan *metadataPayload, 1) + sendMetaRequest := func() { + a.core.metadata.callbacks[key] = metadataCallback{ + created: time.Now(), + call: func(meta *metadataPayload) { + defer func() { recover() }() + select { + case response <- meta: + default: + } + }, + } + a.core.metadata.sendMetadata(key, coords, false) + } + a.core.router.doAdmin(sendMetaRequest) + go func() { + time.Sleep(6 * time.Second) + close(response) + }() + for res := range response { + return *res, nil + } + return metadataPayload{}, errors.New(fmt.Sprintf("getMeta timeout: %s", keyString)) +} + // getResponse_dot returns a response for a graphviz dot formatted representation of the known parts of the network. // This is color-coded and labeled, and includes the self node, switch peers, nodes known to the DHT, and nodes with open sessions. // The graph is structured as a tree with directed links leading away from the root. diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 72a11d7..7a3564d 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -32,6 +32,7 @@ type Core struct { admin admin searches searches multicast multicast + metadata metadata tcp tcpInterface log *log.Logger ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this @@ -124,6 +125,9 @@ 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(metadataPayload("HIYA, THIS IS METADATA")) + if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil { c.log.Println("Failed to start TCP interface") return err @@ -138,7 +142,6 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } - c.sessions.setMetadata(metadata("HIYA, THIS IS METADATA")) c.sessions.setSessionFirewallState(nc.SessionFirewall.Enable) c.sessions.setSessionFirewallDefaults( nc.SessionFirewall.AllowFromDirect, @@ -241,13 +244,13 @@ func (c *Core) GetSubnet() *net.IPNet { } // Gets the node metadata. -func (c *Core) GetMetadata() metadata { - return c.sessions.getMetadata() +func (c *Core) GetMetadata() metadataPayload { + return c.metadata.getMetadata() } // Sets the node metadata. -func (c *Core) SetMetadata(meta metadata) { - c.sessions.setMetadata(meta) +func (c *Core) SetMetadata(meta metadataPayload) { + c.metadata.setMetadata(meta) } // 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 new file mode 100644 index 0000000..7f607ed --- /dev/null +++ b/src/yggdrasil/metadata.go @@ -0,0 +1,92 @@ +package yggdrasil + +import ( + "sync" + "time" +) + +type metadata struct { + core *Core + myMetadata metadataPayload + myMetadataMutex sync.RWMutex + callbacks map[boxPubKey]metadataCallback + cache map[boxPubKey]metadataPayload +} + +type metadataPayload []byte + +type metadataCallback struct { + call func(meta *metadataPayload) + created time.Time +} + +// Initialises the metadata cache/callback stuff +func (m *metadata) init(core *Core) { + m.core = core + m.callbacks = make(map[boxPubKey]metadataCallback) + m.cache = make(map[boxPubKey]metadataPayload) + + go func() { + for { + for boxPubKey, callback := range m.callbacks { + if time.Since(callback.created) > time.Minute { + delete(m.callbacks, boxPubKey) + } + } + time.Sleep(time.Second * 5) + } + }() +} + +// Handles the callback, if there is one +func (m *metadata) callback(sender boxPubKey, meta metadataPayload) { + if callback, ok := m.callbacks[sender]; ok { + callback.call(&meta) + delete(m.callbacks, sender) + } +} + +// Get the metadata +func (m *metadata) getMetadata() metadataPayload { + m.myMetadataMutex.RLock() + defer m.myMetadataMutex.RUnlock() + return m.myMetadata +} + +// Set the metadata +func (m *metadata) setMetadata(meta metadataPayload) { + m.myMetadataMutex.Lock() + defer m.myMetadataMutex.Unlock() + m.myMetadata = meta +} + +// Handles a meta request/response. +func (m *metadata) handleMetadata(meta *sessionMeta) { + if meta.IsResponse { + m.core.metadata.callback(meta.SendPermPub, meta.Metadata) + } else { + m.sendMetadata(meta.SendPermPub, meta.SendCoords, true) + } +} + +// Send metadata request or response +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/router.go b/src/yggdrasil/router.go index 4914a6b..e6f6b0f 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -483,7 +483,7 @@ func (r *router) handleMetadata(bs []byte, fromKey *boxPubKey) { return } req.SendPermPub = *fromKey - r.core.sessions.handleMetadata(&req) + r.core.metadata.handleMetadata(&req) } // Passed a function to call. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 31fe375..418c46a 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -7,44 +7,40 @@ package yggdrasil import ( "bytes" "encoding/hex" - "sync" "time" ) // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { - core *Core - theirAddr address - theirSubnet subnet - theirPermPub boxPubKey - theirSesPub boxPubKey - mySesPub boxPubKey - mySesPriv boxPrivKey - sharedSesKey boxSharedKey // derived from session keys - theirHandle handle - myHandle handle - theirNonce boxNonce - myNonce boxNonce - metaReqTime time.Time - metaResTime time.Time - theirMetadata metadata - theirMTU uint16 - myMTU uint16 - wasMTUFixed bool // Was the MTU fixed by a receive error? - time time.Time // Time we last received a packet - coords []byte // coords of destination - packet []byte // a buffered packet, sent immediately on ping/pong - init bool // Reset if coords change - send chan []byte - recv chan *wire_trafficPacket - nonceMask uint64 - tstamp int64 // tstamp from their last session ping, replay attack mitigation - mtuTime time.Time // time myMTU was last changed - pingTime time.Time // time the first ping was sent since the last received packet - pingSend time.Time // time the last ping was sent - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session + core *Core + theirAddr address + theirSubnet subnet + theirPermPub boxPubKey + theirSesPub boxPubKey + mySesPub boxPubKey + mySesPriv boxPrivKey + sharedSesKey boxSharedKey // derived from session keys + theirHandle handle + myHandle handle + theirNonce boxNonce + myNonce boxNonce + theirMTU uint16 + myMTU uint16 + wasMTUFixed bool // Was the MTU fixed by a receive error? + time time.Time // Time we last received a packet + coords []byte // coords of destination + packet []byte // a buffered packet, sent immediately on ping/pong + init bool // Reset if coords change + send chan []byte + recv chan *wire_trafficPacket + nonceMask uint64 + tstamp int64 // tstamp from their last session ping, replay attack mitigation + mtuTime time.Time // time myMTU was last changed + pingTime time.Time // time the first ping was sent since the last received packet + pingSend time.Time // time the last ping was sent + bytesSent uint64 // Bytes of real traffic sent in this session + bytesRecvd uint64 // Bytes of real traffic received in this session } // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -61,12 +57,11 @@ type sessionPing struct { // Represents a session metadata packet. type sessionMeta struct { SendPermPub boxPubKey // Sender's permanent key + SendCoords []byte // Sender's coords IsResponse bool - Metadata metadata + Metadata metadataPayload } -type metadata []byte - // 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 { @@ -128,9 +123,6 @@ type sessions struct { sessionFirewallAlwaysAllowsOutbound bool sessionFirewallWhitelist []string sessionFirewallBlacklist []string - // Metadata for this node - myMetadata metadata - myMetadataMutex sync.RWMutex } // Initializes the session struct. @@ -145,20 +137,6 @@ func (ss *sessions) init(core *Core) { ss.lastCleanup = time.Now() } -// Get the metadata -func (ss *sessions) getMetadata() metadata { - ss.myMetadataMutex.RLock() - defer ss.myMetadataMutex.RUnlock() - return ss.myMetadata -} - -// Set the metadata -func (ss *sessions) setMetadata(meta metadata) { - ss.myMetadataMutex.Lock() - defer ss.myMetadataMutex.Unlock() - ss.myMetadata = meta -} - // Enable or disable the session firewall func (ss *sessions) setSessionFirewallState(enabled bool) { ss.sessionFirewallEnabled = enabled @@ -496,60 +474,6 @@ func (ss *sessions) handlePing(ping *sessionPing) { bs, sinfo.packet = sinfo.packet, nil ss.core.router.sendPacket(bs) } - // This requests metadata from the remote side fairly quickly after - // establishing the session, and if other time constraints apply (no more - // often than 15 minutes since receiving the last metadata) - //if time.Since(sinfo.metaResTime).Minutes() > 15 { - // if time.Since(sinfo.metaReqTime).Minutes() > 1 { - // ss.sendMetadata(sinfo, false) - // } - //} -} - -func (ss *sessions) sendMetadata(sinfo *sessionInfo, isResponse bool) { - ss.myMetadataMutex.RLock() - meta := sessionMeta{ - IsResponse: isResponse, - Metadata: ss.myMetadata, - } - ss.myMetadataMutex.RUnlock() - bs := meta.encode() - shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) - payload, nonce := boxSeal(shared, bs, nil) - p := wire_protoTrafficPacket{ - Coords: sinfo.coords, - ToKey: sinfo.theirPermPub, - FromKey: ss.core.boxPub, - Nonce: *nonce, - Payload: payload, - } - packet := p.encode() - ss.core.router.out(packet) - if !isResponse { - sinfo.metaReqTime = time.Now() - } -} - -// Handles a meta request/response. -func (ss *sessions) handleMetadata(meta *sessionMeta) { - // Get the corresponding session (or create a new session) - sinfo, isIn := ss.getByTheirPerm(&meta.SendPermPub) - // Check the session firewall - if !isIn && ss.sessionFirewallEnabled { - if !ss.isSessionAllowed(&meta.SendPermPub, false) { - return - } - } - if !isIn || sinfo.timedout() { - return - } - if meta.IsResponse { - sinfo.theirMetadata = meta.Metadata - sinfo.metaResTime = time.Now() - - } else { - ss.sendMetadata(sinfo, true) - } } // Used to subtract one nonce from another, staying in the range +- 64. diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index 2d87a37..d46994d 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -364,6 +364,7 @@ func (p *sessionMeta) encode() []byte { pTypeVal = wire_SessionMetaRequest } bs := wire_encode_uint64(pTypeVal) + bs = wire_put_coords(p.SendCoords, bs) if pTypeVal == wire_SessionMetaResponse { bs = append(bs, p.Metadata...) } @@ -378,12 +379,14 @@ func (p *sessionMeta) decode(bs []byte) bool { return false case pType != wire_SessionMetaRequest && pType != wire_SessionMetaResponse: return false + case !wire_chop_coords(&p.SendCoords, &bs): + return false } if p.IsResponse = pType == wire_SessionMetaResponse; p.IsResponse { if len(bs) == 0 { return false } - p.Metadata = make(metadata, len(bs)) + p.Metadata = make(metadataPayload, len(bs)) if !wire_chop_slice(p.Metadata[:], &bs) { return false } From 8b63e841ea27dc74c1216c9b03a5c21375229589 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 10:39:31 +0000 Subject: [PATCH 11/18] Make threadsafe, add cache --- src/yggdrasil/admin.go | 17 +++++++---------- src/yggdrasil/metadata.go | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index e773630..7c9ed1b 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -829,16 +829,13 @@ func (a *admin) admin_getMeta(keyString, coordString string) (metadataPayload, e } response := make(chan *metadataPayload, 1) sendMetaRequest := func() { - a.core.metadata.callbacks[key] = metadataCallback{ - created: time.Now(), - call: func(meta *metadataPayload) { - defer func() { recover() }() - select { - case response <- meta: - default: - } - }, - } + a.core.metadata.addCallback(key, func(meta *metadataPayload) { + defer func() { recover() }() + select { + case response <- meta: + default: + } + }) a.core.metadata.sendMetadata(key, coords, false) } a.core.router.doAdmin(sendMetaRequest) diff --git a/src/yggdrasil/metadata.go b/src/yggdrasil/metadata.go index 7f607ed..19393b1 100644 --- a/src/yggdrasil/metadata.go +++ b/src/yggdrasil/metadata.go @@ -10,7 +10,9 @@ type metadata struct { myMetadata metadataPayload myMetadataMutex sync.RWMutex callbacks map[boxPubKey]metadataCallback + callbacksMutex sync.Mutex cache map[boxPubKey]metadataPayload + cacheMutex sync.RWMutex } type metadataPayload []byte @@ -38,8 +40,20 @@ func (m *metadata) init(core *Core) { }() } +// Add a callback +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) @@ -60,10 +74,28 @@ func (m *metadata) setMetadata(meta metadataPayload) { m.myMetadata = meta } +// 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] = payload +} + +// Get a metadata entry from the cache +func (m *metadata) getCachedMetadata(key boxPubKey) metadataPayload { + m.cacheMutex.RLock() + defer m.cacheMutex.RUnlock() + if meta, ok := m.cache[key]; ok { + return meta + } + return metadataPayload{} +} + // Handles a meta request/response. func (m *metadata) handleMetadata(meta *sessionMeta) { if meta.IsResponse { - m.core.metadata.callback(meta.SendPermPub, meta.Metadata) + m.callback(meta.SendPermPub, meta.Metadata) + m.addCachedMetadata(meta.SendPermPub, meta.Metadata) } else { m.sendMetadata(meta.SendPermPub, meta.SendCoords, true) } From d07e0ddfa0419c4dbba249e6f1eb29ec12c13aa2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 10:56:46 +0000 Subject: [PATCH 12/18] Default metadata --- src/yggdrasil/admin.go | 7 ++++++- src/yggdrasil/core.go | 12 +++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 7c9ed1b..d78499e 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -325,7 +325,12 @@ func (a *admin) init(c *Core, listenaddr string) { a.addHandler("getMeta", []string{"box_pub_key", "coords"}, func(in admin_info) (admin_info, error) { result, err := a.admin_getMeta(in["box_pub_key"].(string), in["coords"].(string)) if err == nil { - return admin_info{"metadata": string(result)}, nil + var m map[string]interface{} + if err = json.Unmarshal(result, &m); err == nil { + return admin_info{"metadata": m}, nil + } else { + return admin_info{}, err + } } else { return admin_info{}, err } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 559997f..85c5540 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -2,11 +2,13 @@ package yggdrasil import ( "encoding/hex" + "encoding/json" "fmt" "io/ioutil" "log" "net" "regexp" + "runtime" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" @@ -124,7 +126,15 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.admin.init(c, nc.AdminListen) c.metadata.init(c) - c.metadata.setMetadata(metadataPayload("HIYA, THIS IS METADATA")) + m := map[string]string{ + "buildname": GetBuildName(), + "buildversion": GetBuildVersion(), + "buildplatform": runtime.GOOS, + "buildarch": runtime.GOARCH, + } + if json, err := json.Marshal(m); err == nil { + c.metadata.setMetadata(json) + } if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil { c.log.Println("Failed to start TCP interface") From d9884a5cac8cc3008b4f348a895c49354d05d9ae Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 11:15:48 +0000 Subject: [PATCH 13/18] Make use of metadata cache --- src/yggdrasil/admin.go | 15 ++++++++++--- src/yggdrasil/metadata.go | 45 ++++++++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index d78499e..2fba594 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -322,8 +322,12 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{}, err } }) - a.addHandler("getMeta", []string{"box_pub_key", "coords"}, func(in admin_info) (admin_info, error) { - result, err := a.admin_getMeta(in["box_pub_key"].(string), in["coords"].(string)) + a.addHandler("getMeta", []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_getMeta(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 { @@ -813,13 +817,18 @@ 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_getMeta(keyString, coordString string) (metadataPayload, error) { +func (a *admin) admin_getMeta(keyString, coordString string, nocache bool) (metadataPayload, error) { var key boxPubKey if keyBytes, err := hex.DecodeString(keyString); err != nil { return metadataPayload{}, err } else { copy(key[:], keyBytes) } + if !nocache { + if response, err := a.core.metadata.getCachedMetadata(key); err == nil { + return response, nil + } + } var coords []byte for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") { if cstr == "" { diff --git a/src/yggdrasil/metadata.go b/src/yggdrasil/metadata.go index 19393b1..4acd467 100644 --- a/src/yggdrasil/metadata.go +++ b/src/yggdrasil/metadata.go @@ -1,6 +1,7 @@ package yggdrasil import ( + "errors" "sync" "time" ) @@ -11,36 +12,51 @@ type metadata struct { myMetadataMutex sync.RWMutex callbacks map[boxPubKey]metadataCallback callbacksMutex sync.Mutex - cache map[boxPubKey]metadataPayload + 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 stuff +// 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]metadataPayload) + 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) } } - time.Sleep(time.Second * 5) + 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 +// Add a callback for a metadata lookup func (m *metadata) addCallback(sender boxPubKey, call func(meta *metadataPayload)) { m.callbacksMutex.Lock() defer m.callbacksMutex.Unlock() @@ -60,14 +76,14 @@ func (m *metadata) callback(sender boxPubKey, meta metadataPayload) { } } -// Get the metadata +// Get the current node's metadata func (m *metadata) getMetadata() metadataPayload { m.myMetadataMutex.RLock() defer m.myMetadataMutex.RUnlock() return m.myMetadata } -// Set the metadata +// Set the current node's metadata func (m *metadata) setMetadata(meta metadataPayload) { m.myMetadataMutex.Lock() defer m.myMetadataMutex.Unlock() @@ -78,20 +94,23 @@ func (m *metadata) setMetadata(meta metadataPayload) { func (m *metadata) addCachedMetadata(key boxPubKey, payload metadataPayload) { m.cacheMutex.Lock() defer m.cacheMutex.Unlock() - m.cache[key] = payload + m.cache[key] = metadataCached{ + created: time.Now(), + payload: payload, + } } // Get a metadata entry from the cache -func (m *metadata) getCachedMetadata(key boxPubKey) metadataPayload { +func (m *metadata) getCachedMetadata(key boxPubKey) (metadataPayload, error) { m.cacheMutex.RLock() defer m.cacheMutex.RUnlock() if meta, ok := m.cache[key]; ok { - return meta + return meta.payload, nil } - return metadataPayload{} + return metadataPayload{}, errors.New("No cache entry found") } -// Handles a meta request/response. +// Handles a meta request/response - called from the router func (m *metadata) handleMetadata(meta *sessionMeta) { if meta.IsResponse { m.callback(meta.SendPermPub, meta.Metadata) @@ -101,7 +120,7 @@ func (m *metadata) handleMetadata(meta *sessionMeta) { } } -// Send metadata request or response +// 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{ From 92bb63f1964c6fe4e516bdc1993b199a4f459c19 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 11:38:51 +0000 Subject: [PATCH 14/18] Use metadata from config file --- src/config/config.go | 36 ++++++++++++++++++------------------ src/yggdrasil/core.go | 12 +----------- src/yggdrasil/metadata.go | 25 +++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/config/config.go b/src/config/config.go index 66de668..a4cd5c3 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -2,24 +2,24 @@ package config // NodeConfig defines all configuration values needed to run a signle yggdrasil node type NodeConfig struct { - Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` - AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."` - Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` - InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."` - ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."` - AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."` - EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` - EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` - SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` - SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` - MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` - IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."` - IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` - IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP 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."` - 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 interface{} `comment:"Optional node metadata. Entirely optional but visible to all\npeers and nodes with open sessions."` + Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` + AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."` + Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` + InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."` + ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."` + AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."` + EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` + EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` + SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` + SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` + MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` + IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."` + IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` + IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP 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."` + 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."` //Net NetConfig `comment:"Extended options for connecting to peers over other networks."` } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 85c5540..5fbedc8 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -2,13 +2,11 @@ package yggdrasil import ( "encoding/hex" - "encoding/json" "fmt" "io/ioutil" "log" "net" "regexp" - "runtime" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" @@ -126,15 +124,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.admin.init(c, nc.AdminListen) c.metadata.init(c) - m := map[string]string{ - "buildname": GetBuildName(), - "buildversion": GetBuildVersion(), - "buildplatform": runtime.GOOS, - "buildarch": runtime.GOARCH, - } - if json, err := json.Marshal(m); err == nil { - c.metadata.setMetadata(json) - } + c.metadata.setMetadata(nc.Metadata) if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil { c.log.Println("Failed to start TCP interface") diff --git a/src/yggdrasil/metadata.go b/src/yggdrasil/metadata.go index 4acd467..7deb60d 100644 --- a/src/yggdrasil/metadata.go +++ b/src/yggdrasil/metadata.go @@ -1,7 +1,9 @@ package yggdrasil import ( + "encoding/json" "errors" + "runtime" "sync" "time" ) @@ -84,10 +86,29 @@ func (m *metadata) getMetadata() metadataPayload { } // Set the current node's metadata -func (m *metadata) setMetadata(meta metadataPayload) { +func (m *metadata) setMetadata(given interface{}) error { m.myMetadataMutex.Lock() defer m.myMetadataMutex.Unlock() - m.myMetadata = meta + 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 { + m.myMetadata = newjson + return nil + } else { + return err + } } // Add metadata into the cache for a node From 9a5cf96c298025c84f710ec0c7be1fdc7e71fc39 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 12:18:52 +0000 Subject: [PATCH 15/18] Rename admin socket getMeta to getMetadata --- src/yggdrasil/admin.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 2fba594..5def7e0 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -322,12 +322,12 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{}, err } }) - a.addHandler("getMeta", []string{"box_pub_key", "coords", "[nocache]"}, func(in admin_info) (admin_info, error) { + a.addHandler("getMetadata", []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_getMeta(in["box_pub_key"].(string), in["coords"].(string), nocache) + result, err := a.admin_getMetadata(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 { @@ -817,7 +817,7 @@ 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_getMeta(keyString, coordString string, nocache bool) (metadataPayload, error) { +func (a *admin) admin_getMetadata(keyString, coordString string, nocache bool) (metadataPayload, error) { var key boxPubKey if keyBytes, err := hex.DecodeString(keyString); err != nil { return metadataPayload{}, err @@ -860,7 +860,7 @@ func (a *admin) admin_getMeta(keyString, coordString string, nocache bool) (meta for res := range response { return *res, nil } - return metadataPayload{}, errors.New(fmt.Sprintf("getMeta timeout: %s", keyString)) + return metadataPayload{}, errors.New(fmt.Sprintf("getMetadata timeout: %s", keyString)) } // getResponse_dot returns a response for a graphviz dot formatted representation of the known parts of the network. From 07c26176b6272b4c3df8721397f214febeef5167 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 12:21:00 +0000 Subject: [PATCH 16/18] Fix core.SetMetadata --- src/yggdrasil/core.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 5fbedc8..05d528a 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -247,7 +247,7 @@ func (c *Core) GetMetadata() metadataPayload { } // Sets the node metadata. -func (c *Core) SetMetadata(meta metadataPayload) { +func (c *Core) SetMetadata(meta interface{}) { c.metadata.setMetadata(meta) } From 226c72df16f6811d45413627524b2728216bfcb3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 13:18:35 +0000 Subject: [PATCH 17/18] Set max metadata size to 16kb --- src/yggdrasil/metadata.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yggdrasil/metadata.go b/src/yggdrasil/metadata.go index 7deb60d..67bab4a 100644 --- a/src/yggdrasil/metadata.go +++ b/src/yggdrasil/metadata.go @@ -104,6 +104,9 @@ func (m *metadata) setMetadata(given interface{}) error { } } 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 { From e0ff3ca587e8673bdababf23c5b243b2c6901653 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 22:37:11 +0000 Subject: [PATCH 18/18] Rename Metadata to NodeInfo --- src/config/config.go | 2 +- src/yggdrasil/admin.go | 28 +++--- src/yggdrasil/core.go | 18 ++-- src/yggdrasil/metadata.go | 167 ------------------------------------ src/yggdrasil/nodeinfo.go | 175 ++++++++++++++++++++++++++++++++++++++ src/yggdrasil/router.go | 14 +-- src/yggdrasil/session.go | 8 -- src/yggdrasil/wire.go | 28 +++--- 8 files changed, 220 insertions(+), 220 deletions(-) delete mode 100644 src/yggdrasil/metadata.go create mode 100644 src/yggdrasil/nodeinfo.go 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 } }