diff --git a/core/java/src/net/i2p/data/LeaseSet2.java b/core/java/src/net/i2p/data/LeaseSet2.java index f545e037b..dbc5916fb 100644 --- a/core/java/src/net/i2p/data/LeaseSet2.java +++ b/core/java/src/net/i2p/data/LeaseSet2.java @@ -281,6 +281,21 @@ public class LeaseSet2 extends LeaseSet { log.warn("Don't set revocation key in ls2", new Exception("I did it")); } + /** + * Determine whether the leaseset is currently valid, at least within a given + * fudge factor. + * Overridden to use the expiration time instead of the last expiration. + * + * @param fudge milliseconds fudge factor to allow between the current time + * @return true if there are current leases, false otherwise + * @since 0.9.39 + */ + @Override + public boolean isCurrent(long fudge) { + long now = Clock.getInstance().now(); + return _expires > now - fudge; + } + /** without sig! */ @Override protected byte[] getBytes() { diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java index a0c9f49fe..150605aaa 100644 --- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java @@ -28,6 +28,7 @@ import net.i2p.client.I2PClient; import net.i2p.crypto.SessionKeyManager; import net.i2p.data.DatabaseEntry; import net.i2p.data.Destination; +import net.i2p.data.EncryptedLeaseSet; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; import net.i2p.data.Payload; @@ -99,6 +100,7 @@ class ClientConnectionRunner { /** For inbound traffic. true if i2cp.fastReceive = "true"; @since 0.9.4 */ private boolean _dontSendMSMOnReceive; private final AtomicInteger _messageId; // messageId counter + private Hash _encryptedLSHash; // Was 32767 since the beginning (04-2004). // But it's 4 bytes in the I2CP spec and stored as a long in MessageID.... @@ -125,7 +127,10 @@ class ClientConnectionRunner { SessionConfig config; LeaseRequestState leaseRequest; Rerequest rerequestTimer; + /** possibly decrypted */ LeaseSet currentLeaseSet; + /** only if encrypted */ + LeaseSet currentEncryptedLeaseSet; SessionParams(Destination d, boolean isPrimary) { dest = d; @@ -199,11 +204,17 @@ class ClientConnectionRunner { _acceptedPending.clear(); if (_sessionKeyManager != null) _sessionKeyManager.shutdown(); + if (_encryptedLSHash != null) + _manager.unregisterEncryptedDestination(this, _encryptedLSHash); _manager.unregisterConnection(this); // netdb may be null in unit tests if (_context.netDb() != null) { for (SessionParams sp : _sessions.values()) { LeaseSet ls = sp.currentLeaseSet; + if (ls != null) + _context.netDb().unpublish(ls); + // unpublish encrypted LS also + ls = sp.currentEncryptedLeaseSet; if (ls != null) _context.netDb().unpublish(ls); if (!sp.isPrimary) @@ -431,6 +442,10 @@ class ClientConnectionRunner { // Tell client manger _manager.unregisterSession(id, sp.dest); LeaseSet ls = sp.currentLeaseSet; + if (ls != null) + _context.netDb().unpublish(ls); + // unpublish encrypted LS also + ls = sp.currentEncryptedLeaseSet; if (ls != null) _context.netDb().unpublish(ls); isPrimary = sp.isPrimary; @@ -446,6 +461,10 @@ class ClientConnectionRunner { _log.info("Destroying remaining client subsession " + sp.sessionId); _manager.unregisterSession(sp.sessionId, sp.dest); LeaseSet ls = sp.currentLeaseSet; + if (ls != null) + _context.netDb().unpublish(ls); + // unpublish encrypted LS also + ls = sp.currentEncryptedLeaseSet; if (ls != null) _context.netDb().unpublish(ls); _context.tunnelManager().removeAlias(sp.dest); @@ -588,6 +607,8 @@ class ClientConnectionRunner { /** * called after a new leaseSet is granted by the client, the NetworkDb has been * updated. This takes care of all the LeaseRequestState stuff (including firing any jobs) + * + * @param ls, if encrypted, the encrypted LS, not the decrypted one */ void leaseSetCreated(LeaseSet ls) { Hash h = ls.getDestination().calculateHash(); @@ -596,6 +617,11 @@ class ClientConnectionRunner { return; LeaseRequestState state; synchronized (this) { + if (ls.getType() == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) { + EncryptedLeaseSet encls = (EncryptedLeaseSet) ls; + sp.currentEncryptedLeaseSet = encls; + ls = encls.getDecryptedLeaseSet(); + } sp.currentLeaseSet = ls; state = sp.leaseRequest; if (state == null) { @@ -615,6 +641,30 @@ class ClientConnectionRunner { if ( (state != null) && (state.getOnGranted() != null) ) _context.jobQueue().addJob(state.getOnGranted()); } + + /** + * Call after destinationEstablished(), + * when an encrypted leaseset is created, so we know it's local. + * Add to the clients list. Check for a dup hash. + * Caller must call runner.disconnectClient() on failure. + * + * @param hash the location of the encrypted LS, will change every day + * @return success, false on dup + * @since 0.9.39 + */ + public boolean registerEncryptedLS(Hash hash) { + boolean rv = true; + synchronized(this) { + if (!hash.equals(_encryptedLSHash)) { + if (_encryptedLSHash != null) + _manager.unregisterEncryptedDestination(this, _encryptedLSHash); + rv = _manager.registerEncryptedDestination(this, hash); + if (rv) + _encryptedLSHash = hash; + } + } + return rv; + } /** * Send a DisconnectMessage and log with level Log.ERROR. diff --git a/router/java/src/net/i2p/router/client/ClientManager.java b/router/java/src/net/i2p/router/client/ClientManager.java index d145dc2e2..26cad797f 100644 --- a/router/java/src/net/i2p/router/client/ClientManager.java +++ b/router/java/src/net/i2p/router/client/ClientManager.java @@ -272,7 +272,7 @@ class ClientManager { } } } - + /** * Remove only the following session. Does not remove the runner if it has more. * @@ -287,7 +287,21 @@ class ClientManager { _runnersByHash.remove(dest.calculateHash()); } } - + + /** + * Remove the hash for the encrypted LS. + * Call before unregisterConnection, or when the hash changes. + * + * @since 0.9.39 + */ + public void unregisterEncryptedDestination(ClientConnectionRunner runner, Hash hash) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Unregistering encrypted LS " + hash.toBase32()); + synchronized (_runners) { + _runnersByHash.remove(hash); + } + } + /** * Add to the clients list. Check for a dup destination. * Side effect: Sets the session ID of the runner. @@ -327,7 +341,32 @@ class ClientManager { } return rv; } - + + /** + * Call after destinationEstablished(), + * when an encrypted leaseset is created, so we know it's local. + * Add to the clients list. Check for a dup hash. + * Caller must call runner.disconnectClient() on failure. + * + * @param hash the location of the encrypted LS, will change every day + * @return success, false on dup + * @since 0.9.39 + */ + public boolean registerEncryptedDestination(ClientConnectionRunner runner, Hash hash) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("New encrypted LS " + hash.toBase32()); + + boolean rv; + synchronized (_runners) { + rv = !_runnersByHash.containsKey(hash); + if (rv) + _runnersByHash.put(hash, runner); + } + if (!rv) + _log.error("Encrypted dest collision " + hash.toBase32()); + return rv; + } + /** * Generate a new random, unused sessionId. Caller must synch on _runners. * @return null on failure diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java index 03793b627..d2a934067 100644 --- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java +++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java @@ -632,6 +632,17 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi } } try { + if (type == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) { + // so the client manager knows it's a local hash + // we could put this in runner.leaseSetCreated(), but + // need to set it before calling publish() + Hash newHash = ls.getHash(); + boolean ok = _runner.registerEncryptedLS(newHash); + if (!ok) { + _runner.disconnectClient("Duplicate hash of encrypted LS2"); + return; + } + } if (_log.shouldDebug()) _log.debug("Publishing: " + ls); _context.netDb().publish(ls); @@ -652,12 +663,7 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi _log.info("New lease set granted for destination " + dest); // leaseSetCreated takes care of all the LeaseRequestState stuff (including firing any jobs) - if (type == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) { - EncryptedLeaseSet encls = (EncryptedLeaseSet) ls; - _runner.leaseSetCreated(encls.getDecryptedLeaseSet()); - } else { - _runner.leaseSetCreated(ls); - } + _runner.leaseSetCreated(ls); } /** override for testing */ 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 4950b943d..968a1b6d7 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -1200,7 +1200,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad public void unpublish(LeaseSet localLeaseSet) { if (!_initialized) return; - Hash h = localLeaseSet.getDestination().calculateHash(); + Hash h = localLeaseSet.getHash(); DatabaseEntry data = _ds.remove(h); if (data == null) {