diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index ce1b67a6cd492cae68615f544c72170a48c3b70b..26ef4d68e2e8b0f12f3e4dba2ed3cd2fe0c15e50 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -277,7 +277,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { setEntry(headers, "Accept-encoding", ""); socket.setReadTimeout(readTimeout); - Socket s = getSocket(socket.getLocalPort()); + Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort()); long afterSocket = getTunnel().getContext().clock().now(); // instead of i2ptunnelrunner, use something that reads the HTTP // request from the socket, modifies the headers, sends the request to the diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java index b50f7185e0596cd21a6f524a6a5cc85ac3841421..5cd691a3f3e081b1f5a0c0752759915f3541b48d 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -137,7 +137,7 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { buf.append("\r\n"); modifiedRegistration = buf.toString(); } - Socket s = getSocket(socket.getLocalPort()); + Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort()); new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), null); } catch (SocketException ex) { try { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index 5daa64935fc58ceb9438e4e5246178934f511fdd..e79bf7ce456a62d2dd34a03340a33eeda24135c0 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.ConnectException; import java.net.InetAddress; +import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; @@ -33,6 +34,7 @@ import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.client.streaming.I2PSocketManagerFactory; import net.i2p.data.Base64; +import net.i2p.data.Hash; import net.i2p.util.EventDispatcher; import net.i2p.util.I2PAppThread; import net.i2p.util.I2PSSLSocketFactory; @@ -62,6 +64,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { private static final String PROP_USE_POOL = "i2ptunnel.usePool"; private static final boolean DEFAULT_USE_POOL = true; public static final String PROP_USE_SSL = "useSSL"; + public static final String PROP_UNIQUE_LOCAL = "enableUniqueLocal"; /** apparently unused */ protected static volatile long __serverId = 0; /** max number of threads - this many slowlorisses will DOS this server, but too high could OOM the JVM */ @@ -518,7 +521,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { //threads. try { socket.setReadTimeout(readTimeout); - Socket s = getSocket(socket.getLocalPort()); + Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort()); afterSocket = getTunnel().getContext().clock().now(); new I2PTunnelRunner(s, socket, slock, null, null); @@ -543,9 +546,10 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { * To configure a specific host:port as the server for incoming port xx, * set option targetForPort.xx=host:port * + * @param from may be used to construct local address since 0.9.13 * @since 0.9.9 */ - protected Socket getSocket(int incomingPort) throws IOException { + protected Socket getSocket(Hash from, int incomingPort) throws IOException { InetAddress host = remoteHost; int port = remotePort; if (incomingPort != 0 && !_socketMap.isEmpty()) { @@ -557,16 +561,17 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { port = isa.getPort(); } } - return getSocket(host, port); + return getSocket(from, host, port); } /** * Get a regular or SSL socket depending on config. * The SSL config applies to all hosts/ports. * + * @param from may be used to construct local address since 0.9.13 * @since 0.9.9 */ - protected Socket getSocket(InetAddress remoteHost, int remotePort) throws IOException { + protected Socket getSocket(Hash from, InetAddress remoteHost, int remotePort) throws IOException { String opt = getTunnel().getClientOptions().getProperty(PROP_USE_SSL); if (Boolean.parseBoolean(opt)) { synchronized(sslLock) { @@ -583,7 +588,26 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { } return _sslFactory.createSocket(remoteHost, remotePort); } else { - return new Socket(remoteHost, remotePort); + // as suggested in https://lists.torproject.org/pipermail/tor-dev/2014-March/006576.html + boolean unique = Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_UNIQUE_LOCAL)); + if (unique && remoteHost.isLoopbackAddress()) { + byte[] addr; + if (remoteHost instanceof Inet4Address) { + addr = new byte[4]; + addr[0] = 127; + System.arraycopy(from.getData(), 0, addr, 1, 3); + } else { + addr = new byte[16]; + addr[0] = (byte) 0xfd; + System.arraycopy(from.getData(), 0, addr, 1, 15); + } + InetAddress local = InetAddress.getByAddress(addr); + // Javadocs say local port of 0 allowed in Java 7. + // Not clear if supported in Java 6 or not. + return new Socket(remoteHost, remotePort, local, 0); + } else { + return new Socket(remoteHost, remotePort); + } } } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index 2f5152ad1f160d9e2e40d0bcbe6b92cd1b7e7f57..1abc56c98d910c3d403ab409bb7185e778817e26 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -25,6 +25,7 @@ import net.i2p.i2ptunnel.I2PTunnelHTTPClient; import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase; import net.i2p.i2ptunnel.I2PTunnelHTTPServer; import net.i2p.i2ptunnel.I2PTunnelIRCClient; +import net.i2p.i2ptunnel.I2PTunnelServer; import net.i2p.i2ptunnel.TunnelController; import net.i2p.i2ptunnel.TunnelControllerGroup; import net.i2p.util.Addresses; @@ -324,6 +325,11 @@ public class EditBean extends IndexBean { public int getPostTotalBanTime(int tunnel) { return getProperty(tunnel, I2PTunnelHTTPServer.OPT_POST_TOTAL_BAN_TIME, I2PTunnelHTTPServer.DEFAULT_POST_TOTAL_BAN_TIME) / 60; } + + /** @since 0.9.13 */ + public boolean getUniqueLocal(int tunnel) { + return getBooleanProperty(tunnel, I2PTunnelServer.PROP_UNIQUE_LOCAL); + } private int getProperty(int tunnel, String prop, int def) { TunnelController tun = getController(tunnel); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index d69080592c13a43678bedc9d50a8a7ad3a146f5f..d275d385ce14a3a83cdf70b46faaabe361f20277 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -823,6 +823,11 @@ public class IndexBean { return false; } + /** @since 0.9.13 */ + public void setUniqueLocal(String moo) { + _booleanOptions.add(I2PTunnelServer.PROP_UNIQUE_LOCAL); + } + protected static final String PROP_ENABLE_ACCESS_LIST = "i2cp.enableAccessList"; protected static final String PROP_ENABLE_BLACKLIST = "i2cp.enableBlackList"; @@ -1259,7 +1264,8 @@ public class IndexBean { private static final String _booleanServerOpts[] = { "i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST, I2PTunnelServer.PROP_USE_SSL, - I2PTunnelHTTPServer.OPT_REJECT_INPROXY + I2PTunnelHTTPServer.OPT_REJECT_INPROXY, + I2PTunnelServer.PROP_UNIQUE_LOCAL }; private static final String _otherClientOpts[] = { "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime", diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 6a39b7a4af1747d4af21b9a47aeca602a0c49a94..4dba5773f7d45b2b3e958adb8ae335baa378d9fa 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -206,9 +206,15 @@ input.default { width: 1px; height: 1px; visibility: hidden; } <%=intl._("Local destination")%>(<span class="accessKey">L</span>): </label> <textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Read Only: Local Destination (if known)" wrap="off" spellcheck="false"><%=editBean.getDestinationBase64(curTunnel)%></textarea> - <% if (!"".equals(editBean.getDestinationBase64(curTunnel))) { %> - <a href="/susidns/addressbook.jsp?book=private&hostname=<%=editBean.getTunnelName(curTunnel)%>&destination=<%=editBean.getDestinationBase64(curTunnel)%>#add"><%=intl._("Add to local addressbook")%></a> - <% } %> + <% String b64 = editBean.getDestinationBase64(curTunnel); + if (!"".equals(b64)) { + String name = editBean.getSpoofedHost(curTunnel); + if (name == null || name.equals("")) + name = editBean.getTunnelName(curTunnel); + if (!"".equals(name)) { %> + <a href="/susidns/addressbook.jsp?book=private&hostname=<%=name%>&destination=<%=b64%>#add"><%=intl._("Add to local addressbook")%></a> + <% } + } %> </div> <% if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) { @@ -396,7 +402,20 @@ input.default { width: 1px; height: 1px; visibility: hidden; } </div> </div> <% } // httpserver - %><div class="subdivider"> + %><div class="rowItem"> + <div id="optionsField" class="rowItem"> + <label> + <%=intl._("Unique Local Address per Client")%>: + </label> + </div> + <div id="portField" class="rowItem"> + <label for="access" accesskey="d"> + <%=intl._("Enable")%>: + </label> + <input value="1" type="checkbox" id="startOnLoad" name="uniqueLocal" title="Use unique IP addresses for each connecting client (local non-SSL servers only)"<%=(editBean.getUniqueLocal(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> + </div> + </div> + <div class="subdivider"> <hr /> </div>