第2章 深入理解Netd
本章所涉及的源代码文件名及位置
·main.cpp system/ netd/ main.cpp
·NetlinkManager.cpp system/ netd/ NetlinkManager.cpp
·NetlinkHandler.cpp system/ netd/ NetlinkHandler.cpp
·CommandListener.cpp system/ netd/ CommandListener.cpp
·DnsProxyListener.cpp system/ netd/ DnsProxyListener.cpp
·MDnsSdListener.cpp system/ netd/ MDnsSdListener.cpp
·getaddrinfo.c bionic/ libc/ netbsd/ net/ getaddrinfo.c
·dns_sd.h external/ mdnsresponder/ mDNSShared/ dns_sd.h
·ifc_utils.c system/ core/ libnetutils/ ifc_utils.c
·ndc.c system/ netd/ ndc.c
·SecondaryTableController.cpp
system/ netd/ SecondaryTableController.cpp
·InterfaceController.cpp system/ netd/ InterfaceController.cpp
·FirewallController.cpp system/ netd/ FirewallController.cpp
·logwrap.c system/ netd/ logwrap.c
·TetherController.cpp system/ netd/ TetherController.cpp
·SoftapController.cpp system/ netd/ SoftapController.cpp
·SystemServer.java
framework/ base/ services/ java/ com/ android/ server/ SystemServer.java
·NetworkManagementService.java
framework/ base/ services/ java/ com/ android/ server/ NetworkManagementService.java
2.1 概述
Netd是Android系统中专门负责网络管理和控制的后台daemon程序,其功能主要分三
部分。
·设置防火墙(Firewall)、网络地址转换(NAT)、带宽控制、无线网卡软接入点
(Soft Access Point)控制,网络设备绑定(Tether)等。
·Android系统中DNS信息的缓存和管理。
·网络服务搜索(Net Service Discovery,NSD)功能,包括服务注册(Service
Registration)、服务搜索(Service Browse)和服务名解析(Service Resolve)等。
Netd的工作流程和Vold类似,其工作可分成两部分。
·Netd接收并处理来自Framework层中NetworkManagementService或
NsdService的命令。这些命令最终由Netd中对应的Command对象去处理。
·Netd接收并解析来自Kernel的UEvent消息,然后再转发给Framework层中对应
Service去处理。
由上述内容可知,Netd位于Framework层和Kernel层之间,它是Android系统中网络
相关消息和命令转发及处理的中枢模块。Netd的代码量不大,难度较低,但其所涉及的相
关背景知识却比较多。本章对Netd的分析将从以下几个方面入手。
·首先介绍Netd的大体工作流程以及DNS、MDns相关的背景知识。关于Netd的工作
流程分析,读者也可参考其他资料 ① 。
·本章集中介绍Netd中涉及的Android系统中网络管理和控制的相关工具。它们是
iptables、tc和ip。
·然后介绍Netd中CommandListener的命令处理。这些命令的正常工作依赖于前面
介绍的iptables等工具。
·最后,介绍Java Framework中的NetworkManagementService服务。
提示 NsdService比较简单,感兴趣的读者不妨阅读作者的一篇博文"Android Says
Bonjour"中的2.2节“NsdService介绍”。地址位于
http:/ / blog.csdn.net/ innost/ article/ details/ 8629139。
① 可参考《深入理解Android:卷Ⅰ》第9章关于Vold的分析。
2.2 Netd工作流程
Netd进程由init进程根据init.rc的对应配置项 ① 而启动,其配置项如图2-1所示。
由图2-1可知,Netd启动时将创建三个TCP监听socket,其名称分别为netd、
dnsproxyd和mdns。
图2-1 Netd启动配置参数
根据本章后续分析,读者将会看到以下内容。
·Framework层中的NetworkManagementService和Nsd-Service将分别和netd及
mdns监听socket建立链接并交互。
·每一个调用和域名解析相关的socket API(如getaddrinfo或gethostbyname等)
的进程都会借由dnsproxyd监听socket与netd建立链接。
下面开始分析Netd进程。
① 关于init工作原理以及init.rc的分析方法,可参考《深入理解Android:卷Ⅰ》第3章
关于init进程的分析。
2.2.1 main函数分析
Netd进程的入口函数是其main函数,代码如下所示。
[–>main.cpp]
int main() {
CommandListener *cl;
NetlinkManager *nm;
DnsProxyListener *dpl;
MDnsSdListener *mdnsl;
ALOGI(“Netd 1.0 starting”);
// 为Netd进程屏蔽SIGPIPE信号
blockSigpipe();
// ①创建NetlinkManager
nm = NetlinkManager::Instance();
// ②创建CommandListener,它将创建名为"netd"的监听socket
cl = new CommandListener();
// 设置NetlinkManager的消息发送者(Broadcaster)为CommandListener。
nm->setBroadcaster((SocketListener *) cl);
// 启动NetlinkManager
nm->start();

// 注意下面这行代码,它为本Netd设置环境变量ANDROID_DNS_MODE为"local",其作用将在2.2.4节介绍
setenv(“ANDROID_DNS_MODE”, “local”, 1);
// ③创建DnsProxyListener,它将创建名为"dnsproxyd"的监听socket
dpl = new DnsProxyListener();
dpl->startListener();
// ④创建MDnsSdListener并启动监听,它将创建名为"mdns"的监听socket
mdnsl = new MDnsSdListener();
mdnsl->startListener();
cl->startListener();
while(1) {
sleep(1000);
}
exit(0);
}
Netd的main函数非常简单,主要是创建几个重要成员并启动相应的工作,这四个重要
成员分别如下。
·NetlinkManager:接收并处理来自Kernel的UEvent消息。这些消息经
NetlinkManager解析后将借助它的Broadcaster(也就是代码中为NetlinkManager设置
的CommandListener)发送给Framework层的NetworkManagementService。
·CommandListener、DnsProxyListener、MDnsSdListener:分别创建名
为"netd"、“dnsproxyd”、“mdns"的监听socket,并处理来客户端的命令。
下面将分别讨论这四位成员的作用。
2.2.2 NetlinkManager分析
NetlinkManager(以后简称NM)主要负责接收并解析来自Kernel的UEvent消息。
其核心代码在start函数中,如下所示。
[–>NetlinkManager.cpp::start]
int NetlinkManager::start() {
// 创建接收NETLINK_KOBJECT_UEVENT消息的socket,其值保存在mUeventSock中
// 其中,NETLINK_FORMAT_ASCII代表UEvent消息的内容为ASCII字符串
mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT,
0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII);
// 创建接收RTMGPR_LINK消息的socket,其值保存在mRouteSock中
// 其中,NETLINK_FORMAT_BINARY代表UEvent消息的类型为结构体,故需要进行二进制解析
mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE, RTMGRP_LINK,
NetlinkListener::NETLINK_FORMAT_BINARY);
// 创建接收NETLINK_NFLOG消息的socket,其值保存在mQuotaSock中
mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG,
NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY);
return 0;
}
NM的start函数主要是向Kernel注册三个用于接收UEvent事件的socket,这三个
UEvent [1][2] 分别对应于以下内容。
·NETLINK_KOBJECT_UEVENT:代表kobject事件,由于这些事件包含的信息由
ASCII字符串表达,故上述代码中使用了NETLINK_FOMRAT_ASCII。它表示将采用字
符串解析的方法去解析接收到的UEvent消息。kobject一般用来通知内核中某个模块的加
载或卸载。对于NM来说,其关注的是/ sys/ class/ net下相应模块的加载或卸载消息。
·NETLINK_ROUTE:代表Kernel中routing或link改变时对应的消息。
NETLINK_ROUTE包含很多子项,上述代码中使用了RTMGRP_LINK项。二者结合起来
使用,表示NM希望收到网络链路断开或接通时对应的UEvent消息(笔者在Ubuntu PC上
测试过,当网卡上拔掉或插入网线时,会触发这些UEvent消息的发送)。由于对应UEvent
消息内部封装了nlmsghdr等相关结构体,故上述代码使用了
NETLINK_FORMAT_BINARY来指示解析UEvent消息时将使用二进制的解析方法。
·NETLINK_NFLOG:和2.3.6节介绍的带宽控制有关。Netd中的带宽控制可以设置
一个预警值,当网络数据超过一定字节数就会触发Kernel发送一个警告。该功能属于
iptables的扩展项,但由于iptables的文档更新速度较慢(这也是很多开源项目的一大弊
端),笔者一直未能找到相关的正式说明。值得指出的是,上述代码中有关
NETLINK_NFLOG相关socket的设置并非所有Kernel版本都支持。同
时,NFLOG_QUOTA_GROUP的值是直接定义在NetlinkManager.cpp中的,而非和其他
类似系统定义一样定义在系统头文件中,这也表明NFLOG_QUOTA_GROUP的功能比较
新。
提示 读者可通过在Linux终端中执行man PF_LINK得到有关NETLINK的详细说
明。
上述start函数将调用setupSocket创建用于接收UEvent消息的socket以及一个解析
对象NetlinkHandler。setupSocket代码本身比较简单,此处就不展开分析。
下面来看NM及其家族成员,它们之间的关系如图2-2所示。
图2-2 NM家族成员
由图2-2可知:
·NetlinkHandler和CommandListener均间接从SocketListener派生。其
中,NetlinkHandler收到的socket消息将通过onEvent回调处理。
·结合前文所述,NetlinkManager分别注册了三个用于接收UEvent的socket,其对
应的NetlinkHandler分别是mUeventHandler、mRouteHandler和mQuotaHandler。
·NetlinkHandler接收到的UEvent消息会转换成一个NetlinkEvent对象。
NetlinkEvent对象封装了对UEvent消息的解析方法。对于NETLINK_FOMRAT_ASCII
类型,其parseAsciiNetlinkMessage函数会被调用,而对于
NETLINK_FORMAT_BINARY类型,其parseBinaryNetlinkMessage函数会被调用。
·NM处理流程的输入为一个解析后的NetlinkEvent对象。NM完成相应工作后,其处
理结果将经由mBroadcaster对象传递给Framework层的接收者,也就是
NetworkManagementService。
·CommandListener从FrameworkListener派生,而FrameworkListener内部有一
个数组mCommands,用来存储注册到FrameworkListener中的命令处理对象。
下面简单了解NetlinkHandler的onEvent函数,由于其内部已针对不同属性的
NetlinkEvent进行了分类处理,故浏览这段代码能对前文所述不同UEvent消息的作用加深
理解。
[–>NetlinkHandler.cpp::onEvent]
void NetlinkHandler::onEvent(NetlinkEvent *evt) {
const char *subsys = evt->getSubsystem();

// 处理对应NETLINK_KOBJECT_UEVENT和NETLINK_ROUTE的信息
if (!strcmp(subsys, “net”)) {
int action = evt->getAction();
const char *iface = evt->findParam(“INTERFACE”); // 查找消息中携带的网络设备名
if (action == evt->NlActionAdd) {
notifyInterfaceAdded(iface);// 添加NIC(Network Interface Card)的消息
} else if (action == evt->NlActionRemove) {
notifyInterfaceRemoved(iface); // NIC被移除的消息
} else if (action == evt->NlActionChange) {
evt->dump();
notifyInterfaceChanged(“nana”, true); // NIC变化消息
} else if (action == evt->NlActionLinkUp) {// 下面两个消息来自NETLINK_ROUTE
notifyInterfaceLinkChanged(iface, true); // 链路启用(类似插网线)
} else if (action == evt->NlActionLinkDown) {
notifyInterfaceLinkChanged(iface, false); // 链路断开(类似拔网线)
}
} else if (!strcmp(subsys, “qlog”)) { // 对应NETLINK_NFLOG
const char *alertName = evt->findParam(“ALERT_NAME”);
const char *iface = evt->findParam(“INTERFACE”);
notifyQuotaLimitReached(alertName, iface);// 当数据量超过预警值,则会收到该通知
} else if (!strcmp(subsys, “xt_idletimer”)) {
// 这和后文的idletimer有关,用于跟踪某个NIC的工作状态,即"idle"或"active”
// 检测时间按秒计算
int action = evt->getAction();
const char *label = evt->findParam(“LABEL”);
const char *state = evt->findParam(“STATE”);
if (label == NULL) {
label = evt->findParam(“INTERFACE”);
}
if (state)
notifyInterfaceClassActivity(label, !strcmp(“active”, state));
}

}
由上边代码可知,NETLINK_KOBJECT_UEVENT和NETLINK_ROUTE主要反映网
络设备的事件和状态,包括NIC的添加、删除和修改,以及链路的连接状态等。
NETLINK_NFLOG用于反映设置的log是否超过配额。另外,上边代码中还处理了
xt_idletimer的uevent消息,它和后文介绍的IdleTimerCmd有关,主要用来监视网络设
备的收发工作状态。当对应设备工作或空闲时间超过设置的监控时间后,Kernel将会发送
携带其状态(idle或active)的UEvent消息。
图2-3所示为NetlinkHandler的工作流程。
图2-3 NM工作流程
由图2-3可知,NM创建NetlinkHandler后,工作便转交给NetlinkHandler来完成,
而每个NetlinkHandler对象均会单独创建一个线程用于接收socket消息。当Kernel发送
UEvent消息后,NetlinkHandler便从select调用中返回,然后调用其onDataAvailable函
数,该函数内部会创建一个NetlinkEvent对象。NetlinkEvent对象根据socket创建时指
定的解析类型去解析来自Kernel的UEvent消息。最终NetlinkHandler的onEvent将被调
用,不同的UEvent消息将在此函数中进行分类处理。NetlinkHandler最终将处理结果经由
NM内部变量mBroadcaster转发给NetworkManagementService。
提醒 请读者结合上文所述流程自行研读相关代码。
2.2.3 CommandListener分析
Netd中第二个重要成员是CommandListener(以后简称CL),其主要作用是接收来
自Framework层NetworkManageService的命令。从角色来看,CL仅是一个Listener。
它在收到命令后,只是将它们转交给对应的命令处理对象去处理。CL内部定义了许多命
令,而这些命令都有较深的背景知识。本节以分析CL的工作流程为主,而相关的命令处理
则放到后文分析。
图2-4所示为CL中的Command对象及对应的Controller对象。
图2-4 CL中的命令及控制类
由图2-4可知,CL定义了11个和网络相关的Command类。这些类均从NetdCommand
派生(注意,为保持绘图简洁,这11个Command的派生关系由1个派生箭头表达)。CL还
定义了10个控制类,这些控制类将和命令类共同完成相应的命令处理工作。
结合图2-2中对NM家族成员的介绍,CL创建时,需要注册自己支持的命令类。这部分
代码在其构造函数中实现,代码如下所示。
[–>CommandListener::CommandListener构造函数]
CommandListener::CommandListener() :
FrameworkListener(“netd”, true) {
registerCmd(new InterfaceCmd()); // 注册11个命令类对象
registerCmd(new IpFwdCmd());
registerCmd(new TetherCmd());
registerCmd(new NatCmd());
registerCmd(new ListTtysCmd());
registerCmd(new PppdCmd());
registerCmd(new SoftapCmd());
registerCmd(new BandwidthControlCmd());
registerCmd(new IdletimerControlCmd());
registerCmd(new ResolverCmd());
registerCmd(new FirewallCmd());
// 创建对应的控制类对象
if (!sSecondaryTableCtrl)
sSecondaryTableCtrl = new SecondaryTableController();
if (!sTetherCtrl)
sTetherCtrl = new TetherController();
if (!sNatCtrl)
sNatCtrl = new NatController(sSecondaryTableCtrl);
if (!sPppCtrl)
sPppCtrl = new PppController();
if (!sSoftapCtrl)
sSoftapCtrl = new SoftapController();
if (!sBandwidthCtrl)
sBandwidthCtrl = new BandwidthController();
if (!sIdletimerCtrl)
sIdletimerCtrl = new IdletimerController();
if (!sResolverCtrl)
sResolverCtrl = new ResolverController();
if (!sFirewallCtrl)
sFirewallCtrl = new FirewallController();
if (!sInterfaceCtrl)
sInterfaceCtrl = new InterfaceController();
// 其他重要工作,后文再分析
}
由于CL的间接基类也是SocketListener,所以其工作流程和NetlinkHandler类似。
图2-5给出了CL的工作流程。
图2-5 CL的工作流程
图2-5中,假设Client端发送的命令名是"nat",当CL收到这个命令后,首先会从其构
造函数中注册的那些命令对象中找到对应该名字(即nat)的命令对象,其结果就是图中的
NatCmd对象。而该命令最终的处理工作将由此NatCmd对象的runCommand函数完成。
2.2.4 DnsProxyListener分析
DnsProxyListener和Android系统中的DNS管理有关。什么是DNS呢?Android系统
中DNS又有什么特点呢?来看下文。
1.Android DNS简介 [3]
DNS(Domain Name System,域名系统)主要作用是在域名和IP地址之间建立一种
映射。简单来说,DNS的功能类似于电话簿,它可将人名映射到相应的电话号码。在DNS
中,人名就是域名,电话号码就是IP地址。域名系统的管理由DNS服务器来完成。全球范
围内的DNS服务器共同构成了一个分布式的域名-IP数据库。
对使用域名来发起网络操作的网络程序来说,其域名解析工作主要分两步。
1)将域名转换成IP。由于域名和IP的转换关系存储在DNS服务器上,所以该网络程序
要向DNS服务器发起请求,以获取域名对应的IP地址。
2)DNS服务器根据DNS解析规则解析并得到该域名对应的IP地址,然后返回给客户
端。在DNS中,每一个域名和IP的对应关系称为一条记录。客户端一般会缓存这条记录以
备后续之用。
提醒 DNS解析规则比较复杂,感兴趣的读者可研究DNS的相关协议。
对软件开发者来说,常用的域名解析socket API有两个。
·getaddrinfo:根据指定的host名或service名得到对应的IP地址(由结构体
addrinfo表达)。
·getnameinfo:根据指定的IP地址(由结构体sockaddr表达)得到对应的host或
service的名称。
Android中,这两个函数均由Bionic C实现。其代码实现基于NetBSD的解析库
(resolver library),并经过一些修改。这些修改如下。
·没有实现name-server-switch功能。这是为了保持Bionic C库的轻便性而做的裁
剪。
·DNS服务器的配置文件由/ etc/ resolv.conf变成/ system/ etc/ resolv.conf①。在
Android系统中,/ etc目录实际上为/ system/ etc目录的链接。resolv.conf存储的是DNS服
务器的IP地址。
·系统属性中保存了一些DNS服务器的地址,它们通过诸如"net.dns1"或"net.dns2"之
类的属性来表达。这些属性由dhcpd进程或其他系统模块负责维护。
·每个进程还可以设置进程特定的DNS服务器地址,它们通过诸如"net.dns1.
“或"net.dns2.“的系统属性来表达。
·不同的网络设备也有对应的DNS服务器地址,例如通过wlan接口发起的网络操作,
其对应的DNS服务器由系统属性"net.wlan.dns1"表示。
图2-6所示为三星Galaxy Note 2中有关dns的信息。由图可知,系统中有些进程有自
己特定的DNS服务器。不同网络设备也设置了对应的DNS服务器地址。
图2-6 net.dns设置
2.getaddrinfo函数分析
本节介绍Android中getaddrinfo的实现,我们将只关注Android对其做的改动。
[–>getaddrinfo.c::getaddrinfo]
int getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res)
{
…// getaddrinfo的正常处理
// Android平台的特殊定制
if (android_getaddrinfo_proxy(hostname, servname, hints, res) == 0) {
return 0;
}
…// 如果上述函数处理失败,则继续getaddrinfo的正常处理
return error
}
由上述代码可知,Android平台中的getaddrinfo会调用其定制的
android_getaddrinfo_proxy函数完成一些特殊操作,该函数的实现如下所示。
[–>getaddrinfo.c::android_getaddrinfo_proxy]
static int android_getaddrinfo_proxy(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo *res)
{

// 取ANDROID_DNS_MODE环境变量。只有Netd进程设置了它
const char
cache_mode = getenv(“ANDROID_DNS_MODE”);

// 由于Netd进程设置了此环境变量,故Netd进程调用getaddrinfo将不会采用这套定制的方法
if (cache_mode != NULL && strcmp(cache_mode, “local”) == 0) {
return -1;
}
// 获取本进程对应的DNS地址
snprintf(propname, sizeof(propname), “net.dns1.%d”, getpid());
if (__system_property_get(propname, propvalue) > 0) {
return -1;
}
// 建立和Netd中DnsProxyListener的连接,将请求转发给它去执行
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
return -1;
}

