diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java
index ce32ac378262fb371a0ae212e0847b5619fd54ed..924104d886171f7b877b04ba9f38d8cd954118dd 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java
@@ -250,7 +250,7 @@ public class ConfigNetHandler extends FormHandler {
             // If hidden mode value changes, restart is required
             if (_hiddenMode && "false".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) {
                 _context.router().setConfigSetting(Router.PROP_HIDDEN, "true");
-                _context.router().getRouterInfo().addCapability(RouterInfo.CAPABILITY_HIDDEN);
+                _context.router().addCapabilities(_context.router().getRouterInfo());
                 addFormNotice("Gracefully restarting into Hidden Router Mode. Make sure you have no 0-1 length "
                               + "<a href=\"configtunnels.jsp\">tunnels!</a>");
                 hiddenSwitch();
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java
index 8b53d023a2c6ff6bddb3697bf08c4ddc1a53ec0c..71ff04db73975383fa010aa476870c5f53d93bd0 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java
@@ -101,7 +101,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
         setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE));
         setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1));
         setProfile(getInt(opts, PROP_PROFILE, PROFILE_BULK));
-        setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, 4*1024));
+        setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, 960)); // 960 fits inside a single tunnel message
         setRTT(getInt(opts, PROP_INITIAL_RTT, 10*1000));
         setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
         setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 1000));
diff --git a/apps/syndie/java/src/net/i2p/syndie/BlogManager.java b/apps/syndie/java/src/net/i2p/syndie/BlogManager.java
index f0e2cfdc3785e541ecdef411d4f77d775fa63abd..30c2eb0567b1265892b82217fd1514e1ad002e1d 100644
--- a/apps/syndie/java/src/net/i2p/syndie/BlogManager.java
+++ b/apps/syndie/java/src/net/i2p/syndie/BlogManager.java
@@ -890,6 +890,8 @@ public class BlogManager {
         try {
             BlogInfo info = new BlogInfo();
             info.load(metadataStream);
+            if (isBanned(info.getKey().calculateHash()))
+                return false;
             return _archive.storeBlogInfo(info);
         } catch (IOException ioe) {
             _log.error("Error importing meta", ioe);
@@ -906,6 +908,8 @@ public class BlogManager {
         try {
             EntryContainer c = new EntryContainer();
             c.load(entryStream);
+            if (isBanned(c.getURI().getKeyHash()))
+                return false;
             return _archive.storeEntry(c);
         } catch (IOException ioe) {
             _log.error("Error importing entry", ioe);
diff --git a/apps/syndie/jsp/syndie.css b/apps/syndie/jsp/syndie.css
index 0f13e8b40287dca5334bbffbee5425c72db881de..3d7ccef2473c70117c39270b791c342862582e93 100644
--- a/apps/syndie/jsp/syndie.css
+++ b/apps/syndie/jsp/syndie.css
@@ -134,9 +134,9 @@ select {
    display: inline;
 }
 .controlBar {
-	margin: 0em;
-	padding: 0em;
-//	border: medium solid #DDF;
+        border-bottom: thick double #CCF;
+        border-left: medium solid #CCF;
+        border-right: medium solid #CCF;
 	background-color: #EEF;
 	color: inherit;
 	font-size: small;
diff --git a/history.txt b/history.txt
index d504329babff7ce85266ed9e6314bb52f3192ac6..51e1e5701f3592f242c9a4b9480f9d7beba82cdf 100644
--- a/history.txt
+++ b/history.txt
@@ -1,4 +1,25 @@
-$Id: history.txt,v 1.442 2006/04/01 14:05:37 jrandom Exp $
+$Id: history.txt,v 1.443 2006/04/03 05:07:24 jrandom Exp $
+
+2006-04-05  jrandom
+    * Fix during the ssu handshake to avoid an unnecessary failure on
+      packet retransmission (thanks ripple!)
+    * Fix during the SSU handshake to use the negotiated session key asap,
+      rather than using the intro key for more than we should (thanks ripple!)
+    * Fixes to the message reply registry (thanks Complication!)
+    * More comprehensive syndie banning (for repeated pushes)
+    * Publish the router's ballpark bandwidth limit (w/in a power of 2), for 
+      testing purposes
+    * Put a floor back on the capacity threshold, so too many failing peers
+      won't cause us to pick very bad peers (unless we have very few good
+      ones)
+    * Bugfix to cut down on peers using introducers unneessarily (thanks
+      Complication!)
+    * Reduced the default streaming lib message size to fit into a single
+      tunnel message, rather than require 5 tunnel messages to be transferred
+      without loss before recomposition.  This reduces throughput, but should
+      increase reliability, at least for the time being.
+    * Misc small bugfixes in the router (thanks all!)
+    * More tweaking for Syndie's CSS (thanks Doubtful Salmon!)
 
 2006-04-01  jrandom
     * Take out the router watchdog's teeth (don't restart on leaseset failure)
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index 213ac640aa4aeaf8be553a7f4d2b0ccd5e2a4a77..ca0c1fbe8327f5a19706cb7f6e204e6714c24cc5 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -323,13 +323,8 @@ public class Router {
             stats.setProperty(RouterInfo.PROP_NETWORK_ID, NETWORK_ID+"");
             ri.setOptions(stats);
             ri.setAddresses(_context.commSystem().createAddresses());
-            if (FloodfillNetworkDatabaseFacade.floodfillEnabled(_context))
-                ri.addCapability(FloodfillNetworkDatabaseFacade.CAPACITY_FLOODFILL);
-            if("true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) {
-                ri.addCapability(RouterInfo.CAPABILITY_HIDDEN);
-            }
 
-            addReachabilityCapability(ri);
+            addCapabilities(ri);
             SigningPrivateKey key = _context.keyManager().getSigningPrivateKey();
             if (key == null) {
                 _log.log(Log.CRIT, "Internal error - signing private key not known?  wtf");
@@ -358,15 +353,43 @@ public class Router {
         }
     }
     
+    // publicize our ballpark capacity - this does not affect anything at
+    // the moment
+    public static final char CAPABILITY_BW16 = 'K';
+    public static final char CAPABILITY_BW32 = 'L';
+    public static final char CAPABILITY_BW64 = 'M';
+    public static final char CAPABILITY_BW128 = 'N';
+    public static final char CAPABILITY_BW256 = 'O';
+    
     public static final char CAPABILITY_REACHABLE = 'R';
     public static final char CAPABILITY_UNREACHABLE = 'U';
     public static final String PROP_FORCE_UNREACHABLE = "router.forceUnreachable";
 
     public static final char CAPABILITY_NEW_TUNNEL = 'T';
     
-    public void addReachabilityCapability(RouterInfo ri) {
-        // routers who can understand TunnelBuildMessages 
-        ////ri.addCapability(CAPABILITY_NEW_TUNNEL);
+    public void addCapabilities(RouterInfo ri) {
+        int bwLim = Math.min(_context.bandwidthLimiter().getInboundKBytesPerSecond(),
+                             _context.bandwidthLimiter().getInboundKBytesPerSecond());
+        if (_log.shouldLog(Log.WARN))
+            _log.warn("Adding capabilities w/ bw limit @ " + bwLim, new Exception("caps"));
+        
+        if (bwLim <= 16) {
+            ri.addCapability(CAPABILITY_BW16);
+        } else if (bwLim <= 32) {
+            ri.addCapability(CAPABILITY_BW32);
+        } else if (bwLim <= 64) {
+            ri.addCapability(CAPABILITY_BW64);
+        } else if (bwLim <= 128) {
+            ri.addCapability(CAPABILITY_BW128);
+        } else { // ok, more than 128KBps... aka "lots"
+            ri.addCapability(CAPABILITY_BW256);
+        }
+        
+        if (FloodfillNetworkDatabaseFacade.floodfillEnabled(_context))
+            ri.addCapability(FloodfillNetworkDatabaseFacade.CAPACITY_FLOODFILL);
+        
+        if("true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false")))
+            ri.addCapability(RouterInfo.CAPABILITY_HIDDEN);
         
         String forceUnreachable = _context.getProperty(PROP_FORCE_UNREACHABLE);
         if ( (forceUnreachable != null) && ("true".equalsIgnoreCase(forceUnreachable)) ) {
diff --git a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java
index 6311b55f17d53c4388bcdf68a18015f45ff0bc1a..346cdc3fd3a0745b51eaa8abdd7c324d6b7c1ed8 100644
--- a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java
+++ b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java
@@ -45,13 +45,8 @@ public class PublishLocalRouterInfoJob extends JobImpl {
             ri.setPublished(getContext().clock().now());
             ri.setOptions(stats);
             ri.setAddresses(getContext().commSystem().createAddresses());
-            if (FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext()))
-                ri.addCapability(FloodfillNetworkDatabaseFacade.CAPACITY_FLOODFILL);
 
-            if ("true".equalsIgnoreCase(getContext().getProperty(Router.PROP_HIDDEN, "false")))
-                ri.addCapability(RouterInfo.CAPABILITY_HIDDEN);
-
-            getContext().router().addReachabilityCapability(ri);
+            getContext().router().addCapabilities(ri);
             SigningPrivateKey key = getContext().keyManager().getSigningPrivateKey();
             if (key == null) {
                 _log.log(Log.CRIT, "Internal error - signing private key not known?  rescheduling publish for 30s");
diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
index 7bf9e10db0f32fe5f6e9322f42be225eb47442b8..b8ea42c2aa8cdd5ab34c4956a165393baece4894 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
@@ -682,6 +682,12 @@ public class ProfileOrganizer {
                           + "], but there aren't enough of them " + numExceedingMean);
             _thresholdCapacityValue = Math.max(thresholdAtMinHighCap, thresholdAtLowest);
         }
+        
+        // the base growth factor is the value we give to new routers that we don't
+        // know anything about.  dont go under that limit unless you want to expose
+        // the selection to simple ident flooding attacks
+        if (_thresholdCapacityValue <= CapacityCalculator.GROWTH_FACTOR)
+            _thresholdCapacityValue = CapacityCalculator.GROWTH_FACTOR + 0.0001;
     }
     
     /**
diff --git a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java
index 6fcbcaeaf1a1f1a5e4325bb2d7b3638500c028b6..18e524d3180fe80f41bc0bd087dd722cd2086f07 100644
--- a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java
+++ b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java
@@ -54,12 +54,8 @@ public class CreateRouterInfoJob extends JobImpl {
             info.setAddresses(getContext().commSystem().createAddresses());
             Properties stats = getContext().statPublisher().publishStatistics();
             stats.setProperty(RouterInfo.PROP_NETWORK_ID, Router.NETWORK_ID+"");
-            getContext().router().addReachabilityCapability(info);
+            getContext().router().addCapabilities(info);
             info.setOptions(stats);
-            if (FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext()))
-                info.addCapability(FloodfillNetworkDatabaseFacade.CAPACITY_FLOODFILL);
-            if ("true".equalsIgnoreCase(getContext().getProperty(Router.PROP_HIDDEN, "false")))
-                info.addCapability(RouterInfo.CAPABILITY_HIDDEN);
             info.setPeers(new HashSet());
             info.setPublished(getCurrentPublishDate(getContext()));
             RouterIdentity ident = new RouterIdentity();
diff --git a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java
index 76403fe8ca80ca39a3a94f4b47ab07fb28ef4b67..e80af2570aa7cdc6a645669bfb4ec795e6339da2 100644
--- a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java
+++ b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java
@@ -128,14 +128,7 @@ public class RebuildRouterInfoJob extends JobImpl {
                 Properties stats = getContext().statPublisher().publishStatistics();
                 stats.setProperty(RouterInfo.PROP_NETWORK_ID, ""+Router.NETWORK_ID);
                 info.setOptions(stats);
-                if (FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext()))
-                    info.addCapability(FloodfillNetworkDatabaseFacade.CAPACITY_FLOODFILL);
-
-                // Set caps=H for hidden mode routers
-                if ("true".equalsIgnoreCase(getContext().getProperty(Router.PROP_HIDDEN, "false"))) 
-                    info.addCapability(RouterInfo.CAPABILITY_HIDDEN);
-
-                getContext().router().addReachabilityCapability(info);
+                getContext().router().addCapabilities(info);
                 // info.setPeers(new HashSet()); // this would have the trusted peers
                 info.setPublished(CreateRouterInfoJob.getCurrentPublishDate(getContext()));
                 
diff --git a/router/java/src/net/i2p/router/transport/OutboundMessageRegistry.java b/router/java/src/net/i2p/router/transport/OutboundMessageRegistry.java
index aaf6b13e0320f82851d7f2f40bf9daace353c418..77f7f19a69ea8c0ca362606b3d3a4bdb896b431a 100644
--- a/router/java/src/net/i2p/router/transport/OutboundMessageRegistry.java
+++ b/router/java/src/net/i2p/router/transport/OutboundMessageRegistry.java
@@ -25,7 +25,7 @@ public class OutboundMessageRegistry {
     private Log _log;
     /** list of currently active MessageSelector instances */
     private List _selectors;
-    /** map of active MessageSelector to the OutNetMessage causing it (for quick removal) */
+    /** map of active MessageSelector to either an OutNetMessage or a List of OutNetMessages causing it (for quick removal) */
     private Map _selectorToMessage;
     /** set of active OutNetMessage (for quick removal and selector fetching) */
     private Set _activeMessages;
@@ -61,6 +61,8 @@ public class OutboundMessageRegistry {
         synchronized (_selectors) {
             for (int i = 0; i < _selectors.size(); i++) {
                 MessageSelector sel = (MessageSelector)_selectors.get(i);
+                if (sel == null)
+                    continue;
                 boolean isMatch = sel.isMatch(message);
                 if (isMatch) {
                     if (matchedSelectors == null) matchedSelectors = new ArrayList(1);
@@ -82,19 +84,36 @@ public class OutboundMessageRegistry {
                 MessageSelector sel = (MessageSelector)matchedSelectors.get(i);
                 boolean removed = false;
                 OutNetMessage msg = null;
+                List msgs = null;
                 synchronized (_selectorToMessage) {
+                    Object o = null;
                     if ( (removedSelectors != null) && (removedSelectors.contains(sel)) ) {
-                        msg = (OutNetMessage)_selectorToMessage.remove(sel);
+                        o = _selectorToMessage.remove(sel);
                         removed = true;
                     } else {
-                        msg = (OutNetMessage)_selectorToMessage.get(sel);
+                        o = _selectorToMessage.get(sel);
+                    }
+                    
+                    if (o instanceof OutNetMessage) {
+                        msg = (OutNetMessage)o;
+                        if (msg != null)
+                            rv.add(msg);
+                    } else if (o instanceof List) {
+                        msgs = (List)o;
+                        if (msgs != null)
+                            for (int j = 0; j < msgs.size(); j++)
+                                rv.add(msgs.get(j));
                     }
-                    if (msg != null)
-                        rv.add(msg);
                 }
-                if (removed && msg != null) {
-                    synchronized (_activeMessages) {
-                        _activeMessages.remove(msg);
+                if (removed) {
+                    if (msg != null) {
+                        synchronized (_activeMessages) {
+                            _activeMessages.remove(msg);
+                        }
+                    } else if (msgs != null) {
+                        synchronized (_activeMessages) {
+                            _activeMessages.removeAll(msgs);
+                        }
                     }
                 }
             }
@@ -128,7 +147,24 @@ public class OutboundMessageRegistry {
             if (!_activeMessages.add(msg))
                 return; // dont add dups
         }
-        synchronized (_selectorToMessage) { _selectorToMessage.put(sel, msg); }
+        synchronized (_selectorToMessage) { 
+            Object oldMsg = _selectorToMessage.put(sel, msg);
+            if (oldMsg != null) {
+                List multi = null;
+                if (oldMsg instanceof OutNetMessage) {
+                    multi = new ArrayList(4);
+                    multi.add(oldMsg);
+                    multi.add(msg);
+                    _selectorToMessage.put(sel, multi);
+                } else if (oldMsg instanceof List) {
+                    multi = (List)oldMsg;
+                    multi.add(msg);
+                    _selectorToMessage.put(sel, multi);
+                }
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn("a single message selector [" + sel + "] with multiple messages ("+ multi + ")");
+            }
+        }
         synchronized (_selectors) { _selectors.add(sel); }
 
         _cleanupTask.scheduleExpiration(sel);
@@ -136,9 +172,22 @@ public class OutboundMessageRegistry {
     
     public void unregisterPending(OutNetMessage msg) {
         MessageSelector sel = msg.getReplySelector();
-        // remember, order matters
-        synchronized (_selectors) { _selectors.add(sel); }
-        synchronized (_selectorToMessage) { _selectorToMessage.put(sel, msg); }
+        boolean stillActive = false;
+        synchronized (_selectorToMessage) { 
+            Object old = _selectorToMessage.remove(sel);
+            if (old != null) {
+                if (old instanceof List) {
+                    List l = (List)old;
+                    l.remove(msg);
+                    if (l.size() > 0) {
+                        _selectorToMessage.put(sel, l);
+                        stillActive = true;
+                    }
+                }
+            }
+        }
+        if (!stillActive)
+            synchronized (_selectors) { _selectors.remove(sel); }
         synchronized (_activeMessages) { _activeMessages.remove(msg); }
     }
 
@@ -156,6 +205,7 @@ public class OutboundMessageRegistry {
             synchronized (_selectors) {
                 for (int i = 0; i < _selectors.size(); i++) {
                     MessageSelector sel = (MessageSelector)_selectors.get(i);
+                    if (sel == null) continue;
                     long expiration = sel.getExpiration();
                     if (expiration <= now) {
                         _removing.add(sel);
@@ -170,8 +220,13 @@ public class OutboundMessageRegistry {
                 for (int i = 0; i < _removing.size(); i++) {
                     MessageSelector sel = (MessageSelector)_removing.get(i);
                     OutNetMessage msg = null;
+                    List msgs = null;
                     synchronized (_selectorToMessage) {
-                        msg = (OutNetMessage)_selectorToMessage.remove(sel);
+                        Object o = _selectorToMessage.remove(sel);
+                        if (o instanceof OutNetMessage)
+                            msg = (OutNetMessage)o;
+                        else if (o instanceof List)
+                            msgs = (List)o;
                     }
                     if (msg != null) {
                         synchronized (_activeMessages) {
@@ -180,6 +235,16 @@ public class OutboundMessageRegistry {
                         Job fail = msg.getOnFailedReplyJob();
                         if (fail != null)
                             _context.jobQueue().addJob(fail);
+                    } else if (msgs != null) {
+                        synchronized (_activeMessages) {
+                            _activeMessages.removeAll(msgs);
+                        }
+                        for (int j = 0; j < msgs.size(); j++) {
+                            msg = (OutNetMessage)msgs.get(i);
+                            Job fail = msg.getOnFailedReplyJob();
+                            if (fail != null)
+                                _context.jobQueue().addJob(fail);
+                        }
                     }
                 }
                 _removing.clear();
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 a4253eb3d58f1700d7f6999f204984aa66a770e1..2b9fb6ca8246be93aec8ea7f55f61519d67c5893 100644
--- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
@@ -552,7 +552,11 @@ public class EstablishmentManager {
             // offer to relay
             // (perhaps we should check our bw usage and/or how many peers we are 
             //  already offering introducing?)
-            state.setSentRelayTag(_context.random().nextLong(MAX_TAG_VALUE));
+            if (state.getSentRelayTag() < 0) {
+                state.setSentRelayTag(_context.random().nextLong(MAX_TAG_VALUE));
+            } else {
+                // don't change it, since we've already prepared our sig
+            }
         } else {
             // don't offer to relay
             state.setSentRelayTag(0);
diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
index 3aa70072ff859226b747a287ba1f5f05dbe3ebcc..86ced1b64994214af19babf8657b6501d655afb5 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
@@ -567,7 +567,7 @@ public class PacketBuilder {
             if ( (off % 16) != 0)
                 off += 16 - (off % 16);
             packet.getPacket().setLength(off);
-            authenticate(packet, state.getIntroKey(), state.getIntroKey());
+            authenticate(packet, state.getCipherKey(), state.getMACKey());
         } 
         
         setTo(packet, to, state.getSentPort());
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 05a94354a6f4afefc59d5ef590084d7098243443..5e9d6fee3c7c72e2acf05f5a3cf94162c469d9c9 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
@@ -337,6 +337,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                 if ( (_externalListenHost == null) ||
                      (!eq(_externalListenHost.getAddress(), _externalListenPort, ourIP, ourPort)) ) {
                     if ( (_reachabilityStatus == CommSystemFacade.STATUS_UNKNOWN) ||
+                         (_externalListenHost == null) || (_externalListenPort <= 0) ||
                          (_context.clock().now() - _reachabilityStatusLastUpdated > 2*TEST_FREQUENCY) ) {
                         // they told us something different and our tests are either old or failing
                         if (_log.shouldLog(Log.INFO))
@@ -358,7 +359,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                         // so lets test again
                         fireTest = true;
                         if (_log.shouldLog(Log.INFO))
-                            _log.info("Different address, but we're fine..");
+                            _log.info("Different address, but we're fine.. (" + _reachabilityStatus + ")");
                     }
                 } else {
                     // matched what we expect
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 0e11a561dbd54b055bb99a9f876292386bd72b7a..23b65a7ca849837ee605817545073291d9629129 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java
@@ -3,6 +3,8 @@ package net.i2p.router.tunnel.pool;
 import java.util.*;
 import net.i2p.router.RouterContext;
 import net.i2p.router.TunnelPoolSettings;
+import net.i2p.stat.Rate;
+import net.i2p.stat.RateStat;
 import net.i2p.util.Log;
 
 /**
@@ -30,7 +32,7 @@ class ExploratoryPeerSelector extends TunnelPeerSelector {
         Set exclude = getExclude(ctx, settings.isInbound(), settings.isExploratory());
         exclude.add(ctx.routerHash());
         HashSet matches = new HashSet(length);
-        boolean exploreHighCap = Boolean.valueOf(ctx.getProperty("router.exploreHighCapacity", "false")).booleanValue();
+        boolean exploreHighCap = shouldPickHighCap(ctx);
         if (exploreHighCap) 
             ctx.profileOrganizer().selectHighCapacityPeers(length, exclude, matches);
         else
@@ -48,4 +50,38 @@ class ExploratoryPeerSelector extends TunnelPeerSelector {
             rv.add(ctx.routerHash());
         return rv;
     }
+    
+    private boolean shouldPickHighCap(RouterContext ctx) {
+        if (Boolean.valueOf(ctx.getProperty("router.exploreHighCapacity", "false")).booleanValue())
+            return true;
+        // no need to explore too wildly at first
+        if (ctx.router().getUptime() <= 10*1000)
+            return true;
+        // ok, if we aren't explicitly asking for it, we should try to pick peers
+        // randomly from the 'not failing' pool.  However, if we are having a
+        // hard time building exploratory tunnels, lets fall back again on the
+        // high capacity peers, at least for a little bit.
+        int failPct = getExploratoryFailPercentage(ctx);
+        return (failPct >= ctx.random().nextInt(100));
+    }
+    
+    private int getExploratoryFailPercentage(RouterContext ctx) {
+        int timeout = getEvents(ctx, "tunnel.buildExploratoryExpire", 10*60*1000);
+        int reject = getEvents(ctx, "tunnel.buildExploratoryReject", 10*60*1000);
+        int accept = getEvents(ctx, "tunnel.buildExploratorySuccess", 10*60*1000);
+        if (accept + reject + timeout <= 0)
+            return 0;
+        double pct = (double)(reject + timeout) / (accept + reject + timeout);
+        return (int)(100 * pct);
+    }
+    
+    private int getEvents(RouterContext ctx, String stat, long period) {
+        RateStat rs = ctx.statManager().getRate(stat);
+        if (rs == null) 
+            return 0;
+        Rate r = rs.getRate(period);
+        if (r == null)
+            return 0;
+        return (int)r.getLastEventCount();
+    }
 }
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
index 4db87586186b5a0cb9802b31fffc1d4d15f77047..50694575c9bb19abfeae1282b56c7adb0a1f333a 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
@@ -601,6 +601,7 @@ public class TunnelPool {
             peers.add(_context.routerHash());
         }
         PooledTunnelCreatorConfig cfg = new PooledTunnelCreatorConfig(_context, peers.size(), settings.isInbound(), settings.getDestination());
+        cfg.setTunnelPool(this);
         // peers[] is ordered endpoint first, but cfg.getPeer() is ordered gateway first
         for (int i = 0; i < peers.size(); i++) {
             int j = peers.size() - 1 - i;