Skip to content
Merged
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
2 changes: 2 additions & 0 deletions protocol/src/main/resources/kaitai/ssh_enums.ksy
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ enums:
98: ssh_msg_channel_request
99: ssh_msg_channel_success
100: ssh_msg_channel_failure
192: ssh_msg_ping
193: ssh_msg_pong
global_request_type:
0: empty_response
1: tcpip_forward
Expand Down
12 changes: 12 additions & 0 deletions protocol/src/main/resources/kaitai/ssh_msg_ping.ksy
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
meta:
id: ssh_msg_ping
endian: be
imports:
- byte_string
doc-ref: https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL
doc: >
A ping message sent by either the client or server. The recipient must
reply with SSH_MSG_PONG containing the same data.
seq:
- id: data
type: byte_string
12 changes: 12 additions & 0 deletions protocol/src/main/resources/kaitai/ssh_msg_pong.ksy
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
meta:
id: ssh_msg_pong
endian: be
imports:
- byte_string
doc-ref: https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL
doc: >
A pong reply to SSH_MSG_PING. The data field must be an exact copy of
the data from the corresponding ping message.
seq:
- id: data
type: byte_string
41 changes: 41 additions & 0 deletions sshlib/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,45 @@ package org.connectbot.sshlib {
method public suspend java.lang.Object? verify(org.connectbot.sshlib.PublicKey key, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
}

public abstract sealed exhaustive class PingResult {
}

public static final class PingResult.Failure extends org.connectbot.sshlib.PingResult {
ctor public PingResult.Failure(java.lang.Throwable cause);
method public java.lang.Throwable component1();
method public org.connectbot.sshlib.PingResult.Failure copy(optional java.lang.Throwable cause);
method public boolean equals(java.lang.Object? other);
method @InaccessibleFromKotlin public java.lang.Throwable getCause();
method public int hashCode();
method public java.lang.String toString();
property public Throwable cause;
}

public static final class PingResult.NotAuthenticated extends org.connectbot.sshlib.PingResult {
method public boolean equals(java.lang.Object? other);
method public int hashCode();
method public java.lang.String toString();
field public static final org.connectbot.sshlib.PingResult.NotAuthenticated INSTANCE;
}

public static final class PingResult.NotSupported extends org.connectbot.sshlib.PingResult {
method public boolean equals(java.lang.Object? other);
method public int hashCode();
method public java.lang.String toString();
field public static final org.connectbot.sshlib.PingResult.NotSupported INSTANCE;
}

public static final class PingResult.Success extends org.connectbot.sshlib.PingResult {
ctor public PingResult.Success(long elapsedNs);
method public long component1();
method public org.connectbot.sshlib.PingResult.Success copy(optional long elapsedNs);
method public boolean equals(java.lang.Object? other);
method @InaccessibleFromKotlin public long getElapsedNs();
method public int hashCode();
method public java.lang.String toString();
property public long elapsedNs;
}

public interface PortForwarder {
method public default void close();
method @InaccessibleFromKotlin public java.lang.String getBoundHost();
Expand Down Expand Up @@ -498,6 +537,7 @@ package org.connectbot.sshlib {
method public org.connectbot.sshlib.transport.TransportFactory? openDirectTcpipTransport(java.lang.String remoteHost, int remotePort, optional java.lang.String originAddr, optional int originPort);
method public suspend java.lang.Object? openSession(kotlin.coroutines.Continuation<? super org.connectbot.sshlib.SshSession?>);
method public suspend java.lang.Object? openSftp(kotlin.coroutines.Continuation<? super org.connectbot.sshlib.SftpResult<? extends org.connectbot.sshlib.SftpClient>>);
method public suspend java.lang.Object? ping(kotlin.coroutines.Continuation<? super org.connectbot.sshlib.PingResult>);
method public suspend java.lang.Object? remotePortForward(java.lang.String remoteBindAddress, int remoteBindPort, java.lang.String localHost, int localPort, kotlin.coroutines.Continuation<? super org.connectbot.sshlib.PortForwarder?>);
property public org.connectbot.sshlib.ConnectionInfo? connectionInfo;
property public kotlinx.coroutines.flow.SharedFlow<java.lang.Throwable?> disconnectedFlow;
Expand Down Expand Up @@ -675,6 +715,7 @@ package org.connectbot.sshlib.blocking {
method public org.connectbot.sshlib.transport.TransportFactory? openDirectTcpipTransport(java.lang.String remoteHost, int remotePort, optional java.lang.String originAddr, optional int originPort);
method public org.connectbot.sshlib.SshSession? openSession();
method @kotlin.jvm.Throws(exceptionClasses=SftpException::class) public org.connectbot.sshlib.SftpClient openSftp() throws org.connectbot.sshlib.SftpException;
method public org.connectbot.sshlib.PingResult ping();
method public org.connectbot.sshlib.PortForwarder? remotePortForward(java.lang.String remoteBindAddress, int remoteBindPort, java.lang.String localHost, int localPort);
property public kotlinx.coroutines.flow.SharedFlow<java.lang.Throwable?> disconnectedFlow;
property public boolean isAuthenticated;
Expand Down
18 changes: 18 additions & 0 deletions sshlib/src/main/kotlin/org/connectbot/sshlib/PingResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.connectbot.sshlib

/**
* Result of a [SshClient.ping] call.
*/
sealed class PingResult {
/** The server replied; [elapsedNs] is the round-trip time in nanoseconds. */
data class Success(val elapsedNs: Long) : PingResult()

/** The server did not advertise ping support via SSH2_MSG_EXT_INFO. */
data object NotSupported : PingResult()

/** There is no active authenticated connection. */
data object NotAuthenticated : PingResult()

/** An error occurred while sending the ping or waiting for the reply. */
data class Failure(val cause: Throwable) : PingResult()
}
20 changes: 20 additions & 0 deletions sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import org.connectbot.sshlib.PingResult
import org.connectbot.sshlib.client.DynamicPortForwarder
import org.connectbot.sshlib.client.LocalPortForwarder
import org.connectbot.sshlib.client.RemotePortForwarder
Expand Down Expand Up @@ -652,6 +653,25 @@ class SshClient private constructor(
}
}

/**
* Send an SSH ping to the server and return the round-trip time.
*
* Requires a prior successful [connect] and authentication. Returns
* [PingResult.NotAuthenticated] if there is no active connection or
* authentication has not completed, [PingResult.NotSupported] if the server
* did not advertise `ping@openssh.com` support via SSH2_MSG_EXT_INFO, or
* [PingResult.Failure] if the ping cannot be sent or the connection closes
* before the server replies.
*
* @return [PingResult.Success] with round-trip nanoseconds, [PingResult.NotSupported],
* [PingResult.NotAuthenticated], or [PingResult.Failure]
*/
suspend fun ping(): PingResult {
val conn = connection ?: return PingResult.NotAuthenticated
Comment thread
kruton marked this conversation as resolved.
if (!authenticated) return PingResult.NotAuthenticated
return conn.ping()
Comment thread
kruton marked this conversation as resolved.
Comment thread
kruton marked this conversation as resolved.
}

/**
* Disconnect from the SSH server.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.connectbot.sshlib.AuthResult
import org.connectbot.sshlib.ConnectResult
import org.connectbot.sshlib.HostKeyVerifier
import org.connectbot.sshlib.KeyboardInteractiveCallback
import org.connectbot.sshlib.PingResult
import org.connectbot.sshlib.PortForwarder
import org.connectbot.sshlib.SftpClient
import org.connectbot.sshlib.SftpException
Expand Down Expand Up @@ -247,6 +248,11 @@ class BlockingSshClient internal constructor(
@Throws(SftpException::class)
fun openSftp(): SftpClient = runBlocking { client.openSftp().getOrThrow() }

/**
* Send an SSH ping to the server and return the result.
*/
fun ping(): PingResult = runBlocking { client.ping() }

/**
* Start local port forwarding.
*/
Expand Down
Loading
Loading