For anyone who considers both Foundation and Core Foundation too rarified there is always a C
function API for performing DNS/mDNS based service discovery and registration.
This is declared in the header file
/usr/include/dns_sd.h
1.0 DNSServiceRefs And Connection Sharing
By default each function in the API which requires DNS functionality establishes a separate connection to the DNS service which it returns as a DNSServiceRef
.
In this context the name DNSServiceRef
can be a bit confusing.
A DNSServiceRef
is a reference to the DNS Service, that is, the thing doing the search for services using DNS, not a reference to a service found using DNS.
There is an alternative to having a per function call connection and DNSServiceRef
.
A connection to the DNS service can be established separately and then shared by passing it to each function that would otherwise create a new connection.
2.0 The Function Pattern
All the functions in the API which access the DNS service are declared using a common pattern
DNSServiceErrorType <function-name>(
DNSServiceRef* sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
... <function specific arguments> ...
<function specific type> callBack,
void* context);
2.1 The sdRef Argument
DNSServiceRef* sdRef
DNSServiceRef* sdRef
The sdRef
argument is either
-
a pointer to an uninitalized
DNSServiceRef
which will be initialized to a validDNSServiceRef
if the call succeeds, or -
a pointer to a shared
DNSServiceRef
which should be used by the function
2.2 The flags argument
DNSServiceFlags flags
The flags
argument unsurprisingly specifies zero or more flags. If an initialized DNSServiceRef
is being passed via the sdRef
argument then the flag
kDNSServiceFlagsShareConnection
must be set.
2.3 The interfaceIndex Argument
uint32_t interfaceIndex
The index of the network interface to use to perform the requested DNS operation(s).
If the specific network interface is not important then when starting the search for services the constant
kDNSServiceInterfaceIndexAny
can be used.
When a service is found the index of the network interface it is associated with is reported and subsequent calls can use this value.
2.4 The callBack Argument
The type of the callBack
argument is specific to the function to which it is being passed but in each case it specifies the function to be invoked when a a result is available or an error occurs.
2.5 The info Argument
void* context
The value of the context
argument will be passed as an argument to the callback function specified by
the callBack
argument.
3.0 The Callback Function Pattern
All the callback function types are declared using a common pattern.
typedef void (*<type-name>)(
DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
... <function specific arguments> ...
void* context);
3.1 The sdRef Argument
The DNSServiceRef
which was passed to the function which invoked this callback.
3.2 The flags Argument
The flags
argument is used to pass general status information, e.g., if the
kDNSServiceFlagsMoreComing
flag is set then this callback will be invoked again.
3.3 The interfaceIndex Argument
The index of the network interface on which the result was obtained.
3.4 The errorCode Argument
If the errorCode
argument is not
kDNSServiceErr_NoError
then an error has occurred.
3.5 The context Argument
The value of the context
argument passed to the function which invoked this callback.
4.0 Searching For Services
4.1 DNSServiceBrowse
We can start the search for services of a given type by using the function DNSServiceBrowse
which is declared like this
DNSServiceErrorType DNSServiceBrowse(
DNSServiceRef* sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
const char* regtype,
const char* domain,
DNSServiceBrowseReply callBack,
void* context);
The regtype
argument should be the domain relative type name, e.g.,
"_ipp.tcp."
The domain
argument should be the absolute name of the domain to search in, e.g.,
"local."
4.2 The DNSServiceBrowse Callback Function
The function passed as the callBack
argument to DNSServiceBrowse
will be called once for each service of the given type that is found.
If the information about a service that was found becomes invalid, implying that it has ‘disappeared’, then the callback function will called again.’
4.3 The DNSServiceBrowseReply Function Type
The function type DNSServiceBrowseReply
is declared like this
typedef void (*DNSServiceBrowseReply)(
DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char* serviceName,
const char* regtype,
const char* replyDomain,
void* context);
When a function of this type is invoked, then if the errorCode
argument is kDNSServiceErr_NoError
-
serviceName
is the type relative name of the service. -
regtype
is the domain relative name of the service type -
replyDomain
is the absolute name of the domain the service is registered in
The kDNSServiceFlagsAdd
flag will be set in the flags
argument if the service has been found, and clear if the service has been ‘lost’.
5.0 Resolving Services
Resolving a service involves two functions
DNSServiceResolve
which obtains the service’s SRV and TXT records, and
and
DNSServiceGetAddrInfo.
which obtains the address or addresses of the host on which the service is running
5.1 DNSServiceResolve
We can obtain the information contained in the SRV and TXT records associated with a given service by using the function
DNSServiceResolve
which is declared like this
DNSServiceErrorType DNSServiceResolve(
DNSServiceRef* sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
const char* name,
const char* regtype,
const char* domain,
DNSServiceResolveReply callBack,
void* context);
The name
argument should be the type relative name of the service.
The regtype
argument should be the domain relative name of the service type.
The domain
argument should be the absolute name of the domain in which the service is registered.
5.1.1 The DNSServiceResolve Callback Function
The function passed as the callBack
argument to DNSServiceResolve
will be called once, either with the results of with an error.
5.1.1 The DNSServiceResolveReply Function Type
The function type DNSServiceResolveReply
is declared like this
typedef void (*DNSServiceResolveReply)(
DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char* fullname,
const char* hosttarget,
uint16_t port,
uint16_t txtLen,
const unsigned char* txtRecord,
void* context);
When a function of this type is invoked, then, if the errorCode
argument is kDNSServiceErr_NoError
-
fullname
is the absolute name of the service, e.g.,"ipp_server_1._ipp._tcp.local."
. -
hosttarget
is the name of the host the service is running on. -
port
is the port, in network byte order, the service is listening on. -
txtLen
is is the length of the TXT record data. -
txtRecord
is a pointer to the TXT record data itself.
5.2 DNSServiceGetAddrInfo
We can obtain the address of a host using the function DNSServiceGetAddrInfo
which is declared like this
DNSServiceErrorType DNSServiceGetAddrInfo(
DNSServiceRef* sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceProtocol protocol,
const char* hostname,
DNSServiceGetAddrInfoReply callBack,
void* context);
If the
kDNSServiceFlagsTimeout
is set in the flags
argument then the operation may timeout after a system defined amount of time.
The protocol
argument specifies the type of the address requested.
The value should be one of
-
0
-
kDNSServiceProtocol_IPv4
-
kDNSServiceProtocol_IPv6
-
kDNSServiceProtocol_IPv4|kDNSServiceProtocol_IPv6
A value of 0
(zero) is usually equivalent to requesting both the IPv4 and IPv6 addresses.
The hostname
argument should be the absolute name of the host.
5.2.1 The DNSServiceGetAddrInfo Callback Function
The function passed as the callBack
argument to DNSServiceGetAddrInfo
will be called once for each address type that was requested and is found
It will also be called if the address of the host becomes invalid, e.g., because the host has been turned off.
5.2.2 The DNSServiceGetAddrInfoReply Function Type
The function type DNSServiceGetAddrInfoReply
is declared like this
typedef void (*DNSServiceGetAddrInfoReply)(
DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char* hostname,
const struct sockaddr* address,
uint32_t ttl,
void* context);
When a function of this type is invoked, then, if the errorCode
argument is kDNSServiceErr_NoError
-
hostname
is the name of the host whose address this is -
address
is a pointer to its address, and -
ttl
is the time in seconds for which the given address is valid
The kDNSServiceFlagsAdd
flag will be set in the flags
argument if the address has been ‘found’, and clear if the address is no longer valid.
The kDNSServiceFlagsMoreComing
will be set in the flags
argument if there are more addresses, and will be clear if this is the last address.
The type of the address will of course depend upon what what specified as the protocol
argument in the call to DNSServiceGetAddrInfo
.
If both IPv4 and IPv6 addresses were requested then it will be necessary to examine the sa_family
field of the sockaddr
struct to find out which one it is.
6.0 The Care And Maintenance Of Your DNSServiceRef
Something that is not perhaps immediately apparent is that the shared DNSServiceRef
or one created by a function like DNSServiceBrowse
has to be actively handled on the client side.
There are two ways to do this, either
-
by obtaining the file descriptor associated with the connection by calling
DNSServiceRefSockFD
and doing it all yourself, or -
by calling the function
DNSServiceSetDispatchQueue
which will result in the connection being handled ‘automatically’ on the dispatch queue of your choice.
6.1 DNSServiceRefSockFD
The function DNSServiceRefSockFD
is declared like this
int DNSSD_API DNSServiceRefSockFD(DNSServiceRef sdRef);
It takes a DNSServiceRef
and returns the file descriptor of the underlying connection to the DNS service.
Once you have obtained your file descriptor you will need to determine when it is readable.
To do this you are going to need either
-
an
fd_set
and a system call, or -
a
pollfd struct
and a different system call
6.1.1 Using select
To use the select
system call and assuming sr
is the DNSServiceRef
you will need to do something like this.
...
int fd = DNSServiceRefSockFD(sr);
if (fd == -1)
{
fprintf(stderr, "fd == -1 !");
return;
}
fd_set readFDs;
struct timeval tv;
while (true)
{
FD_ZERO(&readFDs);
FD_SET(fd, &readFDs);
tv.tv_sec = 1000000;
tv.tv_usec = 0;
int status = select(fd + 1, &readFDs, NULL, NULL, &tv);
if (status == -1)
{
fprintf(stderr, "status == -1\n");
break;
}
else
if (status == 0)
{
fprintf(stderr, "status == 0\n");
}
else
if (FD_ISSET(fd, &readFDs))
{
int error = DNSServiceProcessResult(sr);
if (error != kDNSServiceErr_NoError)
{
fprintf(stderr, "DNSServiceProcessResult: error == %d\n", error);
break;
}
}
}
...
When the file descriptor is readable the function DNSServiceProcessResult
is invoked to handle the input. It is this call that results in callback functions being invoked.
This assumes that you are sharing a single DNSServiceRef
if not then you are going to end up knee deep in file descriptors and its all going to get very messy very fast.
6.1.2 Using poll
If the use of select
is too retro for you you can always use the new-fangled poll
system call.
The code looks very similar because poll
is just select
with unlimited [1] file descriptors.
...
int fd = DNSServiceRefSockFD(sr);
if (fd == -1)
{
NSLog(@"fd == -1 !");
return;
}
struct pollfd pollFD;
while (true)
{
pollFD.fd = fd;
pollFD.events = POLL_IN;
int status = poll(&pollFD, 1 , 1000000);
if (status == -1)
{
fprintf(stderr, "status == -1\n");
break;
}
else
if (status == 0)
{
fprintf(stderr, "status == 0\n");
}
else
if ((pollFD.revents & POLL_IN) != 0)
{
int error = DNSServiceProcessResult(sr);
if (error != kDNSServiceErr_NoError)
{
fprintf(stderr, "DNSServiceProcessResult: error == %d\n", error);
break;
}
}
}
...
6.2 DNSServiceSetDispatchQueue
The alternative to wrestling with file descriptors is the function DNSServiceSetDispatchQueue
which is declared like this
DNSServiceErrorType DNSServiceSetDispatchQueue(
DNSServiceRef service,
dispatch_queue_t queue);
The DNSServiceRef
can be associated with a shared connection or with a per function connection.
See below for an example of its use.
7.0 Creating A DNSServiceRef For A Shared Connection
The only way to create a DNSServiceRef
for a connection which can be shared is by using the function
DNSServiceCreateConnection
which is declared like this
DNSServiceErrorType DNSServiceCreateConnection(DNSServiceRef* sdRef);
A copy of the initialized DNSServiceRef
that results should be passed to each function that is going to share the connection.
See below for an example of its use.
8.0 DNSServiceBrowse In Action
This is the start
method of the third version of the FindServices
class.
- (BOOL)start
{
DNSServiceErrorType error = DNSServiceCreateConnection(&dnsServiceRef);
if (error != kDNSServiceErr_NoError)
{
NSLog(@"Error: DNSServiceCreateConnection %d", error);
return NO;
}
error = DNSServiceSetDispatchQueue(dnsServiceRef, dispatch_get_main_queue());
if (error != kDNSServiceErr_NoError)
{
NSLog(@"Error: DNSServiceSetDispatchQueue %d", error);
return NO;
}
browseRef = dnsServiceRef;
error = DNSServiceBrowse(
&browseRef,
kDNSServiceFlagsShareConnection,
kDNSServiceInterfaceIndexAny,
[self.type UTF8String],
[self.domain UTF8String],
browseCallback,
(__bridge void*)self);
if (error != kDNSServiceErr_NoError)
{
NSLog(@"Error: DNSServiceBrowse %d", error);
return NO;
}
return YES;
}
static void browseCallback(
DNSServiceRef sdRef,
DNSServiceFlags theFlags,
uint32_t theInterfaceIndex,
DNSServiceErrorType theErrorCode,
const char* theName,
const char* theType,
const char* theDomain,
void* theContext)
{
NSLog(@"browseCallback: error == %d flags == %s", theErrorCode, flagsToString(theFlags));
if (theErrorCode == kDNSServiceErr_NoError)
{
ServiceIdentifier si = { theName, theType, theDomain };
if ((theFlags & kDNSServiceFlagsAdd) != 0)
{
[(__bridge FindServices*)theContext serviceFound:&si onInterface:theInterfaceIndex];
}
else
{
[(__bridge FindServices*)theContext serviceLost:&si];
}
}
else
{
[(__bridge FindServices*)theContext browseFailed:theErrorCode];
}
}
9.0 DNSServiceResolve In Action
This is the resolve:onInterface:
method of the ServiceResolver
class
- (void)resolve:(ServiceIdentifier*)theServiceId onInterface:(uint32_t)theInterfaceIndex
{
DNSServiceErrorType error;
error = DNSServiceResolve(
&resolveRef,
kDNSServiceFlagsShareConnection,
theInterfaceIndex,
theServiceId->name,
theServiceId->type,
theServiceId->domain,
resolveCallback,
(__bridge void*)self);
if (error != kDNSServiceErr_NoError)
{
NSLog(@"DNSServiceResolve: %d", error);
[self.delegate serviceResolver:self didFail:error];
}
}
and this is the associated callback function.
static void resolveCallback(
DNSServiceRef theRef,
DNSServiceFlags theFlags,
uint32_t theInterfaceIndex,
DNSServiceErrorType theErrorCode,
const char* theFullName,
const char* theTarget,
uint16_t thePort,
uint16_t theTXTRecordLength,
const unsigned char* theTXTRecord,
void* theContext)
{
NSLog(@"resolveCallback: error == %d flags == %s", theErrorCode, flagsToString(theFlags));
if (theErrorCode != kDNSServiceErr_NoError)
{
NSLog(@"resolveCallback: error !");
[(__bridge ServiceResolver*)theContext resolveFailed:theErrorCode];
}
else
if (theFlags == 0)
{
ServiceInfo si =
{
theFullName,
theTarget,
ntohs(thePort),
theTXTRecordLength,
theTXTRecord
};
NSLog(@"%s %s %u", theFullName, theTarget, thePort);
[(__bridge ServiceResolver*)theContext resolved:&si onInterface:theInterfaceIndex];
}
else
{
NSLog(@"resolveCallback: flags set !");
[(__bridge ServiceResolver*)theContext internalError];
}
}
10.0 DNSServiceGetAddrInfo In Action
This is the resolved:onInterface:
method of the ServiceResolver
class
- (void)resolved:(const ServiceInfo*)theServiceInfo onInterface:(uint32_t)theInterfaceIndex
{
DNSServiceErrorType error;
error = DNSServiceGetAddrInfo(
&addressRef,
kDNSServiceFlagsShareConnection,
theInterfaceIndex,
kDNSServiceProtocol_IPv4|kDNSServiceProtocol_IPv6,
theServiceInfo->target,
addressInfoCallback,
(__bridge void*)self);
if (error == kDNSServiceErr_NoError)
{
[self.builder serviceInfo:theServiceInfo];
}
else
{
NSLog(@"DNSServiceGetAddrInfo: %d", error);
[self.delegate serviceResolver:self didFail:error];
}
}
and this is the associated callback function.
static void addressInfoCallback(
DNSServiceRef theServiceRef,
DNSServiceFlags theFlags,
uint32_t theInterfaceIndex,
DNSServiceErrorType theErrorCode,
const char* theHostname,
const struct sockaddr* theAddress,
uint32_t theTTL,
void* theContext)
{
NSLog(@"addressInfoCallback: error == %d flags == %s ", theErrorCode, flagsToString(theFlags));
if (theErrorCode != kDNSServiceErr_NoError)
{
NSLog(@"addressInfoCallback error");
[(__bridge ServiceResolver*)theContext getAddrInfoFailed:theErrorCode];
}
else
if ((theFlags & kDNSServiceFlagsAdd) != 0)
{
NSLog(@"theHostname == %s", theHostname);
NSLog(@"theAddress->sa_family == %d", theAddress->sa_family);
[(__bridge ServiceResolver*)theContext address:theAddress];
if ((theFlags & kDNSServiceFlagsMoreComing) == 0)
{
[(__bridge ServiceResolver*)theContext done];
}
}
else
{
NSLog(@"theHostname == %s", theHostname);
NSLog(@"theAddress->sa_family == %d", theAddress->sa_family);
// ignore
}
}
11.0 Examples
In each case FindServices
is looking for services of type
"_ipp._tcp."
in the domain
"local."
In each case the log output is from FindServices
and its delegate running on an iPad running iOS 7.0.
7.1 A Single IPPServer
A single instance of the CUPS test server IPPServer
with the name ipp_server_1
running on a Mac and then being stopped.
In this case the addressInfoCallback
function is called twice, first with the IPv6 address then with the IPv4 address.
...
2013-11-18 14:53:20.304 XperTakeThree[334:60b] browseCallback: error == 0 flags == kDNSServiceFlagsAdd
2013-11-18 14:53:20.307 XperTakeThree[334:60b] serviceFound: ipp_server_1._ipp._tcp.local.
2013-11-18 14:53:20.309 XperTakeThree[334:60b] resolveCallback: error == 0 flags == <none>
2013-11-18 14:53:20.310 XperTakeThree[334:60b] ipp_server_1._ipp._tcp.local. Simons-Computer.local. 56088
2013-11-18 14:53:20.312 XperTakeThree[334:60b] addressInfoCallback: error == 0 flags == kDNSServiceFlags{Add,MoreComing}
2013-11-18 14:53:20.313 XperTakeThree[334:60b] theHostname == Simons-Computer.local.
2013-11-18 14:53:20.314 XperTakeThree[334:60b] theAddress->sa_family == 30
2013-11-18 14:53:20.315 XperTakeThree[334:60b] addressInfoCallback: error == 0 flags == kDNSServiceFlagsAdd
2013-11-18 14:53:20.316 XperTakeThree[334:60b] theHostname == Simons-Computer.local.
2013-11-18 14:53:20.317 XperTakeThree[334:60b] theAddress->sa_family == 2
2013-11-18 14:54:18.545 XperTakeThree[334:60b] browseCallback: error == 0 flags == <none>
2013-11-18 14:54:18.547 XperTakeThree[334:60b] serviceLost: name == ipp_server_1._ipp._tcp.local.
...
11.2 A Single Printer
A printer being turned on and then turned off a couple of minutes later.
In this case we only get a single address, the IPV4 one, but we do get a second call to the function addressInfoCallback
function when the printer is turned off
...
2013-11-18 14:55:55.137 XperFS_DNS_SD[351:60b] browseCallback: error == 0 flags == kDNSServiceFlagsAdd
2013-11-18 14:55:55.140 XperFS_DNS_SD[351:60b] serviceFound: Canon MG6200 series._ipp._tcp.local.
2013-11-18 14:55:55.141 XperFS_DNS_SD[351:60b] resolveCallback: error == 0 flags == <none>
2013-11-18 14:55:55.142 XperFS_DNS_SD[351:60b] Canon32MG620032series._ipp._tcp.local. 7D300C000000.local. 30466
2013-11-18 14:55:55.144 XperFS_DNS_SD[351:60b] addressInfoCallback: error == 0 flags == kDNSServiceFlagsAdd
2013-11-18 14:55:55.145 XperFS_DNS_SD[351:60b] theHostname == 7D300C000000.local.
2013-11-18 14:55:55.146 XperFS_DNS_SD[351:60b] theAddress->sa_family == 2
2013-11-18 15:02:18.835 XperFS_DNS_SD[351:60b] addressInfoCallback: error == 0 flags == <none>
2013-11-18 15:02:18.837 XperFS_DNS_SD[351:60b] theHostname == 7D300C000000.local.
2013-11-18 15:02:18.839 XperFS_DNS_SD[351:60b] theAddress->sa_family == 2
2013-11-18 15:02:19.936 XperFS_DNS_SD[351:60b] browseCallback: error == 0 flags == <none>
2013-11-18 15:02:19.938 XperFS_DNS_SD[351:60b] serviceLost: name == Canon MG6200 series._ipp._tcp.local.
...
Notes
-
Subject to terms and conditions. The number of file descriptors may be subject to limits.
Copyright (c) 2013 By Simon Lewis. All Rights Reserved.
Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.
Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.