From 363210ebccdfa049e5699d7dda3851638f5d871a Mon Sep 17 00:00:00 2001 From: Ron Pedde Date: Sun, 8 Apr 2007 04:43:22 +0000 Subject: [PATCH] Update Apple's mDNS to 107.6, the Apache/BSD licenced version, to remove aspl taint. This is high in autoconf hackery, though --- configure.in | 11 +- reconf.sh.templ | 2 +- src/Makefile.am | 14 +- src/mDNS.c | 7073 --------------------------------- src/mDNSClientAPI.h | 1478 ------- src/mDNSDebug.h | 124 - src/mDNSPlatformFunctions.h | 86 - src/mDNSPosix.c | 1016 ----- src/mDNSPosix.h | 104 - src/mDNSUNP.c | 458 --- src/mDNSUNP.h | 126 - src/mdns/DNSCommon.c | 2179 +++++++++++ src/mdns/DNSCommon.h | 366 ++ src/mdns/DNSDigest.c | 1464 +++++++ src/mdns/GenLinkedList.c | 332 ++ src/mdns/GenLinkedList.h | 107 + src/mdns/Makefile.am | 11 + src/mdns/dns_sd.h | 1722 ++++++++ src/mdns/mDNS.c | 7360 +++++++++++++++++++++++++++++++++++ src/mdns/mDNSDebug.c | 22 + src/mdns/mDNSDebug.h | 190 + src/mdns/mDNSEmbeddedAPI.h | 2848 ++++++++++++++ src/mdns/mDNSPosix.c | 1755 +++++++++ src/mdns/mDNSPosix.h | 145 + src/mdns/mDNSUNP.c | 766 ++++ src/mdns/mDNSUNP.h | 187 + src/mdns/uDNS.c | 5242 +++++++++++++++++++++++++ src/mdns/uDNS.h | 190 + src/rend-posix.c | 18 +- 29 files changed, 24902 insertions(+), 10494 deletions(-) delete mode 100644 src/mDNS.c delete mode 100644 src/mDNSClientAPI.h delete mode 100644 src/mDNSDebug.h delete mode 100644 src/mDNSPlatformFunctions.h delete mode 100644 src/mDNSPosix.c delete mode 100644 src/mDNSPosix.h delete mode 100644 src/mDNSUNP.c delete mode 100644 src/mDNSUNP.h create mode 100644 src/mdns/DNSCommon.c create mode 100644 src/mdns/DNSCommon.h create mode 100644 src/mdns/DNSDigest.c create mode 100755 src/mdns/GenLinkedList.c create mode 100755 src/mdns/GenLinkedList.h create mode 100644 src/mdns/Makefile.am create mode 100755 src/mdns/dns_sd.h create mode 100755 src/mdns/mDNS.c create mode 100644 src/mdns/mDNSDebug.c create mode 100755 src/mdns/mDNSDebug.h create mode 100755 src/mdns/mDNSEmbeddedAPI.h create mode 100755 src/mdns/mDNSPosix.c create mode 100755 src/mdns/mDNSPosix.h create mode 100755 src/mdns/mDNSUNP.c create mode 100755 src/mdns/mDNSUNP.h create mode 100755 src/mdns/uDNS.c create mode 100755 src/mdns/uDNS.h diff --git a/configure.in b/configure.in index 0f0c7515..c792ff0f 100644 --- a/configure.in +++ b/configure.in @@ -2,7 +2,6 @@ dnl $Id$ dnl dnl Process this file with autoconf to produce a configure script. AC_INIT(config.h.in) - AM_CONFIG_HEADER(config.h) AM_INIT_AUTOMAKE(mt-daapd,0.2.1.1) @@ -160,14 +159,16 @@ dnl case $host in *solaris*) - CPPFLAGS="$CPPFLAGS -DNOT_HAVE_SA_LEN -D_XPG4_2" - CPPFLAGS="$CPPFLAGS -D__EXTENSIONS__ -DHAVE_BROKEN_RECVIF_NAME" + CPPFLAGS="$CPPFLAGS -DNOT_HAVE_DAEMON -DNOT_HAVE_SA_LEN " + CPPFLAGS="$CPPFLAGS -DNOT_HAVE_SOCKLEN_T -DNOT_HAVE_IF_NAMETOINDEX " + CPPFLAGS="$CPPFLAGS -DLOG_PERROR=0 -D_XPG4_2 -D__EXTENSIONS__ " + CPPFLAGS="$CPPFLAGS -DHAVE_BROKEN_RECVIF_NAME " CPPFLAGS="$CPPFLAGS -D_POSIX_PTHREAD_SEMANTICS" LIBS="${LIBS} -lnsl -lsocket";; *freebsd*) CPPFLAGS="$CPPFLAGS -DFREEBSD";; *linux*) - CPPFLAGS="$CPPFLAGS -DNOT_HAVE_SA_LEN";; + CPPFLAGS="$CPPFLAGS -DNOT_HAVE_SA_LEN -DUSES_NETLINK -DHAVE_LINUX";; *openbsd*) CPPFLAGS="$CPPFLAGS -DHAVE_BROKEN_RECVDSTADDR";; *darwin*) @@ -436,4 +437,4 @@ AC_CHECK_LIB(socket,socket,V_NETLIBS="-lsocket $V_NETLIBS",,) AC_SUBST(V_NETLIBS) -AC_OUTPUT(src/Makefile src/plugins/Makefile admin-root/Makefile admin-root/lib-js/Makefile admin-root/lib-js/script.aculo.us/Makefile contrib/Makefile contrib/init.d/Makefile Makefile) +AC_OUTPUT(src/Makefile src/plugins/Makefile admin-root/Makefile admin-root/lib-js/Makefile admin-root/lib-js/script.aculo.us/Makefile contrib/Makefile contrib/init.d/Makefile src/mdns/Makefile Makefile) diff --git a/reconf.sh.templ b/reconf.sh.templ index 5aae22a9..d9b2fab1 100755 --- a/reconf.sh.templ +++ b/reconf.sh.templ @@ -3,7 +3,7 @@ rm -f config.cache rm -f config.status #rm -f acconfig.h libtoolize --force -aclocal +aclocal -I m4 autoconf autoheader automake -a diff --git a/src/Makefile.am b/src/Makefile.am index b1b399a5..b4658b24 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,13 +1,17 @@ # $Id$ # -SUBDIRS=plugins +SUBDIRS = plugins sbin_PROGRAMS = mt-daapd bin_PROGRAMS = wavstreamer +LDADD= + if COND_REND_POSIX -PRENDSRC=mDNS.c mDNSClientAPI.h mDNSDebug.h mDNSPosix.c mDNSUNP.c \ -rend-posix.c mDNSPlatformFunctions.h rend-unix.c +SUBDIRS += mdns +AM_CPPFLAGS = -I./mdns +LDADD+=mdns/libmdns.la +PRENDSRC=rend-unix.c rend-posix.c endif if COND_REND_HOWL @@ -66,9 +70,7 @@ mt_daapd_SOURCES = main.c daapd.h rend.h uici.c uici.h webserver.c \ $(PRENDSRC) $(ORENDSRC) $(HRENDSRC) $(ARENDSRC) $(OGGVORBISSRC) \ $(FLACSRC) $(MUSEPACKSRC) $(SQLITEDB) $(SQLITE3DB) $(SQLDB) $(GDBM) -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 scan-mpc.c \ +EXTRA_DIST = rend-howl.c rend-posix.c rend-osx.c scan-mpc.c \ scan-ogg.c scan-flac.c db-sql.c db-sql.h \ db-sql-sqlite2.h db-sql-sqlite2.c \ db-sql-sqlite3.h db-sql-sqlite3.c \ diff --git a/src/mDNS.c b/src/mDNS.c deleted file mode 100644 index c941fd78..00000000 --- a/src/mDNS.c +++ /dev/null @@ -1,7073 +0,0 @@ -/* - * 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.3 2006/02/26 08:46:24 rpedde -Merged win32-branch - -Revision 1.2.4.1 2006/02/26 08:28:35 rpedde -unix fixes from win32 port - -Revision 1.2 2005/01/10 01:07:00 rpedde -Synchronize mDNS to Apples 58.8 drop - -Revision 1.307.2.8 2004/04/03 05:18:19 bradley -Added cast to fix signed/unsigned warning due to int promotion. - -Revision 1.307.2.7 2004/03/30 06:46:24 cheshire -Compiler warning fixes from Don Woodward at Roku Labs - -Revision 1.307.2.6 2004/03/09 03:03:38 cheshire - Don't take lock until after mDNS_Update() has validated that the data is good - -Revision 1.307.2.5 2004/03/02 02:55:24 cheshire - Properly support "_services._dns-sd._udp" meta-queries - -Revision 1.307.2.4 2004/02/18 01:55:08 cheshire -: Increase delay to 400ms when answering queries with multi-packet KA lists - -Revision 1.307.2.3 2004/01/28 23:08:45 cheshire -: Hard code domain enumeration functions to return ".local" only - -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 -#include "daapd.h" -// 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._dns-sd._udp.local.", - "_default._browse._dns-sd._udp.local.", - "_register._dns-sd._udp.local.", - "_default._register._dns-sd._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)); - 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)); - 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 *)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 = (mDNSu32)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; - mDNSs32 delayresponse = 0; - 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 = mDNSPlatformOneSecond; // Divided by 50 = 20ms - - // *** - // *** 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 = mDNSPlatformOneSecond; // Divided by 50 = 20ms - - // 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 - - // *** - // *** 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; - } - } - } - if (rr->resrec.RecordType == kDNSRecordTypeShared) - { - if (query->h.flags.b[0] & kDNSFlag0_TC) delayresponse = mDNSPlatformOneSecond * 20; // Divided by 50 = 400ms - else delayresponse = mDNSPlatformOneSecond; // Divided by 50 = 20ms - } - } - 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 || (m->SuppressSending - m->timenow) < (delayresponse + 49) / 50)) - { - // Pick a random delay: - // We start with the base delay chosen above (typically either 1 second or 20 seconds), - // and add a random value in the range 0-5 seconds (making 1-6 seconds or 20-25 seconds). - // This is an integer value, with resolution determined by the platform clock rate. - // We then divide that by 50 to get the delay value in ticks. We defer the division until last - // to get better results on platforms with coarse clock granularity (e.g. ten ticks per second). - // The +49 before dividing is to ensure we round up, not down, to ensure that even - // on platforms where the native clock rate is less than fifty ticks per second, - // we still guarantee that the final calculated delay is at least one platform tick. - // We want to make sure we don't ever allow the delay to be zero ticks, - // because if that happens we'll fail the Rendezvous Conformance Test. - // Our final computed delay is 20-120ms for normal delayed replies, - // or 400-500ms in the case of multi-packet known-answer lists. - m->SuppressSending = m->timenow + (delayresponse + (mDNSs32)mDNSRandom((mDNSu32)mDNSPlatformOneSecond*5) + 49) / 50; - 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; - - // No sense doing this until we actually support unicast query/update - //return(mDNS_StartQuery(m, question)); - (void)m; // Unused - return(mStatus_NoError); - } - -// *************************************************************************** -#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) - { - 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); } - - mDNS_Lock(m); - - // 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*)"\x07_dns-sd\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 deleted file mode 100644 index ae6342dd..00000000 --- a/src/mDNSClientAPI.h +++ /dev/null @@ -1,1478 +0,0 @@ -/* - * 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.6 2006/02/26 08:46:24 rpedde -Merged win32-branch - -Revision 1.5.2.1 2006/02/26 08:28:35 rpedde -unix fixes from win32 port - -Revision 1.5 2005/02/21 08:10:34 rpedde -integrate server-side conversion patches, -Wall cleanups, AMD64 fixes, and xml-rpc cleanups - -Revision 1.4 2005/01/10 01:07:01 rpedde -Synchronize mDNS to Apples 58.8 drop - -Revision 1.114.2.9 2004/04/22 03:17:35 cheshire -Fix use of "struct __attribute__((__packed__))" so it only applies on GCC >= 2.9 - -Revision 1.114.2.8 2004/03/30 06:55:37 cheshire -Gave name to anonymous struct, to avoid errors on certain compilers. -(Thanks to ramaprasad.kr@hp.com for reporting this.) - -Revision 1.114.2.7 2004/03/09 02:31:27 cheshire -Remove erroneous underscore in 'packed_struct' (makes no difference now, but might in future) - -Revision 1.114.2.6 2004/03/02 02:55:25 cheshire - Properly support "_services._dns-sd._udp" meta-queries - -Revision 1.114.2.5 2004/02/18 23:35:17 cheshire -: Hard code domain enumeration functions to return ".local" only -Also make mDNS_StopGetDomains() a no-op too, so that we don't get warning messages in syslog - -Revision 1.114.2.4 2004/01/28 23:29:20 cheshire -Fix structure packing (only affects third-party Darwin developers) - -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 in a C file, it means the function is not callable outside this file -#ifndef mDNSlocal -#define mDNSlocal static -#endif -// If you see "mDNSexport" before a symbol in a C file, it means the symbol is exported for use by clients -// For every "mDNSexport" in a C file, there needs to be a corresponding "extern" declaration in some header file -// (When a C file #includes a header file, the "extern" declarations tell the compiler: -// "This symbol exists -- but not necessarily in this C file.") -#ifndef mDNSexport -#define mDNSexport -#endif - -// *************************************************************************** -// Structure packing macro - -// If we're not using GNUC, it's not fatal. -// Most compilers naturally pack the on-the-wire structures correctly anyway, so a plain "struct" is usually fine. -// In the event that structures are not packed correctly, mDNS_Init() will detect this and report an error, so the -// developer will know what's wrong, and can investigate what needs to be done on that compiler to provide proper packing. -#ifndef packedstruct - #if ((__GNUC__ > 2) || ((__GNUC__ == 2) && (__GNUC_MINOR__ >= 9))) - #define packedstruct struct __attribute__((__packed__)) - #define packedunion union __attribute__((__packed__)) - #else - #define packedstruct struct - #define packedunion union - #endif -#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; -#include -typedef int32_t mDNSs32; -typedef u_int32_t 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 mDNSInterfaceID_dummystruct { 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 packedunion { mDNSu8 b[2]; mDNSu16 NotAnInteger; } mDNSOpaque16; -typedef packedunion { mDNSu8 b[4]; mDNSu32 NotAnInteger; } mDNSOpaque32; -typedef packedunion { 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 packedstruct { mDNSu16 priority; mDNSu16 weight; mDNSIPPort port; domainname target; } rdataSRV; -typedef packedstruct { 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._dns-sd._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 transmission of this Q on *all* applicable interfaces - mDNSs32 ThisQInterval; // LastQTime + ThisQInterval is the next scheduled transmission 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); -// See notes above on use of NoCache/ZeroCacheSize -#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); -// In the Panther mDNSResponder we don't do unicast queries yet, so there's no point trying to do domain enumeration -// mDNS_GetDomains() and mDNS_StopGetDomains() are set to be no-ops so that clients don't try to do browse/register operations that will fail -//#define mDNS_StopGetDomains mDNS_StopQuery -#define mDNS_StopGetDomains(m,q) ((void)(m),(void)(q)) -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 packedstruct - { - 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 packedstruct - { - 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 deleted file mode 100644 index d4a215ec..00000000 --- a/src/mDNSDebug.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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.4 2006/02/26 08:46:24 rpedde -Merged win32-branch - -Revision 1.3.2.1 2006/02/26 08:28:35 rpedde -unix fixes from win32 port - -Revision 1.3 2005/07/21 03:40:07 rpedde -Crank up mdns debug messages - -Revision 1.2 2005/01/10 01:07:01 rpedde -Synchronize mDNS to Apples 58.8 drop - -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 deleted file mode 100644 index 6fff2006..00000000 --- a/src/mDNSPlatformFunctions.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.2 2005/01/10 01:07:01 rpedde -Synchronize mDNS to Apples 58.8 drop - -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 deleted file mode 100644 index 07665f67..00000000 --- a/src/mDNSPosix.c +++ /dev/null @@ -1,1016 +0,0 @@ -/* - * 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.6 2006/02/26 08:46:24 rpedde -Merged win32-branch - -Revision 1.5.4.1 2006/02/26 08:28:35 rpedde -unix fixes from win32 port - -Revision 1.5 2005/01/10 01:07:01 rpedde -Synchronize mDNS to Apples 58.8 drop - -Revision 1.25.2.1 2004/04/09 17:57:31 cheshire -Make sure to set the TxAndRx field so that duplicate suppression works correctly - -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 "daapd.h" -#include "err.h" -#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); - - DPRINTF(E_INF,L_REND,"%s\n",buffer); - } - -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); - DPRINTF(E_DBG,L_REND,"%s\n",buffer); - } - -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); - DPRINTF(E_WARN,L_REND,"%s\n",buffer); - } - -#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.ip.v4.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) - { - DPRINTF(E_LOG,L_REND, - "*** 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) DPRINTF(E_LOG,L_REND, "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; - DPRINTF(E_LOG,L_REND,"bind: %s\n",strerror(errno)); - } - } - } // 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; - intf->coreIntf.TxAndRx = mDNStrue; - - // 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) - DPRINTF(E_LOG,L_REND, "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 deleted file mode 100644 index 1c319a53..00000000 --- a/src/mDNSPosix.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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.3 2006/02/26 08:46:24 rpedde -Merged win32-branch - -Revision 1.2.4.1 2006/02/26 08:28:35 rpedde -unix fixes from win32 port - -Revision 1.2 2005/01/10 01:07:01 rpedde -Synchronize mDNS to Apples 58.8 drop - -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 deleted file mode 100644 index 8847f859..00000000 --- a/src/mDNSUNP.c +++ /dev/null @@ -1,458 +0,0 @@ -/* - * 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.6 2006/02/26 08:46:24 rpedde -Merged win32-branch - -Revision 1.5.2.1 2006/02/26 08:28:35 rpedde -unix fixes from win32 port - -Revision 1.5 2005/09/23 05:26:52 rpedde -commit the iTunes 5 fixes - -Revision 1.4 2005/03/05 06:37:37 rpedde -Roll back patch - breaks fbsd - -Revision 1.2 2005/01/10 01:07:01 rpedde -Synchronize mDNS to Apples 58.8 drop - -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 - -#include "daapd.h" -#include "err.h" - -/* 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; - // int index; - -#if defined(AF_INET6) && defined(HAVE_IPV6) - struct sockaddr_in6 *sinptr6; -#endif - - DPRINTF(E_DBG,L_REND,"get_ifi_info\n"); - - 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); - - /* This is completely whacked, and I really need to - * find out why this is the case, but I need to - * release a 0.2.2, and as the next stable won't - * have the apple mDNS included, I guess it's a - * small price to pay. - */ -#ifdef FREEBSD - ptr += sizeof(ifr->ifr_name) + len; /* for next one in buffer */ - // ptr += sizeof(*ifr); -#else - - ptr += sizeof(struct ifreq); /* for next one in buffer */ -#endif - - DPRINTF(E_DBG,L_REND,"intf name=%s AF=%d, flags=%08x\n", ifr->ifr_name, ifr->ifr_addr.sa_family,ifr->ifr_flags); - - 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 deleted file mode 100644 index 4794a41f..00000000 --- a/src/mDNSUNP.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.3 2005/01/10 18:11:29 rpedde -Fix compile error on solaris - -Revision 1.2 2005/01/10 01:07:01 rpedde -Synchronize mDNS to Apples 58.8 drop - -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 diff --git a/src/mdns/DNSCommon.c b/src/mdns/DNSCommon.c new file mode 100644 index 00000000..08507b02 --- /dev/null +++ b/src/mdns/DNSCommon.c @@ -0,0 +1,2179 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + Change History (most recent first): + +$Log: DNSCommon.c,v $ +Revision 1.100.2.1 2006/08/29 06:24:22 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.100 2006/06/08 22:58:46 cheshire + IPv6 link-local address prefix is FE80::/10, not FE80::/16 + +Revision 1.99 2006/05/18 01:32:33 cheshire + iChat: Lost connection with Bonjour +(mDNSResponder insufficiently defensive against malformed browsing PTR responses) + +Revision 1.98 2006/03/19 17:00:58 cheshire +Define symbol MaxMsg instead of using hard-coded constant value '80' + +Revision 1.97 2006/03/18 21:47:56 cheshire + Improve logic for delaying packets after repeated interface transitions + +Revision 1.96 2006/03/10 21:51:42 cheshire + After record update, old record sometimes remains in cache +Split out SameRDataBody() into a separate routine so it can be called from other code + +Revision 1.95 2006/03/08 22:43:11 cheshire +Use "localdomain" symbol instead of literal string + +Revision 1.94 2006/03/02 21:59:55 cheshire + Spurious warning "GetLargeResourceRecord: m->rec appears to be already in use" +Improve sanity checks & debugging support in GetLargeResourceRecord() + +Revision 1.93 2006/03/02 20:30:47 cheshire +Improved GetRRDisplayString to also show priority, weight, and port for SRV records + +Revision 1.92 2005/09/16 21:06:49 cheshire +Use mDNS_TimeNow_NoLock macro, instead of writing "mDNSPlatformRawTime() + m->timenow_adjust" all over the place + +Revision 1.91 2005/07/10 22:10:37 cheshire +The getOptRdata routine implicitly assumes the destination ResourceRecord is large enough to +hold MaximumRDSize bytes, but its parameter was a generic ResourceRecord, which need not be that +large. Changing the parameter to a LargeCacheRecord makes it clearer what the routine requires. + +Revision 1.90 2005/03/21 00:33:51 shersche + Fix build warnings on Win32 platform + +Revision 1.89 2005/03/17 18:59:38 ksekar + Properly parse multiple LLQ Options per packet on Windows + +Revision 1.88 2005/03/16 00:42:32 ksekar + Long-lived queries not working on Windows + +Revision 1.87 2005/02/25 04:21:00 cheshire + mDNS -F returns the same domain multiple times with different casing + +Revision 1.86 2005/02/18 00:43:12 cheshire + mDNSResponder should auto-truncate service names that are too long + +Revision 1.85 2005/02/10 22:35:17 cheshire + Update name + +Revision 1.84 2005/02/03 00:44:38 cheshire + DNSServiceUpdateRecord returns kDNSServiceErr_Invalid when rdlen=0, rdata=NULL + +Revision 1.83 2005/01/27 22:57:55 cheshire +Fix compile errors on gcc4 + +Revision 1.82 2005/01/19 03:27:03 cheshire + CPU Spin in mDNSResponder +GetNextScheduledEvent() needs to check LocalRecordReady() + +Revision 1.81 2004/12/18 03:13:45 cheshire + kDNSServiceInterfaceIndexLocalOnly should return all local records + +Revision 1.80 2004/12/16 21:46:43 cheshire +Add DNSTypeName case for kDNSType_SOA + +Revision 1.79 2004/12/16 21:38:37 cheshire +Add DNSTypeName case for kDNSType_NS + +Revision 1.78 2004/12/16 21:27:37 ksekar +Fixed build failures when compiled with verbose debugging messages + +Revision 1.77 2004/12/16 20:12:59 cheshire + Cache memory management improvements + +Revision 1.76 2004/12/16 08:05:29 shersche +Remove extranenous semicolons that cause compilation errors on Windows + +Revision 1.75 2004/12/15 02:11:22 ksekar + Don't check for Dynamic DNS hostname uniqueness + +Revision 1.74 2004/12/09 22:49:15 ksekar + Wide-Area Goodbyes broken + +Revision 1.73 2004/12/07 22:49:06 cheshire + BIND doesn't allow zero-length TXT records + +Revision 1.72 2004/12/06 21:15:20 ksekar + mDNSResponder crashed in CheckServiceRegistrations + +Revision 1.71 2004/12/04 02:12:45 cheshire + mDNSResponder puts LargeCacheRecord on the stack + +Revision 1.70 2004/12/03 19:52:44 ksekar +Use PutResourceRecordTTLJumbo for putDeletionRecord() + +Revision 1.69 2004/12/03 07:20:50 ksekar + Wide-Area: Registration of large TXT record fails + +Revision 1.68 2004/11/24 00:10:43 cheshire + For unicast operations, verify that service types are legal + +Revision 1.67 2004/10/26 03:52:02 cheshire +Update checkin comments + +Revision 1.66 2004/10/23 01:16:00 cheshire + uDNS operations not always reliable on multi-homed hosts + +Revision 1.65 2004/10/20 02:15:09 cheshire +Add case in GetRRDisplayString() to display NS rdata + +Revision 1.64 2004/10/13 00:24:02 cheshire +Disable "array is too small to include a terminating null character" warning on Windows + +Revision 1.63 2004/10/10 06:57:14 cheshire +Change definition of "localdomain" to make code compile a little smaller + +Revision 1.62 2004/10/06 01:44:19 cheshire + Resolving too quickly sometimes returns stale TXT record + +Revision 1.61 2004/09/30 00:24:56 ksekar + Dynamically update default registration domains on config change + +Revision 1.60 2004/09/27 23:25:30 cheshire +Fix compiler warning: soa.serial is signed, not unsigned + +Revision 1.59 2004/09/27 22:53:45 ksekar +Fixed getLargeResourceRecord for SOA rdata. + +Revision 1.58 2004/09/25 02:41:39 cheshire + Deliver near-pending "remove" events before new "add" events + +Revision 1.57 2004/09/25 02:24:27 cheshire +Removed unused rr->UseCount + +Revision 1.56 2004/09/24 20:57:39 cheshire + Eliminate inappropriate casts that cause misaligned-address errors + +Revision 1.55 2004/09/17 01:08:48 cheshire +Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h + The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces + declared in that file are ONLY appropriate to single-address-space embedded applications. + For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used. + +Revision 1.54 2004/09/17 00:49:51 cheshire +Get rid of now-unused GetResourceRecord -- the correct (safe) routine to use +is GetLargeResourceRecord + +Revision 1.53 2004/09/17 00:31:51 cheshire +For consistency with ipv6, renamed rdata field 'ip' to 'ipv4' + +Revision 1.52 2004/09/17 00:19:10 cheshire +For consistency with AllDNSLinkGroupv6, rename AllDNSLinkGroup to AllDNSLinkGroupv4 + +Revision 1.51 2004/09/16 02:29:39 cheshire +Moved mDNS_Lock/mDNS_Unlock to DNSCommon.c; Added necessary locking around +uDNS_ReceiveMsg, uDNS_StartQuery, uDNS_UpdateRecord, uDNS_RegisterService + +Revision 1.50 2004/09/16 01:58:14 cheshire +Fix compiler warnings + +Revision 1.49 2004/09/14 23:42:35 cheshire + Need to seed random number generator from platform-layer data + +Revision 1.48 2004/09/14 23:27:46 cheshire +Fix compile errors + +Revision 1.47 2004/08/25 02:50:04 cheshire + Browses are no longer piggybacking on other browses +Make mDNSSameAddress() recognise that two mDNSAddrType_None addresses are necessarily equal + +Revision 1.46 2004/08/18 17:35:40 ksekar +: Feature #9586: Need support for Legacy NAT gateways + +Revision 1.45 2004/08/15 18:26:00 cheshire +Don't use strcpy() on "struct domainname" objects; use AssignDomainName() instead +(A "struct domainname" is a collection of packed pascal strings, not a C string.) + +Revision 1.44 2004/08/13 23:46:58 cheshire +"asyncronous" -> "asynchronous" + +Revision 1.43 2004/08/12 02:55:46 ksekar +Fix param order error moving putPrereqNameNotInUse from uDNS.c using +ustrcpy macro to DNSCommon.c using mDNSPlatformStrCopy(). + +Revision 1.42 2004/08/10 23:19:14 ksekar +: DNS Extension daemon for Wide Area Service Discovery +Moved routines/constants to allow extern access for garbage collection daemon + +Revision 1.41 2004/08/10 01:10:01 cheshire + Current method of doing subtypes causes name collisions +Minor revision from Roger Pantos + +Revision 1.40 2004/08/04 22:10:46 cheshire + Current method of doing subtypes causes name collisions +Change to use "._sub." instead of ".s." to mark subtypes. + +Revision 1.39 2004/07/13 21:24:24 rpantos +Fix for . + +Revision 1.38 2004/06/18 21:08:58 cheshire + Applications are registering invalid records +Attempts to create domain names like "www..apple.com." now logged to aid debugging + +Revision 1.37 2004/06/18 20:25:42 cheshire + Add a syslog message if someone tries to use "local.arpa". + +Revision 1.36 2004/06/18 19:09:59 cheshire + Current method of doing subtypes causes name collisions + +Revision 1.35 2004/06/05 00:14:44 cheshire +Fix signed/unsigned and other compiler warnings + +Revision 1.34 2004/06/04 00:25:25 cheshire +Fix misaligned write exception that occurs on some platforms + +Revision 1.33 2004/06/04 00:16:18 cheshire +Remove non-portable use of 'inline' + +Revision 1.32 2004/06/03 03:09:58 ksekar +: Garbage Collection for Dynamic Updates + +Revision 1.31 2004/05/28 23:42:36 ksekar +: Feature: DNS server->client notification on record changes (#7805) + +Revision 1.30 2004/05/26 09:08:04 bradley +Added cast to correct structure pointer when allocating domain name list element to fix C++ builds. + +Revision 1.29 2004/05/18 23:51:25 cheshire +Tidy up all checkin comments to use consistent "" format for bug numbers + +Revision 1.28 2004/05/13 04:54:20 ksekar +Unified list copy/free code. Added symetric list for + +Revision 1.27 2004/04/22 20:29:07 cheshire +Log error message if no count field passed to PutResourceRecordTTL() + +Revision 1.26 2004/04/22 04:07:01 cheshire +Fix from Bob Bradley: Don't try to do inline functions on compilers that don't support it + +Revision 1.25 2004/04/22 03:05:28 cheshire +kDNSClass_ANY should be kDNSQClass_ANY + +Revision 1.24 2004/04/22 02:51:20 cheshire +Use common code for HINFO/TXT and TSIG cases in putRData + +Revision 1.23 2004/04/15 00:51:28 bradley +Minor tweaks for Windows and C++ builds. Added casts for signed/unsigned integers and 64-bit pointers. +Prefix some functions with mDNS to avoid conflicts. Disable benign warnings on Microsoft compilers. + +Revision 1.22 2004/04/14 23:09:28 ksekar +Support for TSIG signed dynamic updates. + +Revision 1.21 2004/04/09 16:47:28 cheshire +: mDNSResponder escape handling inconsistent with BIND + +Revision 1.20 2004/04/09 16:37:15 cheshire +Suggestion from Bob Bradley: +Move NumCacheRecordsForInterfaceID() to DNSCommon.c so it's available to all platform layers + +Revision 1.19 2004/04/02 19:34:38 cheshire +Fix broken comment + +Revision 1.18 2004/03/30 06:45:00 cheshire +Compiler warning fixes from Don Woodward at Roku Labs + +Revision 1.17 2004/03/19 22:25:20 cheshire +: Need to limit service types to fourteen characters +Won't actually do this for now, but keep the code around just in case + +Revision 1.16 2004/03/08 02:45:35 cheshire +Minor change to make a couple of the log messages a bit shorter + +Revision 1.15 2004/03/08 02:44:09 cheshire +: Need to limit service types to fourteen characters + +Revision 1.14 2004/02/21 02:06:24 cheshire +Can't use anonymous unions -- they're non-standard and don't work on all compilers + +Revision 1.13 2004/02/06 23:04:18 ksekar +Basic Dynamic Update support via mDNS_Register (dissabled via +UNICAST_REGISTRATION #define) + +Revision 1.12 2004/02/03 22:37:10 cheshire +Delete unused (commented-out) code + +Revision 1.11 2004/02/03 22:35:34 cheshire +: Should not allow empty string for resolve domain + +Revision 1.10 2004/02/03 19:47:36 ksekar +Added an asynchronous state machine mechanism to uDNS.c, including +calls to find the parent zone for a domain name. Changes include code +in repository previously dissabled via "#if 0 incomplete". Codepath +is currently unused, and will be called to create update records, etc. + +Revision 1.9 2004/01/27 20:15:22 cheshire +: Time to prune obsolete code for listening on port 53 + +Revision 1.8 2004/01/24 23:24:36 cheshire +Expanded out the list of local domains to reduce risk of mistakes in future + +Revision 1.7 2004/01/24 08:32:30 bradley +Mask values with 0xFF before casting to avoid runtime truncation errors on Windows debug builds. +Separated octal-escaped sequences preceding decimal digits to avoid errors with some compilers wanting +to signal potentially hidden errors about the subsequent digit not being part of the octal sequence. + +Revision 1.6 2004/01/24 04:59:15 cheshire +Fixes so that Posix/Linux, OS9, Windows, and VxWorks targets build again + +Revision 1.5 2004/01/23 23:23:14 ksekar +Added TCP support for truncated unicast messages. + +Revision 1.4 2004/01/22 02:15:33 cheshire +: Link-local reverse-mapping domains need to be resolved using link-local multicast + +Revision 1.3 2004/01/21 21:16:29 cheshire +Minor tidy-up: Deleted a bunch of blank lines, trailing spaces, tabs, etc. + +Revision 1.2 2003/12/13 05:47:48 bradley +Made local ptr const to fix error when assigning from const structure. Disable benign conditional +expression is constant warning when building with Microsoft compilers. + +Revision 1.1 2003/12/13 03:05:27 ksekar +: DynDNS: Unicast query of service records + + */ + +// Set mDNS_InstantiateInlines to tell mDNSEmbeddedAPI.h to instantiate inline functions, if necessary +#define mDNS_InstantiateInlines 1 +#include "DNSCommon.h" + +// 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 "array is too small to include a terminating null character" warning + // -- domain labels have an initial length byte, not a terminating null character + #pragma warning(disable:4295) +#endif + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNameList copy/deallocation routines +#endif + +mDNSexport DNameListElem *mDNS_CopyDNameList(const DNameListElem *orig) + { + DNameListElem *copy = mDNSNULL, *newelem; + const DNameListElem *ptr; + + for (ptr = orig; ptr; ptr = ptr->next) + { + newelem = (DNameListElem*)mDNSPlatformMemAllocate(sizeof(DNameListElem)); + if (!newelem) { LogMsg("ERROR: malloc"); return mDNSNULL; } + AssignDomainName(&newelem->name, &ptr->name); + newelem->next = copy; + copy = newelem; + } + return copy; + } + +mDNSexport void mDNS_FreeDNameList(DNameListElem *list) + { + DNameListElem *fptr; + + while (list) + { + fptr = list; + list = list->next; + mDNSPlatformMemFree(fptr); + } + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - General Utility Functions +#endif + +// return true for RFC1918 private addresses +mDNSexport mDNSBool IsPrivateV4Addr(mDNSAddr *addr) + { + mDNSu8 *b; + + if (addr->type != mDNSAddrType_IPv4) return mDNSfalse; + b = addr->ip.v4.b; + + return ((b[0] == 10) || // 10/8 prefix + (b[0] == 172 && b[1] > 15 && b[1] < 32) || // 172.16/12 + (b[0] == 192 && b[1] == 168)); // 192.168/16 + } + +mDNSexport const NetworkInterfaceInfo *GetFirstActiveInterface(const NetworkInterfaceInfo *intf) + { + while (intf && !intf->InterfaceActive) intf = intf->next; + return(intf); + } + +mDNSexport mDNSInterfaceID GetNextActiveInterfaceID(const NetworkInterfaceInfo *intf) + { + const NetworkInterfaceInfo *next = GetFirstActiveInterface(intf->next); + if (next) return(next->InterfaceID); else return(mDNSNULL); + } + +mDNSexport mDNSu32 NumCacheRecordsForInterfaceID(const mDNS *const m, mDNSInterfaceID id) + { + mDNSu32 slot, used = 0; + CacheGroup *cg; + CacheRecord *rr; + FORALL_CACHERECORDS(slot, cg, rr) + if (rr->resrec.InterfaceID == id) used++; + return(used); + } + +mDNSexport char *DNSTypeName(mDNSu16 rrtype) + { + switch (rrtype) + { + case kDNSType_A: return("Addr"); + case kDNSType_NS: return("NS"); + case kDNSType_CNAME:return("CNAME"); + case kDNSType_SOA: return("SOA"); + 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); + } + } + } + +// Note slight bug: this code uses the rdlength from the ResourceRecord object, to display +// the rdata from the RDataBody object. Sometimes this could be the wrong length -- but as +// long as this routine is only used for debugging messages, it probably isn't a big problem. +mDNSexport char *GetRRDisplayString_rdb(const ResourceRecord *rr, RDataBody *rd, char *buffer) + { + #define Max (MaxMsg-1) + char *ptr = buffer; + mDNSu32 length = mDNS_snprintf(buffer, Max, "%4d %##s %s ", rr->rdlength, rr->name->c, DNSTypeName(rr->rrtype)); + switch (rr->rrtype) + { + case kDNSType_A: mDNS_snprintf(buffer+length, Max-length, "%.4a", &rd->ipv4); break; + + case kDNSType_NS: // Same as PTR + case kDNSType_CNAME:// Same as PTR + case kDNSType_PTR: mDNS_snprintf(buffer+length, Max-length, "%##s", rd->name.c); break; + + case kDNSType_HINFO:// Display this the same as TXT (just show first string) + case kDNSType_TXT: mDNS_snprintf(buffer+length, Max-length, "%#s", rd->txt.c); break; + + case kDNSType_AAAA: mDNS_snprintf(buffer+length, Max-length, "%.16a", &rd->ipv6); break; + case kDNSType_SRV: mDNS_snprintf(buffer+length, Max-length, "%u %u %u %##s", + rd->srv.priority, rd->srv.weight, mDNSVal16(rd->srv.port), rd->srv.target.c); break; + default: mDNS_snprintf(buffer+length, Max-length, "RDLen %d: %s", rr->rdlength, rd->data); break; + } + for (ptr = buffer; *ptr; ptr++) if (*ptr < ' ') *ptr='.'; + return(buffer); + } + +mDNSexport mDNSu32 mDNSRandom(mDNSu32 max) + { + static mDNSu32 seed = 0; + mDNSu32 mask = 1; + + if (!seed) + { + int i; + seed = mDNSPlatformRandomSeed(); // Pick an initial seed + for (i=0; i<100; i++) seed = seed * 21 + 1; // And mix it up a bit + } + while (mask < max) mask = (mask << 1) | 1; + do seed = seed * 21 + 1; while ((seed & mask) > max); + return (seed & mask); + } + +mDNSexport mDNSu32 mDNSRandomFromFixedSeed(mDNSu32 seed, mDNSu32 max) + { + mDNSu32 mask = 1; + while (mask < max) mask = (mask << 1) | 1; + do seed = seed * 21 + 1; while ((seed & mask) > max); + return (seed & mask); + } + +mDNSexport mDNSBool mDNSSameAddress(const mDNSAddr *ip1, const mDNSAddr *ip2) + { + if (ip1->type == ip2->type) + { + switch (ip1->type) + { + case mDNSAddrType_None : return(mDNStrue); // Empty addresses have no data and are therefore always equal + 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); + } + +mDNSexport mDNSBool mDNSAddrIsDNSMulticast(const mDNSAddr *ip) + { + switch(ip->type) + { + case mDNSAddrType_IPv4: return(mDNSBool)(ip->ip.v4.NotAnInteger == AllDNSLinkGroupv4.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); + } + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Domain Name Utility Functions +#endif + +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); + } + +mDNSexport mDNSBool IsLocalDomain(const domainname *d) + { + // Domains that are defined to be resolved via link-local multicast are: + // local., 254.169.in-addr.arpa., and {8,9,A,B}.E.F.ip6.arpa. + static const domainname *nL = (domainname*)"\x5" "local"; + static const domainname *nR = (domainname*)"\x3" "254" "\x3" "169" "\x7" "in-addr" "\x4" "arpa"; + static const domainname *n8 = (domainname*)"\x1" "8" "\x1" "e" "\x1" "f" "\x3" "ip6" "\x4" "arpa"; + static const domainname *n9 = (domainname*)"\x1" "9" "\x1" "e" "\x1" "f" "\x3" "ip6" "\x4" "arpa"; + static const domainname *nA = (domainname*)"\x1" "a" "\x1" "e" "\x1" "f" "\x3" "ip6" "\x4" "arpa"; + static const domainname *nB = (domainname*)"\x1" "b" "\x1" "e" "\x1" "f" "\x3" "ip6" "\x4" "arpa"; + + const domainname *d1, *d2, *d3, *d4, *d5, *d6; // Top-level domain, second-level domain, etc. + d1 = d2 = d3 = d4 = d5 = d6 = mDNSNULL; + while (d->c[0]) + { + d6 = d5; d5 = d4; d4 = d3; d3 = d2; d2 = d1; d1 = d; + d = (domainname*)(d->c + 1 + d->c[0]); + } + + if (d1 && SameDomainName(d1, nL)) return(mDNStrue); + if (d4 && SameDomainName(d4, nR)) return(mDNStrue); + if (d6 && SameDomainName(d6, n8)) return(mDNStrue); + if (d6 && SameDomainName(d6, n9)) return(mDNStrue); + if (d6 && SameDomainName(d6, nA)) return(mDNStrue); + if (d6 && SameDomainName(d6, nB)) return(mDNStrue); + return(mDNSfalse); + } + +// 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). +mDNSexport 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 *cstring) + { + const char *cstr = cstring; + 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 + if (*cstr == '.') { LogMsg("AppendDNSNameString: Illegal empty label in name \"%s\"", cstring); return(mDNSNULL); } + 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 + { + c = (mDNSu8)*cstr++; // Assume we'll just take the next character + if (mdnsIsDigit(cstr[-1]) && mdnsIsDigit(cstr[0]) && mdnsIsDigit(cstr[1])) + { // If three decimal digits, + int v0 = cstr[-1] - '0'; // then interpret as three-digit decimal + int v1 = cstr[ 0] - '0'; + int v2 = cstr[ 1] - '0'; + int val = v0 * 100 + v1 * 10 + v2; + if (val <= 255) { c = (mDNSu8)val; cstr += 2; } // If valid three-digit decimal 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 MAX_ESCAPED_DOMAIN_NAME (1005 bytes) +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 + +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 && type) + { + const mDNSu8 *s0 = type->c; + if (s0[0] && s0[0] < 0x40) // If legal first label (at least one character, and no more than 63) + { + const mDNSu8 * s1 = s0 + 1 + s0[0]; + if (s1[0] && s1[0] < 0x40) // and legal second label (at least one character, and no more than 63) + { + const mDNSu8 *s2 = s1 + 1 + s1[0]; + if (s2[0] && s2[0] < 0x40 && s2[1+s2[0]] == 0) // and we have three and only three labels + { + static const mDNSu8 SubTypeLabel[5] = "\x04_sub"; + src = s0; // Copy the first label + len = *src; + for (i=0; i <= len; i++) *dst++ = *src++; + for (i=0; i < (int)sizeof(SubTypeLabel); i++) *dst++ = SubTypeLabel[i]; + type = (domainname *)s1; + + // Special support for queries done by some third-party network monitoring software + // For these queries, we retract the "._sub" we just added between the subtype and the main type + if (SameDomainName((domainname*)s0, (domainname*)"\x09_services\x07_dns-sd\x04_udp") || + SameDomainName((domainname*)s0, (domainname*)"\x09_services\x05_mdns\x04_udp")) + dst -= sizeof(SubTypeLabel); + } + } + } + } + + 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 || (len > 15 && !SameDomainName(domain, &localdomain))) + { + errormsg="Application protocol name must be underscore plus 1-14 characters. See "; + goto fail; + } + if (src[1] != '_') { errormsg="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="Application protocol name must contain only letters, digits, and hyphens"; goto fail; } + for (i=0; i<=len; i++) *dst++ = *src++; + + len = *src; + 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="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; + if (!domain->c[0]) { errormsg="Service domain must be non-empty"; goto fail; } + if (SameDomainName(domain, (domainname*)"\x05" "local" "\x04" "arpa")) + { errormsg="Illegal domain \"local.arpa.\" Use \"local.\" (or empty string)"; goto fail; } + 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); + } + +// A service name has the form: instance.application-protocol.transport-protocol.domain +// DeconstructServiceName is currently fairly forgiving: It doesn't try to enforce character +// set or length limits for the protocol names, and the final domain is allowed to be empty. +// However, if the given FQDN doesn't contain at least three labels, +// DeconstructServiceName will reject it and return mDNSfalse. +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 + len = *src; + if (!len) { debugf("DeconstructServiceName: FQDN empty!"); return(mDNSfalse); } + if (len >= 0x40) { debugf("DeconstructServiceName: Instance name too long"); return(mDNSfalse); } + for (i=0; i<=len; i++) *dst++ = *src++; + + dst = type->c; // Extract the service type + len = *src; + if (!len) { debugf("DeconstructServiceName: FQDN contains only one label!"); return(mDNSfalse); } + if (len >= 0x40) { debugf("DeconstructServiceName: Application protocol name too long"); return(mDNSfalse); } + for (i=0; i<=len; i++) *dst++ = *src++; + + len = *src; + if (!len) { debugf("DeconstructServiceName: FQDN contains only two labels!"); return(mDNSfalse); } + if (len >= 0x40) { debugf("DeconstructServiceName: Transport protocol name too long"); return(mDNSfalse); } + for (i=0; i<=len; i++) *dst++ = *src++; + *dst++ = 0; // Put terminator on the end of service type + + dst = domain->c; // Extract the service domain + while (*src) + { + len = *src; + if (len >= 0x40) + { debugf("DeconstructServiceName: Label in service domain too long"); return(mDNSfalse); } + if (src + 1 + len + 1 >= max) + { debugf("DeconstructServiceName: Total 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); + } + +// Notes on UTF-8: +// 0xxxxxxx represents a 7-bit ASCII value from 0x00 to 0x7F +// 10xxxxxx is a continuation byte of a multi-byte character +// 110xxxxx is the first byte of a 2-byte character (11 effective bits; values 0x 80 - 0x 800-1) +// 1110xxxx is the first byte of a 3-byte character (16 effective bits; values 0x 800 - 0x 10000-1) +// 11110xxx is the first byte of a 4-byte character (21 effective bits; values 0x 10000 - 0x 200000-1) +// 111110xx is the first byte of a 5-byte character (26 effective bits; values 0x 200000 - 0x 4000000-1) +// 1111110x is the first byte of a 6-byte character (31 effective bits; values 0x4000000 - 0x80000000-1) +// +// UTF-16 surrogate pairs are used in UTF-16 to encode values larger than 0xFFFF. +// Although UTF-16 surrogate pairs are not supposed to appear in legal UTF-8, we want to be defensive +// about that too. (See , "What are surrogates?") +// The first of pair is a UTF-16 value in the range 0xD800-0xDBFF (11101101 1010xxxx 10xxxxxx in UTF-8), +// and the second is a UTF-16 value in the range 0xDC00-0xDFFF (11101101 1011xxxx 10xxxxxx in UTF-8). + +mDNSexport mDNSu32 TruncateUTF8ToLength(mDNSu8 *string, mDNSu32 length, mDNSu32 max) + { + if (length > max) + { + mDNSu8 c1 = string[max]; // First byte after cut point + mDNSu8 c2 = (max+1 < length) ? string[max+1] : 0xB0; // Second byte after cut point + length = max; // Trim length down + while (length > 0) + { + // Check if the byte right after the chop point is a UTF-8 continuation byte, + // or if the character right after the chop point is the second of a UTF-16 surrogate pair. + // If so, then we continue to chop more bytes until we get to a legal chop point. + mDNSBool continuation = ((c1 & 0xC0) == 0x80); + mDNSBool secondsurrogate = (c1 == 0xED && (c2 & 0xF0) == 0xB0); + if (!continuation && !secondsurrogate) break; + c2 = c1; + c1 = string[--length]; + } + // Having truncated characters off the end of our string, also cut off any residual white space + while (length > 0 && string[length-1] <= ' ') length--; + } + return(length); + } + +// 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. +mDNSexport 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. +mDNSexport 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). +mDNSexport void AppendLabelSuffix(domainlabel *name, mDNSu32 val, mDNSBool RichText) + { + mDNSu32 divisor = 1, chars = 2; // Shortest possible RFC1034 name suffix is 2 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++; } + + name->c[0] = (mDNSu8) TruncateUTF8ToLength(name->c+1, name->c[0], MAX_DOMAIN_LABEL - chars); + + 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 + +mDNSexport 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); + } + +// r1 has to be a full ResourceRecord including rrtype and rdlength +// r2 is just a bare RDataBody, which MUST be the same rrtype and rdlength as r1 +mDNSexport mDNSBool SameRDataBody(const ResourceRecord *const r1, const RDataBody *const r2) + { + switch(r1->rrtype) + { + case kDNSType_CNAME:// Same as PTR + case kDNSType_PTR: return(SameDomainName(&r1->rdata->u.name, &r2->name)); + + case kDNSType_SRV: return(mDNSBool)( r1->rdata->u.srv.priority == r2->srv.priority && + r1->rdata->u.srv.weight == r2->srv.weight && + r1->rdata->u.srv.port.NotAnInteger == r2->srv.port.NotAnInteger && + SameDomainName(&r1->rdata->u.srv.target, &r2->srv.target) ); + + default: return(mDNSPlatformMemSame(r1->rdata->u.data, r2->data, r1->rdlength)); + } + } + +mDNSexport 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); + return(SameRDataBody(r1, &r2->rdata->u)); + } + +mDNSexport mDNSBool SameResourceRecord(ResourceRecord *r1, ResourceRecord *r2) + { + return (r1->namehash == r2->namehash && + r1->rrtype == r2->rrtype && + SameDomainName(r1->name, r2->name) && + SameRData(r1, r2)); + } + +mDNSexport mDNSBool ResourceRecordAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q) + { + if (rr->InterfaceID && + q ->InterfaceID && q->InterfaceID != mDNSInterface_LocalOnly && + 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)); + } + +mDNSexport mDNSu16 GetRDLength(const ResourceRecord *const rr, mDNSBool estimate) + { + const RDataBody *rd = &rr->rdata->u; + const domainname *const name = estimate ? rr->name : mDNSNULL; + switch (rr->rrtype) + { + case kDNSType_A: return(sizeof(rd->ipv4)); + case kDNSType_CNAME:// Same as PTR + case kDNSType_NS: // 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)); + case kDNSType_SRV: return(mDNSu16)(6 + CompressedDomainNameLength(&rd->srv.target, name)); + case kDNSType_SOA: return (mDNSu16)(CompressedDomainNameLength(&rd->soa.mname, name) + + CompressedDomainNameLength(&rd->soa.rname, name) + + 5 * sizeof(mDNSOpaque32)); + case kDNSType_OPT: return(rr->rdlength); + default: debugf("Warning! Don't know how to get length of resource type %d", rr->rrtype); + return(rr->rdlength); + } + } + +mDNSexport 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: if (!rdlength) return(mDNSfalse); + 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: if (!rdlength) return(mDNSfalse); // TXT record has to be at least one byte (RFC 1035) + { + 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: if (!rdlength) return(mDNSfalse); + len = DomainNameLength(&rd->u.mx.exchange); + return(len <= MAX_DOMAIN_NAME && rdlength == 2+len); + + case kDNSType_SRV: if (!rdlength) return(mDNSfalse); + len = DomainNameLength(&rd->u.srv.target); + return(len <= MAX_DOMAIN_NAME && rdlength == 6+len); + + default: return(mDNStrue); // Allow all other types without checking + } + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - +#pragma mark - DNS Message Creation Functions +#endif + +mDNSexport 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; + } + +mDNSexport 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 +mDNSexport mDNSu8 *putDomainNameAsLabels(const DNSMessage *const msg, + mDNSu8 *ptr, const mDNSu8 *const limit, const domainname *const name) + { + const mDNSu8 *const base = (const mDNSu8 *)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 & 0xFF); + 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; i> 8 ) & 0xFF); + ptr[1] = (mDNSu8)((val ) & 0xFF); + return ptr + sizeof(mDNSOpaque16); + } + +mDNSlocal mDNSu8 *putVal32(mDNSu8 *ptr, mDNSu32 val) + { + ptr[0] = (mDNSu8)((val >> 24) & 0xFF); + ptr[1] = (mDNSu8)((val >> 16) & 0xFF); + ptr[2] = (mDNSu8)((val >> 8) & 0xFF); + ptr[3] = (mDNSu8)((val ) & 0xFF); + return ptr + sizeof(mDNSu32); + } + +mDNSlocal mDNSu8 *putOptRData(mDNSu8 *ptr, const mDNSu8 *limit, ResourceRecord *rr) + { + int nput = 0; + rdataOpt *opt; + + while (nput < rr->rdlength) + { + // check if space for opt/optlen + if (ptr + (2 * sizeof(mDNSu16)) > limit) goto space_err; + opt = (rdataOpt *)(rr->rdata->u.data + nput); + ptr = putVal16(ptr, opt->opt); + ptr = putVal16(ptr, opt->optlen); + nput += 2 * sizeof(mDNSu16); + if (opt->opt == kDNSOpt_LLQ) + { + if (ptr + LLQ_OPTLEN > limit) goto space_err; + ptr = putVal16(ptr, opt->OptData.llq.vers); + ptr = putVal16(ptr, opt->OptData.llq.llqOp); + ptr = putVal16(ptr, opt->OptData.llq.err); + mDNSPlatformMemCopy(opt->OptData.llq.id, ptr, 8); // 8-byte id + ptr += 8; + ptr = putVal32(ptr, opt->OptData.llq.lease); + nput += LLQ_OPTLEN; + } + else if (opt->opt == kDNSOpt_Lease) + { + if (ptr + sizeof(mDNSs32) > limit) goto space_err; + ptr = putVal32(ptr, opt->OptData.lease); + nput += sizeof(mDNSs32); + } + else { LogMsg("putOptRData - unknown option %d", opt->opt); return mDNSNULL; } + } + + return ptr; + + space_err: + LogMsg("ERROR: putOptRData - out of space"); + return mDNSNULL; + } + +mDNSlocal mDNSu16 getVal16(const mDNSu8 **ptr) + { + mDNSu16 val = (mDNSu16)(((mDNSu16)(*ptr)[0]) << 8 | (*ptr)[1]); + *ptr += sizeof(mDNSOpaque16); + return val; + } + +mDNSlocal const mDNSu8 *getOptRdata(const mDNSu8 *ptr, const mDNSu8 *const limit, LargeCacheRecord *const cr, mDNSu16 pktRDLen) + { + int nread = 0; + ResourceRecord *const rr = &cr->r.resrec; + rdataOpt *opt = (rdataOpt *)rr->rdata->u.data; + + while (nread < pktRDLen && (mDNSu8 *)opt < rr->rdata->u.data + MaximumRDSize - sizeof(rdataOpt)) + { + // space for opt + optlen + if (nread + (2 * sizeof(mDNSu16)) > rr->rdata->MaxRDLength) goto space_err; + opt->opt = getVal16(&ptr); + opt->optlen = getVal16(&ptr); + nread += 2 * sizeof(mDNSu16); + if (opt->opt == kDNSOpt_LLQ) + { + if ((unsigned)(limit - ptr) < LLQ_OPTLEN) goto space_err; + opt->OptData.llq.vers = getVal16(&ptr); + opt->OptData.llq.llqOp = getVal16(&ptr); + opt->OptData.llq.err = getVal16(&ptr); + mDNSPlatformMemCopy(ptr, opt->OptData.llq.id, 8); + ptr += 8; + opt->OptData.llq.lease = (mDNSu32) ((mDNSu32)ptr[0] << 24 | (mDNSu32)ptr[1] << 16 | (mDNSu32)ptr[2] << 8 | ptr[3]); + if (opt->OptData.llq.lease > 0x70000000UL / mDNSPlatformOneSecond) + opt->OptData.llq.lease = 0x70000000UL / mDNSPlatformOneSecond; + ptr += sizeof(mDNSOpaque32); + nread += LLQ_OPTLEN; + } + else if (opt->opt == kDNSOpt_Lease) + { + if ((unsigned)(limit - ptr) < sizeof(mDNSs32)) goto space_err; + + opt->OptData.lease = (mDNSu32) ((mDNSu32)ptr[0] << 24 | (mDNSu32)ptr[1] << 16 | (mDNSu32)ptr[2] << 8 | ptr[3]); + if (opt->OptData.lease > 0x70000000UL / mDNSPlatformOneSecond) + opt->OptData.lease = 0x70000000UL / mDNSPlatformOneSecond; + ptr += sizeof(mDNSs32); + nread += sizeof(mDNSs32); + } + else { LogMsg("ERROR: getOptRdata - unknown opt %d", opt->opt); return mDNSNULL; } + opt++; // increment pointer into rdatabody + } + + rr->rdlength = pktRDLen; + return ptr; + + space_err: + LogMsg("ERROR: getLLQRdata - out of space"); + return mDNSNULL; + } + +mDNSexport mDNSu8 *putRData(const DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit, ResourceRecord *rr) + { + switch (rr->rrtype) + { + 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.ipv4.b[0]; + *ptr++ = rr->rdata->u.ipv4.b[1]; + *ptr++ = rr->rdata->u.ipv4.b[2]; + *ptr++ = rr->rdata->u.ipv4.b[3]; + return(ptr); + + case kDNSType_CNAME:// Same as PTR + case kDNSType_PTR: return(putDomainNameAsLabels(msg, ptr, limit, &rr->rdata->u.name)); + + 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 & 0xFF); + *ptr++ = (mDNSu8)(rr->rdata->u.srv.weight >> 8); + *ptr++ = (mDNSu8)(rr->rdata->u.srv.weight & 0xFF); + *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)); + case kDNSType_OPT: return putOptRData(ptr, limit, rr); + + default: debugf("putRData: Warning! Writing unknown resource type %d as raw data", rr->rrtype); + // Fall through to common code below + case kDNSType_HINFO: + case kDNSType_TXT: + case kDNSType_TSIG: if (ptr + rr->rdlength > limit) return(mDNSNULL); + mDNSPlatformMemCopy(rr->rdata->u.data, ptr, rr->rdlength); + return(ptr + rr->rdlength); + } + } + +mDNSexport mDNSu8 *PutResourceRecordTTLWithLimit(DNSMessage *const msg, mDNSu8 *ptr, mDNSu16 *count, ResourceRecord *rr, mDNSu32 ttl, const mDNSu8 *limit) + { + mDNSu8 *endofrdata; + mDNSu16 actualLength; + + 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 & 0xFF); + ptr[2] = (mDNSu8)(rr->rrclass >> 8); + ptr[3] = (mDNSu8)(rr->rrclass & 0xFF); + ptr[4] = (mDNSu8)((ttl >> 24) & 0xFF); + ptr[5] = (mDNSu8)((ttl >> 16) & 0xFF); + ptr[6] = (mDNSu8)((ttl >> 8) & 0xFF); + ptr[7] = (mDNSu8)( ttl & 0xFF); + 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 & 0xFF); + + if (count) (*count)++; + else LogMsg("PutResourceRecordTTL: ERROR: No target count to update for %##s (%s)", rr->name->c, DNSTypeName(rr->rrtype)); + return(endofrdata); + } + +mDNSexport 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)); + } + +mDNSexport mDNSu8 *putEmptyResourceRecord(DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit, + mDNSu16 *count, const AuthRecord *rr) + { + ptr = putDomainNameAsLabels(msg, ptr, limit, rr->resrec.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 & 0xFF); + ptr[2] = (mDNSu8)(rr->resrec.rrclass >> 8); // Put class + ptr[3] = (mDNSu8)(rr->resrec.rrclass & 0xFF); + 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); + } + +mDNSexport 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 & 0xFF); + ptr[2] = (mDNSu8)(rrclass >> 8); + ptr[3] = (mDNSu8)(rrclass & 0xFF); + msg->h.numQuestions++; + return(ptr+4); + } + +// for dynamic updates +mDNSexport mDNSu8 *putZone(DNSMessage *const msg, mDNSu8 *ptr, mDNSu8 *limit, const domainname *zone, mDNSOpaque16 zoneClass) + { + ptr = putDomainNameAsLabels(msg, ptr, limit, zone); + if (!ptr || ptr + 4 > limit) return mDNSNULL; // If we're out-of-space, return NULL + *ptr++ = (mDNSu8)(kDNSType_SOA >> 8); + *ptr++ = (mDNSu8)(kDNSType_SOA & 0xFF); + *ptr++ = zoneClass.b[0]; + *ptr++ = zoneClass.b[1]; + msg->h.mDNS_numZones++; + return ptr; + } + +// for dynamic updates +mDNSexport mDNSu8 *putPrereqNameNotInUse(domainname *name, DNSMessage *msg, mDNSu8 *ptr, mDNSu8 *end) + { + AuthRecord prereq; + + mDNSPlatformMemZero(&prereq, sizeof(AuthRecord)); + mDNS_SetupResourceRecord(&prereq, mDNSNULL, mDNSInterface_Any, kDNSQType_ANY, kStandardTTL, 0, mDNSNULL, mDNSNULL); + AssignDomainName(prereq.resrec.name, name); + prereq.resrec.rrtype = kDNSQType_ANY; + prereq.resrec.rrclass = kDNSClass_NONE; + ptr = putEmptyResourceRecord(msg, ptr, end, &msg->h.mDNS_numPrereqs, &prereq); + return ptr; + } + +// for dynamic updates +mDNSexport mDNSu8 *putDeletionRecord(DNSMessage *msg, mDNSu8 *ptr, ResourceRecord *rr) + { + mDNSu16 origclass; + // deletion: specify record w/ TTL 0, class NONE + + origclass = rr->rrclass; + rr->rrclass = kDNSClass_NONE; + ptr = PutResourceRecordTTLJumbo(msg, ptr, &msg->h.mDNS_numUpdates, rr, 0); + rr->rrclass = origclass; + return ptr; + } + +mDNSexport mDNSu8 *putDeleteRRSet(DNSMessage *msg, mDNSu8 *ptr, const domainname *name, mDNSu16 rrtype) + { + const mDNSu8 *limit = msg->data + AbsoluteMaxDNSMessageData; + mDNSu16 class = kDNSQClass_ANY; + + ptr = putDomainNameAsLabels(msg, ptr, limit, name); + if (!ptr || ptr + 10 >= limit) return mDNSNULL; // If we're out-of-space, return mDNSNULL + ptr[0] = (mDNSu8)(rrtype >> 8); + ptr[1] = (mDNSu8)(rrtype & 0xFF); + ptr[2] = (mDNSu8)(class >> 8); + ptr[3] = (mDNSu8)(class & 0xFF); + ptr[4] = ptr[5] = ptr[6] = ptr[7] = 0; // zero ttl + ptr[8] = ptr[9] = 0; // zero rdlength/rdata + + msg->h.mDNS_numUpdates++; + return ptr + 10; + } + +// for dynamic updates +mDNSexport mDNSu8 *putDeleteAllRRSets(DNSMessage *msg, mDNSu8 *ptr, const domainname *name) + { + const mDNSu8 *limit = msg->data + AbsoluteMaxDNSMessageData; + mDNSu16 class = kDNSQClass_ANY; + mDNSu16 rrtype = kDNSQType_ANY; + + ptr = putDomainNameAsLabels(msg, ptr, limit, name); + if (!ptr || ptr + 10 >= limit) return mDNSNULL; // If we're out-of-space, return mDNSNULL + ptr[0] = (mDNSu8)(rrtype >> 8); + ptr[1] = (mDNSu8)(rrtype & 0xFF); + ptr[2] = (mDNSu8)(class >> 8); + ptr[3] = (mDNSu8)(class & 0xFF); + ptr[4] = ptr[5] = ptr[6] = ptr[7] = 0; // zero ttl + ptr[8] = ptr[9] = 0; // zero rdlength/rdata + + msg->h.mDNS_numUpdates++; + return ptr + 10; + } + +// for dynamic updates +mDNSexport mDNSu8 *putUpdateLease(DNSMessage *msg, mDNSu8 *end, mDNSu32 lease) + { + AuthRecord rr; + ResourceRecord *opt = &rr.resrec; + rdataOpt *optRD; + + mDNSPlatformMemZero(&rr, sizeof(AuthRecord)); + mDNS_SetupResourceRecord(&rr, mDNSNULL, mDNSInterface_Any, kDNSType_OPT, kStandardTTL, 0, mDNSNULL, mDNSNULL); + + opt->RecordType = kDNSRecordTypeKnownUnique; // to avoid warnings in other layers + opt->rrtype = kDNSType_OPT; + opt->rdlength = LEASE_OPT_RDLEN; + opt->rdestimate = LEASE_OPT_RDLEN; + + optRD = &rr.resrec.rdata->u.opt; + optRD->opt = kDNSOpt_Lease; + optRD->optlen = sizeof(mDNSs32); + optRD->OptData.lease = lease; + end = PutResourceRecordTTLJumbo(msg, end, &msg->h.numAdditionals, opt, 0); + if (!end) { LogMsg("ERROR: putUpdateLease - PutResourceRecordTTL"); return mDNSNULL; } + + return end; + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNS Message Parsing Functions +#endif + +mDNSexport 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); + } + +mDNSexport 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 = target ? DomainNameHashValue(target) : RDataHashValue(rr->rdlength, &rr->rdata->u); + } + +mDNSexport 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. +mDNSexport 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); + } + +mDNSexport 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); + } + +mDNSexport const mDNSu8 *GetLargeResourceRecord(mDNS *const m, const DNSMessage * const msg, const mDNSu8 *ptr, + const mDNSu8 *end, const mDNSInterfaceID InterfaceID, mDNSu8 RecordType, LargeCacheRecord *largecr) + { + CacheRecord *rr = &largecr->r; + mDNSu16 pktrdlength; + + if (largecr == &m->rec && largecr->r.resrec.RecordType) + LogMsg("GetLargeResourceRecord: m->rec appears to be already in use for %s", CRDisplayString(m, &largecr->r)); + + rr->next = mDNSNULL; + rr->resrec.name = &largecr->namestorage; + + rr->NextInKAList = mDNSNULL; + rr->TimeRcvd = m ? m->timenow : 0; + rr->DelayDelivery = 0; + rr->NextRequiredQuery = m ? m->timenow : 0; // Will be updated to the real value when we call SetNextCacheCheckTime() + rr->LastUsed = m ? m->timenow : 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 && (mDNSs32)rr->resrec.rroriginalttl != -1) + 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)) + RecordType |= kDNSRecordTypePacketUniqueMask; + ptr += 10; + if (ptr + pktrdlength > end) { debugf("GetResourceRecord: RDATA exceeds end of packet"); return(mDNSNULL); } + end = ptr + pktrdlength; // Adjust end to indicate the end of the rdata for this resource record + + rr->resrec.rdata = (RData*)&rr->rdatastorage; + rr->resrec.rdata->MaxRDLength = MaximumRDSize; + + if (!RecordType) LogMsg("GetLargeResourceRecord: No RecordType for %##s", rr->resrec.name->c); + + switch (rr->resrec.rrtype) + { + case kDNSType_A: rr->resrec.rdata->u.ipv4.b[0] = ptr[0]; + rr->resrec.rdata->u.ipv4.b[1] = ptr[1]; + rr->resrec.rdata->u.ipv4.b[2] = ptr[2]; + rr->resrec.rdata->u.ipv4.b[3] = ptr[3]; + break; + + case kDNSType_CNAME:// Same as PTR + case kDNSType_NS: + 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; + + case kDNSType_SOA: ptr = getDomainName(msg, ptr, end, &rr->resrec.rdata->u.soa.mname); + if (!ptr) { debugf("GetResourceRecord: Malformed SOA RDATA mname"); return mDNSNULL; } + ptr = getDomainName(msg, ptr, end, &rr->resrec.rdata->u.soa.rname); + if (!ptr) { debugf("GetResourceRecord: Malformed SOA RDATA rname"); return mDNSNULL; } + if (ptr + 0x14 != end) { debugf("GetResourceRecord: Malformed SOA RDATA"); return mDNSNULL; } + rr->resrec.rdata->u.soa.serial = (mDNSs32) ((mDNSs32)ptr[0x00] << 24 | (mDNSs32)ptr[0x01] << 16 | (mDNSs32)ptr[0x02] << 8 | ptr[0x03]); + rr->resrec.rdata->u.soa.refresh = (mDNSu32) ((mDNSu32)ptr[0x04] << 24 | (mDNSu32)ptr[0x05] << 16 | (mDNSu32)ptr[0x06] << 8 | ptr[0x07]); + rr->resrec.rdata->u.soa.retry = (mDNSu32) ((mDNSu32)ptr[0x08] << 24 | (mDNSu32)ptr[0x09] << 16 | (mDNSu32)ptr[0x0A] << 8 | ptr[0x0B]); + rr->resrec.rdata->u.soa.expire = (mDNSu32) ((mDNSu32)ptr[0x0C] << 24 | (mDNSu32)ptr[0x0D] << 16 | (mDNSu32)ptr[0x0E] << 8 | ptr[0x0F]); + rr->resrec.rdata->u.soa.min = (mDNSu32) ((mDNSu32)ptr[0x10] << 24 | (mDNSu32)ptr[0x11] << 16 | (mDNSu32)ptr[0x12] << 8 | ptr[0x13]); + break; + + case kDNSType_OPT: getOptRdata(ptr, end, largecr, 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); + + // Success! Now fill in RecordType to show this record contains valid data + rr->resrec.RecordType = RecordType; + return(ptr + pktrdlength); + } + +mDNSexport 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); + } + +mDNSexport 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); + } + +mDNSexport 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); + } + +mDNSexport 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); + } + +mDNSexport const mDNSu8 *LocateAdditionals(const DNSMessage *const msg, const mDNSu8 *const end) + { + int i; + const mDNSu8 *ptr = LocateAuthorities(msg, end); + for (i = 0; i < msg->h.numAuthorities; i++) ptr = skipResourceRecord(msg, ptr, end); + return (ptr); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - +#pragma mark - Packet Sending Functions +#endif + +mDNSexport mStatus mDNSSendDNSMessage(const mDNS *const m, DNSMessage *const msg, mDNSu8 *end, + mDNSInterfaceID InterfaceID, const mDNSAddr *dst, mDNSIPPort dstport, int sd, uDNS_AuthInfo *authInfo) + { + mStatus status; + int nsent; + mDNSs32 msglen; + mDNSu8 lenbuf[2]; + mDNSu16 numQuestions = msg->h.numQuestions; + mDNSu16 numAnswers = msg->h.numAnswers; + mDNSu16 numAuthorities = msg->h.numAuthorities; + mDNSu16 numAdditionals = msg->h.numAdditionals; + mDNSu8 *ptr = (mDNSu8 *)&msg->h.numQuestions; + + // Put all the integer values in IETF byte-order (MSB first, LSB second) + *ptr++ = (mDNSu8)(numQuestions >> 8); + *ptr++ = (mDNSu8)(numQuestions & 0xFF); + *ptr++ = (mDNSu8)(numAnswers >> 8); + *ptr++ = (mDNSu8)(numAnswers & 0xFF); + *ptr++ = (mDNSu8)(numAuthorities >> 8); + *ptr++ = (mDNSu8)(numAuthorities & 0xFF); + *ptr++ = (mDNSu8)(numAdditionals >> 8); + *ptr++ = (mDNSu8)(numAdditionals & 0xFF); + + if (authInfo) + { + end = DNSDigest_SignMessage(msg, &end, &numAdditionals, authInfo); + if (!end) return mStatus_UnknownErr; + } + + // Send the packet on the wire + + if (sd >= 0) + { + msglen = (mDNSu16)(end - (mDNSu8 *)msg); + lenbuf[0] = (mDNSu8)(msglen >> 8); // host->network byte conversion + lenbuf[1] = (mDNSu8)(msglen & 0xFF); + nsent = mDNSPlatformWriteTCP(sd, (char*)lenbuf, 2); + //!!!KRS make sure kernel is sending these as 1 packet! + if (nsent != 2) goto tcp_error; + nsent = mDNSPlatformWriteTCP(sd, (char *)msg, msglen); + if (nsent != msglen) goto tcp_error; + status = mStatus_NoError; + } + else + { + status = mDNSPlatformSendUDP(m, msg, end, InterfaceID, 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 = (mDNSu16)(authInfo ? numAdditionals - 1 : numAdditionals); + + return(status); + + tcp_error: + LogMsg("mDNSSendDNSMessage: error sending message over tcp"); + return mStatus_UnknownErr; + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - RR List Management & Task Management +#endif + +mDNSexport 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, mDNS_TimeNow_NoLock(m)); + m->timenow = mDNS_TimeNow_NoLock(m); + 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 = mDNS_TimeNow_NoLock(m); + if (m->timenow == 0) m->timenow = 1; + } + + if (m->timenow_last - m->timenow > 0) + { + m->timenow_adjust += m->timenow_last - m->timenow; + LogMsg("mDNSPlatformRawTime 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) + { + if (m->NewQuestions->DelayAnswering) e = m->NewQuestions->DelayAnswering; + else return(m->timenow); + } + if (m->NewLocalOnlyQuestions) return(m->timenow); + if (m->NewLocalRecords && LocalRecordReady(m->NewLocalRecords)) return(m->timenow); + if (m->SuppressSending) return(m->SuppressSending); +#ifndef UNICAST_DISABLED + if (e - m->uDNS_info.nextevent > 0) e = m->uDNS_info.nextevent; +#endif + 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); + } + +mDNSexport 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); + } diff --git a/src/mdns/DNSCommon.h b/src/mdns/DNSCommon.h new file mode 100644 index 00000000..3ec6fc34 --- /dev/null +++ b/src/mdns/DNSCommon.h @@ -0,0 +1,366 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + Change History (most recent first): + +$Log: DNSCommon.h,v $ +Revision 1.34.2.1 2006/08/29 06:24:22 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.34 2006/03/18 21:47:56 cheshire + Improve logic for delaying packets after repeated interface transitions + +Revision 1.33 2006/03/10 21:51:41 cheshire + After record update, old record sometimes remains in cache +Split out SameRDataBody() into a separate routine so it can be called from other code + +Revision 1.32 2005/03/21 00:33:51 shersche + Fix build warnings on Win32 platform + +Revision 1.31 2005/02/18 00:43:11 cheshire + mDNSResponder should auto-truncate service names that are too long + +Revision 1.30 2005/01/19 03:12:44 cheshire +Move LocalRecordReady() macro from mDNS.c to DNSCommon.h + +Revision 1.29 2004/12/15 02:11:22 ksekar + Don't check for Dynamic DNS hostname uniqueness + +Revision 1.28 2004/12/06 21:15:22 ksekar + mDNSResponder crashed in CheckServiceRegistrations + +Revision 1.27 2004/12/03 07:20:50 ksekar + Wide-Area: Registration of large TXT record fails + +Revision 1.26 2004/12/03 05:18:33 ksekar + mDNSResponder needs to return more specific TSIG errors + +Revision 1.25 2004/10/26 03:52:02 cheshire +Update checkin comments + +Revision 1.24 2004/10/23 01:16:00 cheshire + uDNS operations not always reliable on multi-homed hosts + +Revision 1.23 2004/10/03 23:18:58 cheshire +Move address comparison macros from DNSCommon.h to mDNSEmbeddedAPI.h + +Revision 1.22 2004/09/30 00:24:56 ksekar + Dynamically update default registration domains on config change + +Revision 1.21 2004/09/17 01:08:48 cheshire +Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h + The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces + declared in that file are ONLY appropriate to single-address-space embedded applications. + For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used. + +Revision 1.20 2004/09/17 00:49:51 cheshire +Get rid of now-unused GetResourceRecord -- the correct (safe) routine to use +is GetLargeResourceRecord + +Revision 1.19 2004/09/16 21:59:15 cheshire +For consistency with zerov6Addr, rename zeroIPAddr to zerov4Addr + +Revision 1.18 2004/09/16 02:29:39 cheshire +Moved mDNS_Lock/mDNS_Unlock to DNSCommon.c; Added necessary locking around +uDNS_ReceiveMsg, uDNS_StartQuery, uDNS_UpdateRecord, uDNS_RegisterService + +Revision 1.17 2004/09/14 23:27:46 cheshire +Fix compile errors + +Revision 1.16 2004/08/13 23:46:58 cheshire +"asyncronous" -> "asynchronous" + +Revision 1.15 2004/08/10 23:19:14 ksekar +: DNS Extension daemon for Wide Area Service Discovery +Moved routines/constants to allow extern access for garbage collection daemon + +Revision 1.14 2004/05/28 23:42:36 ksekar +: Feature: DNS server->client notification on record changes (#7805) + +Revision 1.13 2004/05/18 23:51:25 cheshire +Tidy up all checkin comments to use consistent "" format for bug numbers + +Revision 1.12 2004/04/22 04:03:59 cheshire +Headers should use "extern" declarations, not "mDNSexport" + +Revision 1.11 2004/04/14 23:09:28 ksekar +Support for TSIG signed dynamic updates. + +Revision 1.10 2004/03/13 01:57:33 ksekar +: DynDNS: Dynamic update of service records + +Revision 1.9 2004/02/21 08:56:58 bradley +Wrap prototypes with extern "C" for C++ builds. + +Revision 1.8 2004/02/06 23:04:18 ksekar +Basic Dynamic Update support via mDNS_Register (dissabled via +UNICAST_REGISTRATION #define) + +Revision 1.7 2004/02/03 19:47:36 ksekar +Added an asynchronous state machine mechanism to uDNS.c, including +calls to find the parent zone for a domain name. Changes include code +in repository previously dissabled via "#if 0 incomplete". Codepath +is currently unused, and will be called to create update records, etc. + +Revision 1.6 2004/01/27 20:15:22 cheshire +: Time to prune obsolete code for listening on port 53 + +Revision 1.5 2004/01/24 03:40:56 cheshire +Move mDNSAddrIsDNSMulticast() from DNSCommon.h to mDNSEmbeddedAPI.h so embedded clients can use it + +Revision 1.4 2004/01/24 03:38:27 cheshire +Fix minor syntactic error: Headers should use "extern" declarations, not "mDNSexport" + +Revision 1.3 2004/01/23 23:23:14 ksekar +Added TCP support for truncated unicast messages. + +Revision 1.2 2004/01/21 21:12:23 cheshire +Add missing newline at end of file to make Unix tools happier + +Revision 1.1 2003/12/13 03:05:27 ksekar +: DynDNS: Unicast query of service records + + + */ + +#ifndef __DNSCOMMON_H_ +#define __DNSCOMMON_H_ + +#include "mDNSEmbeddedAPI.h" + +#ifdef __cplusplus + extern "C" { +#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; + +typedef enum + { + TSIG_ErrBadSig = 16, + TSIG_ErrBadKey = 17, + TSIG_ErrBadTime = 18 + } TSIG_ErrorCode; + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - General Utility Functions +#endif + +extern const NetworkInterfaceInfo *GetFirstActiveInterface(const NetworkInterfaceInfo *intf); +extern mDNSInterfaceID GetNextActiveInterfaceID(const NetworkInterfaceInfo *intf); + +extern mDNSu32 mDNSRandom(mDNSu32 max); +extern mDNSu32 mDNSRandomFromFixedSeed(mDNSu32 seed, mDNSu32 max); + + +// *************************************************************************** +#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)) + +#define mdnsValidHostChar(X, notfirst, notlast) (mdnsIsLetter(X) || mdnsIsDigit(X) || ((notfirst) && (notlast) && (X) == '-') ) + +extern mDNSu16 CompressedDomainNameLength(const domainname *const name, const domainname *parent); + +extern mDNSu32 TruncateUTF8ToLength(mDNSu8 *string, mDNSu32 length, mDNSu32 max); +extern mDNSBool LabelContainsSuffix(const domainlabel *const name, const mDNSBool RichText); +extern mDNSu32 RemoveLabelSuffix(domainlabel *name, mDNSBool RichText); +extern void AppendLabelSuffix(domainlabel *name, mDNSu32 val, mDNSBool RichText); +extern void mDNS_HostNameCallback(mDNS *const m, AuthRecord *const rr, mStatus result); +#define ValidateDomainName(N) (DomainNameLength(N) <= MAX_DOMAIN_NAME) + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Resource Record Utility Functions +#endif + +extern mDNSu32 RDataHashValue(mDNSu16 const rdlength, const RDataBody *const rdb); + +extern mDNSBool SameRDataBody(const ResourceRecord *const r1, const RDataBody *const r2); +extern mDNSBool SameRData(const ResourceRecord *const r1, const ResourceRecord *const r2); + +extern mDNSBool ResourceRecordAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q); + +extern mDNSBool SameResourceRecord(ResourceRecord *r1, ResourceRecord *r2); + +extern mDNSu16 GetRDLength(const ResourceRecord *const rr, mDNSBool estimate); + +#define GetRRDomainNameTarget(RR) ( \ + ((RR)->rrtype == kDNSType_CNAME || (RR)->rrtype == kDNSType_PTR || (RR)->rrtype == kDNSType_NS) \ + ? &(RR)->rdata->u.name : \ + ((RR)->rrtype == kDNSType_SRV ) ? &(RR)->rdata->u.srv.target : mDNSNULL ) + +extern mDNSBool ValidateRData(const mDNSu16 rrtype, const mDNSu16 rdlength, const RData *const rd); +#define LocalRecordReady(X) ((X)->resrec.RecordType != kDNSRecordTypeUnique && (X)->resrec.RecordType != kDNSRecordTypeDeregistering) + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - +#pragma mark - DNS Message Creation Functions +#endif + +extern void InitializeDNSMessage(DNSMessageHeader *h, mDNSOpaque16 id, mDNSOpaque16 flags); +extern const mDNSu8 *FindCompressionPointer(const mDNSu8 *const base, const mDNSu8 *const end, const mDNSu8 *const domname); + +extern mDNSu8 *putDomainNameAsLabels(const DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit, const domainname *const name); + +extern mDNSu8 *putRData(const DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit, ResourceRecord *rr); + +// 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 + +extern mDNSu8 *PutResourceRecordTTLWithLimit(DNSMessage *const msg, mDNSu8 *ptr, mDNSu16 *count, ResourceRecord *rr, mDNSu32 ttl, const mDNSu8 *limit); + +#define PutResourceRecordTTL(msg, ptr, count, rr, ttl) PutResourceRecordTTLWithLimit((msg), (ptr), (count), (rr), (ttl), \ + ((msg)->h.numAnswers || (msg)->h.numAuthorities || (msg)->h.numAdditionals) ? (msg)->data + NormalMaxDNSMessageData : (msg)->data + AbsoluteMaxDNSMessageData) + +#define PutResourceRecordTTLJumbo(msg, ptr, count, rr, ttl) PutResourceRecordTTLWithLimit((msg), (ptr), (count), (rr), (ttl), \ + (msg)->data + AbsoluteMaxDNSMessageData) + +extern mDNSu8 *PutResourceRecordCappedTTL(DNSMessage *const msg, mDNSu8 *ptr, mDNSu16 *count, ResourceRecord *rr, mDNSu32 maxttl); + +extern mDNSu8 *putEmptyResourceRecord(DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit, mDNSu16 *count, const AuthRecord *rr); + +extern mDNSu8 *putQuestion(DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit, const domainname *const name, mDNSu16 rrtype, mDNSu16 rrclass); + +extern mDNSu8 *putZone(DNSMessage *const msg, mDNSu8 *ptr, mDNSu8 *limit, const domainname *zone, mDNSOpaque16 zoneClass); + +extern mDNSu8 *putPrereqNameNotInUse(domainname *name, DNSMessage *msg, mDNSu8 *ptr, mDNSu8 *end); + +extern mDNSu8 *putDeletionRecord(DNSMessage *msg, mDNSu8 *ptr, ResourceRecord *rr); + +extern mDNSu8 *putDeleteRRSet(DNSMessage *msg, mDNSu8 *ptr, const domainname *name, mDNSu16 rrtype); + +extern mDNSu8 *putDeleteAllRRSets(DNSMessage *msg, mDNSu8 *ptr, const domainname *name); + +extern mDNSu8 *putUpdateLease(DNSMessage *msg, mDNSu8 *end, mDNSu32 lease); + +#define PutResourceRecord(MSG, P, C, RR) PutResourceRecordTTL((MSG), (P), (C), (RR), (RR)->rroriginalttl) + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNS Message Parsing Functions +#endif + +extern mDNSu32 DomainNameHashValue(const domainname *const name); + +extern void SetNewRData(ResourceRecord *const rr, RData *NewRData, mDNSu16 rdlength); + + +extern const mDNSu8 *skipDomainName(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *const end); + +extern const mDNSu8 *getDomainName(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *const end, + domainname *const name); + +extern const mDNSu8 *skipResourceRecord(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end); + +extern const mDNSu8 *GetLargeResourceRecord(mDNS *const m, const DNSMessage * const msg, const mDNSu8 *ptr, + const mDNSu8 * end, const mDNSInterfaceID InterfaceID, mDNSu8 RecordType, LargeCacheRecord *largecr); + +extern const mDNSu8 *skipQuestion(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end); + +extern const mDNSu8 *getQuestion(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end, const mDNSInterfaceID InterfaceID, + DNSQuestion *question); + +extern const mDNSu8 *LocateAnswers(const DNSMessage *const msg, const mDNSu8 *const end); + +extern const mDNSu8 *LocateAuthorities(const DNSMessage *const msg, const mDNSu8 *const end); + +extern const mDNSu8 *LocateAdditionals(const DNSMessage *const msg, const mDNSu8 *const end); + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - +#pragma mark - Packet Sending Functions +#endif + +extern mStatus mDNSSendDNSMessage(const mDNS *const m, DNSMessage *const msg, mDNSu8 *end, + mDNSInterfaceID InterfaceID, const mDNSAddr *dst, mDNSIPPort dstport, int sd, uDNS_AuthInfo *authInfo); + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - RR List Management & Task Management +#endif + +extern void mDNS_Lock(mDNS *const m); +extern void mDNS_Unlock(mDNS *const m); + +#ifdef __cplusplus + } +#endif + +#endif // __DNSCOMMON_H_ diff --git a/src/mdns/DNSDigest.c b/src/mdns/DNSDigest.c new file mode 100644 index 00000000..91b7078c --- /dev/null +++ b/src/mdns/DNSDigest.c @@ -0,0 +1,1464 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + Change History (most recent first): + +$Log: DNSDigest.c,v $ +Revision 1.15.2.1 2006/08/29 06:24:22 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.15 2006/06/20 04:12:30 cheshire + DNS Update broken + +Revision 1.14 2006/02/25 23:12:07 cheshire + Fix to avoid code generation warning/error on FreeBSD 7 + +Revision 1.13 2004/12/16 20:12:59 cheshire + Cache memory management improvements + +Revision 1.12 2004/12/03 07:20:50 ksekar + Wide-Area: Registration of large TXT record fails + +Revision 1.11 2004/12/02 01:10:27 cheshire +Fix to compile cleanly on 64-bit x86 + +Revision 1.10 2004/11/01 20:36:04 ksekar + mDNSResponder should not receive Keychain Notifications + +Revision 1.9 2004/10/26 09:00:12 cheshire +Save a few bytes by creating HMAC_MD5_AlgName as a C string instead of a 256-byte object + +Revision 1.8 2004/09/17 01:08:48 cheshire +Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h + The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces + declared in that file are ONLY appropriate to single-address-space embedded applications. + For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used. + +Revision 1.7 2004/08/15 18:36:38 cheshire +Don't use strcpy() and strlen() on "struct domainname" objects; +use AssignDomainName() and DomainNameLength() instead +(A "struct domainname" is a collection of packed pascal strings, not a C string.) + +Revision 1.6 2004/06/02 00:17:46 ksekar +Referenced original OpenSSL license headers in source file description. + +Revision 1.5 2004/05/20 18:37:37 cheshire +Fix compiler warnings + +Revision 1.4 2004/04/22 20:28:20 cheshire +Use existing facility of PutResourceRecordTTL() to update count field for us + +Revision 1.3 2004/04/22 03:05:28 cheshire +kDNSClass_ANY should be kDNSQClass_ANY + +Revision 1.2 2004/04/15 00:51:28 bradley +Minor tweaks for Windows and C++ builds. Added casts for signed/unsigned integers and 64-bit pointers. +Prefix some functions with mDNS to avoid conflicts. Disable benign warnings on Microsoft compilers. + +Revision 1.1 2004/04/14 23:09:28 ksekar +Support for TSIG signed dynamic updates. + + + +*/ + + +#ifdef __cplusplus +extern "C" { +#endif + +#include "mDNSEmbeddedAPI.h" +#include "DNSCommon.h" + +// 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) +#endif + + // *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - MD5 Hash Functions +#endif + + +/* The source for the has is derived CommonCrypto files CommonDigest.h, md32_common.h, md5_locl.h, md5_locl.h, and openssl/md5.h. + * The following changes have been made to the original sources: + * replaced CC_LONG w/ mDNSu32 + * replaced CC_MD5* with MD5* + * replaced CC_LONG w/ mDNSu32, removed conditional #defines from md5.h + * removed extern decls for MD5_Init/Update/Final from CommonDigest.h + * removed APPLE_COMMON_DIGEST specific #defines from md5_locl.h + * + * Note: machine archetecure specific conditionals from the original sources are turned off, but are left in the code + * to aid in platform-specific optimizations and debugging. + * Sources originally distributed under the following license headers: + * CommonDigest.c - APSL + * + * md32_Common.h + * ==================================================================== + * Copyright (c) 1999-2002 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * licensing@OpenSSL.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * md5_dgst.c, md5_locl.h + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + * Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + * + */ + +//from CommonDigest.h + +#define MD5_DIGEST_LENGTH 16 /* digest length in bytes */ +#define MD5_BLOCK_BYTES 64 /* block size in bytes */ +#define MD5_BLOCK_LONG (MD5_BLOCK_BYTES / sizeof(mDNSu32)) + +typedef struct MD5state_st +{ + mDNSu32 A,B,C,D; + mDNSu32 Nl,Nh; + mDNSu32 data[MD5_BLOCK_LONG]; + int num; +} MD5_CTX; + + +// from openssl/md5.h + +#define MD5_CBLOCK 64 +#define MD5_LBLOCK (MD5_CBLOCK/4) +#define MD5_DIGEST_LENGTH 16 + +int MD5_Init(MD5_CTX *c); +int MD5_Update(MD5_CTX *c, const void *data, unsigned long len); +int MD5_Final(unsigned char *md, MD5_CTX *c); +void MD5_Transform(MD5_CTX *c, const unsigned char *b); + +// From md5_locl.h + +#ifndef MD5_LONG_LOG2 +#define MD5_LONG_LOG2 2 /* default to 32 bits */ +#endif + +#ifdef MD5_ASM +# if defined(__i386) || defined(__i386__) || defined(_M_IX86) || defined(__INTEL__) +# define md5_block_host_order md5_block_asm_host_order +# elif defined(__sparc) && defined(OPENSSL_SYS_ULTRASPARC) + void md5_block_asm_data_order_aligned (MD5_CTX *c, const mDNSu32 *p,int num); +# define HASH_BLOCK_DATA_ORDER_ALIGNED md5_block_asm_data_order_aligned +# endif +#endif + +void md5_block_host_order (MD5_CTX *c, const void *p,int num); +void md5_block_data_order (MD5_CTX *c, const void *p,int num); + +#if defined(__i386) || defined(__i386__) || defined(_M_IX86) || defined(__INTEL__) +/* + * *_block_host_order is expected to handle aligned data while + * *_block_data_order - unaligned. As algorithm and host (x86) + * are in this case of the same "endianness" these two are + * otherwise indistinguishable. But normally you don't want to + * call the same function because unaligned access in places + * where alignment is expected is usually a "Bad Thing". Indeed, + * on RISCs you get punished with BUS ERROR signal or *severe* + * performance degradation. Intel CPUs are in turn perfectly + * capable of loading unaligned data without such drastic side + * effect. Yes, they say it's slower than aligned load, but no + * exception is generated and therefore performance degradation + * is *incomparable* with RISCs. What we should weight here is + * costs of unaligned access against costs of aligning data. + * According to my measurements allowing unaligned access results + * in ~9% performance improvement on Pentium II operating at + * 266MHz. I won't be surprised if the difference will be higher + * on faster systems:-) + * + * + */ +#define md5_block_data_order md5_block_host_order +#endif + +#define DATA_ORDER_IS_LITTLE_ENDIAN + +#define HASH_LONG mDNSu32 +#define HASH_LONG_LOG2 MD5_LONG_LOG2 +#define HASH_CTX MD5_CTX +#define HASH_CBLOCK MD5_CBLOCK +#define HASH_LBLOCK MD5_LBLOCK + +#define HASH_UPDATE MD5_Update +#define HASH_TRANSFORM MD5_Transform +#define HASH_FINAL MD5_Final + +#define HASH_MAKE_STRING(c,s) do { \ + unsigned long ll; \ + ll=(c)->A; HOST_l2c(ll,(s)); \ + ll=(c)->B; HOST_l2c(ll,(s)); \ + ll=(c)->C; HOST_l2c(ll,(s)); \ + ll=(c)->D; HOST_l2c(ll,(s)); \ + } while (0) +#define HASH_BLOCK_HOST_ORDER md5_block_host_order +#if !defined(L_ENDIAN) || defined(md5_block_data_order) +#define HASH_BLOCK_DATA_ORDER md5_block_data_order +/* + * Little-endians (Intel and Alpha) feel better without this. + * It looks like memcpy does better job than generic + * md5_block_data_order on copying-n-aligning input data. + * But frankly speaking I didn't expect such result on Alpha. + * On the other hand I've got this with egcs-1.0.2 and if + * program is compiled with another (better?) compiler it + * might turn out other way around. + * + * + */ +#endif + + +// from md32_common.h + +/* + * This is a generic 32 bit "collector" for message digest algorithms. + * Whenever needed it collects input character stream into chunks of + * 32 bit values and invokes a block function that performs actual hash + * calculations. + * + * Porting guide. + * + * Obligatory macros: + * + * DATA_ORDER_IS_BIG_ENDIAN or DATA_ORDER_IS_LITTLE_ENDIAN + * this macro defines byte order of input stream. + * HASH_CBLOCK + * size of a unit chunk HASH_BLOCK operates on. + * HASH_LONG + * has to be at lest 32 bit wide, if it's wider, then + * HASH_LONG_LOG2 *has to* be defined along + * HASH_CTX + * context structure that at least contains following + * members: + * typedef struct { + * ... + * HASH_LONG Nl,Nh; + * HASH_LONG data[HASH_LBLOCK]; + * int num; + * ... + * } HASH_CTX; + * HASH_UPDATE + * name of "Update" function, implemented here. + * HASH_TRANSFORM + * name of "Transform" function, implemented here. + * HASH_FINAL + * name of "Final" function, implemented here. + * HASH_BLOCK_HOST_ORDER + * name of "block" function treating *aligned* input message + * in host byte order, implemented externally. + * HASH_BLOCK_DATA_ORDER + * name of "block" function treating *unaligned* input message + * in original (data) byte order, implemented externally (it + * actually is optional if data and host are of the same + * "endianess"). + * HASH_MAKE_STRING + * macro convering context variables to an ASCII hash string. + * + * Optional macros: + * + * B_ENDIAN or L_ENDIAN + * defines host byte-order. + * HASH_LONG_LOG2 + * defaults to 2 if not states otherwise. + * HASH_LBLOCK + * assumed to be HASH_CBLOCK/4 if not stated otherwise. + * HASH_BLOCK_DATA_ORDER_ALIGNED + * alternative "block" function capable of treating + * aligned input message in original (data) order, + * implemented externally. + * + * MD5 example: + * + * #define DATA_ORDER_IS_LITTLE_ENDIAN + * + * #define HASH_LONG mDNSu32 + * #define HASH_LONG_LOG2 mDNSu32_LOG2 + * #define HASH_CTX MD5_CTX + * #define HASH_CBLOCK MD5_CBLOCK + * #define HASH_LBLOCK MD5_LBLOCK + * #define HASH_UPDATE MD5_Update + * #define HASH_TRANSFORM MD5_Transform + * #define HASH_FINAL MD5_Final + * #define HASH_BLOCK_HOST_ORDER md5_block_host_order + * #define HASH_BLOCK_DATA_ORDER md5_block_data_order + * + * + */ + +#if !defined(DATA_ORDER_IS_BIG_ENDIAN) && !defined(DATA_ORDER_IS_LITTLE_ENDIAN) +#error "DATA_ORDER must be defined!" +#endif + +#ifndef HASH_CBLOCK +#error "HASH_CBLOCK must be defined!" +#endif +#ifndef HASH_LONG +#error "HASH_LONG must be defined!" +#endif +#ifndef HASH_CTX +#error "HASH_CTX must be defined!" +#endif + +#ifndef HASH_UPDATE +#error "HASH_UPDATE must be defined!" +#endif +#ifndef HASH_TRANSFORM +#error "HASH_TRANSFORM must be defined!" +#endif +#ifndef HASH_FINAL +#error "HASH_FINAL must be defined!" +#endif + +#ifndef HASH_BLOCK_HOST_ORDER +#error "HASH_BLOCK_HOST_ORDER must be defined!" +#endif + +#if 0 +/* + * Moved below as it's required only if HASH_BLOCK_DATA_ORDER_ALIGNED + * isn't defined. + */ +#ifndef HASH_BLOCK_DATA_ORDER +#error "HASH_BLOCK_DATA_ORDER must be defined!" +#endif +#endif + +#ifndef HASH_LBLOCK +#define HASH_LBLOCK (HASH_CBLOCK/4) +#endif + +#ifndef HASH_LONG_LOG2 +#define HASH_LONG_LOG2 2 +#endif + +/* + * Engage compiler specific rotate intrinsic function if available. + */ +#undef ROTATE +#ifndef PEDANTIC +# if 0 /* defined(_MSC_VER) */ +# define ROTATE(a,n) _lrotl(a,n) +# elif defined(__MWERKS__) +# if defined(__POWERPC__) +# define ROTATE(a,n) (unsigned MD32_REG_T)__rlwinm((int)a,n,0,31) +# elif defined(__MC68K__) + /* Motorola specific tweak. */ +# define ROTATE(a,n) ( n<24 ? __rol(a,n) : __ror(a,32-n) ) +# else +# define ROTATE(a,n) __rol(a,n) +# endif +# elif defined(__GNUC__) && __GNUC__>=2 && !defined(OPENSSL_NO_ASM) && !defined(OPENSSL_NO_INLINE_ASM) + /* + * Some GNU C inline assembler templates. Note that these are + * rotates by *constant* number of bits! But that's exactly + * what we need here... + * + * + */ +# if defined(__i386) || defined(__i386__) || defined(__x86_64) || defined(__x86_64__) +# define ROTATE(a,n) ({ register unsigned int ret; \ + asm ( \ + "roll %1,%0" \ + : "=r"(ret) \ + : "I"(n), "0"(a) \ + : "cc"); \ + ret; \ + }) +# elif defined(__powerpc) || defined(__ppc) +# define ROTATE(a,n) ({ register unsigned int ret; \ + asm ( \ + "rlwinm %0,%1,%2,0,31" \ + : "=r"(ret) \ + : "r"(a), "I"(n)); \ + ret; \ + }) +# endif +# endif + +/* + * Engage compiler specific "fetch in reverse byte order" + * intrinsic function if available. + */ +# if defined(__GNUC__) && __GNUC__>=2 && !defined(OPENSSL_NO_ASM) && !defined(OPENSSL_NO_INLINE_ASM) + /* some GNU C inline assembler templates by */ +# if (defined(__i386) || defined(__i386__) || defined(__x86_64) || defined(__x86_64__)) && !defined(I386_ONLY) +# define BE_FETCH32(a) ({ register unsigned int l=(a);\ + asm ( \ + "bswapl %0" \ + : "=r"(l) : "0"(l)); \ + l; \ + }) +# elif defined(__powerpc) +# define LE_FETCH32(a) ({ register unsigned int l; \ + asm ( \ + "lwbrx %0,0,%1" \ + : "=r"(l) \ + : "r"(a)); \ + l; \ + }) + +# elif defined(__sparc) && defined(OPENSSL_SYS_ULTRASPARC) +# define LE_FETCH32(a) ({ register unsigned int l; \ + asm ( \ + "lda [%1]#ASI_PRIMARY_LITTLE,%0"\ + : "=r"(l) \ + : "r"(a)); \ + l; \ + }) +# endif +# endif +#endif /* PEDANTIC */ + +#if HASH_LONG_LOG2==2 /* Engage only if sizeof(HASH_LONG)== 4 */ +/* A nice byte order reversal from Wei Dai */ +#ifdef ROTATE +/* 5 instructions with rotate instruction, else 9 */ +#define REVERSE_FETCH32(a,l) ( \ + l=*(const HASH_LONG *)(a), \ + ((ROTATE(l,8)&0x00FF00FF)|(ROTATE((l&0x00FF00FF),24))) \ + ) +#else +/* 6 instructions with rotate instruction, else 8 */ +#define REVERSE_FETCH32(a,l) ( \ + l=*(const HASH_LONG *)(a), \ + l=(((l>>8)&0x00FF00FF)|((l&0x00FF00FF)<<8)), \ + ROTATE(l,16) \ + ) +/* + * Originally the middle line started with l=(((l&0xFF00FF00)>>8)|... + * It's rewritten as above for two reasons: + * - RISCs aren't good at long constants and have to explicitely + * compose 'em with several (well, usually 2) instructions in a + * register before performing the actual operation and (as you + * already realized:-) having same constant should inspire the + * compiler to permanently allocate the only register for it; + * - most modern CPUs have two ALUs, but usually only one has + * circuitry for shifts:-( this minor tweak inspires compiler + * to schedule shift instructions in a better way... + * + * + */ +#endif +#endif + +#ifndef ROTATE +#define ROTATE(a,n) (((a)<<(n))|(((a)&0xffffffff)>>(32-(n)))) +#endif + +/* + * Make some obvious choices. E.g., HASH_BLOCK_DATA_ORDER_ALIGNED + * and HASH_BLOCK_HOST_ORDER ought to be the same if input data + * and host are of the same "endianess". It's possible to mask + * this with blank #define HASH_BLOCK_DATA_ORDER though... + * + * + */ +#if defined(B_ENDIAN) +# if defined(DATA_ORDER_IS_BIG_ENDIAN) +# if !defined(HASH_BLOCK_DATA_ORDER_ALIGNED) && HASH_LONG_LOG2==2 +# define HASH_BLOCK_DATA_ORDER_ALIGNED HASH_BLOCK_HOST_ORDER +# endif +# elif defined(DATA_ORDER_IS_LITTLE_ENDIAN) +# ifndef HOST_FETCH32 +# ifdef LE_FETCH32 +# define HOST_FETCH32(p,l) LE_FETCH32(p) +# elif defined(REVERSE_FETCH32) +# define HOST_FETCH32(p,l) REVERSE_FETCH32(p,l) +# endif +# endif +# endif +#elif defined(L_ENDIAN) +# if defined(DATA_ORDER_IS_LITTLE_ENDIAN) +# if !defined(HASH_BLOCK_DATA_ORDER_ALIGNED) && HASH_LONG_LOG2==2 +# define HASH_BLOCK_DATA_ORDER_ALIGNED HASH_BLOCK_HOST_ORDER +# endif +# elif defined(DATA_ORDER_IS_BIG_ENDIAN) +# ifndef HOST_FETCH32 +# ifdef BE_FETCH32 +# define HOST_FETCH32(p,l) BE_FETCH32(p) +# elif defined(REVERSE_FETCH32) +# define HOST_FETCH32(p,l) REVERSE_FETCH32(p,l) +# endif +# endif +# endif +#endif + +#if !defined(HASH_BLOCK_DATA_ORDER_ALIGNED) +#ifndef HASH_BLOCK_DATA_ORDER +#error "HASH_BLOCK_DATA_ORDER must be defined!" +#endif +#endif + +#if defined(DATA_ORDER_IS_BIG_ENDIAN) + +#define HOST_c2l(c,l) (l =(((unsigned long)(*((c)++)))<<24), \ + l|=(((unsigned long)(*((c)++)))<<16), \ + l|=(((unsigned long)(*((c)++)))<< 8), \ + l|=(((unsigned long)(*((c)++))) ), \ + l) +#define HOST_p_c2l(c,l,n) { \ + switch (n) { \ + case 0: l =((unsigned long)(*((c)++)))<<24; \ + case 1: l|=((unsigned long)(*((c)++)))<<16; \ + case 2: l|=((unsigned long)(*((c)++)))<< 8; \ + case 3: l|=((unsigned long)(*((c)++))); \ + } } +#define HOST_p_c2l_p(c,l,sc,len) { \ + switch (sc) { \ + case 0: l =((unsigned long)(*((c)++)))<<24; \ + if (--len == 0) break; \ + case 1: l|=((unsigned long)(*((c)++)))<<16; \ + if (--len == 0) break; \ + case 2: l|=((unsigned long)(*((c)++)))<< 8; \ + } } +/* NOTE the pointer is not incremented at the end of this */ +#define HOST_c2l_p(c,l,n) { \ + l=0; (c)+=n; \ + switch (n) { \ + case 3: l =((unsigned long)(*(--(c))))<< 8; \ + case 2: l|=((unsigned long)(*(--(c))))<<16; \ + case 1: l|=((unsigned long)(*(--(c))))<<24; \ + } } +#define HOST_l2c(l,c) (*((c)++)=(unsigned char)(((l)>>24)&0xff), \ + *((c)++)=(unsigned char)(((l)>>16)&0xff), \ + *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ + *((c)++)=(unsigned char)(((l) )&0xff), \ + l) + +#elif defined(DATA_ORDER_IS_LITTLE_ENDIAN) + +#define HOST_c2l(c,l) (l =(((unsigned long)(*((c)++))) ), \ + l|=(((unsigned long)(*((c)++)))<< 8), \ + l|=(((unsigned long)(*((c)++)))<<16), \ + l|=(((unsigned long)(*((c)++)))<<24), \ + l) +#define HOST_p_c2l(c,l,n) { \ + switch (n) { \ + case 0: l =((unsigned long)(*((c)++))); \ + case 1: l|=((unsigned long)(*((c)++)))<< 8; \ + case 2: l|=((unsigned long)(*((c)++)))<<16; \ + case 3: l|=((unsigned long)(*((c)++)))<<24; \ + } } +#define HOST_p_c2l_p(c,l,sc,len) { \ + switch (sc) { \ + case 0: l =((unsigned long)(*((c)++))); \ + if (--len == 0) break; \ + case 1: l|=((unsigned long)(*((c)++)))<< 8; \ + if (--len == 0) break; \ + case 2: l|=((unsigned long)(*((c)++)))<<16; \ + } } +/* NOTE the pointer is not incremented at the end of this */ +#define HOST_c2l_p(c,l,n) { \ + l=0; (c)+=n; \ + switch (n) { \ + case 3: l =((unsigned long)(*(--(c))))<<16; \ + case 2: l|=((unsigned long)(*(--(c))))<< 8; \ + case 1: l|=((unsigned long)(*(--(c)))); \ + } } +#define HOST_l2c(l,c) (*((c)++)=(unsigned char)(((l) )&0xff), \ + *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ + *((c)++)=(unsigned char)(((l)>>16)&0xff), \ + *((c)++)=(unsigned char)(((l)>>24)&0xff), \ + l) + +#endif + +/* + * Time for some action:-) + */ + +int HASH_UPDATE (HASH_CTX *c, const void *data_, unsigned long len) + { + const unsigned char *data=(const unsigned char *)data_; + register HASH_LONG * p; + register unsigned long l; + int sw,sc,ew,ec; + + if (len==0) return 1; + + l=(c->Nl+(len<<3))&0xffffffffL; + /* 95-05-24 eay Fixed a bug with the overflow handling, thanks to + * Wei Dai for pointing it out. */ + if (l < c->Nl) /* overflow */ + c->Nh++; + c->Nh+=(len>>29); + c->Nl=l; + + if (c->num != 0) + { + p=c->data; + sw=c->num>>2; + sc=c->num&0x03; + + if ((c->num+len) >= HASH_CBLOCK) + { + l=p[sw]; HOST_p_c2l(data,l,sc); p[sw++]=l; + for (; swnum); + c->num=0; + /* drop through and do the rest */ + } + else + { + c->num+=len; + if ((sc+len) < 4) /* ugly, add char's to a word */ + { + l=p[sw]; HOST_p_c2l_p(data,l,sc,len); p[sw]=l; + } + else + { + ew=(c->num>>2); + ec=(c->num&0x03); + if (sc) + l=p[sw]; + HOST_p_c2l(data,l,sc); + p[sw++]=l; + for (; sw < ew; sw++) + { + HOST_c2l(data,l); p[sw]=l; + } + if (ec) + { + HOST_c2l_p(data,l,ec); p[sw]=l; + } + } + return 1; + } + } + + sw=(int)(len/HASH_CBLOCK); + if (sw > 0) + { +#if defined(HASH_BLOCK_DATA_ORDER_ALIGNED) + /* + * Note that HASH_BLOCK_DATA_ORDER_ALIGNED gets defined + * only if sizeof(HASH_LONG)==4. + */ + if ((((unsigned long)data)%4) == 0) + { + /* data is properly aligned so that we can cast it: */ + HASH_BLOCK_DATA_ORDER_ALIGNED (c,(HASH_LONG *)data,sw); + sw*=HASH_CBLOCK; + data+=sw; + len-=sw; + } + else +#if !defined(HASH_BLOCK_DATA_ORDER) + while (sw--) + { + memcpy (p=c->data,data,HASH_CBLOCK); + HASH_BLOCK_DATA_ORDER_ALIGNED(c,p,1); + data+=HASH_CBLOCK; + len-=HASH_CBLOCK; + } +#endif +#endif +#if defined(HASH_BLOCK_DATA_ORDER) + { + HASH_BLOCK_DATA_ORDER(c,data,sw); + sw*=HASH_CBLOCK; + data+=sw; + len-=sw; + } +#endif + } + + if (len!=0) + { + p = c->data; + c->num = (int)len; + ew=(int)(len>>2); /* words to copy */ + ec=(int)(len&0x03); + for (; ew; ew--,p++) + { + HOST_c2l(data,l); *p=l; + } + HOST_c2l_p(data,l,ec); + *p=l; + } + return 1; + } + + +void HASH_TRANSFORM (HASH_CTX *c, const unsigned char *data) + { +#if defined(HASH_BLOCK_DATA_ORDER_ALIGNED) + if ((((unsigned long)data)%4) == 0) + /* data is properly aligned so that we can cast it: */ + HASH_BLOCK_DATA_ORDER_ALIGNED (c,(HASH_LONG *)data,1); + else +#if !defined(HASH_BLOCK_DATA_ORDER) + { + memcpy (c->data,data,HASH_CBLOCK); + HASH_BLOCK_DATA_ORDER_ALIGNED (c,c->data,1); + } +#endif +#endif +#if defined(HASH_BLOCK_DATA_ORDER) + HASH_BLOCK_DATA_ORDER (c,data,1); +#endif + } + + +int HASH_FINAL (unsigned char *md, HASH_CTX *c) + { + register HASH_LONG *p; + register unsigned long l; + register int i,j; + static const unsigned char end[4]={0x80,0x00,0x00,0x00}; + const unsigned char *cp=end; + + /* c->num should definitly have room for at least one more byte. */ + p=c->data; + i=c->num>>2; + j=c->num&0x03; + +#if 0 + /* purify often complains about the following line as an + * Uninitialized Memory Read. While this can be true, the + * following p_c2l macro will reset l when that case is true. + * This is because j&0x03 contains the number of 'valid' bytes + * already in p[i]. If and only if j&0x03 == 0, the UMR will + * occur but this is also the only time p_c2l will do + * l= *(cp++) instead of l|= *(cp++) + * Many thanks to Alex Tang for pickup this + * 'potential bug' */ +#ifdef PURIFY + if (j==0) p[i]=0; /* Yeah, but that's not the way to fix it:-) */ +#endif + l=p[i]; +#else + l = (j==0) ? 0 : p[i]; +#endif + HOST_p_c2l(cp,l,j); p[i++]=l; /* i is the next 'undefined word' */ + + if (i>(HASH_LBLOCK-2)) /* save room for Nl and Nh */ + { + if (iNh; + p[HASH_LBLOCK-1]=c->Nl; +#elif defined(DATA_ORDER_IS_LITTLE_ENDIAN) + p[HASH_LBLOCK-2]=c->Nl; + p[HASH_LBLOCK-1]=c->Nh; +#endif + HASH_BLOCK_HOST_ORDER (c,p,1); + +#ifndef HASH_MAKE_STRING +#error "HASH_MAKE_STRING must be defined!" +#else + HASH_MAKE_STRING(c,md); +#endif + + c->num=0; + /* clear stuff, HASH_BLOCK may be leaving some stuff on the stack + * but I'm not worried :-) + OPENSSL_cleanse((void *)c,sizeof(HASH_CTX)); + */ + return 1; + } + +#ifndef MD32_REG_T +#define MD32_REG_T long +/* + * This comment was originaly written for MD5, which is why it + * discusses A-D. But it basically applies to all 32-bit digests, + * which is why it was moved to common header file. + * + * In case you wonder why A-D are declared as long and not + * as mDNSu32. Doing so results in slight performance + * boost on LP64 architectures. The catch is we don't + * really care if 32 MSBs of a 64-bit register get polluted + * with eventual overflows as we *save* only 32 LSBs in + * *either* case. Now declaring 'em long excuses the compiler + * from keeping 32 MSBs zeroed resulting in 13% performance + * improvement under SPARC Solaris7/64 and 5% under AlphaLinux. + * Well, to be honest it should say that this *prevents* + * performance degradation. + * + * Apparently there're LP64 compilers that generate better + * code if A-D are declared int. Most notably GCC-x86_64 + * generates better code. + * + */ +#endif + + +// from md5_locl.h (continued) + +/* +#define F(x,y,z) (((x) & (y)) | ((~(x)) & (z))) +#define G(x,y,z) (((x) & (z)) | ((y) & (~(z)))) +*/ + +/* As pointed out by Wei Dai , the above can be + * simplified to the code below. Wei attributes these optimizations + * to Peter Gutmann's SHS code, and he attributes it to Rich Schroeppel. + */ +#define F(b,c,d) ((((c) ^ (d)) & (b)) ^ (d)) +#define G(b,c,d) ((((b) ^ (c)) & (d)) ^ (c)) +#define H(b,c,d) ((b) ^ (c) ^ (d)) +#define I(b,c,d) (((~(d)) | (b)) ^ (c)) + +#define R0(a,b,c,d,k,s,t) { \ + a+=((k)+(t)+F((b),(c),(d))); \ + a=ROTATE(a,s); \ + a+=b; };\ + +#define R1(a,b,c,d,k,s,t) { \ + a+=((k)+(t)+G((b),(c),(d))); \ + a=ROTATE(a,s); \ + a+=b; }; + +#define R2(a,b,c,d,k,s,t) { \ + a+=((k)+(t)+H((b),(c),(d))); \ + a=ROTATE(a,s); \ + a+=b; }; + +#define R3(a,b,c,d,k,s,t) { \ + a+=((k)+(t)+I((b),(c),(d))); \ + a=ROTATE(a,s); \ + a+=b; }; + +// from md5_dgst.c + + +/* Implemented from RFC1321 The MD5 Message-Digest Algorithm + */ + +#define INIT_DATA_A (unsigned long)0x67452301L +#define INIT_DATA_B (unsigned long)0xefcdab89L +#define INIT_DATA_C (unsigned long)0x98badcfeL +#define INIT_DATA_D (unsigned long)0x10325476L + +int MD5_Init(MD5_CTX *c) + { + c->A=INIT_DATA_A; + c->B=INIT_DATA_B; + c->C=INIT_DATA_C; + c->D=INIT_DATA_D; + c->Nl=0; + c->Nh=0; + c->num=0; + return 1; + } + +#ifndef md5_block_host_order +void md5_block_host_order (MD5_CTX *c, const void *data, int num) + { + const mDNSu32 *X=(const mDNSu32 *)data; + register unsigned MD32_REG_T A,B,C,D; + + A=c->A; + B=c->B; + C=c->C; + D=c->D; + + for (;num--;X+=HASH_LBLOCK) + { + /* Round 0 */ + R0(A,B,C,D,X[ 0], 7,0xd76aa478L); + R0(D,A,B,C,X[ 1],12,0xe8c7b756L); + R0(C,D,A,B,X[ 2],17,0x242070dbL); + R0(B,C,D,A,X[ 3],22,0xc1bdceeeL); + R0(A,B,C,D,X[ 4], 7,0xf57c0fafL); + R0(D,A,B,C,X[ 5],12,0x4787c62aL); + R0(C,D,A,B,X[ 6],17,0xa8304613L); + R0(B,C,D,A,X[ 7],22,0xfd469501L); + R0(A,B,C,D,X[ 8], 7,0x698098d8L); + R0(D,A,B,C,X[ 9],12,0x8b44f7afL); + R0(C,D,A,B,X[10],17,0xffff5bb1L); + R0(B,C,D,A,X[11],22,0x895cd7beL); + R0(A,B,C,D,X[12], 7,0x6b901122L); + R0(D,A,B,C,X[13],12,0xfd987193L); + R0(C,D,A,B,X[14],17,0xa679438eL); + R0(B,C,D,A,X[15],22,0x49b40821L); + /* Round 1 */ + R1(A,B,C,D,X[ 1], 5,0xf61e2562L); + R1(D,A,B,C,X[ 6], 9,0xc040b340L); + R1(C,D,A,B,X[11],14,0x265e5a51L); + R1(B,C,D,A,X[ 0],20,0xe9b6c7aaL); + R1(A,B,C,D,X[ 5], 5,0xd62f105dL); + R1(D,A,B,C,X[10], 9,0x02441453L); + R1(C,D,A,B,X[15],14,0xd8a1e681L); + R1(B,C,D,A,X[ 4],20,0xe7d3fbc8L); + R1(A,B,C,D,X[ 9], 5,0x21e1cde6L); + R1(D,A,B,C,X[14], 9,0xc33707d6L); + R1(C,D,A,B,X[ 3],14,0xf4d50d87L); + R1(B,C,D,A,X[ 8],20,0x455a14edL); + R1(A,B,C,D,X[13], 5,0xa9e3e905L); + R1(D,A,B,C,X[ 2], 9,0xfcefa3f8L); + R1(C,D,A,B,X[ 7],14,0x676f02d9L); + R1(B,C,D,A,X[12],20,0x8d2a4c8aL); + /* Round 2 */ + R2(A,B,C,D,X[ 5], 4,0xfffa3942L); + R2(D,A,B,C,X[ 8],11,0x8771f681L); + R2(C,D,A,B,X[11],16,0x6d9d6122L); + R2(B,C,D,A,X[14],23,0xfde5380cL); + R2(A,B,C,D,X[ 1], 4,0xa4beea44L); + R2(D,A,B,C,X[ 4],11,0x4bdecfa9L); + R2(C,D,A,B,X[ 7],16,0xf6bb4b60L); + R2(B,C,D,A,X[10],23,0xbebfbc70L); + R2(A,B,C,D,X[13], 4,0x289b7ec6L); + R2(D,A,B,C,X[ 0],11,0xeaa127faL); + R2(C,D,A,B,X[ 3],16,0xd4ef3085L); + R2(B,C,D,A,X[ 6],23,0x04881d05L); + R2(A,B,C,D,X[ 9], 4,0xd9d4d039L); + R2(D,A,B,C,X[12],11,0xe6db99e5L); + R2(C,D,A,B,X[15],16,0x1fa27cf8L); + R2(B,C,D,A,X[ 2],23,0xc4ac5665L); + /* Round 3 */ + R3(A,B,C,D,X[ 0], 6,0xf4292244L); + R3(D,A,B,C,X[ 7],10,0x432aff97L); + R3(C,D,A,B,X[14],15,0xab9423a7L); + R3(B,C,D,A,X[ 5],21,0xfc93a039L); + R3(A,B,C,D,X[12], 6,0x655b59c3L); + R3(D,A,B,C,X[ 3],10,0x8f0ccc92L); + R3(C,D,A,B,X[10],15,0xffeff47dL); + R3(B,C,D,A,X[ 1],21,0x85845dd1L); + R3(A,B,C,D,X[ 8], 6,0x6fa87e4fL); + R3(D,A,B,C,X[15],10,0xfe2ce6e0L); + R3(C,D,A,B,X[ 6],15,0xa3014314L); + R3(B,C,D,A,X[13],21,0x4e0811a1L); + R3(A,B,C,D,X[ 4], 6,0xf7537e82L); + R3(D,A,B,C,X[11],10,0xbd3af235L); + R3(C,D,A,B,X[ 2],15,0x2ad7d2bbL); + R3(B,C,D,A,X[ 9],21,0xeb86d391L); + + A = c->A += A; + B = c->B += B; + C = c->C += C; + D = c->D += D; + } + } +#endif + +#ifndef md5_block_data_order +#ifdef X +#undef X +#endif +void md5_block_data_order (MD5_CTX *c, const void *data_, int num) + { + const unsigned char *data=data_; + register unsigned MD32_REG_T A,B,C,D,l; +#ifndef MD32_XARRAY + /* See comment in crypto/sha/sha_locl.h for details. */ + unsigned MD32_REG_T XX0, XX1, XX2, XX3, XX4, XX5, XX6, XX7, + XX8, XX9,XX10,XX11,XX12,XX13,XX14,XX15; +# define X(i) XX##i +#else + mDNSu32 XX[MD5_LBLOCK]; +# define X(i) XX[i] +#endif + + A=c->A; + B=c->B; + C=c->C; + D=c->D; + + for (;num--;) + { + HOST_c2l(data,l); X( 0)=l; HOST_c2l(data,l); X( 1)=l; + /* Round 0 */ + R0(A,B,C,D,X( 0), 7,0xd76aa478L); HOST_c2l(data,l); X( 2)=l; + R0(D,A,B,C,X( 1),12,0xe8c7b756L); HOST_c2l(data,l); X( 3)=l; + R0(C,D,A,B,X( 2),17,0x242070dbL); HOST_c2l(data,l); X( 4)=l; + R0(B,C,D,A,X( 3),22,0xc1bdceeeL); HOST_c2l(data,l); X( 5)=l; + R0(A,B,C,D,X( 4), 7,0xf57c0fafL); HOST_c2l(data,l); X( 6)=l; + R0(D,A,B,C,X( 5),12,0x4787c62aL); HOST_c2l(data,l); X( 7)=l; + R0(C,D,A,B,X( 6),17,0xa8304613L); HOST_c2l(data,l); X( 8)=l; + R0(B,C,D,A,X( 7),22,0xfd469501L); HOST_c2l(data,l); X( 9)=l; + R0(A,B,C,D,X( 8), 7,0x698098d8L); HOST_c2l(data,l); X(10)=l; + R0(D,A,B,C,X( 9),12,0x8b44f7afL); HOST_c2l(data,l); X(11)=l; + R0(C,D,A,B,X(10),17,0xffff5bb1L); HOST_c2l(data,l); X(12)=l; + R0(B,C,D,A,X(11),22,0x895cd7beL); HOST_c2l(data,l); X(13)=l; + R0(A,B,C,D,X(12), 7,0x6b901122L); HOST_c2l(data,l); X(14)=l; + R0(D,A,B,C,X(13),12,0xfd987193L); HOST_c2l(data,l); X(15)=l; + R0(C,D,A,B,X(14),17,0xa679438eL); + R0(B,C,D,A,X(15),22,0x49b40821L); + /* Round 1 */ + R1(A,B,C,D,X( 1), 5,0xf61e2562L); + R1(D,A,B,C,X( 6), 9,0xc040b340L); + R1(C,D,A,B,X(11),14,0x265e5a51L); + R1(B,C,D,A,X( 0),20,0xe9b6c7aaL); + R1(A,B,C,D,X( 5), 5,0xd62f105dL); + R1(D,A,B,C,X(10), 9,0x02441453L); + R1(C,D,A,B,X(15),14,0xd8a1e681L); + R1(B,C,D,A,X( 4),20,0xe7d3fbc8L); + R1(A,B,C,D,X( 9), 5,0x21e1cde6L); + R1(D,A,B,C,X(14), 9,0xc33707d6L); + R1(C,D,A,B,X( 3),14,0xf4d50d87L); + R1(B,C,D,A,X( 8),20,0x455a14edL); + R1(A,B,C,D,X(13), 5,0xa9e3e905L); + R1(D,A,B,C,X( 2), 9,0xfcefa3f8L); + R1(C,D,A,B,X( 7),14,0x676f02d9L); + R1(B,C,D,A,X(12),20,0x8d2a4c8aL); + /* Round 2 */ + R2(A,B,C,D,X( 5), 4,0xfffa3942L); + R2(D,A,B,C,X( 8),11,0x8771f681L); + R2(C,D,A,B,X(11),16,0x6d9d6122L); + R2(B,C,D,A,X(14),23,0xfde5380cL); + R2(A,B,C,D,X( 1), 4,0xa4beea44L); + R2(D,A,B,C,X( 4),11,0x4bdecfa9L); + R2(C,D,A,B,X( 7),16,0xf6bb4b60L); + R2(B,C,D,A,X(10),23,0xbebfbc70L); + R2(A,B,C,D,X(13), 4,0x289b7ec6L); + R2(D,A,B,C,X( 0),11,0xeaa127faL); + R2(C,D,A,B,X( 3),16,0xd4ef3085L); + R2(B,C,D,A,X( 6),23,0x04881d05L); + R2(A,B,C,D,X( 9), 4,0xd9d4d039L); + R2(D,A,B,C,X(12),11,0xe6db99e5L); + R2(C,D,A,B,X(15),16,0x1fa27cf8L); + R2(B,C,D,A,X( 2),23,0xc4ac5665L); + /* Round 3 */ + R3(A,B,C,D,X( 0), 6,0xf4292244L); + R3(D,A,B,C,X( 7),10,0x432aff97L); + R3(C,D,A,B,X(14),15,0xab9423a7L); + R3(B,C,D,A,X( 5),21,0xfc93a039L); + R3(A,B,C,D,X(12), 6,0x655b59c3L); + R3(D,A,B,C,X( 3),10,0x8f0ccc92L); + R3(C,D,A,B,X(10),15,0xffeff47dL); + R3(B,C,D,A,X( 1),21,0x85845dd1L); + R3(A,B,C,D,X( 8), 6,0x6fa87e4fL); + R3(D,A,B,C,X(15),10,0xfe2ce6e0L); + R3(C,D,A,B,X( 6),15,0xa3014314L); + R3(B,C,D,A,X(13),21,0x4e0811a1L); + R3(A,B,C,D,X( 4), 6,0xf7537e82L); + R3(D,A,B,C,X(11),10,0xbd3af235L); + R3(C,D,A,B,X( 2),15,0x2ad7d2bbL); + R3(B,C,D,A,X( 9),21,0xeb86d391L); + + A = c->A += A; + B = c->B += B; + C = c->C += C; + D = c->D += D; + } + } +#endif + + + + // *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - base64 -> binary conversion +#endif + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + + +#define mDNSisspace(x) (x == '\t' || x == '\n' || x == '\v' || x == '\f' || x == '\r' || x == ' ') + +static const char *mDNSstrchr(const char *s, int c) + { + while (1) + { + if (c == *s) return s; + if (!*s) return mDNSNULL; + s++; + } + } + +// skips all whitespace anywhere. +// converts characters, four at a time, starting at (or after) +// src from base - 64 numbers into three 8 bit bytes in the target area. +// it returns the number of data bytes stored at the target, or -1 on error. +// adapted from BIND sources + +mDNSexport mDNSs32 DNSDigest_Base64ToBin(const char *src, mDNSu8 *target, mDNSu32 targsize) + { + int tarindex, state, ch; + const char *pos; + + state = 0; + tarindex = 0; + + while ((ch = *src++) != '\0') { + if (mDNSisspace(ch)) /* Skip whitespace anywhere. */ + continue; + + if (ch == Pad64) + break; + + pos = mDNSstrchr(Base64, ch); + if (pos == 0) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if ((mDNSu32)tarindex >= targsize) + return (-1); + target[tarindex] = (mDNSu8)((pos - Base64) << 2); + } + state = 1; + break; + case 1: + if (target) { + if ((mDNSu32)tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 4; + target[tarindex+1] = (mDNSu8)(((pos - Base64) & 0x0f) << 4); + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + if ((mDNSu32)tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 2; + target[tarindex+1] = (mDNSu8)(((pos - Base64) & 0x03) << 6); + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + if ((mDNSu32)tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + default: + return -1; + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for ((void)mDNSNULL; ch != '\0'; ch = *src++) + if (!mDNSisspace(ch)) + break; + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) + return (-1); + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for ((void)mDNSNULL; ch != '\0'; ch = *src++) + if (!mDNSisspace(ch)) + return (-1); + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target && target[tarindex] != 0) + return (-1); + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + return (tarindex); + } + + + // *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - API exported to mDNS Core +#endif + +// Constants +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5c +#define MD5_LEN 16 + +#define HMAC_MD5_AlgName (*(const domainname*) "\010" "hmac-md5" "\007" "sig-alg" "\003" "reg" "\003" "int") + +// Adapted from Appendix, RFC 2104 +mDNSexport void DNSDigest_ConstructHMACKey(uDNS_AuthInfo *info, const mDNSu8 *key, mDNSu32 len) + { + MD5_CTX k; + mDNSu8 buf[MD5_LEN]; + int i; + + // If key is longer than HMAC_LEN reset it to MD5(key) + if (len > HMAC_LEN) + { + MD5_Init(&k); + MD5_Update(&k, key, len); + MD5_Final(buf, &k); + key = buf; + len = MD5_LEN; + } + + // store key in pads + mDNSPlatformMemZero(info->key.ipad, HMAC_LEN); + mDNSPlatformMemZero(info->key.opad, HMAC_LEN); + mDNSPlatformMemCopy(key, info->key.ipad, len); + mDNSPlatformMemCopy(key, info->key.opad, len); + + // XOR key with ipad and opad values + for (i = 0; i < HMAC_LEN; i++) + { + info->key.ipad[i] ^= HMAC_IPAD; + info->key.opad[i] ^= HMAC_OPAD; + } + + } + +mDNSexport mDNSu8 *DNSDigest_SignMessage(DNSMessage *msg, mDNSu8 **end, mDNSu16 *numAdditionals, uDNS_AuthInfo *info) + { + AuthRecord tsig; + mDNSu8 *countPtr, *rdata; + mDNSu32 utc32; + mDNSu8 utc48[6]; + mDNSu8 digest[MD5_LEN]; + mDNSu8 *ptr = *end; + mDNSu32 len; + mDNSOpaque16 buf; + MD5_CTX c; + + // Init MD5 context, digest inner key pad and message + MD5_Init(&c); + MD5_Update(&c, info->key.ipad, HMAC_LEN); + MD5_Update(&c, (mDNSu8 *)msg, (unsigned long)(*end - (mDNSu8 *)msg)); + + // Construct TSIG RR, digesting variables as apporpriate + mDNSPlatformMemZero(&tsig, sizeof(AuthRecord)); + mDNS_SetupResourceRecord(&tsig, mDNSNULL, 0, kDNSType_TSIG, 0, kDNSRecordTypeKnownUnique, mDNSNULL, mDNSNULL); + + // key name + AssignDomainName(tsig.resrec.name, &info->keyname); + MD5_Update(&c, info->keyname.c, DomainNameLength(&info->keyname)); + + // class + tsig.resrec.rrclass = kDNSQClass_ANY; + buf = mDNSOpaque16fromIntVal(kDNSQClass_ANY); + MD5_Update(&c, buf.b, sizeof(mDNSOpaque16)); + + // ttl + tsig.resrec.rroriginalttl = 0; + MD5_Update(&c, (mDNSu8 *)&tsig.resrec.rroriginalttl, sizeof(tsig.resrec.rroriginalttl)); + + // alg name + AssignDomainName(&tsig.resrec.rdata->u.name, &HMAC_MD5_AlgName); + len = DomainNameLength(&HMAC_MD5_AlgName); + rdata = tsig.resrec.rdata->u.data + len; + MD5_Update(&c, HMAC_MD5_AlgName.c, len); + + // time + // get UTC (universal time), convert to 48-bit unsigned in network byte order + utc32 = (mDNSu32)mDNSPlatformUTC(); + if (utc32 == (unsigned)-1) { LogMsg("ERROR: DNSDigest_SignMessage - mDNSPlatformUTC returned bad time -1"); return mDNSNULL; } + utc48[0] = 0; + utc48[1] = 0; + utc48[2] = (mDNSu8)((utc32 >> 24) & 0xff); + utc48[3] = (mDNSu8)((utc32 >> 16) & 0xff); + utc48[4] = (mDNSu8)((utc32 >> 8) & 0xff); + utc48[5] = (mDNSu8)( utc32 & 0xff); + + mDNSPlatformMemCopy(utc48, rdata, 6); + rdata += 6; + MD5_Update(&c, utc48, 6); + + // 300 sec is fudge recommended in RFC 2485 + rdata[0] = (mDNSu8)((300 >> 8) & 0xff); + rdata[1] = (mDNSu8)( 300 & 0xff); + MD5_Update(&c, rdata, sizeof(mDNSOpaque16)); + rdata += sizeof(mDNSOpaque16); + + // digest error and other data len (both zero) - we'll add them to the rdata later + buf.NotAnInteger = 0; + MD5_Update(&c, buf.b, sizeof(mDNSOpaque16)); // error + MD5_Update(&c, buf.b, sizeof(mDNSOpaque16)); // other data len + + // finish the message & tsig var hash + MD5_Final(digest, &c); + + // perform outer MD5 (outer key pad, inner digest) + MD5_Init(&c); + MD5_Update(&c, info->key.opad, HMAC_LEN); + MD5_Update(&c, digest, MD5_LEN); + MD5_Final(digest, &c); + + // set remaining rdata fields + rdata[0] = (mDNSu8)((MD5_LEN >> 8) & 0xff); + rdata[1] = (mDNSu8)( MD5_LEN & 0xff); + rdata += sizeof(mDNSOpaque16); + mDNSPlatformMemCopy(digest, rdata, MD5_LEN); // MAC + rdata += MD5_LEN; + rdata[0] = msg->h.id.b[0]; // original ID + rdata[1] = msg->h.id.b[1]; + rdata[2] = 0; // no error + rdata[3] = 0; + rdata[4] = 0; // other data len + rdata[5] = 0; + rdata += 6; + + tsig.resrec.rdlength = (mDNSu16)(rdata - tsig.resrec.rdata->u.data); + *end = PutResourceRecordTTLJumbo(msg, ptr, numAdditionals, &tsig.resrec, 0); + if (!*end) { LogMsg("ERROR: DNSDigest_SignMessage - could not put TSIG"); return mDNSNULL; } + + // update num additionals + countPtr = (mDNSu8 *)&msg->h.numAdditionals; // increment (network-byte ordered) header value + *countPtr++ = (mDNSu8)(*numAdditionals >> 8); + *countPtr++ = (mDNSu8)(*numAdditionals & 0xFF); + + return *end; + } + +#ifdef __cplusplus +} +#endif diff --git a/src/mdns/GenLinkedList.c b/src/mdns/GenLinkedList.c new file mode 100755 index 00000000..d9f90334 --- /dev/null +++ b/src/mdns/GenLinkedList.c @@ -0,0 +1,332 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + File: GenLinkedList.c + + Contains: implementation of generic linked lists. + + Version: 1.0 + Tabs: 4 spaces + + Change History (most recent first): + +$Log: GenLinkedList.c,v $ +Revision 1.4 2006/08/14 23:24:56 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.3 2004/04/22 21:14:42 cheshire +Fix comment spelling mistake + +Revision 1.2 2004/02/05 07:41:08 cheshire +Add Log header + +*/ + +#include "GenLinkedList.h" + + +// Return the link pointer contained within element e at offset o. +#define GETLINK( e, o) ( *(void**)((char*) (e) + (o)) ) + +// Assign the link pointer l to element e at offset o. +#define ASSIGNLINK( e, l, o) ( *((void**)((char*) (e) + (o))) = (l)) + + +// GenLinkedList ///////////////////////////////////////////////////////////// + +void InitLinkedList( GenLinkedList *pList, size_t linkOffset) +/* Initialize the block of memory pointed to by pList as a linked list. */ +{ + pList->Head = NULL; + pList->Tail = NULL; + pList->LinkOffset = linkOffset; +} + + +void AddToTail( GenLinkedList *pList, void *elem) +/* Add a linked list element to the tail of the list. */ +{ + if ( pList->Tail) { + ASSIGNLINK( pList->Tail, elem, pList->LinkOffset); + } else + pList->Head = elem; + ASSIGNLINK( elem, NULL, pList->LinkOffset); + + pList->Tail = elem; +} + + +void AddToHead( GenLinkedList *pList, void *elem) +/* Add a linked list element to the head of the list. */ +{ + ASSIGNLINK( elem, pList->Head, pList->LinkOffset); + if ( pList->Tail == NULL) + pList->Tail = elem; + + pList->Head = elem; +} + + +int RemoveFromList( GenLinkedList *pList, void *elem) +/* Remove a linked list element from the list. Return 0 if it was not found. */ +/* If the element is removed, its link will be set to NULL. */ +{ +void *iElem, *lastElem; + + for ( iElem = pList->Head, lastElem = NULL; iElem; iElem = GETLINK( iElem, pList->LinkOffset)) { + if ( iElem == elem) { + if ( lastElem) { // somewhere past the head + ASSIGNLINK( lastElem, GETLINK( elem, pList->LinkOffset), pList->LinkOffset); + } else { // at the head + pList->Head = GETLINK( elem, pList->LinkOffset); + } + if ( pList->Tail == elem) + pList->Tail = lastElem ? lastElem : NULL; + ASSIGNLINK( elem, NULL, pList->LinkOffset); // maybe catch a stale reference bug. + return 1; + } + lastElem = iElem; + } + + return 0; +} + + +int ReplaceElem( GenLinkedList *pList, void *elemInList, void *newElem) +/* Replace an element in the list with a new element, in the same position. */ +{ +void *iElem, *lastElem; + + if ( elemInList == NULL || newElem == NULL) + return 0; + + for ( iElem = pList->Head, lastElem = NULL; iElem; iElem = GETLINK( iElem, pList->LinkOffset)) + { + if ( iElem == elemInList) + { + ASSIGNLINK( newElem, GETLINK( elemInList, pList->LinkOffset), pList->LinkOffset); + if ( lastElem) // somewhere past the head + { + ASSIGNLINK( lastElem, newElem, pList->LinkOffset); + } + else // at the head + { + pList->Head = newElem; + } + if ( pList->Tail == elemInList) + pList->Tail = newElem; + return 1; + } + lastElem = iElem; + } + + return 0; +} + + +// GenDoubleLinkedList ///////////////////////////////////////////////////////// + +void InitDoubleLinkedList( GenDoubleLinkedList *pList, size_t fwdLinkOffset, + size_t backLinkOffset) +/* Initialize the block of memory pointed to by pList as a double linked list. */ +{ + pList->Head = NULL; + pList->Tail = NULL; + pList->FwdLinkOffset = fwdLinkOffset; + pList->BackLinkOffset = backLinkOffset; +} + + +void DLLAddToHead( GenDoubleLinkedList *pList, void *elem) +/* Add a linked list element to the head of the list. */ +{ +void *pNext; + + pNext = pList->Head; + + // fix up the forward links + ASSIGNLINK( elem, pList->Head, pList->FwdLinkOffset); + pList->Head = elem; + + // fix up the backward links + if ( pNext) { + ASSIGNLINK( pNext, elem, pList->BackLinkOffset); + } else + pList->Tail = elem; + ASSIGNLINK( elem, NULL, pList->BackLinkOffset); +} + + +void DLLRemoveFromList( GenDoubleLinkedList *pList, void *elem) +/* Remove a linked list element from the list. */ +/* When the element is removed, its link will be set to NULL. */ +{ +void *pNext, *pPrev; + + pNext = GETLINK( elem, pList->FwdLinkOffset); + pPrev = GETLINK( elem, pList->BackLinkOffset); + + // fix up the forward links + if ( pPrev) + ASSIGNLINK( pPrev, pNext, pList->FwdLinkOffset); + else + pList->Head = pNext; + + // fix up the backward links + if ( pNext) + ASSIGNLINK( pNext, pPrev, pList->BackLinkOffset); + else + pList->Tail = pPrev; + + ASSIGNLINK( elem, NULL, pList->FwdLinkOffset); + ASSIGNLINK( elem, NULL, pList->BackLinkOffset); +} + + +// GenLinkedOffsetList ///////////////////////////////////////////////////// + +// Extract the Next offset from element +#define GETOFFSET( e, o) ( *(size_t*)((char*) (e) + (o)) ) + +static void AssignOffsetLink( void *elem, void *link, size_t linkOffset); + + +static void AssignOffsetLink( void *elem, void *link, size_t linkOffset) +// Assign link to elem as an offset from elem. Assign 0 to elem if link is NULL. +{ + GETOFFSET( elem, linkOffset) = link ? (size_t) link - (size_t) elem : 0; +} + + +void *GetHeadPtr( GenLinkedOffsetList *pList) +/* Return a pointer to the head element of a list, or NULL if none. */ +{ + return pList->Head ? ( (char*) (pList) + pList->Head) : NULL; +} + + +void *GetTailPtr( GenLinkedOffsetList *pList) +/* Return a pointer to the tail element of a list, or NULL if none. */ +{ + return pList->Tail ? ( (char*) (pList) + pList->Tail) : NULL; +} + + +void *GetOffsetLink( GenLinkedOffsetList *pList, void *elem) +/* Return the link pointer contained within element e for pList, or NULL if it is 0. */ +{ +size_t nextOffset; + + nextOffset = GETOFFSET( elem, pList->LinkOffset); + + return nextOffset ? (char*) elem + nextOffset : NULL; +} + + +void InitLinkedOffsetList( GenLinkedOffsetList *pList, size_t linkOffset) +/* Initialize the block of memory pointed to by pList as a linked list. */ +{ + pList->Head = 0; + pList->Tail = 0; + pList->LinkOffset = linkOffset; +} + + +void OffsetAddToTail( GenLinkedOffsetList *pList, void *elem) +/* Add a linked list element to the tail of the list. */ +{ + if ( pList->Tail) { + AssignOffsetLink( GetTailPtr( pList), elem, pList->LinkOffset); + } else + pList->Head = (size_t) elem - (size_t) pList; + AssignOffsetLink( elem, NULL, pList->LinkOffset); + + pList->Tail = (size_t) elem - (size_t) pList; +} + + +void OffsetAddToHead( GenLinkedOffsetList *pList, void *elem) +/* Add a linked list element to the head of the list. */ +{ + AssignOffsetLink( elem, GetHeadPtr( pList), pList->LinkOffset); + if ( pList->Tail == 0) + pList->Tail = (size_t) elem - (size_t) pList; + + pList->Head = (size_t) elem - (size_t) pList; +} + + +int OffsetRemoveFromList( GenLinkedOffsetList *pList, void *elem) +/* Remove a linked list element from the list. Return 0 if it was not found. */ +/* If the element is removed, its link will be set to NULL. */ +{ +void *iElem, *lastElem; + + for ( iElem = GetHeadPtr( pList), lastElem = NULL; iElem; + iElem = GetOffsetLink( pList, iElem)) + { + if ( iElem == elem) { + if ( lastElem) { // somewhere past the head + AssignOffsetLink( lastElem, GetOffsetLink( pList, elem), pList->LinkOffset); + } else { // at the head + iElem = GetOffsetLink( pList, elem); + pList->Head = iElem ? (size_t) iElem - (size_t) pList : 0; + } + if ( GetTailPtr( pList) == elem) + pList->Tail = lastElem ? (size_t) lastElem - (size_t) pList : 0; + AssignOffsetLink( elem, NULL, pList->LinkOffset); // maybe catch a stale reference bug. + return 1; + } + lastElem = iElem; + } + + return 0; +} + + +int OffsetReplaceElem( GenLinkedOffsetList *pList, void *elemInList, void *newElem) +/* Replace an element in the list with a new element, in the same position. */ +{ +void *iElem, *lastElem; + + if ( elemInList == NULL || newElem == NULL) + return 0; + + for ( iElem = GetHeadPtr( pList), lastElem = NULL; iElem; + iElem = GetOffsetLink( pList, iElem)) + { + if ( iElem == elemInList) + { + AssignOffsetLink( newElem, GetOffsetLink( pList, elemInList), pList->LinkOffset); + if ( lastElem) // somewhere past the head + { + AssignOffsetLink( lastElem, newElem, pList->LinkOffset); + } + else // at the head + { + pList->Head = (size_t) newElem - (size_t) pList; + } + if ( GetTailPtr( pList) == elemInList) + pList->Tail = (size_t) newElem - (size_t) pList; + return 1; + } + lastElem = iElem; + } + + return 0; +} + + diff --git a/src/mdns/GenLinkedList.h b/src/mdns/GenLinkedList.h new file mode 100755 index 00000000..4df6e67c --- /dev/null +++ b/src/mdns/GenLinkedList.h @@ -0,0 +1,107 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + File: GenLinkedList.c + + Contains: interface to generic linked lists. + + Version: 1.0 + Tabs: 4 spaces + + Change History (most recent first): + +$Log: GenLinkedList.h,v $ +Revision 1.3 2006/08/14 23:24:56 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.2 2004/02/05 07:41:08 cheshire +Add Log header + +*/ + +#ifndef __GenLinkedList__ +#define __GenLinkedList__ + + +#include + + +struct GenLinkedList +{ + void *Head, + *Tail; + size_t LinkOffset; +}; +typedef struct GenLinkedList GenLinkedList; + + +void InitLinkedList( GenLinkedList *pList, size_t linkOffset); + +void AddToHead( GenLinkedList *pList, void *elem); +void AddToTail( GenLinkedList *pList, void *elem); + +int RemoveFromList( GenLinkedList *pList, void *elem); + +int ReplaceElem( GenLinkedList *pList, void *elemInList, void *newElem); + + + +struct GenDoubleLinkedList +{ + void *Head, + *Tail; + size_t FwdLinkOffset, + BackLinkOffset; +}; +typedef struct GenDoubleLinkedList GenDoubleLinkedList; + + +void InitDoubleLinkedList( GenDoubleLinkedList *pList, size_t fwdLinkOffset, + size_t backLinkOffset); + +void DLLAddToHead( GenDoubleLinkedList *pList, void *elem); + +void DLLRemoveFromList( GenDoubleLinkedList *pList, void *elem); + + + +/* A GenLinkedOffsetList is like a GenLinkedList that stores the *Next field as a signed */ +/* offset from the address of the beginning of the element, rather than as a pointer. */ + +struct GenLinkedOffsetList +{ + size_t Head, + Tail; + size_t LinkOffset; +}; +typedef struct GenLinkedOffsetList GenLinkedOffsetList; + + +void InitLinkedOffsetList( GenLinkedOffsetList *pList, size_t linkOffset); + +void *GetHeadPtr( GenLinkedOffsetList *pList); +void *GetTailPtr( GenLinkedOffsetList *pList); +void *GetOffsetLink( GenLinkedOffsetList *pList, void *elem); + +void OffsetAddToHead( GenLinkedOffsetList *pList, void *elem); +void OffsetAddToTail( GenLinkedOffsetList *pList, void *elem); + +int OffsetRemoveFromList( GenLinkedOffsetList *pList, void *elem); + +int OffsetReplaceElem( GenLinkedOffsetList *pList, void *elemInList, void *newElem); + + +#endif // __GenLinkedList__ diff --git a/src/mdns/Makefile.am b/src/mdns/Makefile.am new file mode 100644 index 00000000..8773796b --- /dev/null +++ b/src/mdns/Makefile.am @@ -0,0 +1,11 @@ +AM_CPPFLAGS = -I.. + +noinst_LTLIBRARIES = libmdns.la +libmdns_la_SOURCES = mDNS.c mDNSPosix.c mDNSPosix.h mDNSUNP.c mDNSUNP.h \ + DNSCommon.h DNSCommon.c uDNS.c uDNS.h mDNSEmbeddedAPI.h \ + mDNSDebug.h DNSDigest.c GenLinkedList.c GenLinkedList.h \ + dns_sd.h mDNSDebug.c + + + + diff --git a/src/mdns/dns_sd.h b/src/mdns/dns_sd.h new file mode 100755 index 00000000..b7eb8a22 --- /dev/null +++ b/src/mdns/dns_sd.h @@ -0,0 +1,1722 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _DNS_SD_H +#define _DNS_SD_H + +#ifdef __cplusplus + extern "C" { +#endif + +/* standard calling convention under Win32 is __stdcall */ +/* Note: When compiling Intel EFI (Extensible Firmware Interface) under MS Visual Studio, the */ +/* _WIN32 symbol is defined by the compiler even though it's NOT compiling code for Windows32 */ +#if defined(_WIN32) && !defined(EFI32) && !defined(EFI64) +#define DNSSD_API __stdcall +#else +#define DNSSD_API +#endif + +/* stdint.h does not exist on FreeBSD 4.x; its types are defined in sys/types.h instead */ +#if defined(__FreeBSD__) && (__FreeBSD__ < 5) +#include + +/* Likewise, on Sun, standard integer types are in sys/types.h */ +#elif defined(__sun__) +#include + +/* EFI does not have stdint.h, or anything else equivalent */ +#elif defined(EFI32) || defined(EFI64) +typedef UINT8 uint8_t; +typedef INT8 int8_t; +typedef UINT16 uint16_t; +typedef INT16 int16_t; +typedef UINT32 uint32_t; +typedef INT32 int32_t; + +/* Windows has its own differences */ +#elif defined(_WIN32) +#include +#define _UNUSED +#define bzero(a, b) memset(a, 0, b) +#ifndef _MSL_STDINT_H +typedef UINT8 uint8_t; +typedef INT8 int8_t; +typedef UINT16 uint16_t; +typedef INT16 int16_t; +typedef UINT32 uint32_t; +typedef INT32 int32_t; +#endif + +/* All other Posix platforms use stdint.h */ +#else +#include +#endif + +/* DNSServiceRef, DNSRecordRef + * + * Opaque internal data types. + * Note: client is responsible for serializing access to these structures if + * they are shared between concurrent threads. + */ + +typedef struct _DNSServiceRef_t *DNSServiceRef; +typedef struct _DNSRecordRef_t *DNSRecordRef; + +/* General flags used in functions defined below */ +enum + { + kDNSServiceFlagsMoreComing = 0x1, + /* MoreComing indicates to a callback that at least one more result is + * queued and will be delivered following immediately after this one. + * Applications should not update their UI to display browse + * results when the MoreComing flag is set, because this would + * result in a great deal of ugly flickering on the screen. + * Applications should instead wait until until MoreComing is not set, + * and then update their UI. + * When MoreComing is not set, that doesn't mean there will be no more + * answers EVER, just that there are no more answers immediately + * available right now at this instant. If more answers become available + * in the future they will be delivered as usual. + */ + + kDNSServiceFlagsAdd = 0x2, + kDNSServiceFlagsDefault = 0x4, + /* Flags for domain enumeration and browse/query reply callbacks. + * "Default" applies only to enumeration and is only valid in + * conjuction with "Add". An enumeration callback with the "Add" + * flag NOT set indicates a "Remove", i.e. the domain is no longer + * valid. + */ + + kDNSServiceFlagsNoAutoRename = 0x8, + /* Flag for specifying renaming behavior on name conflict when registering + * non-shared records. By default, name conflicts are automatically handled + * by renaming the service. NoAutoRename overrides this behavior - with this + * flag set, name conflicts will result in a callback. The NoAutorename flag + * is only valid if a name is explicitly specified when registering a service + * (i.e. the default name is not used.) + */ + + kDNSServiceFlagsShared = 0x10, + kDNSServiceFlagsUnique = 0x20, + /* Flag for registering individual records on a connected + * DNSServiceRef. Shared indicates that there may be multiple records + * with this name on the network (e.g. PTR records). Unique indicates that the + * record's name is to be unique on the network (e.g. SRV records). + */ + + kDNSServiceFlagsBrowseDomains = 0x40, + kDNSServiceFlagsRegistrationDomains = 0x80, + /* Flags for specifying domain enumeration type in DNSServiceEnumerateDomains. + * BrowseDomains enumerates domains recommended for browsing, RegistrationDomains + * enumerates domains recommended for registration. + */ + + kDNSServiceFlagsLongLivedQuery = 0x100, + /* Flag for creating a long-lived unicast query for the DNSServiceQueryRecord call. */ + + kDNSServiceFlagsAllowRemoteQuery = 0x200, + /* Flag for creating a record for which we will answer remote queries + * (queries from hosts more than one hop away; hosts not directly connected to the local link). + */ + + kDNSServiceFlagsForceMulticast = 0x400, + /* Flag for signifying that a query or registration should be performed exclusively via multicast DNS, + * even for a name in a domain (e.g. foo.apple.com.) that would normally imply unicast DNS. + */ + + kDNSServiceFlagsReturnCNAME = 0x800 + /* Flag for returning CNAME records in the DNSServiceQueryRecord call. CNAME records are + * normally followed without indicating to the client that there was a CNAME record. + */ + }; + +/* + * The values for DNS Classes and Types are listed in RFC 1035, and are available + * on every OS in its DNS header file. Unfortunately every OS does not have the + * same header file containing DNS Class and Type constants, and the names of + * the constants are not consistent. For example, BIND 8 uses "T_A", + * BIND 9 uses "ns_t_a", Windows uses "DNS_TYPE_A", etc. + * For this reason, these constants are also listed here, so that code using + * the DNS-SD programming APIs can use these constants, so that the same code + * can compile on all our supported platforms. + */ + +enum + { + kDNSServiceClass_IN = 1 /* Internet */ + }; + +enum + { + kDNSServiceType_A = 1, /* Host address. */ + kDNSServiceType_NS = 2, /* Authoritative server. */ + kDNSServiceType_MD = 3, /* Mail destination. */ + kDNSServiceType_MF = 4, /* Mail forwarder. */ + kDNSServiceType_CNAME = 5, /* Canonical name. */ + kDNSServiceType_SOA = 6, /* Start of authority zone. */ + kDNSServiceType_MB = 7, /* Mailbox domain name. */ + kDNSServiceType_MG = 8, /* Mail group member. */ + kDNSServiceType_MR = 9, /* Mail rename name. */ + kDNSServiceType_NULL = 10, /* Null resource record. */ + kDNSServiceType_WKS = 11, /* Well known service. */ + kDNSServiceType_PTR = 12, /* Domain name pointer. */ + kDNSServiceType_HINFO = 13, /* Host information. */ + kDNSServiceType_MINFO = 14, /* Mailbox information. */ + kDNSServiceType_MX = 15, /* Mail routing information. */ + kDNSServiceType_TXT = 16, /* One or more text strings. */ + kDNSServiceType_RP = 17, /* Responsible person. */ + kDNSServiceType_AFSDB = 18, /* AFS cell database. */ + kDNSServiceType_X25 = 19, /* X_25 calling address. */ + kDNSServiceType_ISDN = 20, /* ISDN calling address. */ + kDNSServiceType_RT = 21, /* Router. */ + kDNSServiceType_NSAP = 22, /* NSAP address. */ + kDNSServiceType_NSAP_PTR = 23, /* Reverse NSAP lookup (deprecated). */ + kDNSServiceType_SIG = 24, /* Security signature. */ + kDNSServiceType_KEY = 25, /* Security key. */ + kDNSServiceType_PX = 26, /* X.400 mail mapping. */ + kDNSServiceType_GPOS = 27, /* Geographical position (withdrawn). */ + kDNSServiceType_AAAA = 28, /* IPv6 Address. */ + kDNSServiceType_LOC = 29, /* Location Information. */ + kDNSServiceType_NXT = 30, /* Next domain (security). */ + kDNSServiceType_EID = 31, /* Endpoint identifier. */ + kDNSServiceType_NIMLOC = 32, /* Nimrod Locator. */ + kDNSServiceType_SRV = 33, /* Server Selection. */ + kDNSServiceType_ATMA = 34, /* ATM Address */ + kDNSServiceType_NAPTR = 35, /* Naming Authority PoinTeR */ + kDNSServiceType_KX = 36, /* Key Exchange */ + kDNSServiceType_CERT = 37, /* Certification record */ + kDNSServiceType_A6 = 38, /* IPv6 Address (deprecated) */ + kDNSServiceType_DNAME = 39, /* Non-terminal DNAME (for IPv6) */ + kDNSServiceType_SINK = 40, /* Kitchen sink (experimentatl) */ + kDNSServiceType_OPT = 41, /* EDNS0 option (meta-RR) */ + kDNSServiceType_TKEY = 249, /* Transaction key */ + kDNSServiceType_TSIG = 250, /* Transaction signature. */ + kDNSServiceType_IXFR = 251, /* Incremental zone transfer. */ + kDNSServiceType_AXFR = 252, /* Transfer zone of authority. */ + kDNSServiceType_MAILB = 253, /* Transfer mailbox records. */ + kDNSServiceType_MAILA = 254, /* Transfer mail agent records. */ + kDNSServiceType_ANY = 255 /* Wildcard match. */ + }; + + +/* possible error code values */ +enum + { + kDNSServiceErr_NoError = 0, + kDNSServiceErr_Unknown = -65537, /* 0xFFFE FFFF */ + kDNSServiceErr_NoSuchName = -65538, + kDNSServiceErr_NoMemory = -65539, + kDNSServiceErr_BadParam = -65540, + kDNSServiceErr_BadReference = -65541, + kDNSServiceErr_BadState = -65542, + kDNSServiceErr_BadFlags = -65543, + kDNSServiceErr_Unsupported = -65544, + kDNSServiceErr_NotInitialized = -65545, + kDNSServiceErr_AlreadyRegistered = -65547, + kDNSServiceErr_NameConflict = -65548, + kDNSServiceErr_Invalid = -65549, + kDNSServiceErr_Firewall = -65550, + kDNSServiceErr_Incompatible = -65551, /* client library incompatible with daemon */ + kDNSServiceErr_BadInterfaceIndex = -65552, + kDNSServiceErr_Refused = -65553, + kDNSServiceErr_NoSuchRecord = -65554, + kDNSServiceErr_NoAuth = -65555, + kDNSServiceErr_NoSuchKey = -65556, + kDNSServiceErr_NATTraversal = -65557, + kDNSServiceErr_DoubleNAT = -65558, + kDNSServiceErr_BadTime = -65559 + /* mDNS Error codes are in the range + * FFFE FF00 (-65792) to FFFE FFFF (-65537) */ + }; + + +/* Maximum length, in bytes, of a service name represented as a */ +/* literal C-String, including the terminating NULL at the end. */ + +#define kDNSServiceMaxServiceName 64 + +/* Maximum length, in bytes, of a domain name represented as an *escaped* C-String */ +/* including the final trailing dot, and the C-String terminating NULL at the end. */ + +#define kDNSServiceMaxDomainName 1005 + +/* + * Notes on DNS Name Escaping + * -- or -- + * "Why is kDNSServiceMaxDomainName 1005, when the maximum legal domain name is 255 bytes?" + * + * All strings used in DNS-SD are UTF-8 strings. + * With few exceptions, most are also escaped using standard DNS escaping rules: + * + * '\\' represents a single literal '\' in the name + * '\.' represents a single literal '.' in the name + * '\ddd', where ddd is a three-digit decimal value from 000 to 255, + * represents a single literal byte with that value. + * A bare unescaped '.' is a label separator, marking a boundary between domain and subdomain. + * + * The exceptions, that do not use escaping, are the routines where the full + * DNS name of a resource is broken, for convenience, into servicename/regtype/domain. + * In these routines, the "servicename" is NOT escaped. It does not need to be, since + * it is, by definition, just a single literal string. Any characters in that string + * represent exactly what they are. The "regtype" portion is, technically speaking, + * escaped, but since legal regtypes are only allowed to contain letters, digits, + * and hyphens, there is nothing to escape, so the issue is moot. The "domain" + * portion is also escaped, though most domains in use on the public Internet + * today, like regtypes, don't contain any characters that need to be escaped. + * As DNS-SD becomes more popular, rich-text domains for service discovery will + * become common, so software should be written to cope with domains with escaping. + * + * The servicename may be up to 63 bytes of UTF-8 text (not counting the C-String + * terminating NULL at the end). The regtype is of the form _service._tcp or + * _service._udp, where the "service" part is 1-14 characters, which may be + * letters, digits, or hyphens. The domain part of the three-part name may be + * any legal domain, providing that the resulting servicename+regtype+domain + * name does not exceed 255 bytes. + * + * For most software, these issues are transparent. When browsing, the discovered + * servicenames should simply be displayed as-is. When resolving, the discovered + * servicename/regtype/domain are simply passed unchanged to DNSServiceResolve(). + * When a DNSServiceResolve() succeeds, the returned fullname is already in + * the correct format to pass to standard system DNS APIs such as res_query(). + * For converting from servicename/regtype/domain to a single properly-escaped + * full DNS name, the helper function DNSServiceConstructFullName() is provided. + * + * The following (highly contrived) example illustrates the escaping process. + * Suppose you have an service called "Dr. Smith\Dr. Johnson", of type "_ftp._tcp" + * in subdomain "4th. Floor" of subdomain "Building 2" of domain "apple.com." + * The full (escaped) DNS name of this service's SRV record would be: + * Dr\.\032Smith\\Dr\.\032Johnson._ftp._tcp.4th\.\032Floor.Building\0322.apple.com. + */ + + +/* + * Constants for specifying an interface index + * + * Specific interface indexes are identified via a 32-bit unsigned integer returned + * by the if_nametoindex() family of calls. + * + * If the client passes 0 for interface index, that means "do the right thing", + * which (at present) means, "if the name is in an mDNS local multicast domain + * (e.g. 'local.', '254.169.in-addr.arpa.', '{8,9,A,B}.E.F.ip6.arpa.') then multicast + * on all applicable interfaces, otherwise send via unicast to the appropriate + * DNS server." Normally, most clients will use 0 for interface index to + * automatically get the default sensible behaviour. + * + * If the client passes a positive interface index, then for multicast names that + * indicates to do the operation only on that one interface. For unicast names the + * interface index is ignored unless kDNSServiceFlagsForceMulticast is also set. + * + * If the client passes kDNSServiceInterfaceIndexLocalOnly when registering + * a service, then that service will be found *only* by other local clients + * on the same machine that are browsing using kDNSServiceInterfaceIndexLocalOnly + * or kDNSServiceInterfaceIndexAny. + * If a client has a 'private' service, accessible only to other processes + * running on the same machine, this allows the client to advertise that service + * in a way such that it does not inadvertently appear in service lists on + * all the other machines on the network. + * + * If the client passes kDNSServiceInterfaceIndexLocalOnly when browsing + * then it will find *all* records registered on that same local machine. + * Clients explicitly wishing to discover *only* LocalOnly services can + * accomplish this by inspecting the interfaceIndex of each service reported + * to their DNSServiceBrowseReply() callback function, and discarding those + * where the interface index is not kDNSServiceInterfaceIndexLocalOnly. + */ + +#define kDNSServiceInterfaceIndexAny 0 +#define kDNSServiceInterfaceIndexLocalOnly ( (uint32_t) -1 ) + + +typedef uint32_t DNSServiceFlags; +typedef int32_t DNSServiceErrorType; + + +/********************************************************************************************* + * + * Unix Domain Socket access, DNSServiceRef deallocation, and data processing functions + * + *********************************************************************************************/ + + +/* DNSServiceRefSockFD() + * + * Access underlying Unix domain socket for an initialized DNSServiceRef. + * The DNS Service Discovery implmementation uses this socket to communicate between + * the client and the mDNSResponder daemon. The application MUST NOT directly read from + * or write to this socket. Access to the socket is provided so that it can be used as a + * run loop source, or in a select() loop: when data is available for reading on the socket, + * DNSServiceProcessResult() should be called, which will extract the daemon's reply from + * the socket, and pass it to the appropriate application callback. By using a run loop or + * select(), results from the daemon can be processed asynchronously. Without using these + * constructs, DNSServiceProcessResult() will block until the response from the daemon arrives. + * The client is responsible for ensuring that the data on the socket is processed in a timely + * fashion - the daemon may terminate its connection with a client that does not clear its + * socket buffer. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls. + * + * return value: The DNSServiceRef's underlying socket descriptor, or -1 on + * error. + */ + +int DNSSD_API DNSServiceRefSockFD(DNSServiceRef sdRef); + + +/* DNSServiceProcessResult() + * + * Read a reply from the daemon, calling the appropriate application callback. This call will + * block until the daemon's response is received. Use DNSServiceRefSockFD() in + * conjunction with a run loop or select() to determine the presence of a response from the + * server before calling this function to process the reply without blocking. Call this function + * at any point if it is acceptable to block until the daemon's response arrives. Note that the + * client is responsible for ensuring that DNSServiceProcessResult() is called whenever there is + * a reply from the daemon - the daemon may terminate its connection with a client that does not + * process the daemon's responses. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls + * that take a callback parameter. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns + * an error code indicating the specific failure that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceProcessResult(DNSServiceRef sdRef); + + +/* DNSServiceRefDeallocate() + * + * Terminate a connection with the daemon and free memory associated with the DNSServiceRef. + * Any services or records registered with this DNSServiceRef will be deregistered. Any + * Browse, Resolve, or Query operations called with this reference will be terminated. + * + * Note: If the reference's underlying socket is used in a run loop or select() call, it should + * be removed BEFORE DNSServiceRefDeallocate() is called, as this function closes the reference's + * socket. + * + * Note: If the reference was initialized with DNSServiceCreateConnection(), any DNSRecordRefs + * created via this reference will be invalidated by this call - the resource records are + * deregistered, and their DNSRecordRefs may not be used in subsequent functions. Similarly, + * if the reference was initialized with DNSServiceRegister, and an extra resource record was + * added to the service via DNSServiceAddRecord(), the DNSRecordRef created by the Add() call + * is invalidated when this function is called - the DNSRecordRef may not be used in subsequent + * functions. + * + * Note: This call is to be used only with the DNSServiceRef defined by this API. It is + * not compatible with dns_service_discovery_ref objects defined in the legacy Mach-based + * DNSServiceDiscovery.h API. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls. + * + */ + +void DNSSD_API DNSServiceRefDeallocate(DNSServiceRef sdRef); + + +/********************************************************************************************* + * + * Domain Enumeration + * + *********************************************************************************************/ + +/* DNSServiceEnumerateDomains() + * + * Asynchronously enumerate domains available for browsing and registration. + * + * The enumeration MUST be cancelled via DNSServiceRefDeallocate() when no more domains + * are to be found. + * + * Note that the names returned are (like all of DNS-SD) UTF-8 strings, + * and are escaped using standard DNS escaping rules. + * (See "Notes on DNS Name Escaping" earlier in this file for more details.) + * A graphical browser displaying a hierarchical tree-structured view should cut + * the names at the bare dots to yield individual labels, then de-escape each + * label according to the escaping rules, and then display the resulting UTF-8 text. + * + * DNSServiceDomainEnumReply Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceEnumerateDomains(). + * + * flags: Possible values are: + * kDNSServiceFlagsMoreComing + * kDNSServiceFlagsAdd + * kDNSServiceFlagsDefault + * + * interfaceIndex: Specifies the interface on which the domain exists. (The index for a given + * interface is determined via the if_nametoindex() family of calls.) + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise indicates + * the failure that occurred (other parameters are undefined if errorCode is nonzero). + * + * replyDomain: The name of the domain. + * + * context: The context pointer passed to DNSServiceEnumerateDomains. + * + */ + +typedef void (DNSSD_API *DNSServiceDomainEnumReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *replyDomain, + void *context + ); + + +/* DNSServiceEnumerateDomains() Parameters: + * + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the enumeration operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Possible values are: + * kDNSServiceFlagsBrowseDomains to enumerate domains recommended for browsing. + * kDNSServiceFlagsRegistrationDomains to enumerate domains recommended + * for registration. + * + * interfaceIndex: If non-zero, specifies the interface on which to look for domains. + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to enumerate domains on + * all interfaces. See "Constants for specifying an interface index" for more details. + * + * callBack: The function to be called when a domain is found or the call asynchronously + * fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on succeses (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is not invoked and the DNSServiceRef + * is not initialized.) + */ + +DNSServiceErrorType DNSSD_API DNSServiceEnumerateDomains + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceDomainEnumReply callBack, + void *context /* may be NULL */ + ); + + +/********************************************************************************************* + * + * Service Registration + * + *********************************************************************************************/ + +/* Register a service that is discovered via Browse() and Resolve() calls. + * + * + * DNSServiceRegisterReply() Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceRegister(). + * + * flags: Currently unused, reserved for future use. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred (including name conflicts, + * if the kDNSServiceFlagsNoAutoRename flag was used when registering.) + * Other parameters are undefined if errorCode is nonzero. + * + * name: The service name registered (if the application did not specify a name in + * DNSServiceRegister(), this indicates what name was automatically chosen). + * + * regtype: The type of service registered, as it was passed to the callout. + * + * domain: The domain on which the service was registered (if the application did not + * specify a domain in DNSServiceRegister(), this indicates the default domain + * on which the service was registered). + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceRegisterReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, + const char *regtype, + const char *domain, + void *context + ); + + +/* DNSServiceRegister() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the registration will remain active indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * interfaceIndex: If non-zero, specifies the interface on which to register the service + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to register on all + * available interfaces. See "Constants for specifying an interface index" for more details. + * + * flags: Indicates the renaming behavior on name conflict (most applications + * will pass 0). See flag definitions above for details. + * + * name: If non-NULL, specifies the service name to be registered. + * Most applications will not specify a name, in which case the computer + * name is used (this name is communicated to the client via the callback). + * If a name is specified, it must be 1-63 bytes of UTF-8 text. + * If the name is longer than 63 bytes it will be automatically truncated + * to a legal length, unless the NoAutoRename flag is set, + * in which case kDNSServiceErr_BadParam will be returned. + * + * regtype: The service type followed by the protocol, separated by a dot + * (e.g. "_ftp._tcp"). The service type must be an underscore, followed + * by 1-14 characters, which may be letters, digits, or hyphens. + * The transport protocol must be "_tcp" or "_udp". New service types + * should be registered at . + * + * domain: If non-NULL, specifies the domain on which to advertise the service. + * Most applications will not specify a domain, instead automatically + * registering in the default domain(s). + * + * host: If non-NULL, specifies the SRV target host name. Most applications + * will not specify a host, instead automatically using the machine's + * default host name(s). Note that specifying a non-NULL host does NOT + * create an address record for that host - the application is responsible + * for ensuring that the appropriate address record exists, or creating it + * via DNSServiceRegisterRecord(). + * + * port: The port, in network byte order, on which the service accepts connections. + * Pass 0 for a "placeholder" service (i.e. a service that will not be discovered + * by browsing, but will cause a name conflict if another client tries to + * register that same name). Most clients will not use placeholder services. + * + * txtLen: The length of the txtRecord, in bytes. Must be zero if the txtRecord is NULL. + * + * txtRecord: The TXT record rdata. A non-NULL txtRecord MUST be a properly formatted DNS + * TXT record, i.e. ... + * Passing NULL for the txtRecord is allowed as a synonym for txtLen=1, txtRecord="", + * i.e. it creates a TXT record of length one containing a single empty string. + * RFC 1035 doesn't allow a TXT record to contain *zero* strings, so a single empty + * string is the smallest legal DNS TXT record. + * As with the other parameters, the DNSServiceRegister call copies the txtRecord + * data; e.g. if you allocated the storage for the txtRecord parameter with malloc() + * then you can safely free that memory right after the DNSServiceRegister call returns. + * + * callBack: The function to be called when the registration completes or asynchronously + * fails. The client MAY pass NULL for the callback - The client will NOT be notified + * of the default values picked on its behalf, and the client will NOT be notified of any + * asynchronous errors (e.g. out of memory errors, etc.) that may prevent the registration + * of the service. The client may NOT pass the NoAutoRename flag if the callback is NULL. + * The client may still deregister the service at any time via DNSServiceRefDeallocate(). + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on succeses (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized.) + */ + +DNSServiceErrorType DNSSD_API DNSServiceRegister + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, /* may be NULL */ + const char *regtype, + const char *domain, /* may be NULL */ + const char *host, /* may be NULL */ + uint16_t port, + uint16_t txtLen, + const void *txtRecord, /* may be NULL */ + DNSServiceRegisterReply callBack, /* may be NULL */ + void *context /* may be NULL */ + ); + + +/* DNSServiceAddRecord() + * + * Add a record to a registered service. The name of the record will be the same as the + * registered service's name. + * The record can later be updated or deregistered by passing the RecordRef initialized + * by this function to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * + * Note that the DNSServiceAddRecord/UpdateRecord/RemoveRecord are *NOT* thread-safe + * with respect to a single DNSServiceRef. If you plan to have multiple threads + * in your program simultaneously add, update, or remove records from the same + * DNSServiceRef, then it's the caller's responsibility to use a mutext lock + * or take similar appropriate precautions to serialize those calls. + * + * + * Parameters; + * + * sdRef: A DNSServiceRef initialized by DNSServiceRegister(). + * + * RecordRef: A pointer to an uninitialized DNSRecordRef. Upon succesfull completion of this + * call, this ref may be passed to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * If the above DNSServiceRef is passed to DNSServiceRefDeallocate(), RecordRef is also + * invalidated and may not be used further. + * + * flags: Currently ignored, reserved for future use. + * + * rrtype: The type of the record (e.g. kDNSServiceType_TXT, kDNSServiceType_SRV, etc) + * + * rdlen: The length, in bytes, of the rdata. + * + * rdata: The raw rdata to be contained in the added resource record. + * + * ttl: The time to live of the resource record, in seconds. Pass 0 to use a default value. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred (the RecordRef is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceAddRecord + ( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint16_t rrtype, + uint16_t rdlen, + const void *rdata, + uint32_t ttl + ); + + +/* DNSServiceUpdateRecord + * + * Update a registered resource record. The record must either be: + * - The primary txt record of a service registered via DNSServiceRegister() + * - A record added to a registered service via DNSServiceAddRecord() + * - An individual record registered by DNSServiceRegisterRecord() + * + * + * Parameters: + * + * sdRef: A DNSServiceRef that was initialized by DNSServiceRegister() + * or DNSServiceCreateConnection(). + * + * RecordRef: A DNSRecordRef initialized by DNSServiceAddRecord, or NULL to update the + * service's primary txt record. + * + * flags: Currently ignored, reserved for future use. + * + * rdlen: The length, in bytes, of the new rdata. + * + * rdata: The new rdata to be contained in the updated resource record. + * + * ttl: The time to live of the updated resource record, in seconds. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceUpdateRecord + ( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, /* may be NULL */ + DNSServiceFlags flags, + uint16_t rdlen, + const void *rdata, + uint32_t ttl + ); + + +/* DNSServiceRemoveRecord + * + * Remove a record previously added to a service record set via DNSServiceAddRecord(), or deregister + * an record registered individually via DNSServiceRegisterRecord(). + * + * Parameters: + * + * sdRef: A DNSServiceRef initialized by DNSServiceRegister() (if the + * record being removed was registered via DNSServiceAddRecord()) or by + * DNSServiceCreateConnection() (if the record being removed was registered via + * DNSServiceRegisterRecord()). + * + * recordRef: A DNSRecordRef initialized by a successful call to DNSServiceAddRecord() + * or DNSServiceRegisterRecord(). + * + * flags: Currently ignored, reserved for future use. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceRemoveRecord + ( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags + ); + + +/********************************************************************************************* + * + * Service Discovery + * + *********************************************************************************************/ + +/* Browse for instances of a service. + * + * + * DNSServiceBrowseReply() Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceBrowse(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and kDNSServiceFlagsAdd. + * See flag definitions for details. + * + * interfaceIndex: The interface on which the service is advertised. This index should + * be passed to DNSServiceResolve() when resolving the service. + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * the errorCode is nonzero. + * + * serviceName: The discovered service name. This name should be displayed to the user, + * and stored for subsequent use in the DNSServiceResolve() call. + * + * regtype: The service type, which is usually (but not always) the same as was passed + * to DNSServiceBrowse(). One case where the discovered service type may + * not be the same as the requested service type is when using subtypes: + * The client may want to browse for only those ftp servers that allow + * anonymous connections. The client will pass the string "_ftp._tcp,_anon" + * to DNSServiceBrowse(), but the type of the service that's discovered + * is simply "_ftp._tcp". The regtype for each discovered service instance + * should be stored along with the name, so that it can be passed to + * DNSServiceResolve() when the service is later resolved. + * + * domain: The domain of the discovered service instance. This may or may not be the + * same as the domain that was passed to DNSServiceBrowse(). The domain for each + * discovered service instance should be stored along with the name, so that + * it can be passed to DNSServiceResolve() when the service is later resolved. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceBrowseReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, + void *context + ); + + +/* DNSServiceBrowse() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the browse operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Currently ignored, reserved for future use. + * + * interfaceIndex: If non-zero, specifies the interface on which to browse for services + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to browse on all available + * interfaces. See "Constants for specifying an interface index" for more details. + * + * regtype: The service type being browsed for followed by the protocol, separated by a + * dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". + * + * domain: If non-NULL, specifies the domain on which to browse for services. + * Most applications will not specify a domain, instead browsing on the + * default domain(s). + * + * callBack: The function to be called when an instance of the service being browsed for + * is found, or if the call asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on succeses (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is not invoked and the DNSServiceRef + * is not initialized.) + */ + +DNSServiceErrorType DNSSD_API DNSServiceBrowse + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *regtype, + const char *domain, /* may be NULL */ + DNSServiceBrowseReply callBack, + void *context /* may be NULL */ + ); + + +/* DNSServiceResolve() + * + * Resolve a service name discovered via DNSServiceBrowse() to a target host name, port number, and + * txt record. + * + * Note: Applications should NOT use DNSServiceResolve() solely for txt record monitoring - use + * DNSServiceQueryRecord() instead, as it is more efficient for this task. + * + * Note: When the desired results have been returned, the client MUST terminate the resolve by calling + * DNSServiceRefDeallocate(). + * + * Note: DNSServiceResolve() behaves correctly for typical services that have a single SRV record + * and a single TXT record. To resolve non-standard services with multiple SRV or TXT records, + * DNSServiceQueryRecord() should be used. + * + * DNSServiceResolveReply Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceResolve(). + * + * flags: Currently unused, reserved for future use. + * + * interfaceIndex: The interface on which the service was resolved. + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * the errorCode is nonzero. + * + * fullname: The full service domain name, in the form ... + * (This name is escaped following standard DNS rules, making it suitable for + * passing to standard system DNS APIs such as res_query(), or to the + * special-purpose functions included in this API that take fullname parameters. + * See "Notes on DNS Name Escaping" earlier in this file for more details.) + * + * hosttarget: The target hostname of the machine providing the service. This name can + * be passed to functions like gethostbyname() to identify the host's IP address. + * + * port: The port, in network byte order, on which connections are accepted for this service. + * + * txtLen: The length of the txt record, in bytes. + * + * txtRecord: The service's primary txt record, in standard txt record format. + * + * context: The context pointer that was passed to the callout. + * + * NOTE: In earlier versions of this header file, the txtRecord parameter was declared "const char *" + * This is incorrect, since it contains length bytes which are values in the range 0 to 255, not -128 to +127. + * Depending on your compiler settings, this change may cause signed/unsigned mismatch warnings. + * These should be fixed by updating your own callback function definition to match the corrected + * function signature using "const unsigned char *txtRecord". Making this change may also fix inadvertent + * bugs in your callback function, where it could have incorrectly interpreted a length byte with value 250 + * as being -6 instead, with various bad consequences ranging from incorrect operation to software crashes. + * If you need to maintain portable code that will compile cleanly with both the old and new versions of + * this header file, you should update your callback function definition to use the correct unsigned value, + * and then in the place where you pass your callback function to DNSServiceResolve(), use a cast to eliminate + * the compiler warning, e.g.: + * DNSServiceResolve(sd, flags, index, name, regtype, domain, (DNSServiceResolveReply)MyCallback, context); + * This will ensure that your code compiles cleanly without warnings (and more importantly, works correctly) + * with both the old header and with the new corrected version. + * + */ + +typedef void (DNSSD_API *DNSServiceResolveReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + const char *hosttarget, + uint16_t port, + uint16_t txtLen, + const unsigned char *txtRecord, + void *context + ); + + +/* DNSServiceResolve() Parameters + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the resolve operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Currently ignored, reserved for future use. + * + * interfaceIndex: The interface on which to resolve the service. If this resolve call is + * as a result of a currently active DNSServiceBrowse() operation, then the + * interfaceIndex should be the index reported in the DNSServiceBrowseReply + * callback. If this resolve call is using information previously saved + * (e.g. in a preference file) for later use, then use interfaceIndex 0, because + * the desired service may now be reachable via a different physical interface. + * See "Constants for specifying an interface index" for more details. + * + * name: The name of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * regtype: The type of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * domain: The domain of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on succeses (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized.) + */ + +DNSServiceErrorType DNSSD_API DNSServiceResolve + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + DNSServiceResolveReply callBack, + void *context /* may be NULL */ + ); + + +/********************************************************************************************* + * + * Special Purpose Calls (most applications will not use these) + * + *********************************************************************************************/ + +/* DNSServiceCreateConnection() + * + * Create a connection to the daemon allowing efficient registration of + * multiple individual records. + * + * + * Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. Deallocating + * the reference (via DNSServiceRefDeallocate()) severs the + * connection and deregisters all records registered on this connection. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns + * an error code indicating the specific failure that occurred (in which + * case the DNSServiceRef is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceCreateConnection(DNSServiceRef *sdRef); + + +/* DNSServiceRegisterRecord + * + * Register an individual resource record on a connected DNSServiceRef. + * + * Note that name conflicts occurring for records registered via this call must be handled + * by the client in the callback. + * + * + * DNSServiceRegisterRecordReply() parameters: + * + * sdRef: The connected DNSServiceRef initialized by + * DNSServiceCreateConnection(). + * + * RecordRef: The DNSRecordRef initialized by DNSServiceRegisterRecord(). If the above + * DNSServiceRef is passed to DNSServiceRefDeallocate(), this DNSRecordRef is + * invalidated, and may not be used further. + * + * flags: Currently unused, reserved for future use. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred (including name conflicts.) + * Other parameters are undefined if errorCode is nonzero. + * + * context: The context pointer that was passed to the callout. + * + */ + + typedef void (DNSSD_API *DNSServiceRegisterRecordReply) + ( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + void *context + ); + + +/* DNSServiceRegisterRecord() Parameters: + * + * sdRef: A DNSServiceRef initialized by DNSServiceCreateConnection(). + * + * RecordRef: A pointer to an uninitialized DNSRecordRef. Upon succesfull completion of this + * call, this ref may be passed to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * (To deregister ALL records registered on a single connected DNSServiceRef + * and deallocate each of their corresponding DNSServiceRecordRefs, call + * DNSServiceRefDealloocate()). + * + * flags: Possible values are kDNSServiceFlagsShared or kDNSServiceFlagsUnique + * (see flag type definitions for details). + * + * interfaceIndex: If non-zero, specifies the interface on which to register the record + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Passing 0 causes the record to be registered on all interfaces. + * See "Constants for specifying an interface index" for more details. + * + * fullname: The full domain name of the resource record. + * + * rrtype: The numerical type of the resource record (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN) + * + * rdlen: Length, in bytes, of the rdata. + * + * rdata: A pointer to the raw rdata, as it is to appear in the DNS record. + * + * ttl: The time to live of the resource record, in seconds. Pass 0 to use a default value. + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails (e.g. because of a name conflict.) + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on succeses (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSRecordRef is + * not initialized.) + */ + +DNSServiceErrorType DNSSD_API DNSServiceRegisterRecord + ( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + DNSServiceRegisterRecordReply callBack, + void *context /* may be NULL */ + ); + + +/* DNSServiceQueryRecord + * + * Query for an arbitrary DNS record. + * + * + * DNSServiceQueryRecordReply() Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceQueryRecord(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and + * kDNSServiceFlagsAdd. The Add flag is NOT set for PTR records + * with a ttl of 0, i.e. "Remove" events. + * + * interfaceIndex: The interface on which the query was resolved (the index for a given + * interface is determined via the if_nametoindex() family of calls). + * See "Constants for specifying an interface index" for more details. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * errorCode is nonzero. + * + * fullname: The resource record's full domain name. + * + * rrtype: The resource record's type (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * rdlen: The length, in bytes, of the resource record rdata. + * + * rdata: The raw rdata of the resource record. + * + * ttl: The resource record's time to live, in seconds. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceQueryRecordReply) + ( + DNSServiceRef DNSServiceRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + void *context + ); + + +/* DNSServiceQueryRecord() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the query operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Pass kDNSServiceFlagsLongLivedQuery to create a "long-lived" unicast + * query in a non-local domain. Without setting this flag, unicast queries + * will be one-shot - that is, only answers available at the time of the call + * will be returned. By setting this flag, answers (including Add and Remove + * events) that become available after the initial call is made will generate + * callbacks. This flag has no effect on link-local multicast queries. + * + * interfaceIndex: If non-zero, specifies the interface on which to issue the query + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Passing 0 causes the name to be queried for on all + * interfaces. See "Constants for specifying an interface index" for more details. + * + * fullname: The full domain name of the resource record to be queried for. + * + * rrtype: The numerical type of the resource record to be queried for + * (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on succeses (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized.) + */ + +DNSServiceErrorType DNSSD_API DNSServiceQueryRecord + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + DNSServiceQueryRecordReply callBack, + void *context /* may be NULL */ + ); + + +/* DNSServiceReconfirmRecord + * + * Instruct the daemon to verify the validity of a resource record that appears to + * be out of date (e.g. because tcp connection to a service's target failed.) + * Causes the record to be flushed from the daemon's cache (as well as all other + * daemons' caches on the network) if the record is determined to be invalid. + * + * Parameters: + * + * flags: Currently unused, reserved for future use. + * + * interfaceIndex: If non-zero, specifies the interface of the record in question. + * Passing 0 causes all instances of this record to be reconfirmed. + * + * fullname: The resource record's full domain name. + * + * rrtype: The resource record's type (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * rdlen: The length, in bytes, of the resource record rdata. + * + * rdata: The raw rdata of the resource record. + * + */ + +DNSServiceErrorType DNSSD_API DNSServiceReconfirmRecord + ( + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata + ); + + +/********************************************************************************************* + * + * General Utility Functions + * + *********************************************************************************************/ + +/* DNSServiceConstructFullName() + * + * Concatenate a three-part domain name (as returned by the above callbacks) into a + * properly-escaped full domain name. Note that callbacks in the above functions ALREADY ESCAPE + * strings where necessary. + * + * Parameters: + * + * fullName: A pointer to a buffer that where the resulting full domain name is to be written. + * The buffer must be kDNSServiceMaxDomainName (1005) bytes in length to + * accommodate the longest legal domain name without buffer overrun. + * + * service: The service name - any dots or backslashes must NOT be escaped. + * May be NULL (to construct a PTR record name, e.g. + * "_ftp._tcp.apple.com."). + * + * regtype: The service type followed by the protocol, separated by a dot + * (e.g. "_ftp._tcp"). + * + * domain: The domain name, e.g. "apple.com.". Literal dots or backslashes, + * if any, must be escaped, e.g. "1st\. Floor.apple.com." + * + * return value: Returns 0 on success, -1 on error. + * + */ + +int DNSSD_API DNSServiceConstructFullName + ( + char *fullName, + const char *service, /* may be NULL */ + const char *regtype, + const char *domain + ); + + +/********************************************************************************************* + * + * TXT Record Construction Functions + * + *********************************************************************************************/ + +/* + * A typical calling sequence for TXT record construction is something like: + * + * Client allocates storage for TXTRecord data (e.g. declare buffer on the stack) + * TXTRecordCreate(); + * TXTRecordSetValue(); + * TXTRecordSetValue(); + * TXTRecordSetValue(); + * ... + * DNSServiceRegister( ... TXTRecordGetLength(), TXTRecordGetBytesPtr() ... ); + * TXTRecordDeallocate(); + * Explicitly deallocate storage for TXTRecord data (if not allocated on the stack) + */ + + +/* TXTRecordRef + * + * Opaque internal data type. + * Note: Represents a DNS-SD TXT record. + */ + +typedef union _TXTRecordRef_t { char PrivateData[16]; char *ForceNaturalAlignment; } TXTRecordRef; + + +/* TXTRecordCreate() + * + * Creates a new empty TXTRecordRef referencing the specified storage. + * + * If the buffer parameter is NULL, or the specified storage size is not + * large enough to hold a key subsequently added using TXTRecordSetValue(), + * then additional memory will be added as needed using malloc(). + * + * On some platforms, when memory is low, malloc() may fail. In this + * case, TXTRecordSetValue() will return kDNSServiceErr_NoMemory, and this + * error condition will need to be handled as appropriate by the caller. + * + * You can avoid the need to handle this error condition if you ensure + * that the storage you initially provide is large enough to hold all + * the key/value pairs that are to be added to the record. + * The caller can precompute the exact length required for all of the + * key/value pairs to be added, or simply provide a fixed-sized buffer + * known in advance to be large enough. + * A no-value (key-only) key requires (1 + key length) bytes. + * A key with empty value requires (1 + key length + 1) bytes. + * A key with non-empty value requires (1 + key length + 1 + value length). + * For most applications, DNS-SD TXT records are generally + * less than 100 bytes, so in most cases a simple fixed-sized + * 256-byte buffer will be more than sufficient. + * Recommended size limits for DNS-SD TXT Records are discussed in + * + * + * Note: When passing parameters to and from these TXT record APIs, + * the key name does not include the '=' character. The '=' character + * is the separator between the key and value in the on-the-wire + * packet format; it is not part of either the key or the value. + * + * txtRecord: A pointer to an uninitialized TXTRecordRef. + * + * bufferLen: The size of the storage provided in the "buffer" parameter. + * + * buffer: Optional caller-supplied storage used to hold the TXTRecord data. + * This storage must remain valid for as long as + * the TXTRecordRef. + */ + +void DNSSD_API TXTRecordCreate + ( + TXTRecordRef *txtRecord, + uint16_t bufferLen, + void *buffer + ); + + +/* TXTRecordDeallocate() + * + * Releases any resources allocated in the course of preparing a TXT Record + * using TXTRecordCreate()/TXTRecordSetValue()/TXTRecordRemoveValue(). + * Ownership of the buffer provided in TXTRecordCreate() returns to the client. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + */ + +void DNSSD_API TXTRecordDeallocate + ( + TXTRecordRef *txtRecord + ); + + +/* TXTRecordSetValue() + * + * Adds a key (optionally with value) to a TXTRecordRef. If the "key" already + * exists in the TXTRecordRef, then the current value will be replaced with + * the new value. + * Keys may exist in four states with respect to a given TXT record: + * - Absent (key does not appear at all) + * - Present with no value ("key" appears alone) + * - Present with empty value ("key=" appears in TXT record) + * - Present with non-empty value ("key=value" appears in TXT record) + * For more details refer to "Data Syntax for DNS-SD TXT Records" in + * + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * key: A null-terminated string which only contains printable ASCII + * values (0x20-0x7E), excluding '=' (0x3D). Keys should be + * 8 characters or less (not counting the terminating null). + * + * valueSize: The size of the value. + * + * value: Any binary value. For values that represent + * textual data, UTF-8 is STRONGLY recommended. + * For values that represent textual data, valueSize + * should NOT include the terminating null (if any) + * at the end of the string. + * If NULL, then "key" will be added with no value. + * If non-NULL but valueSize is zero, then "key=" will be + * added with empty value. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_Invalid if the "key" string contains + * illegal characters. + * Returns kDNSServiceErr_NoMemory if adding this key would + * exceed the available storage. + */ + +DNSServiceErrorType DNSSD_API TXTRecordSetValue + ( + TXTRecordRef *txtRecord, + const char *key, + uint8_t valueSize, /* may be zero */ + const void *value /* may be NULL */ + ); + + +/* TXTRecordRemoveValue() + * + * Removes a key from a TXTRecordRef. The "key" must be an + * ASCII string which exists in the TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * key: A key name which exists in the TXTRecordRef. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_NoSuchKey if the "key" does not + * exist in the TXTRecordRef. + */ + +DNSServiceErrorType DNSSD_API TXTRecordRemoveValue + ( + TXTRecordRef *txtRecord, + const char *key + ); + + +/* TXTRecordGetLength() + * + * Allows you to determine the length of the raw bytes within a TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * return value: Returns the size of the raw bytes inside a TXTRecordRef + * which you can pass directly to DNSServiceRegister() or + * to DNSServiceUpdateRecord(). + * Returns 0 if the TXTRecordRef is empty. + */ + +uint16_t DNSSD_API TXTRecordGetLength + ( + const TXTRecordRef *txtRecord + ); + + +/* TXTRecordGetBytesPtr() + * + * Allows you to retrieve a pointer to the raw bytes within a TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * return value: Returns a pointer to the raw bytes inside the TXTRecordRef + * which you can pass directly to DNSServiceRegister() or + * to DNSServiceUpdateRecord(). + */ + +const void * DNSSD_API TXTRecordGetBytesPtr + ( + const TXTRecordRef *txtRecord + ); + + +/********************************************************************************************* + * + * TXT Record Parsing Functions + * + *********************************************************************************************/ + +/* + * A typical calling sequence for TXT record parsing is something like: + * + * Receive TXT record data in DNSServiceResolve() callback + * if (TXTRecordContainsKey(txtLen, txtRecord, "key")) then do something + * val1ptr = TXTRecordGetValuePtr(txtLen, txtRecord, "key1", &len1); + * val2ptr = TXTRecordGetValuePtr(txtLen, txtRecord, "key2", &len2); + * ... + * bcopy(val1ptr, myval1, len1); + * bcopy(val2ptr, myval2, len2); + * ... + * return; + * + * If you wish to retain the values after return from the DNSServiceResolve() + * callback, then you need to copy the data to your own storage using bcopy() + * or similar, as shown in the example above. + * + * If for some reason you need to parse a TXT record you built yourself + * using the TXT record construction functions above, then you can do + * that using TXTRecordGetLength and TXTRecordGetBytesPtr calls: + * TXTRecordGetValue(TXTRecordGetLength(x), TXTRecordGetBytesPtr(x), key, &len); + * + * Most applications only fetch keys they know about from a TXT record and + * ignore the rest. + * However, some debugging tools wish to fetch and display all keys. + * To do that, use the TXTRecordGetCount() and TXTRecordGetItemAtIndex() calls. + */ + +/* TXTRecordContainsKey() + * + * Allows you to determine if a given TXT Record contains a specified key. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * key: A null-terminated ASCII string containing the key name. + * + * return value: Returns 1 if the TXT Record contains the specified key. + * Otherwise, it returns 0. + */ + +int DNSSD_API TXTRecordContainsKey + ( + uint16_t txtLen, + const void *txtRecord, + const char *key + ); + + +/* TXTRecordGetValuePtr() + * + * Allows you to retrieve the value for a given key from a TXT Record. + * + * txtLen: The size of the received TXT Record + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * key: A null-terminated ASCII string containing the key name. + * + * valueLen: On output, will be set to the size of the "value" data. + * + * return value: Returns NULL if the key does not exist in this TXT record, + * or exists with no value (to differentiate between + * these two cases use TXTRecordContainsKey()). + * Returns pointer to location within TXT Record bytes + * if the key exists with empty or non-empty value. + * For empty value, valueLen will be zero. + * For non-empty value, valueLen will be length of value data. + */ + +const void * DNSSD_API TXTRecordGetValuePtr + ( + uint16_t txtLen, + const void *txtRecord, + const char *key, + uint8_t *valueLen + ); + + +/* TXTRecordGetCount() + * + * Returns the number of keys stored in the TXT Record. The count + * can be used with TXTRecordGetItemAtIndex() to iterate through the keys. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * return value: Returns the total number of keys in the TXT Record. + * + */ + +uint16_t DNSSD_API TXTRecordGetCount + ( + uint16_t txtLen, + const void *txtRecord + ); + + +/* TXTRecordGetItemAtIndex() + * + * Allows you to retrieve a key name and value pointer, given an index into + * a TXT Record. Legal index values range from zero to TXTRecordGetCount()-1. + * It's also possible to iterate through keys in a TXT record by simply + * calling TXTRecordGetItemAtIndex() repeatedly, beginning with index zero + * and increasing until TXTRecordGetItemAtIndex() returns kDNSServiceErr_Invalid. + * + * On return: + * For keys with no value, *value is set to NULL and *valueLen is zero. + * For keys with empty value, *value is non-NULL and *valueLen is zero. + * For keys with non-empty value, *value is non-NULL and *valueLen is non-zero. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * index: An index into the TXT Record. + * + * keyBufLen: The size of the string buffer being supplied. + * + * key: A string buffer used to store the key name. + * On return, the buffer contains a null-terminated C string + * giving the key name. DNS-SD TXT keys are usually + * 8 characters or less. To hold the maximum possible + * key name, the buffer should be 256 bytes long. + * + * valueLen: On output, will be set to the size of the "value" data. + * + * value: On output, *value is set to point to location within TXT + * Record bytes that holds the value data. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_NoMemory if keyBufLen is too short. + * Returns kDNSServiceErr_Invalid if index is greater than + * TXTRecordGetCount()-1. + */ + +DNSServiceErrorType DNSSD_API TXTRecordGetItemAtIndex + ( + uint16_t txtLen, + const void *txtRecord, + uint16_t index, + uint16_t keyBufLen, + char *key, + uint8_t *valueLen, + const void **value + ); + +#ifdef __APPLE_API_PRIVATE + +/* + * Mac OS X specific functionality + * 3rd party clients of this API should not depend on future support or availability of this routine + */ + +/* DNSServiceSetDefaultDomainForUser() + * + * Set the default domain for the caller's UID. Future browse and registration + * calls by this user that do not specify an explicit domain will browse and + * register in this wide-area domain in addition to .local. In addition, this + * domain will be returned as a Browse domain via domain enumeration calls. + * + * + * Parameters: + * + * flags: Pass kDNSServiceFlagsAdd to add a domain for a user. Call without + * this flag set to clear a previously added domain. + * + * domain: The domain to be used for the caller's UID. + * + * return value: Returns kDNSServiceErr_NoError on succeses, otherwise returns + * an error code indicating the error that occurred + */ + +DNSServiceErrorType DNSSD_API DNSServiceSetDefaultDomainForUser + ( + DNSServiceFlags flags, + const char *domain + ); + +#endif //__APPLE_API_PRIVATE + +// Some C compiler cleverness. We can make the compiler check certain things for us, +// and report errors at compile-time if anything is wrong. The usual way to do this would +// be to use a run-time "if" statement or the conventional run-time "assert" mechanism, 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 DNS_SD_CompileTimeAssertionChecks + { + char assert0[(sizeof(union _TXTRecordRef_t) == 16) ? 1 : -1]; + }; + +#ifdef __cplusplus + } +#endif + +#endif /* _DNS_SD_H */ diff --git a/src/mdns/mDNS.c b/src/mdns/mDNS.c new file mode 100755 index 00000000..4eb87198 --- /dev/null +++ b/src/mdns/mDNS.c @@ -0,0 +1,7360 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2006 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This code is completely 100% portable C. It does not depend on any external header files + * from outside the mDNS project -- all the types it expects to find are defined right here. + * + * The previous point is very important: This file does not depend on any external + * header files. It should 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: mDNS.c,v $ +Revision 1.537.2.1 2006/08/29 06:24:22 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.537 2006/03/19 02:00:07 cheshire + Improve logic for delaying packets after repeated interface transitions + +Revision 1.536 2006/03/08 23:29:53 cheshire + Improve "Service Renamed" log message + +Revision 1.535 2006/03/02 20:41:17 cheshire + After record update, old record sometimes remains in cache +Minor code tidying and comments to reduce the risk of similar programming errors in future + +Revision 1.534 2006/03/02 03:25:46 cheshire + After record update, old record sometimes remains in cache +Code to harmonize RRSet TTLs was inadvertently rescuing expiring records + +Revision 1.533 2006/02/26 00:54:41 cheshire +Fixes to avoid code generation warning/error on FreeBSD 7 + +Revision 1.532 2005/12/02 20:24:36 cheshire + Adjust cutoff time for KA list by one second + +Revision 1.531 2005/12/02 19:05:42 cheshire +Tidy up constants + +Revision 1.530 2005/11/07 01:49:48 cheshire +For consistency, use NonZeroTime() function instead of ?: expression + +Revision 1.529 2005/10/25 23:42:24 cheshire + Error in ResolveSimultaneousProbe() when type or class don't match +Changed switch statement to an "if" + +Revision 1.528 2005/10/25 23:34:22 cheshire + RequireGoodbye state not set/respected sometimes when machine going to sleep + +Revision 1.527 2005/10/25 22:43:59 cheshire +Add clarifying comments + +Revision 1.526 2005/10/20 00:10:33 cheshire + Add check to avoid crashing NAT gateways that have buggy DNS relay code + +Revision 1.525 2005/09/24 00:47:17 cheshire +Fix comment typos + +Revision 1.524 2005/09/16 21:06:49 cheshire +Use mDNS_TimeNow_NoLock macro, instead of writing "mDNSPlatformRawTime() + m->timenow_adjust" all over the place + +Revision 1.523 2005/03/21 00:33:51 shersche + Fix build warnings on Win32 platform + +Revision 1.522 2005/03/04 21:48:12 cheshire + Fractional time rounded down instead of up on platforms with coarse clock granularity + +Revision 1.521 2005/02/25 04:21:00 cheshire + mDNS -F returns the same domain multiple times with different casing + +Revision 1.520 2005/02/16 01:14:11 cheshire +Convert RR Cache LogOperation() calls to debugf() + +Revision 1.519 2005/02/15 01:57:20 cheshire +When setting "q->LastQTxTime = m->timenow", must also clear q->RecentAnswerPkts to zero + +Revision 1.518 2005/02/10 22:35:17 cheshire + Update name + +Revision 1.517 2005/02/03 00:21:21 cheshire +Update comments about BIND named and zero-length TXT records + +Revision 1.516 2005/01/28 06:06:32 cheshire +Update comment + +Revision 1.515 2005/01/27 00:21:49 cheshire + Remove mDNSResponder sleep/wake syslog message + +Revision 1.514 2005/01/21 01:33:45 cheshire + Shutdown time regression: mDNSResponder not responding to SIGTERM + +Revision 1.513 2005/01/21 00:07:54 cheshire + Infinite loop when the same service is registered twice, and then suffers a name conflict + +Revision 1.512 2005/01/20 00:37:45 cheshire + mDNSResponder crashed in mDNSCoreReceiveResponse +Take care not to recycle records while they are on the CacheFlushRecords list + +Revision 1.511 2005/01/19 22:48:53 cheshire + Handle services with subtypes correctly when doing mDNS_RenameAndReregisterService() + +Revision 1.510 2005/01/19 03:12:45 cheshire +Move LocalRecordReady() macro from mDNS.c to DNSCommon.h + +Revision 1.509 2005/01/19 03:08:49 cheshire + CPU Spin in mDNSResponder +Log messages to help catch and report CPU spins + +Revision 1.508 2005/01/18 18:56:32 cheshire + QU responses not promoted to multicast responses when appropriate + +Revision 1.507 2005/01/18 01:12:07 cheshire + Logging into VPN causes mDNSResponder to reissue multicast probes + +Revision 1.506 2005/01/17 23:28:53 cheshire +Fix compile error + +Revision 1.505 2005/01/11 02:02:56 shersche +Move variable declaration to the beginning of statement block + +Revision 1.504 2004/12/20 20:24:35 cheshire + Network efficiency: Don't keep polling if we have at least one unique-type answer + +Revision 1.503 2004/12/20 18:41:47 cheshire + Low memory support: Provide answers even when we don't have cache space + +Revision 1.502 2004/12/20 18:04:08 cheshire + For now, don't put standard wide-area unicast responses in our main cache + +Revision 1.501 2004/12/19 23:50:18 cheshire + kDNSServiceInterfaceIndexLocalOnly should return all local records +Don't show "No active interface to send" messages for kDNSServiceInterfaceIndexLocalOnly services + +Revision 1.500 2004/12/18 03:13:46 cheshire + kDNSServiceInterfaceIndexLocalOnly should return all local records + +Revision 1.499 2004/12/17 23:37:45 cheshire + Guard against repeating wireless dissociation/re-association +(and other repetitive configuration changes) + +Revision 1.498 2004/12/17 05:25:46 cheshire + Shorten DNS-SD queries to avoid NAT bugs + +Revision 1.497 2004/12/17 03:20:58 cheshire + Don't send unicast replies we know will be ignored + +Revision 1.496 2004/12/16 22:18:26 cheshire +Make AddressIsLocalSubnet() a little more selective -- ignore point-to-point interfaces + +Revision 1.495 2004/12/16 21:27:37 ksekar +Fixed build failures when compiled with verbose debugging messages + +Revision 1.494 2004/12/16 20:46:56 cheshire +Fix compiler warnings + +Revision 1.493 2004/12/16 20:13:00 cheshire + Cache memory management improvements + +Revision 1.492 2004/12/16 08:03:24 shersche +Fix compilation error when UNICAST_DISABLED is set + +Revision 1.491 2004/12/11 01:52:11 cheshire + Support kDNSServiceFlagsAllowRemoteQuery for registering services too + +Revision 1.490 2004/12/10 20:06:25 cheshire + Reduce egregious stack space usage +Reduced SendDelayedUnicastResponse() stack frame from 9K to 112 bytes + +Revision 1.489 2004/12/10 20:03:43 cheshire + Reduce egregious stack space usage +Reduced mDNSCoreReceiveQuery() stack frame from 9K to 144 bytes + +Revision 1.488 2004/12/10 19:50:41 cheshire + Reduce egregious stack space usage +Reduced SendResponses() stack frame from 9K to 176 bytes + +Revision 1.487 2004/12/10 19:39:13 cheshire + Reduce egregious stack space usage +Reduced SendQueries() stack frame from 18K to 112 bytes + +Revision 1.486 2004/12/10 14:16:17 cheshire + Relax update rate limiting +We now allow an average rate of ten updates per minute. +Updates in excess of that are rate limited, but more gently than before. + +Revision 1.485 2004/12/10 02:09:24 cheshire + Modify default TTLs + +Revision 1.484 2004/12/09 03:15:40 ksekar + use _legacy instead of _default to find "empty string" browse domains + +Revision 1.483 2004/12/07 23:00:14 ksekar + DNSServiceRegisterRecord() can crash on deregistration: +Call RecordProbeFailure even if there is no record callback + +Revision 1.482 2004/12/07 22:49:06 cheshire + BIND doesn't allow zero-length TXT records + +Revision 1.481 2004/12/07 21:26:04 ksekar + DNSServiceRegisterRecord() can crash on deregistration + +Revision 1.480 2004/12/07 20:42:33 cheshire +Add explicit context parameter to mDNS_RemoveRecordFromService() + +Revision 1.479 2004/12/07 17:50:49 ksekar + BIND doesn't allow zero-length TXT records + +Revision 1.478 2004/12/06 21:15:22 ksekar + mDNSResponder crashed in CheckServiceRegistrations + +Revision 1.477 2004/12/04 02:12:45 cheshire + mDNSResponder puts LargeCacheRecord on the stack + +Revision 1.476 2004/11/29 23:34:31 cheshire +On platforms with coarse time resolutions, ORing time values with one to ensure they are non-zero +is crude, and effectively halves the time resolution. The more selective NonZeroTime() function +only nudges the time value to 1 if the interval calculation happens to result in the value zero. + +Revision 1.475 2004/11/29 23:13:31 cheshire + All unique records in a set should have the cache flush bit set +Additional check: Make sure we don't unnecessarily send packets containing only additionals. +(This could occur with multi-packet KA lists, if the answer and additionals were marked +by the query packet, and then the answer were later suppressed in a subsequent KA packet.) + +Revision 1.474 2004/11/29 17:18:12 cheshire +Remove "Unknown DNS packet type" message for update responses + +Revision 1.473 2004/11/25 01:57:52 cheshire + All unique records in a set should have the cache flush bit set + +Revision 1.472 2004/11/25 01:28:09 cheshire + Need to implement random delay for 'QU' unicast replies (and set cache flush bit too) + +Revision 1.471 2004/11/25 01:10:13 cheshire +Move code to add additional records to a subroutine called AddAdditionalsToResponseList() + +Revision 1.470 2004/11/24 21:54:44 cheshire + mDNSCore not receiving unicast responses properly + +Revision 1.469 2004/11/24 04:50:39 cheshire +Minor tidying + +Revision 1.468 2004/11/24 01:47:07 cheshire + DNSServiceRegisterRecord should call CallBack on success. + +Revision 1.467 2004/11/24 01:41:28 cheshire +Rename CompleteProbing() to AcknowledgeRecord() + +Revision 1.466 2004/11/23 21:08:07 ksekar +Don't use ID to demux multicast/unicast now that unicast uses random IDs + +Revision 1.465 2004/11/15 20:09:21 ksekar + Wide Area support for Add/Remove record + +Revision 1.464 2004/11/03 01:44:36 cheshire +Update debugging messages + +Revision 1.463 2004/10/29 02:38:48 cheshire +Fix Windows compile errors + +Revision 1.462 2004/10/28 19:21:07 cheshire +Guard against registering interface with zero InterfaceID + +Revision 1.461 2004/10/28 19:02:16 cheshire +Remove \n from LogMsg() call + +Revision 1.460 2004/10/28 03:24:40 cheshire +Rename m->CanReceiveUnicastOn as m->CanReceiveUnicastOn5353 + +Revision 1.459 2004/10/26 22:34:37 cheshire + Need to protect mDNSResponder from unbounded packet flooding + +Revision 1.458 2004/10/26 20:45:28 cheshire +Show mask in "invalid mask" message + +Revision 1.457 2004/10/26 06:28:36 cheshire +Now that we don't check IP TTL any more, remove associated log message + +Revision 1.456 2004/10/26 06:21:42 cheshire +Adjust mask validity check to allow an all-ones mask (for IPv6 ::1 loopback address) + +Revision 1.455 2004/10/26 06:11:40 cheshire +Add improved logging to aid in diagnosis of mDNSResponder crashed + +Revision 1.454 2004/10/23 01:16:00 cheshire + uDNS operations not always reliable on multi-homed hosts + +Revision 1.453 2004/10/22 20:52:06 ksekar + Create NAT port mappings for Long Lived Queries + +Revision 1.452 2004/10/20 01:50:40 cheshire + Cannot resolve non-local registrations using the mach API +Implemented ForceMCast mode for AuthRecords as well as for Questions + +Revision 1.451 2004/10/19 21:33:15 cheshire + Cannot resolve non-local registrations using the mach API +Added flag 'kDNSServiceFlagsForceMulticast'. Passing through an interface id for a unicast name +doesn't force multicast unless you set this flag to indicate explicitly that this is what you want + +Revision 1.450 2004/10/19 17:42:59 ksekar +Fixed compiler warnings for non-debug builds. + +Revision 1.449 2004/10/18 22:57:07 cheshire + Seen in console: Ignored apparent spoof mDNS Response with TTL 1 + +Revision 1.448 2004/10/16 00:16:59 cheshire + Replace IP TTL 255 check with local subnet source address check + +Revision 1.447 2004/10/15 00:51:21 cheshire + Seen in console: Ignored apparent spoof mDNS Response with TTL 1 + +Revision 1.446 2004/10/14 00:43:34 cheshire + Services continue to announce SRV and HINFO + +Revision 1.445 2004/10/12 21:07:09 cheshire +Set up m->p in mDNS_Init() before calling mDNSPlatformTimeInit() + +Revision 1.444 2004/10/11 17:54:16 ksekar +Changed hashtable pointer output from debugf to verbosedebugf. + +Revision 1.443 2004/10/10 07:05:45 cheshire +For consistency, use symbol "localdomain" instead of literal string + +Revision 1.442 2004/10/08 20:25:10 cheshire +Change of plan for -- we're not going to do that at this time + +Revision 1.441 2004/10/08 03:25:01 ksekar + domain enumeration should use LLQs + +Revision 1.440 2004/10/06 01:44:19 cheshire + Resolving too quickly sometimes returns stale TXT record + +Revision 1.439 2004/10/03 23:14:11 cheshire +Add "mDNSEthAddr" type and "zeroEthAddr" constant + +Revision 1.438 2004/09/29 23:07:04 cheshire +Patch from Pavel Repin to fix compile error on Windows + +Revision 1.437 2004/09/28 02:23:50 cheshire + Deliver near-pending "remove" events before new "add" events +Don't need to search the entire cache for nearly-expired records -- just the appropriate hash slot +For records with the cache flush bit set, defer the decision until the end of the packet + +Revision 1.436 2004/09/28 01:27:04 cheshire +Update incorrect log message + +Revision 1.435 2004/09/25 02:41:39 cheshire + Deliver near-pending "remove" events before new "add" events + +Revision 1.434 2004/09/25 02:32:06 cheshire +Update comments + +Revision 1.433 2004/09/25 02:24:27 cheshire +Removed unused rr->UseCount + +Revision 1.432 2004/09/24 21:35:17 cheshire + Browses are no longer piggybacking on other browses +TargetPort and TargetQID are allowed to be undefined if no question->Target is set + +Revision 1.431 2004/09/24 21:33:12 cheshire +Adjust comment + +Revision 1.430 2004/09/24 02:15:49 cheshire + Late conflicts don't send goodbye packets on other interfaces + +Revision 1.429 2004/09/24 00:20:21 cheshire + Any rrtype is a conflict for unique records + +Revision 1.428 2004/09/24 00:12:25 cheshire +Get rid of unused RRUniqueOrKnownUnique(RR) + +Revision 1.427 2004/09/23 20:44:11 cheshire + Reduce timeout before expiring records on failure + +Revision 1.426 2004/09/23 20:21:07 cheshire + Refine "immediate answer burst; restarting exponential backoff sequence" logic +Associate a unique sequence number with each received packet, and only increment the count of recent answer +packets if the packet sequence number for this answer record is not one we've already seen and counted. + +Revision 1.425 2004/09/23 20:14:38 cheshire +Rename "question->RecentAnswers" to "question->RecentAnswerPkts" + +Revision 1.424 2004/09/23 00:58:36 cheshire + Rate limiting interferes with updating TXT records + +Revision 1.423 2004/09/23 00:50:53 cheshire + Don't send a (DE) if a service is unregistered after wake from sleep + +Revision 1.422 2004/09/22 02:34:46 cheshire +Move definitions of default TTL times from mDNS.c to mDNSEmbeddedAPI.h + +Revision 1.421 2004/09/21 23:29:49 cheshire + DNSServiceResolve should delay sending packets + +Revision 1.420 2004/09/21 23:01:42 cheshire +Update debugf messages + +Revision 1.419 2004/09/21 19:51:14 cheshire +Move "Starting time value" message from mDNS.c to mDNSMacOSX/daemon.c + +Revision 1.418 2004/09/21 18:40:17 cheshire + Adjust default record TTLs + +Revision 1.417 2004/09/21 17:32:16 cheshire + Rate limiting imposed too soon + +Revision 1.416 2004/09/20 23:52:01 cheshire +CFSocket{Puma}.c renamed to mDNSMacOSX{Puma}.c + +Revision 1.415 2004/09/18 01:14:09 cheshire + Resolve() should not bother doing AAAA queries on machines with no IPv6 interfaces + +Revision 1.414 2004/09/18 01:06:48 cheshire +Add comments + +Revision 1.413 2004/09/17 01:08:48 cheshire +Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h + The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces + declared in that file are ONLY appropriate to single-address-space embedded applications. + For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used. + +Revision 1.412 2004/09/17 00:46:33 cheshire +mDNS_TimeNow should take const mDNS parameter + +Revision 1.411 2004/09/17 00:31:51 cheshire +For consistency with ipv6, renamed rdata field 'ip' to 'ipv4' + +Revision 1.410 2004/09/17 00:19:10 cheshire +For consistency with AllDNSLinkGroupv6, rename AllDNSLinkGroup to AllDNSLinkGroupv4 + +Revision 1.409 2004/09/16 21:59:15 cheshire +For consistency with zerov6Addr, rename zeroIPAddr to zerov4Addr + +Revision 1.408 2004/09/16 21:36:36 cheshire + Fix unsafe use of mDNSPlatformTimeNow() +Changes to add necessary locking calls around unicast DNS operations + +Revision 1.407 2004/09/16 02:29:39 cheshire +Moved mDNS_Lock/mDNS_Unlock to DNSCommon.c; Added necessary locking around +uDNS_ReceiveMsg, uDNS_StartQuery, uDNS_UpdateRecord, uDNS_RegisterService + +Revision 1.406 2004/09/16 01:58:14 cheshire +Fix compiler warnings + +Revision 1.405 2004/09/16 00:24:48 cheshire + Fix unsafe use of mDNSPlatformTimeNow() + +Revision 1.404 2004/09/15 21:44:11 cheshire + Randomize initial timenow_adjust value in mDNS_Init +Show time value in log to help diagnose errors + +Revision 1.403 2004/09/15 00:46:32 ksekar +Changed debugf to verbosedebugf in CheckCacheExpiration + +Revision 1.402 2004/09/14 23:59:55 cheshire + Randomize initial timenow_adjust value in mDNS_Init + +Revision 1.401 2004/09/14 23:27:46 cheshire +Fix compile errors + +Revision 1.400 2004/09/02 03:48:47 cheshire + Disable targeted unicast query support by default +1. New flag kDNSServiceFlagsAllowRemoteQuery to indicate we want to allow remote queries for this record +2. New field AllowRemoteQuery in AuthRecord structure +3. uds_daemon.c sets AllowRemoteQuery if kDNSServiceFlagsAllowRemoteQuery is set +4. mDNS.c only answers remote queries if AllowRemoteQuery is set + +Revision 1.399 2004/09/02 01:39:40 cheshire +For better readability, follow consistent convention that QR bit comes first, followed by OP bits + +Revision 1.398 2004/09/01 03:59:29 ksekar +: Conditionally compile out uDNS code on Windows + +Revision 1.397 2004/08/25 22:04:25 rpantos +Fix the standard Windows compile error. + +Revision 1.396 2004/08/25 00:37:27 ksekar +: Cleanup DynDNS hostname registration code + +Revision 1.395 2004/08/18 17:21:18 ksekar +Removed double-call of uDNS_AdvertiseInterface from mDNS_SetFQDNs() + +Revision 1.394 2004/08/14 03:22:41 cheshire + Dynamic DNS UI <-> mDNSResponder glue +Add GetUserSpecifiedDDNSName() routine +Convert ServiceRegDomain to domainname instead of C string +Replace mDNS_GenerateFQDN/mDNS_GenerateGlobalFQDN with mDNS_SetFQDNs + +Revision 1.393 2004/08/13 23:42:52 cheshire +Removed unused "zeroDomainNamePtr" + +Revision 1.392 2004/08/13 23:37:02 cheshire +Now that we do both uDNS and mDNS, global replace "uDNS_info.hostname" with +"uDNS_info.UnicastHostname" for clarity + +Revision 1.391 2004/08/13 23:25:00 cheshire +Now that we do both uDNS and mDNS, global replace "m->hostname" with +"m->MulticastHostname" for clarity + +Revision 1.390 2004/08/11 02:17:01 cheshire + Registering service with port number 0 should create a "No Such Service" record + +Revision 1.389 2004/08/10 23:19:14 ksekar +: DNS Extension daemon for Wide Area Service Discovery +Moved routines/constants to allow extern access for garbage collection daemon + +Revision 1.388 2004/07/30 17:40:06 ksekar +: TXT Record updates not available for wide-area services + +Revision 1.387 2004/07/26 22:49:30 ksekar +: Feature #9516: Need support for NATPMP in client + +Revision 1.386 2004/07/13 21:24:24 rpantos +Fix for . + +Revision 1.385 2004/06/18 19:09:59 cheshire + Current method of doing subtypes causes name collisions + +Revision 1.384 2004/06/15 04:31:23 cheshire +Make sure to clear m->CurrentRecord at the end of AnswerNewLocalOnlyQuestion() + +Revision 1.383 2004/06/11 00:04:59 cheshire + TTL must be greater than zero for DNSServiceRegisterRecord + +Revision 1.382 2004/06/08 04:59:40 cheshire +Tidy up wording -- log messages are already prefixed with "mDNSResponder", so don't need to repeat it + +Revision 1.381 2004/06/05 00:57:30 cheshire +Remove incorrect LogMsg() + +Revision 1.380 2004/06/05 00:04:26 cheshire +: wide-area domains should be returned in reg. domain enumeration + +Revision 1.379 2004/05/28 23:42:36 ksekar +: Feature: DNS server->client notification on record changes (#7805) + +Revision 1.378 2004/05/25 17:25:25 cheshire +Remove extraneous blank lines and white space + +Revision 1.377 2004/05/18 23:51:25 cheshire +Tidy up all checkin comments to use consistent "" format for bug numbers + +Revision 1.376 2004/05/05 18:30:44 ksekar +Restored surpressed Cache Tail debug messages. + +Revision 1.375 2004/04/26 21:36:25 cheshire +Only send IPv4 (or v6) multicast when IPv4 (or v6) multicast send/receive +is indicated as being available on that interface + +Revision 1.374 2004/04/21 02:53:26 cheshire +Typo in debugf statement + +Revision 1.373 2004/04/21 02:49:11 cheshire +To reduce future confusion, renamed 'TxAndRx' to 'McastTxRx' + +Revision 1.372 2004/04/21 02:38:51 cheshire +Add debugging checks + +Revision 1.371 2004/04/14 23:09:28 ksekar +Support for TSIG signed dynamic updates. + +Revision 1.370 2004/04/09 17:40:26 cheshire +Remove unnecessary "Multicast" field -- it duplicates the semantics of the existing McastTxRx field + +Revision 1.369 2004/04/09 16:34:00 cheshire +Debugging code for later; currently unused + +Revision 1.368 2004/04/02 19:19:48 cheshire +Add code to do optional logging of multi-packet KA list time intervals + +Revision 1.367 2004/03/20 03:16:10 cheshire +Minor refinement to "Excessive update rate" message + +Revision 1.366 2004/03/20 03:12:57 cheshire +: UpdateCredits not granted promptly enough + +Revision 1.365 2004/03/19 23:51:22 cheshire +Change to use symbolic constant kUpdateCreditRefreshInterval instead of (mDNSPlatformOneSecond * 60) + +Revision 1.364 2004/03/13 01:57:33 ksekar +: DynDNS: Dynamic update of service records + +Revision 1.363 2004/03/12 21:00:51 cheshire +Also show port numbers when logging "apparent spoof mDNS Response" messages + +Revision 1.362 2004/03/12 08:58:18 cheshire +Guard against empty TXT records + +Revision 1.361 2004/03/09 03:00:46 cheshire + Don't take lock until after mDNS_Update() has validated that the data is good. + +Revision 1.360 2004/03/08 02:52:41 cheshire +Minor debugging fix: Make sure 'target' is initialized so we don't crash writing debugging log messages + +Revision 1.359 2004/03/02 03:21:56 cheshire + Properly support "_services._dns-sd._udp" meta-queries + +Revision 1.358 2004/02/20 08:18:34 cheshire +: mDNSResponder sometimes announces AAAA records unnecessarily + +Revision 1.357 2004/02/18 01:47:41 cheshire +: Insufficient delay waiting for multi-packet KA lists causes AirPort traffic storms + +Revision 1.356 2004/02/06 23:04:19 ksekar +Basic Dynamic Update support via mDNS_Register (dissabled via +UNICAST_REGISTRATION #define) + +Revision 1.355 2004/02/05 09:32:33 cheshire +Fix from Bob Bradley: When using the "%.*s" string form, +guard against truncating in the middle of a multi-byte UTF-8 character. + +Revision 1.354 2004/02/05 09:30:22 cheshire +Update comments + +Revision 1.353 2004/01/28 03:41:00 cheshire +: Need ability to do targeted queries as well as multicast queries + +Revision 1.352 2004/01/28 02:30:07 ksekar +Added default Search Domains to unicast browsing, controlled via +Networking sharing prefs pane. Stopped sending unicast messages on +every interface. Fixed unicast resolving via mach-port API. + +Revision 1.351 2004/01/27 20:15:22 cheshire +: Time to prune obsolete code for listening on port 53 + +Revision 1.350 2004/01/24 23:38:16 cheshire +Use mDNSVal16() instead of shifting and ORing operations + +Revision 1.349 2004/01/23 23:23:14 ksekar +Added TCP support for truncated unicast messages. + +Revision 1.348 2004/01/22 03:54:11 cheshire +Create special meta-interface 'mDNSInterface_ForceMCast' (-2), +which means "do this query via multicast, even if it's apparently a unicast domain" + +Revision 1.347 2004/01/22 03:50:49 cheshire +If the client has specified an explicit InterfaceID, then do query by multicast, not unicast + +Revision 1.346 2004/01/22 03:48:41 cheshire +Make sure uDNS client doesn't accidentally use query ID zero + +Revision 1.345 2004/01/22 03:43:08 cheshire +Export constants like mDNSInterface_LocalOnly so that the client layers can use them + +Revision 1.344 2004/01/21 21:53:18 cheshire +: Don't try to receive unicast responses if we're not the first to bind to the UDP port + +Revision 1.343 2003/12/23 00:07:47 cheshire +Make port number in debug message be five-character field, left justified + +Revision 1.342 2003/12/20 01:34:28 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.341 2003/12/18 22:56:12 cheshire +: Reduce syslog messages about ignored spoof packets + +Revision 1.340 2003/12/16 02:31:37 cheshire +Minor update to comments + +Revision 1.339 2003/12/13 05:50:33 bradley +Fixed crash with mDNS_Lock/Unlock being called for the initial GrowCache before the platform +layer has been initialized. Protect mDNS_reentrancy when completing the core initialization to +fix a race condition during async initialization. Fixed buffer overrun for 1 byte mDNS_snprintf. + +Revision 1.338 2003/12/13 03:05:27 ksekar +: DynDNS: Unicast query of service records + +Revision 1.337 2003/12/01 21:46:05 cheshire +mDNS_StartQuery returns mStatus_BadInterfaceErr if the specified interface does not exist + +Revision 1.336 2003/12/01 21:26:19 cheshire +Guard against zero-length sbuffer in mDNS_vsnprintf() + +Revision 1.335 2003/12/01 20:27:48 cheshire +Display IPv6 addresses correctly (e.g. in log messages) on little-endian processors + +Revision 1.334 2003/11/20 22:59:53 cheshire +Changed runtime checks in mDNS.c to be compile-time checks in mDNSEmbeddedAPI.h +Thanks to Bob Bradley for suggesting the ingenious compiler trick to make this work. + +Revision 1.333 2003/11/20 20:49:53 cheshire +Another fix from HP: Use packedstruct macro to ensure proper packing for on-the-wire packet structures + +Revision 1.332 2003/11/20 05:47:37 cheshire +: Don't exclude known answers whose expiry time is before the next query +Now that we only include answers in the known answer list if they are less than +halfway to expiry, the check to also see if we have another query scheduled +before the record expires is no longer necessary (and in fact, not correct). + +Revision 1.331 2003/11/19 22:31:48 cheshire +When automatically adding A records to SRVs, add them as additionals, not answers + +Revision 1.330 2003/11/19 22:28:50 cheshire +Increment/Decrement mDNS_reentrancy around calls to m->MainCallback() +to allow client to make mDNS calls (specifically the call to mDNS_GrowCache()) + +Revision 1.329 2003/11/19 22:19:24 cheshire +Show log message when ignoring packets with bad TTL. +This is to help diagnose problems on Linux versions that may not report the TTL reliably. + +Revision 1.328 2003/11/19 22:06:38 cheshire +Show log messages when a service or hostname is renamed + +Revision 1.327 2003/11/19 22:03:44 cheshire +Move common "m->NextScheduledResponse = m->timenow" to before "if" statement + +Revision 1.326 2003/11/17 22:27:02 cheshire +Another fix from ramaprasad.kr@hp.com: Improve reply delay computation +on platforms that have native clock rates below fifty ticks per second. + +Revision 1.325 2003/11/17 20:41:44 cheshire +Fix some missing mDNS_Lock(m)/mDNS_Unlock(m) calls. + +Revision 1.324 2003/11/17 20:36:32 cheshire +Function rename: Remove "mDNS_" prefix from AdvertiseInterface() and +DeadvertiseInterface() -- they're internal private routines, not API routines. + +Revision 1.323 2003/11/14 20:59:08 cheshire +Clients can't use AssignDomainName macro because mDNSPlatformMemCopy is defined in mDNSPlatformFunctions.h. +Best solution is just to combine mDNSEmbeddedAPI.h and mDNSPlatformFunctions.h into a single file. + +Revision 1.322 2003/11/14 19:47:52 cheshire +Define symbol MAX_ESCAPED_DOMAIN_NAME to indicate recommended buffer size for ConvertDomainNameToCString + +Revision 1.321 2003/11/14 19:18:34 cheshire +Move AssignDomainName macro to mDNSEmbeddedAPI.h to that client layers can use it too + +Revision 1.320 2003/11/13 06:45:04 cheshire +Fix compiler warning on certain compilers + +Revision 1.319 2003/11/13 00:47:40 cheshire + We should delay AAAA record query if A record already in cache. + +Revision 1.318 2003/11/13 00:33:26 cheshire +Change macro "RRIsAddressType" to "RRTypeIsAddressType" + +Revision 1.317 2003/11/13 00:10:49 cheshire +: Verify that rr data is different before updating. + +Revision 1.316 2003/11/08 23:37:54 cheshire +Give explicit zero initializers to blank static structure, required by certain compilers. +(Thanks to ramaprasad.kr@hp.com for reporting this.) + +Revision 1.315 2003/11/07 03:32:56 cheshire + mDNSResponder delivers answers in inconsistent order +This is the real fix. Checkin 1.312 was overly simplistic; Calling GetFreeCacheRR() can sometimes +purge records from the cache, causing tail pointer *rp to be stale on return. The correct fix is +to maintain a system-wide tail pointer for each cache slot, and then if neccesary GetFreeCacheRR() +can update this pointer, so that mDNSCoreReceiveResponse() appends records in the right place. + +Revision 1.314 2003/11/07 03:19:49 cheshire +Minor variable renaming for clarity + +Revision 1.313 2003/11/07 03:14:49 cheshire +Previous checkin proved to be overly simplistic; reversing + +Revision 1.312 2003/11/03 23:45:15 cheshire + mDNSResponder delivers answers in inconsistent order +Build cache lists in FIFO order, not customary C LIFO order +(Append new elements to tail of cache list, instead of prepending at the head.) + +Revision 1.311 2003/10/09 18:00:11 cheshire +Another compiler warning fix. + +Revision 1.310 2003/10/07 20:27:05 cheshire +Patch from Bob Bradley, to fix warning and compile error on Windows + +Revision 1.309 2003/09/26 01:06:36 cheshire + Set kDNSClass_UniqueRRSet bit for updates too +Made new routine HaveSentEntireRRSet() to check if flag should be set + +Revision 1.308 2003/09/23 01:05:01 cheshire +Minor changes to comments and debugf() message + +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 + 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 mDNSPlatformRawTime() + +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 mDNSEmbeddedAPI.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 +: 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 + 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 +: 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 + 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: +mDNSMacOSX.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 + mDNSResponder 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 + Incorrect goodbye packet after conflict + +Revision 1.102 2003/04/17 03:06:28 cheshire + 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 + Added a hash to lookup records in the cache. + +Revision 1.100 2003/04/15 18:53:14 cheshire + Bug in ScheduleNextTask +mDNS.c 1.94 incorrectly combined two "if" statements into one. + +Revision 1.99 2003/04/15 18:09:13 jgraessl + +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 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 + 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 + 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 + 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 + 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 + mDNSResponder needs to include AAAA records in additional answer section + +Revision 1.80 2003/02/21 02:47:53 cheshire + 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 + 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 + Xserve RAID needs to do interface-specific registrations +Reviewed by: Josh Graessley, Bob Bradley + +Revision 1.77 2003/01/31 03:35:59 cheshire + 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 + 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 + 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 + 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 + 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 + 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: + computer name changes not handled properly + service name changes are not properly handled + 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 + 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-existence +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 +*/ + +#include "DNSCommon.h" // Defines general DNS untility routines +#include "uDNS.h" // Defines entry points into unicast-specific routines +// Disable certain benign warnings with Microsoft compilers +#if(defined(_MSC_VER)) + // Disable "conditional expression is constant" warning for debug macros. + // Otherwise, this generates warnings for the perfectly natural construct "while(1)" + // If someone knows a variant way of writing "while(1)" that doesn't generate warning messages, please let us know + #pragma warning(disable:4127) + + // Disable "assignment within conditional expression". + // Other compilers understand the convention that if you place the assignment expression within an extra pair + // of parentheses, this signals to the compiler that you really intended an assignment and no warning is necessary. + // The Microsoft compiler doesn't understand this convention, so in the absense of any other way to signal + // to the compiler that the assignment is intentional, we have to just turn this warning off completely. + #pragma warning(disable:4706) +#endif + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Program Constants +#endif + +mDNSexport const mDNSIPPort zeroIPPort = { { 0 } }; +mDNSexport const mDNSv4Addr zerov4Addr = { { 0 } }; +mDNSexport const mDNSv6Addr zerov6Addr = { { 0 } }; +mDNSexport const mDNSEthAddr zeroEthAddr = { { 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 } }; +mDNSexport const mDNSAddr zeroAddr = { mDNSAddrType_None, {{{ 0 }}} }; + +mDNSexport const mDNSInterfaceID mDNSInterface_Any = 0; +mDNSexport const mDNSInterfaceID mDNSInterface_LocalOnly = (mDNSInterfaceID)1; + +mDNSlocal const mDNSInterfaceID mDNSInterfaceMark = (mDNSInterfaceID)~0; + +#define UnicastDNSPortAsNumber 53 +#define NATPMPPortAsNumber 5351 +#define DNSEXTPortAsNumber 5352 // Port used for end-to-end DNS operations like LLQ, Updates with Leases, etc. +#define MulticastDNSPortAsNumber 5353 +#define LoopbackIPCPortAsNumber 5354 + +mDNSexport const mDNSIPPort UnicastDNSPort = { { UnicastDNSPortAsNumber >> 8, UnicastDNSPortAsNumber & 0xFF } }; +mDNSexport const mDNSIPPort NATPMPPort = { { NATPMPPortAsNumber >> 8, NATPMPPortAsNumber & 0xFF } }; +mDNSexport const mDNSIPPort DNSEXTPort = { { DNSEXTPortAsNumber >> 8, DNSEXTPortAsNumber & 0xFF } }; +mDNSexport const mDNSIPPort MulticastDNSPort = { { MulticastDNSPortAsNumber >> 8, MulticastDNSPortAsNumber & 0xFF } }; +mDNSexport const mDNSIPPort LoopbackIPCPort = { { LoopbackIPCPortAsNumber >> 8, LoopbackIPCPortAsNumber & 0xFF } }; + +mDNSexport const mDNSv4Addr AllDNSAdminGroup = { { 239, 255, 255, 251 } }; +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 } } } }; + +mDNSexport const mDNSOpaque16 zeroID = { { 0, 0 } }; +mDNSexport const mDNSOpaque16 QueryFlags = { { kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery, 0 } }; +mDNSexport const mDNSOpaque16 uQueryFlags = { { kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery | kDNSFlag0_RD, 0 } }; +mDNSexport const mDNSOpaque16 ResponseFlags = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery | kDNSFlag0_AA, 0 } }; +mDNSexport const mDNSOpaque16 UpdateReqFlags = { { kDNSFlag0_QR_Query | kDNSFlag0_OP_Update, 0 } }; +mDNSexport const mDNSOpaque16 UpdateRespFlags = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_Update, 0 } }; + +// Any records bigger than this are considered 'large' records +#define SmallRecordLimit 1024 + +#define kMaxUpdateCredits 10 +#define kUpdateCreditRefreshInterval (mDNSPlatformOneSecond * 6) + +mDNSexport const char *const mDNS_DomainTypeNames[] = + { + "b._dns-sd._udp.", // Browse + "db._dns-sd._udp.", // Default Browse + "lb._dns-sd._udp.", // Legacy Browse + "r._dns-sd._udp.", // Registration + "dr._dns-sd._udp." // Default Registration + }; + +#ifdef UNICAST_DISABLED +#define uDNS_IsActiveQuery(q, u) mDNSfalse +#endif + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - 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 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +mDNSexport mDNSu32 mDNS_vsnprintf(char *sbuffer, mDNSu32 buflen, const char *fmt, va_list arg) + { + mDNSu32 nwritten = 0; + int c; + if (buflen == 0) return(0); + buflen--; // Pre-reserve one space in the buffer for the terminating null + if (buflen == 0) goto exit; + + 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 + { + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + if (F.altForm) + { + mDNSAddr *ip = (mDNSAddr*)a; + switch (ip->type) + { + case mDNSAddrType_IPv4: F.precision = 4; a = (unsigned char *)&ip->ip.v4; break; + case mDNSAddrType_IPv6: F.precision = 16; a = (unsigned char *)&ip->ip.v6; 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), + "%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X", + a[0x0], a[0x1], a[0x2], a[0x3], a[0x4], a[0x5], a[0x6], a[0x7], + a[0x8], a[0x9], a[0xA], a[0xB], a[0xC], a[0xD], a[0xE], a[0xF]); 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: i=0; + if (!F.havePrecision) // C string + while(s[i]) i++; + else + { + while ((i < F.precision) && s[i]) i++; + // Make sure we don't truncate in the middle of a UTF-8 character + // If last character we got was any kind of UTF-8 multi-byte character, + // then see if we have to back up. + // This is not as easy as the similar checks below, because + // here we can't assume it's safe to examine the *next* byte, so we + // have to confine ourselves to working only backwards in the string. + j = i; // Record where we got to + // Now, back up until we find first non-continuation-char + while (i>0 && (s[i-1] & 0xC0) == 0x80) i--; + // Now s[i-1] is the first non-continuation-char + // and (j-i) is the number of continuation-chars we found + if (i>0 && (s[i-1] & 0xC0) == 0xC0) // If we found a start-char + { + i--; // Tentatively eliminate this start-char as well + // Now (j-i) is the number of characters we're considering eliminating. + // To be legal UTF-8, the start-char must contain (j-i) one-bits, + // followed by a zero bit. If we shift it right by (7-(j-i)) bits + // (with sign extension) then the result has to be 0xFE. + // If this is right, then we reinstate the tentatively eliminated bytes. + if (((j-i) < 7) && (((s[i] >> (7-(j-i))) & 0xFF) == 0xFE)) i = j; + } + } + break; + 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; + } + } + // Make sure we don't truncate in the middle of a UTF-8 character (see similar comment below) + if (F.havePrecision && i > F.precision) + { 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); + + // Make sure we don't truncate in the middle of a UTF-8 character. + // Note: s[i] is the first eliminated character; i.e. the next character *after* the last character of the + // allowed output. If s[i] is a UTF-8 continuation character, then we've cut a unicode character in half, + // so back up 'i' until s[i] is no longer a UTF-8 continuation character. (if the input was proprly + // formed, s[i] will now be the UTF-8 start character of the multi-byte character we just eliminated). + if (i > buflen - nwritten) + { 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 + +#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); + } + +mDNSlocal CacheGroup *CacheGroupForName(const mDNS *const m, const mDNSu32 slot, const mDNSu32 namehash, const domainname *const name) + { + CacheGroup *cg; + for (cg = m->rrcache_hash[slot]; cg; cg=cg->next) + if (cg->namehash == namehash && SameDomainName(cg->name, name)) + break; + return(cg); + } + +mDNSlocal CacheGroup *CacheGroupForRecord(const mDNS *const m, const mDNSu32 slot, const ResourceRecord *const rr) + { + return(CacheGroupForName(m, slot, rr->namehash, rr->name)); + } + +mDNSlocal mDNSBool AddressIsLocalSubnet(mDNS *const m, const mDNSInterfaceID InterfaceID, const mDNSAddr *addr) + { + NetworkInterfaceInfo *intf; + + if (addr->type == mDNSAddrType_IPv4) + { + if (addr->ip.v4.b[0] == 169 && addr->ip.v4.b[1] == 254) return(mDNStrue); + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->ip.type == addr->type && intf->InterfaceID == InterfaceID && intf->McastTxRx) + if (((intf->ip.ip.v4.NotAnInteger ^ addr->ip.v4.NotAnInteger) & intf->mask.ip.v4.NotAnInteger) == 0) + return(mDNStrue); + } + + if (addr->type == mDNSAddrType_IPv6) + { + if (addr->ip.v6.b[0] == 0xFE && addr->ip.v6.b[1] == 0x80) return(mDNStrue); + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->ip.type == addr->type && intf->InterfaceID == InterfaceID && intf->McastTxRx) + if ((((intf->ip.ip.v6.l[0] ^ addr->ip.v6.l[0]) & intf->mask.ip.v6.l[0]) == 0) && + (((intf->ip.ip.v6.l[1] ^ addr->ip.v6.l[1]) & intf->mask.ip.v6.l[1]) == 0) && + (((intf->ip.ip.v6.l[2] ^ addr->ip.v6.l[2]) & intf->mask.ip.v6.l[2]) == 0) && + (((intf->ip.ip.v6.l[3] ^ addr->ip.v6.l[3]) & intf->mask.ip.v6.l[3]) == 0)) + return(mDNStrue); + } + + return(mDNSfalse); + } + +// 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) + { + mDNSPlatformMemZero(&rr->uDNS_info, sizeof(uDNS_RegInfo)); + // 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 = DefaultTTLforRRType(rrtype); + + // Field Group 1: The actual information pertaining to this resource record + rr->resrec.RecordType = RecordType; + rr->resrec.InterfaceID = InterfaceID; + rr->resrec.name = &rr->namestorage; + 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); + } + + // Field Group 2: Persistent metadata for Authoritative Records + rr->Additional1 = mDNSNULL; + rr->Additional2 = mDNSNULL; + rr->DependentOn = mDNSNULL; + rr->RRSet = mDNSNULL; + rr->RecordCallback = Callback; + rr->RecordContext = Context; + + rr->HostTarget = mDNSfalse; + rr->AllowRemoteQuery = mDNSfalse; + rr->ForceMCast = mDNSfalse; + + // Field Group 3: Transient state for Authoritative Records (set in mDNS_Register_internal) + + rr->namestorage.c[0] = 0; // MUST be set by client before calling mDNS_Register() + } + +// For a single given DNSQuestion, deliver an add/remove result for the single given AuthRecord +// Used by AnswerLocalQuestions() and AnswerNewLocalOnlyQuestion() +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->LocalAnswer = mDNStrue; + 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 + } + +// When a new local AuthRecord is created or deleted, AnswerLocalQuestions() runs though our LocalOnlyQuestions delivering answers +// to each, stopping if it reaches a NewLocalOnlyQuestion -- brand-new questions are handled by AnswerNewLocalOnlyQuestion(). +// If the AuthRecord is marked mDNSInterface_LocalOnly, then we also deliver it to any other questions we have using mDNSInterface_Any. +// Used by AnswerForNewLocalRecords() and mDNS_Deregister_internal() +mDNSlocal void AnswerLocalQuestions(mDNS *const m, AuthRecord *rr, mDNSBool AddRecord) + { + if (m->CurrentQuestion) LogMsg("AnswerLocalQuestions 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)) + AnswerLocalOnlyQuestionWithResourceRecord(m, q, rr, AddRecord); // MUST NOT dereference q again + } + + // If this AuthRecord is marked LocalOnly, then we want to deliver it to all local 'mDNSInterface_Any' questions + if (rr->resrec.InterfaceID == mDNSInterface_LocalOnly) + { + m->CurrentQuestion = m->Questions; + while (m->CurrentQuestion && m->CurrentQuestion != m->NewQuestions) + { + DNSQuestion *q = m->CurrentQuestion; + m->CurrentQuestion = q->next; + if (ResourceRecordAnswersQuestion(&rr->resrec, q)) + AnswerLocalOnlyQuestionWithResourceRecord(m, q, rr, AddRecord); // MUST NOT dereference q again + } + } + + m->CurrentQuestion = mDNSNULL; + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Resource Record Utility Functions +#endif + +#define RRTypeIsAddressType(T) ((T) == kDNSType_A || (T) == kDNSType_AAAA) + +#define ResourceRecordIsValidAnswer(RR) ( ((RR)-> resrec.RecordType & kDNSRecordTypeActiveMask) && \ + ((RR)->Additional1 == mDNSNULL || ((RR)->Additional1->resrec.RecordType & kDNSRecordTypeActiveMask)) && \ + ((RR)->Additional2 == mDNSNULL || ((RR)->Additional2->resrec.RecordType & kDNSRecordTypeActiveMask)) && \ + ((RR)->DependentOn == mDNSNULL || ((RR)->DependentOn->resrec.RecordType & kDNSRecordTypeActiveMask)) ) + +#define ResourceRecordIsValidInterfaceAnswer(RR, INTID) \ + (ResourceRecordIsValidAnswer(RR) && \ + ((RR)->resrec.InterfaceID == mDNSInterface_Any || (RR)->resrec.InterfaceID == (INTID))) + +#define DefaultProbeCountForTypeUnique ((mDNSu8)3) +#define DefaultProbeCountForRecordType(X) ((X) == kDNSRecordTypeUnique ? DefaultProbeCountForTypeUnique : (mDNSu8)0) + +#define InitialAnnounceCount ((mDNSu8)10) + +// 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 + +// 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 our +// authoratative record is unique (as opposed to shared). For unique records, we are supposed to have +// complete ownership of *all* types for this name, so *any* record type with the same name is a conflict. +// In addition, when probing we send our questions with the wildcard type kDNSQType_ANY, +// so a response of any type should match, even if it is not actually the type the client plans to use. +mDNSlocal mDNSBool PacketRRMatchesSignature(const CacheRecord *const pktrr, const AuthRecord *const authrr) + { + if (!pktrr) { LogMsg("PacketRRMatchesSignature ERROR: pktrr is NULL"); return(mDNSfalse); } + if (!authrr) { LogMsg("PacketRRMatchesSignature ERROR: authrr is NULL"); return(mDNSfalse); } + if (pktrr->resrec.InterfaceID && + authrr->resrec.InterfaceID && + pktrr->resrec.InterfaceID != authrr->resrec.InterfaceID) return(mDNSfalse); + if (!(authrr->resrec.RecordType & kDNSRecordTypeUniqueMask) && 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 void SetNextAnnounceProbeTime(mDNS *const m, const AuthRecord *const rr) + { + if (rr->resrec.RecordType == kDNSRecordTypeUnique) + { + //LogMsg("ProbeCount %d Next %ld %s", + // rr->ProbeCount, (rr->LastAPTime + rr->ThisAPInterval) - m->timenow, ARDisplayString(m, rr)); + 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); + } + } + +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 = NonZeroTime(m->timenow + DefaultProbeIntervalForTypeUnique); + // 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); + } + +#define HashSlot(X) (DomainNameHashValue(X) % CACHE_HASH_SLOTS) + +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->MulticastHostname)) + debugf("SetTargetToHostName: Target of %##s is already %##s", rr->resrec.name->c, target->c); + + if (target && !SameDomainName(target, &m->MulticastHostname)) + { + AssignDomainName(target, &m->MulticastHostname); + 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->RequireGoodbye && rr->resrec.RecordType == kDNSRecordTypeShared) + debugf("Have announced shared record %##s (%s) at least once: should have sent a goodbye packet before updating", + rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype)); + + rr->AnnounceCount = InitialAnnounceCount; + rr->RequireGoodbye = mDNSfalse; + rr->ThisAPInterval = DefaultAPIntervalForRecordType(rr->resrec.RecordType); + InitializeLastAPTime(m,rr); + } + } + +mDNSlocal void AcknowledgeRecord(mDNS *const m, AuthRecord *const rr) + { + 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 + } + } + +// 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; + + mDNSPlatformMemZero(&rr->uDNS_info, sizeof(uDNS_RegInfo)); + + if ((mDNSs32)rr->resrec.rroriginalttl <= 0) + { LogMsg("mDNS_Register_internal: TTL must be 1 - 0x7FFFFFFF %s", ARDisplayString(m, rr)); return(mStatus_BadParamErr); } + +#ifndef UNICAST_DISABLED + if (rr->resrec.InterfaceID == mDNSInterface_LocalOnly || rr->ForceMCast || IsLocalDomain(rr->resrec.name)) + rr->uDNS_info.id = zeroID; + else return uDNS_RegisterRecord(m, rr); +#endif + + while (*p && *p != rr) p=&(*p)->next; + while (*d && *d != rr) d=&(*d)->next; + if (*d || *p) + { + 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 != mDNSInterface_LocalOnly) + { + 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 +// rr->AllowRemoteQuery = set to mDNSfalse in mDNS_SetupResourceRecord; may be overridden by client + // Make sure target is not uninitialized data, or we may crash writing debugging log messages + if (rr->HostTarget && target) target->c[0] = 0; + + // Field Group 2: Transient state for Authoritative Records + rr->Acknowledged = mDNSfalse; + rr->ProbeCount = DefaultProbeCountForRecordType(rr->resrec.RecordType); + rr->AnnounceCount = InitialAnnounceCount; + rr->RequireGoodbye = mDNSfalse; + rr->LocalAnswer = mDNSfalse; + rr->IncludeInProbe = mDNSfalse; + rr->ImmedAnswer = mDNSNULL; + rr->ImmedUnicast = mDNSfalse; + rr->ImmedAdditional = mDNSNULL; + rr->SendRNow = mDNSNULL; + rr->v4Requester = zerov4Addr; + rr->v6Requester = zerov6Addr; + rr->NextResponse = mDNSNULL; + rr->NR_AnswerTo = mDNSNULL; + rr->NR_AdditionalTo = mDNSNULL; + rr->ThisAPInterval = DefaultAPIntervalForRecordType(rr->resrec.RecordType); + if (!rr->HostTarget) 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) + SetTargetToHostName(m, rr); // Also sets rdlength and rdestimate for us, and calls InitializeLastAPTime(); + else + { + rr->resrec.rdlength = GetRDLength(&rr->resrec, mDNSfalse); + rr->resrec.rdestimate = GetRDLength(&rr->resrec, mDNStrue); + } + + if (!ValidateDomainName(rr->resrec.name)) + { LogMsg("Attempt to register record with invalid name: %s", ARDisplayString(m, rr)); return(mStatus_Invalid); } + + // BIND named (name daemon) doesn't allow TXT records with zero-length rdata. This is strictly speaking correct, + // since RFC 1035 specifies a TXT record as "One or more s", not "Zero or more s". + // Since some legacy apps try to create zero-length TXT records, we'll silently correct it here. + if (rr->resrec.rrtype == kDNSType_TXT && rr->resrec.rdlength == 0) { rr->resrec.rdlength = 1; rr->resrec.rdata->u.txt.c[0] = 0; } + + // Don't do this until *after* we've set rr->resrec.rdlength + if (!ValidateRData(rr->resrec.rrtype, rr->resrec.rdlength, rr->resrec.rdata)) + { LogMsg("Attempt to register record with invalid rdata: %s", ARDisplayString(m, rr)); return(mStatus_Invalid); } + + rr->resrec.namehash = DomainNameHashValue(rr->resrec.name); + rr->resrec.rdatahash = target ? DomainNameHashValue(target) : RDataHashValue(rr->resrec.rdlength, &rr->resrec.rdata->u); + + if (rr->resrec.InterfaceID == mDNSInterface_LocalOnly) + { + // 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->ResourceRecords; 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 RecordType = kDNSRecordTypeDeregistering so we'll deliver the callback + { + debugf("Name conflict %p %##s (%s)", rr, rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype)); + rr->resrec.RecordType = kDNSRecordTypeDeregistering; + rr->resrec.rroriginalttl = 0; + rr->ImmedAnswer = mDNSInterfaceMark; + m->NextScheduledResponse = m->timenow; + } + } + } + + // Now that 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 to duplicate list %p %s", rr, ARDisplayString(m,rr)); + *d = rr; + // If the previous copy of this record is already verified unique, + // then indicate that we should move this record promptly to kDNSRecordTypeUnique state. + // Setting ProbeCount to zero will cause SendQueries() to advance this record to + // kDNSRecordTypeVerified state and call the client callback at the next appropriate time. + if (rr->resrec.RecordType == kDNSRecordTypeUnique && r->resrec.RecordType == kDNSRecordTypeVerified) + rr->ProbeCount = 0; + } + else + { + debugf("Adding to active record list %p %s", rr, ARDisplayString(m,rr)); + if (!m->NewLocalRecords) m->NewLocalRecords = rr; + *p = rr; + } + + // For records that are not going to probe, acknowledge them right away + if (rr->resrec.RecordType != kDNSRecordTypeUnique && rr->resrec.RecordType != kDNSRecordTypeDeregistering) + AcknowledgeRecord(m, rr); + + return(mStatus_NoError); + } + +mDNSlocal void RecordProbeFailure(mDNS *const m, const AuthRecord *const rr) + { + m->ProbeFailTime = m->timenow; + m->NumFailedProbes++; + // If we've had fifteen or more probe failures, rate-limit to one every five seconds. + // If a bunch of hosts have all been configured with the same name, then they'll all + // conflict and run through the same series of names: name-2, name-3, name-4, etc., + // up to name-10. After that they'll start adding random increments in the range 1-100, + // so they're more likely to branch out in the available namespace and settle on a set of + // unique names quickly. If after five more tries the host is still conflicting, then we + // may have a serious problem, so we start rate-limiting so we don't melt down the network. + if (m->NumFailedProbes >= 15) + { + m->SuppressProbes = NonZeroTime(m->timenow + mDNSPlatformOneSecond * 5); + LogMsg("Excessive name conflicts (%lu) for %##s (%s); rate limiting in effect", + m->NumFailedProbes, rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype)); + } + } + +mDNSlocal void CompleteRDataUpdate(mDNS *const m, AuthRecord *const rr) + { + RData *OldRData = rr->resrec.rdata; + 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 + } + +// 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) + { + AuthRecord *r2; + mDNSu8 RecordType = rr->resrec.RecordType; + AuthRecord **p = &m->ResourceRecords; // Find this record in our list of active records + +#ifndef UNICAST_DISABLED + if (!(rr->resrec.InterfaceID == mDNSInterface_LocalOnly || rr->ForceMCast || IsLocalDomain(rr->resrec.name))) + return uDNS_DeregisterRecord(m, rr); +#endif + + while (*p && *p != rr) p=&(*p)->next; + + if (*p) + { + // We found our record on the main list. See if there are any duplicates that need special handling. + if (drt == mDNS_Dereg_conflict) // If this was a conflict, see that all duplicates get the same treatment + { + // Scan for duplicates of rr, and mark them for deregistration at the end of this routine, after we've finished + // deregistering rr. We need to do this scan *before* we give the client the chance to free and reuse the rr memory. + for (r2 = m->DuplicateRecords; r2; r2=r2->next) if (RecordIsLocalDuplicate(r2, rr)) r2->ProbeCount = 0xFF; + } + else + { + // Before we delete the record (and potentially send a goodbye packet) + // first see if we have a record on the duplicate list ready to take over from it. + AuthRecord **d = &m->DuplicateRecords; + while (*d && !RecordIsLocalDuplicate(*d, rr)) d=&(*d)->next; + if (*d) + { + AuthRecord *dup = *d; + debugf("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->RequireGoodbye = rr->RequireGoodbye; + dup->ImmedAnswer = rr->ImmedAnswer; + dup->ImmedUnicast = rr->ImmedUnicast; + dup->ImmedAdditional = rr->ImmedAdditional; + dup->v4Requester = rr->v4Requester; + dup->v6Requester = rr->v6Requester; + dup->ThisAPInterval = rr->ThisAPInterval; + dup->AnnounceUntil = rr->AnnounceUntil; + dup->LastAPTime = rr->LastAPTime; + dup->LastMCTime = rr->LastMCTime; + dup->LastMCInterface = rr->LastMCInterface; + rr->RequireGoodbye = mDNSfalse; + } + } + } + else + { + // We didn't find our record on the main list; try the DuplicateRecords list instead. + p = &m->DuplicateRecords; + while (*p && *p != rr) p=&(*p)->next; + // If we found our record on the duplicate list, then make sure we don't send a goodbye for it + if (*p) rr->RequireGoodbye = mDNSfalse; + 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) + LogMsg("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->RequireGoodbye) + { + 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 (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->NewLocalRecords == rr) m->NewLocalRecords = 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) CompleteRDataUpdate(m, rr); // Update our rdata, clear the NewRData pointer, and return memory to the client + + if (rr->LocalAnswer) AnswerLocalQuestions(m, rr, mDNSfalse); + + // 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 (drt != mDNS_Dereg_conflict) + { + if (rr->RecordCallback) rr->RecordCallback(m, rr, mStatus_MemFree); // MUST NOT touch rr after this + } + else + { + RecordProbeFailure(m, rr); + if (rr->RecordCallback) rr->RecordCallback(m, rr, mStatus_NameConflict); // MUST NOT touch rr after this + // Now that we've finished deregistering rr, check our DuplicateRecords list for any that we marked previously. + // Note that with all the client callbacks going on, by the time we get here all the + // records we marked may have been explicitly deregistered by the client anyway. + r2 = m->DuplicateRecords; + while (r2) + { + if (r2->ProbeCount != 0xFF) r2 = r2->next; + else { mDNS_Deregister_internal(m, r2, mDNS_Dereg_conflict); r2 = m->DuplicateRecords; } + } + } + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + } + return(mStatus_NoError); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - +#pragma mark - Packet Sending Functions +#endif + +mDNSlocal void AddRecordToResponseList(AuthRecord ***nrpp, AuthRecord *rr, AuthRecord *add) + { + if (rr->NextResponse == mDNSNULL && *nrpp != &rr->NextResponse) + { + **nrpp = rr; + // NR_AdditionalTo must point to a record with NR_AnswerTo set (and not NR_AdditionalTo) + // If 'add' does not meet this requirement, then follow its NR_AdditionalTo pointer to a record that does + // The referenced record will definitely be acceptable (by recursive application of this rule) + if (add && add->NR_AdditionalTo) add = add->NR_AdditionalTo; + rr->NR_AdditionalTo = add; + *nrpp = &rr->NextResponse; + } + debugf("AddRecordToResponseList: %##s (%s) already in list", rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype)); + } + +mDNSlocal void AddAdditionalsToResponseList(mDNS *const m, AuthRecord *ResponseRecords, AuthRecord ***nrpp, const mDNSInterfaceID InterfaceID) + { + AuthRecord *rr, *rr2; + for (rr=ResponseRecords; rr; rr=rr->NextResponse) // For each record we plan to put + { + // (Note: This is an "if", not a "while". If we add a record, we'll find it again + // later in the "for" loop, and we will follow further "additional" links then.) + if (rr->Additional1 && ResourceRecordIsValidInterfaceAnswer(rr->Additional1, InterfaceID)) + AddRecordToResponseList(nrpp, rr->Additional1, rr); + + if (rr->Additional2 && ResourceRecordIsValidInterfaceAnswer(rr->Additional2, InterfaceID)) + AddRecordToResponseList(nrpp, rr->Additional2, rr); + + // For SRV records, automatically add the Address record(s) for the target host + if (rr->resrec.rrtype == kDNSType_SRV) + for (rr2=m->ResourceRecords; rr2; rr2=rr2->next) // Scan list of resource records + if (RRTypeIsAddressType(rr2->resrec.rrtype) && // For all address records (A/AAAA) ... + ResourceRecordIsValidInterfaceAnswer(rr2, InterfaceID) && // ... which are valid for answer ... + rr->resrec.rdatahash == rr2->resrec.namehash && // ... whose name is the name of the SRV target + SameDomainName(&rr->resrec.rdata->u.srv.target, rr2->resrec.name)) + AddRecordToResponseList(nrpp, rr2, rr); + } + } + +mDNSlocal void SendDelayedUnicastResponse(mDNS *const m, const mDNSAddr *const dest, const mDNSInterfaceID InterfaceID) + { + AuthRecord *rr; + AuthRecord *ResponseRecords = mDNSNULL; + AuthRecord **nrp = &ResponseRecords; + + // Make a list of all our records that need to be unicast to this destination + for (rr = m->ResourceRecords; rr; rr=rr->next) + { + // If we find we can no longer unicast this answer, clear ImmedUnicast + if (rr->ImmedAnswer == mDNSInterfaceMark || + mDNSSameIPv4Address(rr->v4Requester, onesIPv4Addr) || + mDNSSameIPv6Address(rr->v6Requester, onesIPv6Addr) ) + rr->ImmedUnicast = mDNSfalse; + + if (rr->ImmedUnicast && rr->ImmedAnswer == InterfaceID) + if ((dest->type == mDNSAddrType_IPv4 && mDNSSameIPv4Address(rr->v4Requester, dest->ip.v4)) || + (dest->type == mDNSAddrType_IPv6 && mDNSSameIPv6Address(rr->v6Requester, dest->ip.v6))) + { + rr->ImmedAnswer = mDNSNULL; // Clear the state fields + rr->ImmedUnicast = mDNSfalse; + rr->v4Requester = zerov4Addr; + rr->v6Requester = zerov6Addr; + if (rr->NextResponse == mDNSNULL && nrp != &rr->NextResponse) // rr->NR_AnswerTo + { rr->NR_AnswerTo = (mDNSu8*)~0; *nrp = rr; nrp = &rr->NextResponse; } + } + } + + AddAdditionalsToResponseList(m, ResponseRecords, &nrp, InterfaceID); + + while (ResponseRecords) + { + mDNSu8 *responseptr = m->omsg.data; + mDNSu8 *newptr; + InitializeDNSMessage(&m->omsg.h, zeroID, ResponseFlags); + + // Put answers in the packet + while (ResponseRecords && ResponseRecords->NR_AnswerTo) + { + rr = ResponseRecords; + if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) + rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the cache flush bit so PutResourceRecord will set it + newptr = PutResourceRecord(&m->omsg, responseptr, &m->omsg.h.numAnswers, &rr->resrec); + rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet; // Make sure to clear cache flush bit back to normal state + if (!newptr && m->omsg.h.numAnswers) break; // If packet full, send it now + if (newptr) responseptr = newptr; + ResponseRecords = rr->NextResponse; + rr->NextResponse = mDNSNULL; + rr->NR_AnswerTo = mDNSNULL; + rr->NR_AdditionalTo = mDNSNULL; + rr->RequireGoodbye = mDNStrue; + } + + // Add additionals, if there's space + while (ResponseRecords && !ResponseRecords->NR_AnswerTo) + { + rr = ResponseRecords; + if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) + rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the cache flush bit so PutResourceRecord will set it + newptr = PutResourceRecord(&m->omsg, responseptr, &m->omsg.h.numAdditionals, &rr->resrec); + rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet; // Make sure to clear cache flush bit back to normal state + + if (newptr) responseptr = newptr; + if (newptr && m->omsg.h.numAnswers) rr->RequireGoodbye = mDNStrue; + else if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) rr->ImmedAnswer = mDNSInterfaceMark; + ResponseRecords = rr->NextResponse; + rr->NextResponse = mDNSNULL; + rr->NR_AnswerTo = mDNSNULL; + rr->NR_AdditionalTo = mDNSNULL; + } + + if (m->omsg.h.numAnswers) mDNSSendDNSMessage(m, &m->omsg, responseptr, mDNSInterface_Any, dest, MulticastDNSPort, -1, mDNSNULL); + } + } + +mDNSlocal void CompleteDeregistration(mDNS *const m, AuthRecord *rr) + { + // Clearing rr->RequireGoodbye signals mDNS_Deregister_internal() + // that it should go ahead and immediately dispose of this registration + rr->resrec.RecordType = kDNSRecordTypeShared; + rr->RequireGoodbye = mDNSfalse; + mDNS_Deregister_internal(m, rr, mDNS_Dereg_normal); // Don't touch rr after this + } + +// 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; + if (rr->resrec.RecordType == kDNSRecordTypeDeregistering) + CompleteDeregistration(m, rr); // Don't touch rr after this + else + m->CurrentRecord = rr->next; + } + } + +mDNSlocal void GrantUpdateCredit(AuthRecord *rr) + { + if (++rr->UpdateCredits >= kMaxUpdateCredits) rr->NextUpdateCredit = 0; + else rr->NextUpdateCredit = NonZeroTime(rr->NextUpdateCredit + kUpdateCreditRefreshInterval); + } + +// Note about acceleration of announcements to facilitate automatic coalescing of +// multiple independent threads of announcements into a single synchronized thread: +// The announcements in the packet may be at different stages of maturity; +// One-second interval, two-second interval, four-second interval, and so on. +// After we've put in all the announcements that are due, we then consider +// whether there are other nearly-due announcements that are worth accelerating. +// To be eligible for acceleration, a record MUST NOT be older (further along +// its timeline) than the most mature record we've already put in the packet. +// In other words, younger records can have their timelines accelerated to catch up +// with their elder bretheren; this narrows the age gap and helps them eventually get in sync. +// Older records cannot have their timelines accelerated; this would just widen +// the gap between them and their younger bretheren and get them even more out of sync. + +// NOTE: SendResponses calls mDNS_Deregister_internal which can call a user callback, which may change +// the record list and/or question list. +// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this. +mDNSlocal void SendResponses(mDNS *const m) + { + int pktcount = 0; + AuthRecord *rr, *r2; + mDNSs32 maxExistingAnnounceInterval = 0; + const NetworkInterfaceInfo *intf = GetFirstActiveInterface(m->HostInterfaces); + + m->NextScheduledResponse = m->timenow + 0x78000000; + + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->ImmedUnicast) + { + mDNSAddr v4 = { mDNSAddrType_IPv4, {{{0}}} }; + mDNSAddr v6 = { mDNSAddrType_IPv6, {{{0}}} }; + v4.ip.v4 = rr->v4Requester; + v6.ip.v6 = rr->v6Requester; + if (!mDNSIPv4AddressIsZero(rr->v4Requester)) SendDelayedUnicastResponse(m, &v4, rr->ImmedAnswer); + if (!mDNSIPv6AddressIsZero(rr->v6Requester)) SendDelayedUnicastResponse(m, &v6, rr->ImmedAnswer); + if (rr->ImmedUnicast) + { + LogMsg("SendResponses: ERROR: rr->ImmedUnicast still set: %s", ARDisplayString(m, rr)); + rr->ImmedUnicast = mDNSfalse; + } + } + + // *** + // *** 1. Setup: Set the SendRNow and ImmedAnswer fields to indicate which interface(s) the records need to be sent on + // *** + + // Run through our list of records, and decide which ones we're going to announce on all interfaces + for (rr = m->ResourceRecords; rr; rr=rr->next) + { + while (rr->NextUpdateCredit && m->timenow - rr->NextUpdateCredit >= 0) GrantUpdateCredit(rr); + if (TimeToAnnounceThisRecord(rr, m->timenow) && 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 related Address record(s) as additionals + // NOTE: Currently all address records are interface-specific, so it's safe to set ImmedAdditional to their InterfaceID, + // which will be non-null. If by some chance there is an address record that's not interface-specific (should never happen) + // then all that means is that it won't get sent -- which would not be the end of the world. + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->ImmedAnswer && rr->resrec.rrtype == kDNSType_SRV) + for (r2=m->ResourceRecords; r2; r2=r2->next) // Scan list of resource records + if (RRTypeIsAddressType(r2->resrec.rrtype) && // For all address records (A/AAAA) ... + ResourceRecordIsValidAnswer(r2) && // ... which are valid for answer ... + rr->LastMCTime - r2->LastMCTime >= 0 && // ... which we have not sent recently ... + rr->resrec.rdatahash == r2->resrec.namehash && // ... whose name is the name of the SRV target + SameDomainName(&rr->resrec.rdata->u.srv.target, r2->resrec.name) && + (rr->ImmedAnswer == mDNSInterfaceMark || rr->ImmedAnswer == r2->resrec.InterfaceID)) + r2->ImmedAdditional = r2->resrec.InterfaceID; // ... then mark this address record for sending too + + // 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); + //if (rr->SendRNow) LogMsg("%-15.4a %s", &rr->v4Requester, ARDisplayString(m, rr)); + } + + // *** + // *** 2. Loop through interface list, sending records as appropriate + // *** + + while (intf) + { + int numDereg = 0; + int numAnnounce = 0; + int numAnswer = 0; + mDNSu8 *responseptr = m->omsg.data; + mDNSu8 *newptr; + InitializeDNSMessage(&m->omsg.h, zeroID, ResponseFlags); + + // First Pass. Look for: + // 1. Deregistering records that need to send their goodbye packet + // 2. Updated records that need to retract their old data + // 3. Answers and announcements we need to send + // 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(&m->omsg, responseptr, &m->omsg.h.numAnswers, &rr->resrec, 0); + if (!newptr && m->omsg.h.numAnswers) break; + numDereg++; + responseptr = newptr; + } + else if (rr->NewRData && !m->SleepState) // 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" for the old data before we replace it. + if (ResourceRecordIsValidAnswer(rr) && rr->RequireGoodbye) + { + newptr = PutResourceRecordTTL(&m->omsg, responseptr, &m->omsg.h.numAnswers, &rr->resrec, 0); + if (!newptr && m->omsg.h.numAnswers) break; + numDereg++; + responseptr = newptr; + rr->RequireGoodbye = mDNSfalse; + } + // 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) + rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the cache flush bit so PutResourceRecord will set it + newptr = PutResourceRecord(&m->omsg, responseptr, &m->omsg.h.numAnswers, &rr->resrec); + rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet; // Make sure to clear cache flush bit back to normal state + if (newptr) { responseptr = newptr; rr->RequireGoodbye = mDNStrue; } + SetNewRData(&rr->resrec, OldRData, oldrdlength); + } + else + { + if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) + rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the cache flush bit so PutResourceRecord will set it + newptr = PutResourceRecordTTL(&m->omsg, responseptr, &m->omsg.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 && m->omsg.h.numAnswers) break; + rr->RequireGoodbye = (mDNSu8) (!m->SleepState); + 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) + if (ResourceRecordIsValidAnswer(rr)) + { + // If we have at least one answer already in the packet, then plan to add additionals too + mDNSBool SendAdditional = (m->omsg.h.numAnswers > 0); + + // If we're not planning to send any additionals, but this record is a unique one, then + // make sure we haven't already sent any other members of its RRSet -- if we have, then they + // will have had the cache flush bit set, so now we need to finish the job and send the rest. + if (!SendAdditional && (rr->resrec.RecordType & kDNSRecordTypeUniqueMask)) + { + const AuthRecord *a; + for (a = m->ResourceRecords; a; a=a->next) + if (a->LastMCTime == m->timenow && + a->LastMCInterface == intf->InterfaceID && + SameResourceRecordSignature(&a->resrec, &rr->resrec)) { SendAdditional = mDNStrue; break; } + } + if (!SendAdditional) // If we don't want to send this after all, + rr->ImmedAdditional = mDNSNULL; // then cancel its ImmedAdditional field + else if (newptr) // Else, try to add it if we can + { + if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) + rr->resrec.rrclass |= kDNSClass_UniqueRRSet; // Temporarily set the cache flush bit so PutResourceRecord will set it + newptr = PutResourceRecord(&m->omsg, newptr, &m->omsg.h.numAdditionals, &rr->resrec); + rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet; // Make sure to clear cache flush bit back to normal state + if (newptr) + { + responseptr = newptr; + rr->ImmedAdditional = mDNSNULL; + rr->RequireGoodbye = mDNStrue; + // If we successfully put this additional record in the packet, we record LastMCTime & LastMCInterface. + // This matters particularly in the case where we have more than one IPv6 (or IPv4) address, because otherwise, + // when we see our own multicast with the cache flush bit set, if we haven't set LastMCTime, then we'll get + // all concerned and re-announce our record again to make sure it doesn't get flushed from peer caches. + rr->LastMCTime = m->timenow; + rr->LastMCInterface = intf->InterfaceID; + } + } + } + + if (m->omsg.h.numAnswers > 0 || m->omsg.h.numAdditionals) + { + 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", + m->omsg.h.numAdditionals, m->omsg.h.numAdditionals == 1 ? "" : "s", intf->InterfaceID); + if (intf->IPv4Available) mDNSSendDNSMessage(m, &m->omsg, responseptr, intf->InterfaceID, &AllDNSLinkGroup_v4, MulticastDNSPort, -1, mDNSNULL); + if (intf->IPv6Available) mDNSSendDNSMessage(m, &m->omsg, responseptr, intf->InterfaceID, &AllDNSLinkGroup_v6, MulticastDNSPort, -1, mDNSNULL); + if (!m->SuppressSending) m->SuppressSending = NonZeroTime(m->timenow + (mDNSPlatformOneSecond+9)/10); + 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->SendRNow) + { + if (rr->resrec.InterfaceID != mDNSInterface_LocalOnly) + LogMsg("SendResponses: No active interface to send: %s", ARDisplayString(m, rr)); + rr->SendRNow = mDNSNULL; + } + + if (rr->ImmedAnswer) + { + if (rr->NewRData) CompleteRDataUpdate(m,rr); // Update our rdata, clear the NewRData pointer, and return memory to the client + + if (rr->resrec.RecordType == kDNSRecordTypeDeregistering) + CompleteDeregistration(m, rr); // Don't touch rr after this + else + { + rr->ImmedAnswer = mDNSNULL; + rr->ImmedUnicast = mDNSfalse; + rr->v4Requester = zerov4Addr; + rr->v6Requester = zerov6Addr; + } + } + } + verbosedebugf("SendResponses: Next in %ld 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)->DelayDelivery ) ? (mDNSPlatformOneSecond/10) : \ + ((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 +// Also, any time we set rr->DelayDelivery we should call SetNextCacheCheckTime to ensure m->NextCacheCheck is set if necessary +// Clearing rr->DelayDelivery does not require a call to SetNextCacheCheckTime +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 CacheCheckGracePeriod %d ticks", + rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype), + (rr->NextRequiredQuery - m->timenow) / mDNSPlatformOneSecond, CacheCheckGracePeriod(rr)); + } + + if (m->NextCacheCheck - (rr->NextRequiredQuery + CacheCheckGracePeriod(rr)) > 0) + m->NextCacheCheck = (rr->NextRequiredQuery + CacheCheckGracePeriod(rr)); + + if (rr->DelayDelivery) + if (m->NextCacheCheck - rr->DelayDelivery > 0) + m->NextCacheCheck = rr->DelayDelivery; + } + +#define kMinimumReconfirmTime ((mDNSu32)mDNSPlatformOneSecond * 5) +#define kDefaultReconfirmTimeForWake ((mDNSu32)mDNSPlatformOneSecond * 5) +#define kDefaultReconfirmTimeForNoAnswer ((mDNSu32)mDNSPlatformOneSecond * 15) +#define kDefaultReconfirmTimeForFlappingInterface ((mDNSu32)mDNSPlatformOneSecond * 30) + +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 + // For all the reconfirmations in a given batch, we want to use the same random value + // so that the reconfirmation questions can be grouped into a single query packet + if (!m->RandomReconfirmDelay) m->RandomReconfirmDelay = 1 + mDNSRandom(0x3FFFFFFF); + interval += mDNSRandomFromFixedSeed(m->RandomReconfirmDelay, interval/3); + rr->TimeRcvd = m->timenow - (mDNSs32)interval * 3; + rr->resrec.rroriginalttl = (interval * 4 + mDNSPlatformOneSecond - 1) / mDNSPlatformOneSecond; + SetNextCacheCheckTime(m, rr); + } + debugf("mDNS_Reconfirm_internal:%6ld ticks to go for %s", RRExpireTime(rr) - m->timenow, CRDisplayString(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->RequestUnicast) && m->CanReceiveUnicastOn5353; + 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; + const mDNSu32 slot = HashSlot(&q->qname); + CacheGroup *cg = CacheGroupForName(m, slot, q->qnamehash, &q->qname); + CacheRecord *rr; + CacheRecord **ka = *kalistptrptr; // Make a working copy of the pointer we're going to update + + for (rr = cg ? cg->members : mDNSNULL; 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 > // and its half-way-to-expiry time is at least 1 second away + mDNSPlatformOneSecond) // (also ensures we never include goodbye records with TTL=1) + { + *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 = cg ? cg->members : mDNSNULL; 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; + CacheGroup *cg; + CacheRecord *rr; + domainname *target; + FORALL_CACHERECORDS(slot, cg, rr) + if ((target = GetRRDomainNameTarget(&rr->resrec)) && rr->resrec.rdatahash == 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 = (mDNSu32)DomainNameLength(&q->qname) + 4; + const mDNSu32 slot = HashSlot(&q->qname); + CacheGroup *cg = CacheGroupForName(m, slot, q->qnamehash, &q->qname); + CacheRecord *rr; + for (rr = cg ? cg->members : mDNSNULL; 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) + { + mDNSu32 slot; + CacheGroup *cg; + CacheRecord *cr; + AuthRecord *ar; + 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) + { + 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 + FORALL_CACHERECORDS(slot, cg, rr) + 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->Target.type) q->SendQNow = mDNSInterfaceMark; // If unicast query, mark it + else 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 *unicast* queries need to be sent + for (q = m->Questions; q; q=q->next) + if (q->Target.type && (q->SendQNow || TimeToSendThisQuestion(q, m->timenow))) + { + mDNSu8 *qptr = m->omsg.data; + const mDNSu8 *const limit = m->omsg.data + sizeof(m->omsg.data); + InitializeDNSMessage(&m->omsg.h, q->TargetQID, QueryFlags); + qptr = putQuestion(&m->omsg, qptr, limit, &q->qname, q->qtype, q->qclass); + mDNSSendDNSMessage(m, &m->omsg, qptr, mDNSInterface_Any, &q->Target, q->TargetPort, -1, mDNSNULL); + q->ThisQInterval *= 2; + if (q->ThisQInterval > MaxQuestionInterval) + q->ThisQInterval = MaxQuestionInterval; + q->LastQTime = m->timenow; + q->LastQTxTime = m->timenow; + q->RecentAnswerPkts = 0; + q->SendQNow = mDNSNULL; + m->ExpectUnicastResponse = m->timenow; + } + + // Scan our list of questions to see which *multicast* queries we're definitely going to send + for (q = m->Questions; q; q=q->next) + if (!q->Target.type && 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 || + (!q->Target.type && 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, then + // 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); // 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->RecentAnswerPkts = 0; + if (q->RequestUnicast) q->RequestUnicast--; + } + // 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; + AcknowledgeRecord(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) + AcknowledgeRecord(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; + mDNSu8 *queryptr = m->omsg.data; + InitializeDNSMessage(&m->omsg.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 %d forecast total %d", + SuppressOnThisInterface(q->DupSuppress, intf) ? "Suppressing" : "Putting ", + q->qname.c, DNSTypeName(q->qtype), queryptr - m->omsg.data, queryptr + answerforecast - m->omsg.data); + // If we're suppressing this question, or we successfully put it, update its SendQNow state + if (SuppressOnThisInterface(q->DupSuppress, intf) || + BuildQuestion(m, &m->omsg, &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) && m->CanReceiveUnicastOn5353; + mDNSu16 ucbit = (mDNSu16)(ucast ? kDNSQClass_UnicastResponse : 0); + const mDNSu8 *const limit = m->omsg.data + ((m->omsg.h.numQuestions) ? NormalMaxDNSMessageData : AbsoluteMaxDNSMessageData); + mDNSu8 *newptr = putQuestion(&m->omsg, 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)); + m->omsg.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(&m->omsg, queryptr, &m->omsg.h.numAnswers, &rr->resrec, rr->resrec.rroriginalttl - SecsSinceRcvd); + if (newptr) + { + verbosedebugf("SendQueries: Put %##s (%s) at %d - %d", + rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype), queryptr - m->omsg.data, newptr - m->omsg.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 (m->omsg.h.numQuestions > 1) + LogMsg("SendQueries: Put %d answers; No more space for known answers", m->omsg.h.numAnswers); + m->omsg.h.flags.b[0] |= kDNSFlag0_TC; + break; + } + } + + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->IncludeInProbe) + { + mDNSu8 *newptr = PutResourceRecord(&m->omsg, queryptr, &m->omsg.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 > m->omsg.data) + { + if ((m->omsg.h.flags.b[0] & kDNSFlag0_TC) && m->omsg.h.numQuestions > 1) + LogMsg("SendQueries: Should not have more than one question (%d) in a truncated packet", m->omsg.h.numQuestions); + debugf("SendQueries: Sending %d Question%s %d Answer%s %d Update%s on %p", + m->omsg.h.numQuestions, m->omsg.h.numQuestions == 1 ? "" : "s", + m->omsg.h.numAnswers, m->omsg.h.numAnswers == 1 ? "" : "s", + m->omsg.h.numAuthorities, m->omsg.h.numAuthorities == 1 ? "" : "s", intf->InterfaceID); + if (intf->IPv4Available) mDNSSendDNSMessage(m, &m->omsg, queryptr, intf->InterfaceID, &AllDNSLinkGroup_v4, MulticastDNSPort, -1, mDNSNULL); + if (intf->IPv6Available) mDNSSendDNSMessage(m, &m->omsg, queryptr, intf->InterfaceID, &AllDNSLinkGroup_v6, MulticastDNSPort, -1, mDNSNULL); + if (!m->SuppressSending) m->SuppressSending = NonZeroTime(m->timenow + (mDNSPlatformOneSecond+9)/10); + 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; + } + } + + // 4. Final housekeeping + + // 4a. Debugging check: Make sure we announced all our records + for (ar = m->ResourceRecords; ar; ar=ar->next) + if (ar->SendRNow) + { + if (ar->resrec.InterfaceID != mDNSInterface_LocalOnly) + LogMsg("SendQueries: No active interface to send: %s", ARDisplayString(m, ar)); + ar->SendRNow = mDNSNULL; + } + + // 4b. When we have lingering cache records that we're keeping around for a few seconds in the hope + // that their interface which went away might come back again, the logic will want to send queries + // for those records, but we can't because their interface isn't here any more, so to keep the + // state machine ticking over we just pretend we did so. + // If the interface does not come back in time, the cache record will expire naturally + FORALL_CACHERECORDS(slot, cg, cr) + if (cr->CRActiveQuestion && cr->UnansweredQueries < MaxUnansweredQueries && m->timenow - cr->NextRequiredQuery >= 0) + { + cr->UnansweredQueries++; + cr->CRActiveQuestion->SendQNow = mDNSNULL; + SetNextCacheCheckTime(m, cr); + } + + // 4c. Debugging check: Make sure we sent all our planned questions + // Do this AFTER the lingering cache records check above, because that will prevent spurious warnings for questions + // we legitimately couldn't send because the interface is no longer available + for (q = m->Questions; q; q=q->next) + if (q->SendQNow) + { + LogMsg("SendQueries: No active interface to send: %##s %s", q->qname.c, DNSTypeName(q->qtype)); + q->SendQNow = mDNSNULL; + } + } + +// *************************************************************************** +#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)); + + // Note: Use caution here. In the case of records with rr->DelayDelivery set, AnswerQuestionWithResourceRecord(... mDNStrue) + // may be called twice, once when the record is received, and again when it's time to notify local clients. + // If any counters or similar are added here, care must be taken to ensure that they are not double-incremented by this. + + rr->LastUsed = m->timenow; + 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); + } + + // If this is: + // (a) a no-cache add, where we've already done at least one 'QM' query, or + // (b) a normal add, where we have at least one unique-type answer, + // then there's no need to keep polling the network. + // (If we have an answer in the cache, then we'll automatically ask again in time to stop it expiring.) + if ((AddRecord == 2 && !q->RequestUnicast) || + (AddRecord == 1 && (q->ExpectUnique || (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask)))) + if (ActiveQuestion(q)) + { + q->LastQTime = m->timenow; + q->LastQTxTime = m->timenow; + q->RecentAnswerPkts = 0; + q->ThisQInterval = MaxQuestionInterval; + q->RequestUnicast = mDNSfalse; + } + + if (rr->DelayDelivery) return; // We'll come back later when CacheRecordDeferredAdd() calls us + + 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 + // CAUTION: MUST NOT do anything more with q after calling q->QuestionCallback(), 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. + } + +mDNSlocal void CacheRecordDeferredAdd(mDNS *const m, CacheRecord *rr) + { + rr->DelayDelivery = 0; // Note, only need to call SetNextCacheCheckTime() when DelayDelivery is set, not when it's cleared + if (m->CurrentQuestion) LogMsg("CacheRecordDeferredAdd 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)) + AnswerQuestionWithResourceRecord(m, q, rr, mDNStrue); + } + m->CurrentQuestion = mDNSNULL; + } + +mDNSlocal mDNSs32 CheckForSoonToExpireRecords(mDNS *const m, const domainname *const name, const mDNSu32 namehash, const mDNSu32 slot) + { + const mDNSs32 threshhold = m->timenow + mDNSPlatformOneSecond; // See if there are any records expiring within one second + const mDNSs32 start = m->timenow - 0x10000000; + mDNSs32 delay = start; + CacheGroup *cg = CacheGroupForName(m, slot, namehash, name); + CacheRecord *rr; + for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next) + if (rr->resrec.namehash == namehash && SameDomainName(rr->resrec.name, name)) + if (threshhold - RRExpireTime(rr) >= 0) // If we have records about to expire within a second + if (delay - RRExpireTime(rr) < 0) // then delay until after they've been deleted + delay = RRExpireTime(rr); + if (delay - start > 0) return(NonZeroTime(delay)); + else return(0); + } + +// 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 (q->LastAnswerPktNum != m->PktNum) + { + q->LastAnswerPktNum = m->PktNum; + if (ActiveQuestion(q) && ++q->RecentAnswerPkts >= 10 && + q->ThisQInterval > InitialQuestionInterval*32 && 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++; + if (q->CurrentAnswers > 4000) + { + static int msgcount = 0; + if (msgcount++ < 10) + LogMsg("CacheRecordAdd: %##s (%s) has %d answers; shedding records to resist DOS attack", + q->qname.c, DNSTypeName(q->qtype), q->CurrentAnswers); + rr->resrec.rroriginalttl = 1; + rr->UnansweredQueries = MaxUnansweredQueries; + } + AnswerQuestionWithResourceRecord(m, q, rr, mDNStrue); + // MUST NOT dereference q again after calling AnswerQuestionWithResourceRecord() + } + } + m->CurrentQuestion = mDNSNULL; + SetNextCacheCheckTime(m, rr); + } + +// NoCacheAnswer 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 from the wire (kDNSRecordTypePacketAns/AnsUnique/Add/AddUnique) +// but we don't have any place to cache it. We'll deliver question 'add' events now, but we won't have any +// way to deliver 'remove' events in future, nor will we be able to include this in known-answer lists, +// so we immediately bump ThisQInterval up to MaxQuestionInterval to avoid pounding the network. +// NOTE: NoCacheAnswer 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 NoCacheAnswer(mDNS *const m, CacheRecord *rr) + { + LogMsg("No cache space: Delivering non-cached result for %##s", m->rec.r.resrec.name->c); + if (m->CurrentQuestion) LogMsg("NoCacheAnswer ERROR m->CurrentQuestion already set"); + m->CurrentQuestion = m->Questions; + while (m->CurrentQuestion) + { + DNSQuestion *q = m->CurrentQuestion; + m->CurrentQuestion = q->next; + if (ResourceRecordAnswersQuestion(&rr->resrec, q)) + AnswerQuestionWithResourceRecord(m, q, rr, 2); // Value '2' indicates "don't expect 'remove' events for this" + // 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", rr, CRDisplayString(m, rr)); + 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); + } + q->FlappingInterface = mDNSNULL; + AnswerQuestionWithResourceRecord(m, q, rr, mDNSfalse); + // MUST NOT dereference q again after calling AnswerQuestionWithResourceRecord() + } + } + m->CurrentQuestion = mDNSNULL; + } + +mDNSlocal void ReleaseCacheEntity(mDNS *const m, CacheEntity *e) + { +#if MACOSX_MDNS_MALLOC_DEBUGGING >= 1 + unsigned int i; + for (i=0; inext = m->rrcache_free; + m->rrcache_free = e; + m->rrcache_totalused--; + } + +mDNSlocal void ReleaseCacheGroup(mDNS *const m, CacheGroup **cp) + { + CacheEntity *e = (CacheEntity *)(*cp); + //LogMsg("ReleaseCacheGroup: Releasing CacheGroup for %p, %##s", (*cp)->name->c, (*cp)->name->c); + if ((*cp)->rrcache_tail != &(*cp)->members) + LogMsg("ERROR: (*cp)->members == mDNSNULL but (*cp)->rrcache_tail != &(*cp)->members)"); + //if ((*cp)->name != (domainname*)((*cp)->namestorage)) + // LogMsg("ReleaseCacheGroup: %##s, %p %p", (*cp)->name->c, (*cp)->name, (domainname*)((*cp)->namestorage)); + if ((*cp)->name != (domainname*)((*cp)->namestorage)) mDNSPlatformMemFree((*cp)->name); + (*cp)->name = mDNSNULL; + *cp = (*cp)->next; // Cut record from list + ReleaseCacheEntity(m, e); + } + +mDNSlocal void ReleaseCacheRecord(mDNS *const m, CacheRecord *r) + { + if (r->resrec.rdata && r->resrec.rdata != (RData*)&r->rdatastorage) mDNSPlatformMemFree(r->resrec.rdata); + r->resrec.rdata = mDNSNULL; + ReleaseCacheEntity(m, (CacheEntity *)r); + } + +// Note: We want to be careful that we deliver all the CacheRecordRmv calls before delivering +// CacheRecordDeferredAdd calls. The in-order nature of the cache lists ensures that all +// callbacks for old records are delivered before callbacks for newer records. +mDNSlocal void CheckCacheExpiration(mDNS *const m, CacheGroup *cg) + { + CacheRecord **rp = &cg->members; + + 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", CRDisplayString(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--; + } + ReleaseCacheRecord(m, rr); + } + else // else, not expired; see if we need to query + { + if (rr->DelayDelivery && rr->DelayDelivery - m->timenow > 0) + event = rr->DelayDelivery; + else + { + if (rr->DelayDelivery) CacheRecordDeferredAdd(m, rr); + 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; + } + } + } + verbosedebugf("CheckCacheExpiration:%6d %5d %s", + (event-m->timenow) / mDNSPlatformOneSecond, CacheCheckGracePeriod(rr), CRDisplayString(m, rr)); + if (m->NextCacheCheck - (event + CacheCheckGracePeriod(rr)) > 0) + m->NextCacheCheck = (event + CacheCheckGracePeriod(rr)); + rp = &rr->next; + } + } + if (cg->rrcache_tail != rp) verbosedebugf("CheckCacheExpiration: Updating CacheGroup tail from %p to %p", cg->rrcache_tail, rp); + cg->rrcache_tail = 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 + const mDNSu32 slot = HashSlot(&q->qname); + CacheGroup *cg = CacheGroupForName(m, slot, q->qnamehash, &q->qname); + + verbosedebugf("AnswerNewQuestion: Answering %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); + + if (cg) CheckCacheExpiration(m, cg); + 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 + + if (q->InterfaceID == mDNSInterface_Any) // If 'mDNSInterface_Any' question, see if we want to tell it about LocalOnly records + { + if (m->CurrentRecord) LogMsg("AnswerNewLocalOnlyQuestion ERROR m->CurrentRecord already set"); + m->CurrentRecord = m->ResourceRecords; + while (m->CurrentRecord && m->CurrentRecord != m->NewLocalRecords) + { + AuthRecord *rr = m->CurrentRecord; + m->CurrentRecord = rr->next; + if (rr->resrec.InterfaceID == mDNSInterface_LocalOnly) + 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->CurrentRecord = mDNSNULL; + } + + for (rr = cg ? cg->members : mDNSNULL; 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) || (q->ExpectUnique)) + 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; + } + +// When a NewLocalOnlyQuestion is created, AnswerNewLocalOnlyQuestion runs though our ResourceRecords delivering any +// appropriate answers, stopping if it reaches a NewLocalRecord -- these will be handled by AnswerLocalQuestions +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("AnswerNewLocalOnlyQuestion ERROR m->CurrentQuestion already set"); + m->CurrentQuestion = q; // Indicate which question we're answering, so we'll know if it gets deleted + + if (m->CurrentRecord) LogMsg("AnswerNewLocalOnlyQuestion ERROR m->CurrentRecord already set"); + m->CurrentRecord = m->ResourceRecords; + while (m->CurrentRecord && m->CurrentRecord != m->NewLocalRecords) + { + 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; + m->CurrentRecord = mDNSNULL; + } + +mDNSlocal CacheEntity *GetCacheEntity(mDNS *const m, const CacheGroup *const PreserveCG) + { + CacheEntity *e = 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->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + m->MainCallback(m, mStatus_GrowCache); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + } + } + + // 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++) + { + CacheGroup **cp = &m->rrcache_hash[slot]; + while (*cp) + { + CacheRecord **rp = &(*cp)->members; + while (*rp) + { + // Records that answer still-active questions are not candidates for recycling + // Records that are currently linked into the CacheFlushRecords list may not be recycled, or we'll crash + if ((*rp)->CRActiveQuestion || (*rp)->NextInCFList) + rp=&(*rp)->next; + else + { + CacheRecord *rr = *rp; + *rp = (*rp)->next; // Cut record from list + ReleaseCacheRecord(m, rr); + } + } + if ((*cp)->rrcache_tail != rp) + verbosedebugf("GetFreeCacheRR: Updating rrcache_tail[%lu] from %p to %p", slot, (*cp)->rrcache_tail, rp); + (*cp)->rrcache_tail = rp; + if ((*cp)->members || (*cp)==PreserveCG) cp=&(*cp)->next; + else ReleaseCacheGroup(m, cp); + } + } + #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 + { + e = m->rrcache_free; + m->rrcache_free = e->next; + if (++m->rrcache_totalused >= m->rrcache_report) + { + debugf("RR Cache now using %ld objects", m->rrcache_totalused); + if (m->rrcache_report < 100) m->rrcache_report += 10; + else m->rrcache_report += 100; + } + mDNSPlatformMemZero(e, sizeof(*e)); + } + + m->lock_rrcache = 0; + + return(e); + } + +mDNSlocal CacheRecord *GetCacheRecord(mDNS *const m, CacheGroup *cg, mDNSu16 RDLength) + { + CacheRecord *r = (CacheRecord *)GetCacheEntity(m, cg); + if (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 { ReleaseCacheEntity(m, (CacheEntity*)r); r = mDNSNULL; } + } + } + return(r); + } + +mDNSlocal CacheGroup *GetCacheGroup(mDNS *const m, const mDNSu32 slot, const ResourceRecord *const rr) + { + mDNSu16 namelen = DomainNameLength(rr->name); + CacheGroup *cg = (CacheGroup*)GetCacheEntity(m, mDNSNULL); + if (!cg) { LogMsg("GetCacheGroup: Failed to allocate memory for %##s", rr->name->c); return(mDNSNULL); } + cg->next = m->rrcache_hash[slot]; + cg->namehash = rr->namehash; + cg->members = mDNSNULL; + cg->rrcache_tail = &cg->members; + cg->name = (domainname*)cg->namestorage; + //LogMsg("GetCacheGroup: %-10s %d-byte cache name %##s", + // (namelen > InlineCacheGroupNameSize) ? "Allocating" : "Inline", namelen, rr->name->c); + if (namelen > InlineCacheGroupNameSize) cg->name = mDNSPlatformMemAllocate(namelen); + if (!cg->name) + { + LogMsg("GetCacheGroup: Failed to allocate name storage for %##s", rr->name->c); + ReleaseCacheEntity(m, (CacheEntity*)cg); + return(mDNSNULL); + } + AssignDomainName(cg->name, rr->name); + + if (CacheGroupForRecord(m, slot, rr)) LogMsg("GetCacheGroup: Already have CacheGroup for %##s", rr->name->c); + m->rrcache_hash[slot] = cg; + if (CacheGroupForRecord(m, slot, rr) != cg) LogMsg("GetCacheGroup: Not finding CacheGroup for %##s", rr->name->c); + + return(cg); + } + +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); + } + +mDNSexport mDNSs32 mDNS_TimeNow(const mDNS *const m) + { + mDNSs32 time; + mDNSPlatformLock(m); + if (m->mDNS_busy) + { + LogMsg("mDNS_TimeNow called while holding mDNS lock. This is incorrect. Code protected by lock should just use m->timenow."); + if (!m->timenow) LogMsg("mDNS_TimeNow: m->mDNS_busy is %ld but m->timenow not set", m->mDNS_busy); + } + + if (m->timenow) time = m->timenow; + else time = mDNS_TimeNow_NoLock(m); + mDNSPlatformUnlock(m); + return(time); + } + +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++) + { + CacheGroup **cp = &m->rrcache_hash[slot]; + while (*cp) + { + CheckCacheExpiration(m, *cp); + if ((*cp)->members) cp=&(*cp)->next; + else ReleaseCacheGroup(m, cp); + } + } + LogOperation("Cache checked. Next in %ld ticks", m->NextCacheCheck - m->timenow); + } + + // 4. See if we can answer any of our new local questions from the cache + for (i=0; m->NewQuestions && i<1000; i++) + { + if (m->NewQuestions->DelayAnswering && m->timenow - m->NewQuestions->DelayAnswering < 0) break; + AnswerNewQuestion(m); + } + if (i >= 1000) LogMsg("mDNS_Execute: AnswerNewQuestion exceeded loop limit"); + + for (i=0; m->NewLocalOnlyQuestions && i<1000; i++) AnswerNewLocalOnlyQuestion(m); + if (i >= 1000) LogMsg("mDNS_Execute: AnswerNewLocalOnlyQuestion exceeded loop limit"); + + for (i=0; i<1000 && m->NewLocalRecords && LocalRecordReady(m->NewLocalRecords); i++) + { + AuthRecord *rr = m->NewLocalRecords; + m->NewLocalRecords = m->NewLocalRecords->next; + AnswerLocalQuestions(m, rr, mDNStrue); + } + if (i >= 1000) LogMsg("mDNS_Execute: AnswerForNewLocalRecords 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; + } + } + + // Clear RandomDelay values, ready to pick a new different value next time + m->RandomQueryDelay = 0; + m->RandomReconfirmDelay = 0; + } + + // 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). + +#ifndef UNICAST_DISABLED + uDNS_Execute(m); +#endif + 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; + LogOperation("%s at %ld", sleepstate ? "Sleeping" : "Waking", m->timenow); + + if (sleepstate) + { +#ifndef UNICAST_DISABLED + uDNS_Sleep(m); +#endif + // 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->RequireGoodbye) + rr->ImmedAnswer = mDNSInterfaceMark; + SendResponses(m); + } + else + { + DNSQuestion *q; + mDNSu32 slot; + CacheGroup *cg; + CacheRecord *cr; + +#ifndef UNICAST_DISABLED + uDNS_Wake(m); +#endif + // 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->RequestUnicast = 2; // Set to 2 because is decremented once *before* we check it + q->LastQTime = m->timenow - q->ThisQInterval; + q->RecentAnswerPkts = 0; + ExpireDupSuppressInfo(q->DupSuppress, m->timenow); + m->NextScheduledQuery = m->timenow; + } + + // 2. Re-validate our cache records + m->NextCacheCheck = m->timenow; + FORALL_CACHERECORDS(slot, cg, cr) + mDNS_Reconfirm_internal(m, cr, kDefaultReconfirmTimeForWake); + + // 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); + rr->AnnounceCount = InitialAnnounceCount; + 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 + +#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 + + LogMsg("CompareRData ERROR: Invalid state"); + 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++) + { + ptr = GetLargeResourceRecord(m, query, ptr, end, q->InterfaceID, kDNSRecordTypePacketAuth, &m->rec); + if (!ptr) break; + if (ResourceRecordAnswersQuestion(&m->rec.r.resrec, q)) + { + FoundUpdate = mDNStrue; + if (PacketRRConflict(m, our, &m->rec.r)) + { + int result = (int)our->resrec.rrclass - (int)m->rec.r.resrec.rrclass; + if (!result) result = (int)our->resrec.rrtype - (int)m->rec.r.resrec.rrtype; + if (!result) result = CompareRData(our, &m->rec.r); + if (result > 0) + debugf("ResolveSimultaneousProbe: %##s (%s): We won", our->resrec.name->c, DNSTypeName(our->resrec.rrtype)); + else if (result < 0) + { + debugf("ResolveSimultaneousProbe: %##s (%s): We lost", our->resrec.name->c, DNSTypeName(our->resrec.rrtype)); + mDNS_Deregister_internal(m, our, mDNS_Dereg_conflict); + goto exit; + } + } + } + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + } + if (!FoundUpdate) + debugf("ResolveSimultaneousProbe: %##s (%s): No Update Record found", our->resrec.name->c, DNSTypeName(our->resrec.rrtype)); +exit: + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + } + +mDNSlocal CacheRecord *FindIdenticalRecordInCache(const mDNS *const m, ResourceRecord *pktrr) + { + mDNSu32 slot = HashSlot(pktrr->name); + CacheGroup *cg = CacheGroupForRecord(m, slot, pktrr); + CacheRecord *rr; + for (rr = cg ? cg->members : mDNSNULL; 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, + mDNSBool QueryWasLocalUnicast, DNSMessage *const response) + { + mDNSBool FromLocalSubnet = AddressIsLocalSubnet(m, InterfaceID, srcaddr); + 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; + mDNSs32 delayresponse = 0; + mDNSBool SendLegacyResponse = mDNSfalse; + const mDNSu8 *ptr = query->data; + mDNSu8 *responseptr = mDNSNULL; + AuthRecord *rr; + int i; + + // *** + // *** 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) && (QueryWasMulticast || QueryWasLocalUnicast || rr->AllowRemoteQuery)) + { + 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 immediate legacy unicast" (may *also* choose to multicast) + // NR_AnswerTo == (mDNSu8*)~1 means "answer via delayed unicast" (to modern querier; may promote to multicast instead) + // NR_AnswerTo == (mDNSu8*)~0 means "definitely answer via multicast" (can't downgrade to unicast later) + // If we're not multicasting this record because the kDNSQClass_UnicastResponse bit was set, + // but the multicast querier is not on a matching subnet (e.g. because of overalyed subnets on one link) + // then we'll multicast it anyway (if we unicast, the receiver will ignore it because it has an apparently non-local source) + if (QuestionNeedsMulticastResponse || (!FromLocalSubnet && QueryWasMulticast && !LegacyQuery)) + { + // 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 may 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 = LegacyQuery ? ptr : (mDNSu8*)~1; + } + } + } + + // 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 = mDNSPlatformOneSecond; // Divided by 50 = 20ms + + // 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) + { + const mDNSu32 slot = HashSlot(&pktq.qname); + CacheGroup *cg = CacheGroupForName(m, slot, pktq.qnamehash, &pktq.qname); + CacheRecord *rr; + + // 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 = cg ? cg->members : mDNSNULL; 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 (!q->Target.type && 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 + + // *** + // *** 3. Add additional records + // *** + AddAdditionalsToResponseList(m, ResponseRecords, &nrp, InterfaceID); + + // *** + // *** 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... + AuthRecord *rr; + CacheRecord *ourcacherr; + ptr = GetLargeResourceRecord(m, query, ptr, end, InterfaceID, kDNSRecordTypePacketAns, &m->rec); + 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(&m->rec.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(&m->rec.r, rr)) + { + if (srcaddr->type == mDNSAddrType_IPv4) + { + if (mDNSSameIPv4Address(rr->v4Requester, srcaddr->ip.v4)) rr->v4Requester = zerov4Addr; + } + 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; + rr->ImmedUnicast = mDNSfalse; +#if MDNS_LOG_ANSWER_SUPPRESSION_TIMES + LogMsg("Suppressed after%4d: %s", m->timenow - rr->ImmedAnswerMarkTime, ARDisplayString(m, rr)); +#endif + } + } + } + + // 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, &m->rec.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(&m->rec.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(&m->rec.r.resrec, q)) + { *dqp = q->NextInDQList; q->NextInDQList = mDNSNULL; } + else dqp = &q->NextInDQList; + } + } + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + } + + // *** + // *** 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; // Send modern multicast response + mDNSBool SendUnicastResponse = mDNSfalse; // Send modern unicast response (not legacy unicast response) + + // 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 this record was marked for modern (delayed) unicast response, then mark it as promoted to + // multicast response instead (don't want to end up ALSO setting SendUnicastResponse in the check below). + // If this record was marked for legacy unicast response, then we mustn't change the NR_AnswerTo value. + if (rr->NR_AnswerTo == (mDNSu8*)~1) rr->NR_AnswerTo = (mDNSu8*)~0; + } + + // 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 == (mDNSu8*)~1) SendUnicastResponse = mDNStrue; + else if (rr->NR_AnswerTo) SendLegacyResponse = mDNStrue; + + if (SendMulticastResponse || SendUnicastResponse) + { +#if MDNS_LOG_ANSWER_SUPPRESSION_TIMES + rr->ImmedAnswerMarkTime = m->timenow; +#endif + m->NextScheduledResponse = m->timenow; + // 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; + else + { + rr->ImmedAnswer = InterfaceID; // Record interface to send it on + if (SendUnicastResponse) rr->ImmedUnicast = mDNStrue; + 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; + } + } + } + // If TC flag is set, it means we should expect that additional known answers may be coming in another packet, + // so we allow roughly half a second before deciding to reply (we've observed inter-packet delays of 100-200ms on 802.11) + // else, if record is a shared one, spread responses over 100ms to avoid implosion of simultaneous responses + // else, for a simple unique record reply, we can reply immediately; no need for delay + if (query->h.flags.b[0] & kDNSFlag0_TC) delayresponse = mDNSPlatformOneSecond * 20; // Divided by 50 = 400ms + else if (rr->resrec.RecordType == kDNSRecordTypeShared) delayresponse = mDNSPlatformOneSecond; // Divided by 50 = 20ms + } + 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 || (m->SuppressSending - m->timenow) < (delayresponse + 49) / 50)) + { +#if MDNS_LOG_ANSWER_SUPPRESSION_TIMES + mDNSs32 oldss = m->SuppressSending; + if (oldss && delayresponse) + LogMsg("Current SuppressSending delay%5ld; require%5ld", m->SuppressSending - m->timenow, (delayresponse + 49) / 50); +#endif + // Pick a random delay: + // We start with the base delay chosen above (typically either 1 second or 20 seconds), + // and add a random value in the range 0-5 seconds (making 1-6 seconds or 20-25 seconds). + // This is an integer value, with resolution determined by the platform clock rate. + // We then divide that by 50 to get the delay value in ticks. We defer the division until last + // to get better results on platforms with coarse clock granularity (e.g. ten ticks per second). + // The +49 before dividing is to ensure we round up, not down, to ensure that even + // on platforms where the native clock rate is less than fifty ticks per second, + // we still guarantee that the final calculated delay is at least one platform tick. + // We want to make sure we don't ever allow the delay to be zero ticks, + // because if that happens we'll fail the Bonjour Conformance Test. + // Our final computed delay is 20-120ms for normal delayed replies, + // or 400-500ms in the case of multi-packet known-answer lists. + m->SuppressSending = m->timenow + (delayresponse + (mDNSs32)mDNSRandom((mDNSu32)mDNSPlatformOneSecond*5) + 49) / 50; + if (m->SuppressSending == 0) m->SuppressSending = 1; +#if MDNS_LOG_ANSWER_SUPPRESSION_TIMES + if (oldss && delayresponse) + LogMsg("Set SuppressSending to %5ld", m->SuppressSending - m->timenow); +#endif + } + + // *** + // *** 8. If query is from a legacy client, or from a new client requesting a unicast reply, then generate a unicast response too + // *** + if (SendLegacyResponse) + responseptr = GenerateUnicastResponse(query, end, InterfaceID, LegacyQuery, response, ResponseRecords); + +exit: + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + + // *** + // *** 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, CRDisplayString(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, CRDisplayString(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, CRDisplayString(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) + { + mDNSu8 *responseend = mDNSNULL; + mDNSBool QueryWasLocalUnicast = !mDNSAddrIsDNSMulticast(dstaddr) && AddressIsLocalSubnet(m, InterfaceID, srcaddr); + + if (!InterfaceID && mDNSAddrIsDNSMulticast(dstaddr)) + { + LogMsg("Ignoring Query from %#-15a:%-5d to %#-15a:%-5d on 0x%p with " + "%2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s (Multicast, but no InterfaceID)", + srcaddr, mDNSVal16(srcport), dstaddr, mDNSVal16(dstport), InterfaceID, + msg->h.numQuestions, msg->h.numQuestions == 1 ? ", " : "s,", + msg->h.numAnswers, msg->h.numAnswers == 1 ? ", " : "s,", + msg->h.numAuthorities, msg->h.numAuthorities == 1 ? "y, " : "ies,", + msg->h.numAdditionals, msg->h.numAdditionals == 1 ? "" : "s"); + return; + } + + verbosedebugf("Received Query from %#-15a:%-5d to %#-15a:%-5d on 0x%p with " + "%2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s", + srcaddr, mDNSVal16(srcport), dstaddr, mDNSVal16(dstport), InterfaceID, + msg->h.numQuestions, msg->h.numQuestions == 1 ? ", " : "s,", + msg->h.numAnswers, msg->h.numAnswers == 1 ? ", " : "s,", + msg->h.numAuthorities, msg->h.numAuthorities == 1 ? "y, " : "ies,", + msg->h.numAdditionals, msg->h.numAdditionals == 1 ? "" : "s"); + + responseend = ProcessQuery(m, msg, end, srcaddr, InterfaceID, + (srcport.NotAnInteger != MulticastDNSPort.NotAnInteger), mDNSAddrIsDNSMulticast(dstaddr), QueryWasLocalUnicast, &m->omsg); + + if (responseend) // If responseend is non-null, that means we built a unicast response packet + { + debugf("Unicast Response: %d Question%s, %d Answer%s, %d Additional%s to %#-15a:%d on %p/%ld", + m->omsg.h.numQuestions, m->omsg.h.numQuestions == 1 ? "" : "s", + m->omsg.h.numAnswers, m->omsg.h.numAnswers == 1 ? "" : "s", + m->omsg.h.numAdditionals, m->omsg.h.numAdditionals == 1 ? "" : "s", + srcaddr, mDNSVal16(srcport), InterfaceID, srcaddr->type); + mDNSSendDNSMessage(m, &m->omsg, responseend, InterfaceID, srcaddr, srcport, -1, mDNSNULL); + } + } + +// 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 mDNSIPPort srcport, const mDNSAddr *dstaddr, mDNSIPPort dstport, + const mDNSInterfaceID InterfaceID) + { + int i; + + // We ignore questions (if any) in a DNS response packet + const mDNSu8 *ptr = LocateAnswers(response, end); + + // "(CacheRecord*)1" is a special (non-zero) end-of-list marker + // We use this non-zero marker so that records in our CacheFlushRecords list will always have NextInCFList + // set non-zero, and that tells GetCacheEntity() that they're not, at this moment, eligible for recycling. + CacheRecord *CacheFlushRecords = (CacheRecord*)1; + 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 + (void)srcport; + (void)dstport; + + verbosedebugf("Received Response from %#-15a addressed to %#-15a on %p with " + "%2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s", + srcaddr, dstaddr, 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"); + + // If we get a unicast response when we weren't expecting one, then we assume it is someone trying to spoof us + if (!mDNSAddrIsDNSMulticast(dstaddr)) + { + if (!AddressIsLocalSubnet(m, InterfaceID, srcaddr) || (mDNSu32)(m->timenow - m->ExpectUnicastResponse) > (mDNSu32)(mDNSPlatformOneSecond*2)) + return; + // For now we don't put standard wide-area unicast responses in our main cache + // (Later we should fix this and cache all known results in a unified manner.) + if (response->h.id.NotAnInteger != 0 || srcport.NotAnInteger != MulticastDNSPort.NotAnInteger) + return; + } + + for (i = 0; i < totalrecords && ptr && ptr < end; i++) + { + const mDNSu8 RecordType = (mDNSu8)((i < response->h.numAnswers) ? kDNSRecordTypePacketAns : kDNSRecordTypePacketAdd); + ptr = GetLargeResourceRecord(m, response, ptr, end, InterfaceID, RecordType, &m->rec); + if (!ptr) goto exit; // 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(&m->rec.r, rr)) // If interface, name, type (if shared record) and class match... + { + // ... check to see if type and rdata are identical + if (m->rec.r.resrec.rrtype == rr->resrec.rrtype && SameRData(&m->rec.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 (m->rec.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; rr->ImmedUnicast = mDNSfalse; } + } + 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, the packet RR has different type or different rdata -- check to see if this is a conflict + else if (m->rec.r.resrec.rroriginalttl > 0 && PacketRRConflict(m, rr, &m->rec.r)) + { + debugf("mDNSCoreReceiveResponse: Our Record: %08lX %s", rr-> resrec.rdatahash, ARDisplayString(m, rr)); + debugf("mDNSCoreReceiveResponse: Pkt Record: %08lX %s", m->rec.r.resrec.rdatahash, CRDisplayString(m, &m->rec.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 type or 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 any record(s) of the same type that we should re-assert to rescue them + // (see note about "multi-homing and bridged networks" at the end of this function). + else if (m->rec.r.resrec.rrtype == rr->resrec.rrtype) + if ((m->rec.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 + { + const mDNSu32 slot = HashSlot(m->rec.r.resrec.name); + CacheGroup *cg = CacheGroupForRecord(m, slot, &m->rec.r.resrec); + CacheRecord *rr; + // 2a. Check if this packet resource record is already in our cache + for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next) + { + // If we found this exact resource record, refresh its TTL + if (rr->resrec.InterfaceID == InterfaceID && IdenticalResourceRecord(&m->rec.r.resrec, &rr->resrec)) + { + if (m->rec.r.resrec.rdlength > InlineCacheRDSize) + verbosedebugf("Found record size %5d interface %p already in cache: %s", + m->rec.r.resrec.rdlength, InterfaceID, CRDisplayString(m, &m->rec.r)); + rr->TimeRcvd = m->timenow; + + if (m->rec.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask) + { + // If this packet record has the kDNSClass_UniqueRRSet flag set, then add it to our cache flushing list + if (rr->NextInCFList == mDNSNULL && cfp != &rr->NextInCFList) + { *cfp = rr; cfp = &rr->NextInCFList; *cfp = (CacheRecord*)1; } + + // If this packet record is marked unique, and our previous cached copy was not, then fix it + if (!(rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask)) + { + DNSQuestion *q; + for (q = m->Questions; q; q=q->next) if (ResourceRecordAnswersQuestion(&rr->resrec, q)) q->UniqueAnswers++; + rr->resrec.RecordType = m->rec.r.resrec.RecordType; + } + } + + if (!mDNSPlatformMemSame(m->rec.r.resrec.rdata->u.data, rr->resrec.rdata->u.data, m->rec.r.resrec.rdlength)) + { + // If the rdata of the packet record differs in name capitalization from the record in our cache + // then mDNSPlatformMemSame will detect this. In this case, throw the old record away, so that clients get + // a 'remove' event for the record with the old capitalization, and then an 'add' event for the new one. + rr->resrec.rroriginalttl = 0; + rr->UnansweredQueries = MaxUnansweredQueries; + SetNextCacheCheckTime(m, rr); + // DO NOT break out here -- we want to continue as if we never found it + } + else if (m->rec.r.resrec.rroriginalttl > 0) + { + rr->resrec.rroriginalttl = m->rec.r.resrec.rroriginalttl; + rr->UnansweredQueries = 0; + rr->MPUnansweredQ = 0; + rr->MPUnansweredKA = 0; + rr->MPExpectingKA = mDNSfalse; + SetNextCacheCheckTime(m, rr); + break; + } + else + { + // If the packet TTL is zero, that means we're deleting this record. + // To give other hosts on the network a chance to protest, we push the deletion + // out one second into the future. Also, we set UnansweredQueries to MaxUnansweredQueries. + // Otherwise, we'll do final queries for this record at 80% and 90% of its apparent + // lifetime (800ms and 900ms from now) which is a pointless waste of network bandwidth. + 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 && m->rec.r.resrec.rroriginalttl > 0) + { + // If we don't have a CacheGroup for this name, make one now + if (!cg) cg = GetCacheGroup(m, slot, &m->rec.r.resrec); + if (cg) rr = GetCacheRecord(m, cg, m->rec.r.resrec.rdlength); // Make a cache record, being careful not to recycle cg + if (!rr) NoCacheAnswer(m, &m->rec.r); + else + { + RData *saveptr = rr->resrec.rdata; // Save the rr->resrec.rdata pointer + *rr = m->rec.r; // Block copy the CacheRecord object + rr->resrec.rdata = saveptr; // Restore rr->resrec.rdata after the structure assignment + rr->resrec.name = cg->name; // And set rr->resrec.name to point into our CacheGroup header + if (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) + { *cfp = rr; cfp = &rr->NextInCFList; *cfp = (CacheRecord*)1; } + // If this is an oversized record with external storage allocated, copy rdata to external storage + if (rr->resrec.rdata != (RData*)&rr->rdatastorage && !(m->rec.r.resrec.rdlength > InlineCacheRDSize)) + LogMsg("rr->resrec.rdata != &rr->rdatastorage but length <= InlineCacheRDSize %##s", m->rec.r.resrec.name->c); + if (m->rec.r.resrec.rdlength > InlineCacheRDSize) + mDNSPlatformMemCopy(m->rec.r.resrec.rdata, rr->resrec.rdata, sizeofRDataHeader + m->rec.r.resrec.rdlength); + rr->next = mDNSNULL; // Clear 'next' pointer + *(cg->rrcache_tail) = rr; // Append this record to tail of cache slot list + cg->rrcache_tail = &(rr->next); // Advance tail pointer + if (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) // If marked unique, assume we may have + rr->DelayDelivery = m->timenow + mDNSPlatformOneSecond; // to delay delivery of this 'add' event + else + rr->DelayDelivery = CheckForSoonToExpireRecords(m, rr->resrec.name, rr->resrec.namehash, slot); + CacheRecordAdd(m, rr); // CacheRecordAdd calls SetNextCacheCheckTime(m, rr); for us + } + } + } + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + } + +exit: + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + + // 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*)1) + { + CacheRecord *r1 = CacheFlushRecords, *r2; + const mDNSu32 slot = HashSlot(r1->resrec.name); + CacheGroup *cg = CacheGroupForRecord(m, slot, &r1->resrec); + CacheFlushRecords = CacheFlushRecords->NextInCFList; + r1->NextInCFList = mDNSNULL; + for (r2 = cg ? cg->members : mDNSNULL; r2; r2=r2->next) + if (SameResourceRecordSignature(&r1->resrec, &r2->resrec)) + { + // If record was recently positively received + // (i.e. not counting goodbye packets or cache flush events that set the TTL to 1) + // then we need to ensure the whole RRSet has the same TTL (as required by DNS semantics) + if (r2->resrec.rroriginalttl > 1 && m->timenow - r2->TimeRcvd < mDNSPlatformOneSecond) + { + if (r2->resrec.rroriginalttl != r1->resrec.rroriginalttl) + LogMsg("Correcting TTL from %4d to %4d for %s", + r2->resrec.rroriginalttl, r1->resrec.rroriginalttl, CRDisplayString(m, r2)); + r2->resrec.rroriginalttl = r1->resrec.rroriginalttl; + r2->TimeRcvd = m->timenow; + } + else // else, if record is old, mark it to be flushed + { + verbosedebugf("Cache flush %p X %p %s", r1, r2, CRDisplayString(m, r2)); + // 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); + } + if (r1->DelayDelivery) // If we were planning to delay delivery of this record, see if we still need to + { + // Note, only need to call SetNextCacheCheckTime() when DelayDelivery is set, not when it's cleared + r1->DelayDelivery = CheckForSoonToExpireRecords(m, r1->resrec.name, r1->resrec.namehash, slot); + if (!r1->DelayDelivery) CacheRecordDeferredAdd(m, r1); + } + } + } + +mDNSexport void mDNSCoreReceive(mDNS *const m, void *const pkt, const mDNSu8 *const end, + const mDNSAddr *const srcaddr, const mDNSIPPort srcport, const mDNSAddr *const dstaddr, const mDNSIPPort dstport, + const mDNSInterfaceID InterfaceID) + { + DNSMessage *msg = (DNSMessage *)pkt; + const mDNSu8 StdQ = kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery; + const mDNSu8 StdR = kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery; + mDNSu8 QR_OP; + mDNSu8 *ptr = mDNSNULL; + const mDNSu8 UpdateR = kDNSFlag0_QR_Response | kDNSFlag0_OP_Update; + +#ifndef UNICAST_DISABLED + if (srcport.NotAnInteger == NATPMPPort.NotAnInteger) + { + mDNS_Lock(m); + uDNS_ReceiveNATMap(m, pkt, (mDNSu16)(end - (mDNSu8 *)pkt)); + mDNS_Unlock(m); + return; + } +#endif + if ((unsigned)(end - (mDNSu8 *)pkt) < sizeof(DNSMessageHeader)) { LogMsg("DNS Message too short"); return; } + 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) + 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); + m->PktNum++; +#ifndef UNICAST_DISABLED + if (!mDNSAddressIsAllDNSLinkGroup(dstaddr) && (QR_OP == StdR || QR_OP == UpdateR)) + uDNS_ReceiveMsg(m, msg, end, srcaddr, srcport, dstaddr, dstport, InterfaceID); + // Note: mDNSCore also needs to get access to received unicast responses +#endif + if (QR_OP == StdQ) mDNSCoreReceiveQuery (m, msg, end, srcaddr, srcport, dstaddr, dstport, InterfaceID); + else if (QR_OP == StdR) mDNSCoreReceiveResponse(m, msg, end, srcaddr, srcport, dstaddr, dstport, InterfaceID); + else if (QR_OP != UpdateR) + LogMsg("Unknown DNS packet type %02X%02X from %#-15a:%-5d to %#-15a:%-5d on %p (ignored)", + msg->h.flags.b[0], msg->h.flags.b[1], srcaddr, mDNSVal16(srcport), dstaddr, mDNSVal16(dstport), InterfaceID); + + // 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 + +#define SameQTarget(A,B) (mDNSSameAddress(&(A)->Target, &(B)->Target) && (A)->TargetPort.NotAnInteger == (B)->TargetPort.NotAnInteger) + +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, + SameQTarget(q, question) && // and same unicast/multicast target settings + 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->RequestUnicast = question->RequestUnicast; + q->LastQTime = question->LastQTime; + q->RecentAnswerPkts = 0; + q->DuplicateOf = FindDuplicateQuestion(m, q); + q->LastQTxTime = question->LastQTxTime; + SetNextQueryTime(m,q); + } + } + +#define ValidQuestionTarget(Q) (((Q)->Target.type == mDNSAddrType_IPv4 || (Q)->Target.type == mDNSAddrType_IPv6) && \ + ((Q)->TargetPort.NotAnInteger == UnicastDNSPort.NotAnInteger || (Q)->TargetPort.NotAnInteger == MulticastDNSPort.NotAnInteger)) + +mDNSlocal mStatus mDNS_StartQuery_internal(mDNS *const m, DNSQuestion *const question) + { + if (question->Target.type && !ValidQuestionTarget(question)) + { + LogMsg("Warning! Target.type = %ld port = %u (Client forgot to initialize before calling mDNS_StartQuery?)", + question->Target.type, mDNSVal16(question->TargetPort)); + question->Target.type = mDNSAddrType_None; + } + + if (!question->Target.type) // No question->Target specified, so clear TargetPort and TargetQID + { + question->TargetPort = zeroIPPort; + question->TargetQID = zeroID; + } + +#ifndef UNICAST_DISABLED + // If the client has specified 'kDNSServiceFlagsForceMulticast' + // then we do a multicast query on that interface, even for unicast domains. + if (question->InterfaceID == mDNSInterface_LocalOnly || question->ForceMCast || IsLocalDomain(&question->qname)) + question->uDNS_info.id = zeroID; + else return uDNS_StartQuery(m, question); +#else + question->uDNS_info.id = zeroID; +#endif // UNICAST_DISABLED + + //LogOperation("mDNS_StartQuery %##s (%s)", question->qname.c, DNSTypeName(question->qtype)); + + 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 == mDNSInterface_LocalOnly) 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, verify it exists + if (question->InterfaceID && question->InterfaceID != mDNSInterface_LocalOnly) + { + NetworkInterfaceInfo *intf; + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->InterfaceID == question->InterfaceID) break; + if (!intf) + LogMsg("Note: InterfaceID %p for question %##s not currently found in active interface list", + question->InterfaceID, question->qname.c); + } + + if (!ValidateDomainName(&question->qname)) + { + LogMsg("Attempt to start query with invalid qname %##s (%s)", question->qname.c, DNSTypeName(question->qtype)); + return(mStatus_Invalid); + } + + // 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 (!m->RandomQueryDelay) m->RandomQueryDelay = 1 + (mDNSs32)mDNSRandom((mDNSu32)InitialQuestionInterval); + + question->next = mDNSNULL; + question->qnamehash = DomainNameHashValue(&question->qname); // MUST do this before FindDuplicateQuestion() + question->DelayAnswering = CheckForSoonToExpireRecords(m, &question->qname, question->qnamehash, HashSlot(&question->qname)); + question->ThisQInterval = InitialQuestionInterval * 2; // MUST be > zero for an active question + question->RequestUnicast = 2; // Set to 2 because is decremented once *before* we check it + question->LastQTime = m->timenow - m->RandomQueryDelay; // Avoid inter-machine synchronization + question->LastAnswerPktNum = m->PktNum; + question->RecentAnswerPkts = 0; + question->CurrentAnswers = 0; + question->LargeAnswers = 0; + question->UniqueAnswers = 0; + question->FlappingInterface = mDNSNULL; + 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 %d (%p) started", + question->qname.c, DNSTypeName(question->qtype), question->InterfaceID, + question->LastQTime + question->ThisQInterval - m->timenow, question); + else + verbosedebugf("mDNS_StartQuery_internal: Question %##s (%s) %p %d (%p) duplicate of (%p)", + question->qname.c, DNSTypeName(question->qtype), question->InterfaceID, + question->LastQTime + question->ThisQInterval - m->timenow, question, question->DuplicateOf); + + *q = question; + if (question->InterfaceID == mDNSInterface_LocalOnly) + { + 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) + { + const mDNSu32 slot = HashSlot(&question->qname); + CacheGroup *cg = CacheGroupForName(m, slot, question->qnamehash, &question->qname); + CacheRecord *rr; + DNSQuestion **q = &m->Questions; + + if (uDNS_IsActiveQuery(question, &m->uDNS_info)) return uDNS_StopQuery(m, question); + + if (question->InterfaceID == mDNSInterface_LocalOnly) 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 = cg ? cg->members : mDNSNULL; 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 %p", + 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, mDNSBool ForceMCast, mDNSQuestionCallback *Callback, void *Context) + { + question->InterfaceID = InterfaceID; + question->Target = zeroAddr; + question->qtype = kDNSType_PTR; + question->qclass = kDNSClass_IN; + question->LongLived = mDNSfalse; + question->ExpectUnique = mDNSfalse; + question->ForceMCast = ForceMCast; + question->QuestionCallback = Callback; + question->QuestionContext = Context; + if (!ConstructServiceName(&question->qname, mDNSNULL, srv, domain)) return(mStatus_BadParamErr); + +#ifndef UNICAST_DISABLED + if (question->InterfaceID == mDNSInterface_LocalOnly || question->ForceMCast || IsLocalDomain(&question->qname)) + { + question->LongLived = mDNSfalse; + question->uDNS_info.id = zeroID; + return(mDNS_StartQuery(m, question)); + } + else + { + mStatus status; + // Need to explicitly lock here, because mDNS_StartQuery does locking but uDNS_StartQuery does not + mDNS_Lock(m); + question->LongLived = mDNStrue; + status = uDNS_StartQuery(m, question); + mDNS_Unlock(m); + return(status); + } +#else + return(mDNS_StartQuery(m, question)); +#endif // UNICAST_DISABLED + } + +mDNSlocal mDNSBool MachineHasActiveIPv6(mDNS *const m) + { + NetworkInterfaceInfo *intf; + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->ip.type == mDNSAddrType_IPv6) return(mDNStrue); + return(mDNSfalse); + } + +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(m, &query->qAv4); + // Only do the AAAA query if this machine actually has IPv6 active + if (MachineHasActiveIPv6(m)) mDNS_StartQuery(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(m, &query->qAv4); + if (query->qAv6.ThisQInterval >= 0) mDNS_StopQuery(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(m, &query->qAv4); + // Only do the AAAA query if this machine actually has IPv6 active + if (MachineHasActiveIPv6(m)) mDNS_StartQuery(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, + mDNSVal16(answer->rdata->u.srv.port)); + 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; + query->info->TXTinfo[0] = 0; // In case answer->rdlength is zero + 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; + //LogOperation("FoundServiceInfo %d %s", AddRecord, RRDisplayString(m, answer)); + if (!AddRecord) return; + + if (answer->rrtype == kDNSType_A) + { + query->info->ip.type = mDNSAddrType_IPv4; + query->info->ip.ip.v4 = answer->rdata->u.ipv4; + } + 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%ld: %##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) + debugf(answer->rrtype == kDNSType_A ? + "**** WARNING **** have given %lu answers for %##s (A) %.4a" : + "**** WARNING **** have given %lu answers for %##s (AAAA) %.16a", + query->Answers, query->qSRV.qname.c, &answer->rdata->u.data); + 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; // So that mDNS_StopResolveService() knows whether to cancel this question + query->qSRV.InterfaceID = info->InterfaceID; + query->qSRV.Target = zeroAddr; + AssignDomainName(&query->qSRV.qname, &info->name); + query->qSRV.qtype = kDNSType_SRV; + query->qSRV.qclass = kDNSClass_IN; + query->qSRV.LongLived = mDNSfalse; + query->qSRV.ExpectUnique = mDNStrue; + query->qSRV.ForceMCast = mDNSfalse; + query->qSRV.QuestionCallback = FoundServiceInfoSRV; + query->qSRV.QuestionContext = query; + + query->qTXT.ThisQInterval = -1; // So that mDNS_StopResolveService() knows whether to cancel this question + query->qTXT.InterfaceID = info->InterfaceID; + query->qTXT.Target = zeroAddr; + AssignDomainName(&query->qTXT.qname, &info->name); + query->qTXT.qtype = kDNSType_TXT; + query->qTXT.qclass = kDNSClass_IN; + query->qTXT.LongLived = mDNSfalse; + query->qTXT.ExpectUnique = mDNStrue; + query->qTXT.ForceMCast = mDNSfalse; + query->qTXT.QuestionCallback = FoundServiceInfoTXT; + query->qTXT.QuestionContext = query; + + query->qAv4.ThisQInterval = -1; // So that mDNS_StopResolveService() knows whether to cancel this question + query->qAv4.InterfaceID = info->InterfaceID; + query->qAv4.Target = zeroAddr; + query->qAv4.qname.c[0] = 0; + query->qAv4.qtype = kDNSType_A; + query->qAv4.qclass = kDNSClass_IN; + query->qAv4.LongLived = mDNSfalse; + query->qAv4.ExpectUnique = mDNStrue; + query->qAv4.ForceMCast = mDNSfalse; + query->qAv4.QuestionCallback = FoundServiceInfo; + query->qAv4.QuestionContext = query; + + query->qAv6.ThisQInterval = -1; // So that mDNS_StopResolveService() knows whether to cancel this question + query->qAv6.InterfaceID = info->InterfaceID; + query->qAv6.Target = zeroAddr; + query->qAv6.qname.c[0] = 0; + query->qAv6.qtype = kDNSType_AAAA; + query->qAv6.qclass = kDNSClass_IN; + query->qAv6.LongLived = mDNSfalse; + query->qAv6.ExpectUnique = mDNStrue; + query->qAv6.ForceMCast = mDNSfalse; + 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; + + // We use mDNS_StartQuery_internal here because we're already holding the lock + 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 *q) + { + mDNS_Lock(m); + // We use mDNS_StopQuery_internal here because we're already holding the lock + if (q->qSRV.ThisQInterval >= 0 || uDNS_IsActiveQuery(&q->qSRV, &m->uDNS_info)) mDNS_StopQuery_internal(m, &q->qSRV); + if (q->qTXT.ThisQInterval >= 0 || uDNS_IsActiveQuery(&q->qTXT, &m->uDNS_info)) mDNS_StopQuery_internal(m, &q->qTXT); + if (q->qAv4.ThisQInterval >= 0 || uDNS_IsActiveQuery(&q->qAv4, &m->uDNS_info)) mDNS_StopQuery_internal(m, &q->qAv4); + if (q->qAv6.ThisQInterval >= 0 || uDNS_IsActiveQuery(&q->qAv6, &m->uDNS_info)) mDNS_StopQuery_internal(m, &q->qAv6); + mDNS_Unlock(m); + } + +mDNSexport mStatus mDNS_GetDomains(mDNS *const m, DNSQuestion *const question, mDNS_DomainType DomainType, const domainname *dom, + const mDNSInterfaceID InterfaceID, mDNSQuestionCallback *Callback, void *Context) + { + question->InterfaceID = InterfaceID; + question->Target = zeroAddr; + question->qtype = kDNSType_PTR; + question->qclass = kDNSClass_IN; + question->LongLived = mDNSfalse; + question->ExpectUnique = mDNSfalse; + question->ForceMCast = mDNSfalse; + question->QuestionCallback = Callback; + question->QuestionContext = Context; + if (DomainType > mDNS_DomainTypeMax) return(mStatus_BadParamErr); + if (!MakeDomainNameFromDNSNameString(&question->qname, mDNS_DomainTypeNames[DomainType])) return(mStatus_BadParamErr); + if (!dom) dom = &localdomain; + if (!AppendDomainName(&question->qname, dom)) return(mStatus_BadParamErr); + return(mDNS_StartQuery(m, question)); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Responder Functions +#endif + +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) + { +#ifndef UNICAST_DISABLED + mDNSBool unicast = !(rr->resrec.InterfaceID == mDNSInterface_LocalOnly || IsLocalDomain(rr->resrec.name)); +#else + mDNSBool unicast = mDNSfalse; +#endif + + if (!ValidateRData(rr->resrec.rrtype, newrdlength, newrdata)) + { + LogMsg("Attempt to update record with invalid rdata: %s", GetRRDisplayString_rdb(&rr->resrec, &newrdata->u, m->MsgBuffer)); + return(mStatus_Invalid); + } + + mDNS_Lock(m); + + // 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 (!unicast && 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 + } + + rr->NewRData = newrdata; + rr->newrdlength = newrdlength; + rr->UpdateCallback = Callback; + + if (unicast) { mStatus status = uDNS_UpdateRecord(m, rr); mDNS_Unlock(m); return(status); } + + if (rr->resrec.rroriginalttl == newttl && + rr->resrec.rdlength == newrdlength && mDNSPlatformMemSame(rr->resrec.rdata->u.data, newrdata->u.data, newrdlength)) + CompleteRDataUpdate(m, rr); + else + { + domainlabel name; + domainname type, domain; + DeconstructServiceName(rr->resrec.name, &name, &type, &domain); + rr->AnnounceCount = InitialAnnounceCount; + // iChat often does suprious record updates where no data has changed. For the _presence service type, using + // name/value pairs, the mDNSPlatformMemSame() check above catches this and correctly suppresses the wasteful + // update. For the _ichat service type, the XML encoding introduces spurious noise differences into the data + // even though there's no actual semantic change, so the mDNSPlatformMemSame() check doesn't help us. + // To work around this, we simply unilaterally limit all legacy _ichat-type updates to a single announcement. + if (SameDomainLabel(type.c, (mDNSu8*)"\x6_ichat")) rr->AnnounceCount = 1; + rr->ThisAPInterval = DefaultAPIntervalForRecordType(rr->resrec.RecordType); + InitializeLastAPTime(m, rr); + while (rr->NextUpdateCredit && m->timenow - rr->NextUpdateCredit >= 0) GrantUpdateCredit(rr); + if (!rr->UpdateBlocked && rr->UpdateCredits) rr->UpdateCredits--; + if (!rr->NextUpdateCredit) rr->NextUpdateCredit = NonZeroTime(m->timenow + kUpdateCreditRefreshInterval); + if (rr->AnnounceCount > rr->UpdateCredits + 1) rr->AnnounceCount = (mDNSu8)(rr->UpdateCredits + 1); + if (rr->UpdateCredits <= 5) + { + mDNSu32 delay = 6 - rr->UpdateCredits; // Delay 1 second, then 2, then 3, etc. up to 6 seconds maximum + if (!rr->UpdateBlocked) rr->UpdateBlocked = NonZeroTime(m->timenow + (mDNSs32)delay * mDNSPlatformOneSecond); + rr->ThisAPInterval *= 4; + rr->LastAPTime = rr->UpdateBlocked - rr->ThisAPInterval; + LogMsg("Excessive update rate for %##s; delaying announcement by %ld second%s", + rr->resrec.name->c, delay, delay > 1 ? "s" : ""); + } + 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); + } + +mDNSexport void mDNS_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 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 + + // Send dynamic update for non-linklocal IPv4 Addresses + mDNS_SetupResourceRecord(&set->RR_A, mDNSNULL, set->InterfaceID, kDNSType_A, kHostNameTTL, kDNSRecordTypeUnique, mDNS_HostNameCallback, set); + mDNS_SetupResourceRecord(&set->RR_PTR, mDNSNULL, set->InterfaceID, kDNSType_PTR, kHostNameTTL, kDNSRecordTypeKnownUnique, mDNSNULL, mDNSNULL); + mDNS_SetupResourceRecord(&set->RR_HINFO, mDNSNULL, set->InterfaceID, kDNSType_HINFO, kHostNameTTL, kDNSRecordTypeUnique, mDNSNULL, mDNSNULL); + +#if ANSWER_REMOTE_HOSTNAME_QUERIES + set->RR_A .AllowRemoteQuery = mDNStrue; + set->RR_PTR .AllowRemoteQuery = mDNStrue; + set->RR_HINFO.AllowRemoteQuery = mDNStrue; +#endif + // 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->MulticastHostname); + if (set->ip.type == mDNSAddrType_IPv4) + { + set->RR_A.resrec.rrtype = kDNSType_A; + set->RR_A.resrec.rdata->u.ipv4 = 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_PTR.ForceMCast = mDNStrue; // This PTR points to our dot-local name, so don't ever try to write it into a uDNS server + + 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->MulticastHostname); + 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 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 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_SetFQDN(mDNS *const m) + { + domainname newmname; + NetworkInterfaceInfo *intf; + AuthRecord *rr; + newmname.c[0] = 0; + + if (!AppendDomainLabel(&newmname, &m->hostlabel)) { LogMsg("ERROR: mDNS_SetFQDN: Cannot create MulticastHostname"); return; } + if (!AppendLiteralLabelString(&newmname, "local")) { LogMsg("ERROR: mDNS_SetFQDN: Cannot create MulticastHostname"); return; } + if (SameDomainName(&m->MulticastHostname, &newmname)) { LogMsg("mDNS_SetFQDN - hostname unchanged"); return; } + + mDNS_Lock(m); + AssignDomainName(&m->MulticastHostname, &newmname); + + // 1. Stop advertising our address records on all interfaces + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->Advertise) DeadvertiseInterface(m, intf); + + // 2. Start advertising our address records using the new name + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->Advertise) 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); + } + +mDNSexport void mDNS_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("mDNS_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->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + m->MainCallback(m, result); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + } + } + 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->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + m->MainCallback(m, mStatus_NameConflict); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + } + + // 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_SetFQDN(m); + LogMsg("Local Hostname %#s.local already in use; will try %#s.local instead", oldlabel.c, m->hostlabel.c); + } + else if (result == mStatus_MemFree) + { + // .local hostnames do not require goodbyes - we ignore the MemFree (which is sent directly by + // mDNS_Deregister_internal), and allow the caller to deallocate immediately following mDNS_DeadvertiseInterface + debugf("mDNS_HostNameCallback: MemFree (ignored)"); + } + else + LogMsg("mDNS_HostNameCallback: Unknown error %ld for registration of record %s", result, rr->resrec.name->c); + } + +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->McastTxRx) active->IPv4Available = mDNStrue; + if (intf->ip.type == mDNSAddrType_IPv6 && intf->McastTxRx) active->IPv6Available = mDNStrue; + } + } + +mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *set, mDNSBool flapping) + { + mDNSBool FirstOfType = mDNStrue; + NetworkInterfaceInfo **p = &m->HostInterfaces; + + if (!set->InterfaceID) + { LogMsg("Error! Tried to register a NetworkInterfaceInfo %#a with zero InterfaceID", &set->ip); return(mStatus_Invalid); } + + if (!mDNSAddressIsValidNonZero(&set->mask)) + { LogMsg("Error! Tried to register a NetworkInterfaceInfo %#a with invalid mask %#a", &set->ip, &set->mask); return(mStatus_Invalid); } + + mDNS_Lock(m); + + // Assume this interface will be active now, unless we find a duplicate already in the list + set->InterfaceActive = mDNStrue; + set->IPv4Available = (set->ip.type == mDNSAddrType_IPv4 && set->McastTxRx); + set->IPv6Available = (set->ip.type == mDNSAddrType_IPv6 && set->McastTxRx); + + // Scan list to see if this InterfaceID is already represented + while (*p) + { + if (*p == set) + { + LogMsg("Error! Tried to register a NetworkInterfaceInfo that's already in the list"); + mDNS_Unlock(m); + return(mStatus_AlreadyRegistered); + } + + if ((*p)->InterfaceID == set->InterfaceID) + { + // This InterfaceID already represented by a different interface in the list, so mark this instance inactive for now + set->InterfaceActive = mDNSfalse; + if (set->ip.type == (*p)->ip.type) FirstOfType = mDNSfalse; + if (set->ip.type == mDNSAddrType_IPv4 && set->McastTxRx) (*p)->IPv4Available = mDNStrue; + if (set->ip.type == mDNSAddrType_IPv6 && set->McastTxRx) (*p)->IPv6Available = mDNStrue; + } + + p=&(*p)->next; + } + + set->next = mDNSNULL; + *p = set; + + if (set->Advertise) + AdvertiseInterface(m, set); + + LogOperation("mDNS_RegisterInterface: InterfaceID %p %s (%#a) %s", set->InterfaceID, set->ifname, &set->ip, + set->InterfaceActive ? + "not represented in list; marking active and retriggering queries" : + "already represented in list; marking inactive for now"); + + // In early 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 (set->McastTxRx && ((m->KnownBugs & mDNS_KnownBug_PhantomInterfaces) || FirstOfType || set->InterfaceActive)) + { + DNSQuestion *q; + AuthRecord *rr; + // If flapping, delay between first and second queries is eight seconds instead of one + mDNSs32 delay = flapping ? mDNSPlatformOneSecond * 5 : 0; + mDNSu8 announce = flapping ? (mDNSu8)1 : InitialAnnounceCount; + + // 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 all + // to go and hit the network with identical queries at exactly the same moment. + if (!m->SuppressSending) m->SuppressSending = m->timenow + (mDNSs32)mDNSRandom((mDNSu32)InitialQuestionInterval); + + if (flapping) + { + LogMsg("Note: Frequent transitions for interface %s (%#a); network traffic reduction measures in effect", set->ifname, &set->ip); + if (!m->SuppressProbes || + m->SuppressProbes - (m->timenow + delay) < 0) + m->SuppressProbes = (m->timenow + delay); + } + + 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 + mDNSs32 initial = (flapping && q->FlappingInterface != set->InterfaceID) ? InitialQuestionInterval * 8 : InitialQuestionInterval; + mDNSs32 qdelay = (flapping && q->FlappingInterface != set->InterfaceID) ? mDNSPlatformOneSecond * 5 : 0; + if (flapping && q->FlappingInterface == set->InterfaceID) + LogOperation("No cache records for %##s (%s) expired; no need for immediate question", q->qname.c, DNSTypeName(q->qtype)); + + if (!q->ThisQInterval || q->ThisQInterval > initial) + { + q->ThisQInterval = initial; + q->RequestUnicast = 2; // Set to 2 because is decremented once *before* we check it + } + if (q->LastQTime - (m->timenow - q->ThisQInterval + qdelay) > 0) + q->LastQTime = (m->timenow - q->ThisQInterval + qdelay); + q->RecentAnswerPkts = 0; + SetNextQueryTime(m,q); + } + + // 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 < announce) rr->AnnounceCount = announce; + rr->ThisAPInterval = DefaultAPIntervalForRecordType(rr->resrec.RecordType); + InitializeLastAPTime(m, rr); + } + } + + 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, mDNSBool flapping) + { + 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) + { + LogOperation("mDNS_DeregisterInterface: Another representative of InterfaceID %p %s (%#a) exists;" + " making it active", set->InterfaceID, set->ifname, &set->ip); + 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 + { + mDNSu32 slot; + CacheGroup *cg; + CacheRecord *rr; + DNSQuestion *q; + LogOperation("mDNS_DeregisterInterface: Last representative of InterfaceID %p %s (%#a) deregistered;" + " marking questions etc. dormant", set->InterfaceID, set->ifname, &set->ip); + + if (flapping) + LogMsg("Note: Frequent transitions for interface %s (%#a); network traffic reduction measures in effect", + set->ifname, &set->ip); + + // 1. Deactivate any questions specific to this interface, and tag appropriate questions + // so that mDNS_RegisterInterface() knows how swiftly it needs to reactivate them + for (q = m->Questions; q; q=q->next) + { + if (q->InterfaceID == set->InterfaceID) q->ThisQInterval = 0; + if (!q->InterfaceID || q->InterfaceID == set->InterfaceID) + q->FlappingInterface = set->InterfaceID; + } + + // 2. Flush any cache records received on this interface + revalidate = mDNSfalse; // Don't revalidate if we're flushing the records + FORALL_CACHERECORDS(slot, cg, rr) + if (rr->resrec.InterfaceID == set->InterfaceID) + { + // If this interface is deemed flapping, + // postpone deleting the cache records in case the interface comes back again + if (!flapping) PurgeCacheResourceRecord(m, rr); + else mDNS_Reconfirm_internal(m, rr, kDefaultReconfirmTimeForFlappingInterface); + } + } + } + + // If we were advertising on this interface, deregister those address and reverse-lookup records now + if (set->Advertise) 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; + CacheGroup *cg; + CacheRecord *rr; + m->NextCacheCheck = m->timenow; + FORALL_CACHERECORDS(slot, cg, rr) + if (rr->resrec.InterfaceID == set->InterfaceID) + mDNS_Reconfirm_internal(m, rr, kDefaultReconfirmTimeForFlappingInterface); + } + + 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 + + // Only pass on the NoError acknowledgement for the SRV record (when it finishes probing) + if (result == mStatus_NoError && rr != &sr->RR_SRV) return; + + // 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 + 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 records 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); + } + +mDNSlocal void NSSCallback(mDNS *const m, AuthRecord *const rr, mStatus result) + { + ServiceRecordSet *sr = (ServiceRecordSet *)rr->RecordContext; + 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. "_ipp._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->MulticastHostname) 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; + + // If port number is zero, that means the client is really trying to do a RegisterNoSuchService + if (!port.NotAnInteger) + return(mDNS_RegisterNoSuchService(m, &sr->RR_SRV, name, type, domain, mDNSNULL, mDNSInterface_Any, NSSCallback, sr)); + + // Initialize the AuthRecord objects to sane values + mDNS_SetupResourceRecord(&sr->RR_ADV, mDNSNULL, InterfaceID, kDNSType_PTR, kStandardTTL, kDNSRecordTypeAdvisory, ServiceCallback, sr); + mDNS_SetupResourceRecord(&sr->RR_PTR, mDNSNULL, InterfaceID, kDNSType_PTR, kStandardTTL, kDNSRecordTypeShared, ServiceCallback, sr); + mDNS_SetupResourceRecord(&sr->RR_SRV, mDNSNULL, InterfaceID, kDNSType_SRV, kHostNameTTL, kDNSRecordTypeUnique, ServiceCallback, sr); + mDNS_SetupResourceRecord(&sr->RR_TXT, mDNSNULL, InterfaceID, kDNSType_TXT, kStandardTTL, 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*)"\x07_dns-sd\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); + st.c[1+st.c[0]] = 0; // Only want the first label, not the whole FQDN (particularly for mDNS_RenameAndReregisterService()) + AppendDomainName(&st, type); + mDNS_SetupResourceRecord(&sr->SubTypes[i], mDNSNULL, InterfaceID, kDNSType_PTR, kStandardTTL, kDNSRecordTypeShared, ServiceCallback, sr); + if (ConstructServiceName(sr->SubTypes[i].resrec.name, mDNSNULL, &st, 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; sr->RR_SRV.resrec.rdata->u.srv.target.c[0] = '\0'; } + + // 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; + +#ifndef UNICAST_DISABLED + // If the client has specified an explicit InterfaceID, + // then we do a multicast registration on that interface, even for unicast domains. + if (!(InterfaceID == mDNSInterface_LocalOnly || IsLocalDomain(sr->RR_SRV.resrec.name))) + { + mStatus status; + mDNS_Lock(m); + // BIND named (name daemon) doesn't allow TXT records with zero-length rdata. This is strictly speaking correct, + // since RFC 1035 specifies a TXT record as "One or more s", not "Zero or more s". + // Since some legacy apps try to create zero-length TXT records, we'll silently correct it here. + // (We have to duplicate this check here because uDNS_RegisterService() bypasses the usual mDNS_Register_internal() bottleneck) + if (!sr->RR_TXT.resrec.rdlength) { sr->RR_TXT.resrec.rdlength = 1; sr->RR_TXT.resrec.rdata->u.txt.c[0] = 0; } + status = uDNS_RegisterService(m, sr); + mDNS_Unlock(m); + return(status); + } +#endif + 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) + { + ExtraResourceRecord **e; + mStatus status; + + 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); + +#ifndef UNICAST_DISABLED + if (!(sr->RR_SRV.resrec.InterfaceID == mDNSInterface_LocalOnly || IsLocalDomain(sr->RR_SRV.resrec.name))) + { + mDNS_Lock(m); + // BIND named (name daemon) doesn't allow TXT records with zero-length rdata. This is strictly speaking correct, + // since RFC 1035 specifies a TXT record as "One or more s", not "Zero or more s". + // Since some legacy apps try to create zero-length TXT records, we'll silently correct it here. + // (We have to duplicate this check here because uDNS_AddRecordToService() bypasses the usual mDNS_Register_internal() bottleneck) + if (extra->r.resrec.rrtype == kDNSType_TXT && extra->r.resrec.rdlength == 0) + { extra->r.resrec.rdlength = 1; extra->r.resrec.rdata->u.txt.c[0] = 0; } + status = uDNS_AddRecordToService(m, sr, extra); + mDNS_Unlock(m); + return status; + } +#endif + + mDNS_Lock(m); + e = &sr->Extras; + while (*e) e = &(*e)->next; + + if (ttl == 0) ttl = kStandardTTL; + + extra->r.DependentOn = &sr->RR_SRV; + + debugf("mDNS_AddRecordToService adding record to %##s", extra->r.resrec.name->c); + + status = mDNS_Register_internal(m, &extra->r); + if (status == mStatus_NoError) *e = extra; + mDNS_Unlock(m); + return(status); + } + +mDNSexport mStatus mDNS_RemoveRecordFromService(mDNS *const m, ServiceRecordSet *sr, ExtraResourceRecord *extra, + mDNSRecordCallback MemFreeCallback, void *Context) + { + ExtraResourceRecord **e; + mStatus status; + + mDNS_Lock(m); + 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); + status = mStatus_BadReferenceErr; + } + else + { + debugf("mDNS_RemoveRecordFromService removing record from %##s", extra->r.resrec.name->c); + extra->r.RecordCallback = MemFreeCallback; + extra->r.RecordContext = Context; + *e = (*e)->next; +#ifndef UNICAST_DISABLED + if (!(sr->RR_SRV.resrec.InterfaceID == mDNSInterface_LocalOnly || IsLocalDomain(sr->RR_SRV.resrec.name))) + status = uDNS_DeregisterRecord(m, &extra->r); + else +#endif + status = mDNS_Deregister_internal(m, &extra->r, mDNS_Dereg_normal); + } + mDNS_Unlock(m); + return(status); + } + +mDNSexport mStatus mDNS_RenameAndReregisterService(mDNS *const m, ServiceRecordSet *const sr, const domainlabel *newname) + { + // NOTE: Don't need to use mDNS_Lock(m) here, because this code is just using public routines + // mDNS_RegisterService() and mDNS_AddRecordToService(), which do the right locking internally. + domainlabel name1, name2; + domainname type, domain; + domainname *host = mDNSNULL; + ExtraResourceRecord *extras = sr->Extras; + mStatus err; + + DeconstructServiceName(sr->RR_SRV.resrec.name, &name1, &type, &domain); + if (!newname) + { + name2 = name1; + IncrementLabelSuffix(&name2, mDNStrue); + newname = &name2; + } + + if (SameDomainName(&domain, &localdomain)) + LogMsg("%##s service renamed from \"%#s\" to \"%#s\"", type.c, name1.c, newname->c); + else LogMsg("%##s service (domain %##s) renamed from \"%#s\" to \"%#s\"",type.c, domain.c, name1.c, 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 port number is zero, that means this was actually registered using mDNS_RegisterNoSuchService() + if (!sr->RR_SRV.resrec.rdata->u.srv.port.NotAnInteger) return(mDNS_DeregisterNoSuchService(m, &sr->RR_SRV)); + +#ifndef UNICAST_DISABLED + if (!(sr->RR_SRV.resrec.InterfaceID == mDNSInterface_LocalOnly || IsLocalDomain(sr->RR_SRV.resrec.name))) + { + mStatus status; + mDNS_Lock(m); + status = uDNS_DeregisterService(m, sr); + mDNS_Unlock(m); + return(status); + } +#endif + if (sr->RR_PTR.resrec.RecordType == kDNSRecordTypeUnregistered) + { + debugf("Service set for %##s already deregistered", sr->RR_SRV.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_SRV.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, kHostNameTTL, 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, kStandardTTL, 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 + +mDNSlocal void mDNS_GrowCache_internal(mDNS *const m, CacheEntity *storage, mDNSu32 numrecords) + { + if (storage && numrecords) + { + mDNSu32 i; + debugf("Adding cache storage for %d more records (%d bytes)", numrecords, numrecords*sizeof(CacheEntity)); + for (i=0; irrcache_free; + m->rrcache_free = storage; + m->rrcache_size += numrecords; + } + } + +mDNSexport void mDNS_GrowCache(mDNS *const m, CacheEntity *storage, mDNSu32 numrecords) + { + mDNS_Lock(m); + mDNS_GrowCache_internal(m, storage, numrecords); + mDNS_Unlock(m); + } + +mDNSexport mStatus mDNS_Init(mDNS *const m, mDNS_PlatformSupport *const p, + CacheEntity *rrcachestorage, mDNSu32 rrcachesize, + mDNSBool AdvertiseLocalAddresses, mDNSCallback *Callback, void *Context) + { + mDNSu32 slot; + mDNSs32 timenow; + mStatus result; + + if (!rrcachestorage) rrcachesize = 0; + + m->p = p; + m->KnownBugs = 0; + m->CanReceiveUnicastOn5353 = mDNSfalse; // Assume we can't receive unicasts on 5353, unless platform layer tells us otherwise + m->AdvertiseLocalAddresses = AdvertiseLocalAddresses; + m->mDNSPlatformStatus = mStatus_Waiting; + m->UnicastPort4 = zeroIPPort; + m->UnicastPort6 = zeroIPPort; + m->MainCallback = Callback; + m->MainContext = Context; + m->rec.r.resrec.RecordType = 0; + + // 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 + result = mDNSPlatformTimeInit(); + if (result != mStatus_NoError) return(result); + m->timenow_adjust = (mDNSs32)mDNSRandom(0xFFFFFFFF); + timenow = mDNS_TimeNow_NoLock(m); + + m->timenow = 0; // MUST only be set within mDNS_Lock/mDNS_Unlock section + m->timenow_last = timenow; + 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->RandomReconfirmDelay = 0; + m->PktNum = 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; + + mDNS_GrowCache_internal(m, rrcachestorage, rrcachesize); + + // Fields below only required for mDNS Responder... + m->hostlabel.c[0] = 0; + m->nicelabel.c[0] = 0; + m->MulticastHostname.c[0] = 0; + m->HIHardware.c[0] = 0; + m->HISoftware.c[0] = 0; + m->ResourceRecords = mDNSNULL; + m->DuplicateRecords = mDNSNULL; + m->NewLocalRecords = mDNSNULL; + m->CurrentRecord = mDNSNULL; + m->HostInterfaces = mDNSNULL; + m->ProbeFailTime = 0; + m->NumFailedProbes = 0; + m->SuppressProbes = 0; + +#ifndef UNICAST_DISABLED + uDNS_Init(m); + m->SuppressStdPort53Queries = 0; +#endif + result = mDNSPlatformInit(m); + + return(result); + } + +mDNSexport void mDNSCoreInitComplete(mDNS *const m, mStatus result) + { + m->mDNSPlatformStatus = result; + if (m->MainCallback) + { + mDNS_Lock(m); + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + m->MainCallback(m, mStatus_NoError); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + mDNS_Unlock(m); + } + } + +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; + +#ifndef UNICAST_DISABLED + uDNS_Close(m); +#endif + rrcache_totalused = m->rrcache_totalused; + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) + { + while(m->rrcache_hash[slot]) + { + CacheGroup *cg = m->rrcache_hash[slot]; + while (cg->members) + { + CacheRecord *rr = cg->members; + cg->members = cg->members->next; + if (rr->CRActiveQuestion) rrcache_active++; + ReleaseCacheRecord(m, rr); + } + cg->rrcache_tail = &cg->members; + ReleaseCacheGroup(m, &m->rrcache_hash[slot]); + } + } + debugf("mDNS_Close: RR Cache was using %ld records, %lu 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); + + for (intf = m->HostInterfaces; intf; intf = intf->next) + if (intf->Advertise) + 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; + 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); + } + else + m->CurrentRecord = rr->next; + } + + 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 if (m->ResourceRecords) SendResponses(m); + if (m->ResourceRecords) LogMsg("mDNS_Close failed to send goodbye for: %s", ARDisplayString(m, m->ResourceRecords)); + + mDNS_Unlock(m); + debugf("mDNS_Close: mDNSPlatformClose"); + mDNSPlatformClose(m); + debugf("mDNS_Close: done"); + } diff --git a/src/mdns/mDNSDebug.c b/src/mdns/mDNSDebug.c new file mode 100644 index 00000000..2e244928 --- /dev/null +++ b/src/mdns/mDNSDebug.c @@ -0,0 +1,22 @@ +/** + * wrapper for LogMsg to use built-in error logging + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "err.h" + +void LogMsg(const char *format, ...) { + unsigned char buffer[512]; + va_list ptr; + va_start(ptr,format); + memset(buffer,0,sizeof(buffer)); + vsnprintf((char*)buffer, sizeof(buffer), format, ptr); + va_end(ptr); + DPRINTF(E_WARN,L_REND,"%s\n",buffer); +} diff --git a/src/mdns/mDNSDebug.h b/src/mdns/mDNSDebug.h new file mode 100755 index 00000000..591312b8 --- /dev/null +++ b/src/mdns/mDNSDebug.h @@ -0,0 +1,190 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + Change History (most recent first): + +$Log: mDNSDebug.h,v $ +Revision 1.26.2.1 2006/08/29 06:24:22 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.26 2005/07/04 22:40:26 cheshire +Additional debugging code to help catch memory corruption + +Revision 1.25 2004/12/14 21:34:16 cheshire +Add "#define ANSWER_REMOTE_HOSTNAME_QUERIES 0" and comment + +Revision 1.24 2004/09/16 01:58:21 cheshire +Fix compiler warnings + +Revision 1.23 2004/05/18 23:51:25 cheshire +Tidy up all checkin comments to use consistent "" format for bug numbers + +Revision 1.22 2004/04/22 04:27:42 cheshire +Spacing tidyup + +Revision 1.21 2004/04/14 23:21:41 ksekar +Removed accidental checkin of MALLOC_DEBUGING flag in 1.20 + +Revision 1.20 2004/04/14 23:09:28 ksekar +Support for TSIG signed dynamic updates. + +Revision 1.19 2004/03/15 18:57:59 cheshire +Undo last checkin that accidentally made verbose debugging the default for all targets + +Revision 1.18 2004/03/13 01:57:33 ksekar +: DynDNS: Dynamic update of service records + +Revision 1.17 2004/01/28 21:14:23 cheshire +Reconcile debug_mode and gDebugLogging into a single flag (mDNS_DebugMode) + +Revision 1.16 2003/12/09 01:30:06 rpantos +Fix usage of ARGS... macros to build properly on Windows. + +Revision 1.15 2003/12/08 20:55:26 rpantos +Move some definitions here from mDNSMacOSX.h. + +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 +// (If you edit the file here to turn on MDNS_DEBUGMSGS while you're debugging some code, be careful +// not to accidentally check-in that change by mistake when you check in your other changes.) + +//#undef MDNS_DEBUGMSGS +//#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 int mDNS_DebugMode; // If non-zero, LogMsg() writes to stderr instead of syslog +extern void LogMsg(const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2); +extern void LogMsgIdent(const char *ident, const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(2,3); +extern void LogMsgNoIdent(const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2); + +// Set this symbol to 1 to answer remote queries for our Address, reverse mapping PTR, and HINFO records +#define ANSWER_REMOTE_HOSTNAME_QUERIES 0 + +// Set this symbol to 1 to do extra debug checks on malloc() and free() +// Set this symbol to 2 to write a log message for every malloc() and free() +#define MACOSX_MDNS_MALLOC_DEBUGGING 0 + +#if MACOSX_MDNS_MALLOC_DEBUGGING >= 1 +extern void *mallocL(char *msg, unsigned int size); +extern void freeL(char *msg, void *x); +extern void LogMemCorruption(const char *format, ...); +extern void uds_validatelists(void); +#else +#define mallocL(X,Y) malloc(Y) +#define freeL(X,Y) free(Y) +#endif + +#if MACOSX_MDNS_MALLOC_DEBUGGING >= 2 +#define LogMalloc LogMsg +#else + #if (defined( __GNUC__ )) + #define LogMalloc(ARGS...) ((void)0) + #elif (defined( __MWERKS__ )) + #define LogMalloc( ... ) + #else + #define LogMalloc 1 ? ((void)0) : (void) + #endif +#endif + +#define LogAllOperations 0 + +#if LogAllOperations +#define LogOperation LogMsg +#else +#define LogOperation debugf +#endif + +#define ForceAlerts 0 + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/src/mdns/mDNSEmbeddedAPI.h b/src/mdns/mDNSEmbeddedAPI.h new file mode 100755 index 00000000..4b4d8ae1 --- /dev/null +++ b/src/mdns/mDNSEmbeddedAPI.h @@ -0,0 +1,2848 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + + NOTE: + If you're building an application that uses DNS Service Discovery + this is probably NOT the header file you're looking for. + In most cases you will want to use /usr/include/dns_sd.h instead. + + This header file defines the lowest level raw interface to mDNSCore, + which is appropriate *only* on tiny embedded systems where everything + runs in a single address space and memory is extremely constrained. + All the APIs here are malloc-free, which means that the caller is + responsible for passing in a pointer to the relevant storage that + will be used in the execution of that call, and (when called with + correct parameters) all the calls are guaranteed to succeed. There + is never a case where a call can suffer intermittent failures because + the implementation calls malloc() and sometimes malloc() returns NULL + because memory is so limited that no more is available. + This is primarily for devices that need to have precisely known fixed + memory requirements, with absolutely no uncertainty or run-time variation, + but that certainty comes at a cost of more difficult programming. + + For applications running on general-purpose desktop operating systems + (Mac OS, Linux, Solaris, Windows, etc.) the API you should use is + /usr/include/dns_sd.h, which defines the API by which multiple + independent client processes communicate their DNS Service Discovery + requests to a single "mdnsd" daemon running in the background. + + Even on platforms that don't run multiple independent processes in + multiple independent address spaces, you can still use the preferred + dns_sd.h APIs by linking in "dnssd_clientshim.c", which implements + the standard "dns_sd.h" API calls, allocates any required storage + using malloc(), and then calls through to the low-level malloc-free + mDNSCore routines defined here. This has the benefit that even though + you're running on a small embedded system with a single address space, + you can still use the exact same client C code as you'd use on a + general-purpose desktop system. + + + Change History (most recent first): + +$Log: mDNSEmbeddedAPI.h,v $ +Revision 1.296.2.1 2006/08/29 06:24:22 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.296 2006/06/29 05:28:01 cheshire +Added comment about mDNSlocal and mDNSexport + +Revision 1.295 2006/06/29 03:02:43 cheshire + mDNSResponder NXDOMAIN and CNAME support + +Revision 1.294 2006/06/28 06:50:08 cheshire +In future we may want to change definition of mDNSs32 from "signed long" to "signed int" +I doubt anyone is building mDNSResponder on systems where int is 16-bits, +but lets add a compile-time assertion to make sure. + +Revision 1.293 2006/06/12 18:00:43 cheshire +To make code a little more defensive, check _ILP64 before _LP64, +in case both are set by mistake on some platforms + +Revision 1.292 2006/03/19 17:00:57 cheshire +Define symbol MaxMsg instead of using hard-coded constant value '80' + +Revision 1.291 2006/03/19 02:00:07 cheshire + Improve logic for delaying packets after repeated interface transitions + +Revision 1.290 2006/03/08 22:42:23 cheshire +Fix spelling mistake: LocalReverseMapomain -> LocalReverseMapDomain + +Revision 1.289 2006/02/26 00:54:41 cheshire +Fixes to avoid code generation warning/error on FreeBSD 7 + +Revision 1.288 2005/12/21 03:24:58 cheshire + Code changes required to compile on EFI + +Revision 1.287 2005/10/20 00:10:33 cheshire + Add check to avoid crashing NAT gateways that have buggy DNS relay code + +Revision 1.286 2005/09/24 01:09:40 cheshire +Fix comment typos + +Revision 1.285 2005/09/16 20:57:47 cheshire +Add macro mDNS_TimeNow_NoLock(m) to get properly adjusted time without also acquiring lock + +Revision 1.284 2005/07/29 18:04:22 ksekar + Hostname registration should register IPv6 AAAA record with DNS Update + +Revision 1.283 2005/05/13 20:45:09 ksekar + Rapid wide-area txt record updates don't work + +Revision 1.282 2005/03/16 00:42:32 ksekar + Long-lived queries not working on Windows + +Revision 1.281 2005/02/25 17:47:44 ksekar + SendServiceRegistration fails on wake from sleep + +Revision 1.280 2005/02/25 04:21:00 cheshire + mDNS -F returns the same domain multiple times with different casing + +Revision 1.279 2005/02/17 01:56:14 cheshire +Increase ifname field to 64 bytes + +Revision 1.278 2005/02/09 23:38:51 ksekar + Reregister hostname when DNS server changes but IP address does not + +Revision 1.277 2005/02/09 23:31:12 ksekar + NAT-PMP response callback should return a boolean indicating if the packet matched the request + +Revision 1.276 2005/02/01 19:33:29 ksekar + Keychain format too restrictive + +Revision 1.275 2005/01/27 22:57:55 cheshire +Fix compile errors on gcc4 + +Revision 1.274 2005/01/19 21:01:54 ksekar + uDNS needs to support subtype registration and browsing + +Revision 1.273 2005/01/19 19:15:31 ksekar +Refinement to - Simplify mDNS_PurgeResultsForDomain logic and move into daemon layer + +Revision 1.272 2005/01/18 18:10:55 ksekar + Use 10.4 resolver API to get search domains + +Revision 1.271 2005/01/15 00:56:41 ksekar + Unicast services don't disappear when logging +out of VPN + +Revision 1.270 2005/01/14 18:34:22 ksekar + Services registered outside of firewall don't succeed after location change + +Revision 1.269 2005/01/11 22:50:52 ksekar +Fixed constant naming (was using kLLQ_DefLease for update leases) + +Revision 1.268 2004/12/22 22:25:47 ksekar + NATPMP: handle location changes + +Revision 1.267 2004/12/22 00:13:49 ksekar + Change version, port, and polling interval for LLQ + +Revision 1.266 2004/12/18 03:13:45 cheshire + kDNSServiceInterfaceIndexLocalOnly should return all local records + +Revision 1.265 2004/12/17 23:37:45 cheshire + Guard against repeating wireless dissociation/re-association +(and other repetitive configuration changes) + +Revision 1.264 2004/12/17 05:25:46 cheshire + Shorten DNS-SD queries to avoid NAT bugs + +Revision 1.263 2004/12/16 20:40:25 cheshire +Fix compile warnings + +Revision 1.262 2004/12/16 20:13:00 cheshire + Cache memory management improvements + +Revision 1.261 2004/12/14 21:21:20 ksekar + NAT-PMP: Update response format to contain "Seconds Since Boot" + +Revision 1.260 2004/12/12 23:51:42 ksekar + Wide-area registrations should fallback to using DHCP hostname as target + +Revision 1.259 2004/12/11 20:55:29 ksekar + Clean up registration state machines + +Revision 1.258 2004/12/10 20:48:32 cheshire + Need to pick final EDNS numbers for LLQ and GC + +Revision 1.257 2004/12/10 02:09:23 cheshire + Modify default TTLs + +Revision 1.256 2004/12/09 03:15:40 ksekar + use _legacy instead of _default to find "empty string" browse domains + +Revision 1.255 2004/12/07 22:48:37 cheshire +Tidying + +Revision 1.254 2004/12/07 21:26:04 ksekar + DNSServiceRegisterRecord() can crash on deregistration + +Revision 1.253 2004/12/07 20:42:33 cheshire +Add explicit context parameter to mDNS_RemoveRecordFromService() + +Revision 1.252 2004/12/07 03:02:12 ksekar +Fixed comments, grouped unicast-specific routines together + +Revision 1.251 2004/12/06 21:15:22 ksekar + mDNSResponder crashed in CheckServiceRegistrations + +Revision 1.250 2004/12/04 02:12:45 cheshire + mDNSResponder puts LargeCacheRecord on the stack + +Revision 1.249 2004/12/03 05:18:33 ksekar + mDNSResponder needs to return more specific TSIG errors + +Revision 1.248 2004/12/02 20:03:48 ksekar + Still publishes wide-area domains even after switching to a local subnet + +Revision 1.247 2004/12/01 20:57:19 ksekar + Wide Area Service Discovery must be split-DNS aware + +Revision 1.246 2004/11/29 23:26:32 cheshire +Added NonZeroTime() function, which usually returns the value given, with the exception +that if the value given is zero, it returns one instead. For timer values where zero is +used to mean "not set", this can be used to ensure that setting them to the result of an +interval computation (e.g. "now+interval") does not inadvertently result in a zero value. + +Revision 1.245 2004/11/25 01:28:09 cheshire + Need to implement random delay for 'QU' unicast replies (and set cache flush bit too) + +Revision 1.244 2004/11/24 22:00:59 cheshire +Move definition of mDNSAddressIsAllDNSLinkGroup() from mDNSMacOSX.c to mDNSEmbeddedAPI.h + +Revision 1.243 2004/11/23 22:43:53 cheshire +Tidy up code alignment + +Revision 1.242 2004/11/23 03:39:46 cheshire +Let interface name/index mapping capability live directly in JNISupport.c, +instead of having to call through to the daemon via IPC to get this information. + +Revision 1.241 2004/11/22 17:16:19 ksekar + Unicast services don't disappear when you disable all networking + +Revision 1.240 2004/11/19 02:32:43 ksekar +Wide-Area Security: Add LLQ-ID to events + +Revision 1.239 2004/11/15 20:09:23 ksekar + Wide Area support for Add/Remove record + +Revision 1.238 2004/11/12 03:16:48 rpantos +rdar://problem/3809541 Add mDNSPlatformGetInterfaceByName, mDNSPlatformGetInterfaceName + +Revision 1.237 2004/11/10 20:40:53 ksekar + LLQ mobility fragile on non-primary interface + +Revision 1.236 2004/11/01 20:36:11 ksekar + mDNSResponder should not receive Keychain Notifications + +Revision 1.235 2004/11/01 17:48:14 cheshire +Changed SOA serial number back to signed. RFC 1035 may describe it as "unsigned", but +it's wrong. The SOA serial is a modular counter, as explained in "DNS & BIND", page +137. Since C doesn't have a modular type, we used signed, C's closest approximation. + +Revision 1.234 2004/10/29 21:59:02 ksekar +SOA serial should be a unsigned integer, as per RFC 1035 + +Revision 1.233 2004/10/28 03:24:41 cheshire +Rename m->CanReceiveUnicastOn as m->CanReceiveUnicastOn5353 + +Revision 1.232 2004/10/26 06:20:23 cheshire +Add mDNSAddressIsValidNonZero() macro + +Revision 1.231 2004/10/26 06:11:41 cheshire +Add improved logging to aid in diagnosis of mDNSResponder crashed + +Revision 1.230 2004/10/26 03:52:02 cheshire +Update checkin comments + +Revision 1.229 2004/10/25 19:30:52 ksekar + Simplify dynamic host name structures + +Revision 1.228 2004/10/23 01:16:00 cheshire + uDNS operations not always reliable on multi-homed hosts + +Revision 1.227 2004/10/22 20:52:07 ksekar + Create NAT port mappings for Long Lived Queries + +Revision 1.226 2004/10/20 01:50:40 cheshire + Cannot resolve non-local registrations using the mach API +Implemented ForceMCast mode for AuthRecords as well as for Questions + +Revision 1.225 2004/10/19 21:33:17 cheshire + Cannot resolve non-local registrations using the mach API +Added flag 'kDNSServiceFlagsForceMulticast'. Passing through an interface id for a unicast name +doesn't force multicast unless you set this flag to indicate explicitly that this is what you want + +Revision 1.224 2004/10/16 00:16:59 cheshire + Replace IP TTL 255 check with local subnet source address check + +Revision 1.223 2004/10/15 23:00:17 ksekar + Need to update LLQs on location changes + +Revision 1.222 2004/10/12 02:49:20 ksekar + Clean up LLQ sleep/wake, error handling + +Revision 1.221 2004/10/10 06:57:15 cheshire +Change definition of "localdomain" to make code compile a little smaller + +Revision 1.220 2004/10/06 01:44:19 cheshire + Resolving too quickly sometimes returns stale TXT record + +Revision 1.219 2004/10/03 23:18:58 cheshire +Move address comparison macros from DNSCommon.h to mDNSEmbeddedAPI.h + +Revision 1.218 2004/10/03 23:14:12 cheshire +Add "mDNSEthAddr" type and "zeroEthAddr" constant + +Revision 1.217 2004/09/30 00:24:56 ksekar + Dynamically update default registration domains on config change + +Revision 1.216 2004/09/27 23:24:32 cheshire +Fix typo: SOA refresh interval is supposed to be unsigned + +Revision 1.215 2004/09/26 23:20:35 ksekar + Allow default registrations in multiple wide-area domains + +Revision 1.214 2004/09/25 02:41:39 cheshire + Deliver near-pending "remove" events before new "add" events + +Revision 1.213 2004/09/25 02:24:27 cheshire +Removed unused rr->UseCount + +Revision 1.212 2004/09/24 20:57:39 cheshire + Eliminate inappropriate casts that cause misaligned-address errors + +Revision 1.211 2004/09/24 20:33:22 cheshire +Remove unused DNSDigest_MD5 declaration + +Revision 1.210 2004/09/23 20:21:07 cheshire + Refine "immediate answer burst; restarting exponential backoff sequence" logic +Associate a unique sequence number with each received packet, and only increment the count of recent answer +packets if the packet sequence number for this answer record is not one we've already seen and counted. + +Revision 1.209 2004/09/23 20:14:39 cheshire +Rename "question->RecentAnswers" to "question->RecentAnswerPkts" + +Revision 1.208 2004/09/23 00:50:53 cheshire + Don't send a (DE) if a service is unregistered after wake from sleep + +Revision 1.207 2004/09/22 02:34:46 cheshire +Move definitions of default TTL times from mDNS.c to mDNSEmbeddedAPI.h + +Revision 1.206 2004/09/22 00:41:59 cheshire +Move tcp connection status codes into the legal range allocated for mDNS use + +Revision 1.205 2004/09/21 23:40:11 ksekar + mDNSResponder to return errors on NAT traversal failure + +Revision 1.204 2004/09/21 23:29:50 cheshire + DNSServiceResolve should delay sending packets + +Revision 1.203 2004/09/21 20:58:22 cheshire +Add ifname field to NetworkInterfaceInfo_struct + +Revision 1.202 2004/09/17 00:46:34 cheshire +mDNS_TimeNow should take const mDNS parameter + +Revision 1.201 2004/09/17 00:31:51 cheshire +For consistency with ipv6, renamed rdata field 'ip' to 'ipv4' + +Revision 1.200 2004/09/17 00:19:10 cheshire +For consistency with AllDNSLinkGroupv6, rename AllDNSLinkGroup to AllDNSLinkGroupv4 + +Revision 1.199 2004/09/16 21:59:16 cheshire +For consistency with zerov6Addr, rename zeroIPAddr to zerov4Addr + +Revision 1.198 2004/09/16 21:36:36 cheshire + Fix unsafe use of mDNSPlatformTimeNow() +Changes to add necessary locking calls around unicast DNS operations + +Revision 1.197 2004/09/16 00:24:48 cheshire + Fix unsafe use of mDNSPlatformTimeNow() + +Revision 1.196 2004/09/14 23:42:35 cheshire + Need to seed random number generator from platform-layer data + +Revision 1.195 2004/09/14 23:27:46 cheshire +Fix compile errors + +Revision 1.194 2004/09/10 00:49:57 cheshire + Add error code kDNSServiceErr_Firewall, for future use + +Revision 1.193 2004/09/03 19:23:05 ksekar +: Need retransmission mechanism for wide-area service registrations + +Revision 1.192 2004/09/02 03:48:47 cheshire + Disable targeted unicast query support by default +1. New flag kDNSServiceFlagsAllowRemoteQuery to indicate we want to allow remote queries for this record +2. New field AllowRemoteQuery in AuthRecord structure +3. uds_daemon.c sets AllowRemoteQuery if kDNSServiceFlagsAllowRemoteQuery is set +4. mDNS.c only answers remote queries if AllowRemoteQuery is set + +Revision 1.191 2004/08/25 00:37:27 ksekar +: Cleanup DynDNS hostname registration code + +Revision 1.190 2004/08/18 17:35:41 ksekar +: Feature #9586: Need support for Legacy NAT gateways + +Revision 1.189 2004/08/14 03:22:41 cheshire + Dynamic DNS UI <-> mDNSResponder glue +Add GetUserSpecifiedDDNSName() routine +Convert ServiceRegDomain to domainname instead of C string +Replace mDNS_GenerateFQDN/mDNS_GenerateGlobalFQDN with mDNS_SetFQDNs + +Revision 1.188 2004/08/13 23:46:58 cheshire +"asyncronous" -> "asynchronous" + +Revision 1.187 2004/08/13 23:37:02 cheshire +Now that we do both uDNS and mDNS, global replace "uDNS_info.hostname" with +"uDNS_info.UnicastHostname" for clarity + +Revision 1.186 2004/08/13 23:25:00 cheshire +Now that we do both uDNS and mDNS, global replace "m->hostname" with +"m->MulticastHostname" for clarity + +Revision 1.185 2004/08/12 00:32:36 ksekar +: LLQ Refreshes never terminate if unanswered + +Revision 1.184 2004/08/11 17:09:31 cheshire +Add comment clarifying the applicability of these APIs + +Revision 1.183 2004/08/10 23:19:14 ksekar +: DNS Extension daemon for Wide Area Service Discovery +Moved routines/constants to allow extern access for garbage collection daemon + +Revision 1.182 2004/07/30 17:40:06 ksekar +: TXT Record updates not available for wide-area services + +Revision 1.181 2004/07/29 19:27:15 ksekar +NATPMP Support - minor fixes and cleanup + +Revision 1.180 2004/07/29 02:03:35 ksekar +Delete unused #define and structure field + +Revision 1.179 2004/07/26 22:49:30 ksekar +: Feature #9516: Need support for NATPMP in client + +Revision 1.178 2004/07/13 21:24:24 rpantos +Fix for . + +Revision 1.177 2004/06/05 00:04:26 cheshire +: wide-area domains should be returned in reg. domain enumeration + +Revision 1.176 2004/06/04 08:58:29 ksekar +: Keychain integration for secure dynamic update + +Revision 1.175 2004/06/04 00:15:06 cheshire +Move misplaced brackets + +Revision 1.174 2004/06/03 23:30:16 cheshire +Remove extraneous blank lines and white space + +Revision 1.173 2004/06/03 03:09:58 ksekar +: Garbage Collection for Dynamic Updates + +Revision 1.172 2004/06/01 23:46:50 ksekar +: DynDNS: dynamically look up LLQ/Update ports + +Revision 1.171 2004/05/28 23:42:37 ksekar +: Feature: DNS server->client notification on record changes (#7805) + +Revision 1.170 2004/05/18 23:51:25 cheshire +Tidy up all checkin comments to use consistent "" format for bug numbers + +Revision 1.169 2004/05/13 04:54:20 ksekar +Unified list copy/free code. Added symetric list for + +Revision 1.168 2004/05/12 22:03:09 ksekar +Made GetSearchDomainList a true platform-layer call (declaration moved +from mDNSMacOSX.h to mDNSEmbeddedAPI.h), implemented to return "local" +only on non-OSX platforms. Changed call to return a copy of the list +to avoid shared memory issues. Added a routine to free the list. + +Revision 1.167 2004/04/22 04:07:01 cheshire +Fix from Bob Bradley: Don't try to do inline functions on compilers that don't support it + +Revision 1.166 2004/04/22 03:15:56 cheshire +Fix use of "struct __attribute__((__packed__))" so it only applies on GCC >= 2.9 + +Revision 1.165 2004/04/22 03:05:28 cheshire +kDNSClass_ANY should be kDNSQClass_ANY + +Revision 1.164 2004/04/21 02:55:03 cheshire +Update comments describing 'InterfaceActive' field + +Revision 1.163 2004/04/21 02:49:11 cheshire +To reduce future confusion, renamed 'TxAndRx' to 'McastTxRx' + +Revision 1.162 2004/04/15 00:51:28 bradley +Minor tweaks for Windows and C++ builds. Added casts for signed/unsigned integers and 64-bit pointers. +Prefix some functions with mDNS to avoid conflicts. Disable benign warnings on Microsoft compilers. + +Revision 1.161 2004/04/14 23:09:28 ksekar +Support for TSIG signed dynamic updates. + +Revision 1.160 2004/04/09 17:40:26 cheshire +Remove unnecessary "Multicast" field -- it duplicates the semantics of the existing McastTxRx field + +Revision 1.159 2004/04/09 16:37:15 cheshire +Suggestion from Bob Bradley: +Move NumCacheRecordsForInterfaceID() to DNSCommon.c so it's available to all platform layers + +Revision 1.158 2004/04/02 19:38:33 cheshire +Update comment about typical RR TTLs + +Revision 1.157 2004/04/02 19:35:53 cheshire +Add clarifying comments about legal mDNSInterfaceID values + +Revision 1.156 2004/04/02 19:19:48 cheshire +Add code to do optional logging of multi-packet KA list time intervals + +Revision 1.155 2004/03/24 00:29:45 ksekar +Make it safe to call StopQuery in a unicast question callback + +Revision 1.154 2004/03/20 01:05:49 cheshire +Test __LP64__ and __ILP64__ to compile properly on a wider range of 64-bit architectures + +Revision 1.153 2004/03/13 01:57:33 ksekar +: DynDNS: Dynamic update of service records + +Revision 1.152 2004/03/09 02:27:16 cheshire +Remove erroneous underscore in 'packed_struct' (makes no difference now, but might in future) + +Revision 1.151 2004/03/02 03:21:56 cheshire + Properly support "_services._dns-sd._udp" meta-queries + +Revision 1.150 2004/02/21 02:06:24 cheshire +Can't use anonymous unions -- they're non-standard and don't work on all compilers + +Revision 1.149 2004/02/06 23:04:19 ksekar +Basic Dynamic Update support via mDNS_Register (dissabled via +UNICAST_REGISTRATION #define) + +Revision 1.148 2004/02/03 19:47:36 ksekar +Added an asynchronous state machine mechanism to uDNS.c, including +calls to find the parent zone for a domain name. Changes include code +in repository previously dissabled via "#if 0 incomplete". Codepath +is currently unused, and will be called to create update records, etc. + +Revision 1.147 2004/02/03 18:57:35 cheshire +Update comment for "IsLocalDomain()" + +Revision 1.146 2004/01/30 02:20:24 bradley +Map inline to __inline when building with Microsoft C compilers since they do not support C99 inline. + +Revision 1.145 2004/01/29 02:59:17 ksekar +Unicast DNS: Changed from a resource record oriented question/response +matching to packet based matching. New callback architecture allows +collections of records in a response to be processed differently +depending on the nature of the request, and allows the same structure +to be used for internal and client-driven queries with different processing needs. + +Revision 1.144 2004/01/28 20:20:45 ksekar +Unified ActiveQueries and ActiveInternalQueries lists, using a flag to +demux them. Check-in includes work-in-progress code, #ifdef'd out. + +Revision 1.143 2004/01/28 03:41:00 cheshire +: Need ability to do targeted queries as well as multicast queries + +Revision 1.142 2004/01/28 02:30:07 ksekar +Added default Search Domains to unicast browsing, controlled via +Networking sharing prefs pane. Stopped sending unicast messages on +every interface. Fixed unicast resolving via mach-port API. + +Revision 1.141 2004/01/27 20:15:22 cheshire +: Time to prune obsolete code for listening on port 53 + +Revision 1.140 2004/01/24 23:37:08 cheshire +At Kiren's suggestion, made functions to convert mDNSOpaque16s to/from integer values + +Revision 1.139 2004/01/24 08:46:26 bradley +Added InterfaceID<->Index platform interfaces since they are now used by all platforms for the DNS-SD APIs. + +Revision 1.138 2004/01/24 04:59:15 cheshire +Fixes so that Posix/Linux, OS9, Windows, and VxWorks targets build again + +Revision 1.137 2004/01/24 03:40:56 cheshire +Move mDNSAddrIsDNSMulticast() from DNSCommon.h to mDNSEmbeddedAPI.h so clients can use it + +Revision 1.136 2004/01/24 03:38:27 cheshire +Fix minor syntactic error: Headers should use "extern" declarations, not "mDNSexport" + +Revision 1.135 2004/01/23 23:23:15 ksekar +Added TCP support for truncated unicast messages. + +Revision 1.134 2004/01/22 03:54:11 cheshire +Create special meta-interface 'mDNSInterface_ForceMCast' (-2), +which means "do this query via multicast, even if it's apparently a unicast domain" + +Revision 1.133 2004/01/22 03:48:41 cheshire +Make sure uDNS client doesn't accidentally use query ID zero + +Revision 1.132 2004/01/22 03:43:08 cheshire +Export constants like mDNSInterface_LocalOnly so that the client layers can use them + +Revision 1.131 2004/01/21 21:53:18 cheshire +: Don't try to receive unicast responses if we're not the first to bind to the UDP port + +Revision 1.130 2003/12/14 05:05:29 cheshire +Add comments explaining mDNS_Init_NoCache and mDNS_Init_ZeroCacheSize + +Revision 1.129 2003/12/13 03:05:27 ksekar +: DynDNS: Unicast query of service records + +Revision 1.128 2003/12/01 21:44:23 cheshire +Add mStatus_BadInterfaceErr = -65552 for consistency with dns_sd.h + +Revision 1.127 2003/12/01 18:26:37 cheshire +Also pack the OpaqueXX union types. Otherwise, on some systems, mDNSOpaque16 is four bytes! + +Revision 1.126 2003/12/01 18:23:48 cheshire +: Scalar size problem in mDNS code on some 64-bit architectures + +Revision 1.125 2003/11/22 00:18:27 cheshire +Add compile-time asserts to verify correct sizes of mDNSu32, mDNSOpaque16, etc. + +Revision 1.124 2003/11/20 22:59:54 cheshire +Changed runtime checks in mDNS.c to be compile-time checks in mDNSEmbeddedAPI.h +Thanks to Bob Bradley for suggesting the ingenious compiler trick to make this work. + +Revision 1.123 2003/11/20 22:53:01 cheshire +Add comment about MAX_ESCAPED_DOMAIN_LABEL + +Revision 1.122 2003/11/20 20:49:53 cheshire +Another fix from HP: Use packedstruct macro to ensure proper packing for on-the-wire packet structures + +Revision 1.121 2003/11/20 05:01:38 cheshire +Update comments; add explanation of Advertise/DontAdvertiseLocalAddresses + +Revision 1.120 2003/11/14 20:59:08 cheshire +Clients can't use AssignDomainName macro because mDNSPlatformMemCopy is defined in mDNSPlatformFunctions.h. +Best solution is just to combine mDNSEmbeddedAPI.h and mDNSPlatformFunctions.h into a single file. + +Revision 1.119 2003/11/14 19:47:52 cheshire +Define symbol MAX_ESCAPED_DOMAIN_NAME to indicate recommended buffer size for ConvertDomainNameToCString + +Revision 1.118 2003/11/14 19:18:34 cheshire +Move AssignDomainName macro to mDNSEmbeddedAPI.h to that client layers can use it too + +Revision 1.117 2003/11/08 23:32:24 cheshire +Gave name to anonymous struct, to avoid errors on certain compilers. +(Thanks to ramaprasad.kr@hp.com for reporting this.) + +Revision 1.116 2003/11/07 03:32:56 cheshire + mDNSResponder delivers answers in inconsistent order +This is the real fix. Checkin 1.312 was overly simplistic; Calling GetFreeCacheRR() can sometimes +purge records from the cache, causing tail pointer *rp to be stale on return. The correct fix is +to maintain a system-wide tail pointer for each cache slot, and then if neccesary GetFreeCacheRR() +can update this pointer, so that mDNSCoreReceiveResponse() appends records in the right place. + +Revision 1.115 2003/09/23 00:53:54 cheshire +NumFailedProbes should be unsigned + +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 mDNSEmbeddedAPI.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 DNS-SD 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 + + Added a hash to lookup records in the cache. + +Revision 1.41 2003/04/15 18:09:13 jgraessl + + +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 + 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 + 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 + 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 + Xserve RAID needs to do interface-specific registrations +Reviewed by: Josh Graessley, Bob Bradley + +Revision 1.32 2003/01/31 03:35:59 cheshire + 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 + 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: + computer name changes not handled properly + service name changes are not properly handled + 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-existence +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 + +#if defined(EFI32) || defined(EFI64) +// EFI doesn't have stdarg.h +#include "Tiano.h" +#define va_list VA_LIST +#define va_start(a, b) VA_START(a, b) +#define va_end(a) VA_END(a) +#define va_arg(a, b) VA_ARG(a, b) +#else +#include // stdarg.h is required for for va_list support for the mDNS_vsnprintf declaration +#endif + +#include "mDNSDebug.h" + +#ifdef __cplusplus + extern "C" { +#endif + +// *************************************************************************** +// Function scope indicators + +// If you see "mDNSlocal" before a function name in a C file, it means the function is not callable outside this file +#ifndef mDNSlocal +#define mDNSlocal static +#endif +// If you see "mDNSexport" before a symbol in a C file, it means the symbol is exported for use by clients +// For every "mDNSexport" in a C file, there needs to be a corresponding "extern" declaration in some header file +// (When a C file #includes a header file, the "extern" declarations tell the compiler: +// "This symbol exists -- but not necessarily in this C file.") +#ifndef mDNSexport +#define mDNSexport +#endif + +// Explanation: These local/export markers are a little habit of mine for signaling the programmers' intentions. +// When "mDNSlocal" is just a synonym for "static", and "mDNSexport" is a complete no-op, you could be +// forgiven for asking what purpose they serve. The idea is that if you see "mDNSexport" in front of a +// function definition it means the programmer intended it to be exported and callable from other files +// in the project. If you see "mDNSlocal" in front of a function definition it means the programmer +// intended it to be private to that file. If you see neither in front of a function definition it +// means the programmer forgot (so you should work out which it is supposed to be, and fix it). +// Using "mDNSlocal" instead of "static" makes it easier to do a textual searches for one or the other. +// For example you can do a search for "static" to find if any functions declare any local variables as "static" +// (generally a bad idea unless it's also "const", because static storage usually risks being non-thread-safe) +// without the results being cluttered with hundreds of matches for functions declared static. +// - Stuart Cheshire + +// *************************************************************************** +// Structure packing macro + +// If we're not using GNUC, it's not fatal. +// Most compilers naturally pack the on-the-wire structures correctly anyway, so a plain "struct" is usually fine. +// In the event that structures are not packed correctly, mDNS_Init() will detect this and report an error, so the +// developer will know what's wrong, and can investigate what needs to be done on that compiler to provide proper packing. +#ifndef packedstruct + #if ((__GNUC__ > 2) || ((__GNUC__ == 2) && (__GNUC_MINOR__ >= 9))) + #define packedstruct struct __attribute__((__packed__)) + #define packedunion union __attribute__((__packed__)) + #else + #define packedstruct struct + #define packedunion union + #endif +#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 + kDNSType_OPT = 41, // EDNS0 OPT record + kDNSType_TSIG = 250, // 250 Transaction Signature + + 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; + +// says +// __LP64__ _LP64 +// These macros are defined, with value 1, if (and only if) the compilation is +// for a target where long int and pointer both use 64-bits and int uses 32-bit. +// says +// Macro Name __LP64__ Value 1 +// A quick Google search for "defined(__LP64__)" OR "#ifdef __LP64__" gives 2590 hits and +// a search for "#if __LP64__" gives only 12, so I think we'll go with the majority and use defined() +#if defined(_ILP64) || defined(__ILP64__) +typedef signed int32 mDNSs32; +typedef unsigned int32 mDNSu32; +#elif defined(_LP64) || defined(__LP64__) +typedef signed int mDNSs32; +typedef unsigned int mDNSu32; +#else +typedef signed long mDNSs32; +typedef unsigned long mDNSu32; +//typedef signed int mDNSs32; +//typedef unsigned int mDNSu32; +#endif + +// 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 mDNSInterfaceID_dummystruct { 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 packedunion { mDNSu8 b[ 2]; mDNSu16 NotAnInteger; } mDNSOpaque16; +typedef packedunion { mDNSu8 b[ 4]; mDNSu32 NotAnInteger; } mDNSOpaque32; +typedef packedunion { mDNSu8 b[ 6]; mDNSu16 w[3]; mDNSu32 l[1]; } mDNSOpaque48; +typedef packedunion { 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) +typedef mDNSOpaque48 mDNSEthAddr; // An Ethernet address is a six-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, // First value: 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, + mStatus_Firewall = -65550, + mStatus_Incompatible = -65551, + mStatus_BadInterfaceErr = -65552, + mStatus_Refused = -65553, + mStatus_NoSuchRecord = -65554, + mStatus_NoAuth = -65555, + mStatus_NoSuchKey = -65556, + mStatus_NATTraversal = -65557, + mStatus_DoubleNAT = -65558, + mStatus_BadTime = -65559, + mStatus_BadSig = -65560, // while we define this per RFC 2845, BIND 9 returns Refused for bad/missing signatures + mStatus_BadKey = -65561, + mStatus_TransientErr = -65562, // transient failures, e.g. sending packets shortly after a network transition or wake from sleep + // -65563 to -65786 currently unused; available for allocation + + // tcp connection status + mStatus_ConnPending = -65787, + mStatus_ConnFailed = -65788, + mStatus_ConnEstablished = -65789, + + // Non-error values: + mStatus_GrowCache = -65790, + mStatus_ConfigChanged = -65791, + mStatus_MemFree = -65792 // Last value: 0xFFFE FF00 + + // mStatus_MemFree is the last legal mDNS error code, at the end of the range allocated for mDNS + }; + +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 + +// Most records have a TTL of 75 minutes, so that their 80% cache-renewal query occurs once per hour. +// For records containing a hostname (in the name on the left, or in the rdata on the right), +// like A, AAAA, reverse-mapping PTR, and SRV, we use a two-minute TTL by default, because we don't want +// them to hang around for too long in the cache if the host in question crashes or otherwise goes away. +// Wide-area service discovery records have a very short TTL to avoid poluting intermediate caches with +// dynamic records. When discovered via Long Lived Queries (with change notifications), resource record +// TTLs can be safely ignored. + +#define kStandardTTL (3600UL * 100 / 80) +#define kHostNameTTL 120UL +#define kWideAreaTTL 3 + +#define DefaultTTLforRRType(X) (((X) == kDNSType_A || (X) == kDNSType_AAAA || (X) == kDNSType_SRV) ? kHostNameTTL : kStandardTTL) + +// *************************************************************************** +#if 0 +#pragma mark - DNS Message structures +#endif + +#define mDNS_numZones numQuestions +#define mDNS_numPrereqs numAnswers +#define mDNS_numUpdates numAuthorities + +typedef packedstruct + { + 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 packedstruct + { + DNSMessageHeader h; // Note: Size 12 bytes + mDNSu8 data[AbsoluteMaxDNSMessageData]; // 40 (IPv6) + 8 (UDP) + 12 (DNS header) + 8940 (data) = 9000 + } DNSMessage; + +// *************************************************************************** +#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. a few minutes) +// -- 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 + // For Dynamic Update records, Known Unique means the record must already exist on the server. + kDNSRecordTypeUniqueMask = (kDNSRecordTypeUnique | kDNSRecordTypeVerified | kDNSRecordTypeKnownUnique), + kDNSRecordTypeActiveMask = (kDNSRecordTypeAdvisory | kDNSRecordTypeShared | kDNSRecordTypeVerified | kDNSRecordTypeKnownUnique), + + kDNSRecordTypePacketAdd = 0x80, // Received in the Additional Section of a DNS Response + kDNSRecordTypePacketAddUnique = 0x90, // Received in the Additional Section of a DNS Response with kDNSClass_UniqueRRSet set + kDNSRecordTypePacketAuth = 0xA0, // Received in the Authorities Section of a DNS Response + kDNSRecordTypePacketAuthUnique = 0xB0, // Received in the Authorities Section of a DNS Response with kDNSClass_UniqueRRSet set + kDNSRecordTypePacketAns = 0xC0, // Received in the Answer Section of a DNS Response + kDNSRecordTypePacketAnsUnique = 0xD0, // Received in the Answer Section of a DNS Response with kDNSClass_UniqueRRSet set + + kDNSRecordTypePacketAnsMask = 0x40, // True for PacketAns and PacketAnsUnique + kDNSRecordTypePacketUniqueMask = 0x10 // True for PacketAddUnique, PacketAnsUnique, PacketAuthUnique + }; + +typedef packedstruct { mDNSu16 priority; mDNSu16 weight; mDNSIPPort port; domainname target; } rdataSRV; +typedef packedstruct { mDNSu16 preference; domainname exchange; } rdataMX; +typedef packedstruct + { + domainname mname; + domainname rname; + mDNSs32 serial; // Modular counter; increases when zone changes + mDNSu32 refresh; // Time in seconds that a slave waits after successful replication of the database before it attempts replication again + mDNSu32 retry; // Time in seconds that a slave waits after an unsuccessful replication attempt before it attempts replication again + mDNSu32 expire; // Time in seconds that a slave holds on to old data while replication attempts remain unsuccessful + mDNSu32 min; // Nominally the minimum record TTL for this zone, in seconds; also used for negative caching. + } rdataSOA; + +typedef packedstruct + { + mDNSu16 vers; + mDNSu16 llqOp; + mDNSu16 err; + mDNSu8 id[8]; + mDNSu32 lease; + } LLQOptData; + +#define LLQ_OPTLEN ((3 * sizeof(mDNSu16)) + 8 + sizeof(mDNSu32)) +// Windows adds pad bytes to sizeof(LLQOptData). Use this macro when setting length fields or validating option rdata from +// off the wire. Use sizeof(LLQOptData) when dealing with structures (e.g. memcpy). Never memcpy between on-the-wire +// representation and a structure + +// NOTE: rdataOpt format may be repeated an arbitrary number of times in a single resource record +typedef packedstruct + { + mDNSu16 opt; + mDNSu16 optlen; + union { LLQOptData llq; mDNSu32 lease; } OptData; + } rdataOpt; + +// 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 68 +// 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 +// Note that since CacheRecord object and a CacheGroup object are allocated out of the same pool, it's sensible to +// have them both be the same size. Making one smaller without making the other smaller won't actually save any memory. +#define InlineCacheRDSize 68 + +#define InlineCacheGroupNameSize 144 + +typedef union + { + mDNSu8 data[StandardAuthRDSize]; + mDNSv4Addr ipv4; // For 'A' record + mDNSv6Addr ipv6; // For 'AAAA' record + domainname name; // For PTR, NS, and CNAME records + UTF8str255 txt; // For TXT record + rdataSRV srv; // For SRV record + rdataMX mx; // For MX record + rdataSOA soa; // For SOA record + rdataOpt opt; // For eDNS0 opt 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 CacheGroup_struct CacheGroup; +typedef struct DNSQuestion_struct DNSQuestion; +typedef struct mDNS_struct mDNS; +typedef struct mDNS_PlatformSupport_struct mDNS_PlatformSupport; +typedef struct NATTraversalInfo_struct NATTraversalInfo; + +// 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); + +typedef 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; // For rdata containing domain name (e.g. PTR, SRV, CNAME etc.), case-insensitive name hash + // else, for all other rdata, 32-bit hash of the raw rdata + // Note: This requirement is important. Various routines like AddAdditionalsToResponseList(), + // ReconfirmAntecedents(), etc., use rdatahash as a pre-flight check to see + // whether it's worth doing a full SameDomainName() call. If the rdatahash + // is not a correct case-insensitive name hash, they'll get false negatives. + RData *rdata; // Pointer to storage for this rdata + } ResourceRecord; + +// Unless otherwise noted, states may apply to either independent record registrations or service registrations +typedef enum + { + regState_FetchingZoneData = 1, // getting info - update not sent + regState_Pending = 2, // update sent, reply not received + regState_Registered = 3, // update sent, reply received + regState_DeregPending = 4, // dereg sent, reply not received + regState_DeregDeferred = 5, // dereg requested while in Pending state - send dereg AFTER registration is confirmed + regState_Cancelled = 6, // update not sent, reg. cancelled by client + regState_Unregistered = 8, // not in any list + regState_Refresh = 9, // outstanding refresh (or target change) message + regState_NATMap = 10, // establishing NAT port mapping or learning public address + regState_UpdatePending = 11, // update in flight as result of mDNS_Update call + regState_NoTarget = 12, // service registration pending registration of hostname (ServiceRegistrations only) + regState_ExtraQueued = 13, // extra record to be registered upon completion of service registration (RecordRegistrations only) + regState_NATError = 14 // unable to complete NAT traversal + } regState_t; + +// context for both ServiceRecordSet and individual AuthRec structs +typedef struct + { + // registration/lease state + regState_t state; + mDNSBool lease; // dynamic update contains (should contain) lease option + mDNSs32 expire; // expiration of lease (-1 for static) + mDNSBool TestForSelfConflict; // on name conflict, check if we're just seeing our own orphaned records + + // identifier to match update request and response + mDNSOpaque16 id; + + // server info + domainname zone; // the zone that is updated + mDNSAddr ns; // primary name server for the record's zone !!!KRS not technically correct to cache longer than TTL + mDNSIPPort port; // port on which server accepts dynamic updates + + // NAT traversal context + NATTraversalInfo *NATinfo; // may be NULL + + // state for deferred operations + mDNSBool ClientCallbackDeferred; // invoke client callback on completion of pending operation(s) + mStatus DeferredStatus; // status to deliver when above flag is set + mDNSBool SRVUpdateDeferred; // do we need to change target or port once current operation completes? + mDNSBool SRVChanged; // temporarily deregistered service because its SRV target or port changed + + // uDNS_UpdateRecord support fields + RData *OrigRData; mDNSu16 OrigRDLen; // previously registered, being deleted + RData *InFlightRData; mDNSu16 InFlightRDLen; // currently being registered + RData *QueuedRData; mDNSu16 QueuedRDLen; // if the client call Update while an update is in flight, we must finish the + // pending operation (re-transmitting if necessary) THEN register the queued update + mDNSRecordUpdateCallback *UpdateRDCallback; // client callback to free old rdata + } uDNS_RegInfo; + +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 + // Field Group 1: Common ResourceRecord fields + ResourceRecord resrec; + uDNS_RegInfo uDNS_info; + + // Field Group 2: 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, and to free memory asynchronously on deregistration + 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 + mDNSu8 AllowRemoteQuery; // Set if we allow hosts not on the local link to query this record + mDNSu8 ForceMCast; // Set by client to advertise solely via multicast, even for apparently unicast names + + // Field Group 3: 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 RequireGoodbye; // Set if this RR has been announced on the wire and will require a goodbye packet + mDNSu8 LocalAnswer; // Set if this RR has been delivered to LocalOnly questions + 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) + mDNSu8 ImmedUnicast; // Set if we may send our response directly via unicast to the requester +#if MDNS_LOG_ANSWER_SUPPRESSION_TIMES + mDNSs32 ImmedAnswerMarkTime; +#endif + 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 + + domainname namestorage; + 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 + }; + +// Wrapper struct for Auth Records for higher-level code that cannot use the AuthRecord's ->next pointer field +typedef struct ARListElem + { + struct ARListElem *next; + AuthRecord ar; // Note: Must be last struct in field to accomodate oversized AuthRecords + } ARListElem; + +struct CacheGroup_struct // Header object for a list of CacheRecords with the same name + { + CacheGroup *next; // Next CacheGroup object in this hash table bucket + mDNSu32 namehash; // Name-based (i.e. case insensitive) hash of name + CacheRecord *members; // List of CacheRecords with this same name + CacheRecord **rrcache_tail; // Tail end of that list + domainname *name; // Common name for all CacheRecords in this list + mDNSu8 namestorage[InlineCacheGroupNameSize]; + }; + +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 DelayDelivery; // Set if we want to defer delivery of this answer to local clients + mDNSs32 NextRequiredQuery; // In platform time units + mDNSs32 LastUsed; // In platform time units + 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 + }; + +// Storage sufficient to hold either a CacheGroup header or a CacheRecord +typedef union CacheEntity_union CacheEntity; +union CacheEntity_union { CacheEntity *next; CacheGroup cg; CacheRecord cr; }; + +typedef struct + { + CacheRecord r; + mDNSu8 _extradata[MaximumRDSize-InlineCacheRDSize]; // Glue on the necessary number of extra bytes + domainname namestorage; // Needs to go *after* the extra rdata bytes + } LargeCacheRecord; + +typedef struct uDNS_HostnameInfo + { + struct uDNS_HostnameInfo *next; + domainname fqdn; + AuthRecord *arv4; // registered IPv4 address record + AuthRecord *arv6; // registered IPv6 address record + mDNSRecordCallback *StatusCallback; // callback to deliver success or error code to client layer + const void *StatusContext; // Client Context + } uDNS_HostnameInfo; + +enum + { + DNSServer_Untested = 0, + DNSServer_Failed = 1, + DNSServer_Passed = 2 + }; + +typedef struct DNSServer + { + struct DNSServer *next; + mDNSAddr addr; + mDNSBool del; // Set when we're planning to delete this from the list + mDNSu32 teststate; // Have we sent bug-detection query to this server? + domainname domain; // name->server matching for "split dns" + } DNSServer; + +typedef struct NetworkInterfaceInfo_struct NetworkInterfaceInfo; + +// A NetworkInterfaceInfo_struct serves two purposes: +// 1. It holds the address, PTR and HINFO records to advertise a given IP address on a given physical interface +// 2. It tells mDNSCore which physical interfaces are available; each physical interface has its own unique InterfaceID. +// Since there may be multiple IP addresses on a single physical interface, +// there may be multiple NetworkInterfaceInfo_structs with the same InterfaceID. +// In this case, to avoid sending the same packet n times, when there's more than one +// struct with the same InterfaceID, mDNSCore picks one member of the set to be the +// active representative of the set; all others have the 'InterfaceActive' flag unset. + +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; // Set if interface is sending & receiving packets (see comment above) + 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; // Identifies physical interface; MUST NOT be 0, -1, or -2 + mDNSAddr ip; // The IPv4 or IPv6 address to advertise + mDNSAddr mask; + char ifname[64]; // Windows uses a GUID string for the interface name, which doesn't fit in 16 bytes + mDNSBool Advertise; // False if you are only searching on this interface + mDNSBool McastTxRx; // Send/Receive multicast on this { InterfaceID, address family } ? + }; + +typedef struct ExtraResourceRecord_struct ExtraResourceRecord; +struct ExtraResourceRecord_struct + { + ExtraResourceRecord *next; + mDNSu32 ClientID; // Opaque ID field to be used by client to map an AddRecord call to a set of Extra records + 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. + ServiceRecordSet *next; + uDNS_RegInfo uDNS_info; + 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._dns-sd._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; + +typedef enum + { + // Setup states + LLQ_UnInit = 0, + LLQ_GetZoneInfo = 1, + LLQ_InitialRequest = 2, + LLQ_SecondaryRequest = 3, + LLQ_Refresh = 4, + LLQ_Retry = 5, + LLQ_Established = 6, + LLQ_Suspended = 7, + LLQ_SuspendDeferred = 8, // suspend once we get zone info + LLQ_SuspendedPoll = 9, // suspended from polling state + LLQ_NatMapWait = 10, + + // Established/error states + LLQ_Static = 16, + LLQ_Poll = 17, + LLQ_Error = 18, + LLQ_Cancelled = 19 + } LLQ_State; + +typedef struct + { + LLQ_State state; + mDNSAddr servAddr; + mDNSIPPort servPort; + DNSQuestion *question; + mDNSu32 origLease; // seconds (relative) + mDNSs32 retry; // ticks (absolute) + mDNSs32 expire; // ticks (absolute) + mDNSs16 ntries; + mDNSu8 id[8]; + mDNSBool deriveRemovesOnResume; + mDNSBool NATMap; // does this LLQ use the global LLQ NAT mapping? + } LLQ_Info; + +// LLQ constants +#define kDNSOpt_LLQ 1 +#define kDNSOpt_Lease 2 +#define kLLQ_Vers 1 +#define kLLQ_DefLease 7200 // 2 hours +#define kLLQ_MAX_TRIES 3 // retry an operation 3 times max +#define kLLQ_INIT_RESEND 2 // resend an un-ack'd packet after 2 seconds, then double for each additional +#define kLLQ_DEF_RETRY 1800 // retry a failed operation after 30 minutes +// LLQ Operation Codes +#define kLLQOp_Setup 1 +#define kLLQOp_Refresh 2 +#define kLLQOp_Event 3 + +#define LLQ_OPT_RDLEN ((2 * sizeof(mDNSu16)) + LLQ_OPTLEN) +#define LEASE_OPT_RDLEN (2 * sizeof(mDNSu16)) + sizeof(mDNSs32) + +// LLQ Errror Codes +enum + { + LLQErr_NoError = 0, + LLQErr_ServFull = 1, + LLQErr_Static = 2, + LLQErr_FormErr = 3, + LLQErr_NoSuchLLQ = 4, + LLQErr_BadVers = 5, + LLQErr_UnknownErr = 6 + }; + +typedef void (*InternalResponseHndlr)(mDNS *const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, void *internalContext); +typedef struct + { + mDNSOpaque16 id; + mDNSBool internal; + InternalResponseHndlr responseCallback; // NULL if internal field is false + LLQ_Info *llq; // NULL for 1-shot queries + mDNSBool Answered; // have we received an answer (including NXDOMAIN) for this question? + CacheRecord *knownAnswers; + mDNSs32 RestartTime; // Mark when we restart a suspended query + void *context; + } uDNS_QuestionInfo; + +// 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 DelayAnswering; // Set if we want to defer answering this question until the cache settles + mDNSs32 LastQTime; // Last scheduled transmission of this Q on *all* applicable interfaces + mDNSs32 ThisQInterval; // LastQTime + ThisQInterval is the next scheduled transmission 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 + mDNSs32 LastAnswerPktNum; // The sequence number of the last response packet containing an answer to this Q + mDNSu32 RecentAnswerPkts; // 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 + mDNSInterfaceID FlappingInterface;// Set when an interface goes away, to flag if removes are delivered for this Q + 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 + mDNSu32 RequestUnicast; // Non-zero if we want to send query with kDNSQClass_UnicastResponse bit set + mDNSs32 LastQTxTime; // Last time this Q was sent on one (but not necessarily all) interfaces + uDNS_QuestionInfo uDNS_info; + + // Client API fields: The client must set up these fields *before* calling mDNS_StartQuery() + mDNSInterfaceID InterfaceID; // Non-zero if you want to issue queries only on a single specific IP interface + mDNSAddr Target; // Non-zero if you want to direct queries to a specific unicast target address + mDNSIPPort TargetPort; // Must be set if Target is set + mDNSOpaque16 TargetQID; // Must be set if Target is set + domainname qname; + mDNSu16 qtype; + mDNSu16 qclass; + mDNSBool LongLived; // Set by client for calls to mDNS_StartQuery to indicate LLQs to unicast layer. + mDNSBool ExpectUnique; // Set by client if it's expecting unique RR(s) for this question, not shared RRs + mDNSBool ForceMCast; // Set by client to force mDNS query, even for apparently uDNS names + mDNSBool ReturnCNAME; // Set by client to request callbacks for intermediate CNAME records + 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 - NAT Traversal structures and constants +#endif + +#define NATMAP_INIT_RETRY (mDNSPlatformOneSecond / 4) // start at 250ms w/ exponential decay +#define NATMAP_MAX_RETRY mDNSPlatformOneSecond // back off to once per second +#define NATMAP_MAX_TRIES 3 // for max 3 tries +#define NATMAP_DEFAULT_LEASE (60 * 60) // lease life in seconds +#define NATMAP_VERS 0 +#define NATMAP_RESPONSE_MASK 0x80 + +typedef enum + { + NATOp_AddrRequest = 0, + NATOp_MapUDP = 1, + NATOp_MapTCP = 2 + } NATOp_t; + +enum + { + NATErr_None = 0, + NATErr_Vers = 1, + NATErr_Refused = 2, + NATErr_NetFail = 3, + NATErr_Res = 4, + NATErr_Opcode = 5 + }; + +typedef mDNSu16 NATErr_t; + +typedef enum + { + NATState_Init = 0, + NATState_Request = 1, + NATState_Established = 2, + NATState_Legacy = 3, + NATState_Error = 4, + NATState_Refresh = 5, + NATState_Deleted = 6 + } NATState_t; +// Note: we have no explicit "cancelled" state, where a service/interface is deregistered while we + // have an outstanding NAT request. This is conveyed by the "reg" pointer being set to NULL + +typedef packedstruct + { + mDNSu8 vers; + mDNSu8 opcode; + } NATAddrRequest; + +typedef packedstruct + { + mDNSu8 vers; + mDNSu8 opcode; + mDNSOpaque16 err; + mDNSOpaque32 uptime; + mDNSv4Addr PubAddr; + } NATAddrReply; + +typedef packedstruct + { + mDNSu8 vers; + mDNSu8 opcode; + mDNSOpaque16 unused; + mDNSIPPort priv; + mDNSIPPort pub; + mDNSOpaque32 lease; + } NATPortMapRequest; + +typedef packedstruct + { + mDNSu8 vers; + mDNSu8 opcode; + mDNSOpaque16 err; + mDNSOpaque32 uptime; + mDNSIPPort priv; + mDNSIPPort pub; + mDNSOpaque32 lease; + } NATPortMapReply; + +// Pass NULL for pkt on error (including timeout) +typedef mDNSBool (*NATResponseHndlr)(NATTraversalInfo *n, mDNS *m, mDNSu8 *pkt, mDNSu16 len); + +struct NATTraversalInfo_struct + { + NATOp_t op; + NATResponseHndlr ReceiveResponse; + union { AuthRecord *RecordRegistration; ServiceRecordSet *ServiceRegistration; } reg; + mDNSAddr Router; + mDNSIPPort PublicPort; + union { NATAddrRequest AddrReq; NATPortMapRequest PortReq; } request; + mDNSs32 retry; // absolute time when we retry + mDNSs32 RetryInterval; // delta between time sent and retry + int ntries; + NATState_t state; + NATTraversalInfo *next; + }; + +// *************************************************************************** +#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 + }; + +typedef struct + { + mDNSs32 nextevent; + DNSQuestion *ActiveQueries; //!!!KRS this should be a hashtable (hash on messageID) + DNSQuestion *CurrentQuery; // pointer to ActiveQueries list being examined in a loop. Functions that remove + // elements from the ActiveQueries list must update this pointer (if non-NULL) as necessary. + //!!!KRS do the same for registration lists + ServiceRecordSet *ServiceRegistrations; + AuthRecord *RecordRegistrations; + NATTraversalInfo *NATTraversals; + mDNSu16 NextMessageID; + DNSServer *Servers; // list of DNS servers + mDNSAddr Router; + mDNSAddr AdvertisedV4; // IPv4 address pointed to by hostname + mDNSAddr MappedV4; // Cache of public address if PrimaryIP is behind a NAT + mDNSAddr AdvertisedV6; // IPv6 address pointed to by hostname + NATTraversalInfo *LLQNatInfo; // Nat port mapping to receive LLQ events + domainname ServiceRegDomain; // (going away w/ multi-user support) + struct uDNS_AuthInfo *AuthInfoList; // list of domains requiring authentication for updates. + uDNS_HostnameInfo *Hostnames; // List of registered hostnames + hostname metadata + DNSQuestion ReverseMap; // Reverse-map query to find static hostname for service target + mDNSBool ReverseMapActive; // Is above query active? + domainname StaticHostname; // Current answer to reverse-map query (above) + mDNSBool DelaySRVUpdate; // Delay SRV target/port update to avoid "flap" + mDNSs32 NextSRVUpdate; // Time to perform delayed update + } uDNS_GlobalInfo; + +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 CanReceiveUnicastOn5353; + mDNSBool AdvertiseLocalAddresses; + mStatus mDNSPlatformStatus; + mDNSIPPort UnicastPort4; + mDNSIPPort UnicastPort6; + 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; + #define MaxMsg 120 + char MsgBuffer[MaxMsg]; // Temp storage used while building error log messages + + // Task Scheduling variables + mDNSs32 timenow_adjust; // Correction applied if we ever discover time went backwards + mDNSs32 timenow; // The time that this particular activation of the mDNS code started + mDNSs32 timenow_last; // The time the last time we ran + 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 + mDNSu32 RandomReconfirmDelay; // For de-synchronization of reconfirmation queries on the wire + mDNSs32 PktNum; // Unique sequence number assigned to each received packet + 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 mDNSInterface_LocalOnly + 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; + CacheEntity *rrcache_free; + CacheGroup *rrcache_hash[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 MulticastHostname; // Fully Qualified "dot-local" 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 *NewLocalRecords; // Fresh local-only records not yet delivered to local-only questions + AuthRecord *CurrentRecord; // Next AuthRecord about to be examined + NetworkInterfaceInfo *HostInterfaces; + mDNSs32 ProbeFailTime; + mDNSu32 NumFailedProbes; + mDNSs32 SuppressProbes; + + // unicast-specific data + uDNS_GlobalInfo uDNS_info; + mDNSs32 SuppressStdPort53Queries; // Wait before allowing the next standard unicast query to the user's configured DNS server + + // Fixed storage, to avoid creating large objects on the stack + DNSMessage imsg; // Incoming message received from wire + DNSMessage omsg; // Outgoing message we're building + LargeCacheRecord rec; // Resource Record extracted from received message + }; + +#define FORALL_CACHERECORDS(SLOT,CG,CR) \ + for ((SLOT) = 0; (SLOT) < CACHE_HASH_SLOTS; (SLOT)++) \ + for((CG)=m->rrcache_hash[(SLOT)]; (CG); (CG)=(CG)->next) \ + for ((CR) = (CG)->members; (CR); (CR)=(CR)->next) + +// *************************************************************************** +#if 0 +#pragma mark - Useful Static Constants +#endif + +extern const mDNSIPPort zeroIPPort; +extern const mDNSv4Addr zerov4Addr; +extern const mDNSv6Addr zerov6Addr; +extern const mDNSEthAddr zeroEthAddr; +extern const mDNSv4Addr onesIPv4Addr; +extern const mDNSv6Addr onesIPv6Addr; +extern const mDNSAddr zeroAddr; + +extern const mDNSInterfaceID mDNSInterface_Any; // Zero +extern const mDNSInterfaceID mDNSInterface_LocalOnly; // Special value + +extern const mDNSIPPort UnicastDNSPort; +extern const mDNSIPPort NATPMPPort; +extern const mDNSIPPort DNSEXTPort; +extern const mDNSIPPort MulticastDNSPort; +extern const mDNSIPPort LoopbackIPCPort; + +extern const mDNSv4Addr AllDNSAdminGroup; +#define AllDNSLinkGroupv4 (AllDNSLinkGroup_v4.ip.v4) +#define AllDNSLinkGroupv6 (AllDNSLinkGroup_v6.ip.v6) +extern const mDNSAddr AllDNSLinkGroup_v4; +extern const mDNSAddr AllDNSLinkGroup_v6; + +extern const mDNSOpaque16 zeroID; +extern const mDNSOpaque16 QueryFlags; +extern const mDNSOpaque16 uQueryFlags; +extern const mDNSOpaque16 ResponseFlags; +extern const mDNSOpaque16 UpdateReqFlags; +extern const mDNSOpaque16 UpdateRespFlags; + +#define localdomain (*(const domainname *)"\x5" "local") +#define LocalReverseMapDomain (*(const domainname *)"\x3" "254" "\x3" "169" "\x7" "in-addr" "\x4" "arpa") + +// *************************************************************************** +#if 0 +#pragma mark - Inline functions +#endif + +#if (defined(_MSC_VER)) + #define mDNSinline static __inline +#elif ((__GNUC__ > 2) || ((__GNUC__ == 2) && (__GNUC_MINOR__ >= 9))) + #define mDNSinline static inline +#endif + +// If we're not doing inline functions, then this header needs to have the extern declarations +#if !defined(mDNSinline) +extern mDNSs32 NonZeroTime(mDNSs32 t); +extern mDNSu16 mDNSVal16(mDNSOpaque16 x); +extern mDNSu32 mDNSVal32(mDNSOpaque32 x); +extern mDNSOpaque16 mDNSOpaque16fromIntVal(mDNSu16 v); +extern mDNSOpaque32 mDNSOpaque32fromIntVal(mDNSu32 v); +#endif + +// If we're compiling the particular C file that instantiates our inlines, then we +// define "mDNSinline" (to empty string) so that we generate code in the following section +#if (!defined(mDNSinline) && mDNS_InstantiateInlines) +#define mDNSinline +#endif + +#ifdef mDNSinline + +mDNSinline mDNSs32 NonZeroTime(mDNSs32 t) { if (t) return(t); else return(1); } + +mDNSinline mDNSu16 mDNSVal16(mDNSOpaque16 x) { return((mDNSu16)((mDNSu16)x.b[0] << 8 | (mDNSu16)x.b[1])); } +mDNSinline mDNSu32 mDNSVal32(mDNSOpaque32 x) { return((mDNSu32)((mDNSu32)x.b[0] << 24 | (mDNSu32)x.b[1] << 16 | (mDNSu32)x.b[2] << 8 | (mDNSu32)x.b[3])); } + +mDNSinline mDNSOpaque16 mDNSOpaque16fromIntVal(mDNSu16 v) + { + mDNSOpaque16 x; + x.b[0] = (mDNSu8)(v >> 8); + x.b[1] = (mDNSu8)(v & 0xFF); + return(x); + } + +mDNSinline mDNSOpaque32 mDNSOpaque32fromIntVal(mDNSu32 v) + { + mDNSOpaque32 x; + x.b[0] = (mDNSu8) (v >> 24) ; + x.b[1] = (mDNSu8)((v >> 16) & 0xFF); + x.b[2] = (mDNSu8)((v >> 8 ) & 0xFF); + x.b[3] = (mDNSu8)((v ) & 0xFF); + return x; + } + +#endif + +// *************************************************************************** +#if 0 +#pragma mark - Main Client Functions +#endif + +// Every client should call mDNS_Init, passing in storage for the mDNS object and the mDNS_PlatformSupport object. +// +// Clients that are only advertising services should use mDNS_Init_NoCache and mDNS_Init_ZeroCacheSize. +// Clients that plan to perform queries (mDNS_StartQuery, mDNS_StartBrowse, mDNS_StartResolveService, etc.) +// need to provide storage for the resource record cache, or the query calls will return 'mStatus_NoCache'. +// The rrcachestorage parameter is the address of memory for the resource record cache, and +// the rrcachesize parameter is the number of entries in the CacheRecord array passed in. +// (i.e. the size of the cache memory needs to be sizeof(CacheRecord) * rrcachesize). +// OS X 10.3 Panther uses an initial cache size of 64 entries, and then mDNSCore sends an +// mStatus_GrowCache message if it needs more. +// +// Most clients should 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). +// Following deregistration, the RecordCallback will be called with result mStatus_MemFree to signal that it is safe to deallocate +// the record's storage (memory must be freed asynchronously to allow for goodbye packets and dynamic update deregistration). +// +// 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, + CacheEntity *rrcachestorage, mDNSu32 rrcachesize, + mDNSBool AdvertiseLocalAddresses, + mDNSCallback *Callback, void *Context); +// See notes above on use of NoCache/ZeroCacheSize +#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, CacheEntity *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); +extern mDNSs32 mDNS_TimeNow(const mDNS *const m); + +// *************************************************************************** +#if 0 +#pragma mark - Platform support functions that are accessible to the client layer too +#endif + +extern mDNSs32 mDNSPlatformOneSecond; + +// *************************************************************************** +#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_AddRecordToService adds an additional record to a Service Record Set. This record may be deregistered +// via mDNS_RemoveRecordFromService, or by deregistering the service. mDNS_RemoveRecordFromService is passed a +// callback to free the memory associated with the extra RR when it is safe to do so. The ExtraResourceRecord +// object can be found in the record's context pointer. + +// 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, mDNSRecordCallback MemFreeCallback, void *Context); +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, mDNSBool ForceMCast, 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_DomainTypeBrowseLegacy = 2, + mDNS_DomainTypeRegistration = 3, + mDNS_DomainTypeRegistrationDefault = 4, + + mDNS_DomainTypeMax = 4 + } mDNS_DomainType; + +extern const char *const mDNS_DomainTypeNames[]; + +extern mStatus mDNS_GetDomains(mDNS *const m, DNSQuestion *const question, mDNS_DomainType DomainType, const domainname *dom, + 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); +extern mDNSBool IsLocalDomain(const domainname *d); // returns true for domains that by default should be looked up using link-local multicast + +// 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 and macros +#endif + +// mDNS_vsnprintf/snprintf return the number of characters written, excluding the final terminating null. +// The output is always null-terminated: for example, if the output turns out to be exactly buflen long, +// then the output will be truncated by one character to allow space for the terminating null. +// Unlike standard C vsnprintf/snprintf, they return the number of characters *actually* written, +// not the number of characters that *would* have been printed were buflen unlimited. +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 mDNSu32 NumCacheRecordsForInterfaceID(const mDNS *const m, mDNSInterfaceID id); +extern char *DNSTypeName(mDNSu16 rrtype); +extern char *GetRRDisplayString_rdb(const ResourceRecord *rr, RDataBody *rd, char *buffer); +#define RRDisplayString(m, rr) GetRRDisplayString_rdb(rr, &(rr)->rdata->u, (m)->MsgBuffer) +#define ARDisplayString(m, rr) GetRRDisplayString_rdb(&(rr)->resrec, &(rr)->resrec.rdata->u, (m)->MsgBuffer) +#define CRDisplayString(m, rr) GetRRDisplayString_rdb(&(rr)->resrec, &(rr)->resrec.rdata->u, (m)->MsgBuffer) +extern mDNSBool mDNSSameAddress(const mDNSAddr *ip1, const mDNSAddr *ip2); +extern void IncrementLabelSuffix(domainlabel *name, mDNSBool RichText); +extern mDNSBool IsPrivateV4Addr(mDNSAddr *addr); // returns true for RFC1918 private addresses + +#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 mDNSSameEthAddress(A,B) ((A)->w[0] == (B)->w[0] && (A)->w[1] == (B)->w[1] && (A)->w[2] == (B)->w[2]) + +#define mDNSIPv4AddressIsZero(A) mDNSSameIPv4Address((A), zerov4Addr) +#define mDNSIPv6AddressIsZero(A) mDNSSameIPv6Address((A), zerov6Addr) + +#define mDNSIPv4AddressIsOnes(A) mDNSSameIPv4Address((A), onesIPv4Addr) +#define mDNSIPv6AddressIsOnes(A) mDNSSameIPv6Address((A), onesIPv6Addr) + +#define mDNSAddressIsAllDNSLinkGroup(X) ( \ + ((X)->type == mDNSAddrType_IPv4 && mDNSSameIPv4Address((X)->ip.v4, AllDNSLinkGroupv4)) || \ + ((X)->type == mDNSAddrType_IPv6 && mDNSSameIPv6Address((X)->ip.v6, AllDNSLinkGroupv6)) ) + +#define mDNSAddressIsZero(X) ( \ + ((X)->type == mDNSAddrType_IPv4 && mDNSIPv4AddressIsZero((X)->ip.v4)) || \ + ((X)->type == mDNSAddrType_IPv6 && mDNSIPv6AddressIsZero((X)->ip.v6)) ) + +#define mDNSAddressIsValidNonZero(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) + + +// *************************************************************************** +#if 0 +#pragma mark - Authentication Support +#endif + +#define HMAC_LEN 64 +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5c +#define MD5_LEN 16 + +// padded keys for inned/outer hash rounds +typedef struct + { + mDNSu8 ipad[HMAC_LEN]; + mDNSu8 opad[HMAC_LEN]; + } HMAC_Key; + +// Internal data structure to maintain authentication information for an update domain +typedef struct uDNS_AuthInfo + { + domainname zone; + domainname keyname; + HMAC_Key key; + struct uDNS_AuthInfo *next; + } uDNS_AuthInfo; + +// Unicast DNS and Dynamic Update specific Client Calls +// +// mDNS_SetSecretForZone tells the core to authenticate (via TSIG with an HMAC_MD5 hash of the shared secret) +// when dynamically updating a given zone (and its subdomains). The key used in authentication must be in +// domain name format. The shared secret must be a null-terminated base64 encoded string. A minimum size of +// 16 bytes (128 bits) is recommended for an MD5 hash as per RFC 2485. +// Calling this routine multiple times for a zone replaces previously entered values. Call with a NULL key +// to dissable authentication for the zone. + +extern mStatus mDNS_SetSecretForZone(mDNS *m, const domainname *zone, const domainname *key, const char *sharedSecret); + +// Hostname/Unicast Interface Configuration + +// All hostnames advertised point to one IPv4 address and/or one IPv6 address, set via SetPrimaryInterfaceInfo. Invoking this routine +// updates all existing hostnames to point to the new address. + +// A hostname is added via AddDynDNSHostName, which points to the primary interface's v4 and/or v6 addresss + +// The status callback is invoked to convey success or failure codes - the callback should not modify the AuthRecord or free memory. +// Added hostnames may be removed (deregistered) via mDNS_RemoveDynDNSHostName. + +// Host domains added prior to specification of the primary interface address and computer name will be deferred until +// these values are initialized. + +// When routable V4 interfaces are added or removed, mDNS_UpdateLLQs should be called to re-estabish LLQs in case the +// destination address for events (i.e. the route) has changed. For performance reasons, the caller is responsible for +// batching changes, e.g. calling the routine only once if multiple interfaces are simultanously removed or added. + +// DNS servers used to resolve unicast queries are specified by mDNS_AddDNSServer, and may later be removed via mDNS_DeleteDNSServers. +// For "split" DNS configurations, in which queries for different domains are sent to different servers (e.g. VPN and external), +// a domain may be associated with a DNS server. For standard configurations, specify the root label (".") or NULL. + +extern void mDNS_AddDynDNSHostName(mDNS *m, const domainname *fqdn, mDNSRecordCallback *StatusCallback, const void *StatusContext); +extern void mDNS_RemoveDynDNSHostName(mDNS *m, const domainname *fqdn); +extern void mDNS_SetPrimaryInterfaceInfo(mDNS *m, const mDNSAddr *v4addr, const mDNSAddr *v6addr, const mDNSAddr *router); +extern void mDNS_UpdateLLQs(mDNS *m); +extern void mDNS_AddDNSServer(mDNS *const m, const mDNSAddr *dnsAddr, const domainname *domain); +extern void mDNS_DeleteDNSServers(mDNS *const m); + +// Routines called by the core, exported by DNSDigest.c + +// Convert a base64 encoded key into a binary byte stream +extern mDNSs32 DNSDigest_Base64ToBin(const char *src, mDNSu8 *target, mDNSu32 targsize); + +// Convert an arbitrary binary key (of any length) into an HMAC key (stored in AuthInfo struct) +extern void DNSDigest_ConstructHMACKey(uDNS_AuthInfo *info, const mDNSu8 *key, mDNSu32 len); + +// sign a DNS message. The message must be compete, with all values in network byte order. end points to the end +// of the message, and is modified by this routine. numAdditionals is a pointer to the number of additional +// records in HOST byte order, which is incremented upon successful completion of this routine. The function returns +// the new end pointer on success, and NULL on failure. +extern mDNSu8 *DNSDigest_SignMessage(DNSMessage *msg, mDNSu8 **end, mDNSu16 *numAdditionals, uDNS_AuthInfo *info); + +// *************************************************************************** +#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() + +// 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 and unicast DNS. +// 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). +// +// USE CAUTION WHEN CALLING mDNSPlatformRawTime: The m->timenow_adjust correction factor needs to be added +// Generally speaking: +// Code that's protected by the main mDNS lock should just use the m->timenow value +// Code outside the main mDNS lock should use mDNS_TimeNow(m) to get properly adjusted time +// In certain cases there may be reasons why it's necessary to get the time without taking the lock first +// (e.g. inside the routines that are doing the locking and unlocking, where a call to get the lock would result in a +// recursive loop); in these cases use mDNS_TimeNow_NoLock(m) to get mDNSPlatformRawTime with the proper correction factor added. +// +// mDNSPlatformUTC returns the time, in seconds, since Jan 1st 1970 UTC and is required for generating TSIG records + +extern mStatus mDNSPlatformInit (mDNS *const m); +extern void mDNSPlatformClose (mDNS *const m); +extern mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const msg, const mDNSu8 *const end, +mDNSInterfaceID InterfaceID, 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 mDNSu32 mDNSPlatformRandomSeed (void); +extern mStatus mDNSPlatformTimeInit (void); +extern mDNSs32 mDNSPlatformRawTime (void); +extern mDNSs32 mDNSPlatformUTC (void); +#define mDNS_TimeNow_NoLock(m) (mDNSPlatformRawTime() + m->timenow_adjust) + +// Platform support modules should provide the following functions to map between opaque interface IDs +// and interface indexes in order to support the DNS-SD API. If your target platform does not support +// multiple interfaces and/or does not support the DNS-SD API, these functions can be empty. +extern mDNSInterfaceID mDNSPlatformInterfaceIDfromInterfaceIndex(mDNS *const m, mDNSu32 index); +extern mDNSu32 mDNSPlatformInterfaceIndexfromInterfaceID(mDNS *const m, mDNSInterfaceID id); + +// Every platform support module must provide the following functions if it is to support unicast DNS +// and Dynamic Update. +// All TCP socket operations implemented by the platform layer MUST NOT BLOCK. +// mDNSPlatformTCPConnect initiates a TCP connection with a peer, adding the socket descriptor to the +// main event loop. The return value indicates whether the connection succeeded, failed, or is pending +// (i.e. the call would block.) On return, the descriptor parameter is set to point to the connected socket. +// The TCPConnectionCallback is subsequently invoked when the connection +// completes (in which case the ConnectionEstablished parameter is true), or data is available for +// reading on the socket (indicated by the ConnectionEstablished parameter being false.) If the connection +// asynchronously fails, the TCPConnectionCallback should be invoked as usual, with the error being +// returned in subsequent calls to PlatformReadTCP or PlatformWriteTCP. (This allows for platforms +// with limited asynchronous error detection capabilities.) PlatformReadTCP and PlatformWriteTCP must +// return the number of bytes read/written, 0 if the call would block, and -1 if an error. +// PlatformTCPCloseConnection must close the connection to the peer and remove the descriptor from the +// event loop. CloseConnectin may be called at any time, including in a ConnectionCallback. + +typedef void (*TCPConnectionCallback)(int sd, void *context, mDNSBool ConnectionEstablished); +extern mStatus mDNSPlatformTCPConnect(const mDNSAddr *dst, mDNSOpaque16 dstport, mDNSInterfaceID InterfaceID, + TCPConnectionCallback callback, void *context, int *descriptor); +extern void mDNSPlatformTCPCloseConnection(int sd); +extern int mDNSPlatformReadTCP(int sd, void *buf, int buflen); +extern int mDNSPlatformWriteTCP(int sd, const char *msg, int len); + +// Platforms that support unicast browsing and dynamic update registration for clients who do not specify a domain +// in browse/registration calls must implement these routines to get the "default" browse/registration list. +// The Get() functions must return a linked list of DNameListElem structs, allocated via mDNSPlatformMemAllocate. +// Platforms may implement the Get() calls via the mDNS_CopyDNameList() helper routine. +// Callers should free lists obtained via the Get() calls with th mDNS_FreeDNameList routine, provided by the core. + +typedef struct DNameListElem + { + domainname name; + struct DNameListElem *next; + } DNameListElem; + +extern DNameListElem *mDNSPlatformGetSearchDomainList(void); +extern DNameListElem *mDNSPlatformGetRegDomainList(void); + +// Helper functions provided by the core +extern DNameListElem *mDNS_CopyDNameList(const DNameListElem *orig); +extern void mDNS_FreeDNameList(DNameListElem *list); + +#ifdef _LEGACY_NAT_TRAVERSAL_ +// Support for legacy NAT traversal protocols, implemented by the platform layer and callable by the core. + +#define DYN_PORT_MIN 49152 // ephemeral port range +#define DYN_PORT_MAX 65535 +#define LEGACY_NATMAP_MAX_TRIES 4 // if our desired mapping is taken, how many times we try mapping to a random port + +extern mStatus LNT_GetPublicIP(mDNSOpaque32 *ip); +extern mStatus LNT_MapPort(mDNSIPPort priv, mDNSIPPort pub, mDNSBool tcp); +extern mStatus LNT_UnmapPort(mDNSIPPort PubPort, mDNSBool tcp); +#endif // _LEGACY_NAT_TRAVERSAL_ + +// The core mDNS code provides these functions, for the platform support code to call at appropriate times +// +// mDNS_SetFQDN() is called once on startup (typically from mDNSPlatformInit()) +// and then again on each subsequent change of the 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) +// IMPORTANT: The specified mDNSInterfaceID MUST NOT be 0, -1, or -2; these values have special meaning +// mDNS_RegisterInterface does not result in the registration of global hostnames via dynamic update - +// see mDNS_SetPrimaryInterfaceInfo, mDNS_AddDynDNSHostName, etc. for this purpose. +// Note that the set may be deallocated immediately after it is deregistered via mDNS_DeegisterInterface. +// +// mDNS_RegisterDNS() is used by the platform support layer to provide the core with the addresses of +// available domain name servers for unicast queries/updates. RegisterDNS() should be called once for +// each name server, typically at startup, or when a new name server becomes available. DeregiterDNS() +// must be called whenever a registered name server becomes unavailable. DeregisterDNSList deregisters +// all registered servers. mDNS_DNSRegistered() returns true if one or more servers are registered in the core. +// +// 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_SetFQDN(mDNS *const m); +extern mStatus mDNS_RegisterInterface (mDNS *const m, NetworkInterfaceInfo *set, mDNSBool flapping); +extern void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *set, mDNSBool flapping); +extern void mDNSCoreInitComplete(mDNS *const m, mStatus result); +extern void mDNSCoreReceive(mDNS *const m, void *const msg, const mDNSu8 *const end, + const mDNSAddr *const srcaddr, const mDNSIPPort srcport, + const mDNSAddr *const dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID); +extern void mDNSCoreMachineSleep(mDNS *const m, mDNSBool wake); + +extern mDNSBool mDNSAddrIsDNSMulticast(const mDNSAddr *ip); + +// *************************************************************************** +#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]; + char assertC[(sizeof(CacheRecord ) >= sizeof(CacheGroup) ) ? 1 : -1]; + char assertD[(sizeof(int) >= 4 ) ? 1 : -1]; + }; + +// *************************************************************************** + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/src/mdns/mDNSPosix.c b/src/mdns/mDNSPosix.c new file mode 100755 index 00000000..afa9a800 --- /dev/null +++ b/src/mdns/mDNSPosix.c @@ -0,0 +1,1755 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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: mDNSPosix.c,v $ +Revision 1.78.2.1 2006/08/29 06:24:34 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.78 2006/06/28 09:12:22 cheshire +Added debugging message + +Revision 1.77 2006/03/19 02:00:11 cheshire + Improve logic for delaying packets after repeated interface transitions + +Revision 1.76 2006/01/09 19:29:16 cheshire + Cap number of "sendto failed" messages we allow mDNSResponder to log + +Revision 1.75 2006/01/05 22:04:57 cheshire + Log error message when send fails with "operation not permitted" + +Revision 1.74 2006/01/05 21:45:27 cheshire + Fix uninitialized structure member in IPv6 code + +Revision 1.73 2005/10/11 21:31:46 cheshire + Don't depend on IP_RECVTTL succeeding (not available on all platforms) + +Revision 1.72 2005/09/08 20:45:26 cheshire +Default dot-local host name should be "Computer" not "Macintosh", +since the machine this is running on is most likely NOT a Mac. + +Revision 1.71 2005/02/26 01:29:12 cheshire +Ignore multicasts accidentally delivered to our unicast receiving socket + +Revision 1.70 2005/02/04 00:39:59 cheshire +Move ParseDNSServers() from PosixDaemon.c to mDNSPosix.c so all Posix client layers can use it + +Revision 1.69 2004/12/18 02:03:28 cheshire +Need to #include "dns_sd.h" + +Revision 1.68 2004/12/18 00:51:52 cheshire +Use symbolic constant kDNSServiceInterfaceIndexLocalOnly instead of (mDNSu32) ~0 + +Revision 1.67 2004/12/17 23:37:48 cheshire + Guard against repeating wireless dissociation/re-association +(and other repetitive configuration changes) + +Revision 1.66 2004/12/01 04:27:28 cheshire + Darwin patches for Solaris and Suse +Don't use uint32_t, etc. -- they require stdint.h, which doesn't exist on FreeBSD 4.x, Solaris, etc. + +Revision 1.65 2004/11/30 22:37:01 cheshire +Update copyright dates and add "Mode: C; tab-width: 4" headers + +Revision 1.64 2004/11/23 03:39:47 cheshire +Let interface name/index mapping capability live directly in JNISupport.c, +instead of having to call through to the daemon via IPC to get this information. + +Revision 1.63 2004/11/12 03:16:43 rpantos +rdar://problem/3809541 Add mDNSPlatformGetInterfaceByName, mDNSPlatformGetInterfaceName + +Revision 1.62 2004/10/28 03:24:42 cheshire +Rename m->CanReceiveUnicastOn as m->CanReceiveUnicastOn5353 + +Revision 1.61 2004/10/16 00:17:01 cheshire + Replace IP TTL 255 check with local subnet source address check + +Revision 1.60 2004/09/26 23:20:36 ksekar + Allow default registrations in multiple wide-area domains + +Revision 1.59 2004/09/21 21:02:55 cheshire +Set up ifname before calling mDNS_RegisterInterface() + +Revision 1.58 2004/09/17 01:08:54 cheshire +Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h + The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces + declared in that file are ONLY appropriate to single-address-space embedded applications. + For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used. + +Revision 1.57 2004/09/17 00:19:11 cheshire +For consistency with AllDNSLinkGroupv6, rename AllDNSLinkGroup to AllDNSLinkGroupv4 + +Revision 1.56 2004/09/17 00:15:56 cheshire +Rename mDNSPlatformInit_ReceiveUnicast to mDNSPlatformInit_CanReceiveUnicast + +Revision 1.55 2004/09/16 00:24:49 cheshire + Fix unsafe use of mDNSPlatformTimeNow() + +Revision 1.54 2004/09/15 23:55:00 ksekar + mDNSPosix should #include stdint.h + +Revision 1.53 2004/09/14 23:42:36 cheshire + Need to seed random number generator from platform-layer data + +Revision 1.52 2004/08/25 16:42:13 ksekar +Fix Posix build - change mDNS_SetFQDNs to mDNS_SetFQDN, remove unicast +hostname parameter. + +Revision 1.51 2004/08/14 03:22:42 cheshire + Dynamic DNS UI <-> mDNSResponder glue +Add GetUserSpecifiedDDNSName() routine +Convert ServiceRegDomain to domainname instead of C string +Replace mDNS_GenerateFQDN/mDNS_GenerateGlobalFQDN with mDNS_SetFQDNs + +Revision 1.50 2004/08/11 01:20:20 cheshire +Declare private local functions using "mDNSlocal" + +Revision 1.49 2004/07/26 22:49:31 ksekar +: Feature #9516: Need support for NATPMP in client + +Revision 1.48 2004/07/20 01:47:36 rpantos +NOT_HAVE_SA_LEN applies to v6, too. And use more-portable s6_addr. + +Revision 1.47 2004/06/25 00:26:27 rpantos +Changes to fix the Posix build on Solaris. + +Revision 1.46 2004/05/13 04:54:20 ksekar +Unified list copy/free code. Added symetric list for + +Revision 1.45 2004/05/12 22:03:09 ksekar +Made GetSearchDomainList a true platform-layer call (declaration moved +from mDNSMacOSX.h to mDNSEmbeddedAPI.h), impelemted to return "local" +only on non-OSX platforms. Changed call to return a copy of the list +to avoid shared memory issues. Added a routine to free the list. + +Revision 1.44 2004/04/21 02:49:11 cheshire +To reduce future confusion, renamed 'TxAndRx' to 'McastTxRx' + +Revision 1.43 2004/04/14 23:09:29 ksekar +Support for TSIG signed dynamic updates. + +Revision 1.42 2004/04/09 17:43:04 cheshire +Make sure to set the McastTxRx field so that duplicate suppression works correctly + +Revision 1.41 2004/02/06 01:19:51 cheshire +Conditionally exclude IPv6 code unless HAVE_IPV6 is set + +Revision 1.40 2004/02/05 01:00:01 rpantos +Fix some issues that turned up when building for FreeBSD. + +Revision 1.39 2004/01/28 21:12:15 cheshire +Reconcile mDNSIPv6Support & HAVE_IPV6 into a single flag (HAVE_IPV6) + +Revision 1.38 2004/01/27 20:15:23 cheshire +: Time to prune obsolete code for listening on port 53 + +Revision 1.37 2004/01/24 05:12:03 cheshire +: Need separate socket for issuing unicast queries + +Revision 1.36 2004/01/24 04:59:16 cheshire +Fixes so that Posix/Linux, OS9, Windows, and VxWorks targets build again + +Revision 1.35 2004/01/23 21:37:08 cheshire +For consistency, rename multicastSocket to multicastSocket4, and multicastSocketv6 to multicastSocket6 + +Revision 1.34 2004/01/22 03:43:09 cheshire +Export constants like mDNSInterface_LocalOnly so that the client layers can use them + +Revision 1.33 2004/01/21 21:54:20 cheshire +: Don't try to receive unicast responses if we're not the first to bind to the UDP port + +Revision 1.32 2004/01/20 01:49:28 rpantos +Tweak error handling of last checkin a bit. + +Revision 1.31 2004/01/20 01:39:27 rpantos +Respond to If changes by rebuilding interface list. + +Revision 1.30 2003/12/11 19:40:36 cheshire +Fix 'destAddr.type == senderAddr.type;' that should have said 'destAddr.type = senderAddr.type;' + +Revision 1.29 2003/12/11 18:53:22 cheshire +Fix compiler warning reported by Paul Guyot + +Revision 1.28 2003/12/11 03:03:51 rpantos +Clean up mDNSPosix so that it builds on OS X again. + +Revision 1.27 2003/12/08 20:47:02 rpantos +Add support for mDNSResponder on Linux. + +Revision 1.26 2003/11/14 20:59:09 cheshire +Clients can't use AssignDomainName macro because mDNSPlatformMemCopy is defined in mDNSPlatformFunctions.h. +Best solution is just to combine mDNSEmbeddedAPI.h and mDNSPlatformFunctions.h into a single file. + +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 mDNSPlatformRawTime() + +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 "mDNSEmbeddedAPI.h" // Defines the interface provided to the client layer above +#include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform +#include "dns_sd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // platform support for UTC time + +#if USES_NETLINK +#include +#include +#include +#else // USES_NETLINK +#include +#include +#endif // USES_NETLINK + +#include "mDNSUNP.h" +#include "GenLinkedList.h" + +// *************************************************************************** +// Structures + +// We keep a list of client-supplied event sources in PosixEventSource records +struct PosixEventSource + { + mDNSPosixEventCallback Callback; + void *Context; + int fd; + struct PosixEventSource *Next; + }; +typedef struct PosixEventSource PosixEventSource; + +// Context record for interface change callback +struct IfChangeRec + { + int NotifySD; + mDNS* mDNS; + }; +typedef struct IfChangeRec IfChangeRec; + +// Note that static data is initialized to zero in (modern) C. +static fd_set gEventFDs; +static int gMaxFD; // largest fd in gEventFDs +static GenLinkedList gEventSources; // linked list of PosixEventSource's +static sigset_t gEventSignalSet; // Signals which event loop listens for +static sigset_t gEventSignals; // Signals which were received while inside loop + +// *************************************************************************** +// 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; + +#define PosixErrorToStatus(errNum) ((errNum) == 0 ? mStatus_NoError : mStatus_UnknownErr) + +mDNSlocal 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; + } + +#if HAVE_IPV6 + case AF_INET6: + { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa; +#ifndef NOT_HAVE_SA_LEN + assert(sin6->sin6_len == sizeof(*sin6)); +#endif + 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 void *const msg, const mDNSu8 *const end, + mDNSInterfaceID InterfaceID, const mDNSAddr *dst, mDNSIPPort dstPort) + { + int err = 0; + struct sockaddr_storage to; + PosixNetworkInterface * thisIntf = (PosixNetworkInterface *)(InterfaceID); + int sendingsocket = -1; + + assert(m != NULL); + assert(msg != NULL); + assert(end != NULL); + assert( (((char *) end) - ((char *) msg)) > 0 ); + assert(dstPort.NotAnInteger != 0); + + 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; + sendingsocket = thisIntf ? thisIntf->multicastSocket4 : m->p->unicastSocket4; + } + +#if HAVE_IPV6 + else if (dst->type == mDNSAddrType_IPv6) + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&to; + mDNSPlatformMemZero(sin6, sizeof(*sin6)); +#ifndef NOT_HAVE_SA_LEN + sin6->sin6_len = sizeof(*sin6); +#endif + sin6->sin6_family = AF_INET6; + sin6->sin6_port = dstPort.NotAnInteger; + sin6->sin6_addr = *(struct in6_addr*)&dst->ip.v6; + sendingsocket = thisIntf ? thisIntf->multicastSocket6 : m->p->unicastSocket6; + } +#endif + + if (sendingsocket >= 0) + err = sendto(sendingsocket, msg, (char*)end - (char*)msg, 0, (struct sockaddr *)&to, GET_SA_LEN(to)); + + if (err > 0) err = 0; + else if (err < 0) + { + static int MessageCount = 0; + // Don't report EHOSTDOWN (i.e. ARP failure), ENETDOWN, or no route to host for unicast destinations + if (!mDNSAddressIsAllDNSLinkGroup(dst)) + if (errno == EHOSTDOWN || errno == ENETDOWN || errno == EHOSTUNREACH || errno == ENETUNREACH) return(mStatus_TransientErr); + + if (MessageCount < 1000) + { + MessageCount++; + if (thisIntf) + LogMsg("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); + else + LogMsg("mDNSPlatformSendUDP got error %d (%s) sending packet to %#a", errno, strerror(errno), dst); + } + } + + return PosixErrorToStatus(err); + } + +// This routine is called when the main loop detects that data is available on a socket. +mDNSlocal 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; + mDNSu8 ttl; + mDNSBool reject; + const mDNSInterfaceID InterfaceID = intf ? intf->coreIntf.InterfaceID : NULL; + + assert(m != NULL); + assert(skt >= 0); + + fromLen = sizeof(from); + flags = 0; + packetLen = recvfrom_flags(skt, &packet, sizeof(packet), &flags, (struct sockaddr *) &from, &fromLen, &packetInfo, &ttl); + + 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 = AllDNSLinkGroupv4; + 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 (!intf) + { + // Ignore multicasts accidentally delivered to our unicast receiving socket + if (mDNSAddrIsDNSMulticast(&destAddr)) packetLen = -1; + } + else + { + 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/%d", + &senderAddr, &destAddr, packetInfo.ipi_ifname, packetInfo.ipi_ifindex, + &intf->coreIntf.ip, intf->intfName, intf->index, skt); + 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/%d", + &senderAddr, &destAddr, &intf->coreIntf.ip, intf->intfName, intf->index, skt); + num_pkts_accepted++; + } + } + } + + if (packetLen >= 0) + mDNSCoreReceive(m, &packet, (mDNSu8 *)&packet + packetLen, + &senderAddr, senderPort, &destAddr, MulticastDNSPort, InterfaceID); + } + +mDNSexport mStatus mDNSPlatformTCPConnect(const mDNSAddr *dst, mDNSOpaque16 dstport, mDNSInterfaceID InterfaceID, + TCPConnectionCallback callback, void *context, int *descriptor) + { + (void)dst; // Unused + (void)dstport; // Unused + (void)InterfaceID; // Unused + (void)callback; // Unused + (void)context; // Unused + (void)descriptor; // Unused + return(mStatus_UnsupportedErr); + } + +mDNSexport void mDNSPlatformTCPCloseConnection(int sd) + { + (void)sd; // Unused + } + +mDNSexport int mDNSPlatformReadTCP(int sd, void *buf, int buflen) + { + (void)sd; // Unused + (void)buf; // Unused + (void)buflen; // Unused + return(0); + } + +mDNSexport int mDNSPlatformWriteTCP(int sd, const char *msg, int len) + { + (void)sd; // Unused + (void)msg; // Unused + (void)len; // Unused + return(0); + } + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Get/Free Search Domain List +#endif + +mDNSexport DNameListElem *mDNSPlatformGetSearchDomainList(void) + { + static DNameListElem tmp; + static mDNSBool init = mDNSfalse; + + if (!init) + { + MakeDomainNameFromDNSNameString(&tmp.name, "local."); + tmp.next = NULL; + init = mDNStrue; + } + return mDNS_CopyDNameList(&tmp); + } + +mDNSexport DNameListElem *mDNSPlatformGetRegDomainList(void) + { + return NULL; + } + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Init and Term +#endif + +// 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; + } + +// 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) + { + // On Unix we have no better name than the host name, so we just use that. + GetUserSpecifiedRFC1034ComputerName( namelabel); + } + +mDNSexport int ParseDNSServers(mDNS *m, const char *filePath) + { + char line[256]; + char nameserver[16]; + char keyword[10]; + int numOfServers = 0; + FILE *fp = fopen(filePath, "r"); + if (fp == NULL) return -1; + while (fgets(line,sizeof(line),fp)) + { + struct in_addr ina; + line[255]='\0'; // just to be safe + if (sscanf(line,"%10s %15s", keyword, nameserver) != 2) continue; // it will skip whitespaces + if (strncmp(keyword,"nameserver",10)) continue; + if (inet_aton(nameserver, (struct in_addr *)&ina) != 0) + { + mDNSAddr DNSAddr; + DNSAddr.type = mDNSAddrType_IPv4; + DNSAddr.ip.v4.NotAnInteger = ina.s_addr; + mDNS_AddDNSServer(m, &DNSAddr, NULL); + numOfServers++; + } + } + return (numOfServers > 0) ? 0 : -1; + } + +// Searches the interface list looking for the named interface. +// Returns a pointer to if it found, or NULL otherwise. +mDNSlocal 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; + } + +mDNSexport mDNSInterfaceID mDNSPlatformInterfaceIDfromInterfaceIndex(mDNS *const m, mDNSu32 index) + { + PosixNetworkInterface *intf; + + assert(m != NULL); + + if (index == kDNSServiceInterfaceIndexLocalOnly) return(mDNSInterface_LocalOnly); + + intf = (PosixNetworkInterface*)(m->HostInterfaces); + while ( (intf != NULL) && (mDNSu32) intf->index != index) + intf = (PosixNetworkInterface *)(intf->coreIntf.next); + + return (mDNSInterfaceID) intf; + } + +mDNSexport mDNSu32 mDNSPlatformInterfaceIndexfromInterfaceID(mDNS *const m, mDNSInterfaceID id) + { + PosixNetworkInterface *intf; + + assert(m != NULL); + + if (id == mDNSInterface_LocalOnly) return(kDNSServiceInterfaceIndexLocalOnly); + + intf = (PosixNetworkInterface*)(m->HostInterfaces); + while ( (intf != NULL) && (mDNSInterfaceID) intf != id) + intf = (PosixNetworkInterface *)(intf->coreIntf.next); + + return intf ? intf->index : 0; + } + +// Frees the specified PosixNetworkInterface structure. The underlying +// interface must have already been deregistered with the mDNS core. +mDNSlocal void FreePosixNetworkInterface(PosixNetworkInterface *intf) + { + assert(intf != NULL); + if (intf->intfName != NULL) free((void *)intf->intfName); + if (intf->multicastSocket4 != -1) assert(close(intf->multicastSocket4) == 0); +#if HAVE_IPV6 + if (intf->multicastSocket6 != -1) assert(close(intf->multicastSocket6) == 0); +#endif + free(intf); + } + +// Grab the first interface, deregister it, free it, and repeat until done. +mDNSlocal void ClearInterfaceList(mDNS *const m) + { + assert(m != NULL); + + while (m->HostInterfaces) + { + PosixNetworkInterface *intf = (PosixNetworkInterface*)(m->HostInterfaces); + mDNS_DeregisterInterface(m, &intf->coreIntf, mDNSfalse); + 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 send/receive socket. +// If mDNSIPPort port is non-zero, then it's a multicast socket on the specified interface +// If mDNSIPPort port is zero, then it's a randomly assigned port number, used for sending unicast queries +mDNSlocal 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; + const mDNSBool JoinMulticastGroup = (port.NotAnInteger != 0); + + (void) interfaceIndex; // This parameter unused on plaforms that don't have IPv6 + 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); +#if HAVE_IPV6 + 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 it's for multicast receiving + if (err == 0 && port.NotAnInteger) + { + #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 + } + #if defined(IP_RECVTTL) // Linux + if (err == 0) + { + setsockopt(*sktPtr, IPPROTO_IP, IP_RECVTTL, &kOn, sizeof(kOn)); + // We no longer depend on being able to get the received TTL, so don't worry if the option fails + } + #endif + + // Add multicast group membership on this interface + if (err == 0 && JoinMulticastGroup) + { + imr.imr_multiaddr.s_addr = AllDNSLinkGroupv4.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 && JoinMulticastGroup) + { + 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) + +#if HAVE_IPV6 + else if (intfAddr->sa_family == AF_INET6) + { + struct ipv6_mreq imr6; + struct sockaddr_in6 bindAddr6; + #if defined(IPV6_PKTINFO) + if (err == 0) + { + 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 + #if defined(IPV6_HOPLIMIT) + if (err == 0) + { + err = setsockopt(*sktPtr, IPPROTO_IPV6, IPV6_HOPLIMIT, &kOn, sizeof(kOn)); + if (err < 0) { err = errno; perror("setsockopt - IPV6_HOPLIMIT"); } + } + #endif + + // Add multicast group membership on this interface + if (err == 0 && JoinMulticastGroup) + { + imr6.ipv6mr_multiaddr = *(const struct in6_addr*)&AllDNSLinkGroupv6; + imr6.ipv6mr_interface = interfaceIndex; + //LogMsg("Joining %.16a on %d", &imr6.ipv6mr_multiaddr, imr6.ipv6mr_interface); + 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 && JoinMulticastGroup) + { + 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)); +#ifndef NOT_HAVE_SA_LEN + bindAddr6.sin6_len = sizeof(bindAddr6); +#endif + bindAddr6.sin6_family = AF_INET6; + bindAddr6.sin6_port = port.NotAnInteger; + bindAddr6.sin6_flowinfo = 0; + bindAddr6.sin6_addr = in6addr_any; // 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. +mDNSlocal int SetupOneInterface(mDNS *const m, struct sockaddr *intfAddr, struct sockaddr *intfMask, const char *intfName, int intfIndex) + { + int err = 0; + PosixNetworkInterface *intf; + PosixNetworkInterface *alias = NULL; + + assert(m != NULL); + assert(intfAddr != NULL); + assert(intfName != NULL); + assert(intfMask != NULL); + + // Allocate the interface structure itself. + intf = (PosixNetworkInterface*)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); + SockAddrTomDNSAddr(intfMask, &intf->coreIntf.mask, NULL); + //LogMsg("SetupOneInterface: %#a %#a", &intf->coreIntf.ip, &intf->coreIntf.mask); + strncpy(intf->coreIntf.ifname, intfName, sizeof(intf->coreIntf.ifname)); + intf->coreIntf.ifname[sizeof(intf->coreIntf.ifname)-1] = 0; + intf->coreIntf.Advertise = m->AdvertiseLocalAddresses; + intf->coreIntf.McastTxRx = mDNStrue; + + // Set up the extra fields in PosixNetworkInterface. + assert(intf->intfName != NULL); // intf->intfName already set up above + intf->index = intfIndex; + intf->multicastSocket4 = -1; +#if HAVE_IPV6 + intf->multicastSocket6 = -1; +#endif + 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->multicastSocket4 == -1 && intfAddr->sa_family == AF_INET) + err = SetupSocket(intfAddr, MulticastDNSPort, intf->index, &alias->multicastSocket4); +#if HAVE_IPV6 + else if (alias->multicastSocket6 == -1 && intfAddr->sa_family == AF_INET6) + err = SetupSocket(intfAddr, MulticastDNSPort, intf->index, &alias->multicastSocket6); +#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, mDNSfalse); + + // 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; + } + +// Call get_ifi_info() to obtain a list of active interfaces and call SetupOneInterface() on each one. +mDNSlocal 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; + +#if HAVE_IPV6 + 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) +#if HAVE_IPV6 + || (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_netmask, i->ifi_name, i->ifi_index) == 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_netmask, firstLoopback->ifi_name, firstLoopback->ifi_index); + } + + // Clean up. + if (intfList != NULL) free_ifi_info(intfList); + return err; + } + +#if USES_NETLINK + +// See for a description of NetLink + +// Open a socket that will receive interface change notifications +mDNSlocal mStatus OpenIfNotifySocket( int *pFD) + { + mStatus err = mStatus_NoError; + struct sockaddr_nl snl; + int sock; + int ret; + + sock = socket( AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock < 0) + return errno; + + // Configure read to be non-blocking because inbound msg size is not known in advance + (void) fcntl( sock, F_SETFL, O_NONBLOCK); + + /* Subscribe the socket to Link & IP addr notifications. */ + bzero( &snl, sizeof snl); + snl.nl_family = AF_NETLINK; + snl.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR; + ret = bind( sock, (struct sockaddr *) &snl, sizeof snl); + if ( 0 == ret) + *pFD = sock; + else + err = errno; + + return err; + } + +#if MDNS_DEBUGMSGS +mDNSlocal void PrintNetLinkMsg( const struct nlmsghdr *pNLMsg) + { + const char *kNLMsgTypes[] = { "", "NLMSG_NOOP", "NLMSG_ERROR", "NLMSG_DONE", "NLMSG_OVERRUN" }; + const char *kNLRtMsgTypes[] = { "RTM_NEWLINK", "RTM_DELLINK", "RTM_GETLINK", "RTM_NEWADDR", "RTM_DELADDR", "RTM_GETADDR" }; + + printf( "nlmsghdr len=%d, type=%s, flags=0x%x\n", pNLMsg->nlmsg_len, + pNLMsg->nlmsg_type < RTM_BASE ? kNLMsgTypes[ pNLMsg->nlmsg_type] : kNLRtMsgTypes[ pNLMsg->nlmsg_type - RTM_BASE], + pNLMsg->nlmsg_flags); + + if ( RTM_NEWLINK <= pNLMsg->nlmsg_type && pNLMsg->nlmsg_type <= RTM_GETLINK) + { + struct ifinfomsg *pIfInfo = (struct ifinfomsg*) NLMSG_DATA( pNLMsg); + printf( "ifinfomsg family=%d, type=%d, index=%d, flags=0x%x, change=0x%x\n", pIfInfo->ifi_family, + pIfInfo->ifi_type, pIfInfo->ifi_index, pIfInfo->ifi_flags, pIfInfo->ifi_change); + + } + else if ( RTM_NEWADDR <= pNLMsg->nlmsg_type && pNLMsg->nlmsg_type <= RTM_GETADDR) + { + struct ifaddrmsg *pIfAddr = (struct ifaddrmsg*) NLMSG_DATA( pNLMsg); + printf( "ifaddrmsg family=%d, index=%d, flags=0x%x\n", pIfAddr->ifa_family, + pIfAddr->ifa_index, pIfAddr->ifa_flags); + } + printf( "\n"); + } +#endif + +mDNSlocal mDNSu32 ProcessRoutingNotification( int sd) +// Read through the messages on sd and if any indicate that any interface records should +// be torn down and rebuilt, return affected indices as a bitmask. Otherwise return 0. + { + ssize_t readCount; + char buff[ 4096]; + struct nlmsghdr *pNLMsg = (struct nlmsghdr*) buff; + mDNSu32 result = 0; + + // The structure here is more complex than it really ought to be because, + // unfortunately, there's no good way to size a buffer in advance large + // enough to hold all pending data and so avoid message fragmentation. + // (Note that FIONREAD is not supported on AF_NETLINK.) + + readCount = read( sd, buff, sizeof buff); + while ( 1) + { + // Make sure we've got an entire nlmsghdr in the buffer, and payload, too. + // If not, discard already-processed messages in buffer and read more data. + if ( ( (char*) &pNLMsg[1] > ( buff + readCount)) || // i.e. *pNLMsg extends off end of buffer + ( (char*) pNLMsg + pNLMsg->nlmsg_len > ( buff + readCount))) + { + if ( buff < (char*) pNLMsg) // we have space to shuffle + { + // discard processed data + readCount -= ( (char*) pNLMsg - buff); + memmove( buff, pNLMsg, readCount); + pNLMsg = (struct nlmsghdr*) buff; + + // read more data + readCount += read( sd, buff + readCount, sizeof buff - readCount); + continue; // spin around and revalidate with new readCount + } + else + break; // Otherwise message does not fit in buffer + } + +#if MDNS_DEBUGMSGS + PrintNetLinkMsg( pNLMsg); +#endif + + // Process the NetLink message + if ( pNLMsg->nlmsg_type == RTM_GETLINK || pNLMsg->nlmsg_type == RTM_NEWLINK) + result |= 1 << ((struct ifinfomsg*) NLMSG_DATA( pNLMsg))->ifi_index; + else if ( pNLMsg->nlmsg_type == RTM_DELADDR || pNLMsg->nlmsg_type == RTM_NEWADDR) + result |= 1 << ((struct ifaddrmsg*) NLMSG_DATA( pNLMsg))->ifa_index; + + // Advance pNLMsg to the next message in the buffer + if ( ( pNLMsg->nlmsg_flags & NLM_F_MULTI) != 0 && pNLMsg->nlmsg_type != NLMSG_DONE) + { + ssize_t len = readCount - ( (char*)pNLMsg - buff); + pNLMsg = NLMSG_NEXT( pNLMsg, len); + } + else + break; // all done! + } + + return result; + } + +#else // USES_NETLINK + +// Open a socket that will receive interface change notifications +mDNSlocal mStatus OpenIfNotifySocket( int *pFD) + { + *pFD = socket( AF_ROUTE, SOCK_RAW, 0); + + if ( *pFD < 0) + return mStatus_UnknownErr; + + // Configure read to be non-blocking because inbound msg size is not known in advance + (void) fcntl( *pFD, F_SETFL, O_NONBLOCK); + + return mStatus_NoError; + } + +#if MDNS_DEBUGMSGS +mDNSlocal void PrintRoutingSocketMsg( const struct ifa_msghdr *pRSMsg) + { + const char *kRSMsgTypes[] = { "", "RTM_ADD", "RTM_DELETE", "RTM_CHANGE", "RTM_GET", "RTM_LOSING", + "RTM_REDIRECT", "RTM_MISS", "RTM_LOCK", "RTM_OLDADD", "RTM_OLDDEL", "RTM_RESOLVE", + "RTM_NEWADDR", "RTM_DELADDR", "RTM_IFINFO", "RTM_NEWMADDR", "RTM_DELMADDR" }; + + int index = pRSMsg->ifam_type == RTM_IFINFO ? ((struct if_msghdr*) pRSMsg)->ifm_index : pRSMsg->ifam_index; + + printf( "ifa_msghdr len=%d, type=%s, index=%d\n", pRSMsg->ifam_msglen, kRSMsgTypes[ pRSMsg->ifam_type], index); + } +#endif + +mDNSlocal mDNSu32 ProcessRoutingNotification( int sd) +// Read through the messages on sd and if any indicate that any interface records should +// be torn down and rebuilt, return affected indices as a bitmask. Otherwise return 0. + { + ssize_t readCount; + char buff[ 4096]; + struct ifa_msghdr *pRSMsg = (struct ifa_msghdr*) buff; + mDNSu32 result = 0; + + readCount = read( sd, buff, sizeof buff); + if ( readCount < (ssize_t) sizeof( struct ifa_msghdr)) + return mStatus_UnsupportedErr; // cannot decipher message + +#if MDNS_DEBUGMSGS + PrintRoutingSocketMsg( pRSMsg); +#endif + + // Process the message + if ( pRSMsg->ifam_type == RTM_NEWADDR || pRSMsg->ifam_type == RTM_DELADDR || + pRSMsg->ifam_type == RTM_IFINFO) + { + if ( pRSMsg->ifam_type == RTM_IFINFO) + result |= 1 << ((struct if_msghdr*) pRSMsg)->ifm_index; + else + result |= 1 << pRSMsg->ifam_index; + } + + return result; + } + +#endif // USES_NETLINK + +// Called when data appears on interface change notification socket +mDNSlocal void InterfaceChangeCallback( void *context) + { + IfChangeRec *pChgRec = (IfChangeRec*) context; + fd_set readFDs; + mDNSu32 changedInterfaces = 0; + struct timeval zeroTimeout = { 0, 0 }; + + FD_ZERO( &readFDs); + FD_SET( pChgRec->NotifySD, &readFDs); + + do + { + changedInterfaces |= ProcessRoutingNotification( pChgRec->NotifySD); + } + while ( 0 < select( pChgRec->NotifySD + 1, &readFDs, (fd_set*) NULL, (fd_set*) NULL, &zeroTimeout)); + + // Currently we rebuild the entire interface list whenever any interface change is + // detected. If this ever proves to be a performance issue in a multi-homed + // configuration, more care should be paid to changedInterfaces. + if ( changedInterfaces) + mDNSPlatformPosixRefreshInterfaceList( pChgRec->mDNS); + } + +// Register with either a Routing Socket or RtNetLink to listen for interface changes. +mDNSlocal mStatus WatchForInterfaceChange(mDNS *const m) + { + mStatus err; + IfChangeRec *pChgRec; + + pChgRec = (IfChangeRec*) mDNSPlatformMemAllocate( sizeof *pChgRec); + if ( pChgRec == NULL) + return mStatus_NoMemoryErr; + + pChgRec->mDNS = m; + err = OpenIfNotifySocket( &pChgRec->NotifySD); + if ( err == 0) + err = mDNSPosixAddFDToEventLoop( pChgRec->NotifySD, InterfaceChangeCallback, pChgRec); + + return err; + } + +// Test to see if we're the first client running on UDP port 5353, by trying to bind to 5353 without using SO_REUSEPORT. +// If we fail, someone else got here first. That's not a big problem; we can share the port for multicast responses -- +// we just need to be aware that we shouldn't expect to successfully receive unicast UDP responses. +mDNSlocal mDNSBool mDNSPlatformInit_CanReceiveUnicast(void) + { + int err; + int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + struct sockaddr_in s5353; + s5353.sin_family = AF_INET; + s5353.sin_port = MulticastDNSPort.NotAnInteger; + s5353.sin_addr.s_addr = 0; + err = bind(s, (struct sockaddr *)&s5353, sizeof(s5353)); + close(s); + if (err) debugf("No unicast UDP responses"); + else debugf("Unicast UDP responses okay"); + return(err == 0); + } + +// mDNS core calls this routine to initialise the platform-specific data. +mDNSexport mStatus mDNSPlatformInit(mDNS *const m) + { + int err = 0; + struct sockaddr sa; + assert(m != NULL); + + if (mDNSPlatformInit_CanReceiveUnicast()) m->CanReceiveUnicastOn5353 = mDNStrue; + + // 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, "Computer"); + + // Set up the RFC 1034-compliant label + m->hostlabel.c[0] = 0; + GetUserSpecifiedRFC1034ComputerName(&m->hostlabel); + if (m->hostlabel.c[0] == 0) MakeDomainLabelFromLiteralString(&m->hostlabel, "Computer"); + + mDNS_SetFQDN(m); + + sa.sa_family = AF_INET; + m->p->unicastSocket4 = -1; + if (err == mStatus_NoError) err = SetupSocket(&sa, zeroIPPort, 0, &m->p->unicastSocket4); +#if HAVE_IPV6 + sa.sa_family = AF_INET6; + m->p->unicastSocket6 = -1; + if (err == mStatus_NoError) err = SetupSocket(&sa, zeroIPPort, 0, &m->p->unicastSocket6); +#endif + + // Tell mDNS core about the network interfaces on this machine. + if (err == mStatus_NoError) err = SetupInterfaceList(m); + + // Tell mDNS core about DNS Servers + if (err == mStatus_NoError) ParseDNSServers(m, uDNS_SERVERS_FILE); + + if (err == mStatus_NoError) + { + err = WatchForInterfaceChange(m); + // Failure to observe interface changes is non-fatal. + if ( err != mStatus_NoError) + { + fprintf(stderr, "mDNS(%d) WARNING: Unable to detect interface changes (%d).\n", getpid(), err); + err = mStatus_NoError; + } + } + + // 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 == mStatus_NoError) + 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); + if (m->p->unicastSocket4 != -1) assert(close(m->p->unicastSocket4) == 0); +#if HAVE_IPV6 + if (m->p->unicastSocket6 != -1) assert(close(m->p->unicastSocket6) == 0); +#endif + } + +mDNSexport 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 mDNSu32 mDNSPlatformRandomSeed(void) + { + struct timeval tv; + gettimeofday(&tv, NULL); + return(tv.tv_usec); + } + +mDNSexport mDNSs32 mDNSPlatformOneSecond = 1024; + +mDNSexport mStatus mDNSPlatformTimeInit(void) + { + // 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 + return(mStatus_NoError); + } + +mDNSexport mDNSs32 mDNSPlatformRawTime() + { + 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 mDNSs32 mDNSPlatformUTC(void) + { + return time(NULL); + } + +mDNSlocal void mDNSPosixAddToFDSet(int *nfds, fd_set *readfds, int s) + { + if (*nfds < s + 1) *nfds = s + 1; + FD_SET(s, readfds); + } + +mDNSexport void mDNSPosixGetFDSet(mDNS *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); + if (m->p->unicastSocket4 != -1) mDNSPosixAddToFDSet(nfds, readfds, m->p->unicastSocket4); +#if HAVE_IPV6 + if (m->p->unicastSocket6 != -1) mDNSPosixAddToFDSet(nfds, readfds, m->p->unicastSocket6); +#endif + while (info) + { + if (info->multicastSocket4 != -1) mDNSPosixAddToFDSet(nfds, readfds, info->multicastSocket4); +#if HAVE_IPV6 + if (info->multicastSocket6 != -1) mDNSPosixAddToFDSet(nfds, readfds, info->multicastSocket6); +#endif + info = (PosixNetworkInterface *)(info->coreIntf.next); + } + + // 3. Calculate the time remaining to the next scheduled event (in struct timeval format) + ticks = nextevent - mDNS_TimeNow(m); + 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); + + if (m->p->unicastSocket4 != -1 && FD_ISSET(m->p->unicastSocket4, readfds)) + { + FD_CLR(m->p->unicastSocket4, readfds); + SocketDataReady(m, NULL, m->p->unicastSocket4); + } +#if HAVE_IPV6 + if (m->p->unicastSocket6 != -1 && FD_ISSET(m->p->unicastSocket6, readfds)) + { + FD_CLR(m->p->unicastSocket6, readfds); + SocketDataReady(m, NULL, m->p->unicastSocket6); + } +#endif + + while (info) + { + if (info->multicastSocket4 != -1 && FD_ISSET(info->multicastSocket4, readfds)) + { + FD_CLR(info->multicastSocket4, readfds); + SocketDataReady(m, info, info->multicastSocket4); + } +#if HAVE_IPV6 + if (info->multicastSocket6 != -1 && FD_ISSET(info->multicastSocket6, readfds)) + { + FD_CLR(info->multicastSocket6, readfds); + SocketDataReady(m, info, info->multicastSocket6); + } +#endif + info = (PosixNetworkInterface *)(info->coreIntf.next); + } + } + +// update gMaxFD +mDNSlocal void DetermineMaxEventFD( void ) + { + PosixEventSource *iSource; + + gMaxFD = 0; + for ( iSource=(PosixEventSource*)gEventSources.Head; iSource; iSource = iSource->Next) + if ( gMaxFD < iSource->fd) + gMaxFD = iSource->fd; + } + +// Add a file descriptor to the set that mDNSPosixRunEventLoopOnce() listens to. +mStatus mDNSPosixAddFDToEventLoop( int fd, mDNSPosixEventCallback callback, void *context) + { + PosixEventSource *newSource; + + if ( gEventSources.LinkOffset == 0) + InitLinkedList( &gEventSources, offsetof( PosixEventSource, Next)); + + if ( fd >= (int) FD_SETSIZE || fd < 0) + return mStatus_UnsupportedErr; + if ( callback == NULL) + return mStatus_BadParamErr; + + newSource = (PosixEventSource*) malloc( sizeof *newSource); + if ( NULL == newSource) + return mStatus_NoMemoryErr; + + newSource->Callback = callback; + newSource->Context = context; + newSource->fd = fd; + + AddToTail( &gEventSources, newSource); + FD_SET( fd, &gEventFDs); + + DetermineMaxEventFD(); + + return mStatus_NoError; + } + +// Remove a file descriptor from the set that mDNSPosixRunEventLoopOnce() listens to. +mStatus mDNSPosixRemoveFDFromEventLoop( int fd) + { + PosixEventSource *iSource; + + for ( iSource=(PosixEventSource*)gEventSources.Head; iSource; iSource = iSource->Next) + { + if ( fd == iSource->fd) + { + FD_CLR( fd, &gEventFDs); + RemoveFromList( &gEventSources, iSource); + free( iSource); + DetermineMaxEventFD(); + return mStatus_NoError; + } + } + return mStatus_NoSuchNameErr; + } + +// Simply note the received signal in gEventSignals. +mDNSlocal void NoteSignal( int signum) + { + sigaddset( &gEventSignals, signum); + } + +// Tell the event package to listen for signal and report it in mDNSPosixRunEventLoopOnce(). +mStatus mDNSPosixListenForSignalInEventLoop( int signum) + { + struct sigaction action; + mStatus err; + + bzero( &action, sizeof action); // more portable than member-wise assignment + action.sa_handler = NoteSignal; + err = sigaction( signum, &action, (struct sigaction*) NULL); + + sigaddset( &gEventSignalSet, signum); + + return err; + } + +// Tell the event package to stop listening for signal in mDNSPosixRunEventLoopOnce(). +mStatus mDNSPosixIgnoreSignalInEventLoop( int signum) + { + struct sigaction action; + mStatus err; + + bzero( &action, sizeof action); // more portable than member-wise assignment + action.sa_handler = SIG_DFL; + err = sigaction( signum, &action, (struct sigaction*) NULL); + + sigdelset( &gEventSignalSet, signum); + + return err; + } + +// Do a single pass through the attendent event sources and dispatch any found to their callbacks. +// Return as soon as internal timeout expires, or a signal we're listening for is received. +mStatus mDNSPosixRunEventLoopOnce( mDNS *m, const struct timeval *pTimeout, + sigset_t *pSignalsReceived, mDNSBool *pDataDispatched) + { + fd_set listenFDs = gEventFDs; + int fdMax = 0, numReady; + struct timeval timeout = *pTimeout; + + // Include the sockets that are listening to the wire in our select() set + mDNSPosixGetFDSet( m, &fdMax, &listenFDs, &timeout); // timeout may get modified + if ( fdMax < gMaxFD) + fdMax = gMaxFD; + + numReady = select( fdMax + 1, &listenFDs, (fd_set*) NULL, (fd_set*) NULL, &timeout); + + // If any data appeared, invoke its callback + if ( numReady > 0) + { + PosixEventSource *iSource; + + (void) mDNSPosixProcessFDSet( m, &listenFDs); // call this first to process wire data for clients + + for ( iSource=(PosixEventSource*)gEventSources.Head; iSource; iSource = iSource->Next) + { + if ( FD_ISSET( iSource->fd, &listenFDs)) + { + iSource->Callback( iSource->Context); + break; // in case callback removed elements from gEventSources + } + } + *pDataDispatched = mDNStrue; + } + else + *pDataDispatched = mDNSfalse; + + (void) sigprocmask( SIG_BLOCK, &gEventSignalSet, (sigset_t*) NULL); + *pSignalsReceived = gEventSignals; + sigemptyset( &gEventSignals); + (void) sigprocmask( SIG_UNBLOCK, &gEventSignalSet, (sigset_t*) NULL); + + return mStatus_NoError; + } diff --git a/src/mdns/mDNSPosix.h b/src/mdns/mDNSPosix.h new file mode 100755 index 00000000..f62d9c30 --- /dev/null +++ b/src/mdns/mDNSPosix.h @@ -0,0 +1,145 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + Change History (most recent first): + +$Log: mDNSPosix.h,v $ +Revision 1.18 2006/08/14 23:24:47 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.17 2005/02/04 00:39:59 cheshire +Move ParseDNSServers() from PosixDaemon.c to mDNSPosix.c so all Posix client layers can use it + +Revision 1.16 2004/11/30 22:37:01 cheshire +Update copyright dates and add "Mode: C; tab-width: 4" headers + +Revision 1.15 2004/02/06 01:19:51 cheshire +Conditionally exclude IPv6 code unless HAVE_IPV6 is set + +Revision 1.14 2004/01/28 21:12:15 cheshire +Reconcile mDNSIPv6Support & HAVE_IPV6 into a single flag (HAVE_IPV6) + +Revision 1.13 2004/01/24 05:12:03 cheshire +: Need separate socket for issuing unicast queries + +Revision 1.12 2004/01/23 21:37:08 cheshire +For consistency, rename multicastSocket to multicastSocket4, and multicastSocketv6 to multicastSocket6 + +Revision 1.11 2003/12/11 03:03:51 rpantos +Clean up mDNSPosix so that it builds on OS X again. + +Revision 1.10 2003/12/08 20:47:02 rpantos +Add support for mDNSResponder on Linux. + +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 +#include + +#ifdef __cplusplus + extern "C" { +#endif + +// 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 multicastSocket4; +#if HAVE_IPV6 + int multicastSocket6; +#endif + }; + +// This is a global because debugf_() needs to be able to check its value +extern int gMDNSPlatformPosixVerboseLevel; + +struct mDNS_PlatformSupport_struct + { + int unicastSocket4; +#if HAVE_IPV6 + int unicastSocket6; +#endif + }; + +#define uDNS_SERVERS_FILE "/etc/resolv.conf" +extern int ParseDNSServers(mDNS *m, const char *filePath); +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 *m, int *nfds, fd_set *readfds, struct timeval *timeout); +extern void mDNSPosixProcessFDSet(mDNS *const m, fd_set *readfds); + +typedef void (*mDNSPosixEventCallback)( void *context); + +extern mStatus mDNSPosixAddFDToEventLoop( int fd, mDNSPosixEventCallback callback, void *context); +extern mStatus mDNSPosixRemoveFDFromEventLoop( int fd); +extern mStatus mDNSPosixListenForSignalInEventLoop( int signum); +extern mStatus mDNSPosixIgnoreSignalInEventLoop( int signum); +extern mStatus mDNSPosixRunEventLoopOnce( mDNS *m, const struct timeval *pTimeout, sigset_t *pSignalsReceived, mDNSBool *pDataDispatched); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/src/mdns/mDNSUNP.c b/src/mdns/mDNSUNP.c new file mode 100755 index 00000000..ad1821ca --- /dev/null +++ b/src/mdns/mDNSUNP.c @@ -0,0 +1,766 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + Change History (most recent first): + +$Log: mDNSUNP.c,v $ +Revision 1.34 2006/08/14 23:24:47 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.33 2006/03/13 23:14:21 cheshire + Compile problems on FreeBSD +Use instead of + +Revision 1.32 2005/12/21 02:56:43 cheshire + get_ifi_info() should fake ifi_index when SIOCGIFINDEX undefined + +Revision 1.31 2005/12/21 02:46:05 cheshire + mDNSUNP.c needs to include on 4.4BSD Lite + +Revision 1.30 2005/11/29 20:03:02 mkrochma +Wrapped sin_len with #ifndef NOT_HAVE_SA_LEN + +Revision 1.29 2005/11/12 02:23:10 cheshire + mDNSUNP.c needs to deal with lame results from SIOCGIFNETMASK, SIOCGIFBRDADDR and SIOCGIFDSTADDR + +Revision 1.28 2005/10/31 22:09:45 cheshire +Buffer "char addr6[33]" was seven bytes too small + +Revision 1.27 2005/06/29 15:54:21 cheshire + mDNSResponder-107.1 does not work on FreeBSD +Refine last checkin so that it (hopefully) doesn't break get_ifi_info() for every other OS + +Revision 1.26 2005/04/08 21:43:59 ksekar + mDNSPosix (v98) retrieve interface list bug on AMD64 architecture +Submitted by Andrew de Quincey + +Revision 1.25 2005/04/08 21:37:57 ksekar + get_ifi_info doesn't return IPv6 interfaces on Linux + +Revision 1.24 2005/04/08 21:30:16 ksekar + Compiling problems with mDNSResponder-98 on Solaris/Sparc v9 +Patch submitted by Bernd Kuhls + +Revision 1.23 2004/12/01 04:25:05 cheshire + Darwin patches for Solaris and Suse +Provide daemon() for platforms that don't have it + +Revision 1.22 2004/11/30 22:37:01 cheshire +Update copyright dates and add "Mode: C; tab-width: 4" headers + +Revision 1.21 2004/11/08 22:13:59 rpantos +Create sockf6 lazily when v6 interface found. + +Revision 1.20 2004/10/16 00:17:01 cheshire + Replace IP TTL 255 check with local subnet source address check + +Revision 1.19 2004/07/20 01:47:36 rpantos +NOT_HAVE_SA_LEN applies to v6, too. And use more-portable s6_addr. + +Revision 1.18 2004/07/08 21:30:21 rpantos + +Revision 1.17 2004/06/25 00:26:27 rpantos +Changes to fix the Posix build on Solaris. + +Revision 1.16 2004/03/20 05:37:09 cheshire +Fix contributed by Terry Lambert & Alfred Perlstein: +Don't use uint8_t -- it requires stdint.h, which doesn't exist on FreeBSD 4.x + +Revision 1.15 2004/02/14 01:09:45 rpantos +Just use HAVE_IPV6 rather than defined(HAVE_IPV6). + +Revision 1.14 2003/12/11 18:53:40 cheshire +Fix compiler warning reported by Paul Guyot + +Revision 1.13 2003/12/08 20:47:02 rpantos +Add support for mDNSResponder on Linux. + +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 + +/* Some weird platforms derived from 4.4BSD Lite (e.g. EFI) need the ALIGN(P) + macro, usually defined in or someplace like that, to make sure the + CMSG_NXTHDR macro is well-formed. On such platforms, the symbol NEED_ALIGN_MACRO + should be set to the name of the header to include to get the ALIGN(P) macro. +*/ +#ifdef NEED_ALIGN_MACRO +#include NEED_ALIGN_MACRO +#endif + +/* 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 + +#if defined(AF_INET6) && HAVE_IPV6 && !HAVE_LINUX +#include +#include +// NOTE: netinet/in_var.h implicitly includes netinet6/in6_var.h for us +#endif + +#if defined(AF_INET6) && HAVE_IPV6 && HAVE_LINUX +#include +#include + +/* Converts a prefix length to IPv6 network mask */ +void plen_to_mask(int plen, char *addr) { + int i; + int colons=7; /* Number of colons in IPv6 address */ + int bits_in_block=16; /* Bits per IPv6 block */ + for(i=0;i<=colons;i++) { + int block, ones=0xffff, ones_in_block; + if(plen>bits_in_block) ones_in_block=bits_in_block; + else ones_in_block=plen; + block = ones & (ones << (bits_in_block-ones_in_block)); + i==0 ? sprintf(addr, "%x", block) : sprintf(addr, "%s:%x", addr, block); + plen -= ones_in_block; + } + } + +/* Gets IPv6 interface information from the /proc filesystem in linux*/ +struct ifi_info *get_ifi_info_linuxv6(int family, int doaliases) + { + struct ifi_info *ifi, *ifihead, **ifipnext; + FILE *fp; + char addr[8][5]; + int flags, myflags, index, plen, scope; + char ifname[8], lastname[IFNAMSIZ]; + char addr6[32+7+1]; /* don't forget the seven ':' */ + struct addrinfo hints, *res0; + struct sockaddr_in6 *sin6; + struct in6_addr *addrptr; + int err; + + res0=NULL; + ifihead = NULL; + ifipnext = &ifihead; + lastname[0] = 0; + + if ((fp = fopen(PROC_IFINET6_PATH, "r")) != NULL) { + while (fscanf(fp, + "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %8s\n", + addr[0],addr[1],addr[2],addr[3], + addr[4],addr[5],addr[6],addr[7], + &index, &plen, &scope, &flags, ifname) != EOF) { + + myflags = 0; + if (strncmp(lastname, ifname, IFNAMSIZ) == 0) { + if (doaliases == 0) + continue; /* already processed this interface */ + myflags = IFI_ALIAS; + } + memcpy(lastname, ifname, IFNAMSIZ); + ifi = (struct ifi_info*)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 */ + + sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s", + addr[0],addr[1],addr[2],addr[3], + addr[4],addr[5],addr[6],addr[7]); + + /* Add address of the interface */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_flags = AI_NUMERICHOST; + err = getaddrinfo(addr6, NULL, &hints, &res0); + if (err) { + goto gotError; + } + ifi->ifi_addr = calloc(1, sizeof(struct sockaddr_in6)); + if (ifi->ifi_addr == NULL) { + goto gotError; + } + memcpy(ifi->ifi_addr, res0->ai_addr, sizeof(struct sockaddr_in6)); + + /* Add netmask of the interface */ + char ipv6addr[INET6_ADDRSTRLEN]; + plen_to_mask(plen, ipv6addr); + ifi->ifi_netmask = calloc(1, sizeof(struct sockaddr_in6)); + if (ifi->ifi_addr == NULL) { + goto gotError; + } + sin6=calloc(1, sizeof(struct sockaddr_in6)); + addrptr=calloc(1, sizeof(struct in6_addr)); + inet_pton(family, ipv6addr, addrptr); + sin6->sin6_family=family; + sin6->sin6_addr=*addrptr; + sin6->sin6_scope_id=scope; + memcpy(ifi->ifi_netmask, sin6, sizeof(struct sockaddr_in6)); + free(sin6); + + + /* Add interface name */ + memcpy(ifi->ifi_name, ifname, IFI_NAME); + + /* Add interface index */ + ifi->ifi_index = index; + + /* If interface is in /proc then it is up*/ + ifi->ifi_flags = IFF_UP; + + freeaddrinfo(res0); + res0=NULL; + } + } + goto done; + + gotError: + if (ifihead != NULL) { + free_ifi_info(ifihead); + ifihead = NULL; + } + if (res0 != NULL) { + freeaddrinfo(res0); + res0=NULL; + } + done: + return(ifihead); /* pointer to first structure in linked list */ + } +#endif // defined(AF_INET6) && HAVE_IPV6 && HAVE_LINUX + +struct ifi_info *get_ifi_info(int family, int doaliases) +{ + int junk; + struct ifi_info *ifi, *ifihead, **ifipnext; + int sockfd, sockf6, len, lastlen, flags, myflags; +#ifdef NOT_HAVE_IF_NAMETOINDEX + int index = 200; +#endif + char *ptr, *buf, lastname[IFNAMSIZ], *cptr; + struct ifconf ifc; + struct ifreq *ifr, ifrcopy; + struct sockaddr_in *sinptr; + +#if defined(AF_INET6) && HAVE_IPV6 + struct sockaddr_in6 *sinptr6; +#endif + +#if defined(AF_INET6) && HAVE_IPV6 && HAVE_LINUX + if(family == AF_INET6) return get_ifi_info_linuxv6(family, doaliases); +#endif + + sockfd = -1; + sockf6 = -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 = (char*)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; + + /* Advance to next one in buffer */ + if (sizeof(struct ifreq) > sizeof(ifr->ifr_name) + GET_SA_LEN(ifr->ifr_addr)) + ptr += sizeof(struct ifreq); + else + ptr += sizeof(ifr->ifr_name) + GET_SA_LEN(ifr->ifr_addr); + +// fprintf(stderr, "intf %p 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 = (struct ifi_info*)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 */ +#ifndef NOT_HAVE_IF_NAMETOINDEX + ifi->ifi_index = if_nametoindex(ifr->ifr_name); +#else + ifrcopy = *ifr; +#ifdef SIOCGIFINDEX + if ( 0 >= ioctl(sockfd, SIOCGIFINDEX, &ifrcopy)) + ifi->ifi_index = ifrcopy.ifr_index; + else +#endif + ifi->ifi_index = index++; /* SIOCGIFINDEX is broken on Solaris 2.5ish, so fake it */ +#endif + 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 = (struct sockaddr*)calloc(1, sizeof(struct sockaddr_in)); + if (ifi->ifi_addr == NULL) { + goto gotError; + } + memcpy(ifi->ifi_addr, sinptr, sizeof(struct sockaddr_in)); + +#ifdef SIOCGIFNETMASK + if (ioctl(sockfd, SIOCGIFNETMASK, &ifrcopy) < 0) goto gotError; + ifi->ifi_netmask = (struct sockaddr*)calloc(1, sizeof(struct sockaddr_in)); + if (ifi->ifi_netmask == NULL) goto gotError; + sinptr = (struct sockaddr_in *) &ifrcopy.ifr_addr; + /* The BSD ioctls (including Mac OS X) stick some weird values in for sin_len and sin_family */ +#ifndef NOT_HAVE_SA_LEN + sinptr->sin_len = sizeof(struct sockaddr_in); +#endif + sinptr->sin_family = AF_INET; + memcpy(ifi->ifi_netmask, sinptr, sizeof(struct sockaddr_in)); +#endif + +#ifdef SIOCGIFBRDADDR + if (flags & IFF_BROADCAST) { + if (ioctl(sockfd, SIOCGIFBRDADDR, &ifrcopy) < 0) { + goto gotError; + } + sinptr = (struct sockaddr_in *) &ifrcopy.ifr_broadaddr; + /* The BSD ioctls (including Mac OS X) stick some weird values in for sin_len and sin_family */ +#ifndef NOT_HAVE_SA_LEN + sinptr->sin_len = sizeof( struct sockaddr_in ); +#endif + sinptr->sin_family = AF_INET; + ifi->ifi_brdaddr = (struct sockaddr*)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; + /* The BSD ioctls (including Mac OS X) stick some weird values in for sin_len and sin_family */ +#ifndef NOT_HAVE_SA_LEN + sinptr->sin_len = sizeof( struct sockaddr_in ); +#endif + sinptr->sin_family = AF_INET; + ifi->ifi_dstaddr = (struct sockaddr*)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) && 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.s6_addr[2] = sinptr6->sin6_addr.s6_addr[3] = 0; + memcpy(ifi->ifi_addr, sinptr6, sizeof(struct sockaddr_in6)); + +#ifdef SIOCGIFNETMASK_IN6 + { + struct in6_ifreq ifr6; + if (sockf6 == -1) + sockf6 = socket(AF_INET6, SOCK_DGRAM, 0); + bzero(&ifr6, sizeof(ifr6)); + memcpy(&ifr6.ifr_name, &ifr->ifr_name, sizeof(ifr6.ifr_name )); + memcpy(&ifr6.ifr_ifru.ifru_addr, &ifr->ifr_addr, sizeof(ifr6.ifr_ifru.ifru_addr)); + if (ioctl(sockf6, SIOCGIFNETMASK_IN6, &ifr6) < 0) goto gotError; + ifi->ifi_netmask = (struct sockaddr*)calloc(1, sizeof(struct sockaddr_in6)); + if (ifi->ifi_netmask == NULL) goto gotError; + sinptr6 = (struct sockaddr_in6 *) &ifr6.ifr_ifru.ifru_addr; + memcpy(ifi->ifi_netmask, sinptr6, sizeof(struct sockaddr_in6)); + } +#endif + } + 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); + } + if (sockf6 != -1) { + junk = close(sockf6); + 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, u_char *ttl) +{ + 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; + + *ttl = 255; // If kernel fails to provide TTL data then assume the TTL was 255 as it should be + + 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 = (char *) sa; + msg.msg_namelen = *salenptr; + iov[0].iov_base = (char *)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); +#ifndef HAVE_BROKEN_RECVIF_NAME + int nameLen = (sdl->sdl_nlen < IFI_NAME - 1) ? sdl->sdl_nlen : (IFI_NAME - 1); + strncpy(pktp->ipi_ifname, sdl->sdl_data, nameLen); +#endif + pktp->ipi_ifindex = sdl->sdl_index; + assert(pktp->ipi_ifname[IFI_NAME - 1] == 0); + // null terminated because of memset above + continue; + } +#endif + +#ifdef IP_RECVTTL + if (cmptr->cmsg_level == IPPROTO_IP && + cmptr->cmsg_type == IP_RECVTTL) { + *ttl = *(u_char*)CMSG_DATA(cmptr); + continue; + } + else if (cmptr->cmsg_level == IPPROTO_IP && + cmptr->cmsg_type == IP_TTL) { // some implementations seem to send IP_TTL instead of IP_RECVTTL + *ttl = *(int*)CMSG_DATA(cmptr); + continue; + } +#endif + +#if defined(IPV6_PKTINFO) && 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; +#ifndef NOT_HAVE_SA_LEN + sin6->sin6_len = sizeof(*sin6); +#endif + 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 + +#if defined(IPV6_HOPLIMIT) && HAVE_IPV6 + if (cmptr->cmsg_level == IPPROTO_IPV6 && + cmptr->cmsg_type == IPV6_HOPLIMIT) { + *ttl = *(int*)CMSG_DATA(cmptr); + continue; + } +#endif + assert(0); // unknown ancillary data + } + return(n); +#endif /* CMSG_FIRSTHDR */ +} + +// ********************************************************************************************** + +// daemonize the process. Adapted from "Unix Network Programming" vol 1 by Stevens, section 12.4. +// Returns 0 on success, -1 on failure. + +#ifdef NOT_HAVE_DAEMON +#include +#include +#include + +int daemon(int nochdir, int noclose) + { + switch (fork()) + { + case -1: return (-1); // Fork failed + case 0: break; // Child -- continue + default: _exit(0); // Parent -- exit + } + + if (setsid() == -1) return(-1); + + signal(SIGHUP, SIG_IGN); + + switch (fork()) // Fork again, primarily for reasons of Unix trivia + { + case -1: return (-1); // Fork failed + case 0: break; // Child -- continue + default: _exit(0); // Parent -- exit + } + + if (!nochdir) (void)chdir("/"); + umask(0); + + if (!noclose) + { + int fd = open("/dev/null", O_RDWR, 0); + if (fd != -1) + { + // Avoid unnecessarily duplicating a file descriptor to itself + if (fd != STDIN_FILENO) (void)dup2(fd, STDIN_FILENO); + if (fd != STDOUT_FILENO) (void)dup2(fd, STDOUT_FILENO); + if (fd != STDERR_FILENO) (void)dup2(fd, STDERR_FILENO); + if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO) + (void)close (fd); + } + } + return (0); + } +#endif /* NOT_HAVE_DAEMON */ diff --git a/src/mdns/mDNSUNP.h b/src/mdns/mDNSUNP.h new file mode 100755 index 00000000..d0d75c4e --- /dev/null +++ b/src/mdns/mDNSUNP.h @@ -0,0 +1,187 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + Change History (most recent first): + +$Log: mDNSUNP.h,v $ +Revision 1.19 2006/08/14 23:24:47 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.18 2005/04/08 21:37:57 ksekar + get_ifi_info doesn't return IPv6 interfaces on Linux + +Revision 1.17 2004/12/17 19:32:43 cheshire +Add missing semicolon + +Revision 1.16 2004/12/01 04:25:05 cheshire + Darwin patches for Solaris and Suse +Provide daemon() for platforms that don't have it + +Revision 1.15 2004/11/30 22:37:01 cheshire +Update copyright dates and add "Mode: C; tab-width: 4" headers + +Revision 1.14 2004/10/16 00:17:01 cheshire + Replace IP TTL 255 check with local subnet source address check + +Revision 1.13 2004/03/20 05:37:09 cheshire +Fix contributed by Terry Lambert & Alfred Perlstein: +Don't use uint8_t -- it requires stdint.h, which doesn't exist on FreeBSD 4.x + +Revision 1.12 2004/01/28 21:12:15 cheshire +Reconcile mDNSIPv6Support & HAVE_IPV6 into a single flag (HAVE_IPV6) + +Revision 1.11 2003/12/13 05:43:09 bradley +Fixed non-sa_len and non-IPv6 version of GET_SA_LEN macro to cast as sockaddr to access +sa_family so it works with any sockaddr-compatible address structure (e.g. sockaddr_storage). + +Revision 1.10 2003/12/11 03:03:51 rpantos +Clean up mDNSPosix so that it builds on OS X again. + +Revision 1.9 2003/12/08 20:47:02 rpantos +Add support for mDNSResponder on Linux. + +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 HAVE_LINUX +#include +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +#ifdef NOT_HAVE_SOCKLEN_T + typedef unsigned int socklen_t; +#endif + +#if !defined(_SS_MAXSIZE) +#if HAVE_IPV6 +#define sockaddr_storage sockaddr_in6 +#else +#define sockaddr_storage sockaddr +#endif // HAVE_IPV6 +#endif // !defined(_SS_MAXSIZE) + +#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 HAVE_IPV6 +#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 */ +}; + +/* From the text (Stevens, section 20.2): */ +/* 'As an example of recvmsg we will write a function named recvfrom_flags that */ +/* is similar to recvfrom but also returns: */ +/* 1. the returned msg_flags value, */ +/* 2. the destination addres of the received datagram (from the IP_RECVDSTADDR socket option, and */ +/* 3. the index of the interface on which the datagram was received (the IP_RECVIF socket option).' */ +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, u_char *ttl); + +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_netmask; + struct sockaddr *ifi_brdaddr;/* broadcast address */ + struct sockaddr *ifi_dstaddr;/* destination address */ + struct ifi_info *ifi_next; /* next of these structures */ +}; + +#if defined(AF_INET6) && HAVE_IPV6 && HAVE_LINUX +#define PROC_IFINET6_PATH "/proc/net/if_inet6" +extern struct ifi_info *get_ifi_info_linuxv6(int family, int doaliases); +#endif + +#if defined(AF_INET6) && HAVE_IPV6 +#define INET6_ADDRSTRLEN 46 /*Maximum length of IPv6 address */ +#endif + + + +#define IFI_ALIAS 1 /* ifi_addr is an alias */ + +/* From the text (Stevens, section 16.6): */ +/* 'Since many programs need to know all the interfaces on a system, we will develop a */ +/* function of our own named get_ifi_info that returns a linked list of structures, one */ +/* for each interface that is currently "up."' */ +extern struct ifi_info *get_ifi_info(int family, int doaliases); + +/* 'The free_ifi_info function, which takes a pointer that was */ +/* returned by get_ifi_info and frees all the dynamic memory.' */ +extern void free_ifi_info(struct ifi_info *); + +#ifdef NOT_HAVE_DAEMON +extern int daemon(int nochdir, int noclose); +#endif + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/src/mdns/uDNS.c b/src/mdns/uDNS.c new file mode 100755 index 00000000..20cdadb4 --- /dev/null +++ b/src/mdns/uDNS.c @@ -0,0 +1,5242 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2006 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + Change History (most recent first): + +$Log: uDNS.c,v $ +Revision 1.230.2.1 2006/08/29 06:24:23 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.230 2006/06/29 03:02:44 cheshire + mDNSResponder NXDOMAIN and CNAME support + +Revision 1.229 2006/03/02 22:03:41 cheshire + Spurious warning "GetLargeResourceRecord: m->rec appears to be already in use" +Refinement: m->rec.r.resrec.RecordType needs to be cleared *every* time around for loop, not just once at the end + +Revision 1.228 2006/02/26 00:54:42 cheshire +Fixes to avoid code generation warning/error on FreeBSD 7 + +Revision 1.227 2006/01/09 20:47:05 cheshire + Spurious warning "GetLargeResourceRecord: m->rec appears to be already in use" + +Revision 1.226 2005/12/20 02:46:33 cheshire + mDNSPosix wide-area registration broken +Check too strict -- we can still do wide-area registration (without NAT-PMP) +without having to know our gateway address + +Revision 1.225 2005/10/21 22:51:17 cheshire + Add check to avoid crashing NAT gateways that have buggy DNS relay code +Refinement: Shorten "check-for-broken-dns-relay" to just "dnsbugtest" +to avoid crashing NAT gateways that have a different DNS relay bug + +Revision 1.224 2005/10/20 00:10:33 cheshire + Add check to avoid crashing NAT gateways that have buggy DNS relay code + +Revision 1.223 2005/10/17 18:52:42 cheshire + mDNSResponder crashed in CheckRecordRegistrations +Move code to unregister the service's extra records from uDNS_DeregisterService() to unlinkSRS(). + +Revision 1.222 2005/10/05 23:04:10 cheshire +Add more information to unlinkAR and startLLQHandshakeCallback error messages + +Revision 1.221 2005/10/05 17:27:48 herscher + Change 200ms delay to 10ms + +Revision 1.220 2005/09/24 01:10:09 cheshire +Fix comment typos + +Revision 1.219 2005/09/22 07:28:25 herscher +Double the delay to 200000 usec after sending out a DNS query + +Revision 1.218 2005/09/13 01:06:14 herscher + Add 100ms delay in sendQuery. + +Revision 1.217 2005/08/04 18:08:24 cheshire +Update comments + +Revision 1.216 2005/07/29 23:05:22 ksekar + Hostname registration should register IPv6 AAAA record with DNS Update +Services should point to IPv6 address if IPv4 NAT mapping fails + +Revision 1.215 2005/07/29 21:01:51 ksekar + Hostname registration should register IPv6 AAAA record with DNS Update +correction to original checkin - misplaced return in HostnameCallback and logic error determining v6 changes + +Revision 1.214 2005/07/29 19:46:10 ksekar + reduce polling period on failed LLQs to 15 minutes + +Revision 1.213 2005/07/29 18:04:22 ksekar + Hostname registration should register IPv6 AAAA record with DNS Update + +Revision 1.212 2005/07/22 19:35:50 ksekar + SUTiger: LLQ event acknowledgments are not formated correctly + +Revision 1.211 2005/07/21 18:51:04 ksekar + mDNSResponder times out when mapping ports after sleep + +Revision 1.210 2005/07/21 18:47:31 ksekar + NAT-PMP refresh Requested Public Port should contain actual mapped port + +Revision 1.209 2005/07/04 21:16:37 cheshire +Minor code tidying -- initialize variables where they are declared + +Revision 1.208 2005/06/28 00:24:28 ksekar + memory smasher in conQueryCallback + +Revision 1.207 2005/05/13 20:45:10 ksekar + Rapid wide-area txt record updates don't work + +Revision 1.206 2005/03/31 02:19:55 cheshire + Fix build warnings +Reviewed by: Scott Herscher + +Revision 1.205 2005/03/21 00:33:51 shersche + Fix build warnings on Win32 platform + +Revision 1.204 2005/03/16 00:42:32 ksekar + Long-lived queries not working on Windows + +Revision 1.203 2005/03/04 03:00:03 ksekar + Retransmissions happen too early, causing registrations to conflict with themselves + +Revision 1.202 2005/03/01 19:29:17 ksekar +changed LogMsgs to debugfs + +Revision 1.201 2005/02/26 03:04:13 cheshire + Should not indicate successful dynamic update if no network connection +Don't try to do updates to root name server. This ensures status dot turns red if user +enters a bad host name such as just "fred" instead of a properly fully-qualified name. + +Revision 1.200 2005/02/25 17:47:45 ksekar + SendServiceRegistration fails on wake from sleep + +Revision 1.199 2005/02/25 04:21:00 cheshire + mDNS -F returns the same domain multiple times with different casing + +Revision 1.198 2005/02/25 02:35:22 cheshire + Should not indicate successful dynamic update if no network connection +If we get NXDomain error looking for the _dns-update._udp record, +update status from 1 (in progress) to mStatus_NoSuchNameErr (failed) + +Revision 1.197 2005/02/24 21:56:59 ksekar +Change LogMsgs to debugfs + +Revision 1.196 2005/02/24 21:52:28 ksekar + Remove "deferred deregistration" logic for hostnames + +Revision 1.195 2005/02/22 17:53:08 ksekar +Changed successful NAT Traversals from LogMsg to LogOperation + +Revision 1.194 2005/02/15 18:38:03 ksekar + change expected/redundant log messages to debugfs. + +Revision 1.193 2005/02/15 01:17:48 ksekar +Fixed build failure. + +Revision 1.192 2005/02/14 23:01:28 ksekar +Refinement to previous checkin - don't log bad LLQ opcode if we had to send the request more than once. + +Revision 1.191 2005/02/14 18:26:51 ksekar + mDNSResponder complains about bad LLQ Opcode 2 + +Revision 1.190 2005/02/11 19:44:06 shersche +Remove extra semicolon at end of line + +Revision 1.189 2005/02/10 21:07:02 ksekar +Don't goto error in ReceiveNATAddrResponse if we receive a malformatted response + +Revision 1.188 2005/02/10 02:02:44 ksekar +Remove double semi-colon + +Revision 1.187 2005/02/09 23:28:01 ksekar + NAT-PMP response callback should return a +boolean indicating if the packet matched the request + +Revision 1.186 2005/02/04 21:56:29 ksekar + Simultaneous port map requests sometimes fail +- Refinement to previous checkin. + +Revision 1.185 2005/02/03 23:48:22 ksekar + Simultaneous port map requests sometimes fail + +Revision 1.184 2005/02/01 19:33:29 ksekar + Keychain format too restrictive + +Revision 1.183 2005/01/27 22:57:55 cheshire +Fix compile errors on gcc4 + +Revision 1.182 2005/01/25 18:55:05 ksekar +Shortened log message + +Revision 1.181 2005/01/25 02:17:32 cheshire + Don't use query ID zero in uDNS queries + +Revision 1.180 2005/01/19 21:01:54 ksekar + uDNS needs to support subtype registration and browsing + +Revision 1.179 2005/01/19 19:15:35 ksekar +Refinement to - Simplify mDNS_PurgeResultsForDomain logic and move into daemon layer + +Revision 1.178 2005/01/17 23:47:58 cheshire + Wide-area services not found on little-endian + +Revision 1.177 2005/01/17 23:41:26 cheshire +Fix compile errors + +Revision 1.176 2005/01/17 21:03:04 cheshire + Wide-area services not found on little-endian + +Revision 1.175 2005/01/15 00:56:41 ksekar + Unicast services don't disappear when logging +out of VPN + +Revision 1.174 2005/01/14 18:44:28 ksekar + mDNSResponder is crashing when changing domains + +Revision 1.173 2005/01/14 18:34:22 ksekar + Services registered outside of firewall don't succeed after location change + +Revision 1.172 2005/01/11 22:50:52 ksekar +Fixed constant naming (was using kLLQ_DefLease for update leases) + +Revision 1.171 2005/01/10 04:52:49 ksekar +Changed LogMsg to debugf + +Revision 1.170 2005/01/08 00:50:05 ksekar +Fixed spelling mistake in log msg + +Revision 1.169 2005/01/08 00:42:18 ksekar + Clean up syslog messages + +Revision 1.168 2004/12/23 23:22:47 ksekar + Unicast known answers "name" pointers point to garbage stack memory + +Revision 1.167 2004/12/22 22:25:47 ksekar + NATPMP: handle location changes + +Revision 1.166 2004/12/22 00:04:12 ksekar + mDNSResponder crashing in ReceivePortMapReply + +Revision 1.165 2004/12/18 03:14:22 cheshire +DblNAT -> DoubleNAT + +Revision 1.164 2004/12/17 03:55:40 ksekar +Don't use -1 as special meaning for expiration timer (it is a valid +value, and is redundant with our state variables) + +Revision 1.163 2004/12/17 03:51:53 ksekar + Don't update TXT record if service registration fails + +Revision 1.162 2004/12/17 01:29:11 ksekar + Questions can go deaf on location changes + +Revision 1.161 2004/12/16 20:42:02 cheshire +Fix compiler warnings + +Revision 1.160 2004/12/16 20:13:00 cheshire + Cache memory management improvements + +Revision 1.159 2004/12/15 02:11:22 ksekar + Don't check for Dynamic DNS hostname uniqueness + +Revision 1.158 2004/12/15 02:04:28 ksekar +Refinement to previous checkin - we should still return NatTraversal error when the port mapping fails + +Revision 1.157 2004/12/15 01:39:21 ksekar +Refinement to previous checkin - we should still return NatTraversal error when the port mapping fails + +Revision 1.156 2004/12/15 01:18:57 ksekar + Call DeregisterService on nat port map failure + +Revision 1.155 2004/12/14 21:21:20 ksekar + NAT-PMP: Update response format to contain "Seconds Since Boot" + +Revision 1.154 2004/12/14 20:52:27 cheshire +Add question->qnamehash and cr->resrec.namehash to log message + +Revision 1.153 2004/12/14 20:45:02 cheshire +Improved error logging in "unexpected answer" message + +Revision 1.152 2004/12/14 03:02:10 ksekar + Rare race condition can cause crash + +Revision 1.151 2004/12/13 21:45:08 ksekar +uDNS_DeregisterService should return NoError if called twice (to follow mDNS behavior expected by daemon layer) + +Revision 1.150 2004/12/13 20:42:41 ksekar +Fixed LogMsg + +Revision 1.149 2004/12/13 18:10:03 ksekar +Fixed LogMsg + +Revision 1.148 2004/12/13 01:18:04 ksekar +Fixed unused variable warning for non-debug builds + +Revision 1.147 2004/12/12 23:51:42 ksekar + Wide-area registrations should fallback to using DHCP hostname as target + +Revision 1.146 2004/12/12 23:30:40 ksekar + Extra RRs not properly unlinked when parent service registration fails + +Revision 1.145 2004/12/12 22:56:29 ksekar + Need to properly handle duplicate long-lived queries + +Revision 1.144 2004/12/11 20:55:29 ksekar + Clean up registration state machines + +Revision 1.143 2004/12/10 01:21:27 cheshire + Get rid of "LLQ Responses over TCP not currently supported" message + +Revision 1.142 2004/12/08 02:03:31 ksekar + Looping on NAT Traversal error - check for +NULL RR on error + +Revision 1.141 2004/12/07 01:39:28 cheshire +Don't fail if the same server is responsible for more than one domain +(e.g. the same DNS server may be responsible for both apple.com. and 17.in-addr.arpa.) + +Revision 1.140 2004/12/06 21:15:22 ksekar + mDNSResponder crashed in CheckServiceRegistrations + +Revision 1.139 2004/12/06 19:08:03 cheshire +Add clarifying comment -- CountLabels() excludes the final root label. + +Revision 1.138 2004/12/06 01:45:54 ksekar +Correct wording in LogMsg + +Revision 1.137 2004/12/03 20:40:35 ksekar + Looping on NAT Traversal error + +Revision 1.136 2004/12/03 07:20:50 ksekar + Wide-Area: Registration of large TXT record fails + +Revision 1.135 2004/12/03 05:18:33 ksekar + mDNSResponder needs to return more specific TSIG errors + +Revision 1.134 2004/12/02 20:03:49 ksekar + Still publishes wide-area domains even after switching to a local subnet + +Revision 1.133 2004/12/02 18:37:52 ksekar + Registering with port number zero should not create a port mapping + +Revision 1.132 2004/12/01 20:57:19 ksekar + Wide Area Service Discovery must be split-DNS aware + +Revision 1.131 2004/12/01 19:59:27 cheshire + Crash in mDNSPlatformTCPConnect +If a TCP response has the TC bit set, don't respond by just trying another TCP connection + +Revision 1.130 2004/12/01 02:43:23 cheshire +Don't call StatusCallback if function pointer is null + +Revision 1.129 2004/11/30 23:51:06 cheshire +Remove double semicolons + +Revision 1.128 2004/11/25 01:48:30 ksekar + Logging into VPN does not trigger registration of address record + +Revision 1.127 2004/11/25 01:41:36 ksekar +Changed unnecessary LogMsgs to debugfs + +Revision 1.126 2004/11/23 23:54:17 ksekar + Wide-Area DNSServiceRegisterRecord() failures +can crash mDNSResponder + +Revision 1.125 2004/11/23 04:16:48 cheshire +Removed receiveMsg() routine. + +Revision 1.124 2004/11/23 04:06:51 cheshire +Get rid of floating point constant -- in a small embedded device, bringing in all +the floating point libraries just to halve an integer value is a bit too heavyweight. + +Revision 1.123 2004/11/22 17:16:20 ksekar + Unicast services don't disappear when you disable all networking + +Revision 1.122 2004/11/19 18:00:34 ksekar + Security: use random ID for one-shot unicast queries + +Revision 1.121 2004/11/19 04:24:08 ksekar + Security: Enforce a "window" on one-shot wide-area queries + +Revision 1.120 2004/11/19 02:32:43 ksekar + Wide-Area Security: Add LLQ-ID to events + +Revision 1.119 2004/11/18 23:21:24 ksekar + LLQ Security: Need to verify src port/address for LLQ handshake + +Revision 1.118 2004/11/18 22:58:37 ksekar +Removed old comment. + +Revision 1.117 2004/11/18 18:04:21 ksekar +Restore checkins lost due to repository disk failure: Update comments & + +Revision 1.xxx 2004/11/17 06:17:57 cheshire +Update comments to show correct SRV names: _dns-update._udp.. and _dns-llq._udp.. + +Revision 1.xxx 2004/11/17 00:45:28 ksekar + Result of putUpdateLease not error-checked + +Revision 1.116 2004/11/16 01:41:47 ksekar +Fixed typo in debugf + +Revision 1.115 2004/11/15 20:09:24 ksekar + Wide Area support for Add/Remove record + +Revision 1.114 2004/11/13 02:32:47 ksekar + LLQ mobility fragile on non-primary interface +- fixed incorrect state comparison in CheckQueries + +Revision 1.113 2004/11/13 02:29:52 ksekar + LLQ refreshes not reliable + +Revision 1.112 2004/11/11 20:45:14 ksekar + self-conflict test not compatible with some BIND servers + +Revision 1.111 2004/11/11 20:14:55 ksekar + Wide-Area registrations not deregistered on sleep + +Revision 1.110 2004/11/10 23:53:53 ksekar +Remove no longer relevant comment + +Revision 1.109 2004/11/10 20:40:53 ksekar + LLQ mobility fragile on non-primary interface + +Revision 1.108 2004/11/01 20:36:16 ksekar + mDNSResponder should not receive Keychain Notifications + +Revision 1.107 2004/10/26 06:11:41 cheshire +Add improved logging to aid in diagnosis of mDNSResponder crashed + +Revision 1.106 2004/10/26 03:52:03 cheshire +Update checkin comments + +Revision 1.105 2004/10/26 01:15:06 cheshire +Use "#if 0" instead of commenting out code + +Revision 1.104 2004/10/25 21:41:38 ksekar + wide-area name conflicts can cause crash + +Revision 1.103 2004/10/25 19:30:52 ksekar + Simplify dynamic host name structures + +Revision 1.102 2004/10/23 01:16:00 cheshire + uDNS operations not always reliable on multi-homed hosts + +Revision 1.101 2004/10/22 20:52:07 ksekar + Create NAT port mappings for Long Lived Queries + +Revision 1.100 2004/10/20 02:16:41 cheshire +Improve "could not confirm existence of NS record" error message +Don't call newRR->RecordCallback if it is NULL + +Revision 1.99 2004/10/19 21:33:18 cheshire + Cannot resolve non-local registrations using the mach API +Added flag 'kDNSServiceFlagsForceMulticast'. Passing through an interface id for a unicast name +doesn't force multicast unless you set this flag to indicate explicitly that this is what you want + +Revision 1.98 2004/10/16 00:16:59 cheshire + Replace IP TTL 255 check with local subnet source address check + +Revision 1.97 2004/10/15 23:00:18 ksekar + Need to update LLQs on location changes + +Revision 1.96 2004/10/12 23:30:44 ksekar + mDNSResponder needs to follow CNAME referrals + +Revision 1.95 2004/10/12 03:15:09 ksekar + mDNS_StartQuery shouldn't return transient no-server error + +Revision 1.94 2004/10/12 02:49:20 ksekar + Clean up LLQ sleep/wake, error handling + +Revision 1.93 2004/10/08 04:17:25 ksekar + Don't use DNS extensions if the server does not advertise required SRV record + +Revision 1.92 2004/10/08 03:54:35 ksekar + Refine unicast polling intervals + +Revision 1.91 2004/09/30 17:45:34 ksekar + lots of log messages: mDNS_SetPrimaryIP: IP address unchanged + +Revision 1.90 2004/09/25 00:22:13 ksekar + Crash in uDNS_RegisterService + +Revision 1.89 2004/09/24 19:14:53 cheshire +Remove unused "extern mDNS mDNSStorage" + +Revision 1.88 2004/09/23 20:48:15 ksekar +Clarify retransmission debugf messages. + +Revision 1.87 2004/09/22 00:41:59 cheshire +Move tcp connection status codes into the legal range allocated for mDNS use + +Revision 1.86 2004/09/21 23:40:11 ksekar + mDNSResponder to return errors on NAT traversal failure + +Revision 1.85 2004/09/21 22:38:27 ksekar + PrimaryIP type uninitialized + +Revision 1.84 2004/09/18 00:30:39 cheshire + Infinite loop in CheckServiceRegistrations + +Revision 1.83 2004/09/17 00:31:51 cheshire +For consistency with ipv6, renamed rdata field 'ip' to 'ipv4' + +Revision 1.82 2004/09/16 21:36:36 cheshire + Fix unsafe use of mDNSPlatformTimeNow() +Changes to add necessary locking calls around unicast DNS operations + +Revision 1.81 2004/09/16 02:29:39 cheshire +Moved mDNS_Lock/mDNS_Unlock to DNSCommon.c; Added necessary locking around +uDNS_ReceiveMsg, uDNS_StartQuery, uDNS_UpdateRecord, uDNS_RegisterService + +Revision 1.80 2004/09/16 01:58:21 cheshire +Fix compiler warnings + +Revision 1.79 2004/09/16 00:24:48 cheshire + Fix unsafe use of mDNSPlatformTimeNow() + +Revision 1.78 2004/09/15 01:16:57 ksekar + mDNSResponder printing too many messages + +Revision 1.77 2004/09/14 23:27:47 cheshire +Fix compile errors + +Revision 1.76 2004/09/14 22:22:00 ksekar + Legacy browses broken against some BIND versions + +Revision 1.75 2004/09/03 19:23:05 ksekar +: Need retransmission mechanism for wide-area service registrations + +Revision 1.74 2004/09/02 17:49:04 ksekar +: 8A246: mDNSResponder crash while logging on restart +Fixed incorrect conversions, changed %s to %##s for all domain names. + +Revision 1.73 2004/09/02 01:39:40 cheshire +For better readability, follow consistent convention that QR bit comes first, followed by OP bits + +Revision 1.72 2004/09/01 03:59:29 ksekar +: Conditionally compile out uDNS code on Windows + +Revision 1.71 2004/08/27 17:51:53 ksekar +Replaced unnecessary LogMsg with debugf. + +Revision 1.70 2004/08/25 00:37:27 ksekar +: Cleanup DynDNS hostname registration code + +Revision 1.69 2004/08/18 17:35:41 ksekar +: Feature #9586: Need support for Legacy NAT gateways + +Revision 1.68 2004/08/14 03:22:41 cheshire + Dynamic DNS UI <-> mDNSResponder glue +Add GetUserSpecifiedDDNSName() routine +Convert ServiceRegDomain to domainname instead of C string +Replace mDNS_GenerateFQDN/mDNS_GenerateGlobalFQDN with mDNS_SetFQDNs + +Revision 1.67 2004/08/13 23:46:58 cheshire +"asyncronous" -> "asynchronous" + +Revision 1.66 2004/08/13 23:37:02 cheshire +Now that we do both uDNS and mDNS, global replace "uDNS_info.hostname" with +"uDNS_info.UnicastHostname" for clarity + +Revision 1.65 2004/08/13 23:12:32 cheshire +Don't use strcpy() and strlen() on "struct domainname" objects; +use AssignDomainName() and DomainNameLength() instead +(A "struct domainname" is a collection of packed pascal strings, not a C string.) + +Revision 1.64 2004/08/13 23:01:05 cheshire +Use platform-independent mDNSNULL instead of NULL + +Revision 1.63 2004/08/12 00:32:36 ksekar +: LLQ Refreshes never terminate if unanswered + +Revision 1.62 2004/08/10 23:19:14 ksekar +: DNS Extension daemon for Wide Area Service Discovery +Moved routines/constants to allow extern access for garbage collection daemon + +Revision 1.61 2004/07/30 17:40:06 ksekar +: TXT Record updates not available for wide-area services + +Revision 1.60 2004/07/29 19:40:05 ksekar +NATPMP Support - minor fixes and cleanup + +Revision 1.59 2004/07/29 19:27:15 ksekar +NATPMP Support - minor fixes and cleanup + +Revision 1.58 2004/07/27 07:35:38 shersche +fix syntax error, variables declared in the middle of a block + +Revision 1.57 2004/07/26 22:49:30 ksekar +: Feature #9516: Need support for NATPMP in client + +Revision 1.56 2004/07/26 19:14:44 ksekar +: 8A210: mDNSResponder crashed in startLLQHandshakeCallback + +Revision 1.55 2004/07/15 19:01:33 ksekar +: Check for incorrect time comparisons + +Revision 1.54 2004/06/22 02:10:53 ksekar +: Lighthouse failure causes packet flood to DNS + +Revision 1.53 2004/06/17 20:49:09 ksekar +: mDNSResponder crash while location cycling + +Revision 1.52 2004/06/17 01:13:11 ksekar +: polling interval too short + +Revision 1.51 2004/06/10 04:36:44 cheshire +Fix compiler warning + +Revision 1.50 2004/06/10 00:55:13 ksekar +: crash on network reconnect + +Revision 1.49 2004/06/10 00:10:50 ksekar +: Infinite Loop in uDNS_Execute() + +Revision 1.48 2004/06/09 20:03:37 ksekar +: Incorrect copying of resource record in deregistration + +Revision 1.47 2004/06/09 03:48:28 ksekar +: nameserver address fails with prod. Lighthouse server + +Revision 1.46 2004/06/09 01:44:30 ksekar + reworked Cache Record copy code + +Revision 1.45 2004/06/08 18:54:47 ksekar +: mDNSResponder leaks after exploring in Printer Setup Utility + +Revision 1.44 2004/06/05 00:33:51 cheshire +: Check for incorrect time comparisons + +Revision 1.43 2004/06/05 00:14:44 cheshire +Fix signed/unsigned and other compiler warnings + +Revision 1.42 2004/06/04 22:36:16 ksekar +Properly set u->nextevent in uDNS_Execute + +Revision 1.41 2004/06/04 08:58:29 ksekar +: Keychain integration for secure dynamic update + +Revision 1.40 2004/06/03 03:09:58 ksekar +: Garbage Collection for Dynamic Updates + +Revision 1.39 2004/06/01 23:46:50 ksekar +: DynDNS: dynamically look up LLQ/Update ports + +Revision 1.38 2004/05/31 22:19:44 ksekar +: Feature: DNS server->client notification on +record changes (#7805) - revert to polling mode on setup errors + +Revision 1.37 2004/05/28 23:42:37 ksekar +: Feature: DNS server->client notification on record changes (#7805) + +Revision 1.36 2004/05/18 23:51:25 cheshire +Tidy up all checkin comments to use consistent "" format for bug numbers + +Revision 1.35 2004/05/07 23:01:04 ksekar +Cleaned up list traversal in deriveGoodbyes - removed unnecessary +conditional assignment. + +Revision 1.34 2004/05/05 18:26:12 ksekar +Periodically re-transmit questions if the send() fails. Include +internal questions in retransmission. + +Revision 1.33 2004/05/05 17:40:06 ksekar +Removed prerequisite from deregistration update - it does not work for +shared records, and is unnecessary until we have more sophisticated +name conflict management. + +Revision 1.32 2004/05/05 17:32:18 ksekar +Prevent registration of loopback interface caused by removal of +Multicast flag in interface structure. + +Revision 1.31 2004/05/05 17:05:02 ksekar +Use LargeCacheRecord structs when pulling records off packets + +Revision 1.30 2004/04/16 21:33:27 ksekar +Fixed bug in processing GetZoneData responses that do not use BIND formatting. + +Revision 1.29 2004/04/15 20:03:13 ksekar +Clarified log message when pulling bad resource records off packet. + +Revision 1.28 2004/04/15 00:51:28 bradley +Minor tweaks for Windows and C++ builds. Added casts for signed/unsigned integers and 64-bit pointers. +Prefix some functions with mDNS to avoid conflicts. Disable benign warnings on Microsoft compilers. + +Revision 1.27 2004/04/14 23:09:28 ksekar +Support for TSIG signed dynamic updates. + +Revision 1.26 2004/04/14 19:36:05 ksekar +Fixed memory corruption error in deriveGoodbyes. + +Revision 1.25 2004/04/14 04:07:11 ksekar +Fixed crash in IsActiveUnicastQuery(). Removed redundant checks in routine. + +Revision 1.24 2004/04/08 09:41:40 bradley +Added const to AuthRecord in deadvertiseIfCallback to match callback typedef. + +Revision 1.23 2004/03/24 00:29:45 ksekar +Make it safe to call StopQuery in a unicast question callback + +Revision 1.22 2004/03/19 10:11:09 bradley +Added AuthRecord * cast from umalloc for C++ builds. + +Revision 1.21 2004/03/15 02:03:45 bradley +Added const to params where needed to match prototypes. Changed SetNewRData calls to use 0 instead +of -1 for unused size to fix warning. Disable assignment within conditional warnings with Visual C++. + +Revision 1.20 2004/03/13 02:07:26 ksekar +: DynDNS: Dynamic update of service records + +Revision 1.19 2004/03/13 01:57:33 ksekar +: DynDNS: Dynamic update of service records + +Revision 1.18 2004/02/21 08:34:15 bradley +Added casts from void * to specific type for C++ builds. Changed void * l-value cast +r-value cast to fix problems with VC++ builds. Removed empty switch to fix VC++ error. + +Revision 1.17 2004/02/21 02:06:24 cheshire +Can't use anonymous unions -- they're non-standard and don't work on all compilers + +Revision 1.16 2004/02/12 01:51:45 cheshire +Don't try to send uDNS queries unless we have at least one uDNS server available + +Revision 1.15 2004/02/10 03:02:46 cheshire +Fix compiler warning + +Revision 1.14 2004/02/06 23:04:19 ksekar +Basic Dynamic Update support via mDNS_Register (dissabled via +UNICAST_REGISTRATION #define) + +Revision 1.13 2004/02/03 22:15:01 ksekar +Fixed nameToAddr error check: don't abort state machine on nxdomain error. + +Revision 1.12 2004/02/03 19:47:36 ksekar +Added an asynchronous state machine mechanism to uDNS.c, including +calls to find the parent zone for a domain name. Changes include code +in repository previously dissabled via "#if 0 incomplete". Codepath +is currently unused, and will be called to create update records, etc. + +Revision 1.11 2004/01/30 02:12:30 ksekar +Changed uDNS_ReceiveMsg() to correctly return void. + +Revision 1.10 2004/01/29 02:59:17 ksekar +Unicast DNS: Changed from a resource record oriented question/response +matching to packet based matching. New callback architecture allows +collections of records in a response to be processed differently +depending on the nature of the request, and allows the same structure +to be used for internal and client-driven queries with different processing needs. + +Revision 1.9 2004/01/28 20:20:45 ksekar +Unified ActiveQueries and ActiveInternalQueries lists, using a flag to +demux them. Check-in includes work-in-progress code, #ifdef'd out. + +Revision 1.8 2004/01/28 02:30:07 ksekar +Added default Search Domains to unicast browsing, controlled via +Networking sharing prefs pane. Stopped sending unicast messages on +every interface. Fixed unicast resolving via mach-port API. + +Revision 1.7 2004/01/27 20:15:22 cheshire +: Time to prune obsolete code for listening on port 53 + +Revision 1.6 2004/01/24 23:47:17 cheshire +Use mDNSOpaque16fromIntVal() instead of shifting and masking + +Revision 1.5 2004/01/24 04:59:15 cheshire +Fixes so that Posix/Linux, OS9, Windows, and VxWorks targets build again + +Revision 1.4 2004/01/24 04:19:26 cheshire +Restore overwritten checkin 1.2 + +Revision 1.3 2004/01/23 23:23:15 ksekar +Added TCP support for truncated unicast messages. + +Revision 1.2 2004/01/22 03:48:41 cheshire +Make sure uDNS client doesn't accidentally use query ID zero + +Revision 1.1 2003/12/13 03:05:27 ksekar +: DynDNS: Unicast query of service records + + */ + +#include "uDNS.h" + +#if(defined(_MSC_VER)) + // Disable "assignment within conditional expression". + // Other compilers understand the convention that if you place the assignment expression within an extra pair + // of parentheses, this signals to the compiler that you really intended an assignment and no warning is necessary. + // The Microsoft compiler doesn't understand this convention, so in the absense of any other way to signal + // to the compiler that the assignment is intentional, we have to just turn this warning off completely. + #pragma warning(disable:4706) +#endif + +#define umalloc(x) mDNSPlatformMemAllocate(x) // short hands for common routines +#define ufree(x) mDNSPlatformMemFree(x) +#define ubzero(x,y) mDNSPlatformMemZero(x,y) +#define umemcpy(x, y, l) mDNSPlatformMemCopy(y, x, l) // uses memcpy(2) arg ordering + +// Asynchronous operation types + +typedef enum + { + zoneDataResult + // other async. operation names go here + } AsyncOpResultType; + +typedef struct + { + domainname zoneName; + mDNSAddr primaryAddr; + mDNSu16 zoneClass; + mDNSIPPort llqPort; + mDNSIPPort updatePort; + } zoneData_t; + +// other async. result struct defs go here + +typedef struct + { + AsyncOpResultType type; + zoneData_t zoneData; + // other async result structs go here + } AsyncOpResult; + +typedef void AsyncOpCallback(mStatus err, mDNS *const m, void *info, const AsyncOpResult *result); + + +// Private Function Prototypes +// Note: In general, functions are ordered such that they do not require forward declarations. +// However, prototypes are used where cyclic call graphs exist (e.g. foo calls bar, and bar calls +// foo), or when they aid in the grouping or readability of code (e.g. state machine code that is easier +// read top-to-bottom.) + +mDNSlocal mDNSBool FreeNATInfo(mDNS *m, NATTraversalInfo *n); +mDNSlocal void hndlTruncatedAnswer(DNSQuestion *question, const mDNSAddr *src, mDNS *m); +mDNSlocal mStatus startGetZoneData(domainname *name, mDNS *m, mDNSBool findUpdatePort, mDNSBool findLLQPort, AsyncOpCallback callback, void *callbackInfo); +mDNSlocal mDNSBool recvLLQResponse(mDNS *m, DNSMessage *msg, const mDNSu8 *end, const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSInterfaceID InterfaceID); +mDNSlocal void sendRecordRegistration(mDNS *const m, AuthRecord *rr); +mDNSlocal void SendServiceRegistration(mDNS *m, ServiceRecordSet *srs); +mDNSlocal void SendServiceDeregistration(mDNS *m, ServiceRecordSet *srs); +mDNSlocal void serviceRegistrationCallback(mStatus err, mDNS *const m, void *srsPtr, const AsyncOpResult *result); +mDNSlocal void SuspendLLQs(mDNS *m, mDNSBool DeregisterActive); +mDNSlocal void RestartQueries(mDNS *m); +mDNSlocal void startLLQHandshake(mDNS *m, LLQ_Info *info, mDNSBool defer); +mDNSlocal void llqResponseHndlr(mDNS * const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, void *context); + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - Temporary workaround +#endif + +// 17 Places in this file directly call mDNSPlatformTimeNow(), which is unsafe +// The platform function is now called mDNSPlatformRawTime(), and +// mDNSPlatformTimeNow() is defined here as a temporary workaround. +// This is a gross hack, and after this change has been tested for a while, +// all these calls should be replaced by simple references to m->timenow + +mDNSlocal mDNSs32 mDNSPlatformTimeNow(mDNS *m) + { + if (m->mDNS_busy && m->timenow) return(m->timenow); + LogMsg("ERROR: uDNS.c code executing without holding main mDNS lock"); + + // To get a quick and easy stack trace to find out *how* this routine + // is being called without holding main mDNS lock, uncomment the line below: + // *(long*)0=0; + + return(mDNS_TimeNow(m)); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - General Utility Functions +#endif + +// CountLabels() returns number of labels in name, excluding final root label +// (e.g. for "apple.com." CountLabels returns 2.) +mDNSlocal int CountLabels(const domainname *d) + { + int count = 0; + const mDNSu8 *ptr; + + for (ptr = d->c; *ptr; ptr = ptr + ptr[0] + 1) count++; + return count; + } + +mDNSlocal mDNSOpaque16 newMessageID(uDNS_GlobalInfo *u) + { + static mDNSBool randomized = mDNSfalse; + + if (!randomized) { u->NextMessageID = (mDNSu16)mDNSRandom(0xFFFF); randomized = mDNStrue; } + if (u->NextMessageID == 0) u->NextMessageID++; + return mDNSOpaque16fromIntVal(u->NextMessageID++); + } + +// unlink an AuthRecord from a linked list +mDNSlocal mStatus unlinkAR(AuthRecord **list, AuthRecord *const rr) + { + while (*list && *list != rr) list = &(*list)->next; + if (*list) { *list = rr->next; rr->next = mDNSNULL; return(mStatus_NoError); } + LogMsg("ERROR: unlinkAR - no such active record %##s", rr->resrec.name->c); + return(mStatus_NoSuchRecord); + } + +mDNSlocal void unlinkSRS(mDNS *m, ServiceRecordSet *srs) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + ServiceRecordSet **p; + NATTraversalInfo *n = u->NATTraversals; + + // verify that no NAT objects reference this service + while (n) + { + if (n->reg.ServiceRegistration == srs) + { + NATTraversalInfo *tmp = n; + n = n->next; + LogMsg("ERROR: Unlinking service record set %##s still referenced by NAT traversal object!", srs->RR_SRV.resrec.name->c); + FreeNATInfo(m, tmp); + } + else n = n->next; + } + + for (p = &u->ServiceRegistrations; *p; p = &(*p)->next) + if (*p == srs) + { + ExtraResourceRecord *e; + *p = srs->next; + srs->next = mDNSNULL; + for (e=srs->Extras; e; e=e->next) + if (unlinkAR(&u->RecordRegistrations, &e->r)) + LogMsg("unlinkSRS: extra record %##s not found", e->r.resrec.name->c); + return; + } + LogMsg("ERROR: unlinkSRS - SRS not found in ServiceRegistrations list %##s", srs->RR_SRV.resrec.name->c); + } + +mDNSlocal void LinkActiveQuestion(uDNS_GlobalInfo *u, DNSQuestion *q) + { + if (uDNS_IsActiveQuery(q, u)) + { LogMsg("LinkActiveQuestion - %##s (%d) already in list!", q->qname.c, q->qtype); return; } + + q->next = u->ActiveQueries; + u->ActiveQueries = q; + } + +// set retry timestamp for record with exponential backoff +// (for service record sets, use RR_SRV as representative for time checks +mDNSlocal void SetRecordRetry(mDNS *const m, AuthRecord *rr, mStatus SendErr) + { + rr->LastAPTime = mDNSPlatformTimeNow(m); + if (SendErr == mStatus_TransientErr || rr->ThisAPInterval < INIT_UCAST_POLL_INTERVAL) rr->ThisAPInterval = INIT_UCAST_POLL_INTERVAL; + else if (rr->ThisAPInterval*2 <= MAX_UCAST_POLL_INTERVAL) rr->ThisAPInterval *= 2; + else if (rr->ThisAPInterval != MAX_UCAST_POLL_INTERVAL) rr->ThisAPInterval = MAX_UCAST_POLL_INTERVAL; + } + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - Name Server List Management +#endif + +mDNSexport void mDNS_AddDNSServer(mDNS *const m, const mDNSAddr *addr, const domainname *d) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + DNSServer *s, **p = &u->Servers; + + mDNS_Lock(m); + if (!d) d = (domainname *)""; + + while (*p) // Check if we already have this {server,domain} pair registered + { + if (mDNSSameAddress(&(*p)->addr, addr) && SameDomainName(&(*p)->domain, d)) + LogMsg("Note: DNS Server %#a for domain %##s registered more than once", addr, d->c); + p=&(*p)->next; + } + + // allocate, add to list + s = umalloc(sizeof(*s)); + if (!s) { LogMsg("Error: mDNS_AddDNSServer - malloc"); goto end; } + s->addr = *addr; + s->del = mDNSfalse; + s->teststate = DNSServer_Untested; + AssignDomainName(&s->domain, d); + s->next = mDNSNULL; + *p = s; + + end: + mDNS_Unlock(m); + } + +mDNSexport void mDNS_DeleteDNSServers(mDNS *const m) + { + DNSServer *s; + mDNS_Lock(m); + + s = m->uDNS_info.Servers; + m->uDNS_info.Servers = mDNSNULL; + while (s) + { + DNSServer *tmp = s; + s = s->next; + ufree(tmp); + } + + mDNS_Unlock(m); + } + + // *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - authorization management +#endif + +mDNSlocal uDNS_AuthInfo *GetAuthInfoForName(const uDNS_GlobalInfo *u, const domainname *name) + { + uDNS_AuthInfo *ptr; + while (name->c[0]) + { + for (ptr = u->AuthInfoList; ptr; ptr = ptr->next) + if (SameDomainName(&ptr->zone, name)) return(ptr); + name = (const domainname *)(name->c + 1 + name->c[0]); + } + return mDNSNULL; + } + +mDNSlocal void DeleteAuthInfoForZone(uDNS_GlobalInfo *u, const domainname *zone) + { + uDNS_AuthInfo *ptr, *prev = mDNSNULL; + + for (ptr = u->AuthInfoList; ptr; ptr = ptr->next) + { + if (SameDomainName(&ptr->zone, zone)) + { + if (prev) prev->next = ptr->next; + else u->AuthInfoList = ptr->next; + ufree(ptr); + return; + } + prev = ptr; + } + } + +mDNSexport mStatus mDNS_SetSecretForZone(mDNS *m, const domainname *zone, const domainname *key, const char *sharedSecret) + { + uDNS_AuthInfo *info; + mDNSu8 keybuf[1024]; + mDNSs32 keylen; + uDNS_GlobalInfo *u = &m->uDNS_info; + mStatus status = mStatus_NoError; + + mDNS_Lock(m); + + if (GetAuthInfoForName(u, zone)) DeleteAuthInfoForZone(u, zone); + if (!key) goto exit; + + info = (uDNS_AuthInfo*)umalloc(sizeof(*info)); + if (!info) { LogMsg("ERROR: umalloc"); status = mStatus_NoMemoryErr; goto exit; } + ubzero(info, sizeof(*info)); + AssignDomainName(&info->zone, zone); + AssignDomainName(&info->keyname, key); + + keylen = DNSDigest_Base64ToBin(sharedSecret, keybuf, 1024); + if (keylen < 0) + { + LogMsg("ERROR: mDNS_SetSecretForZone - could not convert shared secret %s from base64", sharedSecret); + ufree(info); + status = mStatus_UnknownErr; + goto exit; + } + DNSDigest_ConstructHMACKey(info, keybuf, (mDNSu32)keylen); + + // link into list + info->next = m->uDNS_info.AuthInfoList; + m->uDNS_info.AuthInfoList = info; +exit: + mDNS_Unlock(m); + return status; + } + + // *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - NAT Traversal +#endif + +mDNSlocal mDNSBool DomainContainsLabelString(const domainname *d, const char *str) + { + const domainlabel *l; + domainlabel buf; + + if (!MakeDomainLabelFromLiteralString(&buf, str)) return mDNSfalse; + + for (l = (const domainlabel *)d; l->c[0]; l = (const domainlabel *)(l->c + l->c[0]+1)) + if (SameDomainLabel(l->c, buf.c)) return mDNStrue; + return mDNSfalse; + } + +// allocate struct, link into global list, initialize +mDNSlocal NATTraversalInfo *AllocNATInfo(mDNS *const m, NATOp_t op, NATResponseHndlr callback) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + NATTraversalInfo *info = umalloc(sizeof(NATTraversalInfo)); + if (!info) { LogMsg("ERROR: malloc"); return mDNSNULL; } + ubzero(info, sizeof(NATTraversalInfo)); + info->next = u->NATTraversals; + u->NATTraversals = info; + info->retry = mDNSPlatformTimeNow(m) + NATMAP_INIT_RETRY; + info->op = op; + info->state = NATState_Init; + info->ReceiveResponse = callback; + info->PublicPort.NotAnInteger = 0; + info->Router = u->Router; + return info; + } + +// unlink from list, deallocate +mDNSlocal mDNSBool FreeNATInfo(mDNS *m, NATTraversalInfo *n) + { + NATTraversalInfo *ptr, *prev = mDNSNULL; + ServiceRecordSet *s = m->uDNS_info.ServiceRegistrations; + + // Verify that object is not referenced by any services + while (s) + { + if (s->uDNS_info.NATinfo == n) + { + LogMsg("Error: Freeing NAT info object still referenced by Service Record Set %##s!", s->RR_SRV.resrec.name->c); + s->uDNS_info.NATinfo = mDNSNULL; + } + s = s->next; + } + + if (n == m->uDNS_info.LLQNatInfo) m->uDNS_info.LLQNatInfo = mDNSNULL; + ptr = m->uDNS_info.NATTraversals; + while (ptr) + { + if (ptr == n) + { + if (prev) prev->next = ptr->next; + else m->uDNS_info.NATTraversals = ptr->next; + ufree(n); + return mDNStrue; + } + prev = ptr; + ptr = ptr->next; + } + LogMsg("FreeNATInfo: NATTraversalInfo not found in list"); + return mDNSfalse; + } + +mDNSlocal void SendNATMsg(NATTraversalInfo *info, mDNS *m) + { + mStatus err; + uDNS_GlobalInfo *u = &m->uDNS_info; + + if (info->state != NATState_Request && info->state != NATState_Refresh) + { LogMsg("SendNATMsg: Bad state %d", info->state); return; } + + if (u->Router.ip.v4.NotAnInteger) + { + // send msg if we have a router + const mDNSu8 *end = (mDNSu8 *)&info->request; + if (info->op == NATOp_AddrRequest) end += sizeof(NATAddrRequest); + else end += sizeof(NATPortMapRequest); + + err = mDNSPlatformSendUDP(m, &info->request, end, 0, &u->Router, NATPMPPort); + if (!err) (info->ntries++); // don't increment attempt counter if the send failed + } + + // set retry + if (info->RetryInterval < NATMAP_INIT_RETRY) info->RetryInterval = NATMAP_INIT_RETRY; + else if (info->RetryInterval * 2 > NATMAP_MAX_RETRY) info->RetryInterval = NATMAP_MAX_RETRY; + else info->RetryInterval *= 2; + info->retry = mDNSPlatformTimeNow(m) + info->RetryInterval; + } + +mDNSlocal mDNSBool ReceiveNATAddrResponse(NATTraversalInfo *n, mDNS *m, mDNSu8 *pkt, mDNSu16 len) + { + mStatus err = mStatus_NoError; + AuthRecord *rr = mDNSNULL; + NATAddrReply *response = (NATAddrReply *)pkt; + mDNSAddr addr; + + if (n->state != NATState_Request) + { + LogMsg("ReceiveNATAddrResponse: bad state %d", n->state); + return mDNSfalse; + } + + rr = n->reg.RecordRegistration; + if (!rr) + { + LogMsg("ReceiveNATAddrResponse: registration cancelled"); + return mDNSfalse; + } + + addr.type = mDNSAddrType_IPv4; + addr.ip.v4 = rr->resrec.rdata->u.ipv4; + + if (!pkt) // timeout + { +#ifdef _LEGACY_NAT_TRAVERSAL_ + err = LNT_GetPublicIP(&addr.ip.v4); + if (err) goto end; + else n->state = NATState_Legacy; +#else + debugf("ReceiveNATAddrResponse: timeout"); + err = mStatus_NATTraversal; + goto end; +#endif // _LEGACY_NAT_TRAVERSAL_ + } + else + { + if (len < sizeof(*response)) + { + LogMsg("ReceiveNATAddrResponse: response too short (%d bytes)", len); + return mDNSfalse; + } + if (response->vers != NATMAP_VERS) + { + LogMsg("ReceiveNATAddrResponse: received version %d (expect version %d)", pkt[0], NATMAP_VERS); + return mDNSfalse; + } + if (response->opcode != (NATOp_AddrRequest | NATMAP_RESPONSE_MASK)) + { + LogMsg("ReceiveNATAddrResponse: bad response code %d", response->opcode); + return mDNSfalse; + } + if (response->err.NotAnInteger) + { LogMsg("ReceiveAddrResponse: received error %d", mDNSVal16(response->err)); err = mStatus_NATTraversal; goto end; } + + addr.ip.v4 = response->PubAddr; + n->state = NATState_Established; + } + + if (IsPrivateV4Addr(&addr)) + { + LogMsg("ReceiveNATAddrResponse: Double NAT"); + err = mStatus_DoubleNAT; + goto end; + } + + end: + if (err) + { + FreeNATInfo(m, n); + if (rr) + { + rr->uDNS_info.NATinfo = mDNSNULL; + rr->uDNS_info.state = regState_Unregistered; // note that rr is not yet in global list + rr->RecordCallback(m, rr, mStatus_NATTraversal); + // note - unsafe to touch rr after callback + } + return mDNStrue; + } + else LogOperation("Received public IP address %d.%d.%d.%d from NAT.", addr.ip.v4.b[0], addr.ip.v4.b[1], addr.ip.v4.b[2], addr.ip.v4.b[3]); + rr->resrec.rdata->u.ipv4 = addr.ip.v4; // replace rdata w/ public address + uDNS_RegisterRecord(m, rr); + return mDNStrue; + } + + +mDNSlocal void StartGetPublicAddr(mDNS *m, AuthRecord *AddressRec) + { + NATAddrRequest *req; + uDNS_GlobalInfo *u = &m->uDNS_info; + + NATTraversalInfo *info = AllocNATInfo(m, NATOp_AddrRequest, ReceiveNATAddrResponse); + if (!info) { uDNS_RegisterRecord(m, AddressRec); return; } + AddressRec->uDNS_info.NATinfo = info; + info->reg.RecordRegistration = AddressRec; + info->state = NATState_Request; + + // format message + req = &info->request.AddrReq; + req->vers = NATMAP_VERS; + req->opcode = NATOp_AddrRequest; + + if (!u->Router.ip.v4.NotAnInteger) + { + debugf("No router. Will retry NAT traversal in %ld ticks", NATMAP_INIT_RETRY); + return; + } + + SendNATMsg(info, m); + } + + +mDNSlocal void RefreshNATMapping(NATTraversalInfo *n, mDNS *m) + { + n->state = NATState_Refresh; + n->RetryInterval = NATMAP_INIT_RETRY; + n->ntries = 0; + SendNATMsg(n, m); + } + +mDNSlocal void LLQNatMapComplete(mDNS *m) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + LLQ_Info *llqInfo; + NATTraversalInfo *n = u->LLQNatInfo; + + if (!n) { LogMsg("Error: LLQNatMapComplete called with NULL LLQNatInfo"); return; } + if (n->state != NATState_Established && n->state != NATState_Legacy && n->state != NATState_Error) + { LogMsg("LLQNatMapComplete - bad nat state %d", n->state); return; } + + u->CurrentQuery = u->ActiveQueries; + while (u->CurrentQuery) + { + DNSQuestion *q = u->CurrentQuery; + u->CurrentQuery = u->CurrentQuery->next; + llqInfo = q->uDNS_info.llq; + if (q->LongLived && llqInfo->state == LLQ_NatMapWait) + { + if (n->state == NATState_Error) + { + llqInfo->NATMap = mDNSfalse; + llqInfo->question->uDNS_info.responseCallback = llqResponseHndlr; + llqInfo->state = LLQ_Poll; + llqInfo->question->LastQTime = mDNSPlatformTimeNow(m) - (2 * INIT_UCAST_POLL_INTERVAL); // trigger immediate poll + llqInfo->question->ThisQInterval = INIT_UCAST_POLL_INTERVAL; + } + else { llqInfo->state = LLQ_GetZoneInfo; startLLQHandshake(m, llqInfo, mDNSfalse); } + } + } + } + +mDNSlocal mDNSBool ReceivePortMapReply(NATTraversalInfo *n, mDNS *m, mDNSu8 *pkt, mDNSu16 len) + { + ServiceRecordSet *srs = n->reg.ServiceRegistration; + mDNSIPPort priv = srs ? srs->RR_SRV.resrec.rdata->u.srv.port : m->UnicastPort4; + mDNSu32 lease; + mDNSBool deletion = !n->request.PortReq.lease.NotAnInteger; + NATPortMapReply *reply = (NATPortMapReply *)pkt; + mDNSu8 *service = srs ? srs->RR_SRV.resrec.name->c : (mDNSu8 *)"\016LLQ event port"; + + if (n->state != NATState_Request && n->state != NATState_Refresh) + { LogMsg("ReceivePortMapReply (%##s): bad state %d", service, n->state); return mDNSfalse; } + + if (!pkt && !deletion) // timeout + { +#ifdef _LEGACY_NAT_TRAVERSAL_ + mDNSIPPort pub; + int ntries = 0; + mStatus err; + mDNSBool tcp = (srs && DomainContainsLabelString(srs->RR_PTR.resrec.name, "_tcp")); + + pub = priv; // initially request priv == pub + while (1) + { + err = LNT_MapPort(priv, pub, tcp); + if (!err) + { + n->PublicPort = pub; + n->state = NATState_Legacy; + goto end; + } + else if (err != mStatus_AlreadyRegistered || ++ntries > LEGACY_NATMAP_MAX_TRIES) + { + n->state = NATState_Error; + goto end; + } + else + { + // the mapping we want is taken - try a random port + mDNSu16 RandPort = mDNSRandom(DYN_PORT_MAX - DYN_PORT_MIN) + DYN_PORT_MIN; + pub = mDNSOpaque16fromIntVal(RandPort); + } + } +#else + goto end; +#endif // _LEGACY_NAT_TRAVERSAL_ + } + + if (len < sizeof(*reply)) { LogMsg("ReceivePortMapReply: response too short (%d bytes)", len); return mDNSfalse; } + if (reply->vers != NATMAP_VERS) { LogMsg("ReceivePortMapReply: received version %d (expect version %d)", pkt[0], NATMAP_VERS); return mDNSfalse; } + if (reply->opcode != (n->op | NATMAP_RESPONSE_MASK)) { LogMsg("ReceivePortMapReply: bad response code %d", pkt[1]); return mDNSfalse; } + if (reply->err.NotAnInteger) { LogMsg("ReceivePortMapReply: received error %d", mDNSVal16(reply->err)); return mDNSfalse; } + if (priv.NotAnInteger != reply->priv.NotAnInteger) return mDNSfalse; // packet does not match this request + + if (!srs && n != m->uDNS_info.LLQNatInfo) + { + LogMsg("ReceivePortMapReply: registration cancelled"); //!!!KRS change to debugf before checkin + FreeNATInfo(m, n); + return mDNStrue; + } + + if (deletion) { n->state = NATState_Deleted; return mDNStrue; } + + lease = (mDNSu32)mDNSVal32(reply->lease); + if (lease > 0x70000000UL / mDNSPlatformOneSecond) lease = 0x70000000UL / mDNSPlatformOneSecond; + + if (n->state == NATState_Refresh && reply->pub.NotAnInteger != n->PublicPort.NotAnInteger) + LogMsg("ReceivePortMapReply: NAT refresh changed public port from %d to %d", mDNSVal16(n->PublicPort), mDNSVal16(reply->pub)); + // this should never happen + // !!!KRS to be defensive, use SRVChanged flag on service and deregister here + + n->PublicPort = reply->pub; + if (reply->pub.NotAnInteger != n->request.PortReq.pub.NotAnInteger) n->request.PortReq.pub = reply->pub; // set message buffer for refreshes + + n->retry = mDNSPlatformTimeNow(m) + ((mDNSs32)lease * mDNSPlatformOneSecond / 2); // retry half way to expiration + + if (n->state == NATState_Refresh) { n->state = NATState_Established; return mDNStrue; } + n->state = NATState_Established; + + end: + if (n->state != NATState_Established && n->state != NATState_Legacy) + { + LogMsg("NAT Port Mapping (%##s): timeout", service); + if (pkt) LogMsg("!!! timeout with non-null packet"); + n->state = NATState_Error; + if (srs) + { + uDNS_HostnameInfo *hi = m->uDNS_info.Hostnames; + while (hi) + { + if (hi->arv6 && (hi->arv6->uDNS_info.state == regState_Registered || hi->arv6->uDNS_info.state == regState_Refresh)) break; + else hi = hi->next; + } + + if (hi) + { + debugf("Port map failed for service %##s - using IPv6 service target", service); + srs->uDNS_info.NATinfo = mDNSNULL; + FreeNATInfo(m, n); + goto register_service; + } + else srs->uDNS_info.state = regState_NATError; + } + else LLQNatMapComplete(m); + return mDNStrue; + } + else LogOperation("Mapped private port %d to public port %d", mDNSVal16(priv), mDNSVal16(n->PublicPort)); + + if (!srs) { LLQNatMapComplete(m); return mDNStrue; } + + register_service: + if (srs->uDNS_info.ns.ip.v4.NotAnInteger) SendServiceRegistration(m, srs); // non-zero server address means we already have necessary zone data to send update + else + { + srs->uDNS_info.state = regState_FetchingZoneData; + startGetZoneData(srs->RR_SRV.resrec.name, m, mDNStrue, mDNSfalse, serviceRegistrationCallback, srs); + } + return mDNStrue; + } + +mDNSlocal void FormatPortMaprequest(NATTraversalInfo *info, mDNSIPPort port) + { + NATPortMapRequest *req = &info->request.PortReq; + + req->vers = NATMAP_VERS; + req->opcode = info->op; + req->unused.NotAnInteger = 0; + req->priv = port; + req->pub = port; + req->lease = mDNSOpaque32fromIntVal(NATMAP_DEFAULT_LEASE); + } + +mDNSlocal void SendInitialPMapReq(mDNS *m, NATTraversalInfo *info) + { + if (!m->uDNS_info.Router.ip.v4.NotAnInteger) + { + debugf("No router. Will retry NAT traversal in %ld seconds", NATMAP_INIT_RETRY); + info->retry = mDNSPlatformTimeNow(m) + NATMAP_INIT_RETRY; + info->RetryInterval = NATMAP_INIT_RETRY; + return; + } + SendNATMsg(info, m); + return; + } + +mDNSlocal void StartNATPortMap(mDNS *m, ServiceRecordSet *srs) + { + NATOp_t op; + NATTraversalInfo *info; + + if (DomainContainsLabelString(srs->RR_PTR.resrec.name, "_tcp")) op = NATOp_MapTCP; + else if (DomainContainsLabelString(srs->RR_PTR.resrec.name, "_udp")) op = NATOp_MapUDP; + else { LogMsg("StartNATPortMap: could not determine transport protocol of service %##s", srs->RR_SRV.resrec.name->c); goto error; } + + if (srs->uDNS_info.NATinfo) { LogMsg("Error: StartNATPortMap - NAT info already initialized!"); FreeNATInfo(m, srs->uDNS_info.NATinfo); } + info = AllocNATInfo(m, op, ReceivePortMapReply); + srs->uDNS_info.NATinfo = info; + info->reg.ServiceRegistration = srs; + info->state = NATState_Request; + + FormatPortMaprequest(info, srs->RR_SRV.resrec.rdata->u.srv.port); + SendInitialPMapReq(m, info); + return; + + error: + startGetZoneData(srs->RR_SRV.resrec.name, m, mDNStrue, mDNSfalse, serviceRegistrationCallback, srs); + } + +mDNSlocal void DeleteNATPortMapping(mDNS *m, NATTraversalInfo *nat, ServiceRecordSet *srs) + { + if (nat->state == NATState_Established) // let other edge-case states expire for simplicity + { + // zero lease + nat->request.PortReq.lease.NotAnInteger = 0; + nat->state = NATState_Request; + SendNATMsg(nat, m); + } +#ifdef _LEGACY_NAT_TRAVERSAL_ + else if (nat->state == NATState_Legacy) + { + mStatus err = mStatus_NoError; + mDNSBool tcp = srs ? DomainContainsLabelString(srs->RR_PTR.resrec.name, "_tcp") : mDNSfalse; + err = LNT_UnmapPort(nat->PublicPort, tcp); + if (err) LogMsg("Legacy NAT Traversal - unmap request failed with error %ld", err); + } +#else + (void)srs; // unused +#endif // _LEGACY_NAT_TRAVERSAL_ + } + +mDNSlocal void StartLLQNatMap(mDNS *m) + { + NATTraversalInfo *info = AllocNATInfo(m, NATOp_MapUDP, ReceivePortMapReply); + uDNS_GlobalInfo *u = &m->uDNS_info; + + u->LLQNatInfo = info; + + info->reg.RecordRegistration = mDNSNULL; + info->reg.ServiceRegistration = mDNSNULL; + info->state = NATState_Request; + FormatPortMaprequest(info, m->UnicastPort4); + SendInitialPMapReq(m, info); + return; + } + +// if LLQ NAT context unreferenced, delete the mapping +mDNSlocal void CheckForUnreferencedLLQMapping(mDNS *m) + { + NATTraversalInfo *nat = m->uDNS_info.LLQNatInfo; + DNSQuestion *q; + + if (!nat) return; + + for (q = m->uDNS_info.ActiveQueries; q; q = q->next) + if (q->LongLived && q->uDNS_info.llq->NATMap) return; + + //to avoid race condition if we need to recreate before this finishes, we do one-shot deregistration + if (nat->state == NATState_Established || nat->state == NATState_Legacy) + DeleteNATPortMapping(m, nat, mDNSNULL); // for simplicity we allow other states to expire + FreeNATInfo(m, nat); // note: this clears the global LLQNatInfo pointer + } + + // *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - host name and interface management +#endif + +// if we ever want to refine support for multiple hostnames, we can add logic matching service names to a particular hostname +// for now, we grab the first registered DynDNS name, if any, or a static name we learned via a reverse-map query +mDNSlocal mDNSBool GetServiceTarget(uDNS_GlobalInfo *u, AuthRecord *srv, domainname *dst) + { + uDNS_HostnameInfo *hi = u->Hostnames; + (void)srv; // unused + + dst->c[0] = 0; + while (hi) + { + if (hi->arv4 && (hi->arv4->uDNS_info.state == regState_Registered || hi->arv4->uDNS_info.state == regState_Refresh)) + { + AssignDomainName(dst, hi->arv4->resrec.name); + return mDNStrue; + } + if (hi->arv6 && (hi->arv6->uDNS_info.state == regState_Registered || hi->arv6->uDNS_info.state == regState_Refresh)) + { + AssignDomainName(dst, hi->arv4->resrec.name); + return mDNStrue; + } + hi = hi->next; + } + + if (u->StaticHostname.c[0]) { AssignDomainName(dst, &u->StaticHostname); return mDNStrue; } + return mDNSfalse; + } + +mDNSlocal void UpdateSRV(mDNS *m, ServiceRecordSet *srs) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + ExtraResourceRecord *e; + + // Target change if: + // We have a target and were previously waiting for one, or + // We had a target and no longer do, or + // The target has changed + + domainname newtarget; + domainname *curtarget = &srs->RR_SRV.resrec.rdata->u.srv.target; + mDNSBool HaveTarget = GetServiceTarget(u, &srs->RR_SRV, &newtarget); + mDNSBool TargetChanged = (HaveTarget && srs->uDNS_info.state == regState_NoTarget) || (curtarget->c[0] && !HaveTarget) || !SameDomainName(curtarget, &newtarget); + mDNSBool HaveZoneData = srs->uDNS_info.ns.ip.v4.NotAnInteger ? mDNStrue : mDNSfalse; + + // Nat state change if: + // We were behind a NAT, and now we are behind a new NAT, or + // We're not behind a NAT but our port was previously mapped to a different public port + // We were not behind a NAT and now we are + + NATTraversalInfo *nat = srs->uDNS_info.NATinfo; + mDNSIPPort port = srs->RR_SRV.resrec.rdata->u.srv.port; + mDNSBool NATChanged = mDNSfalse; + mDNSBool NowBehindNAT = port.NotAnInteger && IsPrivateV4Addr(&u->AdvertisedV4); + mDNSBool WereBehindNAT = nat != mDNSNULL; + mDNSBool NATRouterChanged = nat && nat->Router.ip.v4.NotAnInteger != u->Router.ip.v4.NotAnInteger; + mDNSBool PortWasMapped = nat && (nat->state == NATState_Established || nat->state == NATState_Legacy) && nat->PublicPort.NotAnInteger != port.NotAnInteger; + + if (WereBehindNAT && NowBehindNAT && NATRouterChanged) NATChanged = mDNStrue; + else if (!NowBehindNAT && PortWasMapped) NATChanged = mDNStrue; + else if (!WereBehindNAT && NowBehindNAT) NATChanged = mDNStrue; + + if (!TargetChanged && !NATChanged) return; + + debugf("UpdateSRV (%##s) HadZoneData=%d, TargetChanged=%d, HaveTarget=%d, NowBehindNAT=%d, WereBehindNAT=%d, NATRouterChanged=%d, PortWasMapped=%d", + srs->RR_SRV.resrec.name->c, HaveZoneData, TargetChanged, HaveTarget, NowBehindNAT, WereBehindNAT, NATRouterChanged, PortWasMapped); + + switch(srs->uDNS_info.state) + { + case regState_FetchingZoneData: + case regState_Cancelled: + case regState_DeregPending: + case regState_DeregDeferred: + case regState_Unregistered: + case regState_NATMap: + case regState_ExtraQueued: + // In these states, the SRV has either not yet been registered (it will get up-to-date information when it is) + // or is in the process of, or has already been, deregistered + return; + + case regState_Pending: + case regState_Refresh: + case regState_UpdatePending: + // let the in-flight operation complete before updating + srs->uDNS_info.SRVUpdateDeferred = mDNStrue; + return; + + case regState_NATError: + if (!NATChanged) return; + // if nat changed, register if we have a target (below) + + case regState_NoTarget: + if (HaveTarget) + { + debugf("UpdateSRV: %s service %##s", HaveZoneData ? (NATChanged && NowBehindNAT ? "Starting Port Map for" : "Registering") : "Getting Zone Data for", srs->RR_SRV.resrec.name->c); + if (!HaveZoneData) + { + srs->uDNS_info.state = regState_FetchingZoneData; + startGetZoneData(srs->RR_SRV.resrec.name, m, mDNStrue, mDNSfalse, serviceRegistrationCallback, srs); + } + else + { + if (nat && (NATChanged || !NowBehindNAT)) { srs->uDNS_info.NATinfo = mDNSNULL; FreeNATInfo(m, nat); } + if (NATChanged && NowBehindNAT) { srs->uDNS_info.state = regState_NATMap; StartNATPortMap(m, srs); } + else SendServiceRegistration(m, srs); + } + } + return; + + case regState_Registered: + // target or nat changed. deregister service. upon completion, we'll look for a new target + debugf("UpdateSRV: SRV record changed for service %##s - deregistering (will re-register with new SRV)", srs->RR_SRV.resrec.name->c); + for (e = srs->Extras; e; e = e->next) e->r.uDNS_info.state = regState_ExtraQueued; // extra will be re-registed if the service is re-registered + srs->uDNS_info.SRVChanged = mDNStrue; + SendServiceDeregistration(m, srs); + return; + } + } + +mDNSlocal void UpdateSRVRecords(mDNS *m) + { + ServiceRecordSet *srs; + + for (srs = m->uDNS_info.ServiceRegistrations; srs; srs = srs->next) UpdateSRV(m, srs); + } + +mDNSlocal void HostnameCallback(mDNS *const m, AuthRecord *const rr, mStatus result) + { + uDNS_HostnameInfo *hi = (uDNS_HostnameInfo *)rr->RecordContext; + + if (result == mStatus_MemFree) + { + if (hi) + { + if (hi->arv4 == rr) hi->arv4 = mDNSNULL; + else if (hi->arv4 == rr) hi->arv6 = mDNSNULL; + rr->RecordContext = mDNSNULL; + if (!hi->arv4 && !hi->arv6) ufree(hi); // free hi when both v4 and v6 AuthRecs deallocated + } + ufree(rr); + return; + } + + if (result) + { + // don't unlink or free - we can retry when we get a new address/router + if (rr->resrec.rrtype == kDNSType_A) + LogMsg("HostnameCallback: Error %ld for registration of %##s IP %.4a", result, rr->resrec.name->c, &rr->resrec.rdata->u.ipv4); + else + LogMsg("HostnameCallback: Error %ld for registration of %##s IP %.16a", result, rr->resrec.name->c, &rr->resrec.rdata->u.ipv6); + if (!hi) { ufree(rr); return; } + if (rr->uDNS_info.state != regState_Unregistered) LogMsg("Error: HostnameCallback invoked with error code for record not in regState_Unregistered!"); + + if ((!hi->arv4 || hi->arv4->uDNS_info.state == regState_Unregistered) && + (!hi->arv6 || hi->arv6->uDNS_info.state == regState_Unregistered)) + { + // only deliver status if both v4 and v6 fail + rr->RecordContext = (void *)hi->StatusContext; + if (hi->StatusCallback) + hi->StatusCallback(m, rr, result); // client may NOT make API calls here + rr->RecordContext = (void *)hi; + } + return; + } + // register any pending services that require a target + UpdateSRVRecords(m); + + // Deliver success to client + if (!hi) { LogMsg("HostnameCallback invoked with orphaned address record"); return; } + if (rr->resrec.rrtype == kDNSType_A) + LogMsg("Registered hostname %##s IP %.4a", rr->resrec.name->c, &rr->resrec.rdata->u.ipv4); + else + LogMsg("Registered hostname %##s IP %.16a", rr->resrec.name->c, &rr->resrec.rdata->u.ipv6); + + rr->RecordContext = (void *)hi->StatusContext; + if (hi->StatusCallback) + hi->StatusCallback(m, rr, result); // client may NOT make API calls here + rr->RecordContext = (void *)hi; + } + +// register record or begin NAT traversal +mDNSlocal void AdvertiseHostname(mDNS *m, uDNS_HostnameInfo *h) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + + if (u->AdvertisedV4.ip.v4.NotAnInteger && h->arv4->uDNS_info.state == regState_Unregistered) + { + if (IsPrivateV4Addr(&u->AdvertisedV4)) + StartGetPublicAddr(m, h->arv4); + else + { + LogMsg("Advertising %##s IP %.4a", h->arv4->resrec.name->c, &u->AdvertisedV4.ip.v4); + uDNS_RegisterRecord(m, h->arv4); + } + } + if (u->AdvertisedV6.ip.v6.b[0] && h->arv6->uDNS_info.state == regState_Unregistered) + { + LogMsg("Advertising %##s IP %.16a", h->arv4->resrec.name->c, &u->AdvertisedV6.ip.v6); + uDNS_RegisterRecord(m, h->arv6); + } + } + +mDNSlocal void FoundStaticHostname(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, mDNSBool AddRecord) + { + const domainname *pktname = &answer->rdata->u.name; + domainname *storedname = &m->uDNS_info.StaticHostname; + uDNS_HostnameInfo *h = m->uDNS_info.Hostnames; + + (void)question; + + debugf("FoundStaticHostname: %##s -> %##s (%s)", question->qname.c, answer->rdata->u.name.c, AddRecord ? "added" : "removed"); + if (AddRecord && !SameDomainName(pktname, storedname)) + { + AssignDomainName(storedname, pktname); + while (h) + { + if ((h->arv4 && (h->arv4->uDNS_info.state == regState_FetchingZoneData || h->arv4->uDNS_info.state == regState_Pending || h->arv4->uDNS_info.state == regState_NATMap)) || + (h->arv6 && (h->arv6->uDNS_info.state == regState_FetchingZoneData || h->arv6->uDNS_info.state == regState_Pending))) + { + // if we're in the process of registering a dynamic hostname, delay SRV update so we don't have to reregister services if the dynamic name succeeds + m->uDNS_info.DelaySRVUpdate = mDNStrue; + m->uDNS_info.NextSRVUpdate = mDNSPlatformTimeNow(m) + (5 * mDNSPlatformOneSecond); + return; + } + h = h->next; + } + UpdateSRVRecords(m); + } + else if (!AddRecord && SameDomainName(pktname, storedname)) + { + storedname->c[0] = 0; + UpdateSRVRecords(m); + } + } + +mDNSlocal void GetStaticHostname(mDNS *m) + { + char buf[MAX_ESCAPED_DOMAIN_NAME]; + DNSQuestion *q = &m->uDNS_info.ReverseMap; + mDNSu8 *ip = m->uDNS_info.AdvertisedV4.ip.v4.b; + mStatus err; + + if (m->uDNS_info.ReverseMapActive) + { + uDNS_StopQuery(m, q); + m->uDNS_info.ReverseMapActive = mDNSfalse; + } + + m->uDNS_info.StaticHostname.c[0] = 0; + if (!m->uDNS_info.AdvertisedV4.ip.v4.NotAnInteger) return; + ubzero(q, sizeof(*q)); + mDNS_snprintf(buf, MAX_ESCAPED_DOMAIN_NAME, "%d.%d.%d.%d.in-addr.arpa.", ip[3], ip[2], ip[1], ip[0]); + if (!MakeDomainNameFromDNSNameString(&q->qname, buf)) { LogMsg("Error: GetStaticHostname - bad name %s", buf); return; } + + q->InterfaceID = mDNSInterface_Any; + q->Target = zeroAddr; + q->qtype = kDNSType_PTR; + q->qclass = kDNSClass_IN; + q->LongLived = mDNSfalse; + q->ExpectUnique = mDNSfalse; + q->ForceMCast = mDNSfalse; + q->QuestionCallback = FoundStaticHostname; + q->QuestionContext = mDNSNULL; + + err = uDNS_StartQuery(m, q); + if (err) LogMsg("Error: GetStaticHostname - StartQuery returned error %d", err); + else m->uDNS_info.ReverseMapActive = mDNStrue; + } + +mDNSlocal void AssignHostnameInfoAuthRecord(mDNS *m, uDNS_HostnameInfo *hi, int type) + { + AuthRecord **dst = (type == mDNSAddrType_IPv4 ? &hi->arv4 : &hi->arv6); + AuthRecord *ar = umalloc(sizeof(*ar)); + uDNS_GlobalInfo *u = &m->uDNS_info; + + if (type != mDNSAddrType_IPv4 && type != mDNSAddrType_IPv6) { LogMsg("ERROR: AssignHostnameInfoAuthRecord - bad type %d", type); return; } + if (!ar) { LogMsg("ERROR: AssignHostnameInfoAuthRecord - malloc"); return; } + + mDNS_SetupResourceRecord(ar, mDNSNULL, 0, type == mDNSAddrType_IPv4 ? kDNSType_A : kDNSType_AAAA, 1, kDNSRecordTypeKnownUnique, HostnameCallback, hi); + AssignDomainName(ar->resrec.name, &hi->fqdn); + + // only set RData if we have a valid IP + if (type == mDNSAddrType_IPv4 && u->AdvertisedV4.ip.v4.NotAnInteger) + { + if (u->MappedV4.ip.v4.NotAnInteger) ar->resrec.rdata->u.ipv4 = u->MappedV4.ip.v4; + else ar->resrec.rdata->u.ipv4 = u->AdvertisedV4.ip.v4; + } + else if (type == mDNSAddrType_IPv6 && u->AdvertisedV6.ip.v6.b[0]) + { + ar->resrec.rdata->u.ipv6 = u->AdvertisedV6.ip.v6; + } + + ar->uDNS_info.state = regState_Unregistered; + + if (*dst) + { + LogMsg("ERROR: AssignHostnameInfoAuthRecord - overwriting %s AuthRec", type == mDNSAddrType_IPv4 ? "IPv4" : "IPv6"); + unlinkAR(&u->RecordRegistrations, *dst); + (*dst)->RecordContext = mDNSNULL; // defensively clear backpointer to avoid doubly-referenced context + } + + *dst = ar; + } + + +// Deregister hostnames and register new names for each host domain with the current global +// values for the hostlabel and primary IP address +mDNSlocal void UpdateHostnameRegistrations(mDNS *m) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + uDNS_HostnameInfo *i; + + for (i = u->Hostnames; i; i = i->next) + { + if (i->arv4 && i->arv4->uDNS_info.state != regState_Unregistered && + i->arv4->resrec.rdata->u.ipv4.NotAnInteger != u->AdvertisedV4.ip.v4.NotAnInteger && + i->arv4->resrec.rdata->u.ipv4.NotAnInteger !=u->MappedV4.ip.v4.NotAnInteger) + { + uDNS_DeregisterRecord(m, i->arv4); + i->arv4 = mDNSNULL; + } + if (i->arv6 && !mDNSPlatformMemSame(i->arv6->resrec.rdata->u.ipv6.b, u->AdvertisedV6.ip.v6.b, 16) && i->arv6->uDNS_info.state != regState_Unregistered) + { + uDNS_DeregisterRecord(m, i->arv6); + i->arv6 = mDNSNULL; + } + + if (!i->arv4 && u->AdvertisedV4.ip.v4.NotAnInteger) AssignHostnameInfoAuthRecord(m, i, mDNSAddrType_IPv4); + else if (i->arv4 && i->arv4->uDNS_info.state == regState_Unregistered) i->arv4->resrec.rdata->u.ipv4 = u->AdvertisedV4.ip.v4; // simply overwrite unregistered + if (!i->arv6 && u->AdvertisedV6.ip.v6.b[0]) AssignHostnameInfoAuthRecord(m, i, mDNSAddrType_IPv6); + else if (i->arv6 &&i->arv6->uDNS_info.state == regState_Unregistered) i->arv6->resrec.rdata->u.ipv6 = u->AdvertisedV6.ip.v6; + + AdvertiseHostname(m, i); + } + } + +mDNSexport void mDNS_AddDynDNSHostName(mDNS *m, const domainname *fqdn, mDNSRecordCallback *StatusCallback, const void *StatusContext) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + uDNS_HostnameInfo *ptr, *new; + + mDNS_Lock(m); + + // check if domain already registered + for (ptr = u->Hostnames; ptr; ptr = ptr->next) + { + if (SameDomainName(fqdn, &ptr->fqdn)) + { LogMsg("Host Domain %##s already in list", fqdn->c); goto exit; } + } + + // allocate and format new address record + new = umalloc(sizeof(*new)); + if (!new) { LogMsg("ERROR: mDNS_AddDynDNSHostname - malloc"); goto exit; } + ubzero(new, sizeof(*new)); + new->next = u->Hostnames; + u->Hostnames = new; + + AssignDomainName(&new->fqdn, fqdn); + new->StatusCallback = StatusCallback; + new->StatusContext = StatusContext; + + if (u->AdvertisedV4.ip.v4.NotAnInteger) AssignHostnameInfoAuthRecord(m, new, mDNSAddrType_IPv4); + else new->arv4 = mDNSNULL; + if (u->AdvertisedV6.ip.v6.b[0]) AssignHostnameInfoAuthRecord(m, new, mDNSAddrType_IPv6); + else new->arv6 = mDNSNULL; + + if (u->AdvertisedV6.ip.v6.b[0] || u->AdvertisedV4.ip.v4.NotAnInteger) AdvertiseHostname(m, new); + +exit: + mDNS_Unlock(m); + } + +mDNSexport void mDNS_RemoveDynDNSHostName(mDNS *m, const domainname *fqdn) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + uDNS_HostnameInfo **ptr = &u->Hostnames; + + mDNS_Lock(m); + + while (*ptr && !SameDomainName(fqdn, &(*ptr)->fqdn)) ptr = &(*ptr)->next; + if (!*ptr) LogMsg("mDNS_RemoveDynDNSHostName: no such domainname %##s", fqdn->c); + else + { + uDNS_HostnameInfo *hi = *ptr; + *ptr = (*ptr)->next; // unlink + if (hi->arv4) + { + hi->arv4->RecordContext = mDNSNULL; // about to free wrapper struct + if (hi->arv4->uDNS_info.state != regState_Unregistered) uDNS_DeregisterRecord(m, hi->arv4); + else { ufree(hi->arv4); hi->arv4 = mDNSNULL; } + } + if (hi->arv6) + { + hi->arv6->RecordContext = mDNSNULL; // about to free wrapper struct + if (hi->arv6->uDNS_info.state != regState_Unregistered) uDNS_DeregisterRecord(m, hi->arv6); + else { ufree(hi->arv6); hi->arv6 = mDNSNULL; } + } + ufree(hi); + } + UpdateSRVRecords(m); + mDNS_Unlock(m); + } + +mDNSexport void mDNS_SetPrimaryInterfaceInfo(mDNS *m, const mDNSAddr *v4addr, const mDNSAddr *v6addr, const mDNSAddr *router) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + mDNSBool v4Changed, v6Changed, RouterChanged; + + if (v4addr && v4addr->type != mDNSAddrType_IPv4) { LogMsg("mDNS_SetPrimaryInterfaceInfo V4 address - incorrect type. Discarding."); return; } + if (v6addr && v6addr->type != mDNSAddrType_IPv6) { LogMsg("mDNS_SetPrimaryInterfaceInfo V6 address - incorrect type. Discarding."); return; } + if (router && router->type != mDNSAddrType_IPv4) { LogMsg("mDNS_SetPrimaryInterfaceInfo passed non-V4 router. Discarding."); return; } + + mDNS_Lock(m); + + v4Changed = (v4addr ? v4addr->ip.v4.NotAnInteger : 0) != u->AdvertisedV4.ip.v4.NotAnInteger; + v6Changed = v6addr ? !mDNSPlatformMemSame(v6addr, &u->AdvertisedV6, sizeof(*v6addr)) : (u->AdvertisedV6.ip.v6.b[0] != 0); + RouterChanged = (router ? router->ip.v4.NotAnInteger : 0) != u->Router.ip.v4.NotAnInteger; + +#if MDNS_DEBUGMSGS + if (v4addr && (v4Changed || RouterChanged)) + LogMsg("mDNS_SetPrimaryInterfaceInfo: address changed from %d.%d.%d.%d to %d.%d.%d.%d:%d", + u->AdvertisedV4.ip.v4.b[0], u->AdvertisedV4.ip.v4.b[1], u->AdvertisedV4.ip.v4.b[2], u->AdvertisedV4.ip.v4.b[3], + v4addr->ip.v4.b[0], v4addr->ip.v4.b[1], v4addr->ip.v4.b[2], v4addr->ip.v4.b[3]); +#endif // MDNS_DEBUGMSGS + + if ((v4Changed || RouterChanged) && u->MappedV4.ip.v4.NotAnInteger) u->MappedV4.ip.v4.NotAnInteger = 0; + if (v4addr) u->AdvertisedV4 = *v4addr; else u->AdvertisedV4.ip.v4.NotAnInteger = 0; + if (v6addr) u->AdvertisedV6 = *v6addr; else ubzero(u->AdvertisedV6.ip.v6.b, 16); + if (router) u->Router = *router; else u->Router.ip.v4.NotAnInteger = 0; + // setting router to zero indicates that nat mappings must be reestablished when router is reset + + if ((v4Changed || RouterChanged || v6Changed) && v4addr) + { + // don't update these unless we've got V4 + UpdateHostnameRegistrations(m); + UpdateSRVRecords(m); + GetStaticHostname(m); // look up reverse map record to find any static hostnames for our IP address + } + + mDNS_Unlock(m); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - Incoming Message Processing +#endif + +mDNSlocal mDNSBool kaListContainsAnswer(DNSQuestion *question, CacheRecord *rr) + { + CacheRecord *ptr; + + for (ptr = question->uDNS_info.knownAnswers; ptr; ptr = ptr->next) + if (SameResourceRecord(&ptr->resrec, &rr->resrec)) return mDNStrue; + + return mDNSfalse; + } + + +mDNSlocal void removeKnownAnswer(DNSQuestion *question, CacheRecord *rr) + { + CacheRecord *ptr, *prev = mDNSNULL; + + for (ptr = question->uDNS_info.knownAnswers; ptr; ptr = ptr->next) + { + if (SameResourceRecord(&ptr->resrec, &rr->resrec)) + { + if (prev) prev->next = ptr->next; + else question->uDNS_info.knownAnswers = ptr->next; + ufree(ptr); + return; + } + prev = ptr; + } + LogMsg("removeKnownAnswer() called for record not in KA list"); + } + + +mDNSlocal void addKnownAnswer(DNSQuestion *question, const CacheRecord *rr) + { + CacheRecord *newCR = mDNSNULL; + mDNSu32 size; + + size = sizeof(CacheRecord) + rr->resrec.rdlength - InlineCacheRDSize; + newCR = (CacheRecord *)umalloc(size); + if (!newCR) { LogMsg("ERROR: addKnownAnswer - malloc"); return; } + umemcpy(newCR, rr, size); + newCR->resrec.rdata = (RData*)&newCR->rdatastorage; + newCR->resrec.rdata->MaxRDLength = rr->resrec.rdlength; + newCR->resrec.name = &question->qname; + newCR->next = question->uDNS_info.knownAnswers; + question->uDNS_info.knownAnswers = newCR; + } + +mDNSlocal void deriveGoodbyes(mDNS * const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question) + { + const mDNSu8 *ptr; + int i; + CacheRecord *fptr, *ka, *cr, *answers = mDNSNULL, *prev = mDNSNULL; + LargeCacheRecord *lcr; + + if (question != m->uDNS_info.CurrentQuery) { LogMsg("ERROR: deriveGoodbyes called without CurrentQuery set!"); return; } + + ptr = LocateAnswers(msg, end); + if (!ptr) goto pkt_error; + + if (!msg->h.numAnswers) + { + // delete the whole KA list + ka = question->uDNS_info.knownAnswers; + while (ka) + { + debugf("deriving goodbye for %##s", ka->resrec.name->c); + + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + question->QuestionCallback(m, question, &ka->resrec, mDNSfalse); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + // CAUTION: Need to be careful after calling question->QuestionCallback(), + // because the client's callback function is allowed to do anything, + // including starting/stopping queries, registering/deregistering records, etc. + if (question != m->uDNS_info.CurrentQuery) + { + debugf("deriveGoodbyes - question removed via callback. returning."); + return; + } + fptr = ka; + ka = ka->next; + ufree(fptr); + } + question->uDNS_info.knownAnswers = mDNSNULL; + return; + } + + // make a list of all the new answers + for (i = 0; i < msg->h.numAnswers; i++) + { + lcr = (LargeCacheRecord *)umalloc(sizeof(LargeCacheRecord)); + if (!lcr) goto malloc_error; + ubzero(lcr, sizeof(LargeCacheRecord)); + ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAns, lcr); + if (!ptr) goto pkt_error; + cr = &lcr->r; + if (ResourceRecordAnswersQuestion(&cr->resrec, question)) + { + cr->next = answers; + answers = cr; + } + else ufree(cr); + } + + // make sure every known answer is in the answer list + ka = question->uDNS_info.knownAnswers; + while (ka) + { + for (cr = answers; cr; cr = cr->next) + { if (SameResourceRecord(&ka->resrec, &cr->resrec)) break; } + if (!cr) + { + // record is in KA list but not answer list - remove from KA list + if (prev) prev->next = ka->next; + else question->uDNS_info.knownAnswers = ka->next; + debugf("deriving goodbye for %##s", ka->resrec.name->c); + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + question->QuestionCallback(m, question, &ka->resrec, mDNSfalse); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + // CAUTION: Need to be careful after calling question->QuestionCallback(), + // because the client's callback function is allowed to do anything, + // including starting/stopping queries, registering/deregistering records, etc. + if (question != m->uDNS_info.CurrentQuery) + { + debugf("deriveGoodbyes - question removed via callback. returning."); + return; + } + fptr = ka; + ka = ka->next; + ufree(fptr); + } + else + { + prev = ka; + ka = ka->next; + } + } + + // free temp answers list + cr = answers; + while (cr) { fptr = cr; cr = cr->next; ufree(fptr); } + + return; + + pkt_error: + LogMsg("ERROR: deriveGoodbyes - received malformed response to query for %##s (%d)", + question->qname.c, question->qtype); + return; + + malloc_error: + LogMsg("ERROR: Malloc"); + } + +mDNSlocal void pktResponseHndlr(mDNS * const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, mDNSBool llq) + { + const mDNSu8 *ptr; + int i; + CacheRecord *cr = &m->rec.r; + mDNSBool goodbye, inKAList; + int followedCNames = 0; + static const int maxCNames = 5; + LLQ_Info *llqInfo = question->uDNS_info.llq; + domainname origname; + origname.c[0] = 0; + + if (question != m->uDNS_info.CurrentQuery) + { LogMsg("ERROR: pktResponseHdnlr called without CurrentQuery ptr set!"); return; } + + if (question->uDNS_info.Answered == 0 && msg->h.numAnswers == 0 && !llq) + { + /* NXDOMAIN error or empty RR set - notify client */ + question->uDNS_info.Answered = mDNStrue; + + /* Create empty resource record */ + cr->resrec.RecordType = kDNSRecordTypeUnregistered; + cr->resrec.InterfaceID = mDNSNULL; + cr->resrec.name = &question->qname; + cr->resrec.rrtype = question->qtype; + cr->resrec.rrclass = question->qclass; + cr->resrec.rroriginalttl = 1; /* What should we use for the TTL? TTL from SOA for domain? */ + cr->resrec.rdlength = 0; + cr->resrec.rdestimate = 0; + cr->resrec.namehash = 0; + cr->resrec.namehash = 0; + cr->resrec.rdata = (RData*)&cr->rdatastorage; + cr->resrec.rdata->MaxRDLength = cr->resrec.rdlength; + + /* Pass empty answer to callback */ + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + question->QuestionCallback(m, question, &cr->resrec, 0); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + // CAUTION: Need to be careful after calling question->QuestionCallback(), + // because the client's callback function is allowed to do anything, + // including starting/stopping queries, registering/deregistering records, etc. + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + if (question != m->uDNS_info.CurrentQuery) + { + debugf("pktResponseHndlr - CurrentQuery changed by QuestionCallback - returning."); + return; + } + } + + question->uDNS_info.Answered = mDNStrue; + + ptr = LocateAnswers(msg, end); + if (!ptr) goto pkt_error; + + for (i = 0; i < msg->h.numAnswers; i++) + { + ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &m->rec); + if (!ptr) goto pkt_error; + if (ResourceRecordAnswersQuestion(&cr->resrec, question)) + { + goodbye = llq ? ((mDNSs32)cr->resrec.rroriginalttl == -1) : mDNSfalse; + if (cr->resrec.rrtype == kDNSType_CNAME) + { + if (followedCNames > (maxCNames - 1)) LogMsg("Error: too many CNAME referals for question %##s", &origname); + else + { + debugf("Following cname %##s -> %##s", question->qname.c, cr->resrec.rdata->u.name.c); + if (question->ReturnCNAME) + { + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + question->QuestionCallback(m, question, &cr->resrec, !goodbye); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + // CAUTION: Need to be careful after calling question->QuestionCallback(), + // because the client's callback function is allowed to do anything, + // including starting/stopping queries, registering/deregistering records, etc. + if (question != m->uDNS_info.CurrentQuery) + { + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + debugf("pktResponseHndlr - CurrentQuery changed by QuestionCallback - returning."); + return; + } + } + AssignDomainName(&origname, &question->qname); + AssignDomainName(&question->qname, &cr->resrec.rdata->u.name); + question->qnamehash = DomainNameHashValue(&question->qname); + followedCNames++; + i = -1; // restart packet answer matching + ptr = LocateAnswers(msg, end); + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + continue; + } + } + + inKAList = kaListContainsAnswer(question, cr); + + if ((goodbye && !inKAList) || (!goodbye && inKAList)) + { + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + continue; // list up to date + } + if (!inKAList) addKnownAnswer(question, cr); + if (goodbye) removeKnownAnswer(question, cr); + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + question->QuestionCallback(m, question, &cr->resrec, !goodbye); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + if (question != m->uDNS_info.CurrentQuery) + { + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + debugf("pktResponseHndlr - CurrentQuery changed by QuestionCallback - returning"); + return; + } + } + + m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it + } + + if (!llq || llqInfo->state == LLQ_Poll || llqInfo->deriveRemovesOnResume) + { + deriveGoodbyes(m, msg, end,question); + if (llq && llqInfo->deriveRemovesOnResume) llqInfo->deriveRemovesOnResume = mDNSfalse; + } + + // Our interval may be set lower to recover from failures -- now that we have an answer, fully back off retry. + // If the server advertised an LLQ-specific port number then that implies that this zone + // *wants* to support LLQs, so if the setup fails (e.g. because we are behind a NAT) + // then we use a slightly faster polling rate to give slightly better user experience. + if (llq && llqInfo->state == LLQ_Poll && llqInfo->servPort.NotAnInteger) question->ThisQInterval = LLQ_POLL_INTERVAL; + else if (question->ThisQInterval < MAX_UCAST_POLL_INTERVAL) question->ThisQInterval = MAX_UCAST_POLL_INTERVAL; + return; + + pkt_error: + LogMsg("ERROR: pktResponseHndlr - received malformed response to query for %##s (%d)", + question->qname.c, question->qtype); + return; + } + +mDNSlocal void simpleResponseHndlr(mDNS * const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, void *context) + { + (void)context; // unused + pktResponseHndlr(m, msg, end, question, mDNSfalse); + } + +mDNSlocal void llqResponseHndlr(mDNS * const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, void *context) + { + (void)context; // unused + pktResponseHndlr(m, msg, end, question, mDNStrue); + } + +mDNSlocal mStatus ParseTSIGError(mDNS *m, const DNSMessage *msg, const mDNSu8 *end, const domainname *displayname) + { + LargeCacheRecord lcr; + const mDNSu8 *ptr; + mStatus err = mStatus_NoError; + int i; + + ptr = LocateAdditionals(msg, end); + if (!ptr) goto finish; + + for (i = 0; i < msg->h.numAdditionals; i++) + { + ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); + if (!ptr) goto finish; + if (lcr.r.resrec.rrtype == kDNSType_TSIG) + { + mDNSu32 macsize; + mDNSu8 *rd = lcr.r.resrec.rdata->u.data; + mDNSu8 *rdend = rd + MaximumRDSize; + int alglen = DomainNameLength(&lcr.r.resrec.rdata->u.name); + + if (rd + alglen > rdend) goto finish; + rd += alglen; // algorithm name + if (rd + 6 > rdend) goto finish; + rd += 6; // 48-bit timestamp + if (rd + sizeof(mDNSOpaque16) > rdend) goto finish; + rd += sizeof(mDNSOpaque16); // fudge + if (rd + sizeof(mDNSOpaque16) > rdend) goto finish; + macsize = mDNSVal16(*(mDNSOpaque16 *)rd); + rd += sizeof(mDNSOpaque16); // MAC size + if (rd + macsize > rdend) goto finish; + rd += macsize; + if (rd + sizeof(mDNSOpaque16) > rdend) goto finish; + rd += sizeof(mDNSOpaque16); // orig id + if (rd + sizeof(mDNSOpaque16) > rdend) goto finish; + err = mDNSVal16(*(mDNSOpaque16 *)rd); // error code + + if (err == TSIG_ErrBadSig) { LogMsg("%##s: bad signature", displayname->c); err = mStatus_BadSig; } + else if (err == TSIG_ErrBadKey) { LogMsg("%##s: bad key", displayname->c); err = mStatus_BadKey; } + else if (err == TSIG_ErrBadTime) { LogMsg("%##s: bad time", displayname->c); err = mStatus_BadTime; } + else if (err) { LogMsg("%##s: unknown tsig error %d", displayname->c, err); err = mStatus_UnknownErr; } + goto finish; + } + } + + finish: + return err; + } + +mDNSlocal mStatus checkUpdateResult(domainname *displayname, mDNSu8 rcode, mDNS *m, const DNSMessage *msg, const mDNSu8 *end) + { + (void)msg; // currently unused, needed for TSIG errors + if (!rcode) return mStatus_NoError; + else if (rcode == kDNSFlag1_RC_YXDomain) + { + debugf("name in use: %##s", displayname->c); + return mStatus_NameConflict; + } + else if (rcode == kDNSFlag1_RC_Refused) + { + LogMsg("Update %##s refused", displayname->c); + return mStatus_Refused; + } + else if (rcode == kDNSFlag1_RC_NXRRSet) + { + LogMsg("Reregister refused (NXRRSET): %##s", displayname->c); + return mStatus_NoSuchRecord; + } + else if (rcode == kDNSFlag1_RC_NotAuth) + { + // TSIG errors should come with FmtErr as per RFC 2845, but BIND 9 sends them with NotAuth so we look here too + mStatus tsigerr = ParseTSIGError(m, msg, end, displayname); + if (!tsigerr) + { + LogMsg("Permission denied (NOAUTH): %##s", displayname->c); + return mStatus_UnknownErr; + } + else return tsigerr; + } + else if (rcode == kDNSFlag1_RC_FmtErr) + { + mStatus tsigerr = ParseTSIGError(m, msg, end, displayname); + if (!tsigerr) + { + LogMsg("Format Error: %##s", displayname->c); + return mStatus_UnknownErr; + } + else return tsigerr; + } + else + { + LogMsg("Update %##s failed with rcode %d", displayname->c, rcode); + return mStatus_UnknownErr; + } + } + +mDNSlocal void hndlServiceUpdateReply(mDNS * const m, ServiceRecordSet *srs, mStatus err) + { + mDNSBool InvokeCallback = mDNSfalse; + uDNS_RegInfo *info = &srs->uDNS_info; + NATTraversalInfo *nat = srs->uDNS_info.NATinfo; + ExtraResourceRecord **e = &srs->Extras; + AuthRecord *txt = &srs->RR_TXT; + uDNS_RegInfo *txtInfo = &txt->uDNS_info; + switch (info->state) + { + case regState_Pending: + if (err == mStatus_NameConflict && !info->TestForSelfConflict) + { + info->TestForSelfConflict = mDNStrue; + debugf("checking for self-conflict of service %##s", srs->RR_SRV.resrec.name->c); + SendServiceRegistration(m, srs); + return; + } + else if (info->TestForSelfConflict) + { + info->TestForSelfConflict = mDNSfalse; + if (err == mStatus_NoSuchRecord) err = mStatus_NameConflict; // NoSuchRecord implies that our prereq was not met, so we actually have a name conflict + if (err) info->state = regState_Unregistered; + else info->state = regState_Registered; + InvokeCallback = mDNStrue; + break; + } + else if (err == mStatus_UnknownErr && info->lease) + { + LogMsg("Re-trying update of service %##s without lease option", srs->RR_SRV.resrec.name->c); + info->lease = mDNSfalse; + SendServiceRegistration(m, srs); + return; + } + else + { + if (err) { LogMsg("Error %ld for registration of service %##s", err, srs->RR_SRV.resrec.name->c); info->state = regState_Unregistered; } //!!!KRS make sure all structs will still get cleaned up when client calls DeregisterService with this state + else info->state = regState_Registered; + InvokeCallback = mDNStrue; + break; + } + case regState_Refresh: + if (err) + { + LogMsg("Error %ld for refresh of service %##s", err, srs->RR_SRV.resrec.name->c); + InvokeCallback = mDNStrue; + info->state = regState_Unregistered; + } + else info->state = regState_Registered; + break; + case regState_DeregPending: + if (err) LogMsg("Error %ld for deregistration of service %##s", err, srs->RR_SRV.resrec.name->c); + if (info->SRVChanged) + { + info->state = regState_NoTarget; // NoTarget will allow us to pick up new target OR nat traversal state + break; + } + err = mStatus_MemFree; + InvokeCallback = mDNStrue; + if (nat) + { + if (nat->state == NATState_Deleted) { info->NATinfo = mDNSNULL; FreeNATInfo(m, nat); } // deletion copmleted + else nat->reg.ServiceRegistration = mDNSNULL; // allow mapping deletion to continue + } + info->state = regState_Unregistered; + break; + case regState_DeregDeferred: + if (err) + { + debugf("Error %ld received prior to deferred derigstration of %##s", err, srs->RR_SRV.resrec.name->c); + err = mStatus_MemFree; + InvokeCallback = mDNStrue; + info->state = regState_Unregistered; + break; + } + else + { + debugf("Performing deferred deregistration of %##s", srs->RR_SRV.resrec.name->c); + info->state = regState_Registered; + SendServiceDeregistration(m, srs); + return; + } + case regState_UpdatePending: + if (err) + { + LogMsg("hndlServiceUpdateReply: error updating TXT record for service %##s", srs->RR_SRV.resrec.name->c); + info->state = regState_Unregistered; + InvokeCallback = mDNStrue; + } + else + { + info->state = regState_Registered; + // deallocate old RData + if (txtInfo->UpdateRDCallback) txtInfo->UpdateRDCallback(m, txt, txtInfo->OrigRData); + SetNewRData(&txt->resrec, txtInfo->InFlightRData, txtInfo->InFlightRDLen); + txtInfo->OrigRData = mDNSNULL; + txtInfo->InFlightRData = mDNSNULL; + } + break; + case regState_FetchingZoneData: + case regState_Registered: + case regState_Cancelled: + case regState_Unregistered: + case regState_NATMap: + case regState_NoTarget: + case regState_ExtraQueued: + case regState_NATError: + LogMsg("hndlServiceUpdateReply called for service %##s in unexpected state %d with error %ld. Unlinking.", + srs->RR_SRV.resrec.name->c, info->state, err); + err = mStatus_UnknownErr; + } + + if ((info->SRVChanged || info->SRVUpdateDeferred) && (info->state == regState_NoTarget || info->state == regState_Registered)) + { + if (InvokeCallback) + { + info->ClientCallbackDeferred = mDNStrue; + info->DeferredStatus = err; + } + info->SRVChanged = mDNSfalse; + UpdateSRV(m, srs); + return; + } + + while (*e) + { + uDNS_RegInfo *einfo = &(*e)->r.uDNS_info; + if (einfo->state == regState_ExtraQueued) + { + if (info->state == regState_Registered && !err) + { + // extra resource record queued for this service - copy zone info and register + AssignDomainName(&einfo->zone, &info->zone); + einfo->ns = info->ns; + einfo->port = info->port; + einfo->lease = info->lease; + sendRecordRegistration(m, &(*e)->r); + e = &(*e)->next; + } + else if (err && einfo->state != regState_Unregistered) + { + // unlink extra from list + einfo->state = regState_Unregistered; + *e = (*e)->next; + } + else e = &(*e)->next; + } + else e = &(*e)->next; + } + + srs->RR_SRV.ThisAPInterval = INIT_UCAST_POLL_INTERVAL - 1; // reset retry delay for future refreshes, dereg, etc. + if (info->state == regState_Unregistered) unlinkSRS(m, srs); + else if (txtInfo->QueuedRData && info->state == regState_Registered) + { + if (InvokeCallback) + { + // if we were supposed to give a client callback, we'll do it after we update the primary txt record + info->ClientCallbackDeferred = mDNStrue; + info->DeferredStatus = err; + } + info->state = regState_UpdatePending; + txtInfo->InFlightRData = txtInfo->QueuedRData; + txtInfo->InFlightRDLen = txtInfo->QueuedRDLen; + info->OrigRData = txt->resrec.rdata; + info->OrigRDLen = txt->resrec.rdlength; + txtInfo->QueuedRData = mDNSNULL; + SendServiceRegistration(m, srs); + return; + } + + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + if (InvokeCallback) srs->ServiceCallback(m, srs, err); + else if (info->ClientCallbackDeferred) + { + info->ClientCallbackDeferred = mDNSfalse; + srs->ServiceCallback(m, srs, info->DeferredStatus); + } + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + // NOTE: do not touch structures after calling ServiceCallback + } + +mDNSlocal void hndlRecordUpdateReply(mDNS *m, AuthRecord *rr, mStatus err) + { + uDNS_RegInfo *info = &rr->uDNS_info; + mDNSBool InvokeCallback = mDNStrue; + + if (info->state == regState_UpdatePending) + { + if (err) + { + LogMsg("Update record failed for %##s (err %d)", rr->resrec.name->c, err); + info->state = regState_Unregistered; + } + else + { + debugf("Update record %##s - success", rr->resrec.name->c); + info->state = regState_Registered; + // deallocate old RData + if (info->UpdateRDCallback) info->UpdateRDCallback(m, rr, info->OrigRData); + SetNewRData(&rr->resrec, info->InFlightRData, info->InFlightRDLen); + info->OrigRData = mDNSNULL; + info->InFlightRData = mDNSNULL; + } + } + + if (info->state == regState_DeregPending) + { + debugf("Received reply for deregister record %##s type %d", rr->resrec.name->c, rr->resrec.rrtype); + if (err) LogMsg("ERROR: Deregistration of record %##s type %d failed with error %ld", + rr->resrec.name->c, rr->resrec.rrtype, err); + err = mStatus_MemFree; + info->state = regState_Unregistered; + } + + if (info->state == regState_DeregDeferred) + { + if (err) + { + LogMsg("Cancelling deferred deregistration record %##s type %d due to registration error %ld", + rr->resrec.name->c, rr->resrec.rrtype, err); + info->state = regState_Unregistered; + } + debugf("Calling deferred deregistration of record %##s type %d", rr->resrec.name->c, rr->resrec.rrtype); + info->state = regState_Registered; + uDNS_DeregisterRecord(m, rr); + return; + } + + if (info->state == regState_Pending || info->state == regState_Refresh) + { + if (!err) + { + info->state = regState_Registered; + if (info->state == regState_Refresh) InvokeCallback = mDNSfalse; + } + else + { + if (info->lease && err == mStatus_UnknownErr) + { + LogMsg("Re-trying update of record %##s without lease option", rr->resrec.name->c); + info->lease = mDNSfalse; + sendRecordRegistration(m, rr); + return; + } + LogMsg("Registration of record %##s type %d failed with error %ld", rr->resrec.name->c, rr->resrec.rrtype, err); + info->state = regState_Unregistered; + } + } + + if (info->state == regState_Unregistered) unlinkAR(&m->uDNS_info.RecordRegistrations, rr); + else rr->ThisAPInterval = INIT_UCAST_POLL_INTERVAL - 1; // reset retry delay for future refreshes, dereg, etc. + + if (info->QueuedRData && info->state == regState_Registered) + { + info->state = regState_UpdatePending; + info->InFlightRData = info->QueuedRData; + info->InFlightRDLen = info->QueuedRDLen; + info->OrigRData = rr->resrec.rdata; + info->OrigRDLen = rr->resrec.rdlength; + info->QueuedRData = mDNSNULL; + sendRecordRegistration(m, rr); + return; + } + + if (InvokeCallback) + { + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + if (rr->RecordCallback) rr->RecordCallback(m, rr, err); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + } + } + + +mDNSlocal void SetUpdateExpiration(mDNS *m, DNSMessage *msg, const mDNSu8 *end, uDNS_RegInfo *info) + { + LargeCacheRecord lcr; + const mDNSu8 *ptr; + int i; + mDNSu32 lease = 0; + mDNSs32 expire; + + ptr = LocateAdditionals(msg, end); + + if (info->lease && (ptr = LocateAdditionals(msg, end))) + { + for (i = 0; i < msg->h.numAdditionals; i++) + { + ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); + if (!ptr) break; + if (lcr.r.resrec.rrtype == kDNSType_OPT) + { + if (lcr.r.resrec.rdlength < LEASE_OPT_RDLEN) continue; + if (lcr.r.resrec.rdata->u.opt.opt != kDNSOpt_Lease) continue; + lease = lcr.r.resrec.rdata->u.opt.OptData.lease; + break; + } + } + } + + if (lease > 0) + { + expire = (mDNSPlatformTimeNow(m) + (((mDNSs32)lease * mDNSPlatformOneSecond)) * 3/4); + if (info->state == regState_UpdatePending) + // if updating individual record, the service record set may expire sooner + { if (expire - info->expire < 0) info->expire = expire; } + else info->expire = expire; + } + else info->lease = mDNSfalse; + } + +mDNSexport void uDNS_ReceiveNATMap(mDNS *m, mDNSu8 *pkt, mDNSu16 len) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + NATTraversalInfo *ptr = u->NATTraversals; + NATOp_t op; + + // check length, version, opcode + if (len < sizeof(NATPortMapReply) && len < sizeof(NATAddrReply)) { LogMsg("NAT Traversal message too short (%d bytes)", len); return; } + if (pkt[0] != NATMAP_VERS) { LogMsg("Received NAT Traversal response with version %d (expect version %d)", pkt[0], NATMAP_VERS); return; } + op = pkt[1]; + if (!(op & NATMAP_RESPONSE_MASK)) { LogMsg("Received NAT Traversal message that is not a response (opcode %d)", op); return; } + + while (ptr) + { + if ((ptr->state == NATState_Request || ptr->state == NATState_Refresh) && (ptr->op | NATMAP_RESPONSE_MASK) == op) + if (ptr->ReceiveResponse(ptr, m, pkt, len)) break; // note callback may invalidate ptr if it return value is non-zero + ptr = ptr->next; + } + } + +mDNSlocal const domainname *DNSRelayTestQuestion = (domainname*) + "\x1" "1" "\x1" "0" "\x1" "0" "\x3" "127" "\xa" "dnsbugtest" + "\x1" "1" "\x1" "0" "\x1" "0" "\x3" "127" "\x7" "in-addr" "\x4" "arpa"; + +// Returns mDNStrue if response was handled +mDNSlocal mDNSBool uDNS_ReceiveTestQuestionResponse(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end, + const mDNSAddr *const srcaddr, const mDNSInterfaceID InterfaceID) + { + const mDNSu8 *ptr = msg->data; + DNSQuestion q; + DNSServer *s; + mDNSu32 result = 0; + mDNSBool found = mDNSfalse; + + // 1. Find out if this is an answer to one of our test questions + if (msg->h.numQuestions != 1) return(mDNSfalse); + ptr = getQuestion(msg, ptr, end, InterfaceID, &q); + if (!ptr) return(mDNSfalse); + if (q.qtype != kDNSType_PTR || q.qclass != kDNSClass_IN) return(mDNSfalse); + if (!SameDomainName(&q.qname, DNSRelayTestQuestion)) return(mDNSfalse); + + // 2. If the DNS relay gave us a positive response, then it's got buggy firmware + // else, if the DNS relay gave us an error or no-answer response, it passed our test + if ((msg->h.flags.b[1] & kDNSFlag1_RC) == kDNSFlag1_RC_NoErr && msg->h.numAnswers > 0) + result = DNSServer_Failed; + else + result = DNSServer_Passed; + + // 3. Find occurrences of this server in our list, and mark them appropriately + for (s = m->uDNS_info.Servers; s; s = s->next) + if (mDNSSameAddress(srcaddr, &s->addr) && s->teststate != result) + { s->teststate = result; found = mDNStrue; } + + // 4. Assuming we found the server in question in our list (don't want to risk being victim of a deliberate DOS attack here) + // log a message to let the user know why Wide-Area Service Discovery isn't working + if (found && result == DNSServer_Failed) + LogMsg("NOTE: Wide-Area Service Discovery disabled to avoid crashing defective DNS relay %#a.", srcaddr); + + return(mDNStrue); // Return mDNStrue to tell uDNS_ReceiveMsg it doens't need to process this packet further + } + +mDNSexport void uDNS_ReceiveMsg(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end, + const mDNSAddr *const srcaddr, const mDNSIPPort srcport, const mDNSAddr *const dstaddr, + const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID) + { + DNSQuestion *qptr; + AuthRecord *rptr; + ServiceRecordSet *sptr; + mStatus err = mStatus_NoError; + uDNS_GlobalInfo *u = &m->uDNS_info; + + mDNSu8 StdR = kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery; + mDNSu8 UpdateR = kDNSFlag0_QR_Response | kDNSFlag0_OP_Update; + mDNSu8 QR_OP = (mDNSu8)(msg->h.flags.b[0] & kDNSFlag0_QROP_Mask); + mDNSu8 rcode = (mDNSu8)(msg->h.flags.b[1] & kDNSFlag1_RC); + + mDNSs32 timenow = mDNSPlatformTimeNow(m); + + // unused + (void)dstaddr; + (void)dstport; + (void)InterfaceID; + + if (QR_OP == StdR) + { + // !!!KRS we should to a table lookup here to see if it answers an LLQ or a 1-shot + // LLQ Responses over TCP not currently supported + if (srcaddr && recvLLQResponse(m, msg, end, srcaddr, srcport, InterfaceID)) return; + + if (uDNS_ReceiveTestQuestionResponse(m, msg, end, srcaddr, InterfaceID)) return; + + for (qptr = u->ActiveQueries; qptr; qptr = qptr->next) + { + //!!!KRS we should have a hashtable, hashed on message id + if (qptr->uDNS_info.id.NotAnInteger == msg->h.id.NotAnInteger) + { + if (timenow - (qptr->LastQTime + RESPONSE_WINDOW) > 0) + { debugf("uDNS_ReceiveMsg - response received after maximum allowed window. Discarding"); return; } + if (msg->h.flags.b[0] & kDNSFlag0_TC) + { hndlTruncatedAnswer(qptr, srcaddr, m); return; } + else + { + u->CurrentQuery = qptr; + qptr->uDNS_info.responseCallback(m, msg, end, qptr, qptr->uDNS_info.context); + u->CurrentQuery = mDNSNULL; + // Note: responseCallback can invalidate qptr + return; + } + } + } + } + if (QR_OP == UpdateR) + { + for (sptr = u->ServiceRegistrations; sptr; sptr = sptr->next) + { + if (sptr->uDNS_info.id.NotAnInteger == msg->h.id.NotAnInteger) + { + err = checkUpdateResult(sptr->RR_SRV.resrec.name, rcode, m, msg, end); + if (!err) SetUpdateExpiration(m, msg, end, &sptr->uDNS_info); + hndlServiceUpdateReply(m, sptr, err); + return; + } + } + for (rptr = u->RecordRegistrations; rptr; rptr = rptr->next) + { + if (rptr->uDNS_info.id.NotAnInteger == msg->h.id.NotAnInteger) + { + err = checkUpdateResult(rptr->resrec.name, rcode, m, msg, end); + if (!err) SetUpdateExpiration(m, msg, end, &rptr->uDNS_info); + hndlRecordUpdateReply(m, rptr, err); + return; + } + } + } + debugf("Received unexpected response: ID %d matches no active records", mDNSVal16(msg->h.id)); + } + +// lookup a DNS Server, matching by name in split-dns configurations. Result stored in addr parameter if successful +mDNSlocal DNSServer *GetServerForName(uDNS_GlobalInfo *u, const domainname *name) + { + DNSServer *curmatch = mDNSNULL, *p = u->Servers; + int i, curmatchlen = -1; + int ncount = name ? CountLabels(name) : 0; + + while (p) + { + int scount = CountLabels(&p->domain); + if (scount <= ncount && scount > curmatchlen) + { + // only inspect if server's domain is longer than current best match and shorter than the name itself + const domainname *tail = name; + for (i = 0; i < ncount - scount; i++) + tail = (domainname *)(tail->c + 1 + tail->c[0]); // find "tail" (scount labels) of name + if (SameDomainName(tail, &p->domain)) { curmatch = p; curmatchlen = scount; } + } + p = p->next; + } + return(curmatch); + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - Query Routines +#endif + +#define sameID(x,y) mDNSPlatformMemSame(x,y,8) + +mDNSlocal void initializeQuery(DNSMessage *msg, DNSQuestion *question) + { + ubzero(msg, sizeof(msg)); + InitializeDNSMessage(&msg->h, question->uDNS_info.id, uQueryFlags); + } + +mDNSlocal mStatus constructQueryMsg(DNSMessage *msg, mDNSu8 **endPtr, DNSQuestion *const question) + { + initializeQuery(msg, question); + + *endPtr = putQuestion(msg, msg->data, msg->data + AbsoluteMaxDNSMessageData, &question->qname, question->qtype, question->qclass); + if (!*endPtr) + { + LogMsg("ERROR: Unicast query out of space in packet"); + return mStatus_UnknownErr; + } + return mStatus_NoError; + } + +mDNSlocal mDNSu8 *putLLQ(DNSMessage *const msg, mDNSu8 *ptr, DNSQuestion *question, LLQOptData *data, mDNSBool includeQuestion) + { + AuthRecord rr; + ResourceRecord *opt = &rr.resrec; + rdataOpt *optRD; + + //!!!KRS when we implement multiple llqs per message, we'll need to memmove anything past the question section + if (includeQuestion) + { + ptr = putQuestion(msg, ptr, msg->data + AbsoluteMaxDNSMessageData, &question->qname, question->qtype, question->qclass); + if (!ptr) { LogMsg("ERROR: putLLQ - putQuestion"); return mDNSNULL; } + } + // locate OptRR if it exists, set pointer to end + // !!!KRS implement me + + + // format opt rr (fields not specified are zero-valued) + ubzero(&rr, sizeof(AuthRecord)); + mDNS_SetupResourceRecord(&rr, mDNSNULL, mDNSInterface_Any, kDNSType_OPT, kStandardTTL, kDNSRecordTypeKnownUnique, mDNSNULL, mDNSNULL); + opt->rdlength = LLQ_OPT_RDLEN; + opt->rdestimate = LLQ_OPT_RDLEN; + + optRD = &rr.resrec.rdata->u.opt; + optRD->opt = kDNSOpt_LLQ; + optRD->optlen = LLQ_OPTLEN; + umemcpy(&optRD->OptData.llq, data, sizeof(*data)); + ptr = PutResourceRecordTTLJumbo(msg, ptr, &msg->h.numAdditionals, opt, 0); + if (!ptr) { LogMsg("ERROR: putLLQ - PutResourceRecordTTLJumbo"); return mDNSNULL; } + + return ptr; + } + + +mDNSlocal mDNSBool getLLQAtIndex(mDNS *m, DNSMessage *msg, const mDNSu8 *end, LLQOptData *llq, int index) + { + LargeCacheRecord lcr; + int i; + const mDNSu8 *ptr; + + ubzero(&lcr, sizeof(lcr)); + + ptr = LocateAdditionals(msg, end); + if (!ptr) return mDNSfalse; + + // find the last additional + for (i = 0; i < msg->h.numAdditionals; i++) +// { ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); if (!ptr) return mDNSfalse; } +//!!!KRS workaround for LH server bug, which puts OPT as first additional + { ptr = GetLargeResourceRecord(m, msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); if (!ptr) return mDNSfalse; if (lcr.r.resrec.rrtype == kDNSType_OPT) break; } + if (lcr.r.resrec.rrtype != kDNSType_OPT) return mDNSfalse; + if (lcr.r.resrec.rdlength < (index + 1) * LLQ_OPT_RDLEN) return mDNSfalse; // rdata too small + umemcpy(llq, (mDNSu8 *)&lcr.r.resrec.rdata->u.opt.OptData.llq + (index * sizeof(*llq)), sizeof(*llq)); + return mDNStrue; + } + +mDNSlocal void recvRefreshReply(mDNS *m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *q) + { + LLQ_Info *qInfo; + LLQOptData pktData; + + qInfo = q->uDNS_info.llq; + if (!getLLQAtIndex(m, msg, end, &pktData, 0)) { LogMsg("ERROR recvRefreshReply - getLLQAtIndex"); return; } + if (pktData.llqOp != kLLQOp_Refresh) return; + if (!sameID(pktData.id, qInfo->id)) { LogMsg("recvRefreshReply - ID mismatch. Discarding"); return; } + if (pktData.err != LLQErr_NoError) { LogMsg("recvRefreshReply: received error %d from server", pktData.err); return; } + + qInfo->expire = mDNSPlatformTimeNow(m) + ((mDNSs32)pktData.lease * mDNSPlatformOneSecond); + qInfo->retry = qInfo->expire - ((mDNSs32)pktData.lease * mDNSPlatformOneSecond/2); + + qInfo->origLease = pktData.lease; + qInfo->state = LLQ_Established; + } + +mDNSlocal void sendLLQRefresh(mDNS *m, DNSQuestion *q, mDNSu32 lease) + { + DNSMessage msg; + mDNSu8 *end; + LLQOptData llq; + LLQ_Info *info = q->uDNS_info.llq; + mStatus err; + mDNSs32 timenow; + + timenow = mDNSPlatformTimeNow(m); + if ((info->state == LLQ_Refresh && info->ntries >= kLLQ_MAX_TRIES) || + info->expire - timenow < 0) + { + LogMsg("Unable to refresh LLQ %##s - will retry in %d minutes", q->qname.c, kLLQ_DEF_RETRY/60); + info->state = LLQ_Retry; + info->retry = mDNSPlatformTimeNow(m) + kLLQ_DEF_RETRY * mDNSPlatformOneSecond; + info->deriveRemovesOnResume = mDNStrue; + return; + //!!!KRS handle this - periodically try to re-establish + } + + llq.vers = kLLQ_Vers; + llq.llqOp = kLLQOp_Refresh; + llq.err = LLQErr_NoError; + umemcpy(llq.id, info->id, 8); + llq.lease = lease; + + initializeQuery(&msg, q); + end = putLLQ(&msg, msg.data, q, &llq, mDNStrue); + if (!end) { LogMsg("ERROR: sendLLQRefresh - putLLQ"); return; } + + err = mDNSSendDNSMessage(m, &msg, end, mDNSInterface_Any, &info->servAddr, info->servPort, -1, mDNSNULL); + if (err) debugf("ERROR: sendLLQRefresh - mDNSSendDNSMessage returned %ld", err); + + if (info->state == LLQ_Established) info->ntries = 1; + else info->ntries++; + info->state = LLQ_Refresh; + q->LastQTime = timenow; + info->retry = (info->expire - q->LastQTime) / 2; + } + +mDNSlocal mDNSBool recvLLQEvent(mDNS *m, DNSQuestion *q, DNSMessage *msg, const mDNSu8 *end, const mDNSAddr *srcaddr, mDNSIPPort srcport, mDNSInterfaceID InterfaceID) + { + DNSMessage ack; + mDNSu8 *ackEnd = ack.data; + mStatus err; + LLQOptData opt; + + (void)InterfaceID; // unused + + // find Opt RR, verify correct ID + if (!getLLQAtIndex(m, msg, end, &opt, 0)) { debugf("Pkt does not contain LLQ Opt"); return mDNSfalse; } + if (!q->uDNS_info.llq) { LogMsg("Error: recvLLQEvent - question object does not contain LLQ metadata"); return mDNSfalse; } + if (!sameID(opt.id, q->uDNS_info.llq->id)) { return mDNSfalse; } + if (opt.llqOp != kLLQOp_Event) { if (!q->uDNS_info.llq->ntries) LogMsg("recvLLQEvent - Bad LLQ Opcode %d", opt.llqOp); return mDNSfalse; } + + // invoke response handler + m->uDNS_info.CurrentQuery = q; + q->uDNS_info.responseCallback(m, msg, end, q, q->uDNS_info.context); + if (m->uDNS_info.CurrentQuery != q) return mDNStrue; + + // format and send ack + InitializeDNSMessage(&ack.h, msg->h.id, ResponseFlags); + ackEnd = putLLQ(&ack, ack.data, mDNSNULL, &opt, mDNSfalse); + if (!ackEnd) { LogMsg("ERROR: recvLLQEvent - putLLQ"); return mDNSfalse; } + err = mDNSSendDNSMessage(m, &ack, ackEnd, mDNSInterface_Any, srcaddr, srcport, -1, mDNSNULL); + if (err) debugf("ERROR: recvLLQEvent - mDNSSendDNSMessage returned %ld", err); + return mDNStrue; + } + + + +mDNSlocal void hndlChallengeResponseAck(mDNS *m, DNSMessage *pktMsg, const mDNSu8 *end, LLQOptData *llq, DNSQuestion *q) + { + LLQ_Info *info = q->uDNS_info.llq; + + if (llq->err) { LogMsg("hndlChallengeResponseAck - received error %d from server", llq->err); goto error; } + if (!sameID(info->id, llq->id)) { LogMsg("hndlChallengeResponseAck - ID changed. discarding"); return; } // this can happen rarely (on packet loss + reordering) + info->expire = mDNSPlatformTimeNow(m) + ((mDNSs32)llq->lease * mDNSPlatformOneSecond); + info->retry = info->expire - ((mDNSs32)llq->lease * mDNSPlatformOneSecond / 2); + + info->origLease = llq->lease; + info->state = LLQ_Established; + + q->uDNS_info.responseCallback = llqResponseHndlr; + llqResponseHndlr(m, pktMsg, end, q, mDNSNULL); + return; + + error: + info->state = LLQ_Error; + } + +mDNSlocal void sendChallengeResponse(mDNS *m, DNSQuestion *q, LLQOptData *llq) + { + LLQ_Info *info = q->uDNS_info.llq; + DNSMessage response; + mDNSu8 *responsePtr = response.data; + mStatus err; + LLQOptData llqBuf; + mDNSs32 timenow = mDNSPlatformTimeNow(m); + + if (info->ntries++ == kLLQ_MAX_TRIES) + { + LogMsg("sendChallengeResponse: %d failed attempts for LLQ %##s. Will re-try in %d minutes", + kLLQ_MAX_TRIES, q->qname.c, kLLQ_DEF_RETRY / 60); + info->state = LLQ_Retry; + info->retry = timenow + (kLLQ_DEF_RETRY * mDNSPlatformOneSecond); + // !!!KRS give a callback error in these cases? + return; + } + + if (!llq) + { + llq = &llqBuf; + llq->vers = kLLQ_Vers; + llq->llqOp = kLLQOp_Setup; + llq->err = LLQErr_NoError; + umemcpy(llq->id, info->id, 8); + llq->lease = info->origLease; + } + + q->LastQTime = timenow; + info->retry = timenow + (kLLQ_INIT_RESEND * info->ntries * mDNSPlatformOneSecond); + + if (constructQueryMsg(&response, &responsePtr, q)) goto error; + responsePtr = putLLQ(&response, responsePtr, q, llq, mDNSfalse); + if (!responsePtr) { LogMsg("ERROR: sendChallengeResponse - putLLQ"); goto error; } + + err = mDNSSendDNSMessage(m, &response, responsePtr, mDNSInterface_Any, &info->servAddr, info->servPort, -1, mDNSNULL); + if (err) debugf("ERROR: sendChallengeResponse - mDNSSendDNSMessage returned %ld", err); + // on error, we procede as normal and retry after the appropriate interval + + return; + + error: + info->state = LLQ_Error; + } + + + +mDNSlocal void hndlRequestChallenge(mDNS *m, DNSMessage *pktMsg, const mDNSu8 *end, LLQOptData *llq, DNSQuestion *q) + { + LLQ_Info *info = q->uDNS_info.llq; + mDNSs32 timenow = mDNSPlatformTimeNow(m); + switch(llq->err) + { + case LLQErr_NoError: break; + case LLQErr_ServFull: + LogMsg("hndlRequestChallenge - received ServFull from server for LLQ %##s. Retry in %lu sec", q->qname.c, llq->lease); + info->retry = timenow + ((mDNSs32)llq->lease * mDNSPlatformOneSecond); + info->state = LLQ_Retry; + simpleResponseHndlr(m, pktMsg, end, q, mDNSNULL); // get available answers + info->deriveRemovesOnResume = mDNStrue; + case LLQErr_Static: + info->state = LLQ_Static; + LogMsg("LLQ %##s: static", q->qname.c); + simpleResponseHndlr(m, pktMsg, end, q, mDNSNULL); + return; + case LLQErr_FormErr: + LogMsg("ERROR: hndlRequestChallenge - received FormErr from server for LLQ %##s", q->qname.c); + goto error; + case LLQErr_BadVers: + LogMsg("ERROR: hndlRequestChallenge - received BadVers from server"); + goto error; + case LLQErr_UnknownErr: + LogMsg("ERROR: hndlRequestChallenge - received UnknownErr from server for LLQ %##s", q->qname.c); + goto error; + default: + LogMsg("ERROR: hndlRequestChallenge - received invalid error %d for LLQ %##s", llq->err, q->qname.c); + goto error; + } + + if (info->origLease != llq->lease) + debugf("hndlRequestChallenge: requested lease %lu, granted lease %lu", info->origLease, llq->lease); + + // cache expiration in case we go to sleep before finishing setup + info->origLease = llq->lease; + info->expire = timenow + ((mDNSs32)llq->lease * mDNSPlatformOneSecond); + + // update state + info->state = LLQ_SecondaryRequest; + umemcpy(info->id, llq->id, 8); + info->ntries = 0; // first attempt to send response + + sendChallengeResponse(m, q, llq); + return; + + + error: + info->state = LLQ_Error; + } + + +// response handler for initial and secondary setup responses +mDNSlocal void recvSetupResponse(mDNS *m, DNSMessage *pktMsg, const mDNSu8 *end, DNSQuestion *q, void *clientContext) + { + DNSQuestion pktQuestion; + LLQOptData llq; + const mDNSu8 *ptr = pktMsg->data; + LLQ_Info *info = q->uDNS_info.llq; + mDNSu8 rcode = (mDNSu8)(pktMsg->h.flags.b[1] & kDNSFlag1_RC); + + (void)clientContext; // unused + + if (rcode && rcode != kDNSFlag1_RC_NXDomain) goto poll; + + ptr = getQuestion(pktMsg, ptr, end, 0, &pktQuestion); + if (!ptr) { LogMsg("ERROR: recvSetupResponse - getQuestion"); goto poll; } + if (!SameDomainName(&q->qname, &pktQuestion.qname)) + { LogMsg("ERROR: recvSetupResponse - mismatched question in response for llq setup %##s", q->qname.c); goto poll; } + + if (!getLLQAtIndex(m, pktMsg, end, &llq, 0)) { debugf("recvSetupResponse - GetLLQAtIndex"); goto poll; } + if (llq.llqOp != kLLQOp_Setup) { LogMsg("ERROR: recvSetupResponse - bad op %d", llq.llqOp); goto poll; } + if (llq.vers != kLLQ_Vers) { LogMsg("ERROR: recvSetupResponse - bad vers %d", llq.vers); goto poll; } + + if (info->state == LLQ_InitialRequest) { hndlRequestChallenge(m, pktMsg, end, &llq, q); return; } + if (info->state == LLQ_SecondaryRequest) { hndlChallengeResponseAck(m, pktMsg, end, &llq, q); return; } + LogMsg("recvSetupResponse - bad state %d", info->state); + + poll: + info->state = LLQ_Poll; + q->uDNS_info.responseCallback = llqResponseHndlr; + info->question->LastQTime = mDNSPlatformTimeNow(m) - (2 * INIT_UCAST_POLL_INTERVAL); // trigger immediate poll + info->question->ThisQInterval = INIT_UCAST_POLL_INTERVAL; + } + +mDNSlocal void startLLQHandshake(mDNS *m, LLQ_Info *info, mDNSBool defer) + { + DNSMessage msg; + mDNSu8 *end; + LLQOptData llqData; + DNSQuestion *q = info->question; + mStatus err = mStatus_NoError; + mDNSs32 timenow = mDNSPlatformTimeNow(m); + uDNS_GlobalInfo *u = &m->uDNS_info; + + if (IsPrivateV4Addr(&u->AdvertisedV4)) + { + if (!u->LLQNatInfo) + { + info->state = LLQ_NatMapWait; + StartLLQNatMap(m); + return; + } + if (u->LLQNatInfo->state == NATState_Error) goto poll; + if (u->LLQNatInfo->state != NATState_Established && u->LLQNatInfo->state != NATState_Legacy) + { info->state = LLQ_NatMapWait; info->NATMap = mDNStrue; return; } + info->NATMap = mDNStrue; // this llq references the global llq nat mapping + } + + if (info->ntries++ >= kLLQ_MAX_TRIES) + { + debugf("startLLQHandshake: %d failed attempts for LLQ %##s. Polling.", kLLQ_MAX_TRIES, q->qname.c, kLLQ_DEF_RETRY / 60); + goto poll; + } + + // set llq rdata + llqData.vers = kLLQ_Vers; + llqData.llqOp = kLLQOp_Setup; + llqData.err = LLQErr_NoError; + ubzero(llqData.id, 8); + llqData.lease = kLLQ_DefLease; + + initializeQuery(&msg, q); + end = putLLQ(&msg, msg.data, q, &llqData, mDNStrue); + if (!end) + { + LogMsg("ERROR: startLLQHandshake - putLLQ"); + info->state = LLQ_Error; + return; + } + + if (!defer) // if we are to defer, we simply set the retry timers so the request goes out in the future + { + err = mDNSSendDNSMessage(m, &msg, end, mDNSInterface_Any, &info->servAddr, info->servPort, -1, mDNSNULL); + if (err) debugf("ERROR: startLLQHandshake - mDNSSendDNSMessage returned %ld", err); + // on error, we procede as normal and retry after the appropriate interval + } + + // update question/info state + info->state = LLQ_InitialRequest; + info->origLease = kLLQ_DefLease; + info->retry = timenow + (kLLQ_INIT_RESEND * mDNSPlatformOneSecond); + q->LastQTime = timenow; + q->uDNS_info.responseCallback = recvSetupResponse; + q->uDNS_info.internal = mDNStrue; + return; + + poll: + info->question->uDNS_info.responseCallback = llqResponseHndlr; + info->state = LLQ_Poll; + info->question->LastQTime = mDNSPlatformTimeNow(m) - (2 * INIT_UCAST_POLL_INTERVAL); // trigger immediate poll + info->question->ThisQInterval = INIT_UCAST_POLL_INTERVAL; + } + +// wrapper for startLLQHandshake, invoked by async op callback +mDNSlocal void startLLQHandshakeCallback(mStatus err, mDNS *const m, void *llqInfo, const AsyncOpResult *result) + { + LLQ_Info *info = (LLQ_Info *)llqInfo; + const zoneData_t *zoneInfo = mDNSNULL; + + // check state first to make sure it is OK to touch question object + if (info->state == LLQ_Cancelled) + { + // StopQuery was called while we were getting the zone info + debugf("startLLQHandshake - LLQ Cancelled."); + info->question = mDNSNULL; // question may be deallocated + ufree(info); + return; + } + + if (!info->question) + { LogMsg("ERROR: startLLQHandshakeCallback invoked with NULL question"); goto error; } + + if (info->state != LLQ_GetZoneInfo) + { LogMsg("ERROR: startLLQHandshake - bad state %d", info->state); goto error; } + + if (err) + { LogMsg("ERROR: startLLQHandshakeCallback %##s invoked with error code %ld", info->question->qname.c, err); goto poll; } + + if (!result) + { LogMsg("ERROR: startLLQHandshakeCallback invoked with NULL result and no error code"); goto error; } + + zoneInfo = &result->zoneData; + + if (!zoneInfo->llqPort.NotAnInteger) + { debugf("LLQ port lookup failed - reverting to polling"); info->servPort.NotAnInteger = 0; goto poll; } + + // cache necessary zone data + info->servAddr = zoneInfo->primaryAddr; + info->servPort = zoneInfo->llqPort; + info->ntries = 0; + + if (info->state == LLQ_SuspendDeferred) info->state = LLQ_Suspended; + else startLLQHandshake(m, info, mDNSfalse); + return; + + poll: + info->question->uDNS_info.responseCallback = llqResponseHndlr; + info->state = LLQ_Poll; + info->question->LastQTime = mDNSPlatformTimeNow(m) - (2 * INIT_UCAST_POLL_INTERVAL); // trigger immediate poll + info->question->ThisQInterval = INIT_UCAST_POLL_INTERVAL; + return; + + error: + info->state = LLQ_Error; + } + +mDNSlocal mStatus startLLQ(mDNS *m, DNSQuestion *question) + { + LLQ_Info *info; + mStatus err = mStatus_NoError; + + // allocate / init info struct + info = umalloc(sizeof(LLQ_Info)); + if (!info) { LogMsg("ERROR: startLLQ - malloc"); return mStatus_NoMemoryErr; } + ubzero(info, sizeof(LLQ_Info)); + info->state = LLQ_GetZoneInfo; + + // link info/question + info->question = question; + question->uDNS_info.llq = info; + + question->uDNS_info.responseCallback = llqResponseHndlr; + + err = startGetZoneData(&question->qname, m, mDNSfalse, mDNStrue, startLLQHandshakeCallback, info); + if (err) + { + LogMsg("ERROR: startLLQ - startGetZoneData returned %ld", err); + info->question = mDNSNULL; + ufree(info); + question->uDNS_info.llq = mDNSNULL; + return err; + } + + LinkActiveQuestion(&m->uDNS_info, question); + return err; + } + +mDNSlocal mDNSBool recvLLQResponse(mDNS *m, DNSMessage *msg, const mDNSu8 *end, const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSInterfaceID InterfaceID) + { + DNSQuestion pktQ, *q; + uDNS_GlobalInfo *u = &m->uDNS_info; + const mDNSu8 *ptr = msg->data; + LLQ_Info *llqInfo; + + if (!msg->h.numQuestions) return mDNSfalse; + + ptr = getQuestion(msg, ptr, end, 0, &pktQ); + if (!ptr) return mDNSfalse; + pktQ.uDNS_info.id = msg->h.id; + + q = u->ActiveQueries; + while (q) + { + llqInfo = q->uDNS_info.llq; + if (q->LongLived && + llqInfo && + q->qnamehash == pktQ.qnamehash && + q->qtype == pktQ.qtype && + SameDomainName(&q->qname, &pktQ.qname)) + { + u->CurrentQuery = q; + if (llqInfo->state == LLQ_Established || (llqInfo->state == LLQ_Refresh && msg->h.numAnswers)) + { if (recvLLQEvent(m, q, msg, end, srcaddr, srcport, InterfaceID)) return mDNStrue; } + else if (msg->h.id.NotAnInteger == q->uDNS_info.id.NotAnInteger) + { + if (llqInfo->state == LLQ_Refresh && msg->h.numAdditionals && !msg->h.numAnswers) + { recvRefreshReply(m, msg, end, q); return mDNStrue; } + if (llqInfo->state < LLQ_Static) + { + if ((llqInfo->state != LLQ_InitialRequest && llqInfo->state != LLQ_SecondaryRequest) || mDNSSameAddress(srcaddr, &llqInfo->servAddr)) + { q->uDNS_info.responseCallback(m, msg, end, q, q->uDNS_info.context); return mDNStrue; } + } + } + } + q = q->next; + } + return mDNSfalse; + } + +mDNSexport mDNSBool uDNS_IsActiveQuery(DNSQuestion *const question, uDNS_GlobalInfo *u) + { + DNSQuestion *q; + + for (q = u->ActiveQueries; q; q = q->next) + { + if (q == question) + { + if (!question->uDNS_info.id.NotAnInteger || question->InterfaceID == mDNSInterface_LocalOnly || IsLocalDomain(&question->qname)) + LogMsg("Warning: Question %##s in Active Unicast Query list with id %d, interfaceID %p", + question->qname.c, question->uDNS_info.id.NotAnInteger, question->InterfaceID); + return mDNStrue; + } + } + return mDNSfalse; + } + +// stopLLQ happens IN ADDITION to stopQuery +mDNSlocal void stopLLQ(mDNS *m, DNSQuestion *question) + { + LLQ_Info *info = question->uDNS_info.llq; + (void)m; // unused + + if (!question->LongLived) { LogMsg("ERROR: stopLLQ - LongLived flag not set"); return; } + if (!info) { LogMsg("ERROR: stopLLQ - llq info is NULL"); return; } + + switch (info->state) + { + case LLQ_UnInit: + LogMsg("ERROR: stopLLQ - state LLQ_UnInit"); + //!!!KRS should we unlink info<->question here? + return; + case LLQ_GetZoneInfo: + case LLQ_SuspendDeferred: + info->question = mDNSNULL; // remove ref to question, as it may be freed when we get called back from async op + info->state = LLQ_Cancelled; + return; + case LLQ_Established: + case LLQ_Refresh: + // refresh w/ lease 0 + sendLLQRefresh(m, question, 0); + goto end; + default: + debugf("stopLLQ - silently discarding LLQ in state %d", info->state); + goto end; + } + + end: + if (info->NATMap) info->NATMap = mDNSfalse; + CheckForUnreferencedLLQMapping(m); + info->question = mDNSNULL; + ufree(info); + question->uDNS_info.llq = mDNSNULL; + question->LongLived = mDNSfalse; + } + +mDNSexport mStatus uDNS_StopQuery(mDNS *const m, DNSQuestion *const question) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + DNSQuestion *qptr, *prev = mDNSNULL; + CacheRecord *ka; + + qptr = u->ActiveQueries; + while (qptr) + { + if (qptr == question) + { + if (question->LongLived && question->uDNS_info.llq) + stopLLQ(m, question); + if (m->uDNS_info.CurrentQuery == question) + m->uDNS_info.CurrentQuery = m->uDNS_info.CurrentQuery->next; + while (question->uDNS_info.knownAnswers) + { + ka = question->uDNS_info.knownAnswers; + question->uDNS_info.knownAnswers = question->uDNS_info.knownAnswers->next; + ufree(ka); + } + if (prev) prev->next = question->next; + else u->ActiveQueries = question->next; + return mStatus_NoError; + } + prev = qptr; + qptr = qptr->next; + } + LogMsg("uDNS_StopQuery: no such active query (%##s)", question->qname.c); + return mStatus_UnknownErr; + } + +mDNSlocal mStatus startQuery(mDNS *const m, DNSQuestion *const question, mDNSBool internal) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + //!!!KRS we should check if the question is already in our activequestion list + if (!ValidateDomainName(&question->qname)) + { + LogMsg("Attempt to start query with invalid qname %##s (%s)", question->qname.c, DNSTypeName(question->qtype)); + return mStatus_Invalid; + } + + question->next = mDNSNULL; + question->qnamehash = DomainNameHashValue(&question->qname); // to do quick domain name comparisons + question->uDNS_info.id = newMessageID(u); + question->uDNS_info.Answered = mDNSfalse; + + // break here if its and LLQ + if (question->LongLived) return startLLQ(m, question); + + question->ThisQInterval = INIT_UCAST_POLL_INTERVAL / 2; + question->LastQTime = mDNSPlatformTimeNow(m) - question->ThisQInterval; + // store the question/id in active question list + question->uDNS_info.internal = internal; + LinkActiveQuestion(u, question); + question->uDNS_info.knownAnswers = mDNSNULL; + LogOperation("uDNS startQuery: %##s (%s)", question->qname.c, DNSTypeName(question->qtype)); + + return mStatus_NoError; + } + +mDNSexport mStatus uDNS_StartQuery(mDNS *const m, DNSQuestion *const question) + { + ubzero(&question->uDNS_info, sizeof(uDNS_QuestionInfo)); + question->uDNS_info.responseCallback = simpleResponseHndlr; + question->uDNS_info.context = mDNSNULL; + //LogOperation("uDNS_StartQuery %##s (%s)", question->qname.c, DNSTypeName(question->qtype)); + return startQuery(m, question, 0); + } + +// explicitly set response handler +mDNSlocal mStatus startInternalQuery(DNSQuestion *q, mDNS *m, InternalResponseHndlr callback, void *hndlrContext) + { + ubzero(&q->uDNS_info, sizeof(uDNS_QuestionInfo)); + q->QuestionContext = hndlrContext; + q->uDNS_info.responseCallback = callback; + q->uDNS_info.context = hndlrContext; + return startQuery(m, q, 1); + } + + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - Domain -> Name Server Conversion +#endif + + +/* startGetZoneData + * + * Asynchronously find the address of the nameserver for the enclosing zone for a given domain name, + * i.e. the server to which update and LLQ requests will be sent for a given name. Once the address is + * derived, it will be passed to the callback, along with a context pointer. If the zone cannot + * be determined or if an error occurs, an all-zeros address will be passed and a message will be + * written to the syslog. + * + * If the FindUpdatePort arg is set, the port on which the server accepts dynamic updates is determined + * by querying for the _dns-update._udp.. SRV record. Likewise, if the FindLLQPort arg is set, + * the port on which the server accepts long lived queries is determined by querying for + * _dns-llq._udp.. record. If either of these queries fail, or flags are not specified, + * the llqPort and updatePort fields in the result structure are set to zero. + * + * Steps for deriving the zone name are as follows: + * + * Query for an SOA record for the required domain. If we don't get an answer (or an SOA in the Authority + * section), we strip the leading label from the name and repeat, until we get an answer. + * + * The name of the SOA record is our enclosing zone. The mname field in the SOA rdata is the domain + * name of the primary NS. + * + * We verify that there is an NS record with this zone for a name and the mname for its rdata. + * (!!!KRS this seems redundant, but BIND does this, and it should normally be zero-overhead since + * the NS query will get us address records in the additionals section, which we'd otherwise have to + * explicitly query for.) + * + * We then query for the address record for this nameserver (if it is not in the addionals section of + * the NS record response.) + */ + + +// state machine types and structs +// + +// state machine states +typedef enum + { + init, + lookupSOA, + foundZone, + lookupNS, + foundNS, + lookupA, + foundA, + lookupPort, + foundPort, + complete + } ntaState; + +// state machine actions +typedef enum + { + smContinue, // continue immediately to next state + smBreak, // break until next packet/timeout + smError // terminal error - cleanup and abort + } smAction; + +typedef struct + { + domainname origName; // name we originally try to convert + domainname *curSOA; // name we have an outstanding SOA query for + ntaState state; // determines what we do upon receiving a packet + mDNS *m; + domainname zone; // left-hand-side of SOA record + mDNSu16 zoneClass; + domainname ns; // mname in SOA rdata, verified in confirmNS state + mDNSv4Addr addr; // address of nameserver + DNSQuestion question; // storage for any active question + DNSQuestion extraQuestion; // additional storage + mDNSBool questionActive; // if true, StopQuery() can be called on the question field + mDNSBool findUpdatePort; + mDNSBool findLLQPort; + mDNSIPPort updatePort; + mDNSIPPort llqPort; + AsyncOpCallback *callback; // caller specified function to be called upon completion + void *callbackInfo; + } ntaContext; + + +// function prototypes (for routines that must be used as fn pointers prior to their definitions, +// and allows states to be read top-to-bottom in logical order) +mDNSlocal void getZoneData(mDNS *const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, void *contextPtr); +mDNSlocal smAction hndlLookupSOA(DNSMessage *msg, const mDNSu8 *end, ntaContext *context); +mDNSlocal void processSOA(ntaContext *context, ResourceRecord *rr); +mDNSlocal smAction confirmNS(DNSMessage *msg, const mDNSu8 *end, ntaContext *context); +mDNSlocal smAction lookupNSAddr(DNSMessage *msg, const mDNSu8 *end, ntaContext *context); +mDNSlocal smAction hndlLookupPorts(DNSMessage *msg, const mDNSu8 *end, ntaContext *context); + +// initialization +mDNSlocal mStatus startGetZoneData(domainname *name, mDNS *m, mDNSBool findUpdatePort, mDNSBool findLLQPort, + AsyncOpCallback callback, void *callbackInfo) + { + ntaContext *context = (ntaContext*)umalloc(sizeof(ntaContext)); + if (!context) { LogMsg("ERROR: startGetZoneData - umalloc failed"); return mStatus_NoMemoryErr; } + ubzero(context, sizeof(ntaContext)); + AssignDomainName(&context->origName, name); + context->state = init; + context->m = m; + context->callback = callback; + context->callbackInfo = callbackInfo; + context->findUpdatePort = findUpdatePort; + context->findLLQPort = findLLQPort; + getZoneData(m, mDNSNULL, mDNSNULL, mDNSNULL, context); + return mStatus_NoError; + } + +// state machine entry routine +mDNSlocal void getZoneData(mDNS *const m, DNSMessage *msg, const mDNSu8 *end, DNSQuestion *question, void *contextPtr) + { + AsyncOpResult result; + ntaContext *context = (ntaContext*)contextPtr; + smAction action; + + // unused + (void)m; + (void)question; + + // stop any active question + if (context->questionActive) + { + uDNS_StopQuery(context->m, &context->question); + context->questionActive = mDNSfalse; + } + + if (msg && msg->h.flags.b[2] >> 4 && msg->h.flags.b[2] >> 4 != kDNSFlag1_RC_NXDomain) + { + // rcode non-zero, non-nxdomain + LogMsg("ERROR: getZoneData - received response w/ rcode %d", msg->h.flags.b[2] >> 4); + goto error; + } + + switch (context->state) + { + case init: + case lookupSOA: + action = hndlLookupSOA(msg, end, context); + if (action == smError) goto error; + if (action == smBreak) return; + case foundZone: + case lookupNS: + action = confirmNS(msg, end, context); + if (action == smError) goto error; + if (action == smBreak) return; + case foundNS: + case lookupA: + action = lookupNSAddr(msg, end, context); + if (action == smError) goto error; + if (action == smBreak) return; + case foundA: + if (!context->findUpdatePort && !context->findLLQPort) + { + context->state = complete; + break; + } + case lookupPort: + action = hndlLookupPorts(msg, end, context); + if (action == smError) goto error; + if (action == smBreak) return; + if (action == smContinue) context->state = complete; + case foundPort: + case complete: break; + } + + if (context->state != complete) + { + LogMsg("ERROR: getZoneData - exited state machine with state %d", context->state); + goto error; + } + + result.type = zoneDataResult; + result.zoneData.primaryAddr.ip.v4 = context->addr; + result.zoneData.primaryAddr.type = mDNSAddrType_IPv4; + AssignDomainName(&result.zoneData.zoneName, &context->zone); + result.zoneData.zoneClass = context->zoneClass; + result.zoneData.llqPort = context->findLLQPort ? context->llqPort : zeroIPPort; + result.zoneData.updatePort = context->findUpdatePort ? context->updatePort : zeroIPPort; + context->callback(mStatus_NoError, context->m, context->callbackInfo, &result); + goto cleanup; + +error: + if (context && context->callback) + context->callback(mStatus_UnknownErr, context->m, context->callbackInfo, mDNSNULL); +cleanup: + if (context && context->questionActive) + { + uDNS_StopQuery(context->m, &context->question); + context->questionActive = mDNSfalse; + } + if (context) ufree(context); + } + +mDNSlocal smAction hndlLookupSOA(DNSMessage *msg, const mDNSu8 *end, ntaContext *context) + { + mStatus err; + LargeCacheRecord lcr; + ResourceRecord *rr = &lcr.r.resrec; + DNSQuestion *query = &context->question; + const mDNSu8 *ptr; + + if (msg) + { + // if msg contains SOA record in answer or authority sections, update context/state and return + int i; + ptr = LocateAnswers(msg, end); + for (i = 0; i < msg->h.numAnswers; i++) + { + ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (!ptr) { LogMsg("ERROR: hndlLookupSOA, Answers - GetLargeResourceRecord returned NULL"); return smError; } + if (rr->rrtype == kDNSType_SOA && SameDomainName(context->curSOA, rr->name)) + { + processSOA(context, rr); + return smContinue; + } + } + ptr = LocateAuthorities(msg, end); + // SOA not in answers, check in authority + for (i = 0; i < msg->h.numAuthorities; i++) + { + ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); ///!!!KRS using type PacketAns for auth + if (!ptr) { LogMsg("ERROR: hndlLookupSOA, Authority - GetLargeResourceRecord returned NULL"); return smError; } + if (rr->rrtype == kDNSType_SOA) + { + processSOA(context, rr); + return smContinue; + } + } + } + + if (context->state != init && !context->curSOA->c[0]) + { + // we've gone down to the root and have not found an SOA + LogMsg("ERROR: hndlLookupSOA - recursed to root label of %##s without finding SOA", + context->origName.c); + return smError; + } + + ubzero(query, sizeof(DNSQuestion)); + // chop off leading label unless this is our first try + if (context->state == init) context->curSOA = &context->origName; + else context->curSOA = (domainname *)(context->curSOA->c + context->curSOA->c[0]+1); + + context->state = lookupSOA; + AssignDomainName(&query->qname, context->curSOA); + query->qtype = kDNSType_SOA; + query->qclass = kDNSClass_IN; + err = startInternalQuery(query, context->m, getZoneData, context); + context->questionActive = mDNStrue; + if (err) LogMsg("hndlLookupSOA: startInternalQuery returned error %ld (breaking until next periodic retransmission)", err); + + return smBreak; // break from state machine until we receive another packet + } + +mDNSlocal void processSOA(ntaContext *context, ResourceRecord *rr) + { + AssignDomainName(&context->zone, rr->name); + context->zoneClass = rr->rrclass; + AssignDomainName(&context->ns, &rr->rdata->u.soa.mname); + context->state = foundZone; + } + + +mDNSlocal smAction confirmNS(DNSMessage *msg, const mDNSu8 *end, ntaContext *context) + { + DNSQuestion *query = &context->question; + mStatus err; + LargeCacheRecord lcr; + const ResourceRecord *const rr = &lcr.r.resrec; + const mDNSu8 *ptr; + int i; + + if (context->state == foundZone) + { + // we've just learned the zone. confirm that an NS record exists + AssignDomainName(&query->qname, &context->zone); + query->qtype = kDNSType_NS; + query->qclass = kDNSClass_IN; + err = startInternalQuery(query, context->m, getZoneData, context); + context->questionActive = mDNStrue; + if (err) LogMsg("confirmNS: startInternalQuery returned error %ld (breaking until next periodic retransmission", err); + context->state = lookupNS; + return smBreak; // break from SM until we receive another packet + } + else if (context->state == lookupNS) + { + ptr = LocateAnswers(msg, end); + for (i = 0; i < msg->h.numAnswers; i++) + { + ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (!ptr) { LogMsg("ERROR: confirmNS, Answers - GetLargeResourceRecord returned NULL"); return smError; } + if (rr->rrtype == kDNSType_NS && + SameDomainName(&context->zone, rr->name) && SameDomainName(&context->ns, &rr->rdata->u.name)) + { + context->state = foundNS; + return smContinue; // next routine will examine additionals section of A record + } + } + debugf("ERROR: could not confirm existence of record %##s NS %##s", context->zone.c, context->ns.c); + return smError; + } + else { LogMsg("ERROR: confirmNS - bad state %d", context->state); return smError; } + } + +mDNSlocal smAction queryNSAddr(ntaContext *context) + { + mStatus err; + DNSQuestion *query = &context->question; + + AssignDomainName(&query->qname, &context->ns); + query->qtype = kDNSType_A; + query->qclass = kDNSClass_IN; + err = startInternalQuery(query, context->m, getZoneData, context); + context->questionActive = mDNStrue; + if (err) LogMsg("confirmNS: startInternalQuery returned error %ld (breaking until next periodic retransmission)", err); + context->state = lookupA; + return smBreak; + } + +mDNSlocal smAction lookupNSAddr(DNSMessage *msg, const mDNSu8 *end, ntaContext *context) + { + const mDNSu8 *ptr; + int i; + LargeCacheRecord lcr; + ResourceRecord *rr = &lcr.r.resrec; + + if (context->state == foundNS) + { + // we just found the NS record - look for the corresponding A record in the Additionals section + if (!msg->h.numAdditionals) return queryNSAddr(context); + ptr = LocateAdditionals(msg, end); + if (!ptr) + { + LogMsg("ERROR: lookupNSAddr - LocateAdditionals returned NULL, expected %d additionals", msg->h.numAdditionals); + return queryNSAddr(context); + } + else + { + for (i = 0; i < msg->h.numAdditionals; i++) + { + ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (!ptr) + { + LogMsg("ERROR: lookupNSAddr, Additionals - GetLargeResourceRecord returned NULL"); + return queryNSAddr(context); + } + if (rr->rrtype == kDNSType_A && SameDomainName(&context->ns, rr->name)) + { + context->addr = rr->rdata->u.ipv4; + context->state = foundA; + return smContinue; + } + } + } + // no A record in Additionals - query the server + return queryNSAddr(context); + } + else if (context->state == lookupA) + { + ptr = LocateAnswers(msg, end); + if (!ptr) { LogMsg("ERROR: lookupNSAddr: LocateAnswers returned NULL"); return smError; } + for (i = 0; i < msg->h.numAnswers; i++) + { + ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (!ptr) { LogMsg("ERROR: lookupNSAddr, Answers - GetLargeResourceRecord returned NULL"); break; } + if (rr->rrtype == kDNSType_A && SameDomainName(&context->ns, rr->name)) + { + context->addr = rr->rdata->u.ipv4; + context->state = foundA; + return smContinue; + } + } + LogMsg("ERROR: lookupNSAddr: Address record not found in answer section"); + return smError; + } + else { LogMsg("ERROR: lookupNSAddr - bad state %d", context->state); return smError; } + } + +mDNSlocal smAction lookupDNSPort(DNSMessage *msg, const mDNSu8 *end, ntaContext *context, char *portName, mDNSIPPort *port) + { + int i; + LargeCacheRecord lcr; + const mDNSu8 *ptr; + DNSQuestion *q; + mStatus err; + + if (context->state == lookupPort) // we've already issued the query + { + if (!msg) { LogMsg("ERROR: hndlLookupUpdatePort - NULL message"); return smError; } + ptr = LocateAnswers(msg, end); + for (i = 0; i < msg->h.numAnswers; i++) + { + ptr = GetLargeResourceRecord(context->m, msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (!ptr) { LogMsg("ERROR: hndlLookupUpdatePort - GetLargeResourceRecord returned NULL"); return smError; } + if (ResourceRecordAnswersQuestion(&lcr.r.resrec, &context->question)) + { + *port = lcr.r.resrec.rdata->u.srv.port; + context->state = foundPort; + return smContinue; + } + } + debugf("hndlLookupUpdatePort - no answer for type %s", portName); + port->NotAnInteger = 0; + context->state = foundPort; + return smContinue; + } + + // query the server for the update port for the zone + context->state = lookupPort; + q = &context->question; + MakeDomainNameFromDNSNameString(&q->qname, portName); + AppendDomainName(&q->qname, &context->zone); + q->qtype = kDNSType_SRV; + q->qclass = kDNSClass_IN; + err = startInternalQuery(q, context->m, getZoneData, context); + context->questionActive = mDNStrue; + if (err) LogMsg("hndlLookupSOA: startInternalQuery returned error %ld (breaking until next periodic retransmission)", err); + return smBreak; // break from state machine until we receive another packet + } + +mDNSlocal smAction hndlLookupPorts(DNSMessage *msg, const mDNSu8 *end, ntaContext *context) + { + smAction action; + + if (context->findUpdatePort && !context->updatePort.NotAnInteger) + { + action = lookupDNSPort(msg, end, context, UPDATE_PORT_NAME, &context->updatePort); + if (action != smContinue) return action; + } + if (context->findLLQPort && !context->llqPort.NotAnInteger) + return lookupDNSPort(msg, end, context, LLQ_PORT_NAME, &context->llqPort); + + return smContinue; + } + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - Truncation Handling +#endif + +typedef struct + { + DNSQuestion *question; + DNSMessage *reply; + mDNSu16 replylen; + int nread; + mDNS *m; + } tcpInfo_t; + +// issue queries over a conected socket +mDNSlocal void conQueryCallback(int sd, void *context, mDNSBool ConnectionEstablished) + { + mStatus err = 0; + char msgbuf[356]; // 96 (hdr) + 256 (domain) + 4 (class/type) + DNSMessage *msg; + mDNSu8 *end; + tcpInfo_t *info = (tcpInfo_t *)context; + DNSQuestion *question = info->question; + int n; + mDNS *m = info->m; + + mDNS_Lock(m); + + if (ConnectionEstablished) + { + // connection is established - send the message + msg = (DNSMessage *)&msgbuf; + err = constructQueryMsg(msg, &end, question); + if (err) { LogMsg("ERROR: conQueryCallback: constructQueryMsg - %ld", err); goto error; } + err = mDNSSendDNSMessage(m, msg, end, mDNSInterface_Any, &zeroAddr, zeroIPPort, sd, mDNSNULL); + question->LastQTime = mDNSPlatformTimeNow(m); + if (err) { debugf("ERROR: conQueryCallback: mDNSSendDNSMessage_tcp - %ld", err); goto error; } + } + else + { + if (!info->nread) + { + // read msg len + mDNSu8 lenbuf[2]; + n = mDNSPlatformReadTCP(sd, lenbuf, 2); + if (n != 2) + { + LogMsg("ERROR:conQueryCallback - attempt to read message length failed (read returned %d)", n); + goto error; + } + info->replylen = (mDNSu16)((mDNSu16)lenbuf[0] << 8 | lenbuf[1]); + if (info->replylen < sizeof(DNSMessageHeader)) + { LogMsg("ERROR: conQueryCallback - length too short (%d bytes)", info->replylen); goto error; } + info->reply = umalloc(info->replylen); + if (!info->reply) { LogMsg("ERROR: conQueryCallback - malloc failed"); goto error; } + } + n = mDNSPlatformReadTCP(sd, ((char *)info->reply) + info->nread, info->replylen - info->nread); + if (n < 0) { LogMsg("ERROR: conQueryCallback - read returned %d", n); goto error; } + info->nread += n; + if (info->nread == info->replylen) + { + // Finished reading message; convert the integer parts which are in IETF byte-order (MSB first, LSB second) + DNSMessage *msg = info->reply; + 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]); + uDNS_ReceiveMsg(m, msg, (mDNSu8 *)msg + info->replylen, mDNSNULL, zeroIPPort, mDNSNULL, zeroIPPort, question->InterfaceID); + mDNSPlatformTCPCloseConnection(sd); + ufree(info->reply); + ufree(info); + } + } + + mDNS_Unlock(m); + return; + + error: + mDNSPlatformTCPCloseConnection(sd); + if (info->reply) ufree(info->reply); + ufree(info); + mDNS_Unlock(m); + } + +mDNSlocal void hndlTruncatedAnswer(DNSQuestion *question, const mDNSAddr *src, mDNS *m) + { + mStatus connectionStatus; + uDNS_QuestionInfo *info = &question->uDNS_info; + int sd; + tcpInfo_t *context; + + if (!src) { LogMsg("hndlTruncatedAnswer: TCP DNS response had TC bit set: ignoring"); return; } + + context = (tcpInfo_t *)umalloc(sizeof(tcpInfo_t)); + if (!context) { LogMsg("ERROR: hndlTruncatedAnswer - memallocate failed"); return; } + ubzero(context, sizeof(tcpInfo_t)); + context->question = question; + context->m = m; + info->id = newMessageID(&m->uDNS_info); + + connectionStatus = mDNSPlatformTCPConnect(src, UnicastDNSPort, question->InterfaceID, conQueryCallback, context, &sd); + if (connectionStatus == mStatus_ConnEstablished) // manually invoke callback if connection completes + { + conQueryCallback(sd, context, mDNStrue); + return; + } + if (connectionStatus == mStatus_ConnPending) return; // callback will be automatically invoked when connection completes + LogMsg("hndlTruncatedAnswer: connection failed"); + uDNS_StopQuery(m, question); //!!!KRS can we really call this here? + } + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - Dynamic Updates +#endif + +mDNSlocal void sendRecordRegistration(mDNS *const m, AuthRecord *rr) + { + DNSMessage msg; + mDNSu8 *ptr = msg.data; + mDNSu8 *end = (mDNSu8 *)&msg + sizeof(DNSMessage); + uDNS_GlobalInfo *u = &m->uDNS_info; + mDNSOpaque16 id; + uDNS_RegInfo *regInfo = &rr->uDNS_info; + mStatus err = mStatus_UnknownErr; + + id = newMessageID(u); + InitializeDNSMessage(&msg.h, id, UpdateReqFlags); + rr->uDNS_info.id = id; + + // set zone + ptr = putZone(&msg, ptr, end, ®Info->zone, mDNSOpaque16fromIntVal(rr->resrec.rrclass)); + if (!ptr) goto error; + + if (regInfo->state == regState_UpdatePending) + { + // delete old RData + SetNewRData(&rr->resrec, regInfo->OrigRData, regInfo->OrigRDLen); + if (!(ptr = putDeletionRecord(&msg, ptr, &rr->resrec))) goto error; // delete old rdata + + // add new RData + SetNewRData(&rr->resrec, regInfo->InFlightRData, regInfo->InFlightRDLen); + if (!(ptr = PutResourceRecordTTLJumbo(&msg, ptr, &msg.h.mDNS_numUpdates, &rr->resrec, rr->resrec.rroriginalttl))) goto error; + } + + else + { + if (rr->resrec.RecordType == kDNSRecordTypeKnownUnique) + { + // KnownUnique: Delete any previous value + ptr = putDeleteRRSet(&msg, ptr, rr->resrec.name, rr->resrec.rrtype); + if (!ptr) goto error; + } + + else if (rr->resrec.RecordType != kDNSRecordTypeShared) + { + ptr = putPrereqNameNotInUse(rr->resrec.name, &msg, ptr, end); + if (!ptr) goto error; + } + + ptr = PutResourceRecordTTLJumbo(&msg, ptr, &msg.h.mDNS_numUpdates, &rr->resrec, rr->resrec.rroriginalttl); + if (!ptr) goto error; + } + + if (rr->uDNS_info.lease) + { ptr = putUpdateLease(&msg, ptr, DEFAULT_UPDATE_LEASE); if (!ptr) goto error; } + + err = mDNSSendDNSMessage(m, &msg, ptr, mDNSInterface_Any, ®Info->ns, regInfo->port, -1, GetAuthInfoForName(u, rr->resrec.name)); + if (err) debugf("ERROR: sendRecordRegistration - mDNSSendDNSMessage - %ld", err); + + SetRecordRetry(m, rr, err); + + if (regInfo->state != regState_Refresh && regInfo->state != regState_DeregDeferred && regInfo->state != regState_UpdatePending) + regInfo->state = regState_Pending; + + return; + +error: + LogMsg("sendRecordRegistration: Error formatting message"); + if (rr->uDNS_info.state != regState_Unregistered) + { + unlinkAR(&u->RecordRegistrations, rr); + rr->uDNS_info.state = regState_Unregistered; + } + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + if (rr->RecordCallback) rr->RecordCallback(m, rr, err); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + // NOTE: not safe to touch any client structures here + } + +mDNSlocal void RecordRegistrationCallback(mStatus err, mDNS *const m, void *authPtr, const AsyncOpResult *result) + { + AuthRecord *newRR = (AuthRecord*)authPtr; + const zoneData_t *zoneData = mDNSNULL; + uDNS_GlobalInfo *u = &m->uDNS_info; + AuthRecord *ptr; + + // make sure record is still in list + for (ptr = u->RecordRegistrations; ptr; ptr = ptr->next) + if (ptr == newRR) break; + if (!ptr) { LogMsg("RecordRegistrationCallback - RR no longer in list. Discarding."); return; } + + // check error/result + if (err) { LogMsg("RecordRegistrationCallback: error %ld", err); goto error; } + if (!result) { LogMsg("ERROR: RecordRegistrationCallback invoked with NULL result and no error"); goto error; } + else zoneData = &result->zoneData; + + if (newRR->uDNS_info.state == regState_Cancelled) + { + //!!!KRS we should send a memfree callback here! + debugf("Registration of %##s type %d cancelled prior to update", + newRR->resrec.name->c, newRR->resrec.rrtype); + newRR->uDNS_info.state = regState_Unregistered; + unlinkAR(&u->RecordRegistrations, newRR); + return; + } + + if (result->type != zoneDataResult) + { + LogMsg("ERROR: buildUpdatePacket passed incorrect result type %d", result->type); + goto error; + } + + if (newRR->resrec.rrclass != zoneData->zoneClass) + { + LogMsg("ERROR: New resource record's class (%d) does not match zone class (%d)", + newRR->resrec.rrclass, zoneData->zoneClass); + goto error; + } + + // Don't try to do updates to the root name server. + // We might be tempted also to block updates to any single-label name server (e.g. com, edu, net, etc.) but some + // organizations use their own private pseudo-TLD, like ".home", etc, and we don't want to block that. + if (zoneData->zoneName.c[0] == 0) + { + LogMsg("ERROR: Only name server claiming responsibility for \"%##s\" is \"%##s\"!", + newRR->resrec.name->c, zoneData->zoneName.c); + err = mStatus_NoSuchNameErr; + goto error; + } + + // cache zone data + AssignDomainName(&newRR->uDNS_info.zone, &zoneData->zoneName); + newRR->uDNS_info.ns = zoneData->primaryAddr; + if (zoneData->updatePort.NotAnInteger) newRR->uDNS_info.port = zoneData->updatePort; + else + { + debugf("Update port not advertised via SRV - guessing port 53, no lease option"); + newRR->uDNS_info.port = UnicastDNSPort; + newRR->uDNS_info.lease = mDNSfalse; + } + + sendRecordRegistration(m, newRR); + return; + +error: + if (newRR->uDNS_info.state != regState_Unregistered) + { + unlinkAR(&u->RecordRegistrations, newRR); + newRR->uDNS_info.state = regState_Unregistered; + } + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + if (newRR->RecordCallback) + newRR->RecordCallback(m, newRR, err); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + // NOTE: not safe to touch any client structures here + } + +mDNSlocal void SendServiceRegistration(mDNS *m, ServiceRecordSet *srs) + { + DNSMessage msg; + mDNSu8 *ptr = msg.data; + mDNSu8 *end = (mDNSu8 *)&msg + sizeof(DNSMessage); + uDNS_GlobalInfo *u = &m->uDNS_info; + mDNSOpaque16 id; + uDNS_RegInfo *rInfo = &srs->uDNS_info; + mStatus err = mStatus_UnknownErr; + mDNSIPPort privport; + NATTraversalInfo *nat = srs->uDNS_info.NATinfo; + mDNSBool mapped = mDNSfalse; + domainname target; + AuthRecord *srv = &srs->RR_SRV; + mDNSu32 i; + + privport = zeroIPPort; + + if (!rInfo->ns.ip.v4.NotAnInteger) { LogMsg("SendServiceRegistration - NS not set!"); return; } + + id = newMessageID(u); + InitializeDNSMessage(&msg.h, id, UpdateReqFlags); + + // setup resource records + SetNewRData(&srs->RR_PTR.resrec, mDNSNULL, 0); + SetNewRData(&srs->RR_TXT.resrec, mDNSNULL, 0); + + // replace port w/ NAT mapping if necessary + if (nat && nat->PublicPort.NotAnInteger && + (nat->state == NATState_Established || nat->state == NATState_Refresh || nat->state == NATState_Legacy)) + { + privport = srv->resrec.rdata->u.srv.port; + srv->resrec.rdata->u.srv.port = nat->PublicPort; + mapped = mDNStrue; + } + + // construct update packet + // set zone + ptr = putZone(&msg, ptr, end, &rInfo->zone, mDNSOpaque16fromIntVal(srv->resrec.rrclass)); + if (!ptr) goto error; + + if (srs->uDNS_info.TestForSelfConflict) + { + // update w/ prereq that SRV already exist to make sure previous registration was ours, and delete any stale TXT records + if (!(ptr = PutResourceRecordTTLJumbo(&msg, ptr, &msg.h.mDNS_numPrereqs, &srs->RR_SRV.resrec, 0))) goto error; + if (!(ptr = putDeleteRRSet(&msg, ptr, srs->RR_TXT.resrec.name, srs->RR_TXT.resrec.rrtype))) goto error; + } + + else if (srs->uDNS_info.state != regState_Refresh && srs->uDNS_info.state != regState_UpdatePending) + { + // use SRV name for prereq + ptr = putPrereqNameNotInUse(srv->resrec.name, &msg, ptr, end); + if (!ptr) goto error; + } + + //!!!KRS Need to do bounds checking and use TCP if it won't fit!!! + if (!(ptr = PutResourceRecordTTLJumbo(&msg, ptr, &msg.h.mDNS_numUpdates, &srs->RR_PTR.resrec, srs->RR_PTR.resrec.rroriginalttl))) goto error; + + for (i = 0; i < srs->NumSubTypes; i++) + if (!(ptr = PutResourceRecordTTLJumbo(&msg, ptr, &msg.h.mDNS_numUpdates, &srs->SubTypes[i].resrec, srs->SubTypes[i].resrec.rroriginalttl))) goto error; + + if (rInfo->state == regState_UpdatePending) // we're updating the txt record + { + AuthRecord *txt = &srs->RR_TXT; + uDNS_RegInfo *txtInfo = &txt->uDNS_info; + // delete old RData + SetNewRData(&txt->resrec, txtInfo->OrigRData, txtInfo->OrigRDLen); + if (!(ptr = putDeletionRecord(&msg, ptr, &srs->RR_TXT.resrec))) goto error; // delete old rdata + + // add new RData + SetNewRData(&txt->resrec, txtInfo->InFlightRData, txtInfo->InFlightRDLen); + if (!(ptr = PutResourceRecordTTLJumbo(&msg, ptr, &msg.h.mDNS_numUpdates, &srs->RR_TXT.resrec, srs->RR_TXT.resrec.rroriginalttl))) goto error; + } + else + if (!(ptr = PutResourceRecordTTLJumbo(&msg, ptr, &msg.h.mDNS_numUpdates, &srs->RR_TXT.resrec, srs->RR_TXT.resrec.rroriginalttl))) goto error; + + if (!GetServiceTarget(u, srv, &target)) + { + debugf("Couldn't get target for service %##s", srv->resrec.name->c); + rInfo->state = regState_NoTarget; + return; + } + + if (!SameDomainName(&target, &srv->resrec.rdata->u.srv.target)) + { + AssignDomainName(&srv->resrec.rdata->u.srv.target, &target); + SetNewRData(&srv->resrec, mDNSNULL, 0); + } + + ptr = PutResourceRecordTTLJumbo(&msg, ptr, &msg.h.mDNS_numUpdates, &srv->resrec, srv->resrec.rroriginalttl); + if (!ptr) goto error; + + if (srs->uDNS_info.lease) + { ptr = putUpdateLease(&msg, ptr, DEFAULT_UPDATE_LEASE); if (!ptr) goto error; } + + err = mDNSSendDNSMessage(m, &msg, ptr, mDNSInterface_Any, &rInfo->ns, rInfo->port, -1, GetAuthInfoForName(u, srs->RR_SRV.resrec.name)); + if (err) debugf("ERROR: SendServiceRegistration - mDNSSendDNSMessage - %ld", err); + + if (rInfo->state != regState_Refresh && rInfo->state != regState_DeregDeferred && srs->uDNS_info.state != regState_UpdatePending) + rInfo->state = regState_Pending; + + SetRecordRetry(m, &srs->RR_SRV, err); + rInfo->id = id; + if (mapped) srv->resrec.rdata->u.srv.port = privport; + return; + +error: + LogMsg("SendServiceRegistration - Error formatting message"); + if (mapped) srv->resrec.rdata->u.srv.port = privport; + unlinkSRS(m, srs); + rInfo->state = regState_Unregistered; + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + srs->ServiceCallback(m, srs, err); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + //!!!KRS will mem still be free'd on error? + // NOTE: not safe to touch any client structures here + } + +mDNSlocal void serviceRegistrationCallback(mStatus err, mDNS *const m, void *srsPtr, const AsyncOpResult *result) + { + ServiceRecordSet *srs = (ServiceRecordSet *)srsPtr; + const zoneData_t *zoneData = mDNSNULL; + + if (err) goto error; + if (!result) { LogMsg("ERROR: serviceRegistrationCallback invoked with NULL result and no error"); goto error; } + else zoneData = &result->zoneData; + + if (result->type != zoneDataResult) + { + LogMsg("ERROR: buildUpdatePacket passed incorrect result type %d", result->type); + goto error; + } + + if (srs->uDNS_info.state == regState_Cancelled) + { + // client cancelled registration while fetching zone data + srs->uDNS_info.state = regState_Unregistered; + unlinkSRS(m, srs); + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + srs->ServiceCallback(m, srs, mStatus_MemFree); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + return; + } + + if (srs->RR_SRV.resrec.rrclass != zoneData->zoneClass) + { + LogMsg("Service %##s - class does not match zone", srs->RR_SRV.resrec.name->c); + goto error; + } + + // cache zone data + AssignDomainName(&srs->uDNS_info.zone, &zoneData->zoneName); + srs->uDNS_info.ns.type = mDNSAddrType_IPv4; + srs->uDNS_info.ns = zoneData->primaryAddr; + if (zoneData->updatePort.NotAnInteger) srs->uDNS_info.port = zoneData->updatePort; + else + { + debugf("Update port not advertised via SRV - guessing port 53, no lease option"); + srs->uDNS_info.port = UnicastDNSPort; + srs->uDNS_info.lease = mDNSfalse; + } + + if (srs->RR_SRV.resrec.rdata->u.srv.port.NotAnInteger && IsPrivateV4Addr(&m->uDNS_info.AdvertisedV4)) + { srs->uDNS_info.state = regState_NATMap; StartNATPortMap(m, srs); } + else SendServiceRegistration(m, srs); + return; + +error: + unlinkSRS(m, srs); + srs->uDNS_info.state = regState_Unregistered; + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + srs->ServiceCallback(m, srs, err); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + // NOTE: not safe to touch any client structures here + } + +mDNSlocal mStatus SetupRecordRegistration(mDNS *m, AuthRecord *rr) + { + domainname *target = GetRRDomainNameTarget(&rr->resrec); + AuthRecord *ptr = m->uDNS_info.RecordRegistrations; + + while (ptr && ptr != rr) ptr = ptr->next; + if (ptr) { LogMsg("Error: SetupRecordRegistration - record %##s already in list!", rr->resrec.name->c); return mStatus_AlreadyRegistered; } + + if (rr->uDNS_info.state == regState_FetchingZoneData || + rr->uDNS_info.state == regState_Pending || + rr->uDNS_info.state == regState_Registered) + { + LogMsg("Requested double-registration of physical record %##s type %d", + rr->resrec.name->c, rr->resrec.rrtype); + return mStatus_AlreadyRegistered; + } + + rr->resrec.rdlength = GetRDLength(&rr->resrec, mDNSfalse); + rr->resrec.rdestimate = GetRDLength(&rr->resrec, mDNStrue); + + if (!ValidateDomainName(rr->resrec.name)) + { + LogMsg("Attempt to register record with invalid name: %s", ARDisplayString(m, rr)); + return mStatus_Invalid; + } + + // Don't do this until *after* we've set rr->resrec.rdlength + if (!ValidateRData(rr->resrec.rrtype, rr->resrec.rdlength, rr->resrec.rdata)) + { + LogMsg("Attempt to register record with invalid rdata: %s", ARDisplayString(m, rr)); + return mStatus_Invalid; + } + + rr->resrec.namehash = DomainNameHashValue(rr->resrec.name); + rr->resrec.rdatahash = target ? DomainNameHashValue(target) : RDataHashValue(rr->resrec.rdlength, &rr->resrec.rdata->u); + + rr->uDNS_info.state = regState_FetchingZoneData; + rr->next = m->uDNS_info.RecordRegistrations; + m->uDNS_info.RecordRegistrations = rr; + rr->uDNS_info.lease = mDNStrue; + + return mStatus_NoError; + } + +mDNSexport mStatus uDNS_RegisterRecord(mDNS *const m, AuthRecord *const rr) + { + mStatus err = SetupRecordRegistration(m, rr); + if (err) return err; + else return startGetZoneData(rr->resrec.name, m, mDNStrue, mDNSfalse, RecordRegistrationCallback, rr); + } + +mDNSlocal void SendRecordDeregistration(mDNS *m, AuthRecord *rr) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + DNSMessage msg; + mDNSu8 *ptr = msg.data; + mDNSu8 *end = (mDNSu8 *)&msg + sizeof(DNSMessage); + mStatus err; + + InitializeDNSMessage(&msg.h, rr->uDNS_info.id, UpdateReqFlags); + + ptr = putZone(&msg, ptr, end, &rr->uDNS_info.zone, mDNSOpaque16fromIntVal(rr->resrec.rrclass)); + if (!ptr) goto error; + if (!(ptr = putDeletionRecord(&msg, ptr, &rr->resrec))) goto error; + + err = mDNSSendDNSMessage(m, &msg, ptr, mDNSInterface_Any, &rr->uDNS_info.ns, rr->uDNS_info.port, -1, GetAuthInfoForName(u, rr->resrec.name)); + if (err) debugf("ERROR: SendRecordDeregistration - mDNSSendDNSMessage - %ld", err); + + SetRecordRetry(m, rr, err); + rr->uDNS_info.state = regState_DeregPending; + return; + + error: + LogMsg("Error: SendRecordDeregistration - could not contruct deregistration packet"); + unlinkAR(&u->RecordRegistrations, rr); + rr->uDNS_info.state = regState_Unregistered; + } + + + +mDNSexport mStatus uDNS_DeregisterRecord(mDNS *const m, AuthRecord *const rr) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + NATTraversalInfo *n = rr->uDNS_info.NATinfo; + + switch (rr->uDNS_info.state) + { + case regState_NATMap: + // we're in the middle of a NAT traversal operation + rr->uDNS_info.NATinfo = mDNSNULL; + if (!n) LogMsg("uDNS_DeregisterRecord: no NAT info context"); + else FreeNATInfo(m, n); // cause response to outstanding request to be ignored. + // Note: normally here we're trying to determine our public address, + //in which case there is not state to be torn down. For simplicity, + //we allow other operations to expire. + rr->uDNS_info.state = regState_Unregistered; + break; + case regState_ExtraQueued: + rr->uDNS_info.state = regState_Unregistered; + break; + case regState_FetchingZoneData: + rr->uDNS_info.state = regState_Cancelled; + return mStatus_NoError; + case regState_Refresh: + case regState_Pending: + case regState_UpdatePending: + rr->uDNS_info.state = regState_DeregDeferred; + LogMsg("Deferring deregistration of record %##s until registration completes", rr->resrec.name->c); + return mStatus_NoError; + case regState_Registered: + case regState_DeregPending: + break; + case regState_DeregDeferred: + case regState_Cancelled: + LogMsg("Double deregistration of record %##s type %d", + rr->resrec.name->c, rr->resrec.rrtype); + return mStatus_UnknownErr; + case regState_Unregistered: + LogMsg("Requested deregistration of unregistered record %##s type %d", + rr->resrec.name->c, rr->resrec.rrtype); + return mStatus_UnknownErr; + case regState_NATError: + case regState_NoTarget: + LogMsg("ERROR: uDNS_DeregisterRecord called for record %##s with bad state %s", rr->resrec.name->c, rr->uDNS_info.state == regState_NoTarget ? "regState_NoTarget" : "regState_NATError"); + return mStatus_UnknownErr; + } + + if (rr->uDNS_info.state == regState_Unregistered) + { + // unlink and deliver memfree + + unlinkAR(&u->RecordRegistrations, rr); + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + if (rr->RecordCallback) rr->RecordCallback(m, rr, mStatus_MemFree); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + return mStatus_NoError; + } + + rr->uDNS_info.NATinfo = mDNSNULL; + if (n) FreeNATInfo(m, n); + + SendRecordDeregistration(m, rr); + return mStatus_NoError; + } + +mDNSexport mStatus uDNS_RegisterService(mDNS *const m, ServiceRecordSet *srs) + { + mDNSu32 i; + domainname target; + uDNS_RegInfo *info = &srs->uDNS_info; + ServiceRecordSet **p = &m->uDNS_info.ServiceRegistrations; + while (*p && *p != srs) p=&(*p)->next; + if (*p) { LogMsg("uDNS_RegisterService: %p %##s already in list", srs, srs->RR_SRV.resrec.name->c); return(mStatus_AlreadyRegistered); } + ubzero(info, sizeof(*info)); + *p = srs; + srs->next = mDNSNULL; + + srs->RR_SRV.resrec.rroriginalttl = kWideAreaTTL; + srs->RR_TXT.resrec.rroriginalttl = kWideAreaTTL; + srs->RR_PTR.resrec.rroriginalttl = kWideAreaTTL; + for (i = 0; i < srs->NumSubTypes;i++) srs->SubTypes[i].resrec.rroriginalttl = kWideAreaTTL; + + info->lease = mDNStrue; + + srs->RR_SRV.resrec.rdata->u.srv.target.c[0] = 0; + if (!GetServiceTarget(&m->uDNS_info, &srs->RR_SRV, &target)) + { + // defer registration until we've got a target + debugf("uDNS_RegisterService - no target for %##s", srs->RR_SRV.resrec.name->c); + info->state = regState_NoTarget; + return mStatus_NoError; + } + + info->state = regState_FetchingZoneData; + return startGetZoneData(srs->RR_SRV.resrec.name, m, mDNStrue, mDNSfalse, serviceRegistrationCallback, srs); + } + +mDNSlocal void SendServiceDeregistration(mDNS *m, ServiceRecordSet *srs) + { + uDNS_RegInfo *info = &srs->uDNS_info; + uDNS_GlobalInfo *u = &m->uDNS_info; + DNSMessage msg; + mDNSOpaque16 id; + mDNSu8 *ptr = msg.data; + mDNSu8 *end = (mDNSu8 *)&msg + sizeof(DNSMessage); + mStatus err = mStatus_UnknownErr; + mDNSu32 i; + + id = newMessageID(u); + InitializeDNSMessage(&msg.h, id, UpdateReqFlags); + + // put zone + ptr = putZone(&msg, ptr, end, &info->zone, mDNSOpaque16fromIntVal(srs->RR_SRV.resrec.rrclass)); + if (!ptr) { LogMsg("ERROR: SendServiceDeregistration - putZone"); goto error; } + + if (!(ptr = putDeleteAllRRSets(&msg, ptr, srs->RR_SRV.resrec.name))) goto error; // this deletes SRV, TXT, and Extras + if (!(ptr = putDeletionRecord(&msg, ptr, &srs->RR_PTR.resrec))) goto error; + for (i = 0; i < srs->NumSubTypes; i++) + if (!(ptr = putDeletionRecord(&msg, ptr, &srs->SubTypes[i].resrec))) goto error; + + + err = mDNSSendDNSMessage(m, &msg, ptr, mDNSInterface_Any, &info->ns, info->port, -1, GetAuthInfoForName(u, srs->RR_SRV.resrec.name)); + if (err && err != mStatus_TransientErr) { debugf("ERROR: SendServiceDeregistration - mDNSSendDNSMessage - %ld", err); goto error; } + + SetRecordRetry(m, &srs->RR_SRV, err); + info->id = id; + info->state = regState_DeregPending; + + return; + + error: + unlinkSRS(m, srs); + info->state = regState_Unregistered; + } + +mDNSexport mStatus uDNS_DeregisterService(mDNS *const m, ServiceRecordSet *srs) + { + NATTraversalInfo *nat = srs->uDNS_info.NATinfo; + char *errmsg = "Unknown State"; + + // don't re-register with a new target following deregistration + srs->uDNS_info.SRVChanged = srs->uDNS_info.SRVUpdateDeferred = mDNSfalse; + + if (nat) + { + if (nat->state == NATState_Established || nat->state == NATState_Refresh || nat->state == NATState_Legacy) + DeleteNATPortMapping(m, nat, srs); + nat->reg.ServiceRegistration = mDNSNULL; + srs->uDNS_info.NATinfo = mDNSNULL; + FreeNATInfo(m, nat); + } + + switch (srs->uDNS_info.state) + { + case regState_Unregistered: + debugf("uDNS_DeregisterService - service %##s not registered", srs->RR_SRV.resrec.name->c); + return mStatus_BadReferenceErr; + case regState_FetchingZoneData: + // let the async op complete, then terminate + srs->uDNS_info.state = regState_Cancelled; + return mStatus_NoError; // deliver memfree upon completion of async op + case regState_Pending: + case regState_Refresh: + case regState_UpdatePending: + // deregister following completion of in-flight operation + srs->uDNS_info.state = regState_DeregDeferred; + return mStatus_NoError; + case regState_DeregPending: + case regState_DeregDeferred: + case regState_Cancelled: + debugf("Double deregistration of service %##s", srs->RR_SRV.resrec.name->c); + return mStatus_NoError; + case regState_NATError: // not registered + case regState_NATMap: // not registered + case regState_NoTarget: // not registered + unlinkSRS(m, srs); + srs->uDNS_info.state = regState_Unregistered; + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + srs->ServiceCallback(m, srs, mStatus_MemFree); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + return mStatus_NoError; + case regState_Registered: + srs->uDNS_info.state = regState_DeregPending; + SendServiceDeregistration(m, srs); + return mStatus_NoError; + case regState_ExtraQueued: // only for record registrations + errmsg = "bad state (regState_ExtraQueued)"; + goto error; + } + + error: + LogMsg("Error, uDNS_DeregisterService: %s", errmsg); + return mStatus_BadReferenceErr; + } + +mDNSexport mStatus uDNS_AddRecordToService(mDNS *const m, ServiceRecordSet *sr, ExtraResourceRecord *extra) + { + mStatus err = mStatus_UnknownErr; + + extra->r.resrec.RecordType = kDNSRecordTypeShared; // don't want it to conflict with the service name + extra->r.RecordCallback = mDNSNULL; // don't generate callbacks for extra RRs + + if (sr->uDNS_info.state == regState_Registered || sr->uDNS_info.state == regState_Refresh) + err = uDNS_RegisterRecord(m, &extra->r); + else + { + err = SetupRecordRegistration(m, &extra->r); + extra->r.uDNS_info.state = regState_ExtraQueued; // %%% Is it okay to overwrite the previous uDNS_info.state? + } + + if (!err) + { + extra->next = sr->Extras; + sr->Extras = extra; + } + return err; + } + +mDNSexport mStatus uDNS_UpdateRecord(mDNS *m, AuthRecord *rr) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + ServiceRecordSet *parent = mDNSNULL; + AuthRecord *rptr; + uDNS_RegInfo *info = &rr->uDNS_info; + regState_t *stateptr = mDNSNULL; + + // find the record in registered service list + for (parent = u->ServiceRegistrations; parent; parent = parent->next) + if (&parent->RR_TXT == rr) { stateptr = &parent->uDNS_info.state; break; } + + if (!parent) + { + // record not part of a service - check individual record registrations + for (rptr = u->RecordRegistrations; rptr; rptr = rptr->next) + if (rptr == rr) { stateptr = &rr->uDNS_info.state; break; } + if (!rptr) goto unreg_error; + } + + switch(*stateptr) + { + case regState_DeregPending: + case regState_DeregDeferred: + case regState_Cancelled: + case regState_Unregistered: + // not actively registered + goto unreg_error; + + case regState_FetchingZoneData: + case regState_NATMap: + case regState_ExtraQueued: + case regState_NoTarget: + // change rdata directly since it hasn't been sent yet + if (info->UpdateRDCallback) info->UpdateRDCallback(m, rr, rr->resrec.rdata); + SetNewRData(&rr->resrec, rr->NewRData, rr->newrdlength); + rr->NewRData = mDNSNULL; + return mStatus_NoError; + + case regState_Pending: + case regState_Refresh: + case regState_UpdatePending: + // registration in-flight. queue rdata and return + if (info->QueuedRData && info->UpdateRDCallback) + // if unsent rdata is already queued, free it before we replace it + info->UpdateRDCallback(m, rr, info->QueuedRData); + info->QueuedRData = rr->NewRData; + info->QueuedRDLen = rr->newrdlength; + rr->NewRData = mDNSNULL; + return mStatus_NoError; + + case regState_Registered: + info->OrigRData = rr->resrec.rdata; + info->OrigRDLen = rr->resrec.rdlength; + info->InFlightRData = rr->NewRData; + info->InFlightRDLen = rr->newrdlength; + rr->NewRData = mDNSNULL; + *stateptr = regState_UpdatePending; + if (parent) SendServiceRegistration(m, parent); + else sendRecordRegistration(m, rr); + return mStatus_NoError; + + case regState_NATError: + LogMsg("ERROR: uDNS_UpdateRecord called for record %##s with bad state regState_NATError", rr->resrec.name->c); + return mStatus_UnknownErr; // states for service records only + } + + unreg_error: + LogMsg("Requested update of record %##s type %d, part of service not currently registered", + rr->resrec.name->c, rr->resrec.rrtype); + return mStatus_Invalid; + } + + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - Periodic Execution Routines +#endif + + +mDNSlocal mDNSs32 CheckNATMappings(mDNS *m, mDNSs32 timenow) + { + NATTraversalInfo *ptr = m->uDNS_info.NATTraversals; + mDNSs32 nextevent = timenow + MIN_UCAST_PERIODIC_EXEC; + + while (ptr) + { + NATTraversalInfo *cur = ptr; + ptr = ptr->next; + if (cur->op != NATOp_AddrRequest || cur->state != NATState_Established) // no refresh necessary for established Add requests + { + if (cur->retry - timenow < 0) + { + if (cur->state == NATState_Established) RefreshNATMapping(cur, m); + else if (cur->state == NATState_Request || cur->state == NATState_Refresh) + { + if (cur->ntries >= NATMAP_MAX_TRIES) cur->ReceiveResponse(cur, m, mDNSNULL, 0); // may invalidate "cur" + else SendNATMsg(cur, m); + } + } + else if (cur->retry - nextevent < 0) nextevent = cur->retry; + } + } + return nextevent; + } + +mDNSlocal mDNSs32 CheckQueries(mDNS *m, mDNSs32 timenow) + { + DNSQuestion *q; + uDNS_GlobalInfo *u = &m->uDNS_info; + LLQ_Info *llq; + mDNSs32 sendtime; + mDNSs32 nextevent = timenow + MIN_UCAST_PERIODIC_EXEC; + DNSMessage msg; + mStatus err = mStatus_NoError; + mDNSu8 *end; + uDNS_QuestionInfo *info; + + u->CurrentQuery = u->ActiveQueries; + while (u->CurrentQuery) + { + q = u->CurrentQuery; + info = &q->uDNS_info; + llq = info->llq; + + if (!info->internal && ((!q->LongLived && !info->Answered) || (llq && llq->state < LLQ_Established)) && + info->RestartTime + RESTART_GOODBYE_DELAY - timenow < 0) + { + // if we've been spinning on restart setup, and we have known answers, give goodbyes (they may be re-added later) + while (info->knownAnswers) + { + CacheRecord *cr = info->knownAnswers; + info->knownAnswers = info->knownAnswers->next; + + m->mDNS_reentrancy++; // Increment to allow client to legally make mDNS API calls from the callback + q->QuestionCallback(m, q, &cr->resrec, mDNSfalse); + m->mDNS_reentrancy--; // Decrement to block mDNS API calls again + ufree(cr); + if (q != u->CurrentQuery) { debugf("CheckQueries - question removed via callback."); break; } + } + } + if (q != u->CurrentQuery) continue; + + if (q->LongLived && llq->state != LLQ_Poll) + { + if (llq->state >= LLQ_InitialRequest && llq->state <= LLQ_Established) + { + if (llq->retry - timenow < 0) + { + // sanity check to avoid packet flood bugs + if (!llq->retry) + LogMsg("ERROR: retry timer not set for LLQ %##s in state %d", q->qname.c, llq->state); + else if (llq->state == LLQ_Established || llq->state == LLQ_Refresh) + sendLLQRefresh(m, q, llq->origLease); + else if (llq->state == LLQ_InitialRequest) + startLLQHandshake(m, llq, mDNSfalse); + else if (llq->state == LLQ_SecondaryRequest) + sendChallengeResponse(m, q, mDNSNULL); + else if (llq->state == LLQ_Retry) + { llq->ntries = 0; startLLQHandshake(m, llq, mDNSfalse); } + } + else if (llq->retry - nextevent < 0) nextevent = llq->retry; + } + } + else + { + sendtime = q->LastQTime + q->ThisQInterval; + if (m->SuppressStdPort53Queries && + sendtime - m->SuppressStdPort53Queries < 0) // Don't allow sendtime to be earlier than SuppressStdPort53Queries + sendtime = m->SuppressStdPort53Queries; + if (sendtime - timenow < 0) + { + DNSServer *server = GetServerForName(&m->uDNS_info, &q->qname); + if (server) + { + if (server->teststate == DNSServer_Untested) + { + InitializeDNSMessage(&msg.h, newMessageID(&m->uDNS_info), uQueryFlags); + end = putQuestion(&msg, msg.data, msg.data + AbsoluteMaxDNSMessageData, DNSRelayTestQuestion, kDNSType_PTR, kDNSClass_IN); + } + else + err = constructQueryMsg(&msg, &end, q); + if (err) LogMsg("Error: uDNS_Idle - constructQueryMsg. Skipping question %##s", q->qname.c); + else + { + if (server->teststate != DNSServer_Failed) + err = mDNSSendDNSMessage(m, &msg, end, mDNSInterface_Any, &server->addr, UnicastDNSPort, -1, mDNSNULL); + m->SuppressStdPort53Queries = NonZeroTime(m->timenow + (mDNSPlatformOneSecond+99)/100); + q->LastQTime = timenow; + if (err) debugf("ERROR: uDNS_idle - mDNSSendDNSMessage - %ld", err); // surpress syslog messages if we have no network + else if (q->ThisQInterval < MAX_UCAST_POLL_INTERVAL) q->ThisQInterval = q->ThisQInterval * 2; // don't increase interval if send failed + } + } + } + else if (sendtime - nextevent < 0) nextevent = sendtime; + } + u->CurrentQuery = u->CurrentQuery->next; + } + return nextevent; + } + +mDNSlocal mDNSs32 CheckRecordRegistrations(mDNS *m, mDNSs32 timenow) + { + AuthRecord *rr; + uDNS_RegInfo *rInfo; + uDNS_GlobalInfo *u = &m->uDNS_info; + mDNSs32 nextevent = timenow + MIN_UCAST_PERIODIC_EXEC; + + //!!!KRS list should be pre-sorted by expiration + for (rr = u->RecordRegistrations; rr; rr = rr->next) + { + rInfo = &rr->uDNS_info; + if (rInfo->state == regState_Pending || rInfo->state == regState_DeregPending || rInfo->state == regState_UpdatePending || rInfo->state == regState_DeregDeferred || rInfo->state == regState_Refresh) + { + if (rr->LastAPTime + rr->ThisAPInterval - timenow < 0) + { +#if MDNS_DEBUGMSGS + char *op = "(unknown operation)"; + if (rInfo->state == regState_Pending) op = "registration"; + else if (rInfo->state == regState_DeregPending) op = "deregistration"; + else if (rInfo->state == regState_Refresh) op = "refresh"; + debugf("Retransmit record %s %##s", op, rr->resrec.name->c); +#endif + //LogMsg("Retransmit record %##s", rr->resrec.name->c); + if (rInfo->state == regState_DeregPending) SendRecordDeregistration(m, rr); + else sendRecordRegistration(m, rr); + } + if (rr->LastAPTime + rr->ThisAPInterval - nextevent < 0) nextevent = rr->LastAPTime + rr->ThisAPInterval; + } + if (rInfo->lease && rInfo->state == regState_Registered) + { + if (rInfo->expire - timenow < 0) + { + debugf("refreshing record %##s", rr->resrec.name->c); + rInfo->state = regState_Refresh; + sendRecordRegistration(m, rr); + } + if (rInfo->expire - nextevent < 0) nextevent = rInfo->expire; + } + } + return nextevent; + } + +mDNSlocal mDNSs32 CheckServiceRegistrations(mDNS *m, mDNSs32 timenow) + { + ServiceRecordSet *s = m->uDNS_info.ServiceRegistrations; + uDNS_RegInfo *rInfo; + mDNSs32 nextevent = timenow + MIN_UCAST_PERIODIC_EXEC; + + // Note: ServiceRegistrations list is in the order they were created; important for in-order event delivery + while (s) + { + ServiceRecordSet *srs = s; + // NOTE: Must advance s here -- SendServiceDeregistration may delete the object we're looking at, + // and then if we tried to do srs = srs->next at the end we'd be referencing a dead object + s = s->next; + + rInfo = &srs->uDNS_info; + if (rInfo->state == regState_Pending || rInfo->state == regState_DeregPending || rInfo->state == regState_DeregDeferred || rInfo->state == regState_Refresh || rInfo->state == regState_UpdatePending) + { + if (srs->RR_SRV.LastAPTime + srs->RR_SRV.ThisAPInterval - timenow < 0) + { +#if MDNS_DEBUGMSGS + char *op = "unknown"; + if (rInfo->state == regState_Pending) op = "registration"; + else if (rInfo->state == regState_DeregPending) op = "deregistration"; + else if (rInfo->state == regState_Refresh) op = "refresh"; + else if (rInfo->state == regState_UpdatePending) op = "txt record update"; + debugf("Retransmit service %s %##s", op, srs->RR_SRV.resrec.name->c); +#endif + if (rInfo->state == regState_DeregPending) { SendServiceDeregistration(m, srs); continue; } + else SendServiceRegistration (m, srs); + } + if (nextevent - srs->RR_SRV.LastAPTime + srs->RR_SRV.ThisAPInterval > 0) + nextevent = srs->RR_SRV.LastAPTime + srs->RR_SRV.ThisAPInterval; + } + + if (rInfo->lease && rInfo->state == regState_Registered) + { + if (rInfo->expire - timenow < 0) + { + debugf("refreshing service %##s", srs->RR_SRV.resrec.name->c); + rInfo->state = regState_Refresh; + SendServiceRegistration(m, srs); + } + if (rInfo->expire - nextevent < 0) nextevent = rInfo->expire; + } + } + return nextevent; + } + +mDNSexport void uDNS_Execute(mDNS *const m) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + mDNSs32 nexte, timenow = mDNSPlatformTimeNow(m); + + u->nextevent = timenow + MIN_UCAST_PERIODIC_EXEC; + + if (u->DelaySRVUpdate && u->NextSRVUpdate - timenow < 0) + { + u->DelaySRVUpdate = mDNSfalse; + UpdateSRVRecords(m); + } + + nexte = CheckNATMappings(m, timenow); + if (nexte - u->nextevent < 0) u->nextevent = nexte; + + if (m->SuppressStdPort53Queries && m->timenow - m->SuppressStdPort53Queries >= 0) + m->SuppressStdPort53Queries = 0; // If suppression time has passed, clear it + + nexte = CheckQueries(m, timenow); + if (nexte - u->nextevent < 0) u->nextevent = nexte; + + nexte = CheckRecordRegistrations(m, timenow); + if (nexte - u->nextevent < 0) u->nextevent = nexte; + + nexte = CheckServiceRegistrations(m, timenow); + if (nexte - u->nextevent < 0) u->nextevent = nexte; + + } + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - Startup, Shutdown, and Sleep +#endif + +// DeregisterActive causes active LLQs to be removed from the server, e.g. before sleep. Pass false +// following a location change, as the server will reject deletions from a source address different +// from the address on which the LLQ was created. + +mDNSlocal void SuspendLLQs(mDNS *m, mDNSBool DeregisterActive) + { + DNSQuestion *q; + LLQ_Info *llq; + for (q = m->uDNS_info.ActiveQueries; q; q = q->next) + { + llq = q->uDNS_info.llq; + if (q->LongLived && llq) + { + if (llq->state == LLQ_GetZoneInfo) + { + debugf("Marking %##s suspend-deferred", q->qname.c); + llq->state = LLQ_SuspendDeferred; // suspend once we're done getting zone info + } + else if (llq->state < LLQ_Suspended) + { + if (DeregisterActive && (llq->state == LLQ_Established || llq->state == LLQ_Refresh)) + { debugf("Deleting LLQ %##s", q->qname.c); sendLLQRefresh(m, q, 0); } + debugf("Marking %##s suspended", q->qname.c); + llq->state = LLQ_Suspended; + ubzero(llq->id, 8); + } + else if (llq->state == LLQ_Poll) { debugf("Marking %##s suspended-poll", q->qname.c); llq->state = LLQ_SuspendedPoll; } + if (llq->NATMap) llq->NATMap = mDNSfalse; // may not need nat mapping if we restart with new route + } + } + CheckForUnreferencedLLQMapping(m); + } + +mDNSlocal void RestartQueries(mDNS *m) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + DNSQuestion *q; + LLQ_Info *llqInfo; + mDNSs32 timenow = mDNSPlatformTimeNow(m); + + u->CurrentQuery = u->ActiveQueries; + while (u->CurrentQuery) + { + q = u->CurrentQuery; + u->CurrentQuery = u->CurrentQuery->next; + llqInfo = q->uDNS_info.llq; + q->uDNS_info.RestartTime = timenow; + q->uDNS_info.Answered = mDNSfalse; + if (q->LongLived) + { + if (!llqInfo) { LogMsg("Error: RestartQueries - %##s long-lived with NULL info", q->qname.c); continue; } + if (llqInfo->state == LLQ_Suspended || llqInfo->state == LLQ_NatMapWait) + { + llqInfo->ntries = -1; + llqInfo->deriveRemovesOnResume = mDNStrue; + startLLQHandshake(m, llqInfo, mDNStrue); // we set defer to true since several events that may generate restarts often arrive in rapid succession, and this cuts unnecessary packets + } + else if (llqInfo->state == LLQ_SuspendDeferred) + llqInfo->state = LLQ_GetZoneInfo; // we never finished getting zone data - proceed as usual + else if (llqInfo->state == LLQ_SuspendedPoll) + { + // if we were polling, we may have had bad zone data due to firewall, etc. - refetch + llqInfo->ntries = 0; + llqInfo->deriveRemovesOnResume = mDNStrue; + llqInfo->state = LLQ_GetZoneInfo; + startGetZoneData(&q->qname, m, mDNSfalse, mDNStrue, startLLQHandshakeCallback, llqInfo); + } + } + else { q->LastQTime = timenow; q->ThisQInterval = INIT_UCAST_POLL_INTERVAL; } // trigger poll in 1 second (to reduce packet rate when restarts come in rapid succession) + } + } + +mDNSexport void mDNS_UpdateLLQs(mDNS *m) + { + uDNS_GlobalInfo *u = &m->uDNS_info; + + mDNS_Lock(m); + if (u->LLQNatInfo) + { + DeleteNATPortMapping(m, u->LLQNatInfo, mDNSNULL); + FreeNATInfo(m, u->LLQNatInfo); // routine clears u->LLQNatInfo ptr + } + SuspendLLQs(m, mDNStrue); + RestartQueries(m); + mDNS_Unlock(m); + } + +// simplest sleep logic - rather than having sleep states that must be dealt with explicitly in all parts of +// the code, we simply send a deregistration, and put the service in Refresh state, with a timeout far enough +// in the future that we'll sleep (or the sleep will be cancelled) before it is retransmitted. Then to wake, +// we just move up the timers. + + + +mDNSlocal void SleepRecordRegistrations(mDNS *m) + { + DNSMessage msg; + AuthRecord *rr = m->uDNS_info.RecordRegistrations; + mDNSs32 timenow = mDNSPlatformTimeNow(m); + + while (rr) + { + if (rr->uDNS_info.state == regState_Registered || + rr->uDNS_info.state == regState_Refresh) + { + mDNSu8 *ptr = msg.data, *end = (mDNSu8 *)&msg + sizeof(DNSMessage); + InitializeDNSMessage(&msg.h, newMessageID(&m->uDNS_info), UpdateReqFlags); + + // construct deletion update + ptr = putZone(&msg, ptr, end, &rr->uDNS_info.zone, mDNSOpaque16fromIntVal(rr->resrec.rrclass)); + if (!ptr) { LogMsg("Error: SleepRecordRegistrations - could not put zone"); return; } + ptr = putDeletionRecord(&msg, ptr, &rr->resrec); + if (!ptr) { LogMsg("Error: SleepRecordRegistrations - could not put deletion record"); return; } + + mDNSSendDNSMessage(m, &msg, ptr, mDNSInterface_Any, &rr->uDNS_info.ns, rr->uDNS_info.port, -1, GetAuthInfoForName(&m->uDNS_info, rr->resrec.name)); + rr->uDNS_info.state = regState_Refresh; + rr->LastAPTime = timenow; + rr->ThisAPInterval = 300 * mDNSPlatformOneSecond; + } + rr = rr->next; + } + } + +mDNSlocal void WakeRecordRegistrations(mDNS *m) + { + mDNSs32 timenow = mDNSPlatformTimeNow(m); + AuthRecord *rr = m->uDNS_info.RecordRegistrations; + + while (rr) + { + if (rr->uDNS_info.state == regState_Refresh) + { + // trigger slightly delayed refresh (we usually get this message before kernel is ready to send packets) + rr->LastAPTime = timenow; + rr->ThisAPInterval = INIT_UCAST_POLL_INTERVAL; + } + rr = rr->next; + } + } + +mDNSlocal void SleepServiceRegistrations(mDNS *m) + { + ServiceRecordSet *srs = m->uDNS_info.ServiceRegistrations; + while(srs) + { + uDNS_RegInfo *info = &srs->uDNS_info; + NATTraversalInfo *nat = info->NATinfo; + + if (nat) + { + if (nat->state == NATState_Established || nat->state == NATState_Refresh || nat->state == NATState_Legacy) + DeleteNATPortMapping(m, nat, srs); + nat->reg.ServiceRegistration = mDNSNULL; + srs->uDNS_info.NATinfo = mDNSNULL; + FreeNATInfo(m, nat); + } + + if (info->state == regState_UpdatePending) + { + // act as if the update succeeded, since we're about to delete the name anyway + AuthRecord *txt = &srs->RR_TXT; + uDNS_RegInfo *txtInfo = &txt->uDNS_info; + info->state = regState_Registered; + // deallocate old RData + if (txtInfo->UpdateRDCallback) txtInfo->UpdateRDCallback(m, txt, txtInfo->OrigRData); + SetNewRData(&txt->resrec, txtInfo->InFlightRData, txtInfo->InFlightRDLen); + txtInfo->OrigRData = mDNSNULL; + txtInfo->InFlightRData = mDNSNULL; + } + + if (info->state == regState_Registered || info->state == regState_Refresh) + { + mDNSOpaque16 origid = srs->uDNS_info.id; + info->state = regState_DeregPending; // state expected by SendDereg() + SendServiceDeregistration(m, srs); + info->id = origid; + info->state = regState_NoTarget; // when we wake, we'll re-register (and optionally nat-map) once our address record completes + srs->RR_SRV.resrec.rdata->u.srv.target.c[0] = 0; + } + srs = srs->next; + } + } + +mDNSlocal void WakeServiceRegistrations(mDNS *m) + { + mDNSs32 timenow = mDNSPlatformTimeNow(m); + ServiceRecordSet *srs = m->uDNS_info.ServiceRegistrations; + while(srs) + { + if (srs->uDNS_info.state == regState_Refresh) + { + // trigger slightly delayed refresh (we usually get this message before kernel is ready to send packets) + srs->RR_SRV.LastAPTime = timenow; + srs->RR_SRV.ThisAPInterval = INIT_UCAST_POLL_INTERVAL; + } + srs = srs->next; + } + } + +mDNSexport void uDNS_Init(mDNS *const m) + { + mDNSPlatformMemZero(&m->uDNS_info, sizeof(uDNS_GlobalInfo)); + m->uDNS_info.nextevent = m->timenow_last + 0x78000000; + } + +mDNSexport void uDNS_Sleep(mDNS *const m) + { + SuspendLLQs(m, mDNStrue); + SleepServiceRegistrations(m); + SleepRecordRegistrations(m); + } + +mDNSexport void uDNS_Wake(mDNS *const m) + { + RestartQueries(m); + WakeServiceRegistrations(m); + WakeRecordRegistrations(m); + } diff --git a/src/mdns/uDNS.h b/src/mdns/uDNS.h new file mode 100755 index 00000000..c7e0e7ca --- /dev/null +++ b/src/mdns/uDNS.h @@ -0,0 +1,190 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + Change History (most recent first): + +$Log: uDNS.h,v $ +Revision 1.32.2.1 2006/08/29 06:24:23 cheshire +Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 + +Revision 1.32 2005/07/29 19:46:10 ksekar + reduce polling period on failed LLQs to 15 minutes + +Revision 1.31 2005/03/31 02:19:56 cheshire + Fix build warnings +Reviewed by: Scott Herscher + +Revision 1.30 2005/03/04 03:00:03 ksekar + Retransmissions happen too early, causing registrations to conflict with themselves + +Revision 1.29 2005/01/11 22:50:53 ksekar +Fixed constant naming (was using kLLQ_DefLease for update leases) + +Revision 1.28 2004/12/22 00:13:49 ksekar + Change version, port, and polling interval for LLQ + +Revision 1.27 2004/11/23 04:06:50 cheshire +Get rid of floating point constant -- in a small embedded device, bringing in all +the floating point libraries just to halve an integer value is a bit too heavyweight. + +Revision 1.26 2004/11/22 17:49:15 ksekar +Changed INIT_REFRESH from fraction to decimal + +Revision 1.25 2004/11/22 17:16:20 ksekar + Unicast services don't disappear when you disable all networking + +Revision 1.24 2004/11/19 04:24:08 ksekar + Security: Enforce a "window" on one-shot wide-area queries + +Revision 1.23 2004/11/18 18:04:21 ksekar +Add INIT_REFRESH constant + +Revision 1.22 2004/11/15 20:09:24 ksekar + Wide Area support for Add/Remove record + +Revision 1.21 2004/11/11 20:14:55 ksekar + Wide-Area registrations not deregistered on sleep + +Revision 1.20 2004/10/16 00:16:59 cheshire + Replace IP TTL 255 check with local subnet source address check + +Revision 1.19 2004/09/17 01:08:49 cheshire +Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h + The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces + declared in that file are ONLY appropriate to single-address-space embedded applications. + For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used. + +Revision 1.18 2004/09/03 19:23:05 ksekar +: Need retransmission mechanism for wide-area service registrations + +Revision 1.17 2004/09/01 03:59:29 ksekar +: Conditionally compile out uDNS code on Windows + +Revision 1.16 2004/08/25 00:37:27 ksekar +: Cleanup DynDNS hostname registration code + +Revision 1.15 2004/07/30 17:40:06 ksekar +: TXT Record updates not available for wide-area services + +Revision 1.14 2004/07/29 19:27:15 ksekar +NATPMP Support - minor fixes and cleanup + +Revision 1.13 2004/07/29 02:03:35 ksekar +Delete unused #define and structure field + +Revision 1.12 2004/07/26 22:49:30 ksekar +: Feature #9516: Need support for NATPMP in client + +Revision 1.11 2004/06/17 01:13:11 ksekar +: polling interval too short + +Revision 1.10 2004/06/11 05:45:03 ksekar +: Change SRV names for LLQ/Update port lookups + +Revision 1.9 2004/06/01 23:46:50 ksekar +: DynDNS: dynamically look up LLQ/Update ports + +Revision 1.8 2004/05/28 23:42:37 ksekar +: Feature: DNS server->client notification on record changes (#7805) + +Revision 1.7 2004/05/18 23:51:25 cheshire +Tidy up all checkin comments to use consistent "" format for bug numbers + +Revision 1.6 2004/03/13 01:57:33 ksekar +: DynDNS: Dynamic update of service records + +Revision 1.5 2004/02/21 08:56:58 bradley +Wrap prototypes with extern "C" for C++ builds. + +Revision 1.4 2004/02/06 23:04:19 ksekar +Basic Dynamic Update support via mDNS_Register (dissabled via +UNICAST_REGISTRATION #define) + +Revision 1.3 2004/01/24 03:38:27 cheshire +Fix minor syntactic error: Headers should use "extern" declarations, not "mDNSexport" + +Revision 1.2 2004/01/23 23:23:15 ksekar +Added TCP support for truncated unicast messages. + +Revision 1.1 2003/12/13 03:05:27 ksekar +: DynDNS: Unicast query of service records + + + */ + +#ifndef __UDNS_H_ +#define __UDNS_H_ + +#include "mDNSEmbeddedAPI.h" +#include "DNSCommon.h" + +#ifdef __cplusplus + extern "C" { +#endif + +#define RESTART_GOODBYE_DELAY (6 * mDNSPlatformOneSecond) // delay after restarting LLQ before nuking previous known answers (avoids flutter if we restart before we have networking up) +#define MIN_UCAST_PERIODIC_EXEC (5 * mDNSPlatformOneSecond) +#define INIT_UCAST_POLL_INTERVAL (3 * mDNSPlatformOneSecond) // this interval is used after send failures on network transitions + // which typically heal quickly, so we start agressively and exponentially back off +#define MAX_UCAST_POLL_INTERVAL (60 * 60 * mDNSPlatformOneSecond) +#define LLQ_POLL_INTERVAL (15 * 60 * mDNSPlatformOneSecond) // Polling interval for zones w/ an advertised LLQ port (ie not static zones) if LLQ fails due to NAT, etc. +#define RESPONSE_WINDOW (60 * mDNSPlatformOneSecond) // require server responses within one minute of request +#define UPDATE_PORT_NAME "_dns-update._udp." +#define LLQ_PORT_NAME "_dns-llq._udp" +#define DEFAULT_UPDATE_LEASE 7200 + +// Entry points into unicast-specific routines + +extern mStatus uDNS_StartQuery(mDNS *const m, DNSQuestion *const question); +extern mDNSBool uDNS_IsActiveQuery(DNSQuestion *const question, uDNS_GlobalInfo *u); // returns true if OK to call StopQuery +extern mStatus uDNS_StopQuery(mDNS *const m, DNSQuestion *const question); + +extern void uDNS_Init(mDNS *const m); +extern void uDNS_Sleep(mDNS *const m); +extern void uDNS_Wake(mDNS *const m); +#define uDNS_Close uDNS_Sleep + +// uDNS_UpdateRecord +// following fields must be set, and the update validated, upon entry. +// rr->NewRData +// rr->newrdlength +// rr->UpdateCallback + +extern mStatus uDNS_AddRecordToService(mDNS *const m, ServiceRecordSet *sr, ExtraResourceRecord *extra); +extern mStatus uDNS_UpdateRecord(mDNS *m, AuthRecord *rr); + +extern mStatus uDNS_RegisterRecord(mDNS *const m, AuthRecord *const rr); +extern mStatus uDNS_DeregisterRecord(mDNS *const m, AuthRecord *const rr); + +extern mStatus uDNS_RegisterService(mDNS *const m, ServiceRecordSet *srs); +extern mStatus uDNS_DeregisterService(mDNS *const m, ServiceRecordSet *srs); + +// integer fields of msg header must be in HOST byte order before calling this routine +extern void uDNS_ReceiveMsg(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end, + const mDNSAddr *const srcaddr, const mDNSIPPort srcport, const mDNSAddr *const dstaddr, + const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID); + +extern void uDNS_ReceiveNATMap(mDNS *m, mDNSu8 *pkt, mDNSu16 len); + +// returns time of next scheduled event +extern void uDNS_Execute(mDNS *const m); + + +#ifdef __cplusplus + } +#endif + +#endif // __UDNS_H_ diff --git a/src/rend-posix.c b/src/rend-posix.c index c6816cb7..2b1e1863 100644 --- a/src/rend-posix.c +++ b/src/rend-posix.c @@ -196,7 +196,7 @@ */ -#include "mDNSClientAPI.h"// Defines the interface to the client layer above +#include "mDNSEmbeddedAPI.h"// Defines the interface to the client layer above #include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform #include @@ -217,22 +217,6 @@ #include "rend.h" #include "rend-unix.h" - -/* - * I'll take some extra hackishness, please... - */ -typedef struct PosixNetworkInterface PosixNetworkInterface; - -struct PosixNetworkInterface { - NetworkInterfaceInfo coreIntf; - const char * intfName; - PosixNetworkInterface * aliasIntf; - int index; - int multicastSocket; - int multicastSocketv6; -}; - - static mDNS mDNSStorage; // mDNS core uses this to store its globals static mDNS_PlatformSupport PlatformStorage; // Stores this platform's globals