/* * Copyright (c) 2017 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. */ #include // DNS64 code is only for iOS, which is currently the only Apple OS that supports DNS proxy network extensions. #if TARGET_OS_IOS #include "DNS64.h" #include #if __has_include() #include #else #include #endif #include #include #include "dns_sd.h" #include "dns_sd_internal.h" #include "uDNS.h" //=========================================================================================================================== // Constants //=========================================================================================================================== #define kDNS64IPv4OnlyFQDNString "\x8" "ipv4only" "\x4" "arpa" #define kDNS64IPv4OnlyFQDN ((const domainname *) kDNS64IPv4OnlyFQDNString) #define kDNS64IPv4OnlyFQDNLength 15 // 9 bytes for first label, 5 bytes for second label, and 1 byte for the root label. #define sizeof_field(TYPE, FIELD) sizeof(((TYPE *)0)->FIELD) // From CoreUtils.h check_compile_time(sizeof(kDNS64IPv4OnlyFQDNString) == kDNS64IPv4OnlyFQDNLength); check_compile_time(sizeof_field(DNSQuestion, qname) >= kDNS64IPv4OnlyFQDNLength); check_compile_time(sizeof_field(DNS64, qnameStash) == kDNS64IPv4OnlyFQDNLength); //=========================================================================================================================== // Local Prototypes //=========================================================================================================================== mDNSlocal mStatus DNS64GetIPv6Addrs(mDNS *m, mDNSu16 inResGroupID, struct in6_addr **outAddrs, uint32_t *outAddrCount); mDNSlocal mStatus DNS64GetPrefixes(mDNS *m, mDNSu16 inResGroupID, nw_nat64_prefix_t **outPrefixes, uint32_t *outPrefixCount); mDNSlocal mDNSBool DNS64GetReverseIPv6Addr(const domainname *inQName, struct in6_addr *outAddr); mDNSlocal mDNSu32 DNS64IPv4OnlyFQDNHash(void); mDNSlocal void DNS64RestartQuestion(mDNS *m, DNSQuestion *q, DNS64State newState); mDNSlocal mDNSBool DNS64TestIPv6Synthesis(mDNS *m, mDNSu16 inResGroupID, const mDNSv4Addr *inV4Addr); //=========================================================================================================================== // DNS64StateMachine //=========================================================================================================================== mDNSexport mDNSBool DNS64StateMachine(mDNS *m, DNSQuestion *inQ, const ResourceRecord *inRR, QC_result inResult) { // If this is an mDNS question, then exit early. DNS64 is only for unicast DNS questions. if (mDNSOpaque16IsZero(inQ->TargetQID)) return (mDNSfalse); switch (inQ->dns64.state) { // If this question is going to be answered with a negative AAAA record and the question is not for "ipv4only.arpa." and // the question's DNS server's interface supports NAT64, then restart the question as an "ipv4only.arpa." AAAA question. // Otherwise, do nothing. case kDNS64State_Initial: if ((inRR->RecordType == kDNSRecordTypePacketNegative) && (inResult == QC_add)) { if ((inQ->qtype == kDNSType_AAAA) && (inRR->rrtype == kDNSType_AAAA) && (inRR->rrclass == kDNSClass_IN) && ((inQ->qnamehash != DNS64IPv4OnlyFQDNHash()) || !SameDomainName(&inQ->qname, kDNS64IPv4OnlyFQDN)) && inQ->qDNSServer && nw_nat64_does_interface_index_support_nat64((uint32_t)(uintptr_t)inQ->qDNSServer->interface)) { DNS64RestartQuestion(m, inQ, kDNS64State_PrefixDiscovery); return (mDNStrue); } else if ((inQ->qtype == kDNSType_PTR) && (inRR->rrtype == kDNSType_PTR) && (inRR->rrclass == kDNSClass_IN) && inQ->qDNSServer && nw_nat64_does_interface_index_support_nat64((uint32_t)(uintptr_t)inQ->qDNSServer->interface) && DNS64GetReverseIPv6Addr(&inQ->qname, NULL)) { DNS64RestartQuestion(m, inQ, kDNS64State_PrefixDiscoveryPTR); return (mDNStrue); } } break; // If the "ipv4only.arpa." question is going to be answered with a positive AAAA record, then restart it as a question // for an A record with the original AAAA qname. // Otherwise, restart the question for the original AAAA record. case kDNS64State_PrefixDiscovery: if ((inRR->RecordType != kDNSRecordTypePacketNegative) && (inResult == QC_add) && (inRR->rrtype == kDNSType_AAAA) && (inRR->rrclass == kDNSClass_IN)) { DNS64RestartQuestion(m, inQ, kDNS64State_QueryA); return (mDNStrue); } else { DNS64RestartQuestion(m, inQ, kDNS64State_QueryAAAA); return (mDNStrue); } break; // The "ipv4only.arpa." question is going to be answered. Restart the question now. DNS64HandleNewQuestion() will decide // whether or not to change it to a reverse IPv4 question. case kDNS64State_PrefixDiscoveryPTR: DNS64RestartQuestion(m, inQ, kDNS64State_QueryPTR); return (mDNStrue); break; // If this question is going to be answered with a CNAME, then do nothing. // If this question is going to be answered with a positive A record that's synthesizable, then set the state to // QueryARecord2. // Otherwise, restart the question for the original AAAA record. case kDNS64State_QueryA: if (inRR->rrtype != kDNSType_CNAME) { if ((inRR->RecordType != kDNSRecordTypePacketNegative) && (inResult == QC_add) && (inRR->rrtype == kDNSType_A) && (inRR->rrclass == kDNSClass_IN) && inQ->qDNSServer && DNS64TestIPv6Synthesis(m, inQ->qDNSServer->resGroupID, &inRR->rdata->u.ipv4)) { inQ->dns64.state = kDNS64State_QueryA2; } else { DNS64RestartQuestion(m, inQ, kDNS64State_QueryAAAA); return (mDNStrue); } } break; // For all other states, do nothing. case kDNS64State_QueryA2: case kDNS64State_QueryAAAA: case kDNS64State_QueryPTR: case kDNS64State_ReverseIPv4: case kDNS64State_ReverseIPv6: break; default: LogMsg("DNS64StateMachine: unrecognized DNS64 state %d", inQ->dns64.state); break; } return (mDNSfalse); } //=========================================================================================================================== // DNS64AnswerQuestion //=========================================================================================================================== mDNSexport mStatus DNS64AnswerQuestion(mDNS *m, DNSQuestion *inQ, const ResourceRecord *inRR, QC_result inResult) { mStatus err; ResourceRecord newRR; RData rdata; nw_nat64_prefix_t * prefixes = NULL; uint32_t prefixCount; uint32_t i; struct in_addr v4Addr; struct in6_addr synthV6; require_action_quiet(inQ->qDNSServer, exit, err = mStatus_BadParamErr); err = DNS64GetPrefixes(m, inQ->qDNSServer->resGroupID, &prefixes, &prefixCount); require_noerr_quiet(err, exit); newRR = *inRR; newRR.rrtype = kDNSType_AAAA; newRR.rdlength = 16; rdata.MaxRDLength = newRR.rdlength; newRR.rdata = &rdata; memcpy(&v4Addr.s_addr, inRR->rdata->u.ipv4.b, 4); for (i = 0; i < prefixCount; i++) { if (nw_nat64_synthesize_v6(&prefixes[i], &v4Addr, &synthV6)) { memcpy(rdata.u.ipv6.b, synthV6.s6_addr, 16); inQ->QuestionCallback(m, inQ, &newRR, inResult); } } err = mStatus_NoError; exit: if (prefixes) free(prefixes); return (err); } //=========================================================================================================================== // DNS64HandleNewQuestion //=========================================================================================================================== mDNSexport void DNS64HandleNewQuestion(mDNS *m, DNSQuestion *inQ) { if (inQ->dns64.state == kDNS64State_QueryPTR) { struct in6_addr v6Addr; inQ->dns64.state = kDNS64State_ReverseIPv6; if (inQ->qDNSServer && DNS64GetReverseIPv6Addr(&inQ->qname, &v6Addr)) { mStatus err; nw_nat64_prefix_t * prefixes; uint32_t prefixCount; uint32_t i; struct in_addr v4Addr; char qnameStr[MAX_REVERSE_MAPPING_NAME_V4]; err = DNS64GetPrefixes(m, inQ->qDNSServer->resGroupID, &prefixes, &prefixCount); require_noerr_quiet(err, exit); for (i = 0; i < prefixCount; i++) { if (nw_nat64_extract_v4(&prefixes[i], &v6Addr, &v4Addr)) { const mDNSu8 * const a = (const mDNSu8 *)&v4Addr.s_addr; snprintf(qnameStr, sizeof(qnameStr), "%u.%u.%u.%u.in-addr.arpa.", a[3], a[2], a[1], a[0]); MakeDomainNameFromDNSNameString(&inQ->qname, qnameStr); inQ->qnamehash = DomainNameHashValue(&inQ->qname); inQ->dns64.state = kDNS64State_ReverseIPv4; break; } } free(prefixes); } } exit: return; } //=========================================================================================================================== // DNS64ResetState //=========================================================================================================================== // Called from mDNS_StopQuery_internal(). mDNSexport void DNS64ResetState(DNSQuestion *inQ) { switch (inQ->dns64.state) { case kDNS64State_PrefixDiscoveryPTR: inQ->qtype = kDNSType_PTR; // Restore qtype to PTR and fall through. case kDNS64State_PrefixDiscovery: memcpy(&inQ->qname, inQ->dns64.qnameStash, sizeof(inQ->dns64.qnameStash)); // Restore the previous qname. inQ->qnamehash = DomainNameHashValue(&inQ->qname); break; case kDNS64State_QueryA: case kDNS64State_QueryA2: inQ->qtype = kDNSType_AAAA; // Restore qtype to AAAA. break; // Do nothing for the other states. case kDNS64State_Initial: case kDNS64State_QueryAAAA: case kDNS64State_QueryPTR: case kDNS64State_ReverseIPv4: case kDNS64State_ReverseIPv6: break; default: LogMsg("DNS64ResetState: unrecognized DNS64 state %d", inQ->dns64.state); break; } inQ->dns64.state = kDNS64State_Initial; } //=========================================================================================================================== // DNS64RestartQuestions //=========================================================================================================================== mDNSexport void DNS64RestartQuestions(mDNS *m) { DNSQuestion * q; DNSQuestion * restartList = NULL; DNSServer * newServer; m->RestartQuestion = m->Questions; while (m->RestartQuestion) { q = m->RestartQuestion; m->RestartQuestion = q->next; if (q->dns64.state != kDNS64State_Initial) { SetValidDNSServers(m, q); q->triedAllServersOnce = 0; newServer = GetServerForQuestion(m, q); if (q->qDNSServer != newServer) { if (!CacheRecordRmvEventsForQuestion(m, q)) { LogInfo("DNS64RestartQuestions: Question deleted while delivering RMV events from cache"); } else { LogInfo("DNS64RestartQuestions: Stop question %p %##s (%s)", q, q->qname.c, DNSTypeName(q->qtype)); mDNS_StopQuery_internal(m, q); q->next = restartList; restartList = q; } } } } while ((q = restartList) != NULL) { restartList = restartList->next; q->next = NULL; LogInfo("DNS64RestartQuestions: Start question %p %##s (%s)", q, q->qname.c, DNSTypeName(q->qtype)); mDNS_StartQuery_internal(m, q); } } //=========================================================================================================================== // DNS64GetIPv6Addrs //=========================================================================================================================== #define IsPositiveAAAAFromResGroup(RR, RES_GROUP_ID) \ ((RR)->rDNSServer && \ ((RR)->rDNSServer->resGroupID == RES_GROUP_ID) && \ ((RR)->rrtype == kDNSType_AAAA) && \ ((RR)->RecordType != kDNSRecordTypePacketNegative) && \ !(RR)->InterfaceID) mDNSlocal mStatus DNS64GetIPv6Addrs(mDNS *m, const mDNSu16 inResGroupID, struct in6_addr **outAddrs, uint32_t *outAddrCount) { mStatus err; const CacheGroup * cg; const CacheRecord * cr; struct in6_addr * addrs = NULL; uint32_t addrCount; uint32_t recordCount; cg = CacheGroupForName(m, DNS64IPv4OnlyFQDNHash(), kDNS64IPv4OnlyFQDN); require_action_quiet(cg, exit, err = mStatus_NoSuchRecord); recordCount = 0; for (cr = cg->members; cr; cr = cr->next) { if (IsPositiveAAAAFromResGroup(&cr->resrec, inResGroupID)) { recordCount++; } } require_action_quiet(recordCount > 0, exit, err = mStatus_NoSuchRecord); addrs = (struct in6_addr *)calloc(recordCount, sizeof(*addrs)); require_action_quiet(addrs, exit, err = mStatus_NoMemoryErr); addrCount = 0; for (cr = cg->members; cr && (addrCount < recordCount); cr = cr->next) { if (IsPositiveAAAAFromResGroup(&cr->resrec, inResGroupID)) { memcpy(addrs[addrCount].s6_addr, cr->resrec.rdata->u.ipv6.b, 16); addrCount++; } } *outAddrs = addrs; addrs = NULL; *outAddrCount = addrCount; err = mStatus_NoError; exit: if (addrs) free(addrs); return (err); } //=========================================================================================================================== // DNS64GetPrefixes //=========================================================================================================================== mDNSlocal mStatus DNS64GetPrefixes(mDNS *m, mDNSu16 inResGroupID, nw_nat64_prefix_t **outPrefixes, uint32_t *outPrefixCount) { mStatus err; struct in6_addr * v6Addrs; uint32_t v6AddrCount; nw_nat64_prefix_t * prefixes; int32_t prefixCount; err = DNS64GetIPv6Addrs(m, inResGroupID, &v6Addrs, &v6AddrCount); require_noerr_quiet(err, exit); prefixCount = nw_nat64_copy_prefixes_from_ipv4only_records(v6Addrs, v6AddrCount, &prefixes); free(v6Addrs); require_action_quiet(prefixCount > 0, exit, err = mStatus_UnknownErr); *outPrefixes = prefixes; *outPrefixCount = prefixCount; exit: return (err); } //=========================================================================================================================== // DNS64GetReverseIPv6Addr //=========================================================================================================================== #define kReverseIPv6Domain ((const domainname *) "\x3" "ip6" "\x4" "arpa") mDNSlocal mDNSBool DNS64GetReverseIPv6Addr(const domainname *inQName, struct in6_addr *outAddr) { const mDNSu8 * ptr; int i; unsigned int c; unsigned int nl; unsigned int nu; // If the name is of the form "x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa.", where each x // is a hex digit, then the sequence of 32 hex digit labels represents the nibbles of an IPv6 address in reverse order. // See . ptr = (const mDNSu8 *)inQName; for (i = 0; i < 16; i++) { if (*ptr++ != 1) return (mDNSfalse); // If this label's length is not 1, then fail. c = *ptr++; // Get label byte. if ( (c >= '0') && (c <= '9')) nl = c - '0'; // If it's a hex digit, get its numeric value. else if ((c >= 'a') && (c <= 'f')) nl = (c - 'a') + 10; else if ((c >= 'A') && (c <= 'F')) nl = (c - 'A') + 10; else return (mDNSfalse); // Otherwise, fail. if (*ptr++ != 1) return (mDNSfalse); // If this label's length is not 1, then fail. c = *ptr++; // Get label byte. if ( (c >= '0') && (c <= '9')) nu = c - '0'; // If it's a hex digit, get its numeric value. else if ((c >= 'a') && (c <= 'f')) nu = (c - 'a') + 10; else if ((c >= 'A') && (c <= 'F')) nu = (c - 'A') + 10; else return (mDNSfalse); // Otherwise, fail. if (outAddr) outAddr->s6_addr[15 - i] = (mDNSu8)((nu << 4) | nl); } // The rest of the name needs to be "ip6.arpa.". If it isn't, fail. if (!SameDomainName((const domainname *)ptr, kReverseIPv6Domain)) return (mDNSfalse); return (mDNStrue); } //=========================================================================================================================== // DNS64IPv4OnlyFQDNHash //=========================================================================================================================== mDNSlocal mDNSu32 DNS64IPv4OnlyFQDNHash(void) { static dispatch_once_t sHashOnce; static mDNSu32 sHash; dispatch_once(&sHashOnce, ^{ sHash = DomainNameHashValue(kDNS64IPv4OnlyFQDN); }); return (sHash); } //=========================================================================================================================== // DNS64RestartQuestion //=========================================================================================================================== mDNSlocal void DNS64RestartQuestion(mDNS *const m, DNSQuestion *inQ, DNS64State inNewState) { mDNS_StopQuery_internal(m, inQ); inQ->dns64.state = inNewState; switch (inQ->dns64.state) { case kDNS64State_Initial: break; case kDNS64State_PrefixDiscovery: case kDNS64State_PrefixDiscoveryPTR: // Save the first 15 bytes from the original qname that are displaced by setting qname to "ipv4only.arpa.". memcpy(inQ->dns64.qnameStash, &inQ->qname, sizeof(inQ->dns64.qnameStash)); AssignDomainName(&inQ->qname, kDNS64IPv4OnlyFQDN); inQ->qnamehash = DNS64IPv4OnlyFQDNHash(); inQ->qtype = kDNSType_AAAA; break; case kDNS64State_QueryA: case kDNS64State_QueryA2: inQ->qtype = kDNSType_A; break; case kDNS64State_QueryPTR: case kDNS64State_ReverseIPv4: case kDNS64State_ReverseIPv6: inQ->qtype = kDNSType_PTR; break; case kDNS64State_QueryAAAA: inQ->qtype = kDNSType_AAAA; break; default: LogMsg("DNS64RestartQuestion: unrecognized DNS64 state %d", inQ->dns64.state); break; } mDNS_StartQuery_internal(m, inQ); } //=========================================================================================================================== // DNS64TestIPv6Synthesis //=========================================================================================================================== mDNSlocal mDNSBool DNS64TestIPv6Synthesis(mDNS *m, mDNSu16 inResGroupID, const mDNSv4Addr *inV4Addr) { mStatus err; nw_nat64_prefix_t * prefixes = NULL; uint32_t prefixCount; uint32_t i; struct in_addr v4Addr; struct in6_addr synthV6; mDNSBool result = mDNSfalse; err = DNS64GetPrefixes(m, inResGroupID, &prefixes, &prefixCount); require_noerr_quiet(err, exit); memcpy(&v4Addr.s_addr, inV4Addr->b, 4); for (i = 0; i < prefixCount; i++) { if (nw_nat64_synthesize_v6(&prefixes[i], &v4Addr, &synthV6)) { result = mDNStrue; break; } } exit: if (prefixes) free(prefixes); return (result); } #endif // TARGET_OS_IOS