mDNS20170217 of network protocol

DNS (Domain Name System, Domain Name System) on the Internet is a distributed database that maps domain names and IP addresses to each other, enabling users to access the Internet more conveniently without having to remember IP strings that can be directly read by machines. The process of obtaining the IP address corresponding to the host name through the host name is called domain name resolution (or host name resolution). The DNS protocol runs on top of the UDP protocol and uses port number 53. In the RFC document, RFC 2181 has a specification for DNS, RFC 2136 describes the dynamic update of DNS, and RFC 2308 describes the reverse cache of DNS query.

1. The specific protocol specification address of mDNS is as follows: http://www.ietf.org/rfc/rfc6762.txt

mdns is Multicast DNS (Multicast DNS). mDNS mainly realizes mutual discovery and communication between hosts in the local area network without traditional DNS server. The port used is 5353, which follows the dns protocol and uses the existing DNS information structure. , name syntax, and resource record type. And no new action code or response code was specified.

 

In a local area network, a device needs to know the IP address of the other party before communicating with each other. In most cases, the IP address of the device is not a static IP address, but an IP address dynamically allocated through the dhcp protocol. , For example: Now the communication between the IoT device and the app, either the app sends some specific information through broadcast or multicast, the interested device responds, and realizes the discovery of LAN devices. Of course, mdns is much more powerful than this.

1.mDNS is based on UDP protocol.

Multicast address: The multicast address uses a class D address, and the address range is: 224.0.0.0—239.255.255.255

 

2. A brief description of the working principle of mdns:

 

The multicast address used by mdns is: 224.0.0.251 (ipv6: FF02::FB), the port is 5353, mdns is used inside the LAN, and the domain name of the host ends in .local, each host entering the LAN, if mDNS is enabled If the service is used, it will multicast a message to all hosts in the local area network, who am I (domain name), and what is my IP address. Then other hosts with mdns service will respond and tell you who it is (domain name) and what its IP address is. Of course, when the device needs service, it uses mdns to query the corresponding IP address of the domain name. After the corresponding device receives the message, it also responds by multicast. At this time, other host devices can also receive the response message, and other hosts can also Will record the domain name and ip and ttl, etc., update the cache

 

For example, host A enters the LAN, enables the mDNS service, and registers the following information with the mDNS service: I provide FTP service, my IP is 192.168.1.101, and the port is 21. When host B enters the LAN and requests the mDNS service of host B, I want to find the FTP server in the LAN, and the mDNS of host B will go to the LAN to ask other mDNS, and finally tell you that there is an IP address 192.168.1.101 , the host whose port number is 21, that is, host A provides FTP service, so host B knows the IP address and port number of host A.

 

The general principle is like this. mDNS provides far more services than this. Of course, there are many services but not complicated.

3. Relationship between mDNSResponder and Bonjour:

The mDNSResponder project is a component of Bonjour,

Apple's ease-of-use IP networking initiative:

<http://developer.apple.com/bonjour/>

Bonjour means Hello in French. It is Apple's name for an open, zero-configuration networking standard based on multicast DNS. Devices using Bonjour automatically multicast their own service information in the network and monitor the service information of other devices. The devices are like greeting each other, which is why the technology is named Bonjour. Bonjour makes it easy to find systems and services on a local area network even without a network administrator.

Take a simple example: in a local area network, if you want to perform printing services, you must first know the IP address of the print server. This IP address is generally assigned by someone in the IT department, and then he has to send an email to all the staff to publicize the address. With Bonjour, the print server itself will find an available IP within the local area network according to the zero-configuration network standard and register a print service called "print service" or the like. When a client needs a print service, it will first search for a print server within the network. Since the IP address of the print server is not known, clients can only find printers by names such as "print service". With the help of Bonjour, the client can finally find the printer registered with the name "print service" and obtain its IP address and port number.

From a Bonjour perspective, the technology mainly solves three problems:

  • Addressing: assigning IP to the host. Bonjour's Addressing process is relatively simple, that is, each host finds an IP within the optional range of addresses within the network, and then checks whether there are other hosts within the network for reuse. If the IP is not assigned, it will use this IP.
  • Naming: Naming resolves the correspondence between host names and IP addresses. Bonjour uses Multiple DNS technology, that is, DNS query messages will be sent through UDP multicast. Once a machine in the network finds that the queried machine name is the same as the one set by itself, it will reply to this request. In addition, Bonjour also expands the use of MDNS, that is, in addition to searching for hosts, it also supports searching for services. However, Bonjour's Naming has a limitation, that is, there cannot be a host or service with the same name within the network.
  • Service Discovery: SD is based on the above Naming work, which enables applications to find services within the network and resolve the IP address and port number corresponding to the service. Once the application obtains the IP address and port number of the service, it can directly establish an interactive relationship with the service.

Bonjour technology has been widely used in Mac OS, Itunes and Iphone. For further promotion, Apple open sourced it through the open source project mdnsresponder. On Windows platform, it will generate a daemon mdnsresponder. On Android platforms (or Linux platforms that support POSIX) it is a program called mdnsd. However, whether it is mdnsresponder or mdnsd, all application developers have to do is to use Bonjour's API to initiate service registration, service query and service resolution requests to them and receive processing results from them.

Below we will introduce the three most used functions in the Bonjour API, which are service registration, service query and service resolution. Understanding the functions of these three functions is also the basis for understanding MDnsSdListener.

To use the Bonjour API you must include the following header files and dynamic libraries and link to:

#include < dns_sd.h > //This header file must be included

libmdnssd.so   // link to this so

In Bonjour, the API for service registration is DNSServiceRegister. The prototype is shown in Figure 1:

                    

Figure 1 DNSServiceRegister prototype

The function is explained as follows:

  • sdRef: Represents an uninitialized DNSService entity. Its type DNSServiceRef is a pointer. This parameter is finally allocated and initialized by the DNSServiceRegister function.
  • flags: Indicates the conflict handling when there are services with the same name in the network. The default is to modify the service names sequentially. For example, the name of the service to be registered is "printer", when a duplicate name conflict is detected, it can be renamed "printer(1)".
  • interfaceIndex: Indicates which network interfaces the service outputs to the host. A value of -1 means native support only, that is, the service is used on the loop interface.
  • name: Indicates the service name, if it is empty, the machine name is used.
  • regtype: Service type, expressed as a string. Bonjour requires the format to be "_servicename._transport protocol", eg "_ftp._tcp". The current transport protocol only supports TCP and UDP.
  • domian and host are generally empty.
  • port indicates the port of the service. If 0, Bonjour will automatically assign one.
  • The txtLen and txtRecord strings are used to describe the service. Usually set to empty.
  • callBack: Set the callback function. The request result registered by the server will be called back to the client through it.
  • context: context pointer, set by the application.

