diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
index c0c272ae79db5a7384c7b957810f2c9665f71acc..ef194ac3f4516437cf17a516eff75e28b23d5768 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
@@ -14,11 +14,13 @@ import java.util.StringTokenizer;
 import net.i2p.I2PAppContext;
 import net.i2p.I2PException;
 import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionException;
 import net.i2p.client.streaming.I2PServerSocket;
 import net.i2p.client.streaming.I2PSocket;
 import net.i2p.client.streaming.I2PSocketEepGet;
 import net.i2p.client.streaming.I2PSocketManager;
 import net.i2p.client.streaming.I2PSocketManagerFactory;
+import net.i2p.data.Base32;
 import net.i2p.data.DataFormatException;
 import net.i2p.data.Destination;
 import net.i2p.data.Hash;
@@ -341,21 +343,44 @@ public class I2PSnarkUtil {
         }
     }
 
+    private static final int BASE32_HASH_LENGTH = 52;   // 1 + Hash.HASH_LENGTH * 8 / 5
+
     /** Base64 Hash or Hash.i2p or name.i2p using naming service */
     Destination getDestination(String ip) {
         if (ip == null) return null;
         if (ip.endsWith(".i2p")) {
             if (ip.length() < 520) {   // key + ".i2p"
-                Destination dest = _context.namingService().lookup(ip);
-                if (dest != null)
-                    return dest;
+                if (_manager != null && ip.length() == BASE32_HASH_LENGTH + 8 && ip.endsWith(".b32.i2p")) {
+                    // Use existing I2PSession for b32 lookups if we have it
+                    // This is much more efficient than using the naming service
+                    I2PSession sess = _manager.getSession();
+                    if (sess != null) {
+                        byte[] b = Base32.decode(ip.substring(0, BASE32_HASH_LENGTH));
+                        if (b != null) {
+                            Hash h = new Hash(b);
+                            if (_log.shouldLog(Log.INFO))
+                                _log.info("Using existing session for lookup of " + ip);
+                            try {
+                                return sess.lookupDest(h);
+                            } catch (I2PSessionException ise) {
+                            }
+                        }
+                    }
+                }
+                if (_log.shouldLog(Log.INFO))
+                    _log.info("Using naming service for lookup of " + ip);
+                return _context.namingService().lookup(ip);
             }
+            if (_log.shouldLog(Log.INFO))
+                _log.info("Creating Destination for " + ip);
             try {
                 return new Destination(ip.substring(0, ip.length()-4)); // sans .i2p
             } catch (DataFormatException dfe) {
                 return null;
             }
         } else {
+            if (_log.shouldLog(Log.INFO))
+                _log.info("Creating Destination for " + ip);
             try {
                 return new Destination(ip);
             } catch (DataFormatException dfe) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
index 7cd2c8cb712f63e4d8a087d30e2e14d5b4ac5179..4ce5c5b99ac476dc09fc332c5e953deb5e918e56 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
@@ -2,8 +2,6 @@ package net.i2p.router.web;
 
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
@@ -13,7 +11,7 @@ import java.util.TreeSet;
 
 import net.i2p.router.client.ClientManagerFacadeImpl;
 import net.i2p.router.startup.ClientAppConfig;
-import net.i2p.router.transport.Addresses;
+import net.i2p.util.Addresses;
 
 public class ConfigClientsHelper extends HelperBase {
     private String _edit;
@@ -67,22 +65,8 @@ public class ConfigClientsHelper extends HelperBase {
 
     /** @since 0.8.3 */
     public String[] intfcAddresses() {
-        String[] addrs = Addresses.getAllAddresses();
-        List<String> aList = new ArrayList();
-        aList.addAll(Arrays.asList(addrs));
-        boolean ipv6 = false;
-        for (String a : aList) {
-            if (a.indexOf(':') >= 0) {
-                ipv6 = true;
-                break;
-            }
-        }
-        if (!aList.contains("0.0.0.0"))
-            aList.add("0.0.0.0");
-        if (ipv6 && !aList.contains("0:0:0:0:0:0:0:0"))
-            aList.add("0:0:0:0:0:0:0:0");  // we could do "::" but all the other ones are probably in long form
-        Collections.sort(aList);
-        return aList.toArray(addrs);
+        ArrayList<String> al = new ArrayList(Addresses.getAllAddresses());
+        return al.toArray(new String[al.size()]);
     }
 
     /** @since 0.8.3 */
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java
index 512b515938b01686ef2febd6ad31cf1b1e5c41c2..9388d06d2a45fc2ed5669b755d5f91e8ce9197fc 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java
@@ -1,14 +1,16 @@
 package net.i2p.router.web;
 
+import java.util.ArrayList;
+
 import net.i2p.data.DataHelper;
 import net.i2p.data.RouterAddress;
 import net.i2p.router.CommSystemFacade;
 import net.i2p.router.Router;
-import net.i2p.router.transport.Addresses;
 import net.i2p.router.transport.TransportManager;
 import net.i2p.router.transport.udp.UDPAddress;
 import net.i2p.router.transport.udp.UDPTransport;
 import net.i2p.time.Timestamper;
+import net.i2p.util.Addresses;
 
 public class ConfigNetHelper extends HelperBase {
     public ConfigNetHelper() {}
@@ -147,7 +149,8 @@ public class ConfigNetHelper extends HelperBase {
     }
     
     public String[] getAddresses() {
-        return Addresses.getAddresses();
+        ArrayList<String> al = new ArrayList(Addresses.getAddresses());
+        return al.toArray(new String[al.size()]);
     }
 
     public String getInboundRate() {
diff --git a/core/java/src/net/i2p/client/DestReplyMessageHandler.java b/core/java/src/net/i2p/client/DestReplyMessageHandler.java
index 573389cfb2679475fd0f4cce1dccf674afb07323..8d5527d59b4e7d4c4d65f6687e702b920615f515 100644
--- a/core/java/src/net/i2p/client/DestReplyMessageHandler.java
+++ b/core/java/src/net/i2p/client/DestReplyMessageHandler.java
@@ -10,6 +10,9 @@ import net.i2p.data.i2cp.I2CPMessage;
 import net.i2p.data.i2cp.DestReplyMessage;
 import net.i2p.util.Log;
 
+import net.i2p.data.Destination;
+import net.i2p.data.Hash;
+
 /**
  * Handle I2CP dest replies from the router
  */
@@ -22,6 +25,12 @@ class DestReplyMessageHandler extends HandlerImpl {
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Handle message " + message);
         DestReplyMessage msg = (DestReplyMessage) message;
-       ((I2PSimpleSession)session).destReceived(msg.getDestination());
+        Destination d = msg.getDestination();
+        if (d != null)
+            session.destReceived(d);
+        Hash h = msg.getHash();
+        if (h != null)
+            session.destLookupFailed(h);
+        // else let it time out
     }
 }
diff --git a/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java b/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java
index 6f0d950514db09e3187743e67d87b2c8006b5a5e..76350cbb219df7fc5c2a5e5dd30d6e4d361e006a 100644
--- a/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java
+++ b/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java
@@ -10,6 +10,8 @@ package net.i2p.client;
  */
 
 import net.i2p.I2PAppContext;
+import net.i2p.data.i2cp.BandwidthLimitsMessage;
+import net.i2p.data.i2cp.DestReplyMessage;
 import net.i2p.data.i2cp.DisconnectMessage;
 import net.i2p.data.i2cp.MessagePayloadMessage;
 import net.i2p.data.i2cp.MessageStatusMessage;
@@ -36,6 +38,8 @@ class I2PClientMessageHandlerMap {
         highest = Math.max(highest, MessagePayloadMessage.MESSAGE_TYPE);
         highest = Math.max(highest, MessageStatusMessage.MESSAGE_TYPE);
         highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE);
+        highest = Math.max(highest, DestReplyMessage.MESSAGE_TYPE);
+        highest = Math.max(highest, BandwidthLimitsMessage.MESSAGE_TYPE);
         
         _handlers = new I2CPMessageHandler[highest+1];
         _handlers[DisconnectMessage.MESSAGE_TYPE] = new DisconnectMessageHandler(context);
@@ -44,6 +48,8 @@ class I2PClientMessageHandlerMap {
         _handlers[MessagePayloadMessage.MESSAGE_TYPE] = new MessagePayloadMessageHandler(context);
         _handlers[MessageStatusMessage.MESSAGE_TYPE] = new MessageStatusMessageHandler(context);
         _handlers[SetDateMessage.MESSAGE_TYPE] = new SetDateMessageHandler(context);
+        _handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context);
+        _handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context);
     }
 
     public I2CPMessageHandler getHandler(int messageTypeId) {
diff --git a/core/java/src/net/i2p/client/I2PSession.java b/core/java/src/net/i2p/client/I2PSession.java
index 1998dad55a737a016d558e456ae2f632eff13e6c..cd20cfc2d655ef86580e18c1faaf248421d5e33d 100644
--- a/core/java/src/net/i2p/client/I2PSession.java
+++ b/core/java/src/net/i2p/client/I2PSession.java
@@ -138,13 +138,21 @@ public interface I2PSession {
     public SigningPrivateKey getPrivateKey();
 
     /**
-     * Lookup up a Hash
-     *
+     * Lookup a Destination by Hash.
+     * Blocking. Waits a max of 10 seconds by default.
      */
     public Destination lookupDest(Hash h) throws I2PSessionException;
 
     /**
-     * Get the current bandwidth limits
+     *  Blocking.
+     *  @param maxWait ms
+     *  @since 0.8.3
+     *  @return null on failure
+     */
+    public Destination lookupDest(Hash h, long maxWait) throws I2PSessionException;
+
+    /**
+     * Get the current bandwidth limits. Blocking.
      */
     public int[] bandwidthLimits() throws I2PSessionException;
 
diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java
index 8090e0eaed88e65144a23f521edc75e650cd672b..e101ff7252c06a28601f94a07949b6a22b3ecddb 100644
--- a/core/java/src/net/i2p/client/I2PSessionImpl.java
+++ b/core/java/src/net/i2p/client/I2PSessionImpl.java
@@ -15,7 +15,6 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
 import java.net.UnknownHostException;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -23,6 +22,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingQueue;
 
 import net.i2p.I2PAppContext;
 import net.i2p.data.DataFormatException;
@@ -33,6 +34,8 @@ import net.i2p.data.PrivateKey;
 import net.i2p.data.SessionKey;
 import net.i2p.data.SessionTag;
 import net.i2p.data.SigningPrivateKey;
+import net.i2p.data.i2cp.DestLookupMessage;
+import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
 import net.i2p.data.i2cp.GetDateMessage;
 import net.i2p.data.i2cp.I2CPMessage;
 import net.i2p.data.i2cp.I2CPMessageException;
@@ -95,6 +98,11 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
     protected I2CPMessageProducer _producer;
     /** map of Long --> MessagePayloadMessage */
     protected Map<Long, MessagePayloadMessage> _availableMessages;
+
+    /** hashes of lookups we are waiting for */
+    protected final LinkedBlockingQueue<LookupWaiter> _pendingLookups = new LinkedBlockingQueue();
+    protected final Object _bwReceivedLock = new Object();
+    protected int[] _bwLimits;
     
     protected I2PClientMessageHandlerMap _handlerMap;
     
@@ -786,12 +794,104 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
         return buf.toString();
     }
 
+    /** called by the message handler */
+    void destReceived(Destination d) {
+        Hash h = d.calculateHash();
+        for (LookupWaiter w : _pendingLookups) {
+            if (w.hash.equals(h)) {
+                w.destination = d;
+                synchronized (w) {
+                    w.notifyAll();
+                }
+            }
+        }
+    }
+
+    /** called by the message handler */
+    void destLookupFailed(Hash h) {
+        for (LookupWaiter w : _pendingLookups) {
+            if (w.hash.equals(h)) {
+                synchronized (w) {
+                    w.notifyAll();
+                }
+            }
+        }
+    }
+
+    /** called by the message handler */
+    void bwReceived(int[] i) {
+        _bwLimits = i;
+        synchronized (_bwReceivedLock) {
+            _bwReceivedLock.notifyAll();
+        }
+    }
+
+    /**
+     *  Simple object to wait for lookup replies
+     *  @since 0.8.3
+     */
+    private static class LookupWaiter {
+        /** the request */
+        public final Hash hash;
+        /** the reply */
+        public Destination destination;
+
+        public LookupWaiter(Hash h) {
+            this.hash = h;
+        }
+    }
+
+    /**
+     *  Blocking. Waits a max of 10 seconds by default.
+     *  See lookupDest with maxWait parameter to change.
+     *  Implemented in 0.8.3 in I2PSessionImpl;
+     *  previously was available only in I2PSimpleSession.
+     *  Multiple outstanding lookups are now allowed.
+     *  @return null on failure
+     */
     public Destination lookupDest(Hash h) throws I2PSessionException {
-        return null;
+        return lookupDest(h, 10*1000);
+    }
+
+    /**
+     *  Blocking.
+     *  @param maxWait ms
+     *  @since 0.8.3
+     *  @return null on failure
+     */
+    public Destination lookupDest(Hash h, long maxWait) throws I2PSessionException {
+        if (_closed)
+            return null;
+        LookupWaiter waiter = new LookupWaiter(h);
+        _pendingLookups.offer(waiter);
+        sendMessage(new DestLookupMessage(h));
+        try {
+            synchronized (waiter) {
+                waiter.wait(maxWait);
+            }
+        } catch (InterruptedException ie) {}
+        _pendingLookups.remove(waiter);
+        return waiter.destination;
     }
 
+    /**
+     *  Blocking. Waits a max of 5 seconds.
+     *  But shouldn't take long.
+     *  Implemented in 0.8.3 in I2PSessionImpl;
+     *  previously was available only in I2PSimpleSession.
+     *  Multiple outstanding lookups are now allowed.
+     *  @return null on failure
+     */
     public int[] bandwidthLimits() throws I2PSessionException {
-        return null;
+        if (_closed)
+            return null;
+        sendMessage(new GetBandwidthLimitsMessage());
+        try {
+            synchronized (_bwReceivedLock) {
+                _bwReceivedLock.wait(5*1000);
+            }
+        } catch (InterruptedException ie) {}
+        return _bwLimits;
     }
 
     protected void updateActivity() {
diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java
index ed9ec5cc369e1a492bc4e0c80b0cb1d6c60f0b2b..e984b6d30712e00b93afd0d3542aac41f28634ef 100644
--- a/core/java/src/net/i2p/client/I2PSimpleSession.java
+++ b/core/java/src/net/i2p/client/I2PSimpleSession.java
@@ -33,12 +33,6 @@ import net.i2p.util.I2PAppThread;
  * @author zzz
  */
 class I2PSimpleSession extends I2PSessionImpl2 {
-    private boolean _destReceived;
-    private /* FIXME final FIXME */ Object _destReceivedLock;
-    private Destination _destination;
-    private boolean _bwReceived;
-    private /* FIXME final FIXME */ Object _bwReceivedLock;
-    private int[] _bwLimits;
 
     /**
      * Create a new session for doing naming and bandwidth queries only. Do not create a destination.
@@ -104,57 +98,6 @@ class I2PSimpleSession extends I2PSessionImpl2 {
         }
     }
 
-    /** called by the message handler */
-    void destReceived(Destination d) {
-        _destReceived = true;
-        _destination = d;
-        synchronized (_destReceivedLock) {
-            _destReceivedLock.notifyAll();
-        }
-    }
-
-    void bwReceived(int[] i) {
-        _bwReceived = true;
-        _bwLimits = i;
-        synchronized (_bwReceivedLock) {
-            _bwReceivedLock.notifyAll();
-        }
-    }
-
-    @Override
-    public Destination lookupDest(Hash h) throws I2PSessionException {
-        if (_closed)
-            return null;
-        _destReceivedLock = new Object();
-        sendMessage(new DestLookupMessage(h));
-        for (int i = 0; i < 10 && !_destReceived; i++) {
-            try {
-                synchronized (_destReceivedLock) {
-                    _destReceivedLock.wait(1000);
-                }
-            } catch (InterruptedException ie) {}
-        }
-        _destReceived = false;
-        return _destination;
-    }
-
-    @Override
-    public int[] bandwidthLimits() throws I2PSessionException {
-        if (_closed)
-            return null;
-        _bwReceivedLock = new Object();
-        sendMessage(new GetBandwidthLimitsMessage());
-        for (int i = 0; i < 5 && !_bwReceived; i++) {
-            try {
-                synchronized (_bwReceivedLock) {
-                    _bwReceivedLock.wait(1000);
-                }
-            } catch (InterruptedException ie) {}
-        }
-        _bwReceived = false;
-        return _bwLimits;
-    }
-
     /**
      * Only map message handlers that we will use
      */
diff --git a/core/java/src/net/i2p/client/naming/LookupDest.java b/core/java/src/net/i2p/client/naming/LookupDest.java
index c90b4a6738dc87ff1b87c83d057474ab2d6987cf..d131efade8ee02a3e2562ab0171efc080c1dc30b 100644
--- a/core/java/src/net/i2p/client/naming/LookupDest.java
+++ b/core/java/src/net/i2p/client/naming/LookupDest.java
@@ -22,6 +22,13 @@ import net.i2p.data.Hash;
  *
  * All calls are blocking and return null on failure.
  * Timeout is set to 10 seconds in I2PSimpleSession.
+ *
+ * As of 0.8.3, standard I2PSessions support lookups,
+ * including multiple lookups in parallel, and overriding
+ * the default timeout.
+ * Using an existing I2PSession is much more efficient and
+ * flexible than using this class.
+ *
  */
 class LookupDest {
 
diff --git a/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java b/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java
index d3b2df9e10277c180147d23dbdade842408ef048..7aaba9c8923a83e70f33e71b4e91073bf4ae48eb 100644
--- a/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java
@@ -13,14 +13,18 @@ import java.io.InputStream;
 import net.i2p.data.DataFormatException;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Destination;
+import net.i2p.data.Hash;
 
 /**
- * Response to DestLookupMessage
- *
+ * Response to DestLookupMessage.
+ * As of 0.8.3, the response may include the hash from the request, indicating
+ * a failure for a specific request.
+ * Payload may be empty (failure), a Hash (failure), or a Destination.
  */
 public class DestReplyMessage extends I2CPMessageImpl {
     public final static int MESSAGE_TYPE = 35;
     private Destination _dest;
+    private Hash _hash;
 
     public DestReplyMessage() {
         super();
@@ -30,23 +34,52 @@ public class DestReplyMessage extends I2CPMessageImpl {
         _dest = d;
     }
 
+    /**
+     *  @param h non-null with non-null data
+     *  @since 0.8.3
+     */
+    public DestReplyMessage(Hash h) {
+        _hash = h;
+    }
+
     public Destination getDestination() {
         return _dest;
     }
 
+    /**
+     *  @since 0.8.3
+     */
+    public Hash getHash() {
+        return _hash;
+    }
+
     protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
-        try {
-            Destination d = new Destination();
-            d.readBytes(in);
-            _dest = d;
-        } catch (DataFormatException dfe) {
-            _dest = null; // null dest allowed
+        if (size == 0) {
+            _dest = null;
+            _hash = null;
+        } else {
+            try {
+                if (size == Hash.HASH_LENGTH) {
+                    Hash h = new Hash();
+                    h.readBytes(in);
+                    _hash = h;
+                } else {
+                    Destination d = new Destination();
+                    d.readBytes(in);
+                    _dest = d;
+                }
+            } catch (DataFormatException dfe) {
+                _dest = null;
+                _hash = null;
+            }
         }
     }
 
     protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
-        if (_dest == null)
+        if (_dest == null && _hash == null)
             return new byte[0];  // null response allowed
+        if (_dest == null && _hash != null)
+            return _hash.getData();
         ByteArrayOutputStream os = new ByteArrayOutputStream(_dest.size());
         try {
             _dest.writeBytes(os);
@@ -65,7 +98,8 @@ public class DestReplyMessage extends I2CPMessageImpl {
     public boolean equals(Object object) {
         if ((object != null) && (object instanceof DestReplyMessage)) {
             DestReplyMessage msg = (DestReplyMessage) object;
-            return DataHelper.eq(getDestination(), msg.getDestination());
+            return DataHelper.eq(getDestination(), msg.getDestination()) &&
+                   DataHelper.eq(getHash(), msg.getHash());
         }
         return false;
     }
@@ -75,6 +109,7 @@ public class DestReplyMessage extends I2CPMessageImpl {
         StringBuilder buf = new StringBuilder();
         buf.append("[DestReplyMessage: ");
         buf.append("\n\tDestination: ").append(_dest);
+        buf.append("\n\tHash: ").append(_hash);
         buf.append("]");
         return buf.toString();
     }
diff --git a/core/java/src/net/i2p/util/Addresses.java b/core/java/src/net/i2p/util/Addresses.java
new file mode 100644
index 0000000000000000000000000000000000000000..3af5ea65a03ae710c5254a57a2800e4d63547212
--- /dev/null
+++ b/core/java/src/net/i2p/util/Addresses.java
@@ -0,0 +1,125 @@
+package net.i2p.util;
+
+/*
+ * public domain
+ */
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+
+/**
+ * Get the local addresses
+ *
+ * @since 0.8.3 moved to core
+ * @author zzz
+ */
+public abstract class Addresses {
+    
+    /** @return the first non-local address it finds, or null */
+    public static String getAnyAddress() {
+        SortedSet<String> a = getAddresses();
+        if (!a.isEmpty())
+            return a.first();
+        return null;
+    }
+
+    /**
+     *  @return a sorted set of all addresses, excluding
+     *  IPv6, local, broadcast, multicast, etc.
+     */
+    public static SortedSet<String> getAddresses() {
+        return getAddresses(false, false);
+    }
+
+    /**
+     *  @return a sorted set of all addresses, excluding
+     *  only link local and multicast
+     *  @since 0.8.3
+     */
+    public static SortedSet<String> getAllAddresses() {
+        return getAddresses(true, true);
+    }
+
+    /**
+     *  @return a sorted array of all addresses
+     *  @param whether to exclude IPV6 and local
+     *  @return an array of all addresses
+     *  @since 0.8.3
+     */
+    public static SortedSet<String> getAddresses(boolean includeLocal, boolean includeIPv6) {
+        SortedSet<String> rv = new TreeSet();
+        try {
+            InetAddress localhost = InetAddress.getLocalHost();
+            InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName());
+            if (allMyIps != null) {
+                for (int i = 0; i < allMyIps.length; i++) {
+                    if (shouldInclude(allMyIps[i], includeLocal, includeIPv6))
+                        rv.add(allMyIps[i].getHostAddress());
+                }
+            }
+        } catch (UnknownHostException e) {}
+
+        try {
+            for(Enumeration<NetworkInterface> ifcs = NetworkInterface.getNetworkInterfaces(); ifcs.hasMoreElements();) {
+                NetworkInterface ifc = ifcs.nextElement();
+                for(Enumeration<InetAddress> addrs =  ifc.getInetAddresses(); addrs.hasMoreElements();) {
+                    InetAddress addr = addrs.nextElement();
+                    if (shouldInclude(addr, includeLocal, includeIPv6))
+                        rv.add(addr.getHostAddress());
+                }
+            }
+        } catch (SocketException e) {}
+
+        if (includeLocal)
+            rv.add("0.0.0.0");
+        if (includeLocal && includeIPv6) {
+            boolean ipv6 = false;
+            for (String a : rv) {
+                if (a.indexOf(':') >= 0) {
+                    ipv6 = true;
+                    break;
+                }
+            }
+            if (ipv6)
+                rv.add("0:0:0:0:0:0:0:0");  // we could do "::" but all the other ones are probably in long form
+        }
+        return rv;
+    }
+
+    private static boolean shouldInclude(InetAddress ia, boolean includeLocal, boolean includeIPv6) {
+        return
+            (!ia.isLinkLocalAddress()) &&
+            (!ia.isMulticastAddress()) &&
+            (includeLocal ||
+             ((!ia.isAnyLocalAddress()) &&
+              (!ia.isLoopbackAddress()) &&
+              (!ia.isSiteLocalAddress()))) &&
+            // Hamachi 5/8 allocated to RIPE (30 November 2010)
+            // Removed from TransportImpl.isPubliclyRoutable()
+            // Check moved to here, for now, but will eventually need to
+            // remove it from here also.
+            (includeLocal ||
+            (!ia.getHostAddress().startsWith("5."))) &&
+            (includeIPv6 ||
+             (ia instanceof Inet4Address));
+    }
+
+    public static void main(String[] args) {
+        System.err.println("External Addresses:");
+        Set<String> a = getAddresses(false, false);
+        for (String s : a)
+            System.err.println(s);
+        System.err.println("All addresses:");
+        a = getAddresses(true, true);
+        for (String s : a)
+            System.err.println(s);
+    }
+}
diff --git a/router/java/src/net/i2p/router/transport/Addresses.java b/router/java/src/net/i2p/router/transport/Addresses.java
deleted file mode 100644
index 5200e1173dfd5f375a721be86b1c989af50e4769..0000000000000000000000000000000000000000
--- a/router/java/src/net/i2p/router/transport/Addresses.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package net.i2p.router.transport;
-
-/*
- * public domain
- */
-
-import java.net.InetAddress;
-import java.net.Inet4Address;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Set;
-
-
-/**
- * Get the local addresses
- *
- * @author zzz
- */
-public class Addresses {
-    
-    /** @return the first non-local address it finds, or null */
-    public static String getAnyAddress() {
-        String[] a = getAddresses();
-        if (a.length > 0)
-            return a[0];
-        return null;
-    }
-
-    /**
-     *  @return a sorted array of all addresses, excluding
-     *  IPv6, local, broadcast, multicast, etc.
-     */
-    public static String[] getAddresses() {
-        return getAddresses(false);
-    }
-
-    /**
-     *  @return a sorted array of all addresses, excluding
-     *  only link local and multicast
-     *  @since 0.8.3
-     */
-    public static String[] getAllAddresses() {
-        return getAddresses(true);
-    }
-
-    /**
-     *  @return a sorted array of all addresses
-     *  @param whether to exclude IPV6 and local
-     *  @return an array of all addresses
-     *  @since 0.8.3
-     */
-    public static String[] getAddresses(boolean all) {
-        Set<String> rv = new HashSet(4);
-        try {
-            InetAddress localhost = InetAddress.getLocalHost();
-            InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName());
-            if (allMyIps != null) {
-                for (int i = 0; i < allMyIps.length; i++) {
-                    if (all)
-                        addAll(rv, allMyIps[i]);
-                    else
-                        add(rv, allMyIps[i]);
-                }
-            }
-        } catch (UnknownHostException e) {}
-
-        try {
-            for(Enumeration<NetworkInterface> ifcs = NetworkInterface.getNetworkInterfaces(); ifcs.hasMoreElements();) {
-                NetworkInterface ifc = ifcs.nextElement();
-                for(Enumeration<InetAddress> addrs =  ifc.getInetAddresses(); addrs.hasMoreElements();) {
-                    InetAddress addr = addrs.nextElement();
-                    if (all)
-                        addAll(rv, addr);
-                    else
-                        add(rv, addr);
-                }
-            }
-        } catch (SocketException e) {}
-
-        String[] rva = rv.toArray(new String[rv.size()]);
-        Arrays.sort(rva);
-        return rva;
-    }
-
-    private static void add(Set<String> set, InetAddress ia) {
-        if (ia.isAnyLocalAddress() ||
-            ia.isLinkLocalAddress() ||
-            ia.isLoopbackAddress() ||
-            ia.isMulticastAddress() ||
-            ia.isSiteLocalAddress() ||
-            // Hamachi 5/8 allocated to RIPE (30 November 2010)
-            // Removed from TransportImpl.isPubliclyRoutable()
-            // Check moved to here, for now, but will eventually need to
-            // remove it from here also.
-            ia.getHostAddress().startsWith("5.") ||
-            !(ia instanceof Inet4Address)) {
-//            System.err.println("Skipping: " + ia.getHostAddress());
-            return;
-        }
-        String ip = ia.getHostAddress();
-        set.add(ip);
-    }
-
-    private static void addAll(Set<String> set, InetAddress ia) {
-        if (ia.isLinkLocalAddress() ||
-            ia.isMulticastAddress())
-            return;
-        String ip = ia.getHostAddress();
-        set.add(ip);
-    }
-
-    public static void main(String[] args) {
-        String[] a = getAddresses(true);
-        for (String s : a)
-            System.err.println("Address: " + s);
-    }
-}
diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java
index cf21af11d9275b5ae750756487c4598c5e32184c..a682840b1780fa28749d2e0d52870fa4f36983eb 100644
--- a/router/java/src/net/i2p/router/transport/TransportManager.java
+++ b/router/java/src/net/i2p/router/transport/TransportManager.java
@@ -32,6 +32,7 @@ import net.i2p.router.OutNetMessage;
 import net.i2p.router.RouterContext;
 import net.i2p.router.transport.ntcp.NTCPTransport;
 import net.i2p.router.transport.udp.UDPTransport;
+import net.i2p.util.Addresses;
 import net.i2p.util.Log;
 import net.i2p.util.Translate;