Android DNS之getaddrinfo()的实现

这篇笔记分析了库函数getaddrinfo()的代码实现。

原型解读

int getaddrinfo(const char *hostname, const char *servname,
    const struct addrinfo *hints, struct addrinfo **res);
  • hostname: 和gethostbyname()的入参hostname相同,要查询的域名;
  • servname:要查询的服务名,如果指定,则返回值中还会有服务名对应的端口号;也就是该函数还有getservbyname()的功能;
  • hints:表面意思为暗示,使用该参数可以对查询结果进行约束,只返回那些满足条件的查询结果;后面称该参数为过滤器
  • res:该参数用于保存指向查询结果地址的指针,保存查询结果的内存有该函数内部分配,使用完毕后调用者需要调用freeaddrinfo()清除,调用者只需要提供保存struct addrinfo *的地址即可。

关于该函数的更详细介绍可以参考getaddrinfo(3)。

入口

int getaddrinfo(const char *hostname, const char *servname,
    const struct addrinfo *hints, struct addrinfo **res)
{
	//增加了入参NETID_UNSET和MARK_UNSET
	return android_getaddrinfofornet(hostname, servname, hints, NETID_UNSET, MARK_UNSET, res);
}

int android_getaddrinfofornet(const char *hostname, const char *servname,
    const struct addrinfo *hints, unsigned netid, unsigned mark, struct addrinfo **res)
{
	//入参中netid和mark值可以影响DNS请求包从哪个网卡出去,属于网络相关的配置,将这些信息组织成参数
    //netcontext继续向下传递
	struct android_net_context netcontext = {
		.app_netid = netid,
		.app_mark = mark,
		.dns_netid = netid,
		.dns_mark = mark,
		.uid = NET_CONTEXT_INVALID_UID,
        };
	return android_getaddrinfofornetcontext(hostname, servname, hints, &netcontext, res);
}

这里有必要对这两个函数做个说明:

  • getaddrinfo()是标准的libc接口,系统中Framework以下的C/C++代码可以调用该接口实现DNS查询;
  • android_getaddrinfofornet()是Android对libc的扩展,当APP在Framework通过InetAddress类提供的接口进行DNS查询时,最终会通过JNI调用到该函数,也就是说该接口是给Framework使用的。

android_getaddrinfofornetcontext()

该函数的作用主要是对输入参数的合法性进行检查,以及处理hostname为空或者是纯数字格式的情形,如果需要真正发起DNS请求,调用explore_fqdn()完成。

