Channels ▼
RSS

.NET

DNS Service Discovery on Windows


An Example — Discover Service Types

I've written a demo program that browses the network for details on every service instance it can find and presents the results in a tree format. Figure 2 shows the program running on my home network, where I have drilled down to get information about an instance of a print service.

Figure 2: The ServiceBrowser sample program.

This program has to start by doing a top-level iteration of all the service types currently seen on my network. In my blog post, I noted that I can use a special browse command to accomplish this. If you've installed the Bonjour SDK, you can run the dns-sd program and browse for service type _services._dns-sd._udp, which should give results something like this:

C:\Users\Mark>dns-sd -B _services._dns-sd._udp
Browsing for _services._dns-sd._udp
Timestamp     A/R Flags if Domain  Service Type  Instance Name
17:38:09.407  Add     3 13 .       _tcp.local.     _smb
17:38:09.407  Add     3 13 .       _tcp.local.     _printer
17:38:09.408  Add     3 13 .       _tcp.local.     _pdl-datastream
17:38:09.408  Add     3 13 .       _tcp.local.     _http
17:38:09.408  Add     3 13 .       _tcp.local.     _tivo-videos
17:38:09.409  Add     2 13 .       _tcp.local.     _csco-sb

Looking to do the same thing in my demo program, I turn to the header file dns_sd.h. This file ships with the Bonjour SDK and contains not only the API definition, but what passes for documentation. In that header file, I see that there is a function called DNSServiceBrowse, and it looks like it does exactly what I want. In my sample program, my call to this routine is shown below:

DNSServiceRef client = NULL;
DNSServiceErrorType err;
err = DNSServiceBrowse( &client, 
                        0, 
                        0, 
                        "_services._dns-sd._udp", 
                        "", 
                        IterateServiceTypes, 
                        this );

You can get some exposition on each of the arguments I pass to the function from the header file, my brief comments on each are given below:

  • sdRef: Every call to the Bonjour API creates a new DNSServiceRef handle. It is initialized by the function call, and used later to retrieve the results.
  • flags: This parameter is not used in this version of the SDK.
  • interfaceIndex: This argument is used to select a specific network interface. For this particular function I want to browse on all available interfaces, so a value of 0 is used.
  • regType: The type of service being browsed for. Normally when you call DNSServiceBrowse you will use this parameter to specify a specific service type that you are interested in, such as _http._tcp. I'm using the special type of _services._dns-sd._udp in order to get a list of all published service types, not specific instances.
  • domain: By passing an empty string, I tell the service that I want to see advertisements from all domains.
  • callback: The pointer to a callback function that will receive the responses to this request. The argument I pass in, IterateServiceTypes, is a static member of my MCF Dialog class.
  • context: The context variable is an opaque pointer type that is passed in to the Bonjour service. When it performs a callback, it will include a copy of the context pointer for the use of the callback function. I always pass in a pointer to my MFC Dialog class, so my callback functions have full access to the class members.

The important thing to note here is that the call to the API function doesn't return any important data. All I get back is an error code indicating that the request is being processed, and a DNSServiceRef handle that I use to track that progress.

The real action comes when my callback function is invoked. IterateServiceTypes is a static member of my Dialog class. Apple kept things simple by having one callback type for C and C++, which means no member functions. You could easily build shims to make it appear as though the DLL was calling member functions — it would just take a small modification to the code I'll show you here.

The function definition has to follow exactly the declaration given in dns_sd.h. My implementation starts like this:

void DNSSD_API CServiceBrowserDlg::IterateServiceTypes( DNSServiceRef sdRef,
                                                        DNSServiceFlags flags,
                                                        uint32_t interfaceIndex,
                                                        DNSServiceErrorType errorCode,
                                                        const char *serviceName,
                                                        const char *regtype,
                                                        const char *domain,
                                                        void *context )
{
	CServiceBrowserDlg *p = (CServiceBrowserDlg *) context;

It's worth walking through a look at each of the parameters in the callback:

  • sdRef: This is the same DNSServiceRef value that was created when the call was made to DNSServiceBrowse. You don't need to make any use of it in the callback, but it does provide a good way to correlate results with function calls, particularly if you are using a single callback function to process many different results.
  • flags: There are two important flag bits to check in this value. The first value, kDNSServiceFlagsMoreComing is used to indicate that there are definitely more callbacks coming. If that bit is cleared, there is no more pending data. The second flag bit, kDNSServiceFlagsAdd is used to indicate whether this service is being added or deleted. When you first start browsing, all the callbacks will be for services added. As services are added and removed from the system, additional callbacks will be generated with this bit both set and cleared.
  • interfaceIndex: In the callback, this index will be set to the index of the network interface where the advertisement was found. When it comes time to resolve this service, you need to pass in the correct index.
  • errorCode: If this value is not zero, the callback is indicating an error. As long as it is zero, your code can process the input safely.
  • serviceName: This value contains the name of a discovered service — it is the whole point of the callback. Normally, this will contain the name of an instance of a service. However, when browsing for the special name _services._dns-sd._udp, the instance name is actually a service type.
  • regtype: The type of the service — you may already know this information by the time you reach the callback, but if the callback is handling the results from multiple queries, it can be helfpul.
  • domain: The domain of the discovered service. Like the interface index, you need use the domain when you are attempting to resolve the service.
  • context: A copy of the context variable passed in when the browse call was made.

If you look at the first line of the aforementioned code sample, the first thing I do is cast the context pointer to its correct type, a pointer to my MFC Dialog class. Then I can make full use of all the members of the class, albeit via a call through a pointer instead of directly.

So what do I do with these services once I receive them? Well, for each service type that I find, I kick off a new browse process, looking for specific instances of the service. As an example, in my callback IterateServiceTypes, one of the callbacks returns a service type of _printer._tcp. In order to find all instances of this service, I have to call DNSServiceBrowse again, with that service name and the correct interface and domain. After inserting the service type into the tree, I make that call so I can start adding those instances:

HTREEITEM item = p->m_Tree.InsertItem( CA2T(service_type.c_str()), TVI_ROOT, TVI_SORT );
DNSServiceRef client = NULL;
DNSServiceErrorType err;
err = DNSServiceBrowse( &client, 
                        0, 
                        0, 
                        service_type.c_str(), 
                        "", 
                        IterateServiceInstances, 
                        context );

The key point to note about this call is that the callback function, IterateServiceInstances, is a different member function — one that expects to get the results of my browsing for instances of a specific service.

Driving the Callbacks

So how do these callbacks actually get generated? Does the DLL asynchronously make calls into my code whenever events occur?

The Bonjour SDK lets your program control when callbacks occur by giving you the handle to the message pump. When you call DNSServiceProcessResult() with a single argument of a DNSServiceRef, you will generate a single callback message for the given reference. The callback will occur within the context of the call to DNSServiceProcessResult().

When you call DNSServiceProcessResult(), the Bonjour DLL will block if there are no messages ready to process. So how do you know when there are messages ready?

The indicator that messages are ready is given by a file descriptor associated with the DNSServiceRef. You can get a copy of the file descriptor by calling DNSServiceRefSockFD(), passing in a copy of the reference. When the file descriptor has data ready to read, you have callbacks pending. The easiest way to check this condition is to use the select() function, which can check multiple references in one fell swoop.

In my implementation of the callback message pump, I rely on an unordered_map called m_ClientToFdMap that contains a copy of all the DNSServiceRef references currently waiting for responses. I create the necessary data structure used by select(), then call it to get a list of all references that have callbacks pending. The core of this code looks like this:

int result = select(0, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
if ( result > 0 ) {
//
// While iterating through the loop, the callback functions might delete
// the client pointed to by the current iterator, so I have to increment
// it BEFORE calling DNSServiceProcessResult
//
    for ( auto ii = m_ClientToFdMap.cbegin() ; ii != m_ClientToFdMap.cend() ; ) {
        auto jj = ii++;
        if (FD_ISSET(jj->second, &readfds) ) 
            DNSServiceErrorType err = DNSServiceProcessResult(jj->first);
    }
}

This generates my callbacks efficiently, and because they are in the context of my main program's UI thread, I avoid a lot of unpleasantness.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video