在工作中我们有一个旧的 C 程序,可以与霍尼韦尔的工业手持终端配合使用。
该终端有自己的 ssh 客户端,用于连接到 Linux Redhat 6.6 服务器。一旦连接到 Linux 机器(使用某个用户),bash shell 就会使用以下参数启动一个 C 程序
export TERM=vt200
stty raw icrnl -echo
$APLI_EXEC/program param1 param2
所以流程如下 => 客户端 ssh --> ssh 服务器-> bash --> c 程序
应用程序(或看起来)运行良好,但有时(每周 1-3-5 次)随机终端停止从服务器接收数据,但应用程序会从服务器接收输入。这就像在 shell 中输入 Ctrl+S 一样。
使用 strace 调试应用程序和 ssh 进程时,我意识到一些奇怪的事情:
应用程序 strace 很好
write(1, "1", 7) = 1
但是 ssh 进程的 strace 不太好(我想...是的,我看到了 ioctl no echo 参数,但是...)
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
read(3, "\227\316\242\350\261\330)\300e\210\352\367\2VX\24\305\2474\272\371\34\273n{\323p.\211\17H\327"..., 16384) = 48
select(14, [3 9], [11], NULL, {900, 0}) = 1 (out [11], left {899, 999996})
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
write(11, "1", 1) = 1
ioctl(11, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 -opost -isig -icanon -echo ...}) = 0
select(14, [3 9], [], NULL, {900, 0} <<<<
ssh进程使用的文件描述符:
lr-x------ 1 root root 64 Feb 15 17:12 9 -> pipe:[383586491]
lr-x------ 1 root root 64 Feb 15 17:12 8 -> /var/lib/sss/mc/group
lrwx------ 1 root root 64 Feb 15 17:12 7 -> socket:[383586484]
lrwx------ 1 root root 64 Feb 15 17:12 6 -> socket:[383586478]
lrwx------ 1 root root 64 Feb 15 17:12 5 -> socket:[383586458]
lrwx------ 1 root root 64 Feb 15 17:12 4 -> socket:[383586457]
lrwx------ 1 root root 64 Feb 15 17:12 3 -> socket:[383585929]
lrwx------ 1 root root 64 Feb 15 17:12 2 -> /dev/null
lrwx------ 1 root root 64 Feb 15 17:12 14 -> /dev/ptmx
lrwx------ 1 root root 64 Feb 15 17:12 13 -> /dev/ptmx
lrwx------ 1 root root 64 Feb 15 17:12 11 -> /dev/ptmx
l-wx------ 1 root root 64 Feb 15 17:12 10 -> pipe:[383586491]
lrwx------ 1 root root 64 Feb 15 17:12 1 -> /dev/null
lrwx------ 1 root root 64 Feb 15 17:12 0 -> /dev/null
在 select 调用中,我错过了 fd #11 或 fd #13
将此与另一个调用进行比较
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
read(3, "\365\354\354C\10|\336-\4\342\327B0P\275&\213)\367\32\24\333)#\364\355V\3\237\337\33\204"..., 16384) = 52
select(14, [3 9 13], [11], NULL, {900, 0}) = 1 (out [11], left {899, 999997})
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
write(11, "a", 1) = 1
ioctl(11, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig -icanon -echo ...}) = 0
select(14, [3 9 13], [], NULL, {900, 0} <<<
另一个调用中的 fd #13 发生了什么?
C 程序是否可能正在做一些事情来锁定 ssh 进程的文件描述符?
手持终端有可能发送 ctrl 键组合来“挂起”标准输出吗?
我没什么主意了。有人能给我指明正确的方向吗?
2016年2月25日
我对此还有更多了解:
select(14, [3 9 13], [], NULL, {900, 0}) = 1 (in [13], left {899, 998835}) <<- sshd realizes about data in fd #13 from C application
read(13, "\33[1;23H1\33[1;24H", 16384) = 15 <<- sshd check data from th fd#13
select(14, [3 9 13], [3], NULL, {900, 0}) = 1 (out [3], left {899, 999998}) <<- sshd sends data to fd#3 (socket)
write(3, "\301\236W\250\333\260\r\204\316o]:*1K\203\242\204\257Vb,V\347l\242\352K\341,,\307d\273\277\202.l\32F\2471\257DJt3\36\303\5\256\21K6\27\212\253\326|l\33\270\262S", 64) = 64 (1) <<- sshd encrypts data to be sent
select(14, [3 9 13], [3], NULL, {900, 0}) = 1 (out [3], left {899, 999998}) <<-- sshd sends data thru the socket
select(14, [3 9 13], [], NULL, {900, 0}) = 1 (in [13], left {899, 998569}) <<- sshd realizes about data in fd #13 from C application
read(13, "\7\33[1;16H \33[6;6H_______\33[7;1H -INFORME CANT. RECOGIDA-\33[7;26H", 16384) = 67 <<- sshd check data from th fd#13
select(14, [3 9], [], NULL, {900, 0}) = 1 (in [3], left {892, 12016}) <<- sshd sends data to fd#3 (socket) but... where is fd#13 where sshd has to read it from?
read it from?
终端接收到“\7\33[1;16H”,但字符串的其余部分“\33[6;6H_______\33[7;1H -INFORME CANT. RECOGIDA-\33[7;26H”
未收到
为什么?
当应用程序写入其输出文件描述符 (fd#1) 时,sshd 会接收它 (fd#13)...但此时 fd#1 是否已被清除?或者 sshd 在发送/加密数据时是否逐个字符地从文件描述符中读取数据?
答案1
我回答我自己说我刚刚发现这个问题我希望以下内容对任何人都有帮助
最后我重新编译了 ssh 源代码(openssh-5.3p1),在代码中插入了几个“陷阱”,看看到底发生了什么。
channel_pre_open(Channel *c, fd_set *readset, fd_set *writeset)
{
u_int limit = compat20 ? c->remote_window : packet_get_maxsize();
int aux = buffer_len(&c->input);
debugtrap("en pre_open c-istate: %d limit %d buffer_len %d c_ostate %d
ctl_fd %d\n",c->istate,limit,aux,c->ostate,c->ctl_fd);
/* the rest of the function code */
开始时的限制变量(默认)为 1024 * 1024
在正常情况下,每次调用 channe_pre_open 函数时,限制变量都会调整其窗口大小(例如使用 putty)
En pre_open c-istate: 0 limit 1048576 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 1048576 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 1048495 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 1048495 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 1048415 buffer_len 0 c_ostate 0 ctl_fd -1
...... time later
En pre_open c-istate: 0 limit 1002267 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 1002267 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 1002267 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 998560 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 998560 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 1048576 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 1048576 buffer_len 0 c_ostate 0 ctl_fd -1
但是,如果我比较手持终端连接到应用程序时的相同跟踪,我可以看到缓冲区正在消耗(它不是每次都重新协商)整个大小
En pre_open c-istate: 0 limit 1048576 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 1048576 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 1048476 buffer_len 0 c_ostate 0 ctl_fd -1
......
En pre_open c-istate: 0 limit 985 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 647 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 647 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 647 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 647 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 632 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 322 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 322 buffer_len 0 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 0 buffer_len 16 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 0 buffer_len 16 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 0 buffer_len 16 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 0 buffer_len 16 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 0 buffer_len 16 c_ostate 0 ctl_fd -1
En pre_open c-istate: 0 limit 0 buffer_len 16 c_ostate 0 ctl_fd -1
最后限制变量变为 0
当发生这种情况时,fd#10 在 select 调用中的 readfds 中丢失
0.000025 select(11, [3 6 10], [3], NULL, {900, 0}) = 1 (in [10], left {899, 999997})
0.000025 select(11, [3 6 10], [3], NULL, {900, 0}) = 1 (in [10], left {899, 999994})
0.000025 select(11, [3 6 10], [3], NULL, {900, 0}) = 1 (in [10], left {899, 999995})
0.000025 select(11, [3 6], [3], NULL, {900, 0}) = 1 (out [3], left {899, 908736})
0.000026 select(11, [3 6], [3], NULL, {900, 0}) = 1 (out [3], left {899, 986906})
0.000025 select(11, [3 6], [3], NULL, {900, 0}) = 1 (out [3], left {899, 992061})
问题是 select 调用没有将此文件描述符包含在集合中,因为它被另一端阻止,直到缓冲区(客户端 <-> sshd)为空(它假设 sshd 无法向 ssh 客户端发送更多字节,因为窗口大小为 0,因此必须阻止 fd 以防止从 shell 端发送更多信息)
使用 putty 客户端时不会发生这种情况,它似乎是基于 Openssh 的 Honeywell 手持终端的相关 ssh 客户端(不知道版本)
无论如何,我刚刚确认了以下版本:OpenSSH_3.8.1p1,OpenSSL 0.9.7d 2004 年 3 月 17 日不受影响(在 Windows 10 上针对 RHEL6 ssh-server 5.3.p1 进行了测试)
纳乔。