res_nsearch()
/*
* Formulate a normal query, send, and retrieve answer in supplied buffer.
* Return the size of the response on success, -1 on error.
* If enabled, implement search rules until answer or unrecoverable failure
* is detected. Error code, if any, is left in H_ERRNO.
*/
@statp: 解析器状态
@name:要解析的域名
@class:DNS报文中的查询类,对于Internet,该参数为IN
@type:DNS报文中的查询类型,如A、AAAA、PTR等
@answer:用于保存响应报文的缓存区
@asnlen:answer缓存区长度0
int res_nsearch(res_state statp,
const char *name, /* domain name */
int class, int type, /* class and type of query */
u_char *answer, /* buffer to put answer */
int anslen) /* size of answer */
{
const char *cp, * const *domain;
HEADER *hp = (HEADER *)(void *)answer;
char tmp[NS_MAXDNAME];
u_int dots;
int trailing_dot, ret, saved_herrno;
int got_nodata = 0, got_servfail = 0, root_on_list = 0;
int tried_as_is = 0;
int searched = 0;
//首先将错误码设置为HOST_NOT_FOUND,表示没有查询到
errno = 0;
RES_SET_H_ERRNO(statp, HOST_NOT_FOUND); /* True if we never query. */
//计算要查询的域名中包含几个"."
dots = 0;
for (cp = name; *cp != '\0'; cp++)
dots += (*cp == '.');
//如果要查询的域名是以"."结尾的,trailing_dot设置为1
trailing_dot = 0;
if (cp > name && *--cp == '.')
trailing_dot++;
//如果要查询的域名中没有一个".",那么认为该域名是一个本地主机的别名,这时调用res_hostalias获取
//完整的域名,如果能够获取到,则调用res_nquery()查询。当前该函数的实现为空,即该if分支不会被执行,
//关于域名搜索背后的理论依据见hostname(7)
/* If there aren't any dots, it could be a user-level alias. */
if (!dots && (cp = res_hostalias(statp, name, tmp, sizeof tmp))!= NULL)
return (res_nquery(statp, cp, class, type, answer, anslen));
/*
* If there are enough dots in the name, let's just give it a
* try 'as is'. The threshold can be set with the "ndots" option.
* Also, query 'as is', if there is a trailing dot in the name.
*/
saved_herrno = -1;
//ndots被配置为1,在res_ninit()中初始化,所以对于一般的域名解析,都会进入该分支
if (dots >= statp->ndots || trailing_dot) {
//调用res_nquerydomain()继续进行查询
ret = res_nquerydomain(statp, name, NULL, class, type,
answer, anslen);
//查询成功或者要查询的域名是一个绝对域名,那么查询结束返回
if (ret > 0 || trailing_dot)
return (ret);
//其它情况会通过域名搜索规则继续尝试查询
saved_herrno = statp->res_h_errno;
tried_as_is++;
}
//下面是域名搜索相关的内容,关于这部分内容,Android中基本不使用,这里不再继续深究,
//关于域名搜索可以参考hostname(7)
/*
* We do at least one level of search if
* - there is no dot and RES_DEFNAME is set, or
* - there is at least one dot, there is no trailing dot,
* and RES_DNSRCH is set.
*/
if ((!dots && (statp->options & RES_DEFNAMES) != 0U) ||
(dots && !trailing_dot && (statp->options & RES_DNSRCH) != 0U)) {
int done = 0;
/* Unfortunately we need to load network-specific info
* (dns servers, search domains) before
* the domain stuff is tried. Will have a better
* fix after thread pools are used as this will
* be loaded once for the thread instead of each
* time a query is tried.
*/
_resolv_populate_res_for_net(statp);
for (domain = (const char * const *)statp->dnsrch; *domain && !done; domain++) {
searched = 1;
if (domain[0][0] == '\0' ||
(domain[0][0] == '.' && domain[0][1] == '\0'))
root_on_list++;
ret = res_nquerydomain(statp, name, *domain,
class, type,
answer, anslen);
if (ret > 0)
return (ret);
/*
* If no server present, give up.
* If name isn't found in this domain,
* keep trying higher domains in the search list
* (if that's enabled).
* On a NO_DATA error, keep trying, otherwise
* a wildcard entry of another type could keep us
* from finding this entry higher in the domain.
* If we get some other error (negative answer or
* server failure), then stop searching up,
* but try the input name below in case it's
* fully-qualified.
*/
if (errno == ECONNREFUSED) {
RES_SET_H_ERRNO(statp, TRY_AGAIN);
return (-1);
}
switch (statp->res_h_errno) {
case NO_DATA:
got_nodata++;
/* FALLTHROUGH */
case HOST_NOT_FOUND:
/* keep trying */
break;
case TRY_AGAIN:
if (hp->rcode == SERVFAIL) {
/* try next search element, if any */
got_servfail++;
break;
}
/* FALLTHROUGH */
default:
/* anything else implies that we're done */
done++;
}
/* if we got here for some reason other than DNSRCH,
* we only wanted one iteration of the loop, so stop.
*/
if ((statp->options & RES_DNSRCH) == 0U)
done++;
}
}
/*
* If the query has not already been tried as is then try it
* unless RES_NOTLDQUERY is set and there were no dots.
*/
if ((dots || !searched || (statp->options & RES_NOTLDQUERY) == 0U) &&
!(tried_as_is || root_on_list)) {
ret = res_nquerydomain(statp, name, NULL, class, type,
answer, anslen);
if (ret > 0)
return (ret);
}
/* if we got here, we didn't satisfy the search.
* if we did an initial full query, return that query's H_ERRNO
* (note that we wouldn't be here if that query had succeeded).
* else if we ever got a nodata, send that back as the reason.
* else send back meaningless H_ERRNO, that being the one from
* the last DNSRCH we did.
*/
if (saved_herrno != -1)
RES_SET_H_ERRNO(statp, saved_herrno);
else if (got_nodata)
RES_SET_H_ERRNO(statp, NO_DATA);
else if (got_servfail)
RES_SET_H_ERRNO(statp, TRY_AGAIN);
return (-1);
}
res_nquerydomain()
/*
* Perform a call on res_query on the concatenation of name and domain,
* removing a trailing dot from name if domain is NULL.
*/
int res_nquerydomain(res_state statp,
const char *name,
const char *domain,
int class, int type, /* class and type of query */
u_char *answer, /* buffer to put answer */
int anslen) /* size of answer */
{
char nbuf[MAXDNAME];
//longname最终会指向要查询的域名
const char *longname = nbuf;
int n, d;
if (domain == NULL) {
/*
* Check for trailing '.';
* copy without '.' if present.
*/
//name过长,返回错误
n = strlen(name);
if (n >= MAXDNAME) {
RES_SET_H_ERRNO(statp, NO_RECOVERY);
return (-1);
}
//去掉name末尾的".",这里为了不改变name指向的内存内容,如果需要删除"."时,会
//先将name所指域名拷贝到nbuf中,然后再删除
n--;
if (n >= 0 && name[n] == '.') {
strncpy(nbuf, name, (size_t)n);
nbuf[n] = '\0';
} else
//name所指域名末尾不为".",所以无需修改,这里直接指向即可
longname = name;
} else {
//domain不为空,所以待查域名为name+domain的拼接
//名字过长,返回错误
n = strlen(name);
d = strlen(domain);
if (n + d + 1 >= MAXDNAME) {
RES_SET_H_ERRNO(statp, NO_RECOVERY);
return (-1);
}
//name在前,domain在后进行拼接
snprintf(nbuf, sizeof(nbuf), "%s.%s", name, domain);
}
//调用res_nquery进行查询
return (res_nquery(statp, longname, class, type, answer, anslen));
}
res_nquery()
/*
* Formulate a normal query, send, and await answer.
* Returned answer is placed in supplied buffer "answer".
* Perform preliminary check of answer, returning success only
* if no error is indicated and the answer count is nonzero.
* Return the size of the response on success, -1 on error.
* Error number is left in H_ERRNO.
*
* Caller must parse answer and determine whether it answers the question.
*/
int res_nquery(res_state statp,
const char *name, /* domain name */
int class, int type, /* class and type of query */
u_char *answer, /* buffer to put answer */
int anslen) /* size of answer buffer */
{
u_char buf[MAXPACKET];
HEADER *hp = (HEADER *)(void *)answer;
int n;
u_int oflags;
oflags = statp->_flags;
again:
hp->rcode = NOERROR; /* default */
//构造一个DNS查询报文到buf中
n = res_nmkquery(statp, QUERY, name, class, type, NULL, 0, NULL,
buf, sizeof(buf));
//报文构造失败,返回NO_RECOVERY错误
if (n <= 0) {
RES_SET_H_ERRNO(statp, NO_RECOVERY);
return (n);
}
//发送DNS请求报文并获取响应
n = res_nsend(statp, buf, n, answer, anslen);
//如果发送失败,返回TRY_AGAIN错误
if (n < 0) {
RES_SET_H_ERRNO(statp, TRY_AGAIN);
return (n);
}
//查询失败时,根据不同情况设置对应的错误码
if (hp->rcode != NOERROR || ntohs(hp->ancount) == 0) {
switch (hp->rcode) {
case NXDOMAIN:
RES_SET_H_ERRNO(statp, HOST_NOT_FOUND);
break;
case SERVFAIL:
RES_SET_H_ERRNO(statp, TRY_AGAIN);
break;
case NOERROR:
RES_SET_H_ERRNO(statp, NO_DATA);
break;
case FORMERR:
case NOTIMP:
case REFUSED:
default:
RES_SET_H_ERRNO(statp, NO_RECOVERY);
break;
}
return (-1);
}
return (n);
}
总结一下,这三个函数都可以由从外部直接调用,而且可以看到最终真正的DNS请求是通过res_nquery()发起的,从上面的代码分析中可以简单总结一下这三个函数的用法:
- res_nsearch():如果需要进行域名搜索相关的处理,那么调用该函数
- res_nquerydomain():如果需要将域名和domain拼接成一个完整域名再进行查询时,可以调用该函数
- res_nquery():该函数的入参就是一个待查询的域名,不再对域名本身做任何的调整,该函数进行DNS组包、发送、接收响应