diff --git a/LICENSE.txt b/LICENSE.txt
index 254fde1a3f7927209308755864474e91c256a1b0..295da33aa2eb99b54ee203c0a9927b9a02330a82 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -252,8 +252,8 @@ Applications:
       Bundles systray4j-2.4.1:
       See licenses/LICENSE-LGPLv2.1.txt
 
-   Tomcat 6.0.43:
-   Copyright 1999-2014 The Apache Software Foundation
+   Tomcat 6.0.44:
+   Copyright 1999-2015 The Apache Software Foundation
    See licenses/LICENSE-Apache2.0.txt
    See licenses/NOTICE-Tomcat.txt
 
diff --git a/apps/jetty/apache-tomcat-deployer/NOTICE b/apps/jetty/apache-tomcat-deployer/NOTICE
index 932b4e744d7face86106f3cf1620f8cb81a7259b..32940950b4f5f0e7bfa5533482accb6f26bfe0cb 100644
--- a/apps/jetty/apache-tomcat-deployer/NOTICE
+++ b/apps/jetty/apache-tomcat-deployer/NOTICE
@@ -1,5 +1,5 @@
 Apache Tomcat
-Copyright 1999-2014 The Apache Software Foundation
+Copyright 1999-2015 The Apache Software Foundation
 
 This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
diff --git a/apps/jetty/apache-tomcat-deployer/README-i2p.txt b/apps/jetty/apache-tomcat-deployer/README-i2p.txt
index c64d393b103a54d28ccaff5439c40210eb3c0da0..c354fb71af810a69c72b2b26a837384326d59123 100644
--- a/apps/jetty/apache-tomcat-deployer/README-i2p.txt
+++ b/apps/jetty/apache-tomcat-deployer/README-i2p.txt
@@ -2,7 +2,7 @@ This is Apache Tomcat 6.x, supporting Servlet 2.5 and JSP 2.1.
 The Glassfish JSP 2.1 bundled in Jetty 6 is way too old.
 
 Retrieved from the file
-	apache-tomcat-6.0.43-deployer.tar.gz
+	apache-tomcat-6.0.44-deployer.tar.gz
 
 minus the following files and directores:
 
diff --git a/apps/jetty/apache-tomcat-deployer/lib/el-api.jar b/apps/jetty/apache-tomcat-deployer/lib/el-api.jar
index def835a2561dbc3e56e151e3cdb4cf48fb9332fd..c6c52f48c15b9e22f76f87695a416f92fc42b652 100644
Binary files a/apps/jetty/apache-tomcat-deployer/lib/el-api.jar and b/apps/jetty/apache-tomcat-deployer/lib/el-api.jar differ
diff --git a/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar b/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar
index 9c779fd396379f38d12c6fc4f19d299e3fec48da..f7d19049e7bcb0eb37ab337867c59029120703c9 100644
Binary files a/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar and b/apps/jetty/apache-tomcat-deployer/lib/jasper-el.jar differ
diff --git a/apps/jetty/apache-tomcat-deployer/lib/jasper.jar b/apps/jetty/apache-tomcat-deployer/lib/jasper.jar
index c7f965777a2458f5f8db673bf150e30932a202fe..f7a2204b4d360497aaad7e4d193750db29d98654 100644
Binary files a/apps/jetty/apache-tomcat-deployer/lib/jasper.jar and b/apps/jetty/apache-tomcat-deployer/lib/jasper.jar differ
diff --git a/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar b/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar
index f405513f54d6425fb96e70fcf87f0fcbf25201f4..534a5953dd5438117f8be4cea0f067fd882cbcbb 100644
Binary files a/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar and b/apps/jetty/apache-tomcat-deployer/lib/tomcat-juli.jar differ
diff --git a/apps/jetty/apache-tomcat/README-i2p.txt b/apps/jetty/apache-tomcat/README-i2p.txt
index 503879d85cc8ffc9e48559ee8680b05b670f4d9a..f62c55e862898c2b60e0b83f2ef10ba23b9d8d8a 100644
--- a/apps/jetty/apache-tomcat/README-i2p.txt
+++ b/apps/jetty/apache-tomcat/README-i2p.txt
@@ -1,7 +1,7 @@
 This is Apache Tomcat 6.x, supporting Servlet 2.5 and JSP 2.1.
 
 Retrieved from the file
