- 1. UsbPortManager-usb role管理
- 2. USB host 管理
- 3. Usb device管理
结构
UsbPortManager
UsbHostManager
- UsbAlsaManager
UsbDeviceManager
1. UsbPortManager-usb role管理
1.1. USB主机和USB设备
USB通信中居于核心地位的是主机(Host),常见的USB主机是PC机。
任何一次USB的数据传输都必须由主机发起和控制
所有的USB外设都只能和主机建立连接
任何二个外设之间或是二个主机之间都无法直接通信
USB主机和USB设备的功能是不同的。
USB主机的功能:
- 通过USB接口给外设提供电源(外设也可以自带电源)
- 检测和配置设备(即设备的枚举)
- 错误检查和管理数据的传输
- 根据设定的传输方式与外设交换数据
USB设备的功能:
管理电源。设备可以由USB接口获取电源,也可能有自己的电源
检测通信
每一个设备都要检测通信信息包中的地址是否和本设备的地址相符,如果不符,设备就会忽略本次通信,这由USB接口硬件自动进行处理。在设备一开始连上USB接口时,使用固定的默认地址0,然后USB主机在检测阶段会给设备分配一个地址,以后的通信都按这个地址进行
通信数据的错误检查。由USB接口硬件保证,不必编程处理
响应请求
主机在检测到有设备连接上以后,会按USB协议发送相应的设备请求来了解设备的类型和能力,并对设备进行一些配置(如设定地址和配置描述符),设备应能响应这些请求,并返回相应的应答数据根据设定的传输方式与主机交换数据
1.2. Usb设备演进
USB Type A:该标准一般适用于个人电脑PC中,是应用于最广泛的接口标准
USB Type B:一般用于3.5寸移动硬盘、以及打印机、显示器等连接
USB Type C:USB Type C这个接口名称是在USB 3.1时代之后出现的,该接口的亮点在于更加纤薄的设计、更快的传输速度(最高10Gbps)以及更强悍的电力传输(最高100W)。Type-C双面可插接口最大的特点是支持USB接口双面插入,主要面向更轻薄、更纤细的设备(未来可能统一手机平板的接口,取代Micro USB接口)。
配备Type-C连接器的标准规格连接线可通过3A电流,同时还支持超出现有USB供电能力的“USB PD”,可以提供最大100W 的电力。
1.3. typec设备 role管理
Type-C与既有的USB Type-A与USB Type-B相比,具备无方向性的插拔、并能同时进行数据传输、影音输出及电源传递。消费者无法从Type-C的物理外观判断何者是主控端
(例如计算机或笔电)或设备端(例如手机或随身碟),这点与传统接口有显著差异(传统上Type-A即是主控端,USB Micro-B是设备端):因此,Type-C与USB Power Delivery(以下简称USB PD)规范定义了数种双模式
的角色,用以解决各种设备相互连接时可能产生的问题。
USB Power Delivery(USB PD):
- POWER_ROLE_SOURCE
- POWER_ROLE_SINK
source 和 sink 对应 主控端和受控端,source提供电力的一方,sink接收电力的一方。
设备需要支持两种模式,与计算机相连时应作为设备端,与随身碟或耳机相连时则转换角色作为主控端
1 | <string name="usb_supplying_notification_title">USB supplying power to attached device</string> |
相关的模式,见上层设置界面:
1 | public static final int[] DEFAULT_MODES = { |
1.3.1. power 模式
MODE_DFP dfp
DFP 是一种在host或hub上的USB Type-C端口,与device相连接MODE_UFP ufp
UFP 是一种在device或hub上的USB Type-C端口,与host或hub的DFP相连接MODE_DUAL dual
DRP 是一种既可作为DFP或UFP进行工作的USB Type-C端口DRP指的是作为Power Source(提供者)和Sink(消费者)的电源端口。例如,笔记本电脑上的USB Type-C端口支持USB-PD DRP,既可以作为Power Source(连接U盘或手机时),也可以作为Sink(连接显示器或电源适配器时)
1.3.2. 数据模式
DATA_ROLE_HOST host
手机作为usb主机使用
DATA_ROLE_DEVICE device
手机作为USB设备使用
1.3.3. 相关底层节点
1 | /sys/class/dual_role_usb |
supportedModes 对应支持的[power模式](#power 模式), 这一项一般是固定的(不可改变)
MODE_DFP | MODE_UFP | MODE_DUAL
mode
none/dfp/ufp
power_role
no-power/source/sink
data_role
no-data/host/device
1 | ./android/0000:7008:A111-09 14:17:01.953 3432 3450 I UsbPortManager: USB port changed: port=UsbPort{id=sprd_dual_role_usb, supportedModes=dual}, status=UsbPortStatus{connected=false, currentMode=none, currentPowerRole=source, currentDataRole=host, supportedRoleCombinations=[source:host]}, canChangeMode=true, canChangePowerRole=false, canChangeDataRole=false |
1.3.4. 状态切换与通知
Hal层通过uevent机制监控上述底层节点的变化, 通过回调PortManager注册的callback的notifyPortStatusChange函数,使portManager更新port. 同时,portManager也可以通过queryPortStatus函数向hal层查询port的信息.
DISPOSITION_ADDED
底层上报的portinfo在上层的mPorts中没有找到
DISPOSITION_CHANGED
底层上报的portinfo在上层的mPorts中找到了,且对应的 currentMode | currentPowerRole | currentDataRole | supportedRoleCombinations 几个值中任一个改变了
DISPOSITION_REMOVED
上层mPorts中有的项没有在底层上报的所有portinfo中, 该项设置为Removed
DISPOSITION_READY
底层上报的portinfo 在上层的mPorts中,且 currentMode | currentPowerRole | currentDataRole | supportedRoleCombinations 都没变.
PortManager对上层提供的接口:
1 | /** |
在有状态变更时, 发送ACTION_USB_PORT_CHANGED的广播
1 | /** @hide */ |
2. USB host 管理
2.1. Usb host 添加和删除设备.
UsbHostManager的初始化
1 | // 音频管理相关 |
UsbService systemReady时,调用UsbHostManager的systemReady
1 | public void systemReady() { |
这里启动了一个线程,调用了 jni 方法 monitorUsbHostBus.
对应 android_server_UsbHostManager_monitorUsbHostBus
1 | static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv* /* env */, jobject thiz) |
2.1.1. usb_host_load小结
- 启动一个线程,初始化了iNotify实例( inotify_init ), 返回fd 赋给 usb_host_context的 fd 成员
- 绑定 add remove的回调函数, added_cb\removed_cb. 赋给 usb_host_context的 cb_added | cb_removed 成员
- 监控 root 根节点 /dev 目录的 CREATE 和 DELETE 事件, 返回 watch 描述符 给 usb_host_context 的 wdd成员
- 监控/dev/bus/usb下存在的子目录 的CREATE 和 DELETE 事件, 返回 watch描述符给 usb_host_context的wds 数组成员
- 查找/dev/bus/usb/<00i>/ 下的设备是否存在, 如果存在这样的设备, 调用added_cb
2.1.2. usb_host_read_event 监听节点
通过INotify中的fd读取相关的事件,usb_host_run函数会在while循环中一直循环。一直运行该函数
1 | // 读取 inotify_init 实例的fd, 读出对应的事件 |
2.1.2.1. usb_host_read_event 小结
该函数即读取此次的inotify实例的fd, 放在event_buf数组中(包含多个inotify_event 事件.), 每个事件都有一个watch fd,
将watch fd 与之前建立的 watch fd比较, 判断是哪一个根目录的事件, 过滤出CREATE 和 DELETE 事件, 再由目录的层级添加对其子目录的监控,如果是CREATE事件,则查找目录下可能存在的usb设备节点, 找到调用added_cb添加解析设备,通知上层添加usb设备. 如果是 /dev/bus/usb/<001-00MAX_USBFS_WD_COUNT> 目录下的CREATE| DELETE 事件, 则直接添加删除设备,并通知到上层.只有在监控/dev目录失败的情况下,才会退出循环,也就是终止线程的运行.其他情况都是返回0,不退出循环,线程一直执行.
2.1.3. 两个回调函数
绑定的两个回调函数为 usb_device_added\usb_device_removed, 这两个函数在jni层.
着重介绍usb_device_added函数.
参数:
1 | const char *devname //native 层通过inotify找到的设备名称 ex: /dev/bus/usb/001/001 |
2.1.4. usb_device_added方法添加设备
先调用usb_device_open 方法打开该设备文件, 返回usb_device结构体.
1 | struct usb_device { |
dev_file
该结构体中 desc为 设备描述内容(包含一个设备描述符/n个配置描述符/n个接口描述符/n个端点描述符) , desc最大大小为4096, len中保存了实际的desc的大小, 即 /dev/bus/usb/001/001 的文件大小.
1 | jboolean result = env->CallBooleanMethod(thiz, method_beginUsbDeviceAdded, |
在USB设备枚举过程中,主机端的协义软件需要解析从USB设备读取的所有描述符信息。在USB主向设备发送读取描述符的请求后,USB设备将所有的描述符以连续的数据流方式传输给USB主机。主机从第一个读到的字符开始,根据双方规定好的数据格式,顺序地解析读到的数据流
设备描述符:给出了USB设备的一般信息,一个设备描述符可以有多个配置描述符
1 | struct usb_device_descriptor { |
设备描述符给出了USB设备的一般信息,包括对设备及在设备配置中起全程作用的信息,包括制造商标识号ID、产品序列号、所属设备类号、默认端点的最大包长度和配置描述符的个数等。一个USB设备必须有且仅有一个设备描述符。设备描述符是设备连接到总线上时USB主机所读取的第一个描述符.
配置描述符:配置描述符定义了设备的配置信息
1 | typedef struct _USB_CONFIGURATION_DESCRIPTOR_ |
配置描述符中包括了描述符的长度(属于此描述符的所有接口描述符和端点描述符的长度的和)、供电方式(自供电/总线供电)、最大耗电量等。主果主机发出USB标准命令Get_Descriptor要求得到设备的某个配置描述符,那么除了此配置描述符以外,此配置包含的所有接口描述符与端点描述符都将提供给USB主机。
接口描述符:接口描述符说明了接口所提供的配置,一个配置所拥有的接口数量通过配置描述符的bNumInterfaces决定
1 | typedef struct _USB_INTERFACE_DESCRIPTOR_ |
配置描述符中包含了一个或多个接口描述符,这里的“接口”并不是指物理存在的接口,在这里把它称之为功能
更易理解些,例如一个设备既有录音的功能又有扬声器的功能,则这个设备至少就有两个“接口”。
如果一个配置描述符不止支持一个接口描述符,并且每个接口描述符都有一个或多个端点描述符,那么在响应USB主机的配置描述符命令时,USB设备的端点描述符总是紧跟着相关的接口描述符后面,作为配置描述符的一部分被返回。接口描述符不可直接用Set_Descriptor和Get_Descriptor来存取。
如果一个接口仅使用端点0,则接口描述符以后就不再返回端点描述符,并且此接口表现的是一个控制接口的特性,它使用与端点0相关联的默认管道进行数据传输。在这种情况下bNumberEndpoints域应被设置成0。接口描述符在说明端点个数并不把端点0计算在内。
端点描述符:USB设备中的每个端点都有自己的端点描述符,由接口描述符中的bNumEndpoint决定其数量
1 | typedef struct _USB_ENDPOINT_DESCRIPTOR_ |
端点是设备与主机之间进行数据传输的逻辑接口,除配置使用的端点0(控制端点,一般一个设备只有一个控制端点)为双向端口外,其它均为单向。端点描述符描述了数据的传输类型、传输方向、数据包大小和端点号(也可称为端点地址)等。
除了描述符中描述的端点外,每个设备必须要有一个默认的控制型端点,地址为0,它的数据传输为双向,而且没有专门的描述符,只是在设备描述符中定义了它的最大包长度。主机通过此端点向设备发送命令,获得设备的各种描述符的信息,并通过它来配置设备。
在将设备描述符的信息发到上层后, 接着遍历设备的各种描述符,之后调用endUsbDeviceAdded结束添加设备
2.1.5. 描述符解析
设备描述符/配置描述符/接口描述符/端点描述符继承父类UsbDescriptor
1 | abstract class UsbDescriptor{ |
继承parseRawDescriptors方法, descriptor前两个字节中存放了长度和类型.解析时根据长度和类型最终将一个device中存放的descriptor的全部描述符全部解析出来.
2.1.5.1.1. USB 端点、管道和接口的关系
USB 系统中数据的传输,宏观的看来是在HOST 和 USB 功能设备之间进行;微观的看是在应用软件的 Buffer 和 USB 功能设备的端点之间进行。一般来说端点都有 Buffer,可以认为USB通讯就是应用软件Buffer和设备端点Buffer之间的数据交换,交换的通道称为管道。应用软件通过和设备之间的数据交换来完成设备的控制和数据传输。通常需要多个管道来完成数据交换,因为同一管道只支持一种类型的数据传输。用在一起来对设备进行控制的若干管道称为设备的接口;
一个 USB 设备可以包括若干个端点,不同的端点以端点编号和方向区分。不同端点可以支持不同的传输类型、访问间隔以及最大数据包大小。除端点 0外,所有的端点只支持一个方向的数据传输。端点 0是一个特殊的端点,它支持双向的控制传输。管道和端点关联,和关联的端点有相同的属性,如支持的传输类型、最大包长度、传输方向等。
2.1.6. added_cb 回调小结
- 将设备描述符的参数通过
beginUsbDeviceAdded
会传给上层 - 将配置描述符的参数通过
addUsbConfiguration
回传给上层 - 将接口描述符的参数通过
addUsbInterface
回传给上层 - 将端口描述符的参数通过
addUsbEndpoint
回传给上层 - 最后调用endUsbDeviceAdded方法回调上层结束该设备的添加过程.
beginUsbDeviceAdded:
1 | // 创建一个 UsbDevice 对象, 并初始化和其绑定的 mNewConfigurations| mNewInterfaces | mNewEndpoints集合 |
1 | // 新建一个 UsbConfiguration对象,并保存到 mNewConfigurations 集合中 |
1 | private void endUsbDeviceAdded() { |
其中几个参数描述了这几个描述符的关系:
- 设备描述符中的 bNumConfigurations : 可能的配置数.指配置字符串的个数
- 配置描述符中的bNumInterfaces, 指接口描述符的个数
- 接口描述符中的bNumEndpoint, 指定端口描述符的个数
除上述几种必须的描述符外, 还有字符串描述符,HID描述符,报告描述符等等.
2.1.7. usb_device_removed方法销毁设备
调用 usbDeviceRemoved 销毁deviceName对应的设备.
1 | private void usbDeviceRemoved(String deviceName) { |
2.2. 上层获取USB 设备并进行传输
UsbManager提供了getDeviceList函数,获取当前的usb设备列表,
最终调用到UsbHostManager中,从其保存的mDevices中拿到设备列表.
拿到设备(UsbDevice)后,可以通过openDevice打开这个设备. 返回 UsbDeviceConnection 对象.
1 | public UsbDeviceConnection openDevice(UsbDevice device) { |
这里涉及到了另一个jni类(android_hardware_UsbDeviceConnection.cpp)
native_open中主要调用了usb_device_new函数(见这里open_device), 返回了 native层 usb_device结构. 并将device跟field_context关联起来.
1 | struct usb_device* device = usb_device_new(deviceNameStr, fd); |
最后用户进程获取的对象就是UsbDeviceConnection,使用UsbDeviceConnection访问usb设备时,最终都会通过field_context找到device.
2.2.1. USB传输通信
google提供了控制传输的两个方法: 官方资料
bulkTransfer (块传输) 在bulk endpoint上进行传输
bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout);
在给定的端点执行一个bulk transaction;
参数:1
2
3
4endpoint: OUT or IN(Host to Device用OUT,Device to Host 用IN);
buffer : 将要发送/接收的指令或数据,当endpoint为OUT,buffer为你定义好的指令或数据,将下发给device,当endpoint为IN,buffer则是一个容器,用来存储device返回的应答指令或数据,注意buffer的大小,以足够存储所有的数据;
length : 即发送/接收指令或数据的大小;
timeout : 即指令或数据的最长通讯时间,在通讯出现问题时,若超时还未通讯完成,视为通讯失败;每个数据包长度高速的时候为512字节,低速设备最大64个字节;用于主机与USB设备之间的批量数据传输,通常一次块传输需要分解成若干个块传输事务。
一次块传输的方向是单一的,对主机而言,要么是输入,要么是输出。因此,一次块传输是由若干个IN事务或由若干个OUT事务组成的。
对于要进行输入的块传输,一般要执行若干个IN事务。每执行一个IN事务时,主机都首先发出IN令牌包。设备端点收到后做出响应,一般是回送一个数据包。如果不能回送数据,则回送NAK包或STALL包。NAK表示设备暂时不能回送数据;STALL表示端点一直停着或需要IJSB系统软件进行干预;如果主机收到合法数据包,则回以ACK握手包;如果主机在接收数据时发现有错,则不给设备任何回音。
对于要进行输出的块传输,一般要执行若干个 OUT事务。每执行一个OUT事务时,主机都首先发出OUT令牌包,接着发出数据包。设备在收到数据包后,根据情况回以握手包;回以ACK表示数据已接收 无误,并通知主机可开始下一个0UT事务,以便传送下一个数据包;回以NAK表示数据已接收无误,但是主机不要再送数据,因为设备暂时不能接收(如缓冲区 满);如果端点已终止,则回以STALL,通知主机不要再重发数据,因为设备出现了故障;如果接收时出现CRC校验错,则不发任何握手包。如果需要输入、输出同时进行,则需要使用2个端点。
controlTransfer (控制传输)
controlTransfer(int requestType, int request, int value, int index, byte[] buffer, int length, int timeout)
零点传输执行一个control transaction,即所有的通讯都是通过endpoint 0;
参数:1
2
3
4
5
6
7requestType, request type for this transaction 设置通信的方向
request ,request ID for this transaction 设置的是访问的类型
value ,value field for this transaction
index ,index field for this transaction
buffer ,同bulkTransfer()
length ,同bulkTransfer()
timeout ,同bulkTransfer()
该函数可以设置设备信息,可以看下面的例子:
1 | private void makeThisDeviceAnAccessory( { UsbDeviceConnection connection) |
定制了设备的 MANUFACTURER / DESCRIPTION / VERSION 等的信息. 设置了相关的String信息后,可以从device设备上读取相应的信息. 同样通过 controlTransfer 函数, 不过requestType变为了 USB_DIR_IN.
USB 体系四种传输类型:
控制传输:主要用于在设备连接时对设备进行枚举以及其他因设备而异的特定操作。
中断传输:用于对延迟要求严格、小数据的可靠传输,如键盘、游戏手柄等。
批量传输:用于对延迟要求宽松,游戏手柄等大量数据的可靠传输,如U盘等, 该模式即为块传输。
同步传输:用于对可靠性要求不高的实时数据传输,如摄像头、USB音响等.
不同的传输类型在物理上并没有太大的区别,只是在传输机制、主机安排传输任务、可占用USB 带宽的限制以及最大包长度有一定的差异。
2.3. UsbAlsaManager 音频管理
2.3.1. UsbAlsaManager初始化
进行初始化扫描, 调用了AlsaCardsParser类对"/proc/asound/cards"
文件进行扫描
1 | // AlsaCardsParser的scan方法 |
扫描结果存放到AlsaCardRecord数组中 (mCardRecords) , 查看AlsaCardRecord的结构如下:
1 | public int mCardNum = -1; |
其中mIsUsb标明该音频设备是否是Usb设备. 判断标准是 包含 at usb-
关键字
1 | bullhead:/proc/asound # cat cards |
2.3.2. UsbAlsaManager systemReady
systemReady时添加对 /dev/snd/
下的文件监控, 监控新建和删除.
同时对其下面的文件调用alsaFileAdded方法, 添加已经存在的设备.
新建文件时,回调alsaFileAdded方法, 删除了文件时回调 alsaFileRemoved 方法
对于alsaFileAdded方法, 过滤目录下文件名:
- 文件名以
pcmC
开头的,以p
结束的为TYPE_PLAYBACK设备(播放设备) - 文件名以
pcmC
开头的,以c
结束的为TYPE_CAPTURE设备(录音设备) - 文件名以
midiC
开头的,为TYPE_MIDI设备.
对于播放设备和录音设备, 创建AlsaDevice,并以filename为key存放到mAlsaDevices 的map中保存起来.
1 | ex: /dev/snd/pcmC0D34c |
对于alsaFileRemoved方法, 只需要将filename对应key的 AlsaDevice 从mAlsaDevices中删除即可.
2.3.3. usbDeviceAdded 事件回调
在前一章中介绍了通过inotify监控/dev/bus/usb下设备的机制.
这里在UsbHostManager中, 在接收到usbDeviceAdded后, 处理后(endUsbDeviceAdded
), 将事件转发给了UsbAlsaManager.在转发给UsbAlsaManager之前, 借助UsbDescriptorParser类对新添加usb设备的描述符进行了解析. 涉及到了 新增的 descriptors文件夹下的多个类的处理.(这里涉及的类是UsbACTerminal) 最终判断出该设备是否是 input设备和 output设备.(含有麦克|含有外放)
1 | /* package */ void usbDeviceAdded(UsbDevice usbDevice, |
通过上面的过程分析, 可见UsbAlsaManager的流程:
对于USB audio类型的设备, 首先扫描 /proc/asound/cards
文件, 找出usb设备, 在inotify检测到/dev/bus/usb
下有新增的usb设备时, UsbHostManager将相应的usbDeviceAdded回调转发给UsbAlsaManager, 转发前解析了设备的desciptor, 判断其中是否含有麦克/外放设备. Alsa处理该转发时, 还需要去查找设备的接口中是否有USB_CLASS_AUDIO Class. 上述条件都满足时, 还要再次对/proc/asound/cards
文件进行扫描. 查找最近添加的usb audio设备. 查到了, 则调用selectAudioCard函数, 连接音频设备.
关键函数是 selectAudioCard 函数. 下面对该函数进行进一步分析:
1 | /* package */ UsbAudioDevice selectAudioCard(int card) { |
关于AudioService的setWiredDeviceConnectionState:
- 判断是否需要发送拔出耳机的intent; (拔出耳机时,需要将当前播放的音频停掉)
- 通过底层AudioSystem处理设备的连接和断开连接
- 发送Intent广播(ex: ACTION_HEADSET_PLUG)通知外设状态变化
关于"/proc/asound/devices"
文件内容:
1 | sp9832e_1h10:/proc/asound # cat devices |
可见对应整个流程,需要四个条件准备就绪:
- /dev/bus/usb/ 下出现了新的设备, 且解析设备描述符为 HEADSET相关的设备:(desciptor, 判断其中是否含有 麦克/外放设备. 还需要去查找设备的接口中是否有USB_CLASS_AUDIO Class)
- /dev/snd/下出现对应的音频设备
- “/proc/asound/cards” 中出现对应的声卡
- “/proc/asound/devices” 中出现对应的设备描述
上述过程对应Alsa架构的音频设备管理.
3. Usb device管理
手机作为usb设备,主要是和pc端的交互.pc作为usb主机, 与手机进行usb通信.
3.1. Usb枚举过程
切换function涉及到的逻辑:
Client 对应app或者其他服务.
一个USB接口扩展出多个设备功能的实现方法有两种:
在设备外部或内部加Hub扩展
以Usb Composite Device方式实现(一般称为复合设备)。
复合设备其实只是一个USB设备,只有一个USB设备地址,它实现多个功能的原因主要在于它扩展实现了多个USB接口,每个接口具有不同的设备类型。Android下采用了USB Composite Device这种方式来实现一个USB口的情况下扩展出多个功能设备,这种情况下一个USB接口(Interface)便对应一种类型的功能设备,需要实现与之对应的功能驱动。

3.2. 传统Usb device与accessory(配件)模式
Android设备作为传统device设备, host端主要是pc(也可以是手机). 如mtp/ptp/mass_storage/虚拟光驱/tethering模式等.
Android设备还可以作为accessory设备, 以USB Device的角色与一些具有USB Host功能,但却扮演着配件角色的设备相连.
这些设备可能是机器人控制器、Dock(基座)、诊断设备、音响设备、配电设备、读卡器等等。
Google引入USB Accessory概念的原因应该主要有如下:
- 非常多的Android设备不具有USB Host的功能而只具有USB Device功能,或者即使具备USB Host的功能,也承担不起对USB外设供电的任务,因为便携式Android设备本身的电池容量就很有限。
- 原来的Android设备,作为USB Device所实现的功能相对比较简单,内置的功能只有U盘或ADB调试设备等,Google希望提供应用层的USB开发库,让更多的软硬件厂商来开发新的功能,比如说安装一个APK应用,然后通过USB连接到一个与电视机配套的Dock上,就可以让一台Android手机变身为一个电视机遥控器。
当Android设备作为Host模式时为USB总线供电;当Android设备连接到一个USB Accessory设备时,USB Accessory设备以Host身份为USB总线供电。
USB Accessory设备: 配件设备, 以host方式供电
Android 设备: device角色, usb function切换为accessory.
两台手机互联时, 在打开accessory mode情况下, 连接adapter的一端为USB Accessory设备, 直接microusb或typec接口的一端为device设备.
3.2.1. accessory模式下的枚举过程:
USB Accessory设备和Android设备两者双方整个枚举识别工作过程如下:
- USB Accessory设备发起USB控制传输进行正常的USB设备枚举,获取设备描述符和配置描述符等信息,此时大部分Android设备上报的还只是普通的U盘或MTP设备;
- USB Accessory设备发起Vendor类型,request值为51(0x33)的控制传输命令(ACCESSORY_GET_PROTOCOL),看看该Android设备是否支持USB Accessory功能,如果支持的话会返回所支持的AOA协议版本
- USB Accessory设备判断到该Android设备支持Accessory功能后,发起request值为52(0x34)的控制传输命令(ACCESSORY_SEND_STRING),并把该Accessory设备的相关信息(包括厂家,序列号等)告知Android设备
- 如果是USB Audio Accessory设备,还会发起request值为58(0x3A)的控制传输命令(SET_AUDIO_MODE命令),通知Android设备进入到Audio Accessory模式
- USB Accessory设备发起request值为53(0x35)的控制传输命令(ACCESSORY_START),通知Android设备切换到Accessory功能模式开始工作。Android设备收到此uevent信息后,会先把sys.usb.config设置为包含accessory功能,此后Android设备工作在accessory 模式下. 厂商可以通过该模式开发出适用各种场景的usb配件.
3.2.2. accessory模式框架
在Android设备切换为accessory function成功后, 会触发configured的流程, 回调accessoryAttached函数.
通过匹配android.hardware.usb.action.USB_ACCESSORY_ATTACHED
intent找到厂商的apk.apk在onCreate时会接收到的主要参数为UsbManager.EXTRA_ACCESSORY, 携带了usb accessory设备的描述信息. 也可以通过getAccessoryList
接口拿到当前已经连接的accessory配件.
1 | private final String mManufacturer; |
1 | <activity android:name="UsbAccessoryActivity" android:label="DemoKit" |
通过openAccessory方法拿到fd, 通过fd拿到输入输出流进行传输控制
1 | UsbManager usbManager = getContext().getSystemService(UsbManager.class); |
而host端传输方式(假设host是手机)
1 | UsbDeviceConnection connection = mUsbManager.openDevice(device); |
3.2.2.1. 申请accessory权限
打开accessory配件需要权限, 需要应用进行动态申请.
1 | //uidList中保存了已经为该accessory申请权限的应用的uid列表. |
1 | UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE); |
accessory模式的框架与host框架开发类似. host模式下device设备开发文档见host overview
3.3. Usb 网络共享
usb模式涉及到usb网络共享. (usb网络绑定与逆绑定)
网络共享服务基于微软开发的rndis协议. 主要用于windows平台中usb网络设备的驱动开发.当手机通过usb连接到主机,启用usb绑定.需要将手机上的usb设置为rndis.主机上识别到新的网卡.要使用通过USB绑定的网卡,需要给主机分配IP地址.
在USB绑定中,主机是DHCP的客户端,手机是DHCP的服务器端.Android中使用了DNSmasq充当DHCP服务器.
切换为网络共享的流程和其他的模式是一样的, 都是通过UsbDeviceManager设置属性, init写入driver的相关节点, 开启rndis模式.开启后通过driver上报的uevent事件, 判断驱动是否可以正常工作.当收到configured状态时,开始真正的配置网络.
3.3.1. 配置网卡
等rndis切换成功后, Android设备网卡的节点会出现rndis0/usb0的接口.
1 | sp9832a_2h11:/sys/class/net # ls -l |
3.3.2. StateMachine
3.3.2.1. state 模式
将对象的状态封装成一个对象,在不同的状态下同样的调用执行不同的操作
对象内部状态决定行为方式,对象状态改变则行为方式改变.封装对象的状态是将对象的状态与行为封装在一起;可以解决庞大的分支语句带来程序阅读性差和不便于进行扩展问题,使整个结构变得更加清晰明了,降低程序管理的复杂性提高灵活度。
StateMachine的构造函数都是protected类型,不能实例化;都是由其子类进行初始化操作;
1 | protected StateMachine(String name) { |
3.3.2.2. State Machine各个模块作用
- State
状态的基类,stateMachine中的状态都是由State派生而来,构造函数protected,不能实例化;只能由子类继承进行实例化.
1 | public class State implements IState |
SmHandler
SmHandler的内部类
StateInfo
存储当前State,和其parentState,以及是否激活状态;用来构建树形层次结构模型
1
2
3
4
5
6
7
8
9private class StateInfo
{
/** the state */
State state;
/** The parent of this state, null if there is no parent */
StateInfo parentStateInfo;
/** True when the state has been entered and on the stack */
boolean active;
}HaltingState与QuittingState
都是State的 派生类,用于在状态停止和放弃之后处理的一些事情;都重写了ProcessMessage方法,
在StateMachine没有实际行动仅仅保留用于扩展。
整个SmHandle是消息处理派发和状态控制切换的核心,运行在单独的线程上。
1 | private static class SmHandler extends Handler |
3.3.2.2.1. Smhandler
SmHandle是构建StateMachine的核心,运行在独立的线程上,有三个功能:
建立树形层次结构存储State;
在构成一个状态机前需要确定当前都多少状态,需要将这些状态集中起来进行管理。
StateMachine提供了这样一个protected类型方法 addState来将状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33/****************************************************
* state: 加入state machine的State
* parent: the parent of state, patent的状态节点可以为空
****************************************************/
private final StateInfo addState(State state, State parent) {
StateInfo parentStateInfo = null;
if (parent != null) {
//获取当前状态parent详细信息 StateInfo
parentStateInfo = mStateInfo.get(parent);
if (parentStateInfo == null) {
//当前状态父状态未加入到StateMachine中,
//递归先加入其Parent State
parentStateInfo = addState(parent, null);
}
}
//判断当前状态是否加入到 StateMachine层次结构中
StateInfo stateInfo = mStateInfo.get(state);
if (stateInfo == null) {
//创建State详细信息对象,将其加入到StateMachine层次结构中
stateInfo = new StateInfo();
mStateInfo.put(state, stateInfo);
}
//验证我们有没有加入相同的状态,在两个不同层次,否则异常
if ((stateInfo.parentStateInfo != null)
&& (stateInfo.parentStateInfo != parentStateInfo)) {
throw new RuntimeException("state already added");
}
stateInfo.state = state;
stateInfo.parentStateInfo = parentStateInfo;
stateInfo.active = false;
return stateInfo;
}1
private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
mStateInfo是按照tree层次组织State的.
1
2
3
4
5
6
7
8
9
10
11SmHandle sm;
sm.addState(S0,null);
sm.addState(S1,S0);
sm.addState(S2,S0);
sm.addState(S3,S1);
sm.addState(S4,S1);
sm.addState(S5,S2);
sm.addState(S6,S2);
sm.addState(S7,S2);
//设置初始状态
setInitialState(S4);状态机的StateStack建立和状态切换;
各状态State加入到StateMachine,各条件初始化OK后,就可以启动状态机了。
启动状态机
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public void start()
{
/** Send the complete construction message */
1. mSmHandler.completeConstruction();
}
1. private final void completeConstruction()
{
//计算State tree的最大深度以便创建运行State Stack
int maxDepth = 0;
for (StateInfo si : mStateInfo.values()) {
int depth = 0;
for (StateInfo i = si; i != null; depth++) {
i = i.parentStateInfo;
}
if (maxDepth < depth) {
maxDepth = depth;
}
}
//创建State Stack
mStateStack = new StateInfo[maxDepth];
mTempStateStack = new StateInfo[maxDepth];
//根据当前mDestState(S5)按照其层次结构沿着其父子关系,
//保存此条路径上的StateInfo 存储到State Stack中于是
//例如:S0--S2—S5 存储到mStateStack中
setupInitialStateStack();
//层次结构状态构建完成调用mStateStack中State的enter方法
//使mStateStack中的State 处于active状态
mIsConstructionCompleted = true;
mMsg = obtainMessage(SM_INIT_CMD);
invokeEnterMethods(0);
//Perform any transitions requested by the enter methods
1.1 performTransitions(); //待下面分析
}State Stack里面的元素结构是根据父子关系组成链式结构:S0——S2——S5;S5是mDestState,
S2,S0都是其parentState;状态是一种父子关系;
状态切换
:StateMachine中提供了方法:
1
2
3
4
5
6
7
8
9protected final void transitionTo(IState destState)
{
mSmHandler.transitionTo(destState);
}
private final void transitionTo(IState destState)
{
// mDestState保存当前状态 来处理消息;
mDestState = (State) destState;
}transitionTo仅仅是改变了当前状态mDestState,从StateStack建立这里可以看到和这个mDestState相关的还有mStateStack,如果改变了mDestState,mStateStack也需要改变,使mStateStack仍然是链式层次式结构。
需要改变mStateStack:
mDestState改变时,没有同时改变 mStateStack,而是等到消息处理派发状态Handle的时候,当前的状态行为处理完,切换到下一个状态,即消息处理完毕然后才进行mStateStack的更新。目的是使状态切换和mStateStack的更新独立开来.
ex:
mStateStack中存储:S0——S2——S5 mDestState为S5 (栈顶)
现在状态切换为S3,mDestState为S3
按照父子关系,mStateStack应该存储有:S0——S1——S3
那么此时S5,S2都要出栈pop from mStateStack
那我们就是要找到一个点,让S5,S2出栈;S3,S1进栈;
1 | 1.1 //Do any transitions |
- 消息处理和派发;
StateMachine处理的核心就是SmHandler,就是一个Handler,运行在单独线程中。
Handler是用来异步处理派发消息,这里使用Handler管理各个状态,派发消息处理到各个状态中去执行。
状态机准备OK后(状态加入和状态栈构建完成)就可以执行某些行为,接收消息进行处理,派发到当前状态去执行。
看一下SmHandler中handleMessage是如何进行消息处理的。
消息接收:
StateMachine提供了sendMessage等方法将消息加入到消息队列中,当然都是交给SmHandler去处理的。
消息处理:
1 | public final void handleMessage(Message msg) |
到这里看到建立状态栈mStateStack
的作用,用来支持进行链式的消息处理;(Chain of Responsibility)
3.3.3. Tethering网络状态机
Tethering对象使用了状态模式来实现共享连接机制的实现,为每个状态创建一个状态对象,一个状态对象根据不同情景可以切换到另一个状态对象。
3.3.3.1. TetherInterfaceSM(TetherInterfaceStateMachine)
Tethering类图如上, Tethering对象为每一个使用共享连接的物理接口维护一个TetherInterfaceSM类型的状态机,管理Tethering接口的状态。TetherInterfaceSM(TetherInterfaceStateMachine
的简写)状态机在NetworkManagementService服务触发的interfaceAdded回调中实例化。
TetherInterfaceSM类型的状态机状态:
- InitialState(初始状态)
- LocalHotspotState
- TetheredState(共享状态)
- UnavailableState(连接不可用状态)
1 | //TetherState |
TetherInterfaceSM通过isAvailable、isTethered、isErrored、getLastError等函数对外提供Tethering接口的状态信息,从而使Tethering的getTetherableIfaces、getTetheredIfaces、getTetheredIfacePairs、getTetheringErroredIfaces、getLastTetherError等函数可以从接口对应的状态机中获得Tethering接口的状态。TetherInterfaceSM状态机在正常共享工作情况下应该处于TetheredState状态,在TetheredState状态通过NetworkManagementService的tetherInterface的函数来添加使用共享连接的接口。
在底层配置好rndis function后, 添加rndis0/usb0的网卡接口,会回调interfaceAdded方法, 这时会新建TetherState实例, 同时会实例化一个TetherInterfaceStateMachine对象,作为TetherState的成员, 最后以接口名称为索引,加到mTetherStates的map中保存.
3.3.3.1.1. TetherInterfaceSM 初始化
1 | public TetherState(TetherInterfaceStateMachine sm) { |
- 初始化工作
- 构建tree, 添加了TISM的四个状态.
- 初始状态为TISM的initialState, 并调用了其enter方法, 通过回调通知到主控状态机更新state.
- 将mIsConstructionCompleted标记为true, 后面可以处理各个state的processmessage方法.
3.3.3.2. TetherMasterSM
Tethering对象还提供了一个TetherMasterSM类型的主控状态机,提供共享连接的启动、停止等管理及连接状态事件的监控并向TetherInterfaceSM状态机发送事件通知。
TetherMasterSM状态机的状态包括:
InitialState(初始状态)
TetherModeAliveState(共享模式激活状态)
ErrorState
ErrorState类型的状态
- SetIpForwardingEnabledErrorState
- SetIpForwardingDisabledErrorState
- StartTetheringErrorState
- StopTetheringErrorState
- SetDnsForwardersErrorState等出错状态
正常共享工作情况下TetherMasterSM状态机处于TetherModeAliveState状态,在TetherModeAliveState状态打开共享连接,并调用NetworkManagementService服务的setIpForwardingEnabled、setDnsForwarders、startTethering函数启动共享连接服务。
3.3.3.2.1. 主控状态机初始化
在Tethering的构造函数中, 进行初始化
1 | mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper); |
TetherMasterSM的InitialState没有enter方法, 所以在启动主控状态机时并没有执行.只是更新了InitialState的stack,并将mIsConstructionCompleted标记为true
3.3.3.2.2. 接收interface状态机的回调
接着TISM的初始化工作讲, 将回调interfaceAdd或interfaceChanged后, 初始化并启动了TISM状态机. 回调了notifyInterfaceStateChange方法
1 | // TISM initalState |
在仅仅有interfaceAdd回调时, 此时的TMSM主控状态机的状态保留在InitialState. 而TISM 接口状态机的状态也保存在InitialState. 同时TetherState的lastState为STATE_AVAILABLE
3.3.3.3. 收到configured状态后, 开启网络共享
在切换rndis成功后, 首先有网卡接口消息的上报, 然后收到configured uevent消息, 并通过广播拿到该状态后, 开启网络共享.
1 | tetherMatchingInterfaces(IControlsTethering.STATE_TETHERED, |
在开启网络共享时, 先通过TISM的状态机切换状态.
TISM的InitialState处理CMD_TETHER_REQUESTED消息.
1 | public boolean processMessage(Message message) { |
在基类StateMachine handleMessage的末尾处理performTransitions函数.
调用切换前State的Exit方法, 更新DestState的Stack, 对更新后的Stack未激活的stack执行其enter方法.
TISM的InitialState没有exit方法. 只执行TetherState的enter方法.
1 | class TetheredState extends BaseServingState { |
在开启网络共享后, 先是TISM行动, 切换并激活了TetherState, 然后tetherState lastState变更到STATE_TETHERD, 并通知到主控状态机TMSM切换到TetherModeAliveState 并激活.
在上面两个状态机激活绑定状态时,即在enter时, 涉及到了usb网卡的配置及激活共享等操作. 这些命令抛开state的流程单独描述.
3.3.3.3.1. 网络配置激活等
- startIPv4
1 | private boolean startIPv4() { return configureIPv4(true); } |
开启网卡usb网络共享状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25mNMService.tetherInterface(mIfaceName);
public void tetherInterface(String iface) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
// SND -> {65 tether interface add usb0}
mConnector.execute("tether", "interface", "add", iface);
}
List<RouteInfo> routes = new ArrayList<>();
// SND -> {66 interface getcfg usb0}
// RCV <- {213 66 9e:84:73:52:ac:0a 192.168.42.129 24 up broadcast multicast}
routes.add(new RouteInfo(getInterfaceConfig(iface).getLinkAddress(), null, iface));
addInterfaceToLocalNetwork(iface, routes);
}
public void addInterfaceToLocalNetwork(String iface, List<RouteInfo> routes) {
// SND -> {67 network interface add local usb0}
modifyInterfaceInNetwork("add", "local", iface);
for (RouteInfo route : routes) {
if (!route.isDefaultRoute()) {
// SND -> {68 network route add local usb0 192.168.42.0/24}
modifyRoute("add", "local", route);
}
}
}turnOnMasterTetherSettings
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
1113.2.1. // 跳转turnOnMasterTetherSettings 函数. 启动共享连接服务
protected boolean turnOnMasterTetherSettings() {
final TetheringConfiguration cfg = mConfig;
try {
1. mNMService.setIpForwardingEnabled(true);
} catch (Exception e) {
mLog.e(e);
transitionTo(mSetIpForwardingEnabledErrorState);
return false;
}
// TODO: Randomize DHCPv4 ranges, especially in hotspot mode.
try {
// TODO: Find a more accurate method name (startDHCPv4()?).
2.1 // TetheringConfiguration最初的信息来自于TetheringConfiguration的构造函数.
2.2 mNMService.startTethering(cfg.dhcpRanges);
} catch (Exception e) {
try {
mNMService.stopTethering();
mNMService.startTethering(cfg.dhcpRanges);
} catch (Exception ee) {
mLog.e(ee);
transitionTo(mStartTetheringErrorState);
return false;
}
}
mLog.log("SET master tether settings: ON");
return true;
}
1. setIpForwardingEnabled
public void setIpForwardingEnabled(boolean enable) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
// SND -> {70 ipfwd enable tethering}
mConnector.execute("ipfwd", enable ? "enable" : "disable", "tethering");
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
2.1 TetheringConfiguration初始化
// Tethering构造函数中
updateConfiguration();
private void updateConfiguration() {
mConfig = new TetheringConfiguration(mContext, mLog);
mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
}
public TetheringConfiguration(Context ctx, SharedLog log) {
final SharedLog configLog = log.forSubComponent("config");
tetherableUsbRegexs = ctx.getResources().getStringArray(
com.android.internal.R.array.config_tether_usb_regexs);
69 <string-array translatable="false" name="config_tether_usb_regexs">
70 <item>"usb\\d"</item>
71 <item>"rndis\\d"</item>
72 </string-array>
// TODO: Evaluate deleting this altogether now that Wi-Fi always passes
// us an interface name. Careful consideration needs to be given to
// implications for Settings and for provisioning checks.
tetherableWifiRegexs = ctx.getResources().getStringArray(
com.android.internal.R.array.config_tether_wifi_regexs);
tetherableBluetoothRegexs = ctx.getResources().getStringArray(
com.android.internal.R.array.config_tether_bluetooth_regexs);
dunCheck = checkDunRequired(ctx);
configLog.log("DUN check returned: " + dunCheckString(dunCheck));
preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, dunCheck);
isDunRequired = preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN);
2.1.1 dhcpRanges = getDhcpRanges(ctx);
defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
configLog.log(toString());
}
2.1.1 private static String[] getDhcpRanges(Context ctx) {
// 这个里面没改的话是空的,
final String[] fromResource = ctx.getResources().getStringArray(
com.android.internal.R.array.config_tether_dhcp_range);
if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
return fromResource;
}
// 前面的为空, 返回下面这个写死的值
/*
private static final String[] DHCP_DEFAULT_RANGE = {
"192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
"192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
"192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
"192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
"192.168.137.2", "192.168.137.254", "192.168.0.2", "192.168.0.254",
};
*/
return copy(DHCP_DEFAULT_RANGE);
}
2.2 mNMService.startTethering(cfg.dhcpRanges);
public void startTethering(String[] dhcpRange) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
final Command cmd = new Command("tether", "start");
for (String d : dhcpRange) {
cmd.appendArg(d);
}
try {
//SND -> {71 tether start 192.168.42.2 192.168.42.254 192.168.43.2 192.168.43.254 192.168.44.2 192.168.44.254 192.168.45.2 192.168.45.254 192.168.46.2 192.168.46.254 192.168.47.2 192.168.47.254 192.168.48.2 192.168.48.254 192.168.49.2 192.168.49.254 192.168.137.2 192.168.137.254 192.168.0.2 192.168.0.254}
mConnector.execute(cmd);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}turnOnMasterTetherSettings 主要工作就是下发了两个命令:
1
2ipfwd enable tethering
tether start 192.168.42.2 192.168.42.254 192.168.43.2 192.168.43.254 192.168.44.2 192.168.44.254 192.168.45.2 192.168.45.254 192.168.46.2 192.168.46.254 192.168.47.2 192.168.47.254 192.168.48.2 192.168.48.254 192.168.49.2 192.168.49.254 192.168.137.2 192.168.137.254 192.168.0.2 192.168.0.254chooseUpstreamType
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
593.2.2 // 跳转chooseUpstreamType函数, 进行网络通路优化 tryCell true
protected int chooseUpstreamType(boolean tryCell) {
// We rebuild configuration on ACTION_CONFIGURATION_CHANGED, but we
// do not currently know how to watch for changes in DUN settings.
// 可能更新了配置信息, 即上面的TetheringConfiguration
maybeUpdateConfiguration();
// preferredUpstreamIfaceTypes是在TetheringConfiguration传入的, 注意系统中只定义了数据流量模式, wifi模式和蓝牙网络可以作为优选网络, 1/7/0. 在数据流量模式时, 需要检查TelephoneManager的dunCheck, dunCheck为DUN_REQUIRED时忽略数据流量模式, 即不能作为优选网络. 网络通路.
final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType(
mConfig.preferredUpstreamIfaceTypes);
if (ns == null) {
if (tryCell) {
mUpstreamNetworkMonitor.registerMobileNetworkRequest();
// We think mobile should be coming up; don't set a retry.
} else {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
}
}
mUpstreamNetworkMonitor.setCurrentUpstream((ns != null) ? ns.network : null);
// 如果找到了优选网络通路. 设置dns转发
3.2.2.1 setUpstreamNetwork(ns);
...//ipv6 相关
return upV6Type;
}
3.2.2.1
protected void setUpstreamNetwork(NetworkState ns) {
String iface = null;
if (ns != null && ns.linkProperties != null) {
// Find the interface with the default IPv4 route. It may be the
// interface described by linkProperties, or one of the interfaces
// stacked on top of it.
mLog.i("Finding IPv4 upstream interface on: " + ns.linkProperties);
// 选择最佳路由
RouteInfo ipv4Default = RouteInfo.selectBestRoute(
ns.linkProperties.getAllRoutes(), Inet4Address.ANY);
if (ipv4Default != null) {
iface = ipv4Default.getInterface();
mLog.i("Found interface " + ipv4Default.getInterface());
} else {
mLog.i("No IPv4 upstream interface, giving up.");
}
}
if (iface != null) {
1. setDnsForwarders(ns.network, ns.linkProperties);
}
// 设置桥接转发
2. notifyDownstreamsOfNewUpstreamIface(iface);
if (ns != null && pertainsToCurrentUpstream(ns)) {
// If we already have NetworkState for this network examine
// it immediately, because there likely will be no second
// EVENT_ON_AVAILABLE (it was already received).
handleNewUpstreamNetworkState(ns);
} else if (mCurrentUpstreamIface == null) {
// There are no available upstream networks, or none that
// have an IPv4 default route (current metric for success).
handleNewUpstreamNetworkState(null);
}
}发现优选网络通路后, 设置dns server地址, 并进行网络地址转换和ip转发
1 | 1. // SND -> {72 tether dns set 100 192.168.1.1} |
在调用turnOnMasterTetherSettings时, 执行了tether start命令, 该命令会启动dnsmasq服务.
dnsmasq启动:
1 | M00AE64 06-29 08:31:02.445 7182 7182 I dnsmasq : started, version 2.51 cachesize 150 |
3.3.3.3.2. 涉及到的网络知识
tether interface add usb0
将interface写到dnsmasq update_ifaces更新列表中, 使dnsmasq监听该新加的interface. 并在后面分配ip地址.
network interface add local usb0
添加usb0 dev网卡设备到 local路由表中
network route add local usb0 192.168.42.0/24
为usb0网卡在local表中添加路由规则
1
2
3
4
5sp9853i_1h10:/ # busybox route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 wlan0
192.168.42.0 0.0.0.0 255.255.255.0 U 0 0 0 usb0ipfwd enable tethering
出于安全考虑,Linux系统默认是禁止数据包转发的。
所谓转发即当主机拥有多于一块的网卡时,其中一块收到数据包,根据数据包的目的ip地址将数据包发往本机另一块网卡,该网卡根据路由表继续发送数据包。这通常是路由器所要实现的功能。
启用ip转发功能, echo 1 > IPV4_FORWARDING_PROC_FILE
1
const char IPV4_FORWARDING_PROC_FILE[] = "/proc/sys/net/ipv4/ip_forward";
tether start 192.168.42.2 192.168.42.254 192.168.43.2 192.168.43.254 192.168.44.2 192.168.44.254 192.168.45.2 192.168.45.254 192.168.46.2 192.168.46.254 192.168.47.2 192.168.47.254 192.168.48.2 192.168.48.254 192.168.49.2 192.168.49.254 192.168.137.2 192.168.137.254 192.168.0.2 192.168.0.254
启动dnsmasq服务, DNSmasq是一个小巧且方便地用于配置DNS和DHCP的工具,适用于小型网络,它提供了DNS功能和可选择的DHCP功能。它服务那些只在本地适用的域名,这些域名是不会在全球的DNS服务器中出现的。DHCP服务器和DNS服务器结合,并且允许DHCP分配的地址能在DNS中正常解析,而这些DHCP分配的地址和相关命令可以配置到每台主机中,也可以配置到一台核心设备中(比如路由器),DNSmasq支持静态和动态两种DHCP配置方式。
tether dns set 100 192.168.1.1
设置dnsmasq的dns服务器地址, 缓存DNS
nat enable usb0 wlan0 1 192.168.42.0/24
网络地址转换, pc地址在该网段内
usb0输入设备 wlan0为输出设备 1代表后面的地址组合只有一个 192.168.42.0/24代表ip地址和子网掩码
目的是修改来自源设备的数据包,使它看起来是目标设备发出的数据包
借助于NAT,私有(保留)地址的”内部”网络通过路由器发送数据包时,私有地址被转换成合法的IP地址,一个局域网只需使用少量IP地址即可实现私有地址网络内所有计算机与Internet的通信需求。
NAT将自动修改IP报文的源IP地址和目的IP地址,Ip地址校验则在NAT处理过程中自动完成。有些应用程序将源IP地址嵌入到IP报文的数据部分中,所以还需要同时对报文的数据部分进行修改,以匹配IP头中已经修改过的源IP地址。否则,在报文数据部分嵌入IP地址的应用程序就不能正常工作
ipfwd add usb0 wlan0
新加一条路由规则. 从usb0来的网转到wlan0去处理.
3.4. Usb pc-share pc互联网
1 | sudo adb shell busybox route -n |
由pc端开启网络共享,给手机使用. pc端对端地址(xp 192.168.0.1), 手机端地址(xp 192.168.0.129),
0000.log|79018| S01155E 07-23 18:12:44.495 732 945 D ConnectivityService: Switching to new default network: NetworkAgentInfo{ ni{[type: USBETHER[], state: CONNECTED/CONNECTED, reason: (unspecified), extra: (none), failover: false, available: true, roaming: false]} network{100} nethandle{432902426637} lp{{InterfaceName: usb0 LinkAddresses: [192.168.0.129/24,] Routes: [0.0.0.0/0 -> 192.168.0.1 usb0,192.168.0.0/24 -> 0.0.0.0 usb0,] DnsAddresses: [192.168.0.1,] UsePrivateDns: false PrivateDnsServerName: null Domains: null MTU: 0}} nc{[ Transports: ETHERNET Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN&FOREGROUND&NOT_SUSPENDED Unwanted: ]} Score{11} everValidated{false} lastValidated{false} created{true} lingering{false} explicitlySelected{false} acceptUnvalidated{false} everCaptivePortalDetected{false} lastCaptivePortalDetected{fals[} clat{null} ]
``: