diff --git a/history.txt b/history.txt index 38dbf09088438b5fa206ce482202dd11a0536c4f..a9bdd48a23a3db5aab3f9d8dd7251cda3f04c52e 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,24 @@ +2023-01-30 zzz + * Tunnels: Refactor peer selection + +2023-01-27 zzz + * Console: Debug page cleanups + * Tools: Add CLI reseed test + * Tunnels: Reduce grace period from 120 to 90 sec. + +2023-01-26 zzz + * Console: Add revision and build date to version info + * i2psnark: Search fixes + +2023-01-25 zzz + * Util: New thread-unsafe version of ObjectCounter + +2023-01-24 zzz + * Router: Preliminary support for congestion caps (proposal 162) + +2023-01-23 zzz + * i2psnark standalone: Fix running from outside the directory + 2023-01-22 zzz * Build: Fix list of changed files in manifests * i2psnark: Add max files per torrent config diff --git a/router/java/src/net/i2p/router/CommSystemFacade.java b/router/java/src/net/i2p/router/CommSystemFacade.java index 0b7c70f399f27d7957fc2496aeb3466abfefb229..64cd9b9de68fc6b03c2694f475af210b13301d99 100644 --- a/router/java/src/net/i2p/router/CommSystemFacade.java +++ b/router/java/src/net/i2p/router/CommSystemFacade.java @@ -13,7 +13,6 @@ import java.io.Writer; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; @@ -171,7 +170,7 @@ public abstract class CommSystemFacade implements Service { * @return the hashes of all the routers we are connected to, non-null * @since 0.9.34 */ - public abstract Set<Hash> getEstablished(); + public abstract List<Hash> getEstablished(); /** @since 0.8.13 */ public boolean isDummy() { return true; } @@ -395,9 +394,11 @@ public abstract class CommSystemFacade implements Service { IPV4_UNKNOWN_IPV6_OK(STATUS_IPV4_UNKNOWN_IPV6_OK, _x("IPv4: Testing; IPv6: OK")), IPV4_FIREWALLED_IPV6_OK(STATUS_IPV4_FIREWALLED_IPV6_OK, _x("IPv4: Firewalled; IPv6: OK")), IPV4_DISABLED_IPV6_OK(STATUS_IPV4_DISABLED_IPV6_OK, _x("IPv4: Disabled; IPv6: OK")), + /** IPv4 symmetric NAT (not source NAT) */ IPV4_SNAT_IPV6_OK(STATUS_IPV4_SNAT_IPV6_OK, _x("IPv4: Symmetric NAT; IPv6: OK")), /** IPv4 symmetric NAT, IPv6 firewalled or disabled or no address */ DIFFERENT(STATUS_DIFFERENT, _x("Symmetric NAT")), + /** IPv4 symmetric NAT (not source NAT) */ IPV4_SNAT_IPV6_UNKNOWN(STATUS_IPV4_SNAT_IPV6_UNKNOWN, _x("IPv4: Symmetric NAT; IPv6: Testing")), IPV4_FIREWALLED_IPV6_UNKNOWN(STATUS_IPV4_FIREWALLED_IPV6_UNKNOWN, _x("IPv4: Firewalled; IPv6: Testing")), /** IPv4 firewalled, IPv6 firewalled or disabled or no address */ diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 35ab2b80b1541b415c48542b42a802e96ab15702..9bc4388a0cf40d2ea51e92e482ebf7e1c2f6b7dd 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 = 4; + public final static long BUILD = 5; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/dummy/VMCommSystem.java b/router/java/src/net/i2p/router/dummy/VMCommSystem.java index 6f2df37924d8e8324421af9a3b26d0cfa88fd73e..ead455361cbe37ff44e91001319e83cc870e0a4e 100644 --- a/router/java/src/net/i2p/router/dummy/VMCommSystem.java +++ b/router/java/src/net/i2p/router/dummy/VMCommSystem.java @@ -3,10 +3,10 @@ package net.i2p.router.dummy; import java.io.IOException; import java.io.Writer; import java.util.Collections; +import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; +import java.util.List; import java.util.Map; -import java.util.Set; import net.i2p.data.Hash; import net.i2p.data.i2np.I2NPMessage; @@ -67,10 +67,10 @@ public class VMCommSystem extends CommSystemFacade { public boolean isEstablished(Hash peer) { return _commSystemFacades.containsKey(peer); } - public Set<Hash> getEstablished() { - Set<Hash> rv; + public List<Hash> getEstablished() { + List<Hash> rv; synchronized (_commSystemFacades) { - rv = new HashSet<Hash>(_commSystemFacades.keySet()); + rv = new ArrayList<Hash>(_commSystemFacades.keySet()); } Hash us = _context.routerHash(); if (us != null) diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java index 568dc8e9d922ab68d7311fbc14f7d1357b4372ed..ce23263de352e8f7010c3c6a2c5babe8960437d6 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java +++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java @@ -604,7 +604,7 @@ public class ProfileOrganizer { * Caution, this does NOT cascade further to non-connected peers, so it should only * be used when there is a good number of connected peers. * - * @param exclude non-null, WARNING - side effect, all not-connected peers are added + * @param exclude non-null, not-connected peers will NOT be added, as of 0.9.58 */ public void selectActiveNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches) { selectActiveNotFailingPeers(howMany, exclude, matches, 0, null); @@ -621,7 +621,7 @@ public class ProfileOrganizer { * Caution, this does NOT cascade further to non-connected peers, so it should only * be used when there is a good number of connected peers. * - * @param exclude non-null, WARNING - side effect, all not-connected peers are added + * @param exclude non-null, not-connected peers will NOT be added, as of 0.9.58 * @param mask 0-4 Number of bytes to match to determine if peers in the same IP range should * not be in the same tunnel. 0 = disable check; 1 = /8; 2 = /16; 3 = /24; 4 = exact IP match * @param ipSet may be null only if mask is 0 @@ -629,14 +629,12 @@ public class ProfileOrganizer { */ public void selectActiveNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, int mask, MaskedIPSet ipSet) { if (matches.size() < howMany) { - Set<Hash> connected = _context.commSystem().getEstablished(); + List<Hash> connected = _context.commSystem().getEstablished(); + if (connected.isEmpty()) + return; getReadLock(); try { - for (Hash peer : _notFailingPeers.keySet()) { - if (!connected.contains(peer)) - exclude.add(peer); - } - locked_selectPeers(_notFailingPeers, howMany, exclude, matches, mask, ipSet); + locked_selectActive(connected, howMany, exclude, matches, mask, ipSet); } finally { releaseReadLock(); } } } @@ -651,6 +649,7 @@ public class ProfileOrganizer { * * This DOES cascade further to non-connected peers. * + * @param exclude non-null, not-connected peers will NOT be added, as of 0.9.58 * @param mask 0-4 Number of bytes to match to determine if peers in the same IP range should * not be in the same tunnel. 0 = disable check; 1 = /8; 2 = /16; 3 = /24; 4 = exact IP match * @param ipSet in/out param, use for multiple calls, may be null only if mask is 0 @@ -658,17 +657,13 @@ public class ProfileOrganizer { */ private void selectActiveNotFailingPeers2(int howMany, Set<Hash> exclude, Set<Hash> matches, int mask, MaskedIPSet ipSet) { if (matches.size() < howMany) { - Set<Hash> connected = _context.commSystem().getEstablished(); - Map<Hash, PeerProfile> activePeers = new HashMap<Hash, PeerProfile>(connected.size()); - getReadLock(); - try { - for (Hash peer : connected) { - PeerProfile prof = _notFailingPeers.get(peer); - if (prof != null) - activePeers.put(peer, prof); - } - locked_selectPeers(activePeers, howMany, exclude, matches, mask, ipSet); - } finally { releaseReadLock(); } + List<Hash> connected = _context.commSystem().getEstablished(); + if (!connected.isEmpty()) { + getReadLock(); + try { + locked_selectActive(connected, howMany, exclude, matches, mask, ipSet); + } finally { releaseReadLock(); } + } } if (matches.size() < howMany) { if (_log.shouldLog(Log.INFO)) @@ -745,69 +740,6 @@ public class ProfileOrganizer { return; } - /** - * Get the peers the transport layer thinks are unreachable, - * and peers requiring introducers. - * - */ - public List<Hash> selectPeersLocallyUnreachable() { - List<Hash> n; - int count; - getReadLock(); - try { - count = _notFailingPeers.size(); - n = new ArrayList<Hash>(_notFailingPeers.keySet()); - } finally { releaseReadLock(); } - List<Hash> l = new ArrayList<Hash>(count / 4); - for (Hash peer : n) { - if (_context.commSystem().wasUnreachable(peer)) { - l.add(peer); - } else { - // Blacklist all peers requiring SSU introducers, because either - // a) it's slow; or - // b) it doesn't work very often; or - // c) in the event they are advertising NTCP, it probably won't work because - // they probably don't have a TCP hole punched in their firewall either. - RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer); - if (info != null) { - RouterAddress ra = info.getTargetAddress("SSU"); - // peers with no SSU address at all are fine. - // as long as they have NTCP - if (ra == null) { - if (info.getTargetAddresses("NTCP", "NTCP2").isEmpty()) - l.add(peer); - continue; - } - // This is the quick way of doing UDPAddress.getIntroducerCount() > 0 - if (ra.getOption("itag0") != null) - l.add(peer); - } - } - } - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Unreachable: " + l); - return l; - } - - /** - * Get the peers that have recently rejected us for bandwidth - * recent == last 20s - * - */ - public List<Hash> selectPeersRecentlyRejecting() { - getReadLock(); - try { - long cutoff = _context.clock().now() - (20*1000); - int count = _notFailingPeers.size(); - List<Hash> l = new ArrayList<Hash>(count / 128); - for (PeerProfile prof : _notFailingPeers.values()) { - if (prof.getTunnelHistory().getLastRejectedBandwidth() > cutoff) - l.add(prof.getPeer()); - } - return l; - } finally { releaseReadLock(); } - } - /** * Find the hashes for all peers we are actively profiling * @@ -1315,6 +1247,9 @@ public class ProfileOrganizer { ok = mask <= 0 || notRestricted(peer, ipSet, mask); if ((!ok) && _log.shouldWarn()) _log.warn("IP restriction prevents " + peer + " from joining " + matches); + } else { + if (toExclude != null) + toExclude.add(peer); } if (ok) matches.add(peer); @@ -1322,6 +1257,44 @@ public class ProfileOrganizer { matches.remove(peer); } } + + /** + * + * For efficiency. Rather than iterating through _notFailingPeers looking for connected peers, + * iterate through the connected peers and then check if failing. + * + * @param mask 0-4 Number of bytes to match to determine if peers in the same IP range should + * not be in the same tunnel. 0 = disable check; 1 = /8; 2 = /16; 3 = /24; 4 = exact IP match + * @param ipSet may be null only if mask is 0 + * @since 0.9.58 + */ + private void locked_selectActive(List<Hash> connected, int howMany, Set<Hash> toExclude, Set<Hash> matches, + int mask, MaskedIPSet ipSet) { + // use RandomIterator to avoid shuffling the whole thing + for (Iterator<Hash> iter = new RandomIterator<Hash>(connected); (matches.size() < howMany) && iter.hasNext(); ) { + Hash peer = iter.next(); + if (toExclude != null && toExclude.contains(peer)) + continue; + if (matches.contains(peer)) + continue; + if (_us.equals(peer)) + continue; + if (_failingPeers.containsKey(peer)) + continue; + // we assume if connected, it's fine, don't look in _notFailingPeers + boolean ok = isSelectable(peer); + if (ok) { + ok = mask <= 0 || notRestricted(peer, ipSet, mask); + if ((!ok) && _log.shouldWarn()) + _log.warn("IP restriction prevents " + peer + " from joining " + matches); + } else { + if (toExclude != null) + toExclude.add(peer); + } + if (ok) + matches.add(peer); + } + } /** * Does the peer's IP address NOT match the IP address of any peer already in the set, @@ -1383,6 +1356,9 @@ public class ProfileOrganizer { ok = mask <= 0 || notRestricted(peer, ipSet, mask); if ((!ok) && _log.shouldWarn()) _log.warn("IP restriction prevents " + peer + " from joining " + matches); + } else { + if (toExclude != null) + toExclude.add(peer); } if (ok) matches.add(peer); diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 48356b0cd8a11e973a299b20680d5c5b6bae53f7..9ad13754b82704078b33c0495e78e888177bb856 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -183,10 +183,10 @@ public class CommSystemFacadeImpl extends CommSystemFacade { } /** - * @return a new set, may be modified + * @return a new list, may be modified * @since 0.9.34 */ - public Set<Hash> getEstablished() { + public List<Hash> getEstablished() { return _manager.getEstablished(); } diff --git a/router/java/src/net/i2p/router/transport/Transport.java b/router/java/src/net/i2p/router/transport/Transport.java index 552a249d221f5ce818201938040e5473f5c1bea0..c22b3836950145cabd4f67f466930e6631711ba5 100644 --- a/router/java/src/net/i2p/router/transport/Transport.java +++ b/router/java/src/net/i2p/router/transport/Transport.java @@ -11,7 +11,6 @@ package net.i2p.router.transport; import java.io.IOException; import java.io.Writer; import java.util.List; -import java.util.Set; import net.i2p.data.Hash; import net.i2p.data.router.RouterAddress; @@ -168,7 +167,7 @@ public interface Transport { * @return may or may not be modifiable; check implementation * @since 0.9.34 */ - public Set<Hash> getEstablished(); + public List<Hash> getEstablished(); public int countPeers(); public int countActivePeers(); diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index d765caefcdf698ec725ad0818ee5efd0aac72a52..68c555846190e0f11da9fe8fbdb8b561f1abb72b 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -667,20 +667,22 @@ public class TransportManager implements TransportEventListener { } /** - * @return a new set, may be modified + * @return a new list, may be modified * @since 0.9.34 */ - public Set<Hash> getEstablished() { - // for efficiency. NTCP is modifiable, SSU isn't + public List<Hash> getEstablished() { + // for efficiency Transport t = _transports.get("NTCP"); - Set<Hash> rv; + List<Hash> rv = null; if (t != null) rv = t.getEstablished(); - else - rv = new HashSet<Hash>(256); t = _transports.get("SSU"); - if (t != null) - rv.addAll(t.getEstablished()); + if (t != null) { + if (rv != null) + rv.addAll(t.getEstablished()); + else + rv = t.getEstablished(); + } return rv; } 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 141bd411447d4c3fc80a2e78b24677e5a9d7d914..e57fcbba97101a5777702d1ae9230e41d3725833 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -760,12 +760,12 @@ public class NTCPTransport extends TransportImpl { * @return a copy, modifiable * @since 0.9.34 */ - public Set<Hash> getEstablished() { - Set<Hash> rv = new HashSet<Hash>(_conByIdent.keySet()); + public List<Hash> getEstablished() { + List<Hash> rv = new ArrayList<Hash>(_conByIdent.size()); for (Map.Entry<Hash, NTCPConnection> e : _conByIdent.entrySet()) { NTCPConnection con = e.getValue(); - if (!con.isEstablished() || con.isClosed()) - rv.remove(e.getKey()); + if (con.isEstablished() && !con.isClosed()) + rv.add(e.getKey()); } return rv; } 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 cba2007dbc660e124fa9fd2f51de8dff55f3ba23..d4915c54cbaceccfae4469415e587ee21dd9321e 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -1803,11 +1803,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority /** * Connected peers. * - * @return not a copy, do not modify + * @return a copy, modifiable * @since 0.9.34 */ - public Set<Hash> getEstablished() { - return _peersByIdent.keySet(); + public List<Hash> getEstablished() { + return new ArrayList<Hash>(_peersByIdent.keySet()); } /** diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java b/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java index 395d93a1304c8ba05ba799345d6b5522080081ab..31167e736369f848b833fb9d31db4071f1c59d33 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java @@ -121,7 +121,7 @@ class BuildExecutor implements Runnable { CommSystemFacade csf = _context.commSystem(); if (csf.getStatus() == Status.DISCONNECTED) return 0; - if (csf.isDummy() && csf.getEstablished().size() <= 0) + if (csf.isDummy() && csf.countActivePeers() <= 0) return 0; int maxKBps = _context.bandwidthLimiter().getOutboundKBytesPerSecond(); int allowed = maxKBps / 6; // Max. 1 concurrent build per 6 KB/s outbound diff --git a/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java index f1d726d198556886a4e4e460a960dec8b44697ed..1134646fa18acd1d3928464797ce93ed7b31b1cb 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java @@ -1,7 +1,6 @@ package net.i2p.router.tunnel.pool; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -14,6 +13,7 @@ import net.i2p.router.TunnelManagerFacade; import net.i2p.router.TunnelPoolSettings; import static net.i2p.router.peermanager.ProfileOrganizer.Slice.*; import net.i2p.router.util.MaskedIPSet; +import net.i2p.util.ArraySet; /** * Pick peers randomly out of the fast pool, and put them into tunnels @@ -52,6 +52,8 @@ class ClientPeerSelector extends TunnelPeerSelector { boolean v6Only = isIPv6Only(); boolean ntcpDisabled = isNTCPDisabled(); boolean ssuDisabled = isSSUDisabled(); + // for these cases, check the closest hop up front, + // otherwise, will be done in checkTunnel() at the end boolean checkClosestHop = v6Only || ntcpDisabled || ssuDisabled; boolean hidden = ctx.router().isHidden() || ctx.router().getRouterInfo().getAddressCount() <= 0 || @@ -65,21 +67,27 @@ class ClientPeerSelector extends TunnelPeerSelector { return selectExplicit(settings, length); Set<Hash> exclude = getExclude(isInbound, false); - Set<Hash> matches = new HashSet<Hash>(length); + Set<Hash> matches = new ArraySet<Hash>(length); if (length == 1) { // closest-hop restrictions - if (checkClosestHop) { - Set<Hash> moreExclude = getClosestHopExclude(isInbound); - if (moreExclude != null) - exclude.addAll(moreExclude); - } + if (checkClosestHop) + exclude = getClosestHopExclude(isInbound, exclude); + if (isInbound) + exclude = new IBGWExcluder(exclude); + else + exclude = new OBEPExcluder(exclude); // 1-hop, IP restrictions not required here if (hiddenInbound) { - // SANFP adds all not-connected to exclude, so make a copy - Set<Hash> SANFPExclude = new HashSet<Hash>(exclude); - ctx.profileOrganizer().selectActiveNotFailingPeers(1, SANFPExclude, matches); + // TODO this doesn't pick from fast + ctx.profileOrganizer().selectActiveNotFailingPeers(1, exclude, matches); } if (matches.isEmpty()) { + if (hiddenInbound) { + // No connected peers found, give up now + if (log.shouldWarn()) + log.warn("CPS SANFP hidden closest IB no active peers found, returning null"); + return null; + } // ANFP does not fall back to non-connected ctx.profileOrganizer().selectFastPeers(length, exclude, matches); } @@ -95,34 +103,30 @@ class ClientPeerSelector extends TunnelPeerSelector { // group 0 or 1 if two hops, otherwise group 0 Set<Hash> lastHopExclude; if (isInbound) { - // exclude existing OBEPs to get some diversity ? - // closest-hop restrictions - if (checkClosestHop) { - Set<Hash> moreExclude = getClosestHopExclude(false); - if (moreExclude != null) { - moreExclude.addAll(exclude); - lastHopExclude = moreExclude; - } else { - lastHopExclude = exclude; - } + if (checkClosestHop && !hidden) { + // exclude existing OBEPs to get some diversity ? + // closest-hop restrictions + lastHopExclude = getClosestHopExclude(true, exclude); } else { - lastHopExclude = exclude; + lastHopExclude = exclude; } + if (log.shouldInfo()) + log.info("CPS SFP closest IB " + lastHopExclude); } else { - lastHopExclude = exclude; + lastHopExclude = new OBEPExcluder(exclude); + if (log.shouldInfo()) + log.info("CPS SFP OBEP " + lastHopExclude); } if (hiddenInbound) { // IB closest hop if (log.shouldInfo()) - log.info("CPS SANFP closest IB exclude " + lastHopExclude.size()); - // SANFP adds all not-connected to exclude, so make a copy - Set<Hash> SANFPExclude = new HashSet<Hash>(lastHopExclude); - ctx.profileOrganizer().selectActiveNotFailingPeers(1, SANFPExclude, matches, ipRestriction, ipSet); + log.info("CPS SANFP hidden closest IB " + lastHopExclude); + ctx.profileOrganizer().selectActiveNotFailingPeers(1, lastHopExclude, matches, ipRestriction, ipSet); if (matches.isEmpty()) { - if (log.shouldInfo()) - log.info("CPS SFP closest IB exclude " + lastHopExclude.size()); - // ANFP does not fall back to non-connected - ctx.profileOrganizer().selectFastPeers(1, lastHopExclude, matches, randomKey, length == 2 ? SLICE_0_1 : SLICE_0, ipRestriction, ipSet); + // No connected peers found, give up now + if (log.shouldWarn()) + log.warn("CPS SANFP hidden closest IB no active peers found, returning null"); + return null; } } else if (hiddenOutbound) { // OBEP @@ -178,15 +182,13 @@ class ClientPeerSelector extends TunnelPeerSelector { } if (pickFurthest) { if (log.shouldInfo()) - log.info("CPS SANFP OBEP exclude " + lastHopExclude.size()); - // SANFP adds all not-connected to exclude, so make a copy - Set<Hash> SANFPExclude = new HashSet<Hash>(lastHopExclude); - ctx.profileOrganizer().selectActiveNotFailingPeers(1, SANFPExclude, matches, ipRestriction, ipSet); + log.info("CPS SANFP OBEP " + lastHopExclude); + ctx.profileOrganizer().selectActiveNotFailingPeers(1, lastHopExclude, matches, ipRestriction, ipSet); if (matches.isEmpty()) { - // ANFP does not fall back to non-connected - if (log.shouldInfo()) - log.info("CPS SFP OBEP exclude " + lastHopExclude.size()); - ctx.profileOrganizer().selectFastPeers(1, lastHopExclude, matches, randomKey, length == 2 ? SLICE_0_1 : SLICE_0, ipRestriction, ipSet); + // No connected peers found, give up now + if (log.shouldWarn()) + log.warn("CPS SANFP hidden OBEP no active peers found, returning null"); + return null; } } else { ctx.profileOrganizer().selectFastPeers(1, lastHopExclude, matches, randomKey, length == 2 ? SLICE_0_1 : SLICE_0, ipRestriction, ipSet); @@ -203,6 +205,8 @@ class ClientPeerSelector extends TunnelPeerSelector { if (length > 2) { // middle hop(s) // group 2 or 3 + if (log.shouldInfo()) + log.info("CPS SFP middle " + exclude); ctx.profileOrganizer().selectFastPeers(length - 2, exclude, matches, randomKey, SLICE_2_3, ipRestriction, ipSet); matches.remove(ctx.routerHash()); if (matches.size() > 1) { @@ -219,20 +223,25 @@ class ClientPeerSelector extends TunnelPeerSelector { // IBGW or OB first hop // group 2 or 3 if two hops, otherwise group 1 - if (!isInbound) { + if (isInbound) { + exclude = new IBGWExcluder(exclude); + if (log.shouldInfo()) + log.info("CPS SFP IBGW " + exclude); + } else { // exclude existing IBGWs to get some diversity ? - // closest-hop restrictions - if (checkClosestHop) { - Set<Hash> moreExclude = getClosestHopExclude(true); - if (moreExclude != null) - exclude.addAll(moreExclude); - } + // OB closest-hop restrictions + if (checkClosestHop) + exclude = getClosestHopExclude(false, exclude); + if (log.shouldInfo()) + log.info("CPS SFP closest OB " + exclude); } // TODO exclude IPv6-only at IBGW? Caught in checkTunnel() below ctx.profileOrganizer().selectFastPeers(1, exclude, matches, randomKey, length == 2 ? SLICE_2_3 : SLICE_1, ipRestriction, ipSet); matches.remove(ctx.routerHash()); rv.addAll(matches); } + if (log.shouldInfo()) + log.info("CPS " + length + (isInbound ? " IB " : " OB ") + "final: " + exclude); if (rv.size() < length) { // not enough peers to build the requested size // client tunnels do not use overrides @@ -255,9 +264,91 @@ class ClientPeerSelector extends TunnelPeerSelector { else rv.add(ctx.routerHash()); if (rv.size() > 1) { - if (!checkTunnel(isInbound, rv)) + if (!checkTunnel(isInbound, false, rv)) rv = null; } return rv; } + + /** + * A Set of Hashes that automatically adds to the + * Set in the contains() check. + * + * So we don't need to generate the exclude set up front. + * + * @since 0.9.58 + */ + private class IBGWExcluder extends ExcluderBase { + + /** + * Automatically check if peer is connected + * and add the Hash to the set if not. + * + * @param set not copied, contents will be modified by all methods + */ + public IBGWExcluder(Set<Hash> set) { + super(set); + } + + /** + * Automatically check if peer is connected + * and add the Hash to the set if not. + * + * @param o a Hash + * @return true if peer should be excluded + */ + public boolean contains(Object o) { + if (s.contains(o)) + return true; + Hash h = (Hash) o; + boolean rv = !allowAsIBGW(h); + if (rv) { + s.add(h); + if (log.shouldDebug()) + log.debug("CPS IBGW exclude " + h.toBase64()); + } + return rv; + } + } + + /** + * A Set of Hashes that automatically adds to the + * Set in the contains() check. + * + * So we don't need to generate the exclude set up front. + * + * @since 0.9.58 + */ + private class OBEPExcluder extends ExcluderBase { + + /** + * Automatically check if peer is connected + * and add the Hash to the set if not. + * + * @param set not copied, contents will be modified by all methods + */ + public OBEPExcluder(Set<Hash> set) { + super(set); + } + + /** + * Automatically check if peer is connected + * and add the Hash to the set if not. + * + * @param o a Hash + * @return true if peer should be excluded + */ + public boolean contains(Object o) { + if (s.contains(o)) + return true; + Hash h = (Hash) o; + boolean rv = !allowAsOBEP(h); + if (rv) { + s.add(h); + if (log.shouldDebug()) + log.debug("CPS OBEP exclude " + h.toBase64()); + } + return rv; + } + } } diff --git a/router/java/src/net/i2p/router/tunnel/pool/ExcluderBase.java b/router/java/src/net/i2p/router/tunnel/pool/ExcluderBase.java new file mode 100644 index 0000000000000000000000000000000000000000..ff0fdf05ad8d0a21cb2346be30e8859892830f9c --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/ExcluderBase.java @@ -0,0 +1,61 @@ +package net.i2p.router.tunnel.pool; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import net.i2p.data.Hash; + +/** + * A Set of Hashes that automatically adds to the + * Set in the contains() check. + * + * So we don't need to generate the exclude set up front. + * Less object churn and copying. + * + * @since 0.9.58 + */ +abstract class ExcluderBase implements Set<Hash> { + protected final Set<Hash> s; + + /** + * Automatically check if peer is connected + * and add the Hash to the set if not. + * + * @param set not copied, contents will be modified by all methods + */ + protected ExcluderBase(Set<Hash> set) { + s = set; + } + + /** + * Automatically check if peer is allowed + * and add the Hash to the set if not. + * + * @param o a Hash + * @return true if peer should be excluded + */ + public abstract boolean contains(Object o); + + public boolean add(Hash h) { return s.add(h); } + public boolean addAll(Collection<? extends Hash> c) { return s.addAll(c); } + public void clear() { s.clear(); } + public boolean containsAll(Collection<?> c) { return s.containsAll(c); } + public boolean equals(Object o) { return s.equals(o); } + public int hashCode() { return s.hashCode(); } + public boolean isEmpty() { return s.isEmpty(); } + public Iterator<Hash> iterator() { return s.iterator(); } + public boolean remove(Object o) { return s.remove(o); } + public boolean removeAll(Collection<?> c) { return s.removeAll(c); } + public boolean retainAll(Collection<?> c) { return s.retainAll(c); } + public int size() { return s.size(); } + public Object[] toArray() { return s.toArray(); } + public <Hash> Hash[] toArray(Hash[] a) { return s.toArray(a); } + + @Override + public String toString() { + return getClass().getSimpleName() + + " (" + s.size() + ") " + + (s.size() <= 10 ? s.toString() : ""); + } +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java index fbff109229902afd475c790f556bb3de5de42439..38c40dd11900dd74e0592f792a4b823c440ed784 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java @@ -1,7 +1,6 @@ package net.i2p.router.tunnel.pool; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -14,6 +13,7 @@ import net.i2p.router.TunnelPoolSettings; import net.i2p.router.util.MaskedIPSet; import net.i2p.stat.Rate; import net.i2p.stat.RateStat; +import net.i2p.util.ArraySet; import net.i2p.util.Log; import net.i2p.util.SystemVersion; @@ -64,6 +64,8 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { boolean v6Only = nonzero && isIPv6Only(); boolean ntcpDisabled = nonzero && isNTCPDisabled(); boolean ssuDisabled = nonzero && isSSUDisabled(); + // for these cases, check the closest hop up front, + // otherwise, will be done in checkTunnel() at the end boolean checkClosestHop = v6Only || ntcpDisabled || ssuDisabled; boolean hidden = nonzero && (ctx.router().isHidden() || ctx.router().getRouterInfo().getAddressCount() <= 0 || @@ -83,43 +85,41 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { if (v6Only || hiddenInbound || lowOutbound) { Set<Hash> closestExclude; if (checkClosestHop) { - closestExclude = getClosestHopExclude(isInbound); - if (closestExclude != null) - closestExclude.addAll(exclude); - else - closestExclude = exclude; + closestExclude = getClosestHopExclude(isInbound, exclude); } else { closestExclude = exclude; } - Set<Hash> closest = new HashSet<Hash>(1); + ArraySet<Hash> closest = new ArraySet<Hash>(1); if (hiddenInbound || lowOutbound) { - // If hidden and inbound, use fast peers - that we probably have recently - // connected to and so they have our real RI - to maximize the chance + // If hidden and inbound, use connected peers to guarantee // that the adjacent hop can connect to us. - // use only connected peers so we don't make more connections - if (log.shouldLog(Log.INFO)) - log.info("EPS SANFP closest " + (isInbound ? "IB" : "OB") + " exclude " + closestExclude.size()); - // SANFP adds all not-connected to exclude, so make a copy - Set<Hash> SANFPExclude = new HashSet<Hash>(closestExclude); - ctx.profileOrganizer().selectActiveNotFailingPeers(1, SANFPExclude, closest, ipRestriction, ipSet); + if (log.shouldInfo()) + log.info("EPS SANFP closest " + (isInbound ? "IB " : "OB ") + closestExclude); + ctx.profileOrganizer().selectActiveNotFailingPeers(1, closestExclude, closest, ipRestriction, ipSet); if (closest.isEmpty()) { + if (hiddenInbound) { + // No connected peers found, give up now + if (log.shouldWarn()) + log.warn("EPS SANFP hidden closest IB no active peers found, returning null"); + return null; + } // ANFP does not fall back to non-connected - if (log.shouldLog(Log.INFO)) - log.info("EPS SFP closest " + (isInbound ? "IB" : "OB") + " exclude " + closestExclude.size()); + if (log.shouldInfo()) + log.info("EPS SFP closest " + (isInbound ? "IB " : "OB ") + closestExclude); ctx.profileOrganizer().selectFastPeers(1, closestExclude, closest, ipRestriction, ipSet); } } else if (exploreHighCap) { - if (log.shouldLog(Log.INFO)) - log.info("EPS SHCP closest " + (isInbound ? "IB" : "OB") + " exclude " + closestExclude.size()); + if (log.shouldInfo()) + log.info("EPS SHCP closest " + (isInbound ? "IB " : "OB ") + closestExclude); ctx.profileOrganizer().selectHighCapacityPeers(1, closestExclude, closest, ipRestriction, ipSet); } else { - if (log.shouldLog(Log.INFO)) - log.info("EPS SNFP closest " + (isInbound ? "IB" : "OB") + " exclude " + closestExclude.size()); + if (log.shouldInfo()) + log.info("EPS SNFP closest " + (isInbound ? "IB " : "OB ") + closestExclude); ctx.profileOrganizer().selectNotFailingPeers(1, closestExclude, closest, false, ipRestriction, ipSet); } if (!closest.isEmpty()) { - closestHop = closest.iterator().next(); + closestHop = closest.get(0); exclude.add(closestHop); length--; } @@ -153,38 +153,30 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { } } if (pickFurthest) { - Set<Hash> furthest = new HashSet<Hash>(1); - if (log.shouldLog(Log.INFO)) - log.info("EPS SANFP furthest OB exclude " + exclude.size()); - // ANFP adds all not-connected to exclude, so make a copy - Set<Hash> SANFPExclude = new HashSet<Hash>(exclude); - ctx.profileOrganizer().selectActiveNotFailingPeers(1, SANFPExclude, furthest, ipRestriction, ipSet); + ArraySet<Hash> furthest = new ArraySet<Hash>(1); + if (log.shouldInfo()) + log.info("EPS SANFP OBEP exclude " + exclude); + ctx.profileOrganizer().selectActiveNotFailingPeers(1, exclude, furthest, ipRestriction, ipSet); if (furthest.isEmpty()) { // ANFP does not fall back to non-connected - if (log.shouldLog(Log.INFO)) - log.info("EPS SFP furthest OB exclude " + exclude.size()); + if (log.shouldInfo()) + log.info("EPS SFP OBEP exclude " + exclude); ctx.profileOrganizer().selectFastPeers(1, exclude, furthest, ipRestriction, ipSet); } if (!furthest.isEmpty()) { - furthestHop = furthest.iterator().next(); + furthestHop = furthest.get(0); exclude.add(furthestHop); length--; } } } - - // Don't use ff peers for exploratory tunnels to lessen exposure to netDb searches and stores - // Hmm if they don't get explored they don't get a speed/capacity rating - // so they don't get used for client tunnels either. - // FloodfillNetworkDatabaseFacade fac = (FloodfillNetworkDatabaseFacade)ctx.netDb(); - // exclude.addAll(fac.getFloodfillPeers()); - HashSet<Hash> matches = new HashSet<Hash>(length); - + ArrayList<Hash> rv = new ArrayList<Hash>(length + 3); if (length > 0) { + Set<Hash> matches = new ArraySet<Hash>(length); if (exploreHighCap) { - if (log.shouldLog(Log.INFO)) - log.info("EPS SHCP " + length + (isInbound ? " IB" : " OB") + " exclude " + exclude.size()); + if (log.shouldInfo()) + log.info("EPS SHCP " + length + (isInbound ? " IB " : " OB ") + exclude); ctx.profileOrganizer().selectHighCapacityPeers(length, exclude, matches, ipRestriction, ipSet); } else { // As of 0.9.23, we include a max of 2 not failing peers, @@ -192,14 +184,17 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { // Peer org credits existing items in matches if (length > 2) ctx.profileOrganizer().selectHighCapacityPeers(length - 2, exclude, matches); - if (log.shouldLog(Log.INFO)) - log.info("EPS SNFP " + length + (isInbound ? " IB" : " OB") + " exclude " + exclude.size()); + // select will check both matches and exclude, no need to add matches to exclude here + if (log.shouldInfo()) + log.info("EPS SNFP " + length + (isInbound ? " IB " : " OB ") + exclude); ctx.profileOrganizer().selectNotFailingPeers(length, exclude, matches, false, ipRestriction, ipSet); } matches.remove(ctx.routerHash()); + rv.addAll(matches); } + if (log.shouldInfo()) + log.info("EPS " + length + (isInbound ? " IB " : " OB ") + "final: " + exclude); - ArrayList<Hash> rv = new ArrayList<Hash>(matches); if (rv.size() > 1) orderPeers(rv, settings.getRandomKey()); if (closestHop != null) { @@ -226,7 +221,7 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { else rv.add(ctx.routerHash()); if (rv.size() > 1) { - if (!checkTunnel(isInbound, rv)) + if (!checkTunnel(isInbound, true, rv)) rv = null; } return rv; @@ -277,7 +272,7 @@ class ExploratoryPeerSelector extends TunnelPeerSelector { // high capacity peers, at least for a little bit. int failPct; // getEvents() will be 0 for first 10 minutes - if (ctx.router().getUptime() <= 11*60*1000) { + if (uptime <= 11*60*1000) { failPct = 100 - MIN_NONFAILING_PCT; } else { failPct = getExploratoryFailPercentage(); diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java index 06f7dc7ac71f079fdf92588ee65950f58eb3d825..8491270f423366117d9e12d515a926ca3605c9ba 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java @@ -28,7 +28,9 @@ import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.TunnelPoolSettings; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; +import net.i2p.router.peermanager.PeerProfile; import net.i2p.router.transport.TransportUtil; +import net.i2p.util.ArraySet; import net.i2p.util.Log; import net.i2p.util.SystemVersion; import net.i2p.util.VersionComparator; @@ -151,7 +153,7 @@ public abstract class TunnelPeerSelector extends ConnectChecker { int more = length - rv.size(); Set<Hash> exclude = getExclude(settings.isInbound(), settings.isExploratory()); exclude.addAll(rv); - Set<Hash> matches = new HashSet<Hash>(more); + Set<Hash> matches = new ArraySet<Hash>(more); // don't bother with IP restrictions here ctx.profileOrganizer().selectFastPeers(more, exclude, matches); rv.addAll(matches); @@ -184,9 +186,19 @@ public abstract class TunnelPeerSelector extends ConnectChecker { } /** - * Pick peers that we want to avoid + * As of 0.9.58, this returns a set populated only by TunnelManager.selectPeersInTooManyTunnels(), + * for passing to ProfileOrganizer. + * The set will be populated via the contains() calls. */ - public Set<Hash> getExclude(boolean isInbound, boolean isExploratory) { + protected Set<Hash> getExclude(boolean isInbound, boolean isExploratory) { + return new Excluder(isInbound, isExploratory); + } + + + /** + * @since 0.9.58, previously getExclude() + */ + private boolean shouldExclude(Hash h, boolean isInbound, boolean isExploratory) { // we may want to update this to skip 'hidden' or 'unreachable' peers, but that // isn't safe, since they may publish one set of routerInfo to us and another to // other peers. the defaults for filterUnreachable has always been to return false, @@ -204,46 +216,35 @@ public abstract class TunnelPeerSelector extends ConnectChecker { // (and even worse for anonymity?). // - Set<Hash> peers = new HashSet<Hash>(8); - peers.addAll(ctx.profileOrganizer().selectPeersRecentlyRejecting()); - if (!ctx.getBooleanProperty("i2np.allowLocal")) - peers.addAll(ctx.tunnelManager().selectPeersInTooManyTunnels()); + PeerProfile prof = ctx.profileOrganizer().getProfileNonblocking(h); + if (prof != null) { + long cutoff = ctx.clock().now() - (20*1000); + if (prof.getTunnelHistory().getLastRejectedBandwidth() > cutoff) + return true; + } + + // the transport layer thinks is unreachable + if (ctx.commSystem().wasUnreachable(h)) + return true; + + RouterInfo info = ctx.netDb().lookupRouterInfoLocally(h); + if (info == null) + return true; + if (filterUnreachable(isInbound, isExploratory)) { - // This is the only use for getPeersByCapability? And the whole set of datastructures in PeerManager? - Collection<Hash> caps = ctx.peerManager().getPeersByCapability(Router.CAPABILITY_UNREACHABLE); - if (caps != null) - peers.addAll(caps); + String caps = info.getCapabilities(); + if (caps.indexOf(Router.CAPABILITY_UNREACHABLE) >= 0) + return true; } - Collection<Hash> local = ctx.profileOrganizer().selectPeersLocallyUnreachable(); - if (local != null) - peers.addAll(local); + if (filterSlow(isInbound, isExploratory)) { // NOTE: filterSlow always returns true String excl = getExcludeCaps(ctx); - - FloodfillNetworkDatabaseFacade fac = (FloodfillNetworkDatabaseFacade)ctx.netDb(); - List<RouterInfo> known = fac.getKnownRouterData(); - if (known != null) { - for (int i = 0; i < known.size(); i++) { - RouterInfo peer = known.get(i); - boolean shouldExclude = shouldExclude(peer, excl); - if (shouldExclude) { - peers.add(peer.getIdentity().calculateHash()); - continue; - } - } - } - /* - for (int i = 0; i < excludeCaps.length(); i++) { - List matches = ctx.peerManager().getPeersByCapability(excludeCaps.charAt(i)); - if (log.shouldLog(Log.INFO)) - log.info("Filtering out " + matches.size() + " peers with capability " + excludeCaps.charAt(i)); - peers.addAll(matches); - } - */ - + if (shouldExclude(info, excl)) + return true; } - return peers; + + return false; } /** @@ -263,9 +264,9 @@ public abstract class TunnelPeerSelector extends ConnectChecker { * the RI. Will not force RI lookups. * Default true. * - * @since 0.9.34 + * @since 0.9.34, protected since 0.9.58 for ClientPeerSelector */ - private boolean allowAsOBEP(Hash h) { + protected boolean allowAsOBEP(Hash h) { RouterInfo ri = ctx.netDb().lookupRouterInfoLocally(h); if (ri == null) return true; @@ -280,9 +281,9 @@ public abstract class TunnelPeerSelector extends ConnectChecker { * the RI. Will not force RI lookups. * Default true. * - * @since 0.9.34 + * @since 0.9.34, protected since 0.9.58 for ClientPeerSelector */ - private boolean allowAsIBGW(Hash h) { + protected boolean allowAsIBGW(Hash h) { RouterInfo ri = ctx.netDb().lookupRouterInfoLocally(h); if (ri == null) return true; @@ -312,51 +313,22 @@ public abstract class TunnelPeerSelector extends ConnectChecker { * Make sure that ClientPeerSelector and TunnelPeerSelector selectPeers() call this when needed. * </ol> * - * Don't call this unless you need to. - * See ClientPeerSelector and TunnelPeerSelector selectPeers(). + * As of 0.9.58, this a set with only toAdd, for use in ProfileOrganizer. + * The set will be populated via the contains() calls. * * @param isInbound - * @return null if none + * @return non-null * @since 0.9.17 */ - protected Set<Hash> getClosestHopExclude(boolean isInbound) { - RouterInfo ri = ctx.router().getRouterInfo(); - if (ri == null) - return null; - - // we can skip this check now, uncomment if we have some new sigtype - //SigType type = ri.getIdentity().getSigType(); - //if (type == SigType.DSA_SHA1) - // return null; - - int ourMask = isInbound ? getInboundMask(ri) : getOutboundMask(ri); - Set<Hash> connected = ctx.commSystem().getEstablished(); - Set<Hash> rv = new HashSet<Hash>(256); - FloodfillNetworkDatabaseFacade fac = (FloodfillNetworkDatabaseFacade)ctx.netDb(); - List<RouterInfo> known = fac.getKnownRouterData(); - if (known != null) { - for (int i = 0; i < known.size(); i++) { - RouterInfo peer = known.get(i); - Hash h = peer.getIdentity().calculateHash(); - - // Uncomment if stricter than in shouldExclude() below - //String v = peer.getVersion(); - //if (VersionComparator.comp(v, "0.9.16") < 0) { - // rv.add(h); - // continue; - //} - - if (connected.contains(h)) - continue; - boolean canConnect = isInbound ? canConnect(peer, ourMask) : canConnect(ourMask, peer); - if (!canConnect) - rv.add(h); - } - } - return rv; + protected Set<Hash> getClosestHopExclude(boolean isInbound, Set<Hash> toAdd) { + return new ClosestHopExcluder(isInbound, toAdd); } - /** warning, this is also called by ProfileOrganizer.isSelectable() */ + /** + * Should the peer be excluded based on its published caps? + * + * Warning, this is also called by ProfileOrganizer.isSelectable() + */ public static boolean shouldExclude(RouterContext ctx, RouterInfo peer) { return shouldExclude(peer, getExcludeCaps(ctx)); } @@ -371,7 +343,11 @@ public abstract class TunnelPeerSelector extends ConnectChecker { /** NTCP2 */ private static final String MIN_VERSION = "0.9.36"; - /** warning, this is also called by ProfileOrganizer.isSelectable() */ + /** + * Should the peer be excluded based on its published caps? + * + * Warning, this is also called by ProfileOrganizer.isSelectable() + */ private static boolean shouldExclude(RouterInfo peer, String excl) { String cap = peer.getCapabilities(); for (int j = 0; j < excl.length(); j++) { @@ -538,9 +514,12 @@ public abstract class TunnelPeerSelector extends ConnectChecker { * @return ok * @since 0.9.34 */ - protected boolean checkTunnel(boolean isInbound, List<Hash> tunnel) { + protected boolean checkTunnel(boolean isInbound, boolean isExploratory, List<Hash> tunnel) { if (!checkTunnel(tunnel)) return false; + // client OBEP/IBGW checks now in CPS + if (!isExploratory) + return true; if (isInbound) { Hash h = tunnel.get(tunnel.size() - 1); if (!allowAsIBGW(h)) { @@ -597,4 +576,119 @@ public abstract class TunnelPeerSelector extends ConnectChecker { } return rv; } + + /** + * A Set of Hashes that automatically adds to the + * Set in the contains() check. + * + * So we don't need to generate the exclude set up front. + * + * @since 0.9.58 + */ + protected class Excluder extends ExcluderBase { + private final boolean _isIn, _isExpl; + + /** + * Automatically adds selectPeersInTooManyTunnels(), unless i2np.allowLocal. + */ + public Excluder(boolean isInbound, boolean isExploratory) { + super(ctx.getBooleanProperty("i2np.allowLocal") ? new HashSet<Hash>() + : ctx.tunnelManager().selectPeersInTooManyTunnels()); + _isIn = isInbound; + _isExpl = isExploratory; + } + + /** + * Does not add selectPeersInTooManyTunnels(). + * Makes a copy of toAdd + * + * @param toAdd initial contents, copied + */ + public Excluder(boolean isInbound, boolean isExploratory, Set<Hash> toAdd) { + super(new HashSet<Hash>(toAdd)); + _isIn = isInbound; + _isExpl = isExploratory; + } + + /** + * Overridden to automatically check our exclusion criteria + * and add the Hash to the set if the criteria are met. + * + * @param o a Hash + * @return true if peer should be excluded + */ + @Override + public boolean contains(Object o) { + if (s.contains(o)) + return true; + Hash h = (Hash) o; + if (shouldExclude(h, _isIn, _isExpl)) { + s.add(h); + //if (log.shouldDebug()) + // log.debug("TPS exclude " + h.toBase64()); + return true; + } + return false; + } + } + + /** + * Only for hidden mode and other tough situations. + * See checkClosestHop boolean. + * Not for hidden inbound; use SANFP instead. + * + * @since 0.9.58 + */ + private class ClosestHopExcluder extends ExcluderBase { + private final boolean isIn; + private final int ourMask; + + /** + * Automatically check if peer can connect to us (for inbound) + * or we can connect to it (for outbound) + * and add the Hash to the set if not. + * + * @param set not copied, contents will be modified by all methods + */ + public ClosestHopExcluder(boolean isInbound, Set<Hash> set) { + super(set); + isIn = isInbound; + RouterInfo ri = ctx.router().getRouterInfo(); + if (ri != null) + ourMask = isInbound ? getInboundMask(ri) : getOutboundMask(ri); + else + ourMask = 0xff; + } + + /** + * Automatically check if peer can connect to us (for inbound) + * or we can connect to it (for outbound) + * and add the Hash to the set if not. + * + * @param o a Hash + * @return true if peer should be excluded + */ + public boolean contains(Object o) { + if (s.contains(o)) + return true; + Hash h = (Hash) o; + if (ctx.commSystem().isEstablished(h)) + return false; + boolean canConnect; + RouterInfo peer = ctx.netDb().lookupRouterInfoLocally(h); + if (peer == null) { + canConnect = false; + } else if (isIn) { + canConnect = canConnect(peer, ourMask); + } else { + canConnect = canConnect(ourMask, peer); + } + if (!canConnect) { + s.add(h); + //if (log.shouldDebug()) + // log.debug("TPS closest exclude " h.toBase64()); + } + return !canConnect; + } + } }