Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -147,4 +147,3 @@ Or you can directly install on the current system with:
sudo make install

Enjoy!

4 changes: 2 additions & 2 deletions doc/sshping.pod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -147,7 +147,7 @@ Echo-Count

The file transfer I<speed test> 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
Expand Down
209 changes: 144 additions & 65 deletions src/sshping.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@
#endif

#include <algorithm>
#include <fcntl.h>
#include <inttypes.h>
#include <iostream>
#include <libssh/libssh.h>
#include <libssh/sftp.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -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) {

Expand All @@ -748,48 +817,51 @@ 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
const int mode = S_IRWXU;
#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;
Expand Down Expand Up @@ -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;
Expand Down