Browse Source

Adding signatures to multicast frames, work in progress, does not build yet

Adam Ierymenko 12 years ago
parent
commit
bcd079b70e
5 changed files with 194 additions and 85 deletions
  1. 1 1
      node/Constants.hpp
  2. 66 0
      node/Multicaster.hpp
  3. 0 13
      node/Network.hpp
  4. 52 27
      node/Packet.hpp
  5. 75 44
      node/Switch.cpp

+ 1 - 1
node/Constants.hpp

@@ -240,7 +240,7 @@ error_no_ZT_ARCH_defined;
 /**
 /**
  * Expiration time in ms for multicast history items
  * Expiration time in ms for multicast history items
  */
  */
-#define ZT_MULTICAST_DEDUP_HISTORY_EXPIRE 8000
+#define ZT_MULTICAST_DEDUP_HISTORY_EXPIRE 4000
 
 
 /**
 /**
  * Number of bits to randomly "decay" in bloom filter per hop
  * Number of bits to randomly "decay" in bloom filter per hop

+ 66 - 0
node/Multicaster.hpp

@@ -31,11 +31,14 @@
 #include <stdint.h>
 #include <stdint.h>
 #include <string.h>
 #include <string.h>
 
 
+#include <openssl/sha.h>
+
 #include <utility>
 #include <utility>
 #include <algorithm>
 #include <algorithm>
 #include <map>
 #include <map>
 #include <set>
 #include <set>
 #include <vector>
 #include <vector>
+#include <string>
 
 
 #include "Constants.hpp"
 #include "Constants.hpp"
 #include "Buffer.hpp"
 #include "Buffer.hpp"
@@ -46,6 +49,7 @@
 #include "Address.hpp"
 #include "Address.hpp"
 #include "SharedPtr.hpp"
 #include "SharedPtr.hpp"
 #include "BloomFilter.hpp"
 #include "BloomFilter.hpp"
+#include "Identity.hpp"
 
 
 // Maximum sample size to pick during choice of multicast propagation peers
 // Maximum sample size to pick during choice of multicast propagation peers
 #define ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE 64
 #define ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE 64
@@ -68,10 +72,49 @@ public:
 	typedef BloomFilter<ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BITS> MulticastBloomFilter;
 	typedef BloomFilter<ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BITS> MulticastBloomFilter;
 
 
 	Multicaster()
 	Multicaster()
+		throw()
 	{
 	{
 		memset(_multicastHistory,0,sizeof(_multicastHistory));
 		memset(_multicastHistory,0,sizeof(_multicastHistory));
 	}
 	}
 
 
+	/**
+	 * Generate a signature of a multicast packet using an identity
+	 *
+	 * @param id Identity to sign with (must have secret key portion)
+	 * @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,const MAC &from,const MulticastGroup &to,unsigned int etherType,const void *data,unsigned int len)
+	{
+		unsigned char digest[32];
+		_hashMulticastPacketForSig(from,to,etherType,data,len,digest);
+		return id.sign(digest);
+	}
+
+	/**
+	 * Verify a signature from a multicast packet
+	 *
+	 * @param id Identity of original signer
+	 * @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,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(from,to,etherType,data,len,digest);
+		return id.verify(digest,signature,siglen);
+	}
+
 	/**
 	/**
 	 * Update the most recent LIKE time for an address in a given multicast group on a given network
 	 * Update the most recent LIKE time for an address in a given multicast group on a given network
 	 *
 	 *
@@ -257,6 +300,29 @@ private:
 		}
 		}
 	};
 	};
 
 
+	static inline void _hashMulticastPacketForSig(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(network->id());
+		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 *)mg.mac().data,6);
+		SHA256_Update(&sha,&zero,1);
+		uint32_t _adi = Utils::hton(mg.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);
+	}
+
 	// [0] - CRC, [1] - timestamp
 	// [0] - CRC, [1] - timestamp
 	uint64_t _multicastHistory[ZT_MULTICAST_DEDUP_HISTORY_LENGTH][2];
 	uint64_t _multicastHistory[ZT_MULTICAST_DEDUP_HISTORY_LENGTH][2];
 
 

+ 0 - 13
node/Network.hpp

@@ -112,19 +112,6 @@ public:
 		return ((_open)||(_members.count(addr) > 0));
 		return ((_open)||(_members.count(addr) > 0));
 	}
 	}
 
 
-	/**
-	 * Shortcut to check open(), whether MAC is ZeroTier, then isMember()
-	 *
-	 * @param mac MAC address to check
-	 * @return True if MAC is allowed
-	 */
-	inline bool isAllowed(const MAC &mac) const
-		throw()
-	{
-		Mutex::Lock _l(_lock);
-		return ((_open)||((mac.isZeroTier())&&(_members.count(Address(mac)) > 0)));
-	}
-
 	/**
 	/**
 	 * @return True if network is open (no membership required)
 	 * @return True if network is open (no membership required)
 	 */
 	 */

