mDNS之airplay实现及问题总结

        mDNS实现之mdnsresponder介绍

 

一、名词介绍

mdnsresponder是Apple实现Benjour的一个开源工程。

BonjourApple基于组播域名服务(multicast DNS)的开放性零配置网络标准所起的名字。Bonjour技术在Mac OS以及iTunes、iPhone上广泛应用(airplay)

zeroconf(Zero configuration networking)零配置网络服务规范,是一种用于自动生成可用IP地址的网络技术,不需要额外的手动配置和专属的配置服务器。Zeroconf规范的提出者是Apple公司。

mDNS:即组播域名服务(multicast DNS)。使用5353端口,在内网没有DNS服务器时,就会出现此组播信息。mNDSS是实现跟DNS相似服务,使得在没有NDS服务的情况下使局域网内的主机实现相互发现和通信。(The name "mDNS" was chosen because this protocol is designed to be,as much as possible, similar to conventional DNS)

 

二、实现机制

开源工程mDNSResponder实现了 Bonjour协议的服务名称与地址的转换以及服务的发现等 Bonjour部分协议的支持。Bonjour协议的服务名称与地址的转换以及服务的发现采用的流程和DNS流程近似包括:登记过程、服务发现过程、服务 地址解析过程以及建立连接等过程,服务发现采用的协议也和DNS协议相似,不过与DNS协议采用的单播方式不同的是采用了组播方式,因此被称为mDNS。

mdnsresponder是C代码实现,支持多种平台,在Windows平台上,它将生成一个后台程序mdnsresponder。在Android平台上(或者说支持POSIX的Linux平台)它是一个名为mdnsd的程序。不过,不论是mdnsresponder还是mdnsd,应用开发者要做的仅仅是利用工程中提供的API向它们发起服务注册、服务查询和服务解析等请求并接收来自它们的处理结果。mdnsd或者mdnsresponder作为守护进程,在开机启动时就开启,用户通过调用dns_sd.h里的API接口来实现服务注册、服务查询和服务解析等功能

三、主要的API接口

服务注册的API为DNSServiceRegister,原型如下。
DNSServiceErrorType DNSSD_API DNSServiceRegister
(
    DNSServiceRef                        *sdRef,
    DNSServiceFlags                flags,
    uint32_t                        interfaceIndex,
    const char                        *name,                /* may be NULL */
    const char                        *regtype,
    const char                        *domain,        /* may be NULL */
    const char                        *host,                /* may be NULL */
    uint16_t                        port,                /* In network byte order */
    uint16_t                        txtLen,
    const void                        *txtRecord,        /* may be NULL */
    DNSServiceRegisterReply        callBack,        /* may be NULL */
    void                                *context        /* may be NULL */
);
该函数的解释如下。
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字符串用来描述该服务。
txtRecord格式为键值对(name/value pairs)例如:0x0A | name=value | 0x08 | paper=A4 | 0x12 | Rendezvous Is Cool |
callBack表示设置回调函数。该服务注册的请求结果都会通过它回调给客户端。
context表示上下文指针,由应用程序设置。
当客户端需要搜索网络内部特定服务时,需要使用DNSServiceBrowser API,其原型如下。
DNSServiceErrorType DNSSD_API DNSServiceBrowse
(
    DNSServiceRef                        *sdRef,
    DNSServiceFlags                flags,
    uint32_t                        interfaceIndex,
    const char                        *regtype,
    const char                        *domain,        /* may be NULL */
    DNSServiceBrowseReply                callBack,
    void                                *context        /* may be NULL */
);
其中,sdref、interfaceIndex、regtype、domain以及context含义与DNSServiceRegister一样。flags在本函数中没有作用。callBack为DNSServiceBrowser处理结果的回调通知接口。
当客户端想获得指定服务的IP和端口号时,需要使用DNSServiceResolve API,其原型如下。
DNSServiceErrorType DNSSD_API DNSServiceResolve
(
    DNSServiceRef                        *sdRef,
    DNSServiceFlags                flags,
    uint32_t                        interfaceIndex,
    const char                        *name,
    const char                        *regtype,
    const char                        *domain,
    DNSServiceResolveReply        callBack,
    void                                *context        /* may be NULL */
);
其中,name、regtype和domain都从DNSServiceBrowse函数的处理结果中获得。callBack用于通知DNSServiceResolve的处理结果。该回调函数将返回服务的IP地址和端口号

mdnsresponder在linux上的实现

一、        工程源码

http://www.opensource.apple.com/source/mDNSResponder/只能浏览,没有提供下载。

http://www.opensource.apple.com/tarballs/mDNSResponder/ 各个版本的打包文件,直接下载。

本文以选用mDNSResponder-320.10.80。

二、工程目录介绍

 

mDNSCore:主要核心协议引擎代码,纯C语言编写,各个平台都需要依赖该核心代码。

mDNSShared:多个平台共享的非核心引擎代码。

mDNSPosix:Posix平台相关代码。

Clients:包括如何使用后台服务提供的API的客户端例子代码等四个目录。

在linux下实现只需要以上几个目录代码。

使用mDNSPosix的Makefile编译(make os=linux)生成以下文件(/build/prod),可以修改Makefile的Debug=1项来生成有debug信息的文件(/build/debug下)

 

编译Clients生成一个dns-sd执行文件用于测试,用于跟mndsd服务通讯。

其中mdnsd是一个后台服务,这个服务应该设置随着系统启动时运行,libmdnssd是一个 MDns监视层(dns-sd)使用的库libmdnssd。

 

 

专用设备使用文件 (printer, network camera, etc.)

  - mDNSClientPosix

  - mDNSResponderPosix

  - mDNSProxyResponderPosix

 

要把程序运行在嵌入式系统板上,需要修改Makefile来进行交叉编译

ifeq ($(findstring linux,$(os)),linux)

CFLAGS_OS = -D_GNU_SOURCE -DHAVE_IPV6 -DNOT_HAVE_SA_LEN -DUSES_NETLINK -DHAVE_LINUX -DTARGET_OS_LINUX -fno-strict-aliasing

LD = gcc –shared

改为

CC = /opt/mtk/gnu-toolchain_4.8.2_2.6.35_cortex-a9-neon/bin/armv7a-mediatek482_001_neon-linux-gnueabi-gcc

LD = /opt/mtk/gnu-toolchain_4.8.2_2.6.35_cortex-a9-neon/bin/armv7a-mediatek482_001_neon-linux-gnueabi-gcc –shared

 

/opt/mtk/gnu-toolchain_4.8.2_2.6.35_cortex-a9-neon/bin/armv7a-mediatek482_001_neon-linux-gnueabi-gcc是具体平台的编译工具链

 

生成mdnsd放到板子上运行,将生成的dns-sd运行起来,./dns-sd –h可以看到dns-sd测试程序的测试提示信息。接下来可以修改dns-sd.c里的代码来定制自己的测试项。

下面是一个注册airplay服务和raop服务的demo:

 

  • Demo:  
  •     {  
  •   
  • #define kRaopPort   50001  
  • #define kAirplayPort    50002  
  •   
  •         static DNSServiceRef airplayRef    = NULL;  
  •         static DNSServiceRef raopRef    = NULL;  
  •   
  •         Opaque16 AirplayPort = { { kAirplayPort >> 8, kAirplayPort & 0xFF } };  
  •         Opaque16 RaopPort = { { kRaopPort >> 8, kRaopPort & 0xFF } };  
  •   
  •         static const char AirplayTXT[] =   
  •             "\x1A" "deviceid=0c:54:a5:56:9d:80" \  
  •             "\x0F" "features=0x3FFF"; \  
  •             //"\x10" "model=AppleTV3,1";  
  •             //"\x0E" "srcvers=150.33";  
  •   
  •         static const char RaopTXT[] =   
  •             "\x06" "tp=UDP" \  
  •             "\x08" "sm=false" \  
  •             "\x08" "sv=false" \  
  •             "\x04" "ek=1" \  
  •             "\x06" "et=0,1" \  
  •             "\x06" "cn=0,1" \  
  •             "\x04" "ch=2" \  
  •             "\x05" "ss=16" \  
  •             "\x08" "sr=44100" \  
  •             "\x08" "pw=false" \  
  •             "\x04" "vn=3" \  
  •             "\x09" "txtvers=1";  
  •               
  •   
  •         err = DNSServiceRegister(&airplayRef, 0, opinterface, "JieTools", "_airplay._tcp.", "", NULL, AirplayPort.NotAnInteger, 0, NULL, reg_reply, NULL);  
  •         if (!err) err = DNSServiceUpdateRecord(airplayRef, NULL, 0, sizeof(AirplayTXT)-1, AirplayTXT, 0);  
  •   
  •         err = DNSServiceRegister(&raopRef, 0, opinterface, "0C54A5569D80@JieTools", "_raop._tcp.", "", NULL, RaopPort.NotAnInteger, 0, NULL, reg_reply, NULL);  
  •         if (!err) err = DNSServiceUpdateRecord(raopRef, NULL, 0, sizeof(RaopTXT)-1, RaopTXT, 0);  
  •   
  •         while(1)getchar();  
  •           
  •         return 0;  
  •     }  
  • #endif

 

