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 }

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