From 0c7a3a3a39713638f31728d722042bc7f678d28d Mon Sep 17 00:00:00 2001
From: str4d <str4d@mail.i2p>
Date: Tue, 15 Jul 2014 12:54:22 +0000
Subject: [PATCH] Stubs for I2CP connections over Unix domain sockets

---
 .../net/i2p/client/DomainSocketFactory.java   | 45 +++++++++++++++++++
 .../src/net/i2p/client/I2PSessionImpl.java    | 23 ++++++++--
 .../router/client/ClientListenerRunner.java   |  7 +--
 .../net/i2p/router/client/ClientManager.java  | 34 ++++++++++----
 .../client/DomainClientListenerRunner.java    | 29 ++++++++++++
 .../client/SSLClientListenerRunner.java       |  2 +-
 6 files changed, 125 insertions(+), 15 deletions(-)
 create mode 100644 core/java/src/net/i2p/client/DomainSocketFactory.java
 create mode 100644 router/java/src/net/i2p/router/client/DomainClientListenerRunner.java

diff --git a/core/java/src/net/i2p/client/DomainSocketFactory.java b/core/java/src/net/i2p/client/DomainSocketFactory.java
new file mode 100644
index 0000000000..280236e5e7
--- /dev/null
+++ b/core/java/src/net/i2p/client/DomainSocketFactory.java
@@ -0,0 +1,45 @@
+package net.i2p.client;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import net.i2p.I2PAppContext;
+
+/**
+ * Bridge to Unix domain socket (or similar).
+ * <p/>
+ * This is a stub that does nothing.
+ * This class is replaced in the Android build.
+ *
+ * @author str4d
+ * @since 0.9.15
+ */
+public class DomainSocketFactory {
+    public static String I2CP_SOCKET_ADDRESS = "net.i2p.client.i2cp";
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    public DomainSocketFactory(I2PAppContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Override in Android.
+     * @throws IOException
+     * @throws UnsupportedOperationException always
+     */
+    public Socket createSocket(String name) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Override in Android.
+     * @throws IOException
+     * @throws UnsupportedOperationException always
+     */
+    public ServerSocket createServerSocket(String name) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java
index 20aa43550b..fd60b7404b 100644
--- a/core/java/src/net/i2p/client/I2PSessionImpl.java
+++ b/core/java/src/net/i2p/client/I2PSessionImpl.java
@@ -52,6 +52,7 @@ import net.i2p.util.LHMCache;
 import net.i2p.util.Log;
 import net.i2p.util.OrderedProperties;
 import net.i2p.util.SimpleTimer2;
+import net.i2p.util.SystemVersion;
 import net.i2p.util.VersionComparator;
 
 /**
@@ -157,6 +158,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
     protected static final String PROP_USER = "i2cp.username";
     protected static final String PROP_PW = "i2cp.password";
 
+    /**
+     * Use Unix domain socket (or similar) to connect to a router
+     * @since 0.9.15
+     */
+    protected static final String PROP_DOMAIN_SOCKET = "i2cp.domainSocket";
+
     private static final long VERIFY_USAGE_TIME = 60*1000;
 
     private static final long MAX_SEND_WAIT = 10*1000;
@@ -279,6 +286,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
         if (_context.isRouterContext())
             // just for logging
             return "[internal connection]";
+        else if (SystemVersion.isAndroid() &&
+                Boolean.parseBoolean(_options.getProperty(PROP_DOMAIN_SOCKET)))
+            // just for logging
+            return "[Domain socket connection]";
         return _options.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
     }
 
@@ -287,7 +298,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
      * @since 0.9.7 was in loadConfig()
      */
     private int getPort() {
-        if (_context.isRouterContext())
+        if (_context.isRouterContext() ||
+                (SystemVersion.isAndroid() &&
+                        Boolean.parseBoolean(_options.getProperty(PROP_DOMAIN_SOCKET))))
             // just for logging
             return 0;
         String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, LISTEN_PORT + "");
@@ -447,7 +460,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
         try {
             // protect w/ closeSocket()
             synchronized(_stateLock) {
-                // If we are in the router JVM, connect using the interal queue
+                // If we are in the router JVM, connect using the internal queue
                 if (_context.isRouterContext()) {
                     // _socket and _writer remain null
                     InternalClientManager mgr = _context.internalClientManager();
@@ -457,7 +470,11 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
                     _queue = mgr.connect();
                     _reader = new QueuedI2CPMessageReader(_queue, this);
                 } else {
-                    if (Boolean.parseBoolean(_options.getProperty(PROP_ENABLE_SSL))) {
+                    if (SystemVersion.isAndroid() &&
+                            Boolean.parseBoolean(_options.getProperty(PROP_DOMAIN_SOCKET))) {
+                        final DomainSocketFactory fact = new DomainSocketFactory(_context);
+                        _socket = fact.createSocket(DomainSocketFactory.I2CP_SOCKET_ADDRESS);
+                    } else if (Boolean.parseBoolean(_options.getProperty(PROP_ENABLE_SSL))) {
                         try {
                             I2PSSLSocketFactory fact = new I2PSSLSocketFactory(_context, false, "certificates/i2cp");
                             _socket = fact.createSocket(_hostname, _portNum);
diff --git a/router/java/src/net/i2p/router/client/ClientListenerRunner.java b/router/java/src/net/i2p/router/client/ClientListenerRunner.java
index 9b169ca757..44d7811b6b 100644
--- a/router/java/src/net/i2p/router/client/ClientListenerRunner.java
+++ b/router/java/src/net/i2p/router/client/ClientListenerRunner.java
@@ -64,7 +64,9 @@ class ClientListenerRunner implements Runnable {
             return new ServerSocket(_port, 0, InetAddress.getByName(listenInterface));
         }
     }
-                
+
+    public void run() { runServer(); }
+
     /** 
      * Start up the socket listener, listens for connections, and
      * fires those connections off via {@link #runConnection runConnection}.  
@@ -72,7 +74,7 @@ class ClientListenerRunner implements Runnable {
      * failure.
      *
      */
-    public void runServer() {
+    protected void runServer() {
         _running = true;
         int curDelay = 1000;
         while (_running) {
@@ -173,5 +175,4 @@ class ClientListenerRunner implements Runnable {
             _socket = null;
         } catch (IOException ioe) {}
     }
-    public void run() { runServer(); }
 }
diff --git a/router/java/src/net/i2p/router/client/ClientManager.java b/router/java/src/net/i2p/router/client/ClientManager.java
index 96e6ac20b6..c982671b75 100644
--- a/router/java/src/net/i2p/router/client/ClientManager.java
+++ b/router/java/src/net/i2p/router/client/ClientManager.java
@@ -10,8 +10,10 @@ package net.i2p.router.client;
 
 import java.io.IOException;
 import java.io.Writer;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -37,6 +39,7 @@ import net.i2p.router.JobImpl;
 import net.i2p.router.RouterContext;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
+import net.i2p.util.SystemVersion;
 
 /**
  * Coordinate connections and various tasks
@@ -45,7 +48,7 @@ import net.i2p.util.Log;
  */
 class ClientManager {
     private final Log _log;
-    protected ClientListenerRunner _listener;
+    protected List<ClientListenerRunner> _listeners;
     // Destination --> ClientConnectionRunner
     // Locked for adds/removes but not lookups
     private final Map<Destination, ClientConnectionRunner>  _runners;
@@ -87,6 +90,7 @@ class ClientManager {
         //                                      "How large are messages received by the client?", 
         //                                      "ClientMessages", 
         //                                      new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
+        _listeners = new ArrayList<ClientListenerRunner>();
         _runners = new ConcurrentHashMap<Destination, ClientConnectionRunner>();
         _runnersByHash = new ConcurrentHashMap<Hash, ClientConnectionRunner>();
         _pendingRunners = new HashSet<ClientConnectionRunner>();
@@ -105,14 +109,22 @@ class ClientManager {
 
     /** Todo: Start a 3rd listener for IPV6? */
     protected void startListeners() {
+        ClientListenerRunner listener;
+        if (SystemVersion.isAndroid()) {
+            listener = new DomainClientListenerRunner(_ctx, this);
+            Thread t = new I2PThread(listener, "DomainClientListener", true);
+            t.start();
+            _listeners.add(listener);
+        }
         if (!_ctx.getBooleanProperty(PROP_DISABLE_EXTERNAL)) {
             // there's no option to start both an SSL and non-SSL listener
             if (_ctx.getBooleanProperty(PROP_ENABLE_SSL))
-                _listener = new SSLClientListenerRunner(_ctx, this, _port);
+                listener = new SSLClientListenerRunner(_ctx, this, _port);
             else
-                _listener = new ClientListenerRunner(_ctx, this, _port);
-            Thread t = new I2PThread(_listener, "ClientListener:" + _port, true);
+                listener = new ClientListenerRunner(_ctx, this, _port);
+            Thread t = new I2PThread(listener, "ClientListener:" + _port, true);
             t.start();
+            _listeners.add(listener);
         }
         _isStarted = true;
     }
@@ -132,8 +144,9 @@ class ClientManager {
     public synchronized void shutdown(String msg) {
         _isStarted = false;
         _log.info("Shutting down the ClientManager");
-        if (_listener != null)
-            _listener.stopListening();
+        for (ClientListenerRunner listener : _listeners)
+            listener.stopListening();
+        _listeners.clear();
         Set<ClientConnectionRunner> runners = new HashSet<ClientConnectionRunner>();
         synchronized (_runners) {
             for (ClientConnectionRunner runner : _runners.values()) {
@@ -169,8 +182,13 @@ class ClientManager {
         return hisQueue;
     }
 
-    public boolean isAlive() {
-        return _isStarted && (_listener == null || _listener.isListening());
+    public synchronized boolean isAlive() {
+        boolean listening = true;
+        if (!_listeners.isEmpty()) {
+            for (ClientListenerRunner listener : _listeners)
+                listening = listening && listener.isListening();
+        }
+        return _isStarted && (_listeners.isEmpty() || listening);
     }
 
     public void registerConnection(ClientConnectionRunner runner) {
diff --git a/router/java/src/net/i2p/router/client/DomainClientListenerRunner.java b/router/java/src/net/i2p/router/client/DomainClientListenerRunner.java
new file mode 100644
index 0000000000..89dbae6b72
--- /dev/null
+++ b/router/java/src/net/i2p/router/client/DomainClientListenerRunner.java
@@ -0,0 +1,29 @@
+package net.i2p.router.client;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+import net.i2p.client.DomainSocketFactory;
+import net.i2p.router.RouterContext;
+
+/**
+ * Unix domain socket version of ClientListenerRunner.
+ * This is a stub that does nothing.
+ * This class is replaced in the Android build.
+ *
+ * @since 0.9.15
+ */
+public class DomainClientListenerRunner extends ClientListenerRunner {
+    public DomainClientListenerRunner(RouterContext context, ClientManager manager) {
+        super(context, manager, -1);
+    }
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    protected ServerSocket getServerSocket() throws IOException {
+        final DomainSocketFactory fact = new DomainSocketFactory(_context);
+        return fact.createServerSocket(DomainSocketFactory.I2CP_SOCKET_ADDRESS);
+    }
+}
diff --git a/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java b/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java
index fcd008ad68..948cb7527a 100644
--- a/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java
+++ b/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java
@@ -181,7 +181,7 @@ class SSLClientListenerRunner extends ClientListenerRunner {
      * Create (if necessary) and load the key store, then run.
      */
     @Override
-    public void runServer() {
+    protected void runServer() {
         File keyStore = new File(_context.getConfigDir(), "keystore/i2cp.ks");
         if (verifyKeyStore(keyStore) && initializeFactory(keyStore)) {
             super.runServer();
-- 
GitLab