4
15
2013
8

lxc 初体验

Linux Containers 是一种系统级的虚拟化方案。其实也就是个增强型的 chroot,和 BSD 的 jail 差不多吧。

准备工作

安装 lxc 以及 bridge-utils 软件包。后者是用来建立网络的。关于对内核的要求什么的请查阅此文。虽然其内容有些过时了,但是参考价值还是很高的。当然,一般新一点的通用内核都支持的啦。

然后想好在建立的 Container 里放什么系统。比如我用的 Funtoo,安装教程在这里。但其实大部分内容没什么用的。按照这里的指示下载一个合适的 stage3 包,并解压到一个目录中。portage 的安装等后续工作就不说了。

创建一个网桥并分配 IP 地址:

$ sudo brctl addbr br0
$ sudo ifconfig br0 192.168.10.1

由于我使用的是无线网络,因此不能使用一般教程中的方法将 eth0 加到网桥中去。建立个 NAT 好了:

echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
sudo iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -j MASQUERADE

配置

按下边这样新建一个文本文件:

# Container 的名字
lxc.utsname = funtoo
# 网络类型。我使用的是 veth
lxc.network.type = veth
lxc.network.flags = up
# 刚刚建立的网桥名
lxc.network.link = br0
#lxc.network.hwaddr 不要乱写,会出错的!让 lxc 自己决定吧。
# IP 地址。和网桥同一网段即可
lxc.network.ipv4 = 192.168.10.2
lxc.network.name = eth0
# fstab 文件
lxc.mount = /home/lilydjwg/workspace/funtoo/funtoo_root/etc/fstab
# 新系统的根文件系统
lxc.rootfs = /home/lilydjwg/workspace/funtoo/funtoo_root
# tty 数量。如果不给出的话 lxc-console 是连不上去的
lxc.tty = 7

然后,修改 funtoo 里的配置文件啦。首先是/etc/fstab

none /home/lilydjwg/workspace/funtoo/funtoo_root/dev/pts devpts defaults 0 0
none /home/lilydjwg/workspace/funtoo/funtoo_root/proc    proc   defaults 0 0
none /home/lilydjwg/workspace/funtoo/funtoo_root/sys     sysfs  defaults 0 0
none /home/lilydjwg/workspace/funtoo/funtoo_root/dev/shm tmpfs  defaults 0 0
/home/lilydjwg/tmpfs /mnt bind 0 0

最后一行就是和主系统共享的目录啦。不过我这里没有生效 :-(

然后删掉/etc/mtab

修改/etc/inittab启动配置文件。lxc-start命令启动的那个终端会是 container 的/dev/console文件:

# TERMINALS
c1:12345:respawn:/sbin/agetty --noclear 38400 console linux
c2:2345:respawn:/sbin/agetty 38400 tty1 linux

ttyN 上的 agetty 可以留着,因为我已经分配了足够的 tty 给它们。使用lxc-console -t 3 -n funtoo就可以连上 tty3。

修改/etc/resolv.conf,配置一个 DNS 服务器。

删除/etc/runlevels/default下关于 udev 的项;udev 是不会工作的。

给新系统中的 root 设置个密码:

$ sudo chroot /path/to/funtoo/root/ /bin/bash
funtoo ~ # passwd

创建新的 container。注意创建之后那个配置文件就没有用了。lxc 会复制一份到/var/lib/lxc/funtoo/config,需要的时候可以改它。

$ sudo lxc-create -f /path/to/config/file -n funtoo

启动啦

如果我没记错的话,现在就可以启动 funtoo 啦:

$ sudo lxc-start -n funtoo

不想它占用一个终端的话就加-d选项让它在后台跑吧。

不过这时候网络还没配置好。虽然 eth0 已经有 IP 地址了,也可以和主系统相互通讯,但是路由表仍需要手动加上,不然访问不了外网的。

funtoo ~ # route add -net 0.0.0.0 gw 192.168.10.1

然后就尽情地玩儿吧 :-)

参考资料

其它链接

Category: Linux | Tags: linux lxc
3
24
2013
24

使用 Wine 运行腾讯 TM

TM 是腾讯出品的一款与 QQ 互通的即时通讯产品。用起来就是瘦过身、去过广告的 QQ。TM2009Beta3.4 是目前最新的、也是唯一能够正常使用的 TM 版本。目前最新版本为 TM2013preview1,按如下方法也可使用。TM官网。最新的 preview2 版本无法安装,请使用此地址下载 preview1 版本。

