在 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 必须得用序号去定位函数,比如这样
    1. from _ctypes import *
    2. from ctypes import *
    3. libc = dlopen("libc.dll")
    4.  
    5. socket  = lambda x,y,z: call_function(dlsym(libc, "339"),   (x,y,z))
    6. ioctl   = lambda x,y,z: call_function(dlsym(libc, "180"),   (x,y,z))
    7. close   = lambda x:     call_function(dlsym(libc, "57"),    (x,))
    8. perror  = lambda x:     call_function(dlsym(libc, "255"),   (x,))
    9. dup     = lambda x:     call_function(dlsym(libc, "67"),    (x,))
    10. close(2)
    11. dup(1) #把标准错误重定向到标准输出,这样 perror 就输出到标准输出了
    12.  
    13. ntohl   = lambda x:     call_function(dlsym(libc, "168"),   (x,))
    14. ntohs   = lambda x:     call_function(dlsym(libc, "169"),   (x,))
    15. print ntohs(1), ntohl(256)  #简单的测试脚本
  4. 没有 dumpbin.exe 这个工具(VC++)的,也可以用 MinGW 的 nm.exe 来处理 libc.lib,只不过输出结果还需要再处理一下
    1. f = open("c:\libc.txt")
    2. idx = 1
    3. for i in f.readlines():
    4.     if i.startswith("00000000 T "):
    5.         if i[11] == '.':
    6.             continue
    7.         print idx, i[11:].strip()
    8.         idx += 1
  5. 根据 SDK 的头文件,找出 SIOCSIFMTU 这样的常量定义写成 python 语句
    1. IOCPARM_MASK = 0x1fff
    2. _IOC = lambda inout,g,n,l: (inout | (l & IOCPARM_MASK) << 16 | ord(g) << 8 | n)
    3. _IOR = lambda group, num, len: _IOC(0x40000000L, group, num, len)
    4. _IOW = lambda group, num, len: _IOC(0x80000000L, group, num, len)
    5. _IOWR = lambda group, num, len: _IOC(0x40000000L|0x80000000L, group, num, len)
    6.  
    7. SIOCGIFCONF     = _IOWR('s', 3, SIZEOF_IFCONF)
    8. SIOCGIFFLAGS    = _IOWR('i', 17, SIZEOF_IFREQ)
    9. SIOCGIFMTU      = _IOWR('i', 51, SIZEOF_IFREQ)
    10. SIOCSIFMTU      = _IOW('i', 52, SIZEOF_IFREQ)
    11.  
    12. IFF_UP          = 0x1
    13.  
    14. AF_INET         = 0x0800
    15. SOCK_DGRAM      = 2
    16. IPPROTO_UDP     = 17
  6. 还要去找 struct ifconf, struct ifreq 是怎样定义结构体的,结构体大小是多少
    1. SIZEOF_IFREQ = 84
    2. SIZEOF_IFCONF = 8
    3. IFREQ_IDXFMT = '52sH'
    4. IFREQ_MTUFMT = '52sI'
    5. IFREQ_FLAGSFMT = '52sBB'
    6. autopadding = lambda start, size: start + str(size - calcsize(start)) + 's'
    7. ifreq_idxfmt = autopadding(IFREQ_IDXFMT, SIZEOF_IFREQ)
    8. ifreq_mtufmt = autopadding(IFREQ_MTUFMT, SIZEOF_IFREQ)
    9. ifreq_flagsfmt = autopadding(IFREQ_FLAGSFMT, SIZEOF_IFREQ)
  7. 真正的操作很简单:
    1. sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
    2.  
    3. result = create_string_buffer(SIZEOF_IFREQ * 20)
    4. ifc = create_string_buffer(pack("II", SIZEOF_IFREQ * 20, addressof(result)), SIZEOF_IFCONF)
    5.  
    6. ret = ioctl(sockfd, SIOCGIFCONF, ifc)
    7. ifc_len, ifc_buf = unpack("II", ifc.raw)
    8. count = ifc_len / SIZEOF_IFREQ
    9. for i in range(0, ifc_len, SIZEOF_IFREQ):
    10.     ifreq = result.raw[i:i+SIZEOF_IFREQ]
    11.     ifr_name, ifr_idx, padding = unpack(ifreq_idxfmt, ifreq)
    12.     ifr = create_string_buffer(ifr_name, SIZEOF_IFREQ)
    13.     ret = ioctl(sockfd, SIOCGIFFLAGS, ifr)
    14.     ifr_name, ifr_flags, ifr_flagshigh, padding = unpack(ifreq_flagsfmt, ifr)
    15.     if not (ifr_flags & IFF_UP):
    16.         print "%s: DOWN" % ifr_name
    17.         continue
    18.     ret = ioctl(sockfd, SIOCGIFMTU, ifr)
    19.     ifr_name, ifr_mtu, padding = unpack(ifreq_mtufmt, ifr)
    20.     print "%s: UP, MTU: %d" % (ifr_name, ifr_mtu)
    21.     if ifr_mtu > 1400: #凡是 MTU > 1400 的,都改成 1400
    22.         ifr = create_string_buffer(pack(ifreq_mtufmt, ifr_name, 1400, padding), SIZEOF_IFREQ)
    23.         if -1 == ioctl(sockfd, SIOCSIFMTU, ifr): foo = perror("why")
  8. 但...为什么 perror 会报 Permission denied 呢? 后来查了半天,发现是 SIOCSIFMTU 需要有 NetworkControl 权限。于是去 这里 申请一个自己手机 IMEI 对应的证书(免费的),证书对应的 key 在这里
  9. 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

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

评论

很不错,但是我没看明白。

为何改客户端呢?

为何不使用能改MTU的路由器呢?

比如DD-WRT Compatible的

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

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

太专业了!我看不懂,但我也遇到这个问题,能不能帮我一下!QQ:34944239