有关 SVN、Cygwin 和 Notepad++

1. svn 的访问控制

很久以前我就自己配置过 svn 服务器,但总是不能访问成功。到最后还是使用文件系统(即用 file:/// 而不是 svn:// 或其它)访问 svn 仓库,因为自己建立的文件系统不需要认证。

今天我又尝试琢磨了一下我 svn 仓库的设置,才发现之前没配置成功的原因:svn 对用户的权限默认是关闭的。因此当我设置了用户名密码,svn ls 时得到的提示信息仍然是:

svn: Authorization failed

时,我就糊涂了,我的用户名密码没错呀,为啥还是Authorization failed?我还以为是密码设置有问题,没想到除了用户名以外,还得给用户配置访问列表(ACL),否则就什么都访问不了。说简单点儿就是 svn 用户访问控制是一个白名单机制,而我当成了黑名单机制。

知道了错误原因,就很简单了。到与 svnserve.conf 同目录下的 authz 为对应用户添加可以访问的项目就可以了。

2. cygwin 的启动速度

最近发现 cygwin 的启动速度大大变慢,一个终端起来至少要 30 秒。而且不仅仅是启动,所有程序的运行速度都变慢了,比如文件名补全竟然需要好几秒!我忍了很久,就差卸了重装它了,只是想到好不容易配好的各种环境,给忍耐了下来。

今天琢磨了一下 cygwin 的启动过程,发现可以在 bash 命令后加 -x 参数打印所有执行的命令。于是把启动 log 打印出来,查找到引起运行变慢的罪魁祸首:bash_completion。我之前装了一个名叫 bash_completion 的包所谓命令补全的增强包,好家伙,在 /etc/bash_completion.d/ 下面添加了 144 个文件。在启动的时候要一个个 source 这些脚本,怪不得慢呢!

卸载掉这个 bash_completion 包后,cygwin 的运行速度回到了原来的水平,敲命令的时候总算不用憋屈地等补全了。

3. Notepad++ 的中文搜索

使用 2.6.8 版本时,又发现无法搜索中文的 bug。我非常搞不懂 Notepad++ 的作者怎么维护软件质量的,这 bug 在我的印象里就反覆出现两次了。这样的bug都不写一个回归测试用例来检查,实在是有点儿不可思议。无奈之下只好退回到 2.6.7 版本了。

我喜欢的黑莓应用

上次手机丢了之后,为图便宜,我换了一个黑莓 8700,黑莓大概是我能负担起的最便宜的智能手机系列。虽然和原来的手机相比,8700 不带照相和扩展卡功能,但各种应用让我觉得比传统手机好玩多了。自从开始玩黑莓的这些有趣应用后,我渐渐觉得以后什么手机应用多,才能占到市场先机。以 Google Android 的开放平台以后肯定有不错的表现。

折腾了一段时间后,下面是一些我喜欢的黑莓应用:

1. Opera Mini 浏览器

如果没有网络,智能手机至少会少了一半的乐趣,现在很多网站都支持手机浏览了,包括我的这个博客,所以一个好用的网络浏览器非常重要。其实 Opera Mini 算不上很好用,但至少比黑莓自带浏览器好用多了,主要是在网页格式化方面。我比较看好的浏览器是 UCWEB,因为它支持标签功能,在 GPRS/EDGE 的低速网络下可以后台打开标签以节省时间。UCWEB 黑莓版已经在内测,希望能尽快推出。

2. Google Maps

通过黑莓上的 Google Maps 我才第一次知道原来没有 GPS 也可以定位。Google Maps 使用基站定位的标称误差大约在 600 米左右,实际误差我觉得大约在三四百米内。虽然误差比较大,但是在北京城里晃悠时,这个范围的误差足够能判断行进方向是否正确了,可以少问很多路。另外还有路线查询功能,有点儿烂,但好歹能用。

3. Google Sync

同步联系人和日历的工具,可以将手机联系人与 Gmail 联系人同步,将手机日程表与 Google 日历同步。这个应用实在是太酷了,在同步完联系人到 Gmail 中之后,我再也不用担心手机丢了会失掉很多人的联系方式。编辑自己的日程表也可以在电脑上进行,不用在手机上慢慢地敲字了。而且只要登出 Google 帐号,就可以删除所有联系人,换手机时候很方便。

4. Key Master

我平时不喜欢用鼠标在一堆图标中找应用程序,所以在电脑上我都是用 Launchy 来启动应用程序。Key Master 是在黑莓上自定义快捷键启动应用程序的工具,非常好用,一般情况下我都不需要再进黑莓的 Applications 目录用滚轮来找程序。

5. Mobipocket Reader

这是一个电子书管理和阅读工具,有手机版和电脑版。电脑版和 iTunes 类似,先把电子书导入到电脑版里,然后通过数据线发送到手机上的 Reader。这不是它最酷的地方,它最酷的是可以直接导入 .pdf, .html, .chm, .epub 格式的电子书。如果有某本书的 pdf 版,就不用再费尽心机寻找 txt 版了,直接导入就可以在手机上阅读。Mobipocket Reader 对 pdf 的重新格式化在我看来已经到了值得称赞的地步,太牛了!

6. 凌波微步 SmartDail

这是一款智能拨号软件,由于黑莓的电话簿不支持首字母查询功能,在电话簿中寻找联系人比较麻烦。这款软件可以支持名字首字母查询,定位联系人非常方便。

7. MidpSSH

黑莓上的 ssh 客户端,不支持中文,但是紧急情况下用来进行简单的服务器维护足够了。

8. Gmail 手机客户端

这个不用解释了。

9. 点讯输入法

用这个输入速度比黑莓原生输入法快多了,主要体现在两个方面:选字快和匹配率高。缺点就是切换麻烦,或者在一些应用中只能拷贝粘贴。

10. BerryFetion

这个软件也相当酷,可以在短信上直接选择“使用飞信回复”,可以省不少短信费!

11. Btalk

黑莓上的 Gtalk 客户端。由于官方的 Gtalk 黑莓版不支持个人用户,开源的 Btalk 让这变成了现实。但是实话说,谁用外出时还用手机挂 Gtalk 呀(又没经验值),也就是偶尔用一下而已。

12. AutoLock 和 TinyLock

自动锁屏软件,用处不大。

剩下还有一些应用,BBWeather, CallAssistant, MobCal, Anyview, BBNotePad 之类的,装上几乎没有用过,也就不评论了。

还有一方面的应用我觉得非常遗憾,我一直没有找到很棒的记事本软件。我希望这个记事本软件能支持(以优先级排序):1. 自动保存,比如15秒保存一次;2. 导出到电脑;3. 支持点讯输入法。不知道这样的应用存在不存在?

PS:在应用之外,还有一款软件不得不提,就是 BlackBerry Master Control Program,简直是安装黑莓软件的神器呀,比黑莓原版的 Desktop Manager 要好太多了!

亲历卓越“硬盘门”事件

前天晚上看到有人在推上讨论卓越超便宜的 118 元 320G 硬盘,过去一看居然是真的。其实经过“25 元门” 之后,我知道这是几乎不可能的,其实我也知道即使我下了单卓越也是会赖掉的,但是我还是抱着“全民调戏,重在参与”的心理加入了抢购大潮,并且进一步散播了这一消息...

事实证明我的预料是对的,我下的两个硬盘的订单在保持 44 个小时后,被卓越取消,卓越再一次(无耻地?成功地?开心地?)赖掉订单:

尊敬的客户:

您好!感谢您的订购。

我们抱歉地通知您,您在卓越亚马逊网站上购买的劲永牌320GB移动硬盘的价格出现错误。卓越亚马逊上有数百万种商品,因此有些时候难免会出现这类问题。根据我们在卓越亚马逊网站上公布的使用条件中的价格政策:http://www.amazon.cn/static/claim.asp?uid=478-5181363-0560506,出现商品价格错误问题时,如果商品正确价格高于错误的标价时,我们不得不取消您的订单,并且通知您。因此,您的订单已被取消。如果您仍然想购买这个商品,请根据正确的价格再次购买。

对于就此给您带来的不便,请您接受我们真诚的道歉。今后我们将继续努力,尽力确保卓越亚马逊上不再出现此类问题。

我们衷心期待继续为您提供服务。

卓越亚马逊客户服务部

不过我依然很开心能亲身见证这一过程。:)

博客被搬家

两年多来我的主页都寄生在徐宥师兄的 HostMonster 空间上,今年5月份博客也搬了过去,因此对徐宥师兄的长期收留致以“崇高的谢意”。期间也发生过无法访问的情况,但基本上都是暂时的技术故障。但是昨天,所有 CN blogger 都担心的非技术故障发生了——我感到非常遗憾。

其实也不是第一次发生这种事情,一般情况下我忍一忍也就过去了,不就那么一段时间不能正常访问嘛。但很郁闷地是我现在正在找工作的关键关口,大家也都看到我在 CV 中列出了我的项目和博客,我可不想当 HR 对我简历感兴趣的时候却无法访问我列出的项目主页。因此我紧急搬了次家,至少希望在未来三个月到半年时间内不要出问题。

由于是独立主机,所以搬家过程算是相当无痛吧,把文件打个包再解包,数据库导出再导入,基本上没丢失啥,访问体验也没有任何改变。只是我多句牢骚,记以为志罢了。

最近博客更新频率大幅下降,可以想见我也是在忙碌和踌躇的当口,没什么好说的,祝自己好运吧!

附近照一张,身着 Perl T 恤乃是在 China-Pub 九周年会抽到的奖品——我第一次在活动中抽到奖品。

着 Pert T 近照

Windows Tips: 修改热键和文件访问权限

我平时习惯使用 Win+E 打开 Windows 的资源管理器,但对资源管理器的左侧栏一直不感冒。用热键打开我的电脑本身就是为了键盘操作方便,但是多了个左侧栏使方向键选择文件夹相当不方便。昨天我总算找到了覆盖 Win+E 热键的方法。

AutoHotKey 是一个编辑和管理 Windows 热键的开源软件,SciTEAutoHotkey 是编辑 AutoHotKey 脚本的开源软件。(也许某些人会惊讶,AutoHotKey 居然不自带脚本编辑器,还要别人帮它写,我想这也许是受到 Unix 哲学的影响:Do one thing and do it well.)

AutoHotKey 脚本的基本语法是很简单的,前面是热键,后面是执行的命令,启动脚本后热键就会起作用了。键盘上每个特殊键对应的符号在热键列表中有列出,像我前面替换热键 Win+E 为打开“我的电脑”且无左侧栏的语句是:

#e::Run ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}

::{20D04FE0-3AEA-1069-A2D8-08002B30309D} 是“我的电脑”的 CLSID(多谢 IronFeet 的提示),如果需要打开非特种文件夹,就不需要这么麻烦了,直接类似于 #e::Run C:WINDOWS 即可。同理,设置用 Win+C 打开控制面板可以写成:

#c::Run ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}::{21EC2020-3AEA-1069-A2DD-08002B30309D}

利用 AutoHotKey 可以做很强大的事情。比如我在 Linux 下使用 Evince 查看 pdf,可以使用 vim 的习惯,jkhl 上下左右移动 pdf 文档,那么我们可以在 Windows 下用 AutoHotKey 来实现对 Adobe Reader 同样的操控,语句如下:

#IfWinActive ahk_class AcrobatSDIWindow
j::Send {Down}
k::Send {Up}
h::Send {Left}
l::Send {Right}
#IfWinActive

这几句话的意思是,如果 AcrobatSDIWindow 这个 ahk_class 窗口在激活状态下的话,那么 jkhl 就被换成键盘上的上下左右键。AutoHotKey 的高级使用还是很麻烦的,它网站上的 Tutorial 是一个不错的入门教程。其实一般我都是用 Launchy 启动程序,对 AutoHotKey 的需求不是那么多,简单的几个组合键就够了。

我昨天才发现 Windows XP 家庭版和专业版的一个大区别:家庭版没有“组策略”编辑器。我电脑的“所有程序”->"启动"目录不知道被什么安全软件设置成只读了,我以为 ATTRIB 命令是修改文件权限的,却发现无法修改“启动”文件夹的权限。后来找到 CACLS 命令,可以用来指定特定用户对文件的访问权限,才把"启动"文件夹访问权限修改成了完全控制:

cacls 启动 /G Solrex:F

设置可写以后就可以把 AutoHotKey 脚本的快捷方式拖到“启动”文件夹,随系统一起启动了。

用 Wireshark 分析 RTP 流

Wireshark 是一个强大的抓包及网络分析软件,可以用来嗅探和分析多种网络协议的数据包和流,RTP 和 RTCP 也是其中的两种。

对 RTP 流的分析过程,在 Wireshark 的 Wiki 上讲得很清楚,下面我只是记录一下我在使用过程中的一些经验:

1. 要想分析 RTP 流,首先要把抓到的 UDP 包用 RTP 协议而不是默认的 UDP 协议 decode; Wireshark 默认只对选中的流(由端口区分)进行 decode,所以对 audio 和 video 流要分别 decode。

2. 直接从菜单中选择 RTP 的 Stream Analysis... 才是对双向的流进行分析,从 Show All Streams 中再分析只是单向的 RTP 流。

3. 不要过度相信 Wireshark 的能力,尤其是在无线网络或者网卡驱动不是很合适的情况下,Wireshark 也会有丢包,所以说 Wireshark 对 RTP 流的分析也是“仅供参考”——除非经过严格测试 Wireshark 不会错过任何包。

4. 这个页面上提到的 Sun 的 JMF JMstudio 的 Linux 版本状况很糟糕。首先其安装文件中使用的 tail 参数和 bash 中的 tail 参数不一样,导致执行安装文件不仅不会安装,反而会清除安装文件的内容。由于其将安装脚本和二进制文件写入到同一个文件中,所以最好是在外部手工用 tail 提取二进制文件的内容;其次无论如何配置,该程序运行时会去监听 IPv6 地址的端口而不是 v4 的端口——我一直想不通是什么原因,所以该程序可以说是基本不可用。

5. rtptools 是个好东西。我们可以先用 Wireshark 录制一段 RTP 流,保存成 rtpdump 格式,就可以用 rtpplay 不断地重放它,用来测试网络状况很方便。原本应该用 JMStudio 收听的,既然它不可用,只有用 rtpdump 在另一端收听了。

回复选登:

james:

博主请问有没有办法能够使wireshark能够在ubuntu下捕捉到所有的rtp包?

由于要做老板的项目,所以需要测量一系列delay,jitter等等数据。所以我用vlc做server向外multicast一个rtp stream (拓扑上用的全部是有线)。若传输的stream质量不高(比如dvdrip,大概速度也就1.5mbps),wireshark能够完全捕获所有的包。但是若是使用较高质量的stream(比如1080p,1080i的,大概在20mbps)就会出现wireshark丢包的情况。

所以我想知道wireshark丢包的原因是不是来自于cpu利用率太高?有没有办法在仍然使用wireshark的情况下捕捉到所有的包?如果有其他抓包、分析软件,博主可否给我推荐一下?

非常感谢!

Solrex Yang:

@james
非常抱歉,我所了解的知识无法解决您遇到的问题。如果您找到了解决方法,非常欢迎您回来再次留下您的评论。

james:

博主你好,这个问题已经解决了。

由于wireshark实时捕捉packet会非常消耗cpu资源,所以我使用tcpdump来抓包,并且加大了libpcap的缓冲区,问题就解决了。

当然,若是要在Gbps的网络环境中抓包,linux下的tcpdump的精度完全不够(尤其在包长度小的时候很明显),这个就是跟libpcap函数相关的。有个意大利大牛写了一个PF_RING的类似“zero copy”的应用,可以在很大程度上解决这个问题,如果大家有兴趣可以尝试。google上有相关介绍。

解决 GAppProxy Set-Cookie 和 HTTPS Cert Bugs

我自己写了一个类似 GAppProxy 的工具,支持 Python 和 PHP,有兴趣可以看这里

研究 GAppProxy 有两个原因:一、最近 Twitter 不能用,而我常用的 GAppProxy 却不支持我登录 Twitter;二、我最近在琢磨 SSL 证书的问题,正好用 GAppProxy 登录 Twitter 也有证书错误。

第一个 BUG:Set-Cookie Bug

GAPPProxy 目前对 Cookie 的处理有一些问题,主要出在对 header 中的多个 Set-Cookie 域处理错误,就会导致用户登录一些网站错误,无法获得正确的会话 Cookie。

举例,当服务器返回的 header 中有多个 Set-Cookie 域时,比如一般的 wordpress 返回的 header 中,Set-Cookie 域至少有三个:

Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-content/plugins; httponly
Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-admin; httponly
Set-Cookie:
wordpress_logged_in_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C545dcea44d5e69aec5c1203c64bee061;
path=/; httponly

GAPPProxy 会把它作为一个串传给本地浏览器:

Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-content/plugins; httponly,
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-admin; httponly,
wordpress_logged_in_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C545dcea44d5e69aec5c1203c64bee061;
path=/; httponly

这样本地浏览器对 Cookie 的设置就会错误。解决办法很简单,将这个长串用split(', ')切开,同样设置三个 Set-Cookie 域即可。

Update 20090710/Solrex:
有人评论说 ', ' 也是可能在 Cookie 中出现的合法字符串;那么我就另外想了一个办法,先用正则表达式替换将 ', ***=' 替换成 'n***=',再用 'n' 对字符串进行切割。由于在 Cookie 中正常出现的 ', ' 后面会首先跟着 ';' 或 ',',然后才可能出现 =,因此用 ‘, ([^,;]+=)’ 匹配就可以了。而且这次把修改放到服务器端了,原来的客户端就不需要修改了

Patch:

Index: fetchserver/fetch.py
===================================================================
--- fetchserver/fetch.py    (revision 92)
+++ fetchserver/fetch.py    (working copy)
@@ -29,6 +29,7 @@
from google.appengine.ext import webapp
from google.appengine.api import urlfetch
from google.appengine.api import urlfetch_errors
+import re
# from accesslog import logAccess

@@ -153,14 +154,12 @@
             if header.strip().lower() in self.HtohHdrs:
                 # don't forward
                 continue
-            ## there may have some problems on multi-cookie process in urlfetch.
-            #if header.lower() == 'set-cookie':
-            #    logging.info('O %s: %s' % (header, resp.headers[header]))
-            #    scs = resp.headers[header].split(',')
-            #    for sc in scs:
-            #        logging.info('N %s: %s' % (header, sc.strip()))
-            #        self.response.out.write('%s: %srn' % (header, sc.strip()))
-            #    continue
+            # NOTE 20090710/Solrex: Fix multi-cookie process problem
+            if header.lower() == 'set-cookie':
+                scs = re.sub(r', ([^,;]+=)', r'n1', resp.headers[header]).split('n')
+                for sc in scs:
+                    self.response.out.write('%s: %srn' % (header, sc.strip()))
+                continue
             # other
             self.response.out.write('%s: %srn' % (header, resp.headers[header]))
             # check Content-Type

第二个 BUG:HTTPS Cert Bug

简单地来说,GAppProxy HTTPS 连接的实现是一个欺骗本地浏览器的过程,类似于中间人攻击。它首先用 GAE 获得页面的明文,抓到本地,然后假冒 HTTPS 站点与本地浏览器通信。因此它就需要提供一个 SSL 证书,来完成 HTTPS 连接的建立。

但这就有一个问题,SSL 证书哪儿来的?目前 GAppProxy 对所有的站点都使用一个证书,而且这个证书是未经任何授权 CA 认证的证书,因此就会产生很多错误。首先,该证书中的授权机构不是可信 CA,Firefox 和 IE 中捆绑的证书中没有该 CA 的证书;其次,该证书的 CN (Common Name)与站点域名不同,不可用于站点的通信。因此可能每次都需要用户自己点击添加证书例外。

为了避免这种缺陷,我想出来的做法是——自己做 CA,正所谓做事情要专业,要骗就骗彻底点,骗得浏览器神不知鬼不觉。首先,建立 CA 的密钥,自己给自己签发一个证书作为 CA 的根证书,将该 CA 的证书安装到 Firefox 浏览器中,在运行时使用该 CA 为每个 HTTPS 连接的站点签发对应于该站点的证书。由于 Firefox 中已经安装了该 CA 的证书,那么所有该 CA 签发的证书都能够通过 Firefox 的检测了。

这个方法比较麻烦,而且需要电脑上有 openssl,对于 Linux 完全没有问题,对于 Windows 可能就有点儿困难了。所以我这里就给出个思路,具体实现就不谈了。
您可以在 http://share.solrex.org/ibuild/ 找到我修改后的代码,感兴趣的话可以下载下来看看。

EE vs. CS

这是一篇很古老的文章,翻译得不好,大家将就着看。

固定链接:http://share.solrex.org/os/ee_vs_cs_cn.html

从前,在一个离这儿不远的国家,一个国王召来他的两个谋臣进行一项测试。国王给他们看了一个闪闪发光的金属盒,盒子上面有两个插槽,一个控制按钮和一个手柄,然后问道:“你们认为这是个什么东西?”

其中一个谋臣,电子工程师,抢先答道:“陛下,这是一个烤吐司机。”国王问他:“如果让你给它设计一个嵌入式计算机,你会怎么做?”这个工程师回答说:“利用一个4位的微控制器即可,我将写一个简单的程序读入亮度盘(译注:the darkness knob,不知道是什么东西),将其量化到从雪白到煤黑的 16 阶亮度水平上。这个程序将使用该亮度水平作为 16 个初始时间值表的索引,然后启动加热元件,用该亮度水平映射到的时间初始化计时器。在计时结束后,关掉加热元件,弹出烤好的吐司。如果您愿意的话,下星期我就能给您一个可以工作的原型。”

第二个谋臣,计算机科学家,立刻认识到了这种短视想法的危险。他说:“烤吐司机并不仅仅是用来把面包变成吐司的,它们同样会被用来加热冷冻华夫饼。在您面前所放置的其实是一个早餐厨具,当您的臣民变得越来越老练时,他们将需要它提供更多功能。他们会希望早餐厨具同样可以用来烤香肠、煎培根和炒鸡蛋,一个只能做吐司的烤吐司机将会很快被大众废弃。如果我们不未雨绸缪,在不远的几年后我们就不得不完全重新设计烤吐司机。”

“考虑到这一点,我们可以制定一个更好的解决方案。首先,创建一个早餐食品基类,特殊化这个基类到几个派生类:谷物、猪肉和禽肉。特殊化的过程可以重复进行下去,比如谷物可以派生出吐司、松饼、薄煎饼和华夫饼;猪肉可以派生出香肠、links和培根;禽肉可以派生出炒鸡蛋、煮鸡蛋、荷包蛋、煎鸡蛋和多种煎蛋卷类。”

“火腿奶酪煎蛋卷类尤其值得特别注意,它必须同时继承猪肉、奶制品和禽肉类的特点,除了使用多重继承,这个问题无法得到妥善解决。在运行时,该程序必须创建合适的对象,并发送消息到该对象:‘把自己弄熟。’当然,由于多态性,这条消息的语义取决于该对象的类型,所以它对于吐司对象和炒鸡蛋对象分别具有不同的含义。”

“回首目前我们的进程,可以看到,分析阶段揭露了一个基本的需求,那就是该厨具需要能做出任何种类的早餐。在设计阶段,我们发现了一些衍生的需求,特别是我们需要一个面向对象的、支持多重继承的语言。当然,没有哪个用户希望当煎培根时鸡蛋变凉了,所以并行处理能力也是必要的。”

“我们绝对不能忘记用户界面。用来控制食物的手柄缺乏通用性,亮度盘令人困惑。一个产品必须具有友好的图形界面,否则不会受到市场欢迎。当这个早餐厨具启动时,用户应该看到一个牛仔出现在屏幕上。用户点击它后,一条消息“正在启动 UNIX v.8.3”将显示在屏幕上。(当该产品推出时,Unix 8.3 应该已经发布了。)然后用户可以下拉菜单,点击他们想做的食品。”

“当在设计阶段做出首先规划软件的聪明决策之后,在实施阶段剩下的只是选择一个合适的硬件平台了。一个使用 Intel 80386 CPU,拥有 8 兆内存、30 兆硬盘和 VGA 显示器的机器应该足够了[1]。如果你选择了一个多任务、面向对象、支持多重继承且内建图形用户界面库的编程语言,写这样一个程序是件轻易而举的事情。想想如果我们傻乎乎地允许一个硬件优先、将我们锁定在一个 4 位微控制器上的愚蠢设计将会给我们带来多少困难!”

国王听完他这番话,作出了将这个计算机科学家斩首的英明决定。人们从此过上了幸福快乐的生活。

[1] 在这篇文章出现的当时,这应该算是挺先进的配置了。

再谈龙芯

这次博客更新间隔比较长,因为最近确实很忙,这篇也是应景之作。

下午一朋友忽然给我来一条消息“龙芯 sb 了,到头来还是用的美国技术”,我还以为他看到了 07 年意法和龙芯的协议那条新闻。结果他告诉我去看看各大门户科技板块的头条,果然发现了一条很热的新闻:龙芯购美公司专利授权:CPU核自主产权战略失败,其来源在这里

于是想起来我两年前写的那篇关于龙芯的文章,现在证明事情确如我当时所想“说龙芯拥有全部自主知识产权,我不信”。但是对当前新闻中所持的那些观点,我认为,太过了。龙芯的是,是新闻炒出来的,龙芯的非,也难脱炒作的嫌疑。

虽然没有参与过龙芯的任何工作,但是我原来在简约纳是做(类 MIPS)芯片工具链的开发,现在也是在清华-Intel 的联合实验室实习,接触过一些和龙芯相关的人——尤其是龙芯编译组。因此我大概可以算是一个稍微明白点儿真相的群众——但也仅仅是群众,请谨慎围观。

首先,新闻标题是“龙芯购美公司专利授权:CPU核自主产权战略失败”——其实从龙芯决定使用 MIPS 的 ISA 起,自主产权战略已经失败了。我想没有人会相信一些受过那么多年良好教育,中国最顶尖的人才们不懂得什么叫做知识产权吧?但是在中国的科研环境下,科研人员的业绩都体现在科研成果上。先不谈有没有相应方面的人才,仅仅说从头开始设计指令集,得花多少钱?得用多长时间?IC 设计本身就是一个烧钱的工作,恐怕没有人敢顶着上峰的压力往一个看不到成果的项目里烧钱吧?

其次,就算报道“自主产权战略失败”,也应该早就报道了。以前龙芯大概不想让大家认识到知识产权问题,转着弯地跟意法半导体合作,07 年新闻里只注重报道意法对龙芯海外市场的买断,但是谁不知道龙芯向意法买 MIPS 架构授权啥意思呢?如果要报道“自主产权战略失败”,也应当那时就报道了。

再次,龙芯直接从 MIPS 手里买授权,我认为这对它是件好事。就像新闻中所说,龙芯以前肯定和 MIPS 谈过,谈不拢才和意法谈。现在它能从 MIPS 直接买,当然要比从意法买要好得多。而且,作为 MIPS-Compatible 的 CPU,利用已有代码,可以减少很多软件移植的工作量。

最后,仅仅因为一个 MIPS 架构否决龙芯团队的所有努力,是错误的,媒体的报道,不理也罢。我虽然不清楚龙芯团队做了些什么工作,但是把一个嵌入式的 CPU 扩展成一个通用 CPU,我认为不是一件简单的工作。(这一句其实有误,因为龙芯的芯片是自己设计的,但我的意思主要是指从媒体批评的 ISA 的角度来说。朋友告诉我说,即使这样说也不对,因为指令集和 CPU 应用类型并没有必然联系。)成也媒体,败也媒体,为什么记者不真真切切地去了解去报道一个科技成果,总是要加上许多夸张的成分呢?

PS:这篇博贴出来,我收到了很多批评,这是我在自己不熟悉领域发表看法的代价,我非常感谢那些愿意指教我的人。不过对于那些只有指责没有指点的评论,我只能说谢谢您,但这里不欢迎您。

博客迁出 yo2.cn

近两年以来我都是使用的 yo2.cn 提供的博客托管服务,就是说 yo2.cn 提供一个 WordPress 空间,我将域名 blog.solrex.cn 绑定到这个空间上。对 yo2.cn 的服务,总的来说,我还是基本满意的,不然我也不会一年后续费继续使用。但是一直以来的服务器不稳定,尤其是最近两个月来的宕机事件,让我对 yo2.cn 的服务失去了信心。09 年 3 月 14 日到 17 日,09 年 5 月 12 日到 15 日,两个月里两次长达三天的宕机事件在我的 Google Analytics 中划出了两个漂亮的谷底!我只好说,拜拜了 yo2。

以前之所以选择 yo2.cn 的服务,主要原因是(1)懒得折腾 WP 系统(2)它的服务器在国内,教育网用户可以无障碍访问(至少我女朋友在使用 CERNET)。这次将博客迁移到国外主机上,可能会造成部分教育网用户无法访问,请见谅。如果您希望继续关注此博客,请使用 Google Reader抓虾等在线阅读器订阅此博客的 RSS

现在博客的系统和主题和原来保持一致,域名也没有变化,所以对一般读者来说,访问没有任何影响。在某些网络状况下,可能速度会比原来慢一点儿;DNS 没有刷新的网络,可能要等待blog.solrex.cn域名记录的刷新;在一些存在缓存的浏览器中,可能要重刷一下缓存。

对于我来说,却是方便了许多,得到了对 WordPress 系统的完全控制,也没有了关键字的过滤,使用起来就没那么窝心了。

谈谈 CoolSIP

说明:本文有一些内容是基于本人的合理猜测。如果您知道更多细节,请不吝指出,但最好请提供事实依据,而不是仅仅指责我主观臆测,不尊重事实。我不知道事实,才会猜测,我如果知道事实,就不用猜测了。

CoolSIP 是清华大学信息网络工程研究中心建设的 IPV6“下一代互联网应用示范平台”提供的一个在 IPV6 网络上运行的 VoIP 软件,该软件提供了主机间通话和主机拨打传统 PSTN 电话的功能。目前该服务仅提供给 IPV6 教育和科研网用户使用,注册该服务需要 edu.cn 或者 ac.cn 后缀的大学或科研机构电子信箱。测试期间是免费的,但需要用在线时长换取话费。

我这里呢,就是说几句闲话,谈谈我对这个软件和服务的看法。

1. CoolSIP 的知识产权问题

我对 CoolSIP 软件的结构并不清楚,但基本可以肯定的一点是 CoolSIP 使用了开源的 osipeXosip。不用逆向工程该软件,查看 CoolSIP 帐户信息的保存位置就可以知道,CoolSIP 的帐户信息保存在 C:Documents and SettingsUsernameeXosip 目录下。

由于 osip 遵从的是 LGPL 协议,eXosip 遵从的却是 GPL 协议,那么从我有限的对知识产权的了解(关于 LGPL 和 GPL 可以参考这里),CoolSIP 也应该遵从 GPL 协议(对这点有问题您可以在评论中指出)。而目前来看,清华大学信息网络工程研究中心并没有发布 CoolSIP 的源代码,而且 CoolSIP 貌似将 osip 和 eXosip 库静态链接进了程序里(大概有隐藏的意图),那么可以认为他们并不认可 GPL 许可证——不过他们还没有错误地在安装文件中包含一份私有版权声明。

另外,http://ngmylife.wirelesslan.edu.cn/multimedia.html 页面中说:“当eTone出现无法登录的问题时,可以使用CoolSIP试试。”可以猜测 eTone 和 CoolSIP 有非常紧密的关系,而 eTone 又是一款流行 VoIP 软件“快门” 的前身,那么我对“快门”软件的版权也持谨慎地怀疑态度——不过我更倾向于快门不大可能犯这种错误。

2. CoolSIP 本身的程序设计问题

CoolSIP 是一个极不成熟的软件,使用过程中居然会出现程序错误,点“忽略”才能继续运行的情况;由于其版本历史仅仅显示到 2007 年 9 月 3 日 ,我想其对 SIP 协议的实现是存在问题的,例如在拨打一个通话中的电话号码时,CoolSIP 不会提示对方正在通话中。总之,该软件除了是 sip6.edu.cn 提供的 VoIP 服务的官方客户端以外,几乎一无是处。

3. CoolSIP 的 VoIP 服务

虽然 CoolSIP 客户端没有什么优点,但是 sip6.edu.cn 的 VoIP 服务器端对 SIP 协议的实现应该还算很标准的。由于 CoolSIP 使用了 eXosip 库,那么我就尝试使用另一款使用 eXosip 库的遵从 GPL 协议的开源软件 Linphone 尝试连接了一下 CoolSIP 服务器,居然能正常连接。

并且 Linphone 显示出比 CoolSIP 更友好的用户体验,例如:Linphone 支持 Windows 和 Linux 系统,更完整地支持 SIP 协议,支持自定义电话联系人列表,多帐户同时在线,通话统计记录显示,多种音频编码,更好的视频支持。像上面提到的 CoolSIP 不支持的“您拨打的电话正在通话中”提示,也可以在 Linphone 中听到。

官方提供的专门客户端 CoolSIP 竟然不如一个开源的通用客户端 Linphone,这大概是“下一代互联网应用示范平台”服务的一个笑柄吧。

PS: 使用 Linphone 登录 CoolSIP 的服务器,需要 libexosip2 和 libosip2 版本大于 3.3,我的经验表明 3.1 会出现较多问题。推荐手动依次编译 libosip2->libeXosip2->linphone。Windows 版的可执行安装程序包含了这两个库,所以不存在库版本低的问题。

关于 eYouIPB 和 CASNET

我不知道为什么在干正事的时候想写这个东西,大概也算是一种强迫症吧,想起来某件事情就总觉得有义务马上去做。那么就赶快写完吧。

这篇文章是写给有心人看的,如果您不知道 eYouIPB 和 CASNET 是什么东西,那么您可以无视以下内容。

eYouIPB 是中科院研究生院目前使用的网关登录客户端,但只能在 Windows 下使用。我用 Python 写的一个小软件 CASNET,就是一个 Linux 下的登录替代品,不过目前它也可以支持 Windows。昨天有同学发信问我有关科苑网关登录的问题,我顺便在这里做个笔记吧。

1. 有关流量统计。目前 CASNET 不支持像 eYouIPB 那样的实时的流量统计功能,因为我觉得没有必要。科苑上网是按照流量收费的,而这个流量是指网关服务器统计的流量,而不是客户端统计的流量,所以客户端的实时精确流量统计没有太大意义。