/* 定义了faimly、socktype、protocol字段的有效组合 */
static const struct explore explore[] = {
#ifdef INET6
	{ PF_INET6, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
	{ PF_INET6, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
	{ PF_INET6, SOCK_RAW, ANY, NULL, 0x05 },
#endif
	{ PF_INET, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
	{ PF_INET, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
	{ PF_INET, SOCK_RAW, ANY, NULL, 0x05 },
	{ PF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
	{ PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
	{ PF_UNSPEC, SOCK_RAW, ANY, NULL, 0x05 },
	{ -1, 0, 0, NULL, 0 },
};

int android_getaddrinfofornetcontext(const char *hostname, const char *servname,
    const struct addrinfo *hints, const struct android_net_context *netcontext,
    struct addrinfo **res)
{
	//这里定义了3个addrinfo结构变量,后面对于这些变量的使用非常绕,要注意理解下面的注释说明
	struct addrinfo sentinel;
	struct addrinfo *cur;
	int error = 0;
	struct addrinfo ai;
	struct addrinfo ai0;
	struct addrinfo *pai;
	const struct explore *ex;

	/* hostname is allowed to be NULL */
	/* servname is allowed to be NULL */
	/* hints is allowed to be NULL */
	assert(res != NULL);
	assert(netcontext != NULL);
    //sentinel清零,并且cur指向该结构
	memset(&sentinel, 0, sizeof(sentinel));
	cur = &sentinel;
    //pai指向ai,并且对ai的成员进行初始化
	pai = &ai;
	pai->ai_flags = 0;
	pai->ai_family = PF_UNSPEC;
	pai->ai_socktype = ANY;
	pai->ai_protocol = ANY;
	pai->ai_addrlen = 0;
	pai->ai_canonname = NULL;
	pai->ai_addr = NULL;
	pai->ai_next = NULL;

    //hostname和servname二者不能同时为空,至少要指定一个
	if (hostname == NULL && servname == NULL)
		return EAI_NONAME;

	//如果指定了过滤器,则对过滤器成员进行检查,因为作为过滤器,并非addrinfo中的每个成员都是可以指定的
	if (hints) {
		/* error check for hints */
        //作为过滤器,addrinfo的这四个成员是不能指定的,必须保持为0才行
		if (hints->ai_addrlen || hints->ai_canonname ||
		    hints->ai_addr || hints->ai_next)
			ERR(EAI_BADHINTS); /* xxx */
        //ai_flags中有没有指定非法的标记
		if (hints->ai_flags & ~AI_MASK)
			ERR(EAI_BADFLAGS);
        //ai_faimly成员只可取下面这三个值,其它值均非法
		switch (hints->ai_family) {
		case PF_UNSPEC:
		case PF_INET:
#ifdef INET6
		case PF_INET6:
#endif
			break;
		default:
			ERR(EAI_FAMILY);
		}
        //从这里可以看出,过滤器参数最终是被保存在了ai中,这就是ai的作用
		memcpy(pai, hints, sizeof(*pai));

		/*
		 * if both socktype/protocol are specified, check if they
		 * are meaningful combination.
		 */
        //如果同时指定了hints参数中的ai_socktype和ai_protocol字段(非0),那么继续检查这两个参数的组合是否合理
        //因为并非所有的组合都是有效的,比如socktype指定为STREAM,但是protocol指定为DGRAM就不合理
		if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) {
        	//expore数组定义了faimly、socktype、protocol的有效组合
			for (ex = explore; ex->e_af >= 0; ex++) {
				if (pai->ai_family != ex->e_af)
					continue;
				if (ex->e_socktype == ANY)
					continue;
				if (ex->e_protocol == ANY)
					continue;
                //如果是非法组合返回BADHINTS错误
				if (pai->ai_socktype == ex->e_socktype && pai->ai_protocol != ex->e_protocol) {
					ERR(EAI_BADHINTS);
				}
			}
		}
	}//end of "if (hints)"

	/*
	 * check for special cases.  (1) numeric servname is disallowed if
	 * socktype/protocol are left unspecified. (2) servname is disallowed
	 * for raw and other inet{,6} sockets.
	 */
    //这段逻辑用于检查服务名和hints参数是否存在冲突(见注释)
	if (MATCH_FAMILY(pai->ai_family, PF_INET, 1)
#ifdef PF_INET6
	 || MATCH_FAMILY(pai->ai_family, PF_INET6, 1)
#endif
	    ) {
        //ai0的作用就是备份hints的内容
		ai0 = *pai;	/* backup *pai */

		if (pai->ai_family == PF_UNSPEC) {
#ifdef PF_INET6
			pai->ai_family = PF_INET6;
#else
			pai->ai_family = PF_INET;
#endif
		}
		error = get_portmatch(pai, servname);
		if (error)
			ERR(error);
		//将备份的hints参数内容再赋值回去,保证这步检查不会修改hints本身的内容
		*pai = ai0;
	}

	ai0 = *pai;
    //处理hostname为空,或者hostname为纯数字的情形
	/* NULL hostname, or numeric hostname */
	for (ex = explore; ex->e_af >= 0; ex++) {
		*pai = ai0;

		/* PF_UNSPEC entries are prepared for DNS queries only */
		if (ex->e_af == PF_UNSPEC)
			continue;

		if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex)))
			continue;
		if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex)))
			continue;
		if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex)))
			continue;

		if (pai->ai_family == PF_UNSPEC)
			pai->ai_family = ex->e_af;
		if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
			pai->ai_socktype = ex->e_socktype;
		if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
			pai->ai_protocol = ex->e_protocol;

		if (hostname == NULL)
        	//处理只请求服务名转换的情况,转黄结果保存到cur->ai_next中。
            //从这里可以看出sentinel的作用来,它本身不保存查询结果,但是查询结果就保存在
            //sentinel.ai_next后面的addrinfo链表中,而cur指针永远指向当前链表的最后一个成员,
            //这样便于查询过程中不断添加查询结果,见下面的while循环
			error = explore_null(pai, servname, &cur->ai_next);
		else
        	//尝试按照数字格式解析hostname以及服务名装换
			error = explore_numeric_scope(pai, hostname, servname,
			    &cur->ai_next);

		if (error)
			goto free;
		//移动cur指针,使其指向当前查询结果链表的最后一个成员
		while (cur->ai_next)
			cur = cur->ai_next;
	}

	/*
	 * XXX
	 * If numeric representation of AF1 can be interpreted as FQDN
	 * representation of AF2, we need to think again about the code below.
	 */
    //如果这里非空,说明上一个for循环中已经解析的结果,请求的hostname就是为空或者是纯数字格式,
    //那么无需再发起DNS查询,返回结果即可
	if (sentinel.ai_next)
		goto good;

	//hostname为空的场景上面应该已经处理,不应该会执行到这里
	if (hostname == NULL)
		ERR(EAI_NODATA);
    //hostname为数字格式的情形上面应该已经处理,不应该会执行到这里
	if (pai->ai_flags & AI_NUMERICHOST)
		ERR(EAI_NONAME);

	//这里是Android的变化,就是将DNS查询导向到netd中,然后由netd处理后再回到这里,
    //这个过程和gethostbyname()中的处理流程一致,可见gethostbyname()实现中的分析
