Compare commits

...

6 Commits

7 changed files with 295 additions and 41 deletions

View File

@ -222,8 +222,8 @@ public class GeneralHelper {
*/
private static final String[] SHARED_OPTIONS = {
// I2CP
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume",
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime",
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.rekeyOnIdle", "i2cp.newDestOnResume",
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.rekeyIdleTime", "i2cp.closeIdleTime",
// dest / LS
I2PClient.PROP_SIGTYPE, "i2cp.leaseSetEncType", "i2cp.leaseSetType",
"persistentClientKey",
@ -885,9 +885,19 @@ public class GeneralHelper {
return getProperty(tunnel, "i2cp.closeIdleTime", def*60*1000) / (60*1000);
}
/** @since 0.9.47 */
public boolean getRekeyOnIdle(int tunnel, boolean def) {
return getBooleanProperty(tunnel, "i2cp.rekeyOnIdle", def);
}
/** @since 0.9.47 */
public int getRekeyTime(int tunnel, int def) {
return getProperty(tunnel, "i2cp.rekeyIdleTime", def*60*1000) / (60*1000);
}
public boolean getNewDest(int tunnel) {
return getBooleanProperty(tunnel, "i2cp.newDestOnResume") &&
getBooleanProperty(tunnel, "i2cp.closeOnIdle") &&
(getBooleanProperty(tunnel, "i2cp.closeOnIdle") || getBooleanProperty(tunnel, "i2cp.restartOnIdle")) &&
!getBooleanProperty(tunnel, "persistentClientKey");
}

View File

@ -264,6 +264,16 @@ public class TunnelConfig {
else
_booleanOptions.remove("i2cp.closeOnIdle");
}
/** @since 0.9.47 */
public void setRekey(boolean val) {
if (val){
_booleanOptions.add("i2cp.rekeyOnIdle");
setNewDest(1);
setShared(false);
}else{
_booleanOptions.remove("i2cp.rekeyOnIdle");
}
}
public void setEncrypt(boolean val) {
if (val)
_booleanOptions.add("i2cp.encryptLeaseSet");
@ -478,6 +488,10 @@ public class TunnelConfig {
public void setCloseTime(int val) {
_otherOptions.put("i2cp.closeIdleTime", Integer.toString(val * 60*1000));
}
/** @since 0.9.47 */
public void setRekeyTime(int val) {
_otherOptions.put("i2cp.rekeyIdleTime", Integer.toString(val * 60*1000));
}
public void setAllowUserAgent(boolean val) {
if (val)
@ -1091,7 +1105,7 @@ public class TunnelConfig {
I2PTunnelIRCClient.PROP_DCC
};
private static final String _booleanClientOpts[] = {
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen",
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.rekeyOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen",
I2PTunnelClientBase.PROP_USE_SSL,
};
private static final String _booleanProxyOpts[] = {
@ -1114,7 +1128,7 @@ public class TunnelConfig {
TunnelController.PROP_LIMITS_SET
};
private static final String _otherClientOpts[] = {
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime",
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime", "i2cp.rekeyIdleTime",
"outproxyUsername", "outproxyPassword",
I2PTunnelHTTPClient.PROP_JUMP_SERVERS,
I2PTunnelHTTPClientBase.PROP_AUTH,

View File

@ -340,11 +340,21 @@ public class EditBean extends IndexBean {
public boolean getClose(int tunnel) {
return _helper.getCloseOnIdle(tunnel, false);
}
public int getCloseTime(int tunnel) {
return _helper.getCloseTime(tunnel, 30);
}
/** @since 0.9.47 */
public boolean getRekey(int tunnel) {
return _helper.getRekeyOnIdle(tunnel, false);
}
/** @since 0.9.47 */
public int getRekeyTime(int tunnel) {
return _helper.getRekeyTime(tunnel, 10);
}
public boolean getNewDest(int tunnel) {
return _helper.getNewDest(tunnel);
}

View File

@ -485,11 +485,11 @@ public class IndexBean {
}
public String getSharedClient(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getSharedClient();
else
return "";
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getSharedClient();
else
return "";
}
public String getClientDestination(int tunnel) {
@ -847,10 +847,10 @@ public class IndexBean {
_config.setStartOnLoad(true);
}
public void setShared(String moo) {
_config.setShared(true);
_config.setShared(true);
}
public void setShared(boolean val) {
_config.setShared(val);
_config.setShared(val);
}
public void setConnectDelay(String moo) {
_config.setConnectDelay(true);
@ -865,6 +865,10 @@ public class IndexBean {
public void setClose(String moo) {
_config.setClose(true);
}
/** @since 0.9.47 */
public void setRekey(String moo) {
_config.setRekey(true);
}
public void setEncrypt(String moo) {
_config.setEncrypt(true);
}
@ -1056,6 +1060,15 @@ public class IndexBean {
}
}
/** @since 0.9.47 */
public void setRekeyTime(String val) {
if (val != null) {
try {
_config.setRekeyTime(Integer.parseInt(val.trim()));
} catch (NumberFormatException nfe) {}
}
}
/** @since 0.9.14 */
public void setAllowUserAgent(String moo) {
_config.setAllowUserAgent(true);

View File

@ -409,6 +409,21 @@
<%
} // !streamrclient
%>
<tr>
<th colspan="2">
<%=intl._t("Rekey tunnel to change destination when idle")%>
</th>
</tr>
<tr>
<td>
<label title="<%=intl._t("To force rotation of tunnel identity, re-key tunnels after an idle period")%>"><input value="1" type="checkbox" name="rekey"<%=(editBean.getRekey(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
<%=intl._t("Change keys of client tunnels after specified idle period(incompatible with \"Shared Tunnels\")")%></label>
</td><td>
<b><%=intl._t("Idle period")%>:</b>
<input type="text" name="rekeyTime" size="4" maxlength="4" title="<%=intl._t("Rekey Tunnel Idle Time")%>" value="<%=editBean.getRekeyTime(curTunnel)%>" class="freetext period" />
minutes
</td>
</tr>
<tr>
<th colspan="2">
<%=intl._t("Reduce tunnel quantity when idle")%>

View File

@ -10,6 +10,8 @@ package net.i2p.client.impl;
*/
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
@ -34,7 +36,9 @@ import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.CoreVersion;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
@ -90,7 +94,7 @@ import net.i2p.util.VersionComparator;
public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessageEventListener {
protected final Log _log;
/** who we are */
private final Destination _myDestination;
private Destination _myDestination;
/** private key for decryption */
private PrivateKey _privateKey;
/** private key for signing */
@ -584,11 +588,11 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
// we don't support EncTypes in Destinations,
// but i2pd does, and also, this code is used from PrivateKeyFile to
// read in router.keys.dat files and we will support EncTypes for RouterIdentities
EncType etype = _myDestination.getPublicKey().getType();
EncType etype = getMyDestination().getPublicKey().getType();
if (etype != EncType.ELGAMAL_2048)
_privateKey = new PrivateKey(etype);
_privateKey.readBytes(destKeyStream);
SigType dtype = _myDestination.getSigningPublicKey().getType();
SigType dtype = getMyDestination().getSigningPublicKey().getType();
_signingPrivateKey = new SigningPrivateKey(dtype);
_signingPrivateKey.readBytes(destKeyStream);
if (_signingPrivateKey.isOffline()) {
@ -607,6 +611,43 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
}
}
/**
* Replace the keys used for the current session in order to force a change of destination.
* It is incompatible with persistent client keys, and will cause persistent client keys to
* change if used with them.
*
* It will re-use the settings it finds from the previous session. Before using it, close the
* tunnel(usually with destroySession) and re-connect it afterward.
*
* @since 0.9.47
*/
public void rekeySession() throws I2PException, DataFormatException, IOException {
verifyClosed();
ByteArrayOutputStream destOutputStream = new ByteArrayOutputStream();
Properties props = getOptions();
SigType sigType = getMyDestination().getSigType();
try {
I2PClientFactory.createClient().createDestination(destOutputStream, sigType);
ByteArrayInputStream destKeyStream = new ByteArrayInputStream(destOutputStream.toByteArray());
_myDestination = new Destination();
if (_log.shouldLog(Log.WARN))
_log.warn("Starting a re-key");
_myDestination.readBytes(destKeyStream);
EncType etype = getMyDestination().getPublicKey().getType();
_privateKey = new PrivateKey(etype);
_privateKey.readBytes(destKeyStream);
SigType dtype = getMyDestination().getSigningPublicKey().getType();
_signingPrivateKey = new SigningPrivateKey(dtype);
_signingPrivateKey.readBytes(destKeyStream);
}catch(I2PException e){
throw e;
}
}
/**
* Does this session have offline and transient keys?
* @since 0.9.38
@ -763,7 +804,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before getDate");
Properties auth = null;
if ((!_context.isRouterContext()) && _options.containsKey(I2PClient.PROP_USER) && _options.containsKey(I2PClient.PROP_PW)) {
// Only supported by routers 0.9.11 or higher, but we don't know the version yet.
// Only supported by routers 0.9.11 or higher, but we don't know the version yet.
// Auth will also be sent in the SessionConfig.
auth = new OrderedProperties();
auth.setProperty(I2PClient.PROP_USER, _options.getProperty(I2PClient.PROP_USER));
@ -1127,8 +1168,33 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
/**
* Retrieve the destination of the session
*
* @since 0.9.47(8?) _myDestination is no longer final, but must
* be re-instantiated in order to change it's value, or it will
* throw an IllegalStateException
*/
public Destination getMyDestination() { return _myDestination; }
public Destination getMyDestination() {
try {
return getMyDestination(false);
}catch(I2PSessionException ise) {
if (_log.shouldLog(Log.WARN))
_log.warn("I2PSessionException retrieving I2P destination");
}
return _myDestination;
}
/**
* Retrieve the destination of the session
*/
public Destination getMyDestination(boolean change) throws I2PSessionException {
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "getting tunnel destination with change: " + change);
while (change) {
verifyClosed();
change = false;
break;
}
return _myDestination;
}
/**
* Retrieve the decryption PrivateKey
@ -1217,6 +1283,53 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
}
}
}
protected void verifyClosed() throws I2PSessionException {
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "Verifying tunnel state is closed, current state is:" + _state);
synchronized(_stateLock) {
boolean loop = true;
boolean wasOpening = false;
while (loop) {
if (_myDestination.equals(new Destination())){
loop = false;
break;
}
switch (_state) {
case INIT:
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "Tunnel is initializing, safe to rekey" + _state);
loop = false;
break;
case CLOSED:
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "Tunnel is closed, safe to rekey" + _state);
loop = false;
break;
case OPENING:
if (_log.shouldLog(Log.WARN)) _log.warn(getPrefix() + "Tunnel is in an opening state, unsafe to rekey" + _state);
wasOpening = true;
try {
_stateLock.wait(10*1000);
} catch (InterruptedException ie) {
throw new I2PSessionException("Interrupted", ie);
}
break;
case GOTDATE:
if (_log.shouldLog(Log.WARN)) _log.warn(getPrefix() + "Tunnel is getting date, unsafe to rekey" + _state);
wasOpening = true;
try {
_stateLock.wait(10*1000);
} catch (InterruptedException ie) {
throw new I2PSessionException("Interrupted", ie);
}
break;
case CLOSING: // fall through and loop over again
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "Tunnel is closing, loop over" + _state);
case OPEN:
if (_log.shouldLog(Log.WARN)) _log.warn(getPrefix() + "Tunnel is open and unavailable for rekey" + _state);
throw new I2PSessionException("Tunnel is open and unavailable for rekey");
}
}
}
}
/**
* Deliver an I2CP message to the router
@ -1330,7 +1443,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + "Closing the socket", new Exception("closeSocket"));
// maybe not the right place for this, but let's be sure
Destination d = _myDestination;
Destination d = getMyDestination();
if (d != null)
_context.keyRing().remove(d.calculateHash());
synchronized(_stateLock) {
@ -1902,6 +2015,14 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
return _lastActivity;
}
private long updateLastActivity() {
if (_lastActivity == 0)
updateActivity();
long la = _lastActivity;
updateActivity();
return la;
}
public void setReduced() {
_isReduced = true;
}
@ -1910,9 +2031,10 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
_isReduced = false;
boolean reduce = Boolean.parseBoolean(_options.getProperty("i2cp.reduceOnIdle"));
boolean close = Boolean.parseBoolean(_options.getProperty("i2cp.closeOnIdle"));
if (reduce || close) {
updateActivity();
_context.simpleTimer2().addEvent(new SessionIdleTimer(_context, this, reduce, close), SessionIdleTimer.MINIMUM_TIME);
boolean restart = Boolean.parseBoolean(_options.getProperty("i2cp.rekeyOnIdle"));
if (reduce || close || restart ) {
// updateActivity();
_context.simpleTimer2().addEvent(new SessionIdleTimer(_context, this, reduce, close, restart, updateLastActivity()), SessionIdleTimer.MINIMUM_TIME);
}
}
@ -1920,8 +2042,8 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
public String toString() {
StringBuilder buf = new StringBuilder(32);
buf.append("Session: ");
if (_myDestination != null)
buf.append(_myDestination.calculateHash().toBase64().substring(0, 4));
if (getMyDestination() != null)
buf.append(getMyDestination().calculateHash().toBase64().substring(0, 4));
else
buf.append("[null dest]");
buf.append(getPrefix());

View File

@ -6,8 +6,10 @@ package net.i2p.client.impl;
*/
import java.util.Properties;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PSessionException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
@ -20,6 +22,7 @@ import net.i2p.util.SimpleTimer;
*/
class SessionIdleTimer implements SimpleTimer.TimedEvent {
public static final long MINIMUM_TIME = 5*60*1000;
private static final long DEFAULT_CYCLE_TIME = 180*60*1000;
private static final long DEFAULT_REDUCE_TIME = 20*60*1000;
private static final long DEFAULT_CLOSE_TIME = 30*60*1000;
private final Log _log;
@ -30,14 +33,16 @@ class SessionIdleTimer implements SimpleTimer.TimedEvent {
private final long _reduceTime;
private final boolean _shutdownEnabled;
private final long _shutdownTime;
private final boolean _restartEnabled;
private final long _restartTime;
private final long _minimumTime;
private long _lastActive;
/**
* reduce, shutdown, or both must be true
* reduce, shutdown, restart or some combination must be true
*/
public SessionIdleTimer(I2PAppContext context, I2PSessionImpl session, boolean reduce, boolean shutdown) {
if (! (reduce || shutdown))
public SessionIdleTimer(I2PAppContext context, I2PSessionImpl session, boolean reduce, boolean shutdown, boolean restart, long starttime) {
if (! (reduce || shutdown || restart))
throw new IllegalArgumentException("At least one must be enabled");
_context = context;
_log = context.logManager().getLog(SessionIdleTimer.class);
@ -46,6 +51,7 @@ class SessionIdleTimer implements SimpleTimer.TimedEvent {
long minimumTime = Long.MAX_VALUE;
long reduceTime = 0;
long shutdownTime = 0;
long restartTime = 0;
int reduceQuantity = 0;
if (reduce) {
reduceQuantity = 1;
@ -77,41 +83,104 @@ class SessionIdleTimer implements SimpleTimer.TimedEvent {
if (reduce && shutdownTime <= reduceTime)
reduce = false;
}
if (restart) {
restartTime = DEFAULT_CYCLE_TIME;
String p = props.getProperty("i2cp.rekeyIdleTime");
if (p != null) {
try {
restartTime = Math.max(Long.parseLong(p), MINIMUM_TIME);
} catch (NumberFormatException nfe) {}
}
if (shutdown && restartTime <= shutdownTime)
shutdown = false;
minimumTime = Math.min(minimumTime, restartTime);
if (reduce && restartTime <= reduceTime)
reduce = false;
}
_reduceEnabled = reduce;
_reduceQuantity = reduceQuantity;
_reduceTime = reduceTime;
_shutdownEnabled = shutdown;
_shutdownTime = shutdownTime;
_restartEnabled = restart;
_restartTime = restartTime;
_minimumTime = minimumTime;
_lastActive = starttime;
}
public void timeReached() {
if (_session.isClosed())
return;
long nextDelay = 0;
long now = _context.clock().now();
long lastActivity = _session.lastActivity();
if (_log.shouldDebug())
_log.debug("Fire idle timer, last activity: " + DataHelper.formatDuration(now - lastActivity) + " ago ");
long nextDelay = 0;
if (_shutdownEnabled && now - lastActivity >= _shutdownTime) {
if (_log.shouldLog(Log.WARN))
_log.warn("Closing on idle " + _session);
_session.destroySession();
if (_session.isClosed() && !_restartEnabled)
return;
} else if (lastActivity <= _lastActive && !_shutdownEnabled) {
if (_log.shouldDebug())
_log.debug("Fire idle timer, last activity: " + DataHelper.formatDuration(now - lastActivity) + " ago " + _session);
// Since re-keys happen all-at-once, it should be safe to do them before we do the
// tunnel count/close adjustments. Also, since there's no good reason to re-key an inactive tunnel
// only do it once per extended period of idle time.
if (_restartEnabled && now - lastActivity >= _restartTime && now - _lastActive < (2*_restartTime)) {
try {
// don't do a rekey if the tunnel was closed to begin with
if (!_session.isClosed()) {
_session.destroySession();
_session.rekeySession();
if (_log.shouldDebug())
_log.debug("Closing on idle for quick rekey" + _session);
}
} catch (I2PException ie) {
if (_log.shouldLog(Log.ERROR))
_log.error("Failed recreating the session" + _session, ie);
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Failed recreating the session" + _session, ioe);
} finally {
nextDelay = _restartTime;
}
}
// Top priority is close-on-idle to conserve resources. If the close-on-idle event has occurred or the time
// since the last activity has been greater than the minimum shutown time, then destroySession if it's not
// already closed.
if (_shutdownEnabled && now - lastActivity >= _shutdownTime) {
if (_log.shouldDebug())
_log.debug("Closing on idle " + _session);
if (!_session.isClosed())
_session.destroySession();
return;
}
// If we've made it here, then close-on-idle isn't in effect so it's OK to reconnect the tunnel with the new
// destination keys. This preserves the functionality of close-on-idle in the presence of the rekeying function.
if (_restartEnabled) {
if (_session.isClosed()){
_session.reconnect();
if (_log.shouldDebug())
_log.debug("Key cycle complete, next re-key in " + _restartTime + " ms, session" + _session);
}
}
// Set the next delay for sleeping tunnels
if (lastActivity <= _lastActive && !_shutdownEnabled) {
if (_log.shouldDebug())
_log.debug("Still idle, sleeping again " + _session);
nextDelay = _reduceTime;
} else if (_reduceEnabled && now - lastActivity >= _reduceTime) {
// Make sure that the tunnel count is correct for the idle duration, adjust down if we've passed the
// reduce-idle-time threshold.
} //else
if (_reduceEnabled && now - lastActivity >= _reduceTime) {
if (_log.shouldDebug())
_log.debug("Reducing quantity on idle " + _session);
try {
_session.getProducer().updateTunnels(_session, _reduceQuantity);
} catch (I2PSessionException ise) {
_log.error("bork idle reduction " + ise);
_log.error("bork idle reduction ", ise);
}
_session.setReduced();
_lastActive = lastActivity;
if (!_restartEnabled)
_lastActive = lastActivity;
if (_shutdownEnabled)
nextDelay = _shutdownTime - (now - lastActivity);
else
@ -119,6 +188,7 @@ class SessionIdleTimer implements SimpleTimer.TimedEvent {
} else {
nextDelay = _minimumTime - (now - lastActivity);
}
_context.simpleTimer2().addEvent(this, nextDelay);
}
}