+ 52 - 27
node/Packet.hpp

@@ -45,8 +45,13 @@
 
 
 /**
 /**
  * Protocol version
  * Protocol version
+ *
+ * 1 - 0.2.0 ... 0.2.5
+ * 2 - 0.3.0 ...
+ *   * Added signature and originating peer to multicast frame
+ *   * Double size of multicast frame bloom filter
  */
  */
-#define ZT_PROTO_VERSION 1
+#define ZT_PROTO_VERSION 2
 
 
 /**
 /**
  * Maximum hop count allowed by packet structure (3 bits, 0-7)
  * Maximum hop count allowed by packet structure (3 bits, 0-7)
@@ -123,8 +128,8 @@
 #define ZT_PROTO_MIN_FRAGMENT_LENGTH ZT_PACKET_FRAGMENT_IDX_PAYLOAD
 #define ZT_PROTO_MIN_FRAGMENT_LENGTH ZT_PACKET_FRAGMENT_IDX_PAYLOAD
 
 
 // Size of bloom filter used in multicast propagation
 // Size of bloom filter used in multicast propagation
-#define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE 32
-#define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BITS 256
+#define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BITS 512
+#define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BYTES 64
 
 
 // Field incides for parsing verbs
 // Field incides for parsing verbs
 #define ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION (ZT_PACKET_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION (ZT_PACKET_IDX_PAYLOAD)
@@ -148,15 +153,18 @@
 #define ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID + 8)
 #define ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID + 8)
 #define ZT_PROTO_VERB_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE + 2)
 #define ZT_PROTO_VERB_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE + 2)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_MULTICAST_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID + 8)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_MULTICAST_MAC + 6)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI + 4)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOPS (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM + ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_LOAD_FACTOR (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOPS + 1)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FROM_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_LOAD_FACTOR + 2)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FROM_MAC + 6)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE + 2)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS (ZT_PACKET_IDX_PAYLOAD)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID + 8)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS + 5)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DESTINATION_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC + 6)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DESTINATION_MAC + 6)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM_FILTER (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI + 4)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOP_COUNT (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM_FILTER + 64)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOP_COUNT + 1)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD_LENGTH (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE + 2)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SIGNATURE_LENGTH (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD_LENGTH + 2)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SIGNATURE_LENGTH + 2)
 
 
 // Field indices for parsing OK and ERROR payloads of replies
 // Field indices for parsing OK and ERROR payloads of replies
 #define ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
@@ -415,20 +423,8 @@ public:
 		 */
 		 */
 		VERB_FRAME = 6,
 		VERB_FRAME = 6,
 
 
-		/* A multicast frame:
-		 *   <[8] 64-bit network ID>
-		 *   <[6] destination multicast Ethernet address>
-		 *   <[4] multicast additional distinguishing information (ADI)>
-		 *   <[32] multicast propagation bloom filter>
-		 *   <[1] 8-bit strict propagation hop count>
-		 *   <[2] reserved, must be 0>
-		 *   <[6] source Ethernet address>
-		 *   <[2] 16-bit ethertype>
-		 *   <[...] ethernet payload>
-		 *
-		 * No OK or ERROR is generated.
-		 */
-		VERB_MULTICAST_FRAME = 7,
+		/* 7 - old VERB_MULTICAST_FRAME, might be reused once all old 0.2
+		 * clients are off the net. */
 
 
 		/* Announce interest in multicast group(s):
 		/* Announce interest in multicast group(s):
 		 *   <[8] 64-bit network ID>
 		 *   <[8] 64-bit network ID>
@@ -438,7 +434,36 @@ public:
 		 *
 		 *
 		 * OK is generated on successful receipt.
 		 * OK is generated on successful receipt.
 		 */
 		 */