#if defined(__ANDROID__)
	//非netd调用会阻塞到这里,等待netd查询完后返回
	int gai_error = android_getaddrinfo_proxy(
		hostname, servname, hints, res, netcontext->app_netid);
	//如果是netd调用,那么android_getaddrinfo_proxy()会返回EAI_SYSTEM错误,继续后续的逻辑
    if (gai_error != EAI_SYSTEM) {
		return gai_error;
	}
#endif

	/*
	 * hostname as alphabetical name.
	 * we would like to prefer AF_INET6 than AF_INET, so we'll make a
	 * outer loop by AFs.
	 */
    //按照explore数组中指定的有效组合,调用explore_fqdn()进行DNS查询
	for (ex = explore; ex->e_af >= 0; ex++) {
		*pai = ai0;

		/* require exact match for family field */
		if (pai->ai_family != ex->e_af)
			continue;

		if (!MATCH(pai->ai_socktype, ex->e_socktype,
				WILD_SOCKTYPE(ex))) {
			continue;
		}
		if (!MATCH(pai->ai_protocol, ex->e_protocol,
				WILD_PROTOCOL(ex))) {
			continue;
		}
		//这步会执行,以为explore中,socktype字段都是固定的,并非通配
		if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
			pai->ai_socktype = ex->e_socktype;
		if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
			pai->ai_protocol = ex->e_protocol;
		//进行DNS查询
		error = explore_fqdn(
			pai, hostname, servname, &cur->ai_next, netcontext);
		//查询结果指针cur前移
		while (cur && cur->ai_next)
			cur = cur->ai_next;
	}
	//sentinel.ai_next不为空,说明有查询结果,所以设置error为0
	if (sentinel.ai_next)
		error = 0;
	//有错误发生,需要调用freeaddrinfo()释放可能已经分配的内存空间
	if (error)
		goto free;

	if (error == 0) {
		if (sentinel.ai_next) {
 good:
 			//查询过程没有问题 && 查询到了结果,返回成功
			*res = sentinel.ai_next;
			return SUCCESS;
		} else
        	//查询过程没有问题(error=0),但是没有查到结果(sentinel.ai_next=NULL),也是失败
			error = EAI_FAIL;
	}
 free:
 bad:
 	//查询失败,或者查询过程出错,释放内存后返回错误码
	if (sentinel.ai_next)
		freeaddrinfo(sentinel.ai_next);
	*res = NULL;
	return error;
}

explore_fqdn()

看到该函数的代码,一定会非常熟悉,在gethostbyname()的实现中,已经见过这种查询表方式,这里不再赘述。

/*
 * FQDN hostname, DNS lookup
 */
