From 088d28a93b5437b259a981a5dde81accf2d8a21c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Oct 2018 18:04:02 +0100 Subject: [PATCH 01/49] 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 02/49] 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 03/49] 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) From 97464feba9394d7403abdceb63e024ac8b26e675 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 12 Dec 2018 19:51:28 +0000 Subject: [PATCH 04/49] 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 18950a4f..66de668b 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 332fa1ce..266d5a89 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 06a3cb31..5a9267c4 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 c5243087..00000000 --- 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 2df359df..a40563b1 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 e088726e..690f603f 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 fd898a6c..7f394d7e 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 73ed563dded392f11dfe3208b82c5c0345f0f5c7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 12 Dec 2018 20:29:56 +0000 Subject: [PATCH 05/49] Update debian package behavior It now won't enable itself automatically on install and it will only start Yggdrasil if enabled in systemd. It also won't break during install on systems where systemd is not present. --- contrib/deb/generate.sh | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index 5af31d5c..618f00f0 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -52,8 +52,11 @@ Architecture: $PKGARCH Replaces: $PKGREPLACES Conflicts: $PKGREPLACES Maintainer: Neil Alexander -Description: Debian yggdrasil package - Binary yggdrasil package for Debian and Ubuntu +Description: Yggdrasil Network + Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6 + network. It is lightweight, self-arranging, supported on multiple platforms and + allows pretty much any IPv6-capable application to communicate securely with + other Yggdrasil nodes. EOF cat > /tmp/$PKGNAME/debian/copyright << EOF Please see https://github.com/yggdrasil-network/yggdrasil-go/ @@ -76,13 +79,22 @@ then echo "Normalising /etc/yggdrasil.conf" /usr/bin/yggdrasil -useconffile /var/backups/yggdrasil.conf.`date +%Y%m%d` -normaliseconf > /etc/yggdrasil.conf fi -systemctl enable yggdrasil -systemctl start yggdrasil +if command -v systemctl >/dev/null; then + systemctl daemon-reload >/dev/null || true + if [ -f /etc/systemd/system/multi-user.target.wants/yggdrasil.service ]; then + echo "Starting Yggdrasil" + systemctl start yggdrasil || true + fi +fi EOF cat > /tmp/$PKGNAME/debian/prerm << EOF #!/bin/sh -systemctl disable yggdrasil -systemctl stop yggdrasil +if command -v systemctl >/dev/null; then + if systemctl is-active --quiet yggdrasil; then + echo "Stopping Yggdrasil" + systemctl stop yggdrasil || true + fi +fi EOF cp yggdrasil /tmp/$PKGNAME/usr/bin/ From 042a3400fe08050ae0e30b8e453ae2f59bb5c50d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 12 Dec 2018 22:40:49 +0000 Subject: [PATCH 06/49] 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 5a9267c4..72a11d72 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 a40563b1..8ac59d8e 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 690f603f..a7bed8a3 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 7f394d7e..2d87a37f 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 07/49] 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 8ac59d8e..b0655eb6 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 a7bed8a3..6a49d582 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 08/49] 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 f09e5d86..b37c2561 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 09/49] 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 4a81cb83..abdfa0ce 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 b0655eb6..4914a6b8 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 6a49d582..31fe375b 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 dc1e2b16..5ca66304 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 10/49] 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 9a56e280..e463518d 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 e1c79837527ace57a8e5e9aaef01a80e2b2ef21c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 14 Dec 2018 14:25:26 +0000 Subject: [PATCH 11/49] Update debian package to fix systemd disabling (not good for remote systems) and add group yggdrasil for admin socket/conf --- contrib/deb/generate.sh | 24 +++++++++++++++++------- contrib/systemd/yggdrasil.service | 3 ++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index 618f00f0..0eb21882 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -71,29 +71,39 @@ etc/systemd/system/*.service etc/systemd/system EOF cat > /tmp/$PKGNAME/debian/postinst << EOF #!/bin/sh + +if ! getent group yggdrasil 2>&1 > /dev/null; then + addgroup --system --quiet yggdrasil +fi + if [ -f /etc/yggdrasil.conf ]; then mkdir -p /var/backups echo "Backing up configuration file to /var/backups/yggdrasil.conf.`date +%Y%m%d`" cp /etc/yggdrasil.conf /var/backups/yggdrasil.conf.`date +%Y%m%d` - echo "Normalising /etc/yggdrasil.conf" + echo "Normalising and updating /etc/yggdrasil.conf" /usr/bin/yggdrasil -useconffile /var/backups/yggdrasil.conf.`date +%Y%m%d` -normaliseconf > /etc/yggdrasil.conf -fi -if command -v systemctl >/dev/null; then - systemctl daemon-reload >/dev/null || true - if [ -f /etc/systemd/system/multi-user.target.wants/yggdrasil.service ]; then - echo "Starting Yggdrasil" + chgrp yggdrasil /etc/yggdrasil.conf + + if command -v systemctl >/dev/null; then + systemctl daemon-reload >/dev/null || true + systemctl enable yggdrasil || true systemctl start yggdrasil || true fi +else + echo "Generating initial configuration file /etc/yggdrasil.conf" + echo "Please familiarise yourself with this file before starting Yggdrasil" + /usr/bin/yggdrasil -genconf > /etc/yggdrasil.conf + chgrp yggdrasil /etc/yggdrasil.conf fi EOF cat > /tmp/$PKGNAME/debian/prerm << EOF #!/bin/sh if command -v systemctl >/dev/null; then if systemctl is-active --quiet yggdrasil; then - echo "Stopping Yggdrasil" systemctl stop yggdrasil || true fi + systemctl disable yggdrasil || true fi EOF diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index 9ae4a079..1b6f1f07 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -4,6 +4,7 @@ Wants=network.target After=network.target [Service] +Group=yggdrasil ProtectHome=true ProtectSystem=true SyslogIdentifier=yggdrasil @@ -12,7 +13,7 @@ ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \ yggdrasil -genconf > /etc/yggdrasil.conf; \ echo 'WARNING: A new /etc/yggdrasil.conf file has been generated.'; \ fi" -ExecStart=/bin/sh -c "exec yggdrasil -useconf < /etc/yggdrasil.conf" +ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf Restart=always [Install] From 10157483f9bd36a5b02ee20569c908f6d8d75914 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 14 Dec 2018 17:35:02 +0000 Subject: [PATCH 12/49] Move tunDevice into router --- src/yggdrasil/admin.go | 18 +++++++++--------- src/yggdrasil/core.go | 10 ++++------ src/yggdrasil/router.go | 14 ++++++++++---- src/yggdrasil/session.go | 2 +- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 266d5a89..e80ca398 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -160,9 +160,9 @@ func (a *admin) init(c *Core, listenaddr string) { }() return admin_info{ - a.core.tun.iface.Name(): admin_info{ - "tap_mode": a.core.tun.iface.IsTAP(), - "mtu": a.core.tun.mtu, + a.core.router.tun.iface.Name(): admin_info{ + "tap_mode": a.core.router.tun.iface.IsTAP(), + "mtu": a.core.router.tun.mtu, }, }, nil }) @@ -185,8 +185,8 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{}, errors.New("Failed to configure adapter") } else { return admin_info{ - a.core.tun.iface.Name(): admin_info{ - "tap_mode": a.core.tun.iface.IsTAP(), + a.core.router.tun.iface.Name(): admin_info{ + "tap_mode": a.core.router.tun.iface.IsTAP(), "mtu": ifmtu, }, }, nil @@ -539,12 +539,12 @@ func (a *admin) removePeer(p string) error { // startTunWithMTU creates the tun/tap device, sets its address, and sets the MTU to the provided value. func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error { // Close the TUN first if open - _ = a.core.tun.close() + _ = a.core.router.tun.close() // Then reconfigure and start it addr := a.core.router.addr straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address_prefix)-1) if ifname != "none" { - err := a.core.tun.setup(ifname, iftapmode, straddr, ifmtu) + err := a.core.router.tun.setup(ifname, iftapmode, straddr, ifmtu) if err != nil { return err } @@ -559,9 +559,9 @@ func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error a.core.sessions.sendPingPong(sinfo, false) } // Aaaaand... go! - go a.core.tun.read() + go a.core.router.tun.read() } - go a.core.tun.write() + go a.core.router.tun.write() return nil } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 5ab91a74..13609ce6 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -28,7 +28,6 @@ type Core struct { sessions sessions router router dht dht - tun tunDevice admin admin searches searches multicast multicast @@ -59,7 +58,6 @@ func (c *Core) init(bpub *boxPubKey, c.peers.init(c) c.router.init(c) c.switchTable.init(c, c.sigPub) // TODO move before peers? before router? - c.tun.init(c) } // Get the current build name. This is usually injected if built from git, @@ -188,7 +186,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { } ip := net.IP(c.router.addr[:]).String() - if err := c.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address_prefix)-1), nc.IfMTU); err != nil { + if err := c.router.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address_prefix)-1), nc.IfMTU); err != nil { c.log.Println("Failed to start TUN/TAP") return err } @@ -200,7 +198,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { // Stops the Yggdrasil node. func (c *Core) Stop() { c.log.Println("Stopping...") - c.tun.close() + c.router.tun.close() c.admin.close() } @@ -293,10 +291,10 @@ func (c *Core) GetTUNDefaultIfTAPMode() bool { // Gets the current TUN/TAP interface name. func (c *Core) GetTUNIfName() string { - return c.tun.iface.Name() + return c.router.tun.iface.Name() } // Gets the current TUN/TAP interface MTU. func (c *Core) GetTUNIfMTU() int { - return c.tun.mtu + return c.router.tun.mtu } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index b4824767..eab18b1e 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -30,6 +30,10 @@ import ( "golang.org/x/net/ipv6" ) +type adapter struct { + tunDevice +} + // The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. // The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. type router struct { @@ -39,6 +43,7 @@ type router struct { in <-chan []byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() + tun tunDevice // TUN/TAP adapter recv chan<- []byte // place where the tun pulls received packets from send <-chan []byte // place where the tun puts outgoing packets reset chan struct{} // signal that coords changed (re-init sessions/dht) @@ -75,11 +80,12 @@ func (r *router) init(core *Core) { send := make(chan []byte, 32) r.recv = recv r.send = send - r.core.tun.recv = recv - r.core.tun.send = send + r.tun.recv = recv + r.tun.send = send r.reset = make(chan struct{}, 1) r.admin = make(chan func(), 32) r.cryptokey.init(r.core) + r.tun.init(r.core) // go r.mainLoop() } @@ -279,7 +285,7 @@ func (r *router) sendPacket(bs []byte) { } // Create the ICMPv6 response from it - icmpv6Buf, err := r.core.tun.icmpv6.create_icmpv6_tun( + icmpv6Buf, err := r.tun.icmpv6.create_icmpv6_tun( bs[8:24], bs[24:40], ipv6.ICMPTypeDestinationUnreachable, 1, ptb) if err == nil { @@ -304,7 +310,7 @@ func (r *router) sendPacket(bs []byte) { } // Create the ICMPv6 response from it - icmpv6Buf, err := r.core.tun.icmpv6.create_icmpv6_tun( + icmpv6Buf, err := r.tun.icmpv6.create_icmpv6_tun( bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb) if err == nil { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 92ae262b..4f2bedf9 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -273,7 +273,7 @@ func (ss *sessions) createSession(theirPermKey *boxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *newBoxNonce() sinfo.theirMTU = 1280 - sinfo.myMTU = uint16(ss.core.tun.mtu) + sinfo.myMTU = uint16(ss.core.router.tun.mtu) now := time.Now() sinfo.time = now sinfo.mtuTime = now From ccf6ce07a444f8acdc3cba86db16ff89c59d1c43 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 14 Dec 2018 17:49:42 +0000 Subject: [PATCH 13/49] Fix Peers and InterfacePeers when not in correct format --- cmd/yggdrasil/main.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 5b756908..ec8c7125 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -12,6 +12,7 @@ import ( "os" "os/signal" "regexp" + "strings" "syscall" "time" @@ -188,6 +189,25 @@ func main() { } } } + // Check to see if the peers are in a parsable format, if not then default + // them to the TCP scheme + for index, peer := range dat["Peers"].([]interface{}) { + uri := peer.(string) + if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { + continue + } + (dat["Peers"].([]interface{}))[index] = "tcp://" + uri + } + // Now do the same with the interface peers + for intf, peers := range dat["InterfacePeers"].(map[string]interface{}) { + for index, peer := range peers.([]interface{}) { + uri := peer.(string) + if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { + continue + } + ((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri + } + } // Overlay our newly mapped configuration onto the autoconf node config that // we generated above. if err = mapstructure.Decode(dat, &cfg); err != nil { From 3ca5f10733fe86241fb185dcac0291bd5d30df94 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 14 Dec 2018 17:52:54 +0000 Subject: [PATCH 14/49] Don't try to correct peers with no schemes in addPeer --- src/yggdrasil/admin.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index e80ca398..577b6efd 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -515,13 +515,7 @@ func (a *admin) addPeer(addr string, sintf string) error { return errors.New("invalid peer: " + addr) } } else { - // no url scheme provided - addr = strings.ToLower(addr) - if strings.HasPrefix(addr, "tcp:") { - addr = addr[4:] - } - a.core.tcp.connect(addr, "") - return nil + return errors.New("invalid peer: " + addr) } return nil } From 1a7df477b011aafeb5425c16ee84087128c9fa54 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 14 Dec 2018 17:55:07 +0000 Subject: [PATCH 15/49] Also correct tcp: into tcp:// --- cmd/yggdrasil/main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index ec8c7125..ee5375be 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -196,6 +196,9 @@ func main() { if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { continue } + if strings.HasPrefix(uri, "tcp:") { + uri = uri[4:] + } (dat["Peers"].([]interface{}))[index] = "tcp://" + uri } // Now do the same with the interface peers @@ -205,6 +208,9 @@ func main() { if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { continue } + if strings.HasPrefix(uri, "tcp:") { + uri = uri[4:] + } ((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri } } From 9eaa2566c1d032d5769574f5825352568374c79f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 14 Dec 2018 18:08:13 +0000 Subject: [PATCH 16/49] Parameterise tun.init --- src/yggdrasil/router.go | 5 +---- src/yggdrasil/tun.go | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index eab18b1e..3b6d2043 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -80,13 +80,10 @@ func (r *router) init(core *Core) { send := make(chan []byte, 32) r.recv = recv r.send = send - r.tun.recv = recv - r.tun.send = send r.reset = make(chan struct{}, 1) r.admin = make(chan func(), 32) r.cryptokey.init(r.core) - r.tun.init(r.core) - // go r.mainLoop() + r.tun.init(r.core, send, recv) } // Starts the mainLoop goroutine. diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index e4625020..85204da5 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -36,9 +36,11 @@ func getSupportedMTU(mtu int) int { } // Initialises the TUN/TAP adapter. -func (tun *tunDevice) init(core *Core) { +func (tun *tunDevice) init(core *Core, send chan<- []byte, recv <-chan []byte) { tun.core = core tun.icmpv6.init(tun) + tun.send = send + tun.recv = recv } // Starts the setup process for the TUN/TAP adapter, and if successful, starts From 2a38ad07cde19d911ff2ee4a41fbb3c346f8f82f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 14 Dec 2018 18:08:40 +0000 Subject: [PATCH 17/49] Don't send ICMPv6 back when tun disabled --- src/yggdrasil/router.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 3b6d2043..d016d1c7 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -270,25 +270,6 @@ func (r *router) sendPacket(bs []byte) { // Drop packets if the session MTU is 0 - this means that one or other // side probably has their TUN adapter disabled if sinfo.getMTU() == 0 { - // Get the size of the oversized payload, up to a max of 900 bytes - window := 900 - if len(bs) < window { - window = len(bs) - } - - // Create the Destination Unreachable response - ptb := &icmp.DstUnreach{ - Data: bs[:window], - } - - // Create the ICMPv6 response from it - icmpv6Buf, err := r.tun.icmpv6.create_icmpv6_tun( - bs[8:24], bs[24:40], - ipv6.ICMPTypeDestinationUnreachable, 1, ptb) - if err == nil { - r.recv <- icmpv6Buf - } - // Don't continue - drop the packet return } From f28360ce4d5d56fe3965bfebe624a0077e53fc6c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 14 Dec 2018 18:10:39 +0000 Subject: [PATCH 18/49] Fix debug builds (foiled by debug builds every time) --- src/yggdrasil/debug.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index e463518d..e5bceee0 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -68,11 +68,11 @@ func (c *Core) DEBUG_getEncryptionPublicKey() boxPubKey { } func (c *Core) DEBUG_getSend() chan<- []byte { - return c.tun.send + return c.router.tun.send } func (c *Core) DEBUG_getRecv() <-chan []byte { - return c.tun.recv + return c.router.tun.recv } // Peer @@ -304,18 +304,18 @@ func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) { addr := c.DEBUG_getAddr() straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address_prefix)) if ifname != "none" { - err := c.tun.setup(ifname, iftapmode, straddr, mtu) + err := c.router.tun.setup(ifname, iftapmode, straddr, mtu) if err != nil { panic(err) } - c.log.Println("Setup TUN/TAP:", c.tun.iface.Name(), straddr) - go func() { panic(c.tun.read()) }() + c.log.Println("Setup TUN/TAP:", c.router.tun.iface.Name(), straddr) + go func() { panic(c.router.tun.read()) }() } - go func() { panic(c.tun.write()) }() + go func() { panic(c.router.tun.write()) }() } func (c *Core) DEBUG_stopTun() { - c.tun.close() + c.router.tun.close() } //////////////////////////////////////////////////////////////////////////////// @@ -546,7 +546,7 @@ func DEBUG_simLinkPeers(p, q *peer) { } func (c *Core) DEBUG_simFixMTU() { - c.tun.mtu = 65535 + c.router.tun.mtu = 65535 } //////////////////////////////////////////////////////////////////////////////// From 8045cb4dc3c5e165c848ce0ab4fe7c71bd99d615 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 14 Dec 2018 18:21:08 +0000 Subject: [PATCH 19/49] Define generic adapter type, rename tunDevice to tunAdapter --- src/yggdrasil/icmpv6.go | 4 ++-- src/yggdrasil/router.go | 7 +++++-- src/yggdrasil/tun.go | 16 +++++++--------- src/yggdrasil/tun_bsd.go | 4 ++-- src/yggdrasil/tun_darwin.go | 4 ++-- src/yggdrasil/tun_linux.go | 4 ++-- src/yggdrasil/tun_other.go | 4 ++-- src/yggdrasil/tun_windows.go | 6 +++--- 8 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/yggdrasil/icmpv6.go b/src/yggdrasil/icmpv6.go index 957b192e..6e9a265b 100644 --- a/src/yggdrasil/icmpv6.go +++ b/src/yggdrasil/icmpv6.go @@ -24,7 +24,7 @@ type macAddress [6]byte const len_ETHER = 14 type icmpv6 struct { - tun *tunDevice + tun *tunAdapter mylladdr net.IP mymac macAddress peermacs map[address]neighbor @@ -57,7 +57,7 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { // Initialises the ICMPv6 module by assigning our link-local IPv6 address and // our MAC address. ICMPv6 messages will always appear to originate from these // addresses. -func (i *icmpv6) init(t *tunDevice) { +func (i *icmpv6) init(t *tunAdapter) { i.tun = t i.peermacs = make(map[address]neighbor) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index d016d1c7..dd9f05d9 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -31,7 +31,9 @@ import ( ) type adapter struct { - tunDevice + core *Core + send chan<- []byte + recv <-chan []byte } // The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. @@ -43,7 +45,8 @@ type router struct { in <-chan []byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() - tun tunDevice // TUN/TAP adapter + tun tunAdapter // TUN/TAP adapter + adapters []adapter // Other adapters recv chan<- []byte // place where the tun pulls received packets from send <-chan []byte // place where the tun puts outgoing packets reset chan struct{} // signal that coords changed (re-init sessions/dht) diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 85204da5..223092e3 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -17,11 +17,9 @@ const tun_IPv6_HEADER_LENGTH = 40 const tun_ETHER_HEADER_LENGTH = 14 // Represents a running TUN/TAP interface. -type tunDevice struct { - core *Core +type tunAdapter struct { + adapter icmpv6 icmpv6 - send chan<- []byte - recv <-chan []byte mtu int iface *water.Interface } @@ -36,7 +34,7 @@ func getSupportedMTU(mtu int) int { } // Initialises the TUN/TAP adapter. -func (tun *tunDevice) init(core *Core, send chan<- []byte, recv <-chan []byte) { +func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) { tun.core = core tun.icmpv6.init(tun) tun.send = send @@ -45,7 +43,7 @@ func (tun *tunDevice) init(core *Core, send chan<- []byte, recv <-chan []byte) { // Starts the setup process for the TUN/TAP adapter, and if successful, starts // the read/write goroutines to handle packets on that interface. -func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) start(ifname string, iftapmode bool, addr string, mtu int) error { if ifname == "none" { return nil } @@ -77,7 +75,7 @@ func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int) // Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP // mode then additional ethernet encapsulation is added for the benefit of the // host operating system. -func (tun *tunDevice) write() error { +func (tun *tunAdapter) write() error { for { data := <-tun.recv if tun.iface == nil { @@ -166,7 +164,7 @@ func (tun *tunDevice) write() error { // is running in TAP mode then the ethernet headers will automatically be // processed and stripped if necessary. If an ICMPv6 packet is found, then // the relevant helper functions in icmpv6.go are called. -func (tun *tunDevice) read() error { +func (tun *tunAdapter) read() error { mtu := tun.mtu if tun.iface.IsTAP() { mtu += tun_ETHER_HEADER_LENGTH @@ -203,7 +201,7 @@ func (tun *tunDevice) read() error { // Closes the TUN/TAP adapter. This is only usually called when the Yggdrasil // process stops. Typically this operation will happen quickly, but on macOS // it can block until a read operation is completed. -func (tun *tunDevice) close() error { +func (tun *tunAdapter) close() error { if tun.iface == nil { return nil } diff --git a/src/yggdrasil/tun_bsd.go b/src/yggdrasil/tun_bsd.go index ca5eaea8..620c79db 100644 --- a/src/yggdrasil/tun_bsd.go +++ b/src/yggdrasil/tun_bsd.go @@ -77,7 +77,7 @@ type in6_ifreq_lifetime struct { // a system socket and making syscalls to the kernel. This is not refined though // and often doesn't work (if at all), therefore if a call fails, it resorts // to calling "ifconfig" instead. -func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { var config water.Config if ifname[:4] == "auto" { ifname = "/dev/tap0" @@ -103,7 +103,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) return tun.setupAddress(addr) } -func (tun *tunDevice) setupAddress(addr string) error { +func (tun *tunAdapter) setupAddress(addr string) error { var sfd int var err error diff --git a/src/yggdrasil/tun_darwin.go b/src/yggdrasil/tun_darwin.go index e49ab528..943468e6 100644 --- a/src/yggdrasil/tun_darwin.go +++ b/src/yggdrasil/tun_darwin.go @@ -14,7 +14,7 @@ import ( ) // Configures the "utun" adapter with the correct IPv6 address and MTU. -func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if iftapmode { tun.core.log.Printf("TAP mode is not supported on this platform, defaulting to TUN") } @@ -62,7 +62,7 @@ type ifreq struct { // Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using // a system socket and making direct syscalls to the kernel. -func (tun *tunDevice) setupAddress(addr string) error { +func (tun *tunAdapter) setupAddress(addr string) error { var fd int var err error diff --git a/src/yggdrasil/tun_linux.go b/src/yggdrasil/tun_linux.go index aa9e7914..7a7c9cb7 100644 --- a/src/yggdrasil/tun_linux.go +++ b/src/yggdrasil/tun_linux.go @@ -13,7 +13,7 @@ import ( ) // Configures the TAP adapter with the correct IPv6 address and MTU. -func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { var config water.Config if iftapmode { config = water.Config{DeviceType: water.TAP} @@ -48,7 +48,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) // is used to do this, so there is not a hard requirement on "ip" or "ifconfig" // to exist on the system, but this will fail if Netlink is not present in the // kernel (it nearly always is). -func (tun *tunDevice) setupAddress(addr string) error { +func (tun *tunAdapter) setupAddress(addr string) error { // Set address var netIF *net.Interface ifces, err := net.Interfaces() diff --git a/src/yggdrasil/tun_other.go b/src/yggdrasil/tun_other.go index 1a3721ac..625f9cd5 100644 --- a/src/yggdrasil/tun_other.go +++ b/src/yggdrasil/tun_other.go @@ -9,7 +9,7 @@ import water "github.com/yggdrasil-network/water" // Creates the TUN/TAP adapter, if supported by the Water library. Note that // no guarantees are made at this point on an unsupported platform. -func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { var config water.Config if iftapmode { config = water.Config{DeviceType: water.TAP} @@ -27,7 +27,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. -func (tun *tunDevice) setupAddress(addr string) error { +func (tun *tunAdapter) setupAddress(addr string) error { tun.core.log.Println("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) return nil } diff --git a/src/yggdrasil/tun_windows.go b/src/yggdrasil/tun_windows.go index d3420df8..150a9766 100644 --- a/src/yggdrasil/tun_windows.go +++ b/src/yggdrasil/tun_windows.go @@ -13,7 +13,7 @@ import ( // Configures the TAP adapter with the correct IPv6 address and MTU. On Windows // we don't make use of a direct operating system API to do this - we instead // delegate the hard work to "netsh". -func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if !iftapmode { tun.core.log.Printf("TUN mode is not supported on this platform, defaulting to TAP") } @@ -65,7 +65,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) } // Sets the MTU of the TAP adapter. -func (tun *tunDevice) setupMTU(mtu int) error { +func (tun *tunAdapter) setupMTU(mtu int) error { // Set MTU cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface", fmt.Sprintf("interface=%s", tun.iface.Name()), @@ -82,7 +82,7 @@ func (tun *tunDevice) setupMTU(mtu int) error { } // Sets the IPv6 address of the TAP adapter. -func (tun *tunDevice) setupAddress(addr string) error { +func (tun *tunAdapter) setupAddress(addr string) error { // Set address cmd := exec.Command("netsh", "interface", "ipv6", "add", "address", fmt.Sprintf("interface=%s", tun.iface.Name()), From f9dc300787a3a871144585b813610d6776dd6fc0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 14 Dec 2018 18:29:00 +0000 Subject: [PATCH 20/49] Define Adapter base type/interface --- src/yggdrasil/adapter.go | 25 +++++++++++++++++++++++++ src/yggdrasil/router.go | 10 ++-------- src/yggdrasil/tun.go | 6 ++---- 3 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 src/yggdrasil/adapter.go diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go new file mode 100644 index 00000000..4a432092 --- /dev/null +++ b/src/yggdrasil/adapter.go @@ -0,0 +1,25 @@ +package yggdrasil + +// Defines the minimum required functions for an adapter type. +type AdapterInterface interface { + init(core *Core, send chan<- []byte, recv <-chan []byte) + read() error + write() error + close() error +} + +// Defines the minimum required struct members for an adapter type (this is +// now the base type for tunAdapter in tun.go) +type Adapter struct { + AdapterInterface + core *Core + send chan<- []byte + recv <-chan []byte +} + +// Initialises the adapter. +func (adapter *Adapter) init(core *Core, send chan<- []byte, recv <-chan []byte) { + adapter.core = core + adapter.send = send + adapter.recv = recv +} diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index dd9f05d9..62480bc4 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -30,12 +30,6 @@ import ( "golang.org/x/net/ipv6" ) -type adapter struct { - core *Core - send chan<- []byte - recv <-chan []byte -} - // The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. // The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. type router struct { @@ -45,8 +39,8 @@ type router struct { in <-chan []byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() - tun tunAdapter // TUN/TAP adapter - adapters []adapter // Other adapters + tun tunAdapter // TUN/TAP adapter + adapters []Adapter // Other adapters recv chan<- []byte // place where the tun pulls received packets from send <-chan []byte // place where the tun puts outgoing packets reset chan struct{} // signal that coords changed (re-init sessions/dht) diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 223092e3..26d32c0b 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -18,7 +18,7 @@ const tun_ETHER_HEADER_LENGTH = 14 // Represents a running TUN/TAP interface. type tunAdapter struct { - adapter + Adapter icmpv6 icmpv6 mtu int iface *water.Interface @@ -35,10 +35,8 @@ func getSupportedMTU(mtu int) int { // Initialises the TUN/TAP adapter. func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) { - tun.core = core + tun.Adapter.init(core, send, recv) tun.icmpv6.init(tun) - tun.send = send - tun.recv = recv } // Starts the setup process for the TUN/TAP adapter, and if successful, starts From abd8b69979eb846dc12b6b7bcea51aebdf8065f4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 14 Dec 2018 18:15:35 -0600 Subject: [PATCH 21/49] send a switch message immediately when peering, and use OS-level TCP keep-alive (shouldn't matter right now, since we have application-level keep-alive that preempts it, but important later) --- src/yggdrasil/peer.go | 9 ++++----- src/yggdrasil/tcp.go | 24 +++++++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 67aa805a..e092513b 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -177,6 +177,7 @@ func (p *peer) doSendSwitchMsgs() { func (p *peer) linkLoop() { tick := time.NewTicker(time.Second) defer tick.Stop() + p.doSendSwitchMsgs() for { select { case _, ok := <-p.doSend: @@ -185,11 +186,9 @@ func (p *peer) linkLoop() { } p.sendSwitchMsg() case _ = <-tick.C: - //break // FIXME disabled the below completely to test something - pdinfo := p.dinfo // FIXME this is a bad workarond NPE on the next line - if pdinfo != nil { - dinfo := *pdinfo - p.core.dht.peers <- &dinfo + dinfo := p.dinfo // FIXME? are pointer reads *always* atomic? + if dinfo != nil { + p.core.dht.peers <- dinfo } } } diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 5ca66304..5d4bfc71 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -31,14 +31,6 @@ const tcp_msgSize = 2048 + 65535 // TODO figure out what makes sense const default_tcp_timeout = 6 * time.Second const tcp_ping_interval = (default_tcp_timeout * 2 / 3) -// Wrapper function for non tcp/ip connections. -func setNoDelay(c net.Conn, delay bool) { - tcp, ok := c.(*net.TCPConn) - if ok { - tcp.SetNoDelay(delay) - } -} - // The TCP listener and information about active TCP connections, to avoid duplication. type tcpInterface struct { core *Core @@ -58,6 +50,20 @@ type tcpInfo struct { remoteAddr string } +// Wrapper function to set additional options for specific connection types. +func (iface *tcpInterface) setExtraOptions(c net.Conn) { + switch sock := c.(type) { + case *net.TCPConn: + sock.SetNoDelay(true) + sock.SetKeepAlive(true) + sock.SetKeepAlivePeriod(iface.tcp_timeout) + panic("DEBUG testing") + // TODO something for socks5 + default: + iface.core.log.Println("Unrecognized connection type: %v", sock) + } +} + // Returns the address of the listener. func (iface *tcpInterface) getAddr() *net.TCPAddr { return iface.serv.Addr().(*net.TCPAddr) @@ -205,6 +211,7 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) { // It defers a bunch of cleanup stuff to tear down all of these things when the reader exists (e.g. due to a closed connection or a timeout). func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { defer sock.Close() + iface.setExtraOptions(sock) // Get our keys myLinkPub, myLinkPriv := newBoxKeys() // ephemeral link keys meta := version_getBaseMetadata() @@ -342,7 +349,6 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { out <- msg } p.close = func() { sock.Close() } - setNoDelay(sock, true) go p.linkLoop() defer func() { // Put all of our cleanup here... From 2c68d41409aebd24f4b3a96ca124d1973a73ba88 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 14 Dec 2018 18:30:36 -0600 Subject: [PATCH 22/49] move files, still need to fix exports and compile errors --- src/{yggdrasil => address}/address.go | 0 src/{yggdrasil => crypto}/crypto.go | 0 src/{yggdrasil => util}/util.go | 0 src/yggdrasil/core.go | 2 ++ 4 files changed, 2 insertions(+) rename src/{yggdrasil => address}/address.go (100%) rename src/{yggdrasil => crypto}/crypto.go (100%) rename src/{yggdrasil => util}/util.go (100%) diff --git a/src/yggdrasil/address.go b/src/address/address.go similarity index 100% rename from src/yggdrasil/address.go rename to src/address/address.go diff --git a/src/yggdrasil/crypto.go b/src/crypto/crypto.go similarity index 100% rename from src/yggdrasil/crypto.go rename to src/crypto/crypto.go diff --git a/src/yggdrasil/util.go b/src/util/util.go similarity index 100% rename from src/yggdrasil/util.go rename to src/util/util.go diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 13609ce6..bf425483 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -8,7 +8,9 @@ import ( "net" "regexp" + "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) From d5031a5cb610cd755039fbf68d013418f77fcb74 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 00:48:27 +0000 Subject: [PATCH 23/49] 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 266d5a89..1bf6af03 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 72a11d72..7a3564d1 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 00000000..7f607ed5 --- /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 4914a6b8..e6f6b0fe 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 31fe375b..418c46a8 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 2d87a37f..d46994de 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 ea4ca02681a0f17c84ca536eb360e4f30b4cf09a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 14 Dec 2018 20:49:18 -0600 Subject: [PATCH 24/49] fix code after moving address/crypto/util --- src/address/address.go | 76 +++++++++++--------- src/crypto/crypto.go | 120 ++++++++++++++++++------------- src/util/util.go | 19 +++-- src/yggdrasil/admin.go | 20 +++--- src/yggdrasil/ckr.go | 43 +++++------ src/yggdrasil/core.go | 47 ++++++------ src/yggdrasil/debug.go | 64 +++++++++-------- src/yggdrasil/dht.go | 46 ++++++------ src/yggdrasil/icmpv6.go | 20 +++--- src/yggdrasil/peer.go | 47 ++++++------ src/yggdrasil/router.go | 84 +++++++++++----------- src/yggdrasil/search.go | 28 ++++---- src/yggdrasil/session.go | 150 ++++++++++++++++++--------------------- src/yggdrasil/switch.go | 53 +++++++------- src/yggdrasil/tcp.go | 22 +++--- src/yggdrasil/tun.go | 10 +-- src/yggdrasil/version.go | 20 +++--- src/yggdrasil/wire.go | 21 +++--- 18 files changed, 469 insertions(+), 421 deletions(-) diff --git a/src/address/address.go b/src/address/address.go index dd2c410a..5c13257b 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -1,21 +1,26 @@ -package yggdrasil +package address + +import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" // address represents an IPv6 address in the yggdrasil address range. -type address [16]byte +type Address [16]byte // subnet represents an IPv6 /64 subnet in the yggdrasil subnet range. -type subnet [8]byte +type Subnet [8]byte // address_prefix is the prefix used for all addresses and subnets in the network. // The current implementation requires this to be a muliple of 8 bits + 7 bits. // The 8th bit of the last byte is used to signal nodes (0) or /64 prefixes (1). // Nodes that configure this differently will be unable to communicate with eachother, though routing and the DHT machinery *should* still work. -var address_prefix = [...]byte{0x02} +func GetPrefix() [1]byte { + return [...]byte{0x02} +} // isValid returns true if an address falls within the range used by nodes in the network. -func (a *address) isValid() bool { - for idx := range address_prefix { - if (*a)[idx] != address_prefix[idx] { +func (a *Address) IsValid() bool { + prefix := GetPrefix() + for idx := range prefix { + if (*a)[idx] != prefix[idx] { return false } } @@ -23,28 +28,29 @@ func (a *address) isValid() bool { } // isValid returns true if a prefix falls within the range usable by the network. -func (s *subnet) isValid() bool { - l := len(address_prefix) - for idx := range address_prefix[:l-1] { - if (*s)[idx] != address_prefix[idx] { +func (s *Subnet) IsValid() bool { + prefix := GetPrefix() + l := len(prefix) + for idx := range prefix[:l-1] { + if (*s)[idx] != prefix[idx] { return false } } - return (*s)[l-1] == address_prefix[l-1]|0x01 + return (*s)[l-1] == prefix[l-1]|0x01 } // address_addrForNodeID takes a *NodeID as an argument and returns an *address. // This subnet begins with the address prefix, with the last bit set to 0 to indicate an address. // The following 8 bits are set to the number of leading 1 bits in the NodeID. // The NodeID, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the address. -func address_addrForNodeID(nid *NodeID) *address { +func AddrForNodeID(nid *crypto.NodeID) *Address { // 128 bit address // Begins with prefix // Next bit is a 0 // Next 7 bits, interpreted as a uint, are # of leading 1s in the NodeID // Leading 1s and first leading 0 of the NodeID are truncated off // The rest is appended to the IPv6 address (truncated to 128 bits total) - var addr address + var addr Address var temp []byte done := false ones := byte(0) @@ -67,9 +73,10 @@ func address_addrForNodeID(nid *NodeID) *address { temp = append(temp, bits) } } - copy(addr[:], address_prefix[:]) - addr[len(address_prefix)] = ones - copy(addr[len(address_prefix)+1:], temp) + prefix := GetPrefix() + copy(addr[:], prefix[:]) + addr[len(prefix)] = ones + copy(addr[len(prefix)+1:], temp) return &addr } @@ -77,14 +84,15 @@ func address_addrForNodeID(nid *NodeID) *address { // This subnet begins with the address prefix, with the last bit set to 1 to indicate a prefix. // The following 8 bits are set to the number of leading 1 bits in the NodeID. // The NodeID, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the subnet. -func address_subnetForNodeID(nid *NodeID) *subnet { +func SubnetForNodeID(nid *crypto.NodeID) *Subnet { // Exactly as the address version, with two exceptions: // 1) The first bit after the fixed prefix is a 1 instead of a 0 // 2) It's truncated to a subnet prefix length instead of 128 bits - addr := *address_addrForNodeID(nid) - var snet subnet + addr := *AddrForNodeID(nid) + var snet Subnet copy(snet[:], addr[:]) - snet[len(address_prefix)-1] |= 0x01 + prefix := GetPrefix() + snet[len(prefix)-1] |= 0x01 return &snet } @@ -92,17 +100,18 @@ func address_subnetForNodeID(nid *NodeID) *subnet { // The first is a NodeID with all the bits known from the address set to their correct values. // The second is a bitmask with 1 bit set for each bit that was known from the address. // This is used to look up NodeIDs in the DHT and tell if they match an address. -func (a *address) getNodeIDandMask() (*NodeID, *NodeID) { +func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { // Mask is a bitmask to mark the bits visible from the address // This means truncated leading 1s, first leading 0, and visible part of addr - var nid NodeID - var mask NodeID - ones := int(a[len(address_prefix)]) + var nid crypto.NodeID + var mask crypto.NodeID + prefix := GetPrefix() + ones := int(a[len(prefix)]) for idx := 0; idx < ones; idx++ { nid[idx/8] |= 0x80 >> byte(idx%8) } nidOffset := ones + 1 - addrOffset := 8*len(address_prefix) + 8 + addrOffset := 8*len(prefix) + 8 for idx := addrOffset; idx < 8*len(a); idx++ { bits := a[idx/8] & (0x80 >> byte(idx%8)) bits <<= byte(idx % 8) @@ -110,7 +119,7 @@ func (a *address) getNodeIDandMask() (*NodeID, *NodeID) { bits >>= byte(nidIdx % 8) nid[nidIdx/8] |= bits } - maxMask := 8*(len(a)-len(address_prefix)-1) + ones + 1 + maxMask := 8*(len(a)-len(prefix)-1) + ones + 1 for idx := 0; idx < maxMask; idx++ { mask[idx/8] |= 0x80 >> byte(idx%8) } @@ -121,16 +130,17 @@ func (a *address) getNodeIDandMask() (*NodeID, *NodeID) { // The first is a NodeID with all the bits known from the address set to their correct values. // The second is a bitmask with 1 bit set for each bit that was known from the subnet. // This is used to look up NodeIDs in the DHT and tell if they match a subnet. -func (s *subnet) getNodeIDandMask() (*NodeID, *NodeID) { +func (s *Subnet) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { // As with the address version, but visible parts of the subnet prefix instead - var nid NodeID - var mask NodeID - ones := int(s[len(address_prefix)]) + var nid crypto.NodeID + var mask crypto.NodeID + prefix := GetPrefix() + ones := int(s[len(prefix)]) for idx := 0; idx < ones; idx++ { nid[idx/8] |= 0x80 >> byte(idx%8) } nidOffset := ones + 1 - addrOffset := 8*len(address_prefix) + 8 + addrOffset := 8*len(prefix) + 8 for idx := addrOffset; idx < 8*len(s); idx++ { bits := s[idx/8] & (0x80 >> byte(idx%8)) bits <<= byte(idx % 8) @@ -138,7 +148,7 @@ func (s *subnet) getNodeIDandMask() (*NodeID, *NodeID) { bits >>= byte(nidIdx % 8) nid[nidIdx/8] |= bits } - maxMask := 8*(len(s)-len(address_prefix)-1) + ones + 1 + maxMask := 8*(len(s)-len(prefix)-1) + ones + 1 for idx := 0; idx < maxMask; idx++ { mask[idx/8] |= 0x80 >> byte(idx%8) } diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index efca41d4..c974a3c0 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -1,4 +1,4 @@ -package yggdrasil +package crypto /* @@ -16,6 +16,8 @@ import ( "golang.org/x/crypto/ed25519" "golang.org/x/crypto/nacl/box" + + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) //////////////////////////////////////////////////////////////////////////////// @@ -28,20 +30,20 @@ const handleLen = 8 type NodeID [NodeIDLen]byte type TreeID [TreeIDLen]byte -type handle [handleLen]byte +type Handle [handleLen]byte -func getNodeID(pub *boxPubKey) *NodeID { +func GetNodeID(pub *BoxPubKey) *NodeID { h := sha512.Sum512(pub[:]) return (*NodeID)(&h) } -func getTreeID(pub *sigPubKey) *TreeID { +func GetTreeID(pub *SigPubKey) *TreeID { h := sha512.Sum512(pub[:]) return (*TreeID)(&h) } -func newHandle() *handle { - var h handle +func NewHandle() *Handle { + var h Handle _, err := rand.Read(h[:]) if err != nil { panic(err) @@ -53,17 +55,17 @@ func newHandle() *handle { // Signatures -const sigPubKeyLen = ed25519.PublicKeySize -const sigPrivKeyLen = ed25519.PrivateKeySize -const sigLen = ed25519.SignatureSize +const SigPubKeyLen = ed25519.PublicKeySize +const SigPrivKeyLen = ed25519.PrivateKeySize +const SigLen = ed25519.SignatureSize -type sigPubKey [sigPubKeyLen]byte -type sigPrivKey [sigPrivKeyLen]byte -type sigBytes [sigLen]byte +type SigPubKey [SigPubKeyLen]byte +type SigPrivKey [SigPrivKeyLen]byte +type SigBytes [SigLen]byte -func newSigKeys() (*sigPubKey, *sigPrivKey) { - var pub sigPubKey - var priv sigPrivKey +func NewSigKeys() (*SigPubKey, *SigPrivKey) { + var pub SigPubKey + var priv SigPrivKey pubSlice, privSlice, err := ed25519.GenerateKey(rand.Reader) if err != nil { panic(err) @@ -73,14 +75,14 @@ func newSigKeys() (*sigPubKey, *sigPrivKey) { return &pub, &priv } -func sign(priv *sigPrivKey, msg []byte) *sigBytes { - var sig sigBytes +func Sign(priv *SigPrivKey, msg []byte) *SigBytes { + var sig SigBytes sigSlice := ed25519.Sign(priv[:], msg) copy(sig[:], sigSlice) return &sig } -func verify(pub *sigPubKey, msg []byte, sig *sigBytes) bool { +func Verify(pub *SigPubKey, msg []byte, sig *SigBytes) bool { // Should sig be an array instead of a slice?... // It's fixed size, but return ed25519.Verify(pub[:], msg, sig[:]) @@ -90,60 +92,60 @@ func verify(pub *sigPubKey, msg []byte, sig *sigBytes) bool { // NaCl-like crypto "box" (curve25519+xsalsa20+poly1305) -const boxPubKeyLen = 32 -const boxPrivKeyLen = 32 -const boxSharedKeyLen = 32 -const boxNonceLen = 24 -const boxOverhead = box.Overhead +const BoxPubKeyLen = 32 +const BoxPrivKeyLen = 32 +const BoxSharedKeyLen = 32 +const BoxNonceLen = 24 +const BoxOverhead = box.Overhead -type boxPubKey [boxPubKeyLen]byte -type boxPrivKey [boxPrivKeyLen]byte -type boxSharedKey [boxSharedKeyLen]byte -type boxNonce [boxNonceLen]byte +type BoxPubKey [BoxPubKeyLen]byte +type BoxPrivKey [BoxPrivKeyLen]byte +type BoxSharedKey [BoxSharedKeyLen]byte +type BoxNonce [BoxNonceLen]byte -func newBoxKeys() (*boxPubKey, *boxPrivKey) { +func NewBoxKeys() (*BoxPubKey, *BoxPrivKey) { pubBytes, privBytes, err := box.GenerateKey(rand.Reader) if err != nil { panic(err) } - pub := (*boxPubKey)(pubBytes) - priv := (*boxPrivKey)(privBytes) + pub := (*BoxPubKey)(pubBytes) + priv := (*BoxPrivKey)(privBytes) return pub, priv } -func getSharedKey(myPrivKey *boxPrivKey, - othersPubKey *boxPubKey) *boxSharedKey { - var shared [boxSharedKeyLen]byte - priv := (*[boxPrivKeyLen]byte)(myPrivKey) - pub := (*[boxPubKeyLen]byte)(othersPubKey) +func GetSharedKey(myPrivKey *BoxPrivKey, + othersPubKey *BoxPubKey) *BoxSharedKey { + var shared [BoxSharedKeyLen]byte + priv := (*[BoxPrivKeyLen]byte)(myPrivKey) + pub := (*[BoxPubKeyLen]byte)(othersPubKey) box.Precompute(&shared, pub, priv) - return (*boxSharedKey)(&shared) + return (*BoxSharedKey)(&shared) } -func boxOpen(shared *boxSharedKey, +func BoxOpen(shared *BoxSharedKey, boxed []byte, - nonce *boxNonce) ([]byte, bool) { - out := util_getBytes() - s := (*[boxSharedKeyLen]byte)(shared) - n := (*[boxNonceLen]byte)(nonce) + nonce *BoxNonce) ([]byte, bool) { + out := util.GetBytes() + s := (*[BoxSharedKeyLen]byte)(shared) + n := (*[BoxNonceLen]byte)(nonce) unboxed, success := box.OpenAfterPrecomputation(out, boxed, n, s) return unboxed, success } -func boxSeal(shared *boxSharedKey, unboxed []byte, nonce *boxNonce) ([]byte, *boxNonce) { +func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *BoxNonce) { if nonce == nil { - nonce = newBoxNonce() + nonce = NewBoxNonce() } - nonce.update() - out := util_getBytes() - s := (*[boxSharedKeyLen]byte)(shared) - n := (*[boxNonceLen]byte)(nonce) + nonce.Increment() + out := util.GetBytes() + s := (*[BoxSharedKeyLen]byte)(shared) + n := (*[BoxNonceLen]byte)(nonce) boxed := box.SealAfterPrecomputation(out, unboxed, n, s) return boxed, nonce } -func newBoxNonce() *boxNonce { - var nonce boxNonce +func NewBoxNonce() *BoxNonce { + var nonce BoxNonce _, err := rand.Read(nonce[:]) for ; err == nil && nonce[0] == 0xff; _, err = rand.Read(nonce[:]) { // Make sure nonce isn't too high @@ -156,7 +158,7 @@ func newBoxNonce() *boxNonce { return &nonce } -func (n *boxNonce) update() { +func (n *BoxNonce) Increment() { oldNonce := *n n[len(n)-1] += 2 for i := len(n) - 2; i >= 0; i-- { @@ -165,3 +167,21 @@ func (n *boxNonce) update() { } } } + +// Used to subtract one nonce from another, staying in the range +- 64. +// This is used by the nonce progression machinery to advance the bitmask of recently received packets (indexed by nonce), or to check the appropriate bit of the bitmask. +// It's basically part of the machinery that prevents replays and duplicate packets. +func (n *BoxNonce) Minus(m *BoxNonce) int64 { + diff := int64(0) + for idx := range n { + diff *= 256 + diff += int64(n[idx]) - int64(m[idx]) + if diff > 64 { + diff = 64 + } + if diff < -64 { + diff = -64 + } + } + return diff +} diff --git a/src/util/util.go b/src/util/util.go index c9897eda..65e6d463 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -1,21 +1,21 @@ -package yggdrasil +package util // These are misc. utility functions that didn't really fit anywhere else import "runtime" // A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere. -func util_yield() { +func Yield() { runtime.Gosched() } // A wrapper around runtime.LockOSThread() so it doesn't need to be imported elsewhere. -func util_lockthread() { +func LockThread() { runtime.LockOSThread() } // A wrapper around runtime.UnlockOSThread() so it doesn't need to be imported elsewhere. -func util_unlockthread() { +func UnlockThread() { runtime.UnlockOSThread() } @@ -23,15 +23,12 @@ func util_unlockthread() { // It's used like a sync.Pool, but with a fixed size and typechecked without type casts to/from interface{} (which were making the profiles look ugly). var byteStore chan []byte -// Initializes the byteStore -func util_initByteStore() { - if byteStore == nil { - byteStore = make(chan []byte, 32) - } +func init() { + byteStore = make(chan []byte, 32) } // Gets an empty slice from the byte store, if one is available, or else returns a new nil slice. -func util_getBytes() []byte { +func GetBytes() []byte { select { case bs := <-byteStore: return bs[:0] @@ -41,7 +38,7 @@ func util_getBytes() []byte { } // Puts a slice in the store, if there's room, or else returns and lets the slice get collected. -func util_putBytes(bs []byte) { +func PutBytes(bs []byte) { select { case byteStore <- bs: default: diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 577b6efd..1f77050d 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -14,6 +14,8 @@ import ( "sync/atomic" "time" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) @@ -314,7 +316,7 @@ func (a *admin) init(c *Core, listenaddr string) { "box_pub_key": hex.EncodeToString(dinfo.key[:]), "coords": fmt.Sprintf("%v", dinfo.coords), } - addr := net.IP(address_addrForNodeID(getNodeID(&dinfo.key))[:]).String() + addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.key))[:]).String() infos[addr] = info } return admin_info{"nodes": infos}, nil @@ -536,7 +538,7 @@ func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error _ = a.core.router.tun.close() // Then reconfigure and start it addr := a.core.router.addr - straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address_prefix)-1) + straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address.GetPrefix())-1) if ifname != "none" { err := a.core.router.tun.setup(ifname, iftapmode, straddr, ifmtu) if err != nil { @@ -590,7 +592,7 @@ func (a *admin) getData_getPeers() []admin_nodeInfo { sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] }) for _, port := range ps { p := ports[port] - addr := *address_addrForNodeID(getNodeID(&p.box)) + addr := *address.AddrForNodeID(crypto.GetNodeID(&p.box)) info := admin_nodeInfo{ {"ip", net.IP(addr[:]).String()}, {"port", port}, @@ -615,7 +617,7 @@ func (a *admin) getData_getSwitchPeers() []admin_nodeInfo { if !isIn { continue } - addr := *address_addrForNodeID(getNodeID(&peer.box)) + addr := *address.AddrForNodeID(crypto.GetNodeID(&peer.box)) coords := elem.locator.getCoords() info := admin_nodeInfo{ {"ip", net.IP(addr[:]).String()}, @@ -673,7 +675,7 @@ func (a *admin) getData_getDHT() []admin_nodeInfo { return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID()) }) for _, v := range dhtInfos { - addr := *address_addrForNodeID(v.getNodeID()) + addr := *address.AddrForNodeID(v.getNodeID()) info := admin_nodeInfo{ {"ip", net.IP(addr[:]).String()}, {"coords", fmt.Sprint(v.coords)}, @@ -723,7 +725,7 @@ func (a *admin) getAllowedEncryptionPublicKeys() []string { func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) { boxBytes, err := hex.DecodeString(bstr) if err == nil { - var box boxPubKey + var box crypto.BoxPubKey copy(box[:], boxBytes) a.core.peers.addAllowedEncryptionPublicKey(&box) } @@ -735,7 +737,7 @@ func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) { func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) { boxBytes, err := hex.DecodeString(bstr) if err == nil { - var box boxPubKey + var box crypto.BoxPubKey copy(box[:], boxBytes) a.core.peers.removeAllowedEncryptionPublicKey(&box) } @@ -744,7 +746,7 @@ func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) { // Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) { - var key boxPubKey + var key crypto.BoxPubKey if keyBytes, err := hex.DecodeString(keyString); err != nil { return dhtRes{}, err } else { @@ -775,7 +777,7 @@ func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtR } else if len(targetBytes) != len(target) { return dhtRes{}, errors.New("Incorrect target NodeID length") } else { - target = NodeID{} + var target crypto.NodeID copy(target[:], targetBytes) } rq := dhtReqKey{info.key, target} diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index d88b8d3c..a3df891e 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -7,6 +7,9 @@ import ( "fmt" "net" "sort" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) // This module implements crypto-key routing, similar to Wireguard, where we @@ -17,15 +20,15 @@ type cryptokey struct { enabled bool ipv4routes []cryptokey_route ipv6routes []cryptokey_route - ipv4cache map[address]cryptokey_route - ipv6cache map[address]cryptokey_route + ipv4cache map[address.Address]cryptokey_route + ipv6cache map[address.Address]cryptokey_route ipv4sources []net.IPNet ipv6sources []net.IPNet } type cryptokey_route struct { subnet net.IPNet - destination boxPubKey + destination crypto.BoxPubKey } // Initialise crypto-key routing. This must be done before any other CKR calls. @@ -33,8 +36,8 @@ func (c *cryptokey) init(core *Core) { c.core = core c.ipv4routes = make([]cryptokey_route, 0) c.ipv6routes = make([]cryptokey_route, 0) - c.ipv4cache = make(map[address]cryptokey_route, 0) - c.ipv6cache = make(map[address]cryptokey_route, 0) + c.ipv4cache = make(map[address.Address]cryptokey_route, 0) + c.ipv6cache = make(map[address.Address]cryptokey_route, 0) c.ipv4sources = make([]net.IPNet, 0) c.ipv6sources = make([]net.IPNet, 0) } @@ -52,7 +55,7 @@ func (c *cryptokey) isEnabled() bool { // Check whether the given address (with the address length specified in bytes) // matches either the current node's address, the node's routed subnet or the // list of subnets specified in IPv4Sources/IPv6Sources. -func (c *cryptokey) isValidSource(addr address, addrlen int) bool { +func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { ip := net.IP(addr[:addrlen]) if addrlen == net.IPv6len { @@ -143,7 +146,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { // Build our references to the routing table and cache var routingtable *[]cryptokey_route - var routingcache *map[address]cryptokey_route + var routingcache *map[address.Address]cryptokey_route // Check if the prefix is IPv4 or IPv6 if prefixsize == net.IPv6len*8 { @@ -157,11 +160,11 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { } // Is the route an Yggdrasil destination? - var addr address - var snet subnet + var addr address.Address + var snet address.Subnet copy(addr[:], ipaddr) copy(snet[:], ipnet.IP) - if addr.isValid() || snet.isValid() { + if addr.IsValid() || snet.IsValid() { return errors.New("Can't specify Yggdrasil destination as crypto-key route") } // Do we already have a route for this subnet? @@ -173,11 +176,11 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { // Decode the public key if bpk, err := hex.DecodeString(dest); err != nil { return err - } else if len(bpk) != boxPubKeyLen { + } else if len(bpk) != crypto.BoxPubKeyLen { return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) } else { // Add the new crypto-key route - var key boxPubKey + var key crypto.BoxPubKey copy(key[:], bpk) *routingtable = append(*routingtable, cryptokey_route{ subnet: *ipnet, @@ -205,16 +208,16 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { // Looks up the most specific route for the given address (with the address // length specified in bytes) from the crypto-key routing table. An error is // returned if the address is not suitable or no route was found. -func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey, error) { +func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) { // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking - if addr.isValid() { - return boxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses") + if addr.IsValid() { + return crypto.BoxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses") } // Build our references to the routing table and cache var routingtable *[]cryptokey_route - var routingcache *map[address]cryptokey_route + var routingcache *map[address.Address]cryptokey_route // Check if the prefix is IPv4 or IPv6 if addrlen == net.IPv6len { @@ -224,7 +227,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey routingtable = &c.ipv4routes routingcache = &c.ipv4cache } else { - return boxPubKey{}, errors.New("Unexpected prefix size") + return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") } // Check if there's a cache entry for this addr @@ -260,7 +263,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey } // No route was found if we got to this point - return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String())) + return crypto.BoxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String())) } // Removes a source subnet, which allows traffic with these source addresses to @@ -312,7 +315,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error { // Build our references to the routing table and cache var routingtable *[]cryptokey_route - var routingcache *map[address]cryptokey_route + var routingcache *map[address.Address]cryptokey_route // Check if the prefix is IPv4 or IPv6 if prefixsize == net.IPv6len*8 { @@ -329,7 +332,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error { bpk, err := hex.DecodeString(dest) if err != nil { return err - } else if len(bpk) != boxPubKeyLen { + } else if len(bpk) != crypto.BoxPubKeyLen { return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) } netStr := ipnet.String() diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index bf425483..2f9c451c 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -21,10 +21,10 @@ var buildVersion string // 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 + boxPub crypto.BoxPubKey + boxPriv crypto.BoxPrivKey + sigPub crypto.SigPubKey + sigPriv crypto.SigPrivKey switchTable switchTable peers peers sessions sessions @@ -38,15 +38,14 @@ type Core struct { 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) { +func (c *Core) init(bpub *crypto.BoxPubKey, + bpriv *crypto.BoxPrivKey, + spub *crypto.SigPubKey, + spriv *crypto.SigPrivKey) { // TODO separate init and start functions // Init sets up structs // Start launches goroutines that depend on structs being set up // This is pretty much required to completely avoid race conditions - util_initByteStore() if c.log == nil { c.log = log.New(ioutil.Discard, "", 0) } @@ -96,10 +95,10 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.log.Println("Starting up...") - var boxPub boxPubKey - var boxPriv boxPrivKey - var sigPub sigPubKey - var sigPriv sigPrivKey + var boxPub crypto.BoxPubKey + var boxPriv crypto.BoxPrivKey + var sigPub crypto.SigPubKey + var sigPriv crypto.SigPrivKey boxPubHex, err := hex.DecodeString(nc.EncryptionPublicKey) if err != nil { return err @@ -188,7 +187,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { } ip := net.IP(c.router.addr[:]).String() - if err := c.router.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address_prefix)-1), nc.IfMTU); err != nil { + if err := c.router.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address.GetPrefix())-1), nc.IfMTU); err != nil { c.log.Println("Failed to start TUN/TAP") return err } @@ -206,35 +205,35 @@ func (c *Core) Stop() { // Generates a new encryption keypair. The encryption keys are used to // encrypt traffic and to derive the IPv6 address/subnet of the node. -func (c *Core) NewEncryptionKeys() (*boxPubKey, *boxPrivKey) { - return newBoxKeys() +func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) { + return crypto.NewBoxKeys() } // Generates a new signing keypair. The signing keys are used to derive the // structure of the spanning tree. -func (c *Core) NewSigningKeys() (*sigPubKey, *sigPrivKey) { - return newSigKeys() +func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) { + return crypto.NewSigKeys() } // Gets the node ID. -func (c *Core) GetNodeID() *NodeID { - return getNodeID(&c.boxPub) +func (c *Core) GetNodeID() *crypto.NodeID { + return crypto.GetNodeID(&c.boxPub) } // Gets the tree ID. -func (c *Core) GetTreeID() *TreeID { - return getTreeID(&c.sigPub) +func (c *Core) GetTreeID() *crypto.TreeID { + return crypto.GetTreeID(&c.sigPub) } // Gets the IPv6 address of the Yggdrasil node. This is always a /128. func (c *Core) GetAddress() *net.IP { - address := net.IP(address_addrForNodeID(c.GetNodeID())[:]) + address := net.IP(address.AddrForNodeID(c.GetNodeID())[:]) return &address } // Gets the routed IPv6 subnet of the Yggdrasil node. This is always a /64. func (c *Core) GetSubnet() *net.IPNet { - subnet := address_subnetForNodeID(c.GetNodeID())[:] + subnet := address.SubnetForNodeID(c.GetNodeID())[:] subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index e5bceee0..4a32eb64 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -22,6 +22,8 @@ import "net/http" import "runtime" import "os" +import "github.com/yggdrasil-network/yggdrasil-go/src/address" +import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" import "github.com/yggdrasil-network/yggdrasil-go/src/defaults" // Start the profiler in debug builds, if the required environment variable is set. @@ -48,8 +50,8 @@ func StartProfiler(log *log.Logger) error { // This function is only called by the simulator to set up a node with random // keys. It should not be used and may be removed in the future. func (c *Core) Init() { - bpub, bpriv := newBoxKeys() - spub, spriv := newSigKeys() + bpub, bpriv := crypto.NewBoxKeys() + spub, spriv := crypto.NewSigKeys() c.init(bpub, bpriv, spub, spriv) c.switchTable.start() c.router.start() @@ -59,12 +61,12 @@ func (c *Core) Init() { // Core -func (c *Core) DEBUG_getSigningPublicKey() sigPubKey { - return (sigPubKey)(c.sigPub) +func (c *Core) DEBUG_getSigningPublicKey() crypto.SigPubKey { + return (crypto.SigPubKey)(c.sigPub) } -func (c *Core) DEBUG_getEncryptionPublicKey() boxPubKey { - return (boxPubKey)(c.boxPub) +func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey { + return (crypto.BoxPubKey)(c.boxPub) } func (c *Core) DEBUG_getSend() chan<- []byte { @@ -81,7 +83,7 @@ func (c *Core) DEBUG_getPeers() *peers { return &c.peers } -func (ps *peers) DEBUG_newPeer(box boxPubKey, sig sigPubKey, link boxSharedKey) *peer { +func (ps *peers) DEBUG_newPeer(box crypto.BoxPubKey, sig crypto.SigPubKey, link crypto.BoxSharedKey) *peer { //in <-chan []byte, //out chan<- []byte) *peer { return ps.newPeer(&box, &sig, &link, "(simulator)") //, in, out) @@ -98,7 +100,7 @@ func (ps *peers) DEBUG_startPeers() { } */ -func (ps *peers) DEBUG_hasPeer(key sigPubKey) bool { +func (ps *peers) DEBUG_hasPeer(key crypto.SigPubKey) bool { ports := ps.ports.Load().(map[switchPort]*peer) for _, p := range ports { if p == nil { @@ -120,7 +122,7 @@ func (ps *peers) DEBUG_getPorts() map[switchPort]*peer { return newPeers } -func (p *peer) DEBUG_getSigKey() sigPubKey { +func (p *peer) DEBUG_getSigKey() crypto.SigPubKey { return p.sig } @@ -292,8 +294,8 @@ func (c *Core) DEBUG_startLoopbackUDPInterface() { //////////////////////////////////////////////////////////////////////////////// -func (c *Core) DEBUG_getAddr() *address { - return address_addrForNodeID(&c.dht.nodeID) +func (c *Core) DEBUG_getAddr() *address.Address { + return address.AddrForNodeID(&c.dht.nodeID) } func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) { @@ -302,7 +304,7 @@ func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) { func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) { addr := c.DEBUG_getAddr() - straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address_prefix)) + straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address.GetPrefix())) if ifname != "none" { err := c.router.tun.setup(ifname, iftapmode, straddr, mtu) if err != nil { @@ -320,38 +322,38 @@ func (c *Core) DEBUG_stopTun() { //////////////////////////////////////////////////////////////////////////////// -func (c *Core) DEBUG_newBoxKeys() (*boxPubKey, *boxPrivKey) { - return newBoxKeys() +func (c *Core) DEBUG_newBoxKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) { + return crypto.NewBoxKeys() } -func (c *Core) DEBUG_getSharedKey(myPrivKey *boxPrivKey, othersPubKey *boxPubKey) *boxSharedKey { - return getSharedKey(myPrivKey, othersPubKey) +func (c *Core) DEBUG_getSharedKey(myPrivKey *crypto.BoxPrivKey, othersPubKey *crypto.BoxPubKey) *crypto.BoxSharedKey { + return crypto.GetSharedKey(myPrivKey, othersPubKey) } -func (c *Core) DEBUG_newSigKeys() (*sigPubKey, *sigPrivKey) { - return newSigKeys() +func (c *Core) DEBUG_newSigKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) { + return crypto.NewSigKeys() } -func (c *Core) DEBUG_getNodeID(pub *boxPubKey) *NodeID { - return getNodeID(pub) +func (c *Core) DEBUG_getNodeID(pub *crypto.BoxPubKey) *crypto.NodeID { + return crypto.GetNodeID(pub) } -func (c *Core) DEBUG_getTreeID(pub *sigPubKey) *TreeID { - return getTreeID(pub) +func (c *Core) DEBUG_getTreeID(pub *crypto.SigPubKey) *crypto.TreeID { + return crypto.GetTreeID(pub) } -func (c *Core) DEBUG_addrForNodeID(nodeID *NodeID) string { - return net.IP(address_addrForNodeID(nodeID)[:]).String() +func (c *Core) DEBUG_addrForNodeID(nodeID *crypto.NodeID) string { + return net.IP(address.AddrForNodeID(nodeID)[:]).String() } func (c *Core) DEBUG_init(bpub []byte, bpriv []byte, spub []byte, spriv []byte) { - var boxPub boxPubKey - var boxPriv boxPrivKey - var sigPub sigPubKey - var sigPriv sigPrivKey + var boxPub crypto.BoxPubKey + var boxPriv crypto.BoxPrivKey + var sigPub crypto.SigPubKey + var sigPriv crypto.SigPrivKey copy(boxPub[:], bpub) copy(boxPriv[:], bpriv) copy(sigPub[:], spub) @@ -553,13 +555,13 @@ func (c *Core) DEBUG_simFixMTU() { func Util_testAddrIDMask() { for idx := 0; idx < 16; idx++ { - var orig NodeID + var orig crypto.NodeID orig[8] = 42 for bidx := 0; bidx < idx; bidx++ { orig[bidx/8] |= (0x80 >> uint8(bidx%8)) } - addr := address_addrForNodeID(&orig) - nid, mask := addr.getNodeIDandMask() + addr := address.AddrForNodeID(&orig) + nid, mask := addr.GetNodeIDandMask() for b := 0; b < len(mask); b++ { nid[b] &= mask[b] orig[b] &= mask[b] diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index aa694f7a..af104f51 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -8,6 +8,8 @@ package yggdrasil import ( "sort" "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) const dht_lookup_size = 16 @@ -15,8 +17,8 @@ const dht_lookup_size = 16 // dhtInfo represents everything we know about a node in the DHT. // This includes its key, a cache of it's NodeID, coords, and timing/ping related info for deciding who/when to ping nodes for maintenance. type dhtInfo struct { - nodeID_hidden *NodeID - key boxPubKey + nodeID_hidden *crypto.NodeID + key crypto.BoxPubKey coords []byte recv time.Time // When we last received a message pings int // Time out if at least 3 consecutive maintenance pings drop @@ -24,9 +26,9 @@ type dhtInfo struct { } // Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times. -func (info *dhtInfo) getNodeID() *NodeID { +func (info *dhtInfo) getNodeID() *crypto.NodeID { if info.nodeID_hidden == nil { - info.nodeID_hidden = getNodeID(&info.key) + info.nodeID_hidden = crypto.GetNodeID(&info.key) } return info.nodeID_hidden } @@ -34,36 +36,36 @@ func (info *dhtInfo) getNodeID() *NodeID { // Request for a node to do a lookup. // Includes our key and coords so they can send a response back, and the destination NodeID we want to ask about. type dhtReq struct { - Key boxPubKey // Key of whoever asked - Coords []byte // Coords of whoever asked - Dest NodeID // NodeID they're asking about + Key crypto.BoxPubKey // Key of whoever asked + Coords []byte // Coords of whoever asked + Dest crypto.NodeID // NodeID they're asking about } // Response to a DHT lookup. // Includes the key and coords of the node that's responding, and the destination they were asked about. // The main part is Infos []*dhtInfo, the lookup response. type dhtRes struct { - Key boxPubKey // key of the sender - Coords []byte // coords of the sender - Dest NodeID + Key crypto.BoxPubKey // key of the sender + Coords []byte // coords of the sender + Dest crypto.NodeID Infos []*dhtInfo // response } // Parts of a DHT req usable as a key in a map. type dhtReqKey struct { - key boxPubKey - dest NodeID + key crypto.BoxPubKey + dest crypto.NodeID } // The main DHT struct. type dht struct { core *Core - nodeID NodeID + nodeID crypto.NodeID peers chan *dhtInfo // other goroutines put incoming dht updates here reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests callbacks map[dhtReqKey]dht_callbackInfo // Search and admin lookup callbacks // These next two could be replaced by a single linked list or similar... - table map[NodeID]*dhtInfo + table map[crypto.NodeID]*dhtInfo imp []*dhtInfo } @@ -80,12 +82,12 @@ func (t *dht) init(c *Core) { // This empties all info from the DHT and drops outstanding requests. func (t *dht) reset() { t.reqs = make(map[dhtReqKey]time.Time) - t.table = make(map[NodeID]*dhtInfo) + t.table = make(map[crypto.NodeID]*dhtInfo) t.imp = nil } // Does a DHT lookup and returns up to dht_lookup_size results. -func (t *dht) lookup(nodeID *NodeID, everything bool) []*dhtInfo { +func (t *dht) lookup(nodeID *crypto.NodeID, everything bool) []*dhtInfo { results := make([]*dhtInfo, 0, len(t.table)) for _, info := range t.table { results = append(results, info) @@ -133,9 +135,9 @@ func (t *dht) insert(info *dhtInfo) { } // Return true if first/second/third are (partially) ordered correctly. -func dht_ordered(first, second, third *NodeID) bool { - lessOrEqual := func(first, second *NodeID) bool { - for idx := 0; idx < NodeIDLen; idx++ { +func dht_ordered(first, second, third *crypto.NodeID) bool { + lessOrEqual := func(first, second *crypto.NodeID) bool { + for idx := 0; idx < crypto.NodeIDLen; idx++ { if first[idx] > second[idx] { return false } @@ -190,7 +192,7 @@ func (t *dht) sendRes(res *dhtRes, req *dhtReq) { // Send a reply for a dhtReq bs := res.encode() shared := t.core.sessions.getSharedKey(&t.core.boxPriv, &req.Key) - payload, nonce := boxSeal(shared, bs, nil) + payload, nonce := crypto.BoxSeal(shared, bs, nil) p := wire_protoTrafficPacket{ Coords: req.Coords, ToKey: req.Key, @@ -252,7 +254,7 @@ func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { // Send a dhtReq to the node in dhtInfo bs := req.encode() shared := t.core.sessions.getSharedKey(&t.core.boxPriv, &dest.key) - payload, nonce := boxSeal(shared, bs, nil) + payload, nonce := crypto.BoxSeal(shared, bs, nil) p := wire_protoTrafficPacket{ Coords: dest.coords, ToKey: dest.key, @@ -267,7 +269,7 @@ func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { } // Sends a lookup to this info, looking for the target. -func (t *dht) ping(info *dhtInfo, target *NodeID) { +func (t *dht) ping(info *dhtInfo, target *crypto.NodeID) { // Creates a req for the node at dhtInfo, asking them about the target (if one is given) or themself (if no target is given) if target == nil { target = &t.nodeID diff --git a/src/yggdrasil/icmpv6.go b/src/yggdrasil/icmpv6.go index 6e9a265b..baae3ab4 100644 --- a/src/yggdrasil/icmpv6.go +++ b/src/yggdrasil/icmpv6.go @@ -17,6 +17,8 @@ import ( "golang.org/x/net/icmp" "golang.org/x/net/ipv6" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" ) type macAddress [6]byte @@ -27,7 +29,7 @@ type icmpv6 struct { tun *tunAdapter mylladdr net.IP mymac macAddress - peermacs map[address]neighbor + peermacs map[address.Address]neighbor } type neighbor struct { @@ -59,7 +61,7 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { // addresses. func (i *icmpv6) init(t *tunAdapter) { i.tun = t - i.peermacs = make(map[address]neighbor) + i.peermacs = make(map[address.Address]neighbor) // Our MAC address and link-local address i.mymac = macAddress{ @@ -172,7 +174,7 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error } case ipv6.ICMPTypeNeighborAdvertisement: if datamac != nil { - var addr address + var addr address.Address var mac macAddress copy(addr[:], ipv6Header.Src[:]) copy(mac[:], (*datamac)[:]) @@ -254,7 +256,7 @@ func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, return responsePacket, nil } -func (i *icmpv6) create_ndp_tap(dst address) ([]byte, error) { +func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) { // Create the ND payload var payload [28]byte copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) @@ -263,7 +265,7 @@ func (i *icmpv6) create_ndp_tap(dst address) ([]byte, error) { copy(payload[22:28], i.mymac[:6]) // Create the ICMPv6 solicited-node address - var dstaddr address + var dstaddr address.Address copy(dstaddr[:13], []byte{ 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -296,13 +298,13 @@ func (i *icmpv6) create_ndp_tap(dst address) ([]byte, error) { // to the Yggdrasil TAP adapter. func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) { // Ignore NDP requests for anything outside of fd00::/8 - var source address + var source address.Address copy(source[:], in[8:]) - var snet subnet + var snet address.Subnet copy(snet[:], in[8:]) switch { - case source.isValid(): - case snet.isValid(): + case source.IsValid(): + case snet.IsValid(): default: return nil, errors.New("Not an NDP for 0200::/7") } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 67aa805a..2fb505d5 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -8,6 +8,9 @@ import ( "sync" "sync/atomic" "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) // The peers struct represents peers with an active connection. @@ -19,7 +22,7 @@ type peers struct { mutex sync.Mutex // Synchronize writes to atomic ports atomic.Value //map[switchPort]*peer, use CoW semantics authMutex sync.RWMutex - allowedEncryptionPublicKeys map[boxPubKey]struct{} + allowedEncryptionPublicKeys map[crypto.BoxPubKey]struct{} } // Initializes the peers struct. @@ -28,11 +31,11 @@ func (ps *peers) init(c *Core) { defer ps.mutex.Unlock() ps.putPorts(make(map[switchPort]*peer)) ps.core = c - ps.allowedEncryptionPublicKeys = make(map[boxPubKey]struct{}) + ps.allowedEncryptionPublicKeys = make(map[crypto.BoxPubKey]struct{}) } // Returns true if an incoming peer connection to a key is allowed, either because the key is in the whitelist or because the whitelist is empty. -func (ps *peers) isAllowedEncryptionPublicKey(box *boxPubKey) bool { +func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool { ps.authMutex.RLock() defer ps.authMutex.RUnlock() _, isIn := ps.allowedEncryptionPublicKeys[*box] @@ -40,24 +43,24 @@ func (ps *peers) isAllowedEncryptionPublicKey(box *boxPubKey) bool { } // Adds a key to the whitelist. -func (ps *peers) addAllowedEncryptionPublicKey(box *boxPubKey) { +func (ps *peers) addAllowedEncryptionPublicKey(box *crypto.BoxPubKey) { ps.authMutex.Lock() defer ps.authMutex.Unlock() ps.allowedEncryptionPublicKeys[*box] = struct{}{} } // Removes a key from the whitelist. -func (ps *peers) removeAllowedEncryptionPublicKey(box *boxPubKey) { +func (ps *peers) removeAllowedEncryptionPublicKey(box *crypto.BoxPubKey) { ps.authMutex.Lock() defer ps.authMutex.Unlock() delete(ps.allowedEncryptionPublicKeys, *box) } // Gets the whitelist of allowed keys for incoming connections. -func (ps *peers) getAllowedEncryptionPublicKeys() []boxPubKey { +func (ps *peers) getAllowedEncryptionPublicKeys() []crypto.BoxPubKey { ps.authMutex.RLock() defer ps.authMutex.RUnlock() - keys := make([]boxPubKey, 0, len(ps.allowedEncryptionPublicKeys)) + keys := make([]crypto.BoxPubKey, 0, len(ps.allowedEncryptionPublicKeys)) for key := range ps.allowedEncryptionPublicKeys { keys = append(keys, key) } @@ -81,10 +84,10 @@ type peer struct { // 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 + box crypto.BoxPubKey + sig crypto.SigPubKey + shared crypto.BoxSharedKey + linkShared crypto.BoxSharedKey endpoint string friendlyName string firstSeen time.Time // To track uptime for getPeers @@ -96,11 +99,11 @@ 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) *peer { +func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, endpoint string) *peer { now := time.Now() p := peer{box: *box, sig: *sig, - shared: *getSharedKey(&ps.core.boxPriv, box), + shared: *crypto.GetSharedKey(&ps.core.boxPriv, box), linkShared: *linkShared, endpoint: endpoint, firstSeen: now, @@ -212,7 +215,7 @@ func (p *peer) handlePacket(packet []byte) { case wire_LinkProtocolTraffic: p.handleLinkTraffic(packet) default: - util_putBytes(packet) + util.PutBytes(packet) } } @@ -236,13 +239,13 @@ func (p *peer) sendPacket(packet []byte) { // This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers. // It sends it to p.linkOut, which bypasses the usual packet queues. func (p *peer) sendLinkPacket(packet []byte) { - innerPayload, innerNonce := boxSeal(&p.linkShared, packet, nil) + innerPayload, innerNonce := crypto.BoxSeal(&p.linkShared, packet, nil) innerLinkPacket := wire_linkProtoTrafficPacket{ Nonce: *innerNonce, Payload: innerPayload, } outerPayload := innerLinkPacket.encode() - bs, nonce := boxSeal(&p.shared, outerPayload, nil) + bs, nonce := crypto.BoxSeal(&p.shared, outerPayload, nil) linkPacket := wire_linkProtoTrafficPacket{ Nonce: *nonce, Payload: bs, @@ -258,7 +261,7 @@ func (p *peer) handleLinkTraffic(bs []byte) { if !packet.decode(bs) { return } - outerPayload, isOK := boxOpen(&p.shared, packet.Payload, &packet.Nonce) + outerPayload, isOK := crypto.BoxOpen(&p.shared, packet.Payload, &packet.Nonce) if !isOK { return } @@ -266,7 +269,7 @@ func (p *peer) handleLinkTraffic(bs []byte) { if !innerPacket.decode(outerPayload) { return } - payload, isOK := boxOpen(&p.linkShared, innerPacket.Payload, &innerPacket.Nonce) + payload, isOK := crypto.BoxOpen(&p.linkShared, innerPacket.Payload, &innerPacket.Nonce) if !isOK { return } @@ -278,7 +281,7 @@ func (p *peer) handleLinkTraffic(bs []byte) { case wire_SwitchMsg: p.handleSwitchMsg(payload) default: - util_putBytes(bs) + util.PutBytes(bs) } } @@ -292,7 +295,7 @@ func (p *peer) sendSwitchMsg() { msg.Hops = append(msg.Hops, switchMsgHop{ Port: p.port, Next: p.sig, - Sig: *sign(&p.core.sigPriv, bs), + Sig: *crypto.Sign(&p.core.sigPriv, bs), }) packet := msg.encode() p.sendLinkPacket(packet) @@ -316,7 +319,7 @@ func (p *peer) handleSwitchMsg(packet []byte) { sigMsg.Hops = msg.Hops[:idx] loc.coords = append(loc.coords, hop.Port) bs := getBytesForSig(&hop.Next, &sigMsg) - if !verify(&prevKey, bs, &hop.Sig) { + if !crypto.Verify(&prevKey, bs, &hop.Sig) { p.core.peers.removePeer(p.port) } prevKey = hop.Next @@ -341,7 +344,7 @@ func (p *peer) handleSwitchMsg(packet []byte) { // This generates the bytes that we sign or check the signature of for a switchMsg. // It begins with the next node's key, followed by the root and the timetsamp, followed by coords being advertised to the next node. -func getBytesForSig(next *sigPubKey, msg *switchMsg) []byte { +func getBytesForSig(next *crypto.SigPubKey, msg *switchMsg) []byte { var loc switchLocator for _, hop := range msg.Hops { loc.coords = append(loc.coords, hop.Port) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 62480bc4..91972b3f 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -28,14 +28,18 @@ import ( "golang.org/x/net/icmp" "golang.org/x/net/ipv6" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) // The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. // The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. type router struct { core *Core - addr address - subnet subnet + addr address.Address + subnet address.Subnet in <-chan []byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() @@ -57,17 +61,17 @@ type router_recvPacket struct { // Initializes the router struct, which includes setting up channels to/from the tun/tap. func (r *router) init(core *Core) { r.core = core - r.addr = *address_addrForNodeID(&r.core.dht.nodeID) - r.subnet = *address_subnetForNodeID(&r.core.dht.nodeID) + 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)") + p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, "(self)") p.out = func(packet []byte) { // This is to make very sure it never blocks select { case in <- packet: return default: - util_putBytes(packet) + util.PutBytes(packet) } } r.in = in @@ -121,7 +125,7 @@ func (r *router) mainLoop() { r.core.switchTable.doMaintenance() r.core.dht.doMaintenance() r.core.sessions.cleanup() - util_getBytes() // To slowly drain things + util.GetBytes() // To slowly drain things } case f := <-r.admin: f() @@ -135,11 +139,11 @@ func (r *router) mainLoop() { // If the session hasn't responded recently, it triggers a ping or search to keep things alive or deal with broken coords *relatively* quickly. // It also deals with oversized packets if there are MTU issues by calling into icmpv6.go to spoof PacketTooBig traffic, or DestinationUnreachable if the other side has their tun/tap disabled. func (r *router) sendPacket(bs []byte) { - var sourceAddr address - var destAddr address - var destSnet subnet - var destPubKey *boxPubKey - var destNodeID *NodeID + var sourceAddr address.Address + var destAddr address.Address + var destSnet address.Subnet + var destPubKey *crypto.BoxPubKey + var destNodeID *crypto.NodeID var addrlen int if bs[0]&0xf0 == 0x60 { // Check if we have a fully-sized header @@ -169,19 +173,19 @@ func (r *router) sendPacket(bs []byte) { // configured crypto-key routing source subnets return } - if !destAddr.isValid() && !destSnet.isValid() { + if !destAddr.IsValid() && !destSnet.IsValid() { // The addresses didn't match valid Yggdrasil node addresses so let's see // whether it matches a crypto-key routing range instead if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil { // A public key was found, get the node ID for the search destPubKey = &key - destNodeID = getNodeID(destPubKey) + destNodeID = crypto.GetNodeID(destPubKey) // Do a quick check to ensure that the node ID refers to a vaild Yggdrasil // address or subnet - this might be superfluous - addr := *address_addrForNodeID(destNodeID) + addr := *address.AddrForNodeID(destNodeID) copy(destAddr[:], addr[:]) copy(destSnet[:], addr[:]) - if !destAddr.isValid() && !destSnet.isValid() { + if !destAddr.IsValid() && !destSnet.IsValid() { return } } else { @@ -190,25 +194,25 @@ func (r *router) sendPacket(bs []byte) { } } doSearch := func(packet []byte) { - var nodeID, mask *NodeID + var nodeID, mask *crypto.NodeID switch { case destNodeID != nil: // We already know the full node ID, probably because it's from a CKR // route in which the public key is known ahead of time nodeID = destNodeID - var m NodeID + var m crypto.NodeID for i := range m { m[i] = 0xFF } mask = &m - case destAddr.isValid(): + case destAddr.IsValid(): // We don't know the full node ID - try and use the address to generate // a truncated node ID - nodeID, mask = destAddr.getNodeIDandMask() - case destSnet.isValid(): + nodeID, mask = destAddr.GetNodeIDandMask() + case destSnet.IsValid(): // We don't know the full node ID - try and use the subnet to generate // a truncated node ID - nodeID, mask = destSnet.getNodeIDandMask() + nodeID, mask = destSnet.GetNodeIDandMask() default: return } @@ -223,10 +227,10 @@ func (r *router) sendPacket(bs []byte) { } var sinfo *sessionInfo var isIn bool - if destAddr.isValid() { + if destAddr.IsValid() { sinfo, isIn = r.core.sessions.getByTheirAddr(&destAddr) } - if destSnet.isValid() { + if destSnet.IsValid() { sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet) } switch { @@ -305,12 +309,12 @@ func (r *router) sendPacket(bs []byte) { func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { // Note: called directly by the session worker, not the router goroutine if len(bs) < 24 { - util_putBytes(bs) + util.PutBytes(bs) return } - var sourceAddr address - var dest address - var snet subnet + var sourceAddr address.Address + var dest address.Address + var snet address.Subnet var addrlen int if bs[0]&0xf0 == 0x60 { // IPv6 address @@ -330,17 +334,17 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { // Check that the packet is destined for either our Yggdrasil address or // subnet, or that it matches one of the crypto-key routing source routes if !r.cryptokey.isValidSource(dest, addrlen) { - util_putBytes(bs) + util.PutBytes(bs) return } // See whether the packet they sent should have originated from this session switch { - case sourceAddr.isValid() && sourceAddr == sinfo.theirAddr: - case snet.isValid() && snet == sinfo.theirSubnet: + case sourceAddr.IsValid() && sourceAddr == sinfo.theirAddr: + case snet.IsValid() && snet == sinfo.theirSubnet: default: key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen) if err != nil || key != sinfo.theirPermPub { - util_putBytes(bs) + util.PutBytes(bs) return } } @@ -366,7 +370,7 @@ func (r *router) handleIn(packet []byte) { // Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets. // Passes them to the crypto session worker to be decrypted and sent to the tun/tap. func (r *router) handleTraffic(packet []byte) { - defer util_putBytes(packet) + defer util.PutBytes(packet) p := wire_trafficPacket{} if !p.decode(packet) { return @@ -386,14 +390,14 @@ func (r *router) handleProto(packet []byte) { return } // Now try to open the payload - var sharedKey *boxSharedKey + var sharedKey *crypto.BoxSharedKey if p.ToKey == r.core.boxPub { // Try to open using our permanent key sharedKey = r.core.sessions.getSharedKey(&r.core.boxPriv, &p.FromKey) } else { return } - bs, isOK := boxOpen(sharedKey, p.Payload, &p.Nonce) + bs, isOK := crypto.BoxOpen(sharedKey, p.Payload, &p.Nonce) if !isOK { return } @@ -414,12 +418,12 @@ func (r *router) handleProto(packet []byte) { case wire_DHTLookupResponse: r.handleDHTRes(bs, &p.FromKey) default: - util_putBytes(packet) + util.PutBytes(packet) } } // Decodes session pings from wire format and passes them to sessions.handlePing where they either create or update a session. -func (r *router) handlePing(bs []byte, fromKey *boxPubKey) { +func (r *router) handlePing(bs []byte, fromKey *crypto.BoxPubKey) { ping := sessionPing{} if !ping.decode(bs) { return @@ -429,12 +433,12 @@ func (r *router) handlePing(bs []byte, fromKey *boxPubKey) { } // Handles session pongs (which are really pings with an extra flag to prevent acknowledgement). -func (r *router) handlePong(bs []byte, fromKey *boxPubKey) { +func (r *router) handlePong(bs []byte, fromKey *crypto.BoxPubKey) { r.handlePing(bs, fromKey) } // Decodes dht requests and passes them to dht.handleReq to trigger a lookup/response. -func (r *router) handleDHTReq(bs []byte, fromKey *boxPubKey) { +func (r *router) handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey) { req := dhtReq{} if !req.decode(bs) { return @@ -444,7 +448,7 @@ func (r *router) handleDHTReq(bs []byte, fromKey *boxPubKey) { } // Decodes dht responses and passes them to dht.handleRes to update the DHT table and further pass them to the search code (if applicable). -func (r *router) handleDHTRes(bs []byte, fromKey *boxPubKey) { +func (r *router) handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey) { res := dhtRes{} if !res.decode(bs) { return diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index be156dc6..c85b719c 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -17,6 +17,8 @@ package yggdrasil import ( "sort" "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) // This defines the maximum number of dhtInfo that we keep track of for nodes to query in an ongoing search. @@ -30,28 +32,28 @@ const search_RETRY_TIME = time.Second // Information about an ongoing search. // Includes the targed NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. type searchInfo struct { - dest NodeID - mask NodeID + dest crypto.NodeID + mask crypto.NodeID time time.Time packet []byte toVisit []*dhtInfo - visited map[NodeID]bool + visited map[crypto.NodeID]bool } // This stores a map of active searches. type searches struct { core *Core - searches map[NodeID]*searchInfo + searches map[crypto.NodeID]*searchInfo } // Intializes the searches struct. func (s *searches) init(core *Core) { s.core = core - s.searches = make(map[NodeID]*searchInfo) + s.searches = make(map[crypto.NodeID]*searchInfo) } // Creates a new search info, adds it to the searches struct, and returns a pointer to the info. -func (s *searches) createSearch(dest *NodeID, mask *NodeID) *searchInfo { +func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo { now := time.Now() for dest, sinfo := range s.searches { if now.Sub(sinfo.time) > time.Minute { @@ -102,7 +104,7 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { } } // Deduplicate - vMap := make(map[NodeID]*dhtInfo) + vMap := make(map[crypto.NodeID]*dhtInfo) for _, info := range sinfo.toVisit { vMap[*info.getNodeID()] = info } @@ -163,10 +165,10 @@ func (s *searches) continueSearch(sinfo *searchInfo) { } // Calls create search, and initializes the iterative search parts of the struct before returning it. -func (s *searches) newIterSearch(dest *NodeID, mask *NodeID) *searchInfo { +func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo { sinfo := s.createSearch(dest, mask) sinfo.toVisit = s.core.dht.lookup(dest, true) - sinfo.visited = make(map[NodeID]bool) + sinfo.visited = make(map[crypto.NodeID]bool) return sinfo } @@ -174,10 +176,10 @@ func (s *searches) newIterSearch(dest *NodeID, mask *NodeID) *searchInfo { // If the response is from the target, get/create a session, trigger a session ping, and return true. // Otherwise return false. func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { - them := getNodeID(&res.Key) - var destMasked NodeID - var themMasked NodeID - for idx := 0; idx < NodeIDLen; idx++ { + them := crypto.GetNodeID(&res.Key) + var destMasked crypto.NodeID + var themMasked crypto.NodeID + for idx := 0; idx < crypto.NodeIDLen; idx++ { destMasked[idx] = info.dest[idx] & info.mask[idx] themMasked[idx] = them[idx] & info.mask[idx] } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 4f2bedf9..4f395b07 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -8,23 +8,27 @@ import ( "bytes" "encoding/hex" "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) // 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 + theirAddr address.Address + theirSubnet address.Subnet + theirPermPub crypto.BoxPubKey + theirSesPub crypto.BoxPubKey + mySesPub crypto.BoxPubKey + mySesPriv crypto.BoxPrivKey + sharedSesKey crypto.BoxSharedKey // derived from session keys + theirHandle crypto.Handle + myHandle crypto.Handle + theirNonce crypto.BoxNonce + myNonce crypto.BoxNonce theirMTU uint16 myMTU uint16 wasMTUFixed bool // Was the MTU fixed by a receive error? @@ -45,9 +49,9 @@ type sessionInfo struct { // 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. type sessionPing struct { - SendPermPub boxPubKey // Sender's permanent key - Handle handle // Random number to ID session - SendSesPub boxPubKey // Session key to use + SendPermPub crypto.BoxPubKey // Sender's permanent key + Handle crypto.Handle // Random number to ID session + SendSesPub crypto.BoxPubKey // Session key to use Coords []byte Tstamp int64 // unix time, but the only real requirement is that it increases IsPong bool @@ -69,8 +73,8 @@ func (s *sessionInfo) update(p *sessionPing) bool { if p.SendSesPub != s.theirSesPub { s.theirSesPub = p.SendSesPub s.theirHandle = p.Handle - s.sharedSesKey = *getSharedKey(&s.mySesPriv, &s.theirSesPub) - s.theirNonce = boxNonce{} + s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub) + s.theirNonce = crypto.BoxNonce{} s.nonceMask = 0 } if p.MTU >= 1280 || p.MTU == 0 { @@ -99,15 +103,15 @@ type sessions struct { core *Core lastCleanup time.Time // Maps known permanent keys to their shared key, used by DHT a lot - permShared map[boxPubKey]*boxSharedKey + permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps (secret) handle onto session info - sinfos map[handle]*sessionInfo + sinfos map[crypto.Handle]*sessionInfo // Maps mySesPub onto handle - byMySes map[boxPubKey]*handle + byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle - byTheirPerm map[boxPubKey]*handle - addrToPerm map[address]*boxPubKey - subnetToPerm map[subnet]*boxPubKey + byTheirPerm map[crypto.BoxPubKey]*crypto.Handle + addrToPerm map[address.Address]*crypto.BoxPubKey + subnetToPerm map[address.Subnet]*crypto.BoxPubKey // Options from the session firewall sessionFirewallEnabled bool sessionFirewallAllowsDirect bool @@ -120,12 +124,12 @@ type sessions struct { // Initializes the session struct. func (ss *sessions) init(core *Core) { ss.core = core - ss.permShared = make(map[boxPubKey]*boxSharedKey) - ss.sinfos = make(map[handle]*sessionInfo) - ss.byMySes = make(map[boxPubKey]*handle) - ss.byTheirPerm = make(map[boxPubKey]*handle) - ss.addrToPerm = make(map[address]*boxPubKey) - ss.subnetToPerm = make(map[subnet]*boxPubKey) + ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey) + ss.sinfos = make(map[crypto.Handle]*sessionInfo) + ss.byMySes = make(map[crypto.BoxPubKey]*crypto.Handle) + ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle) + ss.addrToPerm = make(map[address.Address]*crypto.BoxPubKey) + ss.subnetToPerm = make(map[address.Subnet]*crypto.BoxPubKey) ss.lastCleanup = time.Now() } @@ -154,18 +158,18 @@ func (ss *sessions) setSessionFirewallBlacklist(blacklist []string) { // Determines whether the session with a given publickey is allowed based on // session firewall rules. -func (ss *sessions) isSessionAllowed(pubkey *boxPubKey, initiator bool) bool { +func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool { // Allow by default if the session firewall is disabled if !ss.sessionFirewallEnabled { return true } // Prepare for checking whitelist/blacklist - var box boxPubKey + var box crypto.BoxPubKey // Reject blacklisted nodes for _, b := range ss.sessionFirewallBlacklist { key, err := hex.DecodeString(b) if err == nil { - copy(box[:boxPubKeyLen], key) + copy(box[:crypto.BoxPubKeyLen], key) if box == *pubkey { return false } @@ -175,7 +179,7 @@ func (ss *sessions) isSessionAllowed(pubkey *boxPubKey, initiator bool) bool { for _, b := range ss.sessionFirewallWhitelist { key, err := hex.DecodeString(b) if err == nil { - copy(box[:boxPubKeyLen], key) + copy(box[:crypto.BoxPubKeyLen], key) if box == *pubkey { return true } @@ -208,7 +212,7 @@ func (ss *sessions) isSessionAllowed(pubkey *boxPubKey, initiator bool) bool { } // Gets the session corresponding to a given handle. -func (ss *sessions) getSessionForHandle(handle *handle) (*sessionInfo, bool) { +func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bool) { sinfo, isIn := ss.sinfos[*handle] if isIn && sinfo.timedout() { // We have a session, but it has timed out @@ -218,7 +222,7 @@ func (ss *sessions) getSessionForHandle(handle *handle) (*sessionInfo, bool) { } // Gets a session corresponding to an ephemeral session key used by this node. -func (ss *sessions) getByMySes(key *boxPubKey) (*sessionInfo, bool) { +func (ss *sessions) getByMySes(key *crypto.BoxPubKey) (*sessionInfo, bool) { h, isIn := ss.byMySes[*key] if !isIn { return nil, false @@ -228,7 +232,7 @@ func (ss *sessions) getByMySes(key *boxPubKey) (*sessionInfo, bool) { } // Gets a session corresponding to a permanent key used by the remote node. -func (ss *sessions) getByTheirPerm(key *boxPubKey) (*sessionInfo, bool) { +func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) { h, isIn := ss.byTheirPerm[*key] if !isIn { return nil, false @@ -238,7 +242,7 @@ func (ss *sessions) getByTheirPerm(key *boxPubKey) (*sessionInfo, bool) { } // Gets a session corresponding to an IPv6 address used by the remote node. -func (ss *sessions) getByTheirAddr(addr *address) (*sessionInfo, bool) { +func (ss *sessions) getByTheirAddr(addr *address.Address) (*sessionInfo, bool) { p, isIn := ss.addrToPerm[*addr] if !isIn { return nil, false @@ -248,7 +252,7 @@ func (ss *sessions) getByTheirAddr(addr *address) (*sessionInfo, bool) { } // Gets a session corresponding to an IPv6 /64 subnet used by the remote node/network. -func (ss *sessions) getByTheirSubnet(snet *subnet) (*sessionInfo, bool) { +func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) { p, isIn := ss.subnetToPerm[*snet] if !isIn { return nil, false @@ -259,7 +263,7 @@ func (ss *sessions) getByTheirSubnet(snet *subnet) (*sessionInfo, bool) { // Creates a new session and lazily cleans up old/timedout existing sessions. // This includse initializing session info to sane defaults (e.g. lowest supported MTU). -func (ss *sessions) createSession(theirPermKey *boxPubKey) *sessionInfo { +func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { if ss.sessionFirewallEnabled { if !ss.isSessionAllowed(theirPermKey, true) { return nil @@ -268,10 +272,10 @@ func (ss *sessions) createSession(theirPermKey *boxPubKey) *sessionInfo { sinfo := sessionInfo{} sinfo.core = ss.core sinfo.theirPermPub = *theirPermKey - pub, priv := newBoxKeys() + pub, priv := crypto.NewBoxKeys() sinfo.mySesPub = *pub sinfo.mySesPriv = *priv - sinfo.myNonce = *newBoxNonce() + sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 sinfo.myMTU = uint16(ss.core.router.tun.mtu) now := time.Now() @@ -295,9 +299,9 @@ func (ss *sessions) createSession(theirPermKey *boxPubKey) *sessionInfo { // lower => even nonce sinfo.myNonce[len(sinfo.myNonce)-1] &= 0xfe } - sinfo.myHandle = *newHandle() - sinfo.theirAddr = *address_addrForNodeID(getNodeID(&sinfo.theirPermPub)) - sinfo.theirSubnet = *address_subnetForNodeID(getNodeID(&sinfo.theirPermPub)) + sinfo.myHandle = *crypto.NewHandle() + sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) + sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.send = make(chan []byte, 32) sinfo.recv = make(chan *wire_trafficPacket, 32) go sinfo.doWorker() @@ -324,32 +328,32 @@ func (ss *sessions) cleanup() { s.close() } } - permShared := make(map[boxPubKey]*boxSharedKey, len(ss.permShared)) + permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared)) for k, v := range ss.permShared { permShared[k] = v } ss.permShared = permShared - sinfos := make(map[handle]*sessionInfo, len(ss.sinfos)) + sinfos := make(map[crypto.Handle]*sessionInfo, len(ss.sinfos)) for k, v := range ss.sinfos { sinfos[k] = v } ss.sinfos = sinfos - byMySes := make(map[boxPubKey]*handle, len(ss.byMySes)) + byMySes := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byMySes)) for k, v := range ss.byMySes { byMySes[k] = v } ss.byMySes = byMySes - byTheirPerm := make(map[boxPubKey]*handle, len(ss.byTheirPerm)) + byTheirPerm := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byTheirPerm)) for k, v := range ss.byTheirPerm { byTheirPerm[k] = v } ss.byTheirPerm = byTheirPerm - addrToPerm := make(map[address]*boxPubKey, len(ss.addrToPerm)) + addrToPerm := make(map[address.Address]*crypto.BoxPubKey, len(ss.addrToPerm)) for k, v := range ss.addrToPerm { addrToPerm[k] = v } ss.addrToPerm = addrToPerm - subnetToPerm := make(map[subnet]*boxPubKey, len(ss.subnetToPerm)) + subnetToPerm := make(map[address.Subnet]*crypto.BoxPubKey, len(ss.subnetToPerm)) for k, v := range ss.subnetToPerm { subnetToPerm[k] = v } @@ -380,15 +384,15 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing { Coords: coords, MTU: sinfo.myMTU, } - sinfo.myNonce.update() + sinfo.myNonce.Increment() return ref } // Gets the shared key for a pair of box keys. // Used to cache recently used shared keys for protocol traffic. // This comes up with dht req/res and session ping/pong traffic. -func (ss *sessions) getSharedKey(myPriv *boxPrivKey, - theirPub *boxPubKey) *boxSharedKey { +func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey, + theirPub *crypto.BoxPubKey) *crypto.BoxSharedKey { if skey, isIn := ss.permShared[*theirPub]; isIn { return skey } @@ -401,7 +405,7 @@ func (ss *sessions) getSharedKey(myPriv *boxPrivKey, } delete(ss.permShared, key) } - ss.permShared[*theirPub] = getSharedKey(myPriv, theirPub) + ss.permShared[*theirPub] = crypto.GetSharedKey(myPriv, theirPub) return ss.permShared[*theirPub] } @@ -417,7 +421,7 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { ping.IsPong = isPong bs := ping.encode() shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) - payload, nonce := boxSeal(shared, bs, nil) + payload, nonce := crypto.BoxSeal(shared, bs, nil) p := wire_protoTrafficPacket{ Coords: sinfo.coords, ToKey: sinfo.theirPermPub, @@ -468,24 +472,6 @@ func (ss *sessions) handlePing(ping *sessionPing) { } } -// Used to subtract one nonce from another, staying in the range +- 64. -// This is used by the nonce progression machinery to advance the bitmask of recently received packets (indexed by nonce), or to check the appropriate bit of the bitmask. -// It's basically part of the machinery that prevents replays and duplicate packets. -func (n *boxNonce) minus(m *boxNonce) int64 { - diff := int64(0) - for idx := range n { - diff *= 256 - diff += int64(n[idx]) - int64(m[idx]) - if diff > 64 { - diff = 64 - } - if diff < -64 { - diff = -64 - } - } - return diff -} - // Get the MTU of the session. // Will be equal to the smaller of this node's MTU or the remote node's MTU. // If sending over links with a maximum message size (this was a thing with the old UDP code), it could be further lowered, to a minimum of 1280. @@ -500,9 +486,9 @@ func (sinfo *sessionInfo) getMTU() uint16 { } // Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received. -func (sinfo *sessionInfo) nonceIsOK(theirNonce *boxNonce) bool { +func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool { // The bitmask is to allow for some non-duplicate out-of-order packets - diff := theirNonce.minus(&sinfo.theirNonce) + diff := theirNonce.Minus(&sinfo.theirNonce) if diff > 0 { return true } @@ -510,10 +496,10 @@ func (sinfo *sessionInfo) nonceIsOK(theirNonce *boxNonce) bool { } // Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce -func (sinfo *sessionInfo) updateNonce(theirNonce *boxNonce) { +func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Shift nonce mask if needed // Set bit - diff := theirNonce.minus(&sinfo.theirNonce) + diff := theirNonce.Minus(&sinfo.theirNonce) if diff > 0 { // This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info. sinfo.nonceMask <<= uint64(diff) @@ -559,7 +545,7 @@ func (sinfo *sessionInfo) doWorker() { // This encrypts a packet, creates a trafficPacket struct, encodes it, and sends it to router.out to pass it to the switch layer. func (sinfo *sessionInfo) doSend(bs []byte) { - defer util_putBytes(bs) + defer util.PutBytes(bs) if !sinfo.init { // To prevent using empty session keys return @@ -593,8 +579,8 @@ func (sinfo *sessionInfo) doSend(bs []byte) { coords = wire_put_uint64(flowkey, coords) // Then variable-length encoded flowkey } // Prepare the payload - payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) - defer util_putBytes(payload) + payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) + defer util.PutBytes(payload) p := wire_trafficPacket{ Coords: coords, Handle: sinfo.theirHandle, @@ -612,13 +598,13 @@ func (sinfo *sessionInfo) doSend(bs []byte) { // If a packet does not decrypt successfully, it assumes the packet was truncated, and updates the MTU accordingly. // TODO? remove the MTU updating part? That should never happen with TCP peers, and the old UDP code that caused it was removed (and if replaced, should be replaced with something that can reliably send messages with an arbitrary size). func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) { - defer util_putBytes(p.Payload) + defer util.PutBytes(p.Payload) if !sinfo.nonceIsOK(&p.Nonce) { return } - bs, isOK := boxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) + bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) if !isOK { - util_putBytes(bs) + util.PutBytes(bs) return } sinfo.updateNonce(&p.Nonce) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 99ed25b4..3c1dae61 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -16,6 +16,9 @@ import ( "sync" "sync/atomic" "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) const ( @@ -30,16 +33,16 @@ const ( // The coords represent a path from the root to a node. // This path is generally part of a spanning tree, except possibly the last hop (it can loop when sending coords to your parent, but they see this and know not to use a looping path). type switchLocator struct { - root sigPubKey + root crypto.SigPubKey tstamp int64 coords []switchPort } // Returns true if the first sigPubKey has a higher TreeID. -func firstIsBetter(first, second *sigPubKey) bool { +func firstIsBetter(first, second *crypto.SigPubKey) bool { // Higher TreeID is better - ftid := getTreeID(first) - stid := getTreeID(second) + ftid := crypto.GetTreeID(first) + stid := crypto.GetTreeID(second) for idx := 0; idx < len(ftid); idx++ { if ftid[idx] == stid[idx] { continue @@ -121,7 +124,7 @@ func (x *switchLocator) isAncestorOf(y *switchLocator) bool { // Information about a peer, used by the switch to build the tree and eventually make routing decisions. type peerInfo struct { - key sigPubKey // ID of this peer + key crypto.SigPubKey // ID of this peer locator switchLocator // Should be able to respond with signatures upon request degree uint64 // Self-reported degree time time.Time // Time this node was last seen @@ -159,26 +162,26 @@ type switchData struct { // All the information stored by the switch. type switchTable struct { core *Core - key sigPubKey // Our own key - time time.Time // Time when locator.tstamp was last updated - drop map[sigPubKey]int64 // Tstamp associated with a dropped root - mutex sync.RWMutex // Lock for reads/writes of switchData - parent switchPort // Port of whatever peer is our parent, or self if we're root - data switchData // - updater atomic.Value // *sync.Once - table atomic.Value // lookupTable - packetIn chan []byte // Incoming packets for the worker to handle - idleIn chan switchPort // Incoming idle notifications from peer links - admin chan func() // Pass a lambda for the admin socket to query stuff - queues switch_buffers // Queues - not atomic so ONLY use through admin chan - queueTotalMaxSize uint64 // Maximum combined size of queues + key crypto.SigPubKey // Our own key + time time.Time // Time when locator.tstamp was last updated + drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root + mutex sync.RWMutex // Lock for reads/writes of switchData + parent switchPort // Port of whatever peer is our parent, or self if we're root + data switchData // + updater atomic.Value // *sync.Once + table atomic.Value // lookupTable + packetIn chan []byte // Incoming packets for the worker to handle + idleIn chan switchPort // Incoming idle notifications from peer links + admin chan func() // Pass a lambda for the admin socket to query stuff + queues switch_buffers // Queues - not atomic so ONLY use through admin chan + queueTotalMaxSize uint64 // Maximum combined size of queues } // Minimum allowed total size of switch queues. const SwitchQueueTotalMinSize = 4 * 1024 * 1024 // Initializes the switchTable struct. -func (t *switchTable) init(core *Core, key sigPubKey) { +func (t *switchTable) init(core *Core, key crypto.SigPubKey) { now := time.Now() t.core = core t.key = key @@ -187,7 +190,7 @@ func (t *switchTable) init(core *Core, key sigPubKey) { t.data = switchData{locator: locator, peers: peers} t.updater.Store(&sync.Once{}) t.table.Store(lookupTable{}) - t.drop = make(map[sigPubKey]int64) + t.drop = make(map[crypto.SigPubKey]int64) t.packetIn = make(chan []byte, 1024) t.idleIn = make(chan switchPort, 1024) t.admin = make(chan func()) @@ -302,7 +305,7 @@ func (t *switchTable) cleanDropped() { // This is exchanged with peers to construct the spanning tree. // A subset of this information, excluding the signatures, is used to construct locators that are used elsewhere in the code. type switchMsg struct { - Root sigPubKey + Root crypto.SigPubKey TStamp int64 Hops []switchMsgHop } @@ -310,8 +313,8 @@ type switchMsg struct { // This represents the signed information about the path leading from the root the Next node, via the Port specified here. type switchMsgHop struct { Port switchPort - Next sigPubKey - Sig sigBytes + Next crypto.SigPubKey + Sig crypto.SigBytes } // This returns a *switchMsg to a copy of this node's current switchMsg, which can safely have additional information appended to Hops and sent to a peer. @@ -690,7 +693,7 @@ func (b *switch_buffers) cleanup(t *switchTable) { coords := switch_getPacketCoords(packet.bytes) if t.selfIsClosest(coords) { for _, packet := range buf.packets { - util_putBytes(packet.bytes) + util.PutBytes(packet.bytes) } b.size -= buf.size delete(b.bufs, streamID) @@ -710,7 +713,7 @@ func (b *switch_buffers) cleanup(t *switchTable) { packet, buf.packets = buf.packets[0], buf.packets[1:] buf.size -= uint64(len(packet.bytes)) b.size -= uint64(len(packet.bytes)) - util_putBytes(packet.bytes) + util.PutBytes(packet.bytes) if len(buf.packets) == 0 { delete(b.bufs, streamID) } else { diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 5ca66304..045932a2 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -25,6 +25,10 @@ import ( "time" "golang.org/x/net/proxy" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) const tcp_msgSize = 2048 + 65535 // TODO figure out what makes sense @@ -52,8 +56,8 @@ type tcpInterface struct { // This is used as the key to a map that tracks existing connections, to prevent multiple connections to the same keys and local/remote address pair from occuring. // Different address combinations are allowed, so multi-homing is still technically possible (but not necessarily advisable). type tcpInfo struct { - box boxPubKey - sig sigPubKey + box crypto.BoxPubKey + sig crypto.SigPubKey localAddr string remoteAddr string } @@ -206,7 +210,7 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) { func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { defer sock.Close() // Get our keys - myLinkPub, myLinkPriv := newBoxKeys() // ephemeral link keys + myLinkPub, myLinkPriv := crypto.NewBoxKeys() // ephemeral link keys meta := version_getBaseMetadata() meta.box = iface.core.boxPub meta.sig = iface.core.sigPub @@ -287,7 +291,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()) + p := iface.core.peers.newPeer(&info.box, &info.sig, crypto.GetSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String()) p.linkOut = make(chan []byte, 1) in := func(bs []byte) { p.handlePacket(bs) @@ -301,7 +305,7 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { buf := net.Buffers{tcp_msg[:], msgLen, msg} buf.WriteTo(sock) atomic.AddUint64(&p.bytesSent, uint64(len(tcp_msg)+len(msgLen)+len(msg))) - util_putBytes(msg) + util.PutBytes(msg) } timerInterval := tcp_ping_interval timer := time.NewTimer(timerInterval) @@ -350,8 +354,8 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { }() us, _, _ := net.SplitHostPort(sock.LocalAddr().String()) them, _, _ := net.SplitHostPort(sock.RemoteAddr().String()) - themNodeID := getNodeID(&info.box) - themAddr := address_addrForNodeID(themNodeID) + themNodeID := crypto.GetNodeID(&info.box) + themAddr := address.AddrForNodeID(themNodeID) themAddrString := net.IP(themAddr[:]).String() themString := fmt.Sprintf("%s@%s", themAddrString, them) iface.core.log.Println("Connected:", themString, "source", us) @@ -386,9 +390,9 @@ func (iface *tcpInterface) reader(sock net.Conn, in func([]byte)) error { // We didn't get the whole message yet break } - newMsg := append(util_getBytes(), msg...) + newMsg := append(util.GetBytes(), msg...) in(newMsg) - util_yield() + util.Yield() } frag = append(bs[:0], frag...) } diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 26d32c0b..c3ceeb65 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -10,7 +10,9 @@ import ( "github.com/songgao/packets/ethernet" "github.com/yggdrasil-network/water" + "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) const tun_IPv6_HEADER_LENGTH = 40 @@ -80,7 +82,7 @@ func (tun *tunAdapter) write() error { continue } if tun.iface.IsTAP() { - var destAddr address + var destAddr address.Address if data[0]&0xf0 == 0x60 { if len(data) < 40 { panic("Tried to send a packet shorter than an IPv6 header...") @@ -94,7 +96,7 @@ func (tun *tunAdapter) write() error { } else { return errors.New("Invalid address family") } - sendndp := func(destAddr address) { + sendndp := func(destAddr address.Address) { neigh, known := tun.icmpv6.peermacs[destAddr] known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) if !known { @@ -154,7 +156,7 @@ func (tun *tunAdapter) write() error { panic(err) } } - util_putBytes(data) + util.PutBytes(data) } } @@ -191,7 +193,7 @@ func (tun *tunAdapter) read() error { // tun.icmpv6.recv <- b go tun.icmpv6.parse_packet(b) } - packet := append(util_getBytes(), buf[o:n]...) + packet := append(util.GetBytes(), buf[o:n]...) tun.send <- packet } } diff --git a/src/yggdrasil/version.go b/src/yggdrasil/version.go index 75b08a6f..f71780e0 100644 --- a/src/yggdrasil/version.go +++ b/src/yggdrasil/version.go @@ -4,6 +4,8 @@ package yggdrasil // Used in the inital connection setup and key exchange // Some of this could arguably go in wire.go instead +import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + // This is the version-specific metadata exchanged at the start of a connection. // It must always beign with the 4 bytes "meta" and a wire formatted uint64 major version number. // The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open an connection. @@ -12,9 +14,9 @@ type version_metadata struct { ver uint64 // 1 byte in this version // Everything after this point potentially depends on the version number, and is subject to change in future versions minorVer uint64 // 1 byte in this version - box boxPubKey - sig sigPubKey - link boxPubKey + box crypto.BoxPubKey + sig crypto.SigPubKey + link crypto.BoxPubKey } // Gets a base metadata with no keys set, but with the correct version numbers. @@ -28,12 +30,12 @@ func version_getBaseMetadata() version_metadata { // Gest the length of the metadata for this version, used to know how many bytes to read from the start of a connection. func version_getMetaLength() (mlen int) { - mlen += 4 // meta - mlen += 1 // ver, as long as it's < 127, which it is in this version - mlen += 1 // minorVer, as long as it's < 127, which it is in this version - mlen += boxPubKeyLen // box - mlen += sigPubKeyLen // sig - mlen += boxPubKeyLen // link + mlen += 4 // meta + mlen += 1 // ver, as long as it's < 127, which it is in this version + mlen += 1 // minorVer, as long as it's < 127, which it is in this version + mlen += crypto.BoxPubKeyLen // box + mlen += crypto.SigPubKeyLen // sig + mlen += crypto.BoxPubKeyLen // link return } diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index d05624e2..08e8a62f 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -7,6 +7,11 @@ package yggdrasil // Packet types, as wire_encode_uint64(type) at the start of each packet +import ( + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" +) + const ( wire_Traffic = iota // data being routed somewhere, handle for crypto wire_ProtocolTraffic // protocol traffic, pub keys for crypto @@ -191,14 +196,14 @@ func wire_chop_uint64(toUInt64 *uint64, fromSlice *[]byte) bool { // The wire format for ordinary IPv6 traffic encapsulated by the network. type wire_trafficPacket struct { Coords []byte - Handle handle - Nonce boxNonce + Handle crypto.Handle + Nonce crypto.BoxNonce Payload []byte } // Encodes a wire_trafficPacket into its wire format. func (p *wire_trafficPacket) encode() []byte { - bs := util_getBytes() + bs := util.GetBytes() bs = wire_put_uint64(wire_Traffic, bs) bs = wire_put_coords(p.Coords, bs) bs = append(bs, p.Handle[:]...) @@ -222,16 +227,16 @@ func (p *wire_trafficPacket) decode(bs []byte) bool { case !wire_chop_slice(p.Nonce[:], &bs): return false } - p.Payload = append(util_getBytes(), bs...) + p.Payload = append(util.GetBytes(), bs...) return true } // The wire format for protocol traffic, such as dht req/res or session ping/pong packets. type wire_protoTrafficPacket struct { Coords []byte - ToKey boxPubKey - FromKey boxPubKey - Nonce boxNonce + ToKey crypto.BoxPubKey + FromKey crypto.BoxPubKey + Nonce crypto.BoxNonce Payload []byte } @@ -273,7 +278,7 @@ func (p *wire_protoTrafficPacket) decode(bs []byte) bool { // The keys themselves are exchanged as part of the connection setup, and then omitted from the packets. // The two layer logic is handled in peers.go, but it's kind of ugly. type wire_linkProtoTrafficPacket struct { - Nonce boxNonce + Nonce crypto.BoxNonce Payload []byte } From 72cc1bb321968a9bda5fbb4aad816e08d1d318c3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 14 Dec 2018 20:58:52 -0600 Subject: [PATCH 25/49] make genkeys use the new address/crypto packages --- misc/genkeys.go | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/misc/genkeys.go b/misc/genkeys.go index e995805f..7a0d316a 100644 --- a/misc/genkeys.go +++ b/misc/genkeys.go @@ -12,11 +12,16 @@ This only matters if it's high enough to make you the root of the tree. */ package main -import "encoding/hex" -import "flag" -import "fmt" -import "runtime" -import . "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" +import ( + "encoding/hex" + "flag" + "fmt" + "net" + "runtime" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) var doSig = flag.Bool("sig", false, "generate new signing keys instead") @@ -82,12 +87,7 @@ func isBetter(oldID, newID []byte) bool { } func doBoxKeys(out chan<- keySet, in <-chan []byte) { - c := Core{} - pub, _ := c.DEBUG_newBoxKeys() - bestID := c.DEBUG_getNodeID(pub) - for idx := range bestID { - bestID[idx] = 0 - } + var bestID crypto.NodeID for { select { case newBestID := <-in: @@ -95,22 +95,20 @@ func doBoxKeys(out chan<- keySet, in <-chan []byte) { copy(bestID[:], newBestID) } default: - pub, priv := c.DEBUG_newBoxKeys() - id := c.DEBUG_getNodeID(pub) + pub, priv := crypto.NewBoxKeys() + id := crypto.GetNodeID(pub) if !isBetter(bestID[:], id[:]) { continue } - bestID = id - ip := c.DEBUG_addrForNodeID(id) + bestID = *id + ip := net.IP(address.AddrForNodeID(id)[:]).String() out <- keySet{priv[:], pub[:], id[:], ip} } } } func doSigKeys(out chan<- keySet, in <-chan []byte) { - c := Core{} - pub, _ := c.DEBUG_newSigKeys() - bestID := c.DEBUG_getTreeID(pub) + var bestID crypto.TreeID for idx := range bestID { bestID[idx] = 0 } @@ -122,12 +120,12 @@ func doSigKeys(out chan<- keySet, in <-chan []byte) { } default: } - pub, priv := c.DEBUG_newSigKeys() - id := c.DEBUG_getTreeID(pub) + pub, priv := crypto.NewSigKeys() + id := crypto.GetTreeID(pub) if !isBetter(bestID[:], id[:]) { continue } - bestID = id + bestID = *id out <- keySet{priv[:], pub[:], id[:], ""} } } From 570e85c2972b375ae85196b39af5b15d2ff40da2 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 14 Dec 2018 21:12:25 -0600 Subject: [PATCH 26/49] remove debug code --- src/yggdrasil/tcp.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 5d4bfc71..a7941662 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -57,10 +57,8 @@ func (iface *tcpInterface) setExtraOptions(c net.Conn) { sock.SetNoDelay(true) sock.SetKeepAlive(true) sock.SetKeepAlivePeriod(iface.tcp_timeout) - panic("DEBUG testing") // TODO something for socks5 default: - iface.core.log.Println("Unrecognized connection type: %v", sock) } } From 4875ab89545c3fb1ef20a07f24930b5083410baa Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 14 Dec 2018 21:44:31 -0600 Subject: [PATCH 27/49] peer thread safey for dhtInfo updates --- src/yggdrasil/peer.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index e092513b..e4382a83 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -90,7 +90,7 @@ type peer struct { 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 + dinfo (chan *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 } @@ -105,6 +105,7 @@ func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKe endpoint: endpoint, firstSeen: now, doSend: make(chan struct{}, 1), + dinfo: make(chan *dhtInfo, 1), core: ps.core} ps.mutex.Lock() defer ps.mutex.Unlock() @@ -178,6 +179,7 @@ func (p *peer) linkLoop() { tick := time.NewTicker(time.Second) defer tick.Stop() p.doSendSwitchMsgs() + var dinfo *dhtInfo for { select { case _, ok := <-p.doSend: @@ -185,8 +187,8 @@ func (p *peer) linkLoop() { return } p.sendSwitchMsg() + case dinfo = <-p.dinfo: case _ = <-tick.C: - dinfo := p.dinfo // FIXME? are pointer reads *always* atomic? if dinfo != nil { p.core.dht.peers <- dinfo } @@ -218,8 +220,9 @@ func (p *peer) handlePacket(packet []byte) { // Called to handle traffic or protocolTraffic packets. // In either case, this reads from the coords of the packet header, does a switch lookup, and forwards to the next node. func (p *peer) handleTraffic(packet []byte, pTypeLen int) { - if p.port != 0 && p.dinfo == nil { - // Drop traffic until the peer manages to send us at least one good switchMsg + table := p.core.switchTable.getTable() + if _, isIn := table.elems[p.port]; !isIn && p.port != 0 { + // Drop traffic if the peer isn't in the switch return } p.core.switchTable.packetIn <- packet @@ -323,9 +326,7 @@ func (p *peer) handleSwitchMsg(packet []byte) { p.core.switchTable.handleMsg(&msg, p.port) if !p.core.switchTable.checkRoot(&msg) { // Bad switch message - // Stop forwarding traffic from it - // Stop refreshing it in the DHT - p.dinfo = nil + p.dinfo <- nil return } // Pass a mesage to the dht informing it that this peer (still) exists @@ -334,8 +335,7 @@ func (p *peer) handleSwitchMsg(packet []byte) { key: p.box, coords: loc.getCoords(), } - //p.core.dht.peers <- &dinfo - p.dinfo = &dinfo + p.dinfo <- &dinfo } // This generates the bytes that we sign or check the signature of for a switchMsg. From 8b63e841ea27dc74c1216c9b03a5c21375229589 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 10:39:31 +0000 Subject: [PATCH 28/49] 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 e7736302..7c9ed1b3 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 7f607ed5..19393b10 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 29/49] 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 7c9ed1b3..d78499e6 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 559997fa..85c55408 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 30/49] 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 d78499e6..2fba594e 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 19393b10..4acd467d 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 31/49] 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 66de668b..a4cd5c37 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 85c55408..5fbedc84 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 4acd467d..7deb60d5 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 98a544fd7dc9b63e05ebed97416488016e61e268 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 11:48:43 +0000 Subject: [PATCH 32/49] Fix bug in yggdrasilctl where -endpoint gets ignored --- cmd/yggdrasilctl/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index b37c2561..8135815f 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -87,6 +87,7 @@ func main() { logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) } } else { + endpoint = *server logger.Println("Using endpoint", endpoint, "from command line") } From a50964b334d37b39082c201e75193e52a2f96fbc Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 11:57:25 +0000 Subject: [PATCH 33/49] Fix panic if Peers or InterfacePeers is commented out --- cmd/yggdrasil/main.go | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index ee5375be..d93f5d2a 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -191,19 +191,8 @@ func main() { } // Check to see if the peers are in a parsable format, if not then default // them to the TCP scheme - for index, peer := range dat["Peers"].([]interface{}) { - uri := peer.(string) - if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { - continue - } - if strings.HasPrefix(uri, "tcp:") { - uri = uri[4:] - } - (dat["Peers"].([]interface{}))[index] = "tcp://" + uri - } - // Now do the same with the interface peers - for intf, peers := range dat["InterfacePeers"].(map[string]interface{}) { - for index, peer := range peers.([]interface{}) { + if peers, ok := dat["Peers"].([]interface{}); ok { + for index, peer := range peers { uri := peer.(string) if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { continue @@ -211,7 +200,22 @@ func main() { if strings.HasPrefix(uri, "tcp:") { uri = uri[4:] } - ((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri + (dat["Peers"].([]interface{}))[index] = "tcp://" + uri + } + } + // Now do the same with the interface peers + if interfacepeers, ok := dat["InterfacePeers"].(map[string]interface{}); ok { + for intf, peers := range interfacepeers { + for index, peer := range peers.([]interface{}) { + uri := peer.(string) + if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { + continue + } + if strings.HasPrefix(uri, "tcp:") { + uri = uri[4:] + } + ((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri + } } } // Overlay our newly mapped configuration onto the autoconf node config that From 9a5cf96c298025c84f710ec0c7be1fdc7e71fc39 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 15 Dec 2018 12:18:52 +0000 Subject: [PATCH 34/49] 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 2fba594e..5def7e09 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 35/49] 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 5fbedc84..05d528ae 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 36/49] 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 7deb60d5..67bab4ac 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 37/49] 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 a4cd5c37..b5a1f890 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 5def7e09..f3b5998a 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 05d528ae..66c6964e 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 67bab4ac..00000000 --- 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 00000000..2146b277 --- /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 14e089f4..10670677 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 dbd273f9..4f2bedf9 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 d46994de..5e877843 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 } } From d9f212dd39314cae88a5ccf698c446ae66935b7a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 Dec 2018 17:01:59 -0600 Subject: [PATCH 38/49] don't panic if we write to a closed tun device because tun.close() was called --- src/yggdrasil/tun.go | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index c3ceeb65..9d1e0fe5 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -5,6 +5,7 @@ package yggdrasil import ( "bytes" "errors" + "sync" "time" "github.com/songgao/packets/ethernet" @@ -24,6 +25,8 @@ type tunAdapter struct { icmpv6 icmpv6 mtu int iface *water.Interface + mutex sync.RWMutex // Protects the below + isOpen bool } // Gets the maximum supported MTU for the platform based on the defaults in @@ -50,6 +53,9 @@ func (tun *tunAdapter) start(ifname string, iftapmode bool, addr string, mtu int if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil { return err } + tun.mutex.Lock() + tun.isOpen = true + tun.mutex.Unlock() go func() { panic(tun.read()) }() go func() { panic(tun.write()) }() if iftapmode { @@ -148,12 +154,26 @@ func (tun *tunAdapter) write() error { len(data)) // Payload length copy(frame[tun_ETHER_HEADER_LENGTH:], data[:]) if _, err := tun.iface.Write(frame); err != nil { - panic(err) + tun.mutex.RLock() + open := tun.isOpen + tun.mutex.RUnlock() + if !open { + return nil + } else { + panic(err) + } } } } else { if _, err := tun.iface.Write(data); err != nil { - panic(err) + tun.mutex.RLock() + open := tun.isOpen + tun.mutex.RUnlock() + if !open { + return nil + } else { + panic(err) + } } } util.PutBytes(data) @@ -173,8 +193,15 @@ func (tun *tunAdapter) read() error { for { n, err := tun.iface.Read(buf) if err != nil { - // panic(err) - return err + tun.mutex.RLock() + open := tun.isOpen + tun.mutex.RUnlock() + if !open { + return nil + } else { + // panic(err) + return err + } } o := 0 if tun.iface.IsTAP() { @@ -202,6 +229,9 @@ func (tun *tunAdapter) read() error { // process stops. Typically this operation will happen quickly, but on macOS // it can block until a read operation is completed. func (tun *tunAdapter) close() error { + tun.mutex.Lock() + tun.isOpen = false + tun.mutex.Unlock() if tun.iface == nil { return nil } From db034ce6bd365d3dd3413bda55cc5f38a1c61317 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 Dec 2018 17:23:07 -0600 Subject: [PATCH 39/49] replace panics with warning messages if the tun reader/writer return an error --- src/yggdrasil/tun.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 9d1e0fe5..b6bc9132 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -56,8 +56,8 @@ func (tun *tunAdapter) start(ifname string, iftapmode bool, addr string, mtu int tun.mutex.Lock() tun.isOpen = true tun.mutex.Unlock() - go func() { panic(tun.read()) }() - go func() { panic(tun.write()) }() + go func() { tun.core.log.Println("WARNING: tun.read() exited with error:", tun.read()) }() + go func() { tun.core.log.Println("WARNING: tun.write() exited with error:", tun.write()) }() if iftapmode { go func() { for { From 300f471bab2eb756a5e8111fb2485fb1f933c2ad Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 Dec 2018 18:32:50 -0600 Subject: [PATCH 40/49] don't SetKeepAlive[Period] on tcp connections, since the behavior is platform specific --- src/yggdrasil/tcp.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 873dd63c..6d923440 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -59,8 +59,6 @@ func (iface *tcpInterface) setExtraOptions(c net.Conn) { switch sock := c.(type) { case *net.TCPConn: sock.SetNoDelay(true) - sock.SetKeepAlive(true) - sock.SetKeepAlivePeriod(iface.tcp_timeout) // TODO something for socks5 default: } From 83d734e10953e08a1c0d3e8d772c785fefd12aec Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 17 Dec 2018 10:17:16 +0000 Subject: [PATCH 41/49] Make yggdrasilctl less crashy --- cmd/yggdrasilctl/main.go | 41 ++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 8135815f..4dcbbef7 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -30,6 +30,7 @@ func main() { if r := recover(); r != nil { logger.Println("Fatal error:", r) fmt.Print(logbuffer) + panic(r) os.Exit(1) } }() @@ -37,13 +38,15 @@ func main() { endpoint := defaults.GetDefaults().DefaultAdminListen flag.Usage = func() { - fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n", os.Args[0]) + fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0]) fmt.Println("Options:") flag.PrintDefaults() - fmt.Println("Commands:\n - Use \"list\" for a list of available commands") + fmt.Println("\nPlease note that options must always specified BEFORE the command\non the command line or they will be ignored.\n") + fmt.Println("Commands:\n - Use \"list\" for a list of available commands\n") fmt.Println("Examples:") fmt.Println(" - ", os.Args[0], "list") fmt.Println(" - ", os.Args[0], "getPeers") + fmt.Println(" - ", os.Args[0], "-v getSelf") fmt.Println(" - ", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false") fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT") fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT") @@ -122,24 +125,34 @@ func main() { for c, a := range args { if c == 0 { + if strings.HasPrefix(a, "-") { + logger.Printf("Ignoring flag %s as it should be specified before other parameters\n", a) + continue + } logger.Printf("Sending request: %v\n", a) send["request"] = a continue } tokens := strings.Split(a, "=") - if i, err := strconv.Atoi(tokens[1]); err == nil { - logger.Printf("Sending parameter %s: %d\n", tokens[0], i) - send[tokens[0]] = i - } else { - switch strings.ToLower(tokens[1]) { - case "true": - send[tokens[0]] = true - case "false": - send[tokens[0]] = false - default: - send[tokens[0]] = tokens[1] + if len(tokens) == 1 { + send[tokens[0]] = true + } else if len(tokens) > 2 { + send[tokens[0]] = strings.Join(tokens[1:], "=") + } else if len(tokens) == 2 { + if i, err := strconv.Atoi(tokens[1]); err == nil { + logger.Printf("Sending parameter %s: %d\n", tokens[0], i) + send[tokens[0]] = i + } else { + switch strings.ToLower(tokens[1]) { + case "true": + send[tokens[0]] = true + case "false": + send[tokens[0]] = false + default: + send[tokens[0]] = tokens[1] + } + logger.Printf("Sending parameter %s: %v\n", tokens[0], send[tokens[0]]) } - logger.Printf("Sending parameter %s: %v\n", tokens[0], send[tokens[0]]) } } From 422424af64c539a99a7a7f0d574f1af3eaeb7fe8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 17 Dec 2018 10:19:20 +0000 Subject: [PATCH 42/49] Don't panic --- cmd/yggdrasilctl/main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 4dcbbef7..189a53a7 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -30,7 +30,6 @@ func main() { if r := recover(); r != nil { logger.Println("Fatal error:", r) fmt.Print(logbuffer) - panic(r) os.Exit(1) } }() @@ -201,7 +200,7 @@ func main() { if !keysOrdered { for k := range slv.(map[string]interface{}) { if !*verbose { - if k == "box_pub_key" || k == "box_sig_key" { + if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" { continue } } From 0ee74a4efd364c57af0756812d31c4f792924293 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 17 Dec 2018 10:34:26 +0000 Subject: [PATCH 43/49] Update semver --- contrib/semver/version.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index 331046f3..eab5ab86 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -3,7 +3,7 @@ # Merge commits from this branch are counted DEVELOPBRANCH="yggdrasil-network/develop" -# Get the last tag +# Get the last tag that denotes moving to a major version number TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.0" 2>/dev/null) # Get last merge to master @@ -33,9 +33,6 @@ if [ $? != 0 ]; then exit 1 fi -# Get the number of merges on the current branch since the last tag -BUILD=$(git rev-list $TAG..HEAD --count --merges) - # Split out into major, minor and patch numbers MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1) MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2) @@ -56,6 +53,10 @@ fi # Add the build tag on non-master branches if [ $BRANCH != "master" ]; then + # Get the number of merges on the current branch since the last tag + BUILDTAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null) + BUILD=$(git rev-list $BUILDTAG..HEAD --count --merges) + if [ $BUILD != 0 ]; then printf -- "-%04d" "$BUILD" fi From b20c3538b74d5fc566a37852ec783212bf91f71f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 17 Dec 2018 10:50:57 +0000 Subject: [PATCH 44/49] Point hjson to master repo following comments merge --- cmd/yggdrasil/main.go | 2 +- cmd/yggdrasilctl/main.go | 2 +- contrib/config/yggdrasilconf.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index d93f5d2a..5a9db26c 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -18,9 +18,9 @@ import ( "golang.org/x/text/encoding/unicode" + "github.com/hjson/hjson-go" "github.com/kardianos/minwinsvc" "github.com/mitchellh/mapstructure" - "github.com/neilalexander/hjson-go" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 189a53a7..bbe3cd2e 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -17,7 +17,7 @@ import ( "golang.org/x/text/encoding/unicode" - "github.com/neilalexander/hjson-go" + "github.com/hjson/hjson-go" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) diff --git a/contrib/config/yggdrasilconf.go b/contrib/config/yggdrasilconf.go index 78c0e6d7..ad55e163 100644 --- a/contrib/config/yggdrasilconf.go +++ b/contrib/config/yggdrasilconf.go @@ -14,7 +14,7 @@ import ( "io/ioutil" "strconv" - "github.com/neilalexander/hjson-go" + "github.com/hjson/hjson-go" "golang.org/x/text/encoding/unicode" "github.com/yggdrasil-network/yggdrasil-go/src/config" diff --git a/go.mod b/go.mod index db6d359a..53a5a2b9 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,9 @@ module github.com/yggdrasil-network/yggdrasil-go require ( github.com/docker/libcontainer v2.2.1+incompatible + github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 - github.com/neilalexander/hjson-go v0.0.0-20180509131856-23267a251165 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 diff --git a/go.sum b/go.sum index 7d064112..1695daff 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0= github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw= +github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU= +github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/neilalexander/hjson-go v0.0.0-20180509131856-23267a251165 h1:Oo7Yfu5lEQLGvvh2p9Z8FRHJIsl7fdOCK9xXFNBkqmQ= -github.com/neilalexander/hjson-go v0.0.0-20180509131856-23267a251165/go.mod h1:l+Zao6IpQ+6d/y7LnYnOfbfOeU/9xRiTi4HLVpnkcTg= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1kehCG6x6G+/9txLfq6n3lS5Vp0Mxn1hdiBAc= From ebfc236153db88a244b3ca34892283e075f623cc Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 17 Dec 2018 11:13:11 +0000 Subject: [PATCH 45/49] Update changelog for v0.3.1 --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db4dde1..7c8994fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.1] - 2018-12-17 +### Added +- Build name and version is now imprinted onto the binaries if available/specified during build +- Ability to disable admin socket with `AdminListen: none` +- `AF_UNIX` domain sockets for the admin socket +- Cache size restriction for crypto-key routes +- `NodeInfo` support for specifying node information, e.g. node name or contact, which can be used in network crawls or surveys +- `getNodeInfo` request added to admin socket +- Adds flags `-c`, `-l` and `-t` to `build` script for specifying `GCFLAGS`, `LDFLAGS` or whether to keep symbol/DWARF tables + +### Changed +- Default `AdminListen` in newly generated config is now `unix:///var/run/yggdrasil.sock` +- Formatting of `getRoutes` in the admin socket has been improved +- Debian package now adds `yggdrasil` group to assist with `AF_UNIX` admin socket permissions +- Crypto, address and other utility code refactored into separate Go packages + +### Fixed +- Switch peer convergence is now much faster again (previously it was taking up to a minute once the peering was established) +- `yggdrasilctl` is now less prone to crashing when parameters are specified incorrectly +- Panic fixed when `Peers` or `InterfacePeers` was commented out + ## [0.3.0] - 2018-12-12 ### Added - Crypto-key routing support for tunnelling both IPv4 and IPv6 over Yggdrasil From 712ec7d3fb9051ea5ae7bfeb6ec33e9c90f867be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christer=20War=C3=A9n?= Date: Mon, 17 Dec 2018 15:28:45 +0200 Subject: [PATCH 46/49] Update Dockerfile ENV CGO_ENABLED=0 --- contrib/docker/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 6b4bfcb6..fb4fbfaa 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -2,6 +2,9 @@ FROM docker.io/golang:alpine as builder COPY . /src WORKDIR /src + +ENV CGO_ENABLED=0 + RUN apk add git && ./build FROM docker.io/alpine From 8ec4c66f655eef5bd694dc3a88ea8686c3d722d7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 17 Dec 2018 19:06:52 +0000 Subject: [PATCH 47/49] Multithread the admin socket --- src/yggdrasil/admin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 5028c9b6..bd3c9051 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -395,7 +395,7 @@ func (a *admin) listen() { for { conn, err := a.listener.Accept() if err == nil { - a.handleRequest(conn) + go a.handleRequest(conn) } } } From 0a9b34d121a60f39869c251de942c3ef53fd583b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 17 Dec 2018 19:17:29 +0000 Subject: [PATCH 48/49] Revert "Merge pull request #259 from neilalexander/semver" This reverts commit 51b3746df682b50f5d6a3f6c5bcd10b36e4dc8b1, reversing changes made to 6e87791e50f38fe310771858888b661831a0fd61. --- contrib/semver/version.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index eab5ab86..331046f3 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -3,7 +3,7 @@ # Merge commits from this branch are counted DEVELOPBRANCH="yggdrasil-network/develop" -# Get the last tag that denotes moving to a major version number +# Get the last tag TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.0" 2>/dev/null) # Get last merge to master @@ -33,6 +33,9 @@ if [ $? != 0 ]; then exit 1 fi +# Get the number of merges on the current branch since the last tag +BUILD=$(git rev-list $TAG..HEAD --count --merges) + # Split out into major, minor and patch numbers MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1) MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2) @@ -53,10 +56,6 @@ fi # Add the build tag on non-master branches if [ $BRANCH != "master" ]; then - # Get the number of merges on the current branch since the last tag - BUILDTAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null) - BUILD=$(git rev-list $BUILDTAG..HEAD --count --merges) - if [ $BUILD != 0 ]; then printf -- "-%04d" "$BUILD" fi From a22da8009ff2057332d5cd4dbd9c08e601dcd5be Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 17 Dec 2018 22:07:28 +0000 Subject: [PATCH 49/49] Hopefully fix semver versioning breakage caused by non-master merge commits --- contrib/semver/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index 331046f3..a53ed32c 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -10,7 +10,7 @@ TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.0" 2>/dev/null) MERGE=$(git rev-list $TAG..master --grep "from $DEVELOPBRANCH" 2>/dev/null | head -n 1) # Get the number of merges since the last merge to master -PATCH=$(git rev-list $TAG..master --count --merges --grep="from $DEVELOPBRANCH" 2>/dev/null) +PATCH=$(git rev-list $TAG..master --count --merges --grep="from $DEVELOPBRANCH" --first-parent master 2>/dev/null) # Decide whether we should prepend the version with "v" - the default is that # we do because we use it in git tags, but we might not always need it