8
11
2017
3

谁又用掉了我的磁盘空间?——魔改 ncdu 来对比文件树大小变化

磁盘空间不够用了,或者只是洁癖发作想清理了,可以用 ncdu 来查看到底是什么文件占用了磁盘。ncdu 基于 ncurses,对比 du,更方便交互使用,对比 baobab 这类的 GUI 的工具,ncdu 可以在服务器、Android、树莓派、路由器等没有或者不方便有图形界面的地方跑。

但是呢,我现在有很多很多不同时间的备份,我想知道,是什么东西突然用掉了我好几百兆的空间?我是不是需要把它排除在备份之外?

所以呢,我需要一个支持对比的工具。

本来我是打算什么时候有空了自己写一个的,然而我注意到 ncdu 可以把大小信息保存在文件里。其实我只要对比两个 ncdu 产生的文件,然后照着输出一个差异文件就可以了嘛。不用自己遍历文件树,不用自己做界面,多棒!而且也不一定要像我这样有不同时间的备份才有用。可以定时跑一跑 ncdu,把导出的文件保存起来,将来随时取用。

于是有了 ncdu-diff 脚本

然而事情总是不那么顺利。输出文件拿给 ncdu 加载的时候,ncdu 报错了——它不支持负数。我给它加了支持,然后再加载,BOOM!ncdu 挂掉了……有符号整型和无符号整型的事情,还有格式化输出的事情……总之花了一天,它终于不崩溃了。补丁也放在同一仓库了。

ncdu

从上图可以看出,Android 的 app 越更新越大……以及深入之后可以发现,微信的动画表情占了我好多好多的空间,我去删掉它们……

给 Arch Linux x86_64 现成的包:下载, 签名

Category: Linux | Tags: ncurses linux python
8
5
2017
2

NeWifi 3.2.1.5900 root

新家新路由器。

为了玩 teeworlds,需要 root 权限操作 iptables。我上网找了一堆方案,无果。最后想着,先把自动更新 DNS 的脚本写了吧。

于是研究 API。通讯协议是 JSONRPC 2.0,授权是一个 token。先用从网页取得的 token 调 API,成功~然后我还在想,怎么拿 root shell 呢。结果去看了一下登录后返回的数据:

NeWifi 登录返回的数据

注意看右下角!「open_dropbear」!

于是:

>>> c.api_request('xapi.basic', 'open_dropbear')
[D 08-05 19:28:36.145 connectionpool:243] Resetting dropped connection: localhost
[D 08-05 19:28:36.640 connectionpool:396] http://localhost:8080 "POST http://192.168.99.1/ubus/ HTTP/1.1" 200 None
{'status': 0}

然后就:

>>> ssh root@192.168.99.1
The authenticity of host '192.168.99.1 (192.168.99.1)' can't be established.
RSA key fingerprint is SHA256:............................................
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.99.1' (RSA) to the list of known hosts.
root@192.168.99.1's password:


BusyBox v1.22.1 (2017-03-10 15:06:06 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

        ____      _____
       |  _ \    |_   _|__  __ _ _ __ ___
       | | | |_____| |/ _ \/ _` | '_ ` _ \
       | |_| |_____| |  __/ (_| | | | | | |
       |____/      |_|\___|\__,_|_| |_| |_|

 -----------------------------------------------------
 From BARRIER BREAKER (3.2.1.5900, r39558)
 -----------------------------------------------------
  * By D-Team 2015 present
 -----------------------------------------------------
root@newifi:~#

WTF,就这么简单!

顺便附上我写的简单客户端:

from requestsutils import RequestsBase

class NeWifi(RequestsBase):
  baseurl = 'http://192.168.99.1/'
  token = '00000000000000000000000000000000'

  def api_request(self, cat, name, args={}):
    req = {"jsonrpc":"2.0","id":1,"method":"call","params":[self.token,cat,name,args]}
    ans = self.request('/ubus/', json=req).json()
    return ans['result'][1]

  def login(self, password):
    password = base64.b64encode(password.encode('utf-8')).decode('ascii')
    ans = self.api_request("session","xapi_login",{"username":"root","password":password})
    self.token = ans['ubus_rpc_session']

  def get_wan_info(self):
    return self.api_request('xapi.net', 'get_wan_info')

requestsutils 在此

Category: Linux | Tags: python root 网络 路由器
7
14
2017
9

swapview 更新

距离上一次 swapview 的更新已经一年多了。在这段时间里,不少语言有了比较大的更新,所以再跑一次。

首先是运行不了或者有问题的语言和实现:

  • Julia: 新版本不向后兼容,运行不了。求修
  • Nim: 标准库有些函数的行为有改变:walkFiles 不再返回目录文件,split 不再将连续的空白符作为一个分隔符。
  • Erlang: 不再支持 ~.0f 格式化字符串。

其中不向后兼容的,Julia 和 Nim 还没到达 1.0 版,所以坑人了也就坑了。Erlang 不知道是怎么回事。

然后是有警告的:

  • R: 文件打开失败有警告。不是大问题,不过有点烦。如果你知道怎么去掉它,请告诉我。
  • Elixir: 函数无参调用时不加括号会触发警告。看来 Elixir 也不喜欢 Ruby 函数调用不加操作的行为了呢。

还有发行版的锅:

  • CSharp: mono 与 chicken 冲突,无法安装,所以跑不了。
  • Haskell: Arch Linux 不再支持静态链接了。需要改一下编译参数。

我还对代码做了一些改进:

  • Rust_parallel: 用 rayon 换掉了 threadpool。rayon 更适合这种并行任务处理。另外稍微改进了一下代码。
  • NodeJS: 使用 ECMAScript 6 语法(箭头函数和 const / let 变量声明)。去掉不必要的分号。
  • C: 支持 Android 平台。
  • 修正了一些实现的格式化输出(还剩下一些)。

最后结果如下。因为 CPU 换成了 i7-7700HQ,所以耗时都比之前少了不少。另外注意,前排几名只有前三名都是多线程的,所以 Go_goroutine 比那些 C 和 C++ 版本快很正常。

           Rust_parallel: top:   30.48, min:   27.76, avg:   32.48, max:   37.80, mdev:    2.78, cnt:  20
               C++98_omp: top:   31.24, min:   29.04, avg:   34.42, max:   49.48, mdev:    4.52, cnt:  20
            Go_goroutine: top:   68.30, min:   61.87, avg:   75.89, max:  142.91, mdev:   16.39, cnt:  20
                   C++14: top:   83.17, min:   82.23, avg:   84.71, max:   92.58, mdev:    2.76, cnt:  20
             C++14_boost: top:   83.58, min:   83.20, avg:   84.58, max:   91.00, mdev:    1.72, cnt:  20
                   C++98: top:   83.71, min:   83.09, avg:   85.19, max:   91.48, mdev:    2.44, cnt:  20
                    Rust: top:   91.45, min:   90.81, avg:   93.08, max:   99.38, mdev:    2.07, cnt:  20
                       C: top:   91.49, min:   90.49, avg:   93.41, max:   99.44, mdev:    2.53, cnt:  20
                   C++11: top:   91.81, min:   91.33, avg:   93.52, max:  102.80, mdev:    3.04, cnt:  20
                     PHP: top:   93.91, min:   93.37, avg:   94.98, max:   99.42, mdev:    1.47, cnt:  20
                   OCaml: top:  106.85, min:  105.75, avg:  109.34, max:  118.03, mdev:    3.37, cnt:  20
                     Nim: top:  109.28, min:  108.44, avg:  110.75, max:  117.43, mdev:    2.13, cnt:  20
         D_parallel_llvm: top:  111.25, min:  109.43, avg:  113.21, max:  117.26, mdev:    2.33, cnt:  20
              D_parallel: top:  116.77, min:  114.69, avg:  118.95, max:  125.45, mdev:    2.87, cnt:  20
                    PyPy: top:  126.23, min:  124.29, avg:  128.34, max:  134.07, mdev:    2.79, cnt:  20
                  D_llvm: top:  129.63, min:  128.52, avg:  131.32, max:  137.65, mdev:    2.41, cnt:  20
                  LuaJIT: top:  132.68, min:  131.31, avg:  134.36, max:  143.07, mdev:    2.57, cnt:  20
                      Go: top:  135.57, min:  132.37, avg:  139.25, max:  148.37, mdev:    4.50, cnt:  20
                       D: top:  146.30, min:  145.00, avg:  149.14, max:  159.02, mdev:    3.85, cnt:  20
                Haskell2: top:  150.92, min:  149.41, avg:  153.25, max:  164.60, mdev:    3.53, cnt:  20
                 Python2: top:  155.36, min:  152.26, avg:  158.55, max:  170.20, mdev:    4.60, cnt:  20
                    Vala: top:  159.55, min:  157.87, avg:  161.40, max:  166.52, mdev:    2.26, cnt:  20
                  Erlang: top:  163.00, min:  158.63, avg:  168.76, max:  181.77, mdev:    7.09, cnt:  20
                   Lua51: top:  166.58, min:  164.58, avg:  168.89, max:  181.71, mdev:    3.69, cnt:  20
                   Lua52: top:  168.48, min:  167.40, avg:  170.82, max:  178.11, mdev:    3.36, cnt:  20
           Python3_bytes: top:  174.30, min:  172.65, avg:  176.83, max:  181.64, mdev:    2.91, cnt:  20
                   Lua53: top:  180.20, min:  177.79, avg:  185.01, max:  199.41, mdev:    6.07, cnt:  20
                    Perl: top:  180.22, min:  177.30, avg:  182.21, max:  186.09, mdev:    2.44, cnt:  20
              FreePascal: top:  180.85, min:  179.35, avg:  184.23, max:  197.83, mdev:    4.84, cnt:  20
                 Python3: top:  181.72, min:  178.47, avg:  184.09, max:  189.67, mdev:    2.99, cnt:  20
                    Ruby: top:  199.82, min:  197.16, avg:  203.62, max:  218.32, mdev:    4.92, cnt:  20
                 Chicken: top:  234.69, min:  232.11, avg:  239.61, max:  248.39, mdev:    5.63, cnt:  20
             PyPy3_bytes: top:  238.55, min:  237.18, avg:  242.08, max:  253.68, mdev:    4.53, cnt:  20
                   Guile: top:  254.49, min:  249.14, avg:  260.40, max:  275.83, mdev:    7.12, cnt:  20
              ChezScheme: top:  265.63, min:  262.52, avg:  268.56, max:  278.53, mdev:    3.94, cnt:  20
                    Java: top:  291.35, min:  283.94, avg:  302.36, max:  324.82, mdev:   12.38, cnt:  20
                  NodeJS: top:  317.01, min:  314.61, avg:  321.04, max:  332.05, mdev:    4.71, cnt:  20
                    Dart: top:  329.39, min:  325.63, avg:  334.57, max:  351.19, mdev:    6.92, cnt:  20
           Ruby_rubinius: top:  359.76, min:  357.74, avg:  363.13, max:  373.02, mdev:    4.45, cnt:  20
          CommonLisp_opt: top:  360.57, min:  358.41, avg:  365.15, max:  378.44, mdev:    5.76, cnt:  20
                     Tcl: top:  367.38, min:  363.28, avg:  372.89, max:  388.57, mdev:    6.65, cnt:  20
          CommonLisp_old: top:  376.27, min:  371.99, avg:  379.66, max:  390.55, mdev:    4.33, cnt:  20
                   PyPy3: top:  384.12, min:  376.60, avg:  390.16, max:  401.39, mdev:    7.32, cnt:  20
            CoffeeScript: top:  414.40, min:  393.13, avg:  432.25, max:  466.42, mdev:   20.64, cnt:  20
   CoffeeScript_parallel: top:  451.12, min:  425.11, avg:  464.92, max:  491.52, mdev:   17.05, cnt:  20
            NodeJS_async: top:  454.78, min:  437.13, avg:  465.18, max:  489.06, mdev:   13.02, cnt:  20
         Racket_compiled: top:  510.97, min:  505.22, avg:  516.20, max:  527.69, mdev:    6.23, cnt:  20
                  Racket: top:  520.70, min:  515.11, avg:  525.28, max:  533.79, mdev:    5.87, cnt:  20
         NodeJS_parallel: top:  673.38, min:  664.38, avg:  687.60, max:  724.04, mdev:   16.32, cnt:  20
                   Scala: top:  719.27, min:  698.23, avg:  740.32, max:  815.95, mdev:   27.27, cnt:  20
           Bash_parallel: top:  769.14, min:  751.56, avg:  775.91, max:  791.40, mdev:    8.82, cnt:  20
                 Haskell: top: 1036.33, min: 1013.27, avg: 1048.70, max: 1090.21, mdev: 4186.25, cnt:  20
                  Elixir: top: 1097.32, min: 1075.24, avg: 1113.36, max: 1144.80, mdev: 4186.26, cnt:  20
                       R: top: 1141.37, min: 1120.69, avg: 1156.42, max: 1177.79, mdev: 4186.26, cnt:  20
                    Bash: top: 1368.00, min: 1323.22, avg: 1479.66, max: 1994.19, mdev: 4077.71, cnt:  20
              POSIX_dash: top: 1841.09, min: 1833.25, avg: 1851.09, max: 1881.68, mdev: 3897.64, cnt:  17
               POSIX_zsh: top: 2124.79, min: 2110.81, avg: 2134.32, max: 2156.40, mdev: 3841.56, cnt:  15
              POSIX_bash: top: 2200.64, min: 2195.09, avg: 2206.75, max: 2221.41, mdev: 3807.09, cnt:  14
                  CSharp: FAILED with entity not found
                   Julia: FAILED with entity not found

对比旧结果,可以看到有一些比较大的变化:

Rust 快了不少,并行版一跃成为最快的实现。C++98 OpenMP 版紧随其后。Rust 单线程版也上升了四名,与 C、C++ 版本接近,并超越了所有的 D 实现。Go 并行版也提升了不少,位居第三,但它花费的时间比前两名所花费时间的总和还要多……并且结果也不是很稳定(标准差比前二十名都要大不少)。

Nim 慢了不少,可能是因为没字符串分割函数可用,我改用了 pegs。这东西很慢的样子,也许正则还会快一点……C 也落后了一些,但是与 C++ 版本的差距不大。Haskell 大概是因为改用动态链接的原因,慢了少许。

PyPy 快了很多,竟然超越了 LuaJIT。Erlang、Guile、Rubinius 也都大幅上升,而 NodeJS 不知道怎么了,全面落后于 Python、Ruby、Lua。PHP 更新到 7 之后依旧非常非常快。

完整的排名变化可以看这里

Category: 编程 | Tags: go 编程语言 Rust
6
30
2017
3

nodejs 子进程的正确用法(你应该忽视函数名)

因为库太拙了,需要在 nodejs 里调用子进程来获取数据。然而看到 child_process 的文档真是头疼,这么多种启动子进程的方法直接推到人面前,也没个解释,命名也十分无用。只能一个个地查看详细说明来找到应该使用的那个……所以我整理了一下。

首先是同步创建子进程的那几个函数。会阻塞 nodejs 的主循环。无用。(要是写小脚本的话我直接上 shell 或者 Python 了,干嘛跟自己过不去呢。)

exec:调用 shell 来执行命令的。这部分跟「exec」这个词的 UNIX/C 语义刚好相反。

execFile:不调用 shell,直接执行命令。这命名不明所以。

fork:执行一个新的 nodejs 进程,并且建立一个专用的 IPC 通道。子进程除了 IPC 通道外与父进程无任何瓜葛!命名真是一如既往地误人子弟。默认使用与父进程相同的可执行文件(nodejs 版本),也可以另外指定。

spawn:相当于 Python 的 subprocess,可以指定是否使用 shell。默认不使用 shell。也支持 cwd 啊 env 啊 argv0 啊之类的参数。

结论:如果需要用 Python 的 subprocess.run / Popen 类似的功能,就使用 child_process.spawnexec 开头的那个函数似乎没啥大用,大概跟 subprocess 的 getoutput / check_call 之类的一样只是有一些预设而已吧。

Category: nodejs | Tags: nodejs
6
27
2017
31

电脑被盗事件

半个多月前,6月9日下午,我正在公司奋力工作的时候,接到了自如管家的电话,告诉我家里被盗了。

我有点不敢相信,这样的事情竟然真的发生了?我还有只 mosh 连着家里的电脑呢。切过去一看,半个多小时没有消息了。是真的吗?会不会只是IP更新了?我又打开 GMail 的账号活动页面,确认我的 X250 已经离线半个小时了。看来是真的。

立即回家。大门还是锁得好好的,房门却是被撬过了,还夹着张纸片,是民警留下的。推开门,第一眼看向桌子。电脑果然没有了。第二眼看向旁边的移动硬盘,还好它还在。备份还在,损失就不会那么大了。刚好,我前一天刚备份过一次。

然后是报案什么的。刑警也来过了,尝试采了几个地方的指纹之后就离开了。也是,几万RMB的东西,又不是他们的,并不怎么尽力。

我被盗的除了那台陪伴了我两年的 X250 外,还有和它一起的电源线。连我的罗技无线鼠标和 USB hub 也没了。键盘倒是还在,大概是不好拿吧。合租的那户丢了两部手机、一个 iPad,和一些玉石之类的东西。

我想起我的备份历史。从一开始拥有电脑,我就一直担心电脑被盗、数据丢失。不,是从一开始全心全意地希望拥有电脑的时候就在幻想备份的事情了。

最初的备份只是简单的 tar 包。后来发现 tar 包备份起来慢,想要读取其中的某个文件得整个地解压一遍,也十分地慢,还占了不少磁盘空间,就放弃了。改用一系列脚本,只备份重要的文件。

后来,看到一篇讲 rsync 做增量备份的文章,btrfs 也比较稳定了,就做了个全系统备份方案。这个方案三四年前开始,一直使用至今。除了心安之外,也有过一些用处,比如比对和回滚部分文件,比如以此为模板安装新系统。

没想到,它最初设计的目的达到了。这么快。

被盗一周之后,警方什么消息也没有。我忍受了一周的空荡荡的房间。下单了 T470p。X250 偏向于移动性,然而我发现我并不怎么需要那么好的移动性。那么就买性能好的了。感谢 #archlinux-cn 里朋友的推荐。

设计了新的分区方案,使用 ZFS。折腾了很久,因为 Arch live iso 并不支持 ZFS,所以只好从备份启动。然而因为我很少从备份启动,缺少各种自动化的程序。调整分区,mount --bind 正确版本的内核模块目录,等等。总之是各种折腾,忙活了一天,终于弄好了!

结果只有一个字:卡!不管是用 bfq 还是 cfq,系统时不时在 I/O 量稍大时卡一下,所有执行 I/O 操作的进程都变成 D 状态……

尝试了一些调整,没什么用。后来放弃了,采纳了 ngkaho1234 的意见,换用 XFS。

于是又做了一次备份恢复。这次因为内核直接支持,没那么多麻烦,很快就恢复完毕。透明压缩没了,在线去重没了,磁盘空间使用量上去了,但终于不卡了!

当然还有好些设置要慢慢做。设置双显卡啊,适配 FHD 屏蔽啊什么的。哦对了,因为发现在被盗的电脑上,有部分私人数据没有加密,所以这次我加密了除启动分区和备用系统分区(以及机器自带的 Windows)之外的数据,包括开源代码和 swap 分区。我把一块 dm-crypt 设备给 fdisk 分了区,在上边划出来一个 swap。然后写 mkinitcpio 的 hook 去执行 partprobe 命令来发现这些分区。有点复杂,但配置好就好,也并没有 ArchWiki 上说的那样复杂。

还是丢失了不少数据。一些还没来得及看的电影和视频。一些软件镜像,包括几个 Linux 发行版、Windows、Office 等。winetricks 缓存也没了,有些文件难再下到了。正在调试中的 wine commit 丢失,没有办法继续了。数个 LXC 虚拟机,其中的 Debian Sid 我还时不时会用到的。VirtualBox 虚拟机我备份得少,也不知道丢失了多少数据。已经下载的开源代码没了,花点时间重新下就好。一些手机系统镜像,刷机、root 软件没了。希望我不会再用得到它们。下载的交叉编译工具链、自己交叉编译的程序也没了,需要的时候只能再弄新的了。

偷电脑的贼最可恨了。这不是价值的转移,而是价值的灭失。偷了我的电脑,也不过能卖几千块,给我带来的麻烦又何止这些呢!也幸亏我有备份,要不然,数年的心血就会这么消失了。

Category: 未分类 | Tags:
6
1
2017
45

一个人

一个人吃饭,
一个人上下班
一个人睡觉。

一个人去面试,
一个人回家,
一个人去看病。

一个人在知乎上胡闹,扭曲是非,
一个人在SegmentFault上回答些同样无聊的问题;
一个人提交了一个又一个的 issue,
一个人贡献了一段又一段的开源代码。

一个人默默为喜欢的文章点赞,
一个人静静地写着博客。

一个人听着歌哼唱,
一个人一次次地尝试写诗。

一个人喜欢北岛,
一个人觉得舒婷太美好;
一个人捧着张小娴落泪,
一个人读着刘若英感慨。

一个人为改变世界贡献着微薄之力,
一个人被这个世界慢慢地改变。

一个人哭,
一个人笑,
一个人喜怒无常。

一个人生活,

Category: 未分类 | Tags:
6
1
2017
6

WordPress 被入侵有感

吶,昨天说到VPS被报告有攻击行为,后来异常流量被逮到了,经过追查,真相大白。

前天(还是前天,因为又过了一天)分析了异常流量的时间分布之后,发现该流量从上午9点多开始,一直持续。于是我又 netstat -npt 了一下,看到了大量对各网络公司IP 80端口的连接。它还在呢!其实我早该 htop 瞅一眼的说。

发现三四个「php /tmp/tmp」进程,每个进程有大量连接。试图通过 /proc/PID/fd 取得它打开的文件失败,因为都是空的……然后,为了中止攻击,我停了 php-fpm 服务,杀掉了所有 php 进程。杀完才后悔,我连它是哪个用户跑的都没看呢 -_-||| 心急就忘记了应该发 SIGSTOP 信号的。

时间上与任何 cron job 都不匹配。不过我还是检查了可能的 PHP 脚本任务,并没有发现异常。这时 VPS 的主人 Edison 说,是不是 WordPress 的锅?于是他去扫描了一下 WordPress。这个 WordPress 是数年前 Edison 手工部署的,后来并没有怎么用。并不是通过软件仓库安装,所以 dpkg 没有检查出异常,也因此没有任何更新,年久失修。结果检查出来好多个后门。

这些后门都是在 PHP 脚本里加一两句话,对某个特定的 POST 参数进行求值(eval)。简单有效的后门呢。也难怪我怎么都没有找到恶意程序,原来是直接从网络提交过来的,通过 php-fpm fork 了进程出来。

嗯,就是这样。并没有多么神秘,也没能拿到攻击脚本。这件事充分地说明了我坚持不使用 WordPress 是个无比正确的决定 :-)

其实呢,这种攻击对于 Linux 来说是很奇怪的。就像作为 Python 使用者,SQL 注入对我来说是很奇怪的一样。最不安全的也并不是 PHP,而是 WordPress,核心逻辑上的漏洞不补,再怎么从细枝末节上修补也是软弱无力的。

它丫的 www 用户为什么对 WordPress 的代码文件有写权限啊?

它丫的 www 用户为什么对 WordPress 的代码文件有写权限啊?

它丫的 www 用户为什么对 WordPress 的代码文件有写权限啊?

嗯,重要的事情要说三遍。现在互联网越来越险恶,竟然还有著名软件项目不知道 W^X 这个常识我也是醉了……

一点安全常识,给不懂的人:

  • 任何时候,尽力做到权限最小化(不要学360拿 suid 跑 GUI)
  • Web 网站,用户可写目录一定不能被执行,能够执行的目录执行者一定不能有写权限(你看我的 MediaWiki 曾经暴露在外网那么久也只是招来了一些 spam,从来没人能远程执行代码)
  • 尽量使用发行版提供的软件包,及时更新,特别是对于没有充分时间关注和维护的东西

还有,我还挺想念 systemd 的,直接 systemctl status 就能知道进程属于哪个服务了。而且可以几行配置就把不需要的权限干掉~

Category: Linux | Tags: WordPress PHP Linux
5
31
2017
5

换用 Rust 解析 pcap:快了1000倍!

前天手上有个VPS的提供商发来通知,说VPS在攻击别人的80端口,被第三方投诉了-_-|||

我跑上去看了好久,啥也没发现。进程列表、登录日志、网络使用、各服务我能找到的日志都检查了一遍,也跑了遍 dpkg --verify,啥也没发现。没办法,我就留了句 tcpdump 命令,看看能不能抓到那些恶意流量:

sudo tcpdump -vv -s0 -w port80.log '(dst port 80 and src host 123.456.789.0) or (src port 80 and dst host 123.456.789.0)'

盯了好久,一直只看到有零星的正常访问。

就这么放了一天。第二天想起来的时候过去一看,哇塞,抓到了 2.1G 的包!看来真出问题了。先分析一下得到的 pcap 文件吧。

这么大的 pcap,自然不能拿 Wireshark 打开了。我知道 scapy 能够读取 pcap,于是用它读取 pcap,收集另一方的 IP 地址,看看流量到底都去哪里了。代码很简单,20多行。跑起来~progress 告诉我需要半小时……

于是半小时过去了。我能看到它访问了不少IP,很多是意料之外的。我就想,这些流量是什么时候发生的呢?不过我可不想再花半小时等结果了。然后再想得到点别的数据又得半小时。而且 Python 这种动态类型的语言,稍微复杂一点之后就容易抛异常。说不定快跑出结果的时候,抛个异常出来,前边就全白跑了……

所以就试试 Rust 啦。去 docs.rs 上一搜,还有好几个可以解析 pcap 的库呢。那就第一个吧,叫「pcap」的这个。程序写起来也挺容易的,甚至代码行数都跟 Python 版相仿。时间的处理也是用了一个之前没用过的库「chrono」。并没有花多长时间,直接在文档里找到我需要的功能,然后编译通过就成了 O(∩_∩)O~

extern crate pcap;
extern crate chrono;

use std::collections::BTreeMap;
use chrono::prelude::*;

fn main() {
  let mut capture = pcap::Capture::from_file("../port80.log").unwrap();
  let mut distribution = BTreeMap::new();
  while let Ok(pkt) = capture.next() {
    let t = pkt.header.ts.tv_sec;
    let by_hour = t / 3600 * 3600;
    let dt = Local.timestamp(by_hour, 0);
    *distribution.entry(dt).or_insert(0) += 1;
  }

  for (t, c) in distribution {
    println!("{}: {:8}", t.format("%Y-%m-%d %H:%M"), c);
  }
}

然后花了几秒编译出来优化版本的程序,跑起来~然后我切换到另一个 shell 去跑 progress。啥,没找到进程?再回来一看,哦哦,已经跑完了!只花了一两秒!

这是一千倍的差别啊!虽然两个程序所做的事情并不一样,比如 Python 版用的 scapy,实际上会把整个网络包的各层协议都给解析了,而不像 Rust 版只解析了 pcap 的记录头。但是我又没有别的选择,就像我也不能让 Python 别管引用计数一样。

对于这种小任务,Python 还是强在对少量数据的交互操作,比如 Jupyter Notebook 就很强大。而数据量一旦大起来,就是 Rust 的天下了 :-)

