diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
index 34dc025d56f802e0904c0b0605efb485ba019404..8b322d45a04db3e78e564790c56c927c683ca1d2 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
@@ -1328,25 +1328,30 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
      * @param l logger to receive events and output
      */
     private void runConfig(String args[], Logging l) {
-        if (args.length >= 2) {
+        if (args.length >= 1) {
             int i = 0;
-            if (args[0].equals("-s")) {
+            boolean ssl = args[0].equals("-s");
+            if (ssl) {
                 _clientOptions.setProperty("i2cp.SSL", "true");
                 i++;
             } else {
                 _clientOptions.remove("i2cp.SSL");
             }
-            host = args[i++];
-            listenHost = host;
-            port = args[i];
+            if (i < args.length) {
+                host = args[i++];
+                listenHost = host;
+            }
+            if (i < args.length)
+                port = args[i];
+            l.log("New setting: " + host + ' ' + port + (ssl ? " SSL" : " non-SSL"));
             notifyEvent("configResult", "ok");
         } else {
             boolean ssl = Boolean.parseBoolean(_clientOptions.getProperty("i2cp.SSL"));
             l.log("Usage:\n" +
-                  "  config [-s] <i2phost> <i2pport>\n" +
-                  "  sets the connection to the i2p router.\n" +
-                  "Current setting:\n" +
-                  "  " + host + ' ' + port + (ssl ? " SSL" : ""));
+                  "  config [-s] [<i2phost>] [<i2pport>]\n" +
+                  "  Sets the address and port of the I2P router.\n" +
+                  "  Use -s for SSL.\n" +
+                  "Current setting: " + host + ' ' + port + (ssl ? " SSL" : " non-SSL"));
             notifyEvent("configResult", "error");
         }
     }
@@ -1750,8 +1755,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
      */
     public void log(String s) {
         System.out.println(s);
-        if (_log.shouldLog(Log.INFO))
-            _log.info(getPrefix() + "Display: " + s);
+        //if (_log.shouldLog(Log.INFO))
+        //    _log.info(getPrefix() + "Display: " + s);
     }
 
     /**
@@ -1769,8 +1774,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
             l.log("Generating new keys...");
             I2PClient client = I2PClientFactory.createClient();
             Destination d = client.createDestination(writeTo);
-            l.log("Secret key saved.\n" +
-                  "Public key: " + d.toBase64());
+            l.log("New destination: " + d.toBase32());
             writeTo.flush();
             writeTo.close();
             writePubKey(d, pubDest, l);
@@ -1793,7 +1797,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
         try {
             Destination d = new Destination();
             d.readBytes(readFrom);
-            l.log("Public key: " + d.toBase64());
+            l.log("Destination: " + d.toBase32());
             readFrom.close();
             writePubKey(d, pubDest, l);
         } catch (I2PException ex) {
@@ -1808,7 +1812,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
      * Deprecated - only used by CLI
      *
      * @param d Destination to write
-     * @param o stream to write the destination to
+     * @param o stream to write the destination to, or null for noop
      * @param l logger to send messages to
      */
     private static void writePubKey(Destination d, OutputStream o, Logging l) throws I2PException, IOException {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java
index e3fdee8a37089b031a4b10768d62a942f38f6e86..e35f4342a12018cce181cedbc4299275632c2d50 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java
@@ -102,6 +102,7 @@ public class TunnelControllerGroup implements ClientApp {
      *  @param mgr may be null
      *  @param args one arg, the config file, if not absolute will be relative to the context's config dir,
      *              if empty or null, the default is i2ptunnel.config
+     *  @throws IllegalArgumentException if too many args
      *  @since 0.9.4
      */
     public TunnelControllerGroup(I2PAppContext context, ClientAppManager mgr, String[] args) {
@@ -149,7 +150,19 @@ public class TunnelControllerGroup implements ClientApp {
      *  @since 0.9.4
      */
     public void startup() {
-        loadControllers(_configFile);
+        try {
+            loadControllers(_configFile);
+        } catch (IllegalArgumentException iae) {
+            if (DEFAULT_CONFIG_FILE.equals(_configFile) && !_context.isRouterContext()) {
+                // for i2ptunnel command line
+                synchronized (_controllersLoadedLock) {
+                    _controllersLoaded = true;
+                }
+                _log.logAlways(Log.WARN, "Not in router context and no preconfigured tunnels");
+            } else {
+                throw iae;
+            }
+        }
         startControllers();
         if (_mgr != null)
             _mgr.register(this);
@@ -274,8 +287,12 @@ public class TunnelControllerGroup implements ClientApp {
         synchronized (_controllersLoadedLock) {
             _controllersLoaded = true;
         }
-        if (_log.shouldLog(Log.INFO))
-            _log.info(i + " controllers loaded from " + configFile);
+        if (i > 0) {
+            if (_log.shouldLog(Log.INFO))
+                _log.info(i + " controllers loaded from " + configFile);
+        } else {
+            _log.logAlways(Log.WARN, "No i2ptunnel configurations found in " + configFile);
+        }
     }
 
     /**
@@ -294,6 +311,10 @@ public class TunnelControllerGroup implements ClientApp {
             synchronized(TunnelControllerGroup.this) {
                 _controllersLock.readLock().lock();
                 try {
+                    if (_controllers.size() <= 0) {
+                        _log.logAlways(Log.WARN, "No configured tunnels to start");
+                        return;
+                    }
                     for (int i = 0; i < _controllers.size(); i++) {
                         TunnelController controller = _controllers.get(i);
                         if (controller.getStartOnLoad())
diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
index 226973fe4f6440c0d723cd8a62f3b309e4de1803..10a480c78e56349266b784d1c96f89d3034b48df 100644
--- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
+++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
@@ -165,7 +165,9 @@ class ClientConnectionRunner {
      */
     public synchronized void stopRunning() {
         if (_dead) return;
-        if (_context.router().isAlive() && _log.shouldLog(Log.WARN)) 
+        // router may be null in unit tests
+        if ((_context.router() == null || _context.router().isAlive()) &&
+            _log.shouldWarn()) 
             _log.warn("Stop the I2CP connection!  current leaseSet: " 
                       + _currentLeaseSet, new Exception("Stop client connection"));
         _dead = true;
diff --git a/router/java/src/net/i2p/router/client/ClientListenerRunner.java b/router/java/src/net/i2p/router/client/ClientListenerRunner.java
index 44d7811b6b06243f3ef69083fed58091c262cd6e..8510fa0719f5de7bb6344243c4db5987c31e87eb 100644
--- a/router/java/src/net/i2p/router/client/ClientListenerRunner.java
+++ b/router/java/src/net/i2p/router/client/ClientListenerRunner.java
@@ -15,6 +15,7 @@ import java.net.ServerSocket;
 import java.net.Socket;
 
 import net.i2p.client.I2PClient;
+import net.i2p.router.Router;
 import net.i2p.router.RouterContext;
 import net.i2p.util.Log;
 
@@ -101,17 +102,17 @@ class ClientListenerRunner implements Runnable {
                             } catch (IOException ioe) {}
                         }
                     } catch (IOException ioe) {
-                        if (_context.router().isAlive()) 
+                        if (isAlive()) 
                             _log.error("Server error accepting", ioe);
                     } catch (Throwable t) {
-                        if (_context.router().isAlive()) 
+                        if (isAlive()) 
                             _log.error("Fatal error running client listener - killing the thread!", t);
                         _listening = false;
                         return;
                     }
                 }
             } catch (IOException ioe) {
-                if (_context.router().isAlive()) 
+                if (isAlive()) 
                     _log.error("Error listening on port " + _port, ioe);
             }
             
@@ -121,7 +122,7 @@ class ClientListenerRunner implements Runnable {
                 _socket = null; 
             }
             
-            if (!_context.router().isAlive()) break;
+            if (!isAlive()) break;
             
             if (curDelay < 60*1000)
                 _log.error("Error listening, waiting " + (curDelay/1000) + "s before we try again");
@@ -131,11 +132,20 @@ class ClientListenerRunner implements Runnable {
             curDelay = Math.min(curDelay*3, 60*1000);
         }
 
-        if (_context.router().isAlive())
+        if (isAlive())
             _log.error("CANCELING I2CP LISTEN", new Exception("I2CP Listen cancelled!!!"));
         _running = false;
     }
     
+    /** 
+     *  Just so unit tests don't NPE, where router could be null.
+     *  @since 0.9.20
+     */
+    private boolean isAlive() {
+        Router r = _context.router();
+        return r == null || r.isAlive();
+    }	
+
     /** give the i2cp client 5 seconds to show that they're really i2cp clients */
     protected final static int CONNECT_TIMEOUT = 5*1000;
     private final static int LOOP_DELAY = 250;
diff --git a/router/java/test/junit/net/i2p/router/client/LocalClientConnectionRunner.java b/router/java/test/junit/net/i2p/router/client/LocalClientConnectionRunner.java
index 85dcbcab08e4d320858cd9a2693afa5664ecb54b..9ded6ce909defda33a46d9dc0f59046dcfae9dc2 100644
--- a/router/java/test/junit/net/i2p/router/client/LocalClientConnectionRunner.java
+++ b/router/java/test/junit/net/i2p/router/client/LocalClientConnectionRunner.java
@@ -8,6 +8,8 @@ import net.i2p.data.Lease;
 import net.i2p.data.LeaseSet;
 import net.i2p.data.i2cp.I2CPMessageException;
 import net.i2p.data.i2cp.I2CPMessageReader;
+import net.i2p.data.i2cp.MessageId;
+import net.i2p.data.i2cp.MessageStatusMessage;
 import net.i2p.data.i2cp.RequestVariableLeaseSetMessage;
 import net.i2p.router.Job;
 import net.i2p.router.RouterContext;
@@ -54,6 +56,27 @@ class LocalClientConnectionRunner extends ClientConnectionRunner {
         }
     }
 
+    /**
+     *  No job queue, so super NPEs
+     */
+    @Override
+    void updateMessageDeliveryStatus(MessageId id, long messageNonce, int status) {
+        if (messageNonce <= 0)
+            return;
+        MessageStatusMessage msg = new MessageStatusMessage();
+        msg.setMessageId(id.getMessageId());
+        msg.setSessionId(getSessionId().getSessionId());
+        // has to be >= 0, it is initialized to -1
+        msg.setNonce(messageNonce);
+        msg.setSize(0);
+        msg.setStatus(status);
+        try {
+            doSend(msg);
+        } catch (I2CPMessageException ime) {
+            _log.warn("Error updating the status for " + id, ime);
+        }
+    }
+
     /**
      *  So LocalClientMessageEventListener can lookup other local dests
      */
diff --git a/router/java/test/junit/net/i2p/router/client/LocalClientManager.java b/router/java/test/junit/net/i2p/router/client/LocalClientManager.java
index f8df0298997caf6c52a059a0e319794e5e9fd890..12666969da0b315e6d170a03810b4a4d0d13e639 100644
--- a/router/java/test/junit/net/i2p/router/client/LocalClientManager.java
+++ b/router/java/test/junit/net/i2p/router/client/LocalClientManager.java
@@ -73,7 +73,7 @@ class LocalClientManager extends ClientManager {
         ClientManager mgr = new LocalClientManager(ctx, port);
         mgr.start();
         System.out.println("Listening on port " + port);
-        try { Thread.sleep(5*60*1000); } catch (InterruptedException ie) {}
+        try { Thread.sleep(60*60*1000); } catch (InterruptedException ie) {}
         System.out.println("Done listening on port " + port);
     }
 }
