diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
index 9f08e7156..7df83d0ab 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
@@ -190,8 +190,8 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
} catch (IOException ex) {
if (!finished)
_log.error("Error forwarding", ex);
- else
- _log.warn("You may ignore this", ex);
+ //else
+ // _log.warn("You may ignore this", ex);
} finally {
try {
out.close();
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
index 986a195f1..a496121a7 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
@@ -37,6 +37,9 @@ public class ConfigServiceHandler extends FormHandler {
} else if ("Cancel graceful shutdown".equals(_action)) {
_context.router().cancelGracefulShutdown();
addFormNotice("Graceful shutdown cancelled");
+ } else if ("Graceful restart".equals(_action)) {
+ _context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
+ addFormNotice("Graceful restart requested");
} else if ("Hard restart".equals(_action)) {
_context.router().shutdown(Router.EXIT_HARD_RESTART);
addFormNotice("Hard restart requested");
diff --git a/apps/routerconsole/jsp/configservice.jsp b/apps/routerconsole/jsp/configservice.jsp
index 148e19170..1a705caca 100644
--- a/apps/routerconsole/jsp/configservice.jsp
+++ b/apps/routerconsole/jsp/configservice.jsp
@@ -25,36 +25,47 @@
System.setProperty("net.i2p.router.web.ConfigServiceHandler.nonce", new java.util.Random().nextLong()+""); %>
" />
Shutdown the router
- Graceful shutdown lets the router satisfy the agreements it has already made
+
Graceful shutdown lets the router satisfy the agreements it has already made
before shutting down, but may take a few minutes. If you need to kill the
- router immediately, that option is available as well.
+ router immediately, that option is available as well.
+
+
+
If you want the router to restart itself after shutting down, you can choose one of
+ the following. This is useful in some situations - for example, if you changed
+ some settings that client applications only read at startup, such as the routerconsole password
+ or the interface it listens on. A graceful restart will take a few minutes (but your peers
+ will appreciate your patience), while a hard restart does so immediately. After tearing down
+ the router, it will wait 1 minute before starting back up again.
- On the windows platform, there is a small application to sit in the system
+
On the windows platform, there is a small application to sit in the system
tray, allowing you to view the router's status (later on, I2P client applications
will be able to integrate their own functionality into the system tray as well).
- If you are on windows, you can either enable or disable that icon here.
+ If you are on windows, you can either enable or disable that icon here.
Run on startup
- You can control whether I2P is run on startup or not by selecting one of the
+
You can control whether I2P is run on startup or not by selecting one of the
following options - I2P will install (or remove) a service accordingly. You can
also run the install_i2p_service_winnt.bat (or
- uninstall_i2p_service_winnt.bat) from the command line, if you prefer.
+ uninstall_i2p_service_winnt.bat) from the command line, if you prefer.
- Note: If you are running I2P as service right now, removing it will shut
+
Note: If you are running I2P as service right now, removing it will shut
down your router immediately. You may want to consider shutting down gracefully, as
- above, then running uninstall_i2p_service_winnt.bat.
+ above, then running uninstall_i2p_service_winnt.bat.
<% } %>
Debugging
- At times, it may be helpful to debug I2P by getting a thread dump. To do so,
+
At times, it may be helpful to debug I2P by getting a thread dump. To do so,
please select the following option and review the thread dumped to
-wrapper.log.
+ wrapper.log.
diff --git a/core/java/src/net/i2p/data/RouterInfo.java b/core/java/src/net/i2p/data/RouterInfo.java
index 2f40587e9..ade83c33d 100644
--- a/core/java/src/net/i2p/data/RouterInfo.java
+++ b/core/java/src/net/i2p/data/RouterInfo.java
@@ -221,6 +221,7 @@ public class RouterInfo extends DataStructureImpl {
if (bytes == null) throw new DataFormatException("Not enough data to sign");
// now sign with the key
Signature sig = DSAEngine.getInstance().sign(bytes, key);
+ if (sig == null) throw new DataFormatException("Not enough data to sign, or other signature failure");
setSignature(sig);
//_log.debug("Signed " + SHA256Generator.getInstance().calculateHash(bytes).toBase64() + " with " + key);
//_log.debug("verify ok? " + DSAEngine.getInstance().verifySignature(sig, bytes, getIdentity().getSigningPublicKey()));
diff --git a/history.txt b/history.txt
index eac1e0bc0..8fb8592de 100644
--- a/history.txt
+++ b/history.txt
@@ -1,4 +1,16 @@
-$Id: history.txt,v 1.6 2004/09/04 00:41:42 jrandom Exp $
+$Id: history.txt,v 1.7 2004/09/04 16:54:09 jrandom Exp $
+
+2004-09-06 jrandom
+ * Address a race condition in the key management code that would manifest
+ itself as a corrupt router identity.
+ * Properly clear old transport addresses from being displayed on the old
+ console after soft restarts.
+ * Properly refuse to load the client applications more than once in the
+ same JVM.
+ * Added support for a graceful restart (a graceful shutdown followed by a
+ full JVM restart - useful for restarting client apps).
+ * More defensive programming, HTML cleanup, logging
+ * wrapper.config cleanup of duplicate lines
2004-09-04 jrandom
* Added some basic guards to prevent multiple instances from running.
diff --git a/installer/resources/wrapper.conf b/installer/resources/wrapper.conf
index 0be599987..332e4a79a 100644
--- a/installer/resources/wrapper.conf
+++ b/installer/resources/wrapper.conf
@@ -96,10 +96,7 @@ wrapper.on_exit.4=RESTART
# the router may take a few seconds to save state, etc
wrapper.jvm_exit.timeout=60
-# the router may take a few seconds to save state, etc
-wrapper.jvm_exit.timeout=30
-
-# give the OS 30s to clear all the old sockets / etc before restarting
+# give the OS 60s to clear all the old sockets / etc before restarting
wrapper.restart.delay=60
# use the wrapper's internal timer thread. otherwise this would
diff --git a/router/java/src/net/i2p/router/KeyManager.java b/router/java/src/net/i2p/router/KeyManager.java
index a6008393c..0d2dbb6ce 100644
--- a/router/java/src/net/i2p/router/KeyManager.java
+++ b/router/java/src/net/i2p/router/KeyManager.java
@@ -24,6 +24,7 @@ import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
+import net.i2p.util.Clock;
import net.i2p.util.Log;
/**
@@ -38,8 +39,7 @@ public class KeyManager {
private SigningPrivateKey _signingPrivateKey;
private SigningPublicKey _signingPublicKey;
private Map _leaseSetKeys; // Destination --> LeaseSetKeys
- private boolean _alreadyReadFromDisk;
- private boolean _pendingWrite;
+ private SynchronizeKeysJob _synchronizeJob;
public final static String PROP_KEYDIR = "router.keyBackupDir";
public final static String DEFAULT_KEYDIR = "keyBackup";
@@ -47,43 +47,49 @@ public class KeyManager {
private final static String KEYFILE_PUBLIC_ENC = "publicEncryption.key";
private final static String KEYFILE_PRIVATE_SIGNING = "privateSigning.key";
private final static String KEYFILE_PUBLIC_SIGNING = "publicSigning.key";
- private final static long DELAY = 30*1000;
+ private final static long DELAY = 5*60*1000;
public KeyManager(RouterContext context) {
_context = context;
_log = _context.logManager().getLog(KeyManager.class);
+ _synchronizeJob = new SynchronizeKeysJob();
setPrivateKey(null);
setPublicKey(null);
setSigningPrivateKey(null);
setSigningPublicKey(null);
_leaseSetKeys = new HashMap();
- _alreadyReadFromDisk = false;
- _pendingWrite = false;
- _context.jobQueue().addJob(new SynchronizeKeysJob());
+ }
+
+ public void startup() {
+ queueWrite();
}
/** Configure the router's private key */
public void setPrivateKey(PrivateKey key) {
_privateKey = key;
- _pendingWrite = true;
+ if (key != null)
+ queueWrite();
}
public PrivateKey getPrivateKey() { return _privateKey; }
/** Configure the router's public key */
public void setPublicKey(PublicKey key) {
_publicKey = key;
- _pendingWrite = true;
+ if (key != null)
+ queueWrite();
}
public PublicKey getPublicKey() { return _publicKey; }
/** Configure the router's signing private key */
public void setSigningPrivateKey(SigningPrivateKey key) {
_signingPrivateKey = key;
- _pendingWrite = true;
+ if (key != null)
+ queueWrite();
}
public SigningPrivateKey getSigningPrivateKey() { return _signingPrivateKey; }
/** Configure the router's signing public key */
public void setSigningPublicKey(SigningPublicKey key) {
_signingPublicKey = key;
- _pendingWrite = true;
+ if (key != null)
+ queueWrite();
}
public SigningPublicKey getSigningPublicKey() { return _signingPublicKey; }
@@ -93,24 +99,27 @@ public class KeyManager {
synchronized (_leaseSetKeys) {
_leaseSetKeys.put(dest, keys);
}
- _pendingWrite = true;
+ if (dest != null)
+ queueWrite();
}
- /**
- * True if we've never read the data from disk or if we've
- * updated data in memory.
- */
- private boolean needsSync() {
- return !(_alreadyReadFromDisk && !_pendingWrite);
+ private void queueWrite() {
+ Clock cl = _context.clock();
+ JobQueue q = _context.jobQueue();
+ if ( (cl == null) || (q == null) ) return;
+ _synchronizeJob.getTiming().setStartAfter(cl.now());
+ q.addJob(_synchronizeJob);
}
-
+
public LeaseSetKeys unregisterKeys(Destination dest) {
- _log.info("Unregistering keys for destination " + dest.calculateHash().toBase64());
+ if (_log.shouldLog(Log.INFO))
+ _log.info("Unregistering keys for destination " + dest.calculateHash().toBase64());
LeaseSetKeys rv = null;
synchronized (_leaseSetKeys) {
rv = (LeaseSetKeys)_leaseSetKeys.remove(dest);
}
- _pendingWrite = true;
+ if (dest != null)
+ queueWrite();
return rv;
}
@@ -133,26 +142,27 @@ public class KeyManager {
super(KeyManager.this._context);
}
public void runJob() {
- String keyDir = KeyManager.this._context.getProperty(PROP_KEYDIR);
+ String keyDir = getContext().getProperty(PROP_KEYDIR);
if (keyDir == null)
keyDir = DEFAULT_KEYDIR;
File dir = new File(keyDir);
if (!dir.exists())
dir.mkdirs();
- if (dir.exists() && dir.isDirectory() && dir.canRead() && dir.canWrite())
+ if (dir.exists() && dir.isDirectory() && dir.canRead() && dir.canWrite()) {
syncKeys(dir);
+ } else {
+ _log.log(Log.CRIT, "Unable to synchronize keys in " + keyDir + " - permissions problem?");
+ }
getTiming().setStartAfter(KeyManager.this._context.clock().now()+DELAY);
KeyManager.this._context.jobQueue().addJob(this);
}
private void syncKeys(File keyDir) {
- if (!needsSync()) return;
syncPrivateKey(keyDir);
syncPublicKey(keyDir);
syncSigningKey(keyDir);
syncVerificationKey(keyDir);
- _alreadyReadFromDisk = true;
}
private void syncPrivateKey(File keyDir) {
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index 0926ad1cb..4e5204742 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -55,6 +55,7 @@ public class Router {
private SessionKeyPersistenceHelper _sessionKeyPersistenceHelper;
private boolean _killVMOnEnd;
private boolean _isAlive;
+ private int _gracefulExitCode;
private I2PThread.OOMEventListener _oomListener;
private ShutdownHook _shutdownHook;
private I2PThread _gracefulShutdownDetector;
@@ -168,6 +169,8 @@ public class Router {
Runtime.getRuntime().addShutdownHook(_shutdownHook);
I2PThread.addOOMEventListener(_oomListener);
+ _context.keyManager().startup();
+
readConfig();
setupHandlers();
@@ -543,6 +546,7 @@ public class Router {
public static final int EXIT_HARD = 3;
public static final int EXIT_OOM = 10;
public static final int EXIT_HARD_RESTART = 4;
+ public static final int EXIT_GRACEFUL_RESTART = 5;
public void shutdown(int exitCode) {
_isAlive = false;
@@ -580,6 +584,10 @@ public class Router {
*
*/
public void shutdownGracefully() {
+ shutdownGracefully(EXIT_GRACEFUL);
+ }
+ public void shutdownGracefully(int exitCode) {
+ _gracefulExitCode = exitCode;
_config.setProperty(PROP_SHUTDOWN_IN_PROGRESS, "true");
synchronized (_gracefulShutdownDetector) {
_gracefulShutdownDetector.notifyAll();
@@ -615,7 +623,7 @@ public class Router {
if (_context.tunnelManager().getParticipatingCount() <= 0) {
if (_log.shouldLog(Log.CRIT))
_log.log(Log.CRIT, "Graceful shutdown progress - no more tunnels, safe to die");
- shutdown(EXIT_GRACEFUL);
+ shutdown(_gracefulExitCode);
return;
} else {
try {
diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java
index 8555a3166..0bd1d459e 100644
--- a/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java
+++ b/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java
@@ -222,8 +222,13 @@ public class OutboundClientMessageJob extends JobImpl {
public String getName() { return "Short circuit search"; }
public void runJob() {
LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(_ls.getDestination().calculateHash());
- if (ls == null)
- getContext().netDb().store(_ls.getDestination().calculateHash(), _ls);
+ if (ls == null) {
+ try {
+ getContext().netDb().store(_ls.getDestination().calculateHash(), _ls);
+ } catch (IllegalArgumentException iae) {
+ // ignore - it expired anyway
+ }
+ }
}
}
diff --git a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java
index 6e868130f..2097359ca 100644
--- a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java
+++ b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java
@@ -13,6 +13,7 @@ import java.util.Properties;
import net.i2p.data.DataFormatException;
import net.i2p.data.RouterInfo;
+import net.i2p.data.SigningPrivateKey;
import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
@@ -41,7 +42,13 @@ public class PublishLocalRouterInfoJob extends JobImpl {
ri.setPublished(getContext().clock().now());
ri.setOptions(stats);
ri.setAddresses(getContext().commSystem().createAddresses());
- ri.sign(getContext().keyManager().getSigningPrivateKey());
+ SigningPrivateKey key = getContext().keyManager().getSigningPrivateKey();
+ if (key == null) {
+ _log.log(Log.CRIT, "Internal error - signing private key not known? rescheduling publish for 30s");
+ requeue(30*1000);
+ return;
+ }
+ ri.sign(key);
getContext().router().setRouterInfo(ri);
if (_log.shouldLog(Log.INFO))
_log.info("Newly updated routerInfo is published with " + stats.size()
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 74e5de6b0..589cd1a2e 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
@@ -490,11 +490,15 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
public void publish(RouterInfo localRouterInfo) {
if (!_initialized) return;
Hash h = localRouterInfo.getIdentity().getHash();
- store(h, localRouterInfo);
- synchronized (_explicitSendKeys) {
- _explicitSendKeys.add(h);
+ try {
+ store(h, localRouterInfo);
+ synchronized (_explicitSendKeys) {
+ _explicitSendKeys.add(h);
+ }
+ writeMyInfo(localRouterInfo);
+ } catch (IllegalArgumentException iae) {
+ _log.error("Local routerInfo was invalid? "+ iae.getMessage(), iae);
}
- writeMyInfo(localRouterInfo);
}
/**
diff --git a/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java b/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java
index 97ab1a86c..9af4beef6 100644
--- a/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java
+++ b/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java
@@ -26,12 +26,17 @@ class LoadClientAppsJob extends JobImpl {
private static final String PROP_CLIENT_CONFIG_FILENAME = "router.clientConfigFile";
private static final String DEFAULT_CLIENT_CONFIG_FILENAME = "clients.config";
+ private static boolean _loaded = false;
public LoadClientAppsJob(RouterContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(LoadClientAppsJob.class);
}
public void runJob() {
+ synchronized (LoadClientAppsJob.class) {
+ if (_loaded) return;
+ _loaded = true;
+ }
Properties clientApps = getClientApps();
int i = 0;
while (true) {
diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java
index e981438cc..34237302e 100644
--- a/router/java/src/net/i2p/router/transport/TransportManager.java
+++ b/router/java/src/net/i2p/router/transport/TransportManager.java
@@ -112,6 +112,7 @@ public class TransportManager implements TransportEventListener {
((Transport)_transports.get(i)).stopListening();
}
_transports.clear();
+ _addresses.clear();
}
private boolean isSupported(Set addresses, Transport t) {