Browse Source

Plumb through localInterfaceId to track local interfaces corresponding with remote addresses.

Adam Ierymenko 10 years ago
parent
commit
367ffde00c
12 changed files with 155 additions and 93 deletions
  1. 62 26
      include/ZeroTierOne.h
  2. 29 29
      node/IncomingPacket.cpp
  3. 4 1
      node/IncomingPacket.hpp
  4. 5 3
      node/Node.cpp
  5. 4 1
      node/Node.hpp
  6. 11 10
      node/Peer.cpp
  7. 4 1
      node/Peer.hpp
  8. 7 2
      node/RemotePath.hpp
  9. 15 12
      node/Switch.cpp
  10. 9 5
      node/Switch.hpp
  11. 1 1
      node/Topology.cpp
  12. 4 2
      service/OneService.cpp

+ 62 - 26
include/ZeroTierOne.h

@@ -656,7 +656,12 @@ typedef void ZT1_Node;
  * on failure, and this results in the network being placed into the
  * on failure, and this results in the network being placed into the
  * PORT_ERROR state.
  * PORT_ERROR state.
  */
  */
-typedef int (*ZT1_VirtualNetworkConfigFunction)(ZT1_Node *,void *,uint64_t,enum ZT1_VirtualNetworkConfigOperation,const ZT1_VirtualNetworkConfig *);
+typedef int (*ZT1_VirtualNetworkConfigFunction)(
+	ZT1_Node *,
+	void *,
+	uint64_t,
+	enum ZT1_VirtualNetworkConfigOperation,
+	const ZT1_VirtualNetworkConfig *);
 
 
 /**
 /**
  * Function to send a frame out to a virtual network port
  * Function to send a frame out to a virtual network port
@@ -665,7 +670,16 @@ typedef int (*ZT1_VirtualNetworkConfigFunction)(ZT1_Node *,void *,uint64_t,enum
  * (5) destination MAC, (6) ethertype, (7) VLAN ID, (8) frame data,
  * (5) destination MAC, (6) ethertype, (7) VLAN ID, (8) frame data,
  * (9) frame length.
  * (9) frame length.
  */
  */
-typedef void (*ZT1_VirtualNetworkFrameFunction)(ZT1_Node *,void *,uint64_t,uint64_t,uint64_t,unsigned int,unsigned int,const void *,unsigned int);
+typedef void (*ZT1_VirtualNetworkFrameFunction)(
+	ZT1_Node *,
+	void *,
+	uint64_t,
+	uint64_t,
+	uint64_t,
+	unsigned int,
+	unsigned int,
+	const void *,
+	unsigned int);
 
 
 /**
 /**
  * Callback for events
  * Callback for events
@@ -676,7 +690,11 @@ typedef void (*ZT1_VirtualNetworkFrameFunction)(ZT1_Node *,void *,uint64_t,uint6
  * whether it is present at all) is event type dependent. See the comments
  * whether it is present at all) is event type dependent. See the comments
  * in the definition of ZT1_Event.
  * in the definition of ZT1_Event.
  */
  */
-typedef void (*ZT1_EventCallback)(ZT1_Node *,void *,enum ZT1_Event,const void *);
+typedef void (*ZT1_EventCallback)(
+	ZT1_Node *,
+	void *,
+	enum ZT1_Event,
+	const void *);
 
 
 /**
 /**
  * Function to get an object from the data store
  * Function to get an object from the data store
@@ -698,7 +716,14 @@ typedef void (*ZT1_EventCallback)(ZT1_Node *,void *,enum ZT1_Event,const void *)
  * read. The caller may call the function multiple times to read the whole
  * read. The caller may call the function multiple times to read the whole
  * object.
  * object.
  */
  */
-typedef long (*ZT1_DataStoreGetFunction)(ZT1_Node *,void *,const char *,void *,unsigned long,unsigned long,unsigned long *);
+typedef long (*ZT1_DataStoreGetFunction)(
+	ZT1_Node *,
+	void *,
+	const char *,
+	void *,
+	unsigned long,
+	unsigned long,
+	unsigned long *);
 
 
 /**
 /**
  * Function to store an object in the data store
  * Function to store an object in the data store
@@ -716,19 +741,40 @@ typedef long (*ZT1_DataStoreGetFunction)(ZT1_Node *,void *,const char *,void *,u
  * If the data pointer is null, this must be interpreted as a delete
  * If the data pointer is null, this must be interpreted as a delete
  * operation.
  * operation.
  */
  */
-typedef int (*ZT1_DataStorePutFunction)(ZT1_Node *,void *,const char *,const void *,unsigned long,int);
+typedef int (*ZT1_DataStorePutFunction)(
+	ZT1_Node *,
+	void *,
+	const char *,
+	const void *,
+	unsigned long,
+	int);
 
 
 /**
 /**
  * Function to send a ZeroTier packet out over the wire
  * Function to send a ZeroTier packet out over the wire
  *
  *
- * Parameters: (1) node, (2) user ptr, (3) address, (4) packet data,
- * (5) packet data length.
+ * Parameters:
+ *  (1) Node
+ *  (2) User pointer
+ *  (3) Local interface ID, -1==unspcified/random
+ *  (4) Remote address
+ *  (5) Packet data
+ *  (6) Packet length
+ *
+ * If you have only one local interface it is fine to ignore the local
+ * interface ID field. This is used to support different local interface
+ * endpoints and differentiation between them.
  *
  *
  * The function must return zero on success and may return any error code
  * The function must return zero on success and may return any error code
  * on failure. Note that success does not (of course) guarantee packet
  * on failure. Note that success does not (of course) guarantee packet
  * delivery. It only means that the packet appears to have been sent.
  * delivery. It only means that the packet appears to have been sent.
  */
  */
-typedef int (*ZT1_WirePacketSendFunction)(ZT1_Node *,void *,const struct sockaddr_storage *,const void *,unsigned int);
+typedef int (*ZT1_WirePacketSendFunction)(
+	ZT1_Node *,                      /* Node */
+	void *,                          /* User ptr */
+	int,                             /* Local interface ID, -1 for unspecified/random */
+	const struct sockaddr_storage *, /* Remote address */
+	const void *,                    /* Packet data */
+	unsigned int);                   /* Packet length */
 
 
 /****************************************************************************/
 /****************************************************************************/
 /* C Node API                                                               */
 /* C Node API                                                               */
