/* * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ Change History (most recent first): $Log: uDNS.c,v $ Revision 1.54 2004/06/22 02:10:53 ksekar : Lighthouse failure causes packet flood to DNS Revision 1.53 2004/06/17 20:49:09 ksekar : Tiger8A148: repeated crash of mDNSResponder while location cycling Revision 1.52 2004/06/17 01:13:11 ksekar : polling interval too short Revision 1.51 2004/06/10 04:36:44 cheshire Fix compiler warning Revision 1.50 2004/06/10 00:55:13 ksekar : crash on network reconnect Revision 1.49 2004/06/10 00:10:50 ksekar : Infinite Loop in uDNS_Execute() Revision 1.48 2004/06/09 20:03:37 ksekar : Incorrect copying of resource record in deregistration Revision 1.47 2004/06/09 03:48:28 ksekar : nameserver address fails with prod. Lighthouse server Revision 1.46 2004/06/09 01:44:30 ksekar reworked Cache Record copy code Revision 1.45 2004/06/08 18:54:47 ksekar : mDNSResponder leaks after exploring in Printer Setup Utility Revision 1.44 2004/06/05 00:33:51 cheshire : Check for incorrect time comparisons Revision 1.43 2004/06/05 00:14:44 cheshire Fix signed/unsigned and other compiler warnings Revision 1.42 2004/06/04 22:36:16 ksekar Properly set u->nextevent in uDNS_Execute Revision 1.41 2004/06/04 08:58:29 ksekar : Keychain integration for secure dynamic update Revision 1.40 2004/06/03 03:09:58 ksekar : Garbage Collection for Dynamic Updates Revision 1.39 2004/06/01 23:46:50 ksekar : DynDNS: dynamically look up LLQ/Update ports Revision 1.38 2004/05/31 22:19:44 ksekar : Feature: DNS server->client notification on record changes (#7805) - revert to polling mode on setup errors Revision 1.37 2004/05/28 23:42:37 ksekar : Feature: DNS server->client notification on record changes (#7805) Revision 1.36 2004/05/18 23:51:25 cheshire Tidy up all checkin comments to use consistent "" format for bug numbers Revision 1.35 2004/05/07 23:01:04 ksekar Cleaned up list traversal in deriveGoodbyes - removed unnecessary conditional assignment. Revision 1.34 2004/05/05 18:26:12 ksekar Periodically re-transmit questions if the send() fails. Include internal questions in retransmission. Revision 1.33 2004/05/05 17:40:06 ksekar Removed prerequisite from deregistration update - it does not work for shared records, and is unnecessary until we have more sophisticated name conflict management. Revision 1.32 2004/05/05 17:32:18 ksekar Prevent registration of loopback interface caused by removal of Multicast flag in interface structure. Revision 1.31 2004/05/05 17:05:02 ksekar Use LargeCacheRecord structs when pulling records off packets Revision 1.30 2004/04/16 21:33:27 ksekar Fixed bug in processing GetZoneData responses that do not use BIND formatting. Revision 1.29 2004/04/15 20:03:13 ksekar Clarified log message when pulling bad resource records off packet. Revision 1.28 2004/04/15 00:51:28 bradley Minor tweaks for Windows and C++ builds. Added casts for signed/unsigned integers and 64-bit pointers. Prefix some functions with mDNS to avoid conflicts. Disable benign warnings on Microsoft compilers. Revision 1.27 2004/04/14 23:09:28 ksekar Support for TSIG signed dynamic updates. Revision 1.26 2004/04/14 19:36:05 ksekar Fixed memory corruption error in deriveGoodbyes. Revision 1.25 2004/04/14 04:07:11 ksekar Fixed crash in IsActiveUnicastQuery(). Removed redundant checks in routine. Revision 1.24 2004/04/08 09:41:40 bradley Added const to AuthRecord in deadvertiseIfCallback to match callback typedef. Revision 1.23 2004/03/24 00:29:45 ksekar Make it safe to call StopQuery in a unicast question callback Revision 1.22 2004/03/19 10:11:09 bradley Added AuthRecord * cast from umalloc for C++ builds. Revision 1.21 2004/03/15 02:03:45 bradley Added const to params where needed to match prototypes. Changed SetNewRData calls to use 0 instead of -1 for unused size to fix warning. Disable assignment within conditional warnings with Visual C++. Revision 1.20 2004/03/13 02:07:26 ksekar : DynDNS: Dynamic update of service records Revision 1.19 2004/03/13 01:57:33 ksekar : DynDNS: Dynamic update of service records Revision 1.18 2004/02/21 08:34:15 bradley Added casts from void * to specific type for C++ builds. Changed void * l-value cast r-value cast to fix problems with VC++ builds. Removed empty switch to fix VC++ error. Revision 1.17 2004/02/21 02:06:24 cheshire Can't use anonymous unions -- they're non-standard and don't work on all compilers Revision 1.16 2004/02/12 01:51:45 cheshire Don't try to send uDNS queries unless we have at least one uDNS server available Revision 1.15 2004/02/10 03:02:46 cheshire Fix compiler warning Revision 1.14 2004/02/06 23:04:19 ksekar Basic Dynamic Update support via mDNS_Register (dissabled via UNICAST_REGISTRATION #define) Revision 1.13 2004/02/03 22:15:01 ksekar Fixed nameToAddr error check: don't abort state machine on nxdomain error. Revision 1.12 2004/02/03 19:47:36 ksekar Added an asyncronous state machine mechanism to uDNS.c, including calls to find the parent zone for a domain name. Changes include code in repository previously dissabled via "#if 0 //incomplete". Codepath is currently unused, and will be called to create update records, etc. Revision 1.11 2004/01/30 02:12:30 ksekar Changed uDNS_ReceiveMsg() to correctly return void. Revision 1.10 2004/01/29 02:59:17 ksekar Unicast DNS: Changed from a resource record oriented question/response matching to packet based matching. New callback architecture allows collections of records in a response to be processed differently depending on the nature of the request, and allows the same structure to be used for internal and client-driven queries with different processing needs. Revision 1.9 2004/01/28 20:20:45 ksekar Unified ActiveQueries and ActiveInternalQueries lists, using a flag to demux them. Check-in includes work-in-progress code, #ifdef'd out. Revision 1.8 2004/01/28 02:30:07 ksekar Added default Search Domains to unicast browsing, controlled via Networking sharing prefs pane. Stopped sending unicast messages on every interface. Fixed unicast resolving via mach-port API. Revision 1.7 2004/01/27 20:15:22 cheshire : Time to prune obsolete code for listening on port 53 Revision 1.6 2004/01/24 23:47:17 cheshire Use mDNSOpaque16fromIntVal() instead of shifting and masking Revision 1.5 2004/01/24 04:59:15 cheshire Fixes so that Posix/Linux, OS9, Windows, and VxWorks targets build again Revision 1.4 2004/01/24 04:19:26 cheshire Restore overwritten checkin 1.2 Revision 1.3 2004/01/23 23:23:15 ksekar Added TCP support for truncated unicast messages. Revision 1.2 2004/01/22 03:48:41 cheshire Make sure uDNS client doesn't accidentally use query ID zero Revision 1.1 2003/12/13 03:05:27 ksekar : DynDNS: Unicast query of service records */ #include "uDNS.h" #if(defined(_MSC_VER)) // 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 #ifndef NULL #define NULL mDNSNULL #endif // NULL #define ustrcpy(d,s) mDNSPlatformStrCopy(s,d) // use strcpy(2) param ordering #define ustrlen(s) mDNSPlatformStrLen(s) #define umalloc(x) mDNSPlatformMemAllocate(x) // short hands for common routines #define ufree(x) mDNSPlatformMemFree(x) #define ubzero(x,y) mDNSPlatformMemZero(x,y) #define umemcpy(x, y, l) mDNSPlatformMemCopy(y, x, l) // uses memcpy(2) arg ordering // Asyncronous operation types typedef enum { zoneDataResult // other async. operation names go here } AsyncOpResultType; typedef struct { domainname zoneName; mDNSAddr primaryAddr; mDNSu16 zoneClass; mDNSIPPort llqPort; mDNSIPPort updatePort; } zoneData_t; // other async. result struct defs go here typedef struct { AsyncOpResultType type; zoneData_t zoneData; // other async result structs go here } AsyncOpResult; typedef void AsyncOpCallback(mStatus err, mDNS *const m, void *info, const AsyncOpResult *result); // Private Function Prototypes // Note: In general, functions are ordered such that they do not require forward declarations. // However, prototypes are used where cyclic call graphs exist (e.g. foo calls bar, and bar calls // foo), or when they aid in the grouping or readability of code (e.g. state machine code that is easier // read top-to-bottom.) mDNSlocal void hndlTruncatedAnswer(DNSQuestion *question, const mDNSAddr *src, mDNS *m); mDNSlocal mStatus startGetZoneData(domainname *name, mDNS *m, mDNSBool findUpdatePort, mDNSBool findLLQPort, AsyncOpCallback callback, void *callbackInfo); mDNSlocal mDNSBool recvLLQResponse(mDNS *m, DNSMessage *msg, const mDNSu8 *end, const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSInterfaceID InterfaceID); mDNSlocal void sendRecordRegistration(mDNS *const m, AuthRecord *rr); mDNSlocal void SendServiceRegistration(mDNS *m, ServiceRecordSet *srs); // *************************************************************************** #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - General Utility Functions #endif mDNSlocal mDNSOpaque16 newMessageID(uDNS_GlobalInfo *u) { // if NextMessageID is 0 (ininitialized) or 0xffff (reserved for TCP packets) reset to 1 if (!u->NextMessageID || u->NextMessageID == (mDNSu16)~0) u->NextMessageID = 1; return mDNSOpaque16fromIntVal(u->NextMessageID++); } // unlink an AuthRecord from a linked list mDNSlocal mStatus unlinkAR(AuthRecord **list, AuthRecord *const rr) { AuthRecord *rptr, *prev = NULL; for (rptr = *list; rptr; rptr = rptr->next) { if (rptr == rr) { if (prev) prev->next = rptr->next; else *list = rptr->next; rptr->next = NULL; return mStatus_NoError; } prev = rptr; } LogMsg("ERROR: unlinkAR - no such active record"); return mStatus_UnknownErr; } mDNSlocal void LinkActiveQuestion(uDNS_GlobalInfo *u, DNSQuestion *q) { if (IsActiveUnicastQuery(q, u)) { LogMsg("LinkActiveQuestion - %s (%d) already in list!", q->qname.c, q->qtype); return; } q->next = u->ActiveQueries; u->ActiveQueries = q; } // *************************************************************************** #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - Name Server List Management #endif mDNSexport void mDNS_RegisterDNS(mDNS *const m, mDNSv4Addr *const dnsAddr) { //!!!KRS do this dynamically! uDNS_GlobalInfo *u = &m->uDNS_info; int i; if (!dnsAddr->NotAnInteger) { LogMsg("ERROR: attempt to register DNS with IP address 0"); return; } for (i = 0; i < 32; i++) { if (!u->Servers[i].ip.v4.NotAnInteger) { u->Servers[i].ip.v4.NotAnInteger = dnsAddr->NotAnInteger; u->Servers[i].type = mDNSAddrType_IPv4; return; } if (u->Servers[i].ip.v4.NotAnInteger == dnsAddr->NotAnInteger) { LogMsg("ERROR: mDNS_RegisterDNS - DNS already registered"); return; } } if (i == 32) { LogMsg("ERROR: mDNS_RegisterDNS - too many registered servers"); } } mDNSexport void mDNS_DeregisterDNS(mDNS *const m, mDNSv4Addr *const dnsAddr) { uDNS_GlobalInfo *u = &m->uDNS_info; int i; if (!dnsAddr->NotAnInteger) { LogMsg("ERROR: attempt to deregister DNS with IP address 0"); return; } for (i = 0; i < 32; i++) { if (u->Servers[i].ip.v4.NotAnInteger == dnsAddr->NotAnInteger) { u->Servers[i].ip.v4.NotAnInteger = 0; return; } } if (i == 32) { LogMsg("ERROR: mDNS_DeregisterDNS - no such DNS registered"); } } mDNSexport void mDNS_DeregisterDNSList(mDNS *const m) { ubzero(m->uDNS_info.Servers, 32 * sizeof(mDNSAddr)); } mDNSexport mDNSBool mDNS_DNSRegistered(mDNS *const m) { int i; for (i = 0; i < 32; i++) if (m->uDNS_info.Servers[i].ip.v4.NotAnInteger) return mDNStrue; return mDNSfalse; } // *************************************************************************** #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - authorization management #endif mDNSexport mStatus mDNS_UpdateDomainRequiresAuthentication(mDNS *m, domainname *zone, domainname *key, mDNSu8 *sharedSecret, mDNSu32 ssLen, mDNSBool base64) { uDNS_AuthInfo *info; mDNSu8 keybuf[1024]; mDNSs32 keylen; info = (uDNS_AuthInfo*)umalloc(sizeof(uDNS_AuthInfo) + ssLen); if (!info) { LogMsg("ERROR: umalloc"); return mStatus_NoMemoryErr; } ubzero(info, sizeof(uDNS_AuthInfo)); ustrcpy(info->zone.c, zone->c); ustrcpy(info->keyname.c, key->c); if (base64) { keylen = DNSDigest_Base64ToBin((const char*)sharedSecret, keybuf, 1024); if (keylen < 0) { LogMsg("ERROR: mDNS_UpdateDomainRequiresAuthentication - could not convert shared secret from base64"); ufree(info); return mStatus_UnknownErr; } DNSDigest_ConstructHMACKey(info, keybuf, (mDNSu32)keylen); } else DNSDigest_ConstructHMACKey(info, sharedSecret, ssLen); // link into list // !!!KRS this should be a hashtable since we must check if updates are required on each registration info->next = m->uDNS_info.AuthInfoList; m->uDNS_info.AuthInfoList = info; return mStatus_NoError; } mDNSexport void mDNS_ClearAuthenticationList(mDNS *m) { uDNS_AuthInfo *fptr, *ptr = m->uDNS_info.AuthInfoList; while (ptr) { fptr = ptr; ptr = ptr->next; ufree(fptr); } m->uDNS_info.AuthInfoList = NULL; } mDNSlocal uDNS_AuthInfo *GetAuthInfoForZone(const uDNS_GlobalInfo *u, const domainname *zone) { uDNS_AuthInfo *ptr; domainname *z; mDNSu32 zoneLen, ptrZoneLen; zoneLen = ustrlen(zone->c); for (ptr = u->AuthInfoList; ptr; ptr = ptr->next) { z = &ptr->zone; ptrZoneLen = ustrlen(z->c); if (zoneLen < ptrZoneLen) continue; // return info if zone ends in info->zone if (mDNSPlatformMemSame(z->c, zone->c + (zoneLen - ptrZoneLen), ptrZoneLen)) return ptr; } return NULL; } // *************************************************************************** #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - host name and interface management #endif mDNSlocal void hostnameCallback(mDNS *const m, AuthRecord *const rr, mStatus result) { // note that the rr is already unlinked if result is non-zero if (result == mStatus_MemFree) return; if (result == mStatus_NameConflict && rr->resrec.RecordType == kDNSRecordTypeUnique) { // if we get a name conflict, make sure our name/addr isn't already registered by re-registering rr->resrec.RecordType = kDNSRecordTypeKnownUnique; uDNS_RegisterRecord(m, rr); return; } if (rr->resrec.RecordType == kDNSRecordTypeKnownUnique) // we've already tried to re-register. reset RecordType before returning RR to client { if (result == mStatus_NoSuchRecord) // name is advertised for some other address result = mStatus_NameConflict; rr->resrec.RecordType = kDNSRecordTypeUnique; } if (!result) rr->resrec.RecordType = kDNSRecordTypeVerified; if (result) ((NetworkInterfaceInfo *)(rr->RecordContext))->uDNS_info.registered = mDNSfalse; mDNS_HostNameCallback(m, rr, result); } mDNSlocal void deadvertiseIfCallback(mDNS *const m, AuthRecord *const rr, mStatus err) { (void)m; // unused if (err == mStatus_MemFree) ufree(rr); else LogMsg("deadvertiseIfCallback - error %s for record %s", err, rr->resrec.name.c); } mDNSexport void uDNS_DeadvertiseInterface(mDNS *const m, NetworkInterfaceInfo *set) { AuthRecord *copy; AuthRecord *rr = &set->uDNS_info.RR_A; // NOTE: for compatibility w/ mDNS architecture, we make a copy of the address record before sending a // goodbye, since mDNS does not send goodbyes for address records and expects the memory to be immediately // freed if (set->uDNS_info.registered) { // copy resource record copy = (AuthRecord*)umalloc(sizeof(AuthRecord)); // allocate storage if (!copy) { LogMsg("ERROR: Malloc"); return; } umemcpy(copy, rr, sizeof(AuthRecord)); // copy all fields copy->resrec.rdata = ©->rdatastorage; // set rdata pointer if (rr->resrec.rdata != &rr->rdatastorage) { LogMsg("ERROR: uDNS_DeadvertiseInterface - expected local rdata storage. Aborting deregistration"); return; } // link copy into list copy->next = m->uDNS_info.RecordRegistrations; m->uDNS_info.RecordRegistrations = copy; copy->RecordCallback = deadvertiseIfCallback; // unlink the original unlinkAR(&m->uDNS_info.RecordRegistrations, rr); rr->uDNS_info.state = regState_Unregistered; set->uDNS_info.registered = mDNSfalse; uDNS_DeregisterRecord(m, copy); } else debugf("uDNS_DeadvertiseInterface - interface not registered"); return; } mDNSexport void uDNS_AdvertiseInterface(mDNS *const m, NetworkInterfaceInfo *set) { mDNSu8 *ip = set->ip.ip.v4.b; AuthRecord *a = &set->uDNS_info.RR_A; a->RecordContext = set; if (set->ip.type != mDNSAddrType_IPv4 // non-v4 || (ip[0] == 169 && ip[1] == 254) // link-local || (ip[0] == 127 && ip[1] == 0 && ip[2] == 0 && ip[3] == 1)) // loopback return; if (set->uDNS_info.registered && SameDomainName(&m->uDNS_info.hostname, &set->uDNS_info.regname)) return; // already registered if (!m->uDNS_info.hostname.c[0]) { // no hostname available set->uDNS_info.registered = mDNSfalse; return; } set->uDNS_info.registered = mDNStrue; ustrcpy(set->uDNS_info.regname.c, m->uDNS_info.hostname.c); //!!!KRS temp ttl 1 mDNS_SetupResourceRecord(a, mDNSNULL, 0, kDNSType_A, 1, kDNSRecordTypeShared /*Unique*/, hostnameCallback, set); //!!!KRS ustrcpy(a->resrec.name.c, m->uDNS_info.hostname.c); a->resrec.rdata->u.ip = set->ip.ip.v4; LogMsg("uDNS_AdvertiseInterface: advertising %s", m->uDNS_info.hostname.c); uDNS_RegisterRecord(m, a); } // *************************************************************************** #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - Incoming Message Processing #endif mDNSlocal mDNSBool sameResourceRecord(ResourceRecord *r1, ResourceRecord *r2) { return (r1->namehash == r2->namehash && r1->rrtype == r2->rrtype && SameDomainName(&r1->name, &r2->name) && SameRData(r1, r2)); } mDNSlocal mDNSBool kaListContainsAnswer(DNSQuestion *question, CacheRecord *rr) { CacheRecord *ptr; for (ptr = question->uDNS_info.knownAnswers; ptr; ptr = ptr->next) if (sameResourceRecord(&ptr->resrec, &rr->resrec)) return mDNStrue; return mDNSfalse; } mDNSlocal void removeKnownAnswer(DNSQuestion *question, CacheRecord *rr) { CacheRecord *ptr, *prev = NULL; for (ptr = question->uDNS_info.knownAnswers; ptr; ptr = ptr->next) { if (sameResourceRecord(&ptr->resrec, &rr->resrec)) { if (prev) prev->next = ptr->next; else question->uDNS_info.knownAnswers = ptr->next; ufree(ptr); return; } prev = ptr; } LogMsg("removeKnownAnswer() called for record not in KA list"); } mDNSlocal void addKnownAnswer(DNSQuestion *question, const CacheRecord *rr) { CacheRecord *newCR = NULL; mDNSu32 size; size = sizeof(CacheRecord) + rr->resrec.rdlength - InlineCacheRDSize; newCR = (CacheRecord *)umalloc(size); if (!newCR) { LogMsg("ERROR: addKnownAnswer - malloc"); return; } umemcpy(newCR, rr, size); newCR->resrec.rdata = (RData*)&newCR->rdatastorage; newCR->resrec.rdata->MaxRDLength = rr->resrec.rdlength; newCR->next = question->uDNS_info.knownAnswers; question->uDNS_info.knownAnswers = newCR; } mDNSlocal void deriveGoodbyes(mDNS * const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question) { const mDNSu8 *ptr; int i; CacheRecord *fptr, *ka, *cr, *answers = NULL, *prev = NULL; LargeCacheRecord *lcr; if (question != m->uDNS_info.CurrentQuery) { LogMsg("ERROR: deriveGoodbyes called without CurrentQuery set!"); return; } ptr = LocateAnswers(msg, end); if (!ptr) goto pkt_error; if (!msg->h.numAnswers) { // delete the whole KA list ka = question->uDNS_info.knownAnswers; while (ka) { debugf("deriving goodbye for %s", ka->resrec.name.c); question->QuestionCallback(m, question, &ka->resrec, mDNSfalse); if (question != m->uDNS_info.CurrentQuery) { debugf("deriveGoodbyes - question removed via callback. returning."); return; } fptr = ka; ka = ka->next; ufree(fptr); } question->uDNS_info.knownAnswers = NULL; return; } // make a list of all the new answers for (i = 0; i < msg->h.numAnswers; i++) { lcr = (LargeCacheRecord *)umalloc(sizeof(LargeCacheRecord)); if (!lcr) goto malloc_error; ubzero(lcr, sizeof(LargeCacheRecord)); ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAns, lcr); if (!ptr) goto pkt_error; cr = &lcr->r; if (ResourceRecordAnswersQuestion(&cr->resrec, question)) { cr->next = answers; answers = cr; } else ufree(cr); } // make sure every known answer is in the answer list ka = question->uDNS_info.knownAnswers; while (ka) { for (cr = answers; cr; cr = cr->next) { if (sameResourceRecord(&ka->resrec, &cr->resrec)) break; } if (!cr) { // record is in KA list but not answer list - remove from KA list if (prev) prev->next = ka->next; else question->uDNS_info.knownAnswers = ka->next; debugf("deriving goodbye for %s", ka->resrec.name.c); question->QuestionCallback(m, question, &ka->resrec, mDNSfalse); if (question != m->uDNS_info.CurrentQuery) { debugf("deriveGoodbyes - question removed via callback. returning."); return; } fptr = ka; ka = ka->next; ufree(fptr); } else { prev = ka; ka = ka->next; } } // free temp answers list cr = answers; while (cr) { fptr = cr; cr = cr->next; ufree(fptr); } return; pkt_error: LogMsg("ERROR: deriveGoodbyes - received malformed response to query for %s (%d)", question->qname.c, question->qtype); return; malloc_error: LogMsg("ERROR: Malloc"); } mDNSlocal void pktResponseHndlr(mDNS * const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, mDNSBool llq) { const mDNSu8 *ptr; int i; LargeCacheRecord lcr; CacheRecord *cr = &lcr.r; mDNSBool goodbye, inKAList; LLQ_Info *llqInfo = question->uDNS_info.llq; if (question != m->uDNS_info.CurrentQuery) { LogMsg("ERROR: pktResponseHdnlr called without CurrentQuery ptr set!"); return; } ptr = LocateAnswers(msg, end); if (!ptr) goto pkt_error; for (i = 0; i < msg->h.numAnswers; i++) { ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); if (!ptr) goto pkt_error; if (ResourceRecordAnswersQuestion(&cr->resrec, question)) { goodbye = llq ? ((mDNSs32)cr->resrec.rroriginalttl == -1) : mDNSfalse; inKAList = kaListContainsAnswer(question, cr); if ((goodbye && !inKAList) || (!goodbye && inKAList)) continue; // list up to date if (!inKAList) addKnownAnswer(question, cr); if (goodbye) removeKnownAnswer(question, cr); question->QuestionCallback(m, question, &cr->resrec, !goodbye); if (question != m->uDNS_info.CurrentQuery) { debugf("pktResponseHndlr - CurrentQuery changed by QuestionCallback - returning"); return; } } else { LogMsg("unexpected answer: %s", cr->resrec.name.c); } } if (llq && (llqInfo->state == LLQ_Poll || llqInfo->deriveRemovesOnResume)) { deriveGoodbyes(m, msg, end,question); llqInfo->deriveRemovesOnResume = mDNSfalse; } //!!!KRS should we derive goodbyes for non-LLQs? return; pkt_error: LogMsg("ERROR: pktResponseHndlr - received malformed response to query for %s (%d)", question->qname.c, question->qtype); return; } mDNSlocal void simpleResponseHndlr(mDNS * const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, void *context) { (void)context; // unused pktResponseHndlr(m, msg, end, question, mDNSfalse); } mDNSlocal void llqResponseHndlr(mDNS * const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, void *context) { (void)context; // unused pktResponseHndlr(m, msg, end, question, mDNStrue); } mDNSlocal void unlinkSRS(uDNS_GlobalInfo *u, ServiceRecordSet *srs) { ServiceRecordSet *ptr, *prev = NULL; for (ptr = u->ServiceRegistrations; ptr; ptr = ptr->next) { if (ptr == srs) { if (prev) prev->next = ptr->next; else u->ServiceRegistrations = ptr->next; ptr->next = NULL; return; } prev = ptr; } LogMsg("ERROR: unlinkSRS - SRS not found in ServiceRegistrations list"); } mDNSlocal mStatus checkUpdateResult(domainname *name, mDNSu8 rcode, const DNSMessage *msg) { (void)msg; // currently unused, needed for TSIG errors if (!rcode) return mStatus_NoError; else if (rcode == kDNSFlag1_RC_YXDomain) { LogMsg("Name in use: %s", name->c); return mStatus_NameConflict; } else if (rcode == kDNSFlag1_RC_Refused) { LogMsg("Update %s refused", name->c); return mStatus_Refused; } else if (rcode == kDNSFlag1_RC_NXRRSet) { LogMsg("Reregister refusted (NXRRSET): %s", name->c); return mStatus_NoSuchRecord; } else if (rcode == kDNSFlag1_RC_NotAuth) { LogMsg("Permission denied (NOAUTH): %s", name->c); return mStatus_NoAuth; } else if (rcode == kDNSFlag1_RC_FmtErr) { LogMsg("Format Error: %s", name->c); return mStatus_UnknownErr; //!!!KRS need to parse message for TSIG errors } else { LogMsg("Update %s failed with rcode %d", name->c, rcode); return mStatus_UnknownErr; } } mDNSlocal void hndlServiceUpdateReply(mDNS * const m, ServiceRecordSet *srs, mStatus err) { //!!!KRS make sure we're doing the right thing w/ MemFree switch (srs->uDNS_info.state) { case regState_Pending: case regState_Refresh: if (err) { if (srs->uDNS_info.lease && err == mStatus_UnknownErr) { LogMsg("Re-trying update of service %s without lease option", srs->RR_SRV.resrec.name.c); srs->uDNS_info.lease = mDNSfalse; srs->uDNS_info.expire = -1; SendServiceRegistration(m, srs); return; } else { LogMsg("hndlServiceUpdateReply: Error %d returned for registration of %s", err, srs->RR_SRV.resrec.name.c); srs->uDNS_info.state = regState_Unregistered; break; } } else { if (srs->uDNS_info.state == regState_Refresh) { srs->uDNS_info.state = regState_Registered; return; } srs->uDNS_info.state = regState_Registered; break; } case regState_DeregPending: if (err) LogMsg("hndlServiceUpdateReply: Error %d returned for dereg of %s", err, srs->RR_SRV.resrec.name.c); else err = mStatus_MemFree; break; case regState_DeregDeferred: if (err) LogMsg("hndlServiceUpdateReply: Error %d received prior to deferred derigstration of %s", err, srs->RR_SRV.resrec.name.c); LogMsg("Performing deferred deregistration of %s", srs->RR_SRV.resrec.name.c); uDNS_DeregisterService(m, srs); return; case regState_TargetChange: if (err) { LogMsg("hdnlServiceUpdateReply: Error %d returned for host target update of %s", err, srs->RR_SRV.resrec.name.c); srs->uDNS_info.state = regState_Unregistered; // !!!KRS we are leaving the ptr/txt records registered } else srs->uDNS_info.state = regState_Registered; break; default: LogMsg("hndlServiceUpdateReply called for service %s in unexpected state %d with error %d. Unlinking.", srs->RR_SRV.resrec.name.c, srs->uDNS_info.state, err); err = mStatus_UnknownErr; } if (err) { unlinkSRS(&m->uDNS_info, srs); // name conflicts, force dereg, and errors srs->uDNS_info.state = regState_Unregistered; } srs->ServiceCallback(m, srs, err); // NOTE: do not touch structures after calling ServiceCallback } mDNSlocal void hndlRecordUpdateReply(mDNS *m, AuthRecord *rr, mStatus err) { uDNS_GlobalInfo *u = &m->uDNS_info; if (rr->uDNS_info.state == regState_DeregPending) { debugf("Received reply for deregister record %s type %d", rr->resrec.name.c, rr->resrec.rrtype); if (err) LogMsg("ERROR: Deregistration of record %s type %s failed with error %d", rr->resrec.name.c, rr->resrec.rrtype, err); else err = mStatus_MemFree; if (unlinkAR(&m->uDNS_info.RecordRegistrations, rr)) LogMsg("ERROR: Could not unlink resource record following deregistration"); rr->uDNS_info.state = regState_Unregistered; rr->RecordCallback(m, rr, err); return; } if (rr->uDNS_info.state == regState_DeregDeferred) { if (err) { LogMsg("Cancelling deferred deregistration record %s type %d due to registration error %d", rr->resrec.name.c, rr->resrec.rrtype, err); unlinkAR(&m->uDNS_info.RecordRegistrations, rr); rr->uDNS_info.state = regState_Unregistered; return; } LogMsg("Calling deferred deregistration of record %s type %d", rr->resrec.name.c, rr->resrec.rrtype); rr->uDNS_info.state = regState_Registered; uDNS_DeregisterRecord(m, rr); return; } if (rr->uDNS_info.state == regState_Pending || rr->uDNS_info.state == regState_Refresh) { if (err) { if (rr->uDNS_info.lease && err == mStatus_UnknownErr) { LogMsg("Re-trying update of record %s without lease option", rr->resrec.name.c); rr->uDNS_info.lease = mDNSfalse; rr->uDNS_info.expire = -1; sendRecordRegistration(m, rr); return; } LogMsg("Registration of record %s type %d failed with error %d", rr->resrec.name.c, rr->resrec.rrtype, err); unlinkAR(&u->RecordRegistrations, rr); rr->uDNS_info.state = regState_Unregistered; } else { if (rr->uDNS_info.state == regState_Refresh) rr->uDNS_info.state = regState_Registered; else { rr->uDNS_info.state = regState_Registered; rr->RecordCallback(m, rr, err); } return; } } LogMsg("Received unexpected response for record %s type %d, in state %d, with response error %d", rr->resrec.name.c, rr->resrec.rrtype, rr->uDNS_info.state, err); } mDNSlocal void SetUpdateExpiration(mDNS *m, DNSMessage *msg, const mDNSu8 *end, uDNS_RegInfo *info) { LargeCacheRecord lcr; const mDNSu8 *ptr; int i; mDNSu32 lease = 0; ptr = LocateAdditionals(msg, end); if (info->lease && (ptr = LocateAdditionals(msg, end))) { for (i = 0; i < msg->h.numAdditionals; i++) { ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); if (!ptr) break; if (lcr.r.resrec.rrtype == kDNSType_OPT) { if (lcr.r.resrec.rdlength < LEASE_OPT_SIZE) continue; if (lcr.r.resrec.rdata->u.opt.opt != kDNSOpt_Lease) continue; lease = lcr.r.resrec.rdata->u.opt.OptData.lease; break; } } } if (lease > 0) info->expire = (mDNSPlatformTimeNow() + (((mDNSs32)lease * mDNSPlatformOneSecond)) * 3/4); else info->expire = -1; } mDNSexport void uDNS_ReceiveMsg(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *const srcaddr, const mDNSIPPort srcport, const mDNSAddr *const dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, mDNSu8 ttl) { DNSQuestion *qptr; AuthRecord *rptr; ServiceRecordSet *sptr; mStatus err = mStatus_NoError; uDNS_GlobalInfo *u = &m->uDNS_info; mDNSu8 StdR = kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery; mDNSu8 UpdateR = kDNSFlag0_OP_Update | kDNSFlag0_QR_Response; mDNSu8 QR_OP = (mDNSu8)(msg->h.flags.b[0] & kDNSFlag0_QROP_Mask); mDNSu8 rcode = (mDNSu8)(msg->h.flags.b[1] & kDNSFlag1_RC); // unused (void)srcaddr; (void)srcport; (void)dstaddr; (void)dstport; (void)ttl; (void)InterfaceID; if (QR_OP == StdR) { // !!!KRS we should to a table lookup here to see if it answers an LLQ or a 1-shot if (recvLLQResponse(m, msg, end, srcaddr, srcport, InterfaceID)) return; for (qptr = u->ActiveQueries; qptr; qptr = qptr->next) { //!!!KRS we should have a hashtable, hashed on message id if (qptr->uDNS_info.id.NotAnInteger == msg->h.id.NotAnInteger) { if (msg->h.flags.b[0] & kDNSFlag0_TC) { hndlTruncatedAnswer(qptr, srcaddr, m); return; } else { u->CurrentQuery = qptr; qptr->uDNS_info.responseCallback(m, msg, end, qptr, qptr->uDNS_info.context); u->CurrentQuery = NULL; // Note: responseCallback can invalidate qptr return; } } } } if (QR_OP == UpdateR) { for (sptr = u->ServiceRegistrations; sptr; sptr = sptr->next) { if (sptr->uDNS_info.id.NotAnInteger == msg->h.id.NotAnInteger) { err = checkUpdateResult(&sptr->RR_SRV.resrec.name, rcode, msg); if (!err) SetUpdateExpiration(m, msg, end, &sptr->uDNS_info); hndlServiceUpdateReply(m, sptr, err); return; } } for (rptr = u->RecordRegistrations; rptr; rptr = rptr->next) { if (rptr->uDNS_info.id.NotAnInteger == msg->h.id.NotAnInteger) { err = checkUpdateResult(&rptr->resrec.name, rcode, msg); if (!err) SetUpdateExpiration(m, msg, end, &rptr->uDNS_info); hndlRecordUpdateReply(m, rptr, err); return; } } } debugf("Received unexpected response: ID %d matches no active records", mDNSVal16(msg->h.id)); } mDNSlocal void receiveMsg(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end, const mDNSInterfaceID InterfaceID) { mDNSAddr *sa = NULL, *da = NULL; mDNSIPPort sp, dp; mDNSu8 ttl = 0; sp.NotAnInteger = 0; dp.NotAnInteger = 0; uDNS_ReceiveMsg(m, msg, end, sa, sp, da, dp, InterfaceID, ttl); } //!!!KRS this should go away (don't just pick one randomly!) mDNSlocal const mDNSAddr *getInitializedDNS(uDNS_GlobalInfo *u) { int i; for (i = 0; i < 32; i++) if (u->Servers[i].ip.v4.NotAnInteger) return &u->Servers[i]; return NULL; } // *************************************************************************** #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - Query Routines #endif #define sameID(x,y) mDNSPlatformMemSame(x,y,8) mDNSlocal void initializeQuery(DNSMessage *msg, DNSQuestion *question) { mDNSOpaque16 flags = QueryFlags; ubzero(msg, sizeof(msg)); flags.b[0] |= kDNSFlag0_RD; // recursion desired InitializeDNSMessage(&msg->h, question->uDNS_info.id, flags); } mDNSlocal mStatus constructQueryMsg(DNSMessage *msg, mDNSu8 **endPtr, DNSQuestion *const question) { initializeQuery(msg, question); *endPtr = putQuestion(msg, msg->data, msg->data + AbsoluteMaxDNSMessageData, &question->qname, question->qtype, question->qclass); if (!*endPtr) { LogMsg("ERROR: Unicast query out of space in packet"); return mStatus_UnknownErr; } return mStatus_NoError; } mDNSlocal mDNSu8 *putLLQ(DNSMessage *const msg, mDNSu8 *ptr, DNSQuestion *question, LLQOptData *data, mDNSBool includeQuestion) { AuthRecord rr; ResourceRecord *opt = &rr.resrec; rdataOpt *optRD; //!!!KRS when we implement multiple llqs per message, we'll need to memmove anything past the question section if (includeQuestion) { ptr = putQuestion(msg, ptr, msg->data + AbsoluteMaxDNSMessageData, &question->qname, question->qtype, question->qclass); if (!ptr) { LogMsg("ERROR: putLLQ - putQuestion"); return NULL; } } // locate OptRR if it exists, set pointer to end // !!!KRS implement me // format opt rr (fields not specified are zero-valued) ubzero(&rr, sizeof(AuthRecord)); opt->rdata = &rr.rdatastorage; opt->RecordType = kDNSRecordTypeKnownUnique; // to avoid warnings in other layers opt->rrtype = kDNSType_OPT; opt->rdlength = LLQ_OPT_SIZE; opt->rdestimate = LLQ_OPT_SIZE; optRD = &rr.resrec.rdata->u.opt; optRD->opt = kDNSOpt_LLQ; optRD->optlen = sizeof(LLQOptData); umemcpy(&optRD->OptData.llq, data, sizeof(LLQOptData)); ptr = PutResourceRecordTTL(msg, ptr, &msg->h.numAdditionals, opt, 0); if (!ptr) { LogMsg("ERROR: putLLQ - PutResourceRecordTTL"); return NULL; } return ptr; } mDNSlocal mDNSBool getLLQAtIndex(mDNS *m, DNSMessage *msg, const mDNSu8 *end, LLQOptData *llq, int index) { LargeCacheRecord lcr; int i; const mDNSu8 *ptr; ptr = LocateAdditionals(msg, end); if (!ptr) return mDNSfalse; // find the last additional for (i = 0; i < msg->h.numAdditionals; i++) // { ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); if (!ptr) return mDNSfalse; } //!!!KRS workaround for LH server bug, which puts OPT as first additional { ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); if (!ptr) return mDNSfalse; if (lcr.r.resrec.rrtype == kDNSType_OPT) break; } if (lcr.r.resrec.rrtype != kDNSType_OPT) return mDNSfalse; if (lcr.r.resrec.rdlength < (index + 1) * LLQ_OPT_SIZE) return mDNSfalse; // rdata too small umemcpy(llq, (mDNSu8 *)&lcr.r.resrec.rdata->u.opt.OptData.llq + (index * sizeof(LLQOptData)), sizeof(LLQOptData)); return mDNStrue; } mDNSlocal void recvRefreshReply(mDNS *m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *q) { LLQ_Info *qInfo; LLQOptData pktData; qInfo = q->uDNS_info.llq; if (!getLLQAtIndex(m, msg, end, &pktData, 0)) { LogMsg("ERROR recvRefreshReply - getLLQAtIndex"); return; } if (pktData.llqOp != kLLQ_Refresh) return; if (!sameID(pktData.id, qInfo->id)) { LogMsg("recvRefreshReply - ID mismatch. Discarding"); return; } if (pktData.err != LLQErr_NoError) { LogMsg("recvRefreshReply: received error %d from server", pktData.err); return; } qInfo->expire = mDNSPlatformTimeNow() + ((mDNSs32)pktData.lease * mDNSPlatformOneSecond); qInfo->retry = qInfo->expire + ((mDNSs32)pktData.lease * mDNSPlatformOneSecond * 3/4); qInfo->origLease = pktData.lease; qInfo->state = LLQ_Established; } mDNSlocal void sendLLQRefresh(mDNS *m, DNSQuestion *q, mDNSu32 lease) { DNSMessage msg; mDNSu8 *end; LLQOptData llq; LLQ_Info *info = q->uDNS_info.llq; mStatus err; if (info->state == kLLQ_Refresh && info->ntries >= kLLQ_MAX_TRIES) { LogMsg("sendLLQRefresh - %d failed attempts for llq %s", info->ntries, q->qname.c); info->state = LLQ_Retry; info->retry = mDNSPlatformTimeNow() + kLLQ_DEF_RETRY * mDNSPlatformOneSecond; info->deriveRemovesOnResume = mDNStrue; return; //!!!KRS handle this - periodically try to re-establish } llq.vers = kLLQ_Vers; llq.llqOp = kLLQ_Refresh; llq.err = LLQErr_NoError; umemcpy(llq.id, info->id, 8); llq.lease = lease; initializeQuery(&msg, q); end = putLLQ(&msg, msg.data, q, &llq, mDNStrue); if (!end) { LogMsg("ERROR: sendLLQRefresh - putLLQ"); return; } err = mDNSSendDNSMessage(m, &msg, end, q->InterfaceID, &info->servAddr, info->servPort); if (err) LogMsg("ERROR: sendLLQRefresh - mDNSSendDNSMessage returned %d", err); if (info->state == LLQ_Established) info->ntries = 1; else info->ntries++; info->state = LLQ_Refresh; q->LastQTime = mDNSPlatformTimeNow(); info->retry = (info->expire - q->LastQTime) / 2; } mDNSlocal void recvLLQEvent(mDNS *m, DNSQuestion *q, DNSMessage *msg, const mDNSu8 *end, const mDNSAddr *srcaddr, mDNSIPPort srcport, mDNSInterfaceID InterfaceID) { DNSMessage ack; mDNSu8 *ackEnd = ack.data; mStatus err; // invoke response handler m->uDNS_info.CurrentQuery = q; q->uDNS_info.responseCallback(m, msg, end, q, q->uDNS_info.context); if (m->uDNS_info.CurrentQuery != q) return; // format and send ack InitializeDNSMessage(&ack.h, msg->h.id, ResponseFlags); ackEnd = putQuestion(&ack, ack.data, ack.data + AbsoluteMaxDNSMessageData, &q->qname, q->qtype, q->qclass); if (!ackEnd) { LogMsg("ERROR: recvLLQEvent - putQuestion"); return; } err = mDNSSendDNSMessage(m, &ack, ackEnd, InterfaceID, srcaddr, srcport); if (err) LogMsg("ERROR: recvLLQEvent - mDNSSendDNSMessage returned %d", err); } mDNSlocal void hndlChallengeResponseAck(mDNS *m, DNSMessage *pktMsg, const mDNSu8 *end, LLQOptData *llq, DNSQuestion *q) { LLQ_Info *info = q->uDNS_info.llq; if (llq->err) { LogMsg("hndlChallengeResponseAck - received error %d from server", llq->err); goto error; } if (!sameID(info->id, llq->id)) { LogMsg("hndlChallengeResponseAck - ID changed. discarding"); return; } // this can happen rarely (on packet loss + reordering) info->expire = mDNSPlatformTimeNow() + ((mDNSs32)llq->lease * mDNSPlatformOneSecond); info->retry = info->expire + ((mDNSs32)llq->lease * mDNSPlatformOneSecond * 3/4); info->origLease = llq->lease; info->state = LLQ_Established; q->uDNS_info.responseCallback = llqResponseHndlr; llqResponseHndlr(m, pktMsg, end, q, NULL); return; error: info->state = LLQ_Error; } mDNSlocal void sendChallengeResponse(mDNS *m, DNSQuestion *q, LLQOptData *llq) { LLQ_Info *info = q->uDNS_info.llq; DNSMessage response; mDNSu8 *responsePtr = response.data; mStatus err; LLQOptData llqBuf; mDNSs32 timenow = mDNSPlatformTimeNow(); if (info->ntries++ == kLLQ_MAX_TRIES) { LogMsg("sendChallengeResponse: %d failed attempts for LLQ %s. Will re-try in %d minutes", kLLQ_MAX_TRIES, q->qname.c, kLLQ_DEF_RETRY / 60); info->state = LLQ_Retry; info->retry = timenow + (kLLQ_DEF_RETRY * mDNSPlatformOneSecond); // !!!KRS give a callback error in these cases? return; } if (!llq) { llq = &llqBuf; llq->vers = kLLQ_Vers; llq->llqOp = kLLQ_Setup; llq->err = LLQErr_NoError; umemcpy(llq->id, info->id, 8); llq->lease = info->origLease; } q->LastQTime = timenow; info->retry = timenow + (kLLQ_INIT_RESEND * info->ntries * mDNSPlatformOneSecond); if (constructQueryMsg(&response, &responsePtr, q)) goto error; responsePtr = putLLQ(&response, responsePtr, q, llq, mDNSfalse); if (!responsePtr) { LogMsg("ERROR: sendChallengeResponse - putLLQ"); goto error; } err = mDNSSendDNSMessage(m, &response, responsePtr, q->InterfaceID, &info->servAddr, info->servPort); if (err) LogMsg("ERROR: sendChallengeResponse - mDNSSendDNSMessage returned %d", err); // on error, we procede as normal and retry after the appropriate interval return; error: info->state = LLQ_Error; } mDNSlocal void hndlRequestChallenge(mDNS *m, DNSMessage *pktMsg, const mDNSu8 *end, LLQOptData *llq, DNSQuestion *q) { LLQ_Info *info = q->uDNS_info.llq; mDNSs32 timenow = mDNSPlatformTimeNow(); switch(llq->err) { case LLQErr_NoError: break; case LLQErr_ServFull: LogMsg("hndlRequestChallenge - received ServFull from server for LLQ %s. Retry in %d sec", q->qname.c, llq->lease); info->retry = timenow + ((mDNSs32)llq->lease * mDNSPlatformOneSecond); info->state = LLQ_Retry; simpleResponseHndlr(m, pktMsg, end, q, NULL); // get available answers info->deriveRemovesOnResume = mDNStrue; case LLQErr_Static: info->state = LLQ_Static; LogMsg("LLQ %s: static", q->qname.c); simpleResponseHndlr(m, pktMsg, end, q, NULL); return; case LLQErr_FormErr: LogMsg("ERROR: hndlRequestChallenge - received FormErr from server for LLQ %s", q->qname.c); goto error; case LLQErr_BadVers: LogMsg("ERROR: hndlRequestChallenge - received BadVers from server"); goto error; case LLQErr_UnknownErr: LogMsg("ERROR: hndlRequestChallenge - received UnknownErr from server for LLQ %s", q->qname.c); goto error; default: LogMsg("ERROR: hndlRequestChallenge - received invalid error %d for LLQ %s", llq->err, q->qname.c); goto error; } if (info->origLease != llq->lease) LogMsg("hndlRequestChallenge: requested lease %d, granted lease %d", info->origLease, llq->lease); // cache expiration in case we go to sleep before finishing setup info->origLease = llq->lease; info->expire = timenow + ((mDNSs32)llq->lease * mDNSPlatformOneSecond); // update state and timestamp info->state = LLQ_SecondaryRequest; umemcpy(info->id, llq->id, 8); info->ntries = 0; // first attempt to send response sendChallengeResponse(m, q, llq); return; error: info->state = LLQ_Error; } // response handler for initial and secondary setup responses mDNSlocal void recvSetupResponse(mDNS *m, DNSMessage *pktMsg, const mDNSu8 *end, DNSQuestion *q, void *clientContext) { DNSQuestion pktQuestion; LLQOptData llq; const mDNSu8 *ptr = pktMsg->data; LLQ_Info *info = q->uDNS_info.llq; mDNSu8 rcode = (mDNSu8)(pktMsg->h.flags.b[1] & kDNSFlag1_RC); (void)clientContext; // unused if (rcode && rcode != kDNSFlag1_RC_NXDomain) { LogMsg("LLQ Setup for %s failed with rcode %d. Reverting to polling mode", q->qname.c, rcode); info->state = LLQ_Poll; q->uDNS_info.responseCallback = simpleResponseHndlr; q->LastQTime = mDNSPlatformTimeNow(); q->ThisQInterval = 1; return; } ptr = getQuestion(pktMsg, ptr, end, 0, &pktQuestion); if (!ptr) { LogMsg("ERROR: recvSetupResponse - getQuestion"); goto error; } if (!SameDomainName(&q->qname, &pktQuestion.qname)) { LogMsg("ERROR: recvSetupResponse - mismatched question in response for llq setup %s", q->qname.c); goto error; } if (!getLLQAtIndex(m, pktMsg, end, &llq, 0)) { LogMsg("ERROR: recvSetupResponse - GetLLQAtIndex"); goto error; } if (llq.llqOp != kLLQ_Setup) { LogMsg("ERROR: recvSetupResponse - bad op %d", llq.llqOp); goto error; } if (llq.vers != kLLQ_Vers) { LogMsg("ERROR: recvSetupResponse - bad vers %d", llq.vers); goto error; } if (info->state == LLQ_InitialRequest) { hndlRequestChallenge(m, pktMsg, end, &llq, q); return; } if (info->state == LLQ_SecondaryRequest) { hndlChallengeResponseAck(m, pktMsg, end, &llq, q); return; } LogMsg("recvSetupResponse - bad state %d", info->state); error: info->state = LLQ_Error; } mDNSlocal void startLLQHandshake(mDNS *m, LLQ_Info *info) { DNSMessage msg; mDNSu8 *end; LLQOptData llqData; DNSQuestion *q = info->question; mStatus err; mDNSs32 timenow = mDNSPlatformTimeNow(); if (info->ntries++ == kLLQ_MAX_TRIES) { LogMsg("startLLQHandshake: %d failed attempts for LLQ %s. Will re-try in %d minutes", kLLQ_MAX_TRIES, q->qname.c, kLLQ_DEF_RETRY / 60); info->state = LLQ_Retry; info->retry = timenow + (kLLQ_DEF_RETRY * mDNSPlatformOneSecond); // !!!KRS give a callback error in these cases? return; } // set llq rdata llqData.vers = kLLQ_Vers; llqData.llqOp = kLLQ_Setup; llqData.err = LLQErr_NoError; ubzero(llqData.id, 8); llqData.lease = kLLQ_DefLease; initializeQuery(&msg, q); end = putLLQ(&msg, msg.data, q, &llqData, mDNStrue); if (!end) { LogMsg("ERROR: startLLQHandshake - putLLQ"); info->state = LLQ_Error; return; } err = mDNSSendDNSMessage(m, &msg, end, q->InterfaceID, &info->servAddr, info->servPort); if (err) LogMsg("ERROR: startLLQHandshake - mDNSSendDNSMessage returned %d", err); // on error, we procede as normal and retry after the appropriate interval // update question/info state info->state = LLQ_InitialRequest; info->origLease = kLLQ_DefLease; info->retry = timenow + (kLLQ_INIT_RESEND * mDNSPlatformOneSecond); q->LastQTime = timenow; q->uDNS_info.responseCallback = recvSetupResponse; q->uDNS_info.internal = mDNStrue; } // wrapper for startLLQHandshake, invoked by async op callback mDNSlocal void startLLQHandshakeCallback(mStatus err, mDNS *const m, void *llqInfo, const AsyncOpResult *result) { LLQ_Info *info = (LLQ_Info *)llqInfo; const zoneData_t *zoneInfo = &result->zoneData; if (err) { LogMsg("ERROR: startLLQHandshakeCallback invoked with error code %d", err); info->state = LLQ_Poll; info->question->LastQTime = 0; // trigger immediate poll info->question->ThisQInterval = INIT_UCAST_POLL_INTERVAL; return; } if (info->state == LLQ_Cancelled) { // StopQuery was called while we were getting the zone info LogMsg("startLLQHandshake - LLQ Cancelled."); info->question = NULL; // question may be deallocated ufree(info); return; } if (info->state != LLQ_GetZoneInfo) { LogMsg("ERROR: startLLQHandshake - bad state %d", info->state); return; } // cache necessary zone data info->servAddr.type = zoneInfo->primaryAddr.type; info->servAddr.ip.v4.NotAnInteger = zoneInfo->primaryAddr.ip.v4.NotAnInteger; info->servPort.NotAnInteger = zoneInfo->llqPort.NotAnInteger; info->ntries = 0; startLLQHandshake(m, info); } mDNSlocal mStatus startLLQ(mDNS *m, DNSQuestion *question) { LLQ_Info *info; mStatus err = mStatus_NoError; // allocate / init info struct info = umalloc(sizeof(LLQ_Info)); if (!info) { LogMsg("ERROR: startLLQ - malloc"); return mStatus_NoMemoryErr; } ubzero(info, sizeof(LLQ_Info)); info->state = LLQ_GetZoneInfo; // link info/question info->question = question; question->uDNS_info.llq = info; question->uDNS_info.responseCallback = llqResponseHndlr; err = startGetZoneData(&question->qname, m, mDNSfalse, mDNStrue, startLLQHandshakeCallback, info); if (err) { LogMsg("ERROR: startLLQ - startGetZoneData returned %d", err); info->question = NULL; ufree(info); question->uDNS_info.llq = NULL; return err; } LinkActiveQuestion(&m->uDNS_info, question); return err; } mDNSlocal mDNSBool recvLLQResponse(mDNS *m, DNSMessage *msg, const mDNSu8 *end, const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSInterfaceID InterfaceID) { DNSQuestion pktQ, *q; uDNS_GlobalInfo *u = &m->uDNS_info; const mDNSu8 *ptr = msg->data; LLQ_Info *llqInfo; if (!msg->h.numQuestions) return mDNSfalse; ptr = getQuestion(msg, ptr, end, 0, &pktQ); if (!ptr) return mDNSfalse; pktQ.uDNS_info.id = msg->h.id; // !!!KRS we should do a table lookup to quickly determine if this packet is for an LLQ q = u->ActiveQueries; while (q) { llqInfo = q->uDNS_info.llq; if (q->LongLived && llqInfo && q->qnamehash == pktQ.qnamehash && q->qtype == pktQ.qtype && SameDomainName(&q->qname, &pktQ.qname)) { u->CurrentQuery = q; if (llqInfo->state == LLQ_Established || (llqInfo->state == LLQ_Refresh && msg->h.numAnswers)) { recvLLQEvent(m, q, msg, end, srcaddr, srcport, InterfaceID); return mDNStrue; } else if (msg->h.id.NotAnInteger != q->uDNS_info.id.NotAnInteger) { q = q->next; continue; } else if (llqInfo->state == LLQ_Refresh && msg->h.numAdditionals && !msg->h.numAnswers) { recvRefreshReply(m, msg, end, q); return mDNStrue; } else if (llqInfo->state < LLQ_Static) { q->uDNS_info.responseCallback(m, msg, end, q, q->uDNS_info.context); return mDNStrue; } } q = q->next; } return mDNSfalse; } mDNSexport mDNSBool IsActiveUnicastQuery(DNSQuestion *const question, uDNS_GlobalInfo *u) { DNSQuestion *q; for (q = u->ActiveQueries; q; q = q->next) { if (q == question) { if (!question->uDNS_info.id.NotAnInteger || question->InterfaceID || IsLocalDomain(&question->qname)) LogMsg("Warning: Question %s in Active Unicast Query list with id %d, interfaceID %x", question->qname.c, question->uDNS_info.id.NotAnInteger, question->InterfaceID); return mDNStrue; } } return mDNSfalse; } // stopLLQ happens IN ADDITION to stopQuery mDNSlocal void stopLLQ(mDNS *m, DNSQuestion *question) { LLQ_Info *info = question->uDNS_info.llq; (void)m; // unused if (!question->LongLived) { LogMsg("ERROR: stopLLQ - LongLived flag not set"); return; } if (!info) { LogMsg("ERROR: stopLLQ - llq info is NULL"); return; } switch (info->state) { case LLQ_UnInit: LogMsg("ERROR: stopLLQ - state LLQ_UnInit"); //!!!KRS should we unlink info<->question here? return; case LLQ_GetZoneInfo: info->question = NULL; // remove ref to question, as it may be freed when we get called back from async op info->state = LLQ_Cancelled; return; case LLQ_Established: case LLQ_Refresh: // refresh w/ lease 0 sendLLQRefresh(m, question, 0); goto free_info; default: debugf("stopLLQ - silently discarding LLQ in state %d", info->state); goto free_info; } free_info: info->question = NULL; ufree(info); question->uDNS_info.llq = NULL; } mDNSexport mStatus uDNS_StopQuery(mDNS *const m, DNSQuestion *const question) { uDNS_GlobalInfo *u = &m->uDNS_info; DNSQuestion *qptr, *prev = NULL; CacheRecord *ka; qptr = u->ActiveQueries; while (qptr) { if (qptr == question) { if (question->LongLived && question->uDNS_info.llq) stopLLQ(m, question); if (m->uDNS_info.CurrentQuery == question) m->uDNS_info.CurrentQuery = m->uDNS_info.CurrentQuery->next; while (question->uDNS_info.knownAnswers) { ka = question->uDNS_info.knownAnswers; question->uDNS_info.knownAnswers = question->uDNS_info.knownAnswers->next; ufree(ka); } if (prev) prev->next = question->next; else u->ActiveQueries = question->next; return mStatus_NoError; } prev = qptr; qptr = qptr->next; } LogMsg("uDNS_StopQuery: no such active query (%s)", question->qname.c); return mStatus_UnknownErr; } mDNSexport void uDNS_SuspendLLQs(mDNS *m) { DNSQuestion *q; LLQ_Info *llq; for (q = m->uDNS_info.ActiveQueries; q; q = q->next) { llq = q->uDNS_info.llq; if (q->LongLived && llq && llq->state < LLQ_Suspended) { if (llq->state == LLQ_Established || llq->state == LLQ_Refresh) sendLLQRefresh(m, q, 0); // note that we suspend LLQs in setup states too if (llq->state != LLQ_Retry) llq->state = LLQ_Suspended; } } } extern void uDNS_RestartLLQs(mDNS *m) { uDNS_GlobalInfo *u = &m->uDNS_info; DNSQuestion *q; LLQ_Info *llqInfo; u->CurrentQuery = u->ActiveQueries; while (u->CurrentQuery) { q = u->CurrentQuery; u->CurrentQuery = u->CurrentQuery->next; llqInfo = q->uDNS_info.llq; if (q->LongLived && llqInfo && llqInfo->state == LLQ_Suspended) { llqInfo->ntries = 0; llqInfo->deriveRemovesOnResume = mDNStrue; startLLQHandshake(m, llqInfo); } } } mDNSlocal mStatus startQuery(mDNS *const m, DNSQuestion *const question, mDNSBool internal) { uDNS_GlobalInfo *u = &m->uDNS_info; DNSMessage msg; mDNSu8 *endPtr; mStatus err = mStatus_NoError; const mDNSAddr *server; //!!!KRS we should check if the question is already in our acivequestion list if (!ValidateDomainName(&question->qname)) { LogMsg("Attempt to start query with invalid qname %##s %s", question->qname.c, DNSTypeName(question->qtype)); return mStatus_Invalid; } question->next = NULL; question->qnamehash = DomainNameHashValue(&question->qname); // to do quick domain name comparisons question->uDNS_info.id = newMessageID(u); // break here if its and LLQ if (question->LongLived) return startLLQ(m, question); err = constructQueryMsg(&msg, &endPtr, question); if (err) return err; // else send the query to our server question->LastQTime = mDNSPlatformTimeNow(); question->ThisQInterval = INIT_UCAST_POLL_INTERVAL; // store the question/id in active question list question->uDNS_info.timestamp = question->LastQTime; question->uDNS_info.internal = internal; LinkActiveQuestion(u, question); question->uDNS_info.knownAnswers = NULL; server = getInitializedDNS(u); if (!server) { LogMsg("startQuery - no initialized DNS"); err = mStatus_NotInitializedErr; } else err = mDNSSendDNSMessage(m, &msg, endPtr, question->InterfaceID, server, UnicastDNSPort); if (err) { LogMsg("ERROR: startQuery - %d (keeping question in list for retransmission", err); } return err; } mDNSexport mStatus uDNS_StartQuery(mDNS *const m, DNSQuestion *const question) { ubzero(&question->uDNS_info, sizeof(uDNS_QuestionInfo)); question->uDNS_info.responseCallback = simpleResponseHndlr; question->uDNS_info.context = NULL; return startQuery(m, question, 0); } // explicitly set response handler mDNSlocal mStatus startInternalQuery(DNSQuestion *q, mDNS *m, InternalResponseHndlr callback, void *hndlrContext) { ubzero(&q->uDNS_info, sizeof(uDNS_QuestionInfo)); q->QuestionContext = hndlrContext; q->uDNS_info.responseCallback = callback; q->uDNS_info.context = hndlrContext; return startQuery(m, q, 1); } // *************************************************************************** #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - Domain -> Name Server Conversion #endif /* startGetZoneData * * asyncronously find the address of the nameserver for the enclosing zone for a given domain name, * i.e. the server to which update and LLQ requests will be sent for a given name. Once the address is * derived, it will be passed to the callback, along with a context pointer. If the zone cannot * be determined or if an error occurs, an all-zeros address will be passed and a message will be * written to the syslog. * * If the FindUpdatePort arg is set, the port on which the server accepts dynamic updates is determined * by querying for the _update._dns-sd._udp.. SRV record. Likewise, if the FindLLQPort arg is set, * the port on which the server accepts long lived queries is determined by querying for _llq._dns-sd. * _udp.. record. If either of these queries fail, or flags are not specified, the llqPort and * updatePort fields in the result structure are set to zero. * * Steps for deriving the zone name are as follows: * * Query for an SOA record for the required domain. If we don't get an answer (or an SOA in the Authority * section), we strip the leading label from the name and repeat, until we get an answer. * * The name of the SOA record is our enclosing zone. The mname field in the SOA rdata is the domain * name of the primary NS. * * We verify that there is an NS record with this zone for a name and the mname for its rdata. * (!!!KRS this seems redundant, but BIND does this, and it should normally be zero-overhead since * the NS query will get us address records in the additionals section, which we'd otherwise have to * explicitly query for.) * * We then query for the address record for this nameserver (if it is not in the addionals section of * the NS record response.) */ // state machine types and structs // // state machine states typedef enum { init, lookupSOA, foundZone, lookupNS, foundNS, lookupA, foundA, lookupPort, foundPort, complete } ntaState; // state machine actions typedef enum { smContinue, // continue immediately to next state smBreak, // break until next packet/timeout smError // terminal error - cleanup and abort } smAction; typedef struct { domainname origName; // name we originally try to convert domainname *curSOA; // name we have an outstanding SOA query for ntaState state; // determines what we do upon receiving a packet mDNS *m; domainname zone; // left-hand-side of SOA record mDNSu16 zoneClass; domainname ns; // mname in SOA rdata, verified in confirmNS state mDNSv4Addr addr; // address of nameserver DNSQuestion question; // storage for any active question DNSQuestion extraQuestion; // additional storage mDNSBool questionActive; // if true, StopQuery() can be called on the question field mDNSBool findUpdatePort; mDNSBool findLLQPort; mDNSIPPort updatePort; mDNSIPPort llqPort; AsyncOpCallback *callback; // caller specified function to be called upon completion void *callbackInfo; } ntaContext; // function prototypes (for routines that must be used as fn pointers prior to their definitions, // and allows states to be read top-to-bottom in logical order) mDNSlocal void getZoneData(mDNS *const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, void *contextPtr); mDNSlocal smAction hndlLookupSOA(DNSMessage *msg, const mDNSu8 *end, ntaContext *context); mDNSlocal void processSOA(ntaContext *context, ResourceRecord *rr); mDNSlocal smAction confirmNS(DNSMessage *msg, const mDNSu8 *end, ntaContext *context); mDNSlocal smAction lookupNSAddr(DNSMessage *msg, const mDNSu8 *end, ntaContext *context); mDNSlocal smAction hndlLookupPorts(DNSMessage *msg, const mDNSu8 *end, ntaContext *context); // initialization mDNSlocal mStatus startGetZoneData(domainname *name, mDNS *m, mDNSBool findUpdatePort, mDNSBool findLLQPort, AsyncOpCallback callback, void *callbackInfo) { ntaContext *context = (ntaContext*)umalloc(sizeof(ntaContext)); if (!context) { LogMsg("ERROR: startGetZoneData - umalloc failed"); return mStatus_NoMemoryErr; } ubzero(context, sizeof(ntaContext)); ustrcpy(context->origName.c, name->c); context->state = init; context->m = m; context->callback = callback; context->callbackInfo = callbackInfo; context->findUpdatePort = findUpdatePort; context->findLLQPort = findLLQPort; getZoneData(m, NULL, NULL, NULL, context); return mStatus_NoError; } // state machine entry routine mDNSlocal void getZoneData(mDNS *const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, void *contextPtr) { AsyncOpResult result; ntaContext *context = (ntaContext*)contextPtr; smAction action; // unused (void)m; (void)question; // stop any active question if (context->questionActive) { uDNS_StopQuery(context->m, &context->question); context->questionActive = mDNSfalse; } if (msg && msg->h.flags.b[2] >> 4 && msg->h.flags.b[2] >> 4 != kDNSFlag1_RC_NXDomain) { // rcode non-zero, non-nxdomain LogMsg("ERROR: getZoneData - received response w/ rcode %d", msg->h.flags.b[2] >> 4); goto error; } switch (context->state) { case init: case lookupSOA: action = hndlLookupSOA(msg, end, context); if (action == smError) goto error; if (action == smBreak) return; case foundZone: case lookupNS: action = confirmNS(msg, end, context); if (action == smError) goto error; if (action == smBreak) return; case foundNS: case lookupA: action = lookupNSAddr(msg, end, context); if (action == smError) goto error; if (action == smBreak) return; case foundA: if (!context->findUpdatePort && !context->findLLQPort) { context->state = complete; break; } case lookupPort: action = hndlLookupPorts(msg, end, context); if (action == smError) goto error; if (action == smBreak) return; if (action == smContinue) context->state = complete; case foundPort: case complete: break; } if (context->state != complete) { LogMsg("ERROR: getZoneData - exited state machine with state %d", context->state); goto error; } result.type = zoneDataResult; result.zoneData.primaryAddr.ip.v4.NotAnInteger = context->addr.NotAnInteger; result.zoneData.primaryAddr.type = mDNSAddrType_IPv4; ustrcpy(result.zoneData.zoneName.c, context->zone.c); result.zoneData.zoneClass = context->zoneClass; result.zoneData.llqPort = context->findLLQPort ? context->llqPort : zeroIPPort; result.zoneData.updatePort = context->findUpdatePort ? context->updatePort : zeroIPPort; context->callback(mStatus_NoError, context->m, context->callbackInfo, &result); goto cleanup; error: if (context && context->callback) context->callback(mStatus_UnknownErr, context->m, context->callbackInfo, NULL); cleanup: if (context && context->questionActive) { uDNS_StopQuery(context->m, &context->question); context->questionActive = mDNSfalse; } if (context) ufree(context); } mDNSlocal smAction hndlLookupSOA(DNSMessage *msg, const mDNSu8 *end, ntaContext *context) { mStatus err; LargeCacheRecord lcr; ResourceRecord *rr = &lcr.r.resrec; DNSQuestion *query = &context->question; const mDNSu8 *ptr; if (msg) { // if msg contains SOA record in answer or authority sections, update context/state and return int i; ptr = LocateAnswers(msg, end); for (i = 0; i < msg->h.numAnswers; i++) { ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); if (!ptr) { LogMsg("ERROR: hndlLookupSOA, Answers - GetLargeResourceRecord returned NULL"); return smError; } if (rr->rrtype == kDNSType_SOA && SameDomainName(context->curSOA, &rr->name)) { processSOA(context, rr); return smContinue; } } ptr = LocateAuthorities(msg, end); // SOA not in answers, check in authority for (i = 0; i < msg->h.numAuthorities; i++) { ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); ///!!!KRS using type PacketAns for auth if (!ptr) { LogMsg("ERROR: hndlLookupSOA, Authority - GetLargeResourceRecord returned NULL"); return smError; } if (rr->rrtype == kDNSType_SOA) { processSOA(context, rr); return smContinue; } } } if (context->state != init && !context->curSOA->c[0]) { // we've gone down to the root and have not found an SOA LogMsg("ERROR: hndlLookupSOA - recursed to root label of %s without finding SOA", context->origName.c); return smError; } ubzero(query, sizeof(DNSQuestion)); // chop off leading label unless this is our first try if (context->state == init) context->curSOA = &context->origName; else context->curSOA = (domainname *)(context->curSOA->c + context->curSOA->c[0]+1); context->state = lookupSOA; ustrcpy(query->qname.c, context->curSOA->c); query->qtype = kDNSType_SOA; query->qclass = kDNSClass_IN; err = startInternalQuery(query, context->m, getZoneData, context); context->questionActive = mDNStrue; if (err) LogMsg("hndlLookupSOA: startInternalQuery returned error %d (breaking until next periodic retransmission)", err); return smBreak; // break from state machine until we receive another packet } mDNSlocal void processSOA(ntaContext *context, ResourceRecord *rr) { ustrcpy(context->zone.c, rr->name.c); context->zoneClass = rr->rrclass; ustrcpy(context->ns.c, rr->rdata->u.soa.mname.c); context->state = foundZone; } mDNSlocal smAction confirmNS(DNSMessage *msg, const mDNSu8 *end, ntaContext *context) { DNSQuestion *query = &context->question; mStatus err; LargeCacheRecord lcr; ResourceRecord *rr = &lcr.r.resrec; const mDNSu8 *ptr; int i; if (context->state == foundZone) { // we've just learned the zone. confirm that an NS record exists ustrcpy(query->qname.c, context->zone.c); query->qtype = kDNSType_NS; query->qclass = kDNSClass_IN; err = startInternalQuery(query, context->m, getZoneData, context); context->questionActive = mDNStrue; if (err) LogMsg("confirmNS: startInternalQuery returned error %d (breaking until next periodic retransmission", err); context->state = lookupNS; return smBreak; // break from SM until we receive another packet } else if (context->state == lookupNS) { ptr = LocateAnswers(msg, end); for (i = 0; i < msg->h.numAnswers; i++) { ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); if (!ptr) { LogMsg("ERROR: confirmNS, Answers - GetLargeResourceRecord returned NULL"); return smError; } if (rr->rrtype == kDNSType_NS && SameDomainName(&context->zone, &rr->name) && SameDomainName(&context->ns, &rr->rdata->u.name)) { context->state = foundNS; return smContinue; // next routine will examine additionals section of A record } } LogMsg("ERROR: could not confirm existance of NS record %s", context->zone.c); return smError; } else { LogMsg("ERROR: confirmNS - bad state %d", context->state); return smError; } } mDNSlocal smAction queryNSAddr(ntaContext *context) { mStatus err; DNSQuestion *query = &context->question; ustrcpy(query->qname.c, context->ns.c); query->qtype = kDNSType_A; query->qclass = kDNSClass_IN; err = startInternalQuery(query, context->m, getZoneData, context); context->questionActive = mDNStrue; if (err) LogMsg("confirmNS: startInternalQuery returned error %d (breaking until next periodic retransmission)", err); context->state = lookupA; return smBreak; } mDNSlocal smAction lookupNSAddr(DNSMessage *msg, const mDNSu8 *end, ntaContext *context) { const mDNSu8 *ptr; int i; LargeCacheRecord lcr; ResourceRecord *rr = &lcr.r.resrec; if (context->state == foundNS) { // we just found the NS record - look for the corresponding A record in the Additionals section if (!msg->h.numAdditionals) return queryNSAddr(context); ptr = LocateAdditionals(msg, end); if (!ptr) { LogMsg("ERROR: lookupNSAddr - LocateAdditionals returned NULL, expected %d additionals", msg->h.numAdditionals); return queryNSAddr(context); } else { for (i = 0; i < msg->h.numAdditionals; i++) { ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); if (!ptr) { LogMsg("ERROR: lookupNSAddr, Additionals - GetLargeResourceRecord returned NULL"); return queryNSAddr(context); } if (rr->rrtype == kDNSType_A && SameDomainName(&context->ns, &rr->name)) { context->addr.NotAnInteger = rr->rdata->u.ip.NotAnInteger; context->state = foundA; return smContinue; } } } // no A record in Additionals - query the server return queryNSAddr(context); } else if (context->state == lookupA) { ptr = LocateAnswers(msg, end); if (!ptr) { LogMsg("ERROR: lookupNSAddr: LocateAnswers returned NULL"); return smError; } for (i = 0; i < msg->h.numAnswers; i++) { ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); if (!ptr) { LogMsg("ERROR: lookupNSAddr, Answers - GetLargeResourceRecord returned NULL"); break; } if (rr->rrtype == kDNSType_A && SameDomainName(&context->ns, &rr->name)) { context->addr.NotAnInteger = rr->rdata->u.ip.NotAnInteger; context->state = foundA; return smContinue; } } LogMsg("ERROR: lookupNSAddr: Address record not found in answer section"); return smError; } else { LogMsg("ERROR: lookupNSAddr - bad state %d", context->state); return smError; } } mDNSlocal smAction lookupDNSPort(DNSMessage *msg, const mDNSu8 *end, ntaContext *context, char *portName, mDNSIPPort *port) { int i; LargeCacheRecord lcr; const mDNSu8 *ptr; DNSQuestion *q; mStatus err; if (context->state == lookupPort) // we've already issued the query { if (!msg) { LogMsg("ERROR: hndlLookupUpdatePort - NULL message"); return smError; } ptr = LocateAnswers(msg, end); for (i = 0; i < msg->h.numAnswers; i++) { ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); if (!ptr) { LogMsg("ERROR: hndlLookupUpdatePort - GetLargeResourceRecord returned NULL"); return smError; } if (ResourceRecordAnswersQuestion(&lcr.r.resrec, &context->question)) { port->NotAnInteger = lcr.r.resrec.rdata->u.srv.port.NotAnInteger; context->state = foundPort; return smContinue; } } LogMsg("hndlLookupUpdatePort %s - answer not contained in reply. Guessing port %d", portName, UnicastDNSPort); *port = UnicastDNSPort; context->state = foundPort; return smContinue; } // query the server for the update port for the zone context->state = lookupPort; q = &context->question; MakeDomainNameFromDNSNameString(&q->qname, portName); ustrcpy((q->qname.c + ustrlen(q->qname.c)), context->zone.c); q->qtype = kDNSType_SRV; q->qclass = kDNSClass_IN; err = startInternalQuery(q, context->m, getZoneData, context); context->questionActive = mDNStrue; if (err) LogMsg("hndlLookupSOA: startInternalQuery returned error %d (breaking until next periodic retransmission)", err); return smBreak; // break from state machine until we receive another packet } mDNSlocal smAction hndlLookupPorts(DNSMessage *msg, const mDNSu8 *end, ntaContext *context) { smAction action; if (context->findUpdatePort && !context->updatePort.NotAnInteger) { action = lookupDNSPort(msg, end, context, UPDATE_PORT_NAME, &context->updatePort); if (action != smContinue) return action; } if (context->findLLQPort && !context->llqPort.NotAnInteger) return lookupDNSPort(msg, end, context, LLQ_PORT_NAME, &context->llqPort); return smContinue; } // *************************************************************************** #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - Truncation Handling #endif typedef struct { DNSQuestion *question; DNSMessage reply; mDNSu16 replylen; int nread; mDNS *m; } tcpInfo_t; // issue queries over a conected socket mDNSlocal void conQueryCallback(int sd, void *context, mDNSBool ConnectionEstablished) { mStatus err = 0; char msgbuf[356]; // 96 (hdr) + 256 (domain) + 4 (class/type) DNSMessage *msg; mDNSu8 *end; tcpInfo_t *info = (tcpInfo_t *)context; DNSQuestion *question = info->question; int n; question->uDNS_info.id.NotAnInteger = (mDNSu16)~0; if (ConnectionEstablished) { // connection is established - send the message msg = (DNSMessage *)&msgbuf; err = constructQueryMsg(msg, &end, question); if (err) { LogMsg("ERROR: conQueryCallback: constructQueryMsg - %d", err); goto error; } err = mDNSSendDNSMessage_tcp(info->m, msg, end, sd); if (err) { LogMsg("ERROR: conQueryCallback: mDNSSendDNSMessage_tcp - %d", err); goto error; } return; } else { if (!info->nread) { // read msg len n = mDNSPlatformReadTCP(sd, &info->replylen, 2); if (n != 2) { LogMsg("ERROR:conQueryCallback - attempt to read message length failed (read returned %d)", n); goto error; } } n = mDNSPlatformReadTCP(sd, ((char *)&info->reply) + info->nread, info->replylen - info->nread); if (n < 0) { LogMsg("ERROR: conQueryCallback - read returned %d", n); goto error; } info->nread += n; if (info->nread == info->replylen) { // finished reading message receiveMsg(info->m, &info->reply, ((mDNSu8 *)&info->reply) + info->replylen, question->InterfaceID); mDNSPlatformTCPCloseConnection(sd); ufree(info); return; } else return; } return; error: mDNSPlatformTCPCloseConnection(sd); ufree(info); } mDNSlocal void hndlTruncatedAnswer(DNSQuestion *question, const mDNSAddr *src, mDNS *m) { mStatus connectionStatus; uDNS_QuestionInfo *info = &question->uDNS_info; int sd; tcpInfo_t *context; context = (tcpInfo_t *)umalloc(sizeof(tcpInfo_t)); if (!context) { LogMsg("ERROR: hndlTruncatedAnswer - memallocate failed"); return; } ubzero(context, sizeof(tcpInfo_t)); context->question = question; context->m = m; info->id.NotAnInteger = (mDNSu16)~0; // all 1's indicates TCP queries info->timestamp = mDNSPlatformTimeNow(); // reset timestamp connectionStatus = mDNSPlatformTCPConnect(src, UnicastDNSPort, question->InterfaceID, conQueryCallback, context, &sd); if (connectionStatus == mStatus_ConnectionEstablished) // manually invoke callback if connection completes { conQueryCallback(sd, context, mDNStrue); return; } if (connectionStatus == mStatus_ConnectionPending) return; // callback will be automatically invoked when connection completes LogMsg("hndlTruncatedAnswer: connection failed"); uDNS_StopQuery(m, question); //!!!KRS can we really call this here? } // *************************************************************************** #if COMPILER_LIKES_PRAGMA_MARK #pragma mark - Dynamic Updates #endif mDNSlocal mDNSu8 *putZone(DNSMessage *const msg, mDNSu8 *ptr, mDNSu8 *limit, const domainname *zone, mDNSOpaque16 zoneClass) { ptr = putDomainNameAsLabels(msg, ptr, limit, zone); if (!ptr || ptr + 4 > limit) return NULL; // If we're out-of-space, return NULL ((mDNSOpaque16 *)ptr)->NotAnInteger = kDNSType_SOA; ptr += 2; ((mDNSOpaque16 *)ptr)->NotAnInteger = zoneClass.NotAnInteger; ptr += 2; msg->h.mDNS_numZones++; return ptr; } mDNSlocal mDNSu8 *putPrereqNameNotInUse(domainname *name, DNSMessage *msg, mDNSu8 *ptr, mDNSu8 *end) { AuthRecord prereq; ubzero(&prereq, sizeof(AuthRecord)); ustrcpy(prereq.resrec.name.c, name->c); prereq.resrec.rrtype = kDNSQType_ANY; prereq.resrec.rrclass = kDNSClass_NONE; ptr = putEmptyResourceRecord(msg, ptr, end, &msg->h.mDNS_numPrereqs, &prereq); return ptr; } mDNSlocal mDNSu8 *putDeletionRecord(DNSMessage *msg, mDNSu8 *ptr, ResourceRecord *rr) { mDNSu16 origclass; // deletion: specify record w/ TTL 0, class NONE origclass = rr->rrclass; rr->rrclass = kDNSClass_NONE; ptr = PutResourceRecordTTL(msg, ptr, &msg->h.mDNS_numUpdates, rr, 0); rr->rrclass = origclass; return ptr; } mDNSlocal mDNSu8 *putUpdateLease(DNSMessage *msg, mDNSu8 *end) { AuthRecord rr; ResourceRecord *opt = &rr.resrec; rdataOpt *optRD; ubzero(&rr, sizeof(AuthRecord)); opt->rdata = &rr.rdatastorage; opt->RecordType = kDNSRecordTypeKnownUnique; // to avoid warnings in other layers opt->rrtype = kDNSType_OPT; opt->rdlength = LEASE_OPT_SIZE; opt->rdestimate = LEASE_OPT_SIZE; optRD = &rr.resrec.rdata->u.opt; optRD->opt = kDNSOpt_Lease; optRD->optlen = sizeof(mDNSs32); optRD->OptData.lease = kUpdate_DefLease; end = PutResourceRecordTTL(msg, end, &msg->h.numAdditionals, opt, 0); if (!end) { LogMsg("ERROR: putUpdateLease - PutResourceRecordTTL"); return NULL; } return end; } mDNSlocal void sendRecordRegistration(mDNS *const m, AuthRecord *rr) { DNSMessage msg; mDNSu8 *ptr = msg.data; mDNSu8 *end = (mDNSu8 *)&msg + sizeof(DNSMessage); uDNS_GlobalInfo *u = &m->uDNS_info; mDNSOpaque16 id; uDNS_AuthInfo *authInfo; uDNS_RegInfo *regInfo = &rr->uDNS_info; mStatus err = mStatus_UnknownErr; id = newMessageID(u); InitializeDNSMessage(&msg.h, id, UpdateReqFlags); rr->uDNS_info.id.NotAnInteger = id.NotAnInteger; // set zone ptr = putZone(&msg, ptr, end, ®Info->zone, mDNSOpaque16fromIntVal(rr->resrec.rrclass)); if (!ptr) goto error; if (rr->resrec.RecordType == kDNSRecordTypeKnownUnique || rr->uDNS_info.state == regState_Refresh) { // KnownUnique means the record must ALREADY exist, as does refresh // prereq: record must exist (put record in prereq section w/ TTL 0) ptr = PutResourceRecordTTL(&msg, ptr, &msg.h.mDNS_numPrereqs, &rr->resrec, 0); if (!ptr) goto error; } else if (rr->resrec.RecordType != kDNSRecordTypeShared) { ptr = putPrereqNameNotInUse(&rr->resrec.name, &msg, ptr, end); if (!ptr) goto error; } ptr = PutResourceRecord(&msg, ptr, &msg.h.mDNS_numUpdates, &rr->resrec); if (!ptr) goto error; if (rr->uDNS_info.lease) ptr = putUpdateLease(&msg, ptr); rr->uDNS_info.expire = -1; authInfo = GetAuthInfoForZone(u, ®Info->zone); if (authInfo) { err = mDNSSendSignedDNSMessage(m, &msg, ptr, 0, ®Info->ns, regInfo->port, authInfo); if (err) { LogMsg("ERROR: sendRecordRegistration - mDNSSendSignedDNSMessage - %d", err); goto error; } } else { err = mDNSSendDNSMessage(m, &msg, ptr, 0, ®Info->ns, regInfo->port); if (err) { LogMsg("ERROR: sendRecordRegistration - mDNSSendDNSMessage - %d", err); goto error; } } if (regInfo->state != regState_Refresh) regInfo->state = regState_Pending; return; error: if (rr->uDNS_info.state != regState_Unregistered) { unlinkAR(&u->RecordRegistrations, rr); rr->uDNS_info.state = regState_Unregistered; } rr->RecordCallback(m, rr, err); // NOTE: not safe to touch any client structures here } mDNSlocal void RecordRegistrationCallback(mStatus err, mDNS *const m, void *authPtr, const AsyncOpResult *result) { AuthRecord *newRR = (AuthRecord*)authPtr; const zoneData_t *zoneData = &result->zoneData; uDNS_GlobalInfo *u = &m->uDNS_info; AuthRecord *ptr; for (ptr = u->RecordRegistrations; ptr; ptr = ptr->next) if (ptr == newRR) break; if (!ptr) { LogMsg("RecordRegistrationCallback - RR no longer in list. Discarding."); return; } if (err) { LogMsg("RecordRegistrationCallback: error %d", err); goto error; } if (newRR->uDNS_info.state == regState_Cancelled) { //!!!KRS we should send a memfree callback here! LogMsg("Registration of %s type %d cancelled prior to update", newRR->resrec.name.c, newRR->resrec.rrtype); newRR->uDNS_info.state = regState_Unregistered; unlinkAR(&u->RecordRegistrations, newRR); return; } if (result->type != zoneDataResult) { LogMsg("ERROR: buildUpdatePacket passed incorrect result type %d", result->type); goto error; } if (newRR->resrec.rrclass != zoneData->zoneClass) { LogMsg("ERROR: New resource record's class (%d) does not match zone class (%d)", newRR->resrec.rrclass, zoneData->zoneClass); goto error; } // cache zone data ustrcpy(newRR->uDNS_info.zone.c, zoneData->zoneName.c); newRR->uDNS_info.ns.type = mDNSAddrType_IPv4; newRR->uDNS_info.ns.ip.v4.NotAnInteger = zoneData->primaryAddr.ip.v4.NotAnInteger; newRR->uDNS_info.port.NotAnInteger = zoneData->updatePort.NotAnInteger; sendRecordRegistration(m, newRR); return; error: if (newRR->uDNS_info.state != regState_Unregistered) { unlinkAR(&u->RecordRegistrations, newRR); newRR->uDNS_info.state = regState_Unregistered; } newRR->RecordCallback(m, newRR, err); // NOTE: not safe to touch any client structures here } mDNSlocal mDNSBool setHostTarget(AuthRecord *rr, mDNS *m) { domainname *target; if (!rr->HostTarget) { debugf("Service %s - not updating host target", rr->resrec.name.c); return mDNSfalse; } // set SRV target target = GetRRDomainNameTarget(&rr->resrec); if (!target) { LogMsg("ERROR: setHostTarget: Can't set target of rrtype %d", rr->resrec.rrtype); return mDNSfalse; } if (SameDomainName(target, &m->uDNS_info.hostname)) { debugf("Host target for %s unchanged", rr->resrec.name.c); return mDNSfalse; } AssignDomainName(*target, m->uDNS_info.hostname); SetNewRData(&rr->resrec, NULL, 0); return mDNStrue; } mDNSlocal void SendServiceRegistration(mDNS *m, ServiceRecordSet *srs) { DNSMessage msg; mDNSu8 *ptr = msg.data; mDNSu8 *end = (mDNSu8 *)&msg + sizeof(DNSMessage); uDNS_GlobalInfo *u = &m->uDNS_info; mDNSOpaque16 id; uDNS_AuthInfo *authInfo; uDNS_RegInfo *rInfo = &srs->uDNS_info; mStatus err = mStatus_UnknownErr; id = newMessageID(u); InitializeDNSMessage(&msg.h, id, UpdateReqFlags); rInfo->id.NotAnInteger = id.NotAnInteger; // setup resource records if (setHostTarget(&srs->RR_SRV, m)) SetNewRData(&srs->RR_SRV.resrec, NULL, 0); // set rdlen/estimate/hash //SetNewRData(&srs->RR_ADV.resrec, NULL, 0); //!!!KRS SetNewRData(&srs->RR_PTR.resrec, NULL, 0); SetNewRData(&srs->RR_TXT.resrec, NULL, 0); // construct update packet // set zone ptr = putZone(&msg, ptr, end, &rInfo->zone, mDNSOpaque16fromIntVal(srs->RR_SRV.resrec.rrclass)); if (!ptr) goto error; if (srs->uDNS_info.state == regState_Refresh) { // prereq: record must exist (put record in prereq section w/ TTL 0) ptr = PutResourceRecordTTL(&msg, ptr, &msg.h.mDNS_numPrereqs, &srs->RR_SRV.resrec, 0); if (!ptr) goto error; } else { // use SRV for prereq ptr = putPrereqNameNotInUse(&srs->RR_SRV.resrec.name, &msg, ptr, end); if (!ptr) goto error; } //!!!KRS Need to do bounds checking and use TCP if it won't fit!!! //if (!(ptr = PutResourceRecord(&msg, ptr, &msg.h.mDNS_numUpdates, &srs->RR_ADV.resrec))) goto error; if (!(ptr = PutResourceRecord(&msg, ptr, &msg.h.mDNS_numUpdates, &srs->RR_PTR.resrec))) goto error; if (!(ptr = PutResourceRecord(&msg, ptr, &msg.h.mDNS_numUpdates, &srs->RR_SRV.resrec))) goto error; if (!(ptr = PutResourceRecord(&msg, ptr, &msg.h.mDNS_numUpdates, &srs->RR_TXT.resrec))) goto error; // !!!KRS do subtypes/extras etc. if (srs->uDNS_info.lease) ptr = putUpdateLease(&msg, ptr); srs->uDNS_info.expire = -1; authInfo = GetAuthInfoForZone(u, &rInfo->zone); if (authInfo) { err = mDNSSendSignedDNSMessage(m, &msg, ptr, 0, &rInfo->ns, rInfo->port, authInfo); if (err) { LogMsg("ERROR: SendServiceRegistration - mDNSSendSignedDNSMessage - %d", err); goto error; } } else { err = mDNSSendDNSMessage(m, &msg, ptr, 0, &rInfo->ns, rInfo->port); if (err) { LogMsg("ERROR: SendServiceRegistration - mDNSSendDNSMessage - %d", err); goto error; } } if (rInfo->state != regState_Refresh) rInfo->state = regState_Pending; return; error: unlinkSRS(u, srs); rInfo->state = regState_Unregistered; srs->ServiceCallback(m, srs, err); //!!!KRS will mem still be free'd on error? // NOTE: not safe to touch any client structures here } mDNSlocal void serviceRegistrationCallback(mStatus err, mDNS *const m, void *srsPtr, const AsyncOpResult *result) { ServiceRecordSet *srs = (ServiceRecordSet *)srsPtr; const zoneData_t *zoneData = &result->zoneData; uDNS_GlobalInfo *u = &m->uDNS_info; if (err) goto error; if (result->type != zoneDataResult) { LogMsg("ERROR: buildUpdatePacket passed incorrect result type %d", result->type); goto error; } if (srs->uDNS_info.state == regState_Cancelled) { // client cancelled registration while fetching zone data srs->uDNS_info.state = regState_Unregistered; unlinkSRS(u, srs); srs->ServiceCallback(m, srs, mStatus_MemFree); return; } if (srs->RR_SRV.resrec.rrclass != zoneData->zoneClass) { LogMsg("Service %s - class does not match zone", srs->RR_SRV.resrec.name.c); goto error; } // cache zone data ustrcpy(srs->uDNS_info.zone.c, zoneData->zoneName.c); srs->uDNS_info.ns.type = mDNSAddrType_IPv4; srs->uDNS_info.ns.ip.v4.NotAnInteger = zoneData->primaryAddr.ip.v4.NotAnInteger; srs->uDNS_info.port.NotAnInteger = zoneData->updatePort.NotAnInteger; SendServiceRegistration(m, srs); return; error: unlinkSRS(u, srs); srs->uDNS_info.state = regState_Unregistered; srs->ServiceCallback(m, srs, err); //!!!KRS will mem still be free'd on error? // NOTE: not safe to touch any client structures here } mDNSexport void uDNS_UpdateServiceTargets(mDNS *const m) { DNSMessage msg; mDNSu8 *ptr = msg.data; mDNSu8 *end = (mDNSu8 *)&msg + sizeof(DNSMessage); uDNS_GlobalInfo *u = &m->uDNS_info; ServiceRecordSet *srs; AuthRecord *rr; mStatus err = mStatus_NoError; if (!m->uDNS_info.hostname.c[0]) { LogMsg("ERROR: uDNS_UpdateServiceTargets called before registration of hostname"); return; //!!!KRS need to handle this case properly! } for (srs = u->ServiceRegistrations; srs; srs = srs->next) { if (err) srs = u->ServiceRegistrations; // start again from beginning of list, since it may have changed // (setHostTarget() will skip records already updated) rr = &srs->RR_SRV; if (srs->uDNS_info.state != regState_Registered) { LogMsg("ERROR: uDNS_UpdateServiceTargets - service %s not registered", rr->resrec.name.c); continue; //!!!KRS need to handle this } InitializeDNSMessage(&msg.h, srs->uDNS_info.id, UpdateReqFlags); // construct update packet ptr = putZone(&msg, ptr, end, &srs->uDNS_info.zone, mDNSOpaque16fromIntVal(rr->resrec.rrclass)); if (ptr) ptr = putDeletionRecord(&msg, ptr, &rr->resrec); // delete the old target // update the target if (!setHostTarget(rr, m)) continue; if (ptr) ptr = PutResourceRecord(&msg, ptr, &msg.h.mDNS_numUpdates, &rr->resrec); // put the new target // !!!KRS do subtypes/extras etc. if (!ptr) err = mStatus_UnknownErr; else err = mDNSSendDNSMessage(m, &msg, ptr, 0, &srs->uDNS_info.ns, srs->uDNS_info.port); if (err) { LogMsg("ERROR: uDNS_UpdateServiceTargets - %s", ptr ? "mDNSSendDNSMessage" : "message formatting error"); unlinkSRS(u, srs); srs->uDNS_info.state = regState_Unregistered; srs->ServiceCallback(m, srs, err); //!!!KRS will mem still be free'd on error? // NOTE: not safe to touch any client structures here } else srs->uDNS_info.state = regState_TargetChange; } } mDNSexport mStatus uDNS_RegisterRecord(mDNS *const m, AuthRecord *const rr) { domainname *target = GetRRDomainNameTarget(&rr->resrec); if (rr->uDNS_info.state == regState_FetchingZoneData || rr->uDNS_info.state == regState_Pending || rr->uDNS_info.state == regState_Registered) { LogMsg("Requested double-registration of physical record %s type %s", rr->resrec.name.c, rr->resrec.rrtype); return mStatus_AlreadyRegistered; } 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", GetRRDisplayString(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", GetRRDisplayString(m, rr)); return mStatus_Invalid; } rr->resrec.namehash = DomainNameHashValue(&rr->resrec.name); rr->resrec.rdatahash = RDataHashValue(rr->resrec.rdlength, &rr->resrec.rdata->u); rr->resrec.rdnamehash = target ? DomainNameHashValue(target) : 0; rr->uDNS_info.state = regState_FetchingZoneData; rr->next = m->uDNS_info.RecordRegistrations; m->uDNS_info.RecordRegistrations = rr; rr->uDNS_info.lease = mDNStrue; return startGetZoneData(&rr->resrec.name, m, mDNStrue, mDNSfalse, RecordRegistrationCallback, rr); } mDNSexport mStatus uDNS_DeregisterRecord(mDNS *const m, AuthRecord *const rr) { uDNS_GlobalInfo *u = &m->uDNS_info; DNSMessage msg; mDNSu8 *ptr = msg.data; mDNSu8 *end = (mDNSu8 *)&msg + sizeof(DNSMessage); mStatus err; uDNS_AuthInfo *authInfo; switch (rr->uDNS_info.state) { case regState_FetchingZoneData: rr->uDNS_info.state = regState_Cancelled; return mStatus_NoError; case regState_Pending: rr->uDNS_info.state = regState_DeregDeferred; debugf("Deferring deregistration of record %s until registration completes", rr->resrec.name.c); return mStatus_NoError; case regState_Registered: break; case regState_DeregPending: case regState_Cancelled: LogMsg("Double deregistration of record %s type %d", rr->resrec.name.c, rr->resrec.rrtype); return mStatus_UnknownErr; case regState_Unregistered: LogMsg("Requested deregistration of unregistered record %s type %d", rr->resrec.name.c, rr->resrec.rrtype); return mStatus_UnknownErr; default: LogMsg("ERROR: uDNS_DeregisterRecord called for record %s type %d with unknown state %d", rr->resrec.name.c, rr->resrec.rrtype, rr->uDNS_info.state); return mStatus_UnknownErr; } InitializeDNSMessage(&msg.h, rr->uDNS_info.id, UpdateReqFlags); // put zone ptr = putZone(&msg, ptr, end, &rr->uDNS_info.zone, mDNSOpaque16fromIntVal(rr->resrec.rrclass)); if (!ptr) goto error; if (!(ptr = putDeletionRecord(&msg, ptr, &rr->resrec))) goto error; authInfo = GetAuthInfoForZone(u, &rr->uDNS_info.zone); if (authInfo) { err = mDNSSendSignedDNSMessage(m, &msg, ptr, 0, &rr->uDNS_info.ns, rr->uDNS_info.port, authInfo); if (err) { LogMsg("ERROR: uDNS_DeregiserRecord - mDNSSendSignedDNSMessage - %d", err); goto error; } } else { err = mDNSSendDNSMessage(m, &msg, ptr, 0, &rr->uDNS_info.ns, rr->uDNS_info.port); if (err) { LogMsg("ERROR: uDNS_DeregisterRecord - mDNSSendDNSMessage - %d", err); goto error; } } return mStatus_NoError; error: if (rr->uDNS_info.state != regState_Unregistered) { unlinkAR(&u->RecordRegistrations, rr); rr->uDNS_info.state = regState_Unregistered; } return mStatus_UnknownErr; } mDNSexport mStatus uDNS_RegisterService(mDNS *const m, ServiceRecordSet *srs) { if (!*m->uDNS_info.NameRegDomain) { LogMsg("ERROR: uDNS_RegisterService - cannot register unicast service " "without setting the NameRegDomain via mDNSResponder.conf"); srs->uDNS_info.state = regState_Unregistered; return mStatus_UnknownErr; } srs->RR_SRV.resrec.rroriginalttl = 3; srs->RR_TXT.resrec.rroriginalttl = 3; srs->RR_PTR.resrec.rroriginalttl = 3; // set state and link into list srs->uDNS_info.state = regState_FetchingZoneData; srs->next = m->uDNS_info.ServiceRegistrations; m->uDNS_info.ServiceRegistrations = srs; srs->uDNS_info.lease = mDNStrue; return startGetZoneData(&srs->RR_SRV.resrec.name, m, mDNStrue, mDNSfalse, serviceRegistrationCallback, srs); } mDNSexport mStatus uDNS_DeregisterService(mDNS *const m, ServiceRecordSet *srs) { uDNS_GlobalInfo *u = &m->uDNS_info; DNSMessage msg; mDNSu8 *ptr = msg.data; mDNSu8 *end = (mDNSu8 *)&msg + sizeof(DNSMessage); mStatus err = mStatus_UnknownErr; uDNS_AuthInfo *authInfo; //!!!KRS make sure we're doing the right thing w/ memfree switch (srs->uDNS_info.state) { case regState_Unregistered: LogMsg("ERROR: uDNS_DeregisterService - service not registerd"); return mStatus_UnknownErr; case regState_FetchingZoneData: case regState_Pending: // let the async op complete, then terminate srs->uDNS_info.state = regState_Cancelled; return mStatus_NoError; // deliver memfree upon completion of async op case regState_DeregPending: case regState_DeregDeferred: case regState_Cancelled: LogMsg("uDNS_DeregisterService - deregistration in process"); return mStatus_UnknownErr; } srs->uDNS_info.state = regState_DeregPending; InitializeDNSMessage(&msg.h, srs->uDNS_info.id, UpdateReqFlags); // put zone ptr = putZone(&msg, ptr, end, &srs->uDNS_info.zone, mDNSOpaque16fromIntVal(srs->RR_SRV.resrec.rrclass)); if (!ptr) { LogMsg("ERROR: uDNS_DeregisterService - putZone"); goto error; } // prereq: record must exist (put record in prereq section w/ TTL 0) ptr = PutResourceRecordTTL(&msg, ptr, &msg.h.mDNS_numPrereqs, &srs->RR_SRV.resrec, 0); if (!ptr) { LogMsg("ERROR: uDNS_DeregisterService - PutResourceRecordTTL"); goto error; } if (!(ptr = putDeletionRecord(&msg, ptr, &srs->RR_SRV.resrec))) goto error; if (!(ptr = putDeletionRecord(&msg, ptr, &srs->RR_TXT.resrec))) goto error; if (!(ptr = putDeletionRecord(&msg, ptr, &srs->RR_PTR.resrec))) goto error; //if (!(ptr = putDeletionRecord(&msg, ptr, &srs->RR_ADV.resrec))) goto error; //!!!KRS need to handle extras/subtypes etc authInfo = GetAuthInfoForZone(u, &srs->uDNS_info.zone); if (authInfo) { err = mDNSSendSignedDNSMessage(m, &msg, ptr, 0, &srs->uDNS_info.ns, srs->uDNS_info.port, authInfo); if (err) { LogMsg("ERROR: uDNS_DeregiserService - mDNSSendSignedDNSMessage - %d", err); goto error; } } else { err = mDNSSendDNSMessage(m, &msg, ptr, 0, &srs->uDNS_info.ns, srs->uDNS_info.port); if (err) { LogMsg("ERROR: uDNS_DeregisterService - mDNSSendDNSMessage - %d", err); goto error; } } return mStatus_NoError; error: unlinkSRS(u, srs); srs->uDNS_info.state = regState_Unregistered; return err; } mDNSexport void uDNS_Execute(mDNS *const m) { DNSQuestion *q; DNSMessage msg; mStatus err; mDNSu8 *end; mDNSs32 sendtime; LLQ_Info *llq; AuthRecord *rr; ServiceRecordSet *srs; uDNS_RegInfo *rInfo; uDNS_GlobalInfo *u = &m->uDNS_info; const mDNSAddr *server = getInitializedDNS(&m->uDNS_info); mDNSs32 timenow = mDNSPlatformTimeNow(); u->nextevent = timenow + 0x78000000; if (!server) { debugf("uDNS_Execute - no DNS server"); return; } for (q = u->ActiveQueries; q; q = q->next) { llq = q->uDNS_info.llq; if (q->LongLived && llq->state != LLQ_Poll) { if (llq->state >= LLQ_InitialRequest && llq->state <= LLQ_Suspended && llq->retry <= timenow) { // sanity check to avoid packet flood bugs if (!llq->retry) LogMsg("ERROR: retry timer not set for LLQ %s in state %d", q->qname.c, llq->state); else if (llq->state == LLQ_Established || llq->state == LLQ_Refresh) sendLLQRefresh(m, q, llq->origLease); else if (llq->state == LLQ_InitialRequest) startLLQHandshake(m, llq); else if (llq->state == LLQ_SecondaryRequest) sendChallengeResponse(m, q, NULL); else if (llq->state == LLQ_Retry) { llq->ntries = 0; startLLQHandshake(m, llq); } } } else { sendtime = q->LastQTime + q->ThisQInterval; if (sendtime <= timenow) { err = constructQueryMsg(&msg, &end, q); if (err) { LogMsg("Error: uDNS_Idle - constructQueryMsg. Skipping question %s", q->qname.c); continue; } err = mDNSSendDNSMessage(m, &msg, end, q->InterfaceID, server, UnicastDNSPort); if (err) { debugf("ERROR: uDNS_idle - mDNSSendDNSMessage - %d", err); } // surpress syslog messages if we have no network q->LastQTime = timenow; if (q->ThisQInterval < MAX_UCAST_POLL_INTERVAL) q->ThisQInterval = q->ThisQInterval * 2; } else if (u->nextevent - sendtime > 0) u->nextevent = sendtime; } } //!!!KRS list should be pre-sorted by expiration for (rr = u->RecordRegistrations; rr; rr = rr->next) { rInfo = &rr->uDNS_info; if (rInfo->lease && rInfo->state == regState_Registered && rInfo->expire > 0) { if (rInfo->expire < timenow) { debugf("refreshing record %s", rr->resrec.name.c); rInfo->state = regState_Refresh; sendRecordRegistration(m, rr); } else if (u->nextevent - rInfo->expire > 0) u->nextevent = rInfo->expire; } } //!!!KRS list should be pre-sorted by expiration for (srs = u->ServiceRegistrations; srs; srs = srs->next) { rInfo = &srs->uDNS_info; if (rInfo->lease && rInfo->state == regState_Registered && rInfo->expire > 0) { if (rInfo->expire < timenow) { debugf("refreshing service %s", srs->RR_SRV.resrec.name.c); rInfo->state = regState_Refresh; SendServiceRegistration(m, srs); } else if (u->nextevent - rInfo->expire > 0) u->nextevent = rInfo->expire; } } } mDNSexport void uDNS_Init(mDNS *const m) { mDNSPlatformMemZero(&m->uDNS_info, sizeof(uDNS_GlobalInfo)); m->uDNS_info.nextevent = mDNSPlatformTimeNow() + 0x78000000; }