From c9e6e60c091db9274bcd550f53d4387793593528 Mon Sep 17 00:00:00 2001
From: Adam Ierymenko <adam.ierymenko@gmail.com>
Date: Tue, 3 Nov 2015 10:46:41 -0800
Subject: [PATCH] Handle ZeroTier-RFC4193 IPv6 address NDP queries inline by
 spoofing responses if the addressing scheme matches -- this allows
 multicast-free instant lookup of local IPv6 for better performance and
 reliability.

---
 node/InetAddress.hpp |  2 +-
 node/Switch.cpp      | 85 +++++++++++++++++++++++++++++++++++++-------
 selftest.cpp         |  1 +
 3 files changed, 74 insertions(+), 14 deletions(-)

diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp
index c4d5cfda0..74efc9438 100644
--- a/node/InetAddress.hpp
+++ b/node/InetAddress.hpp
@@ -411,7 +411,7 @@ struct InetAddress : public sockaddr_storage
 				// TODO: Ethernet address (but accept for forward compatibility)
 				return 7;
 			case 0x02:
-				// TODO: Bluetooth address (but accept for forward compatibility) 
+				// TODO: Bluetooth address (but accept for forward compatibility)
 				return 7;
 			case 0x03:
 				// TODO: Other address types (but accept for forward compatibility)
