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:
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -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>
|
||||
|
@ -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()));
|
||||
|
14
history.txt
14
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.
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user