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