From 51f46a009a7de20153ff9594a407c55684468a1e Mon Sep 17 00:00:00 2001
From: Adam Ierymenko <adam.ierymenko@gmail.com>
Date: Mon, 6 Apr 2015 18:27:24 -0700
Subject: [PATCH] Multicast group join/leave and group membership announcement.

---
 include/ZeroTierOne.h |  40 ++++++++++---
 node/Network.cpp      | 128 +++++++++++++++++++++++++++++-------------
 node/Network.hpp      |  38 +++++++++++--
 node/Node.cpp         |  22 +++++++-
 node/Node.hpp         |  15 ++++-
 node/Peer.cpp         |  16 +++---
 6 files changed, 192 insertions(+), 67 deletions(-)

diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h
index 2d0e837a8..e787f7f11 100644
--- a/include/ZeroTierOne.h
+++ b/include/ZeroTierOne.h
@@ -322,6 +322,32 @@ typedef struct
 	unsigned long adi;
 } ZT1_MulticastGroup;
 
+/**
+ * Virtual network configuration update type
+ */
+enum ZT1_VirtualNetworkConfigOperation
+{
+	/**
+	 * Network is coming up (either for the first time or after service restart)
+	 */
+	ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_UP = 1,
+
+	/**
+	 * Network configuration has been updated
+	 */
+	ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE = 2,
+
+	/**
+	 * Network is going down (not permanently)
+	 */
+	ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN = 3,
+
+	/**
+	 * Network is going down permanently (leave/delete)
+	 */
+	ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY = 4
+};
+
 /**
  * Virtual LAN configuration
  */
@@ -548,14 +574,14 @@ typedef void ZT1_Node;
 /****************************************************************************/
 
 /**
- * Callback called to update virtual port configuration
+ * Callback called to update virtual network port configuration
  *
  * This can be called at any time to update the configuration of a virtual
- * network port. If a port is deleted (via leave() or otherwise) this is
- * called with a NULL config parameter.
+ * network port. The parameter after the network ID specifies whether this
+ * port is being brought up, updated, brought down, or permanently deleted.
  *
  * This in turn should be used by the underlying implementation to create
- * and configure tap devices to handle frames, etc.
+ * and configure tap devices at the OS (or virtual network stack) layer.
  *
  * The supplied config pointer is not guaranteed to remain valid, so make
  * a copy if you want one.
@@ -564,7 +590,7 @@ typedef void ZT1_Node;
  * on failure, and this results in the network being placed into the
  * PORT_ERROR state.
  */
-typedef int (*ZT1_VirtualNetworkConfigFunction)(ZT1_Node *,uint64_t,const ZT1_VirtualNetworkConfig *);
+typedef int (*ZT1_VirtualNetworkConfigFunction)(ZT1_Node *,uint64_t,enum ZT1_VirtualNetworkConfigOperation,const ZT1_VirtualNetworkConfig *);
 
 /**
  * Callback for status messages
@@ -771,7 +797,7 @@ enum ZT1_ResultCode ZT1_Node_leave(ZT1_Node *node,uint64_t nwid);
  * If this is not done, ARP will not work reliably.
  *
  * Multiple calls to subscribe to the same multicast address will have no
- * effect.
+ * effect. It is perfectly safe to do this.
  *
  * This does not generate an update call to networkConfigCallback().
  *
@@ -836,7 +862,7 @@ ZT1_VirtualNetworkConfig *ZT1_Node_networkConfig(ZT1_Node *node,uint64_t nwid);
  * @param node Node instance
  * @return List of networks or NULL on failure
  */
