网络协议之mDNS20170217
DNS(Domain Name System,域名系统)因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。DNS协议运行在UDP协议之上,使用端口号53。在RFC文档中RFC 2181对DNS有规范说明,RFC 2136对DNS的动态更新进行说明,RFC 2308对DNS查询的反向缓存进行说明。
一、mDNS 具体协议规范地址如下 : http://www.ietf.org/rfc/rfc6762.txt
mdns 即多播dns(Multicast DNS),mDNS主要实现了在没有传统DNS服务器的情况下使局域网内的主机实现相互发现和通信,使用的端口为5353,遵从dns协议,使用现有的DNS信息结构、名语法和资源记录类型。并且没有指定新的操作代码或响应代码。
在局域网中,设备和设备之前相互通信需要知道对方的ip地址的,大多数情况,设备的ip不是静态ip地址,而是通过dhcp 协议动态分配的ip 地址,如何设备发现呢,就是要mdns大显身手,例如:现在物联网设备和app之间的通信,要么app通过广播,要么通过组播,发一些特定信息,感兴趣设备应答,实现局域网设备的发现,当然mdns 比这强大的多
1.mDNS 基于 UDP 协议。
组播地址: 组播地址使用的是D类地址,地址范围为:224.0.0.0—239.255.255.255
2.mdns 工作原理简单描述:
mdns 使用组播地址为: 224.0.0.251 (ipv6: FF02::FB) 端口为5353,mdns 是用于局域网内部的,并且主机的域名为.local 结尾,每个进入局域网的主机,如果开启了mDNS服务的话,都会向局域网内的所有主机组播一个消息,我是谁(域名),和我的IP地址是多少。然后其他有mdns服务的主机就会响应,也会告诉你,它是谁(域名),它的IP地址是多少。 当然设备需要服务时,就是使用mdns 查询域名对对应的ip地址,对应的设备收到该报文后同样通过组播方式应答,此时其他主机设备也是可以收到该应答报文,其他主机也会记录域名和ip 以及ttl 等,更新缓存
比如,A主机进入局域网,开启了 mDNS 服务,并向 mDNS 服务注册以下信息:我提供 FTP 服务,我的IP是 192.168.1.101,端口是 21。当B主机进入局域网,并向 B 主机的 mDNS 服务请求,我要找局域网内 FTP 服务器,B主机的 mDNS 就会去局域网内向其他的 mDNS 询问,并且最终告诉你,有一个IP地址为 192.168.1.101,端口号是 21 的主机,也就是 A 主机提供 FTP 服务,所以 B 主机就知道了 A 主机的 IP 地址和端口号了。
大概的原理就是这样子,mDNS提供的服务要远远多于这个,当然服务多但并不复杂。
3.mDNSResponder与Bonjour的关系:
The mDNSResponder project is a component of Bonjour,
Apple's ease-of-use IP networking initiative:
<http://developer.apple.com/bonjour/>
Bonjour是法语中的Hello之意。它是Apple公司为基于组播域名服务(multicast DNS)的开放性零配置网络标准所起的名字。使用Bonjour的设备在网络中自动组播它们自己的服务信息并监听其它设备的服务信息。设备之间就像在打招呼,这也是该技术命名为Bonjour的原因。Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。
举一个简单的例子:在局域网中,如果要进行打印服务,必须先知道打印服务器的IP地址。此IP地址一般由IT部门的人负责分配,然后他还得全员发邮件以公示此地址。有了Bonjour以后,打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务,名为“print service”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机。在Bonjour的帮助下,客户端最终能找到这台注册了“print service”名字的打印机,并获得它的IP地址以及端口号。
从Bonjour角度来看,该技术主要解决了三个问题:
- Addressing:即为主机分配IP。Bonjour的Addressing处理比较简单,即每个主机在网络内部的地址可选范围内找一个IP,然后查看网络内部是否有其他主机再用。如果该IP没有被分配的话,它将使用此IP。
- Naming:Naming解决的是host名和IP地址的对应关系。Bonjour采用的是Multiple DNS技术,即DNS查询消息将通过UDP组播方式发送。一旦网络内部某个机器发现查询的机器名和自己设置的一样,就回复这条请求。此外,Bonjour还拓展了MDNS的用途,即除了能查找host外,还支持对service的查找。不过,Bonjour的Naming有一个限制,即网络内部不能有重名的host或service。
- Service Discovery:SD基于上面的Naming工作,它使得应用程序能查找到网络内部的服务,并解析该服务对应的IP地址和端口号。应用程序一旦得到服务的IP地址和端口号,就可以直接和该服务建立交互关系。
Bonjour技术在Mac OS以及Itunes、Iphone上都得到了广泛应用。为了进一步推广,Apple通过开源工程mdnsresponder将其开源出来。在Windows平台上,它将生成一个后台程序mdnsresponder。在Android平台上(或者说支持POSIX的Linux平台)它是一个名为mdnsd的程序。不过,不论是mdnsresponder还是mdnsd,应用开发者要做的仅仅是利用Bonjour的API向它们发起服务注册、服务查询和服务解析等请求并接收来自它们的处理结果。
下面我们将介绍Bonjour API中使用最多的三个函数,它们分别是服务注册、服务查询和服务解析。理解这三个函数的功能也是理解MDnsSdListener的基础。
使用Bonjour API必须包含如下的头文件和动态库,并连接到:
#include <dns_sd.h> //必须包含此头文件
libmdnssd.so //链接到此so
Bonjour中,服务注册的API为DNSServiceRegister,原型如图1所示:
图1 DNSServiceRegister原型
该函数的解释如下:
- sdRef:代表一个未初始化的DNSService实体。其类型DNSServiceRef是指针。该参数最终由DNSServiceRegister函数分配内存并初始化。
- flags:表示当网络内部有重名服务时的冲突处理。默认是按顺序修改服务名。例如要注册的服务名为“printer”,当检测到重名冲突时,就可改名为“printer(1)”。
- interfaceIndex:表示该服务输出到主机的哪些网络接口上。值-1表示仅对本机支持,也就是该服务的用在loop接口上。
- name:表示服务名,为空的话就取机器名。
- regtype:服务类型,用字符串表达。Bonjour要求格式为"_服务名._传输协议",例如"_ftp._tcp"。目前传输协议仅支持TCP和UDP。
- domian和host一般都为空。
- port表示该服务的端口。如果为0的话,Bonjour会自动分配一个。
- txtLen以及txtRecord字符串用来描述该服务。一般都设置为空。
- callBack:设置回调函数。该服注册的请求结果都会通过它回调给客户端。
- context:上下文指针,由应用程序设置。
当客户端需要搜索网络内部特定服务时,需要使用DNSServiceBrowser API,其原型如图2所示:
图2 DNSServiceBrowser原型
其中:
- sdref、interfaceIndex、regtype、domain以及context含义与DNSServiceRegister一样。
- flags:在本函数中没有作用。
- callBack:为DNSServiceBrowser处理结果的回调通知接口。
当客户端想获得指定服务的IP和端口号时,需要使用DNSServiceResolve API,其原型如图3所示:
图3 DNSServiceResolve原型
其中:
- name、regtype和domain都从DNSServiceBrowse函数的处理结果中获得。
- callBack用于通知DNSServiceResolve的处理结果。该回调函数将返回服务的IP地址和端口号。
如果需要了解Bonjour安卓中的使用方法及原理,请阅读该部分的原文: http://blog.csdn.net/innost/article/details/8629139
4.Linux中的使用方法:
附件提供了mDNS的源码,分析源码我们就可以知道如何编译安装以及如何使用:
在mDNSResponder-107.5\mDNSPosix目录中的Responder.c文件中的main我们可以看到
调用了PrintUsage函数中提供了用法说明:
根据上面的描述,使用shell命令调用的例子:
//mDNSResponderPosix来源于bonjour,服务注册
sprintf(buf, "mDNSResponderPosix -n %s -t _ipc_http._tcp. " "-d local. -p 5959 &", gpCC->ccArg.pDevId);//-n 服务名,-t 服务类型,-d域名,-p端口号,
system(buf);//后台运行
再看main函数中执行过程:
初始化后调用了RegisterOurServices函数把要提供的服务注册进去(追踪下去就可以看到最终调用了mdnscore.c中的mDNS_RegisterService函数):
最后如果想详细了解可以阅读如下文件,或者网上搜索下载mDNS的源码阅读
1 /* -*- Mode: C; tab-width: 4 -*- 2 * 3 * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. 4 * 5 * @APPLE_LICENSE_HEADER_START@ 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 * @APPLE_LICENSE_HEADER_END@ 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 domainlabel name; 518 domainname 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 <dts@apple.com> 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