2012年12月14日星期五

A Quick Preview of mruby

某常跟我提到了mruby,正好之前从来没接触过,于是弄回来看了看。我的主要目的是寻找一种在OpenWrt之类受限环境下性能不错且易用性更好的lua替代品,以下是一些初步感受。

功能性

mruby的readme里有这么一句:

mruby is the lightweight implementation of the Ruby language complying to (part of) the ISO standard

看看ISO/IEC 3017:2012就可以发现,ruby标准包含的内容很少,而mruby仅仅是标准的part of,可以用的东西就更少了。

目前的mruby没有标准中包含的Regexp、IO和File,不是标准的Socket和OpenSSL也没有,Net当然就更没有了。难怪某常说mruby有些“太小”,这小得稍微都有些过分了…

iij维护了一个加强版的fork,Regexp和IO都可以用了,不过Net仍然是没有。在mruby-curl一类的东西出现之前,恐怕只能用system去调用外部命令了吧。

编译

mruby编译起来很容易,不管是mingw还是linux,即使是iij的fork加上mruby-sqlite3,编译起来也不困难。交叉编译则要稍微麻烦一点,需要先编译出一个x86版本,然后再修改Makefile。总的来说编译难度不高,即使是交叉编译,只要对Makefile略有了解的人都可以完成。

扩展性

matz说了,mruby里不会有require。个人认为,这实在是一个馊主意。将所有代码都编译进同一个目标文件,不管是加载时间还是内存占用都会增加。倘若某个闭源程序使用mruby作为内嵌脚本语言,用户想要扩充功能也是不可能的了。

iij的fork有require,只是暂时没看见有什么可以使用这一特性的扩展。希望iij可以长期维护他的fork吧,真心觉得matz有时候挺昏庸的。

性能

之前写过一个spider,用来爬新浪微博的内容,前端是一个用lua写的cgi,从sqlite3数据库中读出内容生成json并输出。将其用mruby改写后,在我的路由上跑,测试结果如下:

LangTime
lua 5.1.40.06s
mruby0.07s
mruby-iij0.12s
ruby 1.9.2p02.21s

因为不是什么严谨的测试,代码我就不贴了。单就这一个任务而言,mruby和lua的速度差不多。mruby-iij的可执行文件是835.2K,mruby的则是489.2K,速度比和体积比都是大约1.7,可见可执行文件的体积的确会对执行速度产生影响。至于cruby,实在是有点太慢了。

接着我跟某胖子要了一段数值运算的代码,打算试试PC上跑起来的结果又是如何。这是某胖给我的python版:

from math import *

def getPrimes(up):
    p=[True]*(up+1)
    for i in range(4,up+1,2):
        p[i]=False
    for i in range(3,int(sqrt(up))+1,2):
        if p[i]:
            j=i
            while True:
                j+=i
                if j>=up:
                    break
                p[j]=False
    return [i for i in range(2,up+1) if p[i]]

getPrimes(31415927)

lua版:

function getPrimes(up)
    local p = {}
    for i=1,up do
        p[i]=false
    end
    for i=4,up,2 do
        p[i]=true
    end
    for i=3,math.floor(math.sqrt(up)),2 do
        if p[i] then
            local j = i
            while true do
                j=j+i
                if j>=up then
                    break
                end
                p[j]=false
            end
        end
    end
    local result = {}
    for i=2,up do
        if p[i] then
            table.insert(result,i)
        end
    end
    return result
end

getPrimes(31415927)

我改的mruby版:

def primes(up)
    a = [true] * (up+1)

    i = 4
    while i <= up do
        a[i] = false
        i += 2
    end

    n = Math.sqrt(up).to_i
    i = 3
    while i <= n do
        if a[i] then
            j = i
            while true do
                j += i
                break if j >= up
                a[j] = false
            end
        end
        i += 2
    end

    r = []
    2.upto(up) { |i| r.push(i) if a[i] }
    return r
end

primes(31415927)

我改的ruby版:

def primes(up)
    a = Array.new(up+1, true)

    (4..up).step(2) { |i| a[i] = false }

    (3..Math.sqrt(up).to_i).step(2) do |i|
        next if not a[i]
        j = i
        a[j] = false while (j += i) < up
    end

    r = []
    2.upto(up) { |i| r.push(i) if a[i] }
    return r
end

primes(31415927)

我改的node.js版:

function primes(up) {
    var i = up + 1;
    var a = new Array(i);
    while (--i >= 0) a[i] = true;

    for (i=4;i<=up;i+=2) a[i] = false;

    for (i=3;i<=Math.floor(Math.sqrt(up));i+=2) {
        if (!a[i]) continue;
        j = i;
        while ((j += i) < up) a[j] = false;
    }

    var r = [];
    for (i=2;i<=up;i++) {if (a[i]) r.push(i);}

    return r;
}

primes(31415927);

测试环境为Ubuntu 12.04,AMD Phenom II X4 955,结果如下:

LangTime
python 2.7.316.549s
pypy 1.8.03.642s
lua 5.1.47.205s
luajit 2.0.0-b93.678s
node.js 0.6.123.851s
ruby 1.9.3p32716.056s
ruby 1.9.3p327(mruby版)15.888s
mruby21.811s
mruby-iij23.044s

在PC上跑,磁盘IO对性能的影响比较小,所以以上结果应该更接近各种语言的真实水平。至少单就数组、循环和逻辑判断这几项的性能来说,mruby还差得远,连cruby都比不了,更不要说lua了。

另外还可以看出,mruby对功能的裁剪导致语言的表达能力下降了很多。其实只是少了Range的step而已,最后写出来的代码可读性和长度都比ruby版的要差。

Bug

up = 31415927
if ARGV[0] == 'new' then
    a = Array.new(up+1, true)
else
    a = [true] * (up+1)
end

以上两种数组初始化的方法在cruby中性能差不多,大概是0.115s和0.130s的样子,第一种要略快一点点。但在mruby里,第一种需要4.650s,第二种也要0.363s,明显有什么地方不对劲。

a = [0] * 11
i = 0
begin
    a[i] += 1
end while (i += 2) <= 10

b = [0] * 11
i = 0
b[i] += 1 while (i += 2) <= 10

p a
p b

cruby中,执行以上代码最后输出的两个数组内容是不一样的,区别在于第一种循环是先执行后判断,而第二种则是先判断后执行。mruby中,这两种循环的写法都是先判断后执行,明显mruby的处理有问题。

印象

其一,没有require绝对是昏招;其二,网络访问这么基础的功能都没有;其三,欠缺优化;其四,随便用用就让我碰上bug了…

ruby社区似乎没有Mike Pall这种monster,也没抱上Google一类的粗大腿,再加上我总觉得喜欢ruby的都是懒人,想让ruby快起来恐怕没那么容易。mruby究竟想干嘛?有点看不出来的说。

2012年11月30日星期五

OpenWrt Attitude Adjustment

换上12.09差不多两个月了吧,各方面都很满意。尤其是近几天折腾了一下RTD1186,更觉得有完整的第三方支持是多么幸运多么可贵的一件事。

这是我的补丁包:https://www.boxcn.net/s/ybou0cl9yekzd2x7e5mh

基于svn://svn.openwrt.org/openwrt/branches/attitude_adjustment,r34080,没改多少东西:

./nls-cp936.patch
./package/exfat-utils/Makefile
./package/exfat-utils/src/Makefile
./package/exfat/Makefile
./package/exfat/src/Makefile
./toolchain/uClibc/patches-0.9.33.2/999-posix_fallocate.patch
./feeds/packages/net/aria2/Makefile
./feeds/packages/net/autossh/files/autossh.config
./feeds/packages/net/autossh/files/autossh.init
./feeds/packages/net/openssh/patches/999-env-pwd.patch
./feeds/packages/net/vsftpd/patches/999-ssl.patch
./feeds/packages/net/vsftpd/Makefile
./target/linux/ar71xx/patches-3.3/999-tl-wr941nd-usb.patch

除了nls-cp936.patch需要手工打补丁外,其他文件覆盖即可。

busybox

busybox的中文显示需要特别配置一下,首先是打开unicode支持,其次要将"Range of supported Unicode characters"的值改为0,"Allow wide Unicode characters on output"改为yes。另外vi也可以通过调整配置使之能够显示中文,但也仅仅是显示而已,输入和删除的时候仍然会遇到光标的问题。

nls-cp936.patch

增加nls_cp936.ko,mount的时候可能会用到。

exfat、exfat-utils

虽然放在这里,但不知道是usb供电不足还是代码稳定性不佳,总之我不建议挂OpenWrt上的移动硬盘使用exfat。ntfs我也试过了,跟exfat一样,最后我用的还是ext3。

uClibc

增加了fallocate,据说对samba的性能有帮助,据说。

aria2

OpenWrt挂迅雷离线就靠他了。

autossh、openssh

让openssh可以从环境变量中获得登录密码,出墙需要用,详细说明见之前写的《OpenWRT AutoSSH》

vsftpd

打开ftps支持。如果要给远端共享文件的话,或许用得上。内网还是不要用了,路由的cpu扛不住,会导致速度下降很多。

999-tl-wr941nd-usb.patch

我的路由改了个usb口出来,官方的代码没有提供支持。

2012年11月28日星期三

RTD1186折腾记之修改根分区格式

这个盒子是一个朋友买来之后觉得没用暂时放我这里的,Realtek RTD1186方案,主频750MHz,内存512M,闪存4G。单就硬件而言,比我的路由强很多,只不过没有OpenWrt之类完整的第三方支持,所以干什么都需要自己来。

本地端adb connect 192.168.1.186,然后打开adb putty,Host写transport-any,Port保持5037,居然就这么连进去了。固件本身是已经root了的,倒是少了很多麻烦。只是随便乱逛了一下之后,我有一种头痛的感觉。

从/和/system看来,这像是个标准的Android,只是目录结构和文件各种乱。根目录下居然放了三个内核模块。/system/rtk_rootfs/usr/local/bin/下面有个44.1M大的opt2.8.tar。/system/rtk_rootfs/bin/opt和/system/rtk_rootfs/usr/local/bin/opt下各有一套optware,区别仅仅是前者比后者多了两个可执行文件。至于man、include一类,我都不想提了…

想要删改,第一个问题是/system的分区格式。原厂用的是只读的squashfs,随便改点什么都要刷一次系统,这也太累了,改掉。

刷固件并不需要ttl,但是为了能看见自己都干了啥,ttl还是需要的。

将盒子拆开,电路板上只有一个六针的接口是空着的,标注是J1到J6。用万能表在断电的时候测针脚与高频头外侧之间的电阻,J1是0Ω,J4和J5绝缘,其他三针不为0。上电再测试直流电压,J1为0V,J2为2.8V,J3示数不稳定,J6为3.3V。这应该就是ttl针脚了,J1是GND,J2是RX,J3是TX,J6是VCC。

在最终达到目的之前,我遇到了蛮多问题的,也花了不少时间,这里就不写了,直接上结果。

从ttl看到的启动信息里有这么一段:

One H27UBG8T2A chip has 1 die(s) on board
nand part=H27UBG8T2A, id=add7949a, device_size=4294967296, chip_size=4294967296, num_chips=1, page_size=8192, isLastPage=1, eccBits=24

H27UBG8T2A就是闪存芯片的型号,Page尺寸上面就有,8192字节。以型号为关键字搜到了一个pdf技术文档,其中有写OOB的尺寸,448字节。这两个数据在制作img的时候需要。

从官方下回来的install.img中把mkyaffs2image和squashfs1.img解出来,然后:

unsquashfs squashfs1.img
mv squashfs-root system
mkyaffs2image -f -c 8192 -s 448 ./system yaffs2_root.img

改名的那一步不是多余的,mkyaffs2image那一句的./也不是多余的,神奇的设计。

虽说squashfs是压缩过的,可192M的img转换成yaffs2就膨胀到1.2G,可见官方的固件里有多少垃圾。不过这跟8K的Page尺寸也有关系,这块芯片显然更适合用在DC一类的设备里,当系统盘存小文件实在太浪费空间了。

将原厂install.img里的squashfs1.img替换为自己做的yaffs2_root.img,打开configuration.xml并修改:

--- configuration.xml.orig
+++ configuration.xml
@@ -40,9 +40,10 @@
                 <fileName>package5/bootloader.tar</fileName>
                 <version>N/A</version>
             </image>
-            <image type="squash">
-                <fileName>package5/squashfs1.img</fileName>
+            <image type="yaffs2">
+                <fileName>package5/yaffs2_root.img</fileName>
                 <mountPoint>/</mountPoint>
+                <sizeBytesMin>2147483648</sizeBytesMin>
             </image>
             <image type="yaffs2">
                 <fileName>package5/yaffs2_2.img</fileName>

我把新的/system设置为2G,这样/data还有1.7G。其实MIPS跑安卓压根没什么用,/system再改大点也没关系。

把改好的install.img放到一个U盘的根目录下,按住盒子前面板的reset,或连上ttl的时候按住空格键上电,开始刷固件。bootloader会自动构造传递给内核的参数,原厂init会依序尝试squashfs和yaffs2,所以没什么要改的了。刷完之后会自动重启,挂载分区处不知道为什么需要花很长时间,除此之外一切正常,/system已经变成可写的yaffs2了。

2012年9月12日星期三

Sync Tweets

我对Twitter之类的东西其实没太大兴趣,之所以用,有那么点告诉某些人我还活着的味道。可是这里的某些人,有的用Twitter,有的用人人,有的用微博,于是同步就成了个问题。

刚开始用的是Ping.fm,现在死掉了。然后用的是Weiboto,现在也死掉了。之前在安卓里用的享拍微博通,服务谈不上稳定,iOS版却还要卖钱。好吧,我自己轮一个。

GAE有免费配额,实现Jabber机器人很简单,而且访问Twitter没有墙的问题,首选。打算同步的服务有四个,豆瓣、人人、Twitter和微博。只因为是自己用,希望事情越简单越好,不想去处理复杂的OAuth,所以必须要想办法拿到长期有效的授权。

豆瓣的v1版API用了GData,处理起来各种麻烦。v2还算靠谱,无非是反编译客户端拿到个能用密码换Access Token的Key。只不过/service/auth2/token后面不能加上/,而/shuo/statuses/后面的/却不能少,大概就是这样。

人人的API设计很妙,比如status.set返回值只有“{ "result":1 }”,连个id都不给。如果想删掉,貌似只能先再来个status.gets。验证方式虽然也提供了OAuth2,可除了验证地址,其他API地址都不是Https,安全性也不好。单从API来看,完全不像一家有前途的公司。

Twitter至今还在用OAuth1,好处就是Token长期有效,只不过因为OAuth1的复杂性导致要多一个第三方库。使用API时也遇到了点问题,如果需要用POST方法请求数据,只有把数据作为参数加在URL上才能成功,POST Data的方式会失败。另外,四家的官方API Key中,Twitter的藏得最深。官方Android版中的API Key是一个bytes数组,通过算术运算才能获得真正的Key和Secret。

新浪,嗯,最敖娇。之前曾经研究过,新浪微博的官方客户端用的不是开放版API,而是另外的一套东西。因为有一些研究成果,本来是打算用的。可是部署到GAE上却报错,搜了一下发现是登录保护的问题。可我的测试账号中,压根就没启用这破玩意儿。最后发现,即使不开启登录保护,只要登录IP是国外的,一样需要输验证码。不得已,还是找了个高权限的第三方API Key,老老实实用开放版的API。

虽然最后是做到了能用的程度,可光是折腾四家的API就已经花了不少精力,有一种我这是何必的感觉…

2012年8月27日星期一

PhenomMsrTweaker

或许还是主板太老太次的关系,最近发现即使在BIOS里打开Cool&Quite,CPU的主频仍然不会自动调节。

搜了一下,找到了PhenomMsrTweaker。用倒是能用,不过却有点问题。首先是P0到P4一共五档,我这里却只有P0和P1两档有效。P0设置为16X/1.25V,P1设置为4X/0.8V,重启后空载时CPU-Z显示的主频的确是800MHz,但核心电压却是1.032V。

虽然机箱出风口是凉快了些,但能不能省点儿电实在不确定。近来开着机器下东西的时候挺多,这100多W的,有点儿罪恶感的说。

2012年8月14日星期二

CMCC On Licensed EVDO Version iPhone 4S

因为某些不可抗因素,我终于还是用上了iPhone 4S,国行C网版。不过在为了网速老老实实换号之前,我尝试了另外一个法子。

双模卡解锁

CDMA是一种很悲催的制式,因为高通死要钱,导致现在CDMA终端很少,网络覆盖也很不全面。为了解决全球漫游的问题,于是就出现了三频机和双模卡这类东西。所谓双模卡,指的是一张卡上有两个号码,C网号和G网号各一个。有C网覆盖的地方用C网,没有则尝试用G网。

电信的龙卡就是一种双模卡,其内有一个IMSI以46003开头的C网号和和一个IMSI以20404开头的G网号(该编号属于荷兰沃达丰)。在大陆以内使用前者,出国之后使用后者。

经过前辈们的试验发现,IMSI以46003和20404开头的卡均可激活C网版国行4S。如果想要在国C上使用移动或联通号,可以制作一张特别的双模卡,写入一个20404开头的C网号和一个移动或联通的G网号,以达到解锁激活的目的。

制作双模卡首先需要破解G网号。召唤某胖纸帮忙逛淘宝选货,凤凰读卡器11.5人刀,电信标双模卡17.5人刀。卡比设备还贵,有点小意外。据传不好的空白双模卡有被烧的风险,不过我买的这种倒是没看见什么负面反馈。至于“4S解锁专用”的大鹏4S和谐卡,比我买的这种贵不少,听了某人的,没选。

