From 68d25afcbabbf27dc7efedb132f0de0293aaa5ac Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 9 Jun 2013 14:42:51 +0000 Subject: [PATCH] * NetDB: Prep for leasesets with different expire times - Add new I2CP RequestVariableLeaseSetMessage - Send RVLSM if client supports it; handle on client side; disabled by default for the moment. - Add LeaseSet.getLatestLeaseDate() - Check latest, not earliest, date too far in future in KNDF.validate() - Check latest date too far in past in KNDF.validate() - Only check gateway and tunnel ID for equality in OCMOSJ lease caching to reduce churn - Split up KNDF.validate(RI) for efficiency, don't need to check signature, netid, etc. before lookups, only on store - Remove enforeNetID config - Fix major bug causing newer leasesets to be treated as older, and not stored or published - Increase max adjustment time of earliest lease - TransientDataStore cleanups - RouterInfo and LeaseSet equals() speedups --- .../src/net/i2p/router/web/NetDbRenderer.java | 35 +++-- .../client/I2PClientMessageHandlerMap.java | 3 + .../client/RequestLeaseSetMessageHandler.java | 21 ++- ...RequestVariableLeaseSetMessageHandler.java | 41 ++++++ core/java/src/net/i2p/data/LeaseSet.java | 30 +++- core/java/src/net/i2p/data/RouterInfo.java | 5 +- .../net/i2p/data/i2cp/I2CPMessageHandler.java | 2 + .../i2p/data/i2cp/RequestLeaseSetMessage.java | 6 +- .../i2cp/RequestVariableLeaseSetMessage.java | 128 ++++++++++++++++ history.txt | 17 +++ .../src/net/i2p/router/RouterVersion.java | 2 +- .../i2p/router/client/RequestLeaseSetJob.java | 60 ++++++-- .../OutboundClientMessageOneShotJob.java | 6 +- .../KademliaNetworkDatabaseFacade.java | 137 ++++++++++-------- .../kademlia/RepublishLeaseSetJob.java | 4 +- .../kademlia/TransientDataStore.java | 35 +++-- .../net/i2p/router/tunnel/pool/ExpireJob.java | 10 +- .../i2p/router/tunnel/pool/TunnelPool.java | 10 +- 18 files changed, 432 insertions(+), 120 deletions(-) create mode 100644 core/java/src/net/i2p/client/RequestVariableLeaseSetMessageHandler.java create mode 100644 core/java/src/net/i2p/data/i2cp/RequestVariableLeaseSetMessage.java diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java index 22ab189435..e760118355 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java @@ -17,7 +17,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -27,6 +26,7 @@ import java.util.TreeSet; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; +import net.i2p.data.Lease; import net.i2p.data.LeaseSet; import net.i2p.data.RouterAddress; import net.i2p.data.RouterInfo; @@ -79,9 +79,8 @@ public class NetDbRenderer { renderRouterInfo(buf, _context.router().getRouterInfo(), true, true); } else { boolean notFound = true; - Set routers = _context.netDb().getRouters(); - for (Iterator iter = routers.iterator(); iter.hasNext(); ) { - RouterInfo ri = (RouterInfo)iter.next(); + Set routers = _context.netDb().getRouters(); + for (RouterInfo ri : routers) { Hash key = ri.getIdentity().getHash(); if (key.toBase64().startsWith(routerPrefix)) { renderRouterInfo(buf, ri, false, true); @@ -152,11 +151,12 @@ public class NetDbRenderer { buf.append(dest.toBase64().substring(0, 6)); } buf.append(")
\n"); - long exp = ls.getEarliestLeaseDate()-now; + long exp = ls.getLatestLeaseDate()-now; if (exp > 0) - buf.append(_("Expires in {0}", DataHelper.formatDuration2(exp))).append("
\n"); + buf.append(_("Expires in {0}", DataHelper.formatDuration2(exp))); else - buf.append(_("Expired {0} ago", DataHelper.formatDuration2(0-exp))).append("
\n"); + buf.append(_("Expired {0} ago", DataHelper.formatDuration2(0-exp))); + buf.append("
\n"); if (debug) { buf.append("RAP? " + ls.getReceivedAsPublished()); buf.append(" RAR? " + ls.getReceivedAsReply()); @@ -171,9 +171,18 @@ public class NetDbRenderer { buf.append("Encryption Key: ").append(ls.getEncryptionKey().toBase64().substring(0, 20)).append("...
"); } for (int i = 0; i < ls.getLeaseCount(); i++) { - buf.append(_("Lease")).append(' ').append(i + 1).append(": " + _("Gateway") + ' '); - buf.append(_context.commSystem().renderPeerHTML(ls.getLease(i).getGateway())); - buf.append(' ' + _("Tunnel") + ' ').append(ls.getLease(i).getTunnelId().getTunnelId()).append("
\n"); + Lease lease = ls.getLease(i); + buf.append(_("Lease")).append(' ').append(i + 1).append(": ").append(_("Gateway")).append(' '); + buf.append(_context.commSystem().renderPeerHTML(lease.getGateway())); + buf.append(' ').append(_("Tunnel")).append(' ').append(lease.getTunnelId().getTunnelId()).append(' '); + if (debug) { + long exl = lease.getEndDate().getTime() - now; + if (exl > 0) + buf.append(_("Expires in {0}", DataHelper.formatDuration2(exl))); + else + buf.append(_("Expired {0} ago", DataHelper.formatDuration2(0-exl))); + } + buf.append("
\n"); } buf.append("
\n"); out.write(buf.toString()); @@ -253,8 +262,7 @@ public class NetDbRenderer { Set routers = new TreeSet(new RouterInfoComparator()); routers.addAll(_context.netDb().getRouters()); - for (Iterator iter = routers.iterator(); iter.hasNext(); ) { - RouterInfo ri = (RouterInfo)iter.next(); + for (RouterInfo ri : routers) { Hash key = ri.getIdentity().getHash(); boolean isUs = key.equals(us); if (!isUs) { @@ -391,8 +399,7 @@ public class NetDbRenderer { buf.append(" title=\"").append(_(_context.commSystem().getCountryName(country))).append('\"'); buf.append(" src=\"/flags.jsp?c=").append(country).append("\"> "); } - for (Iterator iter = info.getAddresses().iterator(); iter.hasNext(); ) { - RouterAddress addr = (RouterAddress)iter.next(); + for (RouterAddress addr : info.getAddresses()) { String style = addr.getTransportStyle(); buf.append("").append(DataHelper.stripHTML(style)).append(": "); int cost = addr.getCost(); diff --git a/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java b/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java index 76350cbb21..7bc912b97f 100644 --- a/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java +++ b/core/java/src/net/i2p/client/I2PClientMessageHandlerMap.java @@ -16,6 +16,7 @@ import net.i2p.data.i2cp.DisconnectMessage; import net.i2p.data.i2cp.MessagePayloadMessage; import net.i2p.data.i2cp.MessageStatusMessage; import net.i2p.data.i2cp.RequestLeaseSetMessage; +import net.i2p.data.i2cp.RequestVariableLeaseSetMessage; import net.i2p.data.i2cp.SessionStatusMessage; import net.i2p.data.i2cp.SetDateMessage; @@ -40,6 +41,7 @@ class I2PClientMessageHandlerMap { highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE); highest = Math.max(highest, DestReplyMessage.MESSAGE_TYPE); highest = Math.max(highest, BandwidthLimitsMessage.MESSAGE_TYPE); + highest = Math.max(highest, RequestVariableLeaseSetMessage.MESSAGE_TYPE); _handlers = new I2CPMessageHandler[highest+1]; _handlers[DisconnectMessage.MESSAGE_TYPE] = new DisconnectMessageHandler(context); @@ -50,6 +52,7 @@ class I2PClientMessageHandlerMap { _handlers[SetDateMessage.MESSAGE_TYPE] = new SetDateMessageHandler(context); _handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context); _handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context); + _handlers[RequestVariableLeaseSetMessage.MESSAGE_TYPE] = new RequestVariableLeaseSetMessageHandler(context); } public I2CPMessageHandler getHandler(int messageTypeId) { diff --git a/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java index 30d8e61870..eea6d294d5 100644 --- a/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java @@ -29,7 +29,8 @@ import net.i2p.data.i2cp.RequestLeaseSetMessage; import net.i2p.util.Log; /** - * Handle I2CP RequestLeaseSetMessage from the router by granting all leases + * Handle I2CP RequestLeaseSetMessage from the router by granting all leases, + * using the specified expiration time for each lease. * * @author jrandom */ @@ -37,7 +38,15 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { private final Map _existingLeaseSets; public RequestLeaseSetMessageHandler(I2PAppContext context) { - super(context, RequestLeaseSetMessage.MESSAGE_TYPE); + this(context, RequestLeaseSetMessage.MESSAGE_TYPE); + } + + /** + * For extension + * @since 0.9.7 + */ + protected RequestLeaseSetMessageHandler(I2PAppContext context, int messageType) { + super(context, messageType); // not clear why there would ever be more than one _existingLeaseSets = new ConcurrentHashMap(4); } @@ -55,6 +64,14 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { //lease.setStartDate(msg.getStartDate()); leaseSet.addLease(lease); } + signLeaseSet(leaseSet, session); + } + + /** + * Finish creating and signing the new LeaseSet + * @since 0.9.7 + */ + protected void signLeaseSet(LeaseSet leaseSet, I2PSessionImpl session) { // also, if this session is connected to multiple routers, include other leases here leaseSet.setDestination(session.getMyDestination()); diff --git a/core/java/src/net/i2p/client/RequestVariableLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/RequestVariableLeaseSetMessageHandler.java new file mode 100644 index 0000000000..6ba03d8f69 --- /dev/null +++ b/core/java/src/net/i2p/client/RequestVariableLeaseSetMessageHandler.java @@ -0,0 +1,41 @@ +package net.i2p.client; + +/* + * free (adj.): unencumbered; not under the control of others + * Released into the public domain + * with no warranty of any kind, either expressed or implied. + * It probably won't make your computer catch on fire, or eat + * your children, but it might. Use at your own risk. + * + */ + +import net.i2p.I2PAppContext; +import net.i2p.data.LeaseSet; +import net.i2p.data.i2cp.I2CPMessage; +import net.i2p.data.i2cp.RequestVariableLeaseSetMessage; +import net.i2p.util.Log; + +/** + * Handle I2CP RequestVariableLeaseSetMessage from the router by granting all leases, + * retaining the individual expiration time for each lease. + * + * @since 0.9.7 + */ +class RequestVariableLeaseSetMessageHandler extends RequestLeaseSetMessageHandler { + + public RequestVariableLeaseSetMessageHandler(I2PAppContext context) { + super(context, RequestVariableLeaseSetMessage.MESSAGE_TYPE); + } + + @Override + public void handleMessage(I2CPMessage message, I2PSessionImpl session) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Handle message " + message); + RequestVariableLeaseSetMessage msg = (RequestVariableLeaseSetMessage) message; + LeaseSet leaseSet = new LeaseSet(); + for (int i = 0; i < msg.getEndpoints(); i++) { + leaseSet.addLease(msg.getEndpoint(i)); + } + signLeaseSet(leaseSet, session); + } +} diff --git a/core/java/src/net/i2p/data/LeaseSet.java b/core/java/src/net/i2p/data/LeaseSet.java index 44167a5485..bf7ca7636e 100644 --- a/core/java/src/net/i2p/data/LeaseSet.java +++ b/core/java/src/net/i2p/data/LeaseSet.java @@ -93,6 +93,9 @@ public class LeaseSet extends DatabaseEntry { _firstExpiration = Long.MAX_VALUE; } + /** + * Same as getEarliestLeaseDate() + */ public long getDate() { return getEarliestLeaseDate(); } @@ -198,7 +201,7 @@ public class LeaseSet extends DatabaseEntry { } /** - * Retrieve the end date of the earliest lease include in this leaseSet. + * Retrieve the end date of the earliest lease included in this leaseSet. * This is the date that should be used in comparisons for leaseSet age - to * determine which LeaseSet was published more recently (later earliestLeaseSetDate * means it was published later) @@ -211,6 +214,17 @@ public class LeaseSet extends DatabaseEntry { return _firstExpiration; } + /** + * Retrieve the end date of the latest lease included in this leaseSet. + * This is the date used in isCurrent(). + * + * @return latest end date of any lease in the set, or 0 if there are no leases + * @since 0.9.7 + */ + public long getLatestLeaseDate() { + return _lastExpiration; + } + /** * Verify that the signature matches the lease set's destination's signing public key. * OR the included revocation key. @@ -287,8 +301,12 @@ public class LeaseSet extends DatabaseEntry { /** * This does NOT validate the signature + * + * @throws IllegalStateException if called more than once or Destination already set */ public void readBytes(InputStream in) throws DataFormatException, IOException { + if (_destination != null) + throw new IllegalStateException(); _destination = new Destination(); _destination.readBytes(in); _encryptionKey = PublicKey.create(in); @@ -297,7 +315,6 @@ public class LeaseSet extends DatabaseEntry { if (numLeases > MAX_LEASES) throw new DataFormatException("Too many leases - max is " + MAX_LEASES); //_version = DataHelper.readLong(in, 4); - _leases.clear(); for (int i = 0; i < numLeases; i++) { Lease lease = new Lease(); lease.readBytes(in); @@ -344,9 +361,10 @@ public class LeaseSet extends DatabaseEntry { if (object == this) return true; if ((object == null) || !(object instanceof LeaseSet)) return false; LeaseSet ls = (LeaseSet) object; - return DataHelper.eq(getEncryptionKey(), ls.getEncryptionKey()) && - //DataHelper.eq(getVersion(), ls.getVersion()) && - DataHelper.eq(_leases, ls._leases) && DataHelper.eq(_signature, ls.getSignature()) + return + DataHelper.eq(_signature, ls.getSignature()) + && DataHelper.eq(_leases, ls._leases) + && DataHelper.eq(getEncryptionKey(), ls.getEncryptionKey()) && DataHelper.eq(_signingKey, ls.getSigningKey()) && DataHelper.eq(_destination, ls.getDestination()); @@ -371,7 +389,7 @@ public class LeaseSet extends DatabaseEntry { buf.append("\n\tSignature: ").append(_signature); buf.append("\n\tLeases: #").append(getLeaseCount()); for (int i = 0; i < getLeaseCount(); i++) - buf.append("\n\t\tLease (").append(i).append("): ").append(getLease(i)); + buf.append("\n\t\t").append(getLease(i)); buf.append("]"); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/RouterInfo.java b/core/java/src/net/i2p/data/RouterInfo.java index e8155b7824..264495fede 100644 --- a/core/java/src/net/i2p/data/RouterInfo.java +++ b/core/java/src/net/i2p/data/RouterInfo.java @@ -592,9 +592,10 @@ public class RouterInfo extends DatabaseEntry { if (object == this) return true; if ((object == null) || !(object instanceof RouterInfo)) return false; RouterInfo info = (RouterInfo) object; - return DataHelper.eq(_identity, info.getIdentity()) + return + _published == info.getPublished() && DataHelper.eq(_signature, info.getSignature()) - && _published == info.getPublished(); + && DataHelper.eq(_identity, info.getIdentity()); // Let's speed up the NetDB //&& DataHelper.eq(_addresses, info.getAddresses()) //&& DataHelper.eq(_options, info.getOptions()) diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java index d48fe68748..0a62416617 100644 --- a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java +++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java @@ -77,6 +77,8 @@ public class I2CPMessageHandler { return new ReportAbuseMessage(); case RequestLeaseSetMessage.MESSAGE_TYPE: return new RequestLeaseSetMessage(); + case RequestVariableLeaseSetMessage.MESSAGE_TYPE: + return new RequestVariableLeaseSetMessage(); case SendMessageMessage.MESSAGE_TYPE: return new SendMessageMessage(); case SendMessageExpiresMessage.MESSAGE_TYPE: diff --git a/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java b/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java index 630ee6e378..c7a6240b39 100644 --- a/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java +++ b/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java @@ -50,18 +50,18 @@ public class RequestLeaseSetMessage extends I2CPMessageImpl { } public Hash getRouter(int endpoint) { - if ((endpoint < 0) || (_endpoints.size() < endpoint)) return null; + if ((endpoint < 0) || (_endpoints.size() <= endpoint)) return null; return _endpoints.get(endpoint).getRouter(); } public TunnelId getTunnelId(int endpoint) { - if ((endpoint < 0) || (_endpoints.size() < endpoint)) return null; + if ((endpoint < 0) || (_endpoints.size() <= endpoint)) return null; return _endpoints.get(endpoint).getTunnelId(); } /** @deprecated unused - presumably he meant remove? */ public void remoteEndpoint(int endpoint) { - if ((endpoint >= 0) && (endpoint < _endpoints.size())) _endpoints.remove(endpoint); + if ((endpoint >= 0) && (endpoint <= _endpoints.size())) _endpoints.remove(endpoint); } public void addEndpoint(Hash router, TunnelId tunnel) { diff --git a/core/java/src/net/i2p/data/i2cp/RequestVariableLeaseSetMessage.java b/core/java/src/net/i2p/data/i2cp/RequestVariableLeaseSetMessage.java new file mode 100644 index 0000000000..42bfdf5866 --- /dev/null +++ b/core/java/src/net/i2p/data/i2cp/RequestVariableLeaseSetMessage.java @@ -0,0 +1,128 @@ +package net.i2p.data.i2cp; + +/* + * free (adj.): unencumbered; not under the control of others + * Written by jrandom in 2003 and released into the public domain + * with no warranty of any kind, either expressed or implied. + * It probably won't make your computer catch on fire, or eat + * your children, but it might. Use at your own risk. + * + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.data.Lease; +import net.i2p.util.VersionComparator; + +/** + * Defines the message a router sends to a client to request that + * a leaseset be created and signed. The reply is a CreateLeaseSetMessage. + * + * This message has an expiration time for each lease, unlike RequestLeaseSetMessage, + * which has a single expiration time for all leases. + * + * @since 0.9.7 + */ +public class RequestVariableLeaseSetMessage extends I2CPMessageImpl { + public final static int MESSAGE_TYPE = 37; + private SessionId _sessionId; + private final List _endpoints; + + private static final String MIN_VERSION = "0.9.7"; + + public RequestVariableLeaseSetMessage() { + _endpoints = new ArrayList(); + } + + /** + * Does the client support this message? + * + * @param clientVersion may be null + * @return version != null and version >= 0.9.7 + */ + public static boolean isSupported(String clientVersion) { + return clientVersion != null && + VersionComparator.comp(clientVersion, MIN_VERSION) >= 0; + } + + public SessionId getSessionId() { + return _sessionId; + } + + public void setSessionId(SessionId id) { + _sessionId = id; + } + + public int getEndpoints() { + return _endpoints.size(); + } + + public Lease getEndpoint(int endpoint) { + if ((endpoint < 0) || (_endpoints.size() <= endpoint)) return null; + return _endpoints.get(endpoint); + } + + public void addEndpoint(Lease lease) { + if (lease == null) + throw new IllegalArgumentException(); + _endpoints.add(lease); + } + + @Override + protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { + try { + if (_sessionId == null) + throw new IllegalStateException(); + _sessionId = new SessionId(); + _sessionId.readBytes(in); + int numTunnels = (int) DataHelper.readLong(in, 1); + for (int i = 0; i < numTunnels; i++) { + Lease lease = new Lease(); + lease.readBytes(in); + _endpoints.add(lease); + } + } catch (DataFormatException dfe) { + throw new I2CPMessageException("Unable to load the message data", dfe); + } + } + + @Override + protected byte[] doWriteMessage() throws I2CPMessageException, IOException { + if (_sessionId == null) + throw new I2CPMessageException("No data"); + ByteArrayOutputStream os = new ByteArrayOutputStream(256); + try { + _sessionId.writeBytes(os); + DataHelper.writeLong(os, 1, _endpoints.size()); + for (int i = 0; i < _endpoints.size(); i++) { + _endpoints.get(i).writeBytes(os); + } + } catch (DataFormatException dfe) { + throw new I2CPMessageException("Error writing out the message data", dfe); + } + return os.toByteArray(); + } + + public int getType() { + return MESSAGE_TYPE; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("[RequestVariableLeaseSetMessage: "); + buf.append("\n\tSessionId: ").append(getSessionId()); + buf.append("\n\tTunnels:"); + for (int i = 0; i < getEndpoints(); i++) { + buf.append('\n').append(_endpoints.get(i)); + } + buf.append("]"); + return buf.toString(); + } +} diff --git a/history.txt b/history.txt index ee9f10cf28..d8938d757f 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,20 @@ +2013-06-09 zzz + * NetDB: Prep for leasesets with different expire times + - Add new I2CP RequestVariableLeaseSetMessage + - Send RVLSM if client supports it; handle on client side; + disabled by default for the moment. + - Add LeaseSet.getLatestLeaseDate() + - Check latest, not earliest, date too far in future in KNDF.validate() + - Check latest date too far in past in KNDF.validate() + - Only check gateway and tunnel ID for equality in OCMOSJ lease caching to reduce churn + - Split up KNDF.validate(RI) for efficiency, don't need to check + signature, netid, etc. before lookups, only on store + - Remove enforeNetID config + - Fix major bug causing newer leasesets to be treated as older, and not stored or published + - Increase max adjustment time of earliest lease + - TransientDataStore cleanups + - RouterInfo and LeaseSet equals() speedups + 2013-06-07 zzz * BlockfileNamingService: - Fix bug that kept reverse index from being updated diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index c10128fe62..fecba78d69 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 6; + public final static long BUILD = 7; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java b/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java index 96592e7f36..5ed35faccc 100644 --- a/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java +++ b/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java @@ -10,9 +10,12 @@ package net.i2p.router.client; import java.util.Date; +import net.i2p.data.Lease; import net.i2p.data.LeaseSet; +import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageException; import net.i2p.data.i2cp.RequestLeaseSetMessage; +import net.i2p.data.i2cp.RequestVariableLeaseSetMessage; import net.i2p.router.Job; import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; @@ -31,6 +34,12 @@ class RequestLeaseSetJob extends JobImpl { private final ClientConnectionRunner _runner; private final LeaseRequestState _requestState; + private static final long MAX_FUDGE = 2*1000; + + /** temp for testing */ + private static final String PROP_VARIABLE = "router.variableLeaseExpiration"; + private static final boolean DFLT_VARIABLE = false; + public RequestLeaseSetJob(RouterContext ctx, ClientConnectionRunner runner, LeaseRequestState state) { super(ctx); _log = ctx.logManager().getLog(RequestLeaseSetJob.class); @@ -44,22 +53,51 @@ class RequestLeaseSetJob extends JobImpl { public void runJob() { if (_runner.isDead()) return; - RequestLeaseSetMessage msg = new RequestLeaseSetMessage(); - long endTime = _requestState.getRequested().getEarliestLeaseDate(); - // Add a small number of ms (0-300) that increases as we approach the expire time. + LeaseSet requested = _requestState.getRequested(); + long endTime = requested.getEarliestLeaseDate(); + // Add a small number of ms (0 to MAX_FUDGE) that increases as we approach the expire time. // Since the earliest date functions as a version number, // this will force the floodfill to flood each new version; // otherwise it won't if the earliest time hasn't changed. - long fudge = 300 - ((endTime - getContext().clock().now()) / 2000); + long fudge = MAX_FUDGE - ((endTime - getContext().clock().now()) / (10*60*1000 / MAX_FUDGE)); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Adding fudge " + fudge); endTime += fudge; - Date end = new Date(endTime); - msg.setEndDate(end); - msg.setSessionId(_runner.getSessionId()); - - for (int i = 0; i < _requestState.getRequested().getLeaseCount(); i++) { - msg.addEndpoint(_requestState.getRequested().getLease(i).getGateway(), - _requestState.getRequested().getLease(i).getTunnelId()); + I2CPMessage msg; + if (getContext().getProperty(PROP_VARIABLE, DFLT_VARIABLE) && + (_runner instanceof QueuedClientConnectionRunner || + RequestVariableLeaseSetMessage.isSupported(_runner.getClientVersion()))) { + // new style - leases will have individual expirations + RequestVariableLeaseSetMessage rmsg = new RequestVariableLeaseSetMessage(); + rmsg.setSessionId(_runner.getSessionId()); + for (int i = 0; i < requested.getLeaseCount(); i++) { + Lease lease = requested.getLease(i); + if (lease.getEndDate().getTime() < endTime) { + // don't modify old object, we don't know where it came from + Lease nl = new Lease(); + nl.setGateway(lease.getGateway()); + nl.setTunnelId(lease.getTunnelId()); + nl.setEndDate(new Date(endTime)); + lease = nl; + //if (_log.shouldLog(Log.INFO)) + // _log.info("Adjusted end date to " + endTime + " for " + lease); + } + rmsg.addEndpoint(lease); + } + msg = rmsg; + } else { + // old style - all leases will have same expiration + RequestLeaseSetMessage rmsg = new RequestLeaseSetMessage(); + Date end = new Date(endTime); + rmsg.setEndDate(end); + rmsg.setSessionId(_runner.getSessionId()); + for (int i = 0; i < requested.getLeaseCount(); i++) { + Lease lease = requested.getLease(i); + rmsg.addEndpoint(lease.getGateway(), + lease.getTunnelId()); + } + msg = rmsg; } try { diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java index e62dcf9d2e..bae5b54f9a 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java @@ -304,7 +304,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl { // it (due to failure for example) we won't continue to use it. for (int i = 0; i < _leaseSet.getLeaseCount(); i++) { Lease lease = _leaseSet.getLease(i); - if (_lease.equals(lease)) { + // Don't use Lease.equals(), as that compares expiration time too, + // and that time may change in subsequent publication + //if (_lease.equals(lease)) { + if (_lease.getTunnelId().equals(lease.getTunnelId()) && + _lease.getGateway().equals(lease.getGateway())) { if (_log.shouldLog(Log.INFO)) _log.info(getJobId() + ": Found in cache - lease for " + _toString); return true; 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 00ea5c3b89..d8f3deaa61 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -96,10 +96,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { /** don't probe or broadcast data, just respond and search when explicitly needed */ private static final boolean QUIET = false; - public static final String PROP_ENFORCE_NETID = "router.networkDatabase.enforceNetId"; - private static final boolean DEFAULT_ENFORCE_NETID = false; - private boolean _enforceNetId = DEFAULT_ENFORCE_NETID; - public final static String PROP_DB_DIR = "router.networkDatabase.dbDir"; public final static String DEFAULT_DB_DIR = "netDb"; @@ -143,7 +139,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { _peerSelector = createPeerSelector(); _publishingLeaseSets = new HashMap(8); _activeRequests = new HashMap(8); - _enforceNetId = DEFAULT_ENFORCE_NETID; _reseedChecker = new ReseedChecker(context); context.statManager().createRateStat("netDb.lookupDeferred", "how many lookups are deferred?", "NetworkDatabase", new long[] { 60*60*1000 }); context.statManager().createRateStat("netDb.exploreKeySet", "how many keys are queued for exploration?", "NetworkDatabase", new long[] { 60*60*1000 }); @@ -223,11 +218,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { _log.info("No DB dir specified [" + PROP_DB_DIR + "], using [" + DEFAULT_DB_DIR + "]"); _dbDir = DEFAULT_DB_DIR; } - String enforce = _context.getProperty(PROP_ENFORCE_NETID); - if (enforce != null) - _enforceNetId = Boolean.parseBoolean(enforce); - else - _enforceNetId = DEFAULT_ENFORCE_NETID; _ds.restart(); _exploreKeys.clear(); @@ -249,12 +239,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { _log.info("Starting up the kademlia network database"); RouterInfo ri = _context.router().getRouterInfo(); String dbDir = _context.getProperty(PROP_DB_DIR, DEFAULT_DB_DIR); - String enforce = _context.getProperty(PROP_ENFORCE_NETID); - if (enforce != null) - _enforceNetId = Boolean.parseBoolean(enforce); - else - _enforceNetId = DEFAULT_ENFORCE_NETID; - _kb = new KBucketSet(_context, ri.getIdentity().getHash()); try { _ds = new PersistentDataStore(_context, dbDir, this); @@ -443,7 +427,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { fail(key); } else if (rv.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) { try { - if (validate(key, (RouterInfo)rv) == null) + if (validate((RouterInfo)rv) == null) return rv; } catch (IllegalArgumentException iae) {} fail(key); @@ -512,7 +496,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { // startup allows some lax rules). boolean valid = true; try { - valid = (null == validate(key, (RouterInfo)ds)); + valid = (null == validate((RouterInfo)ds)); } catch (IllegalArgumentException iae) { valid = false; } @@ -531,6 +515,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { } private static final long PUBLISH_DELAY = 3*1000; + public void publish(LeaseSet localLeaseSet) { if (!_initialized) return; Hash h = localLeaseSet.getDestination().calculateHash(); @@ -564,6 +549,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { // remove first since queue is a TreeSet now... _context.jobQueue().removeJob(j); j.getTiming().setStartAfter(nextTime); + if (_log.shouldLog(Log.INFO)) + _log.info("Queueing to publish at " + (new Date(nextTime)) + ' ' + localLeaseSet); _context.jobQueue().addJob(j); } @@ -627,34 +614,46 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { * Determine whether this leaseSet will be accepted as valid and current * given what we know now. * - * TODO this is called several times, only check the key and signature once + * Unlike for RouterInfos, this is only called once, when stored. + * After that, LeaseSet.isCurrent() is used. * * @return reason why the entry is not valid, or null if it is valid */ - String validate(Hash key, LeaseSet leaseSet) { + private String validate(Hash key, LeaseSet leaseSet) { if (!key.equals(leaseSet.getDestination().calculateHash())) { if (_log.shouldLog(Log.ERROR)) _log.error("Invalid store attempt! key does not match leaseSet.destination! key = " + key + ", leaseSet = " + leaseSet); return "Key does not match leaseSet.destination - " + key.toBase64(); - } else if (!leaseSet.verifySignature()) { + } + if (!leaseSet.verifySignature()) { if (_log.shouldLog(Log.ERROR)) _log.error("Invalid leaseSet signature! leaseSet = " + leaseSet); return "Invalid leaseSet signature on " + leaseSet.getDestination().calculateHash().toBase64(); - } else if (leaseSet.getEarliestLeaseDate() <= _context.clock().now() - 2*Router.CLOCK_FUDGE_FACTOR) { - long age = _context.clock().now() - leaseSet.getEarliestLeaseDate(); + } + long earliest = leaseSet.getEarliestLeaseDate(); + long latest = leaseSet.getLatestLeaseDate(); + long now = _context.clock().now(); + if (earliest <= now - 2*Router.CLOCK_FUDGE_FACTOR || + // same as the isCurrent(Router.CLOCK_FUDGE_FACTOR) test in + // lookupLeaseSetLocally() + latest <= now - Router.CLOCK_FUDGE_FACTOR) { + long age = now - earliest; if (_log.shouldLog(Log.WARN)) _log.warn("Old leaseSet! not storing it: " - + leaseSet.getDestination().calculateHash().toBase64() - + " expires on " + new Date(leaseSet.getEarliestLeaseDate()), new Exception("Rejecting store")); + + leaseSet.getDestination().calculateHash() + + " first exp. " + new Date(earliest) + + " last exp. " + new Date(latest), + new Exception("Rejecting store")); return "Expired leaseSet for " + leaseSet.getDestination().calculateHash().toBase64() + " expired " + DataHelper.formatDuration(age) + " ago"; - } else if (leaseSet.getEarliestLeaseDate() > _context.clock().now() + (Router.CLOCK_FUDGE_FACTOR + MAX_LEASE_FUTURE)) { - long age = leaseSet.getEarliestLeaseDate() - _context.clock().now(); + } + if (latest > now + (Router.CLOCK_FUDGE_FACTOR + MAX_LEASE_FUTURE)) { + long age = latest - now; // let's not make this an error, it happens when peers have bad clocks if (_log.shouldLog(Log.WARN)) _log.warn("LeaseSet expires too far in the future: " - + leaseSet.getDestination().calculateHash().toBase64() + + leaseSet.getDestination().calculateHash() + " expires " + DataHelper.formatDuration(age) + " from now"); return "Future expiring leaseSet for " + leaseSet.getDestination().calculateHash() + " expiring in " + DataHelper.formatDuration(age); @@ -720,11 +719,40 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { * Determine whether this routerInfo will be accepted as valid and current * given what we know now. * - * TODO this is called several times, only check the key and signature once + * Call this only on first store, to check the key and signature once * * @return reason why the entry is not valid, or null if it is valid */ - String validate(Hash key, RouterInfo routerInfo) throws IllegalArgumentException { + private String validate(Hash key, RouterInfo routerInfo) throws IllegalArgumentException { + if (!key.equals(routerInfo.getIdentity().getHash())) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid store attempt! key does not match routerInfo.identity! key = " + key + ", router = " + routerInfo); + return "Key does not match routerInfo.identity"; + } + if (!routerInfo.isValid()) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo); + return "Invalid routerInfo signature"; + } + if (routerInfo.getNetworkId() != Router.NETWORK_ID){ + _context.banlist().banlistRouter(key, "Not in our network"); + if (_log.shouldLog(Log.WARN)) + _log.warn("Bad network: " + routerInfo); + return "Not in our network"; + } + return validate(routerInfo); + } + + /** + * Determine whether this routerInfo will be accepted as valid and current + * given what we know now. + * + * Call this before each use, to check expiration + * + * @return reason why the entry is not valid, or null if it is valid + * @since 0.9.7 + */ + private String validate(RouterInfo routerInfo) throws IllegalArgumentException { long now = _context.clock().now(); boolean upLongEnough = _context.router().getUptime() > 60*60*1000; // Once we're over MIN_ROUTERS routers, reduce the expiration time down from the default, @@ -743,59 +771,44 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { ROUTER_INFO_EXPIRATION_MIN + ((ROUTER_INFO_EXPIRATION - ROUTER_INFO_EXPIRATION_MIN) * MIN_ROUTERS / (_kb.size() + 1))); - if (!key.equals(routerInfo.getIdentity().getHash())) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Invalid store attempt! key does not match routerInfo.identity! key = " + key + ", router = " + routerInfo); - return "Key does not match routerInfo.identity - " + key.toBase64(); - } else if (!routerInfo.isValid()) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo); - return "Invalid routerInfo signature on " + key.toBase64(); - } else if (upLongEnough && !routerInfo.isCurrent(adjustedExpiration)) { - if (routerInfo.getNetworkId() != Router.NETWORK_ID) { - _context.banlist().banlistRouter(key, "Peer is not in our network"); - return "Peer is not in our network (" + routerInfo.getNetworkId() + ", wants " - + Router.NETWORK_ID + "): " + routerInfo.calculateHash().toBase64(); - } + if (upLongEnough && !routerInfo.isCurrent(adjustedExpiration)) { long age = _context.clock().now() - routerInfo.getPublished(); int existing = _kb.size(); if (existing >= MIN_REMAINING_ROUTERS) { if (_log.shouldLog(Log.INFO)) - _log.info("Not storing expired router for " + key.toBase64(), new Exception("Rejecting store")); - return "Peer " + key.toBase64() + " expired " + DataHelper.formatDuration(age) + " ago"; + _log.info("Not storing expired RI " + routerInfo.getIdentity().getHash(), new Exception("Rejecting store")); + return "Peer expired " + DataHelper.formatDuration(age) + " ago"; } else { if (_log.shouldLog(Log.WARN)) _log.warn("Even though the peer is old, we have only " + existing - + " peers left (curPeer: " + key.toBase64() + " published on " - + new Date(routerInfo.getPublished())); + + " peers left " + routerInfo); } - } else if (routerInfo.getPublished() > now + 2*Router.CLOCK_FUDGE_FACTOR) { + } + if (routerInfo.getPublished() > now + 2*Router.CLOCK_FUDGE_FACTOR) { long age = routerInfo.getPublished() - _context.clock().now(); if (_log.shouldLog(Log.INFO)) - _log.info("Peer " + key.toBase64() + " published their routerInfo in the future?! [" + _log.info("Peer " + routerInfo.getIdentity().getHash() + " published their routerInfo in the future?! [" + new Date(routerInfo.getPublished()) + "]", new Exception("Rejecting store")); - return "Peer " + key.toBase64() + " published " + DataHelper.formatDuration(age) + " in the future?!"; - } else if (_enforceNetId && (routerInfo.getNetworkId() != Router.NETWORK_ID) ){ - String rv = "Peer " + key.toBase64() + " is from another network, not accepting it (id=" - + routerInfo.getNetworkId() + ", want " + Router.NETWORK_ID + ")"; - return rv; - } else if (upLongEnough && (routerInfo.getPublished() < now - 2*24*60*60*1000l) ) { + return "Peer published " + DataHelper.formatDuration(age) + " in the future?!"; + } + if (upLongEnough && (routerInfo.getPublished() < now - 2*24*60*60*1000l) ) { long age = _context.clock().now() - routerInfo.getPublished(); - return "Peer " + key.toBase64() + " published " + DataHelper.formatDuration(age) + " ago"; - } else if (upLongEnough && !routerInfo.isCurrent(ROUTER_INFO_EXPIRATION_SHORT)) { + return "Peer published " + DataHelper.formatDuration(age) + " ago"; + } + if (upLongEnough && !routerInfo.isCurrent(ROUTER_INFO_EXPIRATION_SHORT)) { if (routerInfo.getAddresses().isEmpty()) - return "Peer " + key.toBase64() + " published > 75m ago with no addresses"; + return "Peer published > 75m ago with no addresses"; // This should cover the introducers case below too // And even better, catches the case where the router is unreachable but knows no introducers if (routerInfo.getCapabilities().indexOf(Router.CAPABILITY_UNREACHABLE) >= 0) - return "Peer " + key.toBase64() + " published > 75m ago and thinks it is unreachable"; + return "Peer published > 75m ago and thinks it is unreachable"; RouterAddress ra = routerInfo.getTargetAddress("SSU"); if (ra != null) { // Introducers change often, introducee will ping introducer for 2 hours if (ra.getOption("ihost0") != null) - return "Peer " + key.toBase64() + " published > 75m ago with SSU Introducers"; + return "Peer published > 75m ago with SSU Introducers"; if (routerInfo.getTargetAddress("NTCP") == null) - return "Peer " + key.toBase64() + " published > 75m ago, SSU only without introducers"; + return "Peer published > 75m ago, SSU only without introducers"; } } return null; diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/RepublishLeaseSetJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/RepublishLeaseSetJob.java index 5d8d932807..237c28e390 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/RepublishLeaseSetJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/RepublishLeaseSetJob.java @@ -47,12 +47,12 @@ public class RepublishLeaseSetJob extends JobImpl { if (getContext().clientManager().isLocal(_dest)) { LeaseSet ls = _facade.lookupLeaseSetLocally(_dest); if (ls != null) { - if (_log.shouldLog(Log.INFO)) - _log.info("Client " + _dest + " is local, so we're republishing it"); if (!ls.isCurrent(Router.CLOCK_FUDGE_FACTOR)) { if (_log.shouldLog(Log.WARN)) _log.warn("Not publishing a LOCAL lease that isn't current - " + _dest, new Exception("Publish expired LOCAL lease?")); } else { + if (_log.shouldLog(Log.INFO)) + _log.info("Publishing " + ls); getContext().statManager().addRateData("netDb.republishLeaseSetCount", 1, 0); _facade.sendStore(_dest, ls, new OnRepublishSuccess(getContext()), new OnRepublishFailure(getContext(), this), REPUBLISH_LEASESET_TIMEOUT, null); _lastPublished = getContext().clock().now(); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/TransientDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/TransientDataStore.java index d3158798db..9f6ff888cf 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/TransientDataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/TransientDataStore.java @@ -81,9 +81,11 @@ class TransientDataStore implements DataStore { return Collections.unmodifiableSet(_data.entrySet()); } - /** for PersistentDataStore only - don't use here @throws IAE always */ + /** for PersistentDataStore only - don't use here + * @throws UnsupportedOperationException always + */ public DatabaseEntry get(Hash key, boolean persist) { - throw new IllegalArgumentException("no"); + throw new UnsupportedOperationException(); } public DatabaseEntry get(Hash key) { @@ -103,9 +105,11 @@ class TransientDataStore implements DataStore { return count; } - /** for PersistentDataStore only - don't use here @throws IAE always */ + /** for PersistentDataStore only - don't use here + * @throws UnsupportedOperationException always + */ public boolean put(Hash key, DatabaseEntry data, boolean persist) { - throw new IllegalArgumentException("no"); + throw new UnsupportedOperationException(); } /** @@ -116,8 +120,7 @@ class TransientDataStore implements DataStore { if (data == null) return false; if (_log.shouldLog(Log.DEBUG)) _log.debug("Storing key " + key); - DatabaseEntry old = null; - old = _data.putIfAbsent(key, data); + DatabaseEntry old = _data.putIfAbsent(key, data); boolean rv = false; if (data.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) { // Don't do this here so we don't reset it at router startup; @@ -128,13 +131,15 @@ class TransientDataStore implements DataStore { RouterInfo ori = (RouterInfo)old; if (ri.getPublished() < ori.getPublished()) { if (_log.shouldLog(Log.INFO)) - _log.info("Almost clobbered an old router! " + key + ": [old published on " + new Date(ori.getPublished()) + " new on " + new Date(ri.getPublished()) + "]"); + _log.info("Almost clobbered an old router! " + key + ": [old published on " + new Date(ori.getPublished()) + + " new on " + new Date(ri.getPublished()) + ']'); } else if (ri.getPublished() == ori.getPublished()) { if (_log.shouldLog(Log.INFO)) _log.info("Duplicate " + key); } else { if (_log.shouldLog(Log.INFO)) - _log.info("Updated the old router for " + key + ": [old published on " + new Date(ori.getPublished()) + " new on " + new Date(ri.getPublished()) + "]"); + _log.info("Updated the old router for " + key + ": [old published on " + new Date(ori.getPublished()) + + " new on " + new Date(ri.getPublished()) + ']'); _data.put(key, data); rv = true; } @@ -149,13 +154,15 @@ class TransientDataStore implements DataStore { LeaseSet ols = (LeaseSet)old; if (ls.getEarliestLeaseDate() < ols.getEarliestLeaseDate()) { if (_log.shouldLog(Log.INFO)) - _log.info("Almost clobbered an old leaseSet! " + key + ": [old published on " + new Date(ols.getEarliestLeaseDate()) + " new on " + new Date(ls.getEarliestLeaseDate()) + "]"); + _log.info("Almost clobbered an old leaseSet! " + key + ": [old expires " + new Date(ols.getEarliestLeaseDate()) + + " new on " + new Date(ls.getEarliestLeaseDate()) + ']'); } else if (ls.getEarliestLeaseDate() == ols.getEarliestLeaseDate()) { if (_log.shouldLog(Log.INFO)) _log.info("Duplicate " + key); } else { if (_log.shouldLog(Log.INFO)) { - _log.info("Updated old leaseSet " + key + ": [old published on " + new Date(ols.getEarliestLeaseDate()) + " new on " + new Date(ls.getEarliestLeaseDate()) + "]"); + _log.info("Updated old leaseSet " + key + ": [old expires " + new Date(ols.getEarliestLeaseDate()) + + " new on " + new Date(ls.getEarliestLeaseDate()) + ']'); if (_log.shouldLog(Log.DEBUG)) _log.debug("RAP? " + ls.getReceivedAsPublished() + " RAR? " + ls.getReceivedAsReply()); } @@ -164,7 +171,7 @@ class TransientDataStore implements DataStore { } } else { if (_log.shouldLog(Log.INFO)) { - _log.info("New leaseset for " + key + ": published on " + new Date(ls.getEarliestLeaseDate())); + _log.info("New leaseset for " + key + ": expires " + new Date(ls.getEarliestLeaseDate())); if (_log.shouldLog(Log.DEBUG)) _log.debug("RAP? " + ls.getReceivedAsPublished() + " RAR? " + ls.getReceivedAsReply()); } @@ -187,9 +194,11 @@ class TransientDataStore implements DataStore { return buf.toString(); } - /** for PersistentDataStore only - don't use here */ + /** for PersistentDataStore only - don't use here + * @throws UnsupportedOperationException always + */ public DatabaseEntry remove(Hash key, boolean persist) { - throw new IllegalArgumentException("no"); + throw new UnsupportedOperationException(); } public DatabaseEntry remove(Hash key) { diff --git a/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java b/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java index 61f4a87fdd..11b96b1839 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java +++ b/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java @@ -16,6 +16,9 @@ class ExpireJob extends JobImpl { private boolean _leaseUpdated; private final long _dropAfter; + private static final long OB_EARLY_EXPIRE = 30*1000; + private static final long IB_EARLY_EXPIRE = OB_EARLY_EXPIRE + 7500; + public ExpireJob(RouterContext ctx, TunnelCreatorConfig cfg, TunnelPool pool) { super(ctx); _pool = pool; @@ -27,9 +30,11 @@ class ExpireJob extends JobImpl { // Also skew the inbound away from the outbound long expire = cfg.getExpiration(); _dropAfter = expire + Router.CLOCK_FUDGE_FACTOR; - expire -= ctx.random().nextLong(60*1000); if (_pool.getSettings().isInbound()) - expire -= ctx.random().nextLong(15*1000); + expire -= IB_EARLY_EXPIRE + ctx.random().nextLong(IB_EARLY_EXPIRE); + else + expire -= OB_EARLY_EXPIRE + ctx.random().nextLong(OB_EARLY_EXPIRE); + // See comments in TunnelPool.locked_buildNewLeaseSet cfg.setExpiration(expire); getTiming().setStartAfter(expire); } @@ -42,6 +47,7 @@ class ExpireJob extends JobImpl { if (!_leaseUpdated) { _pool.removeTunnel(_cfg); _leaseUpdated = true; + // noop for outbound _pool.refreshLeaseSet(); long timeToDrop = _dropAfter - getContext().clock().now(); requeue(timeToDrop); diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java index 9e73535e45..c62e9a81dd 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java @@ -18,6 +18,7 @@ import net.i2p.router.RouterContext; import net.i2p.router.TunnelInfo; import net.i2p.router.TunnelPoolSettings; import net.i2p.router.tunnel.HopConfig; +import net.i2p.router.tunnel.TunnelCreatorConfig; import net.i2p.stat.Rate; import net.i2p.stat.RateAverages; import net.i2p.stat.RateStat; @@ -725,7 +726,13 @@ public class TunnelPool { continue; } Lease lease = new Lease(); - lease.setEndDate(new Date(tunnel.getExpiration())); + // bugfix + // ExpireJob reduces the expiration, which causes a 2nd leaseset with the same lease + // to have an earlier expiration, so it isn't stored. + // Get the "real" expiration from the gateway hop config, + // HopConfig expirations are the same as the "real" expiration and don't change + // see configureNewTunnel() + lease.setEndDate(new Date(((TunnelCreatorConfig)tunnel).getConfig(0).getExpiration())); lease.setTunnelId(inId); lease.setGateway(gw); leases.add(lease); @@ -1131,6 +1138,7 @@ public class TunnelPool { // tunnelIds will be updated during building, and as the creator, we // don't need to worry about prev/next hop } + // note that this will be adjusted by expire job cfg.setExpiration(expiration); if (!settings.isInbound()) cfg.setPriority(settings.getPriority());