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

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