From 94e34ff366b23226f16796fe1c7d1a533602c97d Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 8 May 2013 16:48:39 +0000 Subject: [PATCH] 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++;