这篇笔记分析了库函数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_UNSETreturn 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 INET6case PF_INET6:#endifbreak;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_INET6pai->ai_family = PF_INET6;#elsepai->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为0if (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的真实值是否相等来判断是否匹配。

更多相关文章

  1. Android使用Retrofit进行网络请求
  2. Android(安卓)命令行编译、打包生成apk文件
  3. Android(安卓)Paging组件Demo
  4. 【安卓笔记】android客户端与服务端交互的三种方式
  5. android 命令(adb shell)进入指定模拟器或设备
  6. Android(安卓)startActivityForResult的使用
  7. Android(安卓)中数据库查询方法 query() 中的 select
  8. 13-7-13如何修改android的title
  9. android 单选框

随机推荐

  1. android开发 使用uses-sdk 导致布局不一
  2. Android(安卓)UI学习 - Tab的学习和使用
  3. 让Android(安卓)adb运行在ARM平台上
  4. SQL Server 2000“设备激活错误”的解决
  5. SQLServer按顺序执行多个脚本的方法(sqlcm
  6. SQLServer 2008 CDC功能实现数据变更捕获
  7. IP连接SQL SERVER失败(配置为字符串失败)
  8. SQLSERVER数据库升级脚本图文步骤
  9. 使用row_number()实现分页实例
  10. SQL Server无法生成FRunCM线程的解决方法