(电信标双模卡SIM卡应用中有一项“UIM卡信息”,点击显示“散采客户专用卡”。散采对应的或许是集中采购?这么说该卡未必真跟电信有关系。)

我手上的移动SIM卡是08年办的雅斯拓,不过ICCID内含的制卡年份是84年,很奇葩。用悟空F9解卡,过了半个来小时感觉不太对劲,于是拔下来用橡皮擦了一下金手指重新开始,不到8分钟就解出来了。总数12535,时速113564,似乎是运气很不错的样子。

(烧写SIM卡我不熟,不过我觉得还是有不少安全隐患。首先是写入卡中的数据能不能被直接读出来?我搜了一下,一卡多号的魔术卡可以用SimEasy读取出写入的KI,至于单号卡和双模卡是否可以尚未确认。其次,PIN可以在手机上修改,但PIN被锁可以用PUK解锁,而空白卡的PUK都是很容易被查到的默认值。目前我能确定双模卡的G网号PUK是可以改的,但C网号可不可改,同样尚未确认。基本上,用烧写的手机卡就要注意安全,最好不要掉…

按照网上的说法是先写C网号,却没有解释原因。其实双模卡中虽然有两个号码,但却只有一个ICCID,后写的那个会覆盖掉之前写入的那个。听说C网定制版的4S不但锁网而且锁号,如果锁的是ICCID,或许用这个法子也可以解锁的说。

写C网号的软件是卖家附盘里的UIM Card Personalize(091026 CDMA EVDO)。因为可执行文件特意改名为9600.exe,所以我也老老实实把读卡器的串口速率改了。先Read UIM,将IMSI前几位改为20404就可以写卡了,如果少填了什么必备的数据程序会提示的。

写G网号我用的也是卖家给的程序,文件名叫“双模牛牛版本.exe”,窗口标题“SIM卡写号工具 专业版 V1.0”,可以改PUK。如果是手工输入数据,记得该软件SMSP字段需要+号。

写完的卡我放进中兴U880试过,发现无法接入网络,即使手工选择运营商也不行。再试Nokia N78,可以正常使用。继续试C网的华为C2829,居然还有信号,虽然电话显然不可能打得通。

全部搞定之后剪卡插入4S第一次开机进入并向导,激活正常。不过这毕竟是非常规手段,电话短信是没问题,想要用好还有得折腾。

(去官网查询保修服务和支持期限,发现苹果似乎是从第一次激活的日期开始保修一年,至少我手上这一台是。)

升级、越狱

本机自带系统是5.1(9B179),爬了很久的文我也没找到完美越狱的工具,貌似5.1的完美越狱从来就没出现过?最后还是先升级为5.1.1(9B206),才完成了越狱过程。

越狱工具我用的是绿毒家的Absinthe v2.0.4,redsn0w名气虽然大,可readme里啥有价值的信息都没写,所以。而且用Absinthe来JB那叫一个简单,一次Click就搞定了。

软件源随便吧,威锋网、维维网、178,之类的。

OpenSSH、SBSettings、Insomnia、iFile、Nano,还没开始干正事儿呢,就装了一堆东西了Linux…

问题

使用双模卡解锁的4S国C主要存在三个问题:APN等运营商相关参数不正确;通话记录、短信、来电不显示姓名;本机号码未知。前两个问题都有办法解决,后一个则暂时无解。

运营商参数

4S的运营商设置文件都保持在/System/Library/Carrier Bundles/iPhone/目录下。首先,4S读取SIM卡IMSI前五位,然后与Unknown.bundle/carrier.plist中MandatoryVerif/SIMs数组的内容进行比对。若存在对应元素,则直接从同名子目录下读取运营商设置文件。以我的机器为例,激活卡IMSI前五位是20404,而20404子目录实际上是一个软链接,指向的是Vodafone_nl.bundle。

虽然直接修改修改Vodafone_nl.bundle下的文件也可以达到目的,不过还有个更好的法子。

(据说修改运营商相关文件,需要先装补丁。维维网的源里有,“CDMA iPhone4S 5.1.1 CC破解”。不装会怎样,我没试过。)

打开Unknown.bundle/carrier.plist,MandatoryVerif/SIMs数组中插入一条“20404_ID-898600”的数据。ID-898600表示ICCID以898600开头,这个编码属于移动。

cd "/System/Library/Carrier Bundles/iPhone/"
mkdir 20404_ID-898600
cp 46000/*.* 20404_ID-898600/

在20404ID-898600子目录中,打开version.plist,BuildVersion、CFBundleShortVersionString和CFBundleVersion都改成一个比较大的数,防止运营商数据更新。Info.plist中的CFBundleShortVersionString和CFBundleVersion也一样。然后打开carrier.plist,SupportedSIMs中增加一条“20404ID-898600”。

修改运营商数据可以做不少事,这里能查到一部分资料。不过忙活这么半天,无非就是避免换卡之后APN设置被覆盖。如果不会频繁换卡,也不在乎关于本机中显示一些沃达丰的内容,如此折腾其实没有多大意义,填一下APN就好了。

通话记录、短信、来电不显示姓名

/System/Library/Frameworks/UIKit.framework/PhoneFormats/下有一个UIMobileCountryCodes.plist文件,该文件的内容决定了电话号码的相关格式。

文件中的三位数字Key值,应该就是MCC,也就是ICCID的前三位。以我的机器为例,用于激活的ICCID是204开头,将该文件中的204由默认的nl改为cn即可。

本机号码未知

网上我看到过的解决方案全都试过,均以失败告终。因为本机号码未知,导致对方联系人不能用手机号码,只能用email地址发起iMessage对话

用旧C网手机写入本机号码的法子,我试过华为的C2829和联想的i909。

经查询,我的号码有向00447786205094、00447537410247、00447537410257、00447537410207等号码发送过短信,但并没有收到回信。

2012年8月8日星期三

WMV2MKV

前几天下了个WMV,再次体会到WMV的Seek性能究竟有多糟。突然想起某胖纸折腾过这个问题,于是仔细看了看,于是就有了这么个东西。

本程序使用了系统内置的分离器和Matroska Muxer,只是重新封装,所以没有视频和音频的质量损失。Matroska Muxer优先使用系统已注册了的,如果没有注册则动态加载MatroskaMuxer.ax。

嗯,以上。

使用方法:wmv2mkv in.wmv out.mkv

2012-08-08 v0.3.1

https://www.boxcn.net/s/24db9bb76ae850fbca89

2012年7月5日星期四

Phenom II X4 955 On C.A780H

之前的CPU是Athlon64 X2 4000+,我娘淘汰给我的。撑了这么多年,实在有些受不了了。整个换I5吧,手上的东西全扔了又太可惜,于是594人刀买了个955。想着我也不去超,这一块七彩虹C.A780H应该可以对付,可事实证明,我想得太简单了…

或许是这块主板太古董了的缘故,虽然CPU型号能正确显示,但默认设置的频率却只有800MHz。痛苦的过程就不表了,最终这块CPU可以稳定跑在如下设置上:

  • Custom P-Status:Enabled
  • K10 CPU FID:X16 3200MHz
  • K10 CPU VID:1.25V
  • K10 CPU DID:Divided by 1
  • K10 CPU NB FID:1800MHz
  • K10 CPU NB DID:Divided by 1

Super PI 1M跑下来22.136秒,Windows 7体验指数7.3,基本正常。虽然折腾了一下,但AM2到AM3保持兼容性这一点上,AMD还是很值得表扬的。

2012年6月25日星期一

rb-readline

不知道从哪个版本起,RubyInstaller的irb就开始有一个Bug,表现为输入或粘贴某些中文字符进去的时候,irb会无提示退出。因为不常遇到,所以一直都懒得查。前两天精神来了就追了一下,发现问题出在rb-readline。

rbreadline.rb文件readline_internal_charloop函数,大概4635行:

rl_setstate(RL_STATE_READCMD)
c = rl_read_key()
rl_unsetstate(RL_STATE_READCMD)
# look at input.c:rl_getc() for the circumstances under which this will
#be returned; punt immediately on read error without converting it to
#a newline.
if (c == READERR)
  eof_found = true
  break
end

READERR的值是0xFE.chr,而GBK和BIG5的编码范围都包括了0xFE,于是这又是一个编码问题。Bug已经提交,我是觉得直接把整个if都删掉就好了,看作者打算怎么改吧。

2012年6月23日星期六

Homemade RaySource

好吧,我们都知道fs2you链接长什么样,自然也看得出来,fs2you://后面的部分,其实是经过Base64编码的字符串。

比如:

fs2you://Y2FjaGVmaWxlMzIucmF5ZmlsZS5jb20vemgtY24vZG93bmxvYWQvYjI3ODI1NzQ2YjgxOWYyMmY1ZGY0ZTFkNTE0NDFjNmQvJUU3JTk0JUIwJUU0JUI4JUFEJUU4JThBJUIzJUU2JUEwJTkxLnJhcnwxMjkyMTEzOQ==

经过解码,可以得到:

cachefile32.rayfile.com/zh-cn/download/b27825746b819f22f5df4e1d51441c6d/%E7%94%B0%E4%B8%AD%E8%8A%B3%E6%A0%91.rar|12921139

竖线符分隔的第一个部分看起来是一个URL,第二个部分则是文件尺寸。有时候竖线符有两个,第三部分是另外一个文件名。通过抓取客户端RaySource的数据包,可以验证以上猜测。

GET /zh-cn/download/b27825746b819f22f5df4e1d51441c6d/%E7%94%B0%E4%B8%AD%E8%8A%B3%E6%A0%91.rar HTTP/1.0
Host: cachefile32.rayfile.com
Accept: */*
Referer: 
Cookie: 
User-Agent: Grid Service 2.1.10.8366
Grid: aWQ9nc3JxpnLys4mcF9udW09MCZkX3NwZWVkPTA=
Range: bytes=0-262144

Grid又是一个Base64字符串,解码后的内容是:

"id=\x9D\xCD\xC9\xC6\x99\xCB\xCA\xCE&p_num=0&d_speed=0"

id均为8个字节,当成GBK字符解码,可以得到四个看起来像天书的中文。p_num可能是process,同时下载多个文件时会有不同的值。s_speed有可能是当前的下载速率,可我想不出为什么服务器会对这个数值感兴趣。

经验证,构造一个类似的HTTP请求,可以用别的下载工具获取文件,比如:

curl --verbose --remote-name --user-agent "Grid Service 2.1.10.8366" --header "Accept: */*" --header "Referer: " --header "Cookie: " --header "Grid: aWQ9nc3JxpnLys4mcF9udW09MCZkX3NwZWVkPTA=" --header "Range: bytes=0-12921138" "http://cachefile32.rayfile.com/zh-cn/download/b27825746b819f22f5df4e1d51441c6d/%E7%94%B0%E4%B8%AD%E8%8A%B3%E6%A0%91.rar"

正常情况下,可以得到一个206响应。只要一切正常,即可直接下载到目标文件。不过就像大家都知道的那样,网络是很不靠谱的。当下载时遭遇断线,构造正常的Range头,如“Range: bytes=1234-12921138”并不能断点续传。

关于这个问题,抓包并没有太大的帮助。RaySource发出的请求中,Range的两个数字除了0和文件的实际尺寸外,均为256KB的正整数倍,且整个Range不超过5MB。嗯,这也太奇怪了点儿。

通过各种尝试,我发现有时候Range的起始值可以是不大于文件尺寸的任意数,且只要Range不大于8000000,也可以获得服务器正确的响应。不过这个有时候,具体是什么情况,我也说不清楚…

嗯,基本上目前就是这样,问题依旧没有解决的说。

2012年6月21日星期四

Razer DeathAdder

手上的Razer Diamonback跟了我很多年,除了微动有些粘黏之外,运行状况倒是良好。只不过毕竟用了这么久,背上RAZER几个字母,都快被磨光了。因为这只鼠标是收到的一份礼物,不想完全用坏,于是就买了个新的,Razer DeathAdder。

要说我有多喜欢Razer,其实还好。我眼中Razer的缺点,在这一枚DeathAdder几乎都有体现。背面所谓的类肤材质虽然防滑,不过用久了跑不掉被磨光的命运。相对于背部,左右两侧的镜面材质又有些太滑,手感差异也太大了些。再加上手掌根部缺少支撑,还有发热,DeathAdder的握持感并没有很好。

找个不错的鼠标垫应该能解决一部分发热问题,至于别的,也只好将就了。毕竟我买的只是所谓的升级版,比起换了材质的经典版便宜差不多100块。

微动糟糕的罗技不想再碰,或许下次应该搞个微软来试试。

2012年6月4日星期一

Super Metroid

一周目4小时33分,收集率99%,应该是漏了Ridley那里的Energy Tank。以下是二周目的流程,模拟器Snes9X,PS2手柄。

整个游戏流程我抓了一百多张图,不过Blogger上传图片实在不太好用,干脆丢压缩包吧,解压密码otnth:https://www.boxcn.net/s/1fa631b7c451b5593ca7

以下100%流程没有太过逆天的Sequence Breaking,应该属于中高级难度吧。熟练一点的话,3个小时怎么都够了。

基本内容

SFC手柄除方向键外,还有八个键,本作全部用到。Select切换武器,Start进入菜单,L斜下方瞄准,R斜上方瞄准,A键跳跃,B键加速,X键射击,Y键取消。

虽说本作比GBA上的两作在武器方面更为丰富,某些地方或许是不得已,不过我还是觉得这样的键位设计很怪。例如在加速跑时经常需要射击,于是右手就需要去按手柄右侧上方和下方两个键,并没有很舒服。

本作有专门的加速键,不过跟GBA的两作相比,加速冲刺对攻关的必要性却没那么强。不但无法接力加速,而且冲刺时还会消耗HP。本作中最重要的两项技巧,是Wall Jump和Mockball。

Wall Jump的方法是,面向右边墙壁,按右键和跳跃键起跳,在贴近墙壁滚动下落时,快速按下右键再接跳跃键,即可弹墙往左上方跳。关键在于第二次起跳的方向键和跳跃键不能同时按,但时间间隔也不能太长。Wall Jump可以连续发动,掌握该技能后在拿到HI-Jump Boots和Space Jump之前,就能去到很多一般跳跃去不了的地方。

Mockball相对Wall Jump来说重要性不高,需要用到的地方不多。其方法是加速跑的过程中起跳,在空中按下下键,在着陆的一瞬间再次按下下键变身为球,接着快速切换为前进键,其间加速键和跳跃键一旦按下就都没有放开。Mockball失败最主要的原因是在半空中就完成了变形为球的动作,这种情况下球形Samus着陆后会弹动两下。正确发动的Mockball在着陆后不会损失速度,能以球形状态通过本需要加速跑才能通过的地形。

获得Charge Beam、Power Bomb和其他任意一种枪械后,可以消耗Power Bomb发动特殊攻击。方法是Start键中启用Charge Beam及其他任意一种枪械,Select选中Power Bomb,按住射击键蓄力。由于总是需要切换来切换去的,我个人是觉得用处不大。

获得Charge Beam之后,按住射击键蓄力,完成后跳跃,可以用来撞一般的小兵。有点像Screw Attack,西方居然有人管这一招叫one night stand…

流程

空间站

本区域没有岔路,快速通过即可,走到最后就会看见小Metroid和宿敌Ridley。这场战斗输赢没所谓,所以为了节省时间直接去碰Ridley吧,HP掉到一定程度Ridley就会抓起小Metroid闪人。此时空间站自爆程序启动,限时脱出。

Morphing Ball、Missile、Bomb

Samus追着Ridley来到行星Zebes,游戏流程正式开始。

从飞船处先向左再一路向下,来到Brinstar,拿到Morphine Ball。在右边一个房间内,向下打穿地板,拿到第一个Missile。再右边一个门内现在还可以拿到一个Missile,不过该房间内的其他物品现在拿不到,不想浪费时间可以以后再回来收。

沿路返回Crateria,变身为球拿到Bomb,遭遇第一个小Boss,一个鸟人像Chozo,十来发Missile即可搞定。

接着向地图左边进发,途中拿到第一个Energy Tank后,进入Brinstar。

Super Missile、Charge Beam

下了电梯,右边第一个红色的门内有三个Missile,一个Super Missile和一个Reserve Tank。不过现在就想全部拿到,需要你会Mockball和Wall Jump,否则目前只能拿到一个Missile,其他的需要获得Speed Booster之后才可入手。

这里是整个游戏中练习Mockball技巧最好的地方,有兴趣的人可以试一试。大概的方法是,五发Missile打开红色的门,站在平台边缘按住加速键和右键起跑,在离进门大约一步的地方按住跳跃键起跳过门,发动Mockball着陆至上方平台即可通过。

提前拿到Super Missile并不会大幅度改变游戏流程,算是小小的成就感吧。接着向下穿过通道,来到地图中部最上方打Boss。Boss战很简单,等它张嘴的时候抓住机会用导弹轰就是了。搞定之后爬上通道,从尽头处掉下来拿到Super Missile。

接着去拿Missile和Charge Beam,并向地图右下方前进。途中有一个Missile,Wall Jump上去可以拿到一个Missile。

继续前进来到Brinstar地图右边最后一条上下联通的主通道。如果对自己的Wall Jump技术相当自信,可以尝试一下从这里上去提前拿到Power Bomb。不过这条路比较难,虽然飞天乌龟可以用Super Missile干掉,但仍然需要动作很快才行。而且在这里提前拿到Power Bomb,对游戏流程也没有什么实质性的影响。实在做不到的话,等以后获得Ice Beam或Speed Booster之后再来拿吧。

Spazer、Varia Suit、HI-Jump Boots

会Wall Jump的话,从图示处往上就可以拿到Spazer。同样,从图示处起跳借助Wall Jump也可以现在去打Boss和拿Varia Suit。

Boss叫Kraid,在它张嘴的时候攻击嘴巴,大概四发Super Missile就可以搞定。拿到Varia Suit和一个Energy Tank后原路返回,坐电梯来到Norfair。

一路向下到底,左边红门进去,拿到Energy Tank和Missile各一个以及HI-Jump Boots。如果之前没有拿Spazer和Varia Suit的话则回去拿,否则继续前进。

Wave Beam、Speed Booster、Power Bomb、Grappling Beam、Ice Beam

电梯下面右边第一个蓝门进去,路上有一个Missile。来到一个大房间内,右边尽头处往下,也有一个Missile。

此处依靠Wall Jump可以跳到右上角去拿Speed Booster,不过非常难就是了,我跳了不下百次,也就上去了几次而已。实在做不到的话,可以从这个房间左边地上的门下去,绕一个圈子。路上有一个红色的门,进去跳过几个悬浮平台,可以拿到Missile。在这个平台上加速跑往前跳,借着被地刺扎到的无敌时间,可以用Wall Jump跳上前方平台,进门可以拿到Wave Beam。出来以后跳进地刺,穿过单向地板,从左边出去,往上走同样可以拿到Speed Booster。进门之前记得先把Missile拿掉,不然拿到Speed Booster后岩浆涨潮,会有点儿手忙脚乱。

接下来去地图中部,打Boss Crocomire。Crocomire是打不死的小强,只能在它张嘴的时候用导弹攻击,受到攻击后Crocomire会退后,退到一定程度就会掉进岩浆被烧死。

从图示处起跳,在空中按住加速键并落至悬浮平台边缘,助跑一步后向左边跳,即可跳上左上方平台,拿到第一个Power Bomb。

继续往地图左下方走,沿路有一个Missile。到达底部时,利用加速冲刺向左上方和上方跳,可获得Grappling Beam和Missile。之后就要返回,正常流程是从Grappling Beam那个房间往上走,不过要是会Wall Jump也可以原路返回,个人觉得还会快一点。

从Crocomire所在的房间一直往上,进左边的房间,拿到一个Missile。用Super Missile轰击开关开门,之后来到地图左上角,拿到Ice Beam之后。用Power Bomb炸开地板,在下面的房间清空跑道,撞上左边的墙壁后按发射键,可拿到一个Missile。之后原路返回,搭电梯回到Brinstar。

X-Ray Scope、Gravity Suit

沿路往左再往上,进入遇到的第一个黄门。在图示处放置Bomb,前进后拿到X-Ray Scope。鸟人像背后紧邻天花板的位置,Bomb炸出缺口,向下再向右滚动到动不了的地方再次放置Bomb,即可离开房间。

回到纵向通道,往上走到头,进入右边的门再走到头。往下可以拿到一个Power Bomb和一个Missile。往上在电梯下面左边第一个门里,则有一个Power Bomb。

搭电梯返回Crateria,向上再一直向右走,途中有一个Missile。进入Wrecked Ship后往地图右下方走,去打Boss Phantoon。

Phantoon只有在现出实体时攻击它才有效,攻击它放出的鬼火可以获得补给,基本上仍然是很容易打发的一战。

搞定之后,Wrecked Ship的地图就完全开放了。纵向主通道往上,沿路收了两个Super Missile,一个Missile和一个Energy Tank后来到地图最顶层,右边还有一个Missile。

全部拿到之后从左边的门出去,回到Crateria。借助天上飞的那玩意儿,可以拿到房间左上方的Missile。落地之后用Super Missile打开地板也有一个Missile。只是拿到之后要原路返回,不然会掉到房间下面一层去。前进几步,炸开地板的另外一个出口,往右再次回到Wrecked Ship。

一路前行,在鸟人像爪子上变身球形,鸟人像会启动并行进到下面一层的房间。停下来之后落地,用Bomb炸开地板,右边还有一个Missile。接着用Power Bomb炸掉鸟人像和其后的墙壁,加速冲刺到底往上跳,拿到Reserve Tank。

接着往左走,拿到Gravity Suit后出门,回到Crateria。该房间左下角还有一个Missile,图示处进入即可获得。

绕路清版

接下来要去Maridia,搭乘Crateria中部的电梯就可以,不过有志于100%收集的话可以从Crateria最左边往Brinstar绕一圈清理一下。

首先是飞船所在的房间,加速闪光之后站在飞船顶部按住L键向右上方冲刺,掉下来后进黄门,可以拿到一个Power Bomb。再次加速往左边冲刺,方向合适可以直接冲破墙壁,如果差了一点也可以用Power Bomb。

进入该通道,沿路有一个Energy Tank和两个Missile。拿这两个Missile时要站在两块单向地板的中间,不然需要多跑一趟。

然后搭电梯来到Brinstar,一路向下用Power Bomb到达最底部,往左走有Energy Tank和Super Missile各一个。

向下再向左,会遇到三只Etecoon。按照游戏的设定,Wall Jump这个逆天技能就是它们教给Samus的。用Wall Jump到达顶部,右边墙壁上的缺口进去,有一个Power Bomb。这里需要跳到空中快速按两下下键变成球,再按右键,进不去就只有等拿到Spring Ball技能之后再回来了。

接着前进至一个上下联通的大房间,Wall Jump可以拿到一个Missile。拿到后Power Bomb开路,进去有一个Energy Tank。然后是右边的黄门,里面有一个Power Bomb。用Power Bomb炸开Charge Beam所在房间的通道,进去还有一个Energy Tank。

Space Jump、Spring Ball

向右下方前进,用Power Bomb炸开透明通道进入Maridia。

往上走,在一个上下联通的大房间里有一个Missile,需要从下方冲刺上来才能拿到。

接着进入右下角的门,Wall Jump爬到房间中上部再往右下走,穿过一个隐藏的通道,打开红门进入一个大房间。站在右边打小乌龟,大乌龟会冲过来扁你,站在大乌龟背上,待其冲高时射击右边墙壁可以看到一个Missile。之后切换到电索枪,Wall Jump往房间中间跳,可拿到一个Energy Tank。

回到左边的房间,再往上一个房间,左边墙壁上有一个隐藏的缺口,找不到的话注意看看螃蟹的行进路线。变身为球进去可以拿到一个Super Missile。

回到房间从右上方的门出去,往上走过一个门往右,有一个Missile。再往地图左上方前进,有Missile和Super Missile各一个。

原路返回,炸开地板打开一个绿门,Power Bomb开路,Wall Jump往上去打小Boss。一如既往,打它的头就是了。

向右上方前进,沿途拿到一个Missile后,往下走去打Boss Doreigon。打Doreigon有个技巧,先把两面墙上的发射台打掉,再换成电索枪,当Doreigon抓住你向上飞的时候,向发射台开枪,Doreigon会被电死。

拿到Space Jump后,回到地图右上方最底层,向左走拿到一个Energy Tank。之后跳入流沙,来到地图中部。从左边的门出去,到达房间底层,打破地板,进入右边的流沙,拿到Missile和Power Bomb各一个。

进入流沙来到下方,往右走,利用电索枪去掉挡板往上走,Power Bomb开路帮助清道夫,继续前进获得Spring Ball。

Plasma Beam、Screw Attack

往左,Bomb炸开天花板,进入一个绿门来到地图上方。往右上角走,获得Plasma Beam。

往右下方,回到拿到Space Jump之后拿Energy Tank的地方,再次穿过流沙,回到小Boss所在的房间的下面一层。拿到Missile和Super Missile各一个后,再次打开地板,往左边的流沙下去,获得Missile和Reserve Tank各一个。至此,Maridia的物品也收集齐全了。

往左下角走,如果之前拿Varia Suit时没拿到Missile,现在去。然后坐电梯,前往Norfair。

从拿Ice Beam的那条路向下再向右直线前进到底,打开左边的黄门直线前进,搭电梯来到地图下部,往左到底,用Power Bomb炸开鸟人像,变身为球坐到爪子上去使岩浆退潮。从下方走,得到一个Missile后,打小Boss,加强版的鸟人像Chozo。

这个鸟人像只能用蓄力枪来打,不过也不算难。搞定之后,头顶上还有一个Super Missile,然后进入鸟人像背后的房间,拿到Screw Attack。

沿右上方走,爬到一个通道顶层时打破天花板往右,可找到一个Missile。之后穿过一个岩浆涨潮的大房间,炸开地板,往地图右下方走,路上拿到一个Power Bomb后,会遇到本作最强的宇宙海盗。普通状态下这两货可以免疫攻击,只有等它们跳起来后变成黄色才攻击有效。

搞定之后继续前进,不远处再次遭遇Ridley。基本上,这是本作中唯一稍微难一点的Boss,不过也就是稍微难一点而已。搞定之后进Ridley背后的房间,拿到一个Energy Tank。

最终Boss

从Norfair右方绕回去,路上可以找到一个Energy Tank,一个Power Bomb,四个Missile和一个Reserve Tank。

坐电梯回到Brinstar,从拿Charge Beam再右边一点的房间里的黄门往右,拿到一个Power Bomb,再用Power Bomb开路,可以回到拿Morphing Ball的房间。往右走到底,可以拿到一个Energy Tank。Power Bomb开路,网上走可以拿到两个Missile。

坐电梯回到Crateria,沿路拿到两个Missile和一个Super Missile,全部物品收集齐全。

最终Boss在Tourian,沿途没有岔路。这里的Metroid需要先冻住,然后用五发Missile,跟系列中其他的作品一样。即使是最终Boss仍然一点难度都没有,斜上方45°不停射击就是了。

搞定之后再次限时脱出。拿第一个Missile的地方去一趟可以救出教你加速冲刺和Wall Jump的四只动物。去过的话,Zebes爆炸之后,可以看到一个形似流星的物体往画面右方脱出,应该就是那几个家伙吧。

2012年5月25日星期五

IE代理设置会导致Python的urlopen变慢

import sys
import time
try:
    from urllib2 import urlopen
except:
    from urllib.request import urlopen

url = 'http://www.baidu.com'

start = time.time()
res = urlopen(url).read()
elapsed = time.time() - start
print('Elapsed time: {0}'.format(elapsed))

如果你的IE中有设置代理服务器,如上代码跑下来很可能要接近5秒,但取消代理设置之后,1秒都不需要。

pdb跟了半天,过程就不写了,直接说结论吧,问题出在urllib.py(Python 2.7.1,下同)的1534行,proxy_bypass函数:

    def proxy_bypass(host):
        """Return a dictionary of scheme -> proxy server URL mappings.

        Returns settings gathered from the environment, if specified,
        or the registry.

        """
        if getproxies_environment():
            return proxy_bypass_environment(host)
        else:
            return proxy_bypass_registry(host)

getproxies_environment函数用来获取以“_PROXY”结尾的环境变量,如果不存在则返回空的dict。也就是说,有类似的环境变量存在则执行proxy_bypass_environment函数,没有则执行proxy_bypass_registry函数。

而导致urlopen如此之慢的罪魁祸首,就在urllib.py第1511行,proxy_bypass_registry函数中的这一句:

fqdn = socket.getfqdn(rawHost)

执行getfqdn函数,会向rawHost发送三次NetBIOS Name Query请求。也就是这三次压根不会收到返回数据的请求,导致了urlopen会慢成这样。而proxy_bypass的功能,也不过就是确定某个host要不要走代理而已。

Python 2.7.1和3.2均有这个问题,我想保留这个设计总是有一定道理的吧。不过总这么delay也不是事,所以要在IE里设置代理又要用Python写的东西,只有多设置几个环境变量了:

SET HTTP_PROXY="127.0.0.1:8118"
SET HTTPS_PROXY="127.0.0.1:8118"
SET NO_PROXY="localhost,127.0.0.1"

2012年5月13日星期日

OpenWRT DNS

就我所知,目前可以应对DNS劫持的方法有以下这么些:

  1. 使用加密的通道进行DNS查询。
  2. 使用TCP协议发送DNS请求。
  3. 使用监听于非标准端口的DNS服务器。
  4. 设法挡掉伪造的DNS应答。

方案一的实现方式有VPN、Tor等等,前者需要额外的开销,后者速度和稳定性不佳,暂不考虑。

使用TCP协议发送DNS请求

由于GFW只污染了使用UDP协议发往服务器53端口的请求,所以改用TCP协议发送请求就可以规避污染。只是具体到OpenWRT,其默认采用的dnsmasq无法强制使用TCP协议向上游服务器转发请求,所以非要用这个法子,就得再装一个unbound。而unbound的资源占用对路由器来说颇为不低,所以这个方案对OpenWRT来说实用性并不强。

opkg install unbound后,修改/etc/unbound/unbound.conf:

server:
  port: 5353
  do-ip4: yes
  do-ip6: no
  do-udp: yes
  do-tcp: yes
  tcp-upstream: yes
forward-zone:
  name: "."
  forward-addr: 8.8.8.8
  forward-addr: 8.8.4.4

然后修改/etc/config/dhcp:

config 'dnsmasq'
        #option 'resolvfile' '/tmp/resolv.conf.auto'
        option 'noresolv' '1'
        list 'server' '127.0.0.1#5353'

注释掉resolvfile并打开noresolv选项,是为了让dnsmasq不使用resolvfile中的DNS服务器进行查询,下同。

使用监听于非标准端口的DNS服务器

如上所说,GFW只污染了使用UDP协议发往服务器53端口的请求,所以若是某个服务器由非标准端口提供DNS服务,同样可以规避DNS污染。

使用非标准端口的DNS服务器不多,Google DNS就不行。有个德国隐私基金会倒是提供了一组服务器,不过速度也太慢了点。于是没得选了,只剩下OpenDNS。

修改/etc/config/dhcp:

config 'dnsmasq'
        #option 'resolvfile' '/tmp/resolv.conf.auto'
        option 'noresolv' '1'
        list 'server' '208.67.222.222#5353'
        list 'server' '208.67.220.220#5353'

针对OpenDNS查询不存在的域名显示广告的问题,可以用dnsmasq的bogus-nxdomain来解决,将下面这一句加入dnsmasq的配置文件即可。(如果遇到国内流氓运营商的劫持问题,也可以用这个法子来试试。)

bogus-nxdomain=67.215.65.132

设法挡掉伪造的DNS应答

用tcpdump或Wireshark抓包可以看到,GFW会在正确的DNS应答之前加塞几条错误应答。如果我们可以分辨哪些是GFW伪造的应答并将其忽略,同样可以达到规避DNS污染的目的。

GFW伪造的应答分两种,一种返回一个错误的IP,另外一种不包含任何查询结果。针对后一种情况,AntiDNSPoisoning提供了如下规则:

iptables -I INPUT -p udp --sport 53 -m u32 --u32 "4 & 0x1FFF = 0 && 0 >> 22 & 0x3C @ 8 & 0x8000 = 0x8000 && 0 >> 22 & 0x3C @ 14 = 0" -j DROP
iptables -I FORWARD -p udp --sport 53 -m u32 --u32 "4 & 0x1FFF = 0 && 0 >> 22 & 0x3C @ 8 & 0x8000 = 0x8000 && 0 >> 22 & 0x3C @ 14 = 0" -j DROP

由于需要安装iptables-mod-u32和kmod-ipt-u32,我的OpenWRT需要重新编译,所以暂时没法实际测试。我依照原文的说法,写了下面两条规则丢掉Answer、Authority和Additional均为0的应答:

iptables -I INPUT -p udp --sport 53 -m string --algo bm --hex-string "|81 80 00 01 00 00 00 00 00 00|" --from 30 --to 40 -j DROP
iptables -I FORWARD -p udp --sport 53 -m string --algo bm --hex-string "|81 80 00 01 00 00 00 00 00 00|" --from 30 --to 40 -j DROP

至于返回错误IP的应答则有点复杂。AntiDNSPoisoning的思路是通过向伪DNS服务器查询被污染域名,获得错误IP的列表,然后再将返回这些IP的应答都丢掉。这个思路看起来可行,但问题在于如何获得完整的错误IP列表。

以下是我这里找到的错误IP:

  • 159.106.121.75
  • 37.61.54.158
  • 59.24.3.173
  • 203.98.7.65
  • 243.185.187.39
  • 78.16.49.15
  • 46.82.174.68
  • 159.24.3.173
  • 93.46.8.89
  • 243.185.187.30
  • 8.7.198.45

假如说这个列表完备,且在相对较长的一段时间内都没有变动的话,则AntiDNSPoisoning提出的方案可行。不过这两个前提条件是否成立,那就需要比较长的时间来验证了。另外,iptables的string模块是否会带来比较严重的延迟也是一个问题。毕竟按照AntiDNSPoisoning方案的思路,每个DNS查询响应都要比对十几条规则,或许为dnsmasq打个补丁来做这件事要更好一点。

除了返回的IP外,TTL值也可以用来分辨GFW伪造的DNS查询响应。比如从本机ping 8.8.8.8,得到TTL值51,则可以构造出以下规则:

iptables -I INPUT -p udp -s 8.8.8.8 --sport 53 -m ttl --ttl-lt 52 -j DROP
iptables -I INPUT -p udp -s 8.8.8.8 --sport 53 -m ttl --ttl-gt 52 -j DROP
iptables -I FORWARD -p udp -s 8.8.8.8 --sport 53 -m ttl --ttl-lt 51 -j DROP
iptables -I FORWARD -p udp -s 8.8.8.8 --sport 53 -m ttl --ttl-gt 51 -j DROP

本机的TTL为51,则路由器上少跳一次所以是52。在网络环境一定的情况下,TTL一般不会变化,而GFW伪造的应答与正确应答的TTL刚好一样的可能性也很低,所以这个法子也有一定的实用价值。

小结

从不需要安装更多的程序和规则数量两方面考虑,目前我用的是5353端口的OpenDNS。

2012年5月12日星期六

OpenWRT Tor

不知道为什么官方源里没有tor,我在网上倒是找到了一个,不过这个包是为了那些境外的爱心人士准备的,苦逼天朝民众并不适用。所以我从这个包里提取出了tor的可执行文件,自己弄了一个出来:

https://www.boxcn.net/s/f072873ad52ec5eac450

/opt/bin/tor-ctrl是我用lua写的一个tor控制脚本,运行需要opkg install luasocket,最主要的用途是通过SSH代理抓取网桥。/etc/init.d/tor start之后手工执行一次就可以,如果想的话,也可以加到cron计划任务里。

编辑/etc/config/cron,加入这么一段:

config 'task'
        option 'task_name' 'Check Tor Bridges'
        option 'task_Everyday' '1'
        option 'task_time' 'everyh_1'
        option 'task_task' '/opt/bin/tor-ctrl'
        option 'enabled' '1'

我路由器里这个Dreambox的/etc/init.d/cron脚本有bug,70行左右改成如下这样:

2)
local task_hour=`echo $task_time |cut  -d "_" -f2`

