1 java层
参考文章提及
Java层的快速缓存,应用层如果想要解析DNS,基本上都是调用InetAddress.getByName(String host)接口,最终调用的就是lookupHostByName:
搜一下代码实现
http://androidxref.com/9.0.0_r3/xref/libcore/ojluni/src/main/java/java/net/Inet6AddressImpl.java#115
/**
* 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 {
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);
}
}
http://androidxref.com/9.0.0_r3/xref/libcore/luni/src/main/java/java/net/AddressCache.java#29
/**
* Returns the cached InetAddress[] for 'hostname' on network 'netId'. Returns null
* if nothing is known about 'hostname'. Returns a String suitable for use as an
* UnknownHostException detail message if 'hostname' is known not to exist.
*/
public Object get(String hostname, int netId) {
AddressCacheEntry entry = cache.get(new AddressCacheKey(hostname, netId));
// Do we have a valid cache entry?
if (entry != null && entry.expiryNanos >= System.nanoTime()) {
return entry.value;
}
// Either we didn't find anything, or it had expired.
// No need to remove expired entries: the caller will provide a replacement shortly.
return null;
}
static class AddressCacheEntry {
// Either an InetAddress[] for a positive entry,
// or a String detail message for a negative entry.
final Object value;
/**
* The absolute expiry time in nanoseconds. Nanoseconds from System.nanoTime is ideal
* because -- unlike System.currentTimeMillis -- it can never go backwards.
*
* We don't need to worry about overflow with a TTL_NANOS of 2s.
*/
final long expiryNanos;
AddressCacheEntry(Object value) {
this.value = value;
this.expiryNanos = System.nanoTime() + TTL_NANOS;
}
}
// The TTL for the Java-level cache is short, just 2s.
private static final long TTL_NANOS = 2 * 1000000000L;
可以看到dns的缓存是当前时间加上2s,也就是说java层的dns缓存时间为2s。
/**
* When the cache contains more entries than this, we start dropping the oldest ones.
* This should be a power of two to avoid wasted space in our custom map.
*/
private static final int MAX_ENTRIES = 16;
// The actual cache.
private final BasicLruCache<AddressCacheKey, AddressCacheEntry> cache
= new BasicLruCache<AddressCacheKey, AddressCacheEntry>(MAX_ENTRIES);
另外看下cache的大小和代码结构,LRU是指least-recently-used算法,也就是说会将最近最少使用的dns缓存移除掉,数据结果大小为16
LRU算法是依赖
http://androidxref.com/9.0.0_r3/xref/libcore/ojluni/src/main/java/java/util/LinkedHashMap.java#496
LinkedHashMap的accessOrder参数,如果设置为true,则进行访问的时候会将该元素挪到最后,最不经常访问的自然会到链表头
/**
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @param accessOrder the ordering mode - <tt>true</tt> for
* access-order, <tt>false</tt> for insertion-order
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
*
* <p>A return value of {@code null} does not <i>necessarily</i>
* indicate that the map contains no mapping for the key; it's also
* possible that the map explicitly maps the key to {@code null}.
* The {@link #containsKey containsKey} operation may be used to
* distinguish these two cases.
*/
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
缓存失败
另外如果Inet6AddressImpl中dns查询失败也会进行缓存,也就是说java层的dns最多查询频率为2s一次,如果失败了2s内也会返回失败结果。
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) {
// If the failure appears to have been a lack of INTERNET permission, throw a clear
// SecurityException to aid in debugging this common mistake.
// http://code.google.com/p/android/issues/detail?id=15722
if (gaiException.getCause() instanceof ErrnoException) {
if (((ErrnoException) gaiException.getCause()).errno == EACCES) {
throw new SecurityException("Permission denied (missing INTERNET permission?)", gaiException);
}
}
// Otherwise, throw an UnknownHostException.
String detailMessage = "Unable to resolve host \"" + host + "\": " + Libcore.os.gai_strerror(gaiException.error);
addressCache.putUnknownHost(host, netId, detailMessage);
throw gaiException.rethrowAsUnknownHostException(detailMessage);
}
2 Native C层
InetAddress.getByName接口最终会通过JNI进入libc的getaddrinfo函数,getaddrinfo这个函数的过程比较复杂,还会跟netd进程进行socket通信,这里就不讲了,这里主要讲跟DNS缓存时长相关的地方,DNS请求由res_nsend函数发出:
api调用流程
http://androidxref.com/9.0.0_r3/xref/bionic/libc/dns/net/getaddrinfo.c#567
这边涉及Android的,是和netd进行通信获取ip地址解析结果的
#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
//打开代理,实际上是建立一条到DnsProxyListener的socket连接
FILE* proxy = android_open_proxy();
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;
}
netd这边处理的
http://androidxref.com/9.0.0_r3/xref/system/netd/server/DnsProxyListener.cpp#GetAddrInfoCmd
int DnsProxyListener::GetAddrInfoCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
if (DBG) logArguments(argc, argv);
if (argc != 8) {
char* msg = NULL;
asprintf( &msg, "Invalid number of arguments to getaddrinfo: %i", argc);
ALOGW("%s", msg);
cli->sendMsg(ResponseCode::CommandParameterError, msg, false);
free(msg);
return -1;
}
char* name = argv[1];
if (strcmp("^", name) == 0) {
name = NULL;
} else {
name = strdup(name);
}
char* service = argv[2];
if (strcmp("^", service) == 0) {
service = NULL;
} else {
service = strdup(service);
}
http://androidxref.com/9.0.0_r3/xref/system/netd/server/DnsProxyListener.cpp#256
void DnsProxyListener::GetAddrInfoHandler::run() {
if (DBG) {
ALOGD("GetAddrInfoHandler, now for %s / %s / {%u,%u,%u,%u,%u,%u}", mHost, mService,
mNetContext.app_netid, mNetContext.app_mark,
mNetContext.dns_netid, mNetContext.dns_mark,
mNetContext.uid, mNetContext.flags);
}
struct addrinfo* result = NULL;
Stopwatch s;
maybeFixupNetContext(&mNetContext);
const uid_t uid = mClient->getUid();
uint32_t rv = 0;
if (queryLimiter.start(uid)) {
rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, &result);
queryLimiter.finish(uid);
}
看着这边netd绕完了还会再回到
http://androidxref.com/9.0.0_r3/xref/bionic/libc/dns/net/getaddrinfo.c#_dns_getaddrinfo
这边继续dns的查询
/*
* Form all types of queries.
* Returns the size of the result or -1.
*/
构造查询报文
n = res_nmkquery(res, QUERY, name, class, type, NULL, 0, NULL,
buf, sizeof(buf));
n = res_nsend(res, buf, n, answer, anslen);
http://androidxref.com/9.0.0_r3/xref/bionic/libc/dns/resolv/res_send.c#361
这边去发送dns请求了
http://androidxref.com/9.0.0_r3/xref/bionic/libc/dns/resolv/res_cache.c#1674
_resolv_cache_lookup 这个函数负责了libc里dns 缓存的查询
ttl = answer_getTTL(answer, answerlen);
if (ttl > 0) {
e = entry_alloc(key, answer, answerlen);
if (e != NULL) {
e->expires = ttl + _time_now();
_cache_add_p(cache, lookup, e);
}
}
/* remove stale entries here */
if (now >= e->expires) {
XLOG( " NOT IN CACHE (STALE ENTRY %p DISCARDED)", *lookup );
XLOG_QUERY(e->query, e->querylen);
_cache_remove_p(cache, lookup);
goto Exit;
}
对于缓存是根据dns报文中的ttl进行过期时间设定的,如果超过ttl的时间,则进行cache的移除。
if (cache->num_entries >= cache->max_entries) {
_cache_remove_expired(cache);
if (cache->num_entries >= cache->max_entries) {
_cache_remove_oldest(cache);
}
移除操作与java层差不多,首先移除过期的cache,若还多的话,移除最老的cache.
#define CONFIG_MAX_ENTRIES 64 * 2 * 5
static int
_res_cache_get_max_entries( void )
{
int cache_size = CONFIG_MAX_ENTRIES;
const char* cache_mode = getenv("ANDROID_DNS_MODE");
if (cache_mode == NULL || strcmp(cache_mode, "local") != 0) {
// Don't use the cache in local mode. This is used by the proxy itself.
cache_size = 0;
}
XLOG("cache size: %d", cache_size);
return cache_size;
}
static struct resolv_cache*
_resolv_cache_create( void )
{
struct resolv_cache* cache;
cache = calloc(sizeof(*cache), 1);
if (cache) {
cache->max_entries = _res_cache_get_max_entries();
ttl的话如果dns解析结果有多个,取最小的ttl
/**
* Parse the answer records and find the appropriate
* smallest TTL among the records. This might be from
* the answer records if found or from the SOA record
* if it's a negative result.
*
* The returned TTL is the number of seconds to
* keep the answer in the cache.
*
* In case of parse error zero (0) is returned which
* indicates that the answer shall not be cached.
*/
static u_long
answer_getTTL(const void* answer, int answerlen)
{
ns_msg handle;
int ancount, n;
u_long result, ttl;
ns_rr rr;
result = 0;
if (ns_initparse(answer, answerlen, &handle) >= 0) {
// get number of answer records
ancount = ns_msg_count(handle, ns_s_an);
if (ancount == 0) {
// a response with no answers? Cache this negative result.
result = answer_getNegativeTTL(handle);
} else {
for (n = 0; n < ancount; n++) {
if (ns_parserr(&handle, ns_s_an, n, &rr) == 0) {
ttl = ns_rr_ttl(rr);
if (n == 0 || ttl < result) {
result = ttl;
}
} else {
XLOG("ns_parserr failed ancount no = %d. errno = %s\n", n, strerror(errno));
}
}
}
} else {
XLOG("ns_parserr failed. %s\n", strerror(errno));
}
XLOG("TTL = %lu\n", result);
return result;
}
由上我们可以总结Native C层的DNS缓存:
每个缓存的有效时长就是ttl,也就是DNS服务器返回的一个属性值
DNS缓存的最大个数为#define CONFIG_MAX_ENTRIES 64 * 2 * 5 640个
DNS缓存的管理办法是,个数达到最大值,则一次性清除所有过期的缓存,如果没有过期缓存则删除最久的一个
3.总结
java层
- 每个缓存的有效时长就是2s(失败的结果也是)
- DNS缓存的最大个数为16个
c层
- 每个缓存的有效时长就是ttl,也就是DNS服务器返回的一个属性值,如果有多个,取最小值
- DNS缓存的最大个数为#define CONFIG_MAX_ENTRIES 64 * 2 * 5 640个
- DNS缓存的管理办法是,个数达到最大值,则一次性清除所有过期的缓存,如果没有过期缓存则删除最久的一个