JasonWang's Blog

Android如何进行DNS解析

字数统计: 4k阅读时长: 20 min
2023/04/17

DNS(Domain Name System)即域名解析系统,是网络访问时用于将域名解析成对应IP地址的一种分布式网络服务。比如,要访问www.google.com这个域名,Android系统会首先发送一个UDP请求到标准的53端口系统的域名解析服务器,拿到对应的IP地址后才会与服务端建立连接。除了标准的DNS服务外,目前还有HttpDNS(DNS over Https, DoH)以及基于TSLDNS服务(DNS over TLS, DoT)。

那么,AndroidDNS解析的大致框架是怎么的?整个DNS解析的流程又是怎么样的? 在看具体实现细节之前,我们不妨思考一下几个问题,想一想,如果我们自己从零开始为Android设计一个DNS系统,应该怎么做?

  • DNS服务应该何时启动初始化,以何种形式为应用提供服务?
  • Android中每个应用访问网络都会进行域名解析,如何对解析结果进行缓存,确保同样的域名不会被重复解析?
  • 不同网络切换时,比如从WIFI切换到4G网络时,DNS解析的缓存应该如何清除?

带着这几个问题,本文将从三个方面详细阐述下AndroidDNS解析的具体原理与实现细节:

  • DNS服务的初始化
  • 应用是如何执行域名解析的
  • 网络变化时,DNS域名解析缓存是如何变化的

本文参考的源码是Android S(12)

DNS系统服务的初始化

Android中网络管理的核心服务都是在NETD进程中实现的,DNS也不例外。在netd进程初始化时,系统会对DNS服务进行初始化initDnsResolver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

int main() {
Stopwatch s;
gLog.info("netd 1.0 starting");
...
// Make sure BPF programs are loaded before doing anything
android::bpf::waitForProgsLoaded();
gLog.info("BPF programs are loaded");
...
// Set local DNS mode, to prevent bionic from proxying
// back to this service, recursively.
// TODO: Check if we could remove it since resolver cache no loger
// checks this environment variable after aosp/838050.
setenv("ANDROID_DNS_MODE", "local", 1);
// Note that only call initDnsResolver after gCtls initializing.
if (!initDnsResolver()) {
ALOGE("Unable to init resolver");
exit(1);
}

MDnsSdListener mdnsl;
if (mdnsl.startListener()) {
ALOGE("Unable to start MDnsSdListener (%s)", strerror(errno));
exit(1);
}

FwmarkServer fwmarkServer(&gCtls->netCtrl, &gCtls->eventReporter, &gCtls->trafficCtrl);
if (fwmarkServer.startListener()) {
ALOGE("Unable to start FwmarkServer (%s)", strerror(errno));
exit(1);
}

Stopwatch subTime;
status_t ret;
if ((ret = NetdNativeService::start()) != android::OK) {
ALOGE("Unable to start NetdNativeService: %d", ret);
exit(1);
}
gLog.info("Registering NetdNativeService: %" PRId64 "us", subTime.getTimeAndResetUs());
...

IPCThreadState::self()->joinThreadPool();

gLog.info("netd exiting");

exit(0);
}

initDnsResolver调用resolv_init执行DNS服务的初始化,并注册一个回调函数,用于DNS服务调用时权限检查、获取当前网络ID以及打印日志等:

1
2
3
4
5
6
7
8
9
10
11
12

bool initDnsResolver() {
ResolverNetdCallbacks callbacks = {
.check_calling_permission = &checkCallingPermissionCallback,
.get_network_context = &getNetworkContextCallback,
.log = &logCallback,
.tagSocket = &tagSocketCallback,
.evaluate_domain_name = &evaluateDomainNameCallback,
};
return resolv_init(&callbacks);
}

Android SDNS服务的代码放在了一个单独的模块packages/modules/DnsResolver中,函数resolv_init就是在模块的DnsResolver.cpp中实现的, 主要是设置日志标记以及打印等级,核心逻辑是创建一个DnsResolver对象,并启动DNS服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

bool resolv_init(const ResolverNetdCallbacks* callbacks) {
android::base::InitLogging(/*argv=*/nullptr);
android::base::SetDefaultTag("libnetd_resolv");
LOG(INFO) << __func__ << ": Initializing resolver";
// TODO(b/170539625): restore log level to WARNING after clarifying flaky tests.
resolv_set_log_severity(isUserDebugBuild() ? android::base::DEBUG : android::base::WARNING);
using android::net::gApiLevel;
gApiLevel = getApiLevel();
using android::net::gResNetdCallbacks;
gResNetdCallbacks.check_calling_permission = callbacks->check_calling_permission;
gResNetdCallbacks.get_network_context = callbacks->get_network_context;
gResNetdCallbacks.log = callbacks->log;
if (gApiLevel >= 30) {
gResNetdCallbacks.tagSocket = callbacks->tagSocket;
gResNetdCallbacks.evaluate_domain_name = callbacks->evaluate_domain_name;
}
android::net::gDnsResolv = android::net::DnsResolver::getInstance();
return android::net::gDnsResolv->start();
}

DNS服务的启动主要完成了三件事情:

  • 初始化私有DNS功能DnsTlsDispatcher::getInstance()
  • 启动DNS代理服务,启动一个名为dnsproxydUnix Socket服务,用于监听应用的域名解析请求
  • 启动IDnsResolver.aidlHAL binder服务,主要提供网络切换时设置DNS,创建缓存、清理缓存等接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

DnsResolver::DnsResolver() {
// TODO: make them member variables after fixing the circular dependency:
// DnsTlsDispatcher.h -> resolv_private.h -> DnsResolver.h -> DnsTlsDispatcher.h
auto& dnsTlsDispatcher = DnsTlsDispatcher::getInstance();
auto& privateDnsConfiguration = PrivateDnsConfiguration::getInstance();
privateDnsConfiguration.setObserver(&dnsTlsDispatcher);
}

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;
}

到这里,DNS相关的功能完成了初始化。接下来,我们继续看第二部分: 应用是如何获取到域名的IP地址的。

应用是如何解析域名的

在深入细节之前,不妨看下Android中的DNS服务的大致框架。应用通过标准的JAVA接口getAllByName尝试解析域名,然后通过JNI调用libc中的接口,libc负责将域名解析请求通过名为dnsproxydUnix Socket发送给DNS代理服务,DNS服务查询到对应域名的IP地址列表再通过socket接口返回给应用。

Android DNS服务框架

  • 应用调用InetAddress.getAllByName获取域名对应的IP列表
1
2
3
4
5
6
7
8
9
10
11

/* The implementation is always dual stack IPv6/IPv4 on android */
static final InetAddressImpl impl = new Inet6AddressImpl();
...
public static InetAddress[] getAllByName(String host)
throws UnknownHostException {
// Android-changed: Resolves a hostname using Libcore.os.
// Also, returns both the Inet4 and Inet6 loopback for null/empty host
return impl.lookupAllHostAddr(host, NETID_UNSET).clone();
}

  • 尝试从JAVA中的缓存中获取IP地址,如果没有缓存,则尝试解析域名, 并将结果保存到缓存中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

/**
* Resolves a hostname to its IP addresses using a cache.
*/
private static InetAddress[] lookupHostByName(String host, int netId)
throws UnknownHostException {
BlockGuard.getThreadPolicy().onNetwork();
// Do we have a result cached?
Object cachedResult = addressCache.get(host, netId);
if (cachedResult != null) {
if (cachedResult instanceof InetAddress[]) {
// A cached positive result.
return (InetAddress[]) cachedResult;
} else {
// A cached negative result.
throw new UnknownHostException((String) cachedResult);
}
}
try {
StructAddrinfo hints = new StructAddrinfo();
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_family = AF_UNSPEC;
// If we don't specify a socket type, every address will appear twice, once
// for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
// anyway, just pick one.
hints.ai_socktype = SOCK_STREAM;
InetAddress[] addresses = Libcore.os.android_getaddrinfo(host, hints, netId);
// TODO: should getaddrinfo set the hostname of the InetAddresses it returns?
for (InetAddress address : addresses) {
address.holder().hostName = host;
address.holder().originalHostName = host;
}
addressCache.put(host, netId, addresses);
return addresses;
} catch (GaiException gaiException) {
...
}
}

  • Libcore.os.android_getaddrinfo调用libc中的DNS接口android_getaddrinfofornet尝试解析域名:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