if [ $task_Everyday == "1" ] ; then
echo "0 */$task_hour * * * $task_task 2>/dev/null #${task_name} " >> /etc/crontabs/root
else
echo "0 */$task_hour * * $task_week $task_task 2>/dev/null #${task_name} " >> /etc/crontabs/root
fi

;;

在本地运行还不觉得,可对于路由器来说,Tor的资源占用还是有点大。再加上Tor还是太慢太不稳定了,留着应急吧,日常使用还是算了。

2012年5月7日星期一

OpenWRT AutoSSH

我没有VPN,并且OpenWRT默认的ssh客户端dropbear不能用来创建socks代理,并且我从SSHChina买来的帐户不支持证书登陆,所以得改个OpenSSH并且重新编译。

OpenWRT的SDK编译起来并不麻烦,只是有点耗时间。为OpenSSH写补丁也不难,将下面的内容存为999-env-pwd.patch,放到feeds/packages/net/openssh/patches下编译即可。

--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -866,6 +866,7 @@
  static int attempt = 0;
  char prompt[150];
  char *password;
+ char *env_pwd = getenv("OPENSSH_PASSWORD");
  const char *host = options.host_key_alias ?  options.host_key_alias :
      authctxt->host;
 
@@ -875,17 +876,23 @@
  if (attempt != 1)
   error("Permission denied, please try again.");
 
- snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password: ",
-     authctxt->server_user, host);
- password = read_passphrase(prompt, 0);
+ if (env_pwd == NULL) {
+  snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password: ",
+   authctxt->server_user, host);
+  password = read_passphrase(prompt, 0);
+ }
  packet_start(SSH2_MSG_USERAUTH_REQUEST);
  packet_put_cstring(authctxt->server_user);
  packet_put_cstring(authctxt->service);
  packet_put_cstring(authctxt->method->name);
  packet_put_char(0);
- packet_put_cstring(password);
- memset(password, 0, strlen(password));
- xfree(password);
+ if (env_pwd == NULL) {
+  packet_put_cstring(password);
+  memset(password, 0, strlen(password));
+  xfree(password);
+ } else {
+  packet_put_cstring(env_pwd);
+ }
  packet_add_padding(64);
  packet_send();

这种东西因为账户安全的原因,最好自己编译。不过我还是放了一个上来,放心的话,就用这个吧:https://www.boxcn.net/s/33ceb35647f5d510f1b2

(因为ps能看到所有进程的命令行,所以我改的方式是从OPENSSH_PASSWORD环境变量中读取登陆密码,用起来是这个样子:OPENSSH_PASSWORD=pwd ssh -CfNg -D 192.168.1.1:7070 user@host)

