Android DNS之gethostbyname()的实现

原型解读

struct hostent *gethostbyname(const char *name);

入参

字符串name可取的值分为三种类型:

  1. 十进制数字格式的IPv4地址
  2. 十六进制数字格式的IPv6地址
  3. 域名

返回值

返回值为指向struct hostent类型的指针,调用者显然没有提前分配它,那么该结构一定是有内部实现分配的,所以该函数是不可重入的。struct hostent结构定义如下:

struct hostent {
    char  *h_name;            /* official name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
  • h_name:代表正式名字
  • h_aliases:指针数组,每个成员指向的字符串代表域名的别名,该数组以NULL结尾
  • h_addrtype:表示解析后的地址类型,可取的值为AF_INET和AF_INET6
  • h_length:地址长度,对于IPv4地址为4,对于IPv6为16字节
  • h_addr_list:指针数组,每个成员指向一个解析出来的IP地址,该数组以NULL结尾
  • h_addr:为了向后兼容,也可以用h_addr引用第一个解析结果

入口

struct hostent *
gethostbyname(const char *name)
{
	struct hostent *result = NULL;
    //该结构非常重要,会贯穿整个DNS请求过程
	res_static rs = __res_get_static(); /* Use res_static to provide thread-safety. */

	//调用可重入版本继续请求
	gethostbyname_r(name, &rs->host, rs->hostbuf, sizeof(rs->hostbuf), &result, &h_errno);
	return result;
}

调用可重入版本继续请求

/* The prototype of gethostbyname_r is from glibc, not that in netbsd. */
int
gethostbyname_r(const char *name, struct hostent *hp, char *buf, size_t buflen,
    struct hostent **result, int *errorp)
{
	//获取DNS解析器状态结构
	res_state res = __res_get_state();
	if (res == NULL) {
	  *result = NULL;
		*errorp = NETDB_INTERNAL;
		return -1;
	}
	//检查DNS请求的域名不能为空
	_DIAGASSERT(name != NULL);
	//根据res->options中是否设置了RES_USE_INET6选项,决定是否先发起4A请求
	if (res->options & RES_USE_INET6) {
		*result = gethostbyname_internal(name, AF_INET6, res, hp, buf, buflen, errorp,
		                                 &NETCONTEXT_UNSET);
        //可见,如果4A请求成功返回,那么就不会再发起A类请求
		if (*result) {
			__res_put_state(res);
			return 0;
		}
	}
    //4A请求查询失败,或者没有设置RES_USE_INET6选项,那么就会发起A类型请求
	*result = gethostbyname_internal(name, AF_INET, res, hp, buf, buflen, errorp,
	                                 &NETCONTEXT_UNSET);
	__res_put_state(res);
	if (!*result && errno == ENOSPC) {
	  errno = ERANGE;
	  return ERANGE; /* Return error as in linux manual page. */
	}
    //返回0表示成功,-1为失败
	return (*result) ? 0 : -1;
}

从上面可以看出,gethostbyname()最终只会返回一个查询结果,即使该域名实际上可能会由多个IP地址。

gethostbyname_internal()

static struct hostent *
gethostbyname_internal(const char *name, int af, res_state res, struct hostent *hp, char *hbuf,
                       size_t hbuflen, int *errorp, const struct android_net_context *netcontext)
{
	//打开代理,实际上是建立一条到DnsProxyListener的socket连接
	FILE* proxy = android_open_proxy();
	if (proxy == NULL) {
    	//当从netd调用进来时,由于netd设置了ANDROID_DNS_MODE,所以会进入这个条件
		// Either we're not supposed to be using the proxy or the proxy is unavailable.
		res_setnetcontext(res, netcontext);
        //走这里
		return gethostbyname_internal_real(name, af, res, hp, hbuf, hbuflen, errorp);
	}

	//请求从哪个网卡出去,就是app_netid
	unsigned netid = __netdClientDispatch.netIdForResolv(netcontext->app_netid);

	//向netd发送gethostbyname请求
	// This is writing to system/netd/server/DnsProxyListener.cpp and changes
	// here need to be matched there.
	if (fprintf(proxy, "gethostbyname %u %s %d",
			netid,
			name == NULL ? "^" : name,
			af) < 0) {
		fclose(proxy);
		return NULL;
	}
	//如果写入出错,直接返回
	if (fputc(0, proxy) == EOF || fflush(proxy) != 0) {
		fclose(proxy);
		return NULL;
	}
	//读取来自netd的查询结果
	struct hostent* result = android_read_hostent(proxy, hp, hbuf, hbuflen, errorp);
	fclose(proxy);
	return result;
}

