在 PyS60 上使用 ctypes 修改 WIFI 的 MTU

自从换了 DELL 无线路由后,俺的 E61i 也出现无法正常连接 WLAN 上网的情况,而且和当初 Vista 的故障一样——可以访问 google,但大部分网站的连接不上,据以往经验,估计还是因为手机的 MTU 大于这个路由器最大支持的能力。

在网上搜了半天,发现在 S60 2nd 机型上,有一种改 genericnif.ini 的方法可以改变 MTU,但是到了 S60 3rd 机型,貌似就只能自己动手写程序了。

但 Symbian 下的 C++ 开发俺不熟啊,PyS60 没有接口啊,怎么办。正好在查相关开发信息的时候,发现 Symbian 出了一个 POSIX 兼容层(S60 平台上的称呼是 Open C),PyS60 社区有人据此移植了 libffi_ctypes 过去;又有人包装了兼容 Python2.5 的 ctypes 提供一些外围函数;wiki 上还有一个 open c 下执行 setsockopt 的例子

于是安装了 Open C 的 SDK, 然后在它的文档里找到这么一段:
The following flags will work only on hardware because it is supported by the underlying symbian API.

  • SIOCGIFMETRIC
  • SIOCGIFMTU
  • SIOCGIFNETMASK
  • SIOCGIFBRDADDR
  • SIOCGIFFLAGS
  • SIOCGIFDSTADDR
  • SIOCGIFPHYS
  • SIOCSIFMETRIC
  • SIOCSIFMTU
  • SIOCSIFFLAGS

只要确认 SIOCSIFMTU 是可以用的就好办了,对一个熟练的 C 程序员且有一定 ctypes 使用经验的人来说,除了平台移植问题外,开发是没有什么困难的。