When the client needs to search for a specific service within the network, it needs to use the DNSServiceBrowser API, whose prototype is shown in Figure 2:

                     

Figure 2 DNSServiceBrowser prototype

in:

  • The meanings of sdref, interfaceIndex, regtype, domain, and context are the same as those of DNSServiceRegister.
  • flags: has no effect in this function.
  • callBack: Callback notification interface for DNSServiceBrowser processing results.

When the client wants to obtain the IP and port number of the specified service, it needs to use the DNSServiceResolve API, whose prototype is shown in Figure 3:

                      

Figure 3 DNSServiceResolve prototype

in:

  • The name, regtype and domain are all obtained from the processing result of the DNSServiceBrowse function.
  • callBack is used to notify DNSServiceResolve of the processing result. This callback function will return the IP address and port number of the service.

If you need to understand the usage and principles of Bonjour in Android, please read the original text of this section: http://www.ifindbug.com/doc/id-44760/name-Android Says Bonjour.html

 

4. How to use in Linux:

The attachment provides the source code of mDNS. By analyzing the source code, we can know how to compile, install and use:

In the main in the Responder.c file in the mDNSResponder-107.5\mDNSPosix directory we can see

Usage instructions are provided in Calling the PrintUsage function:

Example using shell command invocation as described above:

//mDNSResponderPosix comes from bonjour, service registration

sprintf(buf, "mDNSResponderPosix -n %s -t _ipc_http._tcp. " "-d local. -p 5959 &", gpCC->ccArg.pDevId);//-n service name, -t service type, -d domain name, -p port number,

    system(buf);//Running in the background

Look at the execution process in the main function:

After initialization, the RegisterOurServices function is called to register the services to be provided (tracking down, you can see that the mDNS_RegisterService function in mdnscore.c is finally called):

