diff --git a/README.md b/README.md index 5f29807..143310d 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ ssh-based ping: measure character-echo latency and bandwidth for an interactive ## Description Use this utility to test the performance of interactive ssh sessions -or scp file transfers. It uses ssh to log into a remote system, then +or SFTP file transfers. It uses ssh to log into a remote system, then runs two tests: the first test sends one character at a time, waiting for each character to be returned while it records the latency time -for each. The second test sends a dummy file over scp to /dev/null -on the remote system. +for each. The second test writes and reads back a dummy file over SFTP +at a target file on the remote system. For the echo test, you may specify a character count limit (-c) or a test time limit (-t), and also the command (-e) used on the remote system that @@ -147,4 +147,3 @@ Or you can directly install on the current system with: sudo make install Enjoy! - diff --git a/doc/sshping.pod b/doc/sshping.pod index f68eb6a..45b7941 100644 --- a/doc/sshping.pod +++ b/doc/sshping.pod @@ -74,7 +74,7 @@ Short and long forms of options take the same arguments as shown. Run these tests e=echo s=speed; default es=both -s, --size MB - For transfer speed test (the scp test), send/receive MB + For transfer speed test (the SFTP test), send/receive MB megabytes; default=8 MB -t, --time SECS @@ -147,7 +147,7 @@ Echo-Count The file transfer I measures the upload and download throughput rate, in bytes per second (Bps), -using the scp subsystem. This is the subsystem typically used +using the SFTP subsystem. This is a subsystem commonly used when copying files over ssh. For the upload test, a random 1MB file is copied to diff --git a/src/sshping.cxx b/src/sshping.cxx index 05e418b..03aa525 100644 --- a/src/sshping.cxx +++ b/src/sshping.cxx @@ -33,9 +33,11 @@ #endif #include +#include #include #include #include +#include #include #include #include @@ -739,6 +741,73 @@ int run_echo_test(ssh_channel & chn) { return SSH_OK; } +// Start an SFTP session on the existing SSH connection +sftp_session begin_sftp_session(ssh_session ses) { + sftp_session sftp = sftp_new(ses); + if (sftp == NULL) { + fprintf(stderr, "*** Cannot allocate SFTP session: %s\n", ssh_get_error(ses)); + return NULL; + } + + int rc = sftp_init(sftp); + if (rc != SSH_OK) { + fprintf(stderr, "*** Cannot init SFTP session: %s (SFTP error %d)\n", + ssh_get_error(ses), + sftp_get_error(sftp)); + sftp_free(sftp); + return NULL; + } + return sftp; +} + +// Write exactly len bytes unless SFTP reports an error +int sftp_write_all(ssh_session ses, + sftp_session sftp, + sftp_file file, + const char* buf, + size_t len) { + size_t written = 0; + while (written < len) { + ssize_t nbytes = sftp_write(file, buf + written, len - written); + if (nbytes < 0) { + fprintf(stderr, "*** Can't write to remote file: %s (SFTP error %d)\n", + ssh_get_error(ses), + sftp_get_error(sftp)); + return SSH_ERROR; + } + if (nbytes == 0) { + fprintf(stderr, "*** SFTP write made no progress\n"); + return SSH_ERROR; + } + written += nbytes; + } + return SSH_OK; +} + +// Read exactly len bytes unless SFTP reports an error or early EOF +int sftp_read_all(ssh_session ses, + sftp_session sftp, + sftp_file file, + char* buf, + size_t len) { + size_t read = 0; + while (read < len) { + ssize_t nbytes = sftp_read(file, buf + read, len - read); + if (nbytes < 0) { + fprintf(stderr, "*** Failed read on file download: %s (SFTP error %d)\n", + ssh_get_error(ses), + sftp_get_error(sftp)); + return SSH_ERROR; + } + if (nbytes == 0) { + fprintf(stderr, "*** Remote file ended before expected download size\n"); + return SSH_ERROR; + } + read += nbytes; + } + return SSH_OK; +} + // Run an upload speed test int run_upload_test(ssh_session ses) { @@ -748,21 +817,11 @@ int run_upload_test(ssh_session ses) { } printf("Upload-Size: %17s\n", fmtnum(size * MEGA, 0, "B").c_str()); - ssh_scp scp = ssh_scp_new(ses, SSH_SCP_WRITE, remfile); - if (scp == NULL) { - fprintf(stderr, "*** Cannot allocate scp context: %s\n", ssh_get_error(ses)); + sftp_session sftp = begin_sftp_session(ses); + if (sftp == NULL) { return SSH_ERROR; } - int rc = ssh_scp_init(scp); - if (rc != SSH_OK) { - fprintf(stderr, "*** Cannot init scp context: %s\n", ssh_get_error(ses)); - ssh_scp_free(scp); - return rc; - } - - uint64_t t2 = get_time(); - #ifdef _WIN32 const int mode = 448; #else @@ -770,26 +829,39 @@ int run_upload_test(ssh_session ses) { #endif char* buf = new char[MEGA]; srand(getpid()); - for (size_t i=0; i < sizeof(buf); i++) { + for (size_t i=0; i < MEGA; i++) { buf[i] = (rand() & 0x3f) + 32; } + uint64_t t2 = get_time(); + for (int i=0; i < size; i++) { - rc = ssh_scp_push_file(scp, remfile, MEGA, mode); - if (rc != SSH_OK) { - fprintf(stderr, "*** Can't open remote file: %s\n", ssh_get_error(ses)); + sftp_file file = sftp_open(sftp, remfile, O_WRONLY | O_CREAT | O_TRUNC, mode); + if (file == NULL) { + fprintf(stderr, "*** Can't open remote file for upload: %s (SFTP error %d)\n", + ssh_get_error(ses), + sftp_get_error(sftp)); + sftp_free(sftp); delete[] buf; - return rc; + return SSH_ERROR; } - rc = ssh_scp_write(scp, buf, MEGA); + int rc = sftp_write_all(ses, sftp, file, buf, MEGA); + int close_rc = sftp_close(file); if (rc != SSH_OK) { - fprintf(stderr, "*** Can't write to remote file: %s\n", ssh_get_error(ses)); + sftp_free(sftp); delete[] buf; return rc; } + if (close_rc != SSH_OK) { + fprintf(stderr, "*** Can't close remote upload file: %s (SFTP error %d)\n", + ssh_get_error(ses), + sftp_get_error(sftp)); + sftp_free(sftp); + delete[] buf; + return close_rc; + } } - ssh_scp_close(scp); - ssh_scp_free(scp); + sftp_free(sftp); uint64_t t3 = get_time(); double duration = double(nsec_diff(t3, t2)) / GIGAF; @@ -818,65 +890,72 @@ int run_download_test(ssh_session ses) { size_t avail = 0; size_t remaining = size * MEGA; + sftp_session sftp = begin_sftp_session(ses); + if (sftp == NULL) { + delete[] buf; + return SSH_ERROR; + } + uint64_t t2 = get_time(); + sftp_attributes attrs = sftp_stat(sftp, remfile); + if (attrs == NULL) { + fprintf(stderr, "*** Cannot stat download file: %s (SFTP error %d)\n", + ssh_get_error(ses), + sftp_get_error(sftp)); + sftp_free(sftp); + delete[] buf; + return SSH_ERROR; + } + avail = attrs->size; + sftp_attributes_free(attrs); + + if (verbosity) { + printf("+++ Available size of download: %lu Bytes\n", (unsigned long)avail); + if (remaining > avail) { + printf(" + (Will repeat download as needed)\n"); + } + } + if (!avail) { + fprintf(stderr, "*** Remote file size must be non-zero\n"); + sftp_free(sftp); + delete[] buf; + return SSH_ERROR; + } + while (remaining) { - ssh_scp scp = ssh_scp_new(ses, SSH_SCP_READ, remfile); - if (scp == NULL) { - fprintf(stderr, "*** Cannot allocate scp context: %s\n", ssh_get_error(ses)); + size_t amount = avail; + if (amount > remaining) amount = remaining; + if (amount > DLBUF_SIZE) amount = DLBUF_SIZE; + + sftp_file file = sftp_open(sftp, remfile, O_RDONLY, 0); + if (file == NULL) { + fprintf(stderr, "*** Can't open remote file for download: %s (SFTP error %d)\n", + ssh_get_error(ses), + sftp_get_error(sftp)); + sftp_free(sftp); delete[] buf; return SSH_ERROR; } - int rc = ssh_scp_init(scp); + int rc = sftp_read_all(ses, sftp, file, buf, amount); + int close_rc = sftp_close(file); if (rc != SSH_OK) { - fprintf(stderr, "*** Cannot init scp context: %s\n", ssh_get_error(ses)); - ssh_scp_free(scp); + sftp_free(sftp); delete[] buf; return rc; } - - rc = ssh_scp_pull_request(scp); - if (rc != SSH_SCP_REQUEST_NEWFILE) { - fprintf(stderr, "*** Cannot request download file - got %d: %s\n", rc, ssh_get_error(ses)); - ssh_scp_close(scp); - ssh_scp_free(scp); + if (close_rc != SSH_OK) { + fprintf(stderr, "*** Can't close remote download file: %s (SFTP error %d)\n", + ssh_get_error(ses), + sftp_get_error(sftp)); + sftp_free(sftp); delete[] buf; - return rc; - } - - if (!avail) { - avail = ssh_scp_request_get_size(scp); - if (verbosity) { - printf("+++ Available size of download: %lu Bytes\n", avail); - if (remaining > avail) { - printf(" + (Will repeat download as needed)\n"); - } - } - if (!avail) { - fprintf(stderr, "*** Remote file size must be non-zero\n"); - ssh_scp_close(scp); - ssh_scp_free(scp); - delete[] buf; - return rc; - } - } - size_t amount = avail; - if (amount > remaining) amount = remaining; - if (amount > DLBUF_SIZE) amount = DLBUF_SIZE; - ssh_scp_accept_request(scp); - rc = ssh_scp_read(scp, buf, amount); - if (rc == SSH_ERROR) { - fprintf(stderr, "*** Failed read on file download: %s\n", ssh_get_error(ses)); - ssh_scp_close(scp); - ssh_scp_free(scp); - delete[] buf; - return rc; + return close_rc; } remaining -= amount; - ssh_scp_close(scp); - ssh_scp_free(scp); } + sftp_free(sftp); uint64_t t3 = get_time(); double duration = double(nsec_diff(t3, t2)) / GIGAF;