static int explore_fqdn(const struct addrinfo *pai, const char *hostname,
    const char *servname, struct addrinfo **res,
    const struct android_net_context *netcontext)
{
	struct addrinfo *result;
	struct addrinfo *cur;
	int error = 0;
    //_files_getaddrinfo()完成基于文件的查询;_dns_getaddrinfo()完成基于DNS服务器的查询;
    //_yp_getaddrinfo()不会被调用
	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getaddrinfo, NULL)
		{ NSSRC_DNS, _dns_getaddrinfo, NULL },	/* force -DHESIOD */
		NS_NIS_CB(_yp_getaddrinfo, NULL)
		{ 0, 0, 0 }
	};

	assert(pai != NULL);
	/* hostname may be NULL */
	/* servname may be NULL */
	assert(res != NULL);
	result = NULL;

	/*
	 * if the servname does not match socktype/protocol, ignore it.
	 */
    //如果指定了servname,检查socktype和protocol是否有冲突,Android中不涉及
	if (get_portmatch(pai, servname) != 0)
		return 0;

	//调用nsdispatch()进行查询分发
	switch (nsdispatch(&result, dtab, NSDB_HOSTS, "getaddrinfo",
			default_dns_files, hostname, pai, netcontext)) {
	case NS_TRYAGAIN:
		error = EAI_AGAIN;
		goto free;
	case NS_UNAVAIL:
		error = EAI_FAIL;
		goto free;
	case NS_NOTFOUND:
		error = EAI_NODATA;
		goto free;
	case NS_SUCCESS:
		error = 0;
        //如果hostname查询成功,如果指定了servname,将对应的端口名转换为端口号,Android不涉及
		for (cur = result; cur; cur = cur->ai_next) {
			GET_PORT(cur, servname);
			/* canonname should be filled already */
		}
		break;
	}

	*res = result;
	return 0;

free:
	if (result)
		freeaddrinfo(result);
	return error;
}

_dns_getaddrinfo()

我们不关注基于文件的查询_files_getaddrinfo()的实现,只看基于DNS服务器的查询实现。