Finally, if you want to know more about it, you can read the following files, or search and download the source code of mDNS on the Internet.

  1 /* -*- Mode: C; tab-width: 4 -*-
  2  *
  3  * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
  4  *
  5  * @[email protected]
  6  * 
  7  * This file contains Original Code and/or Modifications of Original Code
  8  * as defined in and that are subject to the Apple Public Source License
  9  * Version 2.0 (the 'License'). You may not use this file except in
 10  * compliance with the License. Please obtain a copy of the License at
 11  * http://www.opensource.apple.com/apsl/ and read it before using this
 12  * file.
 13  * 
 14  * The Original Code and all software distributed under the License are
 15  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 16  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 17  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 18  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 19  * Please see the License for the specific language governing rights and
 20  * limitations under the License.
 21  * 
 22  * @[email protected]
 23 
 24     Change History (most recent first):
 25 
 26 $Log: Responder.c,v $
 27 Revision 1.30  2005/10/26 22:21:16  cheshire
 28 <rdar://problem/4149841> Potential buffer overflow in mDNSResponderPosix
 29 
 30 Revision 1.29  2005/03/04 21:35:33  cheshire
 31 <rdar://problem/4037201> Services.txt file not parsed properly when it contains more than one service
 32 
 33 Revision 1.28  2005/01/11 01:55:26  ksekar
 34 Fix compile errors in Posix debug build
 35 
 36 Revision 1.27  2004/12/01 04:28:43  cheshire
 37 <rdar://problem/3872803> Darwin patches for Solaris and Suse
 38 Use version of daemon() provided in mDNSUNP.c instead of local copy
 39 
 40 Revision 1.26  2004/11/30 22:37:01  cheshire
 41 Update copyright dates and add "Mode: C; tab-width: 4" headers
 42 
 43 Revision 1.25  2004/11/11 02:00:51  cheshire
 44 Minor fixes to getopt, error message
 45 
 46 Revision 1.24  2004/11/09 19:32:10  rpantos
 47 Suggestion from Ademar de Souza Reis Jr. to allow comments in services file
 48 
 49 Revision 1.23  2004/09/17 01:08:54  cheshire
 50 Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h
 51   The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces
 52   declared in that file are ONLY appropriate to single-address-space embedded applications.
 53   For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used.
 54 
 55 Revision 1.22  2004/09/16 01:58:22  cheshire
 56 Fix compiler warnings
 57 
 58 Revision 1.21  2004/06/15 03:48:07  cheshire
 59 Update mDNSResponderPosix to take multiple name=val arguments in a sane way
 60 
 61 Revision 1.20  2004/05/18 23:51:26  cheshire
 62 Tidy up all checkin comments to use consistent "<rdar://problem/xxxxxxx>" format for bug numbers
 63 
 64 Revision 1.19  2004/03/12 08:03:14  cheshire
 65 Update comments
 66 
 67 Revision 1.18  2004/01/25 00:00:55  cheshire
 68 Change to use mDNSOpaque16fromIntVal() instead of shifting and masking
 69 
 70 Revision 1.17  2003/12/11 19:11:55  cheshire
 71 Fix compiler warning
 72 
 73 Revision 1.16  2003/08/14 02:19:55  cheshire
 74 <rdar://problem/3375491> Split generic ResourceRecord type into two separate types: AuthRecord and CacheRecord
 75 
 76 Revision 1.15  2003/08/12 19:56:26  cheshire
 77 Update to APSL 2.0
 78 
 79 Revision 1.14  2003/08/06 18:20:51  cheshire
 80 Makefile cleanup
 81 
 82 Revision 1.13  2003/07/23 00:00:04  cheshire
 83 Add comments
 84 
 85 Revision 1.12  2003/07/15 01:55:16  cheshire
 86 <rdar://problem/3315777> Need to implement service registration with subtypes
 87 
 88 Revision 1.11  2003/07/14 18:11:54  cheshire
 89 Fix stricter compiler warnings
 90 
 91 Revision 1.10  2003/07/10 20:27:31  cheshire
 92 <rdar://problem/3318717> mDNSResponder Posix version is missing a 'b' in the getopt option string
 93 
 94 Revision 1.9  2003/07/02 21:19:59  cheshire
 95 <rdar://problem/3313413> Update copyright notices, etc., in source code comments
 96 
 97 Revision 1.8  2003/06/18 05:48:41  cheshire
 98 Fix warnings
 99 
100 Revision 1.7  2003/05/06 00:00:50  cheshire
101 <rdar://problem/3248914> Rationalize naming of domainname manipulation functions
102 
103 Revision 1.6  2003/03/08 00:35:56  cheshire
104 Switched to using new "mDNS_Execute" model (see "mDNSCore/Implementer Notes.txt")
105 
106 Revision 1.5  2003/02/20 06:48:36  cheshire
107 <rdar://problem/3169535> Xserve RAID needs to do interface-specific registrations
108 Reviewed by: Josh Graessley, Bob Bradley
109 
110 Revision 1.4  2003/01/28 03:07:46  cheshire
111 Add extra parameter to mDNS_RenameAndReregisterService(),
112 and add support for specifying a domain other than dot-local.
113 
114 Revision 1.3  2002/09/21 20:44:53  zarzycki
115 Added APSL info
116 
117 Revision 1.2  2002/09/19 04:20:44  cheshire
118 Remove high-ascii characters that confuse some systems
119 
120 Revision 1.1  2002/09/17 06:24:35  cheshire
121 First checkin
122 
123 */
124 
125 #include "mDNSEmbeddedAPI.h"// Defines the interface to the client layer above
126 #include "mDNSPosix.h"    // Defines the specific types needed to run mDNS on this platform
127 
128 #include <assert.h>
129 #include <stdio.h>            // For printf()
130 #include <stdlib.h>            // For exit() etc.
131 #include <string.h>            // For strlen() etc.
132 #include <unistd.h>            // For select()
133 #include <errno.h>            // For errno, EINTR
134 #include <signal.h>
135 #include <fcntl.h>
136 
137 #if COMPILER_LIKES_PRAGMA_MARK
138 #pragma mark ***** Globals
139 #endif
140 
141 static mDNS mDNSStorage;       // mDNS core uses this to store its globals
142 static mDNS_PlatformSupport PlatformStorage;  // Stores this platform's globals
143 
144 static const char *gProgramName = "mDNSResponderPosix";
145 
146 #if COMPILER_LIKES_PRAGMA_MARK
147 #pragma mark ***** Signals
148 #endif
149 
150 static volatile mDNSBool gReceivedSigUsr1;
151 static volatile mDNSBool gReceivedSigHup;
152 static volatile mDNSBool gStopNow;
153 
154 // We support 4 signals.
155 //
156 // o SIGUSR1 toggles verbose mode on and off in debug builds
157 // o SIGHUP  triggers the program to re-read its preferences.
158 // o SIGINT  causes an orderly shutdown of the program.
159 // o SIGQUIT causes a somewhat orderly shutdown (direct but dangerous)
160 // o SIGKILL kills us dead (easy to implement :-)
161 //
162 // There are fatal race conditions in our signal handling, but there's not much 
163 // we can do about them while remaining within the Posix space.  Specifically, 
164 // if a signal arrives after we test the globals its sets but before we call 
165 // select, the signal will be dropped.  The user will have to send the signal 
166 // again.  Unfortunately, Posix does not have a "sigselect" to atomically 
167 // modify the signal mask and start a select.
168 
169 static void HandleSigUsr1(int sigraised)
170     // If we get a SIGUSR1 we toggle the state of the 
171     // verbose mode.
172 {
173     assert(sigraised == SIGUSR1);
174     gReceivedSigUsr1 = mDNStrue;
175 }
176 
177 static void HandleSigHup(int sigraised)
178     // A handler for SIGHUP that causes us to break out of the 
179     // main event loop when the user kill 1's us.  This has the 
180     // effect of triggered the main loop to deregister the 
181     // current services and re-read the preferences.
182 {
183     assert(sigraised == SIGHUP);
184     gReceivedSigHup = mDNStrue;
185 }
186 
187 static void HandleSigInt(int sigraised)
188     // A handler for SIGINT that causes us to break out of the 
189     // main event loop when the user types ^C.  This has the 
190     // effect of quitting the program.
191 {
192     assert(sigraised == SIGINT);
193     
194     if (gMDNSPlatformPosixVerboseLevel > 0) {
195         fprintf(stderr, "\nSIGINT\n");
196     }
197     gStopNow = mDNStrue;
198 }
199 
200 static void HandleSigQuit(int sigraised)
201     // If we get a SIGQUIT the user is desperate and we 
202     // just call mDNS_Close directly.  This is definitely 
203     // not safe (because it could reenter mDNS), but 
204     // we presume that the user has already tried the safe 
205     // alternatives.
206 {
207     assert(sigraised == SIGQUIT);
208 
209     if (gMDNSPlatformPosixVerboseLevel > 0) {
210         fprintf(stderr, "\nSIGQUIT\n");
211     }
212     mDNS_Close(&mDNSStorage);
213     exit(0);
214 }
215 
216 #if COMPILER_LIKES_PRAGMA_MARK
217 #pragma mark ***** Parameter Checking
218 #endif
219 
220 static mDNSBool CheckThatRichTextNameIsUsable(const char *richTextName, mDNSBool printExplanation)
221     // Checks that richTextName is reasonable 
222     // label and, if it isn't and printExplanation is true, prints 
223     // an explanation of why not.
224 {
225     mDNSBool result = mDNStrue;
226     if (result && strlen(richTextName) > 63) {
227         if (printExplanation) {
228             fprintf(stderr, 
229                     "%s: Service name is too long (must be 63 characters or less)\n", 
230                     gProgramName);
231         }
232         result = mDNSfalse;
233     }
234     if (result && richTextName[0] == 0) {
235         if (printExplanation) {
236             fprintf(stderr, "%s: Service name can't be empty\n", gProgramName);
237         }
238         result = mDNSfalse;
239     }
240     return result;
241 }
242 
243 static mDNSBool CheckThatServiceTypeIsUsable(const char *serviceType, mDNSBool printExplanation)
244     // Checks that serviceType is a reasonable service type 
245     // label and, if it isn't and printExplanation is true, prints 
246     // an explanation of why not.
247 {
248     mDNSBool result;
249     
250     result = mDNStrue;
251     if (result && strlen(serviceType) > 63) {
252         if (printExplanation) {
253             fprintf(stderr, 
254                     "%s: Service type is too long (must be 63 characters or less)\n", 
255                     gProgramName);
256         }
257         result = mDNSfalse;
258     }
259     if (result && serviceType[0] == 0) {
260         if (printExplanation) {
261             fprintf(stderr, 
262                     "%s: Service type can't be empty\n", 
263                     gProgramName);
264         }
265         result = mDNSfalse;
266     }
267     return result;
268 }
269 
270 static mDNSBool CheckThatPortNumberIsUsable(long portNumber, mDNSBool printExplanation)
271     // Checks that portNumber is a reasonable port number
272     // and, if it isn't and printExplanation is true, prints 
273     // an explanation of why not.
274 {
275     mDNSBool result;
276     
277     result = mDNStrue;
278     if (result && (portNumber <= 0 || portNumber > 65535)) {
279         if (printExplanation) {
280             fprintf(stderr, 
281                     "%s: Port number specified by -p must be in range 1..65535\n", 
282                     gProgramName);
283         }
284         result = mDNSfalse;
285     }
286     return result;
287 }
288 
289 #if COMPILER_LIKES_PRAGMA_MARK
290 #pragma mark ***** Command Line Arguments
291 #endif
292 
293 static const char kDefaultPIDFile[]     = "/var/run/mDNSResponder.pid";
294 static const char kDefaultServiceType[] = "_afpovertcp._tcp.";
295 static const char kDefaultServiceDomain[] = "local.";
296 enum {
297     kDefaultPortNumber = 548
298 };
299 
300 static void PrintUsage()
301 {
302     fprintf(stderr, 
303             "Usage: %s [-v level ] [-r] [-n name] [-t type] [-d domain] [-p port] [-f file] [-b] [-P pidfile] [-x name=val ...]\n", 
304             gProgramName);
305     fprintf(stderr, "          -v verbose mode, level is a number from 0 to 2\n");
306     fprintf(stderr, "             0 = no debugging info (default)\n");
307     fprintf(stderr, "             1 = standard debugging info\n");
308     fprintf(stderr, "             2 = intense debugging info\n");
309     fprintf(stderr, "             can be cycled kill -USR1\n");
310     fprintf(stderr, "          -r also bind to port 53 (port 5353 is always bound)\n");
311     fprintf(stderr, "          -n uses 'name' as the service name (required)\n");
312     fprintf(stderr, "          -t uses 'type' as the service type (default is '%s')\n", kDefaultServiceType);
313     fprintf(stderr, "          -d uses 'domain' as the service domain (default is '%s')\n", kDefaultServiceDomain);
314     fprintf(stderr, "          -p uses 'port' as the port number (default is '%d')\n",  kDefaultPortNumber);
315     fprintf(stderr, "          -f reads a service list from 'file'\n");
316     fprintf(stderr, "          -b forces daemon (background) mode\n");
317     fprintf(stderr, "          -P uses 'pidfile' as the PID file\n");
318     fprintf(stderr, "             (default is '%s')\n",  kDefaultPIDFile);
319     fprintf(stderr, "             only meaningful if -b also specified\n");
320     fprintf(stderr, "          -x stores name=val in TXT record (default is empty).\n");
321     fprintf(stderr, "             MUST be the last command-line argument;\n");
322     fprintf(stderr, "             all subsequent arguments after -x are treated as name=val pairs.\n");
323 }
324 
325 static   mDNSBool  gAvoidPort53      = mDNStrue;
326 static const char *gServiceName      = "";
327 static const char *gServiceType      = kDefaultServiceType;
328 static const char *gServiceDomain    = kDefaultServiceDomain;
329 static mDNSu8      gServiceText[sizeof(RDataBody)];
330 static mDNSu16     gServiceTextLen   = 0;
331 static        int  gPortNumber       = kDefaultPortNumber;
332 static const char *gServiceFile      = "";
333 static   mDNSBool  gDaemon           = mDNSfalse;
334 static const char *gPIDFile          = kDefaultPIDFile;
335 
336 static void ParseArguments(int argc, char **argv)
337     // Parses our command line arguments into the global variables 
338     // listed above.
339 {
340     int ch;
341     
342     // Set gProgramName to the last path component of argv[0]
343     
344     gProgramName = strrchr(argv[0], '/');
345     if (gProgramName == NULL) {
346         gProgramName = argv[0];
347     } else {
348         gProgramName += 1;
349     }
350     
351     // Parse command line options using getopt.
352     
353     do {
354         ch = getopt(argc, argv, "v:rn:t:d:p:f:dP:bx");
355         if (ch != -1) {
356             switch (ch) {
357                 case 'v':
358                     gMDNSPlatformPosixVerboseLevel = atoi(optarg);
359                     if (gMDNSPlatformPosixVerboseLevel < 0 || gMDNSPlatformPosixVerboseLevel > 2) {
360                         fprintf(stderr, 
361                                 "%s: Verbose mode must be in the range 0..2\n", 
362                                 gProgramName);
363                         exit(1);
364                     }
365                     break;
366                 case 'r':
367                     gAvoidPort53 = mDNSfalse;
368                     break;
369                 case 'n':
370                     gServiceName = optarg;
371                     if ( ! CheckThatRichTextNameIsUsable(gServiceName, mDNStrue) ) {
372                         exit(1);
373                     }
374                     break;
375                 case 't':
376                     gServiceType = optarg;
377                     if ( ! CheckThatServiceTypeIsUsable(gServiceType, mDNStrue) ) {
378                         exit(1);
379                     }
380                     break;
381                 case 'd':
382                     gServiceDomain = optarg;
383                     break;
384                 case 'p':
385                     gPortNumber = atol(optarg);
386                     if ( ! CheckThatPortNumberIsUsable(gPortNumber, mDNStrue) ) {
387                         exit(1);
388                     }
389                     break;
390                 case 'f':
391                     gServiceFile = optarg;
392                     break;
393                 case 'b':
394                     gDaemon = mDNStrue;
395                     break;
396                 case 'P':
397                     gPIDFile = optarg;
398                     break;
399                 case 'x':
400                     while (optind < argc)
401                         {
402                         gServiceText[gServiceTextLen] = strlen(argv[optind]);
403                         memcpy(gServiceText+gServiceTextLen+1, argv[optind], gServiceText[gServiceTextLen]);
404                         gServiceTextLen += 1 + gServiceText[gServiceTextLen];
405                         optind++;
406                         }
407                     ch = -1;
408                     break;
409                 case '?':
410                 default:
411                     PrintUsage();
412                     exit(1);
413                     break;
414             }
415         }
416     } while (ch != -1);
417 
418     // Check for any left over command line arguments.
419     
420     if (optind != argc) {
421         PrintUsage();
422         fprintf(stderr, "%s: Unexpected argument '%s'\n", gProgramName, argv[optind]);
423         exit(1);
424     }
425     
426     // Check for inconsistency between the arguments.
427     
428     if ( (gServiceName[0] == 0) && (gServiceFile[0] == 0) ) {
429         PrintUsage();
430         fprintf(stderr, "%s: You must specify a service name to register (-n) or a service file (-f).\n", gProgramName);
431         exit(1);
432     }
433 }
434 
435 #if COMPILER_LIKES_PRAGMA_MARK
436 #pragma mark ***** Registration
437 #endif
438 
439 typedef struct PosixService PosixService;
440 
441 struct PosixService {
442     ServiceRecordSet coreServ;
443     PosixService *next;
444     int serviceID;
445 };
446 
447 static PosixService *gServiceList = NULL;
448 
449 static void RegistrationCallback(mDNS *const m, ServiceRecordSet *const thisRegistration, mStatus status)
450     // mDNS core calls this routine to tell us about the status of 
451     // our registration.  The appropriate action to take depends 
452     // entirely on the value of status.
453 {
454     switch (status) {
455 
456         case mStatus_NoError:      
457             debugf("Callback: %##s Name Registered",   thisRegistration->RR_SRV.resrec.name->c); 
458             // Do nothing; our name was successfully registered.  We may 
459             // get more call backs in the future.
460             break;
461 
462         case mStatus_NameConflict: 
463             debugf("Callback: %##s Name Conflict",     thisRegistration->RR_SRV.resrec.name->c); 
464 
465             // In the event of a conflict, this sample RegistrationCallback 
466             // just calls mDNS_RenameAndReregisterService to automatically 
467             // pick a new unique name for the service. For a device such as a 
468             // printer, this may be appropriate.  For a device with a user 
469             // interface, and a screen, and a keyboard, the appropriate response 
470             // may be to prompt the user and ask them to choose a new name for 
471             // the service.
472             //
473             // Also, what do we do if mDNS_RenameAndReregisterService returns an 
474             // error.  Right now I have no place to send that error to.
475             
476             status = mDNS_RenameAndReregisterService(m, thisRegistration, mDNSNULL);
477             assert(status == mStatus_NoError);
478             break;
479 
480         case mStatus_MemFree:      
481             debugf("Callback: %##s Memory Free",       thisRegistration->RR_SRV.resrec.name->c); 
482             
483             // When debugging is enabled, make sure that thisRegistration 
484             // is not on our gServiceList.
485             
486             #if !defined(NDEBUG)
487                 {
488                     PosixService *cursor;
489                     
490                     cursor = gServiceList;
491                     while (cursor != NULL) {
492                         assert(&cursor->coreServ != thisRegistration);
493                         cursor = cursor->next;
494                     }
495                 }
496             #endif
497             free(thisRegistration);
498             break;
499 
500         default:                   
501             debugf("Callback: %##s Unknown Status %ld", thisRegistration->RR_SRV.resrec.name->c, status); 
502             break;
503     }
504 }
505 
506 static int gServiceID = 0;
507 
508 static mStatus RegisterOneService(const char *  richTextName, 
509                                   const char *  serviceType, 
510                                   const char *  serviceDomain, 
511                                   const mDNSu8  text[],
512                                   mDNSu16       textLen,
513                                   long          portNumber)
514 {
515     mStatus status;
516      PosixService *       thisServ;
517      domain label name;
518      domain name type;
519      domainname domain;
520      
521      status = mStatus_NoError;
522      thisServ = (PosixService *) malloc ( sizeof (* thisServ));
523      if (thisServ == NULL) {
 524          status = mStatus_NoMemoryErr;
525      }
 526      if (status == mStatus_NoError) {
 527         MakeDomainLabelFromLiteralString(&name,  richTextName);
528         MakeDomainNameFromDNSNameString(&type, serviceType);
529         MakeDomainNameFromDNSNameString(&domain, serviceDomain);
530         status = mDNS_RegisterService(&mDNSStorage, &thisServ->coreServ,
531                 &name, &type, &domain,                // Name, type, domain
532                 NULL, mDNSOpaque16fromIntVal(portNumber),
533                 text, textLen,                        // TXT data, length
534                 NULL, 0,                             // Subtypes 
535                  mDNSInterface_Any,                     // Interface ID 
536                  RegistrationCallback, thisServ);    // Callback and context 
537      }
 538      if (status == mStatus_NoError) {
 539          thisServ->serviceID = gServiceID;
540          gServiceID += 1 ;
541  
542          thisServ->next = gServiceList;
543          gServiceList = thisServ;
544  
545          if(gMDNSPlatformPosixVerboseLevel > 0 ) {
 546              fprintf(stderr, 
 547                      " %s: Registered service %d, name '%s', type '%s', port %ld\n " , 
 548                      gProgramName, 
 549                      thisServ-> serviceID, 
 550                      richTextName,
 551                      serviceType,
 552                      portNumber);
553          }
 554      } else {
 555          if (thisServ != NULL) {
 556              free (thisServ);
557         }
558     }
559     return status;
560 }
561 
562 static mDNSBool ReadALine(char *buf, size_t bufSize, FILE *fp)
563 // Read a line, skipping over any blank lines or lines starting with '#'
564 {
565     mDNSBool good, skip;
566     do {
567         good = (fgets(buf, bufSize, fp) != NULL);
568         skip = (good && (buf[0] == '#'));
569     } while (good && skip);
570     if (good)
571     {
572         int        len = strlen( buf);
573         if ( buf[len - 1] == '\r' || buf[len - 1] == '\n')
574             buf[len - 1] = '\0';
575     }
576     return good;
577 }
578 
579 static mStatus RegisterServicesInFile(const char *filePath)
580 {
581     mStatus     status = mStatus_NoError;
582     FILE *      fp = fopen(filePath, "r");
583     int         junk;
584     
585     if (fp == NULL) {
586         status = mStatus_UnknownErr;
587     }
588     if (status == mStatus_NoError) {
589         mDNSBool good = mDNStrue;
590         do {
591             int         ch;
592             char name[256];
593             char type[256];
594             const char *dom = kDefaultServiceDomain;
595             char rawText[1024];
596             mDNSu8  text[sizeof(RDataBody)];
597             unsigned int textLen = 0;
598             char port[256];
599 
600             // Skip over any blank lines.
601             do ch = fgetc(fp); while ( ch == '\n' || ch == '\r' );
602             if (ch != EOF) good = (ungetc(ch, fp) == ch);
603             
604             // Read three lines, check them for validity, and register the service.
605             good = ReadALine(name, sizeof(name), fp);               
606             if (good) {
607                 good = ReadALine(type, sizeof(type), fp);
608             }
609             if (good) {
610                 char *p = type;
611                 while (*p && *p != ' ') p++;
612                 if (*p) {
613                     *p = 0;
614                     dom = p+1;
615                 }
616             }
617             if (good) {
618                 good = ReadALine(port, sizeof(port), fp);
619             }
620             if (good) {
621                 good =     CheckThatRichTextNameIsUsable(name, mDNSfalse)
622                         && CheckThatServiceTypeIsUsable(type, mDNSfalse)
623                         && CheckThatPortNumberIsUsable(atol(port), mDNSfalse);
624             }
625             if (good) {
626                 while (1) {
627                     int len;
628                     if (!ReadALine(rawText, sizeof(rawText), fp)) break;
629                     len = strlen(rawText);
630                     if (len <= 255)
631                         {
632                         unsigned int newlen = textLen + 1 + len;
633                         if (len == 0 || newlen >= sizeof(text)) break;
634                         text[textLen] = len;
635                         memcpy(text + textLen + 1, rawText, len);
636                         textLen = newlen;
637                         }
638                     else
639                         fprintf(stderr, "%s: TXT attribute too long for name = %s, type = %s, port = %s\n", 
640                             gProgramName, name, type, port);
641                 }
642             }
643             if (good) {
644                 status = RegisterOneService(name, type, dom, text, textLen, atol(port));
645                 if (status != mStatus_NoError) {
646                     fprintf(stderr, "%s: Failed to register service, name = %s, type = %s, port = %s\n", 
647                             gProgramName, name, type, port);
648                     status = mStatus_NoError;       // keep reading
649                 }
650             }
651         } while (good && !feof(fp));
652 
653         if ( ! good ) {
654             fprintf(stderr, "%s: Error reading service file %s\n", gProgramName, filePath);
655         }
656     }
657     
658     if (fp != NULL) {
659         junk = fclose(fp);
660         assert(junk == 0);
661     }
662     
663     return status;
664 }
665 
666 static mStatus RegisterOurServices(void)
667 {
668     mStatus status;
669     
670     status = mStatus_NoError;
671     if (gServiceName[0] != 0) {
672         status = RegisterOneService(gServiceName, 
673                                     gServiceType, 
674                                     gServiceDomain, 
675                                     gServiceText, gServiceTextLen, 
676                                     gPortNumber);
677      }
 678      if (status == mStatus_NoError && gServiceFile[ 0 ] != 0 ) {
 679          status = RegisterServicesInFile(gServiceFile);
680      }
 681      return status;
682  }
 683  