eYouIPB 带有实时的统计流量功能,但这却造成了一些问题。eYouIPB 使用 WinPcap 库做流量统计,而且这个库的版本比较低。假如系统里安装了新的 WinPcap 库,例如 Wireshark(Ethereal) 网络监听程序就会安装 WinPcap 库,那么 eYouIPB 就无法工作了。WinPcap 是一个非常强大的抓包库,而 eYouIPB 仅仅使用了其中的流量统计功能,实在很让人费解它为什么要使用 WinPcap 库并且将它的 dll 包含在自己的软件中。我想应该有更简单的办法做流量统计——如果非做不可的话。

2. 有关登录方式。科苑上网有两种登录方式:一种是 eYouIPB,使用私有的协议;一种是网页登录,使用 https 表单交互。由于 eYouIPB 协议私有,CASNET 只能模拟网页登录的方式,所以您会发现 CASNET 登录速度会比 eYouIPB 慢一点儿,因为它有一个完整的 https 安全协商和交互过程,而 eYouIPB 只需要几次简单 tcp 数据包交换就可以了。不过因为同在一个局域网内,这个速度还是在可以忍受的范围之内。

3. 有关安全性。去年我曾经研究过 eYouIPB 的协议,发现还是相对简单的。我记录的笔记已经丢失,但大概是这个样子的(应用层):“首先 eYouIPB 向服务器发起一个连接,服务器应答可以连接,然后 eYouIPB 将用户名和域发送给服务器,服务器看上去会返回一个密钥,然后 eYouIPB 使用该密钥用某种加密算法加密用户口令(或者还有其它信息)发送给服务器,服务器验证口令,回应是否可以登入。”

从该协议的实现来看,安全性有限。一个是用户可以通过不停地登入登出收集明密文对,另外反汇编 eYouIPB 看起来也不是那么困难,尤其是可以使用 Winsock 32 的 API 来定位负责加密的代码段。一旦加密算法被了解,窃听 eYouIPB 的数据包就能获得用户的口令,那么帐户就可以被窃取。所以相比而言,https 的登录方式更值得信任。

PS: 今天顺便搜索了一下,有个朋友曾经对 1.03 版本的 eYouIPB 协议进行过分析,我感觉和 2.0 版本差距不大。而且这个人完全用 C 实现了 eYouIPB 1.03 的加解密函数如果他仅仅凭逆向工程做出这个结果的话,我是相当地佩服的(因为我花了一整天的工夫也没有做出来一点儿东西)。如果当初我能看到这个结果的话,说不定就有 clue 去模拟 eYouIPB 的协议而不是使用 https 方式了,现在是懒得再去钻研这个东西了。只是不知道 2.0 是否还在使用同样的加解密函数,如果仍在使用的话,那么对有心人来说这个口令保护措施基本上算是不存在了。

4. 有关软件运行速度。这个比较主要是 Windows 平台下,CASNET 是用 Python 脚本写成的,而看起来 eYouIPB 使用 VC++ 写的,在软件的大小和运行速度上 eYouIPB 要显然优于 CASNET。由于 CASNET 的 Windows GUI 版需要额外的 GTK 运行时库和 Python 库支持,大概会加载到内存中 16M 左右的数据,不过这些东西运行时真正用到的不多,所以有些部分除了初始化时,其它时候很可能是驻留在交换区中,所以还是可以接受的。

在 Linux 平台下,因为 GTK 和 Python 库是被非常广泛使用的,所以 CASNET 并不需要额外占用很多内存。

之所以会发布 Windows 版,是因为相比 eYouIPB 而言,Wireshark 对我更重要,所以我无法使用 eYouIPB,而又讨厌网页登录时每次都要选择下拉菜单。再加上顺便看看 PyGtk 在 Windows 下的表现如何。但比较讽刺的是,Windows GUI 版的 CASNET 下载量要超过其它版本加一起的下载量,看来它的存在还是对某些用户有一些帮助。

5. 有关软件功能。eYouIPB 的功能已经被锁定了很多年,但是 CASNET 一直在根据用户的需求增加或者删减某些功能,例如余额不足提醒,断线自动重连,一键切换登录模式等。还有一个未发布的功能是关机自动下线。这是一个我一直想实现的功能,因为当用户关机忘记离线时,恰好分配到原 IP 的用户就可以使用该帐号,造成流量被窃取。这个功能在 Linux 版本上已经基本实现,但是 Windows 版本仍然没有找到可用的方法实现。

一本过时的黑客教科书

——评《良性入侵-道德黑客非官方指导》

如果说放在原书出版的 2002 年,本书还是有相当价值的。但是我不理解为什么 2007 年电子科技大学出版社还会去翻译一本过时的黑客技术书籍?

在计算机安全和攻击的领域,时效性非常的强。在 2007 年再读一本讲述怎么攻击 Windows 9x 的书显然是不合时宜的,而且文中提到的各种漏洞和攻击方法早就被新的补丁和协议所防护,或者已经有了新的攻击类型,那么对于当前读者的意义也就不大了。就算是仅仅用于研究,读者也很难找到一个有漏洞的原型系统来做实验。

非常值得诟病的还有本书的翻译,我不得不说,很难看到比这本书翻译得更烂的了,连文字都不通顺,随便举一个例子(第103页):

“(前面一段是代码)作为一个基本的规则,你需要等待更长时间,你会更安全。如果有三分钟的时延,显示,登出。TCP 端口扫描,像 Portscan.java 或者其他的 Windows 的端口扫描(我承认我不小心发现了一句语句重复的排版错误),像 Portscan.java 或者其他的 Windows 的端口扫描这样的 TCP 端口扫描程序将不会对主机的安全性构成威胁,主机将更加安全。”

我基本上第一次没看懂这一段是什么意思。另外还值得一提的是本书的代码量相当大,几乎能占到四成,代码量大不是问题,但是异常多的代码就让人怀疑是否用代码来充实书的内容了,会让读者觉得钱花得不值。(但也有一个可能是受美国法律的限制,不能出口一些敏感代码,只能把代码放到书里来逃避规定,一些密码学的书籍曾这样做过。)

再加上 80 元的售价,天那,给我 80 元我会买很多好书,但绝对不会是这一本。

内网穿透反向隧道代理技术

本文的主要目的是利用使 ssh 服务器(外网)通过客户端(内网)代理上网以及反向控制客户端(内网),不需要网络管理员权限,不需要 NAT。即可上网主机 A 位于内网,不可上网主机 B 位于外网,A 能直接访问 B,但 B 无法访问防火墙内的 A,这样 B 就可以使用 SSH 隧道访问 A 主机上的代理服务器上网。(估计有这种变态需求的人只存在于大学中)

B(210.77.*.*)---->(210.77.*.*)FW(192.168.0.*)|--->(192.168.0.*)A---->(192.168.0.*)FW(123.*.*.*)---->Internet
B(210.77.*.*)< ----(210.77.*.*)FW(192.168.0.*)-----(192.168.0.*)A<----(192.168.0.*)FW(123.*.*.*)<----Internet

一、代理

首先在客户端上安装 socks 代理(HTTP 代理可以使用 Squid,同理)。socks 代理软件使用 Dante。Dante 的启动以及配置需要手动调整,下面是 Dante 的配置文件 /etc/sockd.conf:

compatibility:reuseaddr
internal:0.0.0.0 port = 1080
external:eth0
logoutput:/var/log/sockd/sockd
clientmethod:none
method:none
user.privileged:root
user.notprivileged:solrex
connecttimeout:60
iotimeout:86400

## client access rules
client pass {
 from: 127.0.0.1/0 port 1-65535 to: 0.0.0.0/0
 log: connect disconnect
}

## server operation access rules
#allow bind to ports greater than 1023
pass {
  from: 0.0.0.0/0 to: 0.0.0.0/0 port gt 1023
  command: bind bindreply udpassociate udpreply
  log: connect disconnect
}

pass {
  from: 0.0.0.0/0 to: 0.0.0.0/0 port 1-65535
  protocol: tcp udp
  log: connect disconnect
}

#allow outgoing connections (tcp and udp)
pass {
  from: 127.0.0.1/0 to: 0.0.0.0/0
  command: bind bindreply connect udpassociate udpreply
  log: connect disconnect
}

#allow replies to bind, and incoming udp packets
pass {
   from: 0.0.0.0/0 to: 0.0.0.0/0
   command: bind bindreply connect udpassociate udpreply
   log: connect error
}

#log the rest
block {
   from: 0.0.0.0/0 to: 0.0.0.0/0
   log: connect error
}

下面是 Dante 的启动脚本 /etc/init.d/sockd:

#!/bin/sh
set -e

. /lib/lsb/init-functions

[ -f /usr/local/sbin/sockd ] || exit 0

[ ! -f /etc/sockd.conf ] && exit 1
SOCKD_CONF="-f /etc/sockd.conf"

SOCKD_OPTS="-D"

