Browse Source

Allow retrying connecting SshSession in case of an exception

Connecting to an SshSession may fail due to different reasons. Jsch for 
example often throws an com.jcraft.jsch.JschException: verify: false.[1]
The issue is still not fixed in JSch 0.1.51.
 
In such a case it is worth retrying to connect. The number of connection
attempts can be configured using ssh_config parameter
"ConnectionAttempts" [2].

Don't retry if the user canceled authentication.

[1] http://sourceforge.net/p/jsch/bugs/58/
[2] http://linux.die.net/man/5/ssh_config

Bug: 437656
Change-Id: I6dd2a3786b7d3f15f5a46821d8edac987a57e381
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
stable-3.4
Stefan Lay 11 years ago committed by Matthias Sohn
parent
commit
4b2b3294b8
  1. 34
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
  2. 6
      org.eclipse.jgit/.settings/.api_filters
  3. 1
      org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
  4. 1
      org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
  5. 42
      org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
  6. 31
      org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java

34
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008, Google Inc. * Copyright (C) 2008, 2014 Google Inc.
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -96,6 +96,7 @@ public class OpenSshConfigTest extends RepositoryTestCase {
assertEquals("repo.or.cz", h.getHostName()); assertEquals("repo.or.cz", h.getHostName());
assertEquals("jex_junit", h.getUser()); assertEquals("jex_junit", h.getUser());
assertEquals(22, h.getPort()); assertEquals(22, h.getPort());
assertEquals(1, h.getConnectionAttempts());
assertNull(h.getIdentityFile()); assertNull(h.getIdentityFile());
} }
@ -249,4 +250,35 @@ public class OpenSshConfigTest extends RepositoryTestCase {
assertNotNull(h); assertNotNull(h);
assertTrue(h.isBatchMode()); assertTrue(h.isBatchMode());
} }
@Test
public void testAlias_ConnectionAttemptsDefault() throws Exception {
final Host h = osc.lookup("orcz");
assertNotNull(h);
assertEquals(1, h.getConnectionAttempts());
}
@Test
public void testAlias_ConnectionAttempts() throws Exception {
config("Host orcz\n" + "\tConnectionAttempts 5\n");
final Host h = osc.lookup("orcz");
assertNotNull(h);
assertEquals(5, h.getConnectionAttempts());
}
@Test
public void testAlias_invalidConnectionAttempts() throws Exception {
config("Host orcz\n" + "\tConnectionAttempts -1\n");
final Host h = osc.lookup("orcz");
assertNotNull(h);
assertEquals(1, h.getConnectionAttempts());
}
@Test
public void testAlias_badConnectionAttempts() throws Exception {
config("Host orcz\n" + "\tConnectionAttempts xxx\n");
final Host h = osc.lookup("orcz");
assertNotNull(h);
assertEquals(1, h.getConnectionAttempts());
}
} }

6
org.eclipse.jgit/.settings/.api_filters

@ -7,6 +7,12 @@
<message_argument value="3.2.0"/> <message_argument value="3.2.0"/>
</message_arguments> </message_arguments>
</filter> </filter>
<filter comment="minor addition" id="924844039">
<message_arguments>
<message_argument value="3.4.0"/>
<message_argument value="3.4.0"/>
</message_arguments>
</filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/transport/TransportHttp.java" type="org.eclipse.jgit.transport.TransportHttp"> <resource path="src/org/eclipse/jgit/transport/TransportHttp.java" type="org.eclipse.jgit.transport.TransportHttp">
<filter comment="Method is only used by implementers of TransportHttp's API, minor version are allowed to break implementer API according to OSGi semantic versioning (http://www.osgi.org/wiki/uploads/Links/SemanticVersioning.pdf)" id="338792546"> <filter comment="Method is only used by implementers of TransportHttp's API, minor version are allowed to break implementer API according to OSGi semantic versioning (http://www.osgi.org/wiki/uploads/Links/SemanticVersioning.pdf)" id="338792546">

1
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties

@ -508,6 +508,7 @@ transportProtoHTTP=HTTP
transportProtoLocal=Local Git Repository transportProtoLocal=Local Git Repository
transportProtoSFTP=SFTP transportProtoSFTP=SFTP
transportProtoSSH=SSH transportProtoSSH=SSH
transportSSHRetryInterrupt=Interrupted while waiting for retry
treeEntryAlreadyExists=Tree entry "{0}" already exists. treeEntryAlreadyExists=Tree entry "{0}" already exists.
treeFilterMarkerTooManyFilters=Too many markTreeFilters passed, maximum number is {0} (passed {1}) treeFilterMarkerTooManyFilters=Too many markTreeFilters passed, maximum number is {0} (passed {1})
treeIteratorDoesNotSupportRemove=TreeIterator does not support remove() treeIteratorDoesNotSupportRemove=TreeIterator does not support remove()

1
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java

@ -570,6 +570,7 @@ public class JGitText extends TranslationBundle {
/***/ public String transportProtoLocal; /***/ public String transportProtoLocal;
/***/ public String transportProtoSFTP; /***/ public String transportProtoSFTP;
/***/ public String transportProtoSSH; /***/ public String transportProtoSSH;
/***/ public String transportSSHRetryInterrupt;
/***/ public String treeEntryAlreadyExists; /***/ public String treeEntryAlreadyExists;
/***/ public String treeFilterMarkerTooManyFilters; /***/ public String treeFilterMarkerTooManyFilters;
/***/ public String treeIteratorDoesNotSupportRemove; /***/ public String treeIteratorDoesNotSupportRemove;

42
org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java

@ -110,7 +110,7 @@ public abstract class JschConfigSessionFactory extends SshSessionFactory {
pass, host, port, hc); pass, host, port, hc);
int retries = 0; int retries = 0;
while (!session.isConnected() && retries < 3) { while (!session.isConnected()) {
try { try {
retries++; retries++;
session.connect(tms); session.connect(tms);
@ -120,16 +120,30 @@ public abstract class JschConfigSessionFactory extends SshSessionFactory {
// Make sure our known_hosts is not outdated // Make sure our known_hosts is not outdated
knownHosts(getJSch(hc, fs), fs); knownHosts(getJSch(hc, fs), fs);
// if authentication failed maybe credentials changed at the if (isAuthenticationCanceled(e)) {
// remote end therefore reset credentials and retry throw e;
if (credentialsProvider != null && e.getCause() == null } else if (isAuthenticationFailed(e)
&& e.getMessage().equals("Auth fail") //$NON-NLS-1$ && credentialsProvider != null) {
&& retries < 3) { // if authentication failed maybe credentials changed at
credentialsProvider.reset(uri); // the remote end therefore reset credentials and retry
session = createSession(credentialsProvider, fs, user, if (retries < 3) {
pass, host, port, hc); credentialsProvider.reset(uri);
} else { session = createSession(credentialsProvider, fs,
user, pass, host, port, hc);
} else
throw e;
} else if (retries >= hc.getConnectionAttempts()) {
throw e; throw e;
} else {
try {
Thread.sleep(1000);
session = createSession(credentialsProvider, fs,
user, pass, host, port, hc);
} catch (InterruptedException e1) {
throw new TransportException(
JGitText.get().transportSSHRetryInterrupt,
e1);
}
} }
} }
} }
@ -147,6 +161,14 @@ public abstract class JschConfigSessionFactory extends SshSessionFactory {
} }
private static boolean isAuthenticationFailed(JSchException e) {
return e.getCause() == null && e.getMessage().equals("Auth fail"); //$NON-NLS-1$
}
private static boolean isAuthenticationCanceled(JSchException e) {
return e.getCause() == null && e.getMessage().equals("Auth cancel"); //$NON-NLS-1$
}
private Session createSession(CredentialsProvider credentialsProvider, private Session createSession(CredentialsProvider credentialsProvider,
FS fs, String user, final String pass, String host, int port, FS fs, String user, final String pass, String host, int port,
final OpenSshConfig.Host hc) throws JSchException { final OpenSshConfig.Host hc) throws JSchException {

31
org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2008, 2014, Google Inc.
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -148,6 +148,8 @@ public class OpenSshConfig {
h.user = OpenSshConfig.userName(); h.user = OpenSshConfig.userName();
if (h.port == 0) if (h.port == 0)
h.port = OpenSshConfig.SSH_PORT; h.port = OpenSshConfig.SSH_PORT;
if (h.connectionAttempts == 0)
h.connectionAttempts = 1;
h.patternsApplied = true; h.patternsApplied = true;
return h; return h;
} }
@ -244,6 +246,18 @@ public class OpenSshConfig {
for (final Host c : current) for (final Host c : current)
if (c.strictHostKeyChecking == null) if (c.strictHostKeyChecking == null)
c.strictHostKeyChecking = value; c.strictHostKeyChecking = value;
} else if (StringUtils.equalsIgnoreCase(
"ConnectionAttempts", keyword)) { //$NON-NLS-1$
try {
final int connectionAttempts = Integer.parseInt(dequote(argValue));
if (connectionAttempts > 0) {
for (final Host c : current)
if (c.connectionAttempts == 0)
c.connectionAttempts = connectionAttempts;
}
} catch (NumberFormatException nfe) {
// ignore bad values
}
} }
} }
@ -331,6 +345,8 @@ public class OpenSshConfig {
String strictHostKeyChecking; String strictHostKeyChecking;
int connectionAttempts;
void copyFrom(final Host src) { void copyFrom(final Host src) {
if (hostName == null) if (hostName == null)
hostName = src.hostName; hostName = src.hostName;
@ -346,6 +362,8 @@ public class OpenSshConfig {
batchMode = src.batchMode; batchMode = src.batchMode;
if (strictHostKeyChecking == null) if (strictHostKeyChecking == null)
strictHostKeyChecking = src.strictHostKeyChecking; strictHostKeyChecking = src.strictHostKeyChecking;
if (connectionAttempts == 0)
connectionAttempts = src.connectionAttempts;
} }
/** /**
@ -402,5 +420,16 @@ public class OpenSshConfig {
public boolean isBatchMode() { public boolean isBatchMode() {
return batchMode != null && batchMode.booleanValue(); return batchMode != null && batchMode.booleanValue();
} }
/**
* @return the number of tries (one per second) to connect before
* exiting. The argument must be an integer. This may be useful
* in scripts if the connection sometimes fails. The default is
* 1.
* @since 3.4
*/
public int getConnectionAttempts() {
return connectionAttempts;
}
} }
} }

Loading…
Cancel
Save