-ZT1_VirtualNetworkList *ZT1_Node_listNetworks(ZT1_Node *node);
+ZT1_VirtualNetworkList *ZT1_Node_networks(ZT1_Node *node);
 
 /**
  * Free a query result buffer
diff --git a/node/Network.cpp b/node/Network.cpp
index 7b033181b..fb31f87ce 100644
--- a/node/Network.cpp
+++ b/node/Network.cpp
@@ -100,20 +100,25 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid) :
 
 	ZT1_VirtualNetworkConfig ctmp;
 	_externalConfig(&ctmp);
-	_portError = RR->node->configureVirtualNetworkPort(_id,&ctmp);
+	_portError = RR->node->configureVirtualNetworkPort(_id,ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp);
 }
 
 Network::~Network()
 {
-	RR->node->configureVirtualNetworkPort(_id,(const ZT1_VirtualNetworkConfig *)0);
+	ZT1_VirtualNetworkConfig ctmp;
+	_externalConfig(&ctmp);
 
 	char n[128];
 	if (_destroyed) {
+		RR->node->configureVirtualNetworkPort(_id,ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp);
+
 		Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id);
 		RR->node->dataStoreDelete(n);
 		Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.mcerts",_id);
 		RR->node->dataStoreDelete(n);
 	} else {
+		RR->node->configureVirtualNetworkPort(_id,ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN,&ctmp);
+
 		clean();
 
 		std::string buf("ZTMCD0");
@@ -132,49 +137,35 @@ Network::~Network()
 	}
 }
 
-// Function object used by rescanMulticastGroups()
-class AnnounceMulticastGroupsToPeersWithActiveDirectPaths
+void Network::multicastSubscribe(const MulticastGroup &mg)
 {
-public:
-	AnnounceMulticastGroupsToPeersWithActiveDirectPaths(const RuntimeEnvironment *renv,Network *nw) :
-		RR(renv),
-		_now(Utils::now()),
-		_network(nw),
-		_supernodeAddresses(renv->topology->supernodeAddresses())
-	{}
+	Mutex::Lock _l(_lock);
+	if (std::find(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg) != _myMulticastGroups.end())
+		return;
+	_myMulticastGroups.push_back(mg);
+	std::sort(_myMulticastGroups.begin(),_myMulticastGroups.end());
+}
 
-	inline void operator()(Topology &t,const SharedPtr<Peer> &p)
+void Network::multicastUnsubscribe(const MulticastGroup &mg)
+{
+	bool needAnnounce = false;
 	{
-		if ( ( (p->hasActiveDirectPath(_now)) && (_network->isAllowed(p->address())) ) || (std::find(_supernodeAddresses.begin(),_supernodeAddresses.end(),p->address()) != _supernodeAddresses.end()) ) {
-			Packet outp(p->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE);
+		Mutex::Lock _l(_lock);
 
-			std::vector<MulticastGroup> mgs(_network->multicastGroups());
-			for(std::vector<MulticastGroup>::iterator mg(mgs.begin());mg!=mgs.end();++mg) {
-				if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) {
-					outp.armor(p->key(),true);
-					p->send(RR,outp.data(),outp.size(),_now);
-					outp.reset(p->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE);
-				}
+		std::vector<MulticastGroup> nmg;
+		for(std::vector<MulticastGroup>::const_iterator i(_myMulticastGroups.begin());i!=_myMulticastGroups.end();++i) {
+			if (*i != mg)
+				nmg.push_back(*i);
+		}
 
-				// network ID, MAC, ADI
-				outp.append((uint64_t)_network->id());
-				mg->mac().appendTo(outp);
-				outp.append((uint32_t)mg->adi());
-			}
-
-			if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) {
-				outp.armor(p->key(),true);
-				p->send(RR,outp.data(),outp.size(),_now);
-			}
+		if (nmg.size() != _myMulticastGroups.size()) {
+			_myMulticastGroups.swap(nmg);
+			needAnnounce = true;
 		}
 	}
-
-private:
-	const RuntimeEnvironment *RR;
-	uint64_t _now;
-	Network *_network;
-	std::vector<Address> _supernodeAddresses;
-};
+	if (needAnnounce)
+		_announceMulticastGroups();
+}
 
 bool Network::applyConfiguration(const SharedPtr<NetworkConfig> &conf)
 {
@@ -189,7 +180,7 @@ bool Network::applyConfiguration(const SharedPtr<NetworkConfig> &conf)
 
 			ZT1_VirtualNetworkConfig ctmp;
 			_externalConfig(&ctmp);
-			_portError = RR->node->configureVirtualNetworkPort(_id,&ctmp);
+			_portError = RR->node->configureVirtualNetworkPort(_id,ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE,&ctmp);
 
 			return true;
 		} else {
@@ -405,6 +396,15 @@ void Network::learnBridgeRoute(const MAC &mac,const Address &addr)
 	}
 }
 
+void Network::learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now)
+{
+	Mutex::Lock _l(_lock);
+	unsigned long tmp = _multicastGroupsBehindMe.size();
+	_multicastGroupsBehindMe[mg] = now;
+	if (tmp != _multicastGroupsBehindMe.size())
+		_announceMulticastGroups();
+}
+
 void Network::setEnabled(bool enabled)
 {
 	Mutex::Lock _l(_lock);
@@ -467,4 +467,54 @@ void Network::_externalConfig(ZT1_VirtualNetworkConfig *ec) const
 	} else ec->assignedAddressCount = 0;
 }
 
+// Used in Network::_announceMulticastGroups()
+class _AnnounceMulticastGroupsToPeersWithActiveDirectPaths
+{
+public:
+	_AnnounceMulticastGroupsToPeersWithActiveDirectPaths(const RuntimeEnvironment *renv,Network *nw) :
+		RR(renv),
+		_now(Utils::now()),
+		_network(nw),
+		_supernodeAddresses(renv->topology->supernodeAddresses())
+	{}
+
+	inline void operator()(Topology &t,const SharedPtr<Peer> &p)
+	{
+		if ( ( (p->hasActiveDirectPath(_now)) && (_network->isAllowed(p->address())) ) || (std::find(_supernodeAddresses.begin(),_supernodeAddresses.end(),p->address()) != _supernodeAddresses.end()) ) {
+			Packet outp(p->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE);
+
+			std::vector<MulticastGroup> mgs(_network->allMulticastGroups());
+			for(std::vector<MulticastGroup>::iterator mg(mgs.begin());mg!=mgs.end();++mg) {
+				if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) {
+					outp.armor(p->key(),true);
+					p->send(RR,outp.data(),outp.size(),_now);
+					outp.reset(p->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE);
+				}
+
+				// network ID, MAC, ADI
+				outp.append((uint64_t)_network->id());
+				mg->mac().appendTo(outp);
+				outp.append((uint32_t)mg->adi());
+			}
+
+			if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) {
+				outp.armor(p->key(),true);
+				p->send(RR,outp.data(),outp.size(),_now);
+			}
+		}
+	}
+
+private:
+	const RuntimeEnvironment *RR;
+	uint64_t _now;
+	Network *_network;
+	std::vector<Address> _supernodeAddresses;
+};
+
+void Network::_announceMulticastGroups()
+{
+	_AnnounceMulticastGroupsToPeersWithActiveDirectPaths afunc(RR,this);
+	RR->topology->eachPeer<_AnnounceMulticastGroupsToPeersWithActiveDirectPaths &>(afunc);
+}
+
 } // namespace ZeroTier
diff --git a/node/Network.hpp b/node/Network.hpp
index b51164a3e..ada491a6a 100644
--- a/node/Network.hpp
+++ b/node/Network.hpp
@@ -89,7 +89,7 @@ public:
 	inline Address controller() throw() { return Address(_id >> 24); }
 
 	/**
-	 * @return Latest list of multicast groups for this network's tap
+	 * @return Multicast group memberships for this network's port (local, not learned via bridging)
 	 */
 	inline std::vector<MulticastGroup> multicastGroups() const
 	{
@@ -97,6 +97,21 @@ public:
 		return _myMulticastGroups;
 	}
 