我这里使用的是 Wine 1.5.26。相比之前我运行修改版 QQ 2010 时使用的版本,此版本在 TM 输入密码时不会崩溃、没有黑影等。在线后数秒内自动变成离开状态的问题依然存在。

不像 Winhex 或者 Beyond Compare,TM / QQ 并无法在 Wine 上直接运行,需要使用 winetricks 命令安装一些东西。细节如下:

首先,因为需要使用 winetricks 安装一些东西,为干净考虑,可为 TM 设置单独的 prefix,即

$ export WINEPREFIX=$HOME/.wine4tm

我这里是 64 位的 Arch Linux,因此需要设置 Wine 使用 32 位 Windows 环境:

$ export WINEARCH=win32

注意这种情况下,不要建立未初始化的$WINEPREFIX目录。运行一下winecfg什么的,让 Wine 自己建立之。

因为是 32 位环境,所以有些库可能需要手动安装,比如 Arch 下我手动安装了以下 32 位库:

lib32-libpng lib32-libjpeg-turbo lib32-mpg123 lib32-libxml2

初次运行某个 exe 文件时,注意下终端的输出。如果其中提到某个.so文件没有找到,那么就手动安装上吧。Arch 下查询需要安装的包的方法是,使用 pkgfile 命令查询该文件名,如:

$ pkgfile libpng15.so.15
extra/libpng
multilib/lib32-libpng

lib32-开头那个即是需要安装的软件包。安装之后并不需要重新运行该 exe 文件,除非它已经导致了问题。

lib32-libpng不安装会导致部分界面显示异常,lib32-libjpeg-turbo不安装可能导致好友发送的图片无法显示。

然后使用 wine 运行 TM2009Beta3.exe 这个安装文件。同时可以开始 winetricks 相关工作。

需要使用 winetricks 安装的组件如下:

  1. riched20

    此组件将解决登陆成功后 TM 崩溃的问题。相关bug报告在此。

  2. ie6

    此组件将解决编辑消息时的崩溃。其错误消息为:

    ###!!! ABORT: Main-thread-only object used off the main thread: file /build/wine-mozilla-1.9/xpcom/base/nsCycleCollector.cpp, line 1151
    

    从 winetricks 提供的地址下载 ie6 很慢,并且有些限制。可以通过 Google 搜索「msie60.exe」得到另外的下载链接,比如这个

  3. mfc42

    此组件将解决启动时 TM 界面中的黄条警告,以及其它一些问题。

安装完毕后,在点击链接时 winebrowser 会崩溃。使用 Wine 内建的「urlmon.dll」即可。设置方法是,打开winecfg,切换到「函数库」选项卡,在「已有的函数库顶替」中编辑「urlmon.dll」项,设置其使用「内建」版本。

搞完这些,TM2009 就可以使用啦!来张高清全屏截图——

已知问题:

  • 截图仅能截取一个屏幕。快捷键仅在 Wine 程序拥有焦点时可以工作
  • 接收图片时的动画不正常
  • 输入法光标跟随无效。fcitx 输入框总是位于输入框下方(这里有补丁,据说可以修正这个问题)
  • 在 Awesome 下(特别是双显示器的扩展屏上时),鼠标拖动窗口上边缘可能导致窗口乱跑
  • 可能会卡死(线程死锁,wine 的已知 bug),特别是在打开聊天记录时
  • Awesome 下最大化等同于全屏,wibox 被遮挡
  • 数秒后会自动变成离开状态此问题已经在 Wine 1.7.6 中修复

语音和视频暂未测试。远程协助基本正常。

重要提示:TM 的截屏图片是以 JPEG 格式发送和显示的,会造成截图模糊、失真。(尽管截屏并保存的话是 PNG,从其截屏功能或者从剪贴板粘贴的、发送到会话的图片是 JPEG。)常见的可接受的截图格式只有 PNG、GIF 或 BMP。JPEG 只适合照片这种取自自然而非生成的图像。

Category: Linux | Tags: linux QQ windows wine TM 腾讯
1
21
2013
14

修改 Sony LT26i 的 boot.img

上篇记录了我给 LT26i 刷机的过程,现在该是修改它的启动代码了。

这部分内容我折腾了好久,现在把技术细节分享出来。有次我都看到有人明确表示 Sony 的 boot.img 不一样,还亲自做了新的,却没能看到修改方法,实在遗憾。

所有操作都是在 Linux 下完成的。用到的 Windows 软件 WinHex 是可以 wine 的。

