This commit is contained in:
Grant Limberg 2016-09-18 18:10:03 -07:00
commit 3366b53247
28 changed files with 1133 additions and 700 deletions

View File

@ -566,42 +566,69 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest(
// Determine whether and how member is authorized
const char *authorizedBy = (const char *)0;
if (!_jB(network["private"],true)) {
if (_jB(member["authorized"],false)) {
authorizedBy = "memberIsAuthorized";
} else if (!_jB(network["private"],true)) {
authorizedBy = "networkIsPublic";
// If member already has an authorized field, leave it alone. That way its state is
// preserved if the user toggles the network back to private. Otherwise set it to
// true by default for new members of public nets.
if (!member.count("authorized")) {
member["authorized"] = true;
member["lastAuthorizedTime"] = now;
member["lastAuthorizedBy"] = authorizedBy;
json ah;
ah["a"] = true;
ah["by"] = authorizedBy;
ah["ts"] = now;
ah["ct"] = json();
ah["c"] = json();
member["authHistory"].push_back(ah);
member["lastModified"] = now;
auto revj = member["revision"];
json &revj = member["revision"];
member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
}
} else if (_jB(member["authorized"],false)) {
authorizedBy = "memberIsAuthorized";
} else {
char atok[256];
if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH_TOKEN,atok,sizeof(atok)) > 0) {
atok[255] = (char)0; // not necessary but YDIFLO
if (strlen(atok) > 0) { // extra sanity check since we never want to compare a null token on either side
auto authTokens = network["authTokens"];
char presentedAuth[512];
if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH,presentedAuth,sizeof(presentedAuth)) > 0) {
presentedAuth[511] = (char)0; // sanity check
// Check for bearer token presented by member
if ((strlen(presentedAuth) > 6)&&(!strncmp(presentedAuth,"token:",6))) {
const char *const presentedToken = presentedAuth + 6;
json &authTokens = network["authTokens"];
if (authTokens.is_array()) {
for(unsigned long i=0;i<authTokens.size();++i) {
auto at = authTokens[i];
if (at.is_object()) {
const uint64_t expires = _jI(at["expires"],0ULL);
std::string tok = _jS(at["token"],"");
if ( ((expires == 0ULL)||(expires > now)) && (tok.length() > 0) && (tok == atok) ) {
authorizedBy = "token";
member["authorized"] = true; // tokens actually change member authorization state
member["lastAuthorizedTime"] = now;
member["lastAuthorizedBy"] = authorizedBy;
member["lastModified"] = now;
auto revj = member["revision"];
member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
break;
json &token = authTokens[i];
if (token.is_object()) {
const uint64_t expires = _jI(token["expires"],0ULL);
const uint64_t maxUses = _jI(token["maxUsesPerMember"],0ULL);
std::string tstr = _jS(token["token"],"");
if (((expires == 0ULL)||(expires > now))&&(tstr == presentedToken)) {
bool usable = (maxUses == 0);
if (!usable) {
uint64_t useCount = 0;
json &ahist = member["authHistory"];
if (ahist.is_array()) {
for(unsigned long j=0;j<ahist.size();++j) {
json &ah = ahist[j];
if ((_jS(ah["ct"],"") == "token")&&(_jS(ah["c"],"") == tstr)&&(_jB(ah["a"],false)))
++useCount;
}
}
usable = (useCount < maxUses);
}
if (usable) {
authorizedBy = "token";
member["authorized"] = true;
json ah;
ah["a"] = true;
ah["by"] = authorizedBy;
ah["ts"] = now;
ah["ct"] = "token";
ah["c"] = tstr;
member["authHistory"].push_back(ah);
member["lastModified"] = now;
json &revj = member["revision"];
member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
}
}
}
}
@ -924,13 +951,11 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest(
}
}
if (_jB(network["private"],true)) {
CertificateOfMembership com(now,credentialtmd,nwid,identity.address());
if (com.sign(signingId)) {
nc.com = com;
} else {
return NETCONF_QUERY_INTERNAL_SERVER_ERROR;
}
CertificateOfMembership com(now,credentialtmd,nwid,identity.address());
if (com.sign(signingId)) {
nc.com = com;
} else {
return NETCONF_QUERY_INTERNAL_SERVER_ERROR;
}
_writeJson(memberJP,member);
@ -1139,16 +1164,15 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
if (b.count("authorized")) {
const bool newAuth = _jB(b["authorized"],false);
const bool oldAuth = _jB(member["authorized"],false);
if (newAuth != oldAuth) {
if (newAuth) {
member["authorized"] = true;
member["lastAuthorizedTime"] = now;
member["lastAuthorizedBy"] = "user";
} else {
member["authorized"] = false;
member["lastDeauthorizedTime"] = now;
}
if (newAuth != _jB(member["authorized"],false)) {
member["authorized"] = newAuth;
json ah;
ah["a"] = newAuth;
ah["by"] = "api";
ah["ts"] = now;
ah["ct"] = json();
ah["c"] = json();
member["authHistory"].push_back(ah);
}
}
@ -1429,13 +1453,14 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
if (authTokens.is_array()) {
json nat = json::array();
for(unsigned long i=0;i<authTokens.size();++i) {
auto token = authTokens[i];
json &token = authTokens[i];
if (token.is_object()) {
std::string tstr = token["token"];
if (tstr.length() > 0) {
json t = json::object();
t["token"] = tstr;
t["expires"] = _jI(token["expires"],0ULL);
t["maxUsesPerMember"] = _jI(token["maxUsesPerMember"],0ULL);
nat.push_back(t);
}
}
@ -1585,7 +1610,6 @@ void EmbeddedNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTes
"\t\"upstream\": \"%.10llx\"," ZT_EOL_S
"\t\"current\": \"%.10llx\"," ZT_EOL_S
"\t\"receivedTimestamp\": %llu," ZT_EOL_S
"\t\"remoteTimestamp\": %llu," ZT_EOL_S
"\t\"sourcePacketId\": \"%.16llx\"," ZT_EOL_S
"\t\"flags\": %llu," ZT_EOL_S
"\t\"sourcePacketHopCount\": %u," ZT_EOL_S
@ -1606,7 +1630,6 @@ void EmbeddedNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTes
(unsigned long long)report->upstream,
(unsigned long long)report->current,
(unsigned long long)OSUtils::now(),
(unsigned long long)report->remoteTimestamp,
(unsigned long long)report->sourcePacketId,
(unsigned long long)report->flags,
report->sourcePacketHopCount,

View File

@ -143,9 +143,7 @@ private:
inline void _initMember(nlohmann::json &member)
{
if (!member.count("authorized")) member["authorized"] = false;
if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL;
if (!member.count("lastAuthorizedBy")) member["lastAuthorizedBy"] = "";
if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL;
if (!member.count("authHistory")) member["authHistory"] = nlohmann::json::array();
if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array();
if (!member.count("recentLog")) member["recentLog"] = nlohmann::json::array();
if (!member.count("activeBridge")) member["activeBridge"] = false;

View File

@ -229,9 +229,7 @@ This returns an object containing all currently online members and the most rece
| nwid | string | 16-digit network ID | no |
| clock | integer | Current clock, ms since epoch | no |
| authorized | boolean | Is member authorized? (for private networks) | YES |
| lastAuthorizedTime | integer | Time 'authorized' was last set to 'true' | no |
| lastAuthorizedBy | string | What last set 'authorized' to 'true'? | no |
| lastDeauthorizedTime | integer | Time 'authorized' was last set to 'false' | no |
| authHistory | array[object] | History of auth changes, latest at end | no |
| activeBridge | boolean | Member is able to bridge to other Ethernet nets | YES |
| identity | string | Member's public ZeroTier identity (if known) | no |
| ipAssignments | array[string] | Managed IP address assignments | YES |

View File

@ -154,6 +154,11 @@ extern "C" {
*/
#define ZT_CIRCUIT_TEST_MAX_HOP_BREADTH 8
/**
* Circuit test report flag: upstream peer authorized in path (e.g. by network COM)
*/
#define ZT_CIRCUIT_TEST_REPORT_FLAGS_UPSTREAM_AUTHORIZED_IN_PATH 0x0000000000000001ULL
/**
* Maximum number of cluster members (and max member ID plus one)
*/
@ -865,19 +870,28 @@ enum ZT_VirtualNetworkConfigOperation
ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY = 4
};
enum ZT_RelayPolicy
{
ZT_RELAY_POLICY_NEVER = 0,
ZT_RELAY_POLICY_TRUSTED = 1,
ZT_RELAY_POLICY_ALWAYS = 2
};
/**
* What trust hierarchy role does this peer have?
*/
enum ZT_PeerRole {
enum ZT_PeerRole
{
ZT_PEER_ROLE_LEAF = 0, // ordinary node
ZT_PEER_ROLE_RELAY = 1, // relay node
ZT_PEER_ROLE_ROOT = 2 // root server
ZT_PEER_ROLE_UPSTREAM = 1, // upstream node
ZT_PEER_ROLE_ROOT = 2 // global root
};
/**
* Vendor ID
*/
enum ZT_Vendor {
enum ZT_Vendor
{
ZT_VENDOR_UNSPECIFIED = 0,
ZT_VENDOR_ZEROTIER = 1
};
@ -885,7 +899,8 @@ enum ZT_Vendor {
/**
* Platform type
*/
enum ZT_Platform {
enum ZT_Platform
{
ZT_PLATFORM_UNSPECIFIED = 0,
ZT_PLATFORM_LINUX = 1,
ZT_PLATFORM_WINDOWS = 2,
@ -900,13 +915,15 @@ enum ZT_Platform {
ZT_PLATFORM_VXWORKS = 11,
ZT_PLATFORM_FREERTOS = 12,
ZT_PLATFORM_SYSBIOS = 13,
ZT_PLATFORM_HURD = 14
ZT_PLATFORM_HURD = 14,
ZT_PLATFORM_WEB = 15
};
/**
* Architecture type
*/
enum ZT_Architecture {
enum ZT_Architecture
{
ZT_ARCHITECTURE_UNSPECIFIED = 0,
ZT_ARCHITECTURE_X86 = 1,
ZT_ARCHITECTURE_X64 = 2,
@ -921,7 +938,8 @@ enum ZT_Architecture {
ZT_ARCHITECTURE_SPARC32 = 11,
ZT_ARCHITECTURE_SPARC64 = 12,
ZT_ARCHITECTURE_DOTNET_CLR = 13,
ZT_ARCHITECTURE_JAVA_JVM = 14
ZT_ARCHITECTURE_JAVA_JVM = 14,
ZT_ARCHITECTURE_WEB = 15
};
/**
@ -959,6 +977,11 @@ typedef struct
*/
unsigned int mtu;
/**
* Recommended MTU to avoid fragmentation at the physical layer (hint)
*/
unsigned int physicalMtu;
/**
* If nonzero, the network this port belongs to indicates DHCP availability
*
@ -1218,18 +1241,13 @@ typedef struct {
*/
uint64_t timestamp;
/**
* Timestamp on remote device
*/
uint64_t remoteTimestamp;
/**
* 64-bit packet ID of packet received by the reporting device
*/
uint64_t sourcePacketId;
/**
* Flags (currently unused, will be zero)
* Flags
*/
uint64_t flags;
@ -1591,6 +1609,9 @@ typedef int (*ZT_PathCheckFunction)(
* Note that this can take a few seconds the first time it's called, as it
* will generate an identity.
*
* TODO: should consolidate function pointers into versioned structure for
* better API stability.
*
* @param node Result: pointer is set to new node instance on success
* @param uptr User pointer to pass to functions/callbacks
* @param now Current clock in milliseconds
@ -1681,6 +1702,15 @@ enum ZT_ResultCode ZT_Node_processVirtualNetworkFrame(
*/
enum ZT_ResultCode ZT_Node_processBackgroundTasks(ZT_Node *node,uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline);
/**
* Set node's relay policy
*
* @param node Node instance
* @param rp New relay policy
* @return OK(0) or error code
*/
enum ZT_ResultCode ZT_Node_setRelayPolicy(ZT_Node *node,enum ZT_RelayPolicy rp);
/**
* Join a network
*

View File

@ -236,6 +236,11 @@
*/
#define ZT_MULTICAST_EXPLICIT_GATHER_DELAY (ZT_MULTICAST_LIKE_EXPIRE / 10)
/**
* Expiration for credentials presented for MULTICAST_LIKE or MULTICAST_GATHER (for non-network-members)
*/
#define ZT_MULTICAST_CREDENTIAL_EXPIRATON ZT_MULTICAST_LIKE_EXPIRE
/**
* Timeout for outgoing multicasts
*
@ -263,6 +268,11 @@
*/
#define ZT_PATH_MIN_REACTIVATE_INTERVAL 2500
/**
* Do not accept HELLOs over a given path more often than this
*/
#define ZT_PATH_HELLO_RATE_LIMIT 1000
/**
* Delay between full-fledge pings of directly connected peers
*/
@ -283,6 +293,11 @@
*/
#define ZT_PEER_ACTIVITY_TIMEOUT 500000
/**
* General rate limit timeout for multiple packet types (HELLO, etc.)
*/
#define ZT_PEER_GENERAL_INBOUND_RATE_LIMIT 1000
/**
* Delay between requests for updated network autoconf information
*
@ -340,6 +355,26 @@
*/
#define ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY 4
/**
* Time horizon for VERB_NETWORK_CREDENTIALS cutoff
*/
#define ZT_PEER_CREDENTIALS_CUTOFF_TIME 60000
/**
* Maximum number of VERB_NETWORK_CREDENTIALS within cutoff time
*/
#define ZT_PEER_CREDEITIALS_CUTOFF_LIMIT 15
/**
* General rate limit for other kinds of rate-limited packets (HELLO, credential request, etc.) both inbound and outbound
*/
#define ZT_PEER_GENERAL_RATE_LIMIT 1000
/**
* How long is a path or peer considered to have a trust relationship with us (for e.g. relay policy) since last trusted established packet?
*/
#define ZT_TRUST_EXPIRATION 600000
/**
* Enable support for older network configurations from older (pre-1.1.6) controllers
*/

View File

@ -62,11 +62,8 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR)
return true;
}
} else if ((c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)&&(verb() == Packet::VERB_HELLO)) {
// A null pointer for peer to _doHELLO() tells it to run its own
// special internal authentication logic. This is done for unencrypted
// HELLOs to learn new identities, etc.
SharedPtr<Peer> tmp;
return _doHELLO(RR,tmp);
// Only HELLO is allowed in the clear, but will still have a MAC
return _doHELLO(RR,false);
}
SharedPtr<Peer> peer(RR->topology->getPeer(sourceAddress));
@ -91,7 +88,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR)
peer->received(_path,hops(),packetId(),v,0,Packet::VERB_NOP,false);
return true;
case Packet::VERB_HELLO: return _doHELLO(RR,peer);
case Packet::VERB_HELLO: return _doHELLO(RR,true);
case Packet::VERB_ERROR: return _doERROR(RR,peer);
case Packet::VERB_OK: return _doOK(RR,peer);
case Packet::VERB_WHOIS: return _doWHOIS(RR,peer);
@ -136,6 +133,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
switch(errorCode) {
case Packet::ERROR_OBJ_NOT_FOUND:
// Object not found, currently only meaningful from network controllers.
if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->controller() == peer->address()))
@ -144,6 +142,9 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
break;
case Packet::ERROR_UNSUPPORTED_OPERATION:
// This can be sent in response to any operation, though right now we only
// consider it meaningful from network controllers. This would indicate
// that the queried node does not support acting as a controller.
if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->controller() == peer->address()))
@ -152,21 +153,47 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
break;
case Packet::ERROR_IDENTITY_COLLISION:
// Roots are the only peers currently permitted to state authoritatively
// that an identity has collided. When this occurs the node should be shut
// down and a new identity created. The odds of this ever happening are
// very low.
if (RR->topology->isRoot(peer->identity()))
RR->node->postEvent(ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION);
break;
case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE: {
// This error can be sent in response to any packet that fails network
// authorization. We only listen to it if it's from a peer that has recently
// been authorized on this network.
SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->recentlyAllowedOnNetwork(peer))) {
const uint64_t now = RR->node->now();
if (peer->rateGateComRequest(now)) {
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
network->config().com.serialize(outp);
outp.append((uint8_t)0);
outp.armor(peer->key(),true);
_path->send(RR,outp.data(),outp.size(),now);
}
}
} break;
case Packet::ERROR_NETWORK_ACCESS_DENIED_: {
// Network controller: network access denied.
SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->controller() == peer->address()))
network->setAccessDenied();
} break;
case Packet::ERROR_UNWANTED_MULTICAST: {
uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD);
MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8,6),6),at<uint32_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14));
TRACE("%.16llx: peer %s unsubscrubed from multicast group %s",nwid,peer->address().toString().c_str(),mg.toString().c_str());
RR->mc->remove(nwid,mg,peer->address());
// Members of networks can use this error to indicate that they no longer
// want to receive multicasts on a given channel.
SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->gate(peer,verb(),packetId()))) {
MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8,6),6),at<uint32_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14));
TRACE("%.16llx: peer %s unsubscrubed from multicast group %s",network->id(),peer->address().toString().c_str(),mg.toString().c_str());
RR->mc->remove(network->id(),mg,peer->address());
}
} break;
default: break;
@ -179,16 +206,11 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
return true;
}
bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,SharedPtr<Peer> &peer)
bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,const bool alreadyAuthenticated)
{
/* Note: this is the only packet ever sent in the clear, and it's also
* the only packet that we authenticate via a different path. Authentication
* occurs here and is based on the validity of the identity and the
* integrity of the packet's MAC, but it must be done after we check
* the identity since HELLO is a mechanism for learning new identities
* in the first place. */
try {
const uint64_t now = RR->node->now();
const uint64_t pid = packetId();
const Address fromAddress(source());
const unsigned int protoVersion = (*this)[ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION];
@ -215,31 +237,30 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,SharedPtr<Peer> &peer
}
}
if (protoVersion < ZT_PROTO_VERSION_MIN) {
TRACE("dropped HELLO from %s(%s): protocol version too old",id.address().toString().c_str(),_path->address().toString().c_str());
return true;
}
if (fromAddress != id.address()) {
TRACE("dropped HELLO from %s(%s): identity not for sending address",fromAddress.toString().c_str(),_path->address().toString().c_str());
return true;
}
if (protoVersion < ZT_PROTO_VERSION_MIN) {
TRACE("dropped HELLO from %s(%s): protocol version too old",id.address().toString().c_str(),_path->address().toString().c_str());
return true;
}
if (!peer) { // peer == NULL is the normal case here
peer = RR->topology->getPeer(id.address());
if (peer) {
// We already have an identity with this address -- check for collisions
SharedPtr<Peer> peer(RR->topology->getPeer(id.address()));
if (peer) {
// We already have an identity with this address -- check for collisions
if (!alreadyAuthenticated) {
if (peer->identity() != id) {
// Identity is different from the one we already have -- address collision
unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
uint8_t key[ZT_PEER_SECRET_KEY_LENGTH];
if (RR->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) {
if (dearmor(key)) { // ensure packet is authentic, otherwise drop
TRACE("rejected HELLO from %s(%s): address already claimed",id.address().toString().c_str(),_path->address().toString().c_str());
Packet outp(id.address(),RR->identity.address(),Packet::VERB_ERROR);
outp.append((unsigned char)Packet::VERB_HELLO);
outp.append((uint8_t)Packet::VERB_HELLO);
outp.append((uint64_t)pid);
outp.append((unsigned char)Packet::ERROR_IDENTITY_COLLISION);
outp.append((uint8_t)Packet::ERROR_IDENTITY_COLLISION);
outp.armor(key,true);
_path->send(RR,outp.data(),outp.size(),RR->node->now());
} else {
@ -260,31 +281,39 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,SharedPtr<Peer> &peer
// Continue at // VALID
}
} else {
// We don't already have an identity with this address -- validate and learn it
} // else continue at // VALID
} else {
// We don't already have an identity with this address -- validate and learn it
// Check identity proof of work
if (!id.locallyValidate()) {
TRACE("dropped HELLO from %s(%s): identity invalid",id.address().toString().c_str(),_path->address().toString().c_str());
return true;
}
// Check packet integrity and authentication
SharedPtr<Peer> newPeer(new Peer(RR,RR->identity,id));
if (!dearmor(newPeer->key())) {
TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_path->address().toString().c_str());
return true;
}
peer = RR->topology->addPeer(newPeer);
// Continue at // VALID
// Sanity check: this basically can't happen
if (alreadyAuthenticated) {
TRACE("dropped HELLO from %s(%s): somehow already authenticated with unknown peer?",id.address().toString().c_str(),_path->address().toString().c_str());
return true;
}
// VALID -- if we made it here, packet passed identity and authenticity checks!
// Check that identity's address is valid as per the derivation function
if (!id.locallyValidate()) {
TRACE("dropped HELLO from %s(%s): identity invalid",id.address().toString().c_str(),_path->address().toString().c_str());
return true;
}
// Check packet integrity and authentication
SharedPtr<Peer> newPeer(new Peer(RR,RR->identity,id));
if (!dearmor(newPeer->key())) {
TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_path->address().toString().c_str());
return true;
}
peer = RR->topology->addPeer(newPeer);
// Continue at // VALID
}
// VALID -- if we made it here, packet passed identity and authenticity checks!
// Learn our external surface address from other peers to help us negotiate symmetric NATs
// and detect changes to our global IP that can trigger path renegotiation.
if ((externalSurfaceAddress)&&(hops() == 0))
RR->sa->iam(id.address(),_path->localAddress(),_path->address(),externalSurfaceAddress,RR->topology->isUpstream(id),RR->node->now());
RR->sa->iam(id.address(),_path->localAddress(),_path->address(),externalSurfaceAddress,RR->topology->isUpstream(id),now);
Packet outp(id.address(),RR->identity.address(),Packet::VERB_OK);
outp.append((unsigned char)Packet::VERB_HELLO);
@ -336,7 +365,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,SharedPtr<Peer> &peer
}
outp.armor(peer->key(),true);
_path->send(RR,outp.data(),outp.size(),RR->node->now());
_path->send(RR,outp.data(),outp.size(),now);
peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision); // important for this to go first so received() knows the version
peer->received(_path,hops(),pid,Packet::VERB_HELLO,0,Packet::VERB_NOP,false);
@ -351,8 +380,15 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
try {
const Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB];
const uint64_t inRePacketId = at<uint64_t>(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID);
bool trustEstablished = false;
//TRACE("%s(%s): OK(%s)",source().toString().c_str(),_path->address().toString().c_str(),Packet::verbString(inReVerb));
// Don't parse OK packets that are not in response to a packet ID we sent
if (!RR->node->expectingReplyTo(inRePacketId)) {
TRACE("%s(%s): OK(%s) DROPPED: not expecting reply to %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),Packet::verbString(inReVerb),packetId());
return true;
}
//TRACE("%s(%s): OK(%s)",peer->address().toString().c_str(),_path->address().toString().c_str(),Packet::verbString(inReVerb));
switch(inReVerb) {
@ -406,6 +442,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID);
const SharedPtr<Network> network(RR->node->network(nwid));
if ((network)&&(network->controller() == peer->address())) {
trustEstablished = true;
const unsigned int chunkLen = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN);
const void *chunkData = field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT,chunkLen);
unsigned int chunkIndex = 0;
@ -424,10 +461,14 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
case Packet::VERB_MULTICAST_GATHER: {
const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID);
const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC,6),6),at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI));
//TRACE("%s(%s): OK(MULTICAST_GATHER) %.16llx/%s length %u",source().toString().c_str(),_path->address().toString().c_str(),nwid,mg.toString().c_str(),size());
const unsigned int count = at<uint16_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 4);
RR->mc->addMultiple(RR->node->now(),nwid,mg,field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 6,count * 5),count,at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS));
SharedPtr<Network> network(RR->node->network(nwid));
if ((network)&&(network->gateMulticastGatherReply(peer,verb(),packetId()))) {
trustEstablished = true;
const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC,6),6),at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI));
//TRACE("%s(%s): OK(MULTICAST_GATHER) %.16llx/%s length %u",source().toString().c_str(),_path->address().toString().c_str(),nwid,mg.toString().c_str(),size());
const unsigned int count = at<uint16_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 4);
RR->mc->addMultiple(RR->node->now(),nwid,mg,field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 6,count * 5),count,at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS));
}
} break;
case Packet::VERB_MULTICAST_FRAME: {
@ -437,31 +478,34 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
//TRACE("%s(%s): OK(MULTICAST_FRAME) %.16llx/%s flags %.2x",peer->address().toString().c_str(),_path->address().toString().c_str(),nwid,mg.toString().c_str(),flags);
unsigned int offset = 0;
SharedPtr<Network> network(RR->node->network(nwid));
if (network) {
unsigned int offset = 0;
if ((flags & 0x01) != 0) { // deprecated but still used by older peers
CertificateOfMembership com;
offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS);
if (com) {
SharedPtr<Network> network(RR->node->network(com.networkId()));
if (network)
if ((flags & 0x01) != 0) { // deprecated but still used by older peers
CertificateOfMembership com;
offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS);
if (com)
network->addCredential(com);
}
}
if ((flags & 0x02) != 0) {
// OK(MULTICAST_FRAME) includes implicit gather results
offset += ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS;
unsigned int totalKnown = at<uint32_t>(offset); offset += 4;
unsigned int count = at<uint16_t>(offset); offset += 2;
RR->mc->addMultiple(RR->node->now(),nwid,mg,field(offset,count * 5),count,totalKnown);
if (network->gateMulticastGatherReply(peer,verb(),packetId())) {
trustEstablished = true;
if ((flags & 0x02) != 0) {
// OK(MULTICAST_FRAME) includes implicit gather results
offset += ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS;
unsigned int totalKnown = at<uint32_t>(offset); offset += 4;
unsigned int count = at<uint16_t>(offset); offset += 2;
RR->mc->addMultiple(RR->node->now(),nwid,mg,field(offset,count * 5),count,totalKnown);
}
}
}
} break;
default: break;
}
peer->received(_path,hops(),packetId(),Packet::VERB_OK,inRePacketId,inReVerb,false);
peer->received(_path,hops(),packetId(),Packet::VERB_OK,inRePacketId,inReVerb,trustEstablished);
} catch ( ... ) {
TRACE("dropped OK from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str());
}
@ -471,6 +515,11 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
{
try {
if (!peer->rateGateInboundWhoisRequest(RR->node->now())) {
TRACE("dropped WHOIS from %s(%s): rate limit circuit breaker tripped",source().toString().c_str(),_path->address().toString().c_str());
return true;
}
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
outp.append((unsigned char)Packet::VERB_WHOIS);
outp.append(packetId());
@ -515,27 +564,29 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,const SharedPtr<Peer>
bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
{
try {
const Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH);
const SharedPtr<Peer> rendezvousWith(RR->topology->getPeer(with));
if (rendezvousWith) {
const unsigned int port = at<uint16_t>(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT);
const unsigned int addrlen = (*this)[ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN];
if ((port > 0)&&((addrlen == 4)||(addrlen == 16))) {
const InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port);
if (!RR->topology->isUpstream(peer->identity())) {
TRACE("RENDEZVOUS from %s says %s might be at %s, ignoring since peer is not upstream",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
} else if (RR->node->shouldUsePathForZeroTierTraffic(_path->localAddress(),atAddr)) {
RR->node->putPacket(_path->localAddress(),atAddr,"ABRE",4,2); // send low-TTL junk packet to 'open' local NAT(s) and stateful firewalls
rendezvousWith->attemptToContactAt(_path->localAddress(),atAddr,RR->node->now());
TRACE("RENDEZVOUS from %s says %s might be at %s, sent verification attempt",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
if (!RR->topology->isUpstream(peer->identity())) {
TRACE("RENDEZVOUS from %s ignored since source is not upstream",peer->address().toString().c_str());
} else {
const Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH);
const SharedPtr<Peer> rendezvousWith(RR->topology->getPeer(with));
if (rendezvousWith) {
const unsigned int port = at<uint16_t>(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT);
const unsigned int addrlen = (*this)[ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN];
if ((port > 0)&&((addrlen == 4)||(addrlen == 16))) {
const InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port);
if (RR->node->shouldUsePathForZeroTierTraffic(_path->localAddress(),atAddr)) {
RR->node->putPacket(_path->localAddress(),atAddr,"ABRE",4,2); // send low-TTL junk packet to 'open' local NAT(s) and stateful firewalls
rendezvousWith->attemptToContactAt(_path->localAddress(),atAddr,RR->node->now());
TRACE("RENDEZVOUS from %s says %s might be at %s, sent verification attempt",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
} else {
TRACE("RENDEZVOUS from %s says %s might be at %s, ignoring since path is not suitable",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
}
} else {
TRACE("RENDEZVOUS from %s says %s might be at %s, ignoring since path is not suitable",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
TRACE("dropped corrupt RENDEZVOUS from %s(%s) (bad address or port)",peer->address().toString().c_str(),_path->address().toString().c_str());
}
} else {
TRACE("dropped corrupt RENDEZVOUS from %s(%s) (bad address or port)",peer->address().toString().c_str(),_path->address().toString().c_str());
TRACE("ignored RENDEZVOUS from %s(%s) to meet unknown peer %s",peer->address().toString().c_str(),_path->address().toString().c_str(),with.toString().c_str());
}
} else {
TRACE("ignored RENDEZVOUS from %s(%s) to meet unknown peer %s",peer->address().toString().c_str(),_path->address().toString().c_str(),with.toString().c_str());
}
peer->received(_path,hops(),packetId(),Packet::VERB_RENDEZVOUS,0,Packet::VERB_NOP,false);
} catch ( ... ) {
@ -549,25 +600,25 @@ bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,const SharedPtr<Peer>
try {
const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID);
const SharedPtr<Network> network(RR->node->network(nwid));
bool approved = false;
bool trustEstablished = false;
if (network) {
if (size() > ZT_PROTO_VERB_FRAME_IDX_PAYLOAD) {
if (!network->isAllowed(peer)) {
TRACE("dropped FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),(unsigned long long)network->id());
} else {
if (!network->gate(peer,verb(),packetId())) {
TRACE("dropped FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),(unsigned long long)network->id());
} else {
trustEstablished = true;
if (size() > ZT_PROTO_VERB_FRAME_IDX_PAYLOAD) {
const unsigned int etherType = at<uint16_t>(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE);
const MAC sourceMac(peer->address(),nwid);
const unsigned int frameLen = size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD;
const uint8_t *const frameData = reinterpret_cast<const uint8_t *>(data()) + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD;
if (network->filterIncomingPacket(peer,RR->identity.address(),sourceMac,network->mac(),frameData,frameLen,etherType,0) > 0)
RR->node->putFrame(nwid,network->userPtr(),sourceMac,network->mac(),etherType,0,(const void *)frameData,frameLen);
approved = true; // this means approved on the network in general, not this packet per se
}
}
} else {
TRACE("dropped FRAME from %s(%s): we are not a member of network %.16llx",source().toString().c_str(),_path->address().toString().c_str(),at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID));
}
peer->received(_path,hops(),packetId(),Packet::VERB_FRAME,0,Packet::VERB_NOP,approved);
peer->received(_path,hops(),packetId(),Packet::VERB_FRAME,0,Packet::VERB_NOP,trustEstablished);
} catch ( ... ) {
TRACE("dropped FRAME from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str());
}
@ -580,23 +631,23 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr<P
const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID);
const SharedPtr<Network> network(RR->node->network(nwid));
if (network) {
const unsigned int flags = (*this)[ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS];
unsigned int comLen = 0;
if ((flags & 0x01) != 0) { // inline COM with EXT_FRAME is deprecated but still used with old peers
CertificateOfMembership com;
comLen = com.deserialize(*this,ZT_PROTO_VERB_EXT_FRAME_IDX_COM);
if (com)
network->addCredential(com);
}
if (!network->gate(peer,verb(),packetId())) {
TRACE("dropped EXT_FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),network->id());
peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,false);
return true;
}
if (size() > ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD) {
const unsigned int flags = (*this)[ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS];
unsigned int comLen = 0;
if ((flags & 0x01) != 0) { // deprecated but still used by old peers
CertificateOfMembership com;
comLen = com.deserialize(*this,ZT_PROTO_VERB_EXT_FRAME_IDX_COM);
if (com)
network->addCredential(com);
}
if (!network->isAllowed(peer)) {
TRACE("dropped EXT_FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),network->id());
peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,false);
return true;
}
const unsigned int etherType = at<uint16_t>(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE);
const MAC to(field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_TO,ZT_PROTO_VERB_EXT_FRAME_LEN_TO),ZT_PROTO_VERB_EXT_FRAME_LEN_TO);
const MAC from(field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_FROM,ZT_PROTO_VERB_EXT_FRAME_LEN_FROM),ZT_PROTO_VERB_EXT_FRAME_LEN_FROM);
@ -604,7 +655,7 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr<P
const uint8_t *const frameData = (const uint8_t *)field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD,frameLen);
if ((!from)||(from.isMulticast())||(from == network->mac())) {
TRACE("dropped EXT_FRAME from %s@%s(%s) to %s: invalid source MAC",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str());
TRACE("dropped EXT_FRAME from %s@%s(%s) to %s: invalid source MAC %s",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str(),from.toString().c_str());
peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true); // trustEstablished because COM is okay
return true;
}
@ -620,7 +671,13 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr<P
return true;
}
} else if (to != network->mac()) {
if (!network->config().permitsBridging(RR->identity.address())) {
if (to.isMulticast()) {
if (network->config().multicastLimit == 0) {
TRACE("dropped EXT_FRAME from %s@%s(%s) to %s: network %.16llx does not allow multicast",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str(),network->id());
peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true); // trustEstablished because COM is okay
return true;
}
} else if (!network->config().permitsBridging(RR->identity.address())) {
TRACE("dropped EXT_FRAME from %s@%s(%s) to %s: I cannot bridge to %.16llx or bridging disabled on network",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str(),network->id());
peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true); // trustEstablished because COM is okay
return true;
@ -647,6 +704,11 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr<P
bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
{
try {
if (!peer->rateGateEchoRequest(RR->node->now())) {
TRACE("dropped ECHO from %s(%s): rate limit circuit breaker tripped",source().toString().c_str(),_path->address().toString().c_str());
return true;
}
const uint64_t pid = packetId();
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
outp.append((unsigned char)Packet::VERB_ECHO);
@ -655,6 +717,7 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,const SharedPtr<Peer>
outp.append(reinterpret_cast<const unsigned char *>(data()) + ZT_PACKET_IDX_PAYLOAD,size() - ZT_PACKET_IDX_PAYLOAD);
outp.armor(peer->key(),true);
_path->send(RR,outp.data(),outp.size(),RR->node->now());
peer->received(_path,hops(),pid,Packet::VERB_ECHO,0,Packet::VERB_NOP,false);
} catch ( ... ) {
TRACE("dropped ECHO from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str());
@ -667,14 +730,41 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,const Shared
try {
const uint64_t now = RR->node->now();
uint64_t authOnNetwork[256];
unsigned int authOnNetworkCount = 0;
SharedPtr<Network> network;
bool trustEstablished = false;
// Iterate through 18-byte network,MAC,ADI tuples
for(unsigned int ptr=ZT_PACKET_IDX_PAYLOAD;ptr<size();ptr+=18) {
const uint64_t nwid = at<uint64_t>(ptr);
const MulticastGroup group(MAC(field(ptr + 8,6),6),at<uint32_t>(ptr + 14));
RR->mc->add(now,nwid,group,peer->address());
bool auth = false;
for(unsigned int i=0;i<authOnNetworkCount;++i) {
if (nwid == authOnNetwork[i]) {
auth = true;
break;
}
}
if (!auth) {
if ((!network)||(network->id() != nwid))
network = RR->node->network(nwid);
const bool authOnNet = ((network)&&(network->gate(peer,verb(),packetId())));
trustEstablished |= authOnNet;
if (authOnNet||RR->mc->cacheAuthorized(peer->address(),nwid,now)) {
auth = true;
if (authOnNetworkCount < 256) // sanity check, packets can't really be this big
authOnNetwork[authOnNetworkCount++] = nwid;
}
}
if (auth) {
const MulticastGroup group(MAC(field(ptr + 8,6),6),at<uint32_t>(ptr + 14));
RR->mc->add(now,nwid,group,peer->address());
}
}
peer->received(_path,hops(),packetId(),Packet::VERB_MULTICAST_LIKE,0,Packet::VERB_NOP,false);
peer->received(_path,hops(),packetId(),Packet::VERB_MULTICAST_LIKE,0,Packet::VERB_NOP,trustEstablished);
} catch ( ... ) {
TRACE("dropped MULTICAST_LIKE from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str());
}
@ -684,9 +774,15 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,const Shared
bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
{
try {
if (!peer->rateGateCredentialsReceived(RR->node->now())) {
TRACE("dropped NETWORK_CREDENTIALS from %s(%s): rate limit circuit breaker tripped",source().toString().c_str(),_path->address().toString().c_str());
return true;
}
CertificateOfMembership com;
Capability cap;
Tag tag;
bool trustEstablished = false;
unsigned int p = ZT_PACKET_IDX_PAYLOAD;
while ((p < size())&&((*this)[p])) {
@ -694,9 +790,11 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
if (com) {
SharedPtr<Network> network(RR->node->network(com.networkId()));
if (network) {
if (network->addCredential(com) == 1)
return false; // wait for WHOIS
}
switch (network->addCredential(com)) {
case 0: trustEstablished = true; break;
case 1: return false; // wait for WHOIS
}
} else RR->mc->addCredential(com,false);
}
}
++p; // skip trailing 0 after COMs if present
@ -707,8 +805,10 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
p += cap.deserialize(*this,p);
SharedPtr<Network> network(RR->node->network(cap.networkId()));
if (network) {
if (network->addCredential(cap) == 1)
return false; // wait for WHOIS
switch (network->addCredential(cap)) {
case 0: trustEstablished = true; break;
case 1: return false; // wait for WHOIS
}
}
}
@ -717,13 +817,15 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
p += tag.deserialize(*this,p);
SharedPtr<Network> network(RR->node->network(tag.networkId()));
if (network) {
if (network->addCredential(tag) == 1)
return false; // wait for WHOIS
switch (network->addCredential(tag)) {
case 0: trustEstablished = true; break;
case 1: return false; // wait for WHOIS
}
}
}
}
peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CREDENTIALS,0,Packet::VERB_NOP,false);
peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CREDENTIALS,0,Packet::VERB_NOP,trustEstablished);
} catch ( ... ) {
TRACE("dropped NETWORK_CREDENTIALS from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str());
}
@ -734,22 +836,21 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
{
try {
const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID);
const unsigned int metaDataLength = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN);
const char *metaDataBytes = (const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength);
const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData(metaDataBytes,metaDataLength);
const unsigned int hopCount = hops();
const uint64_t requestPacketId = packetId();
bool netconfOk = false;
bool trustEstablished = false;
if (RR->localNetworkController) {
const unsigned int metaDataLength = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN);
const char *metaDataBytes = (const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength);
const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData(metaDataBytes,metaDataLength);
NetworkConfig *netconf = new NetworkConfig();
try {
switch(RR->localNetworkController->doNetworkConfigRequest((hopCount > 0) ? InetAddress() : _path->address(),RR->identity,peer->identity(),nwid,metaData,*netconf)) {
case NetworkController::NETCONF_QUERY_OK: {
netconfOk = true;
trustEstablished = true;
Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *dconf = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
try {
if (netconf->toDictionary(*dconf,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6)) {
@ -821,7 +922,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
_path->send(RR,outp.data(),outp.size(),RR->node->now());
}
peer->received(_path,hopCount,requestPacketId,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,netconfOk);
peer->received(_path,hopCount,requestPacketId,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,trustEstablished);
} catch (std::exception &exc) {
fprintf(stderr,"WARNING: network config request failed with exception: %s" ZT_EOL_S,exc.what());
TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): %s",source().toString().c_str(),_path->address().toString().c_str(),exc.what());
@ -836,11 +937,13 @@ bool IncomingPacket::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,cons
{
try {
const uint64_t nwid = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD);
bool trustEstablished = false;
if (Network::controllerFor(nwid) == peer->address()) {
SharedPtr<Network> network(RR->node->network(nwid));
if (network) {
network->requestConfiguration();
trustEstablished = true;
} else {
TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): not a member of %.16llx",source().toString().c_str(),_path->address().toString().c_str(),nwid);
peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP,false);
@ -855,7 +958,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,cons
}
}
peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP,false);
peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP,trustEstablished);
} catch ( ... ) {
TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str());
}
@ -872,21 +975,24 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,const Shar
//TRACE("<<MC %s(%s) GATHER up to %u in %.16llx/%s",source().toString().c_str(),_path->address().toString().c_str(),gatherLimit,nwid,mg.toString().c_str());
const SharedPtr<Network> network(RR->node->network(nwid));
if ((flags & 0x01) != 0) {
try {
CertificateOfMembership com;
com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_GATHER_IDX_COM);
if (com) {
SharedPtr<Network> network(RR->node->network(nwid));
if (network)
network->addCredential(com);
else RR->mc->addCredential(com,false);
}
} catch ( ... ) {
TRACE("MULTICAST_GATHER from %s(%s): discarded invalid COM",peer->address().toString().c_str(),_path->address().toString().c_str());
}
}
if (gatherLimit) {
const bool trustEstablished = ((network)&&(network->gate(peer,verb(),packetId())));
if ( ( trustEstablished || RR->mc->cacheAuthorized(peer->address(),nwid,RR->node->now()) ) && (gatherLimit > 0) ) {
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
outp.append((unsigned char)Packet::VERB_MULTICAST_GATHER);
outp.append(packetId());
@ -906,7 +1012,7 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,const Shar
#endif
}
peer->received(_path,hops(),packetId(),Packet::VERB_MULTICAST_GATHER,0,Packet::VERB_NOP,false);
peer->received(_path,hops(),packetId(),Packet::VERB_MULTICAST_GATHER,0,Packet::VERB_NOP,trustEstablished);
} catch ( ... ) {
TRACE("dropped MULTICAST_GATHER from %s(%s): unexpected exception",peer->address().toString().c_str(),_path->address().toString().c_str());
}
@ -932,14 +1038,18 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,const Share
network->addCredential(com);
}
// Check membership after we've read any included COM, since
// that cert might be what we needed.
if (!network->isAllowed(peer)) {
if (!network->gate(peer,verb(),packetId())) {
TRACE("dropped MULTICAST_FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),(unsigned long long)network->id());
peer->received(_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,false);
return true;
}
if (network->config().multicastLimit == 0) {
TRACE("dropped MULTICAST_FRAME from %s(%s): network %.16llx does not allow multicast",peer->address().toString().c_str(),_path->address().toString().c_str(),(unsigned long long)network->id());
peer->received(_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,false);
return true;
}
unsigned int gatherLimit = 0;
if ((flags & 0x02) != 0) {
gatherLimit = at<uint32_t>(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_GATHER_LIMIT);
@ -1018,7 +1128,7 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const Sha
const uint64_t now = RR->node->now();
// First, subject this to a rate limit
if (!peer->shouldRespondToDirectPathPush(now)) {
if (!peer->rateGatePushDirectPaths(now)) {
TRACE("dropped PUSH_DIRECT_PATHS from %s(%s): circuit breaker tripped",source().toString().c_str(),_path->address().toString().c_str());
peer->received(_path,hops(),packetId(),Packet::VERB_PUSH_DIRECT_PATHS,0,Packet::VERB_NOP,false);
return true;
@ -1139,6 +1249,8 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt
// Add length of second "additional fields" section.
vlf += at<uint16_t>(ZT_PACKET_IDX_PAYLOAD + 29 + vlf);
uint64_t reportFlags = 0;
// Check credentials (signature already verified)
if (originatorCredentialNetworkId) {
SharedPtr<Network> network(RR->node->network(originatorCredentialNetworkId));
@ -1147,6 +1259,8 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt
peer->received(_path,hops(),packetId(),Packet::VERB_CIRCUIT_TEST,0,Packet::VERB_NOP,false);
return true;
}
if (network->gate(peer,verb(),packetId()))
reportFlags |= ZT_CIRCUIT_TEST_REPORT_FLAGS_UPSTREAM_AUTHORIZED_IN_PATH;
} else {
TRACE("dropped CIRCUIT_TEST from %s(%s): originator %s did not specify a credential or credential type",source().toString().c_str(),_path->address().toString().c_str(),originatorAddress.toString().c_str());
peer->received(_path,hops(),packetId(),Packet::VERB_CIRCUIT_TEST,0,Packet::VERB_NOP,false);
@ -1188,7 +1302,7 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt
outp.append((uint16_t)ZT_PLATFORM_UNSPECIFIED);
outp.append((uint16_t)ZT_ARCHITECTURE_UNSPECIFIED);
outp.append((uint16_t)0); // error code, currently unused
outp.append((uint64_t)0); // flags, currently unused
outp.append((uint64_t)reportFlags);
outp.append((uint64_t)packetId());
peer->address().appendTo(outp);
outp.append((uint8_t)hops());
@ -1237,7 +1351,6 @@ bool IncomingPacket::_doCIRCUIT_TEST_REPORT(const RuntimeEnvironment *RR,const S
report.upstream = Address(field(ZT_PACKET_IDX_PAYLOAD + 52,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH).toInt();
report.testId = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD + 8);
report.timestamp = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD);
report.remoteTimestamp = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD + 16);
report.sourcePacketId = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD + 44);
report.flags = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD + 36);
report.sourcePacketHopCount = (*this)[ZT_PACKET_IDX_PAYLOAD + 57]; // end of fixed length headers: 58

View File

@ -136,7 +136,7 @@ private:
// These are called internally to handle packet contents once it has
// been authenticated, decrypted, decompressed, and classified.
bool _doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doHELLO(const RuntimeEnvironment *RR,SharedPtr<Peer> &peer); // can be called with NULL peer, while all others cannot
bool _doHELLO(const RuntimeEnvironment *RR,const bool alreadyAuthenticated);
bool _doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doWHOIS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doRENDEZVOUS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);

View File

@ -24,13 +24,13 @@
#include "Packet.hpp"
#include "Node.hpp"
#define ZT_CREDENTIAL_PUSH_EVERY (ZT_NETWORK_AUTOCONF_DELAY / 4)
#define ZT_CREDENTIAL_PUSH_EVERY (ZT_NETWORK_AUTOCONF_DELAY / 3)
namespace ZeroTier {
void Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,const Capability *cap)
{
if ((now - _lastPushAttempt) < 1000ULL)
if ((now - _lastPushAttempt) < 2000ULL)
return;
_lastPushAttempt = now;
@ -71,7 +71,7 @@ void Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint
}
capsAndTags.setAt<uint16_t>(tagCountPos,(uint16_t)appendedTags);
const bool needCom = ((nconf.isPrivate())&&(nconf.com)&&((now - _lastPushedCom) >= ZT_CREDENTIAL_PUSH_EVERY));
const bool needCom = ((nconf.com)&&((now - _lastPushedCom) >= ZT_CREDENTIAL_PUSH_EVERY));
if ( (needCom) || (appendedCaps) || (appendedTags) ) {
Packet outp(peerAddress,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
if (needCom) {
@ -99,9 +99,11 @@ int Membership::addCredential(const RuntimeEnvironment *RR,const CertificateOfMe
const int vr = com.verify(RR);
if (vr == 0) {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (new)",com.issuedTo().toString().c_str(),com.networkId());
if (com.timestamp().first > _com.timestamp().first) {
if (com.timestamp().first >= _com.timestamp().first) {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (new)",com.issuedTo().toString().c_str(),com.networkId());
_com = com;
} else {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED but not used (OK but older than current)",com.issuedTo().toString().c_str(),com.networkId());
}
} else {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (%d)",com.issuedTo().toString().c_str(),com.networkId(),vr);

View File

@ -154,6 +154,23 @@ public:
return nconf.com.agreesWith(_com);
}
/**
* @return True if this member has been on this network recently (or network is public)
*/
inline bool recentlyAllowedOnNetwork(const NetworkConfig &nconf) const
{
if (nconf.isPublic())
return true;
if (_com) {
const uint64_t a = _com.timestamp().first;
if ((_blacklistBefore)&&(a <= _blacklistBefore))
return false;
const uint64_t b = nconf.com.timestamp().first;
return ((a <= b) ? ((b - a) <= ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA) : true);
}
return false;
}
/**
* Check whether a capability or tag is within its max delta from the timestamp of our network config and newer than any blacklist cutoff time
*

View File

@ -34,8 +34,8 @@ namespace ZeroTier {
Multicaster::Multicaster(const RuntimeEnvironment *renv) :
RR(renv),
_groups(1024),
_groups_m()
_groups(256),
_gatherAuth(256)
{
}
@ -244,7 +244,7 @@ void Multicaster::send(
}
for(unsigned int k=0;k<numExplicitGatherPeers;++k) {
const CertificateOfMembership *com = (network) ? (((network->config())&&(network->config().isPrivate())) ? &(network->config().com) : (const CertificateOfMembership *)0) : (const CertificateOfMembership *)0;
const CertificateOfMembership *com = (network) ? ((network->config().com) ? &(network->config().com) : (const CertificateOfMembership *)0) : (const CertificateOfMembership *)0;
Packet outp(explicitGatherPeers[k],RR->identity.address(),Packet::VERB_MULTICAST_GATHER);
outp.append(nwid);
outp.append((uint8_t)((com) ? 0x01 : 0x00));
@ -253,6 +253,7 @@ void Multicaster::send(
outp.append((uint32_t)gatherLimit);
if (com)
com->serialize(outp);
RR->node->expectReplyTo(outp.packetId());
RR->sw->send(outp,true);
}
}
@ -300,42 +301,62 @@ void Multicaster::send(
void Multicaster::clean(uint64_t now)
{
Mutex::Lock _l(_groups_m);
{
Mutex::Lock _l(_groups_m);
Multicaster::Key *k = (Multicaster::Key *)0;
MulticastGroupStatus *s = (MulticastGroupStatus *)0;
Hashtable<Multicaster::Key,MulticastGroupStatus>::Iterator mm(_groups);
while (mm.next(k,s)) {
for(std::list<OutboundMulticast>::iterator tx(s->txQueue.begin());tx!=s->txQueue.end();) {
if ((tx->expired(now))||(tx->atLimit()))
s->txQueue.erase(tx++);
else ++tx;
}
Multicaster::Key *k = (Multicaster::Key *)0;
MulticastGroupStatus *s = (MulticastGroupStatus *)0;
Hashtable<Multicaster::Key,MulticastGroupStatus>::Iterator mm(_groups);
while (mm.next(k,s)) {
for(std::list<OutboundMulticast>::iterator tx(s->txQueue.begin());tx!=s->txQueue.end();) {
if ((tx->expired(now))||(tx->atLimit()))
s->txQueue.erase(tx++);
else ++tx;
}
unsigned long count = 0;
{
std::vector<MulticastGroupMember>::iterator reader(s->members.begin());
std::vector<MulticastGroupMember>::iterator writer(reader);
while (reader != s->members.end()) {
if ((now - reader->timestamp) < ZT_MULTICAST_LIKE_EXPIRE) {
*writer = *reader;
++writer;
++count;
unsigned long count = 0;
{
std::vector<MulticastGroupMember>::iterator reader(s->members.begin());
std::vector<MulticastGroupMember>::iterator writer(reader);
while (reader != s->members.end()) {
if ((now - reader->timestamp) < ZT_MULTICAST_LIKE_EXPIRE) {
*writer = *reader;
++writer;
++count;
}
++reader;
}
++reader;
}
if (count) {
s->members.resize(count);
} else if (s->txQueue.empty()) {
_groups.erase(*k);
} else {
s->members.clear();
}
}
}
if (count) {
s->members.resize(count);
} else if (s->txQueue.empty()) {
_groups.erase(*k);
} else {
s->members.clear();
{
Mutex::Lock _l(_gatherAuth_m);
_GatherAuthKey *k = (_GatherAuthKey *)0;
uint64_t *ts = (uint64_t *)ts;
Hashtable<_GatherAuthKey,uint64_t>::Iterator i(_gatherAuth);
while (i.next(k,ts)) {
if ((now - *ts) >= ZT_MULTICAST_CREDENTIAL_EXPIRATON)
_gatherAuth.erase(*k);
}
}
}
void Multicaster::addCredential(const CertificateOfMembership &com,bool alreadyValidated)
{
if ((alreadyValidated)||(com.verify(RR) == 0)) {
Mutex::Lock _l(_gatherAuth_m);
_gatherAuth[_GatherAuthKey(com.networkId(),com.issuedTo())] = RR->node->now();
}
}
void Multicaster::_add(uint64_t now,uint64_t nwid,const MulticastGroup &mg,MulticastGroupStatus &gs,const Address &member)
{
// assumes _groups_m is locked

View File

@ -179,12 +179,52 @@ public:
*/
void clean(uint64_t now);
/**
* Add an authorization credential
*
* The Multicaster keeps its own track of when valid credentials of network
* membership are presented. This allows it to control MULTICAST_LIKE
* GATHER authorization for networks this node does not belong to.
*
* @param com Certificate of membership
* @param alreadyValidated If true, COM has already been checked and found to be valid and signed
*/
void addCredential(const CertificateOfMembership &com,bool alreadyValidated);
/**
* Check authorization for GATHER and LIKE for non-network-members
*
* @param a Address of peer
* @param nwid Network ID
* @param now Current time
* @return True if GATHER and LIKE should be allowed
*/
bool cacheAuthorized(const Address &a,const uint64_t nwid,const uint64_t now) const
{
Mutex::Lock _l(_gatherAuth_m);
const uint64_t *p = _gatherAuth.get(_GatherAuthKey(nwid,a));
return ((p)&&((now - *p) < ZT_MULTICAST_CREDENTIAL_EXPIRATON));
}
private:
void _add(uint64_t now,uint64_t nwid,const MulticastGroup &mg,MulticastGroupStatus &gs,const Address &member);
const RuntimeEnvironment *RR;
Hashtable<Multicaster::Key,MulticastGroupStatus> _groups;
Mutex _groups_m;
struct _GatherAuthKey
{
_GatherAuthKey() : member(0),networkId(0) {}
_GatherAuthKey(const uint64_t nwid,const Address &a) : member(a.toInt()),networkId(nwid) {}
inline unsigned long hashCode() const { return (member ^ networkId); }
inline bool operator==(const _GatherAuthKey &k) const { return ((member == k.member)&&(networkId == k.networkId)); }
uint64_t member;
uint64_t networkId;
};
Hashtable< _GatherAuthKey,uint64_t > _gatherAuth;
Mutex _gatherAuth_m;
};
} // namespace ZeroTier

View File

@ -656,8 +656,12 @@ bool Network::filterOutgoingPacket(
Mutex::Lock _l(_lock);
Membership &m = _memberships[ztDest];
const unsigned int remoteTagCount = m.getAllTags(_config,remoteTagIds,remoteTagValues,ZT_MAX_NETWORK_TAGS);
Membership *m = (Membership *)0;
unsigned int remoteTagCount = 0;
if (ztDest) {
m = &(_memberships[ztDest]);
remoteTagCount = m->getAllTags(_config,remoteTagIds,remoteTagValues,ZT_MAX_NETWORK_TAGS);
}
switch(_doZtFilter(RR,_config,false,ztSource,ztDest2,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,_config.tags,_config.tagCount,remoteTagIds,remoteTagValues,remoteTagCount,cc,ccLength)) {
@ -737,8 +741,8 @@ bool Network::filterOutgoingPacket(
RR->sw->send(outp,true);
return false; // DROP locally, since we redirected
} else if (ztDest) {
m.sendCredentialsIfNeeded(RR,RR->node->now(),ztDest,_config,relevantCap);
} else if (m) {
m->sendCredentialsIfNeeded(RR,RR->node->now(),ztDest,_config,relevantCap);
}
}
@ -764,7 +768,7 @@ int Network::filterIncomingPacket(
Mutex::Lock _l(_lock);
Membership &m = _membership(ztDest);
Membership &m = _membership(sourcePeer->address());
const unsigned int remoteTagCount = m.getAllTags(_config,remoteTagIds,remoteTagValues,ZT_MAX_NETWORK_TAGS);
switch (_doZtFilter(RR,_config,true,sourcePeer->address(),ztDest2,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,_config.tags,_config.tagCount,remoteTagIds,remoteTagValues,remoteTagCount,cc,ccLength)) {
@ -862,31 +866,24 @@ bool Network::subscribedToMulticastGroup(const MulticastGroup &mg,bool includeBr
return true;
else if (includeBridgedGroups)
return _multicastGroupsBehindMe.contains(mg);
else return false;
return false;
}
void Network::multicastSubscribe(const MulticastGroup &mg)
{
{
Mutex::Lock _l(_lock);
if (std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg))
return;
_myMulticastGroups.push_back(mg);
std::sort(_myMulticastGroups.begin(),_myMulticastGroups.end());
_announceMulticastGroups(&mg);
Mutex::Lock _l(_lock);
if (!std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)) {
_myMulticastGroups.insert(std::upper_bound(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg),mg);
_sendUpdatesToMembers(&mg);
}
}
void Network::multicastUnsubscribe(const MulticastGroup &mg)
{
Mutex::Lock _l(_lock);
std::vector<MulticastGroup> nmg;
for(std::vector<MulticastGroup>::const_iterator i(_myMulticastGroups.begin());i!=_myMulticastGroups.end();++i) {
if (*i != mg)
nmg.push_back(*i);
}
if (nmg.size() != _myMulticastGroups.size())
_myMulticastGroups.swap(nmg);
std::vector<MulticastGroup>::iterator i(std::lower_bound(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg));
if ( (i != _myMulticastGroups.end()) && (*i == mg) )
_myMulticastGroups.erase(i);
}
bool Network::applyConfiguration(const NetworkConfig &conf)
@ -1004,6 +1001,7 @@ void Network::requestConfiguration()
Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> rmd;
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION);
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_VENDOR,(uint64_t)ZT_VENDOR_ZEROTIER);
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,(uint64_t)ZT_PROTO_VERSION);
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MAJOR);
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MINOR);
@ -1014,6 +1012,7 @@ void Network::requestConfiguration()
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS,(uint64_t)ZT_MAX_NETWORK_TAGS);
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS,(uint64_t)0);
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,(uint64_t)ZT_RULES_ENGINE_REVISION);
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_RELAY_POLICY,(uint64_t)RR->node->relayPolicy());
if (ctrl == RR->identity.address()) {
if (RR->localNetworkController) {
@ -1050,12 +1049,56 @@ void Network::requestConfiguration()
} else {
outp.append((unsigned char)0,16);
}
RR->node->expectReplyTo(_inboundConfigPacketId = outp.packetId());
_inboundConfigChunks.clear();
outp.compress();
RR->sw->send(outp,true);
}
// Expect replies with this in-re packet ID
_inboundConfigPacketId = outp.packetId();
_inboundConfigChunks.clear();
bool Network::gate(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uint64_t packetId)
{
const uint64_t now = RR->node->now();
Mutex::Lock _l(_lock);
try {
if (_config) {
Membership &m = _membership(peer->address());
const bool allow = m.isAllowedOnNetwork(_config);
if (allow) {
m.sendCredentialsIfNeeded(RR,now,peer->address(),_config,(const Capability *)0);
if (m.shouldLikeMulticasts(now)) {
_announceMulticastGroupsTo(peer->address(),_allMulticastGroups());
m.likingMulticasts(now);
}
} else if (m.recentlyAllowedOnNetwork(_config)&&peer->rateGateRequestCredentials(now)) {
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
outp.append((uint8_t)verb);
outp.append(packetId);
outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE);
outp.append(_id);
RR->sw->send(outp,true);
}
return allow;
}
} catch ( ... ) {
TRACE("gate() check failed for peer %s: unexpected exception",peer->address().toString().c_str());
}
return false;
}
bool Network::gateMulticastGatherReply(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uint64_t packetId)
{
return ( (peer->address() == controller()) || RR->topology->isUpstream(peer->identity()) || gate(peer,verb,packetId) || _config.isAnchor(peer->address()) );
}
bool Network::recentlyAllowedOnNetwork(const SharedPtr<Peer> &peer) const
{
Mutex::Lock _l(_lock);
const Membership *m = _memberships.get(peer->address());
if (m)
return m->recentlyAllowedOnNetwork(_config);
return false;
}
void Network::clean()
@ -1131,7 +1174,22 @@ void Network::learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now)
const unsigned long tmp = (unsigned long)_multicastGroupsBehindMe.size();
_multicastGroupsBehindMe.set(mg,now);
if (tmp != _multicastGroupsBehindMe.size())
_announceMulticastGroups(&mg);
_sendUpdatesToMembers(&mg);
}
int Network::addCredential(const CertificateOfMembership &com)
{
if (com.networkId() != _id)
return -1;
const Address a(com.issuedTo());
Mutex::Lock _l(_lock);
Membership &m = _membership(a);
const int result = m.addCredential(RR,com);
if (result == 0) {
m.sendCredentialsIfNeeded(RR,RR->node->now(),a,_config,(const Capability *)0);
RR->mc->addCredential(com,true);
}
return result;
}
void Network::destroy()
@ -1168,6 +1226,7 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const
ec->status = _status();
ec->type = (_config) ? (_config.isPrivate() ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC) : ZT_NETWORK_TYPE_PRIVATE;
ec->mtu = ZT_IF_MTU;
ec->physicalMtu = ZT_UDP_DEFAULT_PAYLOAD_MTU - (ZT_PACKET_IDX_PAYLOAD + 16);
ec->dhcp = 0;
std::vector<Address> ab(_config.activeBridges());
ec->bridge = ((_config.allowPassiveBridging())||(std::find(ab.begin(),ab.end(),RR->identity.address()) != ab.end())) ? 1 : 0;
@ -1196,40 +1255,25 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const
}
}
bool Network::_isAllowed(const SharedPtr<Peer> &peer) const
{
// Assumes _lock is locked
try {
if (_config) {
const Membership *const m = _memberships.get(peer->address());
if (m)
return m->isAllowedOnNetwork(_config);
}
} catch ( ... ) {
TRACE("isAllowed() check failed for peer %s: unexpected exception",peer->address().toString().c_str());
}
return false;
}
void Network::_announceMulticastGroups(const MulticastGroup *const onlyThis)
void Network::_sendUpdatesToMembers(const MulticastGroup *const newMulticastGroup)
{
// Assumes _lock is locked
const uint64_t now = RR->node->now();
std::vector<MulticastGroup> groups;
if (onlyThis)
groups.push_back(*onlyThis);
if (newMulticastGroup)
groups.push_back(*newMulticastGroup);
else groups = _allMulticastGroups();
if ((onlyThis)||((now - _lastAnnouncedMulticastGroupsUpstream) >= ZT_MULTICAST_ANNOUNCE_PERIOD)) {
if (!onlyThis)
if ((newMulticastGroup)||((now - _lastAnnouncedMulticastGroupsUpstream) >= ZT_MULTICAST_ANNOUNCE_PERIOD)) {
if (!newMulticastGroup)
_lastAnnouncedMulticastGroupsUpstream = now;
// Announce multicast groups to upstream peers (roots, etc.) and also send
// them our COM so that MULTICAST_GATHER can be authenticated properly.
const std::vector<Address> upstreams(RR->topology->upstreamAddresses());
for(std::vector<Address>::const_iterator a(upstreams.begin());a!=upstreams.end();++a) {
if ((_config.isPrivate())&&(_config.com)) {
if (_config.com) {
Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
_config.com.serialize(outp);
outp.append((uint8_t)0x00);
@ -1238,12 +1282,17 @@ void Network::_announceMulticastGroups(const MulticastGroup *const onlyThis)
_announceMulticastGroupsTo(*a,groups);
}
// Announce to controller, which does not need our COM since it obviously
// knows if we are a member. Of course if we already did or are going to
// below then we can skip it here.
// Also announce to controller, and send COM to simplify and generalize behavior even though in theory it does not need it
const Address c(controller());
if ( (std::find(upstreams.begin(),upstreams.end(),c) == upstreams.end()) && (!_memberships.contains(c)) )
if ( (std::find(upstreams.begin(),upstreams.end(),c) == upstreams.end()) && (!_memberships.contains(c)) ) {
if (_config.com) {
Packet outp(c,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
_config.com.serialize(outp);
outp.append((uint8_t)0x00);
RR->sw->send(outp,true);
}
_announceMulticastGroupsTo(c,groups);
}
}
// Make sure that all "network anchors" have Membership records so we will
@ -1251,19 +1300,21 @@ void Network::_announceMulticastGroups(const MulticastGroup *const onlyThis)
// piecemeal on-demand fashion.
const std::vector<Address> anchors(_config.anchors());
for(std::vector<Address>::const_iterator a(anchors.begin());a!=anchors.end();++a)
_memberships[*a];
_membership(*a);
// Send MULTICAST_LIKE(s) to all members of this network
// Send credentials and multicast LIKEs to members, upstreams, and controller
{
Address *a = (Address *)0;
Membership *m = (Membership *)0;
Hashtable<Address,Membership>::Iterator i(_memberships);
while (i.next(a,m)) {
if ((onlyThis)||(m->shouldLikeMulticasts(now))) {
if (!onlyThis)
m->likingMulticasts(now);
if ( (m->recentlyAllowedOnNetwork(_config)) || (std::find(anchors.begin(),anchors.end(),*a) != anchors.end()) ) {
m->sendCredentialsIfNeeded(RR,RR->node->now(),*a,_config,(const Capability *)0);
_announceMulticastGroupsTo(*a,groups);
if ( ((newMulticastGroup)||(m->shouldLikeMulticasts(now))) && (m->isAllowedOnNetwork(_config)) ) {
if (!newMulticastGroup)
m->likingMulticasts(now);
_announceMulticastGroupsTo(*a,groups);
}
}
}
}
@ -1310,15 +1361,7 @@ std::vector<MulticastGroup> Network::_allMulticastGroups() const
Membership &Network::_membership(const Address &a)
{
// assumes _lock is locked
const unsigned long ms = _memberships.size();
Membership &m = _memberships[a];
if (ms != _memberships.size()) {
const uint64_t now = RR->node->now();
m.sendCredentialsIfNeeded(RR,now,a,_config,(const Capability *)0);
_announceMulticastGroupsTo(a,_allMulticastGroups());
m.likingMulticasts(now);
}
return m;
return _memberships[a];
}
} // namespace ZeroTier

View File

@ -48,7 +48,6 @@ namespace ZeroTier {
class RuntimeEnvironment;
class Peer;
class _MulticastAnnounceAll;
/**
* A virtual LAN
@ -56,7 +55,6 @@ class _MulticastAnnounceAll;
class Network : NonCopyable
{
friend class SharedPtr<Network>;
friend class _MulticastAnnounceAll; // internal function object
public:
/**
@ -250,14 +248,25 @@ public:
void requestConfiguration();
/**
* Membership check gate for incoming packets related to this network
*
* @param peer Peer to check
* @param verb Packet verb
* @param packetId Packet ID
* @return True if peer is allowed to communicate on this network
*/
inline bool isAllowed(const SharedPtr<Peer> &peer) const
{
Mutex::Lock _l(_lock);
return _isAllowed(peer);
}
bool gate(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uint64_t packetId);
/**
* Check whether this peer is allowed to provide multicast info for this network
*/
bool gateMulticastGatherReply(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uint64_t packetId);
/**
* @param peer Peer to check
* @return True if peer has recently been a valid member of this network
*/
bool recentlyAllowedOnNetwork(const SharedPtr<Peer> &peer) const;
/**
* Perform cleanup and possibly save state
@ -265,12 +274,12 @@ public:
void clean();
/**
* Announce multicast groups to all members, anchors, etc.
* Push state to members such as multicast group memberships and latest COM (if needed)
*/
inline void announceMulticastGroups()
inline void sendUpdatesToMembers()
{
Mutex::Lock _l(_lock);
_announceMulticastGroups((const MulticastGroup *)0);
_sendUpdatesToMembers((const MulticastGroup *)0);
}
/**
@ -323,9 +332,7 @@ public:
{
Mutex::Lock _l(_lock);
const Address *const br = _remoteBridgeRoutes.get(mac);
if (br)
return *br;
return Address();
return ((br) ? *br : Address());
}
/**
@ -348,13 +355,7 @@ public:
* @param com Certificate of membership
* @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
*/
inline int addCredential(const CertificateOfMembership &com)
{
if (com.networkId() != _id)
return -1;
Mutex::Lock _l(_lock);
return _membership(com.issuedTo()).addCredential(RR,com);
}
int addCredential(const CertificateOfMembership &com);
/**
* @param cap Capability
@ -408,11 +409,11 @@ public:
private:
ZT_VirtualNetworkStatus _status() const;
void _externalConfig(ZT_VirtualNetworkConfig *ec) const; // assumes _lock is locked
bool _isAllowed(const SharedPtr<Peer> &peer) const;
void _announceMulticastGroups(const MulticastGroup *const onlyThis);
bool _gate(const SharedPtr<Peer> &peer);
void _sendUpdatesToMembers(const MulticastGroup *const newMulticastGroup);
void _announceMulticastGroupsTo(const Address &peer,const std::vector<MulticastGroup> &allMulticastGroups);
std::vector<MulticastGroup> _allMulticastGroups() const;
Membership &_membership(const Address &a); // also lazily sends COM and MULTICAST_LIKE(s) if this is a new member
Membership &_membership(const Address &a);
const RuntimeEnvironment *RR;
void *_uPtr;

View File

@ -108,9 +108,13 @@ namespace ZeroTier {
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION "v"
// Protocol version (see Packet.hpp)
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION "pv"
// Software major, minor, revision
// Software vendor
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_VENDOR "vend"
// Software major version
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION "majv"
// Software minor version
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION "minv"
// Software revision
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION "revv"
// Rules engine revision
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV "revr"
@ -123,9 +127,11 @@ namespace ZeroTier {
// Maximum number of tags this node can accept
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS "mt"
// Network join authorization token (if any)
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH_TOKEN "atok"
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH "a"
// Network configuration meta-data flags
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS "f"
// Relay policy for this node
#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_RELAY_POLICY "rp"
// These dictionary keys are short so they don't take up much room.
// By convention we use upper case for binary blobs, but it doesn't really matter.
@ -285,6 +291,19 @@ public:
return r;
}
/**
* @param a Address to check
* @return True if address is an anchor
*/
inline bool isAnchor(const Address &a) const
{
for(unsigned int i=0;i<specialistCount;++i) {
if ((a == specialists[i])&&((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ANCHOR) != 0))
return true;
}
return false;
}
/**
* @param fromPeer Peer attempting to bridge other Ethernet peers onto network
* @return True if this network allows bridging

View File

@ -71,10 +71,14 @@ Node::Node(
_prngStreamPtr(0),
_now(now),
_lastPingCheck(0),
_lastHousekeepingRun(0)
_lastHousekeepingRun(0),
_relayPolicy(ZT_RELAY_POLICY_TRUSTED)
{
_online = false;
memset(_expectingRepliesToBucketPtr,0,sizeof(_expectingRepliesToBucketPtr));
memset(_expectingRepliesTo,0,sizeof(_expectingRepliesTo));
// Use Salsa20 alone as a high-quality non-crypto PRNG
{
char foo[32];
@ -115,6 +119,9 @@ Node::Node(
throw;
}
if (RR->topology->amRoot())
_relayPolicy = ZT_RELAY_POLICY_ALWAYS;
postEvent(ZT_EVENT_UP);
}
@ -128,6 +135,7 @@ Node::~Node()
delete RR->topology;
delete RR->mc;
delete RR->sw;
#ifdef ZT_ENABLE_CLUSTER
delete RR->cluster;
#endif
@ -263,7 +271,7 @@ ZT_ResultCode Node::processBackgroundTasks(uint64_t now,volatile uint64_t *nextB
for(std::vector< std::pair< uint64_t,SharedPtr<Network> > >::const_iterator n(_networks.begin());n!=_networks.end();++n) {
if (((now - n->second->lastConfigUpdate()) >= ZT_NETWORK_AUTOCONF_DELAY)||(!n->second->hasConfig()))
needConfig.push_back(n->second);
n->second->announceMulticastGroups();
n->second->sendUpdatesToMembers();
}
}
for(std::vector< SharedPtr<Network> >::const_iterator n(needConfig.begin());n!=needConfig.end();++n)
@ -316,6 +324,12 @@ ZT_ResultCode Node::processBackgroundTasks(uint64_t now,volatile uint64_t *nextB
return ZT_RESULT_OK;
}
ZT_ResultCode Node::setRelayPolicy(enum ZT_RelayPolicy rp)
{
_relayPolicy = rp;
return ZT_RESULT_OK;
}
ZT_ResultCode Node::join(uint64_t nwid,void *uptr)
{
Mutex::Lock _l(_networks_m);
@ -821,6 +835,15 @@ enum ZT_ResultCode ZT_Node_processBackgroundTasks(ZT_Node *node,uint64_t now,vol
}
}
enum ZT_ResultCode ZT_Node_setRelayPolicy(ZT_Node *node,enum ZT_RelayPolicy rp)
{
try {
return reinterpret_cast<ZeroTier::Node *>(node)->setRelayPolicy(rp);
} catch ( ... ) {
return ZT_RESULT_FATAL_ERROR_INTERNAL;
}
}
enum ZT_ResultCode ZT_Node_join(ZT_Node *node,uint64_t nwid,void *uptr)
{
try {

View File

@ -44,6 +44,10 @@
#define TRACE(f,...) {}
#endif
// Bit mask for "expecting reply" hash
#define ZT_EXPECTING_REPLIES_BUCKET_MASK1 255
#define ZT_EXPECTING_REPLIES_BUCKET_MASK2 31
namespace ZeroTier {
/**
@ -87,6 +91,7 @@ public:
unsigned int frameLength,
volatile uint64_t *nextBackgroundTaskDeadline);
ZT_ResultCode processBackgroundTasks(uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline);
ZT_ResultCode setRelayPolicy(enum ZT_RelayPolicy rp);
ZT_ResultCode join(uint64_t nwid,void *uptr);
ZT_ResultCode leave(uint64_t nwid,void **uptr);
ZT_ResultCode multicastSubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi);
@ -241,6 +246,7 @@ public:
inline int configureVirtualNetworkPort(uint64_t nwid,void **nuptr,ZT_VirtualNetworkConfigOperation op,const ZT_VirtualNetworkConfig *nc) { return _virtualNetworkConfigFunction(reinterpret_cast<ZT_Node *>(this),_uPtr,nwid,nuptr,op,nc); }
inline bool online() const throw() { return _online; }
inline ZT_RelayPolicy relayPolicy() const { return _relayPolicy; }
#ifdef ZT_TRACE
void postTrace(const char *module,unsigned int line,const char *fmt,...);
@ -250,6 +256,33 @@ public:
void postCircuitTestReport(const ZT_CircuitTestReport *report);
void setTrustedPaths(const struct sockaddr_storage *networks,const uint64_t *ids,unsigned int count);
/**
* Register that we are expecting a reply to a packet ID
*
* @param packetId Packet ID to expect reply to
*/
inline void expectReplyTo(const uint64_t packetId)
{
const unsigned long bucket = (unsigned long)(packetId & ZT_EXPECTING_REPLIES_BUCKET_MASK1);
_expectingRepliesTo[bucket][_expectingRepliesToBucketPtr[bucket]++ & ZT_EXPECTING_REPLIES_BUCKET_MASK2] = packetId;
}
/**
* Check whether a given packet ID is something we are expecting a reply to
*
* @param packetId Packet ID to check
* @return True if we're expecting a reply
*/
inline bool expectingReplyTo(const uint64_t packetId) const
{
const unsigned long bucket = (unsigned long)(packetId & ZT_EXPECTING_REPLIES_BUCKET_MASK1);
for(unsigned long i=0;i<=ZT_EXPECTING_REPLIES_BUCKET_MASK2;++i) {
if (_expectingRepliesTo[bucket][i] == packetId)
return true;
}
return false;
}
private:
inline SharedPtr<Network> _network(uint64_t nwid) const
{
@ -266,6 +299,9 @@ private:
void *_uPtr; // _uptr (lower case) is reserved in Visual Studio :P
uint8_t _expectingRepliesToBucketPtr[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1];
uint64_t _expectingRepliesTo[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1][ZT_EXPECTING_REPLIES_BUCKET_MASK2 + 1];
ZT_DataStoreGetFunction _dataStoreGetFunction;
ZT_DataStorePutFunction _dataStorePutFunction;
ZT_WirePacketSendFunction _wirePacketSendFunction;
@ -292,6 +328,7 @@ private:
uint64_t _now;
uint64_t _lastPingCheck;
uint64_t _lastHousekeepingRun;
ZT_RelayPolicy _relayPolicy;
bool _online;
};

View File

@ -91,6 +91,7 @@ void OutboundMulticast::sendOnly(const RuntimeEnvironment *RR,const Address &toA
//TRACE(">>MC %.16llx -> %s",(unsigned long long)this,toAddr.toString().c_str());
_packet.newInitializationVector();
_packet.setDestination(toAddr2);
RR->node->expectReplyTo(_packet.packetId());
RR->sw->send(_packet,true);
}
}

View File

@ -62,6 +62,7 @@ const char *Packet::errorString(ErrorCode e)
case ERROR_OBJ_NOT_FOUND: return "OBJECT_NOT_FOUND";
case ERROR_IDENTITY_COLLISION: return "IDENTITY_COLLISION";
case ERROR_UNSUPPORTED_OPERATION: return "UNSUPPORTED_OPERATION";
case ERROR_NEED_MEMBERSHIP_CERTIFICATE: return "NEED_MEMBERSHIP_CERTIFICATE";
case ERROR_NETWORK_ACCESS_DENIED_: return "NETWORK_ACCESS_DENIED";
case ERROR_UNWANTED_MULTICAST: return "UNWANTED_MULTICAST";
}

View File

@ -965,7 +965,7 @@ public:
* <[2] 16-bit reporter OS/platform or 0 if not specified>
* <[2] 16-bit reporter architecture or 0 if not specified>
* <[2] 16-bit error code (set to 0, currently unused)>
* <[8] 64-bit report flags (set to 0, currently unused)>
* <[8] 64-bit report flags>
* <[8] 64-bit packet ID of received CIRCUIT_TEST packet>
* <[5] upstream ZeroTier address from which CIRCUIT_TEST was received>
* <[1] 8-bit packet hop count of received CIRCUIT_TEST>
@ -980,6 +980,9 @@ public:
* <[5] ZeroTier address of next hop>
* <[...] current best direct path address, if any, 0 if none>
*
* Report flags:
* 0x1 - Upstream peer in circuit test path allowed in path (e.g. network COM valid)
*
* Circuit test reports can be sent by hops in a circuit test to report
* back results. They should include information about the sender as well
* as about the paths to which next hops are being sent.
@ -1067,6 +1070,9 @@ public:
/* Verb or use case not supported/enabled by this node */
ERROR_UNSUPPORTED_OPERATION = 0x05,
/* Network membership certificate update needed */
ERROR_NEED_MEMBERSHIP_CERTIFICATE = 0x06,
/* Tried to join network, but you're not a member */
ERROR_NETWORK_ACCESS_DENIED_ = 0x07, /* extra _ at end to avoid Windows name conflict */

View File

@ -104,6 +104,7 @@ public:
Path() :
_lastOut(0),
_lastIn(0),
_lastTrustEstablishedPacketReceived(0),
_addr(),
_localAddress(),
_ipScope(InetAddress::IP_SCOPE_NONE)
@ -113,6 +114,7 @@ public:
Path(const InetAddress &localAddress,const InetAddress &addr) :
_lastOut(0),
_lastIn(0),
_lastTrustEstablishedPacketReceived(0),
_addr(addr),
_localAddress(localAddress),
_ipScope(addr.ipScope())
@ -126,6 +128,11 @@ public:
*/
inline void received(const uint64_t t) { _lastIn = t; }
/**
* Set time last trusted packet was received (done in Peer::received())
*/
inline void trustedPacketReceived(const uint64_t t) { _lastTrustEstablishedPacketReceived = t; }
/**
* Send a packet via this path (last out time is also updated)
*
@ -159,6 +166,11 @@ public:
*/
inline InetAddress::IpScope ipScope() const { return _ipScope; }
/**
* @return True if path has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms
*/
inline bool trustEstablished(const uint64_t now) const { return ((now - _lastTrustEstablishedPacketReceived) < ZT_TRUST_EXPIRATION); }
/**
* @return Preference rank, higher == better
*/
@ -232,6 +244,7 @@ public:
private:
uint64_t _lastOut;
uint64_t _lastIn;
uint64_t _lastTrustEstablishedPacketReceived;
InetAddress _addr;
InetAddress _localAddress;
InetAddress::IpScope _ipScope; // memoize this since it's a computed value checked often

View File

@ -47,6 +47,12 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident
_lastMulticastFrame(0),
_lastDirectPathPushSent(0),
_lastDirectPathPushReceive(0),
_lastCredentialRequestSent(0),
_lastWhoisRequestReceived(0),
_lastEchoRequestReceived(0),
_lastComRequestReceived(0),
_lastCredentialsReceived(0),
_lastTrustEstablishedPacketReceived(0),
RR(renv),
_remoteClusterOptimal4(0),
_vProto(0),
@ -56,7 +62,8 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident
_id(peerIdentity),
_numPaths(0),
_latency(0),
_directPathPushCutoffCount(0)
_directPathPushCutoffCount(0),
_credentialsCutoffCount(0)
{
memset(_remoteClusterOptimal6,0,sizeof(_remoteClusterOptimal6));
if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH))
@ -126,6 +133,11 @@ void Peer::received(
else if (verb == Packet::VERB_MULTICAST_FRAME)
_lastMulticastFrame = now;
if (trustEstablished) {
_lastTrustEstablishedPacketReceived = now;
path->trustedPacketReceived(now);
}
if (hops == 0) {
bool pathIsConfirmed = false;
{
@ -194,7 +206,80 @@ void Peer::received(
}
} else if (trustEstablished) {
// Send PUSH_DIRECT_PATHS if hops>0 (relayed) and we have a trust relationship (common network membership)
_pushDirectPaths(path,now);
#ifdef ZT_ENABLE_CLUSTER
// Cluster mode disables normal PUSH_DIRECT_PATHS in favor of cluster-based peer redirection
const bool haveCluster = (RR->cluster);
#else
const bool haveCluster = false;
#endif
if ( ((now - _lastDirectPathPushSent) >= ZT_DIRECT_PATH_PUSH_INTERVAL) && (!haveCluster) ) {
_lastDirectPathPushSent = now;
std::vector<InetAddress> pathsToPush;
std::vector<InetAddress> dps(RR->node->directPaths());
for(std::vector<InetAddress>::const_iterator i(dps.begin());i!=dps.end();++i)
pathsToPush.push_back(*i);
std::vector<InetAddress> sym(RR->sa->getSymmetricNatPredictions());
for(unsigned long i=0,added=0;i<sym.size();++i) {
InetAddress tmp(sym[(unsigned long)RR->node->prng() % sym.size()]);
if (std::find(pathsToPush.begin(),pathsToPush.end(),tmp) == pathsToPush.end()) {
pathsToPush.push_back(tmp);
if (++added >= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY)
break;
}
}
if (pathsToPush.size() > 0) {
#ifdef ZT_TRACE
std::string ps;
for(std::vector<InetAddress>::const_iterator p(pathsToPush.begin());p!=pathsToPush.end();++p) {
if (ps.length() > 0)
ps.push_back(',');
ps.append(p->toString());
}
TRACE("pushing %u direct paths to %s: %s",(unsigned int)pathsToPush.size(),_id.address().toString().c_str(),ps.c_str());
#endif
std::vector<InetAddress>::const_iterator p(pathsToPush.begin());
while (p != pathsToPush.end()) {
Packet outp(_id.address(),RR->identity.address(),Packet::VERB_PUSH_DIRECT_PATHS);
outp.addSize(2); // leave room for count
unsigned int count = 0;
while ((p != pathsToPush.end())&&((outp.size() + 24) < 1200)) {
uint8_t addressType = 4;
switch(p->ss_family) {
case AF_INET:
break;
case AF_INET6:
addressType = 6;
break;
default: // we currently only push IP addresses
++p;
continue;
}
outp.append((uint8_t)0); // no flags
outp.append((uint16_t)0); // no extensions
outp.append(addressType);
outp.append((uint8_t)((addressType == 4) ? 6 : 18));
outp.append(p->rawIpData(),((addressType == 4) ? 4 : 16));
outp.append((uint16_t)p->port());
++count;
++p;
}
if (count) {
outp.setAt(ZT_PACKET_IDX_PAYLOAD,(uint16_t)count);
outp.armor(_key,true);
path->send(RR,outp.data(),outp.size(),now);
}
}
}
}
}
}
@ -266,6 +351,7 @@ void Peer::sendHELLO(const InetAddress &localAddr,const InetAddress &atAddress,u
atAddress.serialize(outp);
outp.append((uint64_t)RR->topology->worldId());
outp.append((uint64_t)RR->topology->worldTimestamp());
RR->node->expectReplyTo(outp.packetId());
outp.armor(_key,false); // HELLO is sent in the clear
RR->node->putPacket(localAddr,atAddress,outp.data(),outp.size());
}
@ -274,6 +360,7 @@ void Peer::attemptToContactAt(const InetAddress &localAddr,const InetAddress &at
{
if ( (_vProto >= 5) && ( !((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0)) ) ) {
Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO);
RR->node->expectReplyTo(outp.packetId());
outp.armor(_key,true);
RR->node->putPacket(localAddr,atAddress,outp.data(),outp.size());
} else {
@ -366,86 +453,4 @@ void Peer::getBestActiveAddresses(uint64_t now,InetAddress &v4,InetAddress &v6)
v6 = _paths[bestp6].path->address();
}
bool Peer::_pushDirectPaths(const SharedPtr<Path> &path,uint64_t now)
{
#ifdef ZT_ENABLE_CLUSTER
// Cluster mode disables normal PUSH_DIRECT_PATHS in favor of cluster-based peer redirection
if (RR->cluster)
return false;
#endif
if ((now - _lastDirectPathPushSent) < ZT_DIRECT_PATH_PUSH_INTERVAL)
return false;
else _lastDirectPathPushSent = now;
std::vector<InetAddress> pathsToPush;
std::vector<InetAddress> dps(RR->node->directPaths());
for(std::vector<InetAddress>::const_iterator i(dps.begin());i!=dps.end();++i)
pathsToPush.push_back(*i);
std::vector<InetAddress> sym(RR->sa->getSymmetricNatPredictions());
for(unsigned long i=0,added=0;i<sym.size();++i) {
InetAddress tmp(sym[(unsigned long)RR->node->prng() % sym.size()]);
if (std::find(pathsToPush.begin(),pathsToPush.end(),tmp) == pathsToPush.end()) {
pathsToPush.push_back(tmp);
if (++added >= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY)
break;
}
}
if (pathsToPush.empty())
return false;
#ifdef ZT_TRACE
{
std::string ps;
for(std::vector<InetAddress>::const_iterator p(pathsToPush.begin());p!=pathsToPush.end();++p) {
if (ps.length() > 0)
ps.push_back(',');
ps.append(p->toString());
}
TRACE("pushing %u direct paths to %s: %s",(unsigned int)pathsToPush.size(),_id.address().toString().c_str(),ps.c_str());
}
#endif
std::vector<InetAddress>::const_iterator p(pathsToPush.begin());
while (p != pathsToPush.end()) {
Packet outp(_id.address(),RR->identity.address(),Packet::VERB_PUSH_DIRECT_PATHS);
outp.addSize(2); // leave room for count
unsigned int count = 0;
while ((p != pathsToPush.end())&&((outp.size() + 24) < 1200)) {
uint8_t addressType = 4;
switch(p->ss_family) {
case AF_INET:
break;
case AF_INET6:
addressType = 6;
break;
default: // we currently only push IP addresses
++p;
continue;
}
outp.append((uint8_t)0); // no flags
outp.append((uint16_t)0); // no extensions
outp.append(addressType);
outp.append((uint8_t)((addressType == 4) ? 6 : 18));
outp.append(p->rawIpData(),((addressType == 4) ? 4 : 16));
outp.append((uint16_t)p->port());
++count;
++p;
}
if (count) {
outp.setAt(ZT_PACKET_IDX_PAYLOAD,(uint16_t)count);
outp.armor(_key,true);
path->send(RR,outp.data(),outp.size(),now);
}
}
return true;
}
} // namespace ZeroTier

View File

@ -312,7 +312,7 @@ public:
/**
* @return 256-bit secret symmetric encryption key
*/
inline const unsigned char *key() const throw() { return _key; }
inline const unsigned char *key() const { return _key; }
/**
* Set the currently known remote version of this peer's client
@ -330,25 +330,22 @@ public:
_vRevision = (uint16_t)vrev;
}
inline unsigned int remoteVersionProtocol() const throw() { return _vProto; }
inline unsigned int remoteVersionMajor() const throw() { return _vMajor; }
inline unsigned int remoteVersionMinor() const throw() { return _vMinor; }
inline unsigned int remoteVersionRevision() const throw() { return _vRevision; }
inline unsigned int remoteVersionProtocol() const { return _vProto; }
inline unsigned int remoteVersionMajor() const { return _vMajor; }
inline unsigned int remoteVersionMinor() const { return _vMinor; }
inline unsigned int remoteVersionRevision() const { return _vRevision; }
inline bool remoteVersionKnown() const throw() { return ((_vMajor > 0)||(_vMinor > 0)||(_vRevision > 0)); }
inline bool remoteVersionKnown() const { return ((_vMajor > 0)||(_vMinor > 0)||(_vRevision > 0)); }
/**
* Update direct path push stats and return true if we should respond
*
* This is a circuit breaker to make VERB_PUSH_DIRECT_PATHS not particularly
* useful as a DDOS amplification attack vector. Otherwise a malicious peer
* could send loads of these and cause others to bombard arbitrary IPs with
* traffic.
*
* @param now Current time
* @return True if we should respond
* @return True if peer has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms
*/
inline bool shouldRespondToDirectPathPush(const uint64_t now)
inline bool trustEstablished(const uint64_t now) const { return ((now - _lastTrustEstablishedPacketReceived) < ZT_TRUST_EXPIRATION); }
/**
* Rate limit gate for VERB_PUSH_DIRECT_PATHS
*/
inline bool rateGatePushDirectPaths(const uint64_t now)
{
if ((now - _lastDirectPathPushReceive) <= ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME)
++_directPathPushCutoffCount;
@ -357,6 +354,66 @@ public:
return (_directPathPushCutoffCount < ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT);
}
/**
* Rate limit gate for VERB_NETWORK_CREDENTIALS
*/
inline bool rateGateCredentialsReceived(const uint64_t now)
{
if ((now - _lastCredentialsReceived) <= ZT_PEER_CREDENTIALS_CUTOFF_TIME)
++_credentialsCutoffCount;
else _credentialsCutoffCount = 0;
_lastCredentialsReceived = now;
return (_directPathPushCutoffCount < ZT_PEER_CREDEITIALS_CUTOFF_LIMIT);
}
/**
* Rate limit gate for sending of ERROR_NEED_MEMBERSHIP_CERTIFICATE
*/
inline bool rateGateRequestCredentials(const uint64_t now)
{
if ((now - _lastCredentialRequestSent) >= ZT_PEER_GENERAL_RATE_LIMIT) {
_lastCredentialRequestSent = now;
return true;
}
return false;
}
/**
* Rate limit gate for inbound WHOIS requests
*/
inline bool rateGateInboundWhoisRequest(const uint64_t now)
{
if ((now - _lastWhoisRequestReceived) >= ZT_PEER_GENERAL_RATE_LIMIT) {
_lastWhoisRequestReceived = now;
return true;
}
return false;
}
/**
* Rate limit gate for inbound ECHO requests
*/
inline bool rateGateEchoRequest(const uint64_t now)
{
if ((now - _lastEchoRequestReceived) >= ZT_PEER_GENERAL_RATE_LIMIT) {
_lastEchoRequestReceived = now;
return true;
}
return false;
}
/**
* Rate gate requests for network COM
*/
inline bool rateGateComRequest(const uint64_t now)
{
if ((now - _lastComRequestReceived) >= ZT_PEER_GENERAL_RATE_LIMIT) {
_lastComRequestReceived = now;
return true;
}
return false;
}
/**
* Find a common set of addresses by which two peers can link, if any
*
@ -378,8 +435,6 @@ public:
}
private:
bool _pushDirectPaths(const SharedPtr<Path> &path,uint64_t now);
inline uint64_t _pathScore(const unsigned int p,const uint64_t now) const
{
uint64_t s = ZT_PEER_PING_PERIOD + _paths[p].lastReceive + (uint64_t)(_paths[p].path->preferenceRank() * (ZT_PEER_PING_PERIOD / ZT_PATH_MAX_PREFERENCE_RANK));
@ -415,6 +470,12 @@ private:
uint64_t _lastMulticastFrame;
uint64_t _lastDirectPathPushSent;
uint64_t _lastDirectPathPushReceive;
uint64_t _lastCredentialRequestSent;
uint64_t _lastWhoisRequestReceived;
uint64_t _lastEchoRequestReceived;
uint64_t _lastComRequestReceived;
uint64_t _lastCredentialsReceived;
uint64_t _lastTrustEstablishedPacketReceived;
const RuntimeEnvironment *RR;
uint32_t _remoteClusterOptimal4;
uint16_t _vProto;
@ -433,6 +494,7 @@ private:
unsigned int _numPaths;
unsigned int _latency;
unsigned int _directPathPushCutoffCount;
unsigned int _credentialsCutoffCount;
AtomicCounter __refCount;
};

View File

@ -105,7 +105,18 @@ void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &from
const Address destination(fragment.destination());
if (destination != RR->identity.address()) {
// Fragment is not for us, so try to relay it
switch(RR->node->relayPolicy()) {
case ZT_RELAY_POLICY_ALWAYS:
break;
case ZT_RELAY_POLICY_TRUSTED:
if (!path->trustEstablished(now))
return;
break;
// case ZT_RELAY_POLICY_NEVER:
default:
return;
}
if (fragment.hops() < ZT_RELAY_MAX_HOPS) {
fragment.incrementHops();
@ -203,9 +214,20 @@ void Switch::onRemotePacket(const InetAddress &localAddr,const InetAddress &from
//TRACE("<< %.16llx %s -> %s (size: %u)",(unsigned long long)packet->packetId(),source.toString().c_str(),destination.toString().c_str(),packet->size());
if (destination != RR->identity.address()) {
switch(RR->node->relayPolicy()) {
case ZT_RELAY_POLICY_ALWAYS:
break;
case ZT_RELAY_POLICY_TRUSTED:
if (!path->trustEstablished(now))
return;
break;
// case ZT_RELAY_POLICY_NEVER:
default:
return;
}
Packet packet(data,len);
// Packet is not for us, so try to relay it
if (packet.hops() < ZT_RELAY_MAX_HOPS) {
packet.incrementHops();
@ -327,6 +349,11 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c
}
if (to.isMulticast()) {
if (network->config().multicastLimit == 0) {
TRACE("%.16llx: dropped multicast: not allowed on network",network->id());
return;
}
// Destination is a multicast address (including broadcast)
MulticastGroup mg(to,0);
@ -734,13 +761,12 @@ unsigned long Switch::doTimerTasks(uint64_t now)
Address Switch::_sendWhoisRequest(const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted)
{
SharedPtr<Peer> root(RR->topology->getBestRoot(peersAlreadyConsulted,numPeersAlreadyConsulted,false));
if (root) {
Packet outp(root->address(),RR->identity.address(),Packet::VERB_WHOIS);
SharedPtr<Peer> upstream(RR->topology->getBestRoot(peersAlreadyConsulted,numPeersAlreadyConsulted,false));
if (upstream) {
Packet outp(upstream->address(),RR->identity.address(),Packet::VERB_WHOIS);
addr.appendTo(outp);
outp.armor(root->key(),true);
if (root->sendDirect(outp.data(),outp.size(),RR->node->now(),true))
return root->address();
RR->node->expectReplyTo(outp.packetId());
send(outp,true);
}
return Address();
}
@ -760,11 +786,8 @@ bool Switch::_trySend(const Packet &packet,bool encrypt)
SharedPtr<Path> viaPath(peer->getBestPath(now,false));
if ( (viaPath) && (!viaPath->alive(now)) && (!RR->topology->isRoot(peer->identity())) ) {
if ((now - viaPath->lastOut()) > std::max((now - viaPath->lastIn()) >> 2,(uint64_t)ZT_PATH_MIN_REACTIVATE_INTERVAL)) {
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ECHO);
outp.armor(peer->key(),true);
viaPath->send(RR,outp.data(),outp.size(),now);
}
if ((now - viaPath->lastOut()) > std::max((now - viaPath->lastIn()) * 4,(uint64_t)ZT_PATH_MIN_REACTIVATE_INTERVAL))
peer->attemptToContactAt(viaPath->localAddress(),viaPath->address(),now);
viaPath.zero();
}
if (!viaPath) {

View File

@ -150,7 +150,7 @@ public:
if (fullSignatureCheck) {
Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH> tmp;
update.serialize(tmp,true);
return C25519::verify(_updateSigningKey,tmp.data(),tmp.size(),update._signature);
return C25519::verify(_updatesMustBeSignedBy,tmp.data(),tmp.size(),update._signature);
} else return true;
}
return false;
@ -169,7 +169,7 @@ public:
b.append((uint8_t)0x01);
b.append((uint64_t)_id);
b.append((uint64_t)_ts);
b.append(_updateSigningKey.data,ZT_C25519_PUBLIC_KEY_LEN);
b.append(_updatesMustBeSignedBy.data,ZT_C25519_PUBLIC_KEY_LEN);
if (!forSign)
b.append(_signature.data,ZT_C25519_SIGNATURE_LEN);
b.append((uint8_t)_roots.size());
@ -195,7 +195,7 @@ public:
_id = b.template at<uint64_t>(p); p += 8;
_ts = b.template at<uint64_t>(p); p += 8;
memcpy(_updateSigningKey.data,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); p += ZT_C25519_PUBLIC_KEY_LEN;
memcpy(_updatesMustBeSignedBy.data,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); p += ZT_C25519_PUBLIC_KEY_LEN;
memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN;
unsigned int numRoots = b[p++];
if (numRoots > ZT_WORLD_MAX_ROOTS)
@ -216,13 +216,13 @@ public:
return (p - startAt);
}
inline bool operator==(const World &w) const throw() { return ((_id == w._id)&&(_ts == w._ts)&&(_updateSigningKey == w._updateSigningKey)&&(_signature == w._signature)&&(_roots == w._roots)); }
inline bool operator==(const World &w) const throw() { return ((_id == w._id)&&(_ts == w._ts)&&(_updatesMustBeSignedBy == w._updatesMustBeSignedBy)&&(_signature == w._signature)&&(_roots == w._roots)); }
inline bool operator!=(const World &w) const throw() { return (!(*this == w)); }
protected:
uint64_t _id;
uint64_t _ts;
C25519::Public _updateSigningKey;
C25519::Public _updatesMustBeSignedBy;
C25519::Signature _signature;
std::vector<Root> _roots;
};

View File

@ -69,23 +69,28 @@ static void _forkTarget(const InetAddress &t,InetAddress &left,InetAddress &righ
{
const unsigned int bits = t.netmaskBits() + 1;
left = t;
if ((t.ss_family == AF_INET)&&(bits <= 32)) {
left.setPort(bits);
right = t;
reinterpret_cast<struct sockaddr_in *>(&right)->sin_addr.s_addr ^= Utils::hton((uint32_t)(1 << (32 - bits)));
right.setPort(bits);
} else if ((t.ss_family == AF_INET6)&&(bits <= 128)) {
left.setPort(bits);
right = t;
uint8_t *b = reinterpret_cast<uint8_t *>(reinterpret_cast<struct sockaddr_in6 *>(&right)->sin6_addr.s6_addr);
b[bits / 8] ^= 1 << (8 - (bits % 8));
right.setPort(bits);
if (t.ss_family == AF_INET) {
if (bits <= 32) {
left.setPort(bits);
right = t;
reinterpret_cast<struct sockaddr_in *>(&right)->sin_addr.s_addr ^= Utils::hton((uint32_t)(1 << (32 - bits)));
right.setPort(bits);
} else {
right.zero();
}
} else if (t.ss_family == AF_INET6) {
if (bits <= 128) {
left.setPort(bits);
right = t;
uint8_t *b = reinterpret_cast<uint8_t *>(reinterpret_cast<struct sockaddr_in6 *>(&right)->sin6_addr.s6_addr);
b[bits / 8] ^= 1 << (8 - (bits % 8));
right.setPort(bits);
} else {
right.zero();
}
}
}
#ifdef __BSD__ // ------------------------------------------------------------
#define ZT_ROUTING_SUPPORT_FOUND 1
struct _RTE
{
InetAddress target;
@ -95,6 +100,9 @@ struct _RTE
bool ifscope;
};
#ifdef __BSD__ // ------------------------------------------------------------
#define ZT_ROUTING_SUPPORT_FOUND 1
static std::vector<_RTE> _getRTEs(const InetAddress &target,bool contains)
{
std::vector<_RTE> rtes;
@ -232,6 +240,7 @@ static std::vector<_RTE> _getRTEs(const InetAddress &target,bool contains)
static void _routeCmd(const char *op,const InetAddress &target,const InetAddress &via,const char *ifscope,const char *localInterface)
{
//printf("route %s %s %s %s %s\n",op,target.toString().c_str(),(via) ? via.toString().c_str() : "(null)",(ifscope) ? ifscope : "(null)",(localInterface) ? localInterface : "(null)");
long p = (long)fork();
if (p > 0) {
int exitcode = -1;
@ -369,145 +378,123 @@ bool ManagedRoute::sync()
return false;
#endif
if ((_target.isDefaultRoute())||((_target.ss_family == AF_INET)&&(_target.netmaskBits() < 32))) {
/* In ZeroTier we create two more specific routes for every one route. We
* do this for default routes and IPv4 routes other than /32s. If there
* is a pre-existing system route that this route will override, we create
* two more specific interface-bound shadow routes for it.
*
* This means that ZeroTier can *itself* continue communicating over
* whatever physical routes might be present while simultaneously
* overriding them for general system traffic. This is mostly for
* "full tunnel" VPN modes of operation, but might be useful for
* virtualizing physical networks in a hybrid design as well. */
// Generate two more specific routes than target with one extra bit
InetAddress leftt,rightt;
_forkTarget(_target,leftt,rightt);
// Generate two more specific routes than target with one extra bit
InetAddress leftt,rightt;
_forkTarget(_target,leftt,rightt);
#ifdef __BSD__ // ------------------------------------------------------------
// Find lowest metric system route that this route should override (if any)
InetAddress newSystemVia;
char newSystemDevice[128];
newSystemDevice[0] = (char)0;
int systemMetric = 9999999;
std::vector<_RTE> rtes(_getRTEs(_target,false));
// Find lowest metric system route that this route should override (if any)
InetAddress newSystemVia;
char newSystemDevice[128];
newSystemDevice[0] = (char)0;
int systemMetric = 9999999;
std::vector<_RTE> rtes(_getRTEs(_target,false));
for(std::vector<_RTE>::iterator r(rtes.begin());r!=rtes.end();++r) {
if (r->via) {
if ( ((!newSystemVia)||(r->metric < systemMetric)) && (strcmp(r->device,_device) != 0) ) {
newSystemVia = r->via;
Utils::scopy(newSystemDevice,sizeof(newSystemDevice),r->device);
systemMetric = r->metric;
}
}
}
// Get device corresponding to route if we don't have that already
if ((newSystemVia)&&(!newSystemDevice[0])) {
rtes = _getRTEs(newSystemVia,true);
for(std::vector<_RTE>::iterator r(rtes.begin());r!=rtes.end();++r) {
if (r->via) {
if ((!newSystemVia)||(r->metric < systemMetric)) {
newSystemVia = r->via;
Utils::scopy(newSystemDevice,sizeof(newSystemDevice),r->device);
systemMetric = r->metric;
}
}
}
if ((newSystemVia)&&(!newSystemDevice[0])) {
rtes = _getRTEs(newSystemVia,true);
for(std::vector<_RTE>::iterator r(rtes.begin());r!=rtes.end();++r) {
if (r->device[0]) {
Utils::scopy(newSystemDevice,sizeof(newSystemDevice),r->device);
break;
}
if ( (r->device[0]) && (strcmp(r->device,_device) != 0) ) {
Utils::scopy(newSystemDevice,sizeof(newSystemDevice),r->device);
break;
}
}
}
if (!newSystemDevice[0])
newSystemVia.zero();
// Shadow system route if it exists, also delete any obsolete shadows
// and replace them with the new state. sync() is called periodically to
// allow us to do that if underlying connectivity changes.
if ( ((_systemVia != newSystemVia)||(strcmp(_systemDevice,newSystemDevice))) && (strcmp(_device,newSystemDevice)) ) {
if ((_systemVia)&&(_systemDevice[0])) {
_routeCmd("delete",leftt,_systemVia,_systemDevice,(const char *)0);
// Shadow system route if it exists, also delete any obsolete shadows
// and replace them with the new state. sync() is called periodically to
// allow us to do that if underlying connectivity changes.
if ((_systemVia != newSystemVia)||(strcmp(_systemDevice,newSystemDevice) != 0)) {
if (_systemVia) {
_routeCmd("delete",leftt,_systemVia,_systemDevice,(const char *)0);
if (rightt)
_routeCmd("delete",rightt,_systemVia,_systemDevice,(const char *)0);
}
}
_systemVia = newSystemVia;
Utils::scopy(_systemDevice,sizeof(_systemDevice),newSystemDevice);
_systemVia = newSystemVia;
Utils::scopy(_systemDevice,sizeof(_systemDevice),newSystemDevice);
if ((_systemVia)&&(_systemDevice[0])) {
_routeCmd("add",leftt,_systemVia,_systemDevice,(const char *)0);
_routeCmd("change",leftt,_systemVia,_systemDevice,(const char *)0);
if (_systemVia) {
_routeCmd("add",leftt,_systemVia,_systemDevice,(const char *)0);
_routeCmd("change",leftt,_systemVia,_systemDevice,(const char *)0);
if (rightt) {
_routeCmd("add",rightt,_systemVia,_systemDevice,(const char *)0);
_routeCmd("change",rightt,_systemVia,_systemDevice,(const char *)0);
}
}
// Apply overriding non-device-scoped routes
if (!_applied) {
if (_via) {
_routeCmd("add",leftt,_via,(const char *)0,(const char *)0);
_routeCmd("change",leftt,_via,(const char *)0,(const char *)0);
_routeCmd("add",rightt,_via,(const char *)0,(const char *)0);
_routeCmd("change",rightt,_via,(const char *)0,(const char *)0);
} else if (_device[0]) {
_routeCmd("add",leftt,_via,(const char *)0,_device);
_routeCmd("change",leftt,_via,(const char *)0,_device);
_routeCmd("add",rightt,_via,(const char *)0,_device);
_routeCmd("change",rightt,_via,(const char *)0,_device);
}
_applied = true;
}
#endif // __BSD__ ------------------------------------------------------------
#ifdef __LINUX__ // ----------------------------------------------------------
if (!_applied) {
_routeCmd("replace",leftt,_via,(_via) ? _device : (const char *)0);
_routeCmd("replace",rightt,_via,(_via) ? _device : (const char *)0);
_applied = true;
}
#endif // __LINUX__ ----------------------------------------------------------
#ifdef __WINDOWS__ // --------------------------------------------------------
if (!_applied) {
_winRoute(false,interfaceLuid,interfaceIndex,leftt,_via);
_winRoute(false,interfaceLuid,interfaceIndex,rightt,_via);
_applied = true;
}
#endif // __WINDOWS__ --------------------------------------------------------
} else {
#ifdef __BSD__ // ------------------------------------------------------------
if (!_applied) {
if (_via) {
_routeCmd("add",_target,_via,(const char *)0,(const char *)0);
_routeCmd("change",_target,_via,(const char *)0,(const char *)0);
} else if (_device[0]) {
_routeCmd("add",_target,_via,(const char *)0,_device);
_routeCmd("change",_target,_via,(const char *)0,_device);
}
_applied = true;
}
#endif // __BSD__ ------------------------------------------------------------
#ifdef __LINUX__ // ----------------------------------------------------------
if (!_applied) {
_routeCmd("replace",_target,_via,(_via) ? _device : (const char *)0);
_applied = true;
}
#endif // __LINUX__ ----------------------------------------------------------
#ifdef __WINDOWS__ // --------------------------------------------------------
if (!_applied) {
_winRoute(false,interfaceLuid,interfaceIndex,_target,_via);
_applied = true;
}
#endif // __WINDOWS__ --------------------------------------------------------
}
if (!_applied.count(leftt)) {
_applied[leftt] = false; // not ifscoped
_routeCmd("add",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device);
_routeCmd("change",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device);
}
if ((rightt)&&(!_applied.count(rightt))) {
_applied[rightt] = false; // not ifscoped
_routeCmd("add",rightt,_via,(const char *)0,(_via) ? (const char *)0 : _device);
_routeCmd("change",rightt,_via,(const char *)0,(_via) ? (const char *)0 : _device);
}
// Create a device-bound default target if there is none in the system. This
// is to allow e.g. IPv6 default route to work even if there is no native
// IPv6 on your LAN.
/*
if (_target.isDefaultRoute()) {
if (_systemVia) {
if (_applied.count(_target)) {
_applied.erase(_target);
_routeCmd("delete",_target,_via,_device,(_via) ? (const char *)0 : _device);
}
} else {
if (!_applied.count(_target)) {
_applied[_target] = true; // ifscoped
_routeCmd("add",_target,_via,_device,(_via) ? (const char *)0 : _device);
_routeCmd("change",_target,_via,_device,(_via) ? (const char *)0 : _device);
}
}
}
*/
#endif // __BSD__ ------------------------------------------------------------
#ifdef __LINUX__ // ----------------------------------------------------------
if (!_applied.count(leftt)) {
_applied[leftt] = false; // boolean unused
_routeCmd("replace",leftt,_via,(_via) ? (const char *)0 : _device);
}
if ((rightt)&&(!_applied.count(rightt))) {
_applied[rightt] = false; // boolean unused
_routeCmd("replace",rightt,_via,(_via) ? (const char *)0 : _device);
}
#endif // __LINUX__ ----------------------------------------------------------
#ifdef __WINDOWS__ // --------------------------------------------------------
if (!_applied.count(leftt)) {
_applied[leftt] = false; // boolean unused
_winRoute(false,interfaceLuid,interfaceIndex,leftt,_via);
}
if ((rightt)&&(!_applied.count(rightt))) {
_applied[rightt] = false; // boolean unused
_winRoute(false,interfaceLuid,interfaceIndex,rightt,_via);
}
#endif // __WINDOWS__ --------------------------------------------------------
return true;
}
@ -521,66 +508,28 @@ void ManagedRoute::remove()
return;
#endif
if (_applied) {
if ((_target.isDefaultRoute())||((_target.ss_family == AF_INET)&&(_target.netmaskBits() < 32))) {
InetAddress leftt,rightt;
_forkTarget(_target,leftt,rightt);
#ifdef __BSD__
if (_systemVia) {
InetAddress leftt,rightt;
_forkTarget(_target,leftt,rightt);
_routeCmd("delete",leftt,_systemVia,_systemDevice,(const char *)0);
if (rightt)
_routeCmd("delete",rightt,_systemVia,_systemDevice,(const char *)0);
}
#endif // __BSD__ ------------------------------------------------------------
for(std::map<InetAddress,bool>::iterator r(_applied.begin());r!=_applied.end();++r) {
#ifdef __BSD__ // ------------------------------------------------------------
if ((_systemVia)&&(_systemDevice[0])) {
_routeCmd("delete",leftt,_systemVia,_systemDevice,(const char *)0);
_routeCmd("delete",rightt,_systemVia,_systemDevice,(const char *)0);
}
if (_via) {
_routeCmd("delete",leftt,_via,(const char *)0,(const char *)0);
_routeCmd("delete",rightt,_via,(const char *)0,(const char *)0);
} else if (_device[0]) {
_routeCmd("delete",leftt,_via,(const char *)0,_device);
_routeCmd("delete",rightt,_via,(const char *)0,_device);
}
_routeCmd("delete",r->first,_via,r->second ? _device : (const char *)0,(_via) ? (const char *)0 : _device);
#endif // __BSD__ ------------------------------------------------------------
#ifdef __LINUX__ // ----------------------------------------------------------
_routeCmd("del",leftt,_via,(_via) ? _device : (const char *)0);
_routeCmd("del",rightt,_via,(_via) ? _device : (const char *)0);
_routeCmd("del",*r,_via,(_via) ? (const char *)0 : _device);
#endif // __LINUX__ ----------------------------------------------------------
#ifdef __WINDOWS__ // --------------------------------------------------------
_winRoute(true,interfaceLuid,interfaceIndex,leftt,_via);
_winRoute(true,interfaceLuid,interfaceIndex,rightt,_via);
_winRoute(true,interfaceLuid,interfaceIndex,*r,_via);
#endif // __WINDOWS__ --------------------------------------------------------
} else {
#ifdef __BSD__ // ------------------------------------------------------------
if (_via) {
_routeCmd("delete",_target,_via,(const char *)0,(const char *)0);
} else if (_device[0]) {
_routeCmd("delete",_target,_via,(const char *)0,_device);
}
#endif // __BSD__ ------------------------------------------------------------
#ifdef __LINUX__ // ----------------------------------------------------------
_routeCmd("del",_target,_via,(_via) ? _device : (const char *)0);
#endif // __LINUX__ ----------------------------------------------------------
#ifdef __WINDOWS__ // --------------------------------------------------------
_winRoute(true,interfaceLuid,interfaceIndex,_target,_via);
#endif // __WINDOWS__ --------------------------------------------------------
}
}
_target.zero();
@ -588,7 +537,7 @@ void ManagedRoute::remove()
_systemVia.zero();
_device[0] = (char)0;
_systemDevice[0] = (char)0;
_applied = false;
_applied.clear();
}
} // namespace ZeroTier

View File

@ -6,23 +6,34 @@
#include "../node/InetAddress.hpp"
#include "../node/Utils.hpp"
#include "../node/SharedPtr.hpp"
#include "../node/AtomicCounter.hpp"
#include "../node/NonCopyable.hpp"
#include <stdexcept>
#include <vector>
#include <map>
namespace ZeroTier {
/**
* A ZT-managed route that used C++ RAII semantics to automatically clean itself up on deallocate
*/
class ManagedRoute
class ManagedRoute : NonCopyable
{
friend class SharedPtr<ManagedRoute>;
public:
ManagedRoute()
ManagedRoute(const InetAddress &target,const InetAddress &via,const char *device)
{
_device[0] = (char)0;
_target = target;
_via = via;
if (via.ss_family == AF_INET)
_via.setPort(32);
else if (via.ss_family == AF_INET6)
_via.setPort(128);
Utils::scopy(_device,sizeof(_device),device);
_systemDevice[0] = (char)0;
_applied = false;
}
~ManagedRoute()
@ -30,45 +41,6 @@ public:
this->remove();
}
ManagedRoute(const ManagedRoute &r)
{
_applied = false;
*this = r;
}
inline ManagedRoute &operator=(const ManagedRoute &r)
{
if ((!_applied)&&(!r._applied)) {
memcpy(this,&r,sizeof(ManagedRoute)); // InetAddress is memcpy'able
} else {
fprintf(stderr,"Applied ManagedRoute isn't copyable!\n");
abort();
}
return *this;
}
/**
* Initialize object and set route
*
* Note: on Windows, use the interface NET_LUID in hexadecimal as the
* "device name."
*
* @param target Route target (e.g. 0.0.0.0/0 for default)
* @param via Route next L3 hop or NULL InetAddress if local in which case it will be routed via device
* @param device Name or hex LUID of ZeroTier device (e.g. zt#)
* @return True if route was successfully set
*/
inline bool set(const InetAddress &target,const InetAddress &via,const char *device)
{
if ((!via)&&(!device[0]))
return false;
this->remove();
_target = target;
_via = via;
Utils::scopy(_device,sizeof(_device),device);
return this->sync();
}
/**
* Set or update currently set route
*
@ -93,13 +65,14 @@ public:
inline const char *device() const { return _device; }
private:
InetAddress _target;
InetAddress _via;
InetAddress _systemVia; // for route overrides
std::map<InetAddress,bool> _applied; // routes currently applied
char _device[128];
char _systemDevice[128]; // for route overrides
bool _applied;
AtomicCounter __refCount;
};
} // namespace ZeroTier

View File

@ -215,7 +215,7 @@ static void _jsonAppend(unsigned int depth,std::string &buf,const ZT_Peer *peer)
const char *prole = "";
switch(peer->role) {
case ZT_PEER_ROLE_LEAF: prole = "LEAF"; break;
case ZT_PEER_ROLE_RELAY: prole = "RELAY"; break;
case ZT_PEER_ROLE_UPSTREAM: prole = "UPSTREAM"; break;
case ZT_PEER_ROLE_ROOT: prole = "ROOT"; break;
}

View File

@ -537,7 +537,7 @@ public:
EthernetTap *tap;
ZT_VirtualNetworkConfig config; // memcpy() of raw config from core
std::vector<InetAddress> managedIps;
std::list<ManagedRoute> managedRoutes;
std::list< SharedPtr<ManagedRoute> > managedRoutes;
NetworkSettings settings;
};
std::map<uint64_t,NetworkState> _nets;
@ -1128,13 +1128,13 @@ public:
std::vector<InetAddress> myIps(n.tap->ips());
// Nuke applied routes that are no longer in n.config.routes[] and/or are not allowed
for(std::list<ManagedRoute>::iterator mr(n.managedRoutes.begin());mr!=n.managedRoutes.end();) {
for(std::list< SharedPtr<ManagedRoute> >::iterator mr(n.managedRoutes.begin());mr!=n.managedRoutes.end();) {
bool haveRoute = false;
if ( (checkIfManagedIsAllowed(n,mr->target())) && ((mr->via().ss_family != mr->target().ss_family)||(!matchIpOnly(myIps,mr->via()))) ) {
if ( (checkIfManagedIsAllowed(n,(*mr)->target())) && (((*mr)->via().ss_family != (*mr)->target().ss_family)||(!matchIpOnly(myIps,(*mr)->via()))) ) {
for(unsigned int i=0;i<n.config.routeCount;++i) {
const InetAddress *const target = reinterpret_cast<const InetAddress *>(&(n.config.routes[i].target));
const InetAddress *const via = reinterpret_cast<const InetAddress *>(&(n.config.routes[i].via));
if ( (mr->target() == *target) && ( ((via->ss_family == target->ss_family)&&(mr->via() == *via)) || (tapdev == mr->device()) ) ) {
if ( ((*mr)->target() == *target) && ( ((via->ss_family == target->ss_family)&&((*mr)->via().ipsEqual(*via))) || (tapdev == (*mr)->device()) ) ) {
haveRoute = true;
break;
}
@ -1168,10 +1168,10 @@ public:
continue;
// If we've already applied this route, just sync it and continue
for(std::list<ManagedRoute>::iterator mr(n.managedRoutes.begin());mr!=n.managedRoutes.end();++mr) {
if ( (mr->target() == *target) && ( ((via->ss_family == target->ss_family)&&(mr->via() == *via)) || (tapdev == mr->device()) ) ) {
for(std::list< SharedPtr<ManagedRoute> >::iterator mr(n.managedRoutes.begin());mr!=n.managedRoutes.end();++mr) {
if ( ((*mr)->target() == *target) && ( ((via->ss_family == target->ss_family)&&((*mr)->via().ipsEqual(*via))) || (tapdev == (*mr)->device()) ) ) {
haveRoute = true;
mr->sync();
(*mr)->sync();
break;
}
}
@ -1179,8 +1179,8 @@ public:
continue;
// Add and apply new routes
n.managedRoutes.push_back(ManagedRoute());
if (!n.managedRoutes.back().set(*target,*via,tapdev))
n.managedRoutes.push_back(SharedPtr<ManagedRoute>(new ManagedRoute(*target,*via,tapdev)));
if (!n.managedRoutes.back()->sync())
n.managedRoutes.pop_back();
}
}