diff --git a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java index eca710227..85df4527a 100644 --- a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java @@ -105,6 +105,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2 private long _offlineExpiration; private Signature _offlineSignature; protected SigningPublicKey _transientSigningPublicKey; + private long _lastLS2SignTime; // subsession stuff // registered subsessions @@ -1185,6 +1186,21 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2 SessionId getSessionId() { return _sessionId; } void setSessionId(SessionId id) { _sessionId = id; } + /** + * The published timestamp of the last LS2 we signed + * + * @return 0 if never + * @since 0.9.64 + */ + long getLastLS2SignTime() { return _lastLS2SignTime; }; + + /** + * The published timestamp of the last LS2 we signed + * + * @since 0.9.64 + */ + void setLastLS2SignTime(long now) { _lastLS2SignTime = now; }; + /** configure the listener */ public void setSessionListener(I2PSessionListener lsnr) { _sessionListener = lsnr; } diff --git a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java index e8d6444b8..2bcff8f74 100644 --- a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java @@ -130,19 +130,25 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { boolean isLS2 = requiresLS2(session); LeaseSet leaseSet; if (isLS2) { + LeaseSet2 ls2; if (_ls2Type == DatabaseEntry.KEY_TYPE_LS2) { - leaseSet = new LeaseSet2(); + ls2 = new LeaseSet2(); } else if (_ls2Type == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) { - leaseSet = new EncryptedLeaseSet(); + ls2 = new EncryptedLeaseSet(); } else if (_ls2Type == DatabaseEntry.KEY_TYPE_META_LS2) { - leaseSet = new MetaLeaseSet(); + ls2= new MetaLeaseSet(); } else { session.propogateError("Unsupported LS2 type", new Exception()); session.destroySession(); return; } if (Boolean.parseBoolean(session.getOptions().getProperty("i2cp.dontPublishLeaseSet"))) - ((LeaseSet2)leaseSet).setUnpublished(); + ls2.setUnpublished(); + // ensure 1-second resolution timestamp is higher than last one + long now = Math.max(_context.clock().now(), session.getLastLS2SignTime() + 1000); + ls2.setPublished(now); + session.setLastLS2SignTime(now); + leaseSet = ls2; } else { leaseSet = new LeaseSet(); } diff --git a/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java index adeaccba1..dcec4dfef 100644 --- a/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java @@ -42,19 +42,25 @@ class RequestVariableLeaseSetMessageHandler extends RequestLeaseSetMessageHandle boolean isLS2 = requiresLS2(session); LeaseSet leaseSet; if (isLS2) { + LeaseSet2 ls2; if (_ls2Type == DatabaseEntry.KEY_TYPE_LS2) { - leaseSet = new LeaseSet2(); + ls2 = new LeaseSet2(); } else if (_ls2Type == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) { - leaseSet = new EncryptedLeaseSet(); + ls2 = new EncryptedLeaseSet(); } else if (_ls2Type == DatabaseEntry.KEY_TYPE_META_LS2) { - leaseSet = new MetaLeaseSet(); + ls2 = new MetaLeaseSet(); } else { session.propogateError("Unsupported LS2 type", new Exception()); session.destroySession(); return; } if (Boolean.parseBoolean(session.getOptions().getProperty("i2cp.dontPublishLeaseSet"))) - ((LeaseSet2)leaseSet).setUnpublished(); + ls2.setUnpublished(); + // ensure 1-second resolution timestamp is higher than last one + long now = Math.max(_context.clock().now(), session.getLastLS2SignTime() + 1000); + ls2.setPublished(now); + session.setLastLS2SignTime(now); + leaseSet = ls2; } else { leaseSet = new LeaseSet(); } diff --git a/core/java/src/net/i2p/data/EncryptedLeaseSet.java b/core/java/src/net/i2p/data/EncryptedLeaseSet.java index fe989607d..444b6af98 100644 --- a/core/java/src/net/i2p/data/EncryptedLeaseSet.java +++ b/core/java/src/net/i2p/data/EncryptedLeaseSet.java @@ -310,7 +310,7 @@ public class EncryptedLeaseSet extends LeaseSet2 { DataHelper.writeLong(out, 2, _signingKey.getType().getCode()); _signingKey.writeBytes(out); if (_published <= 0) - _published = Clock.getInstance().now(); + setPublished(Clock.getInstance().now()); DataHelper.writeLong(out, 4, _published / 1000); DataHelper.writeLong(out, 2, (_expires - _published) / 1000); DataHelper.writeLong(out, 2, _flags); diff --git a/core/java/src/net/i2p/data/LeaseSet.java b/core/java/src/net/i2p/data/LeaseSet.java index d16101705..0f96cdbc0 100644 --- a/core/java/src/net/i2p/data/LeaseSet.java +++ b/core/java/src/net/i2p/data/LeaseSet.java @@ -238,6 +238,9 @@ public class LeaseSet extends DatabaseEntry { * determine which LeaseSet was published more recently (later earliestLeaseSetDate * means it was published later) * + * Warning - do not use this for version comparison for LeaseSet2. + * Use LeaseSet2.getPublished() instead. + * * @return earliest end date of any lease in the set, or -1 if there are no leases */ public long getEarliestLeaseDate() { diff --git a/core/java/src/net/i2p/data/LeaseSet2.java b/core/java/src/net/i2p/data/LeaseSet2.java index c402c11e6..68629f4ca 100644 --- a/core/java/src/net/i2p/data/LeaseSet2.java +++ b/core/java/src/net/i2p/data/LeaseSet2.java @@ -66,6 +66,9 @@ public class LeaseSet2 extends LeaseSet { * Published timestamp, as received. * Different than getDate() or getEarliestLeaseDate(), which are the earliest lease expiration. * + * Use this for LS2 version comparison, NOT getEarliestLeaseDate(), because + * that will return -1 for EncryptedLS and MetaLS. + * * @return in ms, with 1 second resolution * @since 0.9.39 */ @@ -73,6 +76,18 @@ public class LeaseSet2 extends LeaseSet { return _published; } + /** + * Set published timestamp. + * Will be rounded to nearest second. + * If not called, will be set on write. + * + * @since 0.9.64 + */ + public void setPublished(long now) { + // we round it here, so comparisons during verifies aren't wrong + _published = ((now + 500) / 1000) * 1000; + } + /** * Published expiration, as received. * May be different than getLatestLeaseDate(), which is the latest lease expiration. @@ -524,8 +539,7 @@ public class LeaseSet2 extends LeaseSet { protected void writeHeader(OutputStream out) throws DataFormatException, IOException { _destination.writeBytes(out); if (_published <= 0) { - // we round it here, so comparisons during verifies aren't wrong - _published = ((Clock.getInstance().now() + 500) / 1000) * 1000; + setPublished(Clock.getInstance().now()); } long pub1k = _published / 1000; DataHelper.writeLong(out, 4, pub1k); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/DataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/DataStore.java index a7299358b..2129a4b3c 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/DataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/DataStore.java @@ -22,6 +22,17 @@ public interface DataStore { public DatabaseEntry get(Hash key, boolean persist); public boolean put(Hash key, DatabaseEntry data); public boolean put(Hash key, DatabaseEntry data, boolean persist); + + /* + * Unconditionally store, bypass all newer/older checks + * + * @return success + * @param key non-null + * @param data non-null + * @since 0.9.64 + */ + public boolean forcePut(Hash key, DatabaseEntry data); + public DatabaseEntry remove(Hash key); public DatabaseEntry remove(Hash key, boolean persist); public Set getKeys(); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/ExpireLeasesJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/ExpireLeasesJob.java index cea205d81..3462d4f07 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/ExpireLeasesJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/ExpireLeasesJob.java @@ -61,15 +61,18 @@ class ExpireLeasesJob extends JobImpl { * */ private List selectKeysToExpire() { + RouterContext ctx = getContext(); List toExpire = new ArrayList(128); for (Map.Entry entry : _facade.getDataStore().getMapEntries()) { DatabaseEntry obj = entry.getValue(); if (obj.isLeaseSet()) { LeaseSet ls = (LeaseSet)obj; - if (!ls.isCurrent(Router.CLOCK_FUDGE_FACTOR)) - toExpire.add(entry.getKey()); - else if (_log.shouldLog(Log.DEBUG)) - _log.debug("Lease " + ls.getDestination().calculateHash() + " is current, no need to expire"); + if (!ls.isCurrent(Router.CLOCK_FUDGE_FACTOR)) { + Hash h = entry.getKey(); + toExpire.add(h); + if (ctx.clientManager().isLocal(h)) + _log.logAlways(Log.WARN, "Expired local leaseset " + h.toBase32()); + } } } return toExpire; diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java index 9e857e959..166d3d37d 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java @@ -146,22 +146,11 @@ class HandleFloodfillDatabaseStoreMessageJob extends JobImpl { LeaseSet match = _facade.store(key, ls); if (match == null) { wasNew = true; - } else if (match.getEarliestLeaseDate() < ls.getEarliestLeaseDate()) { + } else if (KademliaNetworkDatabaseFacade.isNewer(ls, match)) { wasNew = true; // If it is in our keyspace and we are talking to it //if (match.getReceivedAsPublished()) // ls.setReceivedAsPublished(true); - } else if (type != DatabaseEntry.KEY_TYPE_LEASESET && - match.getType() != DatabaseEntry.KEY_TYPE_LEASESET) { - LeaseSet2 ls2 = (LeaseSet2) ls; - LeaseSet2 match2 = (LeaseSet2) match; - if (match2.getPublished() < ls2.getPublished()) { - wasNew = true; - //if (match.getReceivedAsPublished()) - // ls.setReceivedAsPublished(true); - } else { - wasNew = false; - } } else { wasNew = false; // The FloodOnlyLookupSelector goes away after the first good reply 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 80c35a900..3a8da28c1 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -881,6 +881,8 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad private static final long PUBLISH_DELAY = 3*1000; /** + * Stores in local netdb, and publishes to floodfill if client manager says to + * * @throws IllegalArgumentException if the leaseSet is not valid */ public void publish(LeaseSet localLeaseSet) throws IllegalArgumentException { @@ -891,7 +893,8 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad } Hash h = localLeaseSet.getHash(); try { - store(h, localLeaseSet); + // force overwrite of previous entry + store(h, localLeaseSet, true); } catch (IllegalArgumentException iae) { _log.error("locally published leaseSet is not valid?", iae); throw iae; @@ -1078,7 +1081,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad } return null; } - + /** * Store the leaseSet. * @@ -1090,12 +1093,28 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad * @return previous entry or null */ public LeaseSet store(Hash key, LeaseSet leaseSet) throws IllegalArgumentException { + return store(key, leaseSet, false); + } + + /** + * Store the leaseSet. + * + * If the store fails due to unsupported crypto, it will negative cache + * the hash until restart. + * + * @param force always store even if not newer + * @throws IllegalArgumentException if the leaseSet is not valid + * @throws UnsupportedCryptoException if that's why it failed. + * @return previous entry or null + * @since 0.9.64 + */ + public LeaseSet store(Hash key, LeaseSet leaseSet, boolean force) throws IllegalArgumentException { if (!_initialized) return null; LeaseSet rv; try { rv = (LeaseSet)_ds.get(key); - if (rv != null && rv.getEarliestLeaseDate() >= leaseSet.getEarliestLeaseDate()) { + if (rv != null && !force && !isNewer(leaseSet, rv)) { if (_log.shouldDebug()) _log.debug("Not storing older " + key); // if it hasn't changed, no need to do anything @@ -1117,7 +1136,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad // spoof / hash collision detection // todo allow non-exp to overwrite exp - if (rv != null) { + if (rv != null && !force) { Destination d1 = leaseSet.getDestination(); Destination d2 = rv.getDestination(); if (d1 != null && d2 != null && !d1.equals(d2)) @@ -1159,9 +1178,10 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad if (err != null) throw new IllegalArgumentException("Invalid store attempt - " + err); - if (_log.shouldDebug()) - _log.debug("Storing LS to the data store..."); - _ds.put(key, leaseSet); + if (force) + _ds.forcePut(key, leaseSet); + else + _ds.put(key, leaseSet); if (encls != null) { // we now have decrypted it, store it as well @@ -1205,6 +1225,24 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad return rv; } + + /** + * Utility to determine if a is newer than b. + * Uses publish date if a and b are both LS2, else earliest lease date. + * + * @param a non-null + * @param b non-null + * @return if a is newer than b + * @since 0.9.64 + */ + public static boolean isNewer(LeaseSet a, LeaseSet b) { + if (a.getType() != DatabaseEntry.KEY_TYPE_LEASESET && + b.getType() != DatabaseEntry.KEY_TYPE_LEASESET) { + return ((LeaseSet2) a).getPublished() > ((LeaseSet2) b).getPublished(); + } else { + return a.getEarliestLeaseDate() > b.getEarliestLeaseDate(); + } + } private static final int MIN_ROUTERS = 90; diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java index 014fd6af7..35484dc5b 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java @@ -164,6 +164,24 @@ public class PersistentDataStore extends TransientDataStore { _writer.queue(key, data); return rv; } + + /* + * Unconditionally store, bypass all newer/older checks. + * Persists for RI only. + * + * @param persist if false, call super only, don't access disk + * @return success + * @param key non-null + * @param data non-null + * @since 0.9.64 + */ + @Override + public boolean forcePut(Hash key, DatabaseEntry data) { + boolean rv = super.forcePut(key, data); + if (rv && data.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) + _writer.queue(key, data); + return rv; + } /** How many files to write every 10 minutes. Doesn't make sense to limit it, * they just back up in the queue hogging memory. @@ -329,10 +347,11 @@ public class PersistentDataStore extends TransientDataStore { if (fos != null) try { fos.close(); } catch (IOException ioe) {} } } - private long getPublishDate(DatabaseEntry data) { + + private static long getPublishDate(DatabaseEntry data) { return data.getDate(); } - + /** * This was mostly for manual reseeding, i.e. the user manually * copies RI files to the directory. Nobody does this, 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 46a668f9b..8627de471 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/TransientDataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/TransientDataStore.java @@ -190,6 +190,20 @@ class TransientDataStore implements DataStore { } return rv; } + + /* + * Unconditionally store, bypass all newer/older checks + * + * @return success + * @param key non-null + * @param data non-null + * @since 0.9.64 + */ + @Override + public boolean forcePut(Hash key, DatabaseEntry data) { + _data.put(key, data); + return true; + } @Override public String toString() {