diff --git a/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java b/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java
index 57baf208698d3b3e67803bee84180089d34e4289..f0d24f43f4c77b90aed04e30e1202936e992809c 100644
--- a/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java
+++ b/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java
@@ -172,6 +172,7 @@ public class PostBean {
     private static final int MAX_SIZE = 256*1024;
     
     private void cacheAttachments() throws IOException {
+        if (_user == null) throw new IOException("User not specified");
         File postCacheDir = new File(BlogManager.instance().getTempDir(), _user.getBlog().toBase64());
         if (!postCacheDir.exists())
             postCacheDir.mkdirs();
diff --git a/apps/syndie/java/src/net/i2p/syndie/web/PostServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/PostServlet.java
index a7cc3d7e858f59417efefbaf655f8e74c9332a32..b063b4d904de0b533e3532c88895f726086da9af 100644
--- a/apps/syndie/java/src/net/i2p/syndie/web/PostServlet.java
+++ b/apps/syndie/java/src/net/i2p/syndie/web/PostServlet.java
@@ -49,6 +49,8 @@ public class PostServlet extends BaseServlet {
             String action = req.getParameter(PARAM_ACTION);
             if (!empty(action) && ACTION_CONFIRM.equals(action)) {
                 postEntry(user, req, archive, post, out);
+                post.reinitialize();
+                post.setUser(user);
             } else {
                 String contentType = req.getContentType();
                 if (!empty(contentType) && (contentType.indexOf("boundary=") != -1)) {
@@ -73,8 +75,8 @@ public class PostServlet extends BaseServlet {
         
         out.write("<tr><td colspan=\"3\">");
         
-        post.reinitialize();
-        post.setUser(user);
+        //post.reinitialize();
+        //post.setUser(user);
         
         boolean inNewThread = getInNewThread(req.getString(PARAM_IN_NEW_THREAD));
         boolean refuseReplies = getRefuseReplies(req.getString(PARAM_REFUSE_REPLIES));
@@ -343,6 +345,7 @@ public class PostServlet extends BaseServlet {
             bean = new PostBean();
             req.getSession().setAttribute(ATTR_POST_BEAN, bean);
         }
+        bean.setUser(user);
         return bean;
     }
     
diff --git a/history.txt b/history.txt
index 31316feb48d5f2a578820ddc25866b74d4534ae5..e5b95b6c15cbdd968d6424b9c295aa10650b109c 100644
--- a/history.txt
+++ b/history.txt
@@ -1,4 +1,16 @@
-$Id: history.txt,v 1.337 2005/11/29 11:58:01 jrandom Exp $
+$Id: history.txt,v 1.338 2005/11/29 12:56:03 jrandom Exp $
+
+2005-11-30  jrandom
+    * Don't let the TCP transport alone shitlist a peer, since other
+      transports may be working.  Also display whether TCP connections are
+      inbound or outbound on the peers page.
+    * Fixed some substantial bugs in the SSU introducers where we wouldn't
+      talk to anyone who didn't expose an IP (even if they had introducers),
+      among other goofy things.
+    * When dealing with SSU introducers, send them all a packet at 3s/6s/9s,
+      rather than sending one a packet at 3s, then another a packet at 6s,
+      and a third a packet at 9s.
+    * Fixed Syndie attachments (oops)
 
 2005-11-29  zzz
     * Added a link to orion's jump page on the 'key not found' error page.
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index fb921acea0a261116421696061581dbbebdedc87..9dd14eaf961e065fb76f43bd9605ae6085f5132c 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -15,9 +15,9 @@ import net.i2p.CoreVersion;
  *
  */
 public class RouterVersion {
-    public final static String ID = "$Revision: 1.304 $ $Date: 2005/11/29 11:58:02 $";
+    public final static String ID = "$Revision: 1.305 $ $Date: 2005/11/29 12:56:02 $";
     public final static String VERSION = "0.6.1.6";
-    public final static long BUILD = 4;
+    public final static long BUILD = 5;
     public static void main(String args[]) {
         System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
         System.out.println("Router ID: " + RouterVersion.ID);
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java
index 15ecfebcb22f0d1bf67b71ebbf20632ac77bfda1..920e6567a01a86ce82ed17d4bd13b096c04debdc 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java
@@ -68,8 +68,8 @@ class FloodfillPeerSelector extends PeerSelector {
         }
         public List getFloodfillParticipants() { return _floodfillMatches; }
         public void add(Hash entry) {
-            if (_context.profileOrganizer().isFailing(entry))
-                return;
+            //if (_context.profileOrganizer().isFailing(entry))
+            //    return;
             if ( (_toIgnore != null) && (_toIgnore.contains(entry)) )
                 return;
             if (entry.equals(_context.routerHash()))
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
index 484b46c76d8d61ee21ebe7144da354c038b4a18c..8501383c33d44609dd1b6810a4afda14ce443bb2 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
@@ -657,8 +657,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
             }
         } else if (routerInfo.getPublished() > now + Router.CLOCK_FUDGE_FACTOR) {
             long age = routerInfo.getPublished() - _context.clock().now();
-            if (_log.shouldLog(Log.WARN))
-                _log.warn("Peer " + key.toBase64() + " published their routerInfo in the future?! [" 
+            if (_log.shouldLog(Log.INFO))
+                _log.info("Peer " + key.toBase64() + " published their routerInfo in the future?! [" 
                           + new Date(routerInfo.getPublished()) + "]", new Exception("Rejecting store"));
             return "Peer " + key.toBase64() + " published " + DataHelper.formatDuration(age) + " in the future?!";
         } else if (_enforceNetId && (routerInfo.getNetworkId() != Router.NETWORK_ID) ){
diff --git a/router/java/src/net/i2p/router/transport/tcp/ConnectionHandler.java b/router/java/src/net/i2p/router/transport/tcp/ConnectionHandler.java
index b037c4242c7822f4f10a811419651c7dcace2ea4..a9ba87576f2c94a6dfd9ed117868a1e00a13f3fe 100644
--- a/router/java/src/net/i2p/router/transport/tcp/ConnectionHandler.java
+++ b/router/java/src/net/i2p/router/transport/tcp/ConnectionHandler.java
@@ -169,6 +169,7 @@ public class ConnectionHandler {
             if (_error == null) {
                 if (_log.shouldLog(Log.INFO))
                     _log.info("Establishment successful!  returning the con");
+                con.setIsOutbound(false);
                 return con;
             } else {
                 if (_log.shouldLog(Log.INFO))
diff --git a/router/java/src/net/i2p/router/transport/tcp/TCPConnection.java b/router/java/src/net/i2p/router/transport/tcp/TCPConnection.java
index 3da0b01f1846a44d5fbd49fa351a9231e458a774..cedf1344996fef1eddece7d28ffb2c2a704cf93c 100644
--- a/router/java/src/net/i2p/router/transport/tcp/TCPConnection.java
+++ b/router/java/src/net/i2p/router/transport/tcp/TCPConnection.java
@@ -43,6 +43,7 @@ public class TCPConnection {
     private long _lastRead;
     private long _lastWrite;
     private long _offsetReceived;
+    private boolean _isOutbound;
     
     public TCPConnection(RouterContext ctx) {
         _context = ctx;
@@ -60,6 +61,7 @@ public class TCPConnection {
         _lastRead = 0;
         _lastWrite = 0;
         _offsetReceived = 0;
+        _isOutbound = false;
         _runner = new ConnectionRunner(_context, this);
         _context.statManager().createRateStat("tcp.probabalisticDropQueueSize", "How many bytes were queued to be sent when a message as dropped probabalistically?", "TCP", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l } );
         _context.statManager().createRateStat("tcp.queueSize", "How many bytes were queued on a connection?", "TCP", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l } );
@@ -86,6 +88,8 @@ public class TCPConnection {
     public long getOffsetReceived() { return _offsetReceived; }
     public void setOffsetReceived(long ms) { _offsetReceived = ms; }
     public TCPTransport getTransport() { return _transport; }
+    public boolean getIsOutbound() { return _isOutbound; }
+    public void setIsOutbound(boolean outbound) { _isOutbound = outbound; }
     
     /** 
      * Actually start processing the messages on the connection (and reading
diff --git a/router/java/src/net/i2p/router/transport/tcp/TCPConnectionEstablisher.java b/router/java/src/net/i2p/router/transport/tcp/TCPConnectionEstablisher.java
index 84b78f6a023c7aa92ea20b9e03f35b59acb70fb5..ae2991495568a1f8f91c27161caa5268309edbf4 100644
--- a/router/java/src/net/i2p/router/transport/tcp/TCPConnectionEstablisher.java
+++ b/router/java/src/net/i2p/router/transport/tcp/TCPConnectionEstablisher.java
@@ -47,14 +47,17 @@ public class TCPConnectionEstablisher implements Runnable {
                                + info.getIdentity().getHash().toBase64(), e);
         }
         if (con != null) {
+            con.setIsOutbound(true);
             _transport.connectionEstablished(con);
         } else {
             if (!_context.router().isAlive()) return;
             _transport.addConnectionErrorMessage(cb.getError());
             Hash peer = info.getIdentity().getHash();
             _context.profileManager().commErrorOccurred(peer);
-            _context.shitlist().shitlistRouter(peer, "Unable to contact");
-            _context.netDb().fail(peer);
+            // disabling in preparation for dropping tcp, since other transports may work, and
+            // hence shitlisting is not appropriate
+            //_context.shitlist().shitlistRouter(peer, "Unable to contact");
+            //_context.netDb().fail(peer);
         }
 
         // this removes the _pending block on the address and 
diff --git a/router/java/src/net/i2p/router/transport/tcp/TCPTransport.java b/router/java/src/net/i2p/router/transport/tcp/TCPTransport.java
index 42f2d173bc48aa6b8db36f29300b1941b2218449..7bf39523f45cc8c590f2d34eb018c43e3cf5928b 100644
--- a/router/java/src/net/i2p/router/transport/tcp/TCPTransport.java
+++ b/router/java/src/net/i2p/router/transport/tcp/TCPTransport.java
@@ -783,12 +783,21 @@ public class TCPTransport extends TransportImpl {
     /** Make this stuff pretty (only used in the old console) */
     public void renderStatusHTML(Writer out) throws IOException {
         StringBuffer buf = new StringBuffer(1024);
+        int outbound = 0;
+        int inbound = 0;
         synchronized (_connectionLock) {
             long offsetTotal = 0;
             buf.append("<b>Connections (").append(_connectionsByIdent.size()).append("):</b><ul>\n");
             for (Iterator iter = _connectionsByIdent.values().iterator(); iter.hasNext(); ) {
                 TCPConnection con = (TCPConnection)iter.next();
                 buf.append("<li>");
+                if (con.getIsOutbound()) {
+                    outbound++;
+                    buf.append("Outbound to ");
+                } else {
+                    inbound++;
+                    buf.append("Inbound from ");
+                }
                 buf.append(con.getRemoteRouterIdentity().getHash().toBase64().substring(0,6));
                 buf.append(": up for ").append(DataHelper.formatDuration(con.getLifetime()));
                 buf.append(" transferring at ");
@@ -817,6 +826,7 @@ public class TCPTransport extends TransportImpl {
                 buf.append("</li>\n");
             }
             buf.append("</ul>\n");
+            buf.append("<b>Inbound: ").append(inbound).append(", Outbound: ").append(outbound).append("</b><br />\n");
         }
         
         buf.append("<b>Most recent connection errors:</b><ul>");
diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
index 567b0d35be26ff0ea7baf1fe3e69f8adf3163f8e..1f75052443fc69c89c44cd16fd4a6bac723512f8 100644
--- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
@@ -126,19 +126,26 @@ public class EstablishmentManager {
             return;
         }
         UDPAddress addr = new UDPAddress(ra);
+        RemoteHostId to = null;
         InetAddress remAddr = addr.getHostAddress();
         int port = addr.getPort();
-        RemoteHostId to = new RemoteHostId(remAddr.getAddress(), port);
-        
-        if (!_transport.isValid(to.getIP())) {
-            _transport.failed(msg);
-            _context.shitlist().shitlistRouter(msg.getTarget().getIdentity().calculateHash(), "Invalid SSU address");
-            return;
+        if ( (remAddr != null) && (port > 0) ) {
+            to = new RemoteHostId(remAddr.getAddress(), port);
+
+            if (!_transport.isValid(to.getIP())) {
+                _transport.failed(msg);
+                _context.shitlist().shitlistRouter(msg.getTarget().getIdentity().calculateHash(), "Invalid SSU address");
+                return;
+            }
+            
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Add outbound establish state to: " + to);
+        } else {
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Add indirect outbound establish state to: " + addr);
+            to = new RemoteHostId(msg.getTarget().getIdentity().calculateHash().getData());
         }
         
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Add outobund establish state to: " + to);
-        
         OutboundEstablishState state = null;
         int deferred = 0;
         synchronized (_outboundStates) {
@@ -465,12 +472,18 @@ public class EstablishmentManager {
         long now = _context.clock().now();
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Send request to: " + state.getRemoteHostId().toString());
-        _transport.send(_builder.buildSessionRequestPacket(state));
+        UDPPacket packet = _builder.buildSessionRequestPacket(state);
+        if (packet != null) {
+            _transport.send(packet);
+        } else {
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Unable to build a session request packet for " + state.getRemoteHostId());
+        }
         state.requestSent();
     }
     
     private static final long MAX_NONCE = 0xFFFFFFFFl;
-    /** if we don't get a relayResponse in 3 seconds, try again with another intro peer */
+    /** if we don't get a relayResponse in 3 seconds, try again */
     private static final int INTRO_ATTEMPT_TIMEOUT = 3*1000;
     
     private void handlePendingIntro(OutboundEstablishState state) {
@@ -488,7 +501,11 @@ public class EstablishmentManager {
         SimpleTimer.getInstance().addEvent(new FailIntroduction(state, nonce), INTRO_ATTEMPT_TIMEOUT);
         state.setIntroNonce(nonce);
         _context.statManager().addRateData("udp.sendIntroRelayRequest", 1, 0);
-        _transport.send(_builder.buildRelayRequest(_transport, state, _transport.getIntroKey()));
+        UDPPacket requests[] = _builder.buildRelayRequest(_transport, state, _transport.getIntroKey());
+        for (int i = 0; i < requests.length; i++) {
+            if (requests[i] != null)
+                _transport.send(requests[i]);
+        }
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Send intro for " + state.getRemoteHostId().toString() + " with our intro key as " + _transport.getIntroKey().toBase64());
         state.introSent();
@@ -542,7 +559,15 @@ public class EstablishmentManager {
         }
         _context.statManager().addRateData("udp.receiveIntroRelayResponse", state.getLifetime(), 0);
         int port = reader.getRelayResponseReader().readCharliePort();
+        if (_log.shouldLog(Log.INFO))
+            _log.info("Received relay intro for " + state.getRemoteIdentity().calculateHash().toBase64() + " - they are on " 
+                      + addr.toString() + ":" + port + " (according to " + bob.toString(true) + ")");
+        RemoteHostId oldId = state.getRemoteHostId();
         state.introduced(addr, ip, port);
+        synchronized (_outboundStates) {
+            _outboundStates.remove(oldId);
+            _outboundStates.put(state.getRemoteHostId(), state);
+        }
         notifyActivity();
     }
     
diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
index b160b4ceb415d1767ba73345ce5f6b0182a7314b..23f311c5068e6825839b8e73e3d6e774dab711db 100644
--- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
@@ -77,9 +77,15 @@ public class OutboundEstablishState {
                                   RouterIdentity remotePeer, SessionKey introKey, UDPAddress addr) {
         _context = ctx;
         _log = ctx.logManager().getLog(OutboundEstablishState.class);
-        _bobIP = (remoteHost != null ? remoteHost.getAddress() : null);
-        _bobPort = remotePort;
-        _remoteHostId = new RemoteHostId(_bobIP, _bobPort);
+        if ( (remoteHost != null) && (remotePort > 0) ) {
+            _bobIP = remoteHost.getAddress();
+            _bobPort = remotePort;
+            _remoteHostId = new RemoteHostId(_bobIP, _bobPort);
+        } else {
+            _bobIP = null;
+            _bobPort = -1;
+            _remoteHostId = new RemoteHostId(remotePeer.calculateHash().getData());
+        }
         _remotePeer = remotePeer;
         _introKey = introKey;
         _keyBuilder = null;
@@ -387,6 +393,8 @@ public class OutboundEstablishState {
         _bobIP = bobIP;
         _bobPort = bobPort;
         _remoteHostId = new RemoteHostId(bobIP, bobPort);
+        if (_log.shouldLog(Log.INFO))
+            _log.info("Introduced to " + _remoteHostId + ", now lets get on with establishing");
     }
     
     /** how long have we been trying to establish this session? */
diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
index 19608a2ecb5b8f5b47bbf30c995aa7f3b465b790..a56a540ac92f978e1fee7d682a16ea507dee6dae 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
@@ -746,28 +746,28 @@ public class PacketBuilder {
     private byte[] getOurExplicitIP() { return null; }
     private int getOurExplicitPort() { return 0; }
     
-    public UDPPacket buildRelayRequest(UDPTransport transport, OutboundEstablishState state, SessionKey ourIntroKey) {
+    /** build intro packets for each of the published introducers */
+    public UDPPacket[] buildRelayRequest(UDPTransport transport, OutboundEstablishState state, SessionKey ourIntroKey) {
         UDPAddress addr = state.getRemoteAddress();
         int count = addr.getIntroducerCount();
         if (count <= 0)
-            return null;
-        int index = _context.random().nextInt(count);
+            return new UDPPacket[0];
+        UDPPacket rv[] = new UDPPacket[count];
         for (int i = 0; i < count; i++) {
-            int cur = (i + index) % count;
-            InetAddress iaddr = addr.getIntroducerHost(cur);
-            int iport = addr.getIntroducerPort(cur);
-            byte ikey[] = addr.getIntroducerKey(cur);
-            long tag = addr.getIntroducerTag(cur);
+            InetAddress iaddr = addr.getIntroducerHost(i);
+            int iport = addr.getIntroducerPort(i);
+            byte ikey[] = addr.getIntroducerKey(i);
+            long tag = addr.getIntroducerTag(i);
             if ( (ikey == null) || (iport <= 0) || (iaddr == null) || (tag <= 0) ) {
                 if (_log.shouldLog(_log.WARN))
                     _log.warn("Cannot build a relay request to " + state.getRemoteIdentity().calculateHash().toBase64() 
-                               + ", as their UDP address is invalid: addr=" + addr + " index=" + cur);
+                               + ", as their UDP address is invalid: addr=" + addr + " index=" + i);
                 continue;
             }
             if (transport.isValid(iaddr.getAddress()))
-                return buildRelayRequest(iaddr, iport, ikey, tag, ourIntroKey, state.getIntroNonce(), true);
+                rv[i] = buildRelayRequest(iaddr, iport, ikey, tag, ourIntroKey, state.getIntroNonce(), true);
         }
-        return null;
+        return rv;
     }
     
     public UDPPacket buildRelayRequest(InetAddress introHost, int introPort, byte introKey[], long introTag, SessionKey ourIntroKey, long introNonce, boolean encrypt) {
diff --git a/router/java/src/net/i2p/router/transport/udp/RemoteHostId.java b/router/java/src/net/i2p/router/transport/udp/RemoteHostId.java
index 3f0e8cc2d4d5102169408a179bcc11607d4a426a..117f8c5636003e52ca21d231f526f2733ce39a62 100644
--- a/router/java/src/net/i2p/router/transport/udp/RemoteHostId.java
+++ b/router/java/src/net/i2p/router/transport/udp/RemoteHostId.java
@@ -1,28 +1,37 @@
 package net.i2p.router.transport.udp;
 
+import net.i2p.data.Base64;
 import net.i2p.data.DataHelper;
 
 /**
  * Unique ID for a peer - its IP + port, all bundled into a tidy obj.
- * Aint it cute?
+ * If the remote peer is not reachabe through an IP+port, this contains
+ * the hash of their identity.
  *
  */
 final class RemoteHostId {
     private byte _ip[];
     private int _port;
+    private byte _peerHash[];
     
     public RemoteHostId(byte ip[], int port) {
         _ip = ip;
         _port = port;
     }
+    public RemoteHostId(byte peerHash[]) {
+        _peerHash = peerHash;
+    }
     
     public byte[] getIP() { return _ip; }
     public int getPort() { return _port; }
+    public byte[] getPeerHash() { return _peerHash; }
     
     public int hashCode() {
         int rv = 0;
-        for (int i = 0; i < _ip.length; i++)
+        for (int i = 0; _ip != null && i < _ip.length; i++)
             rv += _ip[i] << i;
+        for (int i = 0; _peerHash != null && i < _peerHash.length; i++)
+            rv += _peerHash[i] << i;
         rv += _port;
         return rv;
     }
@@ -33,15 +42,19 @@ final class RemoteHostId {
         if (!(obj instanceof RemoteHostId)) 
             throw new ClassCastException("obj is a " + obj.getClass().getName());
         RemoteHostId id = (RemoteHostId)obj;
-        return (_port == id.getPort()) && DataHelper.eq(_ip, id.getIP());
+        return (_port == id.getPort()) && DataHelper.eq(_ip, id.getIP()) && DataHelper.eq(_peerHash, id.getPeerHash());
     }
     
     public String toString() { return toString(true); }
     public String toString(boolean includePort) {
-        if (includePort)
-            return toString(_ip) + ':' + _port;
-        else
-            return toString(_ip);
+        if (_ip != null) {
+            if (includePort)
+                return toString(_ip) + ':' + _port;
+            else
+                return toString(_ip);
+        } else {
+            return Base64.encode(_peerHash);
+        }
     }
     public static String toString(byte ip[]) {
         StringBuffer buf = new StringBuffer(ip.length+5);
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 95cf752e8d9884ae0d25d3e1e8e6f111234b57c3..949f17c2e643cd2bde7264dbf5bfa59828351445 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
@@ -97,6 +97,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
 
     /** do we require introducers, regardless of our status? */
     public static final String PROP_FORCE_INTRODUCERS = "i2np.udp.forceIntroducers";
+    /** do we allow direct SSU connections, sans introducers?  */
+    public static final String PROP_ALLOW_DIRECT = "i2np.udp.allowDirect";
         
     /** how many relays offered to us will we use at a time? */
     public static final int PUBLIC_RELAY_COUNT = 3;
@@ -797,8 +799,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                     _log.info("Picked peers: " + found);
                 _introducersSelectedOn = _context.clock().now();
             }
-        }
-        if ( (_externalListenPort > 0) && (_externalListenHost != null) && (isValid(_externalListenHost.getAddress())) ) {
+        } 
+        if ( allowDirectUDP() && (_externalListenPort > 0) && (_externalListenHost != null) && (isValid(_externalListenHost.getAddress())) ) {
             options.setProperty(UDPAddress.PROP_PORT, String.valueOf(_externalListenPort));
             options.setProperty(UDPAddress.PROP_HOST, _externalListenHost.getHostAddress());
             // if we have explicit external addresses, they had better be reachable
@@ -866,6 +868,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         }
     }
     
+    private boolean allowDirectUDP() {
+        String allowDirect = _context.getProperty(PROP_ALLOW_DIRECT);
+        return ( (allowDirect == null) || (Boolean.valueOf(allowDirect).booleanValue()) );
+    }
+
     String getPacketHandlerStatus() {
         PacketHandler handler = _handler;
         if (handler != null)