684  static  void DeregisterOurServices( void )
 685  {
 686      PosixService * thisServ;
687      int thisServID;
688      
689      while (gServiceList !=NULL) {
 690          thisServ = gServiceList;
691          gServiceList = thisServ-> next;
692  
693          thisServID = thisServ-> serviceID;
694          
695          mDNS_DeregisterService(&mDNSStorage, &thisServ-> coreServ);
696  
697          if (gMDNSPlatformPosixVerboseLevel > 0 ) {
 698              fprintf(stderr, 
 699                      " %s: Deregistered service %d\n " ,
 700                      gProgramName, 
 701                      thisServ-> serviceID);
702         }
703     }
704 }
705 
706 #if COMPILER_LIKES_PRAGMA_MARK
707 #pragma mark **** Main
708 #endif
709 
710 int main(int argc, char **argv)
711 {
712     mStatus status;
713     int     result;
714 
715     // Parse our command line arguments.  This won't come back if there's an error.
716     
717     ParseArguments(argc, argv);
718 
719     // If we're told to run as a daemon, then do that straight away.
720     // Note that we don't treat the inability to create our PID 
721     // file as an error.  Also note that we assign getpid to a long 
722     // because printf has no format specified for pid_t.
723     
724     if (gDaemon) {
725         if (gMDNSPlatformPosixVerboseLevel > 0) {
726             fprintf(stderr, "%s: Starting in daemon mode\n", gProgramName);
727         }
728         daemon(0,0);
729         {
730             FILE *fp;
731             int  junk;
732             
733             fp = fopen(gPIDFile, "w");
734             if (fp != NULL) {
735                 fprintf(fp, "%ld\n", (long) getpid());
736                 junk = fclose(fp);
737                 assert(junk == 0);
738             }
739         }
740     } else {
741         if (gMDNSPlatformPosixVerboseLevel > 0) {
742             fprintf(stderr, "%s: Starting in foreground mode, PID %ld\n", gProgramName, (long) getpid());
743         }
744     }
745 
746     status = mDNS_Init(&mDNSStorage, &PlatformStorage,
747         mDNS_Init_NoCache, mDNS_Init_ZeroCacheSize,
748         mDNS_Init_AdvertiseLocalAddresses,
749         mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext);
750     if (status != mStatus_NoError) return(2);
751 
752     status = RegisterOurServices();
753     if (status != mStatus_NoError) return(2);
754     
755     signal(SIGHUP,  HandleSigHup);      // SIGHUP has to be sent by kill -HUP <pid>
756     signal(SIGINT,  HandleSigInt);      // SIGINT is what you get for a Ctrl-C
757     signal(SIGQUIT, HandleSigQuit);     // SIGQUIT is what you get for a Ctrl-\ (indeed)
758     signal(SIGUSR1, HandleSigUsr1);     // SIGUSR1 has to be sent by kill -USR1 <pid>
759 
760     while (!gStopNow)
761         {
762         int nfds = 0;
763         fd_set readfds;
764         struct timeval timeout;
765         int result;
766         
767         // 1. Set up the fd_set as usual here.
768         // This example client has no file descriptors of its own,
769         // but a real application would call FD_SET to add them to the set here
770         FD_ZERO(&readfds);
771         
772         // 2. Set up the timeout.
773         // This example client has no other work it needs to be doing,
774         // so we set an effectively infinite timeout
775         timeout.tv_sec = 0x3FFFFFFF;
776         timeout.tv_usec = 0;
777         
778         // 3. Give the mDNSPosix layer a chance to add its information to the fd_set and timeout
779         mDNSPosixGetFDSet(&mDNSStorage, &nfds, &readfds, &timeout);
780         
781         // 4. Call select as normal
782         verbosedebugf("select(%d, %d.%06d)", nfds, timeout.tv_sec, timeout.tv_usec);
783         result = select(nfds, &readfds, NULL, NULL, &timeout);
784         
785         if (result < 0)
786             {
787             verbosedebugf("select() returned %d errno %d", result, errno);
788             if (errno != EINTR) gStopNow = mDNStrue;
789             else
790                 {
791                 if (gReceivedSigUsr1)
792                     {
793                     gReceivedSigUsr1 = mDNSfalse;
794                     gMDNSPlatformPosixVerboseLevel += 1;
795                     if (gMDNSPlatformPosixVerboseLevel > 2)
796                         gMDNSPlatformPosixVerboseLevel = 0;
797                     if ( gMDNSPlatformPosixVerboseLevel > 0 )
798                         fprintf(stderr, "\nVerbose level %d\n", gMDNSPlatformPosixVerboseLevel);
799                     }
800                 if (gReceivedSigHup)
801                     {
802                     if (gMDNSPlatformPosixVerboseLevel > 0)
803                         fprintf(stderr, "\nSIGHUP\n");
804                     gReceivedSigHup = mDNSfalse;
805                     DeregisterOurServices();
806                     status = mDNSPlatformPosixRefreshInterfaceList(&mDNSStorage);
807                     if (status != mStatus_NoError) break;
808                     status = RegisterOurServices();
809                     if (status != mStatus_NoError) break;
810                     }
811                 }
812             }
813         else
814             {
815             // 5. Call mDNSPosixProcessFDSet to let the mDNSPosix layer do its work
816             mDNSPosixProcessFDSet(&mDNSStorage, &readfds);
817             
818             // 6. This example client has no other work it needs to be doing,
819             // but a real client would do its work here
820             // ... (do work) ...
821             }
822         }
823 
824     debugf("Exiting");
825     
826     DeregisterOurServices();
827     mDNS_Close(&mDNSStorage);
828 
829     if (status == mStatus_NoError) {
830         result = 0;
831     } else {
832         result = 2;
833     }
834     if ( (result != 0) || (gMDNSPlatformPosixVerboseLevel > 0) ) {
835         fprintf(stderr, "%s: Finished with status %ld, result %d\n", gProgramName, status, result);
836     }
837     
838     return result;
839 }
Respond.c
ReadMe About mDNSPosix
----------------------

