leuisken.github.io icon indicating copy to clipboard operation
leuisken.github.io copied to clipboard

一个 Node.js 在 CentOS 6 上启动卡死问题的分析和解决

Open LeuisKen opened this issue 4 years ago • 1 comments

昨天晚上接到用户反馈,我们提供的一个脚本执行卡死。这个脚本里面会下载一个我们编译好的 Node.js 12.13.0,并用它来执行一个 js 脚本。经过初步定位,发现是卡死在了 node tool.js 这一句,非常奇怪,于是对这个问题进行了追踪。

遇事不决先升级

首先升级到了 Node.js 的 12.16.1 版本,但还是会卡死,不行。

尝试远程调试

初步的猜想,可能是 tool.js 里面的逻辑哪里有问题,于是让用户执行以下命令:

node --inspect-brk=0.0.0.0:8080 tool.js

打算远程调试,但是发现远程调试无法连接到用户机器上面。同时用户反馈说,执行后控制台没有任何输出,而如果调试能启动,那么至少会有如下的输出。

image

由于 --inspect-brk 能帮我们排除掉所有 js 逻辑导致的问题,那么说明在执行 js 前,程序就已经卡死了。

分析 strace 日志

strace 是 linux 下的一个命令行工具,我的理解是,它能拦截到程序执行的所有系统调用并记录日志。

在用户机器上面,使用 strace 执行 node,命令为strace ./bin/node -e "console.log(1)" &> a.log,得到 strace 日志,日志最后两行如下:

open("/dev/random", O_RDONLY)           = 17
select(18, [17], NULL, NULL, NULL <unfinished ...>

关注到最后的调用,程序在执行 select 的时候卡住,问题找到了。并且通过前面的分析,select 是由 node 在 js 前的某些逻辑调用的,这个时候就要下载 node 的源码看看了。

我也是第一次去看 node 的代码,先搜 select,没有找到有意义的结果。而 log 前一行打开了 /dev/random 文件,再搜这个关键词,找到了下面这里:

// Ensure that OpenSSL has enough entropy (at least 256 bits) for its PRNG.
// The entropy pool starts out empty and needs to fill up before the PRNG
// can be used securely.  Once the pool is filled, it never dries up again;
// its contents is stirred and reused when necessary.
//
// OpenSSL normally fills the pool automatically but not when someone starts
// generating random numbers before the pool is full: in that case OpenSSL
// keeps lowering the entropy estimate to thwart attackers trying to guess
// the initial state of the PRNG.
//
// When that happens, we will have to wait until enough entropy is available.
// That should normally never take longer than a few milliseconds.
//
// OpenSSL draws from /dev/random and /dev/urandom.  While /dev/random may
// block pending "true" randomness, /dev/urandom is a CSPRNG that doesn't
// block under normal circumstances.
//
// The only time when /dev/urandom may conceivably block is right after boot,
// when the whole system is still low on entropy.  That's not something we can
// do anything about.
inline void CheckEntropy() {
  for (;;) {
    int status = RAND_status();
    CHECK_GE(status, 0);  // Cannot fail.
    if (status != 0)
      break;

    // Give up, RAND_poll() not supported.
    if (RAND_poll() == 0)
      break;
  }
}

代码位于:https://github.com/nodejs/node/blob/v12.16.1/src/node_crypto.cc#L426

这就能看出注释写的好的好处了。尽管我是第一次看 Node.js 的代码,但是这里的注释帮助我很快猜测到了可能出现问题的原因。

Ensure that OpenSSL has enough entropy (at least 256 bits) for its PRNG.

大意是保证 OpenSSL 有足够大的熵池用于 PRNG。

这里有遇到了很多新的概念,PRNG 是啥、熵池是啥,不过至少我们有了关键词,即熵池。那么剩下的问题就是,了解新概念的知识背景,如果操作这个新概念相关的东西,进而解决我们的问题。

花了不少时间给自己科普了相关概念,并最终采用这篇文章中( https://blog.csdn.net/fukai8350/article/details/80429978 )提到的方法解决了问题。解决方案总是很简单的。简单说一下我自己的理解,如有不对欢迎拍砖,毕竟就是为了解决问题大概看的,没有深入了解。

熵池是 linux 里面用于生成随机数的一个东西,相当于是随机的源头,这个源头可以是硬件,也可以是软件。可以用如下命令查看熵池大小:

cat /proc/sys/kernel/random/entropy_avail

在用户机器上执行了一下,果然是 0。

那么如何增加的,在用户的机器上,熵池是空的,执行下面的命令就可以填上了。

sudo rngd -r /dev/urandom

执行完再看看熵池的大小。

image

问题解决。

后记

解决完问题后,才想起去翻翻 Node.js 的 issue,果然还是有一些 close 掉的 issue 说了这个事情。应该早点去翻翻 issue 的。

LeuisKen avatar Mar 03 '20 10:03 LeuisKen

The entropy pool starts out empty and needs to fill up before the PRNG can be used securely. Once the pool is filled, it never dries up again;

OpenSSL normally fills the pool automatically

从这两句来看的话,OpenSSL应该负责填那个熵池,而且填了之后不会清空。看起来好像它没有干这个事情。 不过这个可能就是更深的问题了。。

yuxuan avatar Mar 04 '20 02:03 yuxuan