安卓10平台DNS两层缓存

本文探讨了安卓10平台上的DNS缓存问题,详细解析了从java层到native层的DNS查询过程,揭示了DNS缓存存在于系统层面和应用层,并给出了清除DNS缓存的方法,包括清理java层的addressCache和通过ndc命令清除netd服务中的DNS缓存。
摘要由CSDN通过智能技术生成

一、 背景

当dns被污染后怎么也打不开网页,即使后来dns获取正确了,你会发现有段时间页无法访问该网页。有时候必须手动在浏览器端清除缓存才能正常访问。但是除了应用端的缓存外系统端还会有缓存的。

我们做个实验:

               dns查询log                                                                                             ping操作

由上面的操作我们可以看到 在第一次ping seewo.com的时候  左边dnsmasq的log中显示有query[A] seewo.com 查询操作的,然后返回了对应的ip:113.96.140.33 。 当我们进行第二次ping操作的时候 可以看到左边没有再次进行查询的操作了。这就坐实了在dnsmasq之前 是有缓存的。

二、DNS缓存位置查找

有了以上的思路,我们就可以着手查找哪里存在DNS缓存了。一开始一脸懵,那我们就先看看问题一是怎么请求的吧。一般我们会调用lookupAllHostAddr函数


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

接着在实现类里面:

@Override
 public InetAddress[] lookupAllHostAddr(String host, int netId) throws UnknownHostException {
     if (host == null || host.isEmpty()) {
         // Android-changed: Return both the Inet4 and Inet6 loopback addresses
         // when host == null or empty.
         return loopbackAddresses();
     }
 
     // Is it a numeric address?
     InetAddress result = InetAddressUtils.parseNumericAddressNoThrowStripOptionalBrackets(host);
     if (result != null) {
         return new InetAddress[] { result };
     }
 
     return lookupHostByName(host, netId);
 }

上面通过result 来判断从哪条路径获取InetAddress[]       之后在java缓冲区再分析lookupHostByName。

 1.               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        开始 native层缓冲区寻找       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我们先不管具体路径,先看看拿到InetAddress[]之后如何获取对应IP:

Log.e(TAG, "$hostname: ${it.hostAddress}")

可以看到其是通过  InetAddress的getHostAddress() 接口拿到的DNS解析的IP,


@Override
public String getHostAddress() {
    // Android-changed: getnameinfo returns smarter representations than getHostAddress()
    // return holder6.getHostAddress();
    return Libcore.os.getnameinfo(this, NI_NUMERICHOST); // Can't throw.
}

在实现类里面我们继续找getnameinfo方法

libcore/luni/src/main/java/libcore/io/ForwardingOs.java:


public String getnameinfo(InetAddress address, int flags) throws GaiException { return os.getnameinfo(address, flags); }

JNI方法

libcore/luni/src/main/native/libcore_io_Linux.cpp


static jstring Linux_getnameinfo(JNIEnv* env, jobject, jobject javaAddress, jint flags) {
    sockaddr_storage ss;
    socklen_t sa_len;
    if (!inetAddressToSockaddrVerbatim(env, javaAddress, 0, ss, sa_len)) {
        return NULL;
    }
    char buf[NI_MAXHOST]; // NI_MAXHOST is longer than INET6_ADDRSTRLEN.
    errno = 0;
    int rc = getnameinfo(reinterpret_cast<sockaddr*>(&ss), sa_len, buf, sizeof(buf), NULL, 0, flags);
    if (rc != 0) {
        throwGaiException(env, "getnameinfo", rc);
        return NULL;
    }
    return env->NewStringUTF(buf);
}

getnameinfo 是个native方法

bionic/libc/dns/net/getnameinfo.c

int getnameinfo(const struct sockaddr* sa, socklen_t salen, char* host, size_t hostlen,
        char* serv, size_t servlen, int flags)
{
    return android_getnameinfofornet(sa, salen, host, hostlen, serv, servlen, flags,
            NETID_UNSET, MARK_UNSET);
}
 