mDNSPosix is a port of Apple's Multicast DNS and DNS Service Discovery
code to Posix platforms.

Multicast DNS and DNS Service Discovery are technologies that allow you
to register IP-based services and browse the network for those services.
For more information about mDNS, see the mDNS web site.

  <http://www.multicastdns.org/>

Multicast DNS is part of a family of technologies resulting from the
efforts of the IETF Zeroconf working group.  For information about
other Zeroconf technologies, see the Zeroconf web site.

  <http://www.zeroconf.org/>

Apple uses the trade mark "Bonjour" to describe our implementation of
Zeroconf technologies.  This sample is designed to show how easy it is
to make a device "Bonjour compatible".

The "Bonjour" trade mark can also be licensed at no charge for
inclusion on your own products, packaging, manuals, promotional
materials, or web site. For details and licensing terms, see

  <http://developer.apple.com/bonjour/>

The code in this sample was compiled and tested on Mac OS X (10.1.x,
10.2, 10.3), Solaris (SunOS 5.8), Linux (Redhat 2.4.9-21, Fedora Core 1), 
and OpenBSD (2.9). YMMV.


Packing List
------------

The sample uses the following directories:

o mDNSCore -- A directory containing the core mDNS code.  This code
  is written in pure ANSI C and has proved to be very portable.
  Every platform needs this core protocol engine code.

o mDNSShared -- A directory containing useful code that's not core to
  the main protocol engine itself, but nonetheless useful, and used by
  more than one (but not necessarily all) platforms.

o mDNSPosix -- The files that are specific to Posix platforms: Linux,
  Solaris, FreeBSD, NetBSD, OpenBSD, etc. This code will also work on
  OS X, though that's not its primary purpose.

o Clients -- Example client code showing how to use the API to the
  services provided by the daemon.


Building the Code
-----------------

The sample does not use autoconf technology, primarily because I didn't
want to delay shipping while I learnt how to use it.  Thus the code
builds using a very simple make file.  To build the sample you should
cd to the mDNSPosix directory and type "make os=myos", e.g.

    make os=panther

For Linux you would change that to:

    make os = linux

There are definitions for each of the platforms I ported to.  If you're
porting to any other platform please add appropriate definitions for it
and send us the diffs so they can be incorporated into the main
distribution.


Using the Sample
----------------
When you compile, you will get:

o Main products for general-purpose use (e.g. on a desktop computer):
  - mdnsd
  - libmdns
  - nss_mdns (See nss_ReadMe.txt for important information about nss_mdns)

o Standalone products for dedicated devices (printer, network camera, etc.)
  - mDNSClientPosix
  - mDNSResponderPosix
  - mDNSProxyResponderPosix

