利用autossh和中间主机为内网主机建立稳定ssh 连接

通常会遇到一些内网主机没有独立IP 地址,隐藏在NAT 之后,用户无法直接建立ssh 连接。

这时候就需要一个中间人机器(具有独立IP)做为跳板,内网机器反向连接至中间机器。用户登陆时,首先连接至中间机器,再反向连接至内网主机。

其步骤如下:

  1. 在内网主机,运行 ssh -R 7777:localhost:22 [email protected]
  2. 在中间主机,运行 ssh -p 7777 [email protected]

注意:步骤2的user 是内网主机user。

ssh -R 参数中7777 是远端映射的端口,连接该端口将建立起和内网22 号端口的链接;下面是man ssh 中关于-R 选项的说明

-R [bind_address:]port:host:hostport

-R [bind_address:]port:local_socket

-R remote_socket:host:hostport

-R remote_socket:local_socket

-R [bind_address:]port

Specifies that connections to the given TCP port or Unix socket on the remote (server) host are to be forwarded to the local side.

This works by allocating a socket to listen to either a TCP port or to a Unix socket on the remote side. Whenever a connection is made to this port or Unix socket, the connection is forwarded over the secure channel, and a connection is made from the local machine to either an explicit destination specified by host port hostport, or local_socket, or, if no explicit destination was specified, ssh will act as a SOCKS 4/5 proxy and forward connections to the destinations requested by the remote SOCKS client. Port forwardings can also be specified in the configuration file. Privileged ports can be forwarded only when logging in as root on the remote machine. IPv6 ad‐ dresses can be specified by enclosing the address in square brackets.

By default, TCP listening sockets on the server will be bound to the loopback interface only. This may be overridden by specifying a bind_address. An empty bind_address, or the address ‘*’, indicates that the remote socket should listen on all interfaces. Specifying a remote bind_address will only succeed if the server's GatewayPorts option is enabled (see sshd_config(5)).

If the port argument is ‘0’, the listen port will be dynamically allocated on the server and reported to the client at run time. When used together with -O forward the allocated port will be printed to the standard output.

但这样存在两个问题:1)ssh 连接超过固定时间会自动释放;2)每次连接中间机器都需要用户手动输入密码。

第一个问题通过autossh 解决

autossh 通过将ssh 命令包裹至一个循环中,并在ssh 命令断开时自动建立连接,这样就保证了即使内网机器无法访问,也会自动建立和中间主机的逆向连接。autossh 命令格式如下

autossh [autossh options] [ssh options]

即autossh 除了自身参数,其他参数直接用ssh 的即可。

第二个问题通过公钥免密码登录解决:1)内网主机执行ssh-keygen;2)ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]_machine

结合起autossh 和免密码登录,autossh 命令如下:

autossh -o "PasswordAuthentication=no" -o "PubkeyAuthentication=yes" -i ~/.ssh/id_rsa -R 7777:localhost:22 [email protected]

将该命令添加至开机启动模块中实现开机启动。

记一次有趣的Bug – 返回值被截断为32位

上周修改了hdrt 库文件中头文件的include 关系,结果出现了一个有趣的bug。具体表现为:当函数foo() 调用某个函数fun_called(),返回的值ret_val 总是从64位被截断为32位。

首先,在foo() 函数头和func_called 函数尾输出ret_val,确认是返回值是在调用过程中被截断了。

其次,在gdb 中查看生成的代码,发现ret_val所存储的寄存器rax (返回值一般存储在rax寄存器中)在被返回时,被cltq 指令截断了高32 位。

  0x555555565adb <foo+42> callq 0x555555569874<fun_called>
  0x555555565ae0 <foo+47> cltq
  0x555555565ae2 <foo+49> mov %rax,-0x8(%rbp)
  0x555555565ae6 <foo+53> cmpq $0x0,-0x8(%rbp)
  0x555555565aeb <foo+58> jne 0x555555565b2e <foo+125>

最后,查了相关资料https://stackoverflow.com/a/26209434/1424948 可能原因是:

1)函数声明(prototype)中没有fun_called 的声明;

2)没有引用包含fun_called 声明的头文件。

默认情况下,调用函数foo 在不知道被调用函数fun_called 的返回值类型的情况下,会按照int 类型大小的值处理,即32位。

对于这个bug 其实有两个建议:

1 非特殊情况不要用强制类型转换(即type cast),(char *)这类转换会屏蔽很多暴露问题的warning;

2 要查看/消除warning,例如这个bug 实际隐藏在了warning 中,因为返回值被编译当做默认的int 类型返回时,获取返回值变量不是int 时则告警了类型不匹配。

利用/proc精确计算Linux系统的CPU利用率

Linux 系统并未提供直接获取CPU 利用率的接口,一些应用程序通过访问/proc 文件系统中系统的状态统计从而计算得到CPU利用率。常用查看进程及其相关信息的top 命令和htop 命令即属于这一类。

本文简单介绍如何利用/proc/stat 文件计算CPU 利用率,这种经典方法在top 和htop 工具中也被采用,在StackOverflow 也有说明;接着,将上面经典方法获取的CPU 利用率和一种极端情况下的单核单任务的CPU占用率进行比较,单核单任务指的是,在隔离的单独CPU 和核心上只运行该程序(如果你对这有疑问,可以参考下core affinity 、core isolation 和non irqbalance)。

看下图htop 工具的截图:该系统有8个CPU核心,每个核心的CPU利用率位于图上方横向柱状图。8个核心中1-7都是被隔离的,1/3/4/5/6/7每个核心都分配了一个名称为grt_simple_conc_ispc 的线程,每个线程CPU 占用率(CPU%)位于图下方第5 列,可以看出CPU利用率(上方)和每个程序占CPU利用率(下方)结果是有差异的。

方法一

一种最直接计算程序占用CPU 使用率的计算方法是,采样N 次,如果有R 次该程序正在执行,则其占用CPU使用率为 R/N × 100%。

