How to make apache SshClient understand the remote end have disconnected?

  apache-sshd, windows

How to make apache SshClient understand the remote end have disconnected?

I am looking at the apache sshd library for building an SSH client (SshClient class). The client uses the subsystem channel for IO as I am working with netconf and not cli. I initiate the channel like this

ClientChannel channel = session.createSubsystemChannel("netconf");

However, per default, this subsystem channel does not understand whether the remote session is connected. It will assume the session is alive and silent. The state will neither change for failed commands (which is weird, as you would get a TCP timeout when sending the data in the first place, but maybe it will just assume the connection is crappy until it receives an RST).

2020-02-19T07:49:04,077 INFO   session state is [AUTHED]
2020-02-19T07:49:04,077 INFO   channel state is [OPENED]

In an effort to resolve this I added ssh heartbeat, which will basically send SSH_MSG_IGNORE once in a while,

SshClient client = SshClient.setUpDefaultClient();
client.setSessionHeartbeat(SessionHeartbeatController.HeartbeatType.IGNORE, TimeUnit.MILLISECONDS, 1000);

This partially solves my problem, as I get The semaphore timeout period has expired. after 20 seconds.

2020-02-19T07:49:04,063 WARN   exceptionCaught(ClientSessionImpl[[email protected]/147.214.81.124:830])[state=Opened] IOException: The semaphore timeout period has expired.
2020-02-19T07:49:04,077 INFO   session state is [AUTHED]
2020-02-19T07:49:04,077 INFO   channel state is [CLOSED, OPENED]

However, this is too long, so I want faster feedback if the channel/session has gone down. This is required, because restarting the machine is a netconf operation and can also be done by other means. Then the connection need to be restored afterwards (within limits ofc).

Anyone knowing how to tackle this? My example code is found below.

BR
Patrik

SshClient client = SshClient.setUpDefaultClient();
client.setSessionHeartbeat(SessionHeartbeatController.HeartbeatType.IGNORE, TimeUnit.MILLISECONDS, 1000);
client.start();
try {
    ConnectFuture connect = client.connect("user", "secret", 830);
    try {
        connect.verify(5000);
    } catch (Exception e) {
        LOGGER.error("Exception occured: ", e);
        LOGGER.error("Cause=" + e.getCause());
        exit(1); // Just to terminate the app nicer. Maybe not ideal, but good enough for this demo.
    }
    ClientSession session = connect.getSession();
    session.addPasswordIdentity("secret");
    session.auth().verify(5000);
    ClientChannel channel = session.createSubsystemChannel("netconf");
    channel.setStreaming(ClientChannel.Streaming.Async);
    channel.open().verify(2000);
//    read(channel); // If you want to read NETCONF <hello/> message
    LOGGER.info("Wait for close");
    channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED, ClientChannelEvent.TIMEOUT), 0L);
//Terminate remote system, from the remote system here.
    LOGGER.info("session state is " + session.getSessionState());
    LOGGER.info("channel state is " + channel.getChannelState());
    LOGGER.info("closed now");
} catch (IOException e) {
    e.printStackTrace();
}
client.stop();


Source: StackOverflow

LEAVE A COMMENT