I am writing an app to connect over SSH to a server. My intention is to give the user of the app an internet connection as long as they are connected to the server (The SSH Script runs as Android Service). The Problem is, when I start a session and create a channel everything works fine. But after about 20-30 minutes (sometimes up to several hours) the channel and the session closes.
Connect function:
public String connecting(
String username,
final String password,
String hostname,
int port) {
try {
Log.d("MainActivity", "Start JSch session and connect");
jsch = new JSch();
session = jsch.getSession(username, hostname, port);
session.setPassword(password);
// Avoid asking for key confirmation
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "no");
session.setConfig(prop);
session.connect();
session.setServerAliveInterval(15);
session.setServerAliveCountMax(100);
Channel channel = session.openChannel("shell");
channel.setInputStream(System.in);
channel.setOutputStream(System.out);
channel.connect();
InputStream in = channel.getInputStream();
serviceStatus = true;
streamtext = "";
byte[] tmp = new byte[1024];
while (true) {
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
if (i < 0) break;
streamtext = new String(tmp, 0, i);
}
}
if (channel.isClosed()) {
if (in.available() > 0) continue;
Log.d(TAG, "exit-status: " + channel.getExitStatus());
break;
}
try {
Thread.sleep(1000);
} catch (Exception ee) {
ee.printStackTrace();
}
}
return streamtext;
} catch (Exception except){
except.printStackTrace();
passErrorToActivity("Error: Connection error");
return "Error: Connection error";
}
}
Start function:
public void start(){
try {
new AsyncTask<Integer, Void, Void>() {
#Override
protected Void doInBackground(Integer... params) {
try {
passMessageToActivity(connecting(user, password, host, port));
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}.execute(1);
} catch (Exception exc) {
exc.printStackTrace();
}
"passMessageToActivity" Just creats an intent and sends the "streamtext" to the MainActivity
I've already tryed it with Session#setServerAliveInterval(int milliseconds) but it didn't work. Is there any posibility to keep the session and channel alive?
I've seen the solution of this user but this doesn't work for me because it's important that the connection between server and service is always up.
If setting keepalives only does not help, you need to keep the session busy with more high-level actions. Like sending a dummy command, like pwd. Though you may need to explain, why you have "shell" session open, that look suspicious.
Anyway, there's no way you can guarantee that a connection stays alive. You would have to deal with losing connection occasionally anyway.
Related
I am using JSch to fetch data from remote server on brocade switch. A new session is created and a new channel is opened with type as 'shell'. I have few commands which fetches data from this server. I created new Channel for every command and disconnects channel after fetching data.
Now in Audit log on server shows new login for every channel which i created for each command.
Should login be shown for every session created instead of every channel connected?
public Session getSession(String hostName,
String username,
String password,
Integer port) throws Exception {
Session session = null;
try {
JSch jsch = new JSch();
if (port == null) {
port = 22;
}
session = jsch.getSession(username, hostName, port);
session.setPassword(password);
session.setConfig("max_input_buffer_size", Integer.toString(100 * 1024 * 1024));
session.setConfig("StrictHostKeyChecking", "no");
session.setConfig("UserKnownHostsFile", "/dev/null");
session.connect(15 * 1000);
} catch (Exception e) {
if (session != null) {
session.disconnect();
}
throw new Exception("Error in connecting to: " + hostName, e);
}
return session;
}
private Channel getChannel(Session session,String type) throws Exception {
if (session == null || !session.isConnected()) {
session = getSession();
}
Channel channel = null;
try {
channel = session.openChannel(type);
} catch (Exception e) {
throw e;
}
channel.setInputStream(null);
return channel;
}
SSH channel does not create a new login.
But it creates a new shell session. Maybe your "audit log" logs shell session as if it were a new login. With a normal SSH client, you typically create only one shell session for each login. So it's easy to mismatch these two things.
I have opened session with jsch on Android, this way:
SshObjects Connect(String username, String password, String hostname, int port)
{
JSch jsch=new JSch();
try
{
sshObjects._session = jsch.getSession(username, hostname, port);
}
catch (JSchException e)
{
e.printStackTrace();
return null;
}
sshObjects._session.setPassword(password);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
sshObjects._session.setConfig(config);
try
{
sshObjects._session.connect();
}
catch (JSchException e)
{
e.printStackTrace();
return null;
}
try
{
sshObjects._channel = (ChannelExec) sshObjects._session.openChannel("exec");
}
catch (JSchException e)
{
e.printStackTrace();
return null;
}
connected = true;
return sshObjects;
}
And then, to execute some command on opened session and get result, I did this:
private String ExecuteCommand(SshCommandsEnum cmdType)
{
String result = "";
switch (cmdType)
{
case SERVER_INFO:
sshObjects._channel.setCommand("uname --all");
break;
.......
}
try
{
BufferedReader in=new BufferedReader(new InputStreamReader(sshObjects._channel.getInputStream()));
//sshObjects._channel.disconnect();
try
{
sshObjects._channel.connect();
}
catch (JSchException e)
{
e.printStackTrace();
}
String msg=null;
try
{
while((msg=in.readLine())!=null)
{
System.out.println(msg);
result += msg;
}
sshObjects._channel.disconnect();
}
catch (IOException e)
{
e.printStackTrace();
}
}
catch (IOException e)
{
return "";
}
return result;
}
So I want to open my session only once. And then execute commands as "exec" on it. It works for first command executed after connect - everything seems to be ok and I can get result succesfully. But when I call "Execute Command" again, it doesn't work anymore. My thread hangs on sshObjects._channel.connect(); and nothing works. When I try to disconnect (close channel and session) and connect again - the same. I can connect and disconnect without any problems only if I don't even try to execute command.
However, I don't experience this issue without this:
BufferedReader in=new BufferedReader(new InputStreamReader(sshObjects._channel.getInputStream()));
But, obviously I need it to get my command output. So what's the problem? Do you have any idea what am I doing wrong?
The ChannelExec is not re-usable, so you need to instantiate it for each command.
When the code below has finished running, netstat -a|grep sftp shows an open SFTP connection. It also shows up as an open connection in JProfiler.
channel.isConnected() in the finally block prints false. Any ideas why the connections is not being closed as I'm at a loss?
public static void clean() {
com.jcraft.jsch.ChannelSftp channel = null;
try {
channel = Helper.openNewTLSftpChannel();
channel.connect();
channel.cd(remoteFileDirectory);
List<ChannelSftp.LsEntry> list = channel.ls("*." + fileType);
for (ChannelSftp.LsEntry file : list) {
String fileName = file.getFilename();
DateTime fileDate = new DateTime(parseDateFromFileName(fileName));
//if this file is older than the cutoff date, delete from the SFTP share
if (fileDate.compareTo(cleanupCutoffdate) < 0) {
channel.rm(fileName);
}
}
} catch (Exception exception) {
exception.printStackTrace();
} finally {
if (channel != null) {
channel.disconnect();
System.out.println(channel.isConnected());
}
}
}
Adding openNewTLSftpChannel() below:
public static ChannelSftp openNewSftpChannel(String privateKeyFileName, String password, String username, String host, int port)
throws ConfigurationErrorException {
JSch jsch = new JSch();
File sftpPrivateFile = new File(privateKeyFileName);
Channel channel;
try {
if (!sftpPrivateFile.canRead()) {
throw new ConfigurationErrorException("File access error: " + sftpPrivateFile.getAbsolutePath());
}
jsch.addIdentity(sftpPrivateFile.getAbsolutePath(), password);
Session session = jsch.getSession(username, host, port);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
channel = session.openChannel("sftp");
} catch (JSchException jschException) {
throw new ConfigurationErrorException("File access error: " + sftpPrivateFile.getAbsolutePath());
}
return (ChannelSftp) channel;
}
If you take a look at the JSCH examples for SFTP you'll see how the session is terminated:
//setup Session here
...
session.connect();
...
Channel channel = session.openChannel("sftp");
channel.connect();
ChannelSftp sftpChannel = (ChannelSftp) channel;
...run sftp logic...
//close sessions here
sftpChannel.exit();
session.disconnect();
You'll notice that there are two parts to the connection and disconnection; the Session object and the Channel object.
In my code I use the Session object to set my authentication information, and the Channel object to execute the sftp commands I need.
In your instance, you're creating the Session object in your openNewSftpChannel method, but it is never closed, hence your session stays alive.
For further context, check out the examples.
Robert H is correct, you need to exit your channel and disconnect your session. I wanted to add that the session exists even when the channel has been closed. Since you create your session within a try block inside a method, it seems you have lost your session, but you can get it back using 'getSession' on your sftpChannel channel.
You can change your finally block to this:
} finally {
if (channel != null) {
Session session = channel.getSession();
channel.disconnect();
session.disconnect();
System.out.println(channel.isConnected());
}
}
I'm using JSch for file upload over SFTP. In its current state each thread opens and closes connection when needed.
If it possible to use connection pooling with JSch in order to avoid overhead caused by large number of connection opening and closing?
Here is a example of function called from inside of thread
public static void file_upload(String filename) throws IOException {
JSch jsch = new JSch();
Session session = null;
try {
session = jsch.getSession("user", "server_name", 22);
session.setConfig("StrictHostKeyChecking", "no");
session.setPassword("super_secre_password");
session.connect();
Channel channel = session.openChannel("sftp");
channel.connect();
ChannelSftp sftpChannel = (ChannelSftp) channel;
FileInputStream inputSrr = new FileInputStream(filename);
try {
sftpChannel.put(inputSrr, "/var/temp/"+filename);
} catch (SftpException e) {
e.printStackTrace();
} finally {
if (inputSrr != null) {
inputSrr.close();
}
}
sftpChannel.exit();
session.disconnect();
} catch (JSchException e) {
e.printStackTrace();
} catch (SftpException e) {
e.printStackTrace();
}
}
For that I would prefer commons-pool. ;)
Here's an implementation of Ssh Connection pool
http://www.javacodegeeks.com/2013/02/pool-of-ssh-connections-using-apache-keyedobjectpool.html
you can use grep4j to use this pool
https://code.google.com/p/grep4j/source/browse/trunk/src/main/java/org/grep4j/core/command/linux/SessionFactory.java?r=354
Also make sure you can access the server from the execution machine. For instance if the target server is not in your reach. It'll throw connection timeout.
I wold like to share with you our implementation, We have used Session Manager of jsch-extension library
First of all you need to implement pool object factory that is responsible for lifecycle of pooled objects:
public class ChannelSftpConnectionsFactory extends BasePooledObjectFactory<ChannelSftp> {
private SessionManager sessionManager;
public ChannelSftpConnectionsFactory(final SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
//Create and open channel
#Override
public ChannelSftp create() throws JSchException {
ChannelSftp channelSftp = (ChannelSftp) sessionManager.getSession().openChannel("sftp");
channelSftp.connect();
return channelSftp;
}
//wrapping
#Override
public PooledObject<ChannelSftp> wrap(final ChannelSftp channelSftp) {
return new DefaultPooledObject<>(channelSftp);
}
#Override
//disconnect channel on destroy
public void destroyObject(final PooledObject<ChannelSftp> pooledObject) {
ChannelSftp sftp = pooledObject.getObject();
disconnectChannel(sftp);
}
void disconnectChannel(final ChannelSftp sftp) {
if (sftp.isConnected()) {
sftp.disconnect();
}
}
#Override
//reset channel current folder to home if someone was walking on another folders
public void passivateObject(final PooledObject<ChannelSftp> p) {
ChannelSftp sftp = p.getObject();
try {
sftp.cd(sftp.getHome());
} catch (SftpException ex) {
log.error("Could not reset channel to home folder, closing it");
disconnectChannel(sftp);
}
}
#Override
//validate object before it is borrowed from pool. If false object will be removed from pool
public boolean validateObject(final PooledObject<ChannelSftp> p) {
ChannelSftp sftp = p.getObject();
return sftp.isConnected() && !sftp.isClosed();
}
}
Now you could create pool using configured factory:
ObjectPool<ChannelSftp> createPool(final SessionManager sessionManager, final GenericObjectPoolConfig<ChannelSftp> poolConfig) {
return PoolUtils.synchronizedPool(new GenericObjectPool<>(buildFactory(sessionManager), poolConfig));
}
PooledObjectFactory<ChannelSftp> buildFactory(final SessionManager sessionManager) {
return PoolUtils.synchronizedPooledFactory(new ChannelSftpConnectionsFactory(sessionManager));
}
This java doc would help you to configure pool properly : https://commons.apache.org/proper/commons-pool/api-2.6.0/org/apache/commons/pool2/impl/BaseGenericObjectPool.html
Do not forget about correct borrowing and returning of object into pool: https://commons.apache.org/proper/commons-pool/api-2.6.0/org/apache/commons/pool2/ObjectPool.html
Object obj = null;
try {
obj = pool.borrowObject();
try {
//...use the object...
} catch(Exception e) {
// invalidate the object
pool.invalidateObject(obj);
// do not return the object to the pool twice
obj = null;
} finally {
// make sure the object is returned to the pool
if(null != obj) {
pool.returnObject(obj);
}
}
} catch(Exception e) {
// failed to borrow an object
}
I am facing this current problem now.
I am able to send command to the device and receive response from the device from android emulator to the socket.
But, when I install the same application on tablet, there is a problem. The first time I send command to check status that device is connected or not, it send me the response that device is connected but when I send command second time it throws the following exception:
java.net.ConnectException: /192.168.1.106:8002 - Connection refused.
This is the code that does the request:
public static String sendRequestandResponse(final String host,final int port,
final String command,
final int timeoutInMillis,final int responseLength) throws UnknownHostException,NetworkSettingException
{
if (host == null)
{
throw new NullPointerException("host is null"); //NOPMD
}
Socket clientSocket=null;
try {
/**
* Creating socket connection with IP address and port number to send Command
*/
try{
clientSocket = new Socket();
SocketAddress remoteAdr = new InetSocketAddress(host, port);
clientSocket.connect(remoteAdr, 1000);
clientSocket.setSoTimeout(timeoutInMillis);
}catch (Exception e) {
e.printStackTrace();
throw new NetworkSettingException(e.getMessage());
}
final PrintWriter outPutStream = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream(), CHARSET));
try
{
outPutStream.print(command);
outPutStream.flush();
BufferedReader responseString = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), CHARSET));
response = new StringBuilder();
try
{
int pos = 0;
while (true)
{
pos++;
System.out.println(pos);
int i=responseString.read();
byte[] resp={(byte)i};
System.out.println(new String(resp));
response.append(new String(resp));
if(pos>=responseLength){
{
clientSocket.shutdownInput();
clientSocket.shutdownOutput();
clientSocket.close();
Log.d("ConnectionSocket", "Socket closed with break");
break;
}
}
}
}catch (Exception e) {
e.printStackTrace();
}
finally
{
responseString.close();
}
}
finally
{
outPutStream.close();
}
}
catch(IOException ex){
}
catch(NullPointerException ex){ //NOPMD
}
finally
{
try {
clientSocket.shutdownInput();
clientSocket.shutdownOutput();
clientSocket.close();
} catch (NullPointerException ex) { //NOPMD
} catch (IOException ex) {
}
}
return response.toString();
}
I think it doesnt close the socket first time, so second time it refuse the connection.
The same code works on emulator though.
You will get the Connection Refused only when the server is not accepting the connection.
Mostly, the Problem is the Firewall which blocks the any untrusted incoming connection.
My application shows this message mostly when the server has a firewall which block it.
So u can add the Exception list in Firewall for your application.