From eef2a02d0ab2f329adcb38b42da3fd44944359d2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 18 Apr 2019 16:38:24 +0100 Subject: [PATCH 01/60] Experiment with new API --- src/crypto/crypto.go | 9 ++++ src/yggdrasil/api.go | 113 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 src/yggdrasil/api.go diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index c974a3c0..b47db184 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -13,6 +13,7 @@ It also defines NodeID and TreeID as hashes of keys, and wraps hash functions import ( "crypto/rand" "crypto/sha512" + "encoding/hex" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/nacl/box" @@ -32,6 +33,14 @@ type NodeID [NodeIDLen]byte type TreeID [TreeIDLen]byte type Handle [handleLen]byte +func (n *NodeID) String() string { + return hex.EncodeToString(n[:]) +} + +func (n *NodeID) Network() string { + return "nodeid" +} + func GetNodeID(pub *BoxPubKey) *NodeID { h := sha512.Sum512(pub[:]) return (*NodeID)(&h) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go new file mode 100644 index 00000000..9181269d --- /dev/null +++ b/src/yggdrasil/api.go @@ -0,0 +1,113 @@ +package yggdrasil + +import ( + "encoding/hex" + "errors" + "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) + +func (c *Core) Dial(network, address string) (Conn, error) { + var nodeID *crypto.NodeID + var nodeMask *crypto.NodeID + // Process + switch network { + case "nodeid": + // A node ID was provided - we don't need to do anything special with it + dest, err := hex.DecodeString(address) + if err != nil { + return Conn{}, err + } + copy(nodeID[:], dest) + var m crypto.NodeID + for i := range dest { + m[i] = 0xFF + } + copy(nodeMask[:], m[:]) + default: + // An unexpected address type was given, so give up + return Conn{}, errors.New("unexpected address type") + } + // Try and search for the node on the network + doSearch := func() { + sinfo, isIn := c.searches.searches[*nodeID] + if !isIn { + sinfo = c.searches.newIterSearch(nodeID, nodeMask) + } + c.searches.continueSearch(sinfo) + } + var sinfo *sessionInfo + var isIn bool + switch { + case !isIn || !sinfo.init: + // No or unintiialized session, so we need to search first + doSearch() + case time.Since(sinfo.time) > 6*time.Second: + if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + // We haven't heard from the dest in a while + // We tried pinging but didn't get a response + // They may have changed coords + // Try searching to discover new coords + // Note that search spam is throttled internally + doSearch() + } else { + // We haven't heard about the dest in a while + now := time.Now() + if !sinfo.time.Before(sinfo.pingTime) { + // Update pingTime to start the clock for searches (above) + sinfo.pingTime = now + } + if time.Since(sinfo.pingSend) > time.Second { + // Send at most 1 ping per second + sinfo.pingSend = now + c.sessions.sendPingPong(sinfo, false) + } + } + } + return Conn{ + session: sinfo, + }, nil +} + +type Conn struct { + session *sessionInfo + readDeadline time.Time + writeDeadline time.Time +} + +func (c *Conn) Read(b []byte) (int, error) { + return 0, nil +} + +func (c *Conn) Write(b []byte) (int, error) { + return 0, nil +} + +func (c *Conn) Close() error { + return nil +} + +func (c *Conn) LocalAddr() crypto.NodeID { + return *crypto.GetNodeID(&c.session.core.boxPub) +} + +func (c *Conn) RemoteAddr() crypto.NodeID { + return *crypto.GetNodeID(&c.session.theirPermPub) +} + +func (c *Conn) SetDeadline(t time.Time) error { + c.SetReadDeadline(t) + c.SetWriteDeadline(t) + return nil +} + +func (c *Conn) SetReadDeadline(t time.Time) error { + c.readDeadline = t + return nil +} + +func (c *Conn) SetWriteDeadline(t time.Time) error { + c.writeDeadline = t + return nil +} From 160e01e84f82763d77e7bbfd5863e8192707da7f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 18 Apr 2019 23:38:23 +0100 Subject: [PATCH 02/60] Searches called from api.go, various other tweaks, searches now have a callback for success/failure, node ID now reported by admin socket --- cmd/yggdrasil/main.go | 13 +++++ cmd/yggdrasilctl/main.go | 3 ++ src/yggdrasil/admin.go | 2 + src/yggdrasil/api.go | 108 +++++++++++++++++++++++++++++++-------- src/yggdrasil/router.go | 8 ++- src/yggdrasil/search.go | 43 +++++++++------- src/yggdrasil/session.go | 81 ++++++++++++++--------------- 7 files changed, 177 insertions(+), 81 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index fd8cd7b6..36866b3f 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -10,6 +10,7 @@ import ( "os/signal" "strings" "syscall" + "time" "golang.org/x/text/encoding/unicode" @@ -267,6 +268,18 @@ func main() { defer func() { n.core.Stop() }() + // Some stuff + go func() { + time.Sleep(time.Second * 2) + session, err := n.core.Dial("nodeid", "babd4e4bccb216f77bb723c1b034b63a652060aabfe9506b51f687183e9b0fd13f438876f5a3ab21cac9c8101eb88e2613fe2a8b0724add09d7ef5a72146c31f") + logger.Println(session, err) + b := []byte{1, 2, 3, 4, 5} + for { + logger.Println(session.Write(b)) + logger.Println(session.Read(b)) + time.Sleep(time.Second) + } + }() // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index b8864dc3..b70bbe9e 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -277,6 +277,9 @@ func main() { fmt.Println("Coords:", coords) } if *verbose { + if nodeID, ok := v.(map[string]interface{})["node_id"].(string); ok { + fmt.Println("Node ID:", nodeID) + } if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok { fmt.Println("Public encryption key:", boxPubKey) } diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index a9595f8c..0f91cd15 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -640,7 +640,9 @@ func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error func (a *admin) getData_getSelf() *admin_nodeInfo { table := a.core.switchTable.table.Load().(lookupTable) coords := table.self.getCoords() + nodeid := *crypto.GetNodeID(&a.core.boxPub) self := admin_nodeInfo{ + {"node_id", hex.EncodeToString(nodeid[:])}, {"box_pub_key", hex.EncodeToString(a.core.boxPub[:])}, {"ip", a.core.Address().String()}, {"subnet", a.core.Subnet().String()}, diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 9181269d..0b38525b 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -6,11 +6,13 @@ import ( "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) func (c *Core) Dial(network, address string) (Conn, error) { - var nodeID *crypto.NodeID - var nodeMask *crypto.NodeID + conn := Conn{} + nodeID := crypto.NodeID{} + nodeMask := crypto.NodeID{} // Process switch network { case "nodeid": @@ -20,22 +22,51 @@ func (c *Core) Dial(network, address string) (Conn, error) { return Conn{}, err } copy(nodeID[:], dest) - var m crypto.NodeID - for i := range dest { - m[i] = 0xFF + for i := range nodeMask { + nodeMask[i] = 0xFF } - copy(nodeMask[:], m[:]) default: // An unexpected address type was given, so give up return Conn{}, errors.New("unexpected address type") } + conn.core = c + conn.nodeID = &nodeID + conn.nodeMask = &nodeMask + conn.core.router.doAdmin(func() { + conn.startSearch() + }) + return conn, nil +} + +type Conn struct { + core *Core + nodeID *crypto.NodeID + nodeMask *crypto.NodeID + session *sessionInfo + readDeadline time.Time + writeDeadline time.Time +} + +// This method should only be called from the router goroutine +func (c *Conn) startSearch() { + searchCompleted := func(sinfo *sessionInfo, err error) { + if err != nil { + c.core.log.Debugln("DHT search failed:", err) + return + } + if sinfo != nil { + c.session = sinfo + c.core.log.Println("Search from API found", hex.EncodeToString(sinfo.theirPermPub[:])) + } + } // Try and search for the node on the network doSearch := func() { - sinfo, isIn := c.searches.searches[*nodeID] + sinfo, isIn := c.core.searches.searches[*c.nodeID] if !isIn { - sinfo = c.searches.newIterSearch(nodeID, nodeMask) + c.core.log.Debugln("Starting search for", hex.EncodeToString(c.nodeID[:])) + sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) } - c.searches.continueSearch(sinfo) + c.core.searches.continueSearch(sinfo) } var sinfo *sessionInfo var isIn bool @@ -61,27 +92,62 @@ func (c *Core) Dial(network, address string) (Conn, error) { if time.Since(sinfo.pingSend) > time.Second { // Send at most 1 ping per second sinfo.pingSend = now - c.sessions.sendPingPong(sinfo, false) + c.core.sessions.sendPingPong(sinfo, false) } } } - return Conn{ - session: sinfo, - }, nil -} - -type Conn struct { - session *sessionInfo - readDeadline time.Time - writeDeadline time.Time } func (c *Conn) Read(b []byte) (int, error) { - return 0, nil + if c.session == nil { + return 0, errors.New("invalid session") + } + p := <-c.session.recv + defer util.PutBytes(p.Payload) + if !c.session.nonceIsOK(&p.Nonce) { + return 0, errors.New("invalid nonce") + } + bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) + if !isOK { + util.PutBytes(bs) + return 0, errors.New("failed to decrypt") + } + b = b[:0] + b = append(b, bs...) + c.session.updateNonce(&p.Nonce) + c.session.time = time.Now() + c.session.bytesRecvd += uint64(len(bs)) + return len(b), nil } func (c *Conn) Write(b []byte) (int, error) { - return 0, nil + if c.session == nil { + c.core.router.doAdmin(func() { + c.startSearch() + }) + return 0, errors.New("invalid session") + } + defer util.PutBytes(b) + if !c.session.init { + // To prevent using empty session keys + return 0, errors.New("session not initialised") + } + // code isn't multithreaded so appending to this is safe + coords := c.session.coords + // Prepare the payload + payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) + defer util.PutBytes(payload) + p := wire_trafficPacket{ + Coords: coords, + Handle: c.session.theirHandle, + Nonce: *nonce, + Payload: payload, + } + packet := p.encode() + c.session.bytesSent += uint64(len(b)) + c.session.send <- packet + //c.session.core.router.out(packet) + return len(b), nil } func (c *Conn) Close() error { diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index a3d3d68c..d7923f51 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -245,6 +245,12 @@ func (r *router) sendPacket(bs []byte) { return } } + searchCompleted := func(sinfo *sessionInfo, err error) { + if err != nil { + r.core.log.Debugln("DHT search failed:", err) + return + } + } doSearch := func(packet []byte) { var nodeID, mask *crypto.NodeID switch { @@ -270,7 +276,7 @@ func (r *router) sendPacket(bs []byte) { } sinfo, isIn := r.core.searches.searches[*nodeID] if !isIn { - sinfo = r.core.searches.newIterSearch(nodeID, mask) + sinfo = r.core.searches.newIterSearch(nodeID, mask, searchCompleted) } if packet != nil { sinfo.packet = packet diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index c391dda2..e81a9723 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -15,6 +15,7 @@ package yggdrasil // Some kind of max search steps, in case the node is offline, so we don't crawl through too much of the network looking for a destination that isn't there? import ( + "errors" "sort" "time" @@ -32,12 +33,13 @@ const search_RETRY_TIME = time.Second // Information about an ongoing search. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. type searchInfo struct { - dest crypto.NodeID - mask crypto.NodeID - time time.Time - packet []byte - toVisit []*dhtInfo - visited map[crypto.NodeID]bool + dest crypto.NodeID + mask crypto.NodeID + time time.Time + packet []byte + toVisit []*dhtInfo + visited map[crypto.NodeID]bool + callback func(*sessionInfo, error) } // This stores a map of active searches. @@ -61,7 +63,7 @@ func (s *searches) init(core *Core) { } // Creates a new search info, adds it to the searches struct, and returns a pointer to the info. -func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo { +func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { now := time.Now() for dest, sinfo := range s.searches { if now.Sub(sinfo.time) > time.Minute { @@ -69,9 +71,10 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searc } } info := searchInfo{ - dest: *dest, - mask: *mask, - time: now.Add(-time.Second), + dest: *dest, + mask: *mask, + time: now.Add(-time.Second), + callback: callback, } s.searches[*dest] = &info return &info @@ -137,15 +140,15 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { if len(sinfo.toVisit) == 0 { // Dead end, do cleanup delete(s.searches, sinfo.dest) + sinfo.callback(nil, errors.New("search reached dead end")) return - } else { - // Send to the next search target - var next *dhtInfo - next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] - rq := dhtReqKey{next.key, sinfo.dest} - s.core.dht.addCallback(&rq, s.handleDHTRes) - s.core.dht.ping(next, &sinfo.dest) } + // Send to the next search target + var next *dhtInfo + next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] + rq := dhtReqKey{next.key, sinfo.dest} + s.core.dht.addCallback(&rq, s.handleDHTRes) + s.core.dht.ping(next, &sinfo.dest) } // If we've recenty sent a ping for this search, do nothing. @@ -173,8 +176,8 @@ 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 *crypto.NodeID, mask *crypto.NodeID) *searchInfo { - sinfo := s.createSearch(dest, mask) +func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { + sinfo := s.createSearch(dest, mask, callback) sinfo.toVisit = s.core.dht.lookup(dest, true) sinfo.visited = make(map[crypto.NodeID]bool) return sinfo @@ -200,6 +203,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { sinfo = s.core.sessions.createSession(&res.Key) if sinfo == nil { // nil if the DHT search finished but the session wasn't allowed + info.callback(nil, errors.New("session not allowed")) return true } _, isIn := s.core.sessions.getByTheirPerm(&res.Key) @@ -211,6 +215,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { sinfo.coords = res.Coords sinfo.packet = info.packet s.core.sessions.ping(sinfo) + info.callback(sinfo, nil) // Cleanup delete(s.searches, res.Dest) return true diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 74255c09..fd3a985b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -7,6 +7,7 @@ package yggdrasil import ( "bytes" "encoding/hex" + "sync" "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -17,35 +18,38 @@ 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 - reconfigure chan chan error - 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? - 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 + reconfigure chan chan error + 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 + theirNonceMutex sync.RWMutex // protects the above + myNonce crypto.BoxNonce + myNonceMutex sync.RWMutex // protects the above + 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 + tstampMutex int64 // protects the above + 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. @@ -101,17 +105,14 @@ func (s *sessionInfo) timedout() bool { // Sessions are indexed by handle. // Additionally, stores maps of address/subnet onto keys, and keys onto handles. type sessions struct { - core *Core - reconfigure chan chan error - lastCleanup time.Time - // Maps known permanent keys to their shared key, used by DHT a lot - permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey - // Maps (secret) handle onto session info - sinfos map[crypto.Handle]*sessionInfo - // Maps mySesPub onto handle - byMySes map[crypto.BoxPubKey]*crypto.Handle - // Maps theirPermPub onto handle - byTheirPerm map[crypto.BoxPubKey]*crypto.Handle + core *Core + reconfigure chan chan error + lastCleanup time.Time + permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot + sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info + conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections + byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle + byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle addrToPerm map[address.Address]*crypto.BoxPubKey subnetToPerm map[address.Subnet]*crypto.BoxPubKey } From b2f4f2e1b66cef8008e002d1e253d55c5275b186 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 00:07:26 +0100 Subject: [PATCH 03/60] Update errors, update Write --- cmd/yggdrasil/main.go | 3 ++- src/yggdrasil/api.go | 10 +++++----- src/yggdrasil/session.go | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 36866b3f..8a810bc5 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -271,7 +271,8 @@ func main() { // Some stuff go func() { time.Sleep(time.Second * 2) - session, err := n.core.Dial("nodeid", "babd4e4bccb216f77bb723c1b034b63a652060aabfe9506b51f687183e9b0fd13f438876f5a3ab21cac9c8101eb88e2613fe2a8b0724add09d7ef5a72146c31f") + //session, err := n.core.Dial("nodeid", "babd4e4bccb216f77bb723c1b034b63a652060aabfe9506b51f687183e9b0fd13f438876f5a3ab21cac9c8101eb88e2613fe2a8b0724add09d7ef5a72146c31f") + session, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") logger.Println(session, err) b := []byte{1, 2, 3, 4, 5} for { diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 0b38525b..41ef8c06 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -100,17 +100,17 @@ func (c *Conn) startSearch() { func (c *Conn) Read(b []byte) (int, error) { if c.session == nil { - return 0, errors.New("invalid session") + return 0, errors.New("session not open") } p := <-c.session.recv defer util.PutBytes(p.Payload) if !c.session.nonceIsOK(&p.Nonce) { - return 0, errors.New("invalid nonce") + return 0, errors.New("packet dropped due to invalid nonce") } bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) if !isOK { util.PutBytes(bs) - return 0, errors.New("failed to decrypt") + return 0, errors.New("packet dropped due to decryption failure") } b = b[:0] b = append(b, bs...) @@ -125,7 +125,7 @@ func (c *Conn) Write(b []byte) (int, error) { c.core.router.doAdmin(func() { c.startSearch() }) - return 0, errors.New("invalid session") + return 0, errors.New("session not open") } defer util.PutBytes(b) if !c.session.init { @@ -146,7 +146,7 @@ func (c *Conn) Write(b []byte) (int, error) { packet := p.encode() c.session.bytesSent += uint64(len(b)) c.session.send <- packet - //c.session.core.router.out(packet) + c.session.core.router.out(packet) return len(b), nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index fd3a985b..4c896f2a 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -523,7 +523,7 @@ func (ss *sessions) resetInits() { } //////////////////////////////////////////////////////////////////////////////// - +/* // This is for a per-session worker. // It handles calling the relatively expensive crypto operations. // It's also responsible for checking nonces and dropping out-of-date/duplicate packets, or else calling the function to update nonces if the packet is OK. From b20c8b6da5029d49d8beb74f204a90eb2e91e8b5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 00:11:43 +0100 Subject: [PATCH 04/60] Move some things around a bit, delete session workers --- src/yggdrasil/{api.go => conn.go} | 29 ------- src/yggdrasil/core.go | 32 ++++++++ src/yggdrasil/session.go | 130 ------------------------------ 3 files changed, 32 insertions(+), 159 deletions(-) rename src/yggdrasil/{api.go => conn.go} (85%) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/conn.go similarity index 85% rename from src/yggdrasil/api.go rename to src/yggdrasil/conn.go index 41ef8c06..0ce626c9 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/conn.go @@ -9,35 +9,6 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/util" ) -func (c *Core) Dial(network, address string) (Conn, error) { - conn := Conn{} - nodeID := crypto.NodeID{} - nodeMask := crypto.NodeID{} - // Process - switch network { - case "nodeid": - // A node ID was provided - we don't need to do anything special with it - dest, err := hex.DecodeString(address) - if err != nil { - return Conn{}, err - } - copy(nodeID[:], dest) - for i := range nodeMask { - nodeMask[i] = 0xFF - } - default: - // An unexpected address type was given, so give up - return Conn{}, errors.New("unexpected address type") - } - conn.core = c - conn.nodeID = &nodeID - conn.nodeMask = &nodeMask - conn.core.router.doAdmin(func() { - conn.startSearch() - }) - return conn, nil -} - type Conn struct { core *Core nodeID *crypto.NodeID diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 037ef094..22caf08c 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -254,6 +254,38 @@ func (c *Core) Stop() { c.admin.close() } +// Dial opens a session to the given node. The first paramter should be "nodeid" +// and the second parameter should contain a hexadecimal representation of the +// target node ID. +func (c *Core) Dial(network, address string) (Conn, error) { + conn := Conn{} + nodeID := crypto.NodeID{} + nodeMask := crypto.NodeID{} + // Process + switch network { + case "nodeid": + // A node ID was provided - we don't need to do anything special with it + dest, err := hex.DecodeString(address) + if err != nil { + return Conn{}, err + } + copy(nodeID[:], dest) + for i := range nodeMask { + nodeMask[i] = 0xFF + } + default: + // An unexpected address type was given, so give up + return Conn{}, errors.New("unexpected address type") + } + conn.core = c + conn.nodeID = &nodeID + conn.nodeMask = &nodeMask + conn.core.router.doAdmin(func() { + conn.startSearch() + }) + return conn, nil +} + // ListenTCP starts a new TCP listener. The input URI should match that of the // "Listen" configuration item, e.g. // tcp://a.b.c.d:e diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 4c896f2a..a1835459 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -12,7 +12,6 @@ import ( "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. @@ -307,7 +306,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.send = make(chan []byte, 32) sinfo.recv = make(chan *wire_trafficPacket, 32) - go sinfo.doWorker() ss.sinfos[sinfo.myHandle] = &sinfo ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle @@ -521,131 +519,3 @@ func (ss *sessions) resetInits() { sinfo.init = false } } - -//////////////////////////////////////////////////////////////////////////////// -/* -// This is for a per-session worker. -// It handles calling the relatively expensive crypto operations. -// It's also responsible for checking nonces and dropping out-of-date/duplicate packets, or else calling the function to update nonces if the packet is OK. -func (sinfo *sessionInfo) doWorker() { - send := make(chan []byte, 32) - defer close(send) - go func() { - for bs := range send { - sinfo.doSend(bs) - } - }() - recv := make(chan *wire_trafficPacket, 32) - defer close(recv) - go func() { - for p := range recv { - sinfo.doRecv(p) - } - }() - for { - select { - case p, ok := <-sinfo.recv: - if ok { - select { - case recv <- p: - default: - // We need something to not block, and it's best to drop it before we decrypt - util.PutBytes(p.Payload) - } - } else { - return - } - case bs, ok := <-sinfo.send: - if ok { - send <- bs - } else { - return - } - case e := <-sinfo.reconfigure: - e <- nil - } - } -} - -// 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) - if !sinfo.init { - // To prevent using empty session keys - return - } - // code isn't multithreaded so appending to this is safe - coords := sinfo.coords - // Work out the flowkey - this is used to determine which switch queue - // traffic will be pushed to in the event of congestion - var flowkey uint64 - // Get the IP protocol version from the packet - switch bs[0] & 0xf0 { - case 0x40: // IPv4 packet - // Check the packet meets minimum UDP packet length - if len(bs) >= 24 { - // Is the protocol TCP, UDP or SCTP? - if bs[9] == 0x06 || bs[9] == 0x11 || bs[9] == 0x84 { - ihl := bs[0] & 0x0f * 4 // Header length - flowkey = uint64(bs[9])<<32 /* proto */ | - uint64(bs[ihl+0])<<24 | uint64(bs[ihl+1])<<16 /* sport */ | - uint64(bs[ihl+2])<<8 | uint64(bs[ihl+3]) /* dport */ - } - } - case 0x60: // IPv6 packet - // Check if the flowlabel was specified in the packet header - flowkey = uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3]) - // If the flowlabel isn't present, make protokey from proto | sport | dport - // if the packet meets minimum UDP packet length - if flowkey == 0 && len(bs) >= 48 { - // Is the protocol TCP, UDP or SCTP? - if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 { - flowkey = uint64(bs[6])<<32 /* proto */ | - uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | - uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ - } - } - } - // If we have a flowkey, either through the IPv6 flowlabel field or through - // known TCP/UDP/SCTP proto-sport-dport triplet, then append it to the coords. - // Appending extra coords after a 0 ensures that we still target the local router - // but lets us send extra data (which is otherwise ignored) to help separate - // traffic streams into independent queues - if flowkey != 0 { - coords = append(coords, 0) // First target the local switchport - coords = wire_put_uint64(flowkey, coords) // Then variable-length encoded flowkey - } - // Prepare the payload - payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) - defer util.PutBytes(payload) - p := wire_trafficPacket{ - Coords: coords, - Handle: sinfo.theirHandle, - Nonce: *nonce, - Payload: payload, - } - packet := p.encode() - sinfo.bytesSent += uint64(len(bs)) - sinfo.core.router.out(packet) -} - -// This takes a trafficPacket and checks the nonce. -// If the nonce is OK, it decrypts the packet. -// If the decrypted packet is OK, it calls router.recvPacket to pass the packet to the tun/tap. -// 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) - if !sinfo.nonceIsOK(&p.Nonce) { - return - } - bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) - if !isOK { - util.PutBytes(bs) - return - } - sinfo.updateNonce(&p.Nonce) - sinfo.time = time.Now() - sinfo.bytesRecvd += uint64(len(bs)) - sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo} -} From c59372136212a785e9c2415845bde076a83c5a3b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 00:33:54 +0100 Subject: [PATCH 05/60] Tweaks --- src/yggdrasil/conn.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 0ce626c9..bcf38ede 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -27,10 +27,11 @@ func (c *Conn) startSearch() { } if sinfo != nil { c.session = sinfo - c.core.log.Println("Search from API found", hex.EncodeToString(sinfo.theirPermPub[:])) + c.core.log.Println("Search from API succeeded") + c.core.log.Println("Pubkey:", hex.EncodeToString(sinfo.theirPermPub[:])) + c.core.log.Println("Coords:", sinfo.coords) } } - // Try and search for the node on the network doSearch := func() { sinfo, isIn := c.core.searches.searches[*c.nodeID] if !isIn { @@ -43,25 +44,16 @@ func (c *Conn) startSearch() { var isIn bool switch { case !isIn || !sinfo.init: - // No or unintiialized session, so we need to search first doSearch() case time.Since(sinfo.time) > 6*time.Second: if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { - // We haven't heard from the dest in a while - // We tried pinging but didn't get a response - // They may have changed coords - // Try searching to discover new coords - // Note that search spam is throttled internally doSearch() } else { - // We haven't heard about the dest in a while now := time.Now() if !sinfo.time.Before(sinfo.pingTime) { - // Update pingTime to start the clock for searches (above) sinfo.pingTime = now } if time.Since(sinfo.pingSend) > time.Second { - // Send at most 1 ping per second sinfo.pingSend = now c.core.sessions.sendPingPong(sinfo, false) } @@ -73,6 +65,10 @@ func (c *Conn) Read(b []byte) (int, error) { if c.session == nil { return 0, errors.New("session not open") } + if !c.session.init { + // To prevent blocking forever on a session that isn't initialised + return 0, errors.New("session not initialised") + } p := <-c.session.recv defer util.PutBytes(p.Payload) if !c.session.nonceIsOK(&p.Nonce) { From ade684beffbaeb3e46f4afb623ed2f6e1bc5bc41 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 10:55:15 +0100 Subject: [PATCH 06/60] Signal when a session is closed, other tweaks --- cmd/yggdrasil/main.go | 14 ------------ src/yggdrasil/conn.go | 48 +++++++++++++++++++++++----------------- src/yggdrasil/session.go | 4 ++++ 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 8a810bc5..fd8cd7b6 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -10,7 +10,6 @@ import ( "os/signal" "strings" "syscall" - "time" "golang.org/x/text/encoding/unicode" @@ -268,19 +267,6 @@ func main() { defer func() { n.core.Stop() }() - // Some stuff - go func() { - time.Sleep(time.Second * 2) - //session, err := n.core.Dial("nodeid", "babd4e4bccb216f77bb723c1b034b63a652060aabfe9506b51f687183e9b0fd13f438876f5a3ab21cac9c8101eb88e2613fe2a8b0724add09d7ef5a72146c31f") - session, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") - logger.Println(session, err) - b := []byte{1, 2, 3, 4, 5} - for { - logger.Println(session.Write(b)) - logger.Println(session.Read(b)) - time.Sleep(time.Second) - } - }() // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index bcf38ede..7aa46554 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -27,9 +27,6 @@ func (c *Conn) startSearch() { } if sinfo != nil { c.session = sinfo - c.core.log.Println("Search from API succeeded") - c.core.log.Println("Pubkey:", hex.EncodeToString(sinfo.theirPermPub[:])) - c.core.log.Println("Coords:", sinfo.coords) } } doSearch := func() { @@ -69,25 +66,32 @@ func (c *Conn) Read(b []byte) (int, error) { // To prevent blocking forever on a session that isn't initialised return 0, errors.New("session not initialised") } - p := <-c.session.recv - defer util.PutBytes(p.Payload) - if !c.session.nonceIsOK(&p.Nonce) { - return 0, errors.New("packet dropped due to invalid nonce") + select { + case p, ok := <-c.session.recv: + if !ok { + return 0, errors.New("session was closed") + } + defer util.PutBytes(p.Payload) + if !c.session.nonceIsOK(&p.Nonce) { + return 0, errors.New("packet dropped due to invalid nonce") + } + bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) + if !isOK { + util.PutBytes(bs) + return 0, errors.New("packet dropped due to decryption failure") + } + b = b[:0] + b = append(b, bs...) + c.session.updateNonce(&p.Nonce) + c.session.time = time.Now() + c.session.bytesRecvd += uint64(len(bs)) + return len(b), nil + case <-c.session.closed: + return len(b), errors.New("session was closed") } - bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) - if !isOK { - util.PutBytes(bs) - return 0, errors.New("packet dropped due to decryption failure") - } - b = b[:0] - b = append(b, bs...) - c.session.updateNonce(&p.Nonce) - c.session.time = time.Now() - c.session.bytesRecvd += uint64(len(bs)) - return len(b), nil } -func (c *Conn) Write(b []byte) (int, error) { +func (c *Conn) Write(b []byte) (bytesWritten int, err error) { if c.session == nil { c.core.router.doAdmin(func() { c.startSearch() @@ -112,7 +116,11 @@ func (c *Conn) Write(b []byte) (int, error) { } packet := p.encode() c.session.bytesSent += uint64(len(b)) - c.session.send <- packet + select { + case c.session.send <- packet: + case <-c.session.closed: + return len(b), errors.New("session was closed") + } c.session.core.router.out(packet) return len(b), nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index a1835459..e860dc81 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -41,6 +41,7 @@ type sessionInfo struct { init bool // Reset if coords change send chan []byte recv chan *wire_trafficPacket + closed chan interface{} nonceMask uint64 tstamp int64 // tstamp from their last session ping, replay attack mitigation tstampMutex int64 // protects the above @@ -306,6 +307,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.send = make(chan []byte, 32) sinfo.recv = make(chan *wire_trafficPacket, 32) + sinfo.closed = make(chan interface{}) ss.sinfos[sinfo.myHandle] = &sinfo ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle @@ -364,6 +366,8 @@ func (ss *sessions) cleanup() { // Closes a session, removing it from sessions maps and killing the worker goroutine. func (sinfo *sessionInfo) close() { + sinfo.init = false + close(sinfo.closed) delete(sinfo.core.sessions.sinfos, sinfo.myHandle) delete(sinfo.core.sessions.byMySes, sinfo.mySesPub) delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) From e3eadba4b7f95a151461303c098e68bfa6832eb7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 20:10:41 +0100 Subject: [PATCH 07/60] Protect session nonces with mutexes, modify sent/received bytes atomically --- src/yggdrasil/conn.go | 7 +++++-- src/yggdrasil/session.go | 28 ++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 7aa46554..fd65743c 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -3,6 +3,7 @@ package yggdrasil import ( "encoding/hex" "errors" + "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -84,7 +85,7 @@ func (c *Conn) Read(b []byte) (int, error) { b = append(b, bs...) c.session.updateNonce(&p.Nonce) c.session.time = time.Now() - c.session.bytesRecvd += uint64(len(bs)) + atomic.AddUint64(&c.session.bytesRecvd, uint64(len(b))) return len(b), nil case <-c.session.closed: return len(b), errors.New("session was closed") @@ -106,7 +107,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // code isn't multithreaded so appending to this is safe coords := c.session.coords // Prepare the payload + c.session.myNonceMutex.Lock() payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) + c.session.myNonceMutex.Unlock() defer util.PutBytes(payload) p := wire_trafficPacket{ Coords: coords, @@ -115,7 +118,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { Payload: payload, } packet := p.encode() - c.session.bytesSent += uint64(len(b)) + atomic.AddUint64(&c.session.bytesSent, uint64(len(b))) select { case c.session.send <- packet: case <-c.session.closed: diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index e860dc81..e46761d5 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -29,9 +29,10 @@ type sessionInfo struct { theirHandle crypto.Handle myHandle crypto.Handle theirNonce crypto.BoxNonce - theirNonceMutex sync.RWMutex // protects the above + theirNonceMask uint64 + theirNonceMutex sync.Mutex // protects the above myNonce crypto.BoxNonce - myNonceMutex sync.RWMutex // protects the above + myNonceMutex sync.Mutex // protects the above theirMTU uint16 myMTU uint16 wasMTUFixed bool // Was the MTU fixed by a receive error? @@ -42,7 +43,6 @@ type sessionInfo struct { send chan []byte recv chan *wire_trafficPacket closed chan interface{} - nonceMask uint64 tstamp int64 // tstamp from their last session ping, replay attack mitigation tstampMutex int64 // protects the above mtuTime time.Time // time myMTU was last changed @@ -79,8 +79,10 @@ func (s *sessionInfo) update(p *sessionPing) bool { s.theirSesPub = p.SendSesPub s.theirHandle = p.Handle s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub) + s.theirNonceMutex.Lock() s.theirNonce = crypto.BoxNonce{} - s.nonceMask = 0 + s.theirNonceMask = 0 + s.theirNonceMutex.Unlock() } if p.MTU >= 1280 || p.MTU == 0 { s.theirMTU = p.MTU @@ -270,6 +272,10 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { return nil } sinfo := sessionInfo{} + sinfo.myNonceMutex.Lock() + sinfo.theirNonceMutex.Lock() + defer sinfo.myNonceMutex.Unlock() + defer sinfo.theirNonceMutex.Unlock() sinfo.core = ss.core sinfo.reconfigure = make(chan chan error, 1) sinfo.theirPermPub = *theirPermKey @@ -389,7 +395,9 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing { Coords: coords, MTU: sinfo.myMTU, } + sinfo.myNonceMutex.Lock() sinfo.myNonce.Increment() + sinfo.myNonceMutex.Unlock() return ref } @@ -493,26 +501,30 @@ 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 *crypto.BoxNonce) bool { // The bitmask is to allow for some non-duplicate out-of-order packets + sinfo.theirNonceMutex.Lock() + defer sinfo.theirNonceMutex.Unlock() diff := theirNonce.Minus(&sinfo.theirNonce) if diff > 0 { return true } - return ^sinfo.nonceMask&(0x01< 0 { // This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info. - sinfo.nonceMask <<= uint64(diff) - sinfo.nonceMask &= 0x01 + sinfo.theirNonceMask <<= uint64(diff) + sinfo.theirNonceMask &= 0x01 sinfo.theirNonce = *theirNonce } else { // This nonce is older, so set the bit but do not shift the window. - sinfo.nonceMask &= 0x01 << uint64(-diff) + sinfo.theirNonceMask &= 0x01 << uint64(-diff) } } From 27b78b925df96183b7e45fd5f3a4802e3c36cc98 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 21:23:15 +0100 Subject: [PATCH 08/60] Move mutexes around --- src/yggdrasil/conn.go | 30 +++++++++++++++++++----------- src/yggdrasil/session.go | 12 ------------ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index fd65743c..e337b34f 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -73,18 +73,26 @@ func (c *Conn) Read(b []byte) (int, error) { return 0, errors.New("session was closed") } defer util.PutBytes(p.Payload) - if !c.session.nonceIsOK(&p.Nonce) { - return 0, errors.New("packet dropped due to invalid nonce") + err := func() error { + c.session.theirNonceMutex.Lock() + defer c.session.theirNonceMutex.Unlock() + if !c.session.nonceIsOK(&p.Nonce) { + return errors.New("packet dropped due to invalid nonce") + } + bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) + if !isOK { + util.PutBytes(bs) + return errors.New("packet dropped due to decryption failure") + } + b = b[:0] + b = append(b, bs...) + c.session.updateNonce(&p.Nonce) + c.session.time = time.Now() + return nil + }() + if err != nil { + return 0, err } - bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) - if !isOK { - util.PutBytes(bs) - return 0, errors.New("packet dropped due to decryption failure") - } - b = b[:0] - b = append(b, bs...) - c.session.updateNonce(&p.Nonce) - c.session.time = time.Now() atomic.AddUint64(&c.session.bytesRecvd, uint64(len(b))) return len(b), nil case <-c.session.closed: diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index e46761d5..c5319bc7 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -79,10 +79,8 @@ func (s *sessionInfo) update(p *sessionPing) bool { s.theirSesPub = p.SendSesPub s.theirHandle = p.Handle s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub) - s.theirNonceMutex.Lock() s.theirNonce = crypto.BoxNonce{} s.theirNonceMask = 0 - s.theirNonceMutex.Unlock() } if p.MTU >= 1280 || p.MTU == 0 { s.theirMTU = p.MTU @@ -272,10 +270,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { return nil } sinfo := sessionInfo{} - sinfo.myNonceMutex.Lock() - sinfo.theirNonceMutex.Lock() - defer sinfo.myNonceMutex.Unlock() - defer sinfo.theirNonceMutex.Unlock() sinfo.core = ss.core sinfo.reconfigure = make(chan chan error, 1) sinfo.theirPermPub = *theirPermKey @@ -395,9 +389,7 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing { Coords: coords, MTU: sinfo.myMTU, } - sinfo.myNonceMutex.Lock() sinfo.myNonce.Increment() - sinfo.myNonceMutex.Unlock() return ref } @@ -501,8 +493,6 @@ 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 *crypto.BoxNonce) bool { // The bitmask is to allow for some non-duplicate out-of-order packets - sinfo.theirNonceMutex.Lock() - defer sinfo.theirNonceMutex.Unlock() diff := theirNonce.Minus(&sinfo.theirNonce) if diff > 0 { return true @@ -512,8 +502,6 @@ func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.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 *crypto.BoxNonce) { - sinfo.theirNonceMutex.Lock() - defer sinfo.theirNonceMutex.Unlock() // Shift nonce mask if needed // Set bit diff := theirNonce.Minus(&sinfo.theirNonce) From aac88adbedeb70a7f35068ee2ad5db0a883283b0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 22:57:52 +0100 Subject: [PATCH 09/60] Listen-Accept-Read-Write pattern now works, amazingly --- src/yggdrasil/conn.go | 15 +++++++------- src/yggdrasil/core.go | 14 +++++++++++++ src/yggdrasil/listener.go | 41 +++++++++++++++++++++++++++++++++++++++ src/yggdrasil/session.go | 38 ++++++++++++++++++++++++++---------- 4 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 src/yggdrasil/listener.go diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index e337b34f..a1515375 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -61,11 +61,10 @@ func (c *Conn) startSearch() { func (c *Conn) Read(b []byte) (int, error) { if c.session == nil { - return 0, errors.New("session not open") + return 0, errors.New("session not ready yet") } if !c.session.init { - // To prevent blocking forever on a session that isn't initialised - return 0, errors.New("session not initialised") + return 0, errors.New("waiting for remote side to accept") } select { case p, ok := <-c.session.recv: @@ -84,6 +83,7 @@ func (c *Conn) Read(b []byte) (int, error) { util.PutBytes(bs) return errors.New("packet dropped due to decryption failure") } + // c.core.log.Println("HOW MANY BYTES?", len(bs)) b = b[:0] b = append(b, bs...) c.session.updateNonce(&p.Nonce) @@ -96,7 +96,7 @@ func (c *Conn) Read(b []byte) (int, error) { atomic.AddUint64(&c.session.bytesRecvd, uint64(len(b))) return len(b), nil case <-c.session.closed: - return len(b), errors.New("session was closed") + return len(b), errors.New("session closed") } } @@ -105,12 +105,12 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.core.router.doAdmin(func() { c.startSearch() }) - return 0, errors.New("session not open") + return 0, errors.New("session not ready yet") } defer util.PutBytes(b) if !c.session.init { // To prevent using empty session keys - return 0, errors.New("session not initialised") + return 0, errors.New("waiting for remote side to accept") } // code isn't multithreaded so appending to this is safe coords := c.session.coords @@ -130,13 +130,14 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { select { case c.session.send <- packet: case <-c.session.closed: - return len(b), errors.New("session was closed") + return len(b), errors.New("session closed") } c.session.core.router.out(packet) return len(b), nil } func (c *Conn) Close() error { + c.session.close() return nil } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 22caf08c..dba1c645 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -254,6 +254,20 @@ func (c *Core) Stop() { c.admin.close() } +// ListenConn returns a listener for Yggdrasil session connections. +func (c *Core) ListenConn() (*Listener, error) { + c.sessions.listenerMutex.Lock() + defer c.sessions.listenerMutex.Unlock() + if c.sessions.listener != nil { + return nil, errors.New("a listener already exists") + } + c.sessions.listener = &Listener{ + conn: make(chan *Conn), + close: make(chan interface{}), + } + return c.sessions.listener, nil +} + // Dial opens a session to the given node. The first paramter should be "nodeid" // and the second parameter should contain a hexadecimal representation of the // target node ID. diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go new file mode 100644 index 00000000..268d8b78 --- /dev/null +++ b/src/yggdrasil/listener.go @@ -0,0 +1,41 @@ +package yggdrasil + +import ( + "errors" + "net" +) + +// Listener waits for incoming sessions +type Listener struct { + conn chan *Conn + close chan interface{} +} + +// Accept blocks until a new incoming session is received +func (l *Listener) Accept() (*Conn, error) { + select { + case c, ok := <-l.conn: + if !ok { + return nil, errors.New("listener closed") + } + return c, nil + case <-l.close: + return nil, errors.New("listener closed") + } +} + +// Close will stop the listener +func (l *Listener) Close() (err error) { + defer func() { + recover() + err = errors.New("already closed") + }() + close(l.close) + close(l.conn) + return nil +} + +// Addr is not implemented for this type yet +func (l *Listener) Addr() net.Addr { + return nil +} diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index c5319bc7..64b5d291 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -105,16 +105,18 @@ func (s *sessionInfo) timedout() bool { // Sessions are indexed by handle. // Additionally, stores maps of address/subnet onto keys, and keys onto handles. type sessions struct { - core *Core - reconfigure chan chan error - lastCleanup time.Time - permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot - sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info - conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections - byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle - byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle - addrToPerm map[address.Address]*crypto.BoxPubKey - subnetToPerm map[address.Subnet]*crypto.BoxPubKey + core *Core + listener *Listener + listenerMutex sync.Mutex + reconfigure chan chan error + lastCleanup time.Time + permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot + sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info + conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections + byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle + byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle + addrToPerm map[address.Address]*crypto.BoxPubKey + subnetToPerm map[address.Subnet]*crypto.BoxPubKey } // Initializes the session struct. @@ -461,6 +463,22 @@ func (ss *sessions) handlePing(ping *sessionPing) { if !isIn { panic("This should not happen") } + ss.listenerMutex.Lock() + if ss.listener != nil { + conn := &Conn{ + core: ss.core, + session: sinfo, + nodeID: crypto.GetNodeID(&sinfo.theirPermPub), + nodeMask: &crypto.NodeID{}, + } + for i := range conn.nodeMask { + conn.nodeMask[i] = 0xFF + } + ss.listener.conn <- conn + } else { + ss.core.log.Debugln("Received new session but there is no listener, ignoring") + } + ss.listenerMutex.Unlock() } // Update the session if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ From 7e726b0afbea2fb1aafa406a1a5de96d538f9ea4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 23:04:09 +0100 Subject: [PATCH 10/60] Listener should clean up a bit more when closing --- src/yggdrasil/core.go | 1 + src/yggdrasil/listener.go | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index dba1c645..b5d74e8c 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -262,6 +262,7 @@ func (c *Core) ListenConn() (*Listener, error) { return nil, errors.New("a listener already exists") } c.sessions.listener = &Listener{ + core: c, conn: make(chan *Conn), close: make(chan interface{}), } diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go index 268d8b78..62225412 100644 --- a/src/yggdrasil/listener.go +++ b/src/yggdrasil/listener.go @@ -7,6 +7,7 @@ import ( // Listener waits for incoming sessions type Listener struct { + core *Core conn chan *Conn close chan interface{} } @@ -30,6 +31,9 @@ func (l *Listener) Close() (err error) { recover() err = errors.New("already closed") }() + if l.core.sessions.listener == l { + l.core.sessions.listener = nil + } close(l.close) close(l.conn) return nil From e31b914e384bcd5e60bc7a6baa08439d7e669b0b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 23:30:43 +0100 Subject: [PATCH 11/60] Improve errors and handling of expired sessions --- src/yggdrasil/conn.go | 22 ++++++++++++++++------ src/yggdrasil/session.go | 1 - 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index a1515375..df45d12b 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -17,6 +17,7 @@ type Conn struct { session *sessionInfo readDeadline time.Time writeDeadline time.Time + expired bool } // This method should only be called from the router goroutine @@ -60,8 +61,11 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { + if c.expired { + return 0, errors.New("session is closed") + } if c.session == nil { - return 0, errors.New("session not ready yet") + return 0, errors.New("searching for remote side") } if !c.session.init { return 0, errors.New("waiting for remote side to accept") @@ -69,7 +73,8 @@ func (c *Conn) Read(b []byte) (int, error) { select { case p, ok := <-c.session.recv: if !ok { - return 0, errors.New("session was closed") + c.expired = true + return 0, errors.New("session is closed") } defer util.PutBytes(p.Payload) err := func() error { @@ -83,7 +88,6 @@ func (c *Conn) Read(b []byte) (int, error) { util.PutBytes(bs) return errors.New("packet dropped due to decryption failure") } - // c.core.log.Println("HOW MANY BYTES?", len(bs)) b = b[:0] b = append(b, bs...) c.session.updateNonce(&p.Nonce) @@ -96,16 +100,20 @@ func (c *Conn) Read(b []byte) (int, error) { atomic.AddUint64(&c.session.bytesRecvd, uint64(len(b))) return len(b), nil case <-c.session.closed: - return len(b), errors.New("session closed") + c.expired = true + return len(b), errors.New("session is closed") } } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { + if c.expired { + return 0, errors.New("session is closed") + } if c.session == nil { c.core.router.doAdmin(func() { c.startSearch() }) - return 0, errors.New("session not ready yet") + return 0, errors.New("searching for remote side") } defer util.PutBytes(b) if !c.session.init { @@ -130,13 +138,15 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { select { case c.session.send <- packet: case <-c.session.closed: - return len(b), errors.New("session closed") + c.expired = true + return len(b), errors.New("session is closed") } c.session.core.router.out(packet) return len(b), nil } func (c *Conn) Close() error { + c.expired = true c.session.close() return nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 64b5d291..40259d9b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -368,7 +368,6 @@ func (ss *sessions) cleanup() { // Closes a session, removing it from sessions maps and killing the worker goroutine. func (sinfo *sessionInfo) close() { - sinfo.init = false close(sinfo.closed) delete(sinfo.core.sessions.sinfos, sinfo.myHandle) delete(sinfo.core.sessions.byMySes, sinfo.mySesPub) From 693bcc5713f809a7a63874a7a06229e1f6f5cf80 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 23:30:57 +0100 Subject: [PATCH 12/60] Update sample in cmd/yggdrasil --- cmd/yggdrasil/main.go | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index fd8cd7b6..2267d62c 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -10,6 +10,7 @@ import ( "os/signal" "strings" "syscall" + "time" "golang.org/x/text/encoding/unicode" @@ -267,6 +268,67 @@ func main() { defer func() { n.core.Stop() }() + // Listen for new sessions + go func() { + listener, err := n.core.ListenConn() + if err != nil { + logger.Errorln("Unable to listen for sessions:", err) + return + } + for { + conn, err := listener.Accept() + if err != nil { + logger.Errorln("Accept:", err) + continue + } + logger.Println("Accepted") + for { + b := []byte{} + if n, err := conn.Read(b); err != nil { + logger.Errorln("Read failed:", err) + time.Sleep(time.Second * 2) + } else { + logger.Println("Read", n, "bytes:", b) + b = []byte{5, 5, 5} + if n, err := conn.Write(b); err != nil { + logger.Errorln("Write failed:", err) + time.Sleep(time.Second * 2) + } else { + logger.Println("Wrote", n, "bytes:", b) + } + } + } + } + }() + // Try creating new sessions + go func() { + if cfg.EncryptionPublicKey != "533574224115f835b7c7db6433986bc5aef855ff9c9568c01abeb0fbed3e8810" { + return + } + time.Sleep(time.Second * 2) + conn, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") + if err != nil { + logger.Errorln("Dial:", err) + return + } + go func() { + for { + time.Sleep(time.Second * 2) + b := []byte{1, 2, 3, 4, 5} + if n, err := conn.Write(b); err != nil { + logger.Errorln("Write failed:", err) + } else { + logger.Println("Wrote", n, "bytes:", b) + b = b[:0] + if n, err := conn.Read(b); err != nil { + logger.Errorln("Read failed:", err) + } else { + logger.Println("Read", n, "bytes:", b) + } + } + } + }() + }() // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() From 24281d4049acc6cfa1c4a88245160191fd209230 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 23:47:11 +0100 Subject: [PATCH 13/60] Fix Read, update sample --- cmd/yggdrasil/main.go | 4 ++-- src/yggdrasil/conn.go | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 2267d62c..0e3aa354 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -283,7 +283,7 @@ func main() { } logger.Println("Accepted") for { - b := []byte{} + b := make([]byte, 100) if n, err := conn.Read(b); err != nil { logger.Errorln("Read failed:", err) time.Sleep(time.Second * 2) @@ -319,7 +319,7 @@ func main() { logger.Errorln("Write failed:", err) } else { logger.Println("Wrote", n, "bytes:", b) - b = b[:0] + b = make([]byte, 100) if n, err := conn.Read(b); err != nil { logger.Errorln("Read failed:", err) } else { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index df45d12b..b936d4f9 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -88,8 +88,10 @@ func (c *Conn) Read(b []byte) (int, error) { util.PutBytes(bs) return errors.New("packet dropped due to decryption failure") } - b = b[:0] - b = append(b, bs...) + copy(b, bs) + if len(bs) < len(b) { + b = b[:len(bs)] + } c.session.updateNonce(&p.Nonce) c.session.time = time.Now() return nil From f3e742a297878ebf909c06d438af3d0e23a9bdc3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Apr 2019 11:53:38 +0100 Subject: [PATCH 14/60] Squash a whole load of races (and mutex half the world) --- src/yggdrasil/conn.go | 19 +++++++++++++- src/yggdrasil/core.go | 7 +++++- src/yggdrasil/router.go | 6 +++++ src/yggdrasil/session.go | 54 ++++++++++++++++++++++++++-------------- 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index b936d4f9..874a7a9c 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -3,6 +3,7 @@ package yggdrasil import ( "encoding/hex" "errors" + "sync" "sync/atomic" "time" @@ -15,6 +16,7 @@ type Conn struct { nodeID *crypto.NodeID nodeMask *crypto.NodeID session *sessionInfo + sessionMutex *sync.RWMutex readDeadline time.Time writeDeadline time.Time expired bool @@ -28,7 +30,9 @@ func (c *Conn) startSearch() { return } if sinfo != nil { + c.sessionMutex.Lock() c.session = sinfo + c.sessionMutex.Unlock() } } doSearch := func() { @@ -61,15 +65,20 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { + c.sessionMutex.RLock() + defer c.sessionMutex.RUnlock() if c.expired { return 0, errors.New("session is closed") } if c.session == nil { return 0, errors.New("searching for remote side") } + c.session.initMutex.RLock() if !c.session.init { + c.session.initMutex.RUnlock() return 0, errors.New("waiting for remote side to accept") } + c.session.initMutex.RUnlock() select { case p, ok := <-c.session.recv: if !ok { @@ -93,7 +102,9 @@ func (c *Conn) Read(b []byte) (int, error) { b = b[:len(bs)] } c.session.updateNonce(&p.Nonce) + c.session.timeMutex.Lock() c.session.time = time.Now() + c.session.timeMutex.Unlock() return nil }() if err != nil { @@ -108,6 +119,8 @@ func (c *Conn) Read(b []byte) (int, error) { } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { + c.sessionMutex.RLock() + defer c.sessionMutex.RUnlock() if c.expired { return 0, errors.New("session is closed") } @@ -118,12 +131,16 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return 0, errors.New("searching for remote side") } defer util.PutBytes(b) + c.session.initMutex.RLock() if !c.session.init { - // To prevent using empty session keys + c.session.initMutex.RUnlock() return 0, errors.New("waiting for remote side to accept") } + c.session.initMutex.RUnlock() // code isn't multithreaded so appending to this is safe + c.session.coordsMutex.RLock() coords := c.session.coords + c.session.coordsMutex.RUnlock() // Prepare the payload c.session.myNonceMutex.Lock() payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index b5d74e8c..cfba833b 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -5,6 +5,7 @@ import ( "errors" "io/ioutil" "net" + "sync" "time" "github.com/gologme/log" @@ -273,7 +274,9 @@ func (c *Core) ListenConn() (*Listener, error) { // and the second parameter should contain a hexadecimal representation of the // target node ID. func (c *Core) Dial(network, address string) (Conn, error) { - conn := Conn{} + conn := Conn{ + sessionMutex: &sync.RWMutex{}, + } nodeID := crypto.NodeID{} nodeMask := crypto.NodeID{} // Process @@ -298,6 +301,8 @@ func (c *Core) Dial(network, address string) (Conn, error) { conn.core.router.doAdmin(func() { conn.startSearch() }) + conn.sessionMutex.Lock() + defer conn.sessionMutex.Unlock() return conn, nil } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index d7923f51..693fba44 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -291,6 +291,10 @@ func (r *router) sendPacket(bs []byte) { if destSnet.IsValid() { sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet) } + sinfo.timeMutex.Lock() + sinfo.initMutex.RLock() + defer sinfo.timeMutex.Unlock() + defer sinfo.initMutex.RUnlock() switch { case !isIn || !sinfo.init: // No or unintiialized session, so we need to search first @@ -306,6 +310,7 @@ func (r *router) sendPacket(bs []byte) { } else { // We haven't heard about the dest in a while now := time.Now() + if !sinfo.time.Before(sinfo.pingTime) { // Update pingTime to start the clock for searches (above) sinfo.pingTime = now @@ -315,6 +320,7 @@ func (r *router) sendPacket(bs []byte) { sinfo.pingSend = now r.core.sessions.sendPingPong(sinfo, false) } + sinfo.timeMutex.Unlock() } fallthrough // Also send the packet default: diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 40259d9b..9c09532b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/hex" "sync" + "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -35,21 +36,23 @@ type sessionInfo struct { myNonceMutex sync.Mutex // protects the above 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 + wasMTUFixed bool // Was the MTU fixed by a receive error? + time time.Time // Time we last received a packet + 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 + timeMutex sync.RWMutex // protects all time fields above + coords []byte // coords of destination + coordsMutex sync.RWMutex // protects the above + packet []byte // a buffered packet, sent immediately on ping/pong + init bool // Reset if coords change + initMutex sync.RWMutex send chan []byte recv chan *wire_trafficPacket closed chan interface{} - tstamp int64 // tstamp from their last session ping, replay attack mitigation - tstampMutex int64 // protects the above - 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 + tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation + 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. @@ -66,7 +69,7 @@ type sessionPing struct { // 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 { - if !(p.Tstamp > s.tstamp) { + if !(p.Tstamp > atomic.LoadInt64(&s.tstamp)) { // To protect against replay attacks return false } @@ -90,14 +93,20 @@ func (s *sessionInfo) update(p *sessionPing) bool { s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) } now := time.Now() + s.timeMutex.Lock() s.time = now - s.tstamp = p.Tstamp + s.timeMutex.Unlock() + atomic.StoreInt64(&s.tstamp, p.Tstamp) + s.initMutex.Lock() s.init = true + s.initMutex.Unlock() return true } // Returns true if the session has been idle for longer than the allowed timeout. func (s *sessionInfo) timedout() bool { + s.timeMutex.RLock() + defer s.timeMutex.RUnlock() return time.Since(s.time) > time.Minute } @@ -284,10 +293,12 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myMTU = uint16(ss.core.router.adapter.MTU()) } now := time.Now() + sinfo.timeMutex.Lock() sinfo.time = now sinfo.mtuTime = now sinfo.pingTime = now sinfo.pingSend = now + sinfo.timeMutex.Unlock() higher := false for idx := range ss.core.boxPub { if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { @@ -428,6 +439,7 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { bs := ping.encode() shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) payload, nonce := crypto.BoxSeal(shared, bs, nil) + sinfo.coordsMutex.RLock() p := wire_protoTrafficPacket{ Coords: sinfo.coords, ToKey: sinfo.theirPermPub, @@ -435,10 +447,13 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { Nonce: *nonce, Payload: payload, } + sinfo.coordsMutex.RUnlock() packet := p.encode() ss.core.router.out(packet) if !isPong { + sinfo.timeMutex.Lock() sinfo.pingSend = time.Now() + sinfo.timeMutex.Unlock() } } @@ -465,10 +480,11 @@ func (ss *sessions) handlePing(ping *sessionPing) { ss.listenerMutex.Lock() if ss.listener != nil { conn := &Conn{ - core: ss.core, - session: sinfo, - nodeID: crypto.GetNodeID(&sinfo.theirPermPub), - nodeMask: &crypto.NodeID{}, + core: ss.core, + session: sinfo, + sessionMutex: &sync.RWMutex{}, + nodeID: crypto.GetNodeID(&sinfo.theirPermPub), + nodeMask: &crypto.NodeID{}, } for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF @@ -537,6 +553,8 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. func (ss *sessions) resetInits() { for _, sinfo := range ss.sinfos { + sinfo.initMutex.Lock() sinfo.init = false + sinfo.initMutex.Unlock() } } From 319366513c57379b781e8aa2d305610aad37716b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Apr 2019 11:53:46 +0100 Subject: [PATCH 15/60] Allow building with race detector --- build | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build b/build index f76ee7b2..bad287fa 100755 --- a/build +++ b/build @@ -8,7 +8,7 @@ PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" -while getopts "udaitc:l:" option +while getopts "udaitc:l:r" option do case "${option}" in @@ -19,6 +19,7 @@ do t) TABLES=true;; c) GCFLAGS="$GCFLAGS $OPTARG";; l) LDFLAGS="$LDFLAGS $OPTARG";; + r) RACE="-race";; esac done @@ -43,9 +44,9 @@ else echo "Building: $CMD" if [ $DEBUG ]; then - go build -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD + go build $RACE -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD else - go build -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD + go build $RACE -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD fi if [ $UPX ]; then upx --brute $CMD From d01662c1fb1a887e9406b05d7c3a62b6561bf94e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Apr 2019 16:32:27 +0100 Subject: [PATCH 16/60] Try to convert TUN/TAP to use new yggdrasil.Conn, search masks are still broken --- cmd/yggdrasil/main.go | 237 +++++++++++++++-------------- src/crypto/crypto.go | 27 ++++ src/tuntap/tun.go | 304 +++++++++++++++++++++++++++----------- src/tuntap/tun_bsd.go | 24 +-- src/tuntap/tun_darwin.go | 14 +- src/tuntap/tun_linux.go | 6 +- src/tuntap/tun_other.go | 2 +- src/tuntap/tun_windows.go | 32 ++-- src/yggdrasil/adapter.go | 47 ------ src/yggdrasil/conn.go | 19 ++- src/yggdrasil/core.go | 68 +-------- src/yggdrasil/dialer.go | 70 +++++++++ src/yggdrasil/router.go | 4 - src/yggdrasil/session.go | 14 +- 14 files changed, 502 insertions(+), 366 deletions(-) delete mode 100644 src/yggdrasil/adapter.go create mode 100644 src/yggdrasil/dialer.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 0e3aa354..f3d933c2 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -10,7 +10,6 @@ import ( "os/signal" "strings" "syscall" - "time" "golang.org/x/text/encoding/unicode" @@ -77,57 +76,44 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeCo panic(err) } json.Unmarshal(confJson, &cfg) - // For now we will do a little bit to help the user adjust their - // configuration to match the new configuration format, as some of the key - // names have changed recently. - changes := map[string]string{ - "Multicast": "", - "ReadTimeout": "", - "LinkLocal": "MulticastInterfaces", - "BoxPub": "EncryptionPublicKey", - "BoxPriv": "EncryptionPrivateKey", - "SigPub": "SigningPublicKey", - "SigPriv": "SigningPrivateKey", - "AllowedBoxPubs": "AllowedEncryptionPublicKeys", - } - // Loop over the mappings aove and see if we have anything to fix. - for from, to := range changes { - if _, ok := dat[from]; ok { - if to == "" { - if !*normaliseconf { - log.Println("Warning: Config option", from, "is deprecated") - } - } else { - if !*normaliseconf { - log.Println("Warning: Config option", from, "has been renamed - please change to", to) - } - // If the configuration file doesn't already contain a line with the - // new name then set it to the old value. This makes sure that we - // don't overwrite something that was put there intentionally. - if _, ok := dat[to]; !ok { - dat[to] = dat[from] + /* + // For now we will do a little bit to help the user adjust their + // configuration to match the new configuration format, as some of the key + // names have changed recently. + changes := map[string]string{ + "Multicast": "", + "ReadTimeout": "", + "LinkLocal": "MulticastInterfaces", + "BoxPub": "EncryptionPublicKey", + "BoxPriv": "EncryptionPrivateKey", + "SigPub": "SigningPublicKey", + "SigPriv": "SigningPrivateKey", + "AllowedBoxPubs": "AllowedEncryptionPublicKeys", + } + // Loop over the mappings aove and see if we have anything to fix. + for from, to := range changes { + if _, ok := dat[from]; ok { + if to == "" { + if !*normaliseconf { + log.Println("Warning: Config option", from, "is deprecated") + } + } else { + if !*normaliseconf { + log.Println("Warning: Config option", from, "has been renamed - please change to", to) + } + // If the configuration file doesn't already contain a line with the + // new name then set it to the old value. This makes sure that we + // don't overwrite something that was put there intentionally. + if _, ok := dat[to]; !ok { + dat[to] = dat[from] + } } } } - } - // Check to see if the peers are in a parsable format, if not then default - // them to the TCP scheme - 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 - } - if strings.HasPrefix(uri, "tcp:") { - uri = uri[4:] - } - (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{}) { + // Check to see if the peers are in a parsable format, if not then default + // them to the TCP scheme + 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 @@ -135,19 +121,34 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeCo if strings.HasPrefix(uri, "tcp:") { uri = uri[4:] } - ((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri + (dat["Peers"].([]interface{}))[index] = "tcp://" + uri } } - } - // Do a quick check for old-format Listen statement so that mapstructure - // doesn't fail and crash - if listen, ok := dat["Listen"].(string); ok { - if strings.HasPrefix(listen, "tcp://") { - dat["Listen"] = []string{listen} - } else { - dat["Listen"] = []string{"tcp://" + listen} + // 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 + } + } } - } + // Do a quick check for old-format Listen statement so that mapstructure + // doesn't fail and crash + if listen, ok := dat["Listen"].(string); ok { + if strings.HasPrefix(listen, "tcp://") { + dat["Listen"] = []string{listen} + } else { + dat["Listen"] = []string{"tcp://" + listen} + } + } + */ // Overlay our newly mapped configuration onto the autoconf node config that // we generated above. if err = mapstructure.Decode(dat, &cfg); err != nil { @@ -249,8 +250,6 @@ func main() { // Setup the Yggdrasil node itself. The node{} type includes a Core, so we // don't need to create this manually. n := node{} - // Before we start the node, set the TUN/TAP to be our router adapter - n.core.SetRouterAdapter(&n.tuntap) // Now start Yggdrasil - this starts the DHT, router, switch and other core // components needed for Yggdrasil to operate state, err := n.core.Start(cfg, logger) @@ -263,72 +262,88 @@ func main() { if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } + // Start the TUN/TAP interface + if listener, err := n.core.ConnListen(); err == nil { + if dialer, err := n.core.ConnDialer(); err == nil { + logger.Println("Got listener", listener, "and dialer", dialer) + n.tuntap.Init(state, logger, listener, dialer) + if err := n.tuntap.Start(); err != nil { + logger.Errorln("An error occurred starting TUN/TAP:", err) + } + } else { + logger.Errorln("Unable to get Dialer:", err) + } + } else { + logger.Errorln("Unable to get Listener:", err) + } // The Stop function ensures that the TUN/TAP adapter is correctly shut down // before the program exits. defer func() { n.core.Stop() }() // Listen for new sessions - go func() { - listener, err := n.core.ListenConn() - if err != nil { - logger.Errorln("Unable to listen for sessions:", err) - return - } - for { - conn, err := listener.Accept() - if err != nil { - logger.Errorln("Accept:", err) - continue - } - logger.Println("Accepted") - for { - b := make([]byte, 100) - if n, err := conn.Read(b); err != nil { - logger.Errorln("Read failed:", err) - time.Sleep(time.Second * 2) - } else { - logger.Println("Read", n, "bytes:", b) - b = []byte{5, 5, 5} - if n, err := conn.Write(b); err != nil { - logger.Errorln("Write failed:", err) - time.Sleep(time.Second * 2) - } else { - logger.Println("Wrote", n, "bytes:", b) - } - } - } - } - }() - // Try creating new sessions - go func() { - if cfg.EncryptionPublicKey != "533574224115f835b7c7db6433986bc5aef855ff9c9568c01abeb0fbed3e8810" { - return - } - time.Sleep(time.Second * 2) - conn, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") - if err != nil { - logger.Errorln("Dial:", err) - return - } + /* go func() { + listener, err := n.core.ListenConn() + if err != nil { + logger.Errorln("Unable to listen for sessions:", err) + return + } for { - time.Sleep(time.Second * 2) - b := []byte{1, 2, 3, 4, 5} - if n, err := conn.Write(b); err != nil { - logger.Errorln("Write failed:", err) - } else { - logger.Println("Wrote", n, "bytes:", b) - b = make([]byte, 100) + conn, err := listener.Accept() + if err != nil { + logger.Errorln("Accept:", err) + continue + } + logger.Println("Accepted") + for { + b := make([]byte, 100) if n, err := conn.Read(b); err != nil { logger.Errorln("Read failed:", err) + time.Sleep(time.Second * 2) } else { logger.Println("Read", n, "bytes:", b) + b = []byte{5, 5, 5} + if n, err := conn.Write(b); err != nil { + logger.Errorln("Write failed:", err) + time.Sleep(time.Second * 2) + } else { + logger.Println("Wrote", n, "bytes:", b) + } } } } }() - }() + // Try creating new sessions + go func() { + if cfg.EncryptionPublicKey != "533574224115f835b7c7db6433986bc5aef855ff9c9568c01abeb0fbed3e8810" { + return + } + time.Sleep(time.Second * 2) + conn, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") + if err != nil { + logger.Errorln("Dial:", err) + return + } + go func() { + for { + time.Sleep(time.Second * 2) + b := []byte{1, 2, 3, 4, 5} + if n, err := conn.Write(b); err != nil { + logger.Errorln("Write failed:", err) + } else { + logger.Println("Wrote", n, "bytes:", b) + b = make([]byte, 100) + if n, err := conn.Read(b); err != nil { + logger.Errorln("Read failed:", err) + } else { + logger.Println("Read", n, "bytes:", b) + } + } + } + }() + }() + */ // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index b47db184..d5b467e9 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -37,10 +37,37 @@ func (n *NodeID) String() string { return hex.EncodeToString(n[:]) } +// Network returns "nodeid" nearly always right now. func (n *NodeID) Network() string { return "nodeid" } +// PrefixLength returns the number of bits set in a masked NodeID. +func (n *NodeID) PrefixLength() int { + var len int + for i, v := range *n { + _, _ = i, v + if v == 0xff { + len += 8 + continue + } + for v&0x80 != 0 { + len++ + v <<= 1 + } + if v != 0 { + return -1 + } + for i++; i < NodeIDLen; i++ { + if n[i] != 0 { + return -1 + } + } + break + } + return len +} + func GetNodeID(pub *BoxPubKey) *NodeID { h := sha512.Sum512(pub[:]) return (*NodeID)(&h) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index c93b1163..3010fae4 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -3,7 +3,7 @@ package tuntap // This manages the tun driver to send/recv packets to/from applications import ( - "bytes" + "encoding/hex" "errors" "fmt" "net" @@ -11,16 +11,12 @@ import ( "time" "github.com/gologme/log" - "golang.org/x/net/icmp" - "golang.org/x/net/ipv6" - - "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/config" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -32,14 +28,20 @@ const tun_ETHER_HEADER_LENGTH = 14 // you should pass this object to the yggdrasil.SetRouterAdapter() function // before calling yggdrasil.Start(). type TunAdapter struct { - yggdrasil.Adapter - addr address.Address - subnet address.Subnet - icmpv6 ICMPv6 - mtu int - iface *water.Interface - mutex sync.RWMutex // Protects the below - isOpen bool + config *config.NodeState + log *log.Logger + reconfigure chan chan error + conns map[crypto.NodeID]yggdrasil.Conn + connsMutex sync.RWMutex + listener *yggdrasil.Listener + dialer *yggdrasil.Dialer + addr address.Address + subnet address.Subnet + 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 @@ -94,62 +96,48 @@ func MaximumMTU() int { return defaults.GetDefaults().MaximumIfMTU } -// Init initialises the TUN/TAP adapter. -func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) { - tun.Adapter.Init(config, log, send, recv, reject) - tun.icmpv6.Init(tun) - go func() { - for { - e := <-tun.Reconfigure - tun.Config.Mutex.RLock() - updated := tun.Config.Current.IfName != tun.Config.Previous.IfName || - tun.Config.Current.IfTAPMode != tun.Config.Previous.IfTAPMode || - tun.Config.Current.IfMTU != tun.Config.Previous.IfMTU - tun.Config.Mutex.RUnlock() - if updated { - tun.Log.Warnln("Reconfiguring TUN/TAP is not supported yet") - e <- nil - } else { - e <- nil - } - } - }() +// Init initialises the TUN/TAP module. You must have acquired a Listener from +// the Yggdrasil core before this point and it must not be in use elsewhere. +func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener *yggdrasil.Listener, dialer *yggdrasil.Dialer) { + tun.config = config + tun.log = log + tun.listener = listener + tun.dialer = dialer + tun.conns = make(map[crypto.NodeID]yggdrasil.Conn) } // Start the setup process for the TUN/TAP adapter. If successful, starts the // read/write goroutines to handle packets on that interface. -func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { - tun.addr = a - tun.subnet = s - if tun.Config == nil { +func (tun *TunAdapter) Start() error { + tun.config.Mutex.Lock() + defer tun.config.Mutex.Unlock() + if tun.config == nil || tun.listener == nil || tun.dialer == nil { return errors.New("No configuration available to TUN/TAP") } - tun.Config.Mutex.RLock() - ifname := tun.Config.Current.IfName - iftapmode := tun.Config.Current.IfTAPMode - addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) - mtu := tun.Config.Current.IfMTU - tun.Config.Mutex.RUnlock() + var boxPub crypto.BoxPubKey + boxPubHex, err := hex.DecodeString(tun.config.Current.EncryptionPublicKey) + if err != nil { + return err + } + copy(boxPub[:], boxPubHex) + nodeID := crypto.GetNodeID(&boxPub) + tun.addr = *address.AddrForNodeID(nodeID) + tun.subnet = *address.SubnetForNodeID(nodeID) + tun.mtu = tun.config.Current.IfMTU + ifname := tun.config.Current.IfName + iftapmode := tun.config.Current.IfTAPMode if ifname != "none" { - if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil { + if err := tun.setup(ifname, iftapmode, net.IP(tun.addr[:]).String(), tun.mtu); err != nil { return err } } if ifname == "none" || ifname == "dummy" { - tun.Log.Debugln("Not starting TUN/TAP as ifname is none or dummy") + tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy") return nil } tun.mutex.Lock() tun.isOpen = true tun.mutex.Unlock() - go func() { - tun.Log.Debugln("Starting TUN/TAP reader goroutine") - tun.Log.Errorln("WARNING: tun.read() exited with error:", tun.read()) - }() - go func() { - tun.Log.Debugln("Starting TUN/TAP writer goroutine") - tun.Log.Errorln("WARNING: tun.write() exited with error:", tun.write()) - }() if iftapmode { go func() { for { @@ -167,74 +155,215 @@ func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { } }() } + tun.icmpv6.Init(tun) + go func() { + for { + e := <-tun.reconfigure + e <- nil + } + }() + go tun.handler() + go tun.ifaceReader() return nil } +func (tun *TunAdapter) handler() error { + for { + // Accept the incoming connection + conn, err := tun.listener.Accept() + if err != nil { + tun.log.Errorln("TUN/TAP error accepting connection:", err) + return err + } + tun.log.Println("Accepted connection from", conn.RemoteAddr()) + go tun.connReader(conn) + } +} + +func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { + b := make([]byte, 65535) + for { + n, err := conn.Read(b) + if err != nil { + tun.log.Errorln("TUN/TAP read error:", err) + return err + } + if n == 0 { + continue + } + w, err := tun.iface.Write(b[:n]) + if err != nil { + tun.log.Errorln("TUN/TAP failed to write to interface:", err) + continue + } + if w != n { + tun.log.Errorln("TUN/TAP wrote", w, "instead of", n, "which is bad") + continue + } + } +} + +func (tun *TunAdapter) ifaceReader() error { + tun.log.Println("Start TUN reader") + bs := make([]byte, 65535) + for { + n, err := tun.iface.Read(bs) + if err != nil { + tun.log.Errorln("TUN/TAP iface read error:", err) + } + // Look up if the dstination address is somewhere we already have an + // open connection to + var srcAddr address.Address + var dstAddr address.Address + var dstNodeID *crypto.NodeID + var dstNodeIDMask *crypto.NodeID + var dstSnet address.Subnet + var addrlen int + if bs[0]&0xf0 == 0x60 { + // Check if we have a fully-sized header + if len(bs) < 40 { + panic("Tried to send a packet shorter than an IPv6 header...") + } + // IPv6 address + addrlen = 16 + copy(srcAddr[:addrlen], bs[8:]) + copy(dstAddr[:addrlen], bs[24:]) + copy(dstSnet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // Check if we have a fully-sized header + if len(bs) < 20 { + panic("Tried to send a packet shorter than an IPv4 header...") + } + // IPv4 address + addrlen = 4 + copy(srcAddr[:addrlen], bs[12:]) + copy(dstAddr[:addrlen], bs[16:]) + } else { + // Unknown address length + continue + } + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + // Do we have an active connection for this node ID? + if conn, isIn := tun.conns[*dstNodeID]; isIn { + fmt.Println("We have a connection for", *dstNodeID) + w, err := conn.Write(bs) + if err != nil { + fmt.Println("Unable to write to remote:", err) + continue + } + if w != n { + continue + } + } else { + fmt.Println("Opening connection for", *dstNodeID) + tun.connsMutex.Lock() + maskstr := hex.EncodeToString(dstNodeID[:]) + masklen := dstNodeIDMask.PrefixLength() + cidr := fmt.Sprintf("%s/%d", maskstr, masklen) + if conn, err := tun.dialer.Dial("nodeid", cidr); err == nil { + tun.conns[*dstNodeID] = conn + go tun.connReader(&conn) + } else { + fmt.Println("Error dialing:", err) + } + tun.connsMutex.Unlock() + } + + /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { + // The packet had a src address that doesn't belong to us or our + // configured crypto-key routing src subnets + return + } + if !dstAddr.IsValid() && !dstSnet.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(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + dstPubKey = &key + dstNodeID = crypto.GetNodeID(dstPubKey) + // 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(dstNodeID) + copy(dstAddr[:], addr[:]) + copy(dstSnet[:], addr[:]) + if !dstAddr.IsValid() && !dstSnet.IsValid() { + return + } + } else { + // No public key was found in the CKR table so we've exhausted our options + return + } + }*/ + + } +} + // 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 *TunAdapter) write() error { for { select { case reject := <-tun.Reject: - switch reject.Reason { - case yggdrasil.PacketTooBig: - if mtu, ok := reject.Detail.(int); ok { - // Create the Packet Too Big response - ptb := &icmp.PacketTooBig{ - MTU: int(mtu), - Data: reject.Packet, - } - - // Create the ICMPv6 response from it - icmpv6Buf, err := CreateICMPv6( - reject.Packet[8:24], reject.Packet[24:40], - ipv6.ICMPTypePacketTooBig, 0, ptb) - - // Send the ICMPv6 response back to the TUN/TAP adapter - if err == nil { - tun.iface.Write(icmpv6Buf) - } + switch reject.Reason { + case yggdrasil.PacketTooBig: + if mtu, ok := reject.Detail.(int); ok { + // Create the Packet Too Big response + ptb := &icmp.PacketTooBig{ + MTU: int(mtu), + Data: reject.Packet, + } + + // Create the ICMPv6 response from it + icmpv6Buf, err := CreateICMPv6( + reject.Packet[8:24], reject.Packet[24:40], + ipv6.ICMPTypePacketTooBig, 0, ptb) + + // Send the ICMPv6 response back to the TUN/TAP adapter + if err == nil { + tun.iface.Write(icmpv6Buf) } - fallthrough - default: - continue } + fallthrough + default: + continue + } case data := <-tun.Recv: if tun.iface == nil { continue } if tun.iface.IsTAP() { - var destAddr address.Address + var dstAddr address.Address if data[0]&0xf0 == 0x60 { if len(data) < 40 { //panic("Tried to send a packet shorter than an IPv6 header...") util.PutBytes(data) continue } - copy(destAddr[:16], data[24:]) + copy(dstAddr[:16], data[24:]) } else if data[0]&0xf0 == 0x40 { if len(data) < 20 { //panic("Tried to send a packet shorter than an IPv4 header...") util.PutBytes(data) continue } - copy(destAddr[:4], data[16:]) + copy(dstAddr[:4], data[16:]) } else { return errors.New("Invalid address family") } - sendndp := func(destAddr address.Address) { - neigh, known := tun.icmpv6.peermacs[destAddr] + sendndp := func(dstAddr address.Address) { + neigh, known := tun.icmpv6.peermacs[dstAddr] known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) if !known { - request, err := tun.icmpv6.CreateNDPL2(destAddr) + request, err := tun.icmpv6.CreateNDPL2(dstAddr) if err != nil { panic(err) } if _, err := tun.iface.Write(request); err != nil { panic(err) } - tun.icmpv6.peermacs[destAddr] = neighbor{ + tun.icmpv6.peermacs[dstAddr] = neighbor{ lastsolicitation: time.Now(), } } @@ -242,19 +371,19 @@ func (tun *TunAdapter) write() error { var peermac macAddress var peerknown bool if data[0]&0xf0 == 0x40 { - destAddr = tun.addr + dstAddr = tun.addr } else if data[0]&0xf0 == 0x60 { - if !bytes.Equal(tun.addr[:16], destAddr[:16]) && !bytes.Equal(tun.subnet[:8], destAddr[:8]) { - destAddr = tun.addr + if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { + dstAddr = tun.addr } } - if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned { + if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { peermac = neighbor.mac peerknown = true } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { peermac = neighbor.mac peerknown = true - sendndp(destAddr) + sendndp(dstAddr) } else { sendndp(tun.addr) } @@ -359,3 +488,4 @@ func (tun *TunAdapter) Close() error { } return tun.iface.Close() } +*/ diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 27c9bb2f..996f3140 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -109,14 +109,14 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Create system socket if sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0); err != nil { - tun.Log.Printf("Create AF_INET socket failed: %v.", err) + tun.log.Printf("Create AF_INET socket failed: %v.", err) return err } // Friendly output - tun.Log.Infof("Interface name: %s", tun.iface.Name()) - tun.Log.Infof("Interface IPv6: %s", addr) - tun.Log.Infof("Interface MTU: %d", tun.mtu) + tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) // Create the MTU request var ir in6_ifreq_mtu @@ -126,15 +126,15 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Set the MTU if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno - tun.Log.Errorf("Error in SIOCSIFMTU: %v", errno) + tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) // Fall back to ifconfig to set the MTU cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu)) - tun.Log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) + tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("SIOCSIFMTU fallback failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err) + tun.log.Traceln(string(output)) } } @@ -155,15 +155,15 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Set the interface address if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno - tun.Log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) + tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) // Fall back to ifconfig to set the address cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr) - tun.Log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) + tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err) + tun.log.Traceln(string(output)) } } diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index 60786b81..5dfca137 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -18,7 +18,7 @@ import ( // Configures the "utun" adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if iftapmode { - tun.Log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") + tun.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") } config := water.Config{DeviceType: water.TUN} iface, err := water.New(config) @@ -69,7 +69,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { var err error if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil { - tun.Log.Printf("Create AF_SYSTEM socket failed: %v.", err) + tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err) return err } @@ -98,19 +98,19 @@ func (tun *TunAdapter) setupAddress(addr string) error { copy(ir.ifr_name[:], tun.iface.Name()) ir.ifru_mtu = uint32(tun.mtu) - tun.Log.Infof("Interface name: %s", ar.ifra_name) - tun.Log.Infof("Interface IPv6: %s", addr) - tun.Log.Infof("Interface MTU: %d", ir.ifru_mtu) + tun.log.Infof("Interface name: %s", ar.ifra_name) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", ir.ifru_mtu) if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno - tun.Log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) + tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) return err } if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno - tun.Log.Errorf("Error in SIOCSIFMTU: %v", errno) + tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) return err } diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index 7d228574..c9c03c09 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -40,9 +40,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } } // Friendly output - tun.Log.Infof("Interface name: %s", tun.iface.Name()) - tun.Log.Infof("Interface IPv6: %s", addr) - tun.Log.Infof("Interface MTU: %d", tun.mtu) + tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) return tun.setupAddress(addr) } diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index bb302d19..48276b49 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -28,6 +28,6 @@ func (tun *TunAdapter) 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 *TunAdapter) setupAddress(addr string) error { - tun.Log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) + tun.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) return nil } diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 5a158b14..8a66ac62 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -15,7 +15,7 @@ import ( // delegate the hard work to "netsh". func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if !iftapmode { - tun.Log.Warnln("TUN mode is not supported on this platform, defaulting to TAP") + tun.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP") } config := water.Config{DeviceType: water.TAP} config.PlatformSpecificParams.ComponentID = "tap0901" @@ -31,19 +31,19 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } // Disable/enable the interface to resets its configuration (invalidating iface) cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED") - tun.Log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("Windows netsh failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") - tun.Log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) output, err = cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("Windows netsh failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } // Get a new iface @@ -58,9 +58,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int panic(err) } // Friendly output - tun.Log.Infof("Interface name: %s", tun.iface.Name()) - tun.Log.Infof("Interface IPv6: %s", addr) - tun.Log.Infof("Interface MTU: %d", tun.mtu) + tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) return tun.setupAddress(addr) } @@ -71,11 +71,11 @@ func (tun *TunAdapter) setupMTU(mtu int) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("mtu=%d", mtu), "store=active") - tun.Log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("Windows netsh failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } return nil @@ -88,11 +88,11 @@ func (tun *TunAdapter) setupAddress(addr string) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("addr=%s", addr), "store=active") - tun.Log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("Windows netsh failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } return nil diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go deleted file mode 100644 index 8fadb195..00000000 --- a/src/yggdrasil/adapter.go +++ /dev/null @@ -1,47 +0,0 @@ -package yggdrasil - -import ( - "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/config" -) - -// Adapter defines the minimum required struct members for an adapter type. This -// is now the base type for adapters like tun.go. When implementing a new -// adapter type, you should extend the adapter struct with this one and should -// call the Adapter.Init() function when initialising. -type Adapter struct { - adapterImplementation - Core *Core - Config *config.NodeState - Log *log.Logger - Send chan<- []byte - Recv <-chan []byte - Reject <-chan RejectedPacket - Reconfigure chan chan error -} - -// Defines the minimum required functions for an adapter type. Note that the -// implementation of Init() should call Adapter.Init(). This is not exported -// because doing so breaks the gomobile bindings for iOS/Android. -type adapterImplementation interface { - Init(*config.NodeState, *log.Logger, chan<- []byte, <-chan []byte, <-chan RejectedPacket) - Name() string - MTU() int - IsTAP() bool - Start(address.Address, address.Subnet) error - Close() error -} - -// Init initialises the adapter with the necessary channels to operate from the -// router. When defining a new Adapter type, the Adapter should call this -// function from within it's own Init function to set up the channels. It is -// otherwise not expected for you to call this function directly. -func (adapter *Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan RejectedPacket) { - adapter.Config = config - adapter.Log = log - adapter.Send = send - adapter.Recv = recv - adapter.Reject = reject - adapter.Reconfigure = make(chan chan error, 1) -} diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 874a7a9c..6334eefa 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -16,7 +16,7 @@ type Conn struct { nodeID *crypto.NodeID nodeMask *crypto.NodeID session *sessionInfo - sessionMutex *sync.RWMutex + mutex *sync.RWMutex readDeadline time.Time writeDeadline time.Time expired bool @@ -30,9 +30,10 @@ func (c *Conn) startSearch() { return } if sinfo != nil { - c.sessionMutex.Lock() + c.mutex.Lock() c.session = sinfo - c.sessionMutex.Unlock() + c.nodeID, c.nodeMask = sinfo.theirAddr.GetNodeIDandMask() + c.mutex.Unlock() } } doSearch := func() { @@ -65,8 +66,8 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { - c.sessionMutex.RLock() - defer c.sessionMutex.RUnlock() + c.mutex.RLock() + defer c.mutex.RUnlock() if c.expired { return 0, errors.New("session is closed") } @@ -119,8 +120,8 @@ func (c *Conn) Read(b []byte) (int, error) { } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - c.sessionMutex.RLock() - defer c.sessionMutex.RUnlock() + c.mutex.RLock() + defer c.mutex.RUnlock() if c.expired { return 0, errors.New("session is closed") } @@ -175,7 +176,9 @@ func (c *Conn) LocalAddr() crypto.NodeID { } func (c *Conn) RemoteAddr() crypto.NodeID { - return *crypto.GetNodeID(&c.session.theirPermPub) + c.mutex.RLock() + defer c.mutex.RUnlock() + return *c.nodeID } func (c *Conn) SetDeadline(t time.Time) error { diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index cfba833b..81be1b28 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -5,7 +5,6 @@ import ( "errors" "io/ioutil" "net" - "sync" "time" "github.com/gologme/log" @@ -77,7 +76,6 @@ func (c *Core) init() error { c.searches.init(c) c.dht.init(c) c.sessions.init(c) - //c.multicast.init(c) c.peers.init(c) c.router.init(c) c.switchTable.init(c) // TODO move before peers? before router? @@ -168,21 +166,6 @@ func BuildVersion() string { return buildVersion } -// SetRouterAdapter instructs Yggdrasil to use the given adapter when starting -// the router. The adapter must implement the standard -// adapter.adapterImplementation interface and should extend the adapter.Adapter -// struct. -func (c *Core) SetRouterAdapter(adapter interface{}) error { - // We do this because adapterImplementation is not a valid type for the - // gomobile bindings so we just ask for a generic interface and try to cast it - // to adapterImplementation instead - if a, ok := adapter.(adapterImplementation); ok { - c.router.adapter = a - return nil - } - return errors.New("unsuitable adapter") -} - // Start starts up Yggdrasil using the provided config.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, @@ -233,13 +216,6 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, return nil, err } - if c.router.adapter != nil { - if err := c.router.adapter.Start(c.router.addr, c.router.subnet); err != nil { - c.log.Errorln("Failed to start TUN/TAP") - return nil, err - } - } - go c.addPeerLoop() c.log.Infoln("Startup complete") @@ -249,14 +225,11 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, // Stop shuts down the Yggdrasil node. func (c *Core) Stop() { c.log.Infoln("Stopping...") - if c.router.adapter != nil { - c.router.adapter.Close() - } c.admin.close() } // ListenConn returns a listener for Yggdrasil session connections. -func (c *Core) ListenConn() (*Listener, error) { +func (c *Core) ConnListen() (*Listener, error) { c.sessions.listenerMutex.Lock() defer c.sessions.listenerMutex.Unlock() if c.sessions.listener != nil { @@ -270,40 +243,11 @@ func (c *Core) ListenConn() (*Listener, error) { return c.sessions.listener, nil } -// Dial opens a session to the given node. The first paramter should be "nodeid" -// and the second parameter should contain a hexadecimal representation of the -// target node ID. -func (c *Core) Dial(network, address string) (Conn, error) { - conn := Conn{ - sessionMutex: &sync.RWMutex{}, - } - nodeID := crypto.NodeID{} - nodeMask := crypto.NodeID{} - // Process - switch network { - case "nodeid": - // A node ID was provided - we don't need to do anything special with it - dest, err := hex.DecodeString(address) - if err != nil { - return Conn{}, err - } - copy(nodeID[:], dest) - for i := range nodeMask { - nodeMask[i] = 0xFF - } - default: - // An unexpected address type was given, so give up - return Conn{}, errors.New("unexpected address type") - } - conn.core = c - conn.nodeID = &nodeID - conn.nodeMask = &nodeMask - conn.core.router.doAdmin(func() { - conn.startSearch() - }) - conn.sessionMutex.Lock() - defer conn.sessionMutex.Unlock() - return conn, nil +// ConnDialer returns a dialer for Yggdrasil session connections. +func (c *Core) ConnDialer() (*Dialer, error) { + return &Dialer{ + core: c, + }, nil } // ListenTCP starts a new TCP listener. The input URI should match that of the diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go new file mode 100644 index 00000000..7042bd0e --- /dev/null +++ b/src/yggdrasil/dialer.go @@ -0,0 +1,70 @@ +package yggdrasil + +import ( + "encoding/hex" + "errors" + "fmt" + "strconv" + "strings" + "sync" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) + +// Dialer represents an Yggdrasil connection dialer. +type Dialer struct { + core *Core +} + +// Dial opens a session to the given node. The first paramter should be "nodeid" +// and the second parameter should contain a hexadecimal representation of the +// target node ID. +func (d *Dialer) Dial(network, address string) (Conn, error) { + conn := Conn{ + mutex: &sync.RWMutex{}, + } + nodeID := crypto.NodeID{} + nodeMask := crypto.NodeID{} + // Process + switch network { + case "nodeid": + // A node ID was provided - we don't need to do anything special with it + if tokens := strings.Split(address, "/"); len(tokens) == 2 { + len, err := strconv.Atoi(tokens[1]) + if err != nil { + return Conn{}, err + } + dest, err := hex.DecodeString(tokens[0]) + if err != nil { + return Conn{}, err + } + copy(nodeID[:], dest) + for idx := 0; idx < len; idx++ { + nodeMask[idx/8] |= 0x80 >> byte(idx%8) + } + fmt.Println(nodeID) + fmt.Println(nodeMask) + } else { + dest, err := hex.DecodeString(tokens[0]) + if err != nil { + return Conn{}, err + } + copy(nodeID[:], dest) + for i := range nodeMask { + nodeMask[i] = 0xFF + } + } + default: + // An unexpected address type was given, so give up + return Conn{}, errors.New("unexpected address type") + } + conn.core = d.core + conn.nodeID = &nodeID + conn.nodeMask = &nodeMask + conn.core.router.doAdmin(func() { + conn.startSearch() + }) + conn.mutex.Lock() + defer conn.mutex.Unlock() + return conn, nil +} diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 693fba44..19b62f91 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -41,7 +41,6 @@ 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() - adapter adapterImplementation // TUN/TAP adapter recv chan<- []byte // place where the adapter pulls received packets from send <-chan []byte // place where the adapter puts outgoing packets reject chan<- RejectedPacket // place where we send error packets back to adapter @@ -136,9 +135,6 @@ func (r *router) init(core *Core) { r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.core.config.Mutex.RUnlock() r.cryptokey.init(r.core) - if r.adapter != nil { - r.adapter.Init(&r.core.config, r.core.log, send, recv, reject) - } } // Starts the mainLoop goroutine. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 9c09532b..3bf69a8c 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -289,9 +289,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 - if ss.core.router.adapter != nil { - sinfo.myMTU = uint16(ss.core.router.adapter.MTU()) - } + sinfo.myMTU = 1280 now := time.Now() sinfo.timeMutex.Lock() sinfo.time = now @@ -480,11 +478,11 @@ func (ss *sessions) handlePing(ping *sessionPing) { ss.listenerMutex.Lock() if ss.listener != nil { conn := &Conn{ - core: ss.core, - session: sinfo, - sessionMutex: &sync.RWMutex{}, - nodeID: crypto.GetNodeID(&sinfo.theirPermPub), - nodeMask: &crypto.NodeID{}, + core: ss.core, + session: sinfo, + mutex: &sync.RWMutex{}, + nodeID: crypto.GetNodeID(&sinfo.theirPermPub), + nodeMask: &crypto.NodeID{}, } for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF From 62621f29603bbfcf8ef97e2e7fbcb80822407b7c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Apr 2019 20:22:58 +0100 Subject: [PATCH 17/60] Some tweaks --- src/tuntap/tun.go | 13 ++++--------- src/yggdrasil/conn.go | 3 +++ src/yggdrasil/dialer.go | 23 +++++++++++++---------- src/yggdrasil/search.go | 2 ++ 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 3010fae4..4b72b465 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -5,7 +5,6 @@ package tuntap import ( "encoding/hex" "errors" - "fmt" "net" "sync" "time" @@ -245,26 +244,22 @@ func (tun *TunAdapter) ifaceReader() error { dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? if conn, isIn := tun.conns[*dstNodeID]; isIn { - fmt.Println("We have a connection for", *dstNodeID) w, err := conn.Write(bs) if err != nil { - fmt.Println("Unable to write to remote:", err) + tun.log.Println("Unable to write to remote:", err) continue } if w != n { continue } } else { - fmt.Println("Opening connection for", *dstNodeID) + tun.log.Println("Opening connection for", *dstNodeID) tun.connsMutex.Lock() - maskstr := hex.EncodeToString(dstNodeID[:]) - masklen := dstNodeIDMask.PrefixLength() - cidr := fmt.Sprintf("%s/%d", maskstr, masklen) - if conn, err := tun.dialer.Dial("nodeid", cidr); err == nil { + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { tun.conns[*dstNodeID] = conn go tun.connReader(&conn) } else { - fmt.Println("Error dialing:", err) + tun.log.Println("Error dialing:", err) } tun.connsMutex.Unlock() } diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 6334eefa..9566cae3 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -27,6 +27,9 @@ func (c *Conn) startSearch() { searchCompleted := func(sinfo *sessionInfo, err error) { if err != nil { c.core.log.Debugln("DHT search failed:", err) + c.mutex.Lock() + c.expired = true + c.mutex.Unlock() return } if sinfo != nil { diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 7042bd0e..fee355e4 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -3,7 +3,6 @@ package yggdrasil import ( "encoding/hex" "errors" - "fmt" "strconv" "strings" "sync" @@ -20,11 +19,8 @@ type Dialer struct { // and the second parameter should contain a hexadecimal representation of the // target node ID. func (d *Dialer) Dial(network, address string) (Conn, error) { - conn := Conn{ - mutex: &sync.RWMutex{}, - } - nodeID := crypto.NodeID{} - nodeMask := crypto.NodeID{} + var nodeID crypto.NodeID + var nodeMask crypto.NodeID // Process switch network { case "nodeid": @@ -42,8 +38,6 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { for idx := 0; idx < len; idx++ { nodeMask[idx/8] |= 0x80 >> byte(idx%8) } - fmt.Println(nodeID) - fmt.Println(nodeMask) } else { dest, err := hex.DecodeString(tokens[0]) if err != nil { @@ -54,13 +48,22 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { nodeMask[i] = 0xFF } } + return d.DialByNodeIDandMask(&nodeID, &nodeMask) default: // An unexpected address type was given, so give up return Conn{}, errors.New("unexpected address type") } +} + +// DialByNodeIDandMask opens a session to the given node based on raw +// NodeID parameters. +func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, error) { + conn := Conn{ + mutex: &sync.RWMutex{}, + } conn.core = d.core - conn.nodeID = &nodeID - conn.nodeMask = &nodeMask + conn.nodeID = nodeID + conn.nodeMask = nodeMask conn.core.router.doAdmin(func() { conn.startSearch() }) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index e81a9723..dcf0c81f 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -212,7 +212,9 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { } } // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? + sinfo.coordsMutex.Lock() sinfo.coords = res.Coords + sinfo.coordsMutex.Unlock() sinfo.packet = info.packet s.core.sessions.ping(sinfo) info.callback(sinfo, nil) From 79bcfbf17578c594f53d1ba260cc4c29d5abca9b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Apr 2019 11:50:41 +0100 Subject: [PATCH 18/60] Change some mutexes to atomics, change conns map to pointers, sort of works but seems to deadlock very easily --- src/tuntap/tun.go | 12 ++--- src/yggdrasil/conn.go | 41 ++++++--------- src/yggdrasil/dialer.go | 12 ++--- src/yggdrasil/router.go | 22 ++++---- src/yggdrasil/search.go | 2 - src/yggdrasil/session.go | 107 ++++++++++++++++----------------------- 6 files changed, 81 insertions(+), 115 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 4b72b465..9e46124a 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -30,8 +30,7 @@ type TunAdapter struct { config *config.NodeState log *log.Logger reconfigure chan chan error - conns map[crypto.NodeID]yggdrasil.Conn - connsMutex sync.RWMutex + conns map[crypto.NodeID]*yggdrasil.Conn listener *yggdrasil.Listener dialer *yggdrasil.Dialer addr address.Address @@ -102,7 +101,7 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.log = log tun.listener = listener tun.dialer = dialer - tun.conns = make(map[crypto.NodeID]yggdrasil.Conn) + tun.conns = make(map[crypto.NodeID]*yggdrasil.Conn) } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -180,6 +179,7 @@ func (tun *TunAdapter) handler() error { } func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { + tun.conns[conn.RemoteAddr()] = conn b := make([]byte, 65535) for { n, err := conn.Read(b) @@ -203,7 +203,6 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { } func (tun *TunAdapter) ifaceReader() error { - tun.log.Println("Start TUN reader") bs := make([]byte, 65535) for { n, err := tun.iface.Read(bs) @@ -244,6 +243,7 @@ func (tun *TunAdapter) ifaceReader() error { dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? if conn, isIn := tun.conns[*dstNodeID]; isIn { + tun.log.Println("Got", &conn) w, err := conn.Write(bs) if err != nil { tun.log.Println("Unable to write to remote:", err) @@ -254,14 +254,12 @@ func (tun *TunAdapter) ifaceReader() error { } } else { tun.log.Println("Opening connection for", *dstNodeID) - tun.connsMutex.Lock() if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - tun.conns[*dstNodeID] = conn + tun.conns[*dstNodeID] = &conn go tun.connReader(&conn) } else { tun.log.Println("Error dialing:", err) } - tun.connsMutex.Unlock() } /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9566cae3..daba2987 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -1,8 +1,8 @@ package yggdrasil import ( - "encoding/hex" "errors" + "fmt" "sync" "sync/atomic" "time" @@ -42,27 +42,27 @@ func (c *Conn) startSearch() { doSearch := func() { sinfo, isIn := c.core.searches.searches[*c.nodeID] if !isIn { - c.core.log.Debugln("Starting search for", hex.EncodeToString(c.nodeID[:])) sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) } c.core.searches.continueSearch(sinfo) } - var sinfo *sessionInfo - var isIn bool switch { - case !isIn || !sinfo.init: + case c.session == nil || !c.session.init.Load().(bool): doSearch() - case time.Since(sinfo.time) > 6*time.Second: - if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + case time.Since(c.session.time.Load().(time.Time)) > 6*time.Second: + sTime := c.session.time.Load().(time.Time) + pingTime := c.session.pingTime.Load().(time.Time) + if sTime.Before(pingTime) && time.Since(pingTime) > 6*time.Second { doSearch() } else { + pingSend := c.session.pingSend.Load().(time.Time) now := time.Now() - if !sinfo.time.Before(sinfo.pingTime) { - sinfo.pingTime = now + if !sTime.Before(pingTime) { + c.session.pingTime.Store(now) } - if time.Since(sinfo.pingSend) > time.Second { - sinfo.pingSend = now - c.core.sessions.sendPingPong(sinfo, false) + if time.Since(pingSend) > time.Second { + c.session.pingSend.Store(now) + c.core.sessions.sendPingPong(c.session, false) } } } @@ -77,12 +77,9 @@ func (c *Conn) Read(b []byte) (int, error) { if c.session == nil { return 0, errors.New("searching for remote side") } - c.session.initMutex.RLock() - if !c.session.init { - c.session.initMutex.RUnlock() + if !c.session.init.Load().(bool) { return 0, errors.New("waiting for remote side to accept") } - c.session.initMutex.RUnlock() select { case p, ok := <-c.session.recv: if !ok { @@ -106,9 +103,7 @@ func (c *Conn) Read(b []byte) (int, error) { b = b[:len(bs)] } c.session.updateNonce(&p.Nonce) - c.session.timeMutex.Lock() - c.session.time = time.Now() - c.session.timeMutex.Unlock() + c.session.time.Store(time.Now()) return nil }() if err != nil { @@ -129,22 +124,18 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return 0, errors.New("session is closed") } if c.session == nil { + fmt.Println("No session found, starting search for", &c) c.core.router.doAdmin(func() { c.startSearch() }) return 0, errors.New("searching for remote side") } defer util.PutBytes(b) - c.session.initMutex.RLock() - if !c.session.init { - c.session.initMutex.RUnlock() + if !c.session.init.Load().(bool) { return 0, errors.New("waiting for remote side to accept") } - c.session.initMutex.RUnlock() // code isn't multithreaded so appending to this is safe - c.session.coordsMutex.RLock() coords := c.session.coords - c.session.coordsMutex.RUnlock() // Prepare the payload c.session.myNonceMutex.Lock() payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index fee355e4..4a3d8167 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -35,7 +35,7 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { return Conn{}, err } copy(nodeID[:], dest) - for idx := 0; idx < len; idx++ { + for idx := 0; idx <= len; idx++ { nodeMask[idx/8] |= 0x80 >> byte(idx%8) } } else { @@ -59,15 +59,13 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { // NodeID parameters. func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, error) { conn := Conn{ - mutex: &sync.RWMutex{}, + core: d.core, + mutex: &sync.RWMutex{}, + nodeID: nodeID, + nodeMask: nodeMask, } - conn.core = d.core - conn.nodeID = nodeID - conn.nodeMask = nodeMask conn.core.router.doAdmin(func() { conn.startSearch() }) - conn.mutex.Lock() - defer conn.mutex.Unlock() return conn, nil } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 19b62f91..7da5162f 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -287,16 +287,15 @@ func (r *router) sendPacket(bs []byte) { if destSnet.IsValid() { sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet) } - sinfo.timeMutex.Lock() - sinfo.initMutex.RLock() - defer sinfo.timeMutex.Unlock() - defer sinfo.initMutex.RUnlock() + sTime := sinfo.time.Load().(time.Time) + pingTime := sinfo.pingTime.Load().(time.Time) + pingSend := sinfo.pingSend.Load().(time.Time) switch { - case !isIn || !sinfo.init: + case !isIn || !sinfo.init.Load().(bool): // No or unintiialized session, so we need to search first doSearch(bs) - case time.Since(sinfo.time) > 6*time.Second: - if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + case time.Since(sTime) > 6*time.Second: + if sTime.Before(pingTime) && time.Since(pingTime) > 6*time.Second { // We haven't heard from the dest in a while // We tried pinging but didn't get a response // They may have changed coords @@ -307,16 +306,15 @@ func (r *router) sendPacket(bs []byte) { // We haven't heard about the dest in a while now := time.Now() - if !sinfo.time.Before(sinfo.pingTime) { + if !sTime.Before(pingTime) { // Update pingTime to start the clock for searches (above) - sinfo.pingTime = now + sinfo.pingTime.Store(now) } - if time.Since(sinfo.pingSend) > time.Second { + if time.Since(pingSend) > time.Second { // Send at most 1 ping per second - sinfo.pingSend = now + sinfo.pingSend.Store(now) r.core.sessions.sendPingPong(sinfo, false) } - sinfo.timeMutex.Unlock() } fallthrough // Also send the packet default: diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index dcf0c81f..e81a9723 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -212,9 +212,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { } } // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? - sinfo.coordsMutex.Lock() sinfo.coords = res.Coords - sinfo.coordsMutex.Unlock() sinfo.packet = info.packet s.core.sessions.ping(sinfo) info.callback(sinfo, nil) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 3bf69a8c..b0bba2d0 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -18,41 +18,38 @@ 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 - reconfigure chan chan error - 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 - theirNonceMask uint64 - theirNonceMutex sync.Mutex // protects the above - myNonce crypto.BoxNonce - myNonceMutex sync.Mutex // protects the above - theirMTU uint16 - myMTU uint16 - wasMTUFixed bool // Was the MTU fixed by a receive error? - time time.Time // Time we last received a packet - 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 - timeMutex sync.RWMutex // protects all time fields above - coords []byte // coords of destination - coordsMutex sync.RWMutex // protects the above - packet []byte // a buffered packet, sent immediately on ping/pong - init bool // Reset if coords change - initMutex sync.RWMutex - send chan []byte - recv chan *wire_trafficPacket - closed chan interface{} - tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session + core *Core // + reconfigure chan chan error // + 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 // + theirNonceMask uint64 // + theirNonceMutex sync.Mutex // protects the above + myNonce crypto.BoxNonce // + myNonceMutex sync.Mutex // protects the above + theirMTU uint16 // + myMTU uint16 // + wasMTUFixed bool // Was the MTU fixed by a receive error? + time atomic.Value // time.Time // Time we last received a packet + mtuTime atomic.Value // time.Time // time myMTU was last changed + pingTime atomic.Value // time.Time // time the first ping was sent since the last received packet + pingSend atomic.Value // time.Time // time the last ping was sent + coords []byte // coords of destination + packet []byte // a buffered packet, sent immediately on ping/pong + init atomic.Value // bool // Reset if coords change + send chan []byte // + recv chan *wire_trafficPacket // + closed chan interface{} // + tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation + 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. @@ -60,10 +57,10 @@ type sessionPing struct { 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 - MTU uint16 + Coords []byte // + Tstamp int64 // unix time, but the only real requirement is that it increases + IsPong bool // + MTU uint16 // } // Updates session info in response to a ping, after checking that the ping is OK. @@ -93,21 +90,15 @@ func (s *sessionInfo) update(p *sessionPing) bool { s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) } now := time.Now() - s.timeMutex.Lock() - s.time = now - s.timeMutex.Unlock() + s.time.Store(now) atomic.StoreInt64(&s.tstamp, p.Tstamp) - s.initMutex.Lock() - s.init = true - s.initMutex.Unlock() + s.init.Store(true) return true } // Returns true if the session has been idle for longer than the allowed timeout. func (s *sessionInfo) timedout() bool { - s.timeMutex.RLock() - defer s.timeMutex.RUnlock() - return time.Since(s.time) > time.Minute + return time.Since(s.time.Load().(time.Time)) > time.Minute } // Struct of all active sessions. @@ -291,12 +282,10 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirMTU = 1280 sinfo.myMTU = 1280 now := time.Now() - sinfo.timeMutex.Lock() - sinfo.time = now - sinfo.mtuTime = now - sinfo.pingTime = now - sinfo.pingSend = now - sinfo.timeMutex.Unlock() + sinfo.time.Store(now) + sinfo.mtuTime.Store(now) + sinfo.pingTime.Store(now) + sinfo.pingSend.Store(now) higher := false for idx := range ss.core.boxPub { if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { @@ -437,7 +426,6 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { bs := ping.encode() shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) payload, nonce := crypto.BoxSeal(shared, bs, nil) - sinfo.coordsMutex.RLock() p := wire_protoTrafficPacket{ Coords: sinfo.coords, ToKey: sinfo.theirPermPub, @@ -445,13 +433,10 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { Nonce: *nonce, Payload: payload, } - sinfo.coordsMutex.RUnlock() packet := p.encode() ss.core.router.out(packet) if !isPong { - sinfo.timeMutex.Lock() - sinfo.pingSend = time.Now() - sinfo.timeMutex.Unlock() + sinfo.pingSend.Store(time.Now()) } } @@ -551,8 +536,6 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. func (ss *sessions) resetInits() { for _, sinfo := range ss.sinfos { - sinfo.initMutex.Lock() - sinfo.init = false - sinfo.initMutex.Unlock() + sinfo.init.Store(false) } } From 781cd7571f8ba92fe9ab1834b30b457efb338cdc Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Apr 2019 12:00:31 +0100 Subject: [PATCH 19/60] Fix race on tun conns, but still deadlocks if more than one connection is opened --- cmd/yggdrasil/main.go | 1 - src/tuntap/tun.go | 23 ++++++++++++++++++----- src/yggdrasil/conn.go | 2 -- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index f3d933c2..96349835 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -265,7 +265,6 @@ func main() { // Start the TUN/TAP interface if listener, err := n.core.ConnListen(); err == nil { if dialer, err := n.core.ConnDialer(); err == nil { - logger.Println("Got listener", listener, "and dialer", dialer) n.tuntap.Init(state, logger, listener, dialer) if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 9e46124a..acdcf3a1 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -30,7 +30,6 @@ type TunAdapter struct { config *config.NodeState log *log.Logger reconfigure chan chan error - conns map[crypto.NodeID]*yggdrasil.Conn listener *yggdrasil.Listener dialer *yggdrasil.Dialer addr address.Address @@ -39,6 +38,7 @@ type TunAdapter struct { mtu int iface *water.Interface mutex sync.RWMutex // Protects the below + conns map[crypto.NodeID]*yggdrasil.Conn isOpen bool } @@ -173,13 +173,24 @@ func (tun *TunAdapter) handler() error { tun.log.Errorln("TUN/TAP error accepting connection:", err) return err } - tun.log.Println("Accepted connection from", conn.RemoteAddr()) go tun.connReader(conn) } } func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { - tun.conns[conn.RemoteAddr()] = conn + remoteNodeID := conn.RemoteAddr() + tun.mutex.Lock() + if _, isIn := tun.conns[remoteNodeID]; isIn { + tun.mutex.Unlock() + return errors.New("duplicate connection") + } + tun.conns[remoteNodeID] = conn + tun.mutex.Unlock() + defer func() { + tun.mutex.Lock() + delete(tun.conns, remoteNodeID) + tun.mutex.Unlock() + }() b := make([]byte, 65535) for { n, err := conn.Read(b) @@ -242,8 +253,9 @@ func (tun *TunAdapter) ifaceReader() error { } dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? + tun.mutex.Lock() if conn, isIn := tun.conns[*dstNodeID]; isIn { - tun.log.Println("Got", &conn) + tun.mutex.Unlock() w, err := conn.Write(bs) if err != nil { tun.log.Println("Unable to write to remote:", err) @@ -253,11 +265,12 @@ func (tun *TunAdapter) ifaceReader() error { continue } } else { - tun.log.Println("Opening connection for", *dstNodeID) if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { tun.conns[*dstNodeID] = &conn + tun.mutex.Unlock() go tun.connReader(&conn) } else { + tun.mutex.Unlock() tun.log.Println("Error dialing:", err) } } diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index daba2987..ed9eb6cd 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -2,7 +2,6 @@ package yggdrasil import ( "errors" - "fmt" "sync" "sync/atomic" "time" @@ -124,7 +123,6 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return 0, errors.New("session is closed") } if c.session == nil { - fmt.Println("No session found, starting search for", &c) c.core.router.doAdmin(func() { c.startSearch() }) From 0b8f5b5dda8122b7563b3207a2dcb981d1055cc6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Apr 2019 12:28:46 +0100 Subject: [PATCH 20/60] Tweaks --- src/tuntap/tun.go | 18 +++++++++--------- src/yggdrasil/conn.go | 17 ++++++++++------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index acdcf3a1..d9e0e77e 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -170,7 +170,7 @@ func (tun *TunAdapter) handler() error { // Accept the incoming connection conn, err := tun.listener.Accept() if err != nil { - tun.log.Errorln("TUN/TAP error accepting connection:", err) + tun.log.Errorln("TUN/TAP connection accept error:", err) return err } go tun.connReader(conn) @@ -195,7 +195,7 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { for { n, err := conn.Read(b) if err != nil { - tun.log.Errorln("TUN/TAP read error:", err) + tun.log.Errorln("TUN/TAP conn read error:", err) return err } if n == 0 { @@ -203,11 +203,11 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { } w, err := tun.iface.Write(b[:n]) if err != nil { - tun.log.Errorln("TUN/TAP failed to write to interface:", err) + tun.log.Errorln("TUN/TAP iface write error:", err) continue } if w != n { - tun.log.Errorln("TUN/TAP wrote", w, "instead of", n, "which is bad") + tun.log.Errorln("TUN/TAP iface write len didn't match conn read len") continue } } @@ -231,7 +231,7 @@ func (tun *TunAdapter) ifaceReader() error { if bs[0]&0xf0 == 0x60 { // Check if we have a fully-sized header if len(bs) < 40 { - panic("Tried to send a packet shorter than an IPv6 header...") + continue } // IPv6 address addrlen = 16 @@ -241,14 +241,14 @@ func (tun *TunAdapter) ifaceReader() error { } else if bs[0]&0xf0 == 0x40 { // Check if we have a fully-sized header if len(bs) < 20 { - panic("Tried to send a packet shorter than an IPv4 header...") + continue } // IPv4 address addrlen = 4 copy(srcAddr[:addrlen], bs[12:]) copy(dstAddr[:addrlen], bs[16:]) } else { - // Unknown address length + // Unknown address length or protocol continue } dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() @@ -258,7 +258,7 @@ func (tun *TunAdapter) ifaceReader() error { tun.mutex.Unlock() w, err := conn.Write(bs) if err != nil { - tun.log.Println("Unable to write to remote:", err) + tun.log.Println("TUN/TAP conn write error:", err) continue } if w != n { @@ -271,7 +271,7 @@ func (tun *TunAdapter) ifaceReader() error { go tun.connReader(&conn) } else { tun.mutex.Unlock() - tun.log.Println("Error dialing:", err) + tun.log.Println("TUN/TAP dial error:", err) } } diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index ed9eb6cd..977c7421 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -2,6 +2,7 @@ package yggdrasil import ( "errors" + "fmt" "sync" "sync/atomic" "time" @@ -21,6 +22,10 @@ type Conn struct { expired bool } +func (c *Conn) String() string { + return fmt.Sprintf("c=%p", c) +} + // This method should only be called from the router goroutine func (c *Conn) startSearch() { searchCompleted := func(sinfo *sessionInfo, err error) { @@ -76,8 +81,8 @@ func (c *Conn) Read(b []byte) (int, error) { if c.session == nil { return 0, errors.New("searching for remote side") } - if !c.session.init.Load().(bool) { - return 0, errors.New("waiting for remote side to accept") + if init, ok := c.session.init.Load().(bool); !ok || (ok && !init) { + return 0, errors.New("waiting for remote side to accept " + c.String()) } select { case p, ok := <-c.session.recv: @@ -129,15 +134,12 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return 0, errors.New("searching for remote side") } defer util.PutBytes(b) - if !c.session.init.Load().(bool) { - return 0, errors.New("waiting for remote side to accept") + if init, ok := c.session.init.Load().(bool); !ok || (ok && !init) { + return 0, errors.New("waiting for remote side to accept " + c.String()) } - // code isn't multithreaded so appending to this is safe coords := c.session.coords - // Prepare the payload c.session.myNonceMutex.Lock() payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) - c.session.myNonceMutex.Unlock() defer util.PutBytes(payload) p := wire_trafficPacket{ Coords: coords, @@ -146,6 +148,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { Payload: payload, } packet := p.encode() + c.session.myNonceMutex.Unlock() atomic.AddUint64(&c.session.bytesSent, uint64(len(b))) select { case c.session.send <- packet: From 5dada3952c5c2a59390a5db19f99fceecd4754a7 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Apr 2019 20:38:14 -0500 Subject: [PATCH 21/60] use a session worker to try to avoid mutex hell. compiles, but incomplete and doesn't work yet --- src/yggdrasil/conn.go | 162 +++++++++++++++++++++------------------ src/yggdrasil/dialer.go | 1 + src/yggdrasil/router.go | 34 ++++---- src/yggdrasil/session.go | 145 ++++++++++++++++++++--------------- 4 files changed, 191 insertions(+), 151 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 977c7421..7898a128 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "sync" - "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -15,10 +14,11 @@ type Conn struct { core *Core nodeID *crypto.NodeID nodeMask *crypto.NodeID - session *sessionInfo + recv chan *wire_trafficPacket // Eventually gets attached to session.recv mutex *sync.RWMutex - readDeadline time.Time - writeDeadline time.Time + session *sessionInfo + readDeadline time.Time // TODO timer + writeDeadline time.Time // TODO timer expired bool } @@ -39,6 +39,7 @@ func (c *Conn) startSearch() { if sinfo != nil { c.mutex.Lock() c.session = sinfo + c.session.recv = c.recv c.nodeID, c.nodeMask = sinfo.theirAddr.GetNodeIDandMask() c.mutex.Unlock() } @@ -50,113 +51,124 @@ func (c *Conn) startSearch() { } c.core.searches.continueSearch(sinfo) } - switch { - case c.session == nil || !c.session.init.Load().(bool): + c.mutex.RLock() + defer c.mutex.RUnlock() + if c.session == nil { doSearch() - case time.Since(c.session.time.Load().(time.Time)) > 6*time.Second: - sTime := c.session.time.Load().(time.Time) - pingTime := c.session.pingTime.Load().(time.Time) - if sTime.Before(pingTime) && time.Since(pingTime) > 6*time.Second { - doSearch() - } else { - pingSend := c.session.pingSend.Load().(time.Time) - now := time.Now() - if !sTime.Before(pingTime) { - c.session.pingTime.Store(now) - } - if time.Since(pingSend) > time.Second { - c.session.pingSend.Store(now) - c.core.sessions.sendPingPong(c.session, false) + } else { + sinfo := c.session // In case c.session is somehow changed meanwhile + sinfo.worker <- func() { + switch { + case !sinfo.init: + doSearch() + case time.Since(sinfo.time) > 6*time.Second: + if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + // TODO double check that the above condition is correct + doSearch() + } else { + c.core.sessions.ping(sinfo) + } + default: // Don't do anything, to keep traffic throttled } } } } func (c *Conn) Read(b []byte) (int, error) { - c.mutex.RLock() - defer c.mutex.RUnlock() - if c.expired { - return 0, errors.New("session is closed") - } - if c.session == nil { - return 0, errors.New("searching for remote side") - } - if init, ok := c.session.init.Load().(bool); !ok || (ok && !init) { - return 0, errors.New("waiting for remote side to accept " + c.String()) + err := func() error { + c.mutex.RLock() + defer c.mutex.RUnlock() + if c.expired { + return errors.New("session is closed") + } + return nil + }() + if err != nil { + return 0, err } select { - case p, ok := <-c.session.recv: + // TODO... + case p, ok := <-c.recv: if !ok { + c.mutex.Lock() c.expired = true + c.mutex.Unlock() return 0, errors.New("session is closed") } defer util.PutBytes(p.Payload) - err := func() error { - c.session.theirNonceMutex.Lock() - defer c.session.theirNonceMutex.Unlock() - if !c.session.nonceIsOK(&p.Nonce) { - return errors.New("packet dropped due to invalid nonce") + c.mutex.RLock() + sinfo := c.session + c.mutex.RUnlock() + var err error + sinfo.doWorker(func() { + if !sinfo.nonceIsOK(&p.Nonce) { + err = errors.New("packet dropped due to invalid nonce") + return } - bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) + bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) if !isOK { util.PutBytes(bs) - return errors.New("packet dropped due to decryption failure") + err = errors.New("packet dropped due to decryption failure") + return } copy(b, bs) if len(bs) < len(b) { b = b[:len(bs)] } - c.session.updateNonce(&p.Nonce) - c.session.time.Store(time.Now()) - return nil - }() + sinfo.updateNonce(&p.Nonce) + sinfo.time = time.Now() + sinfo.bytesRecvd += uint64(len(b)) + }) if err != nil { return 0, err } - atomic.AddUint64(&c.session.bytesRecvd, uint64(len(b))) return len(b), nil - case <-c.session.closed: - c.expired = true - return len(b), errors.New("session is closed") + //case <-c.recvTimeout: + //case <-c.session.closed: + // c.expired = true + // return len(b), errors.New("session is closed") } } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - c.mutex.RLock() - defer c.mutex.RUnlock() - if c.expired { - return 0, errors.New("session is closed") + var sinfo *sessionInfo + err = func() error { + c.mutex.RLock() + defer c.mutex.RUnlock() + if c.expired { + return errors.New("session is closed") + } + sinfo = c.session + return nil + }() + if err != nil { + return 0, err } - if c.session == nil { + if sinfo == nil { c.core.router.doAdmin(func() { c.startSearch() }) return 0, errors.New("searching for remote side") } - defer util.PutBytes(b) - if init, ok := c.session.init.Load().(bool); !ok || (ok && !init) { - return 0, errors.New("waiting for remote side to accept " + c.String()) - } - coords := c.session.coords - c.session.myNonceMutex.Lock() - payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) - defer util.PutBytes(payload) - p := wire_trafficPacket{ - Coords: coords, - Handle: c.session.theirHandle, - Nonce: *nonce, - Payload: payload, - } - packet := p.encode() - c.session.myNonceMutex.Unlock() - atomic.AddUint64(&c.session.bytesSent, uint64(len(b))) - select { - case c.session.send <- packet: - case <-c.session.closed: - c.expired = true - return len(b), errors.New("session is closed") - } - c.session.core.router.out(packet) + //defer util.PutBytes(b) + var packet []byte + sinfo.doWorker(func() { + if !sinfo.init { + err = errors.New("waiting for remote side to accept " + c.String()) + return + } + payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce) + defer util.PutBytes(payload) + p := wire_trafficPacket{ + Coords: sinfo.coords, + Handle: sinfo.theirHandle, + Nonce: *nonce, + Payload: payload, + } + packet = p.encode() + sinfo.bytesSent += uint64(len(b)) + }) + sinfo.core.router.out(packet) return len(b), nil } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 4a3d8167..49ce0a98 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -63,6 +63,7 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, err mutex: &sync.RWMutex{}, nodeID: nodeID, nodeMask: nodeMask, + recv: make(chan *wire_trafficPacket, 32), } conn.core.router.doAdmin(func() { conn.startSearch() diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 7da5162f..348a1ed6 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -23,7 +23,7 @@ package yggdrasil // The router then runs some sanity checks before passing it to the adapter import ( - "bytes" + //"bytes" "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -42,12 +42,12 @@ type router struct { out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() recv chan<- []byte // place where the adapter pulls received packets from - send <-chan []byte // place where the adapter puts outgoing packets - reject chan<- RejectedPacket // place where we send error packets back to adapter - reset chan struct{} // signal that coords changed (re-init sessions/dht) - admin chan func() // pass a lambda for the admin socket to query stuff - cryptokey cryptokey - nodeinfo nodeinfo + //send <-chan []byte // place where the adapter puts outgoing packets + reject chan<- RejectedPacket // place where we send error packets back to adapter + reset chan struct{} // signal that coords changed (re-init sessions/dht) + admin chan func() // pass a lambda for the admin socket to query stuff + cryptokey cryptokey + nodeinfo nodeinfo } // Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the adapter. @@ -122,11 +122,11 @@ func (r *router) init(core *Core) { }() r.out = func(packet []byte) { out2 <- packet } r.toRecv = make(chan router_recvPacket, 32) - recv := make(chan []byte, 32) - send := make(chan []byte, 32) + //recv := make(chan []byte, 32) + //send := make(chan []byte, 32) reject := make(chan RejectedPacket, 32) - r.recv = recv - r.send = send + //r.recv = recv + //r.send = send r.reject = reject r.reset = make(chan struct{}, 1) r.admin = make(chan func(), 32) @@ -157,8 +157,8 @@ func (r *router) mainLoop() { r.recvPacket(rp.bs, rp.sinfo) case p := <-r.in: r.handleIn(p) - case p := <-r.send: - r.sendPacket(p) + //case p := <-r.send: + // r.sendPacket(p) case info := <-r.core.dht.peers: r.core.dht.insertPeer(info) case <-r.reset: @@ -181,6 +181,7 @@ func (r *router) mainLoop() { } } +/* // Checks a packet's to/from address to make sure it's in the allowed range. // If a session to the destination exists, gets the session and passes the packet to it. // If no session exists, it triggers (or continues) a search. @@ -353,6 +354,7 @@ func (r *router) sendPacket(bs []byte) { sinfo.send <- bs } } +*/ // Called for incoming traffic by the session worker for that connection. // Checks that the IP address is correct (matches the session) and passes the packet to the adapter. @@ -429,7 +431,11 @@ func (r *router) handleTraffic(packet []byte) { if !isIn { return } - sinfo.recv <- &p + select { + case sinfo.recv <- &p: // FIXME ideally this should be FIFO + default: + util.PutBytes(p.Payload) + } } // Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index b0bba2d0..15d346a1 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -18,38 +18,50 @@ 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 // - reconfigure chan chan error // - 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 // - theirNonceMask uint64 // - theirNonceMutex sync.Mutex // protects the above - myNonce crypto.BoxNonce // - myNonceMutex sync.Mutex // protects the above - theirMTU uint16 // - myMTU uint16 // - wasMTUFixed bool // Was the MTU fixed by a receive error? - time atomic.Value // time.Time // Time we last received a packet - mtuTime atomic.Value // time.Time // time myMTU was last changed - pingTime atomic.Value // time.Time // time the first ping was sent since the last received packet - pingSend atomic.Value // time.Time // time the last ping was sent - coords []byte // coords of destination - packet []byte // a buffered packet, sent immediately on ping/pong - init atomic.Value // bool // Reset if coords change - send chan []byte // - recv chan *wire_trafficPacket // - closed chan interface{} // - tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session + core *Core // + reconfigure chan chan error // + 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 // + theirNonceMask uint64 // + myNonce crypto.BoxNonce // + theirMTU uint16 // + myMTU uint16 // + wasMTUFixed bool // Was the MTU fixed by a receive error? + time time.Time // Time we last received a packet + 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 + coords []byte // coords of destination + packet []byte // a buffered packet, sent immediately on ping/pong + init bool // Reset if coords change + tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation + bytesSent uint64 // Bytes of real traffic sent in this session + bytesRecvd uint64 // Bytes of real traffic received in this session + worker chan func() // Channel to send work to the session worker + recv chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn +} + +func (sinfo *sessionInfo) doWorker(f func()) { + done := make(chan struct{}) + sinfo.worker <- func() { + f() + close(done) + } + <-done +} + +func (sinfo *sessionInfo) workerMain() { + for f := range sinfo.worker { + f() + } } // 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. @@ -89,16 +101,19 @@ func (s *sessionInfo) update(p *sessionPing) bool { // allocate enough space for additional coords s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) } - now := time.Now() - s.time.Store(now) - atomic.StoreInt64(&s.tstamp, p.Tstamp) - s.init.Store(true) + s.time = time.Now() + s.tstamp = p.Tstamp + s.init = true return true } // Returns true if the session has been idle for longer than the allowed timeout. func (s *sessionInfo) timedout() bool { - return time.Since(s.time.Load().(time.Time)) > time.Minute + var timedout bool + s.doWorker(func() { + timedout = time.Since(s.time) > time.Minute + }) + return timedout } // Struct of all active sessions. @@ -282,10 +297,10 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirMTU = 1280 sinfo.myMTU = 1280 now := time.Now() - sinfo.time.Store(now) - sinfo.mtuTime.Store(now) - sinfo.pingTime.Store(now) - sinfo.pingSend.Store(now) + sinfo.time = now + sinfo.mtuTime = now + sinfo.pingTime = now + sinfo.pingSend = now higher := false for idx := range ss.core.boxPub { if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { @@ -305,14 +320,13 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { 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) - sinfo.closed = make(chan interface{}) + sinfo.worker = make(chan func(), 1) ss.sinfos[sinfo.myHandle] = &sinfo ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle ss.addrToPerm[sinfo.theirAddr] = &sinfo.theirPermPub ss.subnetToPerm[sinfo.theirSubnet] = &sinfo.theirPermPub + go sinfo.workerMain() return &sinfo } @@ -366,14 +380,12 @@ func (ss *sessions) cleanup() { // Closes a session, removing it from sessions maps and killing the worker goroutine. func (sinfo *sessionInfo) close() { - close(sinfo.closed) delete(sinfo.core.sessions.sinfos, sinfo.myHandle) delete(sinfo.core.sessions.byMySes, sinfo.mySesPub) delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr) delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet) - close(sinfo.send) - close(sinfo.recv) + close(sinfo.worker) } // Returns a session ping appropriate for the given session info. @@ -436,7 +448,7 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { packet := p.encode() ss.core.router.out(packet) if !isPong { - sinfo.pingSend.Store(time.Now()) + sinfo.pingSend = time.Now() } } @@ -468,29 +480,36 @@ func (ss *sessions) handlePing(ping *sessionPing) { mutex: &sync.RWMutex{}, nodeID: crypto.GetNodeID(&sinfo.theirPermPub), nodeMask: &crypto.NodeID{}, + recv: make(chan *wire_trafficPacket, 32), } for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } + sinfo.recv = conn.recv ss.listener.conn <- conn } else { ss.core.log.Debugln("Received new session but there is no listener, ignoring") } ss.listenerMutex.Unlock() } - // Update the session - if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ - return - } - if !ping.IsPong { - ss.sendPingPong(sinfo, true) - } - if sinfo.packet != nil { - // send - var bs []byte - bs, sinfo.packet = sinfo.packet, nil - ss.core.router.sendPacket(bs) - } + sinfo.doWorker(func() { + // Update the session + if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ + return + } + if !ping.IsPong { + ss.sendPingPong(sinfo, true) + } + if sinfo.packet != nil { + /* FIXME this needs to live in the net.Conn or something, needs work in Write + // send + var bs []byte + bs, sinfo.packet = sinfo.packet, nil + ss.core.router.sendPacket(bs) // FIXME this needs to live in the net.Conn or something, needs work in Write + */ + sinfo.packet = nil + } + }) } // Get the MTU of the session. @@ -536,6 +555,8 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. func (ss *sessions) resetInits() { for _, sinfo := range ss.sinfos { - sinfo.init.Store(false) + sinfo.doWorker(func() { + sinfo.init = false + }) } } From 9ce7fe2e3fbd72e24da88441236e4508160c3554 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Apr 2019 20:56:12 -0500 Subject: [PATCH 22/60] fix tun/tap CIDR notation so things work on linux, may break other platforms for all I know --- src/tuntap/tun.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index d9e0e77e..912998ce 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -5,6 +5,7 @@ package tuntap import ( "encoding/hex" "errors" + "fmt" "net" "sync" "time" @@ -124,8 +125,9 @@ func (tun *TunAdapter) Start() error { tun.mtu = tun.config.Current.IfMTU ifname := tun.config.Current.IfName iftapmode := tun.config.Current.IfTAPMode + addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if ifname != "none" { - if err := tun.setup(ifname, iftapmode, net.IP(tun.addr[:]).String(), tun.mtu); err != nil { + if err := tun.setup(ifname, iftapmode, addr, tun.mtu); err != nil { return err } } From 5a02e2ff443c7eac69ca9abe90226db103f3bf26 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Apr 2019 22:31:56 -0500 Subject: [PATCH 23/60] apparently it was these callbacks that were sometimes deadlocking things --- src/yggdrasil/search.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index e81a9723..3460fd43 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -140,7 +140,7 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { if len(sinfo.toVisit) == 0 { // Dead end, do cleanup delete(s.searches, sinfo.dest) - sinfo.callback(nil, errors.New("search reached dead end")) + go sinfo.callback(nil, errors.New("search reached dead end")) return } // Send to the next search target @@ -203,7 +203,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { sinfo = s.core.sessions.createSession(&res.Key) if sinfo == nil { // nil if the DHT search finished but the session wasn't allowed - info.callback(nil, errors.New("session not allowed")) + go info.callback(nil, errors.New("session not allowed")) return true } _, isIn := s.core.sessions.getByTheirPerm(&res.Key) @@ -215,7 +215,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { sinfo.coords = res.Coords sinfo.packet = info.packet s.core.sessions.ping(sinfo) - info.callback(sinfo, nil) + go info.callback(sinfo, nil) // Cleanup delete(s.searches, res.Dest) return true From 47eb2fc47fef04b7cfde6d97dd5c0ae72ad144c8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 11:20:35 +0100 Subject: [PATCH 24/60] Break deadlock by creating session recv queue when session is created instead of repointing at search completion, also make expired atomic --- src/tuntap/tun.go | 16 ++++++++---- src/yggdrasil/conn.go | 56 ++++++++++++++-------------------------- src/yggdrasil/session.go | 4 +-- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 912998ce..5f87c200 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -193,12 +193,13 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { delete(tun.conns, remoteNodeID) tun.mutex.Unlock() }() + tun.log.Debugln("Start connection reader for", conn.String()) b := make([]byte, 65535) for { n, err := conn.Read(b) if err != nil { tun.log.Errorln("TUN/TAP conn read error:", err) - return err + continue } if n == 0 { continue @@ -209,7 +210,7 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { continue } if w != n { - tun.log.Errorln("TUN/TAP iface write len didn't match conn read len") + tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") continue } } @@ -220,7 +221,7 @@ func (tun *TunAdapter) ifaceReader() error { for { n, err := tun.iface.Read(bs) if err != nil { - tun.log.Errorln("TUN/TAP iface read error:", err) + continue } // Look up if the dstination address is somewhere we already have an // open connection to @@ -253,6 +254,10 @@ func (tun *TunAdapter) ifaceReader() error { // Unknown address length or protocol continue } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + // For now don't deal with any non-Yggdrasil ranges + continue + } dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? tun.mutex.Lock() @@ -260,10 +265,11 @@ func (tun *TunAdapter) ifaceReader() error { tun.mutex.Unlock() w, err := conn.Write(bs) if err != nil { - tun.log.Println("TUN/TAP conn write error:", err) + tun.log.Errorln("TUN/TAP conn write error:", err) continue } if w != n { + tun.log.Errorln("TUN/TAP conn write mismatch:", w, "bytes written vs", n, "bytes given") continue } } else { @@ -273,7 +279,7 @@ func (tun *TunAdapter) ifaceReader() error { go tun.connReader(&conn) } else { tun.mutex.Unlock() - tun.log.Println("TUN/TAP dial error:", err) + tun.log.Errorln("TUN/TAP dial error:", err) } } diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 7898a128..9308d344 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "sync" + "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -17,9 +18,9 @@ type Conn struct { recv chan *wire_trafficPacket // Eventually gets attached to session.recv mutex *sync.RWMutex session *sessionInfo - readDeadline time.Time // TODO timer - writeDeadline time.Time // TODO timer - expired bool + readDeadline atomic.Value // time.Time // TODO timer + writeDeadline atomic.Value // time.Time // TODO timer + expired atomic.Value // bool } func (c *Conn) String() string { @@ -32,14 +33,12 @@ func (c *Conn) startSearch() { if err != nil { c.core.log.Debugln("DHT search failed:", err) c.mutex.Lock() - c.expired = true - c.mutex.Unlock() + c.expired.Store(true) return } if sinfo != nil { c.mutex.Lock() c.session = sinfo - c.session.recv = c.recv c.nodeID, c.nodeMask = sinfo.theirAddr.GetNodeIDandMask() c.mutex.Unlock() } @@ -75,30 +74,20 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { - err := func() error { - c.mutex.RLock() - defer c.mutex.RUnlock() - if c.expired { - return errors.New("session is closed") - } - return nil - }() - if err != nil { - return 0, err + if e, ok := c.expired.Load().(bool); ok && e { + return 0, errors.New("session is closed") } + c.mutex.RLock() + sinfo := c.session + c.mutex.RUnlock() select { // TODO... case p, ok := <-c.recv: if !ok { - c.mutex.Lock() - c.expired = true - c.mutex.Unlock() + c.expired.Store(true) return 0, errors.New("session is closed") } defer util.PutBytes(p.Payload) - c.mutex.RLock() - sinfo := c.session - c.mutex.RUnlock() var err error sinfo.doWorker(func() { if !sinfo.nonceIsOK(&p.Nonce) { @@ -131,19 +120,12 @@ func (c *Conn) Read(b []byte) (int, error) { } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - var sinfo *sessionInfo - err = func() error { - c.mutex.RLock() - defer c.mutex.RUnlock() - if c.expired { - return errors.New("session is closed") - } - sinfo = c.session - return nil - }() - if err != nil { - return 0, err + if e, ok := c.expired.Load().(bool); ok && e { + return 0, errors.New("session is closed") } + c.mutex.RLock() + sinfo := c.session + c.mutex.RUnlock() if sinfo == nil { c.core.router.doAdmin(func() { c.startSearch() @@ -173,7 +155,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } func (c *Conn) Close() error { - c.expired = true + c.expired.Store(true) c.session.close() return nil } @@ -195,11 +177,11 @@ func (c *Conn) SetDeadline(t time.Time) error { } func (c *Conn) SetReadDeadline(t time.Time) error { - c.readDeadline = t + c.readDeadline.Store(t) return nil } func (c *Conn) SetWriteDeadline(t time.Time) error { - c.writeDeadline = t + c.writeDeadline.Store(t) return nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 15d346a1..55d4a0c6 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -321,6 +321,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.worker = make(chan func(), 1) + sinfo.recv = make(chan *wire_trafficPacket, 32) ss.sinfos[sinfo.myHandle] = &sinfo ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle @@ -480,12 +481,11 @@ func (ss *sessions) handlePing(ping *sessionPing) { mutex: &sync.RWMutex{}, nodeID: crypto.GetNodeID(&sinfo.theirPermPub), nodeMask: &crypto.NodeID{}, - recv: make(chan *wire_trafficPacket, 32), + recv: sinfo.recv, } for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } - sinfo.recv = conn.recv ss.listener.conn <- conn } else { ss.core.log.Debugln("Received new session but there is no listener, ignoring") From ccf03fd3b6de8f41d8e192489d85e11f48db3c61 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 11:22:40 +0100 Subject: [PATCH 25/60] Don't write huge mostly empty buffers unnecessarily --- src/tuntap/tun.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 5f87c200..746293d4 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -263,7 +263,7 @@ func (tun *TunAdapter) ifaceReader() error { tun.mutex.Lock() if conn, isIn := tun.conns[*dstNodeID]; isIn { tun.mutex.Unlock() - w, err := conn.Write(bs) + w, err := conn.Write(bs[:n]) if err != nil { tun.log.Errorln("TUN/TAP conn write error:", err) continue From bbd1246f7bf9615941a77608c63260fa6b744e00 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 11:49:47 +0100 Subject: [PATCH 26/60] Fix bug in mask generation for outbound dials, change iface reader mutexes to read-only locks unless RW is needed --- src/tuntap/tun.go | 26 ++++++++++++++++---------- src/yggdrasil/dialer.go | 6 +++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 746293d4..3bdbde25 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -260,9 +260,9 @@ func (tun *TunAdapter) ifaceReader() error { } dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? - tun.mutex.Lock() + tun.mutex.RLock() if conn, isIn := tun.conns[*dstNodeID]; isIn { - tun.mutex.Unlock() + tun.mutex.RUnlock() w, err := conn.Write(bs[:n]) if err != nil { tun.log.Errorln("TUN/TAP conn write error:", err) @@ -273,14 +273,20 @@ func (tun *TunAdapter) ifaceReader() error { continue } } else { - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - tun.conns[*dstNodeID] = &conn - tun.mutex.Unlock() - go tun.connReader(&conn) - } else { - tun.mutex.Unlock() - tun.log.Errorln("TUN/TAP dial error:", err) - } + tun.mutex.RUnlock() + func() { + tun.mutex.Lock() + defer tun.mutex.Unlock() + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + tun.log.Debugln("Opening new session connection") + tun.log.Debugln("Node:", dstNodeID) + tun.log.Debugln("Mask:", dstNodeIDMask) + tun.conns[*dstNodeID] = &conn + go tun.connReader(&conn) + } else { + tun.log.Errorln("TUN/TAP dial error:", err) + } + }() } /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 49ce0a98..bd4b87e2 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -35,7 +35,7 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { return Conn{}, err } copy(nodeID[:], dest) - for idx := 0; idx <= len; idx++ { + for idx := 0; idx < len; idx++ { nodeMask[idx/8] |= 0x80 >> byte(idx%8) } } else { @@ -65,8 +65,8 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, err nodeMask: nodeMask, recv: make(chan *wire_trafficPacket, 32), } - conn.core.router.doAdmin(func() { + conn.core.router.admin <- func() { conn.startSearch() - }) + } return conn, nil } From 9778f5d2b86336189d259b9bb272dcb9e470f0e4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 15:00:19 +0100 Subject: [PATCH 27/60] Fix search behaviour on closed Conns, various other fixes --- src/tuntap/tun.go | 66 +++++++++++++++++-------------- src/yggdrasil/conn.go | 84 +++++++++++++++++++++++++++++++--------- src/yggdrasil/dialer.go | 3 -- src/yggdrasil/search.go | 7 ++-- src/yggdrasil/session.go | 1 + 5 files changed, 107 insertions(+), 54 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 3bdbde25..07264dab 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -193,12 +193,11 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { delete(tun.conns, remoteNodeID) tun.mutex.Unlock() }() - tun.log.Debugln("Start connection reader for", conn.String()) b := make([]byte, 65535) for { n, err := conn.Read(b) if err != nil { - tun.log.Errorln("TUN/TAP conn read error:", err) + tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) continue } if n == 0 { @@ -206,11 +205,11 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { } w, err := tun.iface.Write(b[:n]) if err != nil { - tun.log.Errorln("TUN/TAP iface write error:", err) + tun.log.Errorln(conn.String(), "TUN/TAP iface write error:", err) continue } if w != n { - tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") + tun.log.Errorln(conn.String(), "TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") continue } } @@ -219,20 +218,24 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { func (tun *TunAdapter) ifaceReader() error { bs := make([]byte, 65535) for { + // Wait for a packet to be delivered to us through the TUN/TAP adapter n, err := tun.iface.Read(bs) if err != nil { continue } - // Look up if the dstination address is somewhere we already have an - // open connection to + // From the IP header, work out what our source and destination addresses + // and node IDs are. We will need these in order to work out where to send + // the packet var srcAddr address.Address var dstAddr address.Address var dstNodeID *crypto.NodeID var dstNodeIDMask *crypto.NodeID var dstSnet address.Subnet var addrlen int + // Check the IP protocol - if it doesn't match then we drop the packet and + // do nothing with it if bs[0]&0xf0 == 0x60 { - // Check if we have a fully-sized header + // Check if we have a fully-sized IPv6 header if len(bs) < 40 { continue } @@ -242,7 +245,7 @@ func (tun *TunAdapter) ifaceReader() error { copy(dstAddr[:addrlen], bs[24:]) copy(dstSnet[:addrlen/2], bs[24:]) } else if bs[0]&0xf0 == 0x40 { - // Check if we have a fully-sized header + // Check if we have a fully-sized IPv4 header if len(bs) < 20 { continue } @@ -251,7 +254,7 @@ func (tun *TunAdapter) ifaceReader() error { copy(srcAddr[:addrlen], bs[12:]) copy(dstAddr[:addrlen], bs[16:]) } else { - // Unknown address length or protocol + // Unknown address length or protocol, so drop the packet and ignore it continue } if !dstAddr.IsValid() && !dstSnet.IsValid() { @@ -261,32 +264,39 @@ func (tun *TunAdapter) ifaceReader() error { dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? tun.mutex.RLock() - if conn, isIn := tun.conns[*dstNodeID]; isIn { - tun.mutex.RUnlock() + conn, isIn := tun.conns[*dstNodeID] + tun.mutex.RUnlock() + // If we don't have a connection then we should open one + if !isIn { + // Dial to the remote node + if c, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + // We've been given a connection, so save it in our connections so we + // can refer to it the next time we send a packet to this destination + tun.mutex.Lock() + tun.conns[*dstNodeID] = &c + tun.mutex.Unlock() + // Start the connection reader goroutine + go tun.connReader(&c) + // Then update our reference to the connection + conn, isIn = &c, true + } else { + // We weren't able to dial for some reason so there's no point in + // continuing this iteration - skip to the next one + continue + } + } + // If we have an open connection, either because we already had one or + // because we opened one above, try writing the packet to it + if isIn && conn != nil { w, err := conn.Write(bs[:n]) if err != nil { - tun.log.Errorln("TUN/TAP conn write error:", err) + tun.log.Errorln(conn.String(), "TUN/TAP conn write error:", err) continue } if w != n { - tun.log.Errorln("TUN/TAP conn write mismatch:", w, "bytes written vs", n, "bytes given") + tun.log.Errorln(conn.String(), "TUN/TAP conn write mismatch:", w, "bytes written vs", n, "bytes given") continue } - } else { - tun.mutex.RUnlock() - func() { - tun.mutex.Lock() - defer tun.mutex.Unlock() - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - tun.log.Debugln("Opening new session connection") - tun.log.Debugln("Node:", dstNodeID) - tun.log.Debugln("Mask:", dstNodeIDMask) - tun.conns[*dstNodeID] = &conn - go tun.connReader(&conn) - } else { - tun.log.Errorln("TUN/TAP dial error:", err) - } - }() } /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9308d344..0accf163 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -21,41 +21,50 @@ type Conn struct { readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer expired atomic.Value // bool + searching atomic.Value // bool } func (c *Conn) String() string { - return fmt.Sprintf("c=%p", c) + return fmt.Sprintf("conn=%p", c) } // This method should only be called from the router goroutine func (c *Conn) startSearch() { searchCompleted := func(sinfo *sessionInfo, err error) { + c.searching.Store(false) + c.mutex.Lock() + defer c.mutex.Unlock() if err != nil { - c.core.log.Debugln("DHT search failed:", err) - c.mutex.Lock() + c.core.log.Debugln(c.String(), "DHT search failed:", err) c.expired.Store(true) return } if sinfo != nil { - c.mutex.Lock() + c.core.log.Debugln(c.String(), "DHT search completed") c.session = sinfo c.nodeID, c.nodeMask = sinfo.theirAddr.GetNodeIDandMask() - c.mutex.Unlock() + c.expired.Store(false) + } else { + c.core.log.Debugln(c.String(), "DHT search failed: no session returned") + c.expired.Store(true) + return } } doSearch := func() { + c.searching.Store(true) sinfo, isIn := c.core.searches.searches[*c.nodeID] if !isIn { sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) } c.core.searches.continueSearch(sinfo) } c.mutex.RLock() - defer c.mutex.RUnlock() + sinfo := c.session + c.mutex.RUnlock() if c.session == nil { doSearch() } else { - sinfo := c.session // In case c.session is somehow changed meanwhile sinfo.worker <- func() { switch { case !sinfo.init: @@ -74,43 +83,66 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { + // If the session is marked as expired then do nothing at this point if e, ok := c.expired.Load().(bool); ok && e { return 0, errors.New("session is closed") } + // Take a copy of the session object c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() + // If the session is not initialised, do nothing. Currently in this instance + // in a write, we would trigger a new session, but it doesn't make sense for + // us to block forever here if the session will not reopen. + // TODO: should this return an error or just a zero-length buffer? + if !sinfo.init { + return 0, errors.New("session is closed") + } + // Wait for some traffic to come through from the session select { // TODO... case p, ok := <-c.recv: + // If the channel was closed then mark the connection as expired, this will + // mean that the next write will start a new search and reopen the session if !ok { c.expired.Store(true) return 0, errors.New("session is closed") } defer util.PutBytes(p.Payload) var err error + // Hand over to the session worker sinfo.doWorker(func() { + // If the nonce is bad then drop the packet and return an error if !sinfo.nonceIsOK(&p.Nonce) { err = errors.New("packet dropped due to invalid nonce") return } + // Decrypt the packet bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) + // Check if we were unable to decrypt the packet for some reason and + // return an error if we couldn't if !isOK { util.PutBytes(bs) err = errors.New("packet dropped due to decryption failure") return } + // Return the newly decrypted buffer back to the slice we were given copy(b, bs) + // Trim the slice down to size based on the data we received if len(bs) < len(b) { b = b[:len(bs)] } + // Update the session sinfo.updateNonce(&p.Nonce) sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(b)) }) + // Something went wrong in the session worker so abort if err != nil { return 0, err } + // If we've reached this point then everything went to plan, return the + // number of bytes we populated back into the given slice return len(b), nil //case <-c.recvTimeout: //case <-c.session.closed: @@ -120,27 +152,35 @@ func (c *Conn) Read(b []byte) (int, error) { } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - if e, ok := c.expired.Load().(bool); ok && e { - return 0, errors.New("session is closed") - } c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - if sinfo == nil { - c.core.router.doAdmin(func() { - c.startSearch() - }) - return 0, errors.New("searching for remote side") + // Check whether the connection is expired, if it is we can start a new + // search to revive it + expired, eok := c.expired.Load().(bool) + // If the session doesn't exist, or isn't initialised (which probably means + // that the session was never set up or it closed by timeout), or the conn + // is marked as expired, then see if we can start a new search + if sinfo == nil || !sinfo.init || (eok && expired) { + // Is a search already taking place? + if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) { + // No search was already taking place so start a new one + c.core.router.doAdmin(func() { + c.startSearch() + }) + return 0, errors.New("starting search") + } + // A search is already taking place so wait for it to finish + return 0, errors.New("waiting for search to complete") } //defer util.PutBytes(b) var packet []byte + // Hand over to the session worker sinfo.doWorker(func() { - if !sinfo.init { - err = errors.New("waiting for remote side to accept " + c.String()) - return - } + // Encrypt the packet payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce) defer util.PutBytes(payload) + // Construct the wire packet to send to the router p := wire_trafficPacket{ Coords: sinfo.coords, Handle: sinfo.theirHandle, @@ -150,13 +190,19 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { packet = p.encode() sinfo.bytesSent += uint64(len(b)) }) + // Give the packet to the router sinfo.core.router.out(packet) + // Finally return the number of bytes we wrote return len(b), nil } func (c *Conn) Close() error { + // Mark the connection as expired, so that a future read attempt will fail + // and a future write attempt will start a new search c.expired.Store(true) + // Close the session, if it hasn't been closed already c.session.close() + // This can't fail yet - TODO? return nil } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index bd4b87e2..89016100 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -65,8 +65,5 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, err nodeMask: nodeMask, recv: make(chan *wire_trafficPacket, 32), } - conn.core.router.admin <- func() { - conn.startSearch() - } return conn, nil } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 3460fd43..0a643363 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -90,11 +90,10 @@ func (s *searches) handleDHTRes(res *dhtRes) { if !isIn || s.checkDHTRes(sinfo, res) { // Either we don't recognize this search, or we just finished it return - } else { - // Add to the search and continue - s.addToSearch(sinfo, res) - s.doSearchStep(sinfo) } + // Add to the search and continue + s.addToSearch(sinfo, res) + s.doSearchStep(sinfo) } // Adds the information from a dhtRes to an ongoing search. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 55d4a0c6..e356f612 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -387,6 +387,7 @@ func (sinfo *sessionInfo) close() { delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr) delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet) close(sinfo.worker) + sinfo.init = false } // Returns a session ping appropriate for the given session info. From ea8948f3781877c5371d1b072326498a1d7a48d4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 20:06:39 +0100 Subject: [PATCH 28/60] TUN/TAP addr/subnet to Conn mappings, other fixes --- src/tuntap/tun.go | 95 ++++++++++++++++++++++++++-------------- src/yggdrasil/conn.go | 60 +++++++++++++------------ src/yggdrasil/session.go | 35 +++------------ 3 files changed, 101 insertions(+), 89 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 07264dab..c65c15e8 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -28,19 +28,20 @@ const tun_ETHER_HEADER_LENGTH = 14 // you should pass this object to the yggdrasil.SetRouterAdapter() function // before calling yggdrasil.Start(). type TunAdapter struct { - config *config.NodeState - log *log.Logger - reconfigure chan chan error - listener *yggdrasil.Listener - dialer *yggdrasil.Dialer - addr address.Address - subnet address.Subnet - icmpv6 ICMPv6 - mtu int - iface *water.Interface - mutex sync.RWMutex // Protects the below - conns map[crypto.NodeID]*yggdrasil.Conn - isOpen bool + config *config.NodeState + log *log.Logger + reconfigure chan chan error + listener *yggdrasil.Listener + dialer *yggdrasil.Dialer + addr address.Address + subnet address.Subnet + icmpv6 ICMPv6 + mtu int + iface *water.Interface + mutex sync.RWMutex // Protects the below + addrToConn map[address.Address]*yggdrasil.Conn + subnetToConn map[address.Subnet]*yggdrasil.Conn + isOpen bool } // Gets the maximum supported MTU for the platform based on the defaults in @@ -102,7 +103,8 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.log = log tun.listener = listener tun.dialer = dialer - tun.conns = make(map[crypto.NodeID]*yggdrasil.Conn) + tun.addrToConn = make(map[address.Address]*yggdrasil.Conn) + tun.subnetToConn = make(map[address.Subnet]*yggdrasil.Conn) } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -181,23 +183,40 @@ func (tun *TunAdapter) handler() error { func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { remoteNodeID := conn.RemoteAddr() - tun.mutex.Lock() - if _, isIn := tun.conns[remoteNodeID]; isIn { - tun.mutex.Unlock() - return errors.New("duplicate connection") + remoteAddr := address.AddrForNodeID(&remoteNodeID) + remoteSubnet := address.SubnetForNodeID(&remoteNodeID) + err := func() error { + tun.mutex.RLock() + defer tun.mutex.RUnlock() + if _, isIn := tun.addrToConn[*remoteAddr]; isIn { + return errors.New("duplicate connection for address " + net.IP(remoteAddr[:]).String()) + } + if _, isIn := tun.subnetToConn[*remoteSubnet]; isIn { + return errors.New("duplicate connection for subnet " + net.IP(remoteSubnet[:]).String()) + } + return nil + }() + if err != nil { + //return err + panic(err) } - tun.conns[remoteNodeID] = conn + // Store the connection mapped to address and subnet + tun.mutex.Lock() + tun.addrToConn[*remoteAddr] = conn + tun.subnetToConn[*remoteSubnet] = conn tun.mutex.Unlock() + // Make sure to clean those up later when the connection is closed defer func() { tun.mutex.Lock() - delete(tun.conns, remoteNodeID) + delete(tun.addrToConn, *remoteAddr) + delete(tun.subnetToConn, *remoteSubnet) tun.mutex.Unlock() }() b := make([]byte, 65535) for { n, err := conn.Read(b) if err != nil { - tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) + //tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) continue } if n == 0 { @@ -261,21 +280,28 @@ func (tun *TunAdapter) ifaceReader() error { // For now don't deal with any non-Yggdrasil ranges continue } - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() - // Do we have an active connection for this node ID? + // Do we have an active connection for this node address? tun.mutex.RLock() - conn, isIn := tun.conns[*dstNodeID] + conn, isIn := tun.addrToConn[dstAddr] + if !isIn || conn == nil { + conn, isIn = tun.subnetToConn[dstSnet] + if !isIn || conn == nil { + // Neither an address nor a subnet mapping matched, therefore populate + // the node ID and mask to commence a search + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + } + } tun.mutex.RUnlock() // If we don't have a connection then we should open one - if !isIn { + if !isIn || conn == nil { + // Check we haven't been given empty node ID, really this shouldn't ever + // happen but just to be sure... + if dstNodeID == nil || dstNodeIDMask == nil { + panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") + } // Dial to the remote node if c, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - // We've been given a connection, so save it in our connections so we - // can refer to it the next time we send a packet to this destination - tun.mutex.Lock() - tun.conns[*dstNodeID] = &c - tun.mutex.Unlock() - // Start the connection reader goroutine + // We've been given a connection so start the connection reader goroutine go tun.connReader(&c) // Then update our reference to the connection conn, isIn = &c, true @@ -285,9 +311,10 @@ func (tun *TunAdapter) ifaceReader() error { continue } } - // If we have an open connection, either because we already had one or - // because we opened one above, try writing the packet to it - if isIn && conn != nil { + // If we have a connection now, try writing to it + if conn != nil { + // If we have an open connection, either because we already had one or + // because we opened one above, try writing the packet to it w, err := conn.Write(bs[:n]) if err != nil { tun.log.Errorln(conn.String(), "TUN/TAP conn write error:", err) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 0accf163..903152d2 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -20,7 +20,6 @@ type Conn struct { session *sessionInfo readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer - expired atomic.Value // bool searching atomic.Value // bool } @@ -30,39 +29,58 @@ func (c *Conn) String() string { // This method should only be called from the router goroutine func (c *Conn) startSearch() { + // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { + // Update the connection with the fact that the search completed, which + // allows another search to be triggered if necessary c.searching.Store(false) - c.mutex.Lock() - defer c.mutex.Unlock() + // If the search failed for some reason, e.g. it hit a dead end or timed + // out, then do nothing if err != nil { c.core.log.Debugln(c.String(), "DHT search failed:", err) - c.expired.Store(true) return } + // Take the connection mutex + c.mutex.Lock() + defer c.mutex.Unlock() + // Were we successfully given a sessionInfo pointeR? if sinfo != nil { + // Store it, and update the nodeID and nodeMask (which may have been + // wildcarded before now) with their complete counterparts c.core.log.Debugln(c.String(), "DHT search completed") c.session = sinfo - c.nodeID, c.nodeMask = sinfo.theirAddr.GetNodeIDandMask() - c.expired.Store(false) + c.nodeID = crypto.GetNodeID(&sinfo.theirPermPub) + for i := range c.nodeMask { + c.nodeMask[i] = 0xFF + } } else { - c.core.log.Debugln(c.String(), "DHT search failed: no session returned") - c.expired.Store(true) - return + // No session was returned - this shouldn't really happen because we + // should always return an error reason if we don't return a session + panic("DHT search didn't return an error or a sessionInfo") } } + // doSearch will be called below in response to one or more conditions doSearch := func() { + // Store the fact that we're searching, so that we don't start additional + // searches until this one has completed c.searching.Store(true) + // Check to see if there is a search already matching the destination sinfo, isIn := c.core.searches.searches[*c.nodeID] if !isIn { + // Nothing was found, so create a new search sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) } + // Continue the search c.core.searches.continueSearch(sinfo) } + // Take a copy of the session object, in case it changes later c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() if c.session == nil { + // No session object is present so previous searches, if we ran any, have + // not yielded a useful result (dead end, remote host not found) doSearch() } else { sinfo.worker <- func() { @@ -83,10 +101,6 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { - // If the session is marked as expired then do nothing at this point - if e, ok := c.expired.Load().(bool); ok && e { - return 0, errors.New("session is closed") - } // Take a copy of the session object c.mutex.RLock() sinfo := c.session @@ -95,17 +109,15 @@ func (c *Conn) Read(b []byte) (int, error) { // in a write, we would trigger a new session, but it doesn't make sense for // us to block forever here if the session will not reopen. // TODO: should this return an error or just a zero-length buffer? - if !sinfo.init { + if sinfo == nil || !sinfo.init { return 0, errors.New("session is closed") } // Wait for some traffic to come through from the session select { // TODO... case p, ok := <-c.recv: - // If the channel was closed then mark the connection as expired, this will - // mean that the next write will start a new search and reopen the session + // If the session is closed then do nothing if !ok { - c.expired.Store(true) return 0, errors.New("session is closed") } defer util.PutBytes(p.Payload) @@ -155,13 +167,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - // Check whether the connection is expired, if it is we can start a new - // search to revive it - expired, eok := c.expired.Load().(bool) // If the session doesn't exist, or isn't initialised (which probably means - // that the session was never set up or it closed by timeout), or the conn - // is marked as expired, then see if we can start a new search - if sinfo == nil || !sinfo.init || (eok && expired) { + // that the search didn't complete successfully) then try to search again + if sinfo == nil || !sinfo.init { // Is a search already taking place? if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) { // No search was already taking place so start a new one @@ -173,7 +181,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // A search is already taking place so wait for it to finish return 0, errors.New("waiting for search to complete") } - //defer util.PutBytes(b) + // defer util.PutBytes(b) var packet []byte // Hand over to the session worker sinfo.doWorker(func() { @@ -197,11 +205,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } func (c *Conn) Close() error { - // Mark the connection as expired, so that a future read attempt will fail - // and a future write attempt will start a new search - c.expired.Store(true) // Close the session, if it hasn't been closed already c.session.close() + c.session = nil // This can't fail yet - TODO? return nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index e356f612..967d5f6b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -107,15 +107,6 @@ func (s *sessionInfo) update(p *sessionPing) bool { return true } -// Returns true if the session has been idle for longer than the allowed timeout. -func (s *sessionInfo) timedout() bool { - var timedout bool - s.doWorker(func() { - timedout = time.Since(s.time) > time.Minute - }) - return timedout -} - // Struct of all active sessions. // Sessions are indexed by handle. // Additionally, stores maps of address/subnet onto keys, and keys onto handles. @@ -233,10 +224,6 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b // Gets the session corresponding to a given handle. 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 - return nil, false - } return sinfo, isIn } @@ -280,8 +267,9 @@ func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) return sinfo, isIn } -// 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). +// Creates a new session and lazily cleans up old existing sessions. This +// includse initializing session info to sane defaults (e.g. lowest supported +// MTU). func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { if !ss.isSessionAllowed(theirPermKey, true) { return nil @@ -341,11 +329,6 @@ func (ss *sessions) cleanup() { if time.Since(ss.lastCleanup) < time.Minute { return } - for _, s := range ss.sinfos { - if s.timedout() { - s.close() - } - } permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared)) for k, v := range ss.permShared { permShared[k] = v @@ -387,7 +370,6 @@ func (sinfo *sessionInfo) close() { delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr) delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet) close(sinfo.worker) - sinfo.init = false } // Returns a session ping appropriate for the given session info. @@ -465,17 +447,16 @@ func (ss *sessions) handlePing(ping *sessionPing) { return } } - if !isIn || sinfo.timedout() { - if isIn { - sinfo.close() - } + if !isIn { ss.createSession(&ping.SendPermPub) sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub) if !isIn { panic("This should not happen") } ss.listenerMutex.Lock() - if ss.listener != nil { + // Check and see if there's a Listener waiting to accept connections + // TODO: this should not block if nothing is accepting + if !ping.IsPong && ss.listener != nil { conn := &Conn{ core: ss.core, session: sinfo, @@ -488,8 +469,6 @@ func (ss *sessions) handlePing(ping *sessionPing) { conn.nodeMask[i] = 0xFF } ss.listener.conn <- conn - } else { - ss.core.log.Debugln("Received new session but there is no listener, ignoring") } ss.listenerMutex.Unlock() } From 6e528799e9fa6e9c8f547f71a2d1450a6faa0650 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 22:38:37 +0100 Subject: [PATCH 29/60] Conn Read/Write operations will block while search completes --- src/tuntap/tun.go | 2 +- src/yggdrasil/conn.go | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index c65c15e8..36e29658 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -216,7 +216,7 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { for { n, err := conn.Read(b) if err != nil { - //tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) + tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) continue } if n == 0 { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 903152d2..a5702d33 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -21,6 +21,7 @@ type Conn struct { readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer searching atomic.Value // bool + searchwait chan interface{} } func (c *Conn) String() string { @@ -31,6 +32,8 @@ func (c *Conn) String() string { func (c *Conn) startSearch() { // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { + // Make sure that any blocks on read/write operations are lifted + defer close(c.searchwait) // Update the connection with the fact that the search completed, which // allows another search to be triggered if necessary c.searching.Store(false) @@ -70,6 +73,8 @@ func (c *Conn) startSearch() { // Nothing was found, so create a new search sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) + // Allow writes/reads to block until the search is complete + c.searchwait = make(chan interface{}) } // Continue the search c.core.searches.continueSearch(sinfo) @@ -110,12 +115,16 @@ func (c *Conn) Read(b []byte) (int, error) { // us to block forever here if the session will not reopen. // TODO: should this return an error or just a zero-length buffer? if sinfo == nil || !sinfo.init { - return 0, errors.New("session is closed") + // block + <-c.searchwait + // return 0, errors.New("session is closed") } // Wait for some traffic to come through from the session + fmt.Println("Start select") select { // TODO... case p, ok := <-c.recv: + fmt.Println("Finish select") // If the session is closed then do nothing if !ok { return 0, errors.New("session is closed") @@ -167,6 +176,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() + // A search is already taking place so wait for it to finish + if sinfo == nil || !sinfo.init { + } // If the session doesn't exist, or isn't initialised (which probably means // that the search didn't complete successfully) then try to search again if sinfo == nil || !sinfo.init { @@ -176,10 +188,13 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.core.router.doAdmin(func() { c.startSearch() }) - return 0, errors.New("starting search") + //return 0, errors.New("starting search") } - // A search is already taking place so wait for it to finish - return 0, errors.New("waiting for search to complete") + <-c.searchwait + if sinfo == nil || !sinfo.init { + return 0, errors.New("search was failed") + } + //return 0, errors.New("waiting for search to complete") } // defer util.PutBytes(b) var packet []byte From e1a2d666bf0176da43e47c76d49892cb89f9081e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 23:12:13 +0100 Subject: [PATCH 30/60] Clean up router, tweaks --- src/tuntap/tun.go | 14 ++++-- src/yggdrasil/conn.go | 18 ++++--- src/yggdrasil/router.go | 103 +++------------------------------------- 3 files changed, 28 insertions(+), 107 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 36e29658..709f7051 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -2,6 +2,12 @@ package tuntap // This manages the tun driver to send/recv packets to/from applications +// TODO: Crypto-key routing +// TODO: Set MTU of session properly +// TODO: Reject packets that exceed session MTU +// TODO: Connection timeouts (call Close() when done) +// TODO: Keep packet that was used to set up a session and send it when done + import ( "encoding/hex" "errors" @@ -38,9 +44,9 @@ type TunAdapter struct { icmpv6 ICMPv6 mtu int iface *water.Interface - mutex sync.RWMutex // Protects the below - addrToConn map[address.Address]*yggdrasil.Conn - subnetToConn map[address.Subnet]*yggdrasil.Conn + mutex sync.RWMutex // Protects the below + addrToConn map[address.Address]*yggdrasil.Conn // Managed by connReader + subnetToConn map[address.Subnet]*yggdrasil.Conn // Managed by connReader isOpen bool } @@ -312,7 +318,7 @@ func (tun *TunAdapter) ifaceReader() error { } } // If we have a connection now, try writing to it - if conn != nil { + if isIn && conn != nil { // If we have an open connection, either because we already had one or // because we opened one above, try writing the packet to it w, err := conn.Write(bs[:n]) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index a5702d33..4ffa4b17 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -110,21 +110,24 @@ func (c *Conn) Read(b []byte) (int, error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() + // If there is a search in progress then wait for the result + if searching, ok := c.searching.Load().(bool); ok && searching { + <-c.searchwait + } // If the session is not initialised, do nothing. Currently in this instance // in a write, we would trigger a new session, but it doesn't make sense for // us to block forever here if the session will not reopen. // TODO: should this return an error or just a zero-length buffer? if sinfo == nil || !sinfo.init { - // block - <-c.searchwait - // return 0, errors.New("session is closed") + time.Sleep(time.Second) + return 0, errors.New("session is closed") } // Wait for some traffic to come through from the session - fmt.Println("Start select") + fmt.Println(c.String(), "Start select") select { // TODO... case p, ok := <-c.recv: - fmt.Println("Finish select") + fmt.Println(c.String(), "Finish select") // If the session is closed then do nothing if !ok { return 0, errors.New("session is closed") @@ -176,8 +179,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - // A search is already taking place so wait for it to finish - if sinfo == nil || !sinfo.init { + // If there is a search in progress then wait for the result + if searching, ok := c.searching.Load().(bool); ok && searching { + <-c.searchwait } // If the session doesn't exist, or isn't initialised (which probably means // that the search didn't complete successfully) then try to search again diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 348a1ed6..68700723 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -24,6 +24,7 @@ package yggdrasil import ( //"bytes" + "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -38,43 +39,12 @@ type router struct { reconfigure chan chan error 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() - recv chan<- []byte // place where the adapter pulls received packets from - //send <-chan []byte // place where the adapter puts outgoing packets - reject chan<- RejectedPacket // place where we send error packets back to adapter - reset chan struct{} // signal that coords changed (re-init sessions/dht) - admin chan func() // pass a lambda for the admin socket to query stuff - cryptokey cryptokey - nodeinfo nodeinfo -} - -// Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the adapter. -type router_recvPacket struct { - bs []byte - sinfo *sessionInfo -} - -// RejectedPacketReason is the type code used to represent the reason that a -// packet was rejected. -type RejectedPacketReason int - -const ( - // The router rejected the packet because it exceeds the session MTU for the - // given destination. In TUN/TAP, this results in the generation of an ICMPv6 - // Packet Too Big message. - PacketTooBig = 1 + iota -) - -// RejectedPacket represents a rejected packet from the router. This is passed -// back to the adapter so that the adapter can respond appropriately, e.g. in -// the case of TUN/TAP, a "PacketTooBig" reason can be used to generate an -// ICMPv6 Packet Too Big response. -type RejectedPacket struct { - Reason RejectedPacketReason - Packet []byte - Detail interface{} + 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" + reset chan struct{} // signal that coords changed (re-init sessions/dht) + admin chan func() // pass a lambda for the admin socket to query stuff + cryptokey cryptokey + nodeinfo nodeinfo } // Initializes the router struct, which includes setting up channels to/from the adapter. @@ -121,13 +91,6 @@ func (r *router) init(core *Core) { } }() r.out = func(packet []byte) { out2 <- packet } - r.toRecv = make(chan router_recvPacket, 32) - //recv := make(chan []byte, 32) - //send := make(chan []byte, 32) - reject := make(chan RejectedPacket, 32) - //r.recv = recv - //r.send = send - r.reject = reject r.reset = make(chan struct{}, 1) r.admin = make(chan func(), 32) r.nodeinfo.init(r.core) @@ -153,12 +116,8 @@ func (r *router) mainLoop() { defer ticker.Stop() for { select { - case rp := <-r.toRecv: - r.recvPacket(rp.bs, rp.sinfo) case p := <-r.in: r.handleIn(p) - //case p := <-r.send: - // r.sendPacket(p) case info := <-r.core.dht.peers: r.core.dht.insertPeer(info) case <-r.reset: @@ -356,54 +315,6 @@ func (r *router) sendPacket(bs []byte) { } */ -// Called for incoming traffic by the session worker for that connection. -// Checks that the IP address is correct (matches the session) and passes the packet to the adapter. -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) - return - } - var sourceAddr address.Address - var dest address.Address - var snet address.Subnet - var addrlen int - if bs[0]&0xf0 == 0x60 { - // IPv6 address - addrlen = 16 - copy(sourceAddr[:addrlen], bs[8:]) - copy(dest[:addrlen], bs[24:]) - copy(snet[:addrlen/2], bs[8:]) - } else if bs[0]&0xf0 == 0x40 { - // IPv4 address - addrlen = 4 - copy(sourceAddr[:addrlen], bs[12:]) - copy(dest[:addrlen], bs[16:]) - } else { - // Unknown address length - return - } - // 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) - 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: - default: - key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen) - if err != nil || key != sinfo.theirPermPub { - util.PutBytes(bs) - return - } - } - //go func() { r.recv<-bs }() - r.recv <- bs -} - // Checks incoming traffic type and passes it to the appropriate handler. func (r *router) handleIn(packet []byte) { pType, pTypeLen := wire_decode_uint64(packet) From d7a1c0474842c447b17d8c5a1bb8a07760d49a93 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 23:58:59 +0100 Subject: [PATCH 31/60] It works, sort of, amazingly --- src/yggdrasil/conn.go | 65 +++++++++++++++++++--------------------- src/yggdrasil/dialer.go | 10 +++---- src/yggdrasil/session.go | 12 ++++---- 3 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 4ffa4b17..8d36a60a 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -15,7 +15,6 @@ type Conn struct { core *Core nodeID *crypto.NodeID nodeMask *crypto.NodeID - recv chan *wire_trafficPacket // Eventually gets attached to session.recv mutex *sync.RWMutex session *sessionInfo readDeadline atomic.Value // time.Time // TODO timer @@ -33,10 +32,11 @@ func (c *Conn) startSearch() { // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { // Make sure that any blocks on read/write operations are lifted - defer close(c.searchwait) - // Update the connection with the fact that the search completed, which - // allows another search to be triggered if necessary - c.searching.Store(false) + defer func() { + c.searching.Store(false) + close(c.searchwait) + c.searchwait = make(chan interface{}) + }() // If the search failed for some reason, e.g. it hit a dead end or timed // out, then do nothing if err != nil { @@ -64,8 +64,6 @@ func (c *Conn) startSearch() { } // doSearch will be called below in response to one or more conditions doSearch := func() { - // Store the fact that we're searching, so that we don't start additional - // searches until this one has completed c.searching.Store(true) // Check to see if there is a search already matching the destination sinfo, isIn := c.core.searches.searches[*c.nodeID] @@ -73,8 +71,6 @@ func (c *Conn) startSearch() { // Nothing was found, so create a new search sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) - // Allow writes/reads to block until the search is complete - c.searchwait = make(chan interface{}) } // Continue the search c.core.searches.continueSearch(sinfo) @@ -111,23 +107,23 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo := c.session c.mutex.RUnlock() // If there is a search in progress then wait for the result - if searching, ok := c.searching.Load().(bool); ok && searching { + if sinfo == nil { + // Wait for the search to complete <-c.searchwait - } - // If the session is not initialised, do nothing. Currently in this instance - // in a write, we would trigger a new session, but it doesn't make sense for - // us to block forever here if the session will not reopen. - // TODO: should this return an error or just a zero-length buffer? - if sinfo == nil || !sinfo.init { - time.Sleep(time.Second) - return 0, errors.New("session is closed") + // Retrieve our session info again + c.mutex.RLock() + sinfo = c.session + c.mutex.RUnlock() + // If sinfo is still nil at this point then the search failed and the + // searchwait channel has been recreated, so might as well give up and + // return an error code + if sinfo == nil { + return 0, errors.New("search failed") + } } // Wait for some traffic to come through from the session - fmt.Println(c.String(), "Start select") select { - // TODO... - case p, ok := <-c.recv: - fmt.Println(c.String(), "Finish select") + case p, ok := <-sinfo.recv: // If the session is closed then do nothing if !ok { return 0, errors.New("session is closed") @@ -168,10 +164,6 @@ func (c *Conn) Read(b []byte) (int, error) { // If we've reached this point then everything went to plan, return the // number of bytes we populated back into the given slice return len(b), nil - //case <-c.recvTimeout: - //case <-c.session.closed: - // c.expired = true - // return len(b), errors.New("session is closed") } } @@ -179,12 +171,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - // If there is a search in progress then wait for the result - if searching, ok := c.searching.Load().(bool); ok && searching { - <-c.searchwait - } // If the session doesn't exist, or isn't initialised (which probably means - // that the search didn't complete successfully) then try to search again + // that the search didn't complete successfully) then we may need to wait for + // the search to complete or start the search again if sinfo == nil || !sinfo.init { // Is a search already taking place? if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) { @@ -192,13 +181,19 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.core.router.doAdmin(func() { c.startSearch() }) - //return 0, errors.New("starting search") } + // Wait for the search to complete <-c.searchwait - if sinfo == nil || !sinfo.init { - return 0, errors.New("search was failed") + // Retrieve our session info again + c.mutex.RLock() + sinfo = c.session + c.mutex.RUnlock() + // If sinfo is still nil at this point then the search failed and the + // searchwait channel has been recreated, so might as well give up and + // return an error code + if sinfo == nil { + return 0, errors.New("search failed") } - //return 0, errors.New("waiting for search to complete") } // defer util.PutBytes(b) var packet []byte diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 89016100..325c6b76 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -59,11 +59,11 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { // NodeID parameters. func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, error) { conn := Conn{ - core: d.core, - mutex: &sync.RWMutex{}, - nodeID: nodeID, - nodeMask: nodeMask, - recv: make(chan *wire_trafficPacket, 32), + core: d.core, + mutex: &sync.RWMutex{}, + nodeID: nodeID, + nodeMask: nodeMask, + searchwait: make(chan interface{}), } return conn, nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 967d5f6b..44c83874 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -458,12 +458,12 @@ func (ss *sessions) handlePing(ping *sessionPing) { // TODO: this should not block if nothing is accepting if !ping.IsPong && ss.listener != nil { conn := &Conn{ - core: ss.core, - session: sinfo, - mutex: &sync.RWMutex{}, - nodeID: crypto.GetNodeID(&sinfo.theirPermPub), - nodeMask: &crypto.NodeID{}, - recv: sinfo.recv, + core: ss.core, + session: sinfo, + mutex: &sync.RWMutex{}, + nodeID: crypto.GetNodeID(&sinfo.theirPermPub), + nodeMask: &crypto.NodeID{}, + searchwait: make(chan interface{}), } for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF From 2bee3cd7cac554a0b74de3e798f983cd716468e5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 23 Apr 2019 00:04:22 +0100 Subject: [PATCH 32/60] Update TODOs at top of tun.go --- src/tuntap/tun.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 709f7051..58eca27a 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -2,11 +2,11 @@ package tuntap // This manages the tun driver to send/recv packets to/from applications -// TODO: Crypto-key routing +// TODO: Crypto-key routing support // TODO: Set MTU of session properly -// TODO: Reject packets that exceed session MTU -// TODO: Connection timeouts (call Close() when done) -// TODO: Keep packet that was used to set up a session and send it when done +// TODO: Reject packets that exceed session MTU with ICMPv6 for PMTU Discovery +// TODO: Connection timeouts (call Conn.Close() when we want to time out) +// TODO: Don't block in ifaceReader on writes that are pending searches import ( "encoding/hex" From 870b2b6a2ed09ad7d3509c6b3cc22d05c2b24d18 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 23 Apr 2019 10:28:40 +0100 Subject: [PATCH 33/60] Remove CKR from src/yggdrasil (it will be moved into tuntap) --- src/yggdrasil/admin.go | 4 +- src/yggdrasil/ckr.go | 414 ---------------------------------------- src/yggdrasil/core.go | 1 - src/yggdrasil/router.go | 2 - 4 files changed, 2 insertions(+), 419 deletions(-) delete mode 100644 src/yggdrasil/ckr.go diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 0f91cd15..a24a5852 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -249,7 +249,7 @@ func (a *admin) init(c *Core) { }, errors.New("Failed to remove allowed key") } }) - a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) { + /*a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) { enabled := false a.core.router.doAdmin(func() { enabled = a.core.router.cryptokey.isEnabled() @@ -335,7 +335,7 @@ func (a *admin) init(c *Core) { } else { return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") } - }) + })*/ a.addHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in admin_info) (admin_info, error) { if in["target"] == nil { in["target"] = "none" diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go deleted file mode 100644 index dc4f1b90..00000000 --- a/src/yggdrasil/ckr.go +++ /dev/null @@ -1,414 +0,0 @@ -package yggdrasil - -import ( - "bytes" - "encoding/hex" - "errors" - "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 -// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. - -type cryptokey struct { - core *Core - enabled bool - reconfigure chan chan error - ipv4routes []cryptokey_route - ipv6routes []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 crypto.BoxPubKey -} - -// Initialise crypto-key routing. This must be done before any other CKR calls. -func (c *cryptokey) init(core *Core) { - c.core = core - c.reconfigure = make(chan chan error, 1) - go func() { - for { - e := <-c.reconfigure - var err error - c.core.router.doAdmin(func() { - err = c.core.router.cryptokey.configure() - }) - e <- err - } - }() - - if err := c.configure(); err != nil { - c.core.log.Errorln("CKR configuration failed:", err) - } -} - -// Configure the CKR routes - this must only ever be called from the router -// goroutine, e.g. through router.doAdmin -func (c *cryptokey) configure() error { - current, _ := c.core.config.Get() - - // Set enabled/disabled state - c.setEnabled(current.TunnelRouting.Enable) - - // Clear out existing routes - c.ipv6routes = make([]cryptokey_route, 0) - c.ipv4routes = make([]cryptokey_route, 0) - - // Add IPv6 routes - for ipv6, pubkey := range current.TunnelRouting.IPv6Destinations { - if err := c.addRoute(ipv6, pubkey); err != nil { - return err - } - } - - // Add IPv4 routes - for ipv4, pubkey := range current.TunnelRouting.IPv4Destinations { - if err := c.addRoute(ipv4, pubkey); err != nil { - return err - } - } - - // Clear out existing sources - c.ipv6sources = make([]net.IPNet, 0) - c.ipv4sources = make([]net.IPNet, 0) - - // Add IPv6 sources - c.ipv6sources = make([]net.IPNet, 0) - for _, source := range current.TunnelRouting.IPv6Sources { - if err := c.addSourceSubnet(source); err != nil { - return err - } - } - - // Add IPv4 sources - c.ipv4sources = make([]net.IPNet, 0) - for _, source := range current.TunnelRouting.IPv4Sources { - if err := c.addSourceSubnet(source); err != nil { - return err - } - } - - // Wipe the caches - c.ipv4cache = make(map[address.Address]cryptokey_route, 0) - c.ipv6cache = make(map[address.Address]cryptokey_route, 0) - - return nil -} - -// Enable or disable crypto-key routing. -func (c *cryptokey) setEnabled(enabled bool) { - c.enabled = enabled -} - -// Check if crypto-key routing is enabled. -func (c *cryptokey) isEnabled() bool { - return c.enabled -} - -// 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.Address, addrlen int) bool { - ip := net.IP(addr[:addrlen]) - - if addrlen == net.IPv6len { - // Does this match our node's address? - if bytes.Equal(addr[:16], c.core.router.addr[:16]) { - return true - } - - // Does this match our node's subnet? - if bytes.Equal(addr[:8], c.core.router.subnet[:8]) { - return true - } - } - - // Does it match a configured CKR source? - if c.isEnabled() { - // Build our references to the routing sources - var routingsources *[]net.IPNet - - // Check if the prefix is IPv4 or IPv6 - if addrlen == net.IPv6len { - routingsources = &c.ipv6sources - } else if addrlen == net.IPv4len { - routingsources = &c.ipv4sources - } else { - return false - } - - for _, subnet := range *routingsources { - if subnet.Contains(ip) { - return true - } - } - } - - // Doesn't match any of the above - return false -} - -// Adds a source subnet, which allows traffic with these source addresses to -// be tunnelled using crypto-key routing. -func (c *cryptokey) addSourceSubnet(cidr string) error { - // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing sources - var routingsources *[]net.IPNet - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingsources = &c.ipv6sources - } else if prefixsize == net.IPv4len*8 { - routingsources = &c.ipv4sources - } else { - return errors.New("Unexpected prefix size") - } - - // Check if we already have this CIDR - for _, subnet := range *routingsources { - if subnet.String() == ipnet.String() { - return errors.New("Source subnet already configured") - } - } - - // Add the source subnet - *routingsources = append(*routingsources, *ipnet) - c.core.log.Infoln("Added CKR source subnet", cidr) - return nil -} - -// Adds a destination route for the given CIDR to be tunnelled to the node -// with the given BoxPubKey. -func (c *cryptokey) addRoute(cidr string, dest string) error { - // Is the CIDR we've been given valid? - ipaddr, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing table and cache - var routingtable *[]cryptokey_route - var routingcache *map[address.Address]cryptokey_route - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingtable = &c.ipv6routes - routingcache = &c.ipv6cache - } else if prefixsize == net.IPv4len*8 { - routingtable = &c.ipv4routes - routingcache = &c.ipv4cache - } else { - return errors.New("Unexpected prefix size") - } - - // Is the route an Yggdrasil destination? - var addr address.Address - var snet address.Subnet - copy(addr[:], ipaddr) - copy(snet[:], ipnet.IP) - 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? - for _, route := range *routingtable { - if route.subnet.String() == ipnet.String() { - return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) - } - } - // Decode the public key - if bpk, err := hex.DecodeString(dest); err != nil { - return err - } 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 crypto.BoxPubKey - copy(key[:], bpk) - *routingtable = append(*routingtable, cryptokey_route{ - subnet: *ipnet, - destination: key, - }) - - // Sort so most specific routes are first - sort.Slice(*routingtable, func(i, j int) bool { - im, _ := (*routingtable)[i].subnet.Mask.Size() - jm, _ := (*routingtable)[j].subnet.Mask.Size() - return im > jm - }) - - // Clear the cache as this route might change future routing - // Setting an empty slice keeps the memory whereas nil invokes GC - for k := range *routingcache { - delete(*routingcache, k) - } - - c.core.log.Infoln("Added CKR destination subnet", cidr) - return nil - } -} - -// 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.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 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.Address]cryptokey_route - - // Check if the prefix is IPv4 or IPv6 - if addrlen == net.IPv6len { - routingtable = &c.ipv6routes - routingcache = &c.ipv6cache - } else if addrlen == net.IPv4len { - routingtable = &c.ipv4routes - routingcache = &c.ipv4cache - } else { - return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") - } - - // Check if there's a cache entry for this addr - if route, ok := (*routingcache)[addr]; ok { - return route.destination, nil - } - - // No cache was found - start by converting the address into a net.IP - ip := make(net.IP, addrlen) - copy(ip[:addrlen], addr[:]) - - // Check if we have a route. At this point c.ipv6routes should be - // pre-sorted so that the most specific routes are first - for _, route := range *routingtable { - // Does this subnet match the given IP? - if route.subnet.Contains(ip) { - // Check if the routing cache is above a certain size, if it is evict - // a random entry so we can make room for this one. We take advantage - // of the fact that the iteration order is random here - for k := range *routingcache { - if len(*routingcache) < 1024 { - break - } - delete(*routingcache, k) - } - - // Cache the entry for future packets to get a faster lookup - (*routingcache)[addr] = route - - // Return the boxPubKey - return route.destination, nil - } - } - - // No route was found if we got to this point - 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 -// be tunnelled using crypto-key routing. -func (c *cryptokey) removeSourceSubnet(cidr string) error { - // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing sources - var routingsources *[]net.IPNet - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingsources = &c.ipv6sources - } else if prefixsize == net.IPv4len*8 { - routingsources = &c.ipv4sources - } else { - return errors.New("Unexpected prefix size") - } - - // Check if we already have this CIDR - for idx, subnet := range *routingsources { - if subnet.String() == ipnet.String() { - *routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...) - c.core.log.Infoln("Removed CKR source subnet", cidr) - return nil - } - } - return errors.New("Source subnet not found") -} - -// Removes a destination route for the given CIDR to be tunnelled to the node -// with the given BoxPubKey. -func (c *cryptokey) removeRoute(cidr string, dest string) error { - // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing table and cache - var routingtable *[]cryptokey_route - var routingcache *map[address.Address]cryptokey_route - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingtable = &c.ipv6routes - routingcache = &c.ipv6cache - } else if prefixsize == net.IPv4len*8 { - routingtable = &c.ipv4routes - routingcache = &c.ipv4cache - } else { - return errors.New("Unexpected prefix size") - } - - // Decode the public key - bpk, err := hex.DecodeString(dest) - if err != nil { - return err - } else if len(bpk) != crypto.BoxPubKeyLen { - return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) - } - netStr := ipnet.String() - - for idx, route := range *routingtable { - if bytes.Equal(route.destination[:], bpk) && route.subnet.String() == netStr { - *routingtable = append((*routingtable)[:idx], (*routingtable)[idx+1:]...) - for k := range *routingcache { - delete(*routingcache, k) - } - c.core.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest) - return nil - } - } - return errors.New(fmt.Sprintf("Route does not exists for %s", cidr)) -} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 81be1b28..ac2b494f 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -127,7 +127,6 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { c.sessions.reconfigure, c.peers.reconfigure, c.router.reconfigure, - c.router.cryptokey.reconfigure, c.switchTable.reconfigure, c.link.reconfigure, } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 68700723..cacf0259 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -43,7 +43,6 @@ type router struct { out func([]byte) // packets we're sending to the network, link to peer's "in" reset chan struct{} // signal that coords changed (re-init sessions/dht) admin chan func() // pass a lambda for the admin socket to query stuff - cryptokey cryptokey nodeinfo nodeinfo } @@ -97,7 +96,6 @@ func (r *router) init(core *Core) { r.core.config.Mutex.RLock() r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.core.config.Mutex.RUnlock() - r.cryptokey.init(r.core) } // Starts the mainLoop goroutine. From b4513ca2e843242d6d6127d8b07df4df050bf841 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 23 Apr 2019 10:43:07 +0100 Subject: [PATCH 34/60] Re-add support for TAP mode --- src/tuntap/tun.go | 104 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 58eca27a..eb06cc9b 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -9,6 +9,7 @@ package tuntap // TODO: Don't block in ifaceReader on writes that are pending searches import ( + "bytes" "encoding/hex" "errors" "fmt" @@ -17,12 +18,14 @@ import ( "time" "github.com/gologme/log" + "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/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -111,6 +114,7 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.dialer = dialer tun.addrToConn = make(map[address.Address]*yggdrasil.Conn) tun.subnetToConn = make(map[address.Subnet]*yggdrasil.Conn) + tun.icmpv6.Init(tun) } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -163,7 +167,6 @@ func (tun *TunAdapter) Start() error { } }() } - tun.icmpv6.Init(tun) go func() { for { e := <-tun.reconfigure @@ -228,7 +231,82 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { if n == 0 { continue } - w, err := tun.iface.Write(b[:n]) + var w int + if tun.iface.IsTAP() { + var dstAddr address.Address + if b[0]&0xf0 == 0x60 { + if len(b) < 40 { + //panic("Tried to sendb a packet shorter than an IPv6 header...") + util.PutBytes(b) + continue + } + copy(dstAddr[:16], b[24:]) + } else if b[0]&0xf0 == 0x40 { + if len(b) < 20 { + //panic("Tried to send a packet shorter than an IPv4 header...") + util.PutBytes(b) + continue + } + copy(dstAddr[:4], b[16:]) + } else { + return errors.New("Invalid address family") + } + sendndp := func(dstAddr address.Address) { + neigh, known := tun.icmpv6.peermacs[dstAddr] + known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) + if !known { + request, err := tun.icmpv6.CreateNDPL2(dstAddr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + tun.icmpv6.peermacs[dstAddr] = neighbor{ + lastsolicitation: time.Now(), + } + } + } + var peermac macAddress + var peerknown bool + if b[0]&0xf0 == 0x40 { + dstAddr = tun.addr + } else if b[0]&0xf0 == 0x60 { + if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { + dstAddr = tun.addr + } + } + if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + sendndp(dstAddr) + } else { + sendndp(tun.addr) + } + if peerknown { + var proto ethernet.Ethertype + switch { + case b[0]&0xf0 == 0x60: + proto = ethernet.IPv6 + case b[0]&0xf0 == 0x40: + proto = ethernet.IPv4 + } + var frame ethernet.Frame + frame.Prepare( + peermac[:6], // Destination MAC address + tun.icmpv6.mymac[:6], // Source MAC address + ethernet.NotTagged, // VLAN tagging + proto, // Ethertype + len(b)) // Payload length + copy(frame[tun_ETHER_HEADER_LENGTH:], b[:]) + w, err = tun.iface.Write(b[:n]) + } + } else { + w, err = tun.iface.Write(b[:n]) + } if err != nil { tun.log.Errorln(conn.String(), "TUN/TAP iface write error:", err) continue @@ -248,6 +326,20 @@ func (tun *TunAdapter) ifaceReader() error { if err != nil { continue } + // If it's a TAP adapter, update the buffer slice so that we no longer + // include the ethernet headers + if tun.iface.IsTAP() { + bs = bs[tun_ETHER_HEADER_LENGTH:] + } + // If we detect an ICMP packet then hand it to the ICMPv6 module + if bs[6] == 58 { + if tun.iface.IsTAP() { + // Found an ICMPv6 packet + b := make([]byte, n) + copy(b, bs) + go tun.icmpv6.ParsePacket(b) + } + } // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send // the packet @@ -264,6 +356,10 @@ func (tun *TunAdapter) ifaceReader() error { if len(bs) < 40 { continue } + // Check the packet size + if n != 256*int(bs[4])+int(bs[5])+tun_IPv6_HEADER_LENGTH { + continue + } // IPv6 address addrlen = 16 copy(srcAddr[:addrlen], bs[8:]) @@ -274,6 +370,10 @@ func (tun *TunAdapter) ifaceReader() error { if len(bs) < 20 { continue } + // Check the packet size + if bs[0]&0xf0 == 0x40 && n != 256*int(bs[2])+int(bs[3]) { + continue + } // IPv4 address addrlen = 4 copy(srcAddr[:addrlen], bs[12:]) From 2b44f5d2f6a81b33ac6e62d497e84132f5d873cf Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 23 Apr 2019 11:37:32 +0100 Subject: [PATCH 35/60] Fix TAP support --- src/tuntap/icmpv6.go | 3 +-- src/tuntap/tun.go | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index 55c3280a..8159e0f9 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -169,7 +169,6 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) if err != nil { return nil, err } - // Send it back return responsePacket, nil } else { @@ -186,7 +185,7 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) copy(addr[:], ipv6Header.Src[:]) copy(target[:], datain[48:64]) copy(mac[:], (*datamac)[:]) - // i.tun.core.log.Printf("Learning peer MAC %x for %x\n", mac, target) + // fmt.Printf("Learning peer MAC %x for %x\n", mac, target) neighbor := i.peermacs[target] neighbor.mac = mac neighbor.learned = true diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index eb06cc9b..fed6563a 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -114,7 +114,6 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.dialer = dialer tun.addrToConn = make(map[address.Address]*yggdrasil.Conn) tun.subnetToConn = make(map[address.Subnet]*yggdrasil.Conn) - tun.icmpv6.Init(tun) } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -175,6 +174,7 @@ func (tun *TunAdapter) Start() error { }() go tun.handler() go tun.ifaceReader() + tun.icmpv6.Init(tun) return nil } @@ -328,17 +328,20 @@ func (tun *TunAdapter) ifaceReader() error { } // If it's a TAP adapter, update the buffer slice so that we no longer // include the ethernet headers + offset := 0 if tun.iface.IsTAP() { - bs = bs[tun_ETHER_HEADER_LENGTH:] - } - // If we detect an ICMP packet then hand it to the ICMPv6 module - if bs[6] == 58 { - if tun.iface.IsTAP() { + // Set our offset to beyond the ethernet headers + offset = tun_ETHER_HEADER_LENGTH + // If we detect an ICMP packet then hand it to the ICMPv6 module + if bs[offset+6] == 58 { // Found an ICMPv6 packet b := make([]byte, n) copy(b, bs) go tun.icmpv6.ParsePacket(b) } + // Then offset the buffer so that we can now just treat it as an IP + // packet from now on + bs = bs[offset:] } // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send @@ -357,7 +360,7 @@ func (tun *TunAdapter) ifaceReader() error { continue } // Check the packet size - if n != 256*int(bs[4])+int(bs[5])+tun_IPv6_HEADER_LENGTH { + if n != 256*int(bs[4])+int(bs[5])+offset+tun_IPv6_HEADER_LENGTH { continue } // IPv6 address @@ -371,7 +374,7 @@ func (tun *TunAdapter) ifaceReader() error { continue } // Check the packet size - if bs[0]&0xf0 == 0x40 && n != 256*int(bs[2])+int(bs[3]) { + if n != 256*int(bs[2])+int(bs[3])+offset { continue } // IPv4 address From 75130f7735c961e33db05246c63cb1c048507345 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 23 Apr 2019 11:46:16 +0100 Subject: [PATCH 36/60] Fix TAP support again --- src/tuntap/tun.go | 7 ++++--- src/yggdrasil/session.go | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index fed6563a..01cafaa4 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -236,7 +236,7 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { var dstAddr address.Address if b[0]&0xf0 == 0x60 { if len(b) < 40 { - //panic("Tried to sendb a packet shorter than an IPv6 header...") + //panic("Tried to send a packet shorter than an IPv6 header...") util.PutBytes(b) continue } @@ -301,8 +301,9 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { ethernet.NotTagged, // VLAN tagging proto, // Ethertype len(b)) // Payload length - copy(frame[tun_ETHER_HEADER_LENGTH:], b[:]) - w, err = tun.iface.Write(b[:n]) + copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) + n += tun_ETHER_HEADER_LENGTH + w, err = tun.iface.Write(frame[:n]) } } else { w, err = tun.iface.Write(b[:n]) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 44c83874..bf35e8c3 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -8,7 +8,6 @@ import ( "bytes" "encoding/hex" "sync" - "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -78,7 +77,7 @@ type sessionPing struct { // 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 { - if !(p.Tstamp > atomic.LoadInt64(&s.tstamp)) { + if !(p.Tstamp > s.tstamp) { // To protect against replay attacks return false } From 0059baf36c9202f6327f4434f58825171b21ec90 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Apr 2019 18:07:57 -0500 Subject: [PATCH 37/60] add a newConn function that returns a pointer to a Conn with atomics properly initialized --- src/tuntap/tun.go | 4 ++-- src/yggdrasil/conn.go | 24 ++++++++++++++++++++---- src/yggdrasil/dialer.go | 21 +++++++-------------- src/yggdrasil/session.go | 9 +-------- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 01cafaa4..c19764c3 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -412,9 +412,9 @@ func (tun *TunAdapter) ifaceReader() error { // Dial to the remote node if c, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { // We've been given a connection so start the connection reader goroutine - go tun.connReader(&c) + go tun.connReader(c) // Then update our reference to the connection - conn, isIn = &c, true + conn, isIn = c, true } else { // We weren't able to dial for some reason so there's no point in // continuing this iteration - skip to the next one diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 8d36a60a..08591c4a 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -15,12 +15,26 @@ type Conn struct { core *Core nodeID *crypto.NodeID nodeMask *crypto.NodeID - mutex *sync.RWMutex + mutex sync.RWMutex session *sessionInfo readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer searching atomic.Value // bool - searchwait chan interface{} + searchwait chan struct{} +} + +// TODO func NewConn() that initializes atomic and channel fields so things don't crash or block indefinitely +func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn { + conn := Conn{ + core: core, + nodeID: nodeID, + nodeMask: nodeMask, + session: session, + searchwait: make(chan struct{}), + } + conn.SetDeadline(time.Time{}) + conn.searching.Store(false) + return &conn } func (c *Conn) String() string { @@ -33,9 +47,9 @@ func (c *Conn) startSearch() { searchCompleted := func(sinfo *sessionInfo, err error) { // Make sure that any blocks on read/write operations are lifted defer func() { + defer func() { recover() }() // In case searchwait was closed by another goroutine c.searching.Store(false) - close(c.searchwait) - c.searchwait = make(chan interface{}) + close(c.searchwait) // Never reset this to an open channel }() // If the search failed for some reason, e.g. it hit a dead end or timed // out, then do nothing @@ -106,6 +120,8 @@ func (c *Conn) Read(b []byte) (int, error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() + timer := time.NewTimer(0) + util.TimerStop(timer) // If there is a search in progress then wait for the result if sinfo == nil { // Wait for the search to complete diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 325c6b76..1943c859 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -5,7 +5,6 @@ import ( "errors" "strconv" "strings" - "sync" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) @@ -18,7 +17,7 @@ type Dialer struct { // Dial opens a session to the given node. The first paramter should be "nodeid" // and the second parameter should contain a hexadecimal representation of the // target node ID. -func (d *Dialer) Dial(network, address string) (Conn, error) { +func (d *Dialer) Dial(network, address string) (*Conn, error) { var nodeID crypto.NodeID var nodeMask crypto.NodeID // Process @@ -28,11 +27,11 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { if tokens := strings.Split(address, "/"); len(tokens) == 2 { len, err := strconv.Atoi(tokens[1]) if err != nil { - return Conn{}, err + return nil, err } dest, err := hex.DecodeString(tokens[0]) if err != nil { - return Conn{}, err + return nil, err } copy(nodeID[:], dest) for idx := 0; idx < len; idx++ { @@ -41,7 +40,7 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { } else { dest, err := hex.DecodeString(tokens[0]) if err != nil { - return Conn{}, err + return nil, err } copy(nodeID[:], dest) for i := range nodeMask { @@ -51,19 +50,13 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { return d.DialByNodeIDandMask(&nodeID, &nodeMask) default: // An unexpected address type was given, so give up - return Conn{}, errors.New("unexpected address type") + return nil, errors.New("unexpected address type") } } // DialByNodeIDandMask opens a session to the given node based on raw // NodeID parameters. -func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, error) { - conn := Conn{ - core: d.core, - mutex: &sync.RWMutex{}, - nodeID: nodeID, - nodeMask: nodeMask, - searchwait: make(chan interface{}), - } +func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) { + conn := newConn(d.core, nodeID, nodeMask, nil) return conn, nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index bf35e8c3..724152dd 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -456,14 +456,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { // Check and see if there's a Listener waiting to accept connections // TODO: this should not block if nothing is accepting if !ping.IsPong && ss.listener != nil { - conn := &Conn{ - core: ss.core, - session: sinfo, - mutex: &sync.RWMutex{}, - nodeID: crypto.GetNodeID(&sinfo.theirPermPub), - nodeMask: &crypto.NodeID{}, - searchwait: make(chan interface{}), - } + conn := newConn(ss.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo) for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } From 15051b0a3cc02f39be8ad5c000d65c19eae3e364 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Apr 2019 19:31:47 -0500 Subject: [PATCH 38/60] Add deadline timers, keep searches alive until they complete (or the conn is closed) to keep Write from blocking forever --- src/yggdrasil/conn.go | 128 +++++++++++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 26 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 08591c4a..a019908f 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -11,19 +11,35 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/util" ) +// Error implements the net.Error interface +type ConnError struct { + error + timeout bool + temporary bool +} + +func (e *ConnError) Timeout() bool { + return e.timeout +} + +func (e *ConnError) Temporary() bool { + return e.temporary +} + type Conn struct { core *Core nodeID *crypto.NodeID nodeMask *crypto.NodeID mutex sync.RWMutex + closed bool session *sessionInfo - readDeadline atomic.Value // time.Time // TODO timer - writeDeadline atomic.Value // time.Time // TODO timer - searching atomic.Value // bool - searchwait chan struct{} + readDeadline atomic.Value // time.Time // TODO timer + writeDeadline atomic.Value // time.Time // TODO timer + searching atomic.Value // bool + searchwait chan struct{} // Never reset this, it's only used for the initial search } -// TODO func NewConn() that initializes atomic and channel fields so things don't crash or block indefinitely +// TODO func NewConn() that initializes additional fields as needed func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn { conn := Conn{ core: core, @@ -32,7 +48,6 @@ func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session session: session, searchwait: make(chan struct{}), } - conn.SetDeadline(time.Time{}) conn.searching.Store(false) return &conn } @@ -45,22 +60,27 @@ func (c *Conn) String() string { func (c *Conn) startSearch() { // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { - // Make sure that any blocks on read/write operations are lifted - defer func() { - defer func() { recover() }() // In case searchwait was closed by another goroutine - c.searching.Store(false) - close(c.searchwait) // Never reset this to an open channel - }() + defer c.searching.Store(false) // If the search failed for some reason, e.g. it hit a dead end or timed // out, then do nothing if err != nil { c.core.log.Debugln(c.String(), "DHT search failed:", err) + go func() { + time.Sleep(time.Second) + c.mutex.RLock() + closed := c.closed + c.mutex.RUnlock() + if !closed { + // Restart the search, or else Write can stay blocked forever + c.core.router.admin <- c.startSearch + } + }() return } // Take the connection mutex c.mutex.Lock() defer c.mutex.Unlock() - // Were we successfully given a sessionInfo pointeR? + // Were we successfully given a sessionInfo pointer? if sinfo != nil { // Store it, and update the nodeID and nodeMask (which may have been // wildcarded before now) with their complete counterparts @@ -70,11 +90,19 @@ func (c *Conn) startSearch() { for i := range c.nodeMask { c.nodeMask[i] = 0xFF } + // Make sure that any blocks on read/write operations are lifted + defer func() { recover() }() // So duplicate searches don't panic + close(c.searchwait) } else { // No session was returned - this shouldn't really happen because we // should always return an error reason if we don't return a session panic("DHT search didn't return an error or a sessionInfo") } + if c.closed { + // Things were closed before the search returned + // Go ahead and close it again to make sure the session is cleaned up + go c.Close() + } } // doSearch will be called below in response to one or more conditions doSearch := func() { @@ -115,17 +143,30 @@ func (c *Conn) startSearch() { } } +func getDeadlineTimer(value *atomic.Value) *time.Timer { + timer := time.NewTimer(0) + util.TimerStop(timer) + if deadline, ok := value.Load().(time.Time); ok { + timer.Reset(time.Until(deadline)) + } + return timer +} + func (c *Conn) Read(b []byte) (int, error) { // Take a copy of the session object c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - timer := time.NewTimer(0) - util.TimerStop(timer) + timer := getDeadlineTimer(&c.readDeadline) + defer util.TimerStop(timer) // If there is a search in progress then wait for the result if sinfo == nil { // Wait for the search to complete - <-c.searchwait + select { + case <-c.searchwait: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } // Retrieve our session info again c.mutex.RLock() sinfo = c.session @@ -146,8 +187,9 @@ func (c *Conn) Read(b []byte) (int, error) { } defer util.PutBytes(p.Payload) var err error - // Hand over to the session worker - sinfo.doWorker(func() { + done := make(chan struct{}) + workerFunc := func() { + defer close(done) // If the nonce is bad then drop the packet and return an error if !sinfo.nonceIsOK(&p.Nonce) { err = errors.New("packet dropped due to invalid nonce") @@ -172,7 +214,18 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo.updateNonce(&p.Nonce) sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(b)) - }) + } + // Hand over to the session worker + select { // Send to worker + case sinfo.worker <- workerFunc: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } + select { // Wait for worker to return + case <-done: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } // Something went wrong in the session worker so abort if err != nil { return 0, err @@ -187,6 +240,8 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() + timer := getDeadlineTimer(&c.writeDeadline) + defer util.TimerStop(timer) // If the session doesn't exist, or isn't initialised (which probably means // that the search didn't complete successfully) then we may need to wait for // the search to complete or start the search again @@ -199,7 +254,11 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { }) } // Wait for the search to complete - <-c.searchwait + select { + case <-c.searchwait: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } // Retrieve our session info again c.mutex.RLock() sinfo = c.session @@ -213,8 +272,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } // defer util.PutBytes(b) var packet []byte - // Hand over to the session worker - sinfo.doWorker(func() { + done := make(chan struct{}) + workerFunc := func() { + defer close(done) // Encrypt the packet payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce) defer util.PutBytes(payload) @@ -227,7 +287,18 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } packet = p.encode() sinfo.bytesSent += uint64(len(b)) - }) + } + // Hand over to the session worker + select { // Send to worker + case sinfo.worker <- workerFunc: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } + select { // Wait for worker to return + case <-done: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } // Give the packet to the router sinfo.core.router.out(packet) // Finally return the number of bytes we wrote @@ -235,10 +306,15 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } func (c *Conn) Close() error { - // Close the session, if it hasn't been closed already - c.session.close() - c.session = nil + c.mutex.Lock() + defer c.mutex.Unlock() + if c.session != nil { + // Close the session, if it hasn't been closed already + c.session.close() + c.session = nil + } // This can't fail yet - TODO? + c.closed = true return nil } From 01ea6d3d80ba3bb7006d13ffab4ebb823be3d7b4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Apr 2019 21:49:11 -0500 Subject: [PATCH 39/60] somehow this doesn't seem to deadlock or crash from buffer reuse (util.PutBytes), but I have no idea why it was doing that before and not now --- src/yggdrasil/conn.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index a019908f..51f352f0 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -60,7 +60,6 @@ func (c *Conn) String() string { func (c *Conn) startSearch() { // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { - defer c.searching.Store(false) // If the search failed for some reason, e.g. it hit a dead end or timed // out, then do nothing if err != nil { @@ -77,6 +76,7 @@ func (c *Conn) startSearch() { }() return } + defer c.searching.Store(false) // Take the connection mutex c.mutex.Lock() defer c.mutex.Unlock() @@ -180,6 +180,8 @@ func (c *Conn) Read(b []byte) (int, error) { } // Wait for some traffic to come through from the session select { + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} case p, ok := <-sinfo.recv: // If the session is closed then do nothing if !ok { @@ -197,10 +199,10 @@ func (c *Conn) Read(b []byte) (int, error) { } // Decrypt the packet bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) + defer util.PutBytes(bs) // Check if we were unable to decrypt the packet for some reason and // return an error if we couldn't if !isOK { - util.PutBytes(bs) err = errors.New("packet dropped due to decryption failure") return } @@ -249,9 +251,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // Is a search already taking place? if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) { // No search was already taking place so start a new one - c.core.router.doAdmin(func() { - c.startSearch() - }) + c.core.router.doAdmin(c.startSearch) } // Wait for the search to complete select { From 5d323861f03dc178f18362992d901d5945b392f1 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Apr 2019 22:21:31 -0500 Subject: [PATCH 40/60] properly fix the memory errors, it was caused by a function returning and PutBytes-ing a buffer before a worker had a chance to decrypt the buffer, so it would GetBytes the same buffer by dumb luck and then get an illegal overlap --- src/yggdrasil/conn.go | 15 +++------------ src/yggdrasil/router.go | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 51f352f0..5c71bb90 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -199,7 +199,7 @@ func (c *Conn) Read(b []byte) (int, error) { } // Decrypt the packet bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) - defer util.PutBytes(bs) + defer util.PutBytes(bs) // FIXME commenting this out leads to illegal buffer reuse, this implies there's a memory error somewhere and that this is just flooding things out of the finite pool of old slices that get reused // Check if we were unable to decrypt the packet for some reason and // return an error if we couldn't if !isOK { @@ -223,11 +223,7 @@ func (c *Conn) Read(b []byte) (int, error) { case <-timer.C: return 0, ConnError{errors.New("Timeout"), true, false} } - select { // Wait for worker to return - case <-done: - case <-timer.C: - return 0, ConnError{errors.New("Timeout"), true, false} - } + <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) // Something went wrong in the session worker so abort if err != nil { return 0, err @@ -270,7 +266,6 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return 0, errors.New("search failed") } } - // defer util.PutBytes(b) var packet []byte done := make(chan struct{}) workerFunc := func() { @@ -294,11 +289,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { case <-timer.C: return 0, ConnError{errors.New("Timeout"), true, false} } - select { // Wait for worker to return - case <-done: - case <-timer.C: - return 0, ConnError{errors.New("Timeout"), true, false} - } + <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) // Give the packet to the router sinfo.core.router.out(packet) // Finally return the number of bytes we wrote diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index cacf0259..2e32fb6b 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -341,7 +341,7 @@ func (r *router) handleTraffic(packet []byte) { return } select { - case sinfo.recv <- &p: // FIXME ideally this should be FIFO + case sinfo.recv <- &p: // FIXME ideally this should be front drop default: util.PutBytes(p.Payload) } From 6469e39ff188b4adb7e83598f6d02595debe3268 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Apr 2019 22:42:05 -0500 Subject: [PATCH 41/60] workaround to random timeouts --- src/util/util.go | 11 +++++------ src/yggdrasil/conn.go | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/util/util.go b/src/util/util.go index 49e0207a..94bd5d6a 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -48,13 +48,12 @@ func PutBytes(bs []byte) { // This is a workaround to go's broken timer implementation func TimerStop(t *time.Timer) bool { - if !t.Stop() { - select { - case <-t.C: - default: - } + stopped := t.Stop() + select { + case <-t.C: + default: } - return true + return stopped } // Run a blocking function with a timeout. diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 5c71bb90..aad1dcdd 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -144,7 +144,7 @@ func (c *Conn) startSearch() { } func getDeadlineTimer(value *atomic.Value) *time.Timer { - timer := time.NewTimer(0) + timer := time.NewTimer(24 * 365 * time.Hour) // FIXME for some reason setting this to 0 doesn't always let it stop and drain the channel correctly util.TimerStop(timer) if deadline, ok := value.Load().(time.Time); ok { timer.Reset(time.Until(deadline)) From 5f66c4c95ceb57625c71e8ae5d4cafd1e4b6af18 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 28 Apr 2019 17:14:09 +0100 Subject: [PATCH 42/60] Try using separate workers for each TUN/TAP connection (sometimes produces duplicate packets when communicating with both the node address and a subnet address, sometimes also can't Ctrl-C to quit) --- src/tuntap/conn.go | 75 +++++++ src/tuntap/iface.go | 255 ++++++++++++++++++++++ src/tuntap/tun.go | 520 ++++---------------------------------------- 3 files changed, 376 insertions(+), 474 deletions(-) create mode 100644 src/tuntap/conn.go create mode 100644 src/tuntap/iface.go diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go new file mode 100644 index 00000000..e59e560e --- /dev/null +++ b/src/tuntap/conn.go @@ -0,0 +1,75 @@ +package tuntap + +import ( + "errors" + + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" +) + +type tunConn struct { + tun *TunAdapter + conn *yggdrasil.Conn + send chan []byte + stop chan interface{} +} + +func (s *tunConn) close() { + close(s.stop) +} + +func (s *tunConn) reader() error { + select { + case _, ok := <-s.stop: + if !ok { + return errors.New("session was already closed") + } + default: + } + var n int + var err error + read := make(chan bool) + b := make([]byte, 65535) + for { + go func() { + if n, err = s.conn.Read(b); err != nil { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) + return + } + read <- true + }() + select { + case <-read: + if n > 0 { + s.tun.send <- b[:n] + } + case <-s.stop: + s.tun.log.Debugln("Stopping conn reader for", s) + return nil + } + } +} + +func (s *tunConn) writer() error { + select { + case _, ok := <-s.stop: + if !ok { + return errors.New("session was already closed") + } + default: + } + for { + select { + case <-s.stop: + s.tun.log.Debugln("Stopping conn writer for", s) + return nil + case b, ok := <-s.send: + if !ok { + return errors.New("send closed") + } + if _, err := s.conn.Write(b); err != nil { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) + continue + } + } + } +} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go new file mode 100644 index 00000000..d837633b --- /dev/null +++ b/src/tuntap/iface.go @@ -0,0 +1,255 @@ +package tuntap + +import ( + "bytes" + "errors" + "time" + + "github.com/songgao/packets/ethernet" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" +) + +func (tun *TunAdapter) writer() error { + var w int + var err error + for { + b := <-tun.send + n := len(b) + if n == 0 { + continue + } + if tun.iface.IsTAP() { + var dstAddr address.Address + if b[0]&0xf0 == 0x60 { + if len(b) < 40 { + //panic("Tried to send a packet shorter than an IPv6 header...") + util.PutBytes(b) + continue + } + copy(dstAddr[:16], b[24:]) + } else if b[0]&0xf0 == 0x40 { + if len(b) < 20 { + //panic("Tried to send a packet shorter than an IPv4 header...") + util.PutBytes(b) + continue + } + copy(dstAddr[:4], b[16:]) + } else { + return errors.New("Invalid address family") + } + sendndp := func(dstAddr address.Address) { + neigh, known := tun.icmpv6.peermacs[dstAddr] + known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) + if !known { + request, err := tun.icmpv6.CreateNDPL2(dstAddr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + tun.icmpv6.peermacs[dstAddr] = neighbor{ + lastsolicitation: time.Now(), + } + } + } + var peermac macAddress + var peerknown bool + if b[0]&0xf0 == 0x40 { + dstAddr = tun.addr + } else if b[0]&0xf0 == 0x60 { + if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { + dstAddr = tun.addr + } + } + if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + sendndp(dstAddr) + } else { + sendndp(tun.addr) + } + if peerknown { + var proto ethernet.Ethertype + switch { + case b[0]&0xf0 == 0x60: + proto = ethernet.IPv6 + case b[0]&0xf0 == 0x40: + proto = ethernet.IPv4 + } + var frame ethernet.Frame + frame.Prepare( + peermac[:6], // Destination MAC address + tun.icmpv6.mymac[:6], // Source MAC address + ethernet.NotTagged, // VLAN tagging + proto, // Ethertype + len(b)) // Payload length + copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) + n += tun_ETHER_HEADER_LENGTH + w, err = tun.iface.Write(frame[:n]) + } + } else { + w, err = tun.iface.Write(b[:n]) + } + if err != nil { + tun.log.Errorln("TUN/TAP iface write error:", err) + continue + } + if w != n { + tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") + continue + } + } +} + +func (tun *TunAdapter) reader() error { + bs := make([]byte, 65535) + for { + // Wait for a packet to be delivered to us through the TUN/TAP adapter + n, err := tun.iface.Read(bs) + if err != nil { + panic(err) + } + if n == 0 { + continue + } + // If it's a TAP adapter, update the buffer slice so that we no longer + // include the ethernet headers + offset := 0 + if tun.iface.IsTAP() { + // Set our offset to beyond the ethernet headers + offset = tun_ETHER_HEADER_LENGTH + // If we detect an ICMP packet then hand it to the ICMPv6 module + if bs[offset+6] == 58 { + // Found an ICMPv6 packet + b := make([]byte, n) + copy(b, bs) + go tun.icmpv6.ParsePacket(b) + } + // Then offset the buffer so that we can now just treat it as an IP + // packet from now on + bs = bs[offset:] + } + // From the IP header, work out what our source and destination addresses + // and node IDs are. We will need these in order to work out where to send + // the packet + var srcAddr address.Address + var dstAddr address.Address + var dstNodeID *crypto.NodeID + var dstNodeIDMask *crypto.NodeID + var dstSnet address.Subnet + var addrlen int + // Check the IP protocol - if it doesn't match then we drop the packet and + // do nothing with it + if bs[0]&0xf0 == 0x60 { + // Check if we have a fully-sized IPv6 header + if len(bs) < 40 { + continue + } + // Check the packet size + if n != 256*int(bs[4])+int(bs[5])+offset+tun_IPv6_HEADER_LENGTH { + continue + } + // IPv6 address + addrlen = 16 + copy(srcAddr[:addrlen], bs[8:]) + copy(dstAddr[:addrlen], bs[24:]) + copy(dstSnet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // Check if we have a fully-sized IPv4 header + if len(bs) < 20 { + continue + } + // Check the packet size + if n != 256*int(bs[2])+int(bs[3])+offset { + continue + } + // IPv4 address + addrlen = 4 + copy(srcAddr[:addrlen], bs[12:]) + copy(dstAddr[:addrlen], bs[16:]) + } else { + // Unknown address length or protocol, so drop the packet and ignore it + continue + } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + // For now don't deal with any non-Yggdrasil ranges + continue + } + // Do we have an active connection for this node address? + tun.mutex.RLock() + session, isIn := tun.addrToConn[dstAddr] + if !isIn || session == nil { + session, isIn = tun.subnetToConn[dstSnet] + if !isIn || session == nil { + // Neither an address nor a subnet mapping matched, therefore populate + // the node ID and mask to commence a search + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + } + } + tun.mutex.RUnlock() + // If we don't have a connection then we should open one + if !isIn || session == nil { + // Check we haven't been given empty node ID, really this shouldn't ever + // happen but just to be sure... + if dstNodeID == nil || dstNodeIDMask == nil { + panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") + } + // Dial to the remote node + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + // We've been given a connection so prepare the session wrapper + if s, err := tun.wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP iface wrap:", err) + } else { + // Update our reference to the connection + session, isIn = s, true + } + } else { + // We weren't able to dial for some reason so there's no point in + // continuing this iteration - skip to the next one + continue + } + } + // If we have a connection now, try writing to it + if isIn && session != nil { + select { + case session.send <- bs[:n]: + default: + } + } + + /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { + // The packet had a src address that doesn't belong to us or our + // configured crypto-key routing src subnets + return + } + if !dstAddr.IsValid() && !dstSnet.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(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + dstPubKey = &key + dstNodeID = crypto.GetNodeID(dstPubKey) + // 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(dstNodeID) + copy(dstAddr[:], addr[:]) + copy(dstSnet[:], addr[:]) + if !dstAddr.IsValid() && !dstSnet.IsValid() { + return + } + } else { + // No public key was found in the CKR table so we've exhausted our options + return + } + }*/ + + } +} diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index c19764c3..cfc8a668 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -6,10 +6,9 @@ package tuntap // TODO: Set MTU of session properly // TODO: Reject packets that exceed session MTU with ICMPv6 for PMTU Discovery // TODO: Connection timeouts (call Conn.Close() when we want to time out) -// TODO: Don't block in ifaceReader on writes that are pending searches +// TODO: Don't block in reader on writes that are pending searches import ( - "bytes" "encoding/hex" "errors" "fmt" @@ -18,14 +17,12 @@ import ( "time" "github.com/gologme/log" - "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/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -47,9 +44,10 @@ type TunAdapter struct { icmpv6 ICMPv6 mtu int iface *water.Interface - mutex sync.RWMutex // Protects the below - addrToConn map[address.Address]*yggdrasil.Conn // Managed by connReader - subnetToConn map[address.Subnet]*yggdrasil.Conn // Managed by connReader + send chan []byte + mutex sync.RWMutex // Protects the below + addrToConn map[address.Address]*tunConn + subnetToConn map[address.Subnet]*tunConn isOpen bool } @@ -112,8 +110,8 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.log = log tun.listener = listener tun.dialer = dialer - tun.addrToConn = make(map[address.Address]*yggdrasil.Conn) - tun.subnetToConn = make(map[address.Subnet]*yggdrasil.Conn) + tun.addrToConn = make(map[address.Address]*tunConn) + tun.subnetToConn = make(map[address.Subnet]*tunConn) } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -148,6 +146,7 @@ func (tun *TunAdapter) Start() error { } tun.mutex.Lock() tun.isOpen = true + tun.send = make(chan []byte, 32) // TODO: is this a sensible value? tun.mutex.Unlock() if iftapmode { go func() { @@ -159,9 +158,7 @@ func (tun *TunAdapter) Start() error { if err != nil { panic(err) } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } + tun.send <- request time.Sleep(time.Second) } }() @@ -173,7 +170,8 @@ func (tun *TunAdapter) Start() error { } }() go tun.handler() - go tun.ifaceReader() + go tun.reader() + go tun.writer() tun.icmpv6.Init(tun) return nil } @@ -186,473 +184,47 @@ func (tun *TunAdapter) handler() error { tun.log.Errorln("TUN/TAP connection accept error:", err) return err } - go tun.connReader(conn) + if _, err := tun.wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP handler wrap:", err) + } } } -func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { +func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { + // Prepare a session wrapper for the given connection + s := tunConn{ + tun: tun, + conn: conn, + send: make(chan []byte, 32), // TODO: is this a sensible value? + stop: make(chan interface{}), + } + // Get the remote address and subnet of the other side remoteNodeID := conn.RemoteAddr() remoteAddr := address.AddrForNodeID(&remoteNodeID) remoteSubnet := address.SubnetForNodeID(&remoteNodeID) - err := func() error { - tun.mutex.RLock() - defer tun.mutex.RUnlock() - if _, isIn := tun.addrToConn[*remoteAddr]; isIn { - return errors.New("duplicate connection for address " + net.IP(remoteAddr[:]).String()) - } - if _, isIn := tun.subnetToConn[*remoteSubnet]; isIn { - return errors.New("duplicate connection for subnet " + net.IP(remoteSubnet[:]).String()) - } - return nil - }() - if err != nil { - //return err - panic(err) - } - // Store the connection mapped to address and subnet + // Work out if this is already a destination we already know about tun.mutex.Lock() - tun.addrToConn[*remoteAddr] = conn - tun.subnetToConn[*remoteSubnet] = conn - tun.mutex.Unlock() - // Make sure to clean those up later when the connection is closed - defer func() { - tun.mutex.Lock() - delete(tun.addrToConn, *remoteAddr) - delete(tun.subnetToConn, *remoteSubnet) - tun.mutex.Unlock() - }() - b := make([]byte, 65535) - for { - n, err := conn.Read(b) - if err != nil { - tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) - continue - } - if n == 0 { - continue - } - var w int - if tun.iface.IsTAP() { - var dstAddr address.Address - if b[0]&0xf0 == 0x60 { - if len(b) < 40 { - //panic("Tried to send a packet shorter than an IPv6 header...") - util.PutBytes(b) - continue - } - copy(dstAddr[:16], b[24:]) - } else if b[0]&0xf0 == 0x40 { - if len(b) < 20 { - //panic("Tried to send a packet shorter than an IPv4 header...") - util.PutBytes(b) - continue - } - copy(dstAddr[:4], b[16:]) - } else { - return errors.New("Invalid address family") - } - sendndp := func(dstAddr address.Address) { - neigh, known := tun.icmpv6.peermacs[dstAddr] - known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) - if !known { - request, err := tun.icmpv6.CreateNDPL2(dstAddr) - if err != nil { - panic(err) - } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } - tun.icmpv6.peermacs[dstAddr] = neighbor{ - lastsolicitation: time.Now(), - } - } - } - var peermac macAddress - var peerknown bool - if b[0]&0xf0 == 0x40 { - dstAddr = tun.addr - } else if b[0]&0xf0 == 0x60 { - if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { - dstAddr = tun.addr - } - } - if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { - peermac = neighbor.mac - peerknown = true - } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { - peermac = neighbor.mac - peerknown = true - sendndp(dstAddr) - } else { - sendndp(tun.addr) - } - if peerknown { - var proto ethernet.Ethertype - switch { - case b[0]&0xf0 == 0x60: - proto = ethernet.IPv6 - case b[0]&0xf0 == 0x40: - proto = ethernet.IPv4 - } - var frame ethernet.Frame - frame.Prepare( - peermac[:6], // Destination MAC address - tun.icmpv6.mymac[:6], // Source MAC address - ethernet.NotTagged, // VLAN tagging - proto, // Ethertype - len(b)) // Payload length - copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) - n += tun_ETHER_HEADER_LENGTH - w, err = tun.iface.Write(frame[:n]) - } - } else { - w, err = tun.iface.Write(b[:n]) - } - if err != nil { - tun.log.Errorln(conn.String(), "TUN/TAP iface write error:", err) - continue - } - if w != n { - tun.log.Errorln(conn.String(), "TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") - continue - } + defer tun.mutex.Unlock() + atc, aok := tun.addrToConn[*remoteAddr] + stc, sok := tun.subnetToConn[*remoteSubnet] + // If we know about a connection for this destination already then assume it + // is no longer valid and close it + if aok { + atc.close() + err = errors.New("replaced connection for address") + } else if sok { + stc.close() + err = errors.New("replaced connection for subnet") } + // Save the session wrapper so that we can look it up quickly next time + // we receive a packet through the interface for this address + tun.addrToConn[*remoteAddr] = &s + tun.subnetToConn[*remoteSubnet] = &s + // Start the connection goroutines + go s.reader() + go s.writer() + // Return + return c, err } - -func (tun *TunAdapter) ifaceReader() error { - bs := make([]byte, 65535) - for { - // Wait for a packet to be delivered to us through the TUN/TAP adapter - n, err := tun.iface.Read(bs) - if err != nil { - continue - } - // If it's a TAP adapter, update the buffer slice so that we no longer - // include the ethernet headers - offset := 0 - if tun.iface.IsTAP() { - // Set our offset to beyond the ethernet headers - offset = tun_ETHER_HEADER_LENGTH - // If we detect an ICMP packet then hand it to the ICMPv6 module - if bs[offset+6] == 58 { - // Found an ICMPv6 packet - b := make([]byte, n) - copy(b, bs) - go tun.icmpv6.ParsePacket(b) - } - // Then offset the buffer so that we can now just treat it as an IP - // packet from now on - bs = bs[offset:] - } - // From the IP header, work out what our source and destination addresses - // and node IDs are. We will need these in order to work out where to send - // the packet - var srcAddr address.Address - var dstAddr address.Address - var dstNodeID *crypto.NodeID - var dstNodeIDMask *crypto.NodeID - var dstSnet address.Subnet - var addrlen int - // Check the IP protocol - if it doesn't match then we drop the packet and - // do nothing with it - if bs[0]&0xf0 == 0x60 { - // Check if we have a fully-sized IPv6 header - if len(bs) < 40 { - continue - } - // Check the packet size - if n != 256*int(bs[4])+int(bs[5])+offset+tun_IPv6_HEADER_LENGTH { - continue - } - // IPv6 address - addrlen = 16 - copy(srcAddr[:addrlen], bs[8:]) - copy(dstAddr[:addrlen], bs[24:]) - copy(dstSnet[:addrlen/2], bs[24:]) - } else if bs[0]&0xf0 == 0x40 { - // Check if we have a fully-sized IPv4 header - if len(bs) < 20 { - continue - } - // Check the packet size - if n != 256*int(bs[2])+int(bs[3])+offset { - continue - } - // IPv4 address - addrlen = 4 - copy(srcAddr[:addrlen], bs[12:]) - copy(dstAddr[:addrlen], bs[16:]) - } else { - // Unknown address length or protocol, so drop the packet and ignore it - continue - } - if !dstAddr.IsValid() && !dstSnet.IsValid() { - // For now don't deal with any non-Yggdrasil ranges - continue - } - // Do we have an active connection for this node address? - tun.mutex.RLock() - conn, isIn := tun.addrToConn[dstAddr] - if !isIn || conn == nil { - conn, isIn = tun.subnetToConn[dstSnet] - if !isIn || conn == nil { - // Neither an address nor a subnet mapping matched, therefore populate - // the node ID and mask to commence a search - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() - } - } - tun.mutex.RUnlock() - // If we don't have a connection then we should open one - if !isIn || conn == nil { - // Check we haven't been given empty node ID, really this shouldn't ever - // happen but just to be sure... - if dstNodeID == nil || dstNodeIDMask == nil { - panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") - } - // Dial to the remote node - if c, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - // We've been given a connection so start the connection reader goroutine - go tun.connReader(c) - // Then update our reference to the connection - conn, isIn = c, true - } else { - // We weren't able to dial for some reason so there's no point in - // continuing this iteration - skip to the next one - continue - } - } - // If we have a connection now, try writing to it - if isIn && conn != nil { - // If we have an open connection, either because we already had one or - // because we opened one above, try writing the packet to it - w, err := conn.Write(bs[:n]) - if err != nil { - tun.log.Errorln(conn.String(), "TUN/TAP conn write error:", err) - continue - } - if w != n { - tun.log.Errorln(conn.String(), "TUN/TAP conn write mismatch:", w, "bytes written vs", n, "bytes given") - continue - } - } - - /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { - // The packet had a src address that doesn't belong to us or our - // configured crypto-key routing src subnets - return - } - if !dstAddr.IsValid() && !dstSnet.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(dstAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - dstPubKey = &key - dstNodeID = crypto.GetNodeID(dstPubKey) - // 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(dstNodeID) - copy(dstAddr[:], addr[:]) - copy(dstSnet[:], addr[:]) - if !dstAddr.IsValid() && !dstSnet.IsValid() { - return - } - } else { - // No public key was found in the CKR table so we've exhausted our options - return - } - }*/ - - } -} - -// 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 *TunAdapter) write() error { - for { - select { - case reject := <-tun.Reject: - switch reject.Reason { - case yggdrasil.PacketTooBig: - if mtu, ok := reject.Detail.(int); ok { - // Create the Packet Too Big response - ptb := &icmp.PacketTooBig{ - MTU: int(mtu), - Data: reject.Packet, - } - - // Create the ICMPv6 response from it - icmpv6Buf, err := CreateICMPv6( - reject.Packet[8:24], reject.Packet[24:40], - ipv6.ICMPTypePacketTooBig, 0, ptb) - - // Send the ICMPv6 response back to the TUN/TAP adapter - if err == nil { - tun.iface.Write(icmpv6Buf) - } - } - fallthrough - default: - continue - } - case data := <-tun.Recv: - if tun.iface == nil { - continue - } - if tun.iface.IsTAP() { - var dstAddr address.Address - if data[0]&0xf0 == 0x60 { - if len(data) < 40 { - //panic("Tried to send a packet shorter than an IPv6 header...") - util.PutBytes(data) - continue - } - copy(dstAddr[:16], data[24:]) - } else if data[0]&0xf0 == 0x40 { - if len(data) < 20 { - //panic("Tried to send a packet shorter than an IPv4 header...") - util.PutBytes(data) - continue - } - copy(dstAddr[:4], data[16:]) - } else { - return errors.New("Invalid address family") - } - sendndp := func(dstAddr address.Address) { - neigh, known := tun.icmpv6.peermacs[dstAddr] - known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) - if !known { - request, err := tun.icmpv6.CreateNDPL2(dstAddr) - if err != nil { - panic(err) - } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } - tun.icmpv6.peermacs[dstAddr] = neighbor{ - lastsolicitation: time.Now(), - } - } - } - var peermac macAddress - var peerknown bool - if data[0]&0xf0 == 0x40 { - dstAddr = tun.addr - } else if data[0]&0xf0 == 0x60 { - if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { - dstAddr = tun.addr - } - } - if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { - peermac = neighbor.mac - peerknown = true - } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { - peermac = neighbor.mac - peerknown = true - sendndp(dstAddr) - } else { - sendndp(tun.addr) - } - if peerknown { - var proto ethernet.Ethertype - switch { - case data[0]&0xf0 == 0x60: - proto = ethernet.IPv6 - case data[0]&0xf0 == 0x40: - proto = ethernet.IPv4 - } - var frame ethernet.Frame - frame.Prepare( - peermac[:6], // Destination MAC address - tun.icmpv6.mymac[:6], // Source MAC address - ethernet.NotTagged, // VLAN tagging - proto, // Ethertype - len(data)) // Payload length - copy(frame[tun_ETHER_HEADER_LENGTH:], data[:]) - if _, err := tun.iface.Write(frame); err != nil { - 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 { - tun.mutex.RLock() - open := tun.isOpen - tun.mutex.RUnlock() - if !open { - return nil - } else { - panic(err) - } - } - } - util.PutBytes(data) - } - } -} - -// Reads any packets that are waiting on the TUN/TAP adapter. If the adapter -// 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 *TunAdapter) read() error { - mtu := tun.mtu - if tun.iface.IsTAP() { - mtu += tun_ETHER_HEADER_LENGTH - } - buf := make([]byte, mtu) - for { - n, err := tun.iface.Read(buf) - if err != nil { - tun.mutex.RLock() - open := tun.isOpen - tun.mutex.RUnlock() - if !open { - return nil - } else { - return err - } - } - o := 0 - if tun.iface.IsTAP() { - o = tun_ETHER_HEADER_LENGTH - } - switch { - case buf[o]&0xf0 == 0x60 && n == 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o: - case buf[o]&0xf0 == 0x40 && n == 256*int(buf[o+2])+int(buf[o+3])+o: - default: - continue - } - if buf[o+6] == 58 { - if tun.iface.IsTAP() { - // Found an ICMPv6 packet - b := make([]byte, n) - copy(b, buf) - go tun.icmpv6.ParsePacket(b) - } - } - packet := append(util.GetBytes(), buf[o:n]...) - tun.Send <- packet - } -} - -// 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 *TunAdapter) Close() error { - tun.mutex.Lock() - tun.isOpen = false - tun.mutex.Unlock() - if tun.iface == nil { - return nil - } - return tun.iface.Close() -} -*/ From efdaea1b5ecb00c9de739d54d16718f417deadeb Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 2 May 2019 17:37:49 -0500 Subject: [PATCH 43/60] fix some races and GetBytes/PutBytes usage, but this still seems to deadlock somewhere in iperf tests --- src/tuntap/conn.go | 5 +++-- src/tuntap/iface.go | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index e59e560e..ae8fcc0b 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -3,6 +3,7 @@ package tuntap import ( "errors" + "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -40,7 +41,7 @@ func (s *tunConn) reader() error { select { case <-read: if n > 0 { - s.tun.send <- b[:n] + s.tun.send <- append(util.GetBytes(), b[:n]...) } case <-s.stop: s.tun.log.Debugln("Stopping conn reader for", s) @@ -68,8 +69,8 @@ func (s *tunConn) writer() error { } if _, err := s.conn.Write(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) - continue } + util.PutBytes(b) } } } diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index d837633b..5b8e77bb 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -95,6 +95,7 @@ func (tun *TunAdapter) writer() error { } } else { w, err = tun.iface.Write(b[:n]) + util.PutBytes(b) } if err != nil { tun.log.Errorln("TUN/TAP iface write error:", err) @@ -219,9 +220,11 @@ func (tun *TunAdapter) reader() error { } // If we have a connection now, try writing to it if isIn && session != nil { + packet := append(util.GetBytes(), bs[:n]...) select { - case session.send <- bs[:n]: + case session.send <- packet: default: + util.PutBytes(packet) } } From 522ed147b14f88d5892df3e5fbdfce5df5339189 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 15 May 2019 18:01:26 -0500 Subject: [PATCH 44/60] use the subnet derived ID/mask when creating a connection based on a subnet address, fix a potential blocking channel send in tuntap/conn.go, and get debug.go compiling well enough to profile things (the sim is currently still broken) --- src/tuntap/conn.go | 7 ++++++- src/tuntap/iface.go | 6 +++++- src/yggdrasil/debug.go | 28 ++++++++++++++++++++++------ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index ae8fcc0b..50860f36 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -41,7 +41,12 @@ func (s *tunConn) reader() error { select { case <-read: if n > 0 { - s.tun.send <- append(util.GetBytes(), b[:n]...) + bs := append(util.GetBytes(), b[:n]...) + select { + case s.tun.send <- bs: + default: + util.PutBytes(bs) + } } case <-s.stop: s.tun.log.Debugln("Stopping conn reader for", s) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 5b8e77bb..efccd071 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -190,7 +190,11 @@ func (tun *TunAdapter) reader() error { if !isIn || session == nil { // Neither an address nor a subnet mapping matched, therefore populate // the node ID and mask to commence a search - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + if dstAddr.IsValid() { + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + } else { + dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() + } } } tun.mutex.RUnlock() diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index e575b72d..c4eed639 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -59,13 +59,17 @@ func (c *Core) Init() { hbpriv := hex.EncodeToString(bpriv[:]) hspub := hex.EncodeToString(spub[:]) hspriv := hex.EncodeToString(spriv[:]) - c.config = config.NodeConfig{ + cfg := config.NodeConfig{ EncryptionPublicKey: hbpub, EncryptionPrivateKey: hbpriv, SigningPublicKey: hspub, SigningPrivateKey: hspriv, } - c.init( /*bpub, bpriv, spub, spriv*/ ) + c.config = config.NodeState{ + Current: cfg, + Previous: cfg, + } + c.init() c.switchTable.start() c.router.start() } @@ -82,6 +86,7 @@ func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey { return (crypto.BoxPubKey)(c.boxPub) } +/* func (c *Core) DEBUG_getSend() chan<- []byte { return c.router.tun.send } @@ -89,6 +94,7 @@ func (c *Core) DEBUG_getSend() chan<- []byte { func (c *Core) DEBUG_getRecv() <-chan []byte { return c.router.tun.recv } +*/ // Peer @@ -317,6 +323,7 @@ func (c *Core) DEBUG_getAddr() *address.Address { return address.AddrForNodeID(&c.dht.nodeID) } +/* func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) { c.DEBUG_startTunWithMTU(ifname, iftapmode, 1280) } @@ -338,6 +345,7 @@ func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) { func (c *Core) DEBUG_stopTun() { c.router.tun.close() } +*/ //////////////////////////////////////////////////////////////////////////////// @@ -382,13 +390,17 @@ func (c *Core) DEBUG_init(bpub []byte, hbpriv := hex.EncodeToString(bpriv[:]) hspub := hex.EncodeToString(spub[:]) hspriv := hex.EncodeToString(spriv[:]) - c.config = config.NodeConfig{ + cfg := config.NodeConfig{ EncryptionPublicKey: hbpub, EncryptionPrivateKey: hbpriv, SigningPublicKey: hspub, SigningPrivateKey: hspriv, } - c.init( /*bpub, bpriv, spub, spriv*/ ) + c.config = config.NodeState{ + Current: cfg, + Previous: cfg, + } + c.init() if err := c.router.start(); err != nil { panic(err) @@ -455,7 +467,7 @@ func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) { } */ -//* +/* func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) { c.config.Listen = []string{addrport} if err := c.link.init(c); err != nil { @@ -503,10 +515,11 @@ func (c *Core) DEBUG_addKCPConn(saddr string) { //////////////////////////////////////////////////////////////////////////////// +/* func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) { a := admin{} c.config.AdminListen = addrport - a.init(c /*, addrport*/) + a.init() c.admin = a } @@ -516,6 +529,7 @@ func (c *Core) DEBUG_setupAndStartMulticastInterface() { c.multicast = m m.start() } +*/ //////////////////////////////////////////////////////////////////////////////// @@ -579,9 +593,11 @@ func DEBUG_simLinkPeers(p, q *peer) { q.core.switchTable.idleIn <- q.port } +/* func (c *Core) DEBUG_simFixMTU() { c.router.tun.mtu = 65535 } +*/ //////////////////////////////////////////////////////////////////////////////// From 9c01947b1c83a796f94708835b64dfeffc13e70d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 16 May 2019 18:10:47 -0500 Subject: [PATCH 45/60] reduce allocations in switch --- src/yggdrasil/switch.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 0e164b9a..1bc40501 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -577,23 +577,28 @@ func (t *switchTable) start() error { return nil } +type closerInfo struct { + port switchPort + dist int +} + // Return a map of ports onto distance, keeping only ports closer to the destination than this node // If the map is empty (or nil), then no peer is closer -func (t *switchTable) getCloser(dest []byte) map[switchPort]int { +func (t *switchTable) getCloser(dest []byte) []closerInfo { table := t.getTable() myDist := table.self.dist(dest) if myDist == 0 { // Skip the iteration step if it's impossible to be closer return nil } - closer := make(map[switchPort]int, len(table.elems)) + t.queues.closer = t.queues.closer[:0] for _, info := range table.elems { dist := info.locator.dist(dest) if dist < myDist { - closer[info.port] = dist + t.queues.closer = append(t.queues.closer, closerInfo{info.port, dist}) } } - return closer + return t.queues.closer } // Returns true if the peer is closer to the destination than ourself @@ -656,9 +661,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo var bestDist int var bestTime time.Time ports := t.core.peers.getPorts() - for port, dist := range closer { - to := ports[port] - thisTime, isIdle := idle[port] + for _, cinfo := range closer { + to := ports[cinfo.port] + thisTime, isIdle := idle[cinfo.port] var update bool switch { case to == nil: @@ -667,9 +672,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo //nothing case best == nil: update = true - case dist < bestDist: + case cinfo.dist < bestDist: update = true - case dist > bestDist: + case cinfo.dist > bestDist: //nothing case thisTime.Before(bestTime): update = true @@ -678,7 +683,7 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo } if update { best = to - bestDist = dist + bestDist = cinfo.dist bestTime = thisTime } } @@ -711,6 +716,7 @@ type switch_buffers struct { size uint64 // Total size of all buffers, in bytes maxbufs int maxsize uint64 + closer []closerInfo // Scratch space } func (b *switch_buffers) cleanup(t *switchTable) { From 71ccaf753ecd88194fe3c12b09d192b2905e3cfa Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 17 May 2019 22:09:20 +0100 Subject: [PATCH 46/60] Add crypto-key routing into TUN/TAP --- src/tuntap/ckr.go | 411 ++++++++++++++++++++++++++++++++++++++++++++ src/tuntap/iface.go | 50 +++--- src/tuntap/tun.go | 6 +- 3 files changed, 436 insertions(+), 31 deletions(-) create mode 100644 src/tuntap/ckr.go diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go new file mode 100644 index 00000000..971f0a35 --- /dev/null +++ b/src/tuntap/ckr.go @@ -0,0 +1,411 @@ +package tuntap + +import ( + "bytes" + "encoding/hex" + "errors" + "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 +// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. + +type cryptokey struct { + tun *TunAdapter + enabled bool + reconfigure chan chan error + ipv4routes []cryptokey_route + ipv6routes []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 crypto.BoxPubKey +} + +// Initialise crypto-key routing. This must be done before any other CKR calls. +func (c *cryptokey) init(tun *TunAdapter) { + c.tun = tun + c.reconfigure = make(chan chan error, 1) + go func() { + for { + e := <-c.reconfigure + e <- nil + } + }() + + if err := c.configure(); err != nil { + c.tun.log.Errorln("CKR configuration failed:", err) + } +} + +// Configure the CKR routes - this must only ever be called from the router +// goroutine, e.g. through router.doAdmin +func (c *cryptokey) configure() error { + c.tun.config.Mutex.RLock() + defer c.tun.config.Mutex.RUnlock() + + // Set enabled/disabled state + c.setEnabled(c.tun.config.Current.TunnelRouting.Enable) + + // Clear out existing routes + c.ipv6routes = make([]cryptokey_route, 0) + c.ipv4routes = make([]cryptokey_route, 0) + + // Add IPv6 routes + for ipv6, pubkey := range c.tun.config.Current.TunnelRouting.IPv6Destinations { + if err := c.addRoute(ipv6, pubkey); err != nil { + return err + } + } + + // Add IPv4 routes + for ipv4, pubkey := range c.tun.config.Current.TunnelRouting.IPv4Destinations { + if err := c.addRoute(ipv4, pubkey); err != nil { + return err + } + } + + // Clear out existing sources + c.ipv6sources = make([]net.IPNet, 0) + c.ipv4sources = make([]net.IPNet, 0) + + // Add IPv6 sources + c.ipv6sources = make([]net.IPNet, 0) + for _, source := range c.tun.config.Current.TunnelRouting.IPv6Sources { + if err := c.addSourceSubnet(source); err != nil { + return err + } + } + + // Add IPv4 sources + c.ipv4sources = make([]net.IPNet, 0) + for _, source := range c.tun.config.Current.TunnelRouting.IPv4Sources { + if err := c.addSourceSubnet(source); err != nil { + return err + } + } + + // Wipe the caches + c.ipv4cache = make(map[address.Address]cryptokey_route, 0) + c.ipv6cache = make(map[address.Address]cryptokey_route, 0) + + return nil +} + +// Enable or disable crypto-key routing. +func (c *cryptokey) setEnabled(enabled bool) { + c.enabled = enabled +} + +// Check if crypto-key routing is enabled. +func (c *cryptokey) isEnabled() bool { + return c.enabled +} + +// 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.Address, addrlen int) bool { + ip := net.IP(addr[:addrlen]) + + if addrlen == net.IPv6len { + // Does this match our node's address? + if bytes.Equal(addr[:16], c.tun.addr[:16]) { + return true + } + + // Does this match our node's subnet? + if bytes.Equal(addr[:8], c.tun.subnet[:8]) { + return true + } + } + + // Does it match a configured CKR source? + if c.isEnabled() { + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if addrlen == net.IPv6len { + routingsources = &c.ipv6sources + } else if addrlen == net.IPv4len { + routingsources = &c.ipv4sources + } else { + return false + } + + for _, subnet := range *routingsources { + if subnet.Contains(ip) { + return true + } + } + } + + // Doesn't match any of the above + return false +} + +// Adds a source subnet, which allows traffic with these source addresses to +// be tunnelled using crypto-key routing. +func (c *cryptokey) addSourceSubnet(cidr string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingsources = &c.ipv6sources + } else if prefixsize == net.IPv4len*8 { + routingsources = &c.ipv4sources + } else { + return errors.New("Unexpected prefix size") + } + + // Check if we already have this CIDR + for _, subnet := range *routingsources { + if subnet.String() == ipnet.String() { + return errors.New("Source subnet already configured") + } + } + + // Add the source subnet + *routingsources = append(*routingsources, *ipnet) + c.tun.log.Infoln("Added CKR source subnet", cidr) + return nil +} + +// Adds a destination route for the given CIDR to be tunnelled to the node +// with the given BoxPubKey. +func (c *cryptokey) addRoute(cidr string, dest string) error { + // Is the CIDR we've been given valid? + ipaddr, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing table and cache + var routingtable *[]cryptokey_route + var routingcache *map[address.Address]cryptokey_route + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache + } else if prefixsize == net.IPv4len*8 { + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return errors.New("Unexpected prefix size") + } + + // Is the route an Yggdrasil destination? + var addr address.Address + var snet address.Subnet + copy(addr[:], ipaddr) + copy(snet[:], ipnet.IP) + 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? + for _, route := range *routingtable { + if route.subnet.String() == ipnet.String() { + return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) + } + } + // Decode the public key + if bpk, err := hex.DecodeString(dest); err != nil { + return err + } 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 crypto.BoxPubKey + copy(key[:], bpk) + *routingtable = append(*routingtable, cryptokey_route{ + subnet: *ipnet, + destination: key, + }) + + // Sort so most specific routes are first + sort.Slice(*routingtable, func(i, j int) bool { + im, _ := (*routingtable)[i].subnet.Mask.Size() + jm, _ := (*routingtable)[j].subnet.Mask.Size() + return im > jm + }) + + // Clear the cache as this route might change future routing + // Setting an empty slice keeps the memory whereas nil invokes GC + for k := range *routingcache { + delete(*routingcache, k) + } + + c.tun.log.Infoln("Added CKR destination subnet", cidr) + return nil + } +} + +// 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.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 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.Address]cryptokey_route + + // Check if the prefix is IPv4 or IPv6 + if addrlen == net.IPv6len { + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache + } else if addrlen == net.IPv4len { + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") + } + + // Check if there's a cache entry for this addr + if route, ok := (*routingcache)[addr]; ok { + return route.destination, nil + } + + // No cache was found - start by converting the address into a net.IP + ip := make(net.IP, addrlen) + copy(ip[:addrlen], addr[:]) + + // Check if we have a route. At this point c.ipv6routes should be + // pre-sorted so that the most specific routes are first + for _, route := range *routingtable { + // Does this subnet match the given IP? + if route.subnet.Contains(ip) { + // Check if the routing cache is above a certain size, if it is evict + // a random entry so we can make room for this one. We take advantage + // of the fact that the iteration order is random here + for k := range *routingcache { + if len(*routingcache) < 1024 { + break + } + delete(*routingcache, k) + } + + // Cache the entry for future packets to get a faster lookup + (*routingcache)[addr] = route + + // Return the boxPubKey + return route.destination, nil + } + } + + // No route was found if we got to this point + 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 +// be tunnelled using crypto-key routing. +func (c *cryptokey) removeSourceSubnet(cidr string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingsources = &c.ipv6sources + } else if prefixsize == net.IPv4len*8 { + routingsources = &c.ipv4sources + } else { + return errors.New("Unexpected prefix size") + } + + // Check if we already have this CIDR + for idx, subnet := range *routingsources { + if subnet.String() == ipnet.String() { + *routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...) + c.tun.log.Infoln("Removed CKR source subnet", cidr) + return nil + } + } + return errors.New("Source subnet not found") +} + +// Removes a destination route for the given CIDR to be tunnelled to the node +// with the given BoxPubKey. +func (c *cryptokey) removeRoute(cidr string, dest string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing table and cache + var routingtable *[]cryptokey_route + var routingcache *map[address.Address]cryptokey_route + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache + } else if prefixsize == net.IPv4len*8 { + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return errors.New("Unexpected prefix size") + } + + // Decode the public key + bpk, err := hex.DecodeString(dest) + if err != nil { + return err + } else if len(bpk) != crypto.BoxPubKeyLen { + return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) + } + netStr := ipnet.String() + + for idx, route := range *routingtable { + if bytes.Equal(route.destination[:], bpk) && route.subnet.String() == netStr { + *routingtable = append((*routingtable)[:idx], (*routingtable)[idx+1:]...) + for k := range *routingcache { + delete(*routingcache, k) + } + c.tun.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest) + return nil + } + } + return errors.New(fmt.Sprintf("Route does not exists for %s", cidr)) +} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index efccd071..d70a1305 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -178,10 +178,29 @@ func (tun *TunAdapter) reader() error { // Unknown address length or protocol, so drop the packet and ignore it continue } - if !dstAddr.IsValid() && !dstSnet.IsValid() { - // For now don't deal with any non-Yggdrasil ranges + if !tun.ckr.isValidSource(srcAddr, addrlen) { + // The packet had a source address that doesn't belong to us or our + // configured crypto-key routing source subnets continue } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + dstNodeID = crypto.GetNodeID(&key) + // 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(dstNodeID) + copy(dstAddr[:], addr[:]) + copy(dstSnet[:], addr[:]) + // Are we certain we looked up a valid node? + if !dstAddr.IsValid() && !dstSnet.IsValid() { + continue + } + } else { + // No public key was found in the CKR table so we've exhausted our options + continue + } + } // Do we have an active connection for this node address? tun.mutex.RLock() session, isIn := tun.addrToConn[dstAddr] @@ -231,32 +250,5 @@ func (tun *TunAdapter) reader() error { util.PutBytes(packet) } } - - /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { - // The packet had a src address that doesn't belong to us or our - // configured crypto-key routing src subnets - return - } - if !dstAddr.IsValid() && !dstSnet.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(dstAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - dstPubKey = &key - dstNodeID = crypto.GetNodeID(dstPubKey) - // 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(dstNodeID) - copy(dstAddr[:], addr[:]) - copy(dstSnet[:], addr[:]) - if !dstAddr.IsValid() && !dstSnet.IsValid() { - return - } - } else { - // No public key was found in the CKR table so we've exhausted our options - return - } - }*/ - } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index cfc8a668..a73dde17 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -41,6 +41,7 @@ type TunAdapter struct { dialer *yggdrasil.Dialer addr address.Address subnet address.Subnet + ckr cryptokey icmpv6 ICMPv6 mtu int iface *water.Interface @@ -117,8 +118,8 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener // Start the setup process for the TUN/TAP adapter. If successful, starts the // read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Start() error { - tun.config.Mutex.Lock() - defer tun.config.Mutex.Unlock() + tun.config.Mutex.RLock() + defer tun.config.Mutex.RUnlock() if tun.config == nil || tun.listener == nil || tun.dialer == nil { return errors.New("No configuration available to TUN/TAP") } @@ -173,6 +174,7 @@ func (tun *TunAdapter) Start() error { go tun.reader() go tun.writer() tun.icmpv6.Init(tun) + tun.ckr.init(tun) return nil } From ae2cc13d141cd1385bd920b6a657e6cd8323b586 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 17 May 2019 22:29:52 +0100 Subject: [PATCH 47/60] Fix configuration reloading support --- cmd/yggdrasil/main.go | 65 ++------------------------------------ src/multicast/multicast.go | 34 ++++++++++++++++++++ src/tuntap/tun.go | 32 +++++++++++++++++++ src/yggdrasil/core.go | 6 ++-- 4 files changed, 71 insertions(+), 66 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 96349835..52ddfe9c 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -280,69 +280,6 @@ func main() { defer func() { n.core.Stop() }() - // Listen for new sessions - /* - go func() { - listener, err := n.core.ListenConn() - if err != nil { - logger.Errorln("Unable to listen for sessions:", err) - return - } - for { - conn, err := listener.Accept() - if err != nil { - logger.Errorln("Accept:", err) - continue - } - logger.Println("Accepted") - for { - b := make([]byte, 100) - if n, err := conn.Read(b); err != nil { - logger.Errorln("Read failed:", err) - time.Sleep(time.Second * 2) - } else { - logger.Println("Read", n, "bytes:", b) - b = []byte{5, 5, 5} - if n, err := conn.Write(b); err != nil { - logger.Errorln("Write failed:", err) - time.Sleep(time.Second * 2) - } else { - logger.Println("Wrote", n, "bytes:", b) - } - } - } - } - }() - // Try creating new sessions - go func() { - if cfg.EncryptionPublicKey != "533574224115f835b7c7db6433986bc5aef855ff9c9568c01abeb0fbed3e8810" { - return - } - time.Sleep(time.Second * 2) - conn, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") - if err != nil { - logger.Errorln("Dial:", err) - return - } - go func() { - for { - time.Sleep(time.Second * 2) - b := []byte{1, 2, 3, 4, 5} - if n, err := conn.Write(b); err != nil { - logger.Errorln("Write failed:", err) - } else { - logger.Println("Wrote", n, "bytes:", b) - b = make([]byte, 100) - if n, err := conn.Read(b); err != nil { - logger.Errorln("Read failed:", err) - } else { - logger.Println("Read", n, "bytes:", b) - } - } - } - }() - }() - */ // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() @@ -367,6 +304,8 @@ func main() { if *useconffile != "" { cfg = readConfig(useconf, useconffile, normaliseconf) n.core.UpdateConfig(cfg) + n.tuntap.UpdateConfig(cfg) + n.multicast.UpdateConfig(cfg) } else { logger.Errorln("Reloading config at runtime is only possible with -useconffile") } diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 4e5bc4b1..8d18889d 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -41,6 +41,10 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log go func() { for { e := <-m.reconfigure + // There's nothing particularly to do here because the multicast module + // already consults the config.NodeState when enumerating multicast + // interfaces on each pass. We just need to return nil so that the + // reconfiguration doesn't block indefinitely e <- nil } }() @@ -89,6 +93,36 @@ func (m *Multicast) Stop() error { return nil } +// UpdateConfig updates the multicast module with the provided config.NodeConfig +// and then signals the various module goroutines to reconfigure themselves if +// needed. +func (m *Multicast) UpdateConfig(config *config.NodeConfig) { + m.log.Debugln("Reloading multicast configuration...") + + m.config.Replace(*config) + + errors := 0 + + components := []chan chan error{ + m.reconfigure, + } + + for _, component := range components { + response := make(chan error) + component <- response + if err := <-response; err != nil { + m.log.Errorln(err) + errors++ + } + } + + if errors > 0 { + m.log.Warnln(errors, "multicast module(s) reported errors during configuration reload") + } else { + m.log.Infoln("Multicast configuration reloaded successfully") + } +} + func (m *Multicast) interfaces() map[string]net.Interface { // Get interface expressions from config current, _ := m.config.Get() diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index a73dde17..310e4211 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -148,6 +148,7 @@ func (tun *TunAdapter) Start() error { tun.mutex.Lock() tun.isOpen = true tun.send = make(chan []byte, 32) // TODO: is this a sensible value? + tun.reconfigure = make(chan chan error) tun.mutex.Unlock() if iftapmode { go func() { @@ -178,6 +179,37 @@ func (tun *TunAdapter) Start() error { return nil } +// UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig +// and then signals the various module goroutines to reconfigure themselves if +// needed. +func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) { + tun.log.Debugln("Reloading TUN/TAP configuration...") + + tun.config.Replace(*config) + + errors := 0 + + components := []chan chan error{ + tun.reconfigure, + tun.ckr.reconfigure, + } + + for _, component := range components { + response := make(chan error) + component <- response + if err := <-response; err != nil { + tun.log.Errorln(err) + errors++ + } + } + + if errors > 0 { + tun.log.Warnln(errors, "TUN/TAP module(s) reported errors during configuration reload") + } else { + tun.log.Infoln("TUN/TAP configuration reloaded successfully") + } +} + func (tun *TunAdapter) handler() error { for { // Accept the incoming connection diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index ac2b494f..63d33d91 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -114,7 +114,7 @@ func (c *Core) addPeerLoop() { // config.NodeConfig and then signals the various module goroutines to // reconfigure themselves if needed. func (c *Core) UpdateConfig(config *config.NodeConfig) { - c.log.Infoln("Reloading configuration...") + c.log.Debugln("Reloading node configuration...") c.config.Replace(*config) @@ -141,9 +141,9 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { } if errors > 0 { - c.log.Warnln(errors, "modules reported errors during configuration reload") + c.log.Warnln(errors, "node module(s) reported errors during configuration reload") } else { - c.log.Infoln("Configuration reloaded successfully") + c.log.Infoln("Node configuration reloaded successfully") } } From 2df62e2b9b4febec0e45e3050539b20d2b85f4d2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 17 May 2019 22:52:14 +0100 Subject: [PATCH 48/60] Remove code that translates v0.2 config options (it was commented out anyway) --- cmd/yggdrasil/main.go | 73 ------------------------------------------- 1 file changed, 73 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 52ddfe9c..ccb7061a 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -76,79 +76,6 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeCo panic(err) } json.Unmarshal(confJson, &cfg) - /* - // For now we will do a little bit to help the user adjust their - // configuration to match the new configuration format, as some of the key - // names have changed recently. - changes := map[string]string{ - "Multicast": "", - "ReadTimeout": "", - "LinkLocal": "MulticastInterfaces", - "BoxPub": "EncryptionPublicKey", - "BoxPriv": "EncryptionPrivateKey", - "SigPub": "SigningPublicKey", - "SigPriv": "SigningPrivateKey", - "AllowedBoxPubs": "AllowedEncryptionPublicKeys", - } - // Loop over the mappings aove and see if we have anything to fix. - for from, to := range changes { - if _, ok := dat[from]; ok { - if to == "" { - if !*normaliseconf { - log.Println("Warning: Config option", from, "is deprecated") - } - } else { - if !*normaliseconf { - log.Println("Warning: Config option", from, "has been renamed - please change to", to) - } - // If the configuration file doesn't already contain a line with the - // new name then set it to the old value. This makes sure that we - // don't overwrite something that was put there intentionally. - if _, ok := dat[to]; !ok { - dat[to] = dat[from] - } - } - } - } - // Check to see if the peers are in a parsable format, if not then default - // them to the TCP scheme - 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 - } - if strings.HasPrefix(uri, "tcp:") { - uri = uri[4:] - } - (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 - } - } - } - // Do a quick check for old-format Listen statement so that mapstructure - // doesn't fail and crash - if listen, ok := dat["Listen"].(string); ok { - if strings.HasPrefix(listen, "tcp://") { - dat["Listen"] = []string{listen} - } else { - dat["Listen"] = []string{"tcp://" + listen} - } - } - */ // Overlay our newly mapped configuration onto the autoconf node config that // we generated above. if err = mapstructure.Decode(dat, &cfg); err != nil { From 1b3ec0b93fa93ce5facd80ec30e6ccf09c00283e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 17 May 2019 22:59:29 +0100 Subject: [PATCH 49/60] Fix multicast start check so that it shouldn't give up if interfaces aren't up when Yggdrasil starts (fixes #405) --- src/multicast/multicast.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 8d18889d..cbde7fd9 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -60,7 +60,8 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log // listen for multicast beacons from other hosts and will advertise multicast // beacons out to the network. func (m *Multicast) Start() error { - if len(m.interfaces()) == 0 { + current, _ := m.config.Get() + if len(current.MulticastInterfaces) == 0 { m.log.Infoln("Multicast discovery is disabled") } else { m.log.Infoln("Multicast discovery is enabled") From ce606099064b33c150ff07458d63b53df4ec172e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 18 May 2019 16:16:32 +0100 Subject: [PATCH 50/60] Remove wrappedConn as unnecessary --- src/yggdrasil/dial.go | 58 ------------------------------------------- src/yggdrasil/tcp.go | 7 ------ 2 files changed, 65 deletions(-) delete mode 100644 src/yggdrasil/dial.go diff --git a/src/yggdrasil/dial.go b/src/yggdrasil/dial.go deleted file mode 100644 index 7aec4192..00000000 --- a/src/yggdrasil/dial.go +++ /dev/null @@ -1,58 +0,0 @@ -package yggdrasil - -import ( - "net" - "time" -) - -// wrappedConn implements net.Conn -type wrappedConn struct { - c net.Conn - raddr net.Addr -} - -// wrappedAddr implements net.Addr -type wrappedAddr struct { - network string - addr string -} - -func (a *wrappedAddr) Network() string { - return a.network -} - -func (a *wrappedAddr) String() string { - return a.addr -} - -func (c *wrappedConn) Write(data []byte) (int, error) { - return c.c.Write(data) -} - -func (c *wrappedConn) Read(data []byte) (int, error) { - return c.c.Read(data) -} - -func (c *wrappedConn) SetDeadline(t time.Time) error { - return c.c.SetDeadline(t) -} - -func (c *wrappedConn) SetReadDeadline(t time.Time) error { - return c.c.SetReadDeadline(t) -} - -func (c *wrappedConn) SetWriteDeadline(t time.Time) error { - return c.c.SetWriteDeadline(t) -} - -func (c *wrappedConn) Close() error { - return c.c.Close() -} - -func (c *wrappedConn) LocalAddr() net.Addr { - return c.c.LocalAddr() -} - -func (c *wrappedConn) RemoteAddr() net.Addr { - return c.raddr -} diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 4361ec82..dfb41510 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -255,13 +255,6 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { if err != nil { return } - conn = &wrappedConn{ - c: conn, - raddr: &wrappedAddr{ - network: "tcp", - addr: saddr, - }, - } t.handler(conn, false, dialerdst.String()) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) From 8a6f6f3b2bc9b11941cfcbff5b43c82ebcc1deeb Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 18 May 2019 17:21:02 +0100 Subject: [PATCH 51/60] Implement GetPeers and GetSwitchPeers API functions in Core, in preparation for breaking out the admin socket into a separate module --- src/yggdrasil/api.go | 341 ++++++++++++++++++++++++++++++++++++++++++ src/yggdrasil/core.go | 155 ------------------- 2 files changed, 341 insertions(+), 155 deletions(-) create mode 100644 src/yggdrasil/api.go diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go new file mode 100644 index 00000000..b5278102 --- /dev/null +++ b/src/yggdrasil/api.go @@ -0,0 +1,341 @@ +package yggdrasil + +import ( + "encoding/hex" + "errors" + "net" + "sort" + "sync/atomic" + "time" + + "github.com/gologme/log" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) + +// Peer represents a single peer object. This contains information from the +// preferred switch port for this peer, although there may be more than one in +// reality. +type Peer struct { + PublicKey crypto.BoxPubKey + Endpoint string + BytesSent uint64 + BytesRecvd uint64 + Protocol string + Port uint64 + Uptime time.Duration +} + +// SwitchPeer represents a switch connection to a peer. Note that there may be +// multiple switch peers per actual peer, e.g. if there are multiple connections +// to a given node. +type SwitchPeer struct { + PublicKey crypto.BoxPubKey + Coords []byte + BytesSent uint64 + BytesRecvd uint64 + Port uint64 + Protocol string +} + +type DHTEntry struct { + PublicKey crypto.BoxPubKey + Coords []byte + LastSeen time.Duration +} + +type SwitchQueue struct{} +type Session struct{} + +// GetPeers returns one or more Peer objects containing information about active +// peerings with other Yggdrasil nodes, where one of the responses always +// includes information about the current node (with a port number of 0). If +// there is exactly one entry then this node is not connected to any other nodes +// and is therefore isolated. +func (c *Core) GetPeers() []Peer { + ports := c.peers.ports.Load().(map[switchPort]*peer) + var peers []Peer + var ps []switchPort + for port := range ports { + ps = append(ps, port) + } + sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] }) + for _, port := range ps { + p := ports[port] + info := Peer{ + Endpoint: p.intf.name, + BytesSent: atomic.LoadUint64(&p.bytesSent), + BytesRecvd: atomic.LoadUint64(&p.bytesRecvd), + Protocol: p.intf.info.linkType, + Port: uint64(port), + Uptime: time.Since(p.firstSeen), + } + copy(info.PublicKey[:], p.box[:]) + peers = append(peers, info) + } + return peers +} + +// GetSwitchPeers returns zero or more SwitchPeer objects containing information +// about switch port connections with other Yggdrasil nodes. Note that, unlike +// GetPeers, GetSwitchPeers does not include information about the current node, +// therefore it is possible for this to return zero elements if the node is +// isolated or not connected to any peers. +func (c *Core) GetSwitchPeers() []SwitchPeer { + var switchpeers []SwitchPeer + table := c.switchTable.table.Load().(lookupTable) + peers := c.peers.ports.Load().(map[switchPort]*peer) + for _, elem := range table.elems { + peer, isIn := peers[elem.port] + if !isIn { + continue + } + coords := elem.locator.getCoords() + info := SwitchPeer{ + Coords: coords, + BytesSent: atomic.LoadUint64(&peer.bytesSent), + BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd), + Port: uint64(elem.port), + Protocol: peer.intf.info.linkType, + } + copy(info.PublicKey[:], peer.box[:]) + switchpeers = append(switchpeers, info) + } + return switchpeers +} + +func (c *Core) GetDHT() []DHTEntry { + /* + var infos []admin_nodeInfo + getDHT := func() { + now := time.Now() + var dhtInfos []*dhtInfo + for _, v := range a.core.dht.table { + dhtInfos = append(dhtInfos, v) + } + sort.SliceStable(dhtInfos, func(i, j int) bool { + return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID()) + }) + for _, v := range dhtInfos { + addr := *address.AddrForNodeID(v.getNodeID()) + info := admin_nodeInfo{ + {"ip", net.IP(addr[:]).String()}, + {"coords", fmt.Sprint(v.coords)}, + {"last_seen", int(now.Sub(v.recv).Seconds())}, + {"box_pub_key", hex.EncodeToString(v.key[:])}, + } + infos = append(infos, info) + } + } + a.core.router.doAdmin(getDHT) + return infos + */ + return []DHTEntry{} +} + +func (c *Core) GetSwitchQueues() []SwitchQueue { + /* + var peerInfos admin_nodeInfo + switchTable := &a.core.switchTable + getSwitchQueues := func() { + queues := make([]map[string]interface{}, 0) + for k, v := range switchTable.queues.bufs { + nexthop := switchTable.bestPortForCoords([]byte(k)) + queue := map[string]interface{}{ + "queue_id": k, + "queue_size": v.size, + "queue_packets": len(v.packets), + "queue_port": nexthop, + } + queues = append(queues, queue) + } + peerInfos = admin_nodeInfo{ + {"queues", queues}, + {"queues_count", len(switchTable.queues.bufs)}, + {"queues_size", switchTable.queues.size}, + {"highest_queues_count", switchTable.queues.maxbufs}, + {"highest_queues_size", switchTable.queues.maxsize}, + {"maximum_queues_size", switchTable.queueTotalMaxSize}, + } + } + a.core.switchTable.doAdmin(getSwitchQueues) + return peerInfos + */ + return []SwitchQueue{} +} + +func (c *Core) GetSessions() []Session { + /* + var infos []admin_nodeInfo + getSessions := func() { + for _, sinfo := range a.core.sessions.sinfos { + // TODO? skipped known but timed out sessions? + info := admin_nodeInfo{ + {"ip", net.IP(sinfo.theirAddr[:]).String()}, + {"coords", fmt.Sprint(sinfo.coords)}, + {"mtu", sinfo.getMTU()}, + {"was_mtu_fixed", sinfo.wasMTUFixed}, + {"bytes_sent", sinfo.bytesSent}, + {"bytes_recvd", sinfo.bytesRecvd}, + {"box_pub_key", hex.EncodeToString(sinfo.theirPermPub[:])}, + } + infos = append(infos, info) + } + } + a.core.router.doAdmin(getSessions) + return infos + */ + return []Session{} +} + +// BuildName gets the current build name. This is usually injected if built +// from git, or returns "unknown" otherwise. +func BuildName() string { + if buildName == "" { + return "unknown" + } + return buildName +} + +// BuildVersion gets the current build version. This is usually injected if +// built from git, or returns "unknown" otherwise. +func BuildVersion() string { + if buildVersion == "" { + return "unknown" + } + return buildVersion +} + +// ListenConn returns a listener for Yggdrasil session connections. +func (c *Core) ConnListen() (*Listener, error) { + c.sessions.listenerMutex.Lock() + defer c.sessions.listenerMutex.Unlock() + if c.sessions.listener != nil { + return nil, errors.New("a listener already exists") + } + c.sessions.listener = &Listener{ + core: c, + conn: make(chan *Conn), + close: make(chan interface{}), + } + return c.sessions.listener, nil +} + +// ConnDialer returns a dialer for Yggdrasil session connections. +func (c *Core) ConnDialer() (*Dialer, error) { + return &Dialer{ + core: c, + }, nil +} + +// ListenTCP starts a new TCP listener. The input URI should match that of the +// "Listen" configuration item, e.g. +// tcp://a.b.c.d:e +func (c *Core) ListenTCP(uri string) (*TcpListener, error) { + return c.link.tcp.listen(uri) +} + +// NewEncryptionKeys 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() (*crypto.BoxPubKey, *crypto.BoxPrivKey) { + return crypto.NewBoxKeys() +} + +// NewSigningKeys generates a new signing keypair. The signing keys are used to +// derive the structure of the spanning tree. +func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) { + return crypto.NewSigKeys() +} + +// NodeID gets the node ID. +func (c *Core) NodeID() *crypto.NodeID { + return crypto.GetNodeID(&c.boxPub) +} + +// TreeID gets the tree ID. +func (c *Core) TreeID() *crypto.TreeID { + return crypto.GetTreeID(&c.sigPub) +} + +// SigPubKey gets the node's signing public key. +func (c *Core) SigPubKey() string { + return hex.EncodeToString(c.sigPub[:]) +} + +// BoxPubKey gets the node's encryption public key. +func (c *Core) BoxPubKey() string { + return hex.EncodeToString(c.boxPub[:]) +} + +// Address gets the IPv6 address of the Yggdrasil node. This is always a /128 +// address. +func (c *Core) Address() *net.IP { + address := net.IP(address.AddrForNodeID(c.NodeID())[:]) + return &address +} + +// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a +// /64 subnet. +func (c *Core) Subnet() *net.IPNet { + subnet := address.SubnetForNodeID(c.NodeID())[:] + subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) + return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} +} + +// RouterAddresses returns the raw address and subnet types as used by the +// router +func (c *Core) RouterAddresses() (address.Address, address.Subnet) { + return c.router.addr, c.router.subnet +} + +// NodeInfo gets the currently configured nodeinfo. +func (c *Core) NodeInfo() nodeinfoPayload { + return c.router.nodeinfo.getNodeInfo() +} + +// SetNodeInfo the lcal nodeinfo. Note that nodeinfo can be any value or struct, +// it will be serialised into JSON automatically. +func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { + c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) +} + +// SetLogger 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) { + c.log = log +} + +// AddPeer adds a peer. This should be specified in the peer URI format, e.g.: +// tcp://a.b.c.d:e +// socks://a.b.c.d:e/f.g.h.i:j +// This adds the peer to the peer list, so that they will be called again if the +// connection drops. +func (c *Core) AddPeer(addr string, sintf string) error { + if err := c.CallPeer(addr, sintf); err != nil { + return err + } + c.config.Mutex.Lock() + if sintf == "" { + c.config.Current.Peers = append(c.config.Current.Peers, addr) + } else { + c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) + } + c.config.Mutex.Unlock() + return nil +} + +// CallPeer calls a peer once. This should be specified in the peer URI format, +// e.g.: +// tcp://a.b.c.d:e +// socks://a.b.c.d:e/f.g.h.i:j +// This does not add the peer to the peer list, so if the connection drops, the +// peer will not be called again automatically. +func (c *Core) CallPeer(addr string, sintf string) error { + return c.link.call(addr, sintf) +} + +// AddAllowedEncryptionPublicKey adds an allowed public key. This allow peerings +// to be restricted only to keys that you have selected. +func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { + return c.admin.addAllowedEncryptionPublicKey(boxStr) +} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 63d33d91..41bb11f7 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -2,14 +2,11 @@ package yggdrasil import ( "encoding/hex" - "errors" "io/ioutil" - "net" "time" "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) @@ -147,24 +144,6 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { } } -// BuildName gets the current build name. This is usually injected if built -// from git, or returns "unknown" otherwise. -func BuildName() string { - if buildName == "" { - return "unknown" - } - return buildName -} - -// BuildVersion gets the current build version. This is usually injected if -// built from git, or returns "unknown" otherwise. -func BuildVersion() string { - if buildVersion == "" { - return "unknown" - } - return buildVersion -} - // Start starts up Yggdrasil using the provided config.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, @@ -226,137 +205,3 @@ func (c *Core) Stop() { c.log.Infoln("Stopping...") c.admin.close() } - -// ListenConn returns a listener for Yggdrasil session connections. -func (c *Core) ConnListen() (*Listener, error) { - c.sessions.listenerMutex.Lock() - defer c.sessions.listenerMutex.Unlock() - if c.sessions.listener != nil { - return nil, errors.New("a listener already exists") - } - c.sessions.listener = &Listener{ - core: c, - conn: make(chan *Conn), - close: make(chan interface{}), - } - return c.sessions.listener, nil -} - -// ConnDialer returns a dialer for Yggdrasil session connections. -func (c *Core) ConnDialer() (*Dialer, error) { - return &Dialer{ - core: c, - }, nil -} - -// ListenTCP starts a new TCP listener. The input URI should match that of the -// "Listen" configuration item, e.g. -// tcp://a.b.c.d:e -func (c *Core) ListenTCP(uri string) (*TcpListener, error) { - return c.link.tcp.listen(uri) -} - -// NewEncryptionKeys 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() (*crypto.BoxPubKey, *crypto.BoxPrivKey) { - return crypto.NewBoxKeys() -} - -// NewSigningKeys generates a new signing keypair. The signing keys are used to -// derive the structure of the spanning tree. -func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) { - return crypto.NewSigKeys() -} - -// NodeID gets the node ID. -func (c *Core) NodeID() *crypto.NodeID { - return crypto.GetNodeID(&c.boxPub) -} - -// TreeID gets the tree ID. -func (c *Core) TreeID() *crypto.TreeID { - return crypto.GetTreeID(&c.sigPub) -} - -// SigPubKey gets the node's signing public key. -func (c *Core) SigPubKey() string { - return hex.EncodeToString(c.sigPub[:]) -} - -// BoxPubKey gets the node's encryption public key. -func (c *Core) BoxPubKey() string { - return hex.EncodeToString(c.boxPub[:]) -} - -// Address gets the IPv6 address of the Yggdrasil node. This is always a /128 -// address. -func (c *Core) Address() *net.IP { - address := net.IP(address.AddrForNodeID(c.NodeID())[:]) - return &address -} - -// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a -// /64 subnet. -func (c *Core) Subnet() *net.IPNet { - subnet := address.SubnetForNodeID(c.NodeID())[:] - subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) - return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} -} - -// RouterAddresses returns the raw address and subnet types as used by the -// router -func (c *Core) RouterAddresses() (address.Address, address.Subnet) { - return c.router.addr, c.router.subnet -} - -// NodeInfo gets the currently configured nodeinfo. -func (c *Core) NodeInfo() nodeinfoPayload { - return c.router.nodeinfo.getNodeInfo() -} - -// SetNodeInfo the lcal nodeinfo. Note that nodeinfo can be any value or struct, -// it will be serialised into JSON automatically. -func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { - c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) -} - -// SetLogger 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) { - c.log = log -} - -// AddPeer adds a peer. This should be specified in the peer URI format, e.g.: -// tcp://a.b.c.d:e -// socks://a.b.c.d:e/f.g.h.i:j -// This adds the peer to the peer list, so that they will be called again if the -// connection drops. -func (c *Core) AddPeer(addr string, sintf string) error { - if err := c.CallPeer(addr, sintf); err != nil { - return err - } - c.config.Mutex.Lock() - if sintf == "" { - c.config.Current.Peers = append(c.config.Current.Peers, addr) - } else { - c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) - } - c.config.Mutex.Unlock() - return nil -} - -// CallPeer calls a peer once. This should be specified in the peer URI format, -// e.g.: -// tcp://a.b.c.d:e -// socks://a.b.c.d:e/f.g.h.i:j -// This does not add the peer to the peer list, so if the connection drops, the -// peer will not be called again automatically. -func (c *Core) CallPeer(addr string, sintf string) error { - return c.link.call(addr, sintf) -} - -// AddAllowedEncryptionPublicKey adds an allowed public key. This allow peerings -// to be restricted only to keys that you have selected. -func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { - return c.admin.addAllowedEncryptionPublicKey(boxStr) -} From 7ca5a2533dae672b0d17f5cdc2a26123a3e3423b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 19 May 2019 16:29:04 +0100 Subject: [PATCH 52/60] Implement GetDHT, GetSwitchQueues, GetSessions --- src/yggdrasil/api.go | 178 ++++++++++++++++++++++++------------------- 1 file changed, 100 insertions(+), 78 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index b5278102..d41b5f5a 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -38,14 +38,44 @@ type SwitchPeer struct { Protocol string } +// DHTEntry represents a single DHT entry that has been learned or cached from +// DHT searches. type DHTEntry struct { PublicKey crypto.BoxPubKey Coords []byte LastSeen time.Duration } -type SwitchQueue struct{} -type Session struct{} +// SwitchQueues represents information from the switch related to link +// congestion and a list of switch queues created in response to congestion on a +// given link. +type SwitchQueues struct { + Queues []SwitchQueue + Count uint64 + Size uint64 + HighestCount uint64 + HighestSize uint64 + MaximumSize uint64 +} + +// SwitchQueue represents a single switch queue, which is created in response +// to congestion on a given link. +type SwitchQueue struct { + ID string + Size uint64 + Packets uint64 + Port uint64 +} + +// Session represents an open session with another node. +type Session struct { + PublicKey crypto.BoxPubKey + Coords []byte + BytesSent uint64 + BytesRecvd uint64 + MTU uint16 + WasMTUFixed bool +} // GetPeers returns one or more Peer objects containing information about active // peerings with other Yggdrasil nodes, where one of the responses always @@ -104,88 +134,80 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { return switchpeers } +// GetDHT returns zero or more entries as stored in the DHT, cached primarily +// from searches that have already taken place. func (c *Core) GetDHT() []DHTEntry { - /* - var infos []admin_nodeInfo - getDHT := func() { - now := time.Now() - var dhtInfos []*dhtInfo - for _, v := range a.core.dht.table { - dhtInfos = append(dhtInfos, v) - } - sort.SliceStable(dhtInfos, func(i, j int) bool { - return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID()) - }) - for _, v := range dhtInfos { - addr := *address.AddrForNodeID(v.getNodeID()) - info := admin_nodeInfo{ - {"ip", net.IP(addr[:]).String()}, - {"coords", fmt.Sprint(v.coords)}, - {"last_seen", int(now.Sub(v.recv).Seconds())}, - {"box_pub_key", hex.EncodeToString(v.key[:])}, - } - infos = append(infos, info) - } - } - a.core.router.doAdmin(getDHT) - return infos - */ - return []DHTEntry{} + var dhtentries []DHTEntry + getDHT := func() { + now := time.Now() + var dhtentry []*dhtInfo + for _, v := range c.dht.table { + dhtentry = append(dhtentry, v) + } + sort.SliceStable(dhtentry, func(i, j int) bool { + return dht_ordered(&c.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID()) + }) + for _, v := range dhtentry { + info := DHTEntry{ + Coords: v.coords, + LastSeen: now.Sub(v.recv), + } + copy(info.PublicKey[:], v.key[:]) + dhtentries = append(dhtentries, info) + } + } + c.router.doAdmin(getDHT) + return dhtentries } -func (c *Core) GetSwitchQueues() []SwitchQueue { - /* - var peerInfos admin_nodeInfo - switchTable := &a.core.switchTable - getSwitchQueues := func() { - queues := make([]map[string]interface{}, 0) - for k, v := range switchTable.queues.bufs { - nexthop := switchTable.bestPortForCoords([]byte(k)) - queue := map[string]interface{}{ - "queue_id": k, - "queue_size": v.size, - "queue_packets": len(v.packets), - "queue_port": nexthop, - } - queues = append(queues, queue) - } - peerInfos = admin_nodeInfo{ - {"queues", queues}, - {"queues_count", len(switchTable.queues.bufs)}, - {"queues_size", switchTable.queues.size}, - {"highest_queues_count", switchTable.queues.maxbufs}, - {"highest_queues_size", switchTable.queues.maxsize}, - {"maximum_queues_size", switchTable.queueTotalMaxSize}, - } - } - a.core.switchTable.doAdmin(getSwitchQueues) - return peerInfos - */ - return []SwitchQueue{} +// GetSwitchQueues returns information about the switch queues that are +// currently in effect. These values can change within an instant. +func (c *Core) GetSwitchQueues() SwitchQueues { + var switchqueues SwitchQueues + switchTable := &c.switchTable + getSwitchQueues := func() { + switchqueues = SwitchQueues{ + Count: uint64(len(switchTable.queues.bufs)), + Size: switchTable.queues.size, + HighestCount: uint64(switchTable.queues.maxbufs), + HighestSize: switchTable.queues.maxsize, + MaximumSize: switchTable.queueTotalMaxSize, + } + for k, v := range switchTable.queues.bufs { + nexthop := switchTable.bestPortForCoords([]byte(k)) + queue := SwitchQueue{ + ID: k, + Size: v.size, + Packets: uint64(len(v.packets)), + Port: uint64(nexthop), + } + switchqueues.Queues = append(switchqueues.Queues, queue) + } + + } + c.switchTable.doAdmin(getSwitchQueues) + return switchqueues } +// GetSessions returns a list of open sessions from this node to other nodes. func (c *Core) GetSessions() []Session { - /* - var infos []admin_nodeInfo - getSessions := func() { - for _, sinfo := range a.core.sessions.sinfos { - // TODO? skipped known but timed out sessions? - info := admin_nodeInfo{ - {"ip", net.IP(sinfo.theirAddr[:]).String()}, - {"coords", fmt.Sprint(sinfo.coords)}, - {"mtu", sinfo.getMTU()}, - {"was_mtu_fixed", sinfo.wasMTUFixed}, - {"bytes_sent", sinfo.bytesSent}, - {"bytes_recvd", sinfo.bytesRecvd}, - {"box_pub_key", hex.EncodeToString(sinfo.theirPermPub[:])}, - } - infos = append(infos, info) - } - } - a.core.router.doAdmin(getSessions) - return infos - */ - return []Session{} + var sessions []Session + getSessions := func() { + for _, sinfo := range c.sessions.sinfos { + // TODO? skipped known but timed out sessions? + session := Session{ + Coords: sinfo.coords, + MTU: sinfo.getMTU(), + BytesSent: sinfo.bytesSent, + BytesRecvd: sinfo.bytesRecvd, + WasMTUFixed: sinfo.wasMTUFixed, + } + copy(session.PublicKey[:], sinfo.theirPermPub[:]) + sessions = append(sessions, session) + } + } + c.router.doAdmin(getSessions) + return sessions } // BuildName gets the current build name. This is usually injected if built From 8ef1978cb1c0e1445ce51921084abdd518554178 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 19 May 2019 17:27:48 +0100 Subject: [PATCH 53/60] Start factoring out the admin socket into a separate module (not all functions implemented yet) --- cmd/yggdrasil/main.go | 7 + src/{yggdrasil => admin}/admin.go | 382 +++++++++--------------------- src/yggdrasil/api.go | 11 +- src/yggdrasil/core.go | 9 - 4 files changed, 123 insertions(+), 286 deletions(-) rename src/{yggdrasil => admin}/admin.go (70%) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index ccb7061a..756947d8 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -18,6 +18,7 @@ import ( "github.com/kardianos/minwinsvc" "github.com/mitchellh/mapstructure" + "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" @@ -31,6 +32,7 @@ type node struct { core Core tuntap tuntap.TunAdapter multicast multicast.Multicast + admin admin.AdminSocket } func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig { @@ -184,6 +186,11 @@ func main() { logger.Errorln("An error occurred during startup") panic(err) } + // Start the admin socket + n.admin.Init(&n.core, state, logger, nil) + if err := n.admin.Start(); err != nil { + logger.Errorln("An error occurred starting admin socket:", err) + } // Start the multicast interface n.multicast.Init(&n.core, state, logger, nil) if err := n.multicast.Start(); err != nil { diff --git a/src/yggdrasil/admin.go b/src/admin/admin.go similarity index 70% rename from src/yggdrasil/admin.go rename to src/admin/admin.go index a24a5852..a7e9ac72 100644 --- a/src/yggdrasil/admin.go +++ b/src/admin/admin.go @@ -1,27 +1,27 @@ -package yggdrasil +package admin import ( - "encoding/hex" "encoding/json" - "errors" "fmt" "net" "net/url" "os" - "sort" - "strconv" "strings" - "sync/atomic" "time" + "github.com/gologme/log" + "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/yggdrasil" ) // TODO: Add authentication -type admin struct { - core *Core +type AdminSocket struct { + core *yggdrasil.Core + log *log.Logger reconfigure chan chan error listenaddr string listener net.Listener @@ -46,27 +46,28 @@ type admin_pair struct { type admin_nodeInfo []admin_pair // addHandler is called for each admin function to add the handler and help documentation to the API. -func (a *admin) addHandler(name string, args []string, handler func(admin_info) (admin_info, error)) { +func (a *AdminSocket) addHandler(name string, args []string, handler func(admin_info) (admin_info, error)) { a.handlers = append(a.handlers, admin_handlerInfo{name, args, handler}) } // init runs the initial admin setup. -func (a *admin) init(c *Core) { +func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) { a.core = c + a.log = log a.reconfigure = make(chan chan error, 1) go func() { for { e := <-a.reconfigure - current, previous := a.core.config.Get() + current, previous := state.Get() if current.AdminListen != previous.AdminListen { a.listenaddr = current.AdminListen - a.close() - a.start() + a.Stop() + a.Start() } e <- nil } }() - current, _ := a.core.config.Get() + current, _ := state.Get() a.listenaddr = current.AdminListen a.addHandler("list", []string{}, func(in admin_info) (admin_info, error) { handlers := make(map[string]interface{}) @@ -75,64 +76,93 @@ func (a *admin) init(c *Core) { } return admin_info{"list": handlers}, nil }) - a.addHandler("dot", []string{}, func(in admin_info) (admin_info, error) { + /*a.addHandler("dot", []string{}, func(in admin_info) (admin_info, error) { return admin_info{"dot": string(a.getResponse_dot())}, nil - }) + })*/ a.addHandler("getSelf", []string{}, func(in admin_info) (admin_info, error) { - self := a.getData_getSelf().asMap() - ip := fmt.Sprint(self["ip"]) - delete(self, "ip") - return admin_info{"self": admin_info{ip: self}}, nil + ip := c.Address().String() + return admin_info{ + "self": admin_info{ + ip: admin_info{ + "box_pub_key": c.BoxPubKey(), + "build_name": yggdrasil.BuildName(), + "build_version": yggdrasil.BuildVersion(), + "coords": fmt.Sprintf("%v", c.Coords()), + "subnet": c.Subnet().String(), + }, + }, + }, nil }) a.addHandler("getPeers", []string{}, func(in admin_info) (admin_info, error) { - sort := "ip" peers := make(admin_info) - for _, peerdata := range a.getData_getPeers() { - p := peerdata.asMap() - so := fmt.Sprint(p[sort]) - peers[so] = p - delete(peers[so].(map[string]interface{}), sort) + for _, p := range a.core.GetPeers() { + addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey)) + so := net.IP(addr[:]).String() + peers[so] = admin_info{ + "ip": so, + "port": p.Port, + "uptime": p.Uptime.Seconds(), + "bytes_sent": p.BytesSent, + "bytes_recvd": p.BytesRecvd, + "proto": p.Protocol, + "endpoint": p.Endpoint, + "box_pub_key": p.PublicKey, + } } return admin_info{"peers": peers}, nil }) a.addHandler("getSwitchPeers", []string{}, func(in admin_info) (admin_info, error) { - sort := "port" switchpeers := make(admin_info) - for _, s := range a.getData_getSwitchPeers() { - p := s.asMap() - so := fmt.Sprint(p[sort]) - switchpeers[so] = p - delete(switchpeers[so].(map[string]interface{}), sort) + for _, s := range a.core.GetSwitchPeers() { + addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) + so := fmt.Sprint(s.Port) + switchpeers[so] = admin_info{ + "ip": net.IP(addr[:]).String(), + "coords": fmt.Sprintf("%v", s.Coords), + "port": s.Port, + "bytes_sent": s.BytesSent, + "bytes_recvd": s.BytesRecvd, + "proto": s.Protocol, + "endpoint": s.Endpoint, + "box_pub_key": s.PublicKey, + } } return admin_info{"switchpeers": switchpeers}, nil }) - a.addHandler("getSwitchQueues", []string{}, func(in admin_info) (admin_info, error) { - queues := a.getData_getSwitchQueues() + /*a.addHandler("getSwitchQueues", []string{}, func(in admin_info) (admin_info, error) { + queues := a.core.GetSwitchQueues() return admin_info{"switchqueues": queues.asMap()}, nil - }) + })*/ a.addHandler("getDHT", []string{}, func(in admin_info) (admin_info, error) { - sort := "ip" dht := make(admin_info) - for _, d := range a.getData_getDHT() { - p := d.asMap() - so := fmt.Sprint(p[sort]) - dht[so] = p - delete(dht[so].(map[string]interface{}), sort) + for _, d := range a.core.GetDHT() { + addr := *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey)) + so := net.IP(addr[:]).String() + dht[so] = admin_info{ + "coords": fmt.Sprintf("%v", d.Coords), + "last_seen": d.LastSeen.Seconds(), + "box_pub_key": d.PublicKey, + } } return admin_info{"dht": dht}, nil }) a.addHandler("getSessions", []string{}, func(in admin_info) (admin_info, error) { - sort := "ip" sessions := make(admin_info) - for _, s := range a.getData_getSessions() { - p := s.asMap() - so := fmt.Sprint(p[sort]) - sessions[so] = p - delete(sessions[so].(map[string]interface{}), sort) + for _, s := range a.core.GetSessions() { + addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) + so := net.IP(addr[:]).String() + sessions[so] = admin_info{ + "coords": fmt.Sprintf("%v", s.Coords), + "bytes_sent": s.BytesSent, + "bytes_recvd": s.BytesRecvd, + "mtu": s.MTU, + "was_mtu_fixed": s.WasMTUFixed, + "box_pub_key": s.PublicKey, + } } return admin_info{"sessions": sessions}, nil }) - a.addHandler("addPeer", []string{"uri", "[interface]"}, func(in admin_info) (admin_info, error) { + /*a.addHandler("addPeer", []string{"uri", "[interface]"}, func(in admin_info) (admin_info, error) { // Set sane defaults intf := "" // Has interface been specified? @@ -168,7 +198,7 @@ func (a *admin) init(c *Core) { }, errors.New("Failed to remove peer") } }) - /* a.addHandler("getTunTap", []string{}, func(in admin_info) (r admin_info, e error) { + a.addHandler("getTunTap", []string{}, func(in admin_info) (r admin_info, e error) { defer func() { if err := recover(); err != nil { r = admin_info{"none": admin_info{}} @@ -215,7 +245,7 @@ func (a *admin) init(c *Core) { intfs = append(intfs, v.Name) } return admin_info{"multicast_interfaces": intfs}, nil - })*/ + }) a.addHandler("getAllowedEncryptionPublicKeys", []string{}, func(in admin_info) (admin_info, error) { return admin_info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil }) @@ -249,7 +279,7 @@ func (a *admin) init(c *Core) { }, errors.New("Failed to remove allowed key") } }) - /*a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) { + a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) { enabled := false a.core.router.doAdmin(func() { enabled = a.core.router.cryptokey.isEnabled() @@ -335,7 +365,7 @@ func (a *admin) init(c *Core) { } else { return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") } - })*/ + }) a.addHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in admin_info) (admin_info, error) { if in["target"] == nil { in["target"] = "none" @@ -390,11 +420,11 @@ func (a *admin) init(c *Core) { } else { return admin_info{}, err } - }) + })*/ } // start runs the admin API socket to listen for / respond to admin API calls. -func (a *admin) start() error { +func (a *AdminSocket) Start() error { if a.listenaddr != "none" && a.listenaddr != "" { go a.listen() } @@ -402,7 +432,7 @@ func (a *admin) start() error { } // cleans up when stopping -func (a *admin) close() error { +func (a *AdminSocket) Stop() error { if a.listener != nil { return a.listener.Close() } else { @@ -411,21 +441,21 @@ func (a *admin) close() error { } // listen is run by start and manages API connections. -func (a *admin) listen() { +func (a *AdminSocket) listen() { u, err := url.Parse(a.listenaddr) if err == nil { switch strings.ToLower(u.Scheme) { case "unix": if _, err := os.Stat(a.listenaddr[7:]); err == nil { - a.core.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up") + a.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up") if _, err := net.DialTimeout("unix", a.listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() { - a.core.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process") + a.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process") os.Exit(1) } else { if err := os.Remove(a.listenaddr[7:]); err == nil { - a.core.log.Debugln(a.listenaddr[7:], "was cleaned up") + a.log.Debugln(a.listenaddr[7:], "was cleaned up") } else { - a.core.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err) + a.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err) os.Exit(1) } } @@ -436,7 +466,7 @@ func (a *admin) listen() { case "@": // maybe abstract namespace default: if err := os.Chmod(a.listenaddr[7:], 0660); err != nil { - a.core.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") + a.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") } } } @@ -450,10 +480,10 @@ func (a *admin) listen() { a.listener, err = net.Listen("tcp", a.listenaddr) } if err != nil { - a.core.log.Errorf("Admin socket failed to listen: %v", err) + a.log.Errorf("Admin socket failed to listen: %v", err) os.Exit(1) } - a.core.log.Infof("%s admin socket listening on %s", + a.log.Infof("%s admin socket listening on %s", strings.ToUpper(a.listener.Addr().Network()), a.listener.Addr().String()) defer a.listener.Close() @@ -466,7 +496,7 @@ func (a *admin) listen() { } // handleRequest calls the request handler for each request sent to the admin API. -func (a *admin) handleRequest(conn net.Conn) { +func (a *AdminSocket) handleRequest(conn net.Conn) { decoder := json.NewDecoder(conn) encoder := json.NewEncoder(conn) encoder.SetIndent("", " ") @@ -480,9 +510,9 @@ func (a *admin) handleRequest(conn net.Conn) { "status": "error", "error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax", } - a.core.log.Errorln("Admin socket error:", r) + a.log.Errorln("Admin socket error:", r) if err := encoder.Encode(&send); err != nil { - a.core.log.Errorln("Admin socket JSON encode error:", err) + a.log.Errorln("Admin socket JSON encode error:", err) } conn.Close() } @@ -577,7 +607,7 @@ func (n *admin_nodeInfo) toString() string { } // printInfos returns a newline separated list of strings from admin_nodeInfos, e.g. a printable string of info about all peers. -func (a *admin) printInfos(infos []admin_nodeInfo) string { +func (a *AdminSocket) printInfos(infos []admin_nodeInfo) string { var out []string for _, info := range infos { out = append(out, info.toString()) @@ -586,8 +616,9 @@ func (a *admin) printInfos(infos []admin_nodeInfo) string { return strings.Join(out, "\n") } +/* // addPeer triggers a connection attempt to a node. -func (a *admin) addPeer(addr string, sintf string) error { +func (a *AdminSocket) addPeer(addr string, sintf string) error { err := a.core.link.call(addr, sintf) if err != nil { return err @@ -596,220 +627,18 @@ func (a *admin) addPeer(addr string, sintf string) error { } // removePeer disconnects an existing node (given by the node's port number). -func (a *admin) removePeer(p string) error { +func (a *AdminSocket) removePeer(p string) error { iport, err := strconv.Atoi(p) if err != nil { return err } - a.core.peers.removePeer(switchPort(iport)) - return nil -} - -// 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.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.GetPrefix())-1) - if ifname != "none" { - err := a.core.router.tun.setup(ifname, iftapmode, straddr, ifmtu) - if err != nil { - return err - } - // If we have open sessions then we need to notify them - // that our MTU has now changed - for _, sinfo := range a.core.sessions.sinfos { - if ifname == "none" { - sinfo.myMTU = 0 - } else { - sinfo.myMTU = uint16(ifmtu) - } - a.core.sessions.sendPingPong(sinfo, false) - } - // Aaaaand... go! - go a.core.router.tun.read() - } - go a.core.router.tun.write() + a.core.RemovePeer(iport) return nil } */ - -// getData_getSelf returns the self node's info for admin responses. -func (a *admin) getData_getSelf() *admin_nodeInfo { - table := a.core.switchTable.table.Load().(lookupTable) - coords := table.self.getCoords() - nodeid := *crypto.GetNodeID(&a.core.boxPub) - self := admin_nodeInfo{ - {"node_id", hex.EncodeToString(nodeid[:])}, - {"box_pub_key", hex.EncodeToString(a.core.boxPub[:])}, - {"ip", a.core.Address().String()}, - {"subnet", a.core.Subnet().String()}, - {"coords", fmt.Sprint(coords)}, - } - if name := BuildName(); name != "unknown" { - self = append(self, admin_pair{"build_name", name}) - } - if version := BuildVersion(); version != "unknown" { - self = append(self, admin_pair{"build_version", version}) - } - - return &self -} - -// getData_getPeers returns info from Core.peers for an admin response. -func (a *admin) getData_getPeers() []admin_nodeInfo { - ports := a.core.peers.ports.Load().(map[switchPort]*peer) - var peerInfos []admin_nodeInfo - var ps []switchPort - for port := range ports { - ps = append(ps, port) - } - sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] }) - for _, port := range ps { - p := ports[port] - addr := *address.AddrForNodeID(crypto.GetNodeID(&p.box)) - info := admin_nodeInfo{ - {"ip", net.IP(addr[:]).String()}, - {"port", port}, - {"uptime", int(time.Since(p.firstSeen).Seconds())}, - {"bytes_sent", atomic.LoadUint64(&p.bytesSent)}, - {"bytes_recvd", atomic.LoadUint64(&p.bytesRecvd)}, - {"proto", p.intf.info.linkType}, - {"endpoint", p.intf.name}, - {"box_pub_key", hex.EncodeToString(p.box[:])}, - } - peerInfos = append(peerInfos, info) - } - return peerInfos -} - -// getData_getSwitchPeers returns info from Core.switchTable for an admin response. -func (a *admin) getData_getSwitchPeers() []admin_nodeInfo { - var peerInfos []admin_nodeInfo - table := a.core.switchTable.table.Load().(lookupTable) - peers := a.core.peers.ports.Load().(map[switchPort]*peer) - for _, elem := range table.elems { - peer, isIn := peers[elem.port] - if !isIn { - continue - } - addr := *address.AddrForNodeID(crypto.GetNodeID(&peer.box)) - coords := elem.locator.getCoords() - info := admin_nodeInfo{ - {"ip", net.IP(addr[:]).String()}, - {"coords", fmt.Sprint(coords)}, - {"port", elem.port}, - {"bytes_sent", atomic.LoadUint64(&peer.bytesSent)}, - {"bytes_recvd", atomic.LoadUint64(&peer.bytesRecvd)}, - {"proto", peer.intf.info.linkType}, - {"endpoint", peer.intf.info.remote}, - {"box_pub_key", hex.EncodeToString(peer.box[:])}, - } - peerInfos = append(peerInfos, info) - } - return peerInfos -} - -// getData_getSwitchQueues returns info from Core.switchTable for an queue data. -func (a *admin) getData_getSwitchQueues() admin_nodeInfo { - var peerInfos admin_nodeInfo - switchTable := &a.core.switchTable - getSwitchQueues := func() { - queues := make([]map[string]interface{}, 0) - for k, v := range switchTable.queues.bufs { - nexthop := switchTable.bestPortForCoords([]byte(k)) - queue := map[string]interface{}{ - "queue_id": k, - "queue_size": v.size, - "queue_packets": len(v.packets), - "queue_port": nexthop, - } - queues = append(queues, queue) - } - peerInfos = admin_nodeInfo{ - {"queues", queues}, - {"queues_count", len(switchTable.queues.bufs)}, - {"queues_size", switchTable.queues.size}, - {"highest_queues_count", switchTable.queues.maxbufs}, - {"highest_queues_size", switchTable.queues.maxsize}, - {"maximum_queues_size", switchTable.queueTotalMaxSize}, - } - } - a.core.switchTable.doAdmin(getSwitchQueues) - return peerInfos -} - -// getData_getDHT returns info from Core.dht for an admin response. -func (a *admin) getData_getDHT() []admin_nodeInfo { - var infos []admin_nodeInfo - getDHT := func() { - now := time.Now() - var dhtInfos []*dhtInfo - for _, v := range a.core.dht.table { - dhtInfos = append(dhtInfos, v) - } - sort.SliceStable(dhtInfos, func(i, j int) bool { - return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID()) - }) - for _, v := range dhtInfos { - addr := *address.AddrForNodeID(v.getNodeID()) - info := admin_nodeInfo{ - {"ip", net.IP(addr[:]).String()}, - {"coords", fmt.Sprint(v.coords)}, - {"last_seen", int(now.Sub(v.recv).Seconds())}, - {"box_pub_key", hex.EncodeToString(v.key[:])}, - } - infos = append(infos, info) - } - } - a.core.router.doAdmin(getDHT) - return infos -} - -// getData_getSessions returns info from Core.sessions for an admin response. -func (a *admin) getData_getSessions() []admin_nodeInfo { - var infos []admin_nodeInfo - getSessions := func() { - for _, sinfo := range a.core.sessions.sinfos { - // TODO? skipped known but timed out sessions? - info := admin_nodeInfo{ - {"ip", net.IP(sinfo.theirAddr[:]).String()}, - {"coords", fmt.Sprint(sinfo.coords)}, - {"mtu", sinfo.getMTU()}, - {"was_mtu_fixed", sinfo.wasMTUFixed}, - {"bytes_sent", sinfo.bytesSent}, - {"bytes_recvd", sinfo.bytesRecvd}, - {"box_pub_key", hex.EncodeToString(sinfo.theirPermPub[:])}, - } - infos = append(infos, info) - } - } - a.core.router.doAdmin(getSessions) - return infos -} - -// getAllowedEncryptionPublicKeys returns the public keys permitted for incoming peer connections. -func (a *admin) getAllowedEncryptionPublicKeys() []string { - return a.core.peers.getAllowedEncryptionPublicKeys() -} - -// addAllowedEncryptionPublicKey whitelists a key for incoming peer connections. -func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) { - a.core.peers.addAllowedEncryptionPublicKey(bstr) - return nil -} - -// removeAllowedEncryptionPublicKey removes a key from the whitelist for incoming peer connections. -// If none are set, an empty list permits all incoming connections. -func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) { - a.core.peers.removeAllowedEncryptionPublicKey(bstr) - return nil -} - +/* // 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) { +func (a *AdminSocket) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) { var key crypto.BoxPubKey if keyBytes, err := hex.DecodeString(keyString); err != nil { return dhtRes{}, err @@ -866,7 +695,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_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) { +func (a *AdminSocket) admin_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) { var key crypto.BoxPubKey if keyBytes, err := hex.DecodeString(keyString); err != nil { return nodeinfoPayload{}, err @@ -915,7 +744,7 @@ func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) ( // 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. -func (a *admin) getResponse_dot() []byte { +func (a *AdminSocket) getResponse_dot() []byte { self := a.getData_getSelf() peers := a.getData_getSwitchPeers() dht := a.getData_getDHT() @@ -1037,3 +866,4 @@ func (a *admin) getResponse_dot() []byte { put("}\n") return out } +*/ diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index d41b5f5a..e7050c7b 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -36,6 +36,7 @@ type SwitchPeer struct { BytesRecvd uint64 Port uint64 Protocol string + Endpoint string } // DHTEntry represents a single DHT entry that has been learned or cached from @@ -127,6 +128,7 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd), Port: uint64(elem.port), Protocol: peer.intf.info.linkType, + Endpoint: peer.intf.info.remote, } copy(info.PublicKey[:], peer.box[:]) switchpeers = append(switchpeers, info) @@ -289,6 +291,12 @@ func (c *Core) BoxPubKey() string { return hex.EncodeToString(c.boxPub[:]) } +// Coords returns the current coordinates of the node. +func (c *Core) Coords() []byte { + table := c.switchTable.table.Load().(lookupTable) + return table.self.getCoords() +} + // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 // address. func (c *Core) Address() *net.IP { @@ -359,5 +367,6 @@ func (c *Core) CallPeer(addr string, sintf string) error { // AddAllowedEncryptionPublicKey adds an allowed public key. This allow peerings // to be restricted only to keys that you have selected. func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { - return c.admin.addAllowedEncryptionPublicKey(boxStr) + //return c.admin.addAllowedEncryptionPublicKey(boxStr) + return nil } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 41bb11f7..3a7f9f1b 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -30,7 +30,6 @@ type Core struct { sessions sessions router router dht dht - admin admin searches searches link link log *log.Logger @@ -69,7 +68,6 @@ func (c *Core) init() error { copy(c.sigPub[:], sigPubHex) copy(c.sigPriv[:], sigPrivHex) - c.admin.init(c) c.searches.init(c) c.dht.init(c) c.sessions.init(c) @@ -118,7 +116,6 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { errors := 0 components := []chan chan error{ - c.admin.reconfigure, c.searches.reconfigure, c.dht.reconfigure, c.sessions.reconfigure, @@ -189,11 +186,6 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, return nil, err } - if err := c.admin.start(); err != nil { - c.log.Errorln("Failed to start admin socket") - return nil, err - } - go c.addPeerLoop() c.log.Infoln("Startup complete") @@ -203,5 +195,4 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, // Stop shuts down the Yggdrasil node. func (c *Core) Stop() { c.log.Infoln("Stopping...") - c.admin.close() } From d575b83ec190837e555da17cb5ba286675d5d2b4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 19 May 2019 22:02:04 +0100 Subject: [PATCH 54/60] Refactor admin socket somewhat, allow modules to set up their own handlers --- cmd/yggdrasil/main.go | 2 + src/admin/admin.go | 628 +++++++++++++++-------------------------- src/multicast/admin.go | 13 + src/tuntap/admin.go | 136 +++++++++ 4 files changed, 379 insertions(+), 400 deletions(-) create mode 100644 src/multicast/admin.go create mode 100644 src/tuntap/admin.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 756947d8..5dba3a51 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -196,6 +196,7 @@ func main() { if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } + n.multicast.SetupAdminHandlers(&n.admin) // Start the TUN/TAP interface if listener, err := n.core.ConnListen(); err == nil { if dialer, err := n.core.ConnDialer(); err == nil { @@ -203,6 +204,7 @@ func main() { if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } + n.tuntap.SetupAdminHandlers(&n.admin) } else { logger.Errorln("Unable to get Dialer:", err) } diff --git a/src/admin/admin.go b/src/admin/admin.go index a7e9ac72..ff5476e4 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -2,6 +2,7 @@ package admin import ( "encoding/json" + "errors" "fmt" "net" "net/url" @@ -25,29 +26,27 @@ type AdminSocket struct { reconfigure chan chan error listenaddr string listener net.Listener - handlers []admin_handlerInfo + handlers map[string]handler } -type admin_info map[string]interface{} +// Info refers to information that is returned to the admin socket handler. +type Info map[string]interface{} -type admin_handlerInfo struct { - name string // Checked against the first word of the api call - args []string // List of human-readable argument names - handler func(admin_info) (admin_info, error) // First is input map, second is output +type handler struct { + args []string // List of human-readable argument names + handler func(Info) (Info, error) // First is input map, second is output } -// admin_pair maps things like "IP", "port", "bucket", or "coords" onto values. -type admin_pair struct { - key string - val interface{} -} - -// admin_nodeInfo represents the information we know about a node for an admin response. -type admin_nodeInfo []admin_pair - -// addHandler is called for each admin function to add the handler and help documentation to the API. -func (a *AdminSocket) addHandler(name string, args []string, handler func(admin_info) (admin_info, error)) { - a.handlers = append(a.handlers, admin_handlerInfo{name, args, handler}) +// AddHandler is called for each admin function to add the handler and help documentation to the API. +func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(Info) (Info, error)) error { + if _, ok := a.handlers[strings.ToLower(name)]; ok { + return errors.New("handler already exists") + } + a.handlers[strings.ToLower(name)] = handler{ + args: args, + handler: handlerfunc, + } + return nil } // init runs the initial admin setup. @@ -55,6 +54,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. a.core = c a.log = log a.reconfigure = make(chan chan error, 1) + a.handlers = make(map[string]handler) go func() { for { e := <-a.reconfigure @@ -69,21 +69,23 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. }() current, _ := state.Get() a.listenaddr = current.AdminListen - a.addHandler("list", []string{}, func(in admin_info) (admin_info, error) { + a.AddHandler("list", []string{}, func(in Info) (Info, error) { handlers := make(map[string]interface{}) - for _, handler := range a.handlers { - handlers[handler.name] = admin_info{"fields": handler.args} + for handlername, handler := range a.handlers { + handlers[handlername] = Info{"fields": handler.args} } - return admin_info{"list": handlers}, nil + return Info{"list": handlers}, nil }) - /*a.addHandler("dot", []string{}, func(in admin_info) (admin_info, error) { - return admin_info{"dot": string(a.getResponse_dot())}, nil - })*/ - a.addHandler("getSelf", []string{}, func(in admin_info) (admin_info, error) { + /* + a.AddHandler("dot", []string{}, func(in Info) (Info, error) { + return Info{"dot": string(a.getResponse_dot())}, nil + }) + */ + a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { ip := c.Address().String() - return admin_info{ - "self": admin_info{ - ip: admin_info{ + return Info{ + "self": Info{ + ip: Info{ "box_pub_key": c.BoxPubKey(), "build_name": yggdrasil.BuildName(), "build_version": yggdrasil.BuildVersion(), @@ -93,12 +95,12 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. }, }, nil }) - a.addHandler("getPeers", []string{}, func(in admin_info) (admin_info, error) { - peers := make(admin_info) + a.AddHandler("getPeers", []string{}, func(in Info) (Info, error) { + peers := make(Info) for _, p := range a.core.GetPeers() { addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey)) so := net.IP(addr[:]).String() - peers[so] = admin_info{ + peers[so] = Info{ "ip": so, "port": p.Port, "uptime": p.Uptime.Seconds(), @@ -109,14 +111,14 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "box_pub_key": p.PublicKey, } } - return admin_info{"peers": peers}, nil + return Info{"peers": peers}, nil }) - a.addHandler("getSwitchPeers", []string{}, func(in admin_info) (admin_info, error) { - switchpeers := make(admin_info) + a.AddHandler("getSwitchPeers", []string{}, func(in Info) (Info, error) { + switchpeers := make(Info) for _, s := range a.core.GetSwitchPeers() { addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) so := fmt.Sprint(s.Port) - switchpeers[so] = admin_info{ + switchpeers[so] = Info{ "ip": net.IP(addr[:]).String(), "coords": fmt.Sprintf("%v", s.Coords), "port": s.Port, @@ -127,31 +129,33 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "box_pub_key": s.PublicKey, } } - return admin_info{"switchpeers": switchpeers}, nil + return Info{"switchpeers": switchpeers}, nil }) - /*a.addHandler("getSwitchQueues", []string{}, func(in admin_info) (admin_info, error) { - queues := a.core.GetSwitchQueues() - return admin_info{"switchqueues": queues.asMap()}, nil - })*/ - a.addHandler("getDHT", []string{}, func(in admin_info) (admin_info, error) { - dht := make(admin_info) + /* + a.AddHandler("getSwitchQueues", []string{}, func(in Info) (Info, error) { + queues := a.core.GetSwitchQueues() + return Info{"switchqueues": queues.asMap()}, nil + }) + */ + a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) { + dht := make(Info) for _, d := range a.core.GetDHT() { addr := *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey)) so := net.IP(addr[:]).String() - dht[so] = admin_info{ + dht[so] = Info{ "coords": fmt.Sprintf("%v", d.Coords), "last_seen": d.LastSeen.Seconds(), "box_pub_key": d.PublicKey, } } - return admin_info{"dht": dht}, nil + return Info{"dht": dht}, nil }) - a.addHandler("getSessions", []string{}, func(in admin_info) (admin_info, error) { - sessions := make(admin_info) + a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) { + sessions := make(Info) for _, s := range a.core.GetSessions() { addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) so := net.IP(addr[:]).String() - sessions[so] = admin_info{ + sessions[so] = Info{ "coords": fmt.Sprintf("%v", s.Coords), "bytes_sent": s.BytesSent, "bytes_recvd": s.BytesRecvd, @@ -160,267 +164,134 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "box_pub_key": s.PublicKey, } } - return admin_info{"sessions": sessions}, nil + return Info{"sessions": sessions}, nil }) - /*a.addHandler("addPeer", []string{"uri", "[interface]"}, func(in admin_info) (admin_info, error) { - // Set sane defaults - intf := "" - // Has interface been specified? - if itf, ok := in["interface"]; ok { - intf = itf.(string) - } - if a.addPeer(in["uri"].(string), intf) == nil { - return admin_info{ - "added": []string{ - in["uri"].(string), - }, - }, nil - } else { - return admin_info{ - "not_added": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to add peer") - } - }) - a.addHandler("removePeer", []string{"port"}, func(in admin_info) (admin_info, error) { - if a.removePeer(fmt.Sprint(in["port"])) == nil { - return admin_info{ - "removed": []string{ - fmt.Sprint(in["port"]), - }, - }, nil - } else { - return admin_info{ - "not_removed": []string{ - fmt.Sprint(in["port"]), - }, - }, errors.New("Failed to remove peer") - } - }) - a.addHandler("getTunTap", []string{}, func(in admin_info) (r admin_info, e error) { - defer func() { - if err := recover(); err != nil { - r = admin_info{"none": admin_info{}} - e = nil - } - }() - - return admin_info{ - a.core.router.tun.iface.Name(): admin_info{ - "tap_mode": a.core.router.tun.iface.IsTAP(), - "mtu": a.core.router.tun.mtu, - }, - }, nil - }) - a.addHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in admin_info) (admin_info, error) { + /* + a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { // Set sane defaults - iftapmode := defaults.GetDefaults().DefaultIfTAPMode - ifmtu := defaults.GetDefaults().DefaultIfMTU - // Has TAP mode been specified? - if tap, ok := in["tap_mode"]; ok { - iftapmode = tap.(bool) + intf := "" + // Has interface been specified? + if itf, ok := in["interface"]; ok { + intf = itf.(string) } - // Check we have enough params for MTU - if mtu, ok := in["mtu"]; ok { - if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { - ifmtu = int(in["mtu"].(float64)) - } - } - // Start the TUN adapter - if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { - return admin_info{}, errors.New("Failed to configure adapter") - } else { - return admin_info{ - a.core.router.tun.iface.Name(): admin_info{ - "tap_mode": a.core.router.tun.iface.IsTAP(), - "mtu": ifmtu, + if a.addPeer(in["uri"].(string), intf) == nil { + return Info{ + "added": []string{ + in["uri"].(string), }, }, nil - } - })*/ - /*a.addHandler("getMulticastInterfaces", []string{}, func(in admin_info) (admin_info, error) { - var intfs []string - for _, v := range a.core.multicast.interfaces() { - intfs = append(intfs, v.Name) - } - return admin_info{"multicast_interfaces": intfs}, nil - }) - a.addHandler("getAllowedEncryptionPublicKeys", []string{}, func(in admin_info) (admin_info, error) { - return admin_info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil - }) - a.addHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in admin_info) (admin_info, error) { - if a.addAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { - return admin_info{ - "added": []string{ - in["box_pub_key"].(string), - }, - }, nil - } else { - return admin_info{ - "not_added": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to add allowed key") - } - }) - a.addHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in admin_info) (admin_info, error) { - if a.removeAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { - return admin_info{ - "removed": []string{ - in["box_pub_key"].(string), - }, - }, nil - } else { - return admin_info{ - "not_removed": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to remove allowed key") - } - }) - a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) { - enabled := false - a.core.router.doAdmin(func() { - enabled = a.core.router.cryptokey.isEnabled() - }) - return admin_info{"enabled": enabled}, nil - }) - a.addHandler("setTunnelRouting", []string{"enabled"}, func(in admin_info) (admin_info, error) { - enabled := false - if e, ok := in["enabled"].(bool); ok { - enabled = e - } - a.core.router.doAdmin(func() { - a.core.router.cryptokey.setEnabled(enabled) - }) - return admin_info{"enabled": enabled}, nil - }) - a.addHandler("addSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string)) - }) - if err == nil { - return admin_info{"added": []string{in["subnet"].(string)}}, nil - } else { - return admin_info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") - } - }) - a.addHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin_info) (admin_info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["box_pub_key"].(string)) - }) - if err == nil { - return admin_info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return admin_info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") - } - }) - a.addHandler("getSourceSubnets", []string{}, func(in admin_info) (admin_info, error) { - var subnets []string - a.core.router.doAdmin(func() { - getSourceSubnets := func(snets []net.IPNet) { - for _, subnet := range snets { - subnets = append(subnets, subnet.String()) - } - } - getSourceSubnets(a.core.router.cryptokey.ipv4sources) - getSourceSubnets(a.core.router.cryptokey.ipv6sources) - }) - return admin_info{"source_subnets": subnets}, nil - }) - a.addHandler("getRoutes", []string{}, func(in admin_info) (admin_info, error) { - routes := make(admin_info) - a.core.router.doAdmin(func() { - getRoutes := func(ckrs []cryptokey_route) { - for _, ckr := range ckrs { - routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) - } - } - getRoutes(a.core.router.cryptokey.ipv4routes) - getRoutes(a.core.router.cryptokey.ipv6routes) - }) - return admin_info{"routes": routes}, nil - }) - a.addHandler("removeSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string)) - }) - if err == nil { - return admin_info{"removed": []string{in["subnet"].(string)}}, nil - } else { - return admin_info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") - } - }) - a.addHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin_info) (admin_info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)) - }) - if err == nil { - return admin_info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") - } - }) - a.addHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in admin_info) (admin_info, error) { - if in["target"] == nil { - in["target"] = "none" - } - result, err := a.admin_dhtPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string)) - if err == nil { - infos := make(map[string]map[string]string, len(result.Infos)) - for _, dinfo := range result.Infos { - info := map[string]string{ - "box_pub_key": hex.EncodeToString(dinfo.key[:]), - "coords": fmt.Sprintf("%v", dinfo.coords), - } - addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.key))[:]).String() - infos[addr] = info - } - return admin_info{"nodes": infos}, nil - } else { - return admin_info{}, err - } - }) - 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" - } - var box_pub_key, coords string - if in["box_pub_key"] == nil && in["coords"] == nil { - var nodeinfo []byte - a.core.router.doAdmin(func() { - nodeinfo = []byte(a.core.router.nodeinfo.getNodeInfo()) - }) - var jsoninfo interface{} - if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { - return admin_info{}, err } else { - return admin_info{"nodeinfo": jsoninfo}, nil + return Info{ + "not_added": []string{ + in["uri"].(string), + }, + }, errors.New("Failed to add peer") } - } else if in["box_pub_key"] == nil || in["coords"] == nil { - return admin_info{}, errors.New("Expecting both box_pub_key and coords") - } else { - box_pub_key = in["box_pub_key"].(string) - coords = in["coords"].(string) - } - result, err := a.admin_getNodeInfo(box_pub_key, coords, nocache) - if err == nil { - var m map[string]interface{} - if err = json.Unmarshal(result, &m); err == nil { - return admin_info{"nodeinfo": m}, nil + }) + a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) { + if a.removePeer(fmt.Sprint(in["port"])) == nil { + return Info{ + "removed": []string{ + fmt.Sprint(in["port"]), + }, + }, nil } else { - return admin_info{}, err + return Info{ + "not_removed": []string{ + fmt.Sprint(in["port"]), + }, + }, errors.New("Failed to remove peer") } - } else { - return admin_info{}, err - } - })*/ + }) + a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { + return Info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil + }) + a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { + if a.addAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + return Info{ + "added": []string{ + in["box_pub_key"].(string), + }, + }, nil + } else { + return Info{ + "not_added": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to add allowed key") + } + }) + a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { + if a.removeAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + return Info{ + "removed": []string{ + in["box_pub_key"].(string), + }, + }, nil + } else { + return Info{ + "not_removed": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to remove allowed key") + } + }) + a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { + if in["target"] == nil { + in["target"] = "none" + } + result, err := a.admin_dhtPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string)) + if err == nil { + infos := make(map[string]map[string]string, len(result.Infos)) + for _, dinfo := range result.Infos { + info := map[string]string{ + "box_pub_key": hex.EncodeToString(dinfo.key[:]), + "coords": fmt.Sprintf("%v", dinfo.coords), + } + addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.key))[:]).String() + infos[addr] = info + } + return Info{"nodes": infos}, nil + } else { + return Info{}, err + } + }) + a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) { + var nocache bool + if in["nocache"] != nil { + nocache = in["nocache"].(string) == "true" + } + var box_pub_key, coords string + if in["box_pub_key"] == nil && in["coords"] == nil { + var nodeinfo []byte + a.core.router.doAdmin(func() { + nodeinfo = []byte(a.core.router.nodeinfo.getNodeInfo()) + }) + var jsoninfo interface{} + if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { + return Info{}, err + } else { + return Info{"nodeinfo": jsoninfo}, nil + } + } else if in["box_pub_key"] == nil || in["coords"] == nil { + return Info{}, errors.New("Expecting both box_pub_key and coords") + } else { + box_pub_key = in["box_pub_key"].(string) + coords = in["coords"].(string) + } + result, err := a.admin_getNodeInfo(box_pub_key, coords, nocache) + if err == nil { + var m map[string]interface{} + if err = json.Unmarshal(result, &m); err == nil { + return Info{"nodeinfo": m}, nil + } else { + return Info{}, err + } + } else { + return Info{}, err + } + }) + */ } // start runs the admin API socket to listen for / respond to admin API calls. @@ -500,13 +371,13 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { decoder := json.NewDecoder(conn) encoder := json.NewEncoder(conn) encoder.SetIndent("", " ") - recv := make(admin_info) - send := make(admin_info) + recv := make(Info) + send := make(Info) defer func() { r := recover() if r != nil { - send = admin_info{ + send = Info{ "status": "error", "error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax", } @@ -520,8 +391,8 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { for { // Start with a clean slate on each request - recv = admin_info{} - send = admin_info{} + recv = Info{} + send = Info{} // Decode the input if err := decoder.Decode(&recv); err != nil { @@ -534,44 +405,46 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { send["request"] = recv send["status"] = "error" - handlers: - for _, handler := range a.handlers { - // We've found the handler that matches the request - if strings.ToLower(recv["request"].(string)) == strings.ToLower(handler.name) { - // Check that we have all the required arguments - for _, arg := range handler.args { - // An argument in [square brackets] is optional and not required, - // so we can safely ignore those - if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") { - continue - } - // Check if the field is missing - if _, ok := recv[arg]; !ok { - send = admin_info{ - "status": "error", - "error": "Expected field missing: " + arg, - "expecting": arg, - } - break handlers - } - } + if _, ok := recv["request"]; !ok { + send["error"] = "No request sent" + break + } - // By this point we should have all the fields we need, so call - // the handler - response, err := handler.handler(recv) - if err != nil { - send["error"] = err.Error() - if response != nil { - send["response"] = response - } - } else { - send["status"] = "success" - if response != nil { - send["response"] = response - } - } + n := strings.ToLower(recv["request"].(string)) + if h, ok := a.handlers[strings.ToLower(n)]; ok { + fmt.Println("HANDLER FOUND", n, h) - break + // Check that we have all the required arguments + for _, arg := range h.args { + // An argument in [square brackets] is optional and not required, + // so we can safely ignore those + if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") { + continue + } + // Check if the field is missing + if _, ok := recv[arg]; !ok { + send = Info{ + "status": "error", + "error": "Expected field missing: " + arg, + "expecting": arg, + } + break + } + } + + // By this point we should have all the fields we need, so call + // the handler + response, err := h.handler(recv) + if err != nil { + send["error"] = err.Error() + if response != nil { + send["response"] = response + } + } else { + send["status"] = "success" + if response != nil { + send["response"] = response + } } } @@ -587,55 +460,6 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { } } -// asMap converts an admin_nodeInfo into a map of key/value pairs. -func (n *admin_nodeInfo) asMap() map[string]interface{} { - m := make(map[string]interface{}, len(*n)) - for _, p := range *n { - m[p.key] = p.val - } - return m -} - -// toString creates a printable string representation of an admin_nodeInfo. -func (n *admin_nodeInfo) toString() string { - // TODO return something nicer looking than this - var out []string - for _, p := range *n { - out = append(out, fmt.Sprintf("%v: %v", p.key, p.val)) - } - return strings.Join(out, ", ") -} - -// printInfos returns a newline separated list of strings from admin_nodeInfos, e.g. a printable string of info about all peers. -func (a *AdminSocket) printInfos(infos []admin_nodeInfo) string { - var out []string - for _, info := range infos { - out = append(out, info.toString()) - } - out = append(out, "") // To add a trailing "\n" in the join - return strings.Join(out, "\n") -} - -/* -// addPeer triggers a connection attempt to a node. -func (a *AdminSocket) addPeer(addr string, sintf string) error { - err := a.core.link.call(addr, sintf) - if err != nil { - return err - } - return nil -} - -// removePeer disconnects an existing node (given by the node's port number). -func (a *AdminSocket) removePeer(p string) error { - iport, err := strconv.Atoi(p) - if err != nil { - return err - } - a.core.RemovePeer(iport) - return nil -} -*/ /* // Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. func (a *AdminSocket) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) { @@ -740,21 +564,25 @@ func (a *AdminSocket) admin_getNodeInfo(keyString, coordString string, nocache b } 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. -// 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. +// 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. +/* func (a *AdminSocket) getResponse_dot() []byte { - self := a.getData_getSelf() - peers := a.getData_getSwitchPeers() - dht := a.getData_getDHT() - sessions := a.getData_getSessions() + //self := a.getData_getSelf() + peers := a.core.GetSwitchPeers() + dht := a.core.GetDHT() + sessions := a.core.GetSessions() // Start building a tree from all known nodes type nodeInfo struct { name string key string parent string - port switchPort + port uint64 options string } infos := make(map[string]nodeInfo) @@ -782,7 +610,7 @@ func (a *AdminSocket) getResponse_dot() []byte { portStr := coordsSplit[len(coordsSplit)-1] portUint, err := strconv.ParseUint(portStr, 10, 64) if err == nil { - info.port = switchPort(portUint) + info.port = portUint } } infos[info.key] = info @@ -811,7 +639,7 @@ func (a *AdminSocket) getResponse_dot() []byte { portStr := coordsSplit[len(coordsSplit)-1] portUint, err := strconv.ParseUint(portStr, 10, 64) if err == nil { - newInfo.port = switchPort(portUint) + newInfo.port = portUint } } diff --git a/src/multicast/admin.go b/src/multicast/admin.go new file mode 100644 index 00000000..672b7ca4 --- /dev/null +++ b/src/multicast/admin.go @@ -0,0 +1,13 @@ +package multicast + +import "github.com/yggdrasil-network/yggdrasil-go/src/admin" + +func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { + a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) { + var intfs []string + for _, v := range m.interfaces() { + intfs = append(intfs, v.Name) + } + return admin.Info{"multicast_interfaces": intfs}, nil + }) +} diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go new file mode 100644 index 00000000..1d0b5876 --- /dev/null +++ b/src/tuntap/admin.go @@ -0,0 +1,136 @@ +package tuntap + +import "github.com/yggdrasil-network/yggdrasil-go/src/admin" + +func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { + a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) { + defer func() { + if err := recover(); err != nil { + r = admin.Info{"none": admin.Info{}} + e = nil + } + }() + + return admin.Info{ + t.iface.Name(): admin.Info{ + "tap_mode": t.iface.IsTAP(), + "mtu": t.mtu, + }, + }, nil + }) + /* + a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) { + // Set sane defaults + iftapmode := defaults.GetDefaults().DefaultIfTAPMode + ifmtu := defaults.GetDefaults().DefaultIfMTU + // Has TAP mode been specified? + if tap, ok := in["tap_mode"]; ok { + iftapmode = tap.(bool) + } + // Check we have enough params for MTU + if mtu, ok := in["mtu"]; ok { + if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { + ifmtu = int(in["mtu"].(float64)) + } + } + // Start the TUN adapter + if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { + return Info{}, errors.New("Failed to configure adapter") + } else { + return Info{ + a.core.router.tun.iface.Name(): Info{ + "tap_mode": a.core.router.tun.iface.IsTAP(), + "mtu": ifmtu, + }, + }, nil + } + }) + a.AddHandler("getTunnelRouting", []string{}, func(in Info) (Info, error) { + enabled := false + a.core.router.doAdmin(func() { + enabled = a.core.router.cryptokey.isEnabled() + }) + return Info{"enabled": enabled}, nil + }) + a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in Info) (Info, error) { + enabled := false + if e, ok := in["enabled"].(bool); ok { + enabled = e + } + a.core.router.doAdmin(func() { + a.core.router.cryptokey.setEnabled(enabled) + }) + return Info{"enabled": enabled}, nil + }) + a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in Info) (Info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string)) + }) + if err == nil { + return Info{"added": []string{in["subnet"].(string)}}, nil + } else { + return Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") + } + }) + a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in Info) (Info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["box_pub_key"].(string)) + }) + if err == nil { + return Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil + } else { + return Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") + } + }) + a.AddHandler("getSourceSubnets", []string{}, func(in Info) (Info, error) { + var subnets []string + a.core.router.doAdmin(func() { + getSourceSubnets := func(snets []net.IPNet) { + for _, subnet := range snets { + subnets = append(subnets, subnet.String()) + } + } + getSourceSubnets(a.core.router.cryptokey.ipv4sources) + getSourceSubnets(a.core.router.cryptokey.ipv6sources) + }) + return Info{"source_subnets": subnets}, nil + }) + a.AddHandler("getRoutes", []string{}, func(in Info) (Info, error) { + routes := make(Info) + a.core.router.doAdmin(func() { + getRoutes := func(ckrs []cryptokey_route) { + for _, ckr := range ckrs { + routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) + } + } + getRoutes(a.core.router.cryptokey.ipv4routes) + getRoutes(a.core.router.cryptokey.ipv6routes) + }) + return Info{"routes": routes}, nil + }) + a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in Info) (Info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string)) + }) + if err == nil { + return Info{"removed": []string{in["subnet"].(string)}}, nil + } else { + return Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") + } + }) + a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in Info) (Info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)) + }) + if err == nil { + return Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil + } else { + return Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") + } + }) + */ +} From e9e2d7bc6fffb7f29e86222d299106ec12d19f96 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 19 May 2019 22:03:20 +0100 Subject: [PATCH 55/60] Remove debug println --- src/admin/admin.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index ff5476e4..7cf5f330 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -396,7 +396,7 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { // Decode the input if err := decoder.Decode(&recv); err != nil { - // fmt.Println("Admin socket JSON decode error:", err) + a.log.Debugln("Admin socket JSON decode error:", err) return } @@ -412,8 +412,6 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { n := strings.ToLower(recv["request"].(string)) if h, ok := a.handlers[strings.ToLower(n)]; ok { - fmt.Println("HANDLER FOUND", n, h) - // Check that we have all the required arguments for _, arg := range h.args { // An argument in [square brackets] is optional and not required, From 5b8d8a9341480e36f31c81104ecaa9f7564c9b53 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 20 May 2019 19:51:44 +0100 Subject: [PATCH 56/60] Reimplement getNodeInfo, dhtPing, get/add/removeAllowedEncryptionPublicKey, add/removePeer --- src/admin/admin.go | 351 +++++++++++++++---------------------------- src/yggdrasil/api.go | 178 +++++++++++++++++++++- 2 files changed, 293 insertions(+), 236 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 7cf5f330..27dabdcc 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -1,12 +1,14 @@ package admin import ( + "encoding/hex" "encoding/json" "errors" "fmt" "net" "net/url" "os" + "strconv" "strings" "time" @@ -166,132 +168,131 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. } return Info{"sessions": sessions}, nil }) - /* - a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { - // Set sane defaults - intf := "" - // Has interface been specified? - if itf, ok := in["interface"]; ok { - intf = itf.(string) - } - if a.addPeer(in["uri"].(string), intf) == nil { - return Info{ - "added": []string{ - in["uri"].(string), - }, - }, nil - } else { - return Info{ - "not_added": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to add peer") - } - }) - a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) { - if a.removePeer(fmt.Sprint(in["port"])) == nil { - return Info{ - "removed": []string{ - fmt.Sprint(in["port"]), - }, - }, nil - } else { - return Info{ - "not_removed": []string{ - fmt.Sprint(in["port"]), - }, - }, errors.New("Failed to remove peer") - } - }) - a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { - return Info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil - }) - a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { - if a.addAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { - return Info{ - "added": []string{ - in["box_pub_key"].(string), - }, - }, nil - } else { - return Info{ - "not_added": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to add allowed key") - } - }) - a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { - if a.removeAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { - return Info{ - "removed": []string{ - in["box_pub_key"].(string), - }, - }, nil - } else { - return Info{ - "not_removed": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to remove allowed key") - } - }) - a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { - if in["target"] == nil { - in["target"] = "none" - } - result, err := a.admin_dhtPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string)) - if err == nil { - infos := make(map[string]map[string]string, len(result.Infos)) - for _, dinfo := range result.Infos { - info := map[string]string{ - "box_pub_key": hex.EncodeToString(dinfo.key[:]), - "coords": fmt.Sprintf("%v", dinfo.coords), - } - addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.key))[:]).String() - infos[addr] = info + a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { + // Set sane defaults + intf := "" + // Has interface been specified? + if itf, ok := in["interface"]; ok { + intf = itf.(string) + } + if a.core.AddPeer(in["uri"].(string), intf) == nil { + return Info{ + "added": []string{ + in["uri"].(string), + }, + }, nil + } else { + return Info{ + "not_added": []string{ + in["uri"].(string), + }, + }, errors.New("Failed to add peer") + } + }) + a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) { + port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64) + if err != nil { + return Info{}, err + } + if a.core.DisconnectPeer(uint64(port)) == nil { + return Info{ + "removed": []string{ + fmt.Sprint(port), + }, + }, nil + } else { + return Info{ + "not_removed": []string{ + fmt.Sprint(port), + }, + }, errors.New("Failed to remove peer") + } + }) + a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { + return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil + }) + a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { + if a.core.AddAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + return Info{ + "added": []string{ + in["box_pub_key"].(string), + }, + }, nil + } else { + return Info{ + "not_added": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to add allowed key") + } + }) + a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { + if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + return Info{ + "removed": []string{ + in["box_pub_key"].(string), + }, + }, nil + } else { + return Info{ + "not_removed": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to remove allowed key") + } + }) + a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { + if in["target"] == nil { + in["target"] = "none" + } + result, err := a.core.DHTPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string)) + if err == nil { + infos := make(map[string]map[string]string, len(result.Infos)) + for _, dinfo := range result.Infos { + info := map[string]string{ + "box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]), + "coords": fmt.Sprintf("%v", dinfo.Coords), } - return Info{"nodes": infos}, nil + addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String() + infos[addr] = info + } + return Info{"nodes": infos}, nil + } else { + return Info{}, err + } + }) + a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) { + var nocache bool + if in["nocache"] != nil { + nocache = in["nocache"].(string) == "true" + } + var box_pub_key, coords string + if in["box_pub_key"] == nil && in["coords"] == nil { + nodeinfo := a.core.MyNodeInfo() + var jsoninfo interface{} + if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { + return Info{}, err + } else { + return Info{"nodeinfo": jsoninfo}, nil + } + } else if in["box_pub_key"] == nil || in["coords"] == nil { + return Info{}, errors.New("Expecting both box_pub_key and coords") + } else { + box_pub_key = in["box_pub_key"].(string) + coords = in["coords"].(string) + } + result, err := a.core.GetNodeInfo(box_pub_key, coords, nocache) + if err == nil { + var m map[string]interface{} + if err = json.Unmarshal(result, &m); err == nil { + return Info{"nodeinfo": m}, nil } else { return Info{}, err } - }) - a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) { - var nocache bool - if in["nocache"] != nil { - nocache = in["nocache"].(string) == "true" - } - var box_pub_key, coords string - if in["box_pub_key"] == nil && in["coords"] == nil { - var nodeinfo []byte - a.core.router.doAdmin(func() { - nodeinfo = []byte(a.core.router.nodeinfo.getNodeInfo()) - }) - var jsoninfo interface{} - if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { - return Info{}, err - } else { - return Info{"nodeinfo": jsoninfo}, nil - } - } else if in["box_pub_key"] == nil || in["coords"] == nil { - return Info{}, errors.New("Expecting both box_pub_key and coords") - } else { - box_pub_key = in["box_pub_key"].(string) - coords = in["coords"].(string) - } - result, err := a.admin_getNodeInfo(box_pub_key, coords, nocache) - if err == nil { - var m map[string]interface{} - if err = json.Unmarshal(result, &m); err == nil { - return Info{"nodeinfo": m}, nil - } else { - return Info{}, err - } - } else { - return Info{}, err - } - }) - */ + } else { + return Info{}, err + } + }) } // start runs the admin API socket to listen for / respond to admin API calls. @@ -458,112 +459,6 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { } } -/* -// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. -func (a *AdminSocket) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) { - var key crypto.BoxPubKey - if keyBytes, err := hex.DecodeString(keyString); err != nil { - return dhtRes{}, 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 dhtRes{}, err - } else { - coords = append(coords, uint8(u64)) - } - } - resCh := make(chan *dhtRes, 1) - info := dhtInfo{ - key: key, - coords: coords, - } - target := *info.getNodeID() - if targetString == "none" { - // Leave the default target in place - } else if targetBytes, err := hex.DecodeString(targetString); err != nil { - return dhtRes{}, err - } else if len(targetBytes) != len(target) { - return dhtRes{}, errors.New("Incorrect target NodeID length") - } else { - var target crypto.NodeID - copy(target[:], targetBytes) - } - rq := dhtReqKey{info.key, target} - sendPing := func() { - a.core.dht.addCallback(&rq, func(res *dhtRes) { - defer func() { recover() }() - select { - case resCh <- res: - default: - } - }) - a.core.dht.ping(&info, &target) - } - a.core.router.doAdmin(sendPing) - go func() { - time.Sleep(6 * time.Second) - close(resCh) - }() - for res := range resCh { - return *res, nil - } - return dhtRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString)) -} - -func (a *AdminSocket) admin_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) { - var key crypto.BoxPubKey - if keyBytes, err := hex.DecodeString(keyString); err != nil { - return nodeinfoPayload{}, err - } else { - copy(key[:], keyBytes) - } - if !nocache { - if response, err := a.core.router.nodeinfo.getCachedNodeInfo(key); err == nil { - return response, nil - } - } - 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 nodeinfoPayload{}, err - } else { - coords = append(coords, uint8(u64)) - } - } - response := make(chan *nodeinfoPayload, 1) - sendNodeInfoRequest := func() { - a.core.router.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) { - defer func() { recover() }() - select { - case response <- nodeinfo: - default: - } - }) - a.core.router.nodeinfo.sendNodeInfo(key, coords, false) - } - a.core.router.doAdmin(sendNodeInfoRequest) - go func() { - time.Sleep(6 * time.Second) - close(response) - }() - for res := range response { - return *res, nil - } - 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. This is color-coded and // labeled, and includes the self node, switch peers, nodes known to the DHT, diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index e7050c7b..40e5a2b2 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -3,8 +3,11 @@ package yggdrasil import ( "encoding/hex" "errors" + "fmt" "net" "sort" + "strconv" + "strings" "sync/atomic" "time" @@ -47,6 +50,17 @@ type DHTEntry struct { LastSeen time.Duration } +// DHTRes represents a DHT response, as returned by DHTPing. +type DHTRes struct { + PublicKey crypto.BoxPubKey // key of the sender + Coords []byte // coords of the sender + Dest crypto.NodeID // the destination node ID + Infos []DHTEntry // response +} + +// NodeInfoPayload represents a RequestNodeInfo response, in bytes. +type NodeInfoPayload nodeinfoPayload + // SwitchQueues represents information from the switch related to link // congestion and a list of switch queues created in response to congestion on a // given link. @@ -123,7 +137,7 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { } coords := elem.locator.getCoords() info := SwitchPeer{ - Coords: coords, + Coords: append([]byte{}, coords...), BytesSent: atomic.LoadUint64(&peer.bytesSent), BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd), Port: uint64(elem.port), @@ -151,7 +165,7 @@ func (c *Core) GetDHT() []DHTEntry { }) for _, v := range dhtentry { info := DHTEntry{ - Coords: v.coords, + Coords: append([]byte{}, v.coords...), LastSeen: now.Sub(v.recv), } copy(info.PublicKey[:], v.key[:]) @@ -198,7 +212,7 @@ func (c *Core) GetSessions() []Session { for _, sinfo := range c.sessions.sinfos { // TODO? skipped known but timed out sessions? session := Session{ - Coords: sinfo.coords, + Coords: append([]byte{}, sinfo.coords...), MTU: sinfo.getMTU(), BytesSent: sinfo.bytesSent, BytesRecvd: sinfo.bytesRecvd, @@ -319,7 +333,7 @@ func (c *Core) RouterAddresses() (address.Address, address.Subnet) { } // NodeInfo gets the currently configured nodeinfo. -func (c *Core) NodeInfo() nodeinfoPayload { +func (c *Core) MyNodeInfo() nodeinfoPayload { return c.router.nodeinfo.getNodeInfo() } @@ -329,6 +343,56 @@ func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) } +// GetNodeInfo requests nodeinfo from a remote node, as specified by the public +// key and coordinates specified. The third parameter specifies whether a cached +// result is acceptable - this results in less traffic being generated than is +// necessary when, e.g. crawling the network. +func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInfoPayload, error) { + var key crypto.BoxPubKey + if keyBytes, err := hex.DecodeString(keyString); err != nil { + return NodeInfoPayload{}, err + } else { + copy(key[:], keyBytes) + } + if !nocache { + if response, err := c.router.nodeinfo.getCachedNodeInfo(key); err == nil { + return NodeInfoPayload(response), nil + } + } + 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 NodeInfoPayload{}, err + } else { + coords = append(coords, uint8(u64)) + } + } + response := make(chan *nodeinfoPayload, 1) + sendNodeInfoRequest := func() { + c.router.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) { + defer func() { recover() }() + select { + case response <- nodeinfo: + default: + } + }) + c.router.nodeinfo.sendNodeInfo(key, coords, false) + } + c.router.doAdmin(sendNodeInfoRequest) + go func() { + time.Sleep(6 * time.Second) + close(response) + }() + for res := range response { + return NodeInfoPayload(*res), nil + } + return NodeInfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString)) +} + // SetLogger 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) { @@ -354,6 +418,14 @@ func (c *Core) AddPeer(addr string, sintf string) error { return nil } +// RemovePeer is not implemented yet. +func (c *Core) RemovePeer(addr string, sintf string) error { + // TODO: Implement a reverse of AddPeer, where we look up the port number + // based on the addr and sintf, disconnect it and then remove it from the + // peers list so we don't reconnect to it later + return errors.New("not implemented") +} + // CallPeer calls a peer once. This should be specified in the peer URI format, // e.g.: // tcp://a.b.c.d:e @@ -364,9 +436,99 @@ func (c *Core) CallPeer(addr string, sintf string) error { return c.link.call(addr, sintf) } -// AddAllowedEncryptionPublicKey adds an allowed public key. This allow peerings -// to be restricted only to keys that you have selected. -func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { - //return c.admin.addAllowedEncryptionPublicKey(boxStr) +// DisconnectPeer disconnects a peer once. This should be specified as a port +// number. +func (c *Core) DisconnectPeer(port uint64) error { + c.peers.removePeer(switchPort(port)) return nil } + +// GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming +// peer connections. +func (c *Core) GetAllowedEncryptionPublicKeys() []string { + return c.peers.getAllowedEncryptionPublicKeys() +} + +// AddAllowedEncryptionPublicKey whitelists a key for incoming peer connections. +func (c *Core) AddAllowedEncryptionPublicKey(bstr string) (err error) { + c.peers.addAllowedEncryptionPublicKey(bstr) + return nil +} + +// RemoveAllowedEncryptionPublicKey removes a key from the whitelist for +// incoming peer connections. If none are set, an empty list permits all +// incoming connections. +func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) { + c.peers.removeAllowedEncryptionPublicKey(bstr) + return nil +} + +// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. +func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, error) { + var key crypto.BoxPubKey + if keyBytes, err := hex.DecodeString(keyString); err != nil { + return DHTRes{}, 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 DHTRes{}, err + } else { + coords = append(coords, uint8(u64)) + } + } + resCh := make(chan *dhtRes, 1) + info := dhtInfo{ + key: key, + coords: coords, + } + target := *info.getNodeID() + if targetString == "none" { + // Leave the default target in place + } else if targetBytes, err := hex.DecodeString(targetString); err != nil { + return DHTRes{}, err + } else if len(targetBytes) != len(target) { + return DHTRes{}, errors.New("Incorrect target NodeID length") + } else { + var target crypto.NodeID + copy(target[:], targetBytes) + } + rq := dhtReqKey{info.key, target} + sendPing := func() { + c.dht.addCallback(&rq, func(res *dhtRes) { + defer func() { recover() }() + select { + case resCh <- res: + default: + } + }) + c.dht.ping(&info, &target) + } + c.router.doAdmin(sendPing) + go func() { + time.Sleep(6 * time.Second) + close(resCh) + }() + // TODO: do something better than the below... + for res := range resCh { + r := DHTRes{ + Coords: append([]byte{}, res.Coords...), + } + copy(r.PublicKey[:], res.Key[:]) + for _, i := range res.Infos { + e := DHTEntry{ + Coords: append([]byte{}, i.coords...), + } + copy(e.PublicKey[:], i.key[:]) + r.Infos = append(r.Infos, e) + } + return r, nil + } + return DHTRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString)) +} From 70774fc3de52b2f72ba6b771a92ed9ff419ba536 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 20 May 2019 21:45:33 +0100 Subject: [PATCH 57/60] Reimplement get/setTunnelRouting, add/removeSourceSubnet, add/removeRoute, getRoutes, getSourceSubnets, make CKR threadsafe --- src/tuntap/admin.go | 209 ++++++++++++++++++++------------------------ src/tuntap/ckr.go | 76 +++++++++++++--- 2 files changed, 158 insertions(+), 127 deletions(-) diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 1d0b5876..21c7048d 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -1,6 +1,13 @@ package tuntap -import "github.com/yggdrasil-network/yggdrasil-go/src/admin" +import ( + "encoding/hex" + "errors" + "fmt" + "net" + + "github.com/yggdrasil-network/yggdrasil-go/src/admin" +) func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) { @@ -19,118 +26,94 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { }, nil }) /* - a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) { - // Set sane defaults - iftapmode := defaults.GetDefaults().DefaultIfTAPMode - ifmtu := defaults.GetDefaults().DefaultIfMTU - // Has TAP mode been specified? - if tap, ok := in["tap_mode"]; ok { - iftapmode = tap.(bool) - } - // Check we have enough params for MTU - if mtu, ok := in["mtu"]; ok { - if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { - ifmtu = int(in["mtu"].(float64)) - } - } - // Start the TUN adapter - if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { - return Info{}, errors.New("Failed to configure adapter") - } else { - return Info{ - a.core.router.tun.iface.Name(): Info{ - "tap_mode": a.core.router.tun.iface.IsTAP(), - "mtu": ifmtu, - }, - }, nil - } - }) - a.AddHandler("getTunnelRouting", []string{}, func(in Info) (Info, error) { - enabled := false - a.core.router.doAdmin(func() { - enabled = a.core.router.cryptokey.isEnabled() - }) - return Info{"enabled": enabled}, nil - }) - a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in Info) (Info, error) { - enabled := false - if e, ok := in["enabled"].(bool); ok { - enabled = e + // TODO: rewrite this as I'm fairly sure it doesn't work right on many + // platforms anyway, but it may require changes to Water + a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) { + // Set sane defaults + iftapmode := defaults.GetDefaults().DefaultIfTAPMode + ifmtu := defaults.GetDefaults().DefaultIfMTU + // Has TAP mode been specified? + if tap, ok := in["tap_mode"]; ok { + iftapmode = tap.(bool) + } + // Check we have enough params for MTU + if mtu, ok := in["mtu"]; ok { + if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { + ifmtu = int(in["mtu"].(float64)) } - a.core.router.doAdmin(func() { - a.core.router.cryptokey.setEnabled(enabled) - }) - return Info{"enabled": enabled}, nil - }) - a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string)) - }) - if err == nil { - return Info{"added": []string{in["subnet"].(string)}}, nil - } else { - return Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") - } - }) - a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["box_pub_key"].(string)) - }) - if err == nil { - return Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") - } - }) - a.AddHandler("getSourceSubnets", []string{}, func(in Info) (Info, error) { - var subnets []string - a.core.router.doAdmin(func() { - getSourceSubnets := func(snets []net.IPNet) { - for _, subnet := range snets { - subnets = append(subnets, subnet.String()) - } - } - getSourceSubnets(a.core.router.cryptokey.ipv4sources) - getSourceSubnets(a.core.router.cryptokey.ipv6sources) - }) - return Info{"source_subnets": subnets}, nil - }) - a.AddHandler("getRoutes", []string{}, func(in Info) (Info, error) { - routes := make(Info) - a.core.router.doAdmin(func() { - getRoutes := func(ckrs []cryptokey_route) { - for _, ckr := range ckrs { - routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) - } - } - getRoutes(a.core.router.cryptokey.ipv4routes) - getRoutes(a.core.router.cryptokey.ipv6routes) - }) - return Info{"routes": routes}, nil - }) - a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string)) - }) - if err == nil { - return Info{"removed": []string{in["subnet"].(string)}}, nil - } else { - return Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") - } - }) - a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)) - }) - if err == nil { - return Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") - } - }) + } + // Start the TUN adapter + if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { + return Info{}, errors.New("Failed to configure adapter") + } else { + return Info{ + a.core.router.tun.iface.Name(): Info{ + "tap_mode": a.core.router.tun.iface.IsTAP(), + "mtu": ifmtu, + }, + }, nil + } + }) */ + a.AddHandler("getTunnelRouting", []string{}, func(in admin.Info) (admin.Info, error) { + return admin.Info{"enabled": t.ckr.isEnabled()}, nil + }) + a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in admin.Info) (admin.Info, error) { + enabled := false + if e, ok := in["enabled"].(bool); ok { + enabled = e + } + t.ckr.setEnabled(enabled) + return admin.Info{"enabled": enabled}, nil + }) + a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.addSourceSubnet(in["subnet"].(string)); err == nil { + return admin.Info{"added": []string{in["subnet"].(string)}}, nil + } else { + return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") + } + }) + a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.addRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { + return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil + } else { + return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") + } + }) + a.AddHandler("getSourceSubnets", []string{}, func(in admin.Info) (admin.Info, error) { + var subnets []string + getSourceSubnets := func(snets []net.IPNet) { + for _, subnet := range snets { + subnets = append(subnets, subnet.String()) + } + } + getSourceSubnets(t.ckr.ipv4sources) + getSourceSubnets(t.ckr.ipv6sources) + return admin.Info{"source_subnets": subnets}, nil + }) + a.AddHandler("getRoutes", []string{}, func(in admin.Info) (admin.Info, error) { + routes := make(admin.Info) + getRoutes := func(ckrs []cryptokey_route) { + for _, ckr := range ckrs { + routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) + } + } + getRoutes(t.ckr.ipv4routes) + getRoutes(t.ckr.ipv6routes) + return admin.Info{"routes": routes}, nil + }) + a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.removeSourceSubnet(in["subnet"].(string)); err == nil { + return admin.Info{"removed": []string{in["subnet"].(string)}}, nil + } else { + return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") + } + }) + a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { + return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil + } else { + return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") + } + }) } diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 971f0a35..c996c397 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -7,6 +7,8 @@ import ( "fmt" "net" "sort" + "sync" + "sync/atomic" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -16,15 +18,18 @@ import ( // allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. type cryptokey struct { - tun *TunAdapter - enabled bool - reconfigure chan chan error - ipv4routes []cryptokey_route - ipv6routes []cryptokey_route - ipv4cache map[address.Address]cryptokey_route - ipv6cache map[address.Address]cryptokey_route - ipv4sources []net.IPNet - ipv6sources []net.IPNet + tun *TunAdapter + enabled atomic.Value // bool + reconfigure chan chan error + ipv4routes []cryptokey_route + ipv6routes []cryptokey_route + ipv4cache map[address.Address]cryptokey_route + ipv6cache map[address.Address]cryptokey_route + ipv4sources []net.IPNet + ipv6sources []net.IPNet + mutexroutes sync.RWMutex + mutexcaches sync.RWMutex + mutexsources sync.RWMutex } type cryptokey_route struct { @@ -58,8 +63,10 @@ func (c *cryptokey) configure() error { c.setEnabled(c.tun.config.Current.TunnelRouting.Enable) // Clear out existing routes + c.mutexroutes.Lock() c.ipv6routes = make([]cryptokey_route, 0) c.ipv4routes = make([]cryptokey_route, 0) + c.mutexroutes.Unlock() // Add IPv6 routes for ipv6, pubkey := range c.tun.config.Current.TunnelRouting.IPv6Destinations { @@ -76,8 +83,10 @@ func (c *cryptokey) configure() error { } // Clear out existing sources + c.mutexsources.Lock() c.ipv6sources = make([]net.IPNet, 0) c.ipv4sources = make([]net.IPNet, 0) + c.mutexsources.Unlock() // Add IPv6 sources c.ipv6sources = make([]net.IPNet, 0) @@ -96,26 +105,31 @@ func (c *cryptokey) configure() error { } // Wipe the caches + c.mutexcaches.Lock() c.ipv4cache = make(map[address.Address]cryptokey_route, 0) c.ipv6cache = make(map[address.Address]cryptokey_route, 0) + c.mutexcaches.Unlock() return nil } // Enable or disable crypto-key routing. func (c *cryptokey) setEnabled(enabled bool) { - c.enabled = enabled + c.enabled.Store(true) } // Check if crypto-key routing is enabled. func (c *cryptokey) isEnabled() bool { - return c.enabled + return c.enabled.Load().(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.Address, addrlen int) bool { + c.mutexsources.RLock() + defer c.mutexsources.RUnlock() + ip := net.IP(addr[:addrlen]) if addrlen == net.IPv6len { @@ -158,6 +172,9 @@ func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { // Adds a source subnet, which allows traffic with these source addresses to // be tunnelled using crypto-key routing. func (c *cryptokey) addSourceSubnet(cidr string) error { + c.mutexsources.Lock() + defer c.mutexsources.Unlock() + // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -195,6 +212,11 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { // Adds a destination route for the given CIDR to be tunnelled to the node // with the given BoxPubKey. func (c *cryptokey) addRoute(cidr string, dest string) error { + c.mutexroutes.Lock() + c.mutexcaches.Lock() + defer c.mutexroutes.Unlock() + defer c.mutexcaches.Unlock() + // Is the CIDR we've been given valid? ipaddr, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -269,6 +291,8 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { // 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.Address, addrlen int) (crypto.BoxPubKey, error) { + c.mutexcaches.RLock() + // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking if addr.IsValid() { @@ -281,10 +305,8 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Check if the prefix is IPv4 or IPv6 if addrlen == net.IPv6len { - routingtable = &c.ipv6routes routingcache = &c.ipv6cache } else if addrlen == net.IPv4len { - routingtable = &c.ipv4routes routingcache = &c.ipv4cache } else { return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") @@ -292,9 +314,24 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Check if there's a cache entry for this addr if route, ok := (*routingcache)[addr]; ok { + c.mutexcaches.RUnlock() return route.destination, nil } + c.mutexcaches.RUnlock() + + c.mutexroutes.RLock() + defer c.mutexroutes.RUnlock() + + // Check if the prefix is IPv4 or IPv6 + if addrlen == net.IPv6len { + routingtable = &c.ipv6routes + } else if addrlen == net.IPv4len { + routingtable = &c.ipv4routes + } else { + return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") + } + // No cache was found - start by converting the address into a net.IP ip := make(net.IP, addrlen) copy(ip[:addrlen], addr[:]) @@ -304,6 +341,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c for _, route := range *routingtable { // Does this subnet match the given IP? if route.subnet.Contains(ip) { + c.mutexcaches.Lock() + defer c.mutexcaches.Unlock() + // Check if the routing cache is above a certain size, if it is evict // a random entry so we can make room for this one. We take advantage // of the fact that the iteration order is random here @@ -329,6 +369,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Removes a source subnet, which allows traffic with these source addresses to // be tunnelled using crypto-key routing. func (c *cryptokey) removeSourceSubnet(cidr string) error { + c.mutexsources.Lock() + defer c.mutexsources.Unlock() + // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -364,6 +407,11 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error { // Removes a destination route for the given CIDR to be tunnelled to the node // with the given BoxPubKey. func (c *cryptokey) removeRoute(cidr string, dest string) error { + c.mutexroutes.Lock() + c.mutexcaches.Lock() + defer c.mutexroutes.Unlock() + defer c.mutexcaches.Unlock() + // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -403,7 +451,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error { for k := range *routingcache { delete(*routingcache, k) } - c.tun.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest) + c.tun.log.Infof("Removed CKR destination subnet %s via %s\n", cidr, dest) return nil } } From 5ea864869a4064ee33b2e973b9663fd5b01b887f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 23 May 2019 20:27:52 -0500 Subject: [PATCH 58/60] don't spam searches for unused connections. todo: timeout old connections somehow --- src/tuntap/conn.go | 13 +++++++++++ src/tuntap/tun.go | 16 ++++++------- src/yggdrasil/conn.go | 54 ++++++++++++++++++++----------------------- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 50860f36..ce4645f3 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -3,6 +3,7 @@ package tuntap import ( "errors" + "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -10,11 +11,21 @@ import ( type tunConn struct { tun *TunAdapter conn *yggdrasil.Conn + addr address.Address + snet address.Subnet send chan []byte stop chan interface{} } func (s *tunConn) close() { + s.tun.mutex.Lock() + s._close_nomutex() + s.tun.mutex.Unlock() +} + +func (s *tunConn) _close_nomutex() { + delete(s.tun.addrToConn, s.addr) + delete(s.tun.subnetToConn, s.snet) close(s.stop) } @@ -32,6 +43,7 @@ func (s *tunConn) reader() error { b := make([]byte, 65535) for { go func() { + // TODO read timeout and close if n, err = s.conn.Read(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) return @@ -72,6 +84,7 @@ func (s *tunConn) writer() error { if !ok { return errors.New("send closed") } + // TODO write timeout and close if _, err := s.conn.Write(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 310e4211..b9ff04ec 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -236,26 +236,26 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { } // Get the remote address and subnet of the other side remoteNodeID := conn.RemoteAddr() - remoteAddr := address.AddrForNodeID(&remoteNodeID) - remoteSubnet := address.SubnetForNodeID(&remoteNodeID) + s.addr = *address.AddrForNodeID(&remoteNodeID) + s.snet = *address.SubnetForNodeID(&remoteNodeID) // Work out if this is already a destination we already know about tun.mutex.Lock() defer tun.mutex.Unlock() - atc, aok := tun.addrToConn[*remoteAddr] - stc, sok := tun.subnetToConn[*remoteSubnet] + atc, aok := tun.addrToConn[s.addr] + stc, sok := tun.subnetToConn[s.snet] // If we know about a connection for this destination already then assume it // is no longer valid and close it if aok { - atc.close() + atc._close_nomutex() err = errors.New("replaced connection for address") } else if sok { - stc.close() + stc._close_nomutex() err = errors.New("replaced connection for subnet") } // Save the session wrapper so that we can look it up quickly next time // we receive a packet through the interface for this address - tun.addrToConn[*remoteAddr] = &s - tun.subnetToConn[*remoteSubnet] = &s + tun.addrToConn[s.addr] = &s + tun.subnetToConn[s.snet] = &s // Start the connection goroutines go s.reader() go s.writer() diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index aad1dcdd..1290ad0d 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -37,6 +37,7 @@ type Conn struct { writeDeadline atomic.Value // time.Time // TODO timer searching atomic.Value // bool searchwait chan struct{} // Never reset this, it's only used for the initial search + writebuf [][]byte // Packets to be sent if/when the search finishes } // TODO func NewConn() that initializes additional fields as needed @@ -60,23 +61,13 @@ func (c *Conn) String() string { func (c *Conn) startSearch() { // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { + defer c.searching.Store(false) // If the search failed for some reason, e.g. it hit a dead end or timed // out, then do nothing if err != nil { c.core.log.Debugln(c.String(), "DHT search failed:", err) - go func() { - time.Sleep(time.Second) - c.mutex.RLock() - closed := c.closed - c.mutex.RUnlock() - if !closed { - // Restart the search, or else Write can stay blocked forever - c.core.router.admin <- c.startSearch - } - }() return } - defer c.searching.Store(false) // Take the connection mutex c.mutex.Lock() defer c.mutex.Unlock() @@ -102,6 +93,16 @@ func (c *Conn) startSearch() { // Things were closed before the search returned // Go ahead and close it again to make sure the session is cleaned up go c.Close() + } else { + // Send any messages we may have buffered + var msgs [][]byte + msgs, c.writebuf = c.writebuf, nil + go func() { + for _, msg := range msgs { + c.Write(msg) + util.PutBytes(msg) + } + }() } } // doSearch will be called below in response to one or more conditions @@ -238,8 +239,6 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - timer := getDeadlineTimer(&c.writeDeadline) - defer util.TimerStop(timer) // If the session doesn't exist, or isn't initialised (which probably means // that the search didn't complete successfully) then we may need to wait for // the search to complete or start the search again @@ -249,22 +248,15 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // No search was already taking place so start a new one c.core.router.doAdmin(c.startSearch) } - // Wait for the search to complete - select { - case <-c.searchwait: - case <-timer.C: - return 0, ConnError{errors.New("Timeout"), true, false} - } - // Retrieve our session info again - c.mutex.RLock() - sinfo = c.session - c.mutex.RUnlock() - // If sinfo is still nil at this point then the search failed and the - // searchwait channel has been recreated, so might as well give up and - // return an error code - if sinfo == nil { - return 0, errors.New("search failed") + // Buffer the packet to be sent if/when the search is finished + c.mutex.Lock() + defer c.mutex.Unlock() + c.writebuf = append(c.writebuf, append(util.GetBytes(), b...)) + for len(c.writebuf) > 32 { + util.PutBytes(c.writebuf[0]) + c.writebuf = c.writebuf[1:] } + return len(b), nil } var packet []byte done := make(chan struct{}) @@ -283,13 +275,17 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { packet = p.encode() sinfo.bytesSent += uint64(len(b)) } + // Set up a timer so this doesn't block forever + timer := getDeadlineTimer(&c.writeDeadline) + defer util.TimerStop(timer) // Hand over to the session worker select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: return 0, ConnError{errors.New("Timeout"), true, false} } - <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) + // Wait for the worker to finish, otherwise there are memory errors ([Get||Put]Bytes stuff) + <-done // Give the packet to the router sinfo.core.router.out(packet) // Finally return the number of bytes we wrote From b2513fce563aa0817642cadc5bfbf44eb8add001 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 28 May 2019 18:35:52 -0500 Subject: [PATCH 59/60] have the tunConn close things after a 2 minute timeout --- src/tuntap/conn.go | 56 ++++++++++++++++++++++++++++++++++++++-------- src/tuntap/tun.go | 10 +++++---- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index ce4645f3..66c10119 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -2,6 +2,7 @@ package tuntap import ( "errors" + "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" @@ -9,24 +10,33 @@ import ( ) type tunConn struct { - tun *TunAdapter - conn *yggdrasil.Conn - addr address.Address - snet address.Subnet - send chan []byte - stop chan interface{} + tun *TunAdapter + conn *yggdrasil.Conn + addr address.Address + snet address.Subnet + send chan []byte + stop chan struct{} + alive chan struct{} } func (s *tunConn) close() { s.tun.mutex.Lock() + defer s.tun.mutex.Unlock() s._close_nomutex() - s.tun.mutex.Unlock() } func (s *tunConn) _close_nomutex() { + s.conn.Close() delete(s.tun.addrToConn, s.addr) delete(s.tun.subnetToConn, s.snet) - close(s.stop) + func() { + defer func() { recover() }() + close(s.stop) // Closes reader/writer goroutines + }() + func() { + defer func() { recover() }() + close(s.alive) // Closes timeout goroutine + }() } func (s *tunConn) reader() error { @@ -43,7 +53,7 @@ func (s *tunConn) reader() error { b := make([]byte, 65535) for { go func() { - // TODO read timeout and close + // TODO don't start a new goroutine for every packet read, this is probably a big part of the slowdowns we saw when refactoring if n, err = s.conn.Read(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) return @@ -60,6 +70,7 @@ func (s *tunConn) reader() error { util.PutBytes(bs) } } + s.stillAlive() // TODO? Only stay alive if we read >0 bytes? case <-s.stop: s.tun.log.Debugln("Stopping conn reader for", s) return nil @@ -89,6 +100,33 @@ func (s *tunConn) writer() error { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) } util.PutBytes(b) + s.stillAlive() + } + } +} + +func (s *tunConn) stillAlive() { + select { + case s.alive <- struct{}{}: + default: + } +} + +func (s *tunConn) checkForTimeouts() error { + const timeout = 2 * time.Minute + timer := time.NewTimer(timeout) + defer util.TimerStop(timer) + defer s.close() + for { + select { + case _, ok := <-s.alive: + if !ok { + return errors.New("connection closed") + } + util.TimerStop(timer) + timer.Reset(timeout) + case <-timer.C: + return errors.New("timed out") } } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index b9ff04ec..683b83ac 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -229,10 +229,11 @@ func (tun *TunAdapter) handler() error { func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // Prepare a session wrapper for the given connection s := tunConn{ - tun: tun, - conn: conn, - send: make(chan []byte, 32), // TODO: is this a sensible value? - stop: make(chan interface{}), + tun: tun, + conn: conn, + send: make(chan []byte, 32), // TODO: is this a sensible value? + stop: make(chan struct{}), + alive: make(chan struct{}, 1), } // Get the remote address and subnet of the other side remoteNodeID := conn.RemoteAddr() @@ -259,6 +260,7 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // Start the connection goroutines go s.reader() go s.writer() + go s.checkForTimeouts() // Return return c, err } From 78eb40cbad346202190c817f810b3fd2fc76a4ff Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 29 May 2019 12:59:36 +0100 Subject: [PATCH 60/60] Record session uptime (purely for the admin socket) --- cmd/yggdrasilctl/main.go | 2 +- src/admin/admin.go | 1 + src/yggdrasil/api.go | 2 ++ src/yggdrasil/session.go | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index b70bbe9e..51f4fa51 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -200,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" || k == "nodeinfo" { + if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" { continue } } diff --git a/src/admin/admin.go b/src/admin/admin.go index 27dabdcc..c3f140ae 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -162,6 +162,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "bytes_sent": s.BytesSent, "bytes_recvd": s.BytesRecvd, "mtu": s.MTU, + "uptime": s.Uptime.Seconds(), "was_mtu_fixed": s.WasMTUFixed, "box_pub_key": s.PublicKey, } diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 40e5a2b2..5e58ffaf 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -89,6 +89,7 @@ type Session struct { BytesSent uint64 BytesRecvd uint64 MTU uint16 + Uptime time.Duration WasMTUFixed bool } @@ -216,6 +217,7 @@ func (c *Core) GetSessions() []Session { MTU: sinfo.getMTU(), BytesSent: sinfo.bytesSent, BytesRecvd: sinfo.bytesRecvd, + Uptime: time.Now().Sub(sinfo.timeOpened), WasMTUFixed: sinfo.wasMTUFixed, } copy(session.PublicKey[:], sinfo.theirPermPub[:]) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 724152dd..3a4eb2e9 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -34,6 +34,7 @@ type sessionInfo struct { theirMTU uint16 // myMTU uint16 // wasMTUFixed bool // Was the MTU fixed by a receive error? + timeOpened time.Time // Time the sessino was opened time time.Time // Time we last received a packet mtuTime time.Time // time myMTU was last changed pingTime time.Time // time the first ping was sent since the last received packet @@ -284,6 +285,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirMTU = 1280 sinfo.myMTU = 1280 now := time.Now() + sinfo.timeOpened = now sinfo.time = now sinfo.mtuTime = now sinfo.pingTime = now