diff --git a/router/java/src/net/i2p/router/CommSystemFacade.java b/router/java/src/net/i2p/router/CommSystemFacade.java
index 59a22eb9ca92e9e5188e7200b1be78bc652a0b9d..b81f5bfffb5ea5ba15eae754f66ca1ec2c122eef 100644
--- a/router/java/src/net/i2p/router/CommSystemFacade.java
+++ b/router/java/src/net/i2p/router/CommSystemFacade.java
@@ -14,6 +14,8 @@ import java.util.Collections;
 import java.util.List;
 import net.i2p.data.Hash;
 import net.i2p.data.router.RouterAddress;
+import net.i2p.router.transport.Transport;
+import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
 
 /**
  * Manages the communication subsystem between peers, including connections, 
@@ -85,6 +87,24 @@ public abstract class CommSystemFacade implements Service {
      */
     public void notifyReplaceAddress(RouterAddress UDPAddr) {}
 
+    /**
+     *  Pluggable transport
+     *  @since 0.9.16
+     */
+    public void registerTransport(Transport t) {}
+
+    /**
+     *  Pluggable transport
+     *  @since 0.9.16
+     */
+    public void unregisterTransport(Transport t) {}
+
+    /**
+     *  Hook for pluggable transport creation.
+     *  @since 0.9.16
+     */
+    public DHSessionKeyBuilder.Factory getDHFactory() { return null; }
+
     /** 
      * These must be increasing in "badness" (see TransportManager.java),
      * but UNKNOWN must be last.
diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
index b9e38f9da3bdb876676410f50ed73c464cdab197..0c7cd430f1a3e6d6bb32044206afec805137f61a 100644
--- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
+++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
@@ -22,6 +22,7 @@ import net.i2p.data.router.RouterInfo;
 import net.i2p.router.CommSystemFacade;
 import net.i2p.router.OutNetMessage;
 import net.i2p.router.RouterContext;
+import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
 import net.i2p.router.transport.udp.UDPTransport;
 import net.i2p.router.util.EventLog;
 import net.i2p.util.Addresses;
@@ -222,6 +223,47 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
         }
         _manager.externalAddressReceived(Transport.AddressSource.SOURCE_SSU, ip, port);
     }
+
+    /**
+     *  Pluggable transports. Not for NTCP or SSU.
+     *
+     *  Do not call from transport constructor. Transport must be ready to be started.
+     *
+     *  Following transport methods will be called:
+     *    setListener()
+     *    externalAddressReceived() (zero or more times, one for each known address)
+     *    startListening();
+     *
+     *  @since 0.9.16
+     */
+    @Override
+    public void registerTransport(Transport t) {
+        _manager.registerAndStart(t);
+    }
+
+    /**
+     *  Pluggable transports. Not for NTCP or SSU.
+     *
+     *  Following transport methods will be called:
+     *    setListener(null)
+     *    stoptListening();
+     *
+     *  @since 0.9.16
+     */
+    @Override
+    public void unregisterTransport(Transport t) {
+        _manager.stopAndUnregister(t);
+    }
+
+    /**
+     *  Hook for pluggable transport creation.
+     *
+     *  @since 0.9.16
+     */
+    @Override
+    public DHSessionKeyBuilder.Factory getDHFactory() {
+        return _manager.getDHFactory();
+    }
     
     /*
      * GeoIP stuff
diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java
index 75e843707cbffa222bb096f379cdad206e19d636..5062cc14bbd2264e492ea41d25e3e498d7ee63bf 100644
--- a/router/java/src/net/i2p/router/transport/TransportManager.java
+++ b/router/java/src/net/i2p/router/transport/TransportManager.java
@@ -13,6 +13,7 @@ import java.io.Writer;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -44,6 +45,8 @@ public class TransportManager implements TransportEventListener {
      * If we want more than one transport with the same style we will have to change this.
      */
     private final Map<String, Transport> _transports;
+    /** locking: this */
+    private final Map<String, Transport> _pluggableTransports;
     private final RouterContext _context;
     private final UPnPManager _upnpManager;
     private final DHSessionKeyBuilder.PrecalcRunner _dhThread;
@@ -66,22 +69,74 @@ public class TransportManager implements TransportEventListener {
         _context.statManager().createRateStat("transport.bidFailNoTransports", "Could not attempt to bid on message, as none of the transports could attempt it", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
         _context.statManager().createRateStat("transport.bidFailAllTransports", "Could not attempt to bid on message, as all of the transports had failed", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
         _transports = new ConcurrentHashMap<String, Transport>(2);
+        _pluggableTransports = new HashMap<String, Transport>(2);
         if (_context.getBooleanPropertyDefaultTrue(PROP_ENABLE_UPNP))
             _upnpManager = new UPnPManager(context, this);
         else
             _upnpManager = null;
         _dhThread = new DHSessionKeyBuilder.PrecalcRunner(context);
     }
+
+    /**
+     *  Pluggable transports. Not for NTCP or SSU.
+     *
+     *  @since 0.9.16
+     */
+    synchronized void registerAndStart(Transport t) {
+        String style = t.getStyle();
+        if (style.equals(NTCPTransport.STYLE) || style.equals(UDPTransport.STYLE))
+            throw new IllegalArgumentException("Builtin transport");
+        if (_transports.containsKey(style) || _pluggableTransports.containsKey(style))
+            throw new IllegalStateException("Dup transport");
+        boolean shouldStart = !_transports.isEmpty();
+        _pluggableTransports.put(style, t);
+        addTransport(t);
+        t.setListener(this);
+        if (shouldStart) {
+            initializeAddress(t);
+            t.startListening();
+            _context.router().rebuildRouterInfo();
+        } // else will be started by configTransports() (unlikely)
+    }
+
+    /**
+     *  Pluggable transports. Not for NTCP or SSU.
+     *
+     *  @since 0.9.16
+     */
+    synchronized void stopAndUnregister(Transport t) {
+        String style = t.getStyle();
+        if (style.equals(NTCPTransport.STYLE) || style.equals(UDPTransport.STYLE))
+            throw new IllegalArgumentException("Builtin transport");
+        t.setListener(null);
+        _pluggableTransports.remove(style);
+        removeTransport(t);
+        t.stopListening();
+        _context.router().rebuildRouterInfo();
+    }
+
+    /**
+     *  Hook for pluggable transport creation.
+     *
+     *  @since 0.9.16
+     */
+    DHSessionKeyBuilder.Factory getDHFactory() {
+        return _dhThread;
+    }
     
-    public void addTransport(Transport transport) {
+    private void addTransport(Transport transport) {
         if (transport == null) return;
-        _transports.put(transport.getStyle(), transport);
+        Transport old = _transports.put(transport.getStyle(), transport);
+        if (old != null && old != transport && _log.shouldLog(Log.WARN))
+            _log.warn("Replacing transport " + transport.getStyle());
         transport.setListener(this);
     }
     
-    public void removeTransport(Transport transport) {
+    private void removeTransport(Transport transport) {
         if (transport == null) return;
-        _transports.remove(transport.getStyle());
+        Transport old = _transports.remove(transport.getStyle());
+        if (old != null && _log.shouldLog(Log.WARN))
+            _log.warn("Removing transport " + transport.getStyle());
         transport.setListener(null);
     }
 
@@ -174,7 +229,10 @@ public class TransportManager implements TransportEventListener {
         tp = getTransport(UDPTransport.STYLE);
         if (tp != null)
             tps.add(tp);
-        //for (Transport t : _transports.values()) {
+        // now add any others (pluggable)
+        for (Transport t : _pluggableTransports.values()) {
+             tps.add(t);
+        }
         for (Transport t : tps) {
             t.startListening();
             if (_log.shouldLog(Log.DEBUG))