diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 7b5318e45..9d5c5f179 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -292,6 +292,7 @@ bool NetworkConfig::fromDictionary(const Dictionaryflags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING; if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD)) this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; + this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; // always enable for old-style netconf this->type = (d.getB(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,true)) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,tmp2,sizeof(tmp2)) > 0) { diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index c137a2a5d..117b18d2e 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -47,6 +47,11 @@ */ #define ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST 0x0000000000000002ULL +/** + * Flag: enable IPv6 NDP emulation for certain V6 address patterns + */ +#define ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION 0x0000000000000004ULL + /** * Device is a network preferred relay */ @@ -257,6 +262,11 @@ public: */ inline bool enableBroadcast() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST) != 0); } + /** + * @return True if IPv6 NDP emulation should be allowed for certain "magic" IPv6 address patterns + */ + inline bool ndpEmulation() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION) != 0); } + /** * @return Network type is public (no access control) */ diff --git a/node/Switch.cpp b/node/Switch.cpp index e7cda1bd1..4b08b1fc2 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -351,67 +351,88 @@ void Switch::onLocalEthernet(const SharedPtr &network,const MAC &from,c 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(data)[6] == 0x3a)&&(reinterpret_cast(data)[40] == 0x87)) { // ICMPv6 neighbor solicitation + // IPv6 NDP emulation for certain very special types of IPv6 addresses + if ((network->config().ndpEmulation())&&(reinterpret_cast(data)[6] == 0x3a)&&(reinterpret_cast(data)[40] == 0x87)) { // ICMPv6 neighbor solicitation + Address v6EmbeddedAddress; + const uint8_t *const pkt6 = reinterpret_cast(data) + 40 + 8; + const uint8_t *my6 = (const uint8_t *)0; + + // ZT-rfc4193 address: fdNN:NNNN:NNNN:NNNN:NN99:93DD:DDDD:DDDD / 88 + // ZT-v6host address: fdNN:NNNN:NNDD:DDDD:DDDD:####:####:#### / 40 (/80 usable per-host) + for(unsigned int sipk=0;sipkconfig().staticIpCount;++sipk) { const InetAddress *sip = &(network->config().staticIps[sipk]); - if ((sip->ss_family == AF_INET6)&&(Utils::ntoh((uint16_t)reinterpret_cast(&(*sip))->sin6_port) == 88)) { - const uint8_t *my6 = reinterpret_cast(reinterpret_cast(&(*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(data) + 40 + 8; + const unsigned int sipNetmaskBits = Utils::ntoh((uint16_t)reinterpret_cast(&(*sip))->sin6_port); + if ((sip->ss_family == AF_INET6)&&(sipNetmaskBits == 88)) { + my6 = reinterpret_cast(reinterpret_cast(&(*sip))->sin6_addr.s6_addr); + if ((my6[0] == 0xfd)&&(my6[9] == 0x99)&&(my6[10] == 0x93)) { 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(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(),network->userPtr(),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 - } + if (ptr == 11) { + v6EmbeddedAddress.setTo(pkt6 + ptr,5); + break; + } + } + } else if ((sip->ss_family == AF_INET6)&&(sipNetmaskBits == 40)) { + my6 = reinterpret_cast(reinterpret_cast(&(*sip))->sin6_addr.s6_addr); + const uint64_t nwid = network->id(); + const uint32_t nwid32 = (uint32_t)(nwid & 0xffffffff) ^ (uint32_t)((nwid >> 32) & 0xffffffff); + if ( (my6[0] == 0xfd) && + (my6[1] == (uint8_t)((nwid32 >> 24) & 0xff)) && + (my6[2] == (uint8_t)((nwid32 >> 16) & 0xff)) && + (my6[3] == (uint8_t)((nwid32 >> 8) & 0xff)) && + (my6[4] == (uint8_t)(nwid32 & 0xff)) ) { + unsigned int ptr = 0; + while (ptr != 5) { + if (pkt6[ptr] != my6[ptr]) + break; + ++ptr; + } + if (ptr == 5) { + v6EmbeddedAddress.setTo(pkt6 + ptr,5); + break; } } } } + + if ((v6EmbeddedAddress)&&(v6EmbeddedAddress != RR->identity.address())) { + const MAC peerMac(v6EmbeddedAddress,network->id()); + TRACE("IPv6 NDP emulation: %.16llx: forging response for %s/%s",network->id(),v6EmbeddedAddress.toString().c_str(),peerMac.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] = peerMac[0]; adv[67] = peerMac[1]; adv[68] = peerMac[2]; adv[69] = peerMac[3]; adv[70] = peerMac[4]; adv[71] = peerMac[5]; + + uint16_t pseudo_[36]; + uint8_t *const pseudo = reinterpret_cast(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(),network->userPtr(),peerMac,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 + } } }