diff --git a/go/cmd/zerotier/cli/help.go b/go/cmd/zerotier/cli/help.go index 923cb851e..a0a5ace4e 100644 --- a/go/cmd/zerotier/cli/help.go +++ b/go/cmd/zerotier/cli/help.go @@ -26,64 +26,67 @@ Licensed under the ZeroTier BSL (see LICENSE.txt) Usage: zerotier [-options] [command args] Global Options: - -j Output raw JSON where applicable - -p Use alternate base path - -t Load secret auth token from a file - -T Set secret auth token on command line + -j Output raw JSON where applicable + -p Use alternate base path + -t Load secret auth token from a file + -T Set secret auth token on command line Commands: - help Show this help - version Print version - service Start as service - status Show node status, identity, and config - peers List all VL1 peers - join [fingerprint] Join a virtual network - leave Leave a virtual network - networks List VL2 virtual networks - network Show verbose network info - set [option] [value] Get or set a network config option - manageips Is IP management allowed? - manageroutes Is route management allowed? - globalips Allow assignment of global IPs? - globalroutes Can global IP space routes be set? - defaultroute Can default route be overridden? - set [option] [value] Get or set a service config option - port Primary P2P port - secondaryport Secondary P2P port (0 to disable) - blacklist cidr Toggle physical path blacklisting - blacklist if Toggle interface prefix blacklisting - portmap Toggle use of uPnP or NAT-PMP - controller [option] Local controller management commands - networks List networks run by local controller - new Create a new network - set [setting] [value] Show or modify network settings - members List members of a network - member [setting] [value] Show or modify member level settings - auth Authorize a peer - deauth Deauthorize a peer - identity [args] Identity management commands - new [c25519|p384] Create identity pair (default: c25519) - getpublic Extract only public part of identity - validate Locally validate an identity - sign Sign a file with an identity's key - verify Verify a signature - locator [args] Locator management commands - new
[...] Create and sign a new locator - show [identity] Show locator information - root [command] Root management commands - list List root peers (same as no command) - add Designate a peer as a root - remove
Un-designate a peer as a root + help Show this help + version Print version + service Start as service + status Show node status and configuration + join [fingerprint] Join a virtual network + leave Leave a virtual network + networks List VL2 virtual networks + network [command] [option] - Network management commands + show Show network details (default) + set [option] [value] - Get or set network options + manageips Is IP management allowed? + manageroutes Is route management allowed? + globalips Allow assignment of global IPs? + globalroutes Can global IP space routes be set? + defaultroute Can default route be overridden? + peers List VL1 peers + peer
[command] [option] - Peer management commands + show Show peer details (default) + try [...] Try peer at explicit endpoint + roots List root peers + root [command] - Root management commands + add [endpoint] Designate a peer as a root + remove
Un-designate a peer as a root + set [option] [value] - Get or set a core config option + port Primary P2P port + secondaryport Secondary P2P port (0 to disable) + blacklist cidr Toggle physical path blacklisting + blacklist if Toggle interface prefix blacklisting + portmap Toggle use of uPnP or NAT-PMP + controller [option] - Local controller management commands + networks List networks run by local controller + new Create a new network + set [setting] [value] Show or modify network settings + members List members of a network + member [setting] [value] Show or modify member level settings + auth
Authorize a peer + deauth
Deauthorize a peer + identity [args] - Identity management commands + new [c25519|p384] Create identity (default: c25519) + getpublic Extract only public part of identity + fingerprint Get an identity's fingerprint + validate Locally validate an identity + sign Sign a file with an identity's key + verify Verify a signature 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 -service harness (Windows), etc. -If 'set' is followed by a 16-digit hex number it will get/set network config -options. Otherwise it will get/set local options that pertain to the entire -node. +An
may be specified as a 10-digit short ZeroTier address, a +fingerprint containing both an address and a SHA384 hash, or an identity. +The latter two options are equivalent in terms of specificity and may be +used if stronger security guarantees are desired than those provided by +the basic ZeroTier addressing system. Fields of type must be +full identities and may be specified either verbatim or as a path to a file. -Identities can be specified verbatim on the command line or as a path to -a file. This is detected automatically. +An is a place where a peer may be reached. Currently these are +just 'IP/port' format addresses but other types may be added in the future. `,zerotier.CoreVersionMajor, zerotier.CoreVersionMinor, zerotier.CoreVersionRevision) } diff --git a/go/cmd/zerotier/cli/identity.go b/go/cmd/zerotier/cli/identity.go index 9fa05e7c7..21536fc2c 100644 --- a/go/cmd/zerotier/cli/identity.go +++ b/go/cmd/zerotier/cli/identity.go @@ -35,8 +35,9 @@ func Identity(args []string) { os.Exit(1) } switch args[1] { - case "c25519": - case "p384": + case "c25519", "C25519", "0": + idType = zerotier.IdentityTypeC25519 + case "p384", "P384", "1": idType = zerotier.IdentityTypeP384 default: Help() @@ -57,6 +58,12 @@ func Identity(args []string) { os.Exit(0) } + case "fingerprint": + if len(args) == 2 { + fmt.Println(readIdentity(args[1]).Fingerprint().String()) + os.Exit(0) + } + case "validate": if len(args) == 2 { if readIdentity(args[1]).LocallyValidate() { diff --git a/go/cmd/zerotier/cli/join.go b/go/cmd/zerotier/cli/join.go index ba5972dde..e3af47f0b 100644 --- a/go/cmd/zerotier/cli/join.go +++ b/go/cmd/zerotier/cli/join.go @@ -17,6 +17,7 @@ import ( "fmt" "os" "strconv" + "strings" "zerotier/pkg/zerotier" ) @@ -41,10 +42,19 @@ func Join(basePath, authToken string, args []string) { var fp *zerotier.Fingerprint if len(args) == 2 { - fp, err = zerotier.NewFingerprintFromString(args[1]) - if err != nil { - fmt.Printf("ERROR: invalid network controller fingerprint: %s\n", args[1]) - os.Exit(1) + if strings.ContainsRune(args[1], '-') { + fp, err = zerotier.NewFingerprintFromString(args[1]) + if err != nil { + fmt.Printf("ERROR: invalid network controller fingerprint: %s\n", args[1]) + os.Exit(1) + } + } else { + id, err := zerotier.NewIdentityFromString(args[1]) + if err != nil { + fmt.Printf("ERROR: invalid network controller identity: %s\n", args[1]) + os.Exit(1) + } + fp = id.Fingerprint() } } diff --git a/go/cmd/zerotier/cli/locator.go b/go/cmd/zerotier/cli/locator.go deleted file mode 100644 index 83ea70564..000000000 --- a/go/cmd/zerotier/cli/locator.go +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 cli - -import ( - "fmt" - "os" - "time" - "zerotier/pkg/zerotier" -) - -func Locator(args []string) { - if len(args) > 0 { - switch args[0] { - - case "new": - if len(args) >= 3 { - id := readIdentity(args[1]) - if !id.HasPrivate() { - fmt.Println("ERROR: identity is missing private key and can't be used to sign a locator.") - os.Exit(1) - } - var eps []zerotier.Endpoint - for i:=2;i 1 && len(args) < 4 { - loc := readLocator(args[1]) - var id *zerotier.Identity - if len(args) == 3 { - id = readIdentity(args[2]) - } - ts, fp, eps, valid, _ := loc.GetInfo(id) - fmt.Printf("%s\n Timestamp: %s (%d)\n Validity: ",fp.String(),time.Unix(ts / 1000,ts * 1000).String(),ts) - if id == nil { - fmt.Printf("(no identity provided)\n") - } else { - if valid { - fmt.Printf("SIGNATURE VERIFIED\n") - } else { - fmt.Printf("! INVALID SIGNATURE\n") - } - } - fmt.Print(" Endpoints: ") - for i := range eps { - if i > 0 { - fmt.Print(" ") - } - fmt.Print(eps[i].String()) - } - fmt.Printf("\n") - } - - } - - } - Help() - os.Exit(1) -} diff --git a/go/cmd/zerotier/cli/root.go b/go/cmd/zerotier/cli/root.go index b6bac4c0d..887709826 100644 --- a/go/cmd/zerotier/cli/root.go +++ b/go/cmd/zerotier/cli/root.go @@ -14,4 +14,13 @@ package cli func Root(basePath, authToken string, args []string, jsonOutput bool) { + if len(args) > 0 { + switch args[0] { + + case "add": + + case "remove": + + } + } } diff --git a/go/cmd/zerotier/zerotier.go b/go/cmd/zerotier/zerotier.go index 64712af25..13aa73b54 100644 --- a/go/cmd/zerotier/zerotier.go +++ b/go/cmd/zerotier/zerotier.go @@ -144,8 +144,6 @@ func main() { cli.Set(basePath, authToken, cmdArgs) case "identity": cli.Identity(cmdArgs) - case "locator": - cli.Locator(cmdArgs) case "root": authTokenRequired(authToken) cli.Root(basePath, authToken, cmdArgs, *jflag) diff --git a/go/pkg/zerotier/endpoint.go b/go/pkg/zerotier/endpoint.go index 32d632f45..79649cb84 100644 --- a/go/pkg/zerotier/endpoint.go +++ b/go/pkg/zerotier/endpoint.go @@ -21,7 +21,7 @@ const ( EndpointTypeIp = C.ZT_ENDPOINT_TYPE_IP EndpointTypeIpUdp = C.ZT_ENDPOINT_TYPE_IP_UDP EndpointTypeIpTcp = C.ZT_ENDPOINT_TYPE_IP_TCP - EndpointTypeIpHttp2 = C.ZT_ENDPOINT_TYPE_IP_HTTP2 + EndpointTypeIpHttp = C.ZT_ENDPOINT_TYPE_IP_HTTP ) type Endpoint struct { @@ -65,7 +65,7 @@ func (ep *Endpoint) Type() int { // InetAddress gets this Endpoint as an InetAddress or nil if its type is not addressed by one. func (ep *Endpoint) InetAddress() *InetAddress { switch ep.cep._type { - case EndpointTypeIp, EndpointTypeIpUdp, EndpointTypeIpTcp, EndpointTypeIpHttp2: + case EndpointTypeIp, EndpointTypeIpUdp, EndpointTypeIpTcp, EndpointTypeIpHttp: ua := sockaddrStorageToUDPAddr(C._getSS(&ep.cep)) return &InetAddress{IP: ua.IP, Port: ua.Port} } diff --git a/go/pkg/zerotier/identity.go b/go/pkg/zerotier/identity.go index 1652de59f..7af71507c 100644 --- a/go/pkg/zerotier/identity.go +++ b/go/pkg/zerotier/identity.go @@ -166,6 +166,12 @@ func (id *Identity) Address() Address { return id.address } // HasPrivate returns true if this identity has its own private portion. func (id *Identity) HasPrivate() bool { return len(id.privateKey) > 0 } +// Fingerprint gets this identity's address plus hash of public key(s). +func (id *Identity) Fingerprint() *Fingerprint { + id.initCIdentityPtr() + 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. func (id *Identity) PrivateKeyString() string { switch id.idtype { diff --git a/include/ZeroTierCore.h b/include/ZeroTierCore.h index 0faa5ea12..469c8b6e1 100644 --- a/include/ZeroTierCore.h +++ b/include/ZeroTierCore.h @@ -305,14 +305,9 @@ enum ZT_EndpointType ZT_ENDPOINT_TYPE_IP = 5, // Naked IP (protocol 193) ZT_ENDPOINT_TYPE_IP_UDP = 6, // IP/UDP ZT_ENDPOINT_TYPE_IP_TCP = 7, // IP/TCP - ZT_ENDPOINT_TYPE_IP_HTTP2 = 8 // IP/HTTP2 encapsulation + ZT_ENDPOINT_TYPE_IP_HTTP = 8 // IP/HTTP encapsulation }; -/** - * A string that contains endpoint type IDs indexed by endpoint type (can be used as a lookup array) - */ -#define ZT_ENDPOINT_TYPE_CHAR_INDEX "012345678" - /** * Full identity fingerprint with address and 384-bit hash of public key(s) */ @@ -2277,6 +2272,8 @@ ZT_SDK_API void ZT_version( int *revision, int *build); +/* ---------------------------------------------------------------------------------------------------------------- */ + #ifdef __cplusplus } #endif diff --git a/node/Constants.hpp b/node/Constants.hpp index 0cfc65083..28cb1e633 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -139,6 +139,11 @@ */ #define ZT_NAT_T_PORT_SCAN_MAX 16 +/** + * Minimum interval between attempts to reach a given physical endpoint + */ +#define ZT_PATH_MIN_TRY_INTERVAL ZT_PATH_KEEPALIVE_PERIOD + /** * Delay between calls to the pulse() method in Peer for each peer */ @@ -168,6 +173,11 @@ */ #define ZT_PEER_PRIORITIZE_PATHS_INTERVAL 5000 +/** + * Number of previous endpoints to cache for root-less re-establishment + */ +#define ZT_PEER_ENDPOINT_CACHE_SIZE 8 + /** * Delay between requests for updated network autoconf information * diff --git a/node/Endpoint.cpp b/node/Endpoint.cpp index 3692ee93b..19accfae9 100644 --- a/node/Endpoint.cpp +++ b/node/Endpoint.cpp @@ -18,34 +18,34 @@ namespace ZeroTier { char *Endpoint::toString(char s[ZT_ENDPOINT_STRING_SIZE_MAX]) const noexcept { - static const char *const s_endpointTypeChars = ZT_ENDPOINT_TYPE_CHAR_INDEX; + static const char *const s_endpointTypeChars = "0123456789"; static_assert(ZT_ENDPOINT_STRING_SIZE_MAX > (ZT_INETADDRESS_STRING_SIZE_MAX + 4), "overflow"); static_assert(ZT_ENDPOINT_STRING_SIZE_MAX > (ZT_FINGERPRINT_STRING_SIZE_MAX + 4), "overflow"); switch (this->type) { - default: + default: // ZT_ENDPOINT_TYPE_NIL s[0] = s_endpointTypeChars[ZT_ENDPOINT_TYPE_NIL]; s[1] = 0; break; case ZT_ENDPOINT_TYPE_ZEROTIER: s[0] = s_endpointTypeChars[ZT_ENDPOINT_TYPE_ZEROTIER]; - s[1] = '-'; + s[1] = '='; zt().toString(s + 2); break; case ZT_ENDPOINT_TYPE_ETHERNET: case ZT_ENDPOINT_TYPE_WIFI_DIRECT: case ZT_ENDPOINT_TYPE_BLUETOOTH: s[0] = s_endpointTypeChars[this->type]; - s[1] = '-'; + s[1] = '='; eth().toString(s + 2); break; case ZT_ENDPOINT_TYPE_IP: case ZT_ENDPOINT_TYPE_IP_UDP: case ZT_ENDPOINT_TYPE_IP_TCP: - case ZT_ENDPOINT_TYPE_IP_HTTP2: + case ZT_ENDPOINT_TYPE_IP_HTTP: s[0] = s_endpointTypeChars[this->type]; - s[1] = '-'; + s[1] = '='; ip().toString(s + 2); break; } @@ -59,7 +59,7 @@ bool Endpoint::fromString(const char *s) noexcept if ((!s) || (!*s)) return true; - const char *start = strchr(s, '-'); + const char *start = strchr(s, '='); if (start++ != nullptr) { // Parse a fully qualified type-address format Endpoint. char tmp[16]; @@ -93,7 +93,7 @@ bool Endpoint::fromString(const char *s) noexcept case ZT_ENDPOINT_TYPE_IP: case ZT_ENDPOINT_TYPE_IP_UDP: case ZT_ENDPOINT_TYPE_IP_TCP: - case ZT_ENDPOINT_TYPE_IP_HTTP2: + case ZT_ENDPOINT_TYPE_IP_HTTP: if (!asInetAddress(this->value.ss).fromString(start)) return false; default: @@ -116,8 +116,7 @@ bool Endpoint::fromString(const char *s) noexcept int Endpoint::marshal(uint8_t data[ZT_ENDPOINT_MARSHAL_SIZE_MAX]) const noexcept { switch (this->type) { - //case ZT_ENDPOINT_TYPE_NIL: - default: + default: // ZT_ENDPOINT_TYPE_NIL // NIL endpoints get serialized like NIL InetAddress instances. data[0] = ZT_ENDPOINT_TYPE_NIL; return 1; @@ -141,7 +140,7 @@ int Endpoint::marshal(uint8_t data[ZT_ENDPOINT_MARSHAL_SIZE_MAX]) const noexcept case ZT_ENDPOINT_TYPE_IP: case ZT_ENDPOINT_TYPE_IP_TCP: - case ZT_ENDPOINT_TYPE_IP_HTTP2: + case ZT_ENDPOINT_TYPE_IP_HTTP: // Other IP types get serialized as new version Endpoint instances with type. data[0] = 16 + (uint8_t)this->type; return 1 + asInetAddress(this->value.ss).marshal(data + 1); @@ -197,7 +196,7 @@ int Endpoint::unmarshal(const uint8_t *restrict data, int len) noexcept case ZT_ENDPOINT_TYPE_IP: case ZT_ENDPOINT_TYPE_IP_UDP: case ZT_ENDPOINT_TYPE_IP_TCP: - case ZT_ENDPOINT_TYPE_IP_HTTP2: + case ZT_ENDPOINT_TYPE_IP_HTTP: return asInetAddress(this->value.ss).unmarshal(data + 1, len - 1); default: @@ -227,7 +226,7 @@ bool Endpoint::operator==(const Endpoint &ep) const noexcept case ZT_ENDPOINT_TYPE_IP: case ZT_ENDPOINT_TYPE_IP_UDP: case ZT_ENDPOINT_TYPE_IP_TCP: - case ZT_ENDPOINT_TYPE_IP_HTTP2: + case ZT_ENDPOINT_TYPE_IP_HTTP: return ip() == ep.ip(); default: return true; @@ -249,7 +248,7 @@ bool Endpoint::operator<(const Endpoint &ep) const noexcept case ZT_ENDPOINT_TYPE_IP: case ZT_ENDPOINT_TYPE_IP_UDP: case ZT_ENDPOINT_TYPE_IP_TCP: - case ZT_ENDPOINT_TYPE_IP_HTTP2: + case ZT_ENDPOINT_TYPE_IP_HTTP: return ip() < ep.ip(); default: return true; diff --git a/node/Endpoint.hpp b/node/Endpoint.hpp index 551141e56..696a56711 100644 --- a/node/Endpoint.hpp +++ b/node/Endpoint.hpp @@ -138,12 +138,12 @@ public: case ZT_ENDPOINT_TYPE_IP: case ZT_ENDPOINT_TYPE_IP_UDP: case ZT_ENDPOINT_TYPE_IP_TCP: - case ZT_ENDPOINT_TYPE_IP_HTTP2: + case ZT_ENDPOINT_TYPE_IP_HTTP: switch(ep.type) { case ZT_ENDPOINT_TYPE_IP: case ZT_ENDPOINT_TYPE_IP_UDP: case ZT_ENDPOINT_TYPE_IP_TCP: - case ZT_ENDPOINT_TYPE_IP_HTTP2: + case ZT_ENDPOINT_TYPE_IP_HTTP: return ip().ipsEqual(ep.ip()); default: break; diff --git a/node/Expect.hpp b/node/Expect.hpp index addea21dc..6546632d8 100644 --- a/node/Expect.hpp +++ b/node/Expect.hpp @@ -39,7 +39,8 @@ namespace ZeroTier { class Expect { public: - ZT_INLINE Expect() {} + ZT_INLINE Expect() + {} /** * Called by other code when something is sending a packet that could potentially receive an OK response @@ -47,7 +48,7 @@ public: * @param packetId Packet ID of packet being sent (be sure it's post-armor()) * @param now Current time */ - ZT_INLINE void sending(const uint64_t packetId,const int64_t now) noexcept + ZT_INLINE void sending(const uint64_t packetId, const int64_t now) noexcept { m_packetIdSent[Utils::hash64(packetId ^ Utils::s_mapNonce) % ZT_EXPECT_BUCKETS].store((uint32_t)(now / ZT_EXPECT_TTL)); } @@ -62,7 +63,7 @@ public: * @param now Current time * @return True if we're expecting a reply (and a reset occurred) */ - ZT_INLINE bool expecting(const uint64_t inRePacketId,const int64_t now) noexcept + ZT_INLINE bool expecting(const uint64_t inRePacketId, const int64_t now) noexcept { return (((now / ZT_EXPECT_TTL) - (int64_t)m_packetIdSent[(unsigned long)Utils::hash64(inRePacketId ^ Utils::s_mapNonce) % ZT_EXPECT_BUCKETS].exchange(0)) <= 1); } diff --git a/node/Peer.cpp b/node/Peer.cpp index 541f8a91a..8c08caaa0 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -243,25 +243,51 @@ void Peer::pulse(void *const tPtr, const int64_t now, const bool isRoot) if (m_locator) { for (Vector::const_iterator ep(m_locator->endpoints().begin());ep != m_locator->endpoints().end();++ep) { if (ep->type == ZT_ENDPOINT_TYPE_IP_UDP) { - RR->t->tryingNewPath(tPtr, 0x84b22322, m_id, ep->ip(), InetAddress::NIL, 0, 0, Identity::NIL); - sent(now, m_sendProbe(tPtr, -1, ep->ip(), nullptr, 0, now)); + if (RR->node->shouldUsePathForZeroTierTraffic(tPtr, m_id, -1, ep->ip())) { + int64_t < = m_lastTried[*ep]; + if ((now - lt) > ZT_PATH_MIN_TRY_INTERVAL) { + lt = now; + RR->t->tryingNewPath(tPtr, 0x84b22322, m_id, ep->ip(), InetAddress::NIL, 0, 0, Identity::NIL); + sent(now, m_sendProbe(tPtr, -1, ep->ip(), nullptr, 0, now)); + } + } + } + } + } + + for(unsigned int i=0;i 0) && (m_endpointCache[i].target.type == ZT_ENDPOINT_TYPE_IP_UDP)) { + if (RR->node->shouldUsePathForZeroTierTraffic(tPtr, m_id, -1, m_endpointCache[i].target.ip())) { + int64_t < = m_lastTried[m_endpointCache[i].target]; + if ((now - lt) > ZT_PATH_MIN_TRY_INTERVAL) { + lt = now; + RR->t->tryingNewPath(tPtr, 0x84b22343, m_id, m_endpointCache[i].target.ip(), InetAddress::NIL, 0, 0, Identity::NIL); + sent(now, m_sendProbe(tPtr, -1, m_endpointCache[i].target.ip(), nullptr, 0, now)); + } } } } InetAddress addr; if (RR->node->externalPathLookup(tPtr, m_id, -1, addr)) { - if ((addr) && (RR->node->shouldUsePathForZeroTierTraffic(tPtr, m_id, -1, addr))) { - RR->t->tryingNewPath(tPtr, 0x84a10000, m_id, addr, InetAddress::NIL, 0, 0, Identity::NIL); - sent(now, m_sendProbe(tPtr, -1, addr, nullptr, 0, now)); + if ((addr) && RR->node->shouldUsePathForZeroTierTraffic(tPtr, m_id, -1, addr)) { + int64_t < = m_lastTried[Endpoint(addr)]; + if ((now - lt) > ZT_PATH_MIN_TRY_INTERVAL) { + lt = now; + RR->t->tryingNewPath(tPtr, 0x84a10000, m_id, addr, InetAddress::NIL, 0, 0, Identity::NIL); + sent(now, m_sendProbe(tPtr, -1, addr, nullptr, 0, now)); + } } } } } else { // Attempt up to ZT_NAT_T_MAX_QUEUED_ATTEMPTS_PER_PULSE queued addresses. + // Note that m_lastTried is checked when contact() is called and something + // is added to the try queue, not here. + unsigned int attempts = 0; - for(;;) { + for (;;) { p_TryQueueItem &qi = m_tryQueue.front(); if (qi.target.isInetAddr()) { @@ -326,15 +352,15 @@ void Peer::pulse(void *const tPtr, const int64_t now, const bool isRoot) // Discard front item unless the code skips to requeue_item. discard_queue_item: m_tryQueue.pop_front(); - if ((m_tryQueue.empty()) || (attempts >= ZT_NAT_T_PORT_SCAN_MAX)) + if (attempts >= std::min((unsigned int)m_tryQueue.size(),(unsigned int)ZT_NAT_T_PORT_SCAN_MAX)) break; else continue; // If the code skips here the front item is instead moved to the back. requeue_item: - if (m_tryQueue.size() > 1) + if (m_tryQueue.size() > 1) // no point in doing this splice if there's only one item m_tryQueue.splice(m_tryQueue.end(), m_tryQueue, m_tryQueue.begin()); - if (attempts >= ZT_NAT_T_PORT_SCAN_MAX) + if (attempts >= std::min((unsigned int)m_tryQueue.size(),(unsigned int)ZT_NAT_T_PORT_SCAN_MAX)) break; else continue; } @@ -370,6 +396,13 @@ void Peer::pulse(void *const tPtr, const int64_t now, const bool isRoot) } } } + + // Clean m_lastTried + for (Map::iterator i(m_lastTried.begin());i!=m_lastTried.end();) { + if ((now - i->second) > (ZT_PATH_MIN_TRY_INTERVAL * 4)) + m_lastTried.erase(i++); + else ++i; + } } void Peer::contact(void *tPtr, const int64_t now, const Endpoint &ep, int tries) @@ -387,6 +420,12 @@ void Peer::contact(void *tPtr, const int64_t now, const Endpoint &ep, int tries) } } + // Check underlying path attempt rate limit. + int64_t < = m_lastTried[ep]; + if ((now - lt) < ZT_PATH_MIN_TRY_INTERVAL) + return; + lt = now; + // For IPv4 addresses we send a tiny packet with a low TTL, which helps to // traverse some NAT types. It has no effect otherwise. if (ep.isInetAddr() && ep.ip().isV4()) { diff --git a/node/Peer.hpp b/node/Peer.hpp index 228aca8bb..1c381b55b 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -153,9 +153,7 @@ public: * @param bytes Number of bytes relayed */ ZT_INLINE void relayed(const int64_t now, const unsigned int bytes) noexcept - { - m_relayedMeter.log(now, bytes); - } + { m_relayedMeter.log(now, bytes); } /** * Get the current best direct path or NULL if none @@ -288,25 +286,19 @@ public: * @return The permanent shared key for this peer computed by simple identity agreement */ ZT_INLINE SharedPtr identityKey() noexcept - { - return m_identityKey; - } + { return m_identityKey; } /** * @return AES instance for HELLO dictionary / encrypted section encryption/decryption */ ZT_INLINE const AES &identityHelloDictionaryEncryptionCipher() noexcept - { - return m_helloCipher; - } + { return m_helloCipher; } /** * @return Key for HMAC on HELLOs */ ZT_INLINE const uint8_t *identityHelloHmacKey() noexcept - { - return m_helloMacKey; - } + { return m_helloMacKey; } /** * @return Raw identity key bytes @@ -336,9 +328,7 @@ public: * @return True if this key is ephemeral, false if it's the long-lived identity key */ ZT_INLINE bool isEphemeral(const SharedPtr &k) const noexcept - { - return (m_identityKey != k); - } + { return m_identityKey != k; } /** * Set the currently known remote version of this peer's client @@ -350,10 +340,10 @@ public: */ ZT_INLINE void setRemoteVersion(unsigned int vproto, unsigned int vmaj, unsigned int vmin, unsigned int vrev) noexcept { - m_vProto = (uint16_t) vproto; - m_vMajor = (uint16_t) vmaj; - m_vMinor = (uint16_t) vmin; - m_vRevision = (uint16_t) vrev; + m_vProto = (uint16_t)vproto; + m_vMajor = (uint16_t)vmaj; + m_vMinor = (uint16_t)vmin; + m_vRevision = (uint16_t)vrev; } ZT_INLINE unsigned int remoteVersionProtocol() const noexcept @@ -369,7 +359,7 @@ public: { return m_vRevision; } ZT_INLINE bool remoteVersionKnown() const noexcept - { return ((m_vMajor > 0) || (m_vMinor > 0) || (m_vRevision > 0)); } + { return (m_vMajor > 0) || (m_vMinor > 0) || (m_vRevision > 0); } /** * @return True if there is at least one alive direct path @@ -445,7 +435,7 @@ public: ZT_INLINE bool deduplicateIncomingPacket(const uint64_t packetId) noexcept { // TODO: should take instance ID into account too, but this isn't fully wired. - return m_dedup[Utils::hash32((uint32_t) packetId) & ZT_PEER_DEDUP_BUFFER_MASK].exchange(packetId) == packetId; + return m_dedup[Utils::hash32((uint32_t)packetId) & ZT_PEER_DEDUP_BUFFER_MASK].exchange(packetId) == packetId; } private: @@ -521,7 +511,22 @@ private: // For SharedPtr<> std::atomic __refCount; - // Addresses recieved via PUSH_DIRECT_PATHS etc. that we are scheduled to try. + struct p_EndpointCacheItem + { + Endpoint target; + uint64_t timesSeen; + int64_t firstSeen; + + ZT_INLINE bool operator<(const p_EndpointCacheItem &ci) const noexcept + { return (ci.timesSeen < timesSeen) || ((ci.timesSeen == timesSeen) && (ci.firstSeen < firstSeen)); } + + ZT_INLINE p_EndpointCacheItem() noexcept : target(), timesSeen(0), firstSeen(0) + {} + }; + + // Endpoint cache sorted in ascending order of times seen followed by first seen time. + p_EndpointCacheItem m_endpointCache[ZT_PEER_ENDPOINT_CACHE_SIZE]; + struct p_TryQueueItem { ZT_INLINE p_TryQueueItem() : @@ -539,6 +544,7 @@ private: }; List m_tryQueue; + Map m_lastTried; uint16_t m_vProto; uint16_t m_vMajor; diff --git a/node/Topology.cpp b/node/Topology.cpp index 821d9bcf2..6cd7b3e87 100644 --- a/node/Topology.cpp +++ b/node/Topology.cpp @@ -28,7 +28,7 @@ Topology::Topology(const RuntimeEnvironment *renv, void *tPtr) : for (;;) { Identity id; int l = id.unmarshal(dptr, drem); - if ((l > 0)&&(id)) { + if ((l > 0) && (id)) { if ((drem -= l) <= 0) break; Locator *const loc = new Locator(); @@ -87,9 +87,9 @@ bool Topology::addRoot(void *const tPtr, const Identity &id, const SharedPtr >::const_iterator r(m_rootPeers.begin());r!=m_rootPeers.end();++r) { + for (Vector >::const_iterator r(m_rootPeers.begin());r != m_rootPeers.end();++r) { if ((*r)->address() == address) { - Map< Identity,SharedPtr >::iterator rr(m_roots.find((*r)->identity())); + Map >::iterator rr(m_roots.find((*r)->identity())); if (rr != m_roots.end()) { m_roots.erase(rr); m_updateRootPeers(tPtr); @@ -113,6 +113,8 @@ void Topology::doPeriodicTasks(void *tPtr, const int64_t now) { RWMutex::Lock l1(m_peers_l); for (Map >::iterator i(m_peers.begin());i != m_peers.end();) { + // TODO: also delete if the peer has not exchanged meaningful communication in a while, such as + // a network frame or non-trivial control packet. if (((now - i->second->lastReceive()) > ZT_PEER_ALIVE_TIMEOUT) && (m_roots.count(i->second->identity()) == 0)) { i->second->save(tPtr); m_peers.erase(i++); @@ -147,9 +149,9 @@ void Topology::m_loadCached(void *tPtr, const Address &zta, SharedPtr &pee Vector data(RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_PEER, id)); if (data.size() > 8) { const uint8_t *d = data.data(); - int dl = (int) data.size(); + int dl = (int)data.size(); - const int64_t ts = (int64_t) Utils::loadBigEndian(d); + const int64_t ts = (int64_t)Utils::loadBigEndian(d); Peer *const p = new Peer(RR); int n = p->unmarshal(d + 8, dl - 8); if (n < 0) { @@ -173,7 +175,7 @@ void Topology::m_writeRootList(void *tPtr) uint8_t *const roots = (uint8_t *)malloc((ZT_IDENTITY_MARSHAL_SIZE_MAX + ZT_LOCATOR_MARSHAL_SIZE_MAX + 2) * m_roots.size()); if (roots) { // sanity check int p = 0; - for (Map< Identity,SharedPtr >::const_iterator r(m_roots.begin());r!=m_roots.end();++r) { + for (Map >::const_iterator r(m_roots.begin());r != m_roots.end();++r) { int pp = r->first.marshal(roots + p, false); if (pp > 0) { p += pp; @@ -193,9 +195,9 @@ void Topology::m_writeRootList(void *tPtr) void Topology::m_updateRootPeers(void *tPtr) { // assumes m_peers_l is locked for write - Vector< SharedPtr > rp; - for (Map< Identity,SharedPtr >::iterator r(m_roots.begin());r!=m_roots.end();++r) { - Map< Address,SharedPtr >::iterator pp(m_peers.find(r->first.address())); + Vector > rp; + for (Map >::iterator r(m_roots.begin());r != m_roots.end();++r) { + Map >::iterator pp(m_peers.find(r->first.address())); SharedPtr p; if (pp != m_peers.end()) p = pp->second;