i2ptunnel: Retry accept after router soft restart (ticket #2003)

This sends the router restart indication from I2CP router side
to client side to streaming to I2PTunnelServer via
a new streaming exception.
This commit is contained in:
zzz
2018-02-18 13:53:50 +00:00
parent acebd2ea68
commit 826d8ca07f
10 changed files with 71 additions and 10 deletions

View File

@@ -37,6 +37,7 @@ import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.RouterRestartException;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64;
import net.i2p.data.Hash;
@@ -554,8 +555,8 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
I2PServerSocket ci2pss = i2pss;
if (ci2pss == null)
throw new I2PException("I2PServerSocket closed");
// returns non-null as of 0.9.17
i2ps = ci2pss.accept();
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
if (_usePool) {
try {
_executor.execute(new Handler(i2ps));
@@ -573,10 +574,19 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
// use only for standard servers that can't get slowlorissed! Not for http or irc
blockingHandle(i2ps);
}
} catch (RouterRestartException rre) {
// Delay and loop if router is soft restarting
_log.logAlways(Log.WARN, "Waiting for router restart");
if (i2ps != null) try { i2ps.close(); } catch (IOException ioe) {}
try {
Thread.sleep(2*60*1000);
} catch (InterruptedException ie) {}
// This should be the same as before, but we have to call getServerSocket()
// so sockMgr will call ConnectionManager.setAllowIncomingConnections(true) again
i2pss = sockMgr.getServerSocket();
} catch (I2PException ipe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error accepting - KILLING THE TUNNEL SERVER", ipe);
// TODO delay and loop if internal router is soft restarting?
open = false;
if (i2ps != null) try { i2ps.close(); } catch (IOException ioe) {}
break;

View File

@@ -29,6 +29,7 @@ public interface I2PServerSocket {
*
* @throws I2PException if there is a problem with reading a new socket
* from the data available (e.g. the I2PSession is closed)
* @throws RouterRestartException (extends I2PException) if the router is apparently restarting, since 0.9.34
* @throws ConnectException if the I2PServerSocket is closed, or if interrupted.
* Not actually thrown through 0.9.16; thrown as of 0.9.17
* @throws SocketTimeoutException if a timeout was previously set with setSoTimeout and the timeout has been reached.

View File

@@ -0,0 +1,11 @@
package net.i2p.client.streaming;
import net.i2p.I2PException;
/**
* An I2PException thrown from I2PServerSocket.accept()
* when the router is restarting.
*
* @since 0.9.34
*/
public class RouterRestartException extends I2PException {}

View File

@@ -6,6 +6,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.RouterRestartException;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
@@ -27,6 +28,7 @@ class ConnectionHandler {
private final SimpleTimer2 _timer;
private volatile boolean _active;
private int _acceptTimeout;
private boolean _restartPending;
/** max time after receiveNewSyn() and before the matched accept() */
private static final int DEFAULT_ACCEPT_TIMEOUT = 3*1000;
@@ -48,14 +50,27 @@ class ConnectionHandler {
_acceptTimeout = DEFAULT_ACCEPT_TIMEOUT;
}
/**
* The router told us it's going to restart.
* Call instead of setActive(false).
*
* @since 0.9.34
*/
public synchronized void setRestartPending() {
_restartPending = true;
setActive(false);
}
public synchronized void setActive(boolean active) {
// FIXME active=false this only kills for one thread in accept()
// if there are more, they won't get a poison packet.
if (_log.shouldLog(Log.WARN))
_log.warn("setActive(" + active + ") called, previously " + _active, new Exception("I did it"));
// if starting, clear any old poison
if (active && !_active)
if (active && !_active) {
_restartPending = false;
_synQueue.clear();
}
boolean wasActive = _active;
_active = active;
if (wasActive && !active) {
@@ -116,12 +131,13 @@ class ConnectionHandler {
* than 1ms, wait indefinitely)
* @return connection received. Prior to 0.9.17, or null if there was a timeout or the
* handler was shut down. As of 0.9.17, never null.
* @throws RouterRestartException (extends I2PException) if the router is apparently restarting, since 0.9.34
* @throws ConnectException since 0.9.17, returned null before;
* if the I2PServerSocket is closed, or if interrupted.
* @throws SocketTimeoutException since 0.9.17, returned null before;
* if a timeout was previously set with setSoTimeout and the timeout has been reached.
*/
public Connection accept(long timeoutMs) throws ConnectException, SocketTimeoutException {
public Connection accept(long timeoutMs) throws RouterRestartException, ConnectException, SocketTimeoutException {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Accept("+ timeoutMs+") called");
@@ -137,6 +153,8 @@ class ConnectionHandler {
break;
sendReset(packet);
}
if (_restartPending)
throw new RouterRestartException();
throw new ConnectException("ServerSocket closed");
}
@@ -174,8 +192,11 @@ class ConnectionHandler {
}
if (syn != null) {
if (syn.getOptionalDelay() == PoisonPacket.POISON_MAX_DELAY_REQUEST)
if (syn.getOptionalDelay() == PoisonPacket.POISON_MAX_DELAY_REQUEST) {
if (_restartPending)
throw new RouterRestartException();
throw new ConnectException("ServerSocket closed");
}
// deal with forged / invalid syn packets in _manager.receiveConnection()

View File

@@ -30,6 +30,7 @@ class I2PServerSocketFull implements I2PServerSocket {
*
* @throws I2PException if there is a problem with reading a new socket
* from the data available (e.g. the I2PSession is closed)
* @throws RouterRestartException (extends I2PException) if the router is apparently restarting, since 0.9.34
* @throws ConnectException if the I2PServerSocket is closed, or if interrupted.
* Not actually thrown through 0.9.16; thrown as of 0.9.17
* @throws SocketTimeoutException if a timeout was previously set with setSoTimeout and the timeout has been reached.

View File

@@ -350,6 +350,7 @@ public class I2PSocketManagerFull implements I2PSocketManager {
*
* @return connected I2PSocket, or null through 0.9.16, non-null as of 0.9.17
* @throws I2PException if session is closed
* @throws RouterRestartException (extends I2PException) if the router is apparently restarting, since 0.9.34
* @throws ConnectException (since 0.9.17; I2PServerSocket interface always declared it)
* @throws SocketTimeoutException if a timeout was previously set with setSoTimeout and the timeout has been reached.
*/

View File

@@ -22,6 +22,7 @@ class MessageHandler implements I2PSessionMuxedListener {
private final I2PAppContext _context;
private final Log _log;
private final Set<I2PSocketManager.DisconnectListener> _listeners;
private boolean _restartPending;
public MessageHandler(I2PAppContext ctx, ConnectionManager mgr) {
_manager = mgr;
@@ -104,7 +105,12 @@ class MessageHandler implements I2PSessionMuxedListener {
_log.warn("I2PSession disconnected");
_manager.disconnectAllHard();
// kill anybody waiting in accept()
_manager.getConnectionHandler().setActive(false);
if (_restartPending) {
_manager.getConnectionHandler().setRestartPending();
_restartPending = false;
} else {
_manager.getConnectionHandler().setActive(false);
}
for (I2PSocketManager.DisconnectListener lsnr : _listeners) {
lsnr.sessionDisconnected();
@@ -120,8 +126,9 @@ class MessageHandler implements I2PSessionMuxedListener {
* @param error the actual error
*/
public void errorOccurred(I2PSession session, String message, Throwable error) {
_restartPending = message.contains("restart");
if (_log.shouldLog(Log.WARN))
_log.warn("error occurred: " + message + "- " + error.getMessage(), error);
_log.warn("error occurred: " + message, error);
//_manager.disconnectAllHard();
}

View File

@@ -30,7 +30,7 @@ class DisconnectMessageHandler extends HandlerImpl {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handle message " + message);
String reason = ((DisconnectMessage)message).getReason();
session.propogateError(reason, new I2PSessionException("Disconnect Message received"));
session.propogateError(reason, new I2PSessionException("Disconnect Message received: " + reason));
session.destroySession(false);
if (reason.contains("restart")) {
Thread t = new I2PAppThread(new Reconnector(session), "Reconnect " + session, true);
@@ -47,7 +47,7 @@ class DisconnectMessageHandler extends HandlerImpl {
}
public void run() {
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
try { Thread.sleep(40*1000); } catch (InterruptedException ie) {}
_session.reconnect();
}
}

View File

@@ -1,3 +1,12 @@
2018-02-18 zzz
* i2ptunnel: Retry accept after router soft restart (ticket #2003)
2018-02-17 zzz
* Console: Number formatting tweaks (ticket #1913)
* i2psnark: folder.js cleanup (ticket #2168, PR #14)
* i2ptunnel: Close sockets in finally{}
* SusiMail: Fix mail save truncation
2018-02-16 zzz
* i2psnark: Fix NPE on torrent not found (ticket #2167)
* i2ptunnel: Change POST throttle response to 429

View File

@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 6;
public final static long BUILD = 7;
/** for example "-test" */
public final static String EXTRA = "";