JSch: How to keep the session alive and up - java

I'm writing Java GUI program for static route management using SSH. My code is as follows:
import com.jcraft.jsch.*;
import java.io.*;
public class Konsep {
String status;
static String username;
static String hostname;
String inputcommand;
String output;
static Session session;
JSch jsch = new JSch();
public String status(String stringstatus) {
stringstatus = status;
return stringstatus;
}
public String InputCommand(String inputcommandstatus) {
inputcommandstatus = inputcommand;
return inputcommandstatus;
}
public void connect(String usernamelokal, String hostnamelokal,
String password, int port) {
// JSch jsch=new JSch();
try {
Session sessionlokal = jsch.getSession(usernamelokal,
hostnamelokal, port);
sessionlokal.setPassword(password);
UserInfo ui = new UserInfoku.Infoku();
sessionlokal.setUserInfo(ui);
sessionlokal.setTimeout(0);
sessionlokal.connect();
status = "tersambung \n";
username = usernamelokal;
hostname = hostnamelokal;
session = sessionlokal;
System.out.println(username + " " + hostname);
} catch (Exception e) {
System.out.println(e);
status = "Exception = \n " + e + "\n";
}
}
public void disconnect() {
// JSch jsch=new JSch();
try {
Session sessionlokal = jsch.getSession(username, hostname);
// System.out.println(username +" "+ hostname);
sessionlokal.disconnect();
status = "wes pedhoott \n";
} catch (Exception e) {
System.out.println(e);
status = "Exception = \n " + e + "\n";
}
}
public void addRoute() {
// JSch jsch=new JSch();
System.out.println(username + " " + hostname);
try {
Session sessionlokal = session; // =jsch.getSession(username, hostname);
Channel channel = sessionlokal.openChannel("exec");
((ChannelExec) channel).setCommand(inputcommand);
channel.setInputStream(null);
channel.connect();
((ChannelExec) channel).setErrStream(System.err);
InputStream in = channel.getInputStream();
channel.connect();
byte[] tmp = new byte[1024];
while (true) {
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
if (i < 0)
break;
System.out.print(new String(tmp, 0, i));
}
if (channel.isClosed()) {
System.out.println("exit-status: "
+ channel.getExitStatus());
break;
}
try {
Thread.sleep(1000);
} catch (Exception ee) {
}
}
channel.disconnect();
} catch (Exception e) {
System.out.println(e);
}
}
}
The problem is when I call the connect method and then calling the addroute, the program returns
root 192.168.50.2
root 192.168.50.2
com.jcraft.jsch.JSchException: session is down
I've been trying to get session status with either
Session sessionlokal=session; //returns com.jcraft.jsch.JSchException: ChannelExec
or
Session sessionlokal=jsch.getSession(username, hostname); //returns session is down
I've also tried to use keepalive, but its not working either.
My intention is to create a session to host (log in), while leaving the session up, execute a command or commands and maybe executing other commands later, and then closing the session when its not needed (log out).
I've been searching on this forum and I found this question but the code is create a method to define a command to execute first, and then creating the session, call the command's method and close the session.
Any ideas about how to do as I mentioned above ?

After trying Session.sendKeepAliveMsg() without success, I came to the following solution which seems to be rather stable:
private Session getSession() throws Exception {
try {
if (!session.isConnected()) {
logger.info("Session successfully tested, use it again.");
session.connect();
}
} catch (Throwable t) {
logger.info("Session terminated. Create a new one.");
session = jsch.getSession(user, host, port);
session.setConfig(config);
session.connect();
}
return session;
}
Update: Some days later it failed.
I tried to test it by killing the open session on the server. All prior versions I tested this way showed the exact same behavior, regardless whether the problem popped up after waiting some days or killing the server process, so I thought this test - and its outcome for the above solution - to be meaningful. Unfortunately, it isn't.
I'm going to try some other ways to fix it and keep you up to date.
Update 2: Final solution, guaranteed inelegant and working:
private Session getSession() throws Exception {
try {
ChannelExec testChannel = (ChannelExec) session.openChannel("exec");
testChannel.setCommand("true");
testChannel.connect();
if(logger.isDebugEnabled()) {
logger.debug("Session successfully tested, use it again.");
}
testChannel.exit();
} catch (Throwable t) {
logger.info("Session terminated. Create a new one.");
session = jsch.getSession(user, host, port);
session.setConfig(config);
session.connect();
}
return session;
}
This version runs several weeks in a productive environment. Once a day I have the info message logged.
The costs of opening a channel and performing some do-nothing-command are somewhat annoying, but I found no other way to be definitely sure about the state of the session.