static jobjectArray Linux_android_getaddrinfo(JNIEnv* env, jobject, jstring javaNode,
jobject javaHints, jint netId) {
...
addrinfo hints;
memset(&hints, 0, sizeof(hints));
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;
int rc = android_getaddrinfofornet(node.c_str(), NULL, &hints, netId, 0, &addressList);
std::unique_ptr<addrinfo, addrinfo_deleter> addressListDeleter(addressList);
if (rc != 0) {
throwGaiException(env, "android_getaddrinfo", rc);
return NULL;
}

// Count results so we know how to size the output array.
int addressCount = 0;
for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
++addressCount;
} else {
ALOGE("android_getaddrinfo unexpected ai_family %i", ai->ai_family);
}
}
...
// Prepare output array.
jobjectArray result = env->NewObjectArray(addressCount, JniConstants::GetInetAddressClass(env), NULL);
if (result == NULL) {
return NULL;
}

// Examine returned addresses one by one, save them in the output array.
int index = 0;
for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
// Unknown address family. Skip this address.
ALOGE("android_getaddrinfo unexpected ai_family %i", ai->ai_family);
continue;
}

// Convert each IP address into a Java byte array.
sockaddr_storage& address = *reinterpret_cast<sockaddr_storage*>(ai->ai_addr);
ScopedLocalRef<jobject> inetAddress(env, sockaddrToInetAddress(env, address, NULL));
if (inetAddress.get() == NULL) {
return NULL;
}
env->SetObjectArrayElement(result, index, inetAddress.get());
++index;
}
return result;
}

  • 继续调用android_getaddrinfofornetcontext:该函数实际通过android_getaddrinfo_proxy将域名解析请求通过dnsproxydUnix Socket发送给netdDNS解析服务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

__BIONIC_WEAK_FOR_NATIVE_BRIDGE
int
android_getaddrinfofornetcontext(const char *hostname, const char *servname,
const struct addrinfo *hints, const struct android_net_context *netcontext,
struct addrinfo **res)
{
...
struct addrinfo *pai;
const struct explore *ex;

/* hostname is allowed to be NULL */
/* servname is allowed to be NULL */
/* hints is allowed to be NULL */
assert(res != NULL);
assert(netcontext != 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;
...

//宏变量`__ANDROID__`是在`libc`库编译的时候设置的
#if defined(__ANDROID__)
int gai_error = android_getaddrinfo_proxy(
hostname, servname, hints, res, netcontext->app_netid);
if (gai_error != EAI_SYSTEM) {
return gai_error;
}
#endif
...
}

  • 函数android_getaddrinfo_proxy首先通过NetdClient中的函数dns_open_proxy创建与DNS服务的socket接口,然后向服务端发送一个getaddrinfo www.google.com x x x x x的指令, 然后尝试读取服务端返回的结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

// 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;

// 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;
...

FILE* proxy = fdopen(__netdClientDispatch.dnsOpenProxy(), "r+");
if (proxy == NULL) {
return EAI_SYSTEM;
}
netid = __netdClientDispatch.netIdForResolv(netid);

// Send the request.
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;
}
...

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) {
int32_t have_more;
if (!readBE32(proxy, &have_more)) {
break;
}
if (have_more == 0) {
success = 1;
break;
}

ai = calloc(1, sizeof(struct addrinfo) + sizeof(struct sockaddr_storage));
if (ai == NULL) {
break;
}
ai->ai_addr = (struct sockaddr*)(ai + 1);

// struct addrinfo {
// int ai_flags; /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
// int ai_family; /* PF_xxx */
// int ai_socktype; /* SOCK_xxx */
// int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
// socklen_t ai_addrlen; /* length of ai_addr */
// char *ai_canonname; /* canonical name for hostname */
// struct sockaddr *ai_addr; /* binary address */
// struct addrinfo *ai_next; /* next structure in linked list */
// };

// Read the struct piece by piece because we might be a 32-bit process
// talking to a 64-bit netd.
int32_t addr_len;
bool success =
readBE32(proxy, &ai->ai_flags) &&
readBE32(proxy, &ai->ai_family) &&
readBE32(proxy, &ai->ai_socktype) &&
readBE32(proxy, &ai->ai_protocol) &&
readBE32(proxy, &addr_len);
if (!success) {
break;
}

// Set ai_addrlen and read the ai_addr data.
ai->ai_addrlen = addr_len;
if (addr_len != 0) {
if ((size_t) addr_len > sizeof(struct sockaddr_storage)) {
// Bogus; too big.
break;
}
if (fread(ai->ai_addr, addr_len, 1, proxy) != 1) {
break;
}
}
...
}

