/* * ZeroTier One - Global Peer to Peer Ethernet * Copyright (C) 2012-2013 ZeroTier Networks LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * -- * * ZeroTier may be used and distributed under the terms of the GPLv3, which * are available at: http://www.gnu.org/licenses/gpl-3.0.html * * If you would like to embed ZeroTier into a commercial application or * redistribute it in a modified binary form, please contact ZeroTier Networks * LLC. Start here: http://www.zerotier.com/ */ #ifndef _ZT_MULTICASTER_HPP #define _ZT_MULTICASTER_HPP #include #include #include #include #include #include #include #include #include #include "Constants.hpp" #include "Buffer.hpp" #include "Packet.hpp" #include "MulticastGroup.hpp" #include "Utils.hpp" #include "MAC.hpp" #include "Address.hpp" #include "SharedPtr.hpp" #include "BloomFilter.hpp" #include "Identity.hpp" #include "CMWC4096.hpp" // Maximum sample size to pick during choice of multicast propagation peers #define ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE (ZT_MULTICAST_PROPAGATION_BREADTH * 8) namespace ZeroTier { /** * Multicast propagation engine * * This is written as a generic class so that it can be mocked and tested * in simulation. It also always takes 'now' as an argument, permitting * running in simulated time. * * This does not handle network permission or rate limiting, only the * propagation algorithm. */ class Multicaster { public: /** * Simple bit field bloom filter included with multicast frame packets */ typedef BloomFilter MulticastBloomFilter; Multicaster() throw() { memset(_multicastHistory,0,sizeof(_multicastHistory)); _multicastHistoryPtr = 0; } /** * Generate a signature of a multicast packet using an identity * * @param id Identity to sign with (must have secret key portion) * @param nwid Network ID * @param from MAC address of sender * @param to Multicast group * @param etherType 16-bit ethernet type * @param data Ethernet frame data * @param len Length of frame * @return ECDSA signature */ static inline std::string signMulticastPacket(const Identity &id,uint64_t nwid,const MAC &from,const MulticastGroup &to,unsigned int etherType,const void *data,unsigned int len) { unsigned char digest[32]; _hashMulticastPacketForSig(nwid,from,to,etherType,data,len,digest); return id.sign(digest); } /** * Verify a signature from a multicast packet * * @param id Identity of original signer * @param nwid Network ID * @param from MAC address of sender * @param to Multicast group * @param etherType 16-bit ethernet type * @param data Ethernet frame data * @param len Length of frame * @param signature ECDSA signature * @param siglen Length of signature in bytes * @return ECDSA signature */ static bool verifyMulticastPacket(const Identity &id,uint64_t nwid,const MAC &from,const MulticastGroup &to,unsigned int etherType,const void *data,unsigned int len,const void *signature,unsigned int siglen) { unsigned char digest[32]; _hashMulticastPacketForSig(nwid,from,to,etherType,data,len,digest); return id.verifySignature(digest,signature,siglen); } /** * Compute the CRC64 code for multicast deduplication * * @param nwid Network ID * @param from Sender MAC * @param to Destination multicast group * @param etherType Ethernet frame type * @param payload Multicast frame data * @param len Length of frame */ static inline uint64_t computeMulticastDedupCrc( uint64_t nwid, const MAC &from, const MulticastGroup &to, unsigned int etherType, const void *payload, unsigned int len) throw() { // This CRC is only used locally, so byte order issues and // such don't matter. It can also be changed without protocol // impact. uint64_t crc = Utils::crc64(0,from.data,6); crc = Utils::crc64(crc,to.mac().data,6); crc ^= (uint64_t)to.adi(); crc ^= (uint64_t)etherType; crc = Utils::crc64(crc,payload,len); crc ^= nwid; // also include network ID in CRC return crc; } /** * Check multicast history to see if this is a duplicate * * @param crc Multicast CRC * @param now Current time * @return True if this appears to be a duplicate to within history expiration time */ inline bool checkDuplicate(uint64_t crc,uint64_t now) const throw() { for(unsigned int i=0;i &memberships = _multicastMemberships[MulticastChannel(nwid,mg)]; for(std::vector::iterator mm(memberships.begin());mm!=memberships.end();++mm) { if (mm->first == addr) { mm->second = now; return; } } memberships.push_back(MulticastMembership(addr,now)); } /** * Choose peers to send a propagating multicast to * * @param topology Topology object or mock thereof * @param nwid Network ID * @param mg Multicast group * @param originalSubmitter Original submitter of multicast message to network * @param upstream Address from which message originated, or null (0) address if none * @param bf Bloom filter, updated in place with sums of addresses in chosen peers and/or decay * @param max Maximum number of peers to pick * @param peers Array of objects of type P to fill with up to [max] peers * @param now Current timestamp * @return Number of peers actually stored in peers array * @tparam T Type of topology, which is Topology in running code or a mock in simulation * @tparam P Type of peers, which is SharedPtr in running code or a mock in simulation (mock must behave like a pointer type) */ template inline unsigned int pickNextPropagationPeers( CMWC4096 &prng, T &topology, uint64_t nwid, const MulticastGroup &mg, const Address &originalSubmitter, const Address &upstream, MulticastBloomFilter &bf, unsigned int max, P *peers, uint64_t now) { typename std::set< P,_PeerPropagationPrioritySortOrder