Linux 在/proc/{pid}/stat 文件中记录进程ID 为pid 的进程统计信息(按照类别分为几十个字段,详见 https://man7.org/linux/man-pages/man5/proc.5.html )。如果进程状态字符是R ,则表示进程正在运行(Running),如果状态字符是S ,则表示进程正在睡眠(Sleeping)。

将该方法作为CPU 利用率的前提是:该CPU 核心只运行该进程,尽量少地被中断。

计算CPU 利用率经典方法(方法二)

工具(如top 和htop)定时采样读取/proc/stat 文件内容,该文件记录了每个CPU核心所处不同状态的累计时间,再通过以下公式可计算得到指定核心CPU利用率:

previdle   = previdle + previowait
idle     = idle + iowait
prevnoidle  = prevuser + prevnice + prevsystem + previrq + prevsoftirq + prevsteal
noidle    = user + nice + system + irq + softirq + steal
prevtotal   = previdle + prevnonidle
total     = idle + nonidle
total_delta  = total - prevtotal
idle_delta  = idle - previdle
CPU_usage    = (total_delta - idle_delta)/total_delta

    以prev开头的变量是上一次采用数据,否则是当前采样数据,通过计算两次采样之间的CPU空闲时间(idle_delta)和总计时间(total_delta)得到CPU利用率,这是procs/top 工具默认使用的方法,也是最常见CPU利用率统计方法。

方法三

方法和约束情况类似于方法一,周期性读取/proc/{pid}/stat文件,获取进程用户态时间utime 和核态时间stime,按照如下公式计算CPU利用率

    CPU_usage = (utime+stime)/sample_interval

其中,utime和stime代表进程用户态时间和核态时间,sample_interval是采样间隔。

下面是负载由轻到高,三种方法得到的CPU 利用率:

Linux 内核文档PDF版(Linux kernel v5.8 documents pdf version)

accounting.pdf

doc-guide.pdf

ide.pdf

maintainer.pdf

process.pdf

timers.pdf

admin-guide.pdf

driver-api.pdf

iio.pdf

mhi.pdf

RCU.pdf

trace.pdf

arm64.pdf

fault-injection.pdf

infiniband.pdf

mips.pdf

riscv.pdf

translations.pdf

arm.pdf

fb.pdf

input.pdf

misc-devices.pdf

s390.pdf

usb.pdf

block.pdf

filesystems.pdf

isdn.pdf

netlabel.pdf

scheduler.pdf

userspace-api.pdf

bpf.pdf

firmware-guide.pdf

kbuild.pdf

networking.pdf

scsi.pdf

virt.pdf

cdrom.pdf

fpga.pdf

kernel-hacking.pdf

openrisc.pdf

security.pdf

vm.pdf

core-api.pdf

gpu.pdf

leds.pdf

parisc.pdf

sh.pdf

w1.pdf

cpu-freq.pdf

hid.pdf

PCI.pdf

sound.pdf

watchdog.pdf

crypto.pdf

hwmon.pdf

livepatch.pdf

pcmcia.pdf

sparc.pdf

x86.pdf

devicetree.pdf

i2c.pdf

locking.pdf

powerpc.pdf

spi.pdf

xtensa.pdf

dev-tools.pdf

ia64.pdf

m68k.pdf

power.pdf

target.pdf

你也可以在自己的Linux kernel 源码目录执行以下命令生成自己的pdf,但何必自己造轮子呢。

sudo apt-get install sphinxsearch
sudo apt-get install python-sphinx-rtd-theme
sudo apt-get install texlive-latex-recommended
sudo apt-get install texlive-base
sudo apt-get install graphviz
sudo apt-get install imagemagick
/usr/bin/virtualenv ~/sphinx_version
. ~/sphinx_version/bin/activate
pip install -r Documentation/sphinx/requirements.txt
make pdfdocs

寻找最大容错的扁平XOR 码

参考:

[1] Finding the most fault-tolerant flat XOR-based erasure codes for storage systems [PDF]

[2] Determining fault tolerance of XOR-based erasure codes efficiently [PDF]

本文内容主要来源于上面两篇文章,主要讨论暴力搜寻方法具有最大容错能力(most fault-tolerant)的扁平(Flat)XOR 码。

XOR 码是通过异或运算(exclusive OR)进行数据编码、解码和重建的编码方法,其生成矩阵可以用1/0 二进制矩阵表示。

扁平码是一种每个节点只保存一个符号(数据块),即生成矩阵的每一行(列)对应一个节点。k 和m 分别表示原始数据块个数和校验块个数的话,扁平码的生成矩阵是一个(k+m)×k 大小的0/1 矩阵。常见的RS 码是扁平码,但不是XOR 码;CRS 码不是扁平码,是XOR 码。

暴力搜索通过遍历所有的解空间(所有可能的(k, m) 编码),通过计算每个解的性质,找到满足条件的编码。一个(k+m)×k 大小的0/1 矩阵有2^((m+k)×k) 种可能的二进制矩阵,如果将编码限制在系统码中,则有2^(m×k) 种可能的二进制矩阵。

[2] 中介绍了一种ME (Minimal Erasures)算法,是暴力搜索的关键,该算法通过找到最小失效列表(Minimal Erasure List)寻找某种编码的最大容错能力。ME 算法会用到几个术语:

汉明距离(Hamming distance):如果一个编码的汉明矩阵是d,那么它能容忍的最大失效节点少于d 个。

失效样式(Erasure pattern):一组失效(的数据块)使得有一个原始数据块无法被恢复。

失效列表(Erasure list):所有失效样式的集合。

失效矢量(Erasure vector):长度为m 的矢量,其中第i 个元素是失效列表中长度为i 的失效样式个数的总和。比如 < 0, 0, 3, 7, …> 表示有失效列表中有3 个长度为3 的失效样式,有7 个长度为4 的失效样式。失效矢量中第d 个值是第一个非零值(小于d 的失效均可恢复)。

最小失效(Minimal Erasure,ME)是一种特殊的失效样式,它的每个失效元素都是必须的,如果去掉其中之一,它就不再是失效样式。

最小失效列表(Minimal Erausre List,MEL)是一个编码的所有最小失效的集合。

最小失效矢量(Minimal Erasure Vector,MEV)是一个长度为m 的矢量,其第i 个元素是MEL 中长度为i 的ME 的个数。

ME 算法具体详见[2] ,其输入是编码的生成矩阵(或Tanner 图),输出为MEL 和MEV。下图给出了 m < 8 和 k < 8 的结果。

下图是m < 11 和 k < 21 所有扁平XOR 码的最大汉明距离。

下图是m < 11 和 k < 21 所有扁平XOR 码中具有最优容错的个数。

下图是下图是m < 11 和 k < 21 所有扁平XOR 码中能够达到最大汉明距离的个数。

[1] 还介绍了如何减少搜索的解空间等优化的方法,并指出搜索算法有部分是Python 写的,用C 能够进一步提高运算速度,并增加m 和k 的值。

遗传算法解决数学等式(续)

之前的文章(http://blog.foool.net/2017/08/用遗传算法解决简单的数学等式问题/) 中介绍了过如何使用遗传算法解决数学等式问题,即寻找满足等式

(a + 2b + 3c + 4d) – 30 = 0

的一组解。适应函数确定后,影响算法好坏的主要依赖于交叉概率(crossover ratio),变异概率(mutation ratio)、初始基因组大小(即用于交叉变异的基因个数)和基因初始阈值。基因初始阈值指的是基因的取值范围,比如等式中变量a/b/c/d 的取值范围,我在实验中限定变量为大小不超过100 的整数。下面给出本文的几个结论。

一、遗传算法也可以用于寻找唯一解

之前的文章 中寻找的是四元一次等式的一个解,这个解的个数是无穷的,遗传算法能够找到一个解,但每次找到的解也不相同。但如果要用遗传算法解一个包含四个等式的四元一次方程组(唯一解)也是可行的,只是时间话的需要更长。比如遗传算法解四元一次等式平均需294 次迭代,解四元一次方程组需11400 次迭代(所有参数同之前的文章)。本文使用的四元一次方程组为:

(a + 2b + 3c + 4d) – 30 = 0

(2a + 2b + 2c + 2d) – 24 = 0

(3a + 1b + 7c + 1d) – 60 = 0

(4a + 3b + 2c + 1d) – 30 = 0

二、增加初始基因组大小有可能加速算法速度

增加基因组中基因数量,那么每次迭代/循环中的基因数量更多,可能出现解的概率增大。在以四元一次等式为例的实验中,增加基因组大小的确显著减少了迭代的次数,即使考虑了增加基因数目而带来的增量计算,算法仍减少了程序的整体运行时间。

三、增加初始基因组大小也有可能不能加速算法速度

有点调皮了,这个结论是和第二点结论是相左的,如在其他参数相同情况下,随着基因组大小增大,遗传算法在解四元一次方程组需要迭代的次数增加。

四、对于特定问题,参数大小影响算法性能大

下图是在基因组大小为18 时, 遗传算法1000 次测试四元一次等式求解所需要迭代次数的热力图,横坐标是变异率,从5% 到43%,纵坐标是交叉率,从5% 到70% 。热力图颜色范围是0-300,最深颜色表示300 次或300次以上迭代(比如在交叉率是70%,变异率是43%,实际迭代次数是9268次)。

综上,1.借助于适应函数,交叉、变异过程,遗传算法给出了一个在较大解空间快速求解的途径,但具体设计交叉、变异过程需要考虑特定问题;2.选取合适的参数极大影响了遗传算法性能,一般可以通过相似问题(复杂度更低)的参数选择作为参考(但是这种参考也不一定可靠,比如上面例子中,四元一次方程中增大基因组个数可以减少迭代次数,但在四元方程组中增加基因组个数却增加了迭代次数)。