7.3. Using the DNSServiceDiscovery APIsThis section covers browsing for services, resolving services, registering services, and some of the less common operations, such as enumerating domains. In each case, there are three basic tasks to be performed: you initiate the operation, add the event source to your event loop, and provide the associated callback function. In the case of browsing, you call DNSServiceBrowse( ). In the examples that use the code shown in Example 7-2, you will next call HandleEvents( ) and pass in the now initialized service discovery reference. Finally, you will implement a callback function, which will be called when the mdnsd daemon has a relevant reply. The form of the callback function is specified. So, for example, when browsing for services, the callback function must have the same signature as specified by the typedef DNSServiceBrowseReply( ). The number and type of the parameters passed to the callback function will be as indicated in the typedef. The process and the description of the parameters will quickly become familiar. After we finish describing browsing in detail, any repeated information will be summarized or omitted when describing resolving services, registering services, and enumerating domains. 7.3.1. Browsing for ServicesTo browse for available services, you need to call the DNSServiceBrowse( ) function and specify the type of service you are searching for and the domain in which to search. You also pass in the address of your callback function and the address of an uninitialized DNSServiceRef. After the DNSServiceBrowse( ) call has started the operation and initialized the DNSServiceRef, you can extract the underlying file descriptor to add it to your select( ) loop. Each time the mdnsd daemon responds, your select( ) loop will wake up, you call DNSServiceProcessResult( ), and that calls your callback function for you. This section provides details for the DNSServiceBrowse( ) and DNSServiceBrowseReply( ) functions, along with a table listing possible flag values and an example of how you might browse for a specific type of service. 7.3.1.1. DNSServiceBrowse( )You initiate browsing for a service by calling DNSServiceBrowse( ): DNSServiceRef DNSServiceBrowse( DNSServiceRef *sdRef, DNSServiceFlags flags, uinte32_t interfaceIndex, const char *regtype, const char *domain, DNSServiceBrowseReply callBack, void *context); The first parameter is the address of your uninitialized service discovery reference. The flags parameter is used for specifying optional settings that apply to some of the DNSServiceDiscovery routines. Currently, no optional settings are defined for the DNSServiceBrowse( ) call, so you should pass zero for this parameter. Normally, applications pass 0 for interfaceIndex, and DNS-SD browses on all available interfaces. However, should you wish to restrict browsing to one specific interface, such as Ethernet or wireless, you can specify that interface by giving its interface index, as used in the if_nametoindex( ) family of functions. You can see each interface's index value by using the ifconfig command and looking for the IPv6 scopeid values. Interface indexes are typically small integers. For example, on Mac OS X machines, the Ethernet interface is often index 4, and AirPort is often index 5. The other novel value you can pass for this parameter is kDNSServiceInterfaceIndexLocalOnly. This restricts DNS-SD to only finding other services that were registered on the same machine (though the service itself is not necessarily running on the same machine, because there can be proxies for services running on other machines). The applications that currently use this option are certain parallel processing products that have two versions at different pricesthe single machine version and the network version. If the customer has only paid for the single machine version, the application only wants to find instances of the server process that are registered on the same machine, so it uses kDNSServiceInterfaceIndexLocalOnly. The regtype is the same service type as entered when using the command-line tool in Chapter 6. It is the protocol followed by either ._tcp or by ._udp. For example, the regtype might have the value _http._tcp. The valid service names can be found at http://www.dns-sd.org/ServiceTypes.html. The domain variable can have a specific value, such as local, or it can be NULL to indicate that the system should choose the appropriate list of domains to search. Generally, that list of domains to search will always include local, plus any additional unicast domains added explicitly by the user, plus any "legacy browse" domains automatically learned from the network. The callBack is the address of your callback function to be called when an instance of the specified service is found. Details on the callback function are contained in the next section. The callback function is also called in the event of asynchronous errors. The context parameter is also passed to the callback function. This allows you to write a single callback function, which is used by several different browse operations, because the context parameter allows you to tell which particular DNSServiceBrowse( ) operation this event pertains to. Typically, the context parameter will be the address of some structure or object holding your state pertaining to that operation. C may not be an object-oriented language, but the context parameter here has an equivalent role to the "self" variable in an object-oriented language. You are free to use any value you wish for the context parameter, including NULL, and it will be passed unchanged to your callback function. 7.3.1.2. DNSServiceBrowseReply( )The callback function passed in as a parameter for the DNSServiceBrowse( ) function will have the following form: void MyBrowseReply ( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context ); The most interesting parameters here are errorCode, serviceName, regtype, and replyDomain. If errorCode is nonzero, then an error has occurred, as listed in Table 7-1. If errorCode is zero (kDNSServiceErr_NoError), then serviceName, regtype, and replyDomain tell you the name, type, and domain for the newly discovered (or removed) service. The sdRef is the DNSServiceRef of the operation to which this callback relates. If you have multiple browse operations running at once, being handled by the same callback function, the callback function can use the sdRef, or the context parameter, to help it locate whatever private internal state is necessary for it to make sense of the result. The flags parameter tells the callback function two interesting things. First, if the kDNSServiceFlagsAdd bit is set, then a new service has been discovered. If this bit is not set, then the named service, previously discovered, has gone away and should be removed from your onscreen display. Second, if the kDNSServiceFlagsMoreComing bit is set, the callback function should not bother updating its UI and repainting the screen right away, because more results are coming immediately after this one. Suppose you discover 100 service instances on the networkadding each one to the onscreen list individually and redrawing the window for every one will make a slow and flickery display. If, instead, you wait until all 100 are in your list in memory before updating the screen, then the entire service list appears virtually instantaneously, fully formed on the screen, instead of building up one line at a time. Note that if the kDNSServiceFlagsMoreComing bit is not set, that does not mean that there are no more answers coming ever. What it means is that there are no more answers coming right now, so you should go ahead and update the screen display and perform any other relevant processing you may have deferred. Even after you get a callback with the kDNSServiceFlagsMoreComing bit not set, you could easily get another one just a millisecond later giving data newly discovered from the network, and, in fact, you should expect this to happen quite frequently. Don't make the mistake of canceling your browse operation because you got a callback with the kDNSServiceFlagsMoreComing bit not set, and you thought that meant that was the last answer you'd ever get. The interfaceIndex tells you on which interface the service was discovered, particularly useful if you passed 0 for the interfaceIndex when calling DNSServiceBrowse( ). Note that if your machine has both Ethernet and wireless, and there's some other machine connected via both Ethernet and wireless, then you will discover that machine's services twice, once via the Ethernet interface and once via the wireless interface. If one interface is turned off or disconnected, then you'll get remove events for only the service(s) discovered on the interface that went away. You therefore need to keep track of the interface indexes along with the name, type, and domain of each discovered service, so that when you receive remove events, you know which one to remove. If you're particularly ambitious, you could also make your UI display include an icon indicating on which interface each service was discovered. 7.3.1.3. DNSServiceDiscovery flagsTable 7-2 contains a list of flags that are currently available to be used in the DNSServiceDiscovery APIs. The flags follow the format kDNSServiceFlagsxxx, where xxx represents one of the flags listed in the table. For the sake of brevity in the table, the kDNSServiceFlags portion is not written.
7.3.1.4. Browsing exampleThe example will have two functions. You will start browsing using the function MyDNSServiceBrowse( ). In it, you declare a DNSServiceErrorType and DNSServiceRef variables. You then call DNSServiceBrowse( ) and specify that you are browsing for services of type _http._tcp on the local network and pass in the function named MyBrowseCallBack as the callback function. Pass the service discovery reference to the HandleEvents( ) function in Example 7-2. If there is an error reported, then use DNSServiceRefDeallocate( ) to clean up. In summary, MyDNSServiceBrowse( ) has the following outline: static DNSServiceErrorType MyDNSServiceBrowse( ) { DNSServiceErrorType error; DNSServiceRef serviceRef; error = DNSServiceBrowse(&serviceRef, /* parameters as described above */); if (!error) { HandleEvents(serviceRef); DNSServiceRefDeallocate(serviceRef); } return error; } The second function is the callback function MyBrowseCallBack( ). If no error is reported, the flags are checked to see if the kDNSServiceFlagsMoreComing and kDNSServiceFlagsAdd flags are set. In this example code, a message is then printed to the screen that indicates whether the service is being added or removed, along with the service's name, type, and domain. If the kDNSServiceFlagsMoreComing flag is not set, then standard out is flushed to ensure that the information appears promptly on the user's screen. Both functions, along with main( ), are in Example 7-3. Example 7-3. DNSServiceBrowse example#include <dns_sd.h> #include <stdio.h> // For stdout, stderr #include <string.h> // For strlen(), strcpy( ), bzero( ) extern void HandleEvents(DNSServiceRef); static void MyBrowseCallBack(DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char * name, const char * type, const char * domain, void * context) { #pragma unused(context) if (errorCode != kDNSServiceErr_NoError) fprintf(stderr, "MyBrowseCallBack returned %d\n", errorCode); else { char *addString = (flags & kDNSServiceFlagsAdd) ? "ADD" : "REMOVE"; char *moreString = (flags & kDNSServiceFlagsMoreComing) ? "MORE" : " "; printf("%-7s%-5s %d%s.%s%s\n", addString, moreString, interfaceIndex, name, type, domain); } if (!(flags & kDNSServiceFlagsMoreComing)) fflush(stdout); } static DNSServiceErrorType MyDNSServiceBrowse( ) { DNSServiceErrorType error; DNSServiceRef serviceRef; error = DNSServiceBrowse(&serviceRef, 0, // no flags 0, // all network interfaces "_http._tcp", // service type "", // default domains MyBrowseCallBack, // call back function NULL); // no context if (error == kDNSServiceErr_NoError) { HandleEvents(serviceRef); // Add service to runloop to get callbacks DNSServiceRefDeallocate(serviceRef); } return error; } int main (int argc, const char * argv[]) { DNSServiceErrorType error = MyDNSServiceBrowse( ); if (error) fprintf(stderr, "DNSServiceDiscovery returned %d\n", error); return 0; } Save the code in Example 7-3 as MyDNSSDBrowser.c and the code in Example 7-2 as DNSServiceCallbackSelect.c, then compile and run them. If there are no services of type _http._tcp running on your local network, you can always follow the instructions in Chapter 6 to register a pretend one using the command-line tool. You can also use the example included in the section "Registering a Service," later in this chapter. 7.3.2. Resolving a ServiceThe pattern for resolving is identical to that for browsing. To resolve, you call DNSServiceResolve( ) and then add the event source to your select( ) loop. When a result or results become available, your callback function will be called. This section details both functions and provides an example of resolving a registered service to determine its host and port. 7.3.2.1. DNSServiceResolveTo resolve a service, call DNSServiceResolve( ): DNSServiceErrorType DNSServiceResolve( DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, const char *name, const char *regtype, const char *domain, DNSServiceResolveReply callBack, void *context); Pass name, regtype, and domain exactly as you received them in the DNSServiceBrowse( ) callback. If you are resolving a service that you discovered in a still-active browse call, then pass the discovered interfaceIndex to ensure that you resolve it on the specific interface on which is was discovered. If you are resolving a service that you discovered some time ago (perhaps saving its name, regtype, and domain in a preference file on disk), then you should set interfaceIndex to zero, because that service may now be available via a different interface. For example, the user could have originally discovered the service via Ethernet but now wants to use that same service via wireless. When the daemon has the service information for you, it will call your callback function. 7.3.2.2. DNSServiceResolveReplyYour DNSServiceResolve( ) callback function needs to have the following form: void MyDNSServiceResolveReply ( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context ); As before, errorCode tells you if the operation was successful, and sdRef and context are provided to help your callback easily locate any state it needs. If you resolved without specifying a particular interface, then interfaceIndex tells you on which interface the answer was found. The parameters hosttarget and port tell you where the service can be reached, today. Note that this can change over time. A given named service can be moved to a different machine. This is why it is important to store only the name, type, and domain in preference files on disk and resolve on demand when needed. If you store the hostname, port number, or even worse, the IP address, these could all be out of date when the user comes to access the service at a later date. The port is given in network byte order, exactly as needed for use in the sin_port field of a struct sockaddr_in you'd pass to connect( ). The fullname parameter gives you the fully qualified DNS name for the service, with all necessary escaping of dots, spaces, backslashes, and nonprinting characters, making the name safe to pass to DNSServiceQueryRecord( ) or the standard Unix function res_query( ). Most applications will never need to use this, but the fullname parameter is provided as a convenience for those that do. One example is iChat, which stores the user's picture as another DNS record with the same name as the service (SRV) record. Providing the properly escaped, fully qualified DNS name makes it easy for iChat to retrieve the image record using either res_query( ) or DNSServiceQueryRecord( ). The txtLen and txtrecord parameters give you additional optional information about the service, if present. The data is presented in raw DNS TXT record format. To help you decode this format, the dns_sd.h header file provides helper functions TXTRecordContainsKey( ) and TXtrecordGetValuePtr( ), available in Mac OS X 10.4 and, later, Bonjour for Windows, Bonjour for Linux, etc. int DNSSD_API TXTRecordContainsKey(uint16_t txtLen, const void *txtRecord, const char *key); const void * DNSSD_API TXTRecordGetValuePtr(uint16_t txtLen, const void *txtRecord, const char *key, uint8_t *valueLen ); TXtrecordContainsKey( ) returns a Boolean true/false result indicating whether the named key appears in the text record. TXtrecordGetValuePtr( ) returns a pointer to the value data for the named key. If the returned pointer is NULL, the named key did not appear in the text record or appeared with no = to indicate an associated value. If the returned pointer is non-NULL, then the named key appeared with an =. The valueLen indicates the length of the value data, which may be zero in the case of an empty value (i.e., "key="). 7.3.2.3. Resolution exampleIn Example 7-4, the service with the name Not a real page of type _http._tcp is resolved using DNSServiceResolve( ). When MyResolveCallBack( ) is called, if there has been no error, then the name of the service being resolved is displayed along with the hostname and port number. Example 7-4. DNSServiceResolve Example#include <dns_sd.h> #include <stdio.h> // For stdout, stderr #include <string.h> // For strlen(), strcpy( ), bzero( ) extern void HandleEvents(DNSServiceRef); static void MyResolveCallBack(DNSServiceRef serviceRef, DNSServiceFlags flags, uint32_t interface, DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context) { #pragma unused(flags) #pragma unused(fullname) if (errorCode != kDNSServiceErr_NoError) fprintf(stderr, "MyResolveCallBack returned %d\n", errorCode); else printf("RESOLVE: %s is at %s:%d\n", fullname, hosttarget, ntohs(port)); if (!(flags & kDNSServiceFlagsMoreComing)) fflush(stdout); } static DNSServiceErrorType MyDNSServiceResolve( ) { DNSServiceErrorType error; DNSServiceRef serviceRef; error = DNSServiceResolve(&serviceRef, 0, // no flags 0, // all network interfaces "Not a real page", //name "_http._tcp", // service type "local", //domain MyResolveCallBack, NULL); // no context if (error == kDNSServiceErr_NoError) { HandleEvents(serviceRef); // Add service to runloop to get callbacks DNSServiceRefDeallocate(serviceRef); } return error; } int main (int argc, const char * argv[]) { DNSServiceErrorType error = MyDNSServiceResolve( ); fprintf(stderr, "DNSServiceDiscovery returned %d\n", error); //if function returns print error return 0; } Run this example by compiling it and running it with DNSServiceCallbackSelect.c. If you have a real _http._tcp service available, you can substitute its name in place of Not a real page. Alternatively, you can follow the instructions in Chapter 6 to register a pretend _http._tcp service called Not a real page using the command-line tool, or you can proceed to the example in the section "Registering a Service" to write code to register your own pretend service with that name. When you successfully resolve, you should see something like this: RESOLVE: Not\032a\032real\032page._http._tcp.local is at SuiMai.local.:9092 Notice how the spaces in the fully qualified DNS name are replaced with \032 as part of the escaping process to make the name safe to use with Unix routines such as res_query( ). 7.3.3. Registering a ServiceTo register a service, you call the DNSServiceRegister( ) function and specify the name and the type of the service you are registering, as well as details about the interface, host, domain, and port. As with browsing, you pass in the address of the callback function, a context pointer, and an uninitialized DNSServiceRef, and then add the event source to your select( ) loop. Each time the mdnsd daemon responds, you call DNSServiceProcessResult and the callback function will be invoked. This section provides details for the DNSServiceRegister( ) and DNSServiceRegisterReply( ) functions, along with details of how to add, update, or remove a resource record using DNSServiceAddRecord( ), DNSServiceUpdateRecord( ), and DNSServiceRemoveRecord( ). The section concludes with an example of how you might register a service. 7.3.3.1. DNSServiceRegisterTo register a service, call the DNSServiceRegister( ) function: DNSServiceErrorType DNSServiceRegister( DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, const char *name, const char *regtype, const char *domain, const char *host, uint16_t port, uint16_t txtLen, const void *txtRecord, DNSServiceRegisterReply callBack, void *context); The parameters should seem familiar from the discussion of resolving services using DNSServiceResolve( ). The difference is that now, instead of learning the host, port, and DNS TXT record data, you're providing it. The interfaceIndex parameter allows you to advertise your service on only one specific interface, if desired. Most applications use zero for interfaceIndex. The other novel value you can pass for this parameter is kDNSServiceInterfaceIndexLocalOnly, which means that your service will not actually be advertised on the network but only made visible to other browsing clients on the same machine. One use of this is for background processes that provide a web-based user interface for configuration but (perhaps for security reasons) only allow configuration from the local machine, not remotely over the network. By advertising their service using kDNSServiceInterfaceIndexLocalOnly, their configuration page will appear in the Bonjour list in web browsers running on the local machine only, not on other machines on the network. The name parameter is optional. If you pass NULL or an empty string, a system-wide default name is used for your service. For many services, when there is only usually one instance of that service on a given machine, using a system-wide default name is a sensible choice. Whether you specify an explicit name or use the system-wide default, Multicast DNS will ensure that it is unique on the local network. For example, if you advertise an HTTP server with the name "Web Server" and there is already a different HTTP service on the network with the same name, then yours will be automatically renamed to "Web Server (2)." If there's already a "Web Server (2)," then yours will be automatically renamed to "Web Server (3)," and so on. If you don't want this auto-rename behavior, then use the flag kDNSServiceFlagsNoAutoRename, and instead of renaming automatically, DNS-SD will call your callback function with a kDNSServiceErr_NameConflict error result so you can pick a new name for yourself. When this happens, your service registration will have been terminated. You will need to remove it from your select( ) loop, destroy the DNSServiceRef using DNSServiceRefDeallocate( ), and then try again with a new name. The domain parameter is optional. If you pass NULL or an empty string, it means "pick a sensible default for me," which is what most applications do. Usually the sensible default will be local, possibly plus one wide-area domain as selected by the user. The hostname parameter is optional. If you pass NULL or an empty string, it means the current host, which is what most applications do. You only need to specify a hostname when creating proxy registrations for services running on some other machine. The port number is in network byte order. If you're using a fixed port number, then this is exactly as you would have used in the sin_port field of your struct sockaddr_in in your bind( ) call. If you're using a dynamic port number, this is exactly as you would have received it in the sin_port field of your struct sockaddr_in in your getsockname( ) call. Some service types have extra data stored in the TXT record. For example, with HTTP services, the path to the page in question can be stored in the TXT record, e.g., path=/index.html. You need to provide a pointer to a properly formatted DNS TXT record. To help in the creation of properly formatted DNS TXT records, you can use the TXtrecordCreate( ) family of helper functions from the dns_sd.h header file, available in Mac OS X 10.4 and, later, Bonjour for Windows, Bonjour for Linux, etc. 7.3.3.2. DNSServiceRegisterReplyThe following is the prototype for the callback function used when registering a service: void MyRegisterReply ( DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context ); The parameters are similar to those for DNSServiceBrowseReply( ). Of particular interest is the name parameter. When auto-renaming is in effect, the name parameter tells you what name Multicast DNS finally picked for you. In some kinds of application, knowing your own name is important. For example, iChat kind of acts as both a client and a server on the same machine. When the client side of iChat browses, it finds all service instances, including its own. By knowing its own name, it can filter itself out of the list it presents to the user. 7.3.3.3. DNSServiceAddRecordHaving registered a service, DNSServiceAddRecord( ) lets you add additional records with the same name as the service (SRV) record. This is rare, and most applications never need to do this. One example of an application that uses this is iChat. iChat uses this to add a separate record containing the user's picture. When you add a record using DNSServiceAddRecord( ), you get a DNSRecordRef, which you can subsequently use in DNSServiceUpdateRecord( ) to update the record's data and in DNSServiceRemoveRecord( ) to remove the record from the service registration. Here is the signature of DNSServiceAddRecord( ): int DNSServiceAddRecord( DNSServiceRef sdRef, DNSRecordRef *RecordRef, DNSServiceFlags *flags, uint16_t rrtype, uint16_t rdlen, const void *rdata, uint32_t ttl ); The sdRef parameter specifies the service you're adding the record to, and the specified DNSRecordRef is initialized as a result of this call. The last four parameters are used to describe the record being added. The rrtype is the numerical value of the resource record type. The list of these numbers can be found at http://www.iana.org/assignments/dns-parameters and in the dns_sd.h header file. The rdlen is the length in bytes of rdata, which is (in theory) up to 64 KB of opaque binary data that is to be stored in the resource record being added. In practice, 100 bytes or 200 bytes is a reasonable size, and anything above 1,000 bytes can be inefficient on the network. Finally, ttl is a 32-bit signed value indicating the record's requested time to live in seconds. 7.3.3.4. DNSServiceUpdateRecordUse DNSServiceUpdateRecord( ) to request an update to a DNS record. DNSServiceErrorType DNSServiceUpdateRecord( DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags, uint16_t rdlen, const void *rdata, uint32_t ttl ); RecordRef identifies the record to be updated. Either it is a DNSRecordRef created by DNSServiceAddRecord( ), DNSServiceRegisterRecord( ), or NULL, which means "Update the service's primary TXT record." Most services never update their TXT records. Again, the exception is iChat, which uses its TXT record to show the user's available/idle/away state, and consequently updates it all the time. The rdlen, rdata, and ttl have the same meanings that were described for DNSServiceAddRecord( ). 7.3.3.5. DNSServiceRemoveRecordTo request the removal of a resource record from a service's registration information, call DNSServiceRemoveRecord( ). DNSServiceErrorType DNSServiceRemoveRecord( DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags ); 7.3.3.6. Registration exampleThe registration example shown in Example 7-5 is similar to the browse example shown in Example 7-3. The function MyDNSServiceRegister( ) has roughly the same format as MyDNSServiceBrowse( ). The callback function, MyRegisterCallBack( ), reports that the service has been registered if no errors are reported and reports the errors if any exist. Here is the entire listing. Example 7-5. DNSServiceRegister example#include <dns_sd.h> #include <stdio.h> // For stdout, stderr #include <string.h> // For strlen( ), strcpy( ), bzero( ) extern void HandleEvents(DNSServiceRef); static void MyRegisterCallBack(DNSServiceRef service, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char * name, const char * type, const char * domain, void * context) { #pragma unused(flags) #pragma unused(context) if (errorCode != kDNSServiceErr_NoError) fprintf(stderr, "MyRegisterCallBack returned %d\n", errorCode); else printf("%-15s %s.%s%s\n","REGISTER", name, type, domain); } static DNSServiceErrorType MyDNSServiceRegister( ) { DNSServiceErrorType error; DNSServiceRef serviceRef; error = DNSServiceRegister(&serviceRef, 0, // no flags 0, // all network interfaces "Not a real page", // name "_http._tcp", // service type "", // register in default domain(s) NULL, // use default host name htons(9092), // port number 0, // length of TXT record NULL, // no TXT record MyRegisterCallBack, // call back function NULL); // no context if (error == kDNSServiceErr_NoError) { HandleEvents(serviceRef); DNSServiceRefDeallocate(serviceRef); } return error; } int main (int argc, const char * argv[]) { DNSServiceErrorType error = MyDNSServiceRegister( ); fprintf(stderr, "DNSServiceDiscovery returned %d\n", error); return 0; } Save the code in Example 7-5 as MyDNSSDRegistrar.c and compile it and run it along with DNSServiceCallbackSelect.c. Note that the port number for this service has been hardcoded to 9092. When you run this example, you should see the following message: REGISTER Not a real page._http._tcp.local. You can verify that this service has been registered using the dns-sd command-line tool or by running the previous example of browsing. You should see this message: ADD Not a real page._http._tcp.local. 7.3.4. Enumerating DomainsTo date, most of the use of DNS-SD has been for link-local multicast discovery. It is natural that this is the area that would get the most interest, because this was the area of IP most desperately in need of improvement. However, as link-local DNS-SD becomes mature, people begin to look outward to wide-area service discovery. When you begin to browse domains other than local, the question arises, "How does the machine know which domains to browse?" Forcing the user to configure this manually would not be in keeping with the spirit of Zero Configuration Networking. DNS-SD has mechanisms to learn this information from the local network, and the domain enumeration functions allow applications to access this information to present a good user interface. As with the other DNS-SD APIs, these interfaces are asynchronous and ongoing until cancelled, because information from the network can change at any time. When an application wants to enable browsing in multiple domains, it asks for the list of recommended browsing domains. It should not automatically browse every domain it finds, because that would be extremely expensive on the network. Instead, it should present the list of domains to the user so he can pick. One domain will be delivered with the kDNSServiceFlagsDefault flag set, and that domain should be highlighted by default in the browser. It's the network equivalent of the "you are here" marker on a map. The list is purely advisory; users should still be allowed to manually enter additional domains to browse if they wish. When an application wants to register its service in domains other than just local, it can ask for the list of recommended registration domains. As before, an application should not automatically register in every domain it finds. The list is intended to be shown to the user, so the user may pick one domain from the list. Also as before, the list is advisory, meaning users should be allowed to manually enter a different domain to register in if they wish. Most applications will not need to enumerate registration domains, because they will simply use the user's configured system-wide default by passing NULL for the domain when registering. To enumerate domains, you need to call DNSServiceEnumerateDomain( ) and use a callback function built on the template provided by DNSServiceDomainEnumReply to collect the information. If the network administrator has not created the domain enumeration records described in Chapter 5, the only result you will get is local. In this section, the signatures for DNSServiceEnumerateDomain( ) and DNSServiceDo-mainEnumReply( ) are provided along with an example of using this part of the API. 7.3.4.1. DNSServiceEnumerateDomainBegin searching for recommended browsing or registration domains using DNSServiceEnumerateDomain( ): DNSServiceErrorType DNSServiceEnumerateDomains( DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceDomainEnumReply callBack, void *context); Set the flag either using kDNSServiceFlagsBrowseDomains to return the domains recommended for browsing or using kDNSServicesFlagsRegistrationDomains to return the domains recommended for registering services. The sdRef is an uninitialized service discovery reference that will be initialized when DNSServiceEnumerateDomain( ) is called. The remaining variables are as they were for DNSServiceBrowse( ) and DNSServiceRegister( ). 7.3.4.2. DNSServiceDomainEnumReplyThe callback function for enumerating domains must be modeled on DNSServiceDo-mainEnumReply( ). void MyDNSServiceDomainEnumReply ( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *replyDomain, void *context ); The variables have been described previously for DNSServiceRegisterReply( ) and DNSServiceBrowseReply( ). Consult Table 7-2 for the flags that can be passed in. As before, kDNSServiceFlagsMoreComing indicates that you should wait to update your UI, as the callback function will be called again immediately. If kDNSServiceFlagsAdd is set, then the domain pointed to should be added to the list of domains, and if kDNSServiceFlagsAdd is not present, then the domain pointed to should be removed from the list. The kDNSServiceFlagsDefault flag is set if the domain is the domain that should be selected by default. 7.3.4.3. Enumeration exampleThe enumeration example shown in Example 7-6 follows the same pattern as the previous examples. The call to DNSServiceEnumerateDomains( ) passes in the flag for browse domains. In the callback function, the discovered domains are displayed, along with an indication of whether there are kDNSServiceFlagsMoreComing and with a field showing whether the domain is being added or removed from the list and whether it is a default domain. Example 7-6. DNSServiceEnumerateDomains( ) example#include <dns_sd.h> #include <stdio.h> // For stdout, stderr #include <string.h> // For strlen(), strcpy( ), bzero( ) extern void HandleEvents(DNSServiceRef); static void MyEnumerateBrowseDomainsCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interface, DNSServiceErrorType errorCode, const char *replyDomain, void *context) { #pragma unused(context) if (errorCode != kDNSServiceErr_NoError) fprintf(stderr, "EnumerateDomainsCallBack returned %d\n", errorCode); else { char *moreString = (flags & kDNSServiceFlagsMoreComing) ? "MORE" : ""; char *addString = "REMOVE"; if (flags & kDNSServiceFlagsAdd) addString = (flags & kDNSServiceFlagsDefault) ? "DEFAULT" : "ADD"; printf("%-8s%-5s%s\n", addString, moreString, replyDomain); } if (!(flags & kDNSServiceFlagsMoreComing)) fflush(stdout); } static DNSServiceErrorType MyDNSServiceEnumerateBrowse( ) { DNSServiceErrorType error; DNSServiceRef serviceRef; error = DNSServiceEnumerateDomains( &serviceRef, kDNSServiceFlagsBrowseDomains, // browse domains 0, // all network interfaces MyEnumerateBrowseDomainsCallBack, //callback function NULL); // no context if (error == kDNSServiceErr_NoError) { HandleEvents(serviceRef); // Add service to runloop to get callbacks DNSServiceRefDeallocate(serviceRef); } return error; } int main (int argc, const char * argv[]) { DNSServiceErrorType error = MyDNSServiceEnumerateBrowse( ); if (error) fprintf(stderr, "DNSServiceDiscovery returned %d\n", error); return 0; } You can run this example by compiling it and running it with DNSServiceCallback-Select.c. As mentioned before, if your network administrator has not created any domain enumeration records, the only result you will get is local. ADD local. 7.3.5. Other OperationsThis section outlines some of the other lesser-used functions from dns_sd.h. DNSServiceCreateConnection( ) and DNSServiceRegisterRecord( ) are used by applications that need to create a large number of records. A single DNSServiceRef is created using DNSServiceCreateConnection( ), and then multiple records are registered on that single connection. DNSServiceQueryRecord( ) allows the client to query for any arbitrary DNS record, with any name, type, or class, unicast or multicast. It is, in many ways, similar to the standard Unix res_query( ) function, except that it operates asynchronously with a callback function. DNSServiceReconfirmRecord( ) is used for cache management. Multicast DNS caches data for efficiency, but anytime data is cached, it can become out of date. If a client believes that data is out of date, it can call DNSServiceReconfirmRecord( ) to provide a hint to the cache management algorithm. For example, suppose a client gets a host's address record using DNSServiceQueryRecord( ), but the host does not respond. If the client calls DNSServiceReconfirmRecord( ), then Multicast DNS will requery for the record, and if no response is received, then the record will be deleted from the cache. In addition, any SRV records referencing that target host will automatically be considered potentially suspect and will, in turn, be reconfirmed. If the SRV records are not confirmed, then they too will be deleted from the cache, and any PTR records referencing the now-departed SRV records will also be considered potentially suspect. After a short time, if these records are not confirmed, they will also be deleted from the cache. The end result of this is that services being advertised from the departed host will disappear from browsing lists soon, instead of waiting a full hour for the record's TTL to expire. This call is highly specialized, and most applications will never have to use it, because in normal cases cache reconfirmation is handled automatically. In normal cases, if a host crashes, then the DNSServiceQueryRecord( ) call to look up its IP address will fail, and that will automatically kick off the chain of reconfirmations that purges the stale SRV and PTR records, too. |