android上libevent dns解析的一个bug修复

在测试我们开发的一个 APK(使用了 libevent-2.1.3-alpha 作为网络库) 时发现一个奇怪的问题,域名解析有时报错 Non-recoverable name resolution failure 。在公司偶尔报错,后来程序改动了一下,出错时重试几次,问题没再出现,以为好了。昨天换了个网络环境,结果报错几率变得非常大。

    互联网搜索到这个错误的一个处理办法,说在使用 getnameinfo() 函数时需要显式指定其第二个参数 salen 为 sizeof(struct sockaddr_in) 或者 sizeof(struct sockaddr_in6) ,说是 Solaris 和 Android 上的 getnameinfo() 实现不会查看 saddr 中的 sin_family 来计算出真正 salen 。我尝试了一下,没有解决问题,后来想想, libevent 根本就没有使用系统的域名解析函数,完全是自己实现的,于是只好自己跟代码了。

    由于远程调试的环境没有搭建起来,只能不断地添加日志,反复查看,非常耗时。最后还真给我找到了问题所在。

    libevent 的 dns 解析实现就在 evdns.c 这个文件中,不过如果不懂得 DNS 协议,代码看起来可能比较难懂,我重温了 DNS 协议,然后开始跟代码。

    libevent 在处理 DNS 解析时,针对域名引入了一个随机大小写的概念,在 evdns_base_new() 中把 global_randomize_case 默认设置为 1 ,然后在读取域名服务器配置文件时根据里面的 options 来修改。安卓上没有 resolv.conf ,这些选项就没有修正的机会,于是最终 global_randomize_case 还是 1。

    在 libevent 构造 DNS 请求( request_new() 函数)时,会根据 global_randomize_case 来决定是否对发起 dns 请求时传入的域名进行大小写随机转换,代码如下:

  1. if (base->global_randomize_case) {  
  2.     unsigned i;  
  3.     char randbits[(sizeof(namebuf)+7)/8];  
  4.     strlcpy(namebuf, name, sizeof(namebuf));  
  5.     evutil_secure_rng_get_bytes(randbits, (name_len+7)/8);  
  6.     for (i = 0; i < name_len; ++i) {  
  7.         if (EVUTIL_ISALPHA_(namebuf[i])) {  
  8.             if ((randbits[i >> 3] & (1<<(i & 7))))  
  9.                 namebuf[i] |= 0x20;  
  10.             else  
  11.                 namebuf[i] &= ~0x20;  
  12.         }  
  13.     }  
  14.     name = namebuf;  
  15. }  

 

    然后在处理 DNS 服务器返回的结果时,从 DNS 请求列表中根据 trans_id 找到对应的 request ,拿 DNS 结果中解析出来的名字和 request 中的名字比较,如果不一致,就认为出错了。详情参考 reply_parse() 函数,其中 TESTNAME 宏实现名字比对,原始代码如下:

  1. #define TEST_NAME    \  
  2.  do { tmp_name[0] = ‘\0‘;    \  
  3.   cmp_name[0] = ‘\0‘;    \  
  4.   k = j;     \  
  5.   if (name_parse(packet, length, &j, tmp_name,   \  
  6.    sizeof(tmp_name))<0)   \  
  7.    goto err;     \  
  8.   if (name_parse(req->request, req->request_len, &k,  \  
  9.    cmp_name, sizeof(cmp_name))<0)     \  
  10.    goto err;     \  
  11.   if (base->global_randomize_case) {  \  
  12.    if (strcmp(tmp_name, cmp_name) == 0)  \  
  13.     name_matches = 1;    \  
  14.   } else {   \  
  15.    if (evutil_ascii_strcasecmp(tmp_name, cmp_name) == 0) \  
  16.     name_matches = 1;    \  
  17.   }  \  
  18.  } while (0)  


    这段代码是有问题的,global_randomize_case 标记和字符串比较函数没有匹配上,颠倒了。所以比对就出了问题,有时候正确,有时候不正确。修改成下面的代码就好了:

  1. #define TEST_NAME                           \  
  2.     do { tmp_name[0] = ‘\0‘;                    \  
  3.         cmp_name[0] = ‘\0‘;                 \  
  4.         k = j;                          \  
  5.         if (name_parse(packet, length, &j, tmp_name,        \  
  6.             sizeof(tmp_name))<0)             \  
  7.             goto err;                   \  
  8.         if (name_parse(req->request, req->request_len, &k,    \  
  9.             cmp_name, sizeof(cmp_name))<0)           \  
  10.             goto err;                   \  
  11.         if (base->global_randomize_case) {           \  
  12.             if (evutil_ascii_strcasecmp(tmp_name, cmp_name) == 0)       \  
  13.                 name_matches = 1;           \  
  14.         } else {                        \  
  15.             if (strcmp(tmp_name, cmp_name) == 0) \  
  16.                 name_matches = 1;           \  
  17.         }                           \  
  18.     } while (0) 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。