Android11 DNS解析流程
Android11 DNS解析
1. DNS解析概念
DNS的全称是domain name system,即域名系统。主要目的是将域名解析为IP地址,域名是方便用户记忆,但网络传输中源目地址使用IP地址来进行标识的,所以Android中的网络应用程序在发起http请求之前必然要经历DNS解析过程。
2. Android11的DNS解析过程
2.1 Inet6AddressImpl.lookupHostByName
Android app不管用什么网络框架,dns解析都会走到Inet6AddressImpl.lookupHostByName
方法,我们就以这个方法为入口开始看代码:
/**
* Resolves a hostname to its IP addresses using a cache.
*
* @param host the hostname to resolve.
* @param netId the network to perform resolution upon.
* @return the IP addresses of the host.
*/
private static InetAddress[] lookupHostByName(String host, int netId)
throws UnknownHostException {
//参数host代表dns解析的域名,netId代表使用的网络,如wifi,数据网络等等都是可以自己选择的
BlockGuard.getThreadPolicy().onNetwork();
// 缓存中是否已经有了,这里的缓存只是应用进程的缓存(TTL为2s),并不是DNS的TTL缓存
...
try {
StructAddrinfo hints = new StructAddrinfo();
//不指定协议族,返回包括 IPv4 和 IPv6 的所有地址
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_family = AF_UNSPEC;
//指定socket类型为TCP
hints.ai_socktype = SOCK_STREAM;
//真正进行DNS解析的方法
InetAddress[] addresses = Libcore.os.android_getaddrinfo(host, hints, netId);
...
return addresses;
} catch (GaiException gaiException) {
...
}
// 常见的无网报错
String detailMessage = "Unable to resolve host \"" + host + "\": " + Libcore.os.gai_strerror(gaiException.error);
addressCache.putUnknownHost(host, netId, detailMessage);
throw gaiException.rethrowAsUnknownHostException(detailMessage);
}
}
2.2 BlockGuardOs.android_getaddrinfo
@Override public InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException {
// With AI_NUMERICHOST flag set, the node must a numerical network address, therefore no
// host address lookups will be performed. In this case, it is fine to perform on main
// thread.
boolean isNumericHost = (hints.ai_flags & AI_NUMERICHOST) != 0;
if (!isNumericHost) {
BlockGuard.getThreadPolicy().onNetwork();
}
return super.android_getaddrinfo(node, hints, netId);
}
2.3 Linux.android_getaddrinfo
public native InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException;
这里是一个native方法,对应的native层函数是libcore_io_Linux.Linux_android_getaddrinfo
2.4 libcore_io_Linux.Linux_android_getaddrinfo
static jobjectArray Linux_android_getaddrinfo(JNIEnv* env, jobject, jstring javaNode,
jobject javaHints, jint netId) {
ScopedUtfChars node(env, javaNode);
if (node.c_str() == NULL) {
return NULL;
}
static jfieldID flagsFid = env->GetFieldID(JniConstants::GetStructAddrinfoClass(env), "ai_flags", "I");
static jfieldID familyFid = env->GetFieldID(JniConstants::GetStructAddrinfoClass(env), "ai_family", "I");
static jfieldID socktypeFid = env->GetFieldID(JniConstants::GetStructAddrinfoClass(env), "ai_socktype", "I");
static jfieldID protocolFid = env->GetFieldID(JniConstants::GetStructAddrinfoClass(env), "ai_protocol", "I");
addrinfo hints;
memset(&hints, 0, sizeof(hints));
//使用java层传递的协议项,socket设置等构造addrinfo对象
hints.ai_flags = env->GetIntField(javaHints, flagsFid);
hints.ai_family = env->GetIntField(javaHints, familyFid);
hints.ai_socktype = env->GetIntField(javaHints, socktypeFid);
hints.ai_protocol = env->GetIntField(javaHints, protocolFid);
addrinfo* addressList = NULL;
errno = 0;
//addressList接收最终的DNS解析结果,这里传的server name是null
int rc = android_getaddrinfofornet(node.c_str(), NULL, &hints, netId, 0, &addressList);
//省略后续构造java层对象返回的代码
...
return result;
}
android_getaddrinfofornet
这个函数会调到getaddrinfo.c中:
2.5 getaddrinfo.android_getaddrinfofornet
getaddrinfo.c中经过一系列内部调用,对数据转换,封装之后最终调到android_getaddrinfo_proxy
这个函数:
2.6 getaddrinfo.android_getaddrinfo_proxy
#if defined(__ANDROID__)
// Returns 0 on success, else returns on error.
static int
android_getaddrinfo_proxy(
const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res, unsigned netid)
{
int success = 0;
//省略一些判断
...
//这是核心方法,这里会打开一个socket文件
FILE* proxy = fdopen(__netdClientDispatch.dnsOpenProxy(), "r+");
if (proxy == NULL) {
return EAI_SYSTEM;
}
netid = __netdClientDispatch.netIdForResolv(netid);
//向socket发送请求,其实就是向socket对端写入字符串
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];
// 将对端返回的消息放入buf
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;
//填充addrinfo数据,这是返回的DNS解析数据,代码省略了,不是重点
....
return EAI_NODATA;
}
#endif
上述函数的,主要作用是打开一个socket描述符,然后向此socket写入一堆字符串,我们需要关注的是打开的是哪个socket?监听此socket的进程是哪个?对端如何处理写入的字符串?
首先来看__netdClientDispatch.dnsOpenProxy()
是什么东西?
来看下面一段代码:
template <typename FunctionType>
static void netdClientInitFunction(void* handle, const char* symbol, FunctionType* function) {
typedef void (*InitFunctionType)(FunctionType*);
InitFunctionType initFunction = reinterpret_cast<InitFunctionType>(dlsym(handle, symbol));
if (initFunction != nullptr) {
initFunction(function);
}
}
static void netdClientInitImpl() {
// Prevent netd from looping back fwmarkd connections to itself. It would work, but it's
// a deadlock hazard and unnecessary overhead for the resolver.
if (getuid() == 0 && strcmp(basename(getprogname()), "netd") == 0) {
async_safe_format_log(ANDROID_LOG_INFO, "netdClient",
"Skipping libnetd_client init since *we* are netd");
return;
}
void* handle = dlopen("libnetd_client.so", RTLD_NOW);
if (handle == nullptr) {
// If the library is not available, it's not an error. We'll just use
// default implementations of functions that it would've overridden.
return;
}
netdClientInitFunction(handle, "netdClientInitAccept4", &__netdClientDispatch.accept4);
netdClientInitFunction(handle, "netdClientInitConnect", &__netdClientDispatch.connect);
netdClientInitFunction(handle, "netdClientInitSendmmsg", &__netdClientDispatch.sendmmsg);
netdClientInitFunction(handle, "netdClientInitSendmsg", &__netdClientDispatch.sendmsg);
netdClientInitFunction(handle, "netdClientInitSendto", &__netdClientDispatch.sendto);
netdClientInitFunction(handle, "netdClientInitSocket", &__netdClientDispatch.socket);
netdClientInitFunction(handle, "netdClientInitNetIdForResolv",
&__netdClientDispatch.netIdForResolv);
netdClientInitFunction(handle, "netdClientInitDnsOpenProxy",
&__netdClientDispatch.dnsOpenProxy);
}
上面代码用到了dlopen
和dlsym
函数,dlopen
打开的libnetd_client.so
定义在netd进程中:
dlsym
函数获取了libnetd_client.so
中的netdClientInitDnsOpenProxy
函数地址,其代码实现如下所示:
extern "C" void netdClientInitDnsOpenProxy(DnsOpenProxyType* function) {
if (function) {
*function = dns_open_proxy;
}
}
&__netdClientDispatch.dnsOpenProxy
地址被传入上述函数,作为函数指针指向了dns_open_proxy
函数,所以最终我们知道了前面的fdopen
函数打开的socket的是在dns_open_proxy
函数中返回的
2.7 NetdClient.dns_open_proxy
int dns_open_proxy() {
....
const auto socketFunc = libcSocket ? libcSocket : socket;
int s = socketFunc(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (s == -1) {
return -1;
}
const int one = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
static const struct sockaddr_un proxy_addr = {
.sun_family = AF_UNIX,
.sun_path = "/dev/socket/dnsproxyd",
};
const auto connectFunc = libcConnect ? libcConnect : connect;
if (TEMP_FAILURE_RETRY(
connectFunc(s, (const struct sockaddr*) &proxy_addr, sizeof(proxy_addr))) != 0) {
// Store the errno for connect because we only care about why we can't connect to dnsproxyd
int storedErrno = errno;
close(s);
errno = storedErrno;
return -1;
}
return s;
}
上面代码一目了然,作为客户端打开/dev/socket/dnsproxyd
并连接,回到getaddrinfo.android_getaddrinfo_proxy
函数中,socket连接上之后,发送**“getaddrinfo %s %s %d %d %d %d %u”**字符串,流程图如下:
接着需要知道监听/dev/socket/dnsproxyd
的服务端是哪个进程?如何处理客户端的字符串?
这个socket的服务端在netd进程里面通过SocketListener
监听的,所以DNS相关的请求字符串发给了netd:
接着看字符串的处理,服务端对这些客户端发送的字符处理方式是通过FrameworkListener
注册的命令来处理的,getaddrinfo
对应的是GetAddrInfoCmd
,直接上代码:
2.8 DnsProxyListener::GetAddrInfoCmd::runCommand
DnsProxyListener::GetAddrInfoCmd::GetAddrInfoCmd() : FrameworkCommand("getaddrinfo") {}
int DnsProxyListener::GetAddrInfoCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
//省略一些合法性判断
...
addrinfo* hints = nullptr;
int ai_flags = strtol(argv[3], nullptr, 10);
int ai_family = strtol(argv[4], nullptr, 10);
int ai_socktype = strtol(argv[5], nullptr, 10);
int ai_protocol = strtol(argv[6], nullptr, 10);
unsigned netId = strtoul(argv[7], nullptr, 10);
const bool useLocalNameservers = checkAndClearUseLocalNameserversFlag(&netId);
const uid_t uid = cli->getUid();
const int pid = cli->getPid();
android_net_context netcontext;
//构造netcontext
gResNetdCallbacks.get_network_context(netId, uid, &netcontext);
if (ai_flags != -1 || ai_family != -1 ||
ai_socktype != -1 || ai_protocol != -1) {
//构造addrinfo
hints = (addrinfo*) calloc(1, sizeof(addrinfo));
hints->ai_flags = ai_flags;
hints->ai_family = ai_family;
hints->ai_socktype = ai_socktype;
hints->ai_protocol = ai_protocol;
}
DnsProxyListener::GetAddrInfoHandler* handler =
new DnsProxyListener::GetAddrInfoHandler(cli, name, service, hints, netcontext);
tryThreadOrError(cli, handler);
return 0;
}
2.9 DnsProxyListener::GetAddrInfoHandler::run
void DnsProxyListener::GetAddrInfoHandler::run() {
LOG(DEBUG) << "GetAddrInfoHandler::run: {" << mNetContext.app_netid << " "
<< mNetContext.app_mark << " " << mNetContext.dns_netid << " "
<< mNetContext.dns_mark << " " << mNetContext.uid << " " << mNetContext.flags << "}";
addrinfo* result = nullptr;
Stopwatch s;
maybeFixupNetContext(&mNetContext, mClient->getPid());
const uid_t uid = mClient->getUid();
int32_t rv = 0;
NetworkDnsEventReported event;
initDnsEvent(&event, mNetContext);
if (queryLimiter.start(uid)) {
if (evaluate_domain_name(mNetContext, mHost)) {
//核心函数,result用于接收最后的结果
rv = resolv_getaddrinfo(mHost, mService, mHints, &mNetContext, &result,
&event);
} else {
rv = EAI_SYSTEM;
}
queryLimiter.finish(uid);
} else {
...
}
//IPv4,IPv6地址转换,当 DNS 查询请求中的查询类型为 AAAA(IPv6 地址查询)且查询结果为空时
//使用IPv4进行查询
doDns64Synthesis(&rv, &result, &event);
const int32_t latencyUs = saturate_cast<int32_t>(s.timeTakenUs());
event.set_latency_micros(latencyUs);
event.set_event_type(EVENT_GETADDRINFO);
event.set_hints_ai_flags((mHints ? mHints->ai_flags : 0));
bool success = true;
if (rv) {
// getaddrinfo failed
success = !mClient->sendBinaryMsg(ResponseCode::DnsProxyOperationFailed, &rv, sizeof(rv));
} else {
//DNS解析成功
success = !mClient->sendCode(ResponseCode::DnsProxyQueryResult);
addrinfo* ai = result;
while (ai && success) {
//DNS解析数据返回给客户端
success = sendBE32(mClient, 1) && sendaddrinfo(mClient, ai);
ai = ai->ai_next;
}
success = success && sendBE32(mClient, 0);
}
...
}
上面重点函数resolv_getaddrinfo
,DNS解析核心函数:
2.10 getaddrinfo.resolv_getaddrinfo
int resolv_getaddrinfo(const char* _Nonnull hostname, const char* servname, const addrinfo* hints,
const android_net_context* _Nonnull netcontext, addrinfo** _Nonnull res,
NetworkDnsEventReported* _Nonnull event) {
//省略一些判断
...
addrinfo ai = hints ? *hints : addrinfo{};
addrinfo sentinel = {};
addrinfo* cur = &sentinel;
//explore_options里面定义了一些条件,必须匹配才能进行解析
for (const Explore& ex : explore_options) {
if (ai.ai_family != ex.e_af) continue;
if (!MATCH(ai.ai_socktype, ex.e_socktype, WILD_SOCKTYPE(ex))) continue;
if (!MATCH(ai.ai_protocol, ex.e_protocol, WILD_PROTOCOL(ex))) continue;
addrinfo tmp = ai;
if (tmp.ai_socktype == ANY && ex.e_socktype != ANY) tmp.ai_socktype = ex.e_socktype;
if (tmp.ai_protocol == ANY && ex.e_protocol != ANY) tmp.ai_protocol = ex.e_protocol;
//核心函数
error = explore_fqdn(&tmp, hostname, servname, &cur->ai_next, netcontext, event);
while (cur->ai_next) cur = cur->ai_next;
}
// Propagate the last error from explore_fqdn(), but only when *all* attempts failed.
if ((*res = sentinel.ai_next)) return 0;
// TODO: consider removing freeaddrinfo.
freeaddrinfo(sentinel.ai_next);
*res = nullptr;
return (error == 0) ? EAI_FAIL : error;
}
2.11 getaddrinfo.explore_fqdn
// FQDN hostname, DNS lookup
static int explore_fqdn(const addrinfo* pai, const char* hostname, const char* servname,
addrinfo** res, const android_net_context* netcontext,
NetworkDnsEventReported* event) {
addrinfo* result = nullptr;
int error = 0;
// If the servname does not match socktype/protocol, return error code.
if ((error = get_portmatch(pai, servname))) return error;
if (!files_getaddrinfo(netcontext->dns_netid, hostname, pai, &result)) {
ALOGE("djtang....files_getaddrinfo == false.");
error = dns_getaddrinfo(hostname, pai, netcontext, &result, event);
}
if (error) {
freeaddrinfo(result);
return error;
}
for (addrinfo* cur = result; cur; cur = cur->ai_next) {
// canonname should be filled already
if ((error = get_port(cur, servname, 0))) {
freeaddrinfo(result);
return error;
}
}
*res = result;
return 0;
}
上面代码首先会去缓存文件/system/etc/hosts
去查看是否已经有域名对应IP地址了,如果没有,再调dns_getaddrinfo
函数获取,测试了一下dns解析成功之后也不会将解析结果写入/system/etc/hosts
,关于files_getaddrinfo
函数查缓存的细节就不展开了,不是重点,继续分析dns_getaddrinfo
:
2.12 getaddrinfo.dns_getaddrinfo
static int dns_getaddrinfo(const char* name, const addrinfo* pai,
const android_net_context* netcontext, addrinfo** rv,
NetworkDnsEventReported* event) {
//IPv6,IPv4的解析结果
res_target q = {};
res_target q2 = {};
//省略一堆ipv4,ipv6判断
...
ResState res;
//将netcontext的值赋给res
res_init(&res, netcontext, event);
int he;
//进行dns解析
if (res_searchN(name, &q, &res, &he) < 0) {
//dns解析失败
return herrnoToAiErrno(he);
}
addrinfo sentinel = {};
addrinfo* cur = &sentinel;
//获取解析结果
addrinfo* ai = getanswer(q.answer, q.n, q.name, q.qtype, pai, &he);
if (ai) {
cur->ai_next = ai;
while (cur && cur->ai_next) cur = cur->ai_next;
}
if (q.next) {
//获取解析结果
ai = getanswer(q2.answer, q2.n, q2.name, q2.qtype, pai, &he);
if (ai) cur->ai_next = ai;
}
if (sentinel.ai_next == NULL) {
// Note that getanswer() doesn't set the pair NETDB_INTERNAL and errno.
// See also herrnoToAiErrno().
return herrnoToAiErrno(he);
}
_rfc6724_sort(&sentinel, netcontext->app_mark, netcontext->uid);
*rv = sentinel.ai_next;
return 0;
}
2.13 getaddrinfo.res_searchN
static int res_searchN(const char* name, res_target* target, res_state res, int* herrno) {
const char* cp;
HEADER* hp;
//定义"."的数量
uint32_t dots;
int ret, saved_herrno;
int got_nodata = 0, got_servfail = 0, tried_as_is = 0;
hp = (HEADER*)(void*)target->answer.data();
errno = 0;
*herrno = HOST_NOT_FOUND; /* default, if we never query */
dots = 0;
//这段循环的意思是查看name域名中的"."的数量
for (cp = name; *cp; cp++) dots += (*cp == '.');
const bool trailing_dot = (cp > name && *--cp == '.') ? true : false;
saved_herrno = -1;
//res->ndots在前面初始化为1,当dns请求的域名中的"."数量大于等于1时
if (dots >= res->ndots) {
//dns解析
ret = res_querydomainN(name, NULL, target, res, herrno);
if (ret > 0) return (ret);
saved_herrno = *herrno;
tried_as_is++;
}
...
}
上面代码主要对DNS要解析的域名中的"."进行计算,目的是在处理 DNS 域名时解析域名中有多少个子域名,以方便后面进行分割和处理。
2.14 getaddrinfo.res_querydomainN
static int res_querydomainN(const char* name, const char* domain, res_target* target, res_state res,
int* herrno) {
char nbuf[MAXDNAME];
const char* longname = nbuf;
size_t n, d;
assert(name != NULL);
if (domain == NULL) {
// Check for trailing '.'; copy without '.' if present.
n = strlen(name);
if (n + 1 > sizeof(nbuf)) {
*herrno = NO_RECOVERY;
return -1;
}
if (n > 0 && name[--n] == '.') {
strncpy(nbuf, name, n);
nbuf[n] = '\0';
} else
longname = name;
} else {
n = strlen(name);
d = strlen(domain);
if (n + 1 + d + 1 > sizeof(nbuf)) {
*herrno = NO_RECOVERY;
return -1;
}
snprintf(nbuf, sizeof(nbuf), "%s.%s", name, domain);
}
ALOGE("djtang...res_querydomainN...name:%s,domain:%s,longname:%s",name,domain,longname);
return res_queryN_wrapper(longname, target, res, herrno);
}
上面代码会对传入的主机名(name)和域名(domain)进行处理,其中域名可以为 NULL,如果域名为 NULL,就直接使用主机名作为完整域名。如果域名不为 NULL,则将主机名和域名拼接成完整的域名。对于主机名,如果最后有一个点".",则将其删除,以避免后续拼接出问题,然后使用 snprintf() 函数将主机名和域名拼接成完整域名,并将结果存储到 nbuf 缓冲区中,如下是日志打印结果,供参考:
2.15 getaddrinfo.res_queryN_wrapper
static int res_queryN_wrapper(const char* name, res_target* target, res_state res, int* herrno) {
const bool parallel_lookup =
android::net::Experiments::getInstance()->getFlag("parallel_lookup", 0);
if (parallel_lookup) return res_queryN_parallel(name, target, res, herrno);
return res_queryN(name, target, res, herrno);
}
2.16 getaddrinfo.res_queryN
static int res_queryN(const char* name, res_target* target, res_state res, int* herrno) {
uint8_t buf[MAXPACKET];
int n;
struct res_target* t;
int rcode;
int ancount;
assert(name != NULL);
/* XXX: target may be NULL??? */
rcode = NOERROR;
ancount = 0;
for (t = target; t; t = t->next) {
HEADER* hp = (HEADER*)(void*)t->answer.data();
bool retried = false;
again:
hp->rcode = NOERROR; /* default */
/* make it easier... */
int cl = t->qclass;
int type = t->qtype;
const int anslen = t->answer.size();
LOG(DEBUG) << __func__ << ": (" << cl << ", " << type << ")";
//DNS 查询报文的组装函数,可以将指定类别和类型的 DNS 查询报文组装为二进制格式,并存储到指定缓冲区中,
//首先对 DNS 查询报文的头部进行初始化,包括设置报文 ID、报文类型、查询类型等字段。然后根据操作类型 op,
//分别组装并添加报文的查询部分、附加部分和回答部分
//此函数比较复杂,不必关注
n = res_nmkquery(QUERY, name, cl, type, /*data=*/nullptr, /*datalen=*/0, buf, sizeof(buf),
res->netcontext_flags);
...
//发送请求
n = res_nsend(res, buf, n, t->answer.data(), anslen, &rcode, 0);
...
}
...
return ancount;
}
上面函数会对dns查询报文进行组装,然后通过res_nsend
发送请求报文:
2.17 res_send.res_nsend
这个函数很长,很复杂,作用于 DNS 查询,此函数接收一个 DNS 查询请求并返回相应的 DNS 响应,优先通过查询缓存来优化查询速度,如果查询在缓存中找到,则会在不执行实际查询的情况下返回缓存中的响应。如果启用了 DNS-over-TLS,则使用 DNS-over-TLS 与 DNS 服务器进行通信。否则使用 UDP 或 TCP 协议与 DNS 服务器进行通信,如果在所有 DNS 服务器上都无法获得响应,则该函数将返回错误。
int res_nsend(res_state statp, const uint8_t* buf, int buflen, uint8_t* ans, int anssiz, int* rcode,
uint32_t flags, std::chrono::milliseconds sleepTimeMs) {
/*
将dns请求打印出来,大概长这样:
# Query packet
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29827
;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; QUERY SECTION:
;; www.google.com, type = A, class = IN
*/
res_pquery(buf, buflen);
int anslen = 0;
Stopwatch cacheStopwatch;
//查询缓存
ResolvCacheStatus cache_status =
resolv_cache_lookup(statp->netid, buf, buflen, ans, anssiz, &anslen, flags);
const int32_t cacheLatencyUs = saturate_cast<int32_t>(cacheStopwatch.timeTakenUs());
if (cache_status == RESOLV_CACHE_FOUND) {
HEADER* hp = (HEADER*)(void*)ans;
*rcode = hp->rcode;
DnsQueryEvent* dnsQueryEvent = addDnsQueryEvent(statp->event);
dnsQueryEvent->set_latency_micros(cacheLatencyUs);
dnsQueryEvent->set_cache_hit(static_cast<CacheStatus>(cache_status));
dnsQueryEvent->set_type(getQueryType(buf, buflen));
return anslen;
} else if (cache_status != RESOLV_CACHE_UNSUPPORTED) {
resolv_populate_res_for_net(statp);
}
if (statp->nameserverCount() == 0) {
_resolv_cache_query_failed(statp->netid, buf, buflen, flags);
// TODO: Remove errno once callers stop using it
errno = ESRCH;
return -ESRCH;
}
// If parallel_lookup is enabled, it might be required to wait some time to avoid
// gateways drop packets if queries are sent too close together
if (sleepTimeMs != 0ms) {
std::this_thread::sleep_for(sleepTimeMs);
}
// DoT
if (!(statp->netcontext_flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS)) {
bool fallback = false;
//DNS-over-TLS查询
int resplen = res_tls_send(statp, Slice(const_cast<uint8_t*>(buf), buflen),
Slice(ans, anssiz), rcode, &fallback);
if (resplen > 0) {
LOG(DEBUG) << __func__ << ": got answer from DoT";
res_pquery(ans, resplen);
if (cache_status == RESOLV_CACHE_NOTFOUND) {
resolv_cache_add(statp->netid, buf, buflen, ans, resplen);
}
return resplen;
}
if (!fallback) {
_resolv_cache_query_failed(statp->netid, buf, buflen, flags);
return -ETIMEDOUT;
}
}
res_stats stats[MAXNS]{};
res_params params;
int revision_id = resolv_cache_get_resolver_stats(statp->netid, ¶ms, stats, statp->nsaddrs);
if (revision_id < 0) {
// TODO: Remove errno once callers stop using it
errno = ESRCH;
return -ESRCH;
}
bool usable_servers[MAXNS];
int usableServersCount = android_net_res_stats_get_usable_servers(
¶ms, stats, statp->nameserverCount(), usable_servers);
if ((flags & ANDROID_RESOLV_NO_RETRY) && usableServersCount > 1) {
auto hp = reinterpret_cast<const HEADER*>(buf);
// Select a random server based on the query id
int selectedServer = (hp->id % usableServersCount) + 1;
res_set_usable_server(selectedServer, statp->nameserverCount(), usable_servers);
}
// Send request, RETRY times, or until successful.
int retryTimes = (flags & ANDROID_RESOLV_NO_RETRY) ? 1 : params.retry_count;
int useTcp = buflen > PACKETSZ;
int gotsomewhere = 0;
// Use an impossible error code as default value
int terrno = ETIME;
for (int attempt = 0; attempt < retryTimes; ++attempt) {
for (size_t ns = 0; ns < statp->nsaddrs.size(); ++ns) {
if (!usable_servers[ns]) continue;
*rcode = RCODE_INTERNAL_ERROR;
// Get server addr
const IPSockAddr& serverSockAddr = statp->nsaddrs[ns];
LOG(DEBUG) << __func__ << ": Querying server (# " << ns + 1
<< ") address = " << serverSockAddr.toString();
::android::net::Protocol query_proto = useTcp ? PROTO_TCP : PROTO_UDP;
time_t query_time = 0;
int delay = 0;
bool fallbackTCP = false;
const bool shouldRecordStats = (attempt == 0);
int resplen;
Stopwatch queryStopwatch;
int retry_count_for_event = 0;
size_t actualNs = ns;
// Use an impossible error code as default value
terrno = ETIME;
if (useTcp) {
// TCP查询
attempt = retryTimes;
resplen = send_vc(statp, ¶ms, buf, buflen, ans, anssiz, &terrno, ns,
&query_time, rcode, &delay);
if (buflen <= PACKETSZ && resplen <= 0 &&
statp->tc_mode == aidl::android::net::IDnsResolver::TC_MODE_UDP_TCP) {
// reset to UDP for next query on next DNS server if resolver is currently doing
// TCP fallback retry and current server does not support TCP connectin
useTcp = false;
}
LOG(INFO) << __func__ << ": used send_vc " << resplen << " terrno: " << terrno;
} else {
// UDP查询
resplen = send_dg(statp, ¶ms, buf, buflen, ans, anssiz, &terrno, &actualNs,
&useTcp, &gotsomewhere, &query_time, rcode, &delay);
fallbackTCP = useTcp ? true : false;
retry_count_for_event = attempt;
LOG(INFO) << __func__ << ": used send_dg " << resplen << " terrno: " << terrno;
}
const IPSockAddr& receivedServerAddr = statp->nsaddrs[actualNs];
DnsQueryEvent* dnsQueryEvent = addDnsQueryEvent(statp->event);
dnsQueryEvent->set_cache_hit(static_cast<CacheStatus>(cache_status));
// When |retryTimes| > 1, we cannot actually know the correct latency value if we
// received the answer from the previous server. So temporarily set the latency as -1 if
// that condition happened.
// TODO: make the latency value accurate.
dnsQueryEvent->set_latency_micros(
(actualNs == ns) ? saturate_cast<int32_t>(queryStopwatch.timeTakenUs()) : -1);
dnsQueryEvent->set_dns_server_index(actualNs);
dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(receivedServerAddr.family()));
dnsQueryEvent->set_retry_times(retry_count_for_event);
dnsQueryEvent->set_rcode(static_cast<NsRcode>(*rcode));
dnsQueryEvent->set_protocol(query_proto);
dnsQueryEvent->set_type(getQueryType(buf, buflen));
dnsQueryEvent->set_linux_errno(static_cast<LinuxErrno>(terrno));
// Only record stats the first time we try a query. This ensures that
// queries that deterministically fail (e.g., a name that always returns
// SERVFAIL or times out) do not unduly affect the stats.
if (shouldRecordStats) {
// (b/151166599): This is a workaround to prevent that DnsResolver calculates the
// reliability of DNS servers from being broken when network restricted mode is
// enabled.
// TODO: Introduce the new server selection instead of skipping stats recording.
if (!isNetworkRestricted(terrno)) {
res_sample sample;
res_stats_set_sample(&sample, query_time, *rcode, delay);
// KeepListening UDP mechanism is incompatible with usable_servers of legacy
// stats, so keep the old logic for now.
// TODO: Replace usable_servers of legacy stats with new one.
resolv_cache_add_resolver_stats_sample(
statp->netid, revision_id, serverSockAddr, sample, params.max_samples);
}
resolv_stats_add(statp->netid, receivedServerAddr, dnsQueryEvent);
}
if (resplen == 0) continue;
if (fallbackTCP) {
ns--;
continue;
}
if (resplen < 0) {
_resolv_cache_query_failed(statp->netid, buf, buflen, flags);
statp->closeSockets();
return -terrno;
};
/*
将dns响应打印出来,大概长这样:
# Response packet
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29827
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUERY SECTION:
;; www.google.com, type = A, class = IN
;; ANSWER SECTION:
;; www.google.com. 2m19s IN A 172.217.160.100
*/
res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
if (cache_status == RESOLV_CACHE_NOTFOUND) {
resolv_cache_add(statp->netid, buf, buflen, ans, resplen);
}
statp->closeSockets();
return (resplen);
} // for each ns
} // for each retry
statp->closeSockets();
terrno = useTcp ? terrno : gotsomewhere ? ETIMEDOUT : ECONNREFUSED;
// TODO: Remove errno once callers stop using it
errno = useTcp ? terrno
: gotsomewhere ? ETIMEDOUT /* no answer obtained */
: ECONNREFUSED /* no nameservers found */;
_resolv_cache_query_failed(statp->netid, buf, buflen, flags);
return -terrno;
}
上面代码大概分为三块,1. 查询缓存,2. 使用DNS-over-TLS查询,3. 使用TCP或者UDP查询,我们这儿只关注第三点,由于DNS解析走的UDP协议,只需关注send_dg
函数:
2.18 res_send.send_dg
static int send_dg(res_state statp, res_params* params, const uint8_t* buf, int buflen,
uint8_t* ans, int anssiz, int* terrno, size_t* ns, int* v_circuit,
int* gotsomewhere, time_t* at, int* rcode, int* delay) {
...
if (statp->nssocks[*ns] == -1) {
//创建socket
statp->nssocks[*ns].reset(socket(nsap->sa_family, SOCK_DGRAM | SOCK_CLOEXEC, 0));
if (statp->nssocks[*ns] < 0) {
*terrno = errno;
PLOG(DEBUG) << __func__ << ": socket(dg): ";
switch (errno) {
case EPROTONOSUPPORT:
case EPFNOSUPPORT:
case EAFNOSUPPORT:
return (0);
default:
return (-1);
}
}
const uid_t uid = statp->enforce_dns_uid ? AID_DNS : statp->uid;
resolv_tag_socket(statp->nssocks[*ns], uid, statp->pid);
if (statp->_mark != MARK_UNSET) {
if (setsockopt(statp->nssocks[*ns], SOL_SOCKET, SO_MARK, &(statp->_mark),
sizeof(statp->_mark)) < 0) {
*terrno = errno;
statp->closeSockets();
return -1;
}
}
// Use a "connected" datagram socket to receive an ECONNREFUSED error
// on the next socket operation when the server responds with an
// ICMP port-unreachable error. This way we can detect the absence of
// a nameserver without timing out.
if (random_bind(statp->nssocks[*ns], nsap->sa_family) < 0) {
*terrno = errno;
dump_error("bind(dg)", nsap, nsaplen);
statp->closeSockets();
return (0);
}
//连接到socket,ip地址和端口信息在nsap中,ip地址就是DNS服务器地址,对wifi来说是网关地址,对移动网络来说
//是网络创建时指定的地址
if (connect(statp->nssocks[*ns], nsap, (socklen_t)nsaplen) < 0) {
*terrno = errno;
dump_error("connect(dg)", nsap, nsaplen);
statp->closeSockets();
return (0);
}
LOG(DEBUG) << __func__ << ": new DG socket";
}
//向DNS服务器发送查询报文
if (send(statp->nssocks[*ns], (const char*)buf, (size_t)buflen, 0) != buflen) {
*terrno = errno;
PLOG(DEBUG) << __func__ << ": send: ";
statp->closeSockets();
return 0;
}
//超时时间
timespec timeout = get_timeout(statp, params, *ns);
timespec start_time = evNowTime();
timespec finish = evAddTime(start_time, timeout);
for (;;) {
// 等待回复
auto result = udpRetryingPollWrapper(statp, *ns, &finish);
if (!result.has_value()) {
const bool isTimeout = (result.error().code() == ETIMEDOUT);
*rcode = (isTimeout) ? RCODE_TIMEOUT : *rcode;
*terrno = (isTimeout) ? ETIMEDOUT : errno;
*gotsomewhere = (isTimeout) ? 1 : *gotsomewhere;
// Leave the UDP sockets open on timeout so we can keep listening for
// a late response from this server while retrying on the next server.
if (!isTimeout) statp->closeSockets();
LOG(DEBUG) << __func__ << ": " << (isTimeout) ? "timeout" : "poll";
return 0;
}
//是否重试
bool needRetry = false;
for (int fd : result.value()) {
needRetry = false;
sockaddr_storage from;
socklen_t fromlen = sizeof(from);
//接收响应消息
int resplen =
recvfrom(fd, (char*)ans, (size_t)anssiz, 0, (sockaddr*)(void*)&from, &fromlen);
if (resplen <= 0) {
*terrno = errno;
PLOG(DEBUG) << __func__ << ": recvfrom: ";
continue;
}
*gotsomewhere = 1;
if (resplen < HFIXEDSZ) {
// Undersized message.
LOG(DEBUG) << __func__ << ": undersized: " << resplen;
*terrno = EMSGSIZE;
continue;
}
int receivedFromNs = *ns;
if (needRetry =
ignoreInvalidAnswer(statp, from, buf, buflen, ans, anssiz, &receivedFromNs);
needRetry) {
res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
continue;
}
HEADER* anhp = (HEADER*)(void*)ans;
if (anhp->rcode == FORMERR && (statp->netcontext_flags & NET_CONTEXT_FLAG_USE_EDNS)) {
// Do not retry if the server do not understand EDNS0.
// The case has to be captured here, as FORMERR packet do not
// carry query section, hence res_queriesmatch() returns 0.
LOG(DEBUG) << __func__ << ": server rejected query with EDNS0:";
res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
// record the error
statp->_flags |= RES_F_EDNS0ERR;
*terrno = EREMOTEIO;
continue;
}
timespec done = evNowTime();
*delay = res_stats_calculate_rtt(&done, &start_time);
if (anhp->rcode == SERVFAIL || anhp->rcode == NOTIMP || anhp->rcode == REFUSED) {
LOG(DEBUG) << __func__ << ": server rejected query:";
res_pquery(ans, (resplen > anssiz) ? anssiz : resplen);
*rcode = anhp->rcode;
continue;
}
if (anhp->tc) {
// To get the rest of answer,
// use TCP with same server.
LOG(DEBUG) << __func__ << ": truncated answer";
*terrno = E2BIG;
*v_circuit = 1;
return 1;
}
// All is well, or the error is fatal. Signal that the
// next nameserver ought not be tried.
*rcode = anhp->rcode;
*ns = receivedFromNs;
*terrno = 0;
return resplen;
}
if (!needRetry) return 0;
}
}
DNS解析的大致流程到这儿就结束了,这儿我们只看了大致的函数调用流程,其中的大量细节都没具体看,例如缓存机制,网络选择机制等,大致流程图如下:
总结一下整个过程吧,整个流程分为如下几个步骤:
- 应用程序使用网络框架(如okhttp)发送网络请求时,首先会通过java提供的
Inet6AddressImpl.lookupHostByName
方法获取域名对应的IP地址。 Inet6AddressImpl.lookupHostByName
核心是向"/dev/socket/dnsproxyd"这个socket写入一段字符串"getaddrinfo %s %s %d %d %d %d %u",这段字符串包含方法名+参数。- netd进程的
DnsProxyListener
通过FrameworkListener
监听了**“/dev/socket/dnsproxyd”**,接收到客户端发送的字符串调用对应函数GetAddrInfoCmd::runCommand
。 - 接着就是一系列调用,各种合法判断条件,缓存机制最终其实是创建,连接socket,IP和端口就是DNS解析的地址,wifi一般是路由器地址,移动网络一般是自行配置的地址如114.114.114.114或者8.8.8.8等等。
请求和响应的报文经过解析长这样子:
37 # Query packet
38 resolv : ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29827
39 resolv : ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
40 resolv : ;; QUERY SECTION:
41 resolv : ;; www.google.com, type = A, class = IN
42 resolv :
43 resolv : Hex dump:
44 resolv : 7483010000010000000000000377777706676f6f676c6503636f6d0000010001
45
46 # Response packet
47 resolv : ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29827
48 resolv : ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
49 resolv : ;; QUERY SECTION:
50 resolv : ;; www.google.com, type = A, class = IN
51 resolv :
52 resolv : ;; ANSWER SECTION:
53 resolv : ;; www.google.com. 2m19s IN A 172.217.160.100
54 resolv :
55 resolv : Hex dump:
56 resolv : 7483818000010001000000000377777706676f6f676c6503636f6d0000010001
57 resolv : c00c000100010000008b0004acd9a064