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 41b0eeddd6e9d29e94711e94ae300c56d9de4130..267c4fc2a5b6846f8ac48b67752c72ffb49be768 100644
--- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
@@ -967,6 +967,32 @@ class EstablishmentManager {
         notifyActivity();
     }
 
+    /**
+     *  Called from UDPReceiver.
+     *  Accelerate response to RelayResponse if we haven't sent it yet.
+     *
+     *  @since 0.9.15
+     */
+    void receiveHolePunch(InetAddress from, int fromPort) {
+        RemoteHostId id = new RemoteHostId(from.getAddress(), fromPort);
+        OutboundEstablishState state = _outboundStates.get(id);
+        if (state != null) {
+            boolean sendNow = state.receiveHolePunch();
+            if (sendNow) {
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn("Hole punch from " + state + ", sending SessionRequest now");
+                notifyActivity();
+            } else {
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn("Hole punch from " + state + ", already sent SessionRequest");
+            }
+        } else {
+            // HolePunch received before RelayResponse, and we didn't know the IP/port, or it changed
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("No state found for hole punch from " + from + " port " + fromPort);
+        }
+    }
+
     /**
      *  Are IP and port valid? This is only for checking the relay response.
      *  Reject all IPv6, for now, even if we are configured for it.
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 39adc9b24b36f210e7a7081cb1374cc6dd6b2b46..ba4375dbdb8f8ec34623bd162a7c38df0a7b9d5c 100644
--- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
@@ -99,6 +99,8 @@ class OutboundEstablishState {
     /** max delay including backoff */
     private static final long MAX_DELAY = 15*1000;
 
+    private static final long WAIT_FOR_HOLE_PUNCH_DELAY = 500;
+
     /**
      *  @param claimedAddress an IP/port based RemoteHostId, or null if unknown
      *  @param remoteHostId non-null, == claimedAddress if direct, or a hash-based one if indirect
@@ -556,7 +558,7 @@ class OutboundEstablishState {
     public synchronized void introduced(byte bobIP[], int bobPort) {
         if (_currentState != OutboundState.OB_STATE_PENDING_INTRO)
             return; // we've already successfully been introduced, so don't overwrite old settings
-        _nextSend = _context.clock().now() + 500; // wait briefly for the hole punching
+        _nextSend = _context.clock().now() + WAIT_FOR_HOLE_PUNCH_DELAY; // wait briefly for the hole punching
         _currentState = OutboundState.OB_STATE_INTRODUCED;
         if (_claimedAddress != null && bobPort == _bobPort && DataHelper.eq(bobIP, _bobIP)) {
             // he's who he said he was
@@ -570,6 +572,24 @@ class OutboundEstablishState {
         if (_log.shouldLog(Log.INFO))
             _log.info("Introduced to " + _remoteHostId + ", now lets get on with establishing");
     }
+
+    /**
+     *  Accelerate response to RelayResponse if we haven't sent it yet.
+     *
+     *  @return true if we should send the SessionRequest now
+     *  @since 0.9.15
+     */
+    synchronized boolean receiveHolePunch() {
+        if (_currentState != OutboundState.OB_STATE_INTRODUCED)
+            return false;
+        if (_requestSentCount > 0)
+            return false;
+        long now = _context.clock().now();
+        if (_log.shouldLog(Log.WARN))
+            _log.warn(toString() + " accelerating SessionRequest by " + (_nextSend - now) + " ms");
+        _nextSend = now;
+        return true;
+    }
     
     /** how long have we been trying to establish this session? */
     public long getLifetime() { return _context.clock().now() - _establishBegin; }
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
index be5b97dcf830cd5d06c1da20dfe9627ab4d17ebe..65a7a81cbf5b95c4d87d89cee021a9b74f3d8dc8 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
@@ -1,6 +1,7 @@
 package net.i2p.router.transport.udp;
 
 import java.io.IOException;
+import java.net.DatagramPacket;
 import java.net.DatagramSocket;
 import java.util.Arrays;
 
@@ -220,11 +221,12 @@ class UDPReceiver {
                 //    _socketChanged = false;
                 //}
                 UDPPacket packet = UDPPacket.acquire(_context, true);
+                DatagramPacket dpacket = packet.getPacket();
 
                 // Android ICS bug
                 // http://code.google.com/p/android/issues/detail?id=24748
                 if (_isAndroid)
-                    packet.getPacket().setLength(UDPPacket.MAX_PACKET_SIZE);
+                    dpacket.setLength(UDPPacket.MAX_PACKET_SIZE);
                 
                 // block before we read...
                 //if (_log.shouldLog(Log.DEBUG))
@@ -236,9 +238,9 @@ class UDPReceiver {
                     //if (_log.shouldLog(Log.INFO))
                     //    _log.info("Before blocking socket.receive on " + System.identityHashCode(packet));
                     //synchronized (Runner.this) {
-                        _socket.receive(packet.getPacket());
+                        _socket.receive(dpacket);
                     //}
-                    int size = packet.getPacket().getLength();
+                    int size = dpacket.getLength();
                     if (_log.shouldLog(Log.INFO))
                         _log.info("After blocking socket.receive: packet is " + size + " bytes on " + System.identityHashCode(packet));
                     packet.resetBegin();
@@ -266,7 +268,8 @@ class UDPReceiver {
                         _context.statManager().addRateData("udp.receiveHolePunch", 1);
                         // nat hole punch packets are 0 bytes
                         if (_log.shouldLog(Log.INFO))
-                            _log.info("Received a 0 byte udp packet from " + packet.getPacket().getAddress() + ":" + packet.getPacket().getPort());
+                            _log.info("Received a 0 byte udp packet from " + dpacket.getAddress() + ":" + dpacket.getPort());
+                        _transport.getEstablisher().receiveHolePunch(dpacket.getAddress(), dpacket.getPort());
                         packet.release();
                     }
                 } catch (IOException ioe) {