@@ -747,7 +793,7 @@ typedef int (*ZT1_WirePacketSendFunction)(ZT1_Node *,void *,const struct sockadd
  * @param dataStorePutFunction Function called to put objects in persistent storage
  * @param dataStorePutFunction Function called to put objects in persistent storage
  * @param virtualNetworkConfigFunction Function to be called when virtual LANs are created, deleted, or their config parameters change
  * @param virtualNetworkConfigFunction Function to be called when virtual LANs are created, deleted, or their config parameters change
  * @param eventCallback Function to receive status updates and non-fatal error notices
  * @param eventCallback Function to receive status updates and non-fatal error notices
- * @param overrideRootTopology If not NULL, must contain string-serialize root topology (for testing, default: NULL)
+ * @param overrideRootTopology Alternative root server topology or NULL for default (mostly for test/debug use)
  * @return OK (0) or error code if a fatal error condition has occurred
  * @return OK (0) or error code if a fatal error condition has occurred
  */
  */
 enum ZT1_ResultCode ZT1_Node_new(
 enum ZT1_ResultCode ZT1_Node_new(
@@ -760,11 +806,7 @@ enum ZT1_ResultCode ZT1_Node_new(
 	ZT1_VirtualNetworkFrameFunction virtualNetworkFrameFunction,
 	ZT1_VirtualNetworkFrameFunction virtualNetworkFrameFunction,
 	ZT1_VirtualNetworkConfigFunction virtualNetworkConfigFunction,
 	ZT1_VirtualNetworkConfigFunction virtualNetworkConfigFunction,
 	ZT1_EventCallback eventCallback,
 	ZT1_EventCallback eventCallback,
-	const char *overrideRootTopology
-#ifdef __cplusplus
-    = (const char *)0               
-#endif
-    );
+	const char *overrideRootTopology);
 
 
 /**
 /**
  * Delete a node and free all resources it consumes
  * Delete a node and free all resources it consumes
@@ -781,6 +823,7 @@ void ZT1_Node_delete(ZT1_Node *node);
  *
  *
  * @param node Node instance
  * @param node Node instance
  * @param now Current clock in milliseconds
  * @param now Current clock in milliseconds
+ * @param localInterfaceId Local interface ID on which packet was received (use 0 if only one interface or unsure)
  * @param remoteAddress Origin of packet
  * @param remoteAddress Origin of packet
  * @param packetData Packet data
  * @param packetData Packet data
  * @param packetLength Packet length
  * @param packetLength Packet length
@@ -790,6 +833,7 @@ void ZT1_Node_delete(ZT1_Node *node);
 enum ZT1_ResultCode ZT1_Node_processWirePacket(
 enum ZT1_ResultCode ZT1_Node_processWirePacket(
 	ZT1_Node *node,
 	ZT1_Node *node,
 	uint64_t now,
 	uint64_t now,
+	const int localInterfaceId,
 	const struct sockaddr_storage *remoteAddress,
 	const struct sockaddr_storage *remoteAddress,
 	const void *packetData,
 	const void *packetData,
 	unsigned int packetLength,
 	unsigned int packetLength,
@@ -882,14 +926,10 @@ enum ZT1_ResultCode ZT1_Node_leave(ZT1_Node *node,uint64_t nwid);
  * @param node Node instance
  * @param node Node instance
  * @param nwid 64-bit network ID
  * @param nwid 64-bit network ID
  * @param multicastGroup Ethernet multicast or broadcast MAC (least significant 48 bits)
  * @param multicastGroup Ethernet multicast or broadcast MAC (least significant 48 bits)
- * @param multicastAdi Multicast ADI (least significant 32 bits only, default: 0)
+ * @param multicastAdi Multicast ADI (least significant 32 bits only, use 0 if not needed)
  * @return OK (0) or error code if a fatal error condition has occurred
  * @return OK (0) or error code if a fatal error condition has occurred
  */
  */
-enum ZT1_ResultCode ZT1_Node_multicastSubscribe(ZT1_Node *node,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi
-#ifdef __cplusplus
-    = 0
-#endif
-    );
+enum ZT1_ResultCode ZT1_Node_multicastSubscribe(ZT1_Node *node,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi);
 
 
 /**
 /**
  * Unsubscribe from an Ethernet multicast group (or all groups)
  * Unsubscribe from an Ethernet multicast group (or all groups)
@@ -902,14 +942,10 @@ enum ZT1_ResultCode ZT1_Node_multicastSubscribe(ZT1_Node *node,uint64_t nwid,uin
  * @param node Node instance
  * @param node Node instance
  * @param nwid 64-bit network ID
  * @param nwid 64-bit network ID
  * @param multicastGroup Ethernet multicast or broadcast MAC (least significant 48 bits)
  * @param multicastGroup Ethernet multicast or broadcast MAC (least significant 48 bits)
- * @param multicastAdi Multicast ADI (least significant 32 bits only, default: 0)
+ * @param multicastAdi Multicast ADI (least significant 32 bits only, use 0 if not needed)
  * @return OK (0) or error code if a fatal error condition has occurred
  * @return OK (0) or error code if a fatal error condition has occurred
  */
  */
-enum ZT1_ResultCode ZT1_Node_multicastUnsubscribe(ZT1_Node *node,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi
-#ifdef __cplusplus
-    = 0
-#endif
-    );
+enum ZT1_ResultCode ZT1_Node_multicastUnsubscribe(ZT1_Node *node,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi);
 
 
 /**
 /**
  * Get this node's 40-bit ZeroTier address
  * Get this node's 40-bit ZeroTier address

+ 29 - 29
node/IncomingPacket.cpp

@@ -69,7 +69,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR)
 			switch(verb()) {
 			switch(verb()) {
 				//case Packet::VERB_NOP:
 				//case Packet::VERB_NOP:
 				default: // ignore unknown verbs, but if they pass auth check they are "received"
 				default: // ignore unknown verbs, but if they pass auth check they are "received"
-					peer->received(RR,_remoteAddress,hops(),packetId(),verb(),0,Packet::VERB_NOP);
+					peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),verb(),0,Packet::VERB_NOP);
 					return true;
 					return true;
 				case Packet::VERB_HELLO:                          return _doHELLO(RR);
 				case Packet::VERB_HELLO:                          return _doHELLO(RR);
 				case Packet::VERB_ERROR:                          return _doERROR(RR,peer);
 				case Packet::VERB_ERROR:                          return _doERROR(RR,peer);
@@ -144,7 +144,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 						Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE);
 						Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE);
 						nconf->com().serialize(outp);
 						nconf->com().serialize(outp);
 						outp.armor(peer->key(),true);
 						outp.armor(peer->key(),true);
-						RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+						RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 					}
 					}
 				}
 				}
 			}	break;
 			}	break;
@@ -165,7 +165,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 			default: break;
 			default: break;
 		}
 		}
 
 
-		peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_ERROR,inRePacketId,inReVerb);
+		peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_ERROR,inRePacketId,inReVerb);
 	} catch (std::exception &ex) {
 	} catch (std::exception &ex) {
 		TRACE("dropped ERROR from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 		TRACE("dropped ERROR from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 	} catch ( ... ) {
 	} catch ( ... ) {
@@ -231,7 +231,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR)
 						outp.append(packetId());
 						outp.append(packetId());
 						outp.append((unsigned char)Packet::ERROR_IDENTITY_COLLISION);
 						outp.append((unsigned char)Packet::ERROR_IDENTITY_COLLISION);
 						outp.armor(key,true);
 						outp.armor(key,true);
-						RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+						RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 					} else {
 					} else {
 						RR->node->postEvent(ZT1_EVENT_AUTHENTICATION_FAILURE,(const void *)&_remoteAddress);
 						RR->node->postEvent(ZT1_EVENT_AUTHENTICATION_FAILURE,(const void *)&_remoteAddress);
 						TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_remoteAddress.toString().c_str());
 						TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_remoteAddress.toString().c_str());
@@ -278,7 +278,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR)
 
 
 		// VALID -- continues here
 		// VALID -- continues here
 
 
-		peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_HELLO,0,Packet::VERB_NOP);
+		peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_HELLO,0,Packet::VERB_NOP);
 		peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision);
 		peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision);
 
 
 		bool trusted = false;
 		bool trusted = false;
@@ -316,7 +316,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR)
 		}
 		}
 
 
 		outp.armor(peer->key(),true);
 		outp.armor(peer->key(),true);
-		RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+		RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 	} catch (std::exception &ex) {
 	} catch (std::exception &ex) {
 		TRACE("dropped HELLO from %s(%s): %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 		TRACE("dropped HELLO from %s(%s): %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 	} catch ( ... ) {
 	} catch ( ... ) {
@@ -436,7 +436,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
 			default: break;
 			default: break;
 		}
 		}
 
 
-		peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_OK,inRePacketId,inReVerb);
+		peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_OK,inRePacketId,inReVerb);
 	} catch (std::exception &ex) {
 	} catch (std::exception &ex) {
 		TRACE("dropped OK from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 		TRACE("dropped OK from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 	} catch ( ... ) {
 	} catch ( ... ) {
@@ -456,7 +456,7 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 				outp.append(packetId());
 				outp.append(packetId());
 				queried->identity().serialize(outp,false);
 				queried->identity().serialize(outp,false);
 				outp.armor(peer->key(),true);
 				outp.armor(peer->key(),true);
-				RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+				RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 			} else {
 			} else {
 				Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
 				Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
 				outp.append((unsigned char)Packet::VERB_WHOIS);
 				outp.append((unsigned char)Packet::VERB_WHOIS);
@@ -464,12 +464,12 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 				outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND);
 				outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND);
 				outp.append(payload(),ZT_ADDRESS_LENGTH);
 				outp.append(payload(),ZT_ADDRESS_LENGTH);
 				outp.armor(peer->key(),true);
 				outp.armor(peer->key(),true);
-				RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+				RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 			}
 			}
 		} else {
 		} else {
 			TRACE("dropped WHOIS from %s(%s): missing or invalid address",source().toString().c_str(),_remoteAddress.toString().c_str());
 			TRACE("dropped WHOIS from %s(%s): missing or invalid address",source().toString().c_str(),_remoteAddress.toString().c_str());
 		}
 		}
-		peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_WHOIS,0,Packet::VERB_NOP);
+		peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_WHOIS,0,Packet::VERB_NOP);
 	} catch ( ... ) {
 	} catch ( ... ) {
 		TRACE("dropped WHOIS from %s(%s): unexpected exception",source().toString().c_str(),_remoteAddress.toString().c_str());
 		TRACE("dropped WHOIS from %s(%s): unexpected exception",source().toString().c_str(),_remoteAddress.toString().c_str());
 	}
 	}
@@ -487,8 +487,8 @@ bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,const SharedPtr<
 			if ((port > 0)&&((addrlen == 4)||(addrlen == 16))) {
 			if ((port > 0)&&((addrlen == 4)||(addrlen == 16))) {
 				InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port);
 				InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port);
 				TRACE("RENDEZVOUS from %s says %s might be at %s, starting NAT-t",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
 				TRACE("RENDEZVOUS from %s says %s might be at %s, starting NAT-t",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
-				peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_RENDEZVOUS,0,Packet::VERB_NOP);
-				RR->sw->rendezvous(withPeer,atAddr);
+				peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_RENDEZVOUS,0,Packet::VERB_NOP);
+				RR->sw->rendezvous(withPeer,_localInterfaceId,atAddr);
 			} else {
 			} else {
 				TRACE("dropped corrupt RENDEZVOUS from %s(%s) (bad address or port)",peer->address().toString().c_str(),_remoteAddress.toString().c_str());
 				TRACE("dropped corrupt RENDEZVOUS from %s(%s) (bad address or port)",peer->address().toString().c_str(),_remoteAddress.toString().c_str());
 			}
 			}
@@ -525,7 +525,7 @@ bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 				RR->node->putFrame(network->id(),MAC(peer->address(),network->id()),network->mac(),etherType,0,field(ZT_PROTO_VERB_FRAME_IDX_PAYLOAD,payloadLen),payloadLen);
 				RR->node->putFrame(network->id(),MAC(peer->address(),network->id()),network->mac(),etherType,0,field(ZT_PROTO_VERB_FRAME_IDX_PAYLOAD,payloadLen),payloadLen);
 			}
 			}
 
 
-			peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_FRAME,0,Packet::VERB_NOP);
+			peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_FRAME,0,Packet::VERB_NOP);
 		} else {
 		} else {
 			TRACE("dropped FRAME from %s(%s): we are not connected to network %.16llx",source().toString().c_str(),_remoteAddress.toString().c_str(),at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID));
 			TRACE("dropped FRAME from %s(%s): we are not connected to network %.16llx",source().toString().c_str(),_remoteAddress.toString().c_str(),at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID));
 		}
 		}
@@ -602,7 +602,7 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr<P
 				RR->node->putFrame(network->id(),from,to,etherType,0,field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD,payloadLen),payloadLen);
 				RR->node->putFrame(network->id(),from,to,etherType,0,field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD,payloadLen),payloadLen);
 			}
 			}
 
 
-			peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP);
+			peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP);
 		} else {
 		} else {
 			TRACE("dropped EXT_FRAME from %s(%s): we are not connected to network %.16llx",source().toString().c_str(),_remoteAddress.toString().c_str(),at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID));
 			TRACE("dropped EXT_FRAME from %s(%s): we are not connected to network %.16llx",source().toString().c_str(),_remoteAddress.toString().c_str(),at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID));
 		}
 		}
@@ -623,7 +623,7 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,const Shared
 		for(unsigned int ptr=ZT_PACKET_IDX_PAYLOAD;ptr<size();ptr+=18)
 		for(unsigned int ptr=ZT_PACKET_IDX_PAYLOAD;ptr<size();ptr+=18)
 			RR->mc->add(now,at<uint64_t>(ptr),MulticastGroup(MAC(field(ptr + 8,6),6),at<uint32_t>(ptr + 14)),peer->address());
 			RR->mc->add(now,at<uint64_t>(ptr),MulticastGroup(MAC(field(ptr + 8,6),6),at<uint32_t>(ptr + 14)),peer->address());
 
 
-		peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_MULTICAST_LIKE,0,Packet::VERB_NOP);
+		peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_MULTICAST_LIKE,0,Packet::VERB_NOP);
 	} catch (std::exception &ex) {
 	} catch (std::exception &ex) {
 		TRACE("dropped MULTICAST_LIKE from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 		TRACE("dropped MULTICAST_LIKE from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 	} catch ( ... ) {
 	} catch ( ... ) {
@@ -647,7 +647,7 @@ bool IncomingPacket::_doNETWORK_MEMBERSHIP_CERTIFICATE(const RuntimeEnvironment
 			}
 			}
 		}
 		}
 
 
-		peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE,0,Packet::VERB_NOP);
+		peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE,0,Packet::VERB_NOP);
 	} catch (std::exception &ex) {
 	} catch (std::exception &ex) {
 		TRACE("dropped NETWORK_MEMBERSHIP_CERTIFICATE from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 		TRACE("dropped NETWORK_MEMBERSHIP_CERTIFICATE from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 	} catch ( ... ) {
 	} catch ( ... ) {
@@ -666,7 +666,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 
 
 		const unsigned int h = hops();
 		const unsigned int h = hops();
 		const uint64_t pid = packetId();
 		const uint64_t pid = packetId();
-		peer->received(RR,_remoteAddress,h,pid,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP);
+		peer->received(RR,_localInterfaceId,_remoteAddress,h,pid,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP);
 
 
 		if (RR->localNetworkController) {
 		if (RR->localNetworkController) {
 			Dictionary netconf;
 			Dictionary netconf;
@@ -688,7 +688,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 						if (outp.size() > ZT_PROTO_MAX_PACKET_LENGTH) {
 						if (outp.size() > ZT_PROTO_MAX_PACKET_LENGTH) {
 							TRACE("NETWORK_CONFIG_REQUEST failed: internal error: netconf size %u is too large",(unsigned int)netconfStr.length());
 							TRACE("NETWORK_CONFIG_REQUEST failed: internal error: netconf size %u is too large",(unsigned int)netconfStr.length());
 						} else {
 						} else {
-							RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+							RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 						}
 						}
 					}
 					}
 				}	break;
 				}	break;
@@ -700,7 +700,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 					outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND);
 					outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND);
 					outp.append(nwid);
 					outp.append(nwid);
 					outp.armor(peer->key(),true);
 					outp.armor(peer->key(),true);
-					RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+					RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 				}	break;
 				}	break;
 
 
 				case NetworkController::NETCONF_QUERY_ACCESS_DENIED: {
 				case NetworkController::NETCONF_QUERY_ACCESS_DENIED: {
@@ -710,7 +710,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 					outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_);
 					outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_);
 					outp.append(nwid);
 					outp.append(nwid);
 					outp.armor(peer->key(),true);
 					outp.armor(peer->key(),true);
-					RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+					RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 				} break;
 				} break;
 
 
 				case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR:
 				case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR:
@@ -732,7 +732,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 			outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION);
 			outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION);
 			outp.append(nwid);
 			outp.append(nwid);
 			outp.armor(peer->key(),true);
 			outp.armor(peer->key(),true);
-			RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+			RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 		}
 		}
 	} catch (std::exception &exc) {
 	} catch (std::exception &exc) {
 		TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
 		TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
@@ -753,7 +753,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,cons
 				nw->requestConfiguration();
 				nw->requestConfiguration();
 			ptr += 8;
 			ptr += 8;
 		}
 		}
-		peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP);
+		peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP);
 	} catch (std::exception &exc) {
 	} catch (std::exception &exc) {
 		TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
 		TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
 	} catch ( ... ) {
 	} catch ( ... ) {
@@ -780,11 +780,11 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,const Shar
 			outp.append((uint32_t)mg.adi());
 			outp.append((uint32_t)mg.adi());
 			if (RR->mc->gather(peer->address(),nwid,mg,outp,gatherLimit)) {
 			if (RR->mc->gather(peer->address(),nwid,mg,outp,gatherLimit)) {
 				outp.armor(peer->key(),true);
 				outp.armor(peer->key(),true);
-				RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+				RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 			}
 			}
 		}
 		}
 
 
-		peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_MULTICAST_GATHER,0,Packet::VERB_NOP);
+		peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_MULTICAST_GATHER,0,Packet::VERB_NOP);
 	} catch (std::exception &exc) {
 	} catch (std::exception &exc) {
 		TRACE("dropped MULTICAST_GATHER from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
 		TRACE("dropped MULTICAST_GATHER from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
 	} catch ( ... ) {
 	} catch ( ... ) {
@@ -871,12 +871,12 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,const Share
 				outp.append((unsigned char)0x02); // flag 0x02 = contains gather results
 				outp.append((unsigned char)0x02); // flag 0x02 = contains gather results
 				if (RR->mc->gather(peer->address(),nwid,to,outp,gatherLimit)) {
 				if (RR->mc->gather(peer->address(),nwid,to,outp,gatherLimit)) {
 					outp.armor(peer->key(),true);
 					outp.armor(peer->key(),true);
-					RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+					RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 				}
 				}
 			}
 			}
 		} // else ignore -- not a member of this network
 		} // else ignore -- not a member of this network
 
 
-		peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP);
+		peer->received(RR,_localInterfaceId,_remoteAddress,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP);
 	} catch (std::exception &exc) {
 	} catch (std::exception &exc) {
 		TRACE("dropped MULTICAST_FRAME from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
 		TRACE("dropped MULTICAST_FRAME from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
 	} catch ( ... ) {
 	} catch ( ... ) {
@@ -905,14 +905,14 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const Sha
 					InetAddress a(field(ptr,4),4,at<uint16_t>(ptr + 4));
 					InetAddress a(field(ptr,4),4,at<uint16_t>(ptr + 4));
 					if ( ((flags & (0x01 | 0x02)) == 0) && (Path::isAddressValidForPath(a)) ) {
 					if ( ((flags & (0x01 | 0x02)) == 0) && (Path::isAddressValidForPath(a)) ) {
 						TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str());
 						TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str());
-						peer->attemptToContactAt(RR,a,RR->node->now());
+						peer->attemptToContactAt(RR,_localInterfaceId,a,RR->node->now());
 					}
 					}
 				}	break;
 				}	break;
 				case 6: {
 				case 6: {
 					InetAddress a(field(ptr,16),16,at<uint16_t>(ptr + 16));
 					InetAddress a(field(ptr,16),16,at<uint16_t>(ptr + 16));
 					if ( ((flags & (0x01 | 0x02)) == 0) && (Path::isAddressValidForPath(a)) ) {
 					if ( ((flags & (0x01 | 0x02)) == 0) && (Path::isAddressValidForPath(a)) ) {
 						TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str());
 						TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str());
-						peer->attemptToContactAt(RR,a,RR->node->now());
+						peer->attemptToContactAt(RR,_localInterfaceId,a,RR->node->now());
 					}
 					}
 				}	break;
 				}	break;
 			}
 			}
@@ -934,7 +934,7 @@ void IncomingPacket::_sendErrorNeedCertificate(const RuntimeEnvironment *RR,cons
 	outp.append((unsigned char)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE);
 	outp.append((unsigned char)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE);
 	outp.append(nwid);
 	outp.append(nwid);
 	outp.armor(peer->key(),true);
 	outp.armor(peer->key(),true);
-	RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
+	RR->node->putPacket(_localInterfaceId,_remoteAddress,outp.data(),outp.size());
 }
 }
 
 
 } // namespace ZeroTier
 } // namespace ZeroTier

+ 4 - 1
node/IncomingPacket.hpp

@@ -72,14 +72,16 @@ public:
 	 *
 	 *
 	 * @param data Packet data
 	 * @param data Packet data
 	 * @param len Packet length
 	 * @param len Packet length
+	 * @param localInterfaceId Local interface ID
 	 * @param remoteAddress Address from which packet came
 	 * @param remoteAddress Address from which packet came
 	 * @param now Current time
 	 * @param now Current time
 	 * @throws std::out_of_range Range error processing packet
 	 * @throws std::out_of_range Range error processing packet
 	 */
 	 */
-	IncomingPacket(const void *data,unsigned int len,const InetAddress &remoteAddress,uint64_t now) :
+	IncomingPacket(const void *data,unsigned int len,int localInterfaceId,const InetAddress &remoteAddress,uint64_t now) :
  		Packet(data,len),
  		Packet(data,len),
  		_receiveTime(now),
  		_receiveTime(now),
  		_remoteAddress(remoteAddress),
  		_remoteAddress(remoteAddress),
+ 		_localInterfaceId(localInterfaceId),
  		__refCount()
  		__refCount()
 	{
 	{
 	}
 	}
@@ -128,6 +130,7 @@ private:
 
 
 	uint64_t _receiveTime;
 	uint64_t _receiveTime;
 	InetAddress _remoteAddress;
 	InetAddress _remoteAddress;
+	int _localInterfaceId;
 	AtomicCounter __refCount;
 	AtomicCounter __refCount;
 };
 };
 
 

