From 9449f151d0ccf3ac755d5f2bd9b4057ae2b03157 Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Thu, 30 Jan 2014 13:52:13 +0100 Subject: mDNS: Import The sources can be obtained via: http://opensource.apple.com/tarballs/mDNSResponder/mDNSResponder-544.tar.gz --- mDNSResponder/mDNSPosix/Client.c | 223 +++ mDNSResponder/mDNSPosix/ExampleClientApp.c | 91 + mDNSResponder/mDNSPosix/ExampleClientApp.h | 18 + mDNSResponder/mDNSPosix/Identify.c | 376 ++++ mDNSResponder/mDNSPosix/Makefile | 523 ++++++ mDNSResponder/mDNSPosix/NetMonitor.c | 1003 ++++++++++ mDNSResponder/mDNSPosix/PosixDaemon.c | 258 +++ mDNSResponder/mDNSPosix/ProxyResponder.c | 300 +++ mDNSResponder/mDNSPosix/ReadMe.txt | 314 ++++ mDNSResponder/mDNSPosix/Responder.c | 788 ++++++++ mDNSResponder/mDNSPosix/Services.txt | 36 + mDNSResponder/mDNSPosix/libnss_mdns.8 | 148 ++ mDNSResponder/mDNSPosix/mDNSPosix.c | 1789 ++++++++++++++++++ mDNSResponder/mDNSPosix/mDNSPosix.h | 85 + mDNSResponder/mDNSPosix/mDNSUNP.c | 719 ++++++++ mDNSResponder/mDNSPosix/mDNSUNP.h | 130 ++ mDNSResponder/mDNSPosix/mdnsd.sh | 73 + mDNSResponder/mDNSPosix/nss_ReadMe.txt | 125 ++ mDNSResponder/mDNSPosix/nss_mdns.c | 2723 ++++++++++++++++++++++++++++ mDNSResponder/mDNSPosix/nss_mdns.conf | 13 + mDNSResponder/mDNSPosix/nss_mdns.conf.5 | 135 ++ mDNSResponder/mDNSPosix/parselog.py | 247 +++ 22 files changed, 10117 insertions(+) create mode 100755 mDNSResponder/mDNSPosix/Client.c create mode 100644 mDNSResponder/mDNSPosix/ExampleClientApp.c create mode 100644 mDNSResponder/mDNSPosix/ExampleClientApp.h create mode 100644 mDNSResponder/mDNSPosix/Identify.c create mode 100755 mDNSResponder/mDNSPosix/Makefile create mode 100644 mDNSResponder/mDNSPosix/NetMonitor.c create mode 100644 mDNSResponder/mDNSPosix/PosixDaemon.c create mode 100644 mDNSResponder/mDNSPosix/ProxyResponder.c create mode 100755 mDNSResponder/mDNSPosix/ReadMe.txt create mode 100755 mDNSResponder/mDNSPosix/Responder.c create mode 100755 mDNSResponder/mDNSPosix/Services.txt create mode 100755 mDNSResponder/mDNSPosix/libnss_mdns.8 create mode 100755 mDNSResponder/mDNSPosix/mDNSPosix.c create mode 100755 mDNSResponder/mDNSPosix/mDNSPosix.h create mode 100755 mDNSResponder/mDNSPosix/mDNSUNP.c create mode 100755 mDNSResponder/mDNSPosix/mDNSUNP.h create mode 100644 mDNSResponder/mDNSPosix/mdnsd.sh create mode 100755 mDNSResponder/mDNSPosix/nss_ReadMe.txt create mode 100755 mDNSResponder/mDNSPosix/nss_mdns.c create mode 100755 mDNSResponder/mDNSPosix/nss_mdns.conf create mode 100755 mDNSResponder/mDNSPosix/nss_mdns.conf.5 create mode 100755 mDNSResponder/mDNSPosix/parselog.py (limited to 'mDNSResponder/mDNSPosix') diff --git a/mDNSResponder/mDNSPosix/Client.c b/mDNSResponder/mDNSPosix/Client.c new file mode 100755 index 00000000..c0badf43 --- /dev/null +++ b/mDNSResponder/mDNSPosix/Client.c @@ -0,0 +1,223 @@ +/* -*- 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "mDNSEmbeddedAPI.h" // Defines the interface to the mDNS core code +#include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform +#include "ExampleClientApp.h" + +// Globals +static mDNS mDNSStorage; // mDNS core uses this to store its globals +static mDNS_PlatformSupport PlatformStorage; // Stores this platform's globals +#define RR_CACHE_SIZE 500 +static CacheEntity gRRCache[RR_CACHE_SIZE]; + +mDNSexport const char ProgramName[] = "mDNSClientPosix"; + +static const char *gProgramName = ProgramName; + +static void BrowseCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) +// A callback from the core mDNS code that indicates that we've received a +// response to our query. Note that this code runs on the main thread +// (in fact, there is only one thread!), so we can safely printf the results. +{ + domainlabel name; + domainname type; + domainname domain; + char nameC [MAX_DOMAIN_LABEL+1]; // Unescaped name: up to 63 bytes plus C-string terminating NULL. + char typeC [MAX_ESCAPED_DOMAIN_NAME]; + char domainC[MAX_ESCAPED_DOMAIN_NAME]; + const char *state; + + (void)m; // Unused + (void)question; // Unused + + assert(answer->rrtype == kDNSType_PTR); + + DeconstructServiceName(&answer->rdata->u.name, &name, &type, &domain); + + ConvertDomainLabelToCString_unescaped(&name, nameC); + ConvertDomainNameToCString(&type, typeC); + ConvertDomainNameToCString(&domain, domainC); + + // If the TTL has hit 0, the service is no longer available. + if (!AddRecord) { + state = "Lost "; + } else { + state = "Found"; + } + fprintf(stderr, "*** %s name = '%s', type = '%s', domain = '%s'\n", state, nameC, typeC, domainC); +} + +static mDNSBool CheckThatServiceTypeIsUsable(const char *serviceType, mDNSBool printExplanation) +// Checks that serviceType is a reasonable service type +// label and, if it isn't and printExplanation is true, prints +// an explanation of why not. +{ + mDNSBool result; + + result = mDNStrue; + if (result && strlen(serviceType) > 63) { + if (printExplanation) { + fprintf(stderr, + "%s: Service type specified by -t is too long (must be 63 characters or less)\n", + gProgramName); + } + result = mDNSfalse; + } + if (result && serviceType[0] == 0) { + if (printExplanation) { + fprintf(stderr, + "%s: Service type specified by -t can't be empty\n", + gProgramName); + } + result = mDNSfalse; + } + return result; +} + +static const char kDefaultServiceType[] = "_afpovertcp._tcp"; +static const char kDefaultDomain[] = "local."; + +static void PrintUsage() +{ + fprintf(stderr, + "Usage: %s [-v level] [-t type] [-d domain]\n", + gProgramName); + fprintf(stderr, " -v verbose mode, level is a number from 0 to 2\n"); + fprintf(stderr, " 0 = no debugging info (default)\n"); + fprintf(stderr, " 1 = standard debugging info\n"); + fprintf(stderr, " 2 = intense debugging info\n"); + fprintf(stderr, " -t uses 'type' as the service type (default is '%s')\n", kDefaultServiceType); + fprintf(stderr, " -d uses 'domain' as the domain to browse (default is '%s')\n", kDefaultDomain); +} + +static const char *gServiceType = kDefaultServiceType; +static const char *gServiceDomain = kDefaultDomain; + +static void ParseArguments(int argc, char **argv) +// Parses our command line arguments into the global variables +// listed above. +{ + int ch; + + // Set gProgramName to the last path component of argv[0] + + gProgramName = strrchr(argv[0], '/'); + if (gProgramName == NULL) { + gProgramName = argv[0]; + } else { + gProgramName += 1; + } + + // Parse command line options using getopt. + + do { + ch = getopt(argc, argv, "v:t:d:"); + if (ch != -1) { + switch (ch) { + case 'v': + gMDNSPlatformPosixVerboseLevel = atoi(optarg); + if (gMDNSPlatformPosixVerboseLevel < 0 || gMDNSPlatformPosixVerboseLevel > 2) { + fprintf(stderr, + "%s: Verbose mode must be in the range 0..2\n", + gProgramName); + exit(1); + } + break; + case 't': + gServiceType = optarg; + if ( !CheckThatServiceTypeIsUsable(gServiceType, mDNStrue) ) { + exit(1); + } + break; + case 'd': + gServiceDomain = optarg; + break; + case '?': + default: + PrintUsage(); + exit(1); + break; + } + } + } while (ch != -1); + + // Check for any left over command line arguments. + + if (optind != argc) { + fprintf(stderr, "%s: Unexpected argument '%s'\n", gProgramName, argv[optind]); + exit(1); + } +} + +int main(int argc, char **argv) +// The program's main entry point. The program does a trivial +// mDNS query, looking for all AFP servers in the local domain. +{ + int result; + mStatus status; + DNSQuestion question; + domainname type; + domainname domain; + + // Parse our command line arguments. This won't come back if there's an error. + ParseArguments(argc, argv); + + // Initialise the mDNS core. + status = mDNS_Init(&mDNSStorage, &PlatformStorage, + gRRCache, RR_CACHE_SIZE, + mDNS_Init_DontAdvertiseLocalAddresses, + mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext); + if (status == mStatus_NoError) { + + // Construct and start the query. + + MakeDomainNameFromDNSNameString(&type, gServiceType); + MakeDomainNameFromDNSNameString(&domain, gServiceDomain); + + status = mDNS_StartBrowse(&mDNSStorage, &question, &type, &domain, mDNSNULL, mDNSInterface_Any, 0, mDNSfalse, mDNSfalse, BrowseCallback, NULL); + + // Run the platform main event loop until the user types ^C. + // The BrowseCallback routine is responsible for printing + // any results that we find. + + if (status == mStatus_NoError) { + fprintf(stderr, "Hit ^C when you're bored waiting for responses.\n"); + ExampleClientEventLoop(&mDNSStorage); + mDNS_StopQuery(&mDNSStorage, &question); + mDNS_Close(&mDNSStorage); + } + } + + if (status == mStatus_NoError) { + result = 0; + } else { + result = 2; + } + if ( (result != 0) || (gMDNSPlatformPosixVerboseLevel > 0) ) { + fprintf(stderr, "%s: Finished with status %d, result %d\n", gProgramName, (int)status, result); + } + + return 0; +} diff --git a/mDNSResponder/mDNSPosix/ExampleClientApp.c b/mDNSResponder/mDNSPosix/ExampleClientApp.c new file mode 100644 index 00000000..f23248b8 --- /dev/null +++ b/mDNSResponder/mDNSPosix/ExampleClientApp.c @@ -0,0 +1,91 @@ +/* -*- 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. + */ + +#include // For printf() +#include // For exit() etc. +#include // For strlen() etc. +#include // For select() +#include // For errno, EINTR +#include // For INADDR_NONE +#include // For inet_addr() +#include // For gethostbyname() +#include // For SIGINT, etc. + +#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 + +//******************************************************************************************* +// Main + +static volatile mDNSBool StopNow; + +mDNSlocal void HandleSIG(int signal) +{ + (void)signal; // Unused + debugf("%s",""); + debugf("HandleSIG"); + StopNow = mDNStrue; +} + +mDNSexport void ExampleClientEventLoop(mDNS *const m) +{ + signal(SIGINT, HandleSIG); // SIGINT is what you get for a Ctrl-C + signal(SIGTERM, HandleSIG); + + while (!StopNow) + { + int nfds = 0; + fd_set readfds; + struct timeval timeout; + int result; + + // 1. Set up the fd_set as usual here. + // This example client has no file descriptors of its own, + // but a real application would call FD_SET to add them to the set here + FD_ZERO(&readfds); + + // 2. Set up the timeout. + // This example client has no other work it needs to be doing, + // so we set an effectively infinite timeout + timeout.tv_sec = 0x3FFFFFFF; + timeout.tv_usec = 0; + + // 3. Give the mDNSPosix layer a chance to add its information to the fd_set and timeout + mDNSPosixGetFDSet(m, &nfds, &readfds, &timeout); + + // 4. Call select as normal + verbosedebugf("select(%d, %d.%06d)", nfds, timeout.tv_sec, timeout.tv_usec); + result = select(nfds, &readfds, NULL, NULL, &timeout); + + if (result < 0) + { + verbosedebugf("select() returned %d errno %d", result, errno); + if (errno != EINTR) StopNow = mDNStrue; + } + else + { + // 5. Call mDNSPosixProcessFDSet to let the mDNSPosix layer do its work + mDNSPosixProcessFDSet(m, &readfds); + + // 6. This example client has no other work it needs to be doing, + // but a real client would do its work here + // ... (do work) ... + } + } + + debugf("Exiting"); +} diff --git a/mDNSResponder/mDNSPosix/ExampleClientApp.h b/mDNSResponder/mDNSPosix/ExampleClientApp.h new file mode 100644 index 00000000..53f7f171 --- /dev/null +++ b/mDNSResponder/mDNSPosix/ExampleClientApp.h @@ -0,0 +1,18 @@ +/* -*- 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. + */ + +extern void ExampleClientEventLoop(mDNS *const m); diff --git a/mDNSResponder/mDNSPosix/Identify.c b/mDNSResponder/mDNSPosix/Identify.c new file mode 100644 index 00000000..003ac631 --- /dev/null +++ b/mDNSResponder/mDNSPosix/Identify.c @@ -0,0 +1,376 @@ +/* -*- 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. + * + */ + +//************************************************************************************************************* +// Incorporate mDNS.c functionality + +// We want to use the functionality provided by "mDNS.c", +// except we'll sneak a peek at the packets before forwarding them to the normal mDNSCoreReceive() routine +#define mDNSCoreReceive __MDNS__mDNSCoreReceive +#include "mDNS.c" +#undef mDNSCoreReceive + +//************************************************************************************************************* +// Headers + +#include +#include +#include +#include +#include +#include +#include // For n_long, required by below +#include // For IPTOS_LOWDELAY etc. +#include +#include + +#include "mDNSEmbeddedAPI.h" // Defines the interface to the mDNS core code +#include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform +#include "ExampleClientApp.h" + +//************************************************************************************************************* +// Globals + +static mDNS mDNSStorage; // mDNS core uses this to store its globals +static mDNS_PlatformSupport PlatformStorage; // Stores this platform's globals +#define RR_CACHE_SIZE 500 +static CacheEntity gRRCache[RR_CACHE_SIZE]; +mDNSexport const char ProgramName[] = "mDNSIdentify"; + +static volatile int StopNow; // 0 means running, 1 means stop because we got an answer, 2 means stop because of Ctrl-C +static volatile int NumAnswers, NumAddr, NumAAAA, NumHINFO; +static char hostname[MAX_ESCAPED_DOMAIN_NAME], hardware[256], software[256]; +static mDNSAddr lastsrc, hostaddr, target; +static mDNSOpaque16 lastid, id; + +//************************************************************************************************************* +// Utilities + +// Special version of printf that knows how to print IP addresses, DNS-format name strings, etc. +mDNSlocal mDNSu32 mprintf(const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2); +mDNSlocal mDNSu32 mprintf(const char *format, ...) +{ + mDNSu32 length; + unsigned char buffer[512]; + va_list ptr; + va_start(ptr,format); + length = mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr); + va_end(ptr); + printf("%s", buffer); + return(length); +} + +//************************************************************************************************************* +// Main code + +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) +{ + (void)dstaddr; // Unused + // Snag copy of header ID, then call through + lastid = msg->h.id; + lastsrc = *srcaddr; + + // We *want* to allow off-net unicast responses here. + // For now, the simplest way to allow that is to pretend it was received via multicast so that mDNSCore doesn't reject the packet + __MDNS__mDNSCoreReceive(m, msg, end, srcaddr, srcport, &AllDNSLinkGroup_v4, dstport, InterfaceID); +} + +mDNSlocal void NameCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) +{ + (void)m; // Unused + (void)question; // Unused + (void)AddRecord; // Unused + if (!id.NotAnInteger) id = lastid; + if (answer->rrtype == kDNSType_PTR || answer->rrtype == kDNSType_CNAME) + { + ConvertDomainNameToCString(&answer->rdata->u.name, hostname); + StopNow = 1; + mprintf("%##s %s %##s\n", answer->name->c, DNSTypeName(answer->rrtype), answer->rdata->u.name.c); + } +} + +mDNSlocal void InfoCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) +{ + (void)m; // Unused + (void)question; // Unused + (void)AddRecord; // Unused + if (answer->rrtype == kDNSType_A) + { + if (!id.NotAnInteger) id = lastid; + NumAnswers++; + NumAddr++; + mprintf("%##s %s %.4a\n", answer->name->c, DNSTypeName(answer->rrtype), &answer->rdata->u.ipv4); + hostaddr.type = mDNSAddrType_IPv4; // Prefer v4 target to v6 target, for now + hostaddr.ip.v4 = answer->rdata->u.ipv4; + } + else if (answer->rrtype == kDNSType_AAAA) + { + if (!id.NotAnInteger) id = lastid; + NumAnswers++; + NumAAAA++; + mprintf("%##s %s %.16a\n", answer->name->c, DNSTypeName(answer->rrtype), &answer->rdata->u.ipv6); + if (!hostaddr.type) // Prefer v4 target to v6 target, for now + { + hostaddr.type = mDNSAddrType_IPv6; + hostaddr.ip.v6 = answer->rdata->u.ipv6; + } + } + else if (answer->rrtype == kDNSType_HINFO) + { + mDNSu8 *p = answer->rdata->u.data; + strncpy(hardware, (char*)(p+1), p[0]); + hardware[p[0]] = 0; + p += 1 + p[0]; + strncpy(software, (char*)(p+1), p[0]); + software[p[0]] = 0; + NumAnswers++; + NumHINFO++; + } + + // If we've got everything we're looking for, don't need to wait any more + if (/*NumHINFO && */ (NumAddr || NumAAAA)) StopNow = 1; +} + +mDNSlocal void ServicesCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) +{ + (void)m; // Unused + (void)question; // Unused + (void)AddRecord; // Unused + // Right now the mDNSCore targeted-query code is incomplete -- + // it issues targeted queries, but accepts answers from anywhere + // For now, we'll just filter responses here so we don't get confused by responses from someone else + if (answer->rrtype == kDNSType_PTR && mDNSSameAddress(&lastsrc, &target)) + { + NumAnswers++; + mprintf("%##s %s %##s\n", answer->name->c, DNSTypeName(answer->rrtype), answer->rdata->u.name.c); + } +} + +mDNSlocal void WaitForAnswer(mDNS *const m, int seconds) +{ + struct timeval end; + gettimeofday(&end, NULL); + end.tv_sec += seconds; + StopNow = 0; + NumAnswers = 0; + while (!StopNow) + { + int nfds = 0; + fd_set readfds; + struct timeval now, remain = end; + int result; + + FD_ZERO(&readfds); + gettimeofday(&now, NULL); + if (remain.tv_usec < now.tv_usec) { remain.tv_usec += 1000000; remain.tv_sec--; } + if (remain.tv_sec < now.tv_sec) + { + if (!NumAnswers) printf("No response after %d seconds\n", seconds); + return; + } + remain.tv_usec -= now.tv_usec; + remain.tv_sec -= now.tv_sec; + mDNSPosixGetFDSet(m, &nfds, &readfds, &remain); + result = select(nfds, &readfds, NULL, NULL, &remain); + if (result >= 0) mDNSPosixProcessFDSet(m, &readfds); + else if (errno != EINTR) StopNow = 2; + } +} + +mDNSlocal mStatus StartQuery(DNSQuestion *q, char *qname, mDNSu16 qtype, const mDNSAddr *target, mDNSQuestionCallback callback) +{ + lastsrc = zeroAddr; + if (qname) MakeDomainNameFromDNSNameString(&q->qname, qname); + q->InterfaceID = mDNSInterface_Any; + q->flags = 0; + q->Target = target ? *target : zeroAddr; + q->TargetPort = MulticastDNSPort; + q->TargetQID = zeroID; + q->qtype = qtype; + q->qclass = kDNSClass_IN; + q->LongLived = mDNSfalse; + q->ExpectUnique = mDNSfalse; // Don't want to stop after the first response packet + q->ForceMCast = mDNStrue; // Query via multicast, even for apparently uDNS names like 1.1.1.17.in-addr.arpa. + q->ReturnIntermed = mDNStrue; + q->SuppressUnusable = mDNSfalse; + q->SearchListIndex = 0; + q->AppendSearchDomains = 0; + q->RetryWithSearchDomains = mDNSfalse; + q->TimeoutQuestion = 0; + q->ValidationRequired = 0; + q->ValidatingResponse = 0; + q->WakeOnResolve = 0; + q->UseBackgroundTrafficClass = mDNSfalse; + q->ProxyQuestion = 0; + q->qnameOrig = mDNSNULL; + q->AnonInfo = mDNSNULL; + q->pid = mDNSPlatformGetPID(); + q->QuestionCallback = callback; + q->QuestionContext = NULL; + + //mprintf("%##s %s ?\n", q->qname.c, DNSTypeName(qtype)); + return(mDNS_StartQuery(&mDNSStorage, q)); +} + +mDNSlocal void DoOneQuery(DNSQuestion *q, char *qname, mDNSu16 qtype, const mDNSAddr *target, mDNSQuestionCallback callback) +{ + mStatus status = StartQuery(q, qname, qtype, target, callback); + if (status != mStatus_NoError) + StopNow = 2; + else + { + WaitForAnswer(&mDNSStorage, 4); + mDNS_StopQuery(&mDNSStorage, q); + } +} + +mDNSlocal int DoQuery(DNSQuestion *q, char *qname, mDNSu16 qtype, const mDNSAddr *target, mDNSQuestionCallback callback) +{ + DoOneQuery(q, qname, qtype, target, callback); + if (StopNow == 0 && NumAnswers == 0 && target && target->type) + { + mprintf("%##s %s Trying multicast\n", q->qname.c, DNSTypeName(q->qtype)); + DoOneQuery(q, qname, qtype, NULL, callback); + } + if (StopNow == 0 && NumAnswers == 0) + mprintf("%##s %s *** No Answer ***\n", q->qname.c, DNSTypeName(q->qtype)); + return(StopNow); +} + +mDNSlocal void HandleSIG(int signal) +{ + (void)signal; // Unused + debugf("%s",""); + debugf("HandleSIG"); + StopNow = 2; +} + +mDNSexport int main(int argc, char **argv) +{ + const char *progname = strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0]; + int this_arg = 1; + mStatus status; + struct in_addr s4; +#if HAVE_IPV6 + struct in6_addr s6; +#endif + char buffer[256]; + DNSQuestion q; + + if (argc < 2) goto usage; + + // Since this is a special command-line tool, we want LogMsg() errors to go to stderr, not syslog + mDNS_DebugMode = mDNStrue; + + // Initialise the mDNS core. + status = mDNS_Init(&mDNSStorage, &PlatformStorage, + gRRCache, RR_CACHE_SIZE, + mDNS_Init_DontAdvertiseLocalAddresses, + mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext); + if (status) { fprintf(stderr, "Daemon start: mDNS_Init failed %d\n", (int)status); return(status); } + + signal(SIGINT, HandleSIG); // SIGINT is what you get for a Ctrl-C + signal(SIGTERM, HandleSIG); + + while (this_arg < argc) + { + char *arg = argv[this_arg++]; + if (this_arg > 2) printf("\n"); + + lastid = id = zeroID; + hostaddr = target = zeroAddr; + hostname[0] = hardware[0] = software[0] = 0; + NumAddr = NumAAAA = NumHINFO = 0; + + if (inet_pton(AF_INET, arg, &s4) == 1) + { + mDNSu8 *p = (mDNSu8 *)&s4; + // Note: This is reverse order compared to a normal dotted-decimal IP address, so we can't use our customary "%.4a" format code + mDNS_snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d.in-addr.arpa.", p[3], p[2], p[1], p[0]); + printf("%s\n", buffer); + target.type = mDNSAddrType_IPv4; + target.ip.v4.NotAnInteger = s4.s_addr; + DoQuery(&q, buffer, kDNSType_PTR, &target, NameCallback); + if (StopNow == 2) break; + } +#if HAVE_IPV6 + else if (inet_pton(AF_INET6, arg, &s6) == 1) + { + int i; + mDNSu8 *p = (mDNSu8 *)&s6; + for (i = 0; i < 16; i++) + { + static const char hexValues[] = "0123456789ABCDEF"; + buffer[i * 4 ] = hexValues[p[15-i] & 0x0F]; + buffer[i * 4 + 1] = '.'; + buffer[i * 4 + 2] = hexValues[p[15-i] >> 4]; + buffer[i * 4 + 3] = '.'; + } + mDNS_snprintf(&buffer[64], sizeof(buffer)-64, "ip6.arpa."); + target.type = mDNSAddrType_IPv6; + mDNSPlatformMemCopy(&target.ip.v6, &s6, sizeof(target.ip.v6)); + DoQuery(&q, buffer, kDNSType_PTR, &target, NameCallback); + if (StopNow == 2) break; + } +#endif + else { + if (strlen(arg) >= sizeof(hostname)) { + fprintf(stderr, "hostname must be < %d characters\n", (int)sizeof(hostname)); + goto usage; + } + strcpy(hostname, arg); + } + + // Now we have the host name; get its A, AAAA, and HINFO + if (hostname[0]) DoQuery(&q, hostname, kDNSQType_ANY, &target, InfoCallback); + if (StopNow == 2) break; + + if (hardware[0] || software[0]) + { + printf("HINFO Hardware: %s\n", hardware); + printf("HINFO Software: %s\n", software); + } + else if (NumAnswers) printf("%s has no HINFO record\n", hostname); + else printf("Incorrect dot-local hostname, address, or no mDNSResponder running on that machine\n"); + + if (NumAnswers) + { + // Because of the way we use lastsrc in ServicesCallback, we need to clear the cache to make sure we're getting fresh answers + mDNS *const m = &mDNSStorage; + mDNSu32 slot; + CacheGroup *cg; + CacheRecord *rr; + FORALL_CACHERECORDS(slot, cg, rr) + { + mDNS_PurgeCacheResourceRecord(m, rr); + } + if (target.type == 0) target = hostaddr; // Make sure the services query is targeted + DoQuery(&q, "_services._dns-sd._udp.local.", kDNSType_PTR, &target, ServicesCallback); + if (StopNow == 2) break; + } + } + + mDNS_Close(&mDNSStorage); + return(0); + +usage: + fprintf(stderr, "Usage: %s or or ...\n", progname); + return(-1); +} diff --git a/mDNSResponder/mDNSPosix/Makefile b/mDNSResponder/mDNSPosix/Makefile new file mode 100755 index 00000000..d095a0f4 --- /dev/null +++ b/mDNSResponder/mDNSPosix/Makefile @@ -0,0 +1,523 @@ +# -*- tab-width: 4 -*- +# +# Copyright (c) 2002-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. +# +# IMPORTANT NOTE: This is a Makefile for *GNU make* +# On some systems, a different program may be the default "make" command. +# If "make os=xxx" gives lots of errors like "Missing dependency operator", +# then try typing "gmake os=xxx" instead. +# +# This Makefile builds an mDNSResponder daemon and a libdns_sd.so shared library +# for Linux. It also builds several example programs for embedded systems. +# +# Make with no arguments to build all production targets. +# 'make DEBUG=1' to build debugging targets. +# 'make clean' or 'make clean DEBUG=1' to delete prod/debug objects & targets +# 'sudo make install [DEBUG=1]' to install mdnsd daemon and libdns_sd. +# +# Notes: +# $@ means "The file name of the target of the rule" +# $< means "The name of the first prerequisite" +# $* means "The stem with which an implicit rule matches" +# $+ means "The names of all the prerequisites, with spaces between them, exactly as given" +# For more magic automatic variables, see +# + +############################################################################# + +LIBVERS = 1 + +COREDIR = ../mDNSCore +SHAREDDIR ?= ../mDNSShared +JDK = /usr/jdk + +CC = @cc +BISON = @bison +FLEX = @flex +LD = ld -shared +CP = cp +RM = rm +LN = ln -s -f +CFLAGS_COMMON = -I$(COREDIR) -I$(SHAREDDIR) -I$(OBJDIR) -fwrapv -W -Wall -DPID_FILE=\"/var/run/mdnsd.pid\" -DMDNS_UDS_SERVERPATH=\"/var/run/mdnsd\" +CFLAGS_PTHREAD = +LINKOPTS = +LINKOPTS_PTHREAD = -lpthread +LDSUFFIX = so +JAVACFLAGS_OS = -fPIC -shared -ldns_sd + +# Set up diverging paths for debug vs. prod builds +DEBUG=0 +ifeq ($(DEBUG),1) +CFLAGS_DEBUG = -g -DMDNS_DEBUGMSGS=2 +OBJDIR = objects/debug +BUILDDIR = build/debug +STRIP = echo +else +# We use -Os for two reasons: +# 1. We want to make small binaries, suitable for putting into hardware devices +# 2. Some of the code analysis warnings only work when some form of optimization is enabled +CFLAGS_DEBUG = -Os -DMDNS_DEBUGMSGS=0 +OBJDIR ?= objects/prod +BUILDDIR ?= build/prod +STRIP = strip -S +endif + +# Configure per-OS peculiarities +ifeq ($(os),solaris) +CFLAGS_DEBUG = -O0 -DMDNS_DEBUGMSGS=0 +CFLAGS_OS = -DNOT_HAVE_DAEMON -DNOT_HAVE_SA_LEN -DNOT_HAVE_SOCKLEN_T -DNOT_HAVE_IF_NAMETOINDEX \ + -DLOG_PERROR=0 -D_XPG4_2 -D__EXTENSIONS__ -DHAVE_BROKEN_RECVIF_NAME -DTARGET_OS_SOLARIS +CC = gcc +LD = gcc -shared +LINKOPTS = -lsocket -lnsl -lresolv +JAVACFLAGS_OS += -I$(JDK)/include/solaris +ifneq ($(DEBUG),1) +STRIP = strip +endif +else + +# any target that contains the string "linux" +ifeq ($(findstring linux,$(os)),linux) +CFLAGS_OS = -D_GNU_SOURCE -DHAVE_IPV6 -DNOT_HAVE_SA_LEN -DUSES_NETLINK -DHAVE_LINUX -DTARGET_OS_LINUX -fno-strict-aliasing +LD = gcc -shared +FLEXFLAGS_OS = -l +JAVACFLAGS_OS += -I$(JDK)/include/linux + +# uClibc does not support Name Service Switch +ifneq ($(os),linux-uclibc) +OPTIONALTARG = nss_mdns +OPTINSTALL = InstalledNSS +endif +else + +ifeq ($(os),netbsd) +CFLAGS_OS = +LDCONFIG = ldconfig +else + +ifeq ($(os),freebsd) +# If not already defined, set LOCALBASE to /usr/local +LOCALBASE?=/usr/local +INSTBASE=$(LOCALBASE) +CFLAGS_OS = -DHAVE_IPV6 +# FreeBSD 4 requires threaded code to be compiled and linked using the "-pthread" option, +# and requires that the "-lpthread" link option NOT be used +# This appies only to FreeBSD -- "man cc" on FreeBSD says: +# FreeBSD SPECIFIC OPTIONS +# -pthread +# Link a user-threaded process against libc_r instead of libc. +CFLAGS_PTHREAD = -pthread -D_THREAD_SAFE +LINKOPTS_PTHREAD = -pthread +JAVACFLAGS_OS += -I$(JDK)/include/freebsd +LDCONFIG = ldconfig +else + +ifeq ($(os),openbsd) +CFLAGS_OS = -DHAVE_BROKEN_RECVDSTADDR +LDCONFIG = ldconfig +else + +ifeq ($(os),x) +# We have to define __MAC_OS_X_VERSION_MIN_REQUIRED=__MAC_OS_X_VERSION_10_4 or on Leopard +# we get build failures: ‘daemon’ is deprecated (declared at /usr/include/stdlib.h:283) +CFLAGS_OS = -DHAVE_IPV6 -no-cpp-precomp -Werror -Wdeclaration-after-statement \ + -D__MAC_OS_X_VERSION_MIN_REQUIRED=__MAC_OS_X_VERSION_10_4 \ + -D__APPLE_USE_RFC_2292 #-Wunreachable-code +CC = gcc +LD = $(CC) -dynamiclib +LINKOPTS = -lSystem +LDSUFFIX = dylib +JDK = /System/Library/Frameworks/JavaVM.framework/Home +JAVACFLAGS_OS = -dynamiclib -I/System/Library/Frameworks/JavaVM.framework/Headers -framework JavaVM +else + +$(error ERROR: Must specify target OS on command-line, e.g. "make os=x [target]".\ +Supported operating systems include: x, linux, linux-uclibc, netbsd, freebsd, openbsd, solaris) +endif +endif +endif +endif +endif +endif + +NSSLIBNAME := libnss_mdns +NSSVERSION := 0.2 +NSSLIBFILE := $(NSSLIBNAME)-$(NSSVERSION).so +NSSLINKNAME := $(NSSLIBNAME).so.2 +NSSINSTPATH := /lib + +# If not otherwise defined, we install into /usr/lib and /usr/include +# and our startup script is called mdns (e.g. /etc/init.d/mdns) +INSTBASE?=/usr +STARTUPSCRIPTNAME?=mdns + +ifeq ($(HAVE_IPV6),1) +CFLAGS_OS += -DHAVE_IPV6=1 +else +ifeq ($(HAVE_IPV6),0) +CFLAGS_OS += -DHAVE_IPV6=0 +endif +endif + +# If directory /usr/share/man exists, then we install man pages into that, else /usr/man +ifeq ($(wildcard /usr/share/man), /usr/share/man) +MANPATH := /usr/share/man +else +MANPATH := /usr/man +endif + +# If directories /etc/init.d/rc*.d exist, then we install into that (Suse) +ifeq ($(wildcard /etc/init.d/rc2.d/), /etc/init.d/rc2.d/) +STARTUPSCRIPTDIR = /etc/init.d +RUNLEVELSCRIPTSDIR = /etc/init.d +else +# else if directory /etc/rc.d/init.d/ exists, then we install into that (old Linux) +ifeq ($(wildcard /etc/rc.d/init.d/), /etc/rc.d/init.d/) +STARTUPSCRIPTDIR = /etc/rc.d/init.d +RUNLEVELSCRIPTSDIR = /etc/rc.d +else +# else if directory /etc/init.d/ exists, then we install into that (new Linux) +ifeq ($(wildcard /etc/init.d/), /etc/init.d/) +STARTUPSCRIPTDIR = /etc/init.d +RUNLEVELSCRIPTSDIR = /etc +else +# else install into /etc/rc.d/ (*BSD) +STARTUPSCRIPTDIR = $(INSTBASE)/etc/rc.d +endif +endif +endif + +CFLAGS = $(CFLAGS_COMMON) $(CFLAGS_OS) $(CFLAGS_DEBUG) + +############################################################################# + +all: setup Daemon libdns_sd Clients SAClient SAResponder SAProxyResponder Identify NetMonitor dnsextd $(OPTIONALTARG) + +install: setup InstalledDaemon InstalledStartup InstalledLib InstalledManPages InstalledClients $(OPTINSTALL) + +# 'setup' sets up the build directory structure the way we want +setup: + @if test ! -d $(OBJDIR) ; then mkdir -p $(OBJDIR) ; fi + @if test ! -d $(BUILDDIR) ; then mkdir -p $(BUILDDIR) ; fi + +# clean removes targets and objects +clean: + @if test -d $(OBJDIR) ; then rm -r $(OBJDIR) ; fi + @if test -d $(BUILDDIR) ; then rm -r $(BUILDDIR) ; fi + @$(MAKE) -C ../Clients clean + +############################################################################# + +# daemon target builds the daemon +DAEMONOBJS = $(OBJDIR)/PosixDaemon.c.o $(OBJDIR)/mDNSPosix.c.o $(OBJDIR)/mDNSUNP.c.o $(OBJDIR)/mDNS.c.o \ + $(OBJDIR)/DNSDigest.c.o $(OBJDIR)/uDNS.c.o $(OBJDIR)/DNSCommon.c.o $(OBJDIR)/uds_daemon.c.o \ + $(OBJDIR)/mDNSDebug.c.o $(OBJDIR)/dnssd_ipc.c.o $(OBJDIR)/GenLinkedList.c.o $(OBJDIR)/PlatformCommon.c.o \ + $(OBJDIR)/CryptoAlg.c.o $(OBJDIR)/anonymous.c.o + +# dnsextd target build dnsextd +DNSEXTDOBJ = $(OBJDIR)/mDNSPosix.c.o $(OBJDIR)/mDNSUNP.c.o $(OBJDIR)/mDNSDebug.c.o $(OBJDIR)/GenLinkedList.c.o $(OBJDIR)/DNSDigest.c.o \ + $(OBJDIR)/DNSCommon.c.o $(OBJDIR)/PlatformCommon.c.o $(OBJDIR)/dnsextd_parser.y.o $(OBJDIR)/dnsextd_lexer.l.o \ + $(OBJDIR)/CryptoAlg.c.o + +Daemon: setup $(BUILDDIR)/mdnsd + @echo "Responder daemon done" + +$(BUILDDIR)/mdnsd: $(DAEMONOBJS) + $(CC) -o $@ $+ $(LINKOPTS) + @$(STRIP) $@ + +# libdns_sd target builds the client library +libdns_sd: setup $(BUILDDIR)/libdns_sd.$(LDSUFFIX) + @echo "Client library done" + +CLIENTLIBOBJS = $(OBJDIR)/dnssd_clientlib.c.so.o $(OBJDIR)/dnssd_clientstub.c.so.o $(OBJDIR)/dnssd_ipc.c.so.o + +$(BUILDDIR)/libdns_sd.$(LDSUFFIX): $(CLIENTLIBOBJS) + @$(LD) $(LINKOPTS) -o $@ $+ + @$(STRIP) $@ + +Clients: setup libdns_sd ../Clients/build/dns-sd + @echo "Clients done" + +../Clients/build/dns-sd: + @$(MAKE) -C ../Clients + +# nss_mdns target builds the Name Service Switch module +nss_mdns: setup $(BUILDDIR)/$(NSSLIBFILE) + @echo "Name Service Switch module done" + +$(BUILDDIR)/$(NSSLIBFILE): $(CLIENTLIBOBJS) $(OBJDIR)/nss_mdns.c.so.o + @$(LD) $(LINKOPTS) -o $@ $+ + @$(STRIP) $@ + +############################################################################# + +# The Install targets place built stuff in their proper places +InstalledDaemon: $(INSTBASE)/sbin/mdnsd + @echo $+ " installed" + +InstalledLib: $(INSTBASE)/lib/libdns_sd.$(LDSUFFIX).$(LIBVERS) $(INSTBASE)/include/dns_sd.h + @echo $+ " installed" + +InstalledStartup: $(STARTUPSCRIPTDIR)/$(STARTUPSCRIPTNAME) + @echo $+ " installed" + +InstalledManPages: $(MANPATH)/man8/mdnsd.8 + @echo $+ " installed" + +InstalledClients: $(INSTBASE)/bin/dns-sd + @echo $+ " installed" + +InstalledNSS: $(NSSINSTPATH)/$(NSSLINKNAME) /etc/nss_mdns.conf $(MANPATH)/man5/nss_mdns.conf.5 $(MANPATH)/man8/libnss_mdns.8 + @echo $+ " installed" + +# Note: If daemon already installed, we make sure it's stopped before overwriting it +$(INSTBASE)/sbin/mdnsd: $(BUILDDIR)/mdnsd + @if test -x $@; then $(STARTUPSCRIPTDIR)/$(STARTUPSCRIPTNAME) stop; fi + $(CP) $< $@ + +$(INSTBASE)/lib/libdns_sd.$(LDSUFFIX).$(LIBVERS): $(BUILDDIR)/libdns_sd.$(LDSUFFIX) + $(CP) $< $@ + $(LN) $@ $(INSTBASE)/lib/libdns_sd.$(LDSUFFIX) +ifdef LDCONFIG + # -m means 'merge into existing database', -R means 'rescan directories' + $(LDCONFIG) -mR +endif + +$(INSTBASE)/include/dns_sd.h: $(SHAREDDIR)/dns_sd.h + $(CP) $< $@ + +# We make this target dependent on $(INSTBASE)/sbin/mdnsd because we need to ensure +# that the daemon is installed *before* we try to execute the command to start it. +$(STARTUPSCRIPTDIR)/$(STARTUPSCRIPTNAME): mdnsd.sh $(STARTUPSCRIPTDIR) $(INSTBASE)/sbin/mdnsd + $(CP) $< $@ + chmod ugo+x $@ + $@ start +ifdef RUNLEVELSCRIPTSDIR +ifeq ($(wildcard $(RUNLEVELSCRIPTSDIR)/runlevels/default), $(RUNLEVELSCRIPTSDIR)/runlevels/default) + $(LN) $@ $(RUNLEVELSCRIPTSDIR)/runlevels/default/mdns +else + $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc2.d/S52mdns + $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc3.d/S52mdns + $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc4.d/S52mdns + $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc5.d/S52mdns + $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc0.d/K16mdns + $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc6.d/K16mdns +endif +endif + +$(MANPATH)/man5/%.5: %.5 + cp $< $@ + chmod 444 $@ + +$(MANPATH)/man8/%.8: %.8 + cp $< $@ + chmod 444 $@ + +$(MANPATH)/man8/mdnsd.8: $(SHAREDDIR)/mDNSResponder.8 + cp $< $@ + chmod 444 $@ + +$(INSTBASE)/bin/dns-sd: ../Clients/build/dns-sd + $(CP) $< $@ + +$(NSSINSTPATH)/$(NSSLINKNAME): $(NSSINSTPATH)/$(NSSLIBFILE) + $(LN) $< $@ + ldconfig + +$(NSSINSTPATH)/$(NSSLIBFILE): $(BUILDDIR)/$(NSSLIBFILE) + $(CP) $< $@ + chmod 444 $@ + +/etc/nss_mdns.conf: nss_mdns.conf + $(CP) $< $@ + chmod 444 $@ + # Check the nsswitch.conf file. + # If 'mdns' does not already appear on the "hosts:" line, then add it right before 'dns' + cp -f /etc/nsswitch.conf /etc/nsswitch.conf.pre-mdns + sed -e '/mdns/!s/^\(hosts:.*\)dns\(.*\)/\1mdns dns\2/' /etc/nsswitch.conf.pre-mdns > /etc/nsswitch.conf + +############################################################################# + +# The following targets build Java wrappers for the dns-sd.h API. +# Note that the JavaForXcode targets are used when building the project for OS X using Xcode + +JAVAC = $(JDK)/bin/javac +JAVAH = $(JDK)/bin/javah +JAVADOC = $(JDK)/bin/javadoc +JAR = $(JDK)/bin/jar +JAVACFLAGS = $(CFLAGS) $(JAVACFLAGS_OS) -I$(JDK)/include + +JavaForXcode_: setup $(BUILDDIR)/dns_sd.jar $(PROJECT_DERIVED_FILE_DIR)/DNSSD.java.h + @echo $@ done + +$(PROJECT_DERIVED_FILE_DIR)/DNSSD.java.h: $(OBJDIR)/DNSSD.java.h + @if test ! -d $(PROJECT_DERIVED_FILE_DIR) ; then mkdir -p $(PROJECT_DERIVED_FILE_DIR) ; fi + $(CP) $< $@ + +JavaForXcode_clean: + @if test -d $(OBJDIR) ; then rm -r $(OBJDIR) ; fi + @if test -f $(PROJECT_DERIVED_FILE_DIR)/DNSSD.java.h ; then $(RM) $(PROJECT_DERIVED_FILE_DIR)/DNSSD.java.h ; fi + @if test -f $(BUILDDIR)/dns_sd.jar ; then $(RM) $(BUILDDIR)/dns_sd.jar ; fi + @echo $@ done + +JavaForXcode_installhdrs: + @echo $@ NOOP + +JavaForXcode_install: JavaForXcode_ $(DSTROOT)/$(SYSTEM_LIBRARY_DIR)/Java/Extensions/dns_sd.jar + @echo $@ done + +$(DSTROOT)/$(SYSTEM_LIBRARY_DIR)/Java/Extensions/dns_sd.jar: $(BUILDDIR)/dns_sd.jar + @if test ! -d $(DSTROOT)/$(SYSTEM_LIBRARY_DIR)/Java/Extensions ; then mkdir -p $(DSTROOT)/$(SYSTEM_LIBRARY_DIR)/Java/Extensions ; fi + $(CP) $< $@ + +Java: setup $(BUILDDIR)/dns_sd.jar $(BUILDDIR)/libjdns_sd.$(LDSUFFIX) + @echo "Java wrappers done" + +JAVASRC = $(SHAREDDIR)/Java +JARCONTENTS = $(OBJDIR)/com/apple/dnssd/DNSSDService.class \ + $(OBJDIR)/com/apple/dnssd/DNSSDException.class \ + $(OBJDIR)/com/apple/dnssd/DNSRecord.class \ + $(OBJDIR)/com/apple/dnssd/TXTRecord.class \ + $(OBJDIR)/com/apple/dnssd/DNSSDRegistration.class \ + $(OBJDIR)/com/apple/dnssd/BaseListener.class \ + $(OBJDIR)/com/apple/dnssd/BrowseListener.class \ + $(OBJDIR)/com/apple/dnssd/ResolveListener.class \ + $(OBJDIR)/com/apple/dnssd/RegisterListener.class \ + $(OBJDIR)/com/apple/dnssd/QueryListener.class \ + $(OBJDIR)/com/apple/dnssd/DomainListener.class \ + $(OBJDIR)/com/apple/dnssd/RegisterRecordListener.class \ + $(OBJDIR)/com/apple/dnssd/DNSSDRecordRegistrar.class \ + $(OBJDIR)/com/apple/dnssd/DNSSD.class + +$(BUILDDIR)/dns_sd.jar: $(JARCONTENTS) setup + $(JAR) -cf $@ -C $(OBJDIR) com + +$(BUILDDIR)/libjdns_sd.$(LDSUFFIX): $(JAVASRC)/JNISupport.c $(OBJDIR)/DNSSD.java.h setup libdns_sd + $(CC) -o $@ $< $(JAVACFLAGS) -I$(OBJDIR) -L$(BUILDDIR) + +$(OBJDIR)/com/apple/dnssd/%.class: $(JAVASRC)/%.java + $(JAVAC) -d $(OBJDIR) -classpath $(OBJDIR) $< + +$(OBJDIR)/DNSSD.java.h: $(OBJDIR)/com/apple/dnssd/DNSSD.class + $(JAVAH) -force -classpath $(OBJDIR) -o $@ \ + com.apple.dnssd.AppleDNSSD \ + com.apple.dnssd.AppleBrowser \ + com.apple.dnssd.AppleResolver \ + com.apple.dnssd.AppleRegistration \ + com.apple.dnssd.AppleQuery \ + com.apple.dnssd.AppleDomainEnum \ + com.apple.dnssd.AppleService \ + com.apple.dnssd.AppleDNSRecord \ + com.apple.dnssd.AppleRecordRegistrar + +############################################################################# + +# The following target builds documentation for the Java wrappers. + +JavaDoc: Java setup + $(JAVADOC) $(JAVASRC)/*.java -classpath $(OBJDIR) -d $(BUILDDIR) -public + +############################################################################# + +# The following targets build embedded example programs +SPECIALOBJ = $(OBJDIR)/mDNSPosix.c.o $(OBJDIR)/mDNSUNP.c.o $(OBJDIR)/mDNSDebug.c.o $(OBJDIR)/GenLinkedList.c.o \ + $(OBJDIR)/DNSDigest.c.o $(OBJDIR)/uDNS.c.o $(OBJDIR)/DNSCommon.c.o $(OBJDIR)/PlatformCommon.c.o \ + $(OBJDIR)/CryptoAlg.c.o $(OBJDIR)/anonymous.c.o +COMMONOBJ = $(SPECIALOBJ) $(OBJDIR)/mDNS.c.o +APPOBJ = $(COMMONOBJ) $(OBJDIR)/ExampleClientApp.c.o + +SAClient: setup $(BUILDDIR)/mDNSClientPosix + @echo "Embedded Standalone Client done" + +SAResponder: setup $(BUILDDIR)/mDNSResponderPosix + @echo "Embedded Standalone Responder done" + +SAProxyResponder: setup $(BUILDDIR)/mDNSProxyResponderPosix + @echo "Embedded Standalone ProxyResponder done" + +Identify: setup $(BUILDDIR)/mDNSIdentify + @echo "Identify done" + +NetMonitor: setup $(BUILDDIR)/mDNSNetMonitor + @echo "NetMonitor done" + +dnsextd: setup $(BUILDDIR)/dnsextd + @echo "dnsextd done" + +$(BUILDDIR)/mDNSClientPosix: $(APPOBJ) $(OBJDIR)/Client.c.o + $(CC) $+ -o $@ $(LINKOPTS) + +$(BUILDDIR)/mDNSResponderPosix: $(COMMONOBJ) $(OBJDIR)/Responder.c.o + $(CC) $+ -o $@ $(LINKOPTS) + +$(BUILDDIR)/mDNSProxyResponderPosix: $(COMMONOBJ) $(OBJDIR)/ProxyResponder.c.o + $(CC) $+ -o $@ $(LINKOPTS) + +$(BUILDDIR)/mDNSIdentify: $(SPECIALOBJ) $(OBJDIR)/Identify.c.o + $(CC) $+ -o $@ $(LINKOPTS) + +$(OBJDIR)/Identify.c.o: $(COREDIR)/mDNS.c # Note: Identify.c textually imports mDNS.c + +$(BUILDDIR)/mDNSNetMonitor: $(SPECIALOBJ) $(OBJDIR)/NetMonitor.c.o + $(CC) $+ -o $@ $(LINKOPTS) + +$(OBJDIR)/NetMonitor.c.o: $(COREDIR)/mDNS.c # Note: NetMonitor.c textually imports mDNS.c + +$(BUILDDIR)/dnsextd: $(DNSEXTDOBJ) $(OBJDIR)/dnsextd.c.threadsafe.o + $(CC) $+ -o $@ $(LINKOPTS) $(LINKOPTS_PTHREAD) + +############################################################################# + +# Implicit rules +$(OBJDIR)/%.c.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR)/%.c.o: $(COREDIR)/%.c + $(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR)/%.c.o: $(SHAREDDIR)/%.c + $(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR)/%.c.threadsafe.o: %.c + $(CC) $(CFLAGS) $(CFLAGS_PTHREAD) -D_REENTRANT -c -o $@ $< + +$(OBJDIR)/%.c.threadsafe.o: $(SHAREDDIR)/%.c + $(CC) $(CFLAGS) $(CFLAGS_PTHREAD) -D_REENTRANT -c -o $@ $< + +$(OBJDIR)/%.c.so.o: %.c + $(CC) $(CFLAGS) -c -fPIC -o $@ $< + +$(OBJDIR)/%.c.so.o: $(SHAREDDIR)/%.c + $(CC) $(CFLAGS) -c -fPIC -o $@ $< + +$(OBJDIR)/%.y.o: $(SHAREDDIR)/%.y + $(BISON) -o $(OBJDIR)/$*.c -d $< + $(CC) $(CFLAGS) -c -o $@ $(OBJDIR)/$*.c + +$(OBJDIR)/%.l.o: $(SHAREDDIR)/%.l + $(FLEX) $(FLEXFLAGS_OS) -i -o$(OBJDIR)/$*.l.c $< + $(CC) $(CFLAGS) -Wno-error -c -o $@ $(OBJDIR)/$*.l.c diff --git a/mDNSResponder/mDNSPosix/NetMonitor.c b/mDNSResponder/mDNSPosix/NetMonitor.c new file mode 100644 index 00000000..1354f1f8 --- /dev/null +++ b/mDNSResponder/mDNSPosix/NetMonitor.c @@ -0,0 +1,1003 @@ +/* -*- 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. + * + */ + +//************************************************************************************************************* +// Incorporate mDNS.c functionality + +// We want to use much of the functionality provided by "mDNS.c", +// except we'll steal the packets that would be sent to normal mDNSCoreReceive() routine +#define mDNSCoreReceive __NOT__mDNSCoreReceive__NOT__ +#include "mDNS.c" +#undef mDNSCoreReceive + +//************************************************************************************************************* +// Headers + +#include // For printf() +#include // For malloc() +#include // For strrchr(), strcmp() +#include // For "struct tm" etc. +#include // For SIGINT, SIGTERM +#if defined(WIN32) +// Both mDNS.c and mDNSWin32.h declare UDPSocket_struct type resulting in a compile-time error, so +// trick the compiler when including mDNSWin32.h +# define UDPSocket_struct _UDPSocket_struct +# include +# include +# include +# include +# define IFNAMSIZ 256 +static HANDLE gStopEvent = INVALID_HANDLE_VALUE; +static mDNSBool gRunning; +static void CALLBACK StopNotification( HANDLE event, void *context ) { gRunning = mDNSfalse; } +static BOOL WINAPI ConsoleControlHandler( DWORD inControlEvent ) { SetEvent( gStopEvent ); return TRUE; } +void setlinebuf( FILE * fp ) {} +#else +# include // For gethostbyname() +# include // For AF_INET, AF_INET6, etc. +# include // For IF_NAMESIZE +# include // For INADDR_NONE +# include // For inet_addr() +# include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform +#endif +#include "ExampleClientApp.h" + +//************************************************************************************************************* +// Types and structures + +enum +{ + // Primitive operations + OP_probe = 0, + OP_goodbye = 1, + + // These are meta-categories; + // Query and Answer operations are actually subdivided into two classes: + // Browse query/answer and + // Resolve query/answer + OP_query = 2, + OP_answer = 3, + + // The "Browse" variants of query/answer + OP_browsegroup = 2, + OP_browseq = 2, + OP_browsea = 3, + + // The "Resolve" variants of query/answer + OP_resolvegroup = 4, + OP_resolveq = 4, + OP_resolvea = 5, + + OP_NumTypes = 6 +}; + +typedef struct ActivityStat_struct ActivityStat; +struct ActivityStat_struct +{ + ActivityStat *next; + domainname srvtype; + int printed; + int totalops; + int stat[OP_NumTypes]; +}; + +typedef struct FilterList_struct FilterList; +struct FilterList_struct +{ + FilterList *next; + mDNSAddr FilterAddr; +}; + +//************************************************************************************************************* +// Constants + +#define kReportTopServices 15 +#define kReportTopHosts 15 + +//************************************************************************************************************* +// Globals + +mDNS mDNSStorage; // mDNS core uses this to store its globals +static mDNS_PlatformSupport PlatformStorage; // Stores this platform's globals +mDNSexport const char ProgramName[] = "mDNSNetMonitor"; + +struct timeval tv_start, tv_end, tv_interval; +static int FilterInterface = 0; +static FilterList *Filters; +#define ExactlyOneFilter (Filters && !Filters->next) + +static int NumPktQ, NumPktL, NumPktR, NumPktB; // Query/Legacy/Response/Bad +static int NumProbes, NumGoodbyes, NumQuestions, NumLegacy, NumAnswers, NumAdditionals; + +static ActivityStat *stats; + +#define OPBanner "Total Ops Probe Goodbye BrowseQ BrowseA ResolveQ ResolveA" + +//************************************************************************************************************* +// Utilities + +// Special version of printf that knows how to print IP addresses, DNS-format name strings, etc. +mDNSlocal mDNSu32 mprintf(const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2); +mDNSlocal mDNSu32 mprintf(const char *format, ...) +{ + mDNSu32 length; + unsigned char buffer[512]; + va_list ptr; + va_start(ptr,format); + length = mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr); + va_end(ptr); + printf("%s", buffer); + return(length); +} + +//************************************************************************************************************* +// Host Address List +// +// Would benefit from a hash + +typedef enum +{ + HostPkt_Q = 0, // Query + HostPkt_L = 1, // Legacy Query + HostPkt_R = 2, // Response + HostPkt_B = 3, // Bad + HostPkt_NumTypes = 4 +} HostPkt_Type; + +typedef struct +{ + mDNSAddr addr; + unsigned long pkts[HostPkt_NumTypes]; + unsigned long totalops; + unsigned long stat[OP_NumTypes]; + domainname hostname; + domainname revname; + UTF8str255 HIHardware; + UTF8str255 HISoftware; + mDNSu32 NumQueries; + mDNSs32 LastQuery; +} HostEntry; + +#define HostEntryTotalPackets(H) ((H)->pkts[HostPkt_Q] + (H)->pkts[HostPkt_L] + (H)->pkts[HostPkt_R] + (H)->pkts[HostPkt_B]) + +typedef struct +{ + long num; + long max; + HostEntry *hosts; +} HostList; + +static HostList IPv4HostList = { 0, 0, 0 }; +static HostList IPv6HostList = { 0, 0, 0 }; + +mDNSlocal HostEntry *FindHost(const mDNSAddr *addr, HostList *list) +{ + long i; + + for (i = 0; i < list->num; i++) + { + HostEntry *entry = list->hosts + i; + if (mDNSSameAddress(addr, &entry->addr)) + return entry; + } + + return NULL; +} + +mDNSlocal HostEntry *AddHost(const mDNSAddr *addr, HostList *list) +{ + int i; + HostEntry *entry; + if (list->num >= list->max) + { + long newMax = list->max + 64; + HostEntry *newHosts = realloc(list->hosts, newMax * sizeof(HostEntry)); + if (newHosts == NULL) + return NULL; + list->max = newMax; + list->hosts = newHosts; + } + + entry = list->hosts + list->num++; + + entry->addr = *addr; + for (i=0; ipkts[i] = 0; + entry->totalops = 0; + for (i=0; istat[i] = 0; + entry->hostname.c[0] = 0; + entry->revname.c[0] = 0; + entry->HIHardware.c[0] = 0; + entry->HISoftware.c[0] = 0; + entry->NumQueries = 0; + + if (entry->addr.type == mDNSAddrType_IPv4) + { + mDNSv4Addr ip = entry->addr.ip.v4; + char buffer[32]; + // Note: This is reverse order compared to a normal dotted-decimal IP address, so we can't use our customary "%.4a" format code + mDNS_snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d.in-addr.arpa.", ip.b[3], ip.b[2], ip.b[1], ip.b[0]); + MakeDomainNameFromDNSNameString(&entry->revname, buffer); + } + + return(entry); +} + +mDNSlocal HostEntry *GotPacketFromHost(const mDNSAddr *addr, HostPkt_Type t, mDNSOpaque16 id) +{ + if (ExactlyOneFilter) return(NULL); + else + { + HostList *list = (addr->type == mDNSAddrType_IPv4) ? &IPv4HostList : &IPv6HostList; + HostEntry *entry = FindHost(addr, list); + if (!entry) entry = AddHost(addr, list); + if (!entry) return(NULL); + // Don't count our own interrogation packets + if (id.NotAnInteger != 0xFFFF) entry->pkts[t]++; + return(entry); + } +} + +mDNSlocal void RecordHostInfo(HostEntry *entry, const ResourceRecord *const pktrr) +{ + if (!entry->hostname.c[0]) + { + if (pktrr->rrtype == kDNSType_A || pktrr->rrtype == kDNSType_AAAA) + { + // Should really check that the rdata in the address record matches the source address of this packet + entry->NumQueries = 0; + AssignDomainName(&entry->hostname, pktrr->name); + } + + if (pktrr->rrtype == kDNSType_PTR) + if (SameDomainName(&entry->revname, pktrr->name)) + { + entry->NumQueries = 0; + AssignDomainName(&entry->hostname, &pktrr->rdata->u.name); + } + } + else if (pktrr->rrtype == kDNSType_HINFO) + { + RDataBody *rd = &pktrr->rdata->u; + mDNSu8 *rdend = (mDNSu8 *)rd + pktrr->rdlength; + mDNSu8 *hw = rd->txt.c; + mDNSu8 *sw = hw + 1 + (mDNSu32)hw[0]; + if (sw + 1 + sw[0] <= rdend) + { + AssignDomainName(&entry->hostname, pktrr->name); + mDNSPlatformMemCopy(entry->HIHardware.c, hw, 1 + (mDNSu32)hw[0]); + mDNSPlatformMemCopy(entry->HISoftware.c, sw, 1 + (mDNSu32)sw[0]); + } + } +} + +mDNSlocal void SendUnicastQuery(mDNS *const m, HostEntry *entry, domainname *name, mDNSu16 rrtype, mDNSInterfaceID InterfaceID) +{ + const mDNSOpaque16 id = { { 0xFF, 0xFF } }; + DNSMessage query; + mDNSu8 *qptr = query.data; + const mDNSu8 *const limit = query.data + sizeof(query.data); + const mDNSAddr *target = &entry->addr; + InitializeDNSMessage(&query.h, id, QueryFlags); + qptr = putQuestion(&query, qptr, limit, name, rrtype, kDNSClass_IN); + entry->LastQuery = m->timenow; + entry->NumQueries++; + + // Note: When there are multiple mDNSResponder agents running on a single machine + // (e.g. Apple mDNSResponder plus a SliMP3 server with embedded mDNSResponder) + // it is possible that unicast queries may not go to the primary system responder. + // We try the first query using unicast, but if that doesn't work we try again via multicast. + if (entry->NumQueries > 2) + { + target = &AllDNSLinkGroup_v4; + } + else + { + //mprintf("%#a Q\n", target); + InterfaceID = mDNSInterface_Any; // Send query from our unicast reply socket + } + + mDNSSendDNSMessage(&mDNSStorage, &query, qptr, InterfaceID, mDNSNULL, target, MulticastDNSPort, mDNSNULL, mDNSNULL, mDNSfalse); +} + +mDNSlocal void AnalyseHost(mDNS *const m, HostEntry *entry, const mDNSInterfaceID InterfaceID) +{ + // If we've done four queries without answer, give up + if (entry->NumQueries >= 4) return; + + // If we've done a query in the last second, give the host a chance to reply before trying again + if (entry->NumQueries && m->timenow - entry->LastQuery < mDNSPlatformOneSecond) return; + + // If we don't know the host name, try to find that first + if (!entry->hostname.c[0]) + { + if (entry->revname.c[0]) + { + SendUnicastQuery(m, entry, &entry->revname, kDNSType_PTR, InterfaceID); + //mprintf("%##s PTR %d\n", entry->revname.c, entry->NumQueries); + } + } + // If we have the host name but no HINFO, now ask for that + else if (!entry->HIHardware.c[0]) + { + SendUnicastQuery(m, entry, &entry->hostname, kDNSType_HINFO, InterfaceID); + //mprintf("%##s HINFO %d\n", entry->hostname.c, entry->NumQueries); + } +} + +mDNSlocal int CompareHosts(const void *p1, const void *p2) +{ + return (int)(HostEntryTotalPackets((HostEntry *)p2) - HostEntryTotalPackets((HostEntry *)p1)); +} + +mDNSlocal void ShowSortedHostList(HostList *list, int max) +{ + HostEntry *e, *end = &list->hosts[(max < list->num) ? max : list->num]; + qsort(list->hosts, list->num, sizeof(HostEntry), CompareHosts); + if (list->num) mprintf("\n%-25s%s%s\n", "Source Address", OPBanner, " Pkts Query LegacyQ Response"); + for (e = &list->hosts[0]; e < end; e++) + { + int len = mprintf("%#-25a", &e->addr); + if (len > 25) mprintf("\n%25s", ""); + mprintf("%8lu %8lu %8lu %8lu %8lu %8lu %8lu", e->totalops, + e->stat[OP_probe], e->stat[OP_goodbye], + e->stat[OP_browseq], e->stat[OP_browsea], + e->stat[OP_resolveq], e->stat[OP_resolvea]); + mprintf(" %8lu %8lu %8lu %8lu", + HostEntryTotalPackets(e), e->pkts[HostPkt_Q], e->pkts[HostPkt_L], e->pkts[HostPkt_R]); + if (e->pkts[HostPkt_B]) mprintf("Bad: %8lu", e->pkts[HostPkt_B]); + mprintf("\n"); + if (!e->HISoftware.c[0] && e->NumQueries > 2) + mDNSPlatformMemCopy(&e->HISoftware, "\x27*** Unknown (Jaguar, Windows, etc.) ***", 0x28); + if (e->hostname.c[0] || e->HIHardware.c[0] || e->HISoftware.c[0]) + mprintf("%##-45s %#-14s %#s\n", e->hostname.c, e->HIHardware.c, e->HISoftware.c); + } +} + +//************************************************************************************************************* +// Receive and process packets + +mDNSexport mDNSBool ExtractServiceType(const domainname *const fqdn, domainname *const srvtype) +{ + int i, len; + const mDNSu8 *src = fqdn->c; + mDNSu8 *dst = srvtype->c; + + len = *src; + if (len == 0 || len >= 0x40) return(mDNSfalse); + if (src[1] != '_') src += 1 + len; + + len = *src; + if (len == 0 || len >= 0x40 || src[1] != '_') return(mDNSfalse); + for (i=0; i<=len; i++) *dst++ = *src++; + + len = *src; + if (len == 0 || len >= 0x40 || src[1] != '_') return(mDNSfalse); + for (i=0; i<=len; i++) *dst++ = *src++; + + *dst++ = 0; // Put the null root label on the end of the service type + + return(mDNStrue); +} + +mDNSlocal void recordstat(HostEntry *entry, const domainname *fqdn, int op, mDNSu16 rrtype) +{ + ActivityStat **s = &stats; + domainname srvtype; + + if (op != OP_probe) + { + if (rrtype == kDNSType_SRV || rrtype == kDNSType_TXT) op = op - OP_browsegroup + OP_resolvegroup; + else if (rrtype != kDNSType_PTR) return; + } + + if (!ExtractServiceType(fqdn, &srvtype)) return; + + while (*s && !SameDomainName(&(*s)->srvtype, &srvtype)) s = &(*s)->next; + if (!*s) + { + int i; + *s = malloc(sizeof(ActivityStat)); + if (!*s) exit(-1); + (*s)->next = NULL; + (*s)->srvtype = srvtype; + (*s)->printed = 0; + (*s)->totalops = 0; + for (i=0; istat[i] = 0; + } + + (*s)->totalops++; + (*s)->stat[op]++; + if (entry) + { + entry->totalops++; + entry->stat[op]++; + } +} + +mDNSlocal void printstats(int max) +{ + int i; + if (!stats) return; + for (i=0; inext) + if (!s->printed && max < s->totalops) + { m = s; max = s->totalops; } + if (!m) return; + m->printed = mDNStrue; + if (i==0) mprintf("%-25s%s\n", "Service Type", OPBanner); + mprintf("%##-25s%8d %8d %8d %8d %8d %8d %8d\n", m->srvtype.c, m->totalops, m->stat[OP_probe], + m->stat[OP_goodbye], m->stat[OP_browseq], m->stat[OP_browsea], m->stat[OP_resolveq], m->stat[OP_resolvea]); + } +} + +mDNSlocal const mDNSu8 *FindUpdate(mDNS *const m, const DNSMessage *const query, const mDNSu8 *ptr, const mDNSu8 *const end, + DNSQuestion *q, LargeCacheRecord *pkt) +{ + int i; + for (i = 0; i < query->h.numAuthorities; i++) + { + const mDNSu8 *p2 = ptr; + ptr = GetLargeResourceRecord(m, query, ptr, end, q->InterfaceID, kDNSRecordTypePacketAuth, pkt); + if (!ptr) break; + if (m->rec.r.resrec.RecordType != kDNSRecordTypePacketNegative && ResourceRecordAnswersQuestion(&pkt->r.resrec, q)) return(p2); + } + return(mDNSNULL); +} + +mDNSlocal void DisplayPacketHeader(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSInterfaceID InterfaceID) +{ + const char *const ptype = (msg->h.flags.b[0] & kDNSFlag0_QR_Response) ? "-R- " : + (srcport.NotAnInteger == MulticastDNSPort.NotAnInteger) ? "-Q- " : "-LQ-"; + + struct timeval tv; + struct tm tm; + const mDNSu32 index = mDNSPlatformInterfaceIndexfromInterfaceID(m, InterfaceID, mDNSfalse); + char if_name[IFNAMSIZ]; // Older Linux distributions don't define IF_NAMESIZE + if_indextoname(index, if_name); + gettimeofday(&tv, NULL); + localtime_r((time_t*)&tv.tv_sec, &tm); + mprintf("\n%d:%02d:%02d.%06d Interface %d/%s\n", tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec, index, if_name); + + mprintf("%#-16a %s Q:%3d Ans:%3d Auth:%3d Add:%3d Size:%5d bytes", + srcaddr, ptype, msg->h.numQuestions, msg->h.numAnswers, msg->h.numAuthorities, msg->h.numAdditionals, end - (mDNSu8 *)msg); + + if (msg->h.id.NotAnInteger) mprintf(" ID:%u", mDNSVal16(msg->h.id)); + + if (!mDNSAddrIsDNSMulticast(dstaddr)) mprintf(" To: %#a", dstaddr); + + if (msg->h.flags.b[0] & kDNSFlag0_TC) + { + if (msg->h.flags.b[0] & kDNSFlag0_QR_Response) mprintf(" Truncated"); + else mprintf(" Truncated (KA list continues in next packet)"); + } + mprintf("\n"); +} + +mDNSlocal void DisplayResourceRecord(const mDNSAddr *const srcaddr, const char *const op, const ResourceRecord *const pktrr) +{ + static const char hexchars[16] = "0123456789ABCDEF"; + #define MaxWidth 132 + char buffer[MaxWidth+8]; + char *p = buffer; + + RDataBody *rd = &pktrr->rdata->u; + mDNSu8 *rdend = (mDNSu8 *)rd + pktrr->rdlength; + int n = mprintf("%#-16a %-5s %-5s%5lu %##s -> ", srcaddr, op, DNSTypeName(pktrr->rrtype), pktrr->rroriginalttl, pktrr->name->c); + + if (pktrr->RecordType == kDNSRecordTypePacketNegative) { mprintf("**** ERROR: FAILED TO READ RDATA ****\n"); return; } + + // The kDNSType_OPT case below just calls GetRRDisplayString_rdb + // Perhaps more (or all?) of the cases should do that? + switch(pktrr->rrtype) + { + case kDNSType_A: n += mprintf("%.4a", &rd->ipv4); break; + case kDNSType_PTR: n += mprintf("%##.*s", MaxWidth - n, rd->name.c); break; + case kDNSType_HINFO: // same as kDNSType_TXT below + case kDNSType_TXT: { + mDNSu8 *t = rd->txt.c; + while (t < rdend && t[0] && p < buffer+MaxWidth) + { + int i; + for (i=1; i<=t[0] && p < buffer+MaxWidth; i++) + { + if (t[i] == '\\') *p++ = '\\'; + if (t[i] >= ' ') *p++ = t[i]; + else + { + *p++ = '\\'; + *p++ = '0'; + *p++ = 'x'; + *p++ = hexchars[t[i] >> 4]; + *p++ = hexchars[t[i] & 0xF]; + } + } + t += 1+t[0]; + if (t < rdend && t[0]) { *p++ = '\\'; *p++ = ' '; } + } + *p++ = 0; + n += mprintf("%.*s", MaxWidth - n, buffer); + } break; + case kDNSType_AAAA: n += mprintf("%.16a", &rd->ipv6); break; + case kDNSType_SRV: n += mprintf("%##s:%d", rd->srv.target.c, mDNSVal16(rd->srv.port)); break; + case kDNSType_OPT: { + char b[MaxMsg]; + // Quick hack: we don't want the prefix that GetRRDisplayString_rdb puts at the start of its + // string, because it duplicates the name and rrtype we already display, so we compute the + // length of that prefix and strip that many bytes off the beginning of the string we display. + mDNSu32 striplen = mDNS_snprintf(b, MaxMsg-1, "%4d %##s %s ", pktrr->rdlength, pktrr->name->c, DNSTypeName(pktrr->rrtype)); + GetRRDisplayString_rdb(pktrr, &pktrr->rdata->u, b); + n += mprintf("%.*s", MaxWidth - n, b + striplen); + } break; + case kDNSType_NSEC: { + char b[MaxMsg]; + // See the quick hack above + mDNSu32 striplen = mDNS_snprintf(b, MaxMsg-1, "%4d %##s %s ", pktrr->rdlength, pktrr->name->c, DNSTypeName(pktrr->rrtype)); + GetRRDisplayString_rdb(pktrr, &pktrr->rdata->u, b); + n += mprintf("%s", b+striplen); + } break; + default: { + mDNSu8 *s = rd->data; + while (s < rdend && p < buffer+MaxWidth) + { + if (*s == '\\') *p++ = '\\'; + if (*s >= ' ') *p++ = *s; + else + { + *p++ = '\\'; + *p++ = '0'; + *p++ = 'x'; + *p++ = hexchars[*s >> 4]; + *p++ = hexchars[*s & 0xF]; + } + s++; + } + *p++ = 0; + n += mprintf("%.*s", MaxWidth - n, buffer); + } break; + } + + mprintf("\n"); +} + +mDNSlocal void HexDump(const mDNSu8 *ptr, const mDNSu8 *const end) +{ + while (ptr < end) + { + int i; + for (i=0; i<16; i++) + if (&ptr[i] < end) mprintf("%02X ", ptr[i]); + else mprintf(" "); + for (i=0; i<16; i++) + if (&ptr[i] < end) mprintf("%c", ptr[i] <= ' ' || ptr[i] >= 126 ? '.' : ptr[i]); + ptr += 16; + mprintf("\n"); + } +} + +mDNSlocal void DisplayError(const mDNSAddr *srcaddr, const mDNSu8 *ptr, const mDNSu8 *const end, char *msg) +{ + mprintf("%#-16a **** ERROR: FAILED TO READ %s **** \n", srcaddr, msg); + HexDump(ptr, end); +} + +mDNSlocal void DisplayQuery(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *const end, + const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSInterfaceID InterfaceID) +{ + int i; + const mDNSu8 *ptr = msg->data; + const mDNSu8 *auth = LocateAuthorities(msg, end); + mDNSBool MQ = (srcport.NotAnInteger == MulticastDNSPort.NotAnInteger); + HostEntry *entry = GotPacketFromHost(srcaddr, MQ ? HostPkt_Q : HostPkt_L, msg->h.id); + LargeCacheRecord pkt; + + DisplayPacketHeader(m, msg, end, srcaddr, srcport, dstaddr, InterfaceID); + if (msg->h.id.NotAnInteger != 0xFFFF) + { + if (MQ) NumPktQ++;else NumPktL++; + } + + for (i=0; ih.numQuestions; i++) + { + DNSQuestion q; + mDNSu8 *p2 = (mDNSu8 *)getQuestion(msg, ptr, end, InterfaceID, &q); + mDNSu16 ucbit = q.qclass & kDNSQClass_UnicastResponse; + q.qclass &= ~kDNSQClass_UnicastResponse; + if (!p2) { DisplayError(srcaddr, ptr, end, "QUESTION"); return; } + ptr = p2; + p2 = (mDNSu8 *)FindUpdate(m, msg, auth, end, &q, &pkt); + if (p2) + { + NumProbes++; + DisplayResourceRecord(srcaddr, ucbit ? "(PU)" : "(PM)", &pkt.r.resrec); + recordstat(entry, &q.qname, OP_probe, q.qtype); + p2 = (mDNSu8 *)skipDomainName(msg, p2, end); + // Having displayed this update record, clear type and class so we don't display the same one again. + p2[0] = p2[1] = p2[2] = p2[3] = 0; + } + else + { + const char *ptype = ucbit ? "(QU)" : "(QM)"; + if (srcport.NotAnInteger == MulticastDNSPort.NotAnInteger) NumQuestions++; + else { NumLegacy++; ptype = "(LQ)"; } + mprintf("%#-16a %-5s %-5s %##s\n", srcaddr, ptype, DNSTypeName(q.qtype), q.qname.c); + if (msg->h.id.NotAnInteger != 0xFFFF) recordstat(entry, &q.qname, OP_query, q.qtype); + } + } + + for (i=0; ih.numAnswers; i++) + { + const mDNSu8 *ep = ptr; + ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAns, &pkt); + if (!ptr) { DisplayError(srcaddr, ep, end, "KNOWN ANSWER"); return; } + DisplayResourceRecord(srcaddr, "(KA)", &pkt.r.resrec); + + // In the case of queries with long multi-packet KA lists, we count each subsequent KA packet + // the same as a single query, to more accurately reflect the burden on the network + // (A query with a six-packet KA list is *at least* six times the burden on the network as a single-packet query.) + if (msg->h.numQuestions == 0 && i == 0) + recordstat(entry, pkt.r.resrec.name, OP_query, pkt.r.resrec.rrtype); + } + + for (i=0; ih.numAuthorities; i++) + { + const mDNSu8 *ep = ptr; + ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAuth, &pkt); + if (!ptr) { DisplayError(srcaddr, ep, end, "AUTHORITY"); return; } + // After we display an Update record with its matching question (above) we zero out its type and class + // If any remain that haven't been zero'd out, display them here + if (pkt.r.resrec.rrtype || pkt.r.resrec.rrclass) DisplayResourceRecord(srcaddr, "(AU)", &pkt.r.resrec); + } + + for (i=0; ih.numAdditionals; i++) + { + const mDNSu8 *ep = ptr; + ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAdd, &pkt); + if (!ptr) { DisplayError(srcaddr, ep, end, "ADDITIONAL"); return; } + DisplayResourceRecord(srcaddr, pkt.r.resrec.rrtype == kDNSType_OPT ? "(OP)" : "(AD)", &pkt.r.resrec); + } + + if (entry) AnalyseHost(m, entry, InterfaceID); +} + +mDNSlocal void DisplayResponse(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *end, + const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSInterfaceID InterfaceID) +{ + int i; + const mDNSu8 *ptr = msg->data; + HostEntry *entry = GotPacketFromHost(srcaddr, HostPkt_R, msg->h.id); + LargeCacheRecord pkt; + + DisplayPacketHeader(m, msg, end, srcaddr, srcport, dstaddr, InterfaceID); + if (msg->h.id.NotAnInteger != 0xFFFF) NumPktR++; + + for (i=0; ih.numQuestions; i++) + { + DNSQuestion q; + const mDNSu8 *ep = ptr; + ptr = getQuestion(msg, ptr, end, InterfaceID, &q); + if (!ptr) { DisplayError(srcaddr, ep, end, "QUESTION"); return; } + if (mDNSAddrIsDNSMulticast(dstaddr)) + mprintf("%#-16a (?) **** ERROR: SHOULD NOT HAVE Q IN mDNS RESPONSE **** %-5s %##s\n", srcaddr, DNSTypeName(q.qtype), q.qname.c); + else + mprintf("%#-16a (Q) %-5s %##s\n", srcaddr, DNSTypeName(q.qtype), q.qname.c); + } + + for (i=0; ih.numAnswers; i++) + { + const mDNSu8 *ep = ptr; + ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAns, &pkt); + if (!ptr) { DisplayError(srcaddr, ep, end, "ANSWER"); return; } + if (pkt.r.resrec.rroriginalttl) + { + NumAnswers++; + DisplayResourceRecord(srcaddr, (pkt.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask) ? "(AN)" : "(AN+)", &pkt.r.resrec); + if (msg->h.id.NotAnInteger != 0xFFFF) recordstat(entry, pkt.r.resrec.name, OP_answer, pkt.r.resrec.rrtype); + if (entry) RecordHostInfo(entry, &pkt.r.resrec); + } + else + { + NumGoodbyes++; + DisplayResourceRecord(srcaddr, "(DE)", &pkt.r.resrec); + recordstat(entry, pkt.r.resrec.name, OP_goodbye, pkt.r.resrec.rrtype); + } + } + + for (i=0; ih.numAuthorities; i++) + { + const mDNSu8 *ep = ptr; + ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAuth, &pkt); + if (!ptr) { DisplayError(srcaddr, ep, end, "AUTHORITY"); return; } + if (pkt.r.resrec.rrtype != kDNSType_NSEC3) + mprintf("%#-16a (?) **** ERROR: SHOULD NOT HAVE AUTHORITY IN mDNS RESPONSE **** %-5s %##s\n", + srcaddr, DNSTypeName(pkt.r.resrec.rrtype), pkt.r.resrec.name->c); + } + + for (i=0; ih.numAdditionals; i++) + { + const mDNSu8 *ep = ptr; + ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAdd, &pkt); + if (!ptr) { DisplayError(srcaddr, ep, end, "ADDITIONAL"); return; } + NumAdditionals++; + DisplayResourceRecord(srcaddr, + pkt.r.resrec.rrtype == kDNSType_OPT ? "(OP)" : (pkt.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask) ? "(AD)" : "(AD+)", + &pkt.r.resrec); + if (entry) RecordHostInfo(entry, &pkt.r.resrec); + } + + if (entry) AnalyseHost(m, entry, InterfaceID); +} + +mDNSlocal void ProcessUnicastResponse(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *end, const mDNSAddr *srcaddr, const mDNSInterfaceID InterfaceID) +{ + int i; + const mDNSu8 *ptr = LocateAnswers(msg, end); + HostEntry *entry = GotPacketFromHost(srcaddr, HostPkt_R, msg->h.id); + //mprintf("%#a R\n", srcaddr); + + for (i=0; ih.numAnswers + msg->h.numAuthorities + msg->h.numAdditionals; i++) + { + LargeCacheRecord pkt; + ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAns, &pkt); + if (ptr && pkt.r.resrec.rroriginalttl && entry) RecordHostInfo(entry, &pkt.r.resrec); + } +} + +mDNSlocal mDNSBool AddressMatchesFilterList(const mDNSAddr *srcaddr) +{ + FilterList *f; + if (!Filters) return(srcaddr->type == mDNSAddrType_IPv4); + for (f=Filters; f; f=f->next) if (mDNSSameAddress(srcaddr, &f->FilterAddr)) return(mDNStrue); + return(mDNSfalse); +} + +mDNSexport void mDNSCoreReceive(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end, + const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSAddr *dstaddr, mDNSIPPort dstport, const mDNSInterfaceID InterfaceID) +{ + 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); + mDNSu8 *ptr = (mDNSu8 *)&msg->h.numQuestions; + int goodinterface = (FilterInterface == 0); + + (void)dstaddr; // Unused + (void)dstport; // Unused + + // Read the integer parts which are in IETF byte-order (MSB first, LSB second) + 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]); + + // For now we're only interested in monitoring IPv4 traffic. + // All IPv6 packets should just be duplicates of the v4 packets. + if (!goodinterface) goodinterface = (FilterInterface == (int)mDNSPlatformInterfaceIndexfromInterfaceID(m, InterfaceID, mDNSfalse)); + if (goodinterface && AddressMatchesFilterList(srcaddr)) + { + mDNS_Lock(m); + if (!mDNSAddrIsDNSMulticast(dstaddr)) + { + if (QR_OP == StdQ) mprintf("Unicast query from %#a\n", srcaddr); + else if (QR_OP == StdR) ProcessUnicastResponse(m, msg, end, srcaddr, InterfaceID); + } + else + { + if (QR_OP == StdQ) DisplayQuery (m, msg, end, srcaddr, srcport, dstaddr, InterfaceID); + else if (QR_OP == StdR) DisplayResponse (m, msg, end, srcaddr, srcport, dstaddr, InterfaceID); + else + { + debugf("Unknown DNS packet type %02X%02X (ignored)", msg->h.flags.b[0], msg->h.flags.b[1]); + GotPacketFromHost(srcaddr, HostPkt_B, msg->h.id); + NumPktB++; + } + } + mDNS_Unlock(m); + } +} + +mDNSlocal mStatus mDNSNetMonitor(void) +{ + struct tm tm; + int h, m, s, mul, div, TotPkt; +#if !defined(WIN32) + sigset_t signals; +#endif + + mStatus status = mDNS_Init(&mDNSStorage, &PlatformStorage, + mDNS_Init_NoCache, mDNS_Init_ZeroCacheSize, + mDNS_Init_DontAdvertiseLocalAddresses, + mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext); + if (status) return(status); + + gettimeofday(&tv_start, NULL); + +#if defined( WIN32 ) + status = SetupInterfaceList(&mDNSStorage); + if (status) return(status); + gStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (gStopEvent == INVALID_HANDLE_VALUE) return mStatus_UnknownErr; + mDNSPollRegisterEvent( gStopEvent, StopNotification, NULL ); + if (!SetConsoleCtrlHandler(ConsoleControlHandler, TRUE)) return mStatus_UnknownErr; + gRunning = mDNStrue; while (gRunning) mDNSPoll( INFINITE ); + if (!SetConsoleCtrlHandler(ConsoleControlHandler, FALSE)) return mStatus_UnknownErr; + CloseHandle(gStopEvent); +#else + mDNSPosixListenForSignalInEventLoop(SIGINT); + mDNSPosixListenForSignalInEventLoop(SIGTERM); + + do + { + struct timeval timeout = { 0x3FFFFFFF, 0 }; // wait until SIGINT or SIGTERM + mDNSBool gotSomething; + mDNSPosixRunEventLoopOnce(&mDNSStorage, &timeout, &signals, &gotSomething); + } + while ( !( sigismember( &signals, SIGINT) || sigismember( &signals, SIGTERM))); +#endif + + // Now display final summary + TotPkt = NumPktQ + NumPktL + NumPktR; + gettimeofday(&tv_end, NULL); + tv_interval = tv_end; + if (tv_start.tv_usec > tv_interval.tv_usec) + { tv_interval.tv_usec += 1000000; tv_interval.tv_sec--; } + tv_interval.tv_sec -= tv_start.tv_sec; + tv_interval.tv_usec -= tv_start.tv_usec; + h = (tv_interval.tv_sec / 3600); + m = (tv_interval.tv_sec % 3600) / 60; + s = (tv_interval.tv_sec % 60); + if (tv_interval.tv_sec > 10) + { + mul = 60; + div = tv_interval.tv_sec; + } + else + { + mul = 60000; + div = tv_interval.tv_sec * 1000 + tv_interval.tv_usec / 1000; + if (div == 0) div=1; + } + + mprintf("\n\n"); + localtime_r((time_t*)&tv_start.tv_sec, &tm); + mprintf("Started %3d:%02d:%02d.%06d\n", tm.tm_hour, tm.tm_min, tm.tm_sec, tv_start.tv_usec); + localtime_r((time_t*)&tv_end.tv_sec, &tm); + mprintf("End %3d:%02d:%02d.%06d\n", tm.tm_hour, tm.tm_min, tm.tm_sec, tv_end.tv_usec); + mprintf("Captured for %3d:%02d:%02d.%06d\n", h, m, s, tv_interval.tv_usec); + if (!Filters) + { + mprintf("Unique source addresses seen on network:"); + if (IPv4HostList.num) mprintf(" %ld (IPv4)", IPv4HostList.num); + if (IPv6HostList.num) mprintf(" %ld (IPv6)", IPv6HostList.num); + if (!IPv4HostList.num && !IPv6HostList.num) mprintf(" None"); + mprintf("\n"); + } + mprintf("\n"); + mprintf("Modern Query Packets: %7d (avg%5d/min)\n", NumPktQ, NumPktQ * mul / div); + mprintf("Legacy Query Packets: %7d (avg%5d/min)\n", NumPktL, NumPktL * mul / div); + mprintf("Multicast Response Packets: %7d (avg%5d/min)\n", NumPktR, NumPktR * mul / div); + mprintf("Total Multicast Packets: %7d (avg%5d/min)\n", TotPkt, TotPkt * mul / div); + mprintf("\n"); + mprintf("Total New Service Probes: %7d (avg%5d/min)\n", NumProbes, NumProbes * mul / div); + mprintf("Total Goodbye Announcements: %7d (avg%5d/min)\n", NumGoodbyes, NumGoodbyes * mul / div); + mprintf("Total Query Questions: %7d (avg%5d/min)\n", NumQuestions, NumQuestions * mul / div); + mprintf("Total Queries from Legacy Clients:%7d (avg%5d/min)\n", NumLegacy, NumLegacy * mul / div); + mprintf("Total Answers/Announcements: %7d (avg%5d/min)\n", NumAnswers, NumAnswers * mul / div); + mprintf("Total Additional Records: %7d (avg%5d/min)\n", NumAdditionals, NumAdditionals * mul / div); + mprintf("\n"); + printstats(kReportTopServices); + + if (!ExactlyOneFilter) + { + ShowSortedHostList(&IPv4HostList, kReportTopHosts); + ShowSortedHostList(&IPv6HostList, kReportTopHosts); + } + + mDNS_Close(&mDNSStorage); + return(0); +} + +mDNSexport int main(int argc, char **argv) +{ + const char *progname = strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0]; + int i; + mStatus status; + +#if defined(WIN32) + HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); +#endif + + setlinebuf(stdout); // Want to see lines as they appear, not block buffered + + for (i=1; ih_addr; + else goto usage; + } + + f = malloc(sizeof(*f)); + f->FilterAddr = a; + f->next = Filters; + Filters = f; + } + } + + status = mDNSNetMonitor(); + if (status) { fprintf(stderr, "%s: mDNSNetMonitor failed %d\n", progname, (int)status); return(status); } + return(0); + +usage: + fprintf(stderr, "\nmDNS traffic monitor\n"); + fprintf(stderr, "Usage: %s [-i index] [host]\n", progname); + fprintf(stderr, "Optional [-i index] parameter displays only packets from that interface index\n"); + fprintf(stderr, "Optional [host] parameter displays only packets from that host\n"); + + fprintf(stderr, "\nPer-packet header output:\n"); + fprintf(stderr, "-Q- Multicast Query from mDNS client that accepts multicast responses\n"); + fprintf(stderr, "-R- Multicast Response packet containing answers/announcements\n"); + fprintf(stderr, "-LQ- Multicast Query from legacy client that does *not* listen for multicast responses\n"); + fprintf(stderr, "Q/Ans/Auth/Add Number of questions, answers, authority records and additional records in packet\n"); + + fprintf(stderr, "\nPer-record display:\n"); + fprintf(stderr, "(PM) Probe Question (new service starting), requesting multicast response\n"); + fprintf(stderr, "(PU) Probe Question (new service starting), requesting unicast response\n"); + fprintf(stderr, "(DE) Deletion/Goodbye (service going away)\n"); + fprintf(stderr, "(LQ) Legacy Query Question\n"); + fprintf(stderr, "(QM) Query Question, requesting multicast response\n"); + fprintf(stderr, "(QU) Query Question, requesting unicast response\n"); + fprintf(stderr, "(KA) Known Answer (information querier already knows)\n"); + fprintf(stderr, "(AN) Unique Answer to question (or periodic announcment) (entire RR Set)\n"); + fprintf(stderr, "(AN+) Answer to question (or periodic announcment) (add to existing RR Set members)\n"); + fprintf(stderr, "(AD) Unique Additional Record Set (entire RR Set)\n"); + fprintf(stderr, "(AD+) Additional records (add to existing RR Set members)\n"); + + fprintf(stderr, "\nFinal summary, sorted by service type:\n"); + fprintf(stderr, "Probe Probes for this service type starting up\n"); + fprintf(stderr, "Goodbye Goodbye (deletion) packets for this service type shutting down\n"); + fprintf(stderr, "BrowseQ Browse questions from clients browsing to find a list of instances of this service\n"); + fprintf(stderr, "BrowseA Browse answers/announcments advertising instances of this service\n"); + fprintf(stderr, "ResolveQ Resolve questions from clients actively connecting to an instance of this service\n"); + fprintf(stderr, "ResolveA Resolve answers/announcments giving connection information for an instance of this service\n"); + fprintf(stderr, "\n"); + return(-1); +} diff --git a/mDNSResponder/mDNSPosix/PosixDaemon.c b/mDNSResponder/mDNSPosix/PosixDaemon.c new file mode 100644 index 00000000..88b3292c --- /dev/null +++ b/mDNSResponder/mDNSPosix/PosixDaemon.c @@ -0,0 +1,258 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-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. + + File: daemon.c + + Contains: main & associated Application layer for mDNSResponder on Linux. + + */ + +#if __APPLE__ +// In Mac OS X 10.5 and later trying to use the daemon function gives a “‘daemon’ is deprecated” +// error, which prevents compilation because we build with "-Werror". +// Since this is supposed to be portable cross-platform code, we don't care that daemon is +// deprecated on Mac OS X 10.5, so we use this preprocessor trick to eliminate the error message. +#define daemon yes_we_know_that_daemon_is_deprecated_in_os_x_10_5_thankyou +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __APPLE__ +#undef daemon +extern int daemon(int, int); +#endif + +#include "mDNSEmbeddedAPI.h" +#include "mDNSPosix.h" +#include "mDNSUNP.h" // For daemon() +#include "uds_daemon.h" +#include "PlatformCommon.h" + +#define CONFIG_FILE "/etc/mdnsd.conf" +static domainname DynDNSZone; // Default wide-area zone for service registration +static domainname DynDNSHostname; + +#define RR_CACHE_SIZE 500 +static CacheEntity gRRCache[RR_CACHE_SIZE]; +static mDNS_PlatformSupport PlatformStorage; + +mDNSlocal void mDNS_StatusCallback(mDNS *const m, mStatus result) +{ + (void)m; // Unused + if (result == mStatus_NoError) + { + // On successful registration of dot-local mDNS host name, daemon may want to check if + // any name conflict and automatic renaming took place, and if so, record the newly negotiated + // name in persistent storage for next time. It should also inform the user of the name change. + // On Mac OS X we store the current dot-local mDNS host name in the SCPreferences store, + // and notify the user with a CFUserNotification. + } + else if (result == mStatus_ConfigChanged) + { + udsserver_handle_configchange(m); + } + else if (result == mStatus_GrowCache) + { + // Allocate another chunk of cache storage + CacheEntity *storage = malloc(sizeof(CacheEntity) * RR_CACHE_SIZE); + if (storage) mDNS_GrowCache(m, storage, RR_CACHE_SIZE); + } +} + +// %%% Reconfigure() probably belongs in the platform support layer (mDNSPosix.c), not the daemon cde +// -- all client layers running on top of mDNSPosix.c need to handle network configuration changes, +// not only the Unix Domain Socket Daemon + +static void Reconfigure(mDNS *m) +{ + mDNSAddr DynDNSIP; + const mDNSAddr dummy = { mDNSAddrType_IPv4, { { { 1, 1, 1, 1 } } } };; + mDNS_SetPrimaryInterfaceInfo(m, NULL, NULL, NULL); + if (ParseDNSServers(m, uDNS_SERVERS_FILE) < 0) + LogMsg("Unable to parse DNS server list. Unicast DNS-SD unavailable"); + ReadDDNSSettingsFromConfFile(m, CONFIG_FILE, &DynDNSHostname, &DynDNSZone, NULL); + mDNSPlatformSourceAddrForDest(&DynDNSIP, &dummy); + if (DynDNSHostname.c[0]) mDNS_AddDynDNSHostName(m, &DynDNSHostname, NULL, NULL); + if (DynDNSIP.type) mDNS_SetPrimaryInterfaceInfo(m, &DynDNSIP, NULL, NULL); + mDNS_ConfigChanged(m); +} + +// Do appropriate things at startup with command line arguments. Calls exit() if unhappy. +mDNSlocal void ParseCmdLinArgs(int argc, char **argv) +{ + if (argc > 1) + { + if (0 == strcmp(argv[1], "-debug")) mDNS_DebugMode = mDNStrue; + else printf("Usage: %s [-debug]\n", argv[0]); + } + + if (!mDNS_DebugMode) + { + int result = daemon(0, 0); + if (result != 0) { LogMsg("Could not run as daemon - exiting"); exit(result); } +#if __APPLE__ + LogMsg("The POSIX mdnsd should only be used on OS X for testing - exiting"); + exit(-1); +#endif + } +} + +mDNSlocal void DumpStateLog(mDNS *const m) +// Dump a little log of what we've been up to. +{ + LogMsg("---- BEGIN STATE LOG ----"); + udsserver_info(m); + LogMsg("---- END STATE LOG ----"); +} + +mDNSlocal mStatus MainLoop(mDNS *m) // Loop until we quit. +{ + sigset_t signals; + mDNSBool gotData = mDNSfalse; + + mDNSPosixListenForSignalInEventLoop(SIGINT); + mDNSPosixListenForSignalInEventLoop(SIGTERM); + mDNSPosixListenForSignalInEventLoop(SIGUSR1); + mDNSPosixListenForSignalInEventLoop(SIGPIPE); + mDNSPosixListenForSignalInEventLoop(SIGHUP) ; + + for (; ;) + { + // Work out how long we expect to sleep before the next scheduled task + struct timeval timeout; + mDNSs32 ticks; + + // Only idle if we didn't find any data the last time around + if (!gotData) + { + mDNSs32 nextTimerEvent = mDNS_Execute(m); + nextTimerEvent = udsserver_idle(nextTimerEvent); + ticks = nextTimerEvent - mDNS_TimeNow(m); + if (ticks < 1) ticks = 1; + } + else // otherwise call EventLoop again with 0 timemout + ticks = 0; + + timeout.tv_sec = ticks / mDNSPlatformOneSecond; + timeout.tv_usec = (ticks % mDNSPlatformOneSecond) * 1000000 / mDNSPlatformOneSecond; + + (void) mDNSPosixRunEventLoopOnce(m, &timeout, &signals, &gotData); + + if (sigismember(&signals, SIGHUP )) Reconfigure(m); + if (sigismember(&signals, SIGUSR1)) DumpStateLog(m); + // SIGPIPE happens when we try to write to a dead client; death should be detected soon in request_callback() and cleaned up. + if (sigismember(&signals, SIGPIPE)) LogMsg("Received SIGPIPE - ignoring"); + if (sigismember(&signals, SIGINT) || sigismember(&signals, SIGTERM)) break; + } + return EINTR; +} + +int main(int argc, char **argv) +{ + mStatus err; + + ParseCmdLinArgs(argc, argv); + + LogMsg("%s starting", mDNSResponderVersionString); + + err = mDNS_Init(&mDNSStorage, &PlatformStorage, gRRCache, RR_CACHE_SIZE, mDNS_Init_AdvertiseLocalAddresses, + mDNS_StatusCallback, mDNS_Init_NoInitCallbackContext); + + if (mStatus_NoError == err) + err = udsserver_init(mDNSNULL, 0); + + Reconfigure(&mDNSStorage); + + // Now that we're finished with anything privileged, switch over to running as "nobody" + if (mStatus_NoError == err) + { + const struct passwd *pw = getpwnam("nobody"); + if (pw != NULL) + setuid(pw->pw_uid); + else + LogMsg("WARNING: mdnsd continuing as root because user \"nobody\" does not exist"); + } + + if (mStatus_NoError == err) + err = MainLoop(&mDNSStorage); + + LogMsg("%s stopping", mDNSResponderVersionString); + + mDNS_Close(&mDNSStorage); + + if (udsserver_exit() < 0) + LogMsg("ExitCallback: udsserver_exit failed"); + + #if MDNS_DEBUGMSGS > 0 + printf("mDNSResponder exiting normally with %ld\n", err); + #endif + + return err; +} + +// uds_daemon support //////////////////////////////////////////////////////////// + +mStatus udsSupportAddFDToEventLoop(int fd, udsEventCallback callback, void *context, void **platform_data) +/* Support routine for uds_daemon.c */ +{ + // Depends on the fact that udsEventCallback == mDNSPosixEventCallback + (void) platform_data; + return mDNSPosixAddFDToEventLoop(fd, callback, context); +} + +int udsSupportReadFD(dnssd_sock_t fd, char *buf, int len, int flags, void *platform_data) +{ + (void) platform_data; + return recv(fd, buf, len, flags); +} + +mStatus udsSupportRemoveFDFromEventLoop(int fd, void *platform_data) // Note: This also CLOSES the file descriptor +{ + mStatus err = mDNSPosixRemoveFDFromEventLoop(fd); + (void) platform_data; + close(fd); + return err; +} + +mDNSexport void RecordUpdatedNiceLabel(mDNS *const m, mDNSs32 delay) +{ + (void)m; + (void)delay; + // No-op, for now +} + +#if _BUILDING_XCODE_PROJECT_ +// If the process crashes, then this string will be magically included in the automatically-generated crash log +const char *__crashreporter_info__ = mDNSResponderVersionString_SCCS + 5; +asm (".desc ___crashreporter_info__, 0x10"); +#endif + +// For convenience when using the "strings" command, this is the last thing in the file +#if mDNSResponderVersion > 1 +mDNSexport const char mDNSResponderVersionString_SCCS[] = "@(#) mDNSResponder-" STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")"; +#elif MDNS_VERSIONSTR_NODTS +mDNSexport const char mDNSResponderVersionString_SCCS[] = "@(#) mDNSResponder (Engineering Build)"; +#else +mDNSexport const char mDNSResponderVersionString_SCCS[] = "@(#) mDNSResponder (Engineering Build) (" __DATE__ " " __TIME__ ")"; +#endif diff --git a/mDNSResponder/mDNSPosix/ProxyResponder.c b/mDNSResponder/mDNSPosix/ProxyResponder.c new file mode 100644 index 00000000..3982161d --- /dev/null +++ b/mDNSResponder/mDNSPosix/ProxyResponder.c @@ -0,0 +1,300 @@ +/* -*- 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. + */ + +#include // For printf() +#include // For exit() etc. +#include // For strlen() etc. +#include // For select() +#include // For SIGINT, SIGTERM +#include // For errno, EINTR +#include // For INADDR_NONE +#include // For inet_addr() +#include // For gethostbyname() + +#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 "ExampleClientApp.h" + +// Compatibility workaround: Solaris 2.5 has no INADDR_NONE +#ifndef INADDR_NONE +#define INADDR_NONE (mDNSu32)0xffffffff +#endif + +//************************************************************************************************************* +// Globals +static mDNS mDNSStorage; // mDNS core uses this to store its globals +static mDNS_PlatformSupport PlatformStorage; // Stores this platform's globals +mDNSexport const char ProgramName[] = "mDNSProxyResponderPosix"; + +//************************************************************************************************************* +// Proxy Host Registration + +typedef struct +{ + mDNSv4Addr ip; + domainlabel hostlabel; // Conforms to standard DNS letter-digit-hyphen host name rules + AuthRecord RR_A; // 'A' (address) record for our ".local" name + AuthRecord RR_PTR; // PTR (reverse lookup) record +} ProxyHost; + +mDNSlocal void HostNameCallback(mDNS *const m, AuthRecord *const rr, mStatus result) +{ + ProxyHost *f = (ProxyHost*)rr->RecordContext; + if (result == mStatus_NoError) + debugf("Host name successfully registered: %##s", rr->resrec.name->c); + else + { + debugf("Host name conflict for %##s", rr->resrec.name->c); + mDNS_Deregister(m, &f->RR_A); + mDNS_Deregister(m, &f->RR_PTR); + exit(-1); + } +} + +mDNSlocal mStatus mDNS_RegisterProxyHost(mDNS *m, ProxyHost *p) +{ + char buffer[32]; + + mDNS_SetupResourceRecord(&p->RR_A, mDNSNULL, mDNSInterface_Any, kDNSType_A, 60, kDNSRecordTypeUnique, AuthRecordAny, HostNameCallback, p); + mDNS_SetupResourceRecord(&p->RR_PTR, mDNSNULL, mDNSInterface_Any, kDNSType_PTR, 60, kDNSRecordTypeKnownUnique, AuthRecordAny, HostNameCallback, p); + + p->RR_A.namestorage.c[0] = 0; + AppendDomainLabel(&p->RR_A.namestorage, &p->hostlabel); + AppendLiteralLabelString(&p->RR_A.namestorage, "local"); + + // Note: This is reverse order compared to a normal dotted-decimal IP address, so we can't use our customary "%.4a" format code + mDNS_snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d.in-addr.arpa.", p->ip.b[3], p->ip.b[2], p->ip.b[1], p->ip.b[0]); + MakeDomainNameFromDNSNameString(&p->RR_PTR.namestorage, buffer); + p->RR_PTR.ForceMCast = mDNStrue; // This PTR points to our dot-local name, so don't ever try to write it into a uDNS server + + p->RR_A.resrec.rdata->u.ipv4 = p->ip; + AssignDomainName(&p->RR_PTR.resrec.rdata->u.name, p->RR_A.resrec.name); + + mDNS_Register(m, &p->RR_A); + mDNS_Register(m, &p->RR_PTR); + + debugf("Made Proxy Host Records for %##s", p->RR_A.resrec.name->c); + + return(mStatus_NoError); +} + +//************************************************************************************************************* +// Service Registration + +// This sample ServiceCallback just calls mDNS_RenameAndReregisterService to automatically pick a new +// unique name for the service. For a device such as a printer, this may be appropriate. +// For a device with a user interface, and a screen, and a keyboard, the appropriate +// response may be to prompt the user and ask them to choose a new name for the service. +mDNSlocal void ServiceCallback(mDNS *const m, ServiceRecordSet *const sr, mStatus result) +{ + switch (result) + { + case mStatus_NoError: debugf("Callback: %##s Name Registered", sr->RR_SRV.resrec.name->c); break; + case mStatus_NameConflict: debugf("Callback: %##s Name Conflict", sr->RR_SRV.resrec.name->c); break; + case mStatus_MemFree: debugf("Callback: %##s Memory Free", sr->RR_SRV.resrec.name->c); break; + default: debugf("Callback: %##s Unknown Result %ld", sr->RR_SRV.resrec.name->c, result); break; + } + + if (result == mStatus_NoError) + { + char buffer[MAX_ESCAPED_DOMAIN_NAME]; + ConvertDomainNameToCString(sr->RR_SRV.resrec.name, buffer); + printf("Service %s now registered and active\n", buffer); + } + + if (result == mStatus_NameConflict) + { + char buffer1[MAX_ESCAPED_DOMAIN_NAME], buffer2[MAX_ESCAPED_DOMAIN_NAME]; + ConvertDomainNameToCString(sr->RR_SRV.resrec.name, buffer1); + mDNS_RenameAndReregisterService(m, sr, mDNSNULL); + ConvertDomainNameToCString(sr->RR_SRV.resrec.name, buffer2); + printf("Name Conflict! %s renamed as %s\n", buffer1, buffer2); + } +} + +// RegisterService() is a simple wrapper function which takes C string +// parameters, converts them to domainname parameters, and calls mDNS_RegisterService() +mDNSlocal void RegisterService(mDNS *m, ServiceRecordSet *recordset, + const char name[], const char type[], const char domain[], + const domainname *host, mDNSu16 PortAsNumber, int argc, char **argv) +{ + domainlabel n; + domainname t, d; + unsigned char txtbuffer[1024], *bptr = txtbuffer; + char buffer[MAX_ESCAPED_DOMAIN_NAME]; + + MakeDomainLabelFromLiteralString(&n, name); + MakeDomainNameFromDNSNameString(&t, type); + MakeDomainNameFromDNSNameString(&d, domain); + while (argc) + { + int len = strlen(argv[0]); + if (len > 255 || bptr + 1 + len >= txtbuffer + sizeof(txtbuffer)) break; + printf("STR: %s\n", argv[0]); + bptr[0] = len; + strcpy((char*)(bptr+1), argv[0]); + bptr += 1 + len; + argc--; + argv++; + } + + mDNS_RegisterService(m, recordset, + &n, &t, &d, // Name, type, domain + host, mDNSOpaque16fromIntVal(PortAsNumber), + txtbuffer, bptr-txtbuffer, // TXT data, length + mDNSNULL, 0, // Subtypes + mDNSInterface_Any, // Interface ID + ServiceCallback, mDNSNULL, 0); // Callback, context, flags + + ConvertDomainNameToCString(recordset->RR_SRV.resrec.name, buffer); + printf("Made Service Records for %s\n", buffer); +} + +//************************************************************************************************************* +// Service non-existence assertion +// (claiming a service name without actually providing a service at that name, to prevent anyone else using that name) +// This is useful to avoid confusion between similar services +// e.g. A printer that implements IPP printing service using the name "My Printer", but doesn't implement LPR service, +// should also claim the LPR service name "My Printer" to stop a different printer offering LPR service under the same name, +// since it would be confusing to users to have two equivalent services with the same name. + +mDNSlocal void NoSuchServiceCallback(mDNS *const m, AuthRecord *const rr, mStatus result) +{ + const domainname *proxyhostname = (const domainname *)rr->RecordContext; + switch (result) + { + case mStatus_NoError: debugf("Callback: %##s Name Registered", rr->resrec.name->c); break; + case mStatus_NameConflict: debugf("Callback: %##s Name Conflict", rr->resrec.name->c); break; + case mStatus_MemFree: debugf("Callback: %##s Memory Free", rr->resrec.name->c); break; + default: debugf("Callback: %##s Unknown Result %ld", rr->resrec.name->c, result); break; + } + + if (result == mStatus_NoError) + { + char buffer[MAX_ESCAPED_DOMAIN_NAME]; + ConvertDomainNameToCString(rr->resrec.name, buffer); + printf("Non-existence assertion %s now registered and active\n", buffer); + } + + if (result == mStatus_NameConflict) + { + domainlabel n; + domainname t, d; + char buffer1[MAX_ESCAPED_DOMAIN_NAME], buffer2[MAX_ESCAPED_DOMAIN_NAME]; + ConvertDomainNameToCString(rr->resrec.name, buffer1); + DeconstructServiceName(rr->resrec.name, &n, &t, &d); + IncrementLabelSuffix(&n, mDNStrue); + mDNS_RegisterNoSuchService(m, rr, &n, &t, &d, proxyhostname, mDNSInterface_Any, NoSuchServiceCallback, mDNSNULL, 0); + ConvertDomainNameToCString(rr->resrec.name, buffer2); + printf("Name Conflict! %s renamed as %s\n", buffer1, buffer2); + } +} + +mDNSlocal void RegisterNoSuchService(mDNS *m, AuthRecord *const rr, domainname *proxyhostname, + const char name[], const char type[], const char domain[]) +{ + domainlabel n; + domainname t, d; + char buffer[MAX_ESCAPED_DOMAIN_NAME]; + MakeDomainLabelFromLiteralString(&n, name); + MakeDomainNameFromDNSNameString(&t, type); + MakeDomainNameFromDNSNameString(&d, domain); + mDNS_RegisterNoSuchService(m, rr, &n, &t, &d, proxyhostname, mDNSInterface_Any, NoSuchServiceCallback, proxyhostname, 0); + ConvertDomainNameToCString(rr->resrec.name, buffer); + printf("Made Non-existence Record for %s\n", buffer); +} + +//************************************************************************************************************* +// Main + +mDNSexport int main(int argc, char **argv) +{ + mStatus status; + sigset_t signals; + + if (argc < 3) goto usage; + + status = mDNS_Init(&mDNSStorage, &PlatformStorage, + mDNS_Init_NoCache, mDNS_Init_ZeroCacheSize, + mDNS_Init_DontAdvertiseLocalAddresses, + mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext); + if (status) { fprintf(stderr, "Daemon start: mDNS_Init failed %d\n", (int)status); return(status); } + + mDNSPosixListenForSignalInEventLoop(SIGINT); + mDNSPosixListenForSignalInEventLoop(SIGTERM); + + if (!strcmp(argv[1], "-")) + { + domainname proxyhostname; + AuthRecord proxyrecord; + if (argc < 5) goto usage; + proxyhostname.c[0] = 0; + AppendLiteralLabelString(&proxyhostname, argv[2]); + AppendLiteralLabelString(&proxyhostname, "local"); + RegisterNoSuchService(&mDNSStorage, &proxyrecord, &proxyhostname, argv[3], argv[4], "local."); + } + else + { + ProxyHost proxyhost; + ServiceRecordSet proxyservice; + + proxyhost.ip.NotAnInteger = inet_addr(argv[1]); + if (proxyhost.ip.NotAnInteger == INADDR_NONE) // INADDR_NONE is 0xFFFFFFFF + { + struct hostent *h = gethostbyname(argv[1]); + if (h) proxyhost.ip.NotAnInteger = *(long*)h->h_addr; + } + if (proxyhost.ip.NotAnInteger == INADDR_NONE) // INADDR_NONE is 0xFFFFFFFF + { + fprintf(stderr, "%s is not valid host address\n", argv[1]); + return(-1); + } + + MakeDomainLabelFromLiteralString(&proxyhost.hostlabel, argv[2]); + + mDNS_RegisterProxyHost(&mDNSStorage, &proxyhost); + + if (argc >=6) + RegisterService(&mDNSStorage, &proxyservice, argv[3], argv[4], "local.", + proxyhost.RR_A.resrec.name, atoi(argv[5]), argc-6, &argv[6]); + } + + do + { + struct timeval timeout = { 0x3FFFFFFF, 0 }; // wait until SIGINT or SIGTERM + mDNSBool gotSomething; + mDNSPosixRunEventLoopOnce(&mDNSStorage, &timeout, &signals, &gotSomething); + } + while ( !( sigismember( &signals, SIGINT) || sigismember( &signals, SIGTERM))); + + mDNS_Close(&mDNSStorage); + + return(0); + +usage: + fprintf(stderr, "%s ip hostlabel [srvname srvtype port txt [txt ...]]\n", argv[0]); + fprintf(stderr, "ip Real IP address (or valid host name) of the host where the service actually resides\n"); + fprintf(stderr, "hostlabel First label of the dot-local host name to create for this host, e.g. \"foo\" for \"foo.local.\"\n"); + fprintf(stderr, "srvname Descriptive name of service, e.g. \"Stuart's Ink Jet Printer\"\n"); + fprintf(stderr, "srvtype IANA service type, e.g. \"_ipp._tcp\" or \"_ssh._tcp\", etc.\n"); + fprintf(stderr, "port Port number where the service resides (1-65535)\n"); + fprintf(stderr, "txt Additional name/value pairs specified in service definition, e.g. \"pdl=application/postscript\"\n"); + fprintf(stderr, "e.g. %s 169.254.12.34 thehost (just create a dot-local host name)\n", argv[0]); + fprintf(stderr, "or %s 169.254.12.34 thehost \"My Printer\" _printer._tcp. 515 rp=lpt1 pdl=application/postscript\n", argv[0]); + fprintf(stderr, "or %s - thehost \"My Printer\" _printer._tcp. (assertion of non-existence)\n", argv[0]); + return(-1); +} diff --git a/mDNSResponder/mDNSPosix/ReadMe.txt b/mDNSResponder/mDNSPosix/ReadMe.txt new file mode 100755 index 00000000..c2f56412 --- /dev/null +++ b/mDNSResponder/mDNSPosix/ReadMe.txt @@ -0,0 +1,314 @@ +ReadMe About mDNSPosix +---------------------- + +mDNSPosix is a port of Apple's Multicast DNS and DNS Service Discovery +code to Posix platforms. + +Multicast DNS and DNS Service Discovery are technologies that allow you +to register IP-based services and browse the network for those services. +For more information about mDNS, see the mDNS web site. + + + +Multicast DNS is part of a family of technologies resulting from the +efforts of the IETF Zeroconf working group. For information about +other Zeroconf technologies, see the Zeroconf web site. + + + +Apple uses the trade mark "Bonjour" to describe our implementation of +Zeroconf technologies. This sample is designed to show how easy it is +to make a device "Bonjour compatible". + +The "Bonjour" trade mark can also be licensed at no charge for +inclusion on your own products, packaging, manuals, promotional +materials, or web site. For details and licensing terms, see + + + +The code in this sample was compiled and tested on Mac OS X (10.1.x, +10.2, 10.3), Solaris (SunOS 5.8), Linux (Redhat 2.4.9-21, Fedora Core 1), +and OpenBSD (2.9). YMMV. + + +Packing List +------------ + +The sample uses the following directories: + +o mDNSCore -- A directory containing the core mDNS code. This code + is written in pure ANSI C and has proved to be very portable. + Every platform needs this core protocol engine code. + +o mDNSShared -- A directory containing useful code that's not core to + the main protocol engine itself, but nonetheless useful, and used by + more than one (but not necessarily all) platforms. + +o mDNSPosix -- The files that are specific to Posix platforms: Linux, + Solaris, FreeBSD, NetBSD, OpenBSD, etc. This code will also work on + OS X, though that's not its primary purpose. + +o Clients -- Example client code showing how to use the API to the + services provided by the daemon. + + +Building the Code +----------------- + +The sample does not use autoconf technology, primarily because I didn't +want to delay shipping while I learnt how to use it. Thus the code +builds using a very simple make file. To build the sample you should +cd to the mDNSPosix directory and type "make os=myos", e.g. + + make os=panther + +For Linux you would change that to: + + make os=linux + +There are definitions for each of the platforms I ported to. If you're +porting to any other platform please add appropriate definitions for it +and send us the diffs so they can be incorporated into the main +distribution. + + +Using the Sample +---------------- +When you compile, you will get: + +o Main products for general-purpose use (e.g. on a desktop computer): + - mdnsd + - libmdns + - nss_mdns (See nss_ReadMe.txt for important information about nss_mdns) + +o Standalone products for dedicated devices (printer, network camera, etc.) + - mDNSClientPosix + - mDNSResponderPosix + - mDNSProxyResponderPosix + +o Testing and Debugging tools + - dns-sd command-line tool (from the "Clients" folder) + - mDNSNetMonitor + - mDNSIdentify + +As root type "make install" to install eight things: +o mdnsd (usually in /usr/sbin) +o libmdns (usually in /usr/lib) +o dns_sd.h (usually in /usr/include) +o startup scripts (e.g. in /etc/rc.d) +o manual pages (usually in /usr/share/man) +o dns-sd tool (usually in /usr/bin) +o nss_mdns (usually in /lib) +o nss configuration files (usually in /etc) + +The "make install" concludes by executing the startup script +(usually "/etc/init.d/mdns start") to start the daemon running. +You shouldn't need to reboot unless you really want to. + +Once the daemon is running, you can use the dns-sd test tool +to exercise all the major functionality of the daemon. Running +"dns-sd" with no arguments gives a summary of the available options. +This test tool is also described in detail, with several examples, +in Chapter 6 of the O'Reilly "Zero Configuration Networking" book. + + +How It Works +------------ + +--------------------+ + | Client Application | + +----------------+ +--------------------+ + | uds_daemon.c | <--- Unix Domain Socket ---> | libmdns | + +----------------+ +--------------------+ + | mDNSCore | + +----------------+ + | mDNSPosix.c | + +----------------+ + +mdnsd is divided into three sections. + +o mDNSCore is the main protocol engine +o mDNSPosix.c provides the glue it needs to run on a Posix OS +o uds_daemon.c exports a Unix Domain Socket interface to + the services provided by mDNSCore + +Client applications link with the libmdns, which implements the functions +defined in the dns_sd.h header file, and implements the IPC protocol +used to communicate over the Unix Domain Socket interface to the daemon. + +Note that, strictly speaking, nss_mdns could be just another client of +mdnsd, linking with libmdns just like any other client. However, because +of its central role in the normal operation of multicast DNS, it is built +and installed along with the other essential system support components. + + +Clients for Embedded Systems +---------------------------- + +For small devices with very constrained resources, with a single address +space and (typically) no virtual memory, the uds_daemon.c/UDS/libmdns +layer may be eliminated, and the Client Application may live directly +on top of mDNSCore: + + +--------------------+ + | Client Application | + +--------------------+ + | mDNSCore | + +--------------------+ + | mDNSPosix.c | + +--------------------+ + +Programming to this model is more work, so using the daemon and its +library is recommended if your platform is capable of that. + +The runtime behaviour when using the embedded model is as follows: + +1. The application calls mDNS_Init, which in turns calls the platform + (mDNSPlatformInit). + +2. mDNSPlatformInit gets a list of interfaces (get_ifi_info) and registers + each one with the core (mDNS_RegisterInterface). For each interface + it also creates a multicast socket (SetupSocket). + +3. The application then calls select() repeatedly to handle file descriptor + events. Before calling select() each time, the application calls + mDNSPosixGetFDSet() to give mDNSPosix.c a chance to add its own file + descriptors to the set, and then after select() returns, it calls + mDNSPosixProcessFDSet() to give mDNSPosix.c a chance to receive and + process any packets that may have arrived. + +4. When the core needs to send a UDP packet it calls + mDNSPlatformSendUDP. That routines finds the interface that + corresponds to the source address requested by the core, and + sends the datagram using the UDP socket created for the + interface. If the socket is flow send-side controlled it just + drops the packet. + +5. When SocketDataReady runs it uses a complex routine, + "recvfrom_flags", to actually receive the packet. This is required + because the core needs information about the packet that is + only available via the "recvmsg" call, and that call is complex + to implement in a portable way. I got my implementation of + "recvfrom_flags" from Stevens' "UNIX Network Programming", but + I had to modify it further to work with Linux. + +One thing to note is that the Posix platform code is very deliberately +not multi-threaded. I do everything from a main loop that calls +"select()". This is good because it avoids all the problems that often +accompany multi-threaded code. If you decide to use threads in your +platform, you will have to implement the mDNSPlatformLock() and +mDNSPlatformUnlock() calls which are currently no-ops in mDNSPosix.c. + + +Once you've built the embedded samples you can test them by first +running the client, as shown below. + + quinn% build/mDNSClientPosix + Hit ^C when you're bored waiting for responses. + +By default the client starts a search for AppleShare servers and then +sits and waits, printing a message when services appear and disappear. + +To continue with the test you should start the responder in another +shell window. + + quinn% build/mDNSResponderPosix -n Foo + +This will start the responder and tell it to advertise a AppleShare +service "Foo". In the client window you will see the client print out +the following as the service shows up on the network. + + quinn% build/mDNSClientPosix + Hit ^C when you're bored waiting for responses. + *** Found name = 'Foo', type = '_afpovertcp._tcp.', domain = 'local.' + +Back in the responder window you can quit the responder cleanly using +SIGINT (typically ^C). + + quinn% build/mDNSResponderPosix -n Foo + ^C + quinn% + +As the responder quits it will multicast that the "Foo" service is +disappearing and the client will see that notification and print a +message to that effect (shown below). Finally, when you're done with +the client you can use SIGINT to quit it. + + quinn% build/mDNSClientPosix + Hit ^C when you're bored waiting for responses. + *** Found name = 'Foo', type = '_afpovertcp._tcp.', domain = 'local.' + *** Lost name = 'Foo', type = '_afpovertcp._tcp.', domain = 'local.' + ^C + quinn% + +If things don't work, try starting each program in verbose mode (using +the "-v 1" option, or very verbose mode with "-v 2") to see if there's +an obvious cause. + +That's it for the core functionality. Each program supports a variety +of other options. For example, you can advertise and browse for a +different service type using the "-t type" option. Use the "-?" option +on each program for more user-level information. + + +Caveats +------- +Currently the program uses a simple make file. + +The Multicast DNS protocol can also operate locally over the loopback +interface, but this exposed some problems with the underlying network +stack in early versions of Mac OS X and may expose problems with other +network stacks too. + +o On Mac OS X 10.1.x the code failed to start on the loopback interface + because the IP_ADD_MEMBERSHIP option returns ENOBUFS. + +o On Mac OS X 10.2 the loopback-only case failed because + "sendto" calls fails with error EHOSTUNREACH. (3016042) + +Consequently, the code will attempt service discovery on the loopback +interface only if no other interfaces are available. + +I haven't been able to test the loopback-only case on other platforms +because I don't have access to the physical machine. + + +Licencing +--------- +This source code is Open Source; for details see the "LICENSE" file. + +Credits and Version History +--------------------------- +If you find any problems with this sample, mail and I +will try to fix them up. + +1.0a1 (Jul 2002) was a prerelease version that was distributed +internally at Apple. + +1.0a2 (Jul 2002) was a prerelease version that was distributed +internally at Apple. + +1.0a3 (Aug 2002) was the first shipping version. The core mDNS code is +the code from Mac OS 10.2 (Jaguar) GM. + +Share and Enjoy + +Apple Developer Technical Support +Networking, Communications, Hardware + +6 Aug 2002 + +Impact: A local network user may cause a denial of the Bonjour service +Description: An error handling issue exists in the Bonjour Namespace +Provider. A local network user may send a maliciously crafted multicast +DNS packet leading to an unexpected termination of the Bonjour service. +This update addresses the issue by performing additional validation of +multicast DNS packets. This issue does not affect systems running Mac OS +X or Windows. +CVE-ID +CVE-2011-0220 : JaeSeung Song of the Department of Computing at Imperial +College London + +To Do List +---------- +• port to a System V that's not Solaris +• use sig_atomic_t for signal to main thread flags diff --git a/mDNSResponder/mDNSPosix/Responder.c b/mDNSResponder/mDNSPosix/Responder.c new file mode 100755 index 00000000..3996b7b9 --- /dev/null +++ b/mDNSResponder/mDNSPosix/Responder.c @@ -0,0 +1,788 @@ +/* -*- 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. + */ + +#if __APPLE__ +// In Mac OS X 10.5 and later trying to use the daemon function gives a “‘daemon’ is deprecated” +// error, which prevents compilation because we build with "-Werror". +// Since this is supposed to be portable cross-platform code, we don't care that daemon is +// deprecated on Mac OS X 10.5, so we use this preprocessor trick to eliminate the error message. +#define daemon yes_we_know_that_daemon_is_deprecated_in_os_x_10_5_thankyou +#endif + +#include +#include // For printf() +#include // For exit() etc. +#include // For strlen() etc. +#include // For select() +#include // For errno, EINTR +#include +#include + +#if __APPLE__ +#undef daemon +extern int daemon(int, int); +#endif + +#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 "mDNSUNP.h" // For daemon() + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Globals +#endif + +static mDNS mDNSStorage; // mDNS core uses this to store its globals +static mDNS_PlatformSupport PlatformStorage; // Stores this platform's globals + +mDNSexport const char ProgramName[] = "mDNSResponderPosix"; + +static const char *gProgramName = ProgramName; + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Signals +#endif + +static volatile mDNSBool gReceivedSigUsr1; +static volatile mDNSBool gReceivedSigHup; +static volatile mDNSBool gStopNow; + +// We support 4 signals. +// +// o SIGUSR1 toggles verbose mode on and off in debug builds +// o SIGHUP triggers the program to re-read its preferences. +// o SIGINT causes an orderly shutdown of the program. +// o SIGQUIT causes a somewhat orderly shutdown (direct but dangerous) +// o SIGKILL kills us dead (easy to implement :-) +// +// There are fatal race conditions in our signal handling, but there's not much +// we can do about them while remaining within the Posix space. Specifically, +// if a signal arrives after we test the globals its sets but before we call +// select, the signal will be dropped. The user will have to send the signal +// again. Unfortunately, Posix does not have a "sigselect" to atomically +// modify the signal mask and start a select. + +static void HandleSigUsr1(int sigraised) +// If we get a SIGUSR1 we toggle the state of the +// verbose mode. +{ + assert(sigraised == SIGUSR1); + gReceivedSigUsr1 = mDNStrue; +} + +static void HandleSigHup(int sigraised) +// A handler for SIGHUP that causes us to break out of the +// main event loop when the user kill 1's us. This has the +// effect of triggered the main loop to deregister the +// current services and re-read the preferences. +{ + assert(sigraised == SIGHUP); + gReceivedSigHup = mDNStrue; +} + +static void HandleSigInt(int sigraised) +// A handler for SIGINT that causes us to break out of the +// main event loop when the user types ^C. This has the +// effect of quitting the program. +{ + assert(sigraised == SIGINT); + + if (gMDNSPlatformPosixVerboseLevel > 0) { + fprintf(stderr, "\nSIGINT\n"); + } + gStopNow = mDNStrue; +} + +static void HandleSigQuit(int sigraised) +// If we get a SIGQUIT the user is desperate and we +// just call mDNS_Close directly. This is definitely +// not safe (because it could reenter mDNS), but +// we presume that the user has already tried the safe +// alternatives. +{ + assert(sigraised == SIGQUIT); + + if (gMDNSPlatformPosixVerboseLevel > 0) { + fprintf(stderr, "\nSIGQUIT\n"); + } + mDNS_Close(&mDNSStorage); + exit(0); +} + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Parameter Checking +#endif + +static mDNSBool CheckThatRichTextNameIsUsable(const char *richTextName, mDNSBool printExplanation) +// Checks that richTextName is reasonable +// label and, if it isn't and printExplanation is true, prints +// an explanation of why not. +{ + mDNSBool result = mDNStrue; + if (result && strlen(richTextName) > 63) { + if (printExplanation) { + fprintf(stderr, + "%s: Service name is too long (must be 63 characters or less)\n", + gProgramName); + } + result = mDNSfalse; + } + if (result && richTextName[0] == 0) { + if (printExplanation) { + fprintf(stderr, "%s: Service name can't be empty\n", gProgramName); + } + result = mDNSfalse; + } + return result; +} + +static mDNSBool CheckThatServiceTypeIsUsable(const char *serviceType, mDNSBool printExplanation) +// Checks that serviceType is a reasonable service type +// label and, if it isn't and printExplanation is true, prints +// an explanation of why not. +{ + mDNSBool result; + + result = mDNStrue; + if (result && strlen(serviceType) > 63) { + if (printExplanation) { + fprintf(stderr, + "%s: Service type is too long (must be 63 characters or less)\n", + gProgramName); + } + result = mDNSfalse; + } + if (result && serviceType[0] == 0) { + if (printExplanation) { + fprintf(stderr, + "%s: Service type can't be empty\n", + gProgramName); + } + result = mDNSfalse; + } + return result; +} + +static mDNSBool CheckThatPortNumberIsUsable(long portNumber, mDNSBool printExplanation) +// Checks that portNumber is a reasonable port number +// and, if it isn't and printExplanation is true, prints +// an explanation of why not. +{ + mDNSBool result; + + result = mDNStrue; + if (result && (portNumber <= 0 || portNumber > 65535)) { + if (printExplanation) { + fprintf(stderr, + "%s: Port number specified by -p must be in range 1..65535\n", + gProgramName); + } + result = mDNSfalse; + } + return result; +} + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Command Line Arguments +#endif + +static const char kDefaultPIDFile[] = "/var/run/mDNSResponder.pid"; +static const char kDefaultServiceType[] = "_afpovertcp._tcp."; +static const char kDefaultServiceDomain[] = "local."; +enum { + kDefaultPortNumber = 548 +}; + +static void PrintUsage() +{ + fprintf(stderr, + "Usage: %s [-v level ] [-r] [-n name] [-t type] [-d domain] [-p port] [-f file] [-b] [-P pidfile] [-x name=val ...]\n", + gProgramName); + fprintf(stderr, " -v verbose mode, level is a number from 0 to 2\n"); + fprintf(stderr, " 0 = no debugging info (default)\n"); + fprintf(stderr, " 1 = standard debugging info\n"); + fprintf(stderr, " 2 = intense debugging info\n"); + fprintf(stderr, " can be cycled kill -USR1\n"); + fprintf(stderr, " -r also bind to port 53 (port 5353 is always bound)\n"); + fprintf(stderr, " -n uses 'name' as the service name (required)\n"); + fprintf(stderr, " -t uses 'type' as the service type (default is '%s')\n", kDefaultServiceType); + fprintf(stderr, " -d uses 'domain' as the service domain (default is '%s')\n", kDefaultServiceDomain); + fprintf(stderr, " -p uses 'port' as the port number (default is '%d')\n", kDefaultPortNumber); + fprintf(stderr, " -f reads a service list from 'file'\n"); + fprintf(stderr, " -b forces daemon (background) mode\n"); + fprintf(stderr, " -P uses 'pidfile' as the PID file\n"); + fprintf(stderr, " (default is '%s')\n", kDefaultPIDFile); + fprintf(stderr, " only meaningful if -b also specified\n"); + fprintf(stderr, " -x stores name=val in TXT record (default is empty).\n"); + fprintf(stderr, " MUST be the last command-line argument;\n"); + fprintf(stderr, " all subsequent arguments after -x are treated as name=val pairs.\n"); +} + +static mDNSBool gAvoidPort53 = mDNStrue; +static const char *gServiceName = ""; +static const char *gServiceType = kDefaultServiceType; +static const char *gServiceDomain = kDefaultServiceDomain; +static mDNSu8 gServiceText[sizeof(RDataBody)]; +static mDNSu16 gServiceTextLen = 0; +static int gPortNumber = kDefaultPortNumber; +static const char *gServiceFile = ""; +static mDNSBool gDaemon = mDNSfalse; +static const char *gPIDFile = kDefaultPIDFile; + +static void ParseArguments(int argc, char **argv) +// Parses our command line arguments into the global variables +// listed above. +{ + int ch; + + // Set gProgramName to the last path component of argv[0] + + gProgramName = strrchr(argv[0], '/'); + if (gProgramName == NULL) { + gProgramName = argv[0]; + } else { + gProgramName += 1; + } + + // Parse command line options using getopt. + + do { + ch = getopt(argc, argv, "v:rn:t:d:p:f:dP:bx"); + if (ch != -1) { + switch (ch) { + case 'v': + gMDNSPlatformPosixVerboseLevel = atoi(optarg); + if (gMDNSPlatformPosixVerboseLevel < 0 || gMDNSPlatformPosixVerboseLevel > 2) { + fprintf(stderr, + "%s: Verbose mode must be in the range 0..2\n", + gProgramName); + exit(1); + } + break; + case 'r': + gAvoidPort53 = mDNSfalse; + break; + case 'n': + gServiceName = optarg; + if ( !CheckThatRichTextNameIsUsable(gServiceName, mDNStrue) ) { + exit(1); + } + break; + case 't': + gServiceType = optarg; + if ( !CheckThatServiceTypeIsUsable(gServiceType, mDNStrue) ) { + exit(1); + } + break; + case 'd': + gServiceDomain = optarg; + break; + case 'p': + gPortNumber = atol(optarg); + if ( !CheckThatPortNumberIsUsable(gPortNumber, mDNStrue) ) { + exit(1); + } + break; + case 'f': + gServiceFile = optarg; + break; + case 'b': + gDaemon = mDNStrue; + break; + case 'P': + gPIDFile = optarg; + break; + case 'x': + while (optind < argc) + { + gServiceText[gServiceTextLen] = strlen(argv[optind]); + mDNSPlatformMemCopy(gServiceText+gServiceTextLen+1, argv[optind], gServiceText[gServiceTextLen]); + gServiceTextLen += 1 + gServiceText[gServiceTextLen]; + optind++; + } + ch = -1; + break; + case '?': + default: + PrintUsage(); + exit(1); + break; + } + } + } while (ch != -1); + + // Check for any left over command line arguments. + + if (optind != argc) { + PrintUsage(); + fprintf(stderr, "%s: Unexpected argument '%s'\n", gProgramName, argv[optind]); + exit(1); + } + + // Check for inconsistency between the arguments. + + if ( (gServiceName[0] == 0) && (gServiceFile[0] == 0) ) { + PrintUsage(); + fprintf(stderr, "%s: You must specify a service name to register (-n) or a service file (-f).\n", gProgramName); + exit(1); + } +} + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** Registration +#endif + +typedef struct PosixService PosixService; + +struct PosixService { + ServiceRecordSet coreServ; + PosixService *next; + int serviceID; +}; + +static PosixService *gServiceList = NULL; + +static void RegistrationCallback(mDNS *const m, ServiceRecordSet *const thisRegistration, mStatus status) +// mDNS core calls this routine to tell us about the status of +// our registration. The appropriate action to take depends +// entirely on the value of status. +{ + switch (status) { + + case mStatus_NoError: + debugf("Callback: %##s Name Registered", thisRegistration->RR_SRV.resrec.name->c); + // Do nothing; our name was successfully registered. We may + // get more call backs in the future. + break; + + case mStatus_NameConflict: + debugf("Callback: %##s Name Conflict", thisRegistration->RR_SRV.resrec.name->c); + + // In the event of a conflict, this sample RegistrationCallback + // just calls mDNS_RenameAndReregisterService to automatically + // pick a new unique name for the service. For a device such as a + // printer, this may be appropriate. For a device with a user + // interface, and a screen, and a keyboard, the appropriate response + // may be to prompt the user and ask them to choose a new name for + // the service. + // + // Also, what do we do if mDNS_RenameAndReregisterService returns an + // error. Right now I have no place to send that error to. + + status = mDNS_RenameAndReregisterService(m, thisRegistration, mDNSNULL); + assert(status == mStatus_NoError); + break; + + case mStatus_MemFree: + debugf("Callback: %##s Memory Free", thisRegistration->RR_SRV.resrec.name->c); + + // When debugging is enabled, make sure that thisRegistration + // is not on our gServiceList. + + #if !defined(NDEBUG) + { + PosixService *cursor; + + cursor = gServiceList; + while (cursor != NULL) { + assert(&cursor->coreServ != thisRegistration); + cursor = cursor->next; + } + } + #endif + free(thisRegistration); + break; + + default: + debugf("Callback: %##s Unknown Status %ld", thisRegistration->RR_SRV.resrec.name->c, status); + break; + } +} + +static int gServiceID = 0; + +static mStatus RegisterOneService(const char * richTextName, + const char * serviceType, + const char * serviceDomain, + const mDNSu8 text[], + mDNSu16 textLen, + long portNumber) +{ + mStatus status; + PosixService * thisServ; + domainlabel name; + domainname type; + domainname domain; + + status = mStatus_NoError; + thisServ = (PosixService *) malloc(sizeof(*thisServ)); + if (thisServ == NULL) { + status = mStatus_NoMemoryErr; + } + if (status == mStatus_NoError) { + MakeDomainLabelFromLiteralString(&name, richTextName); + MakeDomainNameFromDNSNameString(&type, serviceType); + MakeDomainNameFromDNSNameString(&domain, serviceDomain); + status = mDNS_RegisterService(&mDNSStorage, &thisServ->coreServ, + &name, &type, &domain, // Name, type, domain + NULL, mDNSOpaque16fromIntVal(portNumber), + text, textLen, // TXT data, length + NULL, 0, // Subtypes + mDNSInterface_Any, // Interface ID + RegistrationCallback, thisServ, 0); // Callback, context, flags + } + if (status == mStatus_NoError) { + thisServ->serviceID = gServiceID; + gServiceID += 1; + + thisServ->next = gServiceList; + gServiceList = thisServ; + + if (gMDNSPlatformPosixVerboseLevel > 0) { + fprintf(stderr, + "%s: Registered service %d, name \"%s\", type \"%s\", domain \"%s\", port %ld\n", + gProgramName, + thisServ->serviceID, + richTextName, + serviceType, + serviceDomain, + portNumber); + } + } else { + if (thisServ != NULL) { + free(thisServ); + } + } + return status; +} + +static mDNSBool ReadALine(char *buf, size_t bufSize, FILE *fp, mDNSBool skipBlankLines) +{ + size_t len; + mDNSBool readNextLine; + + do { + readNextLine = mDNSfalse; + + if (fgets(buf, bufSize, fp) == NULL) + return mDNSfalse; // encountered EOF or an error condition + + // These first characters indicate a blank line. + if (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\r' || buf[0] == '\n') { + if (!skipBlankLines) + return mDNSfalse; + readNextLine = mDNStrue; + } + // always skip comment lines + if (buf[0] == '#') + readNextLine = mDNStrue; + + } while (readNextLine); + + len = strlen( buf); + if ( buf[len - 1] == '\r' || buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + return mDNStrue; +} + +static mStatus RegisterServicesInFile(const char *filePath) +{ + mStatus status = mStatus_NoError; + FILE * fp = fopen(filePath, "r"); + + if (fp == NULL) { + return mStatus_UnknownErr; + } + + if (gMDNSPlatformPosixVerboseLevel > 1) + fprintf(stderr, "Parsing %s for services\n", filePath); + + do { + char nameBuf[256]; + char * name = nameBuf; + char type[256]; + const char *dom = kDefaultServiceDomain; + char rawText[1024]; + mDNSu8 text[sizeof(RDataBody)]; + unsigned int textLen = 0; + char port[256]; + char *p; + + // Read the service name, type, port, and optional text record fields. + // Skip blank lines while looking for the next service name. + if (!ReadALine(name, sizeof(nameBuf), fp, mDNStrue)) + break; + + // Special case that allows service name to begin with a '#' + // character by escaping it with a '\' to distiguish it from + // a comment line. Remove the leading '\' here before + // registering the service. + if (name[0] == '\\' && name[1] == '#') + name++; + + if (gMDNSPlatformPosixVerboseLevel > 1) + fprintf(stderr, "Service name: \"%s\"\n", name); + + // Don't skip blank lines in calls to ReadAline() after finding the + // service name since the next blank line indicates the end + // of this service record. + if (!ReadALine(type, sizeof(type), fp, mDNSfalse)) + break; + + // see if a domain name is specified + p = type; + while (*p && *p != ' ' && *p != '\t') p++; + if (*p) { + *p = 0; // NULL terminate the . string + // skip any leading whitespace before domain name + p++; + while (*p && (*p == ' ' || *p == '\t')) p++; + if (*p) + dom = p; + } + if (gMDNSPlatformPosixVerboseLevel > 1) { + fprintf(stderr, "Service type: \"%s\"\n", type); + fprintf(stderr, "Service domain: \"%s\"\n", dom); + } + + if (!ReadALine(port, sizeof(port), fp, mDNSfalse)) + break; + if (gMDNSPlatformPosixVerboseLevel > 1) + fprintf(stderr, "Service port: %s\n", port); + + if ( !CheckThatRichTextNameIsUsable(name, mDNStrue) + || !CheckThatServiceTypeIsUsable(type, mDNStrue) + || !CheckThatPortNumberIsUsable(atol(port), mDNStrue)) + break; + + // read the TXT record fields + while (1) { + int len; + if (!ReadALine(rawText, sizeof(rawText), fp, mDNSfalse)) break; + if (gMDNSPlatformPosixVerboseLevel > 1) + fprintf(stderr, "Text string: \"%s\"\n", rawText); + len = strlen(rawText); + if (len <= 255) + { + unsigned int newlen = textLen + 1 + len; + if (len == 0 || newlen >= sizeof(text)) break; + text[textLen] = len; + mDNSPlatformMemCopy(text + textLen + 1, rawText, len); + textLen = newlen; + } + else + fprintf(stderr, "%s: TXT attribute too long for name = %s, type = %s, port = %s\n", + gProgramName, name, type, port); + } + + status = RegisterOneService(name, type, dom, text, textLen, atol(port)); + if (status != mStatus_NoError) { + // print error, but try to read and register other services in the file + fprintf(stderr, "%s: Failed to register service, name \"%s\", type \"%s\", domain \"%s\", port %s\n", + gProgramName, name, type, dom, port); + } + + } while (!feof(fp)); + + if (!feof(fp)) { + fprintf(stderr, "%s: Error reading service file %s\n", gProgramName, filePath); + status = mStatus_UnknownErr; + } + + assert(0 == fclose(fp)); + + return status; +} + +static mStatus RegisterOurServices(void) +{ + mStatus status; + + status = mStatus_NoError; + if (gServiceName[0] != 0) { + status = RegisterOneService(gServiceName, + gServiceType, + gServiceDomain, + gServiceText, gServiceTextLen, + gPortNumber); + } + if (status == mStatus_NoError && gServiceFile[0] != 0) { + status = RegisterServicesInFile(gServiceFile); + } + return status; +} + +static void DeregisterOurServices(void) +{ + PosixService *thisServ; + int thisServID; + + while (gServiceList != NULL) { + thisServ = gServiceList; + gServiceList = thisServ->next; + + thisServID = thisServ->serviceID; + + mDNS_DeregisterService(&mDNSStorage, &thisServ->coreServ); + + if (gMDNSPlatformPosixVerboseLevel > 0) { + fprintf(stderr, + "%s: Deregistered service %d\n", + gProgramName, + thisServ->serviceID); + } + } +} + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark **** Main +#endif + +int main(int argc, char **argv) +{ + mStatus status; + int result; + + // Parse our command line arguments. This won't come back if there's an error. + + ParseArguments(argc, argv); + + // If we're told to run as a daemon, then do that straight away. + // Note that we don't treat the inability to create our PID + // file as an error. Also note that we assign getpid to a long + // because printf has no format specified for pid_t. + + if (gDaemon) { + int result; + if (gMDNSPlatformPosixVerboseLevel > 0) { + fprintf(stderr, "%s: Starting in daemon mode\n", gProgramName); + } + result = daemon(0,0); + if (result == 0) { + FILE *fp; + int junk; + + fp = fopen(gPIDFile, "w"); + if (fp != NULL) { + fprintf(fp, "%ld\n", (long) getpid()); + junk = fclose(fp); + assert(junk == 0); + } + } else { + fprintf(stderr, "%s: Could not run as daemon - exiting\n", gProgramName); + exit(result); + } + } else { + if (gMDNSPlatformPosixVerboseLevel > 0) { + fprintf(stderr, "%s: Starting in foreground mode, PID %ld\n", gProgramName, (long) getpid()); + } + } + + status = mDNS_Init(&mDNSStorage, &PlatformStorage, + mDNS_Init_NoCache, mDNS_Init_ZeroCacheSize, + mDNS_Init_AdvertiseLocalAddresses, + mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext); + if (status != mStatus_NoError) return(2); + + status = RegisterOurServices(); + if (status != mStatus_NoError) return(2); + + signal(SIGHUP, HandleSigHup); // SIGHUP has to be sent by kill -HUP + signal(SIGINT, HandleSigInt); // SIGINT is what you get for a Ctrl-C + signal(SIGQUIT, HandleSigQuit); // SIGQUIT is what you get for a Ctrl-\ (indeed) + signal(SIGUSR1, HandleSigUsr1); // SIGUSR1 has to be sent by kill -USR1 + + while (!gStopNow) + { + int nfds = 0; + fd_set readfds; + struct timeval timeout; + int result; + + // 1. Set up the fd_set as usual here. + // This example client has no file descriptors of its own, + // but a real application would call FD_SET to add them to the set here + FD_ZERO(&readfds); + + // 2. Set up the timeout. + // This example client has no other work it needs to be doing, + // so we set an effectively infinite timeout + timeout.tv_sec = 0x3FFFFFFF; + timeout.tv_usec = 0; + + // 3. Give the mDNSPosix layer a chance to add its information to the fd_set and timeout + mDNSPosixGetFDSet(&mDNSStorage, &nfds, &readfds, &timeout); + + // 4. Call select as normal + verbosedebugf("select(%d, %d.%06d)", nfds, timeout.tv_sec, timeout.tv_usec); + result = select(nfds, &readfds, NULL, NULL, &timeout); + + if (result < 0) + { + verbosedebugf("select() returned %d errno %d", result, errno); + if (errno != EINTR) gStopNow = mDNStrue; + else + { + if (gReceivedSigUsr1) + { + gReceivedSigUsr1 = mDNSfalse; + gMDNSPlatformPosixVerboseLevel += 1; + if (gMDNSPlatformPosixVerboseLevel > 2) + gMDNSPlatformPosixVerboseLevel = 0; + if ( gMDNSPlatformPosixVerboseLevel > 0 ) + fprintf(stderr, "\nVerbose level %d\n", gMDNSPlatformPosixVerboseLevel); + } + if (gReceivedSigHup) + { + if (gMDNSPlatformPosixVerboseLevel > 0) + fprintf(stderr, "\nSIGHUP\n"); + gReceivedSigHup = mDNSfalse; + DeregisterOurServices(); + status = mDNSPlatformPosixRefreshInterfaceList(&mDNSStorage); + if (status != mStatus_NoError) break; + status = RegisterOurServices(); + if (status != mStatus_NoError) break; + } + } + } + else + { + // 5. Call mDNSPosixProcessFDSet to let the mDNSPosix layer do its work + mDNSPosixProcessFDSet(&mDNSStorage, &readfds); + + // 6. This example client has no other work it needs to be doing, + // but a real client would do its work here + // ... (do work) ... + } + } + + debugf("Exiting"); + + DeregisterOurServices(); + mDNS_Close(&mDNSStorage); + + if (status == mStatus_NoError) { + result = 0; + } else { + result = 2; + } + if ( (result != 0) || (gMDNSPlatformPosixVerboseLevel > 0) ) { + fprintf(stderr, "%s: Finished with status %d, result %d\n", gProgramName, (int)status, result); + } + + return result; +} diff --git a/mDNSResponder/mDNSPosix/Services.txt b/mDNSResponder/mDNSPosix/Services.txt new file mode 100755 index 00000000..f8d69786 --- /dev/null +++ b/mDNSResponder/mDNSPosix/Services.txt @@ -0,0 +1,36 @@ +# +# Example services file parsed by mDNSResponderPosix. +# +# Lines beginning with '#' are comments/ignored. +# Blank lines indicate the end of a service record specification. +# The first character of the service name can be a '#' if you escape it with +# backslash to distinguish if from a comment line. +# ie, "\#serviceName" will be registered as "#serviceName". +# Note that any line beginning with white space is considered a blank line. +# +# The record format is: +# +# +# . +# +# +# +# +# +# Examples shown below. + +serviceName1 +_afpovertcp._tcp. +548 +name=val1 + +serviceName2 +_afpovertcp._tcp. local. +548 +name=val2 +name2=anotherattribute + +serviceName3 +_afpovertcp._tcp. +548 +name=val3 diff --git a/mDNSResponder/mDNSPosix/libnss_mdns.8 b/mDNSResponder/mDNSPosix/libnss_mdns.8 new file mode 100755 index 00000000..9c00d4fb --- /dev/null +++ b/mDNSResponder/mDNSPosix/libnss_mdns.8 @@ -0,0 +1,148 @@ +.\" +.\" See section LICENSE for license information. +.\" +.Dd June 15, 2004 +.Dt LIBNSS_MDNS 8 +.Os +.Sh NAME +.Nm libnss_mdns +.Nd name service switch module to support Apple mdnsd +.Sh DESCRIPTION +The +.Nm +shared library implements a name service switch module to interface with Apple +mdnsd. It is enabled by adding +.Ql mdns +to the +.Ql hosts: +line of +.Xr nsswitch.conf 5 . +This will cause calls to +.Xr gethostbyname 3 , +.Xr gethostbyname2 3 +and +.Xr gethostbyaddr 3 +to include mdnsd in their lookup path. +.Pp +The +.Nm +shared library should be installed in the system library paths, typically in +.Pa /lib . +.Sh FILES +.Bl -tag -width Pa -compact +.It Pa /etc/nss_mdns.conf +configuration options +.El +.Sh EXAMPLES +In +.Pa /etc/nsswitch.conf : +.Dl hosts: files mdns dns +.Pp +This will cause the name service switch to lookup hostnames first using +.Pa /etc/hosts , +then mdns and finally via DNS. +.Sh DIAGNOSTICS +.Nm +dumps status information via +.Xr syslog 3 . +.Sh SEE ALSO +.\" Cross-references should be ordered by section (low to high), then in +.\" alphabetical order. +.Xr nsswitch.conf 5 , +.Xr nss_mdns.conf 5 , +.Xr ldconfig 8 +.Pp +.Li info libc Qq Name Service Switch +.\" .Sh STANDARDS +.Sh HISTORY +.Nm +was originally written for +.An NICTA Bq http://www.nicta.com.au/ . +.Sh AUTHORS +.An "Andrew White" Bq Andrew.White@nicta.com.au +.Sh BUGS +Return values for obscure error conditions may not match those expected by nsswitch code. +.Pp +Version 0.62 of mdnsd does not support an option to force link-local multicast lookups. +.Ql PTR +reverse lookups for non-link-local domains will not function correctly. +.Sh LICENSE +This software is licensed under the NICTA Public Source License version 1.0 +.Ss NICTA Public Software Licence +Version 1.0 +.Pp +Copyright 2004 National ICT Australia Ltd +.Pp +All rights reserved. +.Pp +By this licence, National ICT Australia Ltd (NICTA) grants permission, +free of charge, to any person who obtains a copy of this software +and any associated documentation files ("the Software") to use and +deal with the Software in source code and binary forms without +restriction, with or without modification, and to permit persons +to whom the Software is furnished to do so, provided that the +following conditions are met: +.Bl -bullet +.It +Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimers. +.It +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimers in +the documentation and/or other materials provided with the +distribution. +.It +The name of NICTA may not be used to endorse or promote products +derived from this Software without specific prior written permission. +.El +.Pp +EXCEPT AS EXPRESSLY STATED IN THIS LICENCE AND TO THE FULL EXTENT +PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS-IS" AND +NICTA MAKES NO REPRESENTATIONS, WARRANTIES OR CONDITIONS OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY +REPRESENTATIONS, WARRANTIES OR CONDITIONS REGARDING THE CONTENTS +OR ACCURACY OF THE SOFTWARE, OR OF TITLE, MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, THE ABSENCE OF LATENT +OR OTHER DEFECTS, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR +NOT DISCOVERABLE. +.Pp +TO THE FULL EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL +NICTA BE LIABLE ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, +NEGLIGENCE) FOR ANY LOSS OR DAMAGE WHATSOEVER, INCLUDING (WITHOUT +LIMITATION) LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR +CORRUPTION OF DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, +OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC LOSS; +OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR +EXEMPLARY DAMAGES ARISING OUT OF OR IN CONNECTION WITH THIS LICENCE, +THE SOFTWARE OR THE USE OF THE SOFTWARE, EVEN IF NICTA HAS BEEN +ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +.Pp +If applicable legislation implies warranties or conditions, or +imposes obligations or liability on NICTA in respect of the Software +that cannot be wholly or partly excluded, restricted or modified, +NICTA's liability is limited, to the full extent permitted by the +applicable legislation, at its option, to: +.Pp +.Bl -tag -width "a." -compact +.It a. +in the case of goods, any one or more of the following: +.Bl -tag -width "iii." -compact +.It i. +the replacement of the goods or the supply of equivalent goods; +.It ii. +the repair of the goods; +.It iii. +the payment of the cost of replacing the goods or of acquiring +equivalent goods; +.It iv. +the payment of the cost of having the goods repaired; or +.El +.It b. +in the case of services: +.Bl -tag -width "iii." -compact +.It i. +the supplying of the services again; or +.It ii. +the payment of the cost of having the services supplied again. +.El +.El diff --git a/mDNSResponder/mDNSPosix/mDNSPosix.c b/mDNSResponder/mDNSPosix/mDNSPosix.c new file mode 100755 index 00000000..953bf64f --- /dev/null +++ b/mDNSResponder/mDNSPosix/mDNSPosix.c @@ -0,0 +1,1789 @@ +/* -*- 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. + * + */ + +#include "mDNSEmbeddedAPI.h" // Defines the interface provided to the client layer above +#include "DNSCommon.h" +#include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform +#include "dns_sd.h" +#include "dnssec.h" +#include "nsec.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, UDPSocket *src, const mDNSAddr *dst, + mDNSIPPort dstPort, mDNSBool useBackgroundTrafficClass) +{ + int err = 0; + struct sockaddr_storage to; + PosixNetworkInterface * thisIntf = (PosixNetworkInterface *)(InterfaceID); + int sendingsocket = -1; + + (void)src; // Will need to use this parameter once we implement mDNSPlatformUDPSocket/mDNSPlatformUDPClose + (void) useBackgroundTrafficClass; + + assert(m != NULL); + assert(msg != NULL); + assert(end != NULL); + assert((((char *) end) - ((char *) msg)) > 0); + + if (dstPort.NotAnInteger == 0) + { + LogMsg("mDNSPlatformSendUDP: Invalid argument -dstPort is set to 0"); + return PosixErrorToStatus(EINVAL); + } + 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 = AllDNSLinkGroup_v4.ip.v4; + else if (senderAddr.type == mDNSAddrType_IPv6) destAddr.ip.v6 = AllDNSLinkGroup_v6.ip.v6; + } + #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 mDNSBool mDNSPlatformPeekUDP(mDNS *const m, UDPSocket *src) +{ + (void)m; // unused + (void)src; // unused + return mDNSfalse; +} + +mDNSexport TCPSocket *mDNSPlatformTCPSocket(mDNS * const m, TCPSocketFlags flags, mDNSIPPort * port, mDNSBool useBackgroundTrafficClass) +{ + (void)m; // Unused + (void)flags; // Unused + (void)port; // Unused + (void)useBackgroundTrafficClass; // Unused + return NULL; +} + +mDNSexport TCPSocket *mDNSPlatformTCPAccept(TCPSocketFlags flags, int sd) +{ + (void)flags; // Unused + (void)sd; // Unused + return NULL; +} + +mDNSexport int mDNSPlatformTCPGetFD(TCPSocket *sock) +{ + (void)sock; // Unused + return -1; +} + +mDNSexport mStatus mDNSPlatformTCPConnect(TCPSocket *sock, const mDNSAddr *dst, mDNSOpaque16 dstport, domainname *hostname, mDNSInterfaceID InterfaceID, + TCPConnectionCallback callback, void *context) +{ + (void)sock; // Unused + (void)dst; // Unused + (void)dstport; // Unused + (void)hostname; // Unused + (void)InterfaceID; // Unused + (void)callback; // Unused + (void)context; // Unused + return(mStatus_UnsupportedErr); +} + +mDNSexport void mDNSPlatformTCPCloseConnection(TCPSocket *sock) +{ + (void)sock; // Unused +} + +mDNSexport long mDNSPlatformReadTCP(TCPSocket *sock, void *buf, unsigned long buflen, mDNSBool * closed) +{ + (void)sock; // Unused + (void)buf; // Unused + (void)buflen; // Unused + (void)closed; // Unused + return 0; +} + +mDNSexport long mDNSPlatformWriteTCP(TCPSocket *sock, const char *msg, unsigned long len) +{ + (void)sock; // Unused + (void)msg; // Unused + (void)len; // Unused + return 0; +} + +mDNSexport UDPSocket *mDNSPlatformUDPSocket(mDNS * const m, mDNSIPPort port) +{ + (void)m; // Unused + (void)port; // Unused + return NULL; +} + +mDNSexport void mDNSPlatformUDPClose(UDPSocket *sock) +{ + (void)sock; // Unused +} + +mDNSexport void mDNSPlatformUpdateProxyList(mDNS *const m, const mDNSInterfaceID InterfaceID) +{ + (void)m; // Unused + (void)InterfaceID; // Unused +} + +mDNSexport void mDNSPlatformSendRawPacket(const void *const msg, const mDNSu8 *const end, mDNSInterfaceID InterfaceID) +{ + (void)msg; // Unused + (void)end; // Unused + (void)InterfaceID; // Unused +} + +mDNSexport void mDNSPlatformSetLocalAddressCacheEntry(mDNS *const m, const mDNSAddr *const tpa, const mDNSEthAddr *const tha, mDNSInterfaceID InterfaceID) +{ + (void)m; // Unused + (void)tpa; // Unused + (void)tha; // Unused + (void)InterfaceID; // Unused +} + +mDNSexport mStatus mDNSPlatformTLSSetupCerts(void) +{ + return(mStatus_UnsupportedErr); +} + +mDNSexport void mDNSPlatformTLSTearDownCerts(void) +{ +} + +mDNSexport void mDNSPlatformSetAllowSleep(mDNS *const m, mDNSBool allowSleep, const char *reason) +{ + (void) m; + (void) allowSleep; + (void) reason; +} + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - /etc/hosts support +#endif + +mDNSexport void FreeEtcHosts(mDNS *const m, AuthRecord *const rr, mStatus result) +{ + (void)m; // unused + (void)rr; + (void)result; +} + + +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark ***** DDNS Config Platform Functions +#endif + +mDNSexport mDNSBool mDNSPlatformSetDNSConfig(mDNS *const m, mDNSBool setservers, mDNSBool setsearch, domainname *const fqdn, DNameListElem **RegDomains, + DNameListElem **BrowseDomains, mDNSBool ackConfig) +{ + (void) m; + (void) setservers; + (void) fqdn; + (void) setsearch; + (void) RegDomains; + (void) BrowseDomains; + (void) ackConfig; + + return mDNStrue; +} + +mDNSexport mStatus mDNSPlatformGetPrimaryInterface(mDNS * const m, mDNSAddr * v4, mDNSAddr * v6, mDNSAddr * router) +{ + (void) m; + (void) v4; + (void) v6; + (void) router; + + return mStatus_UnsupportedErr; +} + +mDNSexport void mDNSPlatformDynDNSHostNameStatusChanged(const domainname *const dname, const mStatus status) +{ + (void) dname; + (void) status; +} + +#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[11]; + 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 (strncasecmp(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, NULL, mDNSInterface_Any, 0, &DNSAddr, UnicastDNSPort, kScopeNone, 0, mDNSfalse, 0, mDNStrue, mDNStrue, mDNSfalse); + 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); + if (index == kDNSServiceInterfaceIndexP2P ) return(mDNSInterface_P2P); + if (index == kDNSServiceInterfaceIndexAny ) return(mDNSInterface_Any); + + 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, mDNSBool suppressNetworkChange) +{ + PosixNetworkInterface *intf; + (void) suppressNetworkChange; // Unused + + assert(m != NULL); + + if (id == mDNSInterface_LocalOnly) return(kDNSServiceInterfaceIndexLocalOnly); + if (id == mDNSInterface_P2P ) return(kDNSServiceInterfaceIndexP2P); + if (id == mDNSInterface_Any ) return(kDNSServiceInterfaceIndexAny); + + 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((intfAddr->sa_family == AF_INET) ? "socket AF_INET" : "socket AF_INET6"); } + + // ... 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 = AllDNSLinkGroup_v4.ip.v4.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_2292_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_2292_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*)&AllDNSLinkGroup_v6.ip.v6; + 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 + } + + // If interface is a direct link, address record will be marked as kDNSRecordTypeKnownUnique + // and skip the probe phase of the probe/announce packet sequence. + intf->coreIntf.DirectLink = mDNSfalse; + + // 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. */ + mDNSPlatformMemZero(&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(int fd, short filter, void *context) +{ + IfChangeRec *pChgRec = (IfChangeRec*) context; + fd_set readFDs; + mDNSu32 changedInterfaces = 0; + struct timeval zeroTimeout = { 0, 0 }; + + (void)fd; // Unused + (void)filter; // Unused + + 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 + mDNS_Lock(m); + if (err == mStatus_NoError) ParseDNSServers(m, uDNS_SERVERS_FILE); + mDNS_Unlock(m); + + 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(void *dst, const void *src) +{ + 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(void *dst, const void *src, 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 *dst, const void *src, mDNSu32 len) +{ + return memcmp(dst, src, len) == 0; +} + +// If the caller wants to know the exact return of memcmp, then use this instead +// of mDNSPlatformMemSame +mDNSexport int mDNSPlatformMemCmp(const void *dst, const void *src, mDNSu32 len) +{ + return (memcmp(dst, src, len)); +} + +mDNSexport void mDNSPlatformQsort(void *base, int nel, int width, int (*compar)(const void *, const void *)) +{ + return (qsort(base, nel, width, compar)); +} + +// DNSSEC stub functions +mDNSexport void VerifySignature(mDNS *const m, DNSSECVerifier *dv, DNSQuestion *q) +{ + (void)m; + (void)dv; + (void)q; +} + +mDNSexport mDNSBool AddNSECSForCacheRecord(mDNS *const m, CacheRecord *crlist, CacheRecord *negcr, mDNSu8 rcode) +{ + (void)m; + (void)crlist; + (void)negcr; + (void)rcode; + return mDNSfalse; +} + +mDNSexport void BumpDNSSECStats(mDNS *const m, DNSSECStatsAction action, DNSSECStatsType type, mDNSu32 value) +{ + (void)m; + (void)action; + (void)type; + (void)value; +} + +// Proxy stub functions +mDNSexport mDNSu8 *DNSProxySetAttributes(DNSQuestion *q, DNSMessageHeader *h, DNSMessage *msg, mDNSu8 *ptr, mDNSu8 *limit) +{ + (void) q; + (void) h; + (void) msg; + (void) ptr; + (void) limit; + + return ptr; +} + +mDNSexport void DNSProxyInit(mDNS *const m, mDNSu32 IpIfArr[], mDNSu32 OpIf) +{ + (void) m; + (void) IpIfArr; + (void) OpIf; +} + +mDNSexport void DNSProxyTerminate(mDNS *const m) +{ + (void) m; +} + +// 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); +} + +mDNSexport void mDNSPlatformSendWakeupPacket(mDNS *const m, mDNSInterfaceID InterfaceID, char *EthAddr, char *IPAddr, int iteration) +{ + (void) m; + (void) InterfaceID; + (void) EthAddr; + (void) IPAddr; + (void) iteration; +} + +mDNSexport mDNSBool mDNSPlatformValidRecordForInterface(AuthRecord *rr, const NetworkInterfaceInfo *intf) +{ + (void) rr; + (void) intf; + + return 1; +} + +mDNSexport mDNSBool mDNSPlatformValidQuestionForInterface(DNSQuestion *q, const NetworkInterfaceInfo *intf) +{ + (void) q; + (void) intf; + + return 1; +} + +// Used for debugging purposes. For now, just set the buffer to zero +mDNSexport void mDNSPlatformFormatTime(unsigned long te, mDNSu8 *buf, int bufsize) +{ + (void) te; + if (bufsize) buf[0] = 0; +} + +mDNSexport void mDNSPlatformSendKeepalive(mDNSAddr *sadd, mDNSAddr *dadd, mDNSIPPort *lport, mDNSIPPort *rport, mDNSu32 seq, mDNSu32 ack, mDNSu16 win) +{ + (void) sadd; // Unused + (void) dadd; // Unused + (void) lport; // Unused + (void) rport; // Unused + (void) seq; // Unused + (void) ack; // Unused + (void) win; // Unused +} + +mDNSexport mStatus mDNSPlatformRetrieveTCPInfo(mDNS *const m, mDNSAddr *laddr, mDNSIPPort *lport, mDNSAddr *raddr, mDNSIPPort *rport, mDNSTCPInfo *mti) +{ + (void) m; // Unused + (void) laddr; // Unused + (void) raddr; // Unused + (void) lport; // Unused + (void) rport; // Unused + (void) mti; // Unused + + return mStatus_NoError; +} + +mDNSexport mStatus mDNSPlatformGetRemoteMacAddr(mDNS *const m, mDNSAddr *raddr) +{ + (void) raddr; // Unused + (void) m; // Unused + + return mStatus_NoError; +} + +mDNSexport mStatus mDNSPlatformStoreSPSMACAddr(mDNSAddr *spsaddr, char *ifname) +{ + (void) spsaddr; // Unused + (void) ifname; // Unused + + return mStatus_NoError; +} + +mDNSexport mStatus mDNSPlatformClearSPSMACAddr(void) +{ + return mStatus_NoError; +} + +mDNSexport mDNSu16 mDNSPlatformGetUDPPort(UDPSocket *sock) +{ + (void) sock; // unused + + return (mDNSu16)-1; +} + +mDNSexport mDNSBool mDNSPlatformInterfaceIsD2D(mDNSInterfaceID InterfaceID) +{ + (void) InterfaceID; // unused + + return mDNSfalse; +} + +mDNSexport mDNSBool mDNSPlatformAllowPID(mDNS *const m, DNSQuestion *q) +{ + (void) m; + (void) q; + return mDNStrue; +} + +mDNSexport mDNSs32 mDNSPlatformGetServiceID(mDNS *const m, DNSQuestion *q) +{ + (void) m; + (void) q; + return -1; +} + +mDNSexport void mDNSPlatformSetDelegatePID(UDPSocket *src, const mDNSAddr *dst, DNSQuestion *q) +{ + (void) src; + (void) dst; + (void) q; +} + +mDNSexport mDNSs32 mDNSPlatformGetPID() +{ + return 0; +} + +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; + + mDNSPlatformMemZero(&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; + + mDNSPlatformMemZero(&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->fd, 0, 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/mDNSResponder/mDNSPosix/mDNSPosix.h b/mDNSResponder/mDNSPosix/mDNSPosix.h new file mode 100755 index 00000000..f9fcea78 --- /dev/null +++ b/mDNSResponder/mDNSPosix/mDNSPosix.h @@ -0,0 +1,85 @@ +/* -*- 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. + */ + +#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)(int fd, short filter, 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/mDNSResponder/mDNSPosix/mDNSUNP.c b/mDNSResponder/mDNSPosix/mDNSUNP.c new file mode 100755 index 00000000..b392fc74 --- /dev/null +++ b/mDNSResponder/mDNSPosix/mDNSUNP.c @@ -0,0 +1,719 @@ +/* -*- 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. + */ + +#include "mDNSUNP.h" + +#include +#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, *ifipold, **ifiptr; + FILE *fp; + char addr[8][5]; + int flags, myflags, index, plen, scope; + char ifname[9], 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; + int sockfd = -1; + struct ifreq ifr; + + res0=NULL; + ifihead = NULL; + ifipnext = &ifihead; + lastname[0] = 0; + + if ((fp = fopen(PROC_IFINET6_PATH, "r")) != NULL) { + sockfd = socket(AF_INET6, SOCK_DGRAM, 0); + if (sockfd < 0) { + goto gotError; + } + 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; + } + + ifipold = *ifipnext; /* need this later */ + ifiptr = ifipnext; + *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; + + /* Add interface flags*/ + memcpy(ifr.ifr_name, ifname, IFNAMSIZ); + if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) { + if (errno == EADDRNOTAVAIL) { + /* + * If the main interface is configured with no IP address but + * an alias interface exists with an IP address, you get + * EADDRNOTAVAIL for the main interface + */ + free(ifi->ifi_addr); + free(ifi); + ifipnext = ifiptr; + *ifipnext = ifipold; + continue; + } else { + goto gotError; + } + } + ifi->ifi_flags = ifr.ifr_flags; + freeaddrinfo(res0); + res0=NULL; + } + } + goto done; + +gotError: + if (ifihead != NULL) { + free_ifi_info(ifihead); + ifihead = NULL; + } + if (res0 != NULL) { + freeaddrinfo(res0); + res0=NULL; + } +done: + if (sockfd != -1) { + assert(close(sockfd) == 0); + } + 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, *ifipold, **ifiptr; + 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; + } + ifipold = *ifipnext; /* need this later */ + ifiptr = ifipnext; + *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) { + if (errno == EADDRNOTAVAIL) { + /* + * If the main interface is configured with no IP address but + * an alias interface exists with an IP address, you get + * EADDRNOTAVAIL for the main interface + */ + free(ifi->ifi_addr); + free(ifi); + ifipnext = ifiptr; + *ifipnext = ifipold; + continue; + } else { + 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); + memset(&ifr6, 0, 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) { + if (errno == EADDRNOTAVAIL) { + /* + * If the main interface is configured with no IP address but + * an alias interface exists with an IP address, you get + * EADDRNOTAVAIL for the main interface + */ + free(ifi->ifi_addr); + free(ifi); + ifipnext = ifiptr; + *ifipnext = ifipold; + continue; + } else { + 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_netmask != NULL) + free(ifi->ifi_netmask); + 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; +#ifdef HAVE_BROKEN_RECVIF_NAME + if (sdl->sdl_index == 0) { + pktp->ipi_ifindex = *(uint_t*)sdl; + } +#endif + 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_2292_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_2292_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/mDNSResponder/mDNSPosix/mDNSUNP.h b/mDNSResponder/mDNSPosix/mDNSUNP.h new file mode 100755 index 00000000..cc81b7d3 --- /dev/null +++ b/mDNSResponder/mDNSPosix/mDNSUNP.h @@ -0,0 +1,130 @@ +/* -*- 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. + */ + +#ifndef __mDNSUNP_h +#define __mDNSUNP_h + +#include +#include +#include +#include + +#ifdef HAVE_LINUX +#include +#define IPV6_2292_PKTINFO IPV6_2292PKTINFO +#define IPV6_2292_HOPLIMIT IPV6_2292HOPLIMIT +#else +// The following are the supported non-linux posix OSes - +// netbsd, freebsd and openbsd. +#if HAVE_IPV6 +#define IPV6_2292_PKTINFO 19 +#define IPV6_2292_HOPLIMIT 20 +#endif +#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/mDNSResponder/mDNSPosix/mdnsd.sh b/mDNSResponder/mDNSPosix/mdnsd.sh new file mode 100644 index 00000000..14fef9b4 --- /dev/null +++ b/mDNSResponder/mDNSPosix/mdnsd.sh @@ -0,0 +1,73 @@ +#!/bin/sh +# Emacs settings: -*- 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. +# +# Linux /etc/init.d script to start/stop the mdnsd daemon. +# +# The following lines are used by the *BSD rcorder system to decide +# the order it's going to run the rc.d scripts at startup time. +# PROVIDE: mdnsd +# REQUIRE: NETWORKING + +if [ -r /usr/sbin/mdnsd ]; then + DAEMON=/usr/sbin/mdnsd +else + DAEMON=/usr/local/sbin/mdnsd +fi + +test -r $DAEMON || exit 0 + +# Some systems have start-stop-daemon, some don't. +if [ -r /sbin/start-stop-daemon ]; then + START="start-stop-daemon --start --quiet --exec" + # Suse Linux doesn't work with symbolic signal names, but we really don't need + # to specify "-s TERM" since SIGTERM (15) is the default stop signal anway + # STOP="start-stop-daemon --stop -s TERM --quiet --oknodo --exec" + STOP="start-stop-daemon --stop --quiet --oknodo --exec" +else + killmdnsd() { + kill -TERM `cat /var/run/mdnsd.pid` + } + START= + STOP=killmdnsd +fi + +case "$1" in + start) + echo -n "Starting Apple Darwin Multicast DNS / DNS Service Discovery daemon:" + echo -n " mdnsd" + $START $DAEMON + echo "." + ;; + stop) + echo -n "Stopping Apple Darwin Multicast DNS / DNS Service Discovery daemon:" + echo -n " mdnsd" ; $STOP $DAEMON + echo "." + ;; + reload|restart|force-reload) + echo -n "Restarting Apple Darwin Multicast DNS / DNS Service Discovery daemon:" + $STOP $DAEMON + sleep 1 + $START $DAEMON + echo -n " mdnsd" + ;; + *) + echo "Usage: /etc/init.d/mDNS {start|stop|reload|restart}" + exit 1 + ;; +esac + +exit 0 diff --git a/mDNSResponder/mDNSPosix/nss_ReadMe.txt b/mDNSResponder/mDNSPosix/nss_ReadMe.txt new file mode 100755 index 00000000..2e3023c0 --- /dev/null +++ b/mDNSResponder/mDNSPosix/nss_ReadMe.txt @@ -0,0 +1,125 @@ +# Readme for libnss_mdns + +Andrew White +June 2004 + +Before using this software, see "Licensing" at bottom of this file. + + +# Introduction + +This code implements a module for the Name Service Switch to perform +hostname lookups using the Darwin mDNSResponder / mdnsd. This code has +been tested on Debian and Redhat Linux. It may work on other platforms. +It *will not* work on Darwin or Mac OS X - the necessary functionality is +already built into the operation system. + + +# Building and Installing: + +See "ReadMe.txt" for instructions on building and installing. + +When you run "make install" as described in that file: +o libnss_mdns-0.2.so and libnss_mdns.so.2 are installed in /lib, +o manual pages libnss_mdns(8) and nss_mdns.conf(5) are installed, +o nss_mdns.conf is installed in /etc, and +o /etc/nsswitch.conf is modified to add "mdns" on the "hosts:" line + +This will cause dns lookups to be passed via mdnsd before trying the dns. + + +# Testing + +For most purposes, 'ping myhostname.local' will tell you if mdns is +working. If MDNS_VERBOSE was set in nss_mdns.c during compilation then +lots of chatty debug messages will be dumped to LOG_DEBUG in syslog. +Otherwise, nss_mdns will only log if something isn't behaving quite right. + + +# Implementation details + +libnss_mdns provides alternative back-end implementations of the libc +functions gethostbyname, gethostbyname2 and gethostbyaddr, using the Name +Service Switch mechanism. More information on writing nsswitch modules +can be found via 'info libc "Name Service Switch"', if installed. + + +# Licensing + +This software is licensed under the NICTA Public Software License, version +1.0, printed below: + +NICTA Public Software Licence +Version 1.0 + +Copyright © 2004 National ICT Australia Ltd + +All rights reserved. + +By this licence, National ICT Australia Ltd (NICTA) grants permission, +free of charge, to any person who obtains a copy of this software +and any associated documentation files ("the Software") to use and +deal with the Software in source code and binary forms without +restriction, with or without modification, and to permit persons +to whom the Software is furnished to do so, provided that the +following conditions are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimers. +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimers in + the documentation and/or other materials provided with the + distribution. +- The name of NICTA may not be used to endorse or promote products + derived from this Software without specific prior written permission. + +EXCEPT AS EXPRESSLY STATED IN THIS LICENCE AND TO THE FULL EXTENT +PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS-IS" AND +NICTA MAKES NO REPRESENTATIONS, WARRANTIES OR CONDITIONS OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY +REPRESENTATIONS, WARRANTIES OR CONDITIONS REGARDING THE CONTENTS +OR ACCURACY OF THE SOFTWARE, OR OF TITLE, MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, THE ABSENCE OF LATENT +OR OTHER DEFECTS, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR +NOT DISCOVERABLE. + +TO THE FULL EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL +NICTA BE LIABLE ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, +NEGLIGENCE) FOR ANY LOSS OR DAMAGE WHATSOEVER, INCLUDING (WITHOUT +LIMITATION) LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR +CORRUPTION OF DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, +OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC LOSS; +OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR +EXEMPLARY DAMAGES ARISING OUT OF OR IN CONNECTION WITH THIS LICENCE, +THE SOFTWARE OR THE USE OF THE SOFTWARE, EVEN IF NICTA HAS BEEN +ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +If applicable legislation implies warranties or conditions, or +imposes obligations or liability on NICTA in respect of the Software +that cannot be wholly or partly excluded, restricted or modified, +NICTA's liability is limited, to the full extent permitted by the +applicable legislation, at its option, to: + +a. in the case of goods, any one or more of the following: + i. the replacement of the goods or the supply of equivalent goods; + ii. the repair of the goods; + iii. the payment of the cost of replacing the goods or of acquiring + equivalent goods; + iv. the payment of the cost of having the goods repaired; or +b. in the case of services: + i. the supplying of the services again; or + ii. the payment of the cost of having the services supplied + again. + + +# Links: + +NICTA + http://www.nicta.com.au/ +Darwin + http://developer.apple.com/darwin/ +DNS service discovery and link-local + http://http://zeroconf.org/ + http://http://multicastdns.org/ + http://http://dns-sd.org/ + http://http://dotlocal.org/ diff --git a/mDNSResponder/mDNSPosix/nss_mdns.c b/mDNSResponder/mDNSPosix/nss_mdns.c new file mode 100755 index 00000000..afadb3c6 --- /dev/null +++ b/mDNSResponder/mDNSPosix/nss_mdns.c @@ -0,0 +1,2723 @@ +/* + NICTA Public Software Licence + Version 1.0 + + Copyright © 2004 National ICT Australia Ltd + + All rights reserved. + + By this licence, National ICT Australia Ltd (NICTA) grants permission, + free of charge, to any person who obtains a copy of this software + and any associated documentation files ("the Software") to use and + deal with the Software in source code and binary forms without + restriction, with or without modification, and to permit persons + to whom the Software is furnished to do so, provided that the + following conditions are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimers. + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimers in + the documentation and/or other materials provided with the + distribution. + - The name of NICTA may not be used to endorse or promote products + derived from this Software without specific prior written permission. + + EXCEPT AS EXPRESSLY STATED IN THIS LICENCE AND TO THE FULL EXTENT + PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS-IS" AND + NICTA MAKES NO REPRESENTATIONS, WARRANTIES OR CONDITIONS OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY + REPRESENTATIONS, WARRANTIES OR CONDITIONS REGARDING THE CONTENTS + OR ACCURACY OF THE SOFTWARE, OR OF TITLE, MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, THE ABSENCE OF LATENT + OR OTHER DEFECTS, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR + NOT DISCOVERABLE. + + TO THE FULL EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL + NICTA BE LIABLE ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) FOR ANY LOSS OR DAMAGE WHATSOEVER, INCLUDING (WITHOUT + LIMITATION) LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR + CORRUPTION OF DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, + OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC LOSS; + OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR + EXEMPLARY DAMAGES ARISING OUT OF OR IN CONNECTION WITH THIS LICENCE, + THE SOFTWARE OR THE USE OF THE SOFTWARE, EVEN IF NICTA HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + If applicable legislation implies warranties or conditions, or + imposes obligations or liability on NICTA in respect of the Software + that cannot be wholly or partly excluded, restricted or modified, + NICTA's liability is limited, to the full extent permitted by the + applicable legislation, at its option, to: + + a. in the case of goods, any one or more of the following: + i. the replacement of the goods or the supply of equivalent goods; + ii. the repair of the goods; + iii. the payment of the cost of replacing the goods or of acquiring + equivalent goods; + iv. the payment of the cost of having the goods repaired; or + b. in the case of services: + i. the supplying of the services again; or + ii. the payment of the cost of having the services supplied + again. + */ + +/* + NSSwitch Implementation of mDNS interface. + + Andrew White (Andrew.White@nicta.com.au) + May 2004 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#define BIND_8_COMPAT 1 +#include + +#include + + +//---------- +// Public functions + +/* + Count the number of dots in a name string. + */ +int +count_dots (const char * name); + + +/* + Test whether a domain name is local. + + Returns + 1 if name ends with ".local" or ".local." + 0 otherwise + */ +int +islocal (const char * name); + + +/* + Format an address structure as a string appropriate for DNS reverse (PTR) + lookup, based on address type. + + Parameters + prefixlen + Prefix length, in bits. When formatting, this will be rounded up + to the nearest appropriate size. If -1, assume maximum. + buf + Output buffer. Must be long enough to hold largest possible + output. + Returns + Pointer to (first character of) output buffer, + or NULL on error. + */ +char * +format_reverse_addr (int af, const void * addr, int prefixlen, char * buf); + + +/* + Format an address structure as a string appropriate for DNS reverse (PTR) + lookup for AF_INET. Output is in .in-addr.arpa domain. + + Parameters + prefixlen + Prefix length, in bits. When formatting, this will be rounded up + to the nearest byte (8). If -1, assume 32. + buf + Output buffer. Must be long enough to hold largest possible + output. For AF_INET, this is 29 characters (including null). + Returns + Pointer to (first character of) output buffer, + or NULL on error. + */ +char * +format_reverse_addr_in ( + const struct in_addr * addr, + int prefixlen, + char * buf + ); +#define DNS_PTR_AF_INET_SIZE 29 + +/* + Format an address structure as a string appropriate for DNS reverse (PTR) + lookup for AF_INET6. Output is in .ip6.arpa domain. + + Parameters + prefixlen + Prefix length, in bits. When formatting, this will be rounded up + to the nearest nibble (4). If -1, assume 128. + buf + Output buffer. Must be long enough to hold largest possible + output. For AF_INET6, this is 72 characters (including null). + Returns + Pointer to (first character of) output buffer, + or NULL on error. + */ +char * +format_reverse_addr_in6 ( + const struct in6_addr * addr, + int prefixlen, + char * buf + ); +#define DNS_PTR_AF_INET6_SIZE 72 + + +/* + Compare whether the given dns name has the given domain suffix. + A single leading '.' on the name or leading or trailing '.' on the + domain is ignored for the purposes of the comparison. + Multiple leading or trailing '.'s are an error. Other DNS syntax + errors are not checked for. The comparison is case insensitive. + + Returns + 1 on success (match) + 0 on failure (no match) + < 0 on error + */ +int +cmp_dns_suffix (const char * name, const char * domain); +enum +{ + CMP_DNS_SUFFIX_SUCCESS = 1, + CMP_DNS_SUFFIX_FAILURE = 0, + CMP_DNS_SUFFIX_BAD_NAME = 1, + CMP_DNS_SUFFIX_BAD_DOMAIN = -2 +}; + +typedef int ns_type_t; +typedef int ns_class_t; + +/* + Convert a DNS resource record (RR) code to an address family (AF) code. + + Parameters + rrtype + resource record type (from nameser.h) + + Returns + Appropriate AF code (from socket.h), or AF_UNSPEC if an appropriate + mapping couldn't be determined + */ +int +rr_to_af (ns_type_t rrtype); + + +/* + Convert an address family (AF) code to a DNS resource record (RR) code. + + Parameters + int + address family code (from socket.h) + Returns + Appropriate RR code (from nameser.h), or ns_t_invalid if an appropriate + mapping couldn't be determined + */ +ns_type_t +af_to_rr (int af); + + +/* + Convert a string to an address family (case insensitive). + + Returns + Matching AF code, or AF_UNSPEC if no match found. + */ +int +str_to_af (const char * str); + + +/* + Convert a string to an ns_class_t (case insensitive). + + Returns + Matching ns_class_t, or ns_c_invalid if no match found. + */ +ns_class_t +str_to_ns_class (const char * str); + + +/* + Convert a string to an ns_type_t (case insensitive). + + Returns + Matching ns_type_t, or ns_t_invalid if no match found. + */ +ns_type_t +str_to_ns_type (const char * str); + + +/* + Convert an address family code to a string. + + Returns + String representation of AF, + or NULL if address family unrecognised or invalid. + */ +const char * +af_to_str (int in); + + +/* + Convert an ns_class_t code to a string. + + Returns + String representation of ns_class_t, + or NULL if ns_class_t unrecognised or invalid. + */ +const char * +ns_class_to_str (ns_class_t in); + + +/* + Convert an ns_type_t code to a string. + + Returns + String representation of ns_type_t, + or NULL if ns_type_t unrecognised or invalid. + */ +const char * +ns_type_to_str (ns_type_t in); + + +/* + Convert DNS rdata in label format (RFC1034, RFC1035) to a name. + + On error, partial data is written to name (as much as was successfully + processed) and an error code is returned. Errors include a name too + long for the buffer and a pointer in the label (which cannot be + resolved). + + Parameters + rdata + Rdata formatted as series of labels. + rdlen + Length of rdata buffer. + name + Buffer to store fully qualified result in. + By RFC1034 section 3.1, a 255 character buffer (256 characters + including null) is long enough for any legal name. + name_len + Number of characters available in name buffer, not including + trailing null. + + Returns + Length of name buffer (not including trailing null). + < 0 on error. + A return of 0 implies the empty domain. + */ +static int +dns_rdata_to_name (const unsigned char * rdata, int rdlen, char * name, unsigned int name_len); +enum +{ + DNS_RDATA_TO_NAME_BAD_FORMAT = -1, + // Format is broken. Usually because we ran out of data + // (according to rdata) before the labels said we should. + DNS_RDATA_TO_NAME_TOO_LONG = -2, + // The converted rdata is longer than the name buffer. + DNS_RDATA_TO_NAME_PTR = -3, + // The rdata contains a pointer. +}; + +#define DNS_LABEL_MAXLEN 63 +// Maximum length of a single DNS label +#define DNS_NAME_MAXLEN 256 +// Maximum length of a DNS name + +//---------- +// Public types + +typedef int errcode_t; +// Used for 0 = success, non-zero = error code functions + + +//---------- +// Public functions + +/* + Test whether a domain name is in a domain covered by nss_mdns. + The name is assumed to be fully qualified (trailing dot optional); + unqualified names will be processed but may return unusual results + if the unqualified prefix happens to match a domain suffix. + + Returns + 1 success + 0 failure + -1 error, check errno + */ +int +config_is_mdns_suffix (const char * name); + + +/* + Loads all relevant data from configuration file. Other code should + rarely need to call this function, since all other public configuration + functions do so implicitly. Once loaded, configuration info doesn't + change. + + Returns + 0 configuration ready + non-zero configuration error code + */ +errcode_t +init_config (); + +#define ENTNAME hostent +#define DATABASE "hosts" + +#include +// For nss_status +#include +// For hostent +#include +// For size_t + +typedef enum nss_status nss_status; +typedef struct hostent hostent; + +/* + gethostbyname implementation + + name: + name to look up + result_buf: + resulting entry + buf: + auxillary buffer + buflen: + length of auxillary buffer + errnop: + pointer to errno + h_errnop: + pointer to h_errno + */ +nss_status +_nss_mdns_gethostbyname_r ( + const char *name, + hostent * result_buf, + char *buf, + size_t buflen, + int *errnop, + int *h_errnop + ); + + +/* + gethostbyname2 implementation + + name: + name to look up + af: + address family + result_buf: + resulting entry + buf: + auxillary buffer + buflen: + length of auxillary buffer + errnop: + pointer to errno + h_errnop: + pointer to h_errno + */ +nss_status +_nss_mdns_gethostbyname2_r ( + const char *name, + int af, + hostent * result_buf, + char *buf, + size_t buflen, + int *errnop, + int *h_errnop + ); + + +/* + gethostbyaddr implementation + + addr: + address structure to look up + len: + length of address structure + af: + address family + result_buf: + resulting entry + buf: + auxillary buffer + buflen: + length of auxillary buffer + errnop: + pointer to errno + h_errnop: + pointer to h_errno + */ +nss_status +_nss_mdns_gethostbyaddr_r ( + const void *addr, + socklen_t len, + int af, + hostent * result_buf, + char *buf, + size_t buflen, + int *errnop, + int *h_errnop + ); + + +//---------- +// Types and Constants + +const int MDNS_VERBOSE = 0; +// This enables verbose syslog messages +// If zero, only "imporant" messages will appear in syslog + +#define k_hostname_maxlen 256 +// As per RFC1034 and RFC1035 +#define k_aliases_max 15 +#define k_addrs_max 15 + +typedef struct buf_header +{ + char hostname [k_hostname_maxlen + 1]; + char * aliases [k_aliases_max + 1]; + char * addrs [k_addrs_max + 1]; +} buf_header_t; + +typedef struct result_map +{ + int done; + nss_status status; + hostent * hostent; + buf_header_t * header; + int aliases_count; + int addrs_count; + char * buffer; + int addr_idx; + // Index for addresses - grow from low end + // Index points to first empty space + int alias_idx; + // Index for aliases - grow from high end + // Index points to lowest entry + int r_errno; + int r_h_errno; +} result_map_t; + +static const struct timeval +k_select_time = { 0, 500000 }; +// 0 seconds, 500 milliseconds + +//---------- +// Local prototypes + +static nss_status +mdns_gethostbyname2 ( + const char *name, + int af, + hostent * result_buf, + char *buf, + size_t buflen, + int *errnop, + int *h_errnop + ); + + +/* + Lookup name using mDNS server + */ +static nss_status +mdns_lookup_name ( + const char * fullname, + int af, + result_map_t * result + ); + +/* + Lookup address using mDNS server + */ +static nss_status +mdns_lookup_addr ( + const void * addr, + socklen_t len, + int af, + const char * addr_str, + result_map_t * result + ); + + +/* + Handle incoming MDNS events + */ +static nss_status +handle_events (DNSServiceRef sdref, result_map_t * result, const char * str); + + +// Callback for mdns_lookup operations +//DNSServiceQueryRecordReply mdns_lookup_callback; +typedef void +mdns_lookup_callback_t +( + DNSServiceRef sdref, + DNSServiceFlags flags, + uint32_t interface_index, + DNSServiceErrorType error_code, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + void *context +); + +mdns_lookup_callback_t mdns_lookup_callback; + + +static int +init_result ( + result_map_t * result, + hostent * result_buf, + char * buf, + size_t buflen + ); + +static int +callback_body_ptr ( + const char * fullname, + result_map_t * result, + int rdlen, + const void * rdata + ); + +static void * +add_address_to_buffer (result_map_t * result, const void * data, int len); +static char * +add_alias_to_buffer (result_map_t * result, const char * data, int len); +static char * +add_hostname_len (result_map_t * result, const char * fullname, int len); +static char * +add_hostname_or_alias (result_map_t * result, const char * data, int len); + +static void * +contains_address (result_map_t * result, const void * data, int len); +static char * +contains_alias (result_map_t * result, const char * data); + + +static const char * +is_applicable_name ( + result_map_t * result, + const char * name, + char * lookup_name + ); + +static const char * +is_applicable_addr ( + result_map_t * result, + const void * addr, + int af, + char * addr_str + ); + + +// Error code functions + +static nss_status +set_err (result_map_t * result, nss_status status, int err, int herr); + +static nss_status set_err_notfound (result_map_t * result); +static nss_status set_err_bad_hostname (result_map_t * result); +static nss_status set_err_buf_too_small (result_map_t * result); +static nss_status set_err_internal_resource_full (result_map_t * result); +static nss_status set_err_system (result_map_t * result); +static nss_status set_err_mdns_failed (result_map_t * result); +static nss_status set_err_success (result_map_t * result); + + +//---------- +// Global variables + + +//---------- +// NSS functions + +nss_status +_nss_mdns_gethostbyname_r ( + const char *name, + hostent * result_buf, + char *buf, + size_t buflen, + int *errnop, + int *h_errnop + ) +{ + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: Called nss_mdns_gethostbyname with %s", + name + ); + + return + mdns_gethostbyname2 ( + name, AF_INET, result_buf, buf, buflen, errnop, h_errnop + ); +} + + +nss_status +_nss_mdns_gethostbyname2_r ( + const char *name, + int af, + hostent * result_buf, + char *buf, + size_t buflen, + int *errnop, + int *h_errnop + ) +{ + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: Called nss_mdns_gethostbyname2 with %s", + name + ); + + return + mdns_gethostbyname2 ( + name, af, result_buf, buf, buflen, errnop, h_errnop + ); +} + + +nss_status +_nss_mdns_gethostbyaddr_r ( + const void *addr, + socklen_t len, + int af, + hostent * result_buf, + char *buf, + size_t buflen, + int *errnop, + int *h_errnop + ) +{ + char addr_str [NI_MAXHOST + 1]; + result_map_t result; + int err_status; + + if (inet_ntop (af, addr, addr_str, NI_MAXHOST) == NULL) + { + const char * family = af_to_str (af); + if (family == NULL) + { + family = "Unknown"; + } + + syslog (LOG_WARNING, + "mdns: Couldn't covert address, family %d (%s) in nss_mdns_gethostbyaddr: %s", + af, + family, + strerror (errno) + ); + + // This address family never applicable to us, so return NOT_FOUND + + *errnop = ENOENT; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + if (MDNS_VERBOSE) + { + syslog (LOG_DEBUG, + "mdns: Called nss_mdns_gethostbyaddr with %s", + addr_str + ); + } + + // Initialise result + err_status = init_result (&result, result_buf, buf, buflen); + if (err_status) + { + *errnop = err_status; + *h_errnop = NETDB_INTERNAL; + return NSS_STATUS_TRYAGAIN; + } + + if (is_applicable_addr (&result, addr, af, addr_str)) + { + nss_status rv; + + rv = mdns_lookup_addr (addr, len, af, addr_str, &result); + if (rv == NSS_STATUS_SUCCESS) + { + return rv; + } + } + + // Return current error status (defaults to NOT_FOUND) + + *errnop = result.r_errno; + *h_errnop = result.r_h_errno; + return result.status; +} + + +//---------- +// Local functions + +nss_status +mdns_gethostbyname2 ( + const char *name, + int af, + hostent * result_buf, + char *buf, + size_t buflen, + int *errnop, + int *h_errnop + ) +{ + char lookup_name [k_hostname_maxlen + 1]; + result_map_t result; + int err_status; + + // Initialise result + err_status = init_result (&result, result_buf, buf, buflen); + if (err_status) + { + *errnop = err_status; + *h_errnop = NETDB_INTERNAL; + return NSS_STATUS_TRYAGAIN; + } + + if (is_applicable_name (&result, name, lookup_name)) + { + // Try using mdns + nss_status rv; + + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: Local name: %s", + name + ); + + rv = mdns_lookup_name (name, af, &result); + if (rv == NSS_STATUS_SUCCESS) + { + return rv; + } + } + + // Return current error status (defaults to NOT_FOUND) + + *errnop = result.r_errno; + *h_errnop = result.r_h_errno; + return result.status; +} + + +/* + Lookup a fully qualified hostname using the default record type + for the specified address family. + + Parameters + fullname + Fully qualified hostname. If not fully qualified the code will + still 'work', but the lookup is unlikely to succeed. + af + Either AF_INET or AF_INET6. Other families are not supported. + result + Initialised 'result' data structure. + */ +static nss_status +mdns_lookup_name ( + const char * fullname, + int af, + result_map_t * result + ) +{ + // Lookup using mDNS. + DNSServiceErrorType errcode; + DNSServiceRef sdref; + ns_type_t rrtype; + nss_status status; + + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: Attempting lookup of %s", + fullname + ); + + switch (af) + { + case AF_INET: + rrtype = kDNSServiceType_A; + result->hostent->h_length = 4; + // Length of an A record + break; + + case AF_INET6: + rrtype = kDNSServiceType_AAAA; + result->hostent->h_length = 16; + // Length of an AAAA record + break; + + default: + syslog (LOG_WARNING, + "mdns: Unsupported address family %d", + af + ); + return set_err_bad_hostname (result); + } + result->hostent->h_addrtype = af; + + errcode = + DNSServiceQueryRecord ( + &sdref, + kDNSServiceFlagsForceMulticast, // force multicast query + kDNSServiceInterfaceIndexAny, // all interfaces + fullname, // full name to query for + rrtype, // resource record type + kDNSServiceClass_IN, // internet class records + mdns_lookup_callback, // callback + result // Context - result buffer + ); + + if (errcode) + { + syslog (LOG_WARNING, + "mdns: Failed to initialise lookup, error %d", + errcode + ); + return set_err_mdns_failed (result); + } + + status = handle_events (sdref, result, fullname); + DNSServiceRefDeallocate (sdref); + return status; +} + + +/* + Reverse (PTR) lookup for the specified address. + + Parameters + addr + Either a struct in_addr or a struct in6_addr + addr_len + size of the address + af + Either AF_INET or AF_INET6. Other families are not supported. + Must match addr + addr_str + Address in format suitable for PTR lookup. + AF_INET: a.b.c.d -> d.c.b.a.in-addr.arpa + AF_INET6: reverse nibble format, x.x.x...x.ip6.arpa + result + Initialised 'result' data structure. + */ +static nss_status +mdns_lookup_addr ( + const void * addr, + socklen_t addr_len, + int af, + const char * addr_str, + result_map_t * result + ) +{ + DNSServiceErrorType errcode; + DNSServiceRef sdref; + nss_status status; + + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: Attempting lookup of %s", + addr_str + ); + + result->hostent->h_addrtype = af; + result->hostent->h_length = addr_len; + + // Query address becomes "address" in result. + if (!add_address_to_buffer (result, addr, addr_len)) + { + return result->status; + } + + result->hostent->h_name [0] = 0; + + errcode = + DNSServiceQueryRecord ( + &sdref, + kDNSServiceFlagsForceMulticast, // force multicast query + kDNSServiceInterfaceIndexAny, // all interfaces + addr_str, // address string to query for + kDNSServiceType_PTR, // pointer RRs + kDNSServiceClass_IN, // internet class records + mdns_lookup_callback, // callback + result // Context - result buffer + ); + + if (errcode) + { + syslog (LOG_WARNING, + "mdns: Failed to initialise mdns lookup, error %d", + errcode + ); + return set_err_mdns_failed (result); + } + + status = handle_events (sdref, result, addr_str); + DNSServiceRefDeallocate (sdref); + return status; +} + + +/* + Wait on result of callback, and process it when it arrives. + + Parameters + sdref + dns-sd reference + result + Initialised 'result' data structure. + str + lookup string, used for status/error reporting. + */ +static nss_status +handle_events (DNSServiceRef sdref, result_map_t * result, const char * str) +{ + int dns_sd_fd = DNSServiceRefSockFD(sdref); + int nfds = dns_sd_fd + 1; + fd_set readfds; + struct timeval tv; + int select_result; + + while (!result->done) + { + FD_ZERO(&readfds); + FD_SET(dns_sd_fd, &readfds); + + tv = k_select_time; + + select_result = + select (nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv); + if (select_result > 0) + { + if (FD_ISSET(dns_sd_fd, &readfds)) + { + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: Reply received for %s", + str + ); + DNSServiceProcessResult(sdref); + } + else + { + syslog (LOG_WARNING, + "mdns: Unexpected return from select on lookup of %s", + str + ); + } + } + else + { + // Terminate loop due to timer expiry + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: %s not found - timer expired", + str + ); + set_err_notfound (result); + break; + } + } + + return result->status; +} + + +/* + Examine incoming data and add to relevant fields in result structure. + This routine is called from DNSServiceProcessResult where appropriate. + */ +void +mdns_lookup_callback +( + DNSServiceRef sdref, + DNSServiceFlags flags, + uint32_t interface_index, + DNSServiceErrorType error_code, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + void *context +) +{ + // A single record is received + + result_map_t * result = (result_map_t *) context; + + (void)sdref; // Unused + (void)interface_index; // Unused + (void)ttl; // Unused + + if (!(flags & kDNSServiceFlagsMoreComing) ) + { + result->done = 1; + } + + if (error_code == kDNSServiceErr_NoError) + { + ns_type_t expected_rr_type = + af_to_rr (result->hostent->h_addrtype); + + // Idiot check class + if (rrclass != C_IN) + { + syslog (LOG_WARNING, + "mdns: Received bad RR class: expected %d (%s)," + " got %d (%s), RR type %d (%s)", + C_IN, + ns_class_to_str (C_IN), + rrclass, + ns_class_to_str (rrclass), + rrtype, + ns_type_to_str (rrtype) + ); + return; + } + + // If a PTR + if (rrtype == kDNSServiceType_PTR) + { + if (callback_body_ptr (fullname, result, rdlen, rdata) < 0) + return; + } + else if (rrtype == expected_rr_type) + { + if (! + add_hostname_or_alias ( + result, + fullname, + strlen (fullname) + ) + ) + { + result->done = 1; + return; + // Abort on error + } + + if (!add_address_to_buffer (result, rdata, rdlen) ) + { + result->done = 1; + return; + // Abort on error + } + } + else + { + syslog (LOG_WARNING, + "mdns: Received bad RR type: expected %d (%s)," + " got %d (%s)", + expected_rr_type, + ns_type_to_str (expected_rr_type), + rrtype, + ns_type_to_str (rrtype) + ); + return; + } + + if (result->status != NSS_STATUS_SUCCESS) + set_err_success (result); + } + else + { + // For now, dump message to syslog and continue + syslog (LOG_WARNING, + "mdns: callback returned error %d", + error_code + ); + } +} + +static int +callback_body_ptr ( + const char * fullname, + result_map_t * result, + int rdlen, + const void * rdata + ) +{ + char result_name [k_hostname_maxlen + 1]; + int rv; + + // Fullname should be .in-addr.arpa or equivalent, which we're + // not interested in. Ignore it. + + rv = dns_rdata_to_name (rdata, rdlen, result_name, k_hostname_maxlen); + if (rv < 0) + { + const char * errmsg; + + switch (rv) + { + case DNS_RDATA_TO_NAME_BAD_FORMAT: + errmsg = "mdns: PTR '%s' result badly formatted ('%s...')"; + break; + + case DNS_RDATA_TO_NAME_TOO_LONG: + errmsg = "mdns: PTR '%s' result too long ('%s...')"; + break; + + case DNS_RDATA_TO_NAME_PTR: + errmsg = "mdns: PTR '%s' result contained pointer ('%s...')"; + break; + + default: + errmsg = "mdns: PTR '%s' result conversion failed ('%s...')"; + } + + syslog (LOG_WARNING, + errmsg, + fullname, + result_name + ); + + return -1; + } + + if (MDNS_VERBOSE) + { + syslog (LOG_DEBUG, + "mdns: PTR '%s' resolved to '%s'", + fullname, + result_name + ); + } + + // Data should be a hostname + if (! + add_hostname_or_alias ( + result, + result_name, + rv + ) + ) + { + result->done = 1; + return -1; + } + + return 0; +} + + +/* + Add an address to the buffer. + + Parameter + result + Result structure to write to + data + Incoming address data buffer + Must be 'int' aligned + len + Length of data buffer (in bytes) + Must match data alignment + + Result + Pointer to start of newly written data, + or NULL on error. + If address already exists in buffer, returns pointer to that instead. + */ +static void * +add_address_to_buffer (result_map_t * result, const void * data, int len) +{ + int new_addr; + void * start; + void * temp; + + if ((temp = contains_address (result, data, len))) + { + return temp; + } + + if (result->addrs_count >= k_addrs_max) + { + // Not enough addr slots + set_err_internal_resource_full (result); + syslog (LOG_ERR, + "mdns: Internal address buffer full; increase size" + ); + return NULL; + } + + // Idiot check + if (len != result->hostent->h_length) + { + syslog (LOG_WARNING, + "mdns: Unexpected rdata length for address. Expected %d, got %d", + result->hostent->h_length, + len + ); + // XXX And continue for now. + } + + new_addr = result->addr_idx + len; + + if (new_addr > result->alias_idx) + { + // Not enough room + set_err_buf_too_small (result); + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: Ran out of buffer when adding address %d", + result->addrs_count + 1 + ); + return NULL; + } + + start = result->buffer + result->addr_idx; + memcpy (start, data, len); + result->addr_idx = new_addr; + result->header->addrs [result->addrs_count] = start; + result->addrs_count++; + result->header->addrs [result->addrs_count] = NULL; + + return start; +} + + +static void * +contains_address (result_map_t * result, const void * data, int len) +{ + int i; + + // Idiot check + if (len != result->hostent->h_length) + { + syslog (LOG_WARNING, + "mdns: Unexpected rdata length for address. Expected %d, got %d", + result->hostent->h_length, + len + ); + // XXX And continue for now. + } + + for (i = 0; result->header->addrs [i]; i++) + { + if (memcmp (result->header->addrs [i], data, len) == 0) + { + return result->header->addrs [i]; + } + } + + return NULL; +} + + +/* + Add an alias to the buffer. + + Parameter + result + Result structure to write to + data + Incoming alias (null terminated) + len + Length of data buffer (in bytes), including trailing null + + Result + Pointer to start of newly written data, + or NULL on error + If alias already exists in buffer, returns pointer to that instead. + */ +static char * +add_alias_to_buffer (result_map_t * result, const char * data, int len) +{ + int new_alias; + char * start; + char * temp; + + if ((temp = contains_alias (result, data))) + { + return temp; + } + + if (result->aliases_count >= k_aliases_max) + { + // Not enough alias slots + set_err_internal_resource_full (result); + syslog (LOG_ERR, + "mdns: Internal alias buffer full; increase size" + ); + return NULL; + } + + new_alias = result->alias_idx - len; + + if (new_alias < result->addr_idx) + { + // Not enough room + set_err_buf_too_small (result); + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: Ran out of buffer when adding alias %d", + result->aliases_count + 1 + ); + return NULL; + } + + start = result->buffer + new_alias; + memcpy (start, data, len); + result->alias_idx = new_alias; + result->header->aliases [result->aliases_count] = start; + result->aliases_count++; + result->header->aliases [result->aliases_count] = NULL; + + return start; +} + + +static char * +contains_alias (result_map_t * result, const char * alias) +{ + int i; + + for (i = 0; result->header->aliases [i]; i++) + { + if (strcmp (result->header->aliases [i], alias) == 0) + { + return result->header->aliases [i]; + } + } + + return NULL; +} + + +/* + Add fully qualified hostname to result. + + Parameter + result + Result structure to write to + fullname + Fully qualified hostname + + Result + Pointer to start of hostname buffer, + or NULL on error (usually hostname too long) + */ + +static char * +add_hostname_len (result_map_t * result, const char * fullname, int len) +{ + if (len >= k_hostname_maxlen) + { + set_err_bad_hostname (result); + syslog (LOG_WARNING, + "mdns: Hostname too long '%.*s': len %d, max %d", + len, + fullname, + len, + k_hostname_maxlen + ); + return NULL; + } + + result->hostent->h_name = + strcpy (result->header->hostname, fullname); + + return result->header->hostname; +} + + +/* + Add fully qualified name as hostname or alias. + + If hostname is not fully qualified this is not an error, but the data + returned may be not what the application wanted. + + Parameter + result + Result structure to write to + data + Incoming alias (null terminated) + len + Length of data buffer (in bytes), including trailing null + + Result + Pointer to start of newly written data, + or NULL on error + If alias or hostname already exists, returns pointer to that instead. + */ +static char * +add_hostname_or_alias (result_map_t * result, const char * data, int len) +{ + char * hostname = result->hostent->h_name; + + if (*hostname) + { + if (strcmp (hostname, data) == 0) + { + return hostname; + } + else + { + return add_alias_to_buffer (result, data, len); + } + } + else + { + return add_hostname_len (result, data, len); + } +} + + +static int +init_result ( + result_map_t * result, + hostent * result_buf, + char * buf, + size_t buflen + ) +{ + if (buflen < sizeof (buf_header_t)) + { + return ERANGE; + } + + result->hostent = result_buf; + result->header = (buf_header_t *) buf; + result->header->hostname[0] = 0; + result->aliases_count = 0; + result->header->aliases[0] = NULL; + result->addrs_count = 0; + result->header->addrs[0] = NULL; + result->buffer = buf + sizeof (buf_header_t); + result->addr_idx = 0; + result->alias_idx = buflen - sizeof (buf_header_t); + result->done = 0; + set_err_notfound (result); + + // Point hostent to the right buffers + result->hostent->h_name = result->header->hostname; + result->hostent->h_aliases = result->header->aliases; + result->hostent->h_addr_list = result->header->addrs; + + return 0; +} + +/* + Set the status in the result. + + Parameters + result + Result structure to update + status + New nss_status value + err + New errno value + herr + New h_errno value + + Returns + New status value + */ +static nss_status +set_err (result_map_t * result, nss_status status, int err, int herr) +{ + result->status = status; + result->r_errno = err; + result->r_h_errno = herr; + + return status; +} + +static nss_status +set_err_notfound (result_map_t * result) +{ + return set_err (result, NSS_STATUS_NOTFOUND, ENOENT, HOST_NOT_FOUND); +} + +static nss_status +set_err_bad_hostname (result_map_t * result) +{ + return set_err (result, NSS_STATUS_TRYAGAIN, ENOENT, NO_RECOVERY); +} + +static nss_status +set_err_buf_too_small (result_map_t * result) +{ + return set_err (result, NSS_STATUS_TRYAGAIN, ERANGE, NETDB_INTERNAL); +} + +static nss_status +set_err_internal_resource_full (result_map_t * result) +{ + return set_err (result, NSS_STATUS_RETURN, ERANGE, NO_RECOVERY); +} + +static nss_status +set_err_system (result_map_t * result) +{ + return set_err (result, NSS_STATUS_UNAVAIL, errno, NETDB_INTERNAL); +} + +static nss_status +set_err_mdns_failed (result_map_t * result) +{ + return set_err (result, NSS_STATUS_TRYAGAIN, EAGAIN, TRY_AGAIN); +} + +static nss_status +set_err_success (result_map_t * result) +{ + result->status = NSS_STATUS_SUCCESS; + return result->status; +} + + +/* + Test whether name is applicable for mdns to process, and if so copy into + lookup_name buffer (if non-NULL). + + Returns + Pointer to name to lookup up, if applicable, or NULL otherwise. + */ +static const char * +is_applicable_name ( + result_map_t * result, + const char * name, + char * lookup_name + ) +{ + int match = config_is_mdns_suffix (name); + if (match > 0) + { + if (lookup_name) + { + strncpy (lookup_name, name, k_hostname_maxlen + 1); + return lookup_name; + } + else + { + return name; + } + } + else + { + if (match < 0) + { + set_err_system (result); + } + return NULL; + } +} + +/* + Test whether address is applicable for mdns to process, and if so copy into + addr_str buffer as an address suitable for ptr lookup. + + Returns + Pointer to name to lookup up, if applicable, or NULL otherwise. + */ +static const char * +is_applicable_addr ( + result_map_t * result, + const void * addr, + int af, + char * addr_str + ) +{ + int match; + + if (!format_reverse_addr (af, addr, -1, addr_str)) + { + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: Failed to create reverse address" + ); + return NULL; + } + + if (MDNS_VERBOSE) + syslog (LOG_DEBUG, + "mdns: Reverse address: %s", + addr_str + ); + + match = config_is_mdns_suffix (addr_str); + if (match > 0) + { + return addr_str; + } + else + { + if (match < 0) + { + set_err_system (result); + } + return NULL; + } +} + +//---------- +// Types and Constants + +const char * k_conf_file = "/etc/nss_mdns.conf"; +#define CONF_LINE_SIZE 1024 + +const char k_comment_char = '#'; + +const char * k_keyword_domain = "domain"; + +const char * k_default_domains [] = +{ + "local", + "254.169.in-addr.arpa", + "8.e.f.ip6.int", + "8.e.f.ip6.arpa", + "9.e.f.ip6.int", + "9.e.f.ip6.arpa", + "a.e.f.ip6.int", + "a.e.f.ip6.arpa", + "b.e.f.ip6.int", + "b.e.f.ip6.arpa", + NULL + // Always null terminated +}; + +// Linked list of domains +typedef struct domain_entry +{ + char * domain; + struct domain_entry * next; +} domain_entry_t; + + +// Config +typedef struct +{ + domain_entry_t * domains; +} config_t; + +const config_t k_empty_config = +{ + NULL +}; + + +// Context - tracks position in config file, used for error reporting +typedef struct +{ + const char * filename; + int linenum; +} config_file_context_t; + + +//---------- +// Local prototypes + +static errcode_t +load_config (config_t * conf); + +static errcode_t +process_config_line ( + config_t * conf, + char * line, + config_file_context_t * context + ); + +static char * +get_next_word (char * input, char **next); + +static errcode_t +default_config (config_t * conf); + +static errcode_t +add_domain (config_t * conf, const char * domain); + +static int +contains_domain (const config_t * conf, const char * domain); + +static int +contains_domain_suffix (const config_t * conf, const char * addr); + + +//---------- +// Global variables + +static config_t * g_config = NULL; +// Configuration info + +pthread_mutex_t g_config_mutex = +#ifdef PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP + PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; +#else + PTHREAD_MUTEX_INITIALIZER; +#endif + + +//---------- +// Configuration functions + + +/* + Initialise the configuration from the config file. + + Returns + 0 success + non-zero error code on failure + */ +errcode_t +init_config () +{ + if (g_config) + { + /* + Safe to test outside mutex. + If non-zero, initialisation is complete and g_config can be + safely used read-only. If zero, then we do proper mutex + testing before initialisation. + */ + return 0; + } + else + { + int errcode = -1; + int presult; + config_t * temp_config; + + // Acquire mutex + presult = pthread_mutex_lock (&g_config_mutex); + if (presult) + { + syslog (LOG_ERR, + "mdns: Fatal mutex lock error in nss_mdns:init_config, %s:%d: %d: %s", + __FILE__, __LINE__, presult, strerror (presult) + ); + return presult; + } + + // Test again now we have mutex, in case initialisation occurred while + // we were waiting + if (!g_config) + { + temp_config = (config_t *) malloc (sizeof (config_t)); + if (temp_config) + { + // Note: This code will leak memory if initialisation fails + // repeatedly. This should only happen in the case of a memory + // error, so I'm not sure if it's a meaningful problem. - AW + *temp_config = k_empty_config; + errcode = load_config (temp_config); + + if (!errcode) + { + g_config = temp_config; + } + } + else + { + syslog (LOG_ERR, + "mdns: Can't allocate memory in nss_mdns:init_config, %s:%d", + __FILE__, __LINE__ + ); + errcode = errno; + } + } + + presult = pthread_mutex_unlock (&g_config_mutex); + if (presult) + { + syslog (LOG_ERR, + "mdns: Fatal mutex unlock error in nss_mdns:init_config, %s:%d: %d: %s", + __FILE__, __LINE__, presult, strerror (presult) + ); + errcode = presult; + } + + return errcode; + } +} + + +int +config_is_mdns_suffix (const char * name) +{ + int errcode = init_config (); + if (!errcode) + { + return contains_domain_suffix (g_config, name); + } + else + { + errno = errcode; + return -1; + } +} + + +//---------- +// Local functions + +static errcode_t +load_config (config_t * conf) +{ + FILE * cf; + char line [CONF_LINE_SIZE]; + config_file_context_t context; + + context.filename = k_conf_file; + context.linenum = 0; + + + cf = fopen (context.filename, "r"); + if (!cf) + { + syslog (LOG_INFO, + "mdns: Couldn't open nss_mdns configuration file %s, using default.", + context.filename + ); + return default_config (conf); + } + + while (fgets (line, CONF_LINE_SIZE, cf)) + { + int errcode; + context.linenum++; + errcode = process_config_line (conf, line, &context); + if (errcode) + { + // Critical error, give up + fclose(cf); + return errcode; + } + } + + fclose (cf); + + return 0; +} + + +/* + Parse a line of the configuration file. + For each keyword recognised, perform appropriate handling. + If the keyword is not recognised, print a message to syslog + and continue. + + Returns + 0 success, or recoverable config file error + non-zero serious system error, processing aborted + */ +static errcode_t +process_config_line ( + config_t * conf, + char * line, + config_file_context_t * context + ) +{ + char * curr = line; + char * word; + + word = get_next_word (curr, &curr); + if (!word || word [0] == k_comment_char) + { + // Nothing interesting on this line + return 0; + } + + if (strcmp (word, k_keyword_domain) == 0) + { + word = get_next_word (curr, &curr); + if (word) + { + int errcode = add_domain (conf, word); + if (errcode) + { + // something badly wrong, bail + return errcode; + } + + if (get_next_word (curr, NULL)) + { + syslog (LOG_WARNING, + "%s, line %d: ignored extra text found after domain", + context->filename, + context->linenum + ); + } + } + else + { + syslog (LOG_WARNING, + "%s, line %d: no domain specified", + context->filename, + context->linenum + ); + } + } + else + { + syslog (LOG_WARNING, + "%s, line %d: unknown keyword %s - skipping", + context->filename, + context->linenum, + word + ); + } + + return 0; +} + + +/* + Get next word (whitespace separated) from input string. + A null character is written into the first whitespace character following + the word. + + Parameters + input + Input string. This string is modified by get_next_word. + next + If non-NULL and the result is non-NULL, a pointer to the + character following the end of the word (after the null) + is written to 'next'. + If no word is found, the original value is unchanged. + If the word extended to the end of the string, 'next' points + to the trailling NULL. + It is safe to pass 'str' as 'input' and '&str' as 'next'. + Returns + Pointer to the first non-whitespace character (and thus word) found. + if no word is found, returns NULL. + */ +static char * +get_next_word (char * input, char **next) +{ + char * curr = input; + char * result; + + while (isspace (*curr)) + { + curr++; + } + + if (*curr == 0) + { + return NULL; + } + + result = curr; + while (*curr && !isspace (*curr)) + { + curr++; + } + if (*curr) + { + *curr = 0; + if (next) + { + *next = curr+1; + } + } + else + { + if (next) + { + *next = curr; + } + } + + return result; +} + + +static errcode_t +default_config (config_t * conf) +{ + int i; + for (i = 0; k_default_domains [i]; i++) + { + int errcode = + add_domain (conf, k_default_domains [i]); + if (errcode) + { + // Something has gone (badly) wrong - let's bail + return errcode; + } + } + + return 0; +} + + +static errcode_t +add_domain (config_t * conf, const char * domain) +{ + if (!contains_domain (conf, domain)) + { + domain_entry_t * d = + (domain_entry_t *) malloc (sizeof (domain_entry_t)); + if (!d) + { + syslog (LOG_ERR, + "mdns: Can't allocate memory in nss_mdns:init_config, %s:%d", + __FILE__, __LINE__ + ); + return ENOMEM; + } + + d->domain = strdup (domain); + if (!d->domain) + { + syslog (LOG_ERR, + "mdns: Can't allocate memory in nss_mdns:init_config, %s:%d", + __FILE__, __LINE__ + ); + free (d); + return ENOMEM; + } + d->next = conf->domains; + conf->domains = d; + } + + return 0; +} + + +static int +contains_domain (const config_t * conf, const char * domain) +{ + const domain_entry_t * curr = conf->domains; + + while (curr != NULL) + { + if (strcasecmp (curr->domain, domain) == 0) + { + return 1; + } + + curr = curr->next; + } + + return 0; +} + + +static int +contains_domain_suffix (const config_t * conf, const char * addr) +{ + const domain_entry_t * curr = conf->domains; + + while (curr != NULL) + { + if (cmp_dns_suffix (addr, curr->domain) > 0) + { + return 1; + } + + curr = curr->next; + } + + return 0; +} + +//---------- +// Types and Constants + +static const char * k_local_suffix = "local"; +static const char k_dns_separator = '.'; + +static const unsigned int k_label_maxlen = DNS_LABEL_MAXLEN; +// Label entries longer than this are actually pointers. + +typedef struct +{ + int value; + const char * name; + const char * comment; +} table_entry_t; + +static const table_entry_t k_table_af [] = +{ + { AF_UNSPEC, NULL, NULL }, + { AF_LOCAL, "LOCAL", NULL }, + { AF_UNIX, "UNIX", NULL }, + { AF_INET, "INET", NULL }, + { AF_INET6, "INET6", NULL } +}; +static const int k_table_af_size = + sizeof (k_table_af) / sizeof (*k_table_af); + +static const char * k_table_ns_class [] = +{ + NULL, + "IN" +}; +static const int k_table_ns_class_size = + sizeof (k_table_ns_class) / sizeof (*k_table_ns_class); + +static const char * k_table_ns_type [] = +{ + NULL, + "A", + "NS", + "MD", + "MF", + "CNAME", + "SOA", + "MB", + "MG", + "MR", + "NULL", + "WKS", + "PTR", + "HINFO", + "MINFO", + "MX", + "TXT", + "RP", + "AFSDB", + "X25", + "ISDN", + "RT", + "NSAP", + NULL, + "SIG", + "KEY", + "PX", + "GPOS", + "AAAA", + "LOC", + "NXT", + "EID", + "NIMLOC", + "SRV", + "ATMA", + "NAPTR", + "KX", + "CERT", + "A6", + "DNAME", + "SINK", + "OPT" +}; +static const int k_table_ns_type_size = + sizeof (k_table_ns_type) / sizeof (*k_table_ns_type); + + +//---------- +// Local prototypes + +static int +simple_table_index (const char * table [], int size, const char * str); + +static int +table_index_name (const table_entry_t table [], int size, const char * str); + +static int +table_index_value (const table_entry_t table [], int size, int n); + + +//---------- +// Global variables + + +//---------- +// Util functions + +int +count_dots (const char * name) +{ + int count = 0; + int i; + for (i = 0; name[i]; i++) + { + if (name [i] == k_dns_separator) + count++; + } + + return count; +} + + +int +islocal (const char * name) +{ + return cmp_dns_suffix (name, k_local_suffix) > 0; +} + + +int +rr_to_af (ns_type_t rrtype) +{ + switch (rrtype) + { + case kDNSServiceType_A: + return AF_INET; + + case kDNSServiceType_AAAA: + return AF_INET6; + + default: + return AF_UNSPEC; + } +} + + +ns_type_t +af_to_rr (int af) +{ + switch (af) + { + case AF_INET: + return kDNSServiceType_A; + + case AF_INET6: + return kDNSServiceType_AAAA; + + default: + //return ns_t_invalid; + return 0; + } +} + + +int +str_to_af (const char * str) +{ + int result = + table_index_name (k_table_af, k_table_af_size, str); + if (result < 0) + result = 0; + + return k_table_af [result].value; +} + + +ns_class_t +str_to_ns_class (const char * str) +{ + return (ns_class_t) + simple_table_index (k_table_ns_class, k_table_ns_class_size, str); +} + + +ns_type_t +str_to_ns_type (const char * str) +{ + return (ns_type_t) + simple_table_index (k_table_ns_type, k_table_ns_type_size, str); +} + + +const char * +af_to_str (int in) +{ + int result = + table_index_value (k_table_af, k_table_af_size, in); + if (result < 0) + result = 0; + + return k_table_af [result].name; +} + + +const char * +ns_class_to_str (ns_class_t in) +{ + if (in < k_table_ns_class_size) + return k_table_ns_class [in]; + else + return NULL; +} + + +const char * +ns_type_to_str (ns_type_t in) +{ + if (in < k_table_ns_type_size) + return k_table_ns_type [in]; + else + return NULL; +} + + +char * +format_reverse_addr_in ( + const struct in_addr * addr, + int prefixlen, + char * buf + ) +{ + char * curr = buf; + int i; + + const uint8_t * in_addr_a = (uint8_t *) addr; + + if (prefixlen > 32) + return NULL; + if (prefixlen < 0) + prefixlen = 32; + + i = (prefixlen + 7) / 8; + // divide prefixlen into bytes, rounding up + + while (i > 0) + { + i--; + curr += sprintf (curr, "%d.", in_addr_a [i]); + } + sprintf (curr, "in-addr.arpa"); + + return buf; +} + + +char * +format_reverse_addr_in6 ( + const struct in6_addr * addr, + int prefixlen, + char * buf + ) +{ + char * curr = buf; + int i; + + const uint8_t * in_addr_a = (uint8_t *) addr; + + if (prefixlen > 128) + return NULL; + if (prefixlen < 0) + prefixlen = 128; + + i = (prefixlen + 3) / 4; + // divide prefixlen into nibbles, rounding up + + // Special handling for first + if (i % 2) + { + curr += sprintf (curr, "%d.", (in_addr_a [i/2] >> 4) & 0x0F); + } + i >>= 1; + // Convert i to bytes (divide by 2) + + while (i > 0) + { + uint8_t val; + + i--; + val = in_addr_a [i]; + curr += sprintf (curr, "%x.%x.", val & 0x0F, (val >> 4) & 0x0F); + } + sprintf (curr, "ip6.arpa"); + + return buf; +} + + +char * +format_reverse_addr ( + int af, + const void * addr, + int prefixlen, + char * buf + ) +{ + switch (af) + { + case AF_INET: + return + format_reverse_addr_in ( + (struct in_addr *) addr, prefixlen, buf + ); + break; + + case AF_INET6: + return + format_reverse_addr_in6 ( + (struct in6_addr *) addr, prefixlen, buf + ); + break; + + default: + return NULL; + } +} + + +int +cmp_dns_suffix (const char * name, const char * domain) +{ + const char * nametail; + const char * domaintail; + + // Idiot checks + if (*name == 0 || *name == k_dns_separator) + { + // Name can't be empty or start with separator + return CMP_DNS_SUFFIX_BAD_NAME; + } + + if (*domain == 0) + { + return CMP_DNS_SUFFIX_SUCCESS; + // trivially true + } + + if (*domain == k_dns_separator) + { + // drop leading separator from domain + domain++; + if (*domain == k_dns_separator) + { + return CMP_DNS_SUFFIX_BAD_DOMAIN; + } + } + + // Find ends of strings + for (nametail = name; *nametail; nametail++) + ; + for (domaintail = domain; *domaintail; domaintail++) + ; + + // Shuffle back to last real character, and drop any trailing '.' + // while we're at it. + nametail--; + if (*nametail == k_dns_separator) + { + nametail--; + if (*nametail == k_dns_separator) + { + return CMP_DNS_SUFFIX_BAD_NAME; + } + } + domaintail--; + if (*domaintail == k_dns_separator) + { + domaintail--; + if (*domaintail == k_dns_separator) + { + return CMP_DNS_SUFFIX_BAD_DOMAIN; + } + } + + // Compare. + while ( + nametail >= name + && domaintail >= domain + && tolower(*nametail) == tolower(*domaintail)) + { + nametail--; + domaintail--; + } + + /* A successful finish will be one of the following: + (leading and trailing . ignored) + + name : domain2.domain1 + domain: domain2.domain1 + ^ + + name : domain3.domain2.domain1 + domain: domain2.domain1 + ^ + */ + if ( + domaintail < domain + && (nametail < name || *nametail == k_dns_separator) + ) + { + return CMP_DNS_SUFFIX_SUCCESS; + } + else + { + return CMP_DNS_SUFFIX_FAILURE; + } +} + + +static int +dns_rdata_to_name (const unsigned char * rdata, int rdlen, char * name, unsigned int name_len) +{ + int i = 0; + // Index into 'name' + const unsigned char * rdata_curr = rdata; + + if (rdlen == 0) return DNS_RDATA_TO_NAME_BAD_FORMAT; + + /* + In RDATA, a DNS name is stored as a series of labels. + Each label consists of a length octet (max value 63) + followed by the data for that label. + The series is terminated with a length 0 octet. + A length octet beginning with bits 11 is a pointer to + somewhere else in the payload, but we don't support these + since we don't have access to the entire payload. + + See RFC1034 section 3.1 and RFC1035 section 3.1. + */ + while (1) + { + unsigned int term_len = *rdata_curr; + rdata_curr++; + + if (term_len == 0) + { + break; + // 0 length record terminates label + } + else if (term_len > k_label_maxlen) + { + name [i] = 0; + return DNS_RDATA_TO_NAME_PTR; + } + else if (rdata_curr + term_len > rdata + rdlen) + { + name [i] = 0; + return DNS_RDATA_TO_NAME_BAD_FORMAT; + } + + if (name_len < i + term_len + 1) + // +1 is separator + { + name [i] = 0; + return DNS_RDATA_TO_NAME_TOO_LONG; + } + + memcpy (name + i, rdata_curr, term_len); + + i += term_len; + rdata_curr += term_len; + + name [i] = k_dns_separator; + i++; + } + + name [i] = 0; + return i; +} + + +//---------- +// Local functions + +/* + Find the index of an string entry in a table. A case insenitive match + is performed. If no entry is found, 0 is returned. + + Parameters + table + Lookup table + Table entries may be NULL. NULL entries will never match. + size + number of entries in table + str + lookup string + + Result + index of first matching entry, or 0 if no matches + */ +static int +simple_table_index (const char * table [], int size, const char * str) +{ + int i; + for (i = 0; i < size; i++) + { + if ( + table [i] + && (strcasecmp (table [i], str) == 0) + ) + { + return i; + } + } + + return 0; +} + + +/* + Find the index of a name in a table. + + Parameters + table + array of table_entry_t records. The name field is compared + (ignoring case) to the input string. + size + number of entries in table + str + lookup string + + Result + index of first matching entry, or -1 if no matches + */ +static int +table_index_name (const table_entry_t table [], int size, const char * str) +{ + int i; + for (i = 0; i < size; i++) + { + if ( + table [i].name + && (strcasecmp (table [i].name, str) == 0) + ) + { + return i; + } + } + + return -1; +} + + +/* + Find the index of a value a table. + + Parameters + table + array of table_entry_t records. The value field is compared to + the input value + size + number of entries in table + n + lookup value + + Result + index of first matching entry, or -1 if no matches + */ +static int +table_index_value (const table_entry_t table [], int size, int n) +{ + int i; + for (i = 0; i < size; i++) + { + if (table [i].value == n) + { + return i; + } + } + + return -1; +} diff --git a/mDNSResponder/mDNSPosix/nss_mdns.conf b/mDNSResponder/mDNSPosix/nss_mdns.conf new file mode 100755 index 00000000..bdcdad7c --- /dev/null +++ b/mDNSResponder/mDNSPosix/nss_mdns.conf @@ -0,0 +1,13 @@ +# Defaut configuration file for nss_mdns + +# Applicable domains +domain local +domain 254.169.in-addr.arpa +domain 8.e.f.ip6.int +domain 9.e.f.ip6.int +domain a.e.f.ip6.int +domain b.e.f.ip6.int +domain 8.e.f.ip6.arpa +domain 9.e.f.ip6.arpa +domain a.e.f.ip6.arpa +domain b.e.f.ip6.arpa diff --git a/mDNSResponder/mDNSPosix/nss_mdns.conf.5 b/mDNSResponder/mDNSPosix/nss_mdns.conf.5 new file mode 100755 index 00000000..7dbefa22 --- /dev/null +++ b/mDNSResponder/mDNSPosix/nss_mdns.conf.5 @@ -0,0 +1,135 @@ +.\" +.\" See section LICENSE for license information. +.\" +.Dd June 15, 2004 +.Dt NSS_MDNS.CONF 5 +.Os +.Sh NAME +.Nm nss_mdns.conf +.Nd configuration file for +.Xr libnss_mdns 8 . +.Sh DESCRIPTION +This file describes the domains that +.Xr libnss_mdns 8 +is to support. If a lookup domain is not in this list, then +.Li NSS_STATUS_NOTFOUND +will be returned to libc and processing will continue according to +.Xr nsswitch.conf 5 . +.Ss Configuration file format +Lines containing only whitespace or lines whose first non-whitespace character is +.Ql # +are ignored. No single line may be greater than 1023 characters plus end-of-line. +.Pp +.D1 Ic domain Ar x.y.z +.Pp +Enable use of +.Xr libnss_mdns 8 +to lookup DNS entries in the +.Ql x.y.z +domain. Leading and trailing dots are dropped. +.Pp +Reverse (PTR) lookups are enabled using their DNS names. IPv6 names use +.Qq nibble format . +.Pp +.Dl domain 254.169.in-addr.arpa +.Dl domain 0.8.e.f.ip6.arpa +.Ss Default configuration +If the configuration file cannot be found then the following is assumed. +.Bd -literal -offset indent +domain local +domain 0.8.e.f.ip6.int +domain 0.8.e.f.ip6.arpa +domain 254.169.in-addr.arpa +.Ed +.Sh SEE ALSO +.\" Cross-references should be ordered by section (low to high), then in +.\" alphabetical order. +.Xr nsswitch.conf 5 , +.Xr libnss_mdns 8 +.\" .Sh STANDARDS +.Sh HISTORY +.Xr libnss_mdns 8 +was originally written for +.An NICTA Bq http://www.nicta.com.au/ . +.Sh AUTHORS +.An "Andrew White" Bq Andrew.White@nicta.com.au +.Sh LICENSE +This software is licensed under the NICTA Public Source License version 1.0 +.Ss NICTA Public Software Licence +Version 1.0 +.Pp +Copyright 2004 National ICT Australia Ltd +.Pp +All rights reserved. +.Pp +By this licence, National ICT Australia Ltd (NICTA) grants permission, +free of charge, to any person who obtains a copy of this software +and any associated documentation files ("the Software") to use and +deal with the Software in source code and binary forms without +restriction, with or without modification, and to permit persons +to whom the Software is furnished to do so, provided that the +following conditions are met: +.Bl -bullet +.It +Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimers. +.It +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimers in +the documentation and/or other materials provided with the +distribution. +.It +The name of NICTA may not be used to endorse or promote products +derived from this Software without specific prior written permission. +.El +.Pp +EXCEPT AS EXPRESSLY STATED IN THIS LICENCE AND TO THE FULL EXTENT +PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS-IS" AND +NICTA MAKES NO REPRESENTATIONS, WARRANTIES OR CONDITIONS OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY +REPRESENTATIONS, WARRANTIES OR CONDITIONS REGARDING THE CONTENTS +OR ACCURACY OF THE SOFTWARE, OR OF TITLE, MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, THE ABSENCE OF LATENT +OR OTHER DEFECTS, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR +NOT DISCOVERABLE. +.Pp +TO THE FULL EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL +NICTA BE LIABLE ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, +NEGLIGENCE) FOR ANY LOSS OR DAMAGE WHATSOEVER, INCLUDING (WITHOUT +LIMITATION) LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR +CORRUPTION OF DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, +OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC LOSS; +OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR +EXEMPLARY DAMAGES ARISING OUT OF OR IN CONNECTION WITH THIS LICENCE, +THE SOFTWARE OR THE USE OF THE SOFTWARE, EVEN IF NICTA HAS BEEN +ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +.Pp +If applicable legislation implies warranties or conditions, or +imposes obligations or liability on NICTA in respect of the Software +that cannot be wholly or partly excluded, restricted or modified, +NICTA's liability is limited, to the full extent permitted by the +applicable legislation, at its option, to: +.Pp +.Bl -tag -width "a." -compact +.It a. +in the case of goods, any one or more of the following: +.Bl -tag -width "iii." -compact +.It i. +the replacement of the goods or the supply of equivalent goods; +.It ii. +the repair of the goods; +.It iii. +the payment of the cost of replacing the goods or of acquiring +equivalent goods; +.It iv. +the payment of the cost of having the goods repaired; or +.El +.It b. +in the case of services: +.Bl -tag -width "iii." -compact +.It i. +the supplying of the services again; or +.It ii. +the payment of the cost of having the services supplied again. +.El +.El diff --git a/mDNSResponder/mDNSPosix/parselog.py b/mDNSResponder/mDNSPosix/parselog.py new file mode 100755 index 00000000..f39d0f23 --- /dev/null +++ b/mDNSResponder/mDNSPosix/parselog.py @@ -0,0 +1,247 @@ +#!/usr/bin/python +# Emacs settings: -*- 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. +# +# parselog.py, written and contributed by Kevin Marks +# +# Requires OS X 10.3 Panther or later, for Python and Core Graphics Python APIs +# Invoke from the command line with "parselog.py fname" where fname is a log file made by mDNSNetMonitor +# +# Caveats: +# It expects plain ASCII, and doesn't handle spaces in record names very well right now +# There's a procedure you can follow to 'sanitize' an mDNSNetMonitor log file to make it more paletable to parselog.py: +# 1. Run mDNSNetMonitor in a terminal window. +# When you have enough traffic, type Ctrl-C and save the content of the terminal window to disk. +# Alternatively, you can use "mDNSNetMonitor > logfile" to write the text directly to a file. +# You now have a UTF-8 text file. +# 2. Open the UTF-8 text file using BBEdit or some other text editor. +# (These instructions are for BBEdit, which I highly recommend you use when doing this.) +# 3. Make sure BBEdit correctly interprets the file as UTF-8. +# Either set your "Text Files Opening" preference to "UTF-8 no BOM", and drop the file onto BBEdit, +# or manually open the File using "File -> Open" and make sure the "Read As" setting is set to "UTF-8 no BOM" +# Check in the document pulldown menu in the window toolbar to make sure that it says "Encoding: UTF-8 no BOM" +# 4. Use "Tools -> Convert to ASCII" to replace all special characters with their seven-bit ascii equivalents. +# (e.g. curly quotes are converted to straight quotes) +# 5. Do a grep search and replace. (Cmd-F; make sure Grep checkbox is turned on.) +# Enter this search text : ^(.................\(................\S*) (.* -> .*)$ +# Enter this replacement text: \1-\2 +# Click "Replace All" +# Press Cmd-Opt-= repeatedly until there are no more instances to be replaced. +# You now have text file with all spaces in names changed to hyphens +# 6. Save the new file. You can save it as "UTF-8 no BOM", or as "Mac Roman". It really doesn't matter which -- +# the file now contains only seven-bit ascii, so it's all the same no matter how you save it. +# 7. Run "parselog.py fname" +# 8. Open the resulting fname.pdf file with a PDF viewer like Preview on OS X +# +# Key to what you see: +# Time is on the horizontal axis +# Individual machines are shown on the vertical axis +# Filled red circle: Normal query Hollow red circle: Query requesting unicast reply +# Filled orange circle: Probe (service starting) Hollow orange circle: First probe (requesting unicast reply) +# Filled green circle: Normal answer Hollow green circle: Goodbye message (record going away) +# Hollow blue circle: Legacy query (from old client) + +from CoreGraphics import * +import math # for pi + +import string +import sys, os +import re + +def parselog(inFile): + f = open(inFile) + hunt = 'getTime' + ipList = {} + querySource = {} + plotPoints = [] + maxTime=0 + minTime = 36*60*60 + spaceExp = re.compile(r'\s+') + print "Reading " + inFile + while 1: + lines = f.readlines(100000) + if not lines: + break + for line in lines: + if (hunt == 'skip'): + if (line == '\n' or line == '\r' or line ==''): + hunt = 'getTime' +# else: +# msg = ("skipped" , line) +# print msg + elif (hunt == 'getTime'): + if (line == "^C\n" ): + break + time = line.split(' ')[0].split(':') + if (len(time)<3): + #print "bad time, skipping",time + hunt = 'skip' + else: + hunt = 'getIP' + #print (("getTime:%s" % (line)), time) + elif (hunt == 'getIP'): + ip = line.split(' ',1) + ip = ip[0] + secs=0 + for t in time: + secs = secs*60 +float(t) + if (secs>maxTime): + maxTime=secs + if (secs" symbol and following rdata + #print qaList + if (qaList[0] == ip): + if (qaList[1] == '(QU)' or qaList[1] == '(LQ)' or qaList[1] == '(PU)'): + plotPoints.append([secs, ipList[ip][0], (qaList[1])[1:-1]]) + elif (qaList[1] == '(QM)'): + plotPoints.append([secs, ipList[ip][0], (qaList[1])[1:-1]]) + querySource[qaList[3]] = len(plotPoints)-1 + elif (qaList[1] == '(PM)'): + plotPoints.append([secs, ipList[ip][0], (qaList[1])[1:-1]]) + querySource[qaList[4]] = len(plotPoints)-1 + elif (qaList[1] == '(AN)' or qaList[1] == '(AN+)' or qaList[1] == '(DE)'): + plotPoints.append([secs, ipList[ip][0], (qaList[1])[1:-1]]) + try: + theQuery = querySource[qaList[4]] + theDelta = secs - plotPoints[theQuery][0] + if (theDelta < 1.0): + plotPoints[-1].append(querySource[qaList[4]]) + #print "Answer AN+ %s points to %d" % (qaList[4],querySource[qaList[4]]) + except: + #print "Couldn't find any preceeding question for", qaList + pass + elif (qaList[1] != '(KA)' and qaList[1] != '(AD)' and qaList[1] != '(AD+)'): + print "Operation unknown", qaList + + if (qaList[1] == '(AN)' or qaList[1] == '(AN+)' or qaList[1] == '(AD)' or qaList[1] == '(AD+)'): + if (qaList[2] == 'HINFO'): + ipList[ip][1] = qaList[4] + ipList[ip][2] = string.join(qaList[6:]) + #print ipList[ip][1] + elif (qaList[2] == 'AAAA'): + if (ipList[ip][1] == ""): + ipList[ip][1] = qaList[4] + ipList[ip][2] = "Panther" + elif (qaList[2] == 'Addr'): + if (ipList[ip][1] == ""): + ipList[ip][1] = qaList[4] + ipList[ip][2] = "Jaguar" + else: + if (line == '\n'): + hunt = 'getTime' + else: + hunt = 'skip' + f.close() + #print plotPoints + #print querySource + #width=20.0*(maxTime-minTime) + if (maxTime < minTime + 10.0): + maxTime = minTime + 10.0 + typesize = 12 + width=20.0*(maxTime-minTime) + pageHeight=(len(ipList)+1) * typesize + scale = width/(maxTime-minTime) + leftMargin = typesize * 60 + bottomMargin = typesize + pageRect = CGRectMake (-leftMargin, -bottomMargin, leftMargin + width, bottomMargin + pageHeight) # landscape + outFile = "%s.pdf" % (".".join(inFile.split('.')[:-1])) + c = CGPDFContextCreateWithFilename (outFile, pageRect) + print "Writing " + outFile + ourColourSpace = c.getColorSpace() + # QM/QU red solid/hollow + # PM/PU orange solid/hollow + # LQ blue hollow + # AN/DA green solid/hollow + #colourLookup = {"L":(0.0,0.0,.75), "Q":(.75,0.0,0.0), "P":(.75,0.5,0.0), "A":(0.0,0.75,0.0), "D":(0.0,0.75,0.0), "?":(.25,0.25,0.25)} + colourLookup = {"L":(0.0,0.0,1.0), "Q":(1.0,0.0,0.0), "P":(1.0,0.8,0.0), "A":(0.0,1.0,0.0), "D":(0.0,1.0,0.0), "?":(1.0,1.0,1.0)} + c.beginPage (pageRect) + c.setRGBFillColor(.75,0.0,0.0,1.0) + c.setRGBStrokeColor(.25,0.75,0.25,1.0) + c.setLineWidth(0.25) + for point in plotPoints: + #c.addArc((point[0]-minTime)*scale,point[1]*typesize+6,5,0,2*math.pi,1) + c.addArc((point[0]-minTime)*scale,point[1]*typesize+6,typesize/4,0,2*math.pi,1) + theColour = colourLookup[(point[2])[0]] + if (((point[2])[0]) != "L") and (((point[2])[0]) != "Q") and (((point[2])[0]) != "P") and (((point[2])[0]) != "A") and (((point[2])[0]) != "D"): + print "Unknown", point + if ((point[2])[-1] == "M" or (point[2])[0]== "A"): + c.setRGBFillColor(theColour[0],theColour[1],theColour[2],.5) + c.fillPath() + else: + c.setRGBStrokeColor(theColour[0],theColour[1],theColour[2],.5) + c.setLineWidth(1.0) + c.strokePath() + c.setRGBStrokeColor(.25,0.75,0.25,1.0) + c.setLineWidth(0.25) + for index in point[3:]: + c.beginPath() + c.moveToPoint((point[0]-minTime)*scale,point[1]*typesize+6) + c.addLineToPoint(((plotPoints[index])[0]-minTime)*scale,(plotPoints[index])[1]*typesize+6) + c.closePath() + c.strokePath() + c.setRGBFillColor (0,0,0, 1) + c.setTextDrawingMode (kCGTextFill) + c.setTextMatrix (CGAffineTransformIdentity) + c.selectFont ('Gill Sans', typesize, kCGEncodingMacRoman) + c.setRGBStrokeColor(0.25,0.0,0.0,1.0) + c.setLineWidth(0.1) + for ip,[height,hname,hinfo] in ipList.items(): + c.beginPath() + c.moveToPoint(pageRect.origin.x,height*typesize+6) + c.addLineToPoint(width,height*typesize+6) + c.closePath() + c.strokePath() + c.showTextAtPoint(pageRect.origin.x + 2, height*typesize + 2, ip, len(ip)) + c.showTextAtPoint(pageRect.origin.x + 2 + typesize*8, height*typesize + 2, hname, len(hname)) + c.showTextAtPoint(pageRect.origin.x + 2 + typesize*25, height*typesize + 2, hinfo, len(hinfo)) + for time in range (int(minTime),int(maxTime)+1): + c.beginPath() + c.moveToPoint((time-minTime)*scale,pageRect.origin.y) + c.addLineToPoint((time-minTime)*scale,pageHeight) + c.closePath() + if (int(time) % 10 == 0): + theHours = time/3600 + theMinutes = time/60 % 60 + theSeconds = time % 60 + theTimeString = '%d:%02d:%02d' % (theHours, theMinutes, theSeconds) + # Should measure string width, but don't know how to do that + theStringWidth = typesize * 3.5 + c.showTextAtPoint((time-minTime)*scale - theStringWidth/2, pageRect.origin.y + 2, theTimeString, len(theTimeString)) + c.setLineWidth(0.3) + else: + c.setLineWidth(0.1) + c.strokePath() + c.endPage() + c.finish() + + +for arg in sys.argv[1:]: + parselog(arg) -- cgit v1.2.3