| /* |
| * Copyright (c) 2009 Brent Fulgham. All rights reserved. |
| * |
| * This source code is a modified version of the CoreFoundation sources released by Apple Inc. under |
| * the terms of the BSD-style license (see below). |
| * |
| * For information about changes from the original Apple source release can be found by reviewing the |
| * source control system for the project at https://sourceforge.net/svn/?group_id=246198. |
| * |
| */ |
| /* |
| File: DNSServiceMetaQuery.c |
| |
| Contains: Sample code which shows how to discover all Bonjour service types |
| being advertised on the local network. |
| |
| Copyright: (c) Copyright 2004-2005 Apple Computer, Inc. All rights reserved. |
| |
| Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
| ("Apple") in consideration of your agreement to the following terms, and your |
| use, installation, modification or redistribution of this Apple software |
| constitutes acceptance of these terms. If you do not agree with these terms, |
| please do not use, install, modify or redistribute this Apple software. |
| |
| In consideration of your agreement to abide by the following terms, and subject |
| to these terms, Apple grants you a personal, non-exclusive license, under Apple's |
| copyrights in this original Apple software (the "Apple Software"), to use, |
| reproduce, modify and redistribute the Apple Software, with or without |
| modifications, in source and/or binary forms; provided that if you redistribute |
| the Apple Software in its entirety and without modifications, you must retain |
| this notice and the following text and disclaimers in all such redistributions of |
| the Apple Software. Neither the name, trademarks, service marks or logos of |
| Apple Computer, Inc. may be used to endorse or promote products derived from the |
| Apple Software without specific prior written permission from Apple. Except as |
| expressly stated in this notice, no other rights or licenses, express or implied, |
| are granted by Apple herein, including but not limited to any patent rights that |
| may be infringed by your derivative works or by other works in which the Apple |
| Software may be incorporated. |
| |
| The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
| WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
| WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
| COMBINATION WITH YOUR PRODUCTS. |
| |
| IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
| CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
| GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
| OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
| (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
| ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| Change History (most recent first): |
| 1.2 May 13, 2005 |
| 1.1 December 8, 2004 |
| 1.0 May 28, 2004 |
| */ |
| |
| #if defined(WIN32) |
| #include <stdio.h> |
| #define WIN32_LEAN_AND_MEAN |
| #include <windows.h> |
| #include <winsock2.h> |
| #include <dns_sd.h> |
| #include <Iprtrmib.h> |
| #include <Iphlpapi.h> |
| |
| static char* if_indextoname (DWORD ifIndex, char* nameBuff); |
| |
| #define usleep(X) Sleep(((X)+999)/1000) |
| #define ns_t_ptr 12 |
| #define ns_c_in 1 |
| #else |
| #include <dns_sd.h> |
| #include <arpa/nameser.h> |
| #include <sys/socket.h> |
| #include <net/if.h> |
| #include <assert.h> |
| #include <unistd.h> |
| #endif |
| |
| #include <CoreFoundation/CoreFoundation.h> |
| |
| #define MAX_DOMAIN_LABEL 63 |
| #define MAX_DOMAIN_NAME 255 |
| #define kServiceMetaQueryName "_services._dns-sd._udp.local." |
| |
| typedef struct { unsigned char c[ 64]; } domainlabel; // One label: length byte and up to 63 characters. |
| typedef struct { unsigned char c[256]; } domainname; // Up to 255 bytes of length-prefixed domainlabels. |
| |
| typedef struct MyDNSServiceState { |
| DNSServiceRef service; |
| CFRunLoopSourceRef source; |
| CFSocketRef socket; |
| } MyDNSServiceState; |
| |
| |
| /* MyConvertDomainLabelToCString() converts a DNS label into a C string. |
| A DNS label string is formatted like "\003com". The converted string |
| would look like "com." */ |
| |
| static char* |
| MyConvertDomainLabelToCString(const domainlabel *const label, char *ptr) |
| { |
| const unsigned char * src = label->c; // Domain label we're reading. |
| const unsigned char len = *src++; // Read length of this (non-null) label. |
| const unsigned char *const end = src + len; // Work out where the label ends. |
| |
| assert(label != NULL); |
| assert(ptr != NULL); |
| |
| if (len > MAX_DOMAIN_LABEL) return(NULL); // If illegal label, abort. |
| while (src < end) { // While we have characters in the label. |
| unsigned char c = *src++; |
| if (c == '.' || c == '\\') // If character is a dot or the escape character |
| *ptr++ = '\\'; // Output escape character. |
| else if (c <= ' ') { // If non-printing ascii, output decimal escape sequence. |
| *ptr++ = '\\'; |
| *ptr++ = (char) ('0' + (c / 100) ); |
| *ptr++ = (char) ('0' + (c / 10) % 10); |
| c = (unsigned char)('0' + (c ) % 10); |
| } |
| *ptr++ = (char)c; // Copy the character. |
| } |
| *ptr = 0; // Null-terminate the string |
| return(ptr); // and return. |
| } |
| |
| |
| /* MyConvertDomainNameToCString() converts a DNS name string into a C string. |
| A DNS name string is formated like "\003www\005apple\003com\0". The converted |
| string would look like "www.apple.com". If the DNS name contains a period "." or |
| a backslash "\", then those characters will be escaped with backslash characters, |
| as in "\." and "\\". Note: To guarantee that there will be no possible overrun, |
| "ptr" must be at least kDNSServiceMaxDomainName (1005 bytes) */ |
| |
| static char* |
| MyConvertDomainNameToCString(const domainname *const name, char *ptr) |
| { |
| const unsigned char *src = name->c; // Domain name we're reading. |
| const unsigned char *const max = name->c + MAX_DOMAIN_NAME; // Maximum that's valid. |
| |
| assert(name != NULL); |
| assert(ptr != NULL); |
| |
| if (*src == 0) *ptr++ = '.'; // Special case: For root, just write a dot. |
| |
| while (*src) { // While more characters in the domain name. |
| if (src + 1 + *src >= max) return(NULL); |
| ptr = MyConvertDomainLabelToCString((const domainlabel *)src, ptr); |
| if (!ptr) return(NULL); |
| src += 1 + *src; |
| *ptr++ = '.'; // Write the dot after the label. |
| } |
| |
| *ptr++ = 0; // Null-terminate the string |
| return(ptr); // and return. |
| } |
| |
| |
| |
| /* The DNSServiceQueryRecord callback returns a DNS PTR record with rdata formatted like: |
| |
| \005_http\004_tcp\005local\0 |
| |
| MyGetTypeAndDomain() takes the rdata and splits it up into two C strings which correspond to the |
| "type" and "domain" of a service. These strings could potentially be passed to a function |
| like DNSServiceBrowse(). Assuming the example rdata above, this function would return "_http._tcp." |
| as the "type", and "local." for the "domain". */ |
| |
| static void |
| MyGetTypeAndDomain(const void * rdata, uint16_t rdlen, char * type, char * domain) |
| { |
| unsigned char *cursor; |
| unsigned char *start; |
| unsigned char *end; |
| |
| assert(rdata != NULL); |
| assert(rdlen != 0); |
| assert(type != NULL); |
| assert(domain != NULL); |
| |
| start = (unsigned char*)malloc(rdlen); |
| assert(start != NULL); |
| memcpy(start, rdata, rdlen); |
| |
| end = start + rdlen; |
| cursor = start; |
| if ((*cursor == 0) || (*cursor >= 64)) goto exitWithError; |
| cursor += 1 + *cursor; // Move to the start of the second DNS label. |
| if (cursor >= end) goto exitWithError; |
| if ((*cursor == 0) || (*cursor >= 64)) goto exitWithError; |
| cursor += 1 + *cursor; // Move to the start of the thrid DNS label. |
| if (cursor >= end) goto exitWithError; |
| |
| /* Take everything from start of third DNS label until end of DNS name and call that the "domain". */ |
| if (MyConvertDomainNameToCString((const domainname *)cursor, domain) == NULL) goto exitWithError; |
| *cursor = 0; // Set the length byte of the third label to zero. |
| |
| /* Take the first two DNS labels and call that the "type". */ |
| if (MyConvertDomainNameToCString((const domainname *)start, type) == NULL) goto exitWithError; |
| free(start); |
| return; |
| |
| exitWithError: |
| fprintf(stderr, "Invalid DNS name string\n"); |
| free(start); |
| } |
| |
| |
| |
| static void |
| MyDNSServiceCleanUp(MyDNSServiceState * query) |
| { |
| /* Remove the CFRunLoopSource from the current run loop. */ |
| CFRunLoopRemoveSource(CFRunLoopGetCurrent(), query->source, kCFRunLoopCommonModes); |
| CFRelease(query->source); |
| |
| /* Invalidate the CFSocket. */ |
| CFSocketInvalidate(query->socket); |
| CFRelease(query->socket); |
| |
| /* Workaround that gives time to CFSocket's select thread so it can remove the socket from its FD set |
| before we close the socket by calling DNSServiceRefDeallocate. <rdar://problem/3585273> */ |
| usleep(1000); |
| |
| /* Terminate the connection with the mDNSResponder daemon, which cancels the query. */ |
| DNSServiceRefDeallocate(query->service); |
| } |
| |
| |
| |
| /* MySocketReadCallback() gets called when data is available for reading from the Unix domain socket |
| connected to the mDNSResponder daemon. This happens when the mDNSResponder delivers us a response to our query. */ |
| |
| static void |
| MySocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void * data, void * info) |
| { |
| #if defined(_APPLE_) |
| #pragma unused(s) |
| #pragma unused(type) |
| #pragma unused(address) |
| #pragma unused(data) |
| #endif |
| |
| DNSServiceErrorType err; |
| |
| MyDNSServiceState* query = (MyDNSServiceState*)info; // context passed in to CFSocketCreateWithNative(). |
| assert(query != NULL); |
| |
| /* Read a reply from the mDNSResponder, which will end up calling MyMetaQueryCallback(). */ |
| err= DNSServiceProcessResult(query->service); |
| if (err != kDNSServiceErr_NoError) |
| { |
| fprintf(stderr, "DNSServiceProcessResult returned %d\n", err); |
| |
| /* Terminate the query operation and release the CFRunLoopSource and CFSocket. */ |
| MyDNSServiceCleanUp(query); |
| CFRunLoopStop(CFRunLoopGetCurrent()); |
| } |
| } |
| |
| |
| |
| void |
| MyDNSServiceAddServiceToRunLoop(MyDNSServiceState* query) |
| { |
| CFSocketNativeHandle sock; |
| CFOptionFlags sockFlags; |
| CFSocketContext context = { 0, query, NULL, NULL, NULL }; // Use MyDNSServiceState as context data. |
| |
| /* Access the underlying Unix domain socket to communicate with the mDNSResponder daemon. */ |
| sock = DNSServiceRefSockFD(query->service); |
| assert(sock != -1); |
| |
| /* Create a CFSocket using the Unix domain socket. */ |
| query->socket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, MySocketReadCallback, &context); |
| assert(query->socket != NULL); |
| |
| /* Prevent CFSocketInvalidate from closing DNSServiceRef's socket. */ |
| sockFlags = CFSocketGetSocketFlags(query->socket); |
| CFSocketSetSocketFlags(query->socket, sockFlags & (~kCFSocketCloseOnInvalidate)); |
| |
| /* Create a CFRunLoopSource from the CFSocket. */ |
| query->source = CFSocketCreateRunLoopSource(NULL, query->socket, 0); |
| assert(query->source != NULL); |
| |
| /* Add the CFRunLoopSource to the current run loop. */ |
| CFRunLoopAddSource(CFRunLoopGetCurrent(), query->source, kCFRunLoopCommonModes); |
| } |
| |
| |
| static void |
| MyConvertInterfaceIndexToName(uint32_t interface, char * interfaceName) |
| { |
| assert(interfaceName != NULL); |
| |
| if (interface == 0) strcpy(interfaceName, "all"); // All active network interfaces. |
| else if (interface == 0xFFFFFFFF) strcpy(interfaceName, "local"); // Only available locally on this machine. |
| else if_indextoname(interface, interfaceName); // Converts interface index to interface name. |
| } |
| |
| |
| static void |
| DNSSD_API MyMetaQueryCallback(DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceID, DNSServiceErrorType error, |
| const char * fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void * rdata, uint32_t ttl, void * context) |
| { |
| #if defined(_APPLE_) |
| #pragma unused(service) |
| #pragma unused(rrclass) |
| #pragma unused(ttl) |
| #pragma unused(context) |
| #endif |
| |
| assert(strcmp(fullname, kServiceMetaQueryName) == 0); |
| |
| if (error == kDNSServiceErr_NoError) { |
| |
| #if defined(_GNUC_) |
| char interfaceName[IF_NAMESIZE] = ""; |
| char domain[MAX_DOMAIN_NAME] = ""; |
| char type[MAX_DOMAIN_NAME] = ""; |
| #else |
| char interfaceName[MAX_DOMAIN_NAME]; |
| char domain[MAX_DOMAIN_NAME]; |
| char type[MAX_DOMAIN_NAME]; |
| |
| memset(interfaceName, 0x00, MAX_DOMAIN_NAME); |
| memset(domain, 0x00, MAX_DOMAIN_NAME); |
| memset(type, 0x00, MAX_DOMAIN_NAME); |
| #endif |
| /* Get the type and domain from the discovered PTR record. */ |
| MyGetTypeAndDomain(rdata, rdlen, type, domain); |
| |
| /* Convert an interface index into a BSD-style interface name. */ |
| MyConvertInterfaceIndexToName(interfaceID, interfaceName); |
| |
| if (flags & kDNSServiceFlagsAdd) { |
| fprintf(stderr, "ADD %-28s %-14s %s\n", type, domain, interfaceName); |
| } else { |
| /* REMOVE is only called when a network interface is disabled or if the record |
| expires from the cache. For network efficiency reasons, clients do not send |
| goodbye packets for meta-query PTR records when deregistering a service. */ |
| fprintf(stderr, "REMOVE %-28s %-14s %s\n", type, domain, interfaceName); |
| } |
| } else { |
| fprintf(stderr, "MyQueryRecordCallback returned %d\n", error); |
| } |
| } |
| |
| |
| |
| static DNSServiceErrorType |
| MyDNSServiceMetaQuery(MyDNSServiceState * query, DNSServiceQueryRecordReply callback) |
| { |
| DNSServiceErrorType error; |
| |
| assert(query != NULL); |
| assert(callback != NULL); |
| |
| /* Issue a Multicast DNS query for the service type meta-query PTR record. */ |
| error = DNSServiceQueryRecord(&query->service, |
| 0, // no flags |
| 0, // all network interfaces |
| kServiceMetaQueryName, // meta-query record name |
| ns_t_ptr, // DNS PTR Record |
| ns_c_in, // Internet Class |
| callback, // callback function ptr |
| NULL); // no context |
| |
| if (error == kDNSServiceErr_NoError) { |
| |
| /* Create a CFRunLoopSource and add it to the run loop to enable asynchronous callbacks. */ |
| MyDNSServiceAddServiceToRunLoop(query); |
| |
| fprintf(stderr, "Event Service Type Domain Interface\n"); |
| fprintf(stderr, "------------------------------------------------------------\n"); |
| } |
| |
| return error; |
| } |
| |
| |
| |
| int |
| main (int argc, const char * argv[]) |
| { |
| #if defined(_APPLE_) |
| #pragma unused(argc) |
| #pragma unused(argv) |
| #endif |
| |
| MyDNSServiceState query; |
| DNSServiceErrorType error; |
| |
| /* Start the DNS-SD services meta-query, create the CFRunLoopSource, add it to the run loop. */ |
| error = MyDNSServiceMetaQuery(&query, (DNSServiceQueryRecordReply)MyMetaQueryCallback); |
| if (error == kDNSServiceErr_NoError) { |
| |
| /* Start the run loop to receive asynchronous callbacks via MyMetaQueryCallback. */ |
| CFRunLoopRun(); |
| |
| /* Terminate the query operation and release the CFRunLoopSource and CFSocket. */ |
| MyDNSServiceCleanUp(&query); |
| |
| } else { |
| fprintf(stderr, "MyDNSServiceMetaQuery returned %d\n", error); |
| } |
| |
| return 0; |
| } |
| |
| #if defined(WIN32) |
| /* |
| * The following implementation is from Apple's Bonjour implementation. |
| * It's so annoying that this is not provided on user-level Windows OS's |
| * (you have to get server 2008 to have this implemented in the OS.) |
| * |
| * Copyright (c) 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. |
| */ |
| static char* |
| if_indextoname( DWORD ifIndex, char * nameBuff) |
| { |
| PIP_ADAPTER_INFO pAdapterInfo = NULL; |
| PIP_ADAPTER_INFO pAdapter = NULL; |
| DWORD dwRetVal = 0; |
| char * ifName = NULL; |
| ULONG ulOutBufLen = 0; |
| |
| if (GetAdaptersInfo( NULL, &ulOutBufLen) != ERROR_BUFFER_OVERFLOW) |
| { |
| goto exit; |
| } |
| |
| pAdapterInfo = (IP_ADAPTER_INFO *) malloc(ulOutBufLen); |
| |
| if (pAdapterInfo == NULL) |
| { |
| goto exit; |
| } |
| |
| dwRetVal = GetAdaptersInfo( pAdapterInfo, &ulOutBufLen ); |
| |
| if (dwRetVal != NO_ERROR) |
| { |
| goto exit; |
| } |
| |
| pAdapter = pAdapterInfo; |
| while (pAdapter) |
| { |
| if (pAdapter->Index == ifIndex) |
| { |
| // It would be better if we passed in the length of nameBuff to this |
| // function, so we would have absolute certainty that no buffer |
| // overflows would occur. Buffer overflows *shouldn't* occur because |
| // nameBuff is of size MAX_ADAPTER_NAME_LENGTH. |
| strcpy( nameBuff, pAdapter->AdapterName ); |
| ifName = nameBuff; |
| break; |
| } |
| |
| pAdapter = pAdapter->Next; |
| } |
| |
| exit: |
| |
| if (pAdapterInfo != NULL) |
| { |
| free( pAdapterInfo ); |
| pAdapterInfo = NULL; |
| } |
| |
| return ifName; |
| } |
| #endif |