Browse Source

A bunch of CLI work

Adam Ierymenko 5 years ago
parent
commit
7fc78129f4

+ 6 - 0
go/cmd/zerotier/cli/common.go

@@ -14,6 +14,7 @@
 package cli
 package cli
 
 
 import (
 import (
+	"encoding/json"
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
@@ -43,3 +44,8 @@ func enabledDisabled(f bool) string {
 	}
 	}
 	return "DISABLED"
 	return "DISABLED"
 }
 }
+
+func jsonDump(obj interface{}) string {
+	j, _ := json.MarshalIndent(obj, "", "  ")
+	return string(j)
+}

+ 13 - 5
go/cmd/zerotier/cli/help.go

@@ -15,6 +15,7 @@ package cli
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+
 	"zerotier/pkg/zerotier"
 	"zerotier/pkg/zerotier"
 )
 )
 
 
@@ -26,7 +27,7 @@ Licensed under the ZeroTier BSL (see LICENSE.txt)`, zerotier.CoreVersionMajor, z
 func Help() {
 func Help() {
 	fmt.Println(copyrightText)
 	fmt.Println(copyrightText)
 	fmt.Println(`
 	fmt.Println(`
-Usage: zerotier [-options] <command> [-options] [command args]
+Usage: zerotier [-options] <command> [command args]
 
 
 Global Options:
 Global Options:
   -j                                   Output raw JSON where applicable
   -j                                   Output raw JSON where applicable
@@ -40,11 +41,18 @@ Commands:
   status                               Show ZeroTier service status and config
   status                               Show ZeroTier service status and config
   peers                                Show VL1 peers
   peers                                Show VL1 peers
   roots                                Show VL1 root servers
   roots                                Show VL1 root servers
-  addroot <locator> [<name>]           Add a VL1 root
+  addroot <locator> [name]             Add a VL1 root
   removeroot <name>                    Remove a VL1 root
   removeroot <name>                    Remove a VL1 root
-  makelocator <secret> <address> [...] Make and sign a locator
-  makelocatordnskey                    Create a new secure DNS name and key
-  makelocatordns <key> <locator>       Make DNS TXT records for a locator
+  locator <command> [args]             Locator management commands
+    new <identity> <address> [...]     Create and sign a locator
+    newdnskey                          Create a secure DNS name and secret
+    getdns <key> <locator>             Create secure DNS TXT records
+  identity <command> [args]            Identity management commands
+    new                                Create new identity (including secret)
+    getpublic <identity>               Extract only public part of identity
+    validate <identity>                Locally validate an identity
+    sign <identity> <file>             Sign a file with an identity's key
+    verify <identity> <file> <sig>     Verify a signature
   networks                             Show joined VL2 virtual networks
   networks                             Show joined VL2 virtual networks
   join <network ID>                    Join a virtual network
   join <network ID>                    Join a virtual network
   leave <network ID>                   Leave a virtual network
   leave <network ID>                   Leave a virtual network

+ 2 - 2
go/cmd/zerotier/cli/makelocator.go → go/cmd/zerotier/cli/identity.go

@@ -13,6 +13,6 @@
 
 
 package cli
 package cli
 
 
-// MakeLocator CLI command
-func MakeLocator(args []string) {
+// Identity command
+func Identity(args []string) {
 }
 }

+ 135 - 0
go/cmd/zerotier/cli/locator.go

@@ -0,0 +1,135 @@
+/*
+ * 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 cli
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+
+	"zerotier/pkg/zerotier"
+)
+
+func locatorNew(args []string) {
+	if len(args) < 2 {
+		Help()
+		os.Exit(1)
+	}
+
+	identityData, err := ioutil.ReadFile(args[0])
+	if err != nil {
+		fmt.Printf("FATAL: unable to read identity: %s\n", err.Error())
+		os.Exit(1)
+	}
+	identity, err := zerotier.NewIdentityFromString(string(identityData))
+	if err != nil {
+		fmt.Printf("FATAL: invalid identity: %s\n", err.Error())
+		os.Exit(1)
+	}
+	if !identity.HasPrivate() {
+		fmt.Println("FATAL: identity does not contain secret key")
+		os.Exit(1)
+	}
+
+	var virt []*zerotier.Identity
+	var phys []*zerotier.InetAddress
+	for i := 1; i < len(args); i++ {
+		if strings.Contains(args[i], "/") {
+			a := zerotier.NewInetAddressFromString(args[i])
+			if a == nil {
+				fmt.Printf("FATAL: IP/port address '%s' is not valid\n", args[i])
+				os.Exit(1)
+			}
+			phys = append(phys, a)
+		} else {
+			a, err := zerotier.NewIdentityFromString(args[i])
+			if err != nil {
+				fmt.Printf("FATAL: identity (virtual address) '%s' is not valid: %s\n", args[i], err.Error())
+				os.Exit(1)
+			}
+			virt = append(virt, a)
+		}
+	}
+
+	loc, err := zerotier.NewLocator(identity, virt, phys)
+	if err != nil {
+		fmt.Printf("FATAL: internal error creating locator: %s\n", err.Error())
+		os.Exit(1)
+	}
+	fmt.Println(jsonDump(loc))
+}
+
+func locatorNewDNSKey(args []string) {
+	if len(args) != 0 {
+		Help()
+		os.Exit(0)
+	}
+
+	sk, err := zerotier.NewLocatorDNSSigningKey()
+	if err != nil {
+		fmt.Printf("FATAL: error creating secure DNS signing key: %s", err.Error())
+		os.Exit(1)
+	}
+	fmt.Println(jsonDump(sk))
+	os.Exit(0)
+}
+
+func locatorGetDNS(args []string) {
+	if len(args) < 2 {
+		Help()
+		os.Exit(1)
+	}
+
+	keyData, err := ioutil.ReadFile(args[0])
+	if err != nil {
+		fmt.Printf("FATAL: unable to read secure DNS key file: %s\n", err.Error())
+		os.Exit(1)
+	}
+	var sk zerotier.LocatorDNSSigningKey
+	err = json.Unmarshal(keyData, &sk)
+	if err != nil {
+		fmt.Printf("FATAL: DNS key file invalid: %s", err.Error())
+		os.Exit(1)
+	}
+
+	locData, err := ioutil.ReadFile(args[1])
+	if err != nil {
+		fmt.Printf("FATAL: unable to read locator: %s\n", err.Error())
+		os.Exit(1)
+	}
+	var loc zerotier.Locator
+	err = json.Unmarshal(locData, &loc)
+	if err != nil {
+		fmt.Printf("FATAL: locator invalid: %s", err.Error())
+		os.Exit(1)
+	}
+}
+
+// Locator CLI command
+func Locator(args []string) {
+	if len(args) > 0 {
+		switch args[0] {
+		case "new":
+			locatorNew(args[1:])
+		case "newdnskey":
+			locatorNewDNSKey(args[1:])
+		case "getdns":
+			locatorGetDNS(args[1:])
+		}
+	}
+	Help()
+	os.Exit(1)
+}

+ 0 - 18
go/cmd/zerotier/cli/makelocatordns.go

@@ -1,18 +0,0 @@
-/*
- * 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 cli
-
-// MakeLocatorDNS CLI command
-func MakeLocatorDNS(args []string) {
-}

+ 0 - 18
go/cmd/zerotier/cli/makelocatordnskey.go

@@ -1,18 +0,0 @@
-/*
- * 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 cli
-
-// MakeLocatorDNSKey CLI command
-func MakeLocatorDNSKey(args []string) {
-}

+ 2 - 3
go/cmd/zerotier/cli/peers.go

@@ -14,9 +14,9 @@
 package cli
 package cli
 
 
 import (
 import (
-	"encoding/json"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
+
 	"zerotier/pkg/zerotier"
 	"zerotier/pkg/zerotier"
 )
 )
 
 
@@ -26,8 +26,7 @@ func Peers(basePath, authToken string, args []string, jsonOutput bool) {
 	apiGet(basePath, authToken, "/peer", &peers)
 	apiGet(basePath, authToken, "/peer", &peers)
 
 
 	if jsonOutput {
 	if jsonOutput {
-		j, _ := json.MarshalIndent(&peers, "", "  ")
-		fmt.Println(string(j))
+		fmt.Println(jsonDump(&peers))
 	} else {
 	} else {
 		fmt.Printf("<ztaddr>   <ver>   <role> <lat> <link> <lastTX> <lastRX> <path(s)>\n")
 		fmt.Printf("<ztaddr>   <ver>   <role> <lat> <link> <lastTX> <lastRX> <path(s)>\n")
 		for _, peer := range peers {
 		for _, peer := range peers {

+ 2 - 3
go/cmd/zerotier/cli/status.go

@@ -14,9 +14,9 @@
 package cli
 package cli
 
 
 import (
 import (
-	"encoding/json"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
+
 	"zerotier/pkg/zerotier"
 	"zerotier/pkg/zerotier"
 )
 )
 
 
@@ -26,8 +26,7 @@ func Status(basePath, authToken string, args []string, jsonOutput bool) {
 	apiGet(basePath, authToken, "/status", &status)
 	apiGet(basePath, authToken, "/status", &status)
 
 
 	if jsonOutput {
 	if jsonOutput {
-		j, _ := json.MarshalIndent(&status, "", "  ")
-		fmt.Println(string(j))
+		fmt.Println(jsonDump(&status))
 	} else {
 	} else {
 		online := "ONLINE"
 		online := "ONLINE"
 		if !status.Online {
 		if !status.Online {

+ 6 - 7
go/cmd/zerotier/zerotier.go

@@ -21,6 +21,7 @@ import (
 	"path"
 	"path"
 	"runtime"
 	"runtime"
 	"strings"
 	"strings"
+
 	"zerotier/cmd/zerotier/cli"
 	"zerotier/cmd/zerotier/cli"
 	"zerotier/pkg/zerotier"
 	"zerotier/pkg/zerotier"
 )
 )
@@ -112,7 +113,7 @@ func main() {
 	case "peers", "listpeers":
 	case "peers", "listpeers":
 		authTokenRequired(authToken)
 		authTokenRequired(authToken)
 		cli.Peers(basePath, authToken, cmdArgs, *jflag)
 		cli.Peers(basePath, authToken, cmdArgs, *jflag)
-	case "roots":
+	case "roots", "listroots", "listmoons":
 		authTokenRequired(authToken)
 		authTokenRequired(authToken)
 		cli.Roots(basePath, authToken, cmdArgs)
 		cli.Roots(basePath, authToken, cmdArgs)
 	case "addroot":
 	case "addroot":
@@ -121,12 +122,10 @@ func main() {
 	case "removeroot":
 	case "removeroot":
 		authTokenRequired(authToken)
 		authTokenRequired(authToken)
 		cli.RemoveRoot(basePath, authToken, cmdArgs)
 		cli.RemoveRoot(basePath, authToken, cmdArgs)
-	case "makelocator":
-		cli.MakeLocator(cmdArgs)
-	case "makelocatordnskey":
-		cli.MakeLocatorDNSKey(cmdArgs)
-	case "makelocatordns":
-		cli.MakeLocatorDNS(cmdArgs)
+	case "locator":
+		cli.Locator(cmdArgs)
+	case "identity":
+		cli.Identity(cmdArgs)
 	case "networks", "listnetworks":
 	case "networks", "listnetworks":
 		authTokenRequired(authToken)
 		authTokenRequired(authToken)
 		cli.Networks(basePath, authToken, cmdArgs)
 		cli.Networks(basePath, authToken, cmdArgs)

+ 1 - 1
go/native/GoGlue.cpp

@@ -732,7 +732,7 @@ extern "C" int ZT_GoLocator_makeSecureDNSName(char *name,unsigned int nameBufSiz
 	const Str n(Locator::makeSecureDnsName(pub));
 	const Str n(Locator::makeSecureDnsName(pub));
 	if (n.length() >= nameBufSize)
 	if (n.length() >= nameBufSize)
 		return -1;
 		return -1;
-	Utils::scopy(name,sizeof(name),n.c_str());
+	Utils::scopy(name,nameBufSize,n.c_str());
 	return ZT_ECC384_PRIVATE_KEY_SIZE;
 	return ZT_ECC384_PRIVATE_KEY_SIZE;
 }
 }
 
 

+ 0 - 1
go/native/GoGlue.h

@@ -132,7 +132,6 @@ int ZT_GoLocator_decodeLocator(const uint8_t *locatorBytes,unsigned int locatorS
 int ZT_GoLocator_makeSignedTxtRecords(
 int ZT_GoLocator_makeSignedTxtRecords(
 	const uint8_t *locator,
 	const uint8_t *locator,
 	unsigned int locatorSize,
 	unsigned int locatorSize,
-	int64_t ts,
 	const char *name,
 	const char *name,
 	const uint8_t *privateKey,
 	const uint8_t *privateKey,
 	unsigned int privateKeySize,
 	unsigned int privateKeySize,

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

@@ -45,7 +45,7 @@ type Identity struct {
 // NewIdentityFromString generates a new identity from its string representation.
 // NewIdentityFromString generates a new identity from its string representation.
 // The private key is imported as well if it is present.
 // The private key is imported as well if it is present.
 func NewIdentityFromString(s string) (*Identity, error) {
 func NewIdentityFromString(s string) (*Identity, error) {
-	ss := strings.Split(s, ":")
+	ss := strings.Split(strings.TrimSpace(s), ":")
 	if len(ss) < 3 {
 	if len(ss) < 3 {
 		return nil, ErrInvalidParameter
 		return nil, ErrInvalidParameter
 	}
 	}

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

@@ -43,7 +43,7 @@ func (i *InetAddress) Less(i2 *InetAddress) bool {
 // NewInetAddressFromString parses an IP[/port] format address
 // NewInetAddressFromString parses an IP[/port] format address
 func NewInetAddressFromString(s string) *InetAddress {
 func NewInetAddressFromString(s string) *InetAddress {
 	i := new(InetAddress)
 	i := new(InetAddress)
-	ss := strings.Split(s, "/")
+	ss := strings.Split(strings.TrimSpace(s), "/")
 	if len(ss) > 0 {
 	if len(ss) > 0 {
 		i.IP = net.ParseIP(ss[0])
 		i.IP = net.ParseIP(ss[0])
 		i4 := i.IP.To4()
 		i4 := i.IP.To4()

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

@@ -151,6 +151,25 @@ func NewLocatorFromBytes(b []byte) (*Locator, error) {
 	return &loc, nil
 	return &loc, nil
 }
 }
 
 
+// MakeTXTRecords creates secure DNS TXT records for this locator
+func (l *Locator) MakeTXTRecords(key *LocatorDNSSigningKey) ([]string, error) {
+	if key == nil || len(l.Bytes) == 0 || len(key.PrivateKey) == 0 {
+		return nil, ErrInvalidParameter
+	}
+	var results [256][256]C.char
+	cName := C.CString(key.SecureDNSName)
+	defer C.free(unsafe.Pointer(cName))
+	count := int(C.ZT_GoLocator_makeSignedTxtRecords((*C.uint8_t)(&l.Bytes[0]), C.uint(len(l.Bytes)), cName, (*C.uint8_t)(&key.PrivateKey[0]), C.uint(len(key.PrivateKey)), &results[0]))
+	if count > 0 {
+		var t []string
+		for i := 0; i < int(count); i++ {
+			t = append(t, C.GoString(&results[i][0]))
+		}
+		return t, nil
+	}
+	return nil, ErrInternal
+}
+
 // MarshalJSON marshals this Locator as its byte encoding
 // MarshalJSON marshals this Locator as its byte encoding
 func (l *Locator) MarshalJSON() ([]byte, error) {
 func (l *Locator) MarshalJSON() ([]byte, error) {
 	return json.Marshal(l)
 	return json.Marshal(l)

+ 11 - 18
go/pkg/zerotier/node.go

@@ -71,6 +71,9 @@ const (
 	// AFInet6 is the address family for IPv6
 	// AFInet6 is the address family for IPv6
 	AFInet6 = C.AF_INET6
 	AFInet6 = C.AF_INET6
 
 
+	networkConfigOpUp     int = C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP
+	networkConfigOpUpdate int = C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE
+
 	defaultVirtualNetworkMTU = C.ZT_DEFAULT_MTU
 	defaultVirtualNetworkMTU = C.ZT_DEFAULT_MTU
 )
 )
 
 
@@ -548,21 +551,11 @@ func (n *Node) Roots() []*Root {
 	if rl != nil {
 	if rl != nil {
 		for i := 0; i < int(rl.count); i++ {
 		for i := 0; i < int(rl.count); i++ {
 			root := (*C.ZT_Root)(unsafe.Pointer(uintptr(unsafe.Pointer(rl)) + C.sizeof_ZT_RootList))
 			root := (*C.ZT_Root)(unsafe.Pointer(uintptr(unsafe.Pointer(rl)) + C.sizeof_ZT_RootList))
-			id, err := NewIdentityFromString(C.GoString(root.identity))
-			if err == nil {
-				var addrs []InetAddress
-				for j := uintptr(0); j < uintptr(root.addressCount); j++ {
-					a := NewInetAddressFromSockaddr(unsafe.Pointer(uintptr(unsafe.Pointer(root.addresses)) + (j * C.sizeof_struct_sockaddr_storage)))
-					if a != nil && a.Valid() {
-						addrs = append(addrs, *a)
-					}
-				}
+			loc, _ := NewLocatorFromBytes(C.GoBytes(root.locator, C.int(root.locatorSize)))
+			if loc != nil {
 				roots = append(roots, &Root{
 				roots = append(roots, &Root{
-					Name:      C.GoString(root.name),
-					Identity:  id,
-					Addresses: addrs,
-					Preferred: root.preferred != 0,
-					Online:    root.online != 0,
+					Name:    C.GoString(root.name),
+					Locator: loc,
 				})
 				})
 			}
 			}
 		}
 		}
@@ -584,11 +577,11 @@ func (n *Node) SetRoot(name string, locator *Locator) error {
 	}
 	}
 	var lb []byte
 	var lb []byte
 	if locator != nil {
 	if locator != nil {
-		lb = locator.Bytes()
+		lb = locator.Bytes
 	}
 	}
 	var lbp unsafe.Pointer
 	var lbp unsafe.Pointer
 	if len(lb) > 0 {
 	if len(lb) > 0 {
-		lbp = &lb[0]
+		lbp = unsafe.Pointer(&lb[0])
 	}
 	}
 	cn := C.CString(name)
 	cn := C.CString(name)
 	defer C.free(unsafe.Pointer(cn))
 	defer C.free(unsafe.Pointer(cn))
@@ -925,8 +918,8 @@ func goVirtualNetworkConfigFunc(gn, _ unsafe.Pointer, nwid C.uint64_t, op C.int,
 		node.networksLock.RUnlock()
 		node.networksLock.RUnlock()
 
 
 		if network != nil {
 		if network != nil {
-			switch op {
-			case C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP, C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP:
+			switch int(op) {
+			case networkConfigOpUp, networkConfigOpUpdate:
 				ncc := (*C.ZT_VirtualNetworkConfig)(conf)
 				ncc := (*C.ZT_VirtualNetworkConfig)(conf)
 				if network.networkConfigRevision() > uint64(ncc.netconfRevision) {
 				if network.networkConfigRevision() > uint64(ncc.netconfRevision) {
 					return
 					return

+ 2 - 4
go/pkg/zerotier/root.go

@@ -15,8 +15,6 @@ package zerotier
 
 
 // Root describes a root server used to find and establish communication with other nodes.
 // Root describes a root server used to find and establish communication with other nodes.
 type Root struct {
 type Root struct {
-	Name      string
-	Locator   *Locator
-	Preferred bool
-	Online    bool
+	Name    string
+	Locator *Locator
 }
 }

+ 4 - 19
include/ZeroTierCore.h

@@ -532,29 +532,14 @@ typedef struct {
 	const char *name;
 	const char *name;
 
 
 	/**
 	/**
-	 * Current public identity or NULL if not known (only possible with dynamic roots)
+	 * Serialized locator
 	 */
 	 */
-	const char *identity;
+	const void *locator;
 
 
 	/**
 	/**
-	 * Current physical address(es) of this root
+	 * The size of locator in bytes
 	 */
 	 */
-	const struct sockaddr_storage *addresses;
-
-	/**
-	 * Number of physical addresses
-	 */
-	unsigned int addressCount;
-
-	/**
-	 * If true, this is the current preferred root
-	 */
-	int preferred;
-
-	/**
-	 * If true, this root server appears online
-	 */
-	int online;
+	unsigned int locatorSize;
 } ZT_Root;
 } ZT_Root;
 
 
 /**
 /**

+ 1 - 2
node/Str.hpp

@@ -104,8 +104,7 @@ public:
 					_l = ZT_STR_CAPACITY;
 					_l = ZT_STR_CAPACITY;
 					throw ZT_EXCEPTION_OUT_OF_BOUNDS;
 					throw ZT_EXCEPTION_OUT_OF_BOUNDS;
 				}
 				}
-				_s[l++] = *s;
-				++s;
+				_s[l++] = *(s++);
 			}
 			}
 			_s[l] = 0;
 			_s[l] = 0;
 			_l = (uint8_t)l;
 			_l = (uint8_t)l;

+ 19 - 50
node/Topology.hpp

@@ -360,63 +360,32 @@ public:
 	 */
 	 */
 	inline ZT_RootList *apiRoots(const int64_t now) const
 	inline ZT_RootList *apiRoots(const int64_t now) const
 	{
 	{
+		ScopedPtr< Buffer<65536> > lbuf(new Buffer<65536>());
 		Mutex::Lock l2(_roots_l);
 		Mutex::Lock l2(_roots_l);
-
-		// The memory allocated here has room for all roots plus the maximum size
-		// of their DNS names, identities, and up to 16 physical addresses. Most
-		// roots will have two: one V4 and one V6.
-		const unsigned int totalRoots = _roots.size();
-		ZT_RootList *rl = reinterpret_cast<ZT_RootList *>(malloc(sizeof(ZT_RootList) + (sizeof(ZT_Root) * totalRoots) + ((sizeof(struct sockaddr_storage) * ZT_MAX_PEER_NETWORK_PATHS) * totalRoots) + ((ZT_IDENTITY_STRING_BUFFER_LENGTH + 1024) * totalRoots)));
-		if (!rl) {
+		ZT_RootList *rl = (ZT_RootList *)malloc(sizeof(ZT_RootList) + (sizeof(ZT_Root) * _roots.size()) + (256 * _roots.size()) + (65536 * _roots.size()));
+		if (!rl)
 			return nullptr;
 			return nullptr;
-		}
+		char *nptr = ((char *)rl) + sizeof(ZT_RootList) + (sizeof(ZT_Root) * _roots.size());
+		uint8_t *lptr = ((uint8_t *)nptr) + (256 * _roots.size());
 
 
 		unsigned int c = 0;
 		unsigned int c = 0;
-		char *nameBufPtr = reinterpret_cast<char *>(rl) + sizeof(ZT_RootList) + (sizeof(ZT_Root) * totalRoots);
-		struct sockaddr_storage *addrBuf = reinterpret_cast<struct sockaddr_storage *>(nameBufPtr);
-		nameBufPtr += (sizeof(struct sockaddr_storage) * ZT_MAX_PEER_NETWORK_PATHS) * totalRoots;
-
-		_bestRoot_l.lock();
-		const Peer *const bestRootPtr = _bestRoot.ptr();
-		_bestRoot_l.unlock();
-
-		{
-			Str *k = (Str *)0;
-			Locator *v = (Locator *)0;
-			Hashtable< Str,Locator >::Iterator i(const_cast<Topology *>(this)->_roots);
-			while (i.next(k,v)) {
-				rl->roots[c].name = nameBufPtr;
-				const char *p = k->c_str();
-				while (*p)
-					*(nameBufPtr++) = *(p++);
-				*(nameBufPtr++) = (char)0;
-
-				if (v->id()) {
-					rl->roots[c].identity = nameBufPtr;
-					v->id().toString(false,nameBufPtr);
-					nameBufPtr += strlen(nameBufPtr) + 1;
-				}
-
-				rl->roots[c].addresses = addrBuf;
-				unsigned int ac = 0;
-				for(unsigned int j=(unsigned int)v->phy().size();(ac<j)&&(ac<16);++ac)
-					*(addrBuf++) = v->phy()[ac];
-				rl->roots[c].addressCount = ac;
-
-				_peers_l.lock();
-				const SharedPtr<Peer> *psptr = _peers.get(v->id().address());
-				if (psptr) {
-					rl->roots[c].preferred = (psptr->ptr() == bestRootPtr) ? 1 : 0;
-					rl->roots[c].online = (*psptr)->alive(now) ? 1 : 0;
-				}
-				_peers_l.unlock();
-
-				++c;
-			}
+		Str *k = (Str *)0;
+		Locator *v = (Locator *)0;
+		Hashtable< Str,Locator >::Iterator i(const_cast<Topology *>(this)->_roots);
+		while (i.next(k,v)) {
+			Utils::scopy(nptr,256,k->c_str());
+			rl->roots[c].name = nptr;
+			nptr += 256;
+			lbuf->clear();
+			v->serialize(*lbuf);
+			memcpy(lptr,lbuf->unsafeData(),lbuf->size());
+			rl->roots[c].locator = lptr;
+			rl->roots[c].locatorSize = lbuf->size();
+			lptr += 65536;
+			++c;
 		}
 		}
 
 
 		rl->count = c;
 		rl->count = c;
-
 		return rl;
 		return rl;
 	}
 	}