2014-12-31

2014年年末座谈会

终:余,我们好像到早了呢,大哥二哥他们似乎还没到。
余:可茶点已经摆好了,是茉理姊姊放的吗?
终:那好,我先吃一个点心……
续:咳咳。
始:大家都到了,那么就开始吧。一年一度的竜堂家年末座谈会。
余:说是一年一度,可是感觉好像很久都没有召开过的样子了呢?
终:我来查一下……啊!上次是2004年。
续:今年是2014年。这么说来,上一次是十年之前了。作者也的确的是够懒的。
终:严正抗议!害我这么多年没有茶点吃。
始:听说作者也曾经为此深深自责过,不过最终还是脸皮战胜了羞愧。
余:我想作者也是有他自己的苦衷吧,我们还是不要太苛刻了。
终:什么苦衷,其实是懒吧!?我的茶点!抗议!
始:好了,终。天上一日,人间一年。对于我们而言,也不过就是十天而已嘛。我们还是进入正题吧。续?
续:嗯,今年发生了很多事情。不过就作者本人而言,他托我们向各位读者道个歉。因为Blog更新得实在不能算得上勤快。
终:我来数数,一、二、三……连这篇在内,2014年也就只写了15篇Blog。而且还有一篇是转载的。
续:基本上是一个月一篇的频率。所以呢,作者表示很惭愧,并且许诺会在新的年度里超过这个数字。
余:其实我看作者在今年六月份的时候还是蛮努力的。曾经有过三天内发表两篇Blog的记录呢。
终:我看看,6月1日,匪军……。大哥,“匪军”是什么?
续:匪军就是像你这样吃饭不给钱的混帐家伙。
终:我什么时候吃饭不给钱了?!
余:终哥哥,我记得好像是上次那个什么“大胃王”比赛……
终:噢,那个啊?哈哈!那是因为我最后赢了啊!赢了就不用给钱啊。胜者为王嘛。嘿嘿。
始:余,别听他的。“胜者为王”是低等动物的法则。
续:听到没?终,低等动物。
终:啊呸。
始:虽然是玩笑话,但事实也是这样的没错。看到有着悠久历史的中华民族如今堕落到与低等动物一个境界,大概连孔夫子也要哀叹吧。
余:说到孔夫子,好像也被匪军用来干坏事了。
终:咦……?
续:是那个什么“孔子学院”是吧?听说已经臭名远扬了。
始:好几个大学已经与其解除协议了。
终:等等……
始:这种做法的确很让人不齿。这种龌龊事情,恐怕即使是日本的无耻政客也做不出来。
余:听说控制这些的是一个女人?
始:没错,叫做许琳,是匪军下辖的所谓“汉办”的主任。听作者说以后打算用英文字母中正数第二个字母来称呼她。
续:……?
余:……?
始:怎么了?
余:续哥哥一定是在奇怪,终哥哥居然这次没有说“这女人看来最适合二哥您了”之类的话。
终:……为什么?说到“匪军”,连余似乎都一清二楚,而我却不知道?
余:终哥哥,你一定很久没有关注作者的Blog了。“匪军”就是指的中国共产党。
终:啊!我想起来了,就是把黄老关起来的那些人?
始:是的没错。而且被他们关进的监狱的,不只有黄老,还有许多别的人,都是出于类似的原因。
终:那末就是百分百的坏人没错了!
续:不过他们中间有许多自己人,最近也进去了。
终:真是活该!
始:因为匪军的头目最近在搞政治运动。每次政治运动都注定会有大清洗,这是历史规律。
余:而且听说我们上次去过的香港,也被匪军搞得乌烟瘴气,再也没有大不列颠统治时期的荣耀了。
终:这帮坏蛋。下次去中国内地的时候,我非好好教训他们不可。
始:终!人类的事情,我们不宜参与太多,静静看着就可以了。中国有句古话,叫“自作孽,不可活”。意思就是说多行不义必自毙。不管怎么说,人类自己的事情,要自己解决。
终:话虽这样说,可是……
始:续,下一个话题是……?
续:作者对明年的展望。
终:可恶,就这样岔开话题。
始:嗯,作者只是个小人物。所以,只要想好自己能做些什么就可以了。
终:报告大家一个好消息:作者明年又要加薪啦!
续:喂,可恶,被你抢先了。
始:呵呵,其实大家应该都已经猜到了。作者之前其实已经差不多算是透露过了。稍稍具体一点地说,明年作者可能要担任更重要的任务了。
余:就是传说中的“升职加薪”吗?
始:也不是,是“任务”不是“职务”哦,余。
续:简单地说,就是更累了。
终:这样啊?那明年座谈会是不是开不成了?如果开不成,今天的茶点我要双份。
始:那就从你压岁钱中扣,如何?金龙?
终:不好。那我还是不要了。
续:虽然明年可能会更累,但作者手下的“小弟”数量也会比今年多噢。
终:……所以?
续:所以,更新Blog比今年稍稍勤快一点,可能还是可以做到的。
余:不管怎么说,在这里要恭喜作者和他的家人了。
终:对了,作者也有一个和余一样可爱的小儿子呢。
续:目前才只有不到两岁而已。不过的确是很可爱。而且主要是没有被终欺负过。
终:我什么时候欺负过余?
续:怎么?我有说过你欺负过余吗?
余:没有。
终:你……你们……
始:哎呀,茶点已经被终吃完了,那么今天就到此为止吧。

