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
This commit is contained in:
jrandom
2004-09-06 05:20:40 +00:00
committed by zzz
parent db339d40de
commit 0eedc1b128
13 changed files with 113 additions and 49 deletions

View File

@ -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();

View File

@ -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");

View File

@ -25,36 +25,47 @@
System.setProperty("net.i2p.router.web.ConfigServiceHandler.nonce", new java.util.Random().nextLong()+""); %>
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigServiceHandler.nonce")%>" />
<h4>Shutdown the router</h4>
Graceful shutdown lets the router satisfy the agreements it has already made
<p>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.<br />
router immediately, that option is available as well.</p>
<input type="submit" name="action" value="Shutdown gracefully" />
<input type="submit" name="action" value="Shutdown immediately" />
<input type="submit" name="action" value="Cancel graceful shutdown" />
<p>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.</p>
<input type="submit" name="action" value="Graceful restart" />
<input type="submit" name="action" value="Hard restart" />
<% if ( (System.getProperty("os.name") != null) && (System.getProperty("os.name").startsWith("Win")) ) { %>
<h4>Systray integration</h4>
On the windows platform, there is a small application to sit in the system
<p>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. <br />
If you are on windows, you can either enable or disable that icon here.</p>
<input type="submit" name="action" value="Show systray icon" />
<input type="submit" name="action" value="Hide systray icon" />
<h4>Run on startup</h4>
You can control whether I2P is run on startup or not by selecting one of the
<p>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 <code>install_i2p_service_winnt.bat</code> (or
<code>uninstall_i2p_service_winnt.bat</code>) from the command line, if you prefer.<br />
<code>uninstall_i2p_service_winnt.bat</code>) from the command line, if you prefer.</p>
<input type="submit" name="action" value="Run I2P on startup" />
<input type="submit" name="action" value="Don't run I2P on startup" /><br />
<b>Note:</b> If you are running I2P as service right now, removing it will shut
<p><b>Note:</b> 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.</p>
<% } %>
<h4>Debugging</h4>
At times, it may be helpful to debug I2P by getting a thread dump. To do so,
<p>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
<a href="logs.jsp#servicelogs">wrapper.log</a>.<br />
<a href="logs.jsp#servicelogs">wrapper.log</a>.</p>
<input type="submit" name="action" value="Dump threads" />
</form>
</div>

View File

@ -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()));

View File

@ -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.

View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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
}
}
}
}

View File

@ -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()

View File

@ -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);
}
/**

View File

@ -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) {

View File

@ -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) {