You can try by invoking the following method before invoking Session#connect().
Session#setServerAliveInterval(int milliseconds)
Although I found one more function Session#sendKeepAliveMsg() which can be tried.

You can use a pool , I used org.apache.commons.pool2 so the pool is responsible of providing connected sessions, basically:
Create JSCH Session on makeObject
connect on activate if disconnected
check if isConnected on validate
sendKeepAliveMsg on passivate
Above worked for me, also keep in mind that if you add logic on validate, the poolConfig should set true on testOnBorrow or testOnReturn.
So I used KeyedObjectPool you should get connections from the pool via borrowObject method and make sure you return them to the pool once you operation is complete via returnObject.
https://commons.apache.org/proper/commons-pool/api-2.2/org/apache/commons/pool2/PooledObjectFactory.html

Related

Hard Time Keeping JSCH Session Alive

I am trying to run a sequence of commands on a CSICO router using JSCH and SSH. But the problem I am having is after executing the first command, the second one just doesn't execute. The session seems to go down after running the first command. It throws this exception:
com.jcraft.jsch.JSchException: Packet corrupt
at com.jcraft.jsch.Session.start_discard(Session.java:1067)
at com.jcraft.jsch.Session.read(Session.java:937)
at com.jcraft.jsch.Session.connect(Session.java:309)
at com.jcraft.jsch.Session.connect(Session.java:183)
at com.att.ncaas.device.connection.ssh.SSHConnectionJSch.connect(SSHConnectionJSch.java:120)
The code look likes this:
public void connect () {
ChannelExec channelExec = null;
JSch jSch = new JSch();
Session session = null;
try {
session = jSch.getSession(userid, ipAddress, port);
// Set the password.
session.setPassword("XXXXXXXXX");
Properties properties = new Properties();
properties.put("StrictHostKeyChecking", "no");
// Set the Ciphers.
/*
* properties.put("cipher.s2c", "aes128-cbc"); properties.put("cipher.c2s",
* "aes128-cbc"); properties.put("CheckCiphers", "aes128-cbc");
*/
session.setConfig(properties);
// Finally make the connection.
session.connect();
channelExec = (ChannelExec) session.openChannel("exec");
// Command 1
channelExec.setCommand("show run");
ByteArrayOutputStream responseStream = new ByteArrayOutputStream();
channelExec.setOutputStream(responseStream);
channelExec.connect();
while (channelExec.isConnected()) {
Thread.sleep(1000);
}
String responseString = new String(responseStream.toByteArray());
System.out.println("Show Run Output:\n");
System.out.println(responseString);
if (session.isConnected()) {
System.out.println("Wooooooooooooooooooohoooooooooooooooooooooooo!!!!");
} else {
session.connect();
}
// Command 2
channelExec.setCommand("show version");
channelExec.connect();
while (channelExec.isConnected()) {
Thread.sleep(1000);
}
responseString = new String(responseStream.toByteArray());
System.out.println("Show Version Output:\n");
System.out.println(responseString);
} catch (JSchException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (session != null) {
session.disconnect();
}
if (channelExec != null) {
channelExec.disconnect();
}
}
}
When it comes for the second session.connect() inside the if block, it throws the error. The only other thing I noticed is the when I put the ciphers in, this part:
// Set the Ciphers.
properties.put("cipher.s2c", "aes128-cbc");
properties.put("cipher.c2s", "aes128-cbc");
properties.put("CheckCiphers", "aes128-cbc");
I get a different error on the same line. I get:
com.jcraft.jsch.JSchException: Session.connect: java.io.IOException: End of IO Stream Read
at com.jcraft.jsch.Session.connect(Session.java:565)
at com.jcraft.jsch.Session.connect(Session.java:183)
Any pointers on how to keep the session alive between two ChannelExec execute would really helpful. Thanks.
First, you cannot reuse the "exec" channel this way. You have to open a new channel for the new command.
And note that Cisco is known not to support multiple commands in one "exec" channel, even if specified together upfront:
Error passing multiple commands to Cisco CLI via plink
I would not be surprised if it did not support multiple "exec" channels in one connection either.
Did you test with an SSH client, if it is even possible, to do what you are trying to implement?

JSch Channel.getExitStatus does not return status of command executed in "shell" channel

I'm trying to compare file by JSch – diff command.
I want to get the status by Java code to know if the file equals or not i.e. echo $status.
How can I do that in java?
This is my class to run SSH commands
public class SSH {
static Logger logger = LogManager.getLogger("sshLogger");
static Process p;
static String s;
int exitStatus = -1;
String user;
String pass;
String server;
String command;
public Channel channel;
public Session session;
public InputStream in;
public SSH(String user, String pass, String server) {
this.user = user;
this.pass = pass;
this.server = server;
logger.debug("instance: " + user + "#" + server);
}
private Session getConnectedSession(String user, String pass, String server) throws JSchException {
session = new JSch().getSession(user, server, 22);
java.util.Properties config = new java.util.Properties();
session.setPassword(pass);
config.put("StrictHostKeyChecking", "no");
config.put("PreferredAuthentications","publickey,keyboard-interactive,password");
session.setConfig(config);
logger.debug("get connected session at: " + server);
session.connect();
logger.debug("connected to session");
return session;
}
public Channel getChannel() {
// ByteArrayOutputStream byteArrOutSt = new ByteArrayOutputStream();
try {
session = getConnectedSession(user, pass, server);
logger.debug("opening channel at: " + server);
channel = session.openChannel("shell");
} catch (JSchException e) {
logger.error("failed to connect to: "+ user + "#"+ server);
e.printStackTrace();
}
// channel.setOutputStream(baos);
channel.setOutputStream(System.out);
channel.setExtOutputStream(System.out, true);
// channel.setPty(Boolean.FALSE);
return channel;
}
public String commandBuilder(String... commands) {
StringBuilder commandString = new StringBuilder();
for (String command : commands) {
logger.debug("adding command: " + command);
commandString.append(command + "\\n");
}
logger.debug("adding command: exit");
commandString.append("exit\\n");
String myCommand = commandString.toString().replace("\\n", "\n");
return myCommand;
}
public String executeCommand(String myCommand) {
channel = getChannel();
//String finishedCommand = commandBuilder(myCommand);
logger.debug(myCommand);
StringBuilder outBuff = new StringBuilder();
String outComm = null;
channel.setInputStream(new ByteArrayInputStream(myCommand.getBytes(StandardCharsets.UTF_8)));
try {
in = channel.getInputStream();
channel.connect();
// read from the output
System.out.println("printing");
while (true) {
for (int c; ((c = in.read()) >= 0);) {
outBuff.append((char) c);
}
if (channel.isClosed()) {
if (in.available() > 0)
continue;
exitStatus = channel.getExitStatus();
break;
}
}
} catch (IOException e) {
logger.error("failed while running or reading output of command: " + myCommand);
} catch (JSchException e) {
logger.error("failed to connect to server while running command: " + myCommand);
}
logger.info(outBuff.toString());
return outBuff.toString();
}
public void endSession() {
logger.debug("ending session");
channel.disconnect();
session.disconnect();
logger.debug("disconnected channel and session");
// print exit status
logger.debug("Exit status of the execution: " + exitStatus);
if (exitStatus == 0) {
logger.debug(" (OK)\n");
} else {
logger.debug(" (NOK)\n");
}
}
public String runCommand(String... commands) {
// many commands as you want
String output = null;
String myCommands;
myCommands = commandBuilder(commands);
try {
output = executeCommand(myCommands);
endSession();
} catch (Exception e) {
logger.error("failed to run ssh comands: "+ commands);
e.printStackTrace();
}
return output;
}
}
using SSH for diff example:
SSH session = new SSH(user, pass, server);
session.runCommand("diff file1.xml file2.xml");
Do not execute commands in "shell" channel in the first place. The "shell" is a black box with an input and output only. See What is the difference between the 'shell' channel and the 'exec' channel in JSch
Use the "exec" channel. In the "exec" channel, the Channel.getExitStatus works.
For a code example, see: How to read JSch command output?
Obligatory warning: Do not use StrictHostKeyChecking=no to blindly accept all host keys. That is a security flaw. You lose a protection against MITM attacks. For the correct (and secure) approach, see: How to resolve Java UnknownHostKey, while using JSch SFTP library?

Sending Jsch commands

Beating my head against a wall here.
I am trying in the constructor to establish a link and log on. Then in the begin method to execute a java application on the remote computer. Finally in terminate, to kill the java process.
No exceptions show up, the command just doesn't execute. I can see the user has logged in if I go to the remote terminal and run a last. The user has only a login time of 0 though. I can also get an error if I change the password to the wrong password.
But the remote java app isn't running. Remote app runs fine when I log in directly from that terminal or ssh in and run it from the command line.
Ideas? Thanks in advance.
//Constants for the login information
String HOST="<HOSTNAME>";
String USER="<userid>";
String PASSWORD="<password>";
//Constants for command strings
String START_COMMAND="java -Djava.library.path=/usr/local/share/OpenCV/java -jar <filename>";
String TERMINATE_COMMAND="killall java";
Channel channel;
Session session;
InputStream in;
public VisionProcessing(){
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
JSch jsch = new JSch();
try {
System.out.println("Trying login");
session = jsch.getSession(USER, HOST, 22);
session.setPassword(PASSWORD);
session.setConfig(config); //XXX No idea what this does
session.connect();
channel=session.openChannel("exec");
((ChannelExec)channel).setPty(true);
channel.connect();//This is important
if (channel.isConnected()){
System.out.println("Connected");
} else {
System.out.println("Not Connected");
}
} catch (JSchException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("Exception logging in");
}
}
public boolean beginTargeting() {
try {
StringBuilder outputBuffer = new StringBuilder();
Channel channel = session.openChannel("exec");
InputStream commandOutput = channel.getInputStream();
int readByte = commandOutput.read();
while(readByte != 0xffffffff)
{
outputBuffer.append((char)readByte);
readByte = commandOutput.read();
}
System.out.println("Begin");
if (channel.isConnected()){
System.out.println("Connected");
((ChannelExec)channel).setCommand(START_COMMAND);
} else {
System.out.println("Not Connected");
}
channel.connect();//This is important
channel.disconnect();
}catch(IOException ioX)
{
System.out.println(ioX);
}
catch(JSchException jschX)
{
System.out.println(jschX);
}
return true;
}
public boolean terminateTargeting() {
try {
System.out.println("Terminate");
channel=session.openChannel("exec");
((ChannelExec)channel).setCommand(TERMINATE_COMMAND);
channel.connect();//This is important
System.out.println("Terminating");
channel.setInputStream(null);
channel.disconnect();
session.disconnect();
System.out.println("DONE");
}catch(Exception ee){
System.out.println(ee);
}
return true;
}