if (ai != NULL) {
// Clean up partially-built addrinfo that we never ended up
// attaching to the response.
freeaddrinfo(ai);
}
...
}

  • 服务端的代理DnsProxyListener收到域名解析指令getaddrinfo后,启动一个新的线程处理该请求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

DnsProxyListener::GetAddrInfoCmd::GetAddrInfoCmd() : FrameworkCommand("getaddrinfo") {}

int DnsProxyListener::GetAddrInfoCmd::runCommand(SocketClient* cli, int argc, char** argv) {
logArguments(argc, argv);

if (argc != 8) {
char* msg = nullptr;
asprintf(&msg, "Invalid number of arguments to getaddrinfo: %i", argc);
LOG(WARNING) << "GetAddrInfoCmd::runCommand: " << (msg ? msg : "null");
cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
free(msg);
return -1;
}

const std::string name = argv[1];
const std::string service = argv[2];
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();

android_net_context netcontext;
gResNetdCallbacks.get_network_context(netId, uid, &netcontext);

if (useLocalNameservers) {
netcontext.flags |= NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS;
}

std::unique_ptr<addrinfo> hints;
if (ai_flags != -1 || ai_family != -1 || ai_socktype != -1 || ai_protocol != -1) {
hints.reset((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;
}

(new GetAddrInfoHandler(cli, name, service, move(hints), netcontext))->spawn();
return 0;
}

  • 处理域名解析请求的线程执行,首先会通过resolv_getaddrinfo尝试域名解析(如果没有cache,则会向域名解析服务器发送解析请求,这里默认是53的UDP端口),拿到解析后的IP地址后,通过socket发送地址列表给客户端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

void DnsProxyListener::GetAddrInfoHandler::run() {
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)) {
const char* host = mHost.starts_with('^') ? nullptr : mHost.c_str();
const char* service = mService.starts_with('^') ? nullptr : mService.c_str();
if (evaluate_domain_name(mNetContext, host)) {
rv = resolv_getaddrinfo(host, service, mHints.get(), &mNetContext, &result, &event);
} else {
rv = EAI_SYSTEM;
}
queryLimiter.finish(uid);
} else {
// Note that this error code is currently not passed down to the client.
// android_getaddrinfo_proxy() returns EAI_NODATA on any error.
rv = EAI_MEMORY;
LOG(ERROR) << "GetAddrInfoHandler::run: from UID " << uid
<< ", max concurrent queries reached";
}

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 {
success = !mClient->sendCode(ResponseCode::DnsProxyQueryResult);
addrinfo* ai = result;
while (ai && success) {
success = sendBE32(mClient, 1) && sendaddrinfo(mClient, ai);
ai = ai->ai_next;
}
success = success && sendBE32(mClient, 0);
}
...

freeaddrinfo(result);
}

有关域名解析的具体实现resolv_getaddrinfo可以参考packages/modules/DnsResolver中的代码,这里就不具体展开分析了。

网络切换时DNS缓存时如何清除的

发生网络切换时,Android需要确保DNS服务使用了正确的服务器进行域名解析,另外要清除掉之前网络域名解析的缓存,确保两个网络的解析结果是独立的。Android的网络管理都是由ConnectivityService系统服务来完成的,当两个网络发生切换时,ConnectivityService会更新网络的DNS配置, 并清除DNS服务中对应的缓存:

  • updateLinkProperties时网络状态切换: 设置DNS解析服务器,清除应用自身的缓存
  • destroyNetwork: 断开网络时,会完全清除系统对应的DNS缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

private void updateLinkProperties(NetworkAgentInfo networkAgent, @NonNull LinkProperties newLp,
@NonNull LinkProperties oldLp) {
int netId = networkAgent.network.getNetId();
...
if (isDefaultNetwork(networkAgent)) {
updateTcpBufferSizes(newLp.getTcpBufferSizes());
}

updateRoutes(newLp, oldLp, netId);

// 更新DNS配置
updateDnses(newLp, oldLp, netId);
// Make sure LinkProperties represents the latest private DNS status.
// This does not need to be done before updateDnses because the
// LinkProperties are not the source of the private DNS configuration.
// updateDnses will fetch the private DNS configuration from DnsManager.
mDnsManager.updatePrivateDnsStatus(netId, newLp);

if (isDefaultNetwork(networkAgent)) {
handleApplyDefaultProxy(newLp.getHttpProxy());
} else {
updateProxy(newLp, oldLp);
}

updateWakeOnLan(newLp);

// Captive portal data is obtained from NetworkMonitor and stored in NetworkAgentInfo.
// It is not always contained in the LinkProperties sent from NetworkAgents, and if it
// does, it needs to be merged here.
newLp.setCaptivePortalData(mergeCaptivePortalData(networkAgent.networkAgentPortalData,
networkAgent.capportApiData));
...
}

