+ const mDNSAddr *srcaddr, const mDNSIPPort srcport, const mDNSAddr *dstaddr, mDNSIPPort dstport,
+ const mDNSInterfaceID InterfaceID)
+{
+ mDNSu8 *responseend = mDNSNULL;
+ mDNSBool QueryWasLocalUnicast = srcaddr && dstaddr &&
+ !mDNSAddrIsDNSMulticast(dstaddr) && mDNS_AddressIsLocalSubnet(m, InterfaceID, srcaddr, mDNSNULL);
+
+ if (!InterfaceID && dstaddr && mDNSAddrIsDNSMulticast(dstaddr))
+ {
+ LogMsg("Ignoring Query from %#-15a:%-5d to %#-15a:%-5d on 0x%p with "
+ "%2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s %d bytes (Multicast, but no InterfaceID)",
+ srcaddr, mDNSVal16(srcport), dstaddr, mDNSVal16(dstport), InterfaceID,
+ msg->h.numQuestions, msg->h.numQuestions == 1 ? ", " : "s,",
+ msg->h.numAnswers, msg->h.numAnswers == 1 ? ", " : "s,",
+ msg->h.numAuthorities, msg->h.numAuthorities == 1 ? "y, " : "ies,",
+ msg->h.numAdditionals, msg->h.numAdditionals == 1 ? " " : "s", end - msg->data);
+ return;
+ }
+
+ verbosedebugf("Received Query from %#-15a:%-5d to %#-15a:%-5d on 0x%p with "
+ "%2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s %d bytes",
+ srcaddr, mDNSVal16(srcport), dstaddr, mDNSVal16(dstport), InterfaceID,
+ msg->h.numQuestions, msg->h.numQuestions == 1 ? ", " : "s,",
+ msg->h.numAnswers, msg->h.numAnswers == 1 ? ", " : "s,",
+ msg->h.numAuthorities, msg->h.numAuthorities == 1 ? "y, " : "ies,",
+ msg->h.numAdditionals, msg->h.numAdditionals == 1 ? " " : "s", end - msg->data);
+
+ responseend = ProcessQuery(m, msg, end, srcaddr, InterfaceID,
+ !mDNSSameIPPort(srcport, MulticastDNSPort), mDNSAddrIsDNSMulticast(dstaddr), QueryWasLocalUnicast, &m->omsg);
+
+ if (responseend) // If responseend is non-null, that means we built a unicast response packet
+ {
+ debugf("Unicast Response: %d Question%s, %d Answer%s, %d Additional%s to %#-15a:%d on %p/%ld",
+ m->omsg.h.numQuestions, m->omsg.h.numQuestions == 1 ? "" : "s",
+ m->omsg.h.numAnswers, m->omsg.h.numAnswers == 1 ? "" : "s",
+ m->omsg.h.numAdditionals, m->omsg.h.numAdditionals == 1 ? "" : "s",
+ srcaddr, mDNSVal16(srcport), InterfaceID, srcaddr->type);
+ mDNSSendDNSMessage(m, &m->omsg, responseend, InterfaceID, mDNSNULL, srcaddr, srcport, mDNSNULL, mDNSNULL, mDNSfalse);
+ }
+}
+
+#if 0
+mDNSlocal mDNSBool TrustedSource(const mDNS *const m, const mDNSAddr *const srcaddr)
+{
+ DNSServer *s;
+ (void)m; // Unused
+ (void)srcaddr; // Unused
+ for (s = m->DNSServers; s; s = s->next)
+ if (mDNSSameAddress(srcaddr, &s->addr)) return(mDNStrue);
+ return(mDNSfalse);
+}
+#endif
+
+struct UDPSocket_struct
+{
+ mDNSIPPort port; // MUST BE FIRST FIELD -- mDNSCoreReceive expects every UDPSocket_struct to begin with mDNSIPPort port
+};
+
+mDNSlocal DNSQuestion *ExpectingUnicastResponseForQuestion(const mDNS *const m, const mDNSIPPort port, const mDNSOpaque16 id, const DNSQuestion *const question, mDNSBool tcp)
+{
+ DNSQuestion *q;
+ for (q = m->Questions; q; q=q->next)
+ {
+ if (!tcp && !q->LocalSocket) continue;
+ if (mDNSSameIPPort(tcp ? q->tcpSrcPort : q->LocalSocket->port, port) &&
+ mDNSSameOpaque16(q->TargetQID, id) &&
+ q->qtype == question->qtype &&
+ q->qclass == question->qclass &&
+ q->qnamehash == question->qnamehash &&
+ SameDomainName(&q->qname, &question->qname))
+ return(q);
+ }
+ return(mDNSNULL);
+}
+
+// This function is called when we receive a unicast response. This could be the case of a unicast response from the
+// DNS server or a response to the QU query. Hence, the cache record's InterfaceId can be both NULL or non-NULL (QU case)
+mDNSlocal DNSQuestion *ExpectingUnicastResponseForRecord(mDNS *const m,
+ const mDNSAddr *const srcaddr, const mDNSBool SrcLocal, const mDNSIPPort port, const mDNSOpaque16 id, const CacheRecord *const rr, mDNSBool tcp)
+{
+ DNSQuestion *q;
+ (void)id;
+ (void)srcaddr;
+
+ for (q = m->Questions; q; q=q->next)
+ {
+ if (!q->DuplicateOf && ResourceRecordAnswersUnicastResponse(&rr->resrec, q))
+ {
+ if (!mDNSOpaque16IsZero(q->TargetQID))
+ {
+ debugf("ExpectingUnicastResponseForRecord msg->h.id %d q->TargetQID %d for %s", mDNSVal16(id), mDNSVal16(q->TargetQID), CRDisplayString(m, rr));
+
+ if (mDNSSameOpaque16(q->TargetQID, id))
+ {
+ mDNSIPPort srcp;
+ if (!tcp)
+ {
+ srcp = q->LocalSocket ? q->LocalSocket->port : zeroIPPort;
+ }
+ else
+ {
+ srcp = q->tcpSrcPort;
+ }
+ if (mDNSSameIPPort(srcp, port)) return(q);
+
+ // if (mDNSSameAddress(srcaddr, &q->Target)) return(mDNStrue);
+ // if (q->LongLived && mDNSSameAddress(srcaddr, &q->servAddr)) return(mDNStrue); Shouldn't need this now that we have LLQType checking
+ // if (TrustedSource(m, srcaddr)) return(mDNStrue);
+ LogInfo("WARNING: Ignoring suspect uDNS response for %##s (%s) [q->Target %#a:%d] from %#a:%d %s",
+ q->qname.c, DNSTypeName(q->qtype), &q->Target, mDNSVal16(srcp), srcaddr, mDNSVal16(port), CRDisplayString(m, rr));
+ return(mDNSNULL);
+ }
+ }
+ else
+ {
+ if (SrcLocal && q->ExpectUnicastResp && (mDNSu32)(m->timenow - q->ExpectUnicastResp) < (mDNSu32)(mDNSPlatformOneSecond*2))
+ return(q);
+ }
+ }
+ }
+ return(mDNSNULL);
+}
+
+// Return a pointer to the primary service name, skipping subtype name if present.
+mDNSlocal const domainname *getPrimaryServiceName(const domainname *domainName)
+{
+ const domainname *primaryName = domainName;
+ const domainname *subName = SkipLeadingLabels(domainName, 1);
+
+ if (SameDomainLabel(subName->c, (const mDNSu8 *)mDNSSubTypeLabel))
+ {
+ // skip "<sub type name>._sub" portion of name
+ primaryName = SkipLeadingLabels(domainName, 2);
+ debugf("getPrimaryServiceName: returning %##s for _sub type", primaryName);
+ }
+
+ return primaryName;
+}
+
+// This function is not called if the packet is from us, which implies that we accept all multicast packets coming from us.
+mDNSlocal mDNSBool ExpectingMulticastResponseForRecord(mDNS *const m, CacheRecord *rr, const mDNSAddr *srcaddr, mDNSBool recordAccepted,
+ CacheRecord **McastNSEC3Records)
+{
+ DNSQuestion *q;
+
+ // Accept A and AAAA if we accepted something before in the same packet as most likely related to the
+ // service records that we may have accepted.
+ if (recordAccepted && (rr->resrec.rrtype == kDNSType_A || rr->resrec.rrtype == kDNSType_AAAA))
+ {
+ LogInfo("ExpectingMulticastResponseForRecord:A:AAAA: accepting %s, from %#a due to same packet %d", CRDisplayString(m, rr), srcaddr, m->PktNum);
+ return mDNStrue;
+ }
+ for (q = m->Questions; q; q=q->next)
+ {
+ if (!q->DuplicateOf && mDNSOpaque16IsZero(q->TargetQID))
+ {
+ mDNSBool ret;
+ // 1. If a resource record answers question, cache it. This also will cache NSECs if it asserts
+ // non-existence of q->qtype. If we have any matching NSEC3 Records for the question, send
+ // it along with the resource record. Do it only for questions that are expecting to
+ // discover only its peers (q->AnonInfo not NULL)
+ if (q->AnonInfo && McastNSEC3Records && !rr->resrec.AnonInfo)
+ {
+ InitializeAnonInfoForCR(m, McastNSEC3Records, rr);
+ }
+ ret = ResourceRecordAnswersQuestion(&rr->resrec, q);
+ if (ret)
+ {
+ // The record and the question belong to the same subset. Set the
+ // anonymous data in the cache record.
+ if (q->AnonInfo && rr->resrec.AnonInfo)
+ {
+ SetAnonData(q, &rr->resrec, mDNSfalse);
+ }
+ LogInfo("ExpectingMulticastResponseForRecord: Name and Type match, accepting %s, from %#a", CRDisplayString(m, rr), srcaddr);
+ if (rr->resrec.rrtype == kDNSType_NSEC)
+ LogInfo("ExpectingMulticastResponseForRecord: record %s, question %##s (%s)", CRDisplayString(m, rr), q->qname.c, DNSTypeName(q->qtype));
+ return mDNStrue;
+ }
+ if (rr->resrec.rrtype == kDNSType_SRV || rr->resrec.rrtype == kDNSType_TXT)
+ {
+ // Point to the service type in the record name
+ const domainname *name = SkipLeadingLabels(rr->resrec.name, 1);
+
+ // If question is for a sub type, just compare against the primary service type
+ const domainname *primaryName = getPrimaryServiceName(&q->qname);
+
+ // 2. If the SRV or TXT record matches the service name, then cache it. If the TXT or SRV record is
+ // before the PTR record in the packet, PTR record may not be in the cache yet and hence the logic
+ // in (3) below will fail to cache it.
+ if (q->qtype == kDNSType_PTR && name && SameDomainName(primaryName, name))
+ {
+ LogInfo("ExpectingMulticastResponseForRecord: Accepting %s due to PTR match, question %##s from %#a, pktnum %d",
+ CRDisplayString(m, rr), q->qname.c, srcaddr, m->PktNum);
+ return mDNStrue;
+ }
+
+ if (name)
+ {
+ const mDNSu32 slot = HashSlot(name);
+ const mDNSu32 namehash = DomainNameHashValue(name);
+ CacheGroup *cg = CacheGroupForName(m, slot, namehash, name);
+ CacheRecord *cr;
+
+ // 3. Same as in (2), but look in the cache in case we don't have the PTR question.
+
+ for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
+ {
+ if (cr->resrec.rrtype == kDNSType_PTR)
+ {
+ primaryName = getPrimaryServiceName(cr->resrec.name);
+
+ if (SameDomainName(primaryName, name))
+ {
+ LogInfo("ExpectingMulticastResponseForRecord: accepting %s, from %#a, pktnum %d",
+ CRDisplayString(m, rr), srcaddr, m->PktNum);
+ return mDNStrue;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ debugf("ExpectingMulticastResponseForRecord: discarding %s, from %#a, pktnum %d", CRDisplayString(m, rr), srcaddr, m->PktNum);
+ return(mDNSfalse);
+}
+
+// Certain data types need more space for in-memory storage than their in-packet rdlength would imply
+// Currently this applies only to rdata types containing more than one domainname,
+// or types where the domainname is not the last item in the structure.
+mDNSlocal mDNSu16 GetRDLengthMem(const ResourceRecord *const rr)
+{
+ switch (rr->rrtype)
+ {
+ case kDNSType_SOA: return sizeof(rdataSOA);
+ case kDNSType_RP: return sizeof(rdataRP);
+ case kDNSType_PX: return sizeof(rdataPX);
+ default: return rr->rdlength;
+ }
+}
+
+mDNSexport CacheRecord *CreateNewCacheEntry(mDNS *const m, const mDNSu32 slot, CacheGroup *cg, mDNSs32 delay, mDNSBool Add, const mDNSAddr *sourceAddress)
+{
+ CacheRecord *rr = mDNSNULL;
+ mDNSu16 RDLength = GetRDLengthMem(&m->rec.r.resrec);
+
+ if (!m->rec.r.resrec.InterfaceID) debugf("CreateNewCacheEntry %s", CRDisplayString(m, &m->rec.r));
+
+ //if (RDLength > InlineCacheRDSize)
+ // LogInfo("Rdata len %4d > InlineCacheRDSize %d %s", RDLength, InlineCacheRDSize, CRDisplayString(m, &m->rec.r));
+
+ if (!cg) cg = GetCacheGroup(m, slot, &m->rec.r.resrec); // If we don't have a CacheGroup for this name, make one now
+ if (cg) rr = GetCacheRecord(m, cg, RDLength); // Make a cache record, being careful not to recycle cg
+ if (!rr) NoCacheAnswer(m, &m->rec.r);
+ else
+ {
+ RData *saveptr = rr->resrec.rdata; // Save the rr->resrec.rdata pointer
+ *rr = m->rec.r; // Block copy the CacheRecord object
+ rr->resrec.rdata = saveptr; // Restore rr->resrec.rdata after the structure assignment
+ rr->resrec.name = cg->name; // And set rr->resrec.name to point into our CacheGroup header
+
+ // We need to add the anonymous info before we call CacheRecordAdd so that
+ // if it finds a matching question with this record, it bumps up the counters like
+ // CurrentAnswers etc. Otherwise, when a cache entry gets removed, CacheRecordRmv
+ // will complain.
+ if (m->rec.r.resrec.AnonInfo)
+ {
+ rr->resrec.AnonInfo = m->rec.r.resrec.AnonInfo;
+ m->rec.r.resrec.AnonInfo = mDNSNULL;
+ }
+ rr->DelayDelivery = delay;
+
+ // If this is an oversized record with external storage allocated, copy rdata to external storage
+ if (rr->resrec.rdata == (RData*)&rr->smallrdatastorage && RDLength > InlineCacheRDSize)
+ LogMsg("rr->resrec.rdata == &rr->rdatastorage but length > InlineCacheRDSize %##s", m->rec.r.resrec.name->c);
+ else if (rr->resrec.rdata != (RData*)&rr->smallrdatastorage && RDLength <= InlineCacheRDSize)
+ LogMsg("rr->resrec.rdata != &rr->rdatastorage but length <= InlineCacheRDSize %##s", m->rec.r.resrec.name->c);
+ if (RDLength > InlineCacheRDSize)
+ mDNSPlatformMemCopy(rr->resrec.rdata, m->rec.r.resrec.rdata, sizeofRDataHeader + RDLength);
+
+ rr->next = mDNSNULL; // Clear 'next' pointer
+ rr->nsec = mDNSNULL;
+ rr->soa = mDNSNULL;
+
+ if (sourceAddress)
+ rr->sourceAddress = *sourceAddress;
+
+ if (!rr->resrec.InterfaceID)
+ {
+ m->rrcache_totalused_unicast += rr->resrec.rdlength;
+ if (DNSSECRecordType(rr->resrec.rrtype))
+ BumpDNSSECStats(m, kStatsActionIncrement, kStatsTypeMemoryUsage, rr->resrec.rdlength);
+ }
+
+ if (Add)
+ {
+ *(cg->rrcache_tail) = rr; // Append this record to tail of cache slot list
+ cg->rrcache_tail = &(rr->next); // Advance tail pointer
+ CacheRecordAdd(m, rr); // CacheRecordAdd calls SetNextCacheCheckTimeForRecord(m, rr); for us
+ }
+ else
+ {
+ // Can't use the "cg->name" if we are not adding to the cache as the
+ // CacheGroup may be released anytime if it is empty
+ domainname *name = mDNSPlatformMemAllocate(DomainNameLength(cg->name));
+ if (name)
+ {
+ AssignDomainName(name, cg->name);
+ rr->resrec.name = name;
+ }
+ else
+ {
+ ReleaseCacheRecord(m, rr);
+ NoCacheAnswer(m, &m->rec.r);
+ rr = mDNSNULL;
+ }
+ }
+ }
+ return(rr);
+}
+
+mDNSlocal void RefreshCacheRecord(mDNS *const m, CacheRecord *rr, mDNSu32 ttl)
+{
+ rr->TimeRcvd = m->timenow;
+ rr->resrec.rroriginalttl = ttl;
+ rr->UnansweredQueries = 0;
+#if ENABLE_MULTI_PACKET_QUERY_SNOOPING
+ rr->MPUnansweredQ = 0;
+ rr->MPUnansweredKA = 0;
+ rr->MPExpectingKA = mDNSfalse;
+#endif
+ SetNextCacheCheckTimeForRecord(m, rr);
+}
+
+mDNSexport void GrantCacheExtensions(mDNS *const m, DNSQuestion *q, mDNSu32 lease)
+{
+ CacheRecord *rr;
+ const mDNSu32 slot = HashSlot(&q->qname);
+ CacheGroup *cg = CacheGroupForName(m, slot, q->qnamehash, &q->qname);
+ for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next)
+ if (rr->CRActiveQuestion == q)
+ {
+ //LogInfo("GrantCacheExtensions: new lease %d / %s", lease, CRDisplayString(m, rr));
+ RefreshCacheRecord(m, rr, lease);
+ }
+}
+
+mDNSlocal mDNSu32 GetEffectiveTTL(const uDNS_LLQType LLQType, mDNSu32 ttl) // TTL in seconds
+{
+ if (LLQType == uDNS_LLQ_Entire) ttl = kLLQ_DefLease;
+ else if (LLQType == uDNS_LLQ_Events)
+ {
+ // If the TTL is -1 for uDNS LLQ event packet, that means "remove"
+ if (ttl == 0xFFFFFFFF) ttl = 0;
+ else ttl = kLLQ_DefLease;
+ }
+ else // else not LLQ (standard uDNS response)
+ {
+ // The TTL is already capped to a maximum value in GetLargeResourceRecord, but just to be extra safe we
+ // also do this check here to make sure we can't get overflow below when we add a quarter to the TTL
+ if (ttl > 0x60000000UL / mDNSPlatformOneSecond) ttl = 0x60000000UL / mDNSPlatformOneSecond;
+
+ ttl = RRAdjustTTL(ttl);
+
+ // For mDNS, TTL zero means "delete this record"
+ // For uDNS, TTL zero means: this data is true at this moment, but don't cache it.
+ // For the sake of network efficiency, we impose a minimum effective TTL of 15 seconds.
+ // This means that we'll do our 80, 85, 90, 95% queries at 12.00, 12.75, 13.50, 14.25 seconds
+ // respectively, and then if we get no response, delete the record from the cache at 15 seconds.
+ // This gives the server up to three seconds to respond between when we send our 80% query at 12 seconds
+ // and when we delete the record at 15 seconds. Allowing cache lifetimes less than 15 seconds would
+ // (with the current code) result in the server having even less than three seconds to respond
+ // before we deleted the record and reported a "remove" event to any active questions.
+ // Furthermore, with the current code, if we were to allow a TTL of less than 2 seconds
+ // then things really break (e.g. we end up making a negative cache entry).
+ // In the future we may want to revisit this and consider properly supporting non-cached (TTL=0) uDNS answers.
+ if (ttl < 15) ttl = 15;
+ }
+
+ return ttl;
+}
+
+// When the response does not match the question directly, we still want to cache them sometimes. The current response is
+// in m->rec.
+mDNSlocal mDNSBool IsResponseAcceptable(mDNS *const m, const CacheRecord *crlist, DNSQuestion *q, mDNSBool *nseclist)
+{
+ CacheRecord *const newcr = &m->rec.r;
+ ResourceRecord *rr = &newcr->resrec;
+ const CacheRecord *cr;
+
+ *nseclist = mDNSfalse;
+ for (cr = crlist; cr != (CacheRecord*)1; cr = cr->NextInCFList)
+ {
+ domainname *target = GetRRDomainNameTarget(&cr->resrec);
+ // When we issue a query for A record, the response might contain both a CNAME and A records. Only the CNAME would
+ // match the question and we already created a cache entry in the previous pass of this loop. Now when we process
+ // the A record, it does not match the question because the record name here is the CNAME. Hence we try to
+ // match with the previous records to make it an AcceptableResponse. We have to be careful about setting the
+ // DNSServer value that we got in the previous pass. This can happen for other record types like SRV also.
+
+ if (target && cr->resrec.rdatahash == rr->namehash && SameDomainName(target, rr->name))
+ {
+ LogInfo("IsResponseAcceptable: Found a matching entry for %##s in the CacheFlushRecords %s", rr->name->c, CRDisplayString(m, cr));
+ return (mDNStrue);
+ }
+ }
+
+ // Either the question requires validation or we are validating a response with DNSSEC in which case
+ // we need to accept the RRSIGs also so that we can validate the response. It is also possible that
+ // we receive NSECs for our query which does not match the qname and we need to cache in that case
+ // too. nseclist is set if they have to be cached as part of the negative cache record.
+ if (q && DNSSECQuestion(q))
+ {
+ mDNSBool same = SameDomainName(&q->qname, rr->name);
+ if (same && (q->qtype == rr->rrtype || rr->rrtype == kDNSType_CNAME))
+ {
+ LogInfo("IsResponseAcceptable: Accepting, same name and qtype %s, CR %s", DNSTypeName(q->qtype),
+ CRDisplayString(m, newcr));
+ return mDNStrue;
+ }
+ // We cache RRSIGS if it covers the question type or NSEC. If it covers a NSEC,
+ // "nseclist" is set
+ if (rr->rrtype == kDNSType_RRSIG)
+ {
+ RDataBody2 *const rdb = (RDataBody2 *)newcr->smallrdatastorage.data;
+ rdataRRSig *rrsig = &rdb->rrsig;
+ mDNSu16 typeCovered = swap16(rrsig->typeCovered);
+
+ // Note the ordering. If we are looking up the NSEC record, then the RRSIG's typeCovered
+ // would match the qtype and they are cached normally as they are not used to prove the
+ // non-existence of any name. In that case, it is like any other normal dnssec validation
+ // and hence nseclist should not be set.
+
+ if (same && ((typeCovered == q->qtype) || (typeCovered == kDNSType_CNAME)))
+ {
+ LogInfo("IsResponseAcceptable: Accepting RRSIG %s matches question type %s", CRDisplayString(m, newcr),
+ DNSTypeName(q->qtype));
+ return mDNStrue;
+ }
+ else if (typeCovered == kDNSType_NSEC || typeCovered == kDNSType_NSEC3)
+ {
+ LogInfo("IsResponseAcceptable: Accepting RRSIG %s matches %s type (nseclist = 1)", CRDisplayString(m, newcr), DNSTypeName(typeCovered));
+ *nseclist = mDNStrue;
+ return mDNStrue;
+ }
+ else if (typeCovered == kDNSType_SOA)
+ {
+ LogInfo("IsResponseAcceptable: Accepting RRSIG %s matches SOA type (nseclist = 1)", CRDisplayString(m, newcr));
+ *nseclist = mDNStrue;
+ return mDNStrue;
+ }
+ else return mDNSfalse;
+ }
+ if (rr->rrtype == kDNSType_NSEC)
+ {
+ if (!UNICAST_NSEC(rr))
+ {
+ LogMsg("IsResponseAcceptable: ERROR!! Not a unicast NSEC %s", CRDisplayString(m, newcr));
+ return mDNSfalse;
+ }
+ LogInfo("IsResponseAcceptable: Accepting NSEC %s (nseclist = 1)", CRDisplayString(m, newcr));
+ *nseclist = mDNStrue;
+ return mDNStrue;
+ }
+ if (rr->rrtype == kDNSType_SOA)
+ {
+ LogInfo("IsResponseAcceptable: Accepting SOA %s (nseclist = 1)", CRDisplayString(m, newcr));
+ *nseclist = mDNStrue;
+ return mDNStrue;
+ }
+ else if (rr->rrtype == kDNSType_NSEC3)
+ {
+ LogInfo("IsResponseAcceptable: Accepting NSEC3 %s (nseclist = 1)", CRDisplayString(m, newcr));
+ *nseclist = mDNStrue;
+ return mDNStrue;
+ }
+ }
+ return mDNSfalse;
+}
+
+mDNSlocal void FreeNSECRecords(mDNS *const m, CacheRecord *NSECRecords)
+{
+ CacheRecord *rp, *next;
+
+ for (rp = NSECRecords; rp; rp = next)
+ {
+ next = rp->next;
+ ReleaseCacheRecord(m, rp);
+ }
+}
+
+// If we received zero DNSSEC records even when the DO/EDNS0 bit was set, we need to provide this
+// information to ValidatingResponse question to indicate the DNSSEC status to the application
+mDNSlocal void mDNSCoreReceiveNoDNSSECAnswers(mDNS *const m, const DNSMessage *const response, const mDNSu8 *end, const mDNSAddr *dstaddr,
+ mDNSIPPort dstport, const mDNSInterfaceID InterfaceID)
+{
+ int i;
+ const mDNSu8 *ptr = response->data;
+
+ for (i = 0; i < response->h.numQuestions && ptr && ptr < end; i++)
+ {
+ DNSQuestion pktq;
+ DNSQuestion *qptr = mDNSNULL;
+ ptr = getQuestion(response, ptr, end, InterfaceID, &pktq);
+ if (ptr && (qptr = ExpectingUnicastResponseForQuestion(m, dstport, response->h.id, &pktq, !dstaddr)) &&
+ qptr->ValidatingResponse)
+ {
+ DNSQuestion *next, *q;
+
+ if (qptr->DuplicateOf)
+ LogMsg("mDNSCoreReceiveNoDNSSECAnswers: ERROR!! qptr %##s (%s) Duplicate question matching response", qptr->qname.c, DNSTypeName(qptr->qtype));
+
+ // Be careful to call the callback for duplicate questions first and then the original
+ // question. If we called the callback on the original question, it could stop and
+ // a duplicate question would become the original question.
+ mDNS_DropLockBeforeCallback(); // Allow client (and us) to legally make mDNS API calls
+ for (q = qptr->next ; q && q != m->NewQuestions; q = next)
+ {
+ next = q->next;
+ if (q->DuplicateOf == qptr)
+ {
+ if (q->ValidatingResponse)
+ LogInfo("mDNSCoreReceiveNoDNSSECAnswers: qptr %##s (%s) Duplicate question found", q->qname.c, DNSTypeName(q->qtype));
+ else
+ LogMsg("mDNSCoreReceiveNoDNSSECAnswers: ERROR!! qptr %##s (%s) Duplicate question not ValidatingResponse", q->qname.c, DNSTypeName(q->qtype));
+ if (q->QuestionCallback)
+ q->QuestionCallback(m, q, mDNSNULL, QC_nodnssec);
+ }
+ }
+ if (qptr->QuestionCallback)
+ qptr->QuestionCallback(m, qptr, mDNSNULL, QC_nodnssec);
+ mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
+ }
+ }
+}
+
+mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *const response, const mDNSu8 *end, const mDNSAddr *dstaddr,
+ mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, uDNS_LLQType LLQType, mDNSu8 rcode, CacheRecord *NSECRecords)
+{
+ int i;
+ const mDNSu8 *ptr = response->data;
+ CacheRecord *SOARecord = mDNSNULL;
+
+ for (i = 0; i < response->h.numQuestions && ptr && ptr < end; i++)
+ {
+ DNSQuestion q;
+ DNSQuestion *qptr = mDNSNULL;
+ ptr = getQuestion(response, ptr, end, InterfaceID, &q);
+ if (ptr && (qptr = ExpectingUnicastResponseForQuestion(m, dstport, response->h.id, &q, !dstaddr)))
+ {
+ CacheRecord *rr, *neg = mDNSNULL;
+ mDNSu32 slot = HashSlot(&q.qname);
+ CacheGroup *cg = CacheGroupForName(m, slot, q.qnamehash, &q.qname);
+ for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next)
+ if (SameNameRecordAnswersQuestion(&rr->resrec, qptr))
+ {
+ // 1. If we got a fresh answer to this query, then don't need to generate a negative entry
+ if (RRExpireTime(rr) - m->timenow > 0) break;
+ // 2. If we already had a negative entry, keep track of it so we can resurrect it instead of creating a new one
+ if (rr->resrec.RecordType == kDNSRecordTypePacketNegative) neg = rr;
+ }
+ // When we're doing parallel unicast and multicast queries for dot-local names (for supporting Microsoft
+ // Active Directory sites) we don't want to waste memory making negative cache entries for all the unicast answers.
+ // Otherwise we just fill up our cache with negative entries for just about every single multicast name we ever look up
+ // (since the Microsoft Active Directory server is going to assert that pretty much every single multicast name doesn't exist).
+ // This is not only a waste of memory, but there's also the problem of those negative entries confusing us later -- e.g. we
+ // suppress sending our mDNS query packet because we think we already have a valid (negative) answer to that query in our cache.
+ // The one exception is that we *DO* want to make a negative cache entry for "local. SOA", for the (common) case where we're
+ // *not* on a Microsoft Active Directory network, and there is no authoritative server for "local". Note that this is not
+ // in conflict with the mDNS spec, because that spec says, "Multicast DNS Zones have no SOA record," so it's okay to cache
+ // negative answers for "local. SOA" from a uDNS server, because the mDNS spec already says that such records do not exist :-)
+ //
+ // By suppressing negative responses, it might take longer to timeout a .local question as it might be expecting a
+ // response e.g., we deliver a positive "A" response and suppress negative "AAAA" response and the upper layer may
+ // be waiting longer to get the AAAA response before returning the "A" response to the application. To handle this
+ // case without creating the negative cache entries, we generate a negative response and let the layer above us
+ // do the appropriate thing. This negative response is also needed for appending new search domains.
+ if (!InterfaceID && q.qtype != kDNSType_SOA && IsLocalDomain(&q.qname))
+ {
+ if (!rr)
+ {
+ LogInfo("mDNSCoreReceiveNoUnicastAnswers: Generate negative response for %##s (%s)", q.qname.c, DNSTypeName(q.qtype));
+ m->CurrentQuestion = qptr;
+ // We are not creating a cache record in this case, we need to pass back
+ // the error we got so that the proxy code can return the right one to
+ // the application
+ if (qptr->ProxyQuestion)
+ qptr->responseFlags = response->h.flags;
+ GenerateNegativeResponse(m, QC_forceresponse);
+ m->CurrentQuestion = mDNSNULL;
+ }
+ else
+ {
+ LogInfo("mDNSCoreReceiveNoUnicastAnswers: Skipping check and not creating a negative cache entry for %##s (%s)", q.qname.c, DNSTypeName(q.qtype));
+ }
+ }
+ else
+ {
+ if (!rr)
+ {
+ // We start off assuming a negative caching TTL of 60 seconds
+ // but then look to see if we can find an SOA authority record to tell us a better value we should be using
+ mDNSu32 negttl = 60;
+ int repeat = 0;
+ const domainname *name = &q.qname;
+ mDNSu32 hash = q.qnamehash;
+
+ // Special case for our special Microsoft Active Directory "local SOA" check.
+ // Some cheap home gateways don't include an SOA record in the authority section when
+ // they send negative responses, so we don't know how long to cache the negative result.
+ // Because we don't want to keep hitting the root name servers with our query to find
+ // if we're on a network using Microsoft Active Directory using "local" as a private
+ // internal top-level domain, we make sure to cache the negative result for at least one day.
+ if (q.qtype == kDNSType_SOA && SameDomainName(&q.qname, &localdomain)) negttl = 60 * 60 * 24;
+
+ // If we're going to make (or update) a negative entry, then look for the appropriate TTL from the SOA record
+ if (response->h.numAuthorities && (ptr = LocateAuthorities(response, end)) != mDNSNULL)
+ {
+ ptr = GetLargeResourceRecord(m, response, ptr, end, InterfaceID, kDNSRecordTypePacketAuth, &m->rec);
+ if (ptr && m->rec.r.resrec.RecordType != kDNSRecordTypePacketNegative && m->rec.r.resrec.rrtype == kDNSType_SOA)
+ {
+ const mDNSu32 s = HashSlot(m->rec.r.resrec.name);
+ CacheGroup *cgSOA = CacheGroupForRecord(m, s, &m->rec.r.resrec);
+ const rdataSOA *const soa = (const rdataSOA *)m->rec.r.resrec.rdata->u.data;
+ mDNSu32 ttl_s = soa->min;
+ // We use the lesser of the SOA.MIN field and the SOA record's TTL, *except*
+ // for the SOA record for ".", where the record is reported as non-cacheable
+ // (TTL zero) for some reason, so in this case we just take the SOA record's TTL as-is
+ if (ttl_s > m->rec.r.resrec.rroriginalttl && m->rec.r.resrec.name->c[0])
+ ttl_s = m->rec.r.resrec.rroriginalttl;
+ if (negttl < ttl_s) negttl = ttl_s;
+
+ // Create the SOA record as we may have to return this to the questions
+ // that we are acting as a proxy for currently or in the future.
+ SOARecord = CreateNewCacheEntry(m, s, cgSOA, 1, mDNSfalse, mDNSNULL);
+
+ // Special check for SOA queries: If we queried for a.b.c.d.com, and got no answer,
+ // with an Authority Section SOA record for d.com, then this is a hint that the authority
+ // is d.com, and consequently SOA records b.c.d.com and c.d.com don't exist either.
+ // To do this we set the repeat count so the while loop below will make a series of negative cache entries for us
+ //
+ // For ProxyQuestions, we don't do this as we need to create additional SOA records to cache them
+ // along with the negative cache record. For simplicity, we don't create the additional records.
+ if (!qptr->ProxyQuestion && q.qtype == kDNSType_SOA)
+ {
+ int qcount = CountLabels(&q.qname);
+ int scount = CountLabels(m->rec.r.resrec.name);
+ if (qcount - 1 > scount)
+ if (SameDomainName(SkipLeadingLabels(&q.qname, qcount - scount), m->rec.r.resrec.name))
+ repeat = qcount - 1 - scount;
+ }
+ }
+ m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it
+ }
+
+ // If we already had a negative entry in the cache, then we double our existing negative TTL. This is to avoid
+ // the case where the record doesn't exist (e.g. particularly for things like our lb._dns-sd._udp.<domain> query),
+ // and the server returns no SOA record (or an SOA record with a small MIN TTL) so we assume a TTL
+ // of 60 seconds, and we end up polling the server every minute for a record that doesn't exist.
+ // With this fix in place, when this happens, we double the effective TTL each time (up to one hour),
+ // so that we back off our polling rate and don't keep hitting the server continually.
+ if (neg)
+ {
+ if (negttl < neg->resrec.rroriginalttl * 2)
+ negttl = neg->resrec.rroriginalttl * 2;
+ if (negttl > 3600)
+ negttl = 3600;
+ }
+
+ negttl = GetEffectiveTTL(LLQType, negttl); // Add 25% grace period if necessary
+
+ // If we already had a negative cache entry just update it, else make one or more new negative cache entries.
+ if (neg)
+ {
+ LogInfo("mDNSCoreReceiveNoUnicastAnswers: Renewing negative TTL from %d to %d %s", neg->resrec.rroriginalttl, negttl, CRDisplayString(m, neg));
+ RefreshCacheRecord(m, neg, negttl);
+ // When we created the cache for the first time and answered the question, the question's
+ // interval was set to MaxQuestionInterval. If the cache is about to expire and we are resending
+ // the queries, the interval should still be at MaxQuestionInterval. If the query is being
+ // restarted (setting it to InitialQuestionInterval) for other reasons e.g., wakeup,
+ // we should reset its question interval here to MaxQuestionInterval.
+ ResetQuestionState(m, qptr);
+ if (DNSSECQuestion(qptr))
+ neg->CRDNSSECQuestion = 1;
+ // Update the NSEC records again.
+ // TBD: Need to purge and revalidate if the cached NSECS and the new set are not same.
+ if (NSECRecords)
+ {
+ if (!AddNSECSForCacheRecord(m, NSECRecords, neg, rcode))
+ {
+ // We might just have an SOA record for zones that are not signed and hence don't log
+ // this as an error
+ LogInfo("mDNSCoreReceiveNoUnicastAnswers: AddNSECSForCacheRecord failed to add NSEC for negcr %s during refresh", CRDisplayString(m, neg));
+ FreeNSECRecords(m, NSECRecords);
+ neg->CRDNSSECQuestion = 0;
+ }
+ NSECRecords = mDNSNULL;
+ }
+ if (SOARecord)
+ {
+ if (neg->soa)
+ ReleaseCacheRecord(m, neg->soa);
+ neg->soa = SOARecord;
+ SOARecord = mDNSNULL;
+ }
+ }
+ else while (1)
+ {
+ CacheRecord *negcr;
+ debugf("mDNSCoreReceiveNoUnicastAnswers making negative cache entry TTL %d for %##s (%s)", negttl, name->c, DNSTypeName(q.qtype));
+ MakeNegativeCacheRecord(m, &m->rec.r, name, hash, q.qtype, q.qclass, negttl, mDNSInterface_Any, qptr->qDNSServer);
+ m->rec.r.responseFlags = response->h.flags;
+ // We create SOA records above which might create new cache groups. Earlier
+ // in the function we looked up the cache group for the name and it could have
+ // been NULL. If we pass NULL cg to new cache entries that we create below,
+ // it will create additional cache groups for the same name. To avoid that,
+ // look up the cache group again to re-initialize cg again.
+ cg = CacheGroupForName(m, slot, hash, name);
+ if (NSECRecords && DNSSECQuestion(qptr))
+ {
+ // Create the cache entry with delay and then add the NSEC records
+ // to it and add it immediately.
+ negcr = CreateNewCacheEntry(m, slot, cg, 1, mDNStrue, mDNSNULL);
+ if (negcr)
+ {
+ negcr->CRDNSSECQuestion = 0;
+ if (!AddNSECSForCacheRecord(m, NSECRecords, negcr, rcode))
+ {
+ LogInfo("mDNSCoreReceiveNoUnicastAnswers: AddNSECSForCacheRecord failed to add NSEC for negcr %s",
+ CRDisplayString(m, negcr));
+ FreeNSECRecords(m, NSECRecords);
+ }
+ else
+ {
+ negcr->CRDNSSECQuestion = 1;
+ LogInfo("mDNSCoreReceiveNoUnicastAnswers: AddNSECSForCacheRecord added neg NSEC for %s", CRDisplayString(m, negcr));
+ }
+ NSECRecords = mDNSNULL;
+ negcr->DelayDelivery = 0;
+ CacheRecordDeferredAdd(m, negcr);
+ }
+ m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it
+ break;
+ }
+ else
+ {
+ // Need to add with a delay so that we can tag the SOA record
+ negcr = CreateNewCacheEntry(m, slot, cg, 1, mDNStrue, mDNSNULL);
+ if (negcr)
+ {
+ negcr->CRDNSSECQuestion = 0;
+ if (DNSSECQuestion(qptr))
+ negcr->CRDNSSECQuestion = 1;
+ negcr->DelayDelivery = 0;
+
+ if (SOARecord)
+ {
+ if (negcr->soa)
+ ReleaseCacheRecord(m, negcr->soa);
+ negcr->soa = SOARecord;
+ SOARecord = mDNSNULL;
+ }
+ CacheRecordDeferredAdd(m, negcr);
+ }
+ }
+ m->rec.r.responseFlags = zeroID;
+ m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it
+ if (!repeat) break;
+ repeat--;
+ name = (const domainname *)(name->c + 1 + name->c[0]);
+ hash = DomainNameHashValue(name);
+ slot = HashSlot(name);
+ cg = CacheGroupForName(m, slot, hash, name);
+ }
+ }
+ }
+ }
+ }
+ if (NSECRecords) { LogInfo("mDNSCoreReceiveNoUnicastAnswers: NSECRecords not used"); FreeNSECRecords(m, NSECRecords); }
+ if (SOARecord) { LogInfo("mDNSCoreReceiveNoUnicastAnswers: SOARecord not used"); ReleaseCacheRecord(m, SOARecord); }
+}
+
+mDNSlocal void mDNSCorePrintStoredProxyRecords(mDNS *const m)
+{
+ AuthRecord *rrPtr = mDNSNULL;
+ LogSPS("Stored Proxy records :");
+ for (rrPtr = m->SPSRRSet; rrPtr; rrPtr = rrPtr->next)
+ {
+ LogSPS("%s", ARDisplayString(m, rrPtr));
+ }
+}
+
+mDNSlocal mDNSBool mDNSCoreRegisteredProxyRecord(mDNS *const m, AuthRecord *rr)
+{
+ AuthRecord *rrPtr = mDNSNULL;
+
+ for (rrPtr = m->SPSRRSet; rrPtr; rrPtr = rrPtr->next)
+ {
+ if (IdenticalResourceRecord(&rrPtr->resrec, &rr->resrec))
+ {
+ LogSPS("mDNSCoreRegisteredProxyRecord: Ignoring packet registered with sleep proxy : %s ", ARDisplayString(m, rr));
+ return mDNStrue;
+ }
+ }
+ mDNSCorePrintStoredProxyRecords(m);
+ return mDNSfalse;
+}
+
+mDNSlocal CacheRecord* mDNSCoreReceiveCacheCheck(mDNS *const m, const DNSMessage *const response, uDNS_LLQType LLQType,
+ const mDNSu32 slot, CacheGroup *cg, DNSQuestion *unicastQuestion, CacheRecord ***cfp, CacheRecord **NSECCachePtr,
+ mDNSInterfaceID InterfaceID)
+{
+ CacheRecord *rr;
+ CacheRecord **cflocal = *cfp;
+
+ for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next)
+ {
+ mDNSBool match;
+ // Resource record received via unicast, the resGroupID should match ?
+ if (!InterfaceID)
+ {
+ mDNSu16 id1 = (rr->resrec.rDNSServer ? rr->resrec.rDNSServer->resGroupID : 0);
+ mDNSu16 id2 = (m->rec.r.resrec.rDNSServer ? m->rec.r.resrec.rDNSServer->resGroupID : 0);
+ match = (id1 == id2);
+ }
+ else
+ match = (rr->resrec.InterfaceID == InterfaceID);
+ // If we found this exact resource record, refresh its TTL
+ if (match && IdenticalSameNameRecord(&m->rec.r.resrec, &rr->resrec))
+ {
+ if (m->rec.r.resrec.rdlength > InlineCacheRDSize)
+ verbosedebugf("mDNSCoreReceiveCacheCheck: Found record size %5d interface %p already in cache: %s",
+ m->rec.r.resrec.rdlength, InterfaceID, CRDisplayString(m, &m->rec.r));
+
+ if (m->rec.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask)
+ {
+ // If this packet record has the kDNSClass_UniqueRRSet flag set, then add it to our cache flushing list
+ if (rr->NextInCFList == mDNSNULL && *cfp != &rr->NextInCFList && LLQType != uDNS_LLQ_Events)
+ {
+ *cflocal = rr;
+ cflocal = &rr->NextInCFList;
+ *cflocal = (CacheRecord*)1;
+ *cfp = &rr->NextInCFList;
+ }
+
+ // If this packet record is marked unique, and our previous cached copy was not, then fix it
+ if (!(rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask))
+ {
+ DNSQuestion *q;
+ for (q = m->Questions; q; q=q->next)
+ {
+ if (ResourceRecordAnswersQuestion(&rr->resrec, q))
+ q->UniqueAnswers++;
+ }
+ rr->resrec.RecordType = m->rec.r.resrec.RecordType;
+ }
+ }
+
+ if (!SameRDataBody(&m->rec.r.resrec, &rr->resrec.rdata->u, SameDomainNameCS))
+ {
+ // If the rdata of the packet record differs in name capitalization from the record in our cache
+ // then mDNSPlatformMemSame will detect this. In this case, throw the old record away, so that clients get
+ // a 'remove' event for the record with the old capitalization, and then an 'add' event for the new one.
+ // <rdar://problem/4015377> mDNS -F returns the same domain multiple times with different casing
+ rr->resrec.rroriginalttl = 0;
+ rr->TimeRcvd = m->timenow;
+ rr->UnansweredQueries = MaxUnansweredQueries;
+ SetNextCacheCheckTimeForRecord(m, rr);
+ LogInfo("mDNSCoreReceiveCacheCheck: Discarding due to domainname case change old: %s", CRDisplayString(m, rr));
+ LogInfo("mDNSCoreReceiveCacheCheck: Discarding due to domainname case change new: %s", CRDisplayString(m, &m->rec.r));
+ LogInfo("mDNSCoreReceiveCacheCheck: Discarding due to domainname case change in %d slot %3d in %d %d",
+ NextCacheCheckEvent(rr) - m->timenow, slot, m->rrcache_nextcheck[slot] - m->timenow, m->NextCacheCheck - m->timenow);
+ // DO NOT break out here -- we want to continue as if we never found it
+ }
+ else if (!IdenticalAnonInfo(m->rec.r.resrec.AnonInfo, rr->resrec.AnonInfo))
+ {
+ // If the NSEC3 record changed, a few possibilities
+ //
+ // 1) the peer reinitialized e.g., after network change and still part of the
+ // same set.
+ // 2) the peer went to a different set but we did not see the goodbyes. If we just
+ // update the nsec3 record, it would be incorrect. Flush the cache so that we
+ // can deliver a RMV followed by ADD.
+ // 3) if the peer is ourselves and we see the goodbye when moving to a different set
+ // and so we flush the cache and create a new cache record with the new set information.
+ // Now we move back to the original set. In this case, we can't just update the
+ // NSEC3 record alone. We need to flush so that we can deliver an RMV followed by ADD
+ // when we create the new cache entry.
+ //
+ // Note: For case (1), we could avoid flushing the cache but we can't tell the difference
+ // from the other cases.
+ rr->resrec.rroriginalttl = 0;
+ rr->TimeRcvd = m->timenow;
+ rr->UnansweredQueries = MaxUnansweredQueries;
+ SetNextCacheCheckTimeForRecord(m, rr);
+ LogInfo("mDNSCoreReceiveCacheCheck: AnonInfo changed for %s", CRDisplayString(m, rr));
+ // DO NOT break out here -- we want to continue as if we never found it. When we return
+ // from this function, we will create a new cache entry with the new NSEC3 record
+ }
+ else if (m->rec.r.resrec.rroriginalttl > 0)
+ {
+ DNSQuestion *q;
+
+ m->mDNSStats.CacheRefreshed++;
+
+ if (rr->resrec.rroriginalttl == 0) debugf("uDNS rescuing %s", CRDisplayString(m, rr));
+ RefreshCacheRecord(m, rr, m->rec.r.resrec.rroriginalttl);
+ rr->responseFlags = response->h.flags;
+
+ // If we may have NSEC records returned with the answer (which we don't know yet as it
+ // has not been processed), we need to cache them along with the first cache
+ // record in the list that answers the question so that it can be used for validation
+ // later. The "type" check below is to make sure that we cache on the cache record
+ // that would answer the question. It is possible that we might cache additional things
+ // e.g., MX question might cache A records also, and we want to cache the NSEC on
+ // the record that answers the question.
+ if (response->h.numAnswers && unicastQuestion && unicastQuestion->qtype == rr->resrec.rrtype
+ && !(*NSECCachePtr))
+ {
+ LogInfo("mDNSCoreReceiveCacheCheck: rescuing RR %s", CRDisplayString(m, rr));
+ *NSECCachePtr = rr;
+ }
+ // We have to reset the question interval to MaxQuestionInterval so that we don't keep
+ // polling the network once we get a valid response back. For the first time when a new
+ // cache entry is created, AnswerCurrentQuestionWithResourceRecord does that.
+ // Subsequently, if we reissue questions from within the mDNSResponder e.g., DNS server
+ // configuration changed, without flushing the cache, we reset the question interval here.
+ // Currently, we do this for for both multicast and unicast questions as long as the record
+ // type is unique. For unicast, resource record is always unique and for multicast it is
+ // true for records like A etc. but not for PTR.
+ if (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask)
+ {
+ for (q = m->Questions; q; q=q->next)
+ {
+ if (!q->DuplicateOf && !q->LongLived &&
+ ActiveQuestion(q) && ResourceRecordAnswersQuestion(&rr->resrec, q))
+ {
+ ResetQuestionState(m, q);
+ debugf("mDNSCoreReceiveCacheCheck: Set MaxQuestionInterval for %p %##s (%s)", q, q->qname.c, DNSTypeName(q->qtype));
+ break; // Why break here? Aren't there other questions we might want to look at?-- SC July 2010
+ }
+ }
+ }
+ break;
+ }
+ else
+ {
+
+ // If the packet TTL is zero, that means we're deleting this record.
+ // To give other hosts on the network a chance to protest, we push the deletion
+ // out one second into the future. Also, we set UnansweredQueries to MaxUnansweredQueries.
+ // Otherwise, we'll do final queries for this record at 80% and 90% of its apparent
+ // lifetime (800ms and 900ms from now) which is a pointless waste of network bandwidth.
+ // If record's current expiry time is more than a second from now, we set it to expire in one second.
+ // If the record is already going to expire in less than one second anyway, we leave it alone --
+ // we don't want to let the goodbye packet *extend* the record's lifetime in our cache.
+ debugf("DE for %s", CRDisplayString(m, rr));
+ if (RRExpireTime(rr) - m->timenow > mDNSPlatformOneSecond)
+ {
+ rr->resrec.rroriginalttl = 1;
+ rr->TimeRcvd = m->timenow;
+ rr->UnansweredQueries = MaxUnansweredQueries;
+ SetNextCacheCheckTimeForRecord(m, rr);
+ }
+ break;
+ }
+ }
+ }
+ return rr;
+}
+
+mDNSlocal void mDNSParseNSEC3Records(mDNS *const m, const DNSMessage *const response, const mDNSu8 *end,
+ const mDNSInterfaceID InterfaceID, CacheRecord **NSEC3Records)
+{
+ const mDNSu8 *ptr = response->data;
+ CacheRecord *rr;
+ int i;
+
+ if (!response->h.numAuthorities)
+ return;
+ ptr = LocateAuthorities(response, end);
+ if (!ptr)
+ {
+ LogInfo("mDNSParseNSEC3Records: ERROR can't locate authorities");
+ return;
+ }
+ for (i = 0; i < response->h.numAuthorities && ptr && ptr < end; i++)
+ {
+ mDNSu32 slot;
+ CacheGroup *cg;
+
+ ptr = GetLargeResourceRecord(m, response, ptr, end, InterfaceID, kDNSRecordTypePacketAuth, &m->rec);
+ if (!ptr || m->rec.r.resrec.RecordType == kDNSRecordTypePacketNegative || m->rec.r.resrec.rrtype != kDNSType_NSEC3)
+ {
+ debugf("mDNSParseNSEC3Records: ptr %p, Record %s, ignoring", ptr, CRDisplayString(m, &m->rec.r));
+ m->rec.r.resrec.RecordType = 0;
+ continue;
+ }
+ slot = HashSlot(m->rec.r.resrec.name);
+ cg = CacheGroupForRecord(m, slot, &m->rec.r.resrec);
+ // Create the cache entry but don't add it to the cache it. We need
+ // to cache this along with the main cache record.
+ rr = CreateNewCacheEntry(m, slot, cg, 0, mDNSfalse, mDNSNULL);
+ if (rr)
+ {
+ debugf("mDNSParseNSEC3Records: %s", CRDisplayString(m, rr));
+ *NSEC3Records = rr;
+ NSEC3Records = &rr->next;
+ }
+ m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it
+ }
+}
+
+mDNSlocal void mDNSCoreResetRecord(mDNS *const m)
+{
+ m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it
+ if (m->rec.r.resrec.AnonInfo)
+ {
+ FreeAnonInfo(m->rec.r.resrec.AnonInfo);
+ m->rec.r.resrec.AnonInfo = mDNSNULL;
+ }
+}
+
+#define DEVICE_INFO_RECORD_LABELS 4
+
+// Determine if the record is an instance of _device-info._tcp.local.
+mDNSlocal mDNSBool IsDeviceInfoRecord(const domainname *d)
+{
+ const domainname *afterInstance;
+
+ if (CountLabels(d) != DEVICE_INFO_RECORD_LABELS)
+ return mDNSfalse;
+
+ // skip the instance name
+ afterInstance = SkipLeadingLabels(d, 1);
+ if (SameDomainName(afterInstance, &LocalDeviceInfoName))
+ return mDNStrue;
+
+ return mDNSfalse;
+}
+
+// Note: mDNSCoreReceiveResponse calls mDNS_Deregister_internal which can call a user callback, which may change