o Testing and Debugging tools
  - dns-sd command-line tool (from the "Clients" folder)
  - mDNSNetMonitor
  - mDNSIdentify

As root type "make install" to install eight things:
o mdnsd                   (usually in /usr/sbin)
o libmdns                 (usually in /usr/lib)
o dns_sd.h                (usually in /usr/include)
o startup scripts         (e.g. in /etc/rc.d)
o manual pages            (usually in /usr/share/man)
o dns-sd tool             (usually in /usr/bin)
o nss_mdns                (usually in /lib)
o nss configuration files (usually in /etc)

The "make install" concludes by executing the startup script
(usually "/etc/init.d/mdns start") to start the daemon running.
You shouldn't need to reboot unless you really want to.

Once the daemon is running, you can use the dns-sd test tool
to exercise all the major functionality of the daemon. Running
"dns-sd" with no arguments gives a summary of the available options.
This test tool is also described in detail, with several examples,
in Chapter 6 of the O'Reilly "Zero Configuration Networking" book.


How It Works
------------
                                                   +--------------------+
                                                   | Client Application |
   +----------------+ +--------------------------------+
   |  uds_daemon.c  | <--- Unix Domain Socket ---> |      libmdns       |
   +----------------+ +--------------------------------+
   |    mDNSCore    |
   +----------------+
   |  mDNSPosix.c   |
   +----------------+

mdnsd is divided into three sections.

o mDNSCore is the main protocol engine
o mDNSPosix.c provides the glue it needs to run on a Posix OS
o uds_daemon.c exports a Unix Domain Socket interface to
  the services provided by mDNSCore

Client applications link with the libmdns, which implements the functions
defined in the dns_sd.h header file, and implements the IPC protocol
used to communicate over the Unix Domain Socket interface to the daemon.

Note that, strictly speaking, nss_mdns could be just another client of
mdnsd, linking with libmdns just like any other client. However, because
of its central role in the normal operation of multicast DNS, it is built
and installed along with the other essential system support components.


Clients for Embedded Systems
----------------------------

For small devices with very constrained resources, with a single address
space and (typically) no virtual memory, the uds_daemon.c/UDS/libmdns
layer may be eliminated, and the Client Application may live directly
on top of mDNSCore:

    +--------------------+
    | Client Application |
    +--------------------+
    |      mDNSCore      |
    +--------------------+
    |    mDNSPosix.c     |
    +--------------------+

Programming to this model is more work, so using the daemon and its
library is recommended if your platform is capable of that.

The runtime behaviour when using the embedded model is as follows:

1. The application calls mDNS_Init, which in turns calls the platform
   (mDNSPlatformInit).

2. mDNSPlatformInit gets a list of interfaces (get_ifi_info) and registers
   each one with the core (mDNS_RegisterInterface).  For each interface
   it also creates a multicast socket (SetupSocket).

3. The application then calls select() repeatedly to handle file descriptor
   events. Before calling select() each time, the application calls
   mDNSPosixGetFDSet() to give mDNSPosix.c a chance to add its own file
   descriptors to the set, and then after select() returns, it calls
   mDNSPosixProcessFDSet() to give mDNSPosix.c a chance to receive and
   process any packets that may have arrived.

4. When the core needs to send a UDP packet it calls
   mDNSPlatformSendUDP.  That routines finds the interface that
   corresponds to the source address requested by the core, and
   sends the datagram using the UDP socket created for the
   interface.  If the socket is flow send-side controlled it just
   drops the packet.

5. When SocketDataReady runs it uses a complex routine,
   "recvfrom_flags", to actually receive the packet.  This is required
   because the core needs information about the packet that is
   only available via the "recvmsg" call, and that call is complex
   to implement in a portable way.  I got my implementation of
   "recvfrom_flags" from Stevens' "UNIX Network Programming", but
   I had to modify it further to work with Linux.

One thing to note is that the Posix platform code is very deliberately
not multi-threaded.  I do everything from a main loop that calls
"select()".  This is good because it avoids all the problems that often
accompany multi-threaded code. If you decide to use threads in your
platform, you will have to implement the mDNSPlatformLock() and
mDNSPlatformUnlock() calls which are currently no-ops in mDNSPosix.c.


Once you've built the embedded samples you can test them by first
running the client, as shown below.

  quinn% build/mDNSClientPosix
  Hit ^C when you're bored waiting for responses.

By default the client starts a search for AppleShare servers and then
sits and waits, printing a message when services appear and disappear.

To continue with the test you should start the responder in another
shell window.

  quinn% build/mDNSResponderPosix -n Foo

This will start the responder and tell it to advertise a AppleShare
service "Foo".  In the client window you will see the client print out
the following as the service shows up on the network.

  quinn% build/mDNSClientPosix
  Hit ^C when you're bored waiting for responses.
  *** Found name = 'Foo', type = '_afpovertcp._tcp.', domain = 'local.'

Back in the responder window you can quit the responder cleanly using
SIGINT (typically ^C).

  quinn% build/mDNSResponderPosix -n Foo
  ^C
  quinn%

As the responder quits it will multicast that the "Foo" service is
disappearing and the client will see that notification and print a
message to that effect (shown below).  Finally, when you're done with
the client you can use SIGINT to quit it.

  quinn% build/mDNSClientPosix
  Hit ^C when you're bored waiting for responses.
  *** Found name = 'Foo', type = '_afpovertcp._tcp.', domain = 'local.'
  *** Lost  name = 'Foo', type = '_afpovertcp._tcp.', domain = 'local.'
  ^C
  quinn%

If things don't work, try starting each program in verbose mode (using
the "-v 1" option, or very verbose mode with "-v 2") to see if there's
an obvious cause.

That's it for the core functionality.  Each program supports a variety
of other options.  For example, you can advertise and browse for a
different service type using the "-t type" option.  Use the "-?" option
on each program for more user-level information.


Caveats
-------
Currently the program uses a simple make file.

The Multicast DNS protocol can also operate locally over the loopback
interface, but this exposed some problems with the underlying network
stack in early versions of Mac OS X and may expose problems with other
network stacks too.

o On Mac OS X 10.1.x the code failed to start on the loopback interface
  because the IP_ADD_MEMBERSHIP option returns ENOBUFS.

o On Mac OS X 10.2 the loopback-only case failed because
  "sendto" calls fails with error EHOSTUNREACH. (3016042)

Consequently, the code will attempt service discovery on the loopback
interface only if no other interfaces are available.

I haven't been able to test the loopback-only case on other platforms
because I don't have access to the physical machine.


Licencing
---------
This code is distributed under the Apple Public Source License.
Information about the licence is included at the top of each source file.


Credits and Version History
---------------------------
If you find any problems with this sample, mail <[email protected]> and I
will try to fix them up.

1.0a1 (Jul 2002) was a prerelease version that was distributed
internally at Apple.

1.0a2 (Jul 2002) was a prerelease version that was distributed
internally at Apple.

1.0a3 (Aug 2002) was the first shipping version.  The core mDNS code is
the code from Mac OS 10.2 (Jaguar) GM.

Share and Enjoy

Apple Developer Technical Support
Networking, Communications, Hardware

6 Aug 2002


To Do List
----------
• port to a System V that's not Solaris
• use sig_atomic_t for signal to main thread flags
ReadMe.txt

 

Related: mDNS20170217 of network protocol