From cdab6f8b76eaabe69837bad2f792a6ef1bcf115f Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Fri, 12 Aug 2016 16:41:42 +0000
Subject: [PATCH] i2ptunnel: Add outproxy plugin support to SOCKS (ticket
 #1824) Make some classes package private Move some fields to SocksServer
 superclass

---
 .../i2ptunnel/socks/I2PSOCKSIRCTunnel.java    |   2 +-
 .../i2p/i2ptunnel/socks/I2PSOCKSTunnel.java   |   2 +-
 .../i2p/i2ptunnel/socks/SOCKS4aServer.java    |  78 ++++++------
 .../net/i2p/i2ptunnel/socks/SOCKS5Server.java |  68 +++++-----
 .../i2p/i2ptunnel/socks/SOCKSException.java   |   7 +-
 .../net/i2p/i2ptunnel/socks/SOCKSServer.java  |  43 ++++++-
 .../i2ptunnel/socks/SOCKSServerFactory.java   |   9 +-
 .../i2p/i2ptunnel/socks/SocketWrapper.java    | 119 ++++++++++++++++++
 8 files changed, 256 insertions(+), 72 deletions(-)
 create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java

diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java
index 8484ef0c23..8b9f705919 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java
@@ -47,7 +47,7 @@ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel {
     protected void clientConnectionRun(Socket s) {
         try {
             //_log.error("SOCKS IRC Tunnel Start");
-            SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s, getTunnel().getClientOptions());
+            SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(_context, s, getTunnel().getClientOptions());
             Socket clientSock = serv.getClientSocket();
             I2PSocket destSock = serv.getDestinationI2PSocket(this);
             StringBuffer expectedPong = new StringBuffer();
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java
index 9167939417..ab6b7a7c1c 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java
@@ -48,7 +48,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
 
     protected void clientConnectionRun(Socket s) {
         try {
-            SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s, getTunnel().getClientOptions());
+            SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(_context, s, getTunnel().getClientOptions());
             Socket clientSock = serv.getClientSocket();
             I2PSocket destSock = serv.getDestinationI2PSocket(this);
             Thread t = new I2PTunnelRunner(clientSock, destSock, sockLock, null, null, mySockets,
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java
index 1a9586b6ef..f32cdabfc0 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java
@@ -19,6 +19,9 @@ import java.util.Properties;
 
 import net.i2p.I2PAppContext;
 import net.i2p.I2PException;
+import net.i2p.app.ClientApp;
+import net.i2p.app.ClientAppManager;
+import net.i2p.app.Outproxy;
 import net.i2p.client.streaming.I2PSocket;
 import net.i2p.client.streaming.I2PSocketOptions;
 import net.i2p.data.DataFormatException;
@@ -32,10 +35,8 @@ import net.i2p.util.Log;
  *
  * @author zzz modded from SOCKS5Server
  */
-public class SOCKS4aServer extends SOCKSServer {
-    private final Log _log;
+class SOCKS4aServer extends SOCKSServer {
 
-    private final Socket clientSock;
     private boolean setupCompleted;
 
     /**
@@ -49,15 +50,12 @@ public class SOCKS4aServer extends SOCKSServer {
      * @param clientSock client socket
      * @param props non-null
      */
-    public SOCKS4aServer(Socket clientSock, Properties props) {
-        this.clientSock = clientSock;
-        this.props = props;
-        _log = I2PAppContext.getGlobalContext().logManager().getLog(SOCKS4aServer.class);
+    public SOCKS4aServer(I2PAppContext ctx, Socket clientSock, Properties props) {
+        super(ctx, clientSock, props);
     }
 
     public Socket getClientSocket() throws SOCKSException {
         setupServer();
-
         return clientSock;
     }
 
@@ -211,12 +209,8 @@ public class SOCKS4aServer extends SOCKSServer {
         I2PSocket destSock;
 
         try {
-            if (connHostName.toLowerCase(Locale.US).endsWith(".i2p") ||
-                connHostName.toLowerCase(Locale.US).endsWith(".onion")) {
-                // Let's not do a new Dest for every request, huh?
-                //I2PSocketManager sm = I2PSocketManagerFactory.createManager();
-                //destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
-                Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(connHostName);
+            if (connHostName.toLowerCase(Locale.US).endsWith(".i2p")) {
+                Destination dest = _context.namingService().lookup(connHostName);
                 if (dest == null) {
                     try {
                         sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
@@ -236,6 +230,7 @@ public class SOCKS4aServer extends SOCKSServer {
                     sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
                 } catch (IOException ioe) {}
                 throw new SOCKSException(err);
+          /****
             } else if (connPort == 80) {
                 // rewrite GET line to include hostname??? or add Host: line???
                 // or forward to local eepProxy (but that's a Socket not an I2PSocket)
@@ -246,30 +241,43 @@ public class SOCKS4aServer extends SOCKSServer {
                     sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
                 } catch (IOException ioe) {}
                 throw new SOCKSException(err);
+           ****/
             } else {
-                List<String> proxies = t.getProxies(connPort);
-                if (proxies == null || proxies.isEmpty()) {
-                    String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName;
-                    _log.error(err);
+                Outproxy outproxy = getOutproxyPlugin();
+                if (outproxy != null) {
                     try {
-                        sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
-                    } catch (IOException ioe) {}
-                    throw new SOCKSException(err);
+                        destSock = new SocketWrapper(outproxy.connect(connHostName, connPort));
+                    } catch (IOException ioe) {
+                        try {
+                            sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+                        } catch (IOException ioe2) {}
+                        throw new SOCKSException("connect failed via outproxy plugin", ioe);
+                    }
+                } else {
+                    List<String> proxies = t.getProxies(connPort);
+                    if (proxies == null || proxies.isEmpty()) {
+                        String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName;
+                        _log.error(err);
+                        try {
+                            sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+                        } catch (IOException ioe) {}
+                        throw new SOCKSException(err);
+                    }
+                    int p = _context.random().nextInt(proxies.size());
+                    String proxy = proxies.get(p);
+                    Destination dest = _context.namingService().lookup(proxy);
+                    if (dest == null) {
+                        try {
+                            sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
+                        } catch (IOException ioe) {}
+                        throw new SOCKSException("Outproxy not found");
+                    }
+                    if (_log.shouldDebug())
+                        _log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "...");
+                    // this isn't going to work, these need to be socks outproxies so we need
+                    // to do a socks session to them?
+                    destSock = t.createI2PSocket(dest);
                 }
-                int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size());
-                String proxy = proxies.get(p);
-                Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(proxy);
-                if (dest == null) {
-                    try {
-                        sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
-                    } catch (IOException ioe) {}
-                    throw new SOCKSException("Outproxy not found");
-                }
-                if (_log.shouldDebug())
-                    _log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "...");
-                // this isn't going to work, these need to be socks outproxies so we need
-                // to do a socks session to them?
-                destSock = t.createI2PSocket(dest);
             }
             confirmConnection();
             _log.debug("connection confirmed - exchanging data...");
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java
index 9c2e19b849..63fbf6c286 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java
@@ -21,6 +21,9 @@ import java.util.Properties;
 
 import net.i2p.I2PAppContext;
 import net.i2p.I2PException;
+import net.i2p.app.ClientApp;
+import net.i2p.app.ClientAppManager;
+import net.i2p.app.Outproxy;
 import net.i2p.client.streaming.I2PSocket;
 import net.i2p.client.streaming.I2PSocketOptions;
 import net.i2p.data.DataFormatException;
@@ -37,12 +40,10 @@ import net.i2p.util.Log;
  *
  * @author human
  */
-public class SOCKS5Server extends SOCKSServer {
-    private final Log _log;
+class SOCKS5Server extends SOCKSServer {
 
     private static final int SOCKS_VERSION_5 = 0x05;
 
-    private final Socket clientSock;
     private boolean setupCompleted = false;
     private final boolean authRequired;
 
@@ -57,14 +58,12 @@ public class SOCKS5Server extends SOCKSServer {
      * @param clientSock client socket
      * @param props non-null
      */
-    public SOCKS5Server(Socket clientSock, Properties props) {
-        this.clientSock = clientSock;
-        this.props = props;
+    public SOCKS5Server(I2PAppContext ctx, Socket clientSock, Properties props) {
+        super(ctx, clientSock, props);
         this.authRequired =
                     Boolean.parseBoolean(props.getProperty(I2PTunnelHTTPClientBase.PROP_AUTH)) &&
                     props.containsKey(I2PTunnelHTTPClientBase.PROP_USER) &&
                     props.containsKey(I2PTunnelHTTPClientBase.PROP_PW);
-        _log = I2PAppContext.getGlobalContext().logManager().getLog(SOCKS5Server.class);
     }
 
     public Socket getClientSocket() throws SOCKSException {
@@ -366,7 +365,7 @@ public class SOCKS5Server extends SOCKSServer {
                 // Let's not do a new Dest for every request, huh?
                 //I2PSocketManager sm = I2PSocketManagerFactory.createManager();
                 //destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
-                Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(connHostName);
+                Destination dest = _context.namingService().lookup(connHostName);
                 if (dest == null) {
                     try {
                         sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
@@ -399,27 +398,40 @@ public class SOCKS5Server extends SOCKSServer {
                 throw new SOCKSException(err);
            ****/
             } else {
-                List<String> proxies = t.getProxies(connPort);
-                if (proxies == null || proxies.isEmpty()) {
-                    String err = "No outproxy configured for port " + connPort + " and no default configured either";
-                    _log.error(err);
+                Outproxy outproxy = getOutproxyPlugin();
+                if (outproxy != null) {
+                    // In HTTPClient, we use OutproxyRunner to run a Socket,
+                    // but here, we wrap a Socket in a I2PSocket and use the regular Runner.
                     try {
-                        sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
-                    } catch (IOException ioe) {}
-                    throw new SOCKSException(err);
-                }
-                int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size());
-                String proxy = proxies.get(p);
-                if (_log.shouldLog(Log.DEBUG))
-                    _log.debug("connecting to proxy " + proxy + " for " + connHostName + " port " + connPort);
-
-                try {
-                    destSock = outproxyConnect(t, proxy);
-                } catch (SOCKSException se) {
+                        destSock = new SocketWrapper(outproxy.connect(connHostName, connPort));
+                    } catch (IOException ioe) {
+                        try {
+                            sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
+                        } catch (IOException ioe2) {}
+                        throw new SOCKSException("connect failed via outproxy plugin", ioe);
+                    }
+                } else {
+                    List<String> proxies = t.getProxies(connPort);
+                    if (proxies == null || proxies.isEmpty()) {
+                        String err = "No outproxy configured for port " + connPort + " and no default configured either";
+                        _log.error(err);
+                        try {
+                            sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
+                        } catch (IOException ioe) {}
+                        throw new SOCKSException(err);
+                    }
+                    int p = _context.random().nextInt(proxies.size());
+                    String proxy = proxies.get(p);
+                    if (_log.shouldLog(Log.DEBUG))
+                        _log.debug("connecting to proxy " + proxy + " for " + connHostName + " port " + connPort);
                     try {
-                        sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
-                    } catch (IOException ioe) {}
-                    throw se;
+                        destSock = outproxyConnect(t, proxy);
+                    } catch (SOCKSException se) {
+                        try {
+                            sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
+                        } catch (IOException ioe) {}
+                        throw se;
+                    }
                 }
             }
             confirmConnection();
@@ -466,7 +478,7 @@ public class SOCKS5Server extends SOCKSServer {
         Properties overrides = new Properties();
         overrides.setProperty("option.i2p.streaming.connectDelay", "1000");
         I2PSocketOptions proxyOpts = tun.buildOptions(overrides);
-        Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(proxy);
+        Destination dest = _context.namingService().lookup(proxy);
         if (dest == null)
             throw new SOCKSException("Outproxy not found");
         I2PSocket destSock = tun.createI2PSocket(dest, proxyOpts);
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSException.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSException.java
index cddd019ab0..3b663dd252 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSException.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSException.java
@@ -20,4 +20,9 @@ public class SOCKSException extends Exception {
     public SOCKSException(String s) {
         super(s);
     }
-}
\ No newline at end of file
+
+    /** @since 0.9.27 */
+    public SOCKSException(String s, Throwable t) {
+        super(s, t);
+    }
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java
index 3e1506793e..b269b6798d 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java
@@ -9,14 +9,20 @@ package net.i2p.i2ptunnel.socks;
 import java.net.Socket;
 import java.util.Properties;
 
+import net.i2p.I2PAppContext;
+import net.i2p.app.ClientApp;
+import net.i2p.app.ClientAppManager;
+import net.i2p.app.Outproxy;
 import net.i2p.client.streaming.I2PSocket;
+import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
+import net.i2p.util.Log;
 
 /**
  * Abstract base class used by all SOCKS servers.
  *
  * @author human
  */
-public abstract class SOCKSServer {
+abstract class SOCKSServer {
 
     private static final String PROP_MAPPING_PREFIX = "ipmapping.";
 
@@ -25,7 +31,18 @@ public abstract class SOCKSServer {
     protected int connPort;
     protected int addressType;
 
-    protected Properties props;
+    protected final I2PAppContext _context;
+    protected final Socket clientSock;
+    protected final Properties props;
+    protected final Log _log;
+
+    /** @since 0.9.27 */
+    protected SOCKSServer(I2PAppContext ctx, Socket clientSock, Properties props) {
+        _context = ctx;
+        this.clientSock = clientSock;
+        this.props = props;
+        _log = ctx.logManager().getLog(getClass());
+    }
 
     /**
      * IP to domain name mapping support. This matches the given IP string
@@ -68,4 +85,26 @@ public abstract class SOCKSServer {
      */
     public abstract I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException;
 
+    /**
+     *  @since 0.9.27
+     */
+    private boolean shouldUseOutproxyPlugin() {
+        return Boolean.parseBoolean(props.getProperty(I2PTunnelHTTPClient.PROP_USE_OUTPROXY_PLUGIN, "true"));
+    }
+
+    /**
+     *  @return null if disabled or not installed
+     *  @since 0.9.27
+     */
+    protected Outproxy getOutproxyPlugin() {
+        if (shouldUseOutproxyPlugin()) {
+            ClientAppManager mgr = _context.clientAppManager();
+            if (mgr != null) {
+                ClientApp op = mgr.getRegisteredApp(Outproxy.NAME);
+                if (op != null)
+                    return (Outproxy) op;
+            }
+        }
+        return null;
+    }
 }
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java
index 049d190b65..416c0a62be 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java
@@ -12,13 +12,14 @@ import java.io.IOException;
 import java.net.Socket;
 import java.util.Properties;
 
+import net.i2p.I2PAppContext;
 import net.i2p.data.DataHelper;
 import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
 
 /**
  * Factory class for creating SOCKS forwarders through I2P
  */
-public class SOCKSServerFactory {
+class SOCKSServerFactory {
 
     private final static String ERR_REQUEST_DENIED =
         "HTTP/1.1 403 Access Denied - This is a SOCKS proxy, not a HTTP proxy\r\n" +
@@ -38,7 +39,7 @@ public class SOCKSServerFactory {
      * @param s a Socket used to choose the SOCKS server type
      * @param props non-null
      */
-    public static SOCKSServer createSOCKSServer(Socket s, Properties props) throws SOCKSException {
+    public static SOCKSServer createSOCKSServer(I2PAppContext ctx, Socket s, Properties props) throws SOCKSException {
         SOCKSServer serv;
 
         try {
@@ -53,11 +54,11 @@ public class SOCKSServerFactory {
                     props.containsKey(I2PTunnelHTTPClientBase.PROP_PW)) {
                     throw new SOCKSException("SOCKS 4/4a not supported when authorization is required");
                 }
-                serv = new SOCKS4aServer(s, props);
+                serv = new SOCKS4aServer(ctx, s, props);
                 break;
             case 0x05:
                 // SOCKS version 5
-                serv = new SOCKS5Server(s, props);
+                serv = new SOCKS5Server(ctx, s, props);
                 break;
             case 'C':
             case 'G':
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java
new file mode 100644
index 0000000000..e6b3b15f65
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java
@@ -0,0 +1,119 @@
+package net.i2p.i2ptunnel.socks;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.channels.SelectableChannel;
+
+import net.i2p.client.streaming.I2PSocket;
+import net.i2p.client.streaming.I2PSocketOptions;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.Destination;
+
+/**
+ *  Wrapper around the Socket obtained from the Outproxy, which is a
+ *  wrapper around the Orchid Stream.
+ *
+ *  @since 0.9.27
+ */
+class SocketWrapper implements I2PSocket {
+
+    private final Socket socket;
+
+    private static final Destination DUMMY_DEST = new Destination();
+    static {
+        try {
+           DUMMY_DEST.fromByteArray(new byte[387]);
+        } catch (DataFormatException dfe) {
+           throw new RuntimeException(dfe);
+        }
+    }
+
+    public SocketWrapper(Socket sock) {
+        socket = sock;
+    }
+
+    /**
+     * @return the Destination of this side of the socket.
+     */
+    public Destination getThisDestination() {
+        return DUMMY_DEST;
+    }
+
+    /**
+     * @return the destination of the peer.
+     */
+    public Destination getPeerDestination() {
+        return DUMMY_DEST;
+    }
+
+    public InputStream getInputStream() throws IOException {
+        return socket.getInputStream();
+    }
+
+    public OutputStream getOutputStream() throws IOException {
+        return socket.getOutputStream();
+    }
+
+    /**
+     *  @return null always
+     */
+    @Deprecated
+    public SelectableChannel getChannel() {
+        return null;
+    }
+
+    /**
+     *  @return null always
+     */
+    public I2PSocketOptions getOptions() {
+        return null;
+    }
+
+    /** 
+     * Does nothing
+     */
+    public void setOptions(I2PSocketOptions options) {}
+    
+    public long getReadTimeout() {
+        return -1;
+    }
+
+    public void setReadTimeout(long ms) {}
+
+    public void close() throws IOException {
+        socket.close();
+    }
+
+    public boolean isClosed() {
+        return socket.isClosed();
+    }
+
+    /**
+     *  Deprecated, unimplemented, does nothing
+     */
+    public void setSocketErrorListener(SocketErrorListener lsnr) {}
+
+    /**
+     *  The remote port.
+     *  @return Default I2PSession.PORT_UNSPECIFIED (0) or PORT_ANY (0)
+     */
+    public int getPort() {
+        try {
+            return socket.getPort();
+        } catch (UnsupportedOperationException uoe) {
+            // prior to 1.2.2-0.2
+            return 0;
+        }
+    }
+
+    /**
+     *  The local port.
+     *  @return 0 always
+     */
+    public int getLocalPort() {
+        return 0;
+    }
+}
-- 
GitLab