概述
Android 以太网的整体框架结构
前言
OS会运行着很多守护进程(daemon)来完成很多杂乱的工作。通过系统中的init.rc文件也可看出,其中每个service中就包含着系统后台服务进程。
而这些服务被分为:core类服务(adbd/servicemanager/healthd/lmkd/logd/vold)和main类服务;main类服务又分为:网络类服务(Netd/mdnsd/mtpd/rild)、图形及媒体类服务(surfaceflinger/bootanimation/mediaserver/dnnserver)、其他类服务(installd/keystore/debuggerd/sdcard/Zygote)
定义
Netd是Network守护进程。负责网络管理后台daemon程序。类似命名有:Vold(Volumn Deamon),Rild(Radio Interface Layer Deamon)。位于FW和 Kernel间,是网络消息转发及处理的中枢。封装了底层各种类型的网络(NAT,PLAN,PPP,SOFTAP,TECHER,ETHO,MDNS 等),隔离了底层网络接口差异,给FW提供了统1调用接口,简化网络的使用。
Netd是网络管家,负责网络的配置/管理/查询等,如:带宽控制,流量统计,NAT、个人局域网、PPP链接、soft-ap、共享上网、配置路由表、interface配置管理等。
通过Netlink、虚拟文件系统等linux内核提供的用户接口,通信内核/直接执行系统模块,管理网络相关部分。
信息上报流程
Kernel<--Network Event--> (Netd) <--Command--> [FW]。事件和命令传送采取socket通信机制
功能
-
设置Firewall、NAT、带宽控制、Soft Access Point控制,网络设备绑定(Tether)等。
-
DNS 信息的缓存和管理
-
网络服务搜索(Net Service Discovery,NSD),包括Service Registration、Service Browse和Service Resolve等。APP可通过它查询各种网络的状态/拦截各种网络事件。
基本原理
接收命令
需更改网络配置时,可通过Netdctl命令/Binder RPC 调用等方式向 Netd 发送命令。Netd监听这些命令并执行相应的操作。
处理命令
Netd 收到命令后按类型将其分配给不同的处理程序。如,若是针对网络接口的命令,则会将其发送给netfilter模块;若是针对NAT的命令,则会将其发送给 Conntrack 模块。
执行操作
处理程序在接收到命令后会执行相应操作。如,netfilter模块可按命令增删改查处理包的规则;Conntrack 模块可按命令查找/更新NAT表项等。
返回结果
处理程序执行完命令后,会将结果返回给 Netd。Netd 会将结果发送给相应请求的客户端,以便其了解命令的执行情况。
Netd 工作流程
Netd 作为内核与FW的沟通桥梁:
1)CommandListener接收并处理来自FW(NMS/NsdService)的网络请求/命令,这些命令最终由Netd中对应的Command对象去处理,向FW层反馈处理结果。
2)监听网络事件(断开/连接/错误等),向FW层上报。Netd 接收并解析来自 Kernel 的 UEvent 消息(NetlinkManager),再转发给 FW层中对应 Service 去处理。UEvent 消息的3个类型:
- kobject, 通知内核中某个模块的加载/卸载;
- route, 网络链路断开/接通;
- nflog, 带宽控制相关;
Netd相关的基本框架
1)Kernel用于检测network相关的所有event事件。
2)Netd作为 Kernel与FW间通信的桥梁。
3)FW层操作Netd,向Netd发送操作命令。
4)UI与FW交互,用于用户进行网络的操控。
源码
CommandListener.cpp
监听FW层命令,实现对应命令的处理函数
NetlinkManager.cpp
监听 kernel 的 event
NetdCommand.cpp
NetlinkHandler.cpp
继承自 NetlinkListener
实现onEvent 函数
按 onEvent 函数命令的解析调用不同的处理函数
并调用 nm->sendBroadcast 广播 ResponseCode
DnsProxyListener.cpp
DNS 解析相关独立部分
MdnsSdListener.cpp
Multi-DNS 解析独立部分
CommandListener、NetlinkHandler
命令的上传/下达
各种cmd功能
Netd工作流程
Netd 服务注册过程
在SystemServiceRegistry中,将 Netd 注册成系统服务:
csharp
registerService(Context.Netd_SERVICE, IBinder.class, new StaticServiceFetcher<IBinder>() {
@Override
public IBinder createService() throws ServiceNotFoundException {
return ServiceManager.getServiceOrThrow(Context.Netd_SERVICE);
}
});
注册后,可通过Context的 getSystemService 获取此服务。其中服务名 Netd_SERVICE 的值为 "Netd",createService函数是向service manager获取服务的 binder 代理。
在 system/Netd/server/main.cpp 的 main 函数中向 service manager 注册了服务:
scss
int main() {
...
if ((ret = NetdNativeService::start()) != android::OK) {
ALOGE("Unable to start NetdNativeService: %d", ret);
exit(1);
}
...
IPCThreadState::self()->joinThreadPool();
exit(0);
}
NetdNativeService 的 start 函数:
rust
status_t NetdNativeService::start() {
IPCThreadState::self()->disableBackgroundScheduling(true);
const status_t ret = BinderService<NetdNativeService>::publish();
if (ret != android::OK) {
return ret;
}
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
ps->giveThreadPoolName();
return android::OK;
}
为简化代码,1般会调用 BinderService 类的 publish 函数进行服务注册:
arduino
template<typename SERVICE>
static status_t publish(bool allowIsolated = false,
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
sp<IServiceManager> sm(defaultServiceManager());
return sm->addService(String16(SERVICE::getServiceName()), new SERVICE(), allowIsolated,
dumpFlags);
}
其中服务名由 NetdNativeService 的静态成员函数 getServiceName 指定:
arduino
static char const* getServiceName() { return "Netd"; }
注意使用 BinderService 类注册服务的方式,服务类1般可继承 BinderService:
kotlin
class NetdNativeService : public BinderService<NetdNativeService>, public BnNetd
Netd.rc
Netd进程是1个可执行的bin文件程序,Android 5及以后Android已将rc权力下放到各个子模块中然后通过import导入了,Netd的启动下放到了Netd.rc。Netd进程由init进程按init.rc的对应配置项而启动。通过命令行可看到:
perl
service Netd /system/bin/Netd
class main
socket Netd stream 0660 root system
socket dnsproxyd stream 0660 root inet
socket mdns stream 0660 root system
socket fwmarkd stream 0660 root inet
- Netd启动时将创建4个TCP监听socket:Netd、dnsproxyd、mdns、fwmarked
- Netd主要与FW的NMS交互, 用于控制网口状态、路由表
- dnsproxyd: DNS代理的控制与配置,用于私有DNS的请求转发。每1个调用和域名解析相关的socket API(如getaddrinfo/gethostbyname等)的进程都会借由"dnsproxyd"监听socket与Netd建立链接。
- mdns: 多播DNS,用于基于WIFI连接的服务发现(NSD, Network Service Discovery)
- fwmarkd 和底层kernel交互,防火墙会对进来的包做标记。
main函数
native进程通常从main函数入手。/system/Netd/server/main.cpp
scss
int main() {
//创建NM并启动
NetlinkManager *nm = NetlinkManager::Instance();
if (nm->start()) {
ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));
exit(1);
}
//创建MDnsSdListener并启动监听,它将创建mdns的监听socket
for (const auto& sock :
{DNSPROXYLISTENER_SOCKET_NAME, FwmarkServer::SOCKET_NAME, MDnsSdListener::SOCKET_NAME}) {
setCloseOnExec(sock);
gLog.info("setCloseOnExec(%s)", sock);
}
//创建fwmarkServer并启动监听,他将创建fwmarked的监听socket
FwmarkServer fwmarkServer(&gCtls->netCtrl, &gCtls->eventReporter);
if (fwmarkServer.startListener()) {
ALOGE("Unable to start FwmarkServer (%s)", strerror(errno));
exit(1);
}
//启动Netd 的binder servervice
if ((ret = NetdNativeService::start()) != android::OK) {
ALOGE("Unable to start NetdNativeService: %d", ret);
exit(1);
}
//开启binder线程
IPCThreadState::self()->joinThreadPool();
}
main函数很简单,主要是创建几个重要成员并启动相应的工作,这几个重要成员分别是:
- NM:接收并处理来自Kernel的UEvent消息。这些消息被解析后将借助它的Broadcaster(即代码中为NetlinkManager设置的CommandListener)发送给FW的NMS。
- CommandListener、DnsProxyListener、MDnsSdListener:分别创建名为"Netd"、"dnsproxyd"、"mdns"的监听socket,并处理来客户端的命令。
NM
关键类图
Netlink是1个异步通信机制的socket,区别于系统调用和ioctl的同步调用机制。
1、NM主要负责接收并解析来自Kernel的UEvent消息。其核心代码在start函数中:
php
///system/Netd/NetlinkManager.cpp
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, false)) == NULL) {
return -1;
}
//创建接收Netlink_ROUTE消息的socket,值保存在mRouteSock中
//其中,Netlink_FORMAT_BINARY代表UEvent消息的类型为结构体,故需进行2进制解析
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, false)) == NULL) {
return -1;
}
//创建接收Netlink_NFLOG消息的socket,其值保存在mQuotaSock中
if ((mQuotaHandler = setupSocket(&mQuotaSock, Netlink_NFLOG,
NFLOG_QUOTA_GROUP, NetlinkListener::Netlink_FORMAT_BINARY, false)) == NULL) {
ALOGE("Unable to open quota socket");
// TODO: return -1 once the emulator gets a new kernel.
}
//创建接收Netlink_NETFILTER消息的socket,值保存在mStrictSock中
if ((mStrictHandler = setupSocket(&mStrictSock, Netlink_NETFILTER,
0, NetlinkListener::Netlink_FORMAT_BINARY_UNICAST, true)) == NULL) {
ALOGE("Unable to open strict socket");
// TODO: return -1 once the emulator gets a new kernel.
}
return 0;
}
NM主要是向kernel注册4个用于接收UEvent事件的socket:
- Netlink_KOBJECT_UEVENT:代表kobject事件,由于这些事件包含的信息有ASCII字符串表达,故上述代码使用了Netlink_FORMAT_ASCII。它表示将采用字符串解析的方法去解析接收到的UEvent消息。NM关注的是/sys/class/net模块下的加载/卸载消息
- Netlink_ROUTE:kernel中路由或链路改变时对应的消息。包含很多子项,上述代码使用了RTMGRP_LINK。2者结合使用,表示NM希望收到网络链路断开/建立对应的UEvent的消息,由于对应UEVENT消息内部封装了nlmsghdr等相关结构体,故采用Netlink_FORMAT_BINARY来指示解析解析UEvent消息时将使用二进制的解析方法。
- Netlink_NFLOG:带宽控制有关。Netd的带宽控制可设置1个预警值,当网络数据超过1定字节数就会触发kernel发送1个警告。该功能属于iptables的扩展功能
- Netlink_NETFILTER:利用1些封包过滤的规则设定,来定义是否接收包。
2、kernel消息上报流程:
kernel-->NM-->NetlinkListener-->NetlinkEvent-->NetlinkHandler-->SocketListener-->NMS
3、每1个Netlink的socket都会新建1个NetlinkHandler, 用于处理内核的消息, 并将该消息广播给上层。NetlinkHandler的onEvent函数,内部针对不同属性的NetlinkEvent进行了分类处理。
scss
void NetlinkHandler::onEvent(NetlinkEvent *evt) {
const char *subsys = evt->getSubsystem();
if (!subsys) {
ALOGW("No subsystem found in Netlink event");
return;
}
//处理对应Netlink_KOBJECT_UEVENT和Netlink_ROUTE的信息
if (!strcmp(subsys, "net")) {
NetlinkEvent::Action action = evt->getAction();
//查找消息中携带的网络设备名
const char *iface = evt->findParam("INTERFACE");
if (action == NetlinkEvent::Action::kAdd) {
//添加NIC的消息
notifyInterfaceAdded(iface);
} else if (action == NetlinkEvent::Action::kRemove) {
//NIC被移除的消息
notifyInterfaceRemoved(iface);
} else if (action == NetlinkEvent::Action::kChange) {
//NIC变化消息
evt->dump();
const char *alertName = evt->findParam("ALERT_NAME");
const char *iface = evt->findParam("INTERFACE");
if (alertName != NULL && iface != NULL) {
ALOGI("Alertname : %s iface (%s)", alertName, iface);
if (!strcmp(alertName, "quotaReachedAlert") && iface) {
notifyQuotaLimitReached(alertName, iface);
}
} else {
notifyInterfaceChanged("nana", true);
}
} else if (action == NetlinkEvent::Action::kLinkUp) {
//链路启用(类似插网线)
notifyInterfaceLinkChanged(iface, true);
} else if (action == NetlinkEvent::Action::kLinkDown) {
//链路断开(类似拔网线)
notifyInterfaceLinkChanged(iface, false);
} else if (action == NetlinkEvent::Action::kAddressUpdated ||
action == NetlinkEvent::Action::kAddressRemoved) {
const char *address = evt->findParam("ADDRESS");
const char *flags = evt->findParam("FLAGS");
const char *scope = evt->findParam("SCOPE");
const char *ifIndex = evt->findParam("IFINDEX");
char addrstr[INET6_ADDRSTRLEN + strlen("/128")];
strlcpy(addrstr, address, sizeof(addrstr));
char *slash = strchr(addrstr, '/');
if (slash) {
*slash = '\0';
}
long ifaceIndex = parseIfIndex(ifIndex);
if (!ifaceIndex) {
ALOGE("invalid interface index: %s(%s)", iface, ifIndex);
}
const bool addrUpdated = (action == NetlinkEvent::Action::kAddressUpdated);
if (addrUpdated) {
gCtls->netCtrl.addInterfaceAddress(ifaceIndex, address);
} else { // action == NetlinkEvent::Action::kAddressRemoved
bool shouldDestroy = gCtls->netCtrl.removeInterfaceAddress(ifaceIndex, address);
if (shouldDestroy) {
SockDiag sd;
if (sd.open()) {
// Pass the interface index iff. destroying sockets on a link-local address.
// This cannot use an interface name as the interface might no longer exist.
int destroyIfaceIndex =
std::string_view(addrstr).starts_with("fe80:") ? ifaceIndex : 0;
int ret = sd.destroySockets(addrstr, destroyIfaceIndex);
if (ret < 0) {
ALOGE("Error destroying sockets: %s", strerror(-ret));
}
} else {
ALOGE("Error opening Netlink_SOCK_DIAG socket: %s", strerror(errno));
}
}
}
// Note: if this interface was deleted, iface is "" and we don't notify.
if (iface && iface[0] && address && flags && scope) {
if (addrUpdated) {
//ip地址改变
notifyAddressUpdated(address, iface, std::stoi(flags), std::stoi(scope));
} else {
notifyAddressRemoved(address, iface, std::stoi(flags), std::stoi(scope));
}
}
} else if (action == NetlinkEvent::Action::kRdnss) {
const char *lifetime = evt->findParam("LIFETIME");
const char *servers = evt->findParam("SERVERS");
if (lifetime && servers) {
//DNS服务器改变
notifyInterfaceDnsServers(iface, strtol(lifetime, nullptr, 10),
android::base::Split(servers, ","));
}
} else if (action == NetlinkEvent::Action::kRouteUpdated ||
action == NetlinkEvent::Action::kRouteRemoved) {
const char *route = evt->findParam("ROUTE");
const char *gateway = evt->findParam("GATEWAY");
const char *iface = evt->findParam("INTERFACE");
if (route && (gateway || iface)) {
notifyRouteChange((action == NetlinkEvent::Action::kRouteUpdated) ? true : false,
route, (gateway == nullptr) ? "" : gateway,
(iface == nullptr) ? "" : iface);
}
}
} else if (!strcmp(subsys, "qlog") || !strcmp(subsys, "xt_quota2")) {
const char *alertName = evt->findParam("ALERT_NAME");
const char *iface = evt->findParam("INTERFACE");
if (alertName && iface) {
//当数据量超过预警值,则会收到该通知
notifyQuotaLimitReached(alertName, iface);
}
} else if (!strcmp(subsys, "strict")) {
const char *uid = evt->findParam("UID");
const char *hex = evt->findParam("HEX");
if (uid && hex) {
notifyStrictCleartext(strtol(uid, nullptr, 10), hex);
}
} else if (!strcmp(subsys, "xt_idletimer")) {
//这个和idletimer有关,用于跟踪某个NIC的工作状态,即是"idle"还是"active"
//检测时间按秒计算
const char *label = evt->findParam("INTERFACE");
const char *state = evt->findParam("STATE");
const char *timestamp = evt->findParam("TIME_NS");
const char *uid = evt->findParam("UID");
if (state) {
bool isActive = !strcmp("active", state);
int64_t processTimestamp = (timestamp == nullptr) ? 0 : strtoll(timestamp, nullptr, 10);
int intLabel;
// NMS only accepts interface class activity changes with integer labels, and only ever
// creates idletimers with integer labels.
if (android::base::ParseInt(label, &intLabel)) {
const long reportedUid =
(uid != nullptr && isActive) ? strtol(uid, nullptr, 10) : -1;
notifyInterfaceClassActivityChanged(intLabel, isActive, processTimestamp,
reportedUid);
}
}
#if !LOG_NDEBUG
} else if (strcmp(subsys, "platform") && strcmp(subsys, "backlight")) {
/* It is not a VSYNC or a backlight event */
ALOGV("unexpected event from subsystem %s", subsys);
#endif
}
}
4、大致流程
- NM创建NetlinkHandler后,工作便转交给NetlinkHandler来完成,而每个NetlinkHandler对象均会单独创建1个线程用于接收Socket消息。
- 当Kernel发送UEvent消息后,NetlinkHandler便从select调用中返回,然后调用其onDataAvailable函数,该函数内部会创建1个NetlinkEvent对象。
- NetlinkEvent对象按socket创建时指定的解析类型去解析来自Kernel的UEvent消息。
- 最终NetlinkHandler的onEvent将被调用,不同的UEvent消息将在此函数中进行分类处理。
- NetlinkHandler最终将处理结果经由NM内部变量mBroadcaster转发给NMS。
SocketListener
从Netd的类图关系看,主要有4个类,其主要作用如下:
- SocketListener :Native层获取底层Uevent的公用接口类,其他模块,如vold模块,也通过该Listener来监听底层Uevent事件。监听到后,通过onDataAvailable来处理底层上报的数据;
- NetlinkListener:该类继承自SocketListener。在onDataAvailable函数中调用onEvent继续封装底层的事件;
- NetlinkHandler:该类继承自NetlinkListener。在onEvent中继续封装处理底层事件,并通过上层注册的监听对象,将该状态上报给上层。
- NM:其内部维护了几个NetinkHandler引用对象,及1个SocketListener引用对象,来管理socket的通信,包括socket的创建、释放等等。
Netd在启动时,会创建NetlinkManager引用对象,并且创建NetlinkHandler的引用对象,并且调用其start来启动监听,最后会调用到SocketListener的runListener函数中,来实时监听底层Uevent事件上报。
CommandListener
接收来自FW层NM的命令,转交到对应的指令对象去处理。CL仅是1个Listener,在收到命令后,只是将它们转交给对应的命令处理对象去处理。
scss
CommandListener::CommandListener(LogBuffer* buf, LogTags* tags, PruneList* prune,
LogStatistics* stats)
: FWListener(getLogSocket()), buf_(buf), tags_(tags), prune_(prune), stats_(stats) {
registerCmd(new ClearCmd(this));
registerCmd(new GetBufSizeCmd(this));
registerCmd(new SetBufSizeCmd(this));
registerCmd(new GetBufSizeReadableCmd(this));
registerCmd(new GetBufSizeUsedCmd(this));
registerCmd(new GetStatisticsCmd(this));
registerCmd(new SetPruneListCmd(this));
registerCmd(new GetPruneListCmd(this));
registerCmd(new GetEventTagCmd(this));
registerCmd(new ReinitCmd(this));
registerCmd(new ExitCmd(this));
}
工作流程
NMS
作用
接受上层模块发下来的命令,并送到CommandListener处理;
接收底层传上来的1些notify消息,并做出相应处理。
NMS 作用:
1)通过 socket 连接 Netd;
2)通过observer通知观察者
ini
private NMS(
Context context, Dependencies deps) {
mContext = context;
mDeps = deps;
mDaemonHandler = new Handler(FgThread.get().getLooper());
mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
mDeps.registerLocalService(new LocalService());
synchronized (mTetheringStatsProviders) {
mTetheringStatsProviders.put(new NetdTetheringStatsProvider(), "Netd");
}
}
工作流程
Netd与NM的交互
SystemServer进程启动时, 创建NMS, 此时NMS会主动与Netd建立socket链接:
ini
t.traceBegin("StartNMS");
try {
networkManagement = NMS.create(context);
ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
} catch (Throwable e) {
reportWtf("starting NetworkManagement Service", e);
}
处理底层上来的消息
java
private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
@Override
public void onInterfaceClassActivityChanged(boolean isActive,
int label, long timestamp, int uid) throws RemoteException {
final long timestampNanos;
if (timestamp <= 0) {
timestampNanos = SystemClock.elapsedRealtimeNanos();
} else {
timestampNanos = timestamp;
}
mDaemonHandler.post(() ->
notifyInterfaceClassActivity(label, isActive, timestampNanos, uid));
}
@Override
public void onQuotaLimitReached(String alertName, String ifName)
throws RemoteException {
mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
}
@Override
public void onInterfaceDnsServerInfo(String ifName,
long lifetime, String[] servers) throws RemoteException {
mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
}
@Override
public void onInterfaceAddressUpdated(String addr,
String ifName, int flags, int scope) throws RemoteException {
final LinkAddress address = new LinkAddress(addr, flags, scope);
mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
}
@Override
public void onInterfaceAddressRemoved(String addr,
String ifName, int flags, int scope) throws RemoteException {
final LinkAddress address = new LinkAddress(addr, flags, scope);
mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
}
@Override
public void onInterfaceAdded(String ifName) throws RemoteException {
mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
}
@Override
public void onInterfaceRemoved(String ifName) throws RemoteException {
mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
}
@Override
public void onInterfaceChanged(String ifName, boolean up)
throws RemoteException {
mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
}
@Override
public void onInterfaceLinkStateChanged(String ifName, boolean up)
throws RemoteException {
mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
}
@Override
public void onRouteChanged(boolean updated,
String route, String gateway, String ifName) throws RemoteException {
final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
ifName, RouteInfo.RTN_UNICAST);
mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
}
。。。。。。
}
MDnsSdListener
Multicast DNS Service Discovery。主要解决了3个问题:
- Addressing:即为主机分配IP。Bonjour的Addressing处理较简单,即每个主机在网络内部的地址可选范围内找1个IP,然后查看下网络内部是否有其他主机再用。若该IP没被分配的话,它将使用此IP。
- Naming:解决的就是host和IP地址的对应关系。Boniour采用的是Multiple DNS技术,即DNS查询消息将通过UDP组播方式发送。1旦网络内部某个机器发现查询的机器名和自己设置的1样,就回复这条请求。此外,Bonjour还拓展了MDNS的用途,即除了能查找host外,还支持对service的查找。不过,Bonjour的Naming有1个限制,即网络内部不能有重名的host或service。
- Service Discovery:SD基于上面的Naming工作,它使得APP能查找到网络内部的服务,并解析该服务对应的IP地址和端口号。APP1旦得到服务的IP地址和端口号,就可直接和该服务建立交互关系。
在Android上的运行机制
在Netd中有MdnsSdListener对象,通过socket与上层对象通信,通过Bonjour API 和mdnsd进行通信
下面是类图
MDnsSdListener对应的FW层服务为NsdService(Network Service Discovery),MDnsSdListener主要工作可分成 3步:
1)Netd创建MDnsSdListener对象,其内部会创建Monitor对象,而Monitor对象将启动1个线程用于和mdnsd通信,并接收来自Handler的请求。
2)NsdService启动完毕后将向MDnsSdListener发送"start-service"命令。
3)NsdService响应APP的请求,向MDnsSdListener发送其他命令,如"discovery"等。Monitor将最终处理这些请求。
当MDnsSdListener构造时,会创建1个Monitor对象,代码如下所示:
ini
MDnsSdListener::Monitor::Monitor() {
mHead = nullptr;
mLiveCount = 0;
mPollFds = nullptr;
mPollRefs = nullptr;
mPollSize = 10;
//创建两个 socket,用于接收 MDnsSdListener对象的指令
socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, mCtrlSocketPair);
//创建线程
const int rval = ::android::Netdutils::threadLaunch(this);
if (rval != 0) {
ALOGW("Error spawning monitor thread: %s (%d)", strerror(-rval), -rval);
}
}
当NsdService发送"start-service"命令后,Handler的runCommand将执行Monitor的startService函数,代码如下所示
arduino
int MDnsSdListener::Monitor::startService() {
char property_value[PROPERTY_VALUE_MAX];
std::lock_guard guard(mMutex);
//MDNS_SERVICE_STATUS1个字符串,值为init.svc.mdnsd。在init.rc配置文件中,mdnsd是1个
//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"命令启动
property_set("ctl.start", MDNS_SERVICE_NAME);
//若mdnsd成功启动,则属性值变成"running"
wait_for_property(MDNS_SERVICE_STATUS, "running", 5);
return -1;
}
return 0;
}
当NsdService发送注册服务请求时,Handler的serviceRegister函数将被调用
arduino
int MDnsSdListener::serviceRegister(int requestId, const char* serviceName, const char* serviceType,
const char* domain, const char* host, int port,
const std::vector<unsigned char>& txtRecord, uint32_t ifIndex) {
if (VDBG) {
ALOGD("serviceRegister(%d, %d, %s, %s, %s, %s, %d, <binary>)", requestId, ifIndex,
serviceName, serviceType, domain ? domain : "null", host ? host : "null", port);
}
Context* context = new Context(requestId);
DNSServiceRef* ref = mMonitor.allocateServiceRef(requestId, context);
if (ref == nullptr) {
ALOGE("requestId %d already in use during register call", requestId);
return -EBUSY;
}
port = htons(port);
DNSServiceFlags nativeFlags = 0;
DNSServiceErrorType result = DNSServiceRegister(
ref, nativeFlags, ifIndex, serviceName, serviceType, domain, host, port,
txtRecord.size(), &txtRecord.front(), &MDnsSdListenerRegisterCallback, context);
if (result != kDNSServiceErr_NoError) {
ALOGE("service register request %d got an error from DNSServiceRegister %d", requestId,
result);
mMonitor.freeServiceRef(requestId);
// Return kDNSServiceErr_* directly instead of transferring to an UNIX error.
// This can help caller to know what going wrong from mdnsresponder side.
return -result;
}
mMonitor.startMonitoring(requestId);
if (VDBG) ALOGD("serviceRegister successful");
return 0;
}
DNSServiceRegister内部将把请求发送给mdnsd去处理,处理的结果通过MDnsSdListenerRegisterCallback返回,该函数代码如下所示
ini
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);
int refNumber = context->mRefNumber;
const auto& listeners = MDnsEventReporter::getInstance().getEventListeners();
if (listeners.empty()) {
ALOGI("Register callback not sent since no IMDnsEventListener receiver is available.");
return;
}
RegistrationInfo info;
info.id = refNumber;
info.serviceName = serviceName;
info.registrationType = regType;
if (errorCode == kDNSServiceErr_NoError) {
if (VDBG) ALOGD("register succeeded for %d as %s", refNumber, serviceName);
info.result = IMDnsEventListener::SERVICE_REGISTERED;
} else {
if (DBG) ALOGE("register failure for %d, error= %d", refNumber, errorCode);
info.result = IMDnsEventListener::SERVICE_REGISTRATION_FAILED;
}
for (const auto& it : listeners) {
it->getListener()->onServiceRegistrationStatus(info);
}
}
DnsProxyListener
所有android的dns查询都会proxy到Netd中。
ini
bool initDnsResolver() {
ResolverNetdCallbacks callbacks = {
.check_calling_permission = &checkCallingPermissionCallback,
.get_network_context = &getNetworkContextCallback,
.log = &logCallback,
.tagSocket = &tagSocketCallback,
.evaluate_domain_name = &evaluateDomainNameCallback,
};
return resolv_init(&callbacks);
}
bool DnsResolver::start() {
if (!verifyCallbacks()) {
LOG(ERROR) << __func__ << ": Callback verification failed";
return false;
}
if (mDnsProxyListener.startListener()) {
PLOG(ERROR) << __func__ << ": Unable to start DnsProxyListener";
return false;
}
binder_status_t ret;
if ((ret = DnsResolverService::start()) != STATUS_OK) {
LOG(ERROR) << __func__ << ": Unable to start DnsResolverService: " << ret;
return false;
}
return true;
}
DnsProxyListener能处理的3种cmd如下,cmd的匹配和分发是自动的(详见:system/core/libsysutils/src/FWListener.cpp
2个关键函数getaddrinfo\getnameinfo