前两行定义指定服务端口,而后的AirplayTXT与RaopTXT分别两个服务的描述内容,下面对AirplayTXT做简单说明:

"\x1A"这样的写法,是为字符串前添加长度字值,为16进制,deviceid后面的值是本机网卡的物理地址,features这个参数不能少,它是airplay服务所支持的特性或能力描述,其它的参数可以忽略。

RaopTXT描述内容是我通过抓包COPY下来的,没有修改过;再接下来调用了两个mDNS SDK中的两个API,DNSServiceRegister用于注册,DNSServiceUpdateRecord用来更新服务的TXTRecord信息。

这里有两组调用服务注册,这里需要注意的是,如果你想实现_airplay服务,那么就必须将这两个服务一起注册,并且服务名称必须一致,如第四个参数是服务名称“JieTools”及“0C54A5569D80@JieTools”,注意命名规则。

OK,不出意外的话,运行它,打开你的手机,就能在airplay中发现自己注册的这个服务了

                                                      问题总结

1、  选择合适测试平台

测试应该选择合适平台,由于我的目的是要移植到linux arm平台,所以我选择linux环境来编译测试,本机在虚拟机上安装ubuntu,在ubuntu上进行编译测试,编译运行都没有问题,但是手机端怎么都发现不了设备,使用工程里ReadMe的测试方法:mDNSResponderPosix mDNSClientPosix测试也没起作用,最后是重新编译放到板子上运行,手机端能够发现到所注册的服务了。测试平台不能选择虚拟机
2、在测试过程中怎么看打印信息
默认情况下,打印信息都保存到/var/log/system.log里面,在运行mdnsd是带上debug参数(mdnsd -debug),或者把Makefile的编译项改成DEBUG=1就能在窗口上实视看到打印输出。调试过程中建议把debug信息打印出来,方便跟踪问题。

3、  调用函数DNSServiceRegister会返回-65549

返回-65549是错误代码,这种情况一版是参数不对造成,一般情况下,txtRecord参数容易出错,debug信息提示

Sep 15 16:06:13 localhost mDNSResponder[192]: Attempt to register record with invalid rdata: 17 Ice Cube._http._tcp.local. TXT ath=/index.html

TXT record的格式是:长度键值对长度键值对(length byte, data, length byte, data)

长度是16进制表示,键值对是=左边是字符串,右边是值,各个键值对之间没有间隔如:\011txtvers=1\020path=/index.html\025note=Bonjour Is Cool!

4、  要在ios端的airplay上发现服务,需要一些专有参数

对DNSServiceRegister函数的参数,regtype参数必须是_服务名._传输协议,并且只支持tcp和udp,其他的参数都没有做特殊要求。但是如果要让ios能够发现服务,_airplay._tcp 和_raop._tcp 两种服务都要注册,并且两个服务名name要一样,如:airplay的服务名称是hzzTools ,则raop的服务名称是0C54A5569D80@JieTools,其中0C54A5569D80是MAC地址。参数txtRecord里两个参数是必须的,deviceid本机网卡的物理地址和features

5、  注册服务后还需要更新服务信息

调用DNSServiceRegister注册服务后,还需要调用DNSServiceUpdateRecord来更新服务的TXTRecord信息。这样ios端才能发现到服务。

6、  在测试过程中除了打开debug信号来跟踪,还可以用抓包工具(如:Wireshark)来分析

通过使用抓包工具来分析,可以最直接的分析到设备间的网络通讯情况。熟练使用工具能够跟快跟踪到问题所在。

原文地址:https://www.cnblogs.com/seven-sky/p/4729962.html