diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
index 1581b7b4dfaa9fa3f0db943a2a17a5f272bde933..42dcd3352bfd5f62c92b83cd90c10c6bf0f8c08a 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
@@ -160,7 +160,6 @@ public class SummaryHelper extends HelperBase {
             case IPV4_OK_IPV6_UNKNOWN:
             case IPV4_OK_IPV6_FIREWALLED:
             case IPV4_UNKNOWN_IPV6_OK:
-            case IPV4_FIREWALLED_IPV6_OK:
             case IPV4_DISABLED_IPV6_OK:
             case IPV4_SNAT_IPV6_OK:
                 RouterAddress ra = routerInfo.getTargetAddress("NTCP");
@@ -179,10 +178,12 @@ public class SummaryHelper extends HelperBase {
                 return _("ERR-SymmetricNAT");
 
             case REJECT_UNSOLICITED:
-            case IPV4_FIREWALLED_IPV6_UNKNOWN:
             case IPV4_DISABLED_IPV6_FIREWALLED:
                 if (routerInfo.getTargetAddress("NTCP") != null)
                     return _("WARN-Firewalled with Inbound TCP Enabled");
+                // fall through...
+            case IPV4_FIREWALLED_IPV6_OK:
+            case IPV4_FIREWALLED_IPV6_UNKNOWN:
                 if (((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled())
                     return _("WARN-Firewalled and Floodfill");
                 //if (_context.router().getRouterInfo().getCapabilities().indexOf('O') >= 0)
diff --git a/router/java/src/net/i2p/router/CommSystemFacade.java b/router/java/src/net/i2p/router/CommSystemFacade.java
index cdd4624f342f7bedffa9faa92fdea5770fd4e8a1..e9c050762fea74a5a99fc6bfc46f5c9419af2624 100644
--- a/router/java/src/net/i2p/router/CommSystemFacade.java
+++ b/router/java/src/net/i2p/router/CommSystemFacade.java
@@ -113,7 +113,19 @@ public abstract class CommSystemFacade implements Service {
     /** 
      * Tell other transports our address changed
      */
-    public void notifyReplaceAddress(RouterAddress UDPAddr) {}
+    public void notifyReplaceAddress(RouterAddress address) {}
+
+    /** 
+     * Tell other transports our address changed
+     * @since 0.9.20
+     */
+    public void notifyRemoveAddress(RouterAddress address) {}
+
+    /** 
+     * Tell other transports our address changed
+     * @since 0.9.20
+     */
+    public void notifyRemoveAddress(boolean ipv6) {}
 
     /**
      *  Pluggable transport
diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
index d2e29f21c85f5508892fafa558ca0690de9d3077..a9b8020607ea8f634696fa03b9d2c902fa0d9d89 100644
--- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
+++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
@@ -238,7 +238,34 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
             if (udp != null)
                 port = udp.getRequestedPort();
         }
-        _manager.externalAddressReceived(Transport.AddressSource.SOURCE_SSU, ip, port);
+        if (ip != null || port > 0)
+            _manager.externalAddressReceived(Transport.AddressSource.SOURCE_SSU, ip, port);
+        else
+            notifyRemoveAddress(false);
+    }
+
+    /** 
+     *  Tell other transports our address changed
+     *
+     *  @param address non-null; but address's host/IP may be null
+     *  @since 0.9.20
+     */
+    @Override
+    public void notifyRemoveAddress(RouterAddress address) {
+        // just keep this simple for now, multiple v4 or v6 addresses not yet supported
+        notifyRemoveAddress(address != null &&
+                            address.getIP() != null &&
+                            address.getIP().length == 16);
+    }
+
+    /** 
+     *  Tell other transports our address changed
+     *
+     *  @since 0.9.20
+     */
+    @Override
+    public void notifyRemoveAddress(boolean ipv6) {
+        _manager.externalAddressRemoved(Transport.AddressSource.SOURCE_SSU, ipv6);
     }
 
     /**
diff --git a/router/java/src/net/i2p/router/transport/Transport.java b/router/java/src/net/i2p/router/transport/Transport.java
index 0789d7e75ffc5125a77066dc7c075c3c99eb4bd0..a9c03b2ee8b07e194751f370ae4b1ecf507d1de2 100644
--- a/router/java/src/net/i2p/router/transport/Transport.java
+++ b/router/java/src/net/i2p/router/transport/Transport.java
@@ -98,6 +98,24 @@ public interface Transport {
      */
     public void externalAddressReceived(AddressSource source, byte[] ip, int port);
 
+    /**
+     *  Notify a transport of an external address change.
+     *  This may be from a local interface, UPnP, a config change, etc.
+     *  This should not be called if the ip didn't change
+     *  (from that source's point of view), or is a local address.
+     *  May be called multiple times for IPv4 or IPv6.
+     *  The transport should also do its own checking on whether to accept
+     *  notifications from this source.
+     *
+     *  This can be called after the transport is running.
+     *
+     *  TODO externalAddressRemoved(source, ip, port)
+     *
+     *  @param source defined in Transport.java
+     *  @since 0.9.20
+     */
+    public void externalAddressRemoved(AddressSource source, boolean ipv6);
+
     /**
      *  Notify a transport of the results of trying to forward a port.
      *
diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java
index 6056089dd038c414dfc322db1891d758ac97269f..b5f0870aadefab5d75e4e1b0580780c33ff5b582 100644
--- a/router/java/src/net/i2p/router/transport/TransportImpl.java
+++ b/router/java/src/net/i2p/router/transport/TransportImpl.java
@@ -554,6 +554,7 @@ public abstract class TransportImpl implements Transport {
      *  with the same IP length (4 or 16) with the given one.
      *  TODO: Allow multiple addresses of the same length.
      *  Calls listener.transportAddressChanged()
+     *  To remove all IPv4 or IPv6 addresses, use removeAddress(boolean).
      *
      *  @param address null to remove all
      */
@@ -566,6 +567,7 @@ public abstract class TransportImpl implements Transport {
             boolean isIPv6 = TransportUtil.isIPv6(address);
             for (RouterAddress ra : _currentAddresses) {
                 if (isIPv6 == TransportUtil.isIPv6(ra))
+                    // COWAL
                     _currentAddresses.remove(ra);
             }
             _currentAddresses.add(address);
@@ -576,6 +578,61 @@ public abstract class TransportImpl implements Transport {
             _listener.transportAddressChanged();
     }
 
+    /**
+     *  Remove only this address.
+     *  Calls listener.transportAddressChanged().
+     *  To remove all IPv4 or IPv6 addresses, use removeAddress(boolean).
+     *  To remove all IPv4 and IPv6 addresses, use replaceAddress(null).
+     *
+     *  @param ipv6 true to remove all IPv6 addresses, false to remove all IPv4 addresses
+     *  @since 0.9.20
+     */
+    protected void removeAddress(RouterAddress address) {
+        if (_log.shouldWarn())
+             _log.warn("Removing address " + address, new Exception());
+        boolean changed = _currentAddresses.remove(address);
+            changed = true;
+        if (changed) {
+            if (_log.shouldWarn())
+                 _log.warn(getStyle() + " now has " + _currentAddresses.size() + " addresses");
+            if (_listener != null)
+                _listener.transportAddressChanged();
+        } else {
+            if (_log.shouldWarn())
+                 _log.warn(getStyle() + " no addresses removed");
+        }
+    }
+
+    /**
+     *  Remove all existing addresses with the specified IP length (4 or 16).
+     *  Calls listener.transportAddressChanged().
+     *  To remove all IPv4 and IPv6 addresses, use replaceAddress(null).
+     *
+     *  @param ipv6 true to remove all IPv6 addresses, false to remove all IPv4 addresses
+     *  @since 0.9.20
+     */
+    protected void removeAddress(boolean ipv6) {
+        if (_log.shouldWarn())
+             _log.warn("Removing addresses, ipv6? " + ipv6, new Exception());
+        boolean changed = false;
+        for (RouterAddress ra : _currentAddresses) {
+            if (ipv6 == TransportUtil.isIPv6(ra)) {
+                // COWAL
+                if (_currentAddresses.remove(ra))
+                    changed = true;
+            }
+        }
+        if (changed) {
+            if (_log.shouldWarn())
+                 _log.warn(getStyle() + " now has " + _currentAddresses.size() + " addresses");
+            if (_listener != null)
+                _listener.transportAddressChanged();
+        } else {
+            if (_log.shouldWarn())
+                 _log.warn(getStyle() + " no addresses removed");
+        }
+    }
+
     /**
      *  Save a local address we were notified about before we started.
      *
@@ -699,6 +756,26 @@ public abstract class TransportImpl implements Transport {
      */
     public void externalAddressReceived(AddressSource source, byte[] ip, int port) {}
 
+    /**
+     *  Notify a transport of an external address change.
+     *  This may be from a local interface, UPnP, a config change, etc.
+     *  This should not be called if the ip didn't change
+     *  (from that source's point of view), or is a local address.
+     *  May be called multiple times for IPv4 or IPv6.
+     *  The transport should also do its own checking on whether to accept
+     *  notifications from this source.
+     *
+     *  This can be called after the transport is running.
+     *
+     *  TODO externalAddressRemoved(source, ip, port)
+     *
+     *  This implementation does nothing. Transports should override if they want notification.
+     *
+     *  @param source defined in Transport.java
+     *  @since 0.9.20
+     */
+    public void externalAddressRemoved(AddressSource source, boolean ipv6) {}
+
     /**
      *  Notify a transport of the results of trying to forward a port.
      *
diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java
index 217b1acbebdd1744a510b1c3226d2e136bf85dd7..eb44660133b224e0deb14b07eb7f2e23143190a0 100644
--- a/router/java/src/net/i2p/router/transport/TransportManager.java
+++ b/router/java/src/net/i2p/router/transport/TransportManager.java
@@ -193,7 +193,8 @@ public class TransportManager implements TransportEventListener {
 
     /**
      * Initialize from interfaces, and callback from UPnP or SSU.
-     * Tell all transports... but don't loop
+     * See CSFI.notifyReplaceAddress().
+     * Tell all transports... but don't loop.
      *
      */
     public void externalAddressReceived(Transport.AddressSource source, byte[] ip, int port) {
@@ -204,6 +205,21 @@ public class TransportManager implements TransportEventListener {
         }
     }
 
+    /**
+     *  Remove all ipv4 or ipv6 addresses.
+     *  See CSFI.notifyRemoveAddress().
+     *  Tell all transports... but don't loop.
+     *
+     *  @since 0.9.20
+     */
+    public void externalAddressRemoved(Transport.AddressSource source, boolean ipv6) {
+        for (Transport t : _transports.values()) {
+            // don't loop
+            if (!(source == SOURCE_SSU && t.getStyle().equals(UDPTransport.STYLE)))
+                t.externalAddressRemoved(source, ipv6);
+        }
+    }
+
     /**
      * callback from UPnP
      *
diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
index 3b681afdb72c0639d8a4a759c6227d906920d9cb..ca6259e456578529bd88d01a526b3196530842e0 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
@@ -602,7 +602,7 @@ public class NTCPTransport extends TransportImpl {
         // try once again to prevent two pumpers which is fatal
         if (_pumper.isAlive())
             return;
-        if (_log.shouldLog(Log.WARN)) _log.warn("Starting ntcp transport listening");
+        if (_log.shouldLog(Log.WARN)) _log.warn("Starting NTCP transport listening");
 
         startIt();
         RouterAddress addr = configureLocalAddress();
@@ -636,16 +636,19 @@ public class NTCPTransport extends TransportImpl {
 
     /**
      *  Only called by externalAddressReceived().
+     *  Calls replaceAddress() or removeAddress().
+     *  To remove all addresses, call replaceAddress(null) directly.
      *
      *  Doesn't actually restart unless addr is non-null and
      *  the port is different from the current listen port.
-     *  If addr is null, removes IPv4 addresses only.
+     *  If addr is null, removes the addresses specified (v4 or v6)
      *
      *  If we had interface addresses before, we lost them.
      *
-     *  @param addr may be null to indicate remove the IPv4 address only
+     *  @param addr may be null to indicate remove the address
+     *  @param ipv6 ignored if addr is non-null
      */
-    private synchronized void restartListening(RouterAddress addr) {
+    private synchronized void restartListening(RouterAddress addr, boolean ipv6) {
         if (addr != null) {
             RouterAddress myAddress = bindAddress(addr.getPort());
             if (myAddress != null)
@@ -654,16 +657,11 @@ public class NTCPTransport extends TransportImpl {
                 replaceAddress(addr);
             // UDPTransport.rebuildExternalAddress() calls router.rebuildRouterInfo()
         } else {
-            // can't do this, want to remove IPv4 only
-            //replaceAddress(null);
-            for (RouterAddress ra : _currentAddresses) {
-                byte[] ip = ra.getIP();
-                if (ip != null && ip.length == 4) {
-                    // COWAL
-                    _currentAddresses.remove(ra);
-                }
-            }
-            _lastInboundIPv4 = 0;
+            removeAddress(ipv6);
+            if (ipv6)
+                _lastInboundIPv6 = 0;
+            else
+                _lastInboundIPv4 = 0;
         }
     }
 
@@ -952,7 +950,7 @@ public class NTCPTransport extends TransportImpl {
     @Override
     public void externalAddressReceived(AddressSource source, byte[] ip, int port) {
         if (_log.shouldLog(Log.WARN))
-            _log.warn("Received address: " + Addresses.toString(ip, port) + " from: " + source);
+            _log.warn("Received address: " + Addresses.toString(ip, port) + " from: " + source, new Exception());
         if ((source == SOURCE_INTERFACE || source == SOURCE_SSU)
              && ip != null && ip.length == 16) {
             // must be set before isValid() call
@@ -978,8 +976,35 @@ public class NTCPTransport extends TransportImpl {
         // ignore UPnP for now, get everything from SSU
         if (source != SOURCE_SSU)
             return;
-        externalAddressReceived(ip, port);
+        boolean isIPv6 = ip != null && ip.length == 16;
+        externalAddressReceived(ip, isIPv6, port);
     }
+
+    /**
+     *  Notify a transport of an external address change.
+     *  This may be from a local interface, UPnP, a config change, etc.
+     *  This should not be called if the ip didn't change
+     *  (from that source's point of view), or is a local address.
+     *  May be called multiple times for IPv4 or IPv6.
+     *  The transport should also do its own checking on whether to accept
+     *  notifications from this source.
+     *
+     *  This can be called after the transport is running.
+     *
+     *  TODO externalAddressRemoved(source, ip, port)
+     *
+     *  @param source defined in Transport.java
+     *  @since 0.9.20
+     */
+    @Override
+    public void externalAddressRemoved(AddressSource source, boolean ipv6) {
+        if (_log.shouldWarn())
+            _log.warn("Removing address, ipv6? " + ipv6 + " from: " + source, new Exception());
+        // ignore UPnP for now, get everything from SSU
+        if (source != SOURCE_SSU)
+            return;
+        externalAddressReceived(null, ipv6, 0);
+    }    
     
     /**
      *  UDP changed addresses, tell NTCP and restart.
@@ -988,10 +1013,9 @@ public class NTCPTransport extends TransportImpl {
      *  @param ip previously validated; may be null to indicate IPv4 failure or port info only
      *  @since IPv6 moved from CSFI.notifyReplaceAddress()
      */
-    private synchronized void externalAddressReceived(byte[] ip, int port) {
+    private synchronized void externalAddressReceived(byte[] ip, boolean isIPv6, int port) {
         // FIXME just take first address for now
         // FIXME if SSU set to hostname, NTCP will be set to IP
-        boolean isIPv6 = ip != null && ip.length == 16;
         RouterAddress oldAddr = getCurrentAddress(isIPv6);
         if (_log.shouldLog(Log.INFO))
             _log.info("Changing NTCP Address? was " + oldAddr);
@@ -1136,13 +1160,11 @@ public class NTCPTransport extends TransportImpl {
         //while (isAlive()) {
         //    try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
         //}
-        restartListening(newAddr);
+        restartListening(newAddr, isIPv6);
         if (_log.shouldLog(Log.WARN))
-            _log.warn("Updating NTCP Address with " + newAddr);
+            _log.warn("Updating NTCP Address (ipv6? " + isIPv6 + ") with " + newAddr);
         return;     	
     }
-    
-
 
     /**
      *  If we didn't used to be forwarded, and we have an address,
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 9e7dbf618029543b930e558fd2faa2269a04355d..41d2cb813770b11b70d2717ff3ec6229d34c77cd 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
@@ -92,6 +92,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
      *  TODO periodically update via CSFI.NetMonitor?
      */
     private boolean _haveIPv6Address;
+    private long _lastInboundIPv6;
     
     /** do we need to rebuild our external router address asap? */
     private boolean _needsRebuild;
@@ -464,6 +465,20 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         // TransportManager.startListening() calls router.rebuildRouterInfo()
         if (newPort > 0 && bindToAddrs.isEmpty()) {
             for (InetAddress ia : getSavedLocalAddresses()) {
+                // Discovered or configured addresses are presumed good at the start.
+                // when externalAddressReceived() was called with SOURCE_INTERFACE,
+                // isAlive() was false, so setReachabilityStatus() was not called
+                // TODO should we set both to unknown and wait for an inbound v6 conn,
+                // since there's no v6 testing?
+                if (ia.getAddress().length == 16) {
+                    // FIXME we need to check and time out after an hour of no inbound ipv6,
+                    // change to firewalled maybe? but we don't have any test to restore
+                    // a v6 address after it's removed.
+                    _lastInboundIPv6 = _context.clock().now();
+                    setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK);
+                } else {
+                    setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
+                }
                 rebuildExternalAddress(ia.getHostAddress(), newPort, false);
             }
         }
@@ -497,6 +512,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         _introManager.reset();
         UDPPacket.clearCache();
         UDPAddress.clearCache();
+        _lastInboundIPv6 = 0;
     }
 
     /**
@@ -664,6 +680,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
     
     void inboundConnectionReceived(boolean isIPv6) {
         if (isIPv6) {
+            _lastInboundIPv6 = _context.clock().now();
             if (_currentOurV6Address != null)
                 setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK);
         } else {
@@ -775,8 +792,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
      * @param ourPort >= 1024
      */
     void externalAddressReceived(Hash from, byte ourIP[], int ourPort) {
-        if (ourIP.length != 4)
-            return;
         boolean isValid = isValid(ourIP) &&
                           TransportUtil.isValidPort(ourPort);
         boolean explicitSpecified = explicitAddressSpecified();
@@ -785,6 +800,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
             _log.info("External address received: " + Addresses.toString(ourIP, ourPort) + " from " 
                       + from + ", isValid? " + isValid + ", explicitSpecified? " + explicitSpecified 
                       + ", receivedInboundRecent? " + inboundRecent + " status " + _reachabilityStatus);
+        if (ourIP.length != 4) {
+            return;
+        }
         
         if (explicitSpecified) 
             return;
@@ -993,7 +1011,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         if (prop != null)
             return Boolean.parseBoolean(prop);
         Status status = getReachabilityStatus();
-        return status != Status.REJECT_UNSOLICITED;
+        return status != Status.REJECT_UNSOLICITED &&
+               status != Status.IPV4_FIREWALLED_IPV6_OK &&
+               status != Status.IPV4_FIREWALLED_IPV6_UNKNOWN;
     }
 
     /** 
@@ -1035,6 +1055,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
      *  @since 0.9.3
      */
     void changePeerPort(PeerState peer, int newPort) {
+        // this happens a lot
         int oldPort;
         synchronized (_addDropLock) {
             oldPort = peer.getRemotePort();
@@ -1044,8 +1065,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                 _peersByRemoteHost.put(peer.getRemoteHostId(), peer);
             }
         }
-        if (_log.shouldLog(Log.WARN) && oldPort != newPort)
-            _log.warn("Changed port from " + oldPort + " to " + newPort + " for " + peer);
+        if (_log.shouldInfo() && oldPort != newPort)
+            _log.info("Changed port from " + oldPort + " to " + newPort + " for " + peer);
     }
 
     /**
@@ -1131,8 +1152,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         if (remotePeer != null) {
             oldPeer = _peersByIdent.put(remotePeer, peer);
             if ( (oldPeer != null) && (oldPeer != peer) ) {
-                if (_log.shouldLog(Log.WARN))
-                    _log.warn("Peer already connected (PBID): old=" + oldPeer + " new=" + peer);
+                // this happens a lot
+                if (_log.shouldInfo())
+                    _log.info("Peer already connected (PBID): old=" + oldPeer + " new=" + peer);
                 // transfer over the old state/inbound message fragments/etc
                 peer.loadFrom(oldPeer);
                 oldEstablishedOn = oldPeer.getKeyEstablishedTime();
@@ -1147,8 +1169,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
             RemoteHostId oldID = oldPeer.getRemoteHostId();
             if (!remoteId.equals(oldID)) {
                 // leak fix, remove old address
-                if (_log.shouldLog(Log.WARN))
-                    _log.warn(remotePeer + " changed address FROM " + oldID + " TO " + remoteId);
+                if (_log.shouldInfo())
+                    _log.info(remotePeer + " changed address FROM " + oldID + " TO " + remoteId);
                 PeerState oldPeer2 = _peersByRemoteHost.remove(oldID);
                 // different ones in the two maps? shouldn't happen
                 if (oldPeer2 != oldPeer && oldPeer2 != null) {
@@ -1532,8 +1554,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         // max of 1000 pps
         int toSleep = Math.max(8, (1000 / burstps));
         int count = 0;
-        if (_log.shouldLog(Log.WARN))
-            _log.warn("Sending destroy to : " + howMany + " peers");
+        if (_log.shouldInfo())
+            _log.info("Sending destroy to : " + howMany + " peers");
         for (PeerState peer : _peersByIdent.values()) {
             sendDestroy(peer);
             // 1000 per second * 48 bytes = 400 KBps
@@ -1858,6 +1880,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
 
     /**
      *  Update our IPv4 or IPv6 address and optionally tell the router to rebuild and republish the router info.
+     *  FIXME no way to remove an IPv6 address
      *
      *  @param host new validated IPv4 or IPv6 or DNS hostname or null
      *  @param port new validated port or 0/-1
@@ -1972,7 +1995,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
 
             if (wantsRebuild) {
                 if (_log.shouldLog(Log.INFO))
-                    _log.info("Address rebuilt: " + addr);
+                    _log.info("Address rebuilt: " + addr, new Exception());
                 replaceAddress(addr);
                 if (allowRebuildRouterInfo)
                     _context.router().rebuildRouterInfo();
@@ -1999,9 +2022,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
             if (hasCurrentAddress()) {
                 // We must remove current address, otherwise the user will see
                 // "firewalled with inbound NTCP enabled" warning in console.
-                // Unfortunately this will remove any IPv6 also,
-                // but we don't have a method to remove just the IPv4 address. FIXME
-                replaceAddress(null);
+                // Remove the IPv4 address only
+                removeAddress(false);
                 if (allowRebuildRouterInfo)
                     _context.router().rebuildRouterInfo();
             }
@@ -2049,6 +2071,30 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         _context.commSystem().notifyReplaceAddress(address);
     }
 
+    /**
+     *  Remove then tell NTCP that we changed.
+     *
+     *  @since 0.9.20
+     */
+    @Override
+    protected void removeAddress(RouterAddress address) {
+        super.removeAddress(address);
+        _context.commSystem().notifyRemoveAddress(address);
+    }
+
+    /**
+     *  Remove then tell NTCP that we changed.
+     *
+     *  @since 0.9.20
+     */
+    @Override
+    protected void removeAddress(boolean ipv6) {
+        super.removeAddress(ipv6);
+        if (ipv6)
+            _lastInboundIPv6 = 0;
+        _context.commSystem().notifyRemoveAddress(ipv6);
+    }
+
     /**
      *  Calls replaceAddress(address), then shuts down the router if
      *  dynamic keys is enabled, which it never is, so all this is unused.
@@ -2858,16 +2904,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
      */
     public Status getReachabilityStatus() { 
         String override = _context.getProperty(PROP_REACHABILITY_STATUS_OVERRIDE);
-        if (override == null)
-            return _reachabilityStatus;
-            
-        if ("ok".equals(override))
-            return Status.OK;
-        else if ("err-reject".equals(override))
-            return Status.REJECT_UNSOLICITED;
-        else if ("err-different".equals(override))
-            return Status.DIFFERENT;
-        
+        if (override != null) {
+            if ("ok".equals(override))
+                return Status.OK;
+            else if ("err-reject".equals(override))
+                return Status.REJECT_UNSOLICITED;
+            else if ("err-different".equals(override))
+                return Status.DIFFERENT;
+        }
         return _reachabilityStatus; 
     }