From 088d28a93b5437b259a981a5dde81accf2d8a21c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Oct 2018 18:04:02 +0100 Subject: [PATCH 1/3] 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 892529b6..74d4ae0a 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 2/3] Fix getSelf in yggdrasilctl --- yggdrasilctl.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/yggdrasilctl.go b/yggdrasilctl.go index 2b5b79a4..fd2d9b8a 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 3/3] 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 630db177..aa73d3b3 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 530c18cb..04c95c2b 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 8f61bf69..9be08abc 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 00000000..c5243087 --- /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 4f792376..da807c96 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 dcc6a5c4..bcec2587 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 0bc27a12..39ea4cbc 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 58d9422e..dc1e2b16 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 d05624e2..fd898a6c 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)