libssh中的频道因没有明显原因而被关闭

时间:2017-05-04 13:39:42

标签: c shell networking ssh libssh

我正在构建一个小型库,它使用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=dumbnpm --no-progress --no-color等。)

因此,独立于npm,我需要一个需要pty的命令的解决方案。有谁知道我怎么能这样做?

0 个答案:

没有答案