From 31ce8548353addd4be489aa171610144b7b87cd3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 12 Oct 2019 15:37:40 -0500 Subject: [PATCH 01/28] update session when a search for an existing session finishes --- src/yggdrasil/search.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 322131ed..6c43cfef 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -205,6 +205,8 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { panic("This should never happen") } } else { + sess.coords = res.Coords // In case coords have updated + sess.ping(sinfo.searches.router) // In case the remote side needs updating sinfo.callback(nil, errors.New("session already exists")) // Cleanup delete(sinfo.searches.searches, res.Dest) From 3491292599fcd523060cebfb84e04a15d940904b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 12 Oct 2019 15:46:56 -0500 Subject: [PATCH 02/28] code cleanup --- src/yggdrasil/search.go | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 6c43cfef..caa8df7c 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -189,34 +189,34 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { if themMasked != destMasked { return false } + finishSearch := func(sess *sessionInfo, err error) { + if sess != nil { + // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? + sess.coords = res.Coords + sess.ping(sinfo.searches.router) + } + if err != nil { + sinfo.callback(nil, err) + } else { + sinfo.callback(sess, nil) + } + // Cleanup + delete(sinfo.searches.searches, res.Dest) + } // They match, so create a session and send a sessionRequest + var err error sess, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key) if !isIn { + // Don't already have a session sess = sinfo.searches.router.sessions.createSession(&res.Key) if sess == nil { - // nil if the DHT search finished but the session wasn't allowed - sinfo.callback(nil, errors.New("session not allowed")) - // Cleanup - delete(sinfo.searches.searches, res.Dest) - return true - } - _, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key) - if !isIn { + err = errors.New("session not allowed") + } else if _, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key); !isIn { panic("This should never happen") } } else { - sess.coords = res.Coords // In case coords have updated - sess.ping(sinfo.searches.router) // In case the remote side needs updating - sinfo.callback(nil, errors.New("session already exists")) - // Cleanup - delete(sinfo.searches.searches, res.Dest) - return true + err = errors.New("session already exists") } - // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? - sess.coords = res.Coords - sess.ping(sinfo.searches.router) - sinfo.callback(sess, nil) - // Cleanup - delete(sinfo.searches.searches, res.Dest) + finishSearch(sess, err) return true } From cb40874f97fb5d076f9c5e0bf1cf26e315a7792c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 19 Oct 2019 15:10:28 -0500 Subject: [PATCH 03/28] have listener return a net.Conn, adjust yggdrasil.Conn to match this interface --- src/tuntap/conn.go | 4 ++-- src/tuntap/tun.go | 8 ++++---- src/yggdrasil/conn.go | 9 +++++---- src/yggdrasil/listener.go | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 6db46b23..207cd14f 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -93,7 +93,7 @@ func (s *tunConn) _read(bs []byte) (err error) { skip = true } else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil { srcNodeID := crypto.GetNodeID(&key) - if s.conn.RemoteAddr() == *srcNodeID { + if *s.conn.RemoteAddr().(*crypto.NodeID) == *srcNodeID { // This is the one allowed CKR case, where source and destination addresses are both good } else { // The CKR key associated with this address doesn't match the sender's NodeID @@ -170,7 +170,7 @@ func (s *tunConn) _write(bs []byte) (err error) { skip = true } else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { dstNodeID := crypto.GetNodeID(&key) - if s.conn.RemoteAddr() == *dstNodeID { + if *s.conn.RemoteAddr().(*crypto.NodeID) == *dstNodeID { // This is the one allowed CKR case, where source and destination addresses are both good } else { // The CKR key associated with this address doesn't match the sender's NodeID diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 74d055ee..0bef9091 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -219,7 +219,7 @@ func (tun *TunAdapter) handler() error { return err } phony.Block(tun, func() { - if _, err := tun._wrap(conn); err != nil { + if _, err := tun._wrap(conn.(*yggdrasil.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) @@ -237,9 +237,9 @@ func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { } c = &s // Get the remote address and subnet of the other side - remoteNodeID := conn.RemoteAddr() - s.addr = *address.AddrForNodeID(&remoteNodeID) - s.snet = *address.SubnetForNodeID(&remoteNodeID) + remoteNodeID := conn.RemoteAddr().(*crypto.NodeID) + s.addr = *address.AddrForNodeID(remoteNodeID) + s.snet = *address.SubnetForNodeID(remoteNodeID) // Work out if this is already a destination we already know about atc, aok := tun.addrToConn[s.addr] stc, sok := tun.subnetToConn[s.snet] diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index bb5964b6..67426f4f 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -3,6 +3,7 @@ package yggdrasil import ( "errors" "fmt" + "net" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -348,14 +349,14 @@ func (c *Conn) Close() (err error) { // LocalAddr returns the complete node ID of the local side of the connection. // This is always going to return your own node's node ID. -func (c *Conn) LocalAddr() crypto.NodeID { - return *crypto.GetNodeID(&c.core.boxPub) +func (c *Conn) LocalAddr() net.Addr { + return crypto.GetNodeID(&c.core.boxPub) } // RemoteAddr returns the complete node ID of the remote side of the connection. -func (c *Conn) RemoteAddr() crypto.NodeID { +func (c *Conn) RemoteAddr() net.Addr { // RemoteAddr is set during the dial or accept, and isn't changed, so it's safe to access directly - return *c.nodeID + return c.nodeID } // SetDeadline is equivalent to calling both SetReadDeadline and diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go index fec543f4..63830970 100644 --- a/src/yggdrasil/listener.go +++ b/src/yggdrasil/listener.go @@ -13,7 +13,7 @@ type Listener struct { } // Accept blocks until a new incoming session is received -func (l *Listener) Accept() (*Conn, error) { +func (l *Listener) Accept() (net.Conn, error) { select { case c, ok := <-l.conn: if !ok { From a81476f4894cc96baa2b124f1a9f6572374fed44 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 20 Oct 2019 20:00:55 -0500 Subject: [PATCH 04/28] fix incorrectly held mutex in ckr getPublicKeyForAddress --- src/tuntap/ckr.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 9af3564a..fdb2bbdc 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -263,7 +263,6 @@ func (c *cryptokey) addRemoteSubnet(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 @@ -285,11 +284,11 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c } // Check if there's a cache entry for this addr + c.mutexcaches.RLock() if route, ok := (*routingcache)[addr]; ok { c.mutexcaches.RUnlock() return route.destination, nil } - c.mutexcaches.RUnlock() c.mutexremotes.RLock() From eccd9a348fe707f40bc3dec31603acb0d121861e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 21 Oct 2019 19:44:06 -0500 Subject: [PATCH 05/28] give yggdrasil.Dialer the same interface as a net.Dialer, so the only differences are what fields exist in the struct --- src/address/address.go | 20 +++++++++++++++++++- src/tuntap/iface.go | 29 +++++++++++++++-------------- src/tuntap/tun.go | 4 ++-- src/yggdrasil/dialer.go | 29 ++++++++++++++++++----------- 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/address/address.go b/src/address/address.go index 3960b783..eba61708 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -2,7 +2,11 @@ // Of particular importance are the functions used to derive addresses or subnets from a NodeID, or to get the NodeID and bitmask of the bits visible from an address, which is needed for DHT searches. package address -import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +import ( + "fmt" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) // Address represents an IPv6 address in the yggdrasil address range. type Address [16]byte @@ -128,6 +132,13 @@ func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { return &nid, &mask } +// GetNodeIDLengthString returns a string representation of the known bits of the NodeID, along with the number of known bits, for use with yggdrasil.Dialer's Dial and DialContext functions. +func (a *Address) GetNodeIDLengthString() string { + nid, mask := a.GetNodeIDandMask() + l := mask.PrefixLength() + return fmt.Sprintf("%s/%d", nid.String(), l) +} + // GetNodeIDandMask returns two *NodeID. // The first is a NodeID with all the bits known from the Subnet set to their correct values. // The second is a bitmask with 1 bit set for each bit that was known from the Subnet. @@ -156,3 +167,10 @@ func (s *Subnet) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { } return &nid, &mask } + +// GetNodeIDLengthString returns a string representation of the known bits of the NodeID, along with the number of known bits, for use with yggdrasil.Dialer's Dial and DialContext functions. +func (s *Subnet) GetNodeIDLengthString() string { + nid, mask := s.GetNodeIDandMask() + l := mask.PrefixLength() + return fmt.Sprintf("%s/%d", nid.String(), l) +} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 0da99631..3d788b1a 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -9,6 +9,7 @@ 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" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" "github.com/Arceliar/phony" ) @@ -225,7 +226,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { return } // Do we have an active connection for this node address? - var dstNodeID, dstNodeIDMask *crypto.NodeID + var dstString string session, isIn := tun.addrToConn[dstAddr] if !isIn || session == nil { session, isIn = tun.subnetToConn[dstSnet] @@ -233,9 +234,9 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { // Neither an address nor a subnet mapping matched, therefore populate // the node ID and mask to commence a search if dstAddr.IsValid() { - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + dstString = dstAddr.GetNodeIDLengthString() } else { - dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() + dstString = dstSnet.GetNodeIDLengthString() } } } @@ -243,27 +244,27 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { 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") + if dstString == "" { + panic("Given empty dstString - this shouldn't happen") } - _, known := tun.dials[*dstNodeID] - tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs) - for len(tun.dials[*dstNodeID]) > 32 { - util.PutBytes(tun.dials[*dstNodeID][0]) - tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] + _, known := tun.dials[dstString] + tun.dials[dstString] = append(tun.dials[dstString], bs) + for len(tun.dials[dstString]) > 32 { + util.PutBytes(tun.dials[dstString][0]) + tun.dials[dstString] = tun.dials[dstString][1:] } if !known { go func() { - conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask) + conn, err := tun.dialer.Dial("nodeid", dstString) tun.Act(nil, func() { - packets := tun.dials[*dstNodeID] - delete(tun.dials, *dstNodeID) + packets := tun.dials[dstString] + delete(tun.dials, dstString) if err != nil { return } // We've been given a connection so prepare the session wrapper var tc *tunConn - if tc, err = tun._wrap(conn); err != nil { + if tc, err = tun._wrap(conn.(*yggdrasil.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) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 0bef9091..5d77ecab 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -52,7 +52,7 @@ type TunAdapter struct { //mutex sync.RWMutex // Protects the below addrToConn map[address.Address]*tunConn subnetToConn map[address.Subnet]*tunConn - dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes + dials map[string][][]byte // Buffer of packets to send after dialing finishes isOpen bool } @@ -117,7 +117,7 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.dialer = dialer tun.addrToConn = make(map[address.Address]*tunConn) tun.subnetToConn = make(map[address.Subnet]*tunConn) - tun.dials = make(map[crypto.NodeID][][]byte) + tun.dials = make(map[string][][]byte) tun.writer.tun = tun tun.reader.tun = tun } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 04410855..e3f24e94 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -1,8 +1,10 @@ package yggdrasil import ( + "context" "encoding/hex" "errors" + "net" "strconv" "strings" "time" @@ -15,12 +17,18 @@ type Dialer struct { core *Core } -// TODO DialContext that allows timeouts/cancellation, Dial should just call this with no timeout set in the context - // 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) { +// target node ID. Internally, it uses DialContext with a 6-second timeout. +func (d *Dialer) Dial(network, address string) (net.Conn, error) { + const timeout = 6 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return d.DialContext(ctx, network, address) +} + +// DialContext is used internally by Dial, and should only be used with a context that includes a timeout. +func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { var nodeID crypto.NodeID var nodeMask crypto.NodeID // Process @@ -28,7 +36,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { 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]) + l, err := strconv.Atoi(tokens[1]) if err != nil { return nil, err } @@ -37,7 +45,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { return nil, err } copy(nodeID[:], dest) - for idx := 0; idx < len; idx++ { + for idx := 0; idx < l; idx++ { nodeMask[idx/8] |= 0x80 >> byte(idx%8) } } else { @@ -50,7 +58,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { nodeMask[i] = 0xFF } } - return d.DialByNodeIDandMask(&nodeID, &nodeMask) + return d.DialByNodeIDandMask(ctx, &nodeID, &nodeMask) default: // An unexpected address type was given, so give up return nil, errors.New("unexpected address type") @@ -59,19 +67,18 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { // DialByNodeIDandMask opens a session to the given node based on raw // NodeID parameters. -func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) { +func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *crypto.NodeID) (net.Conn, error) { conn := newConn(d.core, nodeID, nodeMask, nil) if err := conn.search(); err != nil { + // TODO: make searches take a context, so they can be cancelled early conn.Close() return nil, err } conn.session.setConn(nil, conn) - t := time.NewTimer(6 * time.Second) // TODO use a context instead - defer t.Stop() select { case <-conn.session.init: return conn, nil - case <-t.C: + case <-ctx.Done(): conn.Close() return nil, errors.New("session handshake timeout") } From 681c8ca6f930afe4b8f3690363cdafe0c3c0e5a0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 21 Oct 2019 20:47:50 -0500 Subject: [PATCH 06/28] safer dial timeout handling, in case it was used with a nil context or a context that had no timeout set --- src/yggdrasil/dialer.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index e3f24e94..8ef0582f 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -19,15 +19,12 @@ 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. Internally, it uses DialContext with a 6-second timeout. +// target node ID. It uses DialContext internally. func (d *Dialer) Dial(network, address string) (net.Conn, error) { - const timeout = 6 * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - return d.DialContext(ctx, network, address) + return d.DialContext(nil, network, address) } -// DialContext is used internally by Dial, and should only be used with a context that includes a timeout. +// DialContext is used internally by Dial, and should only be used with a context that includes a timeout. It uses DialByNodeIDandMask internally. func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { var nodeID crypto.NodeID var nodeMask crypto.NodeID @@ -66,7 +63,7 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (net. } // DialByNodeIDandMask opens a session to the given node based on raw -// NodeID parameters. +// NodeID parameters. If ctx is nil or has no timeout, then a default timeout of 6 seconds will apply, beginning *after* the search finishes. func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *crypto.NodeID) (net.Conn, error) { conn := newConn(d.core, nodeID, nodeMask, nil) if err := conn.search(); err != nil { @@ -75,10 +72,19 @@ func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *cryp return nil, err } conn.session.setConn(nil, conn) + var c context.Context + var cancel context.CancelFunc + const timeout = 6 * time.Second + if ctx != nil { + c, cancel = context.WithTimeout(ctx, timeout) + } else { + c, cancel = context.WithTimeout(context.Background(), timeout) + } + defer cancel() select { case <-conn.session.init: return conn, nil - case <-ctx.Done(): + case <-c.Done(): conn.Close() return nil, errors.New("session handshake timeout") } From ea085663ea75ee2c84364244da6b0d195d0b76e0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 21 Oct 2019 20:52:16 -0500 Subject: [PATCH 07/28] slight cleanup of dial's timeout --- src/yggdrasil/dialer.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 8ef0582f..47a68813 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -72,19 +72,16 @@ func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *cryp return nil, err } conn.session.setConn(nil, conn) - var c context.Context var cancel context.CancelFunc - const timeout = 6 * time.Second - if ctx != nil { - c, cancel = context.WithTimeout(ctx, timeout) - } else { - c, cancel = context.WithTimeout(context.Background(), timeout) + if ctx == nil { + ctx = context.Background() } + ctx, cancel = context.WithTimeout(ctx, 6*time.Second) defer cancel() select { case <-conn.session.init: return conn, nil - case <-c.Done(): + case <-ctx.Done(): conn.Close() return nil, errors.New("session handshake timeout") } From a072e063d8864caf24c9c6a41600756990eabb5f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 10:44:58 +0100 Subject: [PATCH 08/28] Define module.Module interface, update admin/tuntap/multicast modules to comply with it, fix #581 --- cmd/yggdrasil/main.go | 18 +++++++---- go.mod | 2 ++ src/admin/admin.go | 61 ++++++++++++++++++++++---------------- src/module/module.go | 20 +++++++++++++ src/multicast/multicast.go | 21 +++++++++++++ src/tuntap/tun.go | 28 ++++++++++------- 6 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 src/module/module.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 33a8769d..91cea9a7 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -23,6 +23,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/module" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/version" @@ -32,9 +33,9 @@ import ( type node struct { core yggdrasil.Core state *config.NodeState - tuntap tuntap.TunAdapter - multicast multicast.Multicast - admin admin.AdminSocket + tuntap module.Module // tuntap.TunAdapter + multicast module.Module // multicast.Multicast + admin module.Module // admin.AdminSocket } func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig { @@ -231,25 +232,30 @@ func main() { } // Register the session firewall gatekeeper function n.core.SetSessionGatekeeper(n.sessionFirewall) + // Allocate our modules + n.admin = &admin.AdminSocket{} + n.multicast = &multicast.Multicast{} + n.tuntap = &tuntap.TunAdapter{} // Start the admin socket n.admin.Init(&n.core, n.state, logger, nil) if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } + n.admin.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the multicast interface n.multicast.Init(&n.core, n.state, logger, nil) if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } - n.multicast.SetupAdminHandlers(&n.admin) + n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the TUN/TAP interface if listener, err := n.core.ConnListen(); err == nil { if dialer, err := n.core.ConnDialer(); err == nil { - n.tuntap.Init(n.state, logger, listener, dialer) + n.tuntap.Init(&n.core, n.state, logger, tuntap.TunOptions{Listener: listener, Dialer: dialer}) if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } - n.tuntap.SetupAdminHandlers(&n.admin) + n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) } else { logger.Errorln("Unable to get Dialer:", err) } diff --git a/go.mod b/go.mod index 83c22924..d1a3587f 100644 --- a/go.mod +++ b/go.mod @@ -17,3 +17,5 @@ require ( golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 ) + +go 1.13 diff --git a/src/admin/admin.go b/src/admin/admin.go index c7fc151d..f7637998 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -25,12 +25,12 @@ import ( // TODO: Add authentication type AdminSocket struct { - core *yggdrasil.Core - log *log.Logger - reconfigure chan chan error - listenaddr string - listener net.Listener - handlers map[string]handler + core *yggdrasil.Core + log *log.Logger + listenaddr string + listener net.Listener + handlers map[string]handler + started bool } // Info refers to information that is returned to the admin socket handler. @@ -54,25 +54,27 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(In } // init runs the initial admin setup. -func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) { +func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { 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 - current, previous := state.GetCurrent(), state.GetPrevious() - if current.AdminListen != previous.AdminListen { - a.listenaddr = current.AdminListen - a.Stop() - a.Start() - } - e <- nil - } - }() current := state.GetCurrent() a.listenaddr = current.AdminListen + return nil +} + +func (a *AdminSocket) UpdateConfig(config *config.NodeConfig) { + a.log.Debugln("Reloading admin configuration...") + if a.listenaddr != config.AdminListen { + a.listenaddr = config.AdminListen + if a.IsStarted() { + a.Stop() + } + a.Start() + } +} + +func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { a.AddHandler("list", []string{}, func(in Info) (Info, error) { handlers := make(map[string]interface{}) for handlername, handler := range a.handlers { @@ -81,15 +83,15 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. return Info{"list": handlers}, nil }) a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { - ip := c.Address().String() - subnet := c.Subnet() + ip := a.core.Address().String() + subnet := a.core.Subnet() return Info{ "self": Info{ ip: Info{ - "box_pub_key": c.EncryptionPublicKey(), + "box_pub_key": a.core.EncryptionPublicKey(), "build_name": version.BuildName(), "build_version": version.BuildVersion(), - "coords": fmt.Sprintf("%v", c.Coords()), + "coords": fmt.Sprintf("%v", a.core.Coords()), "subnet": subnet.String(), }, }, @@ -312,17 +314,24 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. }) } -// start runs the admin API socket to listen for / respond to admin API calls. +// Start runs the admin API socket to listen for / respond to admin API calls. func (a *AdminSocket) Start() error { if a.listenaddr != "none" && a.listenaddr != "" { go a.listen() + a.started = true } return nil } -// cleans up when stopping +// IsStarted returns true if the module has been started. +func (a *AdminSocket) IsStarted() bool { + return a.started +} + +// Stop will stop the admin API and close the socket. func (a *AdminSocket) Stop() error { if a.listener != nil { + a.started = false return a.listener.Close() } else { return nil diff --git a/src/module/module.go b/src/module/module.go new file mode 100644 index 00000000..ab704e70 --- /dev/null +++ b/src/module/module.go @@ -0,0 +1,20 @@ +package module + +import ( + "github.com/gologme/log" + + "github.com/yggdrasil-network/yggdrasil-go/src/admin" + "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" +) + +// Module is an interface that defines which functions must be supported by a +// given Yggdrasil module. +type Module interface { + Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error + Start() error + Stop() error + UpdateConfig(config *config.NodeConfig) + SetupAdminHandlers(a *admin.AdminSocket) + IsStarted() bool +} diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 7044eaa2..6c1af1c4 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -55,6 +55,12 @@ 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.config.GetCurrent().MulticastInterfaces) == 0 { + return fmt.Errorf("no MulticastInterfaces configured") + } + + m.log.Infoln("Starting multicast module") + addr, err := net.ResolveUDPAddr("udp", m.groupAddr) if err != nil { return err @@ -80,8 +86,14 @@ func (m *Multicast) Start() error { return nil } +// IsStarted returns true if the module has been started. +func (m *Multicast) IsStarted() bool { + return m.isOpen +} + // Stop is not implemented for multicast yet. func (m *Multicast) Stop() error { + m.log.Infoln("Stopping multicast module") m.isOpen = false if m.announcer != nil { m.announcer.Stop() @@ -98,7 +110,16 @@ func (m *Multicast) Stop() error { // needed. func (m *Multicast) UpdateConfig(config *config.NodeConfig) { m.log.Debugln("Reloading multicast configuration...") + if m.IsStarted() { + if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort { + m.Stop() + } + } m.config.Replace(*config) + m.listenPort = config.LinkLocalTCPPort + if !m.IsStarted() && len(config.MulticastInterfaces) > 0 { + m.Start() + } } // GetInterfaces returns the currently known/enabled multicast interfaces. It is diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 5d77ecab..06feede0 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -56,6 +56,11 @@ type TunAdapter struct { isOpen bool } +type TunOptions struct { + Listener *yggdrasil.Listener + Dialer *yggdrasil.Dialer +} + // Gets the maximum supported MTU for the platform based on the defaults in // defaults.GetDefaults(). func getSupportedMTU(mtu int) int { @@ -110,16 +115,21 @@ func MaximumMTU() int { // 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) { +func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error { + tunoptions, ok := options.(TunOptions) + if !ok { + return fmt.Errorf("invalid options supplied to TunAdapter module") + } tun.config = config tun.log = log - tun.listener = listener - tun.dialer = dialer + tun.listener = tunoptions.Listener + tun.dialer = tunoptions.Dialer tun.addrToConn = make(map[address.Address]*tunConn) tun.subnetToConn = make(map[address.Subnet]*tunConn) tun.dials = make(map[string][][]byte) tun.writer.tun = tun tun.reader.tun = tun + return nil } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -160,13 +170,6 @@ func (tun *TunAdapter) _start() error { return nil } tun.isOpen = true - tun.reconfigure = make(chan chan error) - go func() { - for { - e := <-tun.reconfigure - e <- nil - } - }() go tun.handler() tun.reader.Act(nil, tun.reader._read) // Start the reader tun.icmpv6.Init(tun) @@ -177,6 +180,11 @@ func (tun *TunAdapter) _start() error { return nil } +// IsStarted returns true if the module has been started. +func (tun *TunAdapter) IsStarted() bool { + return tun.isOpen +} + // 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) Stop() error { From 337626a32cc38c13e6809e63aff60b2c4be1b0ae Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 11:12:51 +0100 Subject: [PATCH 09/28] Act multicast updates for safety --- go.mod | 2 -- src/admin/admin.go | 14 +++++++------- src/multicast/multicast.go | 4 ++++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index d1a3587f..83c22924 100644 --- a/go.mod +++ b/go.mod @@ -17,5 +17,3 @@ require ( golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 ) - -go 1.13 diff --git a/src/admin/admin.go b/src/admin/admin.go index f7637998..8028bcc6 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -60,6 +60,13 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. a.handlers = make(map[string]handler) current := state.GetCurrent() a.listenaddr = current.AdminListen + a.AddHandler("list", []string{}, func(in Info) (Info, error) { + handlers := make(map[string]interface{}) + for handlername, handler := range a.handlers { + handlers[handlername] = Info{"fields": handler.args} + } + return Info{"list": handlers}, nil + }) return nil } @@ -75,13 +82,6 @@ func (a *AdminSocket) UpdateConfig(config *config.NodeConfig) { } func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { - a.AddHandler("list", []string{}, func(in Info) (Info, error) { - handlers := make(map[string]interface{}) - for handlername, handler := range a.handlers { - handlers[handlername] = Info{"fields": handler.args} - } - return Info{"list": handlers}, nil - }) a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { ip := a.core.Address().String() subnet := a.core.Subnet() diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 6c1af1c4..deffecd2 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -109,6 +109,10 @@ func (m *Multicast) Stop() error { // and then signals the various module goroutines to reconfigure themselves if // needed. func (m *Multicast) UpdateConfig(config *config.NodeConfig) { + m.Act(m, func() { m._updateConfig(config) }) +} + +func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloading multicast configuration...") if m.IsStarted() { if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort { From b0bcf29d27f2464cc7d4a16848ac4cc0ab713aa0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 11:15:57 +0100 Subject: [PATCH 10/28] Allow ExecStartPre to fail for containers (#573) --- contrib/systemd/yggdrasil.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index 0223dd90..b48ff78b 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -9,7 +9,7 @@ ProtectHome=true ProtectSystem=true SyslogIdentifier=yggdrasil CapabilityBoundSet=CAP_NET_ADMIN -ExecStartPre=+/sbin/modprobe tun +ExecStartPre=+-/sbin/modprobe tun ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \ then umask 077; \ yggdrasil -genconf > /etc/yggdrasil.conf; \ From f6c7c1b8db69b08f17d55310cb2ef967c9da297c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 11:24:00 +0100 Subject: [PATCH 11/28] Produce armel build (closes #577) --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1456332a..5a9e1e31 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -48,6 +48,7 @@ jobs: PKGARCH=mipsel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mipsel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mipsel; PKGARCH=mips sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mips && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mips; PKGARCH=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armhf && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf; + PKGARCH=armel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armel; PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64; mv *.deb /tmp/upload/ From 6a22e6c9de3f2046c65aaea88587d2493933da5f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 17:26:35 +0100 Subject: [PATCH 12/28] Initial connection upgrade/TLS peering support --- src/yggdrasil/api.go | 9 ++++- src/yggdrasil/link.go | 11 ++++-- src/yggdrasil/tcp.go | 82 +++++++++++++++++++++++++++----------- src/yggdrasil/tls.go | 92 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 27 deletions(-) create mode 100644 src/yggdrasil/tls.go diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 4245f439..80f669b6 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -280,7 +280,14 @@ func (c *Core) ConnDialer() (*Dialer, error) { // "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) + return c.link.tcp.listen(uri, nil) +} + +// ListenTLS starts a new TLS listener. The input URI should match that of the +// "Listen" configuration item, e.g. +// tls://a.b.c.d:e +func (c *Core) ListenTLS(uri string) (*TcpListener, error) { + return c.link.tcp.listen(uri, c.link.tcp.tls.forListener) } // NodeID gets the node ID. This is derived from your router encryption keys. diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 98c080c7..1710e202 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -93,9 +93,11 @@ func (l *link) call(uri string, sintf string) error { pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") switch u.Scheme { case "tcp": - l.tcp.call(u.Host, nil, sintf) + l.tcp.call(u.Host, nil, sintf, nil) case "socks": - l.tcp.call(pathtokens[0], u.Host, sintf) + l.tcp.call(pathtokens[0], u.Host, sintf, nil) + case "tls": + l.tcp.call(u.Host, nil, sintf, l.tcp.tls.forDialer) default: return errors.New("unknown call scheme: " + u.Scheme) } @@ -109,7 +111,10 @@ func (l *link) listen(uri string) error { } switch u.Scheme { case "tcp": - _, err := l.tcp.listen(u.Host) + _, err := l.tcp.listen(u.Host, nil) + return err + case "tls": + _, err := l.tcp.listen(u.Host, l.tcp.tls.forListener) return err default: return errors.New("unknown listen scheme: " + u.Scheme) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 66f708c2..c4569591 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -39,6 +39,7 @@ type tcp struct { listeners map[string]*TcpListener calls map[string]struct{} conns map[linkInfo](chan struct{}) + tls tcptls } // TcpListener is a stoppable TCP listener interface. These are typically @@ -47,9 +48,15 @@ type tcp struct { // multicast interfaces. type TcpListener struct { Listener net.Listener + upgrade *TcpUpgrade stop chan struct{} } +type TcpUpgrade struct { + upgrade func(c net.Conn) (net.Conn, error) + name string +} + func (l *TcpListener) Stop() { defer func() { recover() }() close(l.stop) @@ -81,6 +88,7 @@ func (t *tcp) getAddr() *net.TCPAddr { // Initializes the struct. func (t *tcp) init(l *link) error { t.link = l + t.tls.init(t) t.mutex.Lock() t.calls = make(map[string]struct{}) t.conns = make(map[linkInfo](chan struct{})) @@ -90,12 +98,17 @@ func (t *tcp) init(l *link) error { t.link.core.config.Mutex.RLock() defer t.link.core.config.Mutex.RUnlock() for _, listenaddr := range t.link.core.config.Current.Listen { - if listenaddr[:6] != "tcp://" { + switch listenaddr[:6] { + case "tcp://": + if _, err := t.listen(listenaddr[6:], nil); err != nil { + return err + } + case "tls://": + if _, err := t.listen(listenaddr[6:], t.tls.forListener); err != nil { + return err + } + default: t.link.core.log.Errorln("Failed to add listener: listener", listenaddr, "is not correctly formatted, ignoring") - continue - } - if _, err := t.listen(listenaddr[6:]); err != nil { - return err } } @@ -119,18 +132,21 @@ func (t *tcp) reconfigure() { t.link.core.config.Mutex.RUnlock() if len(added) > 0 || len(deleted) > 0 { for _, a := range added { - if a[:6] != "tcp://" { + switch a[:6] { + case "tcp://": + if _, err := t.listen(a[6:], nil); err != nil { + t.link.core.log.Errorln("Error adding TCP", a[6:], "listener:", err) + } + case "tls://": + if _, err := t.listen(a[6:], t.tls.forListener); err != nil { + t.link.core.log.Errorln("Error adding TLS", a[6:], "listener:", err) + } + default: t.link.core.log.Errorln("Failed to add listener: listener", a, "is not correctly formatted, ignoring") - continue - } - if _, err := t.listen(a[6:]); err != nil { - t.link.core.log.Errorln("Error adding TCP", a[6:], "listener:", err) - } else { - t.link.core.log.Infoln("Started TCP listener:", a[6:]) } } for _, d := range deleted { - if d[:6] != "tcp://" { + if d[:6] != "tcp://" && d[:6] != "tls://" { t.link.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring") continue } @@ -146,7 +162,7 @@ func (t *tcp) reconfigure() { } } -func (t *tcp) listen(listenaddr string) (*TcpListener, error) { +func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, error) { var err error ctx := context.Background() @@ -157,6 +173,7 @@ func (t *tcp) listen(listenaddr string) (*TcpListener, error) { if err == nil { l := TcpListener{ Listener: listener, + upgrade: upgrade, stop: make(chan struct{}), } t.waitgroup.Add(1) @@ -204,7 +221,7 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { return } t.waitgroup.Add(1) - go t.handler(sock, true, nil) + go t.handler(sock, true, nil, l.upgrade) } } @@ -222,11 +239,15 @@ func (t *tcp) startCalling(saddr string) bool { // If the dial is successful, it launches the handler. // When finished, it removes the outgoing call, so reconnection attempts can be made later. // This all happens in a separate goroutine that it spawns. -func (t *tcp) call(saddr string, options interface{}, sintf string) { +func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *TcpUpgrade) { go func() { callname := saddr + callproto := "TCP" + if upgrade != nil { + callproto = strings.ToUpper(upgrade.name) + } if sintf != "" { - callname = fmt.Sprintf("%s/%s", saddr, sintf) + callname = fmt.Sprintf("%s/%s/%s", callproto, saddr, sintf) } if !t.startCalling(callname) { return @@ -261,7 +282,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { return } t.waitgroup.Add(1) - t.handler(conn, false, saddr) + t.handler(conn, false, saddr, nil) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) if err != nil { @@ -322,18 +343,28 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { } conn, err = dialer.Dial("tcp", dst.String()) if err != nil { - t.link.core.log.Debugln("Failed to dial TCP:", err) + t.link.core.log.Debugf("Failed to dial %s: %s", callproto, err) return } t.waitgroup.Add(1) - t.handler(conn, false, nil) + t.handler(conn, false, nil, upgrade) } }() } -func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) { +func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade *TcpUpgrade) { defer t.waitgroup.Done() // Happens after sock.close defer sock.Close() + var upgraded bool + if upgrade != nil { + var err error + if sock, err = upgrade.upgrade(sock); err != nil { + t.link.core.log.Errorln("TCP handler upgrade failed:", err) + return + } else { + upgraded = true + } + } t.setExtraOptions(sock) stream := stream{} stream.init(sock) @@ -344,8 +375,13 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) { local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) remote, _, _ = net.SplitHostPort(socksaddr) } else { - name = "tcp://" + sock.RemoteAddr().String() - proto = "tcp" + if upgraded { + proto = upgrade.name + name = proto + "://" + sock.RemoteAddr().String() + } else { + proto = "tcp" + name = proto + "://" + sock.RemoteAddr().String() + } local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) } diff --git a/src/yggdrasil/tls.go b/src/yggdrasil/tls.go new file mode 100644 index 00000000..78fe3a9d --- /dev/null +++ b/src/yggdrasil/tls.go @@ -0,0 +1,92 @@ +package yggdrasil + +import ( + "bytes" + "crypto/ed25519" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/hex" + "encoding/pem" + "log" + "math/big" + "net" + "time" +) + +type tcptls struct { + tcp *tcp + config *tls.Config + forDialer *TcpUpgrade + forListener *TcpUpgrade +} + +func (t *tcptls) init(tcp *tcp) { + t.tcp = tcp + t.forDialer = &TcpUpgrade{ + upgrade: t.upgradeDialer, + name: "tls", + } + t.forListener = &TcpUpgrade{ + upgrade: t.upgradeListener, + name: "tls", + } + + edpriv := make(ed25519.PrivateKey, ed25519.PrivateKeySize) + copy(edpriv[:], tcp.link.core.sigPriv[:]) + + certBuf := &bytes.Buffer{} + + pubtemp := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: hex.EncodeToString(tcp.link.core.sigPub[:]), + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 365), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + derbytes, err := x509.CreateCertificate(rand.Reader, &pubtemp, &pubtemp, edpriv.Public(), edpriv) + if err != nil { + log.Fatalf("Failed to create certificate: %s", err) + } + + if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derbytes}); err != nil { + panic("failed to encode certificate into PEM") + } + + cpool := x509.NewCertPool() + cpool.AppendCertsFromPEM(derbytes) + + t.config = &tls.Config{ + RootCAs: cpool, + Certificates: []tls.Certificate{ + tls.Certificate{ + Certificate: [][]byte{derbytes}, + PrivateKey: edpriv, + }, + }, + InsecureSkipVerify: true, + MinVersion: tls.VersionTLS13, + } +} + +func (t *tcptls) upgradeListener(c net.Conn) (net.Conn, error) { + conn := tls.Server(c, t.config) + if err := conn.Handshake(); err != nil { + return c, err + } + return conn, nil +} + +func (t *tcptls) upgradeDialer(c net.Conn) (net.Conn, error) { + conn := tls.Client(c, t.config) + if err := conn.Handshake(); err != nil { + return c, err + } + return conn, nil +} From cd77727c1e63a21d3acfacad6a22edb680d4123b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 18:24:08 +0100 Subject: [PATCH 13/28] Set TCP socket options before upgrading connection --- src/yggdrasil/tcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index c4569591..7d5b80b2 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -355,6 +355,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade *TcpUpgrade) { defer t.waitgroup.Done() // Happens after sock.close defer sock.Close() + t.setExtraOptions(sock) var upgraded bool if upgrade != nil { var err error @@ -365,7 +366,6 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade upgraded = true } } - t.setExtraOptions(sock) stream := stream{} stream.init(sock) var name, proto, local, remote string From 996c6b4f474bba3e80ba0b9c3c737a7a4909cdd8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 23 Oct 2019 20:28:11 -0500 Subject: [PATCH 14/28] add one TODO comment and run gofmt --- src/yggdrasil/tls.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/tls.go b/src/yggdrasil/tls.go index 78fe3a9d..7212c4df 100644 --- a/src/yggdrasil/tls.go +++ b/src/yggdrasil/tls.go @@ -38,6 +38,7 @@ func (t *tcptls) init(tcp *tcp) { certBuf := &bytes.Buffer{} + // TODO: because NotAfter is finite, we should add some mechanism to regenerate the certificate and restart the listeners periodically for nodes with very high uptimes. Perhaps regenerate certs and restart listeners every few months or so. pubtemp := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ @@ -65,7 +66,7 @@ func (t *tcptls) init(tcp *tcp) { t.config = &tls.Config{ RootCAs: cpool, Certificates: []tls.Certificate{ - tls.Certificate{ + { Certificate: [][]byte{derbytes}, PrivateKey: edpriv, }, From c3dee478f5b6e45827ed46614a0386921eba85b3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 23 Oct 2019 20:38:09 -0500 Subject: [PATCH 15/28] fix ed25519 dependency for golang 1.12 and earlier, though we may want to update builds to 1.13 anyway... --- src/yggdrasil/tls.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/tls.go b/src/yggdrasil/tls.go index 7212c4df..26ecf1d7 100644 --- a/src/yggdrasil/tls.go +++ b/src/yggdrasil/tls.go @@ -2,7 +2,6 @@ package yggdrasil import ( "bytes" - "crypto/ed25519" "crypto/rand" "crypto/tls" "crypto/x509" @@ -13,6 +12,8 @@ import ( "math/big" "net" "time" + + "golang.org/x/crypto/ed25519" ) type tcptls struct { From f784f33c2d2670cb73bc5c1fcbf1e2c01901cc94 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 09:25:31 +0100 Subject: [PATCH 16/28] Backport fix for #581 from #583 --- src/multicast/multicast.go | 47 +++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 7044eaa2..ed8efafd 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -55,6 +55,21 @@ 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 { + var err error + phony.Block(m, func() { + err = m._start() + }) + return err +} + +func (m *Multicast) _start() error { + if m.isOpen { + return fmt.Errorf("multicast module is already started") + } + if len(m.config.GetCurrent().MulticastInterfaces) == 0 { + return fmt.Errorf("no MulticastInterfaces configured") + } + m.log.Infoln("Starting multicast module") addr, err := net.ResolveUDPAddr("udp", m.groupAddr) if err != nil { return err @@ -80,8 +95,22 @@ func (m *Multicast) Start() error { return nil } -// Stop is not implemented for multicast yet. -func (m *Multicast) Stop() error { +// IsStarted returns true if the module has been started. +func (m *Multicast) IsStarted() bool { + var isOpen bool + phony.Block(m, func() { + isOpen = m.isOpen + }) + return isOpen +} + +// Stop stops the multicast module. +func (m *Multicast) Stop() { + m.Act(m, m._stop) +} + +func (m *Multicast) _stop() { + m.log.Infoln("Stopping multicast module") m.isOpen = false if m.announcer != nil { m.announcer.Stop() @@ -90,15 +119,27 @@ func (m *Multicast) Stop() error { m.platformhandler.Stop() } m.sock.Close() - 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.Act(m, func() { m._updateConfig(config) }) +} + +func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloading multicast configuration...") + if m.IsStarted() { + if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort { + m.Stop() + } + } m.config.Replace(*config) + m.listenPort = config.LinkLocalTCPPort + if !m.IsStarted() && len(config.MulticastInterfaces) > 0 { + m.Start() + } } // GetInterfaces returns the currently known/enabled multicast interfaces. It is From d58f88d29aeccfaa7f7de59e4e41a6be78a8882d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 09:28:09 +0100 Subject: [PATCH 17/28] Update builds to Go 1.13 as this is required for TLS (apparently golang.org/x/crypto/ed25519 is not acceptable to the crypto/tls module and this prevents Yggdrasil from starting) --- .circleci/config.yml | 2 +- README.md | 2 +- go.mod | 2 ++ src/yggdrasil/tls.go | 3 +-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5a9e1e31..146d5e53 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: build-linux: docker: - - image: circleci/golang:1.12.7 + - image: circleci/golang:1.13.3 steps: - checkout diff --git a/README.md b/README.md index 07b202f1..c02151f4 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ You may also find other platform-specific wrappers, scripts or tools in the If you want to build from source, as opposed to installing one of the pre-built packages: -1. Install [Go](https://golang.org) (requires Go 1.12 or later) +1. Install [Go](https://golang.org) (requires Go 1.13 or later) 2. Clone this repository 2. Run `./build` diff --git a/go.mod b/go.mod index 83c22924..d1a3587f 100644 --- a/go.mod +++ b/go.mod @@ -17,3 +17,5 @@ require ( golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 ) + +go 1.13 diff --git a/src/yggdrasil/tls.go b/src/yggdrasil/tls.go index 26ecf1d7..7212c4df 100644 --- a/src/yggdrasil/tls.go +++ b/src/yggdrasil/tls.go @@ -2,6 +2,7 @@ package yggdrasil import ( "bytes" + "crypto/ed25519" "crypto/rand" "crypto/tls" "crypto/x509" @@ -12,8 +13,6 @@ import ( "math/big" "net" "time" - - "golang.org/x/crypto/ed25519" ) type tcptls struct { From 5ca81f916ef1952e58d8699976eb9cb01a15cde8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 09:54:57 +0100 Subject: [PATCH 18/28] Fix deadlocks --- src/multicast/multicast.go | 47 ++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index ed8efafd..82d47ed0 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "regexp" + "sync/atomic" "time" "github.com/Arceliar/phony" @@ -28,7 +29,7 @@ type Multicast struct { groupAddr string listeners map[string]*listenerInfo listenPort uint16 - isOpen bool + isOpen atomic.Value // bool announcer *time.Timer platformhandler *time.Timer } @@ -48,6 +49,7 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" + m.isOpen.Store(false) return nil } @@ -59,15 +61,16 @@ func (m *Multicast) Start() error { phony.Block(m, func() { err = m._start() }) + m.log.Debugln("Started multicast module") return err } func (m *Multicast) _start() error { - if m.isOpen { + if m.IsStarted() { return fmt.Errorf("multicast module is already started") } if len(m.config.GetCurrent().MulticastInterfaces) == 0 { - return fmt.Errorf("no MulticastInterfaces configured") + return nil } m.log.Infoln("Starting multicast module") addr, err := net.ResolveUDPAddr("udp", m.groupAddr) @@ -87,7 +90,7 @@ func (m *Multicast) _start() error { // Windows can't set this flag, so we need to handle it in other ways } - m.isOpen = true + m.isOpen.Store(true) go m.listen() m.Act(m, m.multicastStarted) m.Act(m, m.announce) @@ -97,21 +100,25 @@ func (m *Multicast) _start() error { // IsStarted returns true if the module has been started. func (m *Multicast) IsStarted() bool { - var isOpen bool - phony.Block(m, func() { - isOpen = m.isOpen - }) - return isOpen + if m.isOpen.Load() == nil { + return false + } + return m.isOpen.Load().(bool) } // Stop stops the multicast module. -func (m *Multicast) Stop() { - m.Act(m, m._stop) +func (m *Multicast) Stop() error { + var err error + phony.Block(m, func() { + err = m._stop() + }) + m.log.Debugln("Stopped multicast module") + return nil } -func (m *Multicast) _stop() { +func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") - m.isOpen = false + m.isOpen.Store(false) if m.announcer != nil { m.announcer.Stop() } @@ -119,6 +126,7 @@ func (m *Multicast) _stop() { m.platformhandler.Stop() } m.sock.Close() + return nil } // UpdateConfig updates the multicast module with the provided config.NodeConfig @@ -129,17 +137,22 @@ func (m *Multicast) UpdateConfig(config *config.NodeConfig) { } func (m *Multicast) _updateConfig(config *config.NodeConfig) { - m.log.Debugln("Reloading multicast configuration...") + m.log.Infoln("Reloading multicast configuration...") if m.IsStarted() { if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort { - m.Stop() + if err := m._stop(); err != nil { + m.log.Errorln("Error stopping multicast module:", err) + } } } m.config.Replace(*config) m.listenPort = config.LinkLocalTCPPort if !m.IsStarted() && len(config.MulticastInterfaces) > 0 { - m.Start() + if err := m._start(); err != nil { + m.log.Errorln("Error starting multicast module:", err) + } } + m.log.Debugln("Reloaded multicast configuration successfully") } // GetInterfaces returns the currently known/enabled multicast interfaces. It is @@ -312,7 +325,7 @@ func (m *Multicast) listen() { for { nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs) if err != nil { - if !m.isOpen { + if !m.IsStarted() { return } panic(err) From 51fe1940c593c2a97ecdf4118f0ffa452cc9cfc9 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 10:13:59 +0100 Subject: [PATCH 19/28] Try go 1.13 to see if this fixes failing builds --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 146d5e53..33290840 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: build-linux: docker: - - image: circleci/golang:1.13.3 + - image: circleci/golang:1.13 steps: - checkout From ee644c47e8be8d2fa41ddb614d48972a8044cbad Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 10:16:52 +0100 Subject: [PATCH 20/28] Update go.mod/go.sum, go back to 1.13.3 circleci image again --- .circleci/config.yml | 2 +- go.mod | 17 +++++++---------- go.sum | 29 ++++++++++++----------------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 33290840..146d5e53 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: build-linux: docker: - - image: circleci/golang:1.13 + - image: circleci/golang:1.13.3 steps: - checkout diff --git a/go.mod b/go.mod index d1a3587f..6d27e6ed 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,18 @@ module github.com/yggdrasil-network/yggdrasil-go +go 1.13 + require ( - github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0 + github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 - github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible + github.com/hjson/hjson-go v3.0.0+incompatible github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 - github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b // indirect - github.com/vishvananda/netlink v1.0.0 - github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 - golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 - golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + golang.org/x/net v0.0.0-20191021144547-ec77196f6094 + golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c golang.org/x/text v0.3.2 ) - -go 1.13 diff --git a/go.sum b/go.sum index bca7c231..61fc7242 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,30 @@ -github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0 h1:IOFsvAMFkgnKfSQHxXTeqb1+ODFeR5px1HCHU86KF30= -github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 h1:p3puK8Sl2xK+2FnnIvY/C0N1aqJo2kbEsdAzU+Tnv48= +github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:bLQ2Ve+eW65id3b8xEMQiAwJT4qGZeywAEMLvXjznvw= -github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/hjson/hjson-go v3.0.0+incompatible h1:mc8olpIxqF8mrEx6ePJOD6wCCOkX7+JcrKgINcJES10= +github.com/hjson/hjson-go v3.0.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= -github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= -github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= -github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= -github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= -github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 h1:YY9Pg2BEp0jeUVU60svTOaDr+fs1ySC9RbdC1Qc6wOw= github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8= +golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c h1:usSYQsGq37L8RjJc5eznJ/AbwBxn3QFFEVkWNPAejLs= +golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 0e7ed4c99771785b7817d5c5aa39f69be07124e2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 10:18:08 +0100 Subject: [PATCH 21/28] Actually really use 1.13.3 for all the builds this time --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 146d5e53..17cbfade 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -106,11 +106,11 @@ jobs: echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config - run: - name: Install Go 1.12.7 + name: Install Go 1.13.3 command: | cd /tmp - curl -LO https://dl.google.com/go/go1.12.7.darwin-amd64.pkg - sudo installer -pkg /tmp/go1.12.7.darwin-amd64.pkg -target / + curl -LO https://dl.google.com/go/go1.13.3.darwin-amd64.pkg + sudo installer -pkg /tmp/go1.13.3.darwin-amd64.pkg -target / #- run: # name: Install Gomobile @@ -146,7 +146,7 @@ jobs: build-other: docker: - - image: circleci/golang:1.12.7 + - image: circleci/golang:1.13.3 steps: - checkout From 77ffb5efc4f325bf8e252e60308d9ff44feac93f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 10:47:44 +0100 Subject: [PATCH 22/28] Fix HJSON references in go.mod/go.sum, again... --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6d27e6ed..34fdf2e6 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 - github.com/hjson/hjson-go v3.0.0+incompatible + github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 diff --git a/go.sum b/go.sum index 61fc7242..7b94caa8 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTu github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hjson/hjson-go v3.0.0+incompatible h1:mc8olpIxqF8mrEx6ePJOD6wCCOkX7+JcrKgINcJES10= -github.com/hjson/hjson-go v3.0.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:bLQ2Ve+eW65id3b8xEMQiAwJT4qGZeywAEMLvXjznvw= +github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= From de3bdfa524acbf1c3d2273ea551db723e7b00517 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 23:31:47 +0100 Subject: [PATCH 23/28] No longer use atomic for isOpen in multicast --- src/multicast/multicast.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 82d47ed0..206edab6 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "regexp" - "sync/atomic" "time" "github.com/Arceliar/phony" @@ -29,7 +28,7 @@ type Multicast struct { groupAddr string listeners map[string]*listenerInfo listenPort uint16 - isOpen atomic.Value // bool + isOpen bool announcer *time.Timer platformhandler *time.Timer } @@ -49,7 +48,6 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" - m.isOpen.Store(false) return nil } @@ -66,7 +64,7 @@ func (m *Multicast) Start() error { } func (m *Multicast) _start() error { - if m.IsStarted() { + if m.isOpen { return fmt.Errorf("multicast module is already started") } if len(m.config.GetCurrent().MulticastInterfaces) == 0 { @@ -90,7 +88,7 @@ func (m *Multicast) _start() error { // Windows can't set this flag, so we need to handle it in other ways } - m.isOpen.Store(true) + m.isOpen = true go m.listen() m.Act(m, m.multicastStarted) m.Act(m, m.announce) @@ -100,10 +98,11 @@ func (m *Multicast) _start() error { // IsStarted returns true if the module has been started. func (m *Multicast) IsStarted() bool { - if m.isOpen.Load() == nil { - return false - } - return m.isOpen.Load().(bool) + var isOpen bool + phony.Block(m, func() { + isOpen = m.isOpen + }) + return isOpen } // Stop stops the multicast module. @@ -118,7 +117,7 @@ func (m *Multicast) Stop() error { func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") - m.isOpen.Store(false) + m.isOpen = false if m.announcer != nil { m.announcer.Stop() } @@ -138,7 +137,7 @@ func (m *Multicast) UpdateConfig(config *config.NodeConfig) { func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Infoln("Reloading multicast configuration...") - if m.IsStarted() { + if m.isOpen { if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort { if err := m._stop(); err != nil { m.log.Errorln("Error stopping multicast module:", err) @@ -147,7 +146,7 @@ func (m *Multicast) _updateConfig(config *config.NodeConfig) { } m.config.Replace(*config) m.listenPort = config.LinkLocalTCPPort - if !m.IsStarted() && len(config.MulticastInterfaces) > 0 { + if !m.isOpen && len(config.MulticastInterfaces) > 0 { if err := m._start(); err != nil { m.log.Errorln("Error starting multicast module:", err) } From cd9396993057ba25ef904a890f2e01e5b73b1286 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 23:37:39 +0100 Subject: [PATCH 24/28] Fix isOpen for TUN/TAP actor --- src/tuntap/tun.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 06feede0..f0250c93 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -143,9 +143,12 @@ func (tun *TunAdapter) Start() error { } func (tun *TunAdapter) _start() error { + if tun.isOpen { + return errors.New("TUN/TAP module is already started") + } current := tun.config.GetCurrent() if tun.config == nil || tun.listener == nil || tun.dialer == nil { - return errors.New("No configuration available to TUN/TAP") + return errors.New("no configuration available to TUN/TAP") } var boxPub crypto.BoxPubKey boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey) @@ -182,7 +185,11 @@ func (tun *TunAdapter) _start() error { // IsStarted returns true if the module has been started. func (tun *TunAdapter) IsStarted() bool { - return tun.isOpen + var isOpen bool + phony.Block(tun, func() { + isOpen = tun.isOpen + }) + return isOpen } // Start the setup process for the TUN/TAP adapter. If successful, starts the From ba43c1d874879ee9d2a6ac395992c1b6925657a3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 23:59:58 +0100 Subject: [PATCH 25/28] Changelog for v0.3.11 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf0bfa5..0557012e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> + +## [0.3.11] - 2019-10-25 +### Added +- Support for TLS listeners and peers has been added, allowing the use of `tls://host:port` in `Peers`, `InterfacePeers` and `Listen` configuration settings - this allows hiding Yggdrasil peerings inside regular TLS connections + +### Changed +- Go 1.13 or later is now required for building Yggdrasil +- Some exported API functions have been updated to work with standard Go interfaces: + - `net.Conn` instead of `yggdrasil.Conn` + - `net.Dialer` instead of `yggdrasil.Dialer` + - `net.Listener` instead of `yggdrasil.Listener` +- Session metadata is now updated correctly when a search completes for a node to which we already have an open session +- Multicast module reloading behaviour has been improved + +### Fixed +- An incorrectly held mutex in the crypto-key routing code has been fixed +- Dial timeouts are now handled more safely in the event of a nil context +- Multicast module no longer opens a listener socket if no multicast interfaces are configured + ## [0.3.10] - 2019-10-10 ### Added - The core library now includes several unit tests for peering and `yggdrasil.Conn` connections From aea41f464eb06a076e4c23a3f2678b131a747fb8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 24 Oct 2019 21:47:02 -0500 Subject: [PATCH 26/28] Update CHANGELOG.md --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0557012e..83c4bb0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,14 +34,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Go 1.13 or later is now required for building Yggdrasil - Some exported API functions have been updated to work with standard Go interfaces: - `net.Conn` instead of `yggdrasil.Conn` - - `net.Dialer` instead of `yggdrasil.Dialer` + - `net.Dialer` (or the interface that type would satisfy, if one was defined) instead of `yggdrasil.Dialer` - `net.Listener` instead of `yggdrasil.Listener` - Session metadata is now updated correctly when a search completes for a node to which we already have an open session - Multicast module reloading behaviour has been improved ### Fixed - An incorrectly held mutex in the crypto-key routing code has been fixed -- Dial timeouts are now handled more safely in the event of a nil context - Multicast module no longer opens a listener socket if no multicast interfaces are configured ## [0.3.10] - 2019-10-10 From 9337b17cffb3a733c16bf9bc1542c7767deb98e5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 24 Oct 2019 21:50:10 -0500 Subject: [PATCH 27/28] update a few deps and run 'go mod tidy' --- go.mod | 5 ++++- go.sum | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 34fdf2e6..a45600a3 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,12 @@ require ( github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 + github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b // indirect + github.com/vishvananda/netlink v1.0.0 + github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/net v0.0.0-20191021144547-ec77196f6094 - golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c + golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb golang.org/x/text v0.3.2 ) diff --git a/go.sum b/go.sum index 7b94caa8..ecfa4626 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,12 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= +github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= +github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= +github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= +github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= +github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 h1:YY9Pg2BEp0jeUVU60svTOaDr+fs1ySC9RbdC1Qc6wOw= github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -22,8 +28,8 @@ golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+T golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c h1:usSYQsGq37L8RjJc5eznJ/AbwBxn3QFFEVkWNPAejLs= -golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb h1:ZxSglHghKPYD8WDeRUzRJrUJtDF0PxsTUSxyqr9/5BI= +golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= From 7f758b7bf7b1beeb6ae26e1f54530c15499041e5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 24 Oct 2019 21:55:25 -0500 Subject: [PATCH 28/28] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83c4bb0e..903ffc7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Go 1.13 or later is now required for building Yggdrasil - Some exported API functions have been updated to work with standard Go interfaces: - `net.Conn` instead of `yggdrasil.Conn` - - `net.Dialer` (or the interface that type would satisfy, if one was defined) instead of `yggdrasil.Dialer` + - `net.Dialer` (the interface it would satisfy if it wasn't a concrete type) instead of `yggdrasil.Dialer` - `net.Listener` instead of `yggdrasil.Listener` - Session metadata is now updated correctly when a search completes for a node to which we already have an open session - Multicast module reloading behaviour has been improved