-		VERB_MULTICAST_LIKE = 8
+		VERB_MULTICAST_LIKE = 8,
+
+		/* A multicast frame:
+		 *   <[1] flags, currently unused and must be 0>
+		 *   <[8] 64-bit network ID>
+		 *   <[5] ZeroTier address of original submitter of this multicast>
+		 *   <[6] source MAC address>
+		 *   <[6] destination multicast Ethernet address>
+		 *   <[4] multicast additional distinguishing information (ADI)>
+		 *   <[64] multicast propagation bloom filter>
+		 *   <[1] 8-bit propagation hop count>
+		 *   <[2] 16-bit ethertype>
+		 *   <[2] 16-bit length of payload>
+		 *   <[2] 16-bit length of signature>
+		 *   <[...] ethernet payload>
+		 *   <[...] ECDSA signature>
+		 *
+		 * The signature is made using the key of the original submitter, and
+		 * can be used to authenticate the submitter for security and rate
+		 * control purposes. Fields in the signature are: network ID, source
+		 * MAC, destination MAC, multicast ADI, ethertype, and payload. All
+		 * integers are hashed in big-endian byte order. A zero byte is added
+		 * to the hash between each field.
+		 *
+		 * In the future flags could indicate additional fields appended to the
+		 * end or a different signature algorithm.
+		 *
+		 * No OK or ERROR is generated.
+		 */
+		VERB_MULTICAST_FRAME = 9
 	};
 	};
 
 
 	/**
 	/**

+ 75 - 44
node/Switch.cpp

@@ -219,8 +219,6 @@ Switch_onRemotePacket_complete_packet_handler:
 					qe->second.localPort = localPort;
 					qe->second.localPort = localPort;
 					qe->second.fromAddr = fromAddr;
 					qe->second.fromAddr = fromAddr;
 				}
 				}
-				if (r == PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN)
-					_requestWhois(source);
 			}
 			}
 		}
 		}
 	} catch (std::exception &ex) {
 	} catch (std::exception &ex) {
@@ -569,17 +567,30 @@ void Switch::_propagateMulticast(const SharedPtr<Network> &network,const Address
 	SharedPtr<Peer> propPeers[ZT_MULTICAST_PROPAGATION_BREADTH];
 	SharedPtr<Peer> propPeers[ZT_MULTICAST_PROPAGATION_BREADTH];
 	unsigned int np = _multicaster.pickNextPropagationPeers(*(_r->topology),network->id(),mg,upstream,newBloom,ZT_MULTICAST_PROPAGATION_BREADTH,propPeers,Utils::now());
 	unsigned int np = _multicaster.pickNextPropagationPeers(*(_r->topology),network->id(),mg,upstream,newBloom,ZT_MULTICAST_PROPAGATION_BREADTH,propPeers,Utils::now());
 
 
+	if (!np)
+		return;
+
+	std::string signature(Multicaster::signMulticastPacket(_r->identity,from,mg,etherType,data,len));
+	if (!signature.length()) {
+		TRACE("failure signing multicast message!");
+		return;
+	}
+
 	for(unsigned int i=0;i<np;++i) {
 	for(unsigned int i=0;i<np;++i) {
 		Packet outp(propPeers[i]->address(),_r->identity.address(),Packet::VERB_MULTICAST_FRAME);
 		Packet outp(propPeers[i]->address(),_r->identity.address(),Packet::VERB_MULTICAST_FRAME);
+		outp.append((uint8_t)0);
 		outp.append((uint64_t)network->id());
 		outp.append((uint64_t)network->id());
+		outp.append(_r->identity.address().data(),ZT_ADDRESS_LENGTH);
+		outp.append(from.data,6);
 		outp.append(mg.mac().data,6);
 		outp.append(mg.mac().data,6);
 		outp.append((uint32_t)mg.adi());
 		outp.append((uint32_t)mg.adi());
 		outp.append(newBloom.data(),ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE);
 		outp.append(newBloom.data(),ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE);
 		outp.append((uint8_t)mcHops);
 		outp.append((uint8_t)mcHops);
-		outp.append((unsigned char)0,2); // reserved, 0
-		outp.append(from.data,6);
 		outp.append((uint16_t)etherType);
 		outp.append((uint16_t)etherType);
+		outp.append((uint16_t)len);
+		outp.append((uint16_t)signature.length());
 		outp.append(data,len);
 		outp.append(data,len);
+		outp.append(signature.data(),signature.length());
 		outp.compress();
 		outp.compress();
 		send(outp,true);
 		send(outp,true);
 	}
 	}
@@ -747,45 +758,6 @@ Switch::PacketServiceAttemptResult Switch::_tryHandleRemotePacket(Demarc::Port l
 					TRACE("dropped FRAME from %s: unexpected exception: (unknown)",source.toString().c_str());
 					TRACE("dropped FRAME from %s: unexpected exception: (unknown)",source.toString().c_str());
 				}
 				}
 				break;
 				break;
-			case Packet::VERB_MULTICAST_FRAME:
-				try {
-					SharedPtr<Network> network(_r->nc->network(packet.at<uint64_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID)));
-					if (network) {
-						if (network->isAllowed(source)) {
-							if (packet.size() > ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD) {
-								MulticastGroup mg(MAC(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_MULTICAST_MAC,6)),packet.at<uint32_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI));
-								unsigned int hops = packet[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOPS];
-								MAC fromMac(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FROM_MAC,6));
-								unsigned int etherType = packet.at<uint16_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE);
-								unsigned int payloadLen = packet.size() - ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD;
-								unsigned char *payload = packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,payloadLen);
-
-								if (fromMac == network->tap().mac()) {
-									TRACE("dropped boomerang MULTICAST_FRAME from %s",source.toString().c_str());
-								} if (network->isAllowed(fromMac)) {
-									if (_multicaster.checkAndUpdateMulticastHistory(fromMac,mg,payload,payloadLen,network->id(),now)) {
-										// TODO: check if allowed etherType
-										network->tap().put(fromMac,mg.mac(),etherType,payload,payloadLen);
-									} else {
-										TRACE("duplicate MULTICAST_FRAME from %s: %s -> %s (adi: %.8lx), %u bytes, net: %llu",source.toString().c_str(),fromMac.toString().c_str(),mg.mac().toString().c_str(),(unsigned long)mg.adi(),packet.size() - ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,network->id());
-									}
-									_propagateMulticast(network,source,packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM,ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE),mg,hops+1,fromMac,etherType,payload,payloadLen);
-								} else {
-									TRACE("dropped MULTICAST_FRAME from %s: ultimate sender %s not a member of closed network %llu",source.toString().c_str(),fromMac.toString().c_str(),network->id());
-								}
-							}
-						} else {
-							TRACE("dropped MULTICAST_FRAME from %s: not a member of closed network %llu",source.toString().c_str(),network->id());
-						}
-					} else {
-						TRACE("dropped MULTICAST_FRAME from %s: network %llu unknown",source.toString().c_str(),packet.at<uint64_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID));
-					}
-				} catch (std::exception &ex) {
-					TRACE("dropped MULTICAST_FRAME from %s: unexpected exception: %s",source.toString().c_str(),ex.what());
-				} catch ( ... ) {
-					TRACE("dropped MULTICAST_FRAME from %s: unexpected exception: (unknown)",source.toString().c_str());
-				}
-				break;
 			case Packet::VERB_MULTICAST_LIKE:
 			case Packet::VERB_MULTICAST_LIKE:
 				try {
 				try {
 					unsigned int ptr = ZT_PACKET_IDX_PAYLOAD;
 					unsigned int ptr = ZT_PACKET_IDX_PAYLOAD;
@@ -821,6 +793,62 @@ Switch::PacketServiceAttemptResult Switch::_tryHandleRemotePacket(Demarc::Port l
 					TRACE("dropped MULTICAST_LIKE from %s: unexpected exception: (unknown)",source.toString().c_str());
 					TRACE("dropped MULTICAST_LIKE from %s: unexpected exception: (unknown)",source.toString().c_str());
 				}
 				}
 				break;
 				break;
+			case Packet::VERB_MULTICAST_FRAME:
+				try {
+					SharedPtr<Network> network(_r->nc->network(packet.at<uint64_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID)));
+					if (network) {
+						if (network->isAllowed(source)) {
+							if (packet.size() > ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD) {
+								Address originalSubmitterAddress(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS,ZT_ADDRESS_LENGTH));
+								if (originalSubmitterAddress == _r->identity.address()) {
+									TRACE("dropped boomerang MULTICAST_FRAME received from %s",source.toString().c_str());
+								} else {
+									SharedPtr<Peer> originalSubmitter(_r->topology->getPeer(originalSubmitterAddress));
+									if (!originalSubmitter) {
+										// If we don't know the original submitter, try to look them up
+										// and abort.
+										// TODO: need to rearchitect how we wait for and handle whois
+										// responses so they trigger a re-eval of this packet instantly.
+										_requestWhois(originalSubmitterAddress);
+										return PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN;
+									} else {
+										MAC fromMac(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC,6));
+										MulticastGroup mg(MAC(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DESTINATION_MAC,6)),packet.at<uint32_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI));
+										unsigned int hops = packet[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOP_COUNT];
+										unsigned int etherType = packet.at<uint16_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE);
+										unsigned int datalen = packet.at<uint16_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD_LENGTH);
+										unsigned int signaturelen = packet.at<uint16_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SIGNATURE_LENGTH);
+										unsigned char *dataAndSignature = packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,datalen + signaturelen);
+
+										if (Multicaster::verifyMulticastPacket(originalSubmitter->identity(),fromMac,mg,etherType,data,datalen,dataAndSignature + datalen,signaturelen)) {
+											if (network->isAllowed(originalSubmitterAddress)) {
+												if (_multicaster.checkAndUpdateMulticastHistory(fromMac,mg,payload,payloadLen,network->id(),now)) {
+													// TODO: check if allowed etherType
+													network->tap().put(fromMac,mg.mac(),etherType,payload,payloadLen);
+												} else {
+												}
+												_propagateMulticast(network,source,packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM,ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE),mg,hops+1,fromMac,etherType,payload,payloadLen);
+											} else {
+											}
+										} else {
+										}
+									}
+								}
+							} else {
+							}
+						} else {
+							TRACE("dropped MULTICAST_FRAME from %s: not a member of closed network %llu",source.toString().c_str(),network->id());
+						}
+					} else {
+						TRACE("dropped MULTICAST_FRAME from %s: network %llu unknown or we are not a member",source.toString().c_str(),packet.at<uint64_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID));
+					}
+				} catch (std::exception &ex) {
+					TRACE("dropped MULTICAST_FRAME from %s: unexpected exception: %s",source.toString().c_str(),ex.what());
+				} catch ( ... ) {
+					TRACE("dropped MULTICAST_FRAME from %s: unexpected exception: (unknown)",source.toString().c_str());
+				}
+				break;
+				break;
 			default:
 			default:
 				TRACE("ignored unrecognized verb %.2x from %s",(unsigned int)packet.verb(),source.toString().c_str());
 				TRACE("ignored unrecognized verb %.2x from %s",(unsigned int)packet.verb(),source.toString().c_str());
 				break;
 				break;
@@ -828,7 +856,10 @@ Switch::PacketServiceAttemptResult Switch::_tryHandleRemotePacket(Demarc::Port l
 
 
 		// Update peer timestamps and learn new links
 		// Update peer timestamps and learn new links
 		peer->onReceive(_r,localPort,fromAddr,latency,packet.hops(),packet.verb(),now);
 		peer->onReceive(_r,localPort,fromAddr,latency,packet.hops(),packet.verb(),now);
-	} else return PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN;
+	} else {
+		_requestWhois(source);
+		return PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN;
+	}
 
 
 	return PACKET_SERVICE_ATTEMPT_OK;
 	return PACKET_SERVICE_ATTEMPT_OK;
 }
 }