From afdc91f21fabc2fedd81d7c0b0529669d963f50b Mon Sep 17 00:00:00 2001
From: Travis LaDuke <travisladuke@gmail.com>
Date: Fri, 5 Nov 2021 08:02:10 -0700
Subject: [PATCH] Convince macOS to do ipv6 dns lookups

Puts a value into System Config, similar to how DNS push works.

closes #1466

https://apple.stackexchange.com/questions/309430/ipv6-dns-resolution-on-macos-high-sierra
---
 osdep/MacDNSHelper.hpp   |   3 +
 osdep/MacDNSHelper.mm    | 133 ++++++++++++++++++++++++++++++++++++++-
 osdep/MacEthernetTap.cpp |   1 +
 service/OneService.cpp   |   5 ++
 4 files changed, 141 insertions(+), 1 deletion(-)

diff --git a/osdep/MacDNSHelper.hpp b/osdep/MacDNSHelper.hpp
index 7a2902fa5..2fa283621 100644
--- a/osdep/MacDNSHelper.hpp
+++ b/osdep/MacDNSHelper.hpp
@@ -3,6 +3,7 @@
 
 #include <vector>
 #include "../node/InetAddress.hpp"
+#include "../node/MAC.hpp"
 
 namespace ZeroTier {
 
@@ -11,6 +12,8 @@ class MacDNSHelper
 public:
     static void setDNS(uint64_t nwid, const char *domain, const std::vector<InetAddress> &servers);
     static void removeDNS(uint64_t nwid);
+    static bool addIps(uint64_t nwid, const MAC mac, const char *dev, const std::vector<InetAddress> &addrs);
+    static bool removeIps(uint64_t nwid);
 };
 
 }
diff --git a/osdep/MacDNSHelper.mm b/osdep/MacDNSHelper.mm
index 38e74dc3f..8a5bebd92 100644
--- a/osdep/MacDNSHelper.mm
+++ b/osdep/MacDNSHelper.mm
@@ -6,6 +6,11 @@
 
 namespace ZeroTier {
 
+static void printKeys (const void* key, const void* value, void* context) {
+  CFShow(key);
+  CFShow(value);
+}
+
 void MacDNSHelper::setDNS(uint64_t nwid, const char *domain, const std::vector<InetAddress> &servers)
 {
     SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL);
@@ -85,4 +90,130 @@ void MacDNSHelper::removeDNS(uint64_t nwid)
     CFRelease(ds);
 }
 
