在测试我们开发的一个 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 请求时传入的域名进行大小写随机转换,代码如下:
if (base->global_randomize_case) {
unsigned i;
char randbits[(sizeof(namebuf)+7)/8];
strlcpy(namebuf, name, sizeof(namebuf));
evutil_secure_rng_get_bytes(randbits, (name_len+7)/8);
for (i = 0; i < name_len; ++i) {
if (EVUTIL_ISALPHA_(namebuf[i])) {
if ((randbits[i >> 3] & (1<<(i & 7))))
namebuf[i] |= 0x20;
else
namebuf[i] &= ~0x20;
}
}
name = namebuf;
}
然后在处理 DNS 服务器返回的结果时,从 DNS 请求列表中根据 trans_id 找到对应的 request ,拿 DNS 结果中解析出来的名字和 request 中的名字比较,如果不一致,就认为出错了。详情参考reply_parse() 函数,其中 TESTNAME 宏实现名字比对,原始代码如下:
#define TEST_NAME \
do { tmp_name[0] = '\0'; \
cmp_name[0] = '\0'; \
k = j; \
if (name_parse(packet, length, &j, tmp_name, \
sizeof(tmp_name))<0) \
goto err; \
if (name_parse(req->request, req->request_len, &k, \
cmp_name, sizeof(cmp_name))<0) \
goto err; \
if (base->global_randomize_case) { \
if (strcmp(tmp_name, cmp_name) == 0) \
name_matches = 1; \
} else { \
if (evutil_ascii_strcasecmp(tmp_name, cmp_name) == 0) \
name_matches = 1; \
} \
} while (0)
这段代码是有问题的,global_randomize_case 标记和字符串比较函数没有匹配上,颠倒了。所以比对就出了问题,有时候正确,有时候不正确。修改成下面的代码就好了:
#define TEST_NAME \
do { tmp_name[0] = '\0'; \
cmp_name[0] = '\0'; \
k = j; \
if (name_parse(packet, length, &j, tmp_name, \
sizeof(tmp_name))<0) \
goto err; \
if (name_parse(req->request, req->request_len, &k, \
cmp_name, sizeof(cmp_name))<0) \
goto err; \
if (base->global_randomize_case) { \
if (evutil_ascii_strcasecmp(tmp_name, cmp_name) == 0) \
name_matches = 1; \
} else { \
if (strcmp(tmp_name, cmp_name) == 0) \
name_matches = 1; \
} \
} while (0)
这个问题我花费了4个多小时来跟,最后总算解决了,心终于放下了(之前出错重试的修补方式总让人不踏实)。
分享到:
相关推荐
libevent源码深度解析
libevent 在android上交叉编译脚本,支持各种系统架构
libevent源码解析
Libevent源码解析.pdf
libevent for android 自己调试通过
包含源码和编译结果。 如果要使用openssl 参考:http://download.csdn.net/detail/sos995/6397529
libevent-2.1.8-stable-android libevent_core.a libevent_extra.a
c++版本libevent,仿照libevent写的一个服务器框架,libevent的基本功能已实现,暂时不能在windows平台上使用,定时器是纯粹的timer wheel方式,与libevent的小根堆不一样,而且最大定时时间是固定的,暂时不支持...
Ubuntu下用NDK交叉编译的libevent库文件,对应的platforms使用android-14,gcc是采用arm-linux-androideabi-4.7
速度:libevent 尝试使用每个平台上最高速的非阻塞 IO 实现,并且不引入太多的额外开 销。 可扩展性:libevent 被设计为程序即使需要上万个活动套接字的时候也可以良好工作。 方便:无论何时,最自然的使用 ...
libevent-2.0.22-stable.tar.gz源码编译的Windows和Linux下的静态库,另附源码,电子书《libevent参考手册(中文版).pdf》、《libevent源码深度剖析.pdf》、《LibeventBook.pdf》。 Linux环境下该libevent静态库修改...
基于libevent的一个服务端和客户端,希望给新手学习参考一下
libevent是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机制。著名的用于apache的php缓存库memcached据说也是libevent based,而且libevent在使用...
http://blog.csdn.net/flyingleo1981/article/details/28400859 这个是文章,可以参考,有图有真相
libevent参考手册(中文版),包含libevent的设计说明、原理描述,模块介绍和接口说明。
libevent是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机制。著名分布式缓存软件memcached也是libevent based,而且libevent在使用上可以做到跨...
里面有教你如何在VS上配置libevent库,并配好后有个小例子可以感受下哦。
libevent-1.0c.tar.gz [GPG Sig] - Release 2005-04-03 Bug fixes for Windows, Solaris and improved logging interface. libevent-1.0b.tar.gz [GPG Sig] - Release 2005-01-13 Bug fixes for backwards ...
深入学习linevent