将下载或编译好的ssh放到/opt/bin/ssh下,然后创建一个ssh的配置文件/etc/ssh/ssh_config:

Host *.sshchina.com
  StrictHostKeyChecking no

Host可以指定IP地址,但似乎不能一行写好几个。StrictHostKeyChecking no的意思是,自动接受指定服务器的证书而不询问用户。

接下来,opkg install autossh安装好autossh。打开/etc/config/autossh,改成类似下面这样:

config autossh
        option ssh      '-CfNg -D 0.0.0.0:7070 root@host'
        option password 'pwd'
        option gatetime '0'
        option monitorport      '20000'
        option poll     '600'

再打开/etc/init.d/autossh,将start_instance段改成:

start_instance() {
        local section="$1"

        config_get ssh "$section" 'ssh'
        config_get gatetime "$section" 'gatetime'
        config_get monitorport "$section" 'monitorport'
        config_get poll "$section" 'poll'
        config_get password "$section" 'password'

        export AUTOSSH_PATH="/opt/bin/ssh"
        export OPENSSH_PASSWORD="$password"
        AUTOSSH_GATETIME="${gatetime:-30}" \
        AUTOSSH_POLL="${poll:-600}" \
        service_start /usr/sbin/autossh -M ${monitorport:-20000} -f ${ssh}
}

