From 3ec78e27b4b89e660c4718d16e44d315d3ce31ae Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 May 2013 12:55:35 +0000 Subject: [PATCH 01/38] Start new IPv6 branch - Add new TransportUtil for getting/setting IPv6 config - Prep for supporting multiple RouterAddresses per-transport - RouterAddress hashCode/equals tweaks --- core/java/src/net/i2p/data/RouterAddress.java | 12 ++- .../src/net/i2p/router/CommSystemFacade.java | 4 +- .../transport/CommSystemFacadeImpl.java | 33 ++++--- .../net/i2p/router/transport/Transport.java | 27 +++++- .../i2p/router/transport/TransportImpl.java | 60 ++++++++++--- .../router/transport/TransportManager.java | 87 ++++++++++++------- .../i2p/router/transport/TransportUtil.java | 78 +++++++++++++++++ .../src/net/i2p/router/transport/UPnP.java | 1 + .../net/i2p/router/transport/UPnPManager.java | 9 +- .../router/transport/ntcp/NTCPTransport.java | 14 ++- .../router/transport/udp/UDPTransport.java | 7 +- 11 files changed, 255 insertions(+), 77 deletions(-) create mode 100644 router/java/src/net/i2p/router/transport/TransportUtil.java diff --git a/core/java/src/net/i2p/data/RouterAddress.java b/core/java/src/net/i2p/data/RouterAddress.java index c21f0f5bf1..d003cc0502 100644 --- a/core/java/src/net/i2p/data/RouterAddress.java +++ b/core/java/src/net/i2p/data/RouterAddress.java @@ -238,14 +238,18 @@ public class RouterAddress extends DataStructureImpl { DataHelper.writeProperties(out, _options); } + /** + * Transport, IP, and port only. + * Never look at cost or other properties. + */ @Override public boolean equals(Object object) { if (object == this) return true; if ((object == null) || !(object instanceof RouterAddress)) return false; RouterAddress addr = (RouterAddress) object; - // let's keep this fast as we are putting an address into the RouterInfo set frequently return - _cost == addr._cost && + getPort() == addr.getPort() && + DataHelper.eq(getIP(), addr.getIP()) && DataHelper.eq(_transportStyle, addr._transportStyle); //DataHelper.eq(_options, addr._options) && //DataHelper.eq(_expiration, addr._expiration); @@ -253,13 +257,13 @@ public class RouterAddress extends DataStructureImpl { /** * Just use a few items for speed (expiration is always null). + * Never look at cost or other properties. */ @Override public int hashCode() { return DataHelper.hashCode(_transportStyle) ^ DataHelper.hashCode(getIP()) ^ - getPort() ^ - _cost; + getPort(); } /** diff --git a/router/java/src/net/i2p/router/CommSystemFacade.java b/router/java/src/net/i2p/router/CommSystemFacade.java index 82e73c2e10..1386b87277 100644 --- a/router/java/src/net/i2p/router/CommSystemFacade.java +++ b/router/java/src/net/i2p/router/CommSystemFacade.java @@ -28,8 +28,8 @@ public abstract class CommSystemFacade implements Service { public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException { } public void renderStatusHTML(Writer out) throws IOException { renderStatusHTML(out, null, 0); } - /** Create the set of RouterAddress structures based on the router's config */ - public Set createAddresses() { return Collections.EMPTY_SET; } + /** Create the list of RouterAddress structures based on the router's config */ + public List createAddresses() { return Collections.EMPTY_LIST; } public int countActivePeers() { return 0; } public int countActiveSendPeers() { return 0; } diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 82987ae8e6..d2006750ba 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -10,15 +10,14 @@ package net.i2p.router.transport; import java.io.IOException; import java.io.Writer; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; -import java.util.Set; import java.util.Vector; import net.i2p.data.Hash; @@ -167,7 +166,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade { } @Override - public List getMostRecentErrorMessages() { + public List getMostRecentErrorMessages() { return _manager.getMostRecentErrorMessages(); } @@ -190,26 +189,29 @@ public class CommSystemFacadeImpl extends CommSystemFacade { /** @return non-null, possibly empty */ @Override - public Set createAddresses() { + public List createAddresses() { // No, don't do this, it makes it almost impossible to build inbound tunnels //if (_context.router().isHidden()) // return Collections.EMPTY_SET; - Map addresses = _manager.getAddresses(); + List addresses = new ArrayList(_manager.getAddresses()); + + Transport ntcp = _manager.getTransport(NTCPTransport.STYLE); + boolean hasNTCP = ntcp != null && ntcp.hasCurrentAddress(); + boolean newCreated = false; - - if (!addresses.containsKey(NTCPTransport.STYLE)) { + if (!hasNTCP) { RouterAddress addr = createNTCPAddress(_context); if (_log.shouldLog(Log.INFO)) _log.info("NTCP address: " + addr); if (addr != null) { - addresses.put(NTCPTransport.STYLE, addr); + addresses.add(addr); newCreated = true; } } if (_log.shouldLog(Log.INFO)) _log.info("Creating addresses: " + addresses + " isNew? " + newCreated, new Exception("creator")); - return new HashSet(addresses.values()); + return addresses; } public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname"; @@ -278,9 +280,20 @@ public class CommSystemFacadeImpl extends CommSystemFacade { NTCPTransport t = (NTCPTransport) _manager.getTransport(NTCPTransport.STYLE); if (t == null) return; - RouterAddress oldAddr = t.getCurrentAddress(); + + //////// FIXME just take first IPv4 address for now + List oldAddrs = t.getCurrentAddresses(); + RouterAddress oldAddr = null; + for (RouterAddress ra : oldAddrs) { + byte[] ipx = ra.getIP(); + if (ipx != null && ipx.length == 4) { + oldAddr = ra; + break; + } + } if (_log.shouldLog(Log.INFO)) _log.info("Changing NTCP Address? was " + oldAddr); + RouterAddress newAddr = new RouterAddress(); newAddr.setTransportStyle(NTCPTransport.STYLE); Properties newProps = new Properties(); diff --git a/router/java/src/net/i2p/router/transport/Transport.java b/router/java/src/net/i2p/router/transport/Transport.java index 4bece170db..61f0241461 100644 --- a/router/java/src/net/i2p/router/transport/Transport.java +++ b/router/java/src/net/i2p/router/transport/Transport.java @@ -32,10 +32,29 @@ public interface Transport { * */ public void send(OutNetMessage msg); - public RouterAddress startListening(); + public void startListening(); public void stopListening(); - public RouterAddress getCurrentAddress(); - public RouterAddress updateAddress(); + + /** + * What addresses are we currently listening to? + * Replaces getCurrentAddress() + * @return all addresses, non-null + * @since IPv6 + */ + public List getCurrentAddresses(); + + /** + * Do we have any current address? + * @since IPv6 + */ + public boolean hasCurrentAddress(); + + /** + * Ask the transport to update its addresses based on current information and return them + * @return all addresses, non-null + */ + public List updateAddress(); + public static final String SOURCE_UPNP = "upnp"; public static final String SOURCE_INTERFACE = "local"; public static final String SOURCE_CONFIG = "config"; // unused @@ -51,7 +70,7 @@ public interface Transport { public boolean haveCapacity(); public boolean haveCapacity(int pct); public Vector getClockSkews(); - public List getMostRecentErrorMessages(); + public List getMostRecentErrorMessages(); public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException; public short getReachabilityStatus(); diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index a8d79389bf..52d1f20168 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -10,7 +10,6 @@ package net.i2p.router.transport; import java.io.IOException; import java.io.Writer; -import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -23,6 +22,7 @@ import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CopyOnWriteArrayList; import net.i2p.data.DataHelper; import net.i2p.data.Hash; @@ -50,7 +50,7 @@ import net.i2p.util.SimpleTimer; public abstract class TransportImpl implements Transport { private final Log _log; private TransportEventListener _listener; - private RouterAddress _currentAddress; + protected final List _currentAddresses; // Only used by NTCP. SSU does not use. See send() below. private final BlockingQueue _sendPool; protected final RouterContext _context; @@ -87,6 +87,8 @@ public abstract class TransportImpl implements Transport { _context.statManager().createRequiredRateStat("transport.sendProcessingTime", "Time to process and send a message (ms)", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); //_context.statManager().createRateStat("transport.sendProcessingTime." + getStyle(), "Time to process and send a message (ms)", "Transport", new long[] { 60*1000l }); _context.statManager().createRateStat("transport.expiredOnQueueLifetime", "How long a message that expires on our outbound queue is processed", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l } ); + + _currentAddresses = new CopyOnWriteArrayList(); if (getStyle().equals("NTCP")) _sendPool = new ArrayBlockingQueue(8); else @@ -166,7 +168,7 @@ public abstract class TransportImpl implements Transport { */ public Vector getClockSkews() { return new Vector(); } - public List getMostRecentErrorMessages() { return Collections.EMPTY_LIST; } + public List getMostRecentErrorMessages() { return Collections.EMPTY_LIST; } /** * Nonblocking call to pull the next outbound message @@ -464,27 +466,61 @@ public abstract class TransportImpl implements Transport { /** Do we increase the advertised cost when approaching conn limits? */ public static final boolean ADJUST_COST = true; - /** What addresses are we currently listening to? */ - public RouterAddress getCurrentAddress() { - return _currentAddress; + /** + * What addresses are we currently listening to? + * Replaces getCurrentAddress() + * @return all addresses, non-null + * @since IPv6 + */ + public List getCurrentAddresses() { + return _currentAddresses; + } + + /** + * Do we have any current address? + * @since IPv6 + */ + public boolean hasCurrentAddress() { + return !_currentAddresses.isEmpty(); } /** * Ask the transport to update its address based on current information and return it * Transports should override. + * @return all addresses, non-null * @since 0.7.12 */ - public RouterAddress updateAddress() { - return _currentAddress; + public List updateAddress() { + return _currentAddresses; } /** - * Replace any existing addresses for the current transport with the given - * one. + * Replace any existing addresses for the current transport + * with the same IP length (4 or 16) with the given one. + * TODO: Allow multiple addresses of the same length. + * Calls listener.transportAddressChanged() + * + * @param address null to remove all */ protected void replaceAddress(RouterAddress address) { - // _log.error("Replacing address for " + getStyle() + " was " + _currentAddress + " now " + address); - _currentAddress = address; + if (_log.shouldLog(Log.WARN)) + _log.warn("Replacing address with " + address); + if (address == null) { + _currentAddresses.clear(); + } else { + byte[] ip = address.getIP(); + if (ip == null) { + _log.error("WTF null ip for " + address); + return; + } + int len = ip.length; + for (RouterAddress ra : _currentAddresses) { + byte[] ipx = ra.getIP(); + if (ipx != null && ipx.length == len) + _currentAddresses.remove(ra); + } + _currentAddresses.add(address); + } if (_listener != null) _listener.transportAddressChanged(); } diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index acd9bf0cd6..3c1942ce55 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -14,6 +14,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -248,7 +249,7 @@ public class TransportManager implements TransportEventListener { */ public boolean haveInboundCapacity(int pct) { for (Transport t : _transports.values()) { - if (t.getCurrentAddress() != null && t.haveCapacity(pct)) + if (t.hasCurrentAddress() && t.haveCapacity(pct)) return true; } return false; @@ -332,43 +333,67 @@ public class TransportManager implements TransportEventListener { /** * This forces a rebuild */ - public Map getAddresses() { - Map rv = new HashMap(_transports.size()); + public List getAddresses() { + List rv = new ArrayList(4); // do this first since SSU may force a NTCP change for (Transport t : _transports.values()) t.updateAddress(); for (Transport t : _transports.values()) { - if (t.getCurrentAddress() != null) - rv.put(t.getStyle(), t.getCurrentAddress()); + rv.addAll(t.getCurrentAddresses()); } return rv; } + /** + * @since IPv6 + */ + static class Port { + public final String style; + public final int port; + + public Port(String style, int port) { + this.style = style; + this.port = port; + } + + @Override + public int hashCode() { + return style.hashCode() ^ port; + } + + @Override + public boolean equals(Object o) { + if (o == null) + return false; + if (! (o instanceof Port)) + return false; + Port p = (Port) o; + return port == p.port && style.equals(p.style); + } + } + /** * Include the published port, or the requested port, for each transport * which we will pass along to UPnP */ - private Map getPorts() { - Map rv = new HashMap(_transports.size()); + private Set getPorts() { + Set rv = new HashSet(4); for (Transport t : _transports.values()) { int port = t.getRequestedPort(); - if (t.getCurrentAddress() != null) { - String s = t.getCurrentAddress().getOption("port"); - if (s != null) { - try { - port = Integer.parseInt(s); - } catch (NumberFormatException nfe) {} - } + for (RouterAddress ra : t.getCurrentAddresses()) { + int p = ra.getPort(); + if (p > 0) + port = p; + // Use UDP port for NTCP too - see comment in NTCPTransport.getRequestedPort() for why this is here + if (t.getStyle().equals(NTCPTransport.STYLE) && port <= 0 && + _context.getBooleanProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_AUTO_PORT)) { + Transport udp = getTransport(UDPTransport.STYLE); + if (udp != null) + port = t.getRequestedPort(); + } + if (port > 0) + rv.add(new Port(t.getStyle(), port)); } - // Use UDP port for NTCP too - see comment in NTCPTransport.getRequestedPort() for why this is here - if (t.getStyle().equals(NTCPTransport.STYLE) && port <= 0 && - _context.getBooleanProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_AUTO_PORT)) { - Transport udp = getTransport(UDPTransport.STYLE); - if (udp != null) - port = t.getRequestedPort(); - } - if (port > 0) - rv.put(t.getStyle(), Integer.valueOf(port)); } return rv; } @@ -485,8 +510,8 @@ public class TransportManager implements TransportEventListener { _upnpManager.update(getPorts()); } - public List getMostRecentErrorMessages() { - List rv = new ArrayList(16); + public List getMostRecentErrorMessages() { + List rv = new ArrayList(16); for (Transport t : _transports.values()) { rv.addAll(t.getMostRecentErrorMessages()); } @@ -510,11 +535,15 @@ public class TransportManager implements TransportEventListener { StringBuilder buf = new StringBuilder(4*1024); buf.append("

").append(_("Router Transport Addresses")).append("

\n");
         for (Transport t : _transports.values()) {
-            if (t.getCurrentAddress() != null)
-                buf.append(t.getCurrentAddress());
-            else
+            if (t.hasCurrentAddress()) {
+                for (RouterAddress ra : t.getCurrentAddresses()) {
+                    buf.append(ra.toString());
+                    buf.append("\n\n");
+                }
+            } else {
                 buf.append(_("{0} is used for outbound connections only", t.getStyle()));
-            buf.append("\n\n");
+                buf.append("\n\n");
+            }
         }
         buf.append("
\n"); out.write(buf.toString()); diff --git a/router/java/src/net/i2p/router/transport/TransportUtil.java b/router/java/src/net/i2p/router/transport/TransportUtil.java new file mode 100644 index 0000000000..f581b19443 --- /dev/null +++ b/router/java/src/net/i2p/router/transport/TransportUtil.java @@ -0,0 +1,78 @@ +package net.i2p.router.transport; +/* + * free (adj.): unencumbered; not under the control of others + * Written by jrandom in 2003 and released into the public domain + * with no warranty of any kind { either expressed or implied. + * It probably won't make your computer catch on fire { or eat + * your children { but it might. Use at your own risk. + * + */ + +import java.util.HashMap; +import java.util.Map; + +import net.i2p.router.RouterContext; + +/** + * @since IPv6 + */ +public abstract class TransportUtil { + + public static final String NTCP_IPV6_CONFIG = "i2np.ntcp.ipv6"; + public static final String SSU_IPV6_CONFIG = "i2np.udp.ipv6"; + + public enum IPv6Config { + /** IPv6 disabled */ + IPV6_DISABLED("false"), + + /** lower priority than IPv4 */ + IPV6_NOT_PREFERRED("preferIPv4"), + + /** equal priority to IPv4 */ + IPV6_ENABLED("enable"), + + /** higher priority than IPv4 */ + IPV6_PREFERRED("preferIPv6"), + + /** IPv4 disabled */ + IPV6_ONLY("only"); + + private final String cfgstr; + + IPv6Config(String cfgstr) { + this.cfgstr = cfgstr; + } + + public String toConfigString() { + return cfgstr; + } + } + + private static final Map BY_NAME = new HashMap(); + + static { + for (IPv6Config cfg : IPv6Config.values()) { + BY_NAME.put(cfg.toConfigString(), cfg); + } + } + + public static IPv6Config getIPv6Config(RouterContext ctx, String transportStyle) { + String cfg; + if (transportStyle.equals("NTCP")) + cfg = ctx.getProperty(NTCP_IPV6_CONFIG); + else if (transportStyle.equals("SSU")) + cfg = ctx.getProperty(SSU_IPV6_CONFIG); + else + return IPv6Config.IPV6_DISABLED; + return getIPv6Config(cfg); + } + + public static IPv6Config getIPv6Config(String cfg) { + if (cfg == null) + return IPv6Config.IPV6_DISABLED; + IPv6Config c = BY_NAME.get(cfg); + if (c != null) + return c; + return IPv6Config.IPV6_DISABLED; + } +} diff --git a/router/java/src/net/i2p/router/transport/UPnP.java b/router/java/src/net/i2p/router/transport/UPnP.java index abcd8214e7..cee870c96e 100644 --- a/router/java/src/net/i2p/router/transport/UPnP.java +++ b/router/java/src/net/i2p/router/transport/UPnP.java @@ -52,6 +52,7 @@ import org.freenetproject.ForwardPortStatus; * * @see "http://www.upnp.org/" * @see "http://en.wikipedia.org/wiki/Universal_Plug_and_Play" + * @since 0.7.4 */ /* diff --git a/router/java/src/net/i2p/router/transport/UPnPManager.java b/router/java/src/net/i2p/router/transport/UPnPManager.java index 63acc65c09..fb368b37b8 100644 --- a/router/java/src/net/i2p/router/transport/UPnPManager.java +++ b/router/java/src/net/i2p/router/transport/UPnPManager.java @@ -25,6 +25,7 @@ import org.freenetproject.ForwardPortStatus; * Bridge from the I2P RouterAddress data structure to * the freenet data structures * + * @since 0.7.4 * @author zzz */ class UPnPManager { @@ -106,7 +107,7 @@ class UPnPManager { * which can have multiple UPnP threads running at once, but * that should be ok. */ - public void update(Map ports) { + public void update(Set ports) { if (_log.shouldLog(Log.DEBUG)) _log.debug("UPnP Update with " + ports.size() + " ports"); @@ -121,9 +122,9 @@ class UPnPManager { //} Set forwards = new HashSet(ports.size()); - for (Map.Entry entry : ports.entrySet()) { - String style = entry.getKey(); - int port = entry.getValue().intValue(); + for (TransportManager.Port entry : ports) { + String style = entry.style; + int port = entry.port; int protocol = -1; if ("SSU".equals(style)) protocol = ForwardPort.PROTOCOL_UDP_IPV4; diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index 66cd37e819..56f8797000 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -458,30 +458,28 @@ public class NTCPTransport extends TransportImpl { * verify stopped with isAlive() * Unfortunately TransportManager doesn't do that, so we * check here to prevent two pumpers. - * @return appears to be ignored by caller */ - public synchronized RouterAddress startListening() { + public synchronized void startListening() { // try once again to prevent two pumpers which is fatal if (_pumper.isAlive()) - return _myAddress != null ? _myAddress.toRouterAddress() : null; + return; if (_log.shouldLog(Log.WARN)) _log.warn("Starting ntcp transport listening"); startIt(); configureLocalAddress(); - return bindAddress(); + bindAddress(); } /** * Only called by CSFI. * Caller should stop the transport first, then * verify stopped with isAlive() - * @return appears to be ignored by caller */ - public synchronized RouterAddress restartListening(RouterAddress addr) { + public synchronized void restartListening(RouterAddress addr) { // try once again to prevent two pumpers which is fatal // we could just return null since the return value is ignored if (_pumper.isAlive()) - return _myAddress != null ? _myAddress.toRouterAddress() : null; + return; if (_log.shouldLog(Log.WARN)) _log.warn("Restarting ntcp transport listening"); startIt(); @@ -489,7 +487,7 @@ public class NTCPTransport extends TransportImpl { _myAddress = null; else _myAddress = new NTCPAddress(addr); - return bindAddress(); + bindAddress(); } /** diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 567ce43ece..ba69faf994 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -1446,9 +1446,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // we don't need the following, since we have our own queueing protected void outboundMessageReady() { throw new UnsupportedOperationException("Not used for UDP"); } - public RouterAddress startListening() { + public void startListening() { startup(); - return _externalAddress; } public void stopListening() { @@ -1470,9 +1469,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * @since 0.7.12 */ @Override - public RouterAddress updateAddress() { + public List updateAddress() { rebuildExternalAddress(false); - return getCurrentAddress(); + return getCurrentAddresses(); } private void rebuildExternalAddress() { rebuildExternalAddress(true); } From b71631d2ec9b27df24d1931e08ae23579d6d69e2 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 May 2013 14:12:53 +0000 Subject: [PATCH 02/38] Fixes to isPubliclyRoutable() based on IPv6 config --- .../net/i2p/router/web/ConfigNetHandler.java | 5 +-- .../src/net/i2p/router/web/SummaryHelper.java | 5 +-- .../src/net/i2p/router/RouterVersion.java | 2 +- .../i2p/router/transport/TransportImpl.java | 34 +++++++----------- .../i2p/router/transport/TransportUtil.java | 36 +++++++++++++++++++ .../src/net/i2p/router/transport/UPnP.java | 2 +- .../net/i2p/router/transport/UPnPManager.java | 2 +- .../router/transport/ntcp/NTCPAddress.java | 10 ------ 8 files changed, 58 insertions(+), 38 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java index 67c47fbb7f..827cf47f61 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java @@ -7,7 +7,7 @@ import java.util.Map; import net.i2p.router.Router; import net.i2p.router.transport.FIFOBandwidthRefiller; -import net.i2p.router.transport.TransportImpl; +import net.i2p.router.transport.TransportUtil; import net.i2p.router.transport.TransportManager; import net.i2p.router.transport.udp.UDPTransport; import net.i2p.router.web.ConfigServiceHandler; @@ -370,7 +370,8 @@ public class ConfigNetHandler extends FormHandler { addFormError(_("Invalid address") + ": " + addr); return false; } - boolean rv = TransportImpl.isPubliclyRoutable(iab); + // TODO set IPv6 arg based on configuration? + boolean rv = TransportUtil.isPubliclyRoutable(iab, true); if (!rv) addFormError(_("The hostname or IP {0} is not publicly routable", addr)); return rv; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java index bff1abdd54..7d75790aed 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java @@ -24,7 +24,7 @@ import net.i2p.router.RouterContext; import net.i2p.router.RouterVersion; import net.i2p.router.TunnelPoolSettings; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; -import net.i2p.router.transport.ntcp.NTCPAddress; +import net.i2p.router.transport.TransportUtil; import net.i2p.stat.Rate; import net.i2p.stat.RateStat; import net.i2p.util.PortMapper; @@ -159,7 +159,8 @@ public class SummaryHelper extends HelperBase { switch (status) { case CommSystemFacade.STATUS_OK: RouterAddress ra = routerInfo.getTargetAddress("NTCP"); - if (ra == null || (new NTCPAddress(ra)).isPubliclyRoutable()) + // TODO set IPv6 arg based on configuration? + if (ra == null || TransportUtil.isPubliclyRoutable(ra.getIP(), true)) return _("OK"); return _("ERR-Private TCP Address"); case CommSystemFacade.STATUS_DIFFERENT: diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index f5c9264549..b15b320051 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -21,7 +21,7 @@ public class RouterVersion { public final static long BUILD = 20; /** for example "-test" */ - public final static String EXTRA = ""; + public final static String EXTRA = "-ipv6"; public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA; public static void main(String args[]) { System.out.println("I2P Router version: " + FULL_VERSION); diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 52d1f20168..81a498bced 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -668,26 +668,18 @@ public abstract class TransportImpl implements Transport { } } - /** @param addr non-null */ - public static boolean isPubliclyRoutable(byte addr[]) { - if (addr.length == 4) { - int a0 = addr[0] & 0xFF; - if (a0 == 127) return false; - if (a0 == 10) return false; - int a1 = addr[1] & 0xFF; - if (a0 == 172 && a1 >= 16 && a1 <= 31) return false; - if (a0 == 192 && a1 == 168) return false; - if (a0 >= 224) return false; // no multicast - if (a0 == 0) return false; - if (a0 == 169 && a1 == 254) return false; - // 5/8 allocated to RIPE (30 November 2010) - //if ((addr[0]&0xFF) == 5) return false; // Hamachi - return true; // or at least possible to be true - } else if (addr.length == 16) { - return false; - } else { - // ipv? - return false; - } + /** + * @since IPv6 + */ + protected TransportUtil.IPv6Config getIPv6Config() { + return TransportUtil.getIPv6Config(_context, getStyle()); + } + + /** + * @param addr non-null + */ + protected boolean isPubliclyRoutable(byte addr[]) { + return TransportUtil.isPubliclyRoutable(addr, + getIPv6Config() != TransportUtil.IPv6Config.IPV6_DISABLED); } } diff --git a/router/java/src/net/i2p/router/transport/TransportUtil.java b/router/java/src/net/i2p/router/transport/TransportUtil.java index f581b19443..cc708d1142 100644 --- a/router/java/src/net/i2p/router/transport/TransportUtil.java +++ b/router/java/src/net/i2p/router/transport/TransportUtil.java @@ -8,6 +8,8 @@ package net.i2p.router.transport; * */ +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; @@ -75,4 +77,38 @@ public abstract class TransportUtil { return c; return IPv6Config.IPV6_DISABLED; } + + /** + * @param addr non-null + * @since IPv6 moved from TransportImpl + */ + public static boolean isPubliclyRoutable(byte addr[], boolean allowIPv6) { + if (addr.length == 4) { + int a0 = addr[0] & 0xFF; + if (a0 == 127) return false; + if (a0 == 10) return false; + int a1 = addr[1] & 0xFF; + if (a0 == 172 && a1 >= 16 && a1 <= 31) return false; + if (a0 == 192 && a1 == 168) return false; + if (a0 >= 224) return false; // no multicast + if (a0 == 0) return false; + if (a0 == 169 && a1 == 254) return false; + // 5/8 allocated to RIPE (30 November 2010) + //if ((addr[0]&0xFF) == 5) return false; // Hamachi + return true; // or at least possible to be true + } else if (addr.length == 16) { + if (allowIPv6) { + try { + InetAddress ia = InetAddress.getByAddress(addr); + return + (!ia.isLinkLocalAddress()) && + (!ia.isMulticastAddress()) && + (!ia.isAnyLocalAddress()) && + (!ia.isLoopbackAddress()) && + (!ia.isSiteLocalAddress()); + } catch (UnknownHostException uhe) {} + } + } + return false; + } } diff --git a/router/java/src/net/i2p/router/transport/UPnP.java b/router/java/src/net/i2p/router/transport/UPnP.java index cee870c96e..e8d9dd9a74 100644 --- a/router/java/src/net/i2p/router/transport/UPnP.java +++ b/router/java/src/net/i2p/router/transport/UPnP.java @@ -146,7 +146,7 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener { InetAddress detectedIP = InetAddress.getByName(natAddress); short status = DetectedIP.NOT_SUPPORTED; - thinksWeAreDoubleNatted = !TransportImpl.isPubliclyRoutable(detectedIP.getAddress()); + thinksWeAreDoubleNatted = !TransportUtil.isPubliclyRoutable(detectedIP.getAddress(), false); // If we have forwarded a port AND we don't have a private address if (_log.shouldLog(Log.WARN)) _log.warn("NATAddress: \"" + natAddress + "\" detectedIP: " + detectedIP + " double? " + thinksWeAreDoubleNatted); diff --git a/router/java/src/net/i2p/router/transport/UPnPManager.java b/router/java/src/net/i2p/router/transport/UPnPManager.java index fb368b37b8..10355f64b5 100644 --- a/router/java/src/net/i2p/router/transport/UPnPManager.java +++ b/router/java/src/net/i2p/router/transport/UPnPManager.java @@ -156,7 +156,7 @@ class UPnPManager { if (ips != null) { for (DetectedIP ip : ips) { // store the first public one and tell the transport manager if it changed - if (TransportImpl.isPubliclyRoutable(ip.publicAddress.getAddress())) { + if (TransportUtil.isPubliclyRoutable(ip.publicAddress.getAddress(), false)) { if (_log.shouldLog(Log.DEBUG)) _log.debug("External address: " + ip.publicAddress + " type: " + ip.natType); if (!ip.publicAddress.equals(_detectedAddress)) { diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java index b37d23cc11..43d051af47 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java @@ -88,16 +88,6 @@ public class NTCPAddress { public int getPort() { return _port; } //public void setPort(int port) { _port = port; } - public boolean isPubliclyRoutable() { - return isPubliclyRoutable(_host); - } - - public static boolean isPubliclyRoutable(String host) { - if (host == null) return false; - byte quad[] = Addresses.getIP(host); - return TransportImpl.isPubliclyRoutable(quad); - } - @Override public String toString() { return _host + ":" + _port; } From eecab472eb9dac1f3c36e5ed81ab7500ee5b9e01 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 May 2013 11:27:21 +0000 Subject: [PATCH 03/38] IPv6 SSU MTU adjustments --- .../router/transport/udp/PacketBuilder.java | 20 ++++++-- .../i2p/router/transport/udp/PeerState.java | 48 +++++++++++++------ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index 965db51ed6..2e5f356cae 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -137,6 +137,10 @@ class PacketBuilder { /** 74 */ public static final int MIN_DATA_PACKET_OVERHEAD = IP_HEADER_SIZE + UDP_HEADER_SIZE + DATA_HEADER_SIZE; + public static final int IPV6_HEADER_SIZE = 40; + /** 94 */ + public static final int MIN_IPV6_DATA_PACKET_OVERHEAD = IPV6_HEADER_SIZE + UDP_HEADER_SIZE + DATA_HEADER_SIZE; + /** one byte field */ public static final int ABSOLUTE_MAX_ACKS = 255; @@ -237,7 +241,15 @@ class PacketBuilder { } int currentMTU = peer.getMTU(); - int availableForAcks = currentMTU - MIN_DATA_PACKET_OVERHEAD - dataSize; + int availableForAcks = currentMTU - dataSize; + int ipHeaderSize; + if (peer.getRemoteIP().length == 4) { + availableForAcks -= MIN_DATA_PACKET_OVERHEAD; + ipHeaderSize = IP_HEADER_SIZE; + } else { + availableForAcks -= MIN_IPV6_DATA_PACKET_OVERHEAD; + ipHeaderSize = IPV6_HEADER_SIZE; + } int availableForExplicitAcks = availableForAcks; // ok, now for the body... @@ -398,16 +410,16 @@ class PacketBuilder { setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort()); if (_log.shouldLog(Log.INFO)) { - msg.append(" pkt size ").append(off + (IP_HEADER_SIZE + UDP_HEADER_SIZE)); + msg.append(" pkt size ").append(off + (ipHeaderSize + UDP_HEADER_SIZE)); _log.info(msg.toString()); } // the packet could have been built before the current mtu got lowered, so // compare to LARGE_MTU - if (off + (IP_HEADER_SIZE + UDP_HEADER_SIZE) > PeerState.LARGE_MTU) { + if (off + (ipHeaderSize + UDP_HEADER_SIZE) > PeerState.LARGE_MTU) { _log.error("Size is " + off + " for " + packet + " fragment " + fragment + " data size " + dataSize + - " pkt size " + (off + (IP_HEADER_SIZE + UDP_HEADER_SIZE)) + + " pkt size " + (off + (ipHeaderSize + UDP_HEADER_SIZE)) + " MTU " + currentMTU + ' ' + availableForAcks + " for all acks " + availableForExplicitAcks + " for full acks " + diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java index 31b2dd7bda..e04f3b5312 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java @@ -267,6 +267,7 @@ class PeerState { * and so PacketBuilder.buildPacket() works correctly. */ public static final int MIN_MTU = 620; + public static final int MIN_IPV6_MTU = 1280; private static final int DEFAULT_MTU = MIN_MTU; /* @@ -315,8 +316,13 @@ class PeerState { _receivePeriodBegin = now; _lastCongestionOccurred = -1; _remotePort = remotePort; - _mtu = DEFAULT_MTU; - _mtuReceive = DEFAULT_MTU; + if (remoteIP.length == 4) { + _mtu = DEFAULT_MTU; + _mtuReceive = DEFAULT_MTU; + } else { + _mtu = MIN_IPV6_MTU; + _mtuReceive = MIN_IPV6_MTU; + } _largeMTU = transport.getMTU(); //_mtuLastChecked = -1; _lastACKSend = -1; @@ -1145,12 +1151,12 @@ class PeerState { _context.statManager().addRateData("udp.mtuIncrease", _mtuIncreases); } } else if (!wantLarge && _mtu == _largeMTU) { - _mtu = MIN_MTU; + _mtu = _remoteIP.length == 4 ? MIN_MTU : MIN_IPV6_MTU; _mtuDecreases++; _context.statManager().addRateData("udp.mtuDecrease", _mtuDecreases); } } else { - _mtu = DEFAULT_MTU; + _mtu = _remoteIP.length == 4 ? DEFAULT_MTU : MIN_IPV6_MTU; } } @@ -1158,7 +1164,8 @@ class PeerState { * @since 0.9.2 */ public synchronized void setHisMTU(int mtu) { - if (mtu <= MIN_MTU || mtu >= _largeMTU) + if (mtu <= MIN_MTU || mtu >= _largeMTU || + (_remoteIP.length == 16 && mtu <= MIN_IPV6_MTU)) return; _largeMTU = mtu; if (mtu < _mtu) @@ -1225,17 +1232,27 @@ class PeerState { /** 60 */ private static final int OVERHEAD_SIZE = PacketBuilder.IP_HEADER_SIZE + PacketBuilder.UDP_HEADER_SIZE + UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; + /** 80 */ + private static final int IPV6_OVERHEAD_SIZE = PacketBuilder.IPV6_HEADER_SIZE + PacketBuilder.UDP_HEADER_SIZE + + UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; /** * @param size not including IP header, UDP header, MAC or IV */ public void packetReceived(int size) { _packetsReceived++; - size += OVERHEAD_SIZE; - if (size <= MIN_MTU) { + int minMTU; + if (_remoteIP.length == 4) { + size += OVERHEAD_SIZE; + minMTU = MIN_MTU; + } else { + size += IPV6_OVERHEAD_SIZE; + minMTU = MIN_IPV6_MTU; + } + if (size <= minMTU) { _consecutiveSmall++; if (_consecutiveSmall >= MTU_RCV_DISPLAY_THRESHOLD) - _mtuReceive = MIN_MTU; + _mtuReceive = minMTU; } else { _consecutiveSmall = 0; if (size > _mtuReceive) @@ -1289,7 +1306,7 @@ class PeerState { private int countMaxACKData() { return Math.min(PacketBuilder.ABSOLUTE_MAX_ACKS * 4, _mtu - - PacketBuilder.IP_HEADER_SIZE + - (_remoteIP.length == 4 ? PacketBuilder.IP_HEADER_SIZE : PacketBuilder.IPV6_HEADER_SIZE) - PacketBuilder.UDP_HEADER_SIZE - UDPPacket.IV_SIZE - UDPPacket.MAC_SIZE @@ -1630,11 +1647,14 @@ class PeerState { /** * how much payload data can we shove in there? - * @return MTU - 87, i.e. 533 or 1397 + * @return MTU - 87, i.e. 533 or 1397 (IPv4), MTU - 107 (IPv6) */ - private static final int fragmentSize(int mtu) { - // 46 + 20 + 8 + 13 = 74 + 13 = 87 - return mtu - (PacketBuilder.MIN_DATA_PACKET_OVERHEAD + MIN_ACK_SIZE); + private int fragmentSize() { + // 46 + 20 + 8 + 13 = 74 + 13 = 87 (IPv4) + // 46 + 40 + 8 + 13 = 74 + 13 = 107 (IPv6) + return _mtu - + (_remoteIP.length == 4 ? PacketBuilder.MIN_DATA_PACKET_OVERHEAD : PacketBuilder.MIN_IPV6_DATA_PACKET_OVERHEAD) - + MIN_ACK_SIZE; } private enum ShouldSend { YES, NO, NO_BW }; @@ -1647,7 +1667,7 @@ class PeerState { long now = _context.clock().now(); if (state.getNextSendTime() <= now) { if (!state.isFragmented()) { - state.fragment(fragmentSize(_mtu)); + state.fragment(fragmentSize()); if (state.getMessage() != null) state.getMessage().timestamp("fragment into " + state.getFragmentCount()); From c6121cb31edb4b2c211e46f4060d950715f9df07 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 May 2013 15:03:55 +0000 Subject: [PATCH 04/38] Prep for multiple SSU sockets: - Change from single UDPEndpoint to a List of UDPEndpoints - Move (single) receive queue from UDPReceiver to PacketHandler - Multiple transmit queues (one for each UDPEndpoint/UDPSender), select queue in PacketPusher - Throw exception on UDPEndpoint.startup() failure --- .../router/transport/udp/PacketHandler.java | 67 +++++++++++-- .../router/transport/udp/PacketPusher.java | 51 ++++++++-- .../i2p/router/transport/udp/UDPEndpoint.java | 39 +++++--- .../i2p/router/transport/udp/UDPReceiver.java | 51 ++-------- .../i2p/router/transport/udp/UDPSender.java | 3 + .../router/transport/udp/UDPTransport.java | 95 ++++++++++++------- 6 files changed, 204 insertions(+), 102 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java index 96ceec9c0d..e901ac8595 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java @@ -3,9 +3,11 @@ package net.i2p.router.transport.udp; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.concurrent.BlockingQueue; import net.i2p.router.Router; import net.i2p.router.RouterContext; +import net.i2p.router.util.CoDelBlockingQueue; import net.i2p.data.DataHelper; import net.i2p.util.I2PThread; import net.i2p.util.LHMCache; @@ -26,7 +28,6 @@ class PacketHandler { private final RouterContext _context; private final Log _log; private final UDPTransport _transport; - private final UDPEndpoint _endpoint; private final EstablishmentManager _establisher; private final InboundMessageFragments _inbound; private final PeerTestManager _testManager; @@ -34,19 +35,22 @@ class PacketHandler { private volatile boolean _keepReading; private final Handler[] _handlers; private final Map _failCache; + private final BlockingQueue _inboundQueue; private static final Object DUMMY = new Object(); + private static final int TYPE_POISON = -99999; + private static final int MIN_QUEUE_SIZE = 16; + private static final int MAX_QUEUE_SIZE = 192; private static final int MIN_NUM_HANDLERS = 1; // unless < 32MB private static final int MAX_NUM_HANDLERS = 1; /** let packets be up to 30s slow */ private static final long GRACE_PERIOD = Router.CLOCK_FUDGE_FACTOR + 30*1000; - PacketHandler(RouterContext ctx, UDPTransport transport, UDPEndpoint endpoint, EstablishmentManager establisher, + PacketHandler(RouterContext ctx, UDPTransport transport, EstablishmentManager establisher, InboundMessageFragments inbound, PeerTestManager testManager, IntroductionManager introManager) { _context = ctx; _log = ctx.logManager().getLog(PacketHandler.class); _transport = transport; - _endpoint = endpoint; _establisher = establisher; _inbound = inbound; _testManager = testManager; @@ -56,6 +60,8 @@ class PacketHandler { long maxMemory = Runtime.getRuntime().maxMemory(); if (maxMemory == Long.MAX_VALUE) maxMemory = 96*1024*1024l; + int qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (2*1024*1024))); + _inboundQueue = new CoDelBlockingQueue(ctx, "UDP-Receiver", qsize); int num_handlers; if (maxMemory < 32*1024*1024) num_handlers = 1; @@ -107,6 +113,7 @@ class PacketHandler { public synchronized void shutdown() { _keepReading = false; + stopQueue(); } String getHandlerStatus() { @@ -119,9 +126,55 @@ class PacketHandler { return rv.toString(); } - /** @since 0.8.8 */ - int getHandlerCount() { - return _handlers.length; + /** + * Blocking call to retrieve the next inbound packet, or null if we have + * shut down. + * + * @since IPv6 moved from UDPReceiver + */ + public void queueReceived(UDPPacket packet) throws InterruptedException { + _inboundQueue.put(packet); + } + + + /** + * Blocking for a while + * + * @since IPv6 moved from UDPReceiver + */ + private void stopQueue() { + _inboundQueue.clear(); + for (int i = 0; i < _handlers.length; i++) { + UDPPacket poison = UDPPacket.acquire(_context, false); + poison.setMessageType(TYPE_POISON); + _inboundQueue.offer(poison); + } + for (int i = 1; i <= 5 && !_inboundQueue.isEmpty(); i++) { + try { + Thread.sleep(i * 50); + } catch (InterruptedException ie) {} + } + _inboundQueue.clear(); + } + + /** + * Blocking call to retrieve the next inbound packet, or null if we have + * shut down. + * + * @since IPv6 moved from UDPReceiver + */ + public UDPPacket receiveNext() { + UDPPacket rv = null; + //int remaining = 0; + while (_keepReading && rv == null) { + try { + rv = _inboundQueue.take(); + } catch (InterruptedException ie) {} + if (rv != null && rv.getMessageType() == TYPE_POISON) + return null; + } + //_context.statManager().addRateData("udp.receiveRemaining", remaining, 0); + return rv; } /** the packet is from a peer we are establishing an outbound con to, but failed validation, so fallback */ @@ -144,7 +197,7 @@ class PacketHandler { _state = 1; while (_keepReading) { _state = 2; - UDPPacket packet = _endpoint.receive(); + UDPPacket packet = receiveNext(); _state = 3; if (packet == null) break; // keepReading is probably false, or bind failed... diff --git a/router/java/src/net/i2p/router/transport/udp/PacketPusher.java b/router/java/src/net/i2p/router/transport/udp/PacketPusher.java index cf7ce21928..f40e05e081 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketPusher.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketPusher.java @@ -1,26 +1,29 @@ package net.i2p.router.transport.udp; +import java.util.List; + import net.i2p.router.RouterContext; import net.i2p.util.I2PThread; import net.i2p.util.Log; /** * Blocking thread to grab new packets off the outbound fragment - * pool and toss 'em onto the outbound packet queue + * pool and toss 'em onto the outbound packet queues. * + * Here we select which UDPEndpoint/UDPSender to send it out. */ class PacketPusher implements Runnable { // private RouterContext _context; private final Log _log; private final OutboundMessageFragments _fragments; - private final UDPSender _sender; + private final List _endpoints; private volatile boolean _alive; - public PacketPusher(RouterContext ctx, OutboundMessageFragments fragments, UDPSender sender) { + public PacketPusher(RouterContext ctx, OutboundMessageFragments fragments, List endpoints) { // _context = ctx; _log = ctx.logManager().getLog(PacketPusher.class); _fragments = fragments; - _sender = sender; + _endpoints = endpoints; } public synchronized void startup() { @@ -38,8 +41,7 @@ class PacketPusher implements Runnable { if (packets != null) { for (int i = 0; i < packets.length; i++) { if (packets[i] != null) // null for ACKed fragments - // BLOCKING if queue is full - _sender.add(packets[i]); + send(packets[i]); } } } catch (Exception e) { @@ -47,4 +49,41 @@ class PacketPusher implements Runnable { } } } + + /** + * This sends it directly out, bypassing OutboundMessageFragments + * and the PacketPusher. The only queueing is for the bandwidth limiter. + * BLOCKING if OB queue is full. + * + * @param packet non-null + * @since IPv6 + */ + public void send(UDPPacket packet) { + boolean handled = false; + boolean isIPv4 = packet.getPacket().getAddress().getAddress().length == 4; + for (int j = 0; j < _endpoints.size(); j++) { + // Find the best endpoint (socket) to send this out. + // TODO if we have multiple IPv4, or multiple IPv6 endpoints, + // we have to track which one we're using in the PeerState and + // somehow set that in the UDPPacket so we're consistent + UDPEndpoint ep; + try { + ep = _endpoints.get(j); + } catch (IndexOutOfBoundsException ioobe) { + // whups, list changed + break; + } + if ((isIPv4 && ep.isIPv4()) || + ((!isIPv4) && ep.isIPv6())) { + // BLOCKING if queue is full + ep.getSender().add(packet); + handled = true; + break; + } + } + if (!handled) { + _log.error("No endpoint to send " + packet); + packet.release(); + } + } } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java index 179649de52..2822fe8fbb 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java @@ -2,14 +2,16 @@ package net.i2p.router.transport.udp; import java.net.DatagramSocket; import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.SocketException; import net.i2p.router.RouterContext; import net.i2p.util.Log; /** - * Coordinate the low level datagram socket, managing the UDPSender and - * UDPReceiver + * Coordinate the low-level datagram socket, creating and managing the UDPSender and + * UDPReceiver. */ class UDPEndpoint { private final RouterContext _context; @@ -20,6 +22,7 @@ class UDPEndpoint { private UDPReceiver _receiver; private DatagramSocket _socket; private final InetAddress _bindAddress; + private final boolean _isIPv4, _isIPv6; /** * @param listenPort -1 or the requested port, may not be honored @@ -31,17 +34,19 @@ class UDPEndpoint { _transport = transport; _bindAddress = bindAddress; _listenPort = listenPort; + _isIPv4 = bindAddress == null || bindAddress instanceof Inet4Address; + _isIPv6 = bindAddress == null || bindAddress instanceof Inet6Address; } /** caller should call getListenPort() after this to get the actual bound port and determine success */ - public synchronized void startup() { + public synchronized void startup() throws SocketException { if (_log.shouldLog(Log.DEBUG)) _log.debug("Starting up the UDP endpoint"); shutdown(); _socket = getSocket(); if (_socket == null) { _log.log(Log.CRIT, "UDP Unable to open a port"); - return; + throw new SocketException("SSU Unable to bind to a port on " + _bindAddress); } _sender = new UDPSender(_context, _socket, "UDPSender"); _receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver"); @@ -144,16 +149,6 @@ class UDPEndpoint { _sender.add(packet); } - /** - * Blocking call to receive the next inbound UDP packet from any peer. - * @return null if we have shut down - */ - public UDPPacket receive() { - if (_receiver == null) - return null; - return _receiver.receiveNext(); - } - /** * Clear outbound queue, probably in preparation for sending destroy() to everybody. * @since 0.9.2 @@ -162,4 +157,20 @@ class UDPEndpoint { if (_sender != null) _sender.clear(); } + + /** + * @return true for wildcard too + * @since IPv6 + */ + public boolean isIPv4() { + return _isIPv4; + } + + /** + * @return true for wildcard too + * @since IPv6 + */ + public boolean isIPv6() { + return _isIPv6; + } } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java index b7b06bb8d0..1807af3088 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java @@ -3,11 +3,9 @@ package net.i2p.router.transport.udp; import java.io.IOException; import java.net.DatagramSocket; import java.util.Arrays; -import java.util.concurrent.BlockingQueue; import net.i2p.router.RouterContext; import net.i2p.router.transport.FIFOBandwidthLimiter; -import net.i2p.router.util.CoDelBlockingQueue; import net.i2p.util.I2PThread; import net.i2p.util.Log; import net.i2p.util.SimpleTimer; @@ -20,37 +18,34 @@ import net.i2p.util.SystemVersion; * waiting around too long, they are dropped. Packets should be pulled off * from the queue ASAP by a {@link PacketHandler} * + * There is a UDPReceiver for each UDPEndpoint. + * It contains a thread but no queue. Received packets are queued + * in the common PacketHandler queue. */ class UDPReceiver { private final RouterContext _context; private final Log _log; private final DatagramSocket _socket; private String _name; - private final BlockingQueue _inboundQueue; private volatile boolean _keepRunning; private final Runner _runner; private final UDPTransport _transport; + private final PacketHandler _handler; private static int __id; private final int _id; private static final boolean _isAndroid = SystemVersion.isAndroid(); - private static final int TYPE_POISON = -99999; - private static final int MIN_QUEUE_SIZE = 16; - private static final int MAX_QUEUE_SIZE = 192; - public UDPReceiver(RouterContext ctx, UDPTransport transport, DatagramSocket socket, String name) { _context = ctx; _log = ctx.logManager().getLog(UDPReceiver.class); _id = ++__id; _name = name; - long maxMemory = Runtime.getRuntime().maxMemory(); - if (maxMemory == Long.MAX_VALUE) - maxMemory = 96*1024*1024l; - int qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (2*1024*1024))); - _inboundQueue = new CoDelBlockingQueue(ctx, "UDP-Receiver", qsize); _socket = socket; _transport = transport; + _handler = transport.getPacketHandler(); + if (_handler == null) + throw new IllegalStateException(); _runner = new Runner(); //_context.statManager().createRateStat("udp.receivePacketSize", "How large packets received are", "udp", UDPTransport.RATES); //_context.statManager().createRateStat("udp.receiveRemaining", "How many packets are left sitting on the receiver's queue", "udp", UDPTransport.RATES); @@ -68,18 +63,6 @@ class UDPReceiver { public synchronized void shutdown() { _keepRunning = false; - _inboundQueue.clear(); - for (int i = 0; i < _transport.getPacketHandlerCount(); i++) { - UDPPacket poison = UDPPacket.acquire(_context, false); - poison.setMessageType(TYPE_POISON); - _inboundQueue.offer(poison); - } - for (int i = 1; i <= 5 && !_inboundQueue.isEmpty(); i++) { - try { - Thread.sleep(i * 50); - } catch (InterruptedException ie) {} - } - _inboundQueue.clear(); } /********* @@ -194,7 +177,7 @@ class UDPReceiver { if (!rejected) { ****/ try { - _inboundQueue.put(packet); + _handler.queueReceived(packet); } catch (InterruptedException ie) { packet.release(); _keepRunning = false; @@ -229,24 +212,6 @@ class UDPReceiver { } ****/ - /** - * Blocking call to retrieve the next inbound packet, or null if we have - * shut down. - * - */ - public UDPPacket receiveNext() { - UDPPacket rv = null; - //int remaining = 0; - while (_keepRunning && rv == null) { - try { - rv = _inboundQueue.take(); - } catch (InterruptedException ie) {} - if (rv != null && rv.getMessageType() == TYPE_POISON) - return null; - } - //_context.statManager().addRateData("udp.receiveRemaining", remaining, 0); - return rv; - } private class Runner implements Runnable { //private volatile boolean _socketChanged; diff --git a/router/java/src/net/i2p/router/transport/udp/UDPSender.java b/router/java/src/net/i2p/router/transport/udp/UDPSender.java index 885cc7299d..923dcba617 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPSender.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPSender.java @@ -14,6 +14,9 @@ import net.i2p.util.Log; /** * Lowest level packet sender, pushes anything on its queue ASAP. * + * There is a UDPSender for each UDPEndpoint. + * It contains a thread and a queue. Packet to be sent are queued + * by the PacketPusher. */ class UDPSender { private final RouterContext _context; diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index ba69faf994..47c822025c 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -3,6 +3,7 @@ package net.i2p.router.transport.udp; import java.io.IOException; import java.io.Writer; import java.net.InetAddress; +import java.net.SocketException; import java.net.UnknownHostException; import java.text.DecimalFormat; import java.util.ArrayList; @@ -18,6 +19,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import net.i2p.data.DatabaseEntry; import net.i2p.data.DataHelper; @@ -51,7 +53,7 @@ import net.i2p.util.Translate; */ public class UDPTransport extends TransportImpl implements TimedWeightedPriorityMessageQueue.FailedListener { private final Log _log; - private UDPEndpoint _endpoint; + private final List _endpoints; private final Object _addDropLock = new Object(); /** Peer (Hash) to PeerState */ private final Map _peersByIdent; @@ -206,6 +208,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _peersByIdent = new ConcurrentHashMap(128); _peersByRemoteHost = new ConcurrentHashMap(128); _dropList = new ConcurrentHashSet(2); + _endpoints = new CopyOnWriteArrayList(); // See comments in DummyThrottle.java if (USE_PRIORITY) { @@ -263,8 +266,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _pusher.shutdown(); if (_handler != null) _handler.shutdown(); - if (_endpoint != null) - _endpoint.shutdown(); + for (UDPEndpoint endpoint : _endpoints) { + endpoint.shutdown(); + // should we remove? + _endpoints.remove(endpoint); + } if (_establisher != null) _establisher.shutdown(); if (_refiller != null) @@ -327,14 +333,24 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _log.warn("Binding only to " + bindToAddr); if (_log.shouldLog(Log.INFO)) _log.info("Binding to the port: " + port); - if (_endpoint == null) { - _endpoint = new UDPEndpoint(_context, this, port, bindToAddr); + if (_endpoints.isEmpty()) { + // will always be empty since we are removing them above + UDPEndpoint endpoint = new UDPEndpoint(_context, this, port, bindToAddr); + _endpoints.add(endpoint); + // TODO add additional endpoints for additional addresses/ports } else { - // todo, set bind address too - _endpoint.setListenPort(port); + // unused for now + for (UDPEndpoint endpoint : _endpoints) { + if (endpoint.isIPv4()) { + // hack, first IPv4 endpoint, FIXME + // todo, set bind address too + endpoint.setListenPort(port); + break; + } + } } setMTU(bindToAddr); - + if (_establisher == null) _establisher = new EstablishmentManager(_context, this); @@ -342,7 +358,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _testManager = new PeerTestManager(_context, this); if (_handler == null) - _handler = new PacketHandler(_context, this, _endpoint, _establisher, _inboundFragments, _testManager, _introManager); + _handler = new PacketHandler(_context, this, _establisher, _inboundFragments, _testManager, _introManager); // See comments in DummyThrottle.java if (USE_PRIORITY && _refiller == null) @@ -353,15 +369,26 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // Startup the endpoint with the requested port, check the actual port, and // take action if it failed or was different than requested or it needs to be saved - _endpoint.startup(); - int newPort = _endpoint.getListenPort(); - _externalListenPort = newPort; - if (newPort <= 0) { + int newPort = -1; + for (UDPEndpoint endpoint : _endpoints) { + try { + endpoint.startup(); + // hack, first IPv4 endpoint, FIXME + if (newPort < 0 && endpoint.isIPv4()) { + newPort = endpoint.getListenPort(); + _externalListenPort = newPort; + } + } catch (SocketException se) { + _endpoints.remove(endpoint); + } + } + if (_endpoints.isEmpty()) { _log.log(Log.CRIT, "Unable to open UDP port"); setReachabilityStatus(CommSystemFacade.STATUS_HOSED); return; } - if (newPort != port || newPort != oldIPort || newPort != oldEPort) { + if (newPort > 0 && + (newPort != port || newPort != oldIPort || newPort != oldEPort)) { // attempt to use it as our external port - this will be overridden by // externalAddressReceived(...) Map changes = new HashMap(); @@ -374,7 +401,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _handler.startup(); _fragments.startup(); _inboundFragments.startup(); - _pusher = new PacketPusher(_context, _fragments, _endpoint.getSender()); + _pusher = new PacketPusher(_context, _fragments, _endpoints); _pusher.startup(); if (USE_PRIORITY) _refiller.startup(); @@ -387,8 +414,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority public synchronized void shutdown() { destroyAll(); - if (_endpoint != null) - _endpoint.shutdown(); + for (UDPEndpoint endpoint : _endpoints) { + endpoint.shutdown(); + // should we remove? + _endpoints.remove(endpoint); + } //if (_flooder != null) // _flooder.shutdown(); if (_refiller != null) @@ -416,11 +446,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority */ SessionKey getIntroKey() { return _introKey; } - /** @deprecated unused */ - public int getLocalPort() { - return _endpoint != null ? _endpoint.getListenPort() : -1; - } - public InetAddress getLocalAddress() { return _externalListenHost; } public int getExternalPort() { return _externalListenPort; } @@ -1205,7 +1230,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority void send(UDPPacket packet) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Sending packet " + packet); - _endpoint.send(packet); + _pusher.send(packet); } /** @@ -1231,7 +1256,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * @since 0.8.9 */ private void destroyAll() { - _endpoint.clearOutbound(); + for (UDPEndpoint endpoint : _endpoints) { + endpoint.clearOutbound(); + } int howMany = _peersByIdent.size(); // use no more than 1/4 of configured bandwidth final int burst = 8; @@ -1662,13 +1689,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return ""; } - /** @since 0.8.8 */ - int getPacketHandlerCount() { - PacketHandler handler = _handler; - if (handler != null) - return handler.getHandlerCount(); - else - return 0; + /** @since IPv6 */ + PacketHandler getPacketHandler() { + return _handler; } public void failed(OutboundMessageState msg) { failed(msg, true); } @@ -2521,7 +2544,15 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority long pingCutoff = now - (2 * 60*60*1000); long pingFirewallCutoff = now - PING_FIREWALL_CUTOFF; boolean shouldPingFirewall = _reachabilityStatus != CommSystemFacade.STATUS_OK; - boolean pingOneOnly = shouldPingFirewall && _externalListenPort == _endpoint.getListenPort(); + int currentListenPort = -1; + for (UDPEndpoint endpoint : _endpoints) { + // hack, first IPv4 endpoint, FIXME + if (endpoint.isIPv4()) { + currentListenPort = endpoint.getListenPort(); + break; + } + } + boolean pingOneOnly = shouldPingFirewall && _externalListenPort == currentListenPort; boolean shortLoop = shouldPingFirewall; _expireBuffer.clear(); _runCount++; From 757df8c726a9f9af6db7e8e85e2962dbf0f9a7d6 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 May 2013 16:34:02 +0000 Subject: [PATCH 05/38] prep for multiple address discovery --- .../i2p/router/transport/TransportImpl.java | 11 ++++++--- .../router/transport/TransportManager.java | 24 ++++++++++--------- .../router/transport/udp/UDPTransport.java | 21 ++++++++++++++-- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 81a498bced..eafaad165e 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -529,22 +529,27 @@ public abstract class TransportImpl implements Transport { * Notify a transport of an external address change. * This may be from a local interface, UPnP, a config change, etc. * This should not be called if the ip didn't change - * (from that source's point of view), or is a local address, - * or if the ip is IPv6, but the transport should check anyway. + * (from that source's point of view), or is a local address. + * May be called multiple times for IPv4 or IPv6. * The transport should also do its own checking on whether to accept * notifications from this source. * * This can be called before startListening() to set an initial address, * or after the transport is running. * + * This implementation does nothing. Transports should override if they want notification. + * * @param source defined in Transport.java - * @param ip typ. IPv4 non-local + * @param ip typ. IPv4 or IPv6 non-local * @param port 0 for unknown or unchanged */ public void externalAddressReceived(String source, byte[] ip, int port) {} /** * Notify a transport of the results of trying to forward a port. + * + * This implementation does nothing. Transports should override if they want notification. + * * @param port the internal port * @param externalPort the external port, which for now should always be the same as * the internal port if the forwarding was successful. diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index 3c1942ce55..958c0f5508 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -101,19 +101,21 @@ public class TransportManager implements TransportEventListener { return ctx.getBooleanPropertyDefaultTrue(PROP_ENABLE_NTCP); } + /** + * Notify transport of ALL routable local addresses, including IPv6. + * It's the transport's job to ignore what it can't handle. + */ private void initializeAddress(Transport t) { - String ips = Addresses.getAnyAddress(); - if (ips == null) - return; - InetAddress ia; - try { - ia = InetAddress.getByName(ips); - } catch (UnknownHostException e) { - _log.error("UDP failed to bind to local address", e); - return; + Set ipset = Addresses.getAddresses(false, true); // non-local, include IPv6 + for (String ips : ipset) { + try { + InetAddress ia = InetAddress.getByName(ips); + byte[] ip = ia.getAddress(); + t.externalAddressReceived(Transport.SOURCE_INTERFACE, ip, 0); + } catch (UnknownHostException e) { + _log.error("UDP failed to bind to local address", e); + } } - byte[] ip = ia.getAddress(); - t.externalAddressReceived(Transport.SOURCE_INTERFACE, ip, 0); } /** diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 47c822025c..84d3dec852 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -511,11 +511,15 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _lastInboundReceivedOn = System.currentTimeMillis(); } + // temp prevent multiples + private boolean gotIPv4Addr = false; + private boolean gotIPv6Addr = false; + /** * From config, UPnP, local i/f, ... * * @param source used for logging only - * @param ip publicly routable IPv4 only + * @param ip publicly routable IPv4 or IPv6 * @param port 0 if unknown */ @Override @@ -527,10 +531,22 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority String sources = _context.getProperty(PROP_SOURCES, DEFAULT_SOURCES); if (!sources.contains(source)) return; + if (!isValid(ip)) + return; + if (source.equals(Transport.SOURCE_INTERFACE)) { + // temp prevent multiples + if (ip.length == 4 && !gotIPv4Addr) { + gotIPv4Addr = true; + return; + } else if (ip.length == 16 && !gotIPv6Addr) { + gotIPv6Addr = true; + return; + } + } boolean changed = changeAddress(ip, port); // Assume if we have an interface with a public IP that we aren't firewalled. // If this is wrong, the peer test will figure it out and change the status. - if (changed && source.equals(Transport.SOURCE_INTERFACE)) + if (changed && ip.length == 4 && source.equals(Transport.SOURCE_INTERFACE)) setReachabilityStatus(CommSystemFacade.STATUS_OK); } @@ -620,6 +636,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } /** + * @param ourIP MUST have been previously validated with isValid() * @param ourPort >= 1024 or 0 for no change */ private boolean changeAddress(byte ourIP[], int ourPort) { From 368c2073b225df5903d2a6991c1f2dcf426e51d9 Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 5 May 2013 12:08:28 +0000 Subject: [PATCH 06/38] - Fix multiple-detection code in externalAddressReceived() - Synchronize tracking of last IP/port - Don't accept IPv6 address changes from peers - Remove unused getLocalAddress() - Pkg private getLocalPort() Peer tests: - Use only IPv4 peer for Alice and Bob in peer tests; Charlie may be an IPv6 peer. - Enforce IPv4 (Alice's) address inside PeerTest packet --- .../transport/udp/EstablishmentManager.java | 2 +- .../router/transport/udp/PeerTestManager.java | 23 +++- .../router/transport/udp/UDPTransport.java | 105 +++++++++++++----- 3 files changed, 97 insertions(+), 33 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index 4987314a36..ab0699c7f4 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -940,7 +940,7 @@ class EstablishmentManager { } /** - * Are IP and port valid? + * Are IP and port valid? This is only for relay response. * Refuse anybody in the same /16 * @since 0.9.3 */ diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java index 2adecf5217..5cc72dfcf6 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java @@ -24,6 +24,13 @@ import net.i2p.util.SimpleTimer; * Entry points are runTest() to start a new test as Alice, * and receiveTest() for all received test packets. * + * IPv6 info: All Alice-Bob and Alice-Charlie communication is via IPv4. + * The Bob-Charlie communication may be via IPv6, however Charlie must + * be IPv4-capable. + * The IP address (of Alice) in the message must be IPv4 if present, + * as we only support testing of IPv4. + * Testing of IPv6 could be added in the future. + * * From udp.html on the website:

The automation of collaborative reachability testing for peers is @@ -166,6 +173,8 @@ class PeerTestManager { /** * The next few methods are for when we are Alice + * + * @param bobIP IPv4 only */ public synchronized void runTest(InetAddress bobIP, int bobPort, SessionKey bobCipherKey, SessionKey bobMACKey) { if (_currentTest != null) { @@ -304,7 +313,7 @@ class PeerTestManager { // The reply is from Bob int ipSize = testInfo.readIPSize(); - if (ipSize != 4 && ipSize != 16) { + if (ipSize != 4) { // There appears to be a bug where Bob is sending us a zero-length IP. // We could proceed without setting the IP, but then when Charlie // sends us his message, we will think we are behind a symmetric NAT @@ -366,6 +375,8 @@ class PeerTestManager { throw new UnknownHostException("port 0"); test.setAlicePortFromCharlie(testPort); byte ip[] = new byte[testInfo.readIPSize()]; + if (ip.length != 4) + throw new UnknownHostException("not IPv4"); testInfo.readIP(ip, 0); InetAddress addr = InetAddress.getByAddress(ip); test.setAliceIPFromCharlie(addr); @@ -505,6 +516,7 @@ class PeerTestManager { if ((testPort > 0 && (testPort < 1024 || testPort > 65535)) || (testIP != null && ((!_transport.isValid(testIP)) || + testIP.length != 4 || _context.blocklist().isBlocklisted(testIP)))) { // spoof check, and don't respond to privileged ports if (_log.shouldLog(Log.WARN)) @@ -641,6 +653,8 @@ class PeerTestManager { byte aliceIPData[] = new byte[sz]; try { testInfo.readIP(aliceIPData, 0); + if (sz != 4) + throw new UnknownHostException("not IPv4"); int alicePort = testInfo.readPort(); if (alicePort == 0) throw new UnknownHostException("port 0"); @@ -706,7 +720,12 @@ class PeerTestManager { PeerState charlie; RouterInfo charlieInfo = null; if (state == null) { // pick a new charlie - charlie = _transport.pickTestPeer(from); + if (from.getIP().length != 4) { + if (_log.shouldLog(Log.WARN)) + _log.warn("PeerTest over IPv6 from Alice as Bob? " + from); + return; + } + charlie = _transport.pickTestPeer(CHARLIE, from); } else { charlie = _transport.getPeerState(new RemoteHostId(state.getCharlieIP().getAddress(), state.getCharliePort())); } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 84d3dec852..6f6b563f96 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -38,6 +38,7 @@ import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.router.transport.Transport; import net.i2p.router.transport.TransportBid; import net.i2p.router.transport.TransportImpl; +import static net.i2p.router.transport.udp.PeerTestState.Role.*; import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.router.util.RandomIterator; import net.i2p.util.Addresses; @@ -446,8 +447,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority */ SessionKey getIntroKey() { return _introKey; } - public InetAddress getLocalAddress() { return _externalListenHost; } - public int getExternalPort() { return _externalListenPort; } + int getExternalPort() { return _externalListenPort; } /** * @return IP or null @@ -517,8 +517,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority /** * From config, UPnP, local i/f, ... + * Not for info received from peers - see externalAddressReceived(Hash, ip, port) * - * @param source used for logging only + * @param source as defined in Transport.SOURCE_xxx * @param ip publicly routable IPv4 or IPv6 * @param port 0 if unknown */ @@ -535,12 +536,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return; if (source.equals(Transport.SOURCE_INTERFACE)) { // temp prevent multiples - if (ip.length == 4 && !gotIPv4Addr) { - gotIPv4Addr = true; - return; - } else if (ip.length == 16 && !gotIPv6Addr) { - gotIPv6Addr = true; - return; + if (ip.length == 4) { + if (gotIPv4Addr) + return; + else + gotIPv4Addr = true; + } else if (ip.length == 16) { + if (gotIPv6Addr) + return; + else + gotIPv6Addr = true; } } boolean changed = changeAddress(ip, port); @@ -585,6 +590,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * @param ourPort >= 1024 */ void externalAddressReceived(Hash from, byte ourIP[], int ourPort) { + if (ourIP.length != 4) + return; boolean isValid = isValid(ourIP) && ((ourPort >= MIN_EXTERNAL_PORT && ourPort <= MAX_EXTERNAL_PORT) || ourPort == _externalListenPort || _externalListenPort <= 0); @@ -616,27 +623,36 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // leaving us thinking the second IP is still good. if (_log.shouldLog(Log.INFO)) _log.info("Ignoring IP address suggestion, since we have received an inbound con recently"); - } else if (from.equals(_lastFrom) || !eq(_lastOurIP, _lastOurPort, ourIP, ourPort)) { - _lastFrom = from; - _lastOurIP = ourIP; - _lastOurPort = ourPort; - if (_log.shouldLog(Log.INFO)) - _log.info("The router " + from + " told us we have a new IP - " - + Addresses.toString(ourIP, ourPort) + ". Wait until somebody else tells us the same thing."); } else { - if (_log.shouldLog(Log.INFO)) - _log.info(from + " and " + _lastFrom + " agree we have a new IP - " - + Addresses.toString(ourIP, ourPort) + ". Changing address."); - _lastFrom = from; - _lastOurIP = ourIP; - _lastOurPort = ourPort; - changeAddress(ourIP, ourPort); + // New IP + boolean changeIt = false; + synchronized(this) { + if (from.equals(_lastFrom) || !eq(_lastOurIP, _lastOurPort, ourIP, ourPort)) { + _lastFrom = from; + _lastOurIP = ourIP; + _lastOurPort = ourPort; + if (_log.shouldLog(Log.INFO)) + _log.info("The router " + from + " told us we have a new IP - " + + Addresses.toString(ourIP, ourPort) + ". Wait until somebody else tells us the same thing."); + } else { + _lastFrom = from; + _lastOurIP = ourIP; + _lastOurPort = ourPort; + changeIt = true; + } + } + if (changeIt) { + if (_log.shouldLog(Log.INFO)) + _log.info(from + " and " + _lastFrom + " agree we have a new IP - " + + Addresses.toString(ourIP, ourPort) + ". Changing address."); + changeAddress(ourIP, ourPort); + } } - } /** * @param ourIP MUST have been previously validated with isValid() + * IPv4 or IPv6 OK * @param ourPort >= 1024 or 0 for no change */ private boolean changeAddress(byte ourIP[], int ourPort) { @@ -2702,24 +2718,50 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return _reachabilityStatus; } + @Override public void recheckReachability() { _testEvent.runTest(); } - PeerState pickTestPeer(RemoteHostId dontInclude) { + /** + * Pick a Bob (if we are Alice) or a Charlie (if we are Bob). + * + * For Bob (as called from PeerTestEvent below), returns an established IPv4 peer. + * While the protocol allows Alice to select an unestablished Bob, we don't support that. + * + * For Charlie (as called from PeerTestManager), returns an established IPv4 or IPv6 peer. + * (doesn't matter how Bob and Charlie communicate) + * + * Any returned peer must advertise an IPv4 address to prove it is IPv4-capable. + * + * @param peerRole BOB or CHARLIE only + * @param dontInclude may be null + * @return IPv4 peer or null + */ + PeerState pickTestPeer(PeerTestState.Role peerRole, RemoteHostId dontInclude) { + if (peerRole == ALICE) + throw new IllegalArgumentException(); List peers = new ArrayList(_peersByIdent.values()); for (Iterator iter = new RandomIterator(peers); iter.hasNext(); ) { PeerState peer = iter.next(); if ( (dontInclude != null) && (dontInclude.equals(peer.getRemoteHostId())) ) continue; + // enforce IPv4 connection for BOB + byte[] ip = peer.getRemoteIP(); + if (peerRole == BOB && ip.length != 4) + continue; + // enforce IPv4 advertised for all RouterInfo peerInfo = _context.netDb().lookupRouterInfoLocally(peer.getRemotePeer()); if (peerInfo == null) continue; - RouterAddress addr = peerInfo.getTargetAddress(STYLE); - if (addr == null) - continue; - byte[] ip = addr.getIP(); + ip = null; + List addrs = peerInfo.getTargetAddresses(STYLE); + for (RouterAddress addr : addrs) { + ip = addr.getIP(); + if (ip != null && ip.length == 4) + break; + } if (ip == null) continue; if (DataHelper.eq(ip, 0, getExternalIP(), 0, 2)) @@ -2735,6 +2777,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority //return ( (val != null) && ("true".equals(val)) ); } + /** + * Initiate a test (we are Alice) + */ private class PeerTestEvent extends SimpleTimer2.TimedEvent { private volatile boolean _alive; /** when did we last test our reachability */ @@ -2761,7 +2806,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } private void runTest() { - PeerState bob = pickTestPeer(null); + PeerState bob = pickTestPeer(BOB, null); if (bob != null) { if (_log.shouldLog(Log.INFO)) _log.info("Running periodic test with bob = " + bob); From 228e6d7d0377ce1ade30c9d9847cf3c550071c55 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 6 May 2013 13:30:11 +0000 Subject: [PATCH 07/38] fixup after prop --- .../router/transport/udp/UDPTransport.java | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 5f6afdc3a6..577ff9eed7 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -88,7 +88,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority private RouterAddress _externalAddress; /** * Port number on which we can be reached, or -1 for error, or 0 for unset - * Do NOT use this for current internal port - use _endpoint.getListenPort() + * Do NOT use this for current internal port - use UDPEndpoint.getListenPort() */ private int _externalListenPort; /** IP address of externally reachable host, or null */ @@ -324,7 +324,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // we will check below after starting up the endpoint. int port; int oldIPort = _context.getProperty(PROP_INTERNAL_PORT, -1); - int oldBindPort = _endpoint != null ? _endpoint.getListenPort() : -1; + int oldBindPort = getIPv4ListenPort(); int oldEPort = _context.getProperty(PROP_EXTERNAL_PORT, -1); if (oldIPort > 0) port = oldIPort; @@ -462,6 +462,20 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return ia.getAddress(); } + /** + * The current port of the first IPv4 endpoint. + * To be enhanced to handle multiple IPv4 endpoints. + * @return port or -1 + * @since IPv6 + */ + private int getIPv4ListenPort() { + for (UDPEndpoint endpoint : _endpoints) { + if (endpoint.isIPv4()) + return endpoint.getListenPort(); + } + return -1; + } + /** * The current or configured internal port. * UDPEndpoint should always be instantiated (and a random port picked if not configured) @@ -470,13 +484,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority */ @Override public int getRequestedPort() { - if (_endpoint != null) { - int rv = _endpoint.getListenPort(); - if (rv > 0) - return rv; - } + int rv = getIPv4ListenPort(); + if (rv > 0) + return rv; // fallbacks - int rv = _context.getProperty(PROP_INTERNAL_PORT, -1); + rv = _context.getProperty(PROP_INTERNAL_PORT, -1); if (rv > 0) return rv; return _context.getProperty(PROP_EXTERNAL_PORT, -1); @@ -2588,14 +2600,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority long pingCutoff = now - (2 * 60*60*1000); long pingFirewallCutoff = now - PING_FIREWALL_CUTOFF; boolean shouldPingFirewall = _reachabilityStatus != CommSystemFacade.STATUS_OK; - int currentListenPort = -1; - for (UDPEndpoint endpoint : _endpoints) { - // hack, first IPv4 endpoint, FIXME - if (endpoint.isIPv4()) { - currentListenPort = endpoint.getListenPort(); - break; - } - } + int currentListenPort = getIPv4ListenPort(); boolean pingOneOnly = shouldPingFirewall && _externalListenPort == currentListenPort; boolean shortLoop = shouldPingFirewall; _expireBuffer.clear(); From a85b7aa9f82e95e23d39d3d31d29a014fa797a77 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 7 May 2013 18:22:20 +0000 Subject: [PATCH 08/38] - Hash IPv6 addresses in IPThrottler - CSFI: Pass TransportManager instead of CSFI to GetBidsJob; remove unused methods - Add i2np.disable property for testing --- .../transport/CommSystemFacadeImpl.java | 33 +++++++++++-------- .../net/i2p/router/transport/GetBidsJob.java | 19 ++++++----- .../i2p/router/transport/TransportImpl.java | 3 ++ .../i2p/router/transport/udp/IPThrottler.java | 13 +++++--- .../i2p/router/transport/udp/UDPReceiver.java | 5 ++- .../i2p/router/transport/udp/UDPSender.java | 6 ++++ 6 files changed, 52 insertions(+), 27 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 0aa29de28e..3b69e9b090 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -44,6 +44,12 @@ public class CommSystemFacadeImpl extends CommSystemFacade { private final GeoIP _geoIP; private volatile boolean _netMonitorStatus; private boolean _wasStarted; + + /** + * Disable connections for testing + * @since IPv6 + */ + private static final String PROP_DISABLED = "i2np.disable"; public CommSystemFacadeImpl(RouterContext context) { _context = context; @@ -124,23 +130,17 @@ public class CommSystemFacadeImpl extends CommSystemFacade { return sum * 1000 / frameSize; } - public List getBids(OutNetMessage msg) { - return _manager.getBids(msg); - } - public TransportBid getBid(OutNetMessage msg) { - return _manager.getBid(msg); - } - public TransportBid getNextBid(OutNetMessage msg) { - return _manager.getNextBid(msg); - } - int getTransportCount() { return _manager.getTransportCount(); } - /** Send the message out */ public void processMessage(OutNetMessage msg) { + if (isDummy()) { + // testing + GetBidsJob.fail(_context, msg); + return; + } //GetBidsJob j = new GetBidsJob(_context, this, msg); //j.runJob(); //long before = _context.clock().now(); - GetBidsJob.getBids(_context, this, msg); + GetBidsJob.getBids(_context, _manager, msg); // < 0.4 ms //_context.statManager().addRateData("transport.getBidsJobTime", _context.clock().now() - before); } @@ -569,9 +569,14 @@ public class CommSystemFacadeImpl extends CommSystemFacade { return buf.toString(); } - /** @since 0.8.13 */ + /** + * Is everything disabled for testing? + * @since 0.8.13 + */ @Override - public boolean isDummy() { return false; } + public boolean isDummy() { + return _context.getBooleanProperty(PROP_DISABLED); + } /** * Translate diff --git a/router/java/src/net/i2p/router/transport/GetBidsJob.java b/router/java/src/net/i2p/router/transport/GetBidsJob.java index 85da18c13f..676f72e163 100644 --- a/router/java/src/net/i2p/router/transport/GetBidsJob.java +++ b/router/java/src/net/i2p/router/transport/GetBidsJob.java @@ -23,22 +23,25 @@ import net.i2p.util.Log; */ class GetBidsJob extends JobImpl { private final Log _log; - private final CommSystemFacadeImpl _facade; + private final TransportManager _tmgr; private final OutNetMessage _msg; - public GetBidsJob(RouterContext ctx, CommSystemFacadeImpl facade, OutNetMessage msg) { + /** + * @deprecated unused, see static getBids() + */ + public GetBidsJob(RouterContext ctx, TransportManager tmgr, OutNetMessage msg) { super(ctx); _log = ctx.logManager().getLog(GetBidsJob.class); - _facade = facade; + _tmgr = tmgr; _msg = msg; } public String getName() { return "Fetch bids for a message to be delivered"; } public void runJob() { - getBids(getContext(), _facade, _msg); + getBids(getContext(), _tmgr, _msg); } - static void getBids(RouterContext context, CommSystemFacadeImpl facade, OutNetMessage msg) { + static void getBids(RouterContext context, TransportManager tmgr, OutNetMessage msg) { Log log = context.logManager().getLog(GetBidsJob.class); Hash to = msg.getTarget().getIdentity().getHash(); msg.timestamp("bid"); @@ -61,14 +64,14 @@ class GetBidsJob extends JobImpl { return; } - TransportBid bid = facade.getNextBid(msg); + TransportBid bid = tmgr.getNextBid(msg); if (bid == null) { int failedCount = msg.getFailedTransports().size(); if (failedCount == 0) { context.statManager().addRateData("transport.bidFailNoTransports", msg.getLifetime()); // This used to be "no common transports" but it is almost always no transports at all context.banlist().banlistRouter(to, _x("No transports (hidden or starting up?)")); - } else if (failedCount >= facade.getTransportCount()) { + } else if (failedCount >= tmgr.getTransportCount()) { context.statManager().addRateData("transport.bidFailAllTransports", msg.getLifetime()); // fail after all transports were unsuccessful context.netDb().fail(to); @@ -82,7 +85,7 @@ class GetBidsJob extends JobImpl { } - private static void fail(RouterContext context, OutNetMessage msg) { + static void fail(RouterContext context, OutNetMessage msg) { if (msg.getOnFailedSendJob() != null) { context.jobQueue().addJob(msg.getOnFailedSendJob()); } diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index eafaad165e..f56a04da50 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -123,6 +123,9 @@ public abstract class TransportImpl implements Transport { /** Per-transport connection limit */ public int getMaxConnections() { + if (_context.commSystem().isDummy()) + // testing + return 0; String style = getStyle(); // object churn String maxProp; diff --git a/router/java/src/net/i2p/router/transport/udp/IPThrottler.java b/router/java/src/net/i2p/router/transport/udp/IPThrottler.java index 5f94620621..1308c2e707 100644 --- a/router/java/src/net/i2p/router/transport/udp/IPThrottler.java +++ b/router/java/src/net/i2p/router/transport/udp/IPThrottler.java @@ -3,6 +3,7 @@ package net.i2p.router.transport.udp; import net.i2p.util.ObjectCounter; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; +import net.i2p.util.SipHash; /** * Count IPs @@ -21,12 +22,16 @@ class IPThrottler { /** * Increments before checking - * @return true if ip.length != 4 */ public boolean shouldThrottle(byte[] ip) { - if (ip.length != 4) - return true; - return _counter.increment(toInt(ip)) > _max; + // for IPv4 we simply use the IP; + // for IPv6 we use a secure hash as an attacker could select the lower bytes + Integer key; + if (ip.length == 4) + key = toInt(ip); + else + key = Integer.valueOf(SipHash.hashCode(ip)); + return _counter.increment(key) > _max; } private static Integer toInt(byte ip[]) { diff --git a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java index 1807af3088..468c780ec7 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java @@ -253,7 +253,10 @@ class UDPReceiver { // DatagramSocket javadocs: If the message is longer than the packet's length, the message is truncated. throw new IOException("packet too large! truncated and dropped from: " + packet.getRemoteHost()); } - if (size > 0) { + if (_context.commSystem().isDummy()) { + // testing + packet.release(); + } else if (size > 0) { //FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestInbound(size, "UDP receiver"); //_context.bandwidthLimiter().requestInbound(req, size, "UDP receiver"); FIFOBandwidthLimiter.Request req = diff --git a/router/java/src/net/i2p/router/transport/udp/UDPSender.java b/router/java/src/net/i2p/router/transport/udp/UDPSender.java index 923dcba617..40d16253c9 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPSender.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPSender.java @@ -179,6 +179,12 @@ class UDPSender { _log.error("Dropping large UDP packet " + psz + " bytes: " + packet); return; } + if (_context.commSystem().isDummy()) { + // testing + // back to the cache + packet.release(); + return; + } try { _outboundQueue.put(packet); } catch (InterruptedException ie) { From 60336c9555a5dd538dfddac09bacffa87d4cac38 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 7 May 2013 19:49:13 +0000 Subject: [PATCH 09/38] - Shuffle SSU addresses before picking one - Change address sources to enum --- .../net/i2p/router/transport/Transport.java | 27 ++++++++++++++++--- .../i2p/router/transport/TransportImpl.java | 2 +- .../router/transport/TransportManager.java | 5 ++-- .../net/i2p/router/transport/UPnPManager.java | 3 ++- .../router/transport/udp/UDPTransport.java | 16 +++++++---- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/Transport.java b/router/java/src/net/i2p/router/transport/Transport.java index 61f0241461..72638f77a7 100644 --- a/router/java/src/net/i2p/router/transport/Transport.java +++ b/router/java/src/net/i2p/router/transport/Transport.java @@ -55,10 +55,29 @@ public interface Transport { */ public List updateAddress(); - public static final String SOURCE_UPNP = "upnp"; - public static final String SOURCE_INTERFACE = "local"; - public static final String SOURCE_CONFIG = "config"; // unused - public void externalAddressReceived(String source, byte[] ip, int port); + /** + * @since IPv6 + */ + public enum AddressSource { + SOURCE_UPNP("upnp"), + SOURCE_INTERFACE("local"), + /** unused */ + SOURCE_CONFIG("config"), + /** unused */ + SOURCE_SSU_PEER("ssu"); + + private final String cfgstr; + + AddressSource(String cfgstr) { + this.cfgstr = cfgstr; + } + + public String toConfigString() { + return cfgstr; + } + } + + public void externalAddressReceived(AddressSource source, byte[] ip, int port); public void forwardPortStatus(int port, int externalPort, boolean success, String reason); public int getRequestedPort(); public void setListener(TransportEventListener listener); diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index f56a04da50..44cf01ef0d 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -546,7 +546,7 @@ public abstract class TransportImpl implements Transport { * @param ip typ. IPv4 or IPv6 non-local * @param port 0 for unknown or unchanged */ - public void externalAddressReceived(String source, byte[] ip, int port) {} + public void externalAddressReceived(AddressSource source, byte[] ip, int port) {} /** * Notify a transport of the results of trying to forward a port. diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index 67ac3d340a..6d9ea36de2 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -30,6 +30,7 @@ import net.i2p.data.i2np.I2NPMessage; import net.i2p.router.CommSystemFacade; import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; +import static net.i2p.router.transport.Transport.AddressSource.SOURCE_INTERFACE; import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.router.transport.ntcp.NTCPTransport; import net.i2p.router.transport.udp.UDPTransport; @@ -111,7 +112,7 @@ public class TransportManager implements TransportEventListener { try { InetAddress ia = InetAddress.getByName(ips); byte[] ip = ia.getAddress(); - t.externalAddressReceived(Transport.SOURCE_INTERFACE, ip, 0); + t.externalAddressReceived(SOURCE_INTERFACE, ip, 0); } catch (UnknownHostException e) { _log.error("UDP failed to bind to local address", e); } @@ -123,7 +124,7 @@ public class TransportManager implements TransportEventListener { * Only tell SSU, it will tell NTCP * */ - public void externalAddressReceived(String source, byte[] ip, int port) { + public void externalAddressReceived(Transport.AddressSource source, byte[] ip, int port) { Transport t = getTransport(UDPTransport.STYLE); if (t != null) t.externalAddressReceived(source, ip, port); diff --git a/router/java/src/net/i2p/router/transport/UPnPManager.java b/router/java/src/net/i2p/router/transport/UPnPManager.java index 10355f64b5..ba4d3df5fc 100644 --- a/router/java/src/net/i2p/router/transport/UPnPManager.java +++ b/router/java/src/net/i2p/router/transport/UPnPManager.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.Set; import net.i2p.router.RouterContext; +import static net.i2p.router.transport.Transport.AddressSource.SOURCE_UPNP; import net.i2p.util.Addresses; import net.i2p.util.Log; import net.i2p.util.Translate; @@ -161,7 +162,7 @@ class UPnPManager { _log.debug("External address: " + ip.publicAddress + " type: " + ip.natType); if (!ip.publicAddress.equals(_detectedAddress)) { _detectedAddress = ip.publicAddress; - _manager.externalAddressReceived(Transport.SOURCE_UPNP, _detectedAddress.getAddress(), 0); + _manager.externalAddressReceived(SOURCE_UPNP, _detectedAddress.getAddress(), 0); } break; } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 577ff9eed7..4d64e1adfe 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -36,6 +36,7 @@ import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.router.transport.Transport; +import static net.i2p.router.transport.Transport.AddressSource.*; import net.i2p.router.transport.TransportBid; import net.i2p.router.transport.TransportImpl; import static net.i2p.router.transport.udp.PeerTestState.Role.*; @@ -155,7 +156,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority /** allowed sources of address updates */ public static final String PROP_SOURCES = "i2np.udp.addressSources"; - public static final String DEFAULT_SOURCES = "local,upnp,ssu"; + public static final String DEFAULT_SOURCES = SOURCE_INTERFACE.toConfigString() + ',' + + SOURCE_UPNP.toConfigString() + ',' + + SOURCE_SSU_PEER.toConfigString(); /** remember IP changes */ public static final String PROP_IP= "i2np.lastIP"; public static final String PROP_IP_CHANGE = "i2np.lastIPChange"; @@ -547,17 +550,17 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * @param port 0 if unknown */ @Override - public void externalAddressReceived(String source, byte[] ip, int port) { + public void externalAddressReceived(Transport.AddressSource source, byte[] ip, int port) { if (_log.shouldLog(Log.WARN)) _log.warn("Received address: " + Addresses.toString(ip, port) + " from: " + source); if (explicitAddressSpecified()) return; String sources = _context.getProperty(PROP_SOURCES, DEFAULT_SOURCES); - if (!sources.contains(source)) + if (!sources.contains(source.toConfigString())) return; if (!isValid(ip)) return; - if (source.equals(Transport.SOURCE_INTERFACE)) { + if (source == SOURCE_INTERFACE) { // temp prevent multiples if (ip.length == 4) { if (gotIPv4Addr) @@ -574,7 +577,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority boolean changed = changeAddress(ip, port); // Assume if we have an interface with a public IP that we aren't firewalled. // If this is wrong, the peer test will figure it out and change the status. - if (changed && ip.length == 4 && source.equals(Transport.SOURCE_INTERFACE)) + if (changed && ip.length == 4 && source == SOURCE_INTERFACE) setReachabilityStatus(CommSystemFacade.STATUS_OK); } @@ -1414,6 +1417,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority */ RouterAddress getTargetAddress(RouterInfo target) { List addrs = target.getTargetAddresses(STYLE); + // Shuffle so everybody doesn't use the first one + if (addrs.size() > 1) + Collections.shuffle(addrs, _context.random()); for (int i = 0; i < addrs.size(); i++) { RouterAddress addr = addrs.get(i); if (addr.getOption("ihost0") == null) { From af27c76b2cb8684f721867ce803bec600fefed5c Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 7 May 2013 22:46:55 +0000 Subject: [PATCH 10/38] * Only treat IPv6 addresses as valid if we have a public IPv6 address * SSU Introduction: - Document that Alice-Bob RelayRequest/RelayResponse may be IPv4 or IPv6, but don't implement IPv6 yet. Changes required in IntroductionManager and PacketBuilder to send Alice's IPv4 address in the RelayRequest packet over IPv6, and to publish IPv6 introducer IPs. - Bob-Charlie RelayIntro must be IPv4 - Only offer/accept relay tags as Bob or Charlie if the Bob-Charlie session is IPv4 - Alice-Charlie communication must be IPv4 - javadocs --- .../i2p/router/transport/TransportImpl.java | 2 + .../router/transport/TransportManager.java | 2 +- .../transport/udp/EstablishmentManager.java | 12 +++- .../transport/udp/IntroductionManager.java | 58 +++++++++++++++++++ .../router/transport/udp/PacketBuilder.java | 10 ++++ .../i2p/router/transport/udp/UDPAddress.java | 5 ++ .../router/transport/udp/UDPPacketReader.java | 3 + .../router/transport/udp/UDPTransport.java | 19 +++++- 8 files changed, 106 insertions(+), 5 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 44cf01ef0d..3105262810 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -684,6 +684,8 @@ public abstract class TransportImpl implements Transport { } /** + * Allows IPv6 only if the transport is configured for it. + * Caller must check if we actually have a public IPv6 address. * @param addr non-null */ protected boolean isPubliclyRoutable(byte addr[]) { diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index 6d9ea36de2..e0fdcc0cfe 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -103,7 +103,7 @@ public class TransportManager implements TransportEventListener { } /** - * Notify transport of ALL routable local addresses, including IPv6. + * Notify transport of ALL routable interface addresses, including IPv6. * It's the transport's job to ignore what it can't handle. */ private void initializeAddress(Transport t) { diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index ab0699c7f4..1b87e24fe7 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -459,8 +459,10 @@ class EstablishmentManager { if (isNew) { // Don't offer to relay to privileged ports. + // Only offer for an IPv4 session. // TODO if already we have their RI, only offer if they need it (no 'C' cap) - if (_transport.canIntroduce() && state.getSentPort() >= 1024) { + if (_transport.canIntroduce() && state.getSentPort() >= 1024 && + state.getSentIP().length == 4) { // ensure > 0 long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE); state.setSentRelayTag(tag); @@ -884,6 +886,9 @@ class EstablishmentManager { state.introSent(); } + /** + * We are Alice, we sent a RelayRequest to Bob and got a response back. + */ void receiveRelayResponse(RemoteHostId bob, UDPPacketReader reader) { long nonce = reader.getRelayResponseReader().readNonce(); OutboundEstablishState state = _liveIntroductions.remove(Long.valueOf(nonce)); @@ -893,6 +898,7 @@ class EstablishmentManager { return; // already established } + // Note that we ignore the Alice (us) IP/Port in the RelayResponse int sz = reader.getRelayResponseReader().readCharlieIPSize(); byte ip[] = new byte[sz]; reader.getRelayResponseReader().readCharlieIP(ip, 0); @@ -940,13 +946,15 @@ class EstablishmentManager { } /** - * Are IP and port valid? This is only for relay response. + * Are IP and port valid? This is only for checking the relay response. + * Reject all IPv6, for now, even if we are configured for it. * Refuse anybody in the same /16 * @since 0.9.3 */ private boolean isValid(byte[] ip, int port) { return port >= 1024 && port <= 65535 && + ip != null && ip.length == 4 && _transport.isValid(ip) && (!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) && (!_context.blocklist().isBlocklisted(ip)); diff --git a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java index 0d49d2b5b1..790a0d349f 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -23,6 +23,52 @@ import net.i2p.util.Log; /** * Keep track of inbound and outbound introductions. + * + * IPv6 info: Alice-Bob communication may be via IPv4 or IPv6. + * Bob-Charlie communication must be via established IPv4 session as that's the only way + * that Bob knows Charlie's IPv4 address to give it to Alice. + * Alice-Charlie communication is via IPv4. + * If Alice-Bob is over IPv6, Alice must include her IPv4 address in + * the RelayRequest message. + * + * From udp.html on the website: + +

Indirect session establishment by means of a third party introduction +is necessary for efficient NAT traversal. Charlie, a router behind a +NAT or firewall which does not allow unsolicited inbound UDP packets, +first contacts a few peers, choosing some to serve as introducers. Each +of these peers (Bob, Bill, Betty, etc) provide Charlie with an introduction +tag - a 4 byte random number - which he then makes available to the public +as methods of contacting him. Alice, a router who has Charlie's published +contact methods, first sends a RelayRequest packet to one or more of the +introducers, asking each to introduce her to Charlie (offering the +introduction tag to identify Charlie). Bob then forwards a RelayIntro +packet to Charlie including Alice's public IP and port number, then sends +Alice back a RelayResponse packet containing Charlie's public IP and port +number. When Charlie receives the RelayIntro packet, he sends off a small +random packet to Alice's IP and port (poking a hole in his NAT/firewall), +and when Alice receives Bob's RelayResponse packet, she begins a new +full direction session establishment with the specified IP and port.

+

+Alice first connects to introducer Bob, who relays the request to Charlie. +

+
+        Alice                         Bob                  Charlie
+    RelayRequest ---------------------->
+         <-------------- RelayResponse    RelayIntro ----------->
+         <-------------------------------------------- HolePunch (data ignored)
+    SessionRequest -------------------------------------------->
+         <-------------------------------------------- SessionCreated
+    SessionConfirmed ------------------------------------------>
+         <-------------------------------------------- DeliveryStatusMessage
+         <-------------------------------------------- DatabaseStoreMessage
+    DatabaseStoreMessage -------------------------------------->
+    Data <--------------------------------------------------> Data
+
+ +

+After the hole punch, the session is established between Alice and Charlie as in a direct establishment. +

*/ class IntroductionManager { private final RouterContext _context; @@ -77,6 +123,9 @@ class IntroductionManager { // let's not use an introducer on a privileged port, sounds like trouble if (peer.getRemotePort() < 1024) return; + // Only allow relay as Bob or Charlie if the Bob-Charlie session is IPv4 + if (peer.getRemoteIP().length != 4) + return; if (_log.shouldLog(Log.DEBUG)) _log.debug("Adding peer " + peer.getRemoteHostId() + ", weRelayToThemAs " + peer.getWeRelayToThemAs() + ", theyRelayToUsAs " + peer.getTheyRelayToUsAs()); @@ -136,6 +185,8 @@ class IntroductionManager { _log.info("Picked peer has no local routerInfo: " + cur); continue; } + // FIXME we can include all his addresses including IPv6 even if we don't support IPv6 (isValid() is false) + // but requires RelayRequest support, see below RouterAddress ra = _transport.getTargetAddress(ri); if (ra == null) { if (_log.shouldLog(Log.INFO)) @@ -159,6 +210,8 @@ class IntroductionManager { _log.info("Peer is idle too long: " + cur); continue; } + // FIXME we can include all his addresses including IPv6 even if we don't support IPv6 (isValid() is false) + // but requires RelayRequest support, see below byte[] ip = cur.getRemoteIP(); int port = cur.getRemotePort(); if (!isValid(ip, port)) @@ -331,6 +384,9 @@ class IntroductionManager { // ip/port inside message should be 0:0, as it's unimplemented on send - // see PacketBuilder.buildRelayRequest() + // and we don't read it here. + // FIXME implement for getting Alice's IPv4 in RelayRequest sent over IPv6? + // or is that just too easy to spoof? if (!isValid(alice.getIP(), alice.getPort()) || ipSize != 0 || port != 0) { if (_log.shouldLog(Log.WARN)) { byte ip[] = new byte[ipSize]; @@ -368,12 +424,14 @@ class IntroductionManager { /** * Are IP and port valid? + * Reject all IPv6, for now, even if we are configured for it. * Refuse anybody in the same /16 * @since 0.9.3 */ private boolean isValid(byte[] ip, int port) { return port >= 1024 && port <= 65535 && + ip != null && ip.length == 4 && _transport.isValid(ip) && (!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) && (!_context.blocklist().isBlocklisted(ip)); diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index 2e5f356cae..c1d977a353 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -1058,6 +1058,7 @@ class PacketBuilder { // specify these if we know what our external receive ip/port is and if its different // from what bob is going to think + // FIXME IPv4 addr must be specified when sent over IPv6 private byte[] getOurExplicitIP() { return null; } private int getOurExplicitPort() { return 0; } @@ -1077,6 +1078,8 @@ class PacketBuilder { // let's not use an introducer on a privileged port, sounds like trouble if (ikey == null || iport < 1024 || iport > 65535 || iaddr == null || tag <= 0 || + // must be IPv4 for now as we don't send Alice IP/port, see below + iaddr.getAddress().length != 4 || (!_transport.isValid(iaddr.getAddress())) || Arrays.equals(iaddr.getAddress(), _transport.getExternalIP())) { if (_log.shouldLog(_log.WARN)) @@ -1090,11 +1093,17 @@ class PacketBuilder { return rv; } + /** + * TODO Alice IP/port in packet will always be null/0, must be fixed to + * send a RelayRequest over IPv6 + * + */ public UDPPacket buildRelayRequest(InetAddress introHost, int introPort, byte introKey[], long introTag, SessionKey ourIntroKey, long introNonce, boolean encrypt) { UDPPacket packet = buildPacketHeader(PEER_RELAY_REQUEST_FLAG_BYTE); byte data[] = packet.getPacket().getData(); int off = HEADER_SIZE; + // FIXME must specify these if request is going over IPv6 byte ourIP[] = getOurExplicitIP(); int ourPort = getOurExplicitPort(); @@ -1218,6 +1227,7 @@ class PacketBuilder { DataHelper.toLong(data, off, 2, charlie.getRemotePort()); off += 2; + // Alice IP/Port currently ignored on receive - see UDPPacketReader byte aliceIP[] = alice.getIP(); DataHelper.toLong(data, off, 1, aliceIP.length); off++; diff --git a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java index 5381bfaea4..d139970e06 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java @@ -131,6 +131,7 @@ public class UDPAddress { } public String getHost() { return _host; } + InetAddress getHostAddress() { if (_hostAddress == null) { try { @@ -150,6 +151,7 @@ public class UDPAddress { byte[] getIntroKey() { return _introKey; } int getIntroducerCount() { return (_introAddresses == null ? 0 : _introAddresses.length); } + InetAddress getIntroducerHost(int i) { if (_introAddresses[i] == null) { try { @@ -160,8 +162,11 @@ public class UDPAddress { } return _introAddresses[i]; } + int getIntroducerPort(int i) { return _introPorts[i]; } + byte[] getIntroducerKey(int i) { return _introKeys[i]; } + long getIntroducerTag(int i) { return _introTags[i]; } /** diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java index e8c138a1c5..6a82fcebd5 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java @@ -730,6 +730,7 @@ class UDPPacketReader { return (int)DataHelper.fromLong(_message, offset, 2); } + /** @deprecated unused */ public int readAliceIPSize() { int offset = readBodyOffset(); offset += DataHelper.fromLong(_message, offset, 1); @@ -737,6 +738,7 @@ class UDPPacketReader { offset += 2; return (int)DataHelper.fromLong(_message, offset, 1); } + /** @deprecated unused */ public void readAliceIP(byte target[], int targetOffset) { int offset = readBodyOffset(); offset += DataHelper.fromLong(_message, offset, 1); @@ -746,6 +748,7 @@ class UDPPacketReader { offset++; System.arraycopy(_message, offset, target, targetOffset, sz); } + /** @deprecated unused */ public int readAlicePort() { int offset = readBodyOffset(); offset += DataHelper.fromLong(_message, offset, 1); diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 4d64e1adfe..48623f7ebe 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -81,6 +81,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority private long _lastInboundReceivedOn; private final DHSessionKeyBuilder.Factory _dhFactory; private int _mtu; + /** + * Do we have a public IPv6 address? + * TODO periodically update via CSFI.NetMonitor? + */ + private boolean _haveIPv6Address; /** do we need to rebuild our external router address asap? */ private boolean _needsRebuild; @@ -553,6 +558,10 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority public void externalAddressReceived(Transport.AddressSource source, byte[] ip, int port) { if (_log.shouldLog(Log.WARN)) _log.warn("Received address: " + Addresses.toString(ip, port) + " from: " + source); + if (source == SOURCE_INTERFACE && ip.length == 16) { + // must be set before isValid() call + _haveIPv6Address = true; + } if (explicitAddressSpecified()) return; String sources = _context.getProperty(PROP_SOURCES, DEFAULT_SOURCES); @@ -790,10 +799,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return (rport == lport) && DataHelper.eq(laddr, raddr); } - /** @param addr may be null */ + /** + * An IPv6 address is only valid if we are configured to support IPv6 + * AND we have a public IPv6 address. + * + * @param addr may be null, returns false + */ public final boolean isValid(byte addr[]) { if (addr == null) return false; - if (isPubliclyRoutable(addr)) + if (isPubliclyRoutable(addr) && + (addr.length != 16 || _haveIPv6Address)) return true; return _context.getBooleanProperty("i2np.udp.allowLocal"); } From 94e34ff366b23226f16796fe1c7d1a533602c97d Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 8 May 2013 16:48:39 +0000 Subject: [PATCH 11/38] RouterAddress: - Add new constructor - Add add getHost() and deepEquals() - Compare host string, not IP, in equals() SSU: - Remove all _external* fields; use _currentAddresses in super - Big rework of externalAddressReceived(), rebuildExternalAddress(), needsRebuild(), and replaceAddress() for multiple addresses and IPv6 - Add caching in UDPAddress - More IPv6 flavors of utility methods - Remove two-art replaceAddress() --- core/java/src/net/i2p/data/RouterAddress.java | 40 ++- .../i2p/router/transport/TransportImpl.java | 25 +- .../i2p/router/transport/TransportUtil.java | 11 + .../transport/udp/EstablishmentManager.java | 8 +- .../i2p/router/transport/udp/UDPAddress.java | 87 +++++- .../router/transport/udp/UDPTransport.java | 289 +++++++++++------- 6 files changed, 324 insertions(+), 136 deletions(-) diff --git a/core/java/src/net/i2p/data/RouterAddress.java b/core/java/src/net/i2p/data/RouterAddress.java index d003cc0502..da95901e13 100644 --- a/core/java/src/net/i2p/data/RouterAddress.java +++ b/core/java/src/net/i2p/data/RouterAddress.java @@ -54,6 +54,17 @@ public class RouterAddress extends DataStructureImpl { _options = new OrderedProperties(); } + /** + * For efficiency when created by a Transport. + * @param options not copied; do not reuse or modify + * @since IPv6 + */ + public RouterAddress(String style, OrderedProperties options, int cost) { + _transportStyle = style; + _options = options; + _cost = cost; + } + /** * Retrieve the weighted cost of this address, relative to other methods of * contacting this router. The value 0 means free and 255 means really expensive. @@ -171,7 +182,7 @@ public class RouterAddress extends DataStructureImpl { if (_ip != null) return _ip; byte[] rv = null; - String host = _options.getProperty(PROP_HOST); + String host = getHost(); if (host != null) { rv = Addresses.getIP(host); if (rv != null && @@ -183,6 +194,17 @@ public class RouterAddress extends DataStructureImpl { return rv; } + /** + * Convenience, same as getOption("host"). + * Does no parsing, so faster than getIP(). + * + * @return host string or null + * @since IPv6 + */ + public String getHost() { + return _options.getProperty(PROP_HOST); + } + /** * Caching version of Integer.parseInt(getOption("port")) * Caches valid ports 1-65535 only. @@ -239,7 +261,7 @@ public class RouterAddress extends DataStructureImpl { } /** - * Transport, IP, and port only. + * Transport, host, and port only. * Never look at cost or other properties. */ @Override @@ -249,12 +271,24 @@ public class RouterAddress extends DataStructureImpl { RouterAddress addr = (RouterAddress) object; return getPort() == addr.getPort() && - DataHelper.eq(getIP(), addr.getIP()) && + DataHelper.eq(getHost(), addr.getHost()) && DataHelper.eq(_transportStyle, addr._transportStyle); //DataHelper.eq(_options, addr._options) && //DataHelper.eq(_expiration, addr._expiration); } + /** + * Everything, including Transport, host, port, options, and cost + * @param addr may be null + * @since IPv6 + */ + public boolean deepEquals(RouterAddress addr) { + return + equals(addr) && + _cost == addr._cost && + _options.equals(addr._options); + } + /** * Just use a few items for speed (expiration is always null). * Never look at cost or other properties. diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 3105262810..430b57d41f 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -479,6 +479,21 @@ public abstract class TransportImpl implements Transport { return _currentAddresses; } + /** + * What address are we currently listening to? + * Replaces getCurrentAddress() + * @param ipv6 true for IPv6 only; false for IPv4 only + * @return first matching address or null + * @since IPv6 + */ + public RouterAddress getCurrentAddress(boolean ipv6) { + for (RouterAddress ra : _currentAddresses) { + if (ipv6 == TransportUtil.isIPv6(ra)) + return ra; + } + return null; + } + /** * Do we have any current address? * @since IPv6 @@ -511,15 +526,9 @@ public abstract class TransportImpl implements Transport { if (address == null) { _currentAddresses.clear(); } else { - byte[] ip = address.getIP(); - if (ip == null) { - _log.error("WTF null ip for " + address); - return; - } - int len = ip.length; + boolean isIPv6 = TransportUtil.isIPv6(address); for (RouterAddress ra : _currentAddresses) { - byte[] ipx = ra.getIP(); - if (ipx != null && ipx.length == len) + if (isIPv6 == TransportUtil.isIPv6(ra)) _currentAddresses.remove(ra); } _currentAddresses.add(address); diff --git a/router/java/src/net/i2p/router/transport/TransportUtil.java b/router/java/src/net/i2p/router/transport/TransportUtil.java index cc708d1142..64f284041b 100644 --- a/router/java/src/net/i2p/router/transport/TransportUtil.java +++ b/router/java/src/net/i2p/router/transport/TransportUtil.java @@ -13,6 +13,7 @@ import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; +import net.i2p.data.RouterAddress; import net.i2p.router.RouterContext; /** @@ -78,6 +79,16 @@ public abstract class TransportUtil { return IPv6Config.IPV6_DISABLED; } + /** + * Addresses without a host (i.e. w/introducers) + * are assumed to be IPv4 + */ + public static boolean isIPv6(RouterAddress addr) { + // do this the fast way, without calling getIP() to parse the host string + String host = addr.getOption(RouterAddress.PROP_HOST); + return host != null && host.contains(":"); + } + /** * @param addr non-null * @since IPv6 moved from TransportImpl diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index 1b87e24fe7..f9b9e2d1f9 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -447,7 +447,9 @@ class EstablishmentManager { } if (!_transport.allowConnection()) return; // drop the packet - state = new InboundEstablishState(_context, from.getIP(), from.getPort(), _transport.getExternalPort(), + byte[] fromIP = from.getIP(); + state = new InboundEstablishState(_context, fromIP, from.getPort(), + _transport.getExternalPort(fromIP.length == 16), _transport.getDHBuilder()); state.receiveSessionRequest(reader.getSessionRequestReader()); InboundEstablishState oldState = _inboundStates.putIfAbsent(from, state); @@ -835,7 +837,9 @@ class EstablishmentManager { _inboundStates.remove(state.getRemoteHostId()); return; } - _transport.send(_builder.buildSessionCreatedPacket(state, _transport.getExternalPort(), _transport.getIntroKey())); + _transport.send(_builder.buildSessionCreatedPacket(state, + _transport.getExternalPort(state.getSentIP().length == 16), + _transport.getIntroKey())); state.createdPacketSent(); } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java index d139970e06..9d566a6698 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java @@ -2,10 +2,12 @@ package net.i2p.router.transport.udp; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Map; import net.i2p.data.Base64; import net.i2p.data.RouterAddress; import net.i2p.data.SessionKey; +import net.i2p.util.LHMCache; /** * basic helper to parse out peer info from a udp address @@ -133,13 +135,8 @@ public class UDPAddress { public String getHost() { return _host; } InetAddress getHostAddress() { - if (_hostAddress == null) { - try { - _hostAddress = InetAddress.getByName(_host); - } catch (UnknownHostException uhe) { - _hostAddress = null; - } - } + if (_hostAddress == null) + _hostAddress = getByName(_host); return _hostAddress; } @@ -153,13 +150,8 @@ public class UDPAddress { int getIntroducerCount() { return (_introAddresses == null ? 0 : _introAddresses.length); } InetAddress getIntroducerHost(int i) { - if (_introAddresses[i] == null) { - try { - _introAddresses[i] = InetAddress.getByName(_introHosts[i]); - } catch (UnknownHostException uhe) { - _introAddresses[i] = null; - } - } + if (_introAddresses[i] == null) + _introAddresses[i] = getByName(_introHosts[i]); return _introAddresses[i]; } @@ -197,4 +189,71 @@ public class UDPAddress { } return rv.toString(); } + + //////////////// + // cache copied from Addresses.java but caching InetAddress instead of byte[] + + + /** + * Textual IP to InetAddress, because InetAddress.getByName() is slow. + * + * @since IPv6 + */ + private static final Map _inetAddressCache; + + static { + long maxMemory = Runtime.getRuntime().maxMemory(); + if (maxMemory == Long.MAX_VALUE) + maxMemory = 96*1024*1024l; + long min = 128; + long max = 2048; + // 512 nominal for 128 MB + int size = (int) Math.max(min, Math.min(max, 1 + (maxMemory / (256*1024)))); + _inetAddressCache = new LHMCache(size); + } + + /** + * Caching version of InetAddress.getByName(host), which is slow. + * Caches numeric host names only. + * Will resolve but not cache DNS host names. + * + * Unlike InetAddress.getByName(), we do NOT allow numeric IPs + * of the form d.d.d, d.d, or d, as these are almost certainly mistakes. + * + * @param host DNS or IPv4 or IPv6 host name; if null returns null + * @return InetAddress or null + * @since IPv6 + */ + private static InetAddress getByName(String host) { + if (host == null) + return null; + InetAddress rv; + synchronized (_inetAddressCache) { + rv = _inetAddressCache.get(host); + } + if (rv == null) { + try { + boolean isIPv4 = host.replaceAll("[0-9\\.]", "").length() == 0; + if (isIPv4 && host.replaceAll("[0-9]", "").length() != 3) + return null; + rv = InetAddress.getByName(host); + if (isIPv4 || + host.replaceAll("[0-9a-fA-F:]", "").length() == 0) { + synchronized (_inetAddressCache) { + _inetAddressCache.put(host, rv); + } + } + } catch (UnknownHostException uhe) {} + } + return rv; + } + + /** + * @since IPv6 + */ + static void clearCache() { + synchronized(_inetAddressCache) { + _inetAddressCache.clear(); + } + } } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 48623f7ebe..1751bf453e 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -45,6 +45,7 @@ import net.i2p.router.util.RandomIterator; import net.i2p.util.Addresses; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.Log; +import net.i2p.util.OrderedProperties; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer2; @@ -90,15 +91,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority /** do we need to rebuild our external router address asap? */ private boolean _needsRebuild; - /** summary info to distribute */ - private RouterAddress _externalAddress; - /** - * Port number on which we can be reached, or -1 for error, or 0 for unset - * Do NOT use this for current internal port - use UDPEndpoint.getListenPort() - */ - private int _externalListenPort; - /** IP address of externally reachable host, or null */ - private InetAddress _externalListenHost; /** introduction key */ private SessionKey _introKey; @@ -332,7 +324,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // we will check below after starting up the endpoint. int port; int oldIPort = _context.getProperty(PROP_INTERNAL_PORT, -1); - int oldBindPort = getIPv4ListenPort(); + int oldBindPort = getListenPort(false); int oldEPort = _context.getProperty(PROP_EXTERNAL_PORT, -1); if (oldIPort > 0) port = oldIPort; @@ -387,7 +379,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // hack, first IPv4 endpoint, FIXME if (newPort < 0 && endpoint.isIPv4()) { newPort = endpoint.getListenPort(); - _externalListenPort = newPort; } } catch (SocketException se) { _endpoints.remove(endpoint); @@ -449,6 +440,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _dropList.clear(); _introManager.reset(); UDPPacket.clearCache(); + UDPAddress.clearCache(); } /** @@ -457,42 +449,62 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority */ SessionKey getIntroKey() { return _introKey; } - int getExternalPort() { return _externalListenPort; } + int getExternalPort(boolean ipv6) { + RouterAddress addr = getCurrentAddress(ipv6); + if (addr != null) { + int rv = addr.getPort(); + if (rv > 0) + return rv; + } + return getRequestedPort(ipv6); + } /** + * IPv4 only * @return IP or null * @since 0.9.2 */ byte[] getExternalIP() { - InetAddress ia = _externalListenHost; - if (ia == null) - return null; - return ia.getAddress(); + RouterAddress addr = getCurrentAddress(false); + if (addr != null) + return addr.getIP(); + return null; } /** - * The current port of the first IPv4 endpoint. - * To be enhanced to handle multiple IPv4 endpoints. + * The current port of the first matching endpoint. + * To be enhanced to handle multiple endpoints of the same type. * @return port or -1 * @since IPv6 */ - private int getIPv4ListenPort() { + private int getListenPort(boolean ipv6) { for (UDPEndpoint endpoint : _endpoints) { - if (endpoint.isIPv4()) + if (((!ipv6) && endpoint.isIPv4()) || + (ipv6 && endpoint.isIPv6())) return endpoint.getListenPort(); } return -1; } + /** + * The current or configured internal IPv4 port. + * UDPEndpoint should always be instantiated (and a random port picked if not configured) + * before this is called, so the returned value should be > 0 + * unless the endpoint failed to bind. + */ + @Override + public int getRequestedPort() { + return getRequestedPort(false); + } + /** * The current or configured internal port. * UDPEndpoint should always be instantiated (and a random port picked if not configured) * before this is called, so the returned value should be > 0 * unless the endpoint failed to bind. */ - @Override - public int getRequestedPort() { - int rv = getIPv4ListenPort(); + private int getRequestedPort(boolean ipv6) { + int rv = getListenPort(ipv6); if (rv > 0) return rv; // fallbacks @@ -604,7 +616,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority else _log.warn("UPnP has failed to open the SSU port: " + port + " reason: " + reason); } - if (success && _externalListenHost != null) + if (success && getExternalIP() != null) setReachabilityStatus(CommSystemFacade.STATUS_OK); } @@ -628,8 +640,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority if (ourIP.length != 4) return; boolean isValid = isValid(ourIP) && - ((ourPort >= MIN_EXTERNAL_PORT && ourPort <= MAX_EXTERNAL_PORT) || - ourPort == _externalListenPort || _externalListenPort <= 0); + (ourPort >= MIN_EXTERNAL_PORT && ourPort <= MAX_EXTERNAL_PORT); boolean explicitSpecified = explicitAddressSpecified(); boolean inboundRecent = _lastInboundReceivedOn + ALLOW_IP_CHANGE_INTERVAL > System.currentTimeMillis(); if (_log.shouldLog(Log.INFO)) @@ -651,7 +662,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority markUnreachable(from); //_context.banlist().banlistRouter(from, "They said we had an invalid IP", STYLE); return; - } else if (inboundRecent && _externalListenPort > 0 && _externalListenHost != null) { + } + RouterAddress addr = getCurrentAddress(false); + if (inboundRecent && addr != null && addr.getPort() > 0 && addr.getHost() != null) { // use OS clock since its an ordering thing, not a time thing // Note that this fails us if we switch from one IP to a second, then back to the first, // as some routers still have the first IP and will successfully connect, @@ -686,6 +699,10 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } /** + * Possibly change our external address to the IP/port. + * IP/port are already validated, but not yet compared to current IP/port. + * We compare here. + * * @param ourIP MUST have been previously validated with isValid() * IPv4 or IPv6 OK * @param ourPort >= 1024 or 0 for no change @@ -696,39 +713,33 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority boolean updated = false; boolean fireTest = false; + boolean isIPv6 = ourIP.length == 16; + RouterAddress current = getCurrentAddress(isIPv6); + byte[] externalListenHost = current != null ? current.getIP() : null; + int externalListenPort = current != null ? current.getPort() : getRequestedPort(isIPv6); + if (_log.shouldLog(Log.INFO)) _log.info("Change address? status = " + _reachabilityStatus + " diff = " + (_context.clock().now() - _reachabilityStatusLastUpdated) + - " old = " + _externalListenHost + ':' + _externalListenPort + + " old = " + Addresses.toString(externalListenHost, externalListenPort) + " new = " + Addresses.toString(ourIP, ourPort)); + if ((fixedPort && externalListenPort > 0) || ourPort <= 0) + ourPort = externalListenPort; + synchronized (this) { - if ( (_externalListenHost == null) || - (!eq(_externalListenHost.getAddress(), _externalListenPort, ourIP, ourPort)) ) { + if (ourPort > 0 && + !eq(externalListenHost, externalListenPort, ourIP, ourPort)) { // This prevents us from changing our IP when we are not firewalled //if ( (_reachabilityStatus != CommSystemFacade.STATUS_OK) || // (_externalListenHost == null) || (_externalListenPort <= 0) || // (_context.clock().now() - _reachabilityStatusLastUpdated > 2*TEST_FREQUENCY) ) { // they told us something different and our tests are either old or failing - try { - _externalListenHost = InetAddress.getByAddress(ourIP); - // fixed port defaults to true so we never do this - if (ourPort >= MIN_EXTERNAL_PORT && ourPort <= MAX_EXTERNAL_PORT && !fixedPort) - _externalListenPort = ourPort; if (_log.shouldLog(Log.WARN)) _log.warn("Trying to change our external address to " + - Addresses.toString(ourIP, _externalListenPort)); - if (_externalListenPort > 0) { - rebuildExternalAddress(); - replaceAddress(_externalAddress); - updated = true; - } - } catch (UnknownHostException uhe) { - _externalListenHost = null; - if (_log.shouldLog(Log.WARN)) - _log.warn("Error trying to change our external address to " + - Addresses.toString(ourIP, ourPort), uhe); - } + Addresses.toString(ourIP, ourPort)); + RouterAddress newAddr = rebuildExternalAddress(ourIP, ourPort, true); + updated = newAddr != null; //} else { // // they told us something different, but our tests are recent and positive, // // so lets test again @@ -748,13 +759,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } else if (updated) { _context.statManager().addRateData("udp.addressUpdated", 1); Map changes = new HashMap(); - if (!fixedPort) + if (ourIP.length == 4 && !fixedPort) changes.put(PROP_EXTERNAL_PORT, Integer.toString(ourPort)); // queue a country code lookup of the new IP - _context.commSystem().queueLookup(ourIP); + if (ourIP.length == 4) + _context.commSystem().queueLookup(ourIP); // store these for laptop-mode (change ident on restart... or every time... when IP changes) + // IPV4 ONLY String oldIP = _context.getProperty(PROP_IP); - if (!_externalListenHost.getHostAddress().equals(oldIP)) { + String newIP = Addresses.toString(ourIP); + if (ourIP.length == 4 && !newIP.equals(oldIP)) { long lastChanged = 0; long now = _context.clock().now(); String lcs = _context.getProperty(PROP_IP_CHANGE); @@ -764,7 +778,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } catch (NumberFormatException nfe) {} } - changes.put(PROP_IP, _externalListenHost.getHostAddress()); + changes.put(PROP_IP, newIP); changes.put(PROP_IP_CHANGE, Long.toString(now)); _context.router().saveConfig(changes, null); @@ -784,7 +798,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _context.router().shutdown(Router.EXIT_HARD_RESTART); // doesn't return } - } else if (!fixedPort) { + } else if (ourIP.length == 4 && !fixedPort) { // save PROP_EXTERNAL_PORT _context.router().saveConfig(changes, null); } @@ -795,6 +809,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return updated; } + /** + * @param laddr and raddr may be null + */ private static final boolean eq(byte laddr[], int lport, byte raddr[], int rport) { return (rport == lport) && DataHelper.eq(laddr, raddr); } @@ -839,12 +856,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority /** * Get the states for all peers at the given remote host, ignoring port. * Used for a last-chance search for a peer that changed port, by PacketHandler. + * Always returns empty list for IPv6 hostInfo. * @since 0.9.3 */ List getPeerStatesByIP(RemoteHostId hostInfo) { List rv = new ArrayList(4); byte[] ip = hostInfo.getIP(); - if (ip != null) { + if (ip != null && ip.length == 4) { for (PeerState ps : _peersByIdent.values()) { if (DataHelper.eq(ip, ps.getRemoteIP())) rv.add(ps); @@ -865,7 +883,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * Remove and add to peersByRemoteHost map * @since 0.9.3 */ - public void changePeerPort(PeerState peer, int newPort) { + void changePeerPort(PeerState peer, int newPort) { int oldPort; synchronized (_addDropLock) { oldPort = peer.getRemotePort(); @@ -887,6 +905,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority EstablishmentManager getEstablisher() { return _establisher; } + /** * Intercept RouterInfo entries received directly from a peer to inject them into * the PeersByCapacity listing. @@ -1216,11 +1235,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority if ( (altByHost != null) && (peer != altByHost) ) locked_dropPeer(altByHost, shouldBanlist, "recurse"); } + /** + * Does the IPv4 external address need to be rebuilt? + */ private boolean needsRebuild() { if (_needsRebuild) return true; // simple enough if (_context.router().isHidden()) return false; + RouterAddress addr = getCurrentAddress(false); if (introducersRequired()) { - RouterAddress addr = _externalAddress; UDPAddress ua = new UDPAddress(addr); int valid = 0; for (int i = 0; i < ua.getIntroducerCount(); i++) { @@ -1246,26 +1268,23 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return true; } } else { - boolean rv = (_externalListenHost == null) || (_externalListenPort <= 0); + byte[] externalListenHost = addr != null ? addr.getIP() : null; + int externalListenPort = addr != null ? addr.getPort() : -1; + boolean rv = (externalListenHost == null) || (externalListenPort <= 0); if (!rv) { - RouterAddress addr = _externalAddress; - UDPAddress ua = new UDPAddress(addr); - if (ua.getIntroducerCount() > 0) + // shortcut to determine if introducers are present + if (addr.getOption("ihost0") != null) rv = true; // status == ok and we don't actually need introducers, so rebuild } - if (_log.shouldLog(Log.INFO)) { - if (rv) { - _log.info("Need to initialize our direct SSU info (" + _externalListenHost + ":" + _externalListenPort + ")"); - } else { - RouterAddress addr = _externalAddress; - UDPAddress ua = new UDPAddress(addr); - if ( (ua.getPort() <= 0) || (ua.getHost() == null) ) { - _log.info("Our direct SSU info is initialized, but not used in our address yet"); - rv = true; - } else { - //_log.info("Our direct SSU info is initialized"); - } - } + if (rv) { + if (_log.shouldLog(Log.INFO)) + _log.info("Need to initialize our direct SSU info (" + Addresses.toString(externalListenHost, externalListenPort) + ')'); + } else if (addr.getPort() <= 0 || addr.getHost() == null) { + if (_log.shouldLog(Log.INFO)) + _log.info("Our direct SSU info is initialized, but not used in our address yet"); + rv = true; + } else { + //_log.info("Our direct SSU info is initialized"); } return rv; } @@ -1557,7 +1576,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority public void stopListening() { shutdown(); // will this work? - _externalAddress = null; replaceAddress(null); } @@ -1578,34 +1596,74 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return getCurrentAddresses(); } - private void rebuildExternalAddress() { rebuildExternalAddress(true); } + /** + * Update our IPv4 addresses AND tell the router to rebuild and republish the router info. + * + * @return the new address if changed, else null + */ + private RouterAddress rebuildExternalAddress() { + return rebuildExternalAddress(true); + } - private void rebuildExternalAddress(boolean allowRebuildRouterInfo) { + /** + * Update our IPv4 address and optionally tell the router to rebuild and republish the router info. + * + * @param allowRebuildRouterInfo whether to tell the router + * @return the new address if changed, else null + */ + private RouterAddress rebuildExternalAddress(boolean allowRebuildRouterInfo) { // if the external port is specified, we want to use that to bind to even // if we don't know the external host. - _externalListenPort = _context.getProperty(PROP_EXTERNAL_PORT, -1); + int port = _context.getProperty(PROP_EXTERNAL_PORT, -1); + byte[] ip = null; if (explicitAddressSpecified()) { - try { - String host = _context.getProperty(PROP_EXTERNAL_HOST); - _externalListenHost = InetAddress.getByName(host); - } catch (UnknownHostException uhe) { - _externalListenHost = null; - } + String host = _context.getProperty(PROP_EXTERNAL_HOST); + return rebuildExternalAddress(host, port, allowRebuildRouterInfo); } + return rebuildExternalAddress(ip, port, allowRebuildRouterInfo); + } + + /** + * Update our IPv4 or IPv6 address and optionally tell the router to rebuild and republish the router info. + * + * @param ip new ip valid IPv4 or IPv6 or null + * @param port new valid port or -1 + * @param allowRebuildRouterInfo whether to tell the router + * @return the new address if changed, else null + * @since IPv6 + */ + private RouterAddress rebuildExternalAddress(byte[] ip, int port, boolean allowRebuildRouterInfo) { + if (ip == null || isValid(ip)) + return rebuildExternalAddress(Addresses.toString(ip), port, allowRebuildRouterInfo); + return null; + } + + /** + * Update our IPv4 or IPv6 address and optionally tell the router to rebuild and republish the router info. + * + * @param host new valid IPv4 or IPv6 or DNS hostname or null + * @param port new valid port or -1 + * @param allowRebuildRouterInfo whether to tell the router + * @return the new address if changed, else null + * @since IPv6 + */ + private RouterAddress rebuildExternalAddress(String host, int port, boolean allowRebuildRouterInfo) { if (_context.router().isHidden()) - return; + return null; - Properties options = new Properties(); + OrderedProperties options = new OrderedProperties(); boolean directIncluded = false; - if ( allowDirectUDP() && (_externalListenPort > 0) && (_externalListenHost != null) && (isValid(_externalListenHost.getAddress())) ) { - options.setProperty(UDPAddress.PROP_PORT, String.valueOf(_externalListenPort)); - options.setProperty(UDPAddress.PROP_HOST, _externalListenHost.getHostAddress()); + // DNS name assumed IPv4 + boolean isIPv6 = host != null && host.contains(":"); + if (allowDirectUDP() && port > 0 && host != null) { + options.setProperty(UDPAddress.PROP_PORT, String.valueOf(port)); + options.setProperty(UDPAddress.PROP_HOST, host); directIncluded = true; } - boolean introducersRequired = introducersRequired(); + boolean introducersRequired = (!isIPv6) && introducersRequired(); boolean introducersIncluded = false; if (introducersRequired || !directIncluded) { int found = _introManager.pickInbound(options, PUBLIC_RELAY_COUNT); @@ -1638,40 +1696,42 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority if (_introKey != null) options.setProperty(UDPAddress.PROP_INTRO_KEY, _introKey.toBase64()); - RouterAddress addr = new RouterAddress(); // SSU seems to regulate at about 85%, so make it a little higher. // If this is too low, both NTCP and SSU always have incremented cost and // the whole mechanism is not helpful. + int cost = DEFAULT_COST; if (ADJUST_COST && !haveCapacity(91)) - addr.setCost(DEFAULT_COST + 1); - else - addr.setCost(DEFAULT_COST); - //addr.setExpiration(null); - addr.setTransportStyle(STYLE); - addr.setOptions(options); + cost++; + RouterAddress addr = new RouterAddress(STYLE, options, cost); - boolean wantsRebuild = false; - if ( (_externalAddress == null) || !(_externalAddress.equals(addr)) ) - wantsRebuild = true; + RouterAddress current = getCurrentAddress(isIPv6); + boolean wantsRebuild = !addr.deepEquals(current); - RouterAddress oldAddress = _externalAddress; - _externalAddress = addr; - if (_log.shouldLog(Log.INFO)) - _log.info("Address rebuilt: " + addr); - replaceAddress(addr, oldAddress); - if (allowRebuildRouterInfo && wantsRebuild) - _context.router().rebuildRouterInfo(); - _needsRebuild = false; + if (wantsRebuild) { + if (_log.shouldLog(Log.INFO)) + _log.info("Address rebuilt: " + addr); + replaceAddress(addr); + if (allowRebuildRouterInfo) + _context.router().rebuildRouterInfo(); + } else { + addr = null; + } + if (!isIPv6) + _needsRebuild = false; + return addr; } else { if (_log.shouldLog(Log.WARN)) _log.warn("Wanted to rebuild my SSU address, but couldn't specify either the direct or indirect info (needs introducers? " + introducersRequired + ")", new Exception("source")); _needsRebuild = true; + return null; } } /** - * Replace then tell NTCP that we changed. + * Replace then tell NTCP that we changed. + * + * @param address the new address or null to remove all */ @Override protected void replaceAddress(RouterAddress address) { @@ -1679,6 +1739,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _context.commSystem().notifyReplaceAddress(address); } + /** + * Calls replaceAddress(address), then shuts down the router if + * dynamic keys is enabled, which it never is, so all this is unused. + * + * @param address the new address or null to remove all + */ +/**** protected void replaceAddress(RouterAddress address, RouterAddress oldAddress) { replaceAddress(address); if (oldAddress != null) { @@ -1704,7 +1771,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } } } +****/ + /** + * Do we require introducers? + */ public boolean introducersRequired() { /****************** * Don't do this anymore, as we are removing the checkbox from the UI, @@ -2621,8 +2692,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority long pingCutoff = now - (2 * 60*60*1000); long pingFirewallCutoff = now - PING_FIREWALL_CUTOFF; boolean shouldPingFirewall = _reachabilityStatus != CommSystemFacade.STATUS_OK; - int currentListenPort = getIPv4ListenPort(); - boolean pingOneOnly = shouldPingFirewall && _externalListenPort == currentListenPort; + int currentListenPort = getListenPort(false); + boolean pingOneOnly = shouldPingFirewall && getExternalPort(false) == currentListenPort; boolean shortLoop = shouldPingFirewall; _expireBuffer.clear(); _runCount++; From 3a49d6d28f0f6da5ff6cdc8fce2ae093298aaaa0 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 9 May 2013 16:18:58 +0000 Subject: [PATCH 12/38] * NTCP: Move SSU address notification handling from CSFI to NTCPTransport --- .../transport/CommSystemFacadeImpl.java | 243 +----------------- .../net/i2p/router/transport/Transport.java | 3 +- .../i2p/router/transport/TransportImpl.java | 2 +- .../router/transport/TransportManager.java | 14 +- .../router/transport/ntcp/NTCPTransport.java | 217 +++++++++++++++- .../router/transport/udp/UDPTransport.java | 6 +- 6 files changed, 230 insertions(+), 255 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 3b69e9b090..c8cc315772 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -194,249 +194,26 @@ public class CommSystemFacadeImpl extends CommSystemFacade { //if (_context.router().isHidden()) // return Collections.EMPTY_SET; List addresses = new ArrayList(_manager.getAddresses()); - - Transport ntcp = _manager.getTransport(NTCPTransport.STYLE); - boolean hasNTCP = ntcp != null && ntcp.hasCurrentAddress(); - - boolean newCreated = false; - if (!hasNTCP) { - RouterAddress addr = createNTCPAddress(_context); - if (_log.shouldLog(Log.INFO)) - _log.info("NTCP address: " + addr); - if (addr != null) { - addresses.add(addr); - newCreated = true; - } - } - if (_log.shouldLog(Log.INFO)) - _log.info("Creating addresses: " + addresses + " isNew? " + newCreated, new Exception("creator")); + _log.info("Creating addresses: " + addresses, new Exception("creator")); return addresses; } - public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname"; - public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port"; - public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport"; - public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoip"; - - /** - * This only creates an address if the hostname AND port are set in router.config, - * which should be rare. - * Otherwise, notifyReplaceAddress() below takes care of it. - * Note this is called both from above and from NTCPTransport.startListening() - * - * This should really be moved to ntcp/NTCPTransport.java, why is it here? - */ - public static RouterAddress createNTCPAddress(RouterContext ctx) { - if (!TransportManager.isNTCPEnabled(ctx)) return null; - String name = ctx.router().getConfigSetting(PROP_I2NP_NTCP_HOSTNAME); - String port = ctx.router().getConfigSetting(PROP_I2NP_NTCP_PORT); - /* - boolean isNew = false; - if (name == null) { - name = "localhost"; - isNew = true; - } - if (port == null) { - port = String.valueOf(ctx.random().nextInt(10240)+1024); - isNew = true; - } - */ - if ( (name == null) || (port == null) || (name.trim().length() <= 0) || ("null".equals(name)) ) - return null; - try { - int p = Integer.parseInt(port); - if ( (p <= 0) || (p > 64*1024) ) - return null; - } catch (NumberFormatException nfe) { - return null; - } - Properties props = new Properties(); - props.setProperty(NTCPAddress.PROP_HOST, name); - props.setProperty(NTCPAddress.PROP_PORT, port); - RouterAddress addr = new RouterAddress(); - addr.setCost(NTCPAddress.DEFAULT_COST); - //addr.setExpiration(null); - addr.setOptions(props); - addr.setTransportStyle(NTCPTransport.STYLE); - //if (isNew) { - // why save the same thing? - Map changes = new HashMap(); - changes.put(PROP_I2NP_NTCP_HOSTNAME, name); - changes.put(PROP_I2NP_NTCP_PORT, port); - ctx.router().saveConfig(changes, null); - //} - return addr; - } - /** * UDP changed addresses, tell NTCP and restart - * This should really be moved to ntcp/NTCPTransport.java, why is it here? + * + * All the work moved to NTCPTransport.externalAddressReceived() */ @Override - public synchronized void notifyReplaceAddress(RouterAddress udpAddr) { - if (udpAddr == null) - return; - NTCPTransport t = (NTCPTransport) _manager.getTransport(NTCPTransport.STYLE); - if (t == null) - return; - - //////// FIXME just take first IPv4 address for now - List oldAddrs = t.getCurrentAddresses(); - RouterAddress oldAddr = null; - for (RouterAddress ra : oldAddrs) { - byte[] ipx = ra.getIP(); - if (ipx != null && ipx.length == 4) { - oldAddr = ra; - break; - } - } - if (_log.shouldLog(Log.INFO)) - _log.info("Changing NTCP Address? was " + oldAddr); - - RouterAddress newAddr = new RouterAddress(); - newAddr.setTransportStyle(NTCPTransport.STYLE); - Properties newProps = new Properties(); - if (oldAddr == null) { - newAddr.setCost(NTCPAddress.DEFAULT_COST); - } else { - newAddr.setCost(oldAddr.getCost()); - newProps.putAll(oldAddr.getOptionsMap()); - } - - boolean changed = false; - - // Auto Port Setting - // old behavior (<= 0.7.3): auto-port defaults to false, and true trumps explicit setting - // new behavior (>= 0.7.4): auto-port defaults to true, but explicit setting trumps auto - // TODO rewrite this to operate on ints instead of strings - String oport = newProps.getProperty(NTCPAddress.PROP_PORT); - String nport = null; - String cport = _context.getProperty(PROP_I2NP_NTCP_PORT); - if (cport != null && cport.length() > 0) { - nport = cport; - } else if (_context.getBooleanPropertyDefaultTrue(PROP_I2NP_NTCP_AUTO_PORT)) { - // 0.9.6 change - // This wasn't quite right, as udpAddr is the EXTERNAL port and we really - // want NTCP to bind to the INTERNAL port the first time, - // because if they are different, the NAT is changing them, and - // it probably isn't mapping UDP and TCP the same. + public void notifyReplaceAddress(RouterAddress udpAddr) { + byte[] ip = udpAddr != null ? udpAddr.getIP() : null; + int port = udpAddr != null ? udpAddr.getPort() : 0; + if (port < 0) { Transport udp = _manager.getTransport(UDPTransport.STYLE); - if (udp != null) { - int udpIntPort = udp.getRequestedPort(); - if (udpIntPort > 0) - // should always be true - nport = Integer.toString(udpIntPort); - } - if (nport == null) - // fallback - nport = udpAddr.getOption(UDPAddress.PROP_PORT); + if (udp != null) + port = udp.getRequestedPort(); } - if (_log.shouldLog(Log.INFO)) - _log.info("old: " + oport + " config: " + cport + " new: " + nport); - if (nport == null || nport.length() <= 0) - return; - // 0.9.6 change - // Don't have NTCP "chase" SSU's external port, - // as it may change, possibly frequently. - //if (oport == null || ! oport.equals(nport)) { - if (oport == null) { - newProps.setProperty(NTCPAddress.PROP_PORT, nport); - changed = true; - } - - // Auto IP Setting - // old behavior (<= 0.7.3): auto-ip defaults to false, and trumps configured hostname, - // and ignores reachability status - leading to - // "firewalled with inbound TCP enabled" warnings. - // new behavior (>= 0.7.4): auto-ip defaults to true, and explicit setting trumps auto, - // and only takes effect if reachability is OK. - // And new "always" setting ignores reachability status, like - // "true" was in 0.7.3 - String ohost = newProps.getProperty(NTCPAddress.PROP_HOST); - String enabled = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "true").toLowerCase(Locale.US); - String name = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME); - // hostname config trumps auto config - if (name != null && name.length() > 0) - enabled = "false"; - Transport udp = _manager.getTransport(UDPTransport.STYLE); - short status = STATUS_UNKNOWN; - if (udp != null) - status = udp.getReachabilityStatus(); - if (_log.shouldLog(Log.INFO)) - _log.info("old: " + ohost + " config: " + name + " auto: " + enabled + " status: " + status); - if (enabled.equals("always") || - (Boolean.parseBoolean(enabled) && status == STATUS_OK)) { - String nhost = udpAddr.getOption(UDPAddress.PROP_HOST); - if (_log.shouldLog(Log.INFO)) - _log.info("old: " + ohost + " config: " + name + " new: " + nhost); - if (nhost == null || nhost.length() <= 0) - return; - if (ohost == null || ! ohost.equalsIgnoreCase(nhost)) { - newProps.setProperty(NTCPAddress.PROP_HOST, nhost); - changed = true; - } - } else if (enabled.equals("false") && - name != null && name.length() > 0 && - !name.equals(ohost) && - nport != null) { - // Host name is configured, and we have a port (either auto or configured) - // but we probably only get here if the port is auto, - // otherwise createNTCPAddress() would have done it already - if (_log.shouldLog(Log.INFO)) - _log.info("old: " + ohost + " config: " + name + " new: " + name); - newProps.setProperty(NTCPAddress.PROP_HOST, name); - changed = true; - } else if (ohost == null || ohost.length() <= 0) { - return; - } else if (Boolean.parseBoolean(enabled) && status != STATUS_OK) { - // UDP transitioned to not-OK, turn off NTCP address - // This will commonly happen at startup if we were initially OK - // because UPnP was successful, but a subsequent SSU Peer Test determines - // we are still firewalled (SW firewall, bad UPnP indication, etc.) - if (_log.shouldLog(Log.INFO)) - _log.info("old: " + ohost + " config: " + name + " new: null"); - newAddr = null; - changed = true; - } - - if (!changed) { - if (oldAddr != null) { - int oldCost = oldAddr.getCost(); - int newCost = NTCPAddress.DEFAULT_COST; - if (TransportImpl.ADJUST_COST && !t.haveCapacity()) - newCost++; - if (newCost != oldCost) { - oldAddr.setCost(newCost); - if (_log.shouldLog(Log.WARN)) - _log.warn("Changing NTCP cost from " + oldCost + " to " + newCost); - } else { - _log.info("No change to NTCP Address"); - } - } else { - _log.info("No change to NTCP Address"); - } - return; - } - - // stopListening stops the pumper, readers, and writers, so required even if - // oldAddr == null since startListening starts them all again - // - // really need to fix this so that we can change or create an inbound address - // without tearing down everything - // Especially on disabling the address, we shouldn't tear everything down. - // - _log.warn("Halting NTCP to change address"); - t.stopListening(); - if (newAddr != null) - newAddr.setOptions(newProps); - // Wait for NTCP Pumper to stop so we don't end up with two... - while (t.isAlive()) { - try { Thread.sleep(5*1000); } catch (InterruptedException ie) {} - } - t.restartListening(newAddr); - _log.warn("Changed NTCP Address and started up, address is now " + newAddr); - return; + _manager.externalAddressReceived(Transport.AddressSource.SOURCE_SSU, ip, port); } /* diff --git a/router/java/src/net/i2p/router/transport/Transport.java b/router/java/src/net/i2p/router/transport/Transport.java index 72638f77a7..ffcf0b4ebb 100644 --- a/router/java/src/net/i2p/router/transport/Transport.java +++ b/router/java/src/net/i2p/router/transport/Transport.java @@ -63,8 +63,7 @@ public interface Transport { SOURCE_INTERFACE("local"), /** unused */ SOURCE_CONFIG("config"), - /** unused */ - SOURCE_SSU_PEER("ssu"); + SOURCE_SSU("ssu"); private final String cfgstr; diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 430b57d41f..153e4b358d 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -552,7 +552,7 @@ public abstract class TransportImpl implements Transport { * This implementation does nothing. Transports should override if they want notification. * * @param source defined in Transport.java - * @param ip typ. IPv4 or IPv6 non-local + * @param ip typ. IPv4 or IPv6 non-local; may be null to indicate IPv4 failure or port info only * @param port 0 for unknown or unchanged */ public void externalAddressReceived(AddressSource source, byte[] ip, int port) {} diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index e0fdcc0cfe..6575447003 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -30,7 +30,7 @@ import net.i2p.data.i2np.I2NPMessage; import net.i2p.router.CommSystemFacade; import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; -import static net.i2p.router.transport.Transport.AddressSource.SOURCE_INTERFACE; +import static net.i2p.router.transport.Transport.AddressSource.*; import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.router.transport.ntcp.NTCPTransport; import net.i2p.router.transport.udp.UDPTransport; @@ -120,14 +120,16 @@ public class TransportManager implements TransportEventListener { } /** - * callback from UPnP + * callback from UPnP or SSU * Only tell SSU, it will tell NTCP * */ public void externalAddressReceived(Transport.AddressSource source, byte[] ip, int port) { - Transport t = getTransport(UDPTransport.STYLE); - if (t != null) - t.externalAddressReceived(source, ip, port); + for (Transport t : _transports.values()) { + // don't loop + if (!(source == SOURCE_SSU && t.getStyle().equals(UDPTransport.STYLE))) + t.externalAddressReceived(source, ip, port); + } } /** @@ -385,7 +387,7 @@ public class TransportManager implements TransportEventListener { int port = t.getRequestedPort(); // Use UDP port for NTCP too - see comment in NTCPTransport.getRequestedPort() for why this is here if (t.getStyle().equals(NTCPTransport.STYLE) && port <= 0 && - _context.getBooleanProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_AUTO_PORT)) { + _context.getBooleanProperty(NTCPTransport.PROP_I2NP_NTCP_AUTO_PORT)) { Transport udp = getTransport(UDPTransport.STYLE); if (udp != null) port = t.getRequestedPort(); diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index 22a0d43ff5..c722c34bd3 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -30,12 +30,15 @@ import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; import net.i2p.router.transport.CommSystemFacadeImpl; import net.i2p.router.transport.Transport; +import static net.i2p.router.transport.Transport.AddressSource.*; import net.i2p.router.transport.TransportBid; import net.i2p.router.transport.TransportImpl; import net.i2p.router.transport.crypto.DHSessionKeyBuilder; +import net.i2p.router.transport.udp.UDPTransport; import net.i2p.util.Addresses; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.Log; +import net.i2p.util.OrderedProperties; import net.i2p.util.Translate; /** @@ -62,6 +65,11 @@ public class NTCPTransport extends TransportImpl { */ private final Set _establishing; + public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname"; + public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port"; + public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport"; + public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoip"; + /** this is rarely if ever used, default is to bind to wildcard address */ public static final String PROP_BIND_INTERFACE = "i2np.ntcp.bindInterface"; @@ -471,11 +479,11 @@ public class NTCPTransport extends TransportImpl { } /** - * Only called by CSFI. + * Only called by externalAddressReceived(). * Caller should stop the transport first, then * verify stopped with isAlive() */ - public synchronized void restartListening(RouterAddress addr) { + private synchronized void restartListening(RouterAddress addr) { // try once again to prevent two pumpers which is fatal // we could just return null since the return value is ignored if (_pumper.isAlive()) @@ -528,9 +536,9 @@ public class NTCPTransport extends TransportImpl { // If we are configured with a fixed IP address, // AND it's one of our local interfaces, // bind only to that. - boolean isFixed = _context.getProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_AUTO_IP, "true") + boolean isFixed = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "true") .toLowerCase(Locale.US).equals("false"); - String fixedHost = _context.getProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_HOSTNAME); + String fixedHost = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME); if (isFixed && fixedHost != null) { try { String testAddr = InetAddress.getByName(fixedHost).getHostAddress(); @@ -638,12 +646,8 @@ public class NTCPTransport extends TransportImpl { /** caller must synch on this */ private void configureLocalAddress() { - RouterContext ctx = getContext(); - if (ctx == null) { - System.err.println("NIO transport has no context?"); - } else { // this generally returns null -- see javadoc - RouterAddress ra = CommSystemFacadeImpl.createNTCPAddress(ctx); + RouterAddress ra = createNTCPAddress(); if (ra != null) { NTCPAddress addr = new NTCPAddress(ra); if (addr.getPort() <= 0) { @@ -660,9 +664,200 @@ public class NTCPTransport extends TransportImpl { if (_log.shouldLog(Log.INFO)) _log.info("NTCP address is outbound only"); } - } } + /** + * This only creates an address if the hostname AND port are set in router.config, + * which should be rare. + * Otherwise, notifyReplaceAddress() below takes care of it. + * Note this is called both from above and from NTCPTransport.startListening() + * + * @since IPv6 moved from CSFI + */ + private RouterAddress createNTCPAddress() { + String name = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME); + if ( (name == null) || (name.trim().length() <= 0) || ("null".equals(name)) ) + return null; + int p = _context.getProperty(PROP_I2NP_NTCP_PORT, -1); + if (p <= 0 || p >= 64*1024) + return null; + OrderedProperties props = new OrderedProperties(); + props.setProperty(NTCPAddress.PROP_HOST, name); + props.setProperty(NTCPAddress.PROP_PORT, Integer.toString(p)); + RouterAddress addr = new RouterAddress(STYLE, props, NTCPAddress.DEFAULT_COST); + return addr; + } + + /** + * UDP changed addresses, tell NTCP and restart + * + * @since IPv6 moved from CSFI.notifyReplaceAddress() + */ + @Override + public void externalAddressReceived(AddressSource source, byte[] ip, int port) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Received address: " + Addresses.toString(ip, port) + " from: " + source); + // ignore UPnP for now, get everything from SSU + if (source != SOURCE_SSU) + return; + externalAddressReceived(ip, port); + } + + /** + * UDP changed addresses, tell NTCP and restart + * Port may be set to indicate requested port even if ip is null; + * see CSFI.notifyReplaceAddress() + * + * @since IPv6 moved from CSFI.notifyReplaceAddress() + */ + private synchronized void externalAddressReceived(byte[] ip, int port) { + // FIXME just take first IPv4 address for now + // FIXME if SSU set to hostname, NTCP will be set to IP + RouterAddress oldAddr = getCurrentAddress(false); + if (_log.shouldLog(Log.INFO)) + _log.info("Changing NTCP Address? was " + oldAddr); + + OrderedProperties newProps = new OrderedProperties(); + int cost; + if (oldAddr == null) { + cost = NTCPAddress.DEFAULT_COST; + } else { + cost = oldAddr.getCost(); + newProps.putAll(oldAddr.getOptionsMap()); + } + RouterAddress newAddr = new RouterAddress(STYLE, newProps, cost); + + boolean changed = false; + + // Auto Port Setting + // old behavior (<= 0.7.3): auto-port defaults to false, and true trumps explicit setting + // new behavior (>= 0.7.4): auto-port defaults to true, but explicit setting trumps auto + // TODO rewrite this to operate on ints instead of strings + String oport = newProps.getProperty(NTCPAddress.PROP_PORT); + String nport = null; + String cport = _context.getProperty(PROP_I2NP_NTCP_PORT); + if (cport != null && cport.length() > 0) { + nport = cport; + } else if (_context.getBooleanPropertyDefaultTrue(PROP_I2NP_NTCP_AUTO_PORT)) { + // 0.9.6 change + // This wasn't quite right, as udpAddr is the EXTERNAL port and we really + // want NTCP to bind to the INTERNAL port the first time, + // because if they are different, the NAT is changing them, and + // it probably isn't mapping UDP and TCP the same. + if (port > 0) + // should always be true + nport = Integer.toString(port); + } + if (_log.shouldLog(Log.INFO)) + _log.info("old: " + oport + " config: " + cport + " new: " + nport); + if (nport == null || nport.length() <= 0) + return; + // 0.9.6 change + // Don't have NTCP "chase" SSU's external port, + // as it may change, possibly frequently. + //if (oport == null || ! oport.equals(nport)) { + if (oport == null) { + newProps.setProperty(NTCPAddress.PROP_PORT, nport); + changed = true; + } + + // Auto IP Setting + // old behavior (<= 0.7.3): auto-ip defaults to false, and trumps configured hostname, + // and ignores reachability status - leading to + // "firewalled with inbound TCP enabled" warnings. + // new behavior (>= 0.7.4): auto-ip defaults to true, and explicit setting trumps auto, + // and only takes effect if reachability is OK. + // And new "always" setting ignores reachability status, like + // "true" was in 0.7.3 + String ohost = newProps.getProperty(NTCPAddress.PROP_HOST); + String enabled = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "true").toLowerCase(Locale.US); + String name = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME); + // hostname config trumps auto config + if (name != null && name.length() > 0) + enabled = "false"; + + // assume SSU is happy if the address is non-null + // TODO is this sufficient? + boolean ssuOK = ip != null; + if (_log.shouldLog(Log.INFO)) + _log.info("old: " + ohost + " config: " + name + " auto: " + enabled + " ssuOK? " + ssuOK); + if (enabled.equals("always") || + (Boolean.parseBoolean(enabled) && ssuOK)) { + // ip non-null + String nhost = Addresses.toString(ip); + if (_log.shouldLog(Log.INFO)) + _log.info("old: " + ohost + " config: " + name + " new: " + nhost); + if (nhost == null || nhost.length() <= 0) + return; + if (ohost == null || ! ohost.equalsIgnoreCase(nhost)) { + newProps.setProperty(NTCPAddress.PROP_HOST, nhost); + changed = true; + } + } else if (enabled.equals("false") && + name != null && name.length() > 0 && + !name.equals(ohost) && + nport != null) { + // Host name is configured, and we have a port (either auto or configured) + // but we probably only get here if the port is auto, + // otherwise createNTCPAddress() would have done it already + if (_log.shouldLog(Log.INFO)) + _log.info("old: " + ohost + " config: " + name + " new: " + name); + newProps.setProperty(NTCPAddress.PROP_HOST, name); + changed = true; + } else if (ohost == null || ohost.length() <= 0) { + return; + } else if (Boolean.parseBoolean(enabled) && !ssuOK) { + // UDP transitioned to not-OK, turn off NTCP address + // This will commonly happen at startup if we were initially OK + // because UPnP was successful, but a subsequent SSU Peer Test determines + // we are still firewalled (SW firewall, bad UPnP indication, etc.) + if (_log.shouldLog(Log.INFO)) + _log.info("old: " + ohost + " config: " + name + " new: null"); + newAddr = null; + changed = true; + } + + if (!changed) { + if (oldAddr != null) { + int oldCost = oldAddr.getCost(); + int newCost = NTCPAddress.DEFAULT_COST; + if (TransportImpl.ADJUST_COST && !haveCapacity()) + newCost++; + if (newCost != oldCost) { + oldAddr.setCost(newCost); + if (_log.shouldLog(Log.WARN)) + _log.warn("Changing NTCP cost from " + oldCost + " to " + newCost); + } else { + _log.info("No change to NTCP Address"); + } + } else { + _log.info("No change to NTCP Address"); + } + return; + } + + // stopListening stops the pumper, readers, and writers, so required even if + // oldAddr == null since startListening starts them all again + // + // really need to fix this so that we can change or create an inbound address + // without tearing down everything + // Especially on disabling the address, we shouldn't tear everything down. + // + _log.warn("Halting NTCP to change address"); + stopListening(); + if (newAddr != null) + newAddr.setOptions(newProps); + // Wait for NTCP Pumper to stop so we don't end up with two... + while (isAlive()) { + try { Thread.sleep(5*1000); } catch (InterruptedException ie) {} + } + restartListening(newAddr); + _log.warn("Changed NTCP Address and started up, address is now " + newAddr); + return; + } + + + /** * If we didn't used to be forwarded, and we have an address, * and we are configured to use UPnP, update our RouterAddress @@ -698,7 +893,7 @@ public class NTCPTransport extends TransportImpl { // from here, so we do it in TransportManager. // if (Boolean.valueOf(_context.getProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_AUTO_PORT)).booleanValue()) // return foo; - return _context.getProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_PORT, -1); + return _context.getProperty(PROP_I2NP_NTCP_PORT, -1); } /** diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 1751bf453e..983b4f5907 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -155,7 +155,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority public static final String PROP_SOURCES = "i2np.udp.addressSources"; public static final String DEFAULT_SOURCES = SOURCE_INTERFACE.toConfigString() + ',' + SOURCE_UPNP.toConfigString() + ',' + - SOURCE_SSU_PEER.toConfigString(); + SOURCE_SSU.toConfigString(); /** remember IP changes */ public static final String PROP_IP= "i2np.lastIP"; public static final String PROP_IP_CHANGE = "i2np.lastIPChange"; @@ -563,13 +563,15 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * Not for info received from peers - see externalAddressReceived(Hash, ip, port) * * @param source as defined in Transport.SOURCE_xxx - * @param ip publicly routable IPv4 or IPv6 + * @param ip publicly routable IPv4 or IPv6, null ok * @param port 0 if unknown */ @Override public void externalAddressReceived(Transport.AddressSource source, byte[] ip, int port) { if (_log.shouldLog(Log.WARN)) _log.warn("Received address: " + Addresses.toString(ip, port) + " from: " + source); + if (ip == null) + return; if (source == SOURCE_INTERFACE && ip.length == 16) { // must be set before isValid() call _haveIPv6Address = true; From c76c80043f234c24ced6570937191d3c5437ca24 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 9 May 2013 17:32:29 +0000 Subject: [PATCH 13/38] * NTCP: - Drop NTCPAddress, just use RouterAddress - Drop _myAddress field, use super's currentAddress --- .../router/transport/ntcp/EventPumper.java | 7 +- .../router/transport/ntcp/NTCPAddress.java | 124 ------------------ .../router/transport/ntcp/NTCPConnection.java | 7 +- .../router/transport/ntcp/NTCPTransport.java | 92 ++++++------- .../router/transport/udp/UDPTransport.java | 1 - 5 files changed, 55 insertions(+), 176 deletions(-) delete mode 100644 router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java diff --git a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java index 98a3127e41..075a6277fb 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java @@ -20,6 +20,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; +import net.i2p.data.RouterAddress; import net.i2p.data.RouterIdentity; import net.i2p.router.CommSystemFacade; import net.i2p.router.RouterContext; @@ -778,9 +779,9 @@ class EventPumper implements Runnable { key.attach(con); con.setKey(key); try { - NTCPAddress naddr = con.getRemoteAddress(); - if (naddr.getPort() <= 0) - throw new IOException("Invalid NTCP address: " + naddr); + RouterAddress naddr = con.getRemoteAddress(); + if (naddr.getPort() <= 0) + throw new IOException("Invalid NTCP address: " + naddr); InetSocketAddress saddr = new InetSocketAddress(naddr.getHost(), naddr.getPort()); boolean connected = con.getChannel().connect(saddr); if (connected) { diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java deleted file mode 100644 index 43d051af47..0000000000 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java +++ /dev/null @@ -1,124 +0,0 @@ -package net.i2p.router.transport.ntcp; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.util.Properties; - -import net.i2p.I2PAppContext; -import net.i2p.data.DataHelper; -import net.i2p.data.RouterAddress; -import net.i2p.router.transport.TransportImpl; -import net.i2p.util.Addresses; -import net.i2p.util.Log; - -/** - * Wrap up an address - */ -public class NTCPAddress { - private final int _port; - private final String _host; - //private InetAddress _addr; - /** Port number used in RouterAddress definitions */ - public final static String PROP_PORT = RouterAddress.PROP_PORT; - /** Host name used in RouterAddress definitions */ - public final static String PROP_HOST = RouterAddress.PROP_HOST; - public static final int DEFAULT_COST = 10; - - public NTCPAddress(String host, int port) { - if (host != null) - _host = host.trim(); - else - _host = null; - _port = port; - } - - /* - public NTCPAddress() { - _host = null; - _port = -1; - // _addr = null; - } - - public NTCPAddress(InetAddress addr, int port) { - if (addr != null) - _host = addr.getHostAddress(); - _addr = addr; - _port = port; - } - */ - - public NTCPAddress(RouterAddress addr) { - if (addr == null) { - _host = null; - _port = -1; - return; - } - _host = addr.getOption(PROP_HOST); - _port = addr.getPort(); - } - - public RouterAddress toRouterAddress() { - if ( (_host == null) || (_port <= 0) ) - return null; - - RouterAddress addr = new RouterAddress(); - - addr.setCost(DEFAULT_COST); - //addr.setExpiration(null); - - Properties props = new Properties(); - props.setProperty(PROP_HOST, _host); - props.setProperty(PROP_PORT, ""+_port); - - addr.setOptions(props); - addr.setTransportStyle(NTCPTransport.STYLE); - return addr; - } - - public String getHost() { return _host; } - //public void setHost(String host) { _host = host; } - //public InetAddress getAddress() { return _addr; } - //public void setAddress(InetAddress addr) { _addr = addr; } - public int getPort() { return _port; } - //public void setPort(int port) { _port = port; } - - @Override - public String toString() { return _host + ":" + _port; } - - @Override - public int hashCode() { - int rv = _port; - //if (_addr != null) - // rv += _addr.getHostAddress().hashCode(); - //else - if (_host != null) rv ^= _host.hashCode(); - return rv; - } - - @Override - public boolean equals(Object val) { - if ( (val != null) && (val instanceof NTCPAddress) ) { - NTCPAddress addr = (NTCPAddress)val; - String hostname = null; - if (addr.getHost() != null) - hostname = addr.getHost().trim(); - String ourHost = getHost(); - if (ourHost != null) - ourHost = ourHost.trim(); - return DataHelper.eq(hostname, ourHost) && getPort() == addr.getPort(); - } - return false; - } - - public boolean equals(RouterAddress addr) { - if (addr == null) return false; - return ( (_host.equals(addr.getOption(PROP_HOST))) && - (Integer.toString(_port).equals(addr.getOption(PROP_PORT))) ); - } -} diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java index 96951cc5f3..cc2e6ac29f 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -16,6 +16,7 @@ import java.util.zip.Adler32; import net.i2p.data.Base64; import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; +import net.i2p.data.RouterAddress; import net.i2p.data.RouterIdentity; import net.i2p.data.RouterInfo; import net.i2p.data.SessionKey; @@ -86,7 +87,7 @@ class NTCPConnection { private final NTCPTransport _transport; private final boolean _isInbound; private volatile boolean _closed; - private NTCPAddress _remAddr; + private RouterAddress _remAddr; private RouterIdentity _remotePeer; private long _clockSkew; // in seconds /** @@ -182,7 +183,7 @@ class NTCPConnection { * Create an outbound unconnected NTCP connection * */ - public NTCPConnection(RouterContext ctx, NTCPTransport transport, RouterIdentity remotePeer, NTCPAddress remAddr) { + public NTCPConnection(RouterContext ctx, NTCPTransport transport, RouterIdentity remotePeer, RouterAddress remAddr) { _context = ctx; _log = ctx.logManager().getLog(getClass()); _created = System.currentTimeMillis(); @@ -219,7 +220,7 @@ class NTCPConnection { public boolean isInbound() { return _isInbound; } public boolean isEstablished() { return _established; } public EstablishState getEstablishState() { return _establishState; } - public NTCPAddress getRemoteAddress() { return _remAddr; } + public RouterAddress getRemoteAddress() { return _remAddr; } public RouterIdentity getRemotePeer() { return _remotePeer; } public void setRemotePeer(RouterIdentity ident) { _remotePeer = ident; } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index c722c34bd3..97409073cb 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -55,7 +55,6 @@ public class NTCPTransport extends TransportImpl { private final SharedBid _transientFail; private final Object _conLock; private final Map _conByIdent; - private NTCPAddress _myAddress; private final EventPumper _pumper; private final Reader _reader; private net.i2p.router.transport.ntcp.Writer _writer; @@ -69,6 +68,7 @@ public class NTCPTransport extends TransportImpl { public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port"; public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport"; public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoip"; + public static final int DEFAULT_COST = 10; /** this is rarely if ever used, default is to bind to wildcard address */ public static final String PROP_BIND_INTERFACE = "i2np.ntcp.bindInterface"; @@ -203,10 +203,9 @@ public class NTCPTransport extends TransportImpl { isNew = true; RouterAddress addr = getTargetAddress(target); if (addr != null) { - NTCPAddress naddr = new NTCPAddress(addr); - con = new NTCPConnection(_context, this, ident, naddr); + con = new NTCPConnection(_context, this, ident, addr); if (_log.shouldLog(Log.DEBUG)) - _log.debug("Send on a new con: " + con + " at " + addr + " for " + ih.toBase64()); + _log.debug("Send on a new con: " + con + " at " + addr + " for " + ih); _conByIdent.put(ih, con); } else { _log.error("we bid on a peer who doesn't have an ntcp address? " + target); @@ -326,12 +325,12 @@ public class NTCPTransport extends TransportImpl { if (_log.shouldLog(Log.DEBUG)) _log.debug("slow bid when trying to send to " + peer); if (haveCapacity()) { - if (addr.getCost() > NTCPAddress.DEFAULT_COST) + if (addr.getCost() > DEFAULT_COST) return _slowCostBid; else return _slowBid; } else { - if (addr.getCost() > NTCPAddress.DEFAULT_COST) + if (addr.getCost() > DEFAULT_COST) return _nearCapacityCostBid; else return _nearCapacityBid; @@ -474,14 +473,17 @@ public class NTCPTransport extends TransportImpl { if (_log.shouldLog(Log.WARN)) _log.warn("Starting ntcp transport listening"); startIt(); - configureLocalAddress(); - bindAddress(); + RouterAddress addr = configureLocalAddress(); + if (addr != null) + bindAddress(addr); } /** * Only called by externalAddressReceived(). * Caller should stop the transport first, then * verify stopped with isAlive() + * + * @param addr may be null */ private synchronized void restartListening(RouterAddress addr) { // try once again to prevent two pumpers which is fatal @@ -491,11 +493,8 @@ public class NTCPTransport extends TransportImpl { if (_log.shouldLog(Log.WARN)) _log.warn("Restarting ntcp transport listening"); startIt(); - if (addr == null) - _myAddress = null; - else - _myAddress = new NTCPAddress(addr); - bindAddress(); + if (addr != null) + bindAddress(addr); } /** @@ -526,9 +525,13 @@ public class NTCPTransport extends TransportImpl { return _pumper.isAlive(); } - /** call from synchronized method */ - private RouterAddress bindAddress() { - if (_myAddress != null) { + /** + * call from synchronized method + * @param myAddress new address, may be null + * @return new address or null + */ + private RouterAddress bindAddress(RouterAddress myAddress) { + if (myAddress != null) { InetAddress bindToAddr = null; String bindTo = _context.getProperty(PROP_BIND_INTERFACE); @@ -564,7 +567,7 @@ public class NTCPTransport extends TransportImpl { ServerSocketChannel chan = ServerSocketChannel.open(); chan.configureBlocking(false); - int port = _myAddress.getPort(); + int port = myAddress.getPort(); if (port > 0 && port < 1024) _log.logAlways(Log.WARN, "Specified NTCP port is " + port + ", ports lower than 1024 not recommended"); InetSocketAddress addr = null; @@ -587,11 +590,9 @@ public class NTCPTransport extends TransportImpl { _log.info("Outbound NTCP connections only - no listener configured"); } - if (_myAddress != null) { - RouterAddress rv = _myAddress.toRouterAddress(); - if (rv != null) - replaceAddress(rv); - return rv; + if (myAddress != null) { + replaceAddress(myAddress); + return myAddress; } else { return null; } @@ -644,26 +645,27 @@ public class NTCPTransport extends TransportImpl { //private boolean bindAllInterfaces() { return true; } - /** caller must synch on this */ - private void configureLocalAddress() { + /** + * Generally returns null + * caller must synch on this + */ + private RouterAddress configureLocalAddress() { // this generally returns null -- see javadoc - RouterAddress ra = createNTCPAddress(); - if (ra != null) { - NTCPAddress addr = new NTCPAddress(ra); + RouterAddress addr = createNTCPAddress(); + if (addr != null) { if (addr.getPort() <= 0) { - _myAddress = null; + addr = null; if (_log.shouldLog(Log.ERROR)) _log.error("NTCP address is outbound only, since the NTCP configuration is invalid"); } else { - _myAddress = addr; - replaceAddress(ra); if (_log.shouldLog(Log.INFO)) - _log.info("NTCP address configured: " + _myAddress); + _log.info("NTCP address configured: " + addr); } } else { if (_log.shouldLog(Log.INFO)) _log.info("NTCP address is outbound only"); } + return addr; } /** @@ -682,9 +684,9 @@ public class NTCPTransport extends TransportImpl { if (p <= 0 || p >= 64*1024) return null; OrderedProperties props = new OrderedProperties(); - props.setProperty(NTCPAddress.PROP_HOST, name); - props.setProperty(NTCPAddress.PROP_PORT, Integer.toString(p)); - RouterAddress addr = new RouterAddress(STYLE, props, NTCPAddress.DEFAULT_COST); + props.setProperty(RouterAddress.PROP_HOST, name); + props.setProperty(RouterAddress.PROP_PORT, Integer.toString(p)); + RouterAddress addr = new RouterAddress(STYLE, props, DEFAULT_COST); return addr; } @@ -720,7 +722,7 @@ public class NTCPTransport extends TransportImpl { OrderedProperties newProps = new OrderedProperties(); int cost; if (oldAddr == null) { - cost = NTCPAddress.DEFAULT_COST; + cost = DEFAULT_COST; } else { cost = oldAddr.getCost(); newProps.putAll(oldAddr.getOptionsMap()); @@ -733,7 +735,7 @@ public class NTCPTransport extends TransportImpl { // old behavior (<= 0.7.3): auto-port defaults to false, and true trumps explicit setting // new behavior (>= 0.7.4): auto-port defaults to true, but explicit setting trumps auto // TODO rewrite this to operate on ints instead of strings - String oport = newProps.getProperty(NTCPAddress.PROP_PORT); + String oport = newProps.getProperty(RouterAddress.PROP_PORT); String nport = null; String cport = _context.getProperty(PROP_I2NP_NTCP_PORT); if (cport != null && cport.length() > 0) { @@ -757,7 +759,7 @@ public class NTCPTransport extends TransportImpl { // as it may change, possibly frequently. //if (oport == null || ! oport.equals(nport)) { if (oport == null) { - newProps.setProperty(NTCPAddress.PROP_PORT, nport); + newProps.setProperty(RouterAddress.PROP_PORT, nport); changed = true; } @@ -769,7 +771,7 @@ public class NTCPTransport extends TransportImpl { // and only takes effect if reachability is OK. // And new "always" setting ignores reachability status, like // "true" was in 0.7.3 - String ohost = newProps.getProperty(NTCPAddress.PROP_HOST); + String ohost = newProps.getProperty(RouterAddress.PROP_HOST); String enabled = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "true").toLowerCase(Locale.US); String name = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME); // hostname config trumps auto config @@ -790,7 +792,7 @@ public class NTCPTransport extends TransportImpl { if (nhost == null || nhost.length() <= 0) return; if (ohost == null || ! ohost.equalsIgnoreCase(nhost)) { - newProps.setProperty(NTCPAddress.PROP_HOST, nhost); + newProps.setProperty(RouterAddress.PROP_HOST, nhost); changed = true; } } else if (enabled.equals("false") && @@ -802,7 +804,7 @@ public class NTCPTransport extends TransportImpl { // otherwise createNTCPAddress() would have done it already if (_log.shouldLog(Log.INFO)) _log.info("old: " + ohost + " config: " + name + " new: " + name); - newProps.setProperty(NTCPAddress.PROP_HOST, name); + newProps.setProperty(RouterAddress.PROP_HOST, name); changed = true; } else if (ohost == null || ohost.length() <= 0) { return; @@ -820,7 +822,7 @@ public class NTCPTransport extends TransportImpl { if (!changed) { if (oldAddr != null) { int oldCost = oldAddr.getCost(); - int newCost = NTCPAddress.DEFAULT_COST; + int newCost = DEFAULT_COST; if (TransportImpl.ADJUST_COST && !haveCapacity()) newCost++; if (newCost != oldCost) { @@ -879,11 +881,11 @@ public class NTCPTransport extends TransportImpl { } /** - * @return current port, else NTCP configured port, else -1 (but not UDP port if auto) + * @return current IPv4 port, else NTCP configured port, else -1 (but not UDP port if auto) */ @Override public int getRequestedPort() { - NTCPAddress addr = _myAddress; + RouterAddress addr = getCurrentAddress(false); if (addr != null) { int port = addr.getPort(); if (port > 0) @@ -907,7 +909,8 @@ public class NTCPTransport extends TransportImpl { */ @Override public short getReachabilityStatus() { - if (isAlive() && _myAddress != null) { + // If we have an IPv4 address + if (isAlive() && getCurrentAddress(false) != null) { for (NTCPConnection con : _conByIdent.values()) { if (con.isInbound()) return CommSystemFacade.STATUS_OK; @@ -936,7 +939,6 @@ public class NTCPTransport extends TransportImpl { con.close(); } NTCPConnection.releaseResources(); - // will this work? replaceAddress(null); } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 983b4f5907..015f81154a 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -1577,7 +1577,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority public void stopListening() { shutdown(); - // will this work? replaceAddress(null); } From 5e953b0857e5bb5051538b27f45d311813887112 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 10 May 2013 18:34:02 +0000 Subject: [PATCH 14/38] * Fixes for IPv4 and other breakage after basic testing * Catch exception from UPnP callback * Log tweaks --- .../router/transport/TransportManager.java | 12 ++++--- .../src/net/i2p/router/transport/UPnP.java | 6 +++- .../router/transport/ntcp/NTCPTransport.java | 8 ++--- .../i2p/router/transport/udp/ACKSender.java | 2 +- .../router/transport/udp/UDPTransport.java | 35 +++++++++++++------ 5 files changed, 42 insertions(+), 21 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index 6575447003..4a1f10adad 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -272,8 +272,8 @@ public class TransportManager implements TransportEventListener { if ((tempSkews == null) || (tempSkews.isEmpty())) continue; skews.addAll(tempSkews); } - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Transport manager returning " + skews.size() + " peer clock skews."); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Transport manager returning " + skews.size() + " peer clock skews."); return skews; } @@ -392,6 +392,8 @@ public class TransportManager implements TransportEventListener { if (udp != null) port = t.getRequestedPort(); } + if (port > 0) + rv.add(new Port(t.getStyle(), port)); } return rv; } @@ -492,11 +494,11 @@ public class TransportManager implements TransportEventListener { */ public void messageReceived(I2NPMessage message, RouterIdentity fromRouter, Hash fromRouterHash) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("I2NPMessage received: " + message.getClass().getName(), new Exception("Where did I come from again?")); + _log.debug("I2NPMessage received: " + message.getClass().getSimpleName() /*, new Exception("Where did I come from again?") */ ); try { _context.inNetMessagePool().add(message, fromRouter, fromRouterHash); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Added to in pool"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Added to in pool"); } catch (IllegalArgumentException iae) { if (_log.shouldLog(Log.WARN)) _log.warn("Error receiving message", iae); diff --git a/router/java/src/net/i2p/router/transport/UPnP.java b/router/java/src/net/i2p/router/transport/UPnP.java index e8d9dd9a74..8c54886a42 100644 --- a/router/java/src/net/i2p/router/transport/UPnP.java +++ b/router/java/src/net/i2p/router/transport/UPnP.java @@ -821,7 +821,11 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener { fps = new ForwardPortStatus(ForwardPortStatus.PROBABLE_FAILURE, "UPnP port forwarding apparently failed", port.portNumber); } Map map = Collections.singletonMap(port, fps); - forwardCallback.portForwardStatus(map); + try { + forwardCallback.portForwardStatus(map); + } catch (Exception e) { + _log.error("UPnP RPT error", e); + } } } } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index 97409073cb..ba3cbc58bf 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -845,16 +845,16 @@ public class NTCPTransport extends TransportImpl { // without tearing down everything // Especially on disabling the address, we shouldn't tear everything down. // - _log.warn("Halting NTCP to change address"); + if (_log.shouldLog(Log.WARN)) + _log.warn("Halting NTCP to change address"); stopListening(); - if (newAddr != null) - newAddr.setOptions(newProps); // Wait for NTCP Pumper to stop so we don't end up with two... while (isAlive()) { try { Thread.sleep(5*1000); } catch (InterruptedException ie) {} } restartListening(newAddr); - _log.warn("Changed NTCP Address and started up, address is now " + newAddr); + if (_log.shouldLog(Log.WARN)) + _log.warn("Changed NTCP Address and started up, address is now " + newAddr); return; } diff --git a/router/java/src/net/i2p/router/transport/udp/ACKSender.java b/router/java/src/net/i2p/router/transport/udp/ACKSender.java index f1aedda5ae..8fef9c4342 100644 --- a/router/java/src/net/i2p/router/transport/udp/ACKSender.java +++ b/router/java/src/net/i2p/router/transport/udp/ACKSender.java @@ -62,7 +62,7 @@ class ACKSender implements Runnable { public synchronized void shutdown() { _alive = false; - PeerState poison = new PeerState(_context, _transport, null, 0, null, false); + PeerState poison = new PeerState(_context, _transport, new byte[4], 0, null, false); poison.setTheyRelayToUsAs(POISON_PS); _peersToACK.offer(poison); for (int i = 1; i <= 5 && !_peersToACK.isEmpty(); i++) { diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 015f81154a..be0fcce9bd 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -1603,6 +1603,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * @return the new address if changed, else null */ private RouterAddress rebuildExternalAddress() { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("REA1"); return rebuildExternalAddress(true); } @@ -1613,19 +1615,24 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * @return the new address if changed, else null */ private RouterAddress rebuildExternalAddress(boolean allowRebuildRouterInfo) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("REA2 " + allowRebuildRouterInfo); // if the external port is specified, we want to use that to bind to even // if we don't know the external host. int port = _context.getProperty(PROP_EXTERNAL_PORT, -1); byte[] ip = null; + String host = null; if (explicitAddressSpecified()) { - String host = _context.getProperty(PROP_EXTERNAL_HOST); - return rebuildExternalAddress(host, port, allowRebuildRouterInfo); + host = _context.getProperty(PROP_EXTERNAL_HOST); + } else { + RouterAddress cur = getCurrentAddress(false); + if (cur != null) + host = cur.getHost(); } - return rebuildExternalAddress(ip, port, allowRebuildRouterInfo); + return rebuildExternalAddress(host, port, allowRebuildRouterInfo); } - /** * Update our IPv4 or IPv6 address and optionally tell the router to rebuild and republish the router info. * @@ -1636,7 +1643,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * @since IPv6 */ private RouterAddress rebuildExternalAddress(byte[] ip, int port, boolean allowRebuildRouterInfo) { - if (ip == null || isValid(ip)) + if (_log.shouldLog(Log.DEBUG)) + _log.debug("REA3 " + Addresses.toString(ip, port)); + if (ip == null) + return rebuildExternalAddress((String) null, port, allowRebuildRouterInfo); + if (isValid(ip)) return rebuildExternalAddress(Addresses.toString(ip), port, allowRebuildRouterInfo); return null; } @@ -1644,13 +1655,15 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority /** * Update our IPv4 or IPv6 address and optionally tell the router to rebuild and republish the router info. * - * @param host new valid IPv4 or IPv6 or DNS hostname or null - * @param port new valid port or -1 + * @param host new validated IPv4 or IPv6 or DNS hostname or null + * @param port new validated port or 0/-1 * @param allowRebuildRouterInfo whether to tell the router * @return the new address if changed, else null * @since IPv6 */ private RouterAddress rebuildExternalAddress(String host, int port, boolean allowRebuildRouterInfo) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("REA4 " + host + ':' + port); if (_context.router().isHidden()) return null; @@ -1670,7 +1683,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority int found = _introManager.pickInbound(options, PUBLIC_RELAY_COUNT); if (found > 0) { if (_log.shouldLog(Log.INFO)) - _log.info("Picked peers: " + found); + _log.info("Direct? " + directIncluded + " reqd? " + introducersRequired + + " picked introducers: " + found); _introducersSelectedOn = _context.clock().now(); introducersIncluded = true; } else { @@ -1678,7 +1692,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // maybe we should fail to publish an address at all in this case? // YES that would be better if (_log.shouldLog(Log.WARN)) - _log.warn("Need introducers but we don't know any"); + _log.warn("Direct? " + directIncluded + " reqd? " + introducersRequired + + " no introducers"); } } @@ -1710,7 +1725,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority if (wantsRebuild) { if (_log.shouldLog(Log.INFO)) - _log.info("Address rebuilt: " + addr); + _log.info("Address rebuilt: " + addr, new Exception()); replaceAddress(addr); if (allowRebuildRouterInfo) _context.router().rebuildRouterInfo(); From 5e51c6abefd721bc2cb52f8f67226008df2cabd6 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 11 May 2013 19:22:20 +0000 Subject: [PATCH 15/38] * CSFI compile fix * Start transports in a standard order to make testing easier * When transports learn of interface addresses before being started, save them and use them at startup * Pick SSU random port before startListening() and have the TransportManager pass it to NTCP before starting * Only restart NTCP after changing addresses when necessary; prevent thrashing at startup (ticket #459) * Only call rebuildRouterInfo() once at startup * More checking of min/max SSU port config * Invalid SSU bind config no longer fatal * Allow "true" for ipv6 config * log tweaks * javadocs --- .../transport/CommSystemFacadeImpl.java | 5 +- .../net/i2p/router/transport/Transport.java | 40 ++++ .../i2p/router/transport/TransportImpl.java | 32 ++- .../router/transport/TransportManager.java | 33 ++- .../i2p/router/transport/TransportUtil.java | 2 + .../router/transport/ntcp/NTCPTransport.java | 213 +++++++++++++----- .../udp/InboundMessageFragments.java | 1 + .../i2p/router/transport/udp/UDPEndpoint.java | 15 +- .../router/transport/udp/UDPTransport.java | 61 ++++- 9 files changed, 326 insertions(+), 76 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index c8cc315772..5ef9532cf3 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -26,9 +26,6 @@ import net.i2p.data.RouterInfo; import net.i2p.router.CommSystemFacade; import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; -import net.i2p.router.transport.ntcp.NTCPAddress; -import net.i2p.router.transport.ntcp.NTCPTransport; -import net.i2p.router.transport.udp.UDPAddress; import net.i2p.router.transport.udp.UDPTransport; import net.i2p.util.Addresses; import net.i2p.util.Log; @@ -203,6 +200,8 @@ public class CommSystemFacadeImpl extends CommSystemFacade { * UDP changed addresses, tell NTCP and restart * * All the work moved to NTCPTransport.externalAddressReceived() + * + * @param udpAddr may be null; or udpAddr's host/IP may be null */ @Override public void notifyReplaceAddress(RouterAddress udpAddr) { diff --git a/router/java/src/net/i2p/router/transport/Transport.java b/router/java/src/net/i2p/router/transport/Transport.java index ffcf0b4ebb..0c576b193d 100644 --- a/router/java/src/net/i2p/router/transport/Transport.java +++ b/router/java/src/net/i2p/router/transport/Transport.java @@ -76,10 +76,45 @@ public interface Transport { } } + /** + * Notify a transport of an external address change. + * This may be from a local interface, UPnP, a config change, etc. + * This should not be called if the ip didn't change + * (from that source's point of view), or is a local address. + * May be called multiple times for IPv4 or IPv6. + * The transport should also do its own checking on whether to accept + * notifications from this source. + * + * This can be called before startListening() to set an initial address, + * or after the transport is running. + * + * @param source defined in Transport.java + * @param ip typ. IPv4 or IPv6 non-local; may be null to indicate IPv4 failure or port info only + * @param port 0 for unknown or unchanged + */ public void externalAddressReceived(AddressSource source, byte[] ip, int port); + + /** + * Notify a transport of the results of trying to forward a port. + * + * @param port the internal port + * @param externalPort the external port, which for now should always be the same as + * the internal port if the forwarding was successful. + */ public void forwardPortStatus(int port, int externalPort, boolean success, String reason); + + /** + * What INTERNAL port would the transport like to have forwarded by UPnP. + * This can't be passed via getCurrentAddress(), as we have to open the port + * before we can publish the address, and that's the external port anyway. + * + * @return port or -1 for none or 0 for any + */ public int getRequestedPort(); + + /** Who to notify on message availability */ public void setListener(TransportEventListener listener); + public String getStyle(); public int countPeers(); @@ -94,6 +129,11 @@ public interface Transport { public short getReachabilityStatus(); public void recheckReachability(); public boolean isBacklogged(Hash dest); + + /** + * Was the peer UNreachable (outbound only) the last time we tried it? + * This is NOT reset if the peer contacts us and it is never expired. + */ public boolean wasUnreachable(Hash dest); public boolean isUnreachable(Hash peer); diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 153e4b358d..b3d2c224cd 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -10,6 +10,9 @@ package net.i2p.router.transport; import java.io.IOException; import java.io.Writer; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -57,6 +60,7 @@ public abstract class TransportImpl implements Transport { /** map from routerIdentHash to timestamp (Long) that the peer was last unreachable */ private final Map _unreachableEntries; private final Set _wasUnreachableEntries; + private final Set _localAddresses; /** global router ident -> IP */ private static final Map _IPMap; @@ -95,6 +99,7 @@ public abstract class TransportImpl implements Transport { _sendPool = null; _unreachableEntries = new HashMap(16); _wasUnreachableEntries = new ConcurrentHashSet(16); + _localAddresses = new ConcurrentHashSet(4); _context.simpleScheduler().addPeriodicEvent(new CleanupUnreachable(), 2 * UNREACHABLE_PERIOD, UNREACHABLE_PERIOD / 2); } @@ -537,6 +542,26 @@ public abstract class TransportImpl implements Transport { _listener.transportAddressChanged(); } + /** + * Save a local address we were notified about before we started. + * + * @since IPv6 + */ + protected void saveLocalAddress(InetAddress address) { + _localAddresses.add(address); + } + + /** + * Return and then clear all saved local addresses. + * + * @since IPv6 + */ + protected Collection getSavedLocalAddresses() { + List rv = new ArrayList(_localAddresses); + _localAddresses.clear(); + return rv; + } + /** * Notify a transport of an external address change. * This may be from a local interface, UPnP, a config change, etc. @@ -569,9 +594,9 @@ public abstract class TransportImpl implements Transport { public void forwardPortStatus(int port, int externalPort, boolean success, String reason) {} /** - * What port would the transport like to have forwarded by UPnP. + * What INTERNAL port would the transport like to have forwarded by UPnP. * This can't be passed via getCurrentAddress(), as we have to open the port - * before we can publish the address. + * before we can publish the address, and that's the external port anyway. * * @return port or -1 for none or 0 for any */ @@ -590,6 +615,7 @@ public abstract class TransportImpl implements Transport { public boolean isEstablished(Hash dest) { return false; } private static final long UNREACHABLE_PERIOD = 5*60*1000; + public boolean isUnreachable(Hash peer) { long now = _context.clock().now(); synchronized (_unreachableEntries) { @@ -603,6 +629,7 @@ public abstract class TransportImpl implements Transport { } } } + /** called when we can't reach a peer */ /** This isn't very useful since it is cleared when they contact us */ public void markUnreachable(Hash peer) { @@ -612,6 +639,7 @@ public abstract class TransportImpl implements Transport { } markWasUnreachable(peer, true); } + /** called when we establish a peer connection (outbound or inbound) */ public void markReachable(Hash peer, boolean isInbound) { // if *some* transport can reach them, then we shouldn't banlist 'em diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index 4a1f10adad..f60ab8d7c5 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -87,13 +87,24 @@ public class TransportManager implements TransportEventListener { private void configTransports() { boolean enableUDP = _context.getBooleanPropertyDefaultTrue(PROP_ENABLE_UDP); + Transport udp = null; if (enableUDP) { - UDPTransport udp = new UDPTransport(_context, _dhThread); + udp = new UDPTransport(_context, _dhThread); addTransport(udp); initializeAddress(udp); } - if (isNTCPEnabled(_context)) - addTransport(new NTCPTransport(_context, _dhThread)); + if (isNTCPEnabled(_context)) { + Transport ntcp = new NTCPTransport(_context, _dhThread); + addTransport(ntcp); + initializeAddress(ntcp); + if (udp != null) { + // pass along the port SSU is probably going to use + // so that NTCP may bind early + int port = udp.getRequestedPort(); + if (port > 0) + ntcp.externalAddressReceived(SOURCE_CONFIG, null, port); + } + } if (_transports.isEmpty()) _log.log(Log.CRIT, "No transports are enabled"); } @@ -120,8 +131,8 @@ public class TransportManager implements TransportEventListener { } /** - * callback from UPnP or SSU - * Only tell SSU, it will tell NTCP + * Initialize from interfaces, and callback from UPnP or SSU. + * Tell all transports... but don't loop * */ public void externalAddressReceived(Transport.AddressSource source, byte[] ip, int port) { @@ -154,7 +165,17 @@ public class TransportManager implements TransportEventListener { _upnpManager.start(); configTransports(); _log.debug("Starting up the transport manager"); - for (Transport t : _transports.values()) { + // Let's do this in a predictable order to make testing easier + // Start NTCP first so it can get notified from SSU + List tps = new ArrayList(); + Transport tp = getTransport(NTCPTransport.STYLE); + if (tp != null) + tps.add(tp); + tp = getTransport(UDPTransport.STYLE); + if (tp != null) + tps.add(tp); + //for (Transport t : _transports.values()) { + for (Transport t : tps) { t.startListening(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Transport " + t.getStyle() + " started"); diff --git a/router/java/src/net/i2p/router/transport/TransportUtil.java b/router/java/src/net/i2p/router/transport/TransportUtil.java index 64f284041b..ab15c1f2a2 100644 --- a/router/java/src/net/i2p/router/transport/TransportUtil.java +++ b/router/java/src/net/i2p/router/transport/TransportUtil.java @@ -57,6 +57,8 @@ public abstract class TransportUtil { for (IPv6Config cfg : IPv6Config.values()) { BY_NAME.put(cfg.toConfigString(), cfg); } + // alias + BY_NAME.put("true", IPv6Config.IPV6_ENABLED); } public static IPv6Config getIPv6Config(RouterContext ctx, String transportStyle) { diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index ba3cbc58bf..258953a577 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -8,9 +8,11 @@ import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.text.DecimalFormat; import java.text.NumberFormat; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -58,6 +60,10 @@ public class NTCPTransport extends TransportImpl { private final EventPumper _pumper; private final Reader _reader; private net.i2p.router.transport.ntcp.Writer _writer; + private int _ssuPort; + /** synch on this */ + private final Set _endpoints; + /** * list of NTCPConnection of connections not yet established that we * want to remove on establishment or close on timeout @@ -155,6 +161,7 @@ public class NTCPTransport extends TransportImpl { _context.statManager().createRateStat("ntcp.wantsQueuedWrite", "", "ntcp", RATES); //_context.statManager().createRateStat("ntcp.write", "", "ntcp", RATES); _context.statManager().createRateStat("ntcp.writeError", "", "ntcp", RATES); + _endpoints = new HashSet(4); _establishing = new ConcurrentHashSet(16); _conLock = new Object(); _conByIdent = new ConcurrentHashMap(64); @@ -474,27 +481,47 @@ public class NTCPTransport extends TransportImpl { startIt(); RouterAddress addr = configureLocalAddress(); + int port; if (addr != null) - bindAddress(addr); + // probably not set + port = addr.getPort(); + else + // received by externalAddressReceived() from TransportManager + port = _ssuPort; + RouterAddress myAddress = bindAddress(port); + if (myAddress != null) { + replaceAddress(myAddress); + } else if (port > 0) { + for (InetAddress ia : getSavedLocalAddresses()) { + OrderedProperties props = new OrderedProperties(); + props.setProperty(RouterAddress.PROP_HOST, ia.getHostAddress()); + props.setProperty(RouterAddress.PROP_PORT, Integer.toString(port)); + myAddress = new RouterAddress(STYLE, props, DEFAULT_COST); + replaceAddress(myAddress); + } + } + // TransportManager.startListening() calls router.rebuildRouterInfo() } /** * Only called by externalAddressReceived(). - * Caller should stop the transport first, then - * verify stopped with isAlive() + * + * Doesn't actually restart unless addr is non-null and + * the port is different from the current listen port. + * + * If we had interface addresses before, we lost them. * * @param addr may be null */ private synchronized void restartListening(RouterAddress addr) { - // try once again to prevent two pumpers which is fatal - // we could just return null since the return value is ignored - if (_pumper.isAlive()) - return; - if (_log.shouldLog(Log.WARN)) _log.warn("Restarting ntcp transport listening"); - - startIt(); - if (addr != null) - bindAddress(addr); + if (addr != null) { + RouterAddress myAddress = bindAddress(addr.getPort()); + if (myAddress != null) + replaceAddress(myAddress); + else + replaceAddress(addr); + // UDPTransport.rebuildExternalAddress() calls router.rebuildRouterInfo() + } } /** @@ -526,12 +553,18 @@ public class NTCPTransport extends TransportImpl { } /** + * Only does something if myPort > 0 and myPort != current bound port + * (or there's no current port, or the configured interface or hostname changed). + * If we are changing the bound port, this restarts everything, which takes a long time. + * * call from synchronized method - * @param myAddress new address, may be null - * @return new address or null + * + * @param myPort does nothing if <= 0 + * @return new address ONLY if bound to specific address, otherwise null */ - private RouterAddress bindAddress(RouterAddress myAddress) { - if (myAddress != null) { + private RouterAddress bindAddress(int port) { + RouterAddress myAddress = null; + if (port > 0) { InetAddress bindToAddr = null; String bindTo = _context.getProperty(PROP_BIND_INTERFACE); @@ -539,16 +572,7 @@ public class NTCPTransport extends TransportImpl { // If we are configured with a fixed IP address, // AND it's one of our local interfaces, // bind only to that. - boolean isFixed = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "true") - .toLowerCase(Locale.US).equals("false"); - String fixedHost = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME); - if (isFixed && fixedHost != null) { - try { - String testAddr = InetAddress.getByName(fixedHost).getHostAddress(); - if (Addresses.getAddresses().contains(testAddr)) - bindTo = testAddr; - } catch (UnknownHostException uhe) {} - } + bindTo = getFixedHost(); } if (bindTo != null) { @@ -564,43 +588,104 @@ public class NTCPTransport extends TransportImpl { } try { - ServerSocketChannel chan = ServerSocketChannel.open(); - chan.configureBlocking(false); - - int port = myAddress.getPort(); - if (port > 0 && port < 1024) - _log.logAlways(Log.WARN, "Specified NTCP port is " + port + ", ports lower than 1024 not recommended"); - InetSocketAddress addr = null; + InetSocketAddress addr; if(bindToAddr==null) { addr = new InetSocketAddress(port); } else { addr = new InetSocketAddress(bindToAddr, port); if (_log.shouldLog(Log.WARN)) _log.warn("Binding only to " + bindToAddr); + OrderedProperties props = new OrderedProperties(); + props.setProperty(RouterAddress.PROP_HOST, bindTo); + props.setProperty(RouterAddress.PROP_PORT, Integer.toString(port)); + myAddress = new RouterAddress(STYLE, props, DEFAULT_COST); } + if (!_endpoints.isEmpty()) { + // If we are already bound to the new address, OR + // if the host is specified and we are bound to the wildcard on the same port, + // do nothing. Changing config from wildcard to a specified host will + // require a restart. + if (_endpoints.contains(addr) || + (bindToAddr != null && _endpoints.contains(new InetSocketAddress(port)))) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Already listening on " + addr); + return null; + } + // FIXME support multiple binds + // FIXME just close and unregister + stopWaitAndRestart(); + } + if (port < 1024) + _log.logAlways(Log.WARN, "Specified NTCP port is " + port + ", ports lower than 1024 not recommended"); + ServerSocketChannel chan = ServerSocketChannel.open(); + chan.configureBlocking(false); chan.socket().bind(addr); + _endpoints.add(addr); if (_log.shouldLog(Log.INFO)) _log.info("Listening on " + addr); _pumper.register(chan); } catch (IOException ioe) { _log.error("Error listening", ioe); + myAddress = null; } } else { if (_log.shouldLog(Log.INFO)) _log.info("Outbound NTCP connections only - no listener configured"); } - - if (myAddress != null) { - replaceAddress(myAddress); - return myAddress; - } else { - return null; - } + return myAddress; } + /** + * @return configured host or null. Must be one of our local interfaces. + * @since IPv6 moved from bindAddress() + */ + private String getFixedHost() { + boolean isFixed = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "true") + .toLowerCase(Locale.US).equals("false"); + String fixedHost = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME); + if (isFixed && fixedHost != null) { + try { + String testAddr = InetAddress.getByName(fixedHost).getHostAddress(); + // FIXME range of IPv6 addresses + if (Addresses.getAddresses().contains(testAddr)) + return testAddr; + } catch (UnknownHostException uhe) {} + } + return null; + } + + /** + * Caller must sync + * @since IPv6 moved from externalAddressReceived() + */ + private void stopWaitAndRestart() { + if (_log.shouldLog(Log.WARN)) + _log.warn("Halting NTCP to change address"); + stopListening(); + // Wait for NTCP Pumper to stop so we don't end up with two... + while (isAlive()) { + try { Thread.sleep(5*1000); } catch (InterruptedException ie) {} + } + if (_log.shouldLog(Log.WARN)) + _log.warn("Restarting NTCP transport listening"); + startIt(); + } + + /** + * Hook for NTCPConnection + */ Reader getReader() { return _reader; } + + /** + * Hook for NTCPConnection + */ net.i2p.router.transport.ntcp.Writer getWriter() { return _writer; } + public String getStyle() { return STYLE; } + + /** + * Hook for NTCPConnection + */ EventPumper getPumper() { return _pumper; } /** @@ -677,6 +762,7 @@ public class NTCPTransport extends TransportImpl { * @since IPv6 moved from CSFI */ private RouterAddress createNTCPAddress() { + // Fixme doesn't check PROP_BIND_INTERFACE String name = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME); if ( (name == null) || (name.trim().length() <= 0) || ("null".equals(name)) ) return null; @@ -691,7 +777,7 @@ public class NTCPTransport extends TransportImpl { } /** - * UDP changed addresses, tell NTCP and restart + * UDP changed addresses, tell NTCP and (possibly) restart * * @since IPv6 moved from CSFI.notifyReplaceAddress() */ @@ -699,6 +785,23 @@ public class NTCPTransport extends TransportImpl { public void externalAddressReceived(AddressSource source, byte[] ip, int port) { if (_log.shouldLog(Log.WARN)) _log.warn("Received address: " + Addresses.toString(ip, port) + " from: " + source); + if (ip != null && !isPubliclyRoutable(ip)) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid address: " + Addresses.toString(ip, port) + " from: " + source); + return; + } + if (!isAlive()) { + if (source == SOURCE_INTERFACE) { + try { + InetAddress ia = InetAddress.getByAddress(ip); + saveLocalAddress(ia); + } catch (UnknownHostException uhe) {} + } else if (source == SOURCE_CONFIG) { + // save for startListening() + _ssuPort = port; + } + return; + } // ignore UPnP for now, get everything from SSU if (source != SOURCE_SSU) return; @@ -706,10 +809,10 @@ public class NTCPTransport extends TransportImpl { } /** - * UDP changed addresses, tell NTCP and restart - * Port may be set to indicate requested port even if ip is null; - * see CSFI.notifyReplaceAddress() + * UDP changed addresses, tell NTCP and restart. + * Port may be set to indicate requested port even if ip is null. * + * @param ip previously validated * @since IPv6 moved from CSFI.notifyReplaceAddress() */ private synchronized void externalAddressReceived(byte[] ip, int port) { @@ -845,16 +948,16 @@ public class NTCPTransport extends TransportImpl { // without tearing down everything // Especially on disabling the address, we shouldn't tear everything down. // - if (_log.shouldLog(Log.WARN)) - _log.warn("Halting NTCP to change address"); - stopListening(); + //if (_log.shouldLog(Log.WARN)) + // _log.warn("Halting NTCP to change address"); + //stopListening(); // Wait for NTCP Pumper to stop so we don't end up with two... - while (isAlive()) { - try { Thread.sleep(5*1000); } catch (InterruptedException ie) {} - } + //while (isAlive()) { + // try { Thread.sleep(5*1000); } catch (InterruptedException ie) {} + //} restartListening(newAddr); if (_log.shouldLog(Log.WARN)) - _log.warn("Changed NTCP Address and started up, address is now " + newAddr); + _log.warn("Updating NTCP Address with " + newAddr); return; } @@ -929,17 +1032,17 @@ public class NTCPTransport extends TransportImpl { _writer.stopWriting(); _reader.stopReading(); _finisher.stop(); - Map cons = null; + List cons; synchronized (_conLock) { - cons = new HashMap(_conByIdent); + cons = new ArrayList(_conByIdent.values()); _conByIdent.clear(); } - for (Iterator iter = cons.values().iterator(); iter.hasNext(); ) { - NTCPConnection con = (NTCPConnection)iter.next(); + for (NTCPConnection con : cons) { con.close(); } NTCPConnection.releaseResources(); replaceAddress(null); + _endpoints.clear(); } public static final String STYLE = "NTCP"; diff --git a/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java b/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java index 2d551fda98..33b766d6c0 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java @@ -66,6 +66,7 @@ class InboundMessageFragments /*implements UDPTransport.PartialACKSource */{ _ackSender.shutdown(); _messageReceiver.shutdown(); } + public boolean isAlive() { return _alive; } /** diff --git a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java index 2822fe8fbb..7170a7fcb0 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java @@ -108,9 +108,7 @@ class UDPEndpoint { if (port <= 0) { // try random ports rather than just do new DatagramSocket() // so we stay out of the way of other I2P stuff - int minPort = _context.getProperty(PROP_MIN_PORT, MIN_RANDOM_PORT); - int maxPort = _context.getProperty(PROP_MAX_PORT, MAX_RANDOM_PORT); - port = minPort + _context.random().nextInt(maxPort - minPort); + port = selectRandomPort(_context); } try { if (_bindAddress == null) @@ -136,6 +134,17 @@ class UDPEndpoint { return socket; } + /** + * Pick a random port between the configured boundaries + * @since IPv6 + */ + public static int selectRandomPort(RouterContext ctx) { + int minPort = Math.min(65535, Math.max(1, ctx.getProperty(PROP_MIN_PORT, MIN_RANDOM_PORT))); + int maxPort = Math.min(65535, Math.max(minPort, ctx.getProperty(PROP_MAX_PORT, MAX_RANDOM_PORT))); + return minPort + ctx.random().nextInt(1 + maxPort - minPort); + } + + /** call after startup() to get actual port or -1 on startup failure */ public int getListenPort() { return _listenPort; } public UDPSender getSender() { return _sender; } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index be0fcce9bd..339ea5cdf5 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -243,6 +243,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _introducersSelectedOn = -1; _lastInboundReceivedOn = -1; _mtu = PeerState.LARGE_MTU; + setupPort(); _needsRebuild = true; _context.statManager().createRateStat("udp.alreadyConnected", "What is the lifetime of a reestablished session", "udp", RATES); @@ -264,7 +265,26 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _context.simpleScheduler().addPeriodicEvent(new PingIntroducers(), MIN_EXPIRE_TIMEOUT * 3 / 4); } - public synchronized void startup() { + /** + * Pick a port if not previously configured, so that TransportManager may + * call getRequestedPort() before we've started to get a best-guess of what our + * port is going to be, and pass that to NTCP + * + * @since IPv6 + */ + private void setupPort() { + int port = getRequestedPort(); + if (port < 0) { + port = UDPEndpoint.selectRandomPort(_context); + Map changes = new HashMap(); + changes.put(PROP_INTERNAL_PORT, Integer.toString(port)); + changes.put(PROP_EXTERNAL_PORT, Integer.toString(port)); + _context.router().saveConfig(changes, null); + _log.logAlways(Log.INFO, "UDP selected random port " + port); + } + } + + private synchronized void startup() { _fragments.shutdown(); if (_pusher != null) _pusher.shutdown(); @@ -285,11 +305,10 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _introManager.reset(); UDPPacket.clearCache(); + if (_log.shouldLog(Log.WARN)) _log.warn("Starting SSU transport listening"); _introKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]); System.arraycopy(_context.routerHash().getData(), 0, _introKey.getData(), 0, SessionKey.KEYSIZE_BYTES); - rebuildExternalAddress(); - // bind host String bindTo = _context.getProperty(PROP_BIND_INTERFACE); @@ -312,9 +331,10 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority try { bindToAddr = InetAddress.getByName(bindTo); } catch (UnknownHostException uhe) { - _log.log(Log.CRIT, "Invalid SSU bind interface specified [" + bindTo + "]", uhe); - setReachabilityStatus(CommSystemFacade.STATUS_HOSED); - return; + _log.error("Invalid SSU bind interface specified [" + bindTo + "]", uhe); + //setReachabilityStatus(CommSystemFacade.STATUS_HOSED); + //return; + // fall thru... } } @@ -412,6 +432,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _expireEvent.setIsAlive(true); _testEvent.setIsAlive(true); // this queues it for 3-6 minutes in the future... _testEvent.reschedule(10*1000); // lets requeue it for Real Soon + + // set up external addresses + // REA param is false; + // TransportManager.startListening() calls router.rebuildRouterInfo() + if (newPort > 0 && bindToAddr == null) { + for (InetAddress ia : getSavedLocalAddresses()) { + rebuildExternalAddress(ia.getHostAddress(), newPort, false); + } + } + rebuildExternalAddress(false); } public synchronized void shutdown() { @@ -442,6 +472,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority UDPPacket.clearCache(); UDPAddress.clearCache(); } + + /** @since IPv6 */ + private boolean isAlive() { + return _inboundFragments.isAlive(); + } /** * Introduction key that people should use to contact us @@ -581,8 +616,20 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority String sources = _context.getProperty(PROP_SOURCES, DEFAULT_SOURCES); if (!sources.contains(source.toConfigString())) return; - if (!isValid(ip)) + if (!isValid(ip)) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid address: " + Addresses.toString(ip, port) + " from: " + source); return; + } + if (!isAlive()) { + if (source == SOURCE_INTERFACE) { + try { + InetAddress ia = InetAddress.getByAddress(ip); + saveLocalAddress(ia); + } catch (UnknownHostException uhe) {} + } + return; + } if (source == SOURCE_INTERFACE) { // temp prevent multiples if (ip.length == 4) { From 0be3beb30ee303ee51669c82c9c80266ffb02f46 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 11 May 2013 21:39:25 +0000 Subject: [PATCH 16/38] * SSU fixes for per-address and IPv6 MTU * MTU.main() print all interfaces --- .../src/net/i2p/router/transport/udp/MTU.java | 26 +++++++++-- .../i2p/router/transport/udp/PeerState.java | 6 ++- .../router/transport/udp/UDPTransport.java | 43 +++++++++++++++---- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/MTU.java b/router/java/src/net/i2p/router/transport/udp/MTU.java index 9258043f6b..9a8fd3601f 100644 --- a/router/java/src/net/i2p/router/transport/udp/MTU.java +++ b/router/java/src/net/i2p/router/transport/udp/MTU.java @@ -72,11 +72,29 @@ abstract class MTU { /**** public static void main(String args[]) { + System.out.println("Cmd line interfaces:"); + for (int i = 0; i < args.length; i++) { + try { + InetAddress test = InetAddress.getByName(args[i]); + System.out.println("MTU of " + args[i] + " is " + getMTU(test)); + } catch (Exception e) { + e.printStackTrace(); + } + } + System.out.println("All interfaces:"); try { - InetAddress test = InetAddress.getByName(args[0]); - System.out.println("MTU is " + getMTU(test)); - } catch (Exception e) { - e.printStackTrace(); + Enumeration ifcs = NetworkInterface.getNetworkInterfaces(); + if (ifcs != null) { + while (ifcs.hasMoreElements()) { + NetworkInterface ifc = ifcs.nextElement(); + for(Enumeration addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) { + InetAddress addr = addrs.nextElement(); + System.out.println("MTU of " + addr.getHostAddress() + " is " + getMTU(addr)); + } + } + } + } catch (SocketException se) { + System.out.println("no interfaces"); } } ****/ diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java index e04f3b5312..04b9448159 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java @@ -267,7 +267,8 @@ class PeerState { * and so PacketBuilder.buildPacket() works correctly. */ public static final int MIN_MTU = 620; - public static final int MIN_IPV6_MTU = 1280; + /** 1276 */ + public static final int MIN_IPV6_MTU = MTU.rectify(1280); private static final int DEFAULT_MTU = MIN_MTU; /* @@ -319,11 +320,12 @@ class PeerState { if (remoteIP.length == 4) { _mtu = DEFAULT_MTU; _mtuReceive = DEFAULT_MTU; + _largeMTU = transport.getMTU(false); } else { _mtu = MIN_IPV6_MTU; _mtuReceive = MIN_IPV6_MTU; + _largeMTU = transport.getMTU(true); } - _largeMTU = transport.getMTU(); //_mtuLastChecked = -1; _lastACKSend = -1; _rto = INIT_RTO; diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 339ea5cdf5..ca05e879cb 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -82,6 +82,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority private long _lastInboundReceivedOn; private final DHSessionKeyBuilder.Factory _dhFactory; private int _mtu; + private int _mtu_ipv6; + /** * Do we have a public IPv6 address? * TODO periodically update via CSFI.NetMonitor? @@ -243,6 +245,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _introducersSelectedOn = -1; _lastInboundReceivedOn = -1; _mtu = PeerState.LARGE_MTU; + _mtu_ipv6 = PeerState.MIN_IPV6_MTU; setupPort(); _needsRebuild = true; @@ -552,20 +555,29 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority /** * Set the MTU for the socket interface at addr. * @param addr null ok + * @return the mtu * @since 0.9.2 */ - private void setMTU(InetAddress addr) { + private int setMTU(InetAddress addr) { String p = _context.getProperty(PROP_DEFAULT_MTU); if (p != null) { try { _mtu = MTU.rectify(Integer.parseInt(p)); - return; + _mtu_ipv6 = Math.max(_mtu, PeerState.MIN_IPV6_MTU); + return _mtu; } catch (NumberFormatException nfe) {} } int mtu = MTU.getMTU(addr); - if (mtu <= 0) - mtu = PeerState.LARGE_MTU; - _mtu = mtu; + if (addr != null && addr.getAddress().length == 16) { + if (mtu <= 0) + mtu = PeerState.MIN_IPV6_MTU; + _mtu_ipv6 = mtu; + } else { + if (mtu <= 0) + mtu = PeerState.LARGE_MTU; + _mtu = mtu; + } + return mtu; } /** @@ -574,8 +586,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * @return limited to range PeerState.MIN_MTU to PeerState.LARGE_MTU. * @since 0.9.2 */ - int getMTU() { - return _mtu; + int getMTU(boolean ipv6) { + // TODO multiple interfaces of each type + return ipv6 ? _mtu_ipv6 : _mtu; } /** @@ -1751,11 +1764,23 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority options.setProperty(UDPAddress.PROP_CAPACITY, ""+UDPAddress.CAPACITY_TESTING + UDPAddress.CAPACITY_INTRODUCER); // MTU since 0.9.2 - if (_mtu < PeerState.LARGE_MTU) - options.setProperty(UDPAddress.PROP_MTU, Integer.toString(_mtu)); + int mtu; + if (host == null) { + mtu = _mtu; + } else { + try { + InetAddress ia = InetAddress.getByName(host); + mtu = setMTU(ia); + } catch (UnknownHostException uhe) { + mtu = _mtu; + } + } + if (mtu < PeerState.LARGE_MTU) + options.setProperty(UDPAddress.PROP_MTU, Integer.toString(mtu)); if (directIncluded || introducersIncluded) { // This is called via TransportManager.configTransports() before startup(), prevent NPE + // Note that peers won't connect to us without this - see EstablishmentManager if (_introKey != null) options.setProperty(UDPAddress.PROP_INTRO_KEY, _introKey.toBase64()); From fcaebb441676ccd891a20c874f6a5666885b802a Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 11 May 2013 22:11:02 +0000 Subject: [PATCH 17/38] * Fix UPnP address received before startListening(), broken by isAlive() check * log tweakws --- router/java/src/net/i2p/router/transport/TransportImpl.java | 4 +++- .../java/src/net/i2p/router/transport/ntcp/NTCPTransport.java | 2 +- .../java/src/net/i2p/router/transport/udp/UDPTransport.java | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index b3d2c224cd..deebe9c37c 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -527,7 +527,7 @@ public abstract class TransportImpl implements Transport { */ protected void replaceAddress(RouterAddress address) { if (_log.shouldLog(Log.WARN)) - _log.warn("Replacing address with " + address); + _log.warn("Replacing address with " + address, new Exception()); if (address == null) { _currentAddresses.clear(); } else { @@ -538,6 +538,8 @@ public abstract class TransportImpl implements Transport { } _currentAddresses.add(address); } + if (_log.shouldLog(Log.WARN)) + _log.warn(getStyle() + " now has " + _currentAddresses.size() + " addresses"); if (_listener != null) _listener.transportAddressChanged(); } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index 258953a577..c96e1ca49b 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -791,7 +791,7 @@ public class NTCPTransport extends TransportImpl { return; } if (!isAlive()) { - if (source == SOURCE_INTERFACE) { + if (source == SOURCE_INTERFACE || source == SOURCE_UPNP) { try { InetAddress ia = InetAddress.getByAddress(ip); saveLocalAddress(ia); diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index ca05e879cb..457503155b 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -635,7 +635,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return; } if (!isAlive()) { - if (source == SOURCE_INTERFACE) { + if (source == SOURCE_INTERFACE || source == SOURCE_UPNP) { try { InetAddress ia = InetAddress.getByAddress(ip); saveLocalAddress(ia); @@ -1797,7 +1797,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority if (wantsRebuild) { if (_log.shouldLog(Log.INFO)) - _log.info("Address rebuilt: " + addr, new Exception()); + _log.info("Address rebuilt: " + addr); replaceAddress(addr); if (allowRebuildRouterInfo) _context.router().rebuildRouterInfo(); From 6ceea60c9203af4dc18a6502ca7e0fe69db0a446 Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 12 May 2013 00:24:01 +0000 Subject: [PATCH 18/38] addresses: - blocklist 192.88.88.0/24 6to4 anycast - invalidate 2002::/16 --- core/java/src/net/i2p/util/Addresses.java | 2 +- installer/resources/blocklist.txt | 1 + router/java/src/net/i2p/router/transport/TransportUtil.java | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/src/net/i2p/util/Addresses.java b/core/java/src/net/i2p/util/Addresses.java index e54f93a02a..14c8549e07 100644 --- a/core/java/src/net/i2p/util/Addresses.java +++ b/core/java/src/net/i2p/util/Addresses.java @@ -34,7 +34,7 @@ public abstract class Addresses { return !getAddresses(true, false, false).isEmpty(); } - /** @return the first non-local address it finds, or null */ + /** @return the first non-local address IPv4 address it finds, or null */ public static String getAnyAddress() { SortedSet a = getAddresses(); if (!a.isEmpty()) diff --git a/installer/resources/blocklist.txt b/installer/resources/blocklist.txt index 9f9dde18fb..02440c58ee 100644 --- a/installer/resources/blocklist.txt +++ b/installer/resources/blocklist.txt @@ -46,6 +46,7 @@ Friend of the Chinese Floodfill Flooder:159.226.40.3 The Team Cymru Bogon List v6.8 03 FEB 2011:172.16.0.0/12 The Team Cymru Bogon List v6.8 03 FEB 2011:192.0.0.0/24 The Team Cymru Bogon List v6.8 03 FEB 2011:192.0.2.0/24 +6to4 Anycast:192.88.99.0/24 The Team Cymru Bogon List v6.8 03 FEB 2011:192.168.0.0/16 The Team Cymru Bogon List v6.8 03 FEB 2011:198.18.0.0/15 The Team Cymru Bogon List v6.8 03 FEB 2011:198.51.100.0/24 diff --git a/router/java/src/net/i2p/router/transport/TransportUtil.java b/router/java/src/net/i2p/router/transport/TransportUtil.java index ab15c1f2a2..86f081daa4 100644 --- a/router/java/src/net/i2p/router/transport/TransportUtil.java +++ b/router/java/src/net/i2p/router/transport/TransportUtil.java @@ -111,6 +111,9 @@ public abstract class TransportUtil { return true; // or at least possible to be true } else if (addr.length == 16) { if (allowIPv6) { + // disallow 2002::/16 (6to4 RFC 3056) + if (addr[0] == 0x20 && addr[1] == 0x02) + return false; try { InetAddress ia = InetAddress.getByAddress(addr); return From 1b38a6478b3608fa6cd734d80678a482fc79c6fa Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 12 May 2013 14:44:42 +0000 Subject: [PATCH 19/38] UPnP: - Pass device IP back in forward port callback - Only declare success if forwarded IP is public NTCP: Bad bind config not fatal GeoIP: - Use cached IP in RouterAddresses - Use both NTCP and SSU addresses - Skip IPv6 for now Blocklist: - Add IPv6 in-memory single list - Limit in-memory single list size - Fix dup check in getAddresses() --- router/java/src/net/i2p/router/Blocklist.java | 123 +++++++++++++++--- .../transport/CommSystemFacadeImpl.java | 31 +++-- .../net/i2p/router/transport/Transport.java | 3 +- .../i2p/router/transport/TransportImpl.java | 13 +- .../router/transport/TransportManager.java | 6 +- .../net/i2p/router/transport/UPnPManager.java | 4 +- .../router/transport/ntcp/NTCPTransport.java | 11 +- .../router/transport/udp/UDPTransport.java | 6 +- 8 files changed, 151 insertions(+), 46 deletions(-) diff --git a/router/java/src/net/i2p/router/Blocklist.java b/router/java/src/net/i2p/router/Blocklist.java index 5f8112cc07..3a28bd85a6 100644 --- a/router/java/src/net/i2p/router/Blocklist.java +++ b/router/java/src/net/i2p/router/Blocklist.java @@ -11,10 +11,12 @@ import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.Writer; +import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -31,6 +33,7 @@ import net.i2p.data.RouterInfo; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.util.Addresses; import net.i2p.util.ConcurrentHashSet; +import net.i2p.util.LHMCache; import net.i2p.util.Log; import net.i2p.util.Translate; @@ -62,6 +65,8 @@ import net.i2p.util.Translate; * banlist it forever, then go back to the file to get the original * entry so we can add the reason to the banlist text. * + * On-disk blocklist supports IPv4 only. + * In-memory supports both IPv4 and IPv6. */ public class Blocklist { private final Log _log; @@ -72,8 +77,20 @@ public class Blocklist { private Entry _wrapSave; private final Set _inProcess = new HashSet(4); private Map _peerBlocklist = new HashMap(4); + + /** + * Limits of transient (in-memory) blocklists. + * Note that it's impossible to prevent clogging up + * the tables by a determined attacker, esp. on IPv6 + */ + private static final int MAX_IPV4_SINGLES = 256; + private static final int MAX_IPV6_SINGLES = 512; + private final Set _singleIPBlocklist = new ConcurrentHashSet(4); - + private final Map _singleIPv6Blocklist = new LHMCache(MAX_IPV6_SINGLES); + + private static final Object DUMMY = Integer.valueOf(0); + public Blocklist(RouterContext context) { _context = context; _log = context.logManager().getLog(Blocklist.class); @@ -437,6 +454,8 @@ public class Blocklist { * Maintain a simple in-memory single-IP blocklist * This is used for new additions, NOT for the main list * of IP ranges read in from the file. + * + * @param ip IPv4 or IPv6 */ public void add(String ip) { byte[] pib = Addresses.getIP(ip); @@ -448,16 +467,24 @@ public class Blocklist { * Maintain a simple in-memory single-IP blocklist * This is used for new additions, NOT for the main list * of IP ranges read in from the file. + * + * @param ip IPv4 or IPv6 */ public void add(byte ip[]) { - if (ip.length != 4) - return; - if (add(toInt(ip))) - if (_log.shouldLog(Log.WARN)) - _log.warn("Adding IP to blocklist: " + Addresses.toString(ip)); + boolean rv; + if (ip.length == 4) + rv = add(toInt(ip)); + else if (ip.length == 16) + rv = add(new BigInteger(1, ip)); + else + rv = false; + if (rv && _log.shouldLog(Log.WARN)) + _log.warn("Adding IP to blocklist: " + Addresses.toString(ip)); } private boolean add(int ip) { + if (_singleIPBlocklist.size() >= MAX_IPV4_SINGLES) + return false; return _singleIPBlocklist.add(Integer.valueOf(ip)); } @@ -466,20 +493,41 @@ public class Blocklist { } /** - * this tries to not return duplicates - * but I suppose it could. + * @param ip IPv6 non-negative + * @since IPv6 + */ + private boolean add(BigInteger ip) { + synchronized(_singleIPv6Blocklist) { + return _singleIPv6Blocklist.put(ip, DUMMY) == null; + } + } + + /** + * @param ip IPv6 non-negative + * @since IPv6 + */ + private boolean isOnSingleList(BigInteger ip) { + synchronized(_singleIPv6Blocklist) { + return _singleIPv6Blocklist.get(ip) != null; + } + } + + /** + * Will not contain duplicates. */ private List getAddresses(Hash peer) { - List rv = new ArrayList(1); RouterInfo pinfo = _context.netDb().lookupRouterInfoLocally(peer); - if (pinfo == null) return rv; - byte[] oldpib = null; + if (pinfo == null) + return Collections.EMPTY_LIST; + List rv = new ArrayList(4); // for each peer address for (RouterAddress pa : pinfo.getAddresses()) { byte[] pib = pa.getIP(); if (pib == null) continue; - if (DataHelper.eq(oldpib, pib)) continue; - oldpib = pib; + // O(n**2) + for (int i = 0; i < rv.size(); i++) { + if (DataHelper.eq(rv.get(i), pib)) continue; + } rv.add(pib); } return rv; @@ -491,8 +539,9 @@ public class Blocklist { */ public boolean isBlocklisted(Hash peer) { List ips = getAddresses(peer); - for (Iterator iter = ips.iterator(); iter.hasNext(); ) { - byte ip[] = iter.next(); + if (ips.isEmpty()) + return false; + for (byte[] ip : ips) { if (isBlocklisted(ip)) { if (! _context.banlist().isBanlisted(peer)) // nice knowing you... @@ -505,6 +554,8 @@ public class Blocklist { /** * calling this externally won't banlist the peer, this is just an IP check + * + * @param ip IPv4 or IPv6 */ public boolean isBlocklisted(String ip) { byte[] pib = Addresses.getIP(ip); @@ -514,11 +565,15 @@ public class Blocklist { /** * calling this externally won't banlist the peer, this is just an IP check + * + * @param ip IPv4 or IPv6 */ public boolean isBlocklisted(byte ip[]) { - if (ip.length != 4) - return false; - return isBlocklisted(toInt(ip)); + if (ip.length == 4) + return isBlocklisted(toInt(ip)); + if (ip.length == 16) + return isOnSingleList(new BigInteger(1, ip)); + return false; } /** @@ -760,7 +815,7 @@ public class Blocklist { //out.write("

Banned IPs

"); Set singles = new TreeSet(); singles.addAll(_singleIPBlocklist); - if (!singles.isEmpty()) { + if (!(singles.isEmpty() && _singleIPv6Blocklist.isEmpty())) { out.write(""); @@ -782,6 +837,19 @@ public class Blocklist { out.write(toStr(ip)); out.write("\n"); } + // then IPv6 + if (!_singleIPv6Blocklist.isEmpty()) { + List s6; + synchronized(_singleIPv6Blocklist) { + s6 = new ArrayList(_singleIPv6Blocklist.keySet()); + } + Collections.sort(s6); + for (BigInteger bi : s6) { + out.write("\n"); + } + } out.write("
"); out.write(_("IPs Banned Until Restart")); out.write("
 
"); + out.write(Addresses.toString(toIPBytes(bi))); + out.write(" 
"); } if (_blocklistSize > 0) { @@ -832,6 +900,23 @@ public class Blocklist { out.flush(); } + /** + * Convert a (non-negative) two's complement IP to exactly 16 bytes + * @since IPv6 + */ + private static byte[] toIPBytes(BigInteger bi) { + byte[] ba = bi.toByteArray(); + int len = ba.length; + if (len == 16) + return ba; + byte[] rv = new byte[16]; + if (len < 16) + System.arraycopy(ba, 0, rv, 16 - len, len); + else + System.arraycopy(ba, len - 16, rv, 0, 16); + return rv; + } + /** * Mark a string for extraction by xgettext and translation. * Use this only in static initializers. diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 5ef9532cf3..42dc7c4a9a 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -240,10 +240,10 @@ public class CommSystemFacadeImpl extends CommSystemFacade { RouterInfo ri = _context.netDb().lookupRouterInfoLocally(iter.next()); if (ri == null) continue; - String host = getIPString(ri); - if (host == null) + byte[] ip = getIP(ri); + if (ip == null) continue; - _geoIP.add(host); + _geoIP.add(ip); } _context.simpleScheduler().addPeriodicEvent(new Lookup(), 5000, LOOKUP_TIME); } @@ -287,24 +287,27 @@ public class CommSystemFacadeImpl extends CommSystemFacade { @Override public String getCountry(Hash peer) { byte[] ip = TransportImpl.getIP(peer); - if (ip != null) + // Assume IPv6 doesn't have geoIP for now + if (ip != null && ip.length == 4) return _geoIP.get(ip); RouterInfo ri = _context.netDb().lookupRouterInfoLocally(peer); if (ri == null) return null; - String s = getIPString(ri); - if (s != null) - return _geoIP.get(s); + ip = getIP(ri); + if (ip != null) + return _geoIP.get(ip); return null; } - private String getIPString(RouterInfo ri) { - // use SSU only, it is likely to be an IP not a hostname, - // we don't want to generate a lot of DNS queries at startup - RouterAddress ra = ri.getTargetAddress("SSU"); - if (ra == null) - return null; - return ra.getOption("host"); + private static byte[] getIP(RouterInfo ri) { + // Return first IPv4 we find, any transport + // Assume IPv6 doesn't have geoIP for now + for (RouterAddress ra : ri.getAddresses()) { + byte[] rv = ra.getIP(); + if (rv != null && rv.length == 4) + return rv; + } + return null; } /** full name for a country code, or the code if we don't know the name */ diff --git a/router/java/src/net/i2p/router/transport/Transport.java b/router/java/src/net/i2p/router/transport/Transport.java index 0c576b193d..954b3d1d7e 100644 --- a/router/java/src/net/i2p/router/transport/Transport.java +++ b/router/java/src/net/i2p/router/transport/Transport.java @@ -97,11 +97,12 @@ public interface Transport { /** * Notify a transport of the results of trying to forward a port. * + * @param ip may be null * @param port the internal port * @param externalPort the external port, which for now should always be the same as * the internal port if the forwarding was successful. */ - public void forwardPortStatus(int port, int externalPort, boolean success, String reason); + public void forwardPortStatus(byte[] ip, int port, int externalPort, boolean success, String reason); /** * What INTERNAL port would the transport like to have forwarded by UPnP. diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index deebe9c37c..95a4e51c8f 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -589,11 +589,12 @@ public abstract class TransportImpl implements Transport { * * This implementation does nothing. Transports should override if they want notification. * + * @param ip may be null * @param port the internal port * @param externalPort the external port, which for now should always be the same as * the internal port if the forwarding was successful. */ - public void forwardPortStatus(int port, int externalPort, boolean success, String reason) {} + public void forwardPortStatus(byte[] ip, int port, int externalPort, boolean success, String reason) {} /** * What INTERNAL port would the transport like to have forwarded by UPnP. @@ -691,6 +692,11 @@ public abstract class TransportImpl implements Transport { _log.info(this.getStyle() + " setting wasUnreachable to " + yes + " for " + peer); } + /** + * IP of the peer from the last connection (in or out, any transport). + * + * @param IPv4 or IPv6, non-null + */ public void setIP(Hash peer, byte[] ip) { byte[] old; synchronized (_IPMap) { @@ -700,6 +706,11 @@ public abstract class TransportImpl implements Transport { _context.commSystem().queueLookup(ip); } + /** + * IP of the peer from the last connection (in or out, any transport). + * + * @return IPv4 or IPv6 or null + */ public static byte[] getIP(Hash peer) { synchronized (_IPMap) { return _IPMap.get(peer); diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index f60ab8d7c5..974b75696a 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -147,10 +147,10 @@ public class TransportManager implements TransportEventListener { * callback from UPnP * */ - public void forwardPortStatus(String style, int port, int externalPort, boolean success, String reason) { + public void forwardPortStatus(String style, byte[] ip, int port, int externalPort, boolean success, String reason) { Transport t = getTransport(style); if (t != null) - t.forwardPortStatus(port, externalPort, success, reason); + t.forwardPortStatus(ip, port, externalPort, success, reason); } public synchronized void startListening() { @@ -351,6 +351,8 @@ public class TransportManager implements TransportEventListener { * * For blocking purposes, etc. it's worth checking both * the netDb addresses and this address. + * + * @return IPv4 or IPv6 or null */ public byte[] getIP(Hash dest) { return TransportImpl.getIP(dest); diff --git a/router/java/src/net/i2p/router/transport/UPnPManager.java b/router/java/src/net/i2p/router/transport/UPnPManager.java index ba4d3df5fc..b3fb90cc2b 100644 --- a/router/java/src/net/i2p/router/transport/UPnPManager.java +++ b/router/java/src/net/i2p/router/transport/UPnPManager.java @@ -153,6 +153,7 @@ class UPnPManager { if (_log.shouldLog(Log.DEBUG)) _log.debug("UPnP Callback:"); + byte[] ipaddr = null; DetectedIP[] ips = _upnp.getAddress(); if (ips != null) { for (DetectedIP ip : ips) { @@ -164,6 +165,7 @@ class UPnPManager { _detectedAddress = ip.publicAddress; _manager.externalAddressReceived(SOURCE_UPNP, _detectedAddress.getAddress(), 0); } + ipaddr = ip.publicAddress.getAddress(); break; } } @@ -186,7 +188,7 @@ class UPnPManager { else continue; boolean success = fps.status >= ForwardPortStatus.MAYBE_SUCCESS; - _manager.forwardPortStatus(style, fp.portNumber, fps.externalPort, success, fps.reasonString); + _manager.forwardPortStatus(style, ipaddr, fp.portNumber, fps.externalPort, success, fps.reasonString); } } } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index c96e1ca49b..ed1de62b08 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -579,17 +579,18 @@ public class NTCPTransport extends TransportImpl { try { bindToAddr = InetAddress.getByName(bindTo); } catch (UnknownHostException uhe) { - _log.log(Log.CRIT, "Invalid NTCP bind interface specified [" + bindTo + "]", uhe); + _log.error("Invalid NTCP bind interface specified [" + bindTo + "]", uhe); // this can be implemented later, just updates some stats // see udp/UDPTransport.java //setReachabilityStatus(CommSystemFacade.STATUS_HOSED); - return null; + //return null; + // fall thru } } try { InetSocketAddress addr; - if(bindToAddr==null) { + if (bindToAddr == null) { addr = new InetSocketAddress(port); } else { addr = new InetSocketAddress(bindToAddr, port); @@ -974,10 +975,10 @@ public class NTCPTransport extends TransportImpl { * NTCP address when it transitions to OK. */ @Override - public void forwardPortStatus(int port, int externalPort, boolean success, String reason) { + public void forwardPortStatus(byte[] ip, int port, int externalPort, boolean success, String reason) { if (_log.shouldLog(Log.WARN)) { if (success) - _log.warn("UPnP has opened the NTCP port: " + port); + _log.warn("UPnP has opened the NTCP port: " + port + " via " + Addresses.toString(ip, externalPort)); else _log.warn("UPnP has failed to open the NTCP port: " + port + " reason: " + reason); } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 457503155b..abc6117144 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -671,14 +671,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * Don't do anything if UPnP claims failure. */ @Override - public void forwardPortStatus(int port, int externalPort, boolean success, String reason) { + public void forwardPortStatus(byte[] ip, int port, int externalPort, boolean success, String reason) { if (_log.shouldLog(Log.WARN)) { if (success) - _log.warn("UPnP has opened the SSU port: " + port + " via external port: " + externalPort); + _log.warn("UPnP has opened the SSU port: " + port + " via " + Addresses.toString(ip, externalPort)); else _log.warn("UPnP has failed to open the SSU port: " + port + " reason: " + reason); } - if (success && getExternalIP() != null) + if (success && ip != null && getExternalIP() != null) setReachabilityStatus(CommSystemFacade.STATUS_OK); } From 7318632db994b25f01384fbdeefd31881b25cd75 Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 12 May 2013 15:23:02 +0000 Subject: [PATCH 20/38] strip scope from returned address strings --- core/java/src/net/i2p/util/Addresses.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/core/java/src/net/i2p/util/Addresses.java b/core/java/src/net/i2p/util/Addresses.java index 14c8549e07..fec88eda88 100644 --- a/core/java/src/net/i2p/util/Addresses.java +++ b/core/java/src/net/i2p/util/Addresses.java @@ -95,7 +95,7 @@ public abstract class Addresses { haveIPv6 = true; if (shouldInclude(allMyIps[i], includeSiteLocal, includeLoopbackAndWildcard, includeIPv6)) - rv.add(allMyIps[i].getHostAddress()); + rv.add(stripScope(allMyIps[i].getHostAddress())); } } } catch (UnknownHostException e) {} @@ -113,7 +113,7 @@ public abstract class Addresses { haveIPv6 = true; if (shouldInclude(addr, includeSiteLocal, includeLoopbackAndWildcard, includeIPv6)) - rv.add(addr.getHostAddress()); + rv.add(stripScope(addr.getHostAddress())); } } } @@ -128,6 +128,17 @@ public abstract class Addresses { return rv; } + /** + * Strip the trailing "%nn" from Inet6Address.getHostAddress() + * @since IPv6 + */ + private static String stripScope(String ip) { + int pct = ip.indexOf("%"); + if (pct > 0) + ip = ip.substring(0, pct); + return ip; + } + private static boolean shouldInclude(InetAddress ia, boolean includeSiteLocal, boolean includeLoopbackAndWildcard, boolean includeIPv6) { return From e332c8bc275f3e73e91aa60e38179f693b6ce725 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 13 May 2013 13:57:15 +0000 Subject: [PATCH 21/38] FloodfillPeerSelector, ProfileOrganizer: Use 8 bytes for IPv6 check Transports: - Add IPv6 column on /peers - Other minor /peers cleanup --- .../kademlia/FloodfillPeerSelector.java | 18 +++++++--- .../router/peermanager/ProfileOrganizer.java | 22 ++++++++++--- .../router/transport/ntcp/NTCPConnection.java | 33 +++++++++++++++++-- .../router/transport/ntcp/NTCPTransport.java | 14 +++++--- .../i2p/router/transport/udp/PeerState.java | 6 ++++ .../router/transport/udp/UDPTransport.java | 25 +++++++++----- 6 files changed, 95 insertions(+), 23 deletions(-) diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java index 23a34c32f9..49ff573e4e 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java @@ -294,7 +294,7 @@ class FloodfillPeerSelector extends PeerSelector { * @since 0.9.5 modified from ProfileOrganizer */ private Set maskedIPSet(Hash peer, RouterInfo pinfo, int mask) { - Set rv = new HashSet(2); + Set rv = new HashSet(4); byte[] commIP = _context.commSystem().getIP(peer); if (commIP != null) rv.add(maskedIP(commIP, mask)); @@ -313,12 +313,22 @@ class FloodfillPeerSelector extends PeerSelector { /** * generate an arbitrary unique value for this ip/mask (mask = 1-4) + * If IPv6, force mask = 8. * @since 0.9.5 copied from ProfileOrganizer */ private static Integer maskedIP(byte[] ip, int mask) { - int rv = 0; - for (int i = 0; i < mask; i++) - rv = (rv << 8) | (ip[i] & 0xff); + int rv = ip[0]; + if (ip.length == 16) { + for (int i = 1; i < 8; i++) { + rv <<= i * 4; + rv ^= ip[i]; + } + } else { + for (int i = 1; i < mask; i++) { + rv <<= 8; + rv ^= ip[i]; + } + } return Integer.valueOf(rv); } diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java index 55b57bae93..4ded9f81c1 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java +++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java @@ -1264,7 +1264,7 @@ public class ProfileOrganizer { * @return an opaque set of masked IPs for this peer */ private Set maskedIPSet(Hash peer, int mask) { - Set rv = new HashSet(2); + Set rv = new HashSet(4); byte[] commIP = _context.commSystem().getIP(peer); if (commIP != null) rv.add(maskedIP(commIP, mask)); @@ -1282,11 +1282,23 @@ public class ProfileOrganizer { return rv; } - /** generate an arbitrary unique value for this ip/mask (mask = 1-4) */ + /** + * generate an arbitrary unique value for this ip/mask (mask = 1-4) + * If IPv6, force mask = 8. + */ private static Integer maskedIP(byte[] ip, int mask) { - int rv = 0; - for (int i = 0; i < mask; i++) - rv = (rv << 8) | (ip[i] & 0xff); + int rv = ip[0]; + if (ip.length == 16) { + for (int i = 1; i < 8; i++) { + rv <<= i * 4; + rv ^= ip[i]; + } + } else { + for (int i = 1; i < mask; i++) { + rv <<= 8; + rv ^= ip[i]; + } + } return Integer.valueOf(rv); } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java index cc2e6ac29f..e18933cc99 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -1,6 +1,7 @@ package net.i2p.router.transport.ntcp; import java.io.IOException; +import java.net.Inet6Address; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; @@ -87,7 +88,7 @@ class NTCPConnection { private final NTCPTransport _transport; private final boolean _isInbound; private volatile boolean _closed; - private RouterAddress _remAddr; + private final RouterAddress _remAddr; private RouterIdentity _remotePeer; private long _clockSkew; // in seconds /** @@ -161,6 +162,7 @@ class NTCPConnection { _log = ctx.logManager().getLog(getClass()); _created = System.currentTimeMillis(); _transport = transport; + _remAddr = null; _chan = chan; _readBufs = new ConcurrentLinkedQueue(); _writeBufs = new ConcurrentLinkedQueue(); @@ -212,15 +214,42 @@ class NTCPConnection { _prevReadBlock = new byte[BLOCK_SIZE]; _transport.establishing(this); } - + + /** + * Valid for inbound; valid for outbound shortly after creation + */ public SocketChannel getChannel() { return _chan; } + + /** + * Valid for inbound; valid for outbound shortly after creation + */ public SelectionKey getKey() { return _conKey; } public void setChannel(SocketChannel chan) { _chan = chan; } public void setKey(SelectionKey key) { _conKey = key; } public boolean isInbound() { return _isInbound; } public boolean isEstablished() { return _established; } + + /** + * @since IPv6 + */ + public boolean isIPv6() { + return _chan != null && + _chan.socket().getInetAddress() instanceof Inet6Address; + } + + /** + * Only valid during establishment; null later + */ public EstablishState getEstablishState() { return _establishState; } + + /** + * Only valid for outbound; null for inbound + */ public RouterAddress getRemoteAddress() { return _remAddr; } + + /** + * Valid for outbound; valid for inbound after handshake + */ public RouterIdentity getRemotePeer() { return _remotePeer; } public void setRemotePeer(RouterIdentity ident) { _remotePeer = ident; } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index ed1de62b08..3be51b3df5 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -1052,7 +1052,7 @@ public class NTCPTransport extends TransportImpl { @Override public void renderStatusHTML(java.io.Writer out, String urlBase, int sortFlags) throws IOException { - TreeSet peers = new TreeSet(getComparator(sortFlags)); + TreeSet peers = new TreeSet(getComparator(sortFlags)); peers.addAll(_conByIdent.values()); long offsetTotal = 0; @@ -1070,6 +1070,7 @@ public class NTCPTransport extends TransportImpl { "\n" + "" + "" + + "" + "" + "" + "" + @@ -1082,8 +1083,7 @@ public class NTCPTransport extends TransportImpl { " \n"); out.write(buf.toString()); buf.setLength(0); - for (Iterator iter = peers.iterator(); iter.hasNext(); ) { - NTCPConnection con = (NTCPConnection)iter.next(); + for (NTCPConnection con : peers) { buf.append("\n"); - buf.append(""); long idleIn = Math.max(now-peer.getLastReceiveTime(), 0); long idleOut = Math.max(now-peer.getLastSendTime(), 0); @@ -2660,23 +2667,24 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority numPeers++; } + if (numPeers > 0) { // buf.append("\n"); - buf.append("" + + buf.append("" + "" + "\n" + "\n" + "\n" + ""); } + } // numPeers > 0 buf.append("
").append(_("Peer")).append("").append(_("Dir")).append("").append(_("IPv6")).append("").append(_("Idle")).append("").append(_("In/Out")).append("").append(_("Up")).append("
"); buf.append(_context.commSystem().renderPeerHTML(con.getRemotePeer().calculateHash())); //byte[] ip = getIP(con.getRemotePeer().calculateHash()); @@ -1094,6 +1094,11 @@ public class NTCPTransport extends TransportImpl { buf.append("\"Inbound\""); else buf.append("\"Outbound\""); + buf.append(""); + if (con.isIPv6()) + buf.append("✓"); + else + buf.append(" "); buf.append(""); buf.append(DataHelper.formatDuration2(con.getTimeSinceReceive())); buf.append(THINSP).append(DataHelper.formatDuration2(con.getTimeSinceSend())); @@ -1142,7 +1147,8 @@ public class NTCPTransport extends TransportImpl { if (!peers.isEmpty()) { // buf.append("

").append(peers.size()).append(' ').append(_("peers")).append("  "); + buf.append("
").append(_("SUMMARY")) + .append(""); buf.append("").append(formatRate(bpsRecv/1024)).append(THINSP).append(formatRate(bpsSend/1024)).append(""); buf.append("").append(DataHelper.formatDuration2(totalUptime/peers.size())); buf.append("").append(DataHelper.formatDuration2(offsetTotal*1000/peers.size())); diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java index 04b9448159..a60af4d324 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java @@ -694,6 +694,12 @@ class PeerState { public int getConcurrentSendWindow() { return _concurrentMessagesAllowed; } public int getConsecutiveSendRejections() { return _consecutiveRejections; } public boolean isInbound() { return _isInbound; } + + /** @since IPv6 */ + public boolean isIPv6() { + return _remoteIP.length == 16; + } + public long getIntroducerTime() { return _lastIntroducerTime; } public void setIntroducerTime() { _lastIntroducerTime = _context.clock().now(); } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index abc6117144..90a22d763b 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -2449,6 +2449,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority appendSortLinks(buf, urlBase, sortFlags, _("Sort by peer hash"), FLAG_ALPHA); buf.append("").append(_("Dir")) + .append("").append(_("IPv6")) .append("").append(_("Idle")).append("
"); appendSortLinks(buf, urlBase, sortFlags, _("Sort by idle inbound"), FLAG_IDLE_IN); buf.append(" / "); @@ -2491,8 +2492,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority out.write(buf.toString()); buf.setLength(0); long now = _context.clock().now(); - for (Iterator iter = peers.iterator(); iter.hasNext(); ) { - PeerState peer = (PeerState)iter.next(); + for (PeerState peer : peers) { if (now-peer.getLastReceiveTime() > 60*60*1000) continue; // don't include old peers @@ -2536,6 +2536,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority //if (ip != null) // buf.append(' ').append(_context.blocklist().toStr(ip)); buf.append(""); + + buf.append("
"); + if (peer.isIPv6()) + buf.append("✓"); + else + buf.append(" "); + buf.append("

").append(_("SUMMARY")).append("
").append(_("SUMMARY")).append(""); buf.append(formatKBps(bpsIn)).append(THINSP).append(formatKBps(bpsOut)); - long x = numPeers > 0 ? uptimeMsTotal/numPeers : 0; + long x = uptimeMsTotal/numPeers; buf.append("").append(DataHelper.formatDuration2(x)); - x = numPeers > 0 ? offsetTotal/numPeers : 0; + x = offsetTotal/numPeers; buf.append("").append(DataHelper.formatDuration2(x)).append(""); - buf.append(numPeers > 0 ? cwinTotal/(numPeers*1024) + "K" : "0K"); + buf.append(cwinTotal/(numPeers*1024) + "K"); buf.append(" "); - buf.append(numPeers > 0 ? DataHelper.formatDuration2(rttTotal/numPeers) : '0'); + buf.append(DataHelper.formatDuration2(rttTotal/numPeers)); //buf.append(" "); buf.append(""); - buf.append(numPeers > 0 ? DataHelper.formatDuration2(rtoTotal/numPeers) : '0'); + buf.append(DataHelper.formatDuration2(rtoTotal/numPeers)); buf.append("").append(_mtu).append(""); buf.append(sendTotal).append("").append(recvTotal).append("").append(resentTotal); @@ -2696,6 +2704,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority buf.append(" pBRH direct: ").append(dir).append(" indirect: ").append(indir); buf.append("
\n"); /***** From e0b25cdcf9b209bfec1df75bb29635dc8544ebbb Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 13 May 2013 20:39:32 +0000 Subject: [PATCH 22/38] remove unused method --- router/java/src/net/i2p/router/transport/TransportImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 95a4e51c8f..b4b6e34a75 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -611,7 +611,6 @@ public abstract class TransportImpl implements Transport { public void renderStatusHTML(Writer out) throws IOException {} public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException { renderStatusHTML(out); } - public RouterContext getContext() { return _context; } public short getReachabilityStatus() { return CommSystemFacade.STATUS_UNKNOWN; } public void recheckReachability() {} public boolean isBacklogged(Hash dest) { return false; } From a19140e18690e43dbb051486590c2860cfc15bbb Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 13 May 2013 20:43:30 +0000 Subject: [PATCH 23/38] GeoIPv6 --- .../src/net/i2p/router/transport/GeoIP.java | 98 ++++-- .../src/net/i2p/router/transport/GeoIPv6.java | 333 ++++++++++++++++++ 2 files changed, 399 insertions(+), 32 deletions(-) create mode 100644 router/java/src/net/i2p/router/transport/GeoIPv6.java diff --git a/router/java/src/net/i2p/router/transport/GeoIP.java b/router/java/src/net/i2p/router/transport/GeoIP.java index 5b6cc813f3..a410bbb42b 100644 --- a/router/java/src/net/i2p/router/transport/GeoIP.java +++ b/router/java/src/net/i2p/router/transport/GeoIP.java @@ -48,8 +48,12 @@ class GeoIP { private final Map _codeToName; /** code to itself to prevent String proliferation */ private final Map _codeCache; + + // In the following structures, an IPv4 IP is stored as a non-negative long, 0 to 2**32 - 1, + // and the first 8 bytes of an IPv6 IP are stored as a signed long. private final Map _IPToCountry; private final Set _pendingSearch; + private final Set _pendingIPv6Search; private final Set _notFound; private final AtomicBoolean _lock; private int _lookupRunCount; @@ -58,10 +62,11 @@ class GeoIP { public GeoIP(RouterContext context) { _context = context; _log = context.logManager().getLog(GeoIP.class); - _codeToName = new ConcurrentHashMap(256); - _codeCache = new ConcurrentHashMap(256); + _codeToName = new ConcurrentHashMap(512); + _codeCache = new ConcurrentHashMap(512); _IPToCountry = new ConcurrentHashMap(); _pendingSearch = new ConcurrentHashSet(); + _pendingIPv6Search = new ConcurrentHashSet(); _notFound = new ConcurrentHashSet(); _lock = new AtomicBoolean(); readCountryFile(); @@ -81,6 +86,7 @@ class GeoIP { _codeCache.clear(); _IPToCountry.clear(); _pendingSearch.clear(); + _pendingIPv6Search.clear(); _notFound.clear(); } @@ -107,6 +113,7 @@ class GeoIP { public void blockingLookup() { if (! _context.getBooleanPropertyDefaultTrue(PROP_GEOIP_ENABLED)) { _pendingSearch.clear(); + _pendingIPv6Search.clear(); return; } int pri = Thread.currentThread().getPriority(); @@ -132,18 +139,29 @@ class GeoIP { // clear the negative cache every few runs, to prevent it from getting too big if (((++_lookupRunCount) % CLEAR) == 0) _notFound.clear(); + // IPv4 Long[] search = _pendingSearch.toArray(new Long[_pendingSearch.size()]); - if (search.length <= 0) - return; _pendingSearch.clear(); - Arrays.sort(search); - String[] countries = readGeoIPFile(search); - - for (int i = 0; i < countries.length; i++) { - if (countries[i] != null) - _IPToCountry.put(search[i], countries[i]); - else - _notFound.add(search[i]); + if (search.length > 0) { + String[] countries = readGeoIPFile(search); + for (int i = 0; i < countries.length; i++) { + if (countries[i] != null) + _IPToCountry.put(search[i], countries[i]); + else + _notFound.add(search[i]); + } + } + // IPv6 + search = _pendingSearch.toArray(new Long[_pendingIPv6Search.size()]); + _pendingIPv6Search.clear(); + if (search.length > 0) { + String[] countries = GeoIPv6.readGeoIPFile(_context, search, _codeCache); + for (int i = 0; i < countries.length; i++) { + if (countries[i] != null) + _IPToCountry.put(search[i], countries[i]); + else + _notFound.add(search[i]); + } } } finally { _lock.set(false); @@ -169,16 +187,16 @@ class GeoIP { * */ private void readCountryFile() { - File GeoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT); - GeoFile = new File(GeoFile, COUNTRY_FILE_DEFAULT); - if (!GeoFile.exists()) { + File geoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT); + geoFile = new File(geoFile, COUNTRY_FILE_DEFAULT); + if (!geoFile.exists()) { if (_log.shouldLog(Log.WARN)) - _log.warn("Country file not found: " + GeoFile.getAbsolutePath()); + _log.warn("Country file not found: " + geoFile.getAbsolutePath()); return; } FileInputStream in = null; try { - in = new FileInputStream(GeoFile); + in = new FileInputStream(geoFile); BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); String line = null; while ( (line = br.readLine()) != null) { @@ -228,11 +246,11 @@ class GeoIP { * */ private String[] readGeoIPFile(Long[] search) { - File GeoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT); - GeoFile = new File(GeoFile, GEOIP_FILE_DEFAULT); - if (!GeoFile.exists()) { + File geoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT); + geoFile = new File(geoFile, GEOIP_FILE_DEFAULT); + if (!geoFile.exists()) { if (_log.shouldLog(Log.WARN)) - _log.warn("GeoIP file not found: " + GeoFile.getAbsolutePath()); + _log.warn("GeoIP file not found: " + geoFile.getAbsolutePath()); return new String[0]; } String[] rv = new String[search.length]; @@ -240,7 +258,7 @@ class GeoIP { long start = _context.clock().now(); FileInputStream in = null; try { - in = new FileInputStream(GeoFile); + in = new FileInputStream(geoFile); String buf = null; BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); while ((buf = br.readLine()) != null && idx < search.length) { @@ -268,7 +286,7 @@ class GeoIP { } } catch (IOException ioe) { if (_log.shouldLog(Log.ERROR)) - _log.error("Error reading the GeoFile", ioe); + _log.error("Error reading the geoFile", ioe); } finally { if (in != null) try { in.close(); } catch (IOException ioe) {} } @@ -307,6 +325,7 @@ class GeoIP { /** * Add to the list needing lookup + * @param ip IPv4 or IPv6 */ public void add(String ip) { byte[] pib = Addresses.getIP(ip); @@ -314,20 +333,28 @@ class GeoIP { add(pib); } + /** + * Add to the list needing lookup + * @param ip IPv4 or IPv6 + */ public void add(byte ip[]) { - if (ip.length != 4) - return; add(toLong(ip)); } + /** see above for ip-to-long mapping */ private void add(long ip) { Long li = Long.valueOf(ip); - if (!(_IPToCountry.containsKey(li) || _notFound.contains(li))) - _pendingSearch.add(li); + if (!(_IPToCountry.containsKey(li) || _notFound.contains(li))) { + if (ip >= 0 && ip < (1L << 32)) + _pendingSearch.add(li); + else + _pendingIPv6Search.add(li); + } } /** * Get the country for an IP from the cache. + * @param ip IPv4 or IPv6 * @return lower-case code, generally two letters, or null. */ public String get(String ip) { @@ -338,23 +365,30 @@ class GeoIP { /** * Get the country for an IP from the cache. + * @param ip IPv4 or IPv6 * @return lower-case code, generally two letters, or null. */ public String get(byte ip[]) { - if (ip.length != 4) - return null; return get(toLong(ip)); } + /** see above for ip-to-long mapping */ private String get(long ip) { return _IPToCountry.get(Long.valueOf(ip)); } + /** see above for ip-to-long mapping */ private static long toLong(byte ip[]) { int rv = 0; - for (int i = 0; i < 4; i++) - rv |= (ip[i] & 0xff) << ((3-i)*8); - return rv & 0xffffffffl; + if (ip.length == 16) { + for (int i = 0; i < 8; i++) + rv |= (ip[i] & 0xffL) << ((7-i)*8); + return rv; + } else { + for (int i = 0; i < 4; i++) + rv |= (ip[i] & 0xff) << ((3-i)*8); + return rv & 0xffffffffl; + } } /** diff --git a/router/java/src/net/i2p/router/transport/GeoIPv6.java b/router/java/src/net/i2p/router/transport/GeoIPv6.java new file mode 100644 index 0000000000..605bd0f38a --- /dev/null +++ b/router/java/src/net/i2p/router/transport/GeoIPv6.java @@ -0,0 +1,333 @@ +package net.i2p.router.transport; +/* + * free (adj.): unencumbered; not under the control of others + * Use at your own risk. + */ + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import net.i2p.I2PAppContext; +import net.i2p.data.DataHelper; +import net.i2p.util.Log; + +/** + * Generate compressed geoipv6.dat.gz file, and + * lookup entries in it. + */ +class GeoIPv6 { + + private static final String GEOIP_DIR_DEFAULT = "geoip"; + private static final String GEOIP_FILE_DEFAULT = "geoipv6.dat.gz"; + private static final String MAGIC = "I2PGeoIPv6\0\001\0\0\0\0"; + private static final String COMMENT = "I2P compressed geoipv6 file. See GeoIPv6.java for format."; + /** includes magic */ + private static final int HEADER_LEN = 256; + + /** + * Lookup search items in the geoip file. + * See below for format. + */ + public static String[] readGeoIPFile(I2PAppContext context, Long[] search, Map codeCache) { + Log log = context.logManager().getLog(GeoIPv6.class); + File geoFile = new File(context.getBaseDir(), GEOIP_DIR_DEFAULT); + geoFile = new File(geoFile, GEOIP_FILE_DEFAULT); + if (!geoFile.exists()) { + if (log.shouldLog(Log.WARN)) + log.warn("GeoIP file not found: " + geoFile.getAbsolutePath()); + return new String[0]; + } + return readGeoIPFile(geoFile, search, codeCache, log); + } + + /** + * Lookup search items in the geoip file. + * See below for format. + */ + private static String[] readGeoIPFile(File geoFile, Long[] search, Map codeCache, Log log) { + String[] rv = new String[search.length]; + int idx = 0; + long start = System.currentTimeMillis(); + InputStream in = null; + try { + in = new GZIPInputStream(new BufferedInputStream(new FileInputStream(geoFile))); + byte[] magic = new byte[MAGIC.length()]; + DataHelper.read(in, magic); + if (!DataHelper.eq(magic, DataHelper.getASCII(MAGIC))) + throw new IOException("Not a IPv6 geoip data file"); + // skip timestamp and comments + in.skip(HEADER_LEN - MAGIC.length()); + byte[] buf = new byte[18]; + while (DataHelper.read(in, buf) == 18 && idx < search.length) { + long ip1 = readLong(buf, 0); + long ip2 = readLong(buf, 8); + while (idx < search.length && search[idx].longValue() < ip1) { + idx++; + } + while (idx < search.length && search[idx].longValue() >= ip1 && search[idx].longValue() <= ip2) { + // written in lower case + String lc = new String(buf, 16, 2, "ISO-8859-1"); + // replace the new string with the identical one from the cache + String cached = codeCache.get(lc); + if (cached == null) + cached = lc; + rv[idx++] = cached; + } + } + } catch (IOException ioe) { + if (log.shouldLog(Log.ERROR)) + log.error("Error reading the geoFile", ioe); + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + + if (log.shouldLog(Log.INFO)) + log.info("GeoIPv6 processing finished, time: " + (System.currentTimeMillis() - start)); + return rv; + } + + + /** + * Read in and parse multiple IPv6 geoip CSV files, + * merge them, and write out a gzipped binary IPv6 geoip file. + * + * Acceptable input formats (IPv6 only): + *
+    *   #comment (# must be in column 1)
+    *   "text IP", "text IP", "bigint IP", "bigint IP", "country code", "country name"
+    *
+ * Quotes and spaces optional. Sorting not required. + * Country code case-insensitive. + * Fields 1, 2, and 5 are used; fields 3, 4, and 6 are ignored. + * This is identical to the format of the MaxMind GeoLite IPv6 file. + * + * Example: + *
+    *   "2001:200::", "2001:200:ffff:ffff:ffff:ffff:ffff:ffff", "42540528726795050063891204319802818560", "42540528806023212578155541913346768895", "JP", "Japan"
+    *
+ * + *
+    * Output format:
+    *   Bytes 0-9: Magic number "I2PGeoIPv6"
+    *   Bytes 10-11: version (0x0001)
+    *   Bytes 12-15 flags (0)
+    *   Bytes 16-23: Date (long)
+    *   Bytes 24-xx: Comment (UTF-8)
+    *   Bytes xx-255: null padding
+    *   Bytes 256-: 18 byte records:
+    *       8 byte from (/64)
+    *       8 byte to (/64)
+    *       2 byte country code LOWER case (ASCII)
+    *   Data must be sorted (SIGNED twos complement), no overlap
+    *
+ * + * SLOW. For preprocessing only! + * + * @return success + */ + private static boolean compressGeoIPv6CSVFiles(List inFiles, File outFile) { + boolean DEBUG = false; + List entries = new ArrayList(20000); + for (File geoFile : inFiles) { + int count = 0; + InputStream in = null; + try { + in = new FileInputStream(geoFile); + String buf = null; + BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); + while ((buf = br.readLine()) != null) { + try { + if (buf.charAt(0) == '#') { + continue; + } + String[] s = buf.split(","); + String ips1 = s[0].replace("\"", "").trim(); + String ips2 = s[1].replace("\"", "").trim(); + byte[] ip1 = InetAddress.getByName(ips1).getAddress(); + byte[] ip2 = InetAddress.getByName(ips2).getAddress(); + String country = s[4].replace("\"", "").trim().toLowerCase(Locale.US); + entries.add(new V6Entry(ip1, ip2, country)); + count++; + } catch (UnknownHostException uhe) { + uhe.printStackTrace(); + } catch (RuntimeException re) { + re.printStackTrace(); + } + } + System.err.println("Read " + count + " entries from " + geoFile); + } catch (IOException ioe) { + ioe.printStackTrace(); + //if (_log.shouldLog(Log.ERROR)) + // _log.error("Error reading the geoFile", ioe); + return false; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + } + Collections.sort(entries); + // merge + V6Entry old = null; + for (int i = 0; i < entries.size(); i++) { + V6Entry e = entries.get(i); + if (DEBUG) + System.out.println("proc " + e.toString()); + if (old != null) { + if (e.from == old.from && e.to == old.to) { + // dup + if (DEBUG) + System.out.println("remove dup " + e); + entries.remove(i); + i--; + continue; + } + if (e.from <= old.to) { + // overlap + // truncate old + if (e.from < old.to) { + V6Entry rewrite = new V6Entry(old.from, e.from - 1, old.cc); + if (DEBUG) + System.out.println("rewrite old to " + rewrite); + entries.set(i - 1, rewrite); + } + if (e.to < old.to) { + // e inside old, add new after e + V6Entry insert = new V6Entry(e.to + 1, old.to, old.cc); + if (DEBUG) + System.out.println("insert " + insert); + int j = i + 1; + while (j < entries.size() && insert.compareTo(entries.get(j)) > 0) { + j++; + } + entries.add(j, insert); + } + } + } + old = e; + } + OutputStream out = null; + try { + out = new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(outFile))); + out.write(DataHelper.getASCII(MAGIC)); + writeLong(out, System.currentTimeMillis()); + byte[] comment = DataHelper.getUTF8(COMMENT); + out.write(comment); + out.write(new byte[256 - (16 + 8 + comment.length)]); + for (V6Entry e : entries) { + writeLong(out, e.from); + writeLong(out, e.to); + out.write(DataHelper.getASCII(e.cc)); + } + System.err.println("Wrote " + entries.size() + " entries to " + outFile); + } catch (IOException ioe) { + ioe.printStackTrace(); + //if (_log.shouldLog(Log.ERROR)) + // _log.error("Error reading the geoFile", ioe); + return false; + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} + } + return true; + } + + /** + * Used to temporarily hold, sort, and merge entries before compressing + */ + private static class V6Entry implements Comparable { + public final long from, to; + public final String cc; + + public V6Entry(byte[] f, byte[] t, String c) { + if (f.length != 16 || t.length != 16 || c.length() != 2) + throw new IllegalArgumentException(); + from = toLong(f); + to = toLong(t); + cc = c; + if (to < from) + throw new IllegalArgumentException(toString()); + } + + public V6Entry(long f, long t, String c) { + from = f; + to = t; + cc = c; + if (t < f) + throw new IllegalArgumentException(toString()); + } + + /** twos complement */ + public int compareTo(V6Entry r) { + if (from < r.from) return -1; + if (r.from < from) return 1; + if (to < r.to) return -1; + if (r.to < to) return 1; + return 0; + } + + @Override + public String toString() { + return "0x" + Long.toHexString(from) + " -> 0x" + Long.toHexString(to) + " : " + cc; + } + } + + private static long toLong(byte ip[]) { + long rv = 0; + for (int i = 0; i < 8; i++) + rv |= (ip[i] & 0xffL) << ((7-i)*8); + return rv; + } + + /** like DataHelper.writeLong(rawStream, 8, value) but allows negative values */ + private static void writeLong(OutputStream rawStream, long value) throws IOException { + for (int i = 56; i >= 0; i -= 8) { + byte cur = (byte) (value >> i); + rawStream.write(cur); + } + } + + /** like DataHelper.readLong(src, offset, 8) but allows negative values */ + private static long readLong(byte[] src, int offset) throws IOException { + long rv = 0; + int limit = offset + 8; + for (int i = offset; i < limit; i++) { + rv <<= 8; + rv |= src[i] & 0xFF; + } + return rv; + } + + public static void main(String args[]) { + if (args.length < 2) { + System.err.println("Usage: GeoIP infile1.csv [infile2.csv...] outfile.dat.gz"); + System.exit(1); + } + List infiles = new ArrayList(); + for (int i = 0; i < args.length - 1; i++) { + infiles.add(new File(args[i])); + } + File outfile = new File(args[args.length - 1]); + boolean success = compressGeoIPv6CSVFiles(infiles, outfile); + if (!success) { + System.err.println("Failed"); + System.exit(1); + } + // readback for testing + readGeoIPFile(outfile, new Long[] { Long.MAX_VALUE }, Collections.EMPTY_MAP, new Log(GeoIPv6.class)); + } +} From febc0a5237834fae9512e398e99e5706f86fdd5c Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 16 May 2013 15:35:23 +0000 Subject: [PATCH 24/38] RouterContext: Fix disabling client manager with i2p.dummyClientFacade=true --- .../src/net/i2p/router/RouterContext.java | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index 990f087da2..5101076a8c 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -38,7 +38,8 @@ import net.i2p.util.I2PProperties.I2PPropertyCallback; */ public class RouterContext extends I2PAppContext { private final Router _router; - private ClientManagerFacadeImpl _clientManagerFacade; + private ClientManagerFacade _clientManagerFacade; + private InternalClientManager _internalClientManager; private ClientMessagePool _clientMessagePool; private JobQueue _jobQueue; private InNetMessagePool _inNetMessagePool; @@ -152,15 +153,29 @@ public class RouterContext extends I2PAppContext { } + /** + * The following properties may be used to replace various parts + * of the context with dummy implementations for testing, by setting + * the property to "true": + *
+     *  i2p.dummyClientFacade
+     *  i2p.dummyNetDb
+     *  i2p.dummyPeerManager
+     *  i2p.dummyTunnelManager
+     *  i2p.vmCommSystem (transport)
+     *
+ */ public synchronized void initAll() { if (_initialized) throw new IllegalStateException(); - if (getBooleanProperty("i2p.dummyClientFacade")) - System.err.println("i2p.dummyClientFacade currently unsupported"); - _clientManagerFacade = new ClientManagerFacadeImpl(this); - // removed since it doesn't implement InternalClientManager for now - //else - // _clientManagerFacade = new DummyClientManagerFacade(this); + if (!getBooleanProperty("i2p.dummyClientFacade")) { + ClientManagerFacadeImpl cmfi = new ClientManagerFacadeImpl(this); + _clientManagerFacade = cmfi; + _internalClientManager = cmfi; + } else { + _clientManagerFacade = new DummyClientManagerFacade(this); + // internal client manager is null + } _clientMessagePool = new ClientMessagePool(this); _jobQueue = new JobQueue(this); _inNetMessagePool = new InNetMessagePool(this); @@ -168,23 +183,23 @@ public class RouterContext extends I2PAppContext { _messageHistory = new MessageHistory(this); _messageRegistry = new OutboundMessageRegistry(this); //_messageStateMonitor = new MessageStateMonitor(this); - if ("false".equals(getProperty("i2p.dummyNetDb", "false"))) + if (!getBooleanProperty("i2p.dummyNetDb")) _netDb = new FloodfillNetworkDatabaseFacade(this); // new KademliaNetworkDatabaseFacade(this); else _netDb = new DummyNetworkDatabaseFacade(this); _keyManager = new KeyManager(this); - if ("false".equals(getProperty("i2p.vmCommSystem", "false"))) + if (!getBooleanProperty("i2p.vmCommSystem")) _commSystem = new CommSystemFacadeImpl(this); else _commSystem = new VMCommSystem(this); _profileOrganizer = new ProfileOrganizer(this); - if ("false".equals(getProperty("i2p.dummyPeerManager", "false"))) + if (!getBooleanProperty("i2p.dummyPeerManager")) _peerManagerFacade = new PeerManagerFacadeImpl(this); else _peerManagerFacade = new DummyPeerManagerFacade(); _profileManager = new ProfileManagerImpl(this); _bandwidthLimiter = new FIFOBandwidthLimiter(this); - if ("false".equals(getProperty("i2p.dummyTunnelManager", "false"))) + if (!getBooleanProperty("i2p.dummyTunnelManager")) _tunnelManager = new TunnelPoolManager(this); else _tunnelManager = new DummyTunnelManagerFacade(); @@ -529,7 +544,7 @@ public class RouterContext extends I2PAppContext { */ @Override public InternalClientManager internalClientManager() { - return _clientManagerFacade; + return _internalClientManager; } /** From fcdf837f334b8f76d994904a870caf77061601bf Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 16 May 2013 17:57:33 +0000 Subject: [PATCH 25/38] * New buildTest and prepTest targets * Fix UDPEndpoint usage in unit tests: - Restore receive() - Handle null UDPTransport * Fix UDPEndpointTestStandalone compilation --- build.xml | 15 ++++++++--- router/java/build.xml | 3 +++ .../src/net/i2p/router/transport/GeoIPv6.java | 2 ++ .../i2p/router/transport/udp/UDPEndpoint.java | 27 +++++++++++++++++-- .../udp/UDPEndpointTestStandalone.java | 6 ++++- 5 files changed, 47 insertions(+), 6 deletions(-) diff --git a/build.xml b/build.xml index 3b82c868c2..6caac71705 100644 --- a/build.xml +++ b/build.xml @@ -1292,20 +1292,29 @@ - + - - + + + + + + + + + + + diff --git a/router/java/build.xml b/router/java/build.xml index f473abf0cb..46ec22e491 100644 --- a/router/java/build.xml +++ b/router/java/build.xml @@ -258,7 +258,10 @@ + + + diff --git a/router/java/src/net/i2p/router/transport/GeoIPv6.java b/router/java/src/net/i2p/router/transport/GeoIPv6.java index 605bd0f38a..93affec888 100644 --- a/router/java/src/net/i2p/router/transport/GeoIPv6.java +++ b/router/java/src/net/i2p/router/transport/GeoIPv6.java @@ -31,6 +31,8 @@ import net.i2p.util.Log; /** * Generate compressed geoipv6.dat.gz file, and * lookup entries in it. + * + * @since IPv6 */ class GeoIPv6 { diff --git a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java index 7170a7fcb0..206eaee7bd 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java @@ -1,5 +1,6 @@ package net.i2p.router.transport.udp; +import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Inet4Address; @@ -25,6 +26,7 @@ class UDPEndpoint { private final boolean _isIPv4, _isIPv6; /** + * @param transport may be null for unit testing ONLY * @param listenPort -1 or the requested port, may not be honored * @param bindAddress null ok */ @@ -49,9 +51,11 @@ class UDPEndpoint { throw new SocketException("SSU Unable to bind to a port on " + _bindAddress); } _sender = new UDPSender(_context, _socket, "UDPSender"); - _receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver"); _sender.startup(); - _receiver.startup(); + if (_transport != null) { + _receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver"); + _receiver.startup(); + } } public synchronized void shutdown() { @@ -157,6 +161,25 @@ class UDPEndpoint { public void send(UDPPacket packet) { _sender.add(packet); } + + /** + * Blocking call to receive the next inbound UDP packet from any peer. + * + * UNIT TESTING ONLY. Direct from the socket. + * In normal operation, UDPReceiver thread injects to PacketHandler queue. + * + * @return null if we have shut down, or on failure + */ + public UDPPacket receive() { + UDPPacket packet = UDPPacket.acquire(_context, true); + try { + _socket.receive(packet.getPacket()); + return packet; + } catch (IOException ioe) { + packet.release(); + return null; + } + } /** * Clear outbound queue, probably in preparation for sending destroy() to everybody. diff --git a/router/java/test/junit/net/i2p/router/transport/udp/UDPEndpointTestStandalone.java b/router/java/test/junit/net/i2p/router/transport/udp/UDPEndpointTestStandalone.java index 5b9d3de59c..6f83404d55 100644 --- a/router/java/test/junit/net/i2p/router/transport/udp/UDPEndpointTestStandalone.java +++ b/router/java/test/junit/net/i2p/router/transport/udp/UDPEndpointTestStandalone.java @@ -38,7 +38,11 @@ public class UDPEndpointTestStandalone { _log.debug("Building " + i); UDPEndpoint endpoint = new UDPEndpoint(_context, null, base + i, null); _endpoints[i] = endpoint; - endpoint.startup(); + try { + endpoint.startup(); + } catch (SocketException se) { + throw new RuntimeException(se); + } I2PThread read = new I2PThread(new TestRead(endpoint), "Test read " + i); I2PThread write = new I2PThread(new TestWrite(endpoint), "Test write " + i); //read.setDaemon(true); From a374f00613e09df69d8909b43ee9994da4aeac28 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 16 May 2013 23:08:06 +0000 Subject: [PATCH 26/38] * Fix UDPEndpointTestStandalone: - init context - Add unit test buildPacket() method to PacketBuilder - Fix NPE in MessageHistory * Minor PacketPusher optimization --- .../src/net/i2p/router/MessageHistory.java | 7 ++- .../router/transport/udp/PacketBuilder.java | 26 +++++++- .../router/transport/udp/PacketPusher.java | 11 ++-- .../i2p/router/transport/udp/UDPSender.java | 7 ++- .../udp/UDPEndpointTestStandalone.java | 62 ++++++++++++------- 5 files changed, 76 insertions(+), 37 deletions(-) diff --git a/router/java/src/net/i2p/router/MessageHistory.java b/router/java/src/net/i2p/router/MessageHistory.java index 2ded9f03b8..66e13952f7 100644 --- a/router/java/src/net/i2p/router/MessageHistory.java +++ b/router/java/src/net/i2p/router/MessageHistory.java @@ -91,8 +91,13 @@ public class MessageHistory { */ public synchronized void initialize(boolean forceReinitialize) { if (!forceReinitialize) return; + Router router = _context.router(); + if (router == null) { + // unit testing, presumably + return; + } - if (_context.router().getRouterInfo() == null) { + if (router.getRouterInfo() == null) { _reinitializeJob.getTiming().setStartAfter(_context.clock().now() + 15*1000); _context.jobQueue().addJob(_reinitializeJob); } else { diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index c1d977a353..523141d2c5 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -156,6 +156,9 @@ class PacketBuilder { */ private static final int MAX_RESEND_ACKS_SMALL = 4; + /** + * @param transport may be null for unit testing only + */ public PacketBuilder(I2PAppContext ctx, UDPTransport transport) { _context = ctx; _transport = transport; @@ -571,7 +574,7 @@ class PacketBuilder { state.prepareSessionCreated(); byte sentIP[] = state.getSentIP(); - if ( (sentIP == null) || (sentIP.length <= 0) || ( (_transport != null) && (!_transport.isValid(sentIP)) ) ) { + if ( (sentIP == null) || (sentIP.length <= 0) || (!_transport.isValid(sentIP))) { if (_log.shouldLog(Log.ERROR)) _log.error("How did our sent IP become invalid? " + state); state.fail(); @@ -651,7 +654,7 @@ class PacketBuilder { int off = HEADER_SIZE; byte toIP[] = state.getSentIP(); - if ( (_transport !=null) && (!_transport.isValid(toIP)) ) { + if (!_transport.isValid(toIP)) { packet.release(); return null; } @@ -1252,7 +1255,7 @@ class PacketBuilder { } /** - * Sends an empty unauthenticated packet for hole punching. + * Creates an empty unauthenticated packet for hole punching. * Parameters must be validated previously. */ public UDPPacket buildHolePunch(InetAddress to, int port) { @@ -1269,6 +1272,23 @@ class PacketBuilder { return packet; } + /** + * TESTING ONLY. + * Creates an arbitrary packet for unit testing. + * Null transport in constructor OK. + * + * @param type 0-15 + * @since IPv6 + */ + public UDPPacket buildPacket(byte[] data, InetAddress to, int port) { + UDPPacket packet = UDPPacket.acquire(_context, false); + byte d[] = packet.getPacket().getData(); + System.arraycopy(data, 0, d, 0, data.length); + packet.getPacket().setLength(data.length); + setTo(packet, to, port); + return packet; + } + /** * Create a new packet and add the flag byte and the time stamp. * Caller should add data starting at HEADER_SIZE. diff --git a/router/java/src/net/i2p/router/transport/udp/PacketPusher.java b/router/java/src/net/i2p/router/transport/udp/PacketPusher.java index f40e05e081..db7fb4ea34 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketPusher.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketPusher.java @@ -59,7 +59,6 @@ class PacketPusher implements Runnable { * @since IPv6 */ public void send(UDPPacket packet) { - boolean handled = false; boolean isIPv4 = packet.getPacket().getAddress().getAddress().length == 4; for (int j = 0; j < _endpoints.size(); j++) { // Find the best endpoint (socket) to send this out. @@ -77,13 +76,11 @@ class PacketPusher implements Runnable { ((!isIPv4) && ep.isIPv6())) { // BLOCKING if queue is full ep.getSender().add(packet); - handled = true; - break; + return; } } - if (!handled) { - _log.error("No endpoint to send " + packet); - packet.release(); - } + // not handled + _log.error("No endpoint to send " + packet); + packet.release(); } } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPSender.java b/router/java/src/net/i2p/router/transport/udp/UDPSender.java index 40d16253c9..aa1affbb57 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPSender.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPSender.java @@ -26,13 +26,16 @@ class UDPSender { private final BlockingQueue _outboundQueue; private volatile boolean _keepRunning; private final Runner _runner; + private final boolean _dummy; + private static final int TYPE_POISON = 99999; - + private static final int MIN_QUEUE_SIZE = 64; private static final int MAX_QUEUE_SIZE = 384; public UDPSender(RouterContext ctx, DatagramSocket socket, String name) { _context = ctx; + _dummy = false; // ctx.commSystem().isDummy(); _log = ctx.logManager().getLog(UDPSender.class); long maxMemory = Runtime.getRuntime().maxMemory(); if (maxMemory == Long.MAX_VALUE) @@ -179,7 +182,7 @@ class UDPSender { _log.error("Dropping large UDP packet " + psz + " bytes: " + packet); return; } - if (_context.commSystem().isDummy()) { + if (_dummy) { // testing // back to the cache packet.release(); diff --git a/router/java/test/junit/net/i2p/router/transport/udp/UDPEndpointTestStandalone.java b/router/java/test/junit/net/i2p/router/transport/udp/UDPEndpointTestStandalone.java index 6f83404d55..5e304f5602 100644 --- a/router/java/test/junit/net/i2p/router/transport/udp/UDPEndpointTestStandalone.java +++ b/router/java/test/junit/net/i2p/router/transport/udp/UDPEndpointTestStandalone.java @@ -1,6 +1,8 @@ package net.i2p.router.transport.udp; +import java.net.InetAddress; import java.net.SocketException; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -18,11 +20,11 @@ import net.i2p.util.Log; * --zab */ public class UDPEndpointTestStandalone { - private RouterContext _context; - private Log _log; + private final RouterContext _context; + private final Log _log; private UDPEndpoint _endpoints[]; - private boolean _beginTest; - private List _sentNotReceived; + private volatile boolean _beginTest; + private final List _sentNotReceived; public UDPEndpointTestStandalone(RouterContext ctx) { _context = ctx; @@ -33,7 +35,7 @@ public class UDPEndpointTestStandalone { public void runTest(int numPeers) { _log.debug("Run test("+numPeers+")"); _endpoints = new UDPEndpoint[numPeers]; - int base = 2000 + _context.random().nextInt(10000); + int base = 44000 + _context.random().nextInt(10000); for (int i = 0; i < numPeers; i++) { _log.debug("Building " + i); UDPEndpoint endpoint = new UDPEndpoint(_context, null, base + i, null); @@ -41,6 +43,7 @@ public class UDPEndpointTestStandalone { try { endpoint.startup(); } catch (SocketException se) { + _log.error("die", se); throw new RuntimeException(se); } I2PThread read = new I2PThread(new TestRead(endpoint), "Test read " + i); @@ -55,7 +58,7 @@ public class UDPEndpointTestStandalone { } private class TestRead implements Runnable { - private UDPEndpoint _endpoint; + private final UDPEndpoint _endpoint; public TestRead(UDPEndpoint peer) { _endpoint = peer; } @@ -87,7 +90,7 @@ public class UDPEndpointTestStandalone { } private class TestWrite implements Runnable { - private UDPEndpoint _endpoint; + private final UDPEndpoint _endpoint; public TestWrite(UDPEndpoint peer) { _endpoint = peer; } @@ -96,8 +99,16 @@ public class UDPEndpointTestStandalone { try { Thread.sleep(2000); } catch (InterruptedException ie) {} } try { Thread.sleep(2000); } catch (InterruptedException ie) {} + PacketBuilder builder = new PacketBuilder(_context, null); + InetAddress localhost = null; + try { + localhost = InetAddress.getLocalHost(); + } catch (UnknownHostException uhe) { + _log.error("die", uhe); + System.exit(0); + } _log.debug("Beginning to write"); - for (int curPacket = 0; curPacket < 10000; curPacket++) { + for (int curPacket = 0; curPacket < 2000; curPacket++) { byte data[] = new byte[1024]; _context.random().nextBytes(data); int curPeer = (curPacket % _endpoints.length); @@ -105,27 +116,21 @@ public class UDPEndpointTestStandalone { curPeer++; if (curPeer >= _endpoints.length) curPeer = 0; - short priority = 1; - long expiration = -1; - UDPPacket packet = UDPPacket.acquire(_context, true); - //try { - if (true) throw new RuntimeException("fixme"); - //packet.initialize(priority, expiration, InetAddress.getLocalHost(), _endpoints[curPeer].getListenPort()); - // Following method is commented out in UDPPacket - //packet.writeData(data, 0, 1024); - packet.getPacket().setLength(1024); + UDPPacket packet = builder.buildPacket(data, localhost, _endpoints[curPeer].getListenPort()); int outstanding = _sentNotReceived.size() + 1; _sentNotReceived.add(new ByteArray(data, 0, 1024)); _log.debug("Sending packet " + curPacket + " with outstanding " + outstanding); + try { _endpoint.send(packet); - //try { Thread.sleep(10); } catch (InterruptedException ie) {} - //} catch (UnknownHostException uhe) { - // _log.error("foo!", uhe); - //} - //if (_log.shouldLog(Log.DEBUG)) { + } catch (Exception e) { + _log.error("die", e); + break; + } + try { Thread.sleep(3); } catch (InterruptedException ie) {} + //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Sent to " + _endpoints[curPeer].getListenPort() + " from " + _endpoint.getListenPort()); - //} } + _log.debug("Done sending packets"); try { Thread.sleep(10*1000); } catch (InterruptedException e) {} System.exit(0); } @@ -137,7 +142,16 @@ public class UDPEndpointTestStandalone { Properties props = new Properties(); props.setProperty("stat.logFile", "udpEndpointTest.stats"); props.setProperty("stat.logFilters", "*"); - UDPEndpointTestStandalone test = new UDPEndpointTestStandalone(new RouterContext(null, props)); + props.setProperty("i2p.dummyClientFacade", "true"); + props.setProperty("i2p.dummyNetDb", "true"); + props.setProperty("i2p.dummyPeerManager", "true"); + props.setProperty("i2p.dummyTunnelManager", "true"); + props.setProperty("i2p.vmCommSystem", "true"); + props.setProperty("i2np.bandwidth.inboundKBytesPerSecond", "9999"); + props.setProperty("i2np.bandwidth.outboundKBytesPerSecond", "9999"); + RouterContext ctx = new RouterContext(null, props); + ctx.initAll(); + UDPEndpointTestStandalone test = new UDPEndpointTestStandalone(ctx); test.runTest(2); } } From be262c6a70f4a60b2258d07a6a17f13369b17c4b Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 17 May 2013 15:11:23 +0000 Subject: [PATCH 27/38] * NetDb: - Add floodfillEnabled() to NetworkDatabaseFacade so callers don't have to cast to FNDF, and Dummy NDF will work - Remove static FNDF.floodfillEnabled() * SSUDemo: cleanups --- .../net/i2p/router/NetworkDatabaseFacade.java | 9 +++++ router/java/src/net/i2p/router/Router.java | 2 +- .../kademlia/ExploreKeySelectorJob.java | 2 +- .../FloodfillNetworkDatabaseFacade.java | 4 +- ...ndleFloodfillDatabaseLookupMessageJob.java | 4 +- ...andleFloodfillDatabaseStoreMessageJob.java | 2 +- .../KademliaNetworkDatabaseFacade.java | 2 +- .../router/networkdb/kademlia/SearchJob.java | 2 +- .../networkdb/kademlia/StartExplorersJob.java | 4 +- .../i2p/router/transport/TransportImpl.java | 3 +- .../router/transport/udp/UDPTransport.java | 3 +- .../test/junit/net/i2p/router/SSUDemo.java | 39 +++++++++++++------ 12 files changed, 48 insertions(+), 28 deletions(-) diff --git a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java index f1fbd1b9fd..4ed10ebe88 100644 --- a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java @@ -77,4 +77,13 @@ public abstract class NetworkDatabaseFacade implements Service { /** @since 0.9 */ public ReseedChecker reseedChecker() { return null; }; + + /** + * For convenience, so users don't have to cast to FNDF, and unit tests using + * Dummy NDF will work. + * + * @return false; FNDF overrides to return actual setting + * @since IPv6 + */ + public boolean floodfillEnabled() { return false; }; } diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 125d2e075c..1ae539ab8d 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -601,7 +601,7 @@ public class Router implements RouterClock.ClockShiftListener { } // if prop set to true, don't tell people we are ff even if we are - if (FloodfillNetworkDatabaseFacade.floodfillEnabled(_context) && + if (_context.netDb().floodfillEnabled() && !_context.getBooleanProperty("router.hideFloodfillParticipant")) ri.addCapability(FloodfillNetworkDatabaseFacade.CAPABILITY_FLOODFILL); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/ExploreKeySelectorJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/ExploreKeySelectorJob.java index 6fa0416603..f11937571a 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/ExploreKeySelectorJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/ExploreKeySelectorJob.java @@ -38,7 +38,7 @@ class ExploreKeySelectorJob extends JobImpl { public String getName() { return "Explore Key Selector Job"; } public void runJob() { - if (((FloodfillNetworkDatabaseFacade)_facade).floodfillEnabled()) { + if (_facade.floodfillEnabled()) { requeue(30*RERUN_DELAY_MS); return; } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java index 8496e9a7f0..61bf6a6e05 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java @@ -263,10 +263,8 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad } } + @Override public boolean floodfillEnabled() { return _floodfillEnabled; } - public static boolean floodfillEnabled(RouterContext ctx) { - return ((FloodfillNetworkDatabaseFacade)ctx.netDb()).floodfillEnabled(); - } /** * @param peer may be null, returns false if null diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseLookupMessageJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseLookupMessageJob.java index 1409ecc2a1..0d03d7d4fe 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseLookupMessageJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseLookupMessageJob.java @@ -38,7 +38,7 @@ public class HandleFloodfillDatabaseLookupMessageJob extends HandleDatabaseLooku */ @Override protected boolean answerAllQueries() { - if (!FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext())) return false; + if (!getContext().netDb().floodfillEnabled()) return false; return FloodfillNetworkDatabaseFacade.isFloodfill(getContext().router().getRouterInfo()); } @@ -52,7 +52,7 @@ public class HandleFloodfillDatabaseLookupMessageJob extends HandleDatabaseLooku super.sendClosest(key, routerInfoSet, toPeer, replyTunnel); // go away, you got the wrong guy, send our RI back unsolicited - if (!FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext())) { + if (!getContext().netDb().floodfillEnabled()) { // We could just call sendData(myhash, myri, toPeer, replyTunnel) but // that would increment the netDb.lookupsHandled and netDb.lookupsMatched stats DatabaseStoreMessage msg = new DatabaseStoreMessage(getContext()); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java index 98a41874b7..a56f23098d 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java @@ -187,7 +187,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl { // flood it if (invalidMessage == null && - FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext()) && + getContext().netDb().floodfillEnabled() && _message.getReplyToken() > 0) { if (wasNew) { // DOS prevention diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index 9dcd923180..65d631e885 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -736,7 +736,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { // As the net grows this won't be sufficient, and we'll have to implement // flushing some from memory, while keeping all on disk. long adjustedExpiration; - if (FloodfillNetworkDatabaseFacade.floodfillEnabled(_context)) + if (floodfillEnabled()) adjustedExpiration = ROUTER_INFO_EXPIRATION_FLOODFILL; else // _kb.size() includes leasesets but that's ok diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java index 7df231726f..80815ea820 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java @@ -134,7 +134,7 @@ class SearchJob extends JobImpl { // The other two places this was called (one below and one in FNDF) // have been commented out. // Returning false essentially enables kademlia as a backup to floodfill for search responses. - if (FloodfillNetworkDatabaseFacade.floodfillEnabled(ctx)) + if (ctx.netDb().floodfillEnabled()) return false; return ctx.getProperty("netDb.floodfillOnly", DEFAULT_FLOODFILL_ONLY); } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/StartExplorersJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/StartExplorersJob.java index 0f9f4557b5..49198ad9f6 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/StartExplorersJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/StartExplorersJob.java @@ -51,7 +51,7 @@ class StartExplorersJob extends JobImpl { public String getName() { return "Start Explorers Job"; } public void runJob() { - if (! (((FloodfillNetworkDatabaseFacade)_facade).floodfillEnabled() || + if (! (_facade.floodfillEnabled() || getContext().router().gracefulShutdownInProgress())) { int num = MAX_PER_RUN; if (_facade.getDataStore().size() < LOW_ROUTERS) @@ -93,7 +93,7 @@ class StartExplorersJob extends JobImpl { */ private long getNextRunDelay() { // we don't explore if floodfill - if (((FloodfillNetworkDatabaseFacade)_facade).floodfillEnabled()) + if (_facade.floodfillEnabled()) return MAX_RERUN_DELAY_MS; // If we don't know too many peers, or just started, explore aggressively diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index b4b6e34a75..e82580b19c 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -39,7 +39,6 @@ import net.i2p.router.MessageSelector; import net.i2p.router.OutNetMessage; import net.i2p.router.Router; import net.i2p.router.RouterContext; -import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.LHMCache; import net.i2p.util.Log; @@ -145,7 +144,7 @@ public abstract class TransportImpl implements Transport { if (ri != null) { char bw = ri.getBandwidthTier().charAt(0); if (bw != 'U' && - ! ((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled()) + !_context.netDb().floodfillEnabled()) def = MAX_CONNECTION_FACTOR * (1 + bw - Router.CAPABILITY_BW12); } // increase limit for SSU, for now diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 90a22d763b..b98396379d 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -34,7 +34,6 @@ import net.i2p.router.CommSystemFacade; import net.i2p.router.OutNetMessage; import net.i2p.router.Router; import net.i2p.router.RouterContext; -import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.router.transport.Transport; import static net.i2p.router.transport.Transport.AddressSource.*; import net.i2p.router.transport.TransportBid; @@ -1907,7 +1906,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority (!_context.router().isHidden()) && (!introducersRequired()) && haveCapacity() && - (!((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled()) && + (!_context.netDb().floodfillEnabled()) && _introManager.introducedCount() < IntroductionManager.MAX_OUTBOUND && _introManager.introducedCount() < getMaxConnections() / 4; } diff --git a/router/java/test/junit/net/i2p/router/SSUDemo.java b/router/java/test/junit/net/i2p/router/SSUDemo.java index 30db9d1736..633b16bb4c 100644 --- a/router/java/test/junit/net/i2p/router/SSUDemo.java +++ b/router/java/test/junit/net/i2p/router/SSUDemo.java @@ -32,6 +32,7 @@ public class SSUDemo { } public SSUDemo() {} + public void run() { String cfgFile = "router.config"; Properties envProps = getEnv(); @@ -50,10 +51,10 @@ public class SSUDemo { loadPeers(); } - private Properties getEnv() { + private static Properties getEnv() { Properties envProps = System.getProperties(); - // disable the TCP transport, as its deprecated - envProps.setProperty("i2np.tcp.disable", "true"); + // disable the NTCP transport + envProps.setProperty("i2np.ntcp.enable", "false"); // we want SNTP synchronization for replay prevention envProps.setProperty("time.disabled", "false"); // allow 127.0.0.1/10.0.0.1/etc (useful for testing). If this is false, @@ -73,11 +74,11 @@ public class SSUDemo { envProps.setProperty("i2p.dummyTunnelManager", "true"); // set to false if you want to use HMAC-SHA256-128 instead of HMAC-MD5-128 as // the SSU MAC - envProps.setProperty("i2p.HMACMD5", "true"); + //envProps.setProperty("i2p.HMACMD5", "true"); // if you're using the HMAC MD5, by default it will use a 32 byte MAC field, // which is a bug, as it doesn't generate the same values as a 16 byte MAC field. // set this to false if you don't want the bug - envProps.setProperty("i2p.HMACBrokenSize", "false"); + //envProps.setProperty("i2p.HMACBrokenSize", "false"); // no need to include any stats in the routerInfo we send to people on SSU // session establishment envProps.setProperty("router.publishPeerRankings", "false"); @@ -98,9 +99,9 @@ public class SSUDemo { } /** random place for storing router info files - written as $dir/base64(SHA256(info.getIdentity)) */ - private File getInfoDir() { return new File("/tmp/ssuDemoInfo/"); } + private static File getInfoDir() { return new File("/tmp/ssuDemoInfo/"); } - private void storeMyInfo(RouterInfo info) { + private static void storeMyInfo(RouterInfo info) { File infoDir = getInfoDir(); if (!infoDir.exists()) infoDir.mkdirs(); @@ -198,16 +199,20 @@ public class SSUDemo { public FooJobBuilder() { I2NPMessageImpl.registerBuilder(new FooBuilder(), FooMessage.MESSAGE_TYPE); } + public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { return new FooHandleJob(_us, receivedMessage, from, fromHash); } } - private class FooHandleJob extends JobImpl { - private I2NPMessage _msg; + + private static class FooHandleJob extends JobImpl { + private final I2NPMessage _msg; + public FooHandleJob(RouterContext ctx, I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { super(ctx); _msg = receivedMessage; } + public void runJob() { // we know its a FooMessage, since thats the type of message that the handler // is registered as @@ -216,27 +221,33 @@ public class SSUDemo { } public String getName() { return "Handle Foo message"; } } - private class FooBuilder implements I2NPMessageImpl.Builder { + + private static class FooBuilder implements I2NPMessageImpl.Builder { public I2NPMessage build(I2PAppContext ctx) { return new FooMessage(ctx, null); } } /** * Just carry some data... */ - class FooMessage extends I2NPMessageImpl { + private static class FooMessage extends I2NPMessageImpl { private byte[] _data; public static final int MESSAGE_TYPE = 17; + public FooMessage(I2PAppContext ctx, byte data[]) { super(ctx); _data = data; } + /** pull the read data off */ public byte[] getData() { return _data; } + /** specify the payload to be sent */ public void setData(byte data[]) { _data = data; } public int getType() { return MESSAGE_TYPE; } + protected int calculateWrittenLength() { return _data.length; } + public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException { _data = new byte[dataSize]; System.arraycopy(data, offset, _data, 0, dataSize); @@ -260,12 +271,15 @@ public class SSUDemo { return new HandleJob(_us, receivedMessage, from, fromHash); } } + private class HandleJob extends JobImpl { - private I2NPMessage _msg; + private final I2NPMessage _msg; + public HandleJob(RouterContext ctx, I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { super(ctx); _msg = receivedMessage; } + public void runJob() { // we know its a DatabaseStoreMessage, since thats the type of message that the handler // is registered as @@ -276,6 +290,7 @@ public class SSUDemo { iae.printStackTrace(); } } + public String getName() { return "Handle netDb store"; } } } From 226c7eb8e3178f9129e6b8418e2fbe3e07409b3e Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 18 May 2013 14:27:11 +0000 Subject: [PATCH 28/38] * SSUDemo: configuration fixes, delete RI on exit, log tweaks --- .../test/junit/net/i2p/router/SSUDemo.java | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/router/java/test/junit/net/i2p/router/SSUDemo.java b/router/java/test/junit/net/i2p/router/SSUDemo.java index 633b16bb4c..9b48e548d3 100644 --- a/router/java/test/junit/net/i2p/router/SSUDemo.java +++ b/router/java/test/junit/net/i2p/router/SSUDemo.java @@ -53,8 +53,9 @@ public class SSUDemo { private static Properties getEnv() { Properties envProps = System.getProperties(); - // disable the NTCP transport + // disable the NTCP transport and UPnP envProps.setProperty("i2np.ntcp.enable", "false"); + envProps.setProperty("i2np.upnp.enable", "false"); // we want SNTP synchronization for replay prevention envProps.setProperty("time.disabled", "false"); // allow 127.0.0.1/10.0.0.1/etc (useful for testing). If this is false, @@ -64,8 +65,10 @@ public class SSUDemo { // set, since there has to be someone to detect one's IP off. most don't need // to set these though envProps.setProperty("i2np.udp.host", "127.0.0.1"); - envProps.setProperty("i2np.udp.internalPort", "12000"); - envProps.setProperty("i2np.udp.port", "12000"); + // we don't have a context yet to use its random + String port = Integer.toString(44000 + (((int) System.currentTimeMillis()) & (16384 - 1))); + envProps.setProperty("i2np.udp.internalPort", port); + envProps.setProperty("i2np.udp.port", port); // disable I2CP, the netDb, peer testing/profile persistence, and tunnel // creation/management envProps.setProperty("i2p.dummyClientFacade", "true"); @@ -85,7 +88,17 @@ public class SSUDemo { // write the logs to ./logs/log-router-*.txt (logger configured with the file // ./logger.config, or another config file specified as // -Dlogger.configLocation=blah) - envProps.setProperty("loggerFilenameOverride", "logs/log-router-@.txt"); + // avoid conflicts over log + envProps.setProperty("loggerFilenameOverride", "logs/log-router-" + port + "-@.txt"); + System.setProperty("wrapper.logfile", "wrapper-" + port + ".log"); + // avoid conflicts over key backup etc. so we don't all use the same keys + envProps.setProperty("router.keyBackupDir", "keyBackup/router-" + port); + envProps.setProperty("router.info.location", "router-" + port + ".info"); + envProps.setProperty("router.keys.location", "router-" + port + ".keys"); + envProps.setProperty("router.configLocation", "router-" + port + ".config"); + envProps.setProperty("router.pingFile", "router-" + port + ".ping"); + // avoid conflicts over blockfile + envProps.setProperty("i2p.naming.impl", "net.i2p.client.naming.HostsTxtNamingService"); return envProps; } @@ -107,6 +120,7 @@ public class SSUDemo { infoDir.mkdirs(); FileOutputStream fos = null; File infoFile = new File(infoDir, info.getIdentity().calculateHash().toBase64()); + infoFile.deleteOnExit(); try { fos = new FileOutputStream(infoFile); info.writeBytes(fos); @@ -166,7 +180,8 @@ public class SSUDemo { out.setPriority(100); out.setTarget(ri); FooMessage data = new FooMessage(_us, new byte[] { 0x0, 0x1, 0x2, 0x3 }); - System.out.println("SEND: " + Base64.encode(data.getData())); + System.out.println("SEND: " + Base64.encode(data.getData()) + " to " + + ri.getIdentity().calculateHash()); out.setMessage(data); // job fired if we can't contact them, or if it takes too long to get an ACK out.setOnFailedSendJob(null); @@ -207,17 +222,19 @@ public class SSUDemo { private static class FooHandleJob extends JobImpl { private final I2NPMessage _msg; + private final Hash _from; public FooHandleJob(RouterContext ctx, I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { super(ctx); _msg = receivedMessage; + _from = fromHash; } public void runJob() { // we know its a FooMessage, since thats the type of message that the handler // is registered as FooMessage m = (FooMessage)_msg; - System.out.println("RECV: " + Base64.encode(m.getData())); + System.out.println("RECV: " + Base64.encode(m.getData()) + " from " + _from); } public String getName() { return "Handle Foo message"; } } From 0b49fa98f91150f70fc9706014537af599b2cde3 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 18 May 2013 18:00:17 +0000 Subject: [PATCH 29/38] * SSU: Fixes for i2np.udp.allowLocal, log tweaks, sender/receiver thread name tweaks * Limit tunnel GW pumper threads when testing --- .../net/i2p/router/transport/TransportImpl.java | 3 ++- .../router/transport/udp/EstablishmentManager.java | 2 +- .../transport/udp/OutboundMessageFragments.java | 4 ++-- .../net/i2p/router/transport/udp/UDPEndpoint.java | 7 +++++-- .../net/i2p/router/transport/udp/UDPReceiver.java | 7 ++----- .../net/i2p/router/transport/udp/UDPTransport.java | 14 +++++++++++++- .../net/i2p/router/tunnel/TunnelGatewayPumper.java | 12 ++++++++---- router/java/test/junit/net/i2p/router/SSUDemo.java | 3 ++- 8 files changed, 35 insertions(+), 17 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index e82580b19c..c6d49580f7 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -687,7 +687,8 @@ public abstract class TransportImpl implements Transport { else _wasUnreachableEntries.remove(peer); if (_log.shouldLog(Log.INFO)) - _log.info(this.getStyle() + " setting wasUnreachable to " + yes + " for " + peer); + _log.info(this.getStyle() + " setting wasUnreachable to " + yes + " for " + peer, + yes ? new Exception() : null); } /** diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index f9b9e2d1f9..4afd49b7b6 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -249,7 +249,7 @@ class EstablishmentManager { maybeTo = new RemoteHostId(remAddr.getAddress(), port); if ((!_transport.isValid(maybeTo.getIP())) || - Arrays.equals(maybeTo.getIP(), _transport.getExternalIP())) { + (Arrays.equals(maybeTo.getIP(), _transport.getExternalIP()) && !_transport.allowLocal())) { _transport.failed(msg, "Remote peer's IP isn't valid"); _transport.markUnreachable(toHash); //_context.banlist().banlistRouter(msg.getTarget().getIdentity().calculateHash(), "Invalid SSU address", UDPTransport.STYLE); diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java b/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java index d4c8be8336..96e9a66865 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java @@ -309,8 +309,8 @@ class OutboundMessageFragments { // use max of 1 second so finishMessages() and/or PeerState.finishMessages() // gets called regularly int toWait = Math.min(Math.max(nextSendDelay, 10), MAX_WAIT); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("wait for " + toWait); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("wait for " + toWait); // wait.. or somethin' synchronized (_activePeers) { try { diff --git a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java index 206eaee7bd..b750df660a 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java @@ -6,6 +6,7 @@ import java.net.InetAddress; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.SocketException; +import java.util.concurrent.atomic.AtomicInteger; import net.i2p.router.RouterContext; import net.i2p.util.Log; @@ -24,6 +25,7 @@ class UDPEndpoint { private DatagramSocket _socket; private final InetAddress _bindAddress; private final boolean _isIPv4, _isIPv6; + private static final AtomicInteger _counter = new AtomicInteger(); /** * @param transport may be null for unit testing ONLY @@ -50,10 +52,11 @@ class UDPEndpoint { _log.log(Log.CRIT, "UDP Unable to open a port"); throw new SocketException("SSU Unable to bind to a port on " + _bindAddress); } - _sender = new UDPSender(_context, _socket, "UDPSender"); + int count = _counter.incrementAndGet(); + _sender = new UDPSender(_context, _socket, "UDPSender " + count); _sender.startup(); if (_transport != null) { - _receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver"); + _receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver " + count); _receiver.startup(); } } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java index 468c780ec7..d8976d483e 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java @@ -31,15 +31,12 @@ class UDPReceiver { private final Runner _runner; private final UDPTransport _transport; private final PacketHandler _handler; - private static int __id; - private final int _id; private static final boolean _isAndroid = SystemVersion.isAndroid(); public UDPReceiver(RouterContext ctx, UDPTransport transport, DatagramSocket socket, String name) { _context = ctx; _log = ctx.logManager().getLog(UDPReceiver.class); - _id = ++__id; _name = name; _socket = socket; _transport = transport; @@ -57,7 +54,7 @@ class UDPReceiver { public synchronized void startup() { //adjustDropProbability(); _keepRunning = true; - I2PThread t = new I2PThread(_runner, _name + '.' + _id, true); + I2PThread t = new I2PThread(_runner, _name, true); t.start(); } @@ -154,7 +151,7 @@ class UDPReceiver { } // drop anything apparently from our IP (any port) - if (Arrays.equals(from.getIP(), _transport.getExternalIP())) { + if (Arrays.equals(from.getIP(), _transport.getExternalIP()) && !_transport.allowLocal()) { if (_log.shouldLog(Log.WARN)) _log.warn("Dropping (spoofed?) packet from ourselves"); packet.release(); diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index b98396379d..fe7d7bfc72 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -888,6 +888,15 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority if (isPubliclyRoutable(addr) && (addr.length != 16 || _haveIPv6Address)) return true; + return allowLocal(); + } + + /** + * Are we allowed to connect to local addresses? + * + * @since IPv6 + */ + boolean allowLocal() { return _context.getBooleanProperty("i2np.udp.allowLocal"); } @@ -1522,7 +1531,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority int port = addr.getPort(); if (ip == null || port < MIN_PEER_PORT || (!isValid(ip)) || - Arrays.equals(ip, getExternalIP())) { + (Arrays.equals(ip, getExternalIP()) && !allowLocal())) { continue; } } @@ -1960,6 +1969,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // dropPeer(msg.getPeer(), false); //else if (consecutive > 2 * MAX_CONSECUTIVE_FAILED) // they're sending us data, but we cant reply? // dropPeer(msg.getPeer(), false); + } else { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Failed sending " + msg + " to " + msg.getPeer()); } noteSend(msg, false); if (m != null) diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java b/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java index 7dfe8dc764..680a23966e 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java @@ -42,10 +42,14 @@ class TunnelGatewayPumper implements Runnable { _context = ctx; _wantsPumping = new LinkedHashSet(16); _backlogged = new HashSet(16); - long maxMemory = Runtime.getRuntime().maxMemory(); - if (maxMemory == Long.MAX_VALUE) - maxMemory = 96*1024*1024l; - _pumpers = (int) Math.max(MIN_PUMPERS, Math.min(MAX_PUMPERS, 1 + (maxMemory / (32*1024*1024)))); + if (ctx.getBooleanProperty("i2p.dummyTunnelManager")) { + _pumpers = 1; + } else { + long maxMemory = Runtime.getRuntime().maxMemory(); + if (maxMemory == Long.MAX_VALUE) + maxMemory = 96*1024*1024l; + _pumpers = (int) Math.max(MIN_PUMPERS, Math.min(MAX_PUMPERS, 1 + (maxMemory / (32*1024*1024)))); + } for (int i = 0; i < _pumpers; i++) new I2PThread(this, "Tunnel GW pumper " + (i+1) + '/' + _pumpers, true).start(); } diff --git a/router/java/test/junit/net/i2p/router/SSUDemo.java b/router/java/test/junit/net/i2p/router/SSUDemo.java index 9b48e548d3..f505fea603 100644 --- a/router/java/test/junit/net/i2p/router/SSUDemo.java +++ b/router/java/test/junit/net/i2p/router/SSUDemo.java @@ -234,7 +234,7 @@ public class SSUDemo { // we know its a FooMessage, since thats the type of message that the handler // is registered as FooMessage m = (FooMessage)_msg; - System.out.println("RECV: " + Base64.encode(m.getData()) + " from " + _from); + System.out.println("RECV FooMessage: " + Base64.encode(m.getData()) + " from " + _from); } public String getName() { return "Handle Foo message"; } } @@ -301,6 +301,7 @@ public class SSUDemo { // we know its a DatabaseStoreMessage, since thats the type of message that the handler // is registered as DatabaseStoreMessage m = (DatabaseStoreMessage)_msg; + System.out.println("RECV: " + m); try { _us.netDb().store(m.getKey(), (RouterInfo) m.getEntry()); } catch (IllegalArgumentException iae) { From ec3756a69fe62045b7ac34032f682d37da95c97f Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 18 May 2013 18:30:38 +0000 Subject: [PATCH 30/38] * SSUDemo: Use IPv6; don't use System properties --- router/java/test/junit/net/i2p/router/SSUDemo.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/router/java/test/junit/net/i2p/router/SSUDemo.java b/router/java/test/junit/net/i2p/router/SSUDemo.java index f505fea603..ef301a3c81 100644 --- a/router/java/test/junit/net/i2p/router/SSUDemo.java +++ b/router/java/test/junit/net/i2p/router/SSUDemo.java @@ -52,7 +52,7 @@ public class SSUDemo { } private static Properties getEnv() { - Properties envProps = System.getProperties(); + Properties envProps = new Properties(); // disable the NTCP transport and UPnP envProps.setProperty("i2np.ntcp.enable", "false"); envProps.setProperty("i2np.upnp.enable", "false"); @@ -61,10 +61,13 @@ public class SSUDemo { // allow 127.0.0.1/10.0.0.1/etc (useful for testing). If this is false, // peers who say they're on an invalid IP are banlisted envProps.setProperty("i2np.udp.allowLocal", "true"); + // IPv6 + envProps.setProperty("i2np.udp.ipv6", "enable"); // explicit IP+port. at least one router on the net has to have their IP+port // set, since there has to be someone to detect one's IP off. most don't need // to set these though - envProps.setProperty("i2np.udp.host", "127.0.0.1"); + //envProps.setProperty("i2np.udp.host", "127.0.0.1"); + envProps.setProperty("i2np.udp.host", "::1"); // we don't have a context yet to use its random String port = Integer.toString(44000 + (((int) System.currentTimeMillis()) & (16384 - 1))); envProps.setProperty("i2np.udp.internalPort", port); From 5cda1ec703aff28503785606eea3d342ddc5af86 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 18 May 2013 19:24:16 +0000 Subject: [PATCH 31/38] - Handle IPv6 in too-close checks --- .../transport/udp/EstablishmentManager.java | 2 +- .../transport/udp/IntroductionManager.java | 2 +- .../router/transport/udp/PacketBuilder.java | 2 +- .../router/transport/udp/PeerTestManager.java | 6 ++--- .../i2p/router/transport/udp/UDPPacket.java | 4 +-- .../router/transport/udp/UDPTransport.java | 26 ++++++++++++++++++- 6 files changed, 33 insertions(+), 9 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index 4afd49b7b6..c0c17e1dfe 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -960,7 +960,7 @@ class EstablishmentManager { port <= 65535 && ip != null && ip.length == 4 && _transport.isValid(ip) && - (!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) && + (!_transport.isTooClose(ip)) && (!_context.blocklist().isBlocklisted(ip)); } diff --git a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java index 790a0d349f..6eb9ab0171 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -433,7 +433,7 @@ class IntroductionManager { port <= 65535 && ip != null && ip.length == 4 && _transport.isValid(ip) && - (!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) && + (!_transport.isTooClose(ip)) && (!_context.blocklist().isBlocklisted(ip)); } } diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index 523141d2c5..ed62440a42 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -1084,7 +1084,7 @@ class PacketBuilder { // must be IPv4 for now as we don't send Alice IP/port, see below iaddr.getAddress().length != 4 || (!_transport.isValid(iaddr.getAddress())) || - Arrays.equals(iaddr.getAddress(), _transport.getExternalIP())) { + (Arrays.equals(iaddr.getAddress(), _transport.getExternalIP()) && !_transport.allowLocal())) { if (_log.shouldLog(_log.WARN)) _log.warn("Cannot build a relay request to " + state.getRemoteIdentity().calculateHash() + ", as their UDP address is invalid: addr=" + addr + " index=" + i); diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java index 5cc72dfcf6..ffc512654b 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java @@ -182,7 +182,7 @@ class PeerTestManager { _log.warn("We are already running a test: " + _currentTest + ", aborting test with bob = " + bobIP); return; } - if (DataHelper.eq(bobIP.getAddress(), 0, _transport.getExternalIP(), 0, 2)) { + if (_transport.isTooClose(bobIP.getAddress())) { if (_log.shouldLog(Log.WARN)) _log.warn("Not running test with Bob too close to us " + bobIP); return; @@ -496,7 +496,7 @@ class PeerTestManager { int fromPort = from.getPort(); if (fromPort < 1024 || fromPort > 65535 || (!_transport.isValid(fromIP)) || - DataHelper.eq(fromIP, 0, _transport.getExternalIP(), 0, 2) || + _transport.isTooClose(fromIP) || _context.blocklist().isBlocklisted(fromIP)) { // spoof check, and don't respond to privileged ports if (_log.shouldLog(Log.WARN)) @@ -556,7 +556,7 @@ class PeerTestManager { Long lNonce = Long.valueOf(nonce); PeerTestState state = _activeTests.get(lNonce); - if (testIP != null && DataHelper.eq(testIP, 0, _transport.getExternalIP(), 0, 2)) { + if (testIP != null && _transport.isTooClose(testIP)) { // spoof check - have to do this after receiveTestReply(), since // the field should be us there. // Let's also eliminate anybody in the same /16 diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java index 958a6d4c9e..275152f358 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java @@ -10,6 +10,7 @@ import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.data.SessionKey; import net.i2p.router.util.CDQEntry; +import net.i2p.util.Addresses; import net.i2p.util.Log; /** @@ -292,8 +293,7 @@ class UDPPacket implements CDQEntry { StringBuilder buf = new StringBuilder(256); buf.append(_packet.getLength()); buf.append(" byte pkt with "); - buf.append(_packet.getAddress().getHostAddress()).append(":"); - buf.append(_packet.getPort()); + buf.append(Addresses.toString(_packet.getAddress().getAddress(), _packet.getPort())); //buf.append(" id=").append(System.identityHashCode(this)); if (_messageType >= 0) buf.append(" msgType=").append(_messageType); diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index fe7d7bfc72..726c619467 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -508,6 +508,30 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return null; } + /** + * Is this IP too close to ours to trust it for + * things like relaying? + * @param ip IPv4 or IPv6 + * @since IPv6 + */ + boolean isTooClose(byte[] ip) { + if (allowLocal()) + return false; + for (RouterAddress addr : getCurrentAddresses()) { + byte[] myip = addr.getIP(); + if (myip == null || ip.length != myip.length) + continue; + if (ip.length == 4) { + if (DataHelper.eq(ip, 0, myip, 0, 2)) + return true; + } else if (ip.length == 16) { + if (DataHelper.eq(ip, 0, myip, 0, 8)) + return true; + } + } + return false; + } + /** * The current port of the first matching endpoint. * To be enhanced to handle multiple endpoints of the same type. @@ -2980,7 +3004,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } if (ip == null) continue; - if (DataHelper.eq(ip, 0, getExternalIP(), 0, 2)) + if (isTooClose(ip)) continue; return peer; } From d603c3b5cdad0c9f80f0e6849989463ec103f901 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 18 May 2013 19:58:10 +0000 Subject: [PATCH 32/38] - UDPAddress reduce object churn, check intro key length, don't look for ihost3, reject ports < 1024, cleanups --- .../transport/udp/EstablishmentManager.java | 2 +- .../transport/udp/IntroductionManager.java | 2 +- .../i2p/router/transport/udp/UDPAddress.java | 40 ++++++++++++++----- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index c0c17e1dfe..e279411937 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -956,7 +956,7 @@ class EstablishmentManager { * @since 0.9.3 */ private boolean isValid(byte[] ip, int port) { - return port >= 1024 && + return port >= UDPTransport.MIN_PEER_PORT && port <= 65535 && ip != null && ip.length == 4 && _transport.isValid(ip) && diff --git a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java index 6eb9ab0171..03b70e2a7d 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -429,7 +429,7 @@ class IntroductionManager { * @since 0.9.3 */ private boolean isValid(byte[] ip, int port) { - return port >= 1024 && + return port >= UDPTransport.MIN_PEER_PORT && port <= 65535 && ip != null && ip.length == 4 && _transport.isValid(ip) && diff --git a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java index 9d566a6698..83ce91dc68 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java @@ -39,6 +39,23 @@ public class UDPAddress { public static final String PROP_INTRO_KEY_PREFIX = "ikey"; public static final String PROP_INTRO_TAG_PREFIX = "itag"; static final int MAX_INTRODUCERS = 3; + private static final String[] PROP_INTRO_HOST; + private static final String[] PROP_INTRO_PORT; + private static final String[] PROP_INTRO_IKEY; + private static final String[] PROP_INTRO_TAG; + static { + // object churn + PROP_INTRO_HOST = new String[MAX_INTRODUCERS]; + PROP_INTRO_PORT = new String[MAX_INTRODUCERS]; + PROP_INTRO_IKEY = new String[MAX_INTRODUCERS]; + PROP_INTRO_TAG = new String[MAX_INTRODUCERS]; + for (int i = 0; i < MAX_INTRODUCERS; i++) { + PROP_INTRO_HOST[i] = PROP_INTRO_HOST_PREFIX + i; + PROP_INTRO_PORT[i] = PROP_INTRO_PORT_PREFIX + i; + PROP_INTRO_IKEY[i] = PROP_INTRO_KEY_PREFIX + i; + PROP_INTRO_TAG[i] = PROP_INTRO_TAG_PREFIX + i; + } + } public UDPAddress(RouterAddress addr) { // TODO make everything final @@ -55,29 +72,32 @@ public class UDPAddress { _mtu = MTU.rectify(Integer.parseInt(mtu)); } catch (NumberFormatException nfe) {} String key = addr.getOption(PROP_INTRO_KEY); - if (key != null) - _introKey = Base64.decode(key.trim()); + if (key != null) { + byte[] ik = Base64.decode(key.trim()); + if (ik != null && ik.length == SessionKey.KEYSIZE_BYTES) + _introKey = ik; + } - for (int i = MAX_INTRODUCERS; i >= 0; i--) { - String host = addr.getOption(PROP_INTRO_HOST_PREFIX + i); + for (int i = MAX_INTRODUCERS - 1; i >= 0; i--) { + String host = addr.getOption(PROP_INTRO_HOST[i]); if (host == null) continue; - String port = addr.getOption(PROP_INTRO_PORT_PREFIX + i); + String port = addr.getOption(PROP_INTRO_PORT[i]); if (port == null) continue; - String k = addr.getOption(PROP_INTRO_KEY_PREFIX + i); + String k = addr.getOption(PROP_INTRO_IKEY[i]); if (k == null) continue; byte ikey[] = Base64.decode(k); if ( (ikey == null) || (ikey.length != SessionKey.KEYSIZE_BYTES) ) continue; - String t = addr.getOption(PROP_INTRO_TAG_PREFIX + i); + String t = addr.getOption(PROP_INTRO_TAG[i]); if (t == null) continue; - int p = -1; + int p; try { p = Integer.parseInt(port); - if (p <= 0 || p > 65535) continue; + if (p < UDPTransport.MIN_PEER_PORT || p > 65535) continue; } catch (NumberFormatException nfe) { continue; } - long tag = -1; + long tag; try { tag = Long.parseLong(t); if (tag <= 0) continue; From 729282c0c4b579e2ba02633843fc81e7bba84e72 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 18 May 2013 20:10:10 +0000 Subject: [PATCH 33/38] * UDPAddress: Remove dependency in console; make package private --- .../src/net/i2p/router/web/ConfigNetHelper.java | 16 ++++------------ .../net/i2p/router/transport/udp/UDPAddress.java | 3 +-- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java index cbf8c859c7..11971fb147 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java @@ -7,7 +7,6 @@ import net.i2p.data.RouterAddress; import net.i2p.router.CommSystemFacade; import net.i2p.router.Router; import net.i2p.router.transport.TransportManager; -import net.i2p.router.transport.udp.UDPAddress; import net.i2p.router.transport.udp.UDPTransport; import net.i2p.util.Addresses; @@ -32,22 +31,15 @@ public class ConfigNetHelper extends HelperBase { return _context.getProperty(PROP_I2NP_NTCP_PORT, ""); } - public String getUdpAddress() { - RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU"); - if (addr == null) - return _("unknown"); - UDPAddress ua = new UDPAddress(addr); - return ua.toString(); - } - + /** @return host or "unknown" */ public String getUdpIP() { RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU"); if (addr == null) return _("unknown"); - UDPAddress ua = new UDPAddress(addr); - if (ua.getHost() == null) + String rv = addr.getHost(); + if (rv == null) return _("unknown"); - return ua.getHost(); + return rv; } /** diff --git a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java index 83ce91dc68..ff77ad0e67 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java @@ -11,9 +11,8 @@ import net.i2p.util.LHMCache; /** * basic helper to parse out peer info from a udp address - * FIXME public for ConfigNetHelper */ -public class UDPAddress { +class UDPAddress { private final String _host; private InetAddress _hostAddress; private final int _port; From 55880844a5ed7952d2b2f51e65e342c09f905978 Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 19 May 2013 15:18:45 +0000 Subject: [PATCH 34/38] * SSUDemo: Adapt for NTCP testing too * RouterAddress and RouterInfo toString() cleanups; don't cache RI in _stringified * NTCP: Fix publishing address when host specified but interface is not * log tweaks --- core/java/src/net/i2p/data/RouterAddress.java | 4 +-- core/java/src/net/i2p/data/RouterInfo.java | 34 +++++++++++-------- .../router/transport/ntcp/EstablishState.java | 8 ++--- .../router/transport/ntcp/NTCPTransport.java | 5 +++ .../test/junit/net/i2p/router/SSUDemo.java | 22 ++++++++---- 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/core/java/src/net/i2p/data/RouterAddress.java b/core/java/src/net/i2p/data/RouterAddress.java index da95901e13..adec96f6dd 100644 --- a/core/java/src/net/i2p/data/RouterAddress.java +++ b/core/java/src/net/i2p/data/RouterAddress.java @@ -309,10 +309,10 @@ public class RouterAddress extends DataStructureImpl { public String toString() { StringBuilder buf = new StringBuilder(128); buf.append("[RouterAddress: "); - buf.append("\n\tTransportStyle: ").append(_transportStyle); + buf.append("\n\tType: ").append(_transportStyle); buf.append("\n\tCost: ").append(_cost); //buf.append("\n\tExpiration: ").append(_expiration); - buf.append("\n\tOptions: #: ").append(_options.size()); + buf.append("\n\tOptions (").append(_options.size()).append("):"); for (Map.Entry e : _options.entrySet()) { String key = (String) e.getKey(); String val = (String) e.getValue(); diff --git a/core/java/src/net/i2p/data/RouterInfo.java b/core/java/src/net/i2p/data/RouterInfo.java index e8155b7824..7bc911db07 100644 --- a/core/java/src/net/i2p/data/RouterInfo.java +++ b/core/java/src/net/i2p/data/RouterInfo.java @@ -61,7 +61,7 @@ public class RouterInfo extends DatabaseEntry { private final Properties _options; private volatile boolean _validated; private volatile boolean _isValid; - private volatile String _stringified; + //private volatile String _stringified; private volatile byte _byteified[]; private volatile int _hashCode; private volatile boolean _hashCodeInitialized; @@ -612,30 +612,34 @@ public class RouterInfo extends DatabaseEntry { @Override public String toString() { - if (_stringified != null) return _stringified; - StringBuilder buf = new StringBuilder(5*1024); + //if (_stringified != null) return _stringified; + StringBuilder buf = new StringBuilder(1024); buf.append("[RouterInfo: "); buf.append("\n\tIdentity: ").append(_identity); buf.append("\n\tSignature: ").append(_signature); - buf.append("\n\tPublished on: ").append(new Date(_published)); - buf.append("\n\tAddresses: #: ").append(_addresses.size()); - for (RouterAddress addr : _addresses) { - buf.append("\n\t\tAddress: ").append(addr); + buf.append("\n\tPublished: ").append(new Date(_published)); + if (_peers != null) { + buf.append("\n\tPeers (").append(_peers.size()).append("):"); + for (Hash hash : _peers) { + buf.append("\n\t\tPeer hash: ").append(hash); + } } - Set peers = getPeers(); - buf.append("\n\tPeers: #: ").append(peers.size()); - for (Hash hash : peers) { - buf.append("\n\t\tPeer hash: ").append(hash); - } - buf.append("\n\tOptions: #: ").append(_options.size()); + buf.append("\n\tOptions (").append(_options.size()).append("):"); for (Map.Entry e : _options.entrySet()) { String key = (String) e.getKey(); String val = (String) e.getValue(); buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); } + if (!_addresses.isEmpty()) { + buf.append("\n\tAddresses (").append(_addresses.size()).append("):"); + for (RouterAddress addr : _addresses) { + buf.append("\n\t").append(addr); + } + } buf.append("]"); - _stringified = buf.toString(); - return _stringified; + String rv = buf.toString(); + //_stringified = rv; + return rv; } /** diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java index 04da0506b4..9955268e98 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java @@ -261,8 +261,8 @@ class EstablishState { // ok, we are onto the encrypted area while (src.hasRemaining() && !_corrupt) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(prefix()+"Encrypted bytes available (" + src.hasRemaining() + ")"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug(prefix()+"Encrypted bytes available (" + src.hasRemaining() + ")"); while (_curEncryptedOffset < _curEncrypted.length && src.hasRemaining()) { _curEncrypted[_curEncryptedOffset++] = src.get(); _received++; @@ -299,8 +299,8 @@ class EstablishState { } catch (IOException ioe) { if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe); } - if (_log.shouldLog(Log.DEBUG)) - _log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")"); if (_sz_aliceIdent_tsA_padding_aliceSig.size() >= _sz_aliceIdent_tsA_padding_aliceSigSize) { verifyInbound(); diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index 3be51b3df5..20b00f3999 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -490,8 +490,13 @@ public class NTCPTransport extends TransportImpl { port = _ssuPort; RouterAddress myAddress = bindAddress(port); if (myAddress != null) { + // fixed interface, or bound to the specified host replaceAddress(myAddress); + } else if (addr != null) { + // specified host, bound to wildcard + replaceAddress(addr); } else if (port > 0) { + // all detected interfaces for (InetAddress ia : getSavedLocalAddresses()) { OrderedProperties props = new OrderedProperties(); props.setProperty(RouterAddress.PROP_HOST, ia.getHostAddress()); diff --git a/router/java/test/junit/net/i2p/router/SSUDemo.java b/router/java/test/junit/net/i2p/router/SSUDemo.java index ef301a3c81..fb622294c0 100644 --- a/router/java/test/junit/net/i2p/router/SSUDemo.java +++ b/router/java/test/junit/net/i2p/router/SSUDemo.java @@ -27,15 +27,16 @@ public class SSUDemo { RouterContext _us; public static void main(String args[]) { + boolean testNTCP = args.length > 0 && args[0].equals("ntcp"); SSUDemo demo = new SSUDemo(); - demo.run(); + demo.run(testNTCP); } public SSUDemo() {} - public void run() { + public void run(boolean testNTCP) { String cfgFile = "router.config"; - Properties envProps = getEnv(); + Properties envProps = getEnv(testNTCP); Router r = new Router(cfgFile, envProps); r.runRouter(); _us = r.getContext(); @@ -51,27 +52,36 @@ public class SSUDemo { loadPeers(); } - private static Properties getEnv() { + private static Properties getEnv(boolean testNTCP) { Properties envProps = new Properties(); - // disable the NTCP transport and UPnP - envProps.setProperty("i2np.ntcp.enable", "false"); + // disable one of the transports and UPnP + if (testNTCP) + envProps.setProperty("i2np.udp.enable", "false"); + else + envProps.setProperty("i2np.ntcp.enable", "false"); envProps.setProperty("i2np.upnp.enable", "false"); // we want SNTP synchronization for replay prevention envProps.setProperty("time.disabled", "false"); // allow 127.0.0.1/10.0.0.1/etc (useful for testing). If this is false, // peers who say they're on an invalid IP are banlisted envProps.setProperty("i2np.udp.allowLocal", "true"); + envProps.setProperty("i2np.ntcp.allowLocal", "true"); // IPv6 envProps.setProperty("i2np.udp.ipv6", "enable"); + envProps.setProperty("i2np.ntcp.ipv6", "enable"); // explicit IP+port. at least one router on the net has to have their IP+port // set, since there has to be someone to detect one's IP off. most don't need // to set these though //envProps.setProperty("i2np.udp.host", "127.0.0.1"); envProps.setProperty("i2np.udp.host", "::1"); + envProps.setProperty("i2np.ntcp.autoip", "false"); + envProps.setProperty("i2np.ntcp.hostname", "::1"); // we don't have a context yet to use its random String port = Integer.toString(44000 + (((int) System.currentTimeMillis()) & (16384 - 1))); envProps.setProperty("i2np.udp.internalPort", port); envProps.setProperty("i2np.udp.port", port); + envProps.setProperty("i2np.ntcp.autoport", "false"); + envProps.setProperty("i2np.ntcp.port", port); // disable I2CP, the netDb, peer testing/profile persistence, and tunnel // creation/management envProps.setProperty("i2p.dummyClientFacade", "true"); From c0350702fd29a721e6bb1ddfe13c704f20cc0428 Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 19 May 2013 18:36:29 +0000 Subject: [PATCH 35/38] * RouterAddress: - Deprecate some setters - Add warning about setCost() - Change cost storage from int to short - Cost range checks * NTCP: - Republish even if only changing cost * Transports: - Sort multiple peer addresses by cost, with adjustment for local IPv6 preference - Add default IPv6Config for ease of changing later --- core/java/src/net/i2p/data/RouterAddress.java | 27 +++++--- .../i2p/router/transport/TransportImpl.java | 65 ++++++++++++++++++- .../i2p/router/transport/TransportUtil.java | 7 +- .../router/transport/ntcp/NTCPTransport.java | 41 +++++++++--- .../router/transport/udp/UDPTransport.java | 20 ++++-- 5 files changed, 132 insertions(+), 28 deletions(-) diff --git a/core/java/src/net/i2p/data/RouterAddress.java b/core/java/src/net/i2p/data/RouterAddress.java index adec96f6dd..0b6fde6e0f 100644 --- a/core/java/src/net/i2p/data/RouterAddress.java +++ b/core/java/src/net/i2p/data/RouterAddress.java @@ -38,7 +38,7 @@ import net.i2p.util.OrderedProperties; * @author jrandom */ public class RouterAddress extends DataStructureImpl { - private int _cost; + private short _cost; //private Date _expiration; private String _transportStyle; private final Properties _options; @@ -50,19 +50,21 @@ public class RouterAddress extends DataStructureImpl { public static final String PROP_PORT = "port"; public RouterAddress() { - _cost = -1; _options = new OrderedProperties(); } /** * For efficiency when created by a Transport. * @param options not copied; do not reuse or modify + * @param cost 0-255 * @since IPv6 */ public RouterAddress(String style, OrderedProperties options, int cost) { _transportStyle = style; _options = options; - _cost = cost; + if (cost < 0 || cost > 255) + throw new IllegalArgumentException(); + _cost = (short) cost; } /** @@ -71,6 +73,7 @@ public class RouterAddress extends DataStructureImpl { * No value above 255 is allowed. * * Unused before 0.7.12 + * @return 0-255 */ public int getCost() { return _cost; @@ -78,12 +81,18 @@ public class RouterAddress extends DataStructureImpl { /** * Configure the weighted cost of using the address. - * No value above 255 is allowed. + * No value negative or above 255 is allowed. + * + * WARNING - do not change cost on a published address or it will break the RI sig. + * There is no check here. + * Rarely used, use 3-arg constructor. * * NTCP is set to 10 and SSU to 5 by default, unused before 0.7.12 */ public void setCost(int cost) { - _cost = cost; + if (cost < 0 || cost > 255) + throw new IllegalArgumentException(); + _cost = (short) cost; } /** @@ -124,6 +133,7 @@ public class RouterAddress extends DataStructureImpl { * Configure the type of transport that must be used to communicate on this address * * @throws IllegalStateException if was already set + * @deprecated unused, use 3-arg constructor */ public void setTransportStyle(String transportStyle) { if (_transportStyle != null) @@ -163,6 +173,7 @@ public class RouterAddress extends DataStructureImpl { * Makes a copy. * @param options non-null * @throws IllegalStateException if was already set + * @deprecated unused, use 3-arg constructor */ public void setOptions(Properties options) { if (!_options.isEmpty()) @@ -234,7 +245,7 @@ public class RouterAddress extends DataStructureImpl { public void readBytes(InputStream in) throws DataFormatException, IOException { if (_transportStyle != null) throw new IllegalStateException(); - _cost = (int) DataHelper.readLong(in, 1); + _cost = (short) DataHelper.readLong(in, 1); //_expiration = DataHelper.readDate(in); DataHelper.readDate(in); _transportStyle = DataHelper.readString(in); @@ -251,8 +262,8 @@ public class RouterAddress extends DataStructureImpl { * readin and the signature will fail. */ public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if ((_cost < 0) || (_transportStyle == null)) - throw new DataFormatException("Not enough data to write a router address"); + if (_transportStyle == null) + throw new DataFormatException("uninitialized"); DataHelper.writeLong(out, 1, _cost); //DataHelper.writeDate(out, _expiration); DataHelper.writeDate(out, null); diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index c6d49580f7..57bfeeea34 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -14,6 +14,7 @@ import java.net.InetAddress; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -471,7 +472,8 @@ public abstract class TransportImpl implements Transport { } /** Do we increase the advertised cost when approaching conn limits? */ - public static final boolean ADJUST_COST = true; + protected static final boolean ADJUST_COST = true; + protected static final int CONGESTION_COST_ADJUSTMENT = 2; /** * What addresses are we currently listening to? @@ -563,6 +565,67 @@ public abstract class TransportImpl implements Transport { return rv; } + /** + * Get all available address we can use, + * shuffled and then sorted by cost/preference. + * Lowest cost (most preferred) first. + * @return non-null, possibly empty + * @since IPv6 + */ + protected List getTargetAddresses(RouterInfo target) { + List rv = target.getTargetAddresses(getStyle()); + // Shuffle so everybody doesn't use the first one + if (rv.size() > 1) { + Collections.shuffle(rv, _context.random()); + TransportUtil.IPv6Config config = getIPv6Config(); + int adj; + switch (config) { + case IPV6_DISABLED: + adj = 10; break; + case IPV6_NOT_PREFERRED: + adj = 1; break; + default: + case IPV6_ENABLED: + adj = 0; break; + case IPV6_PREFERRED: + adj = -1; break; + case IPV6_ONLY: + adj = -10; break; + } + Collections.sort(rv, new AddrComparator(adj)); + } + return rv; + } + + /** + * Compare based on published cost, adjusting for our IPv6 preference. + * Lowest cost (most preferred) first. + * @since IPv6 + */ + private static class AddrComparator implements Comparator { + private final int adj; + + public AddrComparator(int ipv6Adjustment) { + adj = ipv6Adjustment; + } + + public int compare(RouterAddress l, RouterAddress r) { + int lc = l.getCost(); + int rc = r.getCost(); + byte[] lip = l.getIP(); + byte[] rip = r.getIP(); + if (lip != null && lip.length == 16) + lc += adj; + if (rip != null && rip.length == 16) + rc += adj; + if (lc > rc) + return 1; + if (lc < rc) + return -1; + return 0; + } + } + /** * Notify a transport of an external address change. * This may be from a local interface, UPnP, a config change, etc. diff --git a/router/java/src/net/i2p/router/transport/TransportUtil.java b/router/java/src/net/i2p/router/transport/TransportUtil.java index 86f081daa4..b41a72e0e5 100644 --- a/router/java/src/net/i2p/router/transport/TransportUtil.java +++ b/router/java/src/net/i2p/router/transport/TransportUtil.java @@ -52,6 +52,7 @@ public abstract class TransportUtil { } private static final Map BY_NAME = new HashMap(); + private static final IPv6Config DEFAULT_IPV6_CONFIG = IPv6Config.IPV6_DISABLED; static { for (IPv6Config cfg : IPv6Config.values()) { @@ -68,17 +69,17 @@ public abstract class TransportUtil { else if (transportStyle.equals("SSU")) cfg = ctx.getProperty(SSU_IPV6_CONFIG); else - return IPv6Config.IPV6_DISABLED; + return DEFAULT_IPV6_CONFIG; return getIPv6Config(cfg); } public static IPv6Config getIPv6Config(String cfg) { if (cfg == null) - return IPv6Config.IPV6_DISABLED; + return DEFAULT_IPV6_CONFIG; IPv6Config c = BY_NAME.get(cfg); if (c != null) return c; - return IPv6Config.IPV6_DISABLED; + return DEFAULT_IPV6_CONFIG; } /** diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index 20b00f3999..2d808c5bbc 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -3,6 +3,7 @@ package net.i2p.router.transport.ntcp; import java.io.IOException; import java.net.InetSocketAddress; import java.net.InetAddress; +import java.net.Inet6Address; import java.net.UnknownHostException; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; @@ -35,6 +36,8 @@ import net.i2p.router.transport.Transport; import static net.i2p.router.transport.Transport.AddressSource.*; import net.i2p.router.transport.TransportBid; import net.i2p.router.transport.TransportImpl; +import net.i2p.router.transport.TransportUtil; +import static net.i2p.router.transport.TransportUtil.IPv6Config.*; import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.router.transport.udp.UDPTransport; import net.i2p.util.Addresses; @@ -350,7 +353,7 @@ public class NTCPTransport extends TransportImpl { * @since 0.9.6 */ private RouterAddress getTargetAddress(RouterInfo target) { - List addrs = target.getTargetAddresses(STYLE); + List addrs = getTargetAddresses(target); for (int i = 0; i < addrs.size(); i++) { RouterAddress addr = addrs.get(i); byte[] ip = addr.getIP(); @@ -501,7 +504,8 @@ public class NTCPTransport extends TransportImpl { OrderedProperties props = new OrderedProperties(); props.setProperty(RouterAddress.PROP_HOST, ia.getHostAddress()); props.setProperty(RouterAddress.PROP_PORT, Integer.toString(port)); - myAddress = new RouterAddress(STYLE, props, DEFAULT_COST); + int cost = getDefaultCost(ia instanceof Inet6Address); + myAddress = new RouterAddress(STYLE, props, cost); replaceAddress(myAddress); } } @@ -604,7 +608,8 @@ public class NTCPTransport extends TransportImpl { OrderedProperties props = new OrderedProperties(); props.setProperty(RouterAddress.PROP_HOST, bindTo); props.setProperty(RouterAddress.PROP_PORT, Integer.toString(port)); - myAddress = new RouterAddress(STYLE, props, DEFAULT_COST); + int cost = getDefaultCost(false); + myAddress = new RouterAddress(STYLE, props, cost); } if (!_endpoints.isEmpty()) { // If we are already bound to the new address, OR @@ -778,10 +783,23 @@ public class NTCPTransport extends TransportImpl { OrderedProperties props = new OrderedProperties(); props.setProperty(RouterAddress.PROP_HOST, name); props.setProperty(RouterAddress.PROP_PORT, Integer.toString(p)); - RouterAddress addr = new RouterAddress(STYLE, props, DEFAULT_COST); + int cost = getDefaultCost(false); + RouterAddress addr = new RouterAddress(STYLE, props, cost); return addr; } + private int getDefaultCost(boolean isIPv6) { + int rv = DEFAULT_COST; + if (isIPv6) { + TransportUtil.IPv6Config config = getIPv6Config(); + if (config == IPV6_PREFERRED) + rv--; + else if (config == IPV6_NOT_PREFERRED) + rv++; + } + return rv; + } + /** * UDP changed addresses, tell NTCP and (possibly) restart * @@ -831,7 +849,7 @@ public class NTCPTransport extends TransportImpl { OrderedProperties newProps = new OrderedProperties(); int cost; if (oldAddr == null) { - cost = DEFAULT_COST; + cost = getDefaultCost(ip != null && ip.length == 16); } else { cost = oldAddr.getCost(); newProps.putAll(oldAddr.getOptionsMap()); @@ -930,21 +948,24 @@ public class NTCPTransport extends TransportImpl { if (!changed) { if (oldAddr != null) { + // change cost only? int oldCost = oldAddr.getCost(); - int newCost = DEFAULT_COST; - if (TransportImpl.ADJUST_COST && !haveCapacity()) - newCost++; + int newCost = getDefaultCost(ohost != null && ohost.contains(":")); + if (ADJUST_COST && !haveCapacity()) + newCost += CONGESTION_COST_ADJUSTMENT; if (newCost != oldCost) { - oldAddr.setCost(newCost); + newAddr.setCost(newCost); if (_log.shouldLog(Log.WARN)) _log.warn("Changing NTCP cost from " + oldCost + " to " + newCost); + // fall thru and republish } else { _log.info("No change to NTCP Address"); + return; } } else { _log.info("No change to NTCP Address"); + return; } - return; } // stopListening stops the pumper, readers, and writers, so required even if diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 726c619467..ca36c3d1b6 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -38,6 +38,8 @@ import net.i2p.router.transport.Transport; import static net.i2p.router.transport.Transport.AddressSource.*; import net.i2p.router.transport.TransportBid; import net.i2p.router.transport.TransportImpl; +import net.i2p.router.transport.TransportUtil; +import static net.i2p.router.transport.TransportUtil.IPv6Config.*; import static net.i2p.router.transport.udp.PeerTestState.Role.*; import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.router.util.RandomIterator; @@ -1544,10 +1546,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * @since 0.9.6 */ RouterAddress getTargetAddress(RouterInfo target) { - List addrs = target.getTargetAddresses(STYLE); - // Shuffle so everybody doesn't use the first one - if (addrs.size() > 1) - Collections.shuffle(addrs, _context.random()); + List addrs = getTargetAddresses(target); for (int i = 0; i < addrs.size(); i++) { RouterAddress addr = addrs.get(i); if (addr.getOption("ihost0") == null) { @@ -1821,7 +1820,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // the whole mechanism is not helpful. int cost = DEFAULT_COST; if (ADJUST_COST && !haveCapacity(91)) - cost++; + cost += CONGESTION_COST_ADJUSTMENT; + if (introducersIncluded) + cost += 2; + if (isIPv6) { + TransportUtil.IPv6Config config = getIPv6Config(); + if (config == IPV6_PREFERRED) + cost--; + else if (config == IPV6_NOT_PREFERRED) + cost++; + } RouterAddress addr = new RouterAddress(STYLE, options, cost); RouterAddress current = getCurrentAddress(isIPv6); @@ -2996,7 +3004,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority if (peerInfo == null) continue; ip = null; - List addrs = peerInfo.getTargetAddresses(STYLE); + List addrs = getTargetAddresses(peerInfo); for (RouterAddress addr : addrs) { ip = addr.getIP(); if (ip != null && ip.length == 4) From 9a4cd11748a5b17d2e7d3f575e9d9348c8111bc5 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 21 May 2013 21:10:23 +0000 Subject: [PATCH 36/38] * SSU: "rectify" IPv4/v6 MTUs differently due to different header size (mod 16) * Penalize addresses w/o IP in sort --- .../src/net/i2p/router/transport/TransportImpl.java | 8 ++++++-- .../router/transport/udp/EstablishmentManager.java | 3 ++- .../java/src/net/i2p/router/transport/udp/MTU.java | 13 ++++++++++--- .../src/net/i2p/router/transport/udp/PeerState.java | 8 ++++++-- .../net/i2p/router/transport/udp/UDPAddress.java | 6 ++++-- .../net/i2p/router/transport/udp/UDPTransport.java | 5 +++-- 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 57bfeeea34..89ab0d5f2f 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -614,9 +614,13 @@ public abstract class TransportImpl implements Transport { int rc = r.getCost(); byte[] lip = l.getIP(); byte[] rip = r.getIP(); - if (lip != null && lip.length == 16) + if (lip == null) + lc += 20; + else if (lip.length == 16) lc += adj; - if (rip != null && rip.length == 16) + if (rip == null) + rc += 20; + else if (rip.length == 16) rc += adj; if (lc > rc) return 1; diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index e279411937..95250aa959 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -677,7 +677,8 @@ class EstablishmentManager { String smtu = addr.getOption(UDPAddress.PROP_MTU); if (smtu != null) { try { - int mtu = MTU.rectify(Integer.parseInt(smtu)); + boolean isIPv6 = state.getSentIP().length == 16; + int mtu = MTU.rectify(isIPv6, Integer.parseInt(smtu)); peer.setHisMTU(mtu); } catch (NumberFormatException nfe) {} } diff --git a/router/java/src/net/i2p/router/transport/udp/MTU.java b/router/java/src/net/i2p/router/transport/udp/MTU.java index 9a8fd3601f..641c121635 100644 --- a/router/java/src/net/i2p/router/transport/udp/MTU.java +++ b/router/java/src/net/i2p/router/transport/udp/MTU.java @@ -1,6 +1,7 @@ package net.i2p.router.transport.udp; import java.net.InetAddress; +import java.net.Inet6Address; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; @@ -41,7 +42,8 @@ abstract class MTU { try { // testing //return ifc.getMTU(); - return rectify(ifc.getMTU()); + boolean isIPv6 = addr instanceof Inet6Address; + return rectify(isIPv6, ifc.getMTU()); } catch (SocketException se) { // ignore } catch (Throwable t) { @@ -58,11 +60,16 @@ abstract class MTU { /** * @return min of PeerState.MIN_MTU, max of PeerState.LARGE_MTU, - * rectified so rv % 16 == 12 + * rectified so rv % 16 == 12 (IPv4) + * or rv % 16 == 0 (IPv6) */ - public static int rectify(int mtu) { + public static int rectify(boolean isIPv6, int mtu) { int rv = mtu; int mod = rv % 16; + if (isIPv6) { + rv -= mod; + return Math.max(PeerState.MIN_IPV6_MTU, Math.min(PeerState.MAX_IPV6_MTU, rv)); + } if (mod > 12) rv -= mod - 12; else if (mod < 12) diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java index a60af4d324..65e236019e 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java @@ -267,8 +267,12 @@ class PeerState { * and so PacketBuilder.buildPacket() works correctly. */ public static final int MIN_MTU = 620; - /** 1276 */ - public static final int MIN_IPV6_MTU = MTU.rectify(1280); + + /** + * IPv6/UDP header is 48 bytes, so we want MTU % 16 == 0. + */ + public static final int MIN_IPV6_MTU = 1280; + public static final int MAX_IPV6_MTU = 1472; // TODO 1488 private static final int DEFAULT_MTU = MIN_MTU; /* diff --git a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java index ff77ad0e67..55fa9788d1 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java @@ -67,8 +67,10 @@ class UDPAddress { _port = addr.getPort(); try { String mtu = addr.getOption(PROP_MTU); - if (mtu != null) - _mtu = MTU.rectify(Integer.parseInt(mtu)); + if (mtu != null) { + boolean isIPv6 = _host != null && _host.contains(":"); + _mtu = MTU.rectify(isIPv6, Integer.parseInt(mtu)); + } } catch (NumberFormatException nfe) {} String key = addr.getOption(PROP_INTRO_KEY); if (key != null) { diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index ca36c3d1b6..c068519253 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -587,8 +587,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority String p = _context.getProperty(PROP_DEFAULT_MTU); if (p != null) { try { - _mtu = MTU.rectify(Integer.parseInt(p)); - _mtu_ipv6 = Math.max(_mtu, PeerState.MIN_IPV6_MTU); + int pmtu = Integer.parseInt(p); + _mtu = MTU.rectify(false, pmtu); + _mtu_ipv6 = MTU.rectify(true, pmtu); return _mtu; } catch (NumberFormatException nfe) {} } From 3daf287de836ab88400bbe6ab27c051b49337165 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 24 May 2013 13:46:17 +0000 Subject: [PATCH 37/38] GeoIPv6: - Handle gzipped input data in merge tool - Add script to generate compressed data - Add local additions - Add compressed data file, generated from Maxmind data fetched 2013-05-24 - Include data in installer and updater - Update Maxmind license info, now CC-SA 3.0 --- LICENSE.txt | 2 +- build.xml | 5 ++- installer/resources/geoipv6-extras.csv | 9 +++++ installer/resources/geoipv6.dat.gz | Bin 0 -> 88279 bytes installer/resources/makegeoipv6.sh | 19 +++++++++ licenses/LICENSE-GeoIP.txt | 36 +++--------------- .../src/net/i2p/router/transport/GeoIPv6.java | 11 +++++- 7 files changed, 49 insertions(+), 33 deletions(-) create mode 100644 installer/resources/geoipv6-extras.csv create mode 100644 installer/resources/geoipv6.dat.gz create mode 100755 installer/resources/makegeoipv6.sh diff --git a/LICENSE.txt b/LICENSE.txt index 741c1ea514..9ee382af21 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -207,7 +207,7 @@ Applications: FatCow icons: See licenses/LICENSE-FatCowIcons.txt GeoIP Data: - Copyright (c) 2008 MaxMind, Inc. All Rights Reserved. + This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com/ See licenses/LICENSE-GeoIP.txt Router Console and I2PSnark themes: diff --git a/build.xml b/build.xml index 6caac71705..07073e1a30 100644 --- a/build.xml +++ b/build.xml @@ -1121,7 +1121,9 @@ - + + + @@ -1137,6 +1139,7 @@ + diff --git a/installer/resources/geoipv6-extras.csv b/installer/resources/geoipv6-extras.csv new file mode 100644 index 0000000000..414882684b --- /dev/null +++ b/installer/resources/geoipv6-extras.csv @@ -0,0 +1,9 @@ +# Local geoIPv6 additions +# Format: from IP,to IP,,,country code[,"country name"] +#### +# common tunnel brokers +2001:5c0:1000:a::,2001:5c0:1000:a::,,,X1,"Freenet6 anonymous" +2001:5c0:1000:b::,2001:5c0:1000:b::,,,X2,"Freenet6 authenticated" +2001:5c0:1100::,2001:5c0:11ff:ffff:ffff:ffff:ffff:ffff,,,X3,"Freenet6 delegated" +# other interesting addresses +2002::,2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff,,,X4,"IPv4 compatibility" diff --git a/installer/resources/geoipv6.dat.gz b/installer/resources/geoipv6.dat.gz new file mode 100644 index 0000000000000000000000000000000000000000..fe9273dd338227c489b5bf5d809c370fb1b88179 GIT binary patch literal 88279 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 XK;Jm6Kn}-U{ZYoe{)N@{pMUrtdHJ~2 literal 0 HcmV?d00001 diff --git a/installer/resources/makegeoipv6.sh b/installer/resources/makegeoipv6.sh new file mode 100755 index 0000000000..390e4ed4b6 --- /dev/null +++ b/installer/resources/makegeoipv6.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# +# Fetch the latest file from Maxmind, merge with +# our additions, and compress. +# + +FILE1=GeoIPv6.csv.gz +FILE2=geoipv6-extras.csv +FILEOUT=geoipv6.dat.gz + +rm -f $FILE1 $FILEOUT +wget http://geolite.maxmind.com/download/geoip/database/$FILE1 +if [ "$?" -ne "0" ] +then + echo 'Cannot fetch' + exit 1 +fi +java -cp ../../build/i2p.jar:../../build/router.jar net.i2p.router.transport.GeoIPv6 $FILE1 $FILE2 $FILEOUT +exit $? diff --git a/licenses/LICENSE-GeoIP.txt b/licenses/LICENSE-GeoIP.txt index 6f5b0713d0..0c91833ef8 100644 --- a/licenses/LICENSE-GeoIP.txt +++ b/licenses/LICENSE-GeoIP.txt @@ -1,32 +1,8 @@ -OPEN DATA LICENSE (GeoLite Country and GeoLite City databases) +The GeoLite databases are distributed under the +Creative Commons Attribution-ShareAlike 3.0 Unported License +http://creativecommons.org/licenses/by-sa/3.0/ . +The attribution requirement may be met by including the following in +all advertising and documentation mentioning features of or use of this database: -Copyright (c) 2008 MaxMind, Inc. All Rights Reserved. - -All advertising materials and documentation mentioning features or use of -this database must display the following acknowledgment: "This product includes GeoLite data created by MaxMind, available from -http://maxmind.com/" - -Redistribution and use with or without modification, are permitted provided -that the following conditions are met: -1. Redistributions must retain the above copyright notice, this list of -conditions and the following disclaimer in the documentation and/or other -materials provided with the distribution. -2. All advertising materials and documentation mentioning features or use of -this database must display the following acknowledgement: -"This product includes GeoLite data created by MaxMind, available from -http://maxmind.com/" -3. "MaxMind" may not be used to endorse or promote products derived from this -database without specific prior written permission. - -THIS DATABASE IS PROVIDED BY MAXMIND, INC ``AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL MAXMIND BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -DATABASE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - +http://www.maxmind.com/" diff --git a/router/java/src/net/i2p/router/transport/GeoIPv6.java b/router/java/src/net/i2p/router/transport/GeoIPv6.java index 93affec888..36e443c083 100644 --- a/router/java/src/net/i2p/router/transport/GeoIPv6.java +++ b/router/java/src/net/i2p/router/transport/GeoIPv6.java @@ -151,7 +151,9 @@ class GeoIPv6 { int count = 0; InputStream in = null; try { - in = new FileInputStream(geoFile); + in = new BufferedInputStream(new FileInputStream(geoFile)); + if (geoFile.getName().endsWith(".gz")) + in = new GZIPInputStream(in); String buf = null; BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); while ((buf = br.readLine()) != null) { @@ -314,6 +316,13 @@ class GeoIPv6 { return rv; } + /** + * Merge and compress CSV files to I2P compressed format + * + * GeoIP infile1.csv[.gz] [infile2.csv[.gz]...] outfile.dat.gz + * + * Used to create the file for distribution, do not comment out + */ public static void main(String args[]) { if (args.length < 2) { System.err.println("Usage: GeoIP infile1.csv [infile2.csv...] outfile.dat.gz"); From 6c62c1f3625933be9a19d897d2a69b008f9d40df Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 24 May 2013 14:11:35 +0000 Subject: [PATCH 38/38] enable IPv6 geoip lookup --- .../i2p/router/transport/CommSystemFacadeImpl.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 42dc7c4a9a..bb60b978ad 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -280,15 +280,15 @@ public class CommSystemFacadeImpl extends CommSystemFacade { /** * Uses the transport IP first because that lookup is fast, - * then the SSU IP from the netDb. + * then the IP from the netDb. * * @return two-letter lower-case country code or null */ @Override public String getCountry(Hash peer) { byte[] ip = TransportImpl.getIP(peer); - // Assume IPv6 doesn't have geoIP for now - if (ip != null && ip.length == 4) + //if (ip != null && ip.length == 4) + if (ip != null) return _geoIP.get(ip); RouterInfo ri = _context.netDb().lookupRouterInfoLocally(peer); if (ri == null) @@ -300,11 +300,12 @@ public class CommSystemFacadeImpl extends CommSystemFacade { } private static byte[] getIP(RouterInfo ri) { - // Return first IPv4 we find, any transport + // Return first IP (v4 or v6) we find, any transport // Assume IPv6 doesn't have geoIP for now for (RouterAddress ra : ri.getAddresses()) { byte[] rv = ra.getIP(); - if (rv != null && rv.length == 4) + //if (rv != null && rv.length == 4) + if (rv != null) return rv; } return null;