Channel in libssh gets closed for no obvious reason

旧时模样 提交于 2020-01-17 08:24:32

问题


I am building a small library which uses libssh to log into a remote host, and fire some commands on a remote shell. I am basically following the tutorial, in that I connect the session, verify the host & user, then open a channel, and request a shell (I do not request a PTY).

After this I am firing commands by writing them on the channel as plain strings, followed by an \n for the command gets executed (1). After each command, I am reading back both from the stdout as well as the stderr stream if bytes are available.

For some strange reason, my test program, which always executes the same commands in the same order, will encounter a closed channel at the same step of commands. In this case, ssh_channel_is_open(channel) will return false, and trying to further read/write the channel will fail.

The debug output of libssh gives me:

    [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....

I added debug messages to show the bytes written and read from the channel. As you can see, the command npm install 2>/dev/null worked, as the channel is still intact afterwards. Then when writing pwd -P and the following separator-echo to the channel, the channel is broken and cannot be read anymore.

Do I have some basic misconception in this? What is going wrong here?

[edit2] Here is the full sourcecode:

    #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;
    }

In the code example, leaving out cat /etc/passwd will make the error disappear. At the same time, just firing a hundred pwd -P's did not result in the observed error.

So I guess it has something to do with the number of bytes read from the channel streams. Please note that this error did not occur when connecting to my localhost. It only occurs when connecting to some actual remote host. In my case, I was connecting from MacOS to a RHEL5 box, using libssh v7.5.0.


Update may 11th

Meanwhile I have found out the reason why the channel is closed. As can be seen in the libssh debug output, just be fore the channel close a SSH packet with type 98 is received:

[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

According to the RFC (https://www.ietf.org/rfc/rfc4254.txt), this is a SSH_MSG_CHANNEL_REQUEST packet, which can be used to e.g. request a pty. Now libssh seems to handle this packet in that it closes the channel, probably because I started the session/channel with a shell only, and no pty. This is especially necessary because I need to properly parse the remote command output, which is not so easily possible with a real PTY, since I will received mixed characters from my own command input as well as the output of the commands.

I kind of verified this by also requesting a pty via libssh. This time, the channel wont be closed, and all commands get executed properly. However, I cannot parse the output of pty correctly, so this is currently no good solution.

As it stands, this packet 98 is triggered by the npm install command, so it seems like that the npm tool is requesting a pty. According to this issue (https://github.com/npm/npm/issues/7568) however, npm should somehow try to find out if it is working on a pty or not, and behave accordingly. However none of the workarounds mentioned (although those guys talk about a different issue) seem to work for me (export TERM=dumb, npm --no-progress --no-color, etc).

So, independently from npm, I would need a solution for commands which need a pty. Does anyone have any idea how I could go about this?

来源:https://stackoverflow.com/questions/43784514/channel-in-libssh-gets-closed-for-no-obvious-reason

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!