在《Android okhttp3 DNS 底层实现追踪(一)》中分析了okhttp3的DNS从framework通过jni到libc的过程,止步于getaddrinfo。
在getaddinfo中,DNS的解析是通过Netd代理的方式进行的。Netd是Network Daemon的缩写,Netd在Android中负责物理端口的网络操作相关的实现,如Bandwidth,NAT,PPP,soft-ap等。Netd为Framework隔离了底层网络接口的差异,提供了统一的调用接口,简化了整个网络逻辑的使用。简单来说就是Android将监听/dev/socket/dnsproxyd,如果系统需要DNS解析服务,那么就需要打开dnsproxyd,然后安装一定的格式写入命令,然后监听等待目标回答。
本文主要分析Netd端的DNS解析的实现。
/bionic/libc/netbsd/net/getaddrinfo.c
int getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res)
{
return android_getaddrinfoforiface(hostname, servname, hints, NULL, 0, res);
}
getaddrinfo直接调用了android_getaddrinfoforiface,主要代码如下:
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, iface);
}
这里cache_mode为空,Netd设置的ANDROID_DNS_MODE环境变量只在进程中有效。
在android_getaddrinfo_proxy中首先通过socket connect
sock = socket(AF_UNIX, SOCK_STREAM, 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;
}
这里的socket name是/dev/socket/dnsproxyd,也就是通过dnsproxd来和netd dameon进程交互。
然后通过fprinf往dnsproxyd写getaddrinfo命令,接下来就交由netd进程处理。
// Send the request.
proxy = fdopen(sock, "r+");
if (fprintf(proxy, "getaddrinfo %s %s %d %d %d %d %s",
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,
iface == NULL ? "^" : iface) < 0) {
goto exit;
}
参考: [Android4.4]DNS流程
new DnsProxyListener //system/netd/main.cpp
–>dpl->startListener ->
—->pthread_create ->
——>SocketListener::threadStart ->
——–>me->runListener ->
———->select
———->accept
———->onDataAvailable -> //FrameworkListener.cpp 客户端写消息到socket dnsproxyd中,dnsproxyd是在FrameworkListener中注册。
————>dispatchCommand ->
————–>runCommand ->
—————->DnsProxyListener::GetAddrInfoCmd::runCommand ->
——————>new DnsProxyListener::GetAddrInfoHandler
——————>handler->start ->
——————–>DnsProxyListener::GetAddrInfoHandler::start ->
——————–>DnsProxyListener::GetAddrInfoHandler::threadStart -> //DnsProxyListener.cpp netd初始化后会启动dnsProxyListener线程监听/dev/socket/dnsproxd来的消息。
———————–>handler->run ->
————————->DnsProxyListener::GetAddrInfoHandler::run ->
—————————>android_getaddrinfoforiface -> //这里不会跑android_getaddrinfo_proxy了,因为此时的ANDROID_DNS_MODE值是local了,所以直接获取dns地址。
—————————–>explore_fqdn ->
——————————->nsdispatch ->
———————————>_files_getaddrinfo //从文件/system/etc/hosts获取
———————————>_dns_getaddrinfo //或者从dns服务器获取
—————————>sendLenAndData //发回给framework端
DnsProxyListener::GetAddrInfoHandler::run()的代码如下:
void DnsProxyListener::GetAddrInfoHandler::run() {
if (DBG) {
ALOGD("GetAddrInfoHandler, now for %s / %s / %s", mHost, mService, mIface);
}
char tmp[IF_NAMESIZE + 1];
int mark = mMark;
if (mIface == NULL) {
//fall back to the per uid interface if no per pid interface exists
if(!_resolv_get_pids_associated_interface(mPid, tmp, sizeof(tmp)))
_resolv_get_uids_associated_interface(mUid, tmp, sizeof(tmp));
}
struct addrinfo* result = NULL;
uint32_t rv = android_getaddrinfoforiface(mHost, mService, mHints, mIface ? mIface : tmp,
mark, &result);
if (rv) {
// getaddrinfo failed
mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, &rv, sizeof(rv));
} else {
bool success = !mClient->sendCode(ResponseCode::DnsProxyQueryResult);
struct addrinfo* ai = result;
while (ai && success) {
success = sendLenAndData(mClient, sizeof(struct addrinfo), ai)
&& sendLenAndData(mClient, ai->ai_addrlen, ai->ai_addr)
&& sendLenAndData(mClient,
ai->ai_canonname ? strlen(ai->ai_canonname) + 1 : 0,
ai->ai_canonname);
ai = ai->ai_next;
}
success = success && sendLenAndData(mClient, 0, "");
if (!success) {
ALOGW("Error writing DNS result to client");
}
}
if (result) {
freeaddrinfo(result);
}
mClient->decRef();
}