从上面可以看出,调用gethostbyname()时,实际上最终的DNS查询是由netd代理的。

netd处理

netd的处理都在DnsProxyListener.cpp中的GetHostByNameHandler类中,具体参考其run()方法,该函数最终会调用android_gethostbynamefornet(),但是要注意的一点是这时已经是在Netd的一个线程中运行了。

android_gethostbynamefornet()

struct hostent *
android_gethostbynamefornet(const char *name, int af, unsigned netid, unsigned mark)
{
	const struct android_net_context netcontext = make_context(netid, mark);
	return android_gethostbynamefornetcontext(name, af, &netcontext);
}

struct hostent *
android_gethostbynamefornetcontext(const char *name, int af,
	const struct android_net_context *netcontext)
{
	struct hostent *hp;
	res_state res = __res_get_state();
	if (res == NULL)
		return NULL;
	res_static rs = __res_get_static(); /* Use res_static to provide thread-safety. */
    //是否非常熟悉,调用到了同一函数
	hp = gethostbyname_internal(name, af, res, &rs->host, rs->hostbuf, sizeof(rs->hostbuf),
	                            &h_errno, netcontext);
	__res_put_state(res);
	return hp;
}

gethostbyname_internal_real()

static struct hostent *
gethostbyname_internal_real(const char *name, int af, res_state res, struct hostent *hp, char *buf,
                            size_t buflen, int *he)
{
	const char *cp;
	struct getnamaddr info;
	char hbuf[MAXHOSTNAMELEN];
	size_t size;
    //查询表
	static const ns_dtab dtab[] = {
		NS_FILES_CB(_hf_gethtbyname, NULL)
		{ NSSRC_DNS, _dns_gethtbyname, NULL },	/* force -DHESIOD */
		NS_NIS_CB(_yp_gethtbyname, NULL)
		NS_NULL_CB
	};

	_DIAGASSERT(name != NULL);

	//根据地址族确定地址大小,AF_INET为4字节,AF_INET6为16字节
	switch (af) {
	case AF_INET:
		size = NS_INADDRSZ;
		break;
	case AF_INET6:
		size = NS_IN6ADDRSZ;
		break;
	default:
		*he = NETDB_INTERNAL;
		errno = EAFNOSUPPORT;
		return NULL;
	}
    //保存结果的buff太小,直接返回NOSPACE错误
	if (buflen < size)
		goto nospc;
	//设置地址族和地址大小
	hp->h_addrtype = af;
	hp->h_length = (int)size;

	/*
	 * if there aren't any dots, it could be a user-level alias.
	 * this is also done in res_nquery() since we are not the only
	 * function that looks up host names.
	 */
	if (!strchr(name, '.') && (cp = res_hostalias(res, name,
	    hbuf, sizeof(hbuf))))
		name = cp;

	/*
	 * disallow names consisting only of digits/dots, unless
	 * they end in a dot.
	 */
    //如果是纯数字类型的主机名,那么无需继续查询,跳转到fake标签处直接构造返回值
	if (isdigit((u_char) name[0]))
		for (cp = name;; ++cp) {
			if (!*cp) {
				if (*--cp == '.')
					break;
				/*
				 * All-numeric, no dot at the end.
				 * Fake up a hostent as if we'd actually
				 * done a lookup.
				 */
				goto fake;
			}
			if (!isdigit((u_char) *cp) && *cp != '.')
				break;
		}
	if ((isxdigit((u_char) name[0]) && strchr(name, ':') != NULL) ||
	    name[0] == ':')
		for (cp = name;; ++cp) {
			if (!*cp) {
				if (*--cp == '.')
					break;
				/*
				 * All-IPv6-legal, no dot at the end.
				 * Fake up a hostent as if we'd actually
				 * done a lookup.
				 */
				goto fake;
			}
			if (!isxdigit((u_char) *cp) && *cp != ':' && *cp != '.')
				break;
		}
	//先将错误码设置为NETDB_INTERNAL,表示是一种查询网络数据库内部错误
	*he = NETDB_INTERNAL;
	info.hp = hp;
	info.buf = buf;
	info.buflen = buflen;
	info.he = he;
    //按照搜索源和查询表的对应关系进行查询,见https://blog.csdn.net/xiaoyu_750516366/article/details/82731634
	if (nsdispatch(&info, dtab, NSDB_HOSTS, "gethostbyname",
	    default_dns_files, name, strlen(name), af) != NS_SUCCESS)
		return NULL;
	*he = NETDB_SUCCESS;
	return hp;
nospc:
	*he = NETDB_INTERNAL;
	errno = ENOSPC;
	return NULL;
fake:
	//对于纯数字形式表示的hostname,实际上不需要发起真正的DNS,只需要将其转换成IP地址即可,
    //这里就是对于此种情况构造返回结果

    //地址列表中只有一个成员,别名列表为空
	HENT_ARRAY(hp->h_addr_list, 1, buf, buflen);
	HENT_ARRAY(hp->h_aliases, 0, buf, buflen);

	hp->h_aliases[0] = NULL;
	if (size > buflen)
		goto nospc;
	//调用标准的接口进行转换
	if (inet_pton(af, name, buf) <= 0) {
		*he = HOST_NOT_FOUND;
		return NULL;
	}
	hp->h_addr_list[0] = buf;
	hp->h_addr_list[1] = NULL;
	buf += size;
	buflen -= size;
	HENT_SCOPY(hp->h_name, name, buf, buflen);
    //如果需要处理IPv6,后面分析
	if (res->options & RES_USE_INET6)
		map_v4v6_hostent(hp, &buf, buf + buflen);
	*he = NETDB_SUCCESS;
	return hp;
}