> toConsider; // Pick up to ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE peers that have // subscribed to this channel and that are not in bloom filter. // Pick randomly from subscribers, but place into a set that is // sorted in descending order of time of most recent unicast // frame transfer. (Implicit social ordering.) Also ignore original // submitter and upstream, since we know these have seen this // message. { Mutex::Lock _l(_multicastMemberships_m); std::map< MulticastChannel,std::vector >::iterator mm(_multicastMemberships.find(MulticastChannel(nwid,mg))); if ((mm != _multicastMemberships.end())&&(!mm->second.empty())) { for(unsigned int stries=0;striessecond[prng.next32() % mm->second.size()]; if (((now - m.second) < ZT_MULTICAST_LIKE_EXPIRE)&&(!bf.contains(m.first.sum()))&&(m.first != originalSubmitter)&&(m.first != upstream)) { P peer(topology.getPeer(m.first)); if (peer) toConsider.insert(peer); } } } } // The first peers in toConsider will be the 'best' unsigned int chosen = 0; for(typename std::set< P,_PeerPropagationPrioritySortOrder

>::iterator i(toConsider.begin());((i!=toConsider.end())&&(chosen < max));++i) bf.set((peers[chosen++] = *i)->address().sum()); // Add a supernode if there are fewer than the desired // number of recipients. Note that we do not use the bloom // filter to track visits to supernodes, intentionally // allowing multicasts to ping pong between supernodes. // Supernodes propagate even messages they've already seen, // while regular nodes do not. Thus this ping-ponging will // cause the supernodes to pick new starting points for // peer to peer graph traversal multiple times. It's a // simple, stateless way to increase supernode-driven // propagation of a multicast in the event that peer to // peer connectivity for its group is sparse. if (chosen < max) { Address avoid[2]; avoid[0] = originalSubmitter; avoid[1] = upstream; P peer = topology.getBestSupernode(avoid,2,true); if (peer) peers[chosen++] = peer; } return chosen; } private: // Sort order for chosen propagation peers template struct _PeerPropagationPrioritySortOrder { inline bool operator()(const P &p1,const P &p2) const { return (p1->lastUnicastFrame() > p2->lastUnicastFrame()); } }; static inline void _hashMulticastPacketForSig(uint64_t nwid,const MAC &from,const MulticastGroup &to,unsigned int etherType,const void *data,unsigned int len,unsigned char *digest) throw() { unsigned char zero = 0; SHA256_CTX sha; SHA256_Init(&sha); uint64_t _nwid = Utils::hton(nwid); SHA256_Update(&sha,(unsigned char *)&_nwid,sizeof(_nwid)); SHA256_Update(&sha,&zero,1); SHA256_Update(&sha,(unsigned char *)from.data,6); SHA256_Update(&sha,&zero,1); SHA256_Update(&sha,(unsigned char *)to.mac().data,6); SHA256_Update(&sha,&zero,1); uint32_t _adi = Utils::hton(to.adi()); SHA256_Update(&sha,(unsigned char *)&_adi,sizeof(_adi)); SHA256_Update(&sha,&zero,1); uint16_t _etype = Utils::hton((uint16_t)etherType); SHA256_Update(&sha,(unsigned char *)&_etype,sizeof(_etype)); SHA256_Update(&sha,&zero,1); SHA256_Update(&sha,(unsigned char *)data,len); SHA256_Final(digest,&sha); } // ring buffer: [0] - CRC, [1] - timestamp uint64_t _multicastHistory[ZT_MULTICAST_DEDUP_HISTORY_LENGTH][2]; volatile unsigned int _multicastHistoryPtr; // A multicast channel, essentially a pub/sub channel. It consists of a // network ID and a multicast group within that network. typedef std::pair MulticastChannel; // Address and time of last LIKE typedef std::pair MulticastMembership; // Network : MulticastGroup -> vector

std::map< MulticastChannel,std::vector > _multicastMemberships; Mutex _multicastMemberships_m; }; } // namespace ZeroTier #endif