然后启动并启用autossh:

/etc/init.d/autossh start
/etc/init.d/autossh enable

HTTP代理方面,Polipo没法做代理调度,所以还是opkg install privoxy安装Privoxy,这样一旦搞好,家里的所有机器都只需要改代理服务器地址就行了。唯一的麻烦是,Privoxy的配置是纯文本的,改起来不是那么容易,不过这个问题以后再想办法解决吧。

/etc/privoxy/config中需要留意的就是下面三行,默认permit-access不包括localhost,连本地访问都不放过…

listen-address  0.0.0.0:8118
permit-access  192.168.1.0/24
permit-access  127.0.0.1

Update 20130928

适用于OpenWrt 12.09正式版的ssh:https://app.boxcn.net/s/hlui5boqrkpkgijfc66h

2012年4月30日星期一

Sync From Twitter To Google Plus

今天收到了Google Voice的一封信,说我的号码已经有很长时间没用过,如果再不用他们就要回收了。本想着四处逛逛,要真找不到什么用处的话,回收就回收吧,谁知竟翻到了一个同步Twitter信息到Google Plus的法子

首先在GV的设置里找Voicemail & Text,启用Text Forwarding。然后发送任意内容的一条短信到号码33669(因为无法直接把GV号码绑定到Twitter,还以为GV不支持短号码),Gmail中会收到一封信,要求你在G+中验证电话号码。接着登陆G+,添加GV号码并完成验证。

回到Gmail,查看刚才那封信的发件人地址。该地址的域名为txt.voice.google.com,用户名格式为:ddddddddddd.33669.xxxxxxxxxx。其中d的部分是你的GV号码,x的部分或许是密钥吧。向这个地址发送邮件,等同于在GV中向33669发送短信,所以我们可以通过向这个地址发送邮件更新G+信息流。

然后是想办法,当我们更新Twitter之后,自动将推文通过email发送到该地址去。原文作者推荐的是ifttt。在ifttt中创建一个新task,内容为:“If new tweet by you then Send an email from Gmail.”To字段写之前找到的那个email地址,Subject写TWITTER2GOOGLEPLUS {{CreatedAt}},Body写{{Text}} +public(通过email更新G+,邮件标题会被忽略,并且默认情况下只向你的圈子共享信息,所以需要在Body中加上+public)。

通过上面的折腾,的确可以实现同步Twitter信息到G+的目的,只是要给ifttt访问Twitter和Gmail帐户的权限,总觉得有那么一点不太放心的感觉…

最后,为了防止Gmail里出现一堆不相干的信息,还要创建两个过滤器。一个收件人写之前找到地址,标题为TWITTER2GOOGLEPLUS,另一个发件人地址为之前找到的地址,标题为SMS from 33669,执行的操作都是删除会话。

以上。

2012年4月27日星期五

NDS Save File

虽然实体机打起来才带感,不过要说修改和制作金手指的话,还是模拟器比较方便一点。NO$GBA模拟效率比较好,但是高级功能远不如DeSmuME。只是DeSmuME的存档后缀名为dsv,跟NO$GBA和各家烧录卡的格式都不一样,于是又有一个存档转换的问题。

在网上随便翻了翻,找到了一个叫DS Save Tools v2.0.0的东西,dsv转为R4的sav报错,“无法识别源存档格式”,不过使用上倒是没遇到问题。

等啥时候有空,翻翻源代码吧…

2012年3月13日星期二

OpenWrt ExtRoot

从淘宝上买了个二手的TL-WR941ND,AR9132,改好USB、内存和Flash,成色和性价比感觉都还不错。不过8M的Flash肯定是不够我折腾的,于是。

OpenWrt Wiki上的相关资料有Flash LayoutExtRoot。在OpenWrt上扩充存储空间,比我想象的容易多了。

首先是准备外部存储器,我用的是一张1G的TF卡。分两个区,第一个850M左右的Ext3,第二个则是剩下的Swap。需要注意的是,TF卡上我其实更倾向于使用Ext2,因为日志型分区格式会带来更多的读写操作,而TF卡却没硬盘那么皮实。不过我也不很确定就是了,所以格式化为Ext3挂载为Ext2用,反正除了一点点空间外,也没什么损失。而分出Swap也只是有备无患,TF卡的读写远慢于内存,实在有需要的时候再swapon吧。

