/* -*- Mode: C; tab-width: 4 -*- * * Copyright (c) 2002-2016 Apple Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // *************************************************************************** // mDNSMacOSX.c: // Supporting routines to run mDNS on a CFRunLoop platform // *************************************************************************** // For debugging, set LIST_ALL_INTERFACES to 1 to display all found interfaces, // including ones that mDNSResponder chooses not to use. #define LIST_ALL_INTERFACES 0 #include "mDNSEmbeddedAPI.h" // Defines the interface provided to the client layer above #include "DNSCommon.h" #include "uDNS.h" #include "mDNSMacOSX.h" // Defines the specific types needed to run mDNS on this platform #include "dns_sd.h" // For mDNSInterface_LocalOnly etc. #include "dns_sd_internal.h" #include "PlatformCommon.h" #include "uds_daemon.h" #include "CryptoSupport.h" #include #include // For va_list support #include // For arc4random #include #include // For IFT_ETHER #include #include // For BIOCSETIF etc. #include #include #include #include #include #include #include #include // platform support for UTC time #include // for inet_aton #include #include // for getaddrinfo #include // for SIOCGIFEFLAGS #include #include // For IP_RECVTTL #ifndef IP_RECVTTL #define IP_RECVTTL 24 // bool; receive reception TTL w/dgram #endif #include // For n_long, required by below #include // For IPTOS_LOWDELAY etc. #include // For IN6_IFF_TENTATIVE etc. #include #include #include "dnsinfo.h" #include #include #include #include #include #include #include #include #include #include "helper.h" #include "P2PPacketFilter.h" #include #if TARGET_OS_IPHONE // For WiFiManagerClientRef etc, declarations. #include #include #include #endif // TARGET_OS_IPHONE // Include definition of opaque_presence_indication for KEV_DL_NODE_PRESENCE handling logic. #include #include // for nwi_state #if APPLE_OSX_mDNSResponder #include #include // for ne_session_set_socket_attributes() #else #define NO_AWACS 1 #endif // APPLE_OSX_mDNSResponder #if APPLE_OSX_mDNSResponder && !TARGET_OS_EMBEDDED #include #endif // APPLE_OSX_mDNSResponder && !TARGET_OS_EMBEDDED #ifdef UNIT_TEST #include "unittest.h" #endif #define kInterfaceSpecificOption "interface=" #define mDNS_IOREG_KEY "mDNS_KEY" #define mDNS_IOREG_VALUE "2009-07-30" #define mDNS_IOREG_KA_KEY "mDNS_Keepalive" #define mDNS_USER_CLIENT_CREATE_TYPE 'mDNS' #define DARK_WAKE_TIME 16 // Time we hold an idle sleep assertion for maintenance after a wake notification // cache the InterfaceID of the AWDL interface mDNSInterfaceID AWDLInterfaceID; // *************************************************************************** // Globals #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - Globals #endif // By default we don't offer sleep proxy service // If OfferSleepProxyService is set non-zero (typically via command-line switch), // then we'll offer sleep proxy service on desktop Macs that are set to never sleep. // We currently do not offer sleep proxy service on laptops, or on machines that are set to go to sleep. mDNSexport int OfferSleepProxyService = 0; mDNSexport int DisableSleepProxyClient = 0; mDNSexport int UseInternalSleepProxy = 1; // Set to non-zero to use internal (in-NIC) Sleep Proxy mDNSexport int OSXVers, iOSVers; mDNSexport int KQueueFD; #ifndef NO_SECURITYFRAMEWORK static CFArrayRef ServerCerts; OSStatus SSLSetAllowAnonymousCiphers(SSLContextRef context, Boolean enable); #endif /* NO_SECURITYFRAMEWORK */ static CFStringRef NetworkChangedKey_IPv4; static CFStringRef NetworkChangedKey_IPv6; static CFStringRef NetworkChangedKey_Hostnames; static CFStringRef NetworkChangedKey_Computername; static CFStringRef NetworkChangedKey_DNS; static CFStringRef NetworkChangedKey_StateInterfacePrefix; static CFStringRef NetworkChangedKey_DynamicDNS = CFSTR("Setup:/Network/DynamicDNS"); static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac"); static CFStringRef NetworkChangedKey_BTMMConnectivity = CFSTR("State:/Network/Connectivity"); static CFStringRef NetworkChangedKey_PowerSettings = CFSTR("State:/IOKit/PowerManagement/CurrentSettings"); static char HINFO_HWstring_buffer[32]; static char *HINFO_HWstring = "Device"; static int HINFO_HWstring_prefixlen = 6; mDNSexport int WatchDogReportingThreshold = 250; dispatch_queue_t SSLqueue; #if TARGET_OS_EMBEDDED #define kmDNSResponderManagedPrefsID CFSTR("/Library/Managed Preferences/mobile/com.apple.mDNSResponder.plist") #endif #if APPLE_OSX_mDNSResponder static mDNSu8 SPMetricPortability = 99; static mDNSu8 SPMetricMarginalPower = 99; static mDNSu8 SPMetricTotalPower = 99; static mDNSu8 SPMetricFeatures = 1; /* The current version supports TCP Keep Alive Feature */ mDNSexport domainname ActiveDirectoryPrimaryDomain; mDNSexport int ActiveDirectoryPrimaryDomainLabelCount; mDNSexport mDNSAddr ActiveDirectoryPrimaryDomainServer; #endif // APPLE_OSX_mDNSResponder // Don't send triggers too often. We arbitrarily limit it to three minutes. #define DNS_TRIGGER_INTERVAL (180 * mDNSPlatformOneSecond) // Used by AutoTunnel const char btmmprefix[] = "btmmdns:"; const char dnsprefix[] = "dns:"; // String Array used to write list of private domains to Dynamic Store static CFArrayRef privateDnsArray = NULL; // *************************************************************************** // Functions #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - #pragma mark - Utility Functions #endif // We only attempt to send and receive multicast packets on interfaces that are // (a) flagged as multicast-capable // (b) *not* flagged as point-to-point (e.g. modem) // Typically point-to-point interfaces are modems (including mobile-phone pseudo-modems), and we don't want // to run up the user's bill sending multicast traffic over a link where there's only a single device at the // other end, and that device (e.g. a modem bank) is probably not answering Multicast DNS queries anyway. #if BONJOUR_ON_DEMAND #define MulticastInterface(i) ((i)->m->BonjourEnabled && ((i)->ifa_flags & IFF_MULTICAST) && !((i)->ifa_flags & IFF_POINTOPOINT)) #else #define MulticastInterface(i) (((i)->ifa_flags & IFF_MULTICAST) && !((i)->ifa_flags & IFF_POINTOPOINT)) #endif #define SPSInterface(i) ((i)->ifinfo.McastTxRx && !((i)->ifa_flags & IFF_LOOPBACK) && !(i)->D2DInterface) mDNSexport void NotifyOfElusiveBug(const char *title, const char *msg) // Both strings are UTF-8 text { // Unless ForceAlerts is defined, we only show these bug report alerts on machines that have a 17.x.x.x address #if !ForceAlerts { // Determine if we're at Apple (17.*.*.*) NetworkInterfaceInfoOSX *i; for (i = mDNSStorage.p->InterfaceList; i; i = i->next) if (i->ifinfo.ip.type == mDNSAddrType_IPv4 && i->ifinfo.ip.ip.v4.b[0] == 17) break; if (!i) return; // If not at Apple, don't show the alert } #endif LogMsg("NotifyOfElusiveBug: %s", title); LogMsg("NotifyOfElusiveBug: %s", msg); // If we display our alert early in the boot process, then it vanishes once the desktop appears. // To avoid this, we don't try to display alerts in the first three minutes after boot. if ((mDNSu32)(mDNSPlatformRawTime()) < (mDNSu32)(mDNSPlatformOneSecond * 180)) { LogMsg("Suppressing notification early in boot: %d", mDNSPlatformRawTime()); return; } #ifndef NO_CFUSERNOTIFICATION static int notifyCount = 0; // To guard against excessive display of warning notifications if (notifyCount < 5) { notifyCount++; mDNSNotify(title, msg); } #endif /* NO_CFUSERNOTIFICATION */ } // Write a syslog message and display an alert, then if ForceAlerts is set, generate a stack trace #if APPLE_OSX_mDNSResponder && MACOSX_MDNS_MALLOC_DEBUGGING >= 1 mDNSexport void LogMemCorruption(const char *format, ...) { char buffer[512]; va_list ptr; va_start(ptr,format); buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0; va_end(ptr); LogMsg("!!!! %s !!!!", buffer); NotifyOfElusiveBug("Memory Corruption", buffer); #if ForceAlerts *(volatile long*)0 = 0; // Trick to crash and get a stack trace right here, if that's what we want #endif } #endif // Like LogMemCorruption above, but only display the alert if ForceAlerts is set and we're going to generate a stack trace #if APPLE_OSX_mDNSResponder mDNSexport void LogFatalError(const char *format, ...) { char buffer[512]; va_list ptr; va_start(ptr,format); buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0; va_end(ptr); LogMsg("!!!! %s !!!!", buffer); #if ForceAlerts NotifyOfElusiveBug("Fatal Error. See /Library/Logs/DiagnosticReports", buffer); *(volatile long*)0 = 0; // Trick to crash and get a stack trace right here, if that's what we want #endif } #endif // Returns true if it is an AppleTV based hardware running iOS, false otherwise mDNSlocal mDNSBool IsAppleTV(void) { #if TARGET_OS_EMBEDDED static mDNSBool sInitialized = mDNSfalse; static mDNSBool sIsAppleTV = mDNSfalse; CFStringRef deviceClass = NULL; if(!sInitialized) { deviceClass = (CFStringRef) MGCopyAnswer(kMGQDeviceClass, NULL); if(deviceClass) { if(CFEqual(deviceClass, kMGDeviceClassAppleTV)) sIsAppleTV = mDNStrue; CFRelease(deviceClass); } sInitialized = mDNStrue; } return(sIsAppleTV); #else return mDNSfalse; #endif // TARGET_OS_EMBEDDED } mDNSlocal struct ifaddrs *myGetIfAddrs(int refresh) { static struct ifaddrs *ifa = NULL; if (refresh && ifa) { freeifaddrs(ifa); ifa = NULL; } if (ifa == NULL) getifaddrs(&ifa); return ifa; } mDNSlocal void DynamicStoreWrite(int key, const char* subkey, uintptr_t value, signed long valueCnt) { CFStringRef sckey = NULL; Boolean release_sckey = FALSE; CFDataRef bytes = NULL; CFPropertyListRef plist = NULL; switch ((enum mDNSDynamicStoreSetConfigKey)key) { case kmDNSMulticastConfig: sckey = CFSTR("State:/Network/" kDNSServiceCompMulticastDNS); break; case kmDNSDynamicConfig: sckey = CFSTR("State:/Network/DynamicDNS"); break; case kmDNSPrivateConfig: sckey = CFSTR("State:/Network/" kDNSServiceCompPrivateDNS); break; case kmDNSBackToMyMacConfig: sckey = CFSTR("State:/Network/BackToMyMac"); break; case kmDNSSleepProxyServersState: { CFMutableStringRef tmp = CFStringCreateMutable(kCFAllocatorDefault, 0); CFStringAppend(tmp, CFSTR("State:/Network/Interface/")); CFStringAppendCString(tmp, subkey, kCFStringEncodingUTF8); CFStringAppend(tmp, CFSTR("/SleepProxyServers")); sckey = CFStringCreateCopy(kCFAllocatorDefault, tmp); release_sckey = TRUE; CFRelease(tmp); break; } case kmDNSDebugState: sckey = CFSTR("State:/Network/mDNSResponder/DebugState"); break; default: LogMsg("unrecognized key %d", key); goto fin; } if (NULL == (bytes = CFDataCreateWithBytesNoCopy(NULL, (void *)value, valueCnt, kCFAllocatorNull))) { LogMsg("CFDataCreateWithBytesNoCopy of value failed"); goto fin; } if (NULL == (plist = CFPropertyListCreateWithData(NULL, bytes, kCFPropertyListImmutable, NULL, NULL))) { LogMsg("CFPropertyListCreateWithData of bytes failed"); goto fin; } CFRelease(bytes); bytes = NULL; SCDynamicStoreSetValue(NULL, sckey, plist); fin: if (NULL != bytes) CFRelease(bytes); if (NULL != plist) CFRelease(plist); if (release_sckey && sckey) CFRelease(sckey); } mDNSexport void mDNSDynamicStoreSetConfig(int key, const char *subkey, CFPropertyListRef value) { CFPropertyListRef valueCopy; char *subkeyCopy = NULL; if (!value) return; // We need to copy the key and value before we dispatch off the block below as the // caller will free the memory once we return from this function. valueCopy = CFPropertyListCreateDeepCopy(NULL, value, kCFPropertyListImmutable); if (!valueCopy) { LogMsg("mDNSDynamicStoreSetConfig: ERROR valueCopy NULL"); return; } if (subkey) { int len = strlen(subkey); subkeyCopy = mDNSPlatformMemAllocate(len + 1); if (!subkeyCopy) { LogMsg("mDNSDynamicStoreSetConfig: ERROR subkeyCopy NULL"); CFRelease(valueCopy); return; } mDNSPlatformMemCopy(subkeyCopy, subkey, len); subkeyCopy[len] = 0; } dispatch_async(dispatch_get_main_queue(), ^{ CFWriteStreamRef stream = NULL; CFDataRef bytes = NULL; CFIndex ret; KQueueLock(); if (NULL == (stream = CFWriteStreamCreateWithAllocatedBuffers(NULL, NULL))) { LogMsg("mDNSDynamicStoreSetConfig : CFWriteStreamCreateWithAllocatedBuffers failed (Object creation failed)"); goto END; } CFWriteStreamOpen(stream); ret = CFPropertyListWrite(valueCopy, stream, kCFPropertyListBinaryFormat_v1_0, 0, NULL); if (ret == 0) { LogMsg("mDNSDynamicStoreSetConfig : CFPropertyListWriteToStream failed (Could not write property list to stream)"); goto END; } if (NULL == (bytes = CFWriteStreamCopyProperty(stream, kCFStreamPropertyDataWritten))) { LogMsg("mDNSDynamicStoreSetConfig : CFWriteStreamCopyProperty failed (Object creation failed) "); goto END; } CFWriteStreamClose(stream); CFRelease(stream); stream = NULL; DynamicStoreWrite(key, subkeyCopy ? subkeyCopy : "", (uintptr_t)CFDataGetBytePtr(bytes), CFDataGetLength(bytes)); END: CFRelease(valueCopy); if (NULL != stream) { CFWriteStreamClose(stream); CFRelease(stream); } if (NULL != bytes) CFRelease(bytes); if (subkeyCopy) mDNSPlatformMemFree(subkeyCopy); KQueueUnlock("mDNSDynamicStoreSetConfig"); }); } // To match *either* a v4 or v6 instance of this interface name, pass AF_UNSPEC for type mDNSlocal NetworkInterfaceInfoOSX *SearchForInterfaceByName(const char *ifname, int type) { NetworkInterfaceInfoOSX *i; for (i = mDNSStorage.p->InterfaceList; i; i = i->next) if (i->Exists && !strcmp(i->ifinfo.ifname, ifname) && ((type == AF_UNSPEC ) || (type == AF_INET && i->ifinfo.ip.type == mDNSAddrType_IPv4) || (type == AF_INET6 && i->ifinfo.ip.type == mDNSAddrType_IPv6))) return(i); return(NULL); } mDNSlocal int myIfIndexToName(u_short ifindex, char *name) { struct ifaddrs *ifa; for (ifa = myGetIfAddrs(0); ifa; ifa = ifa->ifa_next) if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_LINK) if (((struct sockaddr_dl*)ifa->ifa_addr)->sdl_index == ifindex) { strlcpy(name, ifa->ifa_name, IF_NAMESIZE); return 0; } return -1; } mDNSexport NetworkInterfaceInfoOSX *IfindexToInterfaceInfoOSX(mDNSInterfaceID ifindex) { mDNS *const m = &mDNSStorage; mDNSu32 scope_id = (mDNSu32)(uintptr_t)ifindex; NetworkInterfaceInfoOSX *i; // Don't get tricked by inactive interfaces for (i = m->p->InterfaceList; i; i = i->next) if (i->Registered && i->scope_id == scope_id) return(i); return mDNSNULL; } mDNSexport mDNSInterfaceID mDNSPlatformInterfaceIDfromInterfaceIndex(mDNS *const m, mDNSu32 ifindex) { (void) m; if (ifindex == kDNSServiceInterfaceIndexLocalOnly) return(mDNSInterface_LocalOnly); if (ifindex == kDNSServiceInterfaceIndexP2P ) return(mDNSInterface_P2P); if (ifindex == kDNSServiceInterfaceIndexBLE ) return(mDNSInterface_BLE); if (ifindex == kDNSServiceInterfaceIndexAny ) return(mDNSNULL); NetworkInterfaceInfoOSX* ifi = IfindexToInterfaceInfoOSX((mDNSInterfaceID)(uintptr_t)ifindex); if (!ifi) { // Not found. Make sure our interface list is up to date, then try again. LogInfo("mDNSPlatformInterfaceIDfromInterfaceIndex: InterfaceID for interface index %d not found; Updating interface list", ifindex); mDNSMacOSXNetworkChanged(); ifi = IfindexToInterfaceInfoOSX((mDNSInterfaceID)(uintptr_t)ifindex); } if (!ifi) return(mDNSNULL); return(ifi->ifinfo.InterfaceID); } mDNSexport mDNSu32 mDNSPlatformInterfaceIndexfromInterfaceID(mDNS *const m, mDNSInterfaceID id, mDNSBool suppressNetworkChange) { NetworkInterfaceInfoOSX *i; if (id == mDNSInterface_Any ) return(0); if (id == mDNSInterface_LocalOnly) return(kDNSServiceInterfaceIndexLocalOnly); if (id == mDNSInterface_Unicast ) return(0); if (id == mDNSInterface_P2P ) return(kDNSServiceInterfaceIndexP2P); if (id == mDNSInterface_BLE ) return(kDNSServiceInterfaceIndexBLE); mDNSu32 scope_id = (mDNSu32)(uintptr_t)id; // Don't use i->Registered here, because we DO want to find inactive interfaces, which have no Registered set for (i = m->p->InterfaceList; i; i = i->next) if (i->scope_id == scope_id) return(i->scope_id); // If we are supposed to suppress network change, return "id" back if (suppressNetworkChange) return scope_id; // Not found. Make sure our interface list is up to date, then try again. LogInfo("Interface index for InterfaceID %p not found; Updating interface list", id); mDNSMacOSXNetworkChanged(); for (i = m->p->InterfaceList; i; i = i->next) if (i->scope_id == scope_id) return(i->scope_id); return(0); } #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - #pragma mark - UDP & TCP send & receive #endif mDNSlocal mDNSBool AddrRequiresPPPConnection(const struct sockaddr *addr) { mDNSBool result = mDNSfalse; SCNetworkConnectionFlags flags; CFDataRef remote_addr; CFMutableDictionaryRef options; SCNetworkReachabilityRef ReachRef = NULL; options = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); remote_addr = CFDataCreate(NULL, (const UInt8 *)addr, addr->sa_len); CFDictionarySetValue(options, kSCNetworkReachabilityOptionRemoteAddress, remote_addr); CFDictionarySetValue(options, kSCNetworkReachabilityOptionServerBypass, kCFBooleanTrue); ReachRef = SCNetworkReachabilityCreateWithOptions(kCFAllocatorDefault, options); CFRelease(options); CFRelease(remote_addr); if (!ReachRef) { LogMsg("ERROR: RequiresConnection - SCNetworkReachabilityCreateWithOptions"); goto end; } if (!SCNetworkReachabilityGetFlags(ReachRef, &flags)) { LogMsg("ERROR: AddrRequiresPPPConnection - SCNetworkReachabilityGetFlags"); goto end; } result = flags & kSCNetworkFlagsConnectionRequired; end: if (ReachRef) CFRelease(ReachRef); return result; } // Set traffic class for socket mDNSlocal void setTrafficClass(int socketfd, mDNSBool useBackgroundTrafficClass) { int traffic_class; if (useBackgroundTrafficClass) traffic_class = SO_TC_BK_SYS; else traffic_class = SO_TC_CTL; (void) setsockopt(socketfd, SOL_SOCKET, SO_TRAFFIC_CLASS, (void *)&traffic_class, sizeof(traffic_class)); } #ifdef UNIT_TEST // Run the unit test main UNITTEST_SETSOCKOPT #else mDNSlocal int mDNSPlatformGetSocktFd(void *sockCxt, mDNSTransport_Type transType, mDNSAddr_Type addrType) { if (transType == mDNSTransport_UDP) { UDPSocket* sock = (UDPSocket*) sockCxt; return (addrType == mDNSAddrType_IPv4) ? sock->ss.sktv4 : sock->ss.sktv6; } else if (transType == mDNSTransport_TCP) { TCPSocket* sock = (TCPSocket*) sockCxt; return (addrType == mDNSAddrType_IPv4) ? sock->ss.sktv4 : sock->ss.sktv6; } else { LogInfo("mDNSPlatformGetSocktFd: invalid transport %d", transType); return kInvalidSocketRef; } } mDNSexport void mDNSPlatformSetSocktOpt(void *sockCxt, mDNSTransport_Type transType, mDNSAddr_Type addrType, const DNSQuestion *q) { int sockfd; char unenc_name[MAX_ESCAPED_DOMAIN_NAME]; // verify passed-in arguments exist and that sockfd is valid if (q == mDNSNULL || sockCxt == mDNSNULL || (sockfd = mDNSPlatformGetSocktFd(sockCxt, transType, addrType)) < 0) return; if (q->pid) { if (setsockopt(sockfd, SOL_SOCKET, SO_DELEGATED, &q->pid, sizeof(q->pid)) == -1) LogMsg("mDNSPlatformSetSocktOpt: Delegate PID failed %s for PID %d", strerror(errno), q->pid); } else { if (setsockopt(sockfd, SOL_SOCKET, SO_DELEGATED_UUID, &q->uuid, sizeof(q->uuid)) == -1) LogMsg("mDNSPlatformSetSocktOpt: Delegate UUID failed %s", strerror(errno)); } // set the domain on the socket ConvertDomainNameToCString(&q->qname, unenc_name); if (!(ne_session_set_socket_attributes(sockfd, unenc_name, NULL))) LogInfo("mDNSPlatformSetSocktOpt: ne_session_set_socket_attributes()-> setting domain failed for %s", unenc_name); int nowake = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_NOWAKEFROMSLEEP, &nowake, sizeof(nowake)) == -1) LogInfo("mDNSPlatformSetSocktOpt: SO_NOWAKEFROMSLEEP failed %s", strerror(errno)); } #endif // UNIT_TEST // Note: If InterfaceID is NULL, it means, "send this packet through our anonymous unicast socket" // Note: If InterfaceID is non-NULL it means, "send this packet through our port 5353 socket on the specified interface" // OR send via our primary v4 unicast socket // UPDATE: The UDPSocket *src parameter now allows the caller to specify the source socket mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const msg, const mDNSu8 *const end, mDNSInterfaceID InterfaceID, UDPSocket *src, const mDNSAddr *dst, mDNSIPPort dstPort, mDNSBool useBackgroundTrafficClass) { NetworkInterfaceInfoOSX *info = mDNSNULL; struct sockaddr_storage to; int s = -1, err; mStatus result = mStatus_NoError; int sendto_errno; if (InterfaceID) { info = IfindexToInterfaceInfoOSX(InterfaceID); if (info == NULL) { // We may not have registered interfaces with the "core" as we may not have // seen any interface notifications yet. This typically happens during wakeup // where we might try to send DNS requests (non-SuppressUnusable questions internal // to mDNSResponder) before we receive network notifications. LogInfo("mDNSPlatformSendUDP: Invalid interface index %p", InterfaceID); return mStatus_BadParamErr; } } char *ifa_name = InterfaceID ? info->ifinfo.ifname : "unicast"; if (dst->type == mDNSAddrType_IPv4) { struct sockaddr_in *sin_to = (struct sockaddr_in*)&to; sin_to->sin_len = sizeof(*sin_to); sin_to->sin_family = AF_INET; sin_to->sin_port = dstPort.NotAnInteger; sin_to->sin_addr.s_addr = dst->ip.v4.NotAnInteger; s = (src ? src->ss : m->p->permanentsockets).sktv4; if (!mDNSAddrIsDNSMulticast(dst)) { #ifdef IP_BOUND_IF const mDNSu32 ifindex = info ? info->scope_id : IFSCOPE_NONE; setsockopt(s, IPPROTO_IP, IP_BOUND_IF, &ifindex, sizeof(ifindex)); #else static int displayed = 0; if (displayed < 1000) { displayed++; LogInfo("IP_BOUND_IF socket option not defined -- cannot specify interface for unicast packets"); } #endif } else if (info) { #ifdef IP_MULTICAST_IFINDEX err = setsockopt(s, IPPROTO_IP, IP_MULTICAST_IFINDEX, &info->scope_id, sizeof(info->scope_id)); // We get an error when we compile on a machine that supports this option and run the binary on // a different machine that does not support it if (err < 0) { if (errno != ENOPROTOOPT) LogInfo("mDNSPlatformSendUDP: setsockopt: IP_MUTLTICAST_IFINDEX returned %d", errno); err = setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &info->ifa_v4addr, sizeof(info->ifa_v4addr)); if (err < 0 && !m->NetworkChanged) LogMsg("setsockopt - IP_MULTICAST_IF error %.4a %d errno %d (%s)", &info->ifa_v4addr, err, errno, strerror(errno)); } #else err = setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &info->ifa_v4addr, sizeof(info->ifa_v4addr)); if (err < 0 && !m->NetworkChanged) LogMsg("setsockopt - IP_MULTICAST_IF error %.4a %d errno %d (%s)", &info->ifa_v4addr, err, errno, strerror(errno)); #endif } } else if (dst->type == mDNSAddrType_IPv6) { struct sockaddr_in6 *sin6_to = (struct sockaddr_in6*)&to; sin6_to->sin6_len = sizeof(*sin6_to); sin6_to->sin6_family = AF_INET6; sin6_to->sin6_port = dstPort.NotAnInteger; sin6_to->sin6_flowinfo = 0; sin6_to->sin6_addr = *(struct in6_addr*)&dst->ip.v6; sin6_to->sin6_scope_id = info ? info->scope_id : 0; s = (src ? src->ss : m->p->permanentsockets).sktv6; if (info && mDNSAddrIsDNSMulticast(dst)) // Specify outgoing interface { err = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &info->scope_id, sizeof(info->scope_id)); if (err < 0) { const int setsockopt_errno = errno; char name[IFNAMSIZ]; if (if_indextoname(info->scope_id, name) != NULL) LogMsg("setsockopt - IPV6_MULTICAST_IF error %d errno %d (%s)", err, setsockopt_errno, strerror(setsockopt_errno)); else LogInfo("setsockopt - IPV6_MUTLICAST_IF scopeid %d, not a valid interface", info->scope_id); } } #ifdef IPV6_BOUND_IF if (info) // Specify outgoing interface for non-multicast destination { if (!mDNSAddrIsDNSMulticast(dst)) { if (info->scope_id == 0) LogInfo("IPV6_BOUND_IF socket option not set -- info %p (%s) scope_id is zero", info, ifa_name); else setsockopt(s, IPPROTO_IPV6, IPV6_BOUND_IF, &info->scope_id, sizeof(info->scope_id)); } } #endif } else { LogFatalError("mDNSPlatformSendUDP: dst is not an IPv4 or IPv6 address!"); return mStatus_BadParamErr; } if (s >= 0) verbosedebugf("mDNSPlatformSendUDP: sending on InterfaceID %p %5s/%ld to %#a:%d skt %d", InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s); else verbosedebugf("mDNSPlatformSendUDP: NOT sending on InterfaceID %p %5s/%ld (socket of this type not available)", InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort)); // Note: When sending, mDNSCore may often ask us to send both a v4 multicast packet and then a v6 multicast packet // If we don't have the corresponding type of socket available, then return mStatus_Invalid if (s < 0) return(mStatus_Invalid); // switch to background traffic class for this message if requested if (useBackgroundTrafficClass) setTrafficClass(s, useBackgroundTrafficClass); err = sendto(s, msg, (UInt8*)end - (UInt8*)msg, 0, (struct sockaddr *)&to, to.ss_len); sendto_errno = (err < 0) ? errno : 0; // set traffic class back to default value if (useBackgroundTrafficClass) setTrafficClass(s, mDNSfalse); if (err < 0) { static int MessageCount = 0; LogInfo("mDNSPlatformSendUDP -> sendto(%d) failed to send packet on InterfaceID %p %5s/%d to %#a:%d skt %d error %d errno %d (%s) %lu", s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow)); if (!mDNSAddressIsAllDNSLinkGroup(dst)) { if (sendto_errno == EHOSTUNREACH) return(mStatus_HostUnreachErr); if (sendto_errno == EHOSTDOWN || sendto_errno == ENETDOWN || sendto_errno == ENETUNREACH) return(mStatus_TransientErr); } // Don't report EHOSTUNREACH in the first three minutes after boot // This is because mDNSResponder intentionally starts up early in the boot process (See ) // but this means that sometimes it starts before configd has finished setting up the multicast routing entries. if (sendto_errno == EHOSTUNREACH && (mDNSu32)(mDNSPlatformRawTime()) < (mDNSu32)(mDNSPlatformOneSecond * 180)) return(mStatus_TransientErr); // Don't report EADDRNOTAVAIL ("Can't assign requested address") if we're in the middle of a network configuration change if (sendto_errno == EADDRNOTAVAIL && m->NetworkChanged) return(mStatus_TransientErr); if (sendto_errno == EHOSTUNREACH || sendto_errno == EADDRNOTAVAIL || sendto_errno == ENETDOWN) LogInfo("mDNSPlatformSendUDP sendto(%d) failed to send packet on InterfaceID %p %5s/%d to %#a:%d skt %d error %d errno %d (%s) %lu", s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow)); else { MessageCount++; if (MessageCount < 50) // Cap and ensure NO spamming of LogMsgs LogMsg("mDNSPlatformSendUDP: sendto(%d) failed to send packet on InterfaceID %p %5s/%d to %#a:%d skt %d error %d errno %d (%s) %lu MessageCount is %d", s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow), MessageCount); else // If logging is enabled, remove the cap and log aggressively LogInfo("mDNSPlatformSendUDP: sendto(%d) failed to send packet on InterfaceID %p %5s/%d to %#a:%d skt %d error %d errno %d (%s) %lu MessageCount is %d", s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow), MessageCount); } result = mStatus_UnknownErr; } return(result); } mDNSexport ssize_t myrecvfrom(const int s, void *const buffer, const size_t max, struct sockaddr *const from, size_t *const fromlen, mDNSAddr *dstaddr, char ifname[IF_NAMESIZE], mDNSu8 *ttl) { static unsigned int numLogMessages = 0; struct iovec databuffers = { (char *)buffer, max }; struct msghdr msg; ssize_t n; struct cmsghdr *cmPtr; char ancillary[1024]; *ttl = 255; // If kernel fails to provide TTL data (e.g. Jaguar doesn't) then assume the TTL was 255 as it should be // Set up the message msg.msg_name = (caddr_t)from; msg.msg_namelen = *fromlen; msg.msg_iov = &databuffers; msg.msg_iovlen = 1; msg.msg_control = (caddr_t)&ancillary; msg.msg_controllen = sizeof(ancillary); msg.msg_flags = 0; // Receive the data n = recvmsg(s, &msg, 0); if (n<0) { if (errno != EWOULDBLOCK && numLogMessages++ < 100) LogMsg("mDNSMacOSX.c: recvmsg(%d) returned error %d errno %d", s, n, errno); return(-1); } if (msg.msg_controllen < (int)sizeof(struct cmsghdr)) { if (numLogMessages++ < 100) LogMsg("mDNSMacOSX.c: recvmsg(%d) returned %d msg.msg_controllen %d < sizeof(struct cmsghdr) %lu, errno %d", s, n, msg.msg_controllen, sizeof(struct cmsghdr), errno); return(-1); } if (msg.msg_flags & MSG_CTRUNC) { if (numLogMessages++ < 100) LogMsg("mDNSMacOSX.c: recvmsg(%d) msg.msg_flags & MSG_CTRUNC", s); return(-1); } *fromlen = msg.msg_namelen; // Parse each option out of the ancillary data. for (cmPtr = CMSG_FIRSTHDR(&msg); cmPtr; cmPtr = CMSG_NXTHDR(&msg, cmPtr)) { // debugf("myrecvfrom cmsg_level %d cmsg_type %d", cmPtr->cmsg_level, cmPtr->cmsg_type); if (cmPtr->cmsg_level == IPPROTO_IP && cmPtr->cmsg_type == IP_RECVDSTADDR) { dstaddr->type = mDNSAddrType_IPv4; dstaddr->ip.v4 = *(mDNSv4Addr*)CMSG_DATA(cmPtr); //LogMsg("mDNSMacOSX.c: recvmsg IP_RECVDSTADDR %.4a", &dstaddr->ip.v4); } if (cmPtr->cmsg_level == IPPROTO_IP && cmPtr->cmsg_type == IP_RECVIF) { struct sockaddr_dl *sdl = (struct sockaddr_dl *)CMSG_DATA(cmPtr); if (sdl->sdl_nlen < IF_NAMESIZE) { mDNSPlatformMemCopy(ifname, sdl->sdl_data, sdl->sdl_nlen); ifname[sdl->sdl_nlen] = 0; // debugf("IP_RECVIF sdl_index %d, sdl_data %s len %d", sdl->sdl_index, ifname, sdl->sdl_nlen); } } if (cmPtr->cmsg_level == IPPROTO_IP && cmPtr->cmsg_type == IP_RECVTTL) *ttl = *(u_char*)CMSG_DATA(cmPtr); if (cmPtr->cmsg_level == IPPROTO_IPV6 && cmPtr->cmsg_type == IPV6_PKTINFO) { struct in6_pktinfo *ip6_info = (struct in6_pktinfo*)CMSG_DATA(cmPtr); dstaddr->type = mDNSAddrType_IPv6; dstaddr->ip.v6 = *(mDNSv6Addr*)&ip6_info->ipi6_addr; myIfIndexToName(ip6_info->ipi6_ifindex, ifname); } if (cmPtr->cmsg_level == IPPROTO_IPV6 && cmPtr->cmsg_type == IPV6_HOPLIMIT) *ttl = *(int*)CMSG_DATA(cmPtr); } return(n); } // What is this for, and why does it use xor instead of a simple quality check? -- SC mDNSlocal mDNSInterfaceID FindMyInterface(const mDNSAddr *addr) { NetworkInterfaceInfo *intf; if (addr->type == mDNSAddrType_IPv4) { for (intf = mDNSStorage.HostInterfaces; intf; intf = intf->next) { if (intf->ip.type == addr->type && intf->McastTxRx) { if ((intf->ip.ip.v4.NotAnInteger ^ addr->ip.v4.NotAnInteger) == 0) { return(intf->InterfaceID); } } } } if (addr->type == mDNSAddrType_IPv6) { for (intf = mDNSStorage.HostInterfaces; intf; intf = intf->next) { if (intf->ip.type == addr->type && intf->McastTxRx) { if (((intf->ip.ip.v6.l[0] ^ addr->ip.v6.l[0]) == 0) && ((intf->ip.ip.v6.l[1] ^ addr->ip.v6.l[1]) == 0) && ((intf->ip.ip.v6.l[2] ^ addr->ip.v6.l[2]) == 0) && (((intf->ip.ip.v6.l[3] ^ addr->ip.v6.l[3]) == 0))) { return(intf->InterfaceID); } } } } return(mDNSInterface_Any); } mDNSexport void myKQSocketCallBack(int s1, short filter, void *context, mDNSBool encounteredEOF) { KQSocketSet *const ss = (KQSocketSet *)context; mDNS *const m = ss->m; int err = 0, count = 0, closed = 0; if (filter != EVFILT_READ) LogMsg("myKQSocketCallBack: Why is filter %d not EVFILT_READ (%d)?", filter, EVFILT_READ); if (s1 != ss->sktv4 && s1 != ss->sktv6) { LogMsg("myKQSocketCallBack: native socket %d", s1); LogMsg("myKQSocketCallBack: sktv4 %d sktv6 %d", ss->sktv4, ss->sktv6); } if (encounteredEOF) { LogMsg("myKQSocketCallBack: socket %d is no longer readable (EOF)", s1); if (s1 == ss->sktv4) { ss->sktv4EOF = mDNStrue; KQueueSet(ss->sktv4, EV_DELETE, EVFILT_READ, &ss->kqsv4); } else if (s1 == ss->sktv6) { ss->sktv6EOF = mDNStrue; KQueueSet(ss->sktv6, EV_DELETE, EVFILT_READ, &ss->kqsv6); } return; } while (!closed) { mDNSAddr senderAddr, destAddr = zeroAddr; mDNSIPPort senderPort; struct sockaddr_storage from; size_t fromlen = sizeof(from); char packetifname[IF_NAMESIZE] = ""; mDNSu8 ttl; err = myrecvfrom(s1, &m->imsg, sizeof(m->imsg), (struct sockaddr *)&from, &fromlen, &destAddr, packetifname, &ttl); if (err < 0) break; if ((destAddr.type == mDNSAddrType_IPv4 && (destAddr.ip.v4.b[0] & 0xF0) == 0xE0) || (destAddr.type == mDNSAddrType_IPv6 && (destAddr.ip.v6.b[0] == 0xFF))) m->p->num_mcasts++; count++; if (from.ss_family == AF_INET) { struct sockaddr_in *s = (struct sockaddr_in*)&from; senderAddr.type = mDNSAddrType_IPv4; senderAddr.ip.v4.NotAnInteger = s->sin_addr.s_addr; senderPort.NotAnInteger = s->sin_port; //LogInfo("myKQSocketCallBack received IPv4 packet from %#-15a to %#-15a on skt %d %s", &senderAddr, &destAddr, s1, packetifname); } else if (from.ss_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&from; senderAddr.type = mDNSAddrType_IPv6; senderAddr.ip.v6 = *(mDNSv6Addr*)&sin6->sin6_addr; senderPort.NotAnInteger = sin6->sin6_port; //LogInfo("myKQSocketCallBack received IPv6 packet from %#-15a to %#-15a on skt %d %s", &senderAddr, &destAddr, s1, packetifname); } else { LogMsg("myKQSocketCallBack from is unknown address family %d", from.ss_family); return; } // Note: When handling multiple packets in a batch, MUST reset InterfaceID before handling each packet mDNSInterfaceID InterfaceID = mDNSNULL; NetworkInterfaceInfoOSX *intf = m->p->InterfaceList; while (intf) { if (intf->Exists && !strcmp(intf->ifinfo.ifname, packetifname)) break; intf = intf->next; } // When going to sleep we deregister all our interfaces, but if the machine // takes a few seconds to sleep we may continue to receive multicasts // during that time, which would confuse mDNSCoreReceive, because as far // as it's concerned, we should have no active interfaces any more. // Hence we ignore multicasts for which we can find no matching InterfaceID. if (intf) InterfaceID = intf->ifinfo.InterfaceID; else if (mDNSAddrIsDNSMulticast(&destAddr)) continue; if (!InterfaceID) { InterfaceID = FindMyInterface(&destAddr); } // LogMsg("myKQSocketCallBack got packet from %#a to %#a on interface %#a/%s", // &senderAddr, &destAddr, &ss->info->ifinfo.ip, ss->info->ifinfo.ifname); // mDNSCoreReceive may close the socket we're reading from. We must break out of our // loop when that happens, or we may try to read from an invalid FD. We do this by // setting the closeFlag pointer in the socketset, so CloseSocketSet can inform us // if it closes the socketset. ss->closeFlag = &closed; if (ss->proxy) { m->p->UDPProxyCallback(&m->p->UDPProxy, &m->imsg.m, (unsigned char*)&m->imsg + err, &senderAddr, senderPort, &destAddr, ss->port, InterfaceID, NULL); } else { mDNSCoreReceive(m, &m->imsg.m, (unsigned char*)&m->imsg + err, &senderAddr, senderPort, &destAddr, ss->port, InterfaceID); } // if we didn't close, we can safely dereference the socketset, and should to // reset the closeFlag, since it points to something on the stack if (!closed) ss->closeFlag = mDNSNULL; } // If a client application's sockets are marked as defunct // sockets we have delegated to it with SO_DELEGATED will also go defunct. // We get an ENOTCONN error for defunct sockets and should just close the socket in that case. if (err < 0 && errno == ENOTCONN) { LogInfo("myKQSocketCallBack: ENOTCONN, closing socket"); close(s1); return; } if (err < 0 && (errno != EWOULDBLOCK || count == 0)) { // Something is busted here. // kqueue says there is a packet, but myrecvfrom says there is not. // Try calling select() to get another opinion. // Find out about other socket parameter that can help understand why select() says the socket is ready for read // All of this is racy, as data may have arrived after the call to select() static unsigned int numLogMessages = 0; const int save_errno = errno; int so_error = -1; int so_nread = -1; int fionread = -1; socklen_t solen = sizeof(int); fd_set readfds; struct timeval timeout; int selectresult; FD_ZERO(&readfds); FD_SET(s1, &readfds); timeout.tv_sec = 0; timeout.tv_usec = 0; selectresult = select(s1+1, &readfds, NULL, NULL, &timeout); if (getsockopt(s1, SOL_SOCKET, SO_ERROR, &so_error, &solen) == -1) LogMsg("myKQSocketCallBack getsockopt(SO_ERROR) error %d", errno); if (getsockopt(s1, SOL_SOCKET, SO_NREAD, &so_nread, &solen) == -1) LogMsg("myKQSocketCallBack getsockopt(SO_NREAD) error %d", errno); if (ioctl(s1, FIONREAD, &fionread) == -1) LogMsg("myKQSocketCallBack ioctl(FIONREAD) error %d", errno); if (numLogMessages++ < 100) LogMsg("myKQSocketCallBack recvfrom skt %d error %d errno %d (%s) select %d (%spackets waiting) so_error %d so_nread %d fionread %d count %d", s1, err, save_errno, strerror(save_errno), selectresult, FD_ISSET(s1, &readfds) ? "" : "*NO* ", so_error, so_nread, fionread, count); if (numLogMessages > 5) NotifyOfElusiveBug("Flaw in Kernel (select/recvfrom mismatch)", "Congratulations, you've reproduced an elusive bug.\r" "Please send email to radar-3387020@group.apple.com.)\r" "If possible, please leave your machine undisturbed so that someone can come to investigate the problem."); sleep(1); // After logging this error, rate limit so we don't flood syslog } } mDNSlocal void doTcpSocketCallback(TCPSocket *sock) { mDNSBool c = !sock->connected; sock->connected = mDNStrue; sock->callback(sock, sock->context, c, sock->err); // Note: the callback may call CloseConnection here, which frees the context structure! } #ifndef NO_SECURITYFRAMEWORK mDNSlocal OSStatus tlsWriteSock(SSLConnectionRef connection, const void *data, size_t *dataLength) { int ret = send(((TCPSocket *)connection)->fd, data, *dataLength, 0); if (ret >= 0 && (size_t)ret < *dataLength) { *dataLength = ret; return(errSSLWouldBlock); } if (ret >= 0) { *dataLength = ret; return(noErr); } *dataLength = 0; if (errno == EAGAIN ) return(errSSLWouldBlock); if (errno == ENOENT ) return(errSSLClosedGraceful); if (errno == EPIPE || errno == ECONNRESET) return(errSSLClosedAbort); LogMsg("ERROR: tlsWriteSock: %d error %d (%s)\n", ((TCPSocket *)connection)->fd, errno, strerror(errno)); return(errSSLClosedAbort); } mDNSlocal OSStatus tlsReadSock(SSLConnectionRef connection, void *data, size_t *dataLength) { int ret = recv(((TCPSocket *)connection)->fd, data, *dataLength, 0); if (ret > 0 && (size_t)ret < *dataLength) { *dataLength = ret; return(errSSLWouldBlock); } if (ret > 0) { *dataLength = ret; return(noErr); } *dataLength = 0; if (ret == 0 || errno == ENOENT ) return(errSSLClosedGraceful); if ( errno == EAGAIN ) return(errSSLWouldBlock); if ( errno == ECONNRESET) return(errSSLClosedAbort); LogMsg("ERROR: tlsSockRead: error %d (%s)\n", errno, strerror(errno)); return(errSSLClosedAbort); } mDNSlocal OSStatus tlsSetupSock(TCPSocket *sock, SSLProtocolSide pside, SSLConnectionType ctype) { char domname_cstr[MAX_ESCAPED_DOMAIN_NAME]; sock->tlsContext = SSLCreateContext(kCFAllocatorDefault, pside, ctype); if (!sock->tlsContext) { LogMsg("ERROR: tlsSetupSock: SSLCreateContext failed"); return(mStatus_UnknownErr); } mStatus err = SSLSetIOFuncs(sock->tlsContext, tlsReadSock, tlsWriteSock); if (err) { LogMsg("ERROR: tlsSetupSock: SSLSetIOFuncs failed with error code: %d", err); goto fail; } err = SSLSetConnection(sock->tlsContext, (SSLConnectionRef) sock); if (err) { LogMsg("ERROR: tlsSetupSock: SSLSetConnection failed with error code: %d", err); goto fail; } // Set the default ciphersuite configuration err = SSLSetSessionConfig(sock->tlsContext, CFSTR("default")); if (err) { LogMsg("ERROR: tlsSetupSock: SSLSetSessionConfig failed with error code: %d", err); goto fail; } // We already checked for NULL in hostname and this should never happen. Hence, returning -1 // (error not in OSStatus space) is okay. if (!sock->hostname.c[0]) { LogMsg("ERROR: tlsSetupSock: hostname NULL"); err = -1; goto fail; } ConvertDomainNameToCString(&sock->hostname, domname_cstr); err = SSLSetPeerDomainName(sock->tlsContext, domname_cstr, strlen(domname_cstr)); if (err) { LogMsg("ERROR: tlsSetupSock: SSLSetPeerDomainname: %s failed with error code: %d", domname_cstr, err); goto fail; } return(err); fail: if (sock->tlsContext) CFRelease(sock->tlsContext); return(err); } #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM mDNSlocal void doSSLHandshake(TCPSocket *sock) { mStatus err = SSLHandshake(sock->tlsContext); //Can't have multiple threads in mDNS core. When MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM is //defined, KQueueLock is a noop. Hence we need to serialize here // //NOTE: We just can't serialize doTcpSocketCallback alone on the main queue. //We need the rest of the logic also. Otherwise, we can enable the READ //events below, dispatch a doTcpSocketCallback on the main queue. Assume it is //ConnFailed which means we are going to free the tcpInfo. While it //is waiting to be dispatched, another read event can come into tcpKQSocketCallback //and potentially call doTCPCallback with error which can close the fd and free the //tcpInfo. Later when the thread gets dispatched it will crash because the tcpInfo //is already freed. dispatch_async(dispatch_get_main_queue(), ^{ LogInfo("doSSLHandshake %p: got lock", sock); // Log *after* we get the lock if (sock->handshake == handshake_to_be_closed) { LogInfo("SSLHandshake completed after close"); mDNSPlatformTCPCloseConnection(sock); } else { if (sock->fd != -1) KQueueSet(sock->fd, EV_ADD, EVFILT_READ, sock->kqEntry); else LogMsg("doSSLHandshake: sock->fd is -1"); if (err == errSSLWouldBlock) sock->handshake = handshake_required; else { if (err) { LogMsg("SSLHandshake failed: %d%s", err, err == errSSLPeerInternalError ? " (server busy)" : ""); CFRelease(sock->tlsContext); sock->tlsContext = NULL; } sock->err = err ? mStatus_ConnFailed : 0; sock->handshake = handshake_completed; LogInfo("doSSLHandshake: %p calling doTcpSocketCallback fd %d", sock, sock->fd); doTcpSocketCallback(sock); } } LogInfo("SSLHandshake %p: dropping lock for fd %d", sock, sock->fd); return; }); } #else // MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM mDNSlocal void *doSSLHandshake(TCPSocket *sock) { // Warning: Touching sock without the kqueue lock! // We're protected because sock->handshake == handshake_in_progress mStatus err = SSLHandshake(sock->tlsContext); KQueueLock(); debugf("doSSLHandshake %p: got lock", sock); // Log *after* we get the lock if (sock->handshake == handshake_to_be_closed) { LogInfo("SSLHandshake completed after close"); mDNSPlatformTCPCloseConnection(sock); } else { if (sock->fd != -1) KQueueSet(sock->fd, EV_ADD, EVFILT_READ, sock->kqEntry); else LogMsg("doSSLHandshake: sock->fd is -1"); if (err == errSSLWouldBlock) sock->handshake = handshake_required; else { if (err) { LogMsg("SSLHandshake failed: %d%s", err, err == errSSLPeerInternalError ? " (server busy)" : ""); CFRelease(sock->tlsContext); sock->tlsContext = NULL; } sock->err = err ? mStatus_ConnFailed : 0; sock->handshake = handshake_completed; debugf("doSSLHandshake: %p calling doTcpSocketCallback fd %d", sock, sock->fd); doTcpSocketCallback(sock); } } debugf("SSLHandshake %p: dropping lock for fd %d", sock, sock->fd); KQueueUnlock("doSSLHandshake"); return NULL; } #endif // MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM mDNSlocal void spawnSSLHandshake(TCPSocket* sock) { debugf("spawnSSLHandshake %p: entry", sock); if (sock->handshake != handshake_required) LogMsg("spawnSSLHandshake: handshake status not required: %d", sock->handshake); sock->handshake = handshake_in_progress; KQueueSet(sock->fd, EV_DELETE, EVFILT_READ, sock->kqEntry); // Dispatch it on a separate queue to help avoid blocking other threads/queues, and // to limit the number of threads used for SSLHandshake dispatch_async(SSLqueue, ^{doSSLHandshake(sock);}); debugf("spawnSSLHandshake %p: done for %d", sock, sock->fd); } #endif /* NO_SECURITYFRAMEWORK */ mDNSlocal void tcpKQSocketCallback(__unused int fd, short filter, void *context, __unused mDNSBool encounteredEOF) { TCPSocket *sock = context; sock->err = mStatus_NoError; //if (filter == EVFILT_READ ) LogMsg("myKQSocketCallBack: tcpKQSocketCallback %d is EVFILT_READ", filter); //if (filter == EVFILT_WRITE) LogMsg("myKQSocketCallBack: tcpKQSocketCallback %d is EVFILT_WRITE", filter); // EV_ONESHOT doesn't seem to work, so we add the filter with EV_ADD, and explicitly delete it here with EV_DELETE if (filter == EVFILT_WRITE) KQueueSet(sock->fd, EV_DELETE, EVFILT_WRITE, sock->kqEntry); if (sock->flags & kTCPSocketFlags_UseTLS) { #ifndef NO_SECURITYFRAMEWORK if (!sock->setup) { sock->setup = mDNStrue; sock->err = tlsSetupSock(sock, kSSLClientSide, kSSLStreamType); if (sock->err) { LogMsg("ERROR: tcpKQSocketCallback: tlsSetupSock failed with error code: %d", sock->err); return; } } if (sock->handshake == handshake_required) { spawnSSLHandshake(sock); return; } else if (sock->handshake == handshake_in_progress || sock->handshake == handshake_to_be_closed) { return; } else if (sock->handshake != handshake_completed) { if (!sock->err) sock->err = mStatus_UnknownErr; LogMsg("tcpKQSocketCallback called with unexpected SSLHandshake status: %d", sock->handshake); } #else /* NO_SECURITYFRAMEWORK */ sock->err = mStatus_UnsupportedErr; #endif /* NO_SECURITYFRAMEWORK */ } doTcpSocketCallback(sock); } #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM mDNSexport int KQueueSet(int fd, u_short flags, short filter, KQueueEntry *const entryRef) { dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_source_t source; if (flags == EV_DELETE) { if (filter == EVFILT_READ) { dispatch_source_cancel(entryRef->readSource); dispatch_release(entryRef->readSource); entryRef->readSource = mDNSNULL; debugf("KQueueSet: source cancel for read %p, %p", entryRef->readSource, entryRef->writeSource); } else if (filter == EVFILT_WRITE) { dispatch_source_cancel(entryRef->writeSource); dispatch_release(entryRef->writeSource); entryRef->writeSource = mDNSNULL; debugf("KQueueSet: source cancel for write %p, %p", entryRef->readSource, entryRef->writeSource); } else LogMsg("KQueueSet: ERROR: Wrong filter value %d for EV_DELETE", filter); return 0; } if (flags != EV_ADD) LogMsg("KQueueSet: Invalid flags %d", flags); if (filter == EVFILT_READ) { source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue); } else if (filter == EVFILT_WRITE) { source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, queue); } else { LogMsg("KQueueSet: ERROR: Wrong filter value %d for EV_ADD", filter); return -1; } if (!source) return -1; dispatch_source_set_event_handler(source, ^{ mDNSs32 stime = mDNSPlatformRawTime(); entryRef->KQcallback(fd, filter, entryRef->KQcontext); mDNSs32 etime = mDNSPlatformRawTime(); if (etime - stime >= WatchDogReportingThreshold) LogInfo("KQEntryCallback Block: WARNING: took %dms to complete", etime - stime); // Trigger the event delivery to the application. Even though we trigger the // event completion after handling every event source, these all will hopefully // get merged TriggerEventCompletion(); }); dispatch_source_set_cancel_handler(source, ^{ if (entryRef->fdClosed) { //LogMsg("CancelHandler: closing fd %d", fd); close(fd); } }); dispatch_resume(source); if (filter == EVFILT_READ) entryRef->readSource = source; else entryRef->writeSource = source; return 0; } mDNSexport void KQueueLock() { } mDNSexport void KQueueUnlock(const char const *task) { (void)task; //unused } #else mDNSexport int KQueueSet(int fd, u_short flags, short filter, const KQueueEntry *const entryRef) { struct kevent new_event; EV_SET(&new_event, fd, filter, flags, 0, 0, (void*)entryRef); return (kevent(KQueueFD, &new_event, 1, NULL, 0, NULL) < 0) ? errno : 0; } mDNSexport void KQueueLock() { mDNS *const m = &mDNSStorage; pthread_mutex_lock(&m->p->BigMutex); m->p->BigMutexStartTime = mDNSPlatformRawTime(); } mDNSexport void KQueueUnlock(const char* task) { mDNS *const m = &mDNSStorage; mDNSs32 end = mDNSPlatformRawTime(); (void)task; if (end - m->p->BigMutexStartTime >= WatchDogReportingThreshold) LogInfo("WARNING: %s took %dms to complete", task, end - m->p->BigMutexStartTime); pthread_mutex_unlock(&m->p->BigMutex); char wake = 1; if (send(m->p->WakeKQueueLoopFD, &wake, sizeof(wake), 0) == -1) LogMsg("ERROR: KQueueWake: send failed with error code: %d (%s)", errno, strerror(errno)); } #endif mDNSexport void mDNSPlatformCloseFD(KQueueEntry *kq, int fd) { #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM (void) fd; //unused if (kq->readSource) { dispatch_source_cancel(kq->readSource); kq->readSource = mDNSNULL; } if (kq->writeSource) { dispatch_source_cancel(kq->writeSource); kq->writeSource = mDNSNULL; } // Close happens in the cancellation handler debugf("mDNSPlatformCloseFD: resetting sources for %d", fd); kq->fdClosed = mDNStrue; #else (void)kq; //unused close(fd); #endif } mDNSlocal mStatus SetupTCPSocket(TCPSocket *sock, u_short sa_family, mDNSIPPort *port, mDNSBool useBackgroundTrafficClass) { KQSocketSet *cp = &sock->ss; int *s = (sa_family == AF_INET) ? &cp->sktv4 : &cp->sktv6; KQueueEntry *k = (sa_family == AF_INET) ? &cp->kqsv4 : &cp->kqsv6; const int on = 1; // "on" for setsockopt mStatus err; int skt = socket(sa_family, SOCK_STREAM, IPPROTO_TCP); if (skt < 3) { if (errno != EAFNOSUPPORT) LogMsg("SetupTCPSocket: socket error %d errno %d (%s)", skt, errno, strerror(errno));return(skt); } // for TCP sockets, the traffic class is set once and not changed setTrafficClass(skt, useBackgroundTrafficClass); if (sa_family == AF_INET) { // Bind it struct sockaddr_in addr; mDNSPlatformMemZero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = port->NotAnInteger; err = bind(skt, (struct sockaddr*) &addr, sizeof(addr)); if (err < 0) { LogMsg("ERROR: bind %s", strerror(errno)); close(skt); return err; } // Receive interface identifiers err = setsockopt(skt, IPPROTO_IP, IP_RECVIF, &on, sizeof(on)); if (err < 0) { LogMsg("setsockopt IP_RECVIF - %s", strerror(errno)); close(skt); return err; } mDNSPlatformMemZero(&addr, sizeof(addr)); socklen_t len = sizeof(addr); err = getsockname(skt, (struct sockaddr*) &addr, &len); if (err < 0) { LogMsg("getsockname - %s", strerror(errno)); close(skt); return err; } port->NotAnInteger = addr.sin_port; } else { // Bind it struct sockaddr_in6 addr6; mDNSPlatformMemZero(&addr6, sizeof(addr6)); addr6.sin6_family = AF_INET6; addr6.sin6_port = port->NotAnInteger; err = bind(skt, (struct sockaddr*) &addr6, sizeof(addr6)); if (err < 0) { LogMsg("ERROR: bind6 %s", strerror(errno)); close(skt); return err; } // We want to receive destination addresses and receive interface identifiers err = setsockopt(skt, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); if (err < 0) { LogMsg("ERROR: setsockopt IPV6_RECVPKTINFO %s", strerror(errno)); close(skt); return err; } mDNSPlatformMemZero(&addr6, sizeof(addr6)); socklen_t len = sizeof(addr6); err = getsockname(skt, (struct sockaddr *) &addr6, &len); if (err < 0) { LogMsg("getsockname6 - %s", strerror(errno)); close(skt); return err; } port->NotAnInteger = addr6.sin6_port; } *s = skt; k->KQcallback = tcpKQSocketCallback; k->KQcontext = sock; k->KQtask = "mDNSPlatformTCPSocket"; #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM k->readSource = mDNSNULL; k->writeSource = mDNSNULL; k->fdClosed = mDNSfalse; #endif return mStatus_NoError; } mDNSexport TCPSocket *mDNSPlatformTCPSocket(TCPSocketFlags flags, mDNSIPPort *port, mDNSBool useBackgroundTrafficClass) { mStatus err; TCPSocket *sock = mallocL("TCPSocket/mDNSPlatformTCPSocket", sizeof(TCPSocket)); if (!sock) { LogMsg("mDNSPlatformTCPSocket: memory allocation failure"); return(mDNSNULL); } mDNSPlatformMemZero(sock, sizeof(TCPSocket)); sock->ss.m = &mDNSStorage; sock->ss.sktv4 = -1; sock->ss.sktv6 = -1; err = SetupTCPSocket(sock, AF_INET, port, useBackgroundTrafficClass); if (!err) { err = SetupTCPSocket(sock, AF_INET6, port, useBackgroundTrafficClass); if (err) { mDNSPlatformCloseFD(&sock->ss.kqsv4, sock->ss.sktv4); sock->ss.sktv4 = -1; } } if (err) { LogMsg("mDNSPlatformTCPSocket: socket error %d errno %d (%s)", sock->fd, errno, strerror(errno)); freeL("TCPSocket/mDNSPlatformTCPSocket", sock); return(mDNSNULL); } // sock->fd is used as the default fd if the caller does not call mDNSPlatformTCPConnect sock->fd = sock->ss.sktv4; sock->callback = mDNSNULL; sock->flags = flags; sock->context = mDNSNULL; sock->setup = mDNSfalse; sock->connected = mDNSfalse; sock->handshake = handshake_required; sock->m = &mDNSStorage; sock->err = mStatus_NoError; return sock; } mDNSexport mStatus mDNSPlatformTCPConnect(TCPSocket *sock, const mDNSAddr *dst, mDNSOpaque16 dstport, domainname *hostname, mDNSInterfaceID InterfaceID, TCPConnectionCallback callback, void *context) { KQSocketSet *cp = &sock->ss; int *s = (dst->type == mDNSAddrType_IPv4) ? &cp->sktv4 : &cp->sktv6; KQueueEntry *k = (dst->type == mDNSAddrType_IPv4) ? &cp->kqsv4 : &cp->kqsv6; mStatus err = mStatus_NoError; struct sockaddr_storage ss; sock->callback = callback; sock->context = context; sock->setup = mDNSfalse; sock->connected = mDNSfalse; sock->handshake = handshake_required; sock->err = mStatus_NoError; if (hostname) { debugf("mDNSPlatformTCPConnect: hostname %##s", hostname->c); AssignDomainName(&sock->hostname, hostname); } if (dst->type == mDNSAddrType_IPv4) { struct sockaddr_in *saddr = (struct sockaddr_in *)&ss; mDNSPlatformMemZero(saddr, sizeof(*saddr)); saddr->sin_family = AF_INET; saddr->sin_port = dstport.NotAnInteger; saddr->sin_len = sizeof(*saddr); saddr->sin_addr.s_addr = dst->ip.v4.NotAnInteger; } else { struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)&ss; mDNSPlatformMemZero(saddr6, sizeof(*saddr6)); saddr6->sin6_family = AF_INET6; saddr6->sin6_port = dstport.NotAnInteger; saddr6->sin6_len = sizeof(*saddr6); saddr6->sin6_addr = *(struct in6_addr *)&dst->ip.v6; } // Watch for connect complete (write is ready) // EV_ONESHOT doesn't seem to work, so we add the filter with EV_ADD, and explicitly delete it in tcpKQSocketCallback using EV_DELETE if (KQueueSet(*s, EV_ADD /* | EV_ONESHOT */, EVFILT_WRITE, k)) { LogMsg("ERROR: mDNSPlatformTCPConnect - KQueueSet failed"); return errno; } // Watch for incoming data if (KQueueSet(*s, EV_ADD, EVFILT_READ, k)) { LogMsg("ERROR: mDNSPlatformTCPConnect - KQueueSet failed"); return errno; } if (fcntl(*s, F_SETFL, fcntl(*s, F_GETFL, 0) | O_NONBLOCK) < 0) // set non-blocking { LogMsg("ERROR: setsockopt O_NONBLOCK - %s", strerror(errno)); return mStatus_UnknownErr; } // We bind to the interface and all subsequent packets including the SYN will be sent out // on this interface // // Note: If we are in Active Directory domain, we may try TCP (if the response can't fit in // UDP). mDNSInterface_Unicast indicates this case and not a valid interface. if (InterfaceID && InterfaceID != mDNSInterface_Unicast) { NetworkInterfaceInfoOSX *info = IfindexToInterfaceInfoOSX(InterfaceID); if (dst->type == mDNSAddrType_IPv4) { #ifdef IP_BOUND_IF if (info) setsockopt(*s, IPPROTO_IP, IP_BOUND_IF, &info->scope_id, sizeof(info->scope_id)); else { LogMsg("mDNSPlatformTCPConnect: Invalid interface index %p", InterfaceID); return mStatus_BadParamErr; } #else (void)InterfaceID; // Unused (void)info; // Unused #endif } else { #ifdef IPV6_BOUND_IF if (info) setsockopt(*s, IPPROTO_IPV6, IPV6_BOUND_IF, &info->scope_id, sizeof(info->scope_id)); else { LogMsg("mDNSPlatformTCPConnect: Invalid interface index %p", InterfaceID); return mStatus_BadParamErr; } #else (void)InterfaceID; // Unused (void)info; // Unused #endif } } // mDNSPlatformReadTCP/WriteTCP (unlike the UDP counterpart) does not provide the destination address // from which we can infer the destination address family. Hence we need to remember that here. // Instead of remembering the address family, we remember the right fd. sock->fd = *s; sock->kqEntry = k; // initiate connection wth peer if (connect(*s, (struct sockaddr *)&ss, ss.ss_len) < 0) { if (errno == EINPROGRESS) return mStatus_ConnPending; if (errno == EHOSTUNREACH || errno == EADDRNOTAVAIL || errno == ENETDOWN) LogInfo("ERROR: mDNSPlatformTCPConnect - connect failed: socket %d: Error %d (%s)", sock->fd, errno, strerror(errno)); else LogMsg("ERROR: mDNSPlatformTCPConnect - connect failed: socket %d: Error %d (%s) length %d", sock->fd, errno, strerror(errno), ss.ss_len); return mStatus_ConnFailed; } LogMsg("NOTE: mDNSPlatformTCPConnect completed synchronously"); // kQueue should notify us, but this LogMsg is to help track down if it doesn't return err; } // Why doesn't mDNSPlatformTCPAccept actually call accept() ? mDNSexport TCPSocket *mDNSPlatformTCPAccept(TCPSocketFlags flags, int fd) { mStatus err = mStatus_NoError; TCPSocket *sock = mallocL("TCPSocket/mDNSPlatformTCPAccept", sizeof(TCPSocket)); if (!sock) return(mDNSNULL); mDNSPlatformMemZero(sock, sizeof(*sock)); sock->fd = fd; sock->flags = flags; if (flags & kTCPSocketFlags_UseTLS) { #ifndef NO_SECURITYFRAMEWORK if (!ServerCerts) { LogMsg("ERROR: mDNSPlatformTCPAccept: unable to find TLS certificates"); err = mStatus_UnknownErr; goto exit; } err = tlsSetupSock(sock, kSSLServerSide, kSSLStreamType); if (err) { LogMsg("ERROR: mDNSPlatformTCPAccept: tlsSetupSock failed with error code: %d", err); goto exit; } err = SSLSetCertificate(sock->tlsContext, ServerCerts); if (err) { LogMsg("ERROR: mDNSPlatformTCPAccept: SSLSetCertificate failed with error code: %d", err); goto exit; } #else err = mStatus_UnsupportedErr; #endif /* NO_SECURITYFRAMEWORK */ } #ifndef NO_SECURITYFRAMEWORK exit: #endif if (err) { freeL("TCPSocket/mDNSPlatformTCPAccept", sock); return(mDNSNULL); } return(sock); } mDNSexport mDNSu16 mDNSPlatformGetUDPPort(UDPSocket *sock) { mDNSu16 port; port = -1; if (sock) { port = sock->ss.port.NotAnInteger; } return port; } mDNSlocal void CloseSocketSet(KQSocketSet *ss) { if (ss->sktv4 != -1) { mDNSPlatformCloseFD(&ss->kqsv4, ss->sktv4); ss->sktv4 = -1; } if (ss->sktv6 != -1) { mDNSPlatformCloseFD(&ss->kqsv6, ss->sktv6); ss->sktv6 = -1; } if (ss->closeFlag) *ss->closeFlag = 1; } mDNSexport void mDNSPlatformTCPCloseConnection(TCPSocket *sock) { if (sock) { #ifndef NO_SECURITYFRAMEWORK if (sock->tlsContext) { if (sock->handshake == handshake_in_progress) // SSLHandshake thread using this sock (esp. tlsContext) { LogInfo("mDNSPlatformTCPCloseConnection: called while handshake in progress"); // When we come back from SSLHandshake, we will notice that a close was here and // call this function again which will do the cleanup then. sock->handshake = handshake_to_be_closed; return; } SSLClose(sock->tlsContext); CFRelease(sock->tlsContext); sock->tlsContext = NULL; } #endif /* NO_SECURITYFRAMEWORK */ if (sock->ss.sktv4 != -1) shutdown(sock->ss.sktv4, 2); if (sock->ss.sktv6 != -1) shutdown(sock->ss.sktv6, 2); CloseSocketSet(&sock->ss); sock->fd = -1; freeL("TCPSocket/mDNSPlatformTCPCloseConnection", sock); } } mDNSexport long mDNSPlatformReadTCP(TCPSocket *sock, void *buf, unsigned long buflen, mDNSBool *closed) { ssize_t nread = 0; *closed = mDNSfalse; if (sock->flags & kTCPSocketFlags_UseTLS) { #ifndef NO_SECURITYFRAMEWORK if (sock->handshake == handshake_required) { LogMsg("mDNSPlatformReadTCP called while handshake required"); return 0; } else if (sock->handshake == handshake_in_progress) return 0; else if (sock->handshake != handshake_completed) LogMsg("mDNSPlatformReadTCP called with unexpected SSLHandshake status: %d", sock->handshake); //LogMsg("Starting SSLRead %d %X", sock->fd, fcntl(sock->fd, F_GETFL, 0)); mStatus err = SSLRead(sock->tlsContext, buf, buflen, (size_t *)&nread); //LogMsg("SSLRead returned %d (%d) nread %d buflen %d", err, errSSLWouldBlock, nread, buflen); if (err == errSSLClosedGraceful) { nread = 0; *closed = mDNStrue; } else if (err && err != errSSLWouldBlock) { LogMsg("ERROR: mDNSPlatformReadTCP - SSLRead: %d", err); nread = -1; *closed = mDNStrue; } #else nread = -1; *closed = mDNStrue; #endif /* NO_SECURITYFRAMEWORK */ } else { static int CLOSEDcount = 0; static int EAGAINcount = 0; nread = recv(sock->fd, buf, buflen, 0); if (nread > 0) { CLOSEDcount = 0; EAGAINcount = 0; } // On success, clear our error counters else if (nread == 0) { *closed = mDNStrue; if ((++CLOSEDcount % 1000) == 0) { LogMsg("ERROR: mDNSPlatformReadTCP - recv %d got CLOSED %d times", sock->fd, CLOSEDcount); assert(CLOSEDcount < 1000); // Recovery Mechanism to bail mDNSResponder out of trouble: Instead of logging the same error msg multiple times, // crash mDNSResponder using assert() and restart fresh. See advantages below: // 1.Better User Experience // 2.CrashLogs frequency can be monitored // 3.StackTrace can be used for more info } } // else nread is negative -- see what kind of error we got else if (errno == ECONNRESET) { nread = 0; *closed = mDNStrue; } else if (errno != EAGAIN) { LogMsg("ERROR: mDNSPlatformReadTCP - recv: %d (%s)", errno, strerror(errno)); nread = -1; } else // errno is EAGAIN (EWOULDBLOCK) -- no data available { nread = 0; if ((++EAGAINcount % 1000) == 0) { LogMsg("ERROR: mDNSPlatformReadTCP - recv %d got EAGAIN %d times", sock->fd, EAGAINcount); sleep(1); } } } return nread; } mDNSexport long mDNSPlatformWriteTCP(TCPSocket *sock, const char *msg, unsigned long len) { int nsent; if (sock->flags & kTCPSocketFlags_UseTLS) { #ifndef NO_SECURITYFRAMEWORK size_t processed; if (sock->handshake == handshake_required) { LogMsg("mDNSPlatformWriteTCP called while handshake required"); return 0; } if (sock->handshake == handshake_in_progress) return 0; else if (sock->handshake != handshake_completed) LogMsg("mDNSPlatformWriteTCP called with unexpected SSLHandshake status: %d", sock->handshake); mStatus err = SSLWrite(sock->tlsContext, msg, len, &processed); if (!err) nsent = (int) processed; else if (err == errSSLWouldBlock) nsent = 0; else { LogMsg("ERROR: mDNSPlatformWriteTCP - SSLWrite returned %d", err); nsent = -1; } #else nsent = -1; #endif /* NO_SECURITYFRAMEWORK */ } else { nsent = send(sock->fd, msg, len, 0); if (nsent < 0) { if (errno == EAGAIN) nsent = 0; else { LogMsg("ERROR: mDNSPlatformWriteTCP - send %s", strerror(errno)); nsent = -1; } } } return nsent; } mDNSexport int mDNSPlatformTCPGetFD(TCPSocket *sock) { return sock->fd; } // If mDNSIPPort port is non-zero, then it's a multicast socket on the specified interface // If mDNSIPPort port is zero, then it's a randomly assigned port number, used for sending unicast queries mDNSlocal mStatus SetupSocket(KQSocketSet *cp, const mDNSIPPort port, u_short sa_family, mDNSIPPort *const outport) { int *s = (sa_family == AF_INET) ? &cp->sktv4 : &cp->sktv6; KQueueEntry *k = (sa_family == AF_INET) ? &cp->kqsv4 : &cp->kqsv6; const int on = 1; const int twofivefive = 255; mStatus err = mStatus_NoError; char *errstr = mDNSNULL; const int mtu = 0; int saved_errno; cp->closeFlag = mDNSNULL; int skt = socket(sa_family, SOCK_DGRAM, IPPROTO_UDP); if (skt < 3) { if (errno != EAFNOSUPPORT) LogMsg("SetupSocket: socket error %d errno %d (%s)", skt, errno, strerror(errno));return(skt); } // set default traffic class setTrafficClass(skt, mDNSfalse); #ifdef SO_RECV_ANYIF // Enable inbound packets on IFEF_AWDL interface. // Only done for multicast sockets, since we don't expect unicast socket operations // on the IFEF_AWDL interface. Operation is a no-op for other interface types. if (mDNSSameIPPort(port, MulticastDNSPort)) { err = setsockopt(skt, SOL_SOCKET, SO_RECV_ANYIF, &on, sizeof(on)); if (err < 0) { errstr = "setsockopt - SO_RECV_ANYIF"; goto fail; } } #endif // SO_RECV_ANYIF // ... with a shared UDP port, if it's for multicast receiving if (mDNSSameIPPort(port, MulticastDNSPort) || mDNSSameIPPort(port, NATPMPAnnouncementPort)) { err = setsockopt(skt, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)); if (err < 0) { errstr = "setsockopt - SO_REUSEPORT"; goto fail; } } // Don't want to wake from sleep for inbound packets on the mDNS sockets if (mDNSSameIPPort(port, MulticastDNSPort)) { int nowake = 1; if (setsockopt(skt, SOL_SOCKET, SO_NOWAKEFROMSLEEP, &nowake, sizeof(nowake)) == -1) LogInfo("SetupSocket: SO_NOWAKEFROMSLEEP failed %s", strerror(errno)); } if (sa_family == AF_INET) { // We want to receive destination addresses err = setsockopt(skt, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)); if (err < 0) { errstr = "setsockopt - IP_RECVDSTADDR"; goto fail; } // We want to receive interface identifiers err = setsockopt(skt, IPPROTO_IP, IP_RECVIF, &on, sizeof(on)); if (err < 0) { errstr = "setsockopt - IP_RECVIF"; goto fail; } // We want to receive packet TTL value so we can check it err = setsockopt(skt, IPPROTO_IP, IP_RECVTTL, &on, sizeof(on)); if (err < 0) { errstr = "setsockopt - IP_RECVTTL"; goto fail; } // Send unicast packets with TTL 255 err = setsockopt(skt, IPPROTO_IP, IP_TTL, &twofivefive, sizeof(twofivefive)); if (err < 0) { errstr = "setsockopt - IP_TTL"; goto fail; } // And multicast packets with TTL 255 too err = setsockopt(skt, IPPROTO_IP, IP_MULTICAST_TTL, &twofivefive, sizeof(twofivefive)); if (err < 0) { errstr = "setsockopt - IP_MULTICAST_TTL"; goto fail; } // And start listening for packets struct sockaddr_in listening_sockaddr; listening_sockaddr.sin_family = AF_INET; listening_sockaddr.sin_port = port.NotAnInteger; // Pass in opaque ID without any byte swapping listening_sockaddr.sin_addr.s_addr = mDNSSameIPPort(port, NATPMPAnnouncementPort) ? AllHosts_v4.NotAnInteger : 0; err = bind(skt, (struct sockaddr *) &listening_sockaddr, sizeof(listening_sockaddr)); if (err) { errstr = "bind"; goto fail; } if (outport) outport->NotAnInteger = listening_sockaddr.sin_port; } else if (sa_family == AF_INET6) { // NAT-PMP Announcements make no sense on IPv6, and we don't support IPv6 for PCP, so bail early w/o error if (mDNSSameIPPort(port, NATPMPAnnouncementPort)) { if (outport) *outport = zeroIPPort; close(skt); return mStatus_NoError; } // We want to receive destination addresses and receive interface identifiers err = setsockopt(skt, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); if (err < 0) { errstr = "setsockopt - IPV6_RECVPKTINFO"; goto fail; } // We want to receive packet hop count value so we can check it err = setsockopt(skt, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)); if (err < 0) { errstr = "setsockopt - IPV6_RECVHOPLIMIT"; goto fail; } // We want to receive only IPv6 packets. Without this option we get IPv4 packets too, // with mapped addresses of the form 0:0:0:0:0:FFFF:xxxx:xxxx, where xxxx:xxxx is the IPv4 address err = setsockopt(skt, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); if (err < 0) { errstr = "setsockopt - IPV6_V6ONLY"; goto fail; } // Send unicast packets with TTL 255 err = setsockopt(skt, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &twofivefive, sizeof(twofivefive)); if (err < 0) { errstr = "setsockopt - IPV6_UNICAST_HOPS"; goto fail; } // And multicast packets with TTL 255 too err = setsockopt(skt, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &twofivefive, sizeof(twofivefive)); if (err < 0) { errstr = "setsockopt - IPV6_MULTICAST_HOPS"; goto fail; } // Want to receive our own packets err = setsockopt(skt, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &on, sizeof(on)); if (err < 0) { errstr = "setsockopt - IPV6_MULTICAST_LOOP"; goto fail; } // Disable default option to send mDNSv6 packets at min IPv6 MTU: RFC 3542, Sec 11 err = setsockopt(skt, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &mtu, sizeof(mtu)); if (err < 0) // Since it is an optimization if we fail just log the err, no need to close the skt LogMsg("SetupSocket: setsockopt - IPV6_USE_MIN_MTU: IP6PO_MINMTU_DISABLE socket %d err %d errno %d (%s)", skt, err, errno, strerror(errno)); // And start listening for packets struct sockaddr_in6 listening_sockaddr6; mDNSPlatformMemZero(&listening_sockaddr6, sizeof(listening_sockaddr6)); listening_sockaddr6.sin6_len = sizeof(listening_sockaddr6); listening_sockaddr6.sin6_family = AF_INET6; listening_sockaddr6.sin6_port = port.NotAnInteger; // Pass in opaque ID without any byte swapping listening_sockaddr6.sin6_flowinfo = 0; listening_sockaddr6.sin6_addr = in6addr_any; // Want to receive multicasts AND unicasts on this socket listening_sockaddr6.sin6_scope_id = 0; err = bind(skt, (struct sockaddr *) &listening_sockaddr6, sizeof(listening_sockaddr6)); if (err) { errstr = "bind"; goto fail; } if (outport) outport->NotAnInteger = listening_sockaddr6.sin6_port; } fcntl(skt, F_SETFL, fcntl(skt, F_GETFL, 0) | O_NONBLOCK); // set non-blocking fcntl(skt, F_SETFD, 1); // set close-on-exec *s = skt; k->KQcallback = myKQSocketCallBack; k->KQcontext = cp; k->KQtask = "UDP packet reception"; #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM k->readSource = mDNSNULL; k->writeSource = mDNSNULL; k->fdClosed = mDNSfalse; #endif KQueueSet(*s, EV_ADD, EVFILT_READ, k); return(mStatus_NoError); fail: saved_errno = errno; // For "bind" failures, only write log messages for our shared mDNS port, or for binding to zero if (strcmp(errstr, "bind") || mDNSSameIPPort(port, MulticastDNSPort) || mDNSIPPortIsZero(port)) LogMsg("%s skt %d port %d error %d errno %d (%s)", errstr, skt, mDNSVal16(port), err, saved_errno, strerror(saved_errno)); // If we got a "bind" failure of EADDRINUSE, inform the caller as it might need to try another random port if (!strcmp(errstr, "bind") && saved_errno == EADDRINUSE) { err = EADDRINUSE; if (mDNSSameIPPort(port, MulticastDNSPort)) NotifyOfElusiveBug("Setsockopt SO_REUSEPORT failed", "Congratulations, you've reproduced an elusive bug.\r" "Please contact the current assignee of .\r" "Alternatively, you can send email to radar-3387020@group.apple.com. (Note number is different.)\r" "If possible, please leave your machine undisturbed so that someone can come to investigate the problem."); } mDNSPlatformCloseFD(k, skt); return(err); } mDNSexport UDPSocket *mDNSPlatformUDPSocket(const mDNSIPPort requestedport) { mStatus err; mDNSIPPort port = requestedport; mDNSBool randomizePort = mDNSIPPortIsZero(requestedport); int i = 10000; // Try at most 10000 times to get a unique random port UDPSocket *p = mallocL("UDPSocket", sizeof(UDPSocket)); if (!p) { LogMsg("mDNSPlatformUDPSocket: memory exhausted"); return(mDNSNULL); } mDNSPlatformMemZero(p, sizeof(UDPSocket)); p->ss.port = zeroIPPort; p->ss.m = &mDNSStorage; p->ss.sktv4 = -1; p->ss.sktv6 = -1; p->ss.proxy = mDNSfalse; do { // The kernel doesn't do cryptographically strong random port allocation, so we do it ourselves here if (randomizePort) port = mDNSOpaque16fromIntVal(0xC000 + mDNSRandom(0x3FFF)); err = SetupSocket(&p->ss, port, AF_INET, &p->ss.port); if (!err) { err = SetupSocket(&p->ss, port, AF_INET6, &p->ss.port); if (err) { mDNSPlatformCloseFD(&p->ss.kqsv4, p->ss.sktv4); p->ss.sktv4 = -1; } } i--; } while (err == EADDRINUSE && randomizePort && i); if (err) { // In customer builds we don't want to log failures with port 5351, because this is a known issue // of failing to bind to this port when Internet Sharing has already bound to it // We also don't want to log about port 5350, due to a known bug when some other // process is bound to it. if (mDNSSameIPPort(requestedport, NATPMPPort) || mDNSSameIPPort(requestedport, NATPMPAnnouncementPort)) LogInfo("mDNSPlatformUDPSocket: SetupSocket %d failed error %d errno %d (%s)", mDNSVal16(requestedport), err, errno, strerror(errno)); else LogMsg("mDNSPlatformUDPSocket: SetupSocket %d failed error %d errno %d (%s)", mDNSVal16(requestedport), err, errno, strerror(errno)); freeL("UDPSocket", p); return(mDNSNULL); } return(p); } #ifdef UNIT_TEST UNITTEST_UDPCLOSE #else mDNSexport void mDNSPlatformUDPClose(UDPSocket *sock) { CloseSocketSet(&sock->ss); freeL("UDPSocket", sock); } #endif mDNSexport mDNSBool mDNSPlatformUDPSocketEncounteredEOF(const UDPSocket *sock) { return (sock->ss.sktv4EOF || sock->ss.sktv6EOF); } #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - #pragma mark - BPF Raw packet sending/receiving #endif #if APPLE_OSX_mDNSResponder mDNSexport void mDNSPlatformSendRawPacket(const void *const msg, const mDNSu8 *const end, mDNSInterfaceID InterfaceID) { if (!InterfaceID) { LogMsg("mDNSPlatformSendRawPacket: No InterfaceID specified"); return; } NetworkInterfaceInfoOSX *info; info = IfindexToInterfaceInfoOSX(InterfaceID); if (info == NULL) { LogMsg("mDNSPlatformSendRawPacket: Invalid interface index %p", InterfaceID); return; } if (info->BPF_fd < 0) LogMsg("mDNSPlatformSendRawPacket: %s BPF_fd %d not ready", info->ifinfo.ifname, info->BPF_fd); else { //LogMsg("mDNSPlatformSendRawPacket %d bytes on %s", end - (mDNSu8 *)msg, info->ifinfo.ifname); if (write(info->BPF_fd, msg, end - (mDNSu8 *)msg) < 0) LogMsg("mDNSPlatformSendRawPacket: BPF write(%d) failed %d (%s)", info->BPF_fd, errno, strerror(errno)); } } mDNSexport void mDNSPlatformSetLocalAddressCacheEntry(const mDNSAddr *const tpa, const mDNSEthAddr *const tha, mDNSInterfaceID InterfaceID) { if (!InterfaceID) { LogMsg("mDNSPlatformSetLocalAddressCacheEntry: No InterfaceID specified"); return; } NetworkInterfaceInfoOSX *info; info = IfindexToInterfaceInfoOSX(InterfaceID); if (info == NULL) { LogMsg("mDNSPlatformSetLocalAddressCacheEntry: Invalid interface index %p", InterfaceID); return; } // Manually inject an entry into our local ARP cache. // (We can't do this by sending an ARP broadcast, because the kernel only pays attention to incoming ARP packets, not outgoing.) if (!mDNS_AddressIsLocalSubnet(&mDNSStorage, InterfaceID, tpa)) LogSPS("Don't need address cache entry for %s %#a %.6a", info->ifinfo.ifname, tpa, tha); else { int result = mDNSSetLocalAddressCacheEntry(info->scope_id, tpa->type, tpa->ip.v6.b, tha->b); if (result) LogMsg("Set local address cache entry for %s %#a %.6a failed: %d", info->ifinfo.ifname, tpa, tha, result); else LogSPS("Set local address cache entry for %s %#a %.6a", info->ifinfo.ifname, tpa, tha); } } mDNSlocal void CloseBPF(NetworkInterfaceInfoOSX *const i) { LogSPS("%s closing BPF fd %d", i->ifinfo.ifname, i->BPF_fd); #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM // close will happen in the cancel handler dispatch_source_cancel(i->BPF_source); #else // Note: MUST NOT close() the underlying native BSD sockets. // CFSocketInvalidate() will do that for us, in its own good time, which may not necessarily be immediately, because // it first has to unhook the sockets from its select() call on its other thread, before it can safely close them. CFRunLoopRemoveSource(CFRunLoopGetMain(), i->BPF_rls, kCFRunLoopDefaultMode); CFRelease(i->BPF_rls); CFSocketInvalidate(i->BPF_cfs); CFRelease(i->BPF_cfs); #endif i->BPF_fd = -1; if (i->BPF_mcfd >= 0) { close(i->BPF_mcfd); i->BPF_mcfd = -1; } } mDNSlocal void bpf_callback_common(NetworkInterfaceInfoOSX *info) { KQueueLock(); // Now we've got the lock, make sure the kqueue thread didn't close the fd out from under us (will not be a problem once the OS X // kernel has a mechanism for dispatching all events to a single thread, but for now we have to guard against this race condition). if (info->BPF_fd < 0) goto exit; ssize_t n = read(info->BPF_fd, &info->m->imsg, info->BPF_len); const mDNSu8 *ptr = (const mDNSu8 *)&info->m->imsg; const mDNSu8 *end = (const mDNSu8 *)&info->m->imsg + n; debugf("%3d: bpf_callback got %d bytes on %s", info->BPF_fd, n, info->ifinfo.ifname); if (n<0) { /* * sometimes there can be a race condition btw when the bpf socket * gets data and the callback get scheduled and when we call BIOCSETF (which * clears the socket). this can cause the read to hang for a really long time * and effectively prevent us from responding to requests for long periods of time. * to prevent this make the socket non blocking and just bail if we dont get anything */ if (errno == EAGAIN) { LogMsg("bpf_callback got EAGAIN bailing"); goto exit; } LogMsg("Closing %s BPF fd %d due to error %d (%s)", info->ifinfo.ifname, info->BPF_fd, errno, strerror(errno)); CloseBPF(info); goto exit; } while (ptr < end) { const struct bpf_hdr *const bh = (const struct bpf_hdr *)ptr; debugf("%3d: bpf_callback ptr %p bh_hdrlen %d data %p bh_caplen %4d bh_datalen %4d next %p remaining %4d", info->BPF_fd, ptr, bh->bh_hdrlen, ptr + bh->bh_hdrlen, bh->bh_caplen, bh->bh_datalen, ptr + BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen), end - (ptr + BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen))); // Note that BPF guarantees that the NETWORK LAYER header will be word aligned, not the link-layer header. // Given that An Ethernet header is 14 bytes, this means that if the network layer header (e.g. IP header, // ARP message, etc.) is 4-byte aligned, then necessarily the Ethernet header will be NOT be 4-byte aligned. mDNSCoreReceiveRawPacket(info->m, ptr + bh->bh_hdrlen, ptr + bh->bh_hdrlen + bh->bh_caplen, info->ifinfo.InterfaceID); ptr += BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen); } exit: KQueueUnlock("bpf_callback"); } #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM mDNSlocal void bpf_callback_dispatch(NetworkInterfaceInfoOSX *const info) { bpf_callback_common(info); } #else mDNSlocal void bpf_callback(const CFSocketRef cfs, const CFSocketCallBackType CallBackType, const CFDataRef address, const void *const data, void *const context) { (void)cfs; (void)CallBackType; (void)address; (void)data; bpf_callback_common((NetworkInterfaceInfoOSX *)context); } #endif mDNSexport void mDNSPlatformSendKeepalive(mDNSAddr *sadd, mDNSAddr *dadd, mDNSIPPort *lport, mDNSIPPort *rport, mDNSu32 seq, mDNSu32 ack, mDNSu16 win) { LogMsg("mDNSPlatformSendKeepalive called\n"); mDNSSendKeepalive(sadd->ip.v6.b, dadd->ip.v6.b, lport->NotAnInteger, rport->NotAnInteger, seq, ack, win); } mDNSexport mStatus mDNSPlatformClearSPSData(void) { CFStringRef spsAddressKey = NULL; CFStringRef ownerOPTRecKey = NULL; SCDynamicStoreRef addrStore = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:SPSAddresses"), NULL, NULL); SCDynamicStoreRef optStore = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:SPSOPTRecord"), NULL, NULL); spsAddressKey = SCDynamicStoreKeyCreateNetworkInterfaceEntity (kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCCompAnyRegex, CFSTR("BonjourSleepProxyAddress")); if (spsAddressKey != NULL) { CFArrayRef keyList = SCDynamicStoreCopyKeyList(addrStore, spsAddressKey); if (keyList != NULL) { if (SCDynamicStoreSetMultiple(addrStore, NULL, keyList, NULL) == false) LogSPS("mDNSPlatformClearSPSData: Unable to remove %s : error %s", CFStringGetCStringPtr( spsAddressKey, kCFStringEncodingASCII), SCErrorString(SCError())); } if (keyList) CFRelease(keyList); } ownerOPTRecKey= SCDynamicStoreKeyCreateNetworkInterfaceEntity (kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCCompAnyRegex, CFSTR("BonjourSleepProxyOPTRecord")); if(ownerOPTRecKey != NULL) { CFArrayRef keyList = SCDynamicStoreCopyKeyList(addrStore, ownerOPTRecKey); if (keyList != NULL) { if (SCDynamicStoreSetMultiple(optStore, NULL, keyList, NULL) == false) LogSPS("mDNSPlatformClearSPSData: Unable to remove %s : error %s", CFStringGetCStringPtr(ownerOPTRecKey, kCFStringEncodingASCII), SCErrorString(SCError())); } if (keyList) CFRelease(keyList); } if (addrStore) CFRelease(addrStore); if (optStore) CFRelease(optStore); if (spsAddressKey) CFRelease(spsAddressKey); if (ownerOPTRecKey) CFRelease(ownerOPTRecKey); return KERN_SUCCESS; } mDNSlocal int getMACAddress(int family, v6addr_t raddr, v6addr_t gaddr, int *gfamily, ethaddr_t eth) { struct { struct rt_msghdr m_rtm; char m_space[512]; } m_rtmsg; struct rt_msghdr *rtm = &(m_rtmsg.m_rtm); char *cp = m_rtmsg.m_space; int seq = 6367, sock, rlen, i; struct sockaddr_in *sin = NULL; struct sockaddr_in6 *sin6 = NULL; struct sockaddr_dl *sdl = NULL; struct sockaddr_storage sins; struct sockaddr_dl sdl_m; #define NEXTADDR(w, s, len) \ if (rtm->rtm_addrs & (w)) \ { \ bcopy((char *)s, cp, len); \ cp += len; \ } bzero(&sins, sizeof(struct sockaddr_storage)); bzero(&sdl_m, sizeof(struct sockaddr_dl)); bzero((char *)&m_rtmsg, sizeof(m_rtmsg)); sock = socket(PF_ROUTE, SOCK_RAW, 0); if (sock < 0) { const int socket_errno = errno; LogMsg("getMACAddress: Can not open the socket - %s", strerror(socket_errno)); return socket_errno; } rtm->rtm_addrs |= RTA_DST | RTA_GATEWAY; rtm->rtm_type = RTM_GET; rtm->rtm_flags = 0; rtm->rtm_version = RTM_VERSION; rtm->rtm_seq = ++seq; sdl_m.sdl_len = sizeof(sdl_m); sdl_m.sdl_family = AF_LINK; if (family == AF_INET) { sin = (struct sockaddr_in*)&sins; sin->sin_family = AF_INET; sin->sin_len = sizeof(struct sockaddr_in); memcpy(&sin->sin_addr, raddr, sizeof(struct in_addr)); NEXTADDR(RTA_DST, sin, sin->sin_len); } else if (family == AF_INET6) { sin6 = (struct sockaddr_in6 *)&sins; sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_family = AF_INET6; memcpy(&sin6->sin6_addr, raddr, sizeof(struct in6_addr)); NEXTADDR(RTA_DST, sin6, sin6->sin6_len); } NEXTADDR(RTA_GATEWAY, &sdl_m, sdl_m.sdl_len); rtm->rtm_msglen = rlen = cp - (char *)&m_rtmsg; if (write(sock, (char *)&m_rtmsg, rlen) < 0) { const int write_errno = errno; LogMsg("getMACAddress: writing to routing socket: %s", strerror(write_errno)); close(sock); return write_errno; } do { rlen = read(sock, (char *)&m_rtmsg, sizeof(m_rtmsg)); } while (rlen > 0 && (rtm->rtm_seq != seq || rtm->rtm_pid != getpid())); if (rlen < 0) LogMsg("getMACAddress: Read from routing socket failed"); if (family == AF_INET) { sin = (struct sockaddr_in *) (rtm + 1); sdl = (struct sockaddr_dl *) (sin->sin_len + (char *) sin); } else if (family == AF_INET6) { sin6 = (struct sockaddr_in6 *) (rtm +1); sdl = (struct sockaddr_dl *) (sin6->sin6_len + (char *) sin6); } if (!sdl) { LogMsg("getMACAddress: sdl is NULL for family %d", family); close(sock); return -1; } // If the address is not on the local net, we get the IP address of the gateway. // We would have to repeat the process to get the MAC address of the gateway *gfamily = sdl->sdl_family; if (sdl->sdl_family == AF_INET) { if (sin) { struct sockaddr_in *new_sin = (struct sockaddr_in *)(sin->sin_len +(char*) sin); memcpy(gaddr, &new_sin->sin_addr, sizeof(struct in_addr)); } else { LogMsg("getMACAddress: sin is NULL"); } close(sock); return -1; } else if (sdl->sdl_family == AF_INET6) { if (sin6) { struct sockaddr_in6 *new_sin6 = (struct sockaddr_in6 *)(sin6->sin6_len +(char*) sin6); memcpy(gaddr, &new_sin6->sin6_addr, sizeof(struct in6_addr)); } else { LogMsg("getMACAddress: sin6 is NULL"); } close(sock); return -1; } unsigned char *ptr = (unsigned char *)LLADDR(sdl); for (i = 0; i < ETHER_ADDR_LEN; i++) (eth)[i] = *(ptr +i); close(sock); return KERN_SUCCESS; } mDNSlocal int GetRemoteMacinternal(int family, v6addr_t raddr, ethaddr_t eth) { int ret = 0; v6addr_t gateway; int gfamily = 0; int count = 0; do { ret = getMACAddress(family, raddr, gateway, &gfamily, eth); if (ret == -1) { memcpy(raddr, gateway, sizeof(family)); family = gfamily; count++; } } while ((ret == -1) && (count < 5)); return ret; } mDNSlocal int StoreSPSMACAddressinternal(int family, v6addr_t spsaddr, const char *ifname) { ethaddr_t eth; char spsip[INET6_ADDRSTRLEN]; int ret = 0; CFStringRef sckey = NULL; SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:StoreSPSMACAddress"), NULL, NULL); SCDynamicStoreRef ipstore = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:GetIPv6Addresses"), NULL, NULL); CFMutableDictionaryRef dict = NULL; CFStringRef entityname = NULL; CFDictionaryRef ipdict = NULL; CFArrayRef addrs = NULL; if ((store == NULL) || (ipstore == NULL)) { LogMsg("StoreSPSMACAddressinternal: Unable to accesss SC Dynamic Store"); ret = -1; goto fin; } // Get the MAC address of the Sleep Proxy Server memset(eth, 0, sizeof(eth)); ret = GetRemoteMacinternal(family, spsaddr, eth); if (ret != 0) { LogMsg("StoreSPSMACAddressinternal: Failed to determine the MAC address"); goto fin; } // Create/Update the dynamic store entry for the specified interface sckey = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s%s%s"), "State:/Network/Interface/", ifname, "/BonjourSleepProxyAddress"); dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!dict) { LogMsg("StoreSPSMACAddressinternal: SPSCreateDict() Could not create CFDictionary dict"); ret = -1; goto fin; } CFStringRef macaddr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%02x:%02x:%02x:%02x:%02x:%02x"), eth[0], eth[1], eth[2], eth[3], eth[4], eth[5]); CFDictionarySetValue(dict, CFSTR("MACAddress"), macaddr); if (NULL != macaddr) CFRelease(macaddr); if( NULL == inet_ntop(family, (void *)spsaddr, spsip, sizeof(spsip))) { LogMsg("StoreSPSMACAddressinternal: inet_ntop failed: %s", strerror(errno)); ret = -1; goto fin; } CFStringRef ipaddr = CFStringCreateWithCString(NULL, spsip, kCFStringEncodingUTF8); CFDictionarySetValue(dict, CFSTR("IPAddress"), ipaddr); if (NULL != ipaddr) CFRelease(ipaddr); // Get the current IPv6 addresses on this interface and store them so NAs can be sent on wakeup if ((entityname = CFStringCreateWithFormat(NULL, NULL, CFSTR("State:/Network/Interface/%s/IPv6"), ifname)) != NULL) { if ((ipdict = SCDynamicStoreCopyValue(ipstore, entityname)) != NULL) { if((addrs = CFDictionaryGetValue(ipdict, CFSTR("Addresses"))) != NULL) { addrs = CFRetain(addrs); CFDictionarySetValue(dict, CFSTR("RegisteredAddresses"), addrs); } } } SCDynamicStoreSetValue(store, sckey, dict); fin: if (store) CFRelease(store); if (ipstore) CFRelease(ipstore); if (sckey) CFRelease(sckey); if (dict) CFRelease(dict); if (ipdict) CFRelease(ipdict); if (entityname) CFRelease(entityname); if (addrs) CFRelease(addrs); return ret; } mDNSlocal void mDNSStoreSPSMACAddress(int family, v6addr_t spsaddr, char *ifname) { struct { v6addr_t saddr; } addr; int err = 0; mDNSPlatformMemCopy(addr.saddr, spsaddr, sizeof(v6addr_t)); err = StoreSPSMACAddressinternal(family, (uint8_t *)addr.saddr, ifname); if (err != 0) LogMsg("mDNSStoreSPSMACAddress : failed"); } mDNSexport mStatus mDNSPlatformStoreSPSMACAddr(mDNSAddr *spsaddr, char *ifname) { int family = (spsaddr->type == mDNSAddrType_IPv4) ? AF_INET : AF_INET6; LogInfo("mDNSPlatformStoreSPSMACAddr : Storing %#a on interface %s", spsaddr, ifname); mDNSStoreSPSMACAddress(family, spsaddr->ip.v6.b, ifname); return KERN_SUCCESS; } mDNSexport mStatus mDNSPlatformStoreOwnerOptRecord(char *ifname, DNSMessage* msg, int length) { int ret = 0; CFStringRef sckey = NULL; SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:StoreOwnerOPTRecord"), NULL, NULL); CFMutableDictionaryRef dict = NULL; if (store == NULL) { LogMsg("mDNSPlatformStoreOwnerOptRecord: Unable to accesss SC Dynamic Store"); ret = -1; goto fin; } // Create/Update the dynamic store entry for the specified interface sckey = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s%s%s"), "State:/Network/Interface/", ifname, "/BonjourSleepProxyOPTRecord"); dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!dict) { LogMsg("mDNSPlatformStoreOwnerOptRecord: Could not create CFDictionary dictionary to store OPT Record"); ret =-1; goto fin; } CFDataRef optRec = NULL; optRec = CFDataCreate(NULL, (const uint8_t *)msg, (CFIndex)length); CFDictionarySetValue(dict, CFSTR("OwnerOPTRecord"), optRec); if (NULL != optRec) CFRelease(optRec); SCDynamicStoreSetValue(store, sckey, dict); fin: if (NULL != store) CFRelease(store); if (NULL != sckey) CFRelease(sckey); if (NULL != dict) CFRelease(dict); return ret; } mDNSlocal void mDNSGet_RemoteMAC(int family, v6addr_t raddr) { ethaddr_t eth; IPAddressMACMapping *addrMapping; int kr = KERN_FAILURE; struct { v6addr_t addr; } dst; bzero(eth, sizeof(ethaddr_t)); mDNSPlatformMemCopy(dst.addr, raddr, sizeof(v6addr_t)); kr = GetRemoteMacinternal(family, (uint8_t *)dst.addr, eth); // If the call to get the remote MAC address succeeds, allocate and copy // the values and schedule a task to update the MAC address in the TCP Keepalive record. if (kr == 0) { addrMapping = mDNSPlatformMemAllocate(sizeof(IPAddressMACMapping)); snprintf(addrMapping->ethaddr, sizeof(addrMapping->ethaddr), "%02x:%02x:%02x:%02x:%02x:%02x", eth[0], eth[1], eth[2], eth[3], eth[4], eth[5]); if (family == AF_INET) { addrMapping->ipaddr.type = mDNSAddrType_IPv4; mDNSPlatformMemCopy(addrMapping->ipaddr.ip.v4.b, raddr, sizeof(v6addr_t)); } else { addrMapping->ipaddr.type = mDNSAddrType_IPv6; mDNSPlatformMemCopy(addrMapping->ipaddr.ip.v6.b, raddr, sizeof(v6addr_t)); } UpdateRMAC(&mDNSStorage, addrMapping); } } mDNSexport mStatus mDNSPlatformGetRemoteMacAddr(mDNSAddr *raddr) { int family = (raddr->type == mDNSAddrType_IPv4) ? AF_INET : AF_INET6; LogInfo("mDNSPlatformGetRemoteMacAddr calling mDNSGet_RemoteMAC"); mDNSGet_RemoteMAC(family, raddr->ip.v6.b); return KERN_SUCCESS; } mDNSexport mStatus mDNSPlatformRetrieveTCPInfo(mDNSAddr *laddr, mDNSIPPort *lport, mDNSAddr *raddr, mDNSIPPort *rport, mDNSTCPInfo *mti) { mDNSs32 intfid; mDNSs32 error = 0; int family = (laddr->type == mDNSAddrType_IPv4) ? AF_INET : AF_INET6; error = mDNSRetrieveTCPInfo(family, laddr->ip.v6.b, lport->NotAnInteger, raddr->ip.v6.b, rport->NotAnInteger, (uint32_t *)&(mti->seq), (uint32_t *)&(mti->ack), (uint16_t *)&(mti->window), (int32_t*)&intfid); if (error != KERN_SUCCESS) { LogMsg("%s: mDNSRetrieveTCPInfo returned : %d", __func__, error); return error; } mti->IntfId = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, intfid); return error; } #define BPF_SetOffset(from, cond, to) (from)->cond = (to) - 1 - (from) mDNSlocal int CountProxyTargets(NetworkInterfaceInfoOSX *x, int *p4, int *p6) { int numv4 = 0, numv6 = 0; AuthRecord *rr; for (rr = mDNSStorage.ResourceRecords; rr; rr=rr->next) if (rr->resrec.InterfaceID == x->ifinfo.InterfaceID && rr->AddressProxy.type == mDNSAddrType_IPv4) { if (p4) LogSPS("CountProxyTargets: fd %d %-7s IP%2d %.4a", x->BPF_fd, x->ifinfo.ifname, numv4, &rr->AddressProxy.ip.v4); numv4++; } for (rr = mDNSStorage.ResourceRecords; rr; rr=rr->next) if (rr->resrec.InterfaceID == x->ifinfo.InterfaceID && rr->AddressProxy.type == mDNSAddrType_IPv6) { if (p6) LogSPS("CountProxyTargets: fd %d %-7s IP%2d %.16a", x->BPF_fd, x->ifinfo.ifname, numv6, &rr->AddressProxy.ip.v6); numv6++; } if (p4) *p4 = numv4; if (p6) *p6 = numv6; return(numv4 + numv6); } mDNSexport void mDNSPlatformUpdateProxyList(const mDNSInterfaceID InterfaceID) { mDNS *const m = &mDNSStorage; NetworkInterfaceInfoOSX *x; // Note: We can't use IfIndexToInterfaceInfoOSX because that looks for Registered also. for (x = m->p->InterfaceList; x; x = x->next) if ((x->ifinfo.InterfaceID == InterfaceID) && (x->BPF_fd >= 0)) break; if (!x) { LogMsg("mDNSPlatformUpdateProxyList: ERROR InterfaceID %p not found", InterfaceID); return; } #define MAX_BPF_ADDRS 250 int numv4 = 0, numv6 = 0; if (CountProxyTargets(x, &numv4, &numv6) > MAX_BPF_ADDRS) { LogMsg("mDNSPlatformUpdateProxyList: ERROR Too many address proxy records v4 %d v6 %d", numv4, numv6); if (numv4 > MAX_BPF_ADDRS) numv4 = MAX_BPF_ADDRS; numv6 = MAX_BPF_ADDRS - numv4; } LogSPS("mDNSPlatformUpdateProxyList: fd %d %-7s MAC %.6a %d v4 %d v6", x->BPF_fd, x->ifinfo.ifname, &x->ifinfo.MAC, numv4, numv6); // Caution: This is a static structure, so we need to be careful that any modifications we make to it // are done in such a way that they work correctly when mDNSPlatformUpdateProxyList is called multiple times static struct bpf_insn filter[17 + MAX_BPF_ADDRS] = { BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), // 0 Read Ethertype (bytes 12,13) BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0806, 0, 1), // 1 If Ethertype == ARP goto next, else 3 BPF_STMT(BPF_RET + BPF_K, 42), // 2 Return 42-byte ARP BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0800, 4, 0), // 3 If Ethertype == IPv4 goto 8 (IPv4 address list check) else next BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x86DD, 0, 9), // 4 If Ethertype == IPv6 goto next, else exit BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20), // 5 Read Protocol and Hop Limit (bytes 20,21) BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x3AFF, 0, 9), // 6 If (Prot,TTL) == (3A,FF) goto next, else IPv6 address list check BPF_STMT(BPF_RET + BPF_K, 86), // 7 Return 86-byte ND // Is IPv4 packet; check if it's addressed to any IPv4 address we're proxying for BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 30), // 8 Read IPv4 Dst (bytes 30,31,32,33) }; // Special filter program to use when there are no address proxy records static struct bpf_insn nullfilter[] = { BPF_STMT(BPF_RET | BPF_K, 0) // 0 Match no packets and return size 0 }; struct bpf_program prog; if (!numv4 && !numv6) { LogSPS("mDNSPlatformUpdateProxyList: No need for filter"); if (m->timenow == 0) LogMsg("mDNSPlatformUpdateProxyList: m->timenow == 0"); // Cancel any previous ND group memberships we had if (x->BPF_mcfd >= 0) { close(x->BPF_mcfd); x->BPF_mcfd = -1; } // Schedule check to see if we can close this BPF_fd now if (!m->NetworkChanged) m->NetworkChanged = NonZeroTime(m->timenow + mDNSPlatformOneSecond * 2); if (x->BPF_fd < 0) return; // If we've already closed our BPF_fd, no need to generate an error message below prog.bf_len = 1; prog.bf_insns = nullfilter; } else { struct bpf_insn *pc = &filter[9]; struct bpf_insn *chk6 = pc + numv4 + 1; // numv4 address checks, plus a "return 0" struct bpf_insn *fail = chk6 + 1 + numv6; // Get v6 Dst LSW, plus numv6 address checks struct bpf_insn *ret4 = fail + 1; struct bpf_insn *ret6 = ret4 + 4; static const struct bpf_insn rf = BPF_STMT(BPF_RET + BPF_K, 0); // No match: Return nothing static const struct bpf_insn g6 = BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 50); // Read IPv6 Dst LSW (bytes 50,51,52,53) static const struct bpf_insn r4a = BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14); // Get IP Header length (normally 20) static const struct bpf_insn r4b = BPF_STMT(BPF_LD + BPF_IMM, 54); // A = 54 (14-byte Ethernet plus 20-byte TCP + 20 bytes spare) static const struct bpf_insn r4c = BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0); // A += IP Header length static const struct bpf_insn r4d = BPF_STMT(BPF_RET + BPF_A, 0); // Success: Return Ethernet + IP + TCP + 20 bytes spare (normally 74) static const struct bpf_insn r6a = BPF_STMT(BPF_RET + BPF_K, 94); // Success: Return Eth + IPv6 + TCP + 20 bytes spare BPF_SetOffset(&filter[4], jf, fail); // If Ethertype not ARP, IPv4, or IPv6, fail BPF_SetOffset(&filter[6], jf, chk6); // If IPv6 but not ICMPv6, go to IPv6 address list check // BPF Byte-Order Note // The BPF API designers apparently thought that programmers would not be smart enough to use htons // and htonl correctly to convert numeric values to network byte order on little-endian machines, // so instead they chose to make the API implicitly byte-swap *ALL* values, even literal byte strings // that shouldn't be byte-swapped, like ASCII text, Ethernet addresses, IP addresses, etc. // As a result, if we put Ethernet addresses and IP addresses in the right byte order, the BPF API // will byte-swap and make them backwards, and then our filter won't work. So, we have to arrange // that on little-endian machines we deliberately put addresses in memory with the bytes backwards, // so that when the BPF API goes through and swaps them all, they end up back as they should be. // In summary, if we byte-swap all the non-numeric fields that shouldn't be swapped, and we *don't* // swap any of the numeric values that *should* be byte-swapped, then the filter will work correctly. // IPSEC capture size notes: // 8 bytes UDP header // 4 bytes Non-ESP Marker // 28 bytes IKE Header // -- // 40 Total. Capturing TCP Header + 20 gets us enough bytes to receive the IKE Header in a UDP-encapsulated IKE packet. AuthRecord *rr; for (rr = m->ResourceRecords; rr; rr=rr->next) if (rr->resrec.InterfaceID == InterfaceID && rr->AddressProxy.type == mDNSAddrType_IPv4) { mDNSv4Addr a = rr->AddressProxy.ip.v4; pc->code = BPF_JMP + BPF_JEQ + BPF_K; BPF_SetOffset(pc, jt, ret4); pc->jf = 0; pc->k = (bpf_u_int32)a.b[0] << 24 | (bpf_u_int32)a.b[1] << 16 | (bpf_u_int32)a.b[2] << 8 | (bpf_u_int32)a.b[3]; pc++; } *pc++ = rf; if (pc != chk6) LogMsg("mDNSPlatformUpdateProxyList: pc %p != chk6 %p", pc, chk6); *pc++ = g6; // chk6 points here // First cancel any previous ND group memberships we had, then create a fresh socket if (x->BPF_mcfd >= 0) close(x->BPF_mcfd); x->BPF_mcfd = socket(AF_INET6, SOCK_DGRAM, 0); for (rr = m->ResourceRecords; rr; rr=rr->next) if (rr->resrec.InterfaceID == InterfaceID && rr->AddressProxy.type == mDNSAddrType_IPv6) { const mDNSv6Addr *const a = &rr->AddressProxy.ip.v6; pc->code = BPF_JMP + BPF_JEQ + BPF_K; BPF_SetOffset(pc, jt, ret6); pc->jf = 0; pc->k = (bpf_u_int32)a->b[0x0C] << 24 | (bpf_u_int32)a->b[0x0D] << 16 | (bpf_u_int32)a->b[0x0E] << 8 | (bpf_u_int32)a->b[0x0F]; pc++; struct ipv6_mreq i6mr; i6mr.ipv6mr_interface = x->scope_id; i6mr.ipv6mr_multiaddr = *(const struct in6_addr*)&NDP_prefix; i6mr.ipv6mr_multiaddr.s6_addr[0xD] = a->b[0xD]; i6mr.ipv6mr_multiaddr.s6_addr[0xE] = a->b[0xE]; i6mr.ipv6mr_multiaddr.s6_addr[0xF] = a->b[0xF]; // Do precautionary IPV6_LEAVE_GROUP first, necessary to clear stale kernel state mStatus err = setsockopt(x->BPF_mcfd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &i6mr, sizeof(i6mr)); if (err < 0 && (errno != EADDRNOTAVAIL)) LogMsg("mDNSPlatformUpdateProxyList: IPV6_LEAVE_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface); err = setsockopt(x->BPF_mcfd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &i6mr, sizeof(i6mr)); if (err < 0 && (errno != EADDRINUSE)) // Joining same group twice can give "Address already in use" error -- no need to report that LogMsg("mDNSPlatformUpdateProxyList: IPV6_JOIN_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface); LogSPS("Joined IPv6 ND multicast group %.16a for %.16a", &i6mr.ipv6mr_multiaddr, a); } if (pc != fail) LogMsg("mDNSPlatformUpdateProxyList: pc %p != fail %p", pc, fail); *pc++ = rf; // fail points here if (pc != ret4) LogMsg("mDNSPlatformUpdateProxyList: pc %p != ret4 %p", pc, ret4); *pc++ = r4a; // ret4 points here *pc++ = r4b; *pc++ = r4c; *pc++ = r4d; if (pc != ret6) LogMsg("mDNSPlatformUpdateProxyList: pc %p != ret6 %p", pc, ret6); *pc++ = r6a; // ret6 points here #if 0 // For debugging BPF filter program unsigned int q; for (q=0; qBPF_fd, BIOCSETFNR, &prog) < 0) LogMsg("mDNSPlatformUpdateProxyList: BIOCSETFNR(%d) failed %d (%s)", prog.bf_len, errno, strerror(errno)); else LogSPS("mDNSPlatformUpdateProxyList: BIOCSETFNR(%d) successful", prog.bf_len); } mDNSexport void mDNSPlatformReceiveBPF_fd(int fd) { mDNS *const m = &mDNSStorage; mDNS_Lock(m); NetworkInterfaceInfoOSX *i; for (i = m->p->InterfaceList; i; i = i->next) if (i->BPF_fd == -2) break; if (!i) { LogSPS("mDNSPlatformReceiveBPF_fd: No Interfaces awaiting BPF fd %d; closing", fd); close(fd); } else { LogSPS("%s using BPF fd %d", i->ifinfo.ifname, fd); struct bpf_version v; if (ioctl(fd, BIOCVERSION, &v) < 0) LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCVERSION failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno)); else if (BPF_MAJOR_VERSION != v.bv_major || BPF_MINOR_VERSION != v.bv_minor) LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCVERSION header %d.%d kernel %d.%d", fd, i->ifinfo.ifname, BPF_MAJOR_VERSION, BPF_MINOR_VERSION, v.bv_major, v.bv_minor); if (ioctl(fd, BIOCGBLEN, &i->BPF_len) < 0) LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCGBLEN failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno)); if (i->BPF_len > sizeof(m->imsg)) { i->BPF_len = sizeof(m->imsg); if (ioctl(fd, BIOCSBLEN, &i->BPF_len) < 0) LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCSBLEN failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno)); else LogSPS("mDNSPlatformReceiveBPF_fd: %d %s BIOCSBLEN %d", fd, i->ifinfo.ifname, i->BPF_len); } static const u_int opt_one = 1; if (ioctl(fd, BIOCIMMEDIATE, &opt_one) < 0) LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCIMMEDIATE failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno)); //if (ioctl(fd, BIOCPROMISC, &opt_one) < 0) // LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCPROMISC failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno)); //if (ioctl(fd, BIOCSHDRCMPLT, &opt_one) < 0) // LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCSHDRCMPLT failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno)); /* * make socket non blocking see comments in bpf_callback_common for more info */ if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) // set non-blocking { LogMsg("mDNSPlatformReceiveBPF_fd: %d %s O_NONBLOCK failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno)); } struct ifreq ifr; mDNSPlatformMemZero(&ifr, sizeof(ifr)); strlcpy(ifr.ifr_name, i->ifinfo.ifname, sizeof(ifr.ifr_name)); if (ioctl(fd, BIOCSETIF, &ifr) < 0) { LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCSETIF failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno)); i->BPF_fd = -3; } else { #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM i->BPF_fd = fd; i->BPF_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, dispatch_get_main_queue()); if (!i->BPF_source) {LogMsg("mDNSPlatformReceiveBPF_fd: dispatch source create failed"); return;} dispatch_source_set_event_handler(i->BPF_source, ^{bpf_callback_dispatch(i);}); dispatch_source_set_cancel_handler(i->BPF_source, ^{close(fd);}); dispatch_resume(i->BPF_source); #else CFSocketContext myCFSocketContext = { 0, i, NULL, NULL, NULL }; i->BPF_fd = fd; i->BPF_cfs = CFSocketCreateWithNative(kCFAllocatorDefault, fd, kCFSocketReadCallBack, bpf_callback, &myCFSocketContext); i->BPF_rls = CFSocketCreateRunLoopSource(kCFAllocatorDefault, i->BPF_cfs, 0); CFRunLoopAddSource(CFRunLoopGetMain(), i->BPF_rls, kCFRunLoopDefaultMode); #endif mDNSPlatformUpdateProxyList(i->ifinfo.InterfaceID); } } mDNS_Unlock(m); } #endif // APPLE_OSX_mDNSResponder #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - #pragma mark - Key Management #endif #ifndef NO_SECURITYFRAMEWORK mDNSlocal CFArrayRef CopyCertChain(SecIdentityRef identity) { CFMutableArrayRef certChain = NULL; if (!identity) { LogMsg("CopyCertChain: identity is NULL"); return(NULL); } SecCertificateRef cert; OSStatus err = SecIdentityCopyCertificate(identity, &cert); if (err || !cert) LogMsg("CopyCertChain: SecIdentityCopyCertificate() returned %d", (int) err); else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" SecPolicySearchRef searchRef; err = SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, NULL, &searchRef); if (err || !searchRef) LogMsg("CopyCertChain: SecPolicySearchCreate() returned %d", (int) err); else { SecPolicyRef policy; err = SecPolicySearchCopyNext(searchRef, &policy); if (err || !policy) LogMsg("CopyCertChain: SecPolicySearchCopyNext() returned %d", (int) err); else { CFArrayRef wrappedCert = CFArrayCreate(NULL, (const void**) &cert, 1, &kCFTypeArrayCallBacks); if (!wrappedCert) LogMsg("CopyCertChain: wrappedCert is NULL"); else { SecTrustRef trust; err = SecTrustCreateWithCertificates(wrappedCert, policy, &trust); if (err || !trust) LogMsg("CopyCertChain: SecTrustCreateWithCertificates() returned %d", (int) err); else { err = SecTrustEvaluate(trust, NULL); if (err) LogMsg("CopyCertChain: SecTrustEvaluate() returned %d", (int) err); else { CFArrayRef rawCertChain; CSSM_TP_APPLE_EVIDENCE_INFO *statusChain = NULL; err = SecTrustGetResult(trust, NULL, &rawCertChain, &statusChain); if (err || !rawCertChain || !statusChain) LogMsg("CopyCertChain: SecTrustGetResult() returned %d", (int) err); else { certChain = CFArrayCreateMutableCopy(NULL, 0, rawCertChain); if (!certChain) LogMsg("CopyCertChain: certChain is NULL"); else { // Replace the SecCertificateRef at certChain[0] with a SecIdentityRef per documentation for SSLSetCertificate: // CFArraySetValueAtIndex(certChain, 0, identity); // Remove root from cert chain, but keep any and all intermediate certificates that have been signed by the root certificate if (CFArrayGetCount(certChain) > 1) CFArrayRemoveValueAtIndex(certChain, CFArrayGetCount(certChain) - 1); } CFRelease(rawCertChain); // Do not free statusChain: // says: // certChain: Call the CFRelease function to release this object when you are finished with it. // statusChain: Do not attempt to free this pointer; it remains valid until the trust management object is released... } } CFRelease(trust); } CFRelease(wrappedCert); } CFRelease(policy); } CFRelease(searchRef); } #pragma clang diagnostic pop CFRelease(cert); } return certChain; } #endif /* NO_SECURITYFRAMEWORK */ mDNSexport mStatus mDNSPlatformTLSSetupCerts(void) { #ifdef NO_SECURITYFRAMEWORK return mStatus_UnsupportedErr; #else SecIdentityRef identity = nil; SecIdentitySearchRef srchRef = nil; OSStatus err; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // search for "any" identity matching specified key use // In this app, we expect there to be exactly one err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_DECRYPT, &srchRef); if (err) { LogMsg("ERROR: mDNSPlatformTLSSetupCerts: SecIdentitySearchCreate returned %d", (int) err); return err; } err = SecIdentitySearchCopyNext(srchRef, &identity); if (err) { LogMsg("ERROR: mDNSPlatformTLSSetupCerts: SecIdentitySearchCopyNext returned %d", (int) err); return err; } #pragma clang diagnostic pop if (CFGetTypeID(identity) != SecIdentityGetTypeID()) { LogMsg("ERROR: mDNSPlatformTLSSetupCerts: SecIdentitySearchCopyNext CFTypeID failure"); return mStatus_UnknownErr; } // Found one. Call CopyCertChain to create the correct certificate chain. ServerCerts = CopyCertChain(identity); if (ServerCerts == nil) { LogMsg("ERROR: mDNSPlatformTLSSetupCerts: CopyCertChain error"); return mStatus_UnknownErr; } return mStatus_NoError; #endif /* NO_SECURITYFRAMEWORK */ } mDNSexport void mDNSPlatformTLSTearDownCerts(void) { #ifndef NO_SECURITYFRAMEWORK if (ServerCerts) { CFRelease(ServerCerts); ServerCerts = NULL; } #endif /* NO_SECURITYFRAMEWORK */ } // This gets the text of the field currently labelled "Computer Name" in the Sharing Prefs Control Panel mDNSlocal void GetUserSpecifiedFriendlyComputerName(domainlabel *const namelabel) { CFStringEncoding encoding = kCFStringEncodingUTF8; CFStringRef cfs = SCDynamicStoreCopyComputerName(NULL, &encoding); if (cfs) { CFStringGetPascalString(cfs, namelabel->c, sizeof(*namelabel), kCFStringEncodingUTF8); CFRelease(cfs); } } // This gets the text of the field currently labelled "Local Hostname" in the Sharing Prefs Control Panel mDNSlocal void GetUserSpecifiedLocalHostName(domainlabel *const namelabel) { CFStringRef cfs = SCDynamicStoreCopyLocalHostName(NULL); if (cfs) { CFStringGetPascalString(cfs, namelabel->c, sizeof(*namelabel), kCFStringEncodingUTF8); CFRelease(cfs); } } mDNSexport mDNSBool DictionaryIsEnabled(CFDictionaryRef dict) { mDNSs32 val; CFNumberRef state = (CFNumberRef)CFDictionaryGetValue(dict, CFSTR("Enabled")); if (!state) return mDNSfalse; if (!CFNumberGetValue(state, kCFNumberSInt32Type, &val)) { LogMsg("ERROR: DictionaryIsEnabled - CFNumberGetValue"); return mDNSfalse; } return val ? mDNStrue : mDNSfalse; } mDNSlocal mStatus SetupAddr(mDNSAddr *ip, const struct sockaddr *const sa) { if (!sa) { LogMsg("SetupAddr ERROR: NULL sockaddr"); return(mStatus_Invalid); } if (sa->sa_family == AF_INET) { struct sockaddr_in *ifa_addr = (struct sockaddr_in *)sa; ip->type = mDNSAddrType_IPv4; ip->ip.v4.NotAnInteger = ifa_addr->sin_addr.s_addr; return(mStatus_NoError); } if (sa->sa_family == AF_INET6) { struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6 *)sa; // Inside the BSD kernel they use a hack where they stuff the sin6->sin6_scope_id // value into the second word of the IPv6 link-local address, so they can just // pass around IPv6 address structures instead of full sockaddr_in6 structures. // Those hacked IPv6 addresses aren't supposed to escape the kernel in that form, but they do. // To work around this we always whack the second word of any IPv6 link-local address back to zero. if (IN6_IS_ADDR_LINKLOCAL(&ifa_addr->sin6_addr)) ifa_addr->sin6_addr.__u6_addr.__u6_addr16[1] = 0; ip->type = mDNSAddrType_IPv6; ip->ip.v6 = *(mDNSv6Addr*)&ifa_addr->sin6_addr; return(mStatus_NoError); } LogMsg("SetupAddr invalid sa_family %d", sa->sa_family); return(mStatus_Invalid); } mDNSlocal mDNSEthAddr GetBSSID(char *ifa_name) { mDNSEthAddr eth = zeroEthAddr; CFStringRef entityname = CFStringCreateWithFormat(NULL, NULL, CFSTR("State:/Network/Interface/%s/AirPort"), ifa_name); if (entityname) { CFDictionaryRef dict = SCDynamicStoreCopyValue(NULL, entityname); if (dict) { CFRange range = { 0, 6 }; // Offset, length CFDataRef data = CFDictionaryGetValue(dict, CFSTR("BSSID")); if (data && CFDataGetLength(data) == 6) CFDataGetBytes(data, range, eth.b); CFRelease(dict); } CFRelease(entityname); } return(eth); } mDNSlocal int GetMAC(mDNSEthAddr *eth, u_short ifindex) { struct ifaddrs *ifa; for (ifa = myGetIfAddrs(0); ifa; ifa = ifa->ifa_next) if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_LINK) { const struct sockaddr_dl *const sdl = (const struct sockaddr_dl *)ifa->ifa_addr; if (sdl->sdl_index == ifindex) { mDNSPlatformMemCopy(eth->b, sdl->sdl_data + sdl->sdl_nlen, 6); return 0; } } *eth = zeroEthAddr; return -1; } #ifndef SIOCGIFWAKEFLAGS #define SIOCGIFWAKEFLAGS _IOWR('i', 136, struct ifreq) /* get interface wake property flags */ #endif #ifndef IF_WAKE_ON_MAGIC_PACKET #define IF_WAKE_ON_MAGIC_PACKET 0x01 #endif #ifndef ifr_wake_flags #define ifr_wake_flags ifr_ifru.ifru_intval #endif mDNSlocal mDNSBool CheckInterfaceSupport(NetworkInterfaceInfo *const intf, const char *key) { io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, intf->ifname)); if (!service) { LogSPS("CheckInterfaceSupport: No service for interface %s", intf->ifname); return mDNSfalse; } io_name_t n1, n2; IOObjectGetClass(service, n1); io_object_t parent = IO_OBJECT_NULL; mDNSBool ret = mDNSfalse; kern_return_t kr = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parent); if (kr == KERN_SUCCESS) { CFStringRef keystr = CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8); IOObjectGetClass(parent, n2); LogSPS("CheckInterfaceSupport: Interface %s service %s parent %s", intf->ifname, n1, n2); CFTypeRef ref = mDNSNULL; // Currently, the key can be in a different part of the IOKit hierarchy on the AppleTV. // TODO: revist if it is ok to have the same call for all platforms. if (IsAppleTV()) ref = IORegistryEntrySearchCFProperty(parent, kIOServicePlane, keystr, kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively); else ref = IORegistryEntryCreateCFProperty(parent, keystr, kCFAllocatorDefault, mDNSNULL); if (!ref) { LogSPS("CheckInterfaceSupport: No %s for interface %s/%s/%s", key, intf->ifname, n1, n2); ret = mDNSfalse; } else { ret = mDNStrue; CFRelease(ref); } IOObjectRelease(parent); CFRelease(keystr); } else { LogSPS("CheckInterfaceSupport: IORegistryEntryGetParentEntry for %s/%s failed %d", intf->ifname, n1, kr); ret = mDNSfalse; } IOObjectRelease(service); return ret; } mDNSlocal mDNSBool InterfaceSupportsKeepAlive(NetworkInterfaceInfo *const intf) { return CheckInterfaceSupport(intf, mDNS_IOREG_KA_KEY); } mDNSlocal mDNSBool NetWakeInterface(NetworkInterfaceInfoOSX *i) { // We only use Sleep Proxy Service on multicast-capable interfaces, except loopback and D2D. if (!MulticastInterface(i) || (i->ifa_flags & IFF_LOOPBACK) || i->D2DInterface) { LogSPS("NetWakeInterface: returning false for %s", i->ifinfo.ifname); return(mDNSfalse); } // If the interface supports TCPKeepalive, it is capable of waking up for a magic packet // This check is needed since the SIOCGIFWAKEFLAGS ioctl returns wrong values for WOMP capability // when the power source is not AC Power. if (InterfaceSupportsKeepAlive(&i->ifinfo)) { LogSPS("NetWakeInterface: %s supports TCP Keepalive returning true", i->ifinfo.ifname); return mDNStrue; } int s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { LogMsg("NetWakeInterface socket failed %s error %d errno %d (%s)", i->ifinfo.ifname, s, errno, strerror(errno)); return(mDNSfalse); } struct ifreq ifr; strlcpy(ifr.ifr_name, i->ifinfo.ifname, sizeof(ifr.ifr_name)); if (ioctl(s, SIOCGIFWAKEFLAGS, &ifr) < 0) { const int ioctl_errno = errno; // For some strange reason, in /usr/include/sys/errno.h, EOPNOTSUPP is defined to be // 102 when compiling kernel code, and 45 when compiling user-level code. Since this // error code is being returned from the kernel, we need to use the kernel version. #define KERNEL_EOPNOTSUPP 102 if (ioctl_errno != KERNEL_EOPNOTSUPP) // "Operation not supported on socket", the expected result on Leopard and earlier LogMsg("NetWakeInterface SIOCGIFWAKEFLAGS %s errno %d (%s)", i->ifinfo.ifname, ioctl_errno, strerror(ioctl_errno)); // If on Leopard or earlier, we get EOPNOTSUPP, so in that case // we enable WOL if this interface is not AirPort and "Wake for Network access" is turned on. ifr.ifr_wake_flags = (ioctl_errno == KERNEL_EOPNOTSUPP && !(i)->BSSID.l[0] && i->m->SystemWakeOnLANEnabled) ? IF_WAKE_ON_MAGIC_PACKET : 0; } close(s); // ifr.ifr_wake_flags = IF_WAKE_ON_MAGIC_PACKET; // For testing with MacBook Air, using a USB dongle that doesn't actually support Wake-On-LAN LogSPS("NetWakeInterface: %-6s %#-14a %s WOMP", i->ifinfo.ifname, &i->ifinfo.ip, (ifr.ifr_wake_flags & IF_WAKE_ON_MAGIC_PACKET) ? "supports" : "no"); return((ifr.ifr_wake_flags & IF_WAKE_ON_MAGIC_PACKET) != 0); } mDNSlocal u_int64_t getExtendedFlags(char * ifa_name) { int sockFD; struct ifreq ifr; sockFD = socket(AF_INET, SOCK_DGRAM, 0); if (sockFD < 0) { LogMsg("getExtendedFlags: socket() call failed, errno = %d (%s)", errno, strerror(errno)); return 0; } ifr.ifr_addr.sa_family = AF_INET; strlcpy(ifr.ifr_name, ifa_name, sizeof(ifr.ifr_name)); if (ioctl(sockFD, SIOCGIFEFLAGS, (caddr_t)&ifr) == -1) { LogMsg("getExtendedFlags: SIOCGIFEFLAGS failed, errno = %d (%s)", errno, strerror(errno)); ifr.ifr_eflags = 0; } close(sockFD); return ifr.ifr_eflags; } #if TARGET_OS_IPHONE // Function pointers for the routines we use in the MobileWiFi framework. static WiFiManagerClientRef (*WiFiManagerClientCreate_p)(CFAllocatorRef allocator, WiFiClientType type) = mDNSNULL; static CFArrayRef (*WiFiManagerClientCopyDevices_p)(WiFiManagerClientRef manager) = mDNSNULL; static WiFiNetworkRef (*WiFiDeviceClientCopyCurrentNetwork_p)(WiFiDeviceClientRef device) = mDNSNULL; static bool (*WiFiNetworkIsCarPlay_p)(WiFiNetworkRef network) = mDNSNULL; mDNSlocal mDNSBool MobileWiFiLibLoad(void) { static mDNSBool isInitialized = mDNSfalse; static void *MobileWiFiLib_p = mDNSNULL; static const char path[] = "/System/Library/PrivateFrameworks/MobileWiFi.framework/MobileWiFi"; if (!isInitialized) { if (!MobileWiFiLib_p) { MobileWiFiLib_p = dlopen(path, RTLD_LAZY | RTLD_LOCAL); if (!MobileWiFiLib_p) { LogInfo("MobileWiFiLibLoad: dlopen() failed."); goto exit; } } if (!WiFiManagerClientCreate_p) { WiFiManagerClientCreate_p = dlsym(MobileWiFiLib_p, "WiFiManagerClientCreate"); if (!WiFiManagerClientCreate_p) { LogInfo("MobileWiFiLibLoad: load of WiFiManagerClientCreate symbol failed."); goto exit; } } if (!WiFiManagerClientCopyDevices_p) { WiFiManagerClientCopyDevices_p = dlsym(MobileWiFiLib_p, "WiFiManagerClientCopyDevices"); if (!WiFiManagerClientCopyDevices_p) { LogInfo("MobileWiFiLibLoad: load of WiFiManagerClientCopyDevices symbol failed."); goto exit; } } if (!WiFiDeviceClientCopyCurrentNetwork_p) { WiFiDeviceClientCopyCurrentNetwork_p = dlsym(MobileWiFiLib_p, "WiFiDeviceClientCopyCurrentNetwork"); if (!WiFiDeviceClientCopyCurrentNetwork_p) { LogInfo("MobileWiFiLibLoad: load of WiFiDeviceClientCopyCurrentNetwork symbol failed."); goto exit; } } if (!WiFiNetworkIsCarPlay_p) { WiFiNetworkIsCarPlay_p = dlsym(MobileWiFiLib_p, "WiFiNetworkIsCarPlay"); if (!WiFiNetworkIsCarPlay_p) { LogInfo("MobileWiFiLibLoad: load of WiFiNetworkIsCarPlay symbol failed."); goto exit; } } isInitialized = mDNStrue; } exit: return isInitialized; } #define CARPLAY_DEBUG 0 // Return true if the interface is associate to a CarPlay hosted SSID. // If we have associated with a CarPlay hosted SSID, then use the same // optimizations that are used when an interface has the IFEF_DIRECTLINK flag set. mDNSlocal mDNSBool IsCarPlaySSID(char *ifa_name) { static WiFiManagerClientRef manager = NULL; CFArrayRef devices; WiFiDeviceClientRef device; WiFiNetworkRef network; mDNSBool rvalue = mDNSfalse; if (!MobileWiFiLibLoad()) { LogInfo("IsCarPlaySSID: MobileWiFiLibLoad() failed!"); return mDNSfalse; } // Cache the WiFiManagerClientRef. if (manager == NULL) manager = WiFiManagerClientCreate_p(NULL, kWiFiClientTypeNormal); if (manager == NULL) { LogInfo("IsCarPlaySSID: WiFiManagerClientCreate() failed!"); return mDNSfalse; } devices = WiFiManagerClientCopyDevices_p(manager); // If the first call fails, update the cached WiFiManagerClientRef pointer and try again. if (devices == NULL) { LogInfo("IsCarPlaySSID: First call to WiFiManagerClientCopyDevices() returned NULL for %s", ifa_name); // Release the previously cached WiFiManagerClientRef which is apparently now stale. CFRelease(manager); manager = WiFiManagerClientCreate_p(NULL, kWiFiClientTypeNormal); if (manager == NULL) { LogInfo("IsCarPlaySSID: WiFiManagerClientCreate() failed!"); return mDNSfalse; } devices = WiFiManagerClientCopyDevices_p(manager); if (devices == NULL) { LogInfo("IsCarPlaySSID: Second call to WiFiManagerClientCopyDevices() returned NULL for %s", ifa_name); return mDNSfalse; } } device = (WiFiDeviceClientRef)CFArrayGetValueAtIndex(devices, 0); network = WiFiDeviceClientCopyCurrentNetwork_p(device); if (network != NULL) { if (WiFiNetworkIsCarPlay_p(network)) { LogInfo("IsCarPlaySSID: %s is CarPlay hosted", ifa_name); rvalue = mDNStrue; } #if CARPLAY_DEBUG else LogInfo("IsCarPlaySSID: %s is NOT CarPlay hosted", ifa_name); #endif // CARPLAY_DEBUG CFRelease(network); } else LogInfo("IsCarPlaySSID: WiFiDeviceClientCopyCurrentNetwork() returned NULL for %s", ifa_name); CFRelease(devices); return rvalue; } #else // TARGET_OS_IPHONE mDNSlocal mDNSBool IsCarPlaySSID(char *ifa_name) { (void)ifa_name; // unused // OSX WifiManager currently does not implement WiFiNetworkIsCarPlay() return mDNSfalse;; } #endif // TARGET_OS_IPHONE // Returns pointer to newly created NetworkInterfaceInfoOSX object, or // pointer to already-existing NetworkInterfaceInfoOSX object found in list, or // may return NULL if out of memory (unlikely) or parameters are invalid for some reason // (e.g. sa_family not AF_INET or AF_INET6) mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(struct ifaddrs *ifa, mDNSs32 utc) { mDNS *const m = &mDNSStorage; mDNSu32 scope_id = if_nametoindex(ifa->ifa_name); mDNSEthAddr bssid = GetBSSID(ifa->ifa_name); u_int64_t eflags = getExtendedFlags(ifa->ifa_name); mDNSAddr ip, mask; if (SetupAddr(&ip, ifa->ifa_addr ) != mStatus_NoError) return(NULL); if (SetupAddr(&mask, ifa->ifa_netmask) != mStatus_NoError) return(NULL); NetworkInterfaceInfoOSX **p; for (p = &m->p->InterfaceList; *p; p = &(*p)->next) if (scope_id == (*p)->scope_id && mDNSSameAddress(&ip, &(*p)->ifinfo.ip) && mDNSSameEthAddress(&bssid, &(*p)->BSSID)) { debugf("AddInterfaceToList: Found existing interface %lu %.6a with address %#a at %p, ifname before %s, after %s", scope_id, &bssid, &ip, *p, (*p)->ifinfo.ifname, ifa->ifa_name); // The name should be updated to the new name so that we don't report a wrong name in our SIGINFO output. // When interfaces are created with same MAC address, kernel resurrects the old interface. // Even though the interface index is the same (which should be sufficient), when we receive a UDP packet // we get the corresponding name for the interface index on which the packet was received and check against // the InterfaceList for a matching name. So, keep the name in sync strlcpy((*p)->ifinfo.ifname, ifa->ifa_name, sizeof((*p)->ifinfo.ifname)); (*p)->Exists = mDNStrue; // If interface was not in getifaddrs list last time we looked, but it is now, update 'AppearanceTime' for this record if ((*p)->LastSeen != utc) (*p)->AppearanceTime = utc; // If Wake-on-LAN capability of this interface has changed (e.g. because power cable on laptop has been disconnected) // we may need to start or stop or sleep proxy browse operation const mDNSBool NetWake = NetWakeInterface(*p); if ((*p)->ifinfo.NetWake != NetWake) { (*p)->ifinfo.NetWake = NetWake; // If this interface is already registered with mDNSCore, then we need to start or stop its NetWake browse on-the-fly. // If this interface is not already registered (i.e. it's a dormant interface we had in our list // from when we previously saw it) then we mustn't do that, because mDNSCore doesn't know about it yet. // In this case, the mDNS_RegisterInterface() call will take care of starting the NetWake browse if necessary. if ((*p)->Registered) { mDNS_Lock(m); if (NetWake) mDNS_ActivateNetWake_internal (m, &(*p)->ifinfo); else mDNS_DeactivateNetWake_internal(m, &(*p)->ifinfo); mDNS_Unlock(m); } } // Reset the flag if it has changed this time. (*p)->ifinfo.IgnoreIPv4LL = ((eflags & IFEF_ARPLL) != 0) ? mDNSfalse : mDNStrue; return(*p); } NetworkInterfaceInfoOSX *i = (NetworkInterfaceInfoOSX *)mallocL("NetworkInterfaceInfoOSX", sizeof(*i)); debugf("AddInterfaceToList: Making new interface %lu %.6a with address %#a at %p", scope_id, &bssid, &ip, i); if (!i) return(mDNSNULL); mDNSPlatformMemZero(i, sizeof(NetworkInterfaceInfoOSX)); i->ifinfo.InterfaceID = (mDNSInterfaceID)(uintptr_t)scope_id; i->ifinfo.ip = ip; i->ifinfo.mask = mask; strlcpy(i->ifinfo.ifname, ifa->ifa_name, sizeof(i->ifinfo.ifname)); i->ifinfo.ifname[sizeof(i->ifinfo.ifname)-1] = 0; // We can be configured to disable multicast advertisement, but we want to to support // local-only services, which need a loopback address record. i->ifinfo.Advertise = m->DivertMulticastAdvertisements ? ((ifa->ifa_flags & IFF_LOOPBACK) ? mDNStrue : mDNSfalse) : m->AdvertiseLocalAddresses; i->ifinfo.McastTxRx = mDNSfalse; // For now; will be set up later at the end of UpdateInterfaceList i->ifinfo.Loopback = ((ifa->ifa_flags & IFF_LOOPBACK) != 0) ? mDNStrue : mDNSfalse; i->ifinfo.IgnoreIPv4LL = ((eflags & IFEF_ARPLL) != 0) ? mDNSfalse : mDNStrue; // Setting DirectLink indicates we can do the optimization of skipping the probe phase // for the interface address records since they should be unique. // Unfortunately, the legacy p2p* interfaces do not set the IFEF_LOCALNET_PRIVATE // or IFEF_DIRECTLINK flags, so we have to match against the name. if ((eflags & (IFEF_DIRECTLINK | IFEF_AWDL)) || (strncmp(i->ifinfo.ifname, "p2p", 3) == 0)) i->ifinfo.DirectLink = mDNStrue; else i->ifinfo.DirectLink = IsCarPlaySSID(ifa->ifa_name); if (i->ifinfo.DirectLink) LogInfo("AddInterfaceToList: DirectLink set for %s", ifa->ifa_name); i->next = mDNSNULL; i->m = m; i->Exists = mDNStrue; i->Flashing = mDNSfalse; i->Occulting = mDNSfalse; i->D2DInterface = ((eflags & IFEF_LOCALNET_PRIVATE) || (strncmp(i->ifinfo.ifname, "p2p", 3) == 0)) ? mDNStrue: mDNSfalse; if (i->D2DInterface) LogInfo("AddInterfaceToList: D2DInterface set for %s", ifa->ifa_name); i->isExpensive = (eflags & IFEF_EXPENSIVE) ? mDNStrue: mDNSfalse; if (eflags & IFEF_AWDL) { // Set SupportsUnicastMDNSResponse false for the AWDL interface since unicast reserves // limited AWDL resources so we don't set the kDNSQClass_UnicastResponse bit in // Bonjour requests over the AWDL interface. i->ifinfo.SupportsUnicastMDNSResponse = mDNSfalse; AWDLInterfaceID = i->ifinfo.InterfaceID; LogInfo("AddInterfaceToList: AWDLInterfaceID = %d", (int) AWDLInterfaceID); } else { i->ifinfo.SupportsUnicastMDNSResponse = mDNStrue; } i->AppearanceTime = utc; // Brand new interface; AppearanceTime is now i->LastSeen = utc; i->ifa_flags = ifa->ifa_flags; i->scope_id = scope_id; i->BSSID = bssid; i->sa_family = ifa->ifa_addr->sa_family; i->BPF_fd = -1; i->BPF_mcfd = -1; i->BPF_len = 0; i->Registered = mDNSNULL; // Do this AFTER i->BSSID has been set up i->ifinfo.NetWake = (eflags & IFEF_EXPENSIVE)? mDNSfalse : NetWakeInterface(i); GetMAC(&i->ifinfo.MAC, scope_id); if (i->ifinfo.NetWake && !i->ifinfo.MAC.l[0]) LogMsg("AddInterfaceToList: Bad MAC address %.6a for %d %s %#a", &i->ifinfo.MAC, scope_id, i->ifinfo.ifname, &ip); *p = i; return(i); } #if APPLE_OSX_mDNSResponder #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - #pragma mark - AutoTunnel #endif #define kRacoonPort 4500 static DomainAuthInfo* AnonymousRacoonConfig = mDNSNULL; #ifndef NO_SECURITYFRAMEWORK static CFMutableDictionaryRef domainStatusDict = NULL; mDNSlocal mStatus CheckQuestionForStatus(const DNSQuestion *const q) { if (q->LongLived) { if (q->servAddr.type == mDNSAddrType_IPv4 && mDNSIPv4AddressIsOnes(q->servAddr.ip.v4)) return mStatus_NoSuchRecord; else if (q->state == LLQ_Poll) return mStatus_PollingMode; else if (q->state != LLQ_Established && !q->DuplicateOf) return mStatus_TransientErr; } return mStatus_NoError; } mDNSlocal mStatus UpdateLLQStatus(char *buffer, int bufsz, const DomainAuthInfo *const info) { mStatus status = mStatus_NoError; DNSQuestion* q, *worst_q = mDNSNULL; for (q = mDNSStorage.Questions; q; q=q->next) if (q->AuthInfo == info) { mStatus newStatus = CheckQuestionForStatus(q); if (newStatus == mStatus_NoSuchRecord) { status = newStatus; worst_q = q; break; } else if (newStatus == mStatus_PollingMode) { status = newStatus; worst_q = q; } else if (newStatus == mStatus_TransientErr && status == mStatus_NoError) { status = newStatus; worst_q = q; } } if (status == mStatus_NoError) mDNS_snprintf(buffer, bufsz, "Success"); else if (status == mStatus_NoSuchRecord) mDNS_snprintf(buffer, bufsz, "GetZoneData %s: %##s", worst_q->nta ? "not yet complete" : "failed", worst_q->qname.c); else if (status == mStatus_PollingMode) mDNS_snprintf(buffer, bufsz, "Query polling %##s", worst_q->qname.c); else if (status == mStatus_TransientErr) mDNS_snprintf(buffer, bufsz, "Query not yet established %##s", worst_q->qname.c); return status; } mDNSlocal mStatus UpdateRRStatus(char *buffer, int bufsz, const DomainAuthInfo *const info) { AuthRecord *r; if (info->deltime) return mStatus_NoError; for (r = mDNSStorage.ResourceRecords; r; r = r->next) { // This function is called from UpdateAutoTunnelDomainStatus which in turn may be called from // a callback e.g., CheckNATMappings. GetAuthInfoFor_internal does not like that (reentrancy being 1), // hence we inline the code here. We just need the lock to walk the list of AuthInfos which the caller // has already checked const domainname *n = r->resrec.name; while (n->c[0]) { DomainAuthInfo *ptr; for (ptr = mDNSStorage.AuthInfoList; ptr; ptr = ptr->next) if (SameDomainName(&ptr->domain, n)) { if (ptr == info && (r->updateError == mStatus_BadSig || r->updateError == mStatus_BadKey || r->updateError == mStatus_BadTime)) { mDNS_snprintf(buffer, bufsz, "Resource record update failed for %##s", r->resrec.name); return r->updateError; } } n = (const domainname *)(n->c + 1 + n->c[0]); } } return mStatus_NoError; } #endif // ndef NO_SECURITYFRAMEWORK // MUST be called with lock held mDNSlocal void UpdateAutoTunnelDomainStatus(const DomainAuthInfo *const info) { #ifdef NO_SECURITYFRAMEWORK (void)info; #else // Note that in the LLQNAT, the clientCallback being non-zero means it's in use, // whereas in the AutoTunnelNAT, the clientContext being non-zero means it's in use mDNS *const m = &mDNSStorage; const NATTraversalInfo *const llq = m->LLQNAT.clientCallback ? &m->LLQNAT : mDNSNULL; const NATTraversalInfo *const tun = m->AutoTunnelNAT.clientContext ? &m->AutoTunnelNAT : mDNSNULL; char buffer[1024]; CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef domain = NULL; CFStringRef tmp = NULL; CFNumberRef num = NULL; mStatus status = mStatus_NoError; mStatus llqStatus = mStatus_NoError; char llqBuffer[1024]; mDNS_CheckLock(m); if (!domainStatusDict) { domainStatusDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!domainStatusDict) { LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFDictionary domainStatusDict"); return; } } if (!dict) { LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFDictionary dict"); return; } mDNS_snprintf(buffer, sizeof(buffer), "%##s", info->domain.c); domain = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); if (!domain) { LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFString domain"); return; } if (info->deltime) { if (CFDictionaryContainsKey(domainStatusDict, domain)) { CFDictionaryRemoveValue(domainStatusDict, domain); if (!m->ShutdownTime) mDNSDynamicStoreSetConfig(kmDNSBackToMyMacConfig, mDNSNULL, domainStatusDict); } CFRelease(domain); CFRelease(dict); return; } mDNS_snprintf(buffer, sizeof(buffer), "%#a", &m->Router); tmp = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); if (!tmp) LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFString RouterAddress"); else { CFDictionarySetValue(dict, CFSTR("RouterAddress"), tmp); CFRelease(tmp); } if (llq) { mDNSu32 port = mDNSVal16(llq->ExternalPort); num = CFNumberCreate(NULL, kCFNumberSInt32Type, &port); if (!num) LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber LLQExternalPort"); else { CFDictionarySetValue(dict, CFSTR("LLQExternalPort"), num); CFRelease(num); } if (llq->Result) { num = CFNumberCreate(NULL, kCFNumberSInt32Type, &llq->Result); if (!num) LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber LLQNPMStatus"); else { CFDictionarySetValue(dict, CFSTR("LLQNPMStatus"), num); CFRelease(num); } } } if (tun) { mDNSu32 port = mDNSVal16(tun->ExternalPort); num = CFNumberCreate(NULL, kCFNumberSInt32Type, &port); if (!num) LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber AutoTunnelExternalPort"); else { CFDictionarySetValue(dict, CFSTR("AutoTunnelExternalPort"), num); CFRelease(num); } mDNS_snprintf(buffer, sizeof(buffer), "%.4a", &tun->ExternalAddress); tmp = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); if (!tmp) LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFString ExternalAddress"); else { CFDictionarySetValue(dict, CFSTR("ExternalAddress"), tmp); CFRelease(tmp); } if (tun->Result) { num = CFNumberCreate(NULL, kCFNumberSInt32Type, &tun->Result); if (!num) LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber AutoTunnelNPMStatus"); else { CFDictionarySetValue(dict, CFSTR("AutoTunnelNPMStatus"), num); CFRelease(num); } } } if (tun || llq) { mDNSu32 code = m->LastNATMapResultCode; num = CFNumberCreate(NULL, kCFNumberSInt32Type, &code); if (!num) LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber LastNATMapResultCode"); else { CFDictionarySetValue(dict, CFSTR("LastNATMapResultCode"), num); CFRelease(num); } } mDNS_snprintf(buffer, sizeof(buffer), "Success"); llqStatus = UpdateLLQStatus(llqBuffer, sizeof(llqBuffer), info); status = UpdateRRStatus(buffer, sizeof(buffer), info); // If we have a bad signature error updating a RR, it overrides any error as it needs to be // reported so that it can be fixed automatically (or the user needs to be notified) if (status != mStatus_NoError) { LogInfo("UpdateAutoTunnelDomainStatus: RR Status %d, %s", status, buffer); } else if (m->Router.type == mDNSAddrType_None) { status = mStatus_NoRouter; mDNS_snprintf(buffer, sizeof(buffer), "No network connection - none"); } else if (m->Router.type == mDNSAddrType_IPv4 && mDNSIPv4AddressIsZero(m->Router.ip.v4)) { status = mStatus_NoRouter; mDNS_snprintf(buffer, sizeof(buffer), "No network connection - v4 zero"); } else if (mDNSIPv6AddressIsZero(info->AutoTunnelInnerAddress)) { status = mStatus_ServiceNotRunning; mDNS_snprintf(buffer, sizeof(buffer), "No inner address"); } else if (!llq && !tun) { status = mStatus_NotInitializedErr; mDNS_snprintf(buffer, sizeof(buffer), "Neither LLQ nor AutoTunnel NAT port mapping is currently active"); } else if (llqStatus == mStatus_NoSuchRecord) { status = llqStatus; mDNS_snprintf(buffer, sizeof(buffer), "%s", llqBuffer); } else if ((llq && llq->Result == mStatus_DoubleNAT) || (tun && tun->Result == mStatus_DoubleNAT)) { status = mStatus_DoubleNAT; mDNS_snprintf(buffer, sizeof(buffer), "Double NAT: Router is reporting a private address"); } else if ((llq && llq->Result == mStatus_NATPortMappingDisabled) || (tun && tun->Result == mStatus_NATPortMappingDisabled) || (m->LastNATMapResultCode == NATErr_Refused && ((llq && !llq->Result && mDNSIPPortIsZero(llq->ExternalPort)) || (tun && !tun->Result && mDNSIPPortIsZero(tun->ExternalPort))))) { status = mStatus_NATPortMappingDisabled; mDNS_snprintf(buffer, sizeof(buffer), "PCP/NAT-PMP is disabled on the router"); } else if ((llq && llq->Result) || (tun && tun->Result)) { status = mStatus_NATTraversal; mDNS_snprintf(buffer, sizeof(buffer), "Error obtaining NAT port mapping from router"); } else if ((llq && mDNSIPPortIsZero(llq->ExternalPort)) || (tun && mDNSIPPortIsZero(tun->ExternalPort))) { status = mStatus_NATTraversal; mDNS_snprintf(buffer, sizeof(buffer), "Unable to obtain NAT port mapping from router"); } else { status = llqStatus; mDNS_snprintf(buffer, sizeof(buffer), "%s", llqBuffer); LogInfo("UpdateAutoTunnelDomainStatus: LLQ Status %d, %s", status, buffer); } num = CFNumberCreate(NULL, kCFNumberSInt32Type, &status); if (!num) LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber StatusCode"); else { CFDictionarySetValue(dict, CFSTR("StatusCode"), num); CFRelease(num); } tmp = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); if (!tmp) LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFString StatusMessage"); else { CFDictionarySetValue(dict, CFSTR("StatusMessage"), tmp); CFRelease(tmp); } if (!CFDictionaryContainsKey(domainStatusDict, domain) || !CFEqual(dict, (CFMutableDictionaryRef)CFDictionaryGetValue(domainStatusDict, domain))) { CFDictionarySetValue(domainStatusDict, domain, dict); if (!m->ShutdownTime) { LogInfo("UpdateAutoTunnelDomainStatus: %s status %d", status ? "failure" : "success", status); mDNSDynamicStoreSetConfig(kmDNSBackToMyMacConfig, mDNSNULL, domainStatusDict); } } CFRelease(domain); CFRelease(dict); debugf("UpdateAutoTunnelDomainStatus: %s", buffer); #endif // def NO_SECURITYFRAMEWORK } // MUST be called with lock held mDNSexport void UpdateAutoTunnelDomainStatuses(const mDNS *const m) { #ifdef NO_SECURITYFRAMEWORK (void) m; #else mDNS_CheckLock(m); DomainAuthInfo* info; for (info = m->AuthInfoList; info; info = info->next) if (info->AutoTunnel && !info->deltime) UpdateAutoTunnelDomainStatus(info); #endif // def NO_SECURITYFRAMEWORK } mDNSlocal void UpdateAnonymousRacoonConfig(mDNS *m) // Determine whether we need racoon to accept incoming connections { DomainAuthInfo *info; for (info = m->AuthInfoList; info; info = info->next) if (info->AutoTunnel && !info->deltime && (!mDNSIPPortIsZero(m->AutoTunnelNAT.ExternalPort) || !mDNSIPv6AddressIsZero(m->AutoTunnelRelayAddr))) break; if (info != AnonymousRacoonConfig) { AnonymousRacoonConfig = info; LogInfo("UpdateAnonymousRacoonConfig need not be done in mDNSResponder"); } } mDNSlocal void AutoTunnelRecordCallback(mDNS *const m, AuthRecord *const rr, mStatus result); // Caller must hold the lock mDNSlocal mDNSBool DeregisterAutoTunnelRecord(mDNS *m, DomainAuthInfo *info, AuthRecord* record) { mDNS_CheckLock(m); LogInfo("DeregisterAutoTunnelRecord %##s %##s", &info->domain.c, record->namestorage.c); if (record->resrec.RecordType > kDNSRecordTypeDeregistering) { mStatus err = mDNS_Deregister_internal(m, record, mDNS_Dereg_normal); if (err) { record->resrec.RecordType = kDNSRecordTypeUnregistered; LogMsg("DeregisterAutoTunnelRecord error %d deregistering %##s %##s", err, info->domain.c, record->namestorage.c); return mDNSfalse; } else LogInfo("DeregisterAutoTunnelRecord: Deregistered"); } else LogInfo("DeregisterAutoTunnelRecord: Not deregistering, state:%d", record->resrec.RecordType); return mDNStrue; } // Caller must hold the lock mDNSlocal void DeregisterAutoTunnelHostRecord(mDNS *m, DomainAuthInfo *info) { if (!DeregisterAutoTunnelRecord(m, info, &info->AutoTunnelHostRecord)) { info->AutoTunnelHostRecord.namestorage.c[0] = 0; m->NextSRVUpdate = NonZeroTime(m->timenow); } } // Caller must hold the lock mDNSlocal void UpdateAutoTunnelHostRecord(mDNS *m, DomainAuthInfo *info) { mStatus err; mDNSBool NATProblem = mDNSIPPortIsZero(m->AutoTunnelNAT.ExternalPort) || m->AutoTunnelNAT.Result; mDNS_CheckLock(m); if (!info->AutoTunnelServiceStarted || info->deltime || m->ShutdownTime || mDNSIPv6AddressIsZero(info->AutoTunnelInnerAddress) || (m->SleepState != SleepState_Awake && NATProblem)) { LogInfo("UpdateAutoTunnelHostRecord: Dereg %##s : AutoTunnelServiceStarted(%d) deltime(%d) address(%.16a) sleepstate(%d)", info->domain.c, info->AutoTunnelServiceStarted, info->deltime, &info->AutoTunnelInnerAddress, m->SleepState); DeregisterAutoTunnelHostRecord(m, info); } else if (info->AutoTunnelHostRecord.resrec.RecordType == kDNSRecordTypeUnregistered) { mDNS_SetupResourceRecord(&info->AutoTunnelHostRecord, mDNSNULL, mDNSInterface_Any, kDNSType_AAAA, kHostNameTTL, kDNSRecordTypeUnregistered, AuthRecordAny, AutoTunnelRecordCallback, info); info->AutoTunnelHostRecord.namestorage.c[0] = 0; AppendDomainLabel(&info->AutoTunnelHostRecord.namestorage, &m->hostlabel); AppendDomainName (&info->AutoTunnelHostRecord.namestorage, &info->domain); info->AutoTunnelHostRecord.resrec.rdata->u.ipv6 = info->AutoTunnelInnerAddress; info->AutoTunnelHostRecord.resrec.RecordType = kDNSRecordTypeKnownUnique; err = mDNS_Register_internal(m, &info->AutoTunnelHostRecord); if (err) LogMsg("UpdateAutoTunnelHostRecord error %d registering %##s", err, info->AutoTunnelHostRecord.namestorage.c); else { // Make sure we trigger the registration of all SRV records in regState_NoTarget again m->NextSRVUpdate = NonZeroTime(m->timenow); LogInfo("UpdateAutoTunnelHostRecord registering %##s", info->AutoTunnelHostRecord.namestorage.c); } } else LogInfo("UpdateAutoTunnelHostRecord: Type %d", info->AutoTunnelHostRecord.resrec.RecordType); } // Caller must hold the lock mDNSlocal void DeregisterAutoTunnelServiceRecords(mDNS *m, DomainAuthInfo *info) { LogInfo("DeregisterAutoTunnelServiceRecords %##s", info->domain.c); DeregisterAutoTunnelRecord(m, info, &info->AutoTunnelTarget); DeregisterAutoTunnelRecord(m, info, &info->AutoTunnelService); UpdateAutoTunnelHostRecord(m, info); } // Caller must hold the lock mDNSlocal void UpdateAutoTunnelServiceRecords(mDNS *m, DomainAuthInfo *info) { mDNS_CheckLock(m); if (!info->AutoTunnelServiceStarted || info->deltime || m->ShutdownTime || mDNSIPPortIsZero(m->AutoTunnelNAT.ExternalPort) || m->AutoTunnelNAT.Result) { LogInfo("UpdateAutoTunnelServiceRecords: Dereg %##s : AutoTunnelServiceStarted(%d) deltime(%d) ExtPort(%d) NATResult(%d)", info->domain.c, info->AutoTunnelServiceStarted, info->deltime, mDNSVal16(m->AutoTunnelNAT.ExternalPort), m->AutoTunnelNAT.Result); DeregisterAutoTunnelServiceRecords(m, info); } else { if (info->AutoTunnelTarget.resrec.RecordType == kDNSRecordTypeUnregistered) { // 1. Set up our address record for the external tunnel address // (Constructed name, not generally user-visible, used as target in IKE tunnel's SRV record) mDNS_SetupResourceRecord(&info->AutoTunnelTarget, mDNSNULL, mDNSInterface_Any, kDNSType_A, kHostNameTTL, kDNSRecordTypeUnregistered, AuthRecordAny, AutoTunnelRecordCallback, info); AssignDomainName (&info->AutoTunnelTarget.namestorage, (const domainname*) "\x0B" "_autotunnel"); AppendDomainLabel(&info->AutoTunnelTarget.namestorage, &m->hostlabel); AppendDomainName (&info->AutoTunnelTarget.namestorage, &info->domain); info->AutoTunnelTarget.resrec.rdata->u.ipv4 = m->AutoTunnelNAT.ExternalAddress; info->AutoTunnelTarget.resrec.RecordType = kDNSRecordTypeKnownUnique; mStatus err = mDNS_Register_internal(m, &info->AutoTunnelTarget); if (err) LogMsg("UpdateAutoTunnelServiceRecords error %d registering %##s", err, info->AutoTunnelTarget.namestorage.c); else LogInfo("UpdateAutoTunnelServiceRecords registering %##s", info->AutoTunnelTarget.namestorage.c); } else LogInfo("UpdateAutoTunnelServiceRecords: NOOP Target state(%d)", info->AutoTunnelTarget.resrec.RecordType); if (info->AutoTunnelService.resrec.RecordType == kDNSRecordTypeUnregistered) { // 2. Set up IKE tunnel's SRV record: _autotunnel._udp.AutoTunnelHost SRV 0 0 port AutoTunnelTarget mDNS_SetupResourceRecord(&info->AutoTunnelService, mDNSNULL, mDNSInterface_Any, kDNSType_SRV, kHostNameTTL, kDNSRecordTypeUnregistered, AuthRecordAny, AutoTunnelRecordCallback, info); AssignDomainName (&info->AutoTunnelService.namestorage, (const domainname*) "\x0B" "_autotunnel" "\x04" "_udp"); AppendDomainLabel(&info->AutoTunnelService.namestorage, &m->hostlabel); AppendDomainName (&info->AutoTunnelService.namestorage, &info->domain); info->AutoTunnelService.resrec.rdata->u.srv.priority = 0; info->AutoTunnelService.resrec.rdata->u.srv.weight = 0; info->AutoTunnelService.resrec.rdata->u.srv.port = m->AutoTunnelNAT.ExternalPort; AssignDomainName(&info->AutoTunnelService.resrec.rdata->u.srv.target, &info->AutoTunnelTarget.namestorage); info->AutoTunnelService.resrec.RecordType = kDNSRecordTypeKnownUnique; mStatus err = mDNS_Register_internal(m, &info->AutoTunnelService); if (err) LogMsg("UpdateAutoTunnelServiceRecords error %d registering %##s", err, info->AutoTunnelService.namestorage.c); else LogInfo("UpdateAutoTunnelServiceRecords registering %##s", info->AutoTunnelService.namestorage.c); } else LogInfo("UpdateAutoTunnelServiceRecords: NOOP Service state(%d)", info->AutoTunnelService.resrec.RecordType); UpdateAutoTunnelHostRecord(m, info); LogInfo("AutoTunnel server listening for connections on %##s[%.4a]:%d:%##s[%.16a]", info->AutoTunnelTarget.namestorage.c, &m->AdvertisedV4.ip.v4, mDNSVal16(m->AutoTunnelNAT.IntPort), info->AutoTunnelHostRecord.namestorage.c, &info->AutoTunnelInnerAddress); } } // Caller must hold the lock mDNSlocal void DeregisterAutoTunnelDeviceInfoRecord(mDNS *m, DomainAuthInfo *info) { DeregisterAutoTunnelRecord(m, info, &info->AutoTunnelDeviceInfo); } // Caller must hold the lock mDNSlocal void UpdateAutoTunnelDeviceInfoRecord(mDNS *m, DomainAuthInfo *info) { mDNS_CheckLock(m); if (!info->AutoTunnelServiceStarted || info->deltime || m->ShutdownTime) DeregisterAutoTunnelDeviceInfoRecord(m, info); else if (info->AutoTunnelDeviceInfo.resrec.RecordType == kDNSRecordTypeUnregistered) { mDNS_SetupResourceRecord(&info->AutoTunnelDeviceInfo, mDNSNULL, mDNSInterface_Any, kDNSType_TXT, kStandardTTL, kDNSRecordTypeUnregistered, AuthRecordAny, AutoTunnelRecordCallback, info); ConstructServiceName(&info->AutoTunnelDeviceInfo.namestorage, &m->nicelabel, &DeviceInfoName, &info->domain); info->AutoTunnelDeviceInfo.resrec.rdlength = initializeDeviceInfoTXT(m, info->AutoTunnelDeviceInfo.resrec.rdata->u.data); info->AutoTunnelDeviceInfo.resrec.RecordType = kDNSRecordTypeKnownUnique; mStatus err = mDNS_Register_internal(m, &info->AutoTunnelDeviceInfo); if (err) LogMsg("UpdateAutoTunnelDeviceInfoRecord error %d registering %##s", err, info->AutoTunnelDeviceInfo.namestorage.c); else LogInfo("UpdateAutoTunnelDeviceInfoRecord registering %##s", info->AutoTunnelDeviceInfo.namestorage.c); } else LogInfo("UpdateAutoTunnelDeviceInfoRecord: not in Unregistered state: %d",info->AutoTunnelDeviceInfo.resrec.RecordType); } // Caller must hold the lock mDNSlocal void DeregisterAutoTunnel6Record(mDNS *m, DomainAuthInfo *info) { LogInfo("DeregisterAutoTunnel6Record %##s", info->domain.c); DeregisterAutoTunnelRecord(m, info, &info->AutoTunnel6Record); UpdateAutoTunnelHostRecord(m, info); UpdateAutoTunnelDomainStatus(info); } // Caller must hold the lock mDNSlocal void UpdateAutoTunnel6Record(mDNS *m, DomainAuthInfo *info) { mDNS_CheckLock(m); if (!info->AutoTunnelServiceStarted || info->deltime || m->ShutdownTime || mDNSIPv6AddressIsZero(m->AutoTunnelRelayAddr) || m->SleepState != SleepState_Awake) DeregisterAutoTunnel6Record(m, info); else if (info->AutoTunnel6Record.resrec.RecordType == kDNSRecordTypeUnregistered) { mDNS_SetupResourceRecord(&info->AutoTunnel6Record, mDNSNULL, mDNSInterface_Any, kDNSType_AAAA, kHostNameTTL, kDNSRecordTypeUnregistered, AuthRecordAny, AutoTunnelRecordCallback, info); AssignDomainName (&info->AutoTunnel6Record.namestorage, (const domainname*) "\x0C" "_autotunnel6"); AppendDomainLabel(&info->AutoTunnel6Record.namestorage, &m->hostlabel); AppendDomainName (&info->AutoTunnel6Record.namestorage, &info->domain); info->AutoTunnel6Record.resrec.rdata->u.ipv6 = m->AutoTunnelRelayAddr; info->AutoTunnel6Record.resrec.RecordType = kDNSRecordTypeKnownUnique; mStatus err = mDNS_Register_internal(m, &info->AutoTunnel6Record); if (err) LogMsg("UpdateAutoTunnel6Record error %d registering %##s", err, info->AutoTunnel6Record.namestorage.c); else LogInfo("UpdateAutoTunnel6Record registering %##s", info->AutoTunnel6Record.namestorage.c); UpdateAutoTunnelHostRecord(m, info); LogInfo("AutoTunnel6 server listening for connections on %##s[%.16a] :%##s[%.16a]", info->AutoTunnel6Record.namestorage.c, &m->AutoTunnelRelayAddr, info->AutoTunnelHostRecord.namestorage.c, &info->AutoTunnelInnerAddress); } else LogInfo("UpdateAutoTunnel6Record NOOP state(%d)",info->AutoTunnel6Record.resrec.RecordType); } mDNSlocal void AutoTunnelRecordCallback(mDNS *const m, AuthRecord *const rr, mStatus result) { DomainAuthInfo *info = (DomainAuthInfo *)rr->RecordContext; if (result == mStatus_MemFree) { LogInfo("AutoTunnelRecordCallback MemFree %s", ARDisplayString(m, rr)); mDNS_Lock(m); // Reset the host record namestorage to force high-level PTR/SRV/TXT to deregister if (rr == &info->AutoTunnelHostRecord) { rr->namestorage.c[0] = 0; m->NextSRVUpdate = NonZeroTime(m->timenow); LogInfo("AutoTunnelRecordCallback: NextSRVUpdate in %d %d", m->NextSRVUpdate - m->timenow, m->timenow); } if (m->ShutdownTime) { LogInfo("AutoTunnelRecordCallback: Shutdown, returning"); mDNS_Unlock(m); return; } if (rr == &info->AutoTunnelHostRecord) { LogInfo("AutoTunnelRecordCallback: calling UpdateAutoTunnelHostRecord"); UpdateAutoTunnelHostRecord(m,info); } else if (rr == &info->AutoTunnelDeviceInfo) { LogInfo("AutoTunnelRecordCallback: Calling UpdateAutoTunnelDeviceInfoRecord"); UpdateAutoTunnelDeviceInfoRecord(m,info); } else if (rr == &info->AutoTunnelService || rr == &info->AutoTunnelTarget) { LogInfo("AutoTunnelRecordCallback: Calling UpdateAutoTunnelServiceRecords"); UpdateAutoTunnelServiceRecords(m,info); } else if (rr == &info->AutoTunnel6Record) { LogInfo("AutoTunnelRecordCallback: Calling UpdateAutoTunnel6Record"); UpdateAutoTunnel6Record(m,info); } mDNS_Unlock(m); } } mDNSlocal void AutoTunnelNATCallback(mDNS *m, NATTraversalInfo *n) { DomainAuthInfo *info; LogInfo("AutoTunnelNATCallback Result %d %.4a Internal %d External %d", n->Result, &n->ExternalAddress, mDNSVal16(n->IntPort), mDNSVal16(n->ExternalPort)); mDNS_Lock(m); m->NextSRVUpdate = NonZeroTime(m->timenow); LogInfo("AutoTunnelNATCallback: NextSRVUpdate in %d %d", m->NextSRVUpdate - m->timenow, m->timenow); for (info = m->AuthInfoList; info; info = info->next) if (info->AutoTunnel) UpdateAutoTunnelServiceRecords(m, info); UpdateAnonymousRacoonConfig(m); // Determine whether we need racoon to accept incoming connections UpdateAutoTunnelDomainStatuses(m); mDNS_Unlock(m); } mDNSlocal void AutoTunnelHostNameChanged(mDNS *m, DomainAuthInfo *info) { LogInfo("AutoTunnelHostNameChanged %#s.%##s", m->hostlabel.c, info->domain.c); mDNS_Lock(m); // We forcibly deregister the records that are based on the hostname. // When deregistration of each completes, the MemFree callback will make the // appropriate Update* call to use the new name to reregister. DeregisterAutoTunnelHostRecord(m, info); DeregisterAutoTunnelDeviceInfoRecord(m, info); DeregisterAutoTunnelServiceRecords(m, info); DeregisterAutoTunnel6Record(m, info); m->NextSRVUpdate = NonZeroTime(m->timenow); mDNS_Unlock(m); } // Must be called with the lock held mDNSexport void StartServerTunnel(DomainAuthInfo *const info) { mDNS *const m = &mDNSStorage; if (info->deltime) return; if (info->AutoTunnelServiceStarted) { // On wake from sleep, this function will be called when determining SRV targets, // and needs to re-register the host record for the target to be set correctly UpdateAutoTunnelHostRecord(m, info); return; } info->AutoTunnelServiceStarted = mDNStrue; // Now that we have a service in this domain, we need to try to register the // AutoTunnel records, because the relay connection & NAT-T may have already been // started for another domain. If the relay connection is not up or the NAT-T has not // yet succeeded, the Update* functions are smart enough to not register the records. // Note: This should be done after we set AutoTunnelServiceStarted, as that variable is used to // decide whether to register the AutoTunnel records in the calls below. UpdateAutoTunnelServiceRecords(m, info); UpdateAutoTunnel6Record(m, info); UpdateAutoTunnelDeviceInfoRecord(m, info); UpdateAutoTunnelHostRecord(m, info); // If the global AutoTunnel NAT-T is not yet started, start it. if (!m->AutoTunnelNAT.clientContext) { m->AutoTunnelNAT.clientCallback = AutoTunnelNATCallback; m->AutoTunnelNAT.clientContext = (void*)1; // Means AutoTunnelNAT Traversal is active; m->AutoTunnelNAT.Protocol = NATOp_MapUDP; m->AutoTunnelNAT.IntPort = IPSECPort; m->AutoTunnelNAT.RequestedPort = IPSECPort; m->AutoTunnelNAT.NATLease = 0; mStatus err = mDNS_StartNATOperation_internal(m, &m->AutoTunnelNAT); if (err) LogMsg("StartServerTunnel: error %d starting NAT mapping", err); } } mDNSlocal mStatus AutoTunnelSetKeys(ClientTunnel *tun, mDNSBool AddNew) { mDNSv6Addr loc_outer6; mDNSv6Addr rmt_outer6; // When we are tunneling over IPv6 Relay address, the port number is zero if (mDNSIPPortIsZero(tun->rmt_outer_port)) { loc_outer6 = tun->loc_outer6; rmt_outer6 = tun->rmt_outer6; } else { loc_outer6 = zerov6Addr; loc_outer6.b[0] = tun->loc_outer.b[0]; loc_outer6.b[1] = tun->loc_outer.b[1]; loc_outer6.b[2] = tun->loc_outer.b[2]; loc_outer6.b[3] = tun->loc_outer.b[3]; rmt_outer6 = zerov6Addr; rmt_outer6.b[0] = tun->rmt_outer.b[0]; rmt_outer6.b[1] = tun->rmt_outer.b[1]; rmt_outer6.b[2] = tun->rmt_outer.b[2]; rmt_outer6.b[3] = tun->rmt_outer.b[3]; } return(mDNSAutoTunnelSetKeys(AddNew ? kmDNSAutoTunnelSetKeysReplace : kmDNSAutoTunnelSetKeysDelete, tun->loc_inner.b, loc_outer6.b, kRacoonPort, tun->rmt_inner.b, rmt_outer6.b, mDNSVal16(tun->rmt_outer_port), btmmprefix, SkipLeadingLabels(&tun->dstname, 1))); } // If the EUI-64 part of the IPv6 ULA matches, then that means the two addresses point to the same machine #define mDNSSameClientTunnel(A,B) ((A)->l[2] == (B)->l[2] && (A)->l[3] == (B)->l[3]) mDNSlocal void ReissueBlockedQuestionWithType(domainname *d, mDNSBool success, mDNSu16 qtype) { mDNS *const m = &mDNSStorage; DNSQuestion *q = m->Questions; while (q) { if (q->NoAnswer == NoAnswer_Suspended && q->qtype == qtype && q->AuthInfo && q->AuthInfo->AutoTunnel && SameDomainName(&q->qname, d)) { LogInfo("Restart %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); mDNSQuestionCallback *tmp = q->QuestionCallback; q->QuestionCallback = AutoTunnelCallback; // Set QuestionCallback to suppress another call back to AddNewClientTunnel mDNS_StopQuery(m, q); mDNS_StartQuery(m, q); q->QuestionCallback = tmp; // Restore QuestionCallback back to the real value if (!success) q->NoAnswer = NoAnswer_Fail; // When we call mDNS_StopQuery, it's possible for other subordinate questions like the GetZoneData query to be cancelled too. // In general we have to assume that the question list might have changed in arbitrary ways. // This code is itself called from a question callback, so the m->CurrentQuestion mechanism is // already in use. The safest solution is just to go back to the start of the list and start again. // In principle this sounds like an n^2 algorithm, but in practice we almost always activate // just one suspended question, so it's really a 2n algorithm. q = m->Questions; } else q = q->next; } } mDNSlocal void ReissueBlockedQuestions(domainname *d, mDNSBool success) { // 1. We deliberately restart AAAA queries before A queries, because in the common case where a BTTM host has // a v6 address but no v4 address, we prefer the caller to get the positive AAAA response before the A NXDOMAIN. // 2. In the case of AAAA queries, if our tunnel setup failed, then we return a deliberate failure indication to the caller -- // even if the name does have a valid AAAA record, we don't want clients trying to connect to it without a properly encrypted tunnel. // 3. For A queries we never fabricate failures -- if a BTTM service is really using raw IPv4, then it doesn't need the IPv6 tunnel. ReissueBlockedQuestionWithType(d, success, kDNSType_AAAA); ReissueBlockedQuestionWithType(d, mDNStrue, kDNSType_A); } mDNSlocal void UnlinkAndReissueBlockedQuestions(ClientTunnel *tun, mDNSBool success) { mDNS *const m = &mDNSStorage; ClientTunnel **p = &m->TunnelClients; while (*p != tun && *p) p = &(*p)->next; if (*p) *p = tun->next; ReissueBlockedQuestions(&tun->dstname, success); LogInfo("UnlinkAndReissueBlockedQuestions: Disposing ClientTunnel %p", tun); freeL("ClientTunnel", tun); } mDNSlocal mDNSBool TunnelClientDeleteMatching(ClientTunnel *tun, mDNSBool v6Tunnel) { mDNS *const m = &mDNSStorage; ClientTunnel **p; mDNSBool needSetKeys = mDNStrue; p = &tun->next; while (*p) { // Is this a tunnel to the same host that we are trying to setup now? if (!mDNSSameClientTunnel(&(*p)->rmt_inner, &tun->rmt_inner)) p = &(*p)->next; else { ClientTunnel *old = *p; if (v6Tunnel) { if (!mDNSIPPortIsZero(old->rmt_outer_port)) { p = &old->next; continue; } LogInfo("TunnelClientDeleteMatching: Found existing IPv6 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); if (old->q.ThisQInterval >= 0) { LogInfo("TunnelClientDeleteMatching: Stopping query on IPv6 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); mDNS_StopQuery(m, &old->q); } else if (!mDNSSameIPv6Address((*p)->rmt_inner, tun->rmt_inner) || !mDNSSameIPv6Address(old->loc_inner, tun->loc_inner) || !mDNSSameIPv6Address(old->loc_outer6, tun->loc_outer6) || !mDNSSameIPv6Address(old->rmt_outer6, tun->rmt_outer6)) { // Delete the old tunnel if the current tunnel to the same host does not have the same ULA or // the other parameters of the tunnel are different LogInfo("TunnelClientDeleteMatching: Deleting existing IPv6 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); AutoTunnelSetKeys(old, mDNSfalse); } else { // Reusing the existing tunnel means that we reuse the IPsec SAs and the policies. We delete the old // as "tun" and "old" are identical LogInfo("TunnelClientDeleteMatching: Reusing the existing IPv6 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); needSetKeys = mDNSfalse; } } else { if (mDNSIPPortIsZero(old->rmt_outer_port)) { p = &old->next; continue; } LogInfo("TunnelClientDeleteMatching: Found existing IPv4 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); if (old->q.ThisQInterval >= 0) { LogInfo("TunnelClientDeleteMatching: Stopping query on IPv4 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); mDNS_StopQuery(m, &old->q); } else if (!mDNSSameIPv6Address((*p)->rmt_inner, tun->rmt_inner) || !mDNSSameIPv6Address(old->loc_inner, tun->loc_inner) || !mDNSSameIPv4Address(old->loc_outer, tun->loc_outer) || !mDNSSameIPv4Address(old->rmt_outer, tun->rmt_outer) || !mDNSSameIPPort(old->rmt_outer_port, tun->rmt_outer_port)) { // Delete the old tunnel if the current tunnel to the same host does not have the same ULA or // the other parameters of the tunnel are different LogInfo("TunnelClientDeleteMatching: Deleting existing IPv4 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); AutoTunnelSetKeys(old, mDNSfalse); } else { // Reusing the existing tunnel means that we reuse the IPsec SAs and the policies. We delete the old // as "tun" and "old" are identical LogInfo("TunnelClientDeleteMatching: Reusing the existing IPv4 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); needSetKeys = mDNSfalse; } } *p = old->next; LogInfo("TunnelClientDeleteMatching: Disposing ClientTunnel %p", old); freeL("ClientTunnel", old); } } return needSetKeys; } // v6Tunnel indicates whether to delete a tunnel whose outer header is IPv6. If false, outer IPv4 // tunnel will be deleted mDNSlocal void TunnelClientDeleteAny(ClientTunnel *tun, mDNSBool v6Tunnel) { ClientTunnel **p; p = &tun->next; while (*p) { // If there is more than one client tunnel to the same host, delete all of them. // We do this by just checking against the EUI64 rather than the full address if (!mDNSSameClientTunnel(&(*p)->rmt_inner, &tun->rmt_inner)) p = &(*p)->next; else { ClientTunnel *old = *p; if (v6Tunnel) { if (!mDNSIPPortIsZero(old->rmt_outer_port)) { p = &old->next; continue;} LogInfo("TunnelClientDeleteAny: Found existing IPv6 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); } else { if (mDNSIPPortIsZero(old->rmt_outer_port)) { p = &old->next; continue;} LogInfo("TunnelClientDeleteAny: Found existing IPv4 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); } if (old->q.ThisQInterval >= 0) { LogInfo("TunnelClientDeleteAny: Stopping query on AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); mDNS_StopQuery(&mDNSStorage, &old->q); } else { LogInfo("TunnelClientDeleteAny: Deleting existing AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner); AutoTunnelSetKeys(old, mDNSfalse); } *p = old->next; LogInfo("TunnelClientDeleteAny: Disposing ClientTunnel %p", old); freeL("ClientTunnel", old); } } } mDNSlocal void TunnelClientFinish(DNSQuestion *question, const ResourceRecord *const answer) { mDNS *const m = &mDNSStorage; mDNSBool needSetKeys = mDNStrue; ClientTunnel *tun = (ClientTunnel *)question->QuestionContext; mDNSBool v6Tunnel = mDNSfalse; DomainAuthInfo *info; // If the port is zero, then we have a relay address of the peer if (mDNSIPPortIsZero(tun->rmt_outer_port)) v6Tunnel = mDNStrue; if (v6Tunnel) { LogInfo("TunnelClientFinish: Relay address %.16a", &answer->rdata->u.ipv6); tun->rmt_outer6 = answer->rdata->u.ipv6; tun->loc_outer6 = m->AutoTunnelRelayAddr; } else { LogInfo("TunnelClientFinish: SRV target address %.4a", &answer->rdata->u.ipv4); tun->rmt_outer = answer->rdata->u.ipv4; mDNSAddr tmpDst = { mDNSAddrType_IPv4, {{{0}}} }; tmpDst.ip.v4 = tun->rmt_outer; mDNSAddr tmpSrc = zeroAddr; mDNSPlatformSourceAddrForDest(&tmpSrc, &tmpDst); if (tmpSrc.type == mDNSAddrType_IPv4) tun->loc_outer = tmpSrc.ip.v4; else tun->loc_outer = m->AdvertisedV4.ip.v4; } question->ThisQInterval = -1; // So we know this tunnel setup has completed info = GetAuthInfoForName(m, &tun->dstname); if (!info) { LogMsg("TunnelClientFinish: Could not get AuthInfo for %##s", tun->dstname.c); ReissueBlockedQuestions(&tun->dstname, mDNSfalse); return; } tun->loc_inner = info->AutoTunnelInnerAddress; // If we found a v6Relay address for our peer, delete all the v4Tunnels for our peer and // look for existing tunnels to see whether they have the same information for our peer. // If not, delete them and need to create a new tunnel. If they are same, just use the // same tunnel. Do the similar thing if we found a v4Tunnel end point for our peer. TunnelClientDeleteAny(tun, !v6Tunnel); needSetKeys = TunnelClientDeleteMatching(tun, v6Tunnel); if (needSetKeys) LogInfo("TunnelClientFinish: New %s AutoTunnel for %##s %.16a", (v6Tunnel ? "IPv6" : "IPv4"), tun->dstname.c, &tun->rmt_inner); else LogInfo("TunnelClientFinish: Reusing exiting %s AutoTunnel for %##s %.16a", (v6Tunnel ? "IPv6" : "IPv4"), tun->dstname.c, &tun->rmt_inner); mStatus result = needSetKeys ? AutoTunnelSetKeys(tun, mDNStrue) : mStatus_NoError; LogInfo("TunnelClientFinish: Tunnel setup result %d", result); // Kick off any questions that were held pending this tunnel setup ReissueBlockedQuestions(&tun->dstname, (result == mStatus_NoError) ? mDNStrue : mDNSfalse); } mDNSexport void AutoTunnelCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) { ClientTunnel *tun = (ClientTunnel *)question->QuestionContext; DomainAuthInfo *info; LogInfo("AutoTunnelCallback tun %p AddRecord %d rdlength %d qtype %d", tun, AddRecord, answer->rdlength, question->qtype); if (!AddRecord) return; mDNS_StopQuery(m, question); // If we are looking up the AAAA record for _autotunnel6, don't consider it as failure. // The code below will look for _autotunnel._udp SRV record followed by A record if (tun->tc_state != TC_STATE_AAAA_PEER_RELAY && !answer->rdlength) { LogInfo("AutoTunnelCallback NXDOMAIN %##s (%s)", question->qname.c, DNSTypeName(question->qtype)); UnlinkAndReissueBlockedQuestions(tun, mDNSfalse); return; } switch (tun->tc_state) { case TC_STATE_AAAA_PEER: if (question->qtype != kDNSType_AAAA) { LogMsg("AutoTunnelCallback: Bad question type %d in TC_STATE_AAAA_PEER", question->qtype); } info = GetAuthInfoForName(m, &tun->dstname); if (!info) { LogMsg("AutoTunnelCallback: Could not get AuthInfo for %##s", tun->dstname.c); UnlinkAndReissueBlockedQuestions(tun, mDNStrue); return; } if (mDNSSameIPv6Address(answer->rdata->u.ipv6, info->AutoTunnelInnerAddress)) { LogInfo("AutoTunnelCallback: suppressing tunnel to self %.16a", &answer->rdata->u.ipv6); UnlinkAndReissueBlockedQuestions(tun, mDNStrue); return; } if (info && mDNSSameIPv6NetworkPart(answer->rdata->u.ipv6, info->AutoTunnelInnerAddress)) { LogInfo("AutoTunnelCallback: suppressing tunnel to peer %.16a", &answer->rdata->u.ipv6); UnlinkAndReissueBlockedQuestions(tun, mDNStrue); return; } tun->rmt_inner = answer->rdata->u.ipv6; LogInfo("AutoTunnelCallback:TC_STATE_AAAA_PEER: dst host %.16a", &tun->rmt_inner); if (!mDNSIPv6AddressIsZero(m->AutoTunnelRelayAddr)) { LogInfo("AutoTunnelCallback: Looking up _autotunnel6 AAAA"); tun->tc_state = TC_STATE_AAAA_PEER_RELAY; question->qtype = kDNSType_AAAA; AssignDomainName(&question->qname, (const domainname*) "\x0C" "_autotunnel6"); } else { LogInfo("AutoTunnelCallback: Looking up _autotunnel._udp SRV"); tun->tc_state = TC_STATE_SRV_PEER; question->qtype = kDNSType_SRV; AssignDomainName(&question->qname, (const domainname*) "\x0B" "_autotunnel" "\x04" "_udp"); } AppendDomainName(&question->qname, &tun->dstname); mDNS_StartQuery(m, &tun->q); return; case TC_STATE_AAAA_PEER_RELAY: if (question->qtype != kDNSType_AAAA) { LogMsg("AutoTunnelCallback: Bad question type %d in TC_STATE_AAAA_PEER_RELAY", question->qtype); } // If it failed, look for the SRV record. if (!answer->rdlength) { LogInfo("AutoTunnelCallback: Looking up _autotunnel6 AAAA failed, trying SRV"); tun->tc_state = TC_STATE_SRV_PEER; AssignDomainName(&question->qname, (const domainname*) "\x0B" "_autotunnel" "\x04" "_udp"); AppendDomainName(&question->qname, &tun->dstname); question->qtype = kDNSType_SRV; mDNS_StartQuery(m, &tun->q); return; } TunnelClientFinish(question, answer); return; case TC_STATE_SRV_PEER: if (question->qtype != kDNSType_SRV) { LogMsg("AutoTunnelCallback: Bad question type %d in TC_STATE_SRV_PEER", question->qtype); } LogInfo("AutoTunnelCallback: SRV target name %##s", answer->rdata->u.srv.target.c); tun->tc_state = TC_STATE_ADDR_PEER; AssignDomainName(&tun->q.qname, &answer->rdata->u.srv.target); tun->rmt_outer_port = answer->rdata->u.srv.port; question->qtype = kDNSType_A; mDNS_StartQuery(m, &tun->q); return; case TC_STATE_ADDR_PEER: if (question->qtype != kDNSType_A) { LogMsg("AutoTunnelCallback: Bad question type %d in TC_STATE_ADDR_PEER", question->qtype); } TunnelClientFinish(question, answer); return; default: LogMsg("AutoTunnelCallback: Unknown question %p", question); } } // Must be called with the lock held mDNSexport void AddNewClientTunnel(DNSQuestion *const q) { mDNS *const m = &mDNSStorage; ClientTunnel *p = mallocL("ClientTunnel", sizeof(ClientTunnel)); if (!p) return; AssignDomainName(&p->dstname, &q->qname); p->MarkedForDeletion = mDNSfalse; p->loc_inner = zerov6Addr; p->loc_outer = zerov4Addr; p->loc_outer6 = zerov6Addr; p->rmt_inner = zerov6Addr; p->rmt_outer = zerov4Addr; p->rmt_outer6 = zerov6Addr; p->rmt_outer_port = zeroIPPort; p->tc_state = TC_STATE_AAAA_PEER; p->next = m->TunnelClients; m->TunnelClients = p; // We intentionally build list in reverse order p->q.InterfaceID = mDNSInterface_Any; p->q.flags = 0; p->q.Target = zeroAddr; AssignDomainName(&p->q.qname, &q->qname); p->q.qtype = kDNSType_AAAA; p->q.qclass = kDNSClass_IN; p->q.LongLived = mDNSfalse; p->q.ExpectUnique = mDNStrue; p->q.ForceMCast = mDNSfalse; p->q.ReturnIntermed = mDNStrue; p->q.SuppressUnusable = mDNSfalse; p->q.SearchListIndex = 0; p->q.AppendSearchDomains = 0; p->q.RetryWithSearchDomains = mDNSfalse; p->q.TimeoutQuestion = 0; p->q.WakeOnResolve = 0; p->q.UseBackgroundTrafficClass = mDNSfalse; p->q.ValidationRequired = 0; p->q.ValidatingResponse = 0; p->q.ProxyQuestion = 0; p->q.qnameOrig = mDNSNULL; p->q.AnonInfo = mDNSNULL; p->q.pid = mDNSPlatformGetPID(); p->q.euid = 0; p->q.QuestionCallback = AutoTunnelCallback; p->q.QuestionContext = p; LogInfo("AddNewClientTunnel start tun %p %##s (%s)%s", p, &q->qname.c, DNSTypeName(q->qtype), q->LongLived ? " LongLived" : ""); mDNS_StartQuery_internal(m, &p->q); } #endif // APPLE_OSX_mDNSResponder #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - #pragma mark - Power State & Configuration Change Management #endif mDNSlocal mStatus ReorderInterfaceList() { // Disable Reorder lists till is fixed to prevent spurious name conflicts return (mStatus_NoError); mDNS *const m = &mDNSStorage; nwi_state_t state = nwi_state_copy(); if (state == mDNSNULL) { LogMsg("NWI State is NULL!"); return (mStatus_Invalid); } // Get the count of interfaces mDNSu32 count = nwi_state_get_interface_names(state, mDNSNULL, 0); if (count == 0) { LogMsg("Unable to get the ordered list of interface names"); nwi_state_release(state); return (mStatus_Invalid); } // Get the ordered interface list int i; const char *names[count]; count = nwi_state_get_interface_names(state, names, count); NetworkInterfaceInfo *newList = mDNSNULL; for (i = count-1; i >= 0; i--) { // Build a new ordered interface list NetworkInterfaceInfo **ptr = &m->HostInterfaces; while (*ptr != mDNSNULL ) { if (strcmp((*ptr)->ifname, names[i]) == 0) { NetworkInterfaceInfo *node = *ptr; *ptr = (*ptr)->next; node->next = newList; newList = node; } else ptr = &((*ptr)->next); } } // Get to the end of the list NetworkInterfaceInfo *newListEnd = newList; while (newListEnd != mDNSNULL && newListEnd->next != mDNSNULL) newListEnd = newListEnd->next; // Add any remaing interfaces to the end of the sorted list if (newListEnd != mDNSNULL) newListEnd->next = m->HostInterfaces; // If we have a valid new list, point to that now if (newList != mDNSNULL) m->HostInterfaces = newList; nwi_state_release(state); return (mStatus_NoError); } mDNSlocal mStatus UpdateInterfaceList(mDNSs32 utc) { mDNS *const m = &mDNSStorage; mDNSBool foundav4 = mDNSfalse; mDNSBool foundav6 = mDNSfalse; struct ifaddrs *ifa = myGetIfAddrs(0); struct ifaddrs *v4Loopback = NULL; struct ifaddrs *v6Loopback = NULL; char defaultname[64]; int InfoSocket = socket(AF_INET6, SOCK_DGRAM, 0); if (InfoSocket < 3 && errno != EAFNOSUPPORT) LogMsg("UpdateInterfaceList: InfoSocket error %d errno %d (%s)", InfoSocket, errno, strerror(errno)); if (m->SleepState == SleepState_Sleeping) ifa = NULL; while (ifa) { #if LIST_ALL_INTERFACES if (ifa->ifa_addr) { if (ifa->ifa_addr->sa_family == AF_APPLETALK) LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d is AF_APPLETALK", ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family); else if (ifa->ifa_addr->sa_family == AF_LINK) LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d is AF_LINK", ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family); else if (ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6) LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d not AF_INET (2) or AF_INET6 (30)", ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family); } else LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X ifa_addr is NOT set", ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags); if (!(ifa->ifa_flags & IFF_UP)) LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d Interface not IFF_UP", ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr ? ifa->ifa_addr->sa_family : 0); if (!(ifa->ifa_flags & IFF_MULTICAST)) LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d Interface not IFF_MULTICAST", ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr ? ifa->ifa_addr->sa_family : 0); if (ifa->ifa_flags & IFF_POINTOPOINT) LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d Interface IFF_POINTOPOINT", ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr ? ifa->ifa_addr->sa_family : 0); if (ifa->ifa_flags & IFF_LOOPBACK) LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d Interface IFF_LOOPBACK", ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr ? ifa->ifa_addr->sa_family : 0); #endif if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_LINK) { struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr; if (sdl->sdl_type == IFT_ETHER && sdl->sdl_alen == sizeof(m->PrimaryMAC) && mDNSSameEthAddress(&m->PrimaryMAC, &zeroEthAddr)) mDNSPlatformMemCopy(m->PrimaryMAC.b, sdl->sdl_data + sdl->sdl_nlen, 6); } if (ifa->ifa_flags & IFF_UP && ifa->ifa_addr) if (ifa->ifa_addr->sa_family == AF_INET || ifa->ifa_addr->sa_family == AF_INET6) { if (!ifa->ifa_netmask) { mDNSAddr ip; SetupAddr(&ip, ifa->ifa_addr); LogMsg("getifaddrs: ifa_netmask is NULL for %5s(%d) Flags %04X Family %2d %#a", ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family, &ip); } // Apparently it's normal for the sa_family of an ifa_netmask to sometimes be zero, so we don't complain about that // getifaddrs is returning invalid netmask family for fw0 and vmnet else if (ifa->ifa_netmask->sa_family != ifa->ifa_addr->sa_family && ifa->ifa_netmask->sa_family != 0) { mDNSAddr ip; SetupAddr(&ip, ifa->ifa_addr); LogMsg("getifaddrs ifa_netmask for %5s(%d) Flags %04X Family %2d %#a has different family: %d", ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family, &ip, ifa->ifa_netmask->sa_family); } // Currently we use a few internal ones like mDNSInterfaceID_LocalOnly etc. that are negative values (0, -1, -2). else if ((int)if_nametoindex(ifa->ifa_name) <= 0) { LogMsg("UpdateInterfaceList: if_nametoindex returned zero/negative value for %5s(%d)", ifa->ifa_name, if_nametoindex(ifa->ifa_name)); } else { // Make sure ifa_netmask->sa_family is set correctly // getifaddrs is returning invalid netmask family for fw0 and vmnet ifa->ifa_netmask->sa_family = ifa->ifa_addr->sa_family; int ifru_flags6 = 0; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; if (ifa->ifa_addr->sa_family == AF_INET6 && InfoSocket >= 0) { struct in6_ifreq ifr6; mDNSPlatformMemZero((char *)&ifr6, sizeof(ifr6)); strlcpy(ifr6.ifr_name, ifa->ifa_name, sizeof(ifr6.ifr_name)); ifr6.ifr_addr = *sin6; if (ioctl(InfoSocket, SIOCGIFAFLAG_IN6, &ifr6) != -1) ifru_flags6 = ifr6.ifr_ifru.ifru_flags6; verbosedebugf("%s %.16a %04X %04X", ifa->ifa_name, &sin6->sin6_addr, ifa->ifa_flags, ifru_flags6); } if (!(ifru_flags6 & (IN6_IFF_TENTATIVE | IN6_IFF_DETACHED | IN6_IFF_DEPRECATED | IN6_IFF_TEMPORARY))) { if (ifa->ifa_flags & IFF_LOOPBACK) { if (ifa->ifa_addr->sa_family == AF_INET) v4Loopback = ifa; else if (sin6->sin6_addr.s6_addr[0] != 0xFD) v6Loopback = ifa; } else { NetworkInterfaceInfoOSX *i = AddInterfaceToList(ifa, utc); if (i && MulticastInterface(i) && i->ifinfo.Advertise) { if (ifa->ifa_addr->sa_family == AF_INET) foundav4 = mDNStrue; else foundav6 = mDNStrue; } } } } } ifa = ifa->ifa_next; } // For efficiency, we don't register a loopback interface when other interfaces of that family are available and advertising if (!foundav4 && v4Loopback) AddInterfaceToList(v4Loopback, utc); if (!foundav6 && v6Loopback) AddInterfaceToList(v6Loopback, utc); // Now the list is complete, set the McastTxRx setting for each interface. NetworkInterfaceInfoOSX *i; for (i = m->p->InterfaceList; i; i = i->next) if (i->Exists) { mDNSBool txrx = MulticastInterface(i); if (i->ifinfo.McastTxRx != txrx) { i->ifinfo.McastTxRx = txrx; i->Exists = MulticastStateChanged; // State change; need to deregister and reregister this interface } } if (InfoSocket >= 0) close(InfoSocket); mDNS_snprintf(defaultname, sizeof(defaultname), "%.*s-%02X%02X%02X%02X%02X%02X", HINFO_HWstring_prefixlen, HINFO_HWstring, m->PrimaryMAC.b[0], m->PrimaryMAC.b[1], m->PrimaryMAC.b[2], m->PrimaryMAC.b[3], m->PrimaryMAC.b[4], m->PrimaryMAC.b[5]); // Set up the nice label domainlabel nicelabel; nicelabel.c[0] = 0; GetUserSpecifiedFriendlyComputerName(&nicelabel); if (nicelabel.c[0] == 0) { debugf("Couldn’t read user-specified Computer Name; using default “%s” instead", defaultname); MakeDomainLabelFromLiteralString(&nicelabel, defaultname); } // Set up the RFC 1034-compliant label domainlabel hostlabel; hostlabel.c[0] = 0; GetUserSpecifiedLocalHostName(&hostlabel); if (hostlabel.c[0] == 0) { debugf("Couldn’t read user-specified Local Hostname; using default “%s.local” instead", defaultname); MakeDomainLabelFromLiteralString(&hostlabel, defaultname); } mDNSBool namechange = mDNSfalse; // We use a case-sensitive comparison here because even though changing the capitalization // of the name alone is not significant to DNS, it's still a change from the user's point of view if (SameDomainLabelCS(m->p->usernicelabel.c, nicelabel.c)) debugf("Usernicelabel (%#s) unchanged since last time; not changing m->nicelabel (%#s)", m->p->usernicelabel.c, m->nicelabel.c); else { if (m->p->usernicelabel.c[0]) // Don't show message first time through, when we first read name from prefs on boot LogMsg("User updated Computer Name from “%#s” to “%#s”", m->p->usernicelabel.c, nicelabel.c); m->p->usernicelabel = m->nicelabel = nicelabel; namechange = mDNStrue; } if (SameDomainLabelCS(m->p->userhostlabel.c, hostlabel.c)) debugf("Userhostlabel (%#s) unchanged since last time; not changing m->hostlabel (%#s)", m->p->userhostlabel.c, m->hostlabel.c); else { if (m->p->userhostlabel.c[0]) // Don't show message first time through, when we first read name from prefs on boot LogMsg("User updated Local Hostname from “%#s” to “%#s”", m->p->userhostlabel.c, hostlabel.c); m->p->userhostlabel = m->hostlabel = hostlabel; mDNS_SetFQDN(m); namechange = mDNStrue; } if (namechange) // If either name has changed, we need to tickle our AutoTunnel state machine to update its registered records { #if APPLE_OSX_mDNSResponder DomainAuthInfo *info; for (info = m->AuthInfoList; info; info = info->next) if (info->AutoTunnel) AutoTunnelHostNameChanged(m, info); #endif // APPLE_OSX_mDNSResponder } return(mStatus_NoError); } // Returns number of leading one-bits in mask: 0-32 for IPv4, 0-128 for IPv6 // Returns -1 if all the one-bits are not contiguous mDNSlocal int CountMaskBits(mDNSAddr *mask) { int i = 0, bits = 0; int bytes = mask->type == mDNSAddrType_IPv4 ? 4 : mask->type == mDNSAddrType_IPv6 ? 16 : 0; while (i < bytes) { mDNSu8 b = mask->ip.v6.b[i++]; while (b & 0x80) { bits++; b <<= 1; } if (b) return(-1); } while (i < bytes) if (mask->ip.v6.b[i++]) return(-1); return(bits); } // Returns count of non-link local V4 addresses registered (why? -- SC) mDNSlocal int SetupActiveInterfaces(mDNSs32 utc) { mDNS *const m = &mDNSStorage; NetworkInterfaceInfoOSX *i; int count = 0; // Recalculate SuppressProbes time based on the current set of active interfaces. m->SuppressProbes = 0; for (i = m->p->InterfaceList; i; i = i->next) if (i->Exists) { NetworkInterfaceInfo *const n = &i->ifinfo; NetworkInterfaceInfoOSX *primary = SearchForInterfaceByName(i->ifinfo.ifname, AF_UNSPEC); if (!primary) LogMsg("SetupActiveInterfaces ERROR! SearchForInterfaceByName didn't find %s", i->ifinfo.ifname); if (i->Registered && i->Registered != primary) // Sanity check { LogMsg("SetupActiveInterfaces ERROR! n->Registered %p != primary %p", i->Registered, primary); i->Registered = mDNSNULL; } if (!i->Registered) { InterfaceActivationSpeed activationSpeed; // Note: If i->Registered is set, that means we've called mDNS_RegisterInterface() for this interface, // so we need to make sure we call mDNS_DeregisterInterface() before disposing it. // If i->Registered is NOT set, then we haven't registered it and we should not try to deregister it. i->Registered = primary; // If i->LastSeen == utc, then this is a brand-new interface, just created, or an interface that never went away. // If i->LastSeen != utc, then this is an old interface, previously seen, that went away for (utc - i->LastSeen) seconds. // If the interface is an old one that went away and came back in less than a minute, then we're in a flapping scenario. i->Occulting = !(i->ifa_flags & IFF_LOOPBACK) && (utc - i->LastSeen > 0 && utc - i->LastSeen < 60); // The "p2p*" interfaces used for legacy AirDrop reuse the scope-id, MAC address and the IP address // every time a new interface is created. We think it is a duplicate and hence consider it // as flashing and occulting, that is, flapping. If an interface is marked as flapping, // mDNS_RegisterInterface() changes the probe delay from 1/2 second to 5 seconds and // logs a warning message to system.log noting frequent interface transitions. // The same logic applies when the IFEF_DIRECTLINK flag is set on the interface. if ((strncmp(i->ifinfo.ifname, "p2p", 3) == 0) || i->ifinfo.DirectLink) { activationSpeed = FastActivation; LogInfo("SetupActiveInterfaces: %s DirectLink interface registering", i->ifinfo.ifname); } else if (i->Flashing && i->Occulting) { activationSpeed = SlowActivation; } else { activationSpeed = NormalActivation; } mDNS_RegisterInterface(m, n, activationSpeed); if (!mDNSAddressIsLinkLocal(&n->ip)) count++; LogInfo("SetupActiveInterfaces: Registered %7s(%u) BSSID %.6a Struct addr %p, primary %p, %#a/%d%s%s%s", i->ifinfo.ifname, i->scope_id, &i->BSSID, i, primary, &n->ip, CountMaskBits(&n->mask), i->Flashing ? " (Flashing)" : "", i->Occulting ? " (Occulting)" : "", n->InterfaceActive ? " (Primary)" : ""); if (!n->McastTxRx) { debugf("SetupActiveInterfaces: No Tx/Rx on %5s(%lu) %.6a InterfaceID %p %#a", i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID, &n->ip); #if TARGET_OS_EMBEDDED // We join the Bonjour multicast group on Apple embedded platforms ONLY when a client request is active, // so we leave the multicast group here to clear any residual group membership. if (i->sa_family == AF_INET) { struct ip_mreq imr; primary->ifa_v4addr.s_addr = n->ip.ip.v4.NotAnInteger; imr.imr_multiaddr.s_addr = AllDNSLinkGroup_v4.ip.v4.NotAnInteger; imr.imr_interface = primary->ifa_v4addr; if (SearchForInterfaceByName(i->ifinfo.ifname, AF_INET) == i) { LogInfo("SetupActiveInterfaces: %5s(%lu) Doing IP_DROP_MEMBERSHIP for %.4a on %.4a", i->ifinfo.ifname, i->scope_id, &imr.imr_multiaddr, &imr.imr_interface); mStatus err = setsockopt(m->p->permanentsockets.sktv4, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, sizeof(imr)); if (err < 0 && (errno != EADDRNOTAVAIL)) LogMsg("setsockopt - IP_DROP_MEMBERSHIP error %d errno %d (%s)", err, errno, strerror(errno)); } } if (i->sa_family == AF_INET6) { struct ipv6_mreq i6mr; i6mr.ipv6mr_interface = primary->scope_id; i6mr.ipv6mr_multiaddr = *(struct in6_addr*)&AllDNSLinkGroup_v6.ip.v6; if (SearchForInterfaceByName(i->ifinfo.ifname, AF_INET6) == i) { LogInfo("SetupActiveInterfaces: %5s(%lu) Doing IPV6_LEAVE_GROUP for %.16a on %u", i->ifinfo.ifname, i->scope_id, &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface); mStatus err = setsockopt(m->p->permanentsockets.sktv6, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &i6mr, sizeof(i6mr)); if (err < 0 && (errno != EADDRNOTAVAIL)) LogMsg("setsockopt - IPV6_LEAVE_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface); } } #endif // TARGET_OS_EMBEDDED } else { if (i->sa_family == AF_INET) { struct ip_mreq imr; primary->ifa_v4addr.s_addr = n->ip.ip.v4.NotAnInteger; imr.imr_multiaddr.s_addr = AllDNSLinkGroup_v4.ip.v4.NotAnInteger; imr.imr_interface = primary->ifa_v4addr; // If this is our *first* IPv4 instance for this interface name, we need to do a IP_DROP_MEMBERSHIP first, // before trying to join the group, to clear out stale kernel state which may be lingering. // In particular, this happens with removable network interfaces like USB Ethernet adapters -- the kernel has stale state // from the last time the USB Ethernet adapter was connected, and part of the kernel thinks we've already joined the group // on that interface (so we get EADDRINUSE when we try to join again) but a different part of the kernel thinks we haven't // joined the group (so we receive no multicasts). Doing an IP_DROP_MEMBERSHIP before joining seems to flush the stale state. // Also, trying to make the code leave the group when the adapter is removed doesn't work either, // because by the time we get the configuration change notification, the interface is already gone, // so attempts to unsubscribe fail with EADDRNOTAVAIL (errno 49 "Can't assign requested address"). // IP_ADD_MEMBERSHIP fails for previously-connected removable interfaces if (SearchForInterfaceByName(i->ifinfo.ifname, AF_INET) == i) { LogInfo("SetupActiveInterfaces: %5s(%lu) Doing precautionary IP_DROP_MEMBERSHIP for %.4a on %.4a", i->ifinfo.ifname, i->scope_id, &imr.imr_multiaddr, &imr.imr_interface); mStatus err = setsockopt(m->p->permanentsockets.sktv4, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, sizeof(imr)); if (err < 0 && (errno != EADDRNOTAVAIL)) LogMsg("setsockopt - IP_DROP_MEMBERSHIP error %d errno %d (%s)", err, errno, strerror(errno)); } LogInfo("SetupActiveInterfaces: %5s(%lu) joining IPv4 mcast group %.4a on %.4a", i->ifinfo.ifname, i->scope_id, &imr.imr_multiaddr, &imr.imr_interface); mStatus err = setsockopt(m->p->permanentsockets.sktv4, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr)); // Joining same group twice can give "Address already in use" error -- no need to report that if (err < 0 && (errno != EADDRINUSE)) LogMsg("setsockopt - IP_ADD_MEMBERSHIP error %d errno %d (%s) group %.4a on %.4a", err, errno, strerror(errno), &imr.imr_multiaddr, &imr.imr_interface); } if (i->sa_family == AF_INET6) { struct ipv6_mreq i6mr; i6mr.ipv6mr_interface = primary->scope_id; i6mr.ipv6mr_multiaddr = *(struct in6_addr*)&AllDNSLinkGroup_v6.ip.v6; if (SearchForInterfaceByName(i->ifinfo.ifname, AF_INET6) == i) { LogInfo("SetupActiveInterfaces: %5s(%lu) Doing precautionary IPV6_LEAVE_GROUP for %.16a on %u", i->ifinfo.ifname, i->scope_id, &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface); mStatus err = setsockopt(m->p->permanentsockets.sktv6, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &i6mr, sizeof(i6mr)); if (err < 0 && (errno != EADDRNOTAVAIL)) LogMsg("setsockopt - IPV6_LEAVE_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface); } LogInfo("SetupActiveInterfaces: %5s(%lu) joining IPv6 mcast group %.16a on %u", i->ifinfo.ifname, i->scope_id, &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface); mStatus err = setsockopt(m->p->permanentsockets.sktv6, IPPROTO_IPV6, IPV6_JOIN_GROUP, &i6mr, sizeof(i6mr)); // Joining same group twice can give "Address already in use" error -- no need to report that if (err < 0 && (errno != EADDRINUSE)) LogMsg("setsockopt - IPV6_JOIN_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface); } } } } return count; } mDNSlocal void MarkAllInterfacesInactive(mDNSs32 utc) { NetworkInterfaceInfoOSX *i; for (i = mDNSStorage.p->InterfaceList; i; i = i->next) { if (i->Exists) i->LastSeen = utc; i->Exists = mDNSfalse; } } // Returns count of non-link local V4 addresses deregistered (why? -- SC) mDNSlocal int ClearInactiveInterfaces(mDNSs32 utc) { mDNS *const m = &mDNSStorage; // First pass: // If an interface is going away, then deregister this from the mDNSCore. // We also have to deregister it if the primary interface that it's using for its InterfaceID is going away. // We have to do this because mDNSCore will use that InterfaceID when sending packets, and if the memory // it refers to has gone away we'll crash. NetworkInterfaceInfoOSX *i; int count = 0; for (i = m->p->InterfaceList; i; i = i->next) { // If this interface is no longer active, or its InterfaceID is changing, deregister it NetworkInterfaceInfoOSX *primary = SearchForInterfaceByName(i->ifinfo.ifname, AF_UNSPEC); if (i->Registered) if (i->Exists == 0 || i->Exists == MulticastStateChanged || i->Registered != primary) { InterfaceActivationSpeed activationSpeed; i->Flashing = !(i->ifa_flags & IFF_LOOPBACK) && (utc - i->AppearanceTime < 60); LogInfo("ClearInactiveInterfaces: Deregistering %5s(%lu) %.6a InterfaceID %p(%p), primary %p, %#a/%d%s%s%s", i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID, i, primary, &i->ifinfo.ip, CountMaskBits(&i->ifinfo.mask), i->Flashing ? " (Flashing)" : "", i->Occulting ? " (Occulting)" : "", i->ifinfo.InterfaceActive ? " (Primary)" : ""); // "p2p*" interfaces used for legacy AirDrop reuse the scope-id, MAC address and the IP address // every time it creates a new interface. We think it is a duplicate and hence consider it // as flashing and occulting. The "core" does not flush the cache for this case. This leads to // stale data returned to the application even after the interface is removed. The application // then starts to send data but the new interface is not yet created. // The same logic applies when the IFEF_DIRECTLINK flag is set on the interface. if ((strncmp(i->ifinfo.ifname, "p2p", 3) == 0) || i->ifinfo.DirectLink) { activationSpeed = FastActivation; LogInfo("ClearInactiveInterfaces: %s DirectLink interface deregistering", i->ifinfo.ifname); } else if (i->Flashing && i->Occulting) { activationSpeed = SlowActivation; } else { activationSpeed = NormalActivation; } mDNS_DeregisterInterface(m, &i->ifinfo, activationSpeed); if (!mDNSAddressIsLinkLocal(&i->ifinfo.ip)) count++; i->Registered = mDNSNULL; // Note: If i->Registered is set, that means we've called mDNS_RegisterInterface() for this interface, // so we need to make sure we call mDNS_DeregisterInterface() before disposing it. // If i->Registered is NOT set, then it's not registered and we should not call mDNS_DeregisterInterface() on it. // Caution: If we ever decide to add code here to leave the multicast group, we need to make sure that this // is the LAST representative of this physical interface, or we'll unsubscribe from the group prematurely. } } // Second pass: // Now that everything that's going to deregister has done so, we can clean up and free the memory NetworkInterfaceInfoOSX **p = &m->p->InterfaceList; while (*p) { i = *p; // If no longer active, delete interface from list and free memory if (!i->Exists) { if (i->LastSeen == utc) i->LastSeen = utc - 1; mDNSBool delete = (NumCacheRecordsForInterfaceID(m, i->ifinfo.InterfaceID) == 0) && (utc - i->LastSeen >= 60); LogInfo("ClearInactiveInterfaces: %-13s %5s(%lu) %.6a InterfaceID %p(%p) %#a/%d Age %d%s", delete ? "Deleting" : "Holding", i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID, i, &i->ifinfo.ip, CountMaskBits(&i->ifinfo.mask), utc - i->LastSeen, i->ifinfo.InterfaceActive ? " (Primary)" : ""); #if APPLE_OSX_mDNSResponder if (i->BPF_fd >= 0) CloseBPF(i); #endif // APPLE_OSX_mDNSResponder if (delete) { *p = i->next; freeL("NetworkInterfaceInfoOSX", i); continue; // After deleting this object, don't want to do the "p = &i->next;" thing at the end of the loop } } p = &i->next; } return count; } mDNSlocal void AppendDNameListElem(DNameListElem ***List, mDNSu32 uid, domainname *name) { DNameListElem *dnle = (DNameListElem*) mallocL("DNameListElem/AppendDNameListElem", sizeof(DNameListElem)); if (!dnle) LogMsg("ERROR: AppendDNameListElem: memory exhausted"); else { dnle->next = mDNSNULL; dnle->uid = uid; AssignDomainName(&dnle->name, name); **List = dnle; *List = &dnle->next; } } mDNSlocal int compare_dns_configs(const void *aa, const void *bb) { dns_resolver_t *a = *(dns_resolver_t**)aa; dns_resolver_t *b = *(dns_resolver_t**)bb; return (a->search_order < b->search_order) ? -1 : (a->search_order == b->search_order) ? 0 : 1; } mDNSlocal void UpdateSearchDomainHash(MD5_CTX *sdc, char *domain, mDNSInterfaceID InterfaceID) { mDNS *const m = &mDNSStorage; char *buf = "."; mDNSu32 scopeid = 0; char ifid_buf[16]; if (domain) buf = domain; // // Hash the search domain name followed by the InterfaceID. // As we have scoped search domains, we also included InterfaceID. If either of them change, // we will detect it. Even if the order of them change, we will detect it. // // Note: We have to handle a few of these tricky cases. // // 1) Current: com, apple.com Changing to: comapple.com // 2) Current: a.com,b.com Changing to a.comb.com // 3) Current: a.com,b.com (ifid 8), Changing to a.com8b.com (ifid 8) // 4) Current: a.com (ifid 12), Changing to a.com1 (ifid: 2) // // There are more variants of the above. The key thing is if we include the null in each case // at the end of name and the InterfaceID, it will prevent a new name (which can't include // NULL as part of the name) to be mistakenly thought of as a old name. scopeid = mDNSPlatformInterfaceIndexfromInterfaceID(m, InterfaceID, mDNStrue); // mDNS_snprintf always null terminates if (mDNS_snprintf(ifid_buf, sizeof(ifid_buf), "%u", scopeid) >= sizeof(ifid_buf)) LogMsg("UpdateSearchDomainHash: mDNS_snprintf failed for scopeid %u", scopeid); LogInfo("UpdateSearchDomainHash: buf %s, ifid_buf %s", buf, ifid_buf); MD5_Update(sdc, buf, strlen(buf) + 1); MD5_Update(sdc, ifid_buf, strlen(ifid_buf) + 1); } mDNSlocal void FinalizeSearchDomainHash(MD5_CTX *sdc) { mDNS *const m = &mDNSStorage; mDNSu8 md5_hash[MD5_LEN]; MD5_Final(md5_hash, sdc); if (memcmp(md5_hash, m->SearchDomainsHash, MD5_LEN)) { // If the hash is different, either the search domains have changed or // the ordering between them has changed. Restart the questions that // would be affected by this. LogInfo("FinalizeSearchDomains: The hash is different"); memcpy(m->SearchDomainsHash, md5_hash, MD5_LEN); RetrySearchDomainQuestions(m); } else { LogInfo("FinalizeSearchDomains: The hash is same"); } } mDNSexport const char *DNSScopeToString(mDNSu32 scope) { switch (scope) { case kScopeNone: return "Unscoped"; case kScopeInterfaceID: return "InterfaceScoped"; case kScopeServiceID: return "ServiceScoped"; default: return "Unknown"; } } mDNSlocal void ConfigSearchDomains(dns_resolver_t *resolver, mDNSInterfaceID interfaceId, mDNSu32 scope, MD5_CTX *sdc, uint64_t generation) { const char *scopeString = DNSScopeToString(scope); int j; domainname d; if (scope == kScopeNone) interfaceId = mDNSInterface_Any; if (scope == kScopeNone || scope == kScopeInterfaceID) { for (j = 0; j < resolver->n_search; j++) { if (MakeDomainNameFromDNSNameString(&d, resolver->search[j]) != NULL) { static char interface_buf[32]; mDNS_snprintf(interface_buf, sizeof(interface_buf), "for interface %s", InterfaceNameForID(&mDNSStorage, interfaceId)); LogInfo("ConfigSearchDomains: (%s) configuring search domain %s %s (generation= %llu)", scopeString, resolver->search[j], (interfaceId == mDNSInterface_Any) ? "" : interface_buf, generation); UpdateSearchDomainHash(sdc, resolver->search[j], interfaceId); mDNS_AddSearchDomain_CString(resolver->search[j], interfaceId); } else { LogInfo("ConfigSearchDomains: An invalid search domain was detected for %s domain %s n_nameserver %d, (generation= %llu)", DNSScopeToString(scope), resolver->domain, resolver->n_nameserver, generation); } } } else { LogInfo("ConfigSearchDomains: (%s) Ignoring search domain for interface %s", scopeString, InterfaceNameForID(&mDNSStorage, interfaceId)); } } mDNSlocal mDNSInterfaceID ConfigParseInterfaceID(mDNSu32 ifindex) { NetworkInterfaceInfoOSX *ni; mDNSInterfaceID interface; for (ni = mDNSStorage.p->InterfaceList; ni; ni = ni->next) { if (ni->ifinfo.InterfaceID && ni->scope_id == ifindex) break; } if (ni != NULL) { interface = ni->ifinfo.InterfaceID; } else { // In rare circumstances, we could potentially hit this case where we cannot parse the InterfaceID // (see ). At this point, we still accept the DNS Config from configd // Note: We currently ack the whole dns configuration and not individual resolvers or DNS servers. // As the caller is going to ack the configuration always, we have to add all the DNS servers // in the configuration. Otherwise, we won't have any DNS servers up until the network change. LogMsg("ConfigParseInterfaceID: interface specific index %d not found (interface may not be UP)",ifindex); // Set the correct interface from configd before passing this to mDNS_AddDNSServer() below interface = (mDNSInterfaceID)(unsigned long)ifindex; } return interface; } mDNSlocal void ConfigNonUnicastResolver(dns_resolver_t *r) { char *opt = r->options; domainname d; if (opt && !strncmp(opt, "mdns", strlen(opt))) { if (!MakeDomainNameFromDNSNameString(&d, r->domain)) { LogMsg("ConfigNonUnicastResolver: config->resolver bad domain %s", r->domain); return; } mDNS_AddMcastResolver(&mDNSStorage, &d, mDNSInterface_Any, r->timeout); } } mDNSlocal void ConfigDNSServers(dns_resolver_t *r, mDNSInterfaceID interface, mDNSu32 scope, mDNSu16 resGroupID) { int n; domainname d; int serviceID = 0; mDNSBool cellIntf = mDNSfalse; mDNSBool reqA, reqAAAA; NetworkInterfaceInfoOSX *info; mDNSBool isExpensive; if (!r->domain || !*r->domain) { d.c[0] = 0; } else if (!MakeDomainNameFromDNSNameString(&d, r->domain)) { LogMsg("ConfigDNSServers: bad domain %s", r->domain); return; } // Parse the resolver specific attributes that affects all the DNS servers. if (scope == kScopeServiceID) { serviceID = r->service_identifier; } #if TARGET_OS_IPHONE cellIntf = (r->reach_flags & kSCNetworkReachabilityFlagsIsWWAN) ? mDNStrue : mDNSfalse; #endif reqA = (r->flags & DNS_RESOLVER_FLAGS_REQUEST_A_RECORDS ? mDNStrue : mDNSfalse); reqAAAA = (r->flags & DNS_RESOLVER_FLAGS_REQUEST_AAAA_RECORDS ? mDNStrue : mDNSfalse); info = IfindexToInterfaceInfoOSX(interface); isExpensive = info ? info->isExpensive : mDNSfalse; for (n = 0; n < r->n_nameserver; n++) { mDNSAddr saddr; DNSServer *s; if (r->nameserver[n]->sa_family != AF_INET && r->nameserver[n]->sa_family != AF_INET6) continue; if (SetupAddr(&saddr, r->nameserver[n])) { LogMsg("ConfigDNSServers: Bad address"); continue; } // The timeout value is for all the DNS servers in a given resolver, hence we pass // the timeout value only for the first DNSServer. If we don't have a value in the // resolver, then use the core's default value // // Note: this assumes that when the core picks a list of DNSServers for a question, // it takes the sum of all the timeout values for all DNS servers. By doing this, it // tries all the DNS servers in a specified timeout s = mDNS_AddDNSServer(&mDNSStorage, &d, interface, serviceID, &saddr, r->port ? mDNSOpaque16fromIntVal(r->port) : UnicastDNSPort, scope, (n == 0 ? (r->timeout ? r->timeout : DEFAULT_UDNS_TIMEOUT) : 0), cellIntf, isExpensive, resGroupID, reqA, reqAAAA, mDNStrue); if (s) { LogInfo("ConfigDNSServers(%s): DNS server %#a:%d for domain %##s", DNSScopeToString(scope), &s->addr, mDNSVal16(s->port), d.c); } } } // ConfigResolvers is called for different types of resolvers: Unscoped resolver, Interface scope resolver and // Service scope resolvers. This is indicated by the scope argument. // // "resolver" has entries that should only be used for unscoped questions. // // "scoped_resolver" has entries that should only be used for Interface scoped question i.e., questions that specify an // interface index (q->InterfaceID) // // "service_specific_resolver" has entries that should be used for Service scoped question i.e., questions that specify // a service identifier (q->ServiceID) // mDNSlocal void ConfigResolvers(dns_config_t *config, mDNSu32 scope, mDNSBool setsearch, mDNSBool setservers, MD5_CTX *sdc, mDNSu16 resGroupID) { int i; dns_resolver_t **resolver; int nresolvers; const char *scopeString = DNSScopeToString(scope); mDNSInterfaceID interface; switch (scope) { case kScopeNone: resolver = config->resolver; nresolvers = config->n_resolver; break; case kScopeInterfaceID: resolver = config->scoped_resolver; nresolvers = config->n_scoped_resolver; break; case kScopeServiceID: resolver = config->service_specific_resolver; nresolvers = config->n_service_specific_resolver; break; default: return; } qsort(resolver, nresolvers, sizeof(dns_resolver_t*), compare_dns_configs); for (i = 0; i < nresolvers; i++) { dns_resolver_t *r = resolver[i]; LogInfo("ConfigResolvers: %s resolver[%d] domain %s n_nameserver %d", scopeString, i, r->domain, r->n_nameserver); interface = mDNSInterface_Any; // Parse the interface index if (r->if_index != 0) { interface = ConfigParseInterfaceID(r->if_index); } if (setsearch) { ConfigSearchDomains(resolver[i], interface, scope, sdc, config->generation); // Parse other scoped resolvers for search lists if (!setservers) continue; } if (r->port == 5353 || r->n_nameserver == 0) { ConfigNonUnicastResolver(r); } else { // Each scoped resolver gets its own ID (i.e., they are in their own group) so that responses from the // scoped resolver are not used by other non-scoped or scoped resolvers. if (scope != kScopeNone) resGroupID++; ConfigDNSServers(r, interface, scope, resGroupID); } } } #if APPLE_OSX_mDNSResponder mDNSlocal mDNSBool QuestionValidForDNSTrigger(DNSQuestion *q) { if (QuerySuppressed(q)) { debugf("QuestionValidForDNSTrigger: Suppressed: %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); return mDNSfalse; } if (mDNSOpaque16IsZero(q->TargetQID)) { debugf("QuestionValidForDNSTrigger: Multicast: %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); return mDNSfalse; } // If we answered using LocalOnly records e.g., /etc/hosts, don't consider that a valid response // for trigger. if (q->LOAddressAnswers) { debugf("QuestionValidForDNSTrigger: LocalOnly answers: %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); return mDNSfalse; } return mDNStrue; } #endif // This function is called if we are not delivering unicast answers to "A" or "AAAA" questions. // We set our state appropriately so that if we start receiving answers, trigger the // upper layer to retry DNS questions. #if APPLE_OSX_mDNSResponder mDNSexport void mDNSPlatformUpdateDNSStatus(DNSQuestion *q) { mDNS *const m = &mDNSStorage; if (!QuestionValidForDNSTrigger(q)) return; // Ignore applications that start and stop queries for no reason before we ever talk // to any DNS server. if (!q->triedAllServersOnce) { LogInfo("QuestionValidForDNSTrigger: question %##s (%s) stopped too soon", q->qname.c, DNSTypeName(q->qtype)); return; } if (q->qtype == kDNSType_A) m->p->v4answers = 0; if (q->qtype == kDNSType_AAAA) m->p->v6answers = 0; if (!m->p->v4answers || !m->p->v6answers) { LogInfo("mDNSPlatformUpdateDNSStatus: Trigger needed v4 %d, v6 %d, question %##s (%s)", m->p->v4answers, m->p->v6answers, q->qname.c, DNSTypeName(q->qtype)); } } #endif mDNSlocal void AckConfigd(dns_config_t *config) { mDNS_CheckLock(&mDNSStorage); // Acking the configuration triggers configd to reissue the reachability queries mDNSStorage.p->DNSTrigger = NonZeroTime(mDNSStorage.timenow); _dns_configuration_ack(config, "com.apple.mDNSResponder"); } // If v4q is non-NULL, it means we have received some answers for "A" type questions // If v6q is non-NULL, it means we have received some answers for "AAAA" type questions #if APPLE_OSX_mDNSResponder mDNSexport void mDNSPlatformTriggerDNSRetry(DNSQuestion *v4q, DNSQuestion *v6q) { mDNS *const m = &mDNSStorage; mDNSBool trigger = mDNSfalse; mDNSs32 timenow; // Don't send triggers too often. // If we have started delivering answers to questions, we should send a trigger // if the time permits. If we are delivering answers, we should set the state // of v4answers/v6answers to 1 and avoid sending a trigger. But, we don't know // whether the answers that are being delivered currently is for configd or some // other application. If we set the v4answers/v6answers to 1 and not deliver a trigger, // then we won't deliver the trigger later when it is okay to send one as the // "answers" are already set to 1. Hence, don't affect the state of v4answers and // v6answers if we are not delivering triggers. mDNS_Lock(m); timenow = m->timenow; if (m->p->DNSTrigger && (timenow - m->p->DNSTrigger) < DNS_TRIGGER_INTERVAL) { if (!m->p->v4answers || !m->p->v6answers) { debugf("mDNSPlatformTriggerDNSRetry: not triggering, time since last trigger %d ms, v4ans %d, v6ans %d", (timenow - m->p->DNSTrigger), m->p->v4answers, m->p->v6answers); } mDNS_Unlock(m); return; } mDNS_Unlock(m); if (v4q != NULL && QuestionValidForDNSTrigger(v4q)) { int old = m->p->v4answers; m->p->v4answers = 1; // If there are IPv4 answers now and previously we did not have // any answers, trigger a DNS change so that reachability // can retry the queries again. if (!old) { LogInfo("mDNSPlatformTriggerDNSRetry: Triggering because of IPv4, last trigger %d ms, %##s (%s)", (timenow - m->p->DNSTrigger), v4q->qname.c, DNSTypeName(v4q->qtype)); trigger = mDNStrue; } } if (v6q != NULL && QuestionValidForDNSTrigger(v6q)) { int old = m->p->v6answers; m->p->v6answers = 1; // If there are IPv6 answers now and previously we did not have // any answers, trigger a DNS change so that reachability // can retry the queries again. if (!old) { LogInfo("mDNSPlatformTriggerDNSRetry: Triggering because of IPv6, last trigger %d ms, %##s (%s)", (timenow - m->p->DNSTrigger), v6q->qname.c, DNSTypeName(v6q->qtype)); trigger = mDNStrue; } } if (trigger) { dns_config_t *config = dns_configuration_copy(); if (config) { mDNS_Lock(m); AckConfigd(config); mDNS_Unlock(m); dns_configuration_free(config); } else { LogMsg("mDNSPlatformTriggerDNSRetry: ERROR!! configd did not return config"); } } } mDNSlocal void SetupActiveDirectoryDomain(dns_config_t *config) { // Record the so-called "primary" domain, which we use as a hint to tell if the user is on a network set up // by someone using Microsoft Active Directory using "local" as a private internal top-level domain if (config->n_resolver && config->resolver[0]->domain && config->resolver[0]->n_nameserver && config->resolver[0]->nameserver[0]) { MakeDomainNameFromDNSNameString(&ActiveDirectoryPrimaryDomain, config->resolver[0]->domain); } else { ActiveDirectoryPrimaryDomain.c[0] = 0; } //MakeDomainNameFromDNSNameString(&ActiveDirectoryPrimaryDomain, "test.local"); ActiveDirectoryPrimaryDomainLabelCount = CountLabels(&ActiveDirectoryPrimaryDomain); if (config->n_resolver && config->resolver[0]->n_nameserver && SameDomainName(SkipLeadingLabels(&ActiveDirectoryPrimaryDomain, ActiveDirectoryPrimaryDomainLabelCount - 1), &localdomain)) { SetupAddr(&ActiveDirectoryPrimaryDomainServer, config->resolver[0]->nameserver[0]); } else { AssignDomainName(&ActiveDirectoryPrimaryDomain, (const domainname *)""); ActiveDirectoryPrimaryDomainLabelCount = 0; ActiveDirectoryPrimaryDomainServer = zeroAddr; } } #endif mDNSlocal void SetupDDNSDomains(domainname *const fqdn, DNameListElem **RegDomains, DNameListElem **BrowseDomains) { int i; char buf[MAX_ESCAPED_DOMAIN_NAME]; // Max legal C-string name, including terminating NULL domainname d; CFDictionaryRef ddnsdict = SCDynamicStoreCopyValue(NULL, NetworkChangedKey_DynamicDNS); if (ddnsdict) { if (fqdn) { CFArrayRef fqdnArray = CFDictionaryGetValue(ddnsdict, CFSTR("HostNames")); if (fqdnArray && CFArrayGetCount(fqdnArray) > 0) { // for now, we only look at the first array element. if we ever support multiple configurations, we will walk the list CFDictionaryRef fqdnDict = CFArrayGetValueAtIndex(fqdnArray, 0); if (fqdnDict && DictionaryIsEnabled(fqdnDict)) { CFStringRef name = CFDictionaryGetValue(fqdnDict, CFSTR("Domain")); if (name) { if (!CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8) || !MakeDomainNameFromDNSNameString(fqdn, buf) || !fqdn->c[0]) LogMsg("GetUserSpecifiedDDNSConfig SCDynamicStore bad DDNS host name: %s", buf[0] ? buf : "(unknown)"); else debugf("GetUserSpecifiedDDNSConfig SCDynamicStore DDNS host name: %s", buf); } } } } if (RegDomains) { CFArrayRef regArray = CFDictionaryGetValue(ddnsdict, CFSTR("RegistrationDomains")); if (regArray && CFArrayGetCount(regArray) > 0) { CFDictionaryRef regDict = CFArrayGetValueAtIndex(regArray, 0); if (regDict && DictionaryIsEnabled(regDict)) { CFStringRef name = CFDictionaryGetValue(regDict, CFSTR("Domain")); if (name) { if (!CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8) || !MakeDomainNameFromDNSNameString(&d, buf) || !d.c[0]) LogMsg("GetUserSpecifiedDDNSConfig SCDynamicStore bad DDNS registration domain: %s", buf[0] ? buf : "(unknown)"); else { debugf("GetUserSpecifiedDDNSConfig SCDynamicStore DDNS registration domain: %s", buf); AppendDNameListElem(&RegDomains, 0, &d); } } } } } if (BrowseDomains) { CFArrayRef browseArray = CFDictionaryGetValue(ddnsdict, CFSTR("BrowseDomains")); if (browseArray) { for (i = 0; i < CFArrayGetCount(browseArray); i++) { CFDictionaryRef browseDict = CFArrayGetValueAtIndex(browseArray, i); if (browseDict && DictionaryIsEnabled(browseDict)) { CFStringRef name = CFDictionaryGetValue(browseDict, CFSTR("Domain")); if (name) { if (!CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8) || !MakeDomainNameFromDNSNameString(&d, buf) || !d.c[0]) LogMsg("GetUserSpecifiedDDNSConfig SCDynamicStore bad DDNS browsing domain: %s", buf[0] ? buf : "(unknown)"); else { debugf("GetUserSpecifiedDDNSConfig SCDynamicStore DDNS browsing domain: %s", buf); AppendDNameListElem(&BrowseDomains, 0, &d); } } } } } } CFRelease(ddnsdict); } if (RegDomains) { CFDictionaryRef btmm = SCDynamicStoreCopyValue(NULL, NetworkChangedKey_BackToMyMac); if (btmm) { CFIndex size = CFDictionaryGetCount(btmm); const void *key[size]; const void *val[size]; CFDictionaryGetKeysAndValues(btmm, key, val); for (i = 0; i < size; i++) { LogInfo("BackToMyMac %d", i); if (!CFStringGetCString(key[i], buf, sizeof(buf), kCFStringEncodingUTF8)) LogMsg("Can't read BackToMyMac %d key %s", i, buf); else { mDNSu32 uid = atoi(buf); if (!CFStringGetCString(val[i], buf, sizeof(buf), kCFStringEncodingUTF8)) LogMsg("Can't read BackToMyMac %d val %s", i, buf); else if (MakeDomainNameFromDNSNameString(&d, buf) && d.c[0]) { LogInfo("BackToMyMac %d %d %##s", i, uid, d.c); AppendDNameListElem(&RegDomains, uid, &d); } } } CFRelease(btmm); } } } // Returns mDNSfalse, if it does not set the configuration i.e., if the DNS configuration did not change mDNSexport mDNSBool mDNSPlatformSetDNSConfig(mDNSBool setservers, mDNSBool setsearch, domainname *const fqdn, DNameListElem **RegDomains, DNameListElem **BrowseDomains, mDNSBool ackConfig) { mDNS *const m = &mDNSStorage; MD5_CTX sdc; // search domain context static mDNSu16 resolverGroupID = 0; // Need to set these here because we need to do this even if SCDynamicStoreCreate() or SCDynamicStoreCopyValue() below don't succeed if (fqdn ) fqdn->c[0] = 0; if (RegDomains ) *RegDomains = NULL; if (BrowseDomains) *BrowseDomains = NULL; LogInfo("mDNSPlatformSetDNSConfig:%s%s%s%s%s", setservers ? " setservers" : "", setsearch ? " setsearch" : "", fqdn ? " fqdn" : "", RegDomains ? " RegDomains" : "", BrowseDomains ? " BrowseDomains" : ""); if (setsearch) MD5_Init(&sdc); // Add the inferred address-based configuration discovery domains // (should really be in core code I think, not platform-specific) if (setsearch) { struct ifaddrs *ifa = mDNSNULL; struct sockaddr_in saddr; mDNSPlatformMemZero(&saddr, sizeof(saddr)); saddr.sin_len = sizeof(saddr); saddr.sin_family = AF_INET; saddr.sin_port = 0; saddr.sin_addr.s_addr = *(in_addr_t *)&m->Router.ip.v4; // Don't add any reverse-IP search domains if doing the WAB bootstrap queries would cause dial-on-demand connection initiation if (!AddrRequiresPPPConnection((struct sockaddr *)&saddr)) ifa = myGetIfAddrs(1); while (ifa) { mDNSAddr a, n; char buf[64]; if (ifa->ifa_addr->sa_family == AF_INET && ifa->ifa_netmask && !(ifa->ifa_flags & IFF_LOOPBACK) && !SetupAddr(&a, ifa->ifa_addr) && !mDNSv4AddressIsLinkLocal(&a.ip.v4) ) { // Apparently it's normal for the sa_family of an ifa_netmask to sometimes be incorrect, so we explicitly fix it here before calling SetupAddr // getifaddrs is returning invalid netmask family for fw0 and vmnet ifa->ifa_netmask->sa_family = ifa->ifa_addr->sa_family; // Make sure ifa_netmask->sa_family is set correctly SetupAddr(&n, ifa->ifa_netmask); // Note: This is reverse order compared to a normal dotted-decimal IP address, so we can't use our customary "%.4a" format code mDNS_snprintf(buf, sizeof(buf), "%d.%d.%d.%d.in-addr.arpa.", a.ip.v4.b[3] & n.ip.v4.b[3], a.ip.v4.b[2] & n.ip.v4.b[2], a.ip.v4.b[1] & n.ip.v4.b[1], a.ip.v4.b[0] & n.ip.v4.b[0]); UpdateSearchDomainHash(&sdc, buf, NULL); mDNS_AddSearchDomain_CString(buf, mDNSNULL); } ifa = ifa->ifa_next; } } #ifndef MDNS_NO_DNSINFO if (setservers || setsearch) { dns_config_t *config = dns_configuration_copy(); if (!config) { // On 10.4, calls to dns_configuration_copy() early in the boot process often fail. // Apparently this is expected behaviour -- "not a bug". // Accordingly, we suppress syslog messages for the first three minutes after boot. // If we are still getting failures after three minutes, then we log them. if ((mDNSu32)mDNSPlatformRawTime() > (mDNSu32)(mDNSPlatformOneSecond * 180)) LogMsg("mDNSPlatformSetDNSConfig: Error: dns_configuration_copy returned NULL"); } else { LogInfo("mDNSPlatformSetDNSConfig: config->n_resolver = %d, generation %llu, last %llu", config->n_resolver, config->generation, m->p->LastConfigGeneration); // For every network change, mDNSPlatformSetDNSConfig is called twice. First, // to update the search domain list (in which case, the setsearch bool is set); // and second, to update the DNS server list (in which case, the setservers bool // is set). The code assumes only one of these flags, setsearch or setserver, // will be set when mDNSPlatformSetDNSConfig is called to handle a network change. // The mDNSPlatformSetDNSConfig function also assumes that ackCfg will be set // when setservers is set. // The search domains update occurs on every network change to avoid sync issues // that may occur if a network change happens during the processing // of a network change. The dns servers update occurs when the DNS config // changes. The dns servers stay in sync by saving the config's generation number // on every update; and only updating when the generation number changes. // If this is a DNS server update and the configuration hasn't changed, then skip update if (setservers && m->p->LastConfigGeneration == config->generation) { LogInfo("mDNSPlatformSetDNSConfig(setservers): generation number %llu same, not processing", config->generation); dns_configuration_free(config); SetupDDNSDomains(fqdn, RegDomains, BrowseDomains); return mDNSfalse; } #if APPLE_OSX_mDNSResponder SetupActiveDirectoryDomain(config); #endif // With scoped DNS, we don't want to answer a non-scoped question using a scoped cache entry // and vice-versa. As we compare resolverGroupID for matching cache entry with question, we need // to make sure that they don't match. We ensure this by always bumping up resolverGroupID between // the two calls to ConfigResolvers DNSServers for scoped and non-scoped can never have the // same resolverGroupID. // // All non-scoped resolvers use the same resolverGroupID i.e, we treat them all equally. ConfigResolvers(config, kScopeNone, setsearch, setservers, &sdc, ++resolverGroupID); resolverGroupID += config->n_resolver; ConfigResolvers(config, kScopeInterfaceID, setsearch, setservers, &sdc, resolverGroupID); resolverGroupID += config->n_scoped_resolver; ConfigResolvers(config, kScopeServiceID, setsearch, setservers, &sdc, resolverGroupID); // Acking provides a hint to other processes that the current DNS configuration has completed // its update. When configd receives the ack, it publishes a notification. // Applications monitoring the notification then know when to re-issue their DNS queries // after a network change occurs. if (ackConfig) { // Note: We have to set the generation number here when we are acking. // For every DNS configuration change, we do the following: // // 1) Copy dns configuration, handle search domains change // 2) Copy dns configuration, handle dns server change // // If we update the generation number at step (1), we won't process the // DNS servers the second time because generation number would be the same. // As we ack only when we process dns servers, we set the generation number // during acking. m->p->LastConfigGeneration = config->generation; LogInfo("mDNSPlatformSetDNSConfig: Acking configuration setservers %d, setsearch %d", setservers, setsearch); AckConfigd(config); } dns_configuration_free(config); if (setsearch) FinalizeSearchDomainHash(&sdc); } } #endif // MDNS_NO_DNSINFO SetupDDNSDomains(fqdn, RegDomains, BrowseDomains); return mDNStrue; } mDNSexport mStatus mDNSPlatformGetPrimaryInterface(mDNSAddr *v4, mDNSAddr *v6, mDNSAddr *r) { char buf[256]; CFDictionaryRef dict = SCDynamicStoreCopyValue(NULL, NetworkChangedKey_IPv4); if (dict) { r->type = mDNSAddrType_IPv4; r->ip.v4 = zerov4Addr; CFStringRef string = CFDictionaryGetValue(dict, kSCPropNetIPv4Router); if (string) { if (!CFStringGetCString(string, buf, 256, kCFStringEncodingUTF8)) LogMsg("Could not convert router to CString"); else { struct sockaddr_in saddr; saddr.sin_len = sizeof(saddr); saddr.sin_family = AF_INET; saddr.sin_port = 0; inet_aton(buf, &saddr.sin_addr); *(in_addr_t *)&r->ip.v4 = saddr.sin_addr.s_addr; } } string = CFDictionaryGetValue(dict, kSCDynamicStorePropNetPrimaryInterface); if (string) { mDNSBool HavePrimaryGlobalv6 = mDNSfalse; // does the primary interface have a global v6 address? struct ifaddrs *ifa = myGetIfAddrs(1); *v4 = *v6 = zeroAddr; if (!CFStringGetCString(string, buf, 256, kCFStringEncodingUTF8)) { LogMsg("Could not convert router to CString"); goto exit; } // find primary interface in list while (ifa && (mDNSIPv4AddressIsZero(v4->ip.v4) || mDNSv4AddressIsLinkLocal(&v4->ip.v4) || !HavePrimaryGlobalv6)) { if (!ifa->ifa_addr) { LogMsg("Skip interface, %s, since ifa_addr is not set.", (ifa->ifa_name) ? ifa->ifa_name: "name not found"); ifa = ifa->ifa_next; continue; } mDNSAddr tmp6 = zeroAddr; if (!strcmp(buf, ifa->ifa_name)) { if (ifa->ifa_addr->sa_family == AF_INET) { if (mDNSIPv4AddressIsZero(v4->ip.v4) || mDNSv4AddressIsLinkLocal(&v4->ip.v4)) SetupAddr(v4, ifa->ifa_addr); } else if (ifa->ifa_addr->sa_family == AF_INET6) { SetupAddr(&tmp6, ifa->ifa_addr); if (tmp6.ip.v6.b[0] >> 5 == 1) // global prefix: 001 { HavePrimaryGlobalv6 = mDNStrue; *v6 = tmp6; } } } else { // We'll take a V6 address from the non-primary interface if the primary interface doesn't have a global V6 address if (!HavePrimaryGlobalv6 && ifa->ifa_addr->sa_family == AF_INET6 && !v6->ip.v6.b[0]) { SetupAddr(&tmp6, ifa->ifa_addr); if (tmp6.ip.v6.b[0] >> 5 == 1) *v6 = tmp6; } } ifa = ifa->ifa_next; } // Note that while we advertise v6, we still require v4 (possibly NAT'd, but not link-local) because we must use // V4 to communicate w/ our DNS server } exit: CFRelease(dict); } return mStatus_NoError; } mDNSexport void mDNSPlatformDynDNSHostNameStatusChanged(const domainname *const dname, const mStatus status) { LogInfo("mDNSPlatformDynDNSHostNameStatusChanged %d %##s", status, dname->c); char uname[MAX_ESCAPED_DOMAIN_NAME]; // Max legal C-string name, including terminating NUL ConvertDomainNameToCString(dname, uname); char *p = uname; while (*p) { *p = tolower(*p); if (!(*(p+1)) && *p == '.') *p = 0; // if last character, strip trailing dot p++; } // We need to make a CFDictionary called "State:/Network/DynamicDNS" containing (at present) a single entity. // That single entity is a CFDictionary with name "HostNames". // The "HostNames" CFDictionary contains a set of name/value pairs, where the each name is the FQDN // in question, and the corresponding value is a CFDictionary giving the state for that FQDN. // (At present we only support a single FQDN, so this dictionary holds just a single name/value pair.) // The CFDictionary for each FQDN holds (at present) a single name/value pair, // where the name is "Status" and the value is a CFNumber giving an errror code (with zero meaning success). const CFStringRef StateKeys [1] = { CFSTR("HostNames") }; const CFStringRef HostKeys [1] = { CFStringCreateWithCString(NULL, uname, kCFStringEncodingUTF8) }; const CFStringRef StatusKeys[1] = { CFSTR("Status") }; if (!HostKeys[0]) LogMsg("SetDDNSNameStatus: CFStringCreateWithCString(%s) failed", uname); else { const CFNumberRef StatusVals[1] = { CFNumberCreate(NULL, kCFNumberSInt32Type, &status) }; if (!StatusVals[0]) LogMsg("SetDDNSNameStatus: CFNumberCreate(%d) failed", status); else { const CFDictionaryRef HostVals[1] = { CFDictionaryCreate(NULL, (void*)StatusKeys, (void*)StatusVals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks) }; if (HostVals[0]) { const CFDictionaryRef StateVals[1] = { CFDictionaryCreate(NULL, (void*)HostKeys, (void*)HostVals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks) }; if (StateVals[0]) { CFDictionaryRef StateDict = CFDictionaryCreate(NULL, (void*)StateKeys, (void*)StateVals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (StateDict) { mDNSDynamicStoreSetConfig(kmDNSDynamicConfig, mDNSNULL, StateDict); CFRelease(StateDict); } CFRelease(StateVals[0]); } CFRelease(HostVals[0]); } CFRelease(StatusVals[0]); } CFRelease(HostKeys[0]); } } #if APPLE_OSX_mDNSResponder #if !NO_AWACS // checks whether a domain is present in Setup:/Network/BackToMyMac. Just because there is a key in the // keychain for a domain, it does not become a valid BTMM domain. If things get inconsistent, this will // help catch it mDNSlocal mDNSBool IsBTMMDomain(domainname *d) { SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:IsBTMMDomain"), NULL, NULL); if (!store) { LogMsg("IsBTMMDomain: SCDynamicStoreCreate failed: %s", SCErrorString(SCError())); return mDNSfalse; } CFDictionaryRef btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac); if (btmm) { CFIndex size = CFDictionaryGetCount(btmm); char buf[MAX_ESCAPED_DOMAIN_NAME]; // Max legal C-string name, including terminating NUL const void *key[size]; const void *val[size]; domainname dom; int i; CFDictionaryGetKeysAndValues(btmm, key, val); for (i = 0; i < size; i++) { LogInfo("BackToMyMac %d", i); if (!CFStringGetCString(key[i], buf, sizeof(buf), kCFStringEncodingUTF8)) LogMsg("IsBTMMDomain: ERROR!! Can't read BackToMyMac %d key %s", i, buf); else { mDNSu32 uid = atoi(buf); if (!CFStringGetCString(val[i], buf, sizeof(buf), kCFStringEncodingUTF8)) LogMsg("IsBTMMDomain: Can't read BackToMyMac %d val %s", i, buf); else if (MakeDomainNameFromDNSNameString(&dom, buf) && dom.c[0]) { if (SameDomainName(&dom, d)) { LogInfo("IsBTMMDomain: Domain %##s is a btmm domain, uid %u", d->c, uid); CFRelease(btmm); CFRelease(store); return mDNStrue; } } } } CFRelease(btmm); } CFRelease(store); LogInfo("IsBTMMDomain: Domain %##s not a btmm domain", d->c); return mDNSfalse; } // Appends data to the buffer mDNSlocal int AddOneItem(char *buf, int bufsz, char *data, int *currlen) { int len; len = strlcpy(buf + *currlen, data, bufsz - *currlen); if (len >= (bufsz - *currlen)) { // if we have exceeded the space in buf, it has already been NULL terminated // and we have nothing more to do. Set currlen to the last byte so that the caller // knows to do the right thing LogMsg("AddOneItem: Exceeded the max buffer size currlen %d, len %d", *currlen, len); *currlen = bufsz - 1; return -1; } else { (*currlen) += len; } buf[*currlen] = ','; if (*currlen >= bufsz) { LogMsg("AddOneItem: ERROR!! How can currlen be %d", *currlen); *currlen = bufsz - 1; buf[*currlen] = 0; return -1; } // if we have filled up the buffer exactly, then there is no more work to do if (*currlen == bufsz - 1) { buf[*currlen] = 0; return -1; } (*currlen)++; return *currlen; } // If we have at least one BTMM domain, then trigger the connection to the relay. If we have no // BTMM domains, then bring down the connection to the relay. mDNSlocal void UpdateBTMMRelayConnection(mDNS *const m) { DomainAuthInfo *BTMMDomain = mDNSNULL; DomainAuthInfo *FoundInList; static mDNSBool AWACSDConnected = mDNSfalse; char AllUsers[1024]; // maximum size of mach message char AllPass[1024]; // maximum size of mach message char username[MAX_DOMAIN_LABEL + 1]; int currulen = 0; int currplen = 0; // if a domain is being deleted, we want to send a disconnect. If we send a disconnect now, // we may not be able to send the dns queries over the relay connection which may be needed // for sending the deregistrations. Hence, we need to delay sending the disconnect. But we // need to make sure that we send the disconnect before attempting the next connect as the // awacs connections are redirected based on usernames. // // For now we send a disconnect immediately. When we start sending dns queries over the relay // connection, we will need to fix this. for (FoundInList = m->AuthInfoList; FoundInList; FoundInList = FoundInList->next) if (!FoundInList->deltime && FoundInList->AutoTunnel && IsBTMMDomain(&FoundInList->domain)) { // We need the passwd from the first domain. BTMMDomain = FoundInList; ConvertDomainLabelToCString_unescaped((domainlabel *)BTMMDomain->domain.c, username); LogInfo("UpdateBTMMRelayConnection: user %s for domain %##s", username, BTMMDomain->domain.c); if (AddOneItem(AllUsers, sizeof(AllUsers), username, &currulen) == -1) break; if (AddOneItem(AllPass, sizeof(AllPass), BTMMDomain->b64keydata, &currplen) == -1) break; } if (BTMMDomain) { // In the normal case (where we neither exceed the buffer size nor write bytes that // fit exactly into the buffer), currulen/currplen should be a different size than // (AllUsers - 1) / (AllPass - 1). In that case, we need to override the "," with a NULL byte. if (currulen != (int)(sizeof(AllUsers) - 1)) AllUsers[currulen - 1] = 0; if (currplen != (int)(sizeof(AllPass) - 1)) AllPass[currplen - 1] = 0; LogInfo("UpdateBTMMRelayConnection: AWS_Connect for user %s", AllUsers); AWACS_Connect(AllUsers, AllPass, "hello.connectivity.me.com"); AWACSDConnected = mDNStrue; } else { // Disconnect only if we connected previously if (AWACSDConnected) { LogInfo("UpdateBTMMRelayConnection: AWS_Disconnect"); AWACS_Disconnect(); AWACSDConnected = mDNSfalse; } else LogInfo("UpdateBTMMRelayConnection: Not calling AWS_Disconnect"); } } #elif !TARGET_OS_EMBEDDED mDNSlocal void UpdateBTMMRelayConnection(mDNS *const m) { (void) m; // Unused LogInfo("UpdateBTMMRelayConnection: AWACS connection not started, no AWACS library"); } #endif // ! NO_AWACS #if !TARGET_OS_EMBEDDED mDNSlocal void ProcessConndConfigChanges(void); #endif #endif // APPLE_OSX_mDNSResponder // MUST be called holding the lock mDNSlocal void SetDomainSecrets_internal(mDNS *m) { #ifdef NO_SECURITYFRAMEWORK (void) m; LogMsg("Note: SetDomainSecrets: no keychain support"); #else mDNSBool haveAutoTunnels = mDNSfalse; LogInfo("SetDomainSecrets"); // Rather than immediately deleting all keys now, we mark them for deletion in ten seconds. // In the case where the user simultaneously removes their DDNS host name and the key // for it, this gives mDNSResponder ten seconds to gracefully delete the name from the // server before it loses access to the necessary key. Otherwise, we'd leave orphaned // address records behind that we no longer have permission to delete. DomainAuthInfo *ptr; for (ptr = m->AuthInfoList; ptr; ptr = ptr->next) ptr->deltime = NonZeroTime(m->timenow + mDNSPlatformOneSecond*10); #if APPLE_OSX_mDNSResponder { // Mark all TunnelClients for deletion ClientTunnel *client; for (client = m->TunnelClients; client; client = client->next) { LogInfo("SetDomainSecrets: tunnel to %##s marked for deletion", client->dstname.c); client->MarkedForDeletion = mDNStrue; } } #endif // APPLE_OSX_mDNSResponder // String Array used to write list of private domains to Dynamic Store CFMutableArrayRef sa = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); if (!sa) { LogMsg("SetDomainSecrets: CFArrayCreateMutable failed"); return; } CFIndex i; CFDataRef data = NULL; const int itemsPerEntry = 4; // domain name, key name, key value, Name value CFArrayRef secrets = NULL; int err = mDNSKeychainGetSecrets(&secrets); if (err || !secrets) LogMsg("SetDomainSecrets: mDNSKeychainGetSecrets failed error %d CFArrayRef %p", err, secrets); else { CFIndex ArrayCount = CFArrayGetCount(secrets); // Iterate through the secrets for (i = 0; i < ArrayCount; ++i) { mDNSBool AutoTunnel; int j, offset; CFArrayRef entry = CFArrayGetValueAtIndex(secrets, i); if (CFArrayGetTypeID() != CFGetTypeID(entry) || itemsPerEntry != CFArrayGetCount(entry)) { LogMsg("SetDomainSecrets: malformed entry %d, itemsPerEntry %d", i, itemsPerEntry); continue; } for (j = 0; j < CFArrayGetCount(entry); ++j) if (CFDataGetTypeID() != CFGetTypeID(CFArrayGetValueAtIndex(entry, j))) { LogMsg("SetDomainSecrets: malformed entry item %d", j); continue; } // The names have already been vetted by the helper, but checking them again here helps humans and automated tools verify correctness // Max legal domainname as C-string, including space for btmmprefix and terminating NUL // Get DNS domain this key is for (kmDNSKcWhere) char stringbuf[MAX_ESCAPED_DOMAIN_NAME + sizeof(btmmprefix)]; data = CFArrayGetValueAtIndex(entry, kmDNSKcWhere); if (CFDataGetLength(data) >= (int)sizeof(stringbuf)) { LogMsg("SetDomainSecrets: Bad kSecServiceItemAttr length %d", CFDataGetLength(data)); continue; } CFDataGetBytes(data, CFRangeMake(0, CFDataGetLength(data)), (UInt8 *)stringbuf); stringbuf[CFDataGetLength(data)] = '\0'; AutoTunnel = mDNSfalse; offset = 0; if (!strncmp(stringbuf, dnsprefix, strlen(dnsprefix))) offset = strlen(dnsprefix); else if (!strncmp(stringbuf, btmmprefix, strlen(btmmprefix))) { AutoTunnel = mDNStrue; offset = strlen(btmmprefix); } domainname domain; if (!MakeDomainNameFromDNSNameString(&domain, stringbuf + offset)) { LogMsg("SetDomainSecrets: bad key domain %s", stringbuf); continue; } // Get key name (kmDNSKcAccount) data = CFArrayGetValueAtIndex(entry, kmDNSKcAccount); if (CFDataGetLength(data) >= (int)sizeof(stringbuf)) { LogMsg("SetDomainSecrets: Bad kSecAccountItemAttr length %d", CFDataGetLength(data)); continue; } CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), (UInt8 *)stringbuf); stringbuf[CFDataGetLength(data)] = '\0'; domainname keyname; if (!MakeDomainNameFromDNSNameString(&keyname, stringbuf)) { LogMsg("SetDomainSecrets: bad key name %s", stringbuf); continue; } // Get key data (kmDNSKcKey) data = CFArrayGetValueAtIndex(entry, kmDNSKcKey); if (CFDataGetLength(data) >= (int)sizeof(stringbuf)) { LogMsg("SetDomainSecrets: Shared secret too long: %d", CFDataGetLength(data)); continue; } CFDataGetBytes(data, CFRangeMake(0, CFDataGetLength(data)), (UInt8 *)stringbuf); stringbuf[CFDataGetLength(data)] = '\0'; // mDNS_SetSecretForDomain requires NULL-terminated C string for key // Get the Name of the keychain entry (kmDNSKcName) host or host:port // The hostname also has the port number and ":". It should take a maximum of 6 bytes. char hostbuf[MAX_ESCAPED_DOMAIN_NAME + 6]; // Max legal domainname as C-string, including terminating NUL data = CFArrayGetValueAtIndex(entry, kmDNSKcName); if (CFDataGetLength(data) >= (int)sizeof(hostbuf)) { LogMsg("SetDomainSecrets: host:port data too long: %d", CFDataGetLength(data)); continue; } CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), (UInt8 *)hostbuf); hostbuf[CFDataGetLength(data)] = '\0'; domainname hostname; mDNSIPPort port; char *hptr; hptr = strchr(hostbuf, ':'); port.NotAnInteger = 0; if (hptr) { mDNSu8 *p; mDNSu16 val = 0; *hptr++ = '\0'; while(hptr && *hptr != 0) { if (*hptr < '0' || *hptr > '9') { LogMsg("SetDomainSecrets: Malformed Port number %d, val %d", *hptr, val); val = 0; break;} val = val * 10 + *hptr - '0'; hptr++; } if (!val) continue; p = (mDNSu8 *)&val; port.NotAnInteger = p[0] << 8 | p[1]; } // The hostbuf is of the format dsid@hostname:port. We don't care about the dsid. hptr = strchr(hostbuf, '@'); if (hptr) hptr++; else hptr = hostbuf; if (!MakeDomainNameFromDNSNameString(&hostname, hptr)) { LogMsg("SetDomainSecrets: bad host name %s", hptr); continue; } DomainAuthInfo *FoundInList; for (FoundInList = m->AuthInfoList; FoundInList; FoundInList = FoundInList->next) if (SameDomainName(&FoundInList->domain, &domain)) break; #if APPLE_OSX_mDNSResponder if (FoundInList) { // If any client tunnel destination is in this domain, set deletion flag to false ClientTunnel *client; for (client = m->TunnelClients; client; client = client->next) if (FoundInList == GetAuthInfoForName_internal(m, &client->dstname)) { LogInfo("SetDomainSecrets: tunnel to %##s no longer marked for deletion", client->dstname.c); client->MarkedForDeletion = mDNSfalse; } } #endif // APPLE_OSX_mDNSResponder // Uncomment the line below to view the keys as they're read out of the system keychain // DO NOT SHIP CODE THIS WAY OR YOU'LL LEAK SECRET DATA INTO A PUBLICLY READABLE FILE! //LogInfo("SetDomainSecrets: domain %##s keyname %##s key %s hostname %##s port %d", &domain.c, &keyname.c, stringbuf, hostname.c, (port.b[0] << 8 | port.b[1])); LogInfo("SetDomainSecrets: domain %##s keyname %##s hostname %##s port %d", &domain.c, &keyname.c, hostname.c, (port.b[0] << 8 | port.b[1])); // If didn't find desired domain in the list, make a new entry ptr = FoundInList; if (FoundInList && FoundInList->AutoTunnel && haveAutoTunnels == mDNSfalse) haveAutoTunnels = mDNStrue; if (!FoundInList) { ptr = (DomainAuthInfo*)mallocL("DomainAuthInfo", sizeof(*ptr)); if (!ptr) { LogMsg("SetDomainSecrets: No memory"); continue; } } //LogInfo("SetDomainSecrets: %d of %d %##s", i, ArrayCount, &domain); // It is an AutoTunnel if the keychains tells us so (with btmm prefix) or if it is a TunnelModeDomain if (mDNS_SetSecretForDomain(m, ptr, &domain, &keyname, stringbuf, &hostname, &port, AutoTunnel) == mStatus_BadParamErr) { if (!FoundInList) mDNSPlatformMemFree(ptr); // If we made a new DomainAuthInfo here, and it turned out bad, dispose it immediately continue; } ConvertDomainNameToCString(&domain, stringbuf); CFStringRef cfs = CFStringCreateWithCString(NULL, stringbuf, kCFStringEncodingUTF8); if (cfs) { CFArrayAppendValue(sa, cfs); CFRelease(cfs); } } CFRelease(secrets); } if (!privateDnsArray || !CFEqual(privateDnsArray, sa)) { if (privateDnsArray) CFRelease(privateDnsArray); privateDnsArray = sa; CFRetain(privateDnsArray); mDNSDynamicStoreSetConfig(kmDNSPrivateConfig, mDNSNULL, privateDnsArray); } CFRelease(sa); #if APPLE_OSX_mDNSResponder { // clean up ClientTunnels ClientTunnel **pp = &m->TunnelClients; while (*pp) { if ((*pp)->MarkedForDeletion) { ClientTunnel *cur = *pp; LogInfo("SetDomainSecrets: removing client %p %##s from list", cur, cur->dstname.c); if (cur->q.ThisQInterval >= 0) mDNS_StopQuery(m, &cur->q); AutoTunnelSetKeys(cur, mDNSfalse); *pp = cur->next; freeL("ClientTunnel", cur); } else pp = &(*pp)->next; } mDNSBool needAutoTunnelNAT = mDNSfalse; DomainAuthInfo *info; for (info = m->AuthInfoList; info; info = info->next) { if (info->AutoTunnel) { UpdateAutoTunnelDeviceInfoRecord(m, info); UpdateAutoTunnelHostRecord(m, info); UpdateAutoTunnelServiceRecords(m, info); UpdateAutoTunnel6Record(m, info); if (info->deltime) { if (info->AutoTunnelServiceStarted) info->AutoTunnelServiceStarted = mDNSfalse; } else if (info->AutoTunnelServiceStarted) needAutoTunnelNAT = true; UpdateAutoTunnelDomainStatus(info); } } // If the AutoTunnel NAT-T is no longer needed (& is currently running), stop it if (!needAutoTunnelNAT && m->AutoTunnelNAT.clientContext) { // stop the NAT operation, reset port, cleanup state mDNS_StopNATOperation_internal(m, &m->AutoTunnelNAT); m->AutoTunnelNAT.ExternalAddress = zerov4Addr; m->AutoTunnelNAT.NewAddress = zerov4Addr; m->AutoTunnelNAT.ExternalPort = zeroIPPort; m->AutoTunnelNAT.RequestedPort = zeroIPPort; m->AutoTunnelNAT.Lifetime = 0; m->AutoTunnelNAT.Result = mStatus_NoError; m->AutoTunnelNAT.clientContext = mDNSNULL; } UpdateAnonymousRacoonConfig(m); // Determine whether we need racoon to accept incoming connections ProcessConndConfigChanges(); // Update AutoTunnelInnerAddress values and default ipsec policies as necessary } #endif // APPLE_OSX_mDNSResponder CheckSuppressUnusableQuestions(m); #endif /* NO_SECURITYFRAMEWORK */ } mDNSexport void SetDomainSecrets(mDNS *m) { #if DEBUG // Don't get secrets for BTMM if running in debug mode if (!IsDebugSocketInUse()) #endif SetDomainSecrets_internal(m); } mDNSlocal void SetLocalDomains(void) { CFMutableArrayRef sa = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); if (!sa) { LogMsg("SetLocalDomains: CFArrayCreateMutable failed"); return; } CFArrayAppendValue(sa, CFSTR("local")); CFArrayAppendValue(sa, CFSTR("254.169.in-addr.arpa")); CFArrayAppendValue(sa, CFSTR("8.e.f.ip6.arpa")); CFArrayAppendValue(sa, CFSTR("9.e.f.ip6.arpa")); CFArrayAppendValue(sa, CFSTR("a.e.f.ip6.arpa")); CFArrayAppendValue(sa, CFSTR("b.e.f.ip6.arpa")); mDNSDynamicStoreSetConfig(kmDNSMulticastConfig, mDNSNULL, sa); CFRelease(sa); } mDNSlocal void GetCurrentPMSetting(const CFStringRef name, mDNSs32 *val) { CFDictionaryRef dict = SCDynamicStoreCopyValue(NULL, NetworkChangedKey_PowerSettings); if (!dict) { LogSPS("GetCurrentPMSetting: Could not get IOPM CurrentSettings dict"); } else { CFNumberRef number = CFDictionaryGetValue(dict, name); if (!number || CFGetTypeID(number) != CFNumberGetTypeID() || !CFNumberGetValue(number, kCFNumberSInt32Type, val)) *val = 0; CFRelease(dict); } } #if APPLE_OSX_mDNSResponder static CFMutableDictionaryRef spsStatusDict = NULL; static const CFStringRef kMetricRef = CFSTR("Metric"); mDNSlocal void SPSStatusPutNumber(CFMutableDictionaryRef dict, const mDNSu8* const ptr, CFStringRef key) { mDNSu8 tmp = (ptr[0] - '0') * 10 + ptr[1] - '0'; CFNumberRef num = CFNumberCreate(NULL, kCFNumberSInt8Type, &tmp); if (!num) LogMsg("SPSStatusPutNumber: Could not create CFNumber"); else { CFDictionarySetValue(dict, key, num); CFRelease(num); } } mDNSlocal CFMutableDictionaryRef SPSCreateDict(const mDNSu8* const ptr) { CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!dict) { LogMsg("SPSCreateDict: Could not create CFDictionary dict"); return dict; } char buffer[1024]; buffer[mDNS_snprintf(buffer, sizeof(buffer), "%##s", ptr) - 1] = 0; CFStringRef spsname = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); if (!spsname) { LogMsg("SPSCreateDict: Could not create CFString spsname full"); CFRelease(dict); return NULL; } CFDictionarySetValue(dict, CFSTR("FullName"), spsname); CFRelease(spsname); if (ptr[0] >= 2) SPSStatusPutNumber(dict, ptr + 1, CFSTR("Type")); if (ptr[0] >= 5) SPSStatusPutNumber(dict, ptr + 4, CFSTR("Portability")); if (ptr[0] >= 8) SPSStatusPutNumber(dict, ptr + 7, CFSTR("MarginalPower")); if (ptr[0] >= 11) SPSStatusPutNumber(dict, ptr +10, CFSTR("TotalPower")); mDNSu32 tmp = SPSMetric(ptr); CFNumberRef num = CFNumberCreate(NULL, kCFNumberSInt32Type, &tmp); if (!num) LogMsg("SPSCreateDict: Could not create CFNumber"); else { CFDictionarySetValue(dict, kMetricRef, num); CFRelease(num); } if (ptr[0] >= 12) { memcpy(buffer, ptr + 13, ptr[0] - 12); buffer[ptr[0] - 12] = 0; spsname = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); if (!spsname) { LogMsg("SPSCreateDict: Could not create CFString spsname"); CFRelease(dict); return NULL; } else { CFDictionarySetValue(dict, CFSTR("PrettyName"), spsname); CFRelease(spsname); } } return dict; } mDNSlocal CFComparisonResult CompareSPSEntries(const void *val1, const void *val2, void *context) { (void)context; return CFNumberCompare((CFNumberRef)CFDictionaryGetValue((CFDictionaryRef)val1, kMetricRef), (CFNumberRef)CFDictionaryGetValue((CFDictionaryRef)val2, kMetricRef), NULL); } mDNSlocal void UpdateSPSStatus(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) { NetworkInterfaceInfo* info = (NetworkInterfaceInfo*)question->QuestionContext; debugf("UpdateSPSStatus: %s %##s %s %s", info->ifname, question->qname.c, AddRecord ? "Add" : "Rmv", answer ? RRDisplayString(m, answer) : ""); mDNS_Lock(m); mDNS_UpdateAllowSleep(m); mDNS_Unlock(m); if (answer && SPSMetric(answer->rdata->u.name.c) > 999999) return; // Ignore instances with invalid names if (!spsStatusDict) { spsStatusDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!spsStatusDict) { LogMsg("UpdateSPSStatus: Could not create CFDictionary spsStatusDict"); return; } } CFStringRef ifname = CFStringCreateWithCString(NULL, info->ifname, kCFStringEncodingUTF8); if (!ifname) { LogMsg("UpdateSPSStatus: Could not create CFString ifname"); return; } CFMutableArrayRef array = NULL; if (!CFDictionaryGetValueIfPresent(spsStatusDict, ifname, (const void**) &array)) { array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); if (!array) { LogMsg("UpdateSPSStatus: Could not create CFMutableArray"); CFRelease(ifname); return; } CFDictionarySetValue(spsStatusDict, ifname, array); CFRelease(array); // let go of our reference, now that the dict has one } else if (!array) { LogMsg("UpdateSPSStatus: Could not get CFMutableArray for %s", info->ifname); CFRelease(ifname); return; } if (!answer) // special call that means the question has been stopped (because the interface is going away) CFArrayRemoveAllValues(array); else { CFMutableDictionaryRef dict = SPSCreateDict(answer->rdata->u.name.c); if (!dict) { CFRelease(ifname); return; } if (AddRecord) { if (!CFArrayContainsValue(array, CFRangeMake(0, CFArrayGetCount(array)), dict)) { int i=0; for (i=0; iifname, answer->rdata->u.name.c); } else { CFIndex i = CFArrayGetFirstIndexOfValue(array, CFRangeMake(0, CFArrayGetCount(array)), dict); if (i != -1) CFArrayRemoveValueAtIndex(array, i); else LogMsg("UpdateSPSStatus: %s array does not contain %##s", info->ifname, answer->rdata->u.name.c); } CFRelease(dict); } if (!m->ShutdownTime) mDNSDynamicStoreSetConfig(kmDNSSleepProxyServersState, info->ifname, array); CFRelease(ifname); } mDNSlocal mDNSs32 GetSystemSleepTimerSetting(void) { mDNSs32 val = -1; SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:GetSystemSleepTimerSetting"), NULL, NULL); if (!store) LogMsg("GetSystemSleepTimerSetting: SCDynamicStoreCreate failed: %s", SCErrorString(SCError())); else { CFDictionaryRef dict = SCDynamicStoreCopyValue(store, NetworkChangedKey_PowerSettings); if (dict) { CFNumberRef number = CFDictionaryGetValue(dict, CFSTR("System Sleep Timer")); if (number) CFNumberGetValue(number, kCFNumberSInt32Type, &val); CFRelease(dict); } CFRelease(store); } return val; } mDNSlocal void SetSPS(mDNS *const m) { // If we ever want to know InternetSharing status in the future, use DNSXEnableProxy() mDNSu8 sps = (OfferSleepProxyService && GetSystemSleepTimerSetting() == 0) ? mDNSSleepProxyMetric_IncidentalSoftware : 0; // For devices that are not running NAT, but are set to never sleep, we may choose to act // as a Sleep Proxy, but only for non-portable Macs (Portability > 35 means nominal weight < 3kg) //if (sps > mDNSSleepProxyMetric_PrimarySoftware && SPMetricPortability > 35) sps = 0; // If we decide to let laptops act as Sleep Proxy, we should do it only when running on AC power, not on battery // For devices that are unable to sleep at all to save power, or save 1W or less by sleeping, // it makes sense for them to offer low-priority Sleep Proxy service on the network. // We rate such a device as metric 70 ("Incidentally Available Hardware") if (SPMetricMarginalPower <= 60 && !sps) sps = mDNSSleepProxyMetric_IncidentalHardware; // If the launchd plist specifies an explicit value for the Intent Metric, then use that instead of the // computed value (currently 40 "Primary Network Infrastructure Software" or 80 "Incidentally Available Software") if (sps && OfferSleepProxyService && OfferSleepProxyService < 100) sps = OfferSleepProxyService; #ifdef NO_APPLETV_SLEEP_PROXY_ON_WIFI // AppleTVs are not reliable sleep proxy servers on WiFi. Do not offer to be a BSP if the WiFi interface is active. if (IsAppleTV()) { NetworkInterfaceInfo *intf = mDNSNULL; mDNSEthAddr bssid = zeroEthAddr; for (intf = GetFirstActiveInterface(m->HostInterfaces); intf; intf = GetFirstActiveInterface(intf->next)) { if (intf->InterfaceID == AWDLInterfaceID) continue; bssid = GetBSSID(intf->ifname); if (!mDNSSameEthAddress(&bssid, &zeroEthAddr)) { LogMsg("SetSPS: AppleTV on WiFi - not advertising BSP services"); sps = 0; break; } } } #endif // NO_APPLETV_SLEEP_PROXY_ON_WIFI mDNSCoreBeSleepProxyServer(m, sps, SPMetricPortability, SPMetricMarginalPower, SPMetricTotalPower, SPMetricFeatures); } // The definitions below should eventually come from some externally-supplied header file. // However, since these definitions can't really be changed without breaking binary compatibility, // they should never change, so in practice it should not be a big problem to have them defined here. enum { // commands from the daemon to the driver cmd_mDNSOffloadRR = 21, // give the mdns update buffer to the driver }; typedef union { void *ptr; mDNSOpaque64 sixtyfourbits; } FatPtr; typedef struct { // cmd_mDNSOffloadRR structure uint32_t command; // set to OffloadRR uint32_t rrBufferSize; // number of bytes of RR records uint32_t numUDPPorts; // number of SRV UDP ports uint32_t numTCPPorts; // number of SRV TCP ports uint32_t numRRRecords; // number of RR records uint32_t compression; // rrRecords - compression is base for compressed strings FatPtr rrRecords; // address of array of pointers to the rr records FatPtr udpPorts; // address of udp port list (SRV) FatPtr tcpPorts; // address of tcp port list (SRV) } mDNSOffloadCmd; #include #include mDNSlocal mDNSu16 GetPortArray(int trans, mDNSIPPort *portarray) { mDNS *const m = &mDNSStorage; const domainlabel *const tp = (trans == mDNSTransport_UDP) ? (const domainlabel *)"\x4_udp" : (const domainlabel *)"\x4_tcp"; int count = 0; AuthRecord *rr; for (rr = m->ResourceRecords; rr; rr=rr->next) { if (rr->resrec.rrtype == kDNSType_SRV && SameDomainLabel(ThirdLabel(rr->resrec.name)->c, tp->c)) { if (!portarray) count++; else { int i; for (i = 0; i < count; i++) if (mDNSSameIPPort(portarray[i], rr->resrec.rdata->u.srv.port)) break; // Add it into the port list only if it not already present in the list if (i >= count) portarray[count++] = rr->resrec.rdata->u.srv.port; } } } // If Back to My Mac is on, also wake for packets to the IPSEC UDP port (4500) if (trans == mDNSTransport_UDP && m->AutoTunnelNAT.clientContext) { LogSPS("GetPortArray Back to My Mac at %d", count); if (portarray) portarray[count] = IPSECPort; count++; } return(count); } #if APPLE_OSX_mDNSResponder && !TARGET_OS_EMBEDDED mDNSlocal mDNSBool SupportsTCPKeepAlive() { IOReturn ret = kIOReturnSuccess; CFTypeRef obj = NULL; mDNSBool supports = mDNSfalse; ret = IOPlatformCopyFeatureActive(CFSTR("TCPKeepAliveDuringSleep"), &obj); if ((kIOReturnSuccess == ret) && (obj != NULL)) { supports = (obj == kCFBooleanTrue)? mDNStrue : mDNSfalse; CFRelease(obj); } LogSPS("%s: The hardware %s TCP Keep Alive", __func__, (supports ? "supports" : "does not support")); return supports; } mDNSlocal mDNSBool OnBattery(void) { CFTypeRef powerInfo = IOPSCopyPowerSourcesInfo(); CFTypeRef powerSrc = IOPSGetProvidingPowerSourceType(powerInfo); mDNSBool result = mDNSfalse; if (powerInfo != NULL) { result = CFEqual(CFSTR(kIOPSBatteryPowerValue), powerSrc); CFRelease(powerInfo); } LogSPS("%s: The system is on %s", __func__, (result)? "Battery" : "AC Power"); return result; } #endif // !TARGET_OS_EMBEDDED #define TfrRecordToNIC(RR) \ ((!(RR)->resrec.InterfaceID && ((RR)->ForceMCast || IsLocalDomain((RR)->resrec.name)))) mDNSlocal mDNSu32 CountProxyRecords(uint32_t *const numbytes, NetworkInterfaceInfo *const intf, mDNSBool TCPKAOnly, mDNSBool supportsTCPKA) { mDNS *const m = &mDNSStorage; *numbytes = 0; int count = 0; AuthRecord *rr; for (rr = m->ResourceRecords; rr; rr=rr->next) { if (!(rr->AuthFlags & AuthFlagsWakeOnly) && rr->resrec.RecordType > kDNSRecordTypeDeregistering) { #if APPLE_OSX_mDNSResponder && !TARGET_OS_EMBEDDED mDNSBool isKeepAliveRecord = mDNS_KeepaliveRecord(&rr->resrec); // Skip over all other records if we are registering TCP KeepAlive records only // Skip over TCP KeepAlive records if the policy prohibits it or if the interface does not support TCP Keepalive. if ((TCPKAOnly && !isKeepAliveRecord) || (isKeepAliveRecord && !supportsTCPKA)) continue; // Update the record before calculating the number of bytes required // We offload the TCP Keepalive record even if the update fails. When the driver gets the record, it will // attempt to update the record again. if (isKeepAliveRecord && (UpdateKeepaliveRData(m, rr, intf, mDNSfalse, mDNSNULL) != mStatus_NoError)) LogSPS("CountProxyRecords: Failed to update keepalive record - %s", ARDisplayString(m, rr)); // Offload only Valid Keepalive records if (isKeepAliveRecord && !mDNSValidKeepAliveRecord(rr)) continue; #else (void) TCPKAOnly; // unused (void) supportsTCPKA; // unused (void) intf; // unused #endif // APPLE_OSX_mDNSResponder if (TfrRecordToNIC(rr)) { *numbytes += DomainNameLength(rr->resrec.name) + 10 + rr->resrec.rdestimate; LogSPS("CountProxyRecords: %3d size %5d total %5d %s", count, DomainNameLength(rr->resrec.name) + 10 + rr->resrec.rdestimate, *numbytes, ARDisplayString(m,rr)); count++; } } } return(count); } mDNSlocal void GetProxyRecords(DNSMessage *const msg, uint32_t *const numbytes, FatPtr *const records, mDNSBool TCPKAOnly, mDNSBool supportsTCPKA) { mDNS *const m = &mDNSStorage; mDNSu8 *p = msg->data; const mDNSu8 *const limit = p + *numbytes; InitializeDNSMessage(&msg->h, zeroID, zeroID); int count = 0; AuthRecord *rr; for (rr = m->ResourceRecords; rr; rr=rr->next) { if (!(rr->AuthFlags & AuthFlagsWakeOnly) && rr->resrec.RecordType > kDNSRecordTypeDeregistering) { #if APPLE_OSX_mDNSResponder && !TARGET_OS_EMBEDDED mDNSBool isKeepAliveRecord = mDNS_KeepaliveRecord(&rr->resrec); // Skip over all other records if we are registering TCP KeepAlive records only // Skip over TCP KeepAlive records if the policy prohibits it or if the interface does not support TCP Keepalive if ((TCPKAOnly && !isKeepAliveRecord) || (isKeepAliveRecord && !supportsTCPKA)) continue; // Offload only Valid Keepalive records if (isKeepAliveRecord && !mDNSValidKeepAliveRecord(rr)) continue; #else (void) TCPKAOnly; // unused (void) supportsTCPKA; // unused #endif // APPLE_OSX_mDNSResponder if (TfrRecordToNIC(rr)) { records[count].sixtyfourbits = zeroOpaque64; records[count].ptr = p; if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the 'unique' bit so PutResourceRecord will set it p = PutResourceRecordTTLWithLimit(msg, p, &msg->h.mDNS_numUpdates, &rr->resrec, rr->resrec.rroriginalttl, limit); rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet; // Make sure to clear 'unique' bit back to normal state LogSPS("GetProxyRecords: %3d start %p end %p size %5d total %5d %s", count, records[count].ptr, p, p - (mDNSu8 *)records[count].ptr, p - msg->data, ARDisplayString(m,rr)); count++; } } } *numbytes = p - msg->data; } mDNSexport mDNSBool SupportsInNICProxy(NetworkInterfaceInfo *const intf) { if(!UseInternalSleepProxy) { LogMsg("SupportsInNICProxy: Internal Sleep Proxy is disabled"); return mDNSfalse; } return CheckInterfaceSupport(intf, mDNS_IOREG_KEY); } mDNSexport mStatus ActivateLocalProxy(NetworkInterfaceInfo *const intf, mDNSBool *keepaliveOnly) // Called with the lock held { mStatus result = mStatus_UnknownErr; mDNSBool TCPKAOnly = mDNSfalse; mDNSBool supportsTCPKA = mDNSfalse; mDNSBool onbattery = mDNSfalse; io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, intf->ifname)); #if APPLE_OSX_mDNSResponder && !TARGET_OS_EMBEDDED onbattery = OnBattery(); // Check if the interface supports TCP Keepalives and the system policy says it is ok to offload TCP Keepalive records supportsTCPKA = (InterfaceSupportsKeepAlive(intf) && SupportsTCPKeepAlive()); // Only TCP Keepalive records are to be offloaded if // - The system is on battery // - OR wake for network access is not set but powernap is enabled TCPKAOnly = supportsTCPKA && ((mDNSStorage.SystemWakeOnLANEnabled == mDNS_WakeOnBattery) || onbattery); #else (void) onbattery; // unused; #endif if (!service) { LogMsg("ActivateLocalProxy: No service for interface %s", intf->ifname); return(mStatus_UnknownErr); } io_name_t n1, n2; IOObjectGetClass(service, n1); io_object_t parent = IO_OBJECT_NULL; kern_return_t kr = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parent); if (kr != KERN_SUCCESS) LogMsg("ActivateLocalProxy: IORegistryEntryGetParentEntry for %s/%s failed %d", intf->ifname, n1, kr); else { CFTypeRef ref = mDNSNULL; if (IsAppleTV()) { while (service) { ref = IORegistryEntryCreateCFProperty(parent, CFSTR(mDNS_IOREG_KEY), kCFAllocatorDefault, mDNSNULL); if (!ref) { IOObjectRelease(service); service = parent; kr = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parent); if (kr != KERN_SUCCESS) { IOObjectGetClass(service, n1); LogMsg("ActivateLocalProxy: IORegistryEntryGetParentEntry for %s/%s failed %d", intf->ifname, n1, kr); parent = IO_OBJECT_NULL; result = mStatus_BadStateErr; break; } } else { IOObjectGetClass(parent, n2); LogSPS("ActivateLocalProxy: Found %s Interface %s parent %s", mDNS_IOREG_KEY, intf->ifname, n2); break; } } } else { IOObjectGetClass(parent, n2); LogSPS("ActivateLocalProxy: Interface %s service %s parent %s", intf->ifname, n1, n2); ref = IORegistryEntryCreateCFProperty(parent, CFSTR(mDNS_IOREG_KEY), kCFAllocatorDefault, mDNSNULL); } if (!ref || parent == IO_OBJECT_NULL) LogSPS("ActivateLocalProxy: No mDNS_IOREG_KEY for interface %s/%s/%s", intf->ifname, n1, n2); else { if (CFGetTypeID(ref) != CFStringGetTypeID() || !CFEqual(ref, CFSTR(mDNS_IOREG_VALUE))) LogMsg("ActivateLocalProxy: mDNS_IOREG_KEY for interface %s/%s/%s value %s != %s", intf->ifname, n1, n2, CFStringGetCStringPtr(ref, mDNSNULL), mDNS_IOREG_VALUE); else if (!UseInternalSleepProxy) LogSPS("ActivateLocalProxy: Not using internal (NIC) sleep proxy for interface %s", intf->ifname); else { io_connect_t conObj; kr = IOServiceOpen(parent, mach_task_self(), mDNS_USER_CLIENT_CREATE_TYPE, &conObj); if (kr != KERN_SUCCESS) LogMsg("ActivateLocalProxy: IOServiceOpen for %s/%s/%s failed %d", intf->ifname, n1, n2, kr); else { mDNSOffloadCmd cmd; mDNSPlatformMemZero(&cmd, sizeof(cmd)); // When compiling 32-bit, make sure top 32 bits of 64-bit pointers get initialized to zero cmd.command = cmd_mDNSOffloadRR; cmd.numUDPPorts = GetPortArray(mDNSTransport_UDP, mDNSNULL); cmd.numTCPPorts = GetPortArray(mDNSTransport_TCP, mDNSNULL); cmd.numRRRecords = CountProxyRecords(&cmd.rrBufferSize, intf, TCPKAOnly, supportsTCPKA); cmd.compression = sizeof(DNSMessageHeader); DNSMessage *msg = (DNSMessage *)mallocL("mDNSOffloadCmd msg", sizeof(DNSMessageHeader) + cmd.rrBufferSize); cmd.rrRecords.ptr = cmd.numRRRecords ? mallocL("mDNSOffloadCmd rrRecords", cmd.numRRRecords * sizeof(FatPtr)) : NULL; cmd.udpPorts.ptr = cmd.numUDPPorts ? mallocL("mDNSOffloadCmd udpPorts" , cmd.numUDPPorts * sizeof(mDNSIPPort)) : NULL; cmd.tcpPorts.ptr = cmd.numTCPPorts ? mallocL("mDNSOffloadCmd tcpPorts" , cmd.numTCPPorts * sizeof(mDNSIPPort)) : NULL; LogSPS("ActivateLocalProxy: msg %p %d RR %p %d, UDP %p %d, TCP %p %d", msg, cmd.rrBufferSize, cmd.rrRecords.ptr, cmd.numRRRecords, cmd.udpPorts.ptr, cmd.numUDPPorts, cmd.tcpPorts.ptr, cmd.numTCPPorts); if (msg && cmd.rrRecords.ptr) GetProxyRecords(msg, &cmd.rrBufferSize, cmd.rrRecords.ptr, TCPKAOnly, supportsTCPKA); if (cmd.udpPorts.ptr) cmd.numUDPPorts = GetPortArray(mDNSTransport_UDP, cmd.udpPorts.ptr); if (cmd.tcpPorts.ptr) cmd.numTCPPorts = GetPortArray(mDNSTransport_TCP, cmd.tcpPorts.ptr); char outputData[2]; size_t outputDataSize = sizeof(outputData); kr = IOConnectCallStructMethod(conObj, 0, &cmd, sizeof(cmd), outputData, &outputDataSize); LogSPS("ActivateLocalProxy: IOConnectCallStructMethod for %s/%s/%s %d", intf->ifname, n1, n2, kr); if (kr == KERN_SUCCESS) result = mStatus_NoError; if (cmd.tcpPorts.ptr) freeL("mDNSOffloadCmd udpPorts", cmd.tcpPorts.ptr); if (cmd.udpPorts.ptr) freeL("mDNSOffloadCmd tcpPorts", cmd.udpPorts.ptr); if (cmd.rrRecords.ptr) freeL("mDNSOffloadCmd rrRecords", cmd.rrRecords.ptr); if (msg) freeL("mDNSOffloadCmd msg", msg); IOServiceClose(conObj); } } CFRelease(ref); } if (parent != IO_OBJECT_NULL) IOObjectRelease(parent); } if (service != IO_OBJECT_NULL) IOObjectRelease(service); *keepaliveOnly = TCPKAOnly; return result; } #endif // APPLE_OSX_mDNSResponder mDNSlocal mDNSu8 SystemWakeForNetworkAccess(void) { mDNSs32 val = 0; mDNSu8 ret = (mDNSu8)mDNS_NoWake; #if TARGET_OS_IOS LogSPS("SystemWakeForNetworkAccess: Sleep Proxy Client disabled by command-line option"); return ret; #endif if (DisableSleepProxyClient) { LogSPS("SystemWakeForNetworkAccess: Sleep Proxy Client disabled by command-line option"); return ret; } GetCurrentPMSetting(CFSTR("Wake On LAN"), &val); ret = (mDNSu8)(val != 0) ? mDNS_WakeOnAC : mDNS_NoWake; #if APPLE_OSX_mDNSResponder && !TARGET_OS_EMBEDDED // If we have TCP Keepalive support, system is capable of registering for TCP Keepalives. // Further policy decisions on whether to offload the records is handled during sleep processing. if ((ret == mDNS_NoWake) && SupportsTCPKeepAlive()) ret = (mDNSu8)mDNS_WakeOnBattery; #endif // APPLE_OSX_mDNSResponder LogSPS("SystemWakeForNetworkAccess: Wake On LAN: %d", ret); return ret; } mDNSlocal mDNSBool SystemSleepOnlyIfWakeOnLAN(void) { mDNSs32 val = 0; // PrioritizeNetworkReachabilityOverSleep has been deprecated. // GetCurrentPMSetting(CFSTR("PrioritizeNetworkReachabilityOverSleep"), &val); // Statically set the PrioritizeNetworkReachabilityOverSleep value to 1 for AppleTV if (IsAppleTV()) val = 1; return val != 0 ? mDNStrue : mDNSfalse; } #if APPLE_OSX_mDNSResponder // When sleeping, we always ensure that the _autotunnel6 record (if connected to RR relay) // gets deregistered, so that older peers are forced to connect over direct UDP instead of // the RR relay. // // When sleeping w/o a successful AutoTunnel NAT Mapping, we ensure that all our BTMM // service records are deregistered, so they do not appear in peers' Finder sidebars. // We do this by checking for the (non-autotunnel) SRV records, as the PTR and TXT records // depend on their associated SRV record and therefore will be deregistered together in a // single update with the SRV record. // // Also, the per-zone _kerberos TXT record is always there, including while sleeping, so // its presence shouldn't delay sleep. // // Note that the order of record deregistration is: first _autotunnel6 (if connected to RR // relay) and host records get deregistered, then SRV (UpdateAllSrvRecords), PTR and TXT. // // Also note that returning false here will not delay sleep past the maximum of 10 seconds. mDNSexport mDNSBool RecordReadyForSleep(AuthRecord *rr) { mDNS *const m = &mDNSStorage; if (!AuthRecord_uDNS(rr)) return mDNStrue; if ((rr->resrec.rrtype == kDNSType_AAAA) && SameDomainLabel(rr->namestorage.c, (const mDNSu8 *)"\x0c_autotunnel6")) { LogInfo("RecordReadyForSleep: %s not ready for sleep", ARDisplayString(m, rr)); return mDNSfalse; } if ((mDNSIPPortIsZero(m->AutoTunnelNAT.ExternalPort) || m->AutoTunnelNAT.Result)) { if (rr->resrec.rrtype == kDNSType_SRV && rr->state != regState_NoTarget && rr->zone && !SameDomainLabel(rr->namestorage.c, (const mDNSu8 *)"\x0b_autotunnel")) { DomainAuthInfo *info = GetAuthInfoForName_internal(m, rr->zone); if (info && info->AutoTunnel) { LogInfo("RecordReadyForSleep: %s not ready for sleep", ARDisplayString(m, rr)); return mDNSfalse; } } } return mDNStrue; } // Caller must hold the lock mDNSexport void RemoveAutoTunnel6Record(mDNS *const m) { DomainAuthInfo *info; // Set the address to zero before calling UpdateAutoTunnel6Record, so that it will // deregister the record, and the MemFree callback won't re-register. m->AutoTunnelRelayAddr = zerov6Addr; for (info = m->AuthInfoList; info; info = info->next) if (info->AutoTunnel) UpdateAutoTunnel6Record(m, info); } #if !TARGET_OS_EMBEDDED mDNSlocal mDNSBool IPv6AddressIsOnInterface(mDNSv6Addr ipv6Addr, char *ifname) { struct ifaddrs *ifa; struct ifaddrs *ifaddrs; mDNSAddr addr; if (if_nametoindex(ifname) == 0) {LogInfo("IPv6AddressIsOnInterface: Invalid name %s", ifname); return mDNSfalse;} if (getifaddrs(&ifaddrs) < 0) {LogInfo("IPv6AddressIsOnInterface: getifaddrs failed"); return mDNSfalse;} for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { if (strncmp(ifa->ifa_name, ifname, IFNAMSIZ) != 0) continue; if ((ifa->ifa_flags & IFF_UP) == 0 || !ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET6) continue; if (SetupAddr(&addr, ifa->ifa_addr) != mStatus_NoError) { LogInfo("IPv6AddressIsOnInterface: SetupAddr error, continuing to the next address"); continue; } if (mDNSSameIPv6Address(ipv6Addr, *(mDNSv6Addr*)&addr.ip.v6)) { LogInfo("IPv6AddressIsOnInterface: found %.16a", &ipv6Addr); break; } } freeifaddrs(ifaddrs); return ifa != NULL; } mDNSlocal mDNSv6Addr IPv6AddressFromString(char* buf) { mDNSv6Addr retVal; struct addrinfo hints; struct addrinfo *res0; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; hints.ai_flags = AI_NUMERICHOST; int err = getaddrinfo(buf, NULL, &hints, &res0); if (err) return zerov6Addr; retVal = *(mDNSv6Addr*)&((struct sockaddr_in6*)res0->ai_addr)->sin6_addr; freeaddrinfo(res0); return retVal; } mDNSlocal CFDictionaryRef CopyConnectivityBackToMyMacDict() { CFDictionaryRef connd = NULL; CFDictionaryRef BTMMDict = NULL; connd = SCDynamicStoreCopyValue(NULL, NetworkChangedKey_BTMMConnectivity); if (!connd) { LogInfo("CopyConnectivityBackToMyMacDict: SCDynamicStoreCopyValue failed: %s", SCErrorString(SCError())); goto end; } BTMMDict = CFDictionaryGetValue(connd, CFSTR("BackToMyMac")); if (!BTMMDict) { LogInfo("CopyConnectivityBackToMyMacDict: CFDictionaryGetValue: No value for BackToMyMac"); goto end; } // Non-dictionary is treated as non-existent dictionary if (CFGetTypeID(BTMMDict) != CFDictionaryGetTypeID()) { BTMMDict = NULL; LogMsg("CopyConnectivityBackToMyMacDict: BackToMyMac not a dictionary"); goto end; } CFRetain(BTMMDict); end: if (connd) CFRelease(connd); return BTMMDict; } #define MAX_IPV6_TEXTUAL 40 mDNSlocal mDNSv6Addr ParseBackToMyMacAddr(CFDictionaryRef BTMMDict, CFStringRef ifKey, CFStringRef addrKey) { mDNSv6Addr retVal = zerov6Addr; CFTypeRef string = NULL; char ifname[IFNAMSIZ]; char address[MAX_IPV6_TEXTUAL]; if (!BTMMDict) return zerov6Addr; if (!CFDictionaryGetValueIfPresent(BTMMDict, ifKey, &string)) { LogInfo("ParseBackToMyMacAddr: interface key does not exist"); return zerov6Addr; } if (!CFStringGetCString(string, ifname, IFNAMSIZ, kCFStringEncodingUTF8)) { LogMsg("ParseBackToMyMacAddr: Could not convert interface to CString"); return zerov6Addr; } if (!CFDictionaryGetValueIfPresent(BTMMDict, addrKey, &string)) { LogMsg("ParseBackToMyMacAddr: address key does not exist, but interface key does"); return zerov6Addr; } if (!CFStringGetCString(string, address, sizeof(address), kCFStringEncodingUTF8)) { LogMsg("ParseBackToMyMacAddr: Could not convert address to CString"); return zerov6Addr; } retVal = IPv6AddressFromString(address); LogInfo("ParseBackToMyMacAddr: %s (%s) %.16a", ifname, address, &retVal); if (mDNSIPv6AddressIsZero(retVal)) return zerov6Addr; if (!IPv6AddressIsOnInterface(retVal, ifname)) { LogMsg("ParseBackToMyMacAddr: %.16a is not on %s", &retVal, ifname); return zerov6Addr; } return retVal; } mDNSlocal CFDictionaryRef GetBackToMyMacZones(CFDictionaryRef BTMMDict) { CFTypeRef zones = NULL; if (!BTMMDict) return NULL; if (!CFDictionaryGetValueIfPresent(BTMMDict, CFSTR("Zones"), &zones)) { LogInfo("CopyBTMMZones: Zones key does not exist"); return NULL; } return zones; } mDNSlocal mDNSv6Addr ParseBackToMyMacZone(CFDictionaryRef zones, DomainAuthInfo* info) { mDNSv6Addr addr = zerov6Addr; char buffer[MAX_ESCAPED_DOMAIN_NAME]; CFStringRef domain = NULL; CFTypeRef theZone = NULL; if (!zones) return addr; ConvertDomainNameToCString(&info->domain, buffer); domain = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); if (!domain) return addr; if (CFDictionaryGetValueIfPresent(zones, domain, &theZone)) addr = ParseBackToMyMacAddr(theZone, CFSTR("Interface"), CFSTR("Address")); CFRelease(domain); return addr; } mDNSlocal void SetupBackToMyMacInnerAddresses(CFDictionaryRef BTMMDict) { mDNS *const m = &mDNSStorage; DomainAuthInfo* info; CFDictionaryRef zones = GetBackToMyMacZones(BTMMDict); mDNSv6Addr newAddr; for (info = m->AuthInfoList; info; info = info->next) { if (!info->AutoTunnel) continue; newAddr = ParseBackToMyMacZone(zones, info); if (mDNSSameIPv6Address(newAddr, info->AutoTunnelInnerAddress)) continue; info->AutoTunnelInnerAddress = newAddr; DeregisterAutoTunnelHostRecord(m, info); UpdateAutoTunnelHostRecord(m, info); UpdateAutoTunnelDomainStatus(info); } } // MUST be called holding the lock mDNSlocal void ProcessConndConfigChanges(void) { mDNS *const m = &mDNSStorage; CFDictionaryRef dict = CopyConnectivityBackToMyMacDict(); if (!dict) LogInfo("ProcessConndConfigChanges: No BTMM dictionary"); mDNSv6Addr relayAddr = ParseBackToMyMacAddr(dict, CFSTR("RelayInterface"), CFSTR("RelayAddress")); LogInfo("ProcessConndConfigChanges: relay %.16a", &relayAddr); SetupBackToMyMacInnerAddresses(dict); if (dict) CFRelease(dict); if (!mDNSSameIPv6Address(relayAddr, m->AutoTunnelRelayAddr)) { m->AutoTunnelRelayAddr = relayAddr; DomainAuthInfo* info; for (info = m->AuthInfoList; info; info = info->next) if (info->AutoTunnel) { DeregisterAutoTunnel6Record(m, info); UpdateAutoTunnel6Record(m, info); UpdateAutoTunnelDomainStatus(info); } // Determine whether we need racoon to accept incoming connections UpdateAnonymousRacoonConfig(m); } // If awacsd crashes or exits for some reason, restart it UpdateBTMMRelayConnection(m); } #endif // !TARGET_OS_EMBEDDED #endif /* APPLE_OSX_mDNSResponder */ mDNSlocal mDNSBool IsAppleNetwork(mDNS *const m) { DNSServer *s; // Determine if we're on AppleNW based on DNSServer having 17.x.y.z IPv4 addr for (s = m->DNSServers; s; s = s->next) { if (s->addr.ip.v4.b[0] == 17) { LogInfo("IsAppleNetwork: Found 17.x.y.z DNSServer concluding that we are on AppleNW: %##s %#a", s->domain.c, &s->addr); return mDNStrue; } } return mDNSfalse; } // Called with KQueueLock & mDNS lock // SetNetworkChanged is allowed to shorten (but not extend) the pause while we wait for configuration changes to settle mDNSlocal void SetNetworkChanged(mDNSs32 delay) { mDNS *const m = &mDNSStorage; mDNS_CheckLock(m); if (!m->NetworkChanged || m->NetworkChanged - NonZeroTime(m->timenow + delay) > 0) { m->NetworkChanged = NonZeroTime(m->timenow + delay); LogInfo("SetNetworkChanged: Scheduling in %d ticks", delay); } else LogInfo("SetNetworkChanged: *NOT* increasing delay from %d to %d", m->NetworkChanged - m->timenow, delay); } // Called with KQueueLock & mDNS lock mDNSlocal void SetKeyChainTimer(mDNSs32 delay) { mDNS *const m = &mDNSStorage; // If it's not set or it needs to happen sooner than when it's currently set if (!m->p->KeyChainTimer || m->p->KeyChainTimer - NonZeroTime(m->timenow + delay) > 0) { m->p->KeyChainTimer = NonZeroTime(m->timenow + delay); LogInfo("SetKeyChainTimer: %d", delay); } } mDNSexport void mDNSMacOSXNetworkChanged(void) { mDNS *const m = &mDNSStorage; LogInfo("*** Network Configuration Change *** %d ticks late%s", m->NetworkChanged ? mDNS_TimeNow(m) - m->NetworkChanged : 0, m->NetworkChanged ? "" : " (no scheduled configuration change)"); m->NetworkChanged = 0; // If we received a network change event and deferred processing, we're now dealing with it // If we have *any* TENTATIVE IPv6 addresses, wait until they've finished configuring int InfoSocket = socket(AF_INET6, SOCK_DGRAM, 0); if (InfoSocket > 0) { mDNSBool tentative = mDNSfalse; struct ifaddrs *ifa = myGetIfAddrs(1); while (ifa) { if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET6) { struct in6_ifreq ifr6; mDNSPlatformMemZero((char *)&ifr6, sizeof(ifr6)); strlcpy(ifr6.ifr_name, ifa->ifa_name, sizeof(ifr6.ifr_name)); ifr6.ifr_addr = *(struct sockaddr_in6 *)ifa->ifa_addr; // We need to check for IN6_IFF_TENTATIVE here, not IN6_IFF_NOTREADY, because // IN6_IFF_NOTREADY includes both IN6_IFF_TENTATIVE and IN6_IFF_DUPLICATED addresses. // We can expect that an IN6_IFF_TENTATIVE address will shortly become ready, // but an IN6_IFF_DUPLICATED address may not. if (ioctl(InfoSocket, SIOCGIFAFLAG_IN6, &ifr6) != -1) { if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE) { LogInfo("*** Network Configuration Change *** IPv6 address %.16a TENTATIVE, will retry", &ifr6.ifr_addr.sin6_addr); tentative = mDNStrue; // no need to check other interfaces if we already found out that one interface is TENTATIVE break; } } } ifa = ifa->ifa_next; } close(InfoSocket); if (tentative) { mDNS_Lock(m); SetNetworkChanged(mDNSPlatformOneSecond / 2); mDNS_Unlock(m); return; } LogInfo("*** Network Configuration Change *** No IPv6 address TENTATIVE, will continue"); } mDNSs32 utc = mDNSPlatformUTC(); m->SystemWakeOnLANEnabled = SystemWakeForNetworkAccess(); m->SystemSleepOnlyIfWakeOnLAN = SystemSleepOnlyIfWakeOnLAN(); MarkAllInterfacesInactive(utc); UpdateInterfaceList(utc); ClearInactiveInterfaces(utc); SetupActiveInterfaces(utc); ReorderInterfaceList(); #if APPLE_OSX_mDNSResponder #if !TARGET_OS_EMBEDDED mDNS_Lock(m); ProcessConndConfigChanges(); mDNS_Unlock(m); // Scan to find client tunnels whose questions have completed, // but whose local inner/outer addresses have changed since the tunnel was set up ClientTunnel *p; for (p = m->TunnelClients; p; p = p->next) if (p->q.ThisQInterval < 0) { DomainAuthInfo* info = GetAuthInfoForName(m, &p->dstname); if (!info) { LogMsg("mDNSMacOSXNetworkChanged: Could not get AuthInfo for %##s, removing tunnel keys", p->dstname.c); AutoTunnelSetKeys(p, mDNSfalse); } else { mDNSv6Addr inner = info->AutoTunnelInnerAddress; if (!mDNSIPPortIsZero(p->rmt_outer_port)) { mDNSAddr tmpSrc = zeroAddr; mDNSAddr tmpDst = { mDNSAddrType_IPv4, {{{0}}} }; tmpDst.ip.v4 = p->rmt_outer; mDNSPlatformSourceAddrForDest(&tmpSrc, &tmpDst); if (!mDNSSameIPv6Address(p->loc_inner, inner) || !mDNSSameIPv4Address(p->loc_outer, tmpSrc.ip.v4)) { AutoTunnelSetKeys(p, mDNSfalse); p->loc_inner = inner; p->loc_outer = tmpSrc.ip.v4; AutoTunnelSetKeys(p, mDNStrue); } } else { if (!mDNSSameIPv6Address(p->loc_inner, inner) || !mDNSSameIPv6Address(p->loc_outer6, m->AutoTunnelRelayAddr)) { AutoTunnelSetKeys(p, mDNSfalse); p->loc_inner = inner; p->loc_outer6 = m->AutoTunnelRelayAddr; AutoTunnelSetKeys(p, mDNStrue); } } } } #endif //!TARGET_OS_EMBEDDED SetSPS(m); NetworkInterfaceInfoOSX *i; for (i = m->p->InterfaceList; i; i = i->next) { if (!m->SPSSocket) // Not being Sleep Proxy Server; close any open BPF fds { if (i->BPF_fd >= 0 && CountProxyTargets(i, mDNSNULL, mDNSNULL) == 0) CloseBPF(i); } else // else, we're Sleep Proxy Server; open BPF fds { if (i->Exists && (i->Registered == i) && SPSInterface(i) && i->BPF_fd == -1) { LogMsg("%s mDNSMacOSXNetworkChanged: requesting BPF", i->ifinfo.ifname); i->BPF_fd = -2; mDNSRequestBPF(); } } } #endif // APPLE_OSX_mDNSResponder uDNS_SetupDNSConfig(m); mDNS_ConfigChanged(m); if (IsAppleNetwork(m) != mDNS_McastTracingEnabled) { mDNS_McastTracingEnabled = mDNS_McastTracingEnabled ? mDNSfalse : mDNStrue; LogInfo("mDNSMacOSXNetworkChanged: Multicast Tracing %s", mDNS_McastTracingEnabled ? "Enabled" : "Disabled"); UpdateDebugState(); } } // Copy the fourth slash-delimited element from either: // State:/Network/Interface//IPv4 // or // Setup:/Network/Service//Interface mDNSlocal CFStringRef CopyNameFromKey(CFStringRef key) { CFArrayRef a; CFStringRef name = NULL; a = CFStringCreateArrayBySeparatingStrings(NULL, key, CFSTR("/")); if (a && CFArrayGetCount(a) == 5) name = CFRetain(CFArrayGetValueAtIndex(a, 3)); if (a != NULL) CFRelease(a); return name; } // Whether a key from a network change notification corresponds to // an IP service that is explicitly configured for IPv4 Link Local mDNSlocal int ChangedKeysHaveIPv4LL(CFArrayRef inkeys) { CFDictionaryRef dict = NULL; CFMutableArrayRef a; const void **keys = NULL, **vals = NULL; CFStringRef pattern = NULL; int i, ic, j, jc; int found = 0; jc = CFArrayGetCount(inkeys); if (!jc) goto done; a = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); if (a == NULL) goto done; // Setup:/Network/Service/[^/]+/Interface pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetInterface); if (pattern == NULL) goto done; CFArrayAppendValue(a, pattern); CFRelease(pattern); // Setup:/Network/Service/[^/]+/IPv4 pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetIPv4); if (pattern == NULL) goto done; CFArrayAppendValue(a, pattern); CFRelease(pattern); dict = SCDynamicStoreCopyMultiple(NULL, NULL, a); CFRelease(a); if (!dict) { LogMsg("ChangedKeysHaveIPv4LL: Empty dictionary"); goto done; } ic = CFDictionaryGetCount(dict); vals = mDNSPlatformMemAllocate(sizeof (void *) * ic); keys = mDNSPlatformMemAllocate(sizeof (void *) * ic); CFDictionaryGetKeysAndValues(dict, keys, vals); // For each key we were given... for (j = 0; j < jc; j++) { CFStringRef key = CFArrayGetValueAtIndex(inkeys, j); CFStringRef ifname = NULL; char buf[256]; // It would be nice to use a regex here if (!CFStringHasPrefix(key, CFSTR("State:/Network/Interface/")) || !CFStringHasSuffix(key, kSCEntNetIPv4)) continue; if ((ifname = CopyNameFromKey(key)) == NULL) continue; if (mDNS_LoggingEnabled) { if (!CFStringGetCString(ifname, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0; LogInfo("ChangedKeysHaveIPv4LL: potential ifname %s", buf); } // Loop over the interfaces to find matching the ifname, and see if that one has kSCValNetIPv4ConfigMethodLinkLocal for (i = 0; i < ic; i++) { CFDictionaryRef ipv4dict; CFStringRef name; CFStringRef serviceid; CFStringRef configmethod; if (!CFStringHasSuffix(keys[i], kSCEntNetInterface)) continue; if (CFDictionaryGetTypeID() != CFGetTypeID(vals[i])) continue; if ((name = CFDictionaryGetValue(vals[i], kSCPropNetInterfaceDeviceName)) == NULL) continue; if (!CFEqual(ifname, name)) continue; if ((serviceid = CopyNameFromKey(keys[i])) == NULL) continue; if (mDNS_LoggingEnabled) { if (!CFStringGetCString(serviceid, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0; LogInfo("ChangedKeysHaveIPv4LL: found serviceid %s", buf); } pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, serviceid, kSCEntNetIPv4); CFRelease(serviceid); if (pattern == NULL) continue; ipv4dict = CFDictionaryGetValue(dict, pattern); CFRelease(pattern); if (!ipv4dict || CFDictionaryGetTypeID() != CFGetTypeID(ipv4dict)) continue; configmethod = CFDictionaryGetValue(ipv4dict, kSCPropNetIPv4ConfigMethod); if (!configmethod) continue; if (mDNS_LoggingEnabled) { if (!CFStringGetCString(configmethod, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0; LogInfo("ChangedKeysHaveIPv4LL: configmethod %s", buf); } if (CFEqual(configmethod, kSCValNetIPv4ConfigMethodLinkLocal)) { found++; break; } } CFRelease(ifname); } done: if (vals != NULL) mDNSPlatformMemFree(vals); if (keys != NULL) mDNSPlatformMemFree(keys); if (dict != NULL) CFRelease(dict); return found; } mDNSlocal void NetworkChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, void *context) { (void)store; // Parameter not used mDNS *const m = (mDNS *const)context; KQueueLock(); mDNS_Lock(m); //mDNSs32 delay = mDNSPlatformOneSecond * 2; // Start off assuming a two-second delay const mDNSs32 delay = (mDNSPlatformOneSecond + 39) / 40; // 25 ms delay int c = CFArrayGetCount(changedKeys); // Count changes CFRange range = { 0, c }; int c_host = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_Hostnames ) != 0); int c_comp = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_Computername) != 0); int c_udns = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_DNS ) != 0); int c_ddns = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_DynamicDNS ) != 0); int c_btmm = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_BackToMyMac ) != 0); int c_v4ll = ChangedKeysHaveIPv4LL(changedKeys); int c_fast = 0; // Do immediate network changed processing for "p2p*" interfaces and // for interfaces with the IFEF_DIRECTLINK or IFEF_AWDL flag set or association with a CarPlay // hosted SSID. { CFArrayRef labels; CFIndex n; for (int i = 0; i < c; i++) { CFStringRef key = CFArrayGetValueAtIndex(changedKeys, i); // Only look at keys with prefix "State:/Network/Interface/" if (!CFStringHasPrefix(key, NetworkChangedKey_StateInterfacePrefix)) continue; // And suffix "IPv6" or "IPv4". if (!CFStringHasSuffix(key, kSCEntNetIPv6) && !CFStringHasSuffix(key, kSCEntNetIPv4)) continue; labels = CFStringCreateArrayBySeparatingStrings(NULL, key, CFSTR("/")); if (labels == NULL) break; n = CFArrayGetCount(labels); // Interface changes will have keys of the form: // State:/Network/Interface//IPv6 // Thus five '/' seperated fields, the 4th one being the string. if (n == 5) { char buf[256]; // The 4th label (index = 3) should be the interface name. if (CFStringGetCString(CFArrayGetValueAtIndex(labels, 3), buf, sizeof(buf), kCFStringEncodingUTF8) && (strstr(buf, "p2p") || (getExtendedFlags(buf) & (IFEF_DIRECTLINK | IFEF_AWDL)) || IsCarPlaySSID(buf))) { LogInfo("NetworkChanged: interface %s qualifies for reduced change handling delay", buf); c_fast++; CFRelease(labels); break; } } CFRelease(labels); } } //if (c && c - c_host - c_comp - c_udns - c_ddns - c_btmm - c_v4ll - c_fast == 0) // delay = mDNSPlatformOneSecond/10; // If these were the only changes, shorten delay if (mDNS_LoggingEnabled) { int i; for (i=0; i1 ? "s" : "", c_host ? "(Local Hostname) " : "", c_comp ? "(Computer Name) " : "", c_udns ? "(DNS) " : "", c_ddns ? "(DynamicDNS) " : "", c_btmm ? "(BTMM) " : "", c_v4ll ? "(kSCValNetIPv4ConfigMethodLinkLocal) " : "", c_fast ? "(P2P/IFEF_DIRECTLINK/IFEF_AWDL/IsCarPlaySSID) " : "", delay, (c_ddns || c_btmm) ? " + SetKeyChainTimer" : ""); } SetNetworkChanged(delay); // Other software might pick up these changes to register or browse in WAB or BTMM domains, // so in order for secure updates to be made to the server, make sure to read the keychain and // setup the DomainAuthInfo before handing the network change. // If we don't, then we will first try to register services in the clear, then later setup the // DomainAuthInfo, which is incorrect. if (c_ddns || c_btmm) SetKeyChainTimer(delay); // Don't try to call mDNSMacOSXNetworkChanged() here -- we're running on the wrong thread mDNS_Unlock(m); KQueueUnlock("NetworkChanged"); } #if APPLE_OSX_mDNSResponder mDNSlocal void RefreshSPSStatus(const void *key, const void *value, void *context) { (void)context; char buf[IFNAMSIZ]; CFStringRef ifnameStr = (CFStringRef)key; CFArrayRef array = (CFArrayRef)value; if (!CFStringGetCString(ifnameStr, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0; LogInfo("RefreshSPSStatus: Updating SPS state for key %s, array count %d", buf, CFArrayGetCount(array)); mDNSDynamicStoreSetConfig(kmDNSSleepProxyServersState, buf, value); } #endif mDNSlocal void DynamicStoreReconnected(SCDynamicStoreRef store, void *info) { mDNS *const m = (mDNS *const)info; (void)store; KQueueLock(); // serialize with KQueueLoop() LogInfo("DynamicStoreReconnected: Reconnected"); // State:/Network/MulticastDNS SetLocalDomains(); // State:/Network/DynamicDNS if (m->FQDN.c[0]) mDNSPlatformDynDNSHostNameStatusChanged(&m->FQDN, 1); // Note: PrivateDNS and BackToMyMac are automatically populated when configd is restarted // as we receive network change notifications and thus not necessary. But we leave it here // so that if things are done differently in the future, this code still works. // State:/Network/PrivateDNS if (privateDnsArray) mDNSDynamicStoreSetConfig(kmDNSPrivateConfig, mDNSNULL, privateDnsArray); #if APPLE_OSX_mDNSResponder // State:/Network/BackToMyMac UpdateAutoTunnelDomainStatuses(m); // State:/Network/Interface/en0/SleepProxyServers if (spsStatusDict) CFDictionaryApplyFunction(spsStatusDict, RefreshSPSStatus, NULL); #endif KQueueUnlock("DynamicStoreReconnected"); } mDNSlocal mStatus WatchForNetworkChanges(mDNS *const m) { mStatus err = -1; SCDynamicStoreContext context = { 0, m, NULL, NULL, NULL }; SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:WatchForNetworkChanges"), NetworkChanged, &context); CFMutableArrayRef keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); CFStringRef pattern1 = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4); CFStringRef pattern2 = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6); CFMutableArrayRef patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); if (!store) { LogMsg("SCDynamicStoreCreate failed: %s", SCErrorString(SCError())); goto error; } if (!keys || !pattern1 || !pattern2 || !patterns) goto error; CFArrayAppendValue(keys, NetworkChangedKey_IPv4); CFArrayAppendValue(keys, NetworkChangedKey_IPv6); CFArrayAppendValue(keys, NetworkChangedKey_Hostnames); CFArrayAppendValue(keys, NetworkChangedKey_Computername); CFArrayAppendValue(keys, NetworkChangedKey_DNS); CFArrayAppendValue(keys, NetworkChangedKey_DynamicDNS); CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac); CFArrayAppendValue(keys, NetworkChangedKey_PowerSettings); CFArrayAppendValue(keys, NetworkChangedKey_BTMMConnectivity); CFArrayAppendValue(patterns, pattern1); CFArrayAppendValue(patterns, pattern2); CFArrayAppendValue(patterns, CFSTR("State:/Network/Interface/[^/]+/AirPort")); if (!SCDynamicStoreSetNotificationKeys(store, keys, patterns)) { LogMsg("SCDynamicStoreSetNotificationKeys failed: %s", SCErrorString(SCError())); goto error; } #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM if (!SCDynamicStoreSetDispatchQueue(store, dispatch_get_main_queue())) { LogMsg("SCDynamicStoreCreateRunLoopSource failed: %s", SCErrorString(SCError())); goto error; } #else m->p->StoreRLS = SCDynamicStoreCreateRunLoopSource(NULL, store, 0); if (!m->p->StoreRLS) { LogMsg("SCDynamicStoreCreateRunLoopSource failed: %s", SCErrorString(SCError())); goto error; } CFRunLoopAddSource(CFRunLoopGetMain(), m->p->StoreRLS, kCFRunLoopDefaultMode); #endif SCDynamicStoreSetDisconnectCallBack(store, DynamicStoreReconnected); m->p->Store = store; err = 0; goto exit; error: if (store) CFRelease(store); exit: if (patterns) CFRelease(patterns); if (pattern2) CFRelease(pattern2); if (pattern1) CFRelease(pattern1); if (keys) CFRelease(keys); return(err); } #if !TARGET_OS_EMBEDDED // don't setup packet filter rules on embedded mDNSlocal void mDNSSetPacketFilterRules(char * ifname, const ResourceRecord *const excludeRecord) { mDNS *const m = &mDNSStorage; AuthRecord *rr; pfArray_t portArray; pfArray_t protocolArray; uint32_t count = 0; for (rr = m->ResourceRecords; rr; rr=rr->next) { if ((rr->resrec.rrtype == kDNSServiceType_SRV) && ((rr->ARType == AuthRecordAnyIncludeP2P) || (rr->ARType == AuthRecordAnyIncludeAWDLandP2P))) { const mDNSu8 *p; if (count >= PFPortArraySize) { LogMsg("mDNSSetPacketFilterRules: %d service limit, skipping %s", PFPortArraySize, ARDisplayString(m, rr)); continue; } if (excludeRecord && IdenticalResourceRecord(&rr->resrec, excludeRecord)) { LogInfo("mDNSSetPacketFilterRules: record being removed, skipping %s", ARDisplayString(m, rr)); continue; } LogMsg("mDNSSetPacketFilterRules: found %s", ARDisplayString(m, rr)); portArray[count] = rr->resrec.rdata->u.srv.port.NotAnInteger; // Assume ... p = rr->resrec.name->c; // Skip to App Protocol if (p[0]) p += 1 + p[0]; // Skip to Transport Protocol if (p[0]) p += 1 + p[0]; if (SameDomainLabel(p, (mDNSu8 *)"\x4" "_tcp")) { protocolArray[count] = IPPROTO_TCP; } else if (SameDomainLabel(p, (mDNSu8 *)"\x4" "_udp")) { protocolArray[count] = IPPROTO_UDP; } else { LogMsg("mDNSSetPacketFilterRules: could not determine transport protocol of service"); LogMsg("mDNSSetPacketFilterRules: %s", ARDisplayString(m, rr)); return; } count++; } } mDNSPacketFilterControl(PF_SET_RULES, ifname, count, portArray, protocolArray); } // If the p2p interface already exists, update the Bonjour packet filter rules for it. mDNSexport void mDNSUpdatePacketFilter(const ResourceRecord *const excludeRecord) { mDNS *const m = &mDNSStorage; NetworkInterfaceInfo *intf = GetFirstActiveInterface(m->HostInterfaces); while (intf) { if (strncmp(intf->ifname, "p2p", 3) == 0) { LogInfo("mDNSInitPacketFilter: Setting rules for ifname %s", intf->ifname); mDNSSetPacketFilterRules(intf->ifname, excludeRecord); break; } intf = GetFirstActiveInterface(intf->next); } } #else // !TARGET_OS_EMBEDDED // Currently no packet filter setup required on embedded platforms. mDNSexport void mDNSUpdatePacketFilter(const ResourceRecord *const excludeRecord) { (void) excludeRecord; // unused } #endif // !TARGET_OS_EMBEDDED // AWDL should no longer generate KEV_DL_MASTER_ELECTED events, so just log a message if we receive one. mDNSlocal void newMasterElected(struct net_event_data * ptr) { char ifname[IFNAMSIZ]; mDNSu32 interfaceIndex; snprintf(ifname, IFNAMSIZ, "%s%d", ptr->if_name, ptr->if_unit); interfaceIndex = if_nametoindex(ifname); if (!interfaceIndex) { LogMsg("newMasterElected: if_nametoindex(%s) failed", ifname); return; } LogInfo("newMasterElected: KEV_DL_MASTER_ELECTED received on ifname = %s, interfaceIndex = %d", ifname, interfaceIndex); } // An ssth array of all zeroes indicates the peer has no services registered. mDNSlocal mDNSBool allZeroSSTH(struct opaque_presence_indication *op) { int i; int *intp = (int *) op->ssth; // MAX_SSTH_SIZE should always be a multiple of sizeof(int), if // it's not, print an error message and return false so that // corresponding peer records are not flushed when KEV_DL_NODE_PRESENCE event // is received. if (MAX_SSTH_SIZE % sizeof(int)) { LogInfo("allZeroSSTH: MAX_SSTH_SIZE = %d not a multiple of sizeof(int)", MAX_SSTH_SIZE); return mDNSfalse; } for (i = 0; i < (int)(MAX_SSTH_SIZE / sizeof(int)); i++, intp++) { if (*intp) return mDNSfalse; } return mDNStrue; } // Mark records from this peer for deletion from the cache. mDNSlocal void removeCachedPeerRecords(mDNSu32 ifindex, mDNSAddr *ap, bool purgeNow) { mDNS *const m = &mDNSStorage; mDNSu32 slot; CacheGroup *cg; CacheRecord *cr; NetworkInterfaceInfoOSX *infoOSX; mDNSInterfaceID InterfaceID; // Using mDNSPlatformInterfaceIDfromInterfaceIndex() would lead to recursive // locking issues, see: infoOSX = IfindexToInterfaceInfoOSX((mDNSInterfaceID)(uintptr_t)ifindex); if (!infoOSX) { LogInfo("removeCachedPeerRecords: interface %d not yet active", ifindex); return; } InterfaceID = infoOSX->ifinfo.InterfaceID; FORALL_CACHERECORDS(slot, cg, cr) { if ((InterfaceID == cr->resrec.InterfaceID) && mDNSSameAddress(ap, & cr->sourceAddress)) { LogInfo("removeCachedPeerRecords: %s %##s marking for deletion", DNSTypeName(cr->resrec.rrtype), cr->resrec.name->c); if (purgeNow) mDNS_PurgeCacheResourceRecord(m, cr); else mDNS_Reconfirm_internal(m, cr, 0); // use default minimum reconfirm time } } } // Handle KEV_DL_NODE_PRESENCE event. mDNSlocal void nodePresence(struct kev_dl_node_presence * p) { char buf[INET6_ADDRSTRLEN]; struct opaque_presence_indication *op = (struct opaque_presence_indication *) p->node_service_info; if (inet_ntop(AF_INET6, & p->sin6_node_address.sin6_addr, buf, sizeof(buf))) LogInfo("nodePresence: IPv6 address: %s, SUI %d", buf, op->SUI); else LogInfo("nodePresence: inet_ntop() error"); // AWDL will generate a KEV_DL_NODE_PRESENCE event with SSTH field of // all zeroes when a node is present and has no services registered. if (allZeroSSTH(op)) { mDNSAddr peerAddr; peerAddr.type = mDNSAddrType_IPv6; peerAddr.ip.v6 = *(mDNSv6Addr*)&p->sin6_node_address.sin6_addr; LogInfo("nodePresence: ssth is all zeroes, reconfirm cached records for this peer"); removeCachedPeerRecords(p->sdl_node_address.sdl_index, & peerAddr, false); } } // Handle KEV_DL_NODE_ABSENCE event. mDNSlocal void nodeAbsence(struct kev_dl_node_absence * p) { mDNSAddr peerAddr; char buf[INET6_ADDRSTRLEN]; if (inet_ntop(AF_INET6, & p->sin6_node_address.sin6_addr, buf, sizeof(buf))) LogInfo("nodeAbsence: IPv6 address: %s", buf); else LogInfo("nodeAbsence: inet_ntop() error"); peerAddr.type = mDNSAddrType_IPv6; peerAddr.ip.v6 = *(mDNSv6Addr*)&p->sin6_node_address.sin6_addr; LogInfo("nodeAbsence: immediately purge cached records from this peer"); removeCachedPeerRecords(p->sdl_node_address.sdl_index, & peerAddr, true); } mDNSlocal void SysEventCallBack(int s1, short __unused filter, void *context, __unused mDNSBool encounteredEOF) { mDNS *const m = (mDNS *const)context; mDNS_Lock(m); struct { struct kern_event_msg k; char extra[256]; } msg; int bytes = recv(s1, &msg, sizeof(msg), 0); if (bytes < 0) LogMsg("SysEventCallBack: recv error %d errno %d (%s)", bytes, errno, strerror(errno)); else { LogInfo("SysEventCallBack got %d bytes size %d %X %s %X %s %X %s id %d code %d %s", bytes, msg.k.total_size, msg.k.vendor_code, msg.k.vendor_code == KEV_VENDOR_APPLE ? "KEV_VENDOR_APPLE" : "?", msg.k.kev_class, msg.k.kev_class == KEV_NETWORK_CLASS ? "KEV_NETWORK_CLASS" : "?", msg.k.kev_subclass, msg.k.kev_subclass == KEV_DL_SUBCLASS ? "KEV_DL_SUBCLASS" : "?", msg.k.id, msg.k.event_code, msg.k.event_code == KEV_DL_SIFFLAGS ? "KEV_DL_SIFFLAGS" : msg.k.event_code == KEV_DL_SIFMETRICS ? "KEV_DL_SIFMETRICS" : msg.k.event_code == KEV_DL_SIFMTU ? "KEV_DL_SIFMTU" : msg.k.event_code == KEV_DL_SIFPHYS ? "KEV_DL_SIFPHYS" : msg.k.event_code == KEV_DL_SIFMEDIA ? "KEV_DL_SIFMEDIA" : msg.k.event_code == KEV_DL_SIFGENERIC ? "KEV_DL_SIFGENERIC" : msg.k.event_code == KEV_DL_ADDMULTI ? "KEV_DL_ADDMULTI" : msg.k.event_code == KEV_DL_DELMULTI ? "KEV_DL_DELMULTI" : msg.k.event_code == KEV_DL_IF_ATTACHED ? "KEV_DL_IF_ATTACHED" : msg.k.event_code == KEV_DL_IF_DETACHING ? "KEV_DL_IF_DETACHING" : msg.k.event_code == KEV_DL_IF_DETACHED ? "KEV_DL_IF_DETACHED" : msg.k.event_code == KEV_DL_LINK_OFF ? "KEV_DL_LINK_OFF" : msg.k.event_code == KEV_DL_LINK_ON ? "KEV_DL_LINK_ON" : msg.k.event_code == KEV_DL_PROTO_ATTACHED ? "KEV_DL_PROTO_ATTACHED" : msg.k.event_code == KEV_DL_PROTO_DETACHED ? "KEV_DL_PROTO_DETACHED" : msg.k.event_code == KEV_DL_LINK_ADDRESS_CHANGED ? "KEV_DL_LINK_ADDRESS_CHANGED" : msg.k.event_code == KEV_DL_WAKEFLAGS_CHANGED ? "KEV_DL_WAKEFLAGS_CHANGED" : msg.k.event_code == KEV_DL_IF_IDLE_ROUTE_REFCNT ? "KEV_DL_IF_IDLE_ROUTE_REFCNT" : msg.k.event_code == KEV_DL_IFCAP_CHANGED ? "KEV_DL_IFCAP_CHANGED" : msg.k.event_code == KEV_DL_LINK_QUALITY_METRIC_CHANGED ? "KEV_DL_LINK_QUALITY_METRIC_CHANGED" : msg.k.event_code == KEV_DL_NODE_PRESENCE ? "KEV_DL_NODE_PRESENCE" : msg.k.event_code == KEV_DL_NODE_ABSENCE ? "KEV_DL_NODE_ABSENCE" : msg.k.event_code == KEV_DL_MASTER_ELECTED ? "KEV_DL_MASTER_ELECTED" : "?"); if (msg.k.event_code == KEV_DL_NODE_PRESENCE) nodePresence((struct kev_dl_node_presence *) &msg.k.event_data); if (msg.k.event_code == KEV_DL_NODE_ABSENCE) nodeAbsence((struct kev_dl_node_absence *) &msg.k.event_data); if (msg.k.event_code == KEV_DL_MASTER_ELECTED) newMasterElected((struct net_event_data *) &msg.k.event_data); // We receive network change notifications both through configd and through SYSPROTO_EVENT socket. // Configd may not generate network change events for manually configured interfaces (i.e., non-DHCP) // always during sleep/wakeup due to some race conditions (See radar:8666757). At the same time, if // "Wake on Network Access" is not turned on, the notification will not have KEV_DL_WAKEFLAGS_CHANGED. // Hence, during wake up, if we see a KEV_DL_LINK_ON (i.e., link is UP), we trigger a network change. if (msg.k.event_code == KEV_DL_WAKEFLAGS_CHANGED || msg.k.event_code == KEV_DL_LINK_ON) SetNetworkChanged(mDNSPlatformOneSecond * 2); #if !TARGET_OS_EMBEDDED // don't setup packet filter rules on embedded // For p2p interfaces, need to open the advertised service port in the firewall. if (msg.k.event_code == KEV_DL_IF_ATTACHED) { struct net_event_data * p; p = (struct net_event_data *) &msg.k.event_data; if (strncmp(p->if_name, "p2p", 3) == 0) { char ifname[IFNAMSIZ]; snprintf(ifname, IFNAMSIZ, "%s%d", p->if_name, p->if_unit); LogInfo("SysEventCallBack: KEV_DL_IF_ATTACHED if_family = %d, if_unit = %d, if_name = %s", p->if_family, p->if_unit, p->if_name); mDNSSetPacketFilterRules(ifname, NULL); } } // For p2p interfaces, need to clear the firewall rules on interface detach if (msg.k.event_code == KEV_DL_IF_DETACHED) { struct net_event_data * p; p = (struct net_event_data *) &msg.k.event_data; if (strncmp(p->if_name, "p2p", 3) == 0) { pfArray_t portArray, protocolArray; // not initialized since count is 0 for PF_CLEAR_RULES char ifname[IFNAMSIZ]; snprintf(ifname, IFNAMSIZ, "%s%d", p->if_name, p->if_unit); LogInfo("SysEventCallBack: KEV_DL_IF_DETACHED if_family = %d, if_unit = %d, if_name = %s", p->if_family, p->if_unit, p->if_name); mDNSPacketFilterControl(PF_CLEAR_RULES, ifname, 0, portArray, protocolArray); } } #endif // !TARGET_OS_EMBEDDED } mDNS_Unlock(m); } mDNSlocal mStatus WatchForSysEvents(mDNS *const m) { m->p->SysEventNotifier = socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT); if (m->p->SysEventNotifier < 0) { LogMsg("WatchForSysEvents: socket failed error %d errno %d (%s)", m->p->SysEventNotifier, errno, strerror(errno)); return(mStatus_NoMemoryErr); } struct kev_request kev_req = { KEV_VENDOR_APPLE, KEV_NETWORK_CLASS, KEV_DL_SUBCLASS }; int err = ioctl(m->p->SysEventNotifier, SIOCSKEVFILT, &kev_req); if (err < 0) { LogMsg("WatchForSysEvents: SIOCSKEVFILT failed error %d errno %d (%s)", err, errno, strerror(errno)); close(m->p->SysEventNotifier); m->p->SysEventNotifier = -1; return(mStatus_UnknownErr); } m->p->SysEventKQueue.KQcallback = SysEventCallBack; m->p->SysEventKQueue.KQcontext = m; m->p->SysEventKQueue.KQtask = "System Event Notifier"; KQueueSet(m->p->SysEventNotifier, EV_ADD, EVFILT_READ, &m->p->SysEventKQueue); return(mStatus_NoError); } #ifndef NO_SECURITYFRAMEWORK mDNSlocal OSStatus KeychainChanged(SecKeychainEvent keychainEvent, SecKeychainCallbackInfo *info, void *context) { LogInfo("*** Keychain Changed ***"); mDNS *const m = (mDNS *const)context; SecKeychainRef skc; OSStatus err = SecKeychainCopyDefault(&skc); if (!err) { if (info->keychain == skc) { // For delete events, attempt to verify what item was deleted fail because the item is already gone, so we just assume they may be relevant mDNSBool relevant = (keychainEvent == kSecDeleteEvent); if (!relevant) { UInt32 tags[3] = { kSecTypeItemAttr, kSecServiceItemAttr, kSecAccountItemAttr }; SecKeychainAttributeInfo attrInfo = { 3, tags, NULL }; // Count, array of tags, array of formats SecKeychainAttributeList *a = NULL; err = SecKeychainItemCopyAttributesAndData(info->item, &attrInfo, NULL, &a, NULL, NULL); if (!err) { relevant = ((a->attr[0].length == 4 && (!strncasecmp(a->attr[0].data, "ddns", 4) || !strncasecmp(a->attr[0].data, "sndd", 4))) || (a->attr[1].length >= mDNSPlatformStrLen(dnsprefix) && (!strncasecmp(a->attr[1].data, dnsprefix, mDNSPlatformStrLen(dnsprefix)))) || (a->attr[1].length >= mDNSPlatformStrLen(btmmprefix) && (!strncasecmp(a->attr[1].data, btmmprefix, mDNSPlatformStrLen(btmmprefix))))); SecKeychainItemFreeAttributesAndData(a, NULL); } } if (relevant) { LogInfo("*** Keychain Changed *** KeychainEvent=%d %s", keychainEvent, keychainEvent == kSecAddEvent ? "kSecAddEvent" : keychainEvent == kSecDeleteEvent ? "kSecDeleteEvent" : keychainEvent == kSecUpdateEvent ? "kSecUpdateEvent" : ""); // We're running on the CFRunLoop (Mach port) thread, not the kqueue thread, so we need to grab the KQueueLock before proceeding KQueueLock(); mDNS_Lock(m); // To not read the keychain twice: when BTMM is enabled, changes happen to the keychain // then the BTMM DynStore dictionary, so delay reading the keychain for a second. // NetworkChanged() will reset the keychain timer to fire immediately when the DynStore changes. // // In the "fixup" case where the BTMM DNS servers aren't accepting the key mDNSResponder has, // the DynStore dictionary won't change (because the BTMM zone won't change). In that case, // a one second delay is ok, as we'll still converge to correctness, and there's no race // condition between the RegistrationDomain and the DomainAuthInfo. // // Lastly, non-BTMM WAB cases can use the keychain but not the DynStore, so we need to set // the timer here, as it will not get set by NetworkChanged(). SetKeyChainTimer(mDNSPlatformOneSecond); mDNS_Unlock(m); KQueueUnlock("KeychainChanged"); } } CFRelease(skc); } return 0; } #endif mDNSlocal void PowerOn(mDNS *const m) { mDNSCoreMachineSleep(m, false); // Will set m->SleepState = SleepState_Awake; if (m->p->WakeAtUTC) { long utc = mDNSPlatformUTC(); mDNSPowerRequest(-1,-1); // Need to explicitly clear any previous power requests -- they're not cleared automatically on wake if (m->p->WakeAtUTC - utc > 30) { LogSPS("PowerChanged PowerOn %d seconds early, assuming not maintenance wake", m->p->WakeAtUTC - utc); } else if (utc - m->p->WakeAtUTC > 30) { LogSPS("PowerChanged PowerOn %d seconds late, assuming not maintenance wake", utc - m->p->WakeAtUTC); } else if (IsAppleTV()) { LogSPS("PowerChanged PowerOn %d seconds late, device is an AppleTV running iOS so not re-sleeping", utc - m->p->WakeAtUTC); } else { LogSPS("PowerChanged: Waking for network maintenance operations %d seconds early; re-sleeping in 20 seconds", m->p->WakeAtUTC - utc); m->p->RequestReSleep = mDNS_TimeNow(m) + 20 * mDNSPlatformOneSecond; } } // Hold on to a sleep assertion to allow mDNSResponder to perform its maintenance activities. // This allows for the network link to come up, DHCP to get an address, mDNS to issue queries etc. // We will clear this assertion as soon as we think the mainenance activities are done. mDNSPlatformPreventSleep(DARK_WAKE_TIME, "mDNSResponder:maintenance"); } mDNSlocal void PowerChanged(void *refcon, io_service_t service, natural_t messageType, void *messageArgument) { mDNS *const m = (mDNS *const)refcon; KQueueLock(); (void)service; // Parameter not used debugf("PowerChanged %X %lX", messageType, messageArgument); // Make sure our m->SystemWakeOnLANEnabled value correctly reflects the current system setting m->SystemWakeOnLANEnabled = SystemWakeForNetworkAccess(); switch(messageType) { case kIOMessageCanSystemPowerOff: LogSPS("PowerChanged kIOMessageCanSystemPowerOff (no action)"); break; // E0000240 case kIOMessageSystemWillPowerOff: LogSPS("PowerChanged kIOMessageSystemWillPowerOff"); // E0000250 mDNSCoreMachineSleep(m, true); if (m->SleepState == SleepState_Sleeping) mDNSMacOSXNetworkChanged(); break; case kIOMessageSystemWillNotPowerOff: LogSPS("PowerChanged kIOMessageSystemWillNotPowerOff (no action)"); break; // E0000260 case kIOMessageCanSystemSleep: LogSPS("PowerChanged kIOMessageCanSystemSleep"); break; // E0000270 case kIOMessageSystemWillSleep: LogSPS("PowerChanged kIOMessageSystemWillSleep"); // E0000280 mDNSCoreMachineSleep(m, true); break; case kIOMessageSystemWillNotSleep: LogSPS("PowerChanged kIOMessageSystemWillNotSleep (no action)"); break; // E0000290 case kIOMessageSystemHasPoweredOn: LogSPS("PowerChanged kIOMessageSystemHasPoweredOn"); // E0000300 // If still sleeping (didn't get 'WillPowerOn' message for some reason?) wake now if (m->SleepState) { LogMsg("PowerChanged kIOMessageSystemHasPoweredOn: ERROR m->SleepState %d", m->SleepState); PowerOn(m); } // Just to be safe, schedule a mDNSMacOSXNetworkChanged(), in case we never received // the System Configuration Framework "network changed" event that we expect // to receive some time shortly after the kIOMessageSystemWillPowerOn message mDNS_Lock(m); SetNetworkChanged(mDNSPlatformOneSecond * 2); mDNS_Unlock(m); break; case kIOMessageSystemWillRestart: LogSPS("PowerChanged kIOMessageSystemWillRestart (no action)"); break; // E0000310 case kIOMessageSystemWillPowerOn: LogSPS("PowerChanged kIOMessageSystemWillPowerOn"); // E0000320 // Make sure our interface list is cleared to the empty state, then tell mDNSCore to wake if (m->SleepState != SleepState_Sleeping) { LogMsg("kIOMessageSystemWillPowerOn: ERROR m->SleepState %d", m->SleepState); m->SleepState = SleepState_Sleeping; mDNSMacOSXNetworkChanged(); } PowerOn(m); break; default: LogSPS("PowerChanged unknown message %X", messageType); break; } if (messageType == kIOMessageSystemWillSleep) m->p->SleepCookie = (long)messageArgument; else if (messageType == kIOMessageCanSystemSleep) IOAllowPowerChange(m->p->PowerConnection, (long)messageArgument); KQueueUnlock("PowerChanged Sleep/Wake"); } // iPhone OS doesn't currently have SnowLeopard's IO Power Management // but it does define kIOPMAcknowledgmentOptionSystemCapabilityRequirements #if defined(kIOPMAcknowledgmentOptionSystemCapabilityRequirements) && !TARGET_OS_EMBEDDED mDNSlocal void SnowLeopardPowerChanged(void *refcon, IOPMConnection connection, IOPMConnectionMessageToken token, IOPMSystemPowerStateCapabilities eventDescriptor) { mDNS *const m = (mDNS *const)refcon; KQueueLock(); LogSPS("SnowLeopardPowerChanged %X %X %X%s%s%s%s%s", connection, token, eventDescriptor, eventDescriptor & kIOPMSystemPowerStateCapabilityCPU ? " CPU" : "", eventDescriptor & kIOPMSystemPowerStateCapabilityVideo ? " Video" : "", eventDescriptor & kIOPMSystemPowerStateCapabilityAudio ? " Audio" : "", eventDescriptor & kIOPMSystemPowerStateCapabilityNetwork ? " Network" : "", eventDescriptor & kIOPMSystemPowerStateCapabilityDisk ? " Disk" : ""); // Make sure our m->SystemWakeOnLANEnabled value correctly reflects the current system setting m->SystemWakeOnLANEnabled = SystemWakeForNetworkAccess(); if (eventDescriptor & kIOPMSystemPowerStateCapabilityCPU) { // We might be in Sleeping or Transferring state. When we go from "wakeup" to "sleep" state, we don't // go directly to sleep state, but transfer in to the sleep state during which SleepState is set to // SleepState_Transferring. During that time, we might get another wakeup before we transition to Sleeping // state. In that case, we need to acknowledge the previous "sleep" before we acknowledge the wakeup. if (m->SleepLimit) { LogSPS("SnowLeopardPowerChanged: Waking up, Acking old Sleep, SleepLimit %d SleepState %d", m->SleepLimit, m->SleepState); IOPMConnectionAcknowledgeEvent(connection, m->p->SleepCookie); m->SleepLimit = 0; } LogSPS("SnowLeopardPowerChanged: Waking up, Acking Wakeup, SleepLimit %d SleepState %d", m->SleepLimit, m->SleepState); // CPU Waking. Note: Can get this message repeatedly, as other subsystems power up or down. if (m->SleepState != SleepState_Awake) { PowerOn(m); // If the network notifications have already come before we got the wakeup, we ignored them and // in case we get no more, we need to trigger one. mDNS_Lock(m); SetNetworkChanged(mDNSPlatformOneSecond * 2); mDNS_Unlock(m); } IOPMConnectionAcknowledgeEvent(connection, token); } else { // CPU sleeping. Should not get this repeatedly -- once we're told that the CPU is halting // we should hear nothing more until we're told that the CPU has started executing again. if (m->SleepState) LogMsg("SnowLeopardPowerChanged: Sleep Error %X m->SleepState %d", eventDescriptor, m->SleepState); //sleep(5); //mDNSMacOSXNetworkChanged(m); mDNSCoreMachineSleep(m, true); //if (m->SleepState == SleepState_Sleeping) mDNSMacOSXNetworkChanged(m); m->p->SleepCookie = token; } KQueueUnlock("SnowLeopardPowerChanged Sleep/Wake"); } #endif #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - #pragma mark - /etc/hosts support #endif // Implementation Notes // // As /etc/hosts file can be huge (1000s of entries - when this comment was written, the test file had about // 23000 entries with about 4000 duplicates), we can't use a linked list to store these entries. So, we parse // them into a hash table. The implementation need to be able to do the following things efficiently // // 1. Detect duplicates e.g., two entries with "1.2.3.4 foo" // 2. Detect whether /etc/hosts has changed and what has changed since the last read from the disk // 3. Ability to support multiple addresses per name e.g., "1.2.3.4 foo, 2.3.4.5 foo". To support this, we // need to be able set the RRSet of a resource record to the first one in the list and also update when // one of them go away. This is needed so that the core thinks that they are all part of the same RRSet and // not a duplicate // 4. Don't maintain any local state about any records registered with the core to detect changes to /etc/hosts // // CFDictionary is not a suitable candidate because it does not support duplicates and even if we use a custom // "hash" function to solve this, the others are hard to solve. Hence, we share the hash (AuthHash) implementation // of the core layer which does all of the above very efficiently #define ETCHOSTS_BUFSIZE 1024 // Buffer size to parse a single line in /etc/hosts mDNSexport void FreeEtcHosts(mDNS *const m, AuthRecord *const rr, mStatus result) { (void)m; // unused (void)rr; (void)result; if (result == mStatus_MemFree) { LogInfo("FreeEtcHosts: %s", ARDisplayString(m, rr)); freeL("etchosts", rr); } } // Returns true on success and false on failure mDNSlocal mDNSBool mDNSMacOSXCreateEtcHostsEntry(const domainname *domain, const struct sockaddr *sa, const domainname *cname, char *ifname, AuthHash *auth) { AuthRecord *rr; mDNSu32 namehash; AuthGroup *ag; mDNSInterfaceID InterfaceID = mDNSInterface_LocalOnly; mDNSu16 rrtype; if (!domain) { LogMsg("mDNSMacOSXCreateEtcHostsEntry: ERROR!! name NULL"); return mDNSfalse; } if (!sa && !cname) { LogMsg("mDNSMacOSXCreateEtcHostsEntry: ERROR!! sa and cname both NULL"); return mDNSfalse; } if (sa && sa->sa_family != AF_INET && sa->sa_family != AF_INET6) { LogMsg("mDNSMacOSXCreateEtcHostsEntry: ERROR!! sa with bad family %d", sa->sa_family); return mDNSfalse; } if (ifname) { mDNSu32 ifindex = if_nametoindex(ifname); if (!ifindex) { LogMsg("mDNSMacOSXCreateEtcHostsEntry: hosts entry %##s with invalid ifname %s", domain->c, ifname); return mDNSfalse; } InterfaceID = (mDNSInterfaceID)(uintptr_t)ifindex; } if (sa) rrtype = (sa->sa_family == AF_INET ? kDNSType_A : kDNSType_AAAA); else rrtype = kDNSType_CNAME; // Check for duplicates. See whether we parsed an entry before like this ? namehash = DomainNameHashValue(domain); ag = AuthGroupForName(auth, namehash, domain); if (ag) { rr = ag->members; while (rr) { if (rr->resrec.rrtype == rrtype) { if (rrtype == kDNSType_A) { mDNSv4Addr ip; ip.NotAnInteger = ((struct sockaddr_in*)sa)->sin_addr.s_addr; if (mDNSSameIPv4Address(rr->resrec.rdata->u.ipv4, ip)) { LogInfo("mDNSMacOSXCreateEtcHostsEntry: Same IPv4 address for name %##s", domain->c); return mDNSfalse; } } else if (rrtype == kDNSType_AAAA) { mDNSv6Addr ip6; ip6.l[0] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[0]; ip6.l[1] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[1]; ip6.l[2] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[2]; ip6.l[3] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[3]; if (mDNSSameIPv6Address(rr->resrec.rdata->u.ipv6, ip6)) { LogInfo("mDNSMacOSXCreateEtcHostsEntry: Same IPv6 address for name %##s", domain->c); return mDNSfalse; } } else if (rrtype == kDNSType_CNAME) { if (SameDomainName(&rr->resrec.rdata->u.name, cname)) { LogInfo("mDNSMacOSXCreateEtcHostsEntry: Same cname %##s for name %##s", cname->c, domain->c); return mDNSfalse; } } } rr = rr->next; } } rr= mallocL("etchosts", sizeof(*rr)); if (rr == NULL) return mDNSfalse; mDNSPlatformMemZero(rr, sizeof(*rr)); mDNS_SetupResourceRecord(rr, NULL, InterfaceID, rrtype, 1, kDNSRecordTypeKnownUnique, AuthRecordLocalOnly, FreeEtcHosts, NULL); AssignDomainName(&rr->namestorage, domain); if (sa) { rr->resrec.rdlength = sa->sa_family == AF_INET ? sizeof(mDNSv4Addr) : sizeof(mDNSv6Addr); if (sa->sa_family == AF_INET) rr->resrec.rdata->u.ipv4.NotAnInteger = ((struct sockaddr_in*)sa)->sin_addr.s_addr; else { rr->resrec.rdata->u.ipv6.l[0] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[0]; rr->resrec.rdata->u.ipv6.l[1] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[1]; rr->resrec.rdata->u.ipv6.l[2] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[2]; rr->resrec.rdata->u.ipv6.l[3] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[3]; } } else { rr->resrec.rdlength = DomainNameLength(cname); rr->resrec.rdata->u.name.c[0] = 0; AssignDomainName(&rr->resrec.rdata->u.name, cname); } rr->resrec.namehash = DomainNameHashValue(rr->resrec.name); SetNewRData(&rr->resrec, mDNSNULL, 0); // Sets rr->rdatahash for us LogInfo("mDNSMacOSXCreateEtcHostsEntry: Adding resource record %s", ARDisplayString(&mDNSStorage, rr)); InsertAuthRecord(&mDNSStorage, auth, rr); return mDNStrue; } mDNSlocal int EtcHostsParseOneName(int start, int length, char *buffer, char **name) { int i; *name = NULL; for (i = start; i < length; i++) { if (buffer[i] == '#') return -1; if (buffer[i] != ' ' && buffer[i] != ',' && buffer[i] != '\t') { *name = &buffer[i]; // Found the start of a name, find the end and null terminate for (i++; i < length; i++) { if (buffer[i] == ' ' || buffer[i] == ',' || buffer[i] == '\t') { buffer[i] = 0; break; } } return i; } } return -1; } mDNSlocal void mDNSMacOSXParseEtcHostsLine(char *buffer, ssize_t length, AuthHash *auth) { int i; int ifStart = 0; char *ifname = NULL; domainname name1d; domainname name2d; char *name1; char *name2; int aliasIndex; //Ignore leading whitespaces and tabs while (*buffer == ' ' || *buffer == '\t') { buffer++; length--; } // Find the end of the address string for (i = 0; i < length; i++) { if (buffer[i] == ' ' || buffer[i] == ',' || buffer[i] == '\t' || buffer[i] == '%') { if (buffer[i] == '%') ifStart = i + 1; buffer[i] = 0; break; } } // Convert the address string to an address struct addrinfo hints; bzero(&hints, sizeof(hints)); hints.ai_flags = AI_NUMERICHOST; struct addrinfo *gairesults = NULL; if (getaddrinfo(buffer, NULL, &hints, &gairesults) != 0) { LogInfo("mDNSMacOSXParseEtcHostsLine: getaddrinfo returning null"); return; } if (ifStart) { // Parse the interface ifname = &buffer[ifStart]; for (i = ifStart + 1; i < length; i++) { if (buffer[i] == ' ' || buffer[i] == ',' || buffer[i] == '\t') { buffer[i] = 0; break; } } } i = EtcHostsParseOneName(i + 1, length, buffer, &name1); if (i == length) { // Common case (no aliases) : The entry is of the form "1.2.3.4 somehost" with no trailing white spaces/tabs etc. if (!MakeDomainNameFromDNSNameString(&name1d, name1)) { LogMsg("mDNSMacOSXParseEtcHostsLine: ERROR!! cannot convert to domain name %s", name1); freeaddrinfo(gairesults); return; } mDNSMacOSXCreateEtcHostsEntry(&name1d, gairesults->ai_addr, mDNSNULL, ifname, auth); } else if (i != -1) { domainname first; // We might have some extra white spaces at the end for the common case of "1.2.3.4 somehost". // When we parse again below, EtchHostsParseOneName would return -1 and we will end up // doing the right thing. if (!MakeDomainNameFromDNSNameString(&first, name1)) { LogMsg("mDNSMacOSXParseEtcHostsLine: ERROR!! cannot convert to domain name %s", name1); freeaddrinfo(gairesults); return; } mDNSMacOSXCreateEtcHostsEntry(&first, gairesults->ai_addr, mDNSNULL, ifname, auth); // /etc/hosts alias discussion: // // If the /etc/hosts has an entry like this // // ip_address cname [aliases...] // 1.2.3.4 sun star bright // // star and bright are aliases (gethostbyname h_alias should point to these) and sun is the canonical // name (getaddrinfo ai_cannonname and gethostbyname h_name points to "sun") // // To achieve this, we need to add the entry like this: // // sun A 1.2.3.4 // star CNAME sun // bright CNAME sun // // We store the first name we parsed in "first" and add the address (A/AAAA) record. // Then we parse additional names adding CNAME records till we reach the end. aliasIndex = 0; while (i < length) { // Continue to parse additional aliases until we reach end of the line and // for each "alias" parsed, add a CNAME record where "alias" points to the first "name". // See also /etc/hosts alias discussion above i = EtcHostsParseOneName(i + 1, length, buffer, &name2); if (name2) { if ((aliasIndex) && (*buffer == *name2)) break; // break out of the loop if we wrap around if (!MakeDomainNameFromDNSNameString(&name2d, name2)) { LogMsg("mDNSMacOSXParseEtcHostsLine: ERROR!! cannot convert to domain name %s", name2); freeaddrinfo(gairesults); return; } // Ignore if it points to itself if (!SameDomainName(&first, &name2d)) { if (!mDNSMacOSXCreateEtcHostsEntry(&name2d, mDNSNULL, &first, ifname, auth)) { freeaddrinfo(gairesults); return; } } else { LogInfo("mDNSMacOSXParseEtcHostsLine: Ignoring entry with same names first %##s, name2 %##s", first.c, name2d.c); } aliasIndex++; } else if (!aliasIndex) { // We have never parsed any aliases. This case happens if there // is just one name and some extra white spaces at the end. LogInfo("mDNSMacOSXParseEtcHostsLine: White space at the end of %##s", first.c); break; } } } freeaddrinfo(gairesults); } mDNSlocal void mDNSMacOSXParseEtcHosts(int fd, AuthHash *auth) { mDNSBool good; char buf[ETCHOSTS_BUFSIZE]; ssize_t len; FILE *fp; if (fd == -1) { LogInfo("mDNSMacOSXParseEtcHosts: fd is -1"); return; } fp = fopen("/etc/hosts", "r"); if (!fp) { LogInfo("mDNSMacOSXParseEtcHosts: fp is NULL"); return; } while (1) { good = (fgets(buf, ETCHOSTS_BUFSIZE, fp) != NULL); if (!good) break; // skip comment and empty lines if (buf[0] == '#' || buf[0] == '\r' || buf[0] == '\n') continue; len = strlen(buf); if (!len) break; // sanity check //Check for end of line code(mostly only \n but pre-OS X Macs could have only \r) if (buf[len - 1] == '\r' || buf[len - 1] == '\n') { buf[len - 1] = '\0'; len = len - 1; } // fgets always null terminates and hence even if we have no // newline at the end, it is null terminated. The callee // (mDNSMacOSXParseEtcHostsLine) expects the length to be such that // buf[length] is zero and hence we decrement len to reflect that. if (len) { //Additional check when end of line code is 2 chars ie\r\n(DOS, other old OSes) //here we need to check for just \r but taking extra caution. if (buf[len - 1] == '\r' || buf[len - 1] == '\n') { buf[len - 1] = '\0'; len = len - 1; } } if (!len) //Sanity Check: len should never be zero { LogMsg("mDNSMacOSXParseEtcHosts: Length is zero!"); continue; } mDNSMacOSXParseEtcHostsLine(buf, len, auth); } fclose(fp); } mDNSlocal void mDNSMacOSXUpdateEtcHosts(mDNS *const m); mDNSlocal int mDNSMacOSXGetEtcHostsFD(void) { mDNS *const m = &mDNSStorage; #ifdef __DISPATCH_GROUP__ // Can't do this stuff to be notified of changes in /etc/hosts if we don't have libdispatch static dispatch_queue_t etcq = 0; static dispatch_source_t etcsrc = 0; static dispatch_source_t hostssrc = 0; // First time through? just schedule ourselves on the main queue and we'll do the work later if (!etcq) { etcq = dispatch_get_main_queue(); if (etcq) { // Do this work on the queue, not here - solves potential synchronization issues dispatch_async(etcq, ^{mDNSMacOSXUpdateEtcHosts(m);}); } return -1; } if (hostssrc) return dispatch_source_get_handle(hostssrc); #endif int fd = open("/etc/hosts", O_RDONLY); #ifdef __DISPATCH_GROUP__ // Can't do this stuff to be notified of changes in /etc/hosts if we don't have libdispatch if (fd == -1) { // If the open failed and we're already watching /etc, we're done if (etcsrc) { LogInfo("mDNSMacOSXGetEtcHostsFD: Returning etcfd because no etchosts"); return fd; } // we aren't watching /etc, we should be fd = open("/etc", O_RDONLY); if (fd == -1) { LogInfo("mDNSMacOSXGetEtcHostsFD: etc does not exist"); return -1; } etcsrc = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_RENAME, etcq); if (etcsrc == NULL) { close(fd); return -1; } dispatch_source_set_event_handler(etcsrc, ^{ u_int32_t flags = dispatch_source_get_data(etcsrc); LogMsg("mDNSMacOSXGetEtcHostsFD: /etc changed 0x%x", flags); if ((flags & (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME)) != 0) { dispatch_source_cancel(etcsrc); dispatch_release(etcsrc); etcsrc = NULL; dispatch_async(etcq, ^{mDNSMacOSXUpdateEtcHosts(m);}); return; } if ((flags & DISPATCH_VNODE_WRITE) != 0 && hostssrc == NULL) { mDNSMacOSXUpdateEtcHosts(m); } }); dispatch_source_set_cancel_handler(etcsrc, ^{close(fd);}); dispatch_resume(etcsrc); // Try and open /etc/hosts once more now that we're watching /etc, in case we missed the creation fd = open("/etc/hosts", O_RDONLY | O_EVTONLY); if (fd == -1) { LogMsg("mDNSMacOSXGetEtcHostsFD etc hosts does not exist, watching etc"); return -1; } } // create a dispatch source to watch for changes to hosts file hostssrc = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_ATTRIB | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_LINK | DISPATCH_VNODE_REVOKE), etcq); if (hostssrc == NULL) { close(fd); return -1; } dispatch_source_set_event_handler(hostssrc, ^{ u_int32_t flags = dispatch_source_get_data(hostssrc); LogInfo("mDNSMacOSXGetEtcHostsFD: /etc/hosts changed 0x%x", flags); if ((flags & (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME)) != 0) { dispatch_source_cancel(hostssrc); dispatch_release(hostssrc); hostssrc = NULL; // Bug in LibDispatch: wait a second before scheduling the block. If we schedule // the block immediately, we try to open the file and the file may not exist and may // fail to get a notification in the future. When the file does not exist and // we start to monitor the directory, on "dispatch_resume" of that source, there // is no guarantee that the file creation will be notified always because when // the dispatch_resume returns, the kevent manager may not have registered the // kevent yet but the file may have been created usleep(1000000); dispatch_async(etcq, ^{mDNSMacOSXUpdateEtcHosts(m);}); return; } if ((flags & DISPATCH_VNODE_WRITE) != 0) { mDNSMacOSXUpdateEtcHosts(m); } }); dispatch_source_set_cancel_handler(hostssrc, ^{LogInfo("mDNSMacOSXGetEtcHostsFD: Closing etchosts fd %d", fd); close(fd);}); dispatch_resume(hostssrc); // Cleanup /etc source, no need to watch it if we already have /etc/hosts if (etcsrc) { dispatch_source_cancel(etcsrc); dispatch_release(etcsrc); etcsrc = NULL; } LogInfo("mDNSMacOSXGetEtcHostsFD: /etc/hosts being monitored, and not etc"); return hostssrc ? (int)dispatch_source_get_handle(hostssrc) : -1; #else (void)m; return fd; #endif } // When /etc/hosts is modified, flush all the cache records as there may be local // authoritative answers now mDNSlocal void FlushAllCacheRecords(mDNS *const m) { CacheRecord *cr; mDNSu32 slot; CacheGroup *cg; FORALL_CACHERECORDS(slot, cg, cr) { // Skip multicast. if (cr->resrec.InterfaceID) continue; // If a resource record can answer A or AAAA, they need to be flushed so that we will // never used to deliver an ADD or RMV if (RRTypeAnswersQuestionType(&cr->resrec, kDNSType_A) || RRTypeAnswersQuestionType(&cr->resrec, kDNSType_AAAA)) { LogInfo("FlushAllCacheRecords: Purging Resourcerecord %s", CRDisplayString(m, cr)); mDNS_PurgeCacheResourceRecord(m, cr); } } } // Add new entries to the core. If justCheck is set, this function does not add, just returns true mDNSlocal mDNSBool EtcHostsAddNewEntries(AuthHash *newhosts, mDNSBool justCheck) { mDNS *const m = &mDNSStorage; AuthGroup *ag; mDNSu32 slot; AuthRecord *rr, *primary, *rrnext; for (slot = 0; slot < AUTH_HASH_SLOTS; slot++) for (ag = newhosts->rrauth_hash[slot]; ag; ag = ag->next) { primary = NULL; for (rr = ag->members; rr; rr = rrnext) { rrnext = rr->next; AuthGroup *ag1; AuthRecord *rr1; mDNSBool found = mDNSfalse; ag1 = AuthGroupForRecord(&m->rrauth, &rr->resrec); if (ag1 && ag1->members) { if (!primary) primary = ag1->members; rr1 = ag1->members; while (rr1) { // We are not using InterfaceID in checking for duplicates. This means, // if there are two addresses for a given name e.g., fe80::1%en0 and // fe80::1%en1, we only add the first one. It is not clear whether // this is a common case. To fix this, we also need to modify // mDNS_Register_internal in how it handles duplicates. If it becomes a // common case, we will fix it then. if (IdenticalResourceRecord(&rr1->resrec, &rr->resrec)) { LogInfo("EtcHostsAddNewEntries: Skipping, not adding %s", ARDisplayString(m, rr1)); found = mDNStrue; break; } rr1 = rr1->next; } } if (!found) { if (justCheck) { LogInfo("EtcHostsAddNewEntries: Entry %s not registered with core yet", ARDisplayString(m, rr)); return mDNStrue; } RemoveAuthRecord(m, newhosts, rr); // if there is no primary, point to self rr->RRSet = (primary ? primary : rr); rr->next = NULL; LogInfo("EtcHostsAddNewEntries: Adding %s", ARDisplayString(m, rr)); if (mDNS_Register_internal(m, rr) != mStatus_NoError) LogMsg("EtcHostsAddNewEntries: mDNS_Register failed for %s", ARDisplayString(m, rr)); } } } return mDNSfalse; } // Delete entries from the core that are no longer needed. If justCheck is set, this function // does not delete, just returns true mDNSlocal mDNSBool EtcHostsDeleteOldEntries(AuthHash *newhosts, mDNSBool justCheck) { mDNS *const m = &mDNSStorage; AuthGroup *ag; mDNSu32 slot; AuthRecord *rr, *rrnext; for (slot = 0; slot < AUTH_HASH_SLOTS; slot++) for (ag = m->rrauth.rrauth_hash[slot]; ag; ag = ag->next) for (rr = ag->members; rr; rr = rrnext) { mDNSBool found = mDNSfalse; AuthGroup *ag1; AuthRecord *rr1; rrnext = rr->next; if (rr->RecordCallback != FreeEtcHosts) continue; ag1 = AuthGroupForRecord(newhosts, &rr->resrec); if (ag1) { rr1 = ag1->members; while (rr1) { if (IdenticalResourceRecord(&rr1->resrec, &rr->resrec)) { LogInfo("EtcHostsDeleteOldEntries: Old record %s found in new, skipping", ARDisplayString(m, rr)); found = mDNStrue; break; } rr1 = rr1->next; } } // there is no corresponding record in newhosts for the same name. This means // we should delete this from the core. if (!found) { if (justCheck) { LogInfo("EtcHostsDeleteOldEntries: Record %s not found in new, deleting", ARDisplayString(m, rr)); return mDNStrue; } // if primary is going away, make sure that the rest of the records // point to the new primary if (rr == ag->members) { AuthRecord *new_primary = rr->next; AuthRecord *r = new_primary; while (r) { if (r->RRSet == rr) { LogInfo("EtcHostsDeleteOldEntries: Updating Resource Record %s to primary", ARDisplayString(m, r)); r->RRSet = new_primary; } else LogMsg("EtcHostsDeleteOldEntries: ERROR!! Resource Record %s not pointing to primary %##s", ARDisplayString(m, r), r->resrec.name); r = r->next; } } LogInfo("EtcHostsDeleteOldEntries: Deleting %s", ARDisplayString(m, rr)); mDNS_Deregister_internal(m, rr, mDNS_Dereg_normal); } } return mDNSfalse; } mDNSlocal void UpdateEtcHosts(mDNS *const m, void *context) { AuthHash *newhosts = (AuthHash *)context; mDNS_CheckLock(m); //Delete old entries from the core if they are not present in the newhosts EtcHostsDeleteOldEntries(newhosts, mDNSfalse); // Add the new entries to the core if not already present in the core EtcHostsAddNewEntries(newhosts, mDNSfalse); } mDNSlocal void FreeNewHosts(AuthHash *newhosts) { mDNSu32 slot; AuthGroup *ag, *agnext; AuthRecord *rr, *rrnext; for (slot = 0; slot < AUTH_HASH_SLOTS; slot++) for (ag = newhosts->rrauth_hash[slot]; ag; ag = agnext) { agnext = ag->next; for (rr = ag->members; rr; rr = rrnext) { rrnext = rr->next; freeL("etchosts", rr); } freeL("AuthGroups", ag); } } mDNSlocal void mDNSMacOSXUpdateEtcHosts(mDNS *const m) { AuthHash newhosts; // As we will be modifying the core, we can only have one thread running at // any point in time. KQueueLock(); mDNSPlatformMemZero(&newhosts, sizeof(AuthHash)); // Get the file desecriptor (will trigger us to start watching for changes) int fd = mDNSMacOSXGetEtcHostsFD(); if (fd != -1) { LogInfo("mDNSMacOSXUpdateEtcHosts: Parsing /etc/hosts fd %d", fd); mDNSMacOSXParseEtcHosts(fd, &newhosts); } else LogInfo("mDNSMacOSXUpdateEtcHosts: /etc/hosts is not present"); // Optimization: Detect whether /etc/hosts changed or not. // // 1. Check to see if there are any new entries. We do this by seeing whether any entries in // newhosts is already registered with core. If we find at least one entry that is not // registered with core, then it means we have work to do. // // 2. Next, we check to see if any of the entries that are registered with core is not present // in newhosts. If we find at least one entry that is not present, it means we have work to // do. // // Note: We may not have to hold the lock right here as KQueueLock is held which prevents any // other thread from running. But mDNS_Lock is needed here as we will be traversing the core // data structure in EtcHostsDeleteOldEntries/NewEntries which might expect the lock to be held // in the future and this code does not have to change. mDNS_Lock(m); // Add the new entries to the core if not already present in the core if (!EtcHostsAddNewEntries(&newhosts, mDNStrue)) { // No new entries to add, check to see if we need to delete any old entries from the // core if they are not present in the newhosts if (!EtcHostsDeleteOldEntries(&newhosts, mDNStrue)) { LogInfo("mDNSMacOSXUpdateEtcHosts: No work"); FreeNewHosts(&newhosts); mDNS_Unlock(m); KQueueUnlock("/etc/hosts changed"); return; } } // This will flush the cache, stop and start the query so that the queries // can look at the /etc/hosts again // // Notes: // // We can't delete and free the records here. We wait for the mDNSCoreRestartAddressQueries to // deliver RMV events. It has to be done in a deferred way because we can't deliver RMV // events for local records *before* the RMV events for cache records. mDNSCoreRestartAddressQueries // delivers these events in the right order and then calls us back to delete them. // // Similarly, we do a deferred Registration of the record because mDNSCoreRestartAddressQueries // is a common function that looks at all local auth records and delivers a RMV including // the records that we might add here. If we deliver a ADD here, it will get a RMV and then when // the query is restarted, it will get another ADD. To avoid this (ADD-RMV-ADD), we defer registering // the record until the RMVs are delivered in mDNSCoreRestartAddressQueries after which UpdateEtcHosts // is called back where we do the Registration of the record. This results in RMV followed by ADD which // looks normal. mDNSCoreRestartAddressQueries(m, mDNSfalse, FlushAllCacheRecords, UpdateEtcHosts, &newhosts); FreeNewHosts(&newhosts); mDNS_Unlock(m); KQueueUnlock("/etc/hosts changed"); } #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - #pragma mark - Initialization & Teardown #endif CF_EXPORT CFDictionaryRef _CFCopySystemVersionDictionary(void); CF_EXPORT const CFStringRef _kCFSystemVersionProductNameKey; CF_EXPORT const CFStringRef _kCFSystemVersionProductVersionKey; CF_EXPORT const CFStringRef _kCFSystemVersionBuildVersionKey; // Major version 13 is 10.9.x mDNSexport void mDNSMacOSXSystemBuildNumber(char *HINFO_SWstring) { int major = 0, minor = 0; char letter = 0, prodname[256]="", prodvers[256]="", buildver[256]=""; CFDictionaryRef vers = _CFCopySystemVersionDictionary(); if (vers) { CFStringRef cfprodname = CFDictionaryGetValue(vers, _kCFSystemVersionProductNameKey); CFStringRef cfprodvers = CFDictionaryGetValue(vers, _kCFSystemVersionProductVersionKey); CFStringRef cfbuildver = CFDictionaryGetValue(vers, _kCFSystemVersionBuildVersionKey); if (cfprodname) CFStringGetCString(cfprodname, prodname, sizeof(prodname), kCFStringEncodingUTF8); if (cfprodvers) CFStringGetCString(cfprodvers, prodvers, sizeof(prodvers), kCFStringEncodingUTF8); if (cfbuildver && CFStringGetCString(cfbuildver, buildver, sizeof(buildver), kCFStringEncodingUTF8)) sscanf(buildver, "%d%c%d", &major, &letter, &minor); CFRelease(vers); } if (!major) { major = 13; LogMsg("Note: No Major Build Version number found; assuming 13"); } if (HINFO_SWstring) mDNS_snprintf(HINFO_SWstring, 256, "%s %s (%s), %s", prodname, prodvers, buildver, STRINGIFY(mDNSResponderVersion)); //LogMsg("%s %s (%s), %d %c %d", prodname, prodvers, buildver, major, letter, minor); // If product name is "Mac OS X" (or similar) we set OSXVers, else we set iOSVers; if ((prodname[0] & 0xDF) == 'M') OSXVers = major; else iOSVers = major; } // Test to see if we're the first client running on UDP port 5353, by trying to bind to 5353 without using SO_REUSEPORT. // If we fail, someone else got here first. That's not a big problem; we can share the port for multicast responses -- // we just need to be aware that we shouldn't expect to successfully receive unicast UDP responses. mDNSlocal mDNSBool mDNSPlatformInit_CanReceiveUnicast(void) { int err = -1; int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s < 3) LogMsg("mDNSPlatformInit_CanReceiveUnicast: socket error %d errno %d (%s)", s, errno, strerror(errno)); else { struct sockaddr_in s5353; s5353.sin_family = AF_INET; s5353.sin_port = MulticastDNSPort.NotAnInteger; s5353.sin_addr.s_addr = 0; err = bind(s, (struct sockaddr *)&s5353, sizeof(s5353)); close(s); } if (err) LogMsg("No unicast UDP responses"); else debugf("Unicast UDP responses okay"); return(err == 0); } mDNSlocal void CreatePTRRecord(const domainname *domain) { AuthRecord *rr; const domainname *pname = (domainname *)"\x9" "localhost"; rr= mallocL("localhosts", sizeof(*rr)); if (rr == NULL) return; mDNSPlatformMemZero(rr, sizeof(*rr)); mDNS_SetupResourceRecord(rr, mDNSNULL, mDNSInterface_LocalOnly, kDNSType_PTR, kHostNameTTL, kDNSRecordTypeKnownUnique, AuthRecordLocalOnly, mDNSNULL, mDNSNULL); AssignDomainName(&rr->namestorage, domain); rr->resrec.rdlength = DomainNameLength(pname); rr->resrec.rdata->u.name.c[0] = 0; AssignDomainName(&rr->resrec.rdata->u.name, pname); rr->resrec.namehash = DomainNameHashValue(rr->resrec.name); SetNewRData(&rr->resrec, mDNSNULL, 0); // Sets rr->rdatahash for us mDNS_Register(&mDNSStorage, rr); } // Setup PTR records for 127.0.0.1 and ::1. This helps answering them locally rather than relying // on the external DNS server to answer this. Sometimes, the DNS servers don't respond in a timely // fashion and applications depending on this e.g., telnetd, times out after 30 seconds creating // a bad user experience. For now, we specifically create only localhosts to handle radar://9354225 // // Note: We could have set this up while parsing the entries in /etc/hosts. But this is kept separate // intentionally to avoid adding to the complexity of code handling /etc/hosts. mDNSlocal void SetupLocalHostRecords(void) { char buffer[MAX_REVERSE_MAPPING_NAME]; domainname name; int i; struct in6_addr addr; mDNSu8 *ptr = addr.__u6_addr.__u6_addr8; if (inet_pton(AF_INET, "127.0.0.1", &addr) == 1) { mDNS_snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d.in-addr.arpa.", ptr[3], ptr[2], ptr[1], ptr[0]); MakeDomainNameFromDNSNameString(&name, buffer); CreatePTRRecord(&name); } else LogMsg("SetupLocalHostRecords: ERROR!! inet_pton AF_INET failed"); if (inet_pton(AF_INET6, "::1", &addr) == 1) { for (i = 0; i < 16; i++) { static const char hexValues[] = "0123456789ABCDEF"; buffer[i * 4 ] = hexValues[ptr[15 - i] & 0x0F]; buffer[i * 4 + 1] = '.'; buffer[i * 4 + 2] = hexValues[ptr[15 - i] >> 4]; buffer[i * 4 + 3] = '.'; } mDNS_snprintf(&buffer[64], sizeof(buffer)-64, "ip6.arpa."); MakeDomainNameFromDNSNameString(&name, buffer); CreatePTRRecord(&name); } else LogMsg("SetupLocalHostRecords: ERROR!! inet_pton AF_INET6 failed"); } #if APPLE_OSX_mDNSResponder // Don't compile for dnsextd target mDNSlocal void setSameDomainLabelPointer(void); #endif // Construction of Default Browse domain list (i.e. when clients pass NULL) is as follows: // 1) query for b._dns-sd._udp.local on LocalOnly interface // (.local manually generated via explicit callback) // 2) for each search domain (from prefs pane), query for b._dns-sd._udp.. // 3) for each result from (2), register LocalOnly PTR record b._dns-sd._udp.local. -> // 4) result above should generate a callback from question in (1). result added to global list // 5) global list delivered to client via GetSearchDomainList() // 6) client calls to enumerate domains now go over LocalOnly interface // (!!!KRS may add outgoing interface in addition) mDNSlocal mStatus mDNSPlatformInit_setup(mDNS *const m) { mStatus err; char HINFO_SWstring[256] = ""; mDNSMacOSXSystemBuildNumber(HINFO_SWstring); #if APPLE_OSX_mDNSResponder setSameDomainLabelPointer(); #endif err = mDNSHelperInit(); if (err) return err; // Store mDNSResponder Platform if (OSXVers) { m->mDNS_plat = platform_OSX; } else if (iOSVers) { if (IsAppleTV()) m->mDNS_plat = platform_Atv; else m->mDNS_plat = platform_iOS; } else { m->mDNS_plat = platform_NonApple; } // In 10.4, mDNSResponder is launched very early in the boot process, while other subsystems are still in the process of starting up. // If we can't read the user's preferences, then we sleep a bit and try again, for up to five seconds before we give up. int i; for (i=0; i<100; i++) { domainlabel testlabel; testlabel.c[0] = 0; GetUserSpecifiedLocalHostName(&testlabel); if (testlabel.c[0]) break; usleep(50000); } m->hostlabel.c[0] = 0; int get_model[2] = { CTL_HW, HW_MODEL }; size_t len_model = sizeof(HINFO_HWstring_buffer); // Normal Apple model names are of the form "iPhone2,1", and // internal code names are strings containing no commas, e.g. "N88AP". // We used to ignore internal code names, but Apple now uses these internal code names // even in released shipping products, so we no longer ignore strings containing no commas. // if (sysctl(get_model, 2, HINFO_HWstring_buffer, &len_model, NULL, 0) == 0 && strchr(HINFO_HWstring_buffer, ',')) if (sysctl(get_model, 2, HINFO_HWstring_buffer, &len_model, NULL, 0) == 0) HINFO_HWstring = HINFO_HWstring_buffer; // For names of the form "iPhone2,1" we use "iPhone" as the prefix for automatic name generation. // For names of the form "N88AP" containg no comma, we use the entire string. HINFO_HWstring_prefixlen = strchr(HINFO_HWstring_buffer, ',') ? strcspn(HINFO_HWstring, "0123456789") : strlen(HINFO_HWstring); if (mDNSPlatformInit_CanReceiveUnicast()) m->CanReceiveUnicastOn5353 = mDNStrue; mDNSu32 hlen = mDNSPlatformStrLen(HINFO_HWstring); mDNSu32 slen = mDNSPlatformStrLen(HINFO_SWstring); if (hlen + slen < 254) { m->HIHardware.c[0] = hlen; m->HISoftware.c[0] = slen; mDNSPlatformMemCopy(&m->HIHardware.c[1], HINFO_HWstring, hlen); mDNSPlatformMemCopy(&m->HISoftware.c[1], HINFO_SWstring, slen); } m->p->permanentsockets.port = MulticastDNSPort; m->p->permanentsockets.m = m; m->p->permanentsockets.sktv4 = -1; m->p->permanentsockets.kqsv4.KQcallback = myKQSocketCallBack; m->p->permanentsockets.kqsv4.KQcontext = &m->p->permanentsockets; m->p->permanentsockets.kqsv4.KQtask = "IPv4 UDP packet reception"; m->p->permanentsockets.sktv6 = -1; m->p->permanentsockets.kqsv6.KQcallback = myKQSocketCallBack; m->p->permanentsockets.kqsv6.KQcontext = &m->p->permanentsockets; m->p->permanentsockets.kqsv6.KQtask = "IPv6 UDP packet reception"; err = SetupSocket(&m->p->permanentsockets, MulticastDNSPort, AF_INET, mDNSNULL); if (err) LogMsg("mDNSPlatformInit_setup: SetupSocket(AF_INET) failed error %d errno %d (%s)", err, errno, strerror(errno)); err = SetupSocket(&m->p->permanentsockets, MulticastDNSPort, AF_INET6, mDNSNULL); if (err) LogMsg("mDNSPlatformInit_setup: SetupSocket(AF_INET6) failed error %d errno %d (%s)", err, errno, strerror(errno)); struct sockaddr_in s4; socklen_t n4 = sizeof(s4); if (getsockname(m->p->permanentsockets.sktv4, (struct sockaddr *)&s4, &n4) < 0) LogMsg("getsockname v4 error %d (%s)", errno, strerror(errno)); else m->UnicastPort4.NotAnInteger = s4.sin_port; if (m->p->permanentsockets.sktv6 >= 0) { struct sockaddr_in6 s6; socklen_t n6 = sizeof(s6); if (getsockname(m->p->permanentsockets.sktv6, (struct sockaddr *)&s6, &n6) < 0) LogMsg("getsockname v6 error %d (%s)", errno, strerror(errno)); else m->UnicastPort6.NotAnInteger = s6.sin6_port; } m->p->InterfaceList = mDNSNULL; m->p->userhostlabel.c[0] = 0; m->p->usernicelabel.c[0] = 0; m->p->prevoldnicelabel.c[0] = 0; m->p->prevnewnicelabel.c[0] = 0; m->p->prevoldhostlabel.c[0] = 0; m->p->prevnewhostlabel.c[0] = 0; m->p->NotifyUser = 0; m->p->KeyChainTimer = 0; m->p->WakeAtUTC = 0; m->p->RequestReSleep = 0; // Assume that everything is good to begin with. If something is not working, // we will detect that when we start sending questions. m->p->v4answers = 1; m->p->v6answers = 1; m->p->DNSTrigger = 0; m->p->LastConfigGeneration = 0; m->AutoTunnelRelayAddr = zerov6Addr; NetworkChangedKey_IPv4 = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4); NetworkChangedKey_IPv6 = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6); NetworkChangedKey_Hostnames = SCDynamicStoreKeyCreateHostNames(NULL); NetworkChangedKey_Computername = SCDynamicStoreKeyCreateComputerName(NULL); NetworkChangedKey_DNS = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetDNS); NetworkChangedKey_StateInterfacePrefix = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, CFSTR(""), NULL); if (!NetworkChangedKey_IPv4 || !NetworkChangedKey_IPv6 || !NetworkChangedKey_Hostnames || !NetworkChangedKey_Computername || !NetworkChangedKey_DNS || !NetworkChangedKey_StateInterfacePrefix) { LogMsg("SCDynamicStore string setup failed"); return(mStatus_NoMemoryErr); } err = WatchForNetworkChanges(m); if (err) { LogMsg("mDNSPlatformInit_setup: WatchForNetworkChanges failed %d", err); return(err); } err = WatchForSysEvents(m); if (err) { LogMsg("mDNSPlatformInit_setup: WatchForSysEvents failed %d", err); return(err); } mDNSs32 utc = mDNSPlatformUTC(); m->SystemWakeOnLANEnabled = SystemWakeForNetworkAccess(); myGetIfAddrs(1); UpdateInterfaceList(utc); SetupActiveInterfaces(utc); ReorderInterfaceList(); // Explicitly ensure that our Keychain operations utilize the system domain. #ifndef NO_SECURITYFRAMEWORK SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem); #endif mDNS_Lock(m); SetDomainSecrets(m); SetLocalDomains(); mDNS_Unlock(m); #ifndef NO_SECURITYFRAMEWORK err = SecKeychainAddCallback(KeychainChanged, kSecAddEventMask|kSecDeleteEventMask|kSecUpdateEventMask, m); if (err) { LogMsg("mDNSPlatformInit_setup: SecKeychainAddCallback failed %d", err); return(err); } #endif #if !defined(kIOPMAcknowledgmentOptionSystemCapabilityRequirements) || TARGET_OS_EMBEDDED LogMsg("Note: Compiled without SnowLeopard Fine-Grained Power Management support"); #else IOPMConnection c; IOReturn iopmerr = IOPMConnectionCreate(CFSTR("mDNSResponder"), kIOPMSystemPowerStateCapabilityCPU, &c); if (iopmerr) LogMsg("IOPMConnectionCreate failed %d", iopmerr); else { iopmerr = IOPMConnectionSetNotification(c, m, SnowLeopardPowerChanged); if (iopmerr) LogMsg("IOPMConnectionSetNotification failed %d", iopmerr); else { #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM IOPMConnectionSetDispatchQueue(c, dispatch_get_main_queue()); LogInfo("IOPMConnectionSetDispatchQueue is now running"); #else iopmerr = IOPMConnectionScheduleWithRunLoop(c, CFRunLoopGetMain(), kCFRunLoopDefaultMode); if (iopmerr) LogMsg("IOPMConnectionScheduleWithRunLoop failed %d", iopmerr); LogInfo("IOPMConnectionScheduleWithRunLoop is now running"); #endif /* MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM */ } } m->p->IOPMConnection = iopmerr ? mDNSNULL : c; if (iopmerr) // If IOPMConnectionCreate unavailable or failed, proceed with old-style power notification code below #endif // kIOPMAcknowledgmentOptionSystemCapabilityRequirements { m->p->PowerConnection = IORegisterForSystemPower(m, &m->p->PowerPortRef, PowerChanged, &m->p->PowerNotifier); if (!m->p->PowerConnection) { LogMsg("mDNSPlatformInit_setup: IORegisterForSystemPower failed"); return(-1); } else { #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM IONotificationPortSetDispatchQueue(m->p->PowerPortRef, dispatch_get_main_queue()); #else CFRunLoopAddSource(CFRunLoopGetMain(), IONotificationPortGetRunLoopSource(m->p->PowerPortRef), kCFRunLoopDefaultMode); #endif /* MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM */ } } #if APPLE_OSX_mDNSResponder // Note: We use SPMetricPortability > 35 to indicate a laptop of some kind // SPMetricPortability <= 35 means nominally a non-portable machine (i.e. Mac mini or better) // Apple TVs, AirPort base stations, and Time Capsules do not actually weigh 3kg, but we assign them // higher 'nominal' masses to indicate they should be treated as being relatively less portable than a laptop if (!strncasecmp(HINFO_HWstring, "Xserve", 6)) { SPMetricPortability = 25 /* 30kg */; SPMetricMarginalPower = 84 /* 250W */; SPMetricTotalPower = 85 /* 300W */; } else if (!strncasecmp(HINFO_HWstring, "RackMac", 7)) { SPMetricPortability = 25 /* 30kg */; SPMetricMarginalPower = 84 /* 250W */; SPMetricTotalPower = 85 /* 300W */; } else if (!strncasecmp(HINFO_HWstring, "MacPro", 6)) { SPMetricPortability = 27 /* 20kg */; SPMetricMarginalPower = 84 /* 250W */; SPMetricTotalPower = 85 /* 300W */; } else if (!strncasecmp(HINFO_HWstring, "PowerMac", 8)) { SPMetricPortability = 27 /* 20kg */; SPMetricMarginalPower = 82 /* 160W */; SPMetricTotalPower = 83 /* 200W */; } else if (!strncasecmp(HINFO_HWstring, "iMac", 4)) { SPMetricPortability = 30 /* 10kg */; SPMetricMarginalPower = 77 /* 50W */; SPMetricTotalPower = 78 /* 60W */; } else if (!strncasecmp(HINFO_HWstring, "Macmini", 7)) { SPMetricPortability = 33 /* 5kg */; SPMetricMarginalPower = 73 /* 20W */; SPMetricTotalPower = 74 /* 25W */; } else if (!strncasecmp(HINFO_HWstring, "TimeCapsule", 11)) { SPMetricPortability = 34 /* 4kg */; SPMetricMarginalPower = 10 /* ~0W */; SPMetricTotalPower = 70 /* 13W */; } else if (!strncasecmp(HINFO_HWstring, "AirPort", 7)) { SPMetricPortability = 35 /* 3kg */; SPMetricMarginalPower = 10 /* ~0W */; SPMetricTotalPower = 70 /* 12W */; } else if ( IsAppleTV() ) { SPMetricPortability = 35 /* 3kg */; SPMetricMarginalPower = 60 /* 1W */; SPMetricTotalPower = 63 /* 2W */; } else if (!strncasecmp(HINFO_HWstring, "MacBook", 7)) { SPMetricPortability = 37 /* 2kg */; SPMetricMarginalPower = 71 /* 13W */; SPMetricTotalPower = 72 /* 15W */; } else if (!strncasecmp(HINFO_HWstring, "PowerBook", 9)) { SPMetricPortability = 37 /* 2kg */; SPMetricMarginalPower = 71 /* 13W */; SPMetricTotalPower = 72 /* 15W */; } LogSPS("HW_MODEL: %.*s (%s) Portability %d Marginal Power %d Total Power %d Features %d", HINFO_HWstring_prefixlen, HINFO_HWstring, HINFO_HWstring, SPMetricPortability, SPMetricMarginalPower, SPMetricTotalPower, SPMetricFeatures); #endif // APPLE_OSX_mDNSResponder // Currently this is not defined. SSL code will eventually fix this. If it becomes // critical, we will define this to workaround the bug in SSL. #ifdef __SSL_NEEDS_SERIALIZATION__ SSLqueue = dispatch_queue_create("com.apple.mDNSResponder.SSLQueue", NULL); #else SSLqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); #endif if (SSLqueue == mDNSNULL) LogMsg("dispatch_queue_create: SSL queue NULL"); mDNSMacOSXUpdateEtcHosts(m); SetupLocalHostRecords(); return(mStatus_NoError); } mDNSexport mStatus mDNSPlatformInit(mDNS *const m) { #if MDNS_NO_DNSINFO LogMsg("Note: Compiled without Apple-specific Split-DNS support"); #endif // Adding interfaces will use this flag, so set it now. m->DivertMulticastAdvertisements = !m->AdvertiseLocalAddresses; #if APPLE_OSX_mDNSResponder m->SPSBrowseCallback = UpdateSPSStatus; #endif // APPLE_OSX_mDNSResponder mStatus result = mDNSPlatformInit_setup(m); // We don't do asynchronous initialization on OS X, so by the time we get here the setup will already // have succeeded or failed -- so if it succeeded, we should just call mDNSCoreInitComplete() immediately if (result == mStatus_NoError) { mDNSCoreInitComplete(m, mStatus_NoError); initializeD2DPlugins(m); } result = DNSSECCryptoInit(m); return(result); } mDNSexport void mDNSPlatformClose(mDNS *const m) { if (m->p->PowerConnection) { #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM IONotificationPortSetDispatchQueue(m->p->PowerPortRef, NULL); #else CFRunLoopRemoveSource(CFRunLoopGetMain(), IONotificationPortGetRunLoopSource(m->p->PowerPortRef), kCFRunLoopDefaultMode); #endif // According to , a single call // to IORegisterForSystemPower creates *three* objects that need to be disposed individually: IODeregisterForSystemPower(&m->p->PowerNotifier); IOServiceClose ( m->p->PowerConnection); IONotificationPortDestroy ( m->p->PowerPortRef); m->p->PowerConnection = 0; } if (m->p->Store) { #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM if (!SCDynamicStoreSetDispatchQueue(m->p->Store, NULL)) LogMsg("mDNSPlatformClose: SCDynamicStoreSetDispatchQueue failed"); #else CFRunLoopRemoveSource(CFRunLoopGetMain(), m->p->StoreRLS, kCFRunLoopDefaultMode); CFRunLoopSourceInvalidate(m->p->StoreRLS); CFRelease(m->p->StoreRLS); m->p->StoreRLS = NULL; #endif CFRelease(m->p->Store); m->p->Store = NULL; } if (m->p->PMRLS) { CFRunLoopRemoveSource(CFRunLoopGetMain(), m->p->PMRLS, kCFRunLoopDefaultMode); CFRunLoopSourceInvalidate(m->p->PMRLS); CFRelease(m->p->PMRLS); m->p->PMRLS = NULL; } if (m->p->SysEventNotifier >= 0) { close(m->p->SysEventNotifier); m->p->SysEventNotifier = -1; } terminateD2DPlugins(); mDNSs32 utc = mDNSPlatformUTC(); MarkAllInterfacesInactive(utc); ClearInactiveInterfaces(utc); CloseSocketSet(&m->p->permanentsockets); #if APPLE_OSX_mDNSResponder // clean up tunnels while (m->TunnelClients) { ClientTunnel *cur = m->TunnelClients; LogInfo("mDNSPlatformClose: removing client tunnel %p %##s from list", cur, cur->dstname.c); if (cur->q.ThisQInterval >= 0) mDNS_StopQuery(m, &cur->q); AutoTunnelSetKeys(cur, mDNSfalse); m->TunnelClients = cur->next; freeL("ClientTunnel", cur); } if (AnonymousRacoonConfig) { AnonymousRacoonConfig = mDNSNULL; LogInfo("mDNSPlatformClose: Deconfiguring autotunnel need not be done in mDNSResponder"); } #endif // APPLE_OSX_mDNSResponder } #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - #pragma mark - General Platform Support Layer functions #endif mDNSexport mDNSu32 mDNSPlatformRandomNumber(void) { return(arc4random()); } mDNSexport mDNSs32 mDNSPlatformOneSecond = 1000; mDNSexport mDNSu32 mDNSPlatformClockDivisor = 0; mDNSexport mStatus mDNSPlatformTimeInit(void) { // Notes: Typical values for mach_timebase_info: // tbi.numer = 1000 million // tbi.denom = 33 million // These are set such that (mach_absolute_time() * numer/denom) gives us nanoseconds; // numer / denom = nanoseconds per hardware clock tick (e.g. 30); // denom / numer = hardware clock ticks per nanosecond (e.g. 0.033) // (denom*1000000) / numer = hardware clock ticks per millisecond (e.g. 33333) // So: mach_absolute_time() / ((denom*1000000)/numer) = milliseconds // // Arithmetic notes: // tbi.denom is at least 1, and not more than 2^32-1. // Therefore (tbi.denom * 1000000) is at least one million, but cannot overflow a uint64_t. // tbi.denom is at least 1, and not more than 2^32-1. // Therefore clockdivisor should end up being a number roughly in the range 10^3 - 10^9. // If clockdivisor is less than 10^3 then that means that the native clock frequency is less than 1MHz, // which is unlikely on any current or future Macintosh. // If clockdivisor is greater than 10^9 then that means the native clock frequency is greater than 1000GHz. // When we ship Macs with clock frequencies above 1000GHz, we may have to update this code. struct mach_timebase_info tbi; kern_return_t result = mach_timebase_info(&tbi); if (result == KERN_SUCCESS) mDNSPlatformClockDivisor = ((uint64_t)tbi.denom * 1000000) / tbi.numer; return(result); } mDNSexport mDNSs32 mDNSPlatformRawTime(void) { if (mDNSPlatformClockDivisor == 0) { LogMsg("mDNSPlatformRawTime called before mDNSPlatformTimeInit"); return(0); } static uint64_t last_mach_absolute_time = 0; //static uint64_t last_mach_absolute_time = 0x8000000000000000LL; // Use this value for testing the alert display uint64_t this_mach_absolute_time = mach_absolute_time(); if ((int64_t)this_mach_absolute_time - (int64_t)last_mach_absolute_time < 0) { LogMsg("mDNSPlatformRawTime: last_mach_absolute_time %08X%08X", last_mach_absolute_time); LogMsg("mDNSPlatformRawTime: this_mach_absolute_time %08X%08X", this_mach_absolute_time); // Update last_mach_absolute_time *before* calling NotifyOfElusiveBug() last_mach_absolute_time = this_mach_absolute_time; // Note: This bug happens all the time on 10.3 NotifyOfElusiveBug("mach_absolute_time went backwards!", "This error occurs from time to time, often on newly released hardware, " "and usually the exact cause is different in each instance.\r\r" "Please file a new Radar bug report with the title “mach_absolute_time went backwards” " "and assign it to Radar Component “Kernel” Version “X”."); } last_mach_absolute_time = this_mach_absolute_time; return((mDNSs32)(this_mach_absolute_time / mDNSPlatformClockDivisor)); } mDNSexport mDNSs32 mDNSPlatformUTC(void) { return time(NULL); } // Locking is a no-op here, because we're single-threaded with a CFRunLoop, so we can never interrupt ourselves mDNSexport void mDNSPlatformLock (const mDNS *const m) { (void)m; } mDNSexport void mDNSPlatformUnlock (const mDNS *const m) { (void)m; } mDNSexport void mDNSPlatformStrCopy( void *dst, const void *src) { strcpy((char *)dst, (const char *)src); } mDNSexport mDNSu32 mDNSPlatformStrLCopy( void *dst, const void *src, mDNSu32 dstlen) { return (strlcpy((char *)dst, (const char *)src, dstlen)); } mDNSexport mDNSu32 mDNSPlatformStrLen ( const void *src) { return(strlen((const char*)src)); } mDNSexport void mDNSPlatformMemCopy( void *dst, const void *src, mDNSu32 len) { memcpy(dst, src, len); } mDNSexport mDNSBool mDNSPlatformMemSame(const void *dst, const void *src, mDNSu32 len) { return(memcmp(dst, src, len) == 0); } mDNSexport int mDNSPlatformMemCmp(const void *dst, const void *src, mDNSu32 len) { return(memcmp(dst, src, len)); } mDNSexport void mDNSPlatformMemZero( void *dst, mDNSu32 len) { memset(dst, 0, len); } mDNSexport void mDNSPlatformQsort ( void *base, int nel, int width, int (*compar)(const void *, const void *)) { return (qsort(base, nel, width, compar)); } #if !(APPLE_OSX_mDNSResponder && MACOSX_MDNS_MALLOC_DEBUGGING) mDNSexport void * mDNSPlatformMemAllocate(mDNSu32 len) { return(mallocL("mDNSPlatformMemAllocate", len)); } #endif mDNSexport void mDNSPlatformMemFree (void *mem) { freeL("mDNSPlatformMemFree", mem); } mDNSexport void mDNSPlatformSetAllowSleep(mDNSBool allowSleep, const char *reason) { mDNS *const m = &mDNSStorage; if (allowSleep && m->p->IOPMAssertion) { LogInfo("%s Destroying NoIdleSleep power assertion", __FUNCTION__); IOPMAssertionRelease(m->p->IOPMAssertion); m->p->IOPMAssertion = 0; } else if (!allowSleep) { #ifdef kIOPMAssertionTypeNoIdleSleep if (m->p->IOPMAssertion) { IOPMAssertionRelease(m->p->IOPMAssertion); m->p->IOPMAssertion = 0; } CFStringRef assertionName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s.%d %s"), getprogname(), getpid(), reason ? reason : ""); IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, assertionName ? assertionName : CFSTR("mDNSResponder"), &m->p->IOPMAssertion); if (assertionName) CFRelease(assertionName); LogInfo("%s Creating NoIdleSleep power assertion", __FUNCTION__); #endif } } mDNSexport void mDNSPlatformPreventSleep(mDNSu32 timeout, const char *reason) { mDNS *const m = &mDNSStorage; if (m->p->IOPMAssertion) { LogSPS("Sleep Assertion is already being held. Will not attempt to get it again for %d seconds for %s", timeout, reason); return; } #ifdef kIOPMAssertionTypeNoIdleSleep #if TARGET_OS_EMBEDDED if (!IsAppleTV()) return; // No need for maintenance wakes on non-AppleTV embedded devices. #endif double timeoutVal = (double)timeout; CFStringRef str = CFStringCreateWithCString(NULL, reason, kCFStringEncodingUTF8); CFNumberRef Timeout_num = CFNumberCreate(NULL, kCFNumberDoubleType, &timeoutVal); CFMutableDictionaryRef assertionProperties = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (IsAppleTV()) CFDictionarySetValue(assertionProperties, kIOPMAssertionTypeKey, kIOPMAssertPreventUserIdleSystemSleep); else CFDictionarySetValue(assertionProperties, kIOPMAssertionTypeKey, kIOPMAssertMaintenanceActivity); CFDictionarySetValue(assertionProperties, kIOPMAssertionTimeoutKey, Timeout_num); CFDictionarySetValue(assertionProperties, kIOPMAssertionNameKey, str); IOPMAssertionCreateWithProperties(assertionProperties, (IOPMAssertionID *)&m->p->IOPMAssertion); CFRelease(str); CFRelease(Timeout_num); CFRelease(assertionProperties); LogSPS("Got an idle sleep assertion for %d seconds for %s", timeout, reason); #endif } mDNSexport void mDNSPlatformSendWakeupPacket(mDNSInterfaceID InterfaceID, char *EthAddr, char *IPAddr, int iteration) { mDNSu32 ifindex; // Sanity check ifindex = mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, InterfaceID, mDNStrue); if (ifindex <= 0) { LogMsg("mDNSPlatformSendWakeupPacket: ERROR!! Invalid InterfaceID %u", ifindex); return; } mDNSSendWakeupPacket(ifindex, EthAddr, IPAddr, iteration); } mDNSexport mDNSBool mDNSPlatformInterfaceIsD2D(mDNSInterfaceID InterfaceID) { NetworkInterfaceInfoOSX *info; if (InterfaceID == mDNSInterface_P2P) return mDNStrue; // mDNSInterface_BLE not considered a D2D interface for the purpose of this // routine, since it's not implemented via a D2D plugin. if (InterfaceID == mDNSInterface_BLE) return mDNSfalse; if ( (InterfaceID == mDNSInterface_Any) || (InterfaceID == mDNSInterfaceMark) || (InterfaceID == mDNSInterface_LocalOnly) || (InterfaceID == mDNSInterface_Unicast)) return mDNSfalse; // Compare to cached AWDL interface ID. if (AWDLInterfaceID && (InterfaceID == AWDLInterfaceID)) return mDNStrue; info = IfindexToInterfaceInfoOSX(InterfaceID); if (info == NULL) { // this log message can print when operations are stopped on an interface that has gone away LogInfo("mDNSPlatformInterfaceIsD2D: Invalid interface index %d", InterfaceID); return mDNSfalse; } return (mDNSBool) info->D2DInterface; } // Filter records send over P2P (D2D) type interfaces // Note that the terms P2P and D2D are used synonymously in the current code and comments. mDNSexport mDNSBool mDNSPlatformValidRecordForInterface(const AuthRecord *rr, mDNSInterfaceID InterfaceID) { // For an explicit match to a valid interface ID, return true. if (rr->resrec.InterfaceID == InterfaceID) return mDNStrue; // Only filtering records for D2D type interfaces, return true for all other interface types. if (!mDNSPlatformInterfaceIsD2D(InterfaceID)) return mDNStrue; // If it's an AWDL interface the record must be explicitly marked to include AWDL. if (InterfaceID == AWDLInterfaceID) { if (rr->ARType == AuthRecordAnyIncludeAWDL || rr->ARType == AuthRecordAnyIncludeAWDLandP2P) return mDNStrue; else return mDNSfalse; } // Send record if it is explicitly marked to include all other P2P type interfaces. if (rr->ARType == AuthRecordAnyIncludeP2P || rr->ARType == AuthRecordAnyIncludeAWDLandP2P) return mDNStrue; // Don't send the record over this interface. return mDNSfalse; } // Filter questions send over P2P (D2D) type interfaces. mDNSexport mDNSBool mDNSPlatformValidQuestionForInterface(DNSQuestion *q, const NetworkInterfaceInfo *intf) { // For an explicit match to a valid interface ID, return true. if (q->InterfaceID == intf->InterfaceID) return mDNStrue; // Only filtering questions for D2D type interfaces if (!mDNSPlatformInterfaceIsD2D(intf->InterfaceID)) return mDNStrue; // If it's an AWDL interface the question must be explicitly marked to include AWDL. if (intf->InterfaceID == AWDLInterfaceID) { if (q->flags & kDNSServiceFlagsIncludeAWDL) return mDNStrue; else return mDNSfalse; } // Sent question if it is explicitly marked to include all other P2P type interfaces. if (q->flags & kDNSServiceFlagsIncludeP2P) return mDNStrue; // Don't send the question over this interface. return mDNSfalse; } // Returns true unless record was received over the AWDL interface and // the question was not specific to the AWDL interface or did not specify kDNSServiceInterfaceIndexAny // with the kDNSServiceFlagsIncludeAWDL flag set. mDNSexport mDNSBool mDNSPlatformValidRecordForQuestion(const ResourceRecord *const rr, const DNSQuestion *const q) { if (!rr->InterfaceID || (rr->InterfaceID == q->InterfaceID)) return mDNStrue; if ((rr->InterfaceID == AWDLInterfaceID) && !(q->flags & kDNSServiceFlagsIncludeAWDL)) return mDNSfalse; return mDNStrue; } // formating time to RFC 4034 format mDNSexport void mDNSPlatformFormatTime(unsigned long te, mDNSu8 *buf, int bufsize) { struct tm tmTime; time_t t = (time_t)te; // Time since epoch : strftime takes "tm". Convert seconds to "tm" using // gmtime_r first and then use strftime gmtime_r(&t, &tmTime); strftime((char *)buf, bufsize, "%Y%m%d%H%M%S", &tmTime); } mDNSexport mDNSs32 mDNSPlatformGetPID() { return getpid(); } // Schedule a function asynchronously on the main queue mDNSexport void mDNSPlatformDispatchAsync(mDNS *const m, void *context, AsyncDispatchFunc func) { // KQueueLock/Unlock is used for two purposes // // 1. We can't be running along with the KQueue thread and hence acquiring the lock // serializes the access to the "core" // // 2. KQueueUnlock also sends a message wake up the KQueue thread which in turn wakes // up and calls udsserver_idle which schedules the messages across the uds socket. // If "func" delivers something to the uds socket from the dispatch thread, it will // not be delivered immediately if not for the Unlock. dispatch_async(dispatch_get_main_queue(), ^{ KQueueLock(); func(m, context); KQueueUnlock("mDNSPlatformDispatchAsync"); #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM // KQueueUnlock is a noop. Hence, we need to run kick off the idle loop // to handle any message that "func" might deliver. TriggerEventCompletion(); #endif }); } // definitions for device-info record construction #define DEVINFO_MODEL "model=" #define DEVINFO_MODEL_LEN sizeof_string(DEVINFO_MODEL) #define OSX_VER "osxvers=" #define OSX_VER_LEN sizeof_string(OSX_VER) #define VER_NUM_LEN 2 // 2 digits of version number added to base string #define MODEL_COLOR "ecolor=" #define MODEL_COLOR_LEN sizeof_string(MODEL_COLOR) #define MODEL_RGB_VALUE_LEN sizeof_string("255,255,255") // 'r,g,b' // Bytes available in TXT record for model name after subtracting space for other // fixed size strings and their length bytes. #define MAX_MODEL_NAME_LEN (256 - (DEVINFO_MODEL_LEN + 1) - (OSX_VER_LEN + VER_NUM_LEN + 1) - (MODEL_COLOR_LEN + MODEL_RGB_VALUE_LEN + 1)) mDNSlocal mDNSu8 getModelIconColors(char *color) { mDNSPlatformMemZero(color, MODEL_RGB_VALUE_LEN + 1); #if !TARGET_OS_EMBEDDED && defined(kIOPlatformDeviceEnclosureColorKey) mDNSu8 red = 0; mDNSu8 green = 0; mDNSu8 blue = 0; IOReturn rGetDeviceColor = IOPlatformGetDeviceColor(kIOPlatformDeviceEnclosureColorKey, &red, &green, &blue); if (kIOReturnSuccess == rGetDeviceColor) { // IOKit was able to get enclosure color for the current device. return snprintf(color, MODEL_RGB_VALUE_LEN + 1, "%d,%d,%d", red, green, blue); } #endif // !TARGET_OS_EMBEDDED && defined(kIOPlatformDeviceEnclosureColorKey) return 0; } // Initialize device-info TXT record contents and return total length of record data. mDNSexport mDNSu32 initializeDeviceInfoTXT(mDNS *m, mDNSu8 *ptr) { mDNSu8 *bufferStart = ptr; mDNSu8 len = m->HIHardware.c[0] < MAX_MODEL_NAME_LEN ? m->HIHardware.c[0] : MAX_MODEL_NAME_LEN; *ptr = DEVINFO_MODEL_LEN + len; // total length of DEVINFO_MODEL string plus the hardware name string ptr++; mDNSPlatformMemCopy(ptr, DEVINFO_MODEL, DEVINFO_MODEL_LEN); ptr += DEVINFO_MODEL_LEN; mDNSPlatformMemCopy(ptr, m->HIHardware.c + 1, len); ptr += len; // only include this string for OSX if (OSXVers) { char ver_num[VER_NUM_LEN + 1]; // version digits + null written by snprintf *ptr = OSX_VER_LEN + VER_NUM_LEN; // length byte ptr++; mDNSPlatformMemCopy(ptr, OSX_VER, OSX_VER_LEN); ptr += OSX_VER_LEN; // convert version number to ASCII, add 1 for terminating null byte written by snprintf() // WARNING: This code assumes that OSXVers is always exactly two digits snprintf(ver_num, VER_NUM_LEN + 1, "%d", OSXVers); mDNSPlatformMemCopy(ptr, ver_num, VER_NUM_LEN); ptr += VER_NUM_LEN; char rgb[MODEL_RGB_VALUE_LEN + 1]; // RGB value + null written by snprintf len = getModelIconColors(rgb); if (len) { *ptr = MODEL_COLOR_LEN + len; // length byte ptr++; mDNSPlatformMemCopy(ptr, MODEL_COLOR, MODEL_COLOR_LEN); ptr += MODEL_COLOR_LEN; mDNSPlatformMemCopy(ptr, rgb, len); ptr += len; } } return (ptr - bufferStart); } #if APPLE_OSX_mDNSResponder // Don't compile for dnsextd target // Use the scalar version of SameDomainLabel() by default mDNSlocal mDNSBool scalarSameDomainLabel(const mDNSu8 *a, const mDNSu8 *b); mDNSlocal mDNSBool vectorSameDomainLabel(const mDNSu8 *a, const mDNSu8 *b); mDNSlocal mDNSBool (*SameDomainLabelPointer)(const mDNSu8 *a, const mDNSu8 *b) = scalarSameDomainLabel; #include #define _cpu_capabilities ((uint32_t*) _COMM_PAGE_CPU_CAPABILITIES)[0] #if TARGET_OS_EMBEDDED #include // Cache line aligned table that returns 32 for the upper case letters. // This will take up 4 cache lines. static const __attribute__ ((aligned(64))) uint8_t upper_to_lower_case_table[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Neon version mDNSlocal mDNSBool vectorSameDomainLabel(const mDNSu8 *a, const mDNSu8 *b) { const int len = *a++; if (len > MAX_DOMAIN_LABEL) { fprintf(stderr, "v: Malformed label (too long)\n"); return(mDNSfalse); } if (len != *b++) { return(mDNSfalse); } uint32_t len_count = len; uint8x16_t vA, vB, vARotated, vBRotated, vMaskA, vMaskB; uint8x16_t v32 = vdupq_n_u8(32); uint8x16_t v37 = vdupq_n_u8(37); uint8x16_t v101 = vdupq_n_u8(101); #if !defined __arm64__ uint32x4_t vtemp32; uint32x2_t vtemp32d; uint32_t sum; #endif while(len_count > 15) { vA = vld1q_u8(a); vB = vld1q_u8(b); a += 16; b += 16; //Make vA to lowercase if there is any uppercase. vARotated = vaddq_u8(vA, v37); //Map 'A' ~ 'Z' from '65' ~ '90' to '102' ~ '127'. vMaskA = vcgtq_s8(vARotated, v101); //Check if anything is greater than '101' which means we have uppercase letters. vMaskA = vandq_u8(vMaskA, v32); //Prepare 32 for the elements with uppercase letters. vA = vaddq_u8(vA, vMaskA); //Add 32 only to the uppercase letters to make them lowercase letters. //Make vB to lowercase if there is any uppercase. vBRotated = vaddq_u8(vB, v37); //Map 'A' ~ 'Z' from '65' ~ '90' to '102' ~ '127'. vMaskB = vcgtq_s8(vBRotated, v101); //Check if anything is greater than '101' which means we have uppercase letters. vMaskB = vandq_u8(vMaskB, v32); //Prepare 32 for the elements with uppercase letters. vB = vaddq_u8(vB, vMaskB); //Add 32 only to the uppercase letters to make them lowercase letters. //Compare vA & vB vA = vceqq_u8(vA, vB); #if defined __arm64__ //View 8-bit element as 32-bit => a3 a2 a1 a0 //If min of 4 32-bit values in vA is 0xffffffff, then it means we have 0xff for all 16. if(vminvq_u32(vA) != 0xffffffffU) { return(mDNSfalse); } #else //See if any element was not same. //View 8-bit element as 16-bit => a7 a6 a5 a4 a3 a2 a1 a0 //(a7+a6) (a5+a4) (a3+a2) (a1+a0) => Each will be 0xffff + 0xffff = 0x0001fffe when all same. vtemp32 = vpaddlq_u16(vA); vtemp32d = vpadd_u32(vget_low_u32(vtemp32), vget_high_u32(vtemp32)); vtemp32d = vpadd_u32(vtemp32d, vtemp32d); sum = vget_lane_u32(vtemp32d, 0); //0x0001fffe + 0x0001fffe + 0x0001fffe + 0x0001fffe = 0x0007fff8U when all same. if(sum != 0x0007fff8U) { return(mDNSfalse); } #endif len_count -= 16; } uint8x8_t vAd, vBd, vARotatedd, vBRotatedd, vMaskAd, vMaskBd; uint8x8_t v32d = vdup_n_u8(32); uint8x8_t v37d = vdup_n_u8(37); uint8x8_t v101d = vdup_n_u8(101); while(len_count > 7) { vAd = vld1_u8(a); vBd = vld1_u8(b); a += 8; b += 8; //Make vA to lowercase if there is any uppercase. vARotatedd = vadd_u8(vAd, v37d); //Map 'A' ~ 'Z' from '65' ~ '90' to '102' ~ '127'. vMaskAd = vcgt_s8(vARotatedd, v101d); //Check if anything is greater than '101' which means we have uppercase letters. vMaskAd = vand_u8(vMaskAd, v32d); //Prepare 32 for the elements with uppercase letters. vAd = vadd_u8(vAd, vMaskAd); //Add 32 only to the uppercase letters to make them lowercase letters. //Make vB to lowercase if there is any uppercase. vBRotatedd = vadd_u8(vBd, v37d); //Map 'A' ~ 'Z' from '65' ~ '90' to '102' ~ '127'. vMaskBd = vcgt_s8(vBRotatedd, v101d); //Check if anything is greater than '101' which means we have uppercase letters. vMaskBd = vand_u8(vMaskBd, v32d); //Prepare 32 for the elements with uppercase letters. vBd = vadd_u8(vBd, vMaskBd); //Add 32 only to the uppercase letters to make them lowercase letters. //Compare vA & vB vAd = vceq_u8(vAd, vBd); #if defined __arm64__ //View 8-bit element as 32-bit => a1 a0 //If min of 2 32-bit values in vAd is 0xffffffff, then it means we have 0xff for all 16. if(vminv_u32(vAd) != 0xffffffffU) { return(mDNSfalse); } #else //See if any element was not same. //View 8-bit element as 16-bit => a3 a2 a1 a0 //(a3+a2) (a1+a0) => Each will be 0xffff + 0xffff = 0x0001fffe when all same. vtemp32d = vpaddl_u16(vAd); vtemp32d = vpadd_u32(vtemp32d, vtemp32d); sum = vget_lane_u32(vtemp32d, 0); //0x0001fffe + 0x0001fffe = 0x0003fffc when all same. if(sum != 0x0003fffcU) { return(mDNSfalse); } #endif len_count -= 8; } while(len_count > 0) { mDNSu8 ac = *a++; mDNSu8 bc = *b++; ac += upper_to_lower_case_table[ac]; bc += upper_to_lower_case_table[bc]; if (ac != bc) { return(mDNSfalse); } len_count -= 1; } return(mDNStrue); } // Use vectorized implementation if it is supported on this platform. mDNSlocal void setSameDomainLabelPointer(void) { if(_cpu_capabilities & kHasNeon) { // Use Neon Code SameDomainLabelPointer = vectorSameDomainLabel; LogMsg("setSameDomainLabelPointer: using vector code"); } else LogMsg("setSameDomainLabelPointer: using scalar code"); } #else // TARGET_OS_EMBEDDED #include // Cache line aligned table that returns 32 for the upper case letters. // This will take up 4 cache lines. static const __attribute__ ((aligned(64))) uint8_t upper_to_lower_case_table[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // SSE2 version mDNSlocal mDNSBool vectorSameDomainLabel(const mDNSu8 *a, const mDNSu8 *b) { const int len = *a++; if (len > MAX_DOMAIN_LABEL) { fprintf(stderr, "v: Malformed label (too long)\n"); return(mDNSfalse); } if (len != *b++) { return(mDNSfalse); } uint32_t len_count = len; static const __attribute__ ((aligned(16))) unsigned char c_32[16] = { 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32 }; static const __attribute__ ((aligned(16))) unsigned char c_37[16] = { 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37 }; static const __attribute__ ((aligned(16))) unsigned char c_101[16] = { 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101 }; __m128i v37 = _mm_load_si128((__m128i*)c_37); __m128i v101 = _mm_load_si128((__m128i*)c_101); __m128i v32 = _mm_load_si128((__m128i*)c_32); uint32_t is_equal; __m128i vA, vB, vARotated, vBRotated, vMaskA, vMaskB; //AVX code that uses higher bandwidth (more elements per vector) was removed //to speed up the processing on the small sizes. //When I had them, the performance of 1 ~ 8 characters were slower by about 10% ~ 30%. while(len_count > 15) { vA = _mm_loadu_si128((__m128i*)a); vB = _mm_loadu_si128((__m128i*)b); a += 16; b += 16; //Make vA to lowercase if there is any uppercase. vARotated = _mm_add_epi8(vA, v37); //Map 'A' ~ 'Z' from '65' ~ '90' to '102' ~ '127'. vMaskA = _mm_cmpgt_epi8(vARotated, v101); //Check if anything is greater than '101' which means we have uppercase letters. vMaskA = _mm_and_si128(vMaskA, v32); //Prepare 32 for the elements with uppercase letters. vA = _mm_add_epi8(vA, vMaskA); //Add 32 only to the uppercase letters to make them lowercase letters. //Make vB to lowercase if there is any uppercase. vBRotated = _mm_add_epi8(vB, v37); //Map 'A' ~ 'Z' from '65' ~ '90' to '102' ~ '127'. vMaskB = _mm_cmpgt_epi8(vBRotated, v101); //Check if anything is greater than '101' which means we have uppercase letters. vMaskB = _mm_and_si128(vMaskB, v32); //Prepare 32 for the elements with uppercase letters. vB = _mm_add_epi8(vB, vMaskB); //Add 32 only to the uppercase letters to make them lowercase letters. //Compare vA & vB vA = _mm_cmpeq_epi8(vA, vB); //Return if any different. is_equal = _mm_movemask_epi8(vA); is_equal = is_equal & 0xffff; if(is_equal != 0xffff) { return(mDNSfalse); } len_count -= 16; } while(len_count > 0) { mDNSu8 ac = *a++; mDNSu8 bc = *b++; //Table will return 32 for upper case letters only. //0 will be returned for all others. ac += upper_to_lower_case_table[ac]; bc += upper_to_lower_case_table[bc]; //Return if a & b are different. if (ac != bc) { return(mDNSfalse); } len_count -= 1; } return(mDNStrue); } // Use vectorized implementation if it is supported on this platform. mDNSlocal void setSameDomainLabelPointer(void) { if(_cpu_capabilities & kHasSSE4_1) { // Use SSE Code SameDomainLabelPointer = vectorSameDomainLabel; LogMsg("setSameDomainLabelPointer: using vector code"); } else LogMsg("setSameDomainLabelPointer: using scalar code"); } #endif // TARGET_OS_EMBEDDED // Original SameDomainLabel() implementation. mDNSlocal mDNSBool scalarSameDomainLabel(const mDNSu8 *a, const mDNSu8 *b) { int i; const int len = *a++; if (len > MAX_DOMAIN_LABEL) { debugf("Malformed label (too long)"); return(mDNSfalse); } if (len != *b++) return(mDNSfalse); for (i=0; i