From 98c65339c861bcceda3e8c08df7a595d3bbbf2cf Mon Sep 17 00:00:00 2001 From: Rasmus Lerdorf Date: Sat, 4 Apr 2026 07:04:18 -0400 Subject: [PATCH 1/4] Add keepalive support --- config.m4 | 9 ++++++ ssh2.c | 63 +++++++++++++++++++++++++++++++++++++++ tests/ssh2_keepalive.phpt | 28 +++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 tests/ssh2_keepalive.phpt diff --git a/config.m4 b/config.m4 index b2746e2..500c8d6 100644 --- a/config.m4 +++ b/config.m4 @@ -53,6 +53,15 @@ if test "$PHP_SSH2" != "no"; then -L$SSH2_DIR/lib -lm ]) + PHP_CHECK_LIBRARY(ssh2,libssh2_keepalive_config, + [ + AC_DEFINE(PHP_SSH2_KEEPALIVE, 1, [Have libssh2 with keepalive support]) + ],[ + AC_MSG_WARN([libssh2 keepalive support not available]) + ],[ + -L$SSH2_DIR/lib -lm + ]) + PHP_SUBST(SSH2_SHARED_LIBADD) PHP_NEW_EXTENSION(ssh2, ssh2.c ssh2_fopen_wrappers.c ssh2_sftp.c, $ext_shared) diff --git a/ssh2.c b/ssh2.c index e2c28db..7ff1335 100644 --- a/ssh2.c +++ b/ssh2.c @@ -475,6 +475,57 @@ PHP_FUNCTION(ssh2_set_timeout) } /* }}} */ +/* {{{ proto void ssh2_keepalive_config(resource session, bool want_reply, int interval) + * Set how often keepalive messages should be sent. interval is the number + * of seconds that can pass without any I/O; use 0 (the default) to disable + * keepalives. want_reply indicates whether the keepalive messages should + * request a response from the server. + */ +PHP_FUNCTION(ssh2_keepalive_config) +{ + LIBSSH2_SESSION *session; + zval *zsession; + zend_bool want_reply; + zend_long interval; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rbl", &zsession, &want_reply, &interval) == FAILURE) { + return; + } + + if ((session = (LIBSSH2_SESSION *)zend_fetch_resource(Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { + return; + } + + libssh2_keepalive_config(session, want_reply, interval); +} +/* }}} */ + +/* {{{ proto int|false ssh2_keepalive_send(resource session) + * Send a keepalive message if needed. Returns the number of seconds you + * can sleep before you need to call this function again, or false on error. + */ +PHP_FUNCTION(ssh2_keepalive_send) +{ + LIBSSH2_SESSION *session; + zval *zsession; + int seconds_to_next; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zsession) == FAILURE) { + return; + } + + if ((session = (LIBSSH2_SESSION *)zend_fetch_resource(Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { + return; + } + + if (libssh2_keepalive_send(session, &seconds_to_next)) { + RETURN_FALSE; + } + + RETURN_LONG(seconds_to_next); +} +/* }}} */ + /* {{{ proto array ssh2_methods_negotiated(resource session) * Return list of negotiaed methods */ @@ -1430,6 +1481,16 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_ssh2_set_timeout, 0, 0, 2) ZEND_ARG_INFO(0, microseconds) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_ssh2_keepalive_config, 0, 0, 3) + ZEND_ARG_INFO(0, session) + ZEND_ARG_INFO(0, want_reply) + ZEND_ARG_INFO(0, interval) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ssh2_keepalive_send, 0, 0, 1) + ZEND_ARG_INFO(0, session) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO(arginfo_ssh2_methods_negotiated, 1) ZEND_ARG_INFO(0, session) ZEND_END_ARG_INFO() @@ -1640,6 +1701,8 @@ zend_function_entry ssh2_functions[] = { PHP_FE(ssh2_connect, arginfo_ssh2_connect) PHP_FE(ssh2_disconnect, arginfo_ssh2_disconnect) PHP_FE(ssh2_set_timeout, arginfo_ssh2_set_timeout) + PHP_FE(ssh2_keepalive_config, arginfo_ssh2_keepalive_config) + PHP_FE(ssh2_keepalive_send, arginfo_ssh2_keepalive_send) PHP_FE(ssh2_methods_negotiated, arginfo_ssh2_methods_negotiated) PHP_FE(ssh2_fingerprint, arginfo_ssh2_fingerprint) diff --git a/tests/ssh2_keepalive.phpt b/tests/ssh2_keepalive.phpt new file mode 100644 index 0000000..878fd71 --- /dev/null +++ b/tests/ssh2_keepalive.phpt @@ -0,0 +1,28 @@ +--TEST-- +ssh2_keepalive_config() and ssh2_keepalive_send() basic functionality +--SKIPIF-- + +--FILE-- + 0); + +echo "**Disable keepalive\n"; +ssh2_keepalive_config($ssh, false, 0); +$seconds = ssh2_keepalive_send($ssh); +var_dump($seconds === 0); +--EXPECT-- +**Configure keepalive +**Send keepalive +bool(true) +bool(true) +**Disable keepalive +bool(true) From f5aff86b3cac4e54a11c758d9aae8f7f0b7849f9 Mon Sep 17 00:00:00 2001 From: Rasmus Lerdorf Date: Sat, 4 Apr 2026 07:23:41 -0400 Subject: [PATCH 2/4] address reviews --- ssh2.c | 11 ++++++++++- tests/ssh2_keepalive.phpt | 7 ++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ssh2.c b/ssh2.c index 7ff1335..953e0a5 100644 --- a/ssh2.c +++ b/ssh2.c @@ -475,6 +475,7 @@ PHP_FUNCTION(ssh2_set_timeout) } /* }}} */ +#ifdef PHP_SSH2_KEEPALIVE /* {{{ proto void ssh2_keepalive_config(resource session, bool want_reply, int interval) * Set how often keepalive messages should be sent. interval is the number * of seconds that can pass without any I/O; use 0 (the default) to disable @@ -492,11 +493,16 @@ PHP_FUNCTION(ssh2_keepalive_config) return; } + if (interval < 0) { + php_error_docref(NULL, E_WARNING, "Argument #3 ($interval) must be greater than or equal to 0"); + return; + } + if ((session = (LIBSSH2_SESSION *)zend_fetch_resource(Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { return; } - libssh2_keepalive_config(session, want_reply, interval); + libssh2_keepalive_config(session, want_reply, (unsigned int)interval); } /* }}} */ @@ -525,6 +531,7 @@ PHP_FUNCTION(ssh2_keepalive_send) RETURN_LONG(seconds_to_next); } /* }}} */ +#endif /* {{{ proto array ssh2_methods_negotiated(resource session) * Return list of negotiaed methods @@ -1701,8 +1708,10 @@ zend_function_entry ssh2_functions[] = { PHP_FE(ssh2_connect, arginfo_ssh2_connect) PHP_FE(ssh2_disconnect, arginfo_ssh2_disconnect) PHP_FE(ssh2_set_timeout, arginfo_ssh2_set_timeout) +#ifdef PHP_SSH2_KEEPALIVE PHP_FE(ssh2_keepalive_config, arginfo_ssh2_keepalive_config) PHP_FE(ssh2_keepalive_send, arginfo_ssh2_keepalive_send) +#endif PHP_FE(ssh2_methods_negotiated, arginfo_ssh2_methods_negotiated) PHP_FE(ssh2_fingerprint, arginfo_ssh2_fingerprint) diff --git a/tests/ssh2_keepalive.phpt b/tests/ssh2_keepalive.phpt index 878fd71..72596e4 100644 --- a/tests/ssh2_keepalive.phpt +++ b/tests/ssh2_keepalive.phpt @@ -1,7 +1,12 @@ --TEST-- ssh2_keepalive_config() and ssh2_keepalive_send() basic functionality --SKIPIF-- - + --FILE-- Date: Sat, 4 Apr 2026 07:29:42 -0400 Subject: [PATCH 3/4] Assume we have a new enough libssh2 on Windows --- config.w32 | 1 + 1 file changed, 1 insertion(+) diff --git a/config.w32 b/config.w32 index b715414..45a0226 100644 --- a/config.w32 +++ b/config.w32 @@ -38,6 +38,7 @@ if (PHP_SSH2 != "no") { AC_DEFINE('HAVE_SSH2LIB', 1); AC_DEFINE('PHP_SSH2_AGENT_AUTH', 1); AC_DEFINE('PHP_SSH2_SESSION_TIMEOUT', 1); + AC_DEFINE('PHP_SSH2_KEEPALIVE', 1); ADD_EXTENSION_DEP('ssh2', 'zlib') ADD_EXTENSION_DEP('ssh2', 'openssl') } From 57c2fe17011d85dca501ad7591ef33a87afcc642 Mon Sep 17 00:00:00 2001 From: Rasmus Lerdorf Date: Sat, 4 Apr 2026 07:36:51 -0400 Subject: [PATCH 4/4] address reviews --- ssh2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssh2.c b/ssh2.c index 953e0a5..8bd51cb 100644 --- a/ssh2.c +++ b/ssh2.c @@ -493,8 +493,8 @@ PHP_FUNCTION(ssh2_keepalive_config) return; } - if (interval < 0) { - php_error_docref(NULL, E_WARNING, "Argument #3 ($interval) must be greater than or equal to 0"); + if (interval < 0 || interval > UINT_MAX) { + php_error_docref(NULL, E_WARNING, "Argument #3 ($interval) must be between 0 and %u", UINT_MAX); return; }