Netd是Android的网络守护进程。NetD是个网络管家,封装了复杂的底层各种类型的网络(NAT,PLAN,PPP,SOFTAP,TECHER,ETHO,MDNS等),隔离了底层网络接口的差异,给Framework提供了统一调用接口,简化了网络的使用。NetD主要功能是:
NetD程序的大概逻辑: [Kernel] <--Network Event--> (NetD) <--Command--> [Framework]。事件和命令传送采用跨进程通信机制(SOCKET).
Netd:
/System/netd
/system/core/libsysutils/src
/system/core/include/sysutils
Framework:
/frameworks/base/services/java/com/android/server
int main() {
CommandListener *cl;
NetlinkManager *nm;
DnsProxyListener *dpl;
MDnsSdListener *mdnsl;
FwmarkServer* fwmarkServer;
ALOGI("Netd 1.0 starting");
blockSigpipe();
//NetlinkManager是用于接收kernel中的uevent
if (!(nm = NetlinkManager::Instance())) {
ALOGE("Unable to create NetlinkManager");
exit(1);
};
//接收来自Framework层NetworkManageService的命令
cl = new CommandListener();
nm->setBroadcaster((SocketListener *) cl);
if (nm->start()) {
ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));
exit(1);
}
// Set local DNS mode, to prevent bionic from proxying
// back to this service, recursively.
//对接frameworklistener
//GetAddrInfoCmd,和Bionic C库的getaddrinfo函数对应。
//GetHostByAddrCmd,和Bionic C库的gethostbyaddr函数对应。
setenv("ANDROID_DNS_MODE", "local", 1);
dpl = new DnsProxyListener(CommandListener::sNetCtrl);
if (dpl->startListener()) {
ALOGE("Unable to start DnsProxyListener (%s)", strerror(errno));
exit(1);
}
//MDnsSd是Multicast DNS Service Discovery的简称,它和Apple公司的Bonjour技术有关
//Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。
//举一个简单的例子:在局域网中,如果要进行打印服务,必须先知道打印服务器的IP地址。
//此IP地址一般由IT部门的人负责分配,然后他还得全员发邮件以公示此地址。有了Bonjour以后,
//打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务?
//名为“print service”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。
//由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机
mdnsl = new MDnsSdListener();
if (mdnsl->startListener()) {
ALOGE("Unable to start MDnsSdListener (%s)", strerror(errno));
exit(1);
}
//创建 FwmarkServer并启动监听,它将创建名为fwmarkd的监听
fwmarkServer = new FwmarkServer(CommandListener::sNetCtrl);
if (fwmarkServer->startListener()) {
ALOGE("Unable to start FwmarkServer (%s)", strerror(errno));
exit(1);
}
/*
* Now that we're up, we can respond to commands
*/
if (cl->startListener()) {
ALOGE("Unable to start CommandListener (%s)", strerror(errno));
exit(1);
}
// Eventually we'll become the monitoring thread
while(1) {
sleep(1000);
}
ALOGI("Netd exiting");
exit(0);
}
Netd的main函数主要是创建几个重要成员并启动相应的工作,这四个重要成员分别如下: NetlinkManager:接收并处理来自Kernel的UEvent消息。这些消息经NetlinkManager解析后将借助它的Broadcaster(也就是代码中为NetlinkManager设置的CommandListener)发送给Framework层的 NetworkManagementService。
CommandListener、DnsProxyListener、MDnsSDListener、FwmarkServer:分别创建名为netd、dnsproxyd、mdns、fwmarkServer的监听socket.
下面针对上面提到主要成员进行分析。
int NetlinkManager::start() {
//创建接收 NETLINK_KOBJECT_UEVENT 消息的socket,其值保存在mUeventSock中
//其中, NETLINK_FORMAT_ASCII 代表UEvent消息的内容为 ASCII字符串
if ((mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT,
0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII)) == NULL) {
return -1;
}
//创建接收 RTMGRP_LINK 消息的socket,其值保存在 mRouteSock 中
//其中, NETLINK_FORMAT_BINARY 代表 UEvent 消息的类型为结构体,故需要进行二进制解析
if ((mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE,
RTMGRP_LINK |
RTMGRP_IPV4_IFADDR |
RTMGRP_IPV6_IFADDR |
RTMGRP_IPV6_ROUTE |
(1 << (RTNLGRP_ND_USEROPT - 1)),
NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) {
return -1;
}
//创建接收 NETLINK_NFLOG 消息的socket,其值保存在 mQuotaSock 中
if ((mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG,
NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) {
ALOGE("Unable to open quota2 logging socket");
// TODO: return -1 once the emulator gets a new kernel.
}
return 0;
}
NM的start函数主要是向Kernel注册三个用于接收UEvent事件的socket,这三个UEvent分别对应于以下内容
NetlinkHandler接收到的UEvent消息会转换成一个NetlinkEvent对象。NetlinkEvent对象封装了对 UEvent消息的解析方法。对于 NETLINK_FORMAT_ASCII类型,其 parseAsciiNetlinkMessage函数会被调用,而对于 NETLINK_FORMAT_BINARY类型,其 parseBinaryNetlinkMessage 函数会被调用。 NM处理流程的输入为一个解析后的 NetlinkEvent对象。NM完成相应工作后,其处理结果将经由 mBroadcaster对象传递给 Framework层的接收者,也就是 NetworkManagementService。 CommandListener 从 FrameworkListener派生,而 FrameworkListener 内部有一个数组 mCommands,用来存储注册到 FrameworkListener中的命令处理对象。
Netd中第二个重要成员是CommandListener(以后简称CL),其主要作用是接收来自Framework层NetworkManageService的命令。从角色来看,CL仅是一个Listener。它在收到命令后,只是将它们转交给对应的命令处理对象去处理。CL内部定义了许多命令,而这些命令都有较深的背景知识。本节拟以分析CL的工作流程为主,而相关的命令处理则放到后文再集中分析。
CL定义了11个和网络相关的Command类。这些类均从NetdCommand派生(注意,为保持绘图简洁,这11个Command的派生关系由1个派生箭头表达)。 CL还定义了10个控制类,这些控制类将和命令类共同完成相应的命令处理工作
android-5.1/system/netd/server/CommandListener.cpp
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());
registerCmd(new ClatdCmd());
registerCmd(new NetworkCommand());
//创建对应的控制类对象
if (!sNetCtrl)
sNetCtrl = new NetworkController();
if (!sTetherCtrl)
sTetherCtrl = new TetherController();
if (!sNatCtrl)
sNatCtrl = new NatController();
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();
if (!sClatdCtrl)
sClatdCtrl = new ClatdController(sNetCtrl);
/*
* This is the only time we touch top-level chains in iptables; controllers
* should only mutate rules inside of their children chains, as created by
* the constants above.
*
* Modules should never ACCEPT packets (except in well-justified cases);
* they should instead defer to any remaining modules using RETURN, or
* otherwise DROP/REJECT.
*/
// Create chains for children modules
//初始化 iptables 中的各个Table 及相应 Chain 和 Rules
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, "mangle", "FORWARD", MANGLE_FORWARD);
createChildChains(V4, "nat", "PREROUTING", NAT_PREROUTING);
createChildChains(V4, "nat", "POSTROUTING", NAT_POSTROUTING);
// Let each module setup their child chains
//netd允许OEM厂商可自定义一些规则,这些规则在 /system/bin/oem-iptables-init.sh文件中保存
setupOemIptablesHook();
/* When enabled, DROPs all packets except those matching rules. */
//初始化 iptables中的一些chain,以及初始化路由表
sFirewallCtrl->setupIptablesHooks();
/* Does DROPs in FORWARD by default */
sNatCtrl->setupIptablesHooks();
/*
* Does REJECT in INPUT, OUTPUT. Does counting also.
* No DROP/REJECT allowed later in netfilter-flow hook order.
*/
sBandwidthCtrl->setupIptablesHooks();
/*
* Counts in nat: PREROUTING, POSTROUTING.
* No DROP/REJECT allowed later in netfilter-flow hook order.
*/
sIdletimerCtrl->setupIptablesHooks();
//初始化时,Netd将禁止带宽控制功能
sBandwidthCtrl->enableBandwidthControl(false);
if (int ret = RouteController::Init(NetworkController::LOCAL_NET_ID)) {
ALOGE("failed to initialize RouteController (%s)", strerror(-ret));
}
}
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有两个:
android-5.1/bionic/libc/dns/net/getaddrinfo.c
int
getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res)
{
//Android平台的特殊定制
return android_getaddrinfofornet(hostname, servname, hints, NETID_UNSET, MARK_UNSET, res);
}
Android平台的 getaddrinfo 会调用其定制的 android_getaddrinfofornet 函数完成一些特殊操作:
int
android_getaddrinfofornet(const char *hostname, const char *servname,
const struct addrinfo *hints, unsigned netid, unsigned mark, struct addrinfo **res)
{
struct addrinfo sentinel;
struct addrinfo *cur;
int error = 0;
struct addrinfo ai;
struct addrinfo ai0;
struct addrinfo *pai;
const struct explore *ex;
//取 ANDROID_DNS_MODE 环境变量
//只有android-5.1/system/netd/server/main.cpp中设置了此变量:setenv("ANDROID_DNS_MODE", "local", 1);
const char* cache_mode = getenv("ANDROID_DNS_MODE");
/* hostname is allowed to be NULL */
/* servname is allowed to be NULL */
/* hints is allowed to be NULL */
assert(res != NULL);
memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;
pai = &ai;
pai->ai_flags = 0;
pai->ai_family = PF_UNSPEC;
pai->ai_socktype = ANY;
pai->ai_protocol = ANY;
pai->ai_addrlen = 0;
pai->ai_canonname = NULL;
pai->ai_addr = NULL;
pai->ai_next = NULL;
if (hostname == NULL && servname == NULL)
return EAI_NONAME;
if (hints) {
/* error check for hints */
if (hints->ai_addrlen || hints->ai_canonname ||
hints->ai_addr || hints->ai_next)
ERR(EAI_BADHINTS); /* xxx */
if (hints->ai_flags & ~AI_MASK)
ERR(EAI_BADFLAGS);
switch (hints->ai_family) {
case PF_UNSPEC:
case PF_INET:
#ifdef INET6
case PF_INET6:
#endif
break;
default:
ERR(EAI_FAMILY);
}
memcpy(pai, hints, sizeof(*pai));
/*
* if both socktype/protocol are specified, check if they
* are meaningful combination.
*/
if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) {
for (ex = explore; ex->e_af >= 0; ex++) {
if (pai->ai_family != ex->e_af)
continue;
if (ex->e_socktype == ANY)
continue;
if (ex->e_protocol == ANY)
continue;
if (pai->ai_socktype == ex->e_socktype
&& pai->ai_protocol != ex->e_protocol) {
ERR(EAI_BADHINTS);
}
}
}
}
/*
* check for special cases. (1) numeric servname is disallowed if
* socktype/protocol are left unspecified. (2) servname is disallowed
* for raw and other inet{,6} sockets.
*/
if (MATCH_FAMILY(pai->ai_family, PF_INET, 1)
#ifdef PF_INET6
|| MATCH_FAMILY(pai->ai_family, PF_INET6, 1)
#endif
) {
ai0 = *pai; /* backup *pai */
if (pai->ai_family == PF_UNSPEC) {
#ifdef PF_INET6
pai->ai_family = PF_INET6;
#else
pai->ai_family = PF_INET;
#endif
}
error = get_portmatch(pai, servname);
if (error)
ERR(error);
*pai = ai0;
}
ai0 = *pai;
/* NULL hostname, or numeric hostname */
for (ex = explore; ex->e_af >= 0; ex++) {
*pai = ai0;
/* PF_UNSPEC entries are prepared for DNS queries only */
if (ex->e_af == PF_UNSPEC)
continue;
if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex)))
continue;
if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex)))
continue;
if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex)))
continue;
if (pai->ai_family == PF_UNSPEC)
pai->ai_family = ex->e_af;
if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
pai->ai_socktype = ex->e_socktype;
if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
pai->ai_protocol = ex->e_protocol;
if (hostname == NULL)
error = explore_null(pai, servname, &cur->ai_next);
else
error = explore_numeric_scope(pai, hostname, servname,
&cur->ai_next);
if (error)
goto free;
while (cur->ai_next)
cur = cur->ai_next;
}
/*
* XXX
* If numeric representation of AF1 can be interpreted as FQDN
* representation of AF2, we need to think again about the code below.
*/
if (sentinel.ai_next)
goto good;
if (hostname == NULL)
ERR(EAI_NODATA);
if (pai->ai_flags & AI_NUMERICHOST)
ERR(EAI_NONAME);
/*
* BEGIN ANDROID CHANGES; proxying to the cache
*/
//如果是Netd进程调用,则判断不成立,因为两个都返回0
//如果非Netd进程调用,则判断成立
if (cache_mode == NULL || strcmp(cache_mode, "local") != 0) {
// we're not the proxy - pass the request to them
return android_getaddrinfo_proxy(hostname, servname, hints, res, netid);
}
/*
* hostname as alphabetical name.
* we would like to prefer AF_INET6 than AF_INET, so we'll make a
* outer loop by AFs.
*/
for (ex = explore; ex->e_af >= 0; ex++) {
*pai = ai0;
/* require exact match for family field */
if (pai->ai_family != ex->e_af)
continue;
if (!MATCH(pai->ai_socktype, ex->e_socktype,
WILD_SOCKTYPE(ex))) {
continue;
}
if (!MATCH(pai->ai_protocol, ex->e_protocol,
WILD_PROTOCOL(ex))) {
continue;
}
if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
pai->ai_socktype = ex->e_socktype;
if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
pai->ai_protocol = ex->e_protocol;
error = explore_fqdn(pai, hostname, servname,
&cur->ai_next, netid, mark);
while (cur && cur->ai_next)
cur = cur->ai_next;
}
/* XXX */
if (sentinel.ai_next)
error = 0;
if (error)
goto free;
if (error == 0) {
if (sentinel.ai_next) {
good:
*res = sentinel.ai_next;
return SUCCESS;
} else
error = EAI_FAIL;
}
free:
bad:
if (sentinel.ai_next)
freeaddrinfo(sentinel.ai_next);
*res = NULL;
return error;
}
非Netd进程调用时,会执行函数 android_getaddrinfo_proxy。
static int
android_getaddrinfo_proxy(
const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res, unsigned netid)
{
int sock;
const int one = 1;
struct sockaddr_un proxy_addr;
FILE* proxy = NULL;
int success = 0;
// Clear this at start, as we use its non-NULLness later (in the
// error path) to decide if we have to free up any memory we
// allocated in the process (before failing).
*res = NULL;
// Bogus things we can't serialize. Don't use the proxy. These will fail - let them.
if ((hostname != NULL &&
strcspn(hostname, " \n\r\t^'\"") != strlen(hostname)) ||
(servname != NULL &&
strcspn(servname, " \n\r\t^'\"") != strlen(servname))) {
return EAI_NODATA;
}
//建立和Netd中 DnsProxyListener 的连接,将请求转发给它去执行
sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (sock < 0) {
return EAI_NODATA;
}
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
memset(&proxy_addr, 0, sizeof(proxy_addr));
proxy_addr.sun_family = AF_UNIX;
strlcpy(proxy_addr.sun_path, "/dev/socket/dnsproxyd",
sizeof(proxy_addr.sun_path));
if (TEMP_FAILURE_RETRY(connect(sock,
(const struct sockaddr*) &proxy_addr,
sizeof(proxy_addr))) != 0) {
close(sock);
return EAI_NODATA;
}
netid = __netdClientDispatch.netIdForResolv(netid);
// Send the request.
//发送请求,处理回复等
proxy = fdopen(sock, "r+");
if (fprintf(proxy, "getaddrinfo %s %s %d %d %d %d %u",
hostname == NULL ? "^" : hostname,
servname == NULL ? "^" : servname,
hints == NULL ? -1 : hints->ai_flags,
hints == NULL ? -1 : hints->ai_family,
hints == NULL ? -1 : hints->ai_socktype,
hints == NULL ? -1 : hints->ai_protocol,
netid) < 0) {
goto exit;
}
// literal NULL byte at end, required by FrameworkListener
if (fputc(0, proxy) == EOF ||
fflush(proxy) != 0) {
goto exit;
}
char buf[4];
// read result code for gethostbyaddr
if (fread(buf, 1, sizeof(buf), proxy) != sizeof(buf)) {
goto exit;
}
int result_code = (int)strtol(buf, NULL, 10);
// verify the code itself
if (result_code != DnsProxyQueryResult ) {
fread(buf, 1, sizeof(buf), proxy);
goto exit;
}
struct addrinfo* ai = NULL;
struct addrinfo** nextres = res;
while (1) {
uint32_t addrinfo_len;
if (fread(&addrinfo_len, sizeof(addrinfo_len),
1, proxy) != 1) {
break;
}
addrinfo_len = ntohl(addrinfo_len);
if (addrinfo_len == 0) {
success = 1;
break;
}
if (addrinfo_len < sizeof(struct addrinfo)) {
break;
}
struct addrinfo* ai = calloc(1, addrinfo_len +
sizeof(struct sockaddr_storage));
if (ai == NULL) {
break;
}
if (fread(ai, addrinfo_len, 1, proxy) != 1) {
// Error; fall through.
break;
}
// Zero out the pointer fields we copied which aren't
// valid in this address space.
ai->ai_addr = NULL;
ai->ai_canonname = NULL;
ai->ai_next = NULL;
// struct sockaddr
uint32_t addr_len;
if (fread(&addr_len, sizeof(addr_len), 1, proxy) != 1) {
break;
}
addr_len = ntohl(addr_len);
if (addr_len != 0) {
if (addr_len > sizeof(struct sockaddr_storage)) {
// Bogus; too big.
break;
}
struct sockaddr* addr = (struct sockaddr*)(ai + 1);
if (fread(addr, addr_len, 1, proxy) != 1) {
break;
}
ai->ai_addr = addr;
}
// cannonname
uint32_t name_len;
if (fread(&name_len, sizeof(name_len), 1, proxy) != 1) {
break;
}
name_len = ntohl(name_len);
if (name_len != 0) {
ai->ai_canonname = (char*) malloc(name_len);
if (fread(ai->ai_canonname, name_len, 1, proxy) != 1) {
break;
}
if (ai->ai_canonname[name_len - 1] != '\0') {
// The proxy should be returning this
// NULL-terminated.
break;
}
}
*nextres = ai;
nextres = &ai->ai_next;
ai = NULL;
}
if (ai != NULL) {
// Clean up partially-built addrinfo that we never ended up
// attaching to the response.
freeaddrinfo(ai);
}
exit:
if (proxy != NULL) {
fclose(proxy);
}
if (success) {
return 0;
}
// Proxy failed;
// clean up memory we might've allocated.
if (*res) {
freeaddrinfo(*res);
*res = NULL;
}
return EAI_NODATA;
}
当非Netd进程调用 getaddrinfo 时,会调用函数 android_getaddrinfo_proxy, 它将和Netd进程中的 dnsproxyd 监听 socket建立连接,然后把请求发给 DNSProxyListener去执行。当然最后还是会调回到 getaddrinfo 去执行,而此时就变成了 Netd进程的调用了,Netd进程将向指定的 DNS 服务器发起请求以解析域名。 Android系统中通过这种方式来管理DNS的好处是,所有解析后得到的 DNS 记录都将缓存在 Netd 进程中,从而使这些信息成为一个公共的资源,最大程度做到信息共享。
MDnsSd是Multicast DNS Service Discovery的简称,它和Apple公司的Bonjour技术有关,故本节将先介绍Apple Bonjour技术。 Bonjour是法语中的Hello之意。它是Apple公司为基于组播域名服务(multicast DNS)的开放性零配置网络标准所起的名字。使用Bonjour的设备在网络中自动组播它们自己的服务信息并监听其他设备的服务信息,设备之间就像在打招呼,这也是该技术命名为Bonjour的原因。Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。 举一个简单的例子:在局域网中,如果要进行打印服务,就必须先知道打印服务器的IP地址。此IP地址一般由IT部门的人负责分配,然后他还得全员发邮件以公示此地址。有了Bonjour以后,打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务,名为“print service”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机。在Bonjour的帮助下,客户端最终能找到这台注册了“print service”名字的打印机,并获得它的IP地址以及端口号。 从Bonjour角度来看,该技术主要解决了三个问题:
MDnsSdListener 对应的 Framework 层服务为 NsdService(Nsd为 Network Service Discovery的缩写)。 MDnsSdListener的内部类 Monitor 用于和 mdnsd 后台进程通信,它将调用Bonjour AIP。 Monitor 内部针对每个 DNSService 都会建立一个 Element 对象,该对象通过 Monitor 的 mHead 指针保存在一个list中。 MDnsSdListener 运行过程主要可分成三步:
Netd的处理流程:
Network Daemon(Android Netd)架构和源码分析
Netd是Android的网络守护进程。NetD是个网络管家,封装了复杂的底层各种类型的网络(NAT,PLAN,PPP,SOFTAP,TECHER,ETHO,MDNS等),隔离了底层网络接口的差异,给Framework提供了统一调用接口,简化了网络的使用。NetD主要功能是:
NetD程序的大概逻辑: [Kernel] <--Network Event--> (NetD) <--Command--> [Framework]。事件和命令传送采用跨进程通信机制(SOCKET).
Netd:
/System/netd
/system/core/libsysutils/src
/system/core/include/sysutils
Framework:
/frameworks/base/services/java/com/android/server
int main() {
CommandListener *cl;
NetlinkManager *nm;
DnsProxyListener *dpl;
MDnsSdListener *mdnsl;
FwmarkServer* fwmarkServer;
ALOGI("Netd 1.0 starting");
blockSigpipe();
//NetlinkManager是用于接收kernel中的uevent
if (!(nm = NetlinkManager::Instance())) {
ALOGE("Unable to create NetlinkManager");
exit(1);
};
//接收来自Framework层NetworkManageService的命令
cl = new CommandListener();
nm->setBroadcaster((SocketListener *) cl);
if (nm->start()) {
ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));
exit(1);
}
// Set local DNS mode, to prevent bionic from proxying
// back to this service, recursively.
//对接frameworklistener
//GetAddrInfoCmd,和Bionic C库的getaddrinfo函数对应。
//GetHostByAddrCmd,和Bionic C库的gethostbyaddr函数对应。
setenv("ANDROID_DNS_MODE", "local", 1);
dpl = new DnsProxyListener(CommandListener::sNetCtrl);
if (dpl->startListener()) {
ALOGE("Unable to start DnsProxyListener (%s)", strerror(errno));
exit(1);
}
//MDnsSd是Multicast DNS Service Discovery的简称,它和Apple公司的Bonjour技术有关
//Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。
//举一个简单的例子:在局域网中,如果要进行打印服务,必须先知道打印服务器的IP地址。
//此IP地址一般由IT部门的人负责分配,然后他还得全员发邮件以公示此地址。有了Bonjour以后,
//打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务?
//名为“print service”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。
//由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机
mdnsl = new MDnsSdListener();
if (mdnsl->startListener()) {
ALOGE("Unable to start MDnsSdListener (%s)", strerror(errno));
exit(1);
}
//创建 FwmarkServer并启动监听,它将创建名为fwmarkd的监听
fwmarkServer = new FwmarkServer(CommandListener::sNetCtrl);
if (fwmarkServer->startListener()) {
ALOGE("Unable to start FwmarkServer (%s)", strerror(errno));
exit(1);
}
/*
* Now that we're up, we can respond to commands
*/
if (cl->startListener()) {
ALOGE("Unable to start CommandListener (%s)", strerror(errno));
exit(1);
}
// Eventually we'll become the monitoring thread
while(1) {
sleep(1000);
}
ALOGI("Netd exiting");
exit(0);
}
Netd的main函数主要是创建几个重要成员并启动相应的工作,这四个重要成员分别如下: NetlinkManager:接收并处理来自Kernel的UEvent消息。这些消息经NetlinkManager解析后将借助它的Broadcaster(也就是代码中为NetlinkManager设置的CommandListener)发送给Framework层的 NetworkManagementService。
CommandListener、DnsProxyListener、MDnsSDListener、FwmarkServer:分别创建名为netd、dnsproxyd、mdns、fwmarkServer的监听socket.
下面针对上面提到主要成员进行分析。
int NetlinkManager::start() {
//创建接收 NETLINK_KOBJECT_UEVENT 消息的socket,其值保存在mUeventSock中
//其中, NETLINK_FORMAT_ASCII 代表UEvent消息的内容为 ASCII字符串
if ((mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT,
0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII)) == NULL) {
return -1;
}
//创建接收 RTMGRP_LINK 消息的socket,其值保存在 mRouteSock 中
//其中, NETLINK_FORMAT_BINARY 代表 UEvent 消息的类型为结构体,故需要进行二进制解析
if ((mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE,
RTMGRP_LINK |
RTMGRP_IPV4_IFADDR |
RTMGRP_IPV6_IFADDR |
RTMGRP_IPV6_ROUTE |
(1 << (RTNLGRP_ND_USEROPT - 1)),
NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) {
return -1;
}
//创建接收 NETLINK_NFLOG 消息的socket,其值保存在 mQuotaSock 中
if ((mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG,
NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) {
ALOGE("Unable to open quota2 logging socket");
// TODO: return -1 once the emulator gets a new kernel.
}
return 0;
}
NM的start函数主要是向Kernel注册三个用于接收UEvent事件的socket,这三个UEvent分别对应于以下内容
NetlinkHandler接收到的UEvent消息会转换成一个NetlinkEvent对象。NetlinkEvent对象封装了对 UEvent消息的解析方法。对于 NETLINK_FORMAT_ASCII类型,其 parseAsciiNetlinkMessage函数会被调用,而对于 NETLINK_FORMAT_BINARY类型,其 parseBinaryNetlinkMessage 函数会被调用。 NM处理流程的输入为一个解析后的 NetlinkEvent对象。NM完成相应工作后,其处理结果将经由 mBroadcaster对象传递给 Framework层的接收者,也就是 NetworkManagementService。 CommandListener 从 FrameworkListener派生,而 FrameworkListener 内部有一个数组 mCommands,用来存储注册到 FrameworkListener中的命令处理对象。
Netd中第二个重要成员是CommandListener(以后简称CL),其主要作用是接收来自Framework层NetworkManageService的命令。从角色来看,CL仅是一个Listener。它在收到命令后,只是将它们转交给对应的命令处理对象去处理。CL内部定义了许多命令,而这些命令都有较深的背景知识。本节拟以分析CL的工作流程为主,而相关的命令处理则放到后文再集中分析。
CL定义了11个和网络相关的Command类。这些类均从NetdCommand派生(注意,为保持绘图简洁,这11个Command的派生关系由1个派生箭头表达)。 CL还定义了10个控制类,这些控制类将和命令类共同完成相应的命令处理工作
android-5.1/system/netd/server/CommandListener.cpp
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());
registerCmd(new ClatdCmd());
registerCmd(new NetworkCommand());
//创建对应的控制类对象
if (!sNetCtrl)
sNetCtrl = new NetworkController();
if (!sTetherCtrl)
sTetherCtrl = new TetherController();
if (!sNatCtrl)
sNatCtrl = new NatController();
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();
if (!sClatdCtrl)
sClatdCtrl = new ClatdController(sNetCtrl);
/*
* This is the only time we touch top-level chains in iptables; controllers
* should only mutate rules inside of their children chains, as created by
* the constants above.
*
* Modules should never ACCEPT packets (except in well-justified cases);
* they should instead defer to any remaining modules using RETURN, or
* otherwise DROP/REJECT.
*/
// Create chains for children modules
//初始化 iptables 中的各个Table 及相应 Chain 和 Rules
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, "mangle", "FORWARD", MANGLE_FORWARD);
createChildChains(V4, "nat", "PREROUTING", NAT_PREROUTING);
createChildChains(V4, "nat", "POSTROUTING", NAT_POSTROUTING);
// Let each module setup their child chains
//netd允许OEM厂商可自定义一些规则,这些规则在 /system/bin/oem-iptables-init.sh文件中保存
setupOemIptablesHook();
/* When enabled, DROPs all packets except those matching rules. */
//初始化 iptables中的一些chain,以及初始化路由表
sFirewallCtrl->setupIptablesHooks();
/* Does DROPs in FORWARD by default */
sNatCtrl->setupIptablesHooks();
/*
* Does REJECT in INPUT, OUTPUT. Does counting also.
* No DROP/REJECT allowed later in netfilter-flow hook order.
*/
sBandwidthCtrl->setupIptablesHooks();
/*
* Counts in nat: PREROUTING, POSTROUTING.
* No DROP/REJECT allowed later in netfilter-flow hook order.
*/
sIdletimerCtrl->setupIptablesHooks();
//初始化时,Netd将禁止带宽控制功能
sBandwidthCtrl->enableBandwidthControl(false);
if (int ret = RouteController::Init(NetworkController::LOCAL_NET_ID)) {
ALOGE("failed to initialize RouteController (%s)", strerror(-ret));
}
}
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有两个:
android-5.1/bionic/libc/dns/net/getaddrinfo.c
int
getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res)
{
//Android平台的特殊定制
return android_getaddrinfofornet(hostname, servname, hints, NETID_UNSET, MARK_UNSET, res);
}
Android平台的 getaddrinfo 会调用其定制的 android_getaddrinfofornet 函数完成一些特殊操作:
int
android_getaddrinfofornet(const char *hostname, const char *servname,
const struct addrinfo *hints, unsigned netid, unsigned mark, struct addrinfo **res)
{
struct addrinfo sentinel;
struct addrinfo *cur;
int error = 0;
struct addrinfo ai;
struct addrinfo ai0;
struct addrinfo *pai;
const struct explore *ex;
//取 ANDROID_DNS_MODE 环境变量
//只有android-5.1/system/netd/server/main.cpp中设置了此变量:setenv("ANDROID_DNS_MODE", "local", 1);
const char* cache_mode = getenv("ANDROID_DNS_MODE");
/* hostname is allowed to be NULL */
/* servname is allowed to be NULL */
/* hints is allowed to be NULL */
assert(res != NULL);
memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;
pai = &ai;
pai->ai_flags = 0;
pai->ai_family = PF_UNSPEC;
pai->ai_socktype = ANY;
pai->ai_protocol = ANY;
pai->ai_addrlen = 0;
pai->ai_canonname = NULL;
pai->ai_addr = NULL;
pai->ai_next = NULL;
if (hostname == NULL && servname == NULL)
return EAI_NONAME;
if (hints) {
/* error check for hints */
if (hints->ai_addrlen || hints->ai_canonname ||
hints->ai_addr || hints->ai_next)
ERR(EAI_BADHINTS); /* xxx */
if (hints->ai_flags & ~AI_MASK)
ERR(EAI_BADFLAGS);
switch (hints->ai_family) {
case PF_UNSPEC:
case PF_INET:
#ifdef INET6
case PF_INET6:
#endif
break;
default:
ERR(EAI_FAMILY);
}
memcpy(pai, hints, sizeof(*pai));
/*
* if both socktype/protocol are specified, check if they
* are meaningful combination.
*/
if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) {
for (ex = explore; ex->e_af >= 0; ex++) {
if (pai->ai_family != ex->e_af)
continue;
if (ex->e_socktype == ANY)
continue;
if (ex->e_protocol == ANY)
continue;
if (pai->ai_socktype == ex->e_socktype
&& pai->ai_protocol != ex->e_protocol) {
ERR(EAI_BADHINTS);
}
}
}
}
/*
* check for special cases. (1) numeric servname is disallowed if
* socktype/protocol are left unspecified. (2) servname is disallowed
* for raw and other inet{,6} sockets.
*/
if (MATCH_FAMILY(pai->ai_family, PF_INET, 1)
#ifdef PF_INET6
|| MATCH_FAMILY(pai->ai_family, PF_INET6, 1)
#endif
) {
ai0 = *pai; /* backup *pai */
if (pai->ai_family == PF_UNSPEC) {
#ifdef PF_INET6
pai->ai_family = PF_INET6;
#else
pai->ai_family = PF_INET;
#endif
}
error = get_portmatch(pai, servname);
if (error)
ERR(error);
*pai = ai0;
}
ai0 = *pai;
/* NULL hostname, or numeric hostname */
for (ex = explore; ex->e_af >= 0; ex++) {
*pai = ai0;
/* PF_UNSPEC entries are prepared for DNS queries only */
if (ex->e_af == PF_UNSPEC)
continue;
if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex)))
continue;
if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex)))
continue;
if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex)))
continue;
if (pai->ai_family == PF_UNSPEC)
pai->ai_family = ex->e_af;
if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
pai->ai_socktype = ex->e_socktype;
if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
pai->ai_protocol = ex->e_protocol;
if (hostname == NULL)
error = explore_null(pai, servname, &cur->ai_next);
else
error = explore_numeric_scope(pai, hostname, servname,
&cur->ai_next);
if (error)
goto free;
while (cur->ai_next)
cur = cur->ai_next;
}
/*
* XXX
* If numeric representation of AF1 can be interpreted as FQDN
* representation of AF2, we need to think again about the code below.
*/
if (sentinel.ai_next)
goto good;
if (hostname == NULL)
ERR(EAI_NODATA);
if (pai->ai_flags & AI_NUMERICHOST)
ERR(EAI_NONAME);
/*
* BEGIN ANDROID CHANGES; proxying to the cache
*/
//如果是Netd进程调用,则判断不成立,因为两个都返回0
//如果非Netd进程调用,则判断成立
if (cache_mode == NULL || strcmp(cache_mode, "local") != 0) {
// we're not the proxy - pass the request to them
return android_getaddrinfo_proxy(hostname, servname, hints, res, netid);
}
/*
* hostname as alphabetical name.
* we would like to prefer AF_INET6 than AF_INET, so we'll make a
* outer loop by AFs.
*/
for (ex = explore; ex->e_af >= 0; ex++) {
*pai = ai0;
/* require exact match for family field */
if (pai->ai_family != ex->e_af)
continue;
if (!MATCH(pai->ai_socktype, ex->e_socktype,
WILD_SOCKTYPE(ex))) {
continue;
}
if (!MATCH(pai->ai_protocol, ex->e_protocol,
WILD_PROTOCOL(ex))) {
continue;
}
if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
pai->ai_socktype = ex->e_socktype;
if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
pai->ai_protocol = ex->e_protocol;
error = explore_fqdn(pai, hostname, servname,
&cur->ai_next, netid, mark);
while (cur && cur->ai_next)
cur = cur->ai_next;
}
/* XXX */
if (sentinel.ai_next)
error = 0;
if (error)
goto free;
if (error == 0) {
if (sentinel.ai_next) {
good:
*res = sentinel.ai_next;
return SUCCESS;
} else
error = EAI_FAIL;
}
free:
bad:
if (sentinel.ai_next)
freeaddrinfo(sentinel.ai_next);
*res = NULL;
return error;
}
非Netd进程调用时,会执行函数 android_getaddrinfo_proxy。
static int
android_getaddrinfo_proxy(
const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res, unsigned netid)
{
int sock;
const int one = 1;
struct sockaddr_un proxy_addr;
FILE* proxy = NULL;
int success = 0;
// Clear this at start, as we use its non-NULLness later (in the
// error path) to decide if we have to free up any memory we
// allocated in the process (before failing).
*res = NULL;
// Bogus things we can't serialize. Don't use the proxy. These will fail - let them.
if ((hostname != NULL &&
strcspn(hostname, " \n\r\t^'\"") != strlen(hostname)) ||
(servname != NULL &&
strcspn(servname, " \n\r\t^'\"") != strlen(servname))) {
return EAI_NODATA;
}
//建立和Netd中 DnsProxyListener 的连接,将请求转发给它去执行
sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (sock < 0) {
return EAI_NODATA;
}
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
memset(&proxy_addr, 0, sizeof(proxy_addr));
proxy_addr.sun_family = AF_UNIX;
strlcpy(proxy_addr.sun_path, "/dev/socket/dnsproxyd",
sizeof(proxy_addr.sun_path));
if (TEMP_FAILURE_RETRY(connect(sock,
(const struct sockaddr*) &proxy_addr,
sizeof(proxy_addr))) != 0) {
close(sock);
return EAI_NODATA;
}
netid = __netdClientDispatch.netIdForResolv(netid);
// Send the request.
//发送请求,处理回复等
proxy = fdopen(sock, "r+");
if (fprintf(proxy, "getaddrinfo %s %s %d %d %d %d %u",
hostname == NULL ? "^" : hostname,
servname == NULL ? "^" : servname,
hints == NULL ? -1 : hints->ai_flags,
hints == NULL ? -1 : hints->ai_family,
hints == NULL ? -1 : hints->ai_socktype,
hints == NULL ? -1 : hints->ai_protocol,
netid) < 0) {
goto exit;
}
// literal NULL byte at end, required by FrameworkListener
if (fputc(0, proxy) == EOF ||
fflush(proxy) != 0) {
goto exit;
}
char buf[4];
// read result code for gethostbyaddr
if (fread(buf, 1, sizeof(buf), proxy) != sizeof(buf)) {
goto exit;
}
int result_code = (int)strtol(buf, NULL, 10);
// verify the code itself
if (result_code != DnsProxyQueryResult ) {
fread(buf, 1, sizeof(buf), proxy);
goto exit;
}
struct addrinfo* ai = NULL;
struct addrinfo** nextres = res;
while (1) {
uint32_t addrinfo_len;
if (fread(&addrinfo_len, sizeof(addrinfo_len),
1, proxy) != 1) {
break;
}
addrinfo_len = ntohl(addrinfo_len);
if (addrinfo_len == 0) {
success = 1;
break;
}
if (addrinfo_len < sizeof(struct addrinfo)) {
break;
}
struct addrinfo* ai = calloc(1, addrinfo_len +
sizeof(struct sockaddr_storage));
if (ai == NULL) {
break;
}
if (fread(ai, addrinfo_len, 1, proxy) != 1) {
// Error; fall through.
break;
}
// Zero out the pointer fields we copied which aren't
// valid in this address space.
ai->ai_addr = NULL;
ai->ai_canonname = NULL;
ai->ai_next = NULL;
// struct sockaddr
uint32_t addr_len;
if (fread(&addr_len, sizeof(addr_len), 1, proxy) != 1) {
break;
}
addr_len = ntohl(addr_len);
if (addr_len != 0) {
if (addr_len > sizeof(struct sockaddr_storage)) {
// Bogus; too big.
break;
}
struct sockaddr* addr = (struct sockaddr*)(ai + 1);
if (fread(addr, addr_len, 1, proxy) != 1) {
break;
}
ai->ai_addr = addr;
}
// cannonname
uint32_t name_len;
if (fread(&name_len, sizeof(name_len), 1, proxy) != 1) {
break;
}
name_len = ntohl(name_len);
if (name_len != 0) {
ai->ai_canonname = (char*) malloc(name_len);
if (fread(ai->ai_canonname, name_len, 1, proxy) != 1) {
break;
}
if (ai->ai_canonname[name_len - 1] != '\0') {
// The proxy should be returning this
// NULL-terminated.
break;
}
}
*nextres = ai;
nextres = &ai->ai_next;
ai = NULL;
}
if (ai != NULL) {
// Clean up partially-built addrinfo that we never ended up
// attaching to the response.
freeaddrinfo(ai);
}
exit:
if (proxy != NULL) {
fclose(proxy);
}
if (success) {
return 0;
}
// Proxy failed;
// clean up memory we might've allocated.
if (*res) {
freeaddrinfo(*res);
*res = NULL;
}
return EAI_NODATA;
}
当非Netd进程调用 getaddrinfo 时,会调用函数 android_getaddrinfo_proxy, 它将和Netd进程中的 dnsproxyd 监听 socket建立连接,然后把请求发给 DNSProxyListener去执行。当然最后还是会调回到 getaddrinfo 去执行,而此时就变成了 Netd进程的调用了,Netd进程将向指定的 DNS 服务器发起请求以解析域名。 Android系统中通过这种方式来管理DNS的好处是,所有解析后得到的 DNS 记录都将缓存在 Netd 进程中,从而使这些信息成为一个公共的资源,最大程度做到信息共享。
MDnsSd是Multicast DNS Service Discovery的简称,它和Apple公司的Bonjour技术有关,故本节将先介绍Apple Bonjour技术。 Bonjour是法语中的Hello之意。它是Apple公司为基于组播域名服务(multicast DNS)的开放性零配置网络标准所起的名字。使用Bonjour的设备在网络中自动组播它们自己的服务信息并监听其他设备的服务信息,设备之间就像在打招呼,这也是该技术命名为Bonjour的原因。Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。 举一个简单的例子:在局域网中,如果要进行打印服务,就必须先知道打印服务器的IP地址。此IP地址一般由IT部门的人负责分配,然后他还得全员发邮件以公示此地址。有了Bonjour以后,打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务,名为“print service”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机。在Bonjour的帮助下,客户端最终能找到这台注册了“print service”名字的打印机,并获得它的IP地址以及端口号。 从Bonjour角度来看,该技术主要解决了三个问题:
MDnsSdListener 对应的 Framework 层服务为 NsdService(Nsd为 Network Service Discovery的缩写)。 MDnsSdListener的内部类 Monitor 用于和 mdnsd 后台进程通信,它将调用Bonjour AIP。 Monitor 内部针对每个 DNSService 都会建立一个 Element 对象,该对象通过 Monitor 的 mHead 指针保存在一个list中。 MDnsSdListener 运行过程主要可分成三步:
MDnsSdListener::Monitor::Monitor() {
mHead = NULL;
mLiveCount = 0;
mPollFds = NULL;
mPollRefs = NULL;
mPollSize = 10;
//创建两个socket,用于接收 MDnsSdListener 对象的命令
socketpair(AF_LOCAL, SOCK_STREAM, 0, mCtrlSocketPair);
pthread_mutex_init(&mHeadMutex, NULL);
//创建线程,线程函数是 threadStart,其内部会调用 run
pthread_create(&mThread, NULL, MDnsSdListener::Monitor::threadStart, this);
pthread_detach(mThread);
}
Monitor的threadStart线程将调用其 run 函数,该函数通过 poll 方式侦听包括 mCtrlSocketPair 在内的 socket 信息。 当 NsdService 发送 start-service 命令后, Handler 的 runCommand 将执行 Monitor 的 startService 函数: android-5.1/system/netd/server/MDnsSdListener.cpp
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) {
ALOGD("Starting MDNSD");
//如果 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 函数将被调用: android-5.1/system/netd/server/MDnsSdListener.cpp
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) {
if (VDBG) {
ALOGD("serviceRegister(%d, %s, %s, %s, %s, %s, %d, %d, <binary>)", requestId,
interfaceName, serviceName, serviceType, domain, host, port, txtLen);
}
Context *context = new Context(requestId, mListener);
DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
port = htons(port);
if (ref == NULL) {
ALOGE("requestId %d already in use during register call", requestId);
cli->sendMsg(ResponseCode::CommandParameterError,
"RequestId already in use during register call", false);
return;
}
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) {
ALOGE("service register request %d got an error from DNSServiceRegister %d", requestId,
result);
mMonitor->freeServiceRef(requestId);
cli->sendMsg(ResponseCode::CommandParameterError,
"serviceRegister request got an error from DNSServiceRegister", false);
return;
}
调用 Monitor 对象进行 rescan
mMonitor->startMonitoring(requestId);
if (VDBG) ALOGD("serviceRegister successful");
cli->sendMsg(ResponseCode::CommandOkay, "serviceRegister started", false);
return;
}
DNSServiceRegister 内部将把请求发送给 mdnsd 去处理,处理的结果通过 MDnsSdListenerRegisterCallback 返回:
void MDnsSdListenerRegisterCallback(DNSServiceRef /* sdRef */, DNSServiceFlags /* flags */,
DNSServiceErrorType errorCode, const char *serviceName, const char * /* regType */,
const char * /* domain */, void *inContext) {
MDnsSdListener::Context *context = reinterpret_cast<MDnsSdListener::Context *>(inContext);
char *msg;
int refNumber = context->mRefNumber;
if (errorCode != kDNSServiceErr_NoError) {
asprintf(&msg, "%d %d", refNumber, errorCode);
context->mListener->sendBroadcast(ResponseCode::ServiceRegistrationFailed, msg, false);
if (DBG) ALOGE("register failure for %d, error= %d", refNumber, errorCode);
} else {
char *quotedServiceName = SocketClient::quoteArg(serviceName);
asprintf(&msg, "%d %s", refNumber, quotedServiceName);
free(quotedServiceName);
//将处理结果返回给 NsdService
context->mListener->sendBroadcast(ResponseCode::ServiceRegistrationSucceeded, msg, false);
if (VDBG) ALOGD("register succeeded for %d as %s", refNumber, serviceName);
}
free(msg);
}
Netd的处理流程: