Adam Ierymenko 5 年之前
父节点
当前提交
4303c43db7

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

@@ -28,7 +28,7 @@ func Help() {
 
 Usage: zerotier [-options] <command> [-options] [command args]
 
-Global Options
+Global Options:
   -j                                   Output raw JSON where applicable
   -p <path>                            Use alternate base path
   -t <authtoken.secret path>           Use secret auth token from this file

+ 87 - 0
go/native/GoGlue.cpp

@@ -19,6 +19,7 @@
 #include "../../node/Utils.hpp"
 #include "../../node/MAC.hpp"
 #include "../../node/Address.hpp"
+#include "../../node/Locator.hpp"
 #include "../../osdep/OSUtils.hpp"
 #include "../../osdep/EthernetTap.hpp"
 #include "../../osdep/ManagedRoute.hpp"
@@ -497,6 +498,11 @@ extern "C" int ZT_GoNode_phyStartListen(ZT_GoNode *gn,const char *dev,const char
 					int s = (int)recvfrom(udpSock,buf,sizeof(buf),0,reinterpret_cast<struct sockaddr *>(&in6),&salen);
 					if (s > 0) {
 						gn->node->processWirePacket(&gnt,OSUtils::now(),(int64_t)udpSock,reinterpret_cast<const struct sockaddr_storage *>(&in6),buf,(unsigned int)s,&(gn->nextBackgroundTaskDeadline));
+					} else {
+						// If something goes bad with this socket such as its interface vanishing, it
+						// will eventually be closed by higher level (Go) code. Until then prevent the
+						// system from consuming too much CPU.
+						std::this_thread::sleep_for(std::chrono::milliseconds(10));
 					}
 				}
 			});
@@ -714,3 +720,84 @@ extern "C" int ZT_GoTap_removeRoute(ZT_GoTap *tap,int targetAf,const void *targe
 	}
 	return reinterpret_cast<EthernetTap *>(tap)->removeRoute(target,via,metric);
 }
+
+/****************************************************************************/
+
+int ZT_GoLocator_makeSecureDNSName(char *name,unsigned int nameBufSize,uint8_t *privateKey,unsigned int privateKeyBufSize)
+{
+	if ((privateKeyBufSize < ZT_ECC384_PRIVATE_KEY_SIZE)||(nameBufSize < 256))
+		return -1;
+	uint8_t pub[ZT_ECC384_PUBLIC_KEY_SIZE];
+	ECC384GenerateKey(pub,privateKey);
+	const Str n(Locator::makeSecureDnsName(pub));
+	if (n.size() >= nameBufSize)
+		return -1;
+	Utils::scopy(name,sizeof(name),n.c_Str());
+	return ZT_ECC384_PRIVATE_KEY_SIZE;
+}
+
+int ZT_GoLocator_makeLocator(
+	uint8_t *buf,
+	unsigned int bufSize,
+	int64_t ts,
+	const char *id,
+	const struct sockaddr_storage *physicalAddresses,
+	unsigned int physicalAddressCount,
+	const char **virtualAddresses,
+	unsigned int virtualAddressCount)
+{
+	Locator loc;
+	for(unsigned int i=0;i<physicalAddressCount;++i) {
+		loc.add(*reinterpret_cast<const InetAddress *>(physicalAddresses + i));
+	}
+	for(unsigned int i=0;i<virtualAddressCount;++i) {
+		Identity id;
+		if (!id.fromString(virtualAddresses[i]))
+			return -1;
+		loc.add(id);
+	}
+	Identity signingId;
+	if (!signingId.fromString(id))
+		return -1;
+	if (!signingId.hasPrivate())
+		return -1;
+	if (!loc.finish(signingId,ts))
+		return -1;
+	Buffer<65536> *tmp = new Buffer<65536>();
+	loc.serialize(*tmp);
+	if (tmp->size() > bufSize) {
+		delete tmp;
+		return -1;
+	}
+	memcpy(buf,tmp->data(),tmp->size());
+	int s = (int)tmp->size();
+	delete tmp;
+	return s;
+}
+
+int ZT_GoLocator_decodeLocator(const uint8_t *loc,unsigned int locSize,struct ZT_GoLocator_Info *info)
+{
+	memset(info,0,sizeof(struct ZT_GoLocator_Info));
+	return 1;
+}
+
+int ZT_GoLocator_makeSignedTxtRecords(
+	const uint8_t *locator,
+	unsigned int locatorSize,
+	const char *name,
+	const uint8_t *privateKey,
+	unsigned int privateKeySize,
+	char results[256][256])
+{
+	if (privateKeySize != ZT_ECC384_PRIVATE_KEY_SIZE)
+		return -1;
+	Locator loc;
+	if (!loc.deserialize(locator,locatorSize))
+		return -1;
+	std::vector<Str> r(loc.makeTxtRecords(privateKey));
+	if (r.size() > 256)
+		return -1;
+	for(unsigned long i=0;i<r.size();++i)
+		Utils::scopy(results[i],256,r[i].c_str());
+	return (int)r.size();
+}

+ 49 - 0
go/native/GoGlue.h

@@ -90,6 +90,55 @@ int ZT_GoTap_removeRoute(ZT_GoTap *tap,int targetAf,const void *targetIp,int tar
 
 /****************************************************************************/
 
+struct ZT_GoLocator_Info {
+	char id[1024];
+	struct sockaddr_storage phy[256];
+	char virt[256][1024];
+	unsigned int phyCount;
+	unsigned int virtCount;
+}
+
+/* Returns length of private key stored in private key buffer on success, -1 on fail */
+int ZT_GoLocator_makeSecureDNSName(char name[256],unsigned int nameBufSize,uint8_t *privateKey,unsigned int privateKeyBufSize);
+
+/*
+ * The id is the full identity described by the locator. It must include
+ * its secret key to permit the locator to be signed.
+ *
+ * Physical addresses must be IPv4 or IPv6 IP/port pairs. Virtual addresses
+ * must be full ZeroTier identities in string format.
+ *
+ * On success this returns the actual number of bytes stored in the buffer.
+ * On failure -1 is returned.
+ */
+int ZT_GoLocator_makeLocator(
+	uint8_t *buf,
+	unsigned int bufSize,
+	const char *id,
+	const struct sockaddr_storage *physicalAddresses,
+	unsigned int physicalAddressCount,
+	const char **virtualAddresses,
+	unsigned int virtualAddressCount);
+
+/* Returns nonzero on success, fills info structure */
+int ZT_GoLocator_decodeLocator(const uint8_t *loc,unsigned int locSize,struct ZT_GoLocator_Info *info);
+
+/*
+ * The privateKey and privateKeySize are those created by makeSecureDNSName.
+ * Results is filled and the number of lines of TXT are returned. The value
+ * -1 is returned on error.
+ */
+int ZT_GoLocator_makeSignedTxtRecords(
+	const uint8_t *locator,
+	unsigned int locatorSize,
+	int64_t ts,
+	const char *name,
+	const uint8_t *privateKey,
+	unsigned int privateKeySize,
+	char results[256][256]);
+
+/****************************************************************************/
+
 #ifdef __cplusplus
 }
 #endif

+ 1 - 0
go/pkg/zerotier/errors.go

@@ -28,4 +28,5 @@ const (
 	ErrTapInitFailed             Err = "unable to create native Tap instance"
 	ErrUncrecognizedIdentityType Err = "unrecognized identity type"
 	ErrInvalidKey                Err = "invalid key data"
+	ErrSecretKeyRequired         Err = "secret key required"
 )

+ 4 - 8
go/pkg/zerotier/localconfig.go

@@ -95,14 +95,10 @@ func (lc *LocalConfig) Read(p string, saveDefaultsIfNotExist bool) error {
 		lc.Settings.LogSizeMax = 128
 		lc.Settings.MuiltipathMode = 0
 		switch runtime.GOOS {
-		case "darwin":
-			lc.Settings.InterfacePrefixBlacklist = []string{"utun", "tun", "tap", "feth", "lo", "zt"}
-		case "linux":
-			lc.Settings.InterfacePrefixBlacklist = []string{"tun", "tap", "lo", "zt"}
-		case "freebsd", "openbsd", "netbsd", "illumos", "solaris", "dragonfly":
-			lc.Settings.InterfacePrefixBlacklist = []string{"tun", "tap", "zt"}
-		case "android":
-			lc.Settings.InterfacePrefixBlacklist = []string{"tun", "tap"}
+		case "windows":
+			lc.Settings.InterfacePrefixBlacklist = []string{"loopback"}
+		default:
+			lc.Settings.InterfacePrefixBlacklist = []string{"lo"}
 		}
 	}
 

+ 99 - 0
go/pkg/zerotier/locator.go

@@ -0,0 +1,99 @@
+/*
+ * Copyright (c)2019 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2023-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+package zerotier
+
+//#cgo CFLAGS: -O3
+//#include "../../native/GoGlue.h"
+import "C"
+
+import "unsafe"
+
+// LocatorDNSSigningKey is the public (as a secure DNS name) and private keys for entering locators into DNS
+type LocatorDNSSigningKey struct {
+	SecureDNSName string
+	PrivateKey    []byte
+}
+
+// Locator is a binary serialized record containing information about where a ZeroTier node is located on the network
+type Locator struct {
+	// Identity is the full identity of the node being located
+	Identity *Identity
+
+	// Physical is a list of static physical network addresses for this node
+	Physical []*InetAddress
+
+	// Virtual is a list of ZeroTier nodes that can relay to this node
+	Virtual []*Identity
+
+	bytes []byte
+}
+
+// NewLocator creates a new locator with the given identity and addresses and the current time as timestamp.
+// The identity must include its secret key so that it can sign the final locator.
+func NewLocator(id *Identity, virtualAddresses []*Identity, physicalAddresses []*InetAddress) (*Locator, error) {
+	if !id.HasPrivate() {
+		return nil, ErrSecretKeyRequired
+	}
+
+	idstr := id.PrivateKeyString()
+	phy := make([]C.struct_sockaddr_storage, len(physicalAddresses))
+	virt := make([]*C.char, len(virtualAddresses))
+	idCstr := C.CString(idstr)
+
+	defer func() {
+		C.free(unsafe.Pointer(idCstr))
+		for _, v := range virt {
+			if uintptr(unsafe.Pointer(v)) != 0 {
+				C.free(unsafe.Pointer(v))
+			}
+		}
+	}()
+
+	for i := 0; i < len(physicalAddresses); i++ {
+		if !makeSockaddrStorage(physicalAddresses[i].IP, physicalAddresses[i].Port, &phy[i]) {
+			return nil, ErrInvalidParameter
+		}
+	}
+
+	for i := 0; i < len(virtualAddresses); i++ {
+		idstr := virtualAddresses[i].String()
+		virt[i] = C.CString(idstr)
+	}
+
+	var buf [65536]byte
+	var pPhy *C.struct_sockaddr_storage
+	var pVirt *C.char
+	if len(phy) > 0 {
+		pPhy = &phy[0]
+	}
+	if len(virt) > 0 {
+		pVirt = &virt[0]
+	}
+	locSize := C.ZT_GoLocator_makeLocator((*C.uint8_t)(unsafe.Pointer(&buf[0])), 65536, idCstr, pPhy, C.uint(len(phy)), pVirt, C.uint(len(virt)))
+	if locSize <= 0 {
+		return nil, ErrInvalidParameter
+	}
+
+	r := make([]byte, int(locSize))
+	copy(r[:], buf[0:int(locSize)])
+	return &Locator{
+		Identity: id,
+		Physical: physicalAddresses,
+		Virtual:  virtualAddresses,
+		bytes:    r,
+	}, nil
+}
+
+// Bytes returns this locator in byte serialized format
+func (l *Locator) Bytes() []byte { return l.bytes }

+ 243 - 0
go/pkg/zerotier/nativetap.go

@@ -0,0 +1,243 @@
+/*
+ * Copyright (c)2019 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2023-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+package zerotier
+
+//#cgo CFLAGS: -O3
+//#include "../../native/GoGlue.h"
+import "C"
+
+import (
+	"fmt"
+	"net"
+	"sync"
+	"sync/atomic"
+	"unsafe"
+)
+
+// nativeTap is a Tap implementation that wraps a native C++ interface to a system tun/tap device
+type nativeTap struct {
+	tap                        unsafe.Pointer
+	networkStatus              uint32
+	enabled                    uint32
+	multicastGroupHandlers     []func(bool, *MulticastGroup)
+	multicastGroupHandlersLock sync.Mutex
+}
+
+// Close is a no-op for the native tap because GoGlue does this when networks are left
+func (t *nativeTap) Close() {}
+
+// Type returns a human-readable description of this tap implementation
+func (t *nativeTap) Type() string {
+	return "native"
+}
+
+// Error gets this tap device's error status
+func (t *nativeTap) Error() (int, string) {
+	return 0, ""
+}
+
+// SetEnabled sets this tap's enabled state
+func (t *nativeTap) SetEnabled(enabled bool) {
+	if enabled && atomic.SwapUint32(&t.enabled, 1) == 0 {
+		C.ZT_GoTap_setEnabled(t.tap, 1)
+	} else if !enabled && atomic.SwapUint32(&t.enabled, 0) == 1 {
+		C.ZT_GoTap_setEnabled(t.tap, 0)
+	}
+}
+
+// Enabled returns true if this tap is currently processing packets
+func (t *nativeTap) Enabled() bool {
+	return atomic.LoadUint32(&t.enabled) != 0
+}
+
+// AddIP adds an IP address (with netmask) to this tap
+func (t *nativeTap) AddIP(ip *net.IPNet) error {
+	bits, _ := ip.Mask.Size()
+	if len(ip.IP) == 16 {
+		if bits > 128 || bits < 0 {
+			return ErrInvalidParameter
+		}
+		C.ZT_GoTap_addIp(t.tap, C.int(AFInet6), unsafe.Pointer(&ip.IP[0]), C.int(bits))
+	} else if len(ip.IP) == 4 {
+		if bits > 32 || bits < 0 {
+			return ErrInvalidParameter
+		}
+		C.ZT_GoTap_addIp(t.tap, C.int(AFInet), unsafe.Pointer(&ip.IP[0]), C.int(bits))
+	}
+	return ErrInvalidParameter
+}
+
+// RemoveIP removes this IP address (with netmask) from this tap
+func (t *nativeTap) RemoveIP(ip *net.IPNet) error {
+	bits, _ := ip.Mask.Size()
+	if len(ip.IP) == 16 {
+		if bits > 128 || bits < 0 {
+			return ErrInvalidParameter
+		}
+		C.ZT_GoTap_removeIp(t.tap, C.int(AFInet6), unsafe.Pointer(&ip.IP[0]), C.int(bits))
+		return nil
+	}
+	if len(ip.IP) == 4 {
+		if bits > 32 || bits < 0 {
+			return ErrInvalidParameter
+		}
+		C.ZT_GoTap_removeIp(t.tap, C.int(AFInet), unsafe.Pointer(&ip.IP[0]), C.int(bits))
+		return nil
+	}
+	return ErrInvalidParameter
+}
+
+// IPs returns IPs currently assigned to this tap (including externally or system-assigned IPs)
+func (t *nativeTap) IPs() (ips []net.IPNet, err error) {
+	defer func() {
+		e := recover()
+		if e != nil {
+			err = fmt.Errorf("%v", e)
+		}
+	}()
+	var ipbuf [16384]byte
+	count := int(C.ZT_GoTap_ips(t.tap, unsafe.Pointer(&ipbuf[0]), 16384))
+	ipptr := 0
+	for i := 0; i < count; i++ {
+		af := int(ipbuf[ipptr])
+		ipptr++
+		switch af {
+		case AFInet:
+			var ip [4]byte
+			for j := 0; j < 4; j++ {
+				ip[j] = ipbuf[ipptr]
+				ipptr++
+			}
+			bits := ipbuf[ipptr]
+			ipptr++
+			ips = append(ips, net.IPNet{IP: net.IP(ip[:]), Mask: net.CIDRMask(int(bits), 32)})
+		case AFInet6:
+			var ip [16]byte
+			for j := 0; j < 16; j++ {
+				ip[j] = ipbuf[ipptr]
+				ipptr++
+			}
+			bits := ipbuf[ipptr]
+			ipptr++
+			ips = append(ips, net.IPNet{IP: net.IP(ip[:]), Mask: net.CIDRMask(int(bits), 128)})
+		}
+	}
+	return
+}
+
+// DeviceName gets this tap's OS-specific device name
+func (t *nativeTap) DeviceName() string {
+	var dn [256]byte
+	C.ZT_GoTap_deviceName(t.tap, (*C.char)(unsafe.Pointer(&dn[0])))
+	for i, b := range dn {
+		if b == 0 {
+			return string(dn[0:i])
+		}
+	}
+	return ""
+}
+
+// AddMulticastGroupChangeHandler adds a function to be called when the tap subscribes or unsubscribes to a multicast group.
+func (t *nativeTap) AddMulticastGroupChangeHandler(handler func(bool, *MulticastGroup)) {
+	t.multicastGroupHandlersLock.Lock()
+	t.multicastGroupHandlers = append(t.multicastGroupHandlers, handler)
+	t.multicastGroupHandlersLock.Unlock()
+}
+
+// AddRoute adds or updates a managed route on this tap's interface
+func (t *nativeTap) AddRoute(r *Route) error {
+	rc := 0
+	if r != nil {
+		if len(r.Target.IP) == 4 {
+			mask, _ := r.Target.Mask.Size()
+			if len(r.Via) == 4 {
+				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
+			} else {
+				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
+			}
+		} else if len(r.Target.IP) == 16 {
+			mask, _ := r.Target.Mask.Size()
+			if len(r.Via) == 4 {
+				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet6, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
+			} else {
+				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
+			}
+		}
+	}
+	if rc != 0 {
+		return fmt.Errorf("tap device error adding route: %d", rc)
+	}
+	return nil
+}
+
+// RemoveRoute removes a managed route on this tap's interface
+func (t *nativeTap) RemoveRoute(r *Route) error {
+	rc := 0
+	if r != nil {
+		if len(r.Target.IP) == 4 {
+			mask, _ := r.Target.Mask.Size()
+			if len(r.Via) == 4 {
+				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
+			} else {
+				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
+			}
+		} else if len(r.Target.IP) == 16 {
+			mask, _ := r.Target.Mask.Size()
+			if len(r.Via) == 4 {
+				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet6, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
+			} else {
+				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
+			}
+		}
+	}
+	if rc != 0 {
+		return fmt.Errorf("tap device error removing route: %d", rc)
+	}
+	return nil
+}
+
+func handleTapMulticastGroupChange(gn unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t, added bool) {
+	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)
+				}
+			}
+		}
+	}()
+}
+
+//export goHandleTapAddedMulticastGroup
+func goHandleTapAddedMulticastGroup(gn, tapP unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t) {
+	handleTapMulticastGroupChange(gn, nwid, mac, adi, true)
+}
+
+//export goHandleTapRemovedMulticastGroup
+func goHandleTapRemovedMulticastGroup(gn, tapP unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t) {
+	handleTapMulticastGroupChange(gn, nwid, mac, adi, false)
+}

+ 5 - 0
go/pkg/zerotier/network.go

@@ -236,6 +236,11 @@ func (n *Network) MulticastSubscriptions() []*MulticastGroup {
 	return mgs
 }
 
+// leaving is called by Node when the network is being left
+func (n *Network) leaving() {
+	n.tap.Close()
+}
+
 func (n *Network) networkConfigRevision() uint64 {
 	n.configLock.RLock()
 	defer n.configLock.RUnlock()

+ 27 - 282
go/pkg/zerotier/node.go

@@ -13,9 +13,6 @@
 
 package zerotier
 
-// This wraps the C++ Node implementation, C++ EthernetTap implementations,
-// and generally contains all the other CGO stuff.
-
 //#cgo CFLAGS: -O3
 //#cgo darwin LDFLAGS: ${SRCDIR}/../../../build/go/native/libzt_go_native.a ${SRCDIR}/../../../build/node/libzt_core.a ${SRCDIR}/../../../build/osdep/libzt_osdep.a -lc++ -lpthread
 //#cgo linux android LDFLAGS: ${SRCDIR}/../../../build/go/native/libzt_go_native.a ${SRCDIR}/../../../build/node/libzt_core.a ${SRCDIR}/../../../build/osdep/libzt_osdep.a -lstdc++ -lpthread -lm
@@ -81,6 +78,7 @@ 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)
 	nodesByUserPtrLock sync.RWMutex
 )
@@ -299,7 +297,7 @@ func NewNode(basePath string) (*Node, error) {
 	n.online = 0
 	n.running = 1
 
-	n.runLock.Lock()
+	n.runLock.Lock() // used to block Close() until below gorountine exits
 	go func() {
 		lastMaintenanceRun := int64(0)
 		for atomic.LoadUint32(&n.running) != 0 {
@@ -308,12 +306,20 @@ func NewNode(basePath string) (*Node, error) {
 			now := TimeMs()
 			if (now - lastMaintenanceRun) >= 30000 {
 				lastMaintenanceRun = now
+				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()
@@ -330,8 +336,9 @@ func NewNode(basePath string) (*Node, error) {
 					n.networksLock.RUnlock()
 				}
 
-				n.localConfigLock.RLock()
-
+				// 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.
 				n.interfaceAddressesLock.Lock()
 				for astr, ipn := range interfaceAddresses {
 					if _, alreadyKnown := n.interfaceAddresses[astr]; !alreadyKnown {
@@ -372,6 +379,7 @@ func NewNode(basePath string) (*Node, error) {
 				n.interfaceAddresses = interfaceAddresses
 				n.interfaceAddressesLock.Unlock()
 
+				// Trim log if it's gone over its size limit
 				if n.localConfig.Settings.LogSizeMax > 0 && n.logW != nil {
 					n.logW.trim(n.localConfig.Settings.LogSizeMax*1024, 0.5, true)
 				}
@@ -379,7 +387,7 @@ func NewNode(basePath string) (*Node, error) {
 				n.localConfigLock.RUnlock()
 			}
 		}
-		n.runLock.Unlock()
+		n.runLock.Unlock() // signal Close() that maintenance goroutine is done
 	}()
 
 	return n, nil
@@ -393,7 +401,7 @@ func (n *Node) Close() {
 		nodesByUserPtrLock.Lock()
 		delete(nodesByUserPtr, uintptr(unsafe.Pointer(n.gn)))
 		nodesByUserPtrLock.Unlock()
-		n.runLock.Lock() // wait for gorountine to die
+		n.runLock.Lock() // wait for maintenance gorountine to die
 		n.runLock.Unlock()
 	}
 }
@@ -501,12 +509,16 @@ func (n *Node) Join(nwid NetworkID, settings *NetworkLocalSettings, tap Tap) (*N
 }
 
 // Leave leaves a network
-func (n *Node) Leave(nwid uint64) error {
+func (n *Node) Leave(nwid NetworkID) error {
 	n.log.Printf("leaving network %.16x", nwid)
-	C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid))
 	n.networksLock.Lock()
-	delete(n.networks, NetworkID(nwid))
+	nw := n.networks[nwid]
+	delete(n.networks, nwid)
 	n.networksLock.Unlock()
+	if nw != nil {
+		nw.leaving()
+	}
+	C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid))
 	return nil
 }
 
@@ -529,59 +541,6 @@ func (n *Node) Networks() []*Network {
 	return nws
 }
 
-// AddStaticRoot adds a statically defined root server to this node.
-// If a static root with the given identity already exists this will update its IP and port information.
-func (n *Node) AddStaticRoot(id *Identity, addrs []InetAddress) {
-	var saddrs []C.struct_sockaddr_storage
-	var straddrs strings.Builder
-	for _, a := range addrs {
-		ss := new(C.struct_sockaddr_storage)
-		if makeSockaddrStorage(a.IP, a.Port, ss) {
-			saddrs = append(saddrs, *ss)
-			if straddrs.Len() > 0 {
-				straddrs.WriteString(",")
-			}
-			straddrs.WriteString(a.String())
-		}
-	}
-	if len(saddrs) > 0 {
-		n.log.Printf("adding or updating static root %s at address(es) %s", id.String(), straddrs)
-		ids := C.CString(id.String())
-		C.ZT_Node_setStaticRoot(unsafe.Pointer(n.zn), ids, &saddrs[0], C.uint(len(saddrs)))
-		C.free(unsafe.Pointer(ids))
-	}
-}
-
-// RemoveStaticRoot removes a statically defined root server from this node.
-func (n *Node) RemoveStaticRoot(id *Identity) {
-	n.log.Printf("removing static root %s", id.String())
-	ids := C.CString(id.String())
-	C.ZT_Node_removeStaticRoot(unsafe.Pointer(n.zn), ids)
-	C.free(unsafe.Pointer(ids))
-}
-
-// AddDynamicRoot adds a dynamic root to this node.
-// If the locator parameter is non-empty it can contain a binary serialized locator
-// to use if (or until) one can be fetched via DNS.
-func (n *Node) AddDynamicRoot(dnsName string, locator []byte) {
-	dn := C.CString(dnsName)
-	n.log.Printf("adding dynamic root %s", dnsName)
-	if len(locator) > 0 {
-		C.ZT_Node_setDynamicRoot(unsafe.Pointer(n.zn), dn, unsafe.Pointer(&locator[0]), C.uint(len(locator)))
-	} else {
-		C.ZT_Node_setDynamicRoot(unsafe.Pointer(n.zn), dn, nil, 0)
-	}
-	C.free(unsafe.Pointer(dn))
-}
-
-// RemoveDynamicRoot removes a dynamic root from this node.
-func (n *Node) RemoveDynamicRoot(dnsName string) {
-	n.log.Printf("removing dynamic root %s", dnsName)
-	dn := C.CString(dnsName)
-	C.ZT_Node_removeDynamicRoot(unsafe.Pointer(n.zn), dn)
-	C.free(unsafe.Pointer(dn))
-}
-
 // Roots retrieves a list of root servers on this node and their preferred and online status.
 func (n *Node) Roots() []*Root {
 	var roots []*Root
@@ -797,6 +756,10 @@ func (n *Node) handleRemoteTrace(originAddress uint64, dictData []byte) {
 
 //////////////////////////////////////////////////////////////////////////////
 
+// These are callbacks called by the core and GoGlue stuff to talk to the
+// service. These launch gorountines to do their work where possible to
+// avoid blocking anything in the core.
+
 //export goPathCheckFunc
 func goPathCheckFunc(gn unsafe.Pointer, ztAddress C.uint64_t, af C.int, ip unsafe.Pointer, port C.int) C.int {
 	nodesByUserPtrLock.RLock()
@@ -982,221 +945,3 @@ func goZtEvent(gn unsafe.Pointer, eventType C.int, data unsafe.Pointer) {
 		}
 	}()
 }
-
-//////////////////////////////////////////////////////////////////////////////
-
-// nativeTap is a Tap implementation that wraps a native C++ interface to a system tun/tap device
-type nativeTap struct {
-	tap                        unsafe.Pointer
-	networkStatus              uint32
-	enabled                    uint32
-	multicastGroupHandlers     []func(bool, *MulticastGroup)
-	multicastGroupHandlersLock sync.Mutex
-}
-
-// Type returns a human-readable description of this tap implementation
-func (t *nativeTap) Type() string {
-	return "native"
-}
-
-// Error gets this tap device's error status
-func (t *nativeTap) Error() (int, string) {
-	return 0, ""
-}
-
-// SetEnabled sets this tap's enabled state
-func (t *nativeTap) SetEnabled(enabled bool) {
-	if enabled && atomic.SwapUint32(&t.enabled, 1) == 0 {
-		C.ZT_GoTap_setEnabled(t.tap, 1)
-	} else if !enabled && atomic.SwapUint32(&t.enabled, 0) == 1 {
-		C.ZT_GoTap_setEnabled(t.tap, 0)
-	}
-}
-
-// Enabled returns true if this tap is currently processing packets
-func (t *nativeTap) Enabled() bool {
-	return atomic.LoadUint32(&t.enabled) != 0
-}
-
-// AddIP adds an IP address (with netmask) to this tap
-func (t *nativeTap) AddIP(ip *net.IPNet) error {
-	bits, _ := ip.Mask.Size()
-	if len(ip.IP) == 16 {
-		if bits > 128 || bits < 0 {
-			return ErrInvalidParameter
-		}
-		C.ZT_GoTap_addIp(t.tap, C.int(AFInet6), unsafe.Pointer(&ip.IP[0]), C.int(bits))
-	} else if len(ip.IP) == 4 {
-		if bits > 32 || bits < 0 {
-			return ErrInvalidParameter
-		}
-		C.ZT_GoTap_addIp(t.tap, C.int(AFInet), unsafe.Pointer(&ip.IP[0]), C.int(bits))
-	}
-	return ErrInvalidParameter
-}
-
-// RemoveIP removes this IP address (with netmask) from this tap
-func (t *nativeTap) RemoveIP(ip *net.IPNet) error {
-	bits, _ := ip.Mask.Size()
-	if len(ip.IP) == 16 {
-		if bits > 128 || bits < 0 {
-			return ErrInvalidParameter
-		}
-		C.ZT_GoTap_removeIp(t.tap, C.int(AFInet6), unsafe.Pointer(&ip.IP[0]), C.int(bits))
-		return nil
-	}
-	if len(ip.IP) == 4 {
-		if bits > 32 || bits < 0 {
-			return ErrInvalidParameter
-		}
-		C.ZT_GoTap_removeIp(t.tap, C.int(AFInet), unsafe.Pointer(&ip.IP[0]), C.int(bits))
-		return nil
-	}
-	return ErrInvalidParameter
-}
-
-// IPs returns IPs currently assigned to this tap (including externally or system-assigned IPs)
-func (t *nativeTap) IPs() (ips []net.IPNet, err error) {
-	defer func() {
-		e := recover()
-		if e != nil {
-			err = fmt.Errorf("%v", e)
-		}
-	}()
-	var ipbuf [16384]byte
-	count := int(C.ZT_GoTap_ips(t.tap, unsafe.Pointer(&ipbuf[0]), 16384))
-	ipptr := 0
-	for i := 0; i < count; i++ {
-		af := int(ipbuf[ipptr])
-		ipptr++
-		switch af {
-		case AFInet:
-			var ip [4]byte
-			for j := 0; j < 4; j++ {
-				ip[j] = ipbuf[ipptr]
-				ipptr++
-			}
-			bits := ipbuf[ipptr]
-			ipptr++
-			ips = append(ips, net.IPNet{IP: net.IP(ip[:]), Mask: net.CIDRMask(int(bits), 32)})
-		case AFInet6:
-			var ip [16]byte
-			for j := 0; j < 16; j++ {
-				ip[j] = ipbuf[ipptr]
-				ipptr++
-			}
-			bits := ipbuf[ipptr]
-			ipptr++
-			ips = append(ips, net.IPNet{IP: net.IP(ip[:]), Mask: net.CIDRMask(int(bits), 128)})
-		}
-	}
-	return
-}
-
-// DeviceName gets this tap's OS-specific device name
-func (t *nativeTap) DeviceName() string {
-	var dn [256]byte
-	C.ZT_GoTap_deviceName(t.tap, (*C.char)(unsafe.Pointer(&dn[0])))
-	for i, b := range dn {
-		if b == 0 {
-			return string(dn[0:i])
-		}
-	}
-	return ""
-}
-
-// AddMulticastGroupChangeHandler adds a function to be called when the tap subscribes or unsubscribes to a multicast group.
-func (t *nativeTap) AddMulticastGroupChangeHandler(handler func(bool, *MulticastGroup)) {
-	t.multicastGroupHandlersLock.Lock()
-	t.multicastGroupHandlers = append(t.multicastGroupHandlers, handler)
-	t.multicastGroupHandlersLock.Unlock()
-}
-
-// AddRoute adds or updates a managed route on this tap's interface
-func (t *nativeTap) AddRoute(r *Route) error {
-	rc := 0
-	if r != nil {
-		if len(r.Target.IP) == 4 {
-			mask, _ := r.Target.Mask.Size()
-			if len(r.Via) == 4 {
-				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
-			} else {
-				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
-			}
-		} else if len(r.Target.IP) == 16 {
-			mask, _ := r.Target.Mask.Size()
-			if len(r.Via) == 4 {
-				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet6, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
-			} else {
-				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
-			}
-		}
-	}
-	if rc != 0 {
-		return fmt.Errorf("tap device error adding route: %d", rc)
-	}
-	return nil
-}
-
-// RemoveRoute removes a managed route on this tap's interface
-func (t *nativeTap) RemoveRoute(r *Route) error {
-	rc := 0
-	if r != nil {
-		if len(r.Target.IP) == 4 {
-			mask, _ := r.Target.Mask.Size()
-			if len(r.Via) == 4 {
-				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
-			} else {
-				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
-			}
-		} else if len(r.Target.IP) == 16 {
-			mask, _ := r.Target.Mask.Size()
-			if len(r.Via) == 4 {
-				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet6, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
-			} else {
-				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
-			}
-		}
-	}
-	if rc != 0 {
-		return fmt.Errorf("tap device error removing route: %d", rc)
-	}
-	return nil
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-func handleTapMulticastGroupChange(gn unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t, added bool) {
-	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)
-				}
-			}
-		}
-	}()
-}
-
-//export goHandleTapAddedMulticastGroup
-func goHandleTapAddedMulticastGroup(gn, tapP unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t) {
-	handleTapMulticastGroupChange(gn, nwid, mac, adi, true)
-}
-
-//export goHandleTapRemovedMulticastGroup
-func goHandleTapRemovedMulticastGroup(gn, tapP unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t) {
-	handleTapMulticastGroupChange(gn, nwid, mac, adi, false)
-}

+ 1 - 1
go/pkg/zerotier/root.go

@@ -18,7 +18,7 @@ type Root struct {
 	DNSName   string
 	Identity  *Identity
 	Addresses []InetAddress
-	Locator   []byte
+	Locator   Locator
 	Preferred bool
 	Online    bool
 }

+ 24 - 0
go/pkg/zerotier/tap.go

@@ -17,15 +17,39 @@ import "net"
 
 // Tap represents an Ethernet tap connecting a virtual network to a device or something else "real"
 type Tap interface {
+	// Close is called when this tap is being shut down
+	Close()
+
+	// Type is a string describing what kind of tap this is e.g. "native" for OS-native
 	Type() string
+
+	// Error returns the most recent error experienced by this tap
 	Error() (int, string)
+
+	// SetEnabled sets whether this tap will accept and process packets
 	SetEnabled(enabled bool)
+
+	// Enabled returns the current enabled status
 	Enabled() bool
+
+	// AddIP assigns an IP address to this tap device
 	AddIP(ip *net.IPNet) error
+
+	// RemoveIP removes an IP address from this tap
 	RemoveIP(ip *net.IPNet) error
+
+	// IPs returns an array of all IPs currently assigned to this tap including those not assigned by ZeroTier
 	IPs() ([]net.IPNet, error)
+
+	// DeviceName gets the OS-specific device name for this tap or an empty string if none
 	DeviceName() string
+
+	// AddMulticastGroupChangeHandler registers a function to be called on multicast group subscribe or unsubscribe (first argument)
 	AddMulticastGroupChangeHandler(func(bool, *MulticastGroup))
+
+	// AddRoute adds a route to this tap device via the system or other routing table
 	AddRoute(r *Route) error
+
+	// RemoveRoute removes a route from this tap device
 	RemoveRoute(r *Route) error
 }

+ 18 - 3
node/Locator.hpp

@@ -129,9 +129,10 @@ public:
 		Str name;
 		char b32[128];
 		Utils::b32e(tmp,35,b32,sizeof(b32));
+		name << "ztl-";
 		name << b32;
 		Utils::b32e(tmp + 35,(ZT_ECC384_PUBLIC_KEY_SIZE+2) - 35,b32,sizeof(b32));
-		name << '.';
+		name << ".ztl-";
 		name << b32;
 		return name;
 	}
@@ -151,7 +152,9 @@ public:
 		for(char *saveptr=(char *)0,*p=Utils::stok(tmp,".",&saveptr);p;p=Utils::stok((char *)0,".",&saveptr)) {
 			if (b32ptr >= sizeof(b32))
 				break;
-			int s = Utils::b32d(p,b32 + b32ptr,sizeof(b32) - b32ptr);
+			if ((strlen(p) <= 4)||(memcmp(p,"ztl-",4) != 0))
+				continue;
+			int s = Utils::b32d(p + 4,b32 + b32ptr,sizeof(b32) - b32ptr);
 			if (s > 0) {
 				b32ptr += (unsigned int)s;
 				if (b32ptr > 2) {
@@ -186,7 +189,7 @@ public:
 	 * is used here so that FIPS-only nodes can always use DNS to locate roots as
 	 * FIPS-only nodes may be required to disable non-FIPS algorithms.
 	 */
