Skip to content

Commit

Permalink
JENKINS-73889: Cannot reuse an open socket (#204)
Browse files Browse the repository at this point in the history
* JENKINS-73889: Cannot reuse an open socket

* JENKINS-73889: Cannot reuse an open socket

* JENKINS-73889: Cannot reuse an open socket

---------

Co-authored-by: mpet <[email protected]>
  • Loading branch information
mpet and eraonel authored Oct 27, 2024
1 parent fcfe746 commit 04c3bda
Show file tree
Hide file tree
Showing 15 changed files with 871 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ nbactions.xml
nb-configuration.xml

#vscode
.vscode/
.vscode/*

32 changes: 32 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,38 @@
<artifactId>testcontainers</artifactId>
<version>1.20.2</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Use a bridge to log slf4j to jul logging.-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.36</version> <!-- Or the latest version -->
<scope>test</scope>
</dependency>
<!-- Used to verify xml -->
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>2.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-assertj3</artifactId>
<version>2.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-matchers</artifactId>
<version>2.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>

Expand Down
20 changes: 10 additions & 10 deletions src/com/trilead/ssh2/Connection.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,22 @@ public static synchronized String[] getAvailableServerHostKeyAlgorithms()
private boolean authenticated = false;
private ChannelManager cm;

private CryptoWishList cryptoWishList = new CryptoWishList();
protected CryptoWishList cryptoWishList = new CryptoWishList();

private DHGexParameters dhgexpara = new DHGexParameters();
protected DHGexParameters dhgexpara = new DHGexParameters();

private final String hostname;
protected final String hostname;
private final String sourceAddress;

private final int port;
protected final int port;

private TransportManager tm;
protected TransportManager tm;

private boolean tcpNoDelay = false;
protected boolean tcpNoDelay = false;

private ProxyData proxyData = null;
protected ProxyData proxyData = null;

private Vector connectionMonitors = new Vector();
protected Vector connectionMonitors = new Vector();

/**
* Prepares a fresh <code>Connection</code> object which can then be used
Expand Down Expand Up @@ -150,7 +150,7 @@ public Connection(String hostname, int port, String sourceAddress)
this.port = port;
this.sourceAddress = sourceAddress;
}

/**
* After a successful connect, one has to authenticate oneself. This method
* is based on DSA (it uses DSA to sign a challenge sent by the server).
Expand Down Expand Up @@ -1069,7 +1069,7 @@ public synchronized boolean isAuthMethodAvailable(String user, String method) th
return false;
}

private final SecureRandom getOrCreateSecureRND()
protected final SecureRandom getOrCreateSecureRND()
{
if (generator == null)
generator = RandomFactory.create();
Expand Down
67 changes: 67 additions & 0 deletions src/com/trilead/ssh2/transport/Acceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.trilead.ssh2.transport;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.Socket;
import java.net.ServerSocket;

import com.trilead.ssh2.Connection;
import com.trilead.ssh2.ConnectionInfo;
import com.trilead.ssh2.ServerHostKeyVerifier;

/**
* This class is similar to {@link Connection} but is
* used to accept incoming connections from clients.
* Example use-cases are 'NETCONF Call Home' or
* 'reverse SSH'.
*
*/
public class Acceptor extends Connection{

/**
* Constuctor
* @param hostname is the hostname that this class is running on.
* @param port is the port that is used for incoming connections.
*/
public Acceptor(String hostname,int port){
super(hostname,port);
}
/**
* This method reuses most of methods for {@link Connection#connect(ServerHostKeyVerifier, int, int, int)}. Parameters and descriptions applies here too.
* The main difference between
* this class and {@link Connection} is that we use {@link ServerSocket} and we bind with the port specified in constructor. The {@link ServerSocket#accept()}
* will wait (blocks) for an incoming connection for max {@param connectTimeout} . If connection is completed a {@link Socket} is returned and we set a timeout of this socket using
* {@param readTimeout}.
*
* @throws SocketTimeoutException If there is no incoming connection within {@param connectTimeout}.
*
*/
public ConnectionInfo accept(ServerHostKeyVerifier verifier, int connectTimeout, int readTimeout, int kexTimeout) throws IOException{
if (tm != null) {
throw new IOException("Connection to " + hostname + " is already in connected state!");
}
if (connectTimeout < 0)
throw new IllegalArgumentException("connectTimeout must be non-negative!");

if (kexTimeout < 0)
throw new IllegalArgumentException("kexTimeout must be non-negative!");

tm = new TransportManager(hostname, port);
tm.setEnabledCallHomeSSH(true);

tm.setConnectionMonitors(connectionMonitors);
try {
tm.initialize(cryptoWishList, verifier, dhgexpara, connectTimeout, readTimeout, getOrCreateSecureRND(),
proxyData);
} catch (SocketTimeoutException ste) {
throw (SocketTimeoutException) new SocketTimeoutException(
"The accept() operation on the socket timed out.").initCause(ste);
}

tm.setTcpNoDelay(tcpNoDelay);

/* Wait until first KEX has finished */
return tm.getConnectionInfo(1);
}

}
48 changes: 47 additions & 1 deletion src/com/trilead/ssh2/transport/TransportManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.SecureRandom;
Expand Down Expand Up @@ -133,7 +134,7 @@ public void run()
final private String sourceAddress;
String hostname;
int port;
final Socket sock = new Socket();
Socket sock = new Socket();

final Object connectionSemaphore = new Object();

Expand All @@ -151,6 +152,9 @@ public void run()
Vector connectionMonitors = new Vector();
boolean monitorsWereInformed = false;
private ClientServerHello versions;
private boolean enabledCallHomeSSH = false;



/**
* There were reports that there are JDKs which use
Expand Down Expand Up @@ -365,6 +369,10 @@ private void establishConnection(ProxyData proxyData, int connectTimeout, int re

if (proxyData == null)
{
if(enabledCallHomeSSH){
establishCallHomeConnection(connectTimeout,readTimeout);
return;
}

if (sourceAddress != null)
{
Expand Down Expand Up @@ -474,6 +482,27 @@ private void establishConnection(ProxyData proxyData, int connectTimeout, int re

throw new IOException("Unsupported ProxyData");
}

private void establishCallHomeConnection(int connectTimeout,int readTimeout) {
Socket socket = null;
try (ServerSocket serverSocket = new ServerSocket()){
// Create a ServerSocket bound to a specific hostname and port
serverSocket.bind(new InetSocketAddress(port));
serverSocket.setSoTimeout(connectTimeout);

log.log(50,"SSH Call Home Server listen on " + hostname + " at port " + port);

// Accept a client connection (blocks until a connection is made)
socket = serverSocket.accept();
log.log(100,"Call Home SSH accepted connection on host "+sock.getLocalAddress().getHostAddress()+" on port "+port);
if(socket != null){
socket.setSoTimeout(readTimeout);
}
}catch (Exception e){
log.log(100,"Could not create client socket "+sock.getLocalAddress().getHostAddress()+" on port "+port,e);
}
sock = socket;
}

public void initialize(CryptoWishList cwl, ServerHostKeyVerifier verifier, DHGexParameters dhgex,
int connectTimeout, SecureRandom rnd, ProxyData proxyData) throws IOException {
Expand Down Expand Up @@ -820,6 +849,23 @@ public void receiveLoop() throws IOException
}
}

/**
* Get the value for SSH Call Home enaled.
*
* @return true if SSH Call Home enabled.
*/
public boolean isEnabledCallHomeSSH() {
return enabledCallHomeSSH;
}
/**
* Set SSH Call Home
*
* @param enabledCallHomeSSH if set to true it will enable SSH Call Home.
*/
public void setEnabledCallHomeSSH(boolean enabledCallHomeSSH) {
this.enabledCallHomeSSH = enabledCallHomeSSH;
}

/**
* Advertised maximum SSH packet size that the other side can send to us.
*/
Expand Down
119 changes: 119 additions & 0 deletions test/com/trilead/ssh2/transport/CallHomeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.trilead.ssh2.transport;


import org.assertj.core.util.xml.XmlStringPrettyFormatter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.testcontainers.Testcontainers;
import org.testcontainers.containers.GenericContainer;


import com.trilead.ssh2.log.Logger;

import static org.xmlunit.assertj3.XmlAssert.assertThat;



public class CallHomeTest {
private static final Logger LOGGER = Logger.getLogger(CallHomeTest.class);
private SshCallHomeClient sshCallHomeClient;
private GenericContainer<?> netopeer2;

@Before
public void setup() {
//setup logging from java.util.logger used in this project.
JULLoggerSetup.setupJULLogger();
}

/**
* This test creates a NETCONF server ( from Dockerfile) and
* configures it and triggers a SSH Call Home ( server acts as client and vice versa).
* Then we start an SSH Client ( using this library). We wait for incoming
* connection from server in accept(). Then when client and server are connected client send
* NETCONF {@code <hello>} message and read the same from NETCONF server.
* For NETCONF server we use Netopeer2.
*
* @see <a href="https://github.com/CESNET/netopeer2">https://github.com/CESNET/netopeer2/a>
*
* @throws Exception if we fail
*/
@Test()
public void triggerCallHome () throws Exception {

// https://www.testcontainers.org/features/networking/
Testcontainers.exposeHostPorts(4334);

//Start server and trigger SSH Call Home.
netopeer2 = new Netopeer2TestContainer().getNetopeer2Container();

//Start client and wait for incoming calls from server
sshCallHomeClient = new SshCallHomeClient();
sshCallHomeClient.accept();
//Send hello message from client.
sshCallHomeClient.send(clientHelloMsg());
//Wait to get a hello message from server.
String message = sshCallHomeClient.read();
LOGGER.log(50,"Message from node "+message);
assertThat(XmlStringPrettyFormatter.xmlPrettyFormat(message)).and(XmlStringPrettyFormatter.xmlPrettyFormat(serverHelloMsg())).areSimilar();


}

@After
public void cleanUp() {
sshCallHomeClient.disconnect();
netopeer2.stop();
netopeer2.close();
}


private String clientHelloMsg(){
return """
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capabilities>
<capability>urn:ietf:params:netconf:base:1.0</capability>
</capabilities>
</hello>
]]>]]>
""";
}

private String serverHelloMsg(){
return """
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capabilities>
<capability>urn:ietf:params:netconf:base:1.0</capability>
<capability>urn:ietf:params:netconf:base:1.1</capability>
<capability>urn:ietf:params:netconf:capability:writable-running:1.0</capability>
<capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>
<capability>urn:ietf:params:netconf:capability:confirmed-commit:1.1</capability>
<capability>urn:ietf:params:netconf:capability:rollback-on-error:1.0</capability>
<capability>urn:ietf:params:netconf:capability:validate:1.1</capability>
<capability>urn:ietf:params:netconf:capability:startup:1.0</capability>
<capability>urn:ietf:params:netconf:capability:xpath:1.0</capability>
<capability>urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&amp;also-supported=report-all,report-all-tagged,trim,explicit</capability>
<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>
<capability>urn:ietf:params:netconf:capability:interleave:1.0</capability>
<capability>urn:ietf:params:netconf:capability:url:1.0?scheme=ftp,ftps,http,https,scp,sftp</capability>
<capability>urn:ietf:params:xml:ns:yang:ietf-yang-metadata?module=ietf-yang-metadata&amp;revision=2016-08-05</capability>
<capability>urn:ietf:params:xml:ns:yang:ietf-inet-types?module=ietf-inet-types&amp;revision=2013-07-15</capability>
<capability>urn:ietf:params:xml:ns:yang:ietf-yang-types?module=ietf-yang-types&amp;revision=2013-07-15</capability>
<capability>urn:ietf:params:xml:ns:yang:ietf-netconf-acm?module=ietf-netconf-acm&amp;revision=2018-02-14</capability>
<capability>urn:ietf:params:netconf:capability:yang-library:1.1?revision=2019-01-04&amp;content-id=2008448144</capability>
<capability>urn:sysrepo:plugind?module=sysrepo-plugind&amp;revision=2022-08-26</capability>
<capability>urn:ietf:params:xml:ns:netconf:base:1.0?module=ietf-netconf&amp;revision=2013-09-29&amp;features=writable-running,candidate,confirmed-commit,rollback-on-error,validate,startup,url,xpath</capability>
<capability>urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults?module=ietf-netconf-with-defaults&amp;revision=2011-06-01</capability>
<capability>urn:ietf:params:xml:ns:yang:ietf-netconf-notifications?module=ietf-netconf-notifications&amp;revision=2012-02-06</capability>
<capability>urn:ietf:params:xml:ns:netconf:notification:1.0?module=notifications&amp;revision=2008-07-14</capability>
<capability>urn:ietf:params:xml:ns:netmod:notification?module=nc-notifications&amp;revision=2008-07-14</capability>
<capability>urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&amp;revision=2010-10-04</capability>
<capability>urn:ietf:params:xml:ns:yang:ietf-x509-cert-to-name?module=ietf-x509-cert-to-name&amp;revision=2014-12-10</capability>
<capability>urn:ietf:params:xml:ns:yang:iana-crypt-hash?module=iana-crypt-hash&amp;revision=2014-04-04&amp;features=crypt-hash-md5,crypt-hash-sha-256,crypt-hash-sha-512</capability>
</capabilities>
<session-id>1</session-id>
</hello>
""";
}

}
Loading

0 comments on commit 04c3bda

Please sign in to comment.