diff --git a/node/Switch.cpp b/node/Switch.cpp
index b7a9c5227..97befbc62 100644
--- a/node/Switch.cpp
+++ b/node/Switch.cpp
@@ -154,25 +154,84 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c
 		MulticastGroup mg(to,0);
 
 		if (to.isBroadcast()) {
-			if (
-			    (etherType == ZT_ETHERTYPE_ARP)&&
-			    (len >= 28)&&
-			    (
-			     (((const unsigned char *)data)[2] == 0x08)&&
-			     (((const unsigned char *)data)[3] == 0x00)&&
-			     (((const unsigned char *)data)[4] == 6)&&
-			     (((const unsigned char *)data)[5] == 4)&&
-			     (((const unsigned char *)data)[7] == 0x01)
-			    )
-				 ) {
-				// Cram IPv4 IP into ADI field to make IPv4 ARP broadcast channel specific and scalable
-				// Also: enableBroadcast() does not apply to ARP since it's required for IPv4
+			if ( (etherType == ZT_ETHERTYPE_ARP) && (len >= 28) && ((((const uint8_t *)data)[2] == 0x08)&&(((const uint8_t *)data)[3] == 0x00)&&(((const uint8_t *)data)[4] == 6)&&(((const uint8_t *)data)[5] == 4)&&(((const uint8_t *)data)[7] == 0x01)) ) {
+				/* IPv4 ARP is one of the few special cases that we impose upon what is
+				 * otherwise a straightforward Ethernet switch emulation. Vanilla ARP
+				 * is dumb old broadcast and simply doesn't scale. ZeroTier multicast
+				 * groups have an additional field called ADI (additional distinguishing
+			   * information) which was added specifically for ARP though it could
+				 * be used for other things too. We then take ARP broadcasts and turn
+				 * them into multicasts by stuffing the IP address being queried into
+				 * the 32-bit ADI field. In practice this uses our multicast pub/sub
+				 * system to implement a kind of extended/distributed ARP table. */
 				mg = MulticastGroup::deriveMulticastGroupForAddressResolution(InetAddress(((const unsigned char *)data) + 24,4,0));
 			} else if (!nconf->enableBroadcast()) {
 				// Don't transmit broadcasts if this network doesn't want them
 				TRACE("%.16llx: dropped broadcast since ff:ff:ff:ff:ff:ff is not enabled",network->id());
 				return;
 			}
+		} else if ((etherType == ZT_ETHERTYPE_IPV6)&&(len >= (40 + 8 + 16))) {
+			/* IPv6 NDP emulation on ZeroTier-RFC4193 addressed networks! This allows
+			 * for multicast-free operation in IPv6 networks, which both improves
+			 * performance and is friendlier to mobile and (especially) IoT devices.
+			 * In the future there may be a no-multicast build option for embedded
+			 * and IoT use and this will be the preferred addressing mode. Note that
+			 * it plays nice with our L2 emulation philosophy and even with bridging.
+			 * While "real" devices behind the bridge can't have ZT-RFC4193 addresses
+			 * themselves, they can look these addresses up with NDP and it will
+			 * work just fine. */
+			if ((reinterpret_cast<const uint8_t *>(data)[6] == 0x3a)&&(reinterpret_cast<const uint8_t *>(data)[40] == 0x87)) { // ICMPv6 neighbor solicitation
+				for(std::vector<InetAddress>::const_iterator sip(nconf->staticIps().begin()),sipend(nconf->staticIps().end());sip!=sipend;++sip) {
+					if ((sip->ss_family == AF_INET6)&&(Utils::ntoh((uint16_t)reinterpret_cast<const struct sockaddr_in6 *>(&(*sip))->sin6_port) == 88)) {
+						const uint8_t *my6 = reinterpret_cast<const uint8_t *>(reinterpret_cast<const struct sockaddr_in6 *>(&(*sip))->sin6_addr.s6_addr);
+						if ((my6[0] == 0xfd)&&(my6[9] == 0x99)&&(my6[10] == 0x93)) { // ZT-RFC4193 == fd__:____:____:____:__99:93__:____:____ / 88
+							const uint8_t *pkt6 = reinterpret_cast<const uint8_t *>(data) + 40 + 8;
+							unsigned int ptr = 0;
+							while (ptr != 11) {
+								if (pkt6[ptr] != my6[ptr])
+									break;
+								++ptr;
+							}
+							if (ptr == 11) { // /88 matches an assigned address on this network
+								const Address atPeer(pkt6 + ptr,5);
+								if (atPeer != RR->identity.address()) {
+									const MAC atPeerMac(atPeer,network->id());
+									TRACE("ZT-RFC4193 NDP emulation: %.16llx: forging response for %s/%s",network->id(),atPeer.toString().c_str(),atPeerMac.toString().c_str());
+
+									uint8_t adv[72];
+									adv[0] = 0x60; adv[1] = 0x00; adv[2] = 0x00; adv[3] = 0x00;
+									adv[4] = 0x00; adv[5] = 0x20;
+									adv[6] = 0x3a; adv[7] = 0xff;
+									for(int i=0;i<16;++i) adv[8 + i] = pkt6[i];
+									for(int i=0;i<16;++i) adv[24 + i] = my6[i];
+									adv[40] = 0x88; adv[41] = 0x00;
+									adv[42] = 0x00; adv[43] = 0x00; // future home of checksum
+									adv[44] = 0x60; adv[45] = 0x00; adv[46] = 0x00; adv[47] = 0x00;
+									for(int i=0;i<16;++i) adv[48 + i] = pkt6[i];
+									adv[64] = 0x02; adv[65] = 0x01;
+									adv[66] = atPeerMac[0]; adv[67] = atPeerMac[1]; adv[68] = atPeerMac[2]; adv[69] = atPeerMac[3]; adv[70] = atPeerMac[4]; adv[71] = atPeerMac[5];
+
+									uint16_t pseudo_[36];
+									uint8_t *const pseudo = reinterpret_cast<uint8_t *>(pseudo_);
+									for(int i=0;i<32;++i) pseudo[i] = adv[8 + i];
+									pseudo[32] = 0x00; pseudo[33] = 0x00; pseudo[34] = 0x00; pseudo[35] = 0x20;
+									pseudo[36] = 0x00; pseudo[37] = 0x00; pseudo[38] = 0x00; pseudo[39] = 0x3a;
+									for(int i=0;i<32;++i) pseudo[40 + i] = adv[40 + i];
+									uint32_t checksum = 0;
+									for(int i=0;i<36;++i) checksum += Utils::hton(pseudo_[i]);
+									while ((checksum >> 16)) checksum = (checksum & 0xffff) + (checksum >> 16);
+									checksum = ~checksum;
+									adv[42] = (checksum >> 8) & 0xff;
+									adv[43] = checksum & 0xff;
+
+									RR->node->putFrame(network->id(),atPeerMac,from,ZT_ETHERTYPE_IPV6,0,adv,72);
+									return; // stop processing: we have handled this frame with a spoofed local reply so no need to send it anywhere
+								}
+							}
+						}
+					}
+				}
+			}
 		}
 
 		/* Learn multicast groups for bridged-in hosts.
diff --git a/selftest.cpp b/selftest.cpp
index 0787925f8..fa8df48bf 100644
--- a/selftest.cpp
+++ b/selftest.cpp
@@ -41,6 +41,7 @@
 #include "node/InetAddress.hpp"
 #include "node/Utils.hpp"
 #include "node/Identity.hpp"
+#include "node/Buffer.hpp"
 #include "node/Packet.hpp"
 #include "node/Salsa20.hpp"
 #include "node/MAC.hpp"