开发过程如下

  1. 在手机上安装 libffi、Ctypes (_ctypes.pyd)、ctypes
  2. 在 Windows 下安装 S60 C++ SDK,以及 Open C plugin。可以找到 pips_nokia_1_3_SS.sis 等一共 4 个 .sis 文件,都安装到手机上
  3. 找到 winscw\udeb\libc.lib,然后用 dumpbin.exe /exports libc.lib 寻找函数名对应的序号,PyS60 的 ctypes 必须得用序号去定位函数,比如这样
  4. from _ctypes import *
    from ctypes import *
    libc = dlopen("libc.dll")

    socket  = lambda x,y,z: call_function(dlsym(libc, "339"),   (x,y,z))
    ioctl   = lambda x,y,z: call_function(dlsym(libc, "180"),   (x,y,z))
    close   = lambda x:     call_function(dlsym(libc, "57"),    (x,))
    perror  = lambda x:     call_function(dlsym(libc, "255"),   (x,))
    dup     = lambda x:     call_function(dlsym(libc, "67"),    (x,))
    close(2)
    dup(1) #把标准错误重定向到标准输出,这样 perror 就输出到标准输出了

    ntohl   = lambda x:     call_function(dlsym(libc, "168"),   (x,))
    ntohs   = lambda x:     call_function(dlsym(libc, "169"),   (x,))
    print ntohs(1), ntohl(256)  #简单的测试脚本

  5. 没有 dumpbin.exe 这个工具(VC++)的,也可以用 MinGW 的 nm.exe 来处理 libc.lib,只不过输出结果还需要再处理一下
  6. f = open("c:\libc.txt")
    idx = 1
    for i in f.readlines():
        if i.startswith("00000000 T "):
            if i[11] == '.':
                continue
            print idx, i[11:].strip()
            idx += 1
  7. 根据 SDK 的头文件,找出 SIOCSIFMTU 这样的常量定义写成 python 语句
    IOCPARM_MASK = 0x1fff
    _IOC = lambda inout,g,n,l: (inout | (l & IOCPARM_MASK) << 16 | ord(g) << 8 | n)
    _IOR = lambda group, num, len: _IOC(0x40000000L, group, num, len)
    _IOW = lambda group, num, len: _IOC(0x80000000L, group, num, len)
    _IOWR = lambda group, num, len: _IOC(0x40000000L|0x80000000L, group, num, len)

    SIOCGIFCONF     = _IOWR('s', 3, SIZEOF_IFCONF)
    SIOCGIFFLAGS    = _IOWR('i', 17, SIZEOF_IFREQ)
    SIOCGIFMTU      = _IOWR('i', 51, SIZEOF_IFREQ)
    SIOCSIFMTU      = _IOW('i', 52, SIZEOF_IFREQ)

    IFF_UP          = 0x1

    AF_INET         = 0x0800
    SOCK_DGRAM      = 2
    IPPROTO_UDP     = 17

  8. 还要去找 struct ifconf, struct ifreq 是怎样定义结构体的,结构体大小是多少
    SIZEOF_IFREQ = 84
    SIZEOF_IFCONF = 8
    IFREQ_IDXFMT = '52sH'
    IFREQ_MTUFMT = '52sI'
    IFREQ_FLAGSFMT = '52sBB'
    autopadding = lambda start, size: start + str(size - calcsize(start)) + 's'
    ifreq_idxfmt = autopadding(IFREQ_IDXFMT, SIZEOF_IFREQ)
    ifreq_mtufmt = autopadding(IFREQ_MTUFMT, SIZEOF_IFREQ)
    ifreq_flagsfmt = autopadding(IFREQ_FLAGSFMT, SIZEOF_IFREQ)
  9. 真正的操作很简单:
    sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)

    result = create_string_buffer(SIZEOF_IFREQ * 20)
    ifc = create_string_buffer(pack("II", SIZEOF_IFREQ * 20, addressof(result)), SIZEOF_IFCONF)

    ret = ioctl(sockfd, SIOCGIFCONF, ifc)
    ifc_len, ifc_buf = unpack("II", ifc.raw)
    count = ifc_len / SIZEOF_IFREQ
    for i in range(0, ifc_len, SIZEOF_IFREQ):
        ifreq = result.raw[i:i+SIZEOF_IFREQ]
        ifr_name, ifr_idx, padding = unpack(ifreq_idxfmt, ifreq)
        ifr = create_string_buffer(ifr_name, SIZEOF_IFREQ)
        ret = ioctl(sockfd, SIOCGIFFLAGS, ifr)
        ifr_name, ifr_flags, ifr_flagshigh, padding = unpack(ifreq_flagsfmt, ifr)
        if not (ifr_flags & IFF_UP):
            print "%s: DOWN" % ifr_name
            continue
        ret = ioctl(sockfd, SIOCGIFMTU, ifr)
        ifr_name, ifr_mtu, padding = unpack(ifreq_mtufmt, ifr)
        print "%s: UP, MTU: %d" % (ifr_name, ifr_mtu)
        if ifr_mtu > 1400: #凡是 MTU > 1400 的,都改成 1400
            ifr = create_string_buffer(pack(ifreq_mtufmt, ifr_name, 1400, padding), SIZEOF_IFREQ)
            if -1 == ioctl(sockfd, SIOCSIFMTU, ifr): foo = perror("why")

  10. 但...为什么 perror 会报 Permission denied 呢? 后来查了半天,发现是 SIOCSIFMTU 需要有 NetworkControl 权限。于是去 这里 申请一个自己手机 IMEI 对应的证书(免费的),证书对应的 key 在这里
  11. ensymble 这个工具来给 libffi/Ctypes/PythonScriptShell(unsigned_testrange) 重新签名,加上 NetworkControl 权限
    ensymble.py signsis --cert=20081008001.cer --privkey=dospy.key --passphrase="" --execaps="ALL-TCB-ALLFILES-DRM" PythonScriptShell_1_4_4_3rdEd_unsigned_testrange.SIS imei_PythonScriptShell_1_4_4_3rdEd.sis

    ensymble.py signsis --cert=20081008001.cer --privkey=dospy.key --passphrase="" --dllcaps="ALL-TCB-ALLFILES-DRM" libffi.sisx imei_libffi.sis

    ensymble.py signsis --cert=20081008001.cer --privkey=dospy.key --passphrase="" --dllcaps="ALL-TCB-ALLFILES-DRM" ctypes.sisx imei_ctypes.sis

  12. 这下大功告成,我....我终于又可以在家里坐在马桶上用手机上无线了!!

很不错,但是我没看

很不错,但是我没看明白。
为何改客户端呢?

为何不使用能改MTU的路由器呢?
比如DD-WRT Compatible的

http://www.dd-wrt.com/wiki/index.php/Supported_Devices

我的这个 dd-wrt

我的这个 dd-wrt 不知道为什么,MTU 无法调高