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

上面代码用到了dlopendlsym函数,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”**字符串,流程图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MP8J1uYg-1686898811898)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230616144316150.png)]

接着需要知道监听/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, &params, 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(
            &params, 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, &params, 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, &params, 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解析的大致流程到这儿就结束了,这儿我们只看了大致的函数调用流程,其中的大量细节都没具体看,例如缓存机制,网络选择机制等,大致流程图如下:

在这里插入图片描述

总结一下整个过程吧,整个流程分为如下几个步骤:

  1. 应用程序使用网络框架(如okhttp)发送网络请求时,首先会通过java提供的Inet6AddressImpl.lookupHostByName方法获取域名对应的IP地址。
  2. Inet6AddressImpl.lookupHostByName核心是向"/dev/socket/dnsproxyd"这个socket写入一段字符串"getaddrinfo %s %s %d %d %d %d %u",这段字符串包含方法名+参数。
  3. netd进程的DnsProxyListener通过FrameworkListener监听了**“/dev/socket/dnsproxyd”**,接收到客户端发送的字符串调用对应函数GetAddrInfoCmd::runCommand
  4. 接着就是一系列调用,各种合法判断条件,缓存机制最终其实是创建,连接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