44import com .jcraft .jsch .Session ;
55import com .jcraft .jsch .JSchException ;
66import com .testingbot .tunnel .App ;
7+
78import java .io .IOException ;
89import java .util .Timer ;
910import java .util .TimerTask ;
1213import java .util .logging .Logger ;
1314
1415/**
15- *
1616 * @author TestingBot
1717 */
1818public class SSHTunnel {
@@ -23,9 +23,10 @@ public class SSHTunnel {
2323 private final String connectionId ;
2424 private Timer keepAliveTimer ;
2525 private Timer connectionMonitorTimer ;
26- private boolean authenticated = false ;
26+ private Timer portForwardingMonitorTimer ;
2727 private boolean shuttingDown = false ;
28- private CustomConnectionMonitor connectionMonitor ;
28+ private final CustomConnectionMonitor connectionMonitor ;
29+ private boolean portForwardingEstablished = false ;
2930
3031 public SSHTunnel (App app , String server ) throws Exception {
3132 /* Create a connection instance */
@@ -58,9 +59,9 @@ public final void connect() throws Exception {
5859 }
5960
6061 // Authentication is done during connect() with JSch
61- this . authenticated = session .isConnected ();
62-
63- if (!this . authenticated ) {
62+ boolean authenticated = session .isConnected ();
63+
64+ if (!authenticated ) {
6465 Logger .getLogger (SSHTunnel .class .getName ()).log (Level .SEVERE ,
6566 String .format ("[%s] Failed authenticating to the tunnel. Please make sure you are supplying correct login credentials." , connectionId ));
6667 throw new Exception ("Authentication failed" );
@@ -69,10 +70,14 @@ public final void connect() throws Exception {
6970 // Start keep-alive timer with configurable interval
7071 keepAliveTimer = new Timer ("KeepAlive-" + connectionId );
7172 keepAliveTimer .schedule (new KeepAliveTask (), 30000 , 30000 );
72-
73+
7374 // Start connection monitoring timer
7475 connectionMonitorTimer = new Timer ("ConnectionMonitor-" + connectionId );
7576 connectionMonitorTimer .schedule (new ConnectionMonitorTask (), 10000 , 10000 );
77+
78+ // Start port forwarding monitoring timer
79+ portForwardingMonitorTimer = new Timer ("PortForwardingMonitor-" + connectionId );
80+ portForwardingMonitorTimer .schedule (new PortForwardingMonitorTask (), 15000 , 15000 );
7681 }
7782
7883 public void stop (boolean quitting ) {
@@ -81,13 +86,16 @@ public void stop(boolean quitting) {
8186 }
8287
8388 public void stop () {
84- Logger .getLogger (SSHTunnel .class .getName ()).log (Level .INFO , String .format ("[%s] Stopping secure tunnel" ,connectionId ));
89+ Logger .getLogger (SSHTunnel .class .getName ()).log (Level .INFO , String .format ("[%s] Stopping secure tunnel" , connectionId ));
8590 if (keepAliveTimer != null ) {
8691 keepAliveTimer .cancel ();
8792 }
8893 if (connectionMonitorTimer != null ) {
8994 connectionMonitorTimer .cancel ();
9095 }
96+ if (portForwardingMonitorTimer != null ) {
97+ portForwardingMonitorTimer .cancel ();
98+ }
9199
92100 if (session != null && session .isConnected ()) {
93101 session .disconnect ();
@@ -100,10 +108,12 @@ public void createPortForwarding() {
100108 String hubHost = "hub.testingbot.com" ;
101109 session .setPortForwardingL (app .getSSHPort (), hubHost , app .getHubPort ());
102110
111+ portForwardingEstablished = true ;
103112 Logger .getLogger (SSHTunnel .class .getName ()).log (Level .INFO ,
104113 String .format ("[%s] Port forwarding established: %s:2010 -> localhost:%d, localhost:%d -> %s:%d" ,
105114 connectionId , server , app .getJettyPort (), app .getSSHPort (), hubHost , app .getHubPort ()));
106115 } catch (JSchException ex ) {
116+ portForwardingEstablished = false ;
107117 Logger .getLogger (SSHTunnel .class .getName ()).log (Level .SEVERE ,
108118 String .format ("[%s] Could not setup port forwarding. Please make sure we can make an outbound connection to port 2010." , connectionId ), ex );
109119 }
@@ -134,15 +144,15 @@ public void run() {
134144 long roundTripTime = System .currentTimeMillis () - startTime ;
135145
136146 Logger .getLogger (SSHTunnel .class .getName ()).log (Level .FINE ,
137- String .format ("[%s] Keep-alive sent, RTT: %dms" , connectionId , roundTripTime ));
147+ String .format ("[%s] Keep-alive sent, RTT: %dms" , connectionId , roundTripTime ));
138148 }
139149 } catch (Exception ex ) {
140150 Logger .getLogger (SSHTunnel .class .getName ()).log (Level .WARNING ,
141151 String .format ("[%s] Keep-alive failed: %s" , connectionId , ex .getMessage ()));
142152 }
143153 }
144154 }
145-
155+
146156 class ConnectionMonitorTask extends TimerTask {
147157 @ Override
148158 public void run () {
@@ -156,5 +166,60 @@ public void run() {
156166 }
157167 }
158168 }
169+
170+ class PortForwardingMonitorTask extends TimerTask {
171+ @ Override
172+ public void run () {
173+ try {
174+ if (session != null && session .isConnected () && !shuttingDown ) {
175+ // Check if port forwarding is still active by testing the forwarded ports
176+ String [] forwardedPorts = session .getPortForwardingL ();
177+ boolean localForwardingActive = false ;
178+
179+ if (forwardedPorts != null ) {
180+ for (String port : forwardedPorts ) {
181+ if (port .contains (String .valueOf (app .getSSHPort ()))) {
182+ localForwardingActive = true ;
183+ break ;
184+ }
185+ }
186+ }
187+
188+ if (!localForwardingActive && portForwardingEstablished ) {
189+ Logger .getLogger (SSHTunnel .class .getName ()).log (Level .WARNING ,
190+ String .format ("[%s] Local port forwarding lost, attempting to restart" , connectionId ));
191+ restartPortForwarding ();
192+ }
193+ }
194+ } catch (Exception ex ) {
195+ Logger .getLogger (SSHTunnel .class .getName ()).log (Level .WARNING ,
196+ String .format ("[%s] Port forwarding monitoring failed: %s" , connectionId , ex .getMessage ()));
197+ }
198+ }
199+ }
200+
201+ private void restartPortForwarding () {
202+ try {
203+ Logger .getLogger (SSHTunnel .class .getName ()).log (Level .INFO ,
204+ String .format ("[%s] Restarting port forwarding" , connectionId ));
205+
206+ // Clear existing port forwarding
207+ portForwardingEstablished = false ;
208+
209+ // Re-establish port forwarding
210+ createPortForwarding ();
211+
212+ if (portForwardingEstablished ) {
213+ Logger .getLogger (SSHTunnel .class .getName ()).log (Level .INFO ,
214+ String .format ("[%s] Port forwarding successfully restarted" , connectionId ));
215+ } else {
216+ Logger .getLogger (SSHTunnel .class .getName ()).log (Level .SEVERE ,
217+ String .format ("[%s] Failed to restart port forwarding" , connectionId ));
218+ }
219+ } catch (Exception ex ) {
220+ Logger .getLogger (SSHTunnel .class .getName ()).log (Level .SEVERE ,
221+ String .format ("[%s] Error during port forwarding restart: %s" , connectionId , ex .getMessage ()), ex );
222+ }
223+ }
159224}
160225
0 commit comments