跨子网、不依赖多播的 AirPlay 镜像

AirPlay 是苹果的一个私有标准,可以用来将 iDevice(iPhone、iPad、iPod) 上的音视频流或者镜像投射到 Apple TV 上。尽管 AirPlay 协议是私有的,但国内主流的机顶盒,如天猫魔盒、小米盒子等都对其提供了支持。

AirPlay 有一个很大的局限性:只能在 Apple TV(或者支持 AirPlay 的机顶盒)与 iDevice 处在同一子网内才能工作。之所以有这个限制,是因为 AirPlay 的服务发现部分基于 Bonjour

Bonjour 简介

Bonjour 是苹果开发的一种「零配置网络架构」,使得同一局域网内的主机能够相互发现彼此提供的服务,而不需要用户配置 IP 等信息。想象一下,将一台打印机接到局域网内,然后在电脑上就可以直接选择这台打印机。

Bonjour 为了实现「零配置」,做了三件微小的工作:

  • 分配地址

虽然是零配置网络,但实际主机之间的通信还是基于 TCP/IP,于是我们需要分配 IP 地址。传统的 IP 分配方式有两种:静态分配和 DHCP。

苹果增加了另外一种方式:在没有 DHCP 服务器时(如 Ad Hoc 网络),Bonjour 会为主机自动指定随机的一个 IP 地址,然后检测是否有冲突,如果有冲突就再随机指定一个。这样的好处是不依赖路由器,在去中心化的 Ad-hoc 网络中也能正常工作。

  • 命名

Bonjour 为服务(DNS service)指定一个唯一的类似 foo._airplay._tcp.local. 的名字,名字中间的字段表示服务的类型(_airplay)和传输协议(_tcp),后面的服务发现都基于这个名字。类似的,主机(也叫 host,跟西部世界没关系 :])也有唯一的名字,如 magicbox.local.,与服务不同的是,主机的名字没有类型和传输协议。具体命名规则见这里

当一台主机(host)接入局域网时,Bonjour 会自动生成一个局域网内唯一的名字。虽然 host 有了唯一的名字,但实际通信的时候还是需要 IP 地址,名字与 IP 地址的映射依靠 mDNS 来完成。

mDNS(multicast DNS) 不同于常见的 DNS,它是局域网内依赖多播(multicast)工作的 DNS 协议。他不需要独立的 DNS 服务器,当需要解析一个名字时,会向多播地址(224.0.0.251)的 5353 端口来发送查询请求,收到该请求的主机如果发现自己是要找的对象,就会发送回复。macOS、iOS 以及安装了 Bonjour 服务的 Windows 都会有一个 mDNSResponder 进程专门处理这些请求。开发者只需要将自己的服务注册到系统中,mDNSResponder 会自动完成服务发现工作,不需要处理具体的协议细节。

  • 自动发现服务

当用户需要某项服务时,Bonjour 会根据所需类型(如 AirPlay 的类型是 _airplay._tcp)来查找局域网内所有该类型的服务。每个服务对应一个 host,再通过 host 解析为实际的 IP 地址。

通过 DNS 发现服务的过程称为 DNS-SD(DNS Service Discovery)。

AirPlay 的服务发现过程

现在有一台 Mac 和一台天猫魔盒(支持 AirPlay)处在同一子网内,在 Mac 上点击右上角的 AirPlay 按钮,就会自动发现魔盒。

在 Mac 上可以用

dns-sd -Z _airplay._tcp
dns-sd -Z _raop._tcp

来查看两种类型的服务。

_airplay._tcp                                   PTR     zzzzzzz._airplay._tcp
zzzzzzz._airplay._tcp                           SRV     0 0 7200 MagicBox_M16S-8f144d951aed0c2f.local. ; Replace with unicast FQDN of target host
zzzzzzz._airplay._tcp                           TXT     "deviceid=b5:b7:1b:56:da:b9" "features=0x4A7FFFF7,0xE" "srcvers=220.68" "flags=0x4" "vv=2" "model=HappyCast3,1" "pw=0" "rhd=3.0.0.0" "pk=eb41e959a9ceea6a5d942c032492fdd60b14f6da148bcab48277fdbbfff18816" "pi=2e388006-13ba-4041-9a67-25dd4a43d536"

