diff --git a/core/java/src/net/i2p/data/RouterAddress.java b/core/java/src/net/i2p/data/RouterAddress.java index c21f0f5bf..d003cc050 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 82e73c2e1..1386b8727 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 82987ae8e..d2006750b 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 4bece170d..61f024146 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 a8d79389b..52d1f2016 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 acd9bf0cd..3c1942ce5 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 000000000..f581b1944 --- /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 abcd8214e..cee870c96 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 63acc65c0..fb368b37b 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 66cd37e81..56f879700 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 567ce43ec..ba69faf99 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); }