-	apache-tomcat-6.0.43.tar.gz
+	apache-tomcat-6.0.44.tar.gz
 
 containing only a small subset of lib/tomcat-coyote.jar.
 
diff --git a/apps/jetty/apache-tomcat/lib/tomcat-coyote-util.jar b/apps/jetty/apache-tomcat/lib/tomcat-coyote-util.jar
index 1edfeca8512bb56f6dd6093029b7dbd6f04eec5a..54e2d02d7090e07e78e941562a5152ba52bc5b9e 100644
Binary files a/apps/jetty/apache-tomcat/lib/tomcat-coyote-util.jar and b/apps/jetty/apache-tomcat/lib/tomcat-coyote-util.jar differ
diff --git a/apps/jetty/build.xml b/apps/jetty/build.xml
index 22ee3a5fc8b7e92973e526031c17b6a65045392f..2fad37dfafe9fe236bea7bb9125e3506bd78aa35 100644
--- a/apps/jetty/build.xml
+++ b/apps/jetty/build.xml
@@ -10,7 +10,7 @@
     <property name="javac.compilerargs" value="" />
     <property name="javac.version" value="1.6" />
     <property name="tomcat.lib" value="apache-tomcat-deployer/lib" />
-    <property name="tomcat.ver" value="6.0.43" />
+    <property name="tomcat.ver" value="6.0.44" />
     <property name="tomcat2.lib" value="apache-tomcat-${tomcat.ver}/lib" />
     <property name="tomcat2.lib.small" value="apache-tomcat/lib" />
 
diff --git a/apps/jetty/java/src/net/i2p/jetty/JettyStart.java b/apps/jetty/java/src/net/i2p/jetty/JettyStart.java
index 4eb0d6ed158ae5cde4baa467479f027ca374fb6f..73b4b4d786415d518f67a10bf93af20ab0d8f433 100644
--- a/apps/jetty/java/src/net/i2p/jetty/JettyStart.java
+++ b/apps/jetty/java/src/net/i2p/jetty/JettyStart.java
@@ -16,6 +16,7 @@ package net.i2p.jetty;
 // limitations under the License.
 // ========================================================================
 
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -25,8 +26,10 @@ import java.util.Properties;
 import net.i2p.I2PAppContext;
 import net.i2p.app.*;
 import static net.i2p.app.ClientAppState.*;
+import net.i2p.util.PortMapper;
 
