【问题标题】:libssh2: What to do with unsolicited data from the ssh server?libssh2:如何处理来自 ssh 服务器的未经请求的数据?
【发布时间】:2014-04-23 23:43:55
【问题描述】:

我有一个程序使用 libssh2 来管理一些 Linux 机器。这相当简单:它连接到 Linux 机器,下载配置文件,然后保持 libssh2 连接打开(和空闲),以便如果用户按下 GUI 上的按钮,它可以根据需要向 Linux 机器发送适当的 shell 命令。

它使用非阻塞 I/O(通过 libssh2_session_set_blocking(session, 0)),因此网络 I/O 可以由单个线程处理,而会话 A 的活动不会被我的会话 B 阻塞读取或写入等。

这一切都很好。我说主要是因为只有当程序同时连接到许多(即几十个)盒子时才会出现一个奇怪的问题。发生的情况是会话都像往常一样连接,并且配置文件已成功下载,并且连接都像预期的那样处于空闲状态。但是在一些会话中,在下载完成后的几毫秒(即,在我已经 libssh2_channel_read()'d 所有配置文件字节之后,并且在 libssh2_channel_close() 成功之后),一些额外的数据字节(通常是 104 或140) 出现在会话的 TCP 套接字上,可供我读取并传递给 libssh2。

此时我有一个问题,因为(据我所知)libssh2 会话应该是空闲的,而且我不知道如何处理这些未经请求的数据字节。我的选择都不是真正令人满意的:

  • 我可以忽略字节,但是 select() 会一直唤醒,因为字节在那里,这会导致线程旋转,只要程序打开,我的 CPU 使用率就会显着上升,所以这不好。

  • 我可以停止 select()-ing 准备读取具有额外数​​据的套接字,但是我无法判断套接字是否被远程对等方关闭,我想成为能够在发生这种情况时通知用户。

  • 我可以只 recv() 多余的字节并将它们扔掉,但这显然会破坏 libssh2 连接状态机的状态,因为下次我尝试使用它时(例如,当用户希望我使用它时)向机器发送 ssh 命令)该命令不会被发送。

  • 我可以执行 sleep(1) 或关闭连接并重新连接,但对于我认为应该正确处理的问题,这些都是丑陋的变通方法。

所以我的问题是

  • 这些奇怪的未经请求的字节是什么?

  • 我应该调用什么函数来将它们传递给 libssh2 以使其快乐?

ps 以下是一些神秘接收字节的示例,通过使用带有 MSG_PEEK 标志的 recv() 获得:

