summaryrefslogtreecommitdiff
path: root/node/Multicaster.hpp
diff options
context:
space:
mode:
authorAdam Ierymenko <adam.ierymenko@gmail.com>2013-07-10 17:24:27 -0400
committerAdam Ierymenko <adam.ierymenko@gmail.com>2013-07-10 17:24:27 -0400
commit9e28bbfbb2671f71527e76dd20fe4a659109ad4b (patch)
tree8017270bb4dfc7570c40797d73b02df7d37b99d9 /node/Multicaster.hpp
parent47f611e7b8d2ff95a3ff795574798b4e24a6719d (diff)
downloadinfinitytier-9e28bbfbb2671f71527e76dd20fe4a659109ad4b.tar.gz
infinitytier-9e28bbfbb2671f71527e76dd20fe4a659109ad4b.zip
Factored out multicast propagation algorithm from Switch and Topology, also cleaned up and clarified it a bit.
Diffstat (limited to 'node/Multicaster.hpp')
-rw-r--r--node/Multicaster.hpp274
1 files changed, 274 insertions, 0 deletions
diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp
new file mode 100644
index 00000000..190c3033
--- /dev/null
+++ b/node/Multicaster.hpp
@@ -0,0 +1,274 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * 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 <stdint.h>
+#include <string.h>
+
+#include <utility>
+#include <algorithm>
+#include <map>
+#include <set>
+#include <vector>
+
+#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"
+
+// Maximum sample size to pick during choice of multicast propagation peers
+#define ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE 64
+
+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.
+ */
+class Multicaster
+{
+public:
+ /**
+ * 256-bit simple bloom filter included with multicast frame packets
+ */
+ typedef BloomFilter<ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BITS> MulticastBloomFilter;
+
+ Multicaster()
+ {
+ memset(_multicastHistory,0,sizeof(_multicastHistory));
+ }
+
+ /**
+ * Update the most recent LIKE time for an address in a given multicast group on a given network
+ *
+ * @param nwid Network ID
+ * @param mg Multicast group
+ * @param addr Address that likes group on given network
+ * @param now Current timestamp
+ */
+ inline void likesMulticastGroup(const uint64_t nwid,const MulticastGroup &mg,const Address &addr,const uint64_t now)
+ {
+ _multicastMemberships[MulticastChannel(nwid,mg)][addr] = now;
+ }
+
+ /**
+ * Check multicast history to see if this is a duplicate, and add/update entry
+ *
+ * @param from Ultimate sending MAC address
+ * @param to Destination multicast group
+ * @param payload Multicast packet payload
+ * @param len Length of packet
+ * @param nwid Network ID
+ * @param now Current time
+ * @return True if this appears to be a duplicate to within history expiration time
+ */
+ inline bool checkAndUpdateMulticastHistory(
+ const MAC &from,
+ const MulticastGroup &to,
+ const void *payload,
+ unsigned int len,
+ const uint64_t nwid,
+ const uint64_t now)
+ throw()
+ {
+ // Note: CRCs aren't transmitted over the network, so portability and
+ // byte order don't matter. This calculation can be changed. We just
+ // want a unique code.
+ uint64_t crc = Utils::crc64(0,from.data,6);
+ crc = Utils::crc64(crc,to.mac().data,6);
+ crc ^= (uint64_t)to.adi();
+ crc = Utils::crc64(crc,payload,len);
+ crc ^= nwid; // also include network ID in CRC
+
+ // Replace existing entry or pick one to replace with new entry
+ uint64_t earliest = 0xffffffffffffffffULL;
+ unsigned long earliestIdx = 0;
+ for(unsigned int i=0;i<ZT_MULTICAST_DEDUP_HISTORY_LENGTH;++i) {
+ if (_multicastHistory[i][0] == crc) {
+ uint64_t then = _multicastHistory[i][1];
+ _multicastHistory[i][1] = now;
+ return ((now - then) < ZT_MULTICAST_DEDUP_HISTORY_EXPIRE);
+ } else if (_multicastHistory[i][1] < earliest) {
+ earliest = _multicastHistory[i][1];
+ earliestIdx = i;
+ }
+ }
+
+ _multicastHistory[earliestIdx][0] = crc; // replace oldest entry
+ _multicastHistory[earliestIdx][1] = now;
+
+ return false;
+ }
+
+ /**
+ * Choose peers to send a propagating multicast to
+ *
+ * @param topology Topology object or mock thereof
+ * @param nwid Network ID
+ * @param mg Multicast group
+ * @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<Peer> in running code or a mock in simulation (mock must behave like a pointer type)
+ */
+ template<typename T,typename P>
+ inline unsigned int pickNextPropagationPeers(
+ T &topology,
+ uint64_t nwid,
+ const MulticastGroup &mg,
+ const Address &upstream,
+ MulticastBloomFilter &bf,
+ unsigned int max,
+ P *peers,
+ uint64_t now)
+ {
+ P toConsider[ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE];
+ unsigned int sampleSize = 0;
+
+ {
+ Mutex::Lock _l(_multicastMemberships_m);
+
+ // Sample a random subset of peers that we know have LIKEd this multicast
+ // group on this network.
+ std::map< MulticastChannel,std::map<Address,uint64_t> >::iterator channelMembers(_multicastMemberships.find(MulticastChannel(nwid,mg)));
+ if ((channelMembers != _multicastMemberships.end())&&(!channelMembers->second.empty())) {
+ unsigned long numEntriesPermittedToSkip = (channelMembers->second.size() > ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE) ? (unsigned long)(channelMembers->second.size() - ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE) : (unsigned long)0;
+ double skipWhatFraction = (double)numEntriesPermittedToSkip / (double)channelMembers->second.size();
+
+ std::map<Address,uint64_t>::iterator channelMemberEntry(channelMembers->second.begin());
+
+ while (channelMemberEntry != channelMembers->second.end()) {
+ // Auto-clean the channel members map if their LIKEs are expired. This will
+ // technically skew the random distribution of chosen members just a little, but
+ // it's unlikely that enough will expire in any single pick to make much of a
+ // difference overall.
+ if ((now - channelMemberEntry->second) > ZT_MULTICAST_LIKE_EXPIRE) {
+ channelMembers->second.erase(channelMemberEntry++);
+ continue;
+ }
+
+ // Skip some fraction of entries so that our sampling will be randomly distributed,
+ // since there is no other good way to sample randomly from a map.
+ if (numEntriesPermittedToSkip) {
+ double skipThis = (double)(Utils::randomInt<uint32_t>()) / 4294967296.0;
+ if (skipThis <= skipWhatFraction) {
+ --numEntriesPermittedToSkip;
+ ++channelMemberEntry;
+ continue;
+ }
+ }
+
+ // If it's not expired and it's from our random sample, add it to the set of peers
+ // to consider.
+ P peer = topology.getPeer(channelMemberEntry->first);
+ if (peer) {
+ toConsider[sampleSize++] = peer;
+ if (sampleSize >= ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE)
+ break; // abort if we have enough candidates
+ }
+ ++channelMemberEntry;
+ }
+
+ // Auto-clean: erase whole map if there are no more LIKEs for this channel
+ if (channelMembers->second.empty())
+ _multicastMemberships.erase(channelMembers);
+ }
+ }
+
+ // Sort in descending order of most recent direct unicast frame, picking
+ // peers with whom we have recently communicated. This is "implicit social
+ // switching."
+ std::sort(&(toConsider[0]),&(toConsider[sampleSize]),PeerPropagationPrioritySortOrder<P>());
+
+ // Decay a few random bits in bloom filter to probabilistically eliminate
+ // false positives as we go. The odds of decaying an already-set bit
+ // increases as the bloom filter saturates, so in the early hops of
+ // propagation this likely won't have any effect.
+ for(unsigned int i=0;i<ZT_MULTICAST_BLOOM_FILTER_DECAY_RATE;++i)
+ bf.decay();
+
+ // Pick peers not in the bloom filter, setting bloom filter bits accordingly to
+ // remember and pass on these picks.
+ unsigned int picked = 0;
+ for(unsigned int i=0;((i<sampleSize)&&(picked < max));++i) {
+ if (!bf.set(toConsider[i]->address().sum()))
+ peers[picked++] = toConsider[i];
+ }
+
+ // Add a supernode if there's nowhere else to go. Supernodes know of all multicast
+ // LIKEs and so can act to bridge sparse multicast groups. We do not remember them
+ // in the bloom filter, since such bridging may very well need to happen more than
+ // once.
+ if (!picked) {
+ P peer = topology.getBestSupernode();
+ if (peer)
+ peers[picked++] = peer;
+ }
+
+ return picked;
+ }
+
+private:
+ // Sort order for chosen propagation peers
+ template<typename P>
+ struct PeerPropagationPrioritySortOrder
+ {
+ inline bool operator()(const P &p1,const P &p2) const
+ {
+ return (p1->lastUnicastFrame() >= p2->lastUnicastFrame());
+ }
+ };
+
+ // [0] - CRC, [1] - timestamp
+ uint64_t _multicastHistory[ZT_MULTICAST_DEDUP_HISTORY_LENGTH][2];
+
+ // A multicast channel, essentially a pub/sub channel. It consists of a
+ // network ID and a multicast group within that network.
+ typedef std::pair<uint64_t,MulticastGroup> MulticastChannel;
+
+ // Address and time of last LIKE, by network ID and multicast group
+ std::map< MulticastChannel,std::map<Address,uint64_t> > _multicastMemberships;
+ Mutex _multicastMemberships_m;
+};
+
+} // namespace ZeroTier
+
+#endif