(以上内容纯属虚构,并且与田中芳树无关)

2014-11-28

不喜欢,没有为什么

图片来自Google图片,与本文内容无关。

刚才看到Google+上有个PO说因为《三体》里面有政委,所以不喜欢。
具体内容不予评论,我只想说的是,不喜欢一个东西,只需要一个理由就足够了。你可以说出这东西有多么多么的好来,但我就一个理由,也许是很私人很难以理解的理由,但就是不喜欢,足够了。说再多也没用。

我没看过《三体》,也没有看的打算。虽然听到过很多的褒扬,我相信应该是的确有可取之处,也许是超越传统国产科幻小说的存在,但很奇怪,我就是没有兴趣。就好比不管谁跟我说红旗(牌小轿车)有多么多么好,而且就算它的确有多么多么的好,我就是不会去买它。

我是看叶永烈长大的人,当然,也看过郑渊洁。虽然我现在是个中共黑没错,但中国护照我也领,淘宝一号店京东什么的我也刷,海淘什么的我嫌麻烦还从来没去碰过。硬要说我崇洋媚外,我觉得自己也还没有那个资格。

有的人会说(嗯,我猜到他们会说):你连看都还没看过,就认为它不好看?
错了。我不是认为它不好看,我是不喜欢,没兴趣。Understand?

那到底是什么让我不喜欢?我自己也分析不太清楚,可能这涉及到很深层面的心理问题。也许是我不喜欢这个书名?也许是我觉得中国人的名字出现在西方绝对主导的作品体裁里很怪异?也许是习惯性地认为国产XX都那副尿性?也许是因为太多不应该喜欢科幻的人喜欢它所以我就觉得自己应该不喜欢它?

对了,我还不喜欢《货币战争》和《狼图腾》,也不喜欢《越狱》。这些,也都没有为什么。

2014-11-07

丢人现眼的脱口秀

上海台最近搞了一些脱口秀节目。舒悦,还有柏万青,都跑出来立在台上评论一些时事。——其实也不是最近,有一阵子了。公车上的移动电视还经常放,也经常性的让我听得想吐。前几天我又吐了一回,所以决定针对这个事情写篇Blog说一下。

对他们个人,我并没什么看法。舒悦在搞笑和扮老太太方面,真的是有一手的。柏万青呢,做调解的时候,能一眼看透矛盾的关键所在,的确厉害。这些都是让我极为佩服的本事。但是,人不可能全能。硬要捞过界,做自己原本并不擅长的事情,就得做好丢人现眼的打算。比如这两位跑去评论时事。

我觉得吧,舒悦大概是真的见识不够。一个搞传统戏曲艺术的,人生阅历又没到位,实在是讲不出啥好道理,只好拿些言语哄老阿姨开心,也是情理之中的事情。柏万青呢,本来以她的能力应该能拎得清,奈何屁股决定脑袋,老是容易把自己当政府,于是也经常讲出些让人摇头的话来。

比如他们经常拿来开涮的“上网有害论”。某某女孩被网友骗财骗色啦,某某小伙子被网友骗去做传销啦,某某X年网购上当受骗啦,某某大学生开设色情网站被抓啦……,等等等等,大约都是这之类的话题,然后一副“网上那些乱七八糟的东西”的口吻,以及拿“网上的东西好相信伐”当口头禅。反正就是“网络不是个好东西”。

其实这种事情都不需要我来吐槽。网络只不过是通信的一种方式而已。没网络以前有电话,没电话以前还有面基。网络只是让沟通联系更容易而已。会上当受骗的,倒退几百年,没网没电没石油,一样会上当受骗。跟网络搭什么界?

更何况,网络如果真能让信息来去更方便,其实反倒是会让人更不容易上当受骗才对。以前那种不开化的年代,同一个骗局可以全国各地翻来覆去随便骗,现在上Google搜一下你就能知道刚才那个骗子电话到底是怎么个玩法。在阿加莎·克里斯蒂那个年代,伪造身份冒名顶替连波洛都能差点骗过去。现在不要说冒名顶替,就是你想隐姓埋名,也分分钟给你人肉出来。不是网络上的东西不好相信,实在是有些人缺心眼儿。为什么缺心眼儿?怪爹怪妈怪政府呗!

能说出那些混帐逻辑,可见这些脱口秀的主持人实在是不合适去评论时事。脑子早就跟不上时代了,硬要去评,那不是丢人现眼么?

2014-10-31

SingleThread下遇到的并发问题

手下某个小弟,有一天报告我说他写的某个Win32 Application有一个奇怪的Bug,搞了半天搞不定,向我寻求支援。Bug现象是:下载文件,完毕弹框提示,点掉之后报错,Crash。

通常而言,这种问题,往往是因为在释放、删除什么东西的时候,该做的事情没做对,比如对着一个对象的指针进行了重复delete之类。但看了下代码,没觉得这方面有什么问题。因为这是个SingleThread的程序,于是尝试用单步跟踪跟了一下,发现有一段代码似乎在所属对象析构之后还在跑。这就有点奇怪了:SingleThread的Application,不应该有这种属于MultiThread的毛病才对。Socket模型用的是AsyncSelect,也就是说“异步”是用Windows消息做出来的,并不是真的“并发”。那么到底是哪里不对劲呢?

再接下来分析发现,虽然是SingleThread,但最后出错前弹的那个提示框,是在OnReceive的时候通过SendMessage去弹的。这样就有眉目了:ModalDialog并不阻塞ParentWindow的消息循环,所以在弹框等待用户确认的时候,消息循环收到了OnClose,于是Socket对象在用户点击确认按钮之前,其实已经被Destroy了。之前还没跑完的OnReceive,接着再跑的话,当然只能Crash了。

分析到这里,问题就已经很明白了:这就跟MultiThread下临界区没加锁一样嘛。你以为SingleThread下每个函数就都是原子操作,不会被乱入的东西打搅?呵呵,你一DoModal就会给你再嵌个消息循环进去的。可怜很多小弟连DoModel的原理都没搞懂就开始写程序了。我上次还听几个小弟在争论相关问题呢。不是说写程序必须啥都弄明白才能开始,但若是只拎半壶水就开跑,将来就难免会碰上这种“奇怪”的问题。

要修正这个问题,也很简单,改成用PostMessage让MainWindow自己去处理弹框的事情就可以了。不过有点奇怪的是,在XP下好像不会看到错误现象。Win7下直接运行EXE也不报错。只有通过两层以上的CreateProcess去调用,才会看到现象。难怪没什么用户报告这个问题。是不是OS觉得这个EXE反正会很快地Over掉,有些错误就不报算了?看来微软在私底下还是有一些没告诉大家的小动作的哈哈。

总结一下,这个案例教育我们:

  1. 不要以为只要是SingleThread就一定不会遇到并发问题。
  2. 前/后台逻辑应该要区分清晰,是后台代码就别抢前台的活儿。
  3. 还有,SendMessage/PostMessage不要不经大脑就乱用。

2014-07-31

Alipaybsm.exe是个有意思的东西

在接下来的篇幅中,我要讲一个目前还没结束的故事。故事可能还会继续发展下去,也可能因为我的懒而就此打住。但至少我觉得目前已经有足够有意思的信息可以让诸位知道了。这件事,跟支付宝有关,跟(网络)信息安全也可能有一些关系。有兴趣的朋友,可以接着看下去。


我以前曾写过一个服务器Ping值测试程序(参见这里《写了个批量测试服务器Ping值的小工具》)。这个程序一直都能满足我的需要,直到有一天在我老婆的笔记本Win7 x64系统上遇到了问题:对几乎所有的IP,我这个程序的Ping都很快收到了回应,快得不正常,几乎就像做了个本地调用一样,与实际情况不相符。于是我打算看看这是怎么回事情。

当时我人在公司,VC6远程调试又不方便。最后靠着DbgView终于搞清楚了:接收到的数据中,多出来了一份不正常的东西。我之前的代码,并没有估计到这份不正常的数据可能会出现,所以处理上出了些问题。

OK,这算是我的Bug。可这“不正常的数据”到底是什么东西?我把它Dump出来一看,还真是有点奇怪!ICMP Type是8,源地址和目的地址则与预期的Echo回应包刚好相反。算上sendto时候系统自己加上的IP包的包头,跟我送去发送缓冲区里的数据那是一模一样。

要解决我程序里的这个问题非常简单。但是另一个问题就不那么好回答了:为什么其它电脑上不会这样,偏偏这台电脑会出现这种奇怪的事情?
直接答案很简单——它一定跟别的电脑有什么地方不一样!
那么还有第二个问题:到底是什么地方不一样呢?

可以说是我的幸运,也可以说是阿里集团的不幸。因为我的Taskmgr里面进程列表设置为按ASCII字母序排升序的缘故,我很快就找到了这第二个问题的答案:Alipaybsm.exe。杀掉Alipaybsm.exe这个进程,前面提到的那份“不正常的数据”就不再出现。而这个Alipaybsm.exe似乎由AlipaySecSvc.exe在守护,过了一会儿就又自己启动起来了。它一出现在进程列表中,我一试,哈,那个奇怪的现象就又出现了。

后来,我把这事情在Twitter上说了一下,还引发了一场小小的讨论。
我目前还没完全想明白Alipaybsm.exe这样做的目的是什么。初步感觉,有可能是跟背地里监控网络流量有关。毕竟,目的地址不正确的数据,就算被放入Socket接收缓冲里面,在网络层与传输层之间估计也被滤掉了。我这次是因为用了SOCK_RAW,需要自己下到网络(IP)层来处理数据,才碰巧发现了这个情况。如果只是在传输层(TCP/UDP)从事工作,估计不会有任何察觉。
只不过,反过来讲,如果能做到复制数据到Socket接收缓冲,那应该完全可以做到监控流量而不带任何痕迹才对。所以我目前还只能理解为,Alipaybsm.exe想完全监控网络流量,所以利用了这个手段(复制发送的数据到接收缓冲中),但干这事屁股没擦干净(也可能没法擦干净),才产生了我遇到的这些情况。

我本来以为当时那个Alipaybsm.exe是个假货。但看EXE的详细信息,以及绑定的数字证书,都像是支付宝官方的真货。我又以为那只是一个不成熟的版本,可能有Bug,但我前两天为了转一笔账,又去下载并安装了一个支付宝安全控件,然后它又出现了,带着它那奇怪的行为又出现了。
所以,我们来仔细看看这货吧:

看上去挺正常吧?

在Twitter上讨论的时候,有人表示,在Mac上用防火墙没观察到有这个现象。为此,我今天特意去确认了一下:在Windows上抓包,也观察不到这个现象。我估计,只有自己写基于SOCK_RAW的程序,才能收到这些数据。为了检查这种特殊的行为,我专门写了个小程序AlipaybsmTester,基本上就是一个单地址单次单线程的PingTester。

从这幅截图中可以看到,Microsoft Network Monitor只抓到了一来一回共两个包,但我的测试程序发了一个包收到了两个,内容各不相同。如果杀掉Alipaybsm.exe,那就只会收到后一个包了。

接下来再看看这个Alipaybsm.exe的一些更好玩的事情:
很奇怪的是,它其实并不是随着“支付宝安全控件”(Aliedit.exe)装上去的。当你登录支付宝,根据Web页面上的提示安装了“支付宝安全控件”时,只会在Program Files (x86)\alipay下面建一个名字叫alieditplus的目录。

但是过一会儿(我这次过了30分钟左右),在alieditplus下面会出现一个update目录,并下载一个SafeTransaction_Setup.exe放在其“\job\file\tmp\zip_1009_”子目录中(不同时期不同环境中路径可能会有所不同)。随后Program Files (x86)\alipay\SafeTransaction目录便出现,里面就有Alipaybsm.exe(当然还有一些别的)。

我在网上想搜一下关于这个Alipaybsm.exe或SafeTransaction_Setup.exe的相关信息,发现少得可怜。Alipay官方完全没有提到过这些东西,好像它们是感染了AIDS的私生子一样。不过每个安装了支付宝安全控件的电脑上,估计都会有这些个东西(还有个AlipayDHC也值得注意)。我认为以这种方式进行推广的程序,很可能另有其目的,不见得真的是保障个浏览器安全这么简单。如果真是为了保障浏览器安全,完全可以公开(乃至大张旗鼓地)宣传,然后打包到安装包里一起分发下去正大光明地安装,不是吗?

PS: 我后来发现,杀掉AlipaySecSvc.exe也会导致复制数据包的现象中断,并且重启该服务之后,恢复现象花的时间比单单杀掉Alipaybsm.exe要长。可见Alipaybsm.exe的角色大概只是一个行动的发起者和结果的分析者,具体对流量实施监控的行为,很可能是它去调用AlipaySecSvc.exe中的某些个服务来完成的。这说明对于“支付宝安全控件”本身也不能掉以轻心。相关功能其实可能一直就放在AlipaySecSvc.exe中,只是没有人来扣扳机而已。而这个扣扳机的可以是Alipaybsm.exe,也可以是别的谁,那谁谁谁。

2014-05-07

BCB5在Win7 x64上启动时报错“1 transfer item(s) contain syntax errors”

由于WinXP已经被微软官方宣告服务终止,最近把工作环境升级到了Win7,并且安装的是x64版本。装了之后发现,BCB5启动的时候会弹出一个报错对话框,里面的信息很奇怪:
1 transfer item(s) contain syntax errors
点“确定”关闭对话框之后,BCB5使用起来也没有什么问题。但每次启动都会弹框,很讨厌。那么,这是什么情况呢?

一般来讲,Win7与WinXP之间,出现类似兼容性问题的原因大致有:

  • 管理员权限问题
  • 注册表键值问题
  • 系统目录问题
  • DEP问题

在x64系统上,目录问题尤其突出。Program Files现在还有个Program Files (x86)。System32那边也有个SysWOW64。后者一般跟应用软件关系还不太大,但前者常常会导致很多问题。我就见过有的软件安装包都会运行出现问题。

这次的情况其实也类似。照例,先上国外网站的链接。
http://codeverge.com/embarcadero.cppbuilder.install/at-start-up-1-transfer-item-s/1096695
最后那个回复,把操作步骤写得很详尽。做C/C++开发的,英语阅读一般还是不会有问题,我就不翻译了。

总之呢,这个问题就是因为Program Files (x86)直接引起的。另一个回复里面说把BCB5卸载后重新安装在Program Files下也能解决。当然,有问题的地方其实只有一处,所以完全没必要如此大动干戈。

2014-03-20

为什么不用动态内存分配?

在写这篇Blog的时候,我考虑了几分钟,在想要不要把标题写成《为什么有的程序员喜欢用动态内存分配?》。最后我还是把那些修饰词和定语给删了。虽然那个标题更准确一点,但是本文基本上是一篇吐槽文,我还是比较喜欢这种反问句的感觉。

事情是这样开始的:
在工作中,遇到了别的同事以前写的一段代码。作用是显示从某些网上下载的文件的内容。文件下载完后,也在本地保存了一份副本,这样如果下次发现本地有副本,就直接显示不用下载了。
这基本上是一个类似浏览器缓存的功能,实现起来也不难。不过这次我碰到一个Bug,有个文件的副本,在解析的时候报错了。
因为第一次下载的时候并没有报错,所以焦点就集中到这个缓存机制上。这里面有个值得关注的地方在于,大概是出于节省本地硬盘空间的考虑,本地的副本在保存时是压缩过的。于是问题可能出在两个地方:

  • 压缩算法有问题,压缩保存的时候,把文件给弄坏了。
  • 解压缩算法有问题,无法正确还原这个文件。

这套压缩/解压缩的算法,是开源的(zlib)。所以我认为问题不应该出在算法本身,更可能是用法没用对。调用代码大概是这个样子的:
#define chunk 16384
void compress_file(const char* source_file , const char* dest_file)
{
    unsigned char datein[chunk];
    unsigned char dateout[chunk];
    unsigned long datelong = chunk;
    unsigned long sourcelong;
    FILE* source;
    FILE* dest;
    source = fopen(source_file , "r");
    dest = fopen(dest_file, "w+b");
    while (!feof(source))
    {
        sourcelong = fread(datein, 1, chunk, source);
        compress(dateout, &datelong, datein, sourcelong, 1);
        fwrite(dateout, datelong, 1, dest);
    }
    fclose(source);
    fclose(dest);
}
void un_compress_file(const char* source_file , const char* dest_file)
{
    unsigned char datein[chunk];
    unsigned char dateout[chunk];
    unsigned long datelong = chunk;
    unsigned long sourcelong;
    FILE* source;
    FILE* dest;
    source = fopen(source_file , "r+b");
    dest = fopen(dest_file , "w");
    while (!feof(source))
    {
        sourcelong = fread(datein, 1, chun, source);
        datelong = chunk;
        if (uncompress(dateout, &datelong, datein, sourcelong))
        {
            fwrite(dateout, datelong, 1, dest );
        }
    }
    fclose(source);
    fclose(dest);
}
这段代码我也不打算在这里分析太多,问题很明显:代码编写的初衷,是想把文件分块处理。但每块数据压缩之后的大小并没有记录在压缩文件中,也没有采取一些诸如分隔符或区块补齐之类的定位措施,所以解压缩的时候实际上是无法忠实地按压缩时的分块来还原数据的。而出问题的那个文件,大小的确是超过了16384,于是就被弄坏了。

这里就引出了一个问题:为什么要分块?
事实上,如果这段代码没有采用固定长度的C-style数组,而是用动态内存分配的解决方案,压根都不会需要分块,也就不会出现这个Bug。当然,这只是解决这个Bug的方案之一。对分块压缩算法的理解有问题,也是造成这个Bug的原因之一。从这方面着手进行改进也是可以的,各有利弊而已。
但这不是我要表达的重点。在这个案例里,下载的文件并不会很大,几十KB就顶天了。我真正疑惑的地方在于:为什么不用动态内存分配?
可能的解释有:
  • 担心内存碎片问题
  • 担心忘记释放
  • 嫌动态分配内存麻烦
  • 习惯了这种固定长度缓冲区的写法
  • ……
也许还有别的原因,一时半会儿我是想不到了。

那么换个问题:什么时候该用动态内存分配?
这个答案会比较明确一点:
  • 空间大小不确定(运行期确定)
  • 栈上空间不够
  • 方便与线程外部传递/分享数据

在本文的这个例子中,文件的长度是不确定的,每块数据压缩后的长度也是不确定的。很明显,这就是属于应该用上动态内存分配的时候。
该用的时候不用,带来的恶果就是程序的可读性和可维护性就会变得差,出Bug的机会更高。毕竟固定长度的内存区域就一定要处理溢出问题。而且用固定长度去处理变长内容,要分块/分次,要做循环,要留意退出条件,测试时要覆盖1和N……,这些都带来了不必要的开销。
还不如直接分配一块内存出来,只要到时候记得回收就OK。性能方面值得担心的话,也可以自己优化内存管理,这是可以集中处理掉的事情。而那种用固定长度的栈缓冲区来解决此类问题的办法,好听一点叫做“质朴”,难听一点叫“土”。总不能每个需要动态内存分配的地方,都用这种土办法来应对吧。

我其实是觉得,有些程序员,会有意识(或下意识)地避免用动态内存分配。从写代码的时候就开始重视性能,是好事情,但写程序不能只看功能和性能。你写的程序,好不好懂,容不容易出问题,有没有定时炸弹,好不好改,方不方便扩展,这些也都是很重要的。性能不佳可以优化,这种代码级的性能问题(相比架构级而言)优化起来尤其容易。但其它的方面,要改善起来绝非一日之功。
往开了说,作为程序员,应该避免陷入“某个东西就是不好”的思维方式中。思维开始变得狭隘,是自身没法继续再提高(达到上限了)的标志之一。

2014-01-22

2014年1月全国性DNS劫持事件评析

许久没更新博客了。这次全国性事件既然这么轰动,震惊互联网界及翻墙界,那我就借此机会说上两句。

事件是15:20开始的,而我当时15:30刚好有个会,所以这个事件只经历了一点开头:
当时我正在整理自己的收藏夹,进行到windbg和ollydbg的时候,发现需要翻墙才能访问了。刚开始我在疑惑为啥GFW会对调试工具下手,难道愚民政策已经扩展到技术界了?随后我发现大部分时间这两个网站的域名被解析到了 65.49.2.178 这个IP上。少部分时间的尝试是正确的,但这个概率小得不足以完成大部分文件的载入。由于我用的DNS一直都是四个8,所以第一个反应就是这两个站点被DNS污染了。
既然是污染,那么应该能抓到正确的DNS回应包,只是慢点而已。但这次我发现DNS回应包是一对一的,没有多余的包回来。只是第一次解析的时候往往能解析到正确的地址上,后面再解析,回复的就是65.49.2.178了。当时我还没试别的网站。
然后开会的时间到了,我就走了。再回来的时候,故障已经基本结束了,没什么时间和机会去分析。技术方面的分析,可以参考这个。我认为分析得靠谱,符合此次事件的各种特征。我在这里只补充一些文中没有提到的部分。

