From 4ea99b8a10018970ca9aa4568aacd8e9dc95b547 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Sun, 24 May 2015 00:14:32 +0000
Subject: [PATCH] I2CP: Take 2 of fix, so a newly created session isn't
 destroyed and immediately replaced by i2ptunnel, which caused dup shared
 clients in a race at startup; Clarify session exception text if not open

---
 .../i2p/i2ptunnel/I2PTunnelClientBase.java    | 21 ++++++++++++++-----
 core/java/src/net/i2p/client/I2PSession.java  |  2 +-
 .../src/net/i2p/client/I2PSessionImpl.java    |  9 ++++++--
 .../src/net/i2p/client/I2PSessionImpl2.java   |  7 ++++++-
 .../net/i2p/client/I2PSessionMuxedImpl.java   |  7 ++++++-
 .../src/net/i2p/router/RouterVersion.java     |  2 +-
 6 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
index a0a68aa43b..2d4d7e0dd0 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
@@ -77,6 +77,16 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
 
     private volatile ThreadPoolExecutor _executor;
 
+    /** this is ONLY for shared clients */
+    private static I2PSocketManager socketManager;
+
+    /**
+     *  Only destroy and replace a static shared client socket manager if it's been connected before
+     *  @since 0.9.20
+     */
+    private enum SocketManagerState { INIT, CONNECTED }
+    private static SocketManagerState _socketManagerState = SocketManagerState.INIT;
+
     public static final String PROP_USE_SSL = I2PTunnelServer.PROP_USE_SSL;
 
     /**
@@ -239,10 +249,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
         connectManager();
     }
 
-    /** this is ONLY for shared clients */
-    private static I2PSocketManager socketManager;
-
-
     /**
      * This is ONLY for shared clients.
      * As of 0.9.20 this is fast, and does NOT connect the manager to the router.
@@ -283,12 +289,13 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
         Log _log = tunnel.getContext().logManager().getLog(I2PTunnelClientBase.class);
         if (socketManager != null && !socketManager.isDestroyed()) {
             I2PSession s = socketManager.getSession();
-            if (s.isClosed()) {
+            if (s.isClosed() && _socketManagerState != SocketManagerState.INIT) {
                 if (_log.shouldLog(Log.INFO))
                     _log.info(tunnel.getClientOptions().getProperty("inbound.nickname") + ": Building a new socket manager since the old one closed [s=" + s + "]");
                 tunnel.removeSession(s);
                 // make sure the old one is closed
                 socketManager.destroySocketManager();
+                _socketManagerState = SocketManagerState.INIT;
                 // We could be here a LONG time, holding the lock
                 socketManager = buildSocketManager(tunnel, pkf);
             } else {
@@ -424,6 +431,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
         while (sockMgr.getSession().isClosed()) {
             try {
                 sockMgr.getSession().connect();
+                synchronized(I2PTunnelClientBase.class) {
+                    if (sockMgr == socketManager)
+                        _socketManagerState = SocketManagerState.CONNECTED;
+                }
             } catch (I2PSessionException ise) {
                 // shadows instance _log
                 Log _log = getTunnel().getContext().logManager().getLog(I2PTunnelClientBase.class);
diff --git a/core/java/src/net/i2p/client/I2PSession.java b/core/java/src/net/i2p/client/I2PSession.java
index da67e2c02c..a6ce4122ee 100644
--- a/core/java/src/net/i2p/client/I2PSession.java
+++ b/core/java/src/net/i2p/client/I2PSession.java
@@ -257,7 +257,7 @@ public interface I2PSession {
     /** 
      * Have we closed the session? 
      *
-     * @return true if the session is closed
+     * @return true if the session is closed, OR connect() has not been called yet
      */
     public boolean isClosed();
     
diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java
index cd6154c96a..7527dd8862 100644
--- a/core/java/src/net/i2p/client/I2PSessionImpl.java
+++ b/core/java/src/net/i2p/client/I2PSessionImpl.java
@@ -135,7 +135,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
         CLOSED
     }
 
-    private State _state = State.INIT;
+    protected State _state = State.INIT;
     protected final Object _stateLock = new Object();
 
     /** 
@@ -614,7 +614,12 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
      * Report abuse with regards to the given messageId
      */
     public void reportAbuse(int msgId, int severity) throws I2PSessionException {
-        if (isClosed()) throw new I2PSessionException(getPrefix() + "Already closed");
+        synchronized (_stateLock) {
+            if (_state == State.CLOSED)
+                throw new I2PSessionException("Already closed");
+            if (_state == State.INIT)
+                throw new I2PSessionException("Not open, must call connect() first");
+        }
         _producer.reportAbuse(this, msgId, severity);
     }
 
diff --git a/core/java/src/net/i2p/client/I2PSessionImpl2.java b/core/java/src/net/i2p/client/I2PSessionImpl2.java
index 9f8a5465d3..5cd2b930c4 100644
--- a/core/java/src/net/i2p/client/I2PSessionImpl2.java
+++ b/core/java/src/net/i2p/client/I2PSessionImpl2.java
@@ -247,7 +247,12 @@ class I2PSessionImpl2 extends I2PSessionImpl {
     public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expires)
                    throws I2PSessionException {
         if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message");
-        if (isClosed()) throw new I2PSessionException("Already closed");
+        synchronized (_stateLock) {
+            if (_state == State.CLOSED)
+                throw new I2PSessionException("Already closed");
+            if (_state == State.INIT)
+                throw new I2PSessionException("Not open, must call connect() first");
+        }
         updateActivity();
 
         // Sadly there is no way to send something completely uncompressed in a backward-compatible way,
diff --git a/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java
index 48ca6016e8..0185496aa3 100644
--- a/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java
+++ b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java
@@ -256,7 +256,12 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
      * @since 0.9.14
      */
     private byte[] prepPayload(byte[] payload, int offset, int size, int proto, int fromPort, int toPort) throws I2PSessionException {
-        if (isClosed()) throw new I2PSessionException("Already closed");
+        synchronized (_stateLock) {
+            if (_state == State.CLOSED)
+                throw new I2PSessionException("Already closed");
+            if (_state == State.INIT)
+                throw new I2PSessionException("Not open, must call connect() first");
+        }
         updateActivity();
 
         if (shouldCompress(size))
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 0cd02d33d1..5fd579e05b 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 29;
+    public final static long BUILD = 30;
 
     /** for example "-test" */
     public final static String EXTRA = "-rc";
-- 
GitLab