diff --git a/history.txt b/history.txt index 6ac2679d6..4c195dd23 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,14 @@ +2021-03-25 zzz + * SSU: Implement IPv6 introductions (proposal 158) + +2021-03-23 zzz + * NetDB: Sort published addresses for consistency + * SSU: Fix IPv6 transition from firewalled to non-firewalled + +2021-03-22 zzz + * Sybil: Persist blocklist + * Tunnels: Don't count established routers as new in OBEP throttle (ticket #2245) + 2021-03-20 zzz * Tunnels: Fix RED dropping for part. tunnels (Gitlab MR #24) diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 6a9511fad..f40f9f7a0 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Git"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 8; + public final static long BUILD = 9; /** for example "-test" */ public final static String EXTRA = ""; 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 e846ed969..92746dffd 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -375,7 +375,7 @@ class EstablishmentManager { // w/o ext options, it's always 'requested', no need to set // don't ask if they are indirect boolean requestIntroduction = allowExtendedOptions && !isIndirect && - _transport.introducersMaybeRequired(); + _transport.introducersMaybeRequired(TransportUtil.isIPv6(ra)); state = new OutboundEstablishState(_context, maybeTo, to, toIdentity, allowExtendedOptions, requestIntroduction, @@ -500,9 +500,8 @@ class EstablishmentManager { // TODO if already we have their RI, only offer if they need it (no 'C' cap) // if extended options, only if they asked for it if (state.isIntroductionRequested() && - state.getSentIP().length == 4 && state.getSentPort() >= 1024 && - _transport.canIntroduce()) { + _transport.canIntroduce(state.getSentIP().length == 16)) { // ensure > 0 long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE); state.setSentRelayTag(tag); @@ -626,8 +625,8 @@ class EstablishmentManager { //if (admitted > 0) // _log.log(Log.CRIT, "Admitted " + admitted + " with " + remaining + " remaining queued and " + active + " active"); - if (_log.shouldLog(Log.INFO)) - _log.info("Outbound established completely! yay: " + state); + if (_log.shouldDebug()) + _log.debug("Outbound established: " + state); PeerState peer = handleCompletelyEstablished(state); notifyActivity(); return peer; @@ -761,8 +760,8 @@ class EstablishmentManager { */ private void sendInboundComplete(PeerState peer) { // SimpleTimer.getInstance().addEvent(new PublishToNewInbound(peer), 10*1000); - if (_log.shouldLog(Log.INFO)) - _log.info("Completing to the peer after IB confirm: " + peer); + if (_log.shouldDebug()) + _log.debug("IB confirm: " + peer); DeliveryStatusMessage dsm = new DeliveryStatusMessage(_context); dsm.setArrival(_networkID); // overloaded, sure, but future versions can check this // This causes huge values in the inNetPool.droppedDeliveryStatusDelay stat 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 5c751bff2..368b9582b 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -181,10 +181,11 @@ class IntroductionManager { * and we want to keep our introducers valid. * * @param current current router address, may be null + * @param ipv6 what type is the current address we need introducers for? * @param ssuOptions out parameter, options are added * @return number of introducers added */ - public int pickInbound(RouterAddress current, Properties ssuOptions, int howMany) { + public int pickInbound(RouterAddress current, boolean ipv6, Properties ssuOptions, int howMany) { int start = _context.random().nextInt(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Picking inbound out of " + _inbound.size()); @@ -199,8 +200,33 @@ class IntroductionManager { if (sz <= howMany + 2) inactivityCutoff -= UDPTransport.EXPIRE_TIMEOUT / 4; List introducers = new ArrayList(howMany); + String exp = Long.toString((now + INTRODUCER_EXPIRATION) / 1000); + + // reuse old ones if ok + if (current != null) { + UDPAddress ua = new UDPAddress(current); + for (int i = 0; i < ua.getIntroducerCount(); i++) { + long lexp = ua.getIntroducerExpiration(i); + if (lexp > 0 && lexp < now + UDPTransport.INTRODUCER_EXPIRATION_MARGIN) + continue; + long tag = ua.getIntroducerTag(i); + if (!isInboundTagValid(tag)) + continue; + introducers.add(new Introducer(ua.getIntroducerHost(i).getAddress(), + ua.getIntroducerPort(i), + ua.getIntroducerKey(i), + tag, + Long.toString(ua.getIntroducerExpiration(i) / 1000))); + if (_log.shouldInfo()) + _log.info("Reusing introducer: " + ua.getIntroducerHost(i)); + found++; + } + } + for (int i = 0; i < sz && found < howMany; i++) { PeerState cur = peers.get((start + i) % sz); + if (cur.isIPv6() != ipv6) + continue; RouterInfo ri = _context.netDb().lookupRouterInfoLocally(cur.getRemotePeer()); if (ri == null) { if (_log.shouldLog(Log.INFO)) @@ -233,7 +259,16 @@ class IntroductionManager { continue; } int oldFound = found; + loop: for (RouterAddress ra : ras) { + String host = ra.getHost(); + if (host == null) + continue; + // dup check of reused introducers + for (Introducer intro : introducers) { + if (host.equals(intro.sip)) + continue loop; + } byte[] ip = ra.getIP(); int port = ra.getPort(); if (!isValid(ip, port, true)) @@ -249,7 +284,7 @@ class IntroductionManager { byte[] ikey = ura.getIntroKey(); if (ikey == null) continue; - introducers.add(new Introducer(ip, port, ikey, cur.getTheyRelayToUsAs())); + introducers.add(new Introducer(ip, port, ikey, cur.getTheyRelayToUsAs(), exp)); found++; // two per router max if (found - oldFound >= 2) @@ -261,14 +296,13 @@ class IntroductionManager { // we sort them so a change in order only won't happen, and won't cause a republish Collections.sort(introducers); - String exp = Long.toString((now + INTRODUCER_EXPIRATION) / 1000); for (int i = 0; i < found; i++) { Introducer in = introducers.get(i); ssuOptions.setProperty(UDPAddress.PROP_INTRO_HOST_PREFIX + i, in.sip); ssuOptions.setProperty(UDPAddress.PROP_INTRO_PORT_PREFIX + i, in.sport); ssuOptions.setProperty(UDPAddress.PROP_INTRO_KEY_PREFIX + i, in.skey); ssuOptions.setProperty(UDPAddress.PROP_INTRO_TAG_PREFIX + i, in.stag); - String sexp = exp; + String sexp = in.sexp; // look for existing expiration in current published // and reuse if still recent enough, so deepEquals() won't fail in UDPT.rEA if (current != null) { @@ -306,13 +340,14 @@ class IntroductionManager { * @since 0.9.18 */ private static class Introducer implements Comparable { - public final String sip, sport, skey, stag; + public final String sip, sport, skey, stag, sexp; - public Introducer(byte[] ip, int port, byte[] key, long tag) { + public Introducer(byte[] ip, int port, byte[] key, long tag, String exp) { sip = Addresses.toString(ip); sport = String.valueOf(port); skey = Base64.encode(key); stag = String.valueOf(tag); + sexp = exp; } @Override @@ -363,13 +398,22 @@ class IntroductionManager { * Not as elaborate as pickInbound() above. * Just a quick check to see how many volunteers we know, * which the Transport uses to see if we need more. + * + * @param ipv6 what type of address are they introducing us for * @return number of peers that have volunteered to introduce us */ - int introducerCount() { - return _inbound.size(); + int introducerCount(boolean ipv6) { + int rv = 0; + for (PeerState ps : _inbound.values()) { + if (ps.isIPv6() == ipv6) + rv++; + } + return rv; } /** + * Combined IPv4 and IPv6 + * * @return number of peers we have volunteered to introduce * @since 0.9.3 */ @@ -403,7 +447,8 @@ class IntroductionManager { reader.getRelayIntroReader().readIP(ip, 0); int port = reader.getRelayIntroReader().readPort(); - if ((!isValid(ip, port)) || (!isValid(bob.getIP(), bob.getPort()))) { + // allow IPv6 as of 0.9.50 + if ((!isValid(ip, port, true)) || (!isValid(bob.getIP(), bob.getPort(), true))) { if (_log.shouldLog(Log.WARN)) _log.warn("Bad relay intro from " + bob + " for " + Addresses.toString(ip, port)); _context.statManager().addRateData("udp.relayBadIP", 1); @@ -530,10 +575,10 @@ class IntroductionManager { return; } // check again if IP was provided - // here we do not allow IPv6 + // allow IPv6 as of 0.9.50 RemoteHostId aliceRelayID; if (ipIncluded) { - if (!isValid(aliceIP, alicePort)) { + if (!isValid(aliceIP, alicePort, true)) { if (_log.shouldWarn()) _log.warn("Bad relay req from " + alice + " for " + Addresses.toString(aliceIP, alicePort)); _context.statManager().addRateData("udp.relayBadIP", 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 96c738a0e..d4be00c3a 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -87,7 +87,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // only for logging, to be removed private long _reachabilityStatusLastUpdated; private int _reachabilityStatusUnchanged; - private long _introducersSelectedOn; + private long _v4IntroducersSelectedOn; + private long _v6IntroducersSelectedOn; private long _lastInboundReceivedOn; private final DHSessionKeyBuilder.Factory _dhFactory; private final SSUHMACGenerator _hmac; @@ -189,6 +190,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority private static final String CAP_TESTING = Character.toString(UDPAddress.CAPACITY_TESTING); private static final String CAP_TESTING_INTRO = CAP_TESTING + UDPAddress.CAPACITY_INTRODUCER; private static final String CAP_TESTING_4 = CAP_TESTING + CAP_IPV4; + private static final String CAP_TESTING_6 = CAP_TESTING + CAP_IPV6; /** how many relays offered to us will we use at a time? */ public static final int PUBLIC_RELAY_COUNT = 3; @@ -327,7 +329,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _reachabilityStatus = Status.UNKNOWN; _reachabilityStatusPending = Status.OK; _introManager = new IntroductionManager(_context, this); - _introducersSelectedOn = -1; + _v4IntroducersSelectedOn = -1; + _v6IntroducersSelectedOn = -1; _lastInboundReceivedOn = -1; _hmac = new SSUHMACGenerator(); _mtu = PeerState.LARGE_MTU; @@ -1815,9 +1818,36 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority private boolean locked_needsRebuild() { if (_needsRebuild) return true; // simple enough if (_context.router().isHidden()) return false; - boolean v6Only = getIPv6Config() == IPV6_ONLY; - RouterAddress addr = getCurrentAddress(v6Only); - if (!v6Only && introducersRequired()) { + TransportUtil.IPv6Config config = getIPv6Config(); + // IPv4 + boolean v6Only = config == IPV6_ONLY; + if (!v6Only) { + RouterAddress addr = getCurrentAddress(false); + if (locked_needsRebuild(addr, false)) + return true; + } + // IPv6 + // Disable for now until we have introducers working + // because it loops too quickly + /* + boolean v4Only = config == IPV6_DISABLED; + if (!v4Only && _hasIPv6Address) { + RouterAddress addr = getCurrentAddress(true); + if (locked_needsRebuild(addr, true)) + return true; + } + */ + return false; + } + + /** + * Does this address need rebuilding? + * + * @param addr may be null + * @since 0.9.50 split out from above + */ + private boolean locked_needsRebuild(RouterAddress addr, boolean ipv6) { + if (introducersRequired(ipv6)) { UDPAddress ua = new UDPAddress(addr); long now = _context.clock().now(); int valid = 0; @@ -1825,7 +1855,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority long exp = ua.getIntroducerExpiration(i); if (exp > 0 && exp < now + INTRODUCER_EXPIRATION_MARGIN) { if (_log.shouldWarn()) - _log.warn("Introducer " + i + " is expiring soon, need to replace"); + _log.warn((ipv6 ? "IPv6" : "IPv4") + " Introducer " + i + " expiring soon, need to replace"); continue; } long tag = ua.getIntroducerTag(i); @@ -1833,29 +1863,29 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority valid++; } else { if (_log.shouldWarn()) - _log.warn("Introducer " + i + " is no longer connected, need to replace"); + _log.warn((ipv6 ? "IPv6" : "IPv4") + " Introducer " + i + " no longer connected, need to replace"); } } - long sinceSelected = now - _introducersSelectedOn; + long sinceSelected = now - (ipv6 ? _v6IntroducersSelectedOn : _v4IntroducersSelectedOn); if (valid >= PUBLIC_RELAY_COUNT) { // try to shift 'em around every 10 minutes or so - if (sinceSelected > 17*60*1000) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Our introducers are valid, but haven't changed in " + DataHelper.formatDuration(sinceSelected) + ", so lets rechoose"); - return true; - } else { + //if (sinceSelected > 17*60*1000) { + // if (_log.shouldLog(Log.WARN)) + // _log.warn((ipv6 ? "IPv6" : "IPv4") + " introducers valid, haven't changed in " + DataHelper.formatDuration(sinceSelected) + ", reselecting"); + // return true; + //} else { if (_log.shouldLog(Log.INFO)) - _log.info("Our introducers are valid and were selected " + DataHelper.formatDuration(sinceSelected) + " ago"); + _log.info((ipv6 ? "IPv6" : "IPv4") + " introducers valid, selected " + DataHelper.formatDuration(sinceSelected) + " ago"); return false; - } + //} } else if (sinceSelected > 2*60*1000) { // Rate limit to prevent rapid churn after transition to firewalled or at startup if (_log.shouldLog(Log.INFO)) - _log.info("Need more introducers (have " +valid + " need " + PUBLIC_RELAY_COUNT + ')'); + _log.info((ipv6 ? "IPv6" : "IPv4") + " Need more introducers (have " + valid + " need " + PUBLIC_RELAY_COUNT + ')'); return true; } else { if (_log.shouldLog(Log.INFO)) - _log.info("Need more introducers (have " +valid + " need " + PUBLIC_RELAY_COUNT + ')' + + _log.info((ipv6 ? "IPv6" : "IPv4") + " Need more introducers (have " + valid + " need " + PUBLIC_RELAY_COUNT + ')' + " but we just chose them " + DataHelper.formatDuration(sinceSelected) + " ago so wait"); // TODO also check to see if we actually have more available return false; @@ -1871,10 +1901,10 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } if (rv) { if (_log.shouldLog(Log.INFO)) - _log.info("Need to initialize our direct SSU info (" + Addresses.toString(externalListenHost, externalListenPort) + ')'); + _log.info((ipv6 ? "IPv6" : "IPv4") + " 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"); + _log.info((ipv6 ? "IPv6" : "IPv4") + " Our direct SSU info is initialized, but not used in our address yet"); rv = true; } else { //_log.info("Our direct SSU info is initialized"); @@ -2053,11 +2083,15 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // (Otherwise we only talk UDP to those that are firewalled, and we will // never get any introducers) int count = _peersByIdent.size(); + boolean ipv6 = TransportUtil.isIPv6(addr); if (alwaysPreferUDP()) { return _cachedBid[SLOW_PREFERRED_BID]; - } else if (count < _min_peers || - (_haveIPv6Address && count < _min_v6_peers) || - (introducersRequired() && _introManager.introducerCount() < MIN_INTRODUCER_POOL)) { + } else if ((!ipv6 && count < _min_peers) || + (ipv6 && _haveIPv6Address && count < _min_v6_peers) || + (introducersRequired(ipv6) && + addr.getOption(UDPAddress.PROP_CAPACITY) != null && + addr.getOption(UDPAddress.PROP_CAPACITY).indexOf(UDPAddress.CAPACITY_INTRODUCER) >= 0 && + _introManager.introducerCount(ipv6) < MIN_INTRODUCER_POOL)) { // Even if we haven't hit our minimums, give NTCP a chance some of the time. // This may make things work a little faster at startup // (especially when we have an IPv6 address and the increased minimums), @@ -2403,8 +2437,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return rv; } } else { - if (!introducersRequired()) { - boolean v6Only = getIPv6Config() == IPV6_ONLY; + boolean v6Only = getIPv6Config() == IPV6_ONLY; + if (!introducersRequired(v6Only)) { RouterAddress cur = getCurrentExternalAddress(v6Only); if (cur != null) host = cur.getHost(); @@ -2494,7 +2528,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority boolean directIncluded; // DNS name assumed IPv4 - boolean introducersRequired = (!isIPv6) && introducersRequired(); + boolean introducersRequired = introducersRequired(isIPv6); if (!introducersRequired && allowDirectUDP() && port > 0 && host != null) { options.setProperty(UDPAddress.PROP_PORT, String.valueOf(port)); options.setProperty(UDPAddress.PROP_HOST, host); @@ -2509,28 +2543,34 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // deepEquals() below will not fail even with same introducers. // Was only a problem when we had very very few peers to pick from. RouterAddress current = getCurrentAddress(isIPv6); - int found = _introManager.pickInbound(current, options, PUBLIC_RELAY_COUNT); + int found = _introManager.pickInbound(current, isIPv6, options, PUBLIC_RELAY_COUNT); if (found > 0) { if (_log.shouldLog(Log.INFO)) - _log.info("Direct? " + directIncluded + " reqd? " + introducersRequired + - " picked introducers: " + found); - _introducersSelectedOn = _context.clock().now(); + _log.info("ipv6? " + isIPv6 + " picked introducers: " + found); + long now = _context.clock().now(); + if (isIPv6) + _v6IntroducersSelectedOn = now; + else + _v4IntroducersSelectedOn = now; introducersIncluded = true; } else { if (_log.shouldLog(Log.WARN)) - _log.warn("Direct? " + directIncluded + " reqd? " + introducersRequired + - " no introducers"); + _log.warn("ipv6? " + isIPv6 + " no introducers"); } } // if we have explicit external addresses, they had better be reachable String caps; - if (introducersRequired || !canIntroduce()) { - if (!directIncluded && !isIPv6 && - _context.getProperty(PROP_TRANSPORT_CAPS, ENABLE_TRANSPORT_CAPS)) - caps = CAP_TESTING_4; - else + if (introducersRequired || !canIntroduce(isIPv6)) { + if (!directIncluded && + _context.getProperty(PROP_TRANSPORT_CAPS, ENABLE_TRANSPORT_CAPS)) { + if (isIPv6) + caps = CAP_TESTING_6; + else + caps = CAP_TESTING_4; + } else { caps = CAP_TESTING; + } } else { caps = CAP_TESTING_INTRO; } @@ -2620,7 +2660,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } 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")); + + introducersRequired + + " ipv6? " + isIPv6 + + ')', new Exception()); _needsRebuild = true; // save the external address, even if we didn't publish it if (port > 0 && host != null) { @@ -2793,9 +2835,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority /** * Do we require introducers? - * Currently for IPv4 only. */ - public boolean introducersRequired() { + public boolean introducersRequired(boolean ipv6) { + //if (ipv6) return false; /****************** * Don't do this anymore, as we are removing the checkbox from the UI, * and we rarely if ever see the problem of false negatives for firewall detection - @@ -2809,66 +2851,110 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } *******************/ Status status = getReachabilityStatus(); - switch (status) { - case REJECT_UNSOLICITED: - case DIFFERENT: - case IPV4_FIREWALLED_IPV6_OK: - case IPV4_FIREWALLED_IPV6_UNKNOWN: - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Require introducers, because our status is " + status); - return true; - - default: - if (!allowDirectUDP()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Require introducers, because we do not allow direct UDP connections"); - return true; - } + TransportUtil.IPv6Config config = getIPv6Config(); + if (ipv6) { + if (!_haveIPv6Address) return false; + if (config == IPV6_DISABLED) + return false; + // must be published with '6' cap + if (!_context.getProperty(PROP_TRANSPORT_CAPS, ENABLE_TRANSPORT_CAPS)) + return false; + switch (status) { + case REJECT_UNSOLICITED: + case DIFFERENT: + case IPV4_OK_IPV6_FIREWALLED: + case IPV4_UNKNOWN_IPV6_FIREWALLED: + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Require IPv6 introducers, status is " + status); + return true; + } + } else { + if (config == IPV6_ONLY) + return false; + switch (status) { + case REJECT_UNSOLICITED: + case DIFFERENT: + case IPV4_FIREWALLED_IPV6_OK: + case IPV4_FIREWALLED_IPV6_UNKNOWN: + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Require IPv4 introducers, status is " + status); + return true; + } } + if (!allowDirectUDP()) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Require introducers, because we do not allow direct UDP connections"); + return true; + } + return false; } /** * MIGHT we require introducers? * This is like introducersRequired, but if we aren't sure, this returns true. * Used only by EstablishmentManager. - * Currently for IPv4 only. * * @since 0.9.24 */ - boolean introducersMaybeRequired() { + boolean introducersMaybeRequired(boolean ipv6) { + //if (ipv6) return false; Status status = getReachabilityStatus(); - switch (status) { - case REJECT_UNSOLICITED: - case DIFFERENT: - case IPV4_FIREWALLED_IPV6_OK: - case IPV4_FIREWALLED_IPV6_UNKNOWN: - case IPV4_UNKNOWN_IPV6_OK: - case IPV4_UNKNOWN_IPV6_FIREWALLED: - case UNKNOWN: - return _introManager.introducerCount() < 3 * MIN_INTRODUCER_POOL; + TransportUtil.IPv6Config config = getIPv6Config(); + if (ipv6) { + if (!_haveIPv6Address) + return false; + if (config == IPV6_DISABLED) + return false; + // must be published with '6' cap + if (!_context.getProperty(PROP_TRANSPORT_CAPS, ENABLE_TRANSPORT_CAPS)) + return false; + switch (status) { + case REJECT_UNSOLICITED: + case DIFFERENT: + case IPV4_OK_IPV6_FIREWALLED: + case IPV4_UNKNOWN_IPV6_FIREWALLED: + case IPV4_OK_IPV6_UNKNOWN: + case IPV4_FIREWALLED_IPV6_UNKNOWN: + case UNKNOWN: + return _introManager.introducerCount(true) < 3 * MIN_INTRODUCER_POOL; + } + } else { + if (config == IPV6_ONLY) + return false; + switch (status) { + case REJECT_UNSOLICITED: + case DIFFERENT: + case IPV4_FIREWALLED_IPV6_OK: + case IPV4_FIREWALLED_IPV6_UNKNOWN: + case IPV4_UNKNOWN_IPV6_OK: + case IPV4_UNKNOWN_IPV6_FIREWALLED: + case UNKNOWN: + return _introManager.introducerCount(false) < 3 * MIN_INTRODUCER_POOL; - default: - return !allowDirectUDP(); + } } + return !allowDirectUDP(); } /** * For EstablishmentManager. - * Currently for IPv4 only. + * * @since 0.9.3 */ - boolean canIntroduce() { + boolean canIntroduce(boolean ipv6) { // we don't expect inbound connections when hidden, but it could happen // Don't offer if we are approaching max connections. While Relay Intros do not // count as connections, we have to keep the connection to this peer up longer if // we are offering introductions. return (!_context.router().isHidden()) && - (!introducersRequired()) && + (!introducersRequired(ipv6)) && haveCapacity() && (!_context.netDb().floodfillEnabled()) && - getIPv6Config() != IPV6_ONLY && + (!ipv6 || _haveIPv6Address) && + ((!ipv6 && getIPv6Config() != IPV6_ONLY) || + (ipv6 && getIPv6Config() != IPV6_DISABLED)) && _introManager.introducedCount() < IntroductionManager.MAX_OUTBOUND && _introManager.introducedCount() < getMaxConnections() / 4; } @@ -3503,7 +3589,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority */ private class PingIntroducers implements SimpleTimer.TimedEvent { public void timeReached() { - if (introducersRequired()) + if (introducersRequired(false) || introducersRequired(true)) _introManager.pingIntroducers(); } }