编辑/etc/config/fstab,修改成下面这个样子:

config 'global' 'automount'
        option 'from_fstab' '1'
        option 'anon_mount' '1'

config 'global' 'autoswap'
        option 'from_fstab' '1'
        option 'anon_swap' '0'

config 'swap'
        option 'uuid' 'e9cafe2e-04e8-43ac-b41d-26ea5c7c47c4'
        option 'enabled' '0'

config 'mount'
        option 'uuid' 'c8edd984-aea8-4bcc-b2bd-b5b110eefb6a'
        option 'device' '/dev/sda1'
        option 'target' '/mnt/sda1'
        option 'fstype' 'ext2'
        option 'options' 'rw,sync'
        option 'enabled' '1'
        option 'enabled_fsck' '0'
        option 'is_rootfs' '0'

config 'mount'
        option 'device' '/dev/mtdblock3'
        option 'target' '/mnt/overlay'
        option 'fstype' 'jffs2'
        option 'options' 'rw,noatime'
        option 'enabled' '1'
        option 'enabled_fsck' '0'

UUID可以用blkid /dev/sda*获得。

重启,mount一下看看,如果/dev/sda1被正确挂载到/mnt/sda1下,就证明配置文件没问题了。

接下来搬迁现有的overlay:

cp -a /overlay/* /mnt/sda1/

我是没试过,但据说TF卡读写速度比Flash快很多,如果把ROM的内容都放到TF卡上可以加快速度什么的。如果你想试试的话,可以这样:

cp -a /rom/* /mnt/sda1/
cp -a /overlay/* /mnt/sda1/

然后再次编辑/etc/config/fstab,把TF卡相关内容的is_rootfs设置为1,重启。查看df -h输出:

Filesystem                Size      Used Available Use% Mounted on
rootfs                  798.3M     17.1M    740.6M   2% /
/dev/root                 6.3M      6.3M         0 100% /rom
tmpfs                    30.3M    108.0K     30.1M   0% /tmp
tmpfs                   512.0K         0    512.0K   0% /dev
/dev/sda1               798.3M     17.1M    740.6M   2% /overlay
overlayfs:/overlay      798.3M     17.1M    740.6M   2% /

rootfs变大了,搞定。

想要的话,可以编辑/etc/banner,随便在什么地方加个记号,比如“Boot from TF-Card!”。下次ssh进去的时候,如果看见这句话,那就证明ExtRoot正在起作用。

2012年3月8日星期四

Upgrade IX828 via USB-To-TTL

不知道是不是觉得我比较无聊?某天一个朋友说他手上有一台某公司定制的高清播放器一台,问我能不能“破解”一下,所以。

经过一番搜索,我找到了该播放器的原型产品,中科智网出的智影IX828,2008年的老东西了。官方网站上有固件可下载(需注册),最新版是2010年4月1日的v1.67G。

拆开机器,如上图所示可以找到四个触点。断电的情况下,万能表可测得最左边一个触点跟视频高频头外侧之间的电阻为0。加电后,测得最右边的触点对地直流电压接近5v,中间两个触点的电压不记得了,3v左右吧。嗯,应该就是TTL了。经过后续测试得知,这四个触点从左到右分别是:GND、TX、RX、VCC。

在板子上焊上三根线并与TTL线相连,GND对GND,TX对RX,RX对TX。串口通讯参数中,波特率为38400(高了低了都是乱码),数据位为8,停止位为1,校验位、流控制都为none。(Windows下Putty和Tera Term都可用,只是Prolific的PL2303驱动v1.5.0版在Windows 7里有蓝屏的问题,所以我用Linux里的minicom。)

打开串口通讯软件并激活该窗口,按住PC键盘上的ESC键并为IX828加电,可中断启动过程并停留在boot loader中。通常情况下,嵌入式Linux的boot loader都提供了相当多的命令。绝大部分刷高清播放器的文章都会教你怎么在PC端建立一个TFTP服务器,怎么设置播放器的网卡等等。但我手上这台机器的boot loader显然是被阉过的,help命令提示Help is not available,config命令不能用,net等命令无反应。

好在,download命令还能用,所以可以从模拟串口直接发送固件过去。

在PC端执行如下命令:

gzip -9 IX828USBromfs.bin
uuencode IX828USBromfs.bin.gz IX828USBromfs.bin.gz > i.txt

然后在播放器端执行:

# 下面这句调整播放器端的波特率设置,执行完毕后PC端也要做同样的调整
config serial 115200
download serial romfs gz

接着回到PC端,调整波特率后执行:

cat i.txt > /dev/ttyUSB0

接近8M的东西,所以即使是115200的速率也会非常的慢。在传送完毕之前,给自己找点事情做吧。在传送完毕后,继续在播放器端执行:

flash romfs 1

基本上,就是这样了。接上电视重启之后发现,遥控器工作不正常,其他倒是都OK。如果要继续折腾的话,就要修改官方固件了。至于可不可行,以后再说吧。

2012年2月14日星期二

构建PS2开发环境

手上这台39001的PS2跟了我很久了,对我来说也算是一个很特别的存在吧。今年过年的时候又想起了他,于是又把他翻了出来,还在淘宝上花了260人刀买了块美版的网卡打算玩玩HDL。可是因为很久没用,主板上的电池没电了。在自己动手换了电池之后,却没想到一直用得好好的uLaunchELF出问题了,黑屏。

我遇到的是一个看起来很诡异的问题,读盘打游戏都OK,同样基于LaunchELF改的LbFn可以用,HDLoader也可以用,但是uLaunchELF、Free MC Boot和Open PS2 Loader都用不了。

拿到修理店,磨了老板一个下午却没找到究竟是哪里出了问题,最后只能把本来装的Magic 3直读换成了Modbo 3,先将就着用,可心里一直有点不太舒服。

前几天总算是找到时间弄了弄PS2的Homebrew编译环境,用最原始的scr_printf去跟,终于发现了问题所在。我的两个手柄接口,凑巧第二个口坏掉了。因为第一个没坏所以打游戏什么的都正常,但uLE在启动时会去尝试初始化第二个手柄,不知道是因为短路还是啥,总之uLE等不到正确的信号,于是就挂在那儿了。

好吧,以下是Ubuntu下PS2编译环境的构建过程:

安装需要的一些工具:

sudo apt-get install gcc patch wget make subversion

下载ps2toolchain(ps2dev.org的SVN服务器当掉了,找了个mirror):

cd ~/
svn co http://psp.jim.sh/svn/ps2/trunk/ps2toolchain

修改ps2toolchain/scripts下的005-ps2sdk.sh和006-ps2client.sh,将svn://svn.ps2dev.org/替换为http://psp.jim.sh/svn/,然后编译:

cd ps2toolchain
sudo ./toolchain-sudo.sh

编译完成的内容会被安装在/usr/local/ps2dev下,现在修改~/.profile,增加以下内容:

export PS2DEV=/usr/local/ps2dev
export PATH=$PATH:$PS2DEV/bin
export PATH=$PATH:$PS2DEV/ee/bin
export PATH=$PATH:$PS2DEV/iop/bin
export PATH=$PATH:$PS2DEV/dvp/bin
export PS2SDK=$PS2DEV/ps2sdk
export PATH=$PATH:$PS2SDK/bin
export GSKIT=$PS2DEV/gsKit

读取环境设置,删掉已经不需要了的东西:

source ~/.profile
rm -R ~/ps2toolchain

PS2 SDK在后期似乎是处于没人管的处境,所以很多homebrew针对SDK的修改也没地方提交,只好把补丁随源代码发布,这也导致了不同的homebrew需要不同的SDK来编译的状况。这个问题没什么好的解决办法,只能把需要用到的N份SDK都留着。

先把编译好的SDK移回个人目录并修改权限:

mkdir -p ~/PS2dev/SDK
sudo mv /usr/local/ps2dev ~/PS2dev/SDK/
sudo chmod -R a+w ~/PS2dev/SDK/

原始SDK留着,复制一份准备编译uLE用:

cp -R ~/PS2dev/SDK/ps2dev ~/PS2dev/SDK/ps2dev-ULE

创建~/PS2dev/switch-ps2dev.sh,内容为:

#!/bin/bash
if [ $# -eq 0 ]
then
echo "usage: sh $0 "
exit 1
fi
unlink /usr/local/ps2dev &>/dev/null
ln -s ~/PS2dev/SDK/ps2dev-$1 /usr/local/ps2dev || exit 1
echo "$1 dev environment ready."

切换编译环境:

chmod +x ~/PS2dev/switch-ps2dev.sh
sudo sh ~/PS2dev/switch-ps2dev.sh ULE

这里下载uLE的源码,解开来,打开“Source/Changed source for external projects/setup.sh”,同样把SVN地址修改一下,sudo sh setup.sh就可以得到一个可以编译uLE的SDK环境了。