+ 5 - 3
node/Node.cpp

@@ -157,13 +157,14 @@ Node::~Node()
 
 
 ZT1_ResultCode Node::processWirePacket(
 ZT1_ResultCode Node::processWirePacket(
 	uint64_t now,
 	uint64_t now,
+	int localInterfaceId,
 	const struct sockaddr_storage *remoteAddress,
 	const struct sockaddr_storage *remoteAddress,
 	const void *packetData,
 	const void *packetData,
 	unsigned int packetLength,
 	unsigned int packetLength,
 	volatile uint64_t *nextBackgroundTaskDeadline)
 	volatile uint64_t *nextBackgroundTaskDeadline)
 {
 {
 	_now = now;
 	_now = now;
-	RR->sw->onRemotePacket(*(reinterpret_cast<const InetAddress *>(remoteAddress)),packetData,packetLength);
+	RR->sw->onRemotePacket(localInterfaceId,*(reinterpret_cast<const InetAddress *>(remoteAddress)),packetData,packetLength);
 	return ZT1_RESULT_OK;
 	return ZT1_RESULT_OK;
 }
 }
 
 
@@ -261,7 +262,7 @@ ZT1_ResultCode Node::processBackgroundTasks(uint64_t now,volatile uint64_t *next
 				if (nr->second) {
 				if (nr->second) {
 					SharedPtr<Peer> rp(RR->topology->getPeer(nr->first));
 					SharedPtr<Peer> rp(RR->topology->getPeer(nr->first));
 					if ((rp)&&(!rp->hasActiveDirectPath(now)))
 					if ((rp)&&(!rp->hasActiveDirectPath(now)))
-						rp->attemptToContactAt(RR,nr->second,now);
+						rp->attemptToContactAt(RR,-1,nr->second,now);
 				}
 				}
 			}
 			}
 
 
