这篇笔记分析了库函数getaddrinfo()的代码实现。
int getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res);
关于该函数的更详细介绍可以参考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);
}
这里有必要对这两个函数做个说明:
该函数的作用主要是对输入参数的合法性进行检查,以及处理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;
}
看到该函数的代码,一定会非常熟悉,在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;
}
我们不关注基于文件的查询_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()中的逻辑非常类似,感兴趣的可以自行分析。
#define MATCH(x, y, w) \
((x) == (y) || (/*CONSTCOND*/(w) && ((x) == ANY || (y) == ANY)))
作用是检查x和y是否匹配,返回布尔值,匹配分两种情况: