diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp index f28c2ed69..9cd8f8bf0 100644 --- a/node/Multicaster.cpp +++ b/node/Multicaster.cpp @@ -56,10 +56,10 @@ void Multicaster::send(const RuntimeEnvironment *RR,uint64_t nwid,const Certific // If we already have enough members, just send and we're done -- no need for TX queue OutboundMulticast out; - out.init(now,RR->identity.address(),nwid,ZT_MULTICAST_DEFAULT_IMPLICIT_GATHER,src,mg,etherType,data,len); + out.init(now,RR->identity.address(),nwid,com,ZT_MULTICAST_DEFAULT_IMPLICIT_GATHER,src,mg,etherType,data,len); unsigned int count = 0; for(std::vector::const_reverse_iterator m(gs.members.rbegin());m!=gs.members.rend();++m) { - out.sendOnly(*(RR->sw),m->address); + out.sendOnly(*(RR->sw),m->address); // sendOnly() avoids overhead of creating sent log since we're going to discard this immediately if (++count >= limit) break; } @@ -68,25 +68,26 @@ void Multicaster::send(const RuntimeEnvironment *RR,uint64_t nwid,const Certific gs.txQueue.push_back(OutboundMulticast()); OutboundMulticast &out = gs.txQueue.back(); - out.init(now,RR->identity.address(),nwid,ZT_MULTICAST_DEFAULT_IMPLICIT_GATHER,src,mg,etherType,data,len); + out.init(now,RR->identity.address(),nwid,com,ZT_MULTICAST_DEFAULT_IMPLICIT_GATHER,src,mg,etherType,data,len); for(std::vector::const_reverse_iterator m(gs.members.rbegin());m!=gs.members.rend();++m) out.sendAndLog(*(RR->sw),m->address); if ((now - gs.lastExplicitGather) >= ZT_MULTICAST_GATHER_DELAY) { gs.lastExplicitGather = now; - // Explicitly gather -- right now we only do this from supernodes since they - // know all multicast group memberships. In the future this might be more - // distributed somehow. + // TODO / INPROGRESS: right now supernodes track multicast LIKEs, a relic + // from the old algorithm. The next step will be to devolve this duty + // somewhere else, such as node(s) nominated by netconf masters. But + // we'll keep announcing LIKEs to supernodes for the near future to + // gradually migrate from old multicast to new without losing old nodes. SharedPtr sn(RR->topology->getBestSupernode()); if (sn) { Packet outp(sn->address(),RR->identity.address(),Packet::VERB_MULTICAST_GATHER); outp.append(nwid); - outp.append((uint8_t)((com) ? 0x01: 0x00)); + outp.append((uint8_t)0); mg.mac().appendTo(outp); outp.append((uint32_t)mg.adi()); outp.append((uint32_t)((limit - (unsigned int)gs.members.size()) + 1)); // +1 just means we'll have an extra in the queue if available - if (com) com->serialize(outp); outp.armor(sn->key(),true); sn->send(RR,outp.data(),outp.size(),now); } diff --git a/node/Network.cpp b/node/Network.cpp index 18fbb6249..ac3e90735 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -453,7 +453,8 @@ void Network::_CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned void Network::_pushMembershipCertificate(const Address &peer,bool force,uint64_t now) { - // assumes _lock is locked + // assumes _lock is locked and _config is not null + uint64_t pushTimeout = _config->com().timestampMaxDelta() / 2; // Zero means we're still waiting on our own cert diff --git a/node/Network.hpp b/node/Network.hpp index 8d4092bbe..2ea843519 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -148,7 +148,7 @@ public: } /** - * Update multicast groups for this network's tap + * Update multicast groups for this network's tap and announce changes * * @return True if internal multicast group set has changed since last update */ @@ -227,6 +227,35 @@ public: _pushMembershipCertificate(peer,force,now); } + /** + * Send a multicast packet to the members of a group + * + * This performs no rate checking or other permission checking. + * + * @param mg Multicast group destination + * @param src Source Ethernet MAC address + * @param etherType Ethernet frame type + * @param data Payload data + * @param len Length of payload + */ + inline void sendMulticast(const MulticastGroup &mg,const MAC &src,unsigned int etherType,const void *data,unsigned int len) + { + Mutex::Lock _l(_lock); + if (!_config) + return; + _multicaster.send( + RR, + _id, + (((!_config->isPublic())&&(_config->com())) ? &(_config->com()) : (const CertificateOfMembership *)0), + _config->multicastLimit(), + Utils::now(), + mg, + src, + etherType, + data, + len); + } + /** * @param peer Peer address to check * @return True if peer is allowed to communicate on this network diff --git a/node/Node.cpp b/node/Node.cpp index 726c1022b..583736435 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -565,14 +565,9 @@ Node::ReasonForTermination Node::run() if ((now - lastMulticastCheck) >= ZT_MULTICAST_LOCAL_POLL_PERIOD) { lastMulticastCheck = now; try { - std::map< SharedPtr,std::set > toAnnounce; std::vector< SharedPtr > networks(RR->nc->networks()); - for(std::vector< SharedPtr >::const_iterator nw(networks.begin());nw!=networks.end();++nw) { - if ((*nw)->updateMulticastGroups()) - toAnnounce.insert(std::pair< SharedPtr,std::set >(*nw,(*nw)->multicastGroups())); - } - if (toAnnounce.size()) - RR->sw->announceMulticastGroups(toAnnounce); + for(std::vector< SharedPtr >::const_iterator nw(networks.begin());nw!=networks.end();++nw) + (*nw)->updateMulticastGroups()); } catch (std::exception &exc) { LOG("unexpected exception announcing multicast groups: %s",exc.what()); } catch ( ... ) { diff --git a/node/OutboundMulticast.cpp b/node/OutboundMulticast.cpp index 02956dd34..0643820e4 100644 --- a/node/OutboundMulticast.cpp +++ b/node/OutboundMulticast.cpp @@ -39,8 +39,10 @@ void OutboundMulticast::init(uint64_t timestamp,const Address &self,uint64_t nwi _source = src; _destination = dest; _etherType = etherType; + _packet.setSource(self); _packet.setVerb(Packet::VERB_MULTICAST_FRAME); + _packet.append((uint64_t)nwid); _packet.append((uint8_t)((com) ? 0x01 : 0x00)); _packet.append((uint32_t)gatherLimit); // gather limit -- set before send, start with 0 @@ -50,6 +52,7 @@ void OutboundMulticast::init(uint64_t timestamp,const Address &self,uint64_t nwi src.appendTo(_packet); _packet.append((uint16_t)etherType); _packet.append(payload,len); + _packet.compress(); } diff --git a/node/Packet.hpp b/node/Packet.hpp index c2960e235..46c0a88a4 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -513,13 +513,15 @@ public: */ VERB_RENDEZVOUS = 5, - /* A ZT-to-ZT unicast ethernet frame (shortened EXT_FRAME): + /* ZT-to-ZT unicast ethernet frame (shortened EXT_FRAME): * <[8] 64-bit network ID> * <[2] 16-bit ethertype> * <[...] ethernet payload> * * MAC addresses are derived from the packet's source and destination - * ZeroTier addresses. + * ZeroTier addresses. This is a shortened EXT_FRAME that elides full + * Ethernet framing and other optional flags and features when they + * are not necessary. * * ERROR may be generated if a membership certificate is needed for a * closed network. Payload will be network ID. @@ -527,17 +529,21 @@ public: VERB_FRAME = 6, /* - * An ethernet frame to or from specified MAC addresses: + * Full Ethernet frame with MAC addressing and optional fields: * <[8] 64-bit network ID> - * <[1] flags (currently unused, must be 0)> + * <[1] flags> + * [<[...] certificate of network membership>] * <[6] destination MAC or all zero for destination node> * <[6] source MAC or all zero for node of origin> * <[2] 16-bit ethertype> * <[...] ethernet payload> * - * Extended frames include full MAC addressing and are used for bridged - * configurations. Theoretically they could carry multicast as well but - * currently they're not used for that. + * Flags: + * 0x01 - Certificate of network membership is attached + * + * An extended frame carries full MAC addressing, making them a + * superset of VERB_FRAME. They're used for bridging or when we + * want to attach a certificate since FRAME does not support that. * * ERROR may be generated if a membership certificate is needed for a * closed network. Payload will be network ID. @@ -678,24 +684,16 @@ public: /* Request endpoints for multicast distribution: * <[8] 64-bit network ID> - * <[1] flags> + * <[1] flags (unused, must be 0)> * <[6] MAC address of multicast group being queried> * <[4] 32-bit ADI for multicast group being queried> * <[4] 32-bit (suggested) max number of multicast peers desired or 0 for no limit> - * [<[...] network certificate of membership if included>] - * - * Flags: - * 0x01 - Network certificate of membership is attached * * This message asks a peer for additional known endpoints that have * LIKEd a given multicast group. It's sent when the sender wishes * to send multicast but does not have the desired number of recipient * peers. * - * A certificate of network membership can be included so that peers - * can check network membership before responding. Without certificate - * check any peer could probe multicast memberships on any network. - * * OK response payload: * <[8] 64-bit network ID> * <[6] MAC address of multicast group being queried> diff --git a/node/Peer.cpp b/node/Peer.cpp index a58bfe3be..5f27da3f2 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -28,6 +28,9 @@ #include "Constants.hpp" #include "Peer.hpp" #include "Switch.hpp" +#include "Packet.hpp" +#include "Network.hpp" +#include "NodeConfig.hpp" #include "AntiRecursion.hpp" #include @@ -76,14 +79,14 @@ void Peer::receive( // Update system-wide last packet receive time *((const_cast(&(RR->timeOfLastPacketReceived)))) = now; + Mutex::Lock _l(_lock); + // Global last receive time regardless of path _lastReceive = now; - // Learn paths from direct packets (hops == 0) if (!hops) { + // Learn paths from direct packets (hops == 0) { - Mutex::Lock _l(_lock); - bool havePath = false; for(std::vector::iterator p(_paths.begin());p!=_paths.end();++p) { if ((p->address() == remoteAddr)&&(p->tcp() == fromSock->tcp())) { @@ -110,11 +113,36 @@ void Peer::receive( } } - // Announce multicast LIKEs to peers to whom we have a direct link - // Lock can't be locked here or it'll recurse and deadlock. + // Announce multicast groups of interest to direct peers if they are + // considered authorized members of a given network. Also announce to + // supernodes and network controllers. TODO: the former may go + // obsolete with time as network controllers take over this role. if ((now - _lastAnnouncedTo) >= ((ZT_MULTICAST_LIKE_EXPIRE / 2) - 1000)) { _lastAnnouncedTo = now; - RR->sw->announceMulticastGroups(SharedPtr(this)); + + Packet outp(_id.address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); + std::vector< SharedPtr > networks(RR->nc->networks()); + for(std::vector< SharedPtr >::iterator n(networks.begin());n!=networks.end();++n) { + if ( ((*n)->isAllowed(_id.address())) || ((*n)->controller() == _id.address()) || (RR->topology->isSupernode(_id.address())) ) { + std::set mgs((*n)->multicastGroups()); + for(std::set::iterator mg(mgs.begin());mg!=mgs.end();++mg) { + if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) { + outp.armor(_key,true); + fromSock->send(remoteAddr,outp.data(),outp.size()); + outp.reset(_id.address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); + } + + // network ID, MAC, ADI + outp.append((uint64_t)(*n)->id()); + mg->mac().appendTo(outp); + outp.append((uint32_t)mg->adi()); + } + } + } + if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) { + outp.armor(_key,true); + fromSock->send(remoteAddr,outp.data(),outp.size()); + } } } diff --git a/node/Switch.cpp b/node/Switch.cpp index 43e3276e4..8439805ae 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -143,96 +143,20 @@ void Switch::onLocalEthernet(const SharedPtr &network,const MAC &from,c network->learnBridgedMulticastGroup(mg,now); // Check multicast/broadcast bandwidth quotas and reject if quota exceeded - if (!network->updateAndCheckMulticastBalance(RR->identity.address(),mg,data.size())) { + if (!network->updateAndCheckMulticastBalance(mg,data.size())) { TRACE("%s: didn't multicast %d bytes, quota exceeded for multicast group %s",network->tapDeviceName().c_str(),(int)data.size(),mg.toString().c_str()); return; } TRACE("%s: MULTICAST %s -> %s %s %d",network->tapDeviceName().c_str(),from.toString().c_str(),mg.toString().c_str(),etherTypeName(etherType),(int)data.size()); - /* old P5 multicast algorithm - const unsigned int mcid = ++_multicastIdCounter & 0xffffff; - const uint16_t bloomNonce = (uint16_t)(RR->prng->next32() & 0xffff); // doesn't need to be cryptographically strong - unsigned char bloom[ZT_PROTO_VERB_MULTICAST_FRAME_LEN_PROPAGATION_BLOOM]; - unsigned char fifo[ZT_PROTO_VERB_MULTICAST_FRAME_LEN_PROPAGATION_FIFO + ZT_ADDRESS_LENGTH]; // extra ZT_ADDRESS_LENGTH is for first hop, not put in packet but serves as destination for packet - unsigned char *const fifoEnd = fifo + sizeof(fifo); - const unsigned int signedPartLen = (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME - ZT_PROTO_VERB_MULTICAST_FRAME_IDX__START_OF_SIGNED_PORTION) + data.size(); - const SharedPtr supernode(RR->topology->getBestSupernode()); - - // For each bit prefix send a packet to a list of destinations within it - for(unsigned int prefix=0,np=((unsigned int)2 << (nconf->multicastPrefixBits() - 1));prefixidentity.address(), - nconf->multicastPrefixBits(), - prefix, - RR->topology, - now); - for(std::set
::const_iterator ab(nconf->activeBridges().begin());ab!=nconf->activeBridges().end();++ab) { - if (!appender(*ab)) - break; - } - RR->mc->getNextHops(network->id(),mg,appender); - - // Pad remainder of FIFO with zeroes - while (fifoPtr != fifoEnd) - *(fifoPtr++) = (unsigned char)0; - - // First element in FIFO is first hop, rest of FIFO is sent in packet *to* first hop - Address firstHop(fifo,ZT_ADDRESS_LENGTH); - if (!firstHop) { - if (supernode) - firstHop = supernode->address(); - else continue; // nowhere to go - } - - Packet outp(firstHop,RR->identity.address(),Packet::VERB_MULTICAST_FRAME); - outp.append((uint16_t)0); - outp.append(fifo + ZT_ADDRESS_LENGTH,ZT_PROTO_VERB_MULTICAST_FRAME_LEN_PROPAGATION_FIFO); // remainder of fifo is loaded into packet - outp.append(bloom,ZT_PROTO_VERB_MULTICAST_FRAME_LEN_PROPAGATION_BLOOM); - outp.append((nconf->com()) ? (unsigned char)ZT_PROTO_VERB_MULTICAST_FRAME_FLAGS_HAS_MEMBERSHIP_CERTIFICATE : (unsigned char)0); - outp.append(network->id()); - outp.append(bloomNonce); - outp.append((unsigned char)nconf->multicastPrefixBits()); - outp.append((unsigned char)prefix); - RR->identity.address().appendTo(outp); // lower 40 bits of MCID are my address - outp.append((unsigned char)((mcid >> 16) & 0xff)); - outp.append((unsigned char)((mcid >> 8) & 0xff)); - outp.append((unsigned char)(mcid & 0xff)); // upper 24 bits of MCID are from our counter - from.appendTo(outp); - mg.mac().appendTo(outp); - outp.append(mg.adi()); - outp.append((uint16_t)etherType); - outp.append((uint16_t)data.size()); - outp.append(data); - - C25519::Signature sig(RR->identity.sign(outp.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX__START_OF_SIGNED_PORTION,signedPartLen),signedPartLen)); - outp.append((uint16_t)sig.size()); - outp.append(sig.data,(unsigned int)sig.size()); - - // FIXME: now we send the netconf cert with every single multicast, - // which pretty much ensures everyone has it ahead of time but adds - // some redundant payload. Maybe think abouut this in the future. - if (nconf->com()) - nconf->com().serialize(outp); - - outp.compress(); - send(outp,true); - } - */ + network->sendMulticast(mg,from,etherType,data.data(),data.size()); return; } if (to[0] == MAC::firstOctetForNetwork(network->id())) { - // Destination is another ZeroTier node + // Destination is another ZeroTier peer Address toZT(to.toAddress(network->id())); if (network->isAllowed(toZT)) { @@ -266,7 +190,7 @@ void Switch::onLocalEthernet(const SharedPtr &network,const MAC &from,c } { - // Destination is behind another bridge + // Destination is bridged behind a remote peer Address bridges[ZT_MAX_BRIDGE_SPAM]; unsigned int numBridges = 0; @@ -477,72 +401,6 @@ void Switch::contact(const SharedPtr &peer,const InetAddress &atAddr) RR->sm->whack(); } -void Switch::announceMulticastGroups(const std::map< SharedPtr,std::set > &allMemberships) -{ - std::vector< SharedPtr > directPeers; - RR->topology->eachPeer(Topology::CollectPeersWithActiveDirectPath(directPeers,Utils::now())); - -#ifdef ZT_TRACE - unsigned int totalMulticastGroups = 0; - for(std::map< SharedPtr,std::set >::const_iterator i(allMemberships.begin());i!=allMemberships.end();++i) - totalMulticastGroups += (unsigned int)i->second.size(); - TRACE("announcing %u multicast groups for %u networks to %u peers",totalMulticastGroups,(unsigned int)allMemberships.size(),(unsigned int)directPeers.size()); -#endif - - uint64_t now = Utils::now(); - for(std::vector< SharedPtr >::iterator p(directPeers.begin());p!=directPeers.end();++p) { - Packet outp((*p)->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); - - for(std::map< SharedPtr,std::set >::const_iterator nwmgs(allMemberships.begin());nwmgs!=allMemberships.end();++nwmgs) { - nwmgs->first->pushMembershipCertificate((*p)->address(),false,now); - - if ((RR->topology->isSupernode((*p)->address()))||(nwmgs->first->isAllowed((*p)->address()))) { - for(std::set::iterator mg(nwmgs->second.begin());mg!=nwmgs->second.end();++mg) { - if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) { - send(outp,true); - outp.reset((*p)->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); - } - - // network ID, MAC, ADI - outp.append((uint64_t)nwmgs->first->id()); - mg->mac().appendTo(outp); - outp.append((uint32_t)mg->adi()); - } - } - } - - if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) - send(outp,true); - } -} - -void Switch::announceMulticastGroups(const SharedPtr &peer) -{ - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); - std::vector< SharedPtr > networks(RR->nc->networks()); - uint64_t now = Utils::now(); - for(std::vector< SharedPtr >::iterator n(networks.begin());n!=networks.end();++n) { - if (((*n)->isAllowed(peer->address()))||(RR->topology->isSupernode(peer->address()))) { - (*n)->pushMembershipCertificate(peer->address(),false,now); - - std::set mgs((*n)->multicastGroups()); - for(std::set::iterator mg(mgs.begin());mg!=mgs.end();++mg) { - if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) { - send(outp,true); - outp.reset(peer->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); - } - - // network ID, MAC, ADI - outp.append((uint64_t)(*n)->id()); - mg->mac().appendTo(outp); - outp.append((uint32_t)mg->adi()); - } - } - } - if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) - send(outp,true); -} - void Switch::requestWhois(const Address &addr) { //TRACE("requesting WHOIS for %s",addr.toString().c_str()); diff --git a/node/Switch.hpp b/node/Switch.hpp index a1c2f752c..9b3f7cba0 100644 --- a/node/Switch.hpp +++ b/node/Switch.hpp @@ -165,28 +165,6 @@ public: */ void contact(const SharedPtr &peer,const InetAddress &atAddr); - /** - * Announce multicast group memberships - * - * This announces all the groups for all the networks in the supplied map to - * all peers with whom we have an active direct link. Only isAllowed() peers - * and supernodes get announcements for each given network. - * - * @param allMemberships Memberships for a number of networks - */ - void announceMulticastGroups(const std::map< SharedPtr,std::set > &allMemberships); - - /** - * Announce multicast group memberships - * - * This announces all current multicast memberships to a single peer. Only - * memberships for networks where the peer isAllowed() are included, unless - * the peer is a supernode. - * - * @param peer Peer to announce all memberships to - */ - void announceMulticastGroups(const SharedPtr &peer); - /** * Request WHOIS on a given address *