Question

So I'm trying to understand how PuTTY is implementing SCP and have a few questions.

First, here's a copy of an older and (imho) easier to read version of PuTTY's SCP implementation:

https://github.com/Yasushi/putty/blob/4eeae4a39bc9faf539b8f819c12b1d1b9f22fc86/scp.c

Here's the function that sends data:

int scp_send_filedata(char *data, int len)
{
    int bufsize = back->send(data, len);

    /*
     * If the network transfer is backing up - that is, the remote
     * site is not accepting data as fast as we can produce it -
     * then we must loop on network events until we have space in
     * the buffer again.
     */
    while (bufsize > MAX_SCP_BUFSIZE) {
        if (!scp_process_network_event())
            return 1;
        bufsize = back->sendbuffer();
    }

    return 0;
}

Here's scp_process_network_event:

static int scp_process_network_event(void)
{
    fd_set readfds;

    FD_ZERO(&readfds);
    FD_SET(scp_ssh_socket, &readfds);
    if (select(1, &readfds, NULL, NULL, NULL) < 0)
        return 0;                       /* doom */
    select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
    return 1;
}

So scp_process_network_event does a select() system call, blocking until a write operation wouldn't block?

I'm thinking back->sendbuffer and back->send correspond to this:

https://github.com/Yasushi/putty/blob/4eeae4a39bc9faf539b8f819c12b1d1b9f22fc86/ssh.c

/*
 * Called to send data down the Telnet connection.
 */
static int ssh_send(char *buf, int len)
{
    if (s == NULL || ssh_protocol == NULL)
        return 0;

    ssh_protocol(buf, len, 0);

    return ssh_sendbuffer();
}

/*
 * Called to query the current amount of buffered stdin data.
 */
static int ssh_sendbuffer(void)
{
    int override_value;

    if (s == NULL || ssh_protocol == NULL)
        return 0;

    /*
     * If the SSH socket itself has backed up, add the total backup
     * size on that to any individual buffer on the stdin channel.
     */
    override_value = 0;
    if (ssh_throttled_all)
        override_value = ssh_overall_bufsize;

    if (ssh_version == 1) {
        return override_value;
    } else if (ssh_version == 2) {
        if (!mainchan || mainchan->closes > 0)
            return override_value;
        else
            return override_value + bufchain_size(&mainchan->v.v2.outbuffer);
    }

    return 0;
}

I'm not completely sure on that though and tbh I'm not very good at C either :(

Anyway it's not entirely clear to me what bufsize is supposed to be.

Any ideas?

Thanks!

Was it helpful?

Solution

The select is blocking on read. We can't do anything (e.g. send anything) so we have to wait on something turning up to read - once we have read something then we can try to send our queued up data again.

It looks like send() puts a buffer of data onto the end an internal linked list of buffers and tries to send it, and sendbuffer() will flush the internal linked list of buffers of any remaining data (e.g. if send() could not send it all due to it backing up).

The send() / sendbuffer() methods look like they return the amount of data left to send, e.g. there were 5 buffers of 10 bytes to send, you were only able to send 2 buffers leaving 30 bytes still to be sent in the internal buffer linked list so the send methods would return 30.

The important takeaway is that the congestion is detected by looking at the values returned by the send methods - if they return non-zero it means that some of the data remains in the internal send buffer and has not been sent out yet due to congestion presumably.

The ssh back-end (ssh.c) shares an API with the plain old telnet back-end so we can derive the API from the telnet.c and wininet.c sources without worrying about the encryption layer. In wininet.c:711 we actually see where send() is finally called on a socket, if this send is successful we call buffchain_consume() to de-queue so many bytes from the start of the buffer linked list (e.g. FIFO queue). This whole function has an outer while loop that continues until the buffer chain is empty (or unless we return out with an error in the middle).

It is in this method where the 'bytes successfully sent' paradigm we all know from send() in libc (and hence php) is converted into the 'bytes queued waiting to be sent' paradigm used in the putty code in your examples.

Basically you have N bytes of data to be sent, you successfully send() X bytes so you have N-X bytes left to send.

The actual subtraction happens in buffchain_consume():127 the linked list of buffers stuff are managed by the buffchain_*() methods in that file.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top