Usbmux - The iPhone Wiki
Usbmux
During normal operations, iTunes communicates with the iPhone using something called “usbmux” – this is a system for multiplexing several “connections” over one USB pipe. Conceptually, it provides a TCP-like system – processes on the host machine open up connections to specific, numbered ports on the mobile device. (This resemblance is more than superficial – on the mobile device, usbmuxd actually makes TCP connections to localhost using the port number you give it.)
On the Mac, this is handled by /System/Library/PrivateFrameworks/MobileDevice.framework/Resources/usbmuxd, a daemon that is started by launchd (see /System/Library/LaunchDaemons/ com.apple.usbmuxd.plist). It creates a listening UNIX Domain Socket at /var/run/usbmuxd. usbmuxd then watches for iPhone connections via USB; when it detects an iPhone running in normal mode (as opposed to recovery mode), it will connect to it and then start relaying requests that it receives via /var/run/usbmuxd – this is to say, usbmuxd is the only thing that actually speaks USB to the iPhone. This means that third-party applications which wish to talk to the iPhone must either do so through usbmuxd, or usbmuxd must be replaced
对 usbmuxd 的一点研究 · Farlanki
USBMuxd
当 iTunes 和 iPhone 连接时,它们之间会通过 USBMux 进行连接。USBMux 用于在 USB 协议上实现多路 TCP 连接。USBMuxd 能够实现 USB-TCP 协议的转换,将 USB 的端口映射到本机的 TCP 端(基于 Unix Domain Socket),将 USB 通信抽象为 TCP 通信。苹果的 iTunes , XCode 都使用了这个服务。
根据 iphonewiki.com 所说的,USBMuxd 程序存放在 Mac 系统下的 /System/Library/PrivateFrameworks/MobileDevice.framework/Resources
路径中。并且由 plist 文件 /System/Library/LaunchDaemons/com.apple.usbmuxd.plist
可知,USBMuxd 是一个开机启动的服务。
由于 iOS 系统也具有 USBMuxd 服务,所以 iPhone 和 Mac 就能通过 USB 进行 TCP 通信。更进一步的是,只要实现了 USBMuxd,非 OSX 系统也能与iPhone 系统进行通信,例如 Windows 和 Linux。
github 上也存在一个名为 USBMuxd 的项目,这是一个跨平台的开源项目,实现了 usbmux 的功能,支持 MAC/Linux/Windows 平台。
usbmuxd连接流程
以下是Mac应用程序、App与 USBMuxd 的连接流程和数据收发流程。
有几个地方是需要留意的:
- 应用程序需要创建两个 Socket , 一个用于监听 USBMuxd 广播包,以便应用程序得知 iOS 设备的连接与断连;另一个 Socket 用于在得知 iOS 设备连接后请求连接,如果 iOS 设备同意连接,后续的数据传输将通过这个 Socket 进行。
- App 也需要创建两个 Socket,一个为欢迎 Socket,另一个为连接 Socket,这和普通的服务器端套接字流程一样。对于App,USBMuxd 服务是透明的。
- USBMuxd 的广播包使用自己的一套协议,需要按照该协议生成数据包来和 USBMuxd 进行数据传输。
- 当应用程序与 iOS 设备连接后,应用程序和 iOS 进行数据传输的协议由我们自己决定。
GitHub - libimobiledevice/libusbmuxd: A client library to multiplex connections from and to iOS devicesA client library to multiplex connections from and to iOS devices - GitHub - libimobiledevice/libusbmuxd: A client library to multiplex connections from and to iOS deviceshttps://github.com/libimobiledevice/libusbmuxd
。。。
GitHub - libimobiledevice-win32/libimobiledevice-vs: Visual Studio solution to build all of libimobiledevice at onceVisual Studio solution to build all of libimobiledevice at once - GitHub - libimobiledevice-win32/libimobiledevice-vs: Visual Studio solution to build all of libimobiledevice at oncehttps://github.com/libimobiledevice-win32/libimobiledevice-vs
。。。
使用usbmuxd服务,通过USB连接与PC端、Mac端实现通信,Peertalk的使用 - 简书一、usbmuxd 介绍 usbmuxd 是苹果的一个服务,这个服务主要用于在USB协议上实现多路TCP连接,将USB通信抽象为TCP通信。苹果的iTunes、Xcode,都...https://www.jianshu.com/p/eba133891ec6
usbmuxd 是苹果的一个服务,这个服务主要用于在USB协议上实现多路TCP连接,将USB通信抽象为TCP通信。苹果的iTunes、Xcode,都直接或间接地用到了这个服务。
iTunes使用 usbmux 与 iphone 通信, 它提供了一个USB - TCP的转换服务, 这个服务在Mac端是由/System/Library/PrivateFrameworks/MobileDevice.framework/Resources/usbmuxd提供的, 当然, 开机自动启动。
它创建了一个Unix Domain Socket 在 /var/run/usbmuxd. usbmuxd服务程序监控iPhone在USB口上的连接, 当它监控到iPhone以用户模式连接到USB, (相对的是recovery模式), usbmuxd服务程序就会连接到这个/var/run/usbmuxd的TCP端口, 并开始成为一个USB - TCP 请求转发器
那么,如果想编写个第三方程序与iphone进行通信,实现类似iTunes的功能, 你的程序可以通过usbmuxd! 建立一个TCP连接到/var/run/usbmuxd端口, 根据协议发送对应的请求包, usbmuxd服务会将请求转发到USB的iPhone上。
peertalk,一个基于usbmuxd服务的开源代码,可以实现 iPhone 与 Mac 通信。
libimobiledevice,在可以PC端提供usbmuxd服务,实现 iPhone 与 windows 通信。
二、Peertalk 的使用:iPhone 与 Mac 通信
iOS 端
1、创建 channel,监听指定端口
// 创建 channel
PTChannel *channel = [PTChannel channelWithDelegate:self];
// 监听指定端口,PTExampleProtocolIPv4PortNumber自定义端口号
[channel listenOnPort:PTExampleProtocolIPv4PortNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) {
if (error) { // 创建监听失败
} else { // 创建监听成功
}
}];
2、实现 Channel 的代理方法
@protocol PTChannelDelegate <NSObject>
@required
// 收到信息
- (void)ioFrameChannel:(PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData*)payload;
@optional
// 收到消息调用,如回复NO,则忽略这条消息
- (BOOL)ioFrameChannel:(PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize;
// 出错回调
- (void)ioFrameChannel:(PTChannel*)channel didEndWithError:(NSError*)error;
// 连接成功回调
- (void)ioFrameChannel:(PTChannel*)channel didAcceptConnection:(PTChannel*)otherChannel fromAddress:(PTAddress*)address;
@end
3、连接成功后,会发送设备信息
Mac 端
1、监听USB设备的连接/断开
// 开始监听设备的连接与断开
- (void)startListeningForDevices {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
// 监听设备连接
[nc addObserverForName:PTUSBDeviceDidAttachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) {
NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"];
dispatch_async(notConnectedQueue_, ^{
if (!connectingToDeviceID_ || ![deviceID isEqualToNumber:connectingToDeviceID_]) {
// 断开现有连接
[self disconnectFromCurrentChannel];
connectingToDeviceID_ = deviceID;
connectedDeviceProperties_ = [note.userInfo objectForKey:@"Properties"];
// 重新连接
[self enqueueConnectToUSBDevice];
}
});
}];
// 监听设备断开
[nc addObserverForName:PTUSBDeviceDidDetachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) {
NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"];
if ([connectingToDeviceID_ isEqualToNumber:deviceID]) {
connectedDeviceProperties_ = nil;
connectingToDeviceID_ = nil;
if (connectedChannel_) { // 关闭连接通道
[connectedChannel_ close];
}
}
}];
}
2、连接设备
// 连接设备
- (void)connectToLocalIPv4Port {
// 创建通道,设置代理
PTChannel *channel = [PTChannel channelWithDelegate:self];
channel.userInfo = [NSString stringWithFormat:@"127.0.0.1:%d", PTExampleProtocolIPv4PortNumber];
// 连接指定端口地址,与iOS端设置保持一致
[channel connectToPort:PTExampleProtocolIPv4PortNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error, PTAddress *address) {
if (error) { // 连接失败
} else { // 连接成功
}
}];
}
3、连接成功会,会收发送 ping、pong 心跳数据
三、libimobiledevice、Peertalk 的使用:iPhone 与 windows 通信
1、实现原理
windows端 通过 libimobiledevice 运行 usbmuxd 的多路复用守护进程,该进程的作用是建立本地端口和远程端口的转发,实现usb到tcp的转换服务
2、安装服务
windows端首先要安装苹果公司提供的相关服务,才能实现通信功能。服务名称为:AppleApplicationSupport和AppleMobileDeviceSupport
3、规定协议
首先指定 ip地址和端口,端口号建议大些,以免与苹果系统应用端口重复。如:127.0.0.1:62345。PC端可以通过端口转发实现。
定义相同结构体数据,以便数据的加密、解析。PC端可和Peertalk定义的协议保持一致。如:
// 数据头结构体
typedef struct _PTFrame {
uint32_t version; // 对应版本
uint32_t type; // 数据类型
uint32_t tag; // tag标记
uint32_t payloadSize; // 数据大小
} PTFrame;
// 数据类型
enum {
PTExampleFrameTypeDeviceInfo = 100, // 设备信息
PTExampleFrameTypeTextMessage = 101, // 文本数据
PTExampleFrameTypePing = 102, // Ping
PTExampleFrameTypePong = 103, // Pong
};
4、联调测试
定好协议后,指定相同ip地址和端口,进行测试。如连接失败,可尝试更换端口号重试。先调试连接,然后再收发数据,最后进行数据处理。
peertalk
目录
Usbmux
USBMuxd
usbmuxd连接流程
二、Peertalk 的使用:iPhone 与 Mac 通信
iOS 端
Mac 端
三、libimobiledevice、Peertalk 的使用:iPhone 与 windows 通信
1、实现原理
2、安装服务
3、规定协议
4、联调测试
https://blog.csdn.net/yxys01/article/details/77718506
https://www.zhaoxiaodan.com/ios/ituns%E4%B8%8Eiphone%E7%9A%84%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AEusbmuxd%E8%A7%A3%E6%9E%90.html如何获取iphone已安装的app?如何获取iphone文件列表?iTunes使用一种叫https://www.zhaoxiaodan.com/ios/ituns%E4%B8%8Eiphone%E7%9A%84%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AEusbmuxd%E8%A7%A3%E6%9E%90.html
ituns与iphone的通信协议usbmuxd解析
最开始研究与iphone通信, 都会想当然的google下usb协议, 必经iphone是通过usb线连接到电脑. 其实不然, iTunes是通过TCP协议与iPhone通信的
##usbmuxd
iTunes使用一种叫”usbmux”的东西与iphone通信, 这个东西提供了一个USB - TCP的转换服务.
这个服务在Mac端是由/System/Library/PrivateFrameworks/MobileDevice.framework/Resources/usbmuxd
提供的, 当然, 开机自动启动.
它创建了一个Unix Domain Socket 在 /var/run/usbmuxd
. usbmuxd服务程序监控iPhone在USB口上的连接, 当它监控到iPhone以用户模式
连接到USB, (相对的是recovery
模式), usbmuxd服务程序就会连接到这个/var/run/usbmuxd
的TCP端口, 并开始成为一个USB - TCP 请求
转发器
那么,如果想编写个第三方程序与iphone进行通信,实现类似iTunes的功能, 你的程序可以通过usbmuxd! 建立一个TCP连接到/var/run/usbmuxd
端口, 根据协议发送对应的请求包, usbmuxd服务会将请求转发到USB的iPhone上
##lockdownd协议
//协议头
struct usbmux_header {
u32 length; // 消息长度,包括头部
u32 version; // 协议版本号
u32 type; // 消息类型,请求,响应,握手,等
u32 tag; // 消息编号, 用来对应响应
char payload; //请求体
};
//头部中的type类型
enum {
usbmux_result = 1,
usbmux_connect = 2,
usbmux_hello = 3,
usbmux_payload = 8,
};
##监听
知道了iTunes使用的协议, 那么有没有办法看看iTunes都发了些什么包? 有个简单的办法就是使用socat
, 类似:
sudo mv /var/run/usbmuxd /var/run/usbmuxx
sudo socat -t100 -x -v UNIX-LISTEN:/var/run/usbmuxd,mode=777,reuseaddr,fork UNIX-CONNECT:/var/run/usbmuxx
##包示例:
。。。
第一个iTunes向/var/run/usbmuxd
发的请求包, 第一行 e3 01 00 00 01 00 00 00 08 00 00 00 02 00 00 00
是头部 e3 01 00 00
即0x01e3 = 483, 包长度
头部后面紧跟的是payload, 也就是xml格式的请求内容
阿里最新、最强开源iOS自动化测试神器 - 知乎
前言
一直以来,iOS自动化的实现&执行都依赖 Mac 系统,其主要原因是因为需要通过 xcodebuild 编译&安装 WDA (WebDriverAgent) 到 iOS 设备中,通过WDA实现对被测应用进行操作。
导致想要做iOS自动化 就必须拥有 Mac 设备的现象
- 常用电脑非 Mac 的同学 想要做 iOS 自动化的时候, 就需要再申请一台Mac设备 ,可能会出现资源利用不充分的情况
- 云测试平台要搭建 IOS 自动化 服务环境时,也只能批量申请 Mac 设备,经费在燃烧
开源神器: tidevice
一个月前,阿里团队开源了一个内部使用的 iOS自动化工具 :tidevice (
https://github.com/alibaba/taobao-iphone-device
) ,让我们可以更方便、简单的脱离Mac的限制。
tidevice 能做什么
- 设备信息获取
- 应用安装、卸载、启动、停止、查看应用信息、已安装应用列表
- 启动 WebDriverAgent (不依赖 xcodebuild , 跨平台)
- 运行 XcTest UITest
- 性能数据采集
- 设备截图、设备日志 ...
tidevice 核心原理
usbmux通信协议:
实现 Mac/Windows/Linux 与 iOS设备服务间的通信
- Mac
- usbmuxd 是苹果的一个服务,这个服务主要用于在USB协议上实现多路TCP连接,将USB通信抽象为TCP通信。苹果的iTunes、Xcode,都直接或间接地用到了这个服务。
- Linux / Windows
- 本身是没有 usbmux的,不过都有开源项目的实现,可以直接使用/参考
- Windows 另外依赖 AppleApplicationSupport和AppleMobileDeviceSupport 两个服务,安装Itunes 环境即可安装对应服务
usbmux 本身是socket 套接字,通过截获、破解等手段,结合开源界的成果,用python 进行模拟,从而实现了当前工具已有的所有功能
usbmux协议分析 - Lazy Eval
usbmux协议分析
- usbmuxd
- Aug 20, 2019
usbmux协议介绍
初闻usbmux协议时,可能会让人感觉比较陌生,但其实如果你是MAC用户的话,你可能每天都在和它打交道,只是不知道而已。当通过USB将iPhone连接到MAC时,usbmux协议已经在背后为你默默工作了。
usbmux是苹果的私有协议,苹果设计该协议的原因是为了自家的macOS APP能够和iDevice进行通信,从而实现诸如iTunes备份iPhone、Xcode真机调试等功能。只是后来该协议被开发者破解了,于是为众人所知。
usbmuxd是对usbmux协议在macOS平台的上实现,也是macOS系统上的一个守护进程(Daemon),它随着系统的启动而启动。在macOS上,与usbmuxd相关的文件主要有3个:
- /System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/Resources/usbmuxd
- /System/Library/LaunchDaemons/com.apple.usbmuxd.plist
- /var/run/usbmuxd
其中/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/Resources/usbmuxd为可执行文件。
/System/Library/LaunchDaemons/com.apple.usbmuxd.plist为usbmuxd服务的开机启动配置文件,如图所示。launchd在系统启动时会根据该文件的配置信息,启动usbmuxd服务,创建对应的socket(也即/var/run/usbmuxd)。
/var/run/usbmuxd在开机启动时创建,该文件其实是一个Unix Domain Socket,用于usbmuxd进程和其他进程——比如Xcode、iTunes等——之间的进程间通信(IPC)。
➜ zyqhi.github.io git:(master) ✗ file /var/run/usbmuxd
/var/run/usbmuxd: socket
基于usbmux协议的设备间通信
前文也提到,苹果设计usbmux协议是为了解决macOS APP和iOS设备之间的通信问题。如果只是普通的协议的话,倒也没什么可说的,只是该协议呢,提供了一种类似TCP socket的API,使得macOS和iOS设备之间的通信,如同是网络上的两个主机之间的通信。
During normal operations, iTunes communicates with the iPhone using something called “usbmux” – this is a system for multiplexing several “connections” over one USB pipe. Conceptually, it provides a TCP-like system – processes on the host machine open up connections to specific, numbered ports on the mobile device. (This resemblance is more than superficial – on the mobile device, usbmuxd actually makes TCP connections to localhost using the port number you give it.)
FROM: https://www.theiphonewiki.com/wiki/Usbmux
说完usbmux协议的用途,我们再来看看usbmux协议是怎样工作的。这里以iTunes和iOS通信为例,来看下整个通信架构,如图所示:
从图中可以看出,整个通信架构和经典的C/S架构非常类似。其中iOS端的服务(lockdown)的角色是服务端,macOS端的程序(iTunes)则为客户端。
因为没有源码,所以此处的通信架构图只是根据网上前人的工作,结合个人的测试,推测得出的。如有错误,还请指正。
lockdown服务
先来看下iOS端:
图中的lockdown是iOS端的系统服务,它在iOS系统启动时,由launchd进程启动。lockdown服务对应的启动配置文件在iOS系统上的完整路径为:/System/Library/LaunchDaemons/com.apple.mobile.lockdown.plist。
lockdown作为一个网关,用于协调macOS进程和iOS其他服务之间的通信。像我们平时用到的iTunes备份iPhone,Xcode真机调试等功能,在iOS侧,都是需要lockdown服务进行中转处理的。举个例子,Xcode若要执行真机调试,首先需要和lockdown服务通信,发出启动调试请求,lockdown收到请求以后,启动iOS端对应的调试服务(debugserver),然后Xcode便与debugserver之间建立了通信连接。
lockdown服务启动以后,会创建一个TCP listen socket,端口号为62078,地址为本机地址:127.0.0.1。这一点我们可以在越狱机器上得到验证:
如果从TCP/IP网络编程的角度来看lockdown的话,会发现它和普通的TCP Server没有差别。
关于lockdown服务的更多内容,可以参考这篇文章:Understanding usbmux and the iOS lockdown service
usbmuxd服务
再来看macOS端:
前文讲过,usbmuxd是macOS的系统服务,该服务为macOS端的通信代理,当其他应用——比如iTunes——需要与iOS设备通信时,只需要与usbmuxd服务通信即可,usbmuxd负责将iTunes的通信请求转发到iOS端。usbmuxd服务与iOS设备通信基于USB通信协议。
iTunes与usbmuxd服务之间通过Unix Domain Socket进行通信,Unix Domain Socket的API和TCP Socket非常类似。来看下与usbmuxd协议之间建立连接的代码:
int connect_to_usbmuxd() {
// Create Unix domain socket
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
// prevent SIGPIPE
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
// Connect socket
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
// 这个路径就相当于ip地址,为了理解这个过程,可以和TCP进行对比
strcpy(addr.sun_path, "/var/run/usbmuxd");
socklen_t socklen = sizeof(addr);
if (connect(fd, (struct sockaddr *)&addr, socklen) == -1) {
printf("Connect failure, fd is: %d.\n", fd);
} else {
printf("Connect successifully, fd is: %d.\n", fd);
}
return fd;
}
和TCP的差别有以下几点:
- domian参数为
AF_UNIX
,而非熟悉的AF_NET
; - 通信地址不是IP地址,而是路径
/var/run/usbmuxd
; - 不需要指定端口。
除此之外,整个API的使用方式则几乎一样。iTunes在建立和usbmuxd之间的连接时,应该使用了类似的代码。
从一端到另一端
前文中提到,对于macOS端的进程而言,如果要与iOS上的进程通信,只需要和usbmuxd服务通信即可。而在上一节我们介绍了进程和usbmuxd服务建立连接的方式,那么假设iTunes通过上述方式建立了与usbmuxd之间的连接,那么接下来iTunes是不是就可以直接与位于iOS端的lockdown服务通信了呢?
答案是NO。
在回答为什么是NO之前,先来回忆一下基于socket的TCP通信过程。我们知道在TCP/IP通信协议体系里,IP抽象了网络上两台主机之间的通信,而TCP抽象了两台主机上的进程之间的通信。IP地址标识了网络中的一台主机,而端口号则标识了一台主机上的特定进程。
而在上一节,在建立进程(iTunes)与代理(usbmuxd)之间的连接时,并未指定端口号。未指定端口号,那么usbmuxd服务便不知道进程要和iOS端的哪个进程进行通信。
比如iTunes要和lockdown服务建立连接,lockdown服务的端口号是62078,而iTunes在建立连接时并未指定该端口号,因此对于usbmuxd而言,根本不知道iTunes要和lockdown服务通信。建立连接所需的信息是不完备的。
其实,如果用上一节中的提到的建立连接,只是建立了macOS进程与代理之间的通信连接,该连接建立以后,进程能够和代理进行通信,但若要和iOS上的目标进程通信的话,我们还需要对代理进行配置,让代理和iOS端的进程也建立连接,整个通信链路才算打通。
下面我们便来看下如何『配置』代理,从而打通整个通信链路。
Listen
在讲具体的连接过程之前,我们先来想下,从通信的角度讲,如果要usbmuxd服务和iOS端进程之间建立连接,必须要满足哪些条件:
- 一台Mac可以连接多台iOS设备,连接时需要知道和哪一台iOS设备建立连接,因此需要device id来唯一标识一台设备;
- 建立连接时必须要知道当前Mac是否连接到iOS设备,也即需要监听iOS设备的插拔;
- usbmuxd不会主动去连接iOS端上的进程,必须macOS进程驱使其完成,也即对其进行配置,此时macOS进程和usbmuxd之间需要一套通信协议;
- 必须指定需要连接到的目标进程的端口号,这样才能唯一标识一个进程。
先看问题3,苹果定义了一套配置协议,该协议的数据传输格式为plist,并被破解,协议的具体细节会在下文中阐释数据收发过程中讲到。而对于4,后文中会给出解答,现在先忽略。
而对于问题1和2,usbmuxd已经具备该功能,但是如果要『看到』该功能,我们需要向usbmuxd发送命令,这便是本节中要讲的Listen命令。
所谓发送Listen命令,便是macOS进程调用上一节中提到的connect_to_usbmuxd
函数,创建的socket,然后向对应的socket文件描述符中写入一段数据(控制命令),然后读一段数据(usbmuxd的返回值)。发送逻辑的代码如下:
void send_listen_packet() {
listen_channel = connect_to_usbmuxd_channel(); // 该函数调用 connect_to_usbmuxd 创建socket,并根据socket文件描述符创建一个dispatch I/O对象
// 1. Listen指令对应的键值对
NSDictionary *packet = @{
@"ClientVersionString": @"1",
@"MessageType": @"Listen",
@"ProgName": @"Peertalk Example"
};
NSLog(@"send listen packet: %@", packet);
send_packet(packet, 0, listen_channel);
}
void send_packet(NSDictionary *packetDict, int tag, dispatch_io_t channel) {
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:packetDict format:NSPropertyListXMLFormat_v1_0 options:0 error:NULL];
int protocol = USBMuxPacketProtocolPlist;
int type = USBMuxPacketTypePlistPayload;
// 2. 封装成usbmux协议要求的格式
usbmux_packet_t *upacket = usbmux_packet_create(
protocol,
type,
tag,
plistData ? plistData.bytes : nil,
(uint32_t)(plistData.length)
);
dispatch_data_t data = dispatch_data_create((const void*)upacket, upacket->size, usbmuxd_io_queue, ^{
usbmux_packet_free(upacket);
});
dispatch_io_write(channel, 0, data, usbmuxd_io_queue, ^(bool done, dispatch_data_t data, int _errno) {
NSLog(@"dispatch_io_write: done=%d data=%p error=%d", done, data, _errno);
if (!done) { return; }
});
}
此处的listen_channel是一个dispatch I/O对象,所谓的dispatch I/O对象,就是一个封装了文件描述符,以及作用于该文件描述符上的异步I/O策略。本文中可以将其和文件描述符作为等价的概念来理解。
这部分代码核心就2处,代码中已标出:
- 以plist格式封装Listen指令;
- 是将封装后的Listen报文发送到usbmuxd。
对应的报文格式如下图所示:
Listen命令发送成功以后,每当有iOS设备插拔,便会收到usbmuxd服务返回的数据。读取逻辑如下:
void read_packet_on_channle(dispatch_io_t channel) {
// 1. Read the header
usbmux_packet_t ref_upacket;
dispatch_io_read(channel, 0, sizeof(ref_upacket.size), usbmuxd_io_queue, ^(bool done, dispatch_data_t _Nullable data, int error) {
if (!done) { return; }
// Read size of incoming usbmux_packet_t
uint32_t upacket_len = 0;
char *buffer = NULL;
size_t buffer_size = 0;
// data 是读取到的数据,这一步获取到读取到的data的长度,并将buffer指向对应的缓冲区
dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size);
memcpy((void *)&(upacket_len), (const void *)buffer, buffer_size);
// Allocate a new usbmux_packet_t for the expected size
uint32_t payloadLength = upacket_len - (uint32_t)sizeof(usbmux_packet_t);
usbmux_packet_t *upacket = usbmux_packet_alloc(payloadLength);
// 2. Read rest of the incoming usbmux_packet_t
off_t offset = sizeof(ref_upacket.size);
dispatch_io_read(channel, offset, upacket->size - offset, usbmuxd_io_queue, ^(bool done, dispatch_data_t data, int error) {
NSLog(@"dispatch_io_read %lld,%lld: done=%d data=%p error=%d", offset, upacket->size - offset, done, data, error);
if (!done) { return; }
// Copy read bytes onto our usbmux_packet_t
char *buffer = NULL;
size_t buffer_size = 0;
dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size);
memcpy(((void *)(upacket))+offset, (const void *)buffer, buffer_size);
NSLog(@"package protocol is: %u, type is: %u", upacket->protocol, upacket->type);
// 3. Try to decode any payload as plist
NSError *err = nil;
NSDictionary *dict = nil;
if (usbmux_packet_payload_size(upacket)) {
dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:usbmux_packet_payload(upacket) length:usbmux_packet_payload_size(upacket) freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:&err];
}
// 4. Read next
read_packet_on_channle(channel);
usbmux_packet_free(upacket);
});
});
}
读逻辑中我删除了校验和容错逻辑,只保留了比较关键的部分,Listen命令的读操作说明如下:
- 先读取报文头,usbmuxd返回的报文格式也遵循usbmux协议,头部中记录了报文载荷的长度,所以必须先读出报文头之后,才能知道载荷的边界到哪里;
- 从头部解析出载荷长度之后,读取报文载荷数据;
- 头部和载荷读取完成之后,构成一个完整的报文,此时以plist格式解析载荷,获取返回内容;
- 读取下一个报文,读操作采用的I/O模式是阻塞模式,一直监听是否有数据,当有数据时便进行读取解析。
如图所示,是当iOS设备通过USB连接到(Attached)Mac时,usbmuxd服务返回的数据。其中比较关键的字段有两个:
MessageType
: Attached表明有设备连接的Mac,而Detached则表明断开连接;DeviceID
: 唯一标识当前插入的设备,后续与设备建立连接时会用到。
iTunes监听设备插拔时,便是通过类似的方式。
Connect
说完Listen,再来看下Connect。在上一节中,我们抛出了usbmuxd服务和iOS端进程之间建立连接的4点要求,其中前3个在上一节都已经得到解决,而第4个问题——端口号——其实在lockdown的启动配置文件时就已经知道了。此时我们已经具备建立连接的所有条件:
- Listen时得到DeviceID;
- lockdown启动配置文件中得知lockdown的端口号。
在讲Listen时,我们知道可以通过向usbmuxd服务发送Listen命令监听iOS设备插拔,类似地,也可以通过向usbmuxd服务发送Connect命令对其配置,使其建立和iOS端应用程序的连接。
整个过程如下,我们需要再调用connect_to_usbmuxd
函数再创建一个socket,用作Connect命令,和Listen命令的发送方式一样,发送Connect命令也是向socket文件描述符中写入数据:
void send_connect_usb_packet() {
print_empty_lines();
connect_channel = connect_to_usbmuxd_channel();
port = ((port<<8) & 0xFF00) | (port>>8);
NSDictionary *packet = @{
@"ClientVersionString" : @"1",
@"DeviceID" : deviceID,
@"MessageType" : @"Connect",
@"PortNumber" : [NSNumber numberWithInt:port],
@"ProgName" : @"Peertalk Example"
};
NSLog(@"send connect to usb packet: %@", packet);
send_packet(packet, 1, connect_channel);
read_packet_on_channle(connect_channel);
}
其中关键的字段有:
DeviceID
: 设备id,在Linten时获得;MessageType
: 消息类型,Connect表示需要建立连接;PortNumber
: 端口号,对应iOS端目标连接服务的端口,以lockdown为例,为62078。
对应的报文格式为:
注:实际发送过程中此处的PortNumber会以网络字节序的形式传输。
发送Connect命令之后,如果连接成功,此时便会收到usbmuxd返回的数据(读取Connect返回的数据和之前的读取Listen命令返回的数据方式相同):
Number为0表示连接成功。
为了容易理解,我们来和标准的TCP通信做个对比,其中右侧的lockdown,就是标准的TCP Server,而左侧的iTunes,建立与lockdown的连接时不是像标准的TCP Client一样,调用connect函数,而是向usbmuxd服务发送Connect命令。然而,虽然方法不同,但是二者在通信意义上是等价的。
并且,当在Connect命令发送后,在iOS侧,lockdown的accept方法会执行,随后双方便可以像标准的TCP通信一样发送和接收数据。
iTunes发送数据的方式如下:
void send_msg(NSString *msg) {
dispatch_data_t payload = create_payload(msg);
dispatch_data_t frame = create_frame(101, 0, payload);
// send through connect channel, not tcp_channel
dispatch_io_write(connect_channel, 0, frame, usbmuxd_io_queue, ^(bool done, dispatch_data_t _Nullable data, int error) {
NSLog(@"error is: %d", error);
});
}
其中connect_channel是我们在发送Connect命令时创建的文件描述符。
注意Listen和Connect使用不同的文件描述符。
经历以上过程,macOS程序便实现了通过usbmuxd和iOS通信。总结下来:
- macOS端进程通过Unix Domain Socket以类似TCP的方式和usbmuxd服务进行通信;
- macOS端进程通过向usbmuxd服务发送Connect命令,建立与iOS端目标进程之间的通信连接,端到端的通信基于端口号;
- iOS端进程以标准的TCP Server形式存在;
- usbmuxd作为中间代理,封装了USB协议的通信细节,对上提供类TCP的接口;
- 基于usbmux协议和usbmuxd服务,实现了macOS进程和iOS端进程之间简单、高效的通信。
简单在于协议的API基于socket,为大众熟知,而在通信效率方面,基于USB协议的通信效率显然比基于TCP/IP网络要快得多。
usbmux应用
如果是单纯分析macOS进程和iOS进程之间的通信原理的话,那大可不必如此大费周章,usbmuxd的魔力不仅在于它可以用于苹果自带应用和iOS程序之间的通信,而且,我是说而且,它可以用于为我们所用,基于usbmuxd服务,我们可以利用usbmux协议构建我们自己的应用。
我们可以基于usbmuxd服务,利用usbmux协议构建我们自己的应用。
重新审视lockdown
我们回过头来,重新看下lockdown做了哪些事情,从前文中lockdown的启动配置文件可以看出,它在开机时创建了一个listen socket,监听本地端口:62078,然后像普通的TCP Server那样,bind、accept阻塞等待客户端的连接。而当macOS端向usbmuxd发送Connect命令时,lockdown的accept结束阻塞,连接建立。
lookdown的源码没有,但是从启动配置文件和nc端口测试,结合网络知识,我们基本可以确认lockdown的内在逻辑。
那么,如果我们模拟lockdown,新建一个iOS APP,指定端口号,比如2345,然后通过调用socket、bind、listen、accept建立我们自己的TCP Server监听127.0.0.1:2345。
同时新建一个macOS应用,像iTunes一样连接usbmuxd服务,以端口号2345向其发送Connect命令,那么会发生什么呢?
答案是,如我们所愿,利用usbmuxd协议,我们建立了自己的macOS应用和iOS APP之间的通信连接,随后便可在此协议的基础之上,实现自建iOS APP和macOS应用程序之间的高效通信。整个通信框架如下:
前文中花了大量的篇幅来阐述usbmux协议的原理,最终的目的就是这么简单,我们想在自己的产品中应用usbmux协议。
usbmux应用举例
usbmux协议由于通信效率较高,在数据量较大的通信场景中,体验非常突出,毕竟当传输的数据量较大时,效率就是体验。目前基于usbmux有非常多的有意思的应用,常见的有:
- PeerTalk: PeerTalk基于usbmux实现了一个简单的IM,可用于macOS端和和iOS端之间聊天。本文的代码就是参照PeerTalk,如果你想基于usbmux协议构建自己的应用,而又不想深入了解usbmux协议的的话,可以直接搬运PeerTalk的代码。
- lookin: 腾讯开发的iOS界面调试利器,简直比Reveal还好用,而且免费。
参考
- usbmux_demo: 本文中的示例代码。
- libimobiledevice: 跨平台的协议库,用于iOS设备通信
- usbmux
- launchd
- Understanding usbmux and the iOS lockdown service
- Unix domain socket 简介
- Dispath I/O
peertalk 实验
macos example & ios example