strlcpy(proxy_addr.sun_path, “/dev/socket/dnsproxyd”,
sizeof(proxy_addr.sun_path));
…// 发送请求,处理回复等
return -1;
}
由上述代码可知:
·当Netd进程调用getaddrinfo时,由于其设置了ANDROID_DNS_MODE环境变
量,所以该函数会继续原来的流程。
·当非Netd进程调用getaddrinfo函数时,首先会开展android_getaddrinfo_proxy
中的工作,即判断该进程是否有定制的DNS服务器,如果没有它将和位于Netd进程中的
dnsproxyd监听socket建立连接,然后把请求发给DnsProxyListener去执行。
3.DnsProxyListener命令
下面介绍DnsProxyListener(以后简称DPL),图2-7所示为其家族成员示意图。
图2-7 DPL家族成员
由图2-7可知,DPL仅定义了两个命令。
·GetAddrInfoCmd:和Bionic C库的getaddrinfo函数对应。
·GetHostByAddrCmd:和Bionic C库的gethostbyaddr函数对应。
这个两条命令的处理比较简单,此处不展开详细的代码。为方便读者理解,我们将给出
调用序列图,如图2-8所示。
如图2-8所示,GetAddrInfoHandler最终的处理还是交由Bionic C的getaddrinfo函
数来完成。根据前文所述,由于Netd进程设置了ANDROID_DNS_MODE环境变量,故
Netd调用的getaddrinfo将走正常的流程。这个正常流程就是Netd进程将向指定的DNS服
务器发起请求以解析域名。
Android系统中,通过这种方式来管理DNS的好处是,所有解析后得到的DNS记录都
将缓存在Netd进程中,从而使这些信息成为一个公共的资源,最大程度做到信息共享。
图2-8 GetAddrInfoCmd处理流程
① 此处结论来自bionic/ libc/ docs/ OVERVIEW .txt文件,不过根据同目录下
CHANGES.txt的说明,resolv.conf将不再使用。
2.2.5 MDnsSdListener分析
MDnsSd是Multicast DNS Service Discovery的简称,它和Apple公司的Bonjour技
术有关,故本节将先介绍Apple Bonjour技术。
1.Apple Bonjour技术 [4][5][6]
Bonjour是法语中的Hello之意。它是Apple公司为基于组播域名服务(multicast
DNS)的开放性零配置网络标准所起的名字。使用Bonjour的设备在网络中自动组播它们自
己的服务信息并监听其他设备的服务信息,设备之间就像在打招呼,这也是该技术命名为
Bonjour的原因。Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很
容易被找到。
举一个简单的例子,在局域网中,如果要进行打印服务,就必须先知道打印服务器的IP
地址。此IP地址一般由IT部门的人负责分配,然后还得全员发邮件以公示此地址。有了
Bonjour以后,打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注
册一个打印服务,例如"print service”。当客户端需要打印服务时,会先搜索网络内部的打
印服务器。由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去
查找打印机。在Bonjour的帮助下,客户端最终能找到这台注册了"print service"名字的打
印机,并获得它的IP地址以及端口号。
从Bonjour角度来看,该技术主要解决了三个问题。
·Addressing:即为主机分配IP。Bonjour的Addressing处理比较简单,即每个主机
在网络内部的地址可选范围内找一个IP,然后查看下网络内部是否有其他主机再用。如果该
IP没有被分配的话,它将使用此IP。
·Naming:Naming解决的是host和IP地址的对应关系。Bonjour采用的是Multiple
DNS技术,即DNS查询消息将通过UDP组播方式发送。一旦网络内部某个机器发现查询的
机器名和自己设置的一样,就回复这条请求。此外,Bonjour还拓展了MDNS的用途,即除
了能查找host外,还支持对service的查找。不过,Bonjour的Naming有一个限制,即网络
内部不能有重名的host或service。
·Service Discovery:SD基于Naming工作,它使得应用程序能查找到网络内部的服
务,并解析该服务对应的IP地址和端口号。应用程序一旦得到服务的IP地址和端口号,就
可以直接和该服务建立交互关系。
Bonjour技术在Mac OS以及iTunes、iPhone上都得到了广泛应用。为了进一步推
广,Apple通过开源工程mdnsresponder将其发布。在W indows平台上,它将生成一个后
台程序mdnsresponder。在Android平台上(或者说支持POSIX的Linux平台)它是一个
名为mdnsd的程序。不过,不论是mdnsresponder还是mdnsd,应用开发者要做的仅仅是
利用Bonjour的API向它们发起服务注册、服务查询和服务解析等请求并接收来自它们的处
理结果。
下面介绍Bonjour API中使用最多的三个函数,它们分别用来服务注册、服务查询和服
务解析。理解这三个函数的功能也是理解MDnsSdListener的基础。
使用Bonjour API必须包含如下的头文件和动态库,并连接到:
#include <dns_sd.h> // 必须包含此头文件
libmdnssd.so // 链接到此so
Bonjour中,服务注册的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字符串用来描述该服务。一般都设置为空。
·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地址和端
口号。
2.MDnsSdListener详解
MDnsSdListener对应的Framework层服务为NsdService(Nsd为Network Service
Discovery的缩写),它是Android 4.1新增的一个Framework层Service。该服务的实现
比较简单,故本书不详细讨论。感兴趣的读者不妨首先阅读SDK中关于NsdService的相关
文档。
提示 SDK中有一个基于Nsd技术开发的NsdChat例程,读者也可先学习它的实现。
相关文档位置为http:/ / developer.android.com/ training/ connect-devices-
wirelessly/ nsd.html。
图2-9所示为MDnsSdListener家族成员示意图。
图2-9 MDnsSdListener家族成员
由图2-9可知:
·MDnsSdListener的内部类Monitor用于和mdnsd后台进程通信,它将调用前面提
到的Bonjour API。
·Monitor内部针对每个DNSService都会建立一个Element对象,该对象通过
Monitor的mHead指针保存在一个list中。
·Handler是MDnsSdListener注册的Command。
下面简单介绍MDnsSdListener的运行过程,主要工作可分成三步。
1)Netd创建MDnsSdListener对象,其内部会创建Monitor对象,而Monitor对象将
启动一个线程用于和mdnsd通信,并接收来自Handler的请求。
2)NsdService启动完毕后将向MDnsSdListener发送"start-service"命令。
3)NsdService响应应用程序的请求,向MDnsSdListener发送其他命令,例
如"discovery"等。Monitor将最终处理这些请求。
先来看第一步,当MDnsSdListener构造时,会创建一个Monitor对象,代码如下所
示。
[–>MDnsSdListener.cpp::Monitor:Monitor]
MDnsSdListener::Monitor::Monitor() {
mHead = NULL;
pthread_mutex_init(&mHeadMutex, NULL);
// 创建两个socket,用于接收MDnsSdListener对象的指令
socketpair(AF_LOCAL, SOCK_STREAM, 0, mCtrlSocketPair);
// 创建线程,线程函数是threadStart,其内部会调用run
pthread_create(&mThread, NULL, MDnsSdListener::Monitor::threadStart, this);
}
Monitor的threadStart线程将调用其run函数,该函数通过poll方式侦听包括
mCtrlSocketPair在内的socket信息。这部分代码属于基本的Linux socket编程,本书不
开展深入讨论。
当NsdService发送"start-service"命令后,Handler的runCommand将执行Monitor
的startService函数,代码如下所示。
[–>MDnsSdListener.cpp::Monitor:startService]
int MDnsSdListener::Monitor::startService() {
int result = 0;
char property_value[PROPERTY_VALUE_MAX];
pthread_mutex_lock(&mHeadMutex);
/

MDNS_SERVICE_STATUS是一个字符串,值为"init.svc.mdnsd",在init.rc配置文件中,mdnsd是一个
service,而"init.svc.mdnsd"将记录mdnsd进程的运行状态。
*/
property_get(MDNS_SERVICE_STATUS, property_value, “”);
if (strcmp(“running”, property_value) != 0) {
// 如果mdnsd的状态不为"running",则通过设置"ctl.start"命令启动mdnsd
property_set(“ctl.start”, MDNS_SERVICE_NAME);
// 如果mdnsd成功启动,则属性值变成"running"
wait_for_property(MDNS_SERVICE_STATUS, “running”, 5);
result = -1;
} else {
result = 0;
}
pthread_mutex_unlock(&mHeadMutex);
return result;
}
startService的实现比较有趣,充分利用了init的属性控制以启动mdnsd进程。
当NsdService发送注册服务请求时,Handler的serviceRegister函数将被调用,代码
如下所示。
[–>MDnsSdListener.cpp::Handler:serviceRegister]
void MDnsSdListener::Handler::serviceRegister(SocketClient *cli, int requestId,
const char *interfaceName, const char *serviceName, const char *serviceType,
const char *domain, const char *host, int port, int txtLen, void *txtRecord) {
Context *context = new Context(requestId, mListener);
DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
port = htons(port);

DNSServiceFlags nativeFlags = 0;
int interfaceInt = ifaceNameToI(interfaceName);
// 调用Bonjour API DNSServiceRegister,并注册回调函数MDnsSdListenerRegisterCallback
DNSServiceErrorType result = DNSServiceRegister(ref, interfaceInt,
nativeFlags, serviceName, serviceType, domain, host, port,
txtLen, txtRecord, &MDnsSdListenerRegisterCallback, context);
if (result != kDNSServiceErr_NoError) {
…// 错误处理
}
// 通知Monitor对象进行rescan,请读者自行研究该函数
mMonitor->startMonitoring(requestId);
cli->sendMsg(ResponseCode::CommandOkay, “serviceRegister started”, false);
return;
}
DNSServiceRegister内部将把请求发送给mdnsd去处理,处理的结果通过
MDnsSdListener-RegisterCallback返回,该函数代码如下所示。
[–>MDnsSdListener.cpp::MDnsSdListenerRegisterCallback]
void MDnsSdListenerRegisterCallback(DNSServiceRef sdRef, DNSServiceFlags flags,
DNSServiceErrorType errorCode, const char *serviceName, const char *regType,
const char *domain, void *inContext) {
MDnsSdListener::Context *context =
einterpret_cast<MDnsSdListener::Context *>(inContext);
char *msg;
int refNumber = context->mRefNumber;
if (errorCode != kDNSServiceErr_NoError) {
…// 错误处理
} else {
char *quotedServiceName = SocketClient::quoteArg(serviceName);
asprintf(&msg, “%d %s”, refNumber, quotedServiceName);
free(quotedServiceName);
// 将处理结果返回给NsdService
context->mListener->sendBroadcast(ResponseCode::ServiceRegistrationSucceeded,
msg,false);
}
free(msg);
}
提示 Netd的工作流程相关代码相对简单,处理流程也比较固定。
1)NM接收Kernel的UEvent消息,然后转发给Framework层的客户端。
2)CL、DPL以及MDnsSdListener接收来自客户端的请求并处理它们。
对Android中DNS的管理以及Apple Bonjour技术感兴趣的读者不妨阅读章末列出的参
考资料以加深理解。
2.3 CommandListener中的命令
CL一共定义了11个命令,这些命令充分反映了Netd在Android系统中网络管理和控制
方面的职责。本节首先介绍Linux系统中常用的三个网络管理工具,然后再分类介绍CL中
的相关命令。
2.3.1 iptables、tc和ip命令
网络管理和控制一直是一项比较复杂和专业的工作,由于Linux系统中原本就有一些强
大的网络管理工具,故Netd也毫不犹豫充分利用了它们。目前Netd中最依赖三个网络管控
工具,即iptables、tc和ip。
1.iptables命令 [7][8][9]
iptables是Linux系统中最重要的网络管控工具。它与Kernel中的netfilter模块配合工
作,其主要功能是为netfilter设置一些过滤(filter)或网络地址转换(NAT)的规则。当
Kernel收到网络数据包后,将会依据iptables设置的规则进行相应的操作。举个最简单的
例子,可以利用iptables设置这样一条防火墙规则:丢弃来自IP地址为192.168.1.108的所
有数据包。
(1)iptables原理
iptables的语法比较复杂,但工作原理较易理解。清楚iptables的前提是理解它的表
(Table)、链(Chain)和规则(Rule)。三者关系如图2-10所示。
图2-10 iptables三要素关系
由图2-10可知:
·iptables内部(其实是Kernel的netfilter模块)维护着四个Table,分别是filter、
nat、mangle和raw,它们对应着不同的功能,稍后将详细介绍它们的作用。
·Table中定义了Chain。一个Table可以支持多个Chain,Chain实际上是Rule的集
合,每个Table都有默认的Chain。例如filter表默认的Chain有INPUT、OUTPUT、
FORW ARD。用户可以自定义Chain,也可修改Chain中的Rule。稍后将介绍不同Table中
默认Chain方面的知识。
·Rule就是iptables工作的规则。首先,系统将检查要处理的数据包是否满足Rule设
置的条件,如果满足则执行Rule中设置的目标(Target),否则继续执行Chain中的下一
条Rule。
由前述内容可知,iptables中的Table和Chain是理解iptables工作的关键。表2-1总
结了iptables中默认Table及Chain的相关内容。
由表2-1可知,有些Table的默认Chain具有相同的名字,导致我们理解起来有些困
难。为此,读者必须结合图2-11所示的iptables数据包处理流程图来理解前述内容。由图
可知,不同Table和Chain在此处理流程中起着不同的作用。
图2-11 iptables数据包处理流程
(2)iptables Target和常用参数
iptables中的Rule有四个默认定义的Target,如下。
·ACCEPT:接收数据包。
·DROP:直接丢弃数据包。没有任何信息会反馈给数据源端。
·RETURN:返回到调用Chain,略过后续的Rule处理。
·QUEUE:数据返回到用户空间去处理。
提示 iptables的扩展Target还支持REJECT。相比DROP而言,REJECT会发送反
馈信息给数据源端,如主机不可达之类(icmp-host-unreachable)的信息。目前只有
INPUT、OUTPUT、FORW ARD以及被这三个链调用的自定义链支持REJECT。
iptables有很多参数,此处先介绍一些常用参数。
-t:指定table。如果不带此参数,则默认为filter表。
-A,–append chain rule-specification:在指定Chain的末尾添加一条Rule,rule-specification指明该Rule的内容。
-D,–delete chain rule-specification:删除指定Chain中满足rule-specification的那条Rule。
-I,–insert chain[rule num]rule-specification:为指定Chain插入一条Rule,位置由rule num指定。如果没有该参数,则默认加到
Chain的头部。
-N:创建一条新Chain。
-L,–list:显示指定Table的Chain和Rule的信息。
Rule-specification描述该Rule的匹配条件以及目标动作,它也有一些参数来指明这
些信息。
-i:指定接收数据包的网卡名,如eth0、eth1等。
-o:指定发出数据包的网卡名。
-p:指定协议,如tcp、udp等。
-s,–source address[/mask]:指定数据包的源IP地址。
-j,–jump target:跳转到指定目标,如ACCEPT、DROP等。
以前文提到的设置防火墙为例,其对应的iptables设置参数如下。
iptables-t filter-A INPUT-s 192.168.1.108-j DROP
如果仅拦截协议为tcp的数据包,则相应参数如下。
iptables-t filter-A INPUT-p tcp-s 192.168.1.108-j DROP
另外,iptables仅支持IPv4,如果需针对IPv6进行相应设置,则要使用ip6tables工
具。
提示 iptables的用法非常灵活,如果没有长期的使用经验,将很难理解它们的真正作
用。
2.tc命令 [10][11][12][13]
TC是Traffic Control的缩写。在Linux系统中,流量控制是通过建立数据包队列
(Queue),并控制各个队列中数据包的发送方式来实现的。Linux流量控制的基本原理如
图2-12所示,该图描述了Linux系统中网络数据的处理流程。
图2-12 网络数据处理流程
由图2-12可知:
·接收包从输入接口(Input Interface)进来后,将经过输入流量限制(Ingress
Policing)以丢弃不符合规定的数据包。而符合规定的数据包则交给输入多路选择器
(Input De-Multiplexing)进行判断选择。
·输入多路选择器的选择结果是,如果数据包的目的是本机,将该包送给上层处理,否
则需要将数据包交到转发块(Forwarding Block)去处理。转发块同时也接收来自本机上
层(TCP、UDP等)产生的包。
·转发块通过查看路由表,决定处理包的下一跳目的地。然后,转发块对数据包进行排
列整合以便将它们送到对应的输出接口(Output Interface)。
一般而言,我们只能限制本机网卡往外发送的数据包,而不能限制网卡接收的数据包。
Linux中的流量控制就是在数据包通过输出接口时,通过改变发送次序等方式来实现控制传
输速率的。
提示 也可通过IFB设备在输入接口进行流量控制。相关内容见2.3.3节。
在具体实现中,系统会建立许多队列及对应的队列规则(queuing discipline,简称
qdisc)。目前系统包括的qdisc分为两类。
无分类的队列规则(Classless qdisc):该规则对进入网卡的数据包不加区分,统一
对待。使用这种规定的处理能够对数据包重新编排、延迟或丢弃。简而言之,这种类型是针
对整个网卡的流量进行调整。常用的qdisc如下。
·fifo(First In First Out,先进先出队列):最简单的控制。
·SFQ(Stochastic Fairness Queuing,随机公平队列):对发送会话进行重排,这
样每个发送会话都可以公平地发送数据)。
·RED(Random Early Detection,前向随机丢包):用于模拟流量接近带宽限制时
丢包的情况。
·TBF(Token Bucket Filter,令牌桶过滤器):可较好地使得流量减低到预设值。
适合高带宽的环境。这类qdisc使用的流量控制手段主要是排序、限速和丢包。
分类的队列规定(Classfull qdisc):它对进入网络设备的数据包根据不同的需求以
分类的方式区分对待。数据包进入一个分类的队列后,它就需要被送到某一个类中进行分类
处理。对数据包进行分类的工具是过滤器(filter)。过滤器会返回一个决定,qdisc根据该
决定把数据包送入相应的类进行排队。一个类可以包含多个子类,每个子类可再次使用它们
的过滤器进行进一步的分类。当所有分类都处理完后,数据包才进入该类对应的队列排队。
简单言之,如果要利用tc进行流量控制,其主要工作将包含建立队列、建立分类和建立
过滤器三个方面,一般的步骤如下。
1)针对网络物理设备(如以太网卡eth0)绑定一个队列QDisc;
2)在该队列上建立分类class;
3)为每一分类建立一个基于路由的过滤器filter;
4)最后与过滤器相配合,建立特定的路由表。
提示 tc命令所涉及的流量控制方面的知识相当复杂,感兴趣的读者可根据章末列出的
参考资料做进一步的深入研究。
3.ip命令 [14]
ip命令是Linux系统中另一个强大的网络管理工具,主要功能如下。
·可替代ifconfig命令。即通过ip工具可管理系统中的网络接口,包括配置并查看网络
接口情况、使能或禁止指定网络接口。
·可替代route命令。即ip工具支持设置主机路由、网络路由、网关参数等。
·可替代arp命令。即ip工具支持查看、修改和管理系统的ARP缓存等。
ip命令的语法为:
ip [OPTIONS] OBJECT [COMMAND [ARGUMENTS]]
例如为网络设备进行配置的ip命令语法如下:
ip addr[add|del]IFADDR dev STRING
//接口eth0赋予地址192.168.0.1,掩码是255.255.255.0(24代表掩码中1的个数)
//广播地址是192.168.0.255
ip addr add 192.168.0.1/24 broadcast 192.168.0.255 label eth0 dev eth0
提示 本节对Linux系统中常用的三个网络管理工具iptables、tc和ip命令进行了一些
简单介绍。其中,iptables用于管理数据包过滤、NAT等方面的工作。tc用于流量控制,其
背后涉及的知识较为复杂。ip命令可替代ifconfig、route和arp等命令。ip命令的路由控制
示例将在2.3.3节介绍。
2.3.2 CommandListener构造函数和测试工具ndc
本节将介绍CL的构造函数以及Netd的测试工具ndc。
1.CL构造函数
在前文的2.2.3节中已经介绍了CL构造函数的前半部分,下面接着介绍CL构造函数的后
半部分,代码如下所示。
[–>CommandListener::CommandListener构造函数]
CommandListener::CommandListener() :
FrameworkListener(“netd”, true) {
…// 创建命令和命令控制对象
// 初始化iptables中的各个Table及相应Chain和Rules
// createChildChains第一个参数用于指明针对IPv4还是IPv6
createChildChains(V4V6, “filter”, “INPUT”, FILTER_INPUT);
createChildChains(V4V6, “filter”, “FORWARD”, FILTER_FORWARD);
createChildChains(V4V6, “filter”, “OUTPUT”, FILTER_OUTPUT);
createChildChains(V4V6, “raw”, “PREROUTING”, RAW_PREROUTING);
createChildChains(V4V6, “mangle”, “POSTROUTING”, MANGLE_POSTROUTING);
createChildChains(V4, “nat”, “PREROUTING”, NAT_PREROUTING);
createChildChains(V4, “nat”, “POSTROUTING”, NAT_POSTROUTING);
// 4.2以后,Netd允许OEM厂商可自定义一些规则
// 这些规则在/system/bin/oem-iptables-init.sh文件中保存
setupOemIptablesHook();
// 初始化iptables中的一些chain,以及初始化路由表
sFirewallCtrl->setupIptablesHooks();
sNatCtrl->setupIptablesHooks();
sBandwidthCtrl->setupIptablesHooks();
sIdletimerCtrl->setupIptablesHooks();
// 初始时,Netd将禁止带宽控制功能
sBandwidthCtrl->enableBandwidthControl(false);
由上述代码可知,CL构造函数的后半部分工作主要是利用iptables等工具创建较多的
Chain和Rule,以及对某些命令控制对象进行初始化。
本节将重点关注iptables执行后的效果。图2-13所示为CL构造后,iptable中filter表
内所创建的Chain和Rule。
图2-13中列出的是filter表中部分Chain的截图,其中,target是目标名,prot是
protocol之意,opt是选项,source和destination分别表示数据包的源和目标地址。
·bw_INPUT、bw_OUTPUT和bw_FORW ARD Chain用于带宽(Bandwidth)控
制。
·fw_INPUT、fw_OUTPUT和fw_FORW ARD用于防火墙(Firewall)控制。
·natctrl_FORW ARD用于网络地址转换(NAT)控制。
·oem_fwd、oem_out用于OEM厂商自定义的控制。
图2-13 CL创建后filter表的内容
2.ndc测试工具
ndc是Android为Netd提供的一个测试工具。其主要功能有:
·监视Netd中发生的事情。
·支持通过命令行发送命令给Netd去执行。
相信读者很轻松就能想到ndc的实现原理,其实它就是连接上位于netd进程中
的"netd"监听socket,然后从Netd接收信息或发送命令给Netd。
图2-14为使用ndc monitor选项监控Galaxy Note 2打开W i-Fi功能时得到的输出。
图2-14 ndc monitor执行结果
利用ndc来监视Netd的工作状况是一个简单高效的方法。另外,还可利用ndc来测试
CommandListener中所支持的各种命令。这对于网络相关模块的HAL层开发者来说无疑是
一个很大的帮助。为了方便读者理解,本节下文也将利用ndc来展示命令执行的结果。
在正式介绍CL中的命令对象之前,先介绍这些命令对象处理的通用流程。
如图2-4所示,命令对象的真正控制函数是runCommand,而绝大部分命令的
runCommand函数都有类似如下的代码结构(此处以InterfaceCmd为例)。
[–>CommandListener.cpp::InterfaceCmd:runCommand]
int CommandListener::InterfaceCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
if (argc < 2) {// 先做参数检查
cli->sendMsg(ResponseCode::CommandSyntaxError, “Missing argument”, false);
return 0;
}
// 然后分别处理自己支持的各种命令选项
if (!strcmp(argv[1], “list”)) {
…// 处理"list"选项
} else if (!strcmp(argv[1], “readrxcounter”)) {
…// 处理"readrxcounter"选项
}
…// 处理其他选项
}
由上述InteraceCmd的处理函数可知,runCommand的处理流程如下。
1)首先参数检查,一般是检查参数个数是否正确。
2)然后根据不同的选项进行对应的处理。
提示 接下来分析CL中各个命令,由于其中涉及较多的知识,因此单独增加“背景知
识介绍”小节以帮助读者更好地理解它们。
2.3.3 InterfaceCmd命令
InterfaceCmd用来管理和控制系统中的网络设备,其支持较多的控制选项。另
外,InterfaceCmd除了和控制对象InterfaceController交互外,还会和
ThrottleController、SecondaryTableController交互。InterfaceCmd涉及较多的背景
知识,本节先集中介绍这部分内容。
1.背景知识介绍
InterfaceCmd涉及三个重要知识点,分别是IFB设备、Netdevice编程和Linux策略路
由管理。
(1)IFB设备 [13]
IFB(Intermediate Functional Block)是IMQ(InterMediate Queuing)的替代
者,二者都是Linux为更好地完成流量控制而实现的虚拟设备。相比IMQ而言,IFB的实现
代码更少,对SMP(多核)系统支持的也更好。
为什么需要IFB设备呢?2.3.1节介绍tc命令的时候提到,Linux中丰富的流量控制手段
和规则都是针对出口流量的,即大多数排队规则(qdisc)都是用于输出方向的。而输入方
向主要是入口流量限制,只有一个排队规则,即ingress qdisc。有没有办法让输入流量也
能像输出流量那样得到更多的控制呢?
于是,系统新增了一种方式,用于重定向incoming packets。通过ingress qdisc把输
入方向的数据包重定向到虚拟设备IFB,而在IFB的输出方向配置多种qdisc,就可以达到对
输入方向的流量做队列调度的目的。
图2-15所示为加上IFB后整个流量控制示意图。系统中一共有两个IFB设备,IFB
Device 0用于输入流量控制,IFB Device 1用于输出流量控制。
图2-15 增加IFB设备后的流量控制
(2)Netdevice编程 [15]
Netdevice是Linux平台中用于直接针对底层网络设备编程的一套接口,其使用方法很
简单,就是利用socket句柄和ioctl函数来操作指定的网络设备。常用的数据结构如下所
示。
#include <net/if.h>
struct ifreq {
char ifr_name[IFNAMSIZ]; // 指定要操作的NIC名
union {// 一个联合体,可以设置各种参数
struct sockaddr ifr_addr; // 网卡地址
struct sockaddr ifr_dstaddr;
struct sockaddr ifr_broadaddr; // 组播地址
struct sockaddr ifr_netmask; // 网络掩码
struct sockaddr ifr_hwaddr; // MAC地址
short ifr_flags;
int ifr_ifindex;
int ifr_metric;
int ifr_mtu;
struct ifmap ifr_map;
char ifr_slave[IFNAMSIZ];
char ifr_newname[IFNAMSIZ];
char *ifr_data;
};
};
Netdevice支持一些特殊的ioctl参数,表2-2展示了几个常见的参数。
关于Netdevice的详细信息,读者可通过man netdevice获得。Android为Netdevice
编程提供了一些更为简单的API,它们被封装在libnetutils中,统称为ifc_utils,下面介绍
其中的三个常用API。
使用ifc_utils之前,需调用ifc_init进行初始化,其代码如下所示。
[–>ifc_utils.c::ifc_init]
int ifc_init(void) // ifc是interface control的缩写
{
int ret;
if (ifc_ctl_sock == -1) { // 创建一个socket句柄
ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0);
…// 错误处理
}
ret = ifc_ctl_sock < 0 ? -1 : 0;
return ret;
}
如果要启动某个NIC(如"ifb0"设备),需调用ifc_up函数,其代码如下所示。
[–>ifc_utils.c::ifc_up]
int ifc_up(const char *name)
{
// 要启动"ifb0"设备,name设置为"ifb0"
int ret = ifc_set_flags(name, IFF_UP, 0); // 设置IFF_UP参数
return ret;
}
ifc_up函数将通过调用ifc_set_flags函数并传递IFF_UP来启动对应的设
备,ifc_set_flags的代码如下所示。
[–>ifc_utils.c::ifc_set_flags]
static int ifc_set_flags(const char *name, unsigned set, unsigned clr)
{
struct ifreq ifr;
ifc_init_ifr(name, &ifr); // 初始化ifr参数,把name复制到其ifr_name字符数组中
// 先获取该NIC以前的flag信息
if(ioctl(ifc_ctl_sock, SIOCGIFFLAGS, &ifr) < 0) return -1;
// 将新的flag加入到原有的flags中
ifr.ifr_flags = (ifr.ifr_flags & (~clr)) | set;
return ioctl(ifc_ctl_sock, SIOCSIFFLAGS, &ifr);// 将组合后的flag传递给Kernel
}
相比直接使用Netdevice来说,Android封装的ifc_utils更加直观和易用。建议有需要
的读者使用ifc_utils对NIC进行操作。
(3)Linux策略路由 [16][17]
策略路由是指系统依据网络管理员定下的一些策略对IP包进行的路由选择。例如网管可
设置这样的策略:“所有来自网A的包,选择X路径,其他选择Y路径”,或者“所有
TOS(Type Of Service,IP协议头的一部分)为A的包选择路径F,其他选择路径K”。
从Kernel 2.1开始,Linux采用了策略性路由机制。相比传统路由算法,策略路由主要
引入了多路由表及规则的概念。
传统路由算法仅使用一张路由表。但在某些情况下,系统需要使用多个路由表。例如,
一个子网通过一个路由器与外界相连。而该路由器与外界有两条线路相连,其中一条的速度
较快,另一条的速度较慢。对于子网内的大多数用户来说,由于对速度没有特殊要求,可以
让他们用速度较慢的路由;但是子网内有一些特殊用户对速度的要求较苛刻,他们需要使用
速度较快的路由。很明显,仅使用一张路由表是无法实现上述要求的。而如果根据源地址或
其他参数,对不同的用户使用不同的路由表,就可以实现这项要求。
传统Linux下配置路由的工具是route,而实现策略性路由配置的工具是iproute2工具
包,常用的命令就是ip命令。
Linux最多可以支持255张路由表,其中有4张表是内置的。
·表255:本地路由表(local table)。本地接口地址,广播地址和NAT地址都放在这
个表中。该路由表由系统自动维护,管理员不能直接修改。
·表254:主路由表(main table)。如果没有指明路由所属的表,所有的路由都默认
都放在这个表里,一般来说,传统路由工具命令(如route)所添加的路由都会加到这个表
中。一般是普通的路由。
·表253:默认路由表(default table)。一般来说默认的路由都放在这张表,但是如
果特别指明,该表也可以存储所有的网关路由。
·表0:默认保留。
2.3.1节简单介绍了ip命令,ip命令的一些具体用法如下。
// ①查看路由表的内容命令
ip route list table table_number
// ②对于路由的操作包括change、del、add、append、replace、monitor
// 向主路由表(main table)即表254添加一条路由,路由的内容是设置192.168.0.4成为网关
ip route add 0/0 via 192.168.0.4 table main
// 向路由表1添加一条路由,子网192.168.3.0(子网掩码是255.255.255.0)的网关是192.168.0.3
ip route add 192.168.3.0/24 via 192.168.0.3 table 1
在多路由表的路由体系里,所有的路由操作(如添加路由等)都需要指明要操作的路由
表。如果没有指明路由表,系统默认该操作是针对主路由表(表254)开展的。而在单表体
系里,路由操作无须指明路由表。
至此,分别介绍了IFB、Netdevice和Linux路由策略等背景知识。在接下来的分析
中,读者将看到它们在InterfaceCmd中的应用。
2.InterfaceCmd命令选项
InterfaceCmd支持较多命令选项,笔者将它们粗略地分为6类。
(1)NIC设备信息管理选项
此类选项包括以下内容。
·list:列举系统当前的网络设备。这是通过枚举/ sys/ class/ net目录下的文件名而来。
该目录下的文件(其实是一个设备文件)代表一个具体的网络设备,例如eth0、lo等。/ sys
目录是Linux设备文件系统(sysfs)的挂载点,用于Linux统一设备管理之用。
·readrxcounter和readtxcounter:这两个选项分别用于读取系统所有网络设备的接
收字节数和发送字节数等统计信息。它们都是通过读取/ proc/ net/ dev下的对应文件来实现
的。
图2-16所示为Galaxy Note 2对应文件的内容。该文件显示了系统所有网络设备的收发
字节数、包数、错误及丢包等各种统计信息。
图2-16 / proc/ net/ dev文件内容
图2-17 list和readrxcounter结果
图2-17展示了在Galaxy Note 2中用ndc执行interface list和readrxcounter选项后的
结果。其中最后一行,216为readrxcounter选项对应的返回码,0为错误码,23847为
wlan0设备的接收字节数。比较图2-16最后一行wlan0的接收字节数可知,二者完全一致。
(2)输入和输出流量控制选项
InterfaceCmd支持对指定网络设备设置流量阈值,其中包括如下。
·getthrottle:throttle是节流之意,用于流量控制。该选项支持读取发送和接收阈
值。不过目前代码中这两个值都将返回0。
·setthrottle:该选项的参数为setthrottle interface rx_kbps tx_kbps,用于控制
网卡输入和输出流量。
setthrottle的实现比较复杂,核心代码在ThrottleController类的
setInterfaceThrottle函数中。这部分代码充分利用了tc命令。以setthrottle wlan0 100
200为例,调用的命令顺序如下。
#htb是Classful类qdisc的一种,属于tc命令中较为复杂的一种用法
tc qdisc add dev wlan0 root handle 1: htb default 1 r2q 1000
tc class add dev wlan0 parent 1: classid 1:1 htb rate 200kbit
#利用ifc_utils启动IFB0设备
tc qdisc add dev ifb0 root handle 1: htb default 1 r2q 1000
tc class add dev ifb0 parent 1: classid 1:1 htb rate 100kbit
tc qdisc add dev wlan0 ingress
tc filter add dev wlan0 parent ffff: protocol ip prio 10 u32 match
u32 0 0 flowid 1:1 action mirred egress redirect dev ifb0
由上述setthrottle的实现可知,其要完成的功能很简单(就是想对wlan0设备施加输
入和输出流量控制),但实现过程却比较复杂,使用了tc命令的很多高级选项。
提示 本书非专业的Linux网络管理书籍,故此处仅向读者展示这些命令。对其背后原
理感兴趣的读者,不妨阅读本章参考资料中列出的书籍。
(3)Route控制选项
InterfaceCmd支持路由控制,选项名为"route"。它支持对Route的添加、修改和删
除。另外还支持对多路由表的配置。相关代码如下所示。
[–>CommandListener::InterfaceCmd:runCommand]
…// 略去部分代码
if (!strcmp(argv[1], “route”)) {
int prefix_length = 0;
…// 参数检测
if (!strcmp(argv[2], “add”)) {
if (!strcmp(argv[4], “default”)) {// 调用ifc_add_route为指定NIC设备添加路由
ifc_add_route(argv[3], argv[5], prefix_length, argv[7]);
}…// 错误处理
} else if (!strcmp(argv[4], “secondary”)) {// 对多路由表进行操作
return sSecondaryTableCtrl->addRoute(cli, argv[3], argv[5],
prefix_length, argv[7]);
} …// 错误处理
}
…// 处理删除、修改等
}
由上述代码可知:
·当添加的是默认路由时,直接调用ifc_add_route函数为指定设备添加一个路由。该
功能的实现利用了多种ifc_utils的ifc_add_route函数(也就是基于Netdevice封装的
API),对应的IOCTL参数为SIOCADDRT。请读者自行研究该函数的实现。
·如果是针对多路由表的"secondary",则使用SecondaryTableCtrontroller的
addRoute函数来添加路由。该函数中,SecondaryTableCtrl首先会计算一个Table索
引,然后利用ip命令将路由添加到对应的Table中。
addRoute的核心是调用modifyRoute,其代码如下所示。
[–>SecondaryTableController.cpp::modifyRoute]
int SecondaryTableController::modifyRoute(SocketClient *cli,
const char *action, char *iface,
char *dest, int prefix, char gateway, int tableIndex) {
char cmd;
if (strcmp("::", gateway) == 0) {
asprintf(&cmd, “%s route %s %s/%d dev %s table %d”,
IP_PATH, action, dest, prefix, iface,
tableIndex+BASE_TABLE_NUMBER);
} else {
// 调用ip route命令的参数
asprintf(&cmd, “%s route %s %s/%d via %s dev %s table %d”,
IP_PATH, action, dest, prefix, gateway,
iface, tableIndex+BASE_TABLE_NUMBER);
}
if (runAndFree(cli, cmd)) {
…// 错误处理
return -1;
}
// SecondaryTableControl对各个Table表的使用都有计数控制。请读者自行阅读相关代码
…// 略去相关代码
modifyRuleCount(tableIndex, action);
cli->sendMsg(ResponseCode::CommandOkay, “Route modified”, false);
return 0;
}
上述代码中,以wlan0为例,对应的ip命令情况如下。
ip route add 192.168.0.100/24 via 192.168.0.1 dev wlan0 table 78#使用路由表78
(4)Driver控制选项
该选项的名为"driver",由InterfaceController加载/ system/ lib/ libnetcmd.so对
Kernel中的Driver进行控制。而针对Driver操作的API由InterfaceController的
interfaceCmd来完成。
[–>InterfaceController.cpp]
char if_cmd_lib_file_name[] = “/system/lib/libnetcmdiface.so”;
char set_cmd_func_name[] = “net_iface_send_command”;
char set_cmd_init_func_name[] = “net_iface_send_command_init”;
char set_cmd_fini_func_name[] = “net_iface_send_command_fini”;
InterfaceController构造时会加载libnetcmdinface.so,然后获取上面三个函数的指
针。这三个函数用于直接向驱动发送命令。由于代码中没有任何说明,而且也没有地方用到
它,故读者仅简单了解即可。
提示 笔者认为libnetcmdiface的目的应该是支持某些网络设备生产厂商(如
Broadcom)特有的一些命令选项。由于厂商的这些特殊选项不通用,它们不能向其他命令
选项一样在代码中被列举出来。
另外,根据本书审稿专家的反馈,在实际产品中不存在libnetcmdiface.so文件。对博
通公司的芯片而言,其私有命令的代码在
hardware/ broadcom/ wlan/ bcmhdh/ wpa_supplicant_8_lib中,对应的库文件为
lib_driver_cmd_bcmdhd.so。
(5)IFC设备控制选项
IFC设备控制选项主要是利用ifc_utils API对设备进行管理和控制,例如获取某个设备
的配置信息、启动某个设备、配置某个设备的MTU等。主要利用上文介绍的IFC接口对设备
进行控制,例如启动、设置MTU等。本节仅给出利用getcfg选项查询wlan0网络设备配置情
况的示意图,如图2-18所示。
图2-18 getcfg wlan0结果
图2-18显示了Galaxy Note 2打开W i-Fi后wlan0设备的配置信息,其中:
·90:18:7c:69:88:e2为该设备的MAC地址。
·192.168.1.101和24为IP地址以及子网掩码(255.255.255.0)。
·up、broadcast、running和multicast表示该设备当前已经启动并正在运行,并支
持广播和组播。
(6)IPv6控制选项 [18]
InterfaceCmd支持两个IPv6选项。
·ipv6privacyextensions:用于控制网卡使用临时IPv6地址的情况。该功能需要要
3.0以上的kernel控制方法就是向/ proc/ sys/ net/ ipv6/ 网卡名/ conf/ use_tempaddr写入对应
的值。值为0表示不允许IPv6临时地址,值为2表示优先使用临时IP地址,值为1则表示优先
使用非临时IP地址。
·ipv6:用于启动或禁止网卡的IPv6支持。往/ proc/ sys/ net/ ipv6/ 网卡
名/ conf/ disable_ipv6中写入0或1即可。
2.3.4 IpFwd和FirewallCmd命令
本节将介绍IpFwd和FirewallCmd两个命令。
1.IpFwd命令
IpFwd命令比较简单,主要是控制内核ipforward功能,其支持三个选项。
·status:用于判断ipforward功能是否开启。
·enable和disable:分别用于启动和禁止ipforward功能。
上述功能借助TetherController控制对象来完成,其内部代码非常简单,就是通过读
写/ proc/ sys/ net/ ipv4/ ip_forward文件来实现对内核ipforward功能的管理。
提示 读者可上网搜索/ proc/ sys/ net/ ipv4目录下其他文件的作用。
2.FirewallCmd命令
FirewallCmd用于防火墙控制。它支持以下命令选项。
·enable和is_enabled:用于启动防火墙和判断防火墙是否已经启动。
·set_interface_rule:针对单个或多个NIC设备设置防火墙规则。
·set_egress_source_rule和set_egress_dest_rule:基于源IP和目标IP地址设置防
火墙。
·set_uid_rule:根据uid设置单个进程的防火墙。
下面重点介绍防火墙功能的启动以及如果针对单个进程的设置防火墙规则。
(1)启动防火墙
本节介绍enable和set_uid_rule。其中,enable将调用FirewallCtronller的
enableFireW all函数,代码如下所示。
[–>FirewallController.cpp::enableFirewall]
int FirewallController::enableFirewall(void) {
int res = 0;
// 先清空fw_INPUT/fw_OUTPUT/fw_FORWARD链中的规则
disableFirewall();
// 启动防火墙就是设置Filter表中fw_INPUT/fw_OUTPUT/fw_FORWARD的目标为DROP和REJECT
res |= execIptables(V4V6, “-A”, LOCAL_INPUT, “-j”, “DROP”, NULL);
res |= execIptables(V4V6, “-A”, LOCAL_OUTPUT, “-j”, “REJECT”, NULL);
res |= execIptables(V4V6, “-A”, LOCAL_FORWARD, “-j”, “REJECT”, NULL);
return res;
}
由上述代码可知,当启动防火墙时,filter表中三个用于防火墙控制的Chain的目标都
改为DROP或REJECT,这样系统就无法收发网络数据包。图2-19所示为笔者在Ubuntu机
器上设置的规则。
图2-19 enable防火墙
图2-19所示是利用Ubuntu机器模拟enableFirewall函数后的结果。防火墙启动后,机
器就无法连接网络了。
读者可能好奇,为什么enableFirewall的破坏作用如此之大?这是因为防火墙设置一
般有两种方法。
·先允许整个系统都能上网,然后再单独设置防火墙规则以禁止某些模块连接网络。
·先禁止整个系统上网,然后再单独设置防火墙规则以允许某些模块能连接网络。
由此可知,Android采用了第二种更为严厉的方式来设置防火墙。接下来的工作就需要
设置各种防火墙规则以允许某些程序能够上网了。本节讨论如何为单个进程设置防火墙规
则。
提示 采用Ubuntu来测试enableFirewall,因为模拟器和主机也使用socket通信,一
旦启动防火墙,主机就无法再使用adb shell来控制模拟器了。
(2)设置进程防火墙
本节介绍的针对单个应用程序设置其防火墙规则,是大部分软件管家禁止某些应用程序
上网的通用方法。代码在setUidRule函数中,如下所示。
[–>FirewallController.cpp::setUidRule]
int FirewallController::setUidRule(int uid, FirewallRule rule) {
char uidStr[16];
sprintf(uidStr, “%d”, uid);
const char
op;
if (rule == ALLOW) {
op = “-I”;
} else {
op = “-D”;
}
int res = 0;
res |= execIptables(V4V6, op, LOCAL_INPUT,"-m", “owner”,
“–uid-owner”, uidStr,"-j", “RETURN”, NULL);
res |= execIptables(V4V6, op, LOCAL_OUTPUT, “-m”, “owner”,
“–uid-owner”, uidStr,"-j", “RETURN”, NULL);
return res;
}
该函数很简单,就是调用正确的iptables命令,它们分别如下。
iptables-I FORWARD_INPUT-m owner–uid-owner uid-j RETURN
iptables-I FORWARD_OUTPUT-m owner–uid-owner uid-j RETURN
这里要特别指出的是,目前Android只能根据uid来区分进程。由于Android平台允许
不同应用程序共享同一个uid ① 。多个应用程序共享同一个uid的情况下,如果用户禁止了其
中一个应用程序(软件管家的防火墙控制允许用户选择是否禁止某个应用程序上网),则连
带其他的相关程序都不能上网,虽然用户并没有选择禁止其他应用程序。
提示 笔者测试了360安全卫士,当出现上述关联情况时,它能提醒用户哪些关联程序
也会被一并禁止上网。
① 参考《深入理解Android:卷Ⅱ》4.3.1节关于Android系统中UID/ GID知识的介绍。
2.3.5 ListTtysCmd和PppdCmd命令
ListTTysCmd和PppdCmd比较简单,但也涉及两个重要的背景知识。
1.背景知识介绍
本节介绍的两个背景知识点是TTY和ptmx编程,以及PPP和pppd。
(1)TTY和ptmx编程 [19][20]
TTY是Linux系统(更确切地说是UNIX)中终端设备的统称,该词源于
TeleTYpewriter(电传打字机),是一个通过串行线用打印机键盘通过阅读和发送信息的
设备。不过随着计算机技术的发展,这类设备早就被键盘和显示器替代了。
从现在的情况来看,Linux系统中的TTY设备包含许多类型的设备,它们大体可分为以
下三种。
·串行端口终端:这类设备一般命名为/ dev/ ttySn,n为索引号,例如ttyS0、ttyS1
等。它们类似于W indows系统的COM0、COM1等,代表串行端口。当某个应用往ttyS0写
入数据时,另一个应用就能从ttyS0读到对应的数据。
·伪终端(psuedo terminal):这类设备成对出现,其中一个是master设备,另一
个是slave设备。二者的关系类似管道(Pipe),当一个程序往slave设备写数据时,另外一
个打开master设备的程序就能读到数据。以前(UNIX 98 scheme之前)主从设备命名方
式为/ dev/ ptyp0和/ dev/ ttyp0。现在master设备命名为/ dev/ ptmx,而slave设备则对
应/ dev/ pts/ 目录下的文件名。
·控制台终端:这类设备命名从/ dev/ ttyN(N从1~64)和/ dev/ console。/ dev/ tty0
代表当前使用的终端,而/ dev/ console一般指向这个终端。例如初始化时登录系统用的
是/ dev/ tty2,那么/ dev/ tty0指向/ dev/ tty2,如果切换到/ dev/ tty1,则/ dev/ tty0就指
向/ dev/ tty1了。
通过adb shell登录手机时,其实使用的都是伪终端。图2-20所示为笔者用adb shell登
录Galaxy Note 2后用tty命令打印的输出。
图2-20 tty命令
对程序员来说,Linux提供了针对伪终端的编程接口。伪终端在概念上和管道没有太大
区别,但是在实际操作时有一些特殊API需要调用。
主从两端要包含头文件:一般是在两个有亲缘关系的父子进程中使用
#include<stdlib.h>
主端需要打开/dev/ptmx设备,得到一个文件描述符
从端在使用这个文件描述符前,需要调用grantpt以获取权限,然后调用unlockpt解锁
从端通过ptsname获得从端设备文件名
从端打开由ptsname得到的从端设备
Netd中的logwrap将使用伪终端在父子进程中传递log信息。代码如下所示。
[–>logwrap.c]
int logwrap(int argc, const char
argv[])
{
pid_t pid;
int parent_ptty;
int child_ptty;
char child_devname[64];
// 父进程打开/dev/ptmx
parent_ptty = open("/dev/ptmx", O_RDWR);

// 父进程解锁/dev/ptmx。ptsname_r将得到从端设备的名字,例如/dev/pts/1
if (grantpt(parent_ptty) || unlockpt(parent_ptty) ||
ptsname_r(parent_ptty, child_devname, sizeof(child_devname))) {
// ptsname_r是ptsname的可重入(Reentrant,可用于多线程环境)版本
…// 错误处理
}
pid = fork();
if (pid < 0) {
…// 错误处理
} else if (pid == 0) {
// 子进程打开从端设备
child_ptty = open(child_devname, O_RDWR);
// 关闭从父进程那继承到的主设备文件
close(parent_ptty);
dup2(child_ptty, 1);// 重定向标准输入、输出、错误输出到伪终端的从设备
dup2(child_ptty, 2);
close(child_ptty);
child(argc, argv); // 执行child函数
} else {
int rc = parent(argv[0], parent_ptty); // 父进程执行parent函数
close(parent_ptty);
return rc;
}
return 0;
}
为什么Netd要使用这种一般很少用的ptmx伪终端呢?结合代码中的注释,笔者认为原
因有以下两个。
·后文会介绍,Netd将fork很多子进程以执行各种任务,而这些进程的代码一般都直
接来自于已有的开源代码。它们没有采用Android的ALOG宏来记录日志。所以,采用这种
I/ O重定向方法,这些进程的输出将全部传递到Netd进程中来。而Netd进程是可以使用
ALOG宏来打印输出的。上述代码中的parent函数将一直读取/ dev/ ptmx的内容,然后通过
ALOG宏输出。
·使用伪终端来承担父子进程通信重任(还可以使用别的进程间通信手段例如
socketpair、pipe等)的另一个原因是伪终端不会缓存数据。例如一旦子进程往从设备中
写入数据(借助I/ O重定向,当子进程往标准输出中写数据时,其实是往从设备中写数
据),父进程就能读到它们。
(2)PPP和pppd [21][22][23][24]
PPP(Point-to-Point Protocol,点对点协议)是为在同等单元之间传输数据包这样
的简单链路设计的链路层协议。这种链路提供全双工操作,并按照顺序传递数据包。设计目
的主要是通过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器
之间简单连接的一种共通的解决方案。
PPP提供了一整套方案来解决链路建立、维护、拆除、上层协议协商、认证等问题,它
主要包含以下几个部分。
·链路控制协议(Link Control Protocol,LCP):负责创建、维护或终止一次物理
连接。
·网络控制协议(Network Control Protocol,NCP):包含一簇协议,负责解决物
理连接上运行什么网络协议,以及解决上层网络协议发生的问题。
·认证协议:最常用的包括口令验证协议(Password Authentication
Protocol,PAP)和挑战握手验证协议(Challenge-Handshake Authentication
Protocol,CHAP)。使用时,客户端会将自己的身份发送给远端的接入服务器。期间将使
用认证协议避免第三方窃取数据或冒充远程客户接管与客户端的连接。
pppd(PPP Daemon的缩写)是运行PPP协议的后台进程。它和Kernel中的PPP驱动
联动,以完成在直连线路(DSL、拨号网络)上建立IP通信链路的工作。读者可简单认为有
了pppd,就可以在直连线路上传输IP数据包(类似以前我国大部分家庭用Modem和电话线
上网)。
2.ListTTysCmd和PppdCmd命令分析
这两个命令非常简单,ListTtysCmd用于枚举系统中所有的tty设备,这是通过列
举/ sys/ class/ tty目录中以tty开头的文件名来完成的。
PppdCmd仅有attach和detach两个选项。其中,attach用于启动pppd进程,而
detach用于杀死pppd进程。代码在PppController的attachPppd和detachPppd函数中,
请读者自行阅读。
PppController启动pppd时传递的启动参数如下所示。
pppd-detach #启动后转成后台进程
devname\ #使用devname这个设备上链接到另一端的设备
115200\ #波特率设置为115200
iplocal:ipremote\ #设置本地和远端IP
ms-dns d1 #为运行在windows的程序配置dns地址,第一个是主dns的地址
ms-dnsd2 #第二个是从dns的地址
lcp-max-configure 99999 #这里设置LCP config request最大个数为99999
配置并启动pppd后,手机就相当于一个Modem了,是不是很神奇呢?
2.3.6 BandwidthControlCmd和IdletimerControlCmd命令
本节介绍BandwidthControlCmd(简称bwcc)和IdletimerControlCmd(简称
icc)。这两个命令都利用了iptables的扩展模块,所以相应功能基本上完全是靠iptables
来实现的。
1.BandwidthControlCmd命令
bwcc用于Android系统中的带宽控制。目前4.2系统中的带宽控制可针对设备、某个应
用。另外还可以设置预警值,当带宽使用超过该值时会收到相应的通知(见2.2.2节中的
NETLINK_NFQLOG)。
和流量控制类似,带宽控制的实现也是利用iptables。它利用了iptables中扩展模块
libxt_quota2的功能,属于iptables的高级用法。这些内容对于非从事网络管理专业工作的
人来说难度相当大。考虑到这个因素,本节将把bwcc当做一个黑盒,仅介绍其提供的各项
功能。想深入研究的读者可在此基础上结合参考资料进一步了解。
bwcc提供的选项如下。
·enable和disable:开启或关闭带宽控制。
·removequota、getquota、getiquota、setquota、setquotas、
removequotas、removeiquota:删除、查询和添加带宽配额。选项中的’i’针对一个或多
个interface。选项中的’s’代表该选项可携带多个interface参数。
·addnaughtyapps和removenaughtyapps:以uid为目标,开启或关闭单个进程的带
宽控制。
·setglobalalert、removeglobalalert、setsharedalert、removesharedalert、
setinterfacealert、removeinterfacealert:预警值添加/ 删除有关。可以设置全局(即所
有网络接口,例如W i-Fi、3G等)带宽预警值,或者单个设备(如仅针对wlan0)的带宽预
警值。
·gettetherstats:获取绑定(Tether)设备的数据统计。后文将介绍Tether的相关
知识点。
图2-21所示为利用ndc命令为Galaxy Note 2 setglobalalert后的结果。图中为bwcc
设置了全局配额是1000字节,当使用测试机下载数据超过1000字节时,将得到如图2-22所
示的警告消息。
图2-21 bwcc setglobalalert结果
图2-22 ndc monitor得到的警告消息
图2-22所示最后一行打印了来自Kernel的qlog UEvent消息,以通知在wlan0设备上
数据流量已超过配额。
提示 bwcc应该是Netd中难度最大的模块了,其难点是如何利用iptables进行带宽控
制。相比其内部实现而言,掌握bwcc的功能对绝大多数Android开发者来说也许更加实
用。
2.IdletimerControlCmd命令
icc利用了iptables另一个扩展模块libxt_idletimer,其对应的iptables命令格式如
下。
iptables-t raw-A idletimer-i nic-j IDLETIMER–timeout–label lable–send_nl_msg 1
其中,各个参数的含义如下。
·timeout:超时时间,单位是秒。
·label:用来标示该rule的唯一名字。
·send_nl_msg:如果超时,则通过/ sys/ net/ xt_idletimer触发UEvent消息(回顾
2.2.2节中关于"xt_idletimer"UEvent消息的介绍)。
提示 奇怪的是,笔者在模拟器和Galaxy Note 2上利用ndc测试icc命令均不能添加超
时规则。而同样的命令放到Ubuntu 12.04上却工作正常(但Ubuntu上的iptable却不支持-
-send_nl_msg选项)。看来这个新颖的功能在目前的系统中支持得还不够好。
2.3.7 NatCmd命令
NatCmd和NAT相关,本节首先介绍NAT的背景知识。
1.背景知识介绍 [25]
在传统TCP/ IP通信过程中,所有的路由器仅仅是充当一个中间人的角色,也就是通常
所说的存储转发。即路由器不会对转发的数据包进行修改。准确地讲,除了将源MAC地址
换成自己的MAC地址以外,路由器不会对转发的数据包做任何修改。而NAT恰恰是出于某
种特殊需要而对数据包的源IP地址、目的IP地址、源端口、目的端口进行改写的操作。
什么情况下需要NAT(Network Address Translation,网络地址转换)呢?假设想
在公司内网搭建一个W W W 服务器以对外发布信息。由于公司内网使用的都是内部IP,故无
法向外网发布该服务。这时就可通过NAT来解决这个问题。比如网管可在防火墙的外部网
卡上绑定多个合法IP地址,然后通过NAT技术使发给其中某一个IP地址的包转发至这个内
部的W W W 服务器上,然后再将该内部W W W 服务器的响应包伪装成该合法IP发出的包。
NAT分为两种,分别是源NAT(SNAT)和目的NAT(DNAT),顾名思义,SNAT
就是改变转发数据包的源地址,DNAT就是改变转发数据包的目的地址。Linux系统上的
NAT操作是通过iptables的nat表来完成,该表有三条默认Chain,它们分别如下。
·PREROUTING:可在此定义目的NAT的规则,因为路由器进行路由时只检查数据包
的目的IP地址,所以为了使数据包得以正确路由,必须在路由之前就进行目的NAT。
·POSTROUTING:可以在这里定义进行源NAT的规则,系统在决定了数据包的路由
以后再执行该链中的规则。
·OUTPUT:定义对本地产生的数据包的目的NAT规则。
此处给出使用iptables进行NAT转换的两个例子。
//更改所有来自192.168.1.0/24的数据包的源IP地址为1.2.3.4:
iptables-t nat-A POSTROUTING-s 192.168.1.0/24-o eth0-j SNAT–to 1.2.3.4
//更改所有来自192.168.1.0/24的数据包的目的IP地址为1.2.3.4:
iptables-t nat-A PREROUTING-s 192.168.1.0/24-i eth1-j DNAT–to 1.2.3.4
2.NatCmd命令使用
NatCmd仅支持"enable"和"disable"两个命令选项。不过和上一节介绍的不
同,Android中的NAT并不是只利用iptables的nat表来做转换,而是借助ip route命令和
iptables的filter表在源和目标网络设备及指定IP地址之间进行地址转换。以"enable"选项
为例,其使用方式如下。
ndc nat enable lo wlan0 1 202.106.25.35/14
其中:
·lo为本地回环设备、代表输入设备。
·wlan0为无线NIC,代表输出设备。
·1代表后面的地址组合只有一个。
·202.106.25.35/ 14指明IP地址和子网掩码。
以上面的命令为例,NatCmd执行它所涉及的iptables命令的调用序列如下所示。
iptables-A natctrl_FORWARD-i eth0-o lo-m state–state ESTABLISHED,RELATED-j RETURN
iptables-A natctrl_FORWARD-i lo-o eth0-m state–state INVALID-j DROP
iptables-A natctrl_FORWARD-i lo-o eth0-j RETURN
iptables-D natctrl_FORWARD-j DROP
iptables-A natctrl_FORWARD-j DROP
iptables-t nat-A natctrl_nat_POSTROUTING-o eth0-j MASQUERADE
#MASQUERADE能自动读取eth0现在的IP地址然后做SNAT
#这样就避免了每次eth0地址发生改变时都需更新iptables的烦恼
注意 上边的命令中并没有利用传递的IP地址,这是因为这些地址和ip命令的多路由策
略管理有关,只有使用了多路由策略管理,这些IP地址才能被ip命令用上。由于代码中没有
任何说明,所以笔者也很难理解这部分内容。如果有知晓的读者,还请和大家一起分享相关
知识。
根据上面的iptables调用命令,读者也能猜测出enableNat的目的是修改来自源设备的
数据包,使它看起来是目标设备发出的数据包。
图2-23所示为利用ndc在Galaxy Note 2测试nat命令的结果。
图2-23 iptables查看natctrl_FORW ARD规则
图2-23所示为利用ndc nat enable rmnet0 wlan0 0添加nat规则后,再通过
iptables-S查看natctrl_FORW ARD链的具体规则时得到的结果。
注意 其中TCPMSS这条规则应该是三星公司自己添加的,不过令人匪夷所思的是该
命令竟然打出了"FIX ME!“这样的输出。看来,网络管理的确是一个很有技术含量的活。
2.3.8 TetherCmd和SoftapCmd命令
TetherCmd和SoftapCmd命令都和手机中一项名为绑定(Tether)的功能相关。简
单来说,绑定功能即把手机当成Modem用。智能手机一般都有多种连接网络的方式,例如
使用W i-Fi或3G。在某些环境下如高铁列车上,差旅人士只要把手机接到笔记本上,然后
开启3G和Tether,笔记本就可以利用手机上网了(在智能机普及前,类似的场景中就需要
使用3G上网卡)。
另外,如果手机中的无线网络设备支持Soft AP(Soft Access Point,软件实现的接
入点)功能,还可以通过Softap命令将手机变成一个AP(可以把它看成是一个无线路由
器)。
目前Android 4.2系统支持以下三种方式的绑定。
·Soft AP:利用W i-Fi无线网络的特性,开启手机Soft AP功能。主机和手机间通过
W i-Fi通信。
·Bluetooth:主机(PC或笔记本电脑)和手机通过蓝牙协议通信。
·USB:主机和手机通过USB协议通信。手机相当于一个USB上网卡。
本节主要介绍TetherCmd中的USB绑定和Softap命令。其余内容我们将留给读者自行
研究。
提示 TetherCmd还支持所谓的逆绑定(reverse tethering),即手机借助主机上
网。这部分内容请读者自行研究。
1.TetherCmd命令
本节仅介绍利用USB实现Tether的功能,其中涉及RNDIS以及DHCP相关的背景知
识,我们先来介绍它们。
(1)背景知识介绍 [26][27][28][29][30]
RNDIS(Remote Network Driver Interface Specification)是微软公司的,主要
用于W indows平台中USB网络设备的驱动开发。RNDIS的协议栈如图2-24所示。
图2-24 RNDIS协议栈
RNDIS的作用是简化W indows平台上USB网络设备驱动开发的流程。此处不讨论相关
内容,感兴趣的读者可阅读本章最后列出的参考资料。
RNDIS和Android有什么关系呢?当手机通过USB连接到主机(主机一般运行
W indows系统)后,如果要启用USB绑定,必须要把手机的USB设置成RNDIS(绝大部分
厂商的手机都是这么实现的)。这样,主机上的OS就能识别到一个新的网卡。然后用户就
可以选择使用它来开展网络操作了。
提示 本书后续章节将讨论Android平台上USB的相关功能。
假设用户选择使用这个通过USB绑定的网卡,下一步要做的就是给主机分配IP地址了。
此处涉及DHCP协议。
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)的前身是
BOOTP。BOOTP原本是用于无磁盘主机连接的网络上的,网络主机使用BOOT ROM而不
是磁盘启动并连接上网络,BOOTP则可以自动地为那些主机设定TCP/ IP环境。DHCP是
BOOTP的增强版本,它分为两个部分。
·服务器端:所有的IP网络设定数据都由DHCP服务器集中管理,并负责处理客户端的
DHCP要求。
·客户端:客户端会使用从服务器分配下来的IP地址等配置信息。
根据上述介绍,相信读者很容易想到,在USB绑定中,主机将是DHCP的客户端,而手
机是DHCP的服务器端。那么在Android系统中,DHCP服务器端是谁呢?同Pppd类
似,Android也使用了另外一个开源软件DNSmasq来充当DHCP服务器。
DNSmasq是一个用于配置DNS和DHCP的工具,小巧且方便,适用于小型网络,它提
供了DNS功能和可选择的DHCP功能。它服务只在本地适用的域名,这些域名是不会在全球
的DNS服务器中出现的。DHCP服务器和DNS服务器结合,并且允许DHCP分配的地址能
在DNS中正常解析,而这些DHCP分配的地址和相关命令可以配置到每台主机中,也可以配
置到一台核心设备中(如路由器)。
DNSmasq适用于拥有NAT的家庭网络、用Modem、ADSL设备连接到互联网等环境。
对于需求低资源消耗且配置方便简单的小型网络(最多可支持1000台主机)是一个很好的
选择。
(2)TetherCmd命令使用
Android中启动USB Tether功能将涉及Framework层多个模块,其详细过程留待后续
章节介绍。此处读者仅需从TetherCmd角度考虑其中的两个主要步骤。
1)添加需要Tether的接口。对USB绑定来说,接口名为rndis0。对应的处理函数是
TetherController的tetherInterface,代码如下所示。
[–>TetherController.cpp::tetherInterface]
int TetherController::tetherInterface(const char *interface) {
mInterfaces->push_back(strdup(interface));
// 把需要interface名字保存到一个链表即可
return 0;
}
tetherInterface的功能很简单,就是保存需要Tether的设备名。这一步其实没有太多
实质性的内容。
2)通过"start"选项启动Tether。这个步骤将触发TetherController的
startTethering函数被调用。该函数的主要功能就是配置dnsmasq的启动参数并启动它。
这部分代码比较简单,dnsmasq的启动参数如下所示。
dnsmasq
–keep-in-foreground#前台运行
–no-resolv#不解析/etc/resolv.conf,该文件记录域名和dns服务器的一些信息
–no-poll#不关注/etc/resolv.conf文件的变化
–dhcp-option-force=43,ANDROID_METERED#强制的dhcp选项。客户端和dnsmasq交互时,首先
#会获取dhcp服务器的一些配置信息。43是DHCP协议中定义的option的一种,代表vendor specific
#infomation该选项说明vendor specifi information就是ANDROID_METERED
–pid-file#指定dnsmasq记录自己进程id(pid)到某个文件。默认是/var/run/dnsmasq.pid
–dhcp-range=192.168.1.2 192.168.1.100 1h#该选项开启dnsmasq的dhcp服务功能。分配的IP地址
#位于192.168.1.2和192.168.1.100之间。1h代表租约时间为1小时。租约时间即某IP地址可以被DHCP
#客户端使用的时间。如果超过租约时间,dnsmasq必须为该客户端重新分配IP
这两步完成后,USB绑定功能中和TetherCmd相关的任务就完成了。从整个绑定过程
来看,涉及应用(例如Settings提供的设置功能)、网络模块、USB模块、驱动等,是一个
非常复杂的过程。
提示 这个过程对软件开发者来说也是一个挑战,只有对USB Tether涉及的各个模块
都有相应了解,碰到问题时候才能快速定位和解决它。
2.SoftapCmd命令
Softap命令和W i-Fi有紧密关系。本节先简单介绍Soft AP相关的背景知识,后续章节
将对W i-Fi开展深入讨论。
(1)背景知识介绍 [31][32][33]
Soft AP代表通过软件实现Access Point的功能。那么AP是什么?AP和Soft AP有什
么不同?
在W i-Fi无线技术规范中,AP和Station是其中的两个基本概念。
·从功能角度来看,AP作为基站设备,起着连接其他无线设备到有线网的作用,相当
于有线网络中的HUB与交换机。在日常工作和家庭中经常使用的无线路由器就是一个AP。
一般情况下,它一端接着有线网络,另一端连接其他无线设备。
·Station代表配备无线网络接口的设备,如手机、笔记本等。
虽然AP和Station是两个不同的设备,但实际上在Station中用软件也能实现AP拥有的
功能,如桥接、路由等。在基本功能上,Soft AP与AP并没有太大的差别,只是Soft AP设
备的接入能力和覆盖范围不如AP。
以前面提到的高铁列车上的应用场景为例,除了用USB绑定外,还可以打开笔记本和手
机的W i-Fi,并启动手机的Soft AP功能。这样,手机一方面用3G接入互联网,另一方面又
利用Soft AP向笔记本提供W i-Fi接入功能。
在Android系统中使用Soft AP功能还得借助另一个开源软件"hostapd”,这是一个运
行在用户空间的用于AP和认证服务器的守护进程。它实现了IEEE 802.11相关的接入管
理、IEEE 802.1X/ W PA/ W PA2/ EAP认证、RADIUS客户端、EAP服务器和RADIUS认证
服务器。
(2)SoftapCmd命令使用
和TetherCmd类似,开启Android中手机的Soft AP功能将涉及大量Framework层中
的操作,本节仅关注和Netd相关的三个步骤。
1)首先为W i-Fi加载不同的固件(Firmware),这是通过SoftapController的
fwReloadSoftap函数完成的,代码如下所示。
[–>SoftapController.cpp::fwReloadSoftap]
int SoftapController::fwReloadSoftap(int argc, char *argv[])
{
int ret, i = 0;
char *iface;
char *fwpath;
…// 参数检测
iface = argv[2];
if (strcmp(argv[3], “AP”) == 0) {
fwpath = (char *)wifi_get_fw_path(WIFI_GET_FW_PATH_AP);
} else if (strcmp(argv[3], “P2P”) == 0) {
fwpath = (char *)wifi_get_fw_path(WIFI_GET_FW_PATH_P2P);
} else {
fwpath = (char *)wifi_get_fw_path(WIFI_GET_FW_PATH_STA);
}
// 通过往/sys/module/wlan/parameters/fwpath文件中写入固件名
// 触发驱动去加载对应的固件
ret = wifi_change_fw_path((const char *)fwpath);

return ret;
}
上面这段代码表示在Android中,如果要让W i-Fi无线设备扮演不同的角色,得为它们
加载不同的固件(Firmware),具体说明如下。
·W IFI_GET_FW _PATH_AP:代表Soft AP功能的固件,其对应的文件位置由
W IFI_DRIVER_FW _PATH_AP宏表达。三星Tuna平台中,该文件位置
为/ vendor/ firmware/ fw_bcmdhd_apsta.bin。
·W IFI_GET_FW _PATH_P2P:代表P2P功能的固件,其对应的文件位置由
W IFI_DRIVER_FW _PATH_P2P宏表达。三星Tuna平台中,该文件位置
为/ vendor/ firmware/ fw_bcmdp2p.bin。
·W IFI_GET_FW _PATH_STA:代表Station功能的固件,其对应的文件位置由
W IFI_DRIVER_FW _PATH_STA宏表达。三星Tuna平台中,该文件位置
为/ vendor/ firmware/ fw_bcmdhd.bin。
提示 三星Tuna平台对应的配置文件在Android 4.2源码根目
录/ device/ samsung/ tuna目录中。从上面的固件文件名来看,它用的W i-Fi无线芯片是博
通(Broadcom)公司生产的。通过加载不同固件的方式来启用无线芯片硬件的不同功能可
能和W i-Fi驱动及芯片的设计有关。
另外,根据审稿专家的反馈,在Android 4.2中,STA和P2P可同时运行(即所谓的共
存模式),这样STA和P2P实际对应的固件相同,但可能文件名不同。而SoftAP的固件与
STA/ P2P就不一样了。
2)加载完指定的W i-Fi固件后,下一步将对Soft AP功能进行一些配置,配置信息最
终将写到一个配置文件。这部分功能由SoftapController的setSoftap函数完成,代码如下
所示。
[–>SoftapController.cpp::setSoftap]
int SoftapController::setSoftap(int argc, char argv[]) {
char psk_str[2
SHA256_DIGEST_LENGTH+1];
int ret = 0, i = 0, fd;
char *ssid, *iface;
…// 参数检查
iface = argv[2];
char *wbuf = NULL;
char *fbuf = NULL;
if (argc > 3) {
ssid = argv[3];
} else {
ssid = (char )“AndroidAP”; // SSID即接入点的名称
}
asprintf(&wbuf, “interface=%s\ndriver=nl80211\nctrl_interface=”
“/data/misc/wifi/hostapd\nssid=%s\nchannel=6\nieee80211n=1\n”,
iface, ssid);
if (argc > 4) { // 判断AP的加密类型
if (!strcmp(argv[4], “wpa-psk”)) {
generatePsk(ssid, argv[5], psk_str);
asprintf(&fbuf, “%swpa=1\nwpa_pairwise=TKIP CCMP\nwpa_psk=%s\n”,
wbuf, psk_str);
} else if (!strcmp(argv[4], “wpa2-psk”)) {
generatePsk(ssid, argv[5], psk_str);
asprintf(&fbuf, “%swpa=2\nrsn_pairwise=CCMP\nwpa_psk=%s\n”,
wbuf, psk_str);
} else if (!strcmp(argv[4], “open”)) {
asprintf(&fbuf, “%s”, wbuf);
}
} …
// HOSTAPD_CONF_FILE指向/data/misc/wifi/hostapd.conf文件
fd = open(HOSTAPD_CONF_FILE, O_CREAT | O_TRUNC | O_WRONLY, 0660);

if (write(fd, fbuf, strlen(fbuf)) < 0) {
ALOGE(“Cannot write to “%s”: %s”, HOSTAPD_CONF_FILE, strerror(errno));
ret = -1;
}
…// 修改该文件的读写权限等
return ret;
}
上面代码中涉及W i-Fi技术的很多概念,将在后续章节统一介绍。从功能上来
说,setSoftap函数无非就是把一些配置信息写到一个hostapd.conf文件中。可以通过一个
例子文件来了解此文件的内容。
Android4.2/ hardware/ ti/ wlan/ mac80211/ config目录中有一个hostapd.conf文件,
其内容如下所示。
[–>hostapd.conf]
driver=nl80211 #指定Wi-Fi驱动的名称
…#略去部分内容
ssid=AndroidAP #设置接入点名称为AndroidAP
country_code=US
wep_rekey_period=0
eap_server=0
own_ip_addr=127.0.0.1
wpa_group_rekey=0
wpa_gmk_rekey=0 #加密方式等设置
wpa_ptk_rekey=0
interface=wlan1 #网络设备接口
…#略去部分内容
由上边示例的hostapd.conf可知,当使用该配置文件后,其他Station搜索到由这台手
机设置的Soft AP的名称将会是"AndroidAP"。
3)最后,SoftapController的startap函数被调用,它将启动hostapd进程。重点关
注hostapd启动的参数信息,如下所示。
hostapd
-e/data/misc/wifi/entropy.bin \和Wi-Fi协议中的信息加密有关
/data/misc/wifi/hostapd.conf \hostapd的配置文件
2.3.9 ResolverCmd命令
ResolverCmd和Android系统中DNS的实现有关,用于给不同NIC设备配置不同的
DNS。其主要支持四个选项。
·setdefaultif:设置DNS查询时默认的NIC。和Android中DNS的实现有关。
·setifdns:设置不同NIC的DNS配置信息。
·flushdefaultif、flushif:清空默认或某个NIC的DNS配置信息。
提示 感兴趣的读者可结合Android系统中DNS的实现来理解ResolverCmd的功能。
2.4 NetworkManagementService介绍
根据前文所述,NetworkManagementService(以后简称NMService)将通
过"netd"socket和Netd交互。NMService代码非常简单,首先来看其创建的代码,如下所
示。
[–>SystemServer.java::ServerThread:run]
public void run() {
…// 其他Service的创建及相关处理
try {
networkManagement = NetworkManagementService.create(context);
ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
} catch …

final NetworkManagementService networkManagementF = networkManagement;
if (networkManagementF != null) networkManagementF.systemReady();

}
ServerThread是Android Java Framework的中枢,绝大部分重要Service都在该线
程中创建,例如ActivityManagerServie、W indowManagerService、
PackageManagerService以及本书要介绍的W ifiService、W ifiP2pService等。
ServerThread中和NMService相关的重要知识点仅create和systemReady两个函
数。下面将一一介绍。
提示 关于ServerThread的详细信息,请读者阅读《深入理解Android:卷Ⅱ》。
2.4.1 create函数详解
create函数的代码如下所示。
[–>NetworkManagementService.java::create]
public static NetworkManagementService create(Context context)
throws InterruptedException {
// 创建一个NMService对象
final NetworkManagementService service = new NetworkManagementService(context);
final CountDownLatch connectedSignal = service.mConnectedSignal;
service.mThread.start();// 启动NMService中的一个线程
// connectedSignal用于等待某个事情的发生。此处是等待mThread完成初始化工作
connectedSignal.await();
return service;
}
create函数非常简洁,其主要工作就是创建一个NMService对象并启动其中一个线
程。create返回前需要确保mThread线程完成初始化工作。下面来看看NMService的构造
函数。
[–>NetworkManagementService.java]
private NetworkManagementService(Context context) {
mContext = context;
// 对模拟器的处理
if (“simulator”.equals(SystemProperties.get(“ro.product.device”))) return;
/

NativeDaemonConnector是Java Framework中一个特别的类,它用于连接指定的socket,并发送和接收
socket数据。
此处,“netd"参数代表目标socket。NetdCallbackReceiver为具体的socket连接及消息处理对象。
1.当Netd连接成功后,NetdCallbackReceiver的onDaemonConnected函数将被调用。
2.当收到来自Netd的数据后,NetdCallbackReceiver的onEvent函数将被调用。
NativeDaemonConnector代码比较简单,感兴趣的读者不妨自行阅读。
/
mConnector = new NativeDaemonConnector(
new NetdCallbackReceiver(), “netd”, 10, NETD_TAG, 160);
// 创建一个线程,其Runnable对象就是mConnector
mThread = new Thread(mConnector, NETD_TAG);
/

把自己添加到Watchdog中的监控队列中。这样,NMService将受到Watchdog的监控,一旦NMService
出现异常,Watchdog将自杀以重启Android Java World。对Watchdog感兴趣的读者不妨阅读《深
入理解Android:卷Ⅰ》4.5.3节“Watchdog分析”。
*/
Watchdog.getInstance().addMonitor(this);
}
对上述代码来说,最重要的是NetdCallbackReceiver,下面来看看。
[–>NetworkManagementService.java::NetdCallbackReceiver]
private class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks {
public void onDaemonConnected() {
if (mConnectedSignal != null) {
// 通知NMService构造函数中的connectedSignal.await()返回
mConnectedSignal.countDown();
mConnectedSignal = null;
} else {// mMainHandler和mThread线程绑定
mMainHandler.post(new Runnable() {
public void run() {
prepareNativeDaemon(); // 下节介绍
}});
}
}
// 处理来自Netd的消息
public boolean onEvent(int code, String raw, String[] cooked) {
switch (code) {// 目前NMService只处理下面三种Command对应的消息
case NetdResponseCode.InterfaceChange: // 对应InterfaceCmd
…// 略去具体的处理逻辑
case NetdResponseCode.BandwidthControl:// 对应BandwidthControlCmd

case NetdResponseCode.InterfaceClassActivity:// 和IdletimerCmd有关

default: break;
}
return false;
}
}
create及相关函数都比较简单,此处不详述,下面来看systemReady。
2.4.2 systemReady函数详解
systemReady函数详解如下所示。
[–>NetworkManagementService.java::systemReady]
public void systemReady() {
prepareNativeDaemon();
}
prepareNativeDaemon用于将系统中一些与带宽控制、防火墙相关的规则发送给
Netd去执行,其代码如下所示。
[–>NetworkManagementService.java::prepareNativeDaemon]
private void prepareNativeDaemon() {
mBandwidthControlEnabled = false;
// 判断kernel是否支持bandwidthcontrol
final boolean hasKernelSupport = new File(”/proc/net/xt_qtaguid/ctrl").exists();
if (hasKernelSupport) {
try {
mConnector.execute(“bandwidth”, “enable”);// 使能bandwidth功能
mBandwidthControlEnabled = true;
} catch …
}
// 设置Android系统属性"net.qtaguid_enabled"
SystemProperties.set(PROP_QTAGUID_ENABLED, mBandwidthControlEnabled ? “1” : “0”);
// 设置Bandwidth规则
synchronized (mQuotaLock) {
int size = mActiveQuotas.size();
if (size > 0) {
// mActiveQuotas保存了每个interface的配额设置
final HashMap<String, Long> activeQuotas = mActiveQuotas;
mActiveQuotas = Maps.newHashMap();
for (Map.Entry<String, Long> entry : activeQuotas.entrySet())
setInterfaceQuota(entry.getKey(), entry.getValue());
}
…// 其他规则
}
// 设置防火墙规则
setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
}
以setFireW allEnabled函数为例,它和Netd交互的方法如下。
[–>NetworkManagementService.java::setFirewallEnabled]
public void setFirewallEnabled(boolean enabled) {
enforceSystemUid();
try {// 发送firewall相关的Command给Netd
mConnector.execute(“firewall”, enabled ? “enable” : “disable”);
mFirewallEnabled = enabled;
}catch …
}
systemReady函数非常简单,本章就不详述了。
2.5 本章总结和参考资料说明
2.5.1 本章总结
本章对Netd进行了详细讨论。相信读者读完此章的感受是代码这么容易的模块,竟然
涉及如此复杂的背景知识。从代码上看也许它们并不复杂,但是其背后的理论知识却可能大
有来头。对于这些内容而言,代码只是外在的表现形式,其核心一定在其背后的那些知识
中。所以,读者在阅读专题卷的时候,一定要考察自己是否对背景知识有所掌握。
Netd涉及的内容和网络管理与控制有关,例如DNS、Apple Bonjour、利用iptables
等工具实现NAT、防火墙、带宽控制、流量控制、路由控制功能,以及USB绑定W i-Fi、
Soft AP等。请读者在本章的参考资料中找到并继续研究自己感兴趣的内容。
最后,对NetworkManagementService进行了介绍。NMService的内容非常简单。
2.5.2 参考资料说明
1.Linux PF_NETLINK相关资料
[1] Linux man PF_NETLINK
说明:本文档是Linux系统中的帮助文档。从总体上介绍了
PF_NETLINK(AF_NETLINK)的作用和相关的数据结构。对高手比较适用。
[2] http:/ / www.linuxjournal.com/ article/ 8498
说明:“Manipulating the Networking Environment Using RTNETLINK”,这篇文
章以RTNETLINK为主要对象,介绍了如何利用它进行编程以操作网络。此文写得非常详
细,建议读者深入阅读,并且自己动手写测试例子。
2.DNS、Apple Bonjour相关资料
[3] http:/ / baike.baidu.com/ view/ 22276.htm
说明:百度百科中关于DNS的介绍,属于入门级材料,初学者可以先了解相关知识。
[4] http:/ / en.wikipedia.org/ wiki/ MDNS
说明:维基百科中关于Multicast DNS的介绍。入门级材料,包含的信息不是很全,需
要跟踪其中的链接才能对MDNS有全面了解。
[5]
https:/ / developer.apple.com/ library/ mac/ # documentation/ Cocoa/ Conceptual/ NetServices/ Introduc
SW 1
说明:“Introduction to Bonjour Overview”,苹果开发网站上关于Bonjour基础知
识的入口,包含"About Bonjour"、"Bonjour API Architecture"等文档。
[6]
https:/ / developer.apple.com/ library/ mac/ # documentation/ Networking/ Conceptual/ dns_discovery_a
说明:“DNS Service Discovery Programming Guide”,苹果开发网站关于NSD API
的说明。
3.iptables相关资料
iptables的相关文档非常多,虽然Linux也提供了帮助文档(man iptables),但对新
手来说该文档实在不是学习的好资料。
[7] http:/ / www.thegeekstuff.com/ 2011/ 01/ iptables-fundamentals/
说明:“Linux Firewall Tutorial:IPTables Tables,Chains,Rules
Fundamentals”,这篇文章首先从原理上介绍了如何理解iptables,然后介绍了相关的例
子。笔者认为这是iptables最好的入门资料。
[8] http:/ / selboo.com.cn/ post/ 721/
说明:“iptables的相关概念和数据包的流程”,这篇文档介绍了iptables中各个
Table及Chain的处理顺序,请读者结合参考资料[7]来理解iptables。
[9] http:/ / www.frozentux.net/ iptables-tutorial/ cn/ iptables-tutorial-cn-
1.1.19.html
说明:“Iptables指南1.1.19”,这篇文档介绍的iptables版本比较旧(Android 4.2
使用的iptables版本是1.4.11),但对iptables常用参数都有非常详细的介绍。适合已经入
门的读者进行深入阅读。
4.tc相关资料
tc文献的数量和难度远大于iptables,此处精选几个必读文献。
[10] http:/ / linux-ip.net/ articles/ Traffic-Control-HOW TO/ intro.html
说明:“Traffic Control HOW TO”,理解tc的必读文献,覆盖面很广,理论知识讲解
到位。难度稍大,需要仔细琢磨才能完全理解。
[11] http:/ / wenku.baidu.com/ view/ f02078db50e2524de5187e45.html
说明:“TC(Linux下流量控制工具)详细说明及应用实例”,百度文库中的一篇文
档,篇幅虽然不长,但也做到了理论和实例结合。建议读者先阅读此文献,然后再深入研究
参考资料[10]。
[12] http:/ / fanqiang.chinaunix.net/ a1/ b1/ 20010811/ 0705001103.html
说明:“在Linux中实现流量控制器”,一篇博文,主要对tc命令的用法列举了不少示
例,属于tc的实战文章,建议放到最后阅读。
[13] http:/ / www.linuxfoundation.org/ collaborate/ workgroups/ networking/ ifb
说明:这是笔者能找到的关于IFB设备最完整的资料,对IFB的使用、常规用法等进行
了全方位的介绍。
5.ip命令相关资料
[14] http:/ / blog.chinaunix.net/ uid-24921475-id-2547198.html
说明:“Linux ip命令介绍”。ip命令比较简单,这里仅给出一篇文献。
6.NetDevice编程文献
[15] Linux man netdevice
说明:非常详细的NetDevice编程介绍,建议读者认真阅读。
7.Linux策略路由相关资料
[16] http:/ / www.cnblogs.com/ iceocean/ articles/ 1594488.html
说明:“Linux策略路由”,中文文档,知识面覆盖较全,属于入门级资料。
[17] http:/ / www.policyrouting.org/ PolicyRoutingBook/ ONLINE/ TOC.html
说明:《Policy Routing W ith Linux》,这是一本书籍。个人感觉参考资料[16]是本
书的学习总结,属于高级阅读材料,难度较大。
8.Linux IPv6控制相关资料
[18] http:/ / www.ipsidixit.net/ 2012/ 08/ 09/ ipv6-temporary-addresses-and-
privacy-extensions/
说明:“IPv6 temporary addresses and privacy extensions”,介绍Linux中IPv6临
时地址和privacy extensions方面的知识,知识覆盖面较全,属于入门资料。
9.TTY和ptmx编程相关资料
[19] http:/ / tldp.org/ HOW TO/ Text-Terminal-HOW TO.html
说明:“Text-Terminal-HOW TO”,比较旧的资料,覆盖面非常广。读者可阅读自己
想了解的章节。
[20] http:/ / blog.tianya.cn/ blogger/ post_read.asp
BlogID=3616841& PostID=33399981
说明:“Linux下tty/ pty/ pts/ ptmx详解”,很好的中文材料,还列出了参考文献。最
后,关于ptmx,读者还可通过man ptmx获得如何用它进行编程的指导。
10.PPP和pppd相关资料
[21] http:/ / tldp.org/ HOW TO/ PPP-HOW TO/
说明:“Linux PPP HOW TO”,Linux HowTo系列的内容都简单易懂。章节较多,但
很多章节仅一两句内容,可做入门参考。
[22] http:/ / network.51cto.com/ art/ 201009/ 223784.htm
说明:“基础解读PPP协议”,中文文档,一页内容,主要介绍PPP框架性的内容。
[23] http:/ / wenku.baidu.com/ view/ 0c395f15866fb84ae45c8d4a.html
说明:“PPP介绍”,百度文库中的一个关于PPP的PPT。内容翔实,不仅介绍了PPP
协议的数据包,也从框架上介绍了PPP的工作流程。建议读者首先阅读此文献。
[24] Linux man pppd
说明:介绍Pppd中各个选项的作用。
11.NAT相关资料
[25]
http:/ / oa.jmu.edu.cn/ netoa/ libq/ pubdisc.nsf/ 66175841be38919248256e35005f4497/ 7762e8e1056be98f4
OpenDocument
说明:“用iptables实现NAT”,中文文档,简单易懂。
12.Tether、RNDIS、DHCP、DNSmasq相关资料
[26] http:/ / en.wikipedia.org/ wiki/ Tethering
说明:“Tethering”,维基百科中关于Tether的介绍,浅显易懂,属于普及型资料。
[27] http:/ / msdn.microsoft.com/ en-
us/ library/ windows/ hardware/ gg463293.aspx
说明:“Remote NDIS(RNDIS)and W indows”,MSDN文档,非常详实(不得不说
微软在文档方面的工作真的是一丝不苟)。
[28] http:/ / baike.baidu.com/ view/ 7992.htm
subLemmaId=7992& fromenter=% A3% C4% A3% C8% A3% C3% A3% D0
说明:百度百科中关于DHCP的解释,入门资料。
[29] http:/ / baike.baidu.com/ view/ 6681631.htm
说明:百度百科中关于DNSmasq的解释。
[30] http:/ / wenku.baidu.com/ view/ 662b536b561252d380eb6ec1.html
说明:关于DHCP协议中option字段的详细介绍。
13.Softap和hostapd相关资料
[31] 《802.11无线网络权威指南(中文第2版)》
说明:读者可先阅读第1、2章中关于W i-Fi技术的一些基本概念,例如AP和Station。
[32] http:/ / baike.baidu.com/ view/ 2475889.htm
说明:百度百科关于Soft AP的入门级介绍。
[33] 关于hostapd,读者可利用man hostapd得到各个选项的用法。提示,读者必须
先安装hostapd,然后才能查阅其帮助文档。

netd模块工作流程相关推荐

  1. 计算机网络---IP数据报组成计及IP模块工作流程

    IP数据报组成 IP数据报是由首部与数据部两部分组成. 首部的前一部分是固定的20个字节,后一部分是可选的,是每个数据报必不缺少的一部分.后面的数据部分是来自于传输层. 这是IP数据报首部的结构图,上 ...

  2. Vold工作流程分析学习

    一 Vold工作机制分析 vold进程:管理和控制Android平台外部存储设备,包括SD插拨.挂载.卸载.格式化等: vold进程接收来自内核的外部设备消息. Vold框架图如下: Vold接收来自 ...

  3. iOS应用模块化的思考及落地方案(一)模块的划分及模块化工作流程

    1.0 什么是模块化 很多关于重构及设计模式的介绍中,经常提到的几个词语是复用及解耦. 模块化之所以被提出,也更多是为了解决这几个问题. 复用可以减少重复造轮子的情况,很容易理解的是,我们经常使用的g ...

  4. agv系统介绍_AGV物流系统工作流程及模块介绍

    原标题:AGV物流系统工作流程及模块介绍 众所周知,企业内部物流体系是-个复杂系统,很多企业都逐渐开始重视物流提高自己竞争力. 因此能降低零部件库存,降低周转箱数量,平衡物料接收,提高装货卸货效率的A ...

  5. Android 7.0 Vold工作流程

    一.Vold工作机制 Vold是Volume Daemon的缩写,它是Android平台中外部存储系统的管控中心,是管理和控制Android平台外部存储设备的后台进程.其功能主要包括:SD卡的插拔事件 ...

  6. Blender+SP+UE5游戏艺术工作流程学习

    Blender到虚幻引擎5 Blender游戏艺术 Blender for Game Art 你会学到: 如何在Blender中创建三维模型 UV如何展开和布局 如何在Substance Painte ...

  7. Nginx源码分析:master/worker工作流程概述

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> Nginx的master与worker工作模式 在生成环境中的Nginx启动模式基本都是以m ...

  8. Scrapy框架的概念、作用和工作流程

    1. scrapy的概念         Scrapy是一个Python编写的开源网络爬虫框架.它是一个被设计用于爬取网络数据.提取结构性数据的框架. Scrapy是一个为了爬取网站数据,提取结构性数 ...

  9. 小程序工程化实践(上篇)-- 手把手教你撸一个小程序 webpack 插件,一个例子带你熟悉 webpack 工作流程...

    本文基于 webpack 4 和 babel 7,Mac OS,VS Code 小程序开发现状: 小程序开发者工具不好用,官方对 npm 的支持有限,缺少对 webpack, babel 等前端常用工 ...

  10. scrapy 模块功能流程--转

    scrapy工作流程和每个模块的具体作用 其流程可以描述如下: 爬虫中起始的url构造成request对象-->爬虫中间件-->引擎-->调度器 调度器把request-->引 ...

最新文章

  1. make 操作技巧指南--gcc版本设置
  2. 获取鼠标在盒子中的坐标
  3. Python中的range和xrange区别
  4. nginx ngx_http_mirror_module模块
  5. jquery this 与javascript的this
  6. erp开发模式_ERP的完整形式是什么?
  7. 职称计算机word模拟题,2017年职称计算机考试Word2003模拟题及答案(1)
  8. (原创) cocos2d-x 3.0+ lua 学习和工作(4) : 公共函数(5): 返回指定表格中的所有键(key):table.keys...
  9. openGauss持续构筑企业级内核能力,使能行业技术创新
  10. Struts的文件上传与下载
  11. iOS开发 - 获取手机总空间、剩余空间,文件夹占用空间大小
  12. golang之旅--接口 (interface)
  13. 《现代操作系统》读书笔记
  14. 优思学院|精益六西格玛中的8大浪费是什么?
  15. HTML四季变换图,四季星空图
  16. 【神经网络】一文带你轻松解析神经网络(附实例恶搞女友)
  17. 公众号运营工具推荐大会!这些如果你还没用就out啦!
  18. 中文核心期刊目录(2008年版).
  19. beacon设备使用记录
  20. leetcode第88题“非递减顺序排列“是什么意思?

热门文章

  1. 面向过程实列(用简单代码写出斗地主的程序(在一个类中实现))
  2. (一)VirtualBox安装增强功能
  3. 使用Redis,QQ邮箱模拟验证码验证
  4. Stata:投资组合有效边界
  5. 个人带领团队做过的事
  6. 【GitHub】README.md文件中 markdown语法 插入超链接
  7. 科研学术论文搜索利器:Publish or Perish
  8. 贴片钽电容,P型 A型 B型 C型 D型 E型怎么区分?
  9. HTML5 webSQL动态查询应用截图
  10. 平时工作中如何体现个人技术深度?