地址对齐问题引起的 Bug 一则

云风 2011-07-26 13:07

这两天一直在圈人一起玩google+。由于众所周之的原因,在墙内推广这个东西阻力重重。还好不需要必须翻墙,google 在北京是有网关的,修改本机 hosts 文件,把相关的域名指过去即可。在这里就不列出方法了,希望下面的评论中也不要贴出来,私下交流即可。还好移动版 G+并没有封掉,究其原因可能是 m.google.com 是所有 google 移动服务的统一入口,如果封杀影响较大。而 https 协议让墙无法分析 url ,不可能做到部分干扰。如果有一天真封禁了,手机上都无法阅读 greader ,收取 gmail ,那还真是个悲剧。

我在拉我老爸玩 G+ 的过程中,他老人家反应了一个问题,家里用手机使用 android 的 app 发不了帖子。我估摸着可能是被墙了。虽然可以让他 root 掉手机修改 hosts ,但显然这不是一个简便的解决方案。我便着手远程登陆家里的网关。那是一台用LinkStation Pro 改造的 linux debian 机器。拥有一块 Arm9 CPU ,在上面跑一个 DNS 服务,足以让家中的局域网解析到可以登陆 G+ 的 ip 地址。

我先想到的是配置 bind9 服务。这个用过一段时间,大致知道该怎么折腾。不过 google+ 用到的域名很多,配置起来非常麻烦。我想肯定有更简便的解决方案,搜索了一下没有找到,想想这种事情拜一下 twitter 大神即可。果然,5 分钟以后,各种半夜不睡觉的死程就跳出来刷拉拉列了一串解决方案。

我选了 @wuwx 同学的DNRD方案。因为它看起来最接近我一开始自己提出的需求。我需要一个类似 hosts 文件的列表,优先解析这些域名到我指定的 ip ,其它的 dns query 就转发出去好了。

apt-get 没有找到现成的包,便下载了一个编译。还好是 C 而不是 C++ 写的,让那台破机器可以几分钟编译完。但是,我配置了半天都无法正确工作。列表中有的域名可以正确解析,有的则不行。作为一个死程,当然第一反应就是 debug 了。我以为可能是列表格式有问题,但这部分 dnrd 没有给出详细的 log 。我只好自己加了几行,并在查询的地方也输出了一些 log 。但试验结果是,列表中所有的域名都加载了,但 query 时,根本没有进入匹配查询。

把 dnrd 的 debug level 开到最大 4 ,可以看到很详细的信息。仔细比较了一下可以正常工作的域名,和不能正常工作的域名,发现 query type 正常时是 1 ,也就是 'A' 类查询,而不正常时是 0 ,未知类型。class 也分别是 1 和 256 。而从 query 包的字节流看,表示 type 和 class 的地方的四个字节都是 00 01 00 01 。

我的第一反应是错位了。因为 256 正好是 0x100 。检查了一下源代码,没发现什么问题。在 src/dns.c 里发现了这么几行:

y->type = ntohs(*(unsigned short *)(&msg[i]));
i += 2;

y->class = ntohs( *(unsigned short *)(&msg[i]));
i += 2;

脑子里一下闪现出初学 C 语言时某本书上提到的一句话:某些机器上,读取宽字需要地址对齐。

也就是说,如果 &msg[i] 的地址不对齐,从中读取一个 unsigned short 很可能是不可靠的(会强制对齐)。而网络包是紧凑排列,前面排的是域名字符串,不可能刚好在那个位置保证对齐。这也解释了为什么不同长度的域名查询,有的可以正确解析,有的不行。

加了几行 log 验证了我的想法之后,我动手 fix 这个 bug 。非常简单,只需要先用 memcpy 把 &msg[i] 后 sizeof(unsigned short) 个字节复制到一个局部 unsigned short 变量中,然后再调用 ntohs 即可。事实上,drnd 早期的版本正是这样做的,memcpy 的那几行还被注释在下面,不知道为何,后来改成了现在这样错误的版本。

修改过后,一切正常了。

[返回] [原文链接]