Adam Ierymenko hace 5 años
padre
commit
964c235ecf

+ 1 - 1
go/cmd/zerotier/cli/help.go

@@ -63,7 +63,7 @@ Commands:
     validate <identity>                 Locally validate an identity
     sign <identity> <file>              Sign a file with an identity's key
     verify <identity> <file> <sig>      Verify a signature
-    makeroot <identity> <address> [...] Make a root spec (see docs)
+    makeroot <identity> <address[,...]> Make a root spec (see docs)
 
 The 'service' command does not exit until the service receives a signal.
 This is typically run from launchd (Mac), systemd or init (Linux), a Windows

+ 1 - 1
go/go.mod

@@ -5,5 +5,5 @@ go 1.13
 require (
 	github.com/Microsoft/go-winio v0.4.14
 	github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95
-	golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
+	golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
 )

+ 2 - 0
go/go.sum

@@ -19,3 +19,5 @@ golang.org/x/sys v0.0.0-20200120151820-655fe14d7479 h1:LhLiKguPgZL+Tglay4GhVtfF0
 golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

+ 2 - 5
go/pkg/zerotier/fingerprint.go

@@ -18,21 +18,18 @@ package zerotier
 import "C"
 
 import (
-	"encoding/base32"
 	"errors"
 	"strings"
 	"unsafe"
 )
 