-	inline std::vector<Str> makeTxtRecords(const uint8_t p384SigningKeyPublic[ZT_ECC384_PUBLIC_KEY_SIZE],const uint8_t p384SigningKeyPrivate[ZT_ECC384_PUBLIC_KEY_SIZE])
+	inline std::vector<Str> makeTxtRecords(const uint8_t p384SigningKeyPrivate[ZT_ECC384_PUBLIC_KEY_SIZE])
 	{
 		uint8_t s384[48];
 		char enc[256];
@@ -269,6 +272,18 @@ public:
 		}
 	}
 
+	inline bool deserialize(const void *data,unsigned int len)
+	{
+		ScopedPtr< Buffer<65536> > tmp(new Buffer<65536>());
+		tmp->append(data,len);
+		try {
+			deserialize(*tmp,0);
+			return true;
+		} catch ( ... ) {
+			return false;
+		}
+	}
+
 	template<unsigned int C>
 	inline void serialize(Buffer<C> &b,const bool forSign = false) const
 	{

+ 1 - 1
node/Utils.hpp

@@ -255,7 +255,7 @@ public:
 			*dest = (char)0;
 			return true;
 		}
-		char *end = dest + len;
+		char *const end = dest + len;
 		while ((*dest++ = *src++)) {
 			if (dest == end) {
 				*(--dest) = (char)0;