Jsch SFTP client unable to create new native thread

I use Jsch as SFTP client to read and write XML files from a remote SFTP directory.
I use a 5 second job to check if new files available for drafts, after 30 or 40 min loop I get the following error
Caused by: java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method) [rt.jar:1.7.0_65]
at java.lang.Thread.start(Thread.java:714) [rt.jar:1.7.0_65]
at com.jcraft.jsch.Session.connect(Session.java:528) [jsch-0.1.53.jar:]
at com.jcraft.jsch.Session.connect(Session.java:183) [jsch-0.1.53.jar:]
This is the source code used to create connexion
public InputStream getFile(String path){
Session session = null;
Channel channel = null;
try {
ChannelSftp sftp = openConnexion(session, channel);
return sftp.get(path);
} catch (SftpException e) {
new RuntimeException("Error detected during get file from SFTP specific path : " + e.getMessage(), e);
} finally {
closeConnexion(session, channel);
}
}
private ChannelSftp openConnexion(Session session, Channel channel) {
try {
JSch ssh = new JSch();
session = ssh.getSession("user", "hostname", 22);
session.setPassword("password");
session.setConfig("StrictHostKeyChecking", "no");
session.connect();
channel = session.openChannel(SFTP_CHANNEL);
channel.connect();
ChannelSftp sftp = (ChannelSftp) channel;
return sftp;
} catch (JSchException e) {
throw new RuntimeException("Error detected during open SFTP connexion : " + e.getMessage(), e);
}
}
private void closeConnexion(Session session, Channel channel) {
if (channel != null) {
channel.disconnect();
}
if (session != null) {
session.disconnect();
}
}
I tried to increase the size of JVM thread stack and also increase the limits of native process allowed by unix => same error.
I used the following command to do that :
ulimit -u unlimited
I tried to create a pool of jsch session, jsch session when it is not disconnected, it is unusable => "SFTP Error 4"
My job is runned into war deployed on jboss-as-7, this is the JVM option :
JAVA_OPTS="-Xms1024m -Xmx1024m -XX:MaxPermSize=256m -Xss1024k"
Do you have a suggestion for this kind of treatment?
Thank you !
The problem is that you're not closing the channel and session after each loop, which will leak at least the thread that's used to perform the download over SFTP.
The attempt to close the session and channel in the finally block, if it worked, would unfortunately invalidate the InputStream that you're trying to read from; preventing you from processing the file properly.
I'm going to refactor the code slightly, which should address the resource exhaustion issue, with comments:
// session and channel are at the object scope
Session session = null;
Channel channel = null;
public InputStream getFile(String path){
// First, close any existing connections.
try {
closeConnexion();
} catch (SftpException e) {
// You can try to handle an issue here; but it's
// probably not worth it
}
try {
ChannelSftp sftp = openConnexion();
return sftp.get(path);
} catch (SftpException e) {
new RuntimeException("Error detected during get file from SFTP specific path : " + e.getMessage(), e);
} finally {
}
}
private ChannelSftp openConnexion() {
try {
JSch ssh = new JSch();
// use the object's session variable
session = ssh.getSession("user", "hostname", 22);
session.setPassword("password");
session.setConfig("StrictHostKeyChecking", "no");
session.connect();
// use the object's channel object
channel = session.openChannel(SFTP_CHANNEL);
channel.connect();
ChannelSftp sftp = (ChannelSftp) channel;
return sftp;
} catch (JSchException e) {
throw new RuntimeException("Error detected during open SFTP connexion : " + e.getMessage(), e);
}
}
private void closeConnexion() {
// close object's channel and session
if (channel != null) {
channel.disconnect();
channel = null;
}
if (session != null) {
session.disconnect();
session = null;
}
}
If I was to re-design this, I would return a container class rather than an InputStream that contained the channel, session and InputStream. The container class would have a 'close' method, which would close the InputStream, channel and session, and then I wouldn't store the channel and session in the object.