int android_getnameinfofornet(const struct sockaddr* sa, socklen_t salen, char* host,
        size_t hostlen, char* serv, size_t servlen, int flags, unsigned netid,
        unsigned mark)
{
    switch (sa->sa_family) {
    case AF_INET:
    case AF_INET6:
        return getnameinfo_inet(sa, salen, host, hostlen,
                serv, servlen, flags, netid, mark);
    case AF_LOCAL:
        return getnameinfo_local(sa, salen, host, hostlen,
            serv, servlen, flags);
    default:
        return EAI_FAMILY;
    }
}
 
/*
 * getnameinfo_inet():
 * Format an IPv4 or IPv6 sockaddr into a printable string.
 */
static int
getnameinfo_inet(const struct sockaddr* sa, socklen_t salen,
       char *host, socklen_t hostlen,
       char *serv, socklen_t servlen,
       int flags, unsigned netid, unsigned mark)
{
    const struct afd *afd;
    struct servent *sp;
    struct hostent *hp;
    u_short port;
    int family, i;
    const char *addr;
    uint32_t v4a;
    char numserv[512];
    char numaddr[512];
 
    /* sa is checked below */
    /* host may be NULL */
    /* serv may be NULL */
 
    if (sa == NULL)
        return EAI_FAIL;
 
    family = sa->sa_family;
    for (i = 0; afdl[i].a_af; i++)
        if (afdl[i].a_af == family) {
            afd = &afdl[i];
            goto found;
        }
    return EAI_FAMILY;
 
 found:
    // http://b/1889275: callers should be allowed to provide too much
    // space, but not too little.
    if (salen < afd->a_socklen) {
        return EAI_FAMILY;
    }
 
    /* network byte order */
    port = ((const struct sockinet *)(const void *)sa)->si_port;
    addr = (const char *)(const void *)sa + afd->a_off;
 
    if (serv == NULL || servlen == 0) {
        /*
         * do nothing in this case.
         * in case you are wondering if "&&" is more correct than
         * "||" here: rfc2553bis-03 says that serv == NULL OR
         * servlen == 0 means that the caller does not want the result.
         */
    } else {
        if (flags & NI_NUMERICSERV)
            sp = NULL;
        else {
            sp = getservbyport(port,
                (flags & NI_DGRAM) ? "udp" : "tcp");
        }
        if (sp) {
            if (strlen(sp->s_name) + 1 > (size_t)servlen)
                return EAI_MEMORY;
            strlcpy(serv, sp->s_name, servlen);
        } else {
            snprintf(numserv, sizeof(numserv), "%u", ntohs(port));
            if (strlen(numserv) + 1 > (size_t)servlen)
                return EAI_MEMORY;
            strlcpy(serv, numserv, servlen);
        }
    }
 
    switch (sa->sa_family) {
    case AF_INET:
        v4a = (uint32_t)
            ntohl(((const struct sockaddr_in *)
            (const void *)sa)->sin_addr.s_addr);
        if (IN_MULTICAST(v4a) || IN_EXPERIMENTAL(v4a))
            flags |= NI_NUMERICHOST;
        v4a >>= IN_CLASSA_NSHIFT;
        if (v4a == 0)
            flags |= NI_NUMERICHOST;
        break;
#ifdef INET6
    case AF_INET6:
        {
        const struct sockaddr_in6 *sin6;
        sin6 = (const struct sockaddr_in6 *)(const void *)sa;
        switch (sin6->sin6_addr.s6_addr[0]) {
        case 0x00:
            if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
                ;
            else if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr))
                ;
            else if (IN6_IS_ADDR_WKP(&sin6->sin6_addr))
                ;
            else
                flags |= NI_NUMERICHOST;
            break;
        default:
            if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
                flags |= NI_NUMERICHOST;
            }
            else if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr))
                flags |= NI_NUMERICHOST;
            break;
        }
        }
        break;
#endif
    }
    if (host == NULL || hostlen == 0) {
        /*
         * do nothing in this case.
         * in case you are wondering if "&&" is more correct than
  
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值