8
14
2017
0

我为什么离开新浪

时隔两年,终于还是来写这篇文章了。

其实之前已经想写过多次,但是部分内容写起来十分消耗精力,所以一直没动手。但现在无所谓了。

名字问题

每个人都有名字。大部分人的名字都不是自己的,因为那是父母起的。自己相信它是自己的,然后不知道是它真的变成了自己,还是自己变成了它

然而我不要变成它。

在自由之前,呃,也就是经济独立、远离父母之前,我其实对于我自己是没有太多感受的。只是不断地按照他们的要求或者自己的兴趣,学习科学知识,上学考试,吃饭睡觉。甚至给仇人下跪。

后来,我终于自由了。至少是在客观上自由了。我开始渐渐地觉察到自己的存在,自己的需求、自己的感受。我开始变成真正独立自主的人。

然而,那个人给起的名字,却总是提醒我,曾经的伤痛,曾经只能把自己的想法深深地埋藏在心底不敢说出来,曾经不怎么敢说话,怕稍一不小心,哪句话就会激怒那个人。曾经为了生存,我尽量降低自己的存在感,好不招惹麻烦。

那是我不愿意面对的过往。我宁愿永远忘记它们,当作一切没有发生过。但是,看到那个人给起的看似威武、实则俗气的名字,我经常会想起那个人,想起那个人的凶狠,想起那些黑暗的日子,想起那些我憎恶暴力的缘由。

所以我非常厌恶实名制。支付宝有个界面会显示身份证姓名,所以不管它说有多少多少优惠我都不去。招商银行 app 开始在主界面显示身份证姓名之后,我也开始尽量避免使用它。

我之前的工作,都不太需要用到身份证上的名字。其实同事之前不用很容易啦。但是在新浪,每天早上一到公司,各种登录名都是那个名字的拼音。而且密码是动态密码,必须手动输入,所以通过编程自动登录是行不通的。

想想看,每天一早,你就不得不面对自己最想遗忘的事情,你还能好好地工作吗?

我跟 HR 的人反映过登录名的问题,然而并没有得到明确的回复。

我当然考虑过改名的事情了。我之前已经在知乎上问过相关问题。我也在利用不多的机会探听父母目前对我改名字的意见。我在意父母的意见,不是因为我有多在乎他们,而是因为改名需要的户口本在他们手里,而且我得在他们附近去办手续。目前看来还好,但是惨痛的经验使我明白,那个人是完全不能以理智所理解和预测的,所以我还是得做好应对最坏的情况的准备。秋天回去改名吧,夏天太热,心力不足,我怕出事。也不知道拿到新身份证之前坐高铁会不会有问题。

跳板机

新浪使用跳板机,上边跑个 script 程序记录用户操作。设计者的意思是,你们只需要在服务器上手敲命令就可以了。但是我需要传文件啊!需要跑脚本啊!在新浪,想往服务器上传个文件,需要以下步骤:

  1. 使用一个脚本把文件上传到专门的服务器
  2. 在跳板机上使用另一个脚本下载文件
  3. 使用 scp 命令把文件传到目标服务器

多了两步。而且这些步骤没办法使用脚本处理,因为没办法从本地在跳板机上执行命令。script 程序的存在使得无法通过 ssh 命令直接执行命令。

有人说,把所有操作放服务器上操作就好了嘛。可是给我服务器的 root 权限来装各种工具吗?而且 CentOS 6 的软件都死旧死旧的,给我换成 Arch?还是自己装需要的版本,然后再也不升级,直到遇到CVE-2017-1000117然后被入侵?然后装好图形界面我 VNC 过去?不然我怎么让服务器里的 Vim 控制我本地的输入法呢?怎么给浏览器发 URL 呢?怎么复制粘贴大段文字呢?

当然了,这个跳板机是不支持密钥登录这么方便的方式的。也不支持普通密码登录。动态密码,看你还怎么用脚本!

这就像,明明你有架飞机可以很快抵达目的地,但是对方规定你必须在他们边境上滚下来,换上马车慢慢走到天荒地老。

不测试,直接上线

开发完毕,然后呢?上线!

没有测试环境。连本地测试环境都没有,因为没人知道那东西怎么搭。真真正正的 push to deploy。连编译期检查都难。颤抖吧,开发者!敲错了一个字符,直接影响到至少 1/3 的用户!

我喜欢 Rust。在 Rust 之前我喜欢 Haskell,虽然它很难。这两门语言的特点是,类型系统很强大,以至于很多时候,类型检查通过了,代码就是正确的。我更喜欢 Rust 的原因之一是,Haskell 有异常。有时候明明通过了类型检查,但是跑起来,BOOM!文件打不开,或者取了空列表里的元素,就崩掉了。在 Rust 里我不可能会忘记处理它们。

