mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-02-21 10:01:46 +00:00
Multicast group join/leave and group membership announcement.
This commit is contained in:
parent
8001b2c0cb
commit
51f46a009a
@ -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
|
||||
|
128
node/Network.cpp
128
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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user