+	/**
+	 * @return All multicast groups including learned groups that are behind any bridges we're attached to
+	 */
+	inline std::vector<MulticastGroup> allMulticastGroups() const
+	{
+		Mutex::Lock _l(_lock);
+		std::vector<MulticastGroup> mgs(_myMulticastGroups);
+		for(std::map< MulticastGroup,uint64_t >::const_iterator i(_multicastGroupsBehindMe.begin());i!=_multicastGroupsBehindMe.end();++i) {
+			if (std::find(mgs.begin(),mgs.end(),i->first) == mgs.end())
+				mgs.push_back(i->first);
+		}
+		std::sort(mgs.begin(),mgs.end());
+		return mgs;
+	}
+
 	/**
 	 * @param mg Multicast group
 	 * @return True if this network endpoint / peer is a member
@@ -107,6 +122,20 @@ public:
 		return (std::find(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg) != _myMulticastGroups.end());
 	}
 
+	/**
+	 * Subscribe to a multicast group
+	 *
+	 * @param mg New multicast group
+	 */
+	void multicastSubscribe(const MulticastGroup &mg);
+
+	/**
+	 * Unsubscribe from a multicast group
+	 *
+	 * @param mg Multicast group
+	 */
+	void multicastUnsubscribe(const MulticastGroup &mg);
+
 	/**
 	 * Apply a NetworkConfig to this network
 	 *
@@ -308,11 +337,7 @@ public:
 	 * @param mg Multicast group
 	 * @param now Current time
 	 */