Category: 编程 | Tags: Rust tcpdump
4
23
2017
15

UDP: 谁动了我的源地址?

最近 #archlinux-cn 又流行玩 teeworlds 了,然而我却连不上那个服务器。

情况很奇怪。我能 ping 通服务器 IP,TCP 连接也正常,UDP traceroute 也表现得很正常(对关闭端口能够完成,对开放端口会在最后一跳开始得到一堆星号),并且我连接的时候,服务器能看到我在连接。也就是说,TCP 和 ICMP 都正常,UDP 上行正常,下行出了状况。

难道是有防火墙?首先呢,我能连接其它服务器,说明我这边没有问题;大部分人能连接上服务器,说明服务器那边也没有问题。所以,问题出在路上。也确实有另外的北京联通用户连不上这个服务器。但是很奇怪啊,为什么单单只是这一个 IP 的 UDP 包丢失了呢?

于是继续试验。从最简单的开始,用 netcat / socat 尝试通讯。方向反过来,我监听,服务器那边连接。端口是我在路由器上做过端口映射的。结果是正常的。再来,服务器那边监听,我往那边发,果然我就收不到包了。按理说,UDP 双方是对等的,不应该换了个方向就出问题呀。难道是因为端口映射?Wireshark 抓包看到本地使用的端口号之后,在路由器上映射一下,果然就通了!

