我正在构建一个小型库,它使用libssh
登录远程主机,并在远程shell上发出一些命令。我基本上遵循教程,因为我连接会话,验证主机和&用户,然后打开一个频道,并请求一个shell(我不请求一个PTY)。
在此之后,我通过将它们作为普通字符串写入通道来触发命令,然后执行\n
命令执行(1)。在每个命令之后,如果字节可用,我将从stdout和stderr流中读回。
由于一些奇怪的原因,我的测试程序总是以相同的顺序执行相同的命令,在命令的同一步骤会遇到一个封闭的通道。在这种情况下,ssh_channel_is_open(channel)
将返回false,并且尝试进一步读取/写入通道将失败。
libssh的调试输出给了我:
[2017/05/04 15:32:38.763606, 3] ssh_socket_unbuffered_write: Enabling POLLOUT for socket
[2017/05/04 15:32:38.763611, 3] packet_send2: packet: wrote [len=44,padding=11,comp=32,payload=32]
[2017/05/04 15:32:38.763615, 3] channel_write_common: channel_write wrote 23 bytes
wrote bytes: [npm install 2>/dev/null]
[2017/05/04 15:32:38.763643, 3] packet_send2: packet: wrote [len=28,padding=17,comp=10,payload=10]
[2017/05/04 15:32:38.763646, 3] channel_write_common: channel_write wrote 1 bytes
[2017/05/04 15:32:38.763656, 3] ssh_socket_unbuffered_write: Enabling POLLOUT for socket
wrote bytes: [
]
[2017/05/04 15:32:38.763666, 3] packet_send2: packet: wrote [len=60,padding=8,comp=51,payload=51]
[2017/05/04 15:32:38.763669, 3] channel_write_common: channel_write wrote 42 bytes
[2017/05/04 15:32:38.763690, 3] ssh_socket_unbuffered_write: Enabling POLLOUT for socket
wrote bytes: [echo "$? 9903373aee394f16a077720cc5f58e80"]
[2017/05/04 15:32:38.763698, 3] packet_send2: packet: wrote [len=28,padding=17,comp=10,payload=10]
[2017/05/04 15:32:38.763770, 3] channel_write_common: channel_write wrote 1 bytes
[2017/05/04 15:32:38.763800, 3] ssh_socket_unbuffered_write: Enabling POLLOUT for socket
wrote bytes: [
]
[2017/05/04 15:32:38.763812, 3] ssh_channel_read_timeout: Read (1024) buffered : 0 bytes. Window: 1278915
[2017/05/04 15:32:42.386054, 3] ssh_packet_socket_callback: packet: read type 94 [len=60,padding=15,comp=44,payload=44]
[2017/05/04 15:32:42.386072, 3] ssh_packet_process: Dispatching handler for packet type 94
[2017/05/04 15:32:42.386078, 3] channel_rcv_data: Channel receiving 35 bytes data in 0 (local win=1278915 remote win=2096537)
[2017/05/04 15:32:42.386082, 3] channel_default_bufferize: placing 35 bytes into channel buffer (stderr=0)
[2017/05/04 15:32:42.386086, 3] channel_rcv_data: Channel windows are now (local win=1278880 remote win=2096537)
read bytes: [0 9903373aee394f16a077720cc5f58e80
]
read bytes: []
------
[2017/05/04 15:32:42.386131, 3] ssh_socket_unbuffered_write: Enabling POLLOUT for socket
[2017/05/04 15:32:42.386141, 3] packet_send2: packet: wrote [len=28,padding=12,comp=15,payload=15]
[2017/05/04 15:32:42.386145, 3] channel_write_common: channel_write wrote 6 bytes
wrote bytes: [pwd -P]
[2017/05/04 15:32:42.386155, 3] packet_send2: packet: wrote [len=28,padding=17,comp=10,payload=10]
[2017/05/04 15:32:42.386158, 3] channel_write_common: channel_write wrote 1 bytes
[2017/05/04 15:32:42.386168, 3] ssh_socket_unbuffered_write: Enabling POLLOUT for socket
wrote bytes: [
]
[2017/05/04 15:32:42.386178, 3] packet_send2: packet: wrote [len=60,padding=8,comp=51,payload=51]
[2017/05/04 15:32:42.386183, 3] channel_write_common: channel_write wrote 42 bytes
[2017/05/04 15:32:42.386190, 3] ssh_socket_unbuffered_write: Enabling POLLOUT for socket
wrote bytes: [echo "$? 9903373aee394f16a077720cc5f58e80"]
[2017/05/04 15:32:42.386199, 3] packet_send2: packet: wrote [len=28,padding=17,comp=10,payload=10]
[2017/05/04 15:32:42.386202, 3] channel_write_common: channel_write wrote 1 bytes
[2017/05/04 15:32:42.386210, 3] ssh_socket_unbuffered_write: Enabling POLLOUT for socket
wrote bytes: [
]
[2017/05/04 15:32:42.386225, 3] ssh_channel_read_timeout: Read (1024) buffered : 0 bytes. Window: 1278880
[2017/05/04 15:32:42.386319, 3] ssh_packet_socket_callback: packet: read type 96 [len=12,padding=6,comp=5,payload=5]
[2017/05/04 15:32:42.386326, 3] ssh_packet_process: Dispatching handler for packet type 96
[2017/05/04 15:32:42.386331, 3] channel_rcv_eof: Received eof on channel (43:0)
[2017/05/04 15:32:42.386334, 3] ssh_packet_socket_callback: Processing 104 bytes left in socket buffer
[2017/05/04 15:32:42.386341, 3] ssh_packet_socket_callback: packet: read type 98 [len=44,padding=18,comp=25,payload=25]
[2017/05/04 15:32:42.386344, 3] ssh_packet_process: Dispatching handler for packet type 98
[2017/05/04 15:32:42.386349, 3] channel_rcv_request: received exit-status 0
[2017/05/04 15:32:42.386352, 3] ssh_packet_socket_callback: Processing 36 bytes left in socket buffer
[2017/05/04 15:32:42.386358, 3] ssh_packet_socket_callback: packet: read type 97 [len=12,padding=6,comp=5,payload=5]
[2017/05/04 15:32:42.386361, 3] ssh_packet_process: Dispatching handler for packet type 97
[2017/05/04 15:32:42.386365, 3] channel_rcv_close: Received close on channel (43:0)
read bytes: []
ERROR: CHANNEL IS CLOSED, trying to REOPEN....
我添加了调试消息以显示从通道写入和读取的字节。如您所见,命令npm install 2>/dev/null
起作用,因为之后通道仍然完好无损。然后,当将pwd -P
和以下分隔符回显写入通道时,通道将被破坏,无法再读取。
我对此有一些基本的误解吗?这里出了什么问题?
[EDIT2] 这是完整的源代码:
#include <libssh/libssh.h>
#include <stdlib.h>
#include <string>
#include <stdio.h>
#include <list>
enum ReturnValues
{
no = 0,
yes = 1,
na = -1,
fail = na,
success = 0,
done = success
};
enum Errors
{
errFirst = -100,
errNotConnected,
nErrors
};
//-----------------------------------------------------------------
// class SshClient
//-----------------------------------------------------------------
struct Buffer
{
Buffer() { buffer = 0; bufferLen = 0; resize(1024); memset(buffer, 0, bufferLen); }
virtual ~Buffer() { free((void*)buffer); }
int resize(int newSize);
char* buffer;
int bufferLen;
};
struct ServerResponse
{
ServerResponse() { code = 0; }
int code;
std::string error;
Buffer stdout;
Buffer stderr;
};
int connect(const char* host, const char* user, const char* password = 0, int port = 22, int aVerbosity = SSH_LOG_NOLOG);
struct ServerResponse* execute(const char* command);
int writeBytes(const char* bytes, int len);
int readBytes(Buffer* aBuffer, int fromStderr, const char* until = 0);
int checkResponse(ServerResponse* response);
ssh_session session;
ssh_channel channel;
const char* commandSeparator = "9903373aee394f16a077720cc5f58e80";
const char* commandSeparatorWithLF = "9903373aee394f16a077720cc5f58e80\n";
const char* commandSeparatorWithEchoStatus = "echo \"$? 9903373aee394f16a077720cc5f58e80\"";
int verbosity = SSH_LOG_NOLOG;
int main(int argc, const char* argv[])
{
int res = success;
res = connect("some-host", "some-user");
if (res)
return res;
checkResponse(execute("bash -l"));
checkResponse(execute("date"));
checkResponse(execute("pwd -P"));
checkResponse(execute("pwd -P"));
checkResponse(execute("pwd -P"));
checkResponse(execute("cat /etc/passwd"));
checkResponse(execute("pwd -P"));
checkResponse(execute("pwd -P"));
checkResponse(execute("cd /some/dir/with/nodejs/script"));
checkResponse(execute("npm install"));
checkResponse(execute("pwd -P")); // channel broken here
return 0;
}
int checkResponse(ServerResponse* response)
{
if (!response)
return fail;
printf("------\nServer response: Code (%d)\n", response->code);
printf("Server output: [%s]\n", response->stdout.buffer);
printf("Server error: [%s]\n", response->stderr.buffer);
return success;
}
//-----------------------------------------------------------------
// connect
//-----------------------------------------------------------------
int connect(const char* host, const char* user, const char* password,
int port, int aVerbosity)
{
int res = SSH_OK;
// verbosity = SSH_LOG_FUNCTIONS; // aVerbosity
session = ssh_new();
if (session == NULL)
return fail;
ssh_options_set(session, SSH_OPTIONS_USER, user);
ssh_options_set(session, SSH_OPTIONS_HOST, host);
ssh_options_set(session, SSH_OPTIONS_PORT, &port);
ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
res = ssh_connect(session);
if (res != SSH_OK) return fail;
if (password)
res= ssh_userauth_password(session, NULL, password);
else
res= ssh_userauth_publickey_auto(session, NULL, NULL);
if (res != SSH_OK) return fail;
Buffer tmpBuf;
channel = ssh_channel_new(session);
if (channel == NULL) return fail;
res = ssh_channel_open_session(channel);
if (res != SSH_OK) return fail;
res = ssh_channel_request_shell(channel);
if (res != SSH_OK) return fail;
writeBytes("bash -l", strlen("bash -l"));
writeBytes("\n", 1);
writeBytes(commandSeparatorWithEchoStatus, strlen(commandSeparatorWithEchoStatus));
writeBytes("\n", 1);
readBytes(&tmpBuf, 0, commandSeparatorWithLF);
if (verbosity != SSH_LOG_NOLOG) printf("Connected to %s@%s:%d\n", user, host, port);
return success;
}
//-----------------------------------------------------------------
// Buffer::resize
//-----------------------------------------------------------------
int Buffer::resize(int newSize)
{
if (bufferLen == newSize)
return 0;
bufferLen = newSize;
if (buffer)
buffer = (char*)realloc(buffer, bufferLen);
else
buffer = (char*)malloc(bufferLen);
buffer[bufferLen-1] = 0;
return 0;
}
//-----------------------------------------------------------------
// Execute Command
//-----------------------------------------------------------------
struct ServerResponse* execute(const char* command)
{
int rc = success;
char statusBuffer[20]; *statusBuffer = 0;
ServerResponse* res = 0;
printf("Execute [%s]\n", command);
if (!command)
return 0;
// (1) write command + separator echo which will include the return value
rc = writeBytes(command, strlen(command));
if (!rc) rc= writeBytes("\n", 1);
if (!rc) rc= writeBytes(commandSeparatorWithEchoStatus, strlen(commandSeparatorWithEchoStatus));
if (!rc) rc= writeBytes("\n", 1);
res = new ServerResponse;
if (rc) return 0;
// (2) read STDOUT until the separator marker + LF
rc = readBytes(&res->stdout, 0, commandSeparatorWithLF);
if (rc) return 0;
// (4) read STDERR *nonblocking*
rc = readBytes(&res->stderr, 1);
if (rc) return 0;
return res;
}
//-----------------------------------------------------------------
// Write Bytes
//-----------------------------------------------------------------
int writeBytes(const char* buffer, int len)
{
if (!buffer)
return fail;
if (!ssh_channel_is_open(channel))
{
printf("ERROR: CHANNEL IS CLOSED, trying to REOPEN....\n");
return errNotConnected;
}
int nwritten = ssh_channel_write(channel, buffer, len);
if (nwritten != len)
{
const char* err = ssh_get_error(session);
if (verbosity != SSH_LOG_NOLOG) fprintf(stderr, "[SshClient] Error: Sending bytes to server failed: '%s'\n", err ? err : "");
return fail;
}
return success;
}
//-----------------------------------------------------------------
// Read Bytes
//-----------------------------------------------------------------
int readBytes(Buffer* aBuffer, int fromStderr, const char* until)
{
int initial = yes;
int nbytes = 0;
if (!aBuffer)
return fail;
if (!ssh_channel_is_open(channel))
{
printf("ERROR: CHANNEL IS CLOSED, trying to REOPEN....\n");
return errNotConnected;
}
while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel))
{
// initial= no;
if (!fromStderr)
nbytes = ssh_channel_read(channel, aBuffer->buffer+nbytes, aBuffer->bufferLen-nbytes, fromStderr);
else
nbytes = ssh_channel_read_nonblocking(channel, aBuffer->buffer+nbytes, aBuffer->bufferLen-nbytes, fromStderr);
if (nbytes < 0) return fail;
// printf("read bytes: [%s]\n", aBuffer->buffer);
if (!until && nbytes == 0) break;
if (until && strstr(aBuffer->buffer, until)) break;
if (aBuffer->bufferLen-nbytes < 10)
aBuffer->resize(aBuffer->bufferLen*2);
}
return success;
}
在代码示例中,省略cat /etc/passwd
会使错误消失。同时,只发射一百pwd -P
个没有导致观察到的错误。
所以我猜它与从通道流中读取的字节数有关。请注意,连接到我的localhost时没有发生此错误。它仅在连接到某个实际远程主机时发生。就我而言,我使用libssh v7.5.0从MacOS连接到RHEL5盒。
可能会在11日更新
与此同时,我发现了频道关闭的原因。从libssh
调试输出中可以看出,只需在通道关闭之前收到类型为98的SSH数据包:
[2017/05/04 15:32:42.386341, 3] ssh_packet_socket_callback: packet: read type 98 [len=44,padding=18,comp=25,payload=25]
[2017/05/04 15:32:42.386344, 3] ssh_packet_process: Dispatching handler for packet type 98
[2017/05/04 15:32:42.386349, 3] channel_rcv_request: received exit-status 0
根据RFC(https://www.ietf.org/rfc/rfc4254.txt),这是一个SSH_MSG_CHANNEL_REQUEST
数据包,可用于例如请求 pty 。现在libssh
似乎处理了这个数据包,因为它关闭了频道,可能是因为我只使用shell启动了会话/频道,而没有pty 。这是特别必要的,因为我需要正确地解析远程命令输出,这对于真正的PTY来说是不容易的,因为我将从我自己的命令输入以及命令的输出中接收混合字符。
我通过libssh请求pty来验证这一点。这次,通道不会关闭,所有命令都会正确执行。但是,我无法正确解析pty的输出,所以目前这不是一个好的解决方案。
就目前而言,此数据包98由npm install
命令触发,因此似乎npm
工具正在请求pty。然而,根据这个问题(https://github.com/npm/npm/issues/7568),npm 应该以某种方式试图找出它是否在pty上运行,并且相应地表现。然而,没有提到的解决方法(尽管那些人谈论不同的问题)似乎对我有用(export TERM=dumb
,npm --no-progress --no-color
等。)
因此,独立于npm
,我需要一个需要pty的命令的解决方案。有谁知道我怎么能这样做?