/* |
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 |
*/ |
#include <dns_sd.h> |
#include <nameser.h> |
#include <sys/socket.h> |
#include <net/if.h> |
#include <assert.h> |
#include <unistd.h> |
#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 = 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) |
{ |
#pragma unused(s) |
#pragma unused(type) |
#pragma unused(address) |
#pragma unused(data) |
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 |
MyMetaQueryCallback(DNSServiceRef service, DNSServiceFlags flags, uint32_t interface, DNSServiceErrorType error, |
const char * fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void * rdata, uint32_t ttl, void * context) |
{ |
#pragma unused(service) |
#pragma unused(rrclass) |
#pragma unused(ttl) |
#pragma unused(context) |
assert(strcmp(fullname, kServiceMetaQueryName) == 0); |
if (error == kDNSServiceErr_NoError) { |
char interfaceName[IF_NAMESIZE] = ""; |
char domain[MAX_DOMAIN_NAME] = ""; |
char type[MAX_DOMAIN_NAME] = ""; |
/* 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(interface, 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[]) |
{ |
#pragma unused(argc) |
#pragma unused(argv) |
MyDNSServiceState query; |
DNSServiceErrorType error; |
/* Start the DNS-SD services meta-query, create the CFRunLoopSource, add it to the run loop. */ |
error = MyDNSServiceMetaQuery(&query, 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; |
} |
Last updated: 2005-06-01