解包

Sony Xperia S 使用了自己定制的格式,因此网上流传的split_bootimg.pl并不能用。Sony 提供了制作这种文件的脚本,但是没有解包的脚本。不过其格式很简单,自己写一个根本不费事:

#!/usr/bin/env python3
# vim:fileencoding=utf-8

import sys
import os
import struct

def getSegNum(f):
  f.seek(44)
  d = f.read(2)
  return struct.unpack('<H', d)[0]

def readSegInfo(f):
  d = f.read(32)
  info = struct.unpack('<LLLLLLLL', d)
  return info[4], info[1] # size, offset

def main(fname, output):
  if os.path.isdir(output):
    os.rmdir(output)
  os.mkdir(output)
  f = open(fname, 'rb')
  os.chdir(output)
  n = getSegNum(f)
  f.seek(52)
  segs = [readSegInfo(f) for i in range(n)]
  for i, seg in enumerate(segs):
    f.seek(seg[1])
    data = f.read(seg[0])
    with open(str(i), 'wb') as wf:
      wf.write(data)

if __name__ == '__main__':
  if len(sys.argv) != 3:
    print('Which file and where to extract?')
  else:
    main(*sys.argv[1:])

使用方法很简单(至少比 Sony 那个mkelf.py简单 :-)

unpackelf.py .../android/cm-9.1.0-nozomi/boot.img boot_cm

对于 CyanogenMod,解开的目标目录boot_cm下会有012三个文件。使用file命令便知,initramfs(或称 ramdisk)是1。解开它很容易:

mkdir ramdisk && cd ramdisk && gunzip < ../1 | cpio -i

对于官方 ROM 也只是多了一个叫3的文件(它的数据在boot.img的开始处)。

启动图片

很明显,logo.rle最有可能是启动图片了。可是,它是什么格式呢?Google 便有了答案。使用那里的 C 程序可将这图片有损地转成 RGB 原始数据。

5652rgb -rle < logo.rle > logo.raw

然后用 ImageMagick 的convert命令转换成常见图像格式:

convert -depth 8 -size 720x1280 rgb:logo.raw logo.png

-size那里填上自己的屏幕分辨率。不知道的话就根据屏幕比例和像素数解二元二次方程吧 ^_^ 反正要是转换出来不止一张图片肯定就不对了。

以上是显示启动图片的方法。至于生成的方法嘛,我没有需要就不弄了,直接用 Sony 原生 ROM 里的logo.rle文件替换之。

换回官方 ROM,安装 recovery

修改完启动图片,我还是不太满意 CyanogenMod 的主题啊之类的,和 Sony 的比起来太丑了。于是又刷了从网上某处找来的「索爱 LT26i 基于6.1.A.1.58最新官方ROM纯净版」。它已经 root 了,但是没有开机时按音量键的恢复模式了。于是我手动修改 initramfs,自己给它加上了。

通过比较和搜索可知,在init.semc.rc文件中,CyanogenMod 在 early-boot 的时候调用了sbin/bootrec命令。在官方 ROM 的 330 行那里加上即可。另外一个可以修改的地方是default.prop文件的ro.debuggable项,其值改成1,可以让 adb 使用 root 权限,push/pull 系统文件的时候特别方便。

然后把官方 ROM 的sbin下没有的文件从 CyanogenMod 那边复制过来即可。注意有大量软链接,可使用cp -ia来复制。我顺手把sbin/bootrec-device里的sleep 3改成只暂停一秒了。这是启动时检测按键以进入恢复模式的等待时间。同时可以看到,这个脚本会将各种指示灯点亮,有兴趣的也可以改改,比如换个指示灯颜色什么的。

打包

首先把文件们弄回 initramfs 里去:

find . | cpio -o -H newc | gzip > ../new_ram

注意这里一定要指定 cpio 的格式为newc,不然启动不了的。

本来,Sony 官方提供的mkelf.py可以用来打包,对付 CyanogenMod 的 boot.img 足够。但是官方 ROM 多出了一段,因此打包参数不一样。我直接用 WinHex 修改 boot.img 文件了。除了 initramfs 本体之外,还有三处需要修改:initramfs 的长度(两个)、下一个叫「RPM」部分的偏移位置。还好没有 checksum 之类的东西。

弄好后刷回去就可以了。不用担心刷出问题。如果导致不能开机,同时按住开机键和音量上键,直到机身震动一下,然后松开开机键,就进入 bootloader 可以刷回原来的了。

附:Reverse USB Tethering 方法