然后,我注意到了一件十分诡异的事情:虽然我和服务器能够通讯了,但是我的 Wireshark 上只显示了我发出去的包,却看不到回来的包!我抓包时按服务器 IP 做了过滤,所以,回来的包的源 IP 不是服务器的地址!

重新抓包一看,果然。服务器 IP 是 202.118.17.142,但是回来的包的源 IP 变成了 121.22.88.41……看起来这是联通的设备,在下行 traceroute 时能够看到有节点与它 IP 相似(121.22.88.1)。原来又是这著名的「联不通」又干坏事了 -_-|||

虽然 socat 接收 UDP 时不介意源 IP 变化了,但是 teeworlds 介意啊。并且 NAT 那边也会不知所措。所以,首先得告诉路由器把来自这个 IP 的 UDP 包全部扔给我:

ssh 192.168.1.1 iptables -I FORWARD -i ppp0.2 -p udp -s 121.22.88.41 -j ACCEPT

于是数据包有了。接下来是修正源 IP。我试过 SNAT,无效。这东西似乎只对本地发出的包有用?于是我又用 netfilter_queue 了。这东西很强大呢~一个简单的 Python 脚本搞定:

#!/usr/bin/env python3

from netfilterqueue import NetfilterQueue
from scapy.all import *

def main(pkt):
  p = IP(pkt.get_payload())
  # print('recv', p)
  p.src = '202.118.17.142'
  p.chksum = None
  p[UDP].chksum = None
  pkt.set_payload(bytes(p))
  # print('fixed to', p)
  print('.', flush=True, end='')
  pkt.accept()