方法updateDnses主要做两件事情:

  • 设置当前网络的DNS服务器, 调用IDnsResolver的接口setResolverConfiguration设置当前系统的DNS服务的配置
  • 清除当前系统的DNS解析缓存, 发送ACTION_CLEAR_DNS_CACHE来清除缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId) {
if (oldLp != null && newLp.isIdenticalDnses(oldLp)) {
return; // no updating necessary
}

try {
mDnsManager.noteDnsServersForNetwork(netId, newLp);
mDnsManager.flushVmDnsCache();
} catch (Exception e) {
loge("Exception in setDnsConfigurationForNetwork: " + e);
}
}

关于如何设置DNS配置,大家可以查看DnsResolverService.cpp的代码;这里,我们重点看下缓存清理的大致流程。清理系统DNS缓存时,向ActivityManager发送一个ACTION_CLEAR_DNS_CACHE广播:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

/**
* Flush DNS caches and events work before boot has completed.
*/
public void flushVmDnsCache() {
/*
* Tell the VMs to toss their DNS caches
*/
final Intent intent = new Intent(ConnectivityManager.ACTION_CLEAR_DNS_CACHE);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
/*
* Connectivity events can happen before boot has completed ...
*/
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
final long ident = Binder.clearCallingIdentity();
try {
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
}

ActivityManager收到清除的广播后,调用IApplicationThread.clearDnsCache清理所有进程的DNS缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

synchronized (mProcLock) {
mProcessList.clearAllDnsCacheLOSP();
}

@GuardedBy(anyOf = {"mService", "mProcLock"})
void clearAllDnsCacheLOSP() {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mLruProcesses.get(i);
final IApplicationThread thread = r.getThread();
if (thread != null) {
try {
thread.clearDnsCache();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to clear dns cache for: " + r.info.processName);
}
}
}
}

IApplicationThread的实现在ActivityThread中, 接口clearDnsCache清除的是应用自身的解析缓存:

1
2
3
4
5
6
7
8
9

public void clearDnsCache() {
// a non-standard API to get this to libcore
InetAddress.clearDnsCache();
// Allow libcore to perform the necessary actions as it sees fit upon a network
// configuration change.
NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();
}

网络切换完成后,会断开之前的网络,清除系统中对应的解析缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

private void destroyNetwork(NetworkAgentInfo nai) {
if (nai.created) {
// Tell netd to clean up the configuration for this network
// (routing rules, DNS, etc).
// This may be slow as it requires a lot of netd shelling out to ip and
// ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
// after we've rematched networks with requests (which might change the default
// network or service a new request from an app), so network traffic isn't interrupted
// for an unnecessarily long time.
destroyNativeNetwork(nai);
mDnsManager.removeNetwork(nai.network);
}
mNetIdManager.releaseNetId(nai.network.getNetId());
nai.onNetworkDestroyed();
}

//清理系统缓存
private void destroyNativeNetwork(@NonNull NetworkAgentInfo nai) {
try {
mNetd.networkDestroy(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
loge("Exception destroying network(networkDestroy): " + e);
}
try {
mDnsResolver.destroyNetworkCache(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
loge("Exception destroying network: " + e);
}
}

总结

本文阐述了Android中DNS解析的原理和实现细节,重点关注了三个方面:

  • DNS服务的初始化
  • 应用程序如何执行域名解析
  • 网络变化时DNS域名解析缓存的变化

在Android中,DNS服务是在NETD进程中实现的,系统在进程初始化时对DNS服务进行初始化。文章还讨论了应用程序如何执行域名解析以及网络从Wi-Fi切换到4G时DNS域名解析缓存是如何清除的。理清这些流程,一方面是为了更好的定位遇到的网络问题,也可以更深入的理解Android中系统架构设计的思路以及代码的整体框架。

参考文献

原文作者:Jason Wang

更新日期:2023-04-18, 14:07:52

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. DNS系统服务的初始化
  2. 2. 应用是如何解析域名的
  3. 3. 网络切换时DNS缓存时如何清除的
  4. 4. 总结
  5. 5. 参考文献