-import java.io.InputStream;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.component.LifeCycle;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.xml.XmlConfiguration;
@@ -45,13 +48,15 @@ public class JettyStart implements ClientApp {
     private final ClientAppManager _mgr;
     private final String[] _args;
     private final List<LifeCycle> _jettys;
+    private final I2PAppContext _context;
     private volatile ClientAppState _state;
+    private volatile int _port;
 
     /**
      *  All args must be XML file names.
      *  Does not support any of the other argument types from org.mortbay.start.Main.
      *
-     *  @param context unused, may be null
+     *  @param context may be null
      *  @param mgr may be null e.g. for use in plugins
      */
     public JettyStart(I2PAppContext context, ClientAppManager mgr, String[] args) throws Exception {
@@ -59,6 +64,7 @@ public class JettyStart implements ClientApp {
         _mgr = mgr;
         _args = args;
         _jettys = new ArrayList<LifeCycle>(args.length);
+        _context = context;
         parseArgs(args);
         _state = INITIALIZED;
     }
@@ -116,6 +122,22 @@ public class JettyStart implements ClientApp {
                 if (!lc.isRunning()) {
                     try {
                         lc.start();
+                        if (_context != null && _context.portMapper().getPort(PortMapper.SVC_EEPSITE) <= 0) {
+                            if (lc instanceof Server) {
+                                Server server = (Server) lc;
+                                Connector[] connectors = server.getConnectors();
+                                if (connectors.length > 0) {
+                                    int port = connectors[0].getPort();
+                                    if (port > 0) {
+                                        _port = port;
+                                        String host = connectors[0].getHost();
+                                        if (host.equals("0.0.0.0") || host.equals("::"))
+                                            host = "127.0.0.1";
+                                        _context.portMapper().register(PortMapper.SVC_EEPSITE, host, port);
+                                    }
+                                }
+                            }
+                        }
                     } catch (Exception e) {
                         changeState(START_FAILED, e);
                         return;
@@ -154,6 +176,10 @@ public class JettyStart implements ClientApp {
                     }
                 }
             }
+            if (_context != null && _port > 0 && _context.portMapper().getPort(PortMapper.SVC_EEPSITE) == _port) {
+                _port = 0;
+                _context.portMapper().unregister(PortMapper.SVC_EEPSITE);
+            }
             changeState(STOPPED);
         }
     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java
index 114441e5c15817f4128202497688a5525cce4aaa..2238625760abc6350ca87017d8074c9664146a54 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java
@@ -167,13 +167,22 @@ public class HomeHelper extends HelperBase {
         ctx.router().saveConfig(prop, buf.toString());
     }
 
-    private static String renderApps(Collection<App> apps) {
+    private String renderApps(Collection<App> apps) {
+        String website = _("Website");
         StringBuilder buf = new StringBuilder(1024);
         buf.append("<div class=\"appgroup\">");
         for (App app : apps) {
+            String url;
+            if (app.name.equals(website) && app.url.equals("http://127.0.0.1:7658/")) {
+                // fixup eepsite link
+                url = "http://" + _context.portMapper().getHost(PortMapper.SVC_EEPSITE, "127.0.0.1") +
+                      ':' + _context.portMapper().getPort(PortMapper.SVC_EEPSITE, 7658) + '/';
+            } else {
+                url = app.url;
+            }
             buf.append("<div class=\"app\">" +
                        "<div class=\"appimg\">" +
-                       "<a href=\"").append(app.url).append("\">" +
+                       "<a href=\"").append(url).append("\">" +
                        "<img class=\"");
             // toopie is 54x68, not 16x16, needs special alignment and sizing
             if (app.icon.endsWith("/itoopie_sm.png"))
@@ -184,7 +193,7 @@ public class HomeHelper extends HelperBase {
                        "</div>" +
                        "<table class=\"app\"><tr class=\"app\"><td class=\"app\">" +
                        "<div class=\"applabel\">" +
-                       "<a href=\"").append(app.url).append("\" title=\"").append(app.desc).append("\">").append(app.name).append("</a>" +
+                       "<a href=\"").append(url).append("\" title=\"").append(app.desc).append("\">").append(app.name).append("</a>" +
                        "</div>" +
                        "</td></tr></table>" +
                        "</div>\n");
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
index 302316217749cec426d43deef349f91654a27eec..344d36bd1ed53b53a7a9d6877f575effcb04267e 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
@@ -11,6 +11,7 @@ import java.util.Map;
 import net.i2p.crypto.SigType;
 import net.i2p.data.DataHelper;
 import net.i2p.router.RouterContext;
+import net.i2p.util.PortMapper;
 
 /**
  *  Refactored from summarynoframe.jsp to save ~100KB
@@ -146,7 +147,11 @@ public class SummaryBarRenderer {
            .append(nbsp(_("Torrents")))
            .append("</a>\n" +
 
-                   "<a href=\"http://127.0.0.1:7658/\" target=\"_blank\" title=\"")
+                   "<a href=\"http://")
+           .append(_context.portMapper().getHost(PortMapper.SVC_EEPSITE, "127.0.0.1"))
+           .append(':')
+           .append(_context.portMapper().getPort(PortMapper.SVC_EEPSITE, 7658))
+           .append("/\" target=\"_blank\" title=\"")
            .append(_("Local web server"))
            .append("\">")
            .append(nbsp(_("Website")))
diff --git a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java
index a0dbdcf55c589a162338ebae7b72aed0297de016..6d264549bec133b5d5b87c04e66e937240c6975a 100644
--- a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java
+++ b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java
@@ -1254,6 +1254,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
     protected String getPrefix() {
         StringBuilder buf = new StringBuilder();
         buf.append('[');
+        buf.append(_state.toString()).append(' ');
         String s = _options.getProperty("inbound.nickname");
         if (s != null)
             buf.append(s);
@@ -1262,7 +1263,6 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
         SessionId id = _sessionId;
         if (id != null)
             buf.append(" #").append(id.getSessionId());
-        buf.append(' ').append(_state.toString());
         buf.append("]: ");
         return buf.toString();
     }
diff --git a/core/java/src/net/i2p/util/PortMapper.java b/core/java/src/net/i2p/util/PortMapper.java
index b3c8e65e775f289e63e9e233b904392c0cc5c32b..816020433522fbf9fc97dd9566f93bdea9f5db14 100644
--- a/core/java/src/net/i2p/util/PortMapper.java
+++ b/core/java/src/net/i2p/util/PortMapper.java
@@ -2,6 +2,7 @@ package net.i2p.util;
 
 import java.io.IOException;
 import java.io.Writer;
+import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -16,7 +17,7 @@ import net.i2p.I2PAppContext;
  * @since 0.8.12
  */
 public class PortMapper {
-    private final ConcurrentHashMap<String, Integer> _dir;
+    private final ConcurrentHashMap<String, InetSocketAddress> _dir;
 
     public static final String SVC_CONSOLE = "console";
     public static final String SVC_HTTPS_CONSOLE = "https_console";
@@ -37,7 +38,7 @@ public class PortMapper {
      *  @param context unused for now
      */
     public PortMapper(I2PAppContext context) {
-        _dir = new ConcurrentHashMap<String, Integer>(8);
+        _dir = new ConcurrentHashMap<String, InetSocketAddress>(8);
     }
 
     /**
@@ -46,9 +47,19 @@ public class PortMapper {
      *  @return success, false if already registered
      */
     public boolean register(String service, int port) {
-        if (port <= 0)
+        return register(service, "127.0.0.1", port);
+    }
+
+    /**
+     *  Add the service
+     *  @param port > 0
+     *  @return success, false if already registered
+     *  @since 0.9.21
+     */
+    public boolean register(String service, String host, int port) {
+        if (port <= 0 || port > 65535)
             return false;
-        return _dir.putIfAbsent(service, Integer.valueOf(port)) == null;
+        return _dir.putIfAbsent(service, InetSocketAddress.createUnresolved(host, port)) == null;
     }
 
     /**
@@ -73,10 +84,24 @@ public class PortMapper {
      *  @return def if not registered
      */
     public int getPort(String service, int def) {
-        Integer port = _dir.get(service);
-        if (port == null)
+        InetSocketAddress ia = _dir.get(service);
+        if (ia == null)
+            return def;
+        return ia.getPort();
+    }
+
+    /**
+     *  Get the registered host for a service.
+     *  Will return "127.0.0.1" if the service was registered without a host.
+     *  @param def default
+     *  @return def if not registered
+     *  @since 0.9.21
+     */
+    public String getHost(String service, String def) {
+        InetSocketAddress ia = _dir.get(service);
+        if (ia == null)
             return def;
-        return port.intValue();
+        return ia.getHostName();
     }
 
     /**
@@ -85,10 +110,13 @@ public class PortMapper {
      */
     public void renderStatusHTML(Writer out) throws IOException {
         List<String> services = new ArrayList(_dir.keySet());
-        out.write("<h2>Port Mapper</h2><table><tr><th>Service<th>Port\n");
+        out.write("<h2>Port Mapper</h2><table><tr><th>Service<th>Host<th>Port\n");
         Collections.sort(services);
         for (String s : services) {
-            out.write("<tr><td>" + s + "<td>" + _dir.get(s) + '\n');
+            InetSocketAddress ia = _dir.get(s);
+            if (ia == null)
+                continue;
+            out.write("<tr><td>" + s + "<td>" + ia.getHostName() + "<td>" + ia.getPort() + '\n');
         }
         out.write("</table>\n");
     }
diff --git a/history.txt b/history.txt
index 4547341e1bd575506dca0293169e7b1e45d8d5e1..4fcb50aaf1d5da496c097907297d092e55f3dacb 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,14 @@
+2015-06-29 zzz
+ * Transport: More fixes for SSU stalling
+
+2015-06-28 zzz
+ * Apache Tomcat 6.0.44
+
+2015-06-25 zzz
+ * Console: Use registered host/port for eepsite link (ticket #1604)
+ * Jetty starter: Register host/port when started
+ * PortMapper: Add hostname support
+
 2015-06-24 zzz
  * Transport: Add failsafe to prevent complete SSU stall waiting
    for bandwidth limiter
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 5a58b19bc22ea9b489b5610aade18177efc4505e..7f4edb97520b081fa27255ad38c4da1068327822 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 = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 11;
+    public final static long BUILD = 13;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java b/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java
index 2f891ec645e5952366db4c150d298f81953cdd36..fe3a06fc321c0bfef10331e9c51f54c49e61e256 100644
--- a/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java
+++ b/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java
@@ -433,7 +433,7 @@ public class FIFOBandwidthLimiter {
                     if (_log.shouldLog(Log.DEBUG))
                         _log.debug("Still denying the " + _pendingInboundRequests.size() 
                                   + " pending inbound requests (status: " + getStatus().toString()
-                                  + ", longest waited " + locked_getLongestInboundWait() + " in)");
+                                  + ", longest waited " + locked_getLongestInboundWait() + ')');
                 }
             }
         }
@@ -505,7 +505,6 @@ public class FIFOBandwidthLimiter {
      */
     private final void locked_satisfyInboundAvailable(List<Request> satisfied) {
         for (int i = 0; i < _pendingInboundRequests.size(); i++) {
-            if (_availableInbound.get() <= 0) break;
             SimpleRequest req = _pendingInboundRequests.get(i);
             long waited = now() - req.getRequestTime();
             if (req.getAborted()) {
@@ -520,17 +519,22 @@ public class FIFOBandwidthLimiter {
                 i--;
                 continue;
             }
-            if ( (req.getAllocationsSinceWait() > 0) && (req.getCompleteListener() == null) ) {
+            int avi = _availableInbound.get();
+            if (avi <= 0) break;
+            // NO, don't do this, since SSU requires a full allocation to proceed.
+            // By stopping after a partial allocation, we stall SSU.
+            // This never affected NTCP (which also requires a full allocation)
+            // since it registers a CompleteListener, so getAllocationsSinceWait() was always zero.
+            //if (req.getAllocationsSinceWait() > 0) {
                 // we have already allocated some values to this request, but
                 // they haven't taken advantage of it yet (most likely they're
                 // IO bound)
                 // (also, the complete listener is only set for situations where 
                 // waitForNextAllocation() is never called)
-                continue;
-            }
+            //    continue;
+            //}
             // ok, they are really waiting for us to give them stuff
             int requested = req.getPendingRequested();
-            int avi = _availableInbound.get();
             int allocated;
             if (avi >= requested) 
                 allocated = requested;
@@ -579,7 +583,7 @@ public class FIFOBandwidthLimiter {
                     if (_log.shouldLog(Log.INFO))
                         _log.info("Still denying the " + _pendingOutboundRequests.size() 
                                   + " pending outbound requests (status: " + getStatus().toString()
-                                  + ", longest waited " + locked_getLongestOutboundWait() + " out)");
+                                  + ", longest waited " + locked_getLongestOutboundWait() + ')');
                 }
             }
         }
@@ -623,7 +627,6 @@ public class FIFOBandwidthLimiter {
      */
     private final void locked_satisfyOutboundAvailable(List<Request> satisfied) {
         for (int i = 0; i < _pendingOutboundRequests.size(); i++) {
-            if (_availableOutbound.get() <= 0) break;
             SimpleRequest req = _pendingOutboundRequests.get(i);
             long waited = now() - req.getRequestTime();
             if (req.getAborted()) {
@@ -638,17 +641,22 @@ public class FIFOBandwidthLimiter {
                 i--;
                 continue;
             }
-            if (req.getAllocationsSinceWait() > 0) {
+            int avo = _availableOutbound.get();
+            if (avo <= 0) break;
+            // NO, don't do this, since SSU requires a full allocation to proceed.
+            // By stopping after a partial allocation, we stall SSU.
+            // This never affected NTCP (which also requires a full allocation)
+            // since it registers a CompleteListener, so getAllocationsSinceWait() was always zero.
+            //if (req.getAllocationsSinceWait() > 0) {
                 // we have already allocated some values to this request, but
                 // they haven't taken advantage of it yet (most likely they're
                 // IO bound)
-                if (_log.shouldLog(Log.WARN))
-                    _log.warn("multiple allocations since wait... ntcp shouldn't do this: " + req);
-                continue;
-            }
+            //    if (_log.shouldLog(Log.WARN))
+            //        _log.warn("multiple allocations since wait... ntcp shouldn't do this: " + req);
+            //    continue;
+            //}
             // ok, they are really waiting for us to give them stuff
             int requested = req.getPendingRequested();
-            int avo = _availableOutbound.get();
             int allocated;
             if (avo >= requested) 
                 allocated = requested;
@@ -659,15 +667,6 @@ public class FIFOBandwidthLimiter {
             req.allocateBytes(allocated);
             satisfied.add(req);
             if (req.getPendingRequested() > 0) {
-                if (req.attachment() != null) {
-                    if (_log.shouldLog(Log.INFO))
-                         _log.info("Allocating " + allocated + " bytes outbound as a partial grant to " 
-                                    + req
-                                    + " waited " 
-                                    + waited
-                                    + "ms) pending " + _pendingOutboundRequests.size()
-                                    + ", longest waited " + locked_getLongestOutboundWait() + " out");
-                }
                 if (_log.shouldLog(Log.DEBUG))
                      _log.debug("Allocating " + allocated + " bytes outbound as a partial grant to " 
                                 + req
@@ -676,15 +675,6 @@ public class FIFOBandwidthLimiter {
                                 + "ms) pending " + _pendingOutboundRequests.size()
                                 + ", longest waited " + locked_getLongestOutboundWait() + " out");
             } else {
-                if (req.attachment() != null) {
-                    if (_log.shouldLog(Log.INFO))
-                         _log.info("Allocating " + allocated + " bytes outbound to finish the partial grant to " 
-                                    + req
-                                    + " waited " 
-                                    + waited
-                                    + "ms) pending " + _pendingOutboundRequests.size()
-                                    + ", longest waited " + locked_getLongestOutboundWait() + " out)");
-                }
                 if (_log.shouldLog(Log.DEBUG))
                      _log.debug("Allocating " + allocated + " bytes outbound to finish the partial grant to " 
                                 + req
@@ -807,9 +797,9 @@ public class FIFOBandwidthLimiter {
         /** uses System clock, not context clock */
         public long getRequestTime() { return _requestTime; }
         public int getTotalRequested() { return _total; }
-        public int getPendingRequested() { return _total - _allocated; }
+        public synchronized int getPendingRequested() { return _total - _allocated; }
         public boolean getAborted() { return _aborted; }
-        public void abort() {
+        public synchronized void abort() {
             _aborted = true;
             // so isComplete() will return true
             _allocated = _total;
@@ -817,6 +807,9 @@ public class FIFOBandwidthLimiter {
         }
         public CompleteListener getCompleteListener() { return _lsnr; }
 
+        /**
+         *  Only used by NTCP.
+         */
         public void setCompleteListener(CompleteListener lsnr) {
             boolean complete = false;
             synchronized (this) {
@@ -832,18 +825,19 @@ public class FIFOBandwidthLimiter {
             }
         }
         
-        private boolean isComplete() { return _allocated >= _total; }
+        private synchronized boolean isComplete() { return _allocated >= _total; }
         
         /**
+         *  Only used by SSU.
          *  May return without allocating.
          *  Check getPendingRequested() > 0 in a loop.
          */
         public void waitForNextAllocation() {
-            _waited = true;
-            _allocationsSinceWait = 0;
             boolean complete = false;
             try {
                 synchronized (this) {
+                    _waited = true;
+                    _allocationsSinceWait = 0;
                     if (isComplete())
                         complete = true;
                     else
@@ -854,17 +848,21 @@ public class FIFOBandwidthLimiter {
                 _lsnr.complete(this);
         }
 
-        int getAllocationsSinceWait() { return _waited ? _allocationsSinceWait : 0; }
+        /**
+         *  Only returns nonzero if there's no listener and waitForNextAllocation()
+         *  has been called (i.e. SSU)
+         *  Now unused.
+         */
+        synchronized int getAllocationsSinceWait() { return _waited ? _allocationsSinceWait : 0; }
 
-        void allocateBytes(int bytes) {
+        /**
+         *  Increments allocationsSinceWait only if there is a listener.
+         *  Does not notify; caller must call notifyAllocation()
+         */
+        synchronized void allocateBytes(int bytes) {
             _allocated += bytes;
             if (_lsnr == null)
                 _allocationsSinceWait++;
-            //if (isComplete()) {
-            //    if (_log.shouldLog(Log.INFO))
-            //        _log.info("allocate " + bytes + " completed, listener=" + _lsnr);
-            //}
-            //notifyAllocation(); // handled within the satisfy* methods
         }
 
         void notifyAllocation() {
@@ -893,8 +891,8 @@ public class FIFOBandwidthLimiter {
 
         @Override
         public String toString() {
-            return "Req" + _requestId + " priority " + _priority +
-                   _allocated + '/' + _total + " bytes";
+            return "Req: " + _requestId + " priority: " + _priority +
+                   ' ' + _allocated + '/' + _total + " bytes";
         }
     }
 
diff --git a/router/java/src/net/i2p/router/transport/UPnP.java b/router/java/src/net/i2p/router/transport/UPnP.java
index 6753c09fcc927aaf958c83584fc9e2484fe1a333..29e0a037c03cd679ecff2f3f8864c71d312cb3dc 100644
--- a/router/java/src/net/i2p/router/transport/UPnP.java
+++ b/router/java/src/net/i2p/router/transport/UPnP.java
@@ -1067,9 +1067,16 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
 		I2PAppContext ctx = new I2PAppContext(props);
 		UPnP upnp = new UPnP(ctx);
 		ControlPoint cp = new ControlPoint();
-		System.out.println("Searching for UPnP devices:");
+		long start = System.currentTimeMillis();
 		cp.start();
+		long s2 = System.currentTimeMillis();
+		System.out.println("Start took " + (s2 - start));
+		System.out.println("Searching for UPnP devices");
+		start = System.currentTimeMillis();
 		cp.search();
+		s2 = System.currentTimeMillis();
+		System.out.println("Search kickoff took " + (s2 - start));
+		System.out.println("Waiting 10 seconds for responses");
 		Thread.sleep(10000);
 		//while(true) {
 			DeviceList list = cp.getDeviceList();
@@ -1081,7 +1088,7 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
 				Device device = it.next();
 				upnp.listSubDev(device.toString(), device, sb);
 				System.out.println("Here is the listing for device " + (++i) +
-				                   ' ' + device.getFriendlyName() + " :");
+				                   ": " + device.getFriendlyName() + " :");
 				System.out.println(sb.toString());
 				sb.setLength(0);
 			}