--- peek'd data from session #1 (104 bytes): --------------------------------------------
0000: ..#|)62.!.A..... [ab 1f 23 7c 29 36 32 e8 21 cf 41 91 88 de 06 a4]
0016: xc.??n.+aJLS..di [78 63 f2 3f 3f 6e 16 2b 61 4a 4c 53 ab aa 64 69]
0032: ...d.]f.P...4;.3 [c2 d6 9e 64 b4 5d 66 db 50 ba 90 82 34 3b cc 33]
0048: cO"..5..Fr...Yy. [63 4f 22 ba b1 35 9c 00 46 72 a6 9c bb 59 79 a1]
0064: ..L....._..1.>.K [d2 fe 4c 2e e4 81 eb fd 5f 8e f2 31 da 3e c0 4b]
0080: ...........(d... [1d af df 0d 0f d1 ef 1e 07 d1 9f 28 64 f4 07 d3]
0096: 0...sl..         [30 b6 ee f7 73 6c cd 85]

--- peek'd data from session #2 (104 bytes): ----------------------------------------------
0000: g.5"....Q....... [67 d9 35 22 85 90 ab eb 51 95 11 0c e6 ca 9f de]
0016: ...)... .6V&.lkF [ed 04 cb 29 e9 87 95 20 85 36 56 26 a9 6c 6b 46]
0032: ...m).V....JS... [ce b2 d7 6d 29 bb 56 fc 8e 89 a2 4a 53 a9 02 19]
0048: .w1........kY... [a1 77 31 a1 fb f8 b7 94 ee e0 d3 6b 59 ea cc ae]
0064: ...~..[..vZ..... [e3 e8 f2 7e 2e 89 5b c4 82 76 5a da ff b6 ae 91]
0080: ......7.0Z..6g.J [8a cb c8 fc eb e1 37 8e 30 5a e5 b8 36 67 c7 4a]
0096: '...qJS.         [27 da b8 8f 71 4a 53 ef]

--- peek'd data from session #3 (140 bytes): -------------------------------------------------
0000: ...u.d.E..>a.... [04 c3 b1 75 e5 64 d1 45 9e b0 3e 61 81 e9 9b b7]
0016: ..\.....n..D..e. [e4 a2 5c b5 9e da a9 b9 6e 96 b7 44 12 bd 65 d9]
0032: .|(Jp..,k.....r' [c0 7c 28 4a 70 15 90 2c 6b 01 02 1a e6 d1 72 27]
0048: ..%..R]E:...N.CU [0b 8c 25 eb cd 52 5d 45 3a c4 12 f2 4e 11 43 55]
0064: gix$...d:.m..Ps1 [67 69 78 24 a1 e9 85 64 3a d5 6d 91 1a 50 73 31]
0080: Z.]...8#...q..AW [5a ca 5d db 1d 1d 38 23 0e 05 99 71 98 d8 41 57]
0096: ..]....U.7...J.? [ee 86 5d d1 0c b8 ce 55 f6 37 b5 1c 0f 4a b2 3f]
0112: ...10.......h(.* [17 15 ee 31 30 ea ee e0 b9 07 d1 c9 68 28 83 2a]
0128: ....>..g9...     [be c2 e4 f9 3e e2 ea 67 39 0f b2 8c]

【问题讨论】:

  • 你能发布意外的字节吗?将它们编辑到您的问题中。
  • @EJP 完成(这里还有一些输入可以使此评论足够长)

标签: c libssh2


【解决方案1】:

正确关闭 SSH 通道涉及更多步骤,而不仅仅是在一侧“关闭它”。


首先,libssh2_channel_close() 的文档说它发送“一个 SSH_MSG_CLOSE 数据包到远程主机,作为指示不会向它发送更多数据”,这没关系,但也“远程主机仍然可以发回数据,直到它发送自己的关闭消息作为响应”。

因此,此命令后应跟libssh2_channel_wait_closed(),它会处理“直到远程主机关闭指定频道”的其余通信。


另外,如所述,例如在this libssh2 commit 的评论中,“在 RFC 4254 中明确允许在没有通道 EOF 的情况下发送 SSH_MSG_CHANNEL_CLOSE,但是当通道在 EOF 之前关闭时,一些不合格的服务器会挂起或超时”。

因此,在关闭操作本身之前执行 libssh2_channel_send_eof()libssh2_channel_wait_eof() 的另一个组合可能也是为了兼容性。

从 libssh2 版本 1.2.5(2010 年 4 月 13 日)开始,由于that commit mentioned earlier,如果尚未发送,libssh2 应该在关闭时自动发送 EOF。


libssh2 本身有一些例子(比如scp_write.c),它们显式地进行了闭包,看起来像这样:

libssh2_channel_send_eof(channel);
libssh2_channel_wait_eof(channel);
libssh2_channel_wait_closed(channel);
libssh2_channel_free(channel);

(当然,以上所有函数只需要多次调用,只要它们为非阻塞套接字返回LIBSSH2_ERROR_EAGAIN。)

【讨论】:

  • 我对服务器端没有任何控制权,它只是标准的 Linux sshd。
  • 是的,我不建议在服务器端进行任何更改。就在你调用libssh2_channel_close() 之后,也调用libssh2_channel_wait_closed() 并在额外数据进来时再次调用它。
  • 嗯……我试过了;我第一次调用 libssh2_channel_close() 它返回 -37/EAGAIN;然后我第二次调用它,它返回 0(即成功);所以然后我调用 libssh2_channel_wait_closed() 并返回 -34/EINVAL 并且错误字符串是“当通道不处于 EOF 状态时调用 libssh2_channel_wait_closed()”。我不确定这是否意味着 libssh2_channel_wait_closed() 是不必要的,或者我只是做错了什么......
  • 嗯,也许您也需要先发送 EOF 信号——我看到人们按以下顺序执行断开连接:libssh2_channel_send_eof(); libssh2_channel_close(); libssh2_channel_wait_closed(); libssh2_channel_free(); -- 或者有时使用 ..._wait_eof() 而不是 _close(),但总是先使用 send_eof()。
  • 好的,我尝试了一些方法,现在我有一个似乎可以工作的序列:下载完成后,我调用 libssh2_channel_send_eof(),然后调用 libssh2_channel_wait_eof(),然后调用 libssh2_channel_close(),然后调用 libssh2_channel_free() -- 使用这个序列,我没有得到任何 CPU 旋转 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多