From 9babfcb9b63fd2a29605cb1211bdc20f1a272bfa Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 4 Jun 2020 16:03:11 -0700 Subject: [PATCH] A bunch more go plumbing. --- go/cmd/zerotier/cli/misc.go | 10 +-- go/pkg/zerotier/address.go | 5 +- go/pkg/zerotier/api.go | 2 +- go/pkg/zerotier/fingerprint.go | 34 +++++++-- go/pkg/zerotier/identity.go | 9 ++- go/pkg/zerotier/network.go | 2 +- go/pkg/zerotier/node.go | 122 +++++++++++++++++++++++---------- 7 files changed, 128 insertions(+), 56 deletions(-) diff --git a/go/cmd/zerotier/cli/misc.go b/go/cmd/zerotier/cli/misc.go index d1b4621f4..c9c62e933 100644 --- a/go/cmd/zerotier/cli/misc.go +++ b/go/cmd/zerotier/cli/misc.go @@ -193,13 +193,13 @@ func readLocator(s string) *zerotier.Locator { func networkStatusStr(status int) string { switch status { case zerotier.NetworkStatusNotFound: - return "NOTFOUND" + return "not-found" case zerotier.NetworkStatusAccessDenied: - return "ACCESSDENIED" - case zerotier.NetworkStatusRequestConfiguration: - return "UPDATING" + return "access-denied" + case zerotier.NetworkStatusRequestingConfiguration: + return "updating" case zerotier.NetworkStatusOK: - return "OK" + return "ok" } return "???" } diff --git a/go/pkg/zerotier/address.go b/go/pkg/zerotier/address.go index e3fe60f2a..ace1d7291 100644 --- a/go/pkg/zerotier/address.go +++ b/go/pkg/zerotier/address.go @@ -39,7 +39,8 @@ func NewAddressFromBytes(b []byte) (Address, error) { return Address((uint64(b[0]) << 32) | (uint64(b[1]) << 24) | (uint64(b[2]) << 16) | (uint64(b[3]) << 8) | uint64(b[4])), nil } -// Copy this address to a byte array, which must be 5 bytes in length or this will panic. +// CopyTo writes this address to five bytes. +// If b cannot store five bytes this will panic. func (a Address) CopyTo(b []byte) { _ = b[4] b[0] = byte(a >> 32) @@ -52,7 +53,7 @@ func (a Address) CopyTo(b []byte) { // IsReserved returns true if this address is reserved and therefore is not valid for a real node. func (a Address) IsReserved() bool { return a == 0 || (a>>32) == 0xff } -// String returns this address's 10-digit hex identifier +// String returns this address's 10-digit hex identifier. func (a Address) String() string { return fmt.Sprintf("%.10x", uint64(a)) } diff --git a/go/pkg/zerotier/api.go b/go/pkg/zerotier/api.go index 13c2db138..56ff3aa75 100644 --- a/go/pkg/zerotier/api.go +++ b/go/pkg/zerotier/api.go @@ -473,7 +473,7 @@ func createAPIServer(basePath string, node *Node) (*http.Server, *http.Server, e } var nw APINetwork if apiReadObj(out, req, &nw) == nil { - n := node.GetNetwork(nw.ID) + n := node.Network(nw.ID) if n == nil { if nw.ControllerFingerprint != nil && nw.ControllerFingerprint.Address != nw.ID.Controller() { _ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"fingerprint's address does not match what should be the controller's address"}) diff --git a/go/pkg/zerotier/fingerprint.go b/go/pkg/zerotier/fingerprint.go index aede388bb..d7fdc3ef2 100644 --- a/go/pkg/zerotier/fingerprint.go +++ b/go/pkg/zerotier/fingerprint.go @@ -24,11 +24,20 @@ import ( "unsafe" ) +// FingerprintHashSize is the length of a fingerprint hash in bytes. +const FingerprintHashSize = 48 + +// Fingerprint bundles an address with an optional SHA384 full hash of the identity's key(s). type Fingerprint struct { Address Address Hash []byte } +// NewFingerprintFromString decodes a string-format fingerprint. +// A fingerprint has the format address-hash, where address is a 10-digit +// ZeroTier address and a hash is a base32-encoded SHA384 hash. Fingerprints +// can be missing the hash in which case they are represented the same as +// an Address and the hash field will be nil. func NewFingerprintFromString(fps string) (*Fingerprint, error) { if len(fps) < AddressStringLength { return nil, ErrInvalidZeroTierAddress @@ -66,22 +75,28 @@ func newFingerprintFromCFingerprint(cfp *C.ZT_Fingerprint) *Fingerprint { return &fp } +// String returns an address or a full address-hash depenting on whether a hash is present. func (fp *Fingerprint) String() string { - if len(fp.Hash) == 48 { + if len(fp.Hash) == FingerprintHashSize { return fmt.Sprintf("%.10x-%s", uint64(fp.Address), Base32StdLowerCase.EncodeToString(fp.Hash)) } return fp.Address.String() } +// Equals test for full equality with another fingerprint (including hash). func (fp *Fingerprint) Equals(fp2 *Fingerprint) bool { return fp.Address == fp2.Address && bytes.Equal(fp.Hash[:], fp2.Hash[:]) } -func (fp *Fingerprint) cFingerprint() *C.ZT_Fingerprint { - var apifp C.ZT_Fingerprint - apifp.address = C.uint64_t(fp.Address) - copy((*[48]byte)(unsafe.Pointer(&apifp.hash[0]))[:], fp.Hash[:]) - return &apifp +// BestSpecificityEquals compares either just the addresses or also the hashes if both are present. +func (fp *Fingerprint) BestSpecificityEquals(fp2 *Fingerprint) bool { + if fp2 == nil || fp.Address != fp2.Address { + return false + } + if len(fp.Hash) == FingerprintHashSize && len(fp2.Hash) == FingerprintHashSize { + return bytes.Equal(fp.Hash, fp2.Hash) + } + return true } func (fp *Fingerprint) MarshalJSON() ([]byte, error) { @@ -99,3 +114,10 @@ func (fp *Fingerprint) UnmarshalJSON(j []byte) error { fp.Hash = fp2.Hash return err } + +func (fp *Fingerprint) cFingerprint() *C.ZT_Fingerprint { + var apifp C.ZT_Fingerprint + apifp.address = C.uint64_t(fp.Address) + copy((*[48]byte)(unsafe.Pointer(&apifp.hash[0]))[:], fp.Hash[:]) + return &apifp +} diff --git a/go/pkg/zerotier/identity.go b/go/pkg/zerotier/identity.go index 7af71507c..07957f325 100644 --- a/go/pkg/zerotier/identity.go +++ b/go/pkg/zerotier/identity.go @@ -89,7 +89,7 @@ func (id *Identity) initCIdentityPtr() bool { return true } -// NewIdentity generates a new identity of the selected type +// NewIdentity generates a new identity of the selected type. func NewIdentity(identityType int) (*Identity, error) { switch identityType { case C.ZT_IDENTITY_TYPE_C25519: @@ -160,7 +160,7 @@ func NewIdentityFromString(s string) (*Identity, error) { return id, nil } -// Address returns this identity's address +// Address returns this identity's address. func (id *Identity) Address() Address { return id.address } // HasPrivate returns true if this identity has its own private portion. @@ -172,7 +172,8 @@ func (id *Identity) Fingerprint() *Fingerprint { return newFingerprintFromCFingerprint(C.ZT_Identity_fingerprint(id.cid)) } -// PrivateKeyString returns the full identity.secret if the private key is set, or an empty string if no private key is set. +// PrivateKeyString returns the full identity.secret if the private key is set, +// or an empty string if no private key is set. func (id *Identity) PrivateKeyString() string { switch id.idtype { case IdentityTypeC25519: @@ -253,12 +254,10 @@ func (id *Identity) Equals(id2 *Identity) bool { return id.address == id2.address && id.idtype == id2.idtype && bytes.Equal(id.publicKey, id2.publicKey) && bytes.Equal(id.privateKey, id2.privateKey) } -// MarshalJSON marshals this Identity in its string format (private key is never included) func (id *Identity) MarshalJSON() ([]byte, error) { return []byte("\"" + id.String() + "\""), nil } -// UnmarshalJSON unmarshals this Identity from a string func (id *Identity) UnmarshalJSON(j []byte) error { var s string err := json.Unmarshal(j, &s) diff --git a/go/pkg/zerotier/network.go b/go/pkg/zerotier/network.go index 42f490969..18c945fe3 100644 --- a/go/pkg/zerotier/network.go +++ b/go/pkg/zerotier/network.go @@ -155,7 +155,7 @@ func newNetwork(node *Node, id NetworkID, t Tap) (*Network, error) { config: NetworkConfig{ ID: id, MAC: m, - Status: NetworkStatusRequestConfiguration, + Status: NetworkStatusRequestingConfiguration, Type: NetworkTypePrivate, MTU: int(defaultVirtualNetworkMTU), }, diff --git a/go/pkg/zerotier/node.go b/go/pkg/zerotier/node.go index 9b5fcb694..c799af50d 100644 --- a/go/pkg/zerotier/node.go +++ b/go/pkg/zerotier/node.go @@ -47,12 +47,14 @@ var nullLogger = log.New(ioutil.Discard, "", 0) const ( NetworkIDStringLength = 16 + NEtworkIDLength = 8 AddressStringLength = 10 + AddressLength = 5 - NetworkStatusRequestConfiguration int = C.ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION - NetworkStatusOK int = C.ZT_NETWORK_STATUS_OK - NetworkStatusAccessDenied int = C.ZT_NETWORK_STATUS_ACCESS_DENIED - NetworkStatusNotFound int = C.ZT_NETWORK_STATUS_NOT_FOUND + NetworkStatusRequestingConfiguration int = C.ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION + NetworkStatusOK int = C.ZT_NETWORK_STATUS_OK + NetworkStatusAccessDenied int = C.ZT_NETWORK_STATUS_ACCESS_DENIED + NetworkStatusNotFound int = C.ZT_NETWORK_STATUS_NOT_FOUND NetworkTypePrivate int = C.ZT_NETWORK_TYPE_PRIVATE NetworkTypePublic int = C.ZT_NETWORK_TYPE_PUBLIC @@ -62,7 +64,8 @@ const ( defaultVirtualNetworkMTU = C.ZT_DEFAULT_MTU - // maxCNodeRefs is the maximum number of Node instances that can be created in this process (increasing is fine) + // maxCNodeRefs is the maximum number of Node instances that can be created in this process. + // This is perfectly fine to increase. maxCNodeRefs = 8 ) @@ -73,7 +76,10 @@ var ( CoreVersionRevision int CoreVersionBuild int - cNodeRefs [maxCNodeRefs]*Node + // cNodeRefs maps an index to a *Node + cNodeRefs [maxCNodeRefs]*Node + + // cNodeRefsUsed maps an index to whether or not the corresponding cNodeRefs[] entry is used. cNodeRefUsed [maxCNodeRefs]uint32 ) @@ -92,7 +98,9 @@ type Node struct { // Time this node was created startupTime int64 - // an arbitrary uintptr given to the core as its pointer back to Go's Node instance + // an arbitrary uintptr given to the core as its pointer back to Go's Node instance. + // This is an index in the cNodeRefs array, which is synchronized by way of a set of + // used/free booleans accessed atomically. cPtr uintptr // networks contains networks we have joined, and networksByMAC by their local MAC address @@ -100,13 +108,14 @@ type Node struct { networksByMAC map[MAC]*Network // locked by networksLock networksLock sync.RWMutex - // interfaceAddresses are physical IPs assigned to the local machine (detected, not configured) + // interfaceAddresses are physical IPs assigned to the local machine. + // These are the detected IPs, not those configured explicitly. They include + // both private and global IPs. interfaceAddresses map[string]net.IP interfaceAddressesLock sync.Mutex - // online and running are atomic flags set to control and monitor background tasks - online uint32 running uint32 + online uint32 basePath string peersPath string @@ -115,20 +124,20 @@ type Node struct { infoLogPath string errorLogPath string - // localConfig is the current state of local.conf + // localConfig is the current state of local.conf. localConfig *LocalConfig previousLocalConfig *LocalConfig localConfigLock sync.RWMutex - // logs for information, errors, and trace output infoLogW *sizeLimitWriter errLogW *sizeLimitWriter traceLogW io.Writer - infoLog *log.Logger - errLog *log.Logger - traceLog *log.Logger - // gn is the GoNode instance + infoLog *log.Logger + errLog *log.Logger + traceLog *log.Logger + + // gn is the GoNode instance, see go/native/GoNode.hpp gn *C.ZT_GoNode // zn is the underlying ZT_Node (ZeroTier::Node) instance @@ -137,11 +146,12 @@ type Node struct { // id is the identity of this node (includes private key) id *Identity - // HTTP server instances: one for a named socket (Unix domain or Windows named pipe) and one on a local TCP socket - namedSocketApiServer *http.Server - tcpApiServer *http.Server + namedSocketAPIServer *http.Server + tcpAPIServer *http.Server - // runWaitGroup is used to wait for all node goroutines on shutdown + // runWaitGroup is used to wait for all node goroutines on shutdown. + // Any new goroutine is tracked via this wait group so node shutdown can + // itself wait until all goroutines have exited. runWaitGroup sync.WaitGroup } @@ -163,8 +173,11 @@ func NewNode(basePath string) (n *Node, err error) { } } if cPtr < 0 { - return nil, errors.New("too many nodes in this instance") + return nil, ErrInternal } + + // Check and delete node reference pointer if it's non-negative. This helps + // with error handling cleanup. At the end we set cPtr to -1 to disable. defer func() { if cPtr >= 0 { atomic.StoreUint32(&cNodeRefUsed[cPtr], 0) @@ -175,7 +188,7 @@ func NewNode(basePath string) (n *Node, err error) { n.networks = make(map[NetworkID]*Network) n.networksByMAC = make(map[MAC]*Network) n.interfaceAddresses = make(map[string]net.IP) - n.online = 0 + n.running = 1 _ = os.MkdirAll(basePath, 0755) @@ -265,7 +278,7 @@ func NewNode(basePath string) (n *Node, err error) { _ = n.localConfig.Write(n.localConfigPath) } - n.namedSocketApiServer, n.tcpApiServer, err = createAPIServer(basePath, n) + n.namedSocketAPIServer, n.tcpAPIServer, err = createAPIServer(basePath, n) if err != nil { n.infoLog.Printf("FATAL: unable to start API server: %s", err.Error()) return nil, err @@ -293,12 +306,13 @@ func NewNode(basePath string) (n *Node, err error) { defer n.runWaitGroup.Done() lastMaintenanceRun := int64(0) for atomic.LoadUint32(&n.running) != 0 { - time.Sleep(500 * time.Millisecond) + time.Sleep(250 * time.Millisecond) nowS := time.Now().Unix() if (nowS - lastMaintenanceRun) >= 30 { lastMaintenanceRun = nowS n.runMaintenance() } + time.Sleep(250 * time.Millisecond) } }() @@ -311,11 +325,11 @@ func NewNode(basePath string) (n *Node, err error) { // Close closes this Node and frees its underlying C++ Node structures func (n *Node) Close() { if atomic.SwapUint32(&n.running, 0) != 0 { - if n.namedSocketApiServer != nil { - _ = n.namedSocketApiServer.Close() + if n.namedSocketAPIServer != nil { + _ = n.namedSocketAPIServer.Close() } - if n.tcpApiServer != nil { - _ = n.tcpApiServer.Close() + if n.tcpAPIServer != nil { + _ = n.tcpAPIServer.Close() } C.ZT_GoNode_delete(n.gn) @@ -369,9 +383,7 @@ func (n *Node) SetLocalConfig(lc *LocalConfig) (restartRequired bool, err error) } } - if n.localConfig.Settings.PrimaryPort != lc.Settings.PrimaryPort || - n.localConfig.Settings.SecondaryPort != lc.Settings.SecondaryPort || - n.localConfig.Settings.LogSizeMax != lc.Settings.LogSizeMax { + if n.localConfig.Settings.PrimaryPort != lc.Settings.PrimaryPort || n.localConfig.Settings.SecondaryPort != lc.Settings.SecondaryPort || n.localConfig.Settings.LogSizeMax != lc.Settings.LogSizeMax { restartRequired = true } @@ -437,17 +449,29 @@ func (n *Node) Leave(nwid NetworkID) error { return nil } -func (n *Node) AddRoot(id *Identity, loc *Locator) (*Peer, error) { - // TODO - return nil, nil +// AddRoot designates a peer as root, adding it if missing. +func (n *Node) AddRoot(id *Identity) (*Peer, error) { + if !id.initCIdentityPtr() { + return nil, ErrInvalidKey + } + rc := C.ZT_Node_addRoot(n.zn, nil, id.cid) + if rc != 0 { + return nil, ErrInvalidParameter + } + p := n.Peer(id.Fingerprint()) + if p == nil { + return nil, ErrInvalidParameter + } + return p, nil } +// RemoveRoot un-designates a peer as root. func (n *Node) RemoveRoot(address Address) { C.ZT_Node_removeRoot(n.zn, nil, C.uint64_t(address)) } -// GetNetwork looks up a network by ID or returns nil if not joined -func (n *Node) GetNetwork(nwid NetworkID) *Network { +// Network looks up a network by ID or returns nil if not joined +func (n *Node) Network(nwid NetworkID) *Network { n.networksLock.RLock() nw := n.networks[nwid] n.networksLock.RUnlock() @@ -484,6 +508,32 @@ func (n *Node) Peers() []*Peer { return peers } +// Peer looks up a single peer by address or full fingerprint. +// The fpOrAddress parameter may be either. If it is neither nil is returned. +// A nil pointer is returned if nothing is found. +func (n *Node) Peer(fpOrAddress interface{}) *Peer { + fp, _ := fpOrAddress.(*Fingerprint) + if fp == nil { + a, _ := fpOrAddress.(*Address) + if a == nil { + return nil + } + fp = &Fingerprint{Address: *a} + } + pl := C.ZT_Node_peers(n.zn) + if pl != nil { + for i := uintptr(0); i < uintptr(pl.peerCount); i++ { + p, _ := newPeerFromCPeer((*C.ZT_Peer)(unsafe.Pointer(uintptr(unsafe.Pointer(pl.peers)) + (i * C.sizeof_ZT_Peer)))) + if p != nil && p.Identity.Fingerprint().BestSpecificityEquals(fp) { + C.ZT_freeQueryResult(unsafe.Pointer(pl)) + return p + } + } + C.ZT_freeQueryResult(unsafe.Pointer(pl)) + } + return nil +} + // AddPeer adds a peer by explicit identity. func (n *Node) AddPeer(id *Identity) error { if id == nil {