查询

由于搜索源和查询表的定义分别如下:

//搜索源为文件和DNS
static const ns_src default_dns_files[] = {
	{ NSSRC_FILES, 	NS_SUCCESS },
	{ NSSRC_DNS, 	NS_SUCCESS },
	{ 0, 0 }
};

//查询表
static const ns_dtab dtab[] = {
    NS_FILES_CB(_hf_gethtbyname, NULL)
    { NSSRC_DNS, _dns_gethtbyname, NULL },	/* force -DHESIOD */
    NS_NIS_CB(_yp_gethtbyname, NULL)
    NS_NULL_CB
};

按照匹配规则,在上面调用nsdispatch()时,实际上会按照顺序分别调用_hf_gethtbyname()和_dns_gethtbyname()进行域名查询,前者是在/system/etc/hosts文件中搜索,后者会想DNS服务器查询结果,基于文件的查询我们不关注,直接看对DNS服务器进行查询的处理。

基于DNS的查询_dns_gethtbyname()

static int _dns_gethtbyname(void *rv, void *cb_data, va_list ap)
{
	querybuf *buf;
	int n, type;
	struct hostent *hp;
	const char *name;
	res_state res;
	struct getnamaddr *info = rv;

	_DIAGASSERT(rv != NULL);

	name = va_arg(ap, char *);
	/* NOSTRICT skip string len */(void)va_arg(ap, int);
    //传进来的就是地址族AF_INET或者AF_INET6
	info->hp->h_addrtype = va_arg(ap, int);

	//根据地址族确定是A查询还是4A查询
	switch (info->hp->h_addrtype) {
	case AF_INET:
		info->hp->h_length = NS_INADDRSZ;
		type = T_A;
		break;
	case AF_INET6:
		info->hp->h_length = NS_IN6ADDRSZ;
		type = T_AAAA;
		break;
	default:
		return NS_UNAVAIL;
	}
    //分配一个buf用于保存查询结果
	buf = malloc(sizeof(*buf));
	if (buf == NULL) {
		*info->he = NETDB_INTERNAL;
		return NS_NOTFOUND;
	}
	res = __res_get_state();
	if (res == NULL) {
		free(buf);
		return NS_NOTFOUND;
	}
    //调用resolver的res_nsearch()接口进行DNS查询,查询结果保存到buf->buf中
	n = res_nsearch(res, name, C_IN, type, buf->buf, (int)sizeof(buf->buf));
	if (n < 0) {
		free(buf);
		debugprintf("res_nsearch failed (%d)\n", res, n);
		__res_put_state(res);
		return NS_NOTFOUND;
	}
    //查询成功,解析响应报文,如果解析成功,则将查询结果保存到info中,对于报文解析,实际上已经没有必要
    //去仔细分析了,无非就是按照标准协议处理即可
	hp = getanswer(buf, n, name, type, res, info->hp, info->buf,
	    info->buflen, info->he);
	free(buf);
	__res_put_state(res);
    //查询失败则返回错误码
	if (hp == NULL)
		switch (*info->he) {
		case HOST_NOT_FOUND:
			return NS_NOTFOUND;
		case TRY_AGAIN:
			return NS_TRYAGAIN;
		default:
			return NS_UNAVAIL;
		}
	return NS_SUCCESS;
}

猜你喜欢

转载自blog.csdn.net/fanxiaoyu321/article/details/82750979
今日推荐