summaryrefslogtreecommitdiff
path: root/node/Switch.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'node/Switch.cpp')
-rw-r--r--node/Switch.cpp85
1 files changed, 72 insertions, 13 deletions
diff --git a/node/Switch.cpp b/node/Switch.cpp
index b7a9c522..97befbc6 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.