-}
\ No newline at end of file
+// Make macOS believe we do in fact have ipv6 connectivity and that it should resolve dns names
+// over ipv6 if we ask for them.
+// Originally I planned to put all the v6 ip addresses from the network into the config.
+// But only the link local address is necessary and sufficient. Added other v6 addresses
+// doesn't do anything.
+bool MacDNSHelper::addIps(uint64_t nwid, const MAC mac, const char *dev, const std::vector<InetAddress>& addrs)
+{
+
+    bool hasV6 = false;
+    for (unsigned int i = 0; i < addrs.size(); ++i) {
+        if (addrs[i].isV6()) {
+            hasV6 = true;
+            break;
+        }
+    }
+
+    if (!hasV6) {
+        MacDNSHelper::removeIps(nwid);
+        return true;
+    }
+
+
+    SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL);
+    char buf[256] = { 0 };
+    sprintf(buf, "State:/Network/Service/%.16llx/IPv6", nwid);
+
+    InetAddress ll = InetAddress::makeIpv6LinkLocal(mac);
+    char buf2[256] = {0};
+    const char* llStr = ll.toIpString(buf2);
+
+
+    CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
+    CFStringRef* cfaddrs = new CFStringRef[1];
+    CFStringRef* cfprefixes = new CFStringRef[1];
+    CFStringRef* cfdestaddrs = new CFStringRef[1];
+    CFStringRef* cfflags = new CFStringRef[1];
+
+
+    cfaddrs[0] = CFStringCreateWithCString(NULL, llStr, kCFStringEncodingUTF8);
+    cfprefixes[0] = CFStringCreateWithCString(NULL, "64", kCFStringEncodingUTF8);
+    cfdestaddrs[0] = CFStringCreateWithCString(NULL, "::ffff:ffff:ffff:ffff:0:0", kCFStringEncodingUTF8);
+    cfflags[0] = CFStringCreateWithCString(NULL, "0", kCFStringEncodingUTF8);
+
+    CFArrayRef addrArray = CFArrayCreate(NULL, (const void**)cfaddrs, 1, &kCFTypeArrayCallBacks);
+    CFArrayRef prefixArray = CFArrayCreate(NULL, (const void**)cfprefixes, 1, &kCFTypeArrayCallBacks);
+    CFArrayRef destArray = CFArrayCreate(NULL, (const void**)cfdestaddrs, 1, &kCFTypeArrayCallBacks);
+    CFArrayRef flagsArray = CFArrayCreate(NULL, (const void**)cfflags, 1, &kCFTypeArrayCallBacks);
+    CFStringRef cfdev = CFStringCreateWithCString(NULL, dev, kCFStringEncodingUTF8);
+
+    const int SIZE = 5;
+    CFStringRef keys[SIZE];
+    keys[0] = CFSTR("Addresses");
+    keys[1] = CFSTR("DestAddresses");
+    keys[2] = CFSTR("Flags");
+    keys[3] = CFSTR("InterfaceName");
+    keys[4] = CFSTR("PrefixLength");
+
+    CFTypeRef values[SIZE];
+    values[0] = addrArray;
+    values[1] = destArray;
+    values[2] = flagsArray;
+    // values[3] = devArray;
+    values[3] = cfdev;
+    values[4] = prefixArray;
+
+
+    CFDictionaryRef dict = CFDictionaryCreate(NULL,
+        (const void**)keys, (const void**)values, SIZE, &kCFCopyStringDictionaryKeyCallBacks,
+        &kCFTypeDictionaryValueCallBacks);
+
+    // CFDictionaryApplyFunction(dict, printKeys, NULL);
+
+    CFArrayRef list = SCDynamicStoreCopyKeyList(ds, key);
+    CFIndex i = 0, j = CFArrayGetCount(list);
+    bool addrsChanged = true;
+    CFPropertyListRef oldAddrs = NULL;
+
+    bool ret = TRUE;
+    if (j > 0) {
+        oldAddrs = SCDynamicStoreCopyValue(ds, (CFStringRef)CFArrayGetValueAtIndex(list, i));
+        addrsChanged = !CFEqual(oldAddrs,dict);
+    }
+    if (addrsChanged) {
+        if (j <= 0) {
+            ret &= SCDynamicStoreAddValue(ds, key, dict);
+        } else {
+            ret &= SCDynamicStoreSetValue(ds, (CFStringRef)CFArrayGetValueAtIndex(list, i), dict);
+        }
+        if (!ret) {
+            fprintf(stderr, "Error writing IPv6 configuration\n");
+        }
+    }
+
+    CFRelease(addrArray);
+    CFRelease(prefixArray);
+    CFRelease(destArray);
+    CFRelease(flagsArray);
+    CFRelease(cfdev);
+
+    CFRelease(list);
+    CFRelease(dict);
+
+    CFRelease(ds);
+    CFRelease(key);
+
+    delete[] cfaddrs;
+    delete[] cfprefixes;
+    delete[] cfdestaddrs;
+    delete[] cfflags;
+
+    return ret;
+}
+bool MacDNSHelper::removeIps(uint64_t nwid)
+{
+    SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL);
+
+    char buf[256] = {0};
+    sprintf(buf, "State:/Network/Service/%.16llx/IPv6", nwid);
+    CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
+    bool res = SCDynamicStoreRemoveValue(ds, key);
+    CFRelease(key);
+    CFRelease(ds);
+
+    return res;
+}
+
+}
diff --git a/osdep/MacEthernetTap.cpp b/osdep/MacEthernetTap.cpp
index 1ba82dcb7..55822fd5a 100644
--- a/osdep/MacEthernetTap.cpp
+++ b/osdep/MacEthernetTap.cpp
@@ -244,6 +244,7 @@ MacEthernetTap::~MacEthernetTap()
 	pid_t pid0,pid1;
 
 	MacDNSHelper::removeDNS(_nwid);
+	MacDNSHelper::removeIps(_nwid);
 
 	Mutex::Lock _gl(globalTapCreateLock);
 	::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit
diff --git a/service/OneService.cpp b/service/OneService.cpp
index 255bad21e..68891b8e5 100644
--- a/service/OneService.cpp
+++ b/service/OneService.cpp
@@ -2041,6 +2041,11 @@ public:
 						fprintf(stderr,"ERROR: unable to add ip address %s" ZT_EOL_S, ip->toString(ipbuf));
 				}
 			}
+
+#ifdef __APPLE__
+			if (!MacDNSHelper::addIps(n.config.nwid, n.config.mac, n.tap->deviceName().c_str(), newManagedIps))
+				fprintf(stderr, "ERROR: unable to add v6 addresses to system configuration" ZT_EOL_S);
+#endif
 #endif
 			n.managedIps.swap(newManagedIps);
 		}