From 863346cbb986f1f24721d8e586f51d2358488cdc Mon Sep 17 00:00:00 2001 From: Ron Pedde Date: Mon, 29 Mar 2004 17:55:17 +0000 Subject: [PATCH] Flatten mdns stuff --- src/Makefile.am | 6 +- src/mDNS.c | 7034 +++++++++++++++++++++++++++++++++++ src/mDNSClientAPI.h | 1426 +++++++ src/mDNSDebug.h | 118 + src/mDNSPlatformFunctions.h | 89 + src/mDNSPosix.c | 1008 +++++ src/mDNSPosix.h | 101 + src/mDNSUNP.c | 430 +++ src/mDNSUNP.h | 129 + 9 files changed, 10338 insertions(+), 3 deletions(-) create mode 100644 src/mDNS.c create mode 100644 src/mDNSClientAPI.h create mode 100644 src/mDNSDebug.h create mode 100644 src/mDNSPlatformFunctions.h create mode 100644 src/mDNSPosix.c create mode 100644 src/mDNSPosix.h create mode 100644 src/mDNSUNP.c create mode 100644 src/mDNSUNP.h diff --git a/src/Makefile.am b/src/Makefile.am index 0a25ba8b..0ca4089a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,7 +13,7 @@ DBFILE=db-memory.c endif if COND_REND_POSIX -RENDSRC=mdns/mDNS.c mdns/mDNSClientAPI.h mdns/mDNSDebug.h mdns/mDNSPosix.c mdns/mDNSUNP.c rend-posix.c mdns/mDNSPlatformFunctions.h +RENDSRC=mDNS.c mDNSClientAPI.h mDNSDebug.h mDNSPosix.c mDNSUNP.c rend-posix.c mDNSPlatformFunctions.h endif if COND_REND_HOWL @@ -30,8 +30,8 @@ mt_daapd_SOURCES = main.c daapd.h rend.h uici.c uici.h webserver.c \ mp3-scanner.h mp3-scanner.c playlist.c playlist.h rend-unix.c rend-unix.h \ lexer.l parser.y $(RENDSRC) strcasestr.c strcasestr.h strsep.c -EXTRA_DIST = mdns/mDNS.c mdns/mDNSClientAPI.h mdns/mDNSDebug.h mdns/mDNSPosix.c \ - mdns/mDNSUNP.c mdns/mDNSPlatformFunctions.h mdns/mDNSPosix.h mdns/mDNSUNP.h \ +EXTRA_DIST = mDNS.c mDNSClientAPI.h mDNSDebug.h mDNSPosix.c \ + mDNSUNP.c mDNSPlatformFunctions.h mDNSPosix.h mDNSUNP.h \ rend-howl.c rend-posix.c rend-osx.c strcasestr.c strsep.c db-memory.c \ db-gdbm.c strcasestr.h redblack.c redblack.h diff --git a/src/mDNS.c b/src/mDNS.c new file mode 100644 index 00000000..aa85306b --- /dev/null +++ b/src/mDNS.c @@ -0,0 +1,7034 @@ +/* + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * 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@ + * + * This code is completely 100% portable C. It does not depend on any external header files + * from outside the mDNS project -- all the types it expects to find are defined right here. + * + * The previous point is very important: This file does not depend on any external + * header files. It should complile on *any* platform that has a C compiler, without + * making *any* assumptions about availability of so-called "standard" C functions, + * routines, or types (which may or may not be present on any given platform). + + * Formatting notes: + * This code follows the "Whitesmiths style" C indentation rules. Plenty of discussion + * on C indentation can be found on the web, such as , + * but for the sake of brevity here I will say just this: Curly braces are not syntactially + * part of an "if" statement; they are the beginning and ending markers of a compound statement; + * therefore common sense dictates that if they are part of a compound statement then they + * should be indented to the same level as everything else in that compound statement. + * Indenting curly braces at the same level as the "if" implies that curly braces are + * part of the "if", which is false. (This is as misleading as people who write "char* x,y;" + * thinking that variables x and y are both of type "char*" -- and anyone who doesn't + * understand why variable y is not of type "char*" just proves the point that poor code + * layout leads people to unfortunate misunderstandings about how the C language really works.) + + Change History (most recent first): + +$Log$ +Revision 1.1 2004/03/29 17:55:17 rpedde +Flatten mdns stuff + +Revision 1.4 2004/03/02 00:14:26 rpedde +Update to mdns 58.3 + +Revision 1.307.2.2 2003/12/20 01:51:40 cheshire +: Error putting additional records into packets +Another fix from Rampi: responseptr needs to be updated inside the "for" loop, +after every record, not once at the end. + +Revision 1.307.2.1 2003/12/03 11:20:27 cheshire +: Stop and start of a service uses old ip address (with old port number) + +Revision 1.307 2003/09/09 20:13:30 cheshire + Don't send a Goodbye record if we never announced it +Ammend checkin 1.304: Off-by-one error: By this place in the function we've already decremented +rr->AnnounceCount, so the check needs to be for InitialAnnounceCount-1, not InitialAnnounceCount + +Revision 1.306 2003/09/09 03:00:03 cheshire + Services take a long time to disappear when switching networks. +Added two constants: kDefaultReconfirmTimeForNoAnswer and kDefaultReconfirmTimeForCableDisconnect + +Revision 1.305 2003/09/09 02:49:31 cheshire + Initial probes and queries not grouped on wake-from-sleep + +Revision 1.304 2003/09/09 02:41:19 cheshire + Don't send a Goodbye record if we never announced it + +Revision 1.303 2003/09/05 19:55:02 cheshire + Include address records when announcing SRV records + +Revision 1.302 2003/09/05 00:01:36 cheshire + Don't accelerate queries that have large KA lists + +Revision 1.301 2003/09/04 22:51:13 cheshire + Group probes and goodbyes better + +Revision 1.300 2003/09/03 02:40:37 cheshire + mDNSResponder complains about '_'s +Underscores are not supposed to be legal in standard DNS names, but IANA appears +to have allowed them in previous service name registrations, so we should too. + +Revision 1.299 2003/09/03 02:33:09 cheshire + CacheRecordRmv ERROR +Don't update m->NewQuestions until *after* CheckCacheExpiration(); + +Revision 1.298 2003/09/03 01:47:01 cheshire + Rendezvous services always in a state of flux +Change mDNS_Reconfirm_internal() minimum timeout from 5 seconds to 45-60 seconds + +Revision 1.297 2003/08/29 19:44:15 cheshire + Traffic reduction: Eliminate synchronized QUs when a new service appears +1. Use m->RandomQueryDelay to impose a random delay in the range 0-500ms on queries + that already have at least one unique answer in the cache +2. For these queries, go straight to QM, skipping QU + +Revision 1.296 2003/08/29 19:08:21 cheshire + Traffic reduction: Eliminate huge KA lists after wake from sleep +Known answers are no longer eligible to go in the KA list if they are more than half-way to their expiry time. + +Revision 1.295 2003/08/28 01:10:59 cheshire + Add syslog message to report when query is reset because of immediate answer burst + +Revision 1.294 2003/08/27 02:30:22 cheshire + Traffic Reduction: Inefficiencies in DNSServiceResolverResolve() +One more change: "query->GotTXT" is now a straightforward bi-state boolean again + +Revision 1.293 2003/08/27 02:25:31 cheshire + Traffic Reduction: Inefficiencies in DNSServiceResolverResolve() + +Revision 1.292 2003/08/21 19:27:36 cheshire + Traffic reduction: No need to announce record for longer than TTL + +Revision 1.291 2003/08/21 18:57:44 cheshire + Synchronized queries on the network + +Revision 1.290 2003/08/21 02:25:23 cheshire +Minor changes to comments and debugf() messages + +Revision 1.289 2003/08/21 02:21:50 cheshire + Efficiency: Reduce repeated queries + +Revision 1.288 2003/08/20 23:39:30 cheshire + Review syslog messages, and remove as appropriate + +Revision 1.287 2003/08/20 20:47:18 cheshire +Fix compiler warning + +Revision 1.286 2003/08/20 02:18:51 cheshire + Cleanup: Review syslog messages + +Revision 1.285 2003/08/20 01:59:06 cheshire + rdatahash and rdnamehash not updated after changing rdata +Made new routine SetNewRData() to update rdlength, rdestimate, rdatahash and rdnamehash in one place + +Revision 1.284 2003/08/19 22:20:00 cheshire + Don't use IPv6 on interfaces that have a routable IPv4 address configured +More minor refinements + +Revision 1.283 2003/08/19 22:16:27 cheshire +Minor fix: Add missing "mDNS_Unlock(m);" in mDNS_DeregisterInterface() error case. + +Revision 1.282 2003/08/19 06:48:25 cheshire + Guard against excessive record updates +Each record starts with 10 UpdateCredits. +Every update consumes one UpdateCredit. +UpdateCredits are replenished at a rate of one one per minute, up to a maximum of 10. +As the number of UpdateCredits declines, the number of announcements is similarly scaled back. +When fewer than 5 UpdateCredits remain, the first announcement is also delayed by an increasing amount. + +Revision 1.281 2003/08/19 04:49:28 cheshire + Interaction between v4, v6 and dual-stack hosts not working quite right +1. A dual-stack host should only suppress its own query if it sees the same query from other hosts on BOTH IPv4 and IPv6. +2. When we see the first v4 (or first v6) member of a group, we re-trigger questions and probes on that interface. +3. When we see the last v4 (or v6) member of a group go away, we revalidate all the records received on that interface. + +Revision 1.280 2003/08/19 02:33:36 cheshire +Update comments + +Revision 1.279 2003/08/19 02:31:11 cheshire + mDNSResponder overenthusiastic with final expiration queries +Final expiration queries now only mark the question for sending on the particular interface +pertaining to the record that's expiring. + +Revision 1.278 2003/08/18 22:53:37 cheshire + mDNSResponder divide by zero in mDNSPlatformTimeNow() + +Revision 1.277 2003/08/18 19:05:44 cheshire + UpdateRecord not working right +Added "newrdlength" field to hold new length of updated rdata + +Revision 1.276 2003/08/16 03:39:00 cheshire + InterfaceID -1 indicates "local only" + +Revision 1.275 2003/08/16 02:51:27 cheshire + mDNSResponder takes too much RPRVT +Don't try to compute namehash etc, until *after* validating the name + +Revision 1.274 2003/08/16 01:12:40 cheshire + mDNSResponder takes too much RPRVT +Now that the minimum rdata object size has been reduced to 64 bytes, it is no longer safe to do a +simple C structure assignment of a domainname, because that object is defined to be 256 bytes long, +and in the process of copying it, the C compiler may run off the end of the rdata object into +unmapped memory. All assignments of domainname objects of uncertain size are now replaced with a +call to the macro AssignDomainName(), which is careful to copy only as many bytes as are valid. + +Revision 1.273 2003/08/15 20:16:02 cheshire + mDNSResponder takes too much RPRVT +We want to avoid touching the rdata pages, so we don't page them in. +1. RDLength was stored with the rdata, which meant touching the page just to find the length. + Moved this from the RData to the ResourceRecord object. +2. To avoid unnecessarily touching the rdata just to compare it, + compute a hash of the rdata and store the hash in the ResourceRecord object. + +Revision 1.272 2003/08/14 19:29:04 cheshire + Include cache records in SIGINFO output +Moved declarations of DNSTypeName() and GetRRDisplayString to mDNSClientAPI.h so daemon.c can use them + +Revision 1.271 2003/08/14 02:17:05 cheshire + Split generic ResourceRecord type into two separate types: AuthRecord and CacheRecord + +Revision 1.270 2003/08/13 17:07:28 ksekar +Bug #: : Extra RR linked to list even if registration fails - causes crash +Added check to result of mDNS_Register() before linking extra record into list. + +Revision 1.269 2003/08/12 19:56:23 cheshire +Update to APSL 2.0 + +Revision 1.268 2003/08/12 15:01:10 cheshire +Add comments + +Revision 1.267 2003/08/12 14:59:27 cheshire + Rate-limiting blocks some legitimate responses +When setting LastMCTime also record LastMCInterface. When checking LastMCTime to determine +whether to suppress the response, also check LastMCInterface to see if it matches. + +Revision 1.266 2003/08/12 12:47:16 cheshire +In mDNSCoreMachineSleep debugf message, display value of m->timenow + +Revision 1.265 2003/08/11 20:04:28 cheshire + Improve efficiency by restricting cases where we have to walk the entire cache + +Revision 1.264 2003/08/09 00:55:02 cheshire + mDNSResponder is taking 20-30% of the CPU +Don't scan the whole cache after every packet. + +Revision 1.263 2003/08/09 00:35:29 cheshire +Moved AnswerNewQuestion() later in the file, in preparation for next checkin + +Revision 1.262 2003/08/08 19:50:33 cheshire + Remove "Cache size now xxx" messages + +Revision 1.261 2003/08/08 19:18:45 cheshire + Only retrigger questions on platforms with the "PhantomInterfaces" bug + +Revision 1.260 2003/08/08 18:55:48 cheshire + Guard against time going backwards + +Revision 1.259 2003/08/08 18:36:04 cheshire + Only need to revalidate on interface removal on platforms that have the PhantomInterfaces bug + +Revision 1.258 2003/08/08 16:22:05 cheshire + Need to check validity of TXT (and other) records +Remove unneeded LogMsg + +Revision 1.257 2003/08/07 01:41:08 cheshire + Ignore packets with invalid source address (all zeroes or all ones) + +Revision 1.256 2003/08/06 23:25:51 cheshire + Increase TTL for A/AAAA/SRV from one minute to four + +Revision 1.255 2003/08/06 23:22:50 cheshire +Add symbolic constants: kDefaultTTLforUnique (one minute) and kDefaultTTLforShared (two hours) + +Revision 1.254 2003/08/06 21:33:39 cheshire +Fix compiler warnings on PocketPC 2003 (Windows CE) + +Revision 1.253 2003/08/06 20:43:57 cheshire + Need to check validity of TXT (and other) records +Created ValidateDomainName() and ValidateRData(), used by mDNS_Register_internal() and mDNS_Update() + +Revision 1.252 2003/08/06 20:35:47 cheshire +Enhance debugging routine GetRRDisplayString() so it can also be used to display +other RDataBody objects, not just the one currently attached the given ResourceRecord + +Revision 1.251 2003/08/06 19:07:34 cheshire + mDNSResponder not inhibiting multicast responses as much as it should +Was checking LastAPTime instead of LastMCTime + +Revision 1.250 2003/08/06 19:01:55 cheshire +Update comments + +Revision 1.249 2003/08/06 00:13:28 cheshire +Tidy up debugf messages + +Revision 1.248 2003/08/05 22:20:15 cheshire + Need to check IP TTL on responses + +Revision 1.247 2003/08/05 00:56:39 cheshire + mDNSResponder sending additional records, even after precursor record suppressed + +Revision 1.246 2003/08/04 19:20:49 cheshire +Add kDNSQType_ANY to list in DNSTypeName() so it can be displayed in debugging messages + +Revision 1.245 2003/08/02 01:56:29 cheshire +For debugging: log message if we ever get more than one question in a truncated packet + +Revision 1.244 2003/08/01 23:55:32 cheshire +Fix for compiler warnings on Windows, submitted by Bob Bradley + +Revision 1.243 2003/07/25 02:26:09 cheshire +Typo: FIxed missing semicolon + +Revision 1.242 2003/07/25 01:18:41 cheshire +Fix memory leak on shutdown in mDNS_Close() (detected in Windows version) + +Revision 1.241 2003/07/23 21:03:42 cheshire +Only show "Found record..." debugf message in verbose mode + +Revision 1.240 2003/07/23 21:01:11 cheshire + Need Nagle-style algorithm to coalesce multiple packets into one +After sending a packet, suppress further sending for the next 100ms. + +Revision 1.239 2003/07/22 01:30:05 cheshire + Don't try to add the same question to the duplicate-questions list more than once + +Revision 1.238 2003/07/22 00:10:20 cheshire + ConvertDomainLabelToCString() needs to escape escape characters + +Revision 1.237 2003/07/19 03:23:13 cheshire + mDNSResponder needs to receive and cache larger records + +Revision 1.236 2003/07/19 03:04:55 cheshire +Fix warnings; some debugf message improvements + +Revision 1.235 2003/07/19 00:03:32 cheshire + ScheduleNextTask needs to be smarter after a no-op packet is received +ScheduleNextTask is quite an expensive operation. +We don't need to do all that work after receiving a no-op packet that didn't change our state. + +Revision 1.234 2003/07/18 23:52:11 cheshire +To improve consistency of field naming, global search-and-replace: +NextProbeTime -> NextScheduledProbe +NextResponseTime -> NextScheduledResponse + +Revision 1.233 2003/07/18 00:29:59 cheshire + Remove mDNSResponder version from packet header and use HINFO record instead + +Revision 1.232 2003/07/18 00:11:38 cheshire +Add extra case to switch statements to handle HINFO data for Get, Put and Display +(In all but GetRDLength(), this is is just a fall-through to kDNSType_TXT) + +Revision 1.231 2003/07/18 00:06:37 cheshire +To make code a little easier to read in GetRDLength(), search-and-replace "rr->rdata->u." with "rd->" + +Revision 1.230 2003/07/17 18:16:54 cheshire + Rendezvous services always in a state of flux +In preparation for working on this, made some debugf messages a little more selective + +Revision 1.229 2003/07/17 17:35:04 cheshire + Rate-limit responses, to guard against packet flooding + +Revision 1.228 2003/07/16 20:50:27 cheshire + Need to implement "unicast response" request, using top bit of qclass + +Revision 1.227 2003/07/16 05:01:36 cheshire +Add fields 'LargeAnswers' and 'ExpectUnicastResponse' in preparation for + Need to implement "unicast response" request, using top bit of qclass + +Revision 1.226 2003/07/16 04:51:44 cheshire +Fix use of constant 'mDNSPlatformOneSecond' where it should have said 'InitialQuestionInterval' + +Revision 1.225 2003/07/16 04:46:41 cheshire +Minor wording cleanup: The correct DNS term is "response", not "reply" + +Revision 1.224 2003/07/16 04:39:02 cheshire +Textual cleanup (no change to functionality): +Construct "c >= 'A' && c <= 'Z'" appears in too many places; replaced with macro "mDNSIsUpperCase(c)" + +Revision 1.223 2003/07/16 00:09:22 cheshire +Textual cleanup (no change to functionality): +Construct "((mDNSs32)rr->rroriginalttl * mDNSPlatformOneSecond)" appears in too many places; +replace with macro "TicksTTL(rr)" +Construct "rr->TimeRcvd + ((mDNSs32)rr->rroriginalttl * mDNSPlatformOneSecond)" +replaced with macro "RRExpireTime(rr)" + +Revision 1.222 2003/07/15 23:40:46 cheshire +Function rename: UpdateDupSuppressInfo() is more accurately called ExpireDupSuppressInfo() + +Revision 1.221 2003/07/15 22:17:56 cheshire + mDNSResponder is not being efficient when doing certain queries + +Revision 1.220 2003/07/15 02:12:51 cheshire +Slight tidy-up of debugf messages and comments + +Revision 1.219 2003/07/15 01:55:12 cheshire + Need to implement service registration with subtypes + +Revision 1.218 2003/07/14 16:26:06 cheshire + Duplicate query suppression not working right +Refinement: Don't record DS information for a question in the first quarter second +right after we send it -- in the case where a question happens to be accelerated by +the maximum allowed amount, we don't want it to then be suppressed because the previous +time *we* sent that question falls (just) within the valid duplicate suppression window. + +Revision 1.217 2003/07/13 04:43:53 cheshire + Services on multiple interfaces not always resolving +Minor refinement: No need to make address query broader than the original SRV query that provoked it + +Revision 1.216 2003/07/13 03:13:17 cheshire + Services on multiple interfaces not always resolving +If we get an identical SRV on a second interface, convert address queries to non-specific + +Revision 1.215 2003/07/13 02:28:00 cheshire + SendResponses didn't all its responses +Delete all references to RRInterfaceActive -- it's now superfluous + +Revision 1.214 2003/07/13 01:47:53 cheshire +Fix one error and one warning in the Windows build + +Revision 1.213 2003/07/12 04:25:48 cheshire +Fix minor signed/unsigned warnings + +Revision 1.212 2003/07/12 01:59:11 cheshire +Minor changes to debugf messages + +Revision 1.211 2003/07/12 01:47:01 cheshire + After name conflict, appended number should be higher than previous number + +Revision 1.210 2003/07/12 01:43:28 cheshire + Duplicate query suppression not working right +The correct cutoff time for duplicate query suppression is timenow less one-half the query interval. +The code was incorrectly using the last query time plus one-half the query interval. +This was only correct in the case where query acceleration was not in effect. + +Revision 1.209 2003/07/12 01:27:50 cheshire + Hostname conflict naming should not use two hyphens +Fix missing "-1" in RemoveLabelSuffix() + +Revision 1.208 2003/07/11 01:32:38 cheshire +Syntactic cleanup (no change to funcationality): Now that we only have one host name, +rename field "hostname1" to "hostname", and field "RR_A1" to "RR_A". + +Revision 1.207 2003/07/11 01:28:00 cheshire + No more local.arpa + +Revision 1.206 2003/07/11 00:45:02 cheshire + Client should get callback confirming successful host name registration + +Revision 1.205 2003/07/11 00:40:18 cheshire +Tidy up debug message in HostNameCallback() + +Revision 1.204 2003/07/11 00:20:32 cheshire + mDNSResponder should log a message after 16 unsuccessful probes + +Revision 1.203 2003/07/10 23:53:41 cheshire + Hostname conflict naming should not use two hyphens + +Revision 1.202 2003/07/04 02:23:20 cheshire + Responder too aggressive at flushing stale data +Changed mDNSResponder to require four unanswered queries before purging a record, instead of two. + +Revision 1.201 2003/07/04 01:09:41 cheshire + Need to implement subtype queries +Modified ConstructServiceName() to allow three-part service types + +Revision 1.200 2003/07/03 23:55:26 cheshire +Minor change to wording of syslog warning messages + +Revision 1.199 2003/07/03 23:51:13 cheshire +: Lots of "have given xxx answers" syslog warnings +Added more detailed debugging information + +Revision 1.198 2003/07/03 22:19:30 cheshire + Bug fix in 3274153 breaks TiVo +Make exception to allow _tivo_servemedia._tcp. + +Revision 1.197 2003/07/02 22:33:05 cheshire + mDNSResponder needs to start with a smaller cache and then grow it as needed +Minor refinements: +When cache is exhausted, verify that rrcache_totalused == rrcache_size and report if not +Allow cache to grow to 512 records before considering it a potential denial-of-service attack + +Revision 1.196 2003/07/02 21:19:45 cheshire + Update copyright notices, etc., in source code comments + +Revision 1.195 2003/07/02 19:56:58 cheshire + mDNSResponder needs to start with a smaller cache and then grow it as needed +Minor refinement: m->rrcache_active was not being decremented when +an active record was deleted because its TTL expired + +Revision 1.194 2003/07/02 18:47:40 cheshire +Minor wording change to log messages + +Revision 1.193 2003/07/02 02:44:13 cheshire +Fix warning in non-debug build + +Revision 1.192 2003/07/02 02:41:23 cheshire + mDNSResponder needs to start with a smaller cache and then grow it as needed + +Revision 1.191 2003/07/02 02:30:51 cheshire +HashSlot() returns an array index. It can't be negative; hence it should not be signed. + +Revision 1.190 2003/06/27 00:03:05 vlubet + Merge of build failure fix for gcc 3.3 + +Revision 1.189 2003/06/11 19:24:03 cheshire + Crash in SendQueries/SendResponses when no active interfaces +Slight refinement to previous checkin + +Revision 1.188 2003/06/10 20:33:28 cheshire + Crash in SendQueries/SendResponses when no active interfaces + +Revision 1.187 2003/06/10 04:30:44 cheshire + Need to re-probe/re-announce on configuration change +Only interface-specific records were re-probing and re-announcing, not non-specific records. + +Revision 1.186 2003/06/10 04:24:39 cheshire + React when we observe other people query unsuccessfully for a record that's in our cache +Some additional refinements: +Don't try to do this for unicast-response queries +better tracking of Qs and KAs in multi-packet KA lists + +Revision 1.185 2003/06/10 03:52:49 cheshire +Update comments and debug messages + +Revision 1.184 2003/06/10 02:26:39 cheshire + mDNSResponder needs an mDNS_Reconfirm() function +Make mDNS_Reconfirm() call mDNS_Lock(), like the other API routines + +Revision 1.183 2003/06/09 18:53:13 cheshire +Simplify some debugf() statements (replaced block of 25 lines with 2 lines) + +Revision 1.182 2003/06/09 18:38:42 cheshire + Need to be more tolerant when there are mDNS proxies on the network +Only issue a correction if the TTL in the proxy packet is less than half the correct value. + +Revision 1.181 2003/06/07 06:45:05 cheshire + No need for multiple machines to all be sending the same queries + +Revision 1.180 2003/06/07 06:31:07 cheshire +Create little four-line helper function "FindIdenticalRecordInCache()" + +Revision 1.179 2003/06/07 06:28:13 cheshire +For clarity, change name of "DNSQuestion q" to "DNSQuestion pktq" + +Revision 1.178 2003/06/07 06:25:12 cheshire +Update some comments + +Revision 1.177 2003/06/07 04:50:53 cheshire + React when we observe other people query unsuccessfully for a record that's in our cache + +Revision 1.176 2003/06/07 04:33:26 cheshire + When query produces zero results, call mDNS_Reconfirm() on any antecedent records +Minor change: Increment/decrement logic for q->CurrentAnswers should be in +CacheRecordAdd() and CacheRecordRmv(), not AnswerQuestionWithResourceRecord() + +Revision 1.175 2003/06/07 04:11:52 cheshire +Minor changes to comments and debug messages + +Revision 1.174 2003/06/07 01:46:38 cheshire + When query produces zero results, call mDNS_Reconfirm() on any antecedent records + +Revision 1.173 2003/06/07 01:22:13 cheshire + mDNSResponder needs an mDNS_Reconfirm() function + +Revision 1.172 2003/06/07 00:59:42 cheshire + Need some randomness to spread queries on the network + +Revision 1.171 2003/06/06 21:41:10 cheshire +For consistency, mDNS_StopQuery() should return an mStatus result, just like all the other mDNSCore routines + +Revision 1.170 2003/06/06 21:38:55 cheshire +Renamed 'NewData' as 'FreshData' (The data may not be new data, just a refresh of data that we +already had in our cache. This refreshes our TTL on the data, but the data itself stays the same.) + +Revision 1.169 2003/06/06 21:35:55 cheshire +Fix mis-named macro: GetRRHostNameTarget is really GetRRDomainNameTarget +(the target is a domain name, but not necessarily a host name) + +Revision 1.168 2003/06/06 21:33:31 cheshire +Instead of using (mDNSPlatformOneSecond/2) all over the place, define a constant "InitialQuestionInterval" + +Revision 1.167 2003/06/06 21:30:42 cheshire + Don't delay queries for shared record types + +Revision 1.166 2003/06/06 17:20:14 cheshire +For clarity, rename question fields name/rrtype/rrclass as qname/qtype/qclass +(Global search-and-replace; no functional change to code execution.) + +Revision 1.165 2003/06/04 02:53:21 cheshire +Add some "#pragma warning" lines so it compiles clean on Microsoft compilers + +Revision 1.164 2003/06/04 01:25:33 cheshire + Cannot perform multi-packet known-answer suppression messages +Display time interval between first and subsequent queries + +Revision 1.163 2003/06/03 19:58:14 cheshire + mDNS_DeregisterService() fixes: +When forcibly deregistering after a conflict, ensure we don't send an incorrect goodbye packet. +Guard against a couple of possible mDNS_DeregisterService() race conditions. + +Revision 1.162 2003/06/03 19:30:39 cheshire +Minor addition refinements for + Duplicate registrations not handled as efficiently as they should be + +Revision 1.161 2003/06/03 18:29:03 cheshire +Minor changes to comments and debugf() messages + +Revision 1.160 2003/06/03 05:02:16 cheshire + Duplicate registrations not handled as efficiently as they should be + +Revision 1.159 2003/06/03 03:31:57 cheshire + False self-conflict when there are duplicate registrations on one machine + +Revision 1.158 2003/06/02 22:57:09 cheshire +Minor clarifying changes to comments and log messages; +IdenticalResourceRecordAnyInterface() is really more accurately called just IdenticalResourceRecord() + +Revision 1.157 2003/05/31 00:09:49 cheshire + Add ability to discover what services are on a network + +Revision 1.156 2003/05/30 23:56:49 cheshire + Crash after error in mDNS_RegisterService() +Need to set "sr->Extras = mDNSNULL" before returning + +Revision 1.155 2003/05/30 23:48:00 cheshire + Announcements not properly grouped +Due to inconsistent setting of rr->LastAPTime at different places in the +code, announcements were not properly grouped into a single packet. +Fixed by creating a single routine called InitializeLastAPTime(). + +Revision 1.154 2003/05/30 23:38:14 cheshire + Fix error in IPv6 reverse-mapping PTR records +Wrote buffer[32] where it should have said buffer[64] + +Revision 1.153 2003/05/30 19:10:56 cheshire + ConstructServiceName needs to be more restrictive + +Revision 1.152 2003/05/29 22:39:16 cheshire + Don't truncate strings in the middle of a UTF-8 character + +Revision 1.151 2003/05/29 06:35:42 cheshire + mDNSCoreReceiveResponse() purging wrong record + +Revision 1.150 2003/05/29 06:25:45 cheshire + Need to call CheckCacheExpiration() *before* AnswerNewQuestion() + +Revision 1.149 2003/05/29 06:18:39 cheshire + Split AnswerLocalQuestions into CacheRecordAdd and CacheRecordRmv + +Revision 1.148 2003/05/29 06:11:34 cheshire + Report if there appear to be too many "Resolve" callbacks + +Revision 1.147 2003/05/29 06:01:18 cheshire +Change some debugf() calls to LogMsg() calls to help with debugging + +Revision 1.146 2003/05/28 21:00:44 cheshire +Re-enable "immediate answer burst" debugf message + +Revision 1.145 2003/05/28 20:57:44 cheshire + mDNSResponder reports "Cannot perform multi-packet +known-answer suppression ..." This is a known issue caused by a bug in the OS X 10.2 +version of mDNSResponder, so for now we should suppress this warning message. + +Revision 1.144 2003/05/28 18:05:12 cheshire + mDNSResponder allows invalid service registrations +Fix silly mistake: old logic allowed "TDP" and "UCP" as valid names + +Revision 1.143 2003/05/28 04:31:29 cheshire + mDNSResponder not sending probes at the prescribed time + +Revision 1.142 2003/05/28 03:13:07 cheshire + mDNSResponder allows invalid service registrations +Require that the transport protocol be _udp or _tcp + +Revision 1.141 2003/05/28 02:19:12 cheshire + Misleading messages generated by iChat +Better fix: Only generate the log message for queries where the TC bit is set. + +Revision 1.140 2003/05/28 01:55:24 cheshire +Minor change to log messages + +Revision 1.139 2003/05/28 01:52:51 cheshire + Misleading messages generated by iChat + +Revision 1.138 2003/05/27 22:35:00 cheshire + mDNS_RegisterInterface needs to retrigger questions + +Revision 1.137 2003/05/27 20:04:33 cheshire + mDNSResponder crash in mDNS_vsnprintf() + +Revision 1.136 2003/05/27 18:50:07 cheshire + mDNS_StartResolveService doesn't inform client of port number changes + +Revision 1.135 2003/05/26 04:57:28 cheshire + Delay queries when there are already answers in the cache + +Revision 1.134 2003/05/26 04:54:54 cheshire + sprintf/vsprintf-style functions are unsafe; use snprintf/vsnprintf instead +Accidentally deleted '%' case from the switch statement + +Revision 1.133 2003/05/26 03:21:27 cheshire +Tidy up address structure naming: +mDNSIPAddr => mDNSv4Addr (for consistency with mDNSv6Addr) +mDNSAddr.addr.ipv4 => mDNSAddr.ip.v4 +mDNSAddr.addr.ipv6 => mDNSAddr.ip.v6 + +Revision 1.132 2003/05/26 03:01:26 cheshire + sprintf/vsprintf-style functions are unsafe; use snprintf/vsnprintf instead + +Revision 1.131 2003/05/26 00:42:05 cheshire + Temporarily include mDNSResponder version in packets + +Revision 1.130 2003/05/24 16:39:48 cheshire + SendResponses also needs to handle multihoming better + +Revision 1.129 2003/05/23 02:15:37 cheshire +Fixed misleading use of the term "duplicate suppression" where it should have +said "known answer suppression". (Duplicate answer suppression is something +different, and duplicate question suppression is yet another thing, so the use +of the completely vague term "duplicate suppression" was particularly bad.) + +Revision 1.128 2003/05/23 01:55:13 cheshire + After name change, mDNSResponder needs to re-probe for name uniqueness + +Revision 1.127 2003/05/23 01:02:15 ksekar +Bug #: : mDNSResponder needs to include unique id in default name + +Revision 1.126 2003/05/22 02:29:22 cheshire + SendQueries needs to handle multihoming better +Complete rewrite of SendQueries. Works much better now :-) + +Revision 1.125 2003/05/22 01:50:45 cheshire +Fix warnings, and improve log messages + +Revision 1.124 2003/05/22 01:41:50 cheshire +DiscardDeregistrations doesn't need InterfaceID parameter + +Revision 1.123 2003/05/22 01:38:55 cheshire +Change bracketing of #pragma mark + +Revision 1.122 2003/05/21 19:59:04 cheshire + ER: Tweak responder's default name conflict behavior +Minor refinements; make sure we don't truncate in the middle of a multi-byte UTF-8 character + +Revision 1.121 2003/05/21 17:54:07 ksekar +Bug #: ER: Tweak responder's default name conflict behavior +New rename behavior - domain name "foo" becomes "foo--2" on conflict, richtext name becomes "foo (2)" + +Revision 1.120 2003/05/19 22:14:14 ksekar + mDNS probe denials/conflicts not detected unless conflict is of the same type + +Revision 1.119 2003/05/16 01:34:10 cheshire +Fix some warnings + +Revision 1.118 2003/05/14 18:48:40 cheshire + mDNSResponder should be smarter about reconfigurations +More minor refinements: +CFSocket.c needs to do *all* its mDNS_DeregisterInterface calls before freeing memory +mDNS_DeregisterInterface revalidates cache record when *any* representative of an interface goes away + +Revision 1.117 2003/05/14 07:08:36 cheshire + mDNSResponder should be smarter about reconfigurations +Previously, when there was any network configuration change, mDNSResponder +would tear down the entire list of active interfaces and start again. +That was very disruptive, and caused the entire cache to be flushed, +and caused lots of extra network traffic. Now it only removes interfaces +that have really gone, and only adds new ones that weren't there before. + +Revision 1.116 2003/05/14 06:51:56 cheshire + Rendezvous doesn't refresh server info if changed during sleep + +Revision 1.115 2003/05/14 06:44:31 cheshire +Improve debugging message + +Revision 1.114 2003/05/07 01:47:03 cheshire + Also protect against NULL domainlabels + +Revision 1.113 2003/05/07 00:28:18 cheshire + Need to make mDNSResponder more defensive against bad clients + +Revision 1.112 2003/05/06 00:00:46 cheshire + Rationalize naming of domainname manipulation functions + +Revision 1.111 2003/05/05 23:42:08 cheshire + Resolves never succeed +Was setting "rr->LastAPTime = timenow - rr->LastAPTime" +instead of "rr->LastAPTime = timenow - rr->ThisAPInterval" + +Revision 1.110 2003/04/30 21:09:59 cheshire + mDNS_vsnprintf needs to be more defensive against invalid domain names + +Revision 1.109 2003/04/26 02:41:56 cheshire + Change timenow from a local variable to a structure member + +Revision 1.108 2003/04/25 01:45:56 cheshire + mDNS_RegisterNoSuchService needs to include a host name + +Revision 1.107 2003/04/25 00:41:31 cheshire + Create single routine PurgeCacheResourceRecord(), to avoid bugs in future + +Revision 1.106 2003/04/22 03:14:45 cheshire + Include Include instrumented mDNSResponder in panther now + +Revision 1.105 2003/04/22 01:07:43 cheshire + DNSServiceRegistrationUpdateRecord should support a default ttl +If TTL parameter is zero, leave record TTL unchanged + +Revision 1.104 2003/04/21 19:15:52 cheshire +Fix some compiler warnings + +Revision 1.103 2003/04/19 02:26:35 cheshire +Bug #: Incorrect goodbye packet after conflict + +Revision 1.102 2003/04/17 03:06:28 cheshire +Bug #: No need to query again when a service goes away +Set UnansweredQueries to 2 when receiving a "goodbye" packet + +Revision 1.101 2003/04/15 20:58:31 jgraessl +Bug #: 3229014 +Added a hash to lookup records in the cache. + +Revision 1.100 2003/04/15 18:53:14 cheshire +Bug #: Bug in ScheduleNextTask +mDNS.c 1.94 incorrectly combined two "if" statements into one. + +Revision 1.99 2003/04/15 18:09:13 jgraessl +Bug #: 3228892 +Reviewed by: Stuart Cheshire +Added code to keep track of when the next cache item will expire so we can +call TidyRRCache only when necessary. + +Revision 1.98 2003/04/03 03:43:55 cheshire + Off-by-one error in probe rate limiting + +Revision 1.97 2003/04/02 01:48:17 cheshire + mDNSResponder sometimes suffers false self-conflicts when it sees its own packets +Additional fix pointed out by Josh: +Also set ProbeFailTime when incrementing NumFailedProbes when resetting a record back to probing state + +Revision 1.96 2003/04/01 23:58:55 cheshire +Minor comment changes + +Revision 1.95 2003/04/01 23:46:05 cheshire + mDNSResponder can get stuck in infinite loop after many location cycles +mDNS_DeregisterInterface() flushes the RR cache by marking all records received on that interface +to expire in one second. However, if a mDNS_StartResolveService() call is made in that one-second +window, it can get an SRV answer from one of those soon-to-be-deleted records, resulting in +FoundServiceInfoSRV() making an interface-specific query on the interface that was just removed. + +Revision 1.94 2003/03/29 01:55:19 cheshire + mDNSResponder sometimes suffers false self-conflicts when it sees its own packets +Solution: Major cleanup of packet timing and conflict handling rules + +Revision 1.93 2003/03/28 01:54:36 cheshire +Minor tidyup of IPv6 (AAAA) code + +Revision 1.92 2003/03/27 03:30:55 cheshire + Name conflicts not handled properly, resulting in memory corruption, and eventual crash +Problem was that HostNameCallback() was calling mDNS_DeregisterInterface(), which is not safe in a callback +Fixes: +1. Make mDNS_DeregisterInterface() safe to call from a callback +2. Make HostNameCallback() use mDNS_DeadvertiseInterface() instead + (it never really needed to deregister the interface at all) + +Revision 1.91 2003/03/15 04:40:36 cheshire +Change type called "mDNSOpaqueID" to the more descriptive name "mDNSInterfaceID" + +Revision 1.90 2003/03/14 20:26:37 cheshire +Reduce debugging messages (reclassify some "debugf" as "verbosedebugf") + +Revision 1.89 2003/03/12 19:57:50 cheshire +Fixed typo in debug message + +Revision 1.88 2003/03/12 00:17:44 cheshire + GetFreeCacheRR needs to be more willing to throw away recent records + +Revision 1.87 2003/03/11 01:27:20 cheshire +Reduce debugging messages (reclassify some "debugf" as "verbosedebugf") + +Revision 1.86 2003/03/06 20:44:33 cheshire +Comment tidyup + +Revision 1.85 2003/03/05 03:38:35 cheshire +Bug #: 3185731 Bogus error message in console: died or deallocated, but no record of client can be found! +Fixed by leaving client in list after conflict, until client explicitly deallocates + +Revision 1.84 2003/03/05 01:27:30 cheshire +Bug #: 3185482 Different TTL for multicast versus unicast responses +When building unicast responses, record TTLs are capped to 10 seconds + +Revision 1.83 2003/03/04 23:48:52 cheshire +Bug #: 3188865 Double probes after wake from sleep +Don't reset record type to kDNSRecordTypeUnique if record is DependentOn another + +Revision 1.82 2003/03/04 23:38:29 cheshire +Bug #: 3099194 mDNSResponder needs performance improvements +Only set rr->CRActiveQuestion to point to the +currently active representative of a question set + +Revision 1.81 2003/02/21 03:35:34 cheshire +Bug #: 3179007 mDNSResponder needs to include AAAA records in additional answer section + +Revision 1.80 2003/02/21 02:47:53 cheshire +Bug #: 3099194 mDNSResponder needs performance improvements +Several places in the code were calling CacheRRActive(), which searched the entire +question list every time, to see if this cache resource record answers any question. +Instead, we now have a field "CRActiveQuestion" in the resource record structure + +Revision 1.79 2003/02/21 01:54:07 cheshire +Bug #: 3099194 mDNSResponder needs performance improvements +Switched to using new "mDNS_Execute" model (see "Implementer Notes.txt") + +Revision 1.78 2003/02/20 06:48:32 cheshire +Bug #: 3169535 Xserve RAID needs to do interface-specific registrations +Reviewed by: Josh Graessley, Bob Bradley + +Revision 1.77 2003/01/31 03:35:59 cheshire +Bug #: 3147097 mDNSResponder sometimes fails to find the correct results +When there were *two* active questions in the list, they were incorrectly +finding *each other* and *both* being marked as duplicates of another question + +Revision 1.76 2003/01/29 02:46:37 cheshire +Fix for IPv6: +A physical interface is identified solely by its InterfaceID (not by IP and type). +On a given InterfaceID, mDNSCore may send both v4 and v6 multicasts. +In cases where the requested outbound protocol (v4 or v6) is not supported on +that InterfaceID, the platform support layer should simply discard that packet. + +Revision 1.75 2003/01/29 01:47:40 cheshire +Rename 'Active' to 'CRActive' or 'InterfaceActive' for improved clarity + +Revision 1.74 2003/01/28 05:26:25 cheshire +Bug #: 3147097 mDNSResponder sometimes fails to find the correct results +Add 'Active' flag for interfaces + +Revision 1.73 2003/01/28 03:45:12 cheshire +Fixed missing "not" in "!mDNSAddrIsDNSMulticast(dstaddr)" + +Revision 1.72 2003/01/28 01:49:48 cheshire +Bug #: 3147097 mDNSResponder sometimes fails to find the correct results +FindDuplicateQuestion() was incorrectly finding the question itself in the list, +and incorrectly marking it as a duplicate (of itself), so that it became inactive. + +Revision 1.71 2003/01/28 01:41:44 cheshire +Bug #: 3153091 Race condition when network change causes bad stuff +When an interface goes away, interface-specific questions on that interface become orphaned. +Orphan questions cause HaveQueries to return true, but there's no interface to send them on. +Fix: mDNS_DeregisterInterface() now calls DeActivateInterfaceQuestions() + +Revision 1.70 2003/01/23 19:00:20 cheshire +Protect against infinite loops in mDNS_Execute + +Revision 1.69 2003/01/21 22:56:32 jgraessl +Bug #: 3124348 service name changes are not properly handled +Submitted by: Stuart Cheshire +Reviewed by: Joshua Graessley +Applying changes for 3124348 to main branch. 3124348 changes went in to a +branch for SU. + +Revision 1.68 2003/01/17 04:09:27 cheshire +Bug #: 3141038 mDNSResponder Resolves are unreliable on multi-homed hosts + +Revision 1.67 2003/01/17 03:56:45 cheshire +Default 24-hour TTL is far too long. Changing to two hours. + +Revision 1.66 2003/01/13 23:49:41 jgraessl +Merged changes for the following fixes in to top of tree: +3086540 computer name changes not handled properly +3124348 service name changes are not properly handled +3124352 announcements sent in pairs, failing chattiness test + +Revision 1.65 2002/12/23 22:13:28 jgraessl +Reviewed by: Stuart Cheshire +Initial IPv6 support for mDNSResponder. + +Revision 1.64 2002/11/26 20:49:06 cheshire +Bug #: 3104543 RFC 1123 allows the first character of a name label to be either a letter or a digit + +Revision 1.63 2002/09/21 20:44:49 zarzycki +Added APSL info + +Revision 1.62 2002/09/20 03:25:37 cheshire +Fix some compiler warnings + +Revision 1.61 2002/09/20 01:05:24 cheshire +Don't kill the Extras list in mDNS_DeregisterService() + +Revision 1.60 2002/09/19 23:47:35 cheshire +Added mDNS_RegisterNoSuchService() function for assertion of non-existance +of a particular named service + +Revision 1.59 2002/09/19 21:25:34 cheshire +mDNS_snprintf() doesn't need to be in a separate file + +Revision 1.58 2002/09/19 04:20:43 cheshire +Remove high-ascii characters that confuse some systems + +Revision 1.57 2002/09/17 01:07:08 cheshire +Change mDNS_AdvertiseLocalAddresses to be a parameter to mDNS_Init() + +Revision 1.56 2002/09/16 19:44:17 cheshire +Merge in license terms from Quinn's copy, in preparation for Darwin release +*/ + +#define TEST_LOCALONLY_FOR_EVERYTHING 0 + +#include "mDNSClientAPI.h" // Defines the interface provided to the client layer above +#include "mDNSPlatformFunctions.h" // Defines the interface required of the supporting layer below + +// Disable certain benign warnings with Microsoft compilers +#if(defined(_MSC_VER)) + // Disable "conditional expression is constant" warning for debug macros. + // Otherwise, this generates warnings for the perfectly natural construct "while(1)" + // If someone knows a variant way of writing "while(1)" that doesn't generate warning messages, please let us know + #pragma warning(disable:4127) + + // Disable "const object should be initialized" + // We know that static/globals are defined to be zeroed in ANSI C, and to avoid this warning would require some + // *really* ugly chunk of zeroes and curly braces to initialize zeroRR and mDNSprintf_format_default to all zeroes + #pragma warning(disable:4132) + + // Disable "assignment within conditional expression". + // Other compilers understand the convention that if you place the assignment expression within an extra pair + // of parentheses, this signals to the compiler that you really intended an assignment and no warning is necessary. + // The Microsoft compiler doesn't understand this convention, so in the absense of any other way to signal + // to the compiler that the assignment is intentional, we have to just turn this warning off completely. + #pragma warning(disable:4706) +#endif + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - DNS Protocol Constants +#endif + +typedef enum + { + kDNSFlag0_QR_Mask = 0x80, // Query or response? + kDNSFlag0_QR_Query = 0x00, + kDNSFlag0_QR_Response = 0x80, + + kDNSFlag0_OP_Mask = 0x78, // Operation type + kDNSFlag0_OP_StdQuery = 0x00, + kDNSFlag0_OP_Iquery = 0x08, + kDNSFlag0_OP_Status = 0x10, + kDNSFlag0_OP_Unused3 = 0x18, + kDNSFlag0_OP_Notify = 0x20, + kDNSFlag0_OP_Update = 0x28, + + kDNSFlag0_QROP_Mask = kDNSFlag0_QR_Mask | kDNSFlag0_OP_Mask, + + kDNSFlag0_AA = 0x04, // Authoritative Answer? + kDNSFlag0_TC = 0x02, // Truncated? + kDNSFlag0_RD = 0x01, // Recursion Desired? + kDNSFlag1_RA = 0x80, // Recursion Available? + + kDNSFlag1_Zero = 0x40, // Reserved; must be zero + kDNSFlag1_AD = 0x20, // Authentic Data [RFC 2535] + kDNSFlag1_CD = 0x10, // Checking Disabled [RFC 2535] + + kDNSFlag1_RC = 0x0F, // Response code + kDNSFlag1_RC_NoErr = 0x00, + kDNSFlag1_RC_FmtErr = 0x01, + kDNSFlag1_RC_SrvErr = 0x02, + kDNSFlag1_RC_NXDomain = 0x03, + kDNSFlag1_RC_NotImpl = 0x04, + kDNSFlag1_RC_Refused = 0x05, + kDNSFlag1_RC_YXDomain = 0x06, + kDNSFlag1_RC_YXRRSet = 0x07, + kDNSFlag1_RC_NXRRSet = 0x08, + kDNSFlag1_RC_NotAuth = 0x09, + kDNSFlag1_RC_NotZone = 0x0A + } DNS_Flags; + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Program Constants +#endif + +mDNSexport const ResourceRecord zeroRR; +mDNSexport const mDNSIPPort zeroIPPort = { { 0 } }; +mDNSexport const mDNSv4Addr zeroIPAddr = { { 0 } }; +mDNSexport const mDNSv6Addr zerov6Addr = { { 0 } }; +mDNSexport const mDNSv4Addr onesIPv4Addr = { { 255, 255, 255, 255 } }; +mDNSexport const mDNSv6Addr onesIPv6Addr = { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } }; +mDNSlocal const mDNSAddr zeroAddr = { mDNSAddrType_None, {{{ 0 }}} }; + +mDNSexport const mDNSInterfaceID mDNSInterface_Any = { 0 }; +mDNSlocal const mDNSInterfaceID mDNSInterfaceMark = { (mDNSInterfaceID)~0 }; + +#define UnicastDNSPortAsNumber 53 +#define MulticastDNSPortAsNumber 5353 +mDNSexport const mDNSIPPort UnicastDNSPort = { { UnicastDNSPortAsNumber >> 8, UnicastDNSPortAsNumber & 0xFF } }; +mDNSexport const mDNSIPPort MulticastDNSPort = { { MulticastDNSPortAsNumber >> 8, MulticastDNSPortAsNumber & 0xFF } }; +mDNSexport const mDNSv4Addr AllDNSAdminGroup = { { 239, 255, 255, 251 } }; +mDNSexport const mDNSv4Addr AllDNSLinkGroup = { { 224, 0, 0, 251 } }; +mDNSexport const mDNSv6Addr AllDNSLinkGroupv6 = { { 0xFF,0x02,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0xFB } }; +mDNSexport const mDNSAddr AllDNSLinkGroup_v4 = { mDNSAddrType_IPv4, { { { 224, 0, 0, 251 } } } }; +mDNSexport const mDNSAddr AllDNSLinkGroup_v6 = { mDNSAddrType_IPv6, { { { 0xFF,0x02,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0xFB } } } }; + +static const mDNSOpaque16 zeroID = { { 0, 0 } }; +static const mDNSOpaque16 QueryFlags = { { kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery, 0 } }; +static const mDNSOpaque16 ResponseFlags = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery | kDNSFlag0_AA, 0 } }; +#define zeroDomainNamePtr ((domainname*)"") + +// Any records bigger than this are considered 'large' records +#define SmallRecordLimit 1024 + +#define kDefaultTTLforUnique 240 +#define kDefaultTTLforShared (2*3600) + +#define kMaxUpdateCredits 10 + +static const char *const mDNS_DomainTypeNames[] = + { + "_browse._mdns._udp.local.", + "_default._browse._mdns._udp.local.", + "_register._mdns._udp.local.", + "_default._register._mdns._udp.local." + }; + +#define AssignDomainName(DST, SRC) mDNSPlatformMemCopy((SRC).c, (DST).c, DomainNameLength(&(SRC))) + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Specialized mDNS version of vsnprintf +#endif + +static const struct mDNSprintf_format + { + unsigned leftJustify : 1; + unsigned forceSign : 1; + unsigned zeroPad : 1; + unsigned havePrecision : 1; + unsigned hSize : 1; + unsigned lSize : 1; + char altForm; + char sign; // +, - or space + unsigned int fieldWidth; + unsigned int precision; + } mDNSprintf_format_default; + +mDNSexport mDNSu32 mDNS_vsnprintf(char *sbuffer, mDNSu32 buflen, const char *fmt, va_list arg) + { + mDNSu32 nwritten = 0; + int c; + buflen--; // Pre-reserve one space in the buffer for the terminating nul + + for (c = *fmt; c != 0; c = *++fmt) + { + if (c != '%') + { + *sbuffer++ = (char)c; + if (++nwritten >= buflen) goto exit; + } + else + { + unsigned int i=0, j; + // The mDNS Vsprintf Argument Conversion Buffer is used as a temporary holding area for + // generating decimal numbers, hexdecimal numbers, IP addresses, domain name strings, etc. + // The size needs to be enough for a 256-byte domain name plus some error text. + #define mDNS_VACB_Size 300 + char mDNS_VACB[mDNS_VACB_Size]; + #define mDNS_VACB_Lim (&mDNS_VACB[mDNS_VACB_Size]) + #define mDNS_VACB_Remain(s) ((mDNSu32)(mDNS_VACB_Lim - s)) + char *s = mDNS_VACB_Lim, *digits; + struct mDNSprintf_format F = mDNSprintf_format_default; + + while (1) // decode flags + { + c = *++fmt; + if (c == '-') F.leftJustify = 1; + else if (c == '+') F.forceSign = 1; + else if (c == ' ') F.sign = ' '; + else if (c == '#') F.altForm++; + else if (c == '0') F.zeroPad = 1; + else break; + } + + if (c == '*') // decode field width + { + int f = va_arg(arg, int); + if (f < 0) { f = -f; F.leftJustify = 1; } + F.fieldWidth = (unsigned int)f; + c = *++fmt; + } + else + { + for (; c >= '0' && c <= '9'; c = *++fmt) + F.fieldWidth = (10 * F.fieldWidth) + (c - '0'); + } + + if (c == '.') // decode precision + { + if ((c = *++fmt) == '*') + { F.precision = va_arg(arg, unsigned int); c = *++fmt; } + else for (; c >= '0' && c <= '9'; c = *++fmt) + F.precision = (10 * F.precision) + (c - '0'); + F.havePrecision = 1; + } + + if (F.leftJustify) F.zeroPad = 0; + + conv: + switch (c) // perform appropriate conversion + { + unsigned long n; + case 'h' : F.hSize = 1; c = *++fmt; goto conv; + case 'l' : // fall through + case 'L' : F.lSize = 1; c = *++fmt; goto conv; + case 'd' : + case 'i' : if (F.lSize) n = (unsigned long)va_arg(arg, long); + else n = (unsigned long)va_arg(arg, int); + if (F.hSize) n = (short) n; + if ((long) n < 0) { n = (unsigned long)-(long)n; F.sign = '-'; } + else if (F.forceSign) F.sign = '+'; + goto decimal; + case 'u' : if (F.lSize) n = va_arg(arg, unsigned long); + else n = va_arg(arg, unsigned int); + if (F.hSize) n = (unsigned short) n; + F.sign = 0; + goto decimal; + decimal: if (!F.havePrecision) + { + if (F.zeroPad) + { + F.precision = F.fieldWidth; + if (F.sign) --F.precision; + } + if (F.precision < 1) F.precision = 1; + } + if (F.precision > mDNS_VACB_Size - 1) + F.precision = mDNS_VACB_Size - 1; + for (i = 0; n; n /= 10, i++) *--s = (char)(n % 10 + '0'); + for (; i < F.precision; i++) *--s = '0'; + if (F.sign) { *--s = F.sign; i++; } + break; + + case 'o' : if (F.lSize) n = va_arg(arg, unsigned long); + else n = va_arg(arg, unsigned int); + if (F.hSize) n = (unsigned short) n; + if (!F.havePrecision) + { + if (F.zeroPad) F.precision = F.fieldWidth; + if (F.precision < 1) F.precision = 1; + } + if (F.precision > mDNS_VACB_Size - 1) + F.precision = mDNS_VACB_Size - 1; + for (i = 0; n; n /= 8, i++) *--s = (char)(n % 8 + '0'); + if (F.altForm && i && *s != '0') { *--s = '0'; i++; } + for (; i < F.precision; i++) *--s = '0'; + break; + + case 'a' : { + unsigned char *a = va_arg(arg, unsigned char *); + if (!a) { static char emsg[] = "<>"; s = emsg; i = sizeof(emsg)-1; } + else + { + unsigned short *w = (unsigned short *)a; + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + if (F.altForm) + { + mDNSAddr *ip = (mDNSAddr*)a; + a = (unsigned char *)&ip->ip.v4; + w = (unsigned short *)&ip->ip.v6; + switch (ip->type) + { + case mDNSAddrType_IPv4: F.precision = 4; break; + case mDNSAddrType_IPv6: F.precision = 16; break; + default: F.precision = 0; break; + } + } + switch (F.precision) + { + case 4: i = mDNS_snprintf(mDNS_VACB, sizeof(mDNS_VACB), "%d.%d.%d.%d", + a[0], a[1], a[2], a[3]); break; + case 6: i = mDNS_snprintf(mDNS_VACB, sizeof(mDNS_VACB), "%02X:%02X:%02X:%02X:%02X:%02X", + a[0], a[1], a[2], a[3], a[4], a[5]); break; + case 16: i = mDNS_snprintf(mDNS_VACB, sizeof(mDNS_VACB), "%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X", + w[0], w[1], w[2], w[3], w[4], w[5], w[6], w[7]); break; + default: i = mDNS_snprintf(mDNS_VACB, sizeof(mDNS_VACB), "%s", "<< ERROR: Must specify address size " + "(i.e. %.4a=IPv4, %.6a=Ethernet, %.16a=IPv6) >>"); break; + } + } + } + break; + + case 'p' : F.havePrecision = F.lSize = 1; + F.precision = 8; + case 'X' : digits = "0123456789ABCDEF"; + goto hexadecimal; + case 'x' : digits = "0123456789abcdef"; + hexadecimal:if (F.lSize) n = va_arg(arg, unsigned long); + else n = va_arg(arg, unsigned int); + if (F.hSize) n = (unsigned short) n; + if (!F.havePrecision) + { + if (F.zeroPad) + { + F.precision = F.fieldWidth; + if (F.altForm) F.precision -= 2; + } + if (F.precision < 1) F.precision = 1; + } + if (F.precision > mDNS_VACB_Size - 1) + F.precision = mDNS_VACB_Size - 1; + for (i = 0; n; n /= 16, i++) *--s = digits[n % 16]; + for (; i < F.precision; i++) *--s = '0'; + if (F.altForm) { *--s = (char)c; *--s = '0'; i += 2; } + break; + + case 'c' : *--s = (char)va_arg(arg, int); i = 1; break; + + case 's' : s = va_arg(arg, char *); + if (!s) { static char emsg[] = "<>"; s = emsg; i = sizeof(emsg)-1; } + else switch (F.altForm) + { + case 0: { char *a=s; i=0; while(*a++) i++; break; } // C string + case 1: i = (unsigned char) *s++; break; // Pascal string + case 2: { // DNS label-sequence name + unsigned char *a = (unsigned char *)s; + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + if (*a == 0) *s++ = '.'; // Special case for root DNS name + while (*a) + { + if (*a > 63) { s += mDNS_snprintf(s, mDNS_VACB_Remain(s), "<>", *a); break; } + if (s + *a >= &mDNS_VACB[254]) { s += mDNS_snprintf(s, mDNS_VACB_Remain(s), "<>"); break; } + s += mDNS_snprintf(s, mDNS_VACB_Remain(s), "%#s.", a); + a += 1 + *a; + } + i = (mDNSu32)(s - mDNS_VACB); + s = mDNS_VACB; // Reset s back to the start of the buffer + break; + } + } + if (F.havePrecision && i > F.precision) // Make sure we don't truncate in the middle of a UTF-8 character + { i = F.precision; while (i>0 && (s[i] & 0xC0) == 0x80) i--; } + break; + + case 'n' : s = va_arg(arg, char *); + if (F.hSize) * (short *) s = (short)nwritten; + else if (F.lSize) * (long *) s = (long)nwritten; + else * (int *) s = (int)nwritten; + continue; + + default: s = mDNS_VACB; + i = mDNS_snprintf(mDNS_VACB, sizeof(mDNS_VACB), "<>", c); + + case '%' : *sbuffer++ = (char)c; + if (++nwritten >= buflen) goto exit; + break; + } + + if (i < F.fieldWidth && !F.leftJustify) // Pad on the left + do { + *sbuffer++ = ' '; + if (++nwritten >= buflen) goto exit; + } while (i < --F.fieldWidth); + + if (i > buflen - nwritten) // Make sure we don't truncate in the middle of a UTF-8 character + { i = buflen - nwritten; while (i>0 && (s[i] & 0xC0) == 0x80) i--; } + for (j=0; j= buflen) goto exit; + + for (; i < F.fieldWidth; i++) // Pad on the right + { + *sbuffer++ = ' '; + if (++nwritten >= buflen) goto exit; + } + } + } + exit: + *sbuffer++ = 0; + return(nwritten); + } + +mDNSexport mDNSu32 mDNS_snprintf(char *sbuffer, mDNSu32 buflen, const char *fmt, ...) + { + mDNSu32 length; + + va_list ptr; + va_start(ptr,fmt); + length = mDNS_vsnprintf(sbuffer, buflen, fmt, ptr); + va_end(ptr); + + return(length); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - General Utility Functions +#endif + +mDNSexport char *DNSTypeName(mDNSu16 rrtype) + { + switch (rrtype) + { + case kDNSType_A: return("Addr"); + case kDNSType_CNAME:return("CNAME"); + case kDNSType_NULL: return("NULL"); + case kDNSType_PTR: return("PTR"); + case kDNSType_HINFO:return("HINFO"); + case kDNSType_TXT: return("TXT"); + case kDNSType_AAAA: return("AAAA"); + case kDNSType_SRV: return("SRV"); + case kDNSQType_ANY: return("ANY"); + default: { + static char buffer[16]; + mDNS_snprintf(buffer, sizeof(buffer), "(%d)", rrtype); + return(buffer); + } + } + } + +mDNSexport char *GetRRDisplayString_rdb(mDNS *const m, const ResourceRecord *rr, RDataBody *rd) + { + char *ptr = m->MsgBuffer; + mDNSu32 length = mDNS_snprintf(m->MsgBuffer, 79, "%4d %##s %s ", rr->rdlength, rr->name.c, DNSTypeName(rr->rrtype)); + switch (rr->rrtype) + { + case kDNSType_A: mDNS_snprintf(m->MsgBuffer+length, 79-length, "%.4a", &rd->ip); break; + case kDNSType_CNAME:// Same as PTR + case kDNSType_PTR: mDNS_snprintf(m->MsgBuffer+length, 79-length, "%##s", &rd->name); break; + case kDNSType_HINFO:// Display this the same as TXT (just show first string) + case kDNSType_TXT: mDNS_snprintf(m->MsgBuffer+length, 79-length, "%#s", rd->txt.c); break; + case kDNSType_AAAA: mDNS_snprintf(m->MsgBuffer+length, 79-length, "%.16a", &rd->ipv6); break; + case kDNSType_SRV: mDNS_snprintf(m->MsgBuffer+length, 79-length, "%##s", &rd->srv.target); break; + default: mDNS_snprintf(m->MsgBuffer+length, 79-length, "RDLen %d: %s", + rr->rdlength, rd->data); break; + } + for (ptr = m->MsgBuffer; *ptr; ptr++) if (*ptr < ' ') *ptr='.'; + return(m->MsgBuffer); + } + +mDNSlocal mDNSu32 mDNSRandom(mDNSu32 max) + { + static mDNSu32 seed = 0; + mDNSu32 mask = 1; + + if (!seed) seed = (mDNSu32)mDNSPlatformTimeNow(); + while (mask < max) mask = (mask << 1) | 1; + do seed = seed * 21 + 1; while ((seed & mask) > max); + return (seed & mask); + } + +#define mDNSSameIPv4Address(A,B) ((A).NotAnInteger == (B).NotAnInteger) +#define mDNSSameIPv6Address(A,B) ((A).l[0] == (B).l[0] && (A).l[1] == (B).l[1] && (A).l[2] == (B).l[2] && (A).l[3] == (B).l[3]) + +#define mDNSIPv4AddressIsZero(A) mDNSSameIPv4Address((A), zeroIPAddr) +#define mDNSIPv6AddressIsZero(A) mDNSSameIPv6Address((A), zerov6Addr) + +#define mDNSIPv4AddressIsOnes(A) mDNSSameIPv4Address((A), onesIPv4Addr) +#define mDNSIPv6AddressIsOnes(A) mDNSSameIPv6Address((A), onesIPv6Addr) + +#define mDNSAddressIsZero(X) ( \ + ((X)->type == mDNSAddrType_IPv4 && mDNSIPv4AddressIsZero((X)->ip.v4)) || \ + ((X)->type == mDNSAddrType_IPv6 && mDNSIPv6AddressIsZero((X)->ip.v6)) ) + +#define mDNSAddressIsOnes(X) ( \ + ((X)->type == mDNSAddrType_IPv4 && mDNSIPv4AddressIsOnes((X)->ip.v4)) || \ + ((X)->type == mDNSAddrType_IPv6 && mDNSIPv6AddressIsOnes((X)->ip.v6)) ) + +#define mDNSAddressIsValid(X) ( \ + ((X)->type == mDNSAddrType_IPv4) ? !(mDNSIPv4AddressIsZero((X)->ip.v4) || mDNSIPv4AddressIsOnes((X)->ip.v4)) : \ + ((X)->type == mDNSAddrType_IPv6) ? !(mDNSIPv6AddressIsZero((X)->ip.v6) || mDNSIPv6AddressIsOnes((X)->ip.v6)) : mDNSfalse) + +mDNSexport mDNSBool mDNSSameAddress(const mDNSAddr *ip1, const mDNSAddr *ip2) + { + if (ip1->type == ip2->type) + { + switch (ip1->type) + { + case mDNSAddrType_IPv4 : return(mDNSBool)(mDNSSameIPv4Address(ip1->ip.v4, ip2->ip.v4)); + case mDNSAddrType_IPv6 : return(mDNSBool)(mDNSSameIPv6Address(ip1->ip.v6, ip2->ip.v6)); + } + } + return(mDNSfalse); + } + +mDNSlocal mDNSBool mDNSAddrIsDNSMulticast(const mDNSAddr *ip) + { + switch(ip->type) + { + case mDNSAddrType_IPv4: return(mDNSBool)(ip->ip.v4.NotAnInteger == AllDNSLinkGroup.NotAnInteger); + case mDNSAddrType_IPv6: return(mDNSBool)(ip->ip.v6.l[0] == AllDNSLinkGroupv6.l[0] && + ip->ip.v6.l[1] == AllDNSLinkGroupv6.l[1] && + ip->ip.v6.l[2] == AllDNSLinkGroupv6.l[2] && + ip->ip.v6.l[3] == AllDNSLinkGroupv6.l[3] ); + default: return(mDNSfalse); + } + } + +mDNSlocal const NetworkInterfaceInfo *GetFirstActiveInterface(const NetworkInterfaceInfo *intf) + { + while (intf && !intf->InterfaceActive) intf = intf->next; + return(intf); + } + +mDNSlocal mDNSInterfaceID GetNextActiveInterfaceID(const NetworkInterfaceInfo *intf) + { + const NetworkInterfaceInfo *next = GetFirstActiveInterface(intf->next); + if (next) return(next->InterfaceID); else return(mDNSNULL); + } + +#define InitialQuestionInterval (mDNSPlatformOneSecond/2) +#define ActiveQuestion(Q) ((Q)->ThisQInterval > 0 && !(Q)->DuplicateOf) +#define TimeToSendThisQuestion(Q,time) (ActiveQuestion(Q) && (time) - ((Q)->LastQTime + (Q)->ThisQInterval) >= 0) + +mDNSlocal void SetNextQueryTime(mDNS *const m, const DNSQuestion *const q) + { + if (ActiveQuestion(q)) + if (m->NextScheduledQuery - (q->LastQTime + q->ThisQInterval) > 0) + m->NextScheduledQuery = (q->LastQTime + q->ThisQInterval); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Domain Name Utility Functions +#endif + +#define mdnsIsDigit(X) ((X) >= '0' && (X) <= '9') +#define mDNSIsUpperCase(X) ((X) >= 'A' && (X) <= 'Z') +#define mDNSIsLowerCase(X) ((X) >= 'a' && (X) <= 'z') +#define mdnsIsLetter(X) (mDNSIsUpperCase(X) || mDNSIsLowerCase(X)) + +mDNSexport mDNSBool SameDomainLabel(const mDNSu8 *a, const mDNSu8 *b) + { + int i; + const int len = *a++; + + if (len > MAX_DOMAIN_LABEL) + { debugf("Malformed label (too long)"); return(mDNSfalse); } + + if (len != *b++) return(mDNSfalse); + for (i=0; ic; + const mDNSu8 * b = d2->c; + const mDNSu8 *const max = d1->c + MAX_DOMAIN_NAME; // Maximum that's valid + + while (*a || *b) + { + if (a + 1 + *a >= max) + { debugf("Malformed domain name (more than 255 characters)"); return(mDNSfalse); } + if (!SameDomainLabel(a, b)) return(mDNSfalse); + a += 1 + *a; + b += 1 + *b; + } + + return(mDNStrue); + } + +// Returns length of a domain name INCLUDING the byte for the final null label +// i.e. for the root label "." it returns one +// For the FQDN "com." it returns 5 (length byte, three data bytes, final zero) +// Legal results are 1 (just root label) to 255 (MAX_DOMAIN_NAME) +// If the given domainname is invalid, result is 256 +mDNSexport mDNSu16 DomainNameLength(const domainname *const name) + { + const mDNSu8 *src = name->c; + while (*src) + { + if (*src > MAX_DOMAIN_LABEL) return(MAX_DOMAIN_NAME+1); + src += 1 + *src; + if (src - name->c >= MAX_DOMAIN_NAME) return(MAX_DOMAIN_NAME+1); + } + return((mDNSu16)(src - name->c + 1)); + } + +// CompressedDomainNameLength returns the length of a domain name INCLUDING the byte +// for the final null label i.e. for the root label "." it returns one. +// E.g. for the FQDN "foo.com." it returns 9 +// (length, three data bytes, length, three more data bytes, final zero). +// In the case where a parent domain name is provided, and the given name is a child +// of that parent, CompressedDomainNameLength returns the length of the prefix portion +// of the child name, plus TWO bytes for the compression pointer. +// E.g. for the name "foo.com." with parent "com.", it returns 6 +// (length, three data bytes, two-byte compression pointer). +mDNSlocal mDNSu16 CompressedDomainNameLength(const domainname *const name, const domainname *parent) + { + const mDNSu8 *src = name->c; + if (parent && parent->c[0] == 0) parent = mDNSNULL; + while (*src) + { + if (*src > MAX_DOMAIN_LABEL) return(MAX_DOMAIN_NAME+1); + if (parent && SameDomainName((domainname *)src, parent)) return((mDNSu16)(src - name->c + 2)); + src += 1 + *src; + if (src - name->c >= MAX_DOMAIN_NAME) return(MAX_DOMAIN_NAME+1); + } + return((mDNSu16)(src - name->c + 1)); + } + +// AppendLiteralLabelString appends a single label to an existing (possibly empty) domainname. +// The C string contains the label as-is, with no escaping, etc. +// Any dots in the name are literal dots, not label separators +// If successful, AppendLiteralLabelString returns a pointer to the next unused byte +// in the domainname bufer (i.e., the next byte after the terminating zero). +// If unable to construct a legal domain name (i.e. label more than 63 bytes, or total more than 255 bytes) +// AppendLiteralLabelString returns mDNSNULL. +mDNSexport mDNSu8 *AppendLiteralLabelString(domainname *const name, const char *cstr) + { + mDNSu8 * ptr = name->c + DomainNameLength(name) - 1; // Find end of current name + const mDNSu8 *const lim1 = name->c + MAX_DOMAIN_NAME - 1; // Limit of how much we can add (not counting final zero) + const mDNSu8 *const lim2 = ptr + 1 + MAX_DOMAIN_LABEL; + const mDNSu8 *const lim = (lim1 < lim2) ? lim1 : lim2; + mDNSu8 *lengthbyte = ptr++; // Record where the length is going to go + + while (*cstr && ptr < lim) *ptr++ = (mDNSu8)*cstr++; // Copy the data + *lengthbyte = (mDNSu8)(ptr - lengthbyte - 1); // Fill in the length byte + *ptr++ = 0; // Put the null root label on the end + if (*cstr) return(mDNSNULL); // Failure: We didn't successfully consume all input + else return(ptr); // Success: return new value of ptr + } + +// AppendDNSNameString appends zero or more labels to an existing (possibly empty) domainname. +// The C string is in conventional DNS syntax: +// Textual labels, escaped as necessary using the usual DNS '\' notation, separated by dots. +// If successful, AppendDNSNameString returns a pointer to the next unused byte +// in the domainname bufer (i.e., the next byte after the terminating zero). +// If unable to construct a legal domain name (i.e. label more than 63 bytes, or total more than 255 bytes) +// AppendDNSNameString returns mDNSNULL. +mDNSexport mDNSu8 *AppendDNSNameString(domainname *const name, const char *cstr) + { + mDNSu8 * ptr = name->c + DomainNameLength(name) - 1; // Find end of current name + const mDNSu8 *const lim = name->c + MAX_DOMAIN_NAME - 1; // Limit of how much we can add (not counting final zero) + while (*cstr && ptr < lim) // While more characters, and space to put them... + { + mDNSu8 *lengthbyte = ptr++; // Record where the length is going to go + while (*cstr && *cstr != '.' && ptr < lim) // While we have characters in the label... + { + mDNSu8 c = (mDNSu8)*cstr++; // Read the character + if (c == '\\') // If escape character, check next character + { + if (*cstr == '\\' || *cstr == '.') // If a second escape, or a dot, + c = (mDNSu8)*cstr++; // just use the second character + else if (mdnsIsDigit(cstr[0]) && mdnsIsDigit(cstr[1]) && mdnsIsDigit(cstr[2])) + { // else, if three decimal digits, + int v0 = cstr[0] - '0'; // then interpret as three-digit decimal + int v1 = cstr[1] - '0'; + int v2 = cstr[2] - '0'; + int val = v0 * 100 + v1 * 10 + v2; + if (val <= 255) { c = (mDNSu8)val; cstr += 3; } // If valid value, use it + } + } + *ptr++ = c; // Write the character + } + if (*cstr) cstr++; // Skip over the trailing dot (if present) + if (ptr - lengthbyte - 1 > MAX_DOMAIN_LABEL) // If illegal label, abort + return(mDNSNULL); + *lengthbyte = (mDNSu8)(ptr - lengthbyte - 1); // Fill in the length byte + } + + *ptr++ = 0; // Put the null root label on the end + if (*cstr) return(mDNSNULL); // Failure: We didn't successfully consume all input + else return(ptr); // Success: return new value of ptr + } + +// AppendDomainLabel appends a single label to a name. +// If successful, AppendDomainLabel returns a pointer to the next unused byte +// in the domainname bufer (i.e., the next byte after the terminating zero). +// If unable to construct a legal domain name (i.e. label more than 63 bytes, or total more than 255 bytes) +// AppendDomainLabel returns mDNSNULL. +mDNSexport mDNSu8 *AppendDomainLabel(domainname *const name, const domainlabel *const label) + { + int i; + mDNSu8 *ptr = name->c + DomainNameLength(name) - 1; + + // Check label is legal + if (label->c[0] > MAX_DOMAIN_LABEL) return(mDNSNULL); + + // Check that ptr + length byte + data bytes + final zero does not exceed our limit + if (ptr + 1 + label->c[0] + 1 > name->c + MAX_DOMAIN_NAME) return(mDNSNULL); + + for (i=0; i<=label->c[0]; i++) *ptr++ = label->c[i]; // Copy the label data + *ptr++ = 0; // Put the null root label on the end + return(ptr); + } + +mDNSexport mDNSu8 *AppendDomainName(domainname *const name, const domainname *const append) + { + mDNSu8 * ptr = name->c + DomainNameLength(name) - 1; // Find end of current name + const mDNSu8 *const lim = name->c + MAX_DOMAIN_NAME - 1; // Limit of how much we can add (not counting final zero) + const mDNSu8 * src = append->c; + while(src[0]) + { + int i; + if (ptr + 1 + src[0] > lim) return(mDNSNULL); + for (i=0; i<=src[0]; i++) *ptr++ = src[i]; + *ptr = 0; // Put the null root label on the end + src += i; + } + return(ptr); + } + +// MakeDomainLabelFromLiteralString makes a single domain label from a single literal C string (with no escaping). +// If successful, MakeDomainLabelFromLiteralString returns mDNStrue. +// If unable to convert the whole string to a legal domain label (i.e. because length is more than 63 bytes) then +// MakeDomainLabelFromLiteralString makes a legal domain label from the first 63 bytes of the string and returns mDNSfalse. +// In some cases silently truncated oversized names to 63 bytes is acceptable, so the return result may be ignored. +// In other cases silent truncation may not be acceptable, so in those cases the calling function needs to check the return result. +mDNSexport mDNSBool MakeDomainLabelFromLiteralString(domainlabel *const label, const char *cstr) + { + mDNSu8 * ptr = label->c + 1; // Where we're putting it + const mDNSu8 *const limit = label->c + 1 + MAX_DOMAIN_LABEL; // The maximum we can put + while (*cstr && ptr < limit) *ptr++ = (mDNSu8)*cstr++; // Copy the label + label->c[0] = (mDNSu8)(ptr - label->c - 1); // Set the length byte + return(*cstr == 0); // Return mDNStrue if we successfully consumed all input + } + +// MakeDomainNameFromDNSNameString makes a native DNS-format domainname from a C string. +// The C string is in conventional DNS syntax: +// Textual labels, escaped as necessary using the usual DNS '\' notation, separated by dots. +// If successful, MakeDomainNameFromDNSNameString returns a pointer to the next unused byte +// in the domainname bufer (i.e., the next byte after the terminating zero). +// If unable to construct a legal domain name (i.e. label more than 63 bytes, or total more than 255 bytes) +// MakeDomainNameFromDNSNameString returns mDNSNULL. +mDNSexport mDNSu8 *MakeDomainNameFromDNSNameString(domainname *const name, const char *cstr) + { + name->c[0] = 0; // Make an empty domain name + return(AppendDNSNameString(name, cstr)); // And then add this string to it + } + +mDNSexport char *ConvertDomainLabelToCString_withescape(const domainlabel *const label, char *ptr, char esc) + { + const mDNSu8 * src = label->c; // Domain label we're reading + const mDNSu8 len = *src++; // Read length of this (non-null) label + const mDNSu8 *const end = src + len; // Work out where the label ends + if (len > MAX_DOMAIN_LABEL) return(mDNSNULL); // If illegal label, abort + while (src < end) // While we have characters in the label + { + mDNSu8 c = *src++; + if (esc) + { + if (c == '.' || c == esc) // If character is a dot or the escape character + *ptr++ = esc; // Output escape character + else if (c <= ' ') // If non-printing ascii, + { // Output decimal escape sequence + *ptr++ = esc; + *ptr++ = (char) ('0' + (c / 100) ); + *ptr++ = (char) ('0' + (c / 10) % 10); + c = (mDNSu8)('0' + (c ) % 10); + } + } + *ptr++ = (char)c; // Copy the character + } + *ptr = 0; // Null-terminate the string + return(ptr); // and return + } + +// Note, to guarantee that there will be no possible overrun, cstr must be at least 1005 bytes +// The longest legal domain name is 255 bytes, in the form of three 64-byte labels, one 62-byte label, +// and the null root label. +// If every label character has to be escaped as a four-byte escape sequence, the maximum textual +// ascii display of this is 63*4 + 63*4 + 63*4 + 61*4 = 1000 label characters, +// plus four dots and the null at the end of the C string = 1005 +mDNSexport char *ConvertDomainNameToCString_withescape(const domainname *const name, char *ptr, char esc) + { + const mDNSu8 *src = name->c; // Domain name we're reading + const mDNSu8 *const max = name->c + MAX_DOMAIN_NAME; // Maximum that's valid + + if (*src == 0) *ptr++ = '.'; // Special case: For root, just write a dot + + while (*src) // While more characters in the domain name + { + if (src + 1 + *src >= max) return(mDNSNULL); + ptr = ConvertDomainLabelToCString_withescape((const domainlabel *)src, ptr, esc); + if (!ptr) return(mDNSNULL); + src += 1 + *src; + *ptr++ = '.'; // Write the dot after the label + } + + *ptr++ = 0; // Null-terminate the string + return(ptr); // and return + } + +// RFC 1034 rules: +// Host names must start with a letter, end with a letter or digit, +// and have as interior characters only letters, digits, and hyphen. +// This was subsequently modified in RFC 1123 to allow the first character to be either a letter or a digit +#define mdnsValidHostChar(X, notfirst, notlast) (mdnsIsLetter(X) || mdnsIsDigit(X) || ((notfirst) && (notlast) && (X) == '-') ) + +mDNSexport void ConvertUTF8PstringToRFC1034HostLabel(const mDNSu8 UTF8Name[], domainlabel *const hostlabel) + { + const mDNSu8 * src = &UTF8Name[1]; + const mDNSu8 *const end = &UTF8Name[1] + UTF8Name[0]; + mDNSu8 * ptr = &hostlabel->c[1]; + const mDNSu8 *const lim = &hostlabel->c[1] + MAX_DOMAIN_LABEL; + while (src < end) + { + // Delete apostrophes from source name + if (src[0] == '\'') { src++; continue; } // Standard straight single quote + if (src + 2 < end && src[0] == 0xE2 && src[1] == 0x80 && src[2] == 0x99) + { src += 3; continue; } // Unicode curly apostrophe + if (ptr < lim) + { + if (mdnsValidHostChar(*src, (ptr > &hostlabel->c[1]), (src < end-1))) *ptr++ = *src; + else if (ptr > &hostlabel->c[1] && ptr[-1] != '-') *ptr++ = '-'; + } + src++; + } + while (ptr > &hostlabel->c[1] && ptr[-1] == '-') ptr--; // Truncate trailing '-' marks + hostlabel->c[0] = (mDNSu8)(ptr - &hostlabel->c[1]); + } + +mDNSexport mDNSu8 *ConstructServiceName(domainname *const fqdn, + const domainlabel *name, const domainname *type, const domainname *const domain) + { + int i, len; + mDNSu8 *dst = fqdn->c; + const mDNSu8 *src; + const char *errormsg; + + // In the case where there is no name (and ONLY in that case), + // a single-label subtype is allowed as the first label of a three-part "type" + if (!name) + { + const mDNSu8 *s2 = type->c + 1 + type->c[0]; + if (type->c[0] > 0 && type->c[0] < 0x40 && + s2[0] > 0 && s2[0] < 0x40 && + s2[1+s2[0]] > 0 && s2[1+s2[0]] < 0x40) + { + name = (domainlabel *)type; + type = (domainname *)s2; + } + } + + if (name && name->c[0]) + { + src = name->c; // Put the service name into the domain name + len = *src; + if (len >= 0x40) { errormsg="Service instance name too long"; goto fail; } + for (i=0; i<=len; i++) *dst++ = *src++; + } + else + name = (domainlabel*)""; // Set this up to be non-null, to avoid errors if we have to call LogMsg() below + + src = type->c; // Put the service type into the domain name + len = *src; + if (len < 2 || len >= 0x40) { errormsg="Invalid service application protocol name"; goto fail; } + if (src[1] != '_') { errormsg="Service application protocol name must begin with underscore"; goto fail; } + for (i=2; i<=len; i++) + if (!mdnsIsLetter(src[i]) && !mdnsIsDigit(src[i]) && src[i] != '-' && src[i] != '_') + { errormsg="Service application protocol name must contain only letters, digits, and hyphens"; goto fail; } + for (i=0; i<=len; i++) *dst++ = *src++; + + len = *src; + //if (len == 0 || len >= 0x40) { errormsg="Invalid service transport protocol name"; goto fail; } + if (!(len == 4 && src[1] == '_' && + (((src[2] | 0x20) == 'u' && (src[3] | 0x20) == 'd') || ((src[2] | 0x20) == 't' && (src[3] | 0x20) == 'c')) && + (src[4] | 0x20) == 'p')) + { errormsg="Service transport protocol name must be _udp or _tcp"; goto fail; } + for (i=0; i<=len; i++) *dst++ = *src++; + + if (*src) { errormsg="Service type must have only two labels"; goto fail; } + + *dst = 0; + dst = AppendDomainName(fqdn, domain); + if (!dst) { errormsg="Service domain too long"; goto fail; } + return(dst); + +fail: + LogMsg("ConstructServiceName: %s: %#s.%##s%##s", errormsg, name->c, type->c, domain->c); + return(mDNSNULL); + } + +mDNSexport mDNSBool DeconstructServiceName(const domainname *const fqdn, + domainlabel *const name, domainname *const type, domainname *const domain) + { + int i, len; + const mDNSu8 *src = fqdn->c; + const mDNSu8 *max = fqdn->c + MAX_DOMAIN_NAME; + mDNSu8 *dst; + + dst = name->c; // Extract the service name from the domain name + len = *src; + if (len >= 0x40) { debugf("DeconstructServiceName: service name too long"); return(mDNSfalse); } + for (i=0; i<=len; i++) *dst++ = *src++; + + dst = type->c; // Extract the service type from the domain name + len = *src; + if (len >= 0x40) { debugf("DeconstructServiceName: service type too long"); return(mDNSfalse); } + for (i=0; i<=len; i++) *dst++ = *src++; + + len = *src; + if (len >= 0x40) { debugf("DeconstructServiceName: service type too long"); return(mDNSfalse); } + for (i=0; i<=len; i++) *dst++ = *src++; + *dst++ = 0; // Put the null root label on the end of the service type + + dst = domain->c; // Extract the service domain from the domain name + while (*src) + { + len = *src; + if (len >= 0x40) + { debugf("DeconstructServiceName: service domain label too long"); return(mDNSfalse); } + if (src + 1 + len + 1 >= max) + { debugf("DeconstructServiceName: service domain too long"); return(mDNSfalse); } + for (i=0; i<=len; i++) *dst++ = *src++; + } + *dst++ = 0; // Put the null root label on the end + + return(mDNStrue); + } + +// Returns true if a rich text label ends in " (nnn)", or if an RFC 1034 +// name ends in "-nnn", where n is some decimal number. +mDNSlocal mDNSBool LabelContainsSuffix(const domainlabel *const name, const mDNSBool RichText) + { + mDNSu16 l = name->c[0]; + + if (RichText) + { + if (l < 4) return mDNSfalse; // Need at least " (2)" + if (name->c[l--] != ')') return mDNSfalse; // Last char must be ')' + if (!mdnsIsDigit(name->c[l])) return mDNSfalse; // Preceeded by a digit + l--; + while (l > 2 && mdnsIsDigit(name->c[l])) l--; // Strip off digits + return (name->c[l] == '(' && name->c[l - 1] == ' '); + } + else + { + if (l < 2) return mDNSfalse; // Need at least "-2" + if (!mdnsIsDigit(name->c[l])) return mDNSfalse; // Last char must be a digit + l--; + while (l > 2 && mdnsIsDigit(name->c[l])) l--; // Strip off digits + return (name->c[l] == '-'); + } + } + +// removes an auto-generated suffix (appended on a name collision) from a label. caller is +// responsible for ensuring that the label does indeed contain a suffix. returns the number +// from the suffix that was removed. +mDNSlocal mDNSu32 RemoveLabelSuffix(domainlabel *name, mDNSBool RichText) + { + mDNSu32 val = 0, multiplier = 1; + + // Chop closing parentheses from RichText suffix + if (RichText && name->c[0] >= 1 && name->c[name->c[0]] == ')') name->c[0]--; + + // Get any existing numerical suffix off the name + while (mdnsIsDigit(name->c[name->c[0]])) + { val += (name->c[name->c[0]] - '0') * multiplier; multiplier *= 10; name->c[0]--; } + + // Chop opening parentheses or dash from suffix + if (RichText) + { + if (name->c[0] >= 2 && name->c[name->c[0]] == '(' && name->c[name->c[0]-1] == ' ') name->c[0] -= 2; + } + else + { + if (name->c[0] >= 1 && name->c[name->c[0]] == '-') name->c[0] -= 1; + } + + return(val); + } + +// appends a numerical suffix to a label, with the number following a whitespace and enclosed +// in parentheses (rich text) or following two consecutive hyphens (RFC 1034 domain label). +mDNSlocal void AppendLabelSuffix(domainlabel *name, mDNSu32 val, mDNSBool RichText) + { + mDNSu32 divisor = 1, chars = 2; // Shortest possible RFC1034 name suffix is 3 characters ("-2") + if (RichText) chars = 4; // Shortest possible RichText suffix is 4 characters (" (2)") + + // Truncate trailing spaces from RichText names + if (RichText) while (name->c[name->c[0]] == ' ') name->c[0]--; + + while (val >= divisor * 10) { divisor *= 10; chars++; } + + if (name->c[0] > (mDNSu8)(MAX_DOMAIN_LABEL - chars)) + { + name->c[0] = (mDNSu8)(MAX_DOMAIN_LABEL - chars); + // If the following character is a UTF-8 continuation character, + // we just chopped a multi-byte UTF-8 character in the middle, so strip back to a safe truncation point + while (name->c[0] > 0 && (name->c[name->c[0]+1] & 0xC0) == 0x80) name->c[0]--; + } + + if (RichText) { name->c[++name->c[0]] = ' '; name->c[++name->c[0]] = '('; } + else { name->c[++name->c[0]] = '-'; } + + while (divisor) + { + name->c[++name->c[0]] = (mDNSu8)('0' + val / divisor); + val %= divisor; + divisor /= 10; + } + + if (RichText) name->c[++name->c[0]] = ')'; + } + +mDNSexport void IncrementLabelSuffix(domainlabel *name, mDNSBool RichText) + { + mDNSu32 val = 0; + + if (LabelContainsSuffix(name, RichText)) + val = RemoveLabelSuffix(name, RichText); + + // If no existing suffix, start by renaming "Foo" as "Foo (2)" or "Foo-2" as appropriate. + // If existing suffix in the range 2-9, increment it. + // If we've had ten conflicts already, there are probably too many hosts trying to use the same name, + // so add a random increment to improve the chances of finding an available name next time. + if (val == 0) val = 2; + else if (val < 10) val++; + else val += 1 + mDNSRandom(99); + + AppendLabelSuffix(name, val, RichText); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Resource Record Utility Functions +#endif + +#define RRIsAddressType(RR) ((RR)->resrec.rrtype == kDNSType_A || (RR)->resrec.rrtype == kDNSType_AAAA) +#define RRTypeIsAddressType(T) ((T) == kDNSType_A || (T) == kDNSType_AAAA) + +#define ResourceRecordIsValidAnswer(RR) ( ((RR)-> resrec.RecordType & kDNSRecordTypeActiveMask) && \ + ((RR)->Additional1 == mDNSNULL || ((RR)->Additional1->resrec.RecordType & kDNSRecordTypeActiveMask)) && \ + ((RR)->Additional2 == mDNSNULL || ((RR)->Additional2->resrec.RecordType & kDNSRecordTypeActiveMask)) && \ + ((RR)->DependentOn == mDNSNULL || ((RR)->DependentOn->resrec.RecordType & kDNSRecordTypeActiveMask)) ) + +#define ResourceRecordIsValidInterfaceAnswer(RR, INTID) \ + (ResourceRecordIsValidAnswer(RR) && \ + ((RR)->resrec.InterfaceID == mDNSInterface_Any || (RR)->resrec.InterfaceID == (INTID))) + +#define RRUniqueOrKnownUnique(RR) ((RR)->RecordType & (kDNSRecordTypeUnique | kDNSRecordTypeKnownUnique)) + +#define DefaultProbeCountForTypeUnique ((mDNSu8)3) +#define DefaultProbeCountForRecordType(X) ((X) == kDNSRecordTypeUnique ? DefaultProbeCountForTypeUnique : (mDNSu8)0) + +// For records that have *never* been announced on the wire, their AnnounceCount will be set to InitialAnnounceCount (10). +// When de-registering these records we do not need to send any goodbye packet because we never announced them in the first +// place. If AnnounceCount is less than InitialAnnounceCount that means we have announced them at least once, so a goodbye +// packet is needed. For this reason, if we ever reset AnnounceCount (e.g. after an interface change) we set it to +// ReannounceCount (9), not InitialAnnounceCount. If we were to reset AnnounceCount back to InitialAnnounceCount that would +// imply that the record had never been announced on the wire (which is false) and if the client were then to immediately +// deregister that record before it had a chance to announce, we'd fail to send its goodbye packet (which would be a bug). +#define InitialAnnounceCount ((mDNSu8)10) +#define ReannounceCount ((mDNSu8)9) + +// Note that the announce intervals use exponential backoff, doubling each time. The probe intervals do not. +// This means that because the announce interval is doubled after sending the first packet, the first +// observed on-the-wire inter-packet interval between announcements is actually one second. +// The half-second value here may be thought of as a conceptual (non-existent) half-second delay *before* the first packet is sent. +#define DefaultProbeIntervalForTypeUnique (mDNSPlatformOneSecond/4) +#define DefaultAnnounceIntervalForTypeShared (mDNSPlatformOneSecond/2) +#define DefaultAnnounceIntervalForTypeUnique (mDNSPlatformOneSecond/2) + +#define DefaultAPIntervalForRecordType(X) ((X) & (kDNSRecordTypeAdvisory | kDNSRecordTypeShared ) ? DefaultAnnounceIntervalForTypeShared : \ + (X) & (kDNSRecordTypeUnique ) ? DefaultProbeIntervalForTypeUnique : \ + (X) & (kDNSRecordTypeVerified | kDNSRecordTypeKnownUnique) ? DefaultAnnounceIntervalForTypeUnique : 0) + +#define TimeToAnnounceThisRecord(RR,time) ((RR)->AnnounceCount && (time) - ((RR)->LastAPTime + (RR)->ThisAPInterval) >= 0) +#define TimeToSendThisRecord(RR,time) ((TimeToAnnounceThisRecord(RR,time) || (RR)->ImmedAnswer) && ResourceRecordIsValidAnswer(RR)) +#define TicksTTL(RR) ((mDNSs32)(RR)->resrec.rroriginalttl * mDNSPlatformOneSecond) +#define RRExpireTime(RR) ((RR)->TimeRcvd + TicksTTL(RR)) + +#define MaxUnansweredQueries 4 + +mDNSlocal mDNSBool SameRData(const ResourceRecord *const r1, const ResourceRecord *const r2) + { + if (r1->rrtype != r2->rrtype) return(mDNSfalse); + if (r1->rdlength != r2->rdlength) return(mDNSfalse); + if (r1->rdatahash != r2->rdatahash) return(mDNSfalse); + if (r1->rdnamehash != r2->rdnamehash) return(mDNSfalse); + switch(r1->rrtype) + { + case kDNSType_CNAME:// Same as PTR + case kDNSType_PTR: return(SameDomainName(&r1->rdata->u.name, &r2->rdata->u.name)); + + case kDNSType_SRV: return(mDNSBool)( r1->rdata->u.srv.priority == r2->rdata->u.srv.priority && + r1->rdata->u.srv.weight == r2->rdata->u.srv.weight && + r1->rdata->u.srv.port.NotAnInteger == r2->rdata->u.srv.port.NotAnInteger && + SameDomainName(&r1->rdata->u.srv.target, &r2->rdata->u.srv.target) ); + + default: return(mDNSPlatformMemSame(r1->rdata->u.data, r2->rdata->u.data, r1->rdlength)); + } + } + +mDNSlocal mDNSBool ResourceRecordAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q) + { + if (rr->InterfaceID && + q ->InterfaceID && + rr->InterfaceID != q->InterfaceID) return(mDNSfalse); + + // RR type CNAME matches any query type. QTYPE ANY matches any RR type. QCLASS ANY matches any RR class. + if (rr->rrtype != kDNSType_CNAME && rr->rrtype != q->qtype && q->qtype != kDNSQType_ANY ) return(mDNSfalse); + if ( rr->rrclass != q->qclass && q->qclass != kDNSQClass_ANY) return(mDNSfalse); + return(rr->namehash == q->qnamehash && SameDomainName(&rr->name, &q->qname)); + } + +mDNSlocal mDNSu32 DomainNameHashValue(const domainname *const name) + { + mDNSu32 sum = 0; + const mDNSu8 *c; + + for (c = name->c; c[0] != 0 && c[1] != 0; c += 2) + { + sum += ((mDNSIsUpperCase(c[0]) ? c[0] + 'a' - 'A' : c[0]) << 8) | + (mDNSIsUpperCase(c[1]) ? c[1] + 'a' - 'A' : c[1]); + sum = (sum<<3) | (sum>>29); + } + if (c[0]) sum += ((mDNSIsUpperCase(c[0]) ? c[0] + 'a' - 'A' : c[0]) << 8); + return(sum); + } + +#define HashSlot(X) (DomainNameHashValue(X) % CACHE_HASH_SLOTS) + +mDNSlocal mDNSu32 RDataHashValue(mDNSu16 const rdlength, const RDataBody *const rdb) + { + mDNSu32 sum = 0; + int i; + for (i=0; i+1 < rdlength; i+=2) + { + sum += (((mDNSu32)(rdb->data[i])) << 8) | rdb->data[i+1]; + sum = (sum<<3) | (sum>>29); + } + if (i < rdlength) + { + sum += ((mDNSu32)(rdb->data[i])) << 8; + } + return(sum); + } + +// SameResourceRecordSignature returns true if two resources records have the same name, type, and class, and may be sent +// (or were received) on the same interface (i.e. if *both* records specify an interface, then it has to match). +// TTL and rdata may differ. +// This is used for cache flush management: +// When sending a unique record, all other records matching "SameResourceRecordSignature" must also be sent +// When receiving a unique record, all old cache records matching "SameResourceRecordSignature" are flushed +mDNSlocal mDNSBool SameResourceRecordSignature(const ResourceRecord *const r1, const ResourceRecord *const r2) + { + if (!r1) { LogMsg("SameResourceRecordSignature ERROR: r1 is NULL"); return(mDNSfalse); } + if (!r2) { LogMsg("SameResourceRecordSignature ERROR: r2 is NULL"); return(mDNSfalse); } + if (r1->InterfaceID && + r2->InterfaceID && + r1->InterfaceID != r2->InterfaceID) return(mDNSfalse); + return(mDNSBool)(r1->rrtype == r2->rrtype && r1->rrclass == r2->rrclass && r1->namehash == r2->namehash && SameDomainName(&r1->name, &r2->name)); + } + +// PacketRRMatchesSignature behaves as SameResourceRecordSignature, except that types may differ if the +// authoratative record is in the probing state. Probes are sent with the wildcard type, so a response of +// any type should match, even if it is not the type the client plans to use. +mDNSlocal mDNSBool PacketRRMatchesSignature(const CacheRecord *const pktrr, const AuthRecord *const authrr) + { + if (!pktrr) { LogMsg("PacketRRMatchesSignature ERROR: pktrr is NULL"); return(mDNSfalse); } + if (!authrr) { LogMsg("PacketRRMatchesSignature ERROR: authrr is NULL"); return(mDNSfalse); } + if (pktrr->resrec.InterfaceID && + authrr->resrec.InterfaceID && + pktrr->resrec.InterfaceID != authrr->resrec.InterfaceID) return(mDNSfalse); + if (authrr->resrec.RecordType != kDNSRecordTypeUnique && pktrr->resrec.rrtype != authrr->resrec.rrtype) return(mDNSfalse); + return(mDNSBool)(pktrr->resrec.rrclass == authrr->resrec.rrclass && pktrr->resrec.namehash == authrr->resrec.namehash && SameDomainName(&pktrr->resrec.name, &authrr->resrec.name)); + } + +// IdenticalResourceRecord returns true if two resources records have +// the same name, type, class, and identical rdata (InterfaceID and TTL may differ) +mDNSlocal mDNSBool IdenticalResourceRecord(const ResourceRecord *const r1, const ResourceRecord *const r2) + { + if (!r1) { LogMsg("IdenticalResourceRecord ERROR: r1 is NULL"); return(mDNSfalse); } + if (!r2) { LogMsg("IdenticalResourceRecord ERROR: r2 is NULL"); return(mDNSfalse); } + if (r1->rrtype != r2->rrtype || r1->rrclass != r2->rrclass || r1->namehash != r2->namehash || !SameDomainName(&r1->name, &r2->name)) return(mDNSfalse); + return(SameRData(r1, r2)); + } + +// CacheRecord *ks is the CacheRecord from the known answer list in the query. +// This is the information that the requester believes to be correct. +// AuthRecord *rr is the answer we are proposing to give, if not suppressed. +// This is the information that we believe to be correct. +// We've already determined that we plan to give this answer on this interface +// (either the record is non-specific, or it is specific to this interface) +// so now we just need to check the name, type, class, rdata and TTL. +mDNSlocal mDNSBool ShouldSuppressKnownAnswer(const CacheRecord *const ka, const AuthRecord *const rr) + { + // If RR signature is different, or data is different, then don't suppress our answer + if (!IdenticalResourceRecord(&ka->resrec,&rr->resrec)) return(mDNSfalse); + + // If the requester's indicated TTL is less than half the real TTL, + // we need to give our answer before the requester's copy expires. + // If the requester's indicated TTL is at least half the real TTL, + // then we can suppress our answer this time. + // If the requester's indicated TTL is greater than the TTL we believe, + // then that's okay, and we don't need to do anything about it. + // (If two responders on the network are offering the same information, + // that's okay, and if they are offering the information with different TTLs, + // the one offering the lower TTL should defer to the one offering the higher TTL.) + return(mDNSBool)(ka->resrec.rroriginalttl >= rr->resrec.rroriginalttl / 2); + } + +mDNSlocal mDNSu16 GetRDLength(const ResourceRecord *const rr, mDNSBool estimate) + { + RDataBody *rd = &rr->rdata->u; + const domainname *const name = estimate ? &rr->name : mDNSNULL; + switch (rr->rrtype) + { + case kDNSType_A: return(sizeof(rd->ip)); break; + case kDNSType_CNAME:// Same as PTR + case kDNSType_PTR: return(CompressedDomainNameLength(&rd->name, name)); + case kDNSType_HINFO:return(mDNSu16)(2 + (int)rd->data[0] + (int)rd->data[1 + (int)rd->data[0]]); + case kDNSType_NULL: // Same as TXT -- not self-describing, so have to just trust rdlength + case kDNSType_TXT: return(rr->rdlength); // TXT is not self-describing, so have to just trust rdlength + case kDNSType_AAAA: return(sizeof(rd->ipv6)); break; + case kDNSType_SRV: return(mDNSu16)(6 + CompressedDomainNameLength(&rd->srv.target, name)); + default: debugf("Warning! Don't know how to get length of resource type %d", rr->rrtype); + return(rr->rdlength); + } + } + +mDNSlocal void SetNextAnnounceProbeTime(mDNS *const m, const AuthRecord *const rr) + { + if (rr->resrec.RecordType == kDNSRecordTypeUnique) + { + if (m->NextScheduledProbe - (rr->LastAPTime + rr->ThisAPInterval) >= 0) + m->NextScheduledProbe = (rr->LastAPTime + rr->ThisAPInterval); + } + else if (rr->AnnounceCount && ResourceRecordIsValidAnswer(rr)) + { + if (m->NextScheduledResponse - (rr->LastAPTime + rr->ThisAPInterval) >= 0) + m->NextScheduledResponse = (rr->LastAPTime + rr->ThisAPInterval); + } + } + +#define GetRRDomainNameTarget(RR) ( \ + ((RR)->rrtype == kDNSType_CNAME || (RR)->rrtype == kDNSType_PTR) ? &(RR)->rdata->u.name : \ + ((RR)->rrtype == kDNSType_SRV ) ? &(RR)->rdata->u.srv.target : mDNSNULL ) + +mDNSlocal void InitializeLastAPTime(mDNS *const m, AuthRecord *const rr) + { + // To allow us to aggregate probes when a group of services are registered together, + // the first probe is delayed 1/4 second. This means the common-case behaviour is: + // 1/4 second wait; probe + // 1/4 second wait; probe + // 1/4 second wait; probe + // 1/4 second wait; announce (i.e. service is normally announced exactly one second after being registered) + + // If we have no probe suppression time set, or it is in the past, set it now + if (m->SuppressProbes == 0 || m->SuppressProbes - m->timenow < 0) + { + m->SuppressProbes = (m->timenow + DefaultProbeIntervalForTypeUnique) | 1; + // If we already have a probe scheduled to go out sooner, then use that time to get better aggregation + if (m->SuppressProbes - m->NextScheduledProbe >= 0) + m->SuppressProbes = m->NextScheduledProbe; + // If we already have a query scheduled to go out sooner, then use that time to get better aggregation + if (m->SuppressProbes - m->NextScheduledQuery >= 0) + m->SuppressProbes = m->NextScheduledQuery; + } + + // We announce to flush stale data from other caches. It is a reasonable assumption that any + // old stale copies will probably have the same TTL we're using, so announcing longer than + // this serves no purpose -- any stale copies of that record will have expired by then anyway. + rr->AnnounceUntil = m->timenow + TicksTTL(rr); + rr->LastAPTime = m->SuppressProbes - rr->ThisAPInterval; + // Set LastMCTime to now, to inhibit multicast responses + // (no need to send additional multicast responses when we're announcing anyway) + rr->LastMCTime = m->timenow; + rr->LastMCInterface = mDNSInterfaceMark; + + // If this is a record type that's not going to probe, then delay its first announcement so that + // it will go out synchronized with the first announcement for the other records that *are* probing. + // This is a minor performance tweak that helps keep groups of related records synchronized together. + // The addition of "rr->ThisAPInterval / 2" is to make sure that, in the event that any of the probes are + // delayed by a few milliseconds, this announcement does not inadvertently go out *before* the probing is complete. + // When the probing is complete and those records begin to announce, these records will also be picked up and accelerated, + // because they will meet the criterion of being at least half-way to their scheduled announcement time. + if (rr->resrec.RecordType != kDNSRecordTypeUnique) + rr->LastAPTime += DefaultProbeIntervalForTypeUnique * DefaultProbeCountForTypeUnique + rr->ThisAPInterval / 2; + + SetNextAnnounceProbeTime(m, rr); + } + +mDNSlocal void SetNewRData(ResourceRecord *const rr, RData *NewRData, mDNSu16 rdlength) + { + domainname *target; + if (NewRData) + { + rr->rdata = NewRData; + rr->rdlength = rdlength; + } + // Must not try to get target pointer until after updating rr->rdata + target = GetRRDomainNameTarget(rr); + rr->rdlength = GetRDLength(rr, mDNSfalse); + rr->rdestimate = GetRDLength(rr, mDNStrue); + rr->rdatahash = RDataHashValue(rr->rdlength, &rr->rdata->u); + rr->rdnamehash = target ? DomainNameHashValue(target) : 0; + } + +mDNSlocal void SetTargetToHostName(mDNS *const m, AuthRecord *const rr) + { + domainname *target = GetRRDomainNameTarget(&rr->resrec); + + if (!target) debugf("SetTargetToHostName: Don't know how to set the target of rrtype %d", rr->resrec.rrtype); + + if (target && SameDomainName(target, &m->hostname)) + debugf("SetTargetToHostName: Target of %##s is already %##s", rr->resrec.name.c, target->c); + + if (target && !SameDomainName(target, &m->hostname)) + { + AssignDomainName(*target, m->hostname); + SetNewRData(&rr->resrec, mDNSNULL, 0); + + // If we're in the middle of probing this record, we need to start again, + // because changing its rdata may change the outcome of the tie-breaker. + // (If the record type is kDNSRecordTypeUnique (unconfirmed unique) then DefaultProbeCountForRecordType is non-zero.) + rr->ProbeCount = DefaultProbeCountForRecordType(rr->resrec.RecordType); + + // If we've announced this record, we really should send a goodbye packet for the old rdata before + // changing to the new rdata. However, in practice, we only do SetTargetToHostName for unique records, + // so when we announce them we'll set the kDNSClass_UniqueRRSet and clear any stale data that way. + if (rr->AnnounceCount < InitialAnnounceCount && rr->resrec.RecordType == kDNSRecordTypeShared) + debugf("Have announced shared record %##s (%s) at least once: should have sent a goodbye packet before updating", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + + if (rr->AnnounceCount < ReannounceCount) + rr->AnnounceCount = ReannounceCount; + rr->ThisAPInterval = DefaultAPIntervalForRecordType(rr->resrec.RecordType); + InitializeLastAPTime(m,rr); + } + } + +mDNSlocal void CompleteProbing(mDNS *const m, AuthRecord *const rr) + { + verbosedebugf("Probing for %##s (%s) complete", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + if (!rr->Acknowledged && rr->RecordCallback) + { + // CAUTION: MUST NOT do anything more with rr after calling rr->Callback(), because the client's callback function + // is allowed to do anything, including starting/stopping queries, registering/deregistering records, etc. + rr->Acknowledged = mDNStrue; + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + rr->RecordCallback(m, rr, mStatus_NoError); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + } + } + +#define ValidateDomainName(N) (DomainNameLength(N) <= MAX_DOMAIN_NAME) + +mDNSlocal mDNSBool ValidateRData(const mDNSu16 rrtype, const mDNSu16 rdlength, const RData *const rd) + { + mDNSu16 len; + switch(rrtype) + { + case kDNSType_A: return(rdlength == sizeof(mDNSv4Addr)); + + case kDNSType_NS: // Same as PTR + case kDNSType_MD: // Same as PTR + case kDNSType_MF: // Same as PTR + case kDNSType_CNAME:// Same as PTR + //case kDNSType_SOA not checked + case kDNSType_MB: // Same as PTR + case kDNSType_MG: // Same as PTR + case kDNSType_MR: // Same as PTR + //case kDNSType_NULL not checked (no specified format, so always valid) + //case kDNSType_WKS not checked + case kDNSType_PTR: len = DomainNameLength(&rd->u.name); + return(len <= MAX_DOMAIN_NAME && rdlength == len); + + case kDNSType_HINFO:// Same as TXT (roughly) + case kDNSType_MINFO:// Same as TXT (roughly) + case kDNSType_TXT: { + const mDNSu8 *ptr = rd->u.txt.c; + const mDNSu8 *end = rd->u.txt.c + rdlength; + while (ptr < end) ptr += 1 + ptr[0]; + return (ptr == end); + } + + case kDNSType_AAAA: return(rdlength == sizeof(mDNSv6Addr)); + + case kDNSType_MX: len = DomainNameLength(&rd->u.mx.exchange); + return(len <= MAX_DOMAIN_NAME && rdlength == 2+len); + + case kDNSType_SRV: len = DomainNameLength(&rd->u.srv.target); + return(len <= MAX_DOMAIN_NAME && rdlength == 6+len); + + default: return(mDNStrue); // Allow all other types without checking + } + } + +// Two records qualify to be local duplicates if the RecordTypes are the same, or if one is Unique and the other Verified +#define RecordLDT(A,B) ((A)->resrec.RecordType == (B)->resrec.RecordType || ((A)->resrec.RecordType | (B)->resrec.RecordType) == (kDNSRecordTypeUnique | kDNSRecordTypeVerified)) +#define RecordIsLocalDuplicate(A,B) ((A)->resrec.InterfaceID == (B)->resrec.InterfaceID && RecordLDT((A),(B)) && IdenticalResourceRecord(&(A)->resrec, &(B)->resrec)) + +mDNSlocal mStatus mDNS_Register_internal(mDNS *const m, AuthRecord *const rr) + { + domainname *target = GetRRDomainNameTarget(&rr->resrec); + AuthRecord *r; + AuthRecord **p = &m->ResourceRecords; + AuthRecord **d = &m->DuplicateRecords; + AuthRecord **l = &m->LocalOnlyRecords; + +#if TEST_LOCALONLY_FOR_EVERYTHING + rr->resrec.InterfaceID = (mDNSInterfaceID)~0; +#endif + + while (*p && *p != rr) p=&(*p)->next; + while (*d && *d != rr) d=&(*d)->next; + while (*l && *l != rr) l=&(*l)->next; + if (*d || *p || *l) + { + LogMsg("Error! Tried to register a AuthRecord %p %##s (%s) that's already in the list", rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + return(mStatus_AlreadyRegistered); + } + + if (rr->DependentOn) + { + if (rr->resrec.RecordType == kDNSRecordTypeUnique) + rr->resrec.RecordType = kDNSRecordTypeVerified; + else + { + LogMsg("mDNS_Register_internal: ERROR! %##s (%s): rr->DependentOn && RecordType != kDNSRecordTypeUnique", + rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + return(mStatus_Invalid); + } + if (!(rr->DependentOn->resrec.RecordType & (kDNSRecordTypeUnique | kDNSRecordTypeVerified))) + { + LogMsg("mDNS_Register_internal: ERROR! %##s (%s): rr->DependentOn->RecordType bad type %X", + rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), rr->DependentOn->resrec.RecordType); + return(mStatus_Invalid); + } + } + + // If this resource record is referencing a specific interface, make sure it exists + if (rr->resrec.InterfaceID && rr->resrec.InterfaceID != ((mDNSInterfaceID)~0)) + { + NetworkInterfaceInfo *intf; + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->InterfaceID == rr->resrec.InterfaceID) break; + if (!intf) + { + debugf("mDNS_Register_internal: Bogus InterfaceID %p in resource record", rr->resrec.InterfaceID); + return(mStatus_BadReferenceErr); + } + } + + rr->next = mDNSNULL; + + // Field Group 1: Persistent metadata for Authoritative Records +// rr->Additional1 = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client +// rr->Additional2 = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client +// rr->DependentOn = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client +// rr->RRSet = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client +// rr->Callback = already set in mDNS_SetupResourceRecord +// rr->Context = already set in mDNS_SetupResourceRecord +// rr->RecordType = already set in mDNS_SetupResourceRecord +// rr->HostTarget = set to mDNSfalse in mDNS_SetupResourceRecord; may be overridden by client + + // Field Group 2: Transient state for Authoritative Records + rr->Acknowledged = mDNSfalse; + rr->ProbeCount = DefaultProbeCountForRecordType(rr->resrec.RecordType); + rr->AnnounceCount = InitialAnnounceCount; + rr->IncludeInProbe = mDNSfalse; + rr->ImmedAnswer = mDNSNULL; + rr->ImmedAdditional = mDNSNULL; + rr->SendRNow = mDNSNULL; + rr->v4Requester = zeroIPAddr; + rr->v6Requester = zerov6Addr; + rr->NextResponse = mDNSNULL; + rr->NR_AnswerTo = mDNSNULL; + rr->NR_AdditionalTo = mDNSNULL; + rr->ThisAPInterval = DefaultAPIntervalForRecordType(rr->resrec.RecordType); + InitializeLastAPTime(m, rr); +// rr->AnnounceUntil = Set for us in InitializeLastAPTime() +// rr->LastAPTime = Set for us in InitializeLastAPTime() +// rr->LastMCTime = Set for us in InitializeLastAPTime() +// rr->LastMCInterface = Set for us in InitializeLastAPTime() + rr->NewRData = mDNSNULL; + rr->newrdlength = 0; + rr->UpdateCallback = mDNSNULL; + rr->UpdateCredits = kMaxUpdateCredits; + rr->NextUpdateCredit = 0; + rr->UpdateBlocked = 0; + +// rr->resrec.interface = already set in mDNS_SetupResourceRecord +// rr->resrec.name.c = MUST be set by client +// rr->resrec.rrtype = already set in mDNS_SetupResourceRecord +// rr->resrec.rrclass = already set in mDNS_SetupResourceRecord +// rr->resrec.rroriginalttl = already set in mDNS_SetupResourceRecord +// rr->resrec.rdata = MUST be set by client, unless record type is CNAME or PTR and rr->HostTarget is set + + if (rr->HostTarget) + { + if (target) target->c[0] = 0; + SetTargetToHostName(m, rr); // This also sets rdlength and rdestimate for us + } + else + { + rr->resrec.rdlength = GetRDLength(&rr->resrec, mDNSfalse); + rr->resrec.rdestimate = GetRDLength(&rr->resrec, mDNStrue); + } + + if (!ValidateDomainName(&rr->resrec.name)) + { LogMsg("Attempt to register record with invalid name: %s", 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; + + if (rr->resrec.InterfaceID == ((mDNSInterfaceID)~0)) + { + debugf("Adding %p %##s (%s) to LocalOnly list", rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + *l = rr; + if (!m->NewLocalOnlyRecords) m->NewLocalOnlyRecords = rr; + // If this is supposed to be unique, make sure we don't have any name conflicts + if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) + { + const AuthRecord *s1 = rr->RRSet ? rr->RRSet : rr; + for (r = m->LocalOnlyRecords; r; r=r->next) + { + const AuthRecord *s2 = r->RRSet ? r->RRSet : r; + if (s1 != s2 && SameResourceRecordSignature(&r->resrec, &rr->resrec) && !SameRData(&r->resrec, &rr->resrec)) + break; + } + if (r) // If we found a conflict, set DiscardLocalOnlyRecords so we'll deliver the callback + { + debugf("Name conflict %p %##s (%s)", rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + m->DiscardLocalOnlyRecords = mDNStrue; + } + else // else no conflict, so set ProbeCount to zero and update RecordType as appropriate + { + rr->ProbeCount = 0; + if (rr->resrec.RecordType == kDNSRecordTypeUnique) rr->resrec.RecordType = kDNSRecordTypeVerified; + } + } + } + else + { + // Now that's we've finished building our new record, make sure it's not identical to one we already have + for (r = m->ResourceRecords; r; r=r->next) if (RecordIsLocalDuplicate(r, rr)) break; + + if (r) + { + debugf("Adding %p %##s (%s) to duplicate list", rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + *d = rr; + // If the previous copy of this record is already verified unique, + // then indicate that we should move this record promptly to kDNSRecordTypeUnique state. + // Setting ProbeCount to zero will cause SendQueries() to advance this record to + // kDNSRecordTypeVerified state and call the client callback at the next appropriate time. + if (rr->resrec.RecordType == kDNSRecordTypeUnique && r->resrec.RecordType == kDNSRecordTypeVerified) + rr->ProbeCount = 0; + } + else + { + debugf("Adding %p %##s (%s) to active record list", rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + *p = rr; + } + } + return(mStatus_NoError); + } + +mDNSlocal void RecordProbeFailure(mDNS *const m, const AuthRecord *const rr) + { + m->ProbeFailTime = m->timenow; + m->NumFailedProbes++; + // If we've had ten or more probe failures, rate-limit to one every five seconds + // The result is ORed with 1 to make sure SuppressProbes is not accidentally set to zero + if (m->NumFailedProbes >= 10) m->SuppressProbes = (m->timenow + mDNSPlatformOneSecond * 5) | 1; + if (m->NumFailedProbes >= 16) + LogMsg("Name in use: %##s (%s); need to choose another (%d)", + rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), m->NumFailedProbes); + } + +// mDNS_Dereg_normal is used for most calls to mDNS_Deregister_internal +// mDNS_Dereg_conflict is used to indicate that this record is being forcibly deregistered because of a conflict +// mDNS_Dereg_repeat is used when cleaning up, for records that may have already been forcibly deregistered +typedef enum { mDNS_Dereg_normal, mDNS_Dereg_conflict, mDNS_Dereg_repeat } mDNS_Dereg_type; + +// NOTE: mDNS_Deregister_internal can call a user callback, which may change the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSlocal mStatus mDNS_Deregister_internal(mDNS *const m, AuthRecord *const rr, mDNS_Dereg_type drt) + { + mDNSu8 RecordType = rr->resrec.RecordType; + AuthRecord **p = &m->ResourceRecords; // Find this record in our list of active records + if (rr->resrec.InterfaceID == ((mDNSInterfaceID)~0)) p = &m->LocalOnlyRecords; + while (*p && *p != rr) p=&(*p)->next; + + if (*p) + { + // We found our record on the main list. See if there are any duplicates that need special handling. + if (drt == mDNS_Dereg_conflict) // If this was a conflict, see that all duplicates get the same treatment + { + AuthRecord *r2 = m->DuplicateRecords; + while (r2) + { + if (RecordIsLocalDuplicate(r2, rr)) { mDNS_Deregister_internal(m, r2, drt); r2 = m->DuplicateRecords; } + else r2=r2->next; + } + } + else + { + // Before we delete the record (and potentially send a goodbye packet) + // first see if we have a record on the duplicate list ready to take over from it. + AuthRecord **d = &m->DuplicateRecords; + while (*d && !RecordIsLocalDuplicate(*d, rr)) d=&(*d)->next; + if (*d) + { + AuthRecord *dup = *d; + debugf("Duplicate record %p taking over from %p %##s (%s)", dup, rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + *d = dup->next; // Cut replacement record from DuplicateRecords list + dup->next = rr->next; // And then... + rr->next = dup; // ... splice it in right after the record we're about to delete + dup->resrec.RecordType = rr->resrec.RecordType; + dup->ProbeCount = rr->ProbeCount; + dup->AnnounceCount = rr->AnnounceCount; + dup->ImmedAnswer = rr->ImmedAnswer; + dup->ImmedAdditional = rr->ImmedAdditional; + dup->v4Requester = rr->v4Requester; + dup->v6Requester = rr->v6Requester; + dup->ThisAPInterval = rr->ThisAPInterval; + dup->AnnounceUntil = rr->AnnounceUntil; + dup->LastAPTime = rr->LastAPTime; + dup->LastMCTime = rr->LastMCTime; + dup->LastMCInterface = rr->LastMCInterface; + if (RecordType == kDNSRecordTypeShared) rr->AnnounceCount = InitialAnnounceCount; + } + } + } + else + { + // We didn't find our record on the main list; try the DuplicateRecords list instead. + p = &m->DuplicateRecords; + while (*p && *p != rr) p=&(*p)->next; + // If we found our record on the duplicate list, then make sure we don't send a goodbye for it + if (*p && RecordType == kDNSRecordTypeShared) rr->AnnounceCount = InitialAnnounceCount; + if (*p) debugf("DNS_Deregister_internal: Deleting DuplicateRecord %p %##s (%s)", rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + } + + if (!*p) + { + // No need to log an error message if we already know this is a potentially repeated deregistration + if (drt != mDNS_Dereg_repeat) + debugf("mDNS_Deregister_internal: Record %p %##s (%s) not found in list", rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + return(mStatus_BadReferenceErr); + } + + // If this is a shared record and we've announced it at least once, + // we need to retract that announcement before we delete the record + if (RecordType == kDNSRecordTypeShared && rr->AnnounceCount < InitialAnnounceCount) + { + verbosedebugf("mDNS_Deregister_internal: Sending deregister for %##s (%s)", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + rr->resrec.RecordType = kDNSRecordTypeDeregistering; + rr->resrec.rroriginalttl = 0; + rr->ImmedAnswer = mDNSInterfaceMark; + if (rr->resrec.InterfaceID == ((mDNSInterfaceID)~0)) + m->DiscardLocalOnlyRecords = mDNStrue; + else + { + if (m->NextScheduledResponse - (m->timenow + mDNSPlatformOneSecond/10) >= 0) + m->NextScheduledResponse = (m->timenow + mDNSPlatformOneSecond/10); + } + } + else + { + *p = rr->next; // Cut this record from the list + // If someone is about to look at this, bump the pointer forward + if (m->CurrentRecord == rr) m->CurrentRecord = rr->next; + if (m->NewLocalOnlyRecords == rr) m->NewLocalOnlyRecords = rr->next; + rr->next = mDNSNULL; + + if (RecordType == kDNSRecordTypeUnregistered) + debugf("mDNS_Deregister_internal: Record %##s (%s) already marked kDNSRecordTypeUnregistered", + rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + else if (RecordType == kDNSRecordTypeDeregistering) + debugf("mDNS_Deregister_internal: Record %##s (%s) already marked kDNSRecordTypeDeregistering", + rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + else + { + verbosedebugf("mDNS_Deregister_internal: Deleting record for %##s (%s)", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + rr->resrec.RecordType = kDNSRecordTypeUnregistered; + } + + if ((drt == mDNS_Dereg_conflict || drt == mDNS_Dereg_repeat) && RecordType == kDNSRecordTypeShared) + debugf("mDNS_Deregister_internal: Cannot have a conflict on a shared record! %##s (%s)", + rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + + // If we have an update queued up which never executed, give the client a chance to free that memory + if (rr->NewRData) + { + RData *OldRData = rr->resrec.rdata; + SetNewRData(&rr->resrec, rr->NewRData, rr->newrdlength); // Update our rdata + rr->NewRData = mDNSNULL; // Clear the NewRData pointer ... + if (rr->UpdateCallback) + rr->UpdateCallback(m, rr, OldRData); // ... and let the client know + } + + // CAUTION: MUST NOT do anything more with rr after calling rr->Callback(), because the client's callback function + // is allowed to do anything, including starting/stopping queries, registering/deregistering records, etc. + // In this case the likely client action to the mStatus_MemFree message is to free the memory, + // so any attempt to touch rr after this is likely to lead to a crash. + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + if (RecordType == kDNSRecordTypeShared) + { + if (rr->RecordCallback) + rr->RecordCallback(m, rr, mStatus_MemFree); + } + else if (drt == mDNS_Dereg_conflict) + { + RecordProbeFailure(m, rr); + if (rr->RecordCallback) + rr->RecordCallback(m, rr, mStatus_NameConflict); + } + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + } + return(mStatus_NoError); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - +#pragma mark - DNS Message Creation Functions +#endif + +mDNSlocal void InitializeDNSMessage(DNSMessageHeader *h, mDNSOpaque16 id, mDNSOpaque16 flags) + { + h->id = id; + h->flags = flags; + h->numQuestions = 0; + h->numAnswers = 0; + h->numAuthorities = 0; + h->numAdditionals = 0; + } + +mDNSlocal const mDNSu8 *FindCompressionPointer(const mDNSu8 *const base, const mDNSu8 *const end, const mDNSu8 *const domname) + { + const mDNSu8 *result = end - *domname - 1; + + if (*domname == 0) return(mDNSNULL); // There's no point trying to match just the root label + + // This loop examines each possible starting position in packet, starting end of the packet and working backwards + while (result >= base) + { + // If the length byte and first character of the label match, then check further to see + // if this location in the packet will yield a useful name compression pointer. + if (result[0] == domname[0] && result[1] == domname[1]) + { + const mDNSu8 *name = domname; + const mDNSu8 *targ = result; + while (targ + *name < end) + { + // First see if this label matches + int i; + const mDNSu8 *pointertarget; + for (i=0; i <= *name; i++) if (targ[i] != name[i]) break; + if (i <= *name) break; // If label did not match, bail out + targ += 1 + *name; // Else, did match, so advance target pointer + name += 1 + *name; // and proceed to check next label + if (*name == 0 && *targ == 0) return(result); // If no more labels, we found a match! + if (*name == 0) break; // If no more labels to match, we failed, so bail out + + // The label matched, so now follow the pointer (if appropriate) and then see if the next label matches + if (targ[0] < 0x40) continue; // If length value, continue to check next label + if (targ[0] < 0xC0) break; // If 40-BF, not valid + if (targ+1 >= end) break; // Second byte not present! + pointertarget = base + (((mDNSu16)(targ[0] & 0x3F)) << 8) + targ[1]; + if (targ < pointertarget) break; // Pointertarget must point *backwards* in the packet + if (pointertarget[0] >= 0x40) break; // Pointertarget must point to a valid length byte + targ = pointertarget; + } + } + result--; // We failed to match at this search position, so back up the tentative result pointer and try again + } + return(mDNSNULL); + } + +// Put a string of dot-separated labels as length-prefixed labels +// domainname is a fully-qualified name (i.e. assumed to be ending in a dot, even if it doesn't) +// msg points to the message we're building (pass mDNSNULL if we don't want to use compression pointers) +// end points to the end of the message so far +// ptr points to where we want to put the name +// limit points to one byte past the end of the buffer that we must not overrun +// domainname is the name to put +mDNSlocal mDNSu8 *putDomainNameAsLabels(const DNSMessage *const msg, + mDNSu8 *ptr, const mDNSu8 *const limit, const domainname *const name) + { + const mDNSu8 *const base = (const mDNSu8 *const)msg; + const mDNSu8 * np = name->c; + const mDNSu8 *const max = name->c + MAX_DOMAIN_NAME; // Maximum that's valid + const mDNSu8 * pointer = mDNSNULL; + const mDNSu8 *const searchlimit = ptr; + + while (*np && ptr < limit-1) // While we've got characters in the name, and space to write them in the message... + { + if (*np > MAX_DOMAIN_LABEL) + { LogMsg("Malformed domain name %##s (label more than 63 bytes)", name->c); return(mDNSNULL); } + + // This check correctly allows for the final trailing root label: + // e.g. + // Suppose our domain name is exactly 255 bytes long, including the final trailing root label. + // Suppose np is now at name->c[248], and we're about to write our last non-null label ("local"). + // We know that max will be at name->c[255] + // That means that np + 1 + 5 == max - 1, so we (just) pass the "if" test below, write our + // six bytes, then exit the loop, write the final terminating root label, and the domain + // name we've written is exactly 255 bytes long, exactly at the correct legal limit. + // If the name is one byte longer, then we fail the "if" test below, and correctly bail out. + if (np + 1 + *np >= max) + { LogMsg("Malformed domain name %##s (more than 255 bytes)", name->c); return(mDNSNULL); } + + if (base) pointer = FindCompressionPointer(base, searchlimit, np); + if (pointer) // Use a compression pointer if we can + { + mDNSu16 offset = (mDNSu16)(pointer - base); + *ptr++ = (mDNSu8)(0xC0 | (offset >> 8)); + *ptr++ = (mDNSu8)( offset ); + return(ptr); + } + else // Else copy one label and try again + { + int i; + mDNSu8 len = *np++; + if (ptr + 1 + len >= limit) return(mDNSNULL); + *ptr++ = len; + for (i=0; irrtype) + { + case kDNSType_A: if (rr->rdlength != 4) + { + debugf("putRData: Illegal length %d for kDNSType_A", rr->rdlength); + return(mDNSNULL); + } + if (ptr + 4 > limit) return(mDNSNULL); + *ptr++ = rr->rdata->u.ip.b[0]; + *ptr++ = rr->rdata->u.ip.b[1]; + *ptr++ = rr->rdata->u.ip.b[2]; + *ptr++ = rr->rdata->u.ip.b[3]; + return(ptr); + + case kDNSType_CNAME:// Same as PTR + case kDNSType_PTR: return(putDomainNameAsLabels(msg, ptr, limit, &rr->rdata->u.name)); + + case kDNSType_HINFO:// Same as TXT + case kDNSType_TXT: if (ptr + rr->rdlength > limit) return(mDNSNULL); + mDNSPlatformMemCopy(rr->rdata->u.data, ptr, rr->rdlength); + return(ptr + rr->rdlength); + + case kDNSType_AAAA: if (rr->rdlength != sizeof(rr->rdata->u.ipv6)) + { + debugf("putRData: Illegal length %d for kDNSType_AAAA", rr->rdlength); + return(mDNSNULL); + } + if (ptr + sizeof(rr->rdata->u.ipv6) > limit) return(mDNSNULL); + mDNSPlatformMemCopy(&rr->rdata->u.ipv6, ptr, sizeof(rr->rdata->u.ipv6)); + return(ptr + sizeof(rr->rdata->u.ipv6)); + + case kDNSType_SRV: if (ptr + 6 > limit) return(mDNSNULL); + *ptr++ = (mDNSu8)(rr->rdata->u.srv.priority >> 8); + *ptr++ = (mDNSu8)(rr->rdata->u.srv.priority ); + *ptr++ = (mDNSu8)(rr->rdata->u.srv.weight >> 8); + *ptr++ = (mDNSu8)(rr->rdata->u.srv.weight ); + *ptr++ = rr->rdata->u.srv.port.b[0]; + *ptr++ = rr->rdata->u.srv.port.b[1]; + return(putDomainNameAsLabels(msg, ptr, limit, &rr->rdata->u.srv.target)); + + default: if (ptr + rr->rdlength > limit) return(mDNSNULL); + debugf("putRData: Warning! Writing resource type %d as raw data", rr->rrtype); + mDNSPlatformMemCopy(rr->rdata->u.data, ptr, rr->rdlength); + return(ptr + rr->rdlength); + } + } + +mDNSlocal mDNSu8 *PutResourceRecordTTL(DNSMessage *const msg, mDNSu8 *ptr, mDNSu16 *count, ResourceRecord *rr, mDNSu32 ttl) + { + mDNSu8 *endofrdata; + mDNSu16 actualLength; + const mDNSu8 *limit = msg->data + AbsoluteMaxDNSMessageData; + + // If we have a single large record to put in the packet, then we allow the packet to be up to 9K bytes, + // but in the normal case we try to keep the packets below 1500 to avoid IP fragmentation on standard Ethernet + if (msg->h.numAnswers || msg->h.numAuthorities || msg->h.numAdditionals) + limit = msg->data + NormalMaxDNSMessageData; + + if (rr->RecordType == kDNSRecordTypeUnregistered) + { + LogMsg("PutResourceRecord ERROR! Attempt to put kDNSRecordTypeUnregistered %##s (%s)", rr->name.c, DNSTypeName(rr->rrtype)); + return(ptr); + } + + ptr = putDomainNameAsLabels(msg, ptr, limit, &rr->name); + if (!ptr || ptr + 10 >= limit) return(mDNSNULL); // If we're out-of-space, return mDNSNULL + ptr[0] = (mDNSu8)(rr->rrtype >> 8); + ptr[1] = (mDNSu8)(rr->rrtype ); + ptr[2] = (mDNSu8)(rr->rrclass >> 8); + ptr[3] = (mDNSu8)(rr->rrclass ); + ptr[4] = (mDNSu8)(ttl >> 24); + ptr[5] = (mDNSu8)(ttl >> 16); + ptr[6] = (mDNSu8)(ttl >> 8); + ptr[7] = (mDNSu8)(ttl ); + endofrdata = putRData(msg, ptr+10, limit, rr); + if (!endofrdata) { verbosedebugf("Ran out of space in PutResourceRecord for %##s (%s)", rr->name.c, DNSTypeName(rr->rrtype)); return(mDNSNULL); } + + // Go back and fill in the actual number of data bytes we wrote + // (actualLength can be less than rdlength when domain name compression is used) + actualLength = (mDNSu16)(endofrdata - ptr - 10); + ptr[8] = (mDNSu8)(actualLength >> 8); + ptr[9] = (mDNSu8)(actualLength ); + + (*count)++; + return(endofrdata); + } + +#define PutResourceRecord(MSG, P, C, RR) PutResourceRecordTTL((MSG), (P), (C), (RR), (RR)->rroriginalttl) + +mDNSlocal mDNSu8 *PutResourceRecordCappedTTL(DNSMessage *const msg, mDNSu8 *ptr, mDNSu16 *count, ResourceRecord *rr, mDNSu32 maxttl) + { + if (maxttl > rr->rroriginalttl) maxttl = rr->rroriginalttl; + return(PutResourceRecordTTL(msg, ptr, count, rr, maxttl)); + } + +#if 0 +mDNSlocal mDNSu8 *putEmptyResourceRecord(DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit, + mDNSu16 *count, const AuthRecord *rr) + { + ptr = putDomainNameAsLabels(msg, ptr, limit, &rr->name); + if (!ptr || ptr + 10 > limit) return(mDNSNULL); // If we're out-of-space, return mDNSNULL + ptr[0] = (mDNSu8)(rr->resrec.rrtype >> 8); // Put type + ptr[1] = (mDNSu8)(rr->resrec.rrtype ); + ptr[2] = (mDNSu8)(rr->resrec.rrclass >> 8); // Put class + ptr[3] = (mDNSu8)(rr->resrec.rrclass ); + ptr[4] = ptr[5] = ptr[6] = ptr[7] = 0; // TTL is zero + ptr[8] = ptr[9] = 0; // RDATA length is zero + (*count)++; + return(ptr + 10); + } +#endif + +mDNSlocal mDNSu8 *putQuestion(DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit, + const domainname *const name, mDNSu16 rrtype, mDNSu16 rrclass) + { + ptr = putDomainNameAsLabels(msg, ptr, limit, name); + if (!ptr || ptr+4 >= limit) return(mDNSNULL); // If we're out-of-space, return mDNSNULL + ptr[0] = (mDNSu8)(rrtype >> 8); + ptr[1] = (mDNSu8)(rrtype ); + ptr[2] = (mDNSu8)(rrclass >> 8); + ptr[3] = (mDNSu8)(rrclass ); + msg->h.numQuestions++; + return(ptr+4); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNS Message Parsing Functions +#endif + +mDNSlocal const mDNSu8 *skipDomainName(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *const end) + { + mDNSu16 total = 0; + + if (ptr < (mDNSu8*)msg || ptr >= end) + { debugf("skipDomainName: Illegal ptr not within packet boundaries"); return(mDNSNULL); } + + while (1) // Read sequence of labels + { + const mDNSu8 len = *ptr++; // Read length of this label + if (len == 0) return(ptr); // If length is zero, that means this name is complete + switch (len & 0xC0) + { + case 0x00: if (ptr + len >= end) // Remember: expect at least one more byte for the root label + { debugf("skipDomainName: Malformed domain name (overruns packet end)"); return(mDNSNULL); } + if (total + 1 + len >= MAX_DOMAIN_NAME) // Remember: expect at least one more byte for the root label + { debugf("skipDomainName: Malformed domain name (more than 255 characters)"); return(mDNSNULL); } + ptr += len; + total += 1 + len; + break; + + case 0x40: debugf("skipDomainName: Extended EDNS0 label types 0x%X not supported", len); return(mDNSNULL); + case 0x80: debugf("skipDomainName: Illegal label length 0x%X", len); return(mDNSNULL); + case 0xC0: return(ptr+1); + } + } + } + +// Routine to fetch an FQDN from the DNS message, following compression pointers if necessary. +mDNSlocal const mDNSu8 *getDomainName(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *const end, + domainname *const name) + { + const mDNSu8 *nextbyte = mDNSNULL; // Record where we got to before we started following pointers + mDNSu8 *np = name->c; // Name pointer + const mDNSu8 *const limit = np + MAX_DOMAIN_NAME; // Limit so we don't overrun buffer + + if (ptr < (mDNSu8*)msg || ptr >= end) + { debugf("getDomainName: Illegal ptr not within packet boundaries"); return(mDNSNULL); } + + *np = 0; // Tentatively place the root label here (may be overwritten if we have more labels) + + while (1) // Read sequence of labels + { + const mDNSu8 len = *ptr++; // Read length of this label + if (len == 0) break; // If length is zero, that means this name is complete + switch (len & 0xC0) + { + int i; + mDNSu16 offset; + + case 0x00: if (ptr + len >= end) // Remember: expect at least one more byte for the root label + { debugf("getDomainName: Malformed domain name (overruns packet end)"); return(mDNSNULL); } + if (np + 1 + len >= limit) // Remember: expect at least one more byte for the root label + { debugf("getDomainName: Malformed domain name (more than 255 characters)"); return(mDNSNULL); } + *np++ = len; + for (i=0; ic); + return(mDNSNULL); + + case 0x80: debugf("getDomainName: Illegal label length 0x%X in domain name %##s", len, name->c); return(mDNSNULL); + + case 0xC0: offset = (mDNSu16)((((mDNSu16)(len & 0x3F)) << 8) | *ptr++); + if (!nextbyte) nextbyte = ptr; // Record where we got to before we started following pointers + ptr = (mDNSu8 *)msg + offset; + if (ptr < (mDNSu8*)msg || ptr >= end) + { debugf("getDomainName: Illegal compression pointer not within packet boundaries"); return(mDNSNULL); } + if (*ptr & 0xC0) + { debugf("getDomainName: Compression pointer must point to real label"); return(mDNSNULL); } + break; + } + } + + if (nextbyte) return(nextbyte); + else return(ptr); + } + +mDNSlocal const mDNSu8 *skipResourceRecord(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end) + { + mDNSu16 pktrdlength; + + ptr = skipDomainName(msg, ptr, end); + if (!ptr) { debugf("skipResourceRecord: Malformed RR name"); return(mDNSNULL); } + + if (ptr + 10 > end) { debugf("skipResourceRecord: Malformed RR -- no type/class/ttl/len!"); return(mDNSNULL); } + pktrdlength = (mDNSu16)((mDNSu16)ptr[8] << 8 | ptr[9]); + ptr += 10; + if (ptr + pktrdlength > end) { debugf("skipResourceRecord: RDATA exceeds end of packet"); return(mDNSNULL); } + + return(ptr + pktrdlength); + } + +#define GetLargeResourceRecord(m, msg, p, e, i, t, L) \ + (((L)->r.rdatastorage.MaxRDLength = MaximumRDSize), GetResourceRecord((m), (msg), (p), (e), (i), (t), &(L)->r, (RData*)&(L)->r.rdatastorage)) + +mDNSlocal const mDNSu8 *GetResourceRecord(mDNS *const m, const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end, + const mDNSInterfaceID InterfaceID, mDNSu8 RecordType, CacheRecord *rr, RData *RDataStorage) + { + mDNSu16 pktrdlength; + + rr->next = mDNSNULL; + rr->resrec.RecordType = RecordType; + + rr->NextInKAList = mDNSNULL; + rr->TimeRcvd = m->timenow; + rr->NextRequiredQuery = m->timenow; // Will be updated to the real value when we call SetNextCacheCheckTime() + rr->LastUsed = m->timenow; + rr->UseCount = 0; + rr->CRActiveQuestion = mDNSNULL; + rr->UnansweredQueries = 0; + rr->LastUnansweredTime= 0; + rr->MPUnansweredQ = 0; + rr->MPLastUnansweredQT= 0; + rr->MPUnansweredKA = 0; + rr->MPExpectingKA = mDNSfalse; + rr->NextInCFList = mDNSNULL; + + rr->resrec.InterfaceID = InterfaceID; + ptr = getDomainName(msg, ptr, end, &rr->resrec.name); + if (!ptr) { debugf("GetResourceRecord: Malformed RR name"); return(mDNSNULL); } + + if (ptr + 10 > end) { debugf("GetResourceRecord: Malformed RR -- no type/class/ttl/len!"); return(mDNSNULL); } + + rr->resrec.rrtype = (mDNSu16) ((mDNSu16)ptr[0] << 8 | ptr[1]); + rr->resrec.rrclass = (mDNSu16)(((mDNSu16)ptr[2] << 8 | ptr[3]) & kDNSClass_Mask); + rr->resrec.rroriginalttl = (mDNSu32) ((mDNSu32)ptr[4] << 24 | (mDNSu32)ptr[5] << 16 | (mDNSu32)ptr[6] << 8 | ptr[7]); + if (rr->resrec.rroriginalttl > 0x70000000UL / mDNSPlatformOneSecond) + rr->resrec.rroriginalttl = 0x70000000UL / mDNSPlatformOneSecond; + // Note: We don't have to adjust m->NextCacheCheck here -- this is just getting a record into memory for + // us to look at. If we decide to copy it into the cache, then we'll update m->NextCacheCheck accordingly. + pktrdlength = (mDNSu16)((mDNSu16)ptr[8] << 8 | ptr[9]); + if (ptr[2] & (kDNSClass_UniqueRRSet >> 8)) + rr->resrec.RecordType |= kDNSRecordTypePacketUniqueMask; + ptr += 10; + if (ptr + pktrdlength > end) { debugf("GetResourceRecord: RDATA exceeds end of packet"); return(mDNSNULL); } + + if (RDataStorage) + rr->resrec.rdata = RDataStorage; + else + { + rr->resrec.rdata = (RData*)&rr->rdatastorage; + rr->resrec.rdata->MaxRDLength = sizeof(RDataBody); + } + + switch (rr->resrec.rrtype) + { + case kDNSType_A: rr->resrec.rdata->u.ip.b[0] = ptr[0]; + rr->resrec.rdata->u.ip.b[1] = ptr[1]; + rr->resrec.rdata->u.ip.b[2] = ptr[2]; + rr->resrec.rdata->u.ip.b[3] = ptr[3]; + break; + + case kDNSType_CNAME:// Same as PTR + case kDNSType_PTR: if (!getDomainName(msg, ptr, end, &rr->resrec.rdata->u.name)) + { debugf("GetResourceRecord: Malformed CNAME/PTR RDATA name"); return(mDNSNULL); } + //debugf("%##s PTR %##s rdlen %d", rr->resrec.name.c, rr->resrec.rdata->u.name.c, pktrdlength); + break; + + case kDNSType_NULL: //Same as TXT + case kDNSType_HINFO://Same as TXT + case kDNSType_TXT: if (pktrdlength > rr->resrec.rdata->MaxRDLength) + { + debugf("GetResourceRecord: %s rdata size (%d) exceeds storage (%d)", + DNSTypeName(rr->resrec.rrtype), pktrdlength, rr->resrec.rdata->MaxRDLength); + return(mDNSNULL); + } + rr->resrec.rdlength = pktrdlength; + mDNSPlatformMemCopy(ptr, rr->resrec.rdata->u.data, pktrdlength); + break; + + case kDNSType_AAAA: mDNSPlatformMemCopy(ptr, &rr->resrec.rdata->u.ipv6, sizeof(rr->resrec.rdata->u.ipv6)); + break; + + case kDNSType_SRV: rr->resrec.rdata->u.srv.priority = (mDNSu16)((mDNSu16)ptr[0] << 8 | ptr[1]); + rr->resrec.rdata->u.srv.weight = (mDNSu16)((mDNSu16)ptr[2] << 8 | ptr[3]); + rr->resrec.rdata->u.srv.port.b[0] = ptr[4]; + rr->resrec.rdata->u.srv.port.b[1] = ptr[5]; + if (!getDomainName(msg, ptr+6, end, &rr->resrec.rdata->u.srv.target)) + { debugf("GetResourceRecord: Malformed SRV RDATA name"); return(mDNSNULL); } + //debugf("%##s SRV %##s rdlen %d", rr->resrec.name.c, rr->resrec.rdata->u.srv.target.c, pktrdlength); + break; + + default: if (pktrdlength > rr->resrec.rdata->MaxRDLength) + { + debugf("GetResourceRecord: rdata %d (%s) size (%d) exceeds storage (%d)", + rr->resrec.rrtype, DNSTypeName(rr->resrec.rrtype), pktrdlength, rr->resrec.rdata->MaxRDLength); + return(mDNSNULL); + } + debugf("GetResourceRecord: Warning! Reading resource type %d (%s) as opaque data", + rr->resrec.rrtype, DNSTypeName(rr->resrec.rrtype)); + // Note: Just because we don't understand the record type, that doesn't + // mean we fail. The DNS protocol specifies rdlength, so we can + // safely skip over unknown records and ignore them. + // We also grab a binary copy of the rdata anyway, since the caller + // might know how to interpret it even if we don't. + rr->resrec.rdlength = pktrdlength; + mDNSPlatformMemCopy(ptr, rr->resrec.rdata->u.data, pktrdlength); + break; + } + + rr->resrec.namehash = DomainNameHashValue(&rr->resrec.name); + SetNewRData(&rr->resrec, mDNSNULL, 0); + + return(ptr + pktrdlength); + } + +mDNSlocal const mDNSu8 *skipQuestion(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end) + { + ptr = skipDomainName(msg, ptr, end); + if (!ptr) { debugf("skipQuestion: Malformed domain name in DNS question section"); return(mDNSNULL); } + if (ptr+4 > end) { debugf("skipQuestion: Malformed DNS question section -- no query type and class!"); return(mDNSNULL); } + return(ptr+4); + } + +mDNSlocal const mDNSu8 *getQuestion(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end, const mDNSInterfaceID InterfaceID, + DNSQuestion *question) + { + question->InterfaceID = InterfaceID; + ptr = getDomainName(msg, ptr, end, &question->qname); + if (!ptr) { debugf("Malformed domain name in DNS question section"); return(mDNSNULL); } + if (ptr+4 > end) { debugf("Malformed DNS question section -- no query type and class!"); return(mDNSNULL); } + + question->qnamehash = DomainNameHashValue(&question->qname); + question->qtype = (mDNSu16)((mDNSu16)ptr[0] << 8 | ptr[1]); // Get type + question->qclass = (mDNSu16)((mDNSu16)ptr[2] << 8 | ptr[3]); // and class + return(ptr+4); + } + +mDNSlocal const mDNSu8 *LocateAnswers(const DNSMessage *const msg, const mDNSu8 *const end) + { + int i; + const mDNSu8 *ptr = msg->data; + for (i = 0; i < msg->h.numQuestions && ptr; i++) ptr = skipQuestion(msg, ptr, end); + return(ptr); + } + +mDNSlocal const mDNSu8 *LocateAuthorities(const DNSMessage *const msg, const mDNSu8 *const end) + { + int i; + const mDNSu8 *ptr = LocateAnswers(msg, end); + for (i = 0; i < msg->h.numAnswers && ptr; i++) ptr = skipResourceRecord(msg, ptr, end); + return(ptr); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - +#pragma mark - Packet Sending Functions +#endif + +mDNSlocal mStatus mDNSSendDNSMessage(const mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end, + mDNSInterfaceID InterfaceID, mDNSIPPort srcport, const mDNSAddr *dst, mDNSIPPort dstport) + { + mStatus status; + mDNSu16 numQuestions = msg->h.numQuestions; + mDNSu16 numAnswers = msg->h.numAnswers; + mDNSu16 numAuthorities = msg->h.numAuthorities; + mDNSu16 numAdditionals = msg->h.numAdditionals; + + // Put all the integer values in IETF byte-order (MSB first, LSB second) + mDNSu8 *ptr = (mDNSu8 *)&msg->h.numQuestions; + *ptr++ = (mDNSu8)(numQuestions >> 8); + *ptr++ = (mDNSu8)(numQuestions ); + *ptr++ = (mDNSu8)(numAnswers >> 8); + *ptr++ = (mDNSu8)(numAnswers ); + *ptr++ = (mDNSu8)(numAuthorities >> 8); + *ptr++ = (mDNSu8)(numAuthorities ); + *ptr++ = (mDNSu8)(numAdditionals >> 8); + *ptr++ = (mDNSu8)(numAdditionals ); + + // Send the packet on the wire + status = mDNSPlatformSendUDP(m, msg, end, InterfaceID, srcport, dst, dstport); + + // Put all the integer values back the way they were before we return + msg->h.numQuestions = numQuestions; + msg->h.numAnswers = numAnswers; + msg->h.numAuthorities = numAuthorities; + msg->h.numAdditionals = numAdditionals; + + return(status); + } + +mDNSlocal void CompleteDeregistration(mDNS *const m, AuthRecord *rr) + { + // Setting AnnounceCount to InitialAnnounceCount signals mDNS_Deregister_internal() + // that it should go ahead and immediately dispose of this registration + rr->resrec.RecordType = kDNSRecordTypeShared; + rr->AnnounceCount = InitialAnnounceCount; + mDNS_Deregister_internal(m, rr, mDNS_Dereg_normal); + } + +// NOTE: DiscardDeregistrations calls mDNS_Deregister_internal which can call a user callback, which may change +// the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSlocal void DiscardDeregistrations(mDNS *const m) + { + if (m->CurrentRecord) LogMsg("DiscardDeregistrations ERROR m->CurrentRecord already set"); + m->CurrentRecord = m->ResourceRecords; + + while (m->CurrentRecord) + { + AuthRecord *rr = m->CurrentRecord; + m->CurrentRecord = rr->next; + if (rr->resrec.RecordType == kDNSRecordTypeDeregistering) + CompleteDeregistration(m, rr); + } + } + +mDNSlocal mDNSBool HaveSentEntireRRSet(const mDNS *const m, const AuthRecord *const rr, mDNSInterfaceID InterfaceID) + { + // Try to find another member of this set that we're still planning to send on this interface + const AuthRecord *a; + for (a = m->ResourceRecords; a; a=a->next) + if (a->SendRNow == InterfaceID && a != rr && SameResourceRecordSignature(&a->resrec, &rr->resrec)) break; + return (a == mDNSNULL); // If no more members of this set found, then we should set the cache flush bit + } + +// Note about acceleration of announcements to facilitate automatic coalescing of +// multiple independent threads of announcements into a single synchronized thread: +// The announcements in the packet may be at different stages of maturity; +// One-second interval, two-second interval, four-second interval, and so on. +// After we've put in all the announcements that are due, we then consider +// whether there are other nearly-due announcements that are worth accelerating. +// To be eligible for acceleration, a record MUST NOT be older (further along +// its timeline) than the most mature record we've already put in the packet. +// In other words, younger records can have their timelines accelerated to catch up +// with their elder bretheren; this narrows the age gap and helps them eventually get in sync. +// Older records cannot have their timelines accelerated; this would just widen +// the gap between them and their younger bretheren and get them even more out of sync. + +// NOTE: SendResponses calls mDNS_Deregister_internal which can call a user callback, which may change +// the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSlocal void SendResponses(mDNS *const m) + { + int pktcount = 0; + AuthRecord *rr, *r2; + mDNSs32 maxExistingAnnounceInterval = 0; + const NetworkInterfaceInfo *intf = GetFirstActiveInterface(m->HostInterfaces); + + m->NextScheduledResponse = m->timenow + 0x78000000; + + // *** + // *** 1. Setup: Set the SendRNow and ImmedAnswer fields to indicate which interface(s) the records need to be sent on + // *** + + // Run through our list of records, and decide which ones we're going to announce on all interfaces + for (rr = m->ResourceRecords; rr; rr=rr->next) + { + if (rr->NextUpdateCredit && m->timenow - rr->NextUpdateCredit >= 0) + { + if (++rr->UpdateCredits >= kMaxUpdateCredits) rr->NextUpdateCredit = 0; + else rr->NextUpdateCredit = (m->timenow + mDNSPlatformOneSecond * 60) | 1; + } + if (TimeToAnnounceThisRecord(rr, m->timenow) && ResourceRecordIsValidAnswer(rr)) + { + rr->ImmedAnswer = mDNSInterfaceMark; // Send on all interfaces + if (maxExistingAnnounceInterval < rr->ThisAPInterval) + maxExistingAnnounceInterval = rr->ThisAPInterval; + if (rr->UpdateBlocked) rr->UpdateBlocked = 0; + } + } + + // Any interface-specific records we're going to send are marked as being sent on all appropriate interfaces (which is just one) + // Eligible records that are more than half-way to their announcement time are accelerated + for (rr = m->ResourceRecords; rr; rr=rr->next) + if ((rr->resrec.InterfaceID && rr->ImmedAnswer) || + (rr->ThisAPInterval <= maxExistingAnnounceInterval && + TimeToAnnounceThisRecord(rr, m->timenow + rr->ThisAPInterval/2) && + ResourceRecordIsValidAnswer(rr))) + rr->ImmedAnswer = mDNSInterfaceMark; // Send on all interfaces + + // When sending SRV records (particularly when announcing a new service) automatically add the related Address record(s) + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->ImmedAnswer && rr->resrec.rrtype == kDNSType_SRV) + for (r2=m->ResourceRecords; r2; r2=r2->next) // Scan list of resource records + if (RRIsAddressType(r2) && // For all address records (A/AAAA) ... + ResourceRecordIsValidAnswer(r2) && // ... which are valid for answer ... + rr->LastMCTime - r2->LastMCTime >= 0 && // ... which we have not sent recently ... + rr->resrec.rdnamehash == r2->resrec.namehash && // ... whose name is the name of the SRV target + SameDomainName(&rr->resrec.rdata->u.srv.target, &r2->resrec.name) && + (rr->ImmedAnswer == mDNSInterfaceMark || rr->ImmedAnswer == r2->resrec.InterfaceID)) + r2->ImmedAnswer = mDNSInterfaceMark; // ... then mark this address record for sending too + + // If there's a record which is supposed to be unique that we're going to send, then make sure that we give + // the whole RRSet as an atomic unit. That means that if we have any other records with the same name/type/class + // then we need to mark them for sending too. Otherwise, if we set the kDNSClass_UniqueRRSet bit on a + // record, then other RRSet members that have not been sent recently will get flushed out of client caches. + // -- If a record is marked to be sent on a certain interface, make sure the whole set is marked to be sent on that interface + // -- If any record is marked to be sent on all interfaces, make sure the whole set is marked to be sent on all interfaces + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) + { + if (rr->ImmedAnswer) // If we're sending this as answer, see that its whole RRSet is similarly marked + { + for (r2 = m->ResourceRecords; r2; r2=r2->next) + if (ResourceRecordIsValidAnswer(r2)) + if (r2->ImmedAnswer != mDNSInterfaceMark && r2->ImmedAnswer != rr->ImmedAnswer && SameResourceRecordSignature(&r2->resrec, &rr->resrec)) + r2->ImmedAnswer = rr->ImmedAnswer; + } + else if (rr->ImmedAdditional) // If we're sending this as additional, see that its whole RRSet is similarly marked + { + for (r2 = m->ResourceRecords; r2; r2=r2->next) + if (ResourceRecordIsValidAnswer(r2)) + if (r2->ImmedAdditional != rr->ImmedAdditional && SameResourceRecordSignature(&r2->resrec, &rr->resrec)) + r2->ImmedAdditional = rr->ImmedAdditional; + } + } + + // Now set SendRNow state appropriately + for (rr = m->ResourceRecords; rr; rr=rr->next) + { + if (rr->ImmedAnswer == mDNSInterfaceMark) // Sending this record on all appropriate interfaces + { + rr->SendRNow = !intf ? mDNSNULL : (rr->resrec.InterfaceID) ? rr->resrec.InterfaceID : intf->InterfaceID; + rr->ImmedAdditional = mDNSNULL; // No need to send as additional if sending as answer + rr->LastMCTime = m->timenow; + rr->LastMCInterface = rr->ImmedAnswer; + // If we're announcing this record, and it's at least half-way to its ordained time, then consider this announcement done + if (TimeToAnnounceThisRecord(rr, m->timenow + rr->ThisAPInterval/2)) + { + rr->AnnounceCount--; + rr->ThisAPInterval *= 2; + rr->LastAPTime = m->timenow; + if (rr->LastAPTime + rr->ThisAPInterval - rr->AnnounceUntil >= 0) rr->AnnounceCount = 0; + debugf("Announcing %##s (%s) %d", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), rr->AnnounceCount); + } + } + else if (rr->ImmedAnswer) // Else, just respond to a single query on single interface: + { + rr->SendRNow = rr->ImmedAnswer; // Just respond on that interface + rr->ImmedAdditional = mDNSNULL; // No need to send as additional too + rr->LastMCTime = m->timenow; + rr->LastMCInterface = rr->ImmedAnswer; + } + SetNextAnnounceProbeTime(m, rr); + } + + // *** + // *** 2. Loop through interface list, sending records as appropriate + // *** + + while (intf) + { + int numDereg = 0; + int numAnnounce = 0; + int numAnswer = 0; + DNSMessage response; + mDNSu8 *responseptr = response.data; + mDNSu8 *newptr; + InitializeDNSMessage(&response.h, zeroID, ResponseFlags); + + // First Pass. Look for: + // 1. Deregistering records that need to send their goodbye packet + // 2. Updated records that need to retract their old data + // 3. Answers and announcements we need to send + // In all cases, if we fail, and we've put at least one answer, we break out of the for loop so we can + // send this packet and then try again. + // If we have not put even one answer, then we don't bail out. We pretend we succeeded anyway, + // because otherwise we'll end up in an infinite loop trying to send a record that will never fit. + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->SendRNow == intf->InterfaceID) + { + if (rr->resrec.RecordType == kDNSRecordTypeDeregistering) + { + newptr = PutResourceRecordTTL(&response, responseptr, &response.h.numAnswers, &rr->resrec, 0); + if (!newptr && response.h.numAnswers) break; + numDereg++; + responseptr = newptr; + } + else if (rr->NewRData) // If we have new data for this record + { + RData *OldRData = rr->resrec.rdata; + mDNSu16 oldrdlength = rr->resrec.rdlength; + // See if we should send a courtesy "goodbye" the old data before we replace it. + // We compare with "InitialAnnounceCount-1" instead of "InitialAnnounceCount" because by the time + // we get to this place in this routine we've we've already decremented rr->AnnounceCount + if (ResourceRecordIsValidAnswer(rr) && rr->AnnounceCount < InitialAnnounceCount-1) + { + newptr = PutResourceRecordTTL(&response, responseptr, &response.h.numAnswers, &rr->resrec, 0); + if (!newptr && response.h.numAnswers) break; + numDereg++; + responseptr = newptr; + } + // Now try to see if we can fit the update in the same packet (not fatal if we can't) + SetNewRData(&rr->resrec, rr->NewRData, rr->newrdlength); + if ((rr->resrec.RecordType & kDNSRecordTypeUniqueMask) && HaveSentEntireRRSet(m, rr, intf->InterfaceID)) + rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the cache flush bit so PutResourceRecord will set it + newptr = PutResourceRecord(&response, responseptr, &response.h.numAnswers, &rr->resrec); + rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet; // Make sure to clear cache flush bit back to normal state + if (newptr) responseptr = newptr; + SetNewRData(&rr->resrec, OldRData, oldrdlength); + } + else + { + if ((rr->resrec.RecordType & kDNSRecordTypeUniqueMask) && HaveSentEntireRRSet(m, rr, intf->InterfaceID)) + rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the cache flush bit so PutResourceRecord will set it + newptr = PutResourceRecordTTL(&response, responseptr, &response.h.numAnswers, &rr->resrec, m->SleepState ? 0 : rr->resrec.rroriginalttl); + rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet; // Make sure to clear cache flush bit back to normal state + if (!newptr && response.h.numAnswers) break; + if (rr->LastAPTime == m->timenow) numAnnounce++; else numAnswer++; + responseptr = newptr; + } + // If sending on all interfaces, go to next interface; else we're finished now + if (rr->ImmedAnswer == mDNSInterfaceMark && rr->resrec.InterfaceID == mDNSInterface_Any) + rr->SendRNow = GetNextActiveInterfaceID(intf); + else + rr->SendRNow = mDNSNULL; + } + + // Second Pass. Add additional records, if there's space. + newptr = responseptr; + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->ImmedAdditional == intf->InterfaceID) + { + // Since additionals are optional, we clear ImmedAdditional anyway, even if we subsequently find it doesn't fit in the packet + rr->ImmedAdditional = mDNSNULL; + if (newptr && ResourceRecordIsValidAnswer(rr)) + { + if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) + { + // Try to find another member of this set that we're still planning to send on this interface + const AuthRecord *a; + for (a = m->ResourceRecords; a; a=a->next) + if (a->ImmedAdditional == intf->InterfaceID && SameResourceRecordSignature(&a->resrec, &rr->resrec)) break; + if (a == mDNSNULL) // If no more members of this set found + rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the cache flush bit so PutResourceRecord will set it + } + newptr = PutResourceRecord(&response, newptr, &response.h.numAdditionals, &rr->resrec); + if (newptr) responseptr = newptr; + rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet; // Make sure to clear cache flush bit back to normal state + } + } + + if (response.h.numAnswers > 0) // We *never* send a packet with only additionals in it + { + debugf("SendResponses: Sending %d Deregistration%s, %d Announcement%s, %d Answer%s, %d Additional%s on %p", + numDereg, numDereg == 1 ? "" : "s", + numAnnounce, numAnnounce == 1 ? "" : "s", + numAnswer, numAnswer == 1 ? "" : "s", + response.h.numAdditionals, response.h.numAdditionals == 1 ? "" : "s", intf->InterfaceID); + mDNSSendDNSMessage(m, &response, responseptr, intf->InterfaceID, MulticastDNSPort, &AllDNSLinkGroup_v4, MulticastDNSPort); + mDNSSendDNSMessage(m, &response, responseptr, intf->InterfaceID, MulticastDNSPort, &AllDNSLinkGroup_v6, MulticastDNSPort); + if (!m->SuppressSending) m->SuppressSending = (m->timenow + mDNSPlatformOneSecond/10) | 1; // OR with one to ensure non-zero + if (++pktcount >= 1000) + { LogMsg("SendResponses exceeded loop limit %d: giving up", pktcount); break; } + // There might be more things to send on this interface, so go around one more time and try again. + } + else // Nothing more to send on this interface; go to next + { + const NetworkInterfaceInfo *next = GetFirstActiveInterface(intf->next); + #if MDNS_DEBUGMSGS && 0 + const char *const msg = next ? "SendResponses: Nothing more on %p; moving to %p" : "SendResponses: Nothing more on %p"; + debugf(msg, intf, next); + #endif + intf = next; + } + } + + // *** + // *** 3. Cleanup: Now that everything is sent, call client callback functions, and reset state variables + // *** + + if (m->CurrentRecord) LogMsg("SendResponses: ERROR m->CurrentRecord already set"); + m->CurrentRecord = m->ResourceRecords; + while (m->CurrentRecord) + { + rr = m->CurrentRecord; + m->CurrentRecord = rr->next; + + if (rr->NewRData) + { + RData *OldRData = rr->resrec.rdata; + SetNewRData(&rr->resrec, rr->NewRData, rr->newrdlength); // Update our rdata + rr->NewRData = mDNSNULL; // Clear the NewRData pointer ... + if (rr->UpdateCallback) + rr->UpdateCallback(m, rr, OldRData); // ... and let the client know + } + + if (rr->resrec.RecordType == kDNSRecordTypeDeregistering) + CompleteDeregistration(m, rr); + else + { + rr->ImmedAnswer = mDNSNULL; + rr->v4Requester = zeroIPAddr; + rr->v6Requester = zerov6Addr; + } + } + verbosedebugf("SendResponses: Next in %d ticks", m->NextScheduledResponse - m->timenow); + } + +// Calling CheckCacheExpiration() is an expensive operation because it has to look at the entire cache, +// so we want to be lazy about how frequently we do it. +// 1. If a cache record is currently referenced by *no* active questions, +// then we don't mind expiring it up to a minute late (who will know?) +// 2. Else, if a cache record is due for some of its final expiration queries, +// we'll allow them to be late by up to 2% of the TTL +// 3. Else, if a cache record has completed all its final expiration queries without success, +// and is expiring, and had an original TTL more than ten seconds, we'll allow it to be one second late +// 4. Else, it is expiring and had an original TTL of ten seconds or less (includes explicit goodbye packets), +// so allow at most 1/10 second lateness +#define CacheCheckGracePeriod(RR) ( \ + ((RR)->CRActiveQuestion == mDNSNULL ) ? (60 * mDNSPlatformOneSecond) : \ + ((RR)->UnansweredQueries < MaxUnansweredQueries) ? (TicksTTL(rr)/50) : \ + ((RR)->resrec.rroriginalttl > 10 ) ? (mDNSPlatformOneSecond) : (mDNSPlatformOneSecond/10)) + +// Note: MUST call SetNextCacheCheckTime any time we change: +// rr->TimeRcvd +// rr->resrec.rroriginalttl +// rr->UnansweredQueries +// rr->CRActiveQuestion +mDNSlocal void SetNextCacheCheckTime(mDNS *const m, CacheRecord *const rr) + { + rr->NextRequiredQuery = RRExpireTime(rr); + + // If we have an active question, then see if we want to schedule a refresher query for this record. + // Usually we expect to do four queries, at 80-82%, 85-87%, 90-92% and then 95-97% of the TTL. + if (rr->CRActiveQuestion && rr->UnansweredQueries < MaxUnansweredQueries) + { + rr->NextRequiredQuery -= TicksTTL(rr)/20 * (MaxUnansweredQueries - rr->UnansweredQueries); + rr->NextRequiredQuery += mDNSRandom((mDNSu32)TicksTTL(rr)/50); + verbosedebugf("SetNextCacheCheckTime: %##s (%s) NextRequiredQuery in %ld sec", + rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), (rr->NextRequiredQuery - m->timenow) / mDNSPlatformOneSecond); + } + + if (m->NextCacheCheck - (rr->NextRequiredQuery + CacheCheckGracePeriod(rr)) > 0) + m->NextCacheCheck = (rr->NextRequiredQuery + CacheCheckGracePeriod(rr)); + } + +#define kDefaultReconfirmTimeForNoAnswer ((mDNSu32)mDNSPlatformOneSecond * 45) +#define kDefaultReconfirmTimeForCableDisconnect ((mDNSu32)mDNSPlatformOneSecond * 5) +#define kMinimumReconfirmTime ((mDNSu32)mDNSPlatformOneSecond * 5) + +mDNSlocal mStatus mDNS_Reconfirm_internal(mDNS *const m, CacheRecord *const rr, mDNSu32 interval) + { + if (interval < kMinimumReconfirmTime) + interval = kMinimumReconfirmTime; + if (interval > 0x10000000) // Make sure interval doesn't overflow when we multiply by four below + interval = 0x10000000; + + // If the expected expiration time for this record is more than interval+33%, then accelerate its expiration + if (RRExpireTime(rr) - m->timenow > (mDNSs32)((interval * 4) / 3)) + { + // Add a 33% random amount to the interval, to avoid synchronization between multiple hosts + interval += mDNSRandom(interval/3); + rr->TimeRcvd = m->timenow - (mDNSs32)interval * 3; + rr->resrec.rroriginalttl = interval * 4 / mDNSPlatformOneSecond; + SetNextCacheCheckTime(m, rr); + } + debugf("mDNS_Reconfirm_internal:%5ld ticks to go for %s", RRExpireTime(rr) - m->timenow, GetRRDisplayString(m, rr)); + return(mStatus_NoError); + } + +#define MaxQuestionInterval (3600 * mDNSPlatformOneSecond) + +// BuildQuestion puts a question into a DNS Query packet and if successful, updates the value of queryptr. +// It also appends to the list of known answer records that need to be included, +// and updates the forcast for the size of the known answer section. +mDNSlocal mDNSBool BuildQuestion(mDNS *const m, DNSMessage *query, mDNSu8 **queryptr, DNSQuestion *q, + CacheRecord ***kalistptrptr, mDNSu32 *answerforecast) + { + mDNSBool ucast = q->LargeAnswers || q->ThisQInterval <= InitialQuestionInterval*2; + mDNSu16 ucbit = (mDNSu16)(ucast ? kDNSQClass_UnicastResponse : 0); + const mDNSu8 *const limit = query->data + NormalMaxDNSMessageData; + mDNSu8 *newptr = putQuestion(query, *queryptr, limit, &q->qname, q->qtype, (mDNSu16)(q->qclass | ucbit)); + if (!newptr) + { + debugf("BuildQuestion: No more space in this packet for question %##s", q->qname.c); + return(mDNSfalse); + } + else if (newptr + *answerforecast >= limit) + { + verbosedebugf("BuildQuestion: Retracting question %##s new forecast total %d", q->qname.c, newptr + *answerforecast - query->data); + query->h.numQuestions--; + return(mDNSfalse); + } + else + { + mDNSu32 forecast = *answerforecast; + CacheRecord *rr; + CacheRecord **ka = *kalistptrptr; // Make a working copy of the pointer we're going to update + + for (rr=m->rrcache_hash[HashSlot(&q->qname)]; rr; rr=rr->next) // If we have a resource record in our cache, + if (rr->resrec.InterfaceID == q->SendQNow && // received on this interface + rr->NextInKAList == mDNSNULL && ka != &rr->NextInKAList && // which is not already in the known answer list + rr->resrec.rdlength <= SmallRecordLimit && // which is small enough to sensibly fit in the packet + ResourceRecordAnswersQuestion(&rr->resrec, q) && // which answers our question + rr->TimeRcvd + TicksTTL(rr)/2 - m->timenow >= 0 && // and it is less than half-way to expiry + rr->NextRequiredQuery - (m->timenow + q->ThisQInterval) > 0)// and we'll ask at least once again before NextRequiredQuery + { + *ka = rr; // Link this record into our known answer chain + ka = &rr->NextInKAList; + // We forecast: compressed name (2) type (2) class (2) TTL (4) rdlength (2) rdata (n) + forecast += 12 + rr->resrec.rdestimate; + // If we're trying to put more than one question in this packet, and it doesn't fit + // then undo that last question and try again next time + if (query->h.numQuestions > 1 && newptr + forecast >= limit) + { + debugf("BuildQuestion: Retracting question %##s (%s) new forecast total %d", + q->qname.c, DNSTypeName(q->qtype), newptr + forecast - query->data); + query->h.numQuestions--; + ka = *kalistptrptr; // Go back to where we started and retract these answer records + while (*ka) { CacheRecord *rr = *ka; *ka = mDNSNULL; ka = &rr->NextInKAList; } + return(mDNSfalse); // Return false, so we'll try again in the next packet + } + } + + // Traffic reduction: + // If we already have at least one unique answer in the cache, + // OR we have so many shared answers that the KA list is too big to fit in one packet + // The we suppress queries number 3 and 5: + // Query 1 (immediately; ThisQInterval = 1 sec; request unicast replies) + // Query 2 (after 1 second; ThisQInterval = 2 sec; send normally) + // Query 3 (after 2 seconds; ThisQInterval = 4 sec; may suppress) + // Query 4 (after 4 seconds; ThisQInterval = 8 sec; send normally) + // Query 5 (after 8 seconds; ThisQInterval = 16 sec; may suppress) + // Query 6 (after 16 seconds; ThisQInterval = 32 sec; send normally) + if (q->UniqueAnswers || newptr + forecast >= limit) + if (q->ThisQInterval == InitialQuestionInterval * 8 || q->ThisQInterval == InitialQuestionInterval * 32) + { + query->h.numQuestions--; + ka = *kalistptrptr; // Go back to where we started and retract these answer records + while (*ka) { CacheRecord *rr = *ka; *ka = mDNSNULL; ka = &rr->NextInKAList; } + return(mDNStrue); // Return true: pretend we succeeded, even though we actually suppressed this question + } + + // Success! Update our state pointers, increment UnansweredQueries as appropriate, and return + *queryptr = newptr; // Update the packet pointer + *answerforecast = forecast; // Update the forecast + *kalistptrptr = ka; // Update the known answer list pointer + if (ucast) m->ExpectUnicastResponse = m->timenow; + + for (rr=m->rrcache_hash[HashSlot(&q->qname)]; rr; rr=rr->next) // For every resource record in our cache, + if (rr->resrec.InterfaceID == q->SendQNow && // received on this interface + rr->NextInKAList == mDNSNULL && ka != &rr->NextInKAList && // which is not in the known answer list + ResourceRecordAnswersQuestion(&rr->resrec, q)) // which answers our question + { + rr->UnansweredQueries++; // indicate that we're expecting a response + rr->LastUnansweredTime = m->timenow; + SetNextCacheCheckTime(m, rr); + } + + return(mDNStrue); + } + } + +mDNSlocal void ReconfirmAntecedents(mDNS *const m, DNSQuestion *q) + { + mDNSu32 slot; + CacheRecord *rr; + domainname *target; + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) + for (rr = m->rrcache_hash[slot]; rr; rr=rr->next) + if ((target = GetRRDomainNameTarget(&rr->resrec)) && rr->resrec.rdnamehash == q->qnamehash && SameDomainName(target, &q->qname)) + mDNS_Reconfirm_internal(m, rr, kDefaultReconfirmTimeForNoAnswer); + } + +// Only DupSuppressInfos newer than the specified 'time' are allowed to remain active +mDNSlocal void ExpireDupSuppressInfo(DupSuppressInfo ds[DupSuppressInfoSize], mDNSs32 time) + { + int i; + for (i=0; iIPv4Available; // If this interface doesn't do v4, we don't need to find a v4 duplicate of this query + mDNSBool v6 = !intf->IPv6Available; // If this interface doesn't do v6, we don't need to find a v6 duplicate of this query + for (i=0; iInterfaceID) + { + if (ds[i].Type == mDNSAddrType_IPv4) v4 = mDNStrue; + else if (ds[i].Type == mDNSAddrType_IPv6) v6 = mDNStrue; + if (v4 && v6) return(mDNStrue); + } + return(mDNSfalse); + } + +mDNSlocal int RecordDupSuppressInfo(DupSuppressInfo ds[DupSuppressInfoSize], mDNSs32 Time, mDNSInterfaceID InterfaceID, mDNSs32 Type) + { + int i, j; + + // See if we have this one in our list somewhere already + for (i=0; i= DupSuppressInfoSize) + { + i = 0; + for (j=1; jtimenow + q->ThisQInterval/10)) + return(mDNStrue); + + // If half-way to next scheduled query time, only accelerate if it will add less than 512 bytes to the packet + if (TimeToSendThisQuestion(q, m->timenow + q->ThisQInterval/2)) + { + // We forecast: qname (n) type (2) class (2) + mDNSu32 forecast = DomainNameLength(&q->qname) + 4; + CacheRecord *rr; + for (rr=m->rrcache_hash[HashSlot(&q->qname)]; rr; rr=rr->next) // If we have a resource record in our cache, + if (rr->resrec.rdlength <= SmallRecordLimit && // which is small enough to sensibly fit in the packet + ResourceRecordAnswersQuestion(&rr->resrec, q) && // which answers our question + rr->TimeRcvd + TicksTTL(rr)/2 - m->timenow >= 0 && // and it is less than half-way to expiry + rr->NextRequiredQuery - (m->timenow + q->ThisQInterval) > 0)// and we'll ask at least once again before NextRequiredQuery + { + // We forecast: compressed name (2) type (2) class (2) TTL (4) rdlength (2) rdata (n) + forecast += 12 + rr->resrec.rdestimate; + if (forecast >= 512) return(mDNSfalse); // If this would add 512 bytes or more to the packet, don't accelerate + } + return(mDNStrue); + } + + return(mDNSfalse); + } + +// How Standard Queries are generated: +// 1. The Question Section contains the question +// 2. The Additional Section contains answers we already know, to suppress duplicate responses + +// How Probe Queries are generated: +// 1. The Question Section contains queries for the name we intend to use, with QType=ANY because +// if some other host is already using *any* records with this name, we want to know about it. +// 2. The Authority Section contains the proposed values we intend to use for one or more +// of our records with that name (analogous to the Update section of DNS Update packets) +// because if some other host is probing at the same time, we each want to know what the other is +// planning, in order to apply the tie-breaking rule to see who gets to use the name and who doesn't. + +mDNSlocal void SendQueries(mDNS *const m) + { + int pktcount = 0; + DNSQuestion *q; + // For explanation of maxExistingQuestionInterval logic, see comments for maxExistingAnnounceInterval + mDNSs32 maxExistingQuestionInterval = 0; + const NetworkInterfaceInfo *intf = GetFirstActiveInterface(m->HostInterfaces); + CacheRecord *KnownAnswerList = mDNSNULL; + + // 1. If time for a query, work out what we need to do + if (m->timenow - m->NextScheduledQuery >= 0) + { + mDNSu32 slot; + CacheRecord *rr; + m->NextScheduledQuery = m->timenow + 0x78000000; + + // We're expecting to send a query anyway, so see if any expiring cache records are close enough + // to their NextRequiredQuery to be worth batching them together with this one + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) + for (rr = m->rrcache_hash[slot]; rr; rr=rr->next) + if (rr->CRActiveQuestion && rr->UnansweredQueries < MaxUnansweredQueries) + if (m->timenow + TicksTTL(rr)/50 - rr->NextRequiredQuery >= 0) + { + q = rr->CRActiveQuestion; + ExpireDupSuppressInfoOnInterface(q->DupSuppress, m->timenow - TicksTTL(rr)/20, rr->resrec.InterfaceID); + if (q->SendQNow == mDNSNULL) q->SendQNow = rr->resrec.InterfaceID; + else if (q->SendQNow != rr->resrec.InterfaceID) q->SendQNow = mDNSInterfaceMark; + } + + // Scan our list of questions to see which ones we're definitely going to send + for (q = m->Questions; q; q=q->next) + if (TimeToSendThisQuestion(q, m->timenow)) + { + q->SendQNow = mDNSInterfaceMark; // Mark this question for sending on all interfaces + if (maxExistingQuestionInterval < q->ThisQInterval) + maxExistingQuestionInterval = q->ThisQInterval; + } + + // Scan our list of questions + // (a) to see if there are any more that are worth accelerating, and + // (b) to update the state variables for all the questions we're going to send + for (q = m->Questions; q; q=q->next) + { + if (q->SendQNow || (ActiveQuestion(q) && q->ThisQInterval <= maxExistingQuestionInterval && AccelerateThisQuery(m,q))) + { + // If at least halfway to next query time, advance to next interval + // If less than halfway to next query time, treat this as logically a repeat of the last transmission, without advancing the interval + if (m->timenow - (q->LastQTime + q->ThisQInterval/2) >= 0) + { + q->SendQNow = mDNSInterfaceMark; // Mark this question for sending on all interfaces + q->ThisQInterval *= 2; + if (q->ThisQInterval > MaxQuestionInterval) + q->ThisQInterval = MaxQuestionInterval; + else if (q->CurrentAnswers == 0 && q->ThisQInterval == InitialQuestionInterval * 8) + { + debugf("SendQueries: Zero current answers for %##s (%s); will reconfirm antecedents", q->qname.c, DNSTypeName(q->qtype)); + ReconfirmAntecedents(m, q); // If sending third query, and no answers yet, time to begin doubting the source + } + } + + // Mark for sending. (If no active interfaces, then don't even try.) + q->SendOnAll = (q->SendQNow == mDNSInterfaceMark); + if (q->SendOnAll) + { + q->SendQNow = !intf ? mDNSNULL : (q->InterfaceID) ? q->InterfaceID : intf->InterfaceID; + q->LastQTime = m->timenow; + } + + // If we recorded a duplicate suppression for this question less than half an interval ago, + // then we consider it recent enough that we don't need to do an identical query ourselves. + ExpireDupSuppressInfo(q->DupSuppress, m->timenow - q->ThisQInterval/2); + + q->LastQTxTime = m->timenow; + q->RecentAnswers = 0; + } + // For all questions (not just the ones we're sending) check what the next scheduled event will be + SetNextQueryTime(m,q); + } + } + + // 2. Scan our authoritative RR list to see what probes we might need to send + if (m->timenow - m->NextScheduledProbe >= 0) + { + m->NextScheduledProbe = m->timenow + 0x78000000; + + if (m->CurrentRecord) LogMsg("SendQueries: ERROR m->CurrentRecord already set"); + m->CurrentRecord = m->ResourceRecords; + while (m->CurrentRecord) + { + AuthRecord *rr = m->CurrentRecord; + m->CurrentRecord = rr->next; + if (rr->resrec.RecordType == kDNSRecordTypeUnique) // For all records that are still probing... + { + // 1. If it's not reached its probe time, just make sure we update m->NextScheduledProbe correctly + if (m->timenow - (rr->LastAPTime + rr->ThisAPInterval) < 0) + { + SetNextAnnounceProbeTime(m, rr); + } + // 2. else, if it has reached its probe time, mark it for sending and then update m->NextScheduledProbe correctly + else if (rr->ProbeCount) + { + // Mark for sending. (If no active interfaces, then don't even try.) + rr->SendRNow = !intf ? mDNSNULL : (rr->resrec.InterfaceID) ? rr->resrec.InterfaceID : intf->InterfaceID; + rr->LastAPTime = m->timenow; + rr->ProbeCount--; + SetNextAnnounceProbeTime(m, rr); + } + // else, if it has now finished probing, move it to state Verified, and update m->NextScheduledResponse so it will be announced + else + { + AuthRecord *r2; + rr->resrec.RecordType = kDNSRecordTypeVerified; + rr->ThisAPInterval = DefaultAnnounceIntervalForTypeUnique; + rr->LastAPTime = m->timenow - DefaultAnnounceIntervalForTypeUnique; + SetNextAnnounceProbeTime(m, rr); + // If we have any records on our duplicate list that match this one, they have now also completed probing + for (r2 = m->DuplicateRecords; r2; r2=r2->next) + if (r2->resrec.RecordType == kDNSRecordTypeUnique && RecordIsLocalDuplicate(r2, rr)) + r2->ProbeCount = 0; + CompleteProbing(m, rr); + } + } + } + m->CurrentRecord = m->DuplicateRecords; + while (m->CurrentRecord) + { + AuthRecord *rr = m->CurrentRecord; + m->CurrentRecord = rr->next; + if (rr->resrec.RecordType == kDNSRecordTypeUnique && rr->ProbeCount == 0) + CompleteProbing(m, rr); + } + } + + // 3. Now we know which queries and probes we're sending, go through our interface list sending the appropriate queries on each interface + while (intf) + { + AuthRecord *rr; + DNSMessage query; + mDNSu8 *queryptr = query.data; + InitializeDNSMessage(&query.h, zeroID, QueryFlags); + if (KnownAnswerList) verbosedebugf("SendQueries: KnownAnswerList set... Will continue from previous packet"); + if (!KnownAnswerList) + { + // Start a new known-answer list + CacheRecord **kalistptr = &KnownAnswerList; + mDNSu32 answerforecast = 0; + + // Put query questions in this packet + for (q = m->Questions; q; q=q->next) + if (q->SendQNow == intf->InterfaceID) + { + debugf("SendQueries: %s question for %##s (%s) at %lu forecast total %lu", + SuppressOnThisInterface(q->DupSuppress, intf) ? "Suppressing" : "Putting ", + q->qname.c, DNSTypeName(q->qtype), queryptr - query.data, queryptr + answerforecast - query.data); + // If we're suppressing this question, or we successfully put it, update its SendQNow state + if (SuppressOnThisInterface(q->DupSuppress, intf) || + BuildQuestion(m, &query, &queryptr, q, &kalistptr, &answerforecast)) + q->SendQNow = (q->InterfaceID || !q->SendOnAll) ? mDNSNULL : GetNextActiveInterfaceID(intf); + } + + // Put probe questions in this packet + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->SendRNow == intf->InterfaceID) + { + mDNSBool ucast = rr->ProbeCount >= DefaultProbeCountForTypeUnique-1; + mDNSu16 ucbit = (mDNSu16)(ucast ? kDNSQClass_UnicastResponse : 0); + const mDNSu8 *const limit = query.data + ((query.h.numQuestions) ? NormalMaxDNSMessageData : AbsoluteMaxDNSMessageData); + mDNSu8 *newptr = putQuestion(&query, queryptr, limit, &rr->resrec.name, kDNSQType_ANY, (mDNSu16)(rr->resrec.rrclass | ucbit)); + // We forecast: compressed name (2) type (2) class (2) TTL (4) rdlength (2) rdata (n) + mDNSu32 forecast = answerforecast + 12 + rr->resrec.rdestimate; + if (newptr && newptr + forecast < limit) + { + queryptr = newptr; + answerforecast = forecast; + rr->SendRNow = (rr->resrec.InterfaceID) ? mDNSNULL : GetNextActiveInterfaceID(intf); + rr->IncludeInProbe = mDNStrue; + verbosedebugf("SendQueries: Put Question %##s (%s) probecount %d", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), rr->ProbeCount); + } + else + { + verbosedebugf("SendQueries: Retracting Question %##s (%s)", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + query.h.numQuestions--; + } + } + } + + // Put our known answer list (either new one from this question or questions, or remainder of old one from last time) + while (KnownAnswerList) + { + CacheRecord *rr = KnownAnswerList; + mDNSu32 SecsSinceRcvd = ((mDNSu32)(m->timenow - rr->TimeRcvd)) / mDNSPlatformOneSecond; + mDNSu8 *newptr = PutResourceRecordTTL(&query, queryptr, &query.h.numAnswers, &rr->resrec, rr->resrec.rroriginalttl - SecsSinceRcvd); + if (newptr) + { + verbosedebugf("SendQueries: Put %##s (%s) at %lu - %lu", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), queryptr - query.data, newptr - query.data); + queryptr = newptr; + KnownAnswerList = rr->NextInKAList; + rr->NextInKAList = mDNSNULL; + } + else + { + // If we ran out of space and we have more than one question in the packet, that's an error -- + // we shouldn't have put more than one question if there was a risk of us running out of space. + if (query.h.numQuestions > 1) + LogMsg("SendQueries: Put %d answers; No more space for known answers", query.h.numAnswers); + query.h.flags.b[0] |= kDNSFlag0_TC; + break; + } + } + + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->IncludeInProbe) + { + mDNSu8 *newptr = PutResourceRecord(&query, queryptr, &query.h.numAuthorities, &rr->resrec); + rr->IncludeInProbe = mDNSfalse; + if (newptr) queryptr = newptr; + else LogMsg("SendQueries: How did we fail to have space for the Update record %##s (%s)?", + rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + } + + if (queryptr > query.data) + { + if ((query.h.flags.b[0] & kDNSFlag0_TC) && query.h.numQuestions > 1) + LogMsg("SendQueries: Should not have more than one question (%d) in a truncated packet\n", query.h.numQuestions); + debugf("SendQueries: Sending %d Question%s %d Answer%s %d Update%s on %p", + query.h.numQuestions, query.h.numQuestions == 1 ? "" : "s", + query.h.numAnswers, query.h.numAnswers == 1 ? "" : "s", + query.h.numAuthorities, query.h.numAuthorities == 1 ? "" : "s", intf->InterfaceID); + mDNSSendDNSMessage(m, &query, queryptr, intf->InterfaceID, MulticastDNSPort, &AllDNSLinkGroup_v4, MulticastDNSPort); + mDNSSendDNSMessage(m, &query, queryptr, intf->InterfaceID, MulticastDNSPort, &AllDNSLinkGroup_v6, MulticastDNSPort); + if (!m->SuppressSending) m->SuppressSending = (m->timenow + mDNSPlatformOneSecond/10) | 1; // OR with one to ensure non-zero + if (++pktcount >= 1000) + { LogMsg("SendQueries exceeded loop limit %d: giving up", pktcount); break; } + // There might be more records left in the known answer list, or more questions to send + // on this interface, so go around one more time and try again. + } + else // Nothing more to send on this interface; go to next + { + const NetworkInterfaceInfo *next = GetFirstActiveInterface(intf->next); + #if MDNS_DEBUGMSGS && 0 + const char *const msg = next ? "SendQueries: Nothing more on %p; moving to %p" : "SendQueries: Nothing more on %p"; + debugf(msg, intf, next); + #endif + intf = next; + } + } + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - RR List Management & Task Management +#endif + +// NOTE: AnswerQuestionWithResourceRecord can call a user callback, which may change the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSlocal void AnswerQuestionWithResourceRecord(mDNS *const m, DNSQuestion *q, CacheRecord *rr, mDNSBool AddRecord) + { + verbosedebugf("AnswerQuestionWithResourceRecord:%4lu %s TTL%6lu %##s (%s)", + q->CurrentAnswers, AddRecord ? "Add" : "Rmv", rr->resrec.rroriginalttl, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + + rr->LastUsed = m->timenow; + rr->UseCount++; + if (ActiveQuestion(q) && rr->CRActiveQuestion != q) + { + if (!rr->CRActiveQuestion) m->rrcache_active++; // If not previously active, increment rrcache_active count + rr->CRActiveQuestion = q; // We know q is non-null + SetNextCacheCheckTime(m, rr); + } + + // CAUTION: MUST NOT do anything more with q after calling q->Callback(), because the client's callback function + // is allowed to do anything, including starting/stopping queries, registering/deregistering records, etc. + // Right now the only routines that call AnswerQuestionWithResourceRecord() are CacheRecordAdd(), CacheRecordRmv() + // and AnswerNewQuestion(), and all of them use the "m->CurrentQuestion" mechanism to protect against questions + // being deleted out from under them. + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + if (q->QuestionCallback) + q->QuestionCallback(m, q, &rr->resrec, AddRecord); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + } + +// CacheRecordAdd is only called from mDNSCoreReceiveResponse, *never* directly as a result of a client API call. +// If new questions are created as a result of invoking client callbacks, they will be added to +// the end of the question list, and m->NewQuestions will be set to indicate the first new question. +// rr is a new CacheRecord just received into our cache +// (kDNSRecordTypePacketAns/PacketAnsUnique/PacketAdd/PacketAddUnique). +// NOTE: CacheRecordAdd calls AnswerQuestionWithResourceRecord which can call a user callback, +// which may change the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSlocal void CacheRecordAdd(mDNS *const m, CacheRecord *rr) + { + if (m->CurrentQuestion) LogMsg("CacheRecordAdd ERROR m->CurrentQuestion already set"); + m->CurrentQuestion = m->Questions; + while (m->CurrentQuestion && m->CurrentQuestion != m->NewQuestions) + { + DNSQuestion *q = m->CurrentQuestion; + m->CurrentQuestion = q->next; + if (ResourceRecordAnswersQuestion(&rr->resrec, q)) + { + // If this question is one that's actively sending queries, and it's received ten answers within one second of sending the last + // query packet, then that indicates some radical network topology change, so reset its exponential backoff back to the start. + // We must be at least at the eight-second interval to do this. If we're at the four-second interval, or less, + // there's not much benefit accelerating because we will anyway send another query within a few seconds. + // The first reset query is sent out randomized over the next four seconds to reduce possible synchronization between machines. + if (ActiveQuestion(q) && ++q->RecentAnswers >= 10 && + q->ThisQInterval > InitialQuestionInterval*16 && m->timenow - q->LastQTxTime < mDNSPlatformOneSecond) + { + LogMsg("CacheRecordAdd: %##s (%s) got immediate answer burst; restarting exponential backoff sequence", + q->qname.c, DNSTypeName(q->qtype)); + q->LastQTime = m->timenow - InitialQuestionInterval + (mDNSs32)mDNSRandom((mDNSu32)mDNSPlatformOneSecond*4); + q->ThisQInterval = InitialQuestionInterval; + SetNextQueryTime(m,q); + } + verbosedebugf("CacheRecordAdd %p %##s (%s) %lu", rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), rr->resrec.rroriginalttl); + q->CurrentAnswers++; + if (rr->resrec.rdlength > SmallRecordLimit) q->LargeAnswers++; + if (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) q->UniqueAnswers++; + AnswerQuestionWithResourceRecord(m, q, rr, mDNStrue); + // MUST NOT dereference q again after calling AnswerQuestionWithResourceRecord() + } + } + m->CurrentQuestion = mDNSNULL; + } + +// CacheRecordRmv is only called from CheckCacheExpiration, which is called from mDNS_Execute +// If new questions are created as a result of invoking client callbacks, they will be added to +// the end of the question list, and m->NewQuestions will be set to indicate the first new question. +// rr is an existing cache CacheRecord that just expired and is being deleted +// (kDNSRecordTypePacketAns/PacketAnsUnique/PacketAdd/PacketAddUnique). +// NOTE: CacheRecordRmv calls AnswerQuestionWithResourceRecord which can call a user callback, +// which may change the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSlocal void CacheRecordRmv(mDNS *const m, CacheRecord *rr) + { + if (m->CurrentQuestion) LogMsg("CacheRecordRmv ERROR m->CurrentQuestion already set"); + m->CurrentQuestion = m->Questions; + while (m->CurrentQuestion && m->CurrentQuestion != m->NewQuestions) + { + DNSQuestion *q = m->CurrentQuestion; + m->CurrentQuestion = q->next; + if (ResourceRecordAnswersQuestion(&rr->resrec, q)) + { + verbosedebugf("CacheRecordRmv %p %##s (%s)", rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + if (q->CurrentAnswers == 0) + LogMsg("CacheRecordRmv ERROR: How can CurrentAnswers already be zero for %p %##s (%s)?", q, q->qname.c, DNSTypeName(q->qtype)); + else + { + q->CurrentAnswers--; + if (rr->resrec.rdlength > SmallRecordLimit) q->LargeAnswers--; + if (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) q->UniqueAnswers--; + } + if (q->CurrentAnswers == 0) + { + debugf("CacheRecordRmv: Zero current answers for %##s (%s); will reconfirm antecedents", q->qname.c, DNSTypeName(q->qtype)); + ReconfirmAntecedents(m, q); + } + AnswerQuestionWithResourceRecord(m, q, rr, mDNSfalse); + // MUST NOT dereference q again after calling AnswerQuestionWithResourceRecord() + } + } + m->CurrentQuestion = mDNSNULL; + } + +mDNSlocal void ReleaseCacheRR(mDNS *const m, CacheRecord *r) + { + if (r->resrec.rdata && r->resrec.rdata != (RData*)&r->rdatastorage) + mDNSPlatformMemFree(r->resrec.rdata); + r->resrec.rdata = mDNSNULL; + r->next = m->rrcache_free; + m->rrcache_free = r; + m->rrcache_totalused--; + } + +mDNSlocal void CheckCacheExpiration(mDNS *const m, mDNSu32 slot) + { + CacheRecord **rp = &(m->rrcache_hash[slot]); + + if (m->lock_rrcache) { LogMsg("CheckCacheExpiration ERROR! Cache already locked!"); return; } + m->lock_rrcache = 1; + + while (*rp) + { + CacheRecord *const rr = *rp; + mDNSs32 event = RRExpireTime(rr); + if (m->timenow - event >= 0) // If expired, delete it + { + *rp = rr->next; // Cut it from the list + verbosedebugf("CheckCacheExpiration: Deleting %s", GetRRDisplayString(m, rr)); + if (rr->CRActiveQuestion) // If this record has one or more active questions, tell them it's going away + { + CacheRecordRmv(m, rr); + m->rrcache_active--; + } + m->rrcache_used[slot]--; + ReleaseCacheRR(m, rr); + } + else // else, not expired; see if we need to query + { + if (rr->CRActiveQuestion && rr->UnansweredQueries < MaxUnansweredQueries) + { + if (m->timenow - rr->NextRequiredQuery < 0) // If not yet time for next query + event = rr->NextRequiredQuery; // then just record when we want the next query + else // else trigger our question to go out now + { + // Set NextScheduledQuery to timenow so that SendQueries() will run. + // SendQueries() will see that we have records close to expiration, and send FEQs for them. + m->NextScheduledQuery = m->timenow; + // After sending the query we'll increment UnansweredQueries and call SetNextCacheCheckTime(), + // which will correctly update m->NextCacheCheck for us + event = m->timenow + 0x3FFFFFFF; + } + } + if (m->NextCacheCheck - (event + CacheCheckGracePeriod(rr)) > 0) + m->NextCacheCheck = (event + CacheCheckGracePeriod(rr)); + rp = &rr->next; + } + } + if (m->rrcache_tail[slot] != rp) debugf("CheckCacheExpiration: Updating m->rrcache_tail[%d] from %p to %p", slot, m->rrcache_tail[slot], rp); + m->rrcache_tail[slot] = rp; + m->lock_rrcache = 0; + } + +mDNSlocal void AnswerNewQuestion(mDNS *const m) + { + mDNSBool ShouldQueryImmediately = mDNStrue; + CacheRecord *rr; + DNSQuestion *q = m->NewQuestions; // Grab the question we're going to answer + mDNSu32 slot = HashSlot(&q->qname); + + verbosedebugf("AnswerNewQuestion: Answering %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); + + CheckCacheExpiration(m, slot); + m->NewQuestions = q->next; // Advance NewQuestions to the next *after* calling CheckCacheExpiration(); + + if (m->lock_rrcache) LogMsg("AnswerNewQuestion ERROR! Cache already locked!"); + // This should be safe, because calling the client's question callback may cause the + // question list to be modified, but should not ever cause the rrcache list to be modified. + // If the client's question callback deletes the question, then m->CurrentQuestion will + // be advanced, and we'll exit out of the loop + m->lock_rrcache = 1; + if (m->CurrentQuestion) LogMsg("AnswerNewQuestion ERROR m->CurrentQuestion already set"); + m->CurrentQuestion = q; // Indicate which question we're answering, so we'll know if it gets deleted + for (rr=m->rrcache_hash[slot]; rr; rr=rr->next) + if (ResourceRecordAnswersQuestion(&rr->resrec, q)) + { + // SecsSinceRcvd is whole number of elapsed seconds, rounded down + mDNSu32 SecsSinceRcvd = ((mDNSu32)(m->timenow - rr->TimeRcvd)) / mDNSPlatformOneSecond; + if (rr->resrec.rroriginalttl <= SecsSinceRcvd) + { + LogMsg("AnswerNewQuestion: How is rr->resrec.rroriginalttl %lu <= SecsSinceRcvd %lu for %##s (%s)", + rr->resrec.rroriginalttl, SecsSinceRcvd, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + continue; // Go to next one in loop + } + + // If this record set is marked unique, then that means we can reasonably assume we have the whole set + // -- we don't need to rush out on the network and query immediately to see if there are more answers out there + if (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) ShouldQueryImmediately = mDNSfalse; + q->CurrentAnswers++; + if (rr->resrec.rdlength > SmallRecordLimit) q->LargeAnswers++; + if (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) q->UniqueAnswers++; + AnswerQuestionWithResourceRecord(m, q, rr, mDNStrue); + // MUST NOT dereference q again after calling AnswerQuestionWithResourceRecord() + if (m->CurrentQuestion != q) break; // If callback deleted q, then we're finished here + } + else if (RRTypeIsAddressType(rr->resrec.rrtype) && RRTypeIsAddressType(q->qtype)) + if (rr->resrec.namehash == q->qnamehash && SameDomainName(&rr->resrec.name, &q->qname)) + ShouldQueryImmediately = mDNSfalse; + + if (ShouldQueryImmediately && m->CurrentQuestion == q) + { + q->ThisQInterval = InitialQuestionInterval; + q->LastQTime = m->timenow - q->ThisQInterval; + m->NextScheduledQuery = m->timenow; + } + m->CurrentQuestion = mDNSNULL; + m->lock_rrcache = 0; + } + +mDNSlocal void AnswerLocalOnlyQuestionWithResourceRecord(mDNS *const m, DNSQuestion *q, AuthRecord *rr, mDNSBool AddRecord) + { + // Indicate that we've given at least one positive answer for this record, so we should be prepared to send a goodbye for it + if (AddRecord) rr->AnnounceCount = InitialAnnounceCount - 1; + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + if (q->QuestionCallback) + q->QuestionCallback(m, q, &rr->resrec, AddRecord); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + } + +mDNSlocal void AnswerNewLocalOnlyQuestion(mDNS *const m) + { + DNSQuestion *q = m->NewLocalOnlyQuestions; // Grab the question we're going to answer + m->NewLocalOnlyQuestions = q->next; // Advance NewQuestions to the next (if any) + + debugf("AnswerNewLocalOnlyQuestion: Answering %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); + + if (m->CurrentQuestion) LogMsg("AnswerNewQuestion ERROR m->CurrentQuestion already set"); + m->CurrentQuestion = q; // Indicate which question we're answering, so we'll know if it gets deleted + + m->CurrentRecord = m->LocalOnlyRecords; + while (m->CurrentRecord && m->CurrentRecord != m->NewLocalOnlyRecords) + { + AuthRecord *rr = m->CurrentRecord; + m->CurrentRecord = rr->next; + if (ResourceRecordAnswersQuestion(&rr->resrec, q)) + { + AnswerLocalOnlyQuestionWithResourceRecord(m, q, rr, mDNStrue); + // MUST NOT dereference q again after calling AnswerLocalOnlyQuestionWithResourceRecord() + if (m->CurrentQuestion != q) break; // If callback deleted q, then we're finished here + } + } + + m->CurrentQuestion = mDNSNULL; + } + +mDNSlocal void AnswerLocalOnlyQuestions(mDNS *const m, AuthRecord *rr, mDNSBool AddRecord) + { + if (m->CurrentQuestion) LogMsg("AnswerLocalOnlyQuestions ERROR m->CurrentQuestion already set"); + m->CurrentQuestion = m->LocalOnlyQuestions; + while (m->CurrentQuestion && m->CurrentQuestion != m->NewLocalOnlyQuestions) + { + DNSQuestion *q = m->CurrentQuestion; + m->CurrentQuestion = q->next; + if (ResourceRecordAnswersQuestion(&rr->resrec, q)) + { + debugf("AnswerLocalOnlyQuestions %p %##s (%s) %lu", rr, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), rr->resrec.rroriginalttl); + AnswerLocalOnlyQuestionWithResourceRecord(m, q, rr, AddRecord); + // MUST NOT dereference q again after calling AnswerQuestionWithResourceRecord() + } + } + m->CurrentQuestion = mDNSNULL; + } + +mDNSlocal void DiscardLocalOnlyRecords(mDNS *const m) + { + AuthRecord *rr = m->LocalOnlyRecords; + while (rr) + { + if (rr->resrec.RecordType == kDNSRecordTypeDeregistering) + { AnswerLocalOnlyQuestions(m, rr, mDNSfalse); CompleteDeregistration(m, rr); return; } + if (rr->ProbeCount) { mDNS_Deregister_internal(m, rr, mDNS_Dereg_conflict); return; } + rr=rr->next; + } + m->DiscardLocalOnlyRecords = mDNSfalse; + } + +mDNSlocal void AnswerForNewLocalOnlyRecords(mDNS *const m) + { + AuthRecord *rr = m->NewLocalOnlyRecords; + m->NewLocalOnlyRecords = m->NewLocalOnlyRecords->next; + AnswerLocalOnlyQuestions(m, rr, mDNStrue); + } + +mDNSlocal CacheRecord *GetFreeCacheRR(mDNS *const m, mDNSu16 RDLength) + { + CacheRecord *r = mDNSNULL; + + if (m->lock_rrcache) { LogMsg("GetFreeCacheRR ERROR! Cache already locked!"); return(mDNSNULL); } + m->lock_rrcache = 1; + + // If we have no free records, ask the client layer to give us some more memory + if (!m->rrcache_free && m->MainCallback) + { + if (m->rrcache_totalused != m->rrcache_size) + LogMsg("GetFreeCacheRR: count mismatch: m->rrcache_totalused %lu != m->rrcache_size %lu", + m->rrcache_totalused, m->rrcache_size); + + // We don't want to be vulnerable to a malicious attacker flooding us with an infinite + // number of bogus records so that we keep growing our cache until the machine runs out of memory. + // To guard against this, if we're actively using less than 1/32 of our cache, then we + // purge all the unused records and recycle them, instead of allocating more memory. + if (m->rrcache_size >= 512 && m->rrcache_size / 32 > m->rrcache_active) + debugf("Possible denial-of-service attack in progress: m->rrcache_size %lu; m->rrcache_active %lu", + m->rrcache_size, m->rrcache_active); + else + m->MainCallback(m, mStatus_GrowCache); + } + + // If we still have no free records, recycle all the records we can. + // Enumerating the entire cache is moderately expensive, so when we do it, we reclaim all the records we can in one pass. + if (!m->rrcache_free) + { + #if MDNS_DEBUGMSGS + mDNSu32 oldtotalused = m->rrcache_totalused; + #endif + mDNSu32 slot; + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) + { + CacheRecord **rp = &(m->rrcache_hash[slot]); + while (*rp) + { + // Records that answer still-active questions are not candidates for deletion + if ((*rp)->CRActiveQuestion) + rp=&(*rp)->next; + else + { + CacheRecord *rr = *rp; + *rp = (*rp)->next; // Cut record from list + m->rrcache_used[slot]--; // Decrement counts + ReleaseCacheRR(m, rr); + } + } + if (m->rrcache_tail[slot] != rp) debugf("GetFreeCacheRR: Updating m->rrcache_tail[%d] from %p to %p", slot, m->rrcache_tail[slot], rp); + m->rrcache_tail[slot] = rp; + } + #if MDNS_DEBUGMSGS + debugf("Clear unused records; m->rrcache_totalused was %lu; now %lu", oldtotalused, m->rrcache_totalused); + #endif + } + + if (m->rrcache_free) // If there are records in the free list, take one + { + r = m->rrcache_free; + m->rrcache_free = r->next; + } + + if (r) + { + if (++m->rrcache_totalused >= m->rrcache_report) + { + debugf("RR Cache now using %ld records", m->rrcache_totalused); + if (m->rrcache_report < 100) m->rrcache_report += 10; + else m->rrcache_report += 100; + } + mDNSPlatformMemZero(r, sizeof(*r)); + r->resrec.rdata = (RData*)&r->rdatastorage; // By default, assume we're usually going to be using local storage + + if (RDLength > InlineCacheRDSize) // If RDLength is too big, allocate extra storage + { + r->resrec.rdata = (RData*)mDNSPlatformMemAllocate(sizeofRDataHeader + RDLength); + if (r->resrec.rdata) r->resrec.rdata->MaxRDLength = r->resrec.rdlength = RDLength; + else { ReleaseCacheRR(m, r); r = mDNSNULL; } + } + } + + m->lock_rrcache = 0; + + return(r); + } + +mDNSlocal void PurgeCacheResourceRecord(mDNS *const m, CacheRecord *rr) + { + // Make sure we mark this record as thoroughly expired -- we don't ever want to give + // a positive answer using an expired record (e.g. from an interface that has gone away). + // We don't want to clear CRActiveQuestion here, because that would leave the record subject to + // summary deletion without giving the proper callback to any questions that are monitoring it. + // By setting UnansweredQueries to MaxUnansweredQueries we ensure it won't trigger any further expiration queries. + rr->TimeRcvd = m->timenow - mDNSPlatformOneSecond * 60; + rr->UnansweredQueries = MaxUnansweredQueries; + rr->resrec.rroriginalttl = 0; + SetNextCacheCheckTime(m, rr); + } + +mDNSlocal void mDNS_Lock(mDNS *const m) + { + // MUST grab the platform lock FIRST! + mDNSPlatformLock(m); + + // Normally, mDNS_reentrancy is zero and so is mDNS_busy + // However, when we call a client callback mDNS_busy is one, and we increment mDNS_reentrancy too + // If that client callback does mDNS API calls, mDNS_reentrancy and mDNS_busy will both be one + // If mDNS_busy != mDNS_reentrancy that's a bad sign + if (m->mDNS_busy != m->mDNS_reentrancy) + LogMsg("mDNS_Lock: Locking failure! mDNS_busy (%ld) != mDNS_reentrancy (%ld)", m->mDNS_busy, m->mDNS_reentrancy); + + // If this is an initial entry into the mDNSCore code, set m->timenow + // else, if this is a re-entrant entry into the mDNSCore code, m->timenow should already be set + if (m->mDNS_busy == 0) + { + if (m->timenow) + LogMsg("mDNS_Lock: m->timenow already set (%ld/%ld)", m->timenow, mDNSPlatformTimeNow() + m->timenow_adjust); + m->timenow = mDNSPlatformTimeNow() + m->timenow_adjust; + if (m->timenow == 0) m->timenow = 1; + } + else if (m->timenow == 0) + { + LogMsg("mDNS_Lock: m->mDNS_busy is %ld but m->timenow not set", m->mDNS_busy); + m->timenow = mDNSPlatformTimeNow() + m->timenow_adjust; + if (m->timenow == 0) m->timenow = 1; + } + + if (m->timenow_last - m->timenow > 0) + { + m->timenow_adjust += m->timenow_last - m->timenow; + LogMsg("mDNSPlatformTimeNow went backwards by %ld ticks; setting correction factor to %ld", m->timenow_last - m->timenow, m->timenow_adjust); + m->timenow = m->timenow_last; + } + m->timenow_last = m->timenow; + + // Increment mDNS_busy so we'll recognise re-entrant calls + m->mDNS_busy++; + } + +mDNSlocal mDNSs32 GetNextScheduledEvent(const mDNS *const m) + { + mDNSs32 e = m->timenow + 0x78000000; + if (m->mDNSPlatformStatus != mStatus_NoError || m->SleepState) return(e); + if (m->NewQuestions) return(m->timenow); + if (m->NewLocalOnlyQuestions) return(m->timenow); + if (m->NewLocalOnlyRecords) return(m->timenow); + if (m->DiscardLocalOnlyRecords) return(m->timenow); + if (m->SuppressSending) return(m->SuppressSending); + if (e - m->NextCacheCheck > 0) e = m->NextCacheCheck; + if (e - m->NextScheduledQuery > 0) e = m->NextScheduledQuery; + if (e - m->NextScheduledProbe > 0) e = m->NextScheduledProbe; + if (e - m->NextScheduledResponse > 0) e = m->NextScheduledResponse; + return(e); + } + +mDNSlocal void mDNS_Unlock(mDNS *const m) + { + // Decrement mDNS_busy + m->mDNS_busy--; + + // Check for locking failures + if (m->mDNS_busy != m->mDNS_reentrancy) + LogMsg("mDNS_Unlock: Locking failure! mDNS_busy (%ld) != mDNS_reentrancy (%ld)", m->mDNS_busy, m->mDNS_reentrancy); + + // If this is a final exit from the mDNSCore code, set m->NextScheduledEvent and clear m->timenow + if (m->mDNS_busy == 0) + { + m->NextScheduledEvent = GetNextScheduledEvent(m); + if (m->timenow == 0) LogMsg("mDNS_Unlock: ERROR! m->timenow aready zero"); + m->timenow = 0; + } + + // MUST release the platform lock LAST! + mDNSPlatformUnlock(m); + } + +mDNSexport mDNSs32 mDNS_Execute(mDNS *const m) + { + mDNS_Lock(m); // Must grab lock before trying to read m->timenow + + if (m->timenow - m->NextScheduledEvent >= 0) + { + int i; + + verbosedebugf("mDNS_Execute"); + if (m->CurrentQuestion) LogMsg("mDNS_Execute: ERROR! m->CurrentQuestion already set"); + + // 1. If we're past the probe suppression time, we can clear it + if (m->SuppressProbes && m->timenow - m->SuppressProbes >= 0) m->SuppressProbes = 0; + + // 2. If it's been more than ten seconds since the last probe failure, we can clear the counter + if (m->NumFailedProbes && m->timenow - m->ProbeFailTime >= mDNSPlatformOneSecond * 10) m->NumFailedProbes = 0; + + // 3. Purge our cache of stale old records + if (m->rrcache_size && m->timenow - m->NextCacheCheck >= 0) + { + mDNSu32 slot; + m->NextCacheCheck = m->timenow + 0x3FFFFFFF; + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) CheckCacheExpiration(m, slot); + } + + // 4. See if we can answer any of our new local questions from the cache + for (i=0; m->NewQuestions && i<1000; i++) AnswerNewQuestion(m); + if (i >= 1000) debugf("mDNS_Execute: AnswerNewQuestion exceeded loop limit"); + + for (i=0; m->DiscardLocalOnlyRecords && i<1000; i++) DiscardLocalOnlyRecords(m); + if (i >= 1000) debugf("mDNS_Execute: DiscardLocalOnlyRecords exceeded loop limit"); + + for (i=0; m->NewLocalOnlyQuestions && i<1000; i++) AnswerNewLocalOnlyQuestion(m); + if (i >= 1000) debugf("mDNS_Execute: AnswerNewLocalOnlyQuestion exceeded loop limit"); + + for (i=0; m->NewLocalOnlyRecords && i<1000; i++) AnswerForNewLocalOnlyRecords(m); + if (i >= 1000) debugf("mDNS_Execute: AnswerLocalOnlyQuestions exceeded loop limit"); + + // 5. See what packets we need to send + if (m->mDNSPlatformStatus != mStatus_NoError || m->SleepState) DiscardDeregistrations(m); + else if (m->SuppressSending == 0 || m->timenow - m->SuppressSending >= 0) + { + // If the platform code is ready, and we're not suppressing packet generation right now + // then send our responses, probes, and questions. + // We check the cache first, because there might be records close to expiring that trigger questions to refresh them + // We send queries next, because there might be final-stage probes that complete their probing here, causing + // them to advance to announcing state, and we want those to be included in any announcements we send out. + // Finally, we send responses, including the previously mentioned records that just completed probing + m->SuppressSending = 0; + + // 6. Send Query packets. This may cause some probing records to advance to announcing state + if (m->timenow - m->NextScheduledQuery >= 0 || m->timenow - m->NextScheduledProbe >= 0) SendQueries(m); + if (m->timenow - m->NextScheduledQuery >= 0) + { + LogMsg("mDNS_Execute: SendQueries didn't send all its queries; will try again in one second"); + m->NextScheduledQuery = m->timenow + mDNSPlatformOneSecond; + } + if (m->timenow - m->NextScheduledProbe >= 0) + { + LogMsg("mDNS_Execute: SendQueries didn't send all its probes; will try again in one second"); + m->NextScheduledProbe = m->timenow + mDNSPlatformOneSecond; + } + + // 7. Send Response packets, including probing records just advanced to announcing state + if (m->timenow - m->NextScheduledResponse >= 0) SendResponses(m); + if (m->timenow - m->NextScheduledResponse >= 0) + { + LogMsg("mDNS_Execute: SendResponses didn't send all its responses; will try again in one second"); + m->NextScheduledResponse = m->timenow + mDNSPlatformOneSecond; + } + } + + m->RandomQueryDelay = 0; // Clear m->RandomQueryDelay, ready to pick a new different value, when necessary + } + + // Note about multi-threaded systems: + // On a multi-threaded system, some other thread could run right after the mDNS_Unlock(), + // performing mDNS API operations that change our next scheduled event time. + // + // On multi-threaded systems (like the current Windows implementation) that have a single main thread + // calling mDNS_Execute() (and other threads allowed to call mDNS API routines) it is the responsibility + // of the mDNSPlatformUnlock() routine to signal some kind of stateful condition variable that will + // signal whatever blocking primitive the main thread is using, so that it will wake up and execute one + // more iteration of its loop, and immediately call mDNS_Execute() again. The signal has to be stateful + // in the sense that if the main thread has not yet entered its blocking primitive, then as soon as it + // does, the state of the signal will be noticed, causing the blocking primitive to return immediately + // without blocking. This avoids the race condition between the signal from the other thread arriving + // just *before* or just *after* the main thread enters the blocking primitive. + // + // On multi-threaded systems (like the current Mac OS 9 implementation) that are entirely timer-driven, + // with no main mDNS_Execute() thread, it is the responsibility of the mDNSPlatformUnlock() routine to + // set the timer according to the m->NextScheduledEvent value, and then when the timer fires, the timer + // callback function should call mDNS_Execute() (and ignore the return value, which may already be stale + // by the time it gets to the timer callback function). + + mDNS_Unlock(m); // Calling mDNS_Unlock is what gives m->NextScheduledEvent its new value + return(m->NextScheduledEvent); + } + +// Call mDNSCoreMachineSleep(m, mDNStrue) when the machine is about to go to sleep. +// Call mDNSCoreMachineSleep(m, mDNSfalse) when the machine is has just woken up. +// Normally, the platform support layer below mDNSCore should call this, not the client layer above. +// Note that sleep/wake calls do not have to be paired one-for-one; it is acceptable to call +// mDNSCoreMachineSleep(m, mDNSfalse) any time there is reason to believe that the machine may have just +// found itself in a new network environment. For example, if the Ethernet hardware indicates that the +// cable has just been connected, the platform support layer should call mDNSCoreMachineSleep(m, mDNSfalse) +// to make mDNSCore re-issue its outstanding queries, probe for record uniqueness, etc. +// While it is safe to call mDNSCoreMachineSleep(m, mDNSfalse) at any time, it does cause extra network +// traffic, so it should only be called when there is legitimate reason to believe the machine +// may have become attached to a new network. +mDNSexport void mDNSCoreMachineSleep(mDNS *const m, mDNSBool sleepstate) + { + AuthRecord *rr; + + mDNS_Lock(m); + + m->SleepState = sleepstate; + LogMsg("mDNSResponder %s at %ld", sleepstate ? "Sleeping" : "Waking", m->timenow); + + if (sleepstate) + { + // Mark all the records we need to deregister and send them + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->resrec.RecordType == kDNSRecordTypeShared && rr->AnnounceCount < InitialAnnounceCount) + rr->ImmedAnswer = mDNSInterfaceMark; + SendResponses(m); + } + else + { + DNSQuestion *q; + mDNSu32 slot; + CacheRecord *cr; + + // 1. Retrigger all our questions + for (q = m->Questions; q; q=q->next) // Scan our list of questions + if (ActiveQuestion(q)) + { + q->ThisQInterval = InitialQuestionInterval; // MUST be > zero for an active question + q->LastQTime = m->timenow - q->ThisQInterval; + q->RecentAnswers = 0; + ExpireDupSuppressInfo(q->DupSuppress, m->timenow); + m->NextScheduledQuery = m->timenow; + } + + // 2. Re-validate our cache records + m->NextCacheCheck = m->timenow; + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) + for (cr = m->rrcache_hash[slot]; cr; cr=cr->next) + mDNS_Reconfirm_internal(m, cr, kDefaultReconfirmTimeForCableDisconnect); + + // 3. Retrigger probing and announcing for all our authoritative records + for (rr = m->ResourceRecords; rr; rr=rr->next) + { + if (rr->resrec.RecordType == kDNSRecordTypeVerified && !rr->DependentOn) rr->resrec.RecordType = kDNSRecordTypeUnique; + rr->ProbeCount = DefaultProbeCountForRecordType(rr->resrec.RecordType); + if (rr->AnnounceCount < ReannounceCount) + rr->AnnounceCount = ReannounceCount; + rr->ThisAPInterval = DefaultAPIntervalForRecordType(rr->resrec.RecordType); + InitializeLastAPTime(m, rr); + } + + } + + mDNS_Unlock(m); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Packet Reception Functions +#endif + +mDNSlocal void AddRecordToResponseList(AuthRecord ***nrpp, AuthRecord *rr, AuthRecord *add) + { + if (rr->NextResponse == mDNSNULL && *nrpp != &rr->NextResponse) + { + **nrpp = rr; + // NR_AdditionalTo must point to a record with NR_AnswerTo set (and not NR_AdditionalTo) + // If 'add' does not meet this requirement, then follow its NR_AdditionalTo pointer to a record that does + // The referenced record will definitely be acceptable (by recursive application of this rule) + if (add && add->NR_AdditionalTo) add = add->NR_AdditionalTo; + rr->NR_AdditionalTo = add; + *nrpp = &rr->NextResponse; + } + debugf("AddRecordToResponseList: %##s (%s) already in list", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + } + +#define MustSendRecord(RR) ((RR)->NR_AnswerTo || (RR)->NR_AdditionalTo) + +mDNSlocal mDNSu8 *GenerateUnicastResponse(const DNSMessage *const query, const mDNSu8 *const end, + const mDNSInterfaceID InterfaceID, mDNSBool LegacyQuery, DNSMessage *const response, AuthRecord *ResponseRecords) + { + mDNSu8 *responseptr = response->data; + const mDNSu8 *const limit = response->data + sizeof(response->data); + const mDNSu8 *ptr = query->data; + AuthRecord *rr; + mDNSu32 maxttl = 0x70000000; + int i; + + // Initialize the response fields so we can answer the questions + InitializeDNSMessage(&response->h, query->h.id, ResponseFlags); + + // *** + // *** 1. Write out the list of questions we are actually going to answer with this packet + // *** + if (LegacyQuery) + { + maxttl = 10; + for (i=0; ih.numQuestions; i++) // For each question... + { + DNSQuestion q; + ptr = getQuestion(query, ptr, end, InterfaceID, &q); // get the question... + if (!ptr) return(mDNSNULL); + + for (rr=ResponseRecords; rr; rr=rr->NextResponse) // and search our list of proposed answers + { + if (rr->NR_AnswerTo == ptr) // If we're going to generate a record answering this question + { // then put the question in the question section + responseptr = putQuestion(response, responseptr, limit, &q.qname, q.qtype, q.qclass); + if (!responseptr) { debugf("GenerateUnicastResponse: Ran out of space for questions!"); return(mDNSNULL); } + break; // break out of the ResponseRecords loop, and go on to the next question + } + } + } + + if (response->h.numQuestions == 0) { LogMsg("GenerateUnicastResponse: ERROR! Why no questions?"); return(mDNSNULL); } + } + + // *** + // *** 2. Write Answers + // *** + for (rr=ResponseRecords; rr; rr=rr->NextResponse) + if (rr->NR_AnswerTo) + { + mDNSu8 *p = PutResourceRecordCappedTTL(response, responseptr, &response->h.numAnswers, &rr->resrec, maxttl); + if (p) responseptr = p; + else { debugf("GenerateUnicastResponse: Ran out of space for answers!"); response->h.flags.b[0] |= kDNSFlag0_TC; } + } + + // *** + // *** 3. Write Additionals + // *** + for (rr=ResponseRecords; rr; rr=rr->NextResponse) + if (rr->NR_AdditionalTo && !rr->NR_AnswerTo) + { + mDNSu8 *p = PutResourceRecordCappedTTL(response, responseptr, &response->h.numAdditionals, &rr->resrec, maxttl); + if (p) responseptr = p; + else debugf("GenerateUnicastResponse: No more space for additionals"); + } + + return(responseptr); + } + +// AuthRecord *our is our Resource Record +// CacheRecord *pkt is the Resource Record from the response packet we've witnessed on the network +// Returns 0 if there is no conflict +// Returns +1 if there was a conflict and we won +// Returns -1 if there was a conflict and we lost and have to rename +mDNSlocal int CompareRData(AuthRecord *our, CacheRecord *pkt) + { + mDNSu8 ourdata[256], *ourptr = ourdata, *ourend; + mDNSu8 pktdata[256], *pktptr = pktdata, *pktend; + if (!our) { LogMsg("CompareRData ERROR: our is NULL"); return(+1); } + if (!pkt) { LogMsg("CompareRData ERROR: pkt is NULL"); return(+1); } + + ourend = putRData(mDNSNULL, ourdata, ourdata + sizeof(ourdata), &our->resrec); + pktend = putRData(mDNSNULL, pktdata, pktdata + sizeof(pktdata), &pkt->resrec); + while (ourptr < ourend && pktptr < pktend && *ourptr == *pktptr) { ourptr++; pktptr++; } + if (ourptr >= ourend && pktptr >= pktend) return(0); // If data identical, not a conflict + + if (ourptr >= ourend) return(-1); // Our data ran out first; We lost + if (pktptr >= pktend) return(+1); // Packet data ran out first; We won + if (*pktptr > *ourptr) return(-1); // Our data is numerically lower; We lost + if (*pktptr < *ourptr) return(+1); // Packet data is numerically lower; We won + + debugf("CompareRData: How did we get here?"); + return(-1); + } + +// See if we have an authoritative record that's identical to this packet record, +// whose canonical DependentOn record is the specified master record. +// The DependentOn pointer is typically used for the TXT record of service registrations +// It indicates that there is no inherent conflict detection for the TXT record +// -- it depends on the SRV record to resolve name conflicts +// If we find any identical ResourceRecords in our authoritative list, then follow their DependentOn +// pointer chain (if any) to make sure we reach the canonical DependentOn record +// If the record has no DependentOn, then just return that record's pointer +// Returns NULL if we don't have any local RRs that are identical to the one from the packet +mDNSlocal mDNSBool MatchDependentOn(const mDNS *const m, const CacheRecord *const pktrr, const AuthRecord *const master) + { + const AuthRecord *r1; + for (r1 = m->ResourceRecords; r1; r1=r1->next) + { + if (IdenticalResourceRecord(&r1->resrec, &pktrr->resrec)) + { + const AuthRecord *r2 = r1; + while (r2->DependentOn) r2 = r2->DependentOn; + if (r2 == master) return(mDNStrue); + } + } + for (r1 = m->DuplicateRecords; r1; r1=r1->next) + { + if (IdenticalResourceRecord(&r1->resrec, &pktrr->resrec)) + { + const AuthRecord *r2 = r1; + while (r2->DependentOn) r2 = r2->DependentOn; + if (r2 == master) return(mDNStrue); + } + } + return(mDNSfalse); + } + +// Find the canonical RRSet pointer for this RR received in a packet. +// If we find any identical AuthRecord in our authoritative list, then follow its RRSet +// pointers (if any) to make sure we return the canonical member of this name/type/class +// Returns NULL if we don't have any local RRs that are identical to the one from the packet +mDNSlocal const AuthRecord *FindRRSet(const mDNS *const m, const CacheRecord *const pktrr) + { + const AuthRecord *rr; + for (rr = m->ResourceRecords; rr; rr=rr->next) + { + if (IdenticalResourceRecord(&rr->resrec, &pktrr->resrec)) + { + while (rr->RRSet && rr != rr->RRSet) rr = rr->RRSet; + return(rr); + } + } + return(mDNSNULL); + } + +// PacketRRConflict is called when we've received an RR (pktrr) which has the same name +// as one of our records (our) but different rdata. +// 1. If our record is not a type that's supposed to be unique, we don't care. +// 2a. If our record is marked as dependent on some other record for conflict detection, ignore this one. +// 2b. If the packet rr exactly matches one of our other RRs, and *that* record's DependentOn pointer +// points to our record, ignore this conflict (e.g. the packet record matches one of our +// TXT records, and that record is marked as dependent on 'our', its SRV record). +// 3. If we have some *other* RR that exactly matches the one from the packet, and that record and our record +// are members of the same RRSet, then this is not a conflict. +mDNSlocal mDNSBool PacketRRConflict(const mDNS *const m, const AuthRecord *const our, const CacheRecord *const pktrr) + { + const AuthRecord *ourset = our->RRSet ? our->RRSet : our; + + // If not supposed to be unique, not a conflict + if (!(our->resrec.RecordType & kDNSRecordTypeUniqueMask)) return(mDNSfalse); + + // If a dependent record, not a conflict + if (our->DependentOn || MatchDependentOn(m, pktrr, our)) return(mDNSfalse); + + // If the pktrr matches a member of ourset, not a conflict + if (FindRRSet(m, pktrr) == ourset) return(mDNSfalse); + + // Okay, this is a conflict + return(mDNStrue); + } + +// NOTE: ResolveSimultaneousProbe calls mDNS_Deregister_internal which can call a user callback, which may change +// the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSlocal void ResolveSimultaneousProbe(mDNS *const m, const DNSMessage *const query, const mDNSu8 *const end, + DNSQuestion *q, AuthRecord *our) + { + int i; + const mDNSu8 *ptr = LocateAuthorities(query, end); + mDNSBool FoundUpdate = mDNSfalse; + + for (i = 0; i < query->h.numAuthorities; i++) + { + LargeCacheRecord pkt; + ptr = GetLargeResourceRecord(m, query, ptr, end, q->InterfaceID, 0, &pkt); + if (!ptr) break; + if (ResourceRecordAnswersQuestion(&pkt.r.resrec, q)) + { + FoundUpdate = mDNStrue; + if (PacketRRConflict(m, our, &pkt.r)) + { + int result = (int)our->resrec.rrclass - (int)pkt.r.resrec.rrclass; + if (!result) result = (int)our->resrec.rrtype - (int)pkt.r.resrec.rrtype; + if (!result) result = CompareRData(our, &pkt.r); + switch (result) + { + case 1: debugf("ResolveSimultaneousProbe: %##s (%s): We won", our->resrec.name.c, DNSTypeName(our->resrec.rrtype)); + break; + case 0: break; + case -1: debugf("ResolveSimultaneousProbe: %##s (%s): We lost", our->resrec.name.c, DNSTypeName(our->resrec.rrtype)); + mDNS_Deregister_internal(m, our, mDNS_Dereg_conflict); + return; + } + } + } + } + if (!FoundUpdate) + debugf("ResolveSimultaneousProbe: %##s (%s): No Update Record found", our->resrec.name.c, DNSTypeName(our->resrec.rrtype)); + } + +mDNSlocal CacheRecord *FindIdenticalRecordInCache(const mDNS *const m, ResourceRecord *pktrr) + { + CacheRecord *rr; + for (rr = m->rrcache_hash[HashSlot(&pktrr->name)]; rr; rr=rr->next) + if (pktrr->InterfaceID == rr->resrec.InterfaceID && IdenticalResourceRecord(pktrr, &rr->resrec)) break; + return(rr); + } + +// ProcessQuery examines a received query to see if we have any answers to give +mDNSlocal mDNSu8 *ProcessQuery(mDNS *const m, const DNSMessage *const query, const mDNSu8 *const end, + const mDNSAddr *srcaddr, const mDNSInterfaceID InterfaceID, mDNSBool LegacyQuery, mDNSBool QueryWasMulticast, + DNSMessage *const response) + { + AuthRecord *ResponseRecords = mDNSNULL; + AuthRecord **nrp = &ResponseRecords; + CacheRecord *ExpectedAnswers = mDNSNULL; // Records in our cache we expect to see updated + CacheRecord **eap = &ExpectedAnswers; + DNSQuestion *DupQuestions = mDNSNULL; // Our questions that are identical to questions in this packet + DNSQuestion **dqp = &DupQuestions; + mDNSBool delayresponse = mDNSfalse; + mDNSBool HaveUnicastAnswer = mDNSfalse; + const mDNSu8 *ptr = query->data; + mDNSu8 *responseptr = mDNSNULL; + AuthRecord *rr, *rr2; + int i; + + // If TC flag is set, it means we should expect that additional known answers may be coming in another packet. + if (query->h.flags.b[0] & kDNSFlag0_TC) delayresponse = mDNStrue; + + // *** + // *** 1. Parse Question Section and mark potential answers + // *** + for (i=0; ih.numQuestions; i++) // For each question... + { + mDNSBool QuestionNeedsMulticastResponse; + int NumAnswersForThisQuestion = 0; + DNSQuestion pktq, *q; + ptr = getQuestion(query, ptr, end, InterfaceID, &pktq); // get the question... + if (!ptr) goto exit; + + // The only queries that *need* a multicast response are: + // * Queries sent via multicast + // * from port 5353 + // * that don't have the kDNSQClass_UnicastResponse bit set + // These queries need multicast responses because other clients will: + // * suppress their own identical questions when they see these questions, and + // * expire their cache records if they don't see the expected responses + // For other queries, we may still choose to send the occasional multicast response anyway, + // to keep our neighbours caches warm, and for ongoing conflict detection. + QuestionNeedsMulticastResponse = QueryWasMulticast && !LegacyQuery && !(pktq.qclass & kDNSQClass_UnicastResponse); + // Clear the UnicastResponse flag -- don't want to confuse the rest of the code that follows later + pktq.qclass &= ~kDNSQClass_UnicastResponse; + + // Note: We use the m->CurrentRecord mechanism here because calling ResolveSimultaneousProbe + // can result in user callbacks which may change the record list and/or question list. + // Also note: we just mark potential answer records here, without trying to build the + // "ResponseRecords" list, because we don't want to risk user callbacks deleting records + // from that list while we're in the middle of trying to build it. + if (m->CurrentRecord) LogMsg("ProcessQuery ERROR m->CurrentRecord already set"); + m->CurrentRecord = m->ResourceRecords; + while (m->CurrentRecord) + { + rr = m->CurrentRecord; + m->CurrentRecord = rr->next; + if (ResourceRecordAnswersQuestion(&rr->resrec, &pktq)) + { + if (rr->resrec.RecordType == kDNSRecordTypeUnique) + ResolveSimultaneousProbe(m, query, end, &pktq, rr); + else if (ResourceRecordIsValidAnswer(rr)) + { + NumAnswersForThisQuestion++; + // Notes: + // NR_AnswerTo pointing into query packet means "answer via unicast" + // (may also choose to do multicast as well) + // NR_AnswerTo == ~0 means "definitely answer via multicast" (can't downgrade to unicast later) + if (QuestionNeedsMulticastResponse) + { + // We only mark this question for sending if it is at least one second since the last time we multicast it + // on this interface. If it is more than a second, or LastMCInterface is different, then we should multicast it. + // This is to guard against the case where someone blasts us with queries as fast as they can. + if (m->timenow - (rr->LastMCTime + mDNSPlatformOneSecond) >= 0 || + (rr->LastMCInterface != mDNSInterfaceMark && rr->LastMCInterface != InterfaceID)) + rr->NR_AnswerTo = (mDNSu8*)~0; + } + else if (!rr->NR_AnswerTo) rr->NR_AnswerTo = ptr; + } + } + } + + // We only do the following accelerated cache expiration processing and duplicate question suppression processing + // for multicast queries with multicast responses. + // For any query generating a unicast response we don't do this because we can't assume we will see the response + if (QuestionNeedsMulticastResponse) + { + CacheRecord *rr; + // If we couldn't answer this question, someone else might be able to, + // so use random delay on response to reduce collisions + if (NumAnswersForThisQuestion == 0) delayresponse = mDNStrue; + + // Make a list indicating which of our own cache records we expect to see updated as a result of this query + // Note: Records larger than 1K are not habitually multicast, so don't expect those to be updated + for (rr = m->rrcache_hash[HashSlot(&pktq.qname)]; rr; rr=rr->next) + if (ResourceRecordAnswersQuestion(&rr->resrec, &pktq) && rr->resrec.rdlength <= SmallRecordLimit) + if (!rr->NextInKAList && eap != &rr->NextInKAList) + { + *eap = rr; + eap = &rr->NextInKAList; + if (rr->MPUnansweredQ == 0 || m->timenow - rr->MPLastUnansweredQT >= mDNSPlatformOneSecond) + { + // Although MPUnansweredQ is only really used for multi-packet query processing, + // we increment it for both single-packet and multi-packet queries, so that it stays in sync + // with the MPUnansweredKA value, which by necessity is incremented for both query types. + rr->MPUnansweredQ++; + rr->MPLastUnansweredQT = m->timenow; + rr->MPExpectingKA = mDNStrue; + } + } + + // Check if this question is the same as any of mine. + // We only do this for non-truncated queries. Right now it would be too complicated to try + // to keep track of duplicate suppression state between multiple packets, especially when we + // can't guarantee to receive all of the Known Answer packets that go with a particular query. + if (!(query->h.flags.b[0] & kDNSFlag0_TC)) + for (q = m->Questions; q; q=q->next) + if (ActiveQuestion(q) && m->timenow - q->LastQTxTime > mDNSPlatformOneSecond / 4) + if (!q->InterfaceID || q->InterfaceID == InterfaceID) + if (q->NextInDQList == mDNSNULL && dqp != &q->NextInDQList) + if (q->qtype == pktq.qtype && q->qclass == pktq.qclass && q->qnamehash == pktq.qnamehash && SameDomainName(&q->qname, &pktq.qname)) + { *dqp = q; dqp = &q->NextInDQList; } + } + } + + // *** + // *** 2. Now we can safely build the list of marked answers + // *** + for (rr = m->ResourceRecords; rr; rr=rr->next) // Now build our list of potential answers + if (rr->NR_AnswerTo) // If we marked the record... + { + AddRecordToResponseList(&nrp, rr, mDNSNULL); // ... add it to the list + if (rr->resrec.RecordType == kDNSRecordTypeShared) delayresponse = mDNStrue; + } + + // *** + // *** 3. Add additional records + // *** + for (rr=ResponseRecords; rr; rr=rr->NextResponse) // For each record we plan to put + { + // (Note: This is an "if", not a "while". If we add a record, we'll find it again + // later in the "for" loop, and we will follow further "additional" links then.) + if (rr->Additional1 && ResourceRecordIsValidInterfaceAnswer(rr->Additional1, InterfaceID)) + AddRecordToResponseList(&nrp, rr->Additional1, rr); + + if (rr->Additional2 && ResourceRecordIsValidInterfaceAnswer(rr->Additional2, InterfaceID)) + AddRecordToResponseList(&nrp, rr->Additional2, rr); + + // For SRV records, automatically add the Address record(s) for the target host + if (rr->resrec.rrtype == kDNSType_SRV) + for (rr2=m->ResourceRecords; rr2; rr2=rr2->next) // Scan list of resource records + if (RRIsAddressType(rr2) && // For all address records (A/AAAA) ... + ResourceRecordIsValidInterfaceAnswer(rr2, InterfaceID) && // ... which are valid for answer ... + rr->resrec.rdnamehash == rr2->resrec.namehash && + SameDomainName(&rr->resrec.rdata->u.srv.target, &rr2->resrec.name)) // ... whose name is the name of the SRV target + AddRecordToResponseList(&nrp, rr2, rr); + } + + // *** + // *** 4. Parse Answer Section and cancel any records disallowed by Known-Answer list + // *** + for (i=0; ih.numAnswers; i++) // For each record in the query's answer section... + { + // Get the record... + LargeCacheRecord pkt; + AuthRecord *rr; + CacheRecord *ourcacherr; + ptr = GetLargeResourceRecord(m, query, ptr, end, InterfaceID, kDNSRecordTypePacketAns, &pkt); + if (!ptr) goto exit; + + // See if this Known-Answer suppresses any of our currently planned answers + for (rr=ResponseRecords; rr; rr=rr->NextResponse) + if (MustSendRecord(rr) && ShouldSuppressKnownAnswer(&pkt.r, rr)) + { rr->NR_AnswerTo = mDNSNULL; rr->NR_AdditionalTo = mDNSNULL; } + + // See if this Known-Answer suppresses any previously scheduled answers (for multi-packet KA suppression) + for (rr=m->ResourceRecords; rr; rr=rr->next) + { + // If we're planning to send this answer on this interface, and only on this interface, then allow KA suppression + if (rr->ImmedAnswer == InterfaceID && ShouldSuppressKnownAnswer(&pkt.r, rr)) + { + if (srcaddr->type == mDNSAddrType_IPv4) + { + if (mDNSSameIPv4Address(rr->v4Requester, srcaddr->ip.v4)) rr->v4Requester = zeroIPAddr; + } + else if (srcaddr->type == mDNSAddrType_IPv6) + { + if (mDNSSameIPv6Address(rr->v6Requester, srcaddr->ip.v6)) rr->v6Requester = zerov6Addr; + } + if (mDNSIPv4AddressIsZero(rr->v4Requester) && mDNSIPv6AddressIsZero(rr->v6Requester)) rr->ImmedAnswer = mDNSNULL; + } + } + + // See if this Known-Answer suppresses any answers we were expecting for our cache records. We do this always, + // even if the TC bit is not set (the TC bit will *not* be set in the *last* packet of a multi-packet KA list). + ourcacherr = FindIdenticalRecordInCache(m, &pkt.r.resrec); + if (ourcacherr && ourcacherr->MPExpectingKA && m->timenow - ourcacherr->MPLastUnansweredQT < mDNSPlatformOneSecond) + { + ourcacherr->MPUnansweredKA++; + ourcacherr->MPExpectingKA = mDNSfalse; + } + + // Having built our ExpectedAnswers list from the questions in this packet, we can definitively + // remove from our ExpectedAnswers list any records that are suppressed in the very same packet. + // For answers that are suppressed in subsequent KA list packets, we rely on the MPQ/MPKA counting to track them. + eap = &ExpectedAnswers; + while (*eap) + { + CacheRecord *rr = *eap; + if (rr->resrec.InterfaceID == InterfaceID && IdenticalResourceRecord(&pkt.r.resrec, &rr->resrec)) + { *eap = rr->NextInKAList; rr->NextInKAList = mDNSNULL; } + else eap = &rr->NextInKAList; + } + + // See if this Known-Answer is a surprise to us. If so, we shouldn't suppress our own query. + if (!ourcacherr) + { + dqp = &DupQuestions; + while (*dqp) + { + DNSQuestion *q = *dqp; + if (ResourceRecordAnswersQuestion(&pkt.r.resrec, q)) + { *dqp = q->NextInDQList; q->NextInDQList = mDNSNULL; } + else dqp = &q->NextInDQList; + } + } + } + + // *** + // *** 5. Cancel any additionals that were added because of now-deleted records + // *** + for (rr=ResponseRecords; rr; rr=rr->NextResponse) + if (rr->NR_AdditionalTo && !MustSendRecord(rr->NR_AdditionalTo)) + { rr->NR_AnswerTo = mDNSNULL; rr->NR_AdditionalTo = mDNSNULL; } + + // *** + // *** 6. Mark the send flags on the records we plan to send + // *** + for (rr=ResponseRecords; rr; rr=rr->NextResponse) + { + if (rr->NR_AnswerTo) + { + mDNSBool SendMulticastResponse = mDNSfalse; + + // If it's been a while since we multicast this, then send a multicast response for conflict detection, etc. + if (m->timenow - (rr->LastMCTime + TicksTTL(rr)/4) >= 0) SendMulticastResponse = mDNStrue; + + // If the client insists on a multicast response, then we'd better send one + if (rr->NR_AnswerTo == (mDNSu8*)~0) SendMulticastResponse = mDNStrue; + else if (rr->NR_AnswerTo) HaveUnicastAnswer = mDNStrue; + + if (SendMulticastResponse) + { + // If we're already planning to send this on another interface, just send it on all interfaces + if (rr->ImmedAnswer && rr->ImmedAnswer != InterfaceID) + { + rr->ImmedAnswer = mDNSInterfaceMark; + m->NextScheduledResponse = m->timenow; + debugf("ProcessQuery: %##s (%s) : Will send on all interfaces", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + } + else + { + rr->ImmedAnswer = InterfaceID; // Record interface to send it on + m->NextScheduledResponse = m->timenow; + if (srcaddr->type == mDNSAddrType_IPv4) + { + if (mDNSIPv4AddressIsZero(rr->v4Requester)) rr->v4Requester = srcaddr->ip.v4; + else if (!mDNSSameIPv4Address(rr->v4Requester, srcaddr->ip.v4)) rr->v4Requester = onesIPv4Addr; + } + else if (srcaddr->type == mDNSAddrType_IPv6) + { + if (mDNSIPv6AddressIsZero(rr->v6Requester)) rr->v6Requester = srcaddr->ip.v6; + else if (!mDNSSameIPv6Address(rr->v6Requester, srcaddr->ip.v6)) rr->v6Requester = onesIPv6Addr; + } + } + } + } + else if (rr->NR_AdditionalTo && rr->NR_AdditionalTo->NR_AnswerTo == (mDNSu8*)~0) + { + // Since additional records are an optimization anyway, we only ever send them on one interface at a time + // If two clients on different interfaces do queries that invoke the same optional additional answer, + // then the earlier client is out of luck + rr->ImmedAdditional = InterfaceID; + // No need to set m->NextScheduledResponse here + // We'll send these additional records when we send them, or not, as the case may be + } + } + + // *** + // *** 7. If we think other machines are likely to answer these questions, set our packet suppression timer + // *** + if (delayresponse && !m->SuppressSending) + { + // Pick a random delay between 20ms and 120ms. + m->SuppressSending = m->timenow + (mDNSPlatformOneSecond*2 + (mDNSs32)mDNSRandom((mDNSu32)mDNSPlatformOneSecond*10)) / 100; + if (m->SuppressSending == 0) m->SuppressSending = 1; + } + + // *** + // *** 8. If query is from a legacy client, generate a unicast response too + // *** + if (HaveUnicastAnswer) + responseptr = GenerateUnicastResponse(query, end, InterfaceID, LegacyQuery, response, ResponseRecords); + +exit: + // *** + // *** 9. Finally, clear our link chains ready for use next time + // *** + while (ResponseRecords) + { + rr = ResponseRecords; + ResponseRecords = rr->NextResponse; + rr->NextResponse = mDNSNULL; + rr->NR_AnswerTo = mDNSNULL; + rr->NR_AdditionalTo = mDNSNULL; + } + + while (ExpectedAnswers) + { + CacheRecord *rr; + rr = ExpectedAnswers; + ExpectedAnswers = rr->NextInKAList; + rr->NextInKAList = mDNSNULL; + + // For non-truncated queries, we can definitively say that we should expect + // to be seeing a response for any records still left in the ExpectedAnswers list + if (!(query->h.flags.b[0] & kDNSFlag0_TC)) + if (rr->UnansweredQueries == 0 || m->timenow - rr->LastUnansweredTime >= mDNSPlatformOneSecond) + { + rr->UnansweredQueries++; + rr->LastUnansweredTime = m->timenow; + if (rr->UnansweredQueries > 1) + debugf("ProcessQuery: (!TC) UAQ %lu MPQ %lu MPKA %lu %s", + rr->UnansweredQueries, rr->MPUnansweredQ, rr->MPUnansweredKA, GetRRDisplayString(m, rr)); + SetNextCacheCheckTime(m, rr); + } + + // If we've seen multiple unanswered queries for this record, + // then mark it to expire in five seconds if we don't get a response by then. + if (rr->UnansweredQueries >= MaxUnansweredQueries) + { + // Only show debugging message if this record was not about to expire anyway + if (RRExpireTime(rr) - m->timenow > 4 * mDNSPlatformOneSecond) + debugf("ProcessQuery: (Max) UAQ %lu MPQ %lu MPKA %lu mDNS_Reconfirm() for %s", + rr->UnansweredQueries, rr->MPUnansweredQ, rr->MPUnansweredKA, GetRRDisplayString(m, rr)); + mDNS_Reconfirm_internal(m, rr, kDefaultReconfirmTimeForNoAnswer); + } + // Make a guess, based on the multi-packet query / known answer counts, whether we think we + // should have seen an answer for this. (We multiply MPQ by 4 and MPKA by 5, to allow for + // possible packet loss of up to 20% of the additional KA packets.) + else if (rr->MPUnansweredQ * 4 > rr->MPUnansweredKA * 5 + 8) + { + // We want to do this conservatively. + // If there are so many machines on the network that they have to use multi-packet known-answer lists, + // then we don't want them to all hit the network simultaneously with their final expiration queries. + // By setting the record to expire in four minutes, we achieve two things: + // (a) the 90-95% final expiration queries will be less bunched together + // (b) we allow some time for us to witness enough other failed queries that we don't have to do our own + mDNSu32 remain = (mDNSu32)(RRExpireTime(rr) - m->timenow) / 4; + if (remain > 240 * (mDNSu32)mDNSPlatformOneSecond) + remain = 240 * (mDNSu32)mDNSPlatformOneSecond; + + // Only show debugging message if this record was not about to expire anyway + if (RRExpireTime(rr) - m->timenow > 4 * mDNSPlatformOneSecond) + debugf("ProcessQuery: (MPQ) UAQ %lu MPQ %lu MPKA %lu mDNS_Reconfirm() for %s", + rr->UnansweredQueries, rr->MPUnansweredQ, rr->MPUnansweredKA, GetRRDisplayString(m, rr)); + + if (remain <= 60 * (mDNSu32)mDNSPlatformOneSecond) + rr->UnansweredQueries++; // Treat this as equivalent to one definite unanswered query + rr->MPUnansweredQ = 0; // Clear MPQ/MPKA statistics + rr->MPUnansweredKA = 0; + rr->MPExpectingKA = mDNSfalse; + + if (remain < kDefaultReconfirmTimeForNoAnswer) + remain = kDefaultReconfirmTimeForNoAnswer; + mDNS_Reconfirm_internal(m, rr, remain); + } + } + + while (DupQuestions) + { + int i; + DNSQuestion *q = DupQuestions; + DupQuestions = q->NextInDQList; + q->NextInDQList = mDNSNULL; + i = RecordDupSuppressInfo(q->DupSuppress, m->timenow, InterfaceID, srcaddr->type); + debugf("ProcessQuery: Recorded DSI for %##s (%s) on %p/%s %d", q->qname.c, DNSTypeName(q->qtype), InterfaceID, + srcaddr->type == mDNSAddrType_IPv4 ? "v4" : "v6", i); + } + + return(responseptr); + } + +mDNSlocal void mDNSCoreReceiveQuery(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *const end, + const mDNSAddr *srcaddr, const mDNSIPPort srcport, const mDNSAddr *dstaddr, mDNSIPPort dstport, + const mDNSInterfaceID InterfaceID) + { + DNSMessage response; + const mDNSu8 *responseend = mDNSNULL; + + verbosedebugf("Received Query from %#-15a:%d to %#-15a:%d on 0x%.8X with %2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s", + srcaddr, (mDNSu16)srcport.b[0]<<8 | srcport.b[1], + dstaddr, (mDNSu16)dstport.b[0]<<8 | dstport.b[1], + InterfaceID, + msg->h.numQuestions, msg->h.numQuestions == 1 ? ", " : "s,", + msg->h.numAnswers, msg->h.numAnswers == 1 ? ", " : "s,", + msg->h.numAuthorities, msg->h.numAuthorities == 1 ? "y, " : "ies,", + msg->h.numAdditionals, msg->h.numAdditionals == 1 ? "" : "s"); + + responseend = ProcessQuery(m, msg, end, srcaddr, InterfaceID, + (srcport.NotAnInteger != MulticastDNSPort.NotAnInteger), mDNSAddrIsDNSMulticast(dstaddr), &response); + + if (responseend) // If responseend is non-null, that means we built a unicast response packet + { + debugf("Unicast Response: %d Question%s, %d Answer%s, %d Additional%s to %#-15a:%d on %p/%ld", + response.h.numQuestions, response.h.numQuestions == 1 ? "" : "s", + response.h.numAnswers, response.h.numAnswers == 1 ? "" : "s", + response.h.numAdditionals, response.h.numAdditionals == 1 ? "" : "s", + srcaddr, (mDNSu16)srcport.b[0]<<8 | srcport.b[1], InterfaceID, srcaddr->type); + mDNSSendDNSMessage(m, &response, responseend, InterfaceID, dstport, srcaddr, srcport); + } + } + +// NOTE: mDNSCoreReceiveResponse calls mDNS_Deregister_internal which can call a user callback, which may change +// the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m, + const DNSMessage *const response, const mDNSu8 *end, const mDNSAddr *srcaddr, const mDNSAddr *dstaddr, + const mDNSInterfaceID InterfaceID, mDNSu8 ttl) + { + int i; + const mDNSu8 *ptr = LocateAnswers(response, end); // We ignore questions (if any) in a DNS response packet + CacheRecord *CacheFlushRecords = mDNSNULL; + CacheRecord **cfp = &CacheFlushRecords; + + // All records in a DNS response packet are treated as equally valid statements of truth. If we want + // to guard against spoof responses, then the only credible protection against that is cryptographic + // security, e.g. DNSSEC., not worring about which section in the spoof packet contained the record + int totalrecords = response->h.numAnswers + response->h.numAuthorities + response->h.numAdditionals; + + (void)srcaddr; // Currently used only for display in debugging message + + verbosedebugf("Received Response from %#-15a addressed to %#-15a on %p TTL %d with %2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s", + srcaddr, dstaddr, InterfaceID, ttl, + response->h.numQuestions, response->h.numQuestions == 1 ? ", " : "s,", + response->h.numAnswers, response->h.numAnswers == 1 ? ", " : "s,", + response->h.numAuthorities, response->h.numAuthorities == 1 ? "y, " : "ies,", + response->h.numAdditionals, response->h.numAdditionals == 1 ? "" : "s"); + + // TTL should be 255 + // In the case of overlayed subnets that aren't using RFC 3442, some packets may incorrectly + // go to the router first and then come back with a TTL of 254, so we allow that too. + // Anything lower than 254 is a pretty good sign of an off-net spoofing attack. + // Also, if we get a unicast response when we weren't expecting one, then we assume it is someone trying to spoof us + if (ttl < 254 || (!mDNSAddrIsDNSMulticast(dstaddr) && (mDNSu32)(m->timenow - m->ExpectUnicastResponse) > (mDNSu32)mDNSPlatformOneSecond)) + { + debugf("** Ignored apparent spoof mDNS Response from %#-15a to %#-15a TTL %d on %p with %2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s", + srcaddr, dstaddr, ttl, InterfaceID, + response->h.numQuestions, response->h.numQuestions == 1 ? ", " : "s,", + response->h.numAnswers, response->h.numAnswers == 1 ? ", " : "s,", + response->h.numAuthorities, response->h.numAuthorities == 1 ? "y, " : "ies,", + response->h.numAdditionals, response->h.numAdditionals == 1 ? "" : "s"); + return; + } + + for (i = 0; i < totalrecords && ptr && ptr < end; i++) + { + LargeCacheRecord pkt; + const mDNSu8 RecordType = (mDNSu8)((i < response->h.numAnswers) ? kDNSRecordTypePacketAns : kDNSRecordTypePacketAdd); + ptr = GetLargeResourceRecord(m, response, ptr, end, InterfaceID, RecordType, &pkt); + if (!ptr) break; // Break out of the loop and clean up our CacheFlushRecords list before exiting + + // 1. Check that this packet resource record does not conflict with any of ours + if (m->CurrentRecord) LogMsg("mDNSCoreReceiveResponse ERROR m->CurrentRecord already set"); + m->CurrentRecord = m->ResourceRecords; + while (m->CurrentRecord) + { + AuthRecord *rr = m->CurrentRecord; + m->CurrentRecord = rr->next; + if (PacketRRMatchesSignature(&pkt.r, rr)) // If interface, name, type (if verified) and class match... + { + // ... check to see if rdata is identical + if (SameRData(&pkt.r.resrec, &rr->resrec)) + { + // If the RR in the packet is identical to ours, just check they're not trying to lower the TTL on us + if (pkt.r.resrec.rroriginalttl >= rr->resrec.rroriginalttl/2 || m->SleepState) + { + // If we were planning to send on this -- and only this -- interface, then we don't need to any more + if (rr->ImmedAnswer == InterfaceID) rr->ImmedAnswer = mDNSNULL; + } + else + { + if (rr->ImmedAnswer == mDNSNULL) { rr->ImmedAnswer = InterfaceID; m->NextScheduledResponse = m->timenow; } + else if (rr->ImmedAnswer != InterfaceID) { rr->ImmedAnswer = mDNSInterfaceMark; m->NextScheduledResponse = m->timenow; } + } + } + else + { + // else, the packet RR has different rdata -- check to see if this is a conflict + if (pkt.r.resrec.rroriginalttl > 0 && PacketRRConflict(m, rr, &pkt.r)) + { + debugf("mDNSCoreReceiveResponse: Our Record: %08X %08X %s", rr-> resrec.rdatahash, rr-> resrec.rdnamehash, GetRRDisplayString(m, rr)); + debugf("mDNSCoreReceiveResponse: Pkt Record: %08X %08X %s", pkt.r.resrec.rdatahash, pkt.r.resrec.rdnamehash, GetRRDisplayString(m, &pkt.r)); + + // If this record is marked DependentOn another record for conflict detection purposes, + // then *that* record has to be bumped back to probing state to resolve the conflict + while (rr->DependentOn) rr = rr->DependentOn; + + // If we've just whacked this record's ProbeCount, don't need to do it again + if (rr->ProbeCount <= DefaultProbeCountForTypeUnique) + { + // If we'd previously verified this record, put it back to probing state and try again + if (rr->resrec.RecordType == kDNSRecordTypeVerified) + { + debugf("mDNSCoreReceiveResponse: Reseting to Probing: %##s (%s)", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + rr->resrec.RecordType = kDNSRecordTypeUnique; + rr->ProbeCount = DefaultProbeCountForTypeUnique + 1; + rr->ThisAPInterval = DefaultAPIntervalForRecordType(kDNSRecordTypeUnique); + InitializeLastAPTime(m, rr); + RecordProbeFailure(m, rr); // Repeated late conflicts also cause us to back off to the slower probing rate + } + // If we're probing for this record, we just failed + else if (rr->resrec.RecordType == kDNSRecordTypeUnique) + { + debugf("mDNSCoreReceiveResponse: Will rename %##s (%s)", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + mDNS_Deregister_internal(m, rr, mDNS_Dereg_conflict); + } + // We assumed this record must be unique, but we were wrong. + // (e.g. There are two mDNSResponders on the same machine giving + // different answers for the reverse mapping record.) + // This is simply a misconfiguration, and we don't try to recover from it. + else if (rr->resrec.RecordType == kDNSRecordTypeKnownUnique) + { + debugf("mDNSCoreReceiveResponse: Unexpected conflict on %##s (%s) -- discarding our record", + rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + mDNS_Deregister_internal(m, rr, mDNS_Dereg_conflict); + } + else + debugf("mDNSCoreReceiveResponse: Unexpected record type %X %##s (%s)", + rr->resrec.RecordType, rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype)); + } + } + // Else, matching signature, different rdata, but not a considered a conflict. + // If the packet record has the cache-flush bit set, then we check to see if we have to re-assert our record(s) + // to rescue them (see note about "multi-homing and bridged networks" at the end of this function). + else if ((pkt.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask) && m->timenow - rr->LastMCTime > mDNSPlatformOneSecond/2) + { rr->ImmedAnswer = mDNSInterfaceMark; m->NextScheduledResponse = m->timenow; } + } + } + } + + // 2. See if we want to add this packet resource record to our cache + if (m->rrcache_size) // Only try to cache answers if we have a cache to put them in + { + mDNSu32 slot = HashSlot(&pkt.r.resrec.name); + CacheRecord *rr; + // 2a. Check if this packet resource record is already in our cache + for (rr = m->rrcache_hash[slot]; rr; rr=rr->next) + { + // If we found this exact resource record, refresh its TTL + if (rr->resrec.InterfaceID == InterfaceID && IdenticalResourceRecord(&pkt.r.resrec, &rr->resrec)) + { + if (pkt.r.resrec.rdlength > InlineCacheRDSize) + verbosedebugf("Found record size %5d interface %p already in cache: %s", + pkt.r.resrec.rdlength, InterfaceID, GetRRDisplayString(m, &pkt.r)); + rr->TimeRcvd = m->timenow; + + if (pkt.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask) + { + // If this packet record has the kDNSClass_UniqueRRSet flag set, then add it to our cache flushing list + if (rr->NextInCFList == mDNSNULL && cfp != &rr->NextInCFList) + { *cfp = rr; cfp = &rr->NextInCFList; } + + // If this packet record is marked unique, and our previous cached copy was not, then fix it + if (!(rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask)) + { + DNSQuestion *q; + for (q = m->Questions; q; q=q->next) if (ResourceRecordAnswersQuestion(&rr->resrec, q)) q->UniqueAnswers++; + rr->resrec.RecordType = pkt.r.resrec.RecordType; + } + } + + if (pkt.r.resrec.rroriginalttl > 0) + { + rr->resrec.rroriginalttl = pkt.r.resrec.rroriginalttl; + rr->UnansweredQueries = 0; + rr->MPUnansweredQ = 0; + rr->MPUnansweredKA = 0; + rr->MPExpectingKA = mDNSfalse; + } + else + { + // If the packet TTL is zero, that means we're deleting this record. + // To give other hosts on the network a chance to protest, we push the deletion + // out one second into the future. Also, we set UnansweredQueries to MaxUnansweredQueries. + // Otherwise, we'll do final queries for this record at 80% and 90% of its apparent + // lifetime (800ms and 900ms from now) which is a pointless waste of network bandwidth. + rr->resrec.rroriginalttl = 1; + rr->UnansweredQueries = MaxUnansweredQueries; + } + SetNextCacheCheckTime(m, rr); + break; + } + } + + // If packet resource record not in our cache, add it now + // (unless it is just a deletion of a record we never had, in which case we don't care) + if (!rr && pkt.r.resrec.rroriginalttl > 0) + { + rr = GetFreeCacheRR(m, pkt.r.resrec.rdlength); + if (!rr) debugf("No cache space to add record for %#s", pkt.r.resrec.name.c); + else + { + RData *saveptr = rr->resrec.rdata; // Save the rr->resrec.rdata pointer + *rr = pkt.r; + rr->resrec.rdata = saveptr; // and then restore it after the structure assignment + if (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) + { *cfp = rr; cfp = &rr->NextInCFList; } + // If this is an oversized record with external storage allocated, copy rdata to external storage + if (pkt.r.resrec.rdlength > InlineCacheRDSize) + mDNSPlatformMemCopy(pkt.r.resrec.rdata, rr->resrec.rdata, sizeofRDataHeader + pkt.r.resrec.rdlength); + rr->next = mDNSNULL; // Clear 'next' pointer + *(m->rrcache_tail[slot]) = rr; // Append this record to tail of cache slot list + m->rrcache_tail[slot] = &(rr->next); // Advance tail pointer + m->rrcache_used[slot]++; + //debugf("Adding RR %##s to cache (%d)", pkt.r.name.c, m->rrcache_used); + CacheRecordAdd(m, rr); + // MUST do this AFTER CacheRecordAdd(), because that's what sets CRActiveQuestion for us + SetNextCacheCheckTime(m, rr); + } + } + } + } + + // If we've just received one or more records with their cache flush bits set, + // then scan that cache slot to see if there are any old stale records we need to flush + while (CacheFlushRecords) + { + CacheRecord *r1 = CacheFlushRecords, *r2; + CacheFlushRecords = CacheFlushRecords->NextInCFList; + r1->NextInCFList = mDNSNULL; + for (r2 = m->rrcache_hash[HashSlot(&r1->resrec.name)]; r2; r2=r2->next) + if (SameResourceRecordSignature(&r1->resrec, &r2->resrec) && m->timenow - r2->TimeRcvd > mDNSPlatformOneSecond) + { + verbosedebugf("Cache flush %p X %p %##s (%s)", r1, r2, r2->resrec.name.c, DNSTypeName(r2->resrec.rrtype)); + // We set stale records to expire in one second. + // This gives the owner a chance to rescue it if necessary. + // This is important in the case of multi-homing and bridged networks: + // Suppose host X is on Ethernet. X then connects to an AirPort base station, which happens to be + // bridged onto the same Ethernet. When X announces its AirPort IP address with the cache-flush bit + // set, the AirPort packet will be bridged onto the Ethernet, and all other hosts on the Ethernet + // will promptly delete their cached copies of the (still valid) Ethernet IP address record. + // By delaying the deletion by one second, we give X a change to notice that this bridging has + // happened, and re-announce its Ethernet IP address to rescue it from deletion from all our caches. + // We set UnansweredQueries to MaxUnansweredQueries to avoid expensive and unnecessary + // final expiration queries for this record. + r2->resrec.rroriginalttl = 1; + r2->TimeRcvd = m->timenow; + r2->UnansweredQueries = MaxUnansweredQueries; + SetNextCacheCheckTime(m, r2); + } + } + } + +mDNSexport void mDNSCoreReceive(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) + { + const mDNSu8 StdQ = kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery; + const mDNSu8 StdR = kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery; + const mDNSu8 QR_OP = (mDNSu8)(msg->h.flags.b[0] & kDNSFlag0_QROP_Mask); + + // Read the integer parts which are in IETF byte-order (MSB first, LSB second) + mDNSu8 *ptr = (mDNSu8 *)&msg->h.numQuestions; + msg->h.numQuestions = (mDNSu16)((mDNSu16)ptr[0] << 8 | ptr[1]); + msg->h.numAnswers = (mDNSu16)((mDNSu16)ptr[2] << 8 | ptr[3]); + msg->h.numAuthorities = (mDNSu16)((mDNSu16)ptr[4] << 8 | ptr[5]); + msg->h.numAdditionals = (mDNSu16)((mDNSu16)ptr[6] << 8 | ptr[7]); + + if (!m) { LogMsg("mDNSCoreReceive ERROR m is NULL"); return; } + + // We use zero addresses and all-ones addresses at various places in the code to indicate special values like "no address" + // If we accept and try to process a packet with zero or all-ones source address, that could really mess things up + if (!mDNSAddressIsValid(srcaddr)) { debugf("mDNSCoreReceive ignoring packet from %#a", srcaddr); return; } + + mDNS_Lock(m); + if (QR_OP == StdQ) mDNSCoreReceiveQuery (m, msg, end, srcaddr, srcport, dstaddr, dstport, InterfaceID); + else if (QR_OP == StdR) mDNSCoreReceiveResponse(m, msg, end, srcaddr, dstaddr, InterfaceID, ttl); + else debugf("Unknown DNS packet type %02X%02X (ignored)", msg->h.flags.b[0], msg->h.flags.b[1]); + + // Packet reception often causes a change to the task list: + // 1. Inbound queries can cause us to need to send responses + // 2. Conflicing response packets received from other hosts can cause us to need to send defensive responses + // 3. Other hosts announcing deletion of shared records can cause us to need to re-assert those records + // 4. Response packets that answer questions may cause our client to issue new questions + mDNS_Unlock(m); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - +#pragma mark - Searcher Functions +#endif + +mDNSlocal DNSQuestion *FindDuplicateQuestion(const mDNS *const m, const DNSQuestion *const question) + { + DNSQuestion *q; + // Note: A question can only be marked as a duplicate of one that occurs *earlier* in the list. + // This prevents circular references, where two questions are each marked as a duplicate of the other. + // Accordingly, we break out of the loop when we get to 'question', because there's no point searching + // further in the list. + for (q = m->Questions; q && q != question; q=q->next) // Scan our list of questions + if (q->InterfaceID == question->InterfaceID && // for another question with the same InterfaceID, + q->qtype == question->qtype && // type, + q->qclass == question->qclass && // class, + q->qnamehash == question->qnamehash && + SameDomainName(&q->qname, &question->qname)) // and name + return(q); + return(mDNSNULL); + } + +// This is called after a question is deleted, in case other identical questions were being +// suppressed as duplicates +mDNSlocal void UpdateQuestionDuplicates(mDNS *const m, const DNSQuestion *const question) + { + DNSQuestion *q; + for (q = m->Questions; q; q=q->next) // Scan our list of questions + if (q->DuplicateOf == question) // To see if any questions were referencing this as their duplicate + { + q->ThisQInterval = question->ThisQInterval; + q->LastQTime = question->LastQTime; + q->RecentAnswers = 0; + q->DuplicateOf = FindDuplicateQuestion(m, q); + q->LastQTxTime = question->LastQTxTime; + SetNextQueryTime(m,q); + } + } + +mDNSlocal mStatus mDNS_StartQuery_internal(mDNS *const m, DNSQuestion *const question) + { +#if TEST_LOCALONLY_FOR_EVERYTHING + question->InterfaceID = (mDNSInterfaceID)~0; +#endif + if (m->rrcache_size == 0) // Can't do queries if we have no cache space allocated + return(mStatus_NoCache); + else + { + int i; + // Note: It important that new questions are appended at the *end* of the list, not prepended at the start + DNSQuestion **q = &m->Questions; + if (question->InterfaceID == ((mDNSInterfaceID)~0)) q = &m->LocalOnlyQuestions; + while (*q && *q != question) q=&(*q)->next; + + if (*q) + { + LogMsg("Error! Tried to add a question %##s (%s) that's already in the active list", + question->qname.c, DNSTypeName(question->qtype)); + return(mStatus_AlreadyRegistered); + } + + // If this question is referencing a specific interface, make sure it exists + if (question->InterfaceID && question->InterfaceID != ((mDNSInterfaceID)~0)) + { + NetworkInterfaceInfo *intf; + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->InterfaceID == question->InterfaceID) break; + if (!intf) + { + debugf("mDNS_StartQuery_internal: Question %##s InterfaceID %p not found", question->qname.c, question->InterfaceID); + return(mStatus_BadReferenceErr); + } + } + + // Note: In the case where we already have the answer to this question in our cache, that may be all the client + // wanted, and they may immediately cancel their question. In this case, sending an actual query on the wire would + // be a waste. For that reason, we schedule our first query to go out in half a second. If AnswerNewQuestion() finds + // that we have *no* relevant answers currently in our cache, then it will accelerate that to go out immediately. + if (!ValidateDomainName(&question->qname)) + { + LogMsg("Attempt to start query with invalid qname %##s %s", question->qname.c, DNSTypeName(question->qtype)); + return(mStatus_Invalid); + } + + if (!m->RandomQueryDelay) m->RandomQueryDelay = 1 + (mDNSs32)mDNSRandom((mDNSu32)InitialQuestionInterval); + + question->next = mDNSNULL; + question->qnamehash = DomainNameHashValue(&question->qname); // MUST do this before FindDuplicateQuestion() + question->ThisQInterval = InitialQuestionInterval * 2; // MUST be > zero for an active question + question->LastQTime = m->timenow - m->RandomQueryDelay; // Avoid inter-machine synchronization + question->RecentAnswers = 0; + question->CurrentAnswers = 0; + question->LargeAnswers = 0; + question->UniqueAnswers = 0; + question->DuplicateOf = FindDuplicateQuestion(m, question); + question->NextInDQList = mDNSNULL; + for (i=0; iDupSuppress[i].InterfaceID = mDNSNULL; + // question->InterfaceID must be already set by caller + question->SendQNow = mDNSNULL; + question->SendOnAll = mDNSfalse; + question->LastQTxTime = m->timenow; + + if (!question->DuplicateOf) + verbosedebugf("mDNS_StartQuery_internal: Question %##s %s %p (%p) started", + question->qname.c, DNSTypeName(question->qtype), question->InterfaceID, question); + else + verbosedebugf("mDNS_StartQuery_internal: Question %##s %s %p (%p) duplicate of (%p)", + question->qname.c, DNSTypeName(question->qtype), question->InterfaceID, question, question->DuplicateOf); + + *q = question; + if (question->InterfaceID == ((mDNSInterfaceID)~0)) + { + if (!m->NewLocalOnlyQuestions) m->NewLocalOnlyQuestions = question; + } + else + { + if (!m->NewQuestions) m->NewQuestions = question; + SetNextQueryTime(m,question); + } + + return(mStatus_NoError); + } + } + +mDNSlocal mStatus mDNS_StopQuery_internal(mDNS *const m, DNSQuestion *const question) + { + CacheRecord *rr; + DNSQuestion **q = &m->Questions; + if (question->InterfaceID == ((mDNSInterfaceID)~0)) q = &m->LocalOnlyQuestions; + while (*q && *q != question) q=&(*q)->next; + if (*q) *q = (*q)->next; + else + { + if (question->ThisQInterval >= 0) // Only log error message if the query was supposed to be active + LogMsg("mDNS_StopQuery_internal: Question %##s (%s) not found in active list", + question->qname.c, DNSTypeName(question->qtype)); + return(mStatus_BadReferenceErr); + } + + // Take care to cut question from list *before* calling UpdateQuestionDuplicates + UpdateQuestionDuplicates(m, question); + // But don't trash ThisQInterval until afterwards. + question->ThisQInterval = -1; + + // If there are any cache records referencing this as their active question, then see if any other + // question that is also referencing them, else their CRActiveQuestion needs to get set to NULL. + for (rr = m->rrcache_hash[HashSlot(&question->qname)]; rr; rr=rr->next) + { + if (rr->CRActiveQuestion == question) + { + DNSQuestion *q; + for (q = m->Questions; q; q=q->next) // Scan our list of questions + if (ActiveQuestion(q) && ResourceRecordAnswersQuestion(&rr->resrec, q)) + break; + verbosedebugf("mDNS_StopQuery_internal: Cache RR %##s (%s) setting CRActiveQuestion to %X", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), q); + rr->CRActiveQuestion = q; // Question used to be active; new value may or may not be null + if (!q) m->rrcache_active--; // If no longer active, decrement rrcache_active count + } + } + + // If we just deleted the question that CacheRecordAdd() or CacheRecordRmv()is about to look at, + // bump its pointer forward one question. + if (m->CurrentQuestion == question) + { + debugf("mDNS_StopQuery_internal: Just deleted the currently active question: %##s (%s)", + question->qname.c, DNSTypeName(question->qtype)); + m->CurrentQuestion = question->next; + } + + if (m->NewQuestions == question) + { + debugf("mDNS_StopQuery_internal: Just deleted a new question that wasn't even answered yet: %##s (%s)", + question->qname.c, DNSTypeName(question->qtype)); + m->NewQuestions = question->next; + } + + if (m->NewLocalOnlyQuestions == question) m->NewLocalOnlyQuestions = question->next; + + // Take care not to trash question->next until *after* we've updated m->CurrentQuestion and m->NewQuestions + question->next = mDNSNULL; + return(mStatus_NoError); + } + +mDNSexport mStatus mDNS_StartQuery(mDNS *const m, DNSQuestion *const question) + { + mStatus status; + mDNS_Lock(m); + status = mDNS_StartQuery_internal(m, question); + mDNS_Unlock(m); + return(status); + } + +mDNSexport mStatus mDNS_StopQuery(mDNS *const m, DNSQuestion *const question) + { + mStatus status; + mDNS_Lock(m); + status = mDNS_StopQuery_internal(m, question); + mDNS_Unlock(m); + return(status); + } + +mDNSexport mStatus mDNS_Reconfirm(mDNS *const m, CacheRecord *const rr) + { + mStatus status; + mDNS_Lock(m); + status = mDNS_Reconfirm_internal(m, rr, kDefaultReconfirmTimeForNoAnswer); + mDNS_Unlock(m); + return(status); + } + +mDNSexport mStatus mDNS_ReconfirmByValue(mDNS *const m, ResourceRecord *const rr) + { + mStatus status = mStatus_BadReferenceErr; + CacheRecord *cr; + mDNS_Lock(m); + cr = FindIdenticalRecordInCache(m, rr); + if (cr) status = mDNS_Reconfirm_internal(m, cr, kDefaultReconfirmTimeForNoAnswer); + mDNS_Unlock(m); + return(status); + } + +mDNSexport mStatus mDNS_StartBrowse(mDNS *const m, DNSQuestion *const question, + const domainname *const srv, const domainname *const domain, + const mDNSInterfaceID InterfaceID, mDNSQuestionCallback *Callback, void *Context) + { + question->ThisQInterval = -1; // Indicate that query is not yet active + question->InterfaceID = InterfaceID; + question->qtype = kDNSType_PTR; + question->qclass = kDNSClass_IN; + question->QuestionCallback = Callback; + question->QuestionContext = Context; + if (!ConstructServiceName(&question->qname, mDNSNULL, srv, domain)) return(mStatus_BadParamErr); + return(mDNS_StartQuery(m, question)); + } + +mDNSlocal void FoundServiceInfoSRV(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord) + { + ServiceInfoQuery *query = (ServiceInfoQuery *)question->QuestionContext; + mDNSBool PortChanged = (mDNSBool)(query->info->port.NotAnInteger != answer->rdata->u.srv.port.NotAnInteger); + if (!AddRecord) return; + if (answer->rrtype != kDNSType_SRV) return; + + query->info->port = answer->rdata->u.srv.port; + + // If this is our first answer, then set the GotSRV flag and start the address query + if (!query->GotSRV) + { + query->GotSRV = mDNStrue; + query->qAv4.InterfaceID = answer->InterfaceID; + AssignDomainName(query->qAv4.qname, answer->rdata->u.srv.target); + query->qAv6.InterfaceID = answer->InterfaceID; + AssignDomainName(query->qAv6.qname, answer->rdata->u.srv.target); + mDNS_StartQuery_internal(m, &query->qAv4); + mDNS_StartQuery_internal(m, &query->qAv6); + } + // If this is not our first answer, only re-issue the address query if the target host name has changed + else if ((query->qAv4.InterfaceID != query->qSRV.InterfaceID && query->qAv4.InterfaceID != answer->InterfaceID) || + !SameDomainName(&query->qAv4.qname, &answer->rdata->u.srv.target)) + { + mDNS_StopQuery_internal(m, &query->qAv4); + mDNS_StopQuery_internal(m, &query->qAv6); + if (SameDomainName(&query->qAv4.qname, &answer->rdata->u.srv.target) && !PortChanged) + { + // If we get here, it means: + // 1. This is not our first SRV answer + // 2. The interface ID is different, but the target host and port are the same + // This implies that we're seeing the exact same SRV record on more than one interface, so we should + // make our address queries at least as broad as the original SRV query so that we catch all the answers. + query->qAv4.InterfaceID = query->qSRV.InterfaceID; // Will be mDNSInterface_Any, or a specific interface + query->qAv6.InterfaceID = query->qSRV.InterfaceID; + } + else + { + query->qAv4.InterfaceID = answer->InterfaceID; + AssignDomainName(query->qAv4.qname, answer->rdata->u.srv.target); + query->qAv6.InterfaceID = answer->InterfaceID; + AssignDomainName(query->qAv6.qname, answer->rdata->u.srv.target); + } + debugf("FoundServiceInfoSRV: Restarting address queries for %##s", query->qAv4.qname.c); + mDNS_StartQuery_internal(m, &query->qAv4); + mDNS_StartQuery_internal(m, &query->qAv6); + } + else if (query->ServiceInfoQueryCallback && query->GotADD && query->GotTXT && PortChanged) + { + if (++query->Answers >= 100) + debugf("**** WARNING **** Have given %lu answers for %##s (SRV) %##s %u", + query->Answers, query->qSRV.qname.c, answer->rdata->u.srv.target.c, + ((mDNSu16)answer->rdata->u.srv.port.b[0] << 8) | answer->rdata->u.srv.port.b[1]); + query->ServiceInfoQueryCallback(m, query); + } + // CAUTION: MUST NOT do anything more with query after calling query->Callback(), because the client's + // callback function is allowed to do anything, including deleting this query and freeing its memory. + } + +mDNSlocal void FoundServiceInfoTXT(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord) + { + ServiceInfoQuery *query = (ServiceInfoQuery *)question->QuestionContext; + if (!AddRecord) return; + if (answer->rrtype != kDNSType_TXT) return; + if (answer->rdlength > sizeof(query->info->TXTinfo)) return; + + query->GotTXT = mDNStrue; + query->info->TXTlen = answer->rdlength; + mDNSPlatformMemCopy(answer->rdata->u.txt.c, query->info->TXTinfo, answer->rdlength); + + verbosedebugf("FoundServiceInfoTXT: %##s GotADD=%d", query->info->name.c, query->GotADD); + + // CAUTION: MUST NOT do anything more with query after calling query->Callback(), because the client's + // callback function is allowed to do anything, including deleting this query and freeing its memory. + if (query->ServiceInfoQueryCallback && query->GotADD) + { + if (++query->Answers >= 100) + debugf("**** WARNING **** have given %lu answers for %##s (TXT) %#s...", + query->Answers, query->qSRV.qname.c, answer->rdata->u.txt.c); + query->ServiceInfoQueryCallback(m, query); + } + } + +mDNSlocal void FoundServiceInfo(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord) + { + ServiceInfoQuery *query = (ServiceInfoQuery *)question->QuestionContext; + if (!AddRecord) return; + + if (answer->rrtype == kDNSType_A) + { + query->info->ip.type = mDNSAddrType_IPv4; + query->info->ip.ip.v4 = answer->rdata->u.ip; + } + else if (answer->rrtype == kDNSType_AAAA) + { + query->info->ip.type = mDNSAddrType_IPv6; + query->info->ip.ip.v6 = answer->rdata->u.ipv6; + } + else + { + debugf("FoundServiceInfo: answer %##s type %d (%s) unexpected", answer->name.c, answer->rrtype, DNSTypeName(answer->rrtype)); + return; + } + + query->GotADD = mDNStrue; + query->info->InterfaceID = answer->InterfaceID; + + verbosedebugf("FoundServiceInfo v%d: %##s GotTXT=%d", query->info->ip.type, query->info->name.c, query->GotTXT); + + // CAUTION: MUST NOT do anything more with query after calling query->Callback(), because the client's + // callback function is allowed to do anything, including deleting this query and freeing its memory. + if (query->ServiceInfoQueryCallback && query->GotTXT) + { + if (++query->Answers >= 100) + { + if (answer->rrtype == kDNSType_A) + debugf("**** WARNING **** have given %lu answers for %##s (A) %.4a", query->Answers, query->qSRV.qname.c, &answer->rdata->u.ip); + else + debugf("**** WARNING **** have given %lu answers for %##s (AAAA) %.16a", query->Answers, query->qSRV.qname.c, &answer->rdata->u.ipv6); + } + query->ServiceInfoQueryCallback(m, query); + } + } + +// On entry, the client must have set the name and InterfaceID fields of the ServiceInfo structure +// If the query is not interface-specific, then InterfaceID may be zero +// Each time the Callback is invoked, the remainder of the fields will have been filled in +// In addition, InterfaceID will be updated to give the interface identifier corresponding to that response +mDNSexport mStatus mDNS_StartResolveService(mDNS *const m, + ServiceInfoQuery *query, ServiceInfo *info, mDNSServiceInfoQueryCallback *Callback, void *Context) + { + mStatus status; + mDNS_Lock(m); + + query->qSRV.ThisQInterval = -1; // This question not yet in the question list + query->qSRV.InterfaceID = info->InterfaceID; + AssignDomainName(query->qSRV.qname, info->name); + query->qSRV.qtype = kDNSType_SRV; + query->qSRV.qclass = kDNSClass_IN; + query->qSRV.QuestionCallback = FoundServiceInfoSRV; + query->qSRV.QuestionContext = query; + + query->qTXT.ThisQInterval = -1; // This question not yet in the question list + query->qTXT.InterfaceID = info->InterfaceID; + AssignDomainName(query->qTXT.qname, info->name); + query->qTXT.qtype = kDNSType_TXT; + query->qTXT.qclass = kDNSClass_IN; + query->qTXT.QuestionCallback = FoundServiceInfoTXT; + query->qTXT.QuestionContext = query; + + query->qAv4.ThisQInterval = -1; // This question not yet in the question list + query->qAv4.InterfaceID = info->InterfaceID; + query->qAv4.qname.c[0] = 0; + query->qAv4.qtype = kDNSType_A; + query->qAv4.qclass = kDNSClass_IN; + query->qAv4.QuestionCallback = FoundServiceInfo; + query->qAv4.QuestionContext = query; + + query->qAv6.ThisQInterval = -1; // This question not yet in the question list + query->qAv6.InterfaceID = info->InterfaceID; + query->qAv6.qname.c[0] = 0; + query->qAv6.qtype = kDNSType_AAAA; + query->qAv6.qclass = kDNSClass_IN; + query->qAv6.QuestionCallback = FoundServiceInfo; + query->qAv6.QuestionContext = query; + + query->GotSRV = mDNSfalse; + query->GotTXT = mDNSfalse; + query->GotADD = mDNSfalse; + query->Answers = 0; + + query->info = info; + query->ServiceInfoQueryCallback = Callback; + query->ServiceInfoQueryContext = Context; + +// info->name = Must already be set up by client +// info->interface = Must already be set up by client + info->ip = zeroAddr; + info->port = zeroIPPort; + info->TXTlen = 0; + + status = mDNS_StartQuery_internal(m, &query->qSRV); + if (status == mStatus_NoError) status = mDNS_StartQuery_internal(m, &query->qTXT); + if (status != mStatus_NoError) mDNS_StopResolveService(m, query); + + mDNS_Unlock(m); + return(status); + } + +mDNSexport void mDNS_StopResolveService (mDNS *const m, ServiceInfoQuery *query) + { + mDNS_Lock(m); + if (query->qSRV.ThisQInterval >= 0) mDNS_StopQuery_internal(m, &query->qSRV); + if (query->qTXT.ThisQInterval >= 0) mDNS_StopQuery_internal(m, &query->qTXT); + if (query->qAv4.ThisQInterval >= 0) mDNS_StopQuery_internal(m, &query->qAv4); + if (query->qAv6.ThisQInterval >= 0) mDNS_StopQuery_internal(m, &query->qAv6); + mDNS_Unlock(m); + } + +mDNSexport mStatus mDNS_GetDomains(mDNS *const m, DNSQuestion *const question, mDNS_DomainType DomainType, + const mDNSInterfaceID InterfaceID, mDNSQuestionCallback *Callback, void *Context) + { + MakeDomainNameFromDNSNameString(&question->qname, mDNS_DomainTypeNames[DomainType]); + question->InterfaceID = InterfaceID; + question->qtype = kDNSType_PTR; + question->qclass = kDNSClass_IN; + question->QuestionCallback = Callback; + question->QuestionContext = Context; + return(mDNS_StartQuery(m, question)); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Responder Functions +#endif + +// Set up a AuthRecord with sensible default values. +// These defaults may be overwritten with new values before mDNS_Register is called +mDNSexport void mDNS_SetupResourceRecord(AuthRecord *rr, RData *RDataStorage, mDNSInterfaceID InterfaceID, + mDNSu16 rrtype, mDNSu32 ttl, mDNSu8 RecordType, mDNSRecordCallback Callback, void *Context) + { + // Don't try to store a TTL bigger than we can represent in platform time units + if (ttl > 0x7FFFFFFFUL / mDNSPlatformOneSecond) + ttl = 0x7FFFFFFFUL / mDNSPlatformOneSecond; + else if (ttl == 0) // And Zero TTL is illegal + ttl = kDefaultTTLforShared; + + // Field Group 1: Persistent metadata for Authoritative Records + rr->Additional1 = mDNSNULL; + rr->Additional2 = mDNSNULL; + rr->DependentOn = mDNSNULL; + rr->RRSet = mDNSNULL; + rr->RecordCallback = Callback; + rr->RecordContext = Context; + + rr->resrec.RecordType = RecordType; + rr->HostTarget = mDNSfalse; + + // Field Group 2: Transient state for Authoritative Records (set in mDNS_Register_internal) + // Field Group 3: Transient state for Cache Records (set in mDNS_Register_internal) + + // Field Group 4: The actual information pertaining to this resource record + rr->resrec.InterfaceID = InterfaceID; + rr->resrec.name.c[0] = 0; // MUST be set by client + rr->resrec.rrtype = rrtype; + rr->resrec.rrclass = kDNSClass_IN; + rr->resrec.rroriginalttl = ttl; +// rr->resrec.rdlength = MUST set by client and/or in mDNS_Register_internal +// rr->resrec.rdestimate = set in mDNS_Register_internal +// rr->resrec.rdata = MUST be set by client + + if (RDataStorage) + rr->resrec.rdata = RDataStorage; + else + { + rr->resrec.rdata = &rr->rdatastorage; + rr->resrec.rdata->MaxRDLength = sizeof(RDataBody); + } + } + +mDNSexport mStatus mDNS_Register(mDNS *const m, AuthRecord *const rr) + { + mStatus status; + mDNS_Lock(m); + status = mDNS_Register_internal(m, rr); + mDNS_Unlock(m); + return(status); + } + +mDNSexport mStatus mDNS_Update(mDNS *const m, AuthRecord *const rr, mDNSu32 newttl, + const mDNSu16 newrdlength, + RData *const newrdata, mDNSRecordUpdateCallback *Callback) + { + mDNS_Lock(m); + + if (!ValidateRData(rr->resrec.rrtype, newrdlength, newrdata)) + { LogMsg("Attempt to update record with invalid rdata: %s", GetRRDisplayString_rdb(m, &rr->resrec, &newrdata->u)); return(mStatus_Invalid); } + + // If TTL is unspecified, leave TTL unchanged + if (newttl == 0) newttl = rr->resrec.rroriginalttl; + + // If we already have an update queued up which has not gone through yet, + // give the client a chance to free that memory + if (rr->NewRData) + { + RData *n = rr->NewRData; + rr->NewRData = mDNSNULL; // Clear the NewRData pointer ... + if (rr->UpdateCallback) + rr->UpdateCallback(m, rr, n); // ...and let the client free this memory, if necessary + } + + if (rr->AnnounceCount < ReannounceCount) + rr->AnnounceCount = ReannounceCount; + rr->ThisAPInterval = DefaultAPIntervalForRecordType(rr->resrec.RecordType); + InitializeLastAPTime(m, rr); + rr->NewRData = newrdata; + rr->newrdlength = newrdlength; + rr->UpdateCallback = Callback; + if (!rr->UpdateBlocked && rr->UpdateCredits) rr->UpdateCredits--; + if (!rr->NextUpdateCredit) rr->NextUpdateCredit = (m->timenow + mDNSPlatformOneSecond * 60) | 1; + if (rr->AnnounceCount > rr->UpdateCredits + 1) rr->AnnounceCount = (mDNSu8)(rr->UpdateCredits + 1); + if (rr->UpdateCredits <= 5) + { + mDNSs32 delay = 1 << (5 - rr->UpdateCredits); + if (!rr->UpdateBlocked) rr->UpdateBlocked = (m->timenow + delay * mDNSPlatformOneSecond) | 1; + rr->LastAPTime = rr->UpdateBlocked; + rr->ThisAPInterval *= 4; + LogMsg("Excessive update rate for %##s; delaying announcement by %d seconds", rr->resrec.name.c, delay); + } + rr->resrec.rroriginalttl = newttl; + mDNS_Unlock(m); + return(mStatus_NoError); + } + +// NOTE: mDNS_Deregister calls mDNS_Deregister_internal which can call a user callback, which may change +// the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSexport mStatus mDNS_Deregister(mDNS *const m, AuthRecord *const rr) + { + mStatus status; + mDNS_Lock(m); + status = mDNS_Deregister_internal(m, rr, mDNS_Dereg_normal); + mDNS_Unlock(m); + return(status); + } + +mDNSlocal void HostNameCallback(mDNS *const m, AuthRecord *const rr, mStatus result); + +mDNSlocal NetworkInterfaceInfo *FindFirstAdvertisedInterface(mDNS *const m) + { + NetworkInterfaceInfo *intf; + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->Advertise) break; + return(intf); + } + +mDNSlocal void mDNS_AdvertiseInterface(mDNS *const m, NetworkInterfaceInfo *set) + { + char buffer[256]; + NetworkInterfaceInfo *primary = FindFirstAdvertisedInterface(m); + if (!primary) primary = set; // If no existing advertised interface, this new NetworkInterfaceInfo becomes our new primary + + mDNS_SetupResourceRecord(&set->RR_A, mDNSNULL, set->InterfaceID, kDNSType_A, kDefaultTTLforUnique, kDNSRecordTypeUnique, HostNameCallback, set); + mDNS_SetupResourceRecord(&set->RR_PTR, mDNSNULL, set->InterfaceID, kDNSType_PTR, kDefaultTTLforUnique, kDNSRecordTypeKnownUnique, mDNSNULL, mDNSNULL); + mDNS_SetupResourceRecord(&set->RR_HINFO, mDNSNULL, set->InterfaceID, kDNSType_HINFO, kDefaultTTLforUnique, kDNSRecordTypeUnique, mDNSNULL, mDNSNULL); + + // 1. Set up Address record to map from host name ("foo.local.") to IP address + // 2. Set up reverse-lookup PTR record to map from our address back to our host name + AssignDomainName(set->RR_A.resrec.name, m->hostname); + if (set->ip.type == mDNSAddrType_IPv4) + { + set->RR_A.resrec.rrtype = kDNSType_A; + set->RR_A.resrec.rdata->u.ip = set->ip.ip.v4; + // Note: This is reverse order compared to a normal dotted-decimal IP address + mDNS_snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d.in-addr.arpa.", + set->ip.ip.v4.b[3], set->ip.ip.v4.b[2], set->ip.ip.v4.b[1], set->ip.ip.v4.b[0]); + } + else if (set->ip.type == mDNSAddrType_IPv6) + { + int i; + set->RR_A.resrec.rrtype = kDNSType_AAAA; + set->RR_A.resrec.rdata->u.ipv6 = set->ip.ip.v6; + for (i = 0; i < 16; i++) + { + static const char hexValues[] = "0123456789ABCDEF"; + buffer[i * 4 ] = hexValues[set->ip.ip.v6.b[15 - i] & 0x0F]; + buffer[i * 4 + 1] = '.'; + buffer[i * 4 + 2] = hexValues[set->ip.ip.v6.b[15 - i] >> 4]; + buffer[i * 4 + 3] = '.'; + } + mDNS_snprintf(&buffer[64], sizeof(buffer)-64, "ip6.arpa."); + } + + MakeDomainNameFromDNSNameString(&set->RR_PTR.resrec.name, buffer); + set->RR_PTR.HostTarget = mDNStrue; // Tell mDNS that the target of this PTR is to be kept in sync with our host name + + set->RR_A.RRSet = &primary->RR_A; // May refer to self + + mDNS_Register_internal(m, &set->RR_A); + mDNS_Register_internal(m, &set->RR_PTR); + + if (m->HIHardware.c[0] > 0 && m->HISoftware.c[0] > 0 && m->HIHardware.c[0] + m->HISoftware.c[0] <= 254) + { + mDNSu8 *p = set->RR_HINFO.resrec.rdata->u.data; + AssignDomainName(set->RR_HINFO.resrec.name, m->hostname); + set->RR_HINFO.DependentOn = &set->RR_A; + mDNSPlatformMemCopy(&m->HIHardware, p, 1 + (mDNSu32)m->HIHardware.c[0]); + p += 1 + (int)p[0]; + mDNSPlatformMemCopy(&m->HISoftware, p, 1 + (mDNSu32)m->HISoftware.c[0]); + mDNS_Register_internal(m, &set->RR_HINFO); + } + else + { + debugf("Not creating HINFO record: platform support layer provided no information"); + set->RR_HINFO.resrec.RecordType = kDNSRecordTypeUnregistered; + } + } + +mDNSlocal void mDNS_DeadvertiseInterface(mDNS *const m, NetworkInterfaceInfo *set) + { + NetworkInterfaceInfo *intf; + // If we still have address records referring to this one, update them + NetworkInterfaceInfo *primary = FindFirstAdvertisedInterface(m); + AuthRecord *A = primary ? &primary->RR_A : mDNSNULL; + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->RR_A.RRSet == &set->RR_A) + intf->RR_A.RRSet = A; + + // Unregister these records. + // When doing the mDNS_Close processing, we first call mDNS_DeadvertiseInterface for each interface, so by the time the platform + // support layer gets to call mDNS_DeregisterInterface, the address and PTR records have already been deregistered for it. + // Also, in the event of a name conflict, one or more of our records will have been forcibly deregistered. + // To avoid unnecessary and misleading warning messages, we check the RecordType before calling mDNS_Deregister_internal(). + if (set->RR_A. resrec.RecordType) mDNS_Deregister_internal(m, &set->RR_A, mDNS_Dereg_normal); + if (set->RR_PTR. resrec.RecordType) mDNS_Deregister_internal(m, &set->RR_PTR, mDNS_Dereg_normal); + if (set->RR_HINFO.resrec.RecordType) mDNS_Deregister_internal(m, &set->RR_HINFO, mDNS_Dereg_normal); + } + +mDNSexport void mDNS_GenerateFQDN(mDNS *const m) + { + domainname newname; + mDNS_Lock(m); + + newname.c[0] = 0; + if (!AppendDomainLabel(&newname, &m->hostlabel)) LogMsg("ERROR! Cannot create dot-local hostname"); + if (!AppendLiteralLabelString(&newname, "local")) LogMsg("ERROR! Cannot create dot-local hostname"); + if (!SameDomainName(&m->hostname, &newname)) + { + NetworkInterfaceInfo *intf; + AuthRecord *rr; + + m->hostname = newname; + + // 1. Stop advertising our address records on all interfaces + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->Advertise) mDNS_DeadvertiseInterface(m, intf); + + // 2. Start advertising our address records using the new name + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->Advertise) mDNS_AdvertiseInterface(m, intf); + + // 3. Make sure that any SRV records (and the like) that reference our + // host name in their rdata get updated to reference this new host name + for (rr = m->ResourceRecords; rr; rr=rr->next) if (rr->HostTarget) SetTargetToHostName(m, rr); + for (rr = m->DuplicateRecords; rr; rr=rr->next) if (rr->HostTarget) SetTargetToHostName(m, rr); + } + + mDNS_Unlock(m); + } + +mDNSlocal void HostNameCallback(mDNS *const m, AuthRecord *const rr, mStatus result) + { + (void)rr; // Unused parameter + + #if MDNS_DEBUGMSGS + { + char *msg = "Unknown result"; + if (result == mStatus_NoError) msg = "Name registered"; + else if (result == mStatus_NameConflict) msg = "Name conflict"; + debugf("HostNameCallback: %##s (%s) %s (%ld)", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), msg, result); + } + #endif + + if (result == mStatus_NoError) + { + // Notify the client that the host name is successfully registered + if (m->MainCallback) + m->MainCallback(m, result); + } + else if (result == mStatus_NameConflict) + { + domainlabel oldlabel = m->hostlabel; + + // 1. First give the client callback a chance to pick a new name + if (m->MainCallback) + m->MainCallback(m, mStatus_NameConflict); + + // 2. If the client callback didn't do it, add (or increment) an index ourselves + if (SameDomainLabel(m->hostlabel.c, oldlabel.c)) + IncrementLabelSuffix(&m->hostlabel, mDNSfalse); + + // 3. Generate the FQDNs from the hostlabel, + // and make sure all SRV records, etc., are updated to reference our new hostname + mDNS_GenerateFQDN(m); + } + } + +mDNSlocal void UpdateInterfaceProtocols(mDNS *const m, NetworkInterfaceInfo *active) + { + NetworkInterfaceInfo *intf; + active->IPv4Available = mDNSfalse; + active->IPv6Available = mDNSfalse; + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->InterfaceID == active->InterfaceID) + { + if (intf->ip.type == mDNSAddrType_IPv4 && intf->TxAndRx) active->IPv4Available = mDNStrue; + if (intf->ip.type == mDNSAddrType_IPv6 && intf->TxAndRx) active->IPv6Available = mDNStrue; + } + } + +mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *set) + { + mDNSBool FirstOfType = mDNStrue; + NetworkInterfaceInfo **p = &m->HostInterfaces; + mDNS_Lock(m); + + // Assume this interface will be active + set->InterfaceActive = mDNStrue; + set->IPv4Available = (set->ip.type == mDNSAddrType_IPv4 && set->TxAndRx); + set->IPv6Available = (set->ip.type == mDNSAddrType_IPv6 && set->TxAndRx); + + while (*p) + { + if (*p == set) + { + LogMsg("Error! Tried to register a NetworkInterfaceInfo that's already in the list"); + mDNS_Unlock(m); + return(mStatus_AlreadyRegistered); + } + + // This InterfaceID is already in the list, so mark this interface inactive for now + if ((*p)->InterfaceID == set->InterfaceID) + { + set->InterfaceActive = mDNSfalse; + if (set->ip.type == (*p)->ip.type) FirstOfType = mDNSfalse; + if (set->ip.type == mDNSAddrType_IPv4 && set->TxAndRx) (*p)->IPv4Available = mDNStrue; + if (set->ip.type == mDNSAddrType_IPv6 && set->TxAndRx) (*p)->IPv6Available = mDNStrue; + } + + p=&(*p)->next; + } + + set->next = mDNSNULL; + *p = set; + + debugf("mDNS_RegisterInterface: InterfaceID %p %#a %s", set->InterfaceID, &set->ip, + set->InterfaceActive ? + "not represented in list; marking active and retriggering queries" : + "already represented in list; marking inactive for now"); + + // In some versions of OS X the IPv6 address remains on an interface even when the interface is turned off, + // giving the false impression that there's an active representative of this interface when there really isn't. + // Therefore, when registering an interface, we want to re-trigger our questions and re-probe our Resource Records, + // even if we believe that we previously had an active representative of this interface. + if ((m->KnownBugs & mDNS_KnownBug_PhantomInterfaces) || FirstOfType || set->InterfaceActive) + { + DNSQuestion *q; + AuthRecord *rr; + // Use a small amount of randomness: + // In the case of a network administrator turning on an Ethernet hub so that all the connected machines establish link at + // exactly the same time, we don't want them to all go and hit the network with identical queries at exactly the same moment. + if (!m->SuppressSending) m->SuppressSending = m->timenow + (mDNSs32)mDNSRandom((mDNSu32)InitialQuestionInterval); + for (q = m->Questions; q; q=q->next) // Scan our list of questions + if (!q->InterfaceID || q->InterfaceID == set->InterfaceID) // If non-specific Q, or Q on this specific interface, + { // then reactivate this question + q->ThisQInterval = InitialQuestionInterval; // MUST be > zero for an active question + q->LastQTime = m->timenow - q->ThisQInterval; + q->RecentAnswers = 0; + if (ActiveQuestion(q)) m->NextScheduledQuery = m->timenow; + } + + // For all our non-specific authoritative resource records (and any dormant records specific to this interface) + // we now need them to re-probe if necessary, and then re-announce. + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (!rr->resrec.InterfaceID || rr->resrec.InterfaceID == set->InterfaceID) + { + if (rr->resrec.RecordType == kDNSRecordTypeVerified && !rr->DependentOn) rr->resrec.RecordType = kDNSRecordTypeUnique; + rr->ProbeCount = DefaultProbeCountForRecordType(rr->resrec.RecordType); + if (rr->AnnounceCount < ReannounceCount) + rr->AnnounceCount = ReannounceCount; + rr->ThisAPInterval = DefaultAPIntervalForRecordType(rr->resrec.RecordType); + InitializeLastAPTime(m, rr); + } + } + + if (set->Advertise) + mDNS_AdvertiseInterface(m, set); + + mDNS_Unlock(m); + return(mStatus_NoError); + } + +// NOTE: mDNS_DeregisterInterface calls mDNS_Deregister_internal which can call a user callback, which may change +// the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSexport void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *set) + { + NetworkInterfaceInfo **p = &m->HostInterfaces; + + mDNSBool revalidate = mDNSfalse; + // If this platform has the "phantom interfaces" known bug (e.g. Jaguar), we have to revalidate records every + // time an interface goes away. Otherwise, when you disconnect the Ethernet cable, the system reports that it + // still has an IPv6 address, and if we don't revalidate those records don't get deleted in a timely fashion. + if (m->KnownBugs & mDNS_KnownBug_PhantomInterfaces) revalidate = mDNStrue; + + mDNS_Lock(m); + + // Find this record in our list + while (*p && *p != set) p=&(*p)->next; + if (!*p) { debugf("mDNS_DeregisterInterface: NetworkInterfaceInfo not found in list"); mDNS_Unlock(m); return; } + + // Unlink this record from our list + *p = (*p)->next; + set->next = mDNSNULL; + + if (!set->InterfaceActive) + { + // If this interface not the active member of its set, update the v4/v6Available flags for the active member + NetworkInterfaceInfo *intf; + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->InterfaceActive && intf->InterfaceID == set->InterfaceID) + UpdateInterfaceProtocols(m, intf); + } + else + { + NetworkInterfaceInfo *intf; + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->InterfaceID == set->InterfaceID) + break; + if (intf) + { + debugf("mDNS_DeregisterInterface: Another representative of InterfaceID %p exists; making it active", + set->InterfaceID); + intf->InterfaceActive = mDNStrue; + UpdateInterfaceProtocols(m, intf); + + // See if another representative *of the same type* exists. If not, we mave have gone from + // dual-stack to v6-only (or v4-only) so we need to reconfirm which records are still valid. + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->InterfaceID == set->InterfaceID && intf->ip.type == set->ip.type) + break; + if (!intf) revalidate = mDNStrue; + } + else + { + CacheRecord *rr; + DNSQuestion *q; + mDNSu32 slot; + debugf("mDNS_DeregisterInterface: Last representative of InterfaceID %p deregistered; marking questions etc. dormant", + set->InterfaceID); + + // 1. Deactivate any questions specific to this interface + for (q = m->Questions; q; q=q->next) + if (q->InterfaceID == set->InterfaceID) + q->ThisQInterval = 0; + + // 2. Flush any cache records received on this interface + revalidate = mDNSfalse; // Don't revalidate if we're flushing the records + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) + for (rr = m->rrcache_hash[slot]; rr; rr=rr->next) + if (rr->resrec.InterfaceID == set->InterfaceID) + PurgeCacheResourceRecord(m, rr); + } + } + + // If we were advertising on this interface, deregister those address and reverse-lookup records now + if (set->Advertise) + mDNS_DeadvertiseInterface(m, set); + + // If we have any cache records received on this interface that went away, then re-verify them. + // In some versions of OS X the IPv6 address remains on an interface even when the interface is turned off, + // giving the false impression that there's an active representative of this interface when there really isn't. + // Don't need to do this when shutting down, because *all* interfaces are about to go away + if (revalidate && !m->mDNS_shutdown) + { + mDNSu32 slot; + CacheRecord *rr; + m->NextCacheCheck = m->timenow; + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) + for (rr = m->rrcache_hash[slot]; rr; rr=rr->next) + if (rr->resrec.InterfaceID == set->InterfaceID) + mDNS_Reconfirm_internal(m, rr, kDefaultReconfirmTimeForCableDisconnect); + } + + mDNS_Unlock(m); + } + +mDNSlocal void ServiceCallback(mDNS *const m, AuthRecord *const rr, mStatus result) + { + ServiceRecordSet *sr = (ServiceRecordSet *)rr->RecordContext; + (void)m; // Unused parameter + + #if MDNS_DEBUGMSGS + { + char *msg = "Unknown result"; + if (result == mStatus_NoError) msg = "Name Registered"; + else if (result == mStatus_NameConflict) msg = "Name Conflict"; + else if (result == mStatus_MemFree) msg = "Memory Free"; + debugf("ServiceCallback: %##s (%s) %s (%ld)", rr->resrec.name.c, DNSTypeName(rr->resrec.rrtype), msg, result); + } + #endif + + // If we got a name conflict on either SRV or TXT, forcibly deregister this service, and record that we did that + if (result == mStatus_NameConflict) + { + sr->Conflict = mDNStrue; // Record that this service set had a conflict + sr->RR_PTR.AnnounceCount = InitialAnnounceCount; // Make sure we don't send a goodbye for the PTR record + mDNS_DeregisterService(m, sr); // Unlink the records from our list + return; + } + + if (result == mStatus_MemFree) + { + // If the PTR record or any of the subtype PTR record are still in the process of deregistering, + // don't pass on the NameConflict/MemFree message until every record is finished cleaning up. + mDNSu32 i; + if (sr->RR_PTR.resrec.RecordType != kDNSRecordTypeUnregistered) return; + for (i=0; iNumSubTypes; i++) if (sr->SubTypes[i].resrec.RecordType != kDNSRecordTypeUnregistered) return; + + // If this ServiceRecordSet was forcibly deregistered, and now its memory is ready for reuse, + // then we can now report the NameConflict to the client + if (sr->Conflict) result = mStatus_NameConflict; + } + + // CAUTION: MUST NOT do anything more with sr after calling sr->Callback(), because the client's callback + // function is allowed to do anything, including deregistering this service and freeing its memory. + if (sr->ServiceCallback) + sr->ServiceCallback(m, sr, result); + } + +// Note: +// Name is first label of domain name (any dots in the name are actual dots, not label separators) +// Type is service type (e.g. "_printer._tcp.") +// Domain is fully qualified domain name (i.e. ending with a null label) +// We always register a TXT, even if it is empty (so that clients are not +// left waiting forever looking for a nonexistent record.) +// If the host parameter is mDNSNULL or the root domain (ASCII NUL), +// then the default host name (m->hostname1) is automatically used +mDNSexport mStatus mDNS_RegisterService(mDNS *const m, ServiceRecordSet *sr, + const domainlabel *const name, const domainname *const type, const domainname *const domain, + const domainname *const host, mDNSIPPort port, const mDNSu8 txtinfo[], mDNSu16 txtlen, + AuthRecord *SubTypes, mDNSu32 NumSubTypes, + const mDNSInterfaceID InterfaceID, mDNSServiceCallback Callback, void *Context) + { + mStatus err; + mDNSu32 i; + + sr->ServiceCallback = Callback; + sr->ServiceContext = Context; + sr->Extras = mDNSNULL; + sr->NumSubTypes = NumSubTypes; + sr->SubTypes = SubTypes; + sr->Conflict = mDNSfalse; + if (host && host->c[0]) sr->Host = *host; + else sr->Host.c[0] = 0; + + // Initialize the AuthRecord objects to sane values + mDNS_SetupResourceRecord(&sr->RR_ADV, mDNSNULL, InterfaceID, kDNSType_PTR, kDefaultTTLforShared, kDNSRecordTypeAdvisory, ServiceCallback, sr); + mDNS_SetupResourceRecord(&sr->RR_PTR, mDNSNULL, InterfaceID, kDNSType_PTR, kDefaultTTLforShared, kDNSRecordTypeShared, ServiceCallback, sr); + mDNS_SetupResourceRecord(&sr->RR_SRV, mDNSNULL, InterfaceID, kDNSType_SRV, kDefaultTTLforUnique, kDNSRecordTypeUnique, ServiceCallback, sr); + mDNS_SetupResourceRecord(&sr->RR_TXT, mDNSNULL, InterfaceID, kDNSType_TXT, kDefaultTTLforUnique, kDNSRecordTypeUnique, ServiceCallback, sr); + + // If the client is registering an oversized TXT record, + // it is the client's responsibility to alloate a ServiceRecordSet structure that is large enough for it + if (sr->RR_TXT.resrec.rdata->MaxRDLength < txtlen) + sr->RR_TXT.resrec.rdata->MaxRDLength = txtlen; + + // Set up the record names + // For now we only create an advisory record for the main type, not for subtypes + // We need to gain some operational experience before we decide if there's a need to create them for subtypes too + if (ConstructServiceName(&sr->RR_ADV.resrec.name, (domainlabel*)"\x09_services", (domainname*)"\x05_mdns\x04_udp", domain) == mDNSNULL) + return(mStatus_BadParamErr); + if (ConstructServiceName(&sr->RR_PTR.resrec.name, mDNSNULL, type, domain) == mDNSNULL) return(mStatus_BadParamErr); + if (ConstructServiceName(&sr->RR_SRV.resrec.name, name, type, domain) == mDNSNULL) return(mStatus_BadParamErr); + AssignDomainName(sr->RR_TXT.resrec.name, sr->RR_SRV.resrec.name); + + // 1. Set up the ADV record rdata to advertise our service type + AssignDomainName(sr->RR_ADV.resrec.rdata->u.name, sr->RR_PTR.resrec.name); + + // 2. Set up the PTR record rdata to point to our service name + // We set up two additionals, so when a client asks for this PTR we automatically send the SRV and the TXT too + AssignDomainName(sr->RR_PTR.resrec.rdata->u.name, sr->RR_SRV.resrec.name); + sr->RR_PTR.Additional1 = &sr->RR_SRV; + sr->RR_PTR.Additional2 = &sr->RR_TXT; + + // 2a. Set up any subtype PTRs to point to our service name + // If the client is using subtypes, it is the client's responsibility to have + // already set the first label of the record name to the subtype being registered + for (i=0; iSubTypes[i].resrec.name; + mDNS_SetupResourceRecord(&sr->SubTypes[i], mDNSNULL, InterfaceID, kDNSType_PTR, kDefaultTTLforShared, kDNSRecordTypeShared, ServiceCallback, sr); + if (ConstructServiceName(&sr->SubTypes[i].resrec.name, &s, type, domain) == mDNSNULL) return(mStatus_BadParamErr); + AssignDomainName(sr->SubTypes[i].resrec.rdata->u.name, sr->RR_SRV.resrec.name); + sr->SubTypes[i].Additional1 = &sr->RR_SRV; + sr->SubTypes[i].Additional2 = &sr->RR_TXT; + } + + // 3. Set up the SRV record rdata. + sr->RR_SRV.resrec.rdata->u.srv.priority = 0; + sr->RR_SRV.resrec.rdata->u.srv.weight = 0; + sr->RR_SRV.resrec.rdata->u.srv.port = port; + + // Setting HostTarget tells DNS that the target of this SRV is to be automatically kept in sync with our host name + if (sr->Host.c[0]) AssignDomainName(sr->RR_SRV.resrec.rdata->u.srv.target, sr->Host); + else sr->RR_SRV.HostTarget = mDNStrue; + + // 4. Set up the TXT record rdata, + // and set DependentOn because we're depending on the SRV record to find and resolve conflicts for us + if (txtinfo == mDNSNULL) sr->RR_TXT.resrec.rdlength = 0; + else if (txtinfo != sr->RR_TXT.resrec.rdata->u.txt.c) + { + sr->RR_TXT.resrec.rdlength = txtlen; + if (sr->RR_TXT.resrec.rdlength > sr->RR_TXT.resrec.rdata->MaxRDLength) return(mStatus_BadParamErr); + mDNSPlatformMemCopy(txtinfo, sr->RR_TXT.resrec.rdata->u.txt.c, txtlen); + } + sr->RR_TXT.DependentOn = &sr->RR_SRV; + + mDNS_Lock(m); + err = mDNS_Register_internal(m, &sr->RR_SRV); + if (!err) err = mDNS_Register_internal(m, &sr->RR_TXT); + // We register the RR_PTR last, because we want to be sure that in the event of a forced call to + // mDNS_Close, the RR_PTR will be the last one to be forcibly deregistered, since that is what triggers + // the mStatus_MemFree callback to ServiceCallback, which in turn passes on the mStatus_MemFree back to + // the client callback, which is then at liberty to free the ServiceRecordSet memory at will. We need to + // make sure we've deregistered all our records and done any other necessary cleanup before that happens. + if (!err) err = mDNS_Register_internal(m, &sr->RR_ADV); + for (i=0; iSubTypes[i]); + if (!err) err = mDNS_Register_internal(m, &sr->RR_PTR); + mDNS_Unlock(m); + + if (err) mDNS_DeregisterService(m, sr); + return(err); + } + +mDNSexport mStatus mDNS_AddRecordToService(mDNS *const m, ServiceRecordSet *sr, + ExtraResourceRecord *extra, RData *rdata, mDNSu32 ttl) + { + mStatus result = mStatus_UnknownErr; + ExtraResourceRecord **e = &sr->Extras; + while (*e) e = &(*e)->next; + + // If TTL is unspecified, make it the same as the service's TXT and SRV default + if (ttl == 0) ttl = kDefaultTTLforUnique; + + extra->next = mDNSNULL; + mDNS_SetupResourceRecord(&extra->r, rdata, sr->RR_PTR.resrec.InterfaceID, extra->r.resrec.rrtype, ttl, kDNSRecordTypeUnique, ServiceCallback, sr); + AssignDomainName(extra->r.resrec.name, sr->RR_SRV.resrec.name); + extra->r.DependentOn = &sr->RR_SRV; + + debugf("mDNS_AddRecordToService adding record to %##s", extra->r.resrec.name.c); + + result = mDNS_Register(m, &extra->r); + if (!result) *e = extra; + return result; + } + +mDNSexport mStatus mDNS_RemoveRecordFromService(mDNS *const m, ServiceRecordSet *sr, ExtraResourceRecord *extra) + { + ExtraResourceRecord **e = &sr->Extras; + while (*e && *e != extra) e = &(*e)->next; + if (!*e) + { + debugf("mDNS_RemoveRecordFromService failed to remove record from %##s", extra->r.resrec.name.c); + return(mStatus_BadReferenceErr); + } + + debugf("mDNS_RemoveRecordFromService removing record from %##s", extra->r.resrec.name.c); + + *e = (*e)->next; + return(mDNS_Deregister(m, &extra->r)); + } + +mDNSexport mStatus mDNS_RenameAndReregisterService(mDNS *const m, ServiceRecordSet *const sr, const domainlabel *newname) + { + domainlabel name; + domainname type, domain; + domainname *host = mDNSNULL; + ExtraResourceRecord *extras = sr->Extras; + mStatus err; + + DeconstructServiceName(&sr->RR_SRV.resrec.name, &name, &type, &domain); + if (!newname) + { + IncrementLabelSuffix(&name, mDNStrue); + newname = &name; + } + debugf("Reregistering as %#s", newname->c); + if (sr->RR_SRV.HostTarget == mDNSfalse && sr->Host.c[0]) host = &sr->Host; + + err = mDNS_RegisterService(m, sr, newname, &type, &domain, + host, sr->RR_SRV.resrec.rdata->u.srv.port, sr->RR_TXT.resrec.rdata->u.txt.c, sr->RR_TXT.resrec.rdlength, + sr->SubTypes, sr->NumSubTypes, + sr->RR_PTR.resrec.InterfaceID, sr->ServiceCallback, sr->ServiceContext); + + // mDNS_RegisterService() just reset sr->Extras to NULL. + // Fortunately we already grabbed ourselves a copy of this pointer (above), so we can now run + // through the old list of extra records, and re-add them to our freshly created service registration + while (!err && extras) + { + ExtraResourceRecord *e = extras; + extras = extras->next; + err = mDNS_AddRecordToService(m, sr, e, e->r.resrec.rdata, e->r.resrec.rroriginalttl); + } + + return(err); + } + +// NOTE: mDNS_DeregisterService calls mDNS_Deregister_internal which can call a user callback, +// which may change the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSexport mStatus mDNS_DeregisterService(mDNS *const m, ServiceRecordSet *sr) + { + if (sr->RR_PTR.resrec.RecordType == kDNSRecordTypeUnregistered) + { + debugf("Service set for %##s already deregistered", sr->RR_PTR.resrec.name.c); + return(mStatus_BadReferenceErr); + } + else if (sr->RR_PTR.resrec.RecordType == kDNSRecordTypeDeregistering) + { + debugf("Service set for %##s already in the process of deregistering", sr->RR_PTR.resrec.name.c); + return(mStatus_NoError); + } + else + { + mDNSu32 i; + mStatus status; + ExtraResourceRecord *e; + mDNS_Lock(m); + e = sr->Extras; + + // We use mDNS_Dereg_repeat because, in the event of a collision, some or all of the + // SRV, TXT, or Extra records could have already been automatically deregistered, and that's okay + mDNS_Deregister_internal(m, &sr->RR_SRV, mDNS_Dereg_repeat); + mDNS_Deregister_internal(m, &sr->RR_TXT, mDNS_Dereg_repeat); + + mDNS_Deregister_internal(m, &sr->RR_ADV, mDNS_Dereg_normal); + + // We deregister all of the extra records, but we leave the sr->Extras list intact + // in case the client wants to do a RenameAndReregister and reinstate the registration + while (e) + { + mDNS_Deregister_internal(m, &e->r, mDNS_Dereg_repeat); + e = e->next; + } + + for (i=0; iNumSubTypes; i++) + mDNS_Deregister_internal(m, &sr->SubTypes[i], mDNS_Dereg_normal); + + // Be sure to deregister the PTR last! + // Deregistering this record is what triggers the mStatus_MemFree callback to ServiceCallback, + // which in turn passes on the mStatus_MemFree (or mStatus_NameConflict) back to the client callback, + // which is then at liberty to free the ServiceRecordSet memory at will. We need to make sure + // we've deregistered all our records and done any other necessary cleanup before that happens. + status = mDNS_Deregister_internal(m, &sr->RR_PTR, mDNS_Dereg_normal); + mDNS_Unlock(m); + return(status); + } + } + +// Create a registration that asserts that no such service exists with this name. +// This can be useful where there is a given function is available through several protocols. +// For example, a printer called "Stuart's Printer" may implement printing via the "pdl-datastream" and "IPP" +// protocols, but not via "LPR". In this case it would be prudent for the printer to assert the non-existence of an +// "LPR" service called "Stuart's Printer". Without this precaution, another printer than offers only "LPR" printing +// could inadvertently advertise its service under the same name "Stuart's Printer", which might be confusing for users. +mDNSexport mStatus mDNS_RegisterNoSuchService(mDNS *const m, AuthRecord *const rr, + const domainlabel *const name, const domainname *const type, const domainname *const domain, + const domainname *const host, + const mDNSInterfaceID InterfaceID, mDNSRecordCallback Callback, void *Context) + { + mDNS_SetupResourceRecord(rr, mDNSNULL, InterfaceID, kDNSType_SRV, kDefaultTTLforUnique, kDNSRecordTypeUnique, Callback, Context); + if (ConstructServiceName(&rr->resrec.name, name, type, domain) == mDNSNULL) return(mStatus_BadParamErr); + rr->resrec.rdata->u.srv.priority = 0; + rr->resrec.rdata->u.srv.weight = 0; + rr->resrec.rdata->u.srv.port = zeroIPPort; + if (host && host->c[0]) AssignDomainName(rr->resrec.rdata->u.srv.target, *host); + else rr->HostTarget = mDNStrue; + return(mDNS_Register(m, rr)); + } + +mDNSexport mStatus mDNS_AdvertiseDomains(mDNS *const m, AuthRecord *rr, + mDNS_DomainType DomainType, const mDNSInterfaceID InterfaceID, char *domname) + { + mDNS_SetupResourceRecord(rr, mDNSNULL, InterfaceID, kDNSType_PTR, kDefaultTTLforShared, kDNSRecordTypeShared, mDNSNULL, mDNSNULL); + if (!MakeDomainNameFromDNSNameString(&rr->resrec.name, mDNS_DomainTypeNames[DomainType])) return(mStatus_BadParamErr); + if (!MakeDomainNameFromDNSNameString(&rr->resrec.rdata->u.name, domname)) return(mStatus_BadParamErr); + return(mDNS_Register(m, rr)); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - +#pragma mark - Startup and Shutdown +#endif + +mDNSexport void mDNS_GrowCache(mDNS *const m, CacheRecord *storage, mDNSu32 numrecords) + { + if (storage && numrecords) + { + mDNSu32 i; + for (i=0; irrcache_free; + m->rrcache_free = storage; + m->rrcache_size += numrecords; + } + } + +mDNSexport mStatus mDNS_Init(mDNS *const m, mDNS_PlatformSupport *const p, + CacheRecord *rrcachestorage, mDNSu32 rrcachesize, + mDNSBool AdvertiseLocalAddresses, mDNSCallback *Callback, void *Context) + { + mDNSu32 slot; + mDNSs32 timenow; + mStatus result = mDNSPlatformTimeInit(&timenow); + if (result != mStatus_NoError) return(result); + + if (!rrcachestorage) rrcachesize = 0; + + m->p = p; + m->KnownBugs = 0; + m->AdvertiseLocalAddresses = AdvertiseLocalAddresses; + m->mDNSPlatformStatus = mStatus_Waiting; + m->MainCallback = Callback; + m->MainContext = Context; + + // For debugging: To catch and report locking failures + m->mDNS_busy = 0; + m->mDNS_reentrancy = 0; + m->mDNS_shutdown = mDNSfalse; + m->lock_rrcache = 0; + m->lock_Questions = 0; + m->lock_Records = 0; + + // Task Scheduling variables + m->timenow = 0; // MUST only be set within mDNS_Lock/mDNS_Unlock section + m->timenow_last = timenow; + m->timenow_adjust = 0; + m->NextScheduledEvent = timenow; + m->SuppressSending = timenow; + m->NextCacheCheck = timenow + 0x78000000; + m->NextScheduledQuery = timenow + 0x78000000; + m->NextScheduledProbe = timenow + 0x78000000; + m->NextScheduledResponse = timenow + 0x78000000; + m->ExpectUnicastResponse = timenow + 0x78000000; + m->RandomQueryDelay = 0; + m->SendDeregistrations = mDNSfalse; + m->SendImmediateAnswers = mDNSfalse; + m->SleepState = mDNSfalse; + + // These fields only required for mDNS Searcher... + m->Questions = mDNSNULL; + m->NewQuestions = mDNSNULL; + m->CurrentQuestion = mDNSNULL; + m->LocalOnlyQuestions = mDNSNULL; + m->NewLocalOnlyQuestions = mDNSNULL; + m->rrcache_size = 0; + m->rrcache_totalused = 0; + m->rrcache_active = 0; + m->rrcache_report = 10; + m->rrcache_free = mDNSNULL; + + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) + { + m->rrcache_hash[slot] = mDNSNULL; + m->rrcache_tail[slot] = &m->rrcache_hash[slot]; + m->rrcache_used[slot] = 0; + } + + mDNS_GrowCache(m, rrcachestorage, rrcachesize); + + // Fields below only required for mDNS Responder... + m->hostlabel.c[0] = 0; + m->nicelabel.c[0] = 0; + m->hostname.c[0] = 0; + m->HIHardware.c[0] = 0; + m->HISoftware.c[0] = 0; + m->ResourceRecords = mDNSNULL; + m->DuplicateRecords = mDNSNULL; + m->LocalOnlyRecords = mDNSNULL; + m->NewLocalOnlyRecords = mDNSNULL; + m->DiscardLocalOnlyRecords = mDNSfalse; + m->CurrentRecord = mDNSNULL; + m->HostInterfaces = mDNSNULL; + m->ProbeFailTime = 0; + m->NumFailedProbes = 0; + m->SuppressProbes = 0; + + result = mDNSPlatformInit(m); + + return(result); + } + +mDNSexport void mDNSCoreInitComplete(mDNS *const m, mStatus result) + { + m->mDNSPlatformStatus = result; + if (m->MainCallback) + m->MainCallback(m, mStatus_NoError); + } + +mDNSexport void mDNS_Close(mDNS *const m) + { + mDNSu32 rrcache_active = 0; + mDNSu32 rrcache_totalused = 0; + mDNSu32 slot; + NetworkInterfaceInfo *intf; + mDNS_Lock(m); + + m->mDNS_shutdown = mDNStrue; + + rrcache_totalused = m->rrcache_totalused; + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) + { + while (m->rrcache_hash[slot]) + { + CacheRecord *rr = m->rrcache_hash[slot]; + m->rrcache_hash[slot] = rr->next; + if (rr->CRActiveQuestion) rrcache_active++; + m->rrcache_used[slot]--; + ReleaseCacheRR(m, rr); + } + // Reset tail pointer back to empty state (not that it really matters on exit, but we'll do it anyway, for the sake of completeness) + m->rrcache_tail[slot] = &m->rrcache_hash[slot]; + } + debugf("mDNS_Close: RR Cache was using %ld records, %d active", rrcache_totalused, rrcache_active); + if (rrcache_active != m->rrcache_active) + LogMsg("*** ERROR *** rrcache_active %lu != m->rrcache_active %lu", rrcache_active, m->rrcache_active); + + m->Questions = mDNSNULL; // We won't be answering any more questions! + + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->Advertise) + mDNS_DeadvertiseInterface(m, intf); + + // Make sure there are nothing but deregistering records remaining in the list + if (m->CurrentRecord) LogMsg("mDNS_Close ERROR m->CurrentRecord already set"); + m->CurrentRecord = m->ResourceRecords; + while (m->CurrentRecord) + { + AuthRecord *rr = m->CurrentRecord; + m->CurrentRecord = rr->next; + if (rr->resrec.RecordType != kDNSRecordTypeDeregistering) + { + debugf("mDNS_Close: Record type %X still in ResourceRecords list %##s", rr->resrec.RecordType, rr->resrec.name.c); + mDNS_Deregister_internal(m, rr, mDNS_Dereg_normal); + } + } + + if (m->ResourceRecords) debugf("mDNS_Close: Sending final packets for deregistering records"); + else debugf("mDNS_Close: No deregistering records remain"); + + // If any deregistering records remain, send their deregistration announcements before we exit + if (m->mDNSPlatformStatus != mStatus_NoError) + DiscardDeregistrations(m); + else + while (m->ResourceRecords) + SendResponses(m); + + mDNS_Unlock(m); + debugf("mDNS_Close: mDNSPlatformClose"); + mDNSPlatformClose(m); + debugf("mDNS_Close: done"); + } diff --git a/src/mDNSClientAPI.h b/src/mDNSClientAPI.h new file mode 100644 index 00000000..5326db69 --- /dev/null +++ b/src/mDNSClientAPI.h @@ -0,0 +1,1426 @@ +/* + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * 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$ +Revision 1.1 2004/03/29 17:55:17 rpedde +Flatten mdns stuff + +Revision 1.2 2004/03/02 00:03:37 rpedde +Merge new rendezvous code + +Revision 1.114.2.3 2003/12/05 00:03:34 cheshire + Use buffer size MAX_ESCAPED_DOMAIN_NAME instead of 256 + +Revision 1.114.2.2 2003/12/04 23:30:00 cheshire +Add "#define MAX_ESCAPED_DOMAIN_NAME 1005", needed for Posix folder to build + +Revision 1.114.2.1 2003/12/03 11:07:58 cheshire +: Stop and start of a service uses old ip address (with old port number) + +Revision 1.114 2003/08/29 19:44:15 cheshire + Traffic reduction: Eliminate synchronized QUs when a new service appears +1. Use m->RandomQueryDelay to impose a random delay in the range 0-500ms on queries + that already have at least one unique answer in the cache +2. For these queries, go straight to QM, skipping QU + +Revision 1.113 2003/08/21 19:31:58 cheshire +Cosmetic: Swap order of fields + +Revision 1.112 2003/08/21 19:27:36 cheshire + Traffic reduction: No need to announce record for longer than TTL + +Revision 1.111 2003/08/21 02:21:50 cheshire + Efficiency: Reduce repeated queries + +Revision 1.110 2003/08/20 23:39:31 cheshire + Review syslog messages, and remove as appropriate + +Revision 1.109 2003/08/19 22:24:10 cheshire +Comment change + +Revision 1.108 2003/08/19 22:20:00 cheshire + Don't use IPv6 on interfaces that have a routable IPv4 address configured +More minor refinements + +Revision 1.107 2003/08/19 06:48:25 cheshire + Guard against excessive record updates +Each record starts with 10 UpdateCredits. +Every update consumes one UpdateCredit. +UpdateCredits are replenished at a rate of one one per minute, up to a maximum of 10. +As the number of UpdateCredits declines, the number of announcements is similarly scaled back. +When fewer than 5 UpdateCredits remain, the first announcement is also delayed by an increasing amount. + +Revision 1.106 2003/08/19 04:49:28 cheshire + Interaction between v4, v6 and dual-stack hosts not working quite right +1. A dual-stack host should only suppress its own query if it sees the same query from other hosts on BOTH IPv4 and IPv6. +2. When we see the first v4 (or first v6) member of a group, we re-trigger questions and probes on that interface. +3. When we see the last v4 (or v6) member of a group go away, we revalidate all the records received on that interface. + +Revision 1.105 2003/08/19 02:33:37 cheshire +Update comments + +Revision 1.104 2003/08/19 02:31:11 cheshire + mDNSResponder overenthusiastic with final expiration queries +Final expiration queries now only mark the question for sending on the particular interface +pertaining to the record that's expiring. + +Revision 1.103 2003/08/18 19:05:44 cheshire + UpdateRecord not working right +Added "newrdlength" field to hold new length of updated rdata + +Revision 1.102 2003/08/16 03:39:00 cheshire + InterfaceID -1 indicates "local only" + +Revision 1.101 2003/08/15 20:16:02 cheshire + mDNSResponder takes too much RPRVT +We want to avoid touching the rdata pages, so we don't page them in. +1. RDLength was stored with the rdata, which meant touching the page just to find the length. + Moved this from the RData to the ResourceRecord object. +2. To avoid unnecessarily touching the rdata just to compare it, + compute a hash of the rdata and store the hash in the ResourceRecord object. + +Revision 1.100 2003/08/14 19:29:04 cheshire + Include cache records in SIGINFO output +Moved declarations of DNSTypeName() and GetRRDisplayString to mDNSClientAPI.h so daemon.c can use them + +Revision 1.99 2003/08/14 02:17:05 cheshire + Split generic ResourceRecord type into two separate types: AuthRecord and CacheRecord + +Revision 1.98 2003/08/12 19:56:23 cheshire +Update to APSL 2.0 + +Revision 1.97 2003/08/12 14:59:27 cheshire + Rate-limiting blocks some legitimate responses +When setting LastMCTime also record LastMCInterface. When checking LastMCTime to determine +whether to suppress the response, also check LastMCInterface to see if it matches. + +Revision 1.96 2003/08/12 13:57:04 cheshire + Improve cache performance +Changed the number of hash table slots from 37 to 499 + +Revision 1.95 2003/08/09 00:55:02 cheshire + mDNSResponder is taking 20-30% of the CPU +Don't scan the whole cache after every packet. + +Revision 1.94 2003/08/09 00:35:29 cheshire + +Revision 1.93 2003/08/08 18:55:48 cheshire + Guard against time going backwards + +Revision 1.92 2003/08/08 18:36:04 cheshire + Only need to revalidate on interface removal on platforms that have the PhantomInterfaces bug + +Revision 1.91 2003/08/06 21:33:39 cheshire +Fix compiler warnings on PocketPC 2003 (Windows CE) + +Revision 1.90 2003/08/06 20:30:17 cheshire +Add structure definition for rdataMX (not currently used, but good to have it for completeness) + +Revision 1.89 2003/08/06 18:58:19 cheshire +Update comments + +Revision 1.88 2003/07/24 23:45:44 cheshire +To eliminate compiler warnings, changed definition of mDNSBool from +"unsigned char" to "int", since "int" is in fact truly the type that C uses +for the result of comparison operators (a: Feature: New Rendezvous APIs (#7875) (mDNSResponder component) +Added error type for incompatibility between daemon and client versions + +Revision 1.85 2003/07/19 03:23:13 cheshire + mDNSResponder needs to receive and cache larger records + +Revision 1.84 2003/07/18 23:52:12 cheshire +To improve consistency of field naming, global search-and-replace: +NextProbeTime -> NextScheduledProbe +NextResponseTime -> NextScheduledResponse + +Revision 1.83 2003/07/18 00:29:59 cheshire + Remove mDNSResponder version from packet header and use HINFO record instead + +Revision 1.82 2003/07/17 17:35:04 cheshire + Rate-limit responses, to guard against packet flooding + +Revision 1.81 2003/07/16 05:01:36 cheshire +Add fields 'LargeAnswers' and 'ExpectUnicastResponse' in preparation for + Need to implement "unicast response" request, using top bit of qclass + +Revision 1.80 2003/07/15 01:55:12 cheshire + Need to implement service registration with subtypes + +Revision 1.79 2003/07/13 02:28:00 cheshire + SendResponses didn't all its responses +Delete all references to RRInterfaceActive -- it's now superfluous + +Revision 1.78 2003/07/13 01:47:53 cheshire +Fix one error and one warning in the Windows build + +Revision 1.77 2003/07/11 01:32:38 cheshire +Syntactic cleanup (no change to funcationality): Now that we only have one host name, +rename field "hostname1" to "hostname", and field "RR_A1" to "RR_A". + +Revision 1.76 2003/07/11 01:28:00 cheshire + No more local.arpa + +Revision 1.75 2003/07/02 21:19:45 cheshire + Update copyright notices, etc., in source code comments + +Revision 1.74 2003/07/02 02:41:23 cheshire + mDNSResponder needs to start with a smaller cache and then grow it as needed + +Revision 1.73 2003/06/10 04:24:39 cheshire + React when we observe other people query unsuccessfully for a record that's in our cache +Some additional refinements: +Don't try to do this for unicast-response queries +better tracking of Qs and KAs in multi-packet KA lists + +Revision 1.72 2003/06/10 01:46:27 cheshire +Add better comments explaining how these data structures are intended to be used from the client layer + +Revision 1.71 2003/06/07 06:45:05 cheshire + No need for multiple machines to all be sending the same queries + +Revision 1.70 2003/06/07 04:50:53 cheshire + React when we observe other people query unsuccessfully for a record that's in our cache + +Revision 1.69 2003/06/07 04:22:17 cheshire +Add MsgBuffer for error log and debug messages + +Revision 1.68 2003/06/07 01:46:38 cheshire + When query produces zero results, call mDNS_Reconfirm() on any antecedent records + +Revision 1.67 2003/06/07 01:22:14 cheshire + mDNSResponder needs an mDNS_Reconfirm() function + +Revision 1.66 2003/06/07 00:59:43 cheshire + Need some randomness to spread queries on the network + +Revision 1.65 2003/06/06 21:41:11 cheshire +For consistency, mDNS_StopQuery() should return an mStatus result, just like all the other mDNSCore routines + +Revision 1.64 2003/06/06 21:38:55 cheshire +Renamed 'NewData' as 'FreshData' (The data may not be new data, just a refresh of data that we +already had in our cache. This refreshes our TTL on the data, but the data itself stays the same.) + +Revision 1.63 2003/06/06 17:20:14 cheshire +For clarity, rename question fields name/rrtype/rrclass as qname/qtype/qclass +(Global search-and-replace; no functional change to code execution.) + +Revision 1.62 2003/06/04 01:25:33 cheshire + Cannot perform multi-packet known-answer suppression messages +Display time interval between first and subsequent queries + +Revision 1.61 2003/06/03 05:02:16 cheshire + Duplicate registrations not handled as efficiently as they should be + +Revision 1.60 2003/05/31 00:09:49 cheshire + Add ability to discover what services are on a network + +Revision 1.59 2003/05/29 06:11:35 cheshire +: Report if there appear to be too many "Resolve" callbacks + +Revision 1.58 2003/05/29 05:48:06 cheshire +Minor fix for when generating printf warnings: mDNS_snprintf arguments are now 3,4 + +Revision 1.57 2003/05/26 03:21:27 cheshire +Tidy up address structure naming: +mDNSIPAddr => mDNSv4Addr (for consistency with mDNSv6Addr) +mDNSAddr.addr.ipv4 => mDNSAddr.ip.v4 +mDNSAddr.addr.ipv6 => mDNSAddr.ip.v6 + +Revision 1.56 2003/05/26 03:01:27 cheshire + sprintf/vsprintf-style functions are unsafe; use snprintf/vsnprintf instead + +Revision 1.55 2003/05/26 00:47:30 cheshire +Comment clarification + +Revision 1.54 2003/05/24 16:39:48 cheshire + SendResponses also needs to handle multihoming better + +Revision 1.53 2003/05/23 02:15:37 cheshire +Fixed misleading use of the term "duplicate suppression" where it should have +said "known answer suppression". (Duplicate answer suppression is something +different, and duplicate question suppression is yet another thing, so the use +of the completely vague term "duplicate suppression" was particularly bad.) + +Revision 1.52 2003/05/22 02:29:22 cheshire + SendQueries needs to handle multihoming better +Complete rewrite of SendQueries. Works much better now :-) + +Revision 1.51 2003/05/21 20:14:55 cheshire +Fix comments and warnings + +Revision 1.50 2003/05/14 07:08:36 cheshire + mDNSResponder should be smarter about reconfigurations +Previously, when there was any network configuration change, mDNSResponder +would tear down the entire list of active interfaces and start again. +That was very disruptive, and caused the entire cache to be flushed, +and caused lots of extra network traffic. Now it only removes interfaces +that have really gone, and only adds new ones that weren't there before. + +Revision 1.49 2003/05/07 01:49:36 cheshire +Remove "const" in ConstructServiceName prototype + +Revision 1.48 2003/05/07 00:18:44 cheshire +Fix typo: "kDNSQClass_Mask" should be "kDNSClass_Mask" + +Revision 1.47 2003/05/06 00:00:46 cheshire + Rationalize naming of domainname manipulation functions + +Revision 1.46 2003/04/30 20:39:09 cheshire +Add comment + +Revision 1.45 2003/04/29 00:40:50 cheshire +Fix compiler warnings + +Revision 1.44 2003/04/26 02:41:56 cheshire + Change timenow from a local variable to a structure member + +Revision 1.43 2003/04/25 01:45:56 cheshire + mDNS_RegisterNoSuchService needs to include a host name + +Revision 1.42 2003/04/15 20:58:31 jgraessl + +Bug #: 3229014 +Added a hash to lookup records in the cache. + +Revision 1.41 2003/04/15 18:09:13 jgraessl + +Bug #: 3228892 +Reviewed by: Stuart Cheshire +Added code to keep track of when the next cache item will expire so we can +call TidyRRCache only when necessary. + +Revision 1.40 2003/03/29 01:55:19 cheshire + mDNSResponder sometimes suffers false self-conflicts when it sees its own packets +Solution: Major cleanup of packet timing and conflict handling rules + +Revision 1.39 2003/03/27 03:30:55 cheshire + Name conflicts not handled properly, resulting in memory corruption, and eventual crash +Problem was that HostNameCallback() was calling mDNS_DeregisterInterface(), which is not safe in a callback +Fixes: +1. Make mDNS_DeregisterInterface() safe to call from a callback +2. Make HostNameCallback() use mDNS_DeadvertiseInterface() instead + (it never really needed to deregister the interface at all) + +Revision 1.38 2003/03/15 04:40:36 cheshire +Change type called "mDNSOpaqueID" to the more descriptive name "mDNSInterfaceID" + +Revision 1.37 2003/03/14 21:34:11 cheshire + Can't setup and print to Lexmark PS printers via Airport Extreme +Increase size of cache rdata from 512 to 768 + +Revision 1.36 2003/03/05 03:38:35 cheshire +Bug #: 3185731 Bogus error message in console: died or deallocated, but no record of client can be found! +Fixed by leaving client in list after conflict, until client explicitly deallocates + +Revision 1.35 2003/02/21 02:47:54 cheshire +Bug #: 3099194 mDNSResponder needs performance improvements +Several places in the code were calling CacheRRActive(), which searched the entire +question list every time, to see if this cache resource record answers any question. +Instead, we now have a field "CRActiveQuestion" in the resource record structure + +Revision 1.34 2003/02/21 01:54:08 cheshire +Bug #: 3099194 mDNSResponder needs performance improvements +Switched to using new "mDNS_Execute" model (see "Implementer Notes.txt") + +Revision 1.33 2003/02/20 06:48:32 cheshire +Bug #: 3169535 Xserve RAID needs to do interface-specific registrations +Reviewed by: Josh Graessley, Bob Bradley + +Revision 1.32 2003/01/31 03:35:59 cheshire +Bug #: 3147097 mDNSResponder sometimes fails to find the correct results +When there were *two* active questions in the list, they were incorrectly +finding *each other* and *both* being marked as duplicates of another question + +Revision 1.31 2003/01/29 02:46:37 cheshire +Fix for IPv6: +A physical interface is identified solely by its InterfaceID (not by IP and type). +On a given InterfaceID, mDNSCore may send both v4 and v6 multicasts. +In cases where the requested outbound protocol (v4 or v6) is not supported on +that InterfaceID, the platform support layer should simply discard that packet. + +Revision 1.30 2003/01/29 01:47:08 cheshire +Rename 'Active' to 'CRActive' or 'InterfaceActive' for improved clarity + +Revision 1.29 2003/01/28 05:23:43 cheshire +Bug #: 3147097 mDNSResponder sometimes fails to find the correct results +Add 'Active' flag for interfaces + +Revision 1.28 2003/01/28 01:35:56 cheshire +Revise comment about ThisQInterval to reflect new semantics + +Revision 1.27 2003/01/13 23:49:42 jgraessl +Merged changes for the following fixes in to top of tree: +3086540 computer name changes not handled properly +3124348 service name changes are not properly handled +3124352 announcements sent in pairs, failing chattiness test + +Revision 1.26 2002/12/23 22:13:28 jgraessl + +Reviewed by: Stuart Cheshire +Initial IPv6 support for mDNSResponder. + +Revision 1.25 2002/09/21 20:44:49 zarzycki +Added APSL info + +Revision 1.24 2002/09/19 23:47:35 cheshire +Added mDNS_RegisterNoSuchService() function for assertion of non-existance +of a particular named service + +Revision 1.23 2002/09/19 21:25:34 cheshire +mDNS_snprintf() doesn't need to be in a separate file + +Revision 1.22 2002/09/19 04:20:43 cheshire +Remove high-ascii characters that confuse some systems + +Revision 1.21 2002/09/17 01:06:35 cheshire +Change mDNS_AdvertiseLocalAddresses to be a parameter to mDNS_Init() + +Revision 1.20 2002/09/16 18:41:41 cheshire +Merge in license terms from Quinn's copy, in preparation for Darwin release + +*/ + +#ifndef __mDNSClientAPI_h +#define __mDNSClientAPI_h + +#include // stdarg.h is required for for va_list support for the mDNS_vsnprintf declaration +#include "mDNSDebug.h" + +#ifdef __cplusplus + extern "C" { +#endif + +// *************************************************************************** +// Function scope indicators + +// If you see "mDNSlocal" before a function name, it means the function is not callable outside this file +#ifndef mDNSlocal +#define mDNSlocal static +#endif +// If you see "mDNSexport" before a symbol, it means the symbol is exported for use by clients +#ifndef mDNSexport +#define mDNSexport +#endif + +// *************************************************************************** +#if 0 +#pragma mark - DNS Resource Record class and type constants +#endif + +typedef enum // From RFC 1035 + { + kDNSClass_IN = 1, // Internet + kDNSClass_CS = 2, // CSNET + kDNSClass_CH = 3, // CHAOS + kDNSClass_HS = 4, // Hesiod + kDNSClass_NONE = 254, // Used in DNS UPDATE [RFC 2136] + + kDNSClass_Mask = 0x7FFF,// Multicast DNS uses the bottom 15 bits to identify the record class... + kDNSClass_UniqueRRSet = 0x8000,// ... and the top bit indicates that all other cached records are now invalid + + kDNSQClass_ANY = 255, // Not a DNS class, but a DNS query class, meaning "all classes" + kDNSQClass_UnicastResponse = 0x8000 // Top bit set in a question means "unicast response acceptable" + } DNS_ClassValues; + +typedef enum // From RFC 1035 + { + kDNSType_A = 1, // 1 Address + kDNSType_NS, // 2 Name Server + kDNSType_MD, // 3 Mail Destination + kDNSType_MF, // 4 Mail Forwarder + kDNSType_CNAME, // 5 Canonical Name + kDNSType_SOA, // 6 Start of Authority + kDNSType_MB, // 7 Mailbox + kDNSType_MG, // 8 Mail Group + kDNSType_MR, // 9 Mail Rename + kDNSType_NULL, // 10 NULL RR + kDNSType_WKS, // 11 Well-known-service + kDNSType_PTR, // 12 Domain name pointer + kDNSType_HINFO, // 13 Host information + kDNSType_MINFO, // 14 Mailbox information + kDNSType_MX, // 15 Mail Exchanger + kDNSType_TXT, // 16 Arbitrary text string + + kDNSType_AAAA = 28, // 28 IPv6 address + kDNSType_SRV = 33, // 33 Service record + + kDNSQType_ANY = 255 // Not a DNS type, but a DNS query type, meaning "all types" + } DNS_TypeValues; + +// *************************************************************************** +#if 0 +#pragma mark - Simple types +#endif + +// mDNS defines its own names for these common types to simplify portability across +// multiple platforms that may each have their own (different) names for these types. +typedef int mDNSBool; +typedef signed char mDNSs8; +typedef unsigned char mDNSu8; +typedef signed short mDNSs16; +typedef unsigned short mDNSu16; +typedef signed long mDNSs32; +typedef unsigned long mDNSu32; + +// To enforce useful type checking, we make mDNSInterfaceID be a pointer to a dummy struct +// This way, mDNSInterfaceIDs can be assigned, and compared with each other, but not with other types +// Declaring the type to be the typical generic "void *" would lack this type checking +typedef struct { void *dummy; } *mDNSInterfaceID; + +// These types are for opaque two- and four-byte identifiers. +// The "NotAnInteger" fields of the unions allow the value to be conveniently passed around in a +// register for the sake of efficiency, and compared for equality or inequality, but don't forget -- +// just because it is in a register doesn't mean it is an integer. Operations like greater than, +// less than, add, multiply, increment, decrement, etc., are undefined for opaque identifiers, +// and if you make the mistake of trying to do those using the NotAnInteger field, then you'll +// find you get code that doesn't work consistently on big-endian and little-endian machines. +typedef union { mDNSu8 b[2]; mDNSu16 NotAnInteger; } mDNSOpaque16; +typedef union { mDNSu8 b[4]; mDNSu32 NotAnInteger; } mDNSOpaque32; +typedef union { mDNSu8 b[16]; mDNSu16 w[8]; mDNSu32 l[4]; } mDNSOpaque128; + +typedef mDNSOpaque16 mDNSIPPort; // An IP port is a two-byte opaque identifier (not an integer) +typedef mDNSOpaque32 mDNSv4Addr; // An IP address is a four-byte opaque identifier (not an integer) +typedef mDNSOpaque128 mDNSv6Addr; // An IPv6 address is a 16-byte opaque identifier (not an integer) + +enum + { + mDNSAddrType_None = 0, + mDNSAddrType_IPv4 = 4, + mDNSAddrType_IPv6 = 6, + mDNSAddrType_Unknown = ~0 // Special marker value used in known answer list recording + }; + +typedef struct + { + mDNSs32 type; + union { mDNSv6Addr v6; mDNSv4Addr v4; } ip; + } mDNSAddr; + +enum { mDNSfalse = 0, mDNStrue = 1 }; + +#define mDNSNULL 0L + +enum + { + mStatus_Waiting = 1, + mStatus_NoError = 0, + + // mDNS return values are in the range FFFE FF00 (-65792) to FFFE FFFF (-65537) + // The top end of the range (FFFE FFFF) is used for error codes; + // the bottom end of the range (FFFE FF00) is used for non-error values; + + // Error codes: + mStatus_UnknownErr = -65537, // 0xFFFE FFFF + mStatus_NoSuchNameErr = -65538, + mStatus_NoMemoryErr = -65539, + mStatus_BadParamErr = -65540, + mStatus_BadReferenceErr = -65541, + mStatus_BadStateErr = -65542, + mStatus_BadFlagsErr = -65543, + mStatus_UnsupportedErr = -65544, + mStatus_NotInitializedErr = -65545, + mStatus_NoCache = -65546, + mStatus_AlreadyRegistered = -65547, + mStatus_NameConflict = -65548, + mStatus_Invalid = -65549, + // = -65550, + mStatus_Incompatible = -65551, + mStatus_BadInterfaceErr = -65552, + + // -65553 - -65789 currently unused + + // Non-error values: + mStatus_GrowCache = -65790, + mStatus_ConfigChanged = -65791, + mStatus_MemFree = -65792 // 0xFFFE FF00 + }; + +typedef mDNSs32 mStatus; + +// RFC 1034/1035 specify that a domain label consists of a length byte plus up to 63 characters +#define MAX_DOMAIN_LABEL 63 +typedef struct { mDNSu8 c[ 64]; } domainlabel; // One label: length byte and up to 63 characters + +// RFC 1034/1035 specify that a domain name, including length bytes, data bytes, and terminating zero, may be up to 255 bytes long +#define MAX_DOMAIN_NAME 255 +typedef struct { mDNSu8 c[256]; } domainname; // Up to 255 bytes of length-prefixed domainlabels + +typedef struct { mDNSu8 c[256]; } UTF8str255; // Null-terminated C string + +// The longest legal textual form of a DNS name is 1005 bytes, including the C-string terminating NULL at the end. +// Explanation: +// When a native domainname object is converted to printable textual form using ConvertDomainNameToCString(), +// non-printing characters are represented in the conventional DNS way, as '\ddd', where ddd is a three-digit decimal number. +// The longest legal domain name is 255 bytes, in the form of four labels as shown below: +// Length byte, 63 data bytes, length byte, 63 data bytes, length byte, 63 data bytes, length byte, 61 data bytes, zero byte. +// Each label is encoded textually as characters followed by a trailing dot. +// If every character has to be represented as a four-byte escape sequence, then this makes the maximum textual form four labels +// plus the C-string terminating NULL as shown below: +// 63*4+1 + 63*4+1 + 63*4+1 + 61*4+1 + 1 = 1005. +// Note that MAX_ESCAPED_DOMAIN_LABEL is not normally used: If you're only decoding a single label, escaping is usually not required. +// It is for domain names, where dots are used as label separators, that proper escaping is vital. +#define MAX_ESCAPED_DOMAIN_LABEL 254 +#define MAX_ESCAPED_DOMAIN_NAME 1005 + +// *************************************************************************** +#if 0 +#pragma mark - Resource Record structures +#endif + +// Authoritative Resource Records: +// There are four basic types: Shared, Advisory, Unique, Known Unique + +// * Shared Resource Records do not have to be unique +// -- Shared Resource Records are used for DNS-SD service PTRs +// -- It is okay for several hosts to have RRs with the same name but different RDATA +// -- We use a random delay on responses to reduce collisions when all the hosts respond to the same query +// -- These RRs typically have moderately high TTLs (e.g. one hour) +// -- These records are announced on startup and topology changes for the benefit of passive listeners +// -- These records send a goodbye packet when deregistering +// +// * Advisory Resource Records are like Shared Resource Records, except they don't send a goodbye packet +// +// * Unique Resource Records should be unique among hosts within any given mDNS scope +// -- The majority of Resource Records are of this type +// -- If two entities on the network have RRs with the same name but different RDATA, this is a conflict +// -- Responses may be sent immediately, because only one host should be responding to any particular query +// -- These RRs typically have low TTLs (e.g. ten seconds) +// -- On startup and after topology changes, a host issues queries to verify uniqueness + +// * Known Unique Resource Records are treated like Unique Resource Records, except that mDNS does +// not have to verify their uniqueness because this is already known by other means (e.g. the RR name +// is derived from the host's IP or Ethernet address, which is already known to be a unique identifier). + +// Summary of properties of different record types: +// Probe? Does this record type send probes before announcing? +// Conflict? Does this record type react if we observe an apparent conflict? +// Goodbye? Does this record type send a goodbye packet on departure? +// +// Probe? Conflict? Goodbye? Notes +// Unregistered Should not appear in any list (sanity check value) +// Shared No No Yes e.g. Service PTR record +// Deregistering No No Yes Shared record about to announce its departure and leave the list +// Advisory No No No +// Unique Yes Yes No Record intended to be unique -- will probe to verify +// Verified Yes Yes No Record has completed probing, and is verified unique +// KnownUnique No Yes No Record is assumed by other means to be unique + +// Valid lifecycle of a record: +// Unregistered -> Shared -> Deregistering -(goodbye)-> Unregistered +// Unregistered -> Advisory -> Unregistered +// Unregistered -> Unique -(probe)-> Verified -> Unregistered +// Unregistered -> KnownUnique -> Unregistered + +// Each Authoritative kDNSRecordType has only one bit set. This makes it easy to quickly see if a record +// is one of a particular set of types simply by performing the appropriate bitwise masking operation. + +// Cache Resource Records (received from the network): +// There are four basic types: Answer, Unique Answer, Additional, Unique Additional +// Bit 7 (the top bit) of kDNSRecordType is always set for Cache Resource Records; always clear for Authoritative Resource Records +// Bit 6 (value 0x40) is set for answer records; clear for additional records +// Bit 5 (value 0x20) is set for records received with the kDNSClass_UniqueRRSet + +enum + { + kDNSRecordTypeUnregistered = 0x00, // Not currently in any list + kDNSRecordTypeDeregistering = 0x01, // Shared record about to announce its departure and leave the list + + kDNSRecordTypeUnique = 0x02, // Will become a kDNSRecordTypeVerified when probing is complete + + kDNSRecordTypeAdvisory = 0x04, // Like Shared, but no goodbye packet + kDNSRecordTypeShared = 0x08, // Shared means record name does not have to be unique -- use random delay on responses + kDNSRecordTypeVerified = 0x10, // Unique means mDNS should check that name is unique (and then send immediate responses) + kDNSRecordTypeKnownUnique = 0x20, // Known Unique means mDNS can assume name is unique without checking + + kDNSRecordTypeUniqueMask = (kDNSRecordTypeUnique | kDNSRecordTypeVerified | kDNSRecordTypeKnownUnique), + kDNSRecordTypeActiveMask = (kDNSRecordTypeAdvisory | kDNSRecordTypeShared | kDNSRecordTypeVerified | kDNSRecordTypeKnownUnique), + + kDNSRecordTypePacketAdd = 0x80, // Received in the Additional Section of a DNS Response + kDNSRecordTypePacketAddUnique = 0xA0, // Received in the Additional Section of a DNS Response with kDNSClass_UniqueRRSet set + kDNSRecordTypePacketAns = 0xC0, // Received in the Answer Section of a DNS Response + kDNSRecordTypePacketAnsUnique = 0xE0, // Received in the Answer Section of a DNS Response with kDNSClass_UniqueRRSet set + + kDNSRecordTypePacketAnsMask = 0x40, // True for PacketAns and PacketAnsUnique + kDNSRecordTypePacketUniqueMask = 0x20 // True for PacketAddUnique and PacketAnsUnique + }; + +typedef struct { mDNSu16 priority; mDNSu16 weight; mDNSIPPort port; domainname target; } rdataSRV; +typedef struct { mDNSu16 preference; domainname exchange; } rdataMX; + +// StandardAuthRDSize is 264 (256+8), which is large enough to hold a maximum-sized SRV record +// MaximumRDSize is 8K the absolute maximum we support (at least for now) +#define StandardAuthRDSize 264 +#define MaximumRDSize 8192 + +// InlineCacheRDSize is 64 +// Records received from the network with rdata this size or less have their rdata stored right in the CacheRecord object +// Records received from the network with rdata larger than this have additional storage allocated for the rdata +// A quick unscientific sample from a busy network at Apple with lots of machines revealed this: +// 1461 records in cache +// 292 were one-byte TXT records +// 136 were four-byte A records +// 184 were sixteen-byte AAAA records +// 780 were various PTR, TXT and SRV records from 12-64 bytes +// Only 69 records had rdata bigger than 64 bytes +#define InlineCacheRDSize 64 + +typedef union + { + mDNSu8 data[StandardAuthRDSize]; + mDNSv4Addr ip; // For 'A' record + mDNSv6Addr ipv6; // For 'AAAA' record + domainname name; // For PTR and CNAME records + UTF8str255 txt; // For TXT record + rdataSRV srv; // For SRV record + rdataMX mx; // For MX record + } RDataBody; + +typedef struct + { + mDNSu16 MaxRDLength; // Amount of storage allocated for rdata (usually sizeof(RDataBody)) + RDataBody u; + } RData; +#define sizeofRDataHeader (sizeof(RData) - sizeof(RDataBody)) + +typedef struct AuthRecord_struct AuthRecord; +typedef struct CacheRecord_struct CacheRecord; +typedef struct ResourceRecord_struct ResourceRecord; +typedef struct DNSQuestion_struct DNSQuestion; +typedef struct mDNS_struct mDNS; +typedef struct mDNS_PlatformSupport_struct mDNS_PlatformSupport; + +// Note: Within an mDNSRecordCallback mDNS all API calls are legal except mDNS_Init(), mDNS_Close(), mDNS_Execute() +typedef void mDNSRecordCallback(mDNS *const m, AuthRecord *const rr, mStatus result); + +// Note: +// Restrictions: An mDNSRecordUpdateCallback may not make any mDNS API calls. +// The intent of this callback is to allow the client to free memory, if necessary. +// The internal data structures of the mDNS code may not be in a state where mDNS API calls may be made safely. +typedef void mDNSRecordUpdateCallback(mDNS *const m, AuthRecord *const rr, RData *OldRData); + +struct ResourceRecord_struct + { + mDNSu8 RecordType; // See enum above + mDNSInterfaceID InterfaceID; // Set if this RR is specific to one interface + // For records received off the wire, InterfaceID is *always* set to the receiving interface + // For our authoritative records, InterfaceID is usually zero, except for those few records + // that are interface-specific (e.g. address records, especially linklocal addresses) + domainname name; + mDNSu16 rrtype; + mDNSu16 rrclass; + mDNSu32 rroriginalttl; // In seconds + mDNSu16 rdlength; // Size of the raw rdata, in bytes + mDNSu16 rdestimate; // Upper bound on size of rdata after name compression + mDNSu32 namehash; // Name-based (i.e. case insensitive) hash of name + mDNSu32 rdatahash; // 32-bit hash of the raw rdata + mDNSu32 rdnamehash; // Set if this rdata contains a domain name (e.g. PTR, SRV, CNAME etc.) + RData *rdata; // Pointer to storage for this rdata + }; + +struct AuthRecord_struct + { + // For examples of how to set up this structure for use in mDNS_Register(), + // see mDNS_AdvertiseInterface() or mDNS_RegisterService(). + // Basically, resrec and persistent metadata need to be set up before calling mDNS_Register(). + // mDNS_SetupResourceRecord() is avaliable as a helper routine to set up most fields to sensible default values for you + + AuthRecord *next; // Next in list; first element of structure for efficiency reasons + ResourceRecord resrec; + + // Persistent metadata for Authoritative Records + AuthRecord *Additional1; // Recommended additional record to include in response + AuthRecord *Additional2; // Another additional + AuthRecord *DependentOn; // This record depends on another for its uniqueness checking + AuthRecord *RRSet; // This unique record is part of an RRSet + mDNSRecordCallback *RecordCallback; // Callback function to call for state changes + void *RecordContext; // Context parameter for the callback function + mDNSu8 HostTarget; // Set if the target of this record (PTR, CNAME, SRV, etc.) is our host name + + // Transient state for Authoritative Records + mDNSu8 Acknowledged; // Set if we've given the success callback to the client + mDNSu8 ProbeCount; // Number of probes remaining before this record is valid (kDNSRecordTypeUnique) + mDNSu8 AnnounceCount; // Number of announcements remaining (kDNSRecordTypeShared) + mDNSu8 IncludeInProbe; // Set if this RR is being put into a probe right now + mDNSInterfaceID ImmedAnswer; // Someone on this interface issued a query we need to answer (all-ones for all interfaces) + mDNSInterfaceID ImmedAdditional; // Hint that we might want to also send this record, just to be helpful + mDNSInterfaceID SendRNow; // The interface this query is being sent on right now + mDNSv4Addr v4Requester; // Recent v4 query for this record, or all-ones if more than one recent query + mDNSv6Addr v6Requester; // Recent v6 query for this record, or all-ones if more than one recent query + AuthRecord *NextResponse; // Link to the next element in the chain of responses to generate + const mDNSu8 *NR_AnswerTo; // Set if this record was selected by virtue of being a direct answer to a question + AuthRecord *NR_AdditionalTo; // Set if this record was selected by virtue of being additional to another + mDNSs32 ThisAPInterval; // In platform time units: Current interval for announce/probe + mDNSs32 AnnounceUntil; // In platform time units: Creation time + TTL + mDNSs32 LastAPTime; // In platform time units: Last time we sent announcement/probe + mDNSs32 LastMCTime; // Last time we multicast this record (used to guard against packet-storm attacks) + mDNSInterfaceID LastMCInterface; // Interface this record was multicast on at the time LastMCTime was recorded + RData *NewRData; // Set if we are updating this record with new rdata + mDNSu16 newrdlength; // ... and the length of the new RData + mDNSRecordUpdateCallback *UpdateCallback; + mDNSu32 UpdateCredits; // Token-bucket rate limiting of excessive updates + mDNSs32 NextUpdateCredit; // Time next token is added to bucket + mDNSs32 UpdateBlocked; // Set if update delaying is in effect + + RData rdatastorage; // Normally the storage is right here, except for oversized records + // rdatastorage MUST be the last thing in the structure -- when using oversized AuthRecords, extra bytes + // are appended after the end of the AuthRecord, logically augmenting the size of the rdatastorage + // DO NOT ADD ANY MORE FIELDS HERE + }; + +struct CacheRecord_struct + { + CacheRecord *next; // Next in list; first element of structure for efficiency reasons + ResourceRecord resrec; + + // Transient state for Cache Records + CacheRecord *NextInKAList; // Link to the next element in the chain of known answers to send + mDNSs32 TimeRcvd; // In platform time units + mDNSs32 NextRequiredQuery; // In platform time units + mDNSs32 LastUsed; // In platform time units + mDNSu32 UseCount; // Number of times this RR has been used to answer a question + DNSQuestion *CRActiveQuestion; // Points to an active question referencing this answer + mDNSu32 UnansweredQueries; // Number of times we've issued a query for this record without getting an answer + mDNSs32 LastUnansweredTime; // In platform time units; last time we incremented UnansweredQueries + mDNSu32 MPUnansweredQ; // Multi-packet query handling: Number of times we've seen a query for this record + mDNSs32 MPLastUnansweredQT; // Multi-packet query handling: Last time we incremented MPUnansweredQ + mDNSu32 MPUnansweredKA; // Multi-packet query handling: Number of times we've seen this record in a KA list + mDNSBool MPExpectingKA; // Multi-packet query handling: Set when we increment MPUnansweredQ; allows one KA + CacheRecord *NextInCFList; // Set if this is in the list of records we just received with the cache flush bit set + + struct { mDNSu16 MaxRDLength; mDNSu8 data[InlineCacheRDSize]; } rdatastorage; // Storage for small records is right here + }; + +typedef struct + { + CacheRecord r; + mDNSu8 _extradata[MaximumRDSize-InlineCacheRDSize]; // Glue on the necessary number of extra bytes + } LargeCacheRecord; + +typedef struct NetworkInterfaceInfo_struct NetworkInterfaceInfo; + +struct NetworkInterfaceInfo_struct + { + // Internal state fields. These are used internally by mDNSCore; the client layer needn't be concerned with them. + NetworkInterfaceInfo *next; + + mDNSBool InterfaceActive; // InterfaceActive is set if interface is sending & receiving packets + // InterfaceActive is clear if interface is here to represent an address with A + // and/or AAAA records, but there is already an earlier representative for this + // physical interface which will be used for the actual sending & receiving + // packets (this status may change as interfaces are added and removed) + mDNSBool IPv4Available; // If InterfaceActive, set if v4 available on this InterfaceID + mDNSBool IPv6Available; // If InterfaceActive, set if v6 available on this InterfaceID + + // Standard AuthRecords that every Responder host should have (one per active IP address) + AuthRecord RR_A; // 'A' or 'AAAA' (address) record for our ".local" name + AuthRecord RR_PTR; // PTR (reverse lookup) record + AuthRecord RR_HINFO; + + // Client API fields: The client must set up these fields *before* calling mDNS_RegisterInterface() + mDNSInterfaceID InterfaceID; + mDNSAddr ip; + mDNSBool Advertise; // Set Advertise to false if you are only searching on this interface + mDNSBool TxAndRx; // Set to false if not sending and receiving packets on this interface + }; + +typedef struct ExtraResourceRecord_struct ExtraResourceRecord; +struct ExtraResourceRecord_struct + { + ExtraResourceRecord *next; + AuthRecord r; + // Note: Add any additional fields *before* the AuthRecord in this structure, not at the end. + // In some cases clients can allocate larger chunks of memory and set r->rdata->MaxRDLength to indicate + // that this extra memory is available, which would result in any fields after the AuthRecord getting smashed + }; + +// Note: Within an mDNSServiceCallback mDNS all API calls are legal except mDNS_Init(), mDNS_Close(), mDNS_Execute() +typedef struct ServiceRecordSet_struct ServiceRecordSet; +typedef void mDNSServiceCallback(mDNS *const m, ServiceRecordSet *const sr, mStatus result); +struct ServiceRecordSet_struct + { + // Internal state fields. These are used internally by mDNSCore; the client layer needn't be concerned with them. + // No fields need to be set up by the client prior to calling mDNS_RegisterService(); + // all required data is passed as parameters to that function. + mDNSServiceCallback *ServiceCallback; + void *ServiceContext; + ExtraResourceRecord *Extras; // Optional list of extra AuthRecords attached to this service registration + mDNSu32 NumSubTypes; + AuthRecord *SubTypes; + mDNSBool Conflict; // Set if this record set was forcibly deregistered because of a conflict + domainname Host; // Set if this service record does not use the standard target host name + AuthRecord RR_ADV; // e.g. _services._mdns._udp.local. PTR _printer._tcp.local. + AuthRecord RR_PTR; // e.g. _printer._tcp.local. PTR Name._printer._tcp.local. + AuthRecord RR_SRV; // e.g. Name._printer._tcp.local. SRV 0 0 port target + AuthRecord RR_TXT; // e.g. Name._printer._tcp.local. TXT PrintQueueName + // Don't add any fields after AuthRecord RR_TXT. + // This is where the implicit extra space goes if we allocate a ServiceRecordSet containing an oversized RR_TXT record + }; + +// *************************************************************************** +#if 0 +#pragma mark - Question structures +#endif + +// We record the last eight instances of each duplicate query +// This gives us v4/v6 on each of Ethernet/AirPort and Firewire, and two free slots "for future expansion" +// If the host has more active interfaces that this it is not fatal -- duplicate question suppression will degrade gracefully. +// Since we will still remember the last eight, the busiest interfaces will still get the effective duplicate question suppression. +#define DupSuppressInfoSize 8 + +typedef struct + { + mDNSs32 Time; + mDNSInterfaceID InterfaceID; + mDNSs32 Type; // v4 or v6? + } DupSuppressInfo; + +// Note: Within an mDNSQuestionCallback mDNS all API calls are legal except mDNS_Init(), mDNS_Close(), mDNS_Execute() +typedef void mDNSQuestionCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord); +struct DNSQuestion_struct + { + // Internal state fields. These are used internally by mDNSCore; the client layer needn't be concerned with them. + DNSQuestion *next; + mDNSu32 qnamehash; + mDNSs32 LastQTime; // Last scheduled tranmission of this Q on *all* applicable interfaces + mDNSs32 ThisQInterval; // LastQTime + ThisQInterval is the next scheduled tranmission of this Q + // ThisQInterval > 0 for an active question; + // ThisQInterval = 0 for a suspended question that's still in the list + // ThisQInterval = -1 for a cancelled question that's been removed from the list + mDNSu32 RecentAnswers; // Number of answers since the last time we sent this query + mDNSu32 CurrentAnswers; // Number of records currently in the cache that answer this question + mDNSu32 LargeAnswers; // Number of answers with rdata > 1024 bytes + mDNSu32 UniqueAnswers; // Number of answers received with kDNSClass_UniqueRRSet bit set + DNSQuestion *DuplicateOf; + DNSQuestion *NextInDQList; + DupSuppressInfo DupSuppress[DupSuppressInfoSize]; + mDNSInterfaceID SendQNow; // The interface this query is being sent on right now + mDNSBool SendOnAll; // Set if we're sending this question on all active interfaces + mDNSs32 LastQTxTime; // Last time this Q was sent on one (but not necessarily all) interfaces + + // Client API fields: The client must set up these fields *before* calling mDNS_StartQuery() + mDNSInterfaceID InterfaceID; // Non-zero if you want to issue link-local queries only on a single specific IP interface + domainname qname; + mDNSu16 qtype; + mDNSu16 qclass; + mDNSQuestionCallback *QuestionCallback; + void *QuestionContext; + }; + +typedef struct + { + // Client API fields: The client must set up name and InterfaceID *before* calling mDNS_StartResolveService() + // When the callback is invoked, ip, port, TXTlen and TXTinfo will have been filled in with the results learned from the network. + domainname name; + mDNSInterfaceID InterfaceID; // ID of the interface the response was received on + mDNSAddr ip; // Remote (destination) IP address where this service can be accessed + mDNSIPPort port; // Port where this service can be accessed + mDNSu16 TXTlen; + mDNSu8 TXTinfo[2048]; // Additional demultiplexing information (e.g. LPR queue name) + } ServiceInfo; + +// Note: Within an mDNSServiceInfoQueryCallback mDNS all API calls are legal except mDNS_Init(), mDNS_Close(), mDNS_Execute() +typedef struct ServiceInfoQuery_struct ServiceInfoQuery; +typedef void mDNSServiceInfoQueryCallback(mDNS *const m, ServiceInfoQuery *query); +struct ServiceInfoQuery_struct + { + // Internal state fields. These are used internally by mDNSCore; the client layer needn't be concerned with them. + // No fields need to be set up by the client prior to calling mDNS_StartResolveService(); + // all required data is passed as parameters to that function. + // The ServiceInfoQuery structure memory is working storage for mDNSCore to discover the requested information + // and place it in the ServiceInfo structure. After the client has called mDNS_StopResolveService(), it may + // dispose of the ServiceInfoQuery structure while retaining the results in the ServiceInfo structure. + DNSQuestion qSRV; + DNSQuestion qTXT; + DNSQuestion qAv4; + DNSQuestion qAv6; + mDNSu8 GotSRV; + mDNSu8 GotTXT; + mDNSu8 GotADD; + mDNSu32 Answers; + ServiceInfo *info; + mDNSServiceInfoQueryCallback *ServiceInfoQueryCallback; + void *ServiceInfoQueryContext; + }; + +// *************************************************************************** +#if 0 +#pragma mark - Main mDNS object, used to hold all the mDNS state +#endif + +typedef void mDNSCallback(mDNS *const m, mStatus result); + +#define CACHE_HASH_SLOTS 499 + +enum + { + mDNS_KnownBug_PhantomInterfaces = 1 + }; + +struct mDNS_struct + { + // Internal state fields. These hold the main internal state of mDNSCore; + // the client layer needn't be concerned with them. + // No fields need to be set up by the client prior to calling mDNS_Init(); + // all required data is passed as parameters to that function. + + mDNS_PlatformSupport *p; // Pointer to platform-specific data of indeterminite size + mDNSu32 KnownBugs; + mDNSBool AdvertiseLocalAddresses; + mStatus mDNSPlatformStatus; + mDNSCallback *MainCallback; + void *MainContext; + + // For debugging: To catch and report locking failures + mDNSu32 mDNS_busy; // Incremented between mDNS_Lock/mDNS_Unlock section + mDNSu32 mDNS_reentrancy; // Incremented when calling a client callback + mDNSu8 mDNS_shutdown; // Set when we're shutting down, allows us to skip some unnecessary steps + mDNSu8 lock_rrcache; // For debugging: Set at times when these lists may not be modified + mDNSu8 lock_Questions; + mDNSu8 lock_Records; + char MsgBuffer[80]; // Temp storage used while building error log messages + + // Task Scheduling variables + mDNSs32 timenow; // The time that this particular activation of the mDNS code started + mDNSs32 timenow_last; // The time the last time we ran + mDNSs32 timenow_adjust; // Correction applied if we ever discover time went backwards + mDNSs32 NextScheduledEvent; // Derived from values below + mDNSs32 SuppressSending; // Don't send *any* packets during this time + mDNSs32 NextCacheCheck; // Next time to refresh cache record before it expires + mDNSs32 NextScheduledQuery; // Next time to send query in its exponential backoff sequence + mDNSs32 NextScheduledProbe; // Next time to probe for new authoritative record + mDNSs32 NextScheduledResponse; // Next time to send authoritative record(s) in responses + mDNSs32 ExpectUnicastResponse; // Set when we send a query with the kDNSQClass_UnicastResponse bit set + mDNSs32 RandomQueryDelay; // For de-synchronization of query packets on the wire + mDNSBool SendDeregistrations; // Set if we need to send deregistrations (immediately) + mDNSBool SendImmediateAnswers; // Set if we need to send answers (immediately -- or as soon as SuppressSending clears) + mDNSBool SleepState; // Set if we're sleeping (send no more packets) + + // These fields only required for mDNS Searcher... + DNSQuestion *Questions; // List of all registered questions, active and inactive + DNSQuestion *NewQuestions; // Fresh questions not yet answered from cache + DNSQuestion *CurrentQuestion; // Next question about to be examined in AnswerLocalQuestions() + DNSQuestion *LocalOnlyQuestions; // Questions with InterfaceID set to ~0 ("local only") + DNSQuestion *NewLocalOnlyQuestions; // Fresh local-only questions not yet answered + mDNSu32 rrcache_size; // Total number of available cache entries + mDNSu32 rrcache_totalused; // Number of cache entries currently occupied + mDNSu32 rrcache_active; // Number of cache entries currently occupied by records that answer active questions + mDNSu32 rrcache_report; + CacheRecord *rrcache_free; + CacheRecord *rrcache_hash[CACHE_HASH_SLOTS]; + CacheRecord **rrcache_tail[CACHE_HASH_SLOTS]; + mDNSu32 rrcache_used[CACHE_HASH_SLOTS]; + + // Fields below only required for mDNS Responder... + domainlabel nicelabel; // Rich text label encoded using canonically precomposed UTF-8 + domainlabel hostlabel; // Conforms to RFC 1034 "letter-digit-hyphen" ARPANET host name rules + domainname hostname; // Host Name, e.g. "Foo.local." + UTF8str255 HIHardware; + UTF8str255 HISoftware; + AuthRecord *ResourceRecords; + AuthRecord *DuplicateRecords; // Records currently 'on hold' because they are duplicates of existing records + AuthRecord *LocalOnlyRecords; // Local records registered with InterfaceID set to ~0 ("local only") + AuthRecord *NewLocalOnlyRecords; // Fresh local-only records not yet delivered to local-only questions + mDNSBool DiscardLocalOnlyRecords;// Set when we have "remove" events we need to deliver to local-only questions + AuthRecord *CurrentRecord; // Next AuthRecord about to be examined + NetworkInterfaceInfo *HostInterfaces; + mDNSs32 ProbeFailTime; + mDNSs32 NumFailedProbes; + mDNSs32 SuppressProbes; + }; + +// *************************************************************************** +#if 0 +#pragma mark - Useful Static Constants +#endif + +extern const mDNSIPPort zeroIPPort; +extern const mDNSv4Addr zeroIPAddr; +extern const mDNSv6Addr zerov6Addr; +extern const mDNSv4Addr onesIPv4Addr; +extern const mDNSv6Addr onesIPv6Addr; +extern const mDNSInterfaceID mDNSInterface_Any; + +extern const mDNSIPPort UnicastDNSPort; +extern const mDNSIPPort MulticastDNSPort; +extern const mDNSv4Addr AllDNSAdminGroup; +extern const mDNSv4Addr AllDNSLinkGroup; +extern const mDNSv6Addr AllDNSLinkGroupv6; +extern const mDNSAddr AllDNSLinkGroup_v4; +extern const mDNSAddr AllDNSLinkGroup_v6; + +// *************************************************************************** +#if 0 +#pragma mark - Main Client Functions +#endif + +// Every client should call mDNS_Init, passing in storage for the mDNS object, mDNS_PlatformSupport object, and rrcache. +// The rrcachesize parameter is the size of (i.e. number of entries in) the rrcache array passed in. +// Most clients use mDNS_Init_AdvertiseLocalAddresses. This causes mDNSCore to automatically +// create the correct address records for all the hosts interfaces. If you plan to advertise +// services being offered by the local machine, this is almost always what you want. +// There are two cases where you might use mDNS_Init_DontAdvertiseLocalAddresses: +// 1. A client-only device, that browses for services but doesn't advertise any of its own. +// 2. A proxy-registration service, that advertises services being offered by other machines, and takes +// the appropriate steps to manually create the correct address records for those other machines. +// In principle, a proxy-like registration service could manually create address records for its own machine too, +// but this would be pointless extra effort when using mDNS_Init_AdvertiseLocalAddresses does that for you. +// When mDNS has finished setting up the client's callback is called +// A client can also spin and poll the mDNSPlatformStatus field to see when it changes from mStatus_Waiting to mStatus_NoError +// +// Call mDNS_Close to tidy up before exiting +// +// Call mDNS_Register with a completed AuthRecord object to register a resource record +// If the resource record type is kDNSRecordTypeUnique (or kDNSknownunique) then if a conflicting resource record is discovered, +// the resource record's mDNSRecordCallback will be called with error code mStatus_NameConflict. The callback should deregister +// the record, and may then try registering the record again after picking a new name (e.g. by automatically appending a number). +// +// Call mDNS_StartQuery to initiate a query. mDNS will proceed to issue Multicast DNS query packets, and any time a response +// is received containing a record which matches the question, the DNSQuestion's mDNSAnswerCallback function will be called +// Call mDNS_StopQuery when no more answers are required +// +// Care should be taken on multi-threaded or interrupt-driven environments. +// The main mDNS routines call mDNSPlatformLock() on entry and mDNSPlatformUnlock() on exit; +// each platform layer needs to implement these appropriately for its respective platform. +// For example, if the support code on a particular platform implements timer callbacks at interrupt time, then +// mDNSPlatformLock/Unlock need to disable interrupts or do similar concurrency control to ensure that the mDNS +// code is not entered by an interrupt-time timer callback while in the middle of processing a client call. + +extern mStatus mDNS_Init (mDNS *const m, mDNS_PlatformSupport *const p, + CacheRecord *rrcachestorage, mDNSu32 rrcachesize, + mDNSBool AdvertiseLocalAddresses, + mDNSCallback *Callback, void *Context); +#define mDNS_Init_NoCache mDNSNULL +#define mDNS_Init_ZeroCacheSize 0 +// See notes above on use of Advertise/DontAdvertiseLocalAddresses +#define mDNS_Init_AdvertiseLocalAddresses mDNStrue +#define mDNS_Init_DontAdvertiseLocalAddresses mDNSfalse +#define mDNS_Init_NoInitCallback mDNSNULL +#define mDNS_Init_NoInitCallbackContext mDNSNULL + +extern void mDNS_GrowCache (mDNS *const m, CacheRecord *storage, mDNSu32 numrecords); +extern void mDNS_Close (mDNS *const m); +extern mDNSs32 mDNS_Execute (mDNS *const m); + +extern mStatus mDNS_Register (mDNS *const m, AuthRecord *const rr); +extern mStatus mDNS_Update (mDNS *const m, AuthRecord *const rr, mDNSu32 newttl, + const mDNSu16 newrdlength, + RData *const newrdata, mDNSRecordUpdateCallback *Callback); +extern mStatus mDNS_Deregister(mDNS *const m, AuthRecord *const rr); + +extern mStatus mDNS_StartQuery(mDNS *const m, DNSQuestion *const question); +extern mStatus mDNS_StopQuery (mDNS *const m, DNSQuestion *const question); +extern mStatus mDNS_Reconfirm (mDNS *const m, CacheRecord *const cacherr); +extern mStatus mDNS_ReconfirmByValue(mDNS *const m, ResourceRecord *const rr); + +// *************************************************************************** +#if 0 +#pragma mark - Platform support functions that are accessible to the client layer too +#endif + +extern mDNSs32 mDNSPlatformOneSecond; +extern mDNSs32 mDNSPlatformTimeNow(void); + +// *************************************************************************** +#if 0 +#pragma mark - General utility and helper functions +#endif + +// mDNS_RegisterService is a single call to register the set of resource records associated with a given named service. +// +// mDNS_StartResolveService is single call which is equivalent to multiple calls to mDNS_StartQuery, +// to find the IP address, port number, and demultiplexing information for a given named service. +// As with mDNS_StartQuery, it executes asynchronously, and calls the ServiceInfoQueryCallback when the answer is +// found. After the service is resolved, the client should call mDNS_StopResolveService to complete the transaction. +// The client can also call mDNS_StopResolveService at any time to abort the transaction. +// +// mDNS_GetBrowseDomains is a special case of the mDNS_StartQuery call, where the resulting answers +// are a list of PTR records indicating (in the rdata) domains that are recommended for browsing. +// After getting the list of domains to browse, call mDNS_StopQuery to end the search. +// mDNS_GetDefaultBrowseDomain returns the name of the domain that should be highlighted by default. +// +// mDNS_GetRegistrationDomains and mDNS_GetDefaultRegistrationDomain are the equivalent calls to get the list +// of one or more domains that should be offered to the user as choices for where they may register their service, +// and the default domain in which to register in the case where the user has made no selection. + +extern void mDNS_SetupResourceRecord(AuthRecord *rr, RData *RDataStorage, mDNSInterfaceID InterfaceID, + mDNSu16 rrtype, mDNSu32 ttl, mDNSu8 RecordType, mDNSRecordCallback Callback, void *Context); + +extern mStatus mDNS_RegisterService (mDNS *const m, ServiceRecordSet *sr, + const domainlabel *const name, const domainname *const type, const domainname *const domain, + const domainname *const host, mDNSIPPort port, const mDNSu8 txtinfo[], mDNSu16 txtlen, + AuthRecord *SubTypes, mDNSu32 NumSubTypes, + const mDNSInterfaceID InterfaceID, mDNSServiceCallback Callback, void *Context); +extern mStatus mDNS_AddRecordToService(mDNS *const m, ServiceRecordSet *sr, ExtraResourceRecord *extra, RData *rdata, mDNSu32 ttl); +extern mStatus mDNS_RemoveRecordFromService(mDNS *const m, ServiceRecordSet *sr, ExtraResourceRecord *extra); +extern mStatus mDNS_RenameAndReregisterService(mDNS *const m, ServiceRecordSet *const sr, const domainlabel *newname); +extern mStatus mDNS_DeregisterService(mDNS *const m, ServiceRecordSet *sr); + +extern mStatus mDNS_RegisterNoSuchService(mDNS *const m, AuthRecord *const rr, + const domainlabel *const name, const domainname *const type, const domainname *const domain, + const domainname *const host, + const mDNSInterfaceID InterfaceID, mDNSRecordCallback Callback, void *Context); +#define mDNS_DeregisterNoSuchService mDNS_Deregister + +extern mStatus mDNS_StartBrowse(mDNS *const m, DNSQuestion *const question, + const domainname *const srv, const domainname *const domain, + const mDNSInterfaceID InterfaceID, mDNSQuestionCallback *Callback, void *Context); +#define mDNS_StopBrowse mDNS_StopQuery + +extern mStatus mDNS_StartResolveService(mDNS *const m, ServiceInfoQuery *query, ServiceInfo *info, mDNSServiceInfoQueryCallback *Callback, void *Context); +extern void mDNS_StopResolveService (mDNS *const m, ServiceInfoQuery *query); + +typedef enum + { + mDNS_DomainTypeBrowse = 0, + mDNS_DomainTypeBrowseDefault = 1, + mDNS_DomainTypeRegistration = 2, + mDNS_DomainTypeRegistrationDefault = 3 + } mDNS_DomainType; + +extern mStatus mDNS_GetDomains(mDNS *const m, DNSQuestion *const question, mDNS_DomainType DomainType, const mDNSInterfaceID InterfaceID, mDNSQuestionCallback *Callback, void *Context); +#define mDNS_StopGetDomains mDNS_StopQuery +extern mStatus mDNS_AdvertiseDomains(mDNS *const m, AuthRecord *rr, mDNS_DomainType DomainType, const mDNSInterfaceID InterfaceID, char *domname); +#define mDNS_StopAdvertiseDomains mDNS_Deregister + +// *************************************************************************** +#if 0 +#pragma mark - DNS name utility functions +#endif + +// In order to expose the full capabilities of the DNS protocol (which allows any arbitrary eight-bit values +// in domain name labels, including unlikely characters like ascii nulls and even dots) all the mDNS APIs +// work with DNS's native length-prefixed strings. For convenience in C, the following utility functions +// are provided for converting between C's null-terminated strings and DNS's length-prefixed strings. + +// Assignment +// A simple C structure assignment of a domainname can cause a protection fault by accessing unmapped memory, +// because that object is defined to be 256 bytes long, but not all domainname objects are truly the full size. +// This macro uses mDNSPlatformMemCopy() to make sure it only touches the actual bytes that are valid. +#define AssignDomainName(DST, SRC) mDNSPlatformMemCopy((SRC).c, (DST).c, DomainNameLength(&(SRC))) + +// Comparison functions +extern mDNSBool SameDomainLabel(const mDNSu8 *a, const mDNSu8 *b); +extern mDNSBool SameDomainName(const domainname *const d1, const domainname *const d2); + +// Get total length of domain name, in native DNS format, including terminal root label +// (e.g. length of "com." is 5 (length byte, three data bytes, final zero) +extern mDNSu16 DomainNameLength(const domainname *const name); + +// Append functions to append one or more labels to an existing native format domain name: +// AppendLiteralLabelString adds a single label from a literal C string, with no escape character interpretation. +// AppendDNSNameString adds zero or more labels from a C string using conventional DNS dots-and-escaping interpretation +// AppendDomainLabel adds a single label from a native format domainlabel +// AppendDomainName adds zero or more labels from a native format domainname +extern mDNSu8 *AppendLiteralLabelString(domainname *const name, const char *cstr); +extern mDNSu8 *AppendDNSNameString (domainname *const name, const char *cstr); +extern mDNSu8 *AppendDomainLabel (domainname *const name, const domainlabel *const label); +extern mDNSu8 *AppendDomainName (domainname *const name, const domainname *const append); + +// Convert from null-terminated string to native DNS format: +// The DomainLabel form makes a single label from a literal C string, with no escape character interpretation. +// The DomainName form makes native format domain name from a C string using conventional DNS interpretation: +// dots separate labels, and within each label, '\.' represents a literal dot, '\\' represents a literal +// backslash and backslash with three decimal digits (e.g. \000) represents an arbitrary byte value. +extern mDNSBool MakeDomainLabelFromLiteralString(domainlabel *const label, const char *cstr); +extern mDNSu8 *MakeDomainNameFromDNSNameString (domainname *const name, const char *cstr); + +// Convert native format domainlabel or domainname back to C string format +// IMPORTANT: +// When using ConvertDomainLabelToCString, the target buffer must be MAX_ESCAPED_DOMAIN_LABEL (254) bytes long +// to guarantee there will be no buffer overrun. It is only safe to use a buffer shorter than this in rare cases +// where the label is known to be constrained somehow (for example, if the label is known to be either "_tcp" or "_udp"). +// Similarly, when using ConvertDomainNameToCString, the target buffer must be MAX_ESCAPED_DOMAIN_NAME (1005) bytes long. +// See definitions of MAX_ESCAPED_DOMAIN_LABEL and MAX_ESCAPED_DOMAIN_NAME for more detailed explanation. +extern char *ConvertDomainLabelToCString_withescape(const domainlabel *const name, char *cstr, char esc); +#define ConvertDomainLabelToCString_unescaped(D,C) ConvertDomainLabelToCString_withescape((D), (C), 0) +#define ConvertDomainLabelToCString(D,C) ConvertDomainLabelToCString_withescape((D), (C), '\\') +extern char *ConvertDomainNameToCString_withescape(const domainname *const name, char *cstr, char esc); +#define ConvertDomainNameToCString_unescaped(D,C) ConvertDomainNameToCString_withescape((D), (C), 0) +#define ConvertDomainNameToCString(D,C) ConvertDomainNameToCString_withescape((D), (C), '\\') + +extern void ConvertUTF8PstringToRFC1034HostLabel(const mDNSu8 UTF8Name[], domainlabel *const hostlabel); + +extern mDNSu8 *ConstructServiceName(domainname *const fqdn, const domainlabel *name, const domainname *type, const domainname *const domain); +extern mDNSBool DeconstructServiceName(const domainname *const fqdn, domainlabel *const name, domainname *const type, domainname *const domain); + +// Note: Some old functions have been replaced by more sensibly-named versions. +// You can uncomment the hash-defines below if you don't want to have to change your source code right away. +// When updating your code, note that (unlike the old versions) *all* the new routines take the target object +// as their first parameter. +//#define ConvertCStringToDomainName(SRC,DST) MakeDomainNameFromDNSNameString((DST),(SRC)) +//#define ConvertCStringToDomainLabel(SRC,DST) MakeDomainLabelFromLiteralString((DST),(SRC)) +//#define AppendStringLabelToName(DST,SRC) AppendLiteralLabelString((DST),(SRC)) +//#define AppendStringNameToName(DST,SRC) AppendDNSNameString((DST),(SRC)) +//#define AppendDomainLabelToName(DST,SRC) AppendDomainLabel((DST),(SRC)) +//#define AppendDomainNameToName(DST,SRC) AppendDomainName((DST),(SRC)) + +// *************************************************************************** +#if 0 +#pragma mark - Other utility functions +#endif + +extern mDNSu32 mDNS_vsnprintf(char *sbuffer, mDNSu32 buflen, const char *fmt, va_list arg); +extern mDNSu32 mDNS_snprintf(char *sbuffer, mDNSu32 buflen, const char *fmt, ...) IS_A_PRINTF_STYLE_FUNCTION(3,4); +extern char *DNSTypeName(mDNSu16 rrtype); +extern char *GetRRDisplayString_rdb(mDNS *const m, const ResourceRecord *rr, RDataBody *rd); +#define GetRRDisplayString(m, rr) GetRRDisplayString_rdb((m), &(rr)->resrec, &(rr)->resrec.rdata->u) +extern mDNSBool mDNSSameAddress(const mDNSAddr *ip1, const mDNSAddr *ip2); +extern void IncrementLabelSuffix(domainlabel *name, mDNSBool RichText); + +// *************************************************************************** +#if 0 +#pragma mark - PlatformSupport interface +#endif + +// This section defines the interface to the Platform Support layer. +// Normal client code should not use any of types defined here, or directly call any of the functions defined here. +// The definitions are placed here because sometimes clients do use these calls indirectly, via other supported client operations. +// For example, AssignDomainName is a macro defined using mDNSPlatformMemCopy() + +typedef struct + { + mDNSOpaque16 id; + mDNSOpaque16 flags; + mDNSu16 numQuestions; + mDNSu16 numAnswers; + mDNSu16 numAuthorities; + mDNSu16 numAdditionals; + } DNSMessageHeader; + +// We can send and receive packets up to 9000 bytes (Ethernet Jumbo Frame size, if that ever becomes widely used) +// However, in the normal case we try to limit packets to 1500 bytes so that we don't get IP fragmentation on standard Ethernet +// 40 (IPv6 header) + 8 (UDP header) + 12 (DNS message header) + 1440 (DNS message body) = 1500 total +#define AbsoluteMaxDNSMessageData 8940 +#define NormalMaxDNSMessageData 1440 +typedef struct + { + DNSMessageHeader h; // Note: Size 12 bytes + mDNSu8 data[AbsoluteMaxDNSMessageData]; // 40 (IPv6) + 8 (UDP) + 12 (DNS header) + 8940 (data) = 9000 + } DNSMessage; + +// Every platform support module must provide the following functions. +// mDNSPlatformInit() typically opens a communication endpoint, and starts listening for mDNS packets. +// When Setup is complete, the platform support layer calls mDNSCoreInitComplete(). +// mDNSPlatformSendUDP() sends one UDP packet +// When a packet is received, the PlatformSupport code calls mDNSCoreReceive() +// mDNSPlatformClose() tidies up on exit +// Note: mDNSPlatformMemAllocate/mDNSPlatformMemFree are only required for handling oversized resource records. +// If your target platform has a well-defined specialized application, and you know that all the records it uses +// are InlineCacheRDSize or less, then you can just make a simple mDNSPlatformMemAllocate() stub that always returns +// NULL. InlineCacheRDSize is a compile-time constant, which is set by default to 64. If you need to handle records +// a little larger than this and you don't want to have to implement run-time allocation and freeing, then you +// can raise the value of this constant to a suitable value (at the expense of increased memory usage). +extern mStatus mDNSPlatformInit (mDNS *const m); +extern void mDNSPlatformClose (mDNS *const m); +extern mStatus mDNSPlatformSendUDP(const mDNS *const m, const DNSMessage *const msg, const mDNSu8 *const end, + mDNSInterfaceID InterfaceID, mDNSIPPort srcport, const mDNSAddr *dst, mDNSIPPort dstport); + +extern void mDNSPlatformLock (const mDNS *const m); +extern void mDNSPlatformUnlock (const mDNS *const m); + +extern void mDNSPlatformStrCopy (const void *src, void *dst); +extern mDNSu32 mDNSPlatformStrLen (const void *src); +extern void mDNSPlatformMemCopy (const void *src, void *dst, mDNSu32 len); +extern mDNSBool mDNSPlatformMemSame (const void *src, const void *dst, mDNSu32 len); +extern void mDNSPlatformMemZero ( void *dst, mDNSu32 len); +extern void * mDNSPlatformMemAllocate (mDNSu32 len); +extern void mDNSPlatformMemFree (void *mem); +extern mStatus mDNSPlatformTimeInit (mDNSs32 *timenow); + +// The core mDNS code provides these functions, for the platform support code to call at appropriate times +// +// mDNS_GenerateFQDN() is called once on startup (typically from mDNSPlatformInit()) +// and then again on each subsequent change of the dot-local host name. +// +// mDNS_RegisterInterface() is used by the platform support layer to inform mDNSCore of what +// physical and/or logical interfaces are available for sending and receiving packets. +// Typically it is called on startup for each available interface, but register/deregister may be +// called again later, on multiple occasions, to inform the core of interface configuration changes. +// If set->Advertise is set non-zero, then mDNS_RegisterInterface() also registers the standard +// resource records that should be associated with every publicised IP address/interface: +// -- Name-to-address records (A/AAAA) +// -- Address-to-name records (PTR) +// -- Host information (HINFO) +// +// mDNSCoreInitComplete() is called when the platform support layer is finished. +// Typically this is at the end of mDNSPlatformInit(), but may be later +// (on platforms like OT that allow asynchronous initialization of the networking stack). +// +// mDNSCoreReceive() is called when a UDP packet is received +// +// mDNSCoreMachineSleep() is called when the machine sleeps or wakes +// (This refers to heavyweight laptop-style sleep/wake that disables network access, +// not lightweight second-by-second CPU power management modes.) + +extern void mDNS_GenerateFQDN(mDNS *const m); +extern mStatus mDNS_RegisterInterface (mDNS *const m, NetworkInterfaceInfo *set); +extern void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *set); +extern void mDNSCoreInitComplete(mDNS *const m, mStatus result); +extern void mDNSCoreReceive(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); +extern void mDNSCoreMachineSleep(mDNS *const m, mDNSBool wake); + +// *************************************************************************** +#if 0 +#pragma mark - Compile-Time assertion checks +#endif + +// Some C compiler cleverness. We can make the compiler check certain things for +// us, and report compile-time errors if anything is wrong. The usual way to do +// this would be to use a run-time "if" statement, but then you don't find out +// what's wrong until you run the software. This way, if the assertion condition +// is false, the array size is negative, and the complier complains immediately. + +struct mDNS_CompileTimeAssertionChecks + { + // Check that the compiler generated our on-the-wire packet format structure definitions + // properly packed, without adding padding bytes to align fields on 32-bit or 64-bit boundaries. + char assert0[(sizeof(rdataSRV) == 262 ) ? 1 : -1]; + char assert1[(sizeof(DNSMessageHeader) == 12 ) ? 1 : -1]; + char assert2[(sizeof(DNSMessage) == 12+AbsoluteMaxDNSMessageData) ? 1 : -1]; + char assert3[(sizeof(mDNSs8) == 1 ) ? 1 : -1]; + char assert4[(sizeof(mDNSu8) == 1 ) ? 1 : -1]; + char assert5[(sizeof(mDNSs16) == 2 ) ? 1 : -1]; + char assert6[(sizeof(mDNSu16) == 2 ) ? 1 : -1]; + char assert7[(sizeof(mDNSs32) == 4 ) ? 1 : -1]; + char assert8[(sizeof(mDNSu32) == 4 ) ? 1 : -1]; + char assert9[(sizeof(mDNSOpaque16) == 2 ) ? 1 : -1]; + char assertA[(sizeof(mDNSOpaque32) == 4 ) ? 1 : -1]; + char assertB[(sizeof(mDNSOpaque128) == 16 ) ? 1 : -1]; + }; + +// *************************************************************************** + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/src/mDNSDebug.h b/src/mDNSDebug.h new file mode 100644 index 00000000..452d1a9e --- /dev/null +++ b/src/mDNSDebug.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * 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$ +Revision 1.1 2004/03/29 17:55:17 rpedde +Flatten mdns stuff + +Revision 1.2 2004/03/02 00:03:37 rpedde +Merge new rendezvous code + +Revision 1.14 2003/08/12 19:56:24 cheshire +Update to APSL 2.0 + +Revision 1.13 2003/07/02 21:19:46 cheshire + Update copyright notices, etc., in source code comments + +Revision 1.12 2003/05/26 03:01:27 cheshire + sprintf/vsprintf-style functions are unsafe; use snprintf/vsnprintf instead + +Revision 1.11 2003/05/21 17:48:10 cheshire +Add macro to enable GCC's printf format string checking + +Revision 1.10 2003/04/26 02:32:57 cheshire +Add extern void LogMsg(const char *format, ...); + +Revision 1.9 2002/09/21 20:44:49 zarzycki +Added APSL info + +Revision 1.8 2002/09/19 04:20:43 cheshire +Remove high-ascii characters that confuse some systems + +Revision 1.7 2002/09/16 18:41:42 cheshire +Merge in license terms from Quinn's copy, in preparation for Darwin release + +*/ + +#ifndef __mDNSDebug_h +#define __mDNSDebug_h + +// Set MDNS_DEBUGMSGS to 0 to optimize debugf() calls out of the compiled code +// Set MDNS_DEBUGMSGS to 1 to generate normal debugging messages +// Set MDNS_DEBUGMSGS to 2 to generate verbose debugging messages +// MDNS_DEBUGMSGS is normally set in the project options (or makefile) but can also be set here if desired + +//#define MDNS_DEBUGMSGS 2 + +// Set MDNS_CHECK_PRINTF_STYLE_FUNCTIONS to 1 to enable extra GCC compiler warnings +// Note: You don't normally want to do this, because it generates a bunch of +// spurious warnings for the following custom extensions implemented by mDNS_vsnprintf: +// warning: `#' flag used with `%s' printf format (for %#s -- pascal string format) +// warning: repeated `#' flag in format (for %##s -- DNS name string format) +// warning: double format, pointer arg (arg 2) (for %.4a, %.16a, %#a -- IP address formats) +#define MDNS_CHECK_PRINTF_STYLE_FUNCTIONS 0 +#if MDNS_CHECK_PRINTF_STYLE_FUNCTIONS +#define IS_A_PRINTF_STYLE_FUNCTION(F,A) __attribute__ ((format(printf,F,A))) +#else +#define IS_A_PRINTF_STYLE_FUNCTION(F,A) +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +#if MDNS_DEBUGMSGS +#define debugf debugf_ +extern void debugf_(const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2); +#else // If debug breaks are off, use a preprocessor trick to optimize those calls out of the code + #if( defined( __GNUC__ ) ) + #define debugf( ARGS... ) ((void)0) + #elif( defined( __MWERKS__ ) ) + #define debugf( ... ) + #else + #define debugf 1 ? ((void)0) : (void) + #endif +#endif + +#if MDNS_DEBUGMSGS > 1 +#define verbosedebugf verbosedebugf_ +extern void verbosedebugf_(const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2); +#else + #if( defined( __GNUC__ ) ) + #define verbosedebugf( ARGS... ) ((void)0) + #elif( defined( __MWERKS__ ) ) + #define verbosedebugf( ... ) + #else + #define verbosedebugf 1 ? ((void)0) : (void) + #endif +#endif + +// LogMsg is used even in shipping code, to write truly serious error messages to syslog (or equivalent) +extern void LogMsg(const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/src/mDNSPlatformFunctions.h b/src/mDNSPlatformFunctions.h new file mode 100644 index 00000000..1364ee16 --- /dev/null +++ b/src/mDNSPlatformFunctions.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * 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$ +Revision 1.1 2004/03/29 17:55:17 rpedde +Flatten mdns stuff + +Revision 1.2 2004/03/02 00:03:37 rpedde +Merge new rendezvous code + +Revision 1.22.2.1 2003/12/05 00:03:34 cheshire + Use buffer size MAX_ESCAPED_DOMAIN_NAME instead of 256 + +Revision 1.22 2003/08/18 22:53:37 cheshire + mDNSResponder divide by zero in mDNSPlatformTimeNow() + +Revision 1.21 2003/08/15 20:16:57 cheshire +Update comment for mDNSResponder takes too much RPRVT + +Revision 1.20 2003/08/12 19:56:24 cheshire +Update to APSL 2.0 + +Revision 1.19 2003/08/05 22:20:15 cheshire + Need to check IP TTL on responses + +Revision 1.18 2003/07/22 23:57:20 cheshire +Move platform-layer function prototypes from mDNSClientAPI.h to mDNSPlatformFunctions.h where they belong + +Revision 1.17 2003/07/19 03:15:15 cheshire +Add generic MemAllocate/MemFree prototypes to mDNSPlatformFunctions.h, +and add the obvious trivial implementations to each platform support layer + +Revision 1.16 2003/07/02 21:19:46 cheshire + Update copyright notices, etc., in source code comments + +Revision 1.15 2003/05/23 22:39:45 cheshire + Need to adjust maximum packet size for IPv6 + +Revision 1.14 2003/04/28 21:54:57 cheshire +Fix compiler warning + +Revision 1.13 2003/03/15 04:40:36 cheshire +Change type called "mDNSOpaqueID" to the more descriptive name "mDNSInterfaceID" + +Revision 1.12 2003/02/21 01:54:08 cheshire +Bug #: 3099194 mDNSResponder needs performance improvements +Switched to using new "mDNS_Execute" model (see "Implementer Notes.txt") + +Revision 1.11 2002/12/23 22:13:29 jgraessl + +Reviewed by: Stuart Cheshire +Initial IPv6 support for mDNSResponder. + +Revision 1.10 2002/09/21 20:44:49 zarzycki +Added APSL info + +Revision 1.9 2002/09/19 04:20:43 cheshire +Remove high-ascii characters that confuse some systems + +Revision 1.8 2002/09/16 23:12:14 cheshire +Minor code tidying + +Revision 1.7 2002/09/16 18:41:42 cheshire +Merge in license terms from Quinn's copy, in preparation for Darwin release + +*/ + +// Note: All moved to mDNSClientAPI.h diff --git a/src/mDNSPosix.c b/src/mDNSPosix.c new file mode 100644 index 00000000..c0355ad1 --- /dev/null +++ b/src/mDNSPosix.c @@ -0,0 +1,1008 @@ +/* + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * 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@ + * + * Formatting notes: + * This code follows the "Whitesmiths style" C indentation rules. Plenty of discussion + * on C indentation can be found on the web, such as , + * but for the sake of brevity here I will say just this: Curly braces are not syntactially + * part of an "if" statement; they are the beginning and ending markers of a compound statement; + * therefore common sense dictates that if they are part of a compound statement then they + * should be indented to the same level as everything else in that compound statement. + * Indenting curly braces at the same level as the "if" implies that curly braces are + * part of the "if", which is false. (This is as misleading as people who write "char* x,y;" + * thinking that variables x and y are both of type "char*" -- and anyone who doesn't + * understand why variable y is not of type "char*" just proves the point that poor code + * layout leads people to unfortunate misunderstandings about how the C language really works.) + + Change History (most recent first): + +$Log$ +Revision 1.1 2004/03/29 17:55:17 rpedde +Flatten mdns stuff + +Revision 1.4 2004/03/02 00:03:37 rpedde +Merge new rendezvous code + +Revision 1.25 2003/10/30 19:25:49 cheshire +Fix signed/unsigned warning on certain compilers + +Revision 1.24 2003/08/18 23:12:23 cheshire + mDNSResponder divide by zero in mDNSPlatformTimeNow() + +Revision 1.23 2003/08/12 19:56:26 cheshire +Update to APSL 2.0 + +Revision 1.22 2003/08/06 18:46:15 cheshire +LogMsg() errors are serious -- always report them to stderr, regardless of debugging level + +Revision 1.21 2003/08/06 18:20:51 cheshire +Makefile cleanup + +Revision 1.20 2003/08/05 23:56:26 cheshire +Update code to compile with the new mDNSCoreReceive() function that requires a TTL +(Right now mDNSPosix.c just reports 255 -- we should fix this) + +Revision 1.19 2003/07/19 03:15:16 cheshire +Add generic MemAllocate/MemFree prototypes to mDNSPlatformFunctions.h, +and add the obvious trivial implementations to each platform support layer + +Revision 1.18 2003/07/14 18:11:54 cheshire +Fix stricter compiler warnings + +Revision 1.17 2003/07/13 01:08:38 cheshire +There's not much point running mDNS over a point-to-point link; exclude those + +Revision 1.16 2003/07/02 21:19:59 cheshire + Update copyright notices, etc., in source code comments + +Revision 1.15 2003/06/18 05:48:41 cheshire +Fix warnings + +Revision 1.14 2003/05/26 03:21:30 cheshire +Tidy up address structure naming: +mDNSIPAddr => mDNSv4Addr (for consistency with mDNSv6Addr) +mDNSAddr.addr.ipv4 => mDNSAddr.ip.v4 +mDNSAddr.addr.ipv6 => mDNSAddr.ip.v6 + +Revision 1.13 2003/05/26 03:01:28 cheshire + sprintf/vsprintf-style functions are unsafe; use snprintf/vsnprintf instead + +Revision 1.12 2003/05/21 03:49:18 cheshire +Fix warning + +Revision 1.11 2003/05/06 00:00:50 cheshire + Rationalize naming of domainname manipulation functions + +Revision 1.10 2003/04/25 01:45:57 cheshire + mDNS_RegisterNoSuchService needs to include a host name + +Revision 1.9 2003/03/20 21:10:31 cheshire +Fixes done at IETF 56 to make mDNSProxyResponderPosix run on Solaris + +Revision 1.8 2003/03/15 04:40:38 cheshire +Change type called "mDNSOpaqueID" to the more descriptive name "mDNSInterfaceID" + +Revision 1.7 2003/03/13 03:46:21 cheshire +Fixes to make the code build on Linux + +Revision 1.6 2003/03/08 00:35:56 cheshire +Switched to using new "mDNS_Execute" model (see "mDNSCore/Implementer Notes.txt") + +Revision 1.5 2002/12/23 22:13:31 jgraessl +Reviewed by: Stuart Cheshire +Initial IPv6 support for mDNSResponder. + +Revision 1.4 2002/09/27 01:47:45 cheshire +Workaround for Linux 2.0 systems that don't have IP_PKTINFO + +Revision 1.3 2002/09/21 20:44:53 zarzycki +Added APSL info + +Revision 1.2 2002/09/19 21:25:36 cheshire +mDNS_snprintf() doesn't need to be in a separate file + +Revision 1.1 2002/09/17 06:24:34 cheshire +First checkin +*/ + +#include "mDNSClientAPI.h" // Defines the interface provided to the client layer above +#include "mDNSPlatformFunctions.h" // Defines the interface to the supporting layer below +#include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mDNSUNP.h" + +// *************************************************************************** +// Structures + +// PosixNetworkInterface is a record extension of the core NetworkInterfaceInfo +// type that supports extra fields needed by the Posix platform. +// +// IMPORTANT: coreIntf must be the first field in the structure because +// we cast between pointers to the two different types regularly. + +typedef struct PosixNetworkInterface PosixNetworkInterface; + +struct PosixNetworkInterface + { + NetworkInterfaceInfo coreIntf; + const char * intfName; + PosixNetworkInterface * aliasIntf; + int index; + int multicastSocket; + int multicastSocketv6; + }; + +// *************************************************************************** +// Globals (for debugging) + +static int num_registered_interfaces = 0; +static int num_pkts_accepted = 0; +static int num_pkts_rejected = 0; + +// *************************************************************************** +// Functions + +int gMDNSPlatformPosixVerboseLevel = 0; + +// Note, this uses mDNS_vsnprintf instead of standard "vsnprintf", because mDNS_vsnprintf knows +// how to print special data types like IP addresses and length-prefixed domain names +mDNSexport void debugf_(const char *format, ...) + { + unsigned char buffer[512]; + va_list ptr; + va_start(ptr,format); + buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0; + va_end(ptr); + if (gMDNSPlatformPosixVerboseLevel >= 1) + fprintf(stderr, "%s\n", buffer); + fflush(stderr); + } + +mDNSexport void verbosedebugf_(const char *format, ...) + { + unsigned char buffer[512]; + va_list ptr; + va_start(ptr,format); + buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0; + va_end(ptr); + if (gMDNSPlatformPosixVerboseLevel >= 2) + fprintf(stderr, "%s\n", buffer); + fflush(stderr); + } + +mDNSexport void LogMsg(const char *format, ...) + { + unsigned char buffer[512]; + va_list ptr; + va_start(ptr,format); + buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0; + va_end(ptr); + fprintf(stderr, "%s\n", buffer); + fflush(stderr); + } + +#define PosixErrorToStatus(errNum) ((errNum) == 0 ? mStatus_NoError : mStatus_UnknownErr) + +static void SockAddrTomDNSAddr(const struct sockaddr *const sa, mDNSAddr *ipAddr, mDNSIPPort *ipPort) + { + switch (sa->sa_family) + { + case AF_INET: + { + struct sockaddr_in* sin = (struct sockaddr_in*)sa; + ipAddr->type = mDNSAddrType_IPv4; + ipAddr->ip.v4.NotAnInteger = sin->sin_addr.s_addr; + if (ipPort) ipPort->NotAnInteger = sin->sin_port; + break; + } + +#ifdef mDNSIPv6Support + case AF_INET6: + { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa; + assert(sin6->sin6_len == sizeof(*sin6)); + ipAddr->type = mDNSAddrType_IPv6; + ipAddr->ip.v6 = *(mDNSv6Addr*)&sin6->sin6_addr; + if (ipPort) ipPort->NotAnInteger = sin6->sin6_port; + break; + } +#endif + + default: + verbosedebugf("SockAddrTomDNSAddr: Uknown address family %d\n", sa->sa_family); + ipAddr->type = mDNSAddrType_None; + if (ipPort) ipPort->NotAnInteger = 0; + break; + } + } + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Send and Receive +#endif + +// mDNS core calls this routine when it needs to send a packet. +mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const DNSMessage *const msg, const mDNSu8 *const end, + mDNSInterfaceID InterfaceID, mDNSIPPort srcPort, const mDNSAddr *dst, mDNSIPPort dstPort) + { + int err; + struct sockaddr_storage to; + PosixNetworkInterface * thisIntf; + + assert(m != NULL); + assert(msg != NULL); + assert(end != NULL); + assert( (((char *) end) - ((char *) msg)) > 0 ); + assert(InterfaceID != 0); // Can't send from zero source address + assert(srcPort.NotAnInteger != 0); // Nor from a zero source port + assert(dstPort.NotAnInteger != 0); // Nor from a zero source port + + if (dst->type == mDNSAddrType_IPv4) + { + struct sockaddr_in *sin = (struct sockaddr_in*)&to; +#ifndef NOT_HAVE_SA_LEN + sin->sin_len = sizeof(*sin); +#endif + sin->sin_family = AF_INET; + sin->sin_port = dstPort.NotAnInteger; + sin->sin_addr.s_addr = dst->ip.v4.NotAnInteger; + } + +#ifdef mDNSIPv6Support + else if (dst->type == mDNSAddrType_IPv6) + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&to; + mDNSPlatformMemZero(sin6, sizeof(*sin6)); + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = dstPort.NotAnInteger; + sin6->sin6_addr = *(struct in6_addr*)&dst->ip.v6; + } +#endif + + err = 0; + thisIntf = (PosixNetworkInterface *)(InterfaceID); + if (dst->type == mDNSAddrType_IPv4) + err = sendto(thisIntf->multicastSocket, msg, (char*)end - (char*)msg, 0, (struct sockaddr *)&to, GET_SA_LEN(to)); + +#ifdef mDNSIPv6Support + else if (dst->type == mDNSAddrType_IPv6) + err = sendto(thisIntf->multicastSocketv6, msg, (char*)end - (char*)msg, 0, (struct sockaddr *)&to, GET_SA_LEN(to)); +#endif + + if (err > 0) err = 0; + else if (err < 0) + verbosedebugf("mDNSPlatformSendUDP got error %d (%s) sending packet to %#a on interface %#a/%s/%d", + errno, strerror(errno), dst, &thisIntf->coreIntf.ip, thisIntf->intfName, thisIntf->index); + + return PosixErrorToStatus(err); + } + +// This routine is called when the main loop detects that data is available on a socket. +static void SocketDataReady(mDNS *const m, PosixNetworkInterface *intf, int skt) + { + mDNSAddr senderAddr, destAddr; + mDNSIPPort senderPort; + ssize_t packetLen; + DNSMessage packet; + struct my_in_pktinfo packetInfo; + struct sockaddr_storage from; + socklen_t fromLen; + int flags; + mDNSBool reject; + + assert(m != NULL); + assert(intf != NULL); + assert(skt >= 0); + + fromLen = sizeof(from); + flags = 0; + packetLen = recvfrom_flags(skt, &packet, sizeof(packet), &flags, (struct sockaddr *) &from, &fromLen, &packetInfo); + + if (packetLen >= 0) + { + SockAddrTomDNSAddr((struct sockaddr*)&from, &senderAddr, &senderPort); + SockAddrTomDNSAddr((struct sockaddr*)&packetInfo.ipi_addr, &destAddr, NULL); + + // If we have broken IP_RECVDSTADDR functionality (so far + // I've only seen this on OpenBSD) then apply a hack to + // convince mDNS Core that this isn't a spoof packet. + // Basically what we do is check to see whether the + // packet arrived as a multicast and, if so, set its + // destAddr to the mDNS address. + // + // I must admit that I could just be doing something + // wrong on OpenBSD and hence triggering this problem + // but I'm at a loss as to how. + // + // If this platform doesn't have IP_PKTINFO or IP_RECVDSTADDR, then we have + // no way to tell the destination address or interface this packet arrived on, + // so all we can do is just assume it's a multicast + + #if HAVE_BROKEN_RECVDSTADDR || (!defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR)) + if ( (destAddr.NotAnInteger == 0) && (flags & MSG_MCAST) ) + { + destAddr.type == senderAddr.type; + if (senderAddr.type == mDNSAddrType_IPv4) destAddr.ip.v4 = AllDNSLinkGroup; + else if (senderAddr.type == mDNSAddrType_IPv6) destAddr.ip.v6 = AllDNSLinkGroupv6; + } + #endif + + // We only accept the packet if the interface on which it came + // in matches the interface associated with this socket. + // We do this match by name or by index, depending on which + // information is available. recvfrom_flags sets the name + // to "" if the name isn't available, or the index to -1 + // if the index is available. This accomodates the various + // different capabilities of our target platforms. + + reject = mDNSfalse; + if ( packetInfo.ipi_ifname[0] != 0 ) reject = (strcmp(packetInfo.ipi_ifname, intf->intfName) != 0); + else if ( packetInfo.ipi_ifindex != -1 ) reject = (packetInfo.ipi_ifindex != intf->index); + + if (reject) + { + verbosedebugf("SocketDataReady ignored a packet from %#a to %#a on interface %s/%d expecting %#a/%s/%d", + &senderAddr, &destAddr, packetInfo.ipi_ifname, packetInfo.ipi_ifindex, + &intf->coreIntf.ip, intf->intfName, intf->index); + packetLen = -1; + num_pkts_rejected++; + if (num_pkts_rejected > (num_pkts_accepted + 1) * (num_registered_interfaces + 1) * 2) + { + fprintf(stderr, + "*** WARNING: Received %d packets; Accepted %d packets; Rejected %d packets because of interface mismatch\n", + num_pkts_accepted + num_pkts_rejected, num_pkts_accepted, num_pkts_rejected); + num_pkts_accepted = 0; + num_pkts_rejected = 0; + } + } + else + { + verbosedebugf("SocketDataReady got a packet from %#a to %#a on interface %#a/%s/%d", + &senderAddr, &destAddr, &intf->coreIntf.ip, intf->intfName, intf->index); + num_pkts_accepted++; + } + } + + if (packetLen >= 0 && packetLen < (ssize_t)sizeof(DNSMessageHeader)) + { + debugf("SocketDataReady packet length (%d) too short", packetLen); + packetLen = -1; + } + + if (packetLen >= 0) + mDNSCoreReceive(m, &packet, (mDNSu8 *)&packet + packetLen, + &senderAddr, senderPort, &destAddr, MulticastDNSPort, intf->coreIntf.InterfaceID, 255); + } + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Init and Term +#endif + +// On OS X this gets the text of the field labelled "Computer Name" in the Sharing Prefs Control Panel +// Other platforms can either get the information from the appropriate place, +// or they can alternatively just require all registering services to provide an explicit name +mDNSlocal void GetUserSpecifiedFriendlyComputerName(domainlabel *const namelabel) + { + MakeDomainLabelFromLiteralString(namelabel, "Fill in Default Service Name Here"); + } + +// This gets the current hostname, truncating it at the first dot if necessary +mDNSlocal void GetUserSpecifiedRFC1034ComputerName(domainlabel *const namelabel) + { + int len = 0; + gethostname((char *)(&namelabel->c[1]), MAX_DOMAIN_LABEL); + while (len < MAX_DOMAIN_LABEL && namelabel->c[len+1] && namelabel->c[len+1] != '.') len++; + namelabel->c[0] = len; + } + +// Searches the interface list looking for the named interface. +// Returns a pointer to if it found, or NULL otherwise. +static PosixNetworkInterface *SearchForInterfaceByName(mDNS *const m, const char *intfName) + { + PosixNetworkInterface *intf; + + assert(m != NULL); + assert(intfName != NULL); + + intf = (PosixNetworkInterface*)(m->HostInterfaces); + while ( (intf != NULL) && (strcmp(intf->intfName, intfName) != 0) ) + intf = (PosixNetworkInterface *)(intf->coreIntf.next); + + return intf; + } + +// Frees the specified PosixNetworkInterface structure. The underlying +// interface must have already been deregistered with the mDNS core. +static void FreePosixNetworkInterface(PosixNetworkInterface *intf) + { + assert(intf != NULL); + if (intf->intfName != NULL) free((void *)intf->intfName); + if (intf->multicastSocket != -1) assert(close(intf->multicastSocket) == 0); + if (intf->multicastSocketv6 != -1) assert(close(intf->multicastSocketv6) == 0); + free(intf); + } + +// Grab the first interface, deregister it, free it, and repeat until done. +static void ClearInterfaceList(mDNS *const m) + { + assert(m != NULL); + + while (m->HostInterfaces) + { + PosixNetworkInterface *intf = (PosixNetworkInterface*)(m->HostInterfaces); + mDNS_DeregisterInterface(m, &intf->coreIntf); + if (gMDNSPlatformPosixVerboseLevel > 0) fprintf(stderr, "Deregistered interface %s\n", intf->intfName); + FreePosixNetworkInterface(intf); + } + num_registered_interfaces = 0; + num_pkts_accepted = 0; + num_pkts_rejected = 0; + } + +// Sets up a multicast send/receive socket for the specified +// port on the interface specified by the IP addrelss intfAddr. +static int SetupSocket(struct sockaddr *intfAddr, mDNSIPPort port, int interfaceIndex, int *sktPtr) + { + int err = 0; + static const int kOn = 1; + static const int kIntTwoFiveFive = 255; + static const unsigned char kByteTwoFiveFive = 255; + + (void) interfaceIndex; // Unused + assert(intfAddr != NULL); + assert(sktPtr != NULL); + assert(*sktPtr == -1); + + // Open the socket... + if (intfAddr->sa_family == AF_INET) *sktPtr = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); +#ifdef mDNSIPv6Support + else if (intfAddr->sa_family == AF_INET6) *sktPtr = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); +#endif + else return EINVAL; + + if (*sktPtr < 0) { err = errno; perror("socket"); } + + // ... with a shared UDP port + if (err == 0) + { + #if defined(SO_REUSEPORT) + err = setsockopt(*sktPtr, SOL_SOCKET, SO_REUSEPORT, &kOn, sizeof(kOn)); + #elif defined(SO_REUSEADDR) + err = setsockopt(*sktPtr, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + #else + #error This platform has no way to avoid address busy errors on multicast. + #endif + if (err < 0) { err = errno; perror("setsockopt - SO_REUSExxxx"); } + } + + // We want to receive destination addresses and interface identifiers. + if (intfAddr->sa_family == AF_INET) + { + struct ip_mreq imr; + struct sockaddr_in bindAddr; + if (err == 0) + { + #if defined(IP_PKTINFO) // Linux + err = setsockopt(*sktPtr, IPPROTO_IP, IP_PKTINFO, &kOn, sizeof(kOn)); + if (err < 0) { err = errno; perror("setsockopt - IP_PKTINFO"); } + #elif defined(IP_RECVDSTADDR) || defined(IP_RECVIF) // BSD and Solaris + #if defined(IP_RECVDSTADDR) + err = setsockopt(*sktPtr, IPPROTO_IP, IP_RECVDSTADDR, &kOn, sizeof(kOn)); + if (err < 0) { err = errno; perror("setsockopt - IP_RECVDSTADDR"); } + #endif + #if defined(IP_RECVIF) + if (err == 0) + { + err = setsockopt(*sktPtr, IPPROTO_IP, IP_RECVIF, &kOn, sizeof(kOn)); + if (err < 0) { err = errno; perror("setsockopt - IP_RECVIF"); } + } + #endif + #else + #warning This platform has no way to get the destination interface information -- will only work for single-homed hosts + #endif + } + + // Add multicast group membership on this interface + if (err == 0) + { + imr.imr_multiaddr.s_addr = AllDNSLinkGroup.NotAnInteger; + imr.imr_interface = ((struct sockaddr_in*)intfAddr)->sin_addr; + err = setsockopt(*sktPtr, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr)); + if (err < 0) { err = errno; perror("setsockopt - IP_ADD_MEMBERSHIP"); } + } + + // Specify outgoing interface too + if (err == 0) + { + err = setsockopt(*sktPtr, IPPROTO_IP, IP_MULTICAST_IF, &((struct sockaddr_in*)intfAddr)->sin_addr, sizeof(struct in_addr)); + if (err < 0) { err = errno; perror("setsockopt - IP_MULTICAST_IF"); } + } + + // Per the mDNS spec, send unicast packets with TTL 255 + if (err == 0) + { + err = setsockopt(*sktPtr, IPPROTO_IP, IP_TTL, &kIntTwoFiveFive, sizeof(kIntTwoFiveFive)); + if (err < 0) { err = errno; perror("setsockopt - IP_TTL"); } + } + + // and multicast packets with TTL 255 too + // There's some debate as to whether IP_MULTICAST_TTL is an int or a byte so we just try both. + if (err == 0) + { + err = setsockopt(*sktPtr, IPPROTO_IP, IP_MULTICAST_TTL, &kByteTwoFiveFive, sizeof(kByteTwoFiveFive)); + if (err < 0 && errno == EINVAL) + err = setsockopt(*sktPtr, IPPROTO_IP, IP_MULTICAST_TTL, &kIntTwoFiveFive, sizeof(kIntTwoFiveFive)); + if (err < 0) { err = errno; perror("setsockopt - IP_MULTICAST_TTL"); } + } + + // And start listening for packets + if (err == 0) + { + bindAddr.sin_family = AF_INET; + bindAddr.sin_port = port.NotAnInteger; + bindAddr.sin_addr.s_addr = INADDR_ANY; // Want to receive multicasts AND unicasts on this socket + err = bind(*sktPtr, (struct sockaddr *) &bindAddr, sizeof(bindAddr)); + if (err < 0) { err = errno; perror("bind"); fflush(stderr); } + } + } // endif (intfAddr->sa_family == AF_INET) + +#ifdef mDNSIPv6Support + else if (intfAddr->sa_family == AF_INET6) + { + struct ipv6_mreq imr6; + struct sockaddr_in6 bindAddr6; + if (err == 0) + { + #if defined(IPV6_PKTINFO) + err = setsockopt(*sktPtr, IPPROTO_IPV6, IPV6_PKTINFO, &kOn, sizeof(kOn)); + if (err < 0) { err = errno; perror("setsockopt - IPV6_PKTINFO"); } + #else + #warning This platform has no way to get the destination interface information for IPv6 -- will only work for single-homed hosts + #endif + } + + // Add multicast group membership on this interface + if (err == 0) + { + imr6.ipv6mr_multiaddr = *(const struct in6_addr*)&AllDNSLinkGroupv6; + imr6.ipv6mr_interface = interfaceIndex; + err = setsockopt(*sktPtr, IPPROTO_IPV6, IPV6_JOIN_GROUP, &imr6, sizeof(imr6)); + if (err < 0) + { + err = errno; + verbosedebugf("IPV6_JOIN_GROUP %.16a on %d failed.\n", &imr6.ipv6mr_multiaddr, imr6.ipv6mr_interface); + perror("setsockopt - IPV6_JOIN_GROUP"); + } + } + + // Specify outgoing interface too + if (err == 0) + { + u_int multicast_if = interfaceIndex; + err = setsockopt(*sktPtr, IPPROTO_IPV6, IPV6_MULTICAST_IF, &multicast_if, sizeof(multicast_if)); + if (err < 0) { err = errno; perror("setsockopt - IPV6_MULTICAST_IF"); } + } + + // We want to receive only IPv6 packets on this socket. + // Without this option, we may get IPv4 addresses as mapped addresses. + if (err == 0) + { + err = setsockopt(*sktPtr, IPPROTO_IPV6, IPV6_V6ONLY, &kOn, sizeof(kOn)); + if (err < 0) { err = errno; perror("setsockopt - IPV6_V6ONLY"); } + } + + // Per the mDNS spec, send unicast packets with TTL 255 + if (err == 0) + { + err = setsockopt(*sktPtr, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kIntTwoFiveFive, sizeof(kIntTwoFiveFive)); + if (err < 0) { err = errno; perror("setsockopt - IPV6_UNICAST_HOPS"); } + } + + // and multicast packets with TTL 255 too + // There's some debate as to whether IPV6_MULTICAST_HOPS is an int or a byte so we just try both. + if (err == 0) + { + err = setsockopt(*sktPtr, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kByteTwoFiveFive, sizeof(kByteTwoFiveFive)); + if (err < 0 && errno == EINVAL) + err = setsockopt(*sktPtr, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kIntTwoFiveFive, sizeof(kIntTwoFiveFive)); + if (err < 0) { err = errno; perror("setsockopt - IPV6_MULTICAST_HOPS"); } + } + + // And start listening for packets + if (err == 0) + { + mDNSPlatformMemZero(&bindAddr6, sizeof(bindAddr6)); + bindAddr6.sin6_len = sizeof(bindAddr6); + bindAddr6.sin6_family = AF_INET6; + bindAddr6.sin6_port = port.NotAnInteger; + bindAddr6.sin6_flowinfo = 0; +// bindAddr6.sin6_addr.s_addr = IN6ADDR_ANY_INIT; // Want to receive multicasts AND unicasts on this socket + bindAddr6.sin6_scope_id = 0; + err = bind(*sktPtr, (struct sockaddr *) &bindAddr6, sizeof(bindAddr6)); + if (err < 0) { err = errno; perror("bind"); fflush(stderr); } + } + } // endif (intfAddr->sa_family == AF_INET6) +#endif + + // Set the socket to non-blocking. + if (err == 0) + { + err = fcntl(*sktPtr, F_GETFL, 0); + if (err < 0) err = errno; + else + { + err = fcntl(*sktPtr, F_SETFL, err | O_NONBLOCK); + if (err < 0) err = errno; + } + } + + // Clean up + if (err != 0 && *sktPtr != -1) { assert(close(*sktPtr) == 0); *sktPtr = -1; } + assert( (err == 0) == (*sktPtr != -1) ); + return err; + } + +// Creates a PosixNetworkInterface for the interface whose IP address is +// intfAddr and whose name is intfName and registers it with mDNS core. +static int SetupOneInterface(mDNS *const m, struct sockaddr *intfAddr, const char *intfName) + { + int err = 0; + PosixNetworkInterface *intf; + PosixNetworkInterface *alias = NULL; + + assert(m != NULL); + assert(intfAddr != NULL); + assert(intfName != NULL); + + // Allocate the interface structure itself. + intf = malloc(sizeof(*intf)); + if (intf == NULL) { assert(0); err = ENOMEM; } + + // And make a copy of the intfName. + if (err == 0) + { + intf->intfName = strdup(intfName); + if (intf->intfName == NULL) { assert(0); err = ENOMEM; } + } + + if (err == 0) + { + // Set up the fields required by the mDNS core. + SockAddrTomDNSAddr(intfAddr, &intf->coreIntf.ip, NULL); + intf->coreIntf.Advertise = m->AdvertiseLocalAddresses; + + // Set up the extra fields in PosixNetworkInterface. + assert(intf->intfName != NULL); // intf->intfName already set up above + intf->index = if_nametoindex(intf->intfName); + intf->multicastSocket = -1; + intf->multicastSocketv6 = -1; + alias = SearchForInterfaceByName(m, intf->intfName); + if (alias == NULL) alias = intf; + intf->coreIntf.InterfaceID = (mDNSInterfaceID)alias; + + if (alias != intf) + debugf("SetupOneInterface: %s %#a is an alias of %#a", intfName, &intf->coreIntf.ip, &alias->coreIntf.ip); + } + + // Set up the multicast socket + if (err == 0) + { + if (alias->multicastSocket == -1 && intfAddr->sa_family == AF_INET) + err = SetupSocket(intfAddr, MulticastDNSPort, intf->index, &alias->multicastSocket); +#ifdef mDNSIPv6Support + else if (alias->multicastSocketv6 == -1 && intfAddr->sa_family == AF_INET6) + err = SetupSocket(intfAddr, MulticastDNSPort, intf->index, &alias->multicastSocketv6); +#endif + } + + // The interface is all ready to go, let's register it with the mDNS core. + if (err == 0) + err = mDNS_RegisterInterface(m, &intf->coreIntf); + + // Clean up. + if (err == 0) + { + num_registered_interfaces++; + debugf("SetupOneInterface: %s %#a Registered", intf->intfName, &intf->coreIntf.ip); + if (gMDNSPlatformPosixVerboseLevel > 0) + fprintf(stderr, "Registered interface %s\n", intf->intfName); + } + else + { + // Use intfName instead of intf->intfName in the next line to avoid dereferencing NULL. + debugf("SetupOneInterface: %s %#a failed to register %d", intfName, &intf->coreIntf.ip, err); + if (intf) { FreePosixNetworkInterface(intf); intf = NULL; } + } + + assert( (err == 0) == (intf != NULL) ); + + return err; + } + +static int SetupInterfaceList(mDNS *const m) + { + mDNSBool foundav4 = mDNSfalse; + int err = 0; + struct ifi_info *intfList = get_ifi_info(AF_INET, mDNStrue); + struct ifi_info *firstLoopback = NULL; + + assert(m != NULL); + debugf("SetupInterfaceList"); + + if (intfList == NULL) err = ENOENT; + +#ifdef mDNSIPv6Support + if (err == 0) /* Link the IPv6 list to the end of the IPv4 list */ + { + struct ifi_info **p = &intfList; + while (*p) p = &(*p)->ifi_next; + *p = get_ifi_info(AF_INET6, mDNStrue); + } +#endif + + if (err == 0) + { + struct ifi_info *i = intfList; + while (i) + { + if ( ((i->ifi_addr->sa_family == AF_INET) +#ifdef mDNSIPv6Support + || (i->ifi_addr->sa_family == AF_INET6) +#endif + ) && (i->ifi_flags & IFF_UP) && !(i->ifi_flags & IFF_POINTOPOINT) ) + { + if (i->ifi_flags & IFF_LOOPBACK) + { + if (firstLoopback == NULL) + firstLoopback = i; + } + else + { + if (SetupOneInterface(m, i->ifi_addr, i->ifi_name) == 0) + if (i->ifi_addr->sa_family == AF_INET) + foundav4 = mDNStrue; + } + } + i = i->ifi_next; + } + + // If we found no normal interfaces but we did find a loopback interface, register the + // loopback interface. This allows self-discovery if no interfaces are configured. + // Temporary workaround: Multicast loopback on IPv6 interfaces appears not to work. + // In the interim, we skip loopback interface only if we found at least one v4 interface to use + // if ( (m->HostInterfaces == NULL) && (firstLoopback != NULL) ) + if ( !foundav4 && firstLoopback ) + (void) SetupOneInterface(m, firstLoopback->ifi_addr, firstLoopback->ifi_name); + } + + // Clean up. + if (intfList != NULL) free_ifi_info(intfList); + return err; + } + +// mDNS core calls this routine to initialise the platform-specific data. +mDNSexport mStatus mDNSPlatformInit(mDNS *const m) + { + int err; + assert(m != NULL); + + // Tell mDNS core the names of this machine. + + // Set up the nice label + m->nicelabel.c[0] = 0; + GetUserSpecifiedFriendlyComputerName(&m->nicelabel); + if (m->nicelabel.c[0] == 0) MakeDomainLabelFromLiteralString(&m->nicelabel, "Macintosh"); + + // Set up the RFC 1034-compliant label + m->hostlabel.c[0] = 0; + GetUserSpecifiedRFC1034ComputerName(&m->hostlabel); + if (m->hostlabel.c[0] == 0) MakeDomainLabelFromLiteralString(&m->hostlabel, "Macintosh"); + + mDNS_GenerateFQDN(m); + + // Tell mDNS core about the network interfaces on this machine. + err = SetupInterfaceList(m); + + // We don't do asynchronous initialization on the Posix platform, so by the time + // we get here the setup will already have succeeded or failed. If it succeeded, + // we should just call mDNSCoreInitComplete() immediately. + if (err == 0) + mDNSCoreInitComplete(m, mStatus_NoError); + + return PosixErrorToStatus(err); + } + +// mDNS core calls this routine to clean up the platform-specific data. +// In our case all we need to do is to tear down every network interface. +mDNSexport void mDNSPlatformClose(mDNS *const m) + { + assert(m != NULL); + ClearInterfaceList(m); + } + +extern mStatus mDNSPlatformPosixRefreshInterfaceList(mDNS *const m) + { + int err; + ClearInterfaceList(m); + err = SetupInterfaceList(m); + return PosixErrorToStatus(err); + } + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Locking +#endif + +// On the Posix platform, locking is a no-op because we only ever enter +// mDNS core on the main thread. + +// mDNS core calls this routine when it wants to prevent +// the platform from reentering mDNS core code. +mDNSexport void mDNSPlatformLock (const mDNS *const m) + { + (void) m; // Unused + } + +// mDNS core calls this routine when it release the lock taken by +// mDNSPlatformLock and allow the platform to reenter mDNS core code. +mDNSexport void mDNSPlatformUnlock (const mDNS *const m) + { + (void) m; // Unused + } + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Strings +#endif + +// mDNS core calls this routine to copy C strings. +// On the Posix platform this maps directly to the ANSI C strcpy. +mDNSexport void mDNSPlatformStrCopy(const void *src, void *dst) + { + strcpy((char *)dst, (char *)src); + } + +// mDNS core calls this routine to get the length of a C string. +// On the Posix platform this maps directly to the ANSI C strlen. +mDNSexport mDNSu32 mDNSPlatformStrLen (const void *src) + { + return strlen((char*)src); + } + +// mDNS core calls this routine to copy memory. +// On the Posix platform this maps directly to the ANSI C memcpy. +mDNSexport void mDNSPlatformMemCopy(const void *src, void *dst, mDNSu32 len) + { + memcpy(dst, src, len); + } + +// mDNS core calls this routine to test whether blocks of memory are byte-for-byte +// identical. On the Posix platform this is a simple wrapper around ANSI C memcmp. +mDNSexport mDNSBool mDNSPlatformMemSame(const void *src, const void *dst, mDNSu32 len) + { + return memcmp(dst, src, len) == 0; + } + +// mDNS core calls this routine to clear blocks of memory. +// On the Posix platform this is a simple wrapper around ANSI C memset. +mDNSexport void mDNSPlatformMemZero( void *dst, mDNSu32 len) + { + memset(dst, 0, len); + } + +mDNSexport void * mDNSPlatformMemAllocate(mDNSu32 len) { return(malloc(len)); } +mDNSexport void mDNSPlatformMemFree (void *mem) { free(mem); } + +mDNSexport mDNSs32 mDNSPlatformOneSecond = 1024; + +mDNSexport mStatus mDNSPlatformTimeInit(mDNSs32 *timenow) + { + // No special setup is required on Posix -- we just use gettimeofday(); + // This is not really safe, because gettimeofday can go backwards if the user manually changes the date or time + // We should find a better way to do this + *timenow = mDNSPlatformTimeNow(); + return(mStatus_NoError); + } + +mDNSexport mDNSs32 mDNSPlatformTimeNow() + { + struct timeval tv; + gettimeofday(&tv, NULL); + // tv.tv_sec is seconds since 1st January 1970 (GMT, with no adjustment for daylight savings time) + // tv.tv_usec is microseconds since the start of this second (i.e. values 0 to 999999) + // We use the lower 22 bits of tv.tv_sec for the top 22 bits of our result + // and we multiply tv.tv_usec by 16 / 15625 to get a value in the range 0-1023 to go in the bottom 10 bits. + // This gives us a proper modular (cyclic) counter that has a resolution of roughly 1ms (actually 1/1024 second) + // and correctly cycles every 2^22 seconds (4194304 seconds = approx 48 days). + return( (tv.tv_sec << 10) | (tv.tv_usec * 16 / 15625) ); + } + +mDNSexport void mDNSPosixGetFDSet(mDNS *const m, int *nfds, fd_set *readfds, struct timeval *timeout) + { + mDNSs32 ticks; + struct timeval interval; + + // 1. Call mDNS_Execute() to let mDNSCore do what it needs to do + mDNSs32 nextevent = mDNS_Execute(m); + + // 2. Build our list of active file descriptors + PosixNetworkInterface *info = (PosixNetworkInterface *)(m->HostInterfaces); + while (info) + { + if (info->multicastSocket != -1) + { + if (*nfds < info->multicastSocket + 1) + *nfds = info->multicastSocket + 1; + FD_SET(info->multicastSocket, readfds); + } + if (info->multicastSocketv6 != -1) + { + if (*nfds < info->multicastSocketv6 + 1) + *nfds = info->multicastSocketv6 + 1; + FD_SET(info->multicastSocketv6, readfds); + } + info = (PosixNetworkInterface *)(info->coreIntf.next); + } + + // 3. Calculate the time remaining to the next scheduled event (in struct timeval format) + ticks = nextevent - mDNSPlatformTimeNow(); + if (ticks < 1) ticks = 1; + interval.tv_sec = ticks >> 10; // The high 22 bits are seconds + interval.tv_usec = ((ticks & 0x3FF) * 15625) / 16; // The low 10 bits are 1024ths + + // 4. If client's proposed timeout is more than what we want, then reduce it + if (timeout->tv_sec > interval.tv_sec || + (timeout->tv_sec == interval.tv_sec && timeout->tv_usec > interval.tv_usec)) + *timeout = interval; + } + +mDNSexport void mDNSPosixProcessFDSet(mDNS *const m, fd_set *readfds) + { + PosixNetworkInterface *info; + assert(m != NULL); + assert(readfds != NULL); + info = (PosixNetworkInterface *)(m->HostInterfaces); + while (info) + { + if (info->multicastSocket != -1 && FD_ISSET(info->multicastSocket, readfds)) + { + FD_CLR(info->multicastSocket, readfds); + SocketDataReady(m, info, info->multicastSocket); + } + if (info->multicastSocketv6 != -1 && FD_ISSET(info->multicastSocketv6, readfds)) + { + FD_CLR(info->multicastSocketv6, readfds); + SocketDataReady(m, info, info->multicastSocketv6); + } + info = (PosixNetworkInterface *)(info->coreIntf.next); + } + } diff --git a/src/mDNSPosix.h b/src/mDNSPosix.h new file mode 100644 index 00000000..6a3407e4 --- /dev/null +++ b/src/mDNSPosix.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * 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$ +Revision 1.1 2004/03/29 17:55:17 rpedde +Flatten mdns stuff + +Revision 1.2 2004/03/02 00:03:37 rpedde +Merge new rendezvous code + +Revision 1.9 2003/10/30 19:25:19 cheshire +Fix warning on certain compilers + +Revision 1.8 2003/08/12 19:56:26 cheshire +Update to APSL 2.0 + +Revision 1.7 2003/07/02 21:19:59 cheshire + Update copyright notices, etc., in source code comments + +Revision 1.6 2003/03/13 03:46:21 cheshire +Fixes to make the code build on Linux + +Revision 1.5 2003/03/08 00:35:56 cheshire +Switched to using new "mDNS_Execute" model (see "mDNSCore/Implementer Notes.txt") + +Revision 1.4 2002/12/23 22:13:31 jgraessl + +Reviewed by: Stuart Cheshire +Initial IPv6 support for mDNSResponder. + +Revision 1.3 2002/09/21 20:44:53 zarzycki +Added APSL info + +Revision 1.2 2002/09/19 04:20:44 cheshire +Remove high-ascii characters that confuse some systems + +Revision 1.1 2002/09/17 06:24:34 cheshire +First checkin + +*/ + +#ifndef __mDNSPlatformPosix_h +#define __mDNSPlatformPosix_h + +#include + +#if HAVE_IPV6 +#define mDNSIPv6Support 1 +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +// This is a global because debugf_() needs to be able to check its value +extern int gMDNSPlatformPosixVerboseLevel; + +struct mDNS_PlatformSupport_struct + { + // No additional data required for Posix at this time + long dummy[1]; // Some compilers don't like empty structures + }; + +extern mStatus mDNSPlatformPosixRefreshInterfaceList(mDNS *const m); + // See comment in implementation. + +// Call mDNSPosixGetFDSet before calling select(), to update the parameters +// as may be necessary to meet the needs of the mDNSCore code. +// The timeout pointer MUST NOT be NULL. +// Set timeout->tv_sec to 0x3FFFFFFF if you want to have effectively no timeout +// After calling mDNSPosixGetFDSet(), call select(nfds, &readfds, NULL, NULL, &timeout); as usual +// After select() returns, call mDNSPosixProcessFDSet() to let mDNSCore do its work +extern void mDNSPosixGetFDSet(mDNS *const m, int *nfds, fd_set *readfds, struct timeval *timeout); +extern void mDNSPosixProcessFDSet(mDNS *const m, fd_set *readfds); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/src/mDNSUNP.c b/src/mDNSUNP.c new file mode 100644 index 00000000..24107049 --- /dev/null +++ b/src/mDNSUNP.c @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * 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$ +Revision 1.1 2004/03/29 17:55:17 rpedde +Flatten mdns stuff + +Revision 1.3 2004/03/02 00:03:37 rpedde +Merge new rendezvous code + +Revision 1.12 2003/09/02 20:47:13 cheshire +Fix signed/unsigned warning + +Revision 1.11 2003/08/12 19:56:26 cheshire +Update to APSL 2.0 + +Revision 1.10 2003/08/06 18:20:51 cheshire +Makefile cleanup + +Revision 1.9 2003/07/14 18:11:54 cheshire +Fix stricter compiler warnings + +Revision 1.8 2003/07/02 21:19:59 cheshire + Update copyright notices, etc., in source code comments + +Revision 1.7 2003/03/20 21:10:31 cheshire +Fixes done at IETF 56 to make mDNSProxyResponderPosix run on Solaris + +Revision 1.6 2003/03/13 03:46:21 cheshire +Fixes to make the code build on Linux + +Revision 1.5 2003/02/07 03:02:02 cheshire +Submitted by: Mitsutaka Watanabe +The code saying "index += 1;" was effectively making up random interface index values. +The right way to find the correct interface index is if_nametoindex(); + +Revision 1.4 2002/12/23 22:13:31 jgraessl + +Reviewed by: Stuart Cheshire +Initial IPv6 support for mDNSResponder. + +Revision 1.3 2002/09/21 20:44:53 zarzycki +Added APSL info + +Revision 1.2 2002/09/19 04:20:44 cheshire +Remove high-ascii characters that confuse some systems + +Revision 1.1 2002/09/17 06:24:34 cheshire +First checkin + +*/ + +#include "mDNSUNP.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Solaris defined SIOCGIFCONF etc in but + other platforms don't even have that include file. So, + if we haven't yet got a definition, let's try to find + . +*/ + +#ifndef SIOCGIFCONF + #include +#endif + +/* sockaddr_dl is only referenced if we're using IP_RECVIF, + so only include the header in that case. +*/ + +#ifdef IP_RECVIF + #include +#endif + + +struct ifi_info *get_ifi_info(int family, int doaliases) +{ + int junk; + struct ifi_info *ifi, *ifihead, **ifipnext; + int sockfd, len, lastlen, flags, myflags; + char *ptr, *buf, lastname[IFNAMSIZ], *cptr; + struct ifconf ifc; + struct ifreq *ifr, ifrcopy; + struct sockaddr_in *sinptr; + +#if defined(AF_INET6) && defined(HAVE_IPV6) + struct sockaddr_in6 *sinptr6; +#endif + + sockfd = -1; + buf = NULL; + ifihead = NULL; + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + goto gotError; + } + + lastlen = 0; + len = 100 * sizeof(struct ifreq); /* initial buffer size guess */ + for ( ; ; ) { + buf = malloc(len); + if (buf == NULL) { + goto gotError; + } + ifc.ifc_len = len; + ifc.ifc_buf = buf; + if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0) { + if (errno != EINVAL || lastlen != 0) { + goto gotError; + } + } else { + if (ifc.ifc_len == lastlen) + break; /* success, len has not changed */ + lastlen = ifc.ifc_len; + } + len += 10 * sizeof(struct ifreq); /* increment */ + free(buf); + } + ifihead = NULL; + ifipnext = &ifihead; + lastname[0] = 0; +/* end get_ifi_info1 */ + +/* include get_ifi_info2 */ + for (ptr = buf; ptr < buf + ifc.ifc_len; ) { + ifr = (struct ifreq *) ptr; + + len = GET_SA_LEN(ifr->ifr_addr); + ptr += sizeof(ifr->ifr_name) + len; /* for next one in buffer */ + +// fprintf(stderr, "intf %d name=%s AF=%d\n", index, ifr->ifr_name, ifr->ifr_addr.sa_family); + + if (ifr->ifr_addr.sa_family != family) + continue; /* ignore if not desired address family */ + + myflags = 0; + if ( (cptr = strchr(ifr->ifr_name, ':')) != NULL) + *cptr = 0; /* replace colon will null */ + if (strncmp(lastname, ifr->ifr_name, IFNAMSIZ) == 0) { + if (doaliases == 0) + continue; /* already processed this interface */ + myflags = IFI_ALIAS; + } + memcpy(lastname, ifr->ifr_name, IFNAMSIZ); + + ifrcopy = *ifr; + if (ioctl(sockfd, SIOCGIFFLAGS, &ifrcopy) < 0) { + goto gotError; + } + + flags = ifrcopy.ifr_flags; + if ((flags & IFF_UP) == 0) + continue; /* ignore if interface not up */ + + ifi = calloc(1, sizeof(struct ifi_info)); + if (ifi == NULL) { + goto gotError; + } + *ifipnext = ifi; /* prev points to this new one */ + ifipnext = &ifi->ifi_next; /* pointer to next one goes here */ + + ifi->ifi_flags = flags; /* IFF_xxx values */ + ifi->ifi_myflags = myflags; /* IFI_xxx values */ + ifi->ifi_index = if_nametoindex(ifr->ifr_name); + memcpy(ifi->ifi_name, ifr->ifr_name, IFI_NAME); + ifi->ifi_name[IFI_NAME-1] = '\0'; +/* end get_ifi_info2 */ +/* include get_ifi_info3 */ + switch (ifr->ifr_addr.sa_family) { + case AF_INET: + sinptr = (struct sockaddr_in *) &ifr->ifr_addr; + if (ifi->ifi_addr == NULL) { + ifi->ifi_addr = calloc(1, sizeof(struct sockaddr_in)); + if (ifi->ifi_addr == NULL) { + goto gotError; + } + memcpy(ifi->ifi_addr, sinptr, sizeof(struct sockaddr_in)); + +#ifdef SIOCGIFBRDADDR + if (flags & IFF_BROADCAST) { + if (ioctl(sockfd, SIOCGIFBRDADDR, &ifrcopy) < 0) { + goto gotError; + } + sinptr = (struct sockaddr_in *) &ifrcopy.ifr_broadaddr; + ifi->ifi_brdaddr = calloc(1, sizeof(struct sockaddr_in)); + if (ifi->ifi_brdaddr == NULL) { + goto gotError; + } + memcpy(ifi->ifi_brdaddr, sinptr, sizeof(struct sockaddr_in)); + } +#endif + +#ifdef SIOCGIFDSTADDR + if (flags & IFF_POINTOPOINT) { + if (ioctl(sockfd, SIOCGIFDSTADDR, &ifrcopy) < 0) { + goto gotError; + } + sinptr = (struct sockaddr_in *) &ifrcopy.ifr_dstaddr; + ifi->ifi_dstaddr = calloc(1, sizeof(struct sockaddr_in)); + if (ifi->ifi_dstaddr == NULL) { + goto gotError; + } + memcpy(ifi->ifi_dstaddr, sinptr, sizeof(struct sockaddr_in)); + } +#endif + } + break; + +#if defined(AF_INET6) && defined(HAVE_IPV6) + case AF_INET6: + sinptr6 = (struct sockaddr_in6 *) &ifr->ifr_addr; + if (ifi->ifi_addr == NULL) { + ifi->ifi_addr = calloc(1, sizeof(struct sockaddr_in6)); + if (ifi->ifi_addr == NULL) { + goto gotError; + } + + /* Some platforms (*BSD) inject the prefix in IPv6LL addresses */ + /* We need to strip that out */ + if (IN6_IS_ADDR_LINKLOCAL(&sinptr6->sin6_addr)) + sinptr6->sin6_addr.__u6_addr.__u6_addr16[1] = 0; + memcpy(ifi->ifi_addr, sinptr6, sizeof(struct sockaddr_in6)); + } + break; +#endif + + default: + break; + } + } + goto done; + +gotError: + if (ifihead != NULL) { + free_ifi_info(ifihead); + ifihead = NULL; + } + +done: + if (buf != NULL) { + free(buf); + } + if (sockfd != -1) { + junk = close(sockfd); + assert(junk == 0); + } + return(ifihead); /* pointer to first structure in linked list */ +} +/* end get_ifi_info3 */ + +/* include free_ifi_info */ +void +free_ifi_info(struct ifi_info *ifihead) +{ + struct ifi_info *ifi, *ifinext; + + for (ifi = ifihead; ifi != NULL; ifi = ifinext) { + if (ifi->ifi_addr != NULL) + free(ifi->ifi_addr); + if (ifi->ifi_brdaddr != NULL) + free(ifi->ifi_brdaddr); + if (ifi->ifi_dstaddr != NULL) + free(ifi->ifi_dstaddr); + ifinext = ifi->ifi_next; /* can't fetch ifi_next after free() */ + free(ifi); /* the ifi_info{} itself */ + } +} +/* end free_ifi_info */ + +ssize_t +recvfrom_flags(int fd, void *ptr, size_t nbytes, int *flagsp, + struct sockaddr *sa, socklen_t *salenptr, struct my_in_pktinfo *pktp) +{ + struct msghdr msg; + struct iovec iov[1]; + ssize_t n; + +#ifdef CMSG_FIRSTHDR + struct cmsghdr *cmptr; + union { + struct cmsghdr cm; + char control[1024]; + } control_un; + + msg.msg_control = control_un.control; + msg.msg_controllen = sizeof(control_un.control); + msg.msg_flags = 0; +#else + memset(&msg, 0, sizeof(msg)); /* make certain msg_accrightslen = 0 */ +#endif /* CMSG_FIRSTHDR */ + + msg.msg_name = (void *) sa; + msg.msg_namelen = *salenptr; + iov[0].iov_base = ptr; + iov[0].iov_len = nbytes; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + if ( (n = recvmsg(fd, &msg, *flagsp)) < 0) + return(n); + + *salenptr = msg.msg_namelen; /* pass back results */ + if (pktp) { + /* 0.0.0.0, i/f = -1 */ + /* We set the interface to -1 so that the caller can + tell whether we returned a meaningful value or + just some default. Previously this code just + set the value to 0, but I'm concerned that 0 + might be a valid interface value. + */ + memset(pktp, 0, sizeof(struct my_in_pktinfo)); + pktp->ipi_ifindex = -1; + } +/* end recvfrom_flags1 */ + +/* include recvfrom_flags2 */ +#ifndef CMSG_FIRSTHDR + #warning CMSG_FIRSTHDR not defined. Will not be able to determine destination address, received interface, etc. + *flagsp = 0; /* pass back results */ + return(n); +#else + + *flagsp = msg.msg_flags; /* pass back results */ + if (msg.msg_controllen < (socklen_t)sizeof(struct cmsghdr) || + (msg.msg_flags & MSG_CTRUNC) || pktp == NULL) + return(n); + + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL; + cmptr = CMSG_NXTHDR(&msg, cmptr)) { + +#ifdef IP_PKTINFO +#if in_pktinfo_definition_is_missing +struct in_pktinfo +{ + int ipi_ifindex; + struct in_addr ipi_spec_dst; + struct in_addr ipi_addr; +}; +#endif + if (cmptr->cmsg_level == IPPROTO_IP && + cmptr->cmsg_type == IP_PKTINFO) { + struct in_pktinfo *tmp; + struct sockaddr_in *sin = (struct sockaddr_in*)&pktp->ipi_addr; + + tmp = (struct in_pktinfo *) CMSG_DATA(cmptr); + sin->sin_family = AF_INET; + sin->sin_addr = tmp->ipi_addr; + sin->sin_port = 0; + pktp->ipi_ifindex = tmp->ipi_ifindex; + continue; + } +#endif + +#ifdef IP_RECVDSTADDR + if (cmptr->cmsg_level == IPPROTO_IP && + cmptr->cmsg_type == IP_RECVDSTADDR) { + struct sockaddr_in *sin = (struct sockaddr_in*)&pktp->ipi_addr; + + sin->sin_family = AF_INET; + sin->sin_addr = *(struct in_addr*)CMSG_DATA(cmptr); + sin->sin_port = 0; + continue; + } +#endif + +#ifdef IP_RECVIF + if (cmptr->cmsg_level == IPPROTO_IP && + cmptr->cmsg_type == IP_RECVIF) { + struct sockaddr_dl *sdl = (struct sockaddr_dl *) CMSG_DATA(cmptr); + int nameLen = (sdl->sdl_nlen < IFI_NAME - 1) ? sdl->sdl_nlen : (IFI_NAME - 1); + pktp->ipi_ifindex = sdl->sdl_index; +#ifndef HAVE_BROKEN_RECVIF_NAME + strncpy(pktp->ipi_ifname, sdl->sdl_data, nameLen); +#endif + assert(pktp->ipi_ifname[IFI_NAME - 1] == 0); + // null terminated because of memset above + continue; + } +#endif + +#if defined(IPV6_PKTINFO) && defined(HAVE_IPV6) + if (cmptr->cmsg_level == IPPROTO_IPV6 && + cmptr->cmsg_type == IPV6_PKTINFO) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&pktp->ipi_addr; + struct in6_pktinfo *ip6_info = (struct in6_pktinfo*)CMSG_DATA(cmptr); + + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_addr = ip6_info->ipi6_addr; + sin6->sin6_flowinfo = 0; + sin6->sin6_scope_id = 0; + sin6->sin6_port = 0; + pktp->ipi_ifindex = ip6_info->ipi6_ifindex; + continue; + } +#endif + assert(0); // unknown ancillary data + } + return(n); +#endif /* CMSG_FIRSTHDR */ +} diff --git a/src/mDNSUNP.h b/src/mDNSUNP.h new file mode 100644 index 00000000..e35e7867 --- /dev/null +++ b/src/mDNSUNP.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * 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$ +Revision 1.1 2004/03/29 17:55:17 rpedde +Flatten mdns stuff + +Revision 1.3 2004/03/16 05:11:18 rpedde +Fix obvious typecast bug + +Revision 1.2 2004/03/02 00:03:37 rpedde +Merge new rendezvous code + +Revision 1.8 2003/08/12 19:56:26 cheshire +Update to APSL 2.0 + +Revision 1.7 2003/08/06 18:20:51 cheshire +Makefile cleanup + +Revision 1.6 2003/07/02 21:19:59 cheshire + Update copyright notices, etc., in source code comments + +Revision 1.5 2003/03/13 03:46:21 cheshire +Fixes to make the code build on Linux + +Revision 1.4 2002/12/23 22:13:32 jgraessl + +Reviewed by: Stuart Cheshire +Initial IPv6 support for mDNSResponder. + +Revision 1.3 2002/09/21 20:44:53 zarzycki +Added APSL info + +Revision 1.2 2002/09/19 04:20:44 cheshire +Remove high-ascii characters that confuse some systems + +Revision 1.1 2002/09/17 06:24:35 cheshire +First checkin + +*/ + +#ifndef __mDNSUNP_h +#define __mDNSUNP_h + +#include +#include +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +#ifdef NOT_HAVE_SOCKLEN_T + typedef unsigned int socklen_t; +#endif + +#if !defined(_SS_MAXSIZE) + #define sockaddr_storage sockaddr +#endif + +#ifndef NOT_HAVE_SA_LEN +#define GET_SA_LEN(X) (sizeof(struct sockaddr) > ((struct sockaddr*)&(X))->sa_len ? \ + sizeof(struct sockaddr) : ((struct sockaddr*)&(X))->sa_len ) +#elif mDNSIPv6Support +#define GET_SA_LEN(X) (((struct sockaddr*)&(X))->sa_family == AF_INET ? sizeof(struct sockaddr_in) : \ + ((struct sockaddr*)&(X))->sa_family == AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr)) +#else +#define GET_SA_LEN(X) (((struct sockaddr*)&(X))->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr)) +#endif + +#define IFI_NAME 16 /* same as IFNAMSIZ in */ +#define IFI_HADDR 8 /* allow for 64-bit EUI-64 in future */ + +// Renamed from my_in_pktinfo because in_pktinfo is used by Linux. + +struct my_in_pktinfo { + struct sockaddr_storage ipi_addr; + int ipi_ifindex; /* received interface index */ + char ipi_ifname[IFI_NAME]; /* received interface name */ +}; + +extern ssize_t recvfrom_flags(int fd, void *ptr, size_t nbytes, int *flagsp, + struct sockaddr *sa, socklen_t *salenptr, struct my_in_pktinfo *pktp); + +struct ifi_info { + char ifi_name[IFI_NAME]; /* interface name, null terminated */ + u_char ifi_haddr[IFI_HADDR]; /* hardware address */ + u_short ifi_hlen; /* #bytes in hardware address: 0, 6, 8 */ + short ifi_flags; /* IFF_xxx constants from */ + short ifi_myflags; /* our own IFI_xxx flags */ + int ifi_index; /* interface index */ + struct sockaddr *ifi_addr; /* primary address */ + struct sockaddr *ifi_brdaddr;/* broadcast address */ + struct sockaddr *ifi_dstaddr;/* destination address */ + struct ifi_info *ifi_next; /* next of these structures */ +}; + +#define IFI_ALIAS 1 /* ifi_addr is an alias */ + +extern struct ifi_info *get_ifi_info(int family, int doaliases); +extern void free_ifi_info(struct ifi_info *); + +#ifdef __cplusplus + } +#endif + +#endif