static int _dns_getaddrinfo(void *rv, void *cb_data, va_list ap)
{
	struct addrinfo *ai;
	querybuf *buf, *buf2;
	const char *name;
	const struct addrinfo *pai;
	struct addrinfo sentinel, *cur;
	struct res_target q, q2;
	res_state res;
	const struct android_net_context *netcontext;

	//从explore_fqdn()传入的三个参数
	name = va_arg(ap, char *);
	pai = va_arg(ap, const struct addrinfo *);
	netcontext = va_arg(ap, const struct android_net_context *);

	memset(&q, 0, sizeof(q));
	memset(&q2, 0, sizeof(q2));
	memset(&sentinel, 0, sizeof(sentinel));
	cur = &sentinel;
	//buf和buf2用于保存查询结果,见下文的getanswer()
	buf = malloc(sizeof(*buf));
	if (buf == NULL) {
		h_errno = NETDB_INTERNAL;
		return NS_NOTFOUND;
	}
	buf2 = malloc(sizeof(*buf2));
	if (buf2 == NULL) {
		free(buf);
		h_errno = NETDB_INTERNAL;
		return NS_NOTFOUND;
	}

	//这段逻辑非常重要,决定了是否发起AAAA与A查询以及它们的顺序
	switch (pai->ai_family) {
	case AF_UNSPEC:
		//当没有指定具体的failmy时,优先IPv6
		/* prefer IPv6 */
		q.name = name;
		q.qclass = C_IN;
		q.answer = buf->buf;
		q.anslen = sizeof(buf->buf);
		int query_ipv6 = 1, query_ipv4 = 1;
        //如果设置了AI_ADDRCONFIG标记,那么表示要发起对应的请求时,本机必须至少有一个相应类型的IP地址,
        //_have_ipv6()和_have_ipv4()就是分别用来检测本机是否有IPv4和IPv6地址的
		if (pai->ai_flags & AI_ADDRCONFIG) {
			query_ipv6 = _have_ipv6(netcontext->app_mark, netcontext->uid);
			query_ipv4 = _have_ipv4(netcontext->app_mark, netcontext->uid);
		}
        //注意:在需要发起AAAA请求的条件中会判断是否需要发起A请求,这是为了在AAAA请求查询失败时可以继续发起A请求
		if (query_ipv6) {
			q.qtype = T_AAAA;
			if (query_ipv4) {
            	//将q2接入到q的后面,这样可以在AAAA请求查询失败时可以继续发起A请求
				q.next = &q2;
				q2.name = name;
				q2.qclass = C_IN;
				q2.qtype = T_A;
				q2.answer = buf2->buf;
				q2.anslen = sizeof(buf2->buf);
			}
		} else if (query_ipv4) {
			q.qtype = T_A;
		} else {
			free(buf);
			free(buf2);
			return NS_NOTFOUND;
		}
		break
    //如果已经已经具体指定了faimy,那么按照指定的failmy发起A或者AAAA即可
	case AF_INET:
		q.name = name;
		q.qclass = C_IN;
        //A查询
		q.qtype = T_A;
		q.answer = buf->buf;
		q.anslen = sizeof(buf->buf);
		break;
	case AF_INET6:
		q.name = name;
		q.qclass = C_IN;
        //AAAA查询
		q.qtype = T_AAAA;
		q.answer = buf->buf;
		q.anslen = sizeof(buf->buf);
		break;
	default:
    	//其它faimy均为非法值
		free(buf);
		free(buf2);
		return NS_UNAVAIL;
	}
	//获取线程独立的resolver状态结构
	res = __res_get_state();
	if (res == NULL) {
		free(buf);
		free(buf2);
		return NS_NOTFOUND;
	}

	/* this just sets our netid val in the thread private data so we don't have to
	 * modify the api's all the way down to res_send.c's res_nsend.  We could
	 * fully populate the thread private data here, but if we get down there
	 * and have a cache hit that would be wasted, so we do the rest there on miss
	 */
    //将netid、mark和qhook参数设置到res_stats中,这三个参数在res_nsend()中都有使用
	res_setnetcontext(res, netcontext);
    //getaddrinfo()将res_search.c中的三个接口重新实现了一遍,这样做的原因见上面的注释(本人没有理解!!!)
	if (res_searchN(name, &q, res) < 0) {
		__res_put_state(res);
		free(buf);
		free(buf2);
		return NS_NOTFOUND;
	}
    //解析查询结果报文
	ai = getanswer(buf, q.n, q.name, q.qtype, pai);
	if (ai) {
		cur->ai_next = ai;
		while (cur && cur->ai_next)
			cur = cur->ai_next;
	}
    //如果有A请求查询结果,继续解析A请求查询结果报文
	if (q.next) {
		ai = getanswer(buf2, q2.n, q2.name, q2.qtype, pai);
		if (ai)
			cur->ai_next = ai;
	}
	free(buf);
	free(buf2);
    //如果查询失败,返回对应的错误码
	if (sentinel.ai_next == NULL) {
		__res_put_state(res);
		switch (h_errno) {
		case HOST_NOT_FOUND:
			return NS_NOTFOUND;
		case TRY_AGAIN:
			return NS_TRYAGAIN;
		default:
			return NS_UNAVAIL;
		}
	}
	//按照RFC 6762中要求的顺序将查询结果排序后返回,关于这个排序有需要再研究吧
	_rfc6724_sort(&sentinel, netcontext->app_mark, netcontext->uid);

	__res_put_state(res);

	*((struct addrinfo **)rv) = sentinel.ai_next;
	return NS_SUCCESS;
}

后面的代码不再继续分析,它和res_search()中的逻辑非常类似,感兴趣的可以自行分析。

其它

MATCH宏

#define MATCH(x, y, w) 							\
	((x) == (y) || (/*CONSTCOND*/(w) && ((x) == ANY || (y) == ANY)))

作用是检查x和y是否匹配,返回布尔值,匹配分两种情况:

  1. x和y的值确实是相等的,那么匹配,对应表达式中的(x) == (y)
  2. x和y不等,但是w参数非0,并且x和y中有任何一个是通配的,即值为ANY(0),那么也认为是匹配的。实际上,这里的w含义是wildcard,即通配,如果w为0,即表示把通配考虑进去,否则按照x和y的真实值是否相等来判断是否匹配。

你可能感兴趣的:(Android,Android,libc中dns部分源码分析)