-	inline void learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now)
-	{
-		Mutex::Lock _l(_lock);
-		_multicastGroupsBehindMe[mg] = now;
-	}
+	void learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now);
 
 	/**
 	 * @return True if traffic on this network's tap is enabled
@@ -336,6 +361,7 @@ public:
 private:
 	ZT1_VirtualNetworkStatus _status() const;
 	void _externalConfig(ZT1_VirtualNetworkConfig *ec) const; // assumes _lock is locked
+	void _announceMulticastGroups();
 
 	const RuntimeEnvironment *RR;
 	uint64_t _id;
diff --git a/node/Node.cpp b/node/Node.cpp
index 29262eda6..2167d9c10 100644
--- a/node/Node.cpp
+++ b/node/Node.cpp
@@ -151,10 +151,18 @@ ZT1_ResultCode Node::leave(uint64_t nwid)
 
 ZT1_ResultCode Node::multicastSubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi)
 {
+	Mutex::Lock _l(_networks_m);
+	std::map< uint64_t,SharedPtr<Network> >::iterator nw(_networks.find(nwid));
+	if (nw != _networks.end())
+		nw->second->multicastSubscribe(MulticastGroup(MAC(multicastGroup,(uint32_t)(multicastAdi & 0xffffffff))));
 }
 
 ZT1_ResultCode Node::multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi)
 {
+	Mutex::Lock _l(_networks_m);
+	std::map< uint64_t,SharedPtr<Network> >::iterator nw(_networks.find(nwid));
+	if (nw != _networks.end())
+		nw->second->multicastUnsubscribe(MulticastGroup(MAC(multicastGroup,(uint32_t)(multicastAdi & 0xffffffff))));
 }
 
 void Node::status(ZT1_NodeStatus *status)
@@ -167,9 +175,17 @@ ZT1_PeerList *Node::peers()
 
 ZT1_VirtualNetworkConfig *Node::networkConfig(uint64_t nwid)
 {
+	Mutex::Lock _l(_networks_m);
+	std::map< uint64_t,SharedPtr<Network> >::iterator nw(_networks.find(nwid));
+	if (nw != _networks.end()) {
+		ZT1_VirtualNetworkConfig *nc = (ZT1_VirtualNetworkConfig *)::malloc(sizeof(ZT1_VirtualNetworkConfig));
+		nw->second->externalConfig(nc);
+		return nc;
+	}
+	return (ZT1_VirtualNetworkConfig *)0;
 }
 
-ZT1_VirtualNetworkList *Node::listNetworks()
+ZT1_VirtualNetworkList *Node::networks()
 {
 }
 
@@ -344,7 +360,7 @@ void ZT1_Node_status(ZT1_Node *node,ZT1_NodeStatus *status)
 ZT1_PeerList *ZT1_Node_peers(ZT1_Node *node)
 {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->peers();
+		return reinterpret_cast<ZeroTier::Node *>(node)->listPeers();
 	} catch ( ... ) {
 		return (ZT1_PeerList *)0;
 	}
@@ -359,7 +375,7 @@ ZT1_VirtualNetworkConfig *ZT1_Node_networkConfig(ZT1_Node *node,uint64_t nwid)
 	}
 }
 
-ZT1_VirtualNetworkList *ZT1_Node_listNetworks(ZT1_Node *node)
+ZT1_VirtualNetworkList *ZT1_Node_networks(ZT1_Node *node)
 {
 	try {
 		return reinterpret_cast<ZeroTier::Node *>(node)->listNetworks();
diff --git a/node/Node.hpp b/node/Node.hpp
index 5b158228e..07bbbf8b4 100644
--- a/node/Node.hpp
+++ b/node/Node.hpp
@@ -92,8 +92,8 @@ public:
 	ZT1_ResultCode multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi);
 	void status(ZT1_NodeStatus *status);
 	ZT1_PeerList *peers();
-	ZT1_VirtualNetworkConfig *networkConfig(uint64_t nwid);
-	ZT1_VirtualNetworkList *listNetworks();
+	bool networkConfig(uint64_t nwid,ZT1_VirtualNetworkConfig *ec);
+	ZT1_VirtualNetworkList *networks();
 	void freeQueryResult(void *qr);
 	void setNetconfMaster(void *networkConfigMasterInstance);
 
@@ -154,6 +154,15 @@ public:
 		return ((nw == _networks.end()) ? SharedPtr<Network>() : nw->second);
 	}
 
+	inline std::vector< SharedPtr<Network> > allNetworks() const
+	{
+		Mutex::Lock _l(_networks_m);
+		std::vector< SharedPtr<Network> > nw;
+		for(std::map< uint64_t,SharedPtr<Network> >::const_iterator n(_networks.begin());n!=_networks.end();++n)
+			nw.push_back(n->second);
+		return nw;
+	}
+
 	inline bool dataStorePut(const char *name,const void *data,unsigned int len,bool secure) { return (_dataStorePutFunction(reinterpret_cast<ZT1_Node *>(this),name,data,len,(int)secure) == 0); }
 	inline bool dataStorePut(const char *name,const std::string &data,bool secure) { return dataStorePut(name,(const void *)data.data(),(unsigned int)data.length(),secure); }
 	inline void dataStoreDelete(const char *name) { _dataStorePutFunction(reinterpret_cast<ZT1_Node *>(this),name,(const void *)0,0,0); }
@@ -161,7 +170,7 @@ public:
 
 	inline void postEvent(ZT1_Event ev) { _statusCallback(reinterpret_cast<ZT1_Node *>(this),ev); }
 
-	inline int configureVirtualNetworkPort(uint64_t nwid,const ZT1_VirtualNetworkConfig *nc) { return _virtualNetworkConfigFunction(reinterpret_cast<ZT1_Node *>(this),nwid,nc); }
+	inline int configureVirtualNetworkPort(uint64_t nwid,ZT1_VirtualNetworkConfigOperation op,const ZT1_VirtualNetworkConfig *nc) { return _virtualNetworkConfigFunction(reinterpret_cast<ZT1_Node *>(this),nwid,op,nc); }
 
 	void postNewerVersionIfNewer(unsigned int major,unsigned int minor,unsigned int rev);
 
diff --git a/node/Peer.cpp b/node/Peer.cpp
index 0bff2aae4..076dbb410 100644
--- a/node/Peer.cpp
+++ b/node/Peer.cpp
@@ -106,20 +106,18 @@ void Peer::received(
 
 		/* 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. The other place this is done
-		 * is in rescanMulticastGroups() in Network, but that only sends something
-		 * if a network's multicast groups change. */
+		 * supernodes and network controllers. */
 		if ((now - _lastAnnouncedTo) >= ((ZT_MULTICAST_LIKE_EXPIRE / 2) - 1000)) {
 			_lastAnnouncedTo = now;
 
-			bool isSupernode = RR->topology->isSupernode(_id.address());
+			const bool isSupernode = RR->topology->isSupernode(_id.address());
 
 			Packet outp(_id.address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE);
-			std::vector< SharedPtr<Network> > networks(RR->nc->networks());
-			for(std::vector< SharedPtr<Network> >::iterator n(networks.begin());n!=networks.end();++n) {
-				if ( ((*n)->isAllowed(_id.address())) || (isSupernode) ) {
-					std::set<MulticastGroup> mgs((*n)->multicastGroups());
-					for(std::set<MulticastGroup>::iterator mg(mgs.begin());mg!=mgs.end();++mg) {
+			const std::vector< SharedPtr<Network> > networks(RR->node->allNetworks());
+			for(std::vector< SharedPtr<Network> >::const_iterator n(networks.begin());n!=networks.end();++n) {
+				if ( (isSupernode) || ((*n)->isAllowed(_id.address())) ) {
+					const std::vector<MulticastGroup> mgs((*n)->allMulticastGroups());
+					for(std::vector<MulticastGroup>::const_iterator mg(mgs.begin());mg!=mgs.end();++mg) {
 						if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) {
 							outp.armor(_key,true);
 							RR->node->putPacket(remoteAddr,outp.data(),outp.size(),linkDesperation,false);