diff --git a/router/java/test/junit/net/i2p/router/client/LocalClientMessageEventListener.java b/router/java/test/junit/net/i2p/router/client/LocalClientMessageEventListener.java
index 611e0c24fb6d2717c56487e202fdd70db8ae40ef..17cd20e7165986fd68ad264c25f95f92bc78ae45 100644
--- a/router/java/test/junit/net/i2p/router/client/LocalClientMessageEventListener.java
+++ b/router/java/test/junit/net/i2p/router/client/LocalClientMessageEventListener.java
@@ -9,7 +9,9 @@ package net.i2p.router.client;
  */
 
 import java.util.Date;
+import java.util.Locale;
 
+import net.i2p.data.Base32;
 import net.i2p.data.Destination;
 import net.i2p.data.Hash;
 import net.i2p.data.Lease;
@@ -20,7 +22,10 @@ import net.i2p.data.i2cp.CreateLeaseSetMessage;
 import net.i2p.data.i2cp.DestLookupMessage;
 import net.i2p.data.i2cp.DestReplyMessage;
 import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
+import net.i2p.data.i2cp.HostLookupMessage;
+import net.i2p.data.i2cp.HostReplyMessage;
 import net.i2p.data.i2cp.I2CPMessageException;
+import net.i2p.data.i2cp.SessionId;
 import net.i2p.router.RouterContext;
 
 /**
@@ -78,6 +83,40 @@ class LocalClientMessageEventListener extends ClientMessageEventListener {
         }
     }
 
+    /**
+     *  Look only in current local dests
+     */
+    @Override
+    protected void handleHostLookup(HostLookupMessage message) {
+        Hash h = message.getHash();
+        String name = message.getHostname();
+        long reqID = message.getReqID();
+        SessionId sessID = message.getSessionId();
+        if (h == null && name != null && name.length() == 60) {
+            // convert a b32 lookup to a hash lookup
+            String nlc = name.toLowerCase(Locale.US);
+            if (nlc.endsWith(".b32.i2p")) {
+                byte[] b = Base32.decode(nlc.substring(0, 52));
+                if (b != null && b.length == Hash.HASH_LENGTH) {
+                    h = Hash.create(b);
+                }
+            }
+        }
+        Destination d = null;
+        if (h != null)
+            d = ((LocalClientConnectionRunner)_runner).localLookup(h);
+        HostReplyMessage msg;
+        if (d != null)
+            msg = new HostReplyMessage(sessID, d, reqID);
+        else
+            msg = new HostReplyMessage(sessID, HostReplyMessage.RESULT_FAILURE, reqID);
+        try {
+            _runner.doSend(msg);
+        } catch (I2CPMessageException ime) {
+            ime.printStackTrace();
+        }
+    }
+
     /**
      *  Send dummy limits
      */