Now that the server will tell us its limits, we can use those to select a good transfer size. In practice, the default increases from 32KiB to 64KiB when using newer OpenSSH servers. --- sftp-client.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++--- sftp-client.h | 11 ++++++ sftp.c | 7 ++-- 3 files changed, 105 insertions(+), 9 deletions(-) diff --git a/sftp-client.c b/sftp-client.c index d82e31aeeec4..ef3906477bd5 100644 --- a/sftp-client.c +++ b/sftp-client.c @@ -61,6 +61,12 @@ extern volatile sig_atomic_t interrupted; extern int showprogress; +/* Default size of buffer for up/download */ +#define DEFAULT_COPY_BUFLEN 32768 + +/* Default number of concurrent outstanding requests */ +#define DEFAULT_NUM_REQUESTS 64 + /* Minimum amount of data to read at a time */ #define MIN_READ_SIZE 512 @@ -87,6 +93,7 @@ struct sftp_conn { #define SFTP_EXT_HARDLINK 0x00000008 #define SFTP_EXT_FSYNC 0x00000010 #define SFTP_EXT_LSETSTAT 0x00000020 +#define SFTP_EXT_LIMITS 0x00000040 u_int exts; u_int64_t limit_kbps; struct bwlimit bwlimit_in, bwlimit_out; @@ -405,8 +412,10 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, ret->msg_id = 1; ret->fd_in = fd_in; ret->fd_out = fd_out; - ret->transfer_buflen = transfer_buflen; - ret->num_requests = num_requests; + ret->transfer_buflen = + transfer_buflen ? transfer_buflen : DEFAULT_COPY_BUFLEN; + ret->num_requests = + num_requests ? num_requests : DEFAULT_NUM_REQUESTS; ret->exts = 0; ret->limit_kbps = 0; @@ -418,8 +427,6 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, send_msg(ret, msg); - sshbuf_reset(msg); - get_msg_extended(ret, msg, 1); /* Expecting a VERSION reply */ @@ -471,6 +478,10 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_LSETSTAT; known = 1; + } else if (strcmp(name, "limits@xxxxxxxxxxx") == 0 && + strcmp((char *)value, "1") == 0) { + ret->exts |= SFTP_EXT_LIMITS; + known = 1; } if (known) { debug2("Server supports extension \"%s\" revision %s", @@ -484,6 +495,33 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, sshbuf_free(msg); + /* Query the server for its limits */ + if (ret->exts & SFTP_EXT_LIMITS) { + struct sftp_limits limits; + if (do_limits(ret, &limits) != 0) + fatal_f("limits failed"); + + /* If the caller did not specify, find a good value */ + if (transfer_buflen == 0) { + /* TODO: We should have sep read & write limits */ + ret->transfer_buflen = + MINIMUM(limits.read_length, limits.write_length); + debug("Using smaller of server transfer limits " + "[read:%llu, write:%llu] -> %u", + (unsigned long long)limits.read_length, + (unsigned long long)limits.write_length, + ret->transfer_buflen); + } + + /* Use the server limit to scale down our value only */ + if (num_requests == 0 && limits.open_handles) { + ret->num_requests = + MINIMUM(DEFAULT_NUM_REQUESTS, limits.open_handles); + debug("Server handle limit %llu; using %u", + (unsigned long long)limits.open_handles, ret->num_requests); + } + } + /* Some filexfer v.0 servers don't support large packets */ if (ret->version == 0) ret->transfer_buflen = MINIMUM(ret->transfer_buflen, 20480); @@ -505,6 +543,56 @@ sftp_proto_version(struct sftp_conn *conn) return conn->version; } +int +do_limits(struct sftp_conn *conn, struct sftp_limits *limits) +{ + u_int id, msg_id; + u_char type; + struct sshbuf *msg; + int r; + + if ((conn->exts & SFTP_EXT_LIMITS) == 0) { + error("Server does not support limits@xxxxxxxxxxx extension"); + return -1; + } + + if ((msg = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); + + id = conn->msg_id++; + if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || + (r = sshbuf_put_u32(msg, id)) != 0 || + (r = sshbuf_put_cstring(msg, "limits@xxxxxxxxxxx")) != 0) + fatal_fr(r, "compose"); + send_msg(conn, msg); + debug3("Sent message limits@xxxxxxxxxxx I:%u", id); + + get_msg(conn, msg); + + if ((r = sshbuf_get_u8(msg, &type)) != 0 || + (r = sshbuf_get_u32(msg, &msg_id)) != 0) + fatal_fr(r, "parse"); + + debug3("Received limits reply T:%u I:%u", type, msg_id); + if (id != msg_id) + fatal("ID mismatch (%u != %u)", msg_id, id); + if (type != SSH2_FXP_EXTENDED_REPLY) { + fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", + SSH2_FXP_EXTENDED_REPLY, type); + } + + memset(limits, 0, sizeof(*limits)); + if ((r = sshbuf_get_u64(msg, &limits->packet_length)) != 0 || + (r = sshbuf_get_u64(msg, &limits->read_length)) != 0 || + (r = sshbuf_get_u64(msg, &limits->write_length)) != 0 || + (r = sshbuf_get_u64(msg, &limits->open_handles)) != 0) + fatal_fr(r, "parse limits"); + + sshbuf_free(msg); + + return 0; +} + int do_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len) { diff --git a/sftp-client.h b/sftp-client.h index 63a9b8b13bdc..51ba72a3ab22 100644 --- a/sftp-client.h +++ b/sftp-client.h @@ -53,6 +53,14 @@ struct sftp_statvfs { u_int64_t f_namemax; }; +/* Used for limits response on the wire from the server */ +struct sftp_limits { + u_int64_t packet_length; + u_int64_t read_length; + u_int64_t write_length; + u_int64_t open_handles; +}; + /* * Initialise a SSH filexfer connection. Returns NULL on error or * a pointer to a initialized sftp_conn struct on success. @@ -61,6 +69,9 @@ struct sftp_conn *do_init(int, int, u_int, u_int, u_int64_t); u_int sftp_proto_version(struct sftp_conn *); +/* Query server limits */ +int do_limits(struct sftp_conn *, struct sftp_limits *); + /* Close file referred to by 'handle' */ int do_close(struct sftp_conn *, const u_char *, u_int); diff --git a/sftp.c b/sftp.c index b641be2bc4c7..b63f43b5af48 100644 --- a/sftp.c +++ b/sftp.c @@ -70,9 +70,6 @@ typedef void EditLine; #include "sftp-common.h" #include "sftp-client.h" -#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */ -#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */ - /* File to read commands from */ FILE* infile; @@ -2386,8 +2383,8 @@ main(int argc, char **argv) extern int optind; extern char *optarg; struct sftp_conn *conn; - size_t copy_buffer_len = DEFAULT_COPY_BUFLEN; - size_t num_requests = DEFAULT_NUM_REQUESTS; + size_t copy_buffer_len = 0; + size_t num_requests = 0; long long limit_kbps = 0; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ -- 2.28.0 _______________________________________________ openssh-unix-dev mailing list openssh-unix-dev@xxxxxxxxxxx https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev