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)來分析

通過使用抓包工具來分析,可以最直接的分析到設備間的網絡通訊情況。熟練使用工具能夠跟快跟蹤到問題所在。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM