mDNS之airplay实现及问题总结
mDNS之airplay实现及问题总结
mDNS实现之mdnsresponder介绍
⼀、名词介绍
mdnsresponder:是Apple实现Benjour的⼀个开源⼯程。
Bonjour:Apple基于组播域名服务(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
(连接apple id服务器时出错
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上的实现
⼀、⼯程源码
本⽂以选⽤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的测试⽅法: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)来分析
通过使⽤抓包⼯具来分析,可以最直接的分析到设备间的⽹络通讯情况。熟练使⽤⼯具能够跟快跟踪到问题所在。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。