我喜欢有信心地开发。开发完成,测试通过,上线。回家安安稳稳地睡觉,休息日放心大胆地不想不看工作上的事情。甚至开发完成,我就可以忘记它的细节,因为我不需要不断地去修各种不小心导致的 bug。我喜欢把 bug 消灭在襁褓之中,而不是等着它潜伏下来,趁人不备狠咬我一口。

所以我讨厌 PHP 扩展的开发。没有文档,只能翻源码。然而我一时半会看不完近千万行的代码。所以我开发完之后根本没把握自己开发的东西能够一直正确地运行下去。我不知道换下一个版本的 PHP 它会不会崩掉。我用 valgrind 检查了,但是我仍然不知道我是不是正确地释放了内存,有没有遗漏,有没有释放了不该我释放的内存。

可是,连最基本简单的测试都没有,我有什么理由认为我的代码不会出问题呢?连 Rust 都要有测试,何况 C?我又不是高德纳,能够洋洋洒洒写一大段代码还没有问题。我刚写好的代码,有明显的错漏也是很正常的事情。软件项目是工程,又不是艺术。我只要在交付的时候保证质量就好了嘛,为什么要求一次就写对呢?

结语

当然,离开新浪还有其它一些因素,我就不说了。

本文关闭评论,因为有些事情,我不介意公开,但并不想持续被提及。

Category: 未分类 | Tags:
8
13
2017
7

师者不师,学生不学

忆往昔学生生涯,师者不师,学生不学。

小学二、三年级,数学老师、班主任刘静,年轻女性,脾气暴躁,喜欢用教鞭打讲台上的粉笔,也喜欢体罚。她女儿吴灿也在班上,经常被拖出来家暴。

五、六年级,微机课老师张攀,喜欢成绩优秀的小女生,不喜欢我。问问题当没听见,稍微犯点错就被吼。

四——六年级数学老师曺某,教书死板。试卷要求填空 1/7 < ___ < 1/6。标答是 13/84。而我填 2/13,被判错。然后曺某当着全班同学的面证明 a/b < (a+c)/(b+d) < c/d 不成立,怎么办呢?举例子算。然后算的时候给算成 (a+c) / bd,发现值太小了。于是成功证明 2/13 要么大于 1/6,要么小于 1/7。后来中学的时候,学了不等式,我还专门花时间证明过我的方案是普适的。可是有什么用呢?

初中数学老师兼主任,喜欢揪人耳朵,喜欢叫学生去批阅作业。白打工、没有好处。

初中语文老师、班主任,有次考试我没能按时起床,骑着摩托跑来接我,丝毫不顾我还没睡醒,对发生的事情完全不能理解。

高一英语老师,知识渊博、讲课旁征博引,高二时走了。理由是:眼睛总是红红的,被认为有红眼病;同时教高三太忙。

高二、三英语老师,经常被我质疑试卷的标答。为了维护标答的神圣性,扯各种牵强的理由。

高中语文老师,建议我们多看文学杂志。后来上复习、自习课我看杂志被她收走,看完了再还过来……

高三,学校对高三学生关闭图书馆借阅权限。理由是让学生专心读书备考。

高三,之前一起打乒乓球的伙伴都专心做习题去了。

大学,网页设计课,大作业。我做了纯 CSS3 菜单,以及几个 JavaScript 脚本网页,精心制作了页面内容。但可能因为主题仿照 WordPress 默认主题,又因为紧张讲解得不好,只得了85分。

大学,数据库课。老师心血来潮,在课堂上演示 SQL 查询,结果试了好多次,SQL Server 都报错,无法执行。

大学,网络课老师实践经验丰富、讲课趣味十足。唯一不足的是,网络课内容一展开就丰富无比,他讲课的时间不够用了……然后学生们不喜欢,因为他讲课内容丰富多彩,又不划重点,学生们不知道该为学分背诵哪些内容。

大学,编译原理课,我准备好好学习。但一开始有一个重要的公式有印刷错误,因为没睡好所以去得晚坐后排,教室里闹哄哄的,我一不留神没听清老师说公式要怎么改。于是后边的内容都无法理解了。整个编译原理课就废掉了。

大四,面向对象课试听。老师的观点和讲解非常具有启发性。但是选课学生寥寥无几,因为大部分学生的学分已经够了。

师者不师,学生不学。悲矣。

Category: 未分类 | Tags: 随思 教育
8
11
2017
2

谁又用掉了我的磁盘空间?——魔改 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
0

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
28

电脑被盗事件

半个多月前,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
44

一个人

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

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

一个人在知乎上胡闹,扭曲是非,
一个人在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

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