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缓存的管理办法是,个数达到最大值,则一次性清除所有过期的缓存,如果没有过期缓存则删除最久的一个

android 本地dns缓存 android dns缓存时间_缓存