原型解读
struct hostent *gethostbyname(const char *name);
入参
字符串name可取的值分为三种类型:
- 十进制数字格式的IPv4地址
- 十六进制数字格式的IPv6地址
- 域名
返回值
返回值为指向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;
}