case "$1" in
  start)
    # Start daemons.
    log_daemon_msg "Starting Dante socks proxy server" "sockd"
    if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sockd.pid --exec /usr/local/sbin/sockd -- $SOCKD_OPTS; then
	    log_end_msg 0
	else
	    log_end_msg 1
	fi
    ;;
  stop)
    # Stop daemons.
    log_daemon_msg "Stoping Dante socks proxy server" "sockd"
	if start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/sockd.pid; then
	    log_end_msg 0
	else
	    log_end_msg 1
	fi
	;;
  restart)
    log_daemon_msg "Restarting Dante socks proxy server" "sockd"
	start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile /var/run/sockd.pid
	if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sockd.pid --exec /usr/local/sbin/sockd -- $SOCKD_OPTS; then
	    log_end_msg 0
	else
	    log_end_msg 1
	fi
	;;
  *)
    log_action_msg "Usage: /etc/init.d/sockd {start|stop|restart}\n"
    exit 1
esac

exit 0

二、ssh 服务器

服务器端 ssh 服务器最好采用 Openssh,Windows 下应该使用 Cygwin 运行 sshd,FreeSSHd 实践证明崩溃比较频繁。同一台机器的 Windows 和 Linux 上运行的 ssh 服务器最好使用相同的 host key,将 /etc/ssh/ 下面的文件保持一致即可。最好配置服务器为使用公钥进行登录验证,这样能使用自动脚本进行端口映射。

三、手动建立隧道(客户端到服务器端口映射)

用下面命令建立客户端到服务器的端口映射,将客户端的 socks 代理端口 1080 映射到服务器的端口 8080。这样服务器就可以通过自己的 8080 端口反向隧道到客户端的 socks 代理 1080 端口上网。

ssh -C -f -N -g -o PreferredAuthentications=publickey -R SERVER:8080:127.0.0.1:1080 USRNAME@SERVER
# 参数含义:
# -C: 要求对数据进行压缩
# -f:要求 ssh 执行完交互后进入后台运行
# -N:不用建立一个终端
# -g:允许远程服务器连接客户端转发端口
# -R SERVER:8080:127.0.0.1:1080:将客户机(127.0.0.1)的 1080 端口绑定到服务器(SERVER)的 8080 端口。
# USRNAME@SERVER:ssh服务器的用户名和密码
# -p 3022:ssh服务器的端口

为了方便控制,最好也将客户端的 ssh 服务器端口映射到服务器端,服务器端就可以通过登录自己的 8022 端口来登录客户端的 ssh 服务器:

ssh -C -f -N -g -o PreferredAuthentications=publickey -R SERVER:8022:127.0.0.1:22 USRNAME@SERVER

这样两台被防火墙隔开的主机就能实现双向控制。

四、自动建立隧道

手动建立隧道的缺陷是服务器端必须长期开机,并且连接只能用户在客户端手动发起。可以考虑间接的办法,比如使用两台主机均可访问的服务器作为跳板,或者客户端自动登录聊天软件,利用聊天软件接受指令。下面给出一种使用共享服务器的方法:

#!/bin/bash
# {start|stop|restart:username:ipaddress}

COMMAND="stop"
SERVER="0.0.0.0"
USRNAME=""
PORT="22"

INFOURL="http://someserver.com/somepage"

SSHOPTS="-C -f -N -g -o PreferredAuthentications=publickey -o StrictHostKeyChecking=no"

get_server_info()
{
  #info=`wget -nv -O - $INFOURL 2> /dev/null | iconv -f gbk -t utf8 |
        grep -o -e "{.*}" | tr -d '{}'`
  info=`wget -nv -O - $INFOURL 2> /dev/null | iconv -f gbk -t utf8 |
        sed -n "/{*}/s/.*{\(.*\)}.*/\1/p"`
  COMMAND=${info%%:*}
  SERVER=${info#*:}
  USRNAME=${SERVER%:*}
  SERVER=${SERVER#*:}
}

tunneling_status()
{
  tun_ps=`ps aux | grep "ssh -C" | wc -l`
  if [ $tun_ps -gt 4 ]; then
    echo -n "running"
  else
    echo -n "died"
  fi
}

start_tunneling()
{
  ssh $SSHOPTS -R 3128:127.0.0.1:3128 ${USRNAME}@${SERVER} -p $PORT
  ssh $SSHOPTS -R 8022:127.0.0.1:22 ${USRNAME}@${SERVER} -p $PORT
}

failsafe_tunneling()
{
  ssh $SSHOPTS -R 8022:127.0.0.1:22 ${USRNAME}@${SERVER} -p $PORT
}

stop_tunneling()
{
  killall -e ssh
}

echo -n "[`date +%F\ %R`] "
get_server_info

case "$COMMAND" in
  start)
    if [ $(tunneling_status) = "running" ]; then
      echo "Starting ssh tunneling.(started, do nothing)"
    else
      echo "Starting ssh tunneling."
      start_tunneling
    fi
    ;;
  restart)
    if [ $(tunneling_status) = "running" ]; then
      echo "Restarting ssh tunneling."
      stop_tunneling
      start_tunneling
    else
      echo "Restarting ssh tunneling."
      start_tunneling
    fi
    ;;
  stop)
    if [ $(tunneling_status) = "running" ]; then
      echo "Stoping ssh tunneling."
      stop_tunneling
    else
      echo "Stoping ssh tunneling.(stoped, do nothing)"
    fi
    ;;
  failsafe)
    echo "Starging ssh tunneling.(failsafe mode)"
    failsafe_tunneling
    ;;
  sleep)
    echo "Sleeping."
    exit 0
    ;;
  *)
    echo "Unrecogenized server command ($COMMAND)."
    exit 1
    ;;
esac

exit 0

将上面脚本加入 crontab 每十分钟运行一次,就能实现在服务器端对客户端进行有限的控制。

那些 ssh 教我的事

我以前以为 ssh 只能登录主机执行命令,ssh 教我它还会 scp;

我以为会 scp 很厉害了,ssh 教我它还会 sftp;

我以为会 sftp 了不得了,ssh 教我它还会 sshfs;

我以为会 sshfs 已经相当牛了,ssh 教我它还能把服务器端口绑定到本机端口(ssh -L);

我以为把服务器端口绑定到本机端口就已经上天了,ssh 教我它还能把本机端口绑定到服务器端口(ssh -R)...

然后,我怨念已久的共享上网终于实现了!

PS: 用软件还得选老牌开源软件,openssh 那么多年没更新了,在 Windows 下用 cygwin 运行都比 freesshd 稳定,不得不赞一个。Win 下的一些软件,窗口搞得花里胡哨的,功能和稳定性却不知道放到第几位了,跟人一样,长得好看的未必靠谱。

IPV6 获取地址却无法使用的解决方案

最近我的 WinXP 经常无法连接 IPV6 站点,但是 IPV6 地址的获取是正常的,同一台电脑上的 Ubuntu IPV6 工作也正常。经过一些摸索发现可能是以下两个原因造成的:

1. 分配到 2002 开头的 IPV6 地址并使用了它。2002::/16 格式的地址是 6to4 的地址,不是 native 的 IPV6 地址,所以在 IPV4+V6 双栈网络中不应该使用 2002::/16 格式的地址。执行 ping6 ipv6.google.com 可以看到自己使用的是什么 IPV6 地址。

之所以会产生 2002::/16 格式的地址,一个很可能的原因是网络中的 Windows Vista 操作系统默认会发送 IPV6 的路由器公告。使用

netsh interface ipv6 show interface "本地连接"

命令可以查看本地连接的参数,其中有一条:“发送路由器公告”,一定要设置为“否”。如果您的这个选项是“是”,那么您可以使用这个命令关闭它:

netsh interface ipv6 set interface “本地连接” advertise=disabled

如果获得的全部是 2002 开头的地址,可以使用下面命令进行重分配:

netsh interface ipv6 reset

2. IPV6 的路由表(网关)不对。tracert6 ipv6.google.com 就能看到本机是经过什么路由到 ipv6.google.com 的。如果从第一跳就显示连接超时,应该就是路由表出了问题。

两个问题的解决方案如下:

netsh interface ipv6 set prefixpolicy 2001::/16 1 1 persistent

上面这条命令的意思是设置 Windows 更偏好使用 2001 开头的 IPV6 地址,避免使用 2002 开头的地址。如果您 ping ipv6.google.com 使用的是 2001 开头的地址,那么您不必执行上面这条命令。

netsh interface ipv6 add route 2001::/16 "本地连接" fe80::21a:30ff:fe4f:7000 persistent

上面这条命令的意思是为 2001 开头的 IPV6 地址使用正确的网关 fe80::21a:30ff:fe4f:7000(中科院某公寓)。这个网关可能随着用户所在网络的不同而不同,简单点儿的方法可以去看正常用户的 ifconfig 网关地址。

之后执行 ping6 ipv6.google.com 看能否 ping 通,如果能 ping 通就说明 IPV6 工作正常了。

如果您经过以上两步之后仍然无法解决问题的话,您可以使用 netsh interface ipv6 reset 命令来重置所有修改。

PS:

1. 如何确定网络中哪些主机在发送路由器公告?

2002 后面的两个字段就是该主机的 IPV4 地址,比如 2002:3b41:177e:8:18fc:7649:9e1d:2880,其中 3b41:177e 从 16 进制显示换算成 IPV4 的 10 进制显示地址就是 59.65.23.126。一般来说,V4 地址的分配更有规律,您可以从 V4 地址大致确定该主机的位置。

2. 如何确定虚假的路由记录?

一般来说,IPV6 地址的最后 4 个域应该从网卡的物理地址中获得,假设网卡物理地址是:00-17-31-94-99-EA,在 3,4 字节之间插入 FFFE 换成 EUI-64 格式是:00-17-31-FF-FE-94-99-EA,再对第一个字节的第二位取反,就变成 02-17-31-FF-FE-94-99-EA,然后装载到 IPV6 的本地地址中,就变成本地地址 fe80::217:31ff:fe94:99ea。根据网段的不同,在前面加上 4 个网络域,就是主机的公网地址 2001:xxxx:xxxx:xxxx:217:31ff:fe94:99ea。

由于双栈路由器是使用同一个网卡提供 V4 和 V6 的路由,那么路由器的 V4 地址和 V6 地址的物理地址是一样的。通过 ping gateway_ipaddress_v4,然后 arp -a 看 V4 的网关地址对应的物理地址,与上面 V6 网关本地地址中获得的物理地址相对照,就可以确定某路由记录是否为可用的记录。

The Gold Old Tools: Pic and Chem

我本来只想写一下 pic 这个小工具,因为很少有中文资料介绍其用法。但是写下来没想到一个小软件联系到那么多典故,更让我对当年的贝尔实验室高山仰止。The gold old days never come again.

目录
1. Pic 的作者
2. 一个 Pic 的简单例子
3. 一个复杂的例子
4. 关于 chem 的典故
5. 在 LaTeX 中使用 pic

1. Pic 的作者

Pic,我敢打赌大部分读者没有听说过这个软件(或者说语言),但是我也敢打赌大部分读者听说过它的作者。

所以我只好从它的作者 Brian W. Kernighan 开始介绍了。不过也许用不着我介绍,假如你学过 C 语言,并且碰巧读过那本经典的 The C Programming Language(K&R);假如你使用 Linux,并且碰巧用过 AWK;或者你不小心学了一门编程语言,并且碰巧你学会写的第一个程序叫做“Hello, world”,那么你都应该感谢 Kernighan。没错, K&R 和 AWK 中的 K 都是代表 Kernighan,而且 Kernighan 是第一个使用 “Hello, world” 的人

2. 一个 Pic 的简单例子

Pic,是 Kernighan 写的一个图像排版软件,它主要用来作为 troff 中图片的预处理工具。简单点儿来说,pic 就是用来画特定一类图的工具,比如流程图、状态图、Petri~网、化学分子式等等。就像可以用写代码的方式写文档一样(TeX),我们也可以用写代码的方式画图,pic 就是这样一种语言。我们来看一个简单的例子(来自 More Programming Pearls):

.PS
ellipse "Source" "Code"
arrow
box "Compiler"
arrow
ellipse "Object" "Code"
.PE

将上面代码保存成一个 compiler.pic 文件,然后执行下面命令:

$ pic compiler.pic | groff | ps2eps > compiler.eps

我们就能得到名为 compiler.eps 的图像,下面这张图是使用 convert 命令从 compiler.eps 转换成的 png 图像:

Compiler(无法看到此图,可能因为您无法连接国外网站)

其实 Linux 下的 pic 是 GNU 版本的 gpic,groff 也是 GNU 版本的 troff,它们是都是被 GNU 重写了的自由软件版本的 pic 和 troff。

上面编译的步骤是,先用 gpic 将 compiler.pic 预处理为 troff 文档,然后用 groff 生成 ps 文件,然后用 ps2eps 将 ps 文件转化为 eps 图片。由于它们都是从标准输出打印文件,所以我们就用管道将这三个命令连接了起来,并且最后将标准输出重定向到 eps 文件。

3. 一个复杂的例子

看到这些,可能有人就怀疑说,这图很简单嘛,用 Photoshop 或者 GIMP 也花费不了多长时间,我干吗要再去学一门语言呢!那么,你尝试用 Photoshop 画一下下面这张图片。

Chemical Structure(无法看到此图,可能因为您无法连接国外网站)

画这张图的代码如下:

.cstart
    R:  benzene pointing right
        bond left from R.V4 ; HO
        bond -150 from R.V3 ; CH3O
        bond right from R.V1 ; C
        double bond up from C ; O
        bond right from C ; N
        bond 45 ; C2H5
        bond 135 from N ; C2H5
.cend

看到这,可能有人感叹说,哇,pic 真厉害!错(or 半错?),上面这段代码不是 pic 语言,而是 chem 语言。pic 是 troff 的预处理器,而 chem 又是 pic 的预处理器。所以上面这段代码的编译命令是:

$ chem.pl chem.chem | pic | groff | ps2eps > chem.eps

chem.pl 先把上面代码转换成 pic 语言,然后再用 pic 进行处理。

4. 关于 chem 的典故

关于 chem,还有一段典故:

话说当年(别问我,我不知道是哪一年)一个星期一的下午,Kernighan、 Jon Bentley 和一个同在贝尔实验室的化学家 Lynn Jelinski 闲扯了一会儿,Lynn 就向他俩抱怨在文档中插入化学结构式实在太麻烦了。正好 Kernighan 刚写了 pic,就想,咦,这不可以用 pic 画吗?于是那天晚上 K&B 就写了一个简单的前端。Lynn 一看,哎,不错!他们仨花了一个星期时间完成了 chem 语言,还在 Computers & Chemistry 杂志上发了篇 paper,名字叫做:Chem - a program for phototypesetting chemical structure diagrams。

别着急,还没完。话说当年(1990) GNU 要搞自由软件版本的 troff,开始重写 troff,把前端中的 pic, tbl, soelim 和 eqn 都重写了,就剩下画数学公式的 ideal 和 chem 没有重写。到现在你看你自己 Linux 中的 info groff,在 Introduction->Preprocessor 的最后还能找到:“There are other preprocessors in existence, but, unfortunately, no free implementations are available. Among them are preprocessors for drawing mathematical pictures (`ideal') and chemical structures (`chem').“

时间慢慢到了 2006 年 10 月 19 号,一个叫 Bernd Warken 的小子在 groff 的邮件列表中吼了一嗓子:哈哈,我用 Perl 重写了 chem!然后维护者 Werner LEMBERG 大叔就说:Great! 才有了我上文中的 chem.pl,原始的 chem 是用 AWK 语言写的。但是 chem.pl 到现在还没有到 groff 的正式发行版中,原因很囧,groff 在 2006 年 10 月 16 号以后还没有 release 新版本。所以如果你想使用 chem.pl,只能到 groff 的 cvs 仓库下载:http://cvs.savannah.gnu.org/viewvc/groff/contrib/chem/?root=groff

5. 在 LaTeX 中使用 pic

上面只是简单介绍了一下用 pic 做 eps 图片的过程,但是 pic 还可以用到 LaTeX 中,这样简单的图片就直接在 TeX 里写了。比如我们的第一个图片,就可以这样在 TeX 中使用:

\documentstyle{article}
\begin{document}
  \newenvironment{centergpic}{}{\begin{center}~\box\graph~\end{center}}
     \begin{centergpic}
.PS
ellipse "Source" "Code"
arrow
box "Compiler"
arrow
ellipse "Object" "Code"
.PE
     \end{centergpic}
\end{document}

.PS 和 .PE 一定要在行首。将上面文件保存成 compiler.tex,用 pic 预处理一下,再用 latex 编译:

$ pic -t compiler.tex > compiler1.tex
$ latex compiler1.tex
$ dvipdfmx compiler1.dvi

就能生成含有该图的 pdf 文件。

PS:若有人对 Pic 感兴趣,请阅读 Eric S. Raymond 大叔写的 “Making Pictures With GNU PIC”。你没看错,就是写《大教堂和集市》和《Unix 编程艺术》那个 Raymond 大叔。你也不用下载它,到 /usr/share/doc/groff/ 下面找一找吧,那个叫做 pic.ps.gz 的。

Aspell: 程序员的拼写检查利器

作为一个程序员,尤其是非英语母语国家(ESL or EFL)的程序员,写出漂亮的注释可能要比写出漂亮的代码更难。就比如 Eric 的“来自英语母语国家的”女友就有 “Programmers are English-challenged.“ 的评论。

