From 0ba97ed4cdfc3c5ffeb4d4e4bacbd26006f9be84 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 29 Sep 2023 09:57:12 +0000 Subject: [PATCH] SAM: Accept fixes (Gitlab #399) --- .../i2p/client/streaming/I2PServerSocket.java | 3 +- .../src/net/i2p/sam/SAMStreamSession.java | 7 +++ .../java/src/net/i2p/sam/SAMv3Handler.java | 48 +++++++++++++++++-- .../streaming/impl/I2PSocketManagerFull.java | 12 +++-- 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java index b2a6090b1..26ad8aaf2 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java @@ -28,7 +28,8 @@ public interface I2PServerSocket { * @return a connected I2PSocket OR NULL through 0.9.16; never null as of 0.9.17 * * @throws I2PException if there is a problem with reading a new socket - * from the data available (e.g. the I2PSession is closed) + * from the data available (e.g. the I2PSession is closed); + * as of 0.9.60, this may be an I2PSessionException which extends I2PException * @throws RouterRestartException (extends I2PException) if the router is apparently restarting, since 0.9.34 * @throws ConnectException if the I2PServerSocket is closed, or if interrupted. * Not actually thrown through 0.9.16; thrown as of 0.9.17 diff --git a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java index e22a33cb8..7a93773a6 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java @@ -361,6 +361,13 @@ class SAMStreamSession implements SAMMessageSess { socketMgr.destroySocketManager(); } + /** + * Is the underlying streaming socket manager destroyed? + * + * @since 0.9.60 + */ + public boolean isDestroyed() { return socketMgr.isDestroyed(); } + /** * Close a connection managed by the SAM STREAM session. * diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index 0f0f0a387..b1257ff20 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -29,6 +29,7 @@ import net.i2p.I2PException; import net.i2p.client.I2PClient; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; +import net.i2p.client.streaming.RouterRestartException; import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.DataFormatException; @@ -718,7 +719,8 @@ class SAMv3Handler extends SAMv1Handler return false ; } - streamSession = rec.getHandler().streamSession ; + SAMv3Handler ctl = rec.getHandler(); + streamSession = ctl.streamSession; if (streamSession==null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("specified ID is not a stream session"); @@ -727,6 +729,16 @@ class SAMv3Handler extends SAMv1Handler } catch (IOException e) {} return false ; } + if (streamSession.isDestroyed()) { + if (_log.shouldDebug()) + _log.debug("Session manager is destroyed"); + try { + notifyStreamResult(true, "I2P_ERROR", "Session is closed"); + } catch (IOException e) {} + ctl.writeString(SESSION_ERROR, "Session is closed"); + ctl.stopHandling(); + return false; + } if ( opcode.equals ( "CONNECT" ) ) { @@ -734,7 +746,17 @@ class SAMv3Handler extends SAMv1Handler } else if ( opcode.equals ( "ACCEPT" ) ) { - return execStreamAccept ( props ); + try { + return execStreamAccept(props); + } catch (I2PSessionException ise) { + ctl.writeString(SESSION_ERROR, ise.getMessage()); + ctl.stopHandling(); + return false; + } catch (RouterRestartException rre) { + ctl.writeString(SESSION_ERROR, "Router restart"); + ctl.stopHandling(); + return false; + } } else if ( opcode.equals ( "FORWARD") ) { @@ -822,13 +844,18 @@ class SAMv3Handler extends SAMv1Handler return false ; } - private boolean execStreamAccept( Properties props ) + /** + * @return success + * @throws I2PSessionException if socket manager is destroyed while waiting + */ + private boolean execStreamAccept(Properties props) throws I2PSessionException, RouterRestartException { // Messages are NOT sent if SILENT=true, // The specs said that they were. boolean verbose = !Boolean.parseBoolean(props.getProperty("SILENT")); try { try { + // execStreamSession() above checks if session is destroyed first notifyStreamResult(verbose, "OK", null); ((SAMv3StreamSession)streamSession).accept(this, verbose); return true ; @@ -836,6 +863,21 @@ class SAMv3Handler extends SAMv1Handler if (_log.shouldLog(Log.DEBUG)) _log.debug("STREAM ACCEPT failed", e); notifyStreamResult( verbose, "TIMEOUT", e.getMessage() ); + } catch (I2PSessionException e) { + // As of 0.9.60, this is thrown for a destroyed session. + // Kill the SAM session. + if (_log.shouldDebug()) + _log.debug("STREAM ACCEPT failed", e); + notifyStreamResult (verbose, "I2P_ERROR", e.getMessage()); + // throw so caller can close control socket + throw e; + } catch (RouterRestartException e) { + // Kill the SAM session. + if (_log.shouldDebug()) + _log.debug("STREAM ACCEPT failed", e); + notifyStreamResult (verbose, "I2P_ERROR", "Router restart"); + // throw so caller can close control socket + throw e; } catch (I2PException e) { if (_log.shouldLog(Log.DEBUG)) _log.debug("STREAM ACCEPT failed", e); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java index 61044f4a2..9573b1900 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java @@ -372,7 +372,7 @@ public class I2PSocketManagerFull implements I2PSocketManager { * incoming connections on a subsession. * * @return connected I2PSocket, or null through 0.9.16, non-null as of 0.9.17 - * @throws I2PException if session is closed + * @throws I2PException if session is closed; as of 0.9.60, this is an I2PSessionException which extends I2PException * @throws net.i2p.client.streaming.RouterRestartException (extends I2PException) if the router is apparently restarting, since 0.9.34 * @throws ConnectException (since 0.9.17; I2PServerSocket interface always declared it) * @throws SocketTimeoutException if a timeout was previously set with setSoTimeout and the timeout has been reached. @@ -523,14 +523,20 @@ public class I2PSocketManagerFull implements I2PSocketManager { return _realServerSocket; } + /** + * @throws I2PException if session is closed; as of 0.9.60, this is an I2PSessionException which extends I2PException + */ private void verifySession() throws I2PException { verifySession(_connectionManager.getSession()); } - /** @since 0.9.21 */ + /** + * @throws I2PException if session is closed; as of 0.9.60, this is an I2PSessionException which extends I2PException + * @since 0.9.21 + */ private void verifySession(I2PSession session) throws I2PException { if (_isDestroyed.get()) - throw new I2PException("Session was closed"); + throw new I2PSessionException("Session was closed"); if (!session.isClosed()) return; session.connect();