Android okhttp3 DNS 底層實現追蹤(二)
在《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解析的實現。
一、從getaddrinfo到dnsproxyd
/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;
}
二、Netd端的實現
參考: [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();
}