那么如何在程序的注释中避免犯一些低级语法或者拼写错误呢?Eric 也在 Some useful tools for you to write English articles on Linux 中推荐了几个小工具。我这里算拾人牙慧,稍微写一点儿我非常欣赏的 Aspell 拼写检查工具。

Aspell 是一个强大的拼写检查工具,尤其是对于程序员来说。在 Linux 下,大部分程序员应该是用 Vim 或者 Emacs 写代码,它们有内建拼写检查功能,比如 vim 可以用 :setlocal spell spelllang=en_us 开启对美式英语的拼写检查。不过很少人会安装或者使用拼写检查功能,不是每个人都喜欢写代码时面对一堆高亮的词组(当它们不仅检查注释时,哦,天那!)。幸运的是,我们有 Aspell。

Aspell 使用方法非常简单,比如只想检查 C 或者 C++ 风格的注释和字符串中的拼写错误,就可以用这样的命令:

$ aspell --mode=ccpp -c test.cpp

终端里就会列出一个一个注释中的错误,并给出修改意见。接下来的工作就很简单了,按照窗口下面每个键对应的功能,选择更换单词或者忽略该单词。如下图所示:

Aspell 拼写检查工具(无法看到此图,可能因为您无法连接国外网站)

Aspell 还有更多模式,比如检查 HTML, TeX, XML, Perl 等等一些文档或程序中的拼写,更多内容就请看 Aspell 的帮助吧。

Aspell 的用户习惯保存在 ~/.aspell.en.prepl 和 ~/.aspell.en.pws 两个用户自定义替换和忽略单词列表里,可以通过备份或者修改这两个列表来改变 aspell 对某些单词拼写检查的策略。

Feedburner 订阅数图标显示解决办法

很多人以前都用过 Feedburner 烧制自己的 rss feed,但是由于众所周知的原因,Feedburner 的 rss 输出在中国网,封天下无法访问了(不信您可以点击一下我博客右侧的 Feedburner 图标)。虽然用 Google Reader 订阅 Feedburner 的 feed 仍然不受影响,但是博客订阅数图标无法正常显示,所以很多人好奇我是如何让 Feedburner 订阅数图标仍然显示在博客侧栏的

更新:刚才写完,我想看看 feed 有没有更新,忽然发现 Feedburner 的 rss 可以访问了,试了试订阅数图标,也能正常显示了。GFW 打盹了?我说这几天 Feedburner 订阅数增加那么快呢。总之我这篇文章算是白写了......呜呜呜呜,没有提前重现问题的后果。

又更新:12 月 23 日,我又无法访问 feedburner 的 RSS 了,才一天那!看来我这篇文章还算没有完全白写,不能幻想 GFW 的仁慈。

其实我以前是用的这篇博客里的方法。这个方法要求你有个国外主机空间,碰巧我能使用师兄的空间,把那篇文章中的 feedburner.php 上传到空间里就可以使用了。

但是使用过程中我发现这个方法有个很严重的问题:不支持并发访问。这是由于它的方法太生硬,先读取自己文件的内容,如果文件中写的时间比当前时间早 4 个小时,就下载新的订阅数图标,重写自身文件(修改更新时间那一行),并将订阅数图标附在文件最后。注意到这里,它会重写自身文件,一个 php 文件读取自己,改一行再重新写入自己,那么如果多个用户同时访问该文件,那不就乱套了?

所以我对它进行了修改,改为一个相对干净的方法:抓取订阅数统计图标保存为一个 gif 文件,每次访问 php 文件时,php 去判断当前时间与该 gif 文件最后修改时间的差,如果大于过期时间,就重新抓取订阅数统计图标更新 gif 文件,最后将访问重定向到 gif 文件。点击这里 http://solrex.org/feedburner.php 查看效果。

具体 php 代码如下(其实我本想用 file_get_contents 函数的,但发现不好用,只好还用这个原来的 httpSocketConnection 函数了,显得冗长了些):

<?php
$username = "username"// Feedburner account name.
$expire_time = 3600;   // Expire time(in second, 3600s = 1 hour).

$fb_url = "feeds.feedburner.com";
$gif_path = "/~fc/".$username."?bg=99CCFF&fg=444444&anim=0";
$localfile = "fb_".$username.".gif";

if(!function_exists('httpSocketConnection')){
  function httpSocketConnection($host, $method, $path, $data)
  {
    $method = strtoupper($method);
    if ($method == "GET") {
      $path.= '?'.$data;
    }
    $filePointer = fsockopen($host, 80, $errorNumber, $errorString);
    if (!$filePointer) {
      return false;
    }
    $requestHeader = $method." ".$path."  HTTP/1.1\r\n";
    $requestHeader.= "Host: ".$host."\r\n";
    $requestHeader.= "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0\r\n";
    $requestHeader.= "Content-Type: application/x-www-form-urlencoded\r\n";
    if ($method == "POST") {
      $requestHeader.= "Content-Length: ".strlen($data)."\r\n";
    }
    $requestHeader.= "Connection: close\r\n\r\n";
    if ($method == "POST") {
      $requestHeader.= $data;
    }          
    fwrite($filePointer, $requestHeader);
    $responseHeader = '';
    $responseContent = '';
    do {
      $responseHeader.= fread($filePointer, 1);
    }
    while (!preg_match('/\\r\\n\\r\\n$/', $responseHeader));
    if (!strstr($responseHeader, "Transfer-Encoding: chunked")) {
      while (!feof($filePointer)) {
        $responseContent.= fgets($filePointer, 128);
      }
    } else {
      while ($chunk_length = hexdec(fgets($filePointer))) {
        $responseContentChunk = '';
        $read_length = 0;
        while ($read_length < $chunk_length) {
          $responseContentChunk .= fread($filePointer, $chunk_length - $read_length);
          $read_length = strlen($responseContentChunk);
        }
        $responseContent.= $responseContentChunk;
    &nb
sp;   fgets($filePointer);
      }
    }
    return chop($responseContent);
  }
}

function get_fbcount($host, $path, $file)
{
  $content = httpSocketConnection($host, 'GET', $path, NULL);
  $fp = fopen( $file,"w" );
  fwrite($fp, $content);
  fclose($fp);
}

if (file_exists($localfile)) {
  $last_modified = filemtime($localfile);
  if ( date('U') - $last_modified > $expire_time) {
    get_fbcount($fb_url, $gif_path, $localfile);
  }
} else {
  get_fbcount($fb_url, $gif_path, $localfile);
}
Header("Location: $localfile");
?>

将上述代码保存为一个 php 文件,比如 fb_username.php,修改用户名和过期时间,上载到国外或者港台能正常访问 Feedburner 的主机空间中,您就可以在网页中用:

<img src="http://your.host.domain/fb_username.php" style="border: 0pt none ;" alt="Feedburner">

来引用 Feedburner 订阅计数图标了。由于这个 php 脚本按照用户名保存 gif 图标,您可以在一个服务器上为多人提供引用,只需修改 php 文件的 usrname 一项,并上传为另一个 php 文件,您也可以将这两个变量改为 php 文件的参数(不建议这样做,因为会被别人利用)。

您也可以在这里下载到 feedburner.txt,要记得将其后缀改为 php 哦。

即时通信软件协议

我十多天前发表的那篇《定制自己的免费天气预报短信》,相信引起了不少人的兴趣。这篇文章是为那些希望更深入了解即时通信协议,并想做一些 hack 工作的同学提供的一个小索引。

关于飞信的协议,可以参考下面两篇文章,某人的博客和一个飞信插件的源代码:

[1] 付安民, 张玉清. 飞信即时通监控系统的设计与实现. 计算机工程, 2008, 34(13).

[2] 付安民, 张玉清. 即时通实时监控系统的设计与实现. 通信学报, 2008, 29(10).

[3] http://hi.baidu.com/nathan2007/blog/category/飞信协议分析

[4] Pidgin 飞信插件

关于其它常见 IM 的协议,可以参考下面这篇文章和一个开源软件的源代码:

[5] R.B. Jennings, E.M. Nahum, D.P. Olshefski, et al. "A study of Internet instant messaging and chat protocols," IEEE Network, vol.20, no.4, pp. 16-21, 2006.

[6] Pidgin - multi-protocol Instant Messaging client that allows you to use all of your IM accounts at once.

我真的很希望飞信的 Pidgin 插件能更成熟,比如群功能之类的还要完善,最好以后能 merge 到 Pidgin 中,这样我就不用在 Pidgin 之外再开着一个 Libfetion Linux 客户端了。而 Libfetion 仍然也有亟待完善的地方,比如群管理员无法成功发送群消息。如果聪明的您能完成一个近于完美的 Linux 飞信客户端,我真的要谢谢您呢!