blob: e1267543672a7f937f4d04ed42e8bc529c26c9f9 [file] [log] [blame]
/* -*- Mode: C; tab-width: 4 -*-
*
* Copyright (c) 2002-2006 Apple Computer, 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.
*
* This code is completely 100% portable C. It does not depend on any external header files
* from outside the mDNS project -- all the types it expects to find are defined right here.
*
* The previous point is very important: This file does not depend on any external
* header files. It should compile on *any* platform that has a C compiler, without
* making *any* assumptions about availability of so-called "standard" C functions,
* routines, or types (which may or may not be present on any given platform).
* Formatting notes:
* This code follows the "Whitesmiths style" C indentation rules. Plenty of discussion
* on C indentation can be found on the web, such as <http://www.kafejo.com/komp/1tbs.htm>,
* but for the sake of brevity here I will say just this: Curly braces are not syntactially
* part of an "if" statement; they are the beginning and ending markers of a compound statement;
* therefore common sense dictates that if they are part of a compound statement then they
* should be indented to the same level as everything else in that compound statement.
* Indenting curly braces at the same level as the "if" implies that curly braces are
* part of the "if", which is false. (This is as misleading as people who write "char* x,y;"
* thinking that variables x and y are both of type "char*" -- and anyone who doesn't
* understand why variable y is not of type "char*" just proves the point that poor code
* layout leads people to unfortunate misunderstandings about how the C language really works.)
*/
#include "DNSCommon.h" // Defines general DNS untility routines
#include "uDNS.h" // Defines entry points into unicast-specific routines
// Disable certain benign warnings with Microsoft compilers
#if(defined(_MSC_VER))
// Disable "conditional expression is constant" warning for debug macros.
// Otherwise, this generates warnings for the perfectly natural construct "while(1)"
// If someone knows a variant way of writing "while(1)" that doesn't generate warning messages, please let us know
#pragma warning(disable:4127)
// Disable "assignment within conditional expression".
// Other compilers understand the convention that if you place the assignment expression within an extra pair
// of parentheses, this signals to the compiler that you really intended an assignment and no warning is necessary.
// The Microsoft compiler doesn't understand this convention, so in the absense of any other way to signal
// to the compiler that the assignment is intentional, we have to just turn this warning off completely.
#pragma warning(disable:4706)
#endif
#if APPLE_OSX_mDNSResponder
#include <WebFilterDNS/WebFilterDNS.h>
#if ! NO_WCF
WCFConnection *WCFConnectionNew(void) __attribute__((weak_import));
void WCFConnectionDealloc(WCFConnection* c) __attribute__((weak_import));
// Do we really need to define a macro for "if"?
#define CHECK_WCF_FUNCTION(X) if (X)
#endif // ! NO_WCF
#else
#define NO_WCF 1
#endif // APPLE_OSX_mDNSResponder
// Forward declarations
mDNSlocal void BeginSleepProcessing(mDNS *const m);
mDNSlocal void RetrySPSRegistrations(mDNS *const m);
mDNSlocal void SendWakeup(mDNS *const m, mDNSInterfaceID InterfaceID, mDNSEthAddr *EthAddr, mDNSOpaque48 *password);
mDNSlocal mDNSBool CacheRecordRmvEventsForQuestion(mDNS *const m, DNSQuestion *q);
mDNSlocal mDNSBool LocalRecordRmvEventsForQuestion(mDNS *const m, DNSQuestion *q);
mDNSlocal void mDNS_PurgeBeforeResolve(mDNS *const m, DNSQuestion *q);
// ***************************************************************************
#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark - Program Constants
#endif
#define NO_HINFO 1
// Any records bigger than this are considered 'large' records
#define SmallRecordLimit 1024
#define kMaxUpdateCredits 10
#define kUpdateCreditRefreshInterval (mDNSPlatformOneSecond * 6)
mDNSexport const char *const mDNS_DomainTypeNames[] =
{
"b._dns-sd._udp.", // Browse
"db._dns-sd._udp.", // Default Browse
"lb._dns-sd._udp.", // Automatic Browse
"r._dns-sd._udp.", // Registration
"dr._dns-sd._udp." // Default Registration
};
#ifdef UNICAST_DISABLED
#define uDNS_IsActiveQuery(q, u) mDNSfalse
#endif
// ***************************************************************************
#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - General Utility Functions
#endif
// If there is a authoritative LocalOnly record that answers questions of type A, AAAA and CNAME
// this returns true. Main use is to handle /etc/hosts records.
#define LORecordAnswersAddressType(rr) ((rr)->ARType == AuthRecordLocalOnly && \
(rr)->resrec.RecordType & kDNSRecordTypeUniqueMask && \
((rr)->resrec.rrtype == kDNSType_A || (rr)->resrec.rrtype == kDNSType_AAAA || \
(rr)->resrec.rrtype == kDNSType_CNAME))
#define FollowCNAME(q, rr, AddRecord) (AddRecord && (q)->qtype != kDNSType_CNAME && \
(rr)->RecordType != kDNSRecordTypePacketNegative && \
(rr)->rrtype == kDNSType_CNAME)
mDNSlocal void SetNextQueryStopTime(mDNS *const m, const DNSQuestion *const q)
{
if (m->mDNS_busy != m->mDNS_reentrancy+1)
LogMsg("SetNextQueryTime: Lock not held! mDNS_busy (%ld) mDNS_reentrancy (%ld)", m->mDNS_busy, m->mDNS_reentrancy);
#if ForceAlerts
if (m->mDNS_busy != m->mDNS_reentrancy+1) *(long*)0 = 0;
#endif
if (m->NextScheduledStopTime - q->StopTime > 0)
m->NextScheduledStopTime = q->StopTime;
}
mDNSexport void SetNextQueryTime(mDNS *const m, const DNSQuestion *const q)
{
if (m->mDNS_busy != m->mDNS_reentrancy+1)
LogMsg("SetNextQueryTime: Lock not held! mDNS_busy (%ld) mDNS_reentrancy (%ld)", m->mDNS_busy, m->mDNS_reentrancy);
#if ForceAlerts
if (m->mDNS_busy != m->mDNS_reentrancy+1) *(long*)0 = 0;
#endif
if (ActiveQuestion(q))
{
// Depending on whether this is a multicast or unicast question we want to set either:
// m->NextScheduledQuery = NextQSendTime(q) or
// m->NextuDNSEvent = NextQSendTime(q)
mDNSs32 *const timer = mDNSOpaque16IsZero(q->TargetQID) ? &m->NextScheduledQuery : &m->NextuDNSEvent;
if (*timer - NextQSendTime(q) > 0)
*timer = NextQSendTime(q);
}
}
mDNSlocal void ReleaseAuthEntity(AuthHash *r, AuthEntity *e)
{
#if APPLE_OSX_mDNSResponder && MACOSX_MDNS_MALLOC_DEBUGGING >= 1
unsigned int i;
for (i=0; i<sizeof(*e); i++) ((char*)e)[i] = 0xFF;
#endif
e->next = r->rrauth_free;
r->rrauth_free = e;
r->rrauth_totalused--;
}
mDNSlocal void ReleaseAuthGroup(AuthHash *r, AuthGroup **cp)
{
AuthEntity *e = (AuthEntity *)(*cp);
LogMsg("ReleaseAuthGroup: Releasing AuthGroup %##s", (*cp)->name->c);
if ((*cp)->rrauth_tail != &(*cp)->members)
LogMsg("ERROR: (*cp)->members == mDNSNULL but (*cp)->rrauth_tail != &(*cp)->members)");
if ((*cp)->name != (domainname*)((*cp)->namestorage)) mDNSPlatformMemFree((*cp)->name);
(*cp)->name = mDNSNULL;
*cp = (*cp)->next; // Cut record from list
ReleaseAuthEntity(r, e);
}
mDNSlocal AuthEntity *GetAuthEntity(AuthHash *r, const AuthGroup *const PreserveAG)
{
AuthEntity *e = mDNSNULL;
if (r->rrauth_lock) { LogMsg("GetFreeCacheRR ERROR! Cache already locked!"); return(mDNSNULL); }
r->rrauth_lock = 1;
if (!r->rrauth_free)
{
// We allocate just one AuthEntity at a time because we need to be able
// free them all individually which normally happens when we parse /etc/hosts into
// AuthHash where we add the "new" entries and discard (free) the already added
// entries. If we allocate as chunks, we can't free them individually.
AuthEntity *storage = mDNSPlatformMemAllocate(sizeof(AuthEntity));
storage->next = mDNSNULL;
r->rrauth_free = storage;
}
// If we still have no free records, recycle all the records we can.
// Enumerating the entire auth is moderately expensive, so when we do it, we reclaim all the records we can in one pass.
if (!r->rrauth_free)
{
mDNSu32 oldtotalused = r->rrauth_totalused;
mDNSu32 slot;
for (slot = 0; slot < AUTH_HASH_SLOTS; slot++)
{
AuthGroup **cp = &r->rrauth_hash[slot];
while (*cp)
{
if ((*cp)->members || (*cp)==PreserveAG) cp=&(*cp)->next;
else ReleaseAuthGroup(r, cp);
}
}
LogInfo("GetAuthEntity: Recycled %d records to reduce auth cache from %d to %d",
oldtotalused - r->rrauth_totalused, oldtotalused, r->rrauth_totalused);
}
if (r->rrauth_free) // If there are records in the free list, take one
{
e = r->rrauth_free;
r->rrauth_free = e->next;
if (++r->rrauth_totalused >= r->rrauth_report)
{
LogInfo("RR Auth now using %ld objects", r->rrauth_totalused);
if (r->rrauth_report < 100) r->rrauth_report += 10;
else if (r->rrauth_report < 1000) r->rrauth_report += 100;
else r->rrauth_report += 1000;
}
mDNSPlatformMemZero(e, sizeof(*e));
}
r->rrauth_lock = 0;
return(e);
}
mDNSexport AuthGroup *AuthGroupForName(AuthHash *r, const mDNSu32 slot, const mDNSu32 namehash, const domainname *const name)
{
AuthGroup *ag;
for (ag = r->rrauth_hash[slot]; ag; ag=ag->next)
if (ag->namehash == namehash && SameDomainName(ag->name, name))
break;
return(ag);
}
mDNSexport AuthGroup *AuthGroupForRecord(AuthHash *r, const mDNSu32 slot, const ResourceRecord *const rr)
{
return(AuthGroupForName(r, slot, rr->namehash, rr->name));
}
mDNSlocal AuthGroup *GetAuthGroup(AuthHash *r, const mDNSu32 slot, const ResourceRecord *const rr)
{
mDNSu16 namelen = DomainNameLength(rr->name);
AuthGroup *ag = (AuthGroup*)GetAuthEntity(r, mDNSNULL);
if (!ag) { LogMsg("GetAuthGroup: Failed to allocate memory for %##s", rr->name->c); return(mDNSNULL); }
ag->next = r->rrauth_hash[slot];
ag->namehash = rr->namehash;
ag->members = mDNSNULL;
ag->rrauth_tail = &ag->members;
ag->name = (domainname*)ag->namestorage;
ag->NewLocalOnlyRecords = mDNSNULL;
if (namelen > InlineCacheGroupNameSize) ag->name = mDNSPlatformMemAllocate(namelen);
if (!ag->name)
{
LogMsg("GetAuthGroup: Failed to allocate name storage for %##s", rr->name->c);
ReleaseAuthEntity(r, (AuthEntity*)ag);
return(mDNSNULL);
}
AssignDomainName(ag->name, rr->name);
if (AuthGroupForRecord(r, slot, rr)) LogMsg("GetAuthGroup: Already have AuthGroup for %##s", rr->name->c);
r->rrauth_hash[slot] = ag;
if (AuthGroupForRecord(r, slot, rr) != ag) LogMsg("GetAuthGroup: Not finding AuthGroup for %##s", rr->name->c);
return(ag);
}
// Returns the AuthGroup in which the AuthRecord was inserted
mDNSexport AuthGroup *InsertAuthRecord(mDNS *const m, AuthHash *r, AuthRecord *rr)
{
AuthGroup *ag;
const mDNSu32 slot = AuthHashSlot(rr->resrec.name);
ag = AuthGroupForRecord(r, slot, &rr->resrec);
if (!ag) ag = GetAuthGroup(r, slot, &rr->resrec); // If we don't have a AuthGroup for this name, make one now
if (ag)
{
LogInfo("InsertAuthRecord: inserting auth record %s from table", ARDisplayString(m, rr));
*(ag->rrauth_tail) = rr; // Append this record to tail of cache slot list
ag->rrauth_tail = &(rr->next); // Advance tail pointer
}
return ag;
}
mDNSexport AuthGroup *RemoveAuthRecord(mDNS *const m, AuthHash *r, AuthRecord *rr)
{
AuthGroup *a;
AuthGroup **ag = &a;
AuthRecord **rp;
const mDNSu32 slot = AuthHashSlot(rr->resrec.name);
a = AuthGroupForRecord(r, slot, &rr->resrec);
if (!a) { LogMsg("RemoveAuthRecord: ERROR!! AuthGroup not found for %s", ARDisplayString(m, rr)); return mDNSNULL; }
rp = &(*ag)->members;
while (*rp)
{
if (*rp != rr)
rp=&(*rp)->next;
else
{
// We don't break here, so that we can set the tail below without tracking "prev" pointers
LogInfo("RemoveAuthRecord: removing auth record %s from table", ARDisplayString(m, rr));
*rp = (*rp)->next; // Cut record from list
}
}
// TBD: If there are no more members, release authgroup ?
(*ag)->rrauth_tail = rp;
return a;
}
mDNSexport CacheGroup *CacheGroupForName(const mDNS *const m, const mDNSu32 slot, const mDNSu32 namehash, const domainname *const name)
{
CacheGroup *cg;
for (cg = m->rrcache_hash[slot]; cg; cg=cg->next)
if (cg->namehash == namehash && SameDomainName(cg->name, name))
break;
return(cg);
}
mDNSlocal CacheGroup *CacheGroupForRecord(const mDNS *const m, const mDNSu32 slot, const ResourceRecord *const rr)
{
return(CacheGroupForName(m, slot, rr->namehash, rr->name));
}
mDNSexport mDNSBool mDNS_AddressIsLocalSubnet(mDNS *const m, const mDNSInterfaceID InterfaceID, const mDNSAddr *addr)
{
NetworkInterfaceInfo *intf;
if (addr->type == mDNSAddrType_IPv4)
{
// Normally we resist touching the NotAnInteger fields, but here we're doing tricky bitwise masking so we make an exception
if (mDNSv4AddressIsLinkLocal(&addr->ip.v4)) return(mDNStrue);
for (intf = m->HostInterfaces; intf; intf = intf->next)
if (intf->ip.type == addr->type && intf->InterfaceID == InterfaceID && intf->McastTxRx)
if (((intf->ip.ip.v4.NotAnInteger ^ addr->ip.v4.NotAnInteger) & intf->mask.ip.v4.NotAnInteger) == 0)
return(mDNStrue);
}
if (addr->type == mDNSAddrType_IPv6)
{
if (mDNSv6AddressIsLinkLocal(&addr->ip.v6)) return(mDNStrue);
for (intf = m->HostInterfaces; intf; intf = intf->next)
if (intf->ip.type == addr->type && intf->InterfaceID == InterfaceID && intf->McastTxRx)
if ((((intf->ip.ip.v6.l[0] ^ addr->ip.v6.l[0]) & intf->mask.ip.v6.l[0]) == 0) &&
(((intf->ip.ip.v6.l[1] ^ addr->ip.v6.l[1]) & intf->mask.ip.v6.l[1]) == 0) &&
(((intf->ip.ip.v6.l[2] ^ addr->ip.v6.l[2]) & intf->mask.ip.v6.l[2]) == 0) &&
(((intf->ip.ip.v6.l[3] ^ addr->ip.v6.l[3]) & intf->mask.ip.v6.l[3]) == 0))
return(mDNStrue);
}
return(mDNSfalse);
}
mDNSlocal NetworkInterfaceInfo *FirstInterfaceForID(mDNS *const m, const mDNSInterfaceID InterfaceID)
{
NetworkInterfaceInfo *intf = m->HostInterfaces;
while (intf && intf->InterfaceID != InterfaceID) intf = intf->next;
return(intf);
}
mDNSexport char *InterfaceNameForID(mDNS *const m, const mDNSInterfaceID InterfaceID)
{
NetworkInterfaceInfo *intf = FirstInterfaceForID(m, InterfaceID);
return(intf ? intf->ifname : mDNSNULL);
}
// Caller should hold the lock
mDNSlocal void GenerateNegativeResponse(mDNS *const m)
{
DNSQuestion *q;
if (!m->CurrentQuestion) { LogMsg("GenerateNegativeResponse: ERROR!! CurrentQuestion not set"); return; }
q = m->CurrentQuestion;
LogInfo("GenerateNegativeResponse: Generating negative response for question %##s (%s)", q->qname.c, DNSTypeName(q->qtype));
MakeNegativeCacheRecord(m, &m->rec.r, &q->qname, q->qnamehash, q->qtype, q->qclass, 60, mDNSInterface_Any, mDNSNULL);
AnswerCurrentQuestionWithResourceRecord(m, &m->rec.r, QC_addnocache);
if (m->CurrentQuestion == q) { q->ThisQInterval = 0; } // Deactivate this question
// Don't touch the question after this
m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it
}
mDNSlocal void AnswerQuestionByFollowingCNAME(mDNS *const m, DNSQuestion *q, ResourceRecord *rr)
{
const mDNSBool selfref = SameDomainName(&q->qname, &rr->rdata->u.name);
if (q->CNAMEReferrals >= 10 || selfref)
LogMsg("AnswerQuestionByFollowingCNAME: %p %##s (%s) NOT following CNAME referral %d%s for %s",
q, q->qname.c, DNSTypeName(q->qtype), q->CNAMEReferrals, selfref ? " (Self-Referential)" : "", RRDisplayString(m, rr));
else
{
const mDNSu32 c = q->CNAMEReferrals + 1; // Stash a copy of the new q->CNAMEReferrals value
// The SameDomainName check above is to ignore bogus CNAME records that point right back at
// themselves. Without that check we can get into a case where we have two duplicate questions,
// A and B, and when we stop question A, UpdateQuestionDuplicates copies the value of CNAMEReferrals
// from A to B, and then A is re-appended to the end of the list as a duplicate of B (because
// the target name is still the same), and then when we stop question B, UpdateQuestionDuplicates
// copies the B's value of CNAMEReferrals back to A, and we end up not incrementing CNAMEReferrals
// for either of them. This is not a problem for CNAME loops of two or more records because in
// those cases the newly re-appended question A has a different target name and therefore cannot be
// a duplicate of any other question ('B') which was itself a duplicate of the previous question A.
// Right now we just stop and re-use the existing query. If we really wanted to be 100% perfect,
// and track CNAMEs coming and going, we should really create a subordinate query here,
// which we would subsequently cancel and retract if the CNAME referral record were removed.
// In reality this is such a corner case we'll ignore it until someone actually needs it.
LogInfo("AnswerQuestionByFollowingCNAME: %p %##s (%s) following CNAME referral %d for %s",
q, q->qname.c, DNSTypeName(q->qtype), q->CNAMEReferrals, RRDisplayString(m, rr));
mDNS_StopQuery_internal(m, q); // Stop old query
AssignDomainName(&q->qname, &rr->rdata->u.name); // Update qname
q->qnamehash = DomainNameHashValue(&q->qname); // and namehash
// If a unicast query results in a CNAME that points to a .local, we need to re-try
// this as unicast. Setting the mDNSInterface_Unicast tells mDNS_StartQuery_internal
// to try this as unicast query even though it is a .local name
if (!mDNSOpaque16IsZero(q->TargetQID) && IsLocalDomain(&q->qname))
{
LogInfo("AnswerQuestionByFollowingCNAME: Resolving a .local CNAME %p %##s (%s) Record %s",
q, q->qname.c, DNSTypeName(q->qtype), RRDisplayString(m, rr));
q->InterfaceID = mDNSInterface_Unicast;
}
mDNS_StartQuery_internal(m, q); // start new query
// Record how many times we've done this. We need to do this *after* mDNS_StartQuery_internal,
// because mDNS_StartQuery_internal re-initializes CNAMEReferrals to zero
q->CNAMEReferrals = c;
}
}
// For a single given DNSQuestion pointed to by CurrentQuestion, deliver an add/remove result for the single given AuthRecord
// Note: All the callers should use the m->CurrentQuestion to see if the question is still valid or not
mDNSlocal void AnswerLocalQuestionWithLocalAuthRecord(mDNS *const m, AuthRecord *rr, QC_result AddRecord)
{
DNSQuestion *q = m->CurrentQuestion;
mDNSBool followcname;
if (!q)
{
LogMsg("AnswerLocalQuestionWithLocalAuthRecord: ERROR!! CurrentQuestion NULL while answering with %s", ARDisplayString(m, rr));
return;
}
followcname = FollowCNAME(q, &rr->resrec, AddRecord);
// We should not be delivering results for record types Unregistered, Deregistering, and (unverified) Unique
if (!(rr->resrec.RecordType & kDNSRecordTypeActiveMask))
{
LogMsg("AnswerLocalQuestionWithLocalAuthRecord: *NOT* delivering %s event for local record type %X %s",
AddRecord ? "Add" : "Rmv", rr->resrec.RecordType, ARDisplayString(m, rr));
return;
}
// Indicate that we've given at least one positive answer for this record, so we should be prepared to send a goodbye for it
if (AddRecord) rr->AnsweredLocalQ = mDNStrue;
mDNS_DropLockBeforeCallback(); // Allow client to legally make mDNS API calls from the callback
if (q->QuestionCallback && !q->NoAnswer)
{
q->CurrentAnswers += AddRecord ? 1 : -1;
if (LORecordAnswersAddressType(rr))
{
if (!followcname || q->ReturnIntermed)
{
// Don't send this packet on the wire as we answered from /etc/hosts
q->ThisQInterval = 0;
q->LOAddressAnswers += AddRecord ? 1 : -1;
q->QuestionCallback(m, q, &rr->resrec, AddRecord);
}
mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
// The callback above could have caused the question to stop. Detect that
// using m->CurrentQuestion
if (followcname && m->CurrentQuestion == q)
AnswerQuestionByFollowingCNAME(m, q, &rr->resrec);
return;
}
else
q->QuestionCallback(m, q, &rr->resrec, AddRecord);
}
mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
}
mDNSlocal void AnswerInterfaceAnyQuestionsWithLocalAuthRecord(mDNS *const m, AuthRecord *rr, QC_result AddRecord)
{
if (m->CurrentQuestion)
LogMsg("AnswerInterfaceAnyQuestionsWithLocalAuthRecord: ERROR m->CurrentQuestion already set: %##s (%s)",
m->CurrentQuestion->qname.c, DNSTypeName(m->CurrentQuestion->qtype));
m->CurrentQuestion = m->Questions;
while (m->CurrentQuestion && m->CurrentQuestion != m->NewQuestions)
{
mDNSBool answered;
DNSQuestion *q = m->CurrentQuestion;
if (RRAny(rr))
answered = ResourceRecordAnswersQuestion(&rr->resrec, q);
else
answered = LocalOnlyRecordAnswersQuestion(rr, q);
if (answered)
AnswerLocalQuestionWithLocalAuthRecord(m, rr, AddRecord); // MUST NOT dereference q again
if (m->CurrentQuestion == q) // If m->CurrentQuestion was not auto-advanced, do it ourselves now
m->CurrentQuestion = q->next;
}
m->CurrentQuestion = mDNSNULL;
}
// When a new local AuthRecord is created or deleted, AnswerAllLocalQuestionsWithLocalAuthRecord()
// delivers the appropriate add/remove events to listening questions:
// 1. It runs though all our LocalOnlyQuestions delivering answers as appropriate,
// stopping if it reaches a NewLocalOnlyQuestion -- brand-new questions are handled by AnswerNewLocalOnlyQuestion().
// 2. If the AuthRecord is marked mDNSInterface_LocalOnly or mDNSInterface_P2P, then it also runs though
// our main question list, delivering answers to mDNSInterface_Any questions as appropriate,
// stopping if it reaches a NewQuestion -- brand-new questions are handled by AnswerNewQuestion().
//
// AnswerAllLocalQuestionsWithLocalAuthRecord is used by the m->NewLocalRecords loop in mDNS_Execute(),
// and by mDNS_Deregister_internal()
mDNSlocal void AnswerAllLocalQuestionsWithLocalAuthRecord(mDNS *const m, AuthRecord *rr, QC_result AddRecord)
{
if (m->CurrentQuestion)
LogMsg("AnswerAllLocalQuestionsWithLocalAuthRecord ERROR m->CurrentQuestion already set: %##s (%s)",
m->CurrentQuestion->qname.c, DNSTypeName(m->CurrentQuestion->qtype));
m->CurrentQuestion = m->LocalOnlyQuestions;
while (m->CurrentQuestion && m->CurrentQuestion != m->NewLocalOnlyQuestions)
{
mDNSBool answered;
DNSQuestion *q = m->CurrentQuestion;
// We are called with both LocalOnly/P2P record or a regular AuthRecord
if (RRAny(rr))
answered = ResourceRecordAnswersQuestion(&rr->resrec, q);
else
answered = LocalOnlyRecordAnswersQuestion(rr, q);
if (answered)
AnswerLocalQuestionWithLocalAuthRecord(m, rr, AddRecord); // MUST NOT dereference q again
if (m->CurrentQuestion == q) // If m->CurrentQuestion was not auto-advanced, do it ourselves now
m->CurrentQuestion = q->next;
}
m->CurrentQuestion = mDNSNULL;
// If this AuthRecord is marked LocalOnly or P2P, then we want to deliver it to all local 'mDNSInterface_Any' questions
if (rr->ARType == AuthRecordLocalOnly || rr->ARType == AuthRecordP2P)
AnswerInterfaceAnyQuestionsWithLocalAuthRecord(m, rr, AddRecord);
}
// ***************************************************************************
#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - Resource Record Utility Functions
#endif
#define RRTypeIsAddressType(T) ((T) == kDNSType_A || (T) == kDNSType_AAAA)
#define ResourceRecordIsValidAnswer(RR) ( ((RR)-> resrec.RecordType & kDNSRecordTypeActiveMask) && \
((RR)->Additional1 == mDNSNULL || ((RR)->Additional1->resrec.RecordType & kDNSRecordTypeActiveMask)) && \
((RR)->Additional2 == mDNSNULL || ((RR)->Additional2->resrec.RecordType & kDNSRecordTypeActiveMask)) && \
((RR)->DependentOn == mDNSNULL || ((RR)->DependentOn->resrec.RecordType & kDNSRecordTypeActiveMask)) )
#define ResourceRecordIsValidInterfaceAnswer(RR, INTID) \
(ResourceRecordIsValidAnswer(RR) && \
((RR)->resrec.InterfaceID == mDNSInterface_Any || (RR)->resrec.InterfaceID == (INTID)))
#define DefaultProbeCountForTypeUnique ((mDNSu8)3)
#define DefaultProbeCountForRecordType(X) ((X) == kDNSRecordTypeUnique ? DefaultProbeCountForTypeUnique : (mDNSu8)0)
#define InitialAnnounceCount ((mDNSu8)8)
// For goodbye packets we set the count to 3, and for wakeups we set it to 18
// (which will be up to 15 wakeup attempts over the course of 30 seconds,
// and then if the machine fails to wake, 3 goodbye packets).
#define GoodbyeCount ((mDNSu8)3)
#define WakeupCount ((mDNSu8)18)
// Number of wakeups we send if WakeOnResolve is set in the question
#define InitialWakeOnResolveCount ((mDNSu8)3)
// Note that the announce intervals use exponential backoff, doubling each time. The probe intervals do not.
// This means that because the announce interval is doubled after sending the first packet, the first
// observed on-the-wire inter-packet interval between announcements is actually one second.
// The half-second value here may be thought of as a conceptual (non-existent) half-second delay *before* the first packet is sent.
#define DefaultProbeIntervalForTypeUnique (mDNSPlatformOneSecond/4)
#define DefaultAnnounceIntervalForTypeShared (mDNSPlatformOneSecond/2)
#define DefaultAnnounceIntervalForTypeUnique (mDNSPlatformOneSecond/2)
#define DefaultAPIntervalForRecordType(X) ((X) & kDNSRecordTypeActiveSharedMask ? DefaultAnnounceIntervalForTypeShared : \
(X) & kDNSRecordTypeUnique ? DefaultProbeIntervalForTypeUnique : \
(X) & kDNSRecordTypeActiveUniqueMask ? DefaultAnnounceIntervalForTypeUnique : 0)
#define TimeToAnnounceThisRecord(RR,time) ((RR)->AnnounceCount && (time) - ((RR)->LastAPTime + (RR)->ThisAPInterval) >= 0)
#define TimeToSendThisRecord(RR,time) ((TimeToAnnounceThisRecord(RR,time) || (RR)->ImmedAnswer) && ResourceRecordIsValidAnswer(RR))
#define TicksTTL(RR) ((mDNSs32)(RR)->resrec.rroriginalttl * mDNSPlatformOneSecond)
#define RRExpireTime(RR) ((RR)->TimeRcvd + TicksTTL(RR))
#define MaxUnansweredQueries 4
// SameResourceRecordSignature returns true if two resources records have the same name, type, and class, and may be sent
// (or were received) on the same interface (i.e. if *both* records specify an interface, then it has to match).
// TTL and rdata may differ.
// This is used for cache flush management:
// When sending a unique record, all other records matching "SameResourceRecordSignature" must also be sent
// When receiving a unique record, all old cache records matching "SameResourceRecordSignature" are flushed
// SameResourceRecordNameClassInterface is functionally the same as SameResourceRecordSignature, except rrtype does not have to match
#define SameResourceRecordSignature(A,B) (A)->resrec.rrtype == (B)->resrec.rrtype && SameResourceRecordNameClassInterface((A),(B))
mDNSlocal mDNSBool SameResourceRecordNameClassInterface(const AuthRecord *const r1, const AuthRecord *const r2)
{
if (!r1) { LogMsg("SameResourceRecordSignature ERROR: r1 is NULL"); return(mDNSfalse); }
if (!r2) { LogMsg("SameResourceRecordSignature ERROR: r2 is NULL"); return(mDNSfalse); }
if (r1->resrec.InterfaceID &&
r2->resrec.InterfaceID &&
r1->resrec.InterfaceID != r2->resrec.InterfaceID) return(mDNSfalse);
return(mDNSBool)(
r1->resrec.rrclass == r2->resrec.rrclass &&
r1->resrec.namehash == r2->resrec.namehash &&
SameDomainName(r1->resrec.name, r2->resrec.name));
}
// PacketRRMatchesSignature behaves as SameResourceRecordSignature, except that types may differ if our
// authoratative record is unique (as opposed to shared). For unique records, we are supposed to have
// complete ownership of *all* types for this name, so *any* record type with the same name is a conflict.
// In addition, when probing we send our questions with the wildcard type kDNSQType_ANY,
// so a response of any type should match, even if it is not actually the type the client plans to use.
// For now, to make it easier to avoid false conflicts, we treat SPS Proxy records like shared records,
// and require the rrtypes to match for the rdata to be considered potentially conflicting
mDNSlocal mDNSBool PacketRRMatchesSignature(const CacheRecord *const pktrr, const AuthRecord *const authrr)
{
if (!pktrr) { LogMsg("PacketRRMatchesSignature ERROR: pktrr is NULL"); return(mDNSfalse); }
if (!authrr) { LogMsg("PacketRRMatchesSignature ERROR: authrr is NULL"); return(mDNSfalse); }
if (pktrr->resrec.InterfaceID &&
authrr->resrec.InterfaceID &&
pktrr->resrec.InterfaceID != authrr->resrec.InterfaceID) return(mDNSfalse);
if (!(authrr->resrec.RecordType & kDNSRecordTypeUniqueMask) || authrr->WakeUp.HMAC.l[0])
if (pktrr->resrec.rrtype != authrr->resrec.rrtype) return(mDNSfalse);
return(mDNSBool)(
pktrr->resrec.rrclass == authrr->resrec.rrclass &&
pktrr->resrec.namehash == authrr->resrec.namehash &&
SameDomainName(pktrr->resrec.name, authrr->resrec.name));
}
// CacheRecord *ka is the CacheRecord from the known answer list in the query.
// This is the information that the requester believes to be correct.
// AuthRecord *rr is the answer we are proposing to give, if not suppressed.
// This is the information that we believe to be correct.
// We've already determined that we plan to give this answer on this interface
// (either the record is non-specific, or it is specific to this interface)
// so now we just need to check the name, type, class, rdata and TTL.
mDNSlocal mDNSBool ShouldSuppressKnownAnswer(const CacheRecord *const ka, const AuthRecord *const rr)
{
// If RR signature is different, or data is different, then don't suppress our answer
if (!IdenticalResourceRecord(&ka->resrec, &rr->resrec)) return(mDNSfalse);
// If the requester's indicated TTL is less than half the real TTL,
// we need to give our answer before the requester's copy expires.
// If the requester's indicated TTL is at least half the real TTL,
// then we can suppress our answer this time.
// If the requester's indicated TTL is greater than the TTL we believe,
// then that's okay, and we don't need to do anything about it.
// (If two responders on the network are offering the same information,
// that's okay, and if they are offering the information with different TTLs,
// the one offering the lower TTL should defer to the one offering the higher TTL.)
return(mDNSBool)(ka->resrec.rroriginalttl >= rr->resrec.rroriginalttl / 2);
}
mDNSlocal void SetNextAnnounceProbeTime(mDNS *const m, const AuthRecord *const rr)
{
if (rr->resrec.RecordType == kDNSRecordTypeUnique)
{
if ((rr->LastAPTime + rr->ThisAPInterval) - m->timenow > mDNSPlatformOneSecond * 10)
{
LogMsg("SetNextAnnounceProbeTime: ProbeCount %d Next in %d %s", rr->ProbeCount, (rr->LastAPTime + rr->ThisAPInterval) - m->timenow, ARDisplayString(m, rr));
LogMsg("SetNextAnnounceProbeTime: m->SuppressProbes %d m->timenow %d diff %d", m->SuppressProbes, m->timenow, m->SuppressProbes - m->timenow);
}
if (m->NextScheduledProbe - (rr->LastAPTime + rr->ThisAPInterval) >= 0)
m->NextScheduledProbe = (rr->LastAPTime + rr->ThisAPInterval);
// Some defensive code:
// If (rr->LastAPTime + rr->ThisAPInterval) happens to be far in the past, we don't want to allow
// NextScheduledProbe to be set excessively in the past, because that can cause bad things to happen.
// See: <rdar://problem/7795434> mDNS: Sometimes advertising stops working and record interval is set to zero
if (m->NextScheduledProbe - m->timenow < 0)
m->NextScheduledProbe = m->timenow;
}
else if (rr->AnnounceCount && (ResourceRecordIsValidAnswer(rr) || rr->resrec.RecordType == kDNSRecordTypeDeregistering))
{
if (m->NextScheduledResponse - (rr->LastAPTime + rr->ThisAPInterval) >= 0)
m->NextScheduledResponse = (rr->LastAPTime + rr->ThisAPInterval);
}
}
mDNSlocal void InitializeLastAPTime(mDNS *const m, AuthRecord *const rr)
{
// For reverse-mapping Sleep Proxy PTR records, probe interval is one second
rr->ThisAPInterval = rr->AddressProxy.type ? mDNSPlatformOneSecond : DefaultAPIntervalForRecordType(rr->resrec.RecordType);
// * If this is a record type that's going to probe, then we use the m->SuppressProbes time.
// * Otherwise, if it's not going to probe, but m->SuppressProbes is set because we have other
// records that are going to probe, then we delay its first announcement so that it will
// go out synchronized with the first announcement for the other records that *are* probing.
// This is a minor performance tweak that helps keep groups of related records synchronized together.
// The addition of "interval / 2" is to make sure that, in the event that any of the probes are
// delayed by a few milliseconds, this announcement does not inadvertently go out *before* the probing is complete.
// When the probing is complete and those records begin to announce, these records will also be picked up and accelerated,
// because they will meet the criterion of being at least half-way to their scheduled announcement time.
// * If it's not going to probe and m->SuppressProbes is not already set then we should announce immediately.
if (rr->ProbeCount)
{
// If we have no probe suppression time set, or it is in the past, set it now
if (m->SuppressProbes == 0 || m->SuppressProbes - m->timenow < 0)
{
// To allow us to aggregate probes when a group of services are registered together,
// the first probe is delayed 1/4 second. This means the common-case behaviour is:
// 1/4 second wait; probe
// 1/4 second wait; probe
// 1/4 second wait; probe
// 1/4 second wait; announce (i.e. service is normally announced exactly one second after being registered)
m->SuppressProbes = NonZeroTime(m->timenow + DefaultProbeIntervalForTypeUnique/2 + mDNSRandom(DefaultProbeIntervalForTypeUnique/2));
// If we already have a *probe* scheduled to go out sooner, then use that time to get better aggregation
if (m->SuppressProbes - m->NextScheduledProbe >= 0)
m->SuppressProbes = NonZeroTime(m->NextScheduledProbe);
if (m->SuppressProbes - m->timenow < 0) // Make sure we don't set m->SuppressProbes excessively in the past
m->SuppressProbes = m->timenow;
// If we already have a *query* scheduled to go out sooner, then use that time to get better aggregation
if (m->SuppressProbes - m->NextScheduledQuery >= 0)
m->SuppressProbes = NonZeroTime(m->NextScheduledQuery);
if (m->SuppressProbes - m->timenow < 0) // Make sure we don't set m->SuppressProbes excessively in the past
m->SuppressProbes = m->timenow;
// except... don't expect to be able to send before the m->SuppressSending timer fires
if (m->SuppressSending && m->SuppressProbes - m->SuppressSending < 0)
m->SuppressProbes = NonZeroTime(m->SuppressSending);
if (m->SuppressProbes - m->timenow > mDNSPlatformOneSecond * 8)
{
LogMsg("InitializeLastAPTime ERROR m->SuppressProbes %d m->NextScheduledProbe %d m->NextScheduledQuery %d m->SuppressSending %d %d",
m->SuppressProbes - m->timenow,
m->NextScheduledProbe - m->timenow,
m->NextScheduledQuery - m->timenow,
m->SuppressSending,
m->SuppressSending - m->timenow);
m->SuppressProbes = NonZeroTime(m->timenow + DefaultProbeIntervalForTypeUnique/2 + mDNSRandom(DefaultProbeIntervalForTypeUnique/2));
}
}
rr->LastAPTime = m->SuppressProbes - rr->ThisAPInterval;
}
else if (m->SuppressProbes && m->SuppressProbes - m->timenow >= 0)
rr->LastAPTime = m->SuppressProbes - rr->ThisAPInterval + DefaultProbeIntervalForTypeUnique * DefaultProbeCountForTypeUnique + rr->ThisAPInterval / 2;
else
rr->LastAPTime = m->timenow - rr->ThisAPInterval;
// For reverse-mapping Sleep Proxy PTR records we don't want to start probing instantly -- we
// wait one second to give the client a chance to go to sleep, and then start our ARP/NDP probing.
// After three probes one second apart with no answer, we conclude the client is now sleeping
// and we can begin broadcasting our announcements to take over ownership of that IP address.
// If we don't wait for the client to go to sleep, then when the client sees our ARP Announcements there's a risk
// (depending on the OS and networking stack it's using) that it might interpret it as a conflict and change its IP address.
if (rr->AddressProxy.type) rr->LastAPTime = m->timenow;
// Unsolicited Neighbor Advertisements (RFC 2461 Section 7.2.6) give us fast address cache updating,
// but some older IPv6 clients get confused by them, so for now we don't send them. Without Unsolicited
// Neighbor Advertisements we have to rely on Neighbor Unreachability Detection instead, which is slower.
// Given this, we'll do our best to wake for existing IPv6 connections, but we don't want to encourage
// new ones for sleeping clients, so we'll we send deletions for our SPS clients' AAAA records.
if (m->KnownBugs & mDNS_KnownBug_LimitedIPv6)
if (rr->WakeUp.HMAC.l[0] && rr->resrec.rrtype == kDNSType_AAAA)
rr->LastAPTime = m->timenow - rr->ThisAPInterval + mDNSPlatformOneSecond * 10;
// Set LastMCTime to now, to inhibit multicast responses
// (no need to send additional multicast responses when we're announcing anyway)
rr->LastMCTime = m->timenow;
rr->LastMCInterface = mDNSInterfaceMark;
SetNextAnnounceProbeTime(m, rr);
}
mDNSlocal const domainname *SetUnicastTargetToHostName(mDNS *const m, AuthRecord *rr)
{
const domainname *target;
if (rr->AutoTarget)
{
// For autotunnel services pointing at our IPv6 ULA we don't need or want a NAT mapping, but for all other
// advertised services referencing our uDNS hostname, we want NAT mappings automatically created as appropriate,
// with the port number in our advertised SRV record automatically tracking the external mapped port.
DomainAuthInfo *AuthInfo = GetAuthInfoForName_internal(m, rr->resrec.name);
if (!AuthInfo || !AuthInfo->AutoTunnel) rr->AutoTarget = Target_AutoHostAndNATMAP;
}
target = GetServiceTarget(m, rr);
if (!target || target->c[0] == 0)
{
// defer registration until we've got a target
LogInfo("SetUnicastTargetToHostName No target for %s", ARDisplayString(m, rr));
rr->state = regState_NoTarget;
return mDNSNULL;
}
else
{
LogInfo("SetUnicastTargetToHostName target %##s for resource record %s", target->c, ARDisplayString(m,rr));
return target;
}
}
// Right now this only applies to mDNS (.local) services where the target host is always m->MulticastHostname
// Eventually we should unify this with GetServiceTarget() in uDNS.c
mDNSlocal void SetTargetToHostName(mDNS *const m, AuthRecord *const rr)
{
domainname *const target = GetRRDomainNameTarget(&rr->resrec);
const domainname *newname = &m->MulticastHostname;
if (!target) LogInfo("SetTargetToHostName: Don't know how to set the target of rrtype %s", DNSTypeName(rr->resrec.rrtype));
if (!(rr->ForceMCast || rr->ARType == AuthRecordLocalOnly || rr->ARType == AuthRecordP2P || IsLocalDomain(&rr->namestorage)))
{
const domainname *const n = SetUnicastTargetToHostName(m, rr);
if (n) newname = n;
else { target->c[0] = 0; SetNewRData(&rr->resrec, mDNSNULL, 0); return; }
}
if (target && SameDomainName(target, newname))
debugf("SetTargetToHostName: Target of %##s is already %##s", rr->resrec.name->c, target->c);
if (target && !SameDomainName(target, newname))
{
AssignDomainName(target, newname);
SetNewRData(&rr->resrec, mDNSNULL, 0); // Update rdlength, rdestimate, rdatahash
// If we're in the middle of probing this record, we need to start again,
// because changing its rdata may change the outcome of the tie-breaker.
// (If the record type is kDNSRecordTypeUnique (unconfirmed unique) then DefaultProbeCountForRecordType is non-zero.)
rr->ProbeCount = DefaultProbeCountForRecordType(rr->resrec.RecordType);
// If we've announced this record, we really should send a goodbye packet for the old rdata before
// changing to the new rdata. However, in practice, we only do SetTargetToHostName for unique records,
// so when we announce them we'll set the kDNSClass_UniqueRRSet and clear any stale data that way.
if (rr->RequireGoodbye && rr->resrec.RecordType == kDNSRecordTypeShared)
debugf("Have announced shared record %##s (%s) at least once: should have sent a goodbye packet before updating",
rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
rr->AnnounceCount = InitialAnnounceCount;
rr->RequireGoodbye = mDNSfalse;
InitializeLastAPTime(m, rr);
}
}
mDNSlocal void AcknowledgeRecord(mDNS *const m, AuthRecord *const rr)
{
if (rr->RecordCallback)
{
// CAUTION: MUST NOT do anything more with rr after calling rr->Callback(), because the client's callback function
// is allowed to do anything, including starting/stopping queries, registering/deregistering records, etc.
rr->Acknowledged = mDNStrue;
mDNS_DropLockBeforeCallback(); // Allow client to legally make mDNS API calls from the callback
rr->RecordCallback(m, rr, mStatus_NoError);
mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
}
}
mDNSexport void ActivateUnicastRegistration(mDNS *const m, AuthRecord *const rr)
{
// Make sure that we don't activate the SRV record and associated service records, if it is in
// NoTarget state. First time when a service is being instantiated, SRV record may be in NoTarget state.
// We should not activate any of the other reords (PTR, TXT) that are part of the service. When
// the target becomes available, the records will be reregistered.
if (rr->resrec.rrtype != kDNSType_SRV)
{
AuthRecord *srvRR = mDNSNULL;
if (rr->resrec.rrtype == kDNSType_PTR)
srvRR = rr->Additional1;
else if (rr->resrec.rrtype == kDNSType_TXT)
srvRR = rr->DependentOn;
if (srvRR)
{
if (srvRR->resrec.rrtype != kDNSType_SRV)
{
LogMsg("ActivateUnicastRegistration: ERROR!! Resource record %s wrong, expecting SRV type", ARDisplayString(m, srvRR));
}
else
{
LogInfo("ActivateUnicastRegistration: Found Service Record %s in state %d for %##s (%s)",
ARDisplayString(m, srvRR), srvRR->state, rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
rr->state = srvRR->state;
}
}
}
if (rr->state == regState_NoTarget)
{
LogInfo("ActivateUnicastRegistration record %s in regState_NoTarget, not activating", ARDisplayString(m, rr));
return;
}
// When we wake up from sleep, we call ActivateUnicastRegistration. It is possible that just before we went to sleep,
// the service/record was being deregistered. In that case, we should not try to register again. For the cases where
// the records are deregistered due to e.g., no target for the SRV record, we would have returned from above if it
// was already in NoTarget state. If it was in the process of deregistration but did not complete fully before we went
// to sleep, then it is okay to start in Pending state as we will go back to NoTarget state if we don't have a target.
if (rr->resrec.RecordType == kDNSRecordTypeDeregistering)
{
LogInfo("ActivateUnicastRegistration: Resource record %s, current state %d, moving to DeregPending", ARDisplayString(m, rr), rr->state);
rr->state = regState_DeregPending;
}
else
{
LogInfo("ActivateUnicastRegistration: Resource record %s, current state %d, moving to Pending", ARDisplayString(m, rr), rr->state);
rr->state = regState_Pending;
}
rr->ProbeCount = 0;
rr->AnnounceCount = 0;
rr->ThisAPInterval = INIT_RECORD_REG_INTERVAL;
rr->LastAPTime = m->timenow - rr->ThisAPInterval;
rr->expire = 0; // Forget about all the leases, start fresh
rr->uselease = mDNStrue;
rr->updateid = zeroID;
rr->SRVChanged = mDNSfalse;
rr->updateError = mStatus_NoError;
// RestartRecordGetZoneData calls this function whenever a new interface gets registered with core.
// The records might already be registered with the server and hence could have NAT state.
if (rr->NATinfo.clientContext)
{
mDNS_StopNATOperation_internal(m, &rr->NATinfo);
rr->NATinfo.clientContext = mDNSNULL;
}
if (rr->nta) { CancelGetZoneData(m, rr->nta); rr->nta = mDNSNULL; }
if (rr->tcp) { DisposeTCPConn(rr->tcp); rr->tcp = mDNSNULL; }
if (m->NextuDNSEvent - (rr->LastAPTime + rr->ThisAPInterval) >= 0)
m->NextuDNSEvent = (rr->LastAPTime + rr->ThisAPInterval);
}
// Two records qualify to be local duplicates if:
// (a) the RecordTypes are the same, or
// (b) one is Unique and the other Verified
// (c) either is in the process of deregistering
#define RecordLDT(A,B) ((A)->resrec.RecordType == (B)->resrec.RecordType || \
((A)->resrec.RecordType | (B)->resrec.RecordType) == (kDNSRecordTypeUnique | kDNSRecordTypeVerified) || \
((A)->resrec.RecordType == kDNSRecordTypeDeregistering || (B)->resrec.RecordType == kDNSRecordTypeDeregistering))
#define RecordIsLocalDuplicate(A,B) \
((A)->resrec.InterfaceID == (B)->resrec.InterfaceID && RecordLDT((A),(B)) && IdenticalResourceRecord(&(A)->resrec, &(B)->resrec))
mDNSlocal AuthRecord *CheckAuthIdenticalRecord(AuthHash *r, AuthRecord *rr)
{
AuthGroup *a;
AuthGroup **ag = &a;
AuthRecord **rp;
const mDNSu32 slot = AuthHashSlot(rr->resrec.name);
a = AuthGroupForRecord(r, slot, &rr->resrec);
if (!a) return mDNSNULL;
rp = &(*ag)->members;
while (*rp)
{
if (!RecordIsLocalDuplicate(*rp, rr))
rp=&(*rp)->next;
else
{
if ((*rp)->resrec.RecordType == kDNSRecordTypeDeregistering)
{
(*rp)->AnnounceCount = 0;
rp=&(*rp)->next;
}
else return *rp;
}
}
return (mDNSNULL);
}
mDNSlocal mDNSBool CheckAuthRecordConflict(AuthHash *r, AuthRecord *rr)
{
AuthGroup *a;
AuthGroup **ag = &a;
AuthRecord **rp;
const mDNSu32 slot = AuthHashSlot(rr->resrec.name);
a = AuthGroupForRecord(r, slot, &rr->resrec);
if (!a) return mDNSfalse;
rp = &(*ag)->members;
while (*rp)
{
const AuthRecord *s1 = rr->RRSet ? rr->RRSet : rr;
const AuthRecord *s2 = (*rp)->RRSet ? (*rp)->RRSet : *rp;
if (s1 != s2 && SameResourceRecordSignature((*rp), rr) && !IdenticalSameNameRecord(&(*rp)->resrec, &rr->resrec))
return mDNStrue;
else
rp=&(*rp)->next;
}
return (mDNSfalse);
}
// checks to see if "rr" is already present
mDNSlocal AuthRecord *CheckAuthSameRecord(AuthHash *r, AuthRecord *rr)
{
AuthGroup *a;
AuthGroup **ag = &a;
AuthRecord **rp;
const mDNSu32 slot = AuthHashSlot(rr->resrec.name);
a = AuthGroupForRecord(r, slot, &rr->resrec);
if (!a) return mDNSNULL;
rp = &(*ag)->members;
while (*rp)
{
if (*rp != rr)
rp=&(*rp)->next;
else
{
return *rp;
}
}
return (mDNSNULL);
}
// Exported so uDNS.c can call this
mDNSexport mStatus mDNS_Register_internal(mDNS *const m, AuthRecord *const rr)
{
domainname *target = GetRRDomainNameTarget(&rr->resrec);
AuthRecord *r;
AuthRecord **p = &m->ResourceRecords;
AuthRecord **d = &m->DuplicateRecords;
if ((mDNSs32)rr->resrec.rroriginalttl <= 0)
{ LogMsg("mDNS_Register_internal: TTL %X should be 1 - 0x7FFFFFFF %s", rr->resrec.rroriginalttl, ARDisplayString(m, rr)); return(mStatus_BadParamErr); }
if (!rr->resrec.RecordType)
{ LogMsg("mDNS_Register_internal: RecordType must be non-zero %s", ARDisplayString(m, rr)); return(mStatus_BadParamErr); }
if (m->ShutdownTime)
{ LogMsg("mDNS_Register_internal: Shutting down, can't register %s", ARDisplayString(m, rr)); return(mStatus_ServiceNotRunning); }
if (m->DivertMulticastAdvertisements && !AuthRecord_uDNS(rr))
{
mDNSInterfaceID previousID = rr->resrec.InterfaceID;
if (rr->resrec.InterfaceID == mDNSInterface_Any || rr->resrec.InterfaceID == mDNSInterface_P2P)
{
rr->resrec.InterfaceID = mDNSInterface_LocalOnly;
rr->ARType = AuthRecordLocalOnly;
}
if (rr->resrec.InterfaceID != mDNSInterface_LocalOnly)
{
NetworkInterfaceInfo *intf = FirstInterfaceForID(m, rr->resrec.InterfaceID);
if (intf && !intf->Advertise){ rr->resrec.InterfaceID = mDNSInterface_LocalOnly; rr->ARType = AuthRecordLocalOnly; }
}
if (rr->resrec.InterfaceID != previousID)
LogInfo("mDNS_Register_internal: Diverting record to local-only %s", ARDisplayString(m, rr));
}
if (RRLocalOnly(rr))
{
if (CheckAuthSameRecord(&m->rrauth, rr))
{
LogMsg("mDNS_Register_internal: ERROR!! Tried to register LocalOnly AuthRecord %p %##s (%s) that's already in the list",
rr, rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
return(mStatus_AlreadyRegistered);
}
}
else
{
while (*p && *p != rr) p=&(*p)->next;
if (*p)
{
LogMsg("mDNS_Register_internal: ERROR!! Tried to register AuthRecord %p %##s (%s) that's already in the list",
rr, rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
return(mStatus_AlreadyRegistered);
}
}
while (*d && *d != rr) d=&(*d)->next;
if (*d)
{
LogMsg("mDNS_Register_internal: ERROR!! Tried to register AuthRecord %p %##s (%s) that's already in the Duplicate list",
rr, rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
return(mStatus_AlreadyRegistered);
}
if (rr->DependentOn)
{
if (rr->resrec.RecordType == kDNSRecordTypeUnique)
rr->resrec.RecordType = kDNSRecordTypeVerified;
else
{
LogMsg("mDNS_Register_internal: ERROR! %##s (%s): rr->DependentOn && RecordType != kDNSRecordTypeUnique",
rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
return(mStatus_Invalid);
}
if (!(rr->DependentOn->resrec.RecordType & (kDNSRecordTypeUnique | kDNSRecordTypeVerified | kDNSRecordTypeKnownUnique)))
{
LogMsg("mDNS_Register_internal: ERROR! %##s (%s): rr->DependentOn->RecordType bad type %X",
rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype), rr->DependentOn->resrec.RecordType);
return(mStatus_Invalid);
}
}
// If this resource record is referencing a specific interface, make sure it exists.
// Skip checks for LocalOnly and P2P as they are not valid InterfaceIDs. Also, for scoped
// entries in /etc/hosts skip that check as that interface may not be valid at this time.
if (rr->resrec.InterfaceID && rr->ARType != AuthRecordLocalOnly && rr->ARType != AuthRecordP2P)
{
NetworkInterfaceInfo *intf = FirstInterfaceForID(m, rr->resrec.InterfaceID);
if (!intf)
{
debugf("mDNS_Register_internal: Bogus InterfaceID %p in resource record", rr->resrec.InterfaceID);
return(mStatus_BadReferenceErr);
}
}
rr->next = mDNSNULL;
// Field Group 1: The actual information pertaining to this resource record
// Set up by client prior to call
// Field Group 2: Persistent metadata for Authoritative Records
// rr->Additional1 = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client
// rr->Additional2 = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client
// rr->DependentOn = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client
// rr->RRSet = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client
// rr->Callback = already set in mDNS_SetupResourceRecord
// rr->Context = already set in mDNS_SetupResourceRecord
// rr->RecordType = already set in mDNS_SetupResourceRecord
// rr->HostTarget = set to mDNSfalse in mDNS_SetupResourceRecord; may be overridden by client
// rr->AllowRemoteQuery = set to mDNSfalse in mDNS_SetupResourceRecord; may be overridden by client
// Make sure target is not uninitialized data, or we may crash writing debugging log messages
if (rr->AutoTarget && target) target->c[0] = 0;
// Field Group 3: Transient state for Authoritative Records
rr->Acknowledged = mDNSfalse;
rr->ProbeCount = DefaultProbeCountForRecordType(rr->resrec.RecordType);
rr->AnnounceCount = InitialAnnounceCount;
rr->RequireGoodbye = mDNSfalse;
rr->AnsweredLocalQ = mDNSfalse;
rr->IncludeInProbe = mDNSfalse;
rr->ImmedUnicast = mDNSfalse;
rr->SendNSECNow = mDNSNULL;
rr->ImmedAnswer = mDNSNULL;
rr->ImmedAdditional = mDNSNULL;
rr->SendRNow = mDNSNULL;
rr->v4Requester = zerov4Addr;
rr->v6Requester = zerov6Addr;
rr->NextResponse = mDNSNULL;
rr->NR_AnswerTo = mDNSNULL;
rr->NR_AdditionalTo = mDNSNULL;
if (!rr->AutoTarget) InitializeLastAPTime(m, rr);
// rr->LastAPTime = Set for us in InitializeLastAPTime()
// rr->LastMCTime = Set for us in InitializeLastAPTime()
// rr->LastMCInterface = Set for us in InitializeLastAPTime()
rr->NewRData = mDNSNULL;
rr->newrdlength = 0;
rr->UpdateCallback = mDNSNULL;
rr->UpdateCredits = kMaxUpdateCredits;
rr->NextUpdateCredit = 0;
rr->UpdateBlocked = 0;
// For records we're holding as proxy (except reverse-mapping PTR records) two announcements is sufficient
if (rr->WakeUp.HMAC.l[0] && !rr->AddressProxy.type) rr->AnnounceCount = 2;
// Field Group 4: Transient uDNS state for Authoritative Records
rr->state = regState_Zero;
rr->uselease = 0;
rr->expire = 0;
rr->Private = 0;
rr->updateid = zeroID;
rr->zone = rr->resrec.name;
rr->nta = mDNSNULL;
rr->tcp = mDNSNULL;
rr->OrigRData = 0;
rr->OrigRDLen = 0;
rr->InFlightRData = 0;
rr->InFlightRDLen = 0;
rr->QueuedRData = 0;
rr->QueuedRDLen = 0;
//mDNSPlatformMemZero(&rr->NATinfo, sizeof(rr->NATinfo));
// We should be recording the actual internal port for this service record here. Once we initiate our NAT mapping
// request we'll subsequently overwrite srv.port with the allocated external NAT port -- potentially multiple
// times with different values if the external NAT port changes during the lifetime of the service registration.
//if (rr->resrec.rrtype == kDNSType_SRV) rr->NATinfo.IntPort = rr->resrec.rdata->u.srv.port;
// rr->resrec.interface = already set in mDNS_SetupResourceRecord
// rr->resrec.name->c = MUST be set by client
// rr->resrec.rrtype = already set in mDNS_SetupResourceRecord
// rr->resrec.rrclass = already set in mDNS_SetupResourceRecord
// rr->resrec.rroriginalttl = already set in mDNS_SetupResourceRecord
// rr->resrec.rdata = MUST be set by client, unless record type is CNAME or PTR and rr->HostTarget is set
// BIND named (name daemon) doesn't allow TXT records with zero-length rdata. This is strictly speaking correct,
// since RFC 1035 specifies a TXT record as "One or more <character-string>s", not "Zero or more <character-string>s".
// Since some legacy apps try to create zero-length TXT records, we'll silently correct it here.
if (rr->resrec.rrtype == kDNSType_TXT && rr->resrec.rdlength == 0) { rr->resrec.rdlength = 1; rr->resrec.rdata->u.txt.c[0] = 0; }
if (rr->AutoTarget)
{
SetTargetToHostName(m, rr); // Also sets rdlength and rdestimate for us, and calls InitializeLastAPTime();
#ifndef UNICAST_DISABLED
// If we have no target record yet, SetTargetToHostName will set rr->state == regState_NoTarget
// In this case we leave the record half-formed in the list, and later we'll remove it from the list and re-add it properly.
if (rr->state == regState_NoTarget)
{
// Initialize the target so that we don't crash while logging etc.
domainname *tar = GetRRDomainNameTarget(&rr->resrec);
if (tar) tar->c[0] = 0;
LogInfo("mDNS_Register_internal: record %s in NoTarget state", ARDisplayString(m, rr));
}
#endif
}
else
{
rr->resrec.rdlength = GetRDLength(&rr->resrec, mDNSfalse);
rr->resrec.rdestimate = GetRDLength(&rr->resrec, mDNStrue);
}
if (!ValidateDomainName(rr->resrec.name))
{ LogMsg("Attempt to register record with invalid name: %s", ARDisplayString(m, rr)); return(mStatus_Invalid); }
// Don't do this until *after* we've set rr->resrec.rdlength
if (!ValidateRData(rr->resrec.rrtype, rr->resrec.rdlength, rr->resrec.rdata))
{ LogMsg("Attempt to register record with invalid rdata: %s", ARDisplayString(m, rr)); return(mStatus_Invalid); }
rr->resrec.namehash = DomainNameHashValue(rr->resrec.name);
rr->resrec.rdatahash = target ? DomainNameHashValue(target) : RDataHashValue(&rr->resrec);
if (RRLocalOnly(rr))
{
// If this is supposed to be unique, make sure we don't have any name conflicts.
// If we found a conflict, we may still want to insert the record in the list but mark it appropriately
// (kDNSRecordTypeDeregistering) so that we deliver RMV events to the application. But this causes more
// complications and not clear whether there are any benefits. See rdar:9304275 for details.
// Hence, just bail out.
if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask)
{
if (CheckAuthRecordConflict(&m->rrauth, rr))
{
LogInfo("mDNS_Register_internal: Name conflict %s (%p), InterfaceID %p", ARDisplayString(m, rr), rr, rr->resrec.InterfaceID);
return mStatus_NameConflict;
}
}
}
// For uDNS records, we don't support duplicate checks at this time.
#ifndef UNICAST_DISABLED
if (AuthRecord_uDNS(rr))
{
if (!m->NewLocalRecords) m->NewLocalRecords = rr;
// When we called SetTargetToHostName, it may have caused mDNS_Register_internal to be re-entered, appending new
// records to the list, so we now need to update p to advance to the new end to the list before appending our new record.
// Note that for AutoTunnel this should never happen, but this check makes the code future-proof.
while (*p) p=&(*p)->next;
*p = rr;
if (rr->resrec.RecordType == kDNSRecordTypeUnique) rr->resrec.RecordType = kDNSRecordTypeVerified;
rr->ProbeCount = 0;
rr->AnnounceCount = 0;
if (rr->state != regState_NoTarget) ActivateUnicastRegistration(m, rr);
return(mStatus_NoError); // <--- Note: For unicast records, code currently bails out at this point
}
#endif
// Now that we've finished building our new record, make sure it's not identical to one we already have
if (RRLocalOnly(rr))
{
rr->ProbeCount = 0;
rr->AnnounceCount = 0;
r = CheckAuthIdenticalRecord(&m->rrauth, rr);
}
else
{
for (r = m->ResourceRecords; r; r=r->next)
if (RecordIsLocalDuplicate(r, rr))
{
if (r->resrec.RecordType == kDNSRecordTypeDeregistering) r->AnnounceCount = 0;
else break;
}
}
if (r)
{
debugf("mDNS_Register_internal:Adding to duplicate list %s", ARDisplayString(m,rr));
*d = rr;
// If the previous copy of this record is already verified unique,
// then indicate that we should move this record promptly to kDNSRecordTypeUnique state.
// Setting ProbeCount to zero will cause SendQueries() to advance this record to
// kDNSRecordTypeVerified state and call the client callback at the next appropriate time.
if (rr->resrec.RecordType == kDNSRecordTypeUnique && r->resrec.RecordType == kDNSRecordTypeVerified)
rr->ProbeCount = 0;
}
else
{
debugf("mDNS_Register_internal: Adding to active record list %s", ARDisplayString(m,rr));
if (RRLocalOnly(rr))
{
AuthGroup *ag;
ag = InsertAuthRecord(m, &m->rrauth, rr);
if (ag && !ag->NewLocalOnlyRecords) {
m->NewLocalOnlyRecords = mDNStrue;
ag->NewLocalOnlyRecords = rr;
}
// No probing for LocalOnly records, Acknowledge them right away
if (rr->resrec.RecordType == kDNSRecordTypeUnique) rr->resrec.RecordType = kDNSRecordTypeVerified;
AcknowledgeRecord(m, rr);
return(mStatus_NoError);
}
else
{
if (!m->NewLocalRecords) m->NewLocalRecords = rr;
*p = rr;
}
}
if (!AuthRecord_uDNS(rr)) // This check is superfluous, given that for unicast records we (currently) bail out above
{
// For records that are not going to probe, acknowledge them right away
if (rr->resrec.RecordType != kDNSRecordTypeUnique && rr->resrec.RecordType != kDNSRecordTypeDeregistering)
AcknowledgeRecord(m, rr);
// Adding a record may affect whether or not we should sleep
mDNS_UpdateAllowSleep(m);
}
return(mStatus_NoError);
}
mDNSlocal void RecordProbeFailure(mDNS *const m, const AuthRecord *const rr)
{
m->ProbeFailTime = m->timenow;
m->NumFailedProbes++;
// If we've had fifteen or more probe failures, rate-limit to one every five seconds.
// If a bunch of hosts have all been configured with the same name, then they'll all
// conflict and run through the same series of names: name-2, name-3, name-4, etc.,
// up to name-10. After that they'll start adding random increments in the range 1-100,
// so they're more likely to branch out in the available namespace and settle on a set of
// unique names quickly. If after five more tries the host is still conflicting, then we
// may have a serious problem, so we start rate-limiting so we don't melt down the network.
if (m->NumFailedProbes >= 15)
{
m->SuppressProbes = NonZeroTime(m->timenow + mDNSPlatformOneSecond * 5);
LogMsg("Excessive name conflicts (%lu) for %##s (%s); rate limiting in effect",
m->NumFailedProbes, rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
}
}
mDNSlocal void CompleteRDataUpdate(mDNS *const m, AuthRecord *const rr)
{
RData *OldRData = rr->resrec.rdata;
mDNSu16 OldRDLen = rr->resrec.rdlength;
SetNewRData(&rr->resrec, rr->NewRData, rr->newrdlength); // Update our rdata
rr->NewRData = mDNSNULL; // Clear the NewRData pointer ...
if (rr->UpdateCallback)
rr->UpdateCallback(m, rr, OldRData, OldRDLen); // ... and let the client know
}
// Note: mDNS_Deregister_internal can call a user callback, which may change the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
// Exported so uDNS.c can call this
mDNSexport mStatus mDNS_Deregister_internal(mDNS *const m, AuthRecord *const rr, mDNS_Dereg_type drt)
{
AuthRecord *r2;
mDNSu8 RecordType = rr->resrec.RecordType;
AuthRecord **p = &m->ResourceRecords; // Find this record in our list of active records
mDNSBool dupList = mDNSfalse;
if (RRLocalOnly(rr))
{
AuthGroup *a;
AuthGroup **ag = &a;
AuthRecord **rp;
const mDNSu32 slot = AuthHashSlot(rr->resrec.name);
a = AuthGroupForRecord(&m->rrauth, slot, &rr->resrec);
if (!a) return mDNSfalse;
rp = &(*ag)->members;
while (*rp && *rp != rr) rp=&(*rp)->next;
p = rp;
}
else
{
while (*p && *p != rr) p=&(*p)->next;
}
if (*p)
{
// We found our record on the main list. See if there are any duplicates that need special handling.
if (drt == mDNS_Dereg_conflict) // If this was a conflict, see that all duplicates get the same treatment
{
// Scan for duplicates of rr, and mark them for deregistration at the end of this routine, after we've finished
// deregistering rr. We need to do this scan *before* we give the client the chance to free and reuse the rr memory.
for (r2 = m->DuplicateRecords; r2; r2=r2->next) if (RecordIsLocalDuplicate(r2, rr)) r2->ProbeCount = 0xFF;
}
else
{
// Before we delete the record (and potentially send a goodbye packet)
// first see if we have a record on the duplicate list ready to take over from it.
AuthRecord **d = &m->DuplicateRecords;
while (*d && !RecordIsLocalDuplicate(*d, rr)) d=&(*d)->next;
if (*d)
{
AuthRecord *dup = *d;
debugf("mDNS_Register_internal: Duplicate record %p taking over from %p %##s (%s)",
dup, rr, rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
*d = dup->next; // Cut replacement record from DuplicateRecords list
if (RRLocalOnly(rr))
{
dup->next = mDNSNULL;
if (!InsertAuthRecord(m, &m->rrauth, dup)) LogMsg("mDNS_Deregister_internal: ERROR!! cannot insert %s", ARDisplayString(m, dup));
}
else
{
dup->next = rr->next; // And then...
rr->next = dup; // ... splice it in right after the record we're about to delete
}
dup->resrec.RecordType = rr->resrec.RecordType;
dup->ProbeCount = rr->ProbeCount;
dup->AnnounceCount = rr->AnnounceCount;
dup->RequireGoodbye = rr->RequireGoodbye;
dup->AnsweredLocalQ = rr->AnsweredLocalQ;
dup->ImmedAnswer = rr->ImmedAnswer;
dup->ImmedUnicast = rr->ImmedUnicast;
dup->ImmedAdditional = rr->ImmedAdditional;
dup->v4Requester = rr->v4Requester;
dup->v6Requester = rr->v6Requester;
dup->ThisAPInterval = rr->ThisAPInterval;
dup->LastAPTime = rr->LastAPTime;
dup->LastMCTime = rr->LastMCTime;
dup->LastMCInterface = rr->LastMCInterface;
dup->Private = rr->Private;
dup->state = rr->state;
rr->RequireGoodbye = mDNSfalse;
rr->AnsweredLocalQ = mDNSfalse;
}
}
}
else
{
// We didn't find our record on the main list; try the DuplicateRecords list instead.
p = &m->DuplicateRecords;
while (*p && *p != rr) p=&(*p)->next;
// If we found our record on the duplicate list, then make sure we don't send a goodbye for it
if (*p) { rr->RequireGoodbye = mDNSfalse; dupList = mDNStrue; }
if (*p) debugf("mDNS_Deregister_internal: Deleting DuplicateRecord %p %##s (%s)",
rr, rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
}
if (!*p)
{
// No need to log an error message if we already know this is a potentially repeated deregistration
if (drt != mDNS_Dereg_repeat)
LogMsg("mDNS_Deregister_internal: Record %p not found in list %s", rr, ARDisplayString(m,rr));
return(mStatus_BadReferenceErr);
}
// If this is a shared record and we've announced it at least once,
// we need to retract that announcement before we delete the record
// If this is a record (including mDNSInterface_LocalOnly records) for which we've given local-only answers then
// it's tempting to just do "AnswerAllLocalQuestionsWithLocalAuthRecord(m, rr, mDNSfalse)" here, but that would not not be safe.
// The AnswerAllLocalQuestionsWithLocalAuthRecord routine walks the question list invoking client callbacks, using the "m->CurrentQuestion"
// mechanism to cope with the client callback modifying the question list while that's happening.
// However, mDNS_Deregister could have been called from a client callback (e.g. from the domain enumeration callback FoundDomain)
// which means that the "m->CurrentQuestion" mechanism is already in use to protect that list, so we can't use it twice.
// More generally, if we invoke callbacks from within a client callback, then those callbacks could deregister other
// records, thereby invoking yet more callbacks, without limit.
// The solution is to defer delivering the "Remove" events until mDNS_Execute time, just like we do for sending
// actual goodbye packets.
#ifndef UNICAST_DISABLED
if (AuthRecord_uDNS(rr))
{
if (rr->RequireGoodbye)
{
if (rr->tcp) { DisposeTCPConn(rr->tcp); rr->tcp = mDNSNULL; }
rr->resrec.RecordType = kDNSRecordTypeDeregistering;
m->LocalRemoveEvents = mDNStrue;
uDNS_DeregisterRecord(m, rr);
// At this point unconditionally we bail out
// Either uDNS_DeregisterRecord will have completed synchronously, and called CompleteDeregistration,
// which calls us back here with RequireGoodbye set to false, or it will have initiated the deregistration
// process and will complete asynchronously. Either way we don't need to do anything more here.
return(mStatus_NoError);
}
// Sometimes the records don't complete proper deregistration i.e., don't wait for a response
// from the server. In that case, if the records have been part of a group update, clear the
// state here. Some recors e.g., AutoTunnel gets reused without ever being completely initialized
rr->updateid = zeroID;
// We defer cleaning up NAT state only after sending goodbyes. This is important because
// RecordRegistrationGotZoneData guards against creating NAT state if clientContext is non-NULL.
// This happens today when we turn on/off interface where we get multiple network transitions
// and RestartRecordGetZoneData triggers re-registration of the resource records even though
// they may be in Registered state which causes NAT information to be setup multiple times. Defering
// the cleanup here keeps clientContext non-NULL and hence prevents that. Note that cleaning up
// NAT state here takes care of the case where we did not send goodbyes at all.
if (rr->NATinfo.clientContext)
{
mDNS_StopNATOperation_internal(m, &rr->NATinfo);
rr->NATinfo.clientContext = mDNSNULL;
}
if (rr->nta) { CancelGetZoneData(m, rr->nta); rr->nta = mDNSNULL; }
if (rr->tcp) { DisposeTCPConn(rr->tcp); rr->tcp = mDNSNULL; }
}
#endif // UNICAST_DISABLED
if (RecordType == kDNSRecordTypeUnregistered)
LogMsg("mDNS_Deregister_internal: %s already marked kDNSRecordTypeUnregistered", ARDisplayString(m, rr));
else if (RecordType == kDNSRecordTypeDeregistering)
{
LogMsg("mDNS_Deregister_internal: %s already marked kDNSRecordTypeDeregistering", ARDisplayString(m, rr));
return(mStatus_BadReferenceErr);
}
// <rdar://problem/7457925> Local-only questions don't get remove events for unique records
// We may want to consider changing this code so that we generate local-only question "rmv"
// events (and maybe goodbye packets too) for unique records as well as for shared records
// Note: If we change the logic for this "if" statement, need to ensure that the code in
// CompleteDeregistration() sets the appropriate state variables to gaurantee that "else"
// clause will execute here and the record will be cut from the list.
if (rr->WakeUp.HMAC.l[0] ||
(RecordType == kDNSRecordTypeShared && (rr->RequireGoodbye || rr->AnsweredLocalQ)))
{
verbosedebugf("mDNS_Deregister_internal: Starting deregistration for %s", ARDisplayString(m, rr));
rr->resrec.RecordType = kDNSRecordTypeDeregistering;
rr->resrec.rroriginalttl = 0;
rr->AnnounceCount = rr->WakeUp.HMAC.l[0] ? WakeupCount : (drt == mDNS_Dereg_rapid) ? 1 : GoodbyeCount;
rr->ThisAPInterval = mDNSPlatformOneSecond * 2;
rr->LastAPTime = m->timenow - rr->ThisAPInterval;
m->LocalRemoveEvents = mDNStrue;
if (m->NextScheduledResponse - (m->timenow + mDNSPlatformOneSecond/10) >= 0)
m->NextScheduledResponse = (m->timenow + mDNSPlatformOneSecond/10);
}
else
{
if (!dupList && RRLocalOnly(rr))
{
AuthGroup *ag = RemoveAuthRecord(m, &m->rrauth, rr);
if (ag->NewLocalOnlyRecords == rr) ag->NewLocalOnlyRecords = rr->next;
}
else
{
*p = rr->next; // Cut this record from the list
if (m->NewLocalRecords == rr) m->NewLocalRecords = rr->next;
}
// If someone is about to look at this, bump the pointer forward
if (m->CurrentRecord == rr) m->CurrentRecord = rr->next;
rr->next = mDNSNULL;
// Should we generate local remove events here?
// i.e. something like:
// if (rr->AnsweredLocalQ) { AnswerAllLocalQuestionsWithLocalAuthRecord(m, rr, mDNSfalse); rr->AnsweredLocalQ = mDNSfalse; }
verbosedebugf("mDNS_Deregister_internal: Deleting record for %s", ARDisplayString(m, rr));
rr->resrec.RecordType = kDNSRecordTypeUnregistered;
if ((drt == mDNS_Dereg_conflict || drt == mDNS_Dereg_repeat) && RecordType == kDNSRecordTypeShared)
debugf("mDNS_Deregister_internal: Cannot have a conflict on a shared record! %##s (%s)",
rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
// If we have an update queued up which never executed, give the client a chance to free that memory
if (rr->NewRData) CompleteRDataUpdate(m, rr); // Update our rdata, clear the NewRData pointer, and return memory to the client
// CAUTION: MUST NOT do anything more with rr after calling rr->Callback(), because the client's callback function
// is allowed to do anything, including starting/stopping queries, registering/deregistering records, etc.
// In this case the likely client action to the mStatus_MemFree message is to free the memory,
// so any attempt to touch rr after this is likely to lead to a crash.
if (drt != mDNS_Dereg_conflict)
{
mDNS_DropLockBeforeCallback(); // Allow client to legally make mDNS API calls from the callback
LogInfo("mDNS_Deregister_internal: mStatus_MemFree for %s", ARDisplayString(m, rr));
if (rr->RecordCallback)
rr->RecordCallback(m, rr, mStatus_MemFree); // MUST NOT touch rr after this
mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
}
else
{
RecordProbeFailure(m, rr);
mDNS_DropLockBeforeCallback(); // Allow client to legally make mDNS API calls from the callback
if (rr->RecordCallback)
rr->RecordCallback(m, rr, mStatus_NameConflict); // MUST NOT touch rr after this
mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
// Now that we've finished deregistering rr, check our DuplicateRecords list for any that we marked previously.
// Note that with all the client callbacks going on, by the time we get here all the
// records we marked may have been explicitly deregistered by the client anyway.
r2 = m->DuplicateRecords;
while (r2)
{
if (r2->ProbeCount != 0xFF) r2 = r2->next;
else { mDNS_Deregister_internal(m, r2, mDNS_Dereg_conflict); r2 = m->DuplicateRecords; }
}
}
}
mDNS_UpdateAllowSleep(m);
return(mStatus_NoError);
}
// ***************************************************************************
#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - Packet Sending Functions
#endif
mDNSlocal void AddRecordToResponseList(AuthRecord ***nrpp, AuthRecord *rr, AuthRecord *add)
{
if (rr->NextResponse == mDNSNULL && *nrpp != &rr->NextResponse)
{
**nrpp = rr;
// NR_AdditionalTo must point to a record with NR_AnswerTo set (and not NR_AdditionalTo)
// If 'add' does not meet this requirement, then follow its NR_AdditionalTo pointer to a record that does
// The referenced record will definitely be acceptable (by recursive application of this rule)
if (add && add->NR_AdditionalTo) add = add->NR_AdditionalTo;
rr->NR_AdditionalTo = add;
*nrpp = &rr->NextResponse;
}
debugf("AddRecordToResponseList: %##s (%s) already in list", rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
}
mDNSlocal void AddAdditionalsToResponseList(mDNS *const m, AuthRecord *ResponseRecords, AuthRecord ***nrpp, const mDNSInterfaceID InterfaceID)
{
AuthRecord *rr, *rr2;
for (rr=ResponseRecords; rr; rr=rr->NextResponse) // For each record we plan to put
{
// (Note: This is an "if", not a "while". If we add a record, we'll find it again
// later in the "for" loop, and we will follow further "additional" links then.)
if (rr->Additional1 && ResourceRecordIsValidInterfaceAnswer(rr->Additional1, InterfaceID))
AddRecordToResponseList(nrpp, rr->Additional1, rr);
if (rr->Additional2 && ResourceRecordIsValidInterfaceAnswer(rr->Additional2, InterfaceID))
AddRecordToResponseList(nrpp, rr->Additional2, rr);
// For SRV records, automatically add the Address record(s) for the target host
if (rr->resrec.rrtype == kDNSType_SRV)
{
for (rr2=m->ResourceRecords; rr2; rr2=rr2->next) // Scan list of resource records
if (RRTypeIsAddressType(rr2->resrec.rrtype) && // For all address records (A/AAAA) ...
ResourceRecordIsValidInterfaceAnswer(rr2, InterfaceID) && // ... which are valid for answer ...
rr->resrec.rdatahash == rr2->resrec.namehash && // ... whose name is the name of the SRV target
SameDomainName(&rr->resrec.rdata->u.srv.target, rr2->resrec.name))
AddRecordToResponseList(nrpp, rr2, rr);
}
else if (RRTypeIsAddressType(rr->resrec.rrtype)) // For A or AAAA, put counterpart as additional
{
for (rr2=m->ResourceRecords; rr2; rr2=rr2->next) // Scan list of resource records
if (RRTypeIsAddressType(rr2->resrec.rrtype) && // For all address records (A/AAAA) ...
ResourceRecordIsValidInterfaceAnswer(rr2, InterfaceID) && // ... which are valid for answer ...
rr->resrec.namehash == rr2->resrec.namehash && // ... and have the same name
SameDomainName(rr->resrec.name, rr2->resrec.name))
AddRecordToResponseList(nrpp, rr2, rr);
}
else if (rr->resrec.rrtype == kDNSType_PTR) // For service PTR, see if we want to add DeviceInfo record
{
if (ResourceRecordIsValidInterfaceAnswer(&m->DeviceInfo, InterfaceID) &&
SameDomainLabel(rr->resrec.rdata->u.name.c, m->DeviceInfo.resrec.name->c))
AddRecordToResponseList(nrpp, &m->DeviceInfo, rr);
}
}
}
mDNSlocal void SendDelayedUnicastResponse(mDNS *const m, const mDNSAddr *const dest, const mDNSInterfaceID InterfaceID)
{
AuthRecord *rr;
AuthRecord *ResponseRecords = mDNSNULL;
AuthRecord **nrp = &ResponseRecords;
NetworkInterfaceInfo *intf = FirstInterfaceForID(m, InterfaceID);
// Make a list of all our records that need to be unicast to this destination
for (rr = m->ResourceRecords; rr; rr=rr->next)
{
// If we find we can no longer unicast this answer, clear ImmedUnicast
if (rr->ImmedAnswer == mDNSInterfaceMark ||
mDNSSameIPv4Address(rr->v4Requester, onesIPv4Addr) ||
mDNSSameIPv6Address(rr->v6Requester, onesIPv6Addr) )
rr->ImmedUnicast = mDNSfalse;
if (rr->ImmedUnicast && rr->ImmedAnswer == InterfaceID)
{
if ((dest->type == mDNSAddrType_IPv4 && mDNSSameIPv4Address(rr->v4Requester, dest->ip.v4)) ||
(dest->type == mDNSAddrType_IPv6 && mDNSSameIPv6Address(rr->v6Requester, dest->ip.v6)))
{
rr->ImmedAnswer = mDNSNULL; // Clear the state fields
rr->ImmedUnicast = mDNSfalse;
rr->v4Requester = zerov4Addr;
rr->v6Requester = zerov6Addr;
// Only sent records registered for P2P over P2P interfaces
if (intf && !mDNSPlatformValidRecordForInterface(rr, intf))
{
LogInfo("SendDelayedUnicastResponse: Not sending %s, on %s", ARDisplayString(m, rr), InterfaceNameForID(m, InterfaceID));
continue;
}
if (rr->NextResponse == mDNSNULL && nrp != &rr->NextResponse) // rr->NR_AnswerTo
{ rr->NR_AnswerTo = (mDNSu8*)~0; *nrp = rr; nrp = &rr->NextResponse; }
}
}
}
AddAdditionalsToResponseList(m, ResponseRecords, &nrp, InterfaceID);
while (ResponseRecords)
{
mDNSu8 *responseptr = m->omsg.data;
mDNSu8 *newptr;
InitializeDNSMessage(&m->omsg.h, zeroID, ResponseFlags);
// Put answers in the packet
while (ResponseRecords && ResponseRecords->NR_AnswerTo)
{
rr = ResponseRecords;
if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask)
rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the cache flush bit so PutResourceRecord will set it
newptr = PutResourceRecord(&m->omsg, responseptr, &m->omsg.h.numAnswers, &rr->resrec);
rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet; // Make sure to clear cache flush bit back to normal state
if (!newptr && m->omsg.h.numAnswers) break; // If packet full, send it now
if (newptr) responseptr = newptr;
ResponseRecords = rr->NextResponse;
rr->NextResponse = mDNSNULL;
rr->NR_AnswerTo = mDNSNULL;
rr->NR_AdditionalTo = mDNSNULL;
rr->RequireGoodbye = mDNStrue;
}
// Add additionals, if there's space
while (ResponseRecords && !ResponseRecords->NR_AnswerTo)
{
rr = ResponseRecords;
if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask)
rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the cache flush bit so PutResourceRecord will set it
newptr = PutResourceRecord(&m->omsg, responseptr, &m->omsg.h.numAdditionals, &rr->resrec);
rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet; // Make sure to clear cache flush bit back to normal state
if (newptr) responseptr = newptr;
if (newptr && m->omsg.h.numAnswers) rr->RequireGoodbye = mDNStrue;
else if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) rr->ImmedAnswer = mDNSInterfaceMark;
ResponseRecords = rr->NextResponse;
rr->NextResponse = mDNSNULL;
rr->NR_AnswerTo = mDNSNULL;
rr->NR_AdditionalTo = mDNSNULL;
}
if (m->omsg.h.numAnswers)
mDNSSendDNSMessage(m, &m->omsg, responseptr, InterfaceID, mDNSNULL, dest, MulticastDNSPort, mDNSNULL, mDNSNULL);
}
}
// CompleteDeregistration guarantees that on exit the record will have been cut from the m->ResourceRecords list
// and the client's mStatus_MemFree callback will have been invoked
mDNSexport void CompleteDeregistration(mDNS *const m, AuthRecord *rr)
{
LogInfo("CompleteDeregistration: called for Resource record %s", ARDisplayString(m, rr));
// Clearing rr->RequireGoodbye signals mDNS_Deregister_internal() that
// it should go ahead and immediately dispose of this registration
rr->resrec.RecordType = kDNSRecordTypeShared;
rr->RequireGoodbye = mDNSfalse;
rr->WakeUp.HMAC = zeroEthAddr;
if (rr->AnsweredLocalQ) { AnswerAllLocalQuestionsWithLocalAuthRecord(m, rr, mDNSfalse); rr->AnsweredLocalQ = mDNSfalse; }
mDNS_Deregister_internal(m, rr, mDNS_Dereg_normal); // Don't touch rr after this
}
// DiscardDeregistrations is used on shutdown and sleep to discard (forcibly and immediately)
// any deregistering records that remain in the m->ResourceRecords list.
// DiscardDeregistrations calls mDNS_Deregister_internal which can call a user callback,
// which may change the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSlocal void DiscardDeregistrations(mDNS *const m)
{
if (m->CurrentRecord)
LogMsg("DiscardDeregistrations ERROR m->CurrentRecord already set %s", ARDisplayString(m, m->CurrentRecord));
m->CurrentRecord = m->ResourceRecords;
while (m->CurrentRecord)
{
AuthRecord *rr = m->CurrentRecord;
if (!AuthRecord_uDNS(rr) && rr->resrec.RecordType == kDNSRecordTypeDeregistering)
CompleteDeregistration(m, rr); // Don't touch rr after this
else
m->CurrentRecord = rr->next;
}
}
mDNSlocal mStatus GetLabelDecimalValue(const mDNSu8 *const src, mDNSu8 *dst)
{
int i, val = 0;
if (src[0] < 1 || src[0] > 3) return(mStatus_Invalid);
for (i=1; i<=src[0]; i++)
{
if (src[i] < '0' || src[i] > '9') return(mStatus_Invalid);
val = val * 10 + src[i] - '0';
}
if (val > 255) return(mStatus_Invalid);
*dst = (mDNSu8)val;
return(mStatus_NoError);
}
mDNSlocal mStatus GetIPv4FromName(mDNSAddr *const a, const domainname *const name)
{
int skip = CountLabels(name) - 6;
if (skip < 0) { LogMsg("GetIPFromName: Need six labels in IPv4 reverse mapping name %##s", name); return mStatus_Invalid; }
if (GetLabelDecimalValue(SkipLeadingLabels(name, skip+3)->c, &a->ip.v4.b[0]) ||
GetLabelDecimalValue(SkipLeadingLabels(name, skip+2)->c, &a->ip.v4.b[1]) ||
GetLabelDecimalValue(SkipLeadingLabels(name, skip+1)->c, &a->ip.v4.b[2]) ||
GetLabelDecimalValue(SkipLeadingLabels(name, skip+0)->c, &a->ip.v4.b[3])) return mStatus_Invalid;
a->type = mDNSAddrType_IPv4;
return(mStatus_NoError);
}
#define HexVal(X) ( ((X) >= '0' && (X) <= '9') ? ((X) - '0' ) : \
((X) >= 'A' && (X) <= 'F') ? ((X) - 'A' + 10) : \
((X) >= 'a' && (X) <= 'f') ? ((X) - 'a' + 10) : -1)
mDNSlocal mStatus GetIPv6FromName(mDNSAddr *const a, const domainname *const name)
{
int i, h, l;
const domainname *n;
int skip = CountLabels(name) - 34;
if (skip < 0) { LogMsg("GetIPFromName: Need 34 labels in IPv6 reverse mapping name %##s", name); return mStatus_Invalid; }
n = SkipLeadingLabels(name, skip);
for (i=0; i<16; i++)
{
if (n->c[0] != 1) return mStatus_Invalid;
l = HexVal(n->c[1]);
n = (const domainname *)(n->c + 2);
if (n->c[0] != 1) return mStatus_Invalid;
h = HexVal(n->c[1]);
n = (const domainname *)(n->c + 2);
if (l<0 || h<0) return mStatus_Invalid;
a->ip.v6.b[15-i] = (mDNSu8)((h << 4) | l);
}
a->type = mDNSAddrType_IPv6;
return(mStatus_NoError);
}
mDNSlocal mDNSs32 ReverseMapDomainType(const domainname *const name)
{
int skip = CountLabels(name) - 2;
if (skip >= 0)
{
const domainname *suffix = SkipLeadingLabels(name, skip);
if (SameDomainName(suffix, (const domainname*)"\x7" "in-addr" "\x4" "arpa")) return mDNSAddrType_IPv4;
if (SameDomainName(suffix, (const domainname*)"\x3" "ip6" "\x4" "arpa")) return mDNSAddrType_IPv6;
}
return(mDNSAddrType_None);
}
mDNSlocal void SendARP(mDNS *const m, const mDNSu8 op, const AuthRecord *const rr,
const mDNSv4Addr *const spa, const mDNSEthAddr *const tha, const mDNSv4Addr *const tpa, const mDNSEthAddr *const dst)
{
int i;
mDNSu8 *ptr = m->omsg.data;
NetworkInterfaceInfo *intf = FirstInterfaceForID(m, rr->resrec.InterfaceID);
if (!intf) { LogMsg("SendARP: No interface with InterfaceID %p found %s", rr->resrec.InterfaceID, ARDisplayString(m,rr)); return; }
// 0x00 Destination address
for (i=0; i<6; i++) *ptr++ = dst->b[i];
// 0x06 Source address (Note: Since we don't currently set the BIOCSHDRCMPLT option, BPF will fill in the real interface address for us)
for (i=0; i<6; i++) *ptr++ = intf->MAC.b[0];
// 0x0C ARP Ethertype (0x0806)
*ptr++ = 0x08; *ptr++ = 0x06;
// 0x0E ARP header
*ptr++ = 0x00; *ptr++ = 0x01; // Hardware address space; Ethernet = 1
*ptr++ = 0x08; *ptr++ = 0x00; // Protocol address space; IP = 0x0800
*ptr++ = 6; // Hardware address length
*ptr++ = 4; // Protocol address length
*ptr++ = 0x00; *ptr++ = op; // opcode; Request = 1, Response = 2
// 0x16 Sender hardware address (our MAC address)
for (i=0; i<6; i++) *ptr++ = intf->MAC.b[i];
// 0x1C Sender protocol address
for (i=0; i<4; i++) *ptr++ = spa->b[i];
// 0x20 Target hardware address
for (i=0; i<6; i++) *ptr++ = tha->b[i];
// 0x26 Target protocol address
for (i=0; i<4; i++) *ptr++ = tpa->b[i];
// 0x2A Total ARP Packet length 42 bytes
mDNSPlatformSendRawPacket(m->omsg.data, ptr, rr->resrec.InterfaceID);
}
mDNSlocal mDNSu16 CheckSum(const void *const data, mDNSs32 length, mDNSu32 sum)
{
const mDNSu16 *ptr = data;
while (length > 0) { length -= 2; sum += *ptr++; }
sum = (sum & 0xFFFF) + (sum >> 16);
sum = (sum & 0xFFFF) + (sum >> 16);
return(sum != 0xFFFF ? sum : 0);
}
mDNSlocal mDNSu16 IPv6CheckSum(const mDNSv6Addr *const src, const mDNSv6Addr *const dst, const mDNSu8 protocol, const void *const data, const mDNSu32 length)
{
IPv6PseudoHeader ph;
ph.src = *src;
ph.dst = *dst;
ph.len.b[0] = length >> 24;
ph.len.b[1] = length >> 16;
ph.len.b[2] = length >> 8;
ph.len.b[3] = length;
ph.pro.b[0] = 0;
ph.pro.b[1] = 0;
ph.pro.b[2] = 0;
ph.pro.b[3] = protocol;
return CheckSum(&ph, sizeof(ph), CheckSum(data, length, 0));
}
mDNSlocal void SendNDP(mDNS *const m, const mDNSu8 op, const mDNSu8 flags, const AuthRecord *const rr,
const mDNSv6Addr *const spa, const mDNSEthAddr *const tha, const mDNSv6Addr *const tpa, const mDNSEthAddr *const dst)
{
int i;
mDNSOpaque16 checksum;
mDNSu8 *ptr = m->omsg.data;
// Some recipient hosts seem to ignore Neighbor Solicitations if the IPv6-layer destination address is not the
// appropriate IPv6 solicited node multicast address, so we use that IPv6-layer destination address, even though
// at the Ethernet-layer we unicast the packet to the intended target, to avoid wasting network bandwidth.
const mDNSv6Addr mc = { { 0xFF,0x02,0x00,0x00, 0,0,0,0, 0,0,0,1, 0xFF,tpa->b[0xD],tpa->b[0xE],tpa->b[0xF] } };
const mDNSv6Addr *const v6dst = (op == NDP_Sol) ? &mc : tpa;
NetworkInterfaceInfo *intf = FirstInterfaceForID(m, rr->resrec.InterfaceID);
if (!intf) { LogMsg("SendNDP: No interface with InterfaceID %p found %s", rr->resrec.InterfaceID, ARDisplayString(m,rr)); return; }
// 0x00 Destination address
for (i=0; i<6; i++) *ptr++ = dst->b[i];
// Right now we only send Neighbor Solicitations to verify whether the host we're proxying for has gone to sleep yet.
// Since we know who we're looking for, we send it via Ethernet-layer unicast, rather than bothering every host on the
// link with a pointless link-layer multicast.
// Should we want to send traditional Neighbor Solicitations in the future, where we really don't know in advance what
// Ethernet-layer address we're looking for, we'll need to send to the appropriate Ethernet-layer multicast address:
// *ptr++ = 0x33;
// *ptr++ = 0x33;
// *ptr++ = 0xFF;
// *ptr++ = tpa->b[0xD];
// *ptr++ = tpa->b[0xE];
// *ptr++ = tpa->b[0xF];
// 0x06 Source address (Note: Since we don't currently set the BIOCSHDRCMPLT option, BPF will fill in the real interface address for us)
for (i=0; i<6; i++) *ptr++ = (tha ? *tha : intf->MAC).b[i];
// 0x0C IPv6 Ethertype (0x86DD)
*ptr++ = 0x86; *ptr++ = 0xDD;
// 0x0E IPv6 header
*ptr++ = 0x60; *ptr++ = 0x00; *ptr++ = 0x00; *ptr++ = 0x00; // Version, Traffic Class, Flow Label
*ptr++ = 0x00; *ptr++ = 0x20; // Length
*ptr++ = 0x3A; // Protocol == ICMPv6
*ptr++ = 0xFF; // Hop Limit
// 0x16 Sender IPv6 address
for (i=0; i<16; i++) *ptr++ = spa->b[i];
// 0x26 Destination IPv6 address
for (i=0; i<16; i++) *ptr++ = v6dst->b[i];
// 0x36 NDP header
*ptr++ = op; // 0x87 == Neighbor Solicitation, 0x88 == Neighbor Advertisement
*ptr++ = 0x00; // Code
*ptr++ = 0x00; *ptr++ = 0x00; // Checksum placeholder (0x38, 0x39)
*ptr++ = flags;
*ptr++ = 0x00; *ptr++ = 0x00; *ptr++ = 0x00;
if (op == NDP_Sol) // Neighbor Solicitation. The NDP "target" is the address we seek.
{
// 0x3E NDP target.
for (i=0; i<16; i++) *ptr++ = tpa->b[i];
// 0x4E Source Link-layer Address
// <http://www.ietf.org/rfc/rfc2461.txt>
// MUST NOT be included when the source IP address is the unspecified address.
// Otherwise, on link layers that have addresses this option MUST be included
// in multicast solicitations and SHOULD be included in unicast solicitations.
if (!mDNSIPv6AddressIsZero(*spa))
{
*ptr++ = NDP_SrcLL; // Option Type 1 == Source Link-layer Address
*ptr++ = 0x01; // Option length 1 (in units of 8 octets)
for (i=0; i<6; i++) *ptr++ = (tha ? *tha : intf->MAC).b[i];
}
}
else // Neighbor Advertisement. The NDP "target" is the address we're giving information about.
{
// 0x3E NDP target.
for (i=0; i<16; i++) *ptr++ = spa->b[i];
// 0x4E Target Link-layer Address
*ptr++ = NDP_TgtLL; // Option Type 2 == Target Link-layer Address
*ptr++ = 0x01; // Option length 1 (in units of 8 octets)
for (i=0; i<6; i++) *ptr++ = (tha ? *tha : intf->MAC).b[i];
}
// 0x4E or 0x56 Total NDP Packet length 78 or 86 bytes
m->omsg.data[0x13] = ptr - &m->omsg.data[0x36]; // Compute actual length
checksum.NotAnInteger = ~IPv6CheckSum(spa, v6dst, 0x3A, &m->omsg.data[0x36], m->omsg.data[0x13]);
m->omsg.data[0x38] = checksum.b[0];
m->omsg.data[0x39] = checksum.b[1];
mDNSPlatformSendRawPacket(m->omsg.data, ptr, rr->resrec.InterfaceID);
}
mDNSlocal void SetupOwnerOpt(const mDNS *const m, const NetworkInterfaceInfo *const intf, rdataOPT *const owner)
{
owner->u.owner.vers = 0;
owner->u.owner.seq = m->SleepSeqNum;
owner->u.owner.HMAC = m->PrimaryMAC;
owner->u.owner.IMAC = intf->MAC;
owner->u.owner.password = zeroEthAddr;
// Don't try to compute the optlen until *after* we've set up the data fields
// Right now the DNSOpt_Owner_Space macro does not depend on the owner->u.owner being set up correctly, but in the future it might
owner->opt = kDNSOpt_Owner;
owner->optlen = DNSOpt_Owner_Space(&m->PrimaryMAC, &intf->MAC) - 4;
}
mDNSlocal void GrantUpdateCredit(AuthRecord *rr)
{
if (++rr->UpdateCredits >= kMaxUpdateCredits) rr->NextUpdateCredit = 0;
else rr->NextUpdateCredit = NonZeroTime(rr->NextUpdateCredit + kUpdateCreditRefreshInterval);
}
// Note about acceleration of announcements to facilitate automatic coalescing of
// multiple independent threads of announcements into a single synchronized thread:
// The announcements in the packet may be at different stages of maturity;
// One-second interval, two-second interval, four-second interval, and so on.
// After we've put in all the announcements that are due, we then consider
// whether there are other nearly-due announcements that are worth accelerating.
// To be eligible for acceleration, a record MUST NOT be older (further along
// its timeline) than the most mature record we've already put in the packet.
// In other words, younger records can have their timelines accelerated to catch up
// with their elder bretheren; this narrows the age gap and helps them eventually get in sync.
// Older records cannot have their timelines accelerated; this would just widen
// the gap between them and their younger bretheren and get them even more out of sync.
// Note: SendResponses calls mDNS_Deregister_internal which can call a user callback, which may change
// the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSlocal void SendResponses(mDNS *const m)
{
int pktcount = 0;
AuthRecord *rr, *r2;
mDNSs32 maxExistingAnnounceInterval = 0;
const NetworkInterfaceInfo *intf = GetFirstActiveInterface(m->HostInterfaces);
m->NextScheduledResponse = m->timenow + 0x78000000;
if (m->SleepState == SleepState_Transferring) RetrySPSRegistrations(m);
for (rr = m->ResourceRecords; rr; rr=rr->next)
if (rr->ImmedUnicast)
{
mDNSAddr v4 = { mDNSAddrType_IPv4, {{{0}}} };
mDNSAddr v6 = { mDNSAddrType_IPv6, {{{0}}} };
v4.ip.v4 = rr->v4Requester;
v6.ip.v6 = rr->v6Requester;
if (!mDNSIPv4AddressIsZero(rr->v4Requester)) SendDelayedUnicastResponse(m, &v4, rr->ImmedAnswer);
if (!mDNSIPv6AddressIsZero(rr->v6Requester)) SendDelayedUnicastResponse(m, &v6, rr->ImmedAnswer);
if (rr->ImmedUnicast)
{
LogMsg("SendResponses: ERROR: rr->ImmedUnicast still set: %s", ARDisplayString(m, rr));
rr->ImmedUnicast = mDNSfalse;
}
}
// ***
// *** 1. Setup: Set the SendRNow and ImmedAnswer fields to indicate which interface(s) the records need to be sent on
// ***
// Run through our list of records, and decide which ones we're going to announce on all interfaces
for (rr = m->ResourceRecords; rr; rr=rr->next)
{
while (rr->NextUpdateCredit && m->timenow - rr->NextUpdateCredit >= 0) GrantUpdateCredit(rr);
if (TimeToAnnounceThisRecord(rr, m->timenow))
{
if (rr->resrec.RecordType == kDNSRecordTypeDeregistering)
{
if (!rr->WakeUp.HMAC.l[0])
{
if (rr->AnnounceCount) rr->ImmedAnswer = mDNSInterfaceMark; // Send goodbye packet on all interfaces
}
else
{
LogSPS("SendResponses: Sending wakeup %2d for %.6a %s", rr->AnnounceCount-3, &rr->WakeUp.IMAC, ARDisplayString(m, rr));
SendWakeup(m, rr->resrec.InterfaceID, &rr->WakeUp.IMAC, &rr->WakeUp.password);
for (r2 = rr; r2; r2=r2->next)
if (r2->AnnounceCount && r2->resrec.InterfaceID == rr->resrec.InterfaceID && mDNSSameEthAddress(&r2->WakeUp.IMAC, &rr->WakeUp.IMAC))
{
// For now we only want to send a single Unsolicited Neighbor Advertisement restoring the address to the original
// owner, because these packets can cause some IPv6 stacks to falsely conclude that there's an address conflict.
if (r2->AddressProxy.type == mDNSAddrType_IPv6 && r2->AnnounceCount == WakeupCount)
{
LogSPS("NDP Announcement %2d Releasing traffic for H-MAC %.6a I-MAC %.6a %s",
r2->AnnounceCount-3, &r2->WakeUp.HMAC, &r2->WakeUp.IMAC, ARDisplayString(m,r2));
SendNDP(m, NDP_Adv, NDP_Override, r2, &r2->AddressProxy.ip.v6, &r2->WakeUp.IMAC, &AllHosts_v6, &AllHosts_v6_Eth);
}
r2->LastAPTime = m->timenow;
// After 15 wakeups without success (maybe host has left the network) send three goodbyes instead
if (--r2->AnnounceCount <= GoodbyeCount) r2->WakeUp.HMAC = zeroEthAddr;
}
}
}
else if (ResourceRecordIsValidAnswer(rr))
{
if (rr->AddressProxy.type)
{
rr->AnnounceCount--;
rr->ThisAPInterval *= 2;
rr->LastAPTime = m->timenow;
if (rr->AddressProxy.type == mDNSAddrType_IPv4)
{
LogSPS("ARP Announcement %2d Capturing traffic for H-MAC %.6a I-MAC %.6a %s",
rr->AnnounceCount, &rr->WakeUp.HMAC, &rr->WakeUp.IMAC, ARDisplayString(m,rr));
SendARP(m, 1, rr, &rr->AddressProxy.ip.v4, &zeroEthAddr, &rr->AddressProxy.ip.v4, &onesEthAddr);
}
else if (rr->AddressProxy.type == mDNSAddrType_IPv6)
{
LogSPS("NDP Announcement %2d Capturing traffic for H-MAC %.6a I-MAC %.6a %s",
rr->AnnounceCount, &rr->WakeUp.HMAC, &rr->WakeUp.IMAC, ARDisplayString(m,rr));
SendNDP(m, NDP_Adv, NDP_Override, rr, &rr->AddressProxy.ip.v6, mDNSNULL, &AllHosts_v6, &AllHosts_v6_Eth);
}
}
else
{
rr->ImmedAnswer = mDNSInterfaceMark; // Send on all interfaces
if (maxExistingAnnounceInterval < rr->ThisAPInterval)
maxExistingAnnounceInterval = rr->ThisAPInterval;
if (rr->UpdateBlocked) rr->UpdateBlocked = 0;
}
}
}
}
// Any interface-specific records we're going to send are marked as being sent on all appropriate interfaces (which is just one)
// Eligible records that are more than half-way to their announcement time are accelerated
for (rr = m->ResourceRecords; rr; rr=rr->next)
if ((rr->resrec.InterfaceID && rr->ImmedAnswer) ||
(rr->ThisAPInterval <= maxExistingAnnounceInterval &&
TimeToAnnounceThisRecord(rr, m->timenow + rr->ThisAPInterval/2) &&
!rr->AddressProxy.type && // Don't include ARP Annoucements when considering which records to accelerate
ResourceRecordIsValidAnswer(rr)))
rr->ImmedAnswer = mDNSInterfaceMark; // Send on all interfaces
// When sending SRV records (particularly when announcing a new service) automatically add related Address record(s) as additionals
// Note: Currently all address records are interface-specific, so it's safe to set ImmedAdditional to their InterfaceID,
// which will be non-null. If by some chance there is an address record that's not interface-specific (should never happen)
// then all that means is that it won't get sent -- which would not be the end of the world.
for (rr = m->ResourceRecords; rr; rr=rr->next)
{
if (rr->ImmedAnswer && rr->resrec.rrtype == kDNSType_SRV)
for (r2=m->ResourceRecords; r2; r2=r2->next) // Scan list of resource records
if (RRTypeIsAddressType(r2->resrec.rrtype) && // For all address records (A/AAAA) ...
ResourceRecordIsValidAnswer(r2) && // ... which are valid for answer ...
rr->LastMCTime - r2->LastMCTime >= 0 && // ... which we have not sent recently ...
rr->resrec.rdatahash == r2->resrec.namehash && // ... whose name is the name of the SRV target
SameDomainName(&rr->resrec.rdata->u.srv.target, r2->resrec.name) &&
(rr->ImmedAnswer == mDNSInterfaceMark || rr->ImmedAnswer == r2->resrec.InterfaceID))
r2->ImmedAdditional = r2->resrec.InterfaceID; // ... then mark this address record for sending too
// We also make sure we send the DeviceInfo TXT record too, if necessary
// We check for RecordType == kDNSRecordTypeShared because we don't want to tag the
// DeviceInfo TXT record onto a goodbye packet (RecordType == kDNSRecordTypeDeregistering).
if (rr->ImmedAnswer && rr->resrec.RecordType == kDNSRecordTypeShared && rr->resrec.rrtype == kDNSType_PTR)
if (ResourceRecordIsValidAnswer(&m->DeviceInfo) && SameDomainLabel(rr->resrec.rdata->u.name.c, m->DeviceInfo.resrec.name->c))
{
if (!m->DeviceInfo.ImmedAnswer) m->DeviceInfo.ImmedAnswer = rr->ImmedAnswer;
else m->DeviceInfo.ImmedAnswer = mDNSInterfaceMark;
}
}
// If there's a record which is supposed to be unique that we're going to send, then make sure that we give
// the whole RRSet as an atomic unit. That means that if we have any other records with the same name/type/class
// then we need to mark them for sending too. Otherwise, if we set the kDNSClass_UniqueRRSet bit on a
// record, then other RRSet members that have not been sent recently will get flushed out of client caches.
// -- If a record is marked to be sent on a certain interface, make sure the whole set is marked to be sent on that interface
// -- If any record is marked to be sent on all interfaces, make sure the whole set is marked to be sent on all interfaces
for (rr = m->ResourceRecords; rr; rr=rr->next)
if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask)
{
if (rr->ImmedAnswer) // If we're sending this as answer, see that its whole RRSet is similarly marked
{
for (r2 = m->ResourceRecords; r2; r2=r2->next)
if (ResourceRecordIsValidAnswer(r2))
if (r2->ImmedAnswer != mDNSInterfaceMark &&
r2->ImmedAnswer != rr->ImmedAnswer && SameResourceRecordSignature(r2, rr))
r2->ImmedAnswer = !r2->ImmedAnswer ? rr->ImmedAnswer : mDNSInterfaceMark;
}
else if (rr->ImmedAdditional) // If we're sending this as additional, see that its whole RRSet is similarly marked
{
for (r2 = m->ResourceRecords; r2; r2=r2->next)
if (ResourceRecordIsValidAnswer(r2))
if (r2->ImmedAdditional != rr->ImmedAdditional && SameResourceRecordSignature(r2, rr))
r2->ImmedAdditional = rr->ImmedAdditional;
}
}
// Now set SendRNow state appropriately
for (rr = m->ResourceRecords; rr; rr=rr->next)
{
if (rr->ImmedAnswer == mDNSInterfaceMark) // Sending this record on all appropriate interfaces
{
rr->SendRNow = !intf ? mDNSNULL : (rr->resrec.InterfaceID) ? rr->resrec.InterfaceID : intf->InterfaceID;
rr->ImmedAdditional = mDNSNULL; // No need to send as additional if sending as answer
rr->LastMCTime = m->timenow;
rr->LastMCInterface = rr->ImmedAnswer;
// If we're announcing this record, and it's at least half-way to its ordained time, then consider this announcement done
if (TimeToAnnounceThisRecord(rr, m->timenow + rr->ThisAPInterval/2))
{
rr->AnnounceCount--;
if (rr->resrec.RecordType != kDNSRecordTypeDeregistering)
rr->ThisAPInterval *= 2;
rr->LastAPTime = m->timenow;
debugf("Announcing %##s (%s) %d", rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype), rr->AnnounceCount);
}
}
else if (rr->ImmedAnswer) // Else, just respond to a single query on single interface:
{
rr->SendRNow = rr->ImmedAnswer; // Just respond on that interface
rr->ImmedAdditional = mDNSNULL; // No need to send as additional too
rr->LastMCTime = m->timenow;
rr->LastMCInterface = rr->ImmedAnswer;
}
SetNextAnnounceProbeTime(m, rr);
//if (rr->SendRNow) LogMsg("%-15.4a %s", &rr->v4Requester, ARDisplayString(m, rr));
}
// ***
// *** 2. Loop through interface list, sending records as appropriate
// ***
while (intf)
{
const int OwnerRecordSpace = (m->AnnounceOwner && intf->MAC.l[0]) ? DNSOpt_Header_Space + DNSOpt_Owner_Space(&m->PrimaryMAC, &intf->MAC) : 0;
int numDereg = 0;
int numAnnounce = 0;
int numAnswer = 0;
mDNSu8 *responseptr = m->omsg.data;
mDNSu8 *newptr;
InitializeDNSMessage(&m->omsg.h, zeroID, ResponseFlags);
// First Pass. Look for:
// 1. Deregistering records that need to send their goodbye packet
// 2. Updated records that need to retract their old data
// 3. Answers and announcements we need to send
for (rr = m->ResourceRecords; rr; rr=rr->next)
{
// Skip this interface if the record InterfaceID is *Any and the record is not
// appropriate for the interface type.
if ((rr->SendRNow == intf->InterfaceID) &&
((rr->resrec.InterfaceID == mDNSInterface_Any) && !mDNSPlatformValidRecordForInterface(rr, intf)))
{
LogInfo("SendResponses: Not sending %s, on %s", ARDisplayString(m, rr), InterfaceNameForID(m, rr->SendRNow));
rr->SendRNow = GetNextActiveInterfaceID(intf);
}
else if (rr->SendRNow == intf->InterfaceID)
{
RData *OldRData = rr->resrec.rdata;
mDNSu16 oldrdlength = rr->resrec.rdlength;
mDNSu8 active = (mDNSu8)
(rr->resrec.RecordType != kDNSRecordTypeDeregistering &&
(m->SleepState != SleepState_Sleeping || intf->SPSAddr[0].type || intf->SPSAddr[1].type || intf->SPSAddr[2].type));
newptr = mDNSNULL;
if (rr->NewRData && active)
{
// See if we should send a courtesy "goodbye" for the old data before we replace it.
if (ResourceRecordIsValidAnswer(rr) && rr->resrec.RecordType == kDNSRecordTypeShared && rr->RequireGoodbye)
{