-var ztBase32 = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding)
-
 type Fingerprint struct {
 	Address Address  `json:"address"`
 	Hash    [48]byte `json:"hash"`
 }
 
 func NewFingerprintFromString(fps string) (*Fingerprint, error) {
-	fpb, err := ztBase32.DecodeString(strings.TrimSpace(strings.ToLower(fps)))
+	fpb, err := Base32StdLowerCase.DecodeString(strings.TrimSpace(strings.ToLower(fps)))
 	if err != nil {
 		return nil, err
 	}
@@ -49,7 +46,7 @@ func (fp *Fingerprint) String() string {
 	var tmp [53]byte
 	fp.Address.CopyTo(tmp[0:5])
 	copy(tmp[5:],fp.Hash[:])
-	return ztBase32.EncodeToString(tmp[:])
+	return Base32StdLowerCase.EncodeToString(tmp[:])
 }
 
 func (fp *Fingerprint) apiFingerprint() *C.ZT_Fingerprint {

+ 15 - 29
go/pkg/zerotier/identity.go

@@ -27,18 +27,14 @@ import (
 	"unsafe"
 )
 
-// IdentityTypeC25519 is a classic Curve25519/Ed25519 identity
-const IdentityTypeC25519 = 0
-
-// IdentityTypeP384 is an identity containing both NIST P-384 and Curve25519/Ed25519 key types and leveraging both when possible
-const IdentityTypeP384 = 1
-
-// Sizes of components of different identity types
 const (
-	IdentityTypeC25519PublicKeySize  = 64  // C25519/Ed25519 keys
-	IdentityTypeC25519PrivateKeySize = 64  // C25519/Ed25519 private keys
-	IdentityTypeP384PublicKeySize    = 209 // C25519/Ed25519, P-384 point-compressed public, P-384 self-signature
-	IdentityTypeP384PrivateKeySize   = 112 // C25519/Ed25519 and P-384 private keys
+	IdentityTypeC25519 = C.ZT_IDENTITY_TYPE_C25519
+	IdentityTypeP384   = C.ZT_IDENTITY_TYPE_P384
+
+	IdentityTypeC25519PublicKeySize  = C.ZT_IDENTITY_C25519_PUBLIC_KEY_SIZE
+	IdentityTypeC25519PrivateKeySize = C.ZT_IDENTITY_C25519_PRIVATE_KEY_SIZE
+	IdentityTypeP384PublicKeySize    = C.ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE
+	IdentityTypeP384PrivateKeySize   = C.ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE
 )
 
 // Identity is precisely what it sounds like: the address and associated keys for a ZeroTier node
@@ -79,18 +75,18 @@ func newIdentityFromCIdentity(cid unsafe.Pointer) (*Identity, error) {
 	return id, nil
 }
 
-// cIdentity returns a pointer to the core ZT_Identity instance or nil/0 on error.
-func (id *Identity) cIdentity() unsafe.Pointer {
+// initCIdentityPtr returns a pointer to the core ZT_Identity instance or nil/0 on error.
+func (id *Identity) initCIdentityPtr() bool {
 	if uintptr(id.cid) == 0 {
 		idCStr := C.CString(id.String())
 		defer C.free(unsafe.Pointer(idCStr))
 		id.cid = C.ZT_Identity_fromString(idCStr)
 		if uintptr(id.cid) == 0 {
-			return nil
+			return false
 		}
 		runtime.SetFinalizer(id, identityFinalizer)
 	}
-	return id.cid
+	return true
 }
 
 // NewIdentity generates a new identity of the selected type
@@ -197,8 +193,7 @@ func (id *Identity) String() string {
 
 // LocallyValidate performs local self-validation of this identity
 func (id *Identity) LocallyValidate() bool {
-	id.cIdentity()
-	if uintptr(id.cid) == 0 {
+	if !id.initCIdentityPtr() {
 		return false
 	}
 	return C.ZT_Identity_validate(id.cid) != 0
@@ -206,8 +201,7 @@ func (id *Identity) LocallyValidate() bool {
 
 // Sign signs a message with this identity
 func (id *Identity) Sign(msg []byte) ([]byte, error) {
-	id.cIdentity()
-	if uintptr(id.cid) == 0 {
+	if !id.initCIdentityPtr() {
 		return nil, ErrInvalidKey
 	}
 
@@ -226,15 +220,9 @@ func (id *Identity) Sign(msg []byte) ([]byte, error) {
 
 // Verify verifies a signature
 func (id *Identity) Verify(msg, sig []byte) bool {
-	if len(sig) == 0 {
-		return false
-	}
-
-	id.cIdentity()
-	if uintptr(id.cid) == 0 {
+	if len(sig) == 0 || !id.initCIdentityPtr() {
 		return false
 	}
-
 	var dataP unsafe.Pointer
 	if len(msg) > 0 {
 		dataP = unsafe.Pointer(&msg[0])
@@ -247,9 +235,7 @@ func (id *Identity) MakeRoot(addresses []InetAddress) ([]byte, error) {
 	if len(addresses) == 0 {
 		return nil, errors.New("at least one static address must be specified for a root")
 	}
-
-	id.cIdentity()
-	if uintptr(id.cid) == 0 {
+	if !id.initCIdentityPtr() {
 		return nil, errors.New("error initializing ZT_Identity")
 	}
 

+ 20 - 18
go/pkg/zerotier/nativetap.go

@@ -159,25 +159,27 @@ func (t *nativeTap) AddMulticastGroupChangeHandler(handler func(bool, *Multicast
 }
 
 func handleTapMulticastGroupChange(gn unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t, added bool) {
+	node := cNodeRefs[uintptr(gn)]
+	if node == nil {
+		return
+	}
+	node.networksLock.RLock()
+	network := node.networks[NetworkID(nwid)]
+	node.networksLock.RUnlock()
+	if network == nil {
+		return
+	}
+
+	node.runWaitGroup.Add(1)
 	go func() {
-		nodesByUserPtrLock.RLock()
-		node := nodesByUserPtr[uintptr(gn)]
-		nodesByUserPtrLock.RUnlock()
-		if node == nil {
-			return
-		}
-		node.networksLock.RLock()
-		network := node.networks[NetworkID(nwid)]
-		node.networksLock.RUnlock()
-		if network != nil {
-			tap, _ := network.tap.(*nativeTap)
-			if tap != nil {
-				mg := &MulticastGroup{MAC: MAC(mac), ADI: uint32(adi)}
-				tap.multicastGroupHandlersLock.Lock()
-				defer tap.multicastGroupHandlersLock.Unlock()
-				for _, h := range tap.multicastGroupHandlers {
-					h(added, mg)
-				}
+		defer node.runWaitGroup.Done()
+		tap, _ := network.tap.(*nativeTap)
+		if tap != nil {
+			mg := &MulticastGroup{MAC: MAC(mac), ADI: uint32(adi)}
+			tap.multicastGroupHandlersLock.Lock()
+			defer tap.multicastGroupHandlersLock.Unlock()
+			for _, h := range tap.multicastGroupHandlers {
+				h(added, mg)
 			}
 		}
 	}()

+ 189 - 217
go/pkg/zerotier/node.go

@@ -45,7 +45,6 @@ import (
 
 var nullLogger = log.New(ioutil.Discard, "", 0)
 
-// Network status states
 const (
 	NetworkIDStringLength = 16
 	AddressStringLength   = 10
@@ -62,16 +61,17 @@ const (
 	networkConfigOpUpdate int = C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE
 
 	defaultVirtualNetworkMTU = C.ZT_DEFAULT_MTU
+
+	// maxCNodeRefs is the maximum number of Node instances that can be created in this process (increasing is fine)
+	maxCNodeRefs = 4
 )
 
 var (
 	// PlatformDefaultHomePath is the default location of ZeroTier's working path on this system
 	PlatformDefaultHomePath string = C.GoString(C.ZT_PLATFORM_DEFAULT_HOMEPATH)
 
-	// This map is used to get the Go Node object from a pointer passed back in via C callbacks
-	nodesByUserPtr     = make(map[uintptr]*Node)
-	nodesByUserPtrCtr  = uintptr(0)
-	nodesByUserPtrLock sync.RWMutex
+	cNodeRefs    [maxCNodeRefs]*Node
+	cNodeRefUsed [maxCNodeRefs]uint32
 
 	CoreVersionMajor    int
 	CoreVersionMinor    int
@@ -90,6 +90,9 @@ func init() {
 
 // Node is an instance of a virtual port on the global switch.
 type Node struct {
+	// an arbitrary uintptr given to the core as its pointer back to Go's Node instance
+	cPtr uintptr
+
 	// networks contains networks we have joined, and networksByMAC by their local MAC address
 	networks      map[NetworkID]*Network
 	networksByMAC map[MAC]*Network // locked by networksLock
@@ -107,10 +110,13 @@ type Node struct {
 	peersPath       string
 	networksPath    string
 	localConfigPath string
+	infoLogPath     string
+	errorLogPath    string
 
 	// localConfig is the current state of local.conf
-	localConfig     LocalConfig
-	localConfigLock sync.RWMutex
+	localConfig         *LocalConfig
+	previousLocalConfig *LocalConfig
+	localConfigLock     sync.RWMutex
 
 	// logs for information, errors, and trace output
 	infoLogW  *sizeLimitWriter
@@ -120,13 +126,13 @@ type Node struct {
 	errLog    *log.Logger
 	traceLog  *log.Logger
 
-	// gn is the instance of GoNode, the Go wrapper and multithreaded I/O engine
+	// gn is the GoNode instance
 	gn *C.ZT_GoNode
 
-	// zn is the underlying instance of the ZeroTier core
+	// zn is the underlying ZT_Node (ZeroTier::Node) instance
 	zn unsafe.Pointer
 
-	// id is the identity of this node
+	// id is the identity of this node (includes private key)
 	id *Identity
 
 	// HTTP server instances: one for a named socket (Unix domain or Windows named pipe) and one on a local TCP socket
@@ -135,14 +141,34 @@ type Node struct {
 
 	// runWaitGroup is used to wait for all node goroutines on shutdown
 	runWaitGroup sync.WaitGroup
-
-	// an arbitrary uintptr given to the core as its pointer back to Go's Node instance
-	cPtr uintptr
 }
 
 // NewNode creates and initializes a new instance of the ZeroTier node service
 func NewNode(basePath string) (n *Node, err error) {
 	n = new(Node)
+
+	// Register this with the cNodeRefs lookup array and set up a deferred function
+	// to unregister this if we exit before the end of the constructor such as by
+	// returning an error.
+	cPtr := -1
+	for i:=0;i<maxCNodeRefs;i++ {
+		if atomic.CompareAndSwapUint32(&cNodeRefUsed[i],0,1) {
+			cNodeRefs[i] = n
+			cPtr = i
+			n.cPtr = uintptr(i)
+			break
+		}
+	}
+	if cPtr < 0 {
+		return nil, errors.New("too many nodes in this instance")
+	}
+	defer func() {
+		if cPtr >= 0 {
+			atomic.StoreUint32(&cNodeRefUsed[cPtr],0)
+			cNodeRefs[cPtr] = nil
+		}
+	}()
+
 	n.networks = make(map[NetworkID]*Network)
 	n.networksByMAC = make(map[MAC]*Network)
 	n.interfaceAddresses = make(map[string]net.IP)
@@ -168,17 +194,20 @@ func NewNode(basePath string) (n *Node, err error) {
 	n.localConfigPath = path.Join(basePath, "local.conf")
 
 	_, identitySecretNotFoundErr := os.Stat(path.Join(basePath, "identity.secret"))
+	n.localConfig = new(LocalConfig)
 	err = n.localConfig.Read(n.localConfigPath, true, identitySecretNotFoundErr != nil)
 	if err != nil {
 		return
 	}
 
+	n.infoLogPath = path.Join(basePath, "info.log")
+	n.errorLogPath = path.Join(basePath, "error.log")
 	if n.localConfig.Settings.LogSizeMax >= 0 {
-		n.infoLogW, err = sizeLimitWriterOpen(path.Join(basePath, "info.log"))
+		n.infoLogW, err = sizeLimitWriterOpen(n.infoLogPath)
 		if err != nil {
 			return
 		}
-		n.errLogW, err = sizeLimitWriterOpen(path.Join(basePath, "error.log"))
+		n.errLogW, err = sizeLimitWriterOpen(n.errorLogPath)
 		if err != nil {
 			return
 		}
@@ -186,6 +215,7 @@ func NewNode(basePath string) (n *Node, err error) {
 		n.errLog = log.New(n.errLogW, "", log.LstdFlags)
 	} else {
 		n.infoLog = nullLogger
+		n.errLog = nullLogger
 	}
 
 	if n.localConfig.Settings.PortSearch {
@@ -249,29 +279,17 @@ func NewNode(basePath string) (n *Node, err error) {
 		return nil, err
 	}
 
-	nodesByUserPtrLock.Lock()
-	nodesByUserPtrCtr++
-	n.cPtr = nodesByUserPtrCtr
-	nodesByUserPtr[n.cPtr] = n
-	nodesByUserPtrLock.Unlock()
-
 	cPath := C.CString(basePath)
 	n.gn = C.ZT_GoNode_new(cPath, C.uintptr_t(n.cPtr))
 	C.free(unsafe.Pointer(cPath))
 	if n.gn == nil {
 		n.infoLog.Println("FATAL: node initialization failed")
-		nodesByUserPtrLock.Lock()
-		delete(nodesByUserPtr, n.cPtr)
-		nodesByUserPtrLock.Unlock()
 		return nil, ErrNodeInitFailed
 	}
 	n.zn = unsafe.Pointer(C.ZT_GoNode_getNode(n.gn))
 	n.id, err = newIdentityFromCIdentity(C.ZT_Node_identity(n.zn))
 	if err != nil {
 		n.infoLog.Printf("FATAL: error obtaining node's identity")
-		nodesByUserPtrLock.Lock()
-		delete(nodesByUserPtr, n.cPtr)
-		nodesByUserPtrLock.Unlock()
 		C.ZT_GoNode_delete(n.gn)
 		return nil, err
 	}
@@ -281,137 +299,20 @@ func NewNode(basePath string) (n *Node, err error) {
 	n.runWaitGroup.Add(1)
 	go func() {
 		defer n.runWaitGroup.Done()
-		var previousExplicitExternalAddresses []ExternalAddress // empty at first so these will be configured
 		lastMaintenanceRun := int64(0)
-
 		for atomic.LoadUint32(&n.running) != 0 {
 			time.Sleep(500 * time.Millisecond)
-
 			nowS := time.Now().Unix()
 			if (nowS - lastMaintenanceRun) >= 30 {
 				lastMaintenanceRun = nowS
-
-				//////////////////////////////////////////////////////////////////////
-				n.localConfigLock.RLock()
-
-				// Get local physical interface addresses, excluding blacklisted and ZeroTier-created interfaces
-				interfaceAddresses := make(map[string]net.IP)
-				ifs, _ := net.Interfaces()
-				if len(ifs) > 0 {
-					n.networksLock.RLock()
-				scanInterfaces:
-					for _, i := range ifs {
-						for _, bl := range n.localConfig.Settings.InterfacePrefixBlacklist {
-							if strings.HasPrefix(strings.ToLower(i.Name), strings.ToLower(bl)) {
-								continue scanInterfaces
-							}
-						}
-						m, _ := NewMACFromBytes(i.HardwareAddr)
-						if _, isZeroTier := n.networksByMAC[m]; !isZeroTier {
-							addrs, _ := i.Addrs()
-							for _, a := range addrs {
-								ipn, _ := a.(*net.IPNet)
-								if ipn != nil && len(ipn.IP) > 0 && !ipn.IP.IsLinkLocalUnicast() && !ipn.IP.IsMulticast() {
-									interfaceAddresses[ipn.IP.String()] = ipn.IP
-								}
-							}
-						}
-					}
-					n.networksLock.RUnlock()
-				}
-
-				// Open or close locally bound UDP ports for each local interface address.
-				// This opens ports if they are not already open and then closes ports if
-				// they are open but no longer seem to exist.
-				interfaceAddressesChanged := false
-				ports := make([]int, 0, 2)
-				if n.localConfig.Settings.PrimaryPort > 0 && n.localConfig.Settings.PrimaryPort < 65536 {
-					ports = append(ports, n.localConfig.Settings.PrimaryPort)
-				}
-				if n.localConfig.Settings.SecondaryPort > 0 && n.localConfig.Settings.SecondaryPort < 65536 {
-					ports = append(ports, n.localConfig.Settings.SecondaryPort)
-				}
-				n.interfaceAddressesLock.Lock()
-				for astr, ipn := range interfaceAddresses {
-					if _, alreadyKnown := n.interfaceAddresses[astr]; !alreadyKnown {
-						interfaceAddressesChanged = true
-						ipCstr := C.CString(ipn.String())
-						for pn, p := range ports {
-							n.infoLog.Printf("UDP binding to port %d on interface %s", p, astr)
-							primary := C.int(0)
-							if pn == 0 {
-								primary = 1
-							}
-							C.ZT_GoNode_phyStartListen(n.gn, nil, ipCstr, C.int(p), primary)
-						}
-						C.free(unsafe.Pointer(ipCstr))
-					}
-				}
-				for astr, ipn := range n.interfaceAddresses {
-					if _, stillPresent := interfaceAddresses[astr]; !stillPresent {
-						interfaceAddressesChanged = true
-						ipCstr := C.CString(ipn.String())
-						for _, p := range ports {
-							n.infoLog.Printf("UDP closing socket bound to port %d on interface %s", p, astr)
-							C.ZT_GoNode_phyStopListen(n.gn, nil, ipCstr, C.int(p))
-						}
-						C.free(unsafe.Pointer(ipCstr))
-					}
-				}
-				n.interfaceAddresses = interfaceAddresses
-				n.interfaceAddressesLock.Unlock()
-
-				// Update node's understanding of our interface addresses if they've changed
-				if interfaceAddressesChanged || reflect.DeepEqual(n.localConfig.Settings.ExplicitAddresses, previousExplicitExternalAddresses) {
-					previousExplicitExternalAddresses = n.localConfig.Settings.ExplicitAddresses
-
-					// Consolidate explicit and detected interface addresses, removing duplicates.
-					externalAddresses := make(map[[3]uint64]*ExternalAddress)
-					for _, ip := range interfaceAddresses {
-						for _, p := range ports {
-							a := &ExternalAddress{
-								InetAddress: InetAddress{
-									IP:   ip,
-									Port: p,
-								},
-								Permanent: false,
-							}
-							externalAddresses[a.key()] = a
-						}
-					}
-					for _, a := range n.localConfig.Settings.ExplicitAddresses {
-						externalAddresses[a.key()] = &a
-					}
-
-					if len(externalAddresses) > 0 {
-						cAddrs := make([]C.ZT_InterfaceAddress, len(externalAddresses))
-						cAddrCount := 0
-						for _, a := range externalAddresses {
-							makeSockaddrStorage(a.IP, a.Port, &(cAddrs[cAddrCount].address))
-							if a.Permanent {
-								cAddrs[cAddrCount].permanent = 1
-							} else {
-								cAddrs[cAddrCount].permanent = 0
-							}
-							cAddrCount++
-						}
-						C.ZT_Node_setInterfaceAddresses(n.zn, &cAddrs[0], C.uint(cAddrCount))
-					} else {
-						C.ZT_Node_setInterfaceAddresses(n.zn, nil, 0)
-					}
-				}
-
-				// Trim infoLog if it's gone over its size limit
-				if n.localConfig.Settings.LogSizeMax > 0 && n.infoLogW != nil {
-					_ = n.infoLogW.trim(n.localConfig.Settings.LogSizeMax*1024, 0.5, true)
-				}
-
-				n.localConfigLock.RUnlock()
-				//////////////////////////////////////////////////////////////////////
+				n.runMaintenance()
 			}
 		}
 	}()
 
+	// Stop deferred cPtr table cleanup function from deregistering this instance
+	cPtr = -1
+
 	return n, nil
 }
 
@@ -429,9 +330,8 @@ func (n *Node) Close() {
 
 		n.runWaitGroup.Wait()
 
-		nodesByUserPtrLock.Lock()
-		delete(nodesByUserPtr, uintptr(unsafe.Pointer(n)))
-		nodesByUserPtrLock.Unlock()
+		cNodeRefs[n.cPtr] = nil
+		atomic.StoreUint32(&cNodeRefUsed[n.cPtr],0)
 	}
 }
 
@@ -457,7 +357,7 @@ func (n *Node) InterfaceAddresses() []net.IP {
 }
 
 // LocalConfig gets this node's local configuration
-func (n *Node) LocalConfig() LocalConfig {
+func (n *Node) LocalConfig() *LocalConfig {
 	n.localConfigLock.RLock()
 	defer n.localConfigLock.RUnlock()
 	return n.localConfig
@@ -477,24 +377,14 @@ func (n *Node) SetLocalConfig(lc *LocalConfig) (restartRequired bool, err error)
 		}
 	}
 
-	if n.localConfig.Settings.PrimaryPort != lc.Settings.PrimaryPort || n.localConfig.Settings.SecondaryPort != lc.Settings.SecondaryPort {
+	if n.localConfig.Settings.PrimaryPort != lc.Settings.PrimaryPort ||
+		n.localConfig.Settings.SecondaryPort != lc.Settings.SecondaryPort ||
+		n.localConfig.Settings.LogSizeMax != lc.Settings.LogSizeMax {
 		restartRequired = true
 	}
-	if lc.Settings.LogSizeMax < 0 {
-		n.infoLog = nullLogger
-		_ = n.infoLogW.Close()
-		n.infoLogW = nil
-	} else if n.infoLogW != nil {
-		n.infoLogW, err = sizeLimitWriterOpen(path.Join(n.basePath, "service.infoLog"))
-		if err == nil {
-			n.infoLog = log.New(n.infoLogW, "", log.LstdFlags)
-		} else {
-			n.infoLog = nullLogger
-			n.infoLogW = nil
-		}
-	}
 
-	n.localConfig = *lc
+	n.previousLocalConfig = n.localConfig
+	n.localConfig = lc
 
 	return
 }
@@ -555,30 +445,11 @@ func (n *Node) Leave(nwid NetworkID) error {
 	return nil
 }
 
-// AddRoot adds a root server with an optional bootstrap address for establishing first contact.
-// If you're already using roots backed by proper global LF data stores the bootstrap address may
-// be unnecessary as your node can probably find the new root automatically.
-func (n *Node) AddRoot(id *Identity, bootstrap *InetAddress) error {
-	if id == nil {
-		return ErrInvalidParameter
-	}
-	var cBootstrap C.struct_sockaddr_storage
-	if bootstrap != nil {
-		makeSockaddrStorage(bootstrap.IP, bootstrap.Port, &cBootstrap)
-	} else {
-		zeroSockaddrStorage(&cBootstrap)
-	}
-	C.ZT_Node_addRoot(n.zn, nil, id.cIdentity(), &cBootstrap)
+func (n *Node) AddRoot(spec []byte) error {
 	return nil
 }
 
-// RemoveRoot removes a root server for this node.
-// This doesn't instantly close paths to the given peer or forget about it. It just
-// demotes it to a normal peer.
-func (n *Node) RemoveRoot(id *Identity) {
-	if id != nil {
-		C.ZT_Node_removeRoot(n.zn, nil, id.cIdentity())
-	}
+func (n *Node) RemoveRoot(address Address) {
 }
 
 // GetNetwork looks up a network by ID or returns nil if not joined
@@ -618,7 +489,7 @@ func (n *Node) Peers() []*Peer {
 
 			p2.Paths = make([]Path, 0, int(p.pathCount))
 			for j := 0; j < len(p2.Paths); j++ {
-				pt := (*C.ZT_PeerPhysicalPath)(unsafe.Pointer(uintptr(unsafe.Pointer(p.paths)) + uintptr(j * C.sizeof_ZT_PeerPhysicalPath)))
+				pt := (*C.ZT_PeerPhysicalPath)(unsafe.Pointer(uintptr(unsafe.Pointer(p.paths)) + uintptr(j*C.sizeof_ZT_PeerPhysicalPath)))
 				if pt.alive != 0 {
 					a := sockaddrStorageToUDPAddr(&pt.address)
 					if a != nil {
@@ -645,6 +516,116 @@ func (n *Node) Peers() []*Peer {
 
 // --------------------------------------------------------------------------------------------------------------------
 
+func (n *Node) runMaintenance() {
+	n.localConfigLock.RLock()
+	defer n.localConfigLock.RUnlock()
+
+	// Get local physical interface addresses, excluding blacklisted and ZeroTier-created interfaces
+	interfaceAddresses := make(map[string]net.IP)
+	ifs, _ := net.Interfaces()
+	if len(ifs) > 0 {
+		n.networksLock.RLock()
+	scanInterfaces:
+		for _, i := range ifs {
+			for _, bl := range n.localConfig.Settings.InterfacePrefixBlacklist {
+				if strings.HasPrefix(strings.ToLower(i.Name), strings.ToLower(bl)) {
+					continue scanInterfaces
+				}
+			}
+			m, _ := NewMACFromBytes(i.HardwareAddr)
+			if _, isZeroTier := n.networksByMAC[m]; !isZeroTier {
+				addrs, _ := i.Addrs()
+				for _, a := range addrs {
+					ipn, _ := a.(*net.IPNet)
+					if ipn != nil && len(ipn.IP) > 0 && !ipn.IP.IsLinkLocalUnicast() && !ipn.IP.IsMulticast() {
+						interfaceAddresses[ipn.IP.String()] = ipn.IP
+					}
+				}
+			}
+		}
+		n.networksLock.RUnlock()
+	}
+
+	// Open or close locally bound UDP ports for each local interface address.
+	// This opens ports if they are not already open and then closes ports if
+	// they are open but no longer seem to exist.
+	interfaceAddressesChanged := false
+	ports := make([]int, 0, 2)
+	if n.localConfig.Settings.PrimaryPort > 0 && n.localConfig.Settings.PrimaryPort < 65536 {
+		ports = append(ports, n.localConfig.Settings.PrimaryPort)
+	}
+	if n.localConfig.Settings.SecondaryPort > 0 && n.localConfig.Settings.SecondaryPort < 65536 {
+		ports = append(ports, n.localConfig.Settings.SecondaryPort)
+	}
+	n.interfaceAddressesLock.Lock()
+	for astr, ipn := range interfaceAddresses {
+		if _, alreadyKnown := n.interfaceAddresses[astr]; !alreadyKnown {
+			interfaceAddressesChanged = true
+			ipCstr := C.CString(ipn.String())
+			for pn, p := range ports {
+				n.infoLog.Printf("UDP binding to port %d on interface %s", p, astr)
+				primary := C.int(0)
+				if pn == 0 {
+					primary = 1
+				}
+				C.ZT_GoNode_phyStartListen(n.gn, nil, ipCstr, C.int(p), primary)
+			}
+			C.free(unsafe.Pointer(ipCstr))
+		}
+	}
+	for astr, ipn := range n.interfaceAddresses {
+		if _, stillPresent := interfaceAddresses[astr]; !stillPresent {
+			interfaceAddressesChanged = true
+			ipCstr := C.CString(ipn.String())
+			for _, p := range ports {
+				n.infoLog.Printf("UDP closing socket bound to port %d on interface %s", p, astr)
+				C.ZT_GoNode_phyStopListen(n.gn, nil, ipCstr, C.int(p))
+			}
+			C.free(unsafe.Pointer(ipCstr))
+		}
+	}
+	n.interfaceAddresses = interfaceAddresses
+	n.interfaceAddressesLock.Unlock()
+
+	// Update node's interface address list if detected or configured addresses have changed.
+	if interfaceAddressesChanged || n.previousLocalConfig == nil || !reflect.DeepEqual(n.localConfig.Settings.ExplicitAddresses, n.previousLocalConfig.Settings.ExplicitAddresses) {
+		var cAddrs []C.ZT_InterfaceAddress
+		externalAddresses := make(map[[3]uint64]*InetAddress)
+		for _, a := range n.localConfig.Settings.ExplicitAddresses {
+			ak := a.key()
+			if _, have := externalAddresses[ak]; !have {
+				externalAddresses[ak] = &a
+				cAddrs = append(cAddrs, C.ZT_InterfaceAddress{})
+				makeSockaddrStorage(a.IP, a.Port, &(cAddrs[len(cAddrs)-1].address))
+				cAddrs[len(cAddrs)-1].permanent = 1 // explicit addresses are permanent, meaning they can be put in a locator
+			}
+		}
+		for _, ip := range interfaceAddresses {
+			for _, p := range ports {
+				a := InetAddress{ IP: ip, Port: p }
+				ak := a.key()
+				if _, have := externalAddresses[ak]; !have {
+					externalAddresses[ak] = &a
+					cAddrs = append(cAddrs, C.ZT_InterfaceAddress{})
+					makeSockaddrStorage(a.IP, a.Port, &(cAddrs[len(cAddrs)-1].address))
+					cAddrs[len(cAddrs)-1].permanent = 0
+				}
+			}
+		}
+
+		if len(cAddrs) > 0 {
+			C.ZT_Node_setInterfaceAddresses(n.zn, &cAddrs[0], C.uint(len(cAddrs)))
+		} else {
+			C.ZT_Node_setInterfaceAddresses(n.zn, nil, 0)
+		}
+	}
+
+	// Trim infoLog if it's gone over its size limit
+	if n.localConfig.Settings.LogSizeMax > 0 && n.infoLogW != nil {
+		_ = n.infoLogW.trim(n.localConfig.Settings.LogSizeMax*1024, 0.5, true)
+	}
+}
+
 func (n *Node) multicastSubscribe(nwid uint64, mg *MulticastGroup) {
 	C.ZT_Node_multicastSubscribe(n.zn, nil, C.uint64_t(nwid), C.uint64_t(mg.MAC), C.ulong(mg.ADI))
 }
@@ -743,20 +724,17 @@ func (n *Node) handleTrace(traceMessage string) {
 	}
 }
 
-// func (n *Node) handleUserMessage(origin *Identity, messageTypeID uint64, data []byte) {
-// }
-
-//////////////////////////////////////////////////////////////////////////////
-
 // These are callbacks called by the core and GoGlue stuff to talk to the
 // service. These launch goroutines to do their work where possible to
 // avoid blocking anything in the core.
 
 //export goPathCheckFunc
 func goPathCheckFunc(gn, _ unsafe.Pointer, af C.int, ip unsafe.Pointer, _ C.int) C.int {
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
+	node := cNodeRefs[uintptr(gn)]
+	if node == nil {
+		return 0
+	}
+
 	var nip net.IP
 	if af == syscall.AF_INET {
 		nip = ((*[4]byte)(ip))[:]
@@ -765,17 +743,16 @@ func goPathCheckFunc(gn, _ unsafe.Pointer, af C.int, ip unsafe.Pointer, _ C.int)
 	} else {
 		return 0
 	}
-	if node != nil && len(nip) > 0 && node.pathCheck(nip) {
+	if len(nip) > 0 && node.pathCheck(nip) {
 		return 1
 	}
+
 	return 0
 }
 
 //export goPathLookupFunc
 func goPathLookupFunc(gn unsafe.Pointer, _ C.uint64_t, _ int, identity, familyP, ipP, portP unsafe.Pointer) C.int {
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
+	node := cNodeRefs[uintptr(gn)]
 	if node == nil {
 		return 0
 	}
@@ -807,19 +784,17 @@ func goPathLookupFunc(gn unsafe.Pointer, _ C.uint64_t, _ int, identity, familyP,
 
 //export goStateObjectPutFunc
 func goStateObjectPutFunc(gn unsafe.Pointer, objType C.int, id, data unsafe.Pointer, len C.int) {
+	node := cNodeRefs[uintptr(gn)]
+	if node == nil {
+		return
+	}
+
 	id2 := *((*[2]uint64)(id))
 	var data2 []byte
 	if len > 0 {
 		data2 = C.GoBytes(data, len)
 	}
 
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
-	if node == nil {
-		return
-	}
-
 	node.runWaitGroup.Add(1)
 	go func() {
 		if len < 0 {
@@ -833,12 +808,11 @@ func goStateObjectPutFunc(gn unsafe.Pointer, objType C.int, id, data unsafe.Poin
 
 //export goStateObjectGetFunc
 func goStateObjectGetFunc(gn unsafe.Pointer, objType C.int, id, dataP unsafe.Pointer) C.int {
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
+	node := cNodeRefs[uintptr(gn)]
 	if node == nil {
 		return -1
 	}
+
 	*((*uintptr)(dataP)) = 0
 	tmp, found := node.stateObjectGet(int(objType), *((*[2]uint64)(id)))
 	if found && len(tmp) > 0 {
@@ -849,14 +823,13 @@ func goStateObjectGetFunc(gn unsafe.Pointer, objType C.int, id, dataP unsafe.Poi
 		*((*uintptr)(dataP)) = uintptr(cData)
 		return C.int(len(tmp))
 	}
+
 	return -1
 }
 
 //export goVirtualNetworkConfigFunc
 func goVirtualNetworkConfigFunc(gn, _ unsafe.Pointer, nwid C.uint64_t, op C.int, conf unsafe.Pointer) {
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
+	node := cNodeRefs[uintptr(gn)]
 	if node == nil {
 		return
 	}
@@ -916,12 +889,11 @@ func goVirtualNetworkConfigFunc(gn, _ unsafe.Pointer, nwid C.uint64_t, op C.int,
 
 //export goZtEvent
 func goZtEvent(gn unsafe.Pointer, eventType C.int, data unsafe.Pointer) {
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
+	node := cNodeRefs[uintptr(gn)]
 	if node == nil {
 		return
 	}
+
 	switch eventType {
 	case C.ZT_EVENT_OFFLINE:
 		atomic.StoreUint32(&node.online, 0)

+ 1 - 2
go/pkg/zerotier/peer.go

@@ -27,7 +27,6 @@ type Peer struct {
 
 // PeerMutableFields contains only the mutable fields of Peer as nullable pointers.
 type PeerMutableFields struct {
-	Identity  *Identity    `json:"identity"`
 	Root      *bool        `json:"root"`
-	Bootstrap *InetAddress `json:"bootstrap,omitempty"`
+	RootSpec  []byte       `json:"rootSpec"`
 }

+ 30 - 0
go/vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go

@@ -0,0 +1,30 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package unsafeheader contains header declarations for the Go runtime's
+// slice and string implementations.
+//
+// This package allows x/sys to use types equivalent to
+// reflect.SliceHeader and reflect.StringHeader without introducing
+// a dependency on the (relatively heavy) "reflect" package.
+package unsafeheader
+
+import (
+	"unsafe"
+)
+
+// Slice is the runtime representation of a slice.
+// It cannot be used safely or portably and its representation may change in a later release.
+type Slice struct {
+	Data unsafe.Pointer
+	Len  int
+	Cap  int
+}
+
+// String is the runtime representation of a string.
+// It cannot be used safely or portably and its representation may change in a later release.
+type String struct {
+	Data unsafe.Pointer
+	Len  int
+}

+ 29 - 0
go/vendor/golang.org/x/sys/windows/dll_windows.go

@@ -104,6 +104,35 @@ func (d *DLL) MustFindProc(name string) *Proc {
 	return p
 }
 
+// FindProcByOrdinal searches DLL d for procedure by ordinal and returns *Proc
+// if found. It returns an error if search fails.
+func (d *DLL) FindProcByOrdinal(ordinal uintptr) (proc *Proc, err error) {
+	a, e := GetProcAddressByOrdinal(d.Handle, ordinal)
+	name := "#" + itoa(int(ordinal))
+	if e != nil {
+		return nil, &DLLError{
+			Err:     e,
+			ObjName: name,
+			Msg:     "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
+		}
+	}
+	p := &Proc{
+		Dll:  d,
+		Name: name,
+		addr: a,
+	}
+	return p, nil
+}
+
+// MustFindProcByOrdinal is like FindProcByOrdinal but panics if search fails.
+func (d *DLL) MustFindProcByOrdinal(ordinal uintptr) *Proc {
+	p, e := d.FindProcByOrdinal(ordinal)
+	if e != nil {
+		panic(e)
+	}
+	return p
+}
+
 // Release unloads DLL d from memory.
 func (d *DLL) Release() (err error) {
 	return FreeLibrary(d.Handle)

+ 2 - 9
go/vendor/golang.org/x/sys/windows/env_windows.go

@@ -8,7 +8,6 @@ package windows
 
 import (
 	"syscall"
-	"unicode/utf16"
 	"unsafe"
 )
 
@@ -40,17 +39,11 @@ func (token Token) Environ(inheritExisting bool) (env []string, err error) {
 	defer DestroyEnvironmentBlock(block)
 	blockp := uintptr(unsafe.Pointer(block))
 	for {
-		entry := (*[(1 << 30) - 1]uint16)(unsafe.Pointer(blockp))[:]
-		for i, v := range entry {
-			if v == 0 {
-				entry = entry[:i]
-				break
-			}
-		}
+		entry := UTF16PtrToString((*uint16)(unsafe.Pointer(blockp)))
 		if len(entry) == 0 {
 			break
 		}
-		env = append(env, string(utf16.Decode(entry)))
+		env = append(env, entry)
 		blockp += 2 * (uintptr(len(entry)) + 1)
 	}
 	return env, nil

+ 15 - 5
go/vendor/golang.org/x/sys/windows/security_windows.go

@@ -7,6 +7,8 @@ package windows
 import (
 	"syscall"
 	"unsafe"
+
+	"golang.org/x/sys/internal/unsafeheader"
 )
 
 const (
@@ -1229,7 +1231,7 @@ func (sd *SECURITY_DESCRIPTOR) String() string {
 		return ""
 	}
 	defer LocalFree(Handle(unsafe.Pointer(sddl)))
-	return UTF16ToString((*[(1 << 30) - 1]uint16)(unsafe.Pointer(sddl))[:])
+	return UTF16PtrToString(sddl)
 }
 
 // ToAbsolute converts a self-relative security descriptor into an absolute one.
@@ -1307,9 +1309,17 @@ func (absoluteSD *SECURITY_DESCRIPTOR) ToSelfRelative() (selfRelativeSD *SECURIT
 }
 
 func (selfRelativeSD *SECURITY_DESCRIPTOR) copySelfRelativeSecurityDescriptor() *SECURITY_DESCRIPTOR {
-	sdBytes := make([]byte, selfRelativeSD.Length())
-	copy(sdBytes, (*[(1 << 31) - 1]byte)(unsafe.Pointer(selfRelativeSD))[:len(sdBytes)])
-	return (*SECURITY_DESCRIPTOR)(unsafe.Pointer(&sdBytes[0]))
+	sdLen := (int)(selfRelativeSD.Length())
+
+	var src []byte
+	h := (*unsafeheader.Slice)(unsafe.Pointer(&src))
+	h.Data = unsafe.Pointer(selfRelativeSD)
+	h.Len = sdLen
+	h.Cap = sdLen
+
+	dst := make([]byte, sdLen)
+	copy(dst, src)
+	return (*SECURITY_DESCRIPTOR)(unsafe.Pointer(&dst[0]))
 }
 
 // SecurityDescriptorFromString converts an SDDL string describing a security descriptor into a
@@ -1391,6 +1401,6 @@ func ACLFromEntries(explicitEntries []EXPLICIT_ACCESS, mergedACL *ACL) (acl *ACL
 	}
 	defer LocalFree(Handle(unsafe.Pointer(winHeapACL)))
 	aclBytes := make([]byte, winHeapACL.aclSize)
-	copy(aclBytes, (*[(1 << 31) - 1]byte)(unsafe.Pointer(winHeapACL))[:len(aclBytes)])
+	copy(aclBytes, (*[(1 << 31) - 1]byte)(unsafe.Pointer(winHeapACL))[:len(aclBytes):len(aclBytes)])
 	return (*ACL)(unsafe.Pointer(&aclBytes[0])), nil
 }

+ 35 - 2
go/vendor/golang.org/x/sys/windows/syscall_windows.go

@@ -13,6 +13,8 @@ import (
 	"time"
 	"unicode/utf16"
 	"unsafe"
+
+	"golang.org/x/sys/internal/unsafeheader"
 )
 
 type Handle uintptr
@@ -117,6 +119,32 @@ func UTF16PtrFromString(s string) (*uint16, error) {
 	return &a[0], nil
 }
 
+// UTF16PtrToString takes a pointer to a UTF-16 sequence and returns the corresponding UTF-8 encoded string.
+// If the pointer is nil, this returns the empty string. This assumes that the UTF-16 sequence is terminated
+// at a zero word; if the zero word is not present, the program may crash.
+func UTF16PtrToString(p *uint16) string {
+	if p == nil {
+		return ""
+	}
+	if *p == 0 {
+		return ""
+	}
+
+	// Find NUL terminator.
+	n := 0
+	for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; n++ {
+		ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*p))
+	}
+
+	var s []uint16
+	h := (*unsafeheader.Slice)(unsafe.Pointer(&s))
+	h.Data = unsafe.Pointer(p)
+	h.Len = n
+	h.Cap = n
+
+	return string(utf16.Decode(s))
+}
+
 func Getpagesize() int { return 4096 }
 
 // NewCallback converts a Go function to a function pointer conforming to the stdcall calling convention.
@@ -1181,7 +1209,12 @@ type IPv6Mreq struct {
 	Interface uint32
 }
 
-func GetsockoptInt(fd Handle, level, opt int) (int, error) { return -1, syscall.EWINDOWS }
+func GetsockoptInt(fd Handle, level, opt int) (int, error) {
+	v := int32(0)
+	l := int32(unsafe.Sizeof(v))
+	err := Getsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&v)), &l)
+	return int(v), err
+}
 
 func SetsockoptLinger(fd Handle, level, opt int, l *Linger) (err error) {
 	sys := sysLinger{Onoff: uint16(l.Onoff), Linger: uint16(l.Linger)}
@@ -1378,7 +1411,7 @@ func (t Token) KnownFolderPath(folderID *KNOWNFOLDERID, flags uint32) (string, e
 		return "", err
 	}
 	defer CoTaskMemFree(unsafe.Pointer(p))
-	return UTF16ToString((*[(1 << 30) - 1]uint16)(unsafe.Pointer(p))[:]), nil
+	return UTF16PtrToString(p), nil
 }
 
 // RtlGetVersion returns the version of the underlying operating system, ignoring

+ 2 - 1
go/vendor/modules.txt

@@ -4,5 +4,6 @@ github.com/Microsoft/go-winio/pkg/guid
 # github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95
 github.com/hectane/go-acl
 github.com/hectane/go-acl/api
-# golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
+# golang.org/x/sys v0.0.0-20200523222454-059865788121
+golang.org/x/sys/internal/unsafeheader
 golang.org/x/sys/windows

+ 5 - 3
node/Identity.hpp

@@ -24,10 +24,12 @@
 #include "Fingerprint.hpp"
 #include "Containers.hpp"
 
-#define ZT_IDENTITY_STRING_BUFFER_LENGTH 1024
-#define ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE (1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + ZT_ECC384_PUBLIC_KEY_SIZE)
+#define ZT_IDENTITY_STRING_BUFFER_LENGTH           1024
+#define ZT_IDENTITY_C25519_PUBLIC_KEY_SIZE         ZT_C25519_COMBINED_PUBLIC_KEY_SIZE
+#define ZT_IDENTITY_C25519_PRIVATE_KEY_SIZE        ZT_C25519_COMBINED_PRIVATE_KEY_SIZE
+#define ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE  (1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + ZT_ECC384_PUBLIC_KEY_SIZE)
 #define ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE (ZT_C25519_COMBINED_PRIVATE_KEY_SIZE + ZT_ECC384_PRIVATE_KEY_SIZE)
-#define ZT_IDENTITY_MARSHAL_SIZE_MAX (ZT_ADDRESS_LENGTH + 4 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE)
+#define ZT_IDENTITY_MARSHAL_SIZE_MAX               (ZT_ADDRESS_LENGTH + 4 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE)
 
 namespace ZeroTier {