@@ -569,13 +570,14 @@ void ZT1_Node_delete(ZT1_Node *node)
 enum ZT1_ResultCode ZT1_Node_processWirePacket(
 enum ZT1_ResultCode ZT1_Node_processWirePacket(
 	ZT1_Node *node,
 	ZT1_Node *node,
 	uint64_t now,
 	uint64_t now,
+	int localInterfaceId,
 	const struct sockaddr_storage *remoteAddress,
 	const struct sockaddr_storage *remoteAddress,
 	const void *packetData,
 	const void *packetData,
 	unsigned int packetLength,
 	unsigned int packetLength,
 	volatile uint64_t *nextBackgroundTaskDeadline)
 	volatile uint64_t *nextBackgroundTaskDeadline)
 {
 {
 	try {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->processWirePacket(now,remoteAddress,packetData,packetLength,nextBackgroundTaskDeadline);
+		return reinterpret_cast<ZeroTier::Node *>(node)->processWirePacket(now,localInterfaceId,remoteAddress,packetData,packetLength,nextBackgroundTaskDeadline);
 	} catch (std::bad_alloc &exc) {
 	} catch (std::bad_alloc &exc) {
 		return ZT1_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
 		return ZT1_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
 	} catch ( ... ) {
 	} catch ( ... ) {

+ 4 - 1
node/Node.hpp

@@ -80,6 +80,7 @@ public:
 
 
 	ZT1_ResultCode processWirePacket(
 	ZT1_ResultCode processWirePacket(
 		uint64_t now,
 		uint64_t now,
+		int localInterfaceId,
 		const struct sockaddr_storage *remoteAddress,
 		const struct sockaddr_storage *remoteAddress,
 		const void *packetData,
 		const void *packetData,
 		unsigned int packetLength,
 		unsigned int packetLength,
@@ -119,16 +120,18 @@ public:
 	/**
 	/**
 	 * Enqueue a ZeroTier message to be sent
 	 * Enqueue a ZeroTier message to be sent
 	 *
 	 *
+	 * @param localInterfaceId Local interface ID, -1 for unspecified/random
 	 * @param addr Destination address
 	 * @param addr Destination address
 	 * @param data Packet data
 	 * @param data Packet data
 	 * @param len Packet length
 	 * @param len Packet length
 	 * @return True if packet appears to have been sent
 	 * @return True if packet appears to have been sent
 	 */
 	 */
-	inline bool putPacket(const InetAddress &addr,const void *data,unsigned int len)
+	inline bool putPacket(int localInterfaceId,const InetAddress &addr,const void *data,unsigned int len)
 	{
 	{
 		return (_wirePacketSendFunction(
 		return (_wirePacketSendFunction(
 			reinterpret_cast<ZT1_Node *>(this),
 			reinterpret_cast<ZT1_Node *>(this),
 			_uPtr,
 			_uPtr,
+			localInterfaceId,
 			reinterpret_cast<const struct sockaddr_storage *>(&addr),
 			reinterpret_cast<const struct sockaddr_storage *>(&addr),
 			data,
 			data,
 			len) == 0);
 			len) == 0);

+ 11 - 10
node/Peer.cpp

@@ -64,6 +64,7 @@ Peer::Peer(const Identity &myIdentity,const Identity &peerIdentity)
 
 
 void Peer::received(
 void Peer::received(
 	const RuntimeEnvironment *RR,
 	const RuntimeEnvironment *RR,
+	int localInterfaceId,
 	const InetAddress &remoteAddr,
 	const InetAddress &remoteAddr,
 	unsigned int hops,
 	unsigned int hops,
 	uint64_t packetId,
 	uint64_t packetId,
@@ -81,7 +82,7 @@ void Peer::received(
 		{
 		{
 			unsigned int np = _numPaths;
 			unsigned int np = _numPaths;
 			for(unsigned int p=0;p<np;++p) {
 			for(unsigned int p=0;p<np;++p) {
-				if (_paths[p].address() == remoteAddr) {
+				if ((_paths[p].address() == remoteAddr)&&(_paths[p].localInterfaceId() == localInterfaceId)) {
 					_paths[p].received(now);
 					_paths[p].received(now);
 					pathIsConfirmed = true;
 					pathIsConfirmed = true;
 					break;
 					break;
@@ -106,7 +107,7 @@ void Peer::received(
 						}
 						}
 					}
 					}
 					if (slot) {
 					if (slot) {
-						*slot = RemotePath(remoteAddr,false);
+						*slot = RemotePath(localInterfaceId,remoteAddr,false);
 						slot->received(now);
 						slot->received(now);
 						_numPaths = np;
 						_numPaths = np;
 						pathIsConfirmed = true;
 						pathIsConfirmed = true;
@@ -119,7 +120,7 @@ void Peer::received(
 					if ((now - _lastPathConfirmationSent) >= ZT_MIN_PATH_CONFIRMATION_INTERVAL) {
 					if ((now - _lastPathConfirmationSent) >= ZT_MIN_PATH_CONFIRMATION_INTERVAL) {
 						_lastPathConfirmationSent = now;
 						_lastPathConfirmationSent = now;
 						TRACE("got %s via unknown path %s(%s), confirming...",Packet::verbString(verb),_id.address().toString().c_str(),remoteAddr.toString().c_str());
 						TRACE("got %s via unknown path %s(%s), confirming...",Packet::verbString(verb),_id.address().toString().c_str(),remoteAddr.toString().c_str());
-						attemptToContactAt(RR,remoteAddr,now);
+						attemptToContactAt(RR,localInterfaceId,remoteAddr,now);
 					}
 					}
 				}
 				}
 			}
 			}
@@ -141,7 +142,7 @@ void Peer::received(
 					for(std::vector<MulticastGroup>::const_iterator mg(mgs.begin());mg!=mgs.end();++mg) {
 					for(std::vector<MulticastGroup>::const_iterator mg(mgs.begin());mg!=mgs.end();++mg) {
 						if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) {
 						if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) {
 							outp.armor(_key,true);
 							outp.armor(_key,true);
-							RR->node->putPacket(remoteAddr,outp.data(),outp.size());
+							RR->node->putPacket(localInterfaceId,remoteAddr,outp.data(),outp.size());
 							outp.reset(_id.address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE);
 							outp.reset(_id.address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE);
 						}
 						}
 
 
@@ -154,7 +155,7 @@ void Peer::received(
 			}
 			}
 			if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) {
 			if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) {
 				outp.armor(_key,true);
 				outp.armor(_key,true);
-				RR->node->putPacket(remoteAddr,outp.data(),outp.size());
+				RR->node->putPacket(localInterfaceId,remoteAddr,outp.data(),outp.size());
 			}
 			}
 		}
 		}
 	}
 	}
@@ -180,7 +181,7 @@ RemotePath *Peer::getBestPath(uint64_t now)
 	return bestPath;
 	return bestPath;
 }
 }
 
 
-void Peer::attemptToContactAt(const RuntimeEnvironment *RR,const InetAddress &atAddress,uint64_t now)
+void Peer::attemptToContactAt(const RuntimeEnvironment *RR,int localInterfaceId,const InetAddress &atAddress,uint64_t now)
 {
 {
 	Packet outp(_id.address(),RR->identity.address(),Packet::VERB_HELLO);
 	Packet outp(_id.address(),RR->identity.address(),Packet::VERB_HELLO);
 	outp.append((unsigned char)ZT_PROTO_VERSION);
 	outp.append((unsigned char)ZT_PROTO_VERSION);
@@ -208,7 +209,7 @@ void Peer::attemptToContactAt(const RuntimeEnvironment *RR,const InetAddress &at
 	}
 	}
 
 
 	outp.armor(_key,false); // HELLO is sent in the clear
 	outp.armor(_key,false); // HELLO is sent in the clear
-	RR->node->putPacket(atAddress,outp.data(),outp.size());
+	RR->node->putPacket(localInterfaceId,atAddress,outp.data(),outp.size());
 }
 }
 
 
 void Peer::doPingAndKeepalive(const RuntimeEnvironment *RR,uint64_t now)
 void Peer::doPingAndKeepalive(const RuntimeEnvironment *RR,uint64_t now)
@@ -217,12 +218,12 @@ void Peer::doPingAndKeepalive(const RuntimeEnvironment *RR,uint64_t now)
 	if (bestPath) {
 	if (bestPath) {
 		if ((now - bestPath->lastReceived()) >= ZT_PEER_DIRECT_PING_DELAY) {
 		if ((now - bestPath->lastReceived()) >= ZT_PEER_DIRECT_PING_DELAY) {
 			TRACE("PING %s(%s)",_id.address().toString().c_str(),bestPath->address().toString().c_str());
 			TRACE("PING %s(%s)",_id.address().toString().c_str(),bestPath->address().toString().c_str());
-			attemptToContactAt(RR,bestPath->address(),now);
+			attemptToContactAt(RR,bestPath->localInterfaceId(),bestPath->address(),now);
 			bestPath->sent(now);
 			bestPath->sent(now);
 		} else if (((now - bestPath->lastSend()) >= ZT_NAT_KEEPALIVE_DELAY)&&(!bestPath->reliable())) {
 		} else if (((now - bestPath->lastSend()) >= ZT_NAT_KEEPALIVE_DELAY)&&(!bestPath->reliable())) {
 			_natKeepaliveBuf += (uint32_t)((now * 0x9e3779b1) >> 1); // tumble this around to send constantly varying (meaningless) payloads
 			_natKeepaliveBuf += (uint32_t)((now * 0x9e3779b1) >> 1); // tumble this around to send constantly varying (meaningless) payloads
 			TRACE("NAT keepalive %s(%s)",_id.address().toString().c_str(),bestPath->address().toString().c_str());
 			TRACE("NAT keepalive %s(%s)",_id.address().toString().c_str(),bestPath->address().toString().c_str());
-			RR->node->putPacket(bestPath->address(),&_natKeepaliveBuf,sizeof(_natKeepaliveBuf));
+			RR->node->putPacket(bestPath->localInterfaceId(),bestPath->address(),&_natKeepaliveBuf,sizeof(_natKeepaliveBuf));
 			bestPath->sent(now);
 			bestPath->sent(now);
 		}
 		}
 	}
 	}
@@ -354,7 +355,7 @@ bool Peer::resetWithinScope(const RuntimeEnvironment *RR,InetAddress::IpScope sc
 	while (x < np) {
 	while (x < np) {
 		if (_paths[x].address().ipScope() == scope) {
 		if (_paths[x].address().ipScope() == scope) {
 			if (_paths[x].fixed()) {
 			if (_paths[x].fixed()) {
-				attemptToContactAt(RR,_paths[x].address(),now);
+				attemptToContactAt(RR,_paths[x].localInterfaceId(),_paths[x].address(),now);
 				_paths[y++] = _paths[x]; // keep fixed paths
 				_paths[y++] = _paths[x]; // keep fixed paths
 			}
 			}
 		} else {
 		} else {

+ 4 - 1
node/Peer.hpp

@@ -105,6 +105,7 @@ public:
 	 * and appears to be valid.
 	 * and appears to be valid.
 	 *
 	 *
 	 * @param RR Runtime environment
 	 * @param RR Runtime environment
+	 * @param localInterfaceId Local interface ID or -1 if unspecified
 	 * @param remoteAddr Internet address of sender
 	 * @param remoteAddr Internet address of sender
 	 * @param hops ZeroTier (not IP) hops
 	 * @param hops ZeroTier (not IP) hops
 	 * @param packetId Packet ID
 	 * @param packetId Packet ID
@@ -114,6 +115,7 @@ public:
 	 */
 	 */
 	void received(
 	void received(
 		const RuntimeEnvironment *RR,
 		const RuntimeEnvironment *RR,
+		int localInterfaceId,
 		const InetAddress &remoteAddr,
 		const InetAddress &remoteAddr,
 		unsigned int hops,
 		unsigned int hops,
 		uint64_t packetId,
 		uint64_t packetId,
@@ -155,10 +157,11 @@ public:
 	 * for NAT traversal and path verification.
 	 * for NAT traversal and path verification.
 	 *
 	 *
 	 * @param RR Runtime environment
 	 * @param RR Runtime environment
+	 * @param localInterfaceId Local interface ID or -1 for unspecified
 	 * @param atAddress Destination address
 	 * @param atAddress Destination address
 	 * @param now Current time
 	 * @param now Current time
 	 */
 	 */
-	void attemptToContactAt(const RuntimeEnvironment *RR,const InetAddress &atAddress,uint64_t now);
+	void attemptToContactAt(const RuntimeEnvironment *RR,int localInterfaceId,const InetAddress &atAddress,uint64_t now);
 
 
 	/**
 	/**
 	 * Send pings or keepalives depending on configured timeouts
 	 * Send pings or keepalives depending on configured timeouts

+ 7 - 2
node/RemotePath.hpp

@@ -53,14 +53,18 @@ public:
 		Path(),
 		Path(),
 		_lastSend(0),
 		_lastSend(0),
 		_lastReceived(0),
 		_lastReceived(0),
+		_localInterfaceId(-1),
 		_fixed(false) {}
 		_fixed(false) {}
 
 
-	RemotePath(const InetAddress &addr,bool fixed) :
+	RemotePath(int localInterfaceId,const InetAddress &addr,bool fixed) :
 		Path(addr,0,TRUST_NORMAL),
 		Path(addr,0,TRUST_NORMAL),
 		_lastSend(0),
 		_lastSend(0),
 		_lastReceived(0),
 		_lastReceived(0),
+		_localInterfaceId(localInterfaceId),
 		_fixed(fixed) {}
 		_fixed(fixed) {}
 
 
+	inline int localInterfaceId() const throw() { return _localInterfaceId; }
+
 	inline uint64_t lastSend() const throw() { return _lastSend; }
 	inline uint64_t lastSend() const throw() { return _lastSend; }
 	inline uint64_t lastReceived() const throw() { return _lastReceived; }
 	inline uint64_t lastReceived() const throw() { return _lastReceived; }
 
 
@@ -123,7 +127,7 @@ public:
 	 */
 	 */
 	inline bool send(const RuntimeEnvironment *RR,const void *data,unsigned int len,uint64_t now)
 	inline bool send(const RuntimeEnvironment *RR,const void *data,unsigned int len,uint64_t now)
 	{
 	{
-		if (RR->node->putPacket(address(),data,len)) {
+		if (RR->node->putPacket(_localInterfaceId,address(),data,len)) {
 			sent(now);
 			sent(now);
 			RR->antiRec->logOutgoingZT(data,len);
 			RR->antiRec->logOutgoingZT(data,len);
 			return true;
 			return true;
@@ -134,6 +138,7 @@ public:
 private:
 private:
 	uint64_t _lastSend;
 	uint64_t _lastSend;
 	uint64_t _lastReceived;
 	uint64_t _lastReceived;
+	int _localInterfaceId;
 	bool _fixed;
 	bool _fixed;
 };
 };
 
 

+ 15 - 12
node/Switch.cpp

@@ -78,8 +78,11 @@ Switch::~Switch()
 {
 {
 }
 }
 
 
-void Switch::onRemotePacket(const InetAddress &fromAddr,const void *data,unsigned int len)
+void Switch::onRemotePacket(int localInterfaceId,const InetAddress &fromAddr,const void *data,unsigned int len)
 {
 {
+	if (localInterfaceId < 0)
+		localInterfaceId = 0;
+
 	try {
 	try {
 		if (len == 13) {
 		if (len == 13) {
 			/* LEGACY: before VERB_PUSH_DIRECT_PATHS, peers used broadcast
 			/* LEGACY: before VERB_PUSH_DIRECT_PATHS, peers used broadcast
@@ -96,14 +99,14 @@ void Switch::onRemotePacket(const InetAddress &fromAddr,const void *data,unsigne
 					_lastBeaconResponse = now;
 					_lastBeaconResponse = now;
 					Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NOP);
 					Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NOP);
 					outp.armor(peer->key(),false);
 					outp.armor(peer->key(),false);
-					RR->node->putPacket(fromAddr,outp.data(),outp.size());
+					RR->node->putPacket(localInterfaceId,fromAddr,outp.data(),outp.size());
 				}
 				}
 			}
 			}
 		} else if (len > ZT_PROTO_MIN_FRAGMENT_LENGTH) {
 		} else if (len > ZT_PROTO_MIN_FRAGMENT_LENGTH) {
 			if (((const unsigned char *)data)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] == ZT_PACKET_FRAGMENT_INDICATOR) {
 			if (((const unsigned char *)data)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] == ZT_PACKET_FRAGMENT_INDICATOR) {
-				_handleRemotePacketFragment(fromAddr,data,len);
+				_handleRemotePacketFragment(localInterfaceId,fromAddr,data,len);
 			} else if (len >= ZT_PROTO_MIN_PACKET_LENGTH) {
 			} else if (len >= ZT_PROTO_MIN_PACKET_LENGTH) {
-				_handleRemotePacketHead(fromAddr,data,len);
+				_handleRemotePacketHead(localInterfaceId,fromAddr,data,len);
 			}
 			}
 		}
 		}
 	} catch (std::exception &ex) {
 	} catch (std::exception &ex) {
@@ -376,14 +379,14 @@ bool Switch::unite(const Address &p1,const Address &p2,bool force)
 	return true;
 	return true;
 }
 }
 
 
-void Switch::rendezvous(const SharedPtr<Peer> &peer,const InetAddress &atAddr)
+void Switch::rendezvous(const SharedPtr<Peer> &peer,int localInterfaceId,const InetAddress &atAddr)
 {
 {
 	TRACE("sending NAT-t message to %s(%s)",peer->address().toString().c_str(),atAddr.toString().c_str());
 	TRACE("sending NAT-t message to %s(%s)",peer->address().toString().c_str(),atAddr.toString().c_str());
 	const uint64_t now = RR->node->now();
 	const uint64_t now = RR->node->now();
-	peer->attemptToContactAt(RR,atAddr,now);
+	peer->attemptToContactAt(RR,localInterfaceId,atAddr,now);
 	{
 	{
 		Mutex::Lock _l(_contactQueue_m);
 		Mutex::Lock _l(_contactQueue_m);
-		_contactQueue.push_back(ContactQueueEntry(peer,now + ZT_NAT_T_TACTICAL_ESCALATION_DELAY,atAddr));
+		_contactQueue.push_back(ContactQueueEntry(peer,now + ZT_NAT_T_TACTICAL_ESCALATION_DELAY,localInterfaceId,atAddr));
 	}
 	}
 }
 }
 
 
@@ -453,14 +456,14 @@ unsigned long Switch::doTimerTasks(uint64_t now)
 				} else {
 				} else {
 					if (qi->strategyIteration == 0) {
 					if (qi->strategyIteration == 0) {
 						// First strategy: send packet directly to destination
 						// First strategy: send packet directly to destination
-						qi->peer->attemptToContactAt(RR,qi->inaddr,now);
+						qi->peer->attemptToContactAt(RR,qi->localInterfaceId,qi->inaddr,now);
 					} else if (qi->strategyIteration <= 4) {
 					} else if (qi->strategyIteration <= 4) {
 						// Strategies 1-4: try escalating ports for symmetric NATs that remap sequentially
 						// Strategies 1-4: try escalating ports for symmetric NATs that remap sequentially
 						InetAddress tmpaddr(qi->inaddr);
 						InetAddress tmpaddr(qi->inaddr);
 						int p = (int)qi->inaddr.port() + qi->strategyIteration;
 						int p = (int)qi->inaddr.port() + qi->strategyIteration;
 						if (p < 0xffff) {
 						if (p < 0xffff) {
 							tmpaddr.setPort((unsigned int)p);
 							tmpaddr.setPort((unsigned int)p);
-							qi->peer->attemptToContactAt(RR,tmpaddr,now);
+							qi->peer->attemptToContactAt(RR,qi->localInterfaceId,tmpaddr,now);
 						} else qi->strategyIteration = 5;
 						} else qi->strategyIteration = 5;
 					} else {
 					} else {
 						// All strategies tried, expire entry
 						// All strategies tried, expire entry
@@ -551,7 +554,7 @@ unsigned long Switch::doTimerTasks(uint64_t now)
 	return nextDelay;
 	return nextDelay;
 }
 }
 
 
-void Switch::_handleRemotePacketFragment(const InetAddress &fromAddr,const void *data,unsigned int len)
+void Switch::_handleRemotePacketFragment(int localInterfaceId,const InetAddress &fromAddr,const void *data,unsigned int len)
 {
 {
 	Packet::Fragment fragment(data,len);
 	Packet::Fragment fragment(data,len);
 	Address destination(fragment.destination());
 	Address destination(fragment.destination());
@@ -622,9 +625,9 @@ void Switch::_handleRemotePacketFragment(const InetAddress &fromAddr,const void
 	}
 	}
 }
 }
 
 
-void Switch::_handleRemotePacketHead(const InetAddress &fromAddr,const void *data,unsigned int len)
+void Switch::_handleRemotePacketHead(int localInterfaceId,const InetAddress &fromAddr,const void *data,unsigned int len)
 {
 {
-	SharedPtr<IncomingPacket> packet(new IncomingPacket(data,len,fromAddr,RR->node->now()));
+	SharedPtr<IncomingPacket> packet(new IncomingPacket(data,len,localInterfaceId,fromAddr,RR->node->now()));
 
 
 	Address source(packet->source());
 	Address source(packet->source());
 	Address destination(packet->destination());
 	Address destination(packet->destination());

+ 9 - 5
node/Switch.hpp

@@ -79,11 +79,12 @@ public:
 	/**
 	/**
 	 * Called when a packet is received from the real network
 	 * Called when a packet is received from the real network
 	 *
 	 *
+	 * @param localInterfaceId Local interface ID or -1 for unspecified
 	 * @param fromAddr Internet IP address of origin
 	 * @param fromAddr Internet IP address of origin
 	 * @param data Packet data
 	 * @param data Packet data
 	 * @param len Packet length
 	 * @param len Packet length
 	 */
 	 */
-	void onRemotePacket(const InetAddress &fromAddr,const void *data,unsigned int len);
+	void onRemotePacket(int localInterfaceId,const InetAddress &fromAddr,const void *data,unsigned int len);
 
 
 	/**
 	/**
 	 * Called when a packet comes from a local Ethernet tap
 	 * Called when a packet comes from a local Ethernet tap
@@ -140,9 +141,10 @@ public:
 	 * Attempt NAT traversal to peer at a given physical address
 	 * Attempt NAT traversal to peer at a given physical address
 	 *
 	 *
 	 * @param peer Peer to contact
 	 * @param peer Peer to contact
+	 * @param localInterfaceId Local interface ID or -1 if unspecified
 	 * @param atAddr Address of peer
 	 * @param atAddr Address of peer
 	 */
 	 */
-	void rendezvous(const SharedPtr<Peer> &peer,const InetAddress &atAddr);
+	void rendezvous(const SharedPtr<Peer> &peer,int localInterfaceId,const InetAddress &atAddr);
 
 
 	/**
 	/**
 	 * Request WHOIS on a given address
 	 * Request WHOIS on a given address
@@ -179,8 +181,8 @@ public:
 	unsigned long doTimerTasks(uint64_t now);
 	unsigned long doTimerTasks(uint64_t now);
 
 
 private:
 private:
-	void _handleRemotePacketFragment(const InetAddress &fromAddr,const void *data,unsigned int len);
-	void _handleRemotePacketHead(const InetAddress &fromAddr,const void *data,unsigned int len);
+	void _handleRemotePacketFragment(int localInterfaceId,const InetAddress &fromAddr,const void *data,unsigned int len);
+	void _handleRemotePacketHead(int localInterfaceId,const InetAddress &fromAddr,const void *data,unsigned int len);
 	Address _sendWhoisRequest(const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted);
 	Address _sendWhoisRequest(const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted);
 	bool _trySend(const Packet &packet,bool encrypt,uint64_t nwid);
 	bool _trySend(const Packet &packet,bool encrypt,uint64_t nwid);
 
 
@@ -260,15 +262,17 @@ private:
 	struct ContactQueueEntry
 	struct ContactQueueEntry
 	{
 	{
 		ContactQueueEntry() {}
 		ContactQueueEntry() {}
-		ContactQueueEntry(const SharedPtr<Peer> &p,uint64_t ft,const InetAddress &a) :
+		ContactQueueEntry(const SharedPtr<Peer> &p,uint64_t ft,int liid,const InetAddress &a) :
 			peer(p),
 			peer(p),
 			fireAtTime(ft),
 			fireAtTime(ft),
 			inaddr(a),
 			inaddr(a),
+			localInterfaceId(liid),
 			strategyIteration(0) {}
 			strategyIteration(0) {}
 
 
 		SharedPtr<Peer> peer;
 		SharedPtr<Peer> peer;
 		uint64_t fireAtTime;
 		uint64_t fireAtTime;
 		InetAddress inaddr;
 		InetAddress inaddr;
+		int localInterfaceId;
 		unsigned int strategyIteration;
 		unsigned int strategyIteration;
 	};
 	};
 	std::list<ContactQueueEntry> _contactQueue;
 	std::list<ContactQueueEntry> _contactQueue;

+ 1 - 1
node/Topology.cpp

@@ -62,7 +62,7 @@ void Topology::setRootServers(const std::map< Identity,std::vector<InetAddress>
 			if (!p)
 			if (!p)
 				p = SharedPtr<Peer>(new Peer(RR->identity,i->first));
 				p = SharedPtr<Peer>(new Peer(RR->identity,i->first));
 			for(std::vector<InetAddress>::const_iterator j(i->second.begin());j!=i->second.end();++j)
 			for(std::vector<InetAddress>::const_iterator j(i->second.begin());j!=i->second.end();++j)
-				p->addPath(RemotePath(*j,true));
+				p->addPath(RemotePath(0,*j,true));
 			p->use(now);
 			p->use(now);
 			_rootPeers.push_back(p);
 			_rootPeers.push_back(p);
 		}
 		}

+ 4 - 2
service/OneService.cpp

@@ -344,7 +344,7 @@ static int SnodeVirtualNetworkConfigFunction(ZT1_Node *node,void *uptr,uint64_t
 static void SnodeEventCallback(ZT1_Node *node,void *uptr,enum ZT1_Event event,const void *metaData);
 static void SnodeEventCallback(ZT1_Node *node,void *uptr,enum ZT1_Event event,const void *metaData);
 static long SnodeDataStoreGetFunction(ZT1_Node *node,void *uptr,const char *name,void *buf,unsigned long bufSize,unsigned long readIndex,unsigned long *totalSize);
 static long SnodeDataStoreGetFunction(ZT1_Node *node,void *uptr,const char *name,void *buf,unsigned long bufSize,unsigned long readIndex,unsigned long *totalSize);
 static int SnodeDataStorePutFunction(ZT1_Node *node,void *uptr,const char *name,const void *data,unsigned long len,int secure);
 static int SnodeDataStorePutFunction(ZT1_Node *node,void *uptr,const char *name,const void *data,unsigned long len,int secure);
-static int SnodeWirePacketSendFunction(ZT1_Node *node,void *uptr,const struct sockaddr_storage *addr,const void *data,unsigned int len);
+static int SnodeWirePacketSendFunction(ZT1_Node *node,void *uptr,int localInterfaceId,const struct sockaddr_storage *addr,const void *data,unsigned int len);
 static void SnodeVirtualNetworkFrameFunction(ZT1_Node *node,void *uptr,uint64_t nwid,uint64_t sourceMac,uint64_t destMac,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len);
 static void SnodeVirtualNetworkFrameFunction(ZT1_Node *node,void *uptr,uint64_t nwid,uint64_t sourceMac,uint64_t destMac,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len);
 
 
 static void StapFrameHandler(void *uptr,uint64_t nwid,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len);
 static void StapFrameHandler(void *uptr,uint64_t nwid,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len);
@@ -727,6 +727,7 @@ public:
 			_lastDirectReceiveFromGlobal = OSUtils::now();
 			_lastDirectReceiveFromGlobal = OSUtils::now();
 		ZT1_ResultCode rc = _node->processWirePacket(
 		ZT1_ResultCode rc = _node->processWirePacket(
 			OSUtils::now(),
 			OSUtils::now(),
+			0,
 			(const struct sockaddr_storage *)from, // Phy<> uses sockaddr_storage, so it'll always be that big
 			(const struct sockaddr_storage *)from, // Phy<> uses sockaddr_storage, so it'll always be that big
 			data,
 			data,
 			len,
 			len,
@@ -875,6 +876,7 @@ public:
 							if (from) {
 							if (from) {
 								ZT1_ResultCode rc = _node->processWirePacket(
 								ZT1_ResultCode rc = _node->processWirePacket(
 									OSUtils::now(),
 									OSUtils::now(),
+									0,
 									reinterpret_cast<struct sockaddr_storage *>(&from),
 									reinterpret_cast<struct sockaddr_storage *>(&from),
 									data,
 									data,
 									plen,
 									plen,
@@ -1288,7 +1290,7 @@ static long SnodeDataStoreGetFunction(ZT1_Node *node,void *uptr,const char *name
 { return reinterpret_cast<OneServiceImpl *>(uptr)->nodeDataStoreGetFunction(name,buf,bufSize,readIndex,totalSize); }
 { return reinterpret_cast<OneServiceImpl *>(uptr)->nodeDataStoreGetFunction(name,buf,bufSize,readIndex,totalSize); }
 static int SnodeDataStorePutFunction(ZT1_Node *node,void *uptr,const char *name,const void *data,unsigned long len,int secure)
 static int SnodeDataStorePutFunction(ZT1_Node *node,void *uptr,const char *name,const void *data,unsigned long len,int secure)
 { return reinterpret_cast<OneServiceImpl *>(uptr)->nodeDataStorePutFunction(name,data,len,secure); }
 { return reinterpret_cast<OneServiceImpl *>(uptr)->nodeDataStorePutFunction(name,data,len,secure); }
-static int SnodeWirePacketSendFunction(ZT1_Node *node,void *uptr,const struct sockaddr_storage *addr,const void *data,unsigned int len)
+static int SnodeWirePacketSendFunction(ZT1_Node *node,void *uptr,int localInterfaceId,const struct sockaddr_storage *addr,const void *data,unsigned int len)
 { return reinterpret_cast<OneServiceImpl *>(uptr)->nodeWirePacketSendFunction(addr,data,len); }
 { return reinterpret_cast<OneServiceImpl *>(uptr)->nodeWirePacketSendFunction(addr,data,len); }
 static void SnodeVirtualNetworkFrameFunction(ZT1_Node *node,void *uptr,uint64_t nwid,uint64_t sourceMac,uint64_t destMac,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len)
 static void SnodeVirtualNetworkFrameFunction(ZT1_Node *node,void *uptr,uint64_t nwid,uint64_t sourceMac,uint64_t destMac,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len)
 { reinterpret_cast<OneServiceImpl *>(uptr)->nodeVirtualNetworkFrameFunction(nwid,sourceMac,destMac,etherType,vlanId,data,len); }
 { reinterpret_cast<OneServiceImpl *>(uptr)->nodeVirtualNetworkFrameFunction(nwid,sourceMac,destMac,etherType,vlanId,data,len); }