conf.color_theme = DefaultTheme()
nfqueue = NetfilterQueue()
nfqueue.bind(1, main)
try:
  nfqueue.run()
except KeyboardInterrupt:
  pass

然后是 iptables 命令:

sudo iptables -I INPUT -s 121.22.88.41 -p udp -j NFQUEUE --queue-num 1 --queue-bypass

scapy 这个神奇的网络库在 Arch 官方源里叫「scapy3k」。Python 的 netfilterqueue 模块需要用我自己修改过的这个版本

2017年7月30日更新:Python 的依赖有点麻烦,所以我又写了个 Rust 版本,放在 GitHub 上了

Category: 网络 | Tags: linux python 网络 iptables Rust
4
15
2017
0

卸载被挂载点掩蔽的挂载点

刚刚遇到一件很囧的事情:我在 /run/user/1000/cache 挂载了我的 SSD,用作火狐和 neocomplete 的缓存。/run/user/1000 这个目录是 systemd 为用户创建的,用来放那些只在运行时有用的文件,比如 pid 文件啦、套接字啦之类的。把挂载点放这里,很显然可以避免为其在磁盘上创建目录,又不必和 /tmp 里的一堆临时文件混在一起。

然而这一次,出大事了!我的火狐启动不了了!我的 Vim 也报了一堆错!细看下来,发现 /run/user/1000/cache 没了……

