2
0
Эх сурвалжийг харах

Plumb new locator code through to Go, fix some cgo issues, fix some compiler warnings.

Adam Ierymenko 5 жил өмнө
parent
commit
a5390b1bc8

+ 5 - 5
controller/EmbeddedNetworkController.cpp

@@ -484,10 +484,10 @@ void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender)
 	}
 	}
 #endif
 #endif
 
 
-	std::string lfJSON;
+	String lfJSON;
 	OSUtils::readFile((_ztPath + ZT_PATH_SEPARATOR_S "local.conf").c_str(),lfJSON);
 	OSUtils::readFile((_ztPath + ZT_PATH_SEPARATOR_S "local.conf").c_str(),lfJSON);
 	if (lfJSON.length() > 0) {
 	if (lfJSON.length() > 0) {
-		nlohmann::json lfConfig(OSUtils::jsonParse(lfJSON));
+		nlohmann::json lfConfig(OSUtils::jsonParse(std::string(lfJSON.c_str())));
 		nlohmann::json &settings = lfConfig["settings"];
 		nlohmann::json &settings = lfConfig["settings"];
 		if (settings.is_object()) {
 		if (settings.is_object()) {
 			nlohmann::json &controllerDb = settings["controllerDb"];
 			nlohmann::json &controllerDb = settings["controllerDb"];
@@ -831,13 +831,13 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 						json &v6m = b["v6AssignMode"];
 						json &v6m = b["v6AssignMode"];
 						if (!nv6m.is_object()) nv6m = json::object();
 						if (!nv6m.is_object()) nv6m = json::object();
 						if (v6m.is_string()) { // backward compatibility
 						if (v6m.is_string()) { // backward compatibility
-							std::vector<std::string> v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","",""));
+							Vector<String> v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","",""));
 							std::sort(v6ms.begin(),v6ms.end());
 							std::sort(v6ms.begin(),v6ms.end());
 							v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end());
 							v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end());
 							nv6m["rfc4193"] = false;
 							nv6m["rfc4193"] = false;
 							nv6m["zt"] = false;
 							nv6m["zt"] = false;
 							nv6m["6plane"] = false;
 							nv6m["6plane"] = false;
-							for(std::vector<std::string>::iterator i(v6ms.begin());i!=v6ms.end();++i) {
+							for(Vector<String>::iterator i(v6ms.begin());i!=v6ms.end();++i) {
 								if (*i == "rfc4193")
 								if (*i == "rfc4193")
 									nv6m["rfc4193"] = true;
 									nv6m["rfc4193"] = true;
 								else if (*i == "zt")
 								else if (*i == "zt")
@@ -1295,7 +1295,7 @@ void EmbeddedNetworkController::_request(
 	nc->credentialTimeMaxDelta = credentialtmd;
 	nc->credentialTimeMaxDelta = credentialtmd;
 	nc->revision = OSUtils::jsonInt(network["revision"],0ULL);
 	nc->revision = OSUtils::jsonInt(network["revision"],0ULL);
 	nc->issuedTo = identity.address();
 	nc->issuedTo = identity.address();
-	memcpy(nc->issuedToFingerprintHash,identity.fingerprint().hash(),sizeof(nc->issuedToFingerprintHash));
+	memcpy(nc->issuedToFingerprintHash,identity.fingerprint().hash,sizeof(nc->issuedToFingerprintHash));
 	if (OSUtils::jsonBool(network["enableBroadcast"],true)) nc->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST;
 	if (OSUtils::jsonBool(network["enableBroadcast"],true)) nc->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST;
 	Utils::scopy(nc->name,sizeof(nc->name),OSUtils::jsonString(network["name"],"").c_str());
 	Utils::scopy(nc->name,sizeof(nc->name),OSUtils::jsonString(network["name"],"").c_str());
 	nc->mtu = std::max(std::min((unsigned int)OSUtils::jsonInt(network["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU);
 	nc->mtu = std::max(std::min((unsigned int)OSUtils::jsonInt(network["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU);

+ 7 - 7
controller/FileDB.cpp

@@ -19,31 +19,31 @@ namespace ZeroTier
 FileDB::FileDB(const char *path) :
 FileDB::FileDB(const char *path) :
 	DB(),
 	DB(),
 	_path(path),
 	_path(path),
-	_networksPath(_path + ZT_PATH_SEPARATOR_S + "network"),
+	_networksPath((_path + ZT_PATH_SEPARATOR_S + "network").c_str()),
 	_running(true)
 	_running(true)
 {
 {
 	OSUtils::mkdir(_path.c_str());
 	OSUtils::mkdir(_path.c_str());
 	OSUtils::lockDownFile(_path.c_str(),true);
 	OSUtils::lockDownFile(_path.c_str(),true);
 	OSUtils::mkdir(_networksPath.c_str());
 	OSUtils::mkdir(_networksPath.c_str());
 
 
-	std::vector<std::string> networks(OSUtils::listDirectory(_networksPath.c_str(),false));
-	std::string buf;
+	Vector<String> networks(OSUtils::listDirectory(_networksPath.c_str(),false));
+	String buf;
 	for(auto n=networks.begin();n!=networks.end();++n) {
 	for(auto n=networks.begin();n!=networks.end();++n) {
 		buf.clear();
 		buf.clear();
 		if ((n->length() == 21)&&(OSUtils::readFile((_networksPath + ZT_PATH_SEPARATOR_S + *n).c_str(),buf))) {
 		if ((n->length() == 21)&&(OSUtils::readFile((_networksPath + ZT_PATH_SEPARATOR_S + *n).c_str(),buf))) {
 			try {
 			try {
-				nlohmann::json network(OSUtils::jsonParse(buf));
+				nlohmann::json network(OSUtils::jsonParse(std::string(buf.c_str())));
 				const std::string nwids = network["id"];
 				const std::string nwids = network["id"];
 				if (nwids.length() == 16) {
 				if (nwids.length() == 16) {
 					nlohmann::json nullJson;
 					nlohmann::json nullJson;
 					_networkChanged(nullJson,network,false);
 					_networkChanged(nullJson,network,false);
-					std::string membersPath(_networksPath + ZT_PATH_SEPARATOR_S + nwids + ZT_PATH_SEPARATOR_S "member");
-					std::vector<std::string> members(OSUtils::listDirectory(membersPath.c_str(),false));
+					String membersPath((_networksPath + ZT_PATH_SEPARATOR_S + nwids.c_str() + ZT_PATH_SEPARATOR_S "member").c_str());
+					Vector<String> members(OSUtils::listDirectory(membersPath.c_str(),false));
 					for(auto m=members.begin();m!=members.end();++m) {
 					for(auto m=members.begin();m!=members.end();++m) {
 						buf.clear();
 						buf.clear();
 						if ((m->length() == 15)&&(OSUtils::readFile((membersPath + ZT_PATH_SEPARATOR_S + *m).c_str(),buf))) {
 						if ((m->length() == 15)&&(OSUtils::readFile((membersPath + ZT_PATH_SEPARATOR_S + *m).c_str(),buf))) {
 							try {
 							try {
-								nlohmann::json member(OSUtils::jsonParse(buf));
+								nlohmann::json member(OSUtils::jsonParse(std::string(buf.c_str())));
 								const std::string addrs = member["id"];
 								const std::string addrs = member["id"];
 								if (addrs.length() == 10) {
 								if (addrs.length() == 10) {
 									nlohmann::json nullJson2;
 									nlohmann::json nullJson2;

+ 2 - 2
controller/FileDB.hpp

@@ -33,8 +33,8 @@ public:
 	virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
 	virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
 
 
 protected:
 protected:
-	std::string _path;
-	std::string _networksPath;
+	String _path;
+	String _networksPath;
 	std::thread _onlineUpdateThread;
 	std::thread _onlineUpdateThread;
 	std::map< uint64_t,std::map<uint64_t,std::map<int64_t,InetAddress> > > _online;
 	std::map< uint64_t,std::map<uint64_t,std::map<int64_t,InetAddress> > > _online;
 	std::mutex _online_l;
 	std::mutex _online_l;

+ 7 - 4
go/cmd/zerotier/cli/help.go

@@ -38,9 +38,6 @@ Commands:
   service                               Start as service
   service                               Start as service
   status                                Show node status, identity, and config
   status                                Show node status, identity, and config
   peers                                 List all VL1 peers
   peers                                 List all VL1 peers
-  roots                                 List root peers
-  addroot <path | URL>                  Add root from root spec file or URL
-  removeroot <address>                  Remove a peer from the root list
   join <network ID> [fingerprint]       Join a virtual network
   join <network ID> [fingerprint]       Join a virtual network
   leave <network ID>                    Leave a virtual network
   leave <network ID>                    Leave a virtual network
   networks                              List VL2 virtual networks
   networks                              List VL2 virtual networks
@@ -63,7 +60,13 @@ Commands:
     validate <identity>                 Locally validate an identity
     validate <identity>                 Locally validate an identity
     sign <identity> <file>              Sign a file with an identity's key
     sign <identity> <file>              Sign a file with an identity's key
     verify <identity> <file> <sig>      Verify a signature
     verify <identity> <file> <sig>      Verify a signature
-    makeroot <identity> <address[,...]> Make a root spec (see docs)
+  locator <command> [args]              Locator management commands
+    new <identity> <address> [...]      Create and sign a new locator
+    show <locator> [<identity>]         Show locator information
+  roots                                 List root peers
+  addroot <identity> <locator>          Add a root or update its locator
+  addroot <url>                         Add or update roots from a URL
+  removeroot <address>                  Remove a peer from the root list
 
 
 The 'service' command does not exit until the service receives a signal.
 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
 This is typically run from launchd (Mac), systemd or init (Linux), a Windows

+ 0 - 4
go/cmd/zerotier/cli/identity.go

@@ -102,10 +102,6 @@ func Identity(args []string) {
 				}
 				}
 			}
 			}
 
 
-		case "makeroot":
-			if len(args) >= 2 {
-			}
-
 		}
 		}
 	}
 	}
 	Help()
 	Help()

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

@@ -352,11 +352,8 @@ func createAPIServer(basePath string, node *Node) (*http.Server, *http.Server, e
 				if err != nil || len(rsdata) == 0 {
 				if err != nil || len(rsdata) == 0 {
 					_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"read error"})
 					_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"read error"})
 				} else {
 				} else {
-					p, err := node.AddRoot(rsdata)
-					if err != nil {
-						_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"invalid root spec"})
-					}
-					_ = apiSendObj(out, req, http.StatusOK, p)
+					// TODO
+					_ = apiSendObj(out, req, http.StatusOK, nil)
 				}
 				}
 			} else {
 			} else {
 				out.Header().Set("Allow", "POST, PUT")
 				out.Header().Set("Allow", "POST, PUT")

+ 42 - 36
go/pkg/zerotier/endpoint.go

@@ -1,13 +1,15 @@
 package zerotier
 package zerotier
 
 
 // #include "../../native/GoGlue.h"
 // #include "../../native/GoGlue.h"
+// static inline const ZT_Fingerprint *_getFP(const ZT_Endpoint *ep) { return &(ep->value.fp); }
+// static inline uint64_t _getAddress(const ZT_Endpoint *ep) { return ep->value.fp.address; }
+// static inline uint64_t _getMAC(const ZT_Endpoint *ep) { return ep->value.mac; }
+// static inline const struct sockaddr_storage *_getSS(const ZT_Endpoint *ep) { return &(ep->value.ss); }
 import "C"
 import "C"
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
-	"strconv"
-	"strings"
 	"unsafe"
 	"unsafe"
 )
 )
 
 
@@ -29,26 +31,57 @@ type Endpoint struct {
 
 
 // Type returns this endpoint's type.
 // Type returns this endpoint's type.
 func (ep *Endpoint) Type() int {
 func (ep *Endpoint) Type() int {
-	return ep.cep._type
+	return int(ep.cep._type)
 }
 }
 
 
 // InetAddress gets this Endpoint as an InetAddress or nil if its type is not addressed by one.
 // InetAddress gets this Endpoint as an InetAddress or nil if its type is not addressed by one.
 func (ep *Endpoint) InetAddress() *InetAddress {
 func (ep *Endpoint) InetAddress() *InetAddress {
 	switch ep.cep._type {
 	switch ep.cep._type {
 	case EndpointTypeIp, EndpointTypeIpUdp, EndpointTypeIpTcp, EndpointTypeIpHttp2:
 	case EndpointTypeIp, EndpointTypeIpUdp, EndpointTypeIpTcp, EndpointTypeIpHttp2:
-		ua := sockaddrStorageToUDPAddr(&(ep.cep.a.ss))
+		ua := sockaddrStorageToUDPAddr(C._getSS(&ep.cep))
 		return &InetAddress{IP: ua.IP, Port: ua.Port}
 		return &InetAddress{IP: ua.IP, Port: ua.Port}
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
+// Address returns a ZeroTier address if this is a ZeroTier endpoint or a zero address otherwise.
+func (ep *Endpoint) Address() Address {
+	switch ep.cep._type {
+	case EndpointTypeZeroTier:
+		return Address(C._getAddress(&ep.cep))
+	}
+	return Address(0)
+}
+
+// Fingerprint returns a fingerprint if this is a ZeroTier endpoint or nil otherwise.
+func (ep *Endpoint) Fingerprint() *Fingerprint {
+	switch ep.cep._type {
+	case EndpointTypeZeroTier:
+		cfp := C._getFP(&ep.cep)
+		fp := Fingerprint{Address: Address(cfp.address), Hash: C.GoBytes(unsafe.Pointer(&cfp.hash[0]), 48)}
+		if allZero(fp.Hash) {
+			fp.Hash = nil
+		}
+		return &fp
+	}
+	return nil
+}
+
+// MAC returns a MAC address if this is an Ethernet type endpoint or a zero address otherwise.
+func (ep *Endpoint) MAC() MAC {
+	switch ep.cep._type {
+	case EndpointTypeEthernet, EndpointTypeWifiDirect, EndpointTypeBluetooth:
+		return MAC(C._getMAC(&ep.cep))
+	}
+	return MAC(0)
+}
+
 func (ep *Endpoint) String() string {
 func (ep *Endpoint) String() string {
 	switch ep.cep._type {
 	switch ep.cep._type {
 	case EndpointTypeZeroTier:
 	case EndpointTypeZeroTier:
-		fp := Fingerprint{Address: Address(ep.cep.a.fp.address), Hash: *((*[48]byte)(unsafe.Pointer(&ep.cep.a.fp.hash[0])))}
-		return fmt.Sprintf("%d/%s", ep.Type(), fp.String())
+		return fmt.Sprintf("%d/%s", ep.Type(), ep.Fingerprint().String())
 	case EndpointTypeEthernet, EndpointTypeWifiDirect, EndpointTypeBluetooth:
 	case EndpointTypeEthernet, EndpointTypeWifiDirect, EndpointTypeBluetooth:
-		return fmt.Sprintf("%d/%s", ep.Type(), MAC(ep.cep.a.mac).String())
+		return fmt.Sprintf("%d/%s", ep.Type(), ep.MAC().String())
 	case EndpointTypeIp, EndpointTypeIpUdp, EndpointTypeIpTcp, EndpointTypeIpHttp2:
 	case EndpointTypeIp, EndpointTypeIpUdp, EndpointTypeIpTcp, EndpointTypeIpHttp2:
 		return fmt.Sprintf("%d/%s", ep.Type(), ep.InetAddress().String())
 		return fmt.Sprintf("%d/%s", ep.Type(), ep.InetAddress().String())
 	}
 	}
@@ -61,33 +94,6 @@ func (ep *Endpoint) MarshalJSON() ([]byte, error) {
 }
 }
 
 
 func (ep *Endpoint) UnmarshalJSON(j []byte) error {
 func (ep *Endpoint) UnmarshalJSON(j []byte) error {
-	var s string
-	err := json.Unmarshal(j, &s)
-	if err != nil {
-		return err
-	}
-
-	slashIdx := strings.IndexRune(s, '/')
-	if slashIdx < 0 {
-		ep.cep._type = C.uint(strconv.ParseUint(s, 10, 32))
-	} else if slashIdx == 0 || slashIdx >= (len(s)-1) {
-		return ErrInvalidParameter
-	} else {
-		ep.cep._type = C.uint(strconv.ParseUint(s[0:slashIdx], 10, 32))
-		s = s[slashIdx+1:]
-	}
-
-	switch ep.cep._type {
-	case EndpointTypeNil:
-		return nil
-	case EndpointTypeZeroTier:
-		fp, err := NewFingerprintFromString(s)
-		if err != nil {
-			return err
-		}
-		ep.cep.a.fp.address = C.uint64_t(fp.Address)
-		copy(((*[48]byte)(unsafe.Pointer(&ep.cep.a.fp.hash[0])))[:], fp.Hash[:])
-		return nil
-	}
-	return ErrInvalidParameter
+	// TODO
+	return nil
 }
 }

+ 13 - 1
go/pkg/zerotier/fingerprint.go

@@ -53,6 +53,18 @@ func NewFingerprintFromString(fps string) (*Fingerprint, error) {
 	return &Fingerprint{Address: a, Hash: nil}, nil
 	return &Fingerprint{Address: a, Hash: nil}, nil
 }
 }
 
 
+func newFingerprintFromCFingerprint(cfp *C.ZT_Fingerprint) *Fingerprint {
+	var fp Fingerprint
+	if uintptr(unsafe.Pointer(cfp)) != 0 {
+		fp.Address = Address(cfp.address)
+		fp.Hash = C.GoBytes(unsafe.Pointer(&cfp.hash[0]), 48)
+		if allZero(fp.Hash) {
+			fp.Hash = nil
+		}
+	}
+	return &fp
+}
+
 func (fp *Fingerprint) String() string {
 func (fp *Fingerprint) String() string {
 	if len(fp.Hash) == 48 {
 	if len(fp.Hash) == 48 {
 		return fmt.Sprintf("%.10x/%s", uint64(fp.Address), Base32StdLowerCase.EncodeToString(fp.Hash))
 		return fmt.Sprintf("%.10x/%s", uint64(fp.Address), Base32StdLowerCase.EncodeToString(fp.Hash))
@@ -64,7 +76,7 @@ func (fp *Fingerprint) Equals(fp2 *Fingerprint) bool {
 	return fp.Address == fp2.Address && bytes.Equal(fp.Hash[:], fp2.Hash[:])
 	return fp.Address == fp2.Address && bytes.Equal(fp.Hash[:], fp2.Hash[:])
 }
 }
 
 
-func (fp *Fingerprint) apiFingerprint() *C.ZT_Fingerprint {
+func (fp *Fingerprint) cFingerprint() *C.ZT_Fingerprint {
 	var apifp C.ZT_Fingerprint
 	var apifp C.ZT_Fingerprint
 	apifp.address = C.uint64_t(fp.Address)
 	apifp.address = C.uint64_t(fp.Address)
 	copy((*[48]byte)(unsafe.Pointer(&apifp.hash[0]))[:], fp.Hash[:])
 	copy((*[48]byte)(unsafe.Pointer(&apifp.hash[0]))[:], fp.Hash[:])

+ 7 - 25
go/pkg/zerotier/identity.go

@@ -20,7 +20,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/hex"
 	"encoding/hex"
 	"encoding/json"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"fmt"
 	"runtime"
 	"runtime"
 	"strings"
 	"strings"
@@ -92,7 +91,13 @@ func (id *Identity) initCIdentityPtr() bool {
 
 
 // NewIdentity generates a new identity of the selected type
 // NewIdentity generates a new identity of the selected type
 func NewIdentity(identityType int) (*Identity, error) {
 func NewIdentity(identityType int) (*Identity, error) {
-	return newIdentityFromCIdentity(C.ZT_Identity_new(C.enum_ZT_Identity_Type(identityType)))
+	switch identityType {
+	case C.ZT_IDENTITY_TYPE_C25519:
+		return newIdentityFromCIdentity(C.ZT_Identity_new(C.ZT_IDENTITY_TYPE_C25519))
+	case C.ZT_IDENTITY_TYPE_P384:
+		return newIdentityFromCIdentity(C.ZT_Identity_new(C.ZT_IDENTITY_TYPE_P384))
+	}
+	return nil, ErrInvalidParameter
 }
 }
 
 
 // NewIdentityFromString generates a new identity from its string representation.
 // NewIdentityFromString generates a new identity from its string representation.
@@ -231,29 +236,6 @@ func (id *Identity) Verify(msg, sig []byte) bool {
 	return C.ZT_Identity_verify(id.cid, dataP, C.uint(len(msg)), unsafe.Pointer(&sig[0]), C.uint(len(sig))) != 0
 	return C.ZT_Identity_verify(id.cid, dataP, C.uint(len(msg)), unsafe.Pointer(&sig[0]), C.uint(len(sig))) != 0
 }
 }
 
 
-// MakeRoot generates a root spec consisting of a serialized identity and a root locator.
-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")
-	}
-	if !id.initCIdentityPtr() {
-		return nil, errors.New("error initializing ZT_Identity")
-	}
-
-	ss := make([]C.struct_sockaddr_storage, len(addresses))
-	for i := range addresses {
-		if !makeSockaddrStorage(addresses[i].IP, addresses[i].Port, &ss[i]) {
-			return nil, errors.New("invalid address in address list")
-		}
-	}
-	var buf [8192]byte
-	rl := C.ZT_Identity_makeRootSpecification(id.cid, C.int64_t(TimeMs()), &ss[0], C.uint(len(ss)), unsafe.Pointer(&buf[0]), 8192)
-	if rl <= 0 {
-		return nil, errors.New("unable to make root specification (does identity contain a secret key?)")
-	}
-	return buf[0:int(rl)], nil
-}
-
 // Equals performs a deep equality test between this and another identity
 // Equals performs a deep equality test between this and another identity
 func (id *Identity) Equals(id2 *Identity) bool {
 func (id *Identity) Equals(id2 *Identity) bool {
 	if id2 == nil {
 	if id2 == nil {

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

@@ -0,0 +1,86 @@
+/*
+ * Copyright (c)2013-2020 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: 2024-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
+
+// #include "../../native/GoGlue.h"
+import "C"
+
+import (
+	"runtime"
+	"unsafe"
+)
+
+type Locator struct {
+	cl unsafe.Pointer
+}
+
+func locatorFinalizer(obj interface{}) {
+	if obj != nil {
+		C.ZT_Locator_delete(obj.(Locator).cl)
+	}
+}
+
+func NewLocator(ts int64, endpoints []Endpoint, signer *Identity) (*Locator, error) {
+	if ts <= 0 || len(endpoints) == 0 || signer == nil {
+		return nil, ErrInvalidParameter
+	}
+	eps := make([]C.ZT_Endpoint, 0, len(endpoints))
+	for _, e := range endpoints {
+		eps = append(eps, e.cep)
+	}
+	signer.initCIdentityPtr()
+	loc := C.ZT_Locator_create(C.int64_t(ts), &eps[0], C.uint(len(eps)), signer.cid)
+	if uintptr(loc) == 0 {
+		return nil, ErrInvalidParameter
+	}
+	goloc := new(Locator)
+	goloc.cl = unsafe.Pointer(loc)
+	runtime.SetFinalizer(goloc, locatorFinalizer)
+	return goloc, nil
+}
+
+func NewLocatorFromBytes(lb []byte) (*Locator, error) {
+	if len(lb) == 0 {
+		return nil, ErrInvalidParameter
+	}
+	loc := C.ZT_Locator_unmarshal(unsafe.Pointer(&lb[0]), C.uint(len(lb)))
+	if uintptr(loc) == 0 {
+		return nil, ErrInvalidParameter
+	}
+	goloc := new(Locator)
+	goloc.cl = unsafe.Pointer(loc)
+	runtime.SetFinalizer(goloc, locatorFinalizer)
+	return goloc, nil
+}
+
+// GetInfo gets information about this locator, also validating its signature if 'id' is non-nil.
+// If 'id' is nil the 'valid' return value is undefined.
+func (loc *Locator) GetInfo(id *Identity) (fp *Fingerprint, eps []Endpoint, valid bool, err error) {
+	cfp := C.ZT_Locator_fingerprint(loc.cl)
+	if uintptr(unsafe.Pointer(cfp)) == 0 {
+		err = ErrInternal
+		return
+	}
+	fp = newFingerprintFromCFingerprint(cfp)
+	epc := int(C.ZT_Locator_endpointCount(loc.cl))
+	eps = make([]Endpoint, epc)
+	for i:=0;i<epc;i++ {
+		eps[i].cep = *C.ZT_Locator_endpoint(loc.cl, C.uint(i))
+	}
+	if id != nil {
+		id.initCIdentityPtr()
+		valid = C.ZT_Locator_verify(loc.cl, id.cid) != 0
+	}
+	return
+}

+ 13 - 30
go/pkg/zerotier/node.go

@@ -410,7 +410,7 @@ func (n *Node) Join(nwid NetworkID, controllerFingerprint *Fingerprint, settings
 	}
 	}
 	var fp *C.ZT_Fingerprint
 	var fp *C.ZT_Fingerprint
 	if controllerFingerprint != nil {
 	if controllerFingerprint != nil {
-		fp = controllerFingerprint.apiFingerprint()
+		fp = controllerFingerprint.cFingerprint()
 	}
 	}
 	ntap := C.ZT_GoNode_join(n.gn, C.uint64_t(nwid), fp)
 	ntap := C.ZT_GoNode_join(n.gn, C.uint64_t(nwid), fp)
 	if ntap == nil {
 	if ntap == nil {
@@ -448,31 +448,13 @@ func (n *Node) Leave(nwid NetworkID) error {
 	return nil
 	return nil
 }
 }
 
 
-func (n *Node) AddRoot(spec []byte) (*Peer, error) {
-	if len(spec) == 0 {
-		return nil, ErrInvalidParameter
-	}
-	var address C.uint64_t
-	res := C.ZT_Node_addRoot(n.zn, nil, unsafe.Pointer(&spec[0]), C.uint(len(spec)), &address)
-	if res != 0 {
-		return nil, ErrInvalidParameter
-	}
-	peers := n.Peers()
-	for _, p := range peers {
-		if p.Address == Address(uint64(address)) {
-			return p, nil
-		}
-	}
-	return nil, ErrInternal
+func (n *Node) AddRoot(id *Identity, loc *Locator) (*Peer, error) {
+	// TODO
+	return nil, nil
 }
 }
 
 
 func (n *Node) RemoveRoot(address Address) {
 func (n *Node) RemoveRoot(address Address) {
-	var cfp C.ZT_Fingerprint
-	cfp.address = C.uint64_t(address)
-	for i := 0; i < 48; i++ {
-		cfp.hash[i] = 0
-	}
-	C.ZT_Node_removeRoot(n.zn, nil, &cfp)
+	C.ZT_Node_removeRoot(n.zn, nil, C.uint64_t(address))
 }
 }
 
 
 // GetNetwork looks up a network by ID or returns nil if not joined
 // GetNetwork looks up a network by ID or returns nil if not joined
@@ -512,15 +494,16 @@ func (n *Node) Peers() []*Peer {
 
 
 			p2.Paths = make([]Path, 0, int(p.pathCount))
 			p2.Paths = make([]Path, 0, int(p.pathCount))
 			for j := 0; j < len(p2.Paths); j++ {
 			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_Path)(unsafe.Pointer(uintptr(unsafe.Pointer(p.paths)) + uintptr(j*C.sizeof_ZT_Path)))
 				if pt.alive != 0 {
 				if pt.alive != 0 {
-					a := sockaddrStorageToUDPAddr(&pt.address)
+					ep := Endpoint{pt.endpoint}
+					a := ep.InetAddress()
 					if a != nil {
 					if a != nil {
 						p2.Paths = append(p2.Paths, Path{
 						p2.Paths = append(p2.Paths, Path{
-							IP:            a.IP,
-							Port:          a.Port,
-							LastSend:      int64(pt.lastSend),
-							LastReceive:   int64(pt.lastReceive),
+							IP:          a.IP,
+							Port:        a.Port,
+							LastSend:    int64(pt.lastSend),
+							LastReceive: int64(pt.lastReceive),
 						})
 						})
 					}
 					}
 				}
 				}
@@ -528,7 +511,7 @@ func (n *Node) Peers() []*Peer {
 
 
 			peers = append(peers, p2)
 			peers = append(peers, p2)
 		}
 		}
-		C.ZT_Node_freeQueryResult(n.zn, unsafe.Pointer(pl))
+		C.ZT_freeQueryResult(unsafe.Pointer(pl))
 	}
 	}
 	sort.Slice(peers, func(a, b int) bool {
 	sort.Slice(peers, func(a, b int) bool {
 		return peers[a].Address < peers[b].Address
 		return peers[a].Address < peers[b].Address

+ 42 - 4
include/ZeroTierCore.h

@@ -2132,7 +2132,11 @@ ZT_SDK_API void ZT_Identity_delete(ZT_Identity *id);
  * @param signer Identity to sign locator (must include private key)
  * @param signer Identity to sign locator (must include private key)
  * @return Locator or NULL on error (too many endpoints or identity does not have private key)
  * @return Locator or NULL on error (too many endpoints or identity does not have private key)
  */
  */
-ZT_SDK_API ZT_Locator *ZT_Locator_create(int64_t ts,const ZT_Endpoint *endpoints,unsigned int endpointCount,const ZT_Identity *signer);
+ZT_SDK_API ZT_Locator *ZT_Locator_create(
+	int64_t ts,
+	const ZT_Endpoint *endpoints,
+	unsigned int endpointCount,
+	const ZT_Identity *signer);
 
 
 /**
 /**
  * Decode a serialized locator
  * Decode a serialized locator
@@ -2141,7 +2145,29 @@ ZT_SDK_API ZT_Locator *ZT_Locator_create(int64_t ts,const ZT_Endpoint *endpoints
  * @param len Length of data
  * @param len Length of data
  * @return Locator or NULL if data is not valid
  * @return Locator or NULL if data is not valid
  */
  */
-ZT_SDK_API ZT_Locator *ZT_Locator_unmarshal(const void *data,unsigned int len);
+ZT_SDK_API ZT_Locator *ZT_Locator_unmarshal(
+	const void *data,
+	unsigned int len);
+
+/**
+ * Serialize this locator into a buffer
+ *
+ * @param loc Locator to serialize
+ * @param buf Buffer to store bytes
+ * @param bufSize Size of buffer in bytes (needs to be at least 2048 bytes in size)
+ * @return Number of bytes stored to buf or -1 on error such as buffer too small
+ */
+ZT_SDK_API int ZT_Locator_marshal(const ZT_Locator *loc,void *buf,unsigned int bufSize);
+
+/**
+ * Get a pointer to the fingerprint of this locator's signer.
+ *
+ * The returned pointer remains valid as long as the Locator is not deleted.
+ *
+ * @param loc Locator to query
+ * @return Pointer to fingerprint of signer
+ */
+ZT_SDK_API const ZT_Fingerprint *ZT_Locator_fingerprint(const ZT_Locator *loc);
 
 
 /**
 /**
  * Get the number of endpoints in this locator
  * Get the number of endpoints in this locator
@@ -2159,7 +2185,15 @@ ZT_SDK_API unsigned int ZT_Locator_endpointCount(const ZT_Locator *loc);
  * @param ep Endpoint number from 0 to 1 - endpointCount()
  * @param ep Endpoint number from 0 to 1 - endpointCount()
  * @return Endpoint or NULL if out of bounds
  * @return Endpoint or NULL if out of bounds
  */
  */
-ZT_SDK_API const ZT_Endpoint *ZT_Locator_endpoint(const unsigned int ep);
+ZT_SDK_API const ZT_Endpoint *ZT_Locator_endpoint(const ZT_Locator *loc,const unsigned int ep);
+
+/**
+ * Verify this locator's signature
+ *
+ * @param signer Signing identity
+ * @return Non-zero if locator is valid
+ */
+ZT_SDK_API int ZT_Locator_verify(const ZT_Locator *loc,const ZT_Identity *signer);
 
 
 /**
 /**
  * Delete a locator
  * Delete a locator
@@ -2178,7 +2212,11 @@ ZT_SDK_API void ZT_Locator_delete(ZT_Locator *loc);
  * @param revision Result: revision
  * @param revision Result: revision
  * @param build Result: build number
  * @param build Result: build number
  */
  */
-ZT_SDK_API void ZT_version(int *major,int *minor,int *revision,int *build);
+ZT_SDK_API void ZT_version(
+	int *major,
+	int *minor,
+	int *revision,
+	int *build);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

+ 4 - 1
node/Containers.hpp

@@ -157,8 +157,11 @@ class String : public std::basic_string< char,std::char_traits<char>,Utils::Mall
 {
 {
 public:
 public:
 	ZT_INLINE String() {}
 	ZT_INLINE String() {}
-	explicit ZT_INLINE String(const char *const s) { assign(s); }
+	ZT_INLINE String(const String &s) : std::basic_string< char,std::char_traits<char>,Utils::Mallocator<char> >(s.c_str()) {}
+	ZT_INLINE String(const std::string &s) : std::basic_string< char,std::char_traits<char>,Utils::Mallocator<char> >(s.c_str()) {}
+	ZT_INLINE String(const char *const s) : std::basic_string< char,std::char_traits<char>,Utils::Mallocator<char> >(s) {}
 	ZT_INLINE String &operator=(const char *const s) { assign(s); return *this; }
 	ZT_INLINE String &operator=(const char *const s) { assign(s); return *this; }
+	ZT_INLINE String &operator=(const std::string &s) { assign(s.c_str()); return *this; }
 };
 };
 
 
 } // ZeroTier
 } // ZeroTier

+ 1 - 0
node/Endpoint.cpp

@@ -147,6 +147,7 @@ int Endpoint::unmarshal(const uint8_t *restrict data, int len) noexcept
 	// to the unmarshal method of InetAddress and considered UDP endpoints.
 	// to the unmarshal method of InetAddress and considered UDP endpoints.
 	// This allows backward compatibility with old endpoint fields in the
 	// This allows backward compatibility with old endpoint fields in the
 	// protocol that were serialized InetAddress instances.
 	// protocol that were serialized InetAddress instances.
+
 	if (data[0] < 16) {
 	if (data[0] < 16) {
 		switch (data[0]) {
 		switch (data[0]) {
 			case 0:
 			case 0:

+ 20 - 0
node/Fingerprint.hpp

@@ -20,6 +20,7 @@
 #include "Utils.hpp"
 #include "Utils.hpp"
 
 
 #define ZT_FINGERPRINT_STRING_SIZE_MAX 128
 #define ZT_FINGERPRINT_STRING_SIZE_MAX 128
+#define ZT_FINGERPRINT_MARSHAL_SIZE 53
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
@@ -93,6 +94,25 @@ public:
 	ZT_INLINE operator bool() const noexcept
 	ZT_INLINE operator bool() const noexcept
 	{ return this->address != 0; }
 	{ return this->address != 0; }
 
 
+	static constexpr int marshalSizeMax() noexcept
+	{ return ZT_FINGERPRINT_MARSHAL_SIZE; }
+
+	ZT_INLINE int marshal(uint8_t data[ZT_FINGERPRINT_MARSHAL_SIZE]) const noexcept
+	{
+		Address(this->address).copyTo(data);
+		Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(data + ZT_ADDRESS_LENGTH,this->hash);
+		return ZT_FINGERPRINT_MARSHAL_SIZE;
+	}
+
+	ZT_INLINE int unmarshal(const uint8_t *const data, int len) noexcept
+	{
+		if (unlikely(len < ZT_FINGERPRINT_MARSHAL_SIZE))
+			return -1;
+		this->address = Address(data);
+		Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(hash,data + ZT_ADDRESS_LENGTH);
+		return ZT_FINGERPRINT_MARSHAL_SIZE;
+	}
+
 	ZT_INLINE bool operator==(const Fingerprint &h) const noexcept
 	ZT_INLINE bool operator==(const Fingerprint &h) const noexcept
 	{ return ((this->address == h.address) && (memcmp(this->hash, h.hash, ZT_FINGERPRINT_HASH_SIZE) == 0)); }
 	{ return ((this->address == h.address) && (memcmp(this->hash, h.hash, ZT_FINGERPRINT_HASH_SIZE) == 0)); }
 
 

+ 124 - 13
node/Locator.cpp

@@ -12,6 +12,7 @@
 /****/
 /****/
 
 
 #include "Locator.hpp"
 #include "Locator.hpp"
+#include "Identity.hpp"
 
 
 #include <algorithm>
 #include <algorithm>
 
 
@@ -30,6 +31,7 @@ bool Locator::add(const Endpoint &ep)
 bool Locator::sign(const int64_t ts, const Identity &id) noexcept
 bool Locator::sign(const int64_t ts, const Identity &id) noexcept
 {
 {
 	m_ts = ts;
 	m_ts = ts;
+	m_signer = id.fingerprint();
 	std::sort(m_endpoints.begin(), m_endpoints.end());
 	std::sort(m_endpoints.begin(), m_endpoints.end());
 
 
 	uint8_t signdata[ZT_LOCATOR_MARSHAL_SIZE_MAX];
 	uint8_t signdata[ZT_LOCATOR_MARSHAL_SIZE_MAX];
@@ -45,67 +47,176 @@ bool Locator::sign(const int64_t ts, const Identity &id) noexcept
 
 
 bool Locator::verify(const Identity &id) const noexcept
 bool Locator::verify(const Identity &id) const noexcept
 {
 {
-	if (m_ts <= 0)
-		return false;
-	uint8_t signdata[ZT_LOCATOR_MARSHAL_SIZE_MAX];
-	const unsigned int signlen = marshal(signdata, true);
-	return id.verify(signdata, signlen, m_signature.data(), m_signature.size());
+	try {
+		if ((m_ts > 0) && (m_signer == id.fingerprint())) {
+			uint8_t signdata[ZT_LOCATOR_MARSHAL_SIZE_MAX];
+			const unsigned int signlen = marshal(signdata, true);
+			return id.verify(signdata, signlen, m_signature.data(), m_signature.size());
+		}
+	} catch ( ... ) {} // fail verify on any unexpected exception
+	return false;
 }
 }
 
 
 int Locator::marshal(uint8_t data[ZT_LOCATOR_MARSHAL_SIZE_MAX], const bool excludeSignature) const noexcept
 int Locator::marshal(uint8_t data[ZT_LOCATOR_MARSHAL_SIZE_MAX], const bool excludeSignature) const noexcept
 {
 {
 	Utils::storeBigEndian<uint64_t>(data, (uint64_t) m_ts);
 	Utils::storeBigEndian<uint64_t>(data, (uint64_t) m_ts);
-	Utils::storeBigEndian<uint16_t>(data + 8, (uint16_t) m_endpoints.size());
-	int p = 10;
+	int p = 8;
+
+	int l = m_signer.marshal(data + p);
+	if (l <= 0)
+		return -1;
+	p += l;
+
+	Utils::storeBigEndian<uint16_t>(data + p, (uint16_t) m_endpoints.size());
+	p += 2;
 	for (Vector<Endpoint>::const_iterator e(m_endpoints.begin());e != m_endpoints.end();++e) {
 	for (Vector<Endpoint>::const_iterator e(m_endpoints.begin());e != m_endpoints.end();++e) {
-		int l = e->marshal(data + p);
+		l = e->marshal(data + p);
 		if (l <= 0)
 		if (l <= 0)
 			return -1;
 			return -1;
 		p += l;
 		p += l;
 	}
 	}
+
 	Utils::storeAsIsEndian<uint16_t>(data + p, 0); // length of meta-data, currently always 0
 	Utils::storeAsIsEndian<uint16_t>(data + p, 0); // length of meta-data, currently always 0
 	p += 2;
 	p += 2;
+
 	if (!excludeSignature) {
 	if (!excludeSignature) {
 		Utils::storeBigEndian<uint16_t>(data + p, (uint16_t) m_signature.size());
 		Utils::storeBigEndian<uint16_t>(data + p, (uint16_t) m_signature.size());
 		p += 2;
 		p += 2;
 		Utils::copy(data + p, m_signature.data(), m_signature.size());
 		Utils::copy(data + p, m_signature.data(), m_signature.size());
 		p += (int) m_signature.size();
 		p += (int) m_signature.size();
 	}
 	}
+
 	return p;
 	return p;
 }
 }
 
 
 int Locator::unmarshal(const uint8_t *data, const int len) noexcept
 int Locator::unmarshal(const uint8_t *data, const int len) noexcept
 {
 {
-	if (unlikely(len < 10))
+	if (unlikely(len < 8))
 		return -1;
 		return -1;
 	m_ts = (int64_t) Utils::loadBigEndian<uint64_t>(data);
 	m_ts = (int64_t) Utils::loadBigEndian<uint64_t>(data);
+	int p = 8;
+
+	int l = m_signer.unmarshal(data + p,len - p);
+	if (l <= 0)
+		return -1;
+	p += l;
+
+	if (unlikely(p + 2) > len)
+		return -1;
 	unsigned int endpointCount = Utils::loadBigEndian<uint16_t>(data + 8);
 	unsigned int endpointCount = Utils::loadBigEndian<uint16_t>(data + 8);
 	if (unlikely(endpointCount > ZT_LOCATOR_MAX_ENDPOINTS))
 	if (unlikely(endpointCount > ZT_LOCATOR_MAX_ENDPOINTS))
 		return -1;
 		return -1;
-	int p = 10;
 	m_endpoints.resize(endpointCount);
 	m_endpoints.resize(endpointCount);
 	m_endpoints.shrink_to_fit();
 	m_endpoints.shrink_to_fit();
 	for (unsigned int i = 0;i < endpointCount;++i) {
 	for (unsigned int i = 0;i < endpointCount;++i) {
-		int l = m_endpoints[i].unmarshal(data + p, len - p);
+		l = m_endpoints[i].unmarshal(data + p, len - p);
 		if (l <= 0)
 		if (l <= 0)
 			return -1;
 			return -1;
 		p += l;
 		p += l;
 	}
 	}
+
 	if (unlikely((p + 2) > len))
 	if (unlikely((p + 2) > len))
 		return -1;
 		return -1;
 	p += 2 + (int) Utils::loadBigEndian<uint16_t>(data + p);
 	p += 2 + (int) Utils::loadBigEndian<uint16_t>(data + p);
+
 	if (unlikely((p + 2) > len))
 	if (unlikely((p + 2) > len))
 		return -1;
 		return -1;
-	unsigned int siglen = Utils::loadBigEndian<uint16_t>(data + p);
+	const unsigned int siglen = Utils::loadBigEndian<uint16_t>(data + p);
 	p += 2;
 	p += 2;
-	if (unlikely((siglen > ZT_SIGNATURE_BUFFER_SIZE) || ((p + (int) siglen) > len)))
+	if (unlikely((siglen > ZT_SIGNATURE_BUFFER_SIZE) || ((p + (int)siglen) > len)))
 		return -1;
 		return -1;
 	m_signature.unsafeSetSize(siglen);
 	m_signature.unsafeSetSize(siglen);
 	Utils::copy(m_signature.data(), data + p, siglen);
 	Utils::copy(m_signature.data(), data + p, siglen);
 	p += siglen;
 	p += siglen;
 	if (unlikely(p > len))
 	if (unlikely(p > len))
 		return -1;
 		return -1;
+
 	return p;
 	return p;
 }
 }
 
 
 } // namespace ZeroTier
 } // namespace ZeroTier
+
+extern "C" {
+
+ZT_Locator *ZT_Locator_create(
+	int64_t ts,
+	const ZT_Endpoint *endpoints,
+	unsigned int endpointCount,
+	const ZT_Identity *signer)
+{
+	try {
+		if ((ts <= 0) || (!endpoints) || (endpointCount == 0) || (!signer))
+			return nullptr;
+		ZeroTier::Locator *loc = new ZeroTier::Locator();
+		for(unsigned int i=0;i<endpointCount;++i)
+			loc->add(reinterpret_cast<const ZeroTier::Endpoint *>(endpoints)[i]);
+		if (!loc->sign(ts,*reinterpret_cast<const ZeroTier::Identity *>(signer))) {
+			delete loc;
+			return nullptr;
+		}
+		return reinterpret_cast<ZT_Locator *>(loc);
+	} catch ( ... ) {
+		return nullptr;
+	}
+}
+
+ZT_Locator *ZT_Locator_unmarshal(
+	const void *data,
+	unsigned int len)
+{
+	try {
+		if ((!data) || (len == 0))
+			return nullptr;
+		ZeroTier::Locator *loc = new ZeroTier::Locator();
+		if (loc->unmarshal(reinterpret_cast<const uint8_t *>(data),(int)len) <= 0) {
+			delete loc;
+			return nullptr;
+		}
+		return reinterpret_cast<ZT_Locator *>(loc);
+	} catch ( ... ) {
+		return nullptr;
+	}
+}
+
+int ZT_Locator_marshal(const ZT_Locator *loc,void *buf,unsigned int bufSize)
+{
+	if ((!loc) || (bufSize < ZT_LOCATOR_MARSHAL_SIZE_MAX))
+		return -1;
+	return reinterpret_cast<const ZeroTier::Locator *>(loc)->marshal(reinterpret_cast<uint8_t *>(buf),(int)bufSize);
+}
+
+const ZT_Fingerprint *ZT_Locator_fingerprint(const ZT_Locator *loc)
+{
+	if (!loc)
+		return nullptr;
+	return (ZT_Fingerprint *)(&(reinterpret_cast<const ZeroTier::Locator *>(loc)->signer()));
+}
+
+unsigned int ZT_Locator_endpointCount(const ZT_Locator *loc)
+{
+	return (loc) ? (unsigned int)(reinterpret_cast<const ZeroTier::Locator *>(loc)->endpoints().size()) : 0;
+}
+
+const ZT_Endpoint *ZT_Locator_endpoint(const ZT_Locator *loc,const unsigned int ep)
+{
+	if (!loc)
+		return nullptr;
+	if (ep >= (unsigned int)(reinterpret_cast<const ZeroTier::Locator *>(loc)->endpoints().size()))
+		return nullptr;
+	return reinterpret_cast<const ZT_Endpoint *>(&(reinterpret_cast<const ZeroTier::Locator *>(loc)->endpoints()[ep]));
+}
+
+int ZT_Locator_verify(const ZT_Locator *loc,const ZT_Identity *signer)
+{
+	if ((!loc) || (!signer))
+		return 0;
+	return reinterpret_cast<const ZeroTier::Locator *>(loc)->verify(*reinterpret_cast<const ZeroTier::Identity *>(signer)) ? 1 : 0;
+}
+
+void ZT_Locator_delete(ZT_Locator *loc)
+{
+	if (loc)
+		delete reinterpret_cast<ZeroTier::Locator *>(loc);
+}
+
+} // C API functions

+ 9 - 1
node/Locator.hpp

@@ -23,7 +23,7 @@
 #include "Containers.hpp"
 #include "Containers.hpp"
 
 
 #define ZT_LOCATOR_MAX_ENDPOINTS 8
 #define ZT_LOCATOR_MAX_ENDPOINTS 8
-#define ZT_LOCATOR_MARSHAL_SIZE_MAX (8 + 2 + (ZT_LOCATOR_MAX_ENDPOINTS * ZT_ENDPOINT_MARSHAL_SIZE_MAX) + 2 + 2 + ZT_SIGNATURE_BUFFER_SIZE)
+#define ZT_LOCATOR_MARSHAL_SIZE_MAX (8 + ZT_FINGERPRINT_MARSHAL_SIZE + 2 + (ZT_LOCATOR_MAX_ENDPOINTS * ZT_ENDPOINT_MARSHAL_SIZE_MAX) + 2 + 2 + ZT_SIGNATURE_BUFFER_SIZE)
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
@@ -45,6 +45,7 @@ public:
 
 
 	ZT_INLINE Locator(const Locator &loc) noexcept :
 	ZT_INLINE Locator(const Locator &loc) noexcept :
 		m_ts(loc.m_ts),
 		m_ts(loc.m_ts),
+		m_signer(loc.m_signer),
 		m_endpoints(loc.m_endpoints),
 		m_endpoints(loc.m_endpoints),
 		m_signature(loc.m_signature),
 		m_signature(loc.m_signature),
 		__refCount(0)
 		__refCount(0)
@@ -56,6 +57,12 @@ public:
 	ZT_INLINE int64_t timestamp() const noexcept
 	ZT_INLINE int64_t timestamp() const noexcept
 	{ return m_ts; }
 	{ return m_ts; }
 
 
+	/**
+	 * @return Fingerprint of identity that signed this locator
+	 */
+	ZT_INLINE const Fingerprint &signer() const noexcept
+	{ return m_signer; }
+
 	/**
 	/**
 	 * @return Endpoints specified in locator
 	 * @return Endpoints specified in locator
 	 */
 	 */
@@ -110,6 +117,7 @@ public:
 
 
 private:
 private:
 	int64_t m_ts;
 	int64_t m_ts;
+	Fingerprint m_signer;
 	Vector<Endpoint> m_endpoints;
 	Vector<Endpoint> m_endpoints;
 	FCV<uint8_t, ZT_SIGNATURE_BUFFER_SIZE> m_signature;
 	FCV<uint8_t, ZT_SIGNATURE_BUFFER_SIZE> m_signature;
 	std::atomic<int> __refCount;
 	std::atomic<int> __refCount;