首先,这不是“DNS污染”,是“DNS劫持”。
我这里不是在抠字眼,或者讨论这两个词的定义问题。我只是指出这个事实:这次GFW对于DNS的攻击方式,跟以往(或者说一直以来)的DNS污染有所不同。
一般,GFW的做法是抢在DNS服务器的正常应答之前,伪造一个应答,欺骗客户端。正常的应答仍旧会返回到客户端,只不过GFW的欺骗包会很快,相当快,使得客户端不理睬正常的回应包。
但这次不同,从抓包的结果看来,“一问一答”,并没有多余的回应包。即使DNS是境外的8.8.8.8,也是如此。境内的DNS,尚有别的办法可以进行控制。境外的DNS,必须是在DNS查询/回应包的转发路径上对其进行劫持/丢弃,才能实现这种效果。

其次,探索一下GFW这次这个DNS劫持功能的工作模式。
通常而言,要进行DNS劫持,GFW可以有两种基本做法:
  • 劫持DNS查询包:截获DNS查询包,不把它向目的DNS进行转发,然后自己伪造一个DNS回应包给客户端发去。
  • 丢弃DNS回应包:截获DNS回应包,伪造一个DNS回应包发给客户端,然后把正确的回应包丢弃,使其不能正常到达客户端。
实际上的情况,可能比这要更复杂一点。比如,丢包的事情,应该是GFW的一个状态防火墙完成的。而防火墙的规则添加可能需要一点时间,所以第一次查询时有可能会有正确的回应包漏过。
另外,根据每次都是“一问一答”看来,伪造的DNS回应包可能是防火墙规则自动触发。也就是说,DNS回应包被拦截后,才会伪造一个发给客户端。如果没被拦截,就不会伪造。否则无法解释第一次查询时正确的回应包漏过之时为什么也是“一问一答”。
综合上述情况,并结合GFW的部署和需求特点,这次的DNS劫持功能应该是采用的第二种工作模式,也就是说对DNS回应包进行处理。很显然,对于GFW而言:从“非受控区域”过来的数据,才是真正需要控制的“有害数据”。从“受控区域”出去的数据,就算会被判定为“有害”,也没必要进行处理,守株待兔就可以,说不定对方根本就不存在呢。

顺便,对GFW的一些技术细节,从这次事件可以有更多的认识。
要完成DNS劫持,有一个前提:GFW可以控制DNS包转发路径中的(至少)某个路由器,或者GFW自身(的一部分)就是DNS包转发路径中串进去的一环。相比之下,如果要做到以前的DNS污染,只需要在交换机上旁路接入一台设备,不需要串在路由路径中。
具体而言,从这个DNS劫持的功能看来,应该会有三个部件参与:
  • 识别模块:这个可以由一个IDS性质的设备来完成。对来自“非受控区域”的DNS回应包进行检测。这个完全可以在旁路慢慢做,不影响网络出口的性能。
  • 过滤模块:当识别模块检测到“有害信息”后,会向一个状态防火墙添加一条动态规则。生效不一定很快,但因为是全国性质的,漏也漏不了多少。这条动态规则包括丢弃符合条件的DNS回应包,以及驱动欺骗模块去伪造一个DNS回应包。这个设备必须串入骨干路由,应该是部署在互联网出口处,基本上必须是一个群集。
  • 欺骗模块:伪造的工作是跟过滤模块联动的,即:丢弃-伪造。这个模块以前应该就存在,部署在旁路上就可以。

回头想想,这些都并不是什么前沿的技术。花不了多少时间我自己都能写出来。目前阻碍这个功能(DNS劫持)大规模应用的因素,可能主要还是来自性能方面的压力。否则现在对那些敏感域名仍然在大量采用的低效的DNS污染早就该换成劫持了。在性能方面而言,用旁路模式当然会好很多。有关人等大概也知道,在DNS上无论怎么折腾,也都是锁君子锁不住小人,索性不把宝贵的性能浪费在这里了。