« 谈谈 Google+ | 返回首页 | Lua 下实现抢占式多线程 »

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

这两天一直在圈人一起玩 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 的那几行还被注释在下面,不知道为何,后来改成了现在这样错误的版本。

修改过后,一切正常了。

Comments

真牛逼,我也要试试你这样翻墙
以前不知道dnrd
呵呵,翻墙也有被墙的时候
bind9 配置... 需要自己写 zone file. 好麻烦... 当时我也想做这个来着, 打算用v2ex dns 做 backend dns server, 但是怎么都失败, 后来改成8.8.8.8 莫名其妙地好了...... 之后就失去信心去折腾 bind9 了.
在x86下也是可以开启内存访问强制对齐检查的。若访问未对齐,也会出现您所说的情况。
知道了,是云风说的“某些机器上,读取宽字需要地址对齐”的原因把,我真圡。。。
为什么在一个char数组中试验的时候,随便从哪个位置开始读取short都可以读出来呢?
菜鸟问个问题:“如果 &msg[i] 的地址不对齐,从中读取一个 unsigned short ”,&msg[i]是一个字节内容的地址,把它转成short*不是只读取了一个字节的内容吗?怎么读取两个字节的内容啊?谢谢
用ARM 6以下捣鼓老代码那叫一个悲催……
刚刚才知道云风前辈的大名,慕名前来。^_^ 以后多来这里转转。
楼主解析的不错 用的是dnsmasq,可以达到相同的效果。而且dnsmasq可以用apt-get安装。值得一试。我试过了 我咋还那样。。。。
这牛
同问: 云风,你领导的3D引擎及基于它开发的游戏怎样了?是不是挂掉了?貌似做了好几年了,都没点消息
求北京的google网关啊,一直上不了google+,唉,上一会就被墙
是的,不同处理器对自己对齐方式处理不一样,记得sun sparc芯片这种情况会core dump的,x86的不会。
云风,你领导的3D引擎及基于它开发的游戏怎样了?是不是挂掉了?貌似做了好几年了,都没点消息。
ARMv6开始硬件支持不对齐访问(需要修改标志寄存器使能),之前的架构要求对齐,否则结果不可靠。修正方法: 1、改程序。遇到不对齐情况memcpy到对齐buffer处理,但只能解决部分问题,例如某个消息前面对齐但后面不对齐; 2、改消息属性。例如gcc支持__attribute__((packed))属性,将一个指针强制转换成拥有此属性的struct再访问,参看linux内核的get_unalign()宏,同时arm cc也支持类似属性; 3、改内核参数。例如arm linux下echo 3>/proc/cpu/alignment让内核trap&fix不对齐访问异常(bit1),并输出警告(bit0),3=bit1|bit0。
按软件工程这是需要加注释解释一下的,也许搞开源的大牛认为这是常识吧:)
ARM还真是有这个问题。关键是有的系统会给异常,有的不会。不同系统还不一样。
果然不直观的操作还是要把why加到注释里面啊。
牛人!改完给作者发个邮件吧。开源软件也不能保证参与者各个都是身经百战。很可能是后来的程序员经验不足(有多种cpu上编程经历的人很少吧),看到平白无故浪费一次复制,就给优化了,哈哈。
有些arm芯片确实有这样的问题
这叫地址对齐吗?ntohs不是在转换字节顺序吗? 为了验证我是人类?那怎么验证您是人类?
同样用dnsmasq。 估计修改者使用x86系列的cpu做的编译验证,没在arm上测试过。作者和维护者不是同一个人的情况下,常会出现的事情。 不过你还记得宽字节对齐的事情,很不容易啊。我得拿着汇编看,也未必能看出问题来。
请问代号 T3是你做的游戏么?
为啥我以前就没发现dnrd这个好东西啊
前两天刚干过一样的事情。用的是dnsmasq,可以达到相同的效果。而且dnsmasq可以用apt-get安装。值得一试。
真牛逼啊!环环相扣,逻辑严密,判断准确!

Post a comment

非这个主题相关的留言请到:留言本