没有网上说的那么复杂,还网桥什么的,都要把电脑的网络重连 :-(

  1. 连接 USB 线,启用 USB 绑定
  2. 电脑上给新出现的usb0网络接口配置 IP,IP 段可在手机上使用ip addr命令查看rndis0网络接口的地址。比如我这里手机的 IP 是 192.168.42.129,就在电脑上执行
    ifconfig usb0 192.168.42.1
    
  3. 电脑开启 NAT 功能和 IPv4 转发
    echo 1 > /proc/sys/net/ipv4/ip_forward
    iptables -t nat -A POSTROUTING -s 192.168.42.0/24 -j MASQUERADE
    
  4. 手机上配置路由表
    ip route add default via 192.168.42.1 dev rndis0
    

    手机上的route命令很不一样,总是说参数无效,我没弄明白该怎么用,就用ip命令了。

注意这样的网络部分功能不认可导致无法使用,比如 TrackID™、VPN。

Category: Android | Tags: Android linux
1
12
2013
4

Sony LT26i 刷机记录

声明:本文以期有用的目的写作,不保证本文所述操作能够部分或者完全地满足他人的需求,也不保证其不会给他人造成损失。按本文操作者,如对其生命财产造成任何损失请自理,在法律许可的范围内本人不承担责任。

注意:除 SD 卡数据外,其它数据均可能永久性丢失!

  1. 首先,开启手机的 USB 调试模式。安装 Android SDK。
  2. 下载 CyanogenModSony Xperia S 版 ROM,将其放到 SD 卡根目录:adb push cm-9.1.0-nozomi.zip /mnt/sdcard
  3. 解压其中的 boot.img 备用。
  4. 解锁 bootloader。在官方网站 http://unlockbootloader.sonymobile.com/ 上确认需要解锁 bootloader 并填写名字、IMEI(去掉最后一位数)和 Email 地址。注意,此步骤可能导致失去质保!
  5. 等待解锁 key 的邮件。
  6. 关机,按住音量增加键并连接 USB 线。指示灯会变为蓝色,即进入 fastboot 模式。
  7. 执行sudo fastboot -i 0x0fce getvar version返回version: 0.5,连接正确。
  8. 执行解锁操作:sudo fastboot -i 0x0fce oem unlock 0x${key}
  9. 刷入启动镜像:sudo fastboot flash boot boot.img,boot.img 是从 CyanogenMod ROM 里解压出来的那个。
  10. 重启:sudo fastboot reboot
  11. 启动时会出现下图所示的图片。在按键灯和指示灯点亮的三秒里按几次音量键,进入恢复模式。如果错过了,手机卡在启动 Xperia 的启动动画上,使用adb shell reboot重启之。

    CM 开机图片

  12. 此时可能用户数据还在。可能可以通过 adb 备份数据。我使用恢复模式备份,不知是不是操作失误,仅备份成功 boot.img 和 /system,用户数据丢失 TAT
  13. 执行「wipe data/factory reset」,然后再选择安装下载的那个 zip 文件。
  14. 等操作完成后重启。这时启动动画应该变成蓝色机器人了。
  15. 如需要 Google 应用,请单独下载后如同 ROM 一样安装。

安装完成后,我发现 CyanogenMod 的主题和启动动画实在是不好看。目前我已经把启动图片、开机动画和动态壁纸改回 Sony 原来的了。开机动画位于/system/media/bootanimation.zip,直接替换即可。动态壁纸在 Sony 的 ROM,文件名叫「CosmicFlow.apk」(sha256sum: 2c65079df9ff2a55d0bedb917cdaedd5d11e68fa9c9bc476aa20c92a508e3527),安装即可。至于启动图片,在 boot.img 里,而这个 boot.img 是 Sony 特有的格式。下篇详述了。

Category: Android | Tags: linux Android
12
8
2012
7

iptables 访问控制规则两则

防 ssh 暴力破解

一直以来,面对 Vim 显示的 auth.log 里满屏的红色 ssh 登录失败记录,要么容忍,要么换端口号,要么是 fail2ban。换端口号显然会造成很多不便,尤其是使用者比较多的时候。fail2ban 以前也用得挺好的,但是需要手工编辑配置文件,阅读其中长长的注释并且小心翼翼地修改参数。配置好之后还会经常收到 fail2ban 发出的邮件。这些都可以忍受。直到有一天,某位使用者不小心登录失败多次以后,那个 IP 被封掉了。我从 /etc/hosts.deny 中删除了对应的项目,但是没有用,因为 fail2ban 会去检查 auth.log,然后把那个 IP 给加回去……

前两天本来是寻找限速的命令的,却无意之中看到了防 ssh 暴力破解的命令,如下:

iptables -I INPUT -p tcp --dport 22 -i eth0 -m state --state NEW -m recent --set
iptables -I INPUT -p tcp --dport 22 -i eth0 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP

第一句是说,对于外来数据,如果是 TCP 协议,目标端口号是 22,网络接口是 eth0,状态是新连接,那么把它加到最近列表中。第二句是说,对于这样的连接,如果在最近列表中,并且在 60 秒内达到或者超过四次,那么丢弃该数据。其中的-m是模块的意思。

也就是说,如果有人从一个 IP 一分钟内连接尝试四次 ssh 登录的话,那么它就会被加入黑名单,后续连接将会被丢弃。这是对付 ssh 暴力破解的绝佳规则了。不用修改 openssh,也不用另启一个容易招麻烦的服务。不过不知道多久以后那个 IP 才能重新连接上。

我实际使用时正有一北京 IP 在尝试 ssh 登录。命令执行后,auth.log 里的红色失败消息又出现了四次,然后就没有了。后来再查看时,虽然还是能看到不少红色,但是没有以前那么密集了。更重要的是,每四条登录失败消息间的时间间隔比较大了。可谓效果显著啊。

网络限速

这是我这次搜索 iptables 相关信息的本意。起因是这样子的,在本地测试的时候,经常会发现本地连接的速度实在是太快了。对于网站,不能反映其真实的使用体验;对于网络程序,无法测试其在网络不良时的表现,由于测试的规模小,一些真实使用时容易出现的竞态也由于操作完成得太快而无法重现。

很早就知道 iptables 能够对转发流量进行限速。既然是 iptables 而不是某些商业软件,它就没理由只能对外部流量而不对本地接口 lo 进行限速。于是最后弄到如下命令:

iptables -A INPUT -s 127.0.0.1 -p tcp -d 127.0.0.1 --dport 6900:6901 -m limit --limit 1/s -j ACCEPT
iptables -A INPUT -s 127.0.0.1 -p tcp -d 127.0.0.1 --dport 6900:6901 -j DROP

这两条规则组合起来是说,对于所有从 127.0.0.1 到同样的地址的 6900 到 6901 端口的 TCP 连接,每秒只接受一个数据包,多余的丢弃。后边那句是必要的,如果不写的话就没作用了,因为默认策略是接受。

要注意的是,如果使用域名localhost的话,很可能会使用 IPv6 地址::1而不是127.0.0.1了。

参考链接

Category: Linux | Tags: linux 网络 iptables
10
25
2012
24

使用 TLS/SSL 加密你的 HTTP 代理

HTTP 代理是明文的,这导致实际访问的 URL 可以被他人监测到。即使使用 HTTPS 协议,经过 HTTP 代理时会发送CONNECT请求,告诉代理要连续到远程主机的指定端口。于是,访问的目标域名暴露了。

有没有办法将传输内容加密呢?比如像 HTTPS 那样,使用 TLS 协议连接到代理服务器,然后再进行 HTTP 请求。很遗憾的是,我在 ziproxy 的配置里没有发现这样的选项。在 shlug 邮件列表里询问后,Shell Xu 提到了 stunnel 这个工具。以前我试过用它把 HTTP 的网站转成 HTTPS 的,但是网站后端程序无法知晓用户实际上使用的是 HTTPS,有些郁闷,就没管它了。

这次再次请出 stunnel,在代理服务器上执行如下命令:

sudo stunnel -d 0.0.0.0:8081 -r localhost:8080 -p /etc/stunnel/stunnel.pem

这样,所有到服务器的 8081 端口的请求,都会经过 TLS 解密后传递给 8080 端口。同时响应的数据也会被加密后再返回请求方。

接下来的问题是,浏览器无法直接使用这种代理。实际上除了拿 openssl 命令手动连接外,我不知道任何程序能够使用这种代理。那好,本地弄个反过来加密/解密的服务好了。还是使用 stunnel。不过出了点意外:Arch Linux 的 stunnel 是第四版,不再用命令行参数,转而使用配置文件了。于是参考这篇 Upgrading to stunnel 4,写了份 stunnel4 的配置文件:

compression = zlib
foreground = yes
output = /dev/stdout
client = yes
pid = /tmp/stunnel.pid
# or will output to syslog :-(
output = /tmp/stunnel.log

[name]
accept = 8082
connect = server.com:8081

这样在本地 8082 端口监听,把所有请求加密后转发到 server.com 的 8081 端口。同时响应的数据会被解密后再返回。

现在,所有与代理服务器传输的数据都被加密了,不怕被偷窥啦。

后记:

后来,我发现其实代理服务器和我本机都装了两个版本的 stunnel,只是名字中不带版本号的一个是第三版而另一个是第四版而已……

再后来,我猛然想起神器 socat——这家伙是支持 OpenSSL 的!比如客户端这边像下边这样子就可以了:

$ socat tcp-listen:8082,fork openssl:server.com:8081,verify=0

socat 真是神器啊,cat、netcat、rinetd、stunnel 的功能都覆盖了!

这样使用的时候,每次来新请求时,socat 会 fork 一个新进程来处理。有点浪费资源。不过略微查看了下,stunnel 似乎也一样。

6
11
2012
11

rpysh——Windows Python 命令行也要 readline!

rpysh 是为习惯 Linux 的 Pythoners 在不得不处理 Windows 上的事务时写的远程 shell。

源起

前些天,我尝试了使用 Python 控制 Word。但我对 Windows 下的交互式 Python shell 很不满意。

首先,我尝试的是 cmd.exe 那个黑窗口。太难用了!只有最基本的行编辑、在不知不觉中历史记录被窜改、复制粘贴极其麻烦。补全当然也是没有的。

于是,尝试 IDLE。这家伙我选了「IDLE Classic Unix」,但是能工作的键并不多。比如我刚刚尝试的Ctrl-u就不管用。而Ctrl-p竟然是把光标向上移动,回车才会把那行的内容取到输入命令的那行。这样一来,想再次执行最后一条语句,需要视上条命令输出的行数按几下Ctrl-p。另外,鼠标在窗口内点击后光标会被移开。这样,我使用鼠标从其它窗口切回来时,还得再手动定位光标,极其不爽。至于补全么,太智能了,所以在我输入时不时会出现这种情况:

乱七八糟的补全

还有一个问题:我查资料、做笔记、写代码都在 Linux 上,虽然Ctrl-CCtrl-V在物理机和虚拟机间能够无缝操作,但比起选中+中键粘贴的 X 主选区还是麻烦多了!

没办法,我只好重拾很久以前的想法——写个程序,在 Linux 上操作,在 Windows 上执行!

——等等!这和 ssh 差不多吗?或者 telnet?

——不不,Cygwin 的 ssh 跑不了 Windows 控制台程序,而且,不还是没 readline 支持么?

实现

毫无疑问是网络通信了。距离上一次不成功的尝试已经过去很久了,我不仅更加了解了code模块的能力,也知道 Python 命令行补全是怎么回事了。也就是说,Windows 版的 Python 是有补全的接口的,只是没有 readline 的等价物来调用。跑在 Windows 上的服务端要完成以下操作:

  1. 重写相关方法,把用户数据由标准输入改到从客户端读取
  2. 标准输出重定向到网络 socket
  3. 收到客户端的补全请求后,使用rlcompleter模块获取补全结果,再回送给客户端

对于第一点,实际上取代code.InteractiveConsole实例的raw_input方法就行。它和内建的input()函数具有相同的输入和输出形式,也就是会接收命令提示符。将这个直接发给客户端好了。

第二点很简单,直接socket.makefile然后把sys.stdout指过去。

第三点,为了简单起见,我另开了个线程和 socket,专门用于补全。需要传递的参数和返回值全部 pickle 了扔给对方就是了。

写完这些我才发现,其实我的raw_input方法和补全函数具有相似的执行逻辑:发送参数到网络,再从网络获取执行结果——也就是远程过程调用呵。

使用方法

rpyshd.py可选一个参数作为端口号,为方便起见,提供默认值8980。也是为了方便双击执行起见,我添加了.py后缀。

rpyshc相当于telnet命令了,直接接主机地址和端口号两个参数即可。

缺陷

  • 从标准输入读数据时在服务端
  • 偶尔提示符出现不及时
  • 虽然我实现了Ctrl-C,但是实际上没什么用,因为收到消息时之前的操作肯定已经执行完了
Category: python | Tags: linux python readline windows
6
6
2012
12

编程获取本机IPv4及IPv6地址

首先,我要通过编程直接获取,而不是去读诸如ifconfig等命令的输出。

其实是只想获取IPv6地址的,不过我猜想它们差不多,也确实看到不少相关搜索结果,于是顺带着看了。

首先,使用gethostbyname查自己通常是不行的,因为可能得到127.0.0.1,而且我猜,这样不能处理拥有多个IPv4地址的情况。另外一种方式是连上某个主机,然后调用getsockname。这样需要能够直接连上那个主机,好处是如果有多个网络接口,这样可以知道到底走的是哪个接口,调试网络时不错。我最满意的方案在这里,使用ioctl来获取。这个方法可以获取指定网络接口的IPv4地址。至于有哪些网络接口嘛,直接读/proc/net/dev吧。

import fcntl
import socket
import struct
ifname = b'eth0'
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 0x8915 是 SIOCGIFADDR
ip = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
print(ip)

然而,这样只能获取IPv4地址。创建个AF_INET6的 socket 传过去会报错「Inappropriate ioctl for device」。那怎么办呢?Google 没找到,我去搜了下内核源码。inet_ioctl里有对SIOCGIFADDR的处理。但是,inet6_ioctl里却没有了。

于是,我只好去下载ifconfig所属的 net-tools 的源码,找到相关代码:

#if HAVE_AFINET6
    /* FIXME: should be integrated into interface.c.   */

    if ((f = fopen(_PATH_PROCNET_IFINET6, "r")) != NULL) {
    while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %08x %02x %02x %02x %20s\n",
              addr6p[0], addr6p[1], addr6p[2], addr6p[3],
              addr6p[4], addr6p[5], addr6p[6], addr6p[7],
          &if_idx, &plen, &scope, &dad_status, devname) != EOF) {
        if (!strcmp(devname, ptr->name)) {
        sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s",
            addr6p[0], addr6p[1], addr6p[2], addr6p[3],
            addr6p[4], addr6p[5], addr6p[6], addr6p[7]);

这里就是ifconfig输出IPv6部分的代码了。可以看到它打开了一个奇怪的文件。跟过去,发现是

#define _PATH_PROCNET_IFINET6       "/proc/net/if_inet6"

囧,这个文件我早就发现过了的。看来和IPv4的情况不同,IPv6地址只能通过/proc里的文件获取了。而且输出成人可读格式不容易(ifconfig是自己实现的)。

PS: 我还发现了件好玩的事,在 Linux 源码的include/linux/sockios.h中,SIOCGIFINDEX中的字母 C 写漏了。通过git blame我发现,这个拼写错误在至少七年前 Linux 内核代码迁移到 git 前就修正了。Linus Torvalds 说之前的代码导入到 git 后有 3.2GB。我不得不承认这是个无比正确的决定,因为现在的.git已经有600多兆了,git 不支持断点续传,clone 下来已经很不容易了。

另外,我还联想到了 Unix 系统调用中的creat,以及 HTTP 协议中的referer :D

#define SIOCGIFINDEX    0x8933      /* name -> if_index mapping */
#define SIOGIFINDEX SIOCGIFINDEX    /* misprint compatibility :-)   */
Category: Linux | Tags: C代码 linux python 网络
4
29
2012
53

放弃 gnome-terminal,转到 Xfce 终端

前日进行了系统升级,结果分外悲剧。在 Awesome 下,Empathy 和 gnome-terminal 都不能正常使用了。当然,Awesome 并没有升级,它很稳定。关于 Empathy 的事下篇再写,本篇只吐槽 gnome-terminal。

早些时候,我就发现我经常不能在 gnome-terminal 中成功打开输入法。昨天将 gnome-terminal 从 3.2.1-1 版本升级到 3.4.1.1-1 (以及相伴的其它组件升级)后,我发现我已经很难遇到输入法「恰巧」能够打开的时候了。于是给 GNOME 他们报了 bug。而昨天和今天早些时候,只好使用 vimim 或者复制的方式来在终端里输入中文。今天,收到 gnome-terminal 开发者 Christian Persch 的回复:

Please test if this is reproducible with either gnome-shell or metacity (latest versions from gnome 3.4) in click-to-focus mode. Anything else is entirely unsupported.

他说,如果不能在 GNOME 的组件中重现,那么他们不会修复。操你妹啊,你丫又没有平铺式WM!这不就相当于我说你们的某家电在我家里无法使用,经销商却说你们要是不能在商场里重现问题那不关他们的事么!

于是决心放弃一切有问题的 GNOME 组件,换终端了。我不想用 guake 那种特别的终端,因为一个普通的正常终端在 Awesome 下已经被我调教得很听话了。先尝试的是 lxterminal。忽略掉不完整的中文翻译,开始迁移自己的配置。终端光标不能更改为竖线就算了,竟然没办法通过命令行参数的方式指定窗口的任何可以用作区分的参数。原来 gnome-terminal 我是指定 class 的,这样我可以设置一个流动终端——按一个组合键把它叫过来,用完后再按个键让它离开。但这样做必须能够匹配到那个窗口;我还有些放 mosh 会话的终端,我可不希望它们也跟过来。

又尝试了 Xfce 的终端。这个长得和 gnome-terminal 已经比较像了。看看命令行选项,能设置 role。这个足够用了。配置下字体、颜色什么的,再去改改 Awesome 的配置就可以用了。途中还修整了下run_or_raise函数的匹配逻辑。

PS: GTK 3 更新到 3.4 后,UI 发生了很大的变化,如:

  • 数字输入框的加减号变大了,更占地方,点击时也需要移动更远的距离了;
  • 滚动条变窄变丑了,点中它更难了;
  • gnome-terminal 的颜色设置比 Windows 的取色器还难用了;
  • 好端端的复选框不用,非得弄成滑块,这个是要让用户练习鼠标的拖动操作么?
Category: Linux | Tags: GNOME gtk 终端 linux awesome
4
18
2012
5

Haskell 实战:惰性地读取子进程输出

突然想给 locate 命令写个 wrapper,把输出中的家目录和一些因加密而引入的软链接显示为~。自然,这需要读取 locate 命令的输出。在 process 这个库中看到了readProcess函数,似乎是自己想要的(完整代码):

readLocate :: [String] -> IO String
readLocate args = getArgs >>= \cmd ->
  let args' = args ++ cmd
  in readProcess "locate" args' ""

结果却发现,原本 locate 命令是边查找边输出的,现在变成了先静默,然后一下子全部吐出来。没有按 Haskell 惯常的「懒惰」脾气来。这样一来,当我发现输出项目太多想按Ctrl-C中断时已经晚了。

Google 了一下,找到这个

I guess people who want laziness can implement it themselves directly, taking care to get whatever laziness it is that they want.

好吧。我先下回 process 库的源码看看readProcess为什么不是惰性的:

readProcess 
    :: FilePath                 -- ^ command to run
    -> [String]                 -- ^ any arguments
    -> String                   -- ^ standard input
    -> IO String                -- ^ stdout
readProcess cmd args input = do
    (Just inh, Just outh, _, pid) <-
        createProcess (proc cmd args){ std_in  = CreatePipe,
                                       std_out = CreatePipe,
                                       std_err = Inherit }

    -- fork off a thread to start consuming the output
    output  <- hGetContents outh
    outMVar <- newEmptyMVar
    _ <- forkIO $ C.evaluate (length output) >> putMVar outMVar ()

    -- now write and flush any input
    when (not (null input)) $ do hPutStr inh input; hFlush inh
    hClose inh -- done with stdin

    -- wait on the output
    takeMVar outMVar
    hClose outh

    -- wait on the process
    ex <- waitForProcess pid

    case ex of
     ExitSuccess   -> return output
     ExitFailure r -> 
      ioError (mkIOError OtherError ("readProcess: " ++ cmd ++ 
                                     ' ':unwords (map show args) ++ 
                                     " (exit " ++ show r ++ ")")
                                 Nothing Nothing)

原来是另开了一 IO 线程读输出,然后等待进程结束后关闭管道。这解释为什么它不是惰性的——它得进程善后处理。

那好吧,改用createProcess好了:

doLocate :: IO (String, ProcessHandle)
doLocate = do
  argv0 <- getProgName
  let args = case argv0 of
                  "lre" -> ["-b", "--regex"]
                  _ -> []
  args' <- getArgs
  let args'' = args ++ args'
  (_, Just out, _, p) <- createProcess (proc "locate" args''){ std_in = Inherit,
                                                               std_out = CreatePipe,
                                                               std_err = Inherit }
  hSetBuffering out LineBuffering
  (,) <$> hGetContents out <*> return p

改进后的程序,不会等待进程结束,而是返回输出和进程句柄。进程句柄用来等待子进程结束,同时获取退出状态。至于那个管道就不关闭了,留给操作系统解决好了。

main = do
  (out, p) <- doLocate
  putStr $ transform out
  waitForProcess p >>= exitWith

改进版的完整程序在此

Category: Haskell | Tags: Haskell linux

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com