_raop._tcp                                      PTR     b5b71b56dab9@zzzzzzz._raop._tcp
b5b71b56dab9@zzzzzzz._raop._tcp                 SRV     0 0 6200 MagicBox_M16S-8f144d951aed0c2f.local. ; Replace with unicast FQDN of target host
b5b71b56dab9@zzzzzzz._raop._tcp                 TXT     "ch=2" "cn=0,1,2,3" "da=true" "et=0,3,5" "vv=2" "ft=0x4A7FFFF7,0xE" "am=HappyCast2,1" "md=0,1,2" "rhd=3.0.0.0" "pw=false" "sr=44100" "ss=16" "sv=false" "tp=UDP" "txtvers=1" "sf=0x4" "vs=220.68" "vn=65537" "pk=eb41e959a9ceea6a5d942c032492fdd60b14f6da148bcab48277fdbbfff18816"

可以看到,两种服务分别有三条 DNS 记录:第一条是 PTR 类型的,由服务类型指向服务名;第二条是 SRV 类型的,由服务名指向主机名;第三条是 TXT 类型的,是一些服务参数。

TXT 记录中具体的参数含义可以参考这个非官方的 AirPlay 文档

使用 Wireshark 抓包可以看到:

当点击右上角 AirPlay Icon 时,Mac 首先发出 query,查找 _airplay._tcp. 和 _raop._tcp. 类型的服务。

mDNS query

应该是由于缓存,query 中包含了两个 Answer:分别指向两个服务:b5b71b56dab9@zzzzzzz._raop._tcp.local 和 zzzzzzz._airplay._tcp.local。

其中,zzzzzzz 和 b5b71b56dab9@zzzzzzz 分别是是魔盒注册的两个服务名(默认使用天猫魔盒的名字),b5b71b56dab9 是魔盒的 mac 地址。

接下来,Mac 开始解析 b5b71b56dab9@zzzzzzz._raop._tcp.local 服务

SRV query

最后,魔盒发出响应

mDNS response

可以看到 answer 中的记录指明了 b5b71b56dab9@zzzzzzz._raop._tcp.local 服务指向的 host 是 MagicBox_M16S-8f144d951aed0c2f.local。

在 Additional records 里面,包含了一条 A 记录,其中 MagicBox_M16S-8f144d951aed0c2f.local 指向的 IP 地址是 192.168.43.1。

因此整个过程是:

  1. 通过服务类型搜索服务,得到服务名。
  2. 通过服务名找到 host 名称。
  3. 通过 host 名称得到 IP 地址。

Bonjour 依赖局域网上的多播,因此有两个缺点:Bonjour 无法跨越子网发现服务;企业或者学校等大型网络中,多播通常是被禁用的。

让 Bonjour 跨子网工作

让 Bonjour 跨子网工作有下面几种方法:

这里只介绍最后一种方法。

假设有一台 iPhone 和一台天猫魔盒处于不同的子网,或者在同一个子网但多播被禁用了,现在要将 iPhone 的屏幕投射到魔盒上。

AirPlay 的整个过程可以看做两个部分,服务发现(依赖 Bonjour)和实际连接、传输音视频流。因为实际连接的过程并不要求 iPhone 和魔盒在同一子网内,只要 IP 可达即可,所以现在的问题是服务无法被发现。

DNS-SD Proxy 主要的思路是:在 iPhone 上注册一个与魔盒提供的同样的服务,但服务的 Host 指向魔盒的 IP,达到欺骗 iPhone 的目的。iPhone 在搜索服务时会自问自答,因此不依赖局域网的多播,这样就解决了服务发现的问题。

苹果提供了 NSNetServiceDNSService 来注册服务,但是这两个库有一个缺点:无法指定 host(认为本机是提供服务的 host)。这里就需要使用更底层的 DNSServiceRegister 来注册服务,苹果提供了示例代码。这个方法可以传入 host 名称,这里我们填入 MagicBox_M16S-8f144d951aed0c2f.local.,端口跟前面魔盒的数据一致,名字分别叫 AirProxy 和 6c5ab5637001@AirProxy。

调用 DNSServiceRegister 时,第三个参数应该传入 kDNSServiceInterfaceIndexLocalOnly,否则无法正常发现和连接;此外还需要传入 TXT 记录。videoTXTDict 是一个包含 TXT 记录内容的字典,内容跟前面抓取的魔盒的 TXT 记录相同。

    NSDictionary *videoTXTDict = @{
                                   @"deviceid": deviceID,
                                   @"features": @"0x5A7FFFF7,0x1E",
                                   @"flags": @"0x4",
                                   @"model": @"AppleTV2,1",
                                   @"srcvers": @"220.68",
                                   @"vv": @"2",
                                   @"pk": @"8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c",
                                   @"pi": @"2e388006-13ba-4041-9a67-25dd4a43d536",
                                   @"pw": @"0",
                                   @"rhd": @"5.0.0.5",
                                   };

    TXTRecordRef videoTXTRecord;
    TXTRecordCreate(& videoTXTRecord, 0, NULL);
    for (id key in videoTXTDict.allKeys) {
        TXTRecordSetValue(& videoTXTRecord, [key UTF8String], strlen([videoTXTDict[key] UTF8String]), [videoTXTDict[key] UTF8String]);
    }
    
    DNSServiceRegister(&_sdRef, kNilOptions, kDNSServiceInterfaceIndexLocalOnly, [name UTF8String], "_airplay._tcp.", NULL, [host UTF8String], htons(self.port), TXTRecordGetLength(&videoTXTRecord), TXTRecordGetBytesPtr(& videoTXTRecord), RegisterReplyCallback, (__bridge void *)self);

然后用同样的办法注册 _raop._tcp. 类型的服务,这个时候选择 AirProxy 服务就会发现系统提示「无法连接到 AirProxy」。

把手机连上 Mac,输入

rvictl -s <iPhone 的 UUID>

再用 Wireshark 抓 rvi0 接口上的包,就会发现所有的 MDNS 包中都没有 A 记录,也就是说 Bonjour 搜索到服务,找到 host 之后,无法解析 host 的 IP。

这个时候再回到 dns_sd.h 中,就可以发现有一个用来在服务中增加记录的方法 DNSServiceAddRecord,调用方法如下:

NSArray *IPComponents = [[obj host] componentsSeparatedByString:@"."];
char rawData[5];
sprintf(rawData, "%c%c%c%c", (char)[IPComponents[0] integerValue], (char)[IPComponents[1] integerValue], (char)[IPComponents[2] integerValue], (char)[IPComponents[3] integerValue]);
        
DNSRecordRef recordRef = NULL;
DNSServiceErrorType errorCode = DNSServiceAddRecord(sdRef, &recordRef, flags, kDNSServiceType_A, strlen(rawData), rawData, 0);

这个时候再抓包,可以看到 MDNS 包中多了两条 A 记录:

A 记录

这个时候,可以看到 AirProxy._airplay._tcp.local 服务指向的 host 是 MagicBox_M16S-8f144d951aed0c2f.local.,但我们添加的 A 记录名称是 AirProxy._airplay._tcp.local 和 6c5ab5637001@AirProxy._raop._tcp.local。

这样在注册的时候把 host 名称改成 AirProxy._airplay._tcp.local 或者 6c5ab5637001@AirProxy._raop._tcp.local Bonjour 就可以正确解析了。

至此,就达到了欺骗 Bonjour 连接 AirPlay 的目的。

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容