因为需要 root 权限,/run/user/1000/cache 是在 /etc/rc.local 里挂载的。这一次,它抢先挂载了 /run/user/1000/cache,然后我登录,systemd 帮我挂载了 /run/user/1000。就是下边这个样子:

├─/run                           run        tmpfs          rw,nosuid,nodev,relatime,mode=755                                                shared
│ ├─/run/user/1000/cache         test       zfs            rw,xattr,posixacl                                                                shared
│ └─/run/user/1000               tmpfs      tmpfs          rw,nosuid,nodev,relatime,size=804780k,mode=700,uid=1000,gid=1000                 shared
│   └─/run/user/1000/gvfs        gvfsd-fuse fuse.gvfsd-fus rw,nosuid,nodev,relatime,user_id=1000,group_id=1000                              shared

WTF!这样我就访问不到它了呀!我一开始还以为我的 SSD 又出什么状况了呢,结果是这样,挂载上了,但是访问不到……而因为访问不到,所以也没法卸载……

当然啦,我可以先把 /run/user/1000 和下边的那个 gvfs 给卸载掉。但那样做,我不确定 systemd、PulseAudio、D-Bus 它们会有多生气。bind mount 也尝试了,然而并没有什么用。它只能用来访问被挂载点掩蔽的文件,访问不到被挂载点掩蔽的挂载点。

然后我想到了之前玩过的网络命名空间。当然这次需要的是之前没仔细探索的挂载命名空间了。

直接 sudo unshare -m 进去,findmnt -o+PROPAGATION 发现全部都是 private 的,也就是 umount 了不影响外边。于是我就可以把 /run/user/1000 这个树卸载掉啦。然后 mount --make-shared /run/user/1000/cache 把它变成 shared 状态,再卸载,应该就可以把外边那个也卸载掉了吧?

No。失败了。研究了半天 unshare、mount_namespace 的文档之后确认,把 private 的挂载点变成 shared 之后,会创建一个新的「共享组」,而只有在同一个「共享组」里的挂载点才会相互传播。所以,unshare 你别把我的挂载点都变成 private 了好么?

文档下翻,它还真有这么个选项:

sudo unshare -m --propagation shared

然后里外执行这条命令,确认一下「shared:」后边那个数字是一致的:

# cat /proc/self/mountinfo | grep zfs
282 281 0:47 / /run/user/1000/cache rw shared:137 - zfs test rw,xattr,posixacl

没问题了。先 mount --make-private /run/user/1000 等把它们变成私有的,卸载掉,再把 /run/user/1000/cache 给卸载掉。来外边一看,果然被卸载掉啦~

(然后我还是为其在磁盘上专门建立个目录防止出问题好了。zfs,不知道怎么写 systemd 的 .mount 文件。)

Category: Linux | Tags: linux

部分静态文件存储由又拍云存储提供。 | Theme: Aeros 2.0 by TheBuckmaker.com