JSch Shell channel execute commands one by one testing result before proceeding

I have a program for connecting to an SSH shell. I was able to execute the commands and read output from them.
But I have a new requirement. Now I need to execute a command and read the output if successful execute the next command.
I have used the program from this question: JSch issue - Cannot retrieve full command output
public class SshConnectionManager {
private static Session session;
private static ChannelShell channel;
private static String username = "";
private static String password = "";
private static String hostname = "";
private static Session getSession(){
if(session == null || !session.isConnected()){
session = connect(hostname,username,password);
}
return session;
}
private static Channel getChannel(){
if(channel == null || !channel.isConnected()){
try{
channel = (ChannelShell)getSession().openChannel("shell");
channel.setPty(false);
channel.connect();
}catch(Exception e){
System.out.println("Error while opening channel: "+ e);
}
}
return channel;
}
private static Session connect(String hostname, String username, String password){
JSch jSch = new JSch();
try {
session = jSch.getSession(username, hostname, 22);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setPassword(password);
System.out.println("Connecting SSH to " + hostname + " - Please wait for few seconds... ");
session.connect();
System.out.println("Connected!");
}catch(Exception e){
System.out.println("An error occurred while connecting to "+hostname+": "+e);
}
return session;
}
private static void executeCommands(List<String> commands){
try{
Channel channel=getChannel();
System.out.println("Sending commands...");
sendCommands(channel, commands);
readChannelOutput(channel);
System.out.println("Finished sending commands!");
}catch(Exception e){
System.out.println("An error ocurred during executeCommands: "+e);
}
}
private static void sendCommands(Channel channel, List<String> commands){
try{
PrintStream out = new PrintStream(channel.getOutputStream());
out.println("#!/bin/bash");
for(String command : commands){
out.println(command);
}
out.println("exit");
out.flush();
}catch(Exception e){
System.out.println("Error while sending commands: "+ e);
}
}
private static void readChannelOutput(Channel channel){
byte[] buffer = new byte[1024];
try{
InputStream in = channel.getInputStream();
String line = "";
while (true){
while (in.available() > 0) {
int i = in.read(buffer, 0, 1024);
if (i < 0) {
break;
}
line = new String(buffer, 0, i);
System.out.println(line);
}
if(line.contains("logout")){
break;
}
if (channel.isClosed()){
break;
}
try {
Thread.sleep(1000);
} catch (Exception ee){}
}
}catch(Exception e){
System.out.println("Error while reading channel output: "+ e);
}
}
public static void close(){
channel.disconnect();
session.disconnect();
System.out.println("Disconnected channel and session");
}
public static void main(String[] args){
List<String> commands = new ArrayList<String>();
commands.add("ls -l");
executeCommands(commands);
close();
}
}
I need to use "shell" channel instead of "exec" because the server does not allow the "exec" channel: link
In general you should use an "exec" channel for this. The "shell" channel should not be used to automate a command execution.
But I understand that your server does not support the "exec" channel. I've added the above comment for the other readers, not to try to use this solution, unless they really have to.
As #Yair Harel already commented, if you have to use the "shell" channel, you have to use shell techniques.
So in your particular case, you may want to have the remote shell print some unique string and the exit code after the command finishes:
out.println(command + " ; echo Command-has-finished-with-code-$?");
Then you keep reading the output until you find a line starting "Command-has-finished-with-code-". You parse the exit code and decide how to proceed.
Note that the command syntax (particularly the command separator character ; and exit code variable $?) is OS- and shell-specific. As your server is not of a standard kind, the syntax may be different.
Side notes:
The "#!/bin/bash" is a pure nonsense. The shell ignores all commands starting with # (it's comment). There's no point sending it.
Do not use StrictHostKeyChecking=no. See JSch SFTP security with session.setConfig("StrictHostKeyChecking", "no");

Categories

Resources