Android DSN之查询域名处理: res_search

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组包、发送、接收响应

猜你喜欢

转载自blog.csdn.net/fanxiaoyu321/article/details/82765977