diff --git a/src/common-ssh/common-ssh/ssh.h b/src/common-ssh/common-ssh/ssh.h index d96ce44c..8026549d 100644 --- a/src/common-ssh/common-ssh/ssh.h +++ b/src/common-ssh/common-ssh/ssh.h @@ -98,7 +98,7 @@ void guac_common_ssh_uninit(); * if the connection or authentication were not successful. */ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, - const char* hostname, const char* port, guac_common_ssh_user* user); + const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive); /** * Disconnects and destroys the given SSH session, freeing all associated diff --git a/src/common-ssh/ssh.c b/src/common-ssh/ssh.c index 57bc8217..cec3d538 100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@ -414,7 +414,7 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) } guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, - const char* hostname, const char* port, guac_common_ssh_user* user) { + const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive) { int retval; @@ -532,6 +532,20 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, return NULL; } + /* Warn if keepalive below minimum value */ + if (keepalive < 0) { + keepalive = 0; + guac_client_log(client, GUAC_LOG_WARNING, "negative keepalive intervals " + "are converted to 0, disabling keepalive."); + } + else if (keepalive == 1) { + guac_client_log(client, GUAC_LOG_WARNING, "keepalive interval will " + "be rounded up to minimum value of 2."); + } + + /* Configure session keepalive */ + libssh2_keepalive_config(common_session->session, 1, keepalive); + /* Return created session */ return common_session; diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index cf9be2ef..d06fad32 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -977,7 +977,7 @@ void* guac_rdp_client_thread(void* data) { /* Attempt SSH connection */ rdp_client->sftp_session = guac_common_ssh_create_session(client, settings->sftp_hostname, - settings->sftp_port, rdp_client->sftp_user); + settings->sftp_port, rdp_client->sftp_user, settings->sftp_server_alive_interval); /* Fail if SSH connection does not succeed */ if (rdp_client->sftp_session == NULL) { diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index 998f0a21..f73ef9d9 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -84,6 +84,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "sftp-private-key", "sftp-passphrase", "sftp-directory", + "sftp-server-alive-interval", #endif "recording-path", @@ -366,6 +367,13 @@ enum RDP_ARGS_IDX { */ IDX_SFTP_DIRECTORY, + /** + * The interval at which SSH keepalive messages are sent to the server for + * SFTP connections. The default is 0 (disabling keepalives), and a value + * of 1 is automatically increased to 2 by libssh2 to avoid busy loop corner + * cases. + */ + IDX_SFTP_SERVER_ALIVE_INTERVAL, #endif /** @@ -775,6 +783,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, settings->sftp_directory = guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, IDX_SFTP_DIRECTORY, NULL); + + /* Default keepalive value */ + settings->sftp_server_alive_interval = + guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_SERVER_ALIVE_INTERVAL, 0); #endif /* Read recording path */ diff --git a/src/protocols/rdp/rdp_settings.h b/src/protocols/rdp/rdp_settings.h index 3ff634ad..8edb79e4 100644 --- a/src/protocols/rdp/rdp_settings.h +++ b/src/protocols/rdp/rdp_settings.h @@ -359,6 +359,14 @@ typedef struct guac_rdp_settings { * the destination directory is otherwise ambiguous). */ char* sftp_directory; + + /** + * The interval at which SSH keepalive messages are sent to the server for + * SFTP connections. The default is 0 (disabling keepalives), and a value + * of 1 is automatically increased to 2 by libssh2 to avoid busy loop corner + * cases. + */ + int sftp_server_alive_interval; #endif /** diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 35f88e0f..8843923b 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -51,6 +51,7 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "recording-name", "create-recording-path", "read-only", + "server-alive-interval", NULL }; @@ -165,6 +166,13 @@ enum SSH_ARGS_IDX { */ IDX_READ_ONLY, + /** + * Number of seconds between sending alive packets. A default of 0 + * tells SSH not to send these packets. A value of 1 is automatically + * changed by libssh2 to 2 to avoid busy-loop corner cases. + */ + IDX_SERVER_ALIVE_INTERVAL, + SSH_ARGS_COUNT }; @@ -279,6 +287,11 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_CREATE_RECORDING_PATH, false); + /* Parse server alive interval */ + settings->server_alive_interval = + guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_SERVER_ALIVE_INTERVAL, 0); + /* Parsing was successful */ return settings; diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index 6d3e47a5..f49d054d 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -53,6 +53,11 @@ */ #define GUAC_SSH_DEFAULT_RECORDING_NAME "recording" +/** + * The default polling timeout for SSH activity in milliseconds. + */ +#define GUAC_SSH_DEFAULT_POLL_TIMEOUT 1000 + /** * Settings for the SSH connection. The values for this structure are parsed * from the arguments given during the Guacamole protocol handshake using the @@ -181,6 +186,11 @@ typedef struct guac_ssh_settings { */ bool create_recording_path; + /** + * The number of seconds between sending server alive messages. + */ + int server_alive_interval; + } guac_ssh_settings; /** diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 43e66f8b..aa9fdaee 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -218,7 +218,7 @@ void* ssh_client_thread(void* data) { /* Open SSH session */ ssh_client->session = guac_common_ssh_create_session(client, - settings->hostname, settings->port, ssh_client->user); + settings->hostname, settings->port, ssh_client->user, settings->server_alive_interval); if (ssh_client->session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; @@ -258,7 +258,7 @@ void* ssh_client_thread(void* data) { guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP..."); ssh_client->sftp_session = guac_common_ssh_create_session(client, settings->hostname, - settings->port, ssh_client->user); + settings->port, ssh_client->user, settings->server_alive_interval); if (ssh_client->sftp_session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; @@ -323,6 +323,9 @@ void* ssh_client_thread(void* data) { /* Track total amount of data read */ int total_read = 0; + /* Timeout for polling socket activity */ + int timeout; + pthread_mutex_lock(&(ssh_client->term_channel_lock)); /* Stop reading at EOF */ @@ -331,6 +334,17 @@ void* ssh_client_thread(void* data) { break; } + /* Send keepalive at configured interval */ + if (settings->server_alive_interval > 0) { + timeout = 0; + if (libssh2_keepalive_send(ssh_client->session->session, &timeout) > 0) + break; + timeout *= 1000; + } + /* If keepalive is not configured, sleep for the default of 1 second */ + else + timeout = GUAC_SSH_DEFAULT_POLL_TIMEOUT; + /* Read terminal data */ bytes_read = libssh2_channel_read(ssh_client->term_channel, buffer, sizeof(buffer)); @@ -370,8 +384,8 @@ void* ssh_client_thread(void* data) { .revents = 0, }}; - /* Wait up to one second */ - if (poll(fds, 1, 1000) < 0) + /* Wait up to computed timeout */ + if (poll(fds, 1, timeout) < 0) break; } diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index 0977af19..697466df 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -66,6 +66,7 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { "sftp-private-key", "sftp-passphrase", "sftp-directory", + "sftp-server-alive-interval", #endif "recording-path", @@ -227,6 +228,14 @@ enum VNC_ARGS_IDX { * the destination directory is otherwise ambiguous). */ IDX_SFTP_DIRECTORY, + + /** + * The interval at which SSH keepalive messages are sent to the server for + * SFTP connections. The default is 0 (disabling keepalives), and a value + * of 1 is automatically incremented to 2 by libssh2 to avoid busy loop corner + * cases. + */ + IDX_SFTP_SERVER_ALIVE_INTERVAL, #endif /** @@ -395,6 +404,11 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, settings->sftp_directory = guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, IDX_SFTP_DIRECTORY, NULL); + + /* Default keepalive value */ + settings->sftp_server_alive_interval = + guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SFTP_SERVER_ALIVE_INTERVAL, 0); #endif /* Read recording path */ diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h index 17626676..3c7b2587 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -173,6 +173,14 @@ typedef struct guac_vnc_settings { * the destination directory is otherwise ambiguous). */ char* sftp_directory; + + /** + * The interval at which SSH keepalive messages are sent to the server for + * SFTP connections. The default is 0 (disabling keepalives), and a value + * of 1 is automatically increased to 2 by libssh2 to avoid busy loop corner + * cases. + */ + int sftp_server_alive_interval; #endif /** diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 8678ee29..81d46f1b 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -261,7 +261,7 @@ void* guac_vnc_client_thread(void* data) { /* Attempt SSH connection */ vnc_client->sftp_session = guac_common_ssh_create_session(client, settings->sftp_hostname, - settings->sftp_port, vnc_client->sftp_user); + settings->sftp_port, vnc_client->sftp_user, settings->sftp_server_alive_interval); /* Fail if SSH connection does not succeed */ if (vnc_client->sftp_session == NULL) {