forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p' (head 1de143fff53bb56e6eac926d6293d62200f0c392)
to branch 'i2p.i2p.zzz.multisess' (head 70fc07857232668b93ca6ba02c433dffc7639132)
This commit is contained in:
@ -146,6 +146,21 @@ public interface TunnelManagerFacade extends Service {
|
||||
*
|
||||
*/
|
||||
public void buildTunnels(Destination client, ClientTunnelSettings settings);
|
||||
|
||||
/**
|
||||
* Add another destination to the same tunnels.
|
||||
* Must have same encryption key an a different signing key.
|
||||
* @throws IllegalArgumentException if not
|
||||
* @return success
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public boolean addAlias(Destination dest, ClientTunnelSettings settings, Destination existingClient);
|
||||
|
||||
/**
|
||||
* Remove another destination to the same tunnels.
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public void removeAlias(Destination dest);
|
||||
|
||||
public TunnelPoolSettings getInboundSettings();
|
||||
public TunnelPoolSettings getOutboundSettings();
|
||||
|
@ -1,11 +1,13 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.util.SystemVersion;
|
||||
@ -31,6 +33,8 @@ public class TunnelPoolSettings {
|
||||
private final Properties _unknownOptions;
|
||||
private Hash _randomKey;
|
||||
private int _priority;
|
||||
private final Set<Hash> _aliases;
|
||||
private Hash _aliasOf;
|
||||
|
||||
/** prefix used to override the router's defaults for clients */
|
||||
// unimplemented
|
||||
@ -119,6 +123,10 @@ public class TunnelPoolSettings {
|
||||
_randomKey = generateRandomKey();
|
||||
if (_isExploratory && !_isInbound)
|
||||
_priority = EXPLORATORY_PRIORITY;
|
||||
if (!_isExploratory)
|
||||
_aliases = new ConcurrentHashSet<Hash>(4);
|
||||
else
|
||||
_aliases = null;
|
||||
}
|
||||
|
||||
/** how many tunnels should be available at all times */
|
||||
@ -206,6 +214,34 @@ public class TunnelPoolSettings {
|
||||
|
||||
/** what destination is this a client tunnel for (or null if exploratory) */
|
||||
public Hash getDestination() { return _destination; }
|
||||
|
||||
/**
|
||||
* Other destinations that use the same tunnel (or null if exploratory)
|
||||
* Modifiable, concurrent, not a copy
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public Set<Hash> getAliases() {
|
||||
return _aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Other destination that this is an alias of (or null).
|
||||
* If non-null, don't build tunnels.
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public Hash getAliasOf() {
|
||||
return _aliasOf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set other destination that this is an alias of (or null).
|
||||
* If non-null, don't build tunnels.
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public void setAliasOf(Hash h) {
|
||||
_aliasOf = h;
|
||||
}
|
||||
|
||||
/**
|
||||
* random key used for peer ordering
|
||||
@ -235,7 +271,7 @@ public class TunnelPoolSettings {
|
||||
public int getPriority() { return _priority; }
|
||||
|
||||
public Properties getUnknownOptions() { return _unknownOptions; }
|
||||
|
||||
|
||||
/**
|
||||
* Defaults in props are NOT honored.
|
||||
* In-JVM client side must promote defaults to the primary map.
|
||||
|
@ -16,6 +16,7 @@ import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -39,6 +40,7 @@ import net.i2p.data.i2cp.SendMessageMessage;
|
||||
import net.i2p.data.i2cp.SendMessageExpiresMessage;
|
||||
import net.i2p.data.i2cp.SessionConfig;
|
||||
import net.i2p.data.i2cp.SessionId;
|
||||
import net.i2p.data.i2cp.SessionStatusMessage;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
@ -51,6 +53,9 @@ import net.i2p.util.SimpleTimer;
|
||||
/**
|
||||
* Bridge the router and the client - managing state for a client.
|
||||
*
|
||||
* As of release 0.9.19, multiple sessions are supported on a single
|
||||
* I2CP connection. These sessions share tunnels and some configuration.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
class ClientConnectionRunner {
|
||||
@ -61,21 +66,16 @@ class ClientConnectionRunner {
|
||||
private final Socket _socket;
|
||||
/** output stream of the socket that I2CP messages bound to the client should be written to */
|
||||
private OutputStream _out;
|
||||
/** session ID of the current client */
|
||||
private SessionId _sessionId;
|
||||
/** user's config */
|
||||
private SessionConfig _config;
|
||||
|
||||
private final ConcurrentHashMap<Hash, SessionParams> _sessions;
|
||||
|
||||
private String _clientVersion;
|
||||
/**
|
||||
* Mapping of MessageId to Payload, storing messages for retrieval.
|
||||
* Unused for i2cp.fastReceive = "true" (_dontSendMSMOnRecive = true)
|
||||
*/
|
||||
private final Map<MessageId, Payload> _messages;
|
||||
/** lease set request state, or null if there is no request pending on at the moment */
|
||||
private LeaseRequestState _leaseRequest;
|
||||
private int _consecutiveLeaseRequestFails;
|
||||
/** currently allocated leaseSet, or null if none is allocated */
|
||||
private LeaseSet _currentLeaseSet;
|
||||
/**
|
||||
* Set of messageIds created but not yet ACCEPTED.
|
||||
* Unused for i2cp.messageReliability = "none" (_dontSendMSM = true)
|
||||
@ -83,7 +83,7 @@ class ClientConnectionRunner {
|
||||
private final Set<MessageId> _acceptedPending;
|
||||
/** thingy that does stuff */
|
||||
protected I2CPMessageReader _reader;
|
||||
/** just for this destination */
|
||||
/** Used for all sessions, which must all have the same crypto keys */
|
||||
private SessionKeyManager _sessionKeyManager;
|
||||
/**
|
||||
* This contains the last 10 MessageIds that have had their (non-ack) status
|
||||
@ -91,7 +91,6 @@ class ClientConnectionRunner {
|
||||
*/
|
||||
private final List<MessageId> _alreadyProcessed;
|
||||
private ClientWriterRunner _writer;
|
||||
private Hash _destHashCache;
|
||||
/** are we, uh, dead */
|
||||
private volatile boolean _dead;
|
||||
/** For outbound traffic. true if i2cp.messageReliability = "none"; @since 0.8.1 */
|
||||
@ -108,11 +107,30 @@ class ClientConnectionRunner {
|
||||
|
||||
private static final int MAX_LEASE_FAILS = 5;
|
||||
private static final int BUF_SIZE = 32*1024;
|
||||
private static final int MAX_SESSIONS = 4;
|
||||
|
||||
/** @since 0.9.2 */
|
||||
private static final String PROP_TAGS = "crypto.tagsToSend";
|
||||
private static final String PROP_THRESH = "crypto.lowTagThreshold";
|
||||
|
||||
/**
|
||||
* For multisession
|
||||
* @since 0.9.19
|
||||
*/
|
||||
private static class SessionParams {
|
||||
final Destination dest;
|
||||
final boolean isPrimary;
|
||||
SessionId sessionId;
|
||||
SessionConfig config;
|
||||
LeaseRequestState leaseRequest;
|
||||
LeaseSet currentLeaseSet;
|
||||
|
||||
SessionParams(Destination d, boolean isPrimary) {
|
||||
dest = d;
|
||||
this.isPrimary = isPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new runner against the given socket
|
||||
*
|
||||
@ -124,6 +142,7 @@ class ClientConnectionRunner {
|
||||
_socket = socket;
|
||||
// unused for fastReceive
|
||||
_messages = new ConcurrentHashMap<MessageId, Payload>();
|
||||
_sessions = new ConcurrentHashMap<Hash, SessionParams>(4);
|
||||
_alreadyProcessed = new ArrayList<MessageId>();
|
||||
_acceptedPending = new ConcurrentHashSet<MessageId>();
|
||||
_messageId = new AtomicInteger(_context.random().nextInt());
|
||||
@ -168,8 +187,7 @@ class ClientConnectionRunner {
|
||||
// router may be null in unit tests
|
||||
if ((_context.router() == null || _context.router().isAlive()) &&
|
||||
_log.shouldWarn())
|
||||
_log.warn("Stop the I2CP connection! current leaseSet: "
|
||||
+ _currentLeaseSet, new Exception("Stop client connection"));
|
||||
_log.warn("Stop the I2CP connection!", new Exception("Stop client connection"));
|
||||
_dead = true;
|
||||
// we need these keys to unpublish the leaseSet
|
||||
if (_reader != null) _reader.stopReading();
|
||||
@ -180,21 +198,56 @@ class ClientConnectionRunner {
|
||||
if (_sessionKeyManager != null)
|
||||
_sessionKeyManager.shutdown();
|
||||
_manager.unregisterConnection(this);
|
||||
if (_currentLeaseSet != null)
|
||||
_context.netDb().unpublish(_currentLeaseSet);
|
||||
_leaseRequest = null;
|
||||
for (SessionParams sp : _sessions.values()) {
|
||||
LeaseSet ls = sp.currentLeaseSet;
|
||||
if (ls != null)
|
||||
_context.netDb().unpublish(ls);
|
||||
}
|
||||
synchronized (_alreadyProcessed) {
|
||||
_alreadyProcessed.clear();
|
||||
}
|
||||
//_config = null;
|
||||
//_manager = null;
|
||||
_sessions.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current client's config,
|
||||
* will be null before session is established
|
||||
* will be null if session not found
|
||||
* IS subsession aware.
|
||||
* @since 0.9.19 added hash param
|
||||
*/
|
||||
public SessionConfig getConfig() { return _config; }
|
||||
public SessionConfig getConfig(Hash h) {
|
||||
SessionParams sp = _sessions.get(h);
|
||||
if (sp == null)
|
||||
return null;
|
||||
return sp.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current client's config,
|
||||
* will be null if session not found
|
||||
* IS subsession aware.
|
||||
* @since 0.9.19 added id param
|
||||
*/
|
||||
public SessionConfig getConfig(SessionId id) {
|
||||
for (SessionParams sp : _sessions.values()) {
|
||||
if (id.equals(sp.sessionId))
|
||||
return sp.config;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary client's config,
|
||||
* will be null if session not set up
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public SessionConfig getPrimaryConfig() {
|
||||
for (SessionParams sp : _sessions.values()) {
|
||||
if (sp.isPrimary)
|
||||
return sp.config;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The client version.
|
||||
@ -216,41 +269,186 @@ class ClientConnectionRunner {
|
||||
/** current client's sessionkeymanager */
|
||||
public SessionKeyManager getSessionKeyManager() { return _sessionKeyManager; }
|
||||
|
||||
/** currently allocated leaseSet */
|
||||
public LeaseSet getLeaseSet() { return _currentLeaseSet; }
|
||||
void setLeaseSet(LeaseSet ls) { _currentLeaseSet = ls; }
|
||||
/**
|
||||
* Currently allocated leaseSet.
|
||||
* IS subsession aware. Returns primary leaseset only.
|
||||
* @return leaseSet or null if not yet set or unknown hash
|
||||
* @since 0.9.19 added hash parameter
|
||||
*/
|
||||
public LeaseSet getLeaseSet(Hash h) {
|
||||
SessionParams sp = _sessions.get(h);
|
||||
if (sp == null)
|
||||
return null;
|
||||
return sp.currentLeaseSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently allocated leaseSet.
|
||||
* IS subsession aware.
|
||||
*/
|
||||
void setLeaseSet(LeaseSet ls) {
|
||||
Hash h = ls.getDestination().calculateHash();
|
||||
SessionParams sp = _sessions.get(h);
|
||||
if (sp == null)
|
||||
return;
|
||||
sp.currentLeaseSet = ls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to getConfig().getDestination().calculateHash();
|
||||
* will be null before session is established
|
||||
* Not subsession aware. Returns random hash from the sessions.
|
||||
* Don't use if you can help it.
|
||||
*
|
||||
* @return primary hash or null if not yet set
|
||||
*/
|
||||
public Hash getDestHash() { return _destHashCache; }
|
||||
public Hash getDestHash() {
|
||||
for (Hash h : _sessions.keySet()) {
|
||||
return h;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash for the given ID
|
||||
* @return hash or null if unknown
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public Hash getDestHash(SessionId id) {
|
||||
for (Map.Entry<Hash, SessionParams> e : _sessions.entrySet()) {
|
||||
if (id.equals(e.getValue().sessionId))
|
||||
return e.getKey();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dest for the given ID
|
||||
* @return dest or null if unknown
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public Destination getDestination(SessionId id) {
|
||||
for (SessionParams sp : _sessions.values()) {
|
||||
if (id.equals(sp.sessionId))
|
||||
return sp.dest;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current client's sessionId or null if not yet set
|
||||
* Subsession aware.
|
||||
*
|
||||
* @param h the local target
|
||||
* @return current client's sessionId or null if not yet set or not a valid hash
|
||||
* @since 0.9.19
|
||||
*/
|
||||
SessionId getSessionId() { return _sessionId; }
|
||||
SessionId getSessionId(Hash h) {
|
||||
SessionParams sp = _sessions.get(h);
|
||||
if (sp == null)
|
||||
return null;
|
||||
return sp.sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subsession aware.
|
||||
*
|
||||
* @return all current client's sessionIds, non-null
|
||||
* @since 0.9.19
|
||||
*/
|
||||
List<SessionId> getSessionIds() {
|
||||
List<SessionId> rv = new ArrayList<SessionId>(_sessions.size());
|
||||
for (SessionParams sp : _sessions.values()) {
|
||||
SessionId id = sp.sessionId;
|
||||
if (id != null)
|
||||
rv.add(id);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subsession aware.
|
||||
*
|
||||
* @return all current client's destinations, non-null
|
||||
* @since 0.9.19
|
||||
*/
|
||||
List<Destination> getDestinations() {
|
||||
List<Destination> rv = new ArrayList<Destination>(_sessions.size());
|
||||
for (SessionParams sp : _sessions.values()) {
|
||||
rv.add(sp.dest);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called only by ClientManager.
|
||||
*
|
||||
* @param hash for the session
|
||||
* @throws IllegalStateException if already set
|
||||
* @since 0.9.19 added hash param
|
||||
*/
|
||||
void setSessionId(SessionId id) {
|
||||
if (_sessionId != null)
|
||||
void setSessionId(Hash hash, SessionId id) {
|
||||
if (hash == null)
|
||||
throw new IllegalStateException();
|
||||
_sessionId = id;
|
||||
SessionParams sp = _sessions.get(hash);
|
||||
if (sp == null || sp.sessionId != null)
|
||||
throw new IllegalStateException();
|
||||
sp.sessionId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the session. Caller must kill runner if none left.
|
||||
*
|
||||
* @since 0.9.19
|
||||
*/
|
||||
void removeSession(SessionId id) {
|
||||
boolean isPrimary = false;
|
||||
for (Iterator<SessionParams> iter = _sessions.values().iterator(); iter.hasNext(); ) {
|
||||
SessionParams sp = iter.next();
|
||||
if (id.equals(sp.sessionId)) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Destroying client session " + id);
|
||||
iter.remove();
|
||||
// Tell client manger
|
||||
_manager.unregisterSession(id, sp.dest);
|
||||
LeaseSet ls = sp.currentLeaseSet;
|
||||
if (ls != null)
|
||||
_context.netDb().unpublish(ls);
|
||||
isPrimary = sp.isPrimary;
|
||||
}
|
||||
}
|
||||
if (isPrimary) {
|
||||
// kill all the others also
|
||||
for (SessionParams sp : _sessions.values()) {
|
||||
_manager.unregisterSession(id, sp.dest);
|
||||
LeaseSet ls = sp.currentLeaseSet;
|
||||
if (ls != null)
|
||||
_context.netDb().unpublish(ls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** data for the current leaseRequest, or null if there is no active leaseSet request */
|
||||
LeaseRequestState getLeaseRequest() { return _leaseRequest; }
|
||||
/**
|
||||
* Data for the current leaseRequest, or null if there is no active leaseSet request.
|
||||
* Not subsession aware. Returns primary ID only.
|
||||
* @since 0.9.19 added hash param
|
||||
*/
|
||||
LeaseRequestState getLeaseRequest(Hash h) {
|
||||
SessionParams sp = _sessions.get(h);
|
||||
if (sp == null)
|
||||
return null;
|
||||
return sp.leaseRequest;
|
||||
}
|
||||
|
||||
/** @param req non-null */
|
||||
public void failLeaseRequest(LeaseRequestState req) {
|
||||
boolean disconnect = false;
|
||||
Hash h = req.getRequested().getDestination().calculateHash();
|
||||
SessionParams sp = _sessions.get(h);
|
||||
if (sp == null)
|
||||
return;
|
||||
synchronized (this) {
|
||||
if (_leaseRequest == req) {
|
||||
_leaseRequest = null;
|
||||
if (sp.leaseRequest == req) {
|
||||
sp.leaseRequest = null;
|
||||
disconnect = ++_consecutiveLeaseRequestFails > MAX_LEASE_FAILS;
|
||||
}
|
||||
}
|
||||
@ -291,19 +489,34 @@ class ClientConnectionRunner {
|
||||
* @return SessionStatusMessage return code, 1 for success, != 1 for failure
|
||||
*/
|
||||
public int sessionEstablished(SessionConfig config) {
|
||||
_destHashCache = config.getDestination().calculateHash();
|
||||
Destination dest = config.getDestination();
|
||||
Hash destHash = dest.calculateHash();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("SessionEstablished called for destination " + _destHashCache.toBase64());
|
||||
_config = config;
|
||||
_log.debug("SessionEstablished called for destination " + destHash);
|
||||
if (_sessions.size() > MAX_SESSIONS)
|
||||
return SessionStatusMessage.STATUS_REFUSED;
|
||||
boolean isPrimary = _sessions.isEmpty();
|
||||
if (!isPrimary) {
|
||||
// all encryption keys must be the same
|
||||
for (SessionParams sp : _sessions.values()) {
|
||||
if (!dest.getPublicKey().equals(sp.dest.getPublicKey()))
|
||||
return SessionStatusMessage.STATUS_INVALID;
|
||||
}
|
||||
}
|
||||
SessionParams sp = new SessionParams(dest, isPrimary);
|
||||
sp.config = config;
|
||||
SessionParams old = _sessions.putIfAbsent(destHash, sp);
|
||||
if (old != null)
|
||||
return SessionStatusMessage.STATUS_INVALID;
|
||||
// We process a few options here, but most are handled by the tunnel manager.
|
||||
// The ones here can't be changed later.
|
||||
Properties opts = config.getOptions();
|
||||
if (opts != null) {
|
||||
if (isPrimary && opts != null) {
|
||||
_dontSendMSM = "none".equals(opts.getProperty(I2PClient.PROP_RELIABILITY, "").toLowerCase(Locale.US));
|
||||
_dontSendMSMOnReceive = Boolean.parseBoolean(opts.getProperty(I2PClient.PROP_FAST_RECEIVE));
|
||||
}
|
||||
// per-destination session key manager to prevent rather easy correlation
|
||||
if (_sessionKeyManager == null) {
|
||||
if (isPrimary && _sessionKeyManager == null) {
|
||||
int tags = TransientSessionKeyManager.DEFAULT_TAGS;
|
||||
int thresh = TransientSessionKeyManager.LOW_THRESHOLD;
|
||||
if (opts != null) {
|
||||
@ -317,10 +530,8 @@ class ClientConnectionRunner {
|
||||
}
|
||||
}
|
||||
_sessionKeyManager = new TransientSessionKeyManager(_context, tags, thresh);
|
||||
} else {
|
||||
_log.error("SessionEstablished called for twice for destination " + _destHashCache.toBase64().substring(0,4));
|
||||
}
|
||||
return _manager.destinationEstablished(this);
|
||||
return _manager.destinationEstablished(this, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -331,14 +542,21 @@ class ClientConnectionRunner {
|
||||
*
|
||||
* Do not use for status = STATUS_SEND_ACCEPTED; use ackSendMessage() for that.
|
||||
*
|
||||
* @param dest the client
|
||||
* @param id the router's ID for this message
|
||||
* @param messageNonce the client's ID for this message
|
||||
* @param status see I2CP MessageStatusMessage for success/failure codes
|
||||
*/
|
||||
void updateMessageDeliveryStatus(MessageId id, long messageNonce, int status) {
|
||||
void updateMessageDeliveryStatus(Destination dest, MessageId id, long messageNonce, int status) {
|
||||
if (_dead || messageNonce <= 0)
|
||||
return;
|
||||
_context.jobQueue().addJob(new MessageDeliveryStatusUpdate(id, messageNonce, status));
|
||||
SessionParams sp = _sessions.get(dest.calculateHash());
|
||||
if (sp == null)
|
||||
return;
|
||||
SessionId sid = sp.sessionId;
|
||||
if (sid == null)
|
||||
return; // sid = new SessionId(foo) ???
|
||||
_context.jobQueue().addJob(new MessageDeliveryStatusUpdate(sid, id, messageNonce, status));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -346,19 +564,23 @@ class ClientConnectionRunner {
|
||||
* updated. This takes care of all the LeaseRequestState stuff (including firing any jobs)
|
||||
*/
|
||||
void leaseSetCreated(LeaseSet ls) {
|
||||
LeaseRequestState state = null;
|
||||
Hash h = ls.getDestination().calculateHash();
|
||||
SessionParams sp = _sessions.get(h);
|
||||
if (sp == null)
|
||||
return;
|
||||
LeaseRequestState state;
|
||||
synchronized (this) {
|
||||
state = _leaseRequest;
|
||||
state = sp.leaseRequest;
|
||||
if (state == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("LeaseRequest is null and we've received a new lease?! perhaps this is odd... " + ls);
|
||||
return;
|
||||
} else {
|
||||
state.setIsSuccessful(true);
|
||||
_currentLeaseSet = ls;
|
||||
setLeaseSet(ls);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("LeaseSet created fully: " + state + " / " + ls);
|
||||
_leaseRequest = null;
|
||||
sp.leaseRequest = null;
|
||||
_consecutiveLeaseRequestFails = 0;
|
||||
}
|
||||
}
|
||||
@ -427,12 +649,12 @@ class ClientConnectionRunner {
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("** Receiving message " + id.getMessageId() + " with payload of size "
|
||||
+ payload.getSize() + " for session " + _sessionId.getSessionId());
|
||||
+ payload.getSize() + " for session " + message.getSessionId());
|
||||
//long beforeDistribute = _context.clock().now();
|
||||
// the following blocks as described above
|
||||
SessionConfig cfg = _config;
|
||||
if (cfg != null)
|
||||
_manager.distributeMessage(cfg.getDestination(), dest, payload,
|
||||
Destination fromDest = getDestination(message.getSessionId());
|
||||
if (fromDest != null)
|
||||
_manager.distributeMessage(fromDest, dest, payload,
|
||||
id, message.getNonce(), expiration, flags);
|
||||
// else log error?
|
||||
//long timeToDistribute = _context.clock().now() - beforeDistribute;
|
||||
@ -452,11 +674,9 @@ class ClientConnectionRunner {
|
||||
* @param id OUR id for the message
|
||||
* @param nonce HIS id for the message
|
||||
*/
|
||||
void ackSendMessage(MessageId id, long nonce) {
|
||||
void ackSendMessage(SessionId sid, MessageId id, long nonce) {
|
||||
if (_dontSendMSM || nonce == 0)
|
||||
return;
|
||||
SessionId sid = _sessionId;
|
||||
if (sid == null) return;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Acking message send [accepted]" + id + " / " + nonce + " for sessionId "
|
||||
+ sid);
|
||||
@ -480,6 +700,9 @@ class ClientConnectionRunner {
|
||||
*
|
||||
* Note that no failure indication is available.
|
||||
* Fails silently on e.g. queue overflow to client, client dead, etc.
|
||||
*
|
||||
* @param toDest non-null
|
||||
* @param fromDest generally null when from remote, non-null if from local
|
||||
*/
|
||||
void receiveMessage(Destination toDest, Destination fromDest, Payload payload) {
|
||||
if (_dead) return;
|
||||
@ -489,13 +712,33 @@ class ClientConnectionRunner {
|
||||
j.runJob();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously deliver the message to the current runner
|
||||
*
|
||||
* Note that no failure indication is available.
|
||||
* Fails silently on e.g. queue overflow to client, client dead, etc.
|
||||
*
|
||||
* @param toHash non-null
|
||||
* @param fromDest generally null when from remote, non-null if from local
|
||||
* @since 0.9.20
|
||||
*/
|
||||
void receiveMessage(Hash toHash, Destination fromDest, Payload payload) {
|
||||
SessionParams sp = _sessions.get(toHash);
|
||||
if (sp == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No session found for receiveMessage()");
|
||||
return;
|
||||
}
|
||||
receiveMessage(sp.dest, fromDest, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send async abuse message to the client
|
||||
*
|
||||
*/
|
||||
public void reportAbuse(String reason, int severity) {
|
||||
public void reportAbuse(Destination dest, String reason, int severity) {
|
||||
if (_dead) return;
|
||||
_context.jobQueue().addJob(new ReportAbuseJob(_context, this, reason, severity));
|
||||
_context.jobQueue().addJob(new ReportAbuseJob(_context, this, dest, reason, severity));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -504,13 +747,15 @@ class ClientConnectionRunner {
|
||||
* within the timeout specified, queue up the onFailedJob. This call does not
|
||||
* block.
|
||||
*
|
||||
* @param h the Destination's hash
|
||||
* @param set LeaseSet with requested leases - this object must be updated to contain the
|
||||
* signed version (as well as any changed/added/removed Leases)
|
||||
* The LeaseSet contains Leases and destination only, it is unsigned.
|
||||
* @param expirationTime ms to wait before failing
|
||||
* @param onCreateJob Job to run after the LeaseSet is authorized, null OK
|
||||
* @param onFailedJob Job to run after the timeout passes without receiving authorization, null OK
|
||||
*/
|
||||
void requestLeaseSet(LeaseSet set, long expirationTime, Job onCreateJob, Job onFailedJob) {
|
||||
void requestLeaseSet(Hash h, LeaseSet set, long expirationTime, Job onCreateJob, Job onFailedJob) {
|
||||
if (_dead) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Requesting leaseSet from a dead client: " + set);
|
||||
@ -518,6 +763,12 @@ class ClientConnectionRunner {
|
||||
_context.jobQueue().addJob(onFailedJob);
|
||||
return;
|
||||
}
|
||||
SessionParams sp = _sessions.get(h);
|
||||
if (sp == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Requesting leaseSet for an unknown sesssion");
|
||||
return;
|
||||
}
|
||||
// We can't use LeaseSet.equals() here because the dest, keys, and sig on
|
||||
// the new LeaseSet are null. So we compare leases one by one.
|
||||
// In addition, the client rewrites the expiration time of all the leases to
|
||||
@ -528,12 +779,15 @@ class ClientConnectionRunner {
|
||||
// so the comparison will always work.
|
||||
int leases = set.getLeaseCount();
|
||||
// synch so _currentLeaseSet isn't changed out from under us
|
||||
LeaseSet current = null;
|
||||
Destination dest = sp.dest;
|
||||
synchronized (this) {
|
||||
if (_currentLeaseSet != null && _currentLeaseSet.getLeaseCount() == leases) {
|
||||
current = sp.currentLeaseSet;
|
||||
if (current != null && current.getLeaseCount() == leases) {
|
||||
for (int i = 0; i < leases; i++) {
|
||||
if (! _currentLeaseSet.getLease(i).getTunnelId().equals(set.getLease(i).getTunnelId()))
|
||||
if (! current.getLease(i).getTunnelId().equals(set.getLease(i).getTunnelId()))
|
||||
break;
|
||||
if (! _currentLeaseSet.getLease(i).getGateway().equals(set.getLease(i).getGateway()))
|
||||
if (! current.getLease(i).getGateway().equals(set.getLease(i).getGateway()))
|
||||
break;
|
||||
if (i == leases - 1) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -546,10 +800,10 @@ class ClientConnectionRunner {
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Current leaseSet " + _currentLeaseSet + "\nNew leaseSet " + set);
|
||||
LeaseRequestState state = null;
|
||||
_log.info("Current leaseSet " + current + "\nNew leaseSet " + set);
|
||||
LeaseRequestState state;
|
||||
synchronized (this) {
|
||||
state = _leaseRequest;
|
||||
state = sp.leaseRequest;
|
||||
if (state != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Already requesting " + state);
|
||||
@ -561,12 +815,15 @@ class ClientConnectionRunner {
|
||||
// theirs is newer
|
||||
} else {
|
||||
// ours is newer, so wait a few secs and retry
|
||||
set.setDestination(dest);
|
||||
_context.simpleTimer2().addEvent(new Rerequest(set, expirationTime, onCreateJob, onFailedJob), 3*1000);
|
||||
}
|
||||
// fire onCreated?
|
||||
return; // already requesting
|
||||
} else {
|
||||
_leaseRequest = state = new LeaseRequestState(onCreateJob, onFailedJob, _context.clock().now() + expirationTime, set);
|
||||
set.setDestination(dest);
|
||||
sp.leaseRequest = state = new LeaseRequestState(onCreateJob, onFailedJob,
|
||||
_context.clock().now() + expirationTime, set);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New request: " + state);
|
||||
}
|
||||
@ -580,6 +837,7 @@ class ClientConnectionRunner {
|
||||
private final Job _onCreate;
|
||||
private final Job _onFailed;
|
||||
|
||||
/** @param ls dest must be set */
|
||||
public Rerequest(LeaseSet ls, long expirationTime, Job onCreate, Job onFailed) {
|
||||
_ls = ls;
|
||||
_expirationTime = expirationTime;
|
||||
@ -588,7 +846,7 @@ class ClientConnectionRunner {
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
requestLeaseSet(_ls, _expirationTime, _onCreate, _onFailed);
|
||||
requestLeaseSet(_ls.getDestination().calculateHash(), _ls, _expirationTime, _onCreate, _onFailed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -697,6 +955,7 @@ class ClientConnectionRunner {
|
||||
private static final int MAX_REQUEUE = 60; // 30 sec.
|
||||
|
||||
private class MessageDeliveryStatusUpdate extends JobImpl {
|
||||
private final SessionId _sessId;
|
||||
private final MessageId _messageId;
|
||||
private final long _messageNonce;
|
||||
private final int _status;
|
||||
@ -710,8 +969,9 @@ class ClientConnectionRunner {
|
||||
* @param messageNonce the client's ID for this message
|
||||
* @param status see I2CP MessageStatusMessage for success/failure codes
|
||||
*/
|
||||
public MessageDeliveryStatusUpdate(MessageId id, long messageNonce, int status) {
|
||||
public MessageDeliveryStatusUpdate(SessionId sid, MessageId id, long messageNonce, int status) {
|
||||
super(ClientConnectionRunner.this._context);
|
||||
_sessId = sid;
|
||||
_messageId = id;
|
||||
_messageNonce = messageNonce;
|
||||
_status = status;
|
||||
@ -727,7 +987,7 @@ class ClientConnectionRunner {
|
||||
|
||||
MessageStatusMessage msg = new MessageStatusMessage();
|
||||
msg.setMessageId(_messageId.getMessageId());
|
||||
msg.setSessionId(_sessionId.getSessionId());
|
||||
msg.setSessionId(_sessId.getSessionId());
|
||||
// has to be >= 0, it is initialized to -1
|
||||
msg.setNonce(_messageNonce);
|
||||
msg.setSize(0);
|
||||
@ -738,12 +998,12 @@ class ClientConnectionRunner {
|
||||
// bug requeueing forever? failsafe
|
||||
_log.error("Abandon update for message " + _messageId + " to "
|
||||
+ MessageStatusMessage.getStatusString(msg.getStatus())
|
||||
+ " for session " + _sessionId.getSessionId());
|
||||
+ " for " + _sessId);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Almost send an update for message " + _messageId + " to "
|
||||
+ MessageStatusMessage.getStatusString(msg.getStatus())
|
||||
+ " for session " + _sessionId.getSessionId()
|
||||
+ " for " + _sessId
|
||||
+ " before they knew the messageId! delaying .5s");
|
||||
_lastTried = _context.clock().now();
|
||||
requeue(REQUEUE_DELAY);
|
||||
@ -778,14 +1038,14 @@ class ClientConnectionRunner {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.info("Updating message status for message " + _messageId + " to "
|
||||
+ MessageStatusMessage.getStatusString(msg.getStatus())
|
||||
+ " for session " + _sessionId.getSessionId()
|
||||
+ " for " + _sessId
|
||||
+ " (with nonce=2), retrying after "
|
||||
+ (_context.clock().now() - _lastTried));
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Updating message status for message " + _messageId + " to "
|
||||
+ MessageStatusMessage.getStatusString(msg.getStatus())
|
||||
+ " for session " + _sessionId.getSessionId() + " (with nonce=2)");
|
||||
+ " for " + _sessId + " (with nonce=2)");
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -55,9 +55,11 @@ class ClientManager {
|
||||
protected final List<ClientListenerRunner> _listeners;
|
||||
// Destination --> ClientConnectionRunner
|
||||
// Locked for adds/removes but not lookups
|
||||
// If a runner has multiple sessions it will be in here multiple times, one for each dest
|
||||
private final Map<Destination, ClientConnectionRunner> _runners;
|
||||
// Same as what's in _runners, but for fast lookup by Hash
|
||||
// Locked for adds/removes but not lookups
|
||||
// If a runner has multiple sessions it will be in here multiple times, one for each dest
|
||||
private final Map<Hash, ClientConnectionRunner> _runnersByHash;
|
||||
// ClientConnectionRunner for clients w/out a Dest yet
|
||||
private final Set<ClientConnectionRunner> _pendingRunners;
|
||||
@ -214,24 +216,44 @@ class ClientManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all sessions for this runner.
|
||||
*/
|
||||
public void unregisterConnection(ClientConnectionRunner runner) {
|
||||
_log.warn("Unregistering (dropping) a client connection");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unregistering (dropping) a client connection");
|
||||
synchronized (_pendingRunners) {
|
||||
_pendingRunners.remove(runner);
|
||||
}
|
||||
if ( (runner.getConfig() != null) && (runner.getConfig().getDestination() != null) ) {
|
||||
// after connection establishment
|
||||
Destination dest = runner.getConfig().getDestination();
|
||||
synchronized (_runners) {
|
||||
SessionId id = runner.getSessionId();
|
||||
if (id != null)
|
||||
_runnerSessionIds.remove(id);
|
||||
|
||||
List<SessionId> ids = runner.getSessionIds();
|
||||
List<Destination> dests = runner.getDestinations();
|
||||
synchronized (_runners) {
|
||||
for (SessionId id : ids) {
|
||||
_runnerSessionIds.remove(id);
|
||||
}
|
||||
for (Destination dest : dests) {
|
||||
_runners.remove(dest);
|
||||
_runnersByHash.remove(dest.calculateHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove only the following session. Does not remove the runner if it has more.
|
||||
*
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public void unregisterSession(SessionId id, Destination dest) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unregistering client session " + id);
|
||||
synchronized (_runners) {
|
||||
_runnerSessionIds.remove(id);
|
||||
_runners.remove(dest);
|
||||
_runnersByHash.remove(dest.calculateHash());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the clients list. Check for a dup destination.
|
||||
* Side effect: Sets the session ID of the runner.
|
||||
@ -239,8 +261,7 @@ class ClientManager {
|
||||
*
|
||||
* @return SessionStatusMessage return code, 1 for success, != 1 for failure
|
||||
*/
|
||||
public int destinationEstablished(ClientConnectionRunner runner) {
|
||||
Destination dest = runner.getConfig().getDestination();
|
||||
public int destinationEstablished(ClientConnectionRunner runner, Destination dest) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("DestinationEstablished called for destination " + dest.calculateHash().toBase64());
|
||||
|
||||
@ -255,9 +276,10 @@ class ClientManager {
|
||||
} else {
|
||||
SessionId id = locked_getNextSessionId();
|
||||
if (id != null) {
|
||||
runner.setSessionId(id);
|
||||
Hash h = dest.calculateHash();
|
||||
runner.setSessionId(h, id);
|
||||
_runners.put(dest, runner);
|
||||
_runnersByHash.put(dest.calculateHash(), runner);
|
||||
_runnersByHash.put(h, runner);
|
||||
rv = SessionStatusMessage.STATUS_CREATED;
|
||||
} else {
|
||||
rv = SessionStatusMessage.STATUS_REFUSED;
|
||||
@ -323,8 +345,11 @@ class ClientManager {
|
||||
// sender went away
|
||||
return;
|
||||
}
|
||||
ClientMessage msg = new ClientMessage(toDest, payload, runner.getConfig(),
|
||||
runner.getConfig().getDestination(), msgId,
|
||||
SessionConfig config = runner.getConfig(fromDest.calculateHash());
|
||||
if (config == null)
|
||||
return;
|
||||
ClientMessage msg = new ClientMessage(toDest, payload, config,
|
||||
fromDest, msgId,
|
||||
messageNonce, expiration, flags);
|
||||
_ctx.clientMessagePool().add(msg, true);
|
||||
}
|
||||
@ -362,7 +387,7 @@ class ClientManager {
|
||||
// note that receiveMessage() does not indicate a failure,
|
||||
// so a queue overflow is not recognized. we always return success.
|
||||
if (_from != null) {
|
||||
_from.updateMessageDeliveryStatus(_msgId, _messageNonce, MessageStatusMessage.STATUS_SEND_SUCCESS_LOCAL);
|
||||
_from.updateMessageDeliveryStatus(_fromDest, _msgId, _messageNonce, MessageStatusMessage.STATUS_SEND_SUCCESS_LOCAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -378,7 +403,8 @@ class ClientManager {
|
||||
*
|
||||
* @param dest Destination from which the LeaseSet's authorization should be requested
|
||||
* @param set LeaseSet with requested leases - this object must be updated to contain the
|
||||
* signed version (as well as any changed/added/removed Leases)
|
||||
* signed version (as well as any changed/added/removed Leases).
|
||||
* The LeaseSet contains Leases only; it is unsigned and does not have the destination set.
|
||||
* @param timeout ms to wait before failing
|
||||
* @param onCreateJob Job to run after the LeaseSet is authorized
|
||||
* @param onFailedJob Job to run after the timeout passes without receiving authorization
|
||||
@ -386,20 +412,33 @@ class ClientManager {
|
||||
public void requestLeaseSet(Destination dest, LeaseSet set, long timeout, Job onCreateJob, Job onFailedJob) {
|
||||
ClientConnectionRunner runner = getRunner(dest);
|
||||
if (runner == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Cannot request the lease set, as we can't find a client runner for "
|
||||
+ dest.calculateHash().toBase64() + ". disconnected?");
|
||||
_ctx.jobQueue().addJob(onFailedJob);
|
||||
} else {
|
||||
runner.requestLeaseSet(set, timeout, onCreateJob, onFailedJob);
|
||||
runner.requestLeaseSet(dest.calculateHash(), set, timeout, onCreateJob, onFailedJob);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that a particular client authorize the Leases contained in the
|
||||
* LeaseSet.
|
||||
*
|
||||
* @param dest Destination from which the LeaseSet's authorization should be requested
|
||||
* @param ls LeaseSet with requested leases - this object must be updated to contain the
|
||||
* signed version (as well as any changed/added/removed Leases).
|
||||
* The LeaseSet contains Leases only; it is unsigned and does not have the destination set.
|
||||
*/
|
||||
public void requestLeaseSet(Hash dest, LeaseSet ls) {
|
||||
ClientConnectionRunner runner = getRunner(dest);
|
||||
if (runner != null) {
|
||||
// no need to fire off any jobs...
|
||||
runner.requestLeaseSet(ls, REQUEST_LEASESET_TIMEOUT, null, null);
|
||||
runner.requestLeaseSet(dest, ls, REQUEST_LEASESET_TIMEOUT, null, null);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Cannot request the lease set, as we can't find a client runner for "
|
||||
+ dest + ". disconnected?");
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,7 +464,9 @@ class ClientManager {
|
||||
if (destHash == null) return true;
|
||||
ClientConnectionRunner runner = getRunner(destHash);
|
||||
if (runner == null) return true;
|
||||
return !Boolean.parseBoolean(runner.getConfig().getOptions().getProperty(ClientManagerFacade.PROP_CLIENT_ONLY));
|
||||
SessionConfig config = runner.getConfig(destHash);
|
||||
if (config == null) return true;
|
||||
return !Boolean.parseBoolean(config.getOptions().getProperty(ClientManagerFacade.PROP_CLIENT_ONLY));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -452,7 +493,7 @@ class ClientManager {
|
||||
public SessionConfig getClientSessionConfig(Destination dest) {
|
||||
ClientConnectionRunner runner = getRunner(dest);
|
||||
if (runner != null)
|
||||
return runner.getConfig();
|
||||
return runner.getConfig(dest.calculateHash());
|
||||
else
|
||||
return null;
|
||||
}
|
||||
@ -490,7 +531,7 @@ class ClientManager {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Delivering status " + status + " to "
|
||||
+ fromDest.calculateHash() + " for message " + id);
|
||||
runner.updateMessageDeliveryStatus(id, messageNonce, status);
|
||||
runner.updateMessageDeliveryStatus(fromDest, id, messageNonce, status);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Cannot deliver status " + status + " to "
|
||||
@ -514,7 +555,7 @@ class ClientManager {
|
||||
if (dest != null) {
|
||||
ClientConnectionRunner runner = getRunner(dest);
|
||||
if (runner != null) {
|
||||
runner.reportAbuse(reason, severity);
|
||||
runner.reportAbuse(dest, reason, severity);
|
||||
}
|
||||
} else {
|
||||
for (Destination d : _runners.keySet()) {
|
||||
@ -592,21 +633,25 @@ class ClientManager {
|
||||
|
||||
public void runJob() {
|
||||
ClientConnectionRunner runner;
|
||||
if (_msg.getDestination() != null)
|
||||
runner = getRunner(_msg.getDestination());
|
||||
Destination dest = _msg.getDestination();
|
||||
if (dest != null)
|
||||
runner = getRunner(dest);
|
||||
else
|
||||
runner = getRunner(_msg.getDestinationHash());
|
||||
|
||||
if (runner != null) {
|
||||
//_ctx.statManager().addRateData("client.receiveMessageSize",
|
||||
// _msg.getPayload().getSize(), 0);
|
||||
runner.receiveMessage(_msg.getDestination(), null, _msg.getPayload());
|
||||
if (dest != null)
|
||||
runner.receiveMessage(dest, null, _msg.getPayload());
|
||||
else
|
||||
runner.receiveMessage(_msg.getDestinationHash(), null, _msg.getPayload());
|
||||
} else {
|
||||
// no client connection...
|
||||
// we should pool these somewhere...
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Message received but we don't have a connection to "
|
||||
+ _msg.getDestination() + "/" + _msg.getDestinationHash()
|
||||
+ dest + "/" + _msg.getDestinationHash()
|
||||
+ " currently. DROPPED");
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ public class ClientManagerFacadeImpl extends ClientManagerFacade implements Inte
|
||||
for (Destination dest : _manager.getRunnerDestinations()) {
|
||||
ClientConnectionRunner runner = _manager.getRunner(dest);
|
||||
if ( (runner == null) || (runner.getIsDead())) continue;
|
||||
LeaseSet ls = runner.getLeaseSet();
|
||||
LeaseSet ls = runner.getLeaseSet(dest.calculateHash());
|
||||
if (ls == null)
|
||||
continue; // still building
|
||||
long howLongAgo = _context.clock().now() - ls.getEarliestLeaseDate();
|
||||
@ -115,6 +115,7 @@ public class ClientManagerFacadeImpl extends ClientManagerFacade implements Inte
|
||||
* @param dest Destination from which the LeaseSet's authorization should be requested
|
||||
* @param set LeaseSet with requested leases - this object must be updated to contain the
|
||||
* signed version (as well as any changed/added/removed Leases)
|
||||
* The LeaseSet contains Leases only; it is unsigned and does not have the destination set.
|
||||
* @param timeout ms to wait before failing
|
||||
* @param onCreateJob Job to run after the LeaseSet is authorized
|
||||
* @param onFailedJob Job to run after the timeout passes without receiving authorization
|
||||
@ -126,6 +127,15 @@ public class ClientManagerFacadeImpl extends ClientManagerFacade implements Inte
|
||||
_log.error("Null manager on requestLeaseSet!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that a particular client authorize the Leases contained in the
|
||||
* LeaseSet.
|
||||
*
|
||||
* @param dest Destination from which the LeaseSet's authorization should be requested
|
||||
* @param ls LeaseSet with requested leases - this object must be updated to contain the
|
||||
* signed version (as well as any changed/added/removed Leases).
|
||||
* The LeaseSet contains Leases only; it is unsigned and does not have the destination set.
|
||||
*/
|
||||
public void requestLeaseSet(Hash dest, LeaseSet set) {
|
||||
if (_manager != null)
|
||||
_manager.requestLeaseSet(dest, set);
|
||||
|
@ -204,12 +204,13 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
*/
|
||||
private void handleCreateSession(CreateSessionMessage message) {
|
||||
SessionConfig in = message.getSessionConfig();
|
||||
Destination dest = in.getDestination();
|
||||
if (in.verifySignature()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Signature verified correctly on create session message");
|
||||
} else {
|
||||
// For now, we do NOT send a SessionStatusMessage - see javadoc above
|
||||
int itype = in.getDestination().getCertificate().getCertificateType();
|
||||
int itype = dest.getCertificate().getCertificateType();
|
||||
SigType stype = SigType.getByCode(itype);
|
||||
if (stype == null || !stype.isAvailable()) {
|
||||
_log.error("Client requested unsupported signature type " + itype);
|
||||
@ -235,7 +236,7 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
if (!checkAuth(inProps))
|
||||
return;
|
||||
|
||||
SessionId id = _runner.getSessionId();
|
||||
SessionId id = _runner.getSessionId(dest.calculateHash());
|
||||
if (id != null) {
|
||||
_runner.disconnectClient("Already have session " + id);
|
||||
return;
|
||||
@ -244,11 +245,22 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
// Copy over the whole config structure so we don't later corrupt it on
|
||||
// the client side if we change settings or later get a
|
||||
// ReconfigureSessionMessage
|
||||
SessionConfig cfg = new SessionConfig(in.getDestination());
|
||||
SessionConfig cfg = new SessionConfig(dest);
|
||||
cfg.setSignature(in.getSignature());
|
||||
Properties props = new Properties();
|
||||
props.putAll(in.getOptions());
|
||||
boolean isPrimary = _runner.getSessionIds().isEmpty();
|
||||
if (!isPrimary) {
|
||||
// all the primary options, then the overrides from the alias
|
||||
SessionConfig pcfg = _runner.getPrimaryConfig();
|
||||
if (pcfg != null) {
|
||||
props.putAll(pcfg.getOptions());
|
||||
} else {
|
||||
_log.error("no primary config?");
|
||||
}
|
||||
}
|
||||
props.putAll(inProps);
|
||||
cfg.setOptions(props);
|
||||
// this sets the session id
|
||||
int status = _runner.sessionEstablished(cfg);
|
||||
if (status != SessionStatusMessage.STATUS_CREATED) {
|
||||
// For now, we do NOT send a SessionStatusMessage - see javadoc above
|
||||
@ -264,11 +276,33 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_runner.disconnectClient(msg);
|
||||
return;
|
||||
}
|
||||
sendStatusMessage(status);
|
||||
// get the new session ID
|
||||
id = _runner.getSessionId(dest.calculateHash());
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Session " + _runner.getSessionId() + " established for " + _runner.getDestHash());
|
||||
startCreateSessionJob();
|
||||
_log.info("Session " + id + " established for " + dest.calculateHash());
|
||||
if (isPrimary) {
|
||||
sendStatusMessage(id, status);
|
||||
startCreateSessionJob(cfg);
|
||||
} else {
|
||||
SessionConfig pcfg = _runner.getPrimaryConfig();
|
||||
if (pcfg != null) {
|
||||
ClientTunnelSettings settings = new ClientTunnelSettings(dest.calculateHash());
|
||||
settings.readFromProperties(props);
|
||||
// addAlias() sends the create lease set msg, so we have to send the SMS first
|
||||
sendStatusMessage(id, status);
|
||||
boolean ok = _context.tunnelManager().addAlias(dest, settings, pcfg.getDestination());
|
||||
if (!ok) {
|
||||
_log.error("Add alias failed");
|
||||
// FIXME cleanup
|
||||
}
|
||||
} else {
|
||||
_log.error("no primary config?");
|
||||
status = SessionStatusMessage.STATUS_INVALID;
|
||||
sendStatusMessage(id, status);
|
||||
// FIXME cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,8 +348,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
* @since 0.9.8
|
||||
*
|
||||
*/
|
||||
protected void startCreateSessionJob() {
|
||||
_context.jobQueue().addJob(new CreateSessionJob(_context, _runner));
|
||||
protected void startCreateSessionJob(SessionConfig config) {
|
||||
_context.jobQueue().addJob(new CreateSessionJob(_context, config));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -324,7 +358,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
*
|
||||
*/
|
||||
private void handleSendMessage(SendMessageMessage message) {
|
||||
SessionConfig cfg = _runner.getConfig();
|
||||
SessionId sid = message.getSessionId();
|
||||
SessionConfig cfg = _runner.getConfig(sid);
|
||||
if (cfg == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("SendMessage w/o session");
|
||||
@ -336,7 +371,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
long beforeDistribute = _context.clock().now();
|
||||
MessageId id = _runner.distributeMessage(message);
|
||||
long timeToDistribute = _context.clock().now() - beforeDistribute;
|
||||
_runner.ackSendMessage(id, message.getNonce());
|
||||
// TODO validate session id
|
||||
_runner.ackSendMessage(message.getSessionId(), id, message.getNonce());
|
||||
_context.statManager().addRateData("client.distributeTime", timeToDistribute);
|
||||
if ( (timeToDistribute > 50) && (_log.shouldLog(Log.INFO)) )
|
||||
_log.info("Took too long to distribute the message (which holds up the ack): " + timeToDistribute);
|
||||
@ -353,7 +389,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_log.debug("Handling recieve begin: id = " + message.getMessageId());
|
||||
MessagePayloadMessage msg = new MessagePayloadMessage();
|
||||
msg.setMessageId(message.getMessageId());
|
||||
msg.setSessionId(_runner.getSessionId().getSessionId());
|
||||
// TODO validate session id
|
||||
msg.setSessionId(message.getSessionId());
|
||||
Payload payload = _runner.getPayload(new MessageId(message.getMessageId()));
|
||||
if (payload == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -382,9 +419,18 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
}
|
||||
|
||||
private void handleDestroySession(DestroySessionMessage message) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Destroying client session " + _runner.getSessionId());
|
||||
_runner.stopRunning();
|
||||
SessionId id = message.getSessionId();
|
||||
SessionConfig cfg = _runner.getConfig(id);
|
||||
_runner.removeSession(id);
|
||||
int left = _runner.getSessionIds().size();
|
||||
if (left <= 0) {
|
||||
_runner.stopRunning();
|
||||
} else {
|
||||
if (cfg != null)
|
||||
_context.tunnelManager().removeAlias(cfg.getDestination());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Still " + left + " sessions left");
|
||||
}
|
||||
}
|
||||
|
||||
/** override for testing */
|
||||
@ -395,7 +441,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_runner.disconnectClient("Invalid CreateLeaseSetMessage");
|
||||
return;
|
||||
}
|
||||
SessionConfig cfg = _runner.getConfig();
|
||||
SessionId id = message.getSessionId();
|
||||
SessionConfig cfg = _runner.getConfig(id);
|
||||
if (cfg == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("CreateLeaseSet w/o session");
|
||||
@ -446,8 +493,7 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
return;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New lease set granted for destination "
|
||||
+ _runner.getDestHash());
|
||||
_log.info("New lease set granted for destination " + dest);
|
||||
|
||||
// leaseSetCreated takes care of all the LeaseRequestState stuff (including firing any jobs)
|
||||
_runner.leaseSetCreated(message.getLeaseSet());
|
||||
@ -455,6 +501,7 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
|
||||
/** override for testing */
|
||||
protected void handleDestLookup(DestLookupMessage message) {
|
||||
// no session id in DLM
|
||||
_context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getHash(),
|
||||
_runner.getDestHash()));
|
||||
}
|
||||
@ -464,10 +511,12 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
* @since 0.9.11
|
||||
*/
|
||||
protected void handleHostLookup(HostLookupMessage message) {
|
||||
Hash h = _runner.getDestHash(message.getSessionId());
|
||||
if (h == null)
|
||||
return; // ok?
|
||||
_context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getReqID(),
|
||||
message.getTimeout(), message.getSessionId(),
|
||||
message.getHash(), message.getHostname(),
|
||||
_runner.getDestHash()));
|
||||
message.getHash(), message.getHostname(), h));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -482,10 +531,12 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
* In-JVM client side must promote defaults to the primary map.
|
||||
*/
|
||||
private void handleReconfigureSession(ReconfigureSessionMessage message) {
|
||||
SessionConfig cfg = _runner.getConfig();
|
||||
SessionId id = message.getSessionId();
|
||||
SessionConfig cfg = _runner.getConfig(id);
|
||||
if (cfg == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("ReconfigureSession w/o session");
|
||||
//sendStatusMessage(id, SessionStatusMessage.STATUS_INVALID);
|
||||
_runner.disconnectClient("ReconfigureSession w/o session");
|
||||
return;
|
||||
}
|
||||
@ -493,12 +544,12 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_log.info("Updating options - old: " + cfg + " new: " + message.getSessionConfig());
|
||||
if (!message.getSessionConfig().getDestination().equals(cfg.getDestination())) {
|
||||
_log.error("Dest mismatch");
|
||||
sendStatusMessage(SessionStatusMessage.STATUS_INVALID);
|
||||
sendStatusMessage(id, SessionStatusMessage.STATUS_INVALID);
|
||||
_runner.stopRunning();
|
||||
return;
|
||||
}
|
||||
Hash dest = cfg.getDestination().calculateHash();
|
||||
cfg.getOptions().putAll(message.getSessionConfig().getOptions());
|
||||
Hash dest = _runner.getDestHash();
|
||||
ClientTunnelSettings settings = new ClientTunnelSettings(dest);
|
||||
Properties props = new Properties();
|
||||
props.putAll(cfg.getOptions());
|
||||
@ -507,14 +558,11 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
settings.getInboundSettings());
|
||||
_context.tunnelManager().setOutboundSettings(dest,
|
||||
settings.getOutboundSettings());
|
||||
sendStatusMessage(SessionStatusMessage.STATUS_UPDATED);
|
||||
sendStatusMessage(id, SessionStatusMessage.STATUS_UPDATED);
|
||||
}
|
||||
|
||||
private void sendStatusMessage(int status) {
|
||||
private void sendStatusMessage(SessionId id, int status) {
|
||||
SessionStatusMessage msg = new SessionStatusMessage();
|
||||
SessionId id = _runner.getSessionId();
|
||||
if (id == null)
|
||||
id = ClientManager.UNKNOWN_SESSION_ID;
|
||||
msg.setSessionId(id);
|
||||
msg.setStatus(status);
|
||||
try {
|
||||
|
@ -26,25 +26,20 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
class CreateSessionJob extends JobImpl {
|
||||
private final Log _log;
|
||||
private final ClientConnectionRunner _runner;
|
||||
private final SessionConfig _config;
|
||||
|
||||
public CreateSessionJob(RouterContext context, ClientConnectionRunner runner) {
|
||||
public CreateSessionJob(RouterContext context, SessionConfig config) {
|
||||
super(context);
|
||||
_log = context.logManager().getLog(CreateSessionJob.class);
|
||||
_runner = runner;
|
||||
_config = config;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("CreateSessionJob for runner " + _runner + " / config: " + _runner.getConfig());
|
||||
_log.debug("CreateSessionJob for config: " + config);
|
||||
}
|
||||
|
||||
public String getName() { return "Request tunnels for a new client"; }
|
||||
|
||||
public void runJob() {
|
||||
SessionConfig cfg = _runner.getConfig();
|
||||
if ( (cfg == null) || (cfg.getDestination() == null) ) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("No session config on runner " + _runner);
|
||||
return;
|
||||
}
|
||||
Hash dest = cfg.getDestination().calculateHash();
|
||||
Hash dest = _config.getDestination().calculateHash();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Requesting lease set for destination " + dest);
|
||||
ClientTunnelSettings settings = new ClientTunnelSettings(dest);
|
||||
@ -61,10 +56,10 @@ class CreateSessionJob extends JobImpl {
|
||||
// XXX props.putAll(Router.getInstance().getConfigMap());
|
||||
|
||||
// override them by the client's settings
|
||||
props.putAll(cfg.getOptions());
|
||||
props.putAll(_config.getOptions());
|
||||
|
||||
// and load 'em up (using anything not yet set as the software defaults)
|
||||
settings.readFromProperties(props);
|
||||
getContext().tunnelManager().buildTunnels(cfg.getDestination(), settings);
|
||||
getContext().tunnelManager().buildTunnels(_config.getDestination(), settings);
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,9 @@ class LeaseRequestState {
|
||||
|
||||
/**
|
||||
* @param expiration absolute time, when the request expires (not when the LS expires)
|
||||
* @param requested LeaseSet with requested leases - this object must be updated to contain the
|
||||
* signed version (as well as any changed/added/removed Leases)
|
||||
* The LeaseSet contains Leases and destination only, it is unsigned.
|
||||
*/
|
||||
public LeaseRequestState(Job onGranted, Job onFailed, long expiration, LeaseSet requested) {
|
||||
_onGranted = onGranted;
|
||||
@ -40,6 +43,7 @@ class LeaseRequestState {
|
||||
|
||||
/** created lease set from client - FIXME always null */
|
||||
public LeaseSet getGranted() { return _grantedLeaseSet; }
|
||||
|
||||
/** FIXME unused - why? */
|
||||
public void setGranted(LeaseSet ls) { _grantedLeaseSet = ls; }
|
||||
|
||||
|
@ -14,6 +14,7 @@ import net.i2p.data.i2cp.I2CPMessageException;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2cp.MessagePayloadMessage;
|
||||
import net.i2p.data.i2cp.MessageStatusMessage;
|
||||
import net.i2p.data.i2cp.SessionId;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
@ -26,14 +27,20 @@ import net.i2p.util.Log;
|
||||
class MessageReceivedJob extends JobImpl {
|
||||
private final Log _log;
|
||||
private final ClientConnectionRunner _runner;
|
||||
private final Destination _toDest;
|
||||
private final Payload _payload;
|
||||
private final boolean _sendDirect;
|
||||
|
||||
/**
|
||||
* @param toDest non-null, required to pick session
|
||||
* @param fromDest ignored, generally null
|
||||
*/
|
||||
public MessageReceivedJob(RouterContext ctx, ClientConnectionRunner runner, Destination toDest,
|
||||
Destination fromDest, Payload payload, boolean sendDirect) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(MessageReceivedJob.class);
|
||||
_runner = runner;
|
||||
_toDest = toDest;
|
||||
_payload = payload;
|
||||
_sendDirect = sendDirect;
|
||||
}
|
||||
@ -43,8 +50,8 @@ class MessageReceivedJob extends JobImpl {
|
||||
public void runJob() {
|
||||
if (_runner.isDead()) return;
|
||||
MessageId id = null;
|
||||
long nextID = _runner.getNextMessageId();
|
||||
try {
|
||||
long nextID = _runner.getNextMessageId();
|
||||
if (_sendDirect) {
|
||||
sendMessage(nextID);
|
||||
} else {
|
||||
@ -55,7 +62,7 @@ class MessageReceivedJob extends JobImpl {
|
||||
} catch (I2CPMessageException ime) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error writing out the message", ime);
|
||||
if (!_sendDirect)
|
||||
if (id != null && !_sendDirect)
|
||||
_runner.removePayload(id);
|
||||
}
|
||||
}
|
||||
@ -69,7 +76,13 @@ class MessageReceivedJob extends JobImpl {
|
||||
// + " (with nonce=1)", new Exception("available"));
|
||||
MessageStatusMessage msg = new MessageStatusMessage();
|
||||
msg.setMessageId(id.getMessageId());
|
||||
msg.setSessionId(_runner.getSessionId().getSessionId());
|
||||
SessionId sid = _runner.getSessionId(_toDest.calculateHash());
|
||||
if (sid == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No session for " + _toDest.calculateHash());
|
||||
return;
|
||||
}
|
||||
msg.setSessionId(sid.getSessionId());
|
||||
msg.setSize(size);
|
||||
// has to be >= 0, it is initialized to -1
|
||||
msg.setNonce(1);
|
||||
@ -84,7 +97,13 @@ class MessageReceivedJob extends JobImpl {
|
||||
private void sendMessage(long id) throws I2CPMessageException {
|
||||
MessagePayloadMessage msg = new MessagePayloadMessage();
|
||||
msg.setMessageId(id);
|
||||
msg.setSessionId(_runner.getSessionId().getSessionId());
|
||||
SessionId sid = _runner.getSessionId(_toDest.calculateHash());
|
||||
if (sid == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No session for " + _toDest.calculateHash());
|
||||
return;
|
||||
}
|
||||
msg.setSessionId(sid.getSessionId());
|
||||
msg.setPayload(_payload);
|
||||
_runner.doSend(msg);
|
||||
}
|
||||
|
@ -8,10 +8,12 @@ package net.i2p.router.client;
|
||||
*
|
||||
*/
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.i2cp.AbuseReason;
|
||||
import net.i2p.data.i2cp.AbuseSeverity;
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
import net.i2p.data.i2cp.ReportAbuseMessage;
|
||||
import net.i2p.data.i2cp.SessionId;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
@ -23,17 +25,22 @@ import net.i2p.util.Log;
|
||||
class ReportAbuseJob extends JobImpl {
|
||||
private final Log _log;
|
||||
private final ClientConnectionRunner _runner;
|
||||
private final Destination _dest;
|
||||
private final String _reason;
|
||||
private final int _severity;
|
||||
public ReportAbuseJob(RouterContext context, ClientConnectionRunner runner, String reason, int severity) {
|
||||
|
||||
public ReportAbuseJob(RouterContext context, ClientConnectionRunner runner,
|
||||
Destination dest, String reason, int severity) {
|
||||
super(context);
|
||||
_log = context.logManager().getLog(ReportAbuseJob.class);
|
||||
_runner = runner;
|
||||
_dest = dest;
|
||||
_reason = reason;
|
||||
_severity = severity;
|
||||
}
|
||||
|
||||
public String getName() { return "Report Abuse"; }
|
||||
|
||||
public void runJob() {
|
||||
if (_runner.isDead()) return;
|
||||
AbuseReason res = new AbuseReason();
|
||||
@ -41,9 +48,11 @@ class ReportAbuseJob extends JobImpl {
|
||||
AbuseSeverity sev = new AbuseSeverity();
|
||||
sev.setSeverity(_severity);
|
||||
ReportAbuseMessage msg = new ReportAbuseMessage();
|
||||
msg.setMessageId(null);
|
||||
msg.setReason(res);
|
||||
msg.setSessionId(_runner.getSessionId());
|
||||
SessionId id = _runner.getSessionId(_dest.calculateHash());
|
||||
if (id == null)
|
||||
return;
|
||||
msg.setSessionId(id);
|
||||
msg.setSeverity(sev);
|
||||
try {
|
||||
_runner.doSend(msg);
|
||||
|
@ -16,6 +16,7 @@ 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.data.i2cp.SessionId;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
@ -63,13 +64,16 @@ class RequestLeaseSetJob extends JobImpl {
|
||||
// _log.debug("Adding fudge " + fudge);
|
||||
endTime += fudge;
|
||||
|
||||
SessionId id = _runner.getSessionId(_requestState.getRequested().getDestination().calculateHash());
|
||||
if (id == null)
|
||||
return;
|
||||
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());
|
||||
rmsg.setSessionId(id);
|
||||
for (int i = 0; i < requested.getLeaseCount(); i++) {
|
||||
Lease lease = requested.getLease(i);
|
||||
if (lease.getEndDate().getTime() < endTime) {
|
||||
@ -90,7 +94,7 @@ class RequestLeaseSetJob extends JobImpl {
|
||||
RequestLeaseSetMessage rmsg = new RequestLeaseSetMessage();
|
||||
Date end = new Date(endTime);
|
||||
rmsg.setEndDate(end);
|
||||
rmsg.setSessionId(_runner.getSessionId());
|
||||
rmsg.setSessionId(id);
|
||||
for (int i = 0; i < requested.getLeaseCount(); i++) {
|
||||
Lease lease = requested.getLease(i);
|
||||
rmsg.addEndpoint(lease.getGateway(),
|
||||
@ -144,8 +148,7 @@ class RequestLeaseSetJob extends JobImpl {
|
||||
CheckLeaseRequestStatus.this.getContext().statManager().addRateData("client.requestLeaseSetTimeout", 1);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
long waited = System.currentTimeMillis() - _start;
|
||||
_log.error("Failed to receive a leaseSet in the time allotted (" + waited + "): " + _requestState + " for "
|
||||
+ _runner.getConfig().getDestination().calculateHash().toBase64());
|
||||
_log.error("Failed to receive a leaseSet in the time allotted (" + waited + "): " + _requestState);
|
||||
}
|
||||
if (_requestState.getOnFailed() != null)
|
||||
RequestLeaseSetJob.this.getContext().jobQueue().addJob(_requestState.getOnFailed());
|
||||
|
@ -50,6 +50,8 @@ public class DummyTunnelManagerFacade implements TunnelManagerFacade {
|
||||
public int getOutboundClientTunnelCount(Hash destination) { return 0; }
|
||||
public long getLastParticipatingExpiration() { return -1; }
|
||||
public void buildTunnels(Destination client, ClientTunnelSettings settings) {}
|
||||
public boolean addAlias(Destination dest, ClientTunnelSettings settings, Destination existingClient) { return false; }
|
||||
public void removeAlias(Destination dest) {}
|
||||
public TunnelPoolSettings getInboundSettings() { return null; }
|
||||
public TunnelPoolSettings getOutboundSettings() { return null; }
|
||||
public TunnelPoolSettings getInboundSettings(Hash client) { return null; }
|
||||
|
@ -18,6 +18,7 @@ import net.i2p.data.i2np.VariableTunnelBuildReplyMessage;
|
||||
import net.i2p.router.ClientMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelInfo;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
import net.i2p.router.message.GarlicMessageReceiver;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.util.Log;
|
||||
@ -204,11 +205,11 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver {
|
||||
*
|
||||
*/
|
||||
public void handleClove(DeliveryInstructions instructions, I2NPMessage data) {
|
||||
int type = data.getType();
|
||||
switch (instructions.getDeliveryMode()) {
|
||||
case DeliveryInstructions.DELIVERY_MODE_LOCAL:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("local delivery instructions for clove: " + data.getClass().getSimpleName());
|
||||
int type = data.getType();
|
||||
if (type == GarlicMessage.MESSAGE_TYPE) {
|
||||
_receiver.receive((GarlicMessage)data);
|
||||
} else if (type == DatabaseStoreMessage.MESSAGE_TYPE) {
|
||||
@ -296,28 +297,45 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver {
|
||||
_context.inNetMessagePool().add(data, null, null);
|
||||
}
|
||||
return;
|
||||
|
||||
case DeliveryInstructions.DELIVERY_MODE_DESTINATION:
|
||||
Hash to = instructions.getDestination();
|
||||
// Can we route UnknownI2NPMessages to a destination too?
|
||||
if (!(data instanceof DataMessage)) {
|
||||
if (type != DataMessage.MESSAGE_TYPE) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("cant send a " + data.getClass().getSimpleName() + " to a destination");
|
||||
} else if ( (_client != null) && (_client.equals(instructions.getDestination())) ) {
|
||||
} else if (_client != null && _client.equals(to)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("data message came down a tunnel for "
|
||||
+ _client);
|
||||
_log.debug("data message came down a tunnel for " + _client);
|
||||
DataMessage dm = (DataMessage)data;
|
||||
Payload payload = new Payload();
|
||||
payload.setEncryptedData(dm.getData());
|
||||
ClientMessage m = new ClientMessage(_client, payload);
|
||||
_context.clientManager().messageReceived(m);
|
||||
} else if (_client != null) {
|
||||
// Shared tunnel?
|
||||
TunnelPoolSettings tgt = _context.tunnelManager().getInboundSettings(to);
|
||||
if (tgt != null && _client.equals(tgt.getAliasOf())) {
|
||||
// same as above, just different log
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("data message came down a tunnel for "
|
||||
+ _client + " targeting shared " + to);
|
||||
DataMessage dm = (DataMessage)data;
|
||||
Payload payload = new Payload();
|
||||
payload.setEncryptedData(dm.getData());
|
||||
ClientMessage m = new ClientMessage(to, payload);
|
||||
_context.clientManager().messageReceived(m);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Data message came down a tunnel for "
|
||||
+ _client + " but targetted " + to);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("this data message came down a tunnel for "
|
||||
+ (_client == null ? "no one" : _client)
|
||||
+ " but targetted "
|
||||
+ instructions.getDestination());
|
||||
_log.error("Data message came down an exploratory tunnel targeting " + to);
|
||||
}
|
||||
return;
|
||||
|
||||
case DeliveryInstructions.DELIVERY_MODE_ROUTER: // fall through
|
||||
case DeliveryInstructions.DELIVERY_MODE_TUNNEL:
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -325,6 +343,7 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver {
|
||||
+ ", treat recursively to prevent leakage");
|
||||
distribute(data, instructions.getRouter(), instructions.getTunnelId());
|
||||
return;
|
||||
|
||||
default:
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unknown instruction " + instructions.getDeliveryMode() + ": " + instructions);
|
||||
|
@ -0,0 +1,156 @@
|
||||
package net.i2p.router.tunnel.pool;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Lease;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelInfo;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* A tunnel pool with its own settings and Destination,
|
||||
* but uses another pool for its tunnels.
|
||||
*
|
||||
* @since 0.9.20
|
||||
*/
|
||||
public class AliasedTunnelPool extends TunnelPool {
|
||||
|
||||
private final TunnelPool _aliasOf;
|
||||
|
||||
AliasedTunnelPool(RouterContext ctx, TunnelPoolManager mgr, TunnelPoolSettings settings, TunnelPool aliasOf) {
|
||||
super(ctx, mgr, settings, null);
|
||||
if (settings.isExploratory())
|
||||
throw new IllegalArgumentException();
|
||||
if (settings.getAliasOf() == null)
|
||||
throw new IllegalArgumentException();
|
||||
_aliasOf = aliasOf;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized void startup() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(toString() + ": Startup() called, was already alive? " + _alive, new Exception());
|
||||
_alive = true;
|
||||
super.refreshLeaseSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized void shutdown() {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(toString() + ": Shutdown called");
|
||||
_alive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
TunnelInfo selectTunnel() {
|
||||
return _aliasOf.selectTunnel();
|
||||
}
|
||||
|
||||
@Override
|
||||
TunnelInfo selectTunnel(Hash closestTo) {
|
||||
return _aliasOf.selectTunnel(closestTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunnelInfo getTunnel(TunnelId gatewayId) {
|
||||
return _aliasOf.getTunnel(gatewayId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TunnelInfo> listTunnels() {
|
||||
return _aliasOf.listTunnels();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean needFallback() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PooledTunnelCreatorConfig> listPending() {
|
||||
return _aliasOf.listPending();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return _alive && _aliasOf.isAlive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return _aliasOf.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
void addTunnel(TunnelInfo info) {
|
||||
_aliasOf.addTunnel(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
void removeTunnel(TunnelInfo info) {
|
||||
_aliasOf.removeTunnel(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
void tunnelFailed(TunnelInfo cfg) {
|
||||
_aliasOf.tunnelFailed(cfg);
|
||||
}
|
||||
|
||||
@Override
|
||||
void tunnelFailed(TunnelInfo cfg, Hash blamePeer) {
|
||||
_aliasOf.tunnelFailed(cfg, blamePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
void refreshLeaseSet() {}
|
||||
|
||||
@Override
|
||||
boolean buildFallback() {
|
||||
return _aliasOf.buildFallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LeaseSet locked_buildNewLeaseSet() {
|
||||
LeaseSet ls = _context.netDb().lookupLeaseSetLocally(_aliasOf.getSettings().getDestination());
|
||||
if (ls == null)
|
||||
return null;
|
||||
// copy everything so it isn't corrupted
|
||||
LeaseSet rv = new LeaseSet();
|
||||
for (int i = 0; i < ls.getLeaseCount(); i++) {
|
||||
Lease old = ls.getLease(i);
|
||||
Lease lease = new Lease();
|
||||
lease.setEndDate(old.getEndDate());
|
||||
lease.setTunnelId(old.getTunnelId());
|
||||
lease.setGateway(old.getGateway());
|
||||
rv.addLease(lease);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLifetimeProcessed() {
|
||||
return _aliasOf.getLifetimeProcessed();
|
||||
}
|
||||
|
||||
@Override
|
||||
int countHowManyToBuild() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
PooledTunnelCreatorConfig configureNewTunnel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
void buildComplete(PooledTunnelCreatorConfig cfg) {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Aliased " + super.toString();
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
@ -30,13 +31,13 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class TunnelPool {
|
||||
private final List<PooledTunnelCreatorConfig> _inProgress = new ArrayList<PooledTunnelCreatorConfig>();
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
protected final RouterContext _context;
|
||||
protected final Log _log;
|
||||
private TunnelPoolSettings _settings;
|
||||
private final List<TunnelInfo> _tunnels;
|
||||
private final TunnelPeerSelector _peerSelector;
|
||||
private final TunnelPoolManager _manager;
|
||||
private volatile boolean _alive;
|
||||
protected volatile boolean _alive;
|
||||
private long _lifetimeProcessed;
|
||||
private TunnelInfo _lastSelected;
|
||||
private long _lastSelectionPeriod;
|
||||
@ -118,19 +119,15 @@ public class TunnelPool {
|
||||
}
|
||||
}
|
||||
|
||||
void refreshSettings() {
|
||||
if (!_settings.isExploratory()) {
|
||||
private void refreshSettings() {
|
||||
if (!_settings.isExploratory())
|
||||
return; // don't override client specified settings
|
||||
} else {
|
||||
if (_settings.isExploratory()) {
|
||||
Properties props = new Properties();
|
||||
props.putAll(_context.router().getConfigMap());
|
||||
if (_settings.isInbound())
|
||||
_settings.readFromProperties(TunnelPoolSettings.PREFIX_INBOUND_EXPLORATORY, props);
|
||||
else
|
||||
_settings.readFromProperties(TunnelPoolSettings.PREFIX_OUTBOUND_EXPLORATORY, props);
|
||||
}
|
||||
}
|
||||
Properties props = new Properties();
|
||||
props.putAll(_context.router().getConfigMap());
|
||||
if (_settings.isInbound())
|
||||
_settings.readFromProperties(TunnelPoolSettings.PREFIX_INBOUND_EXPLORATORY, props);
|
||||
else
|
||||
_settings.readFromProperties(TunnelPoolSettings.PREFIX_OUTBOUND_EXPLORATORY, props);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -412,11 +409,15 @@ public class TunnelPool {
|
||||
public List<PooledTunnelCreatorConfig> listPending() { synchronized (_inProgress) { return new ArrayList<PooledTunnelCreatorConfig>(_inProgress); } }
|
||||
|
||||
/** duplicate of size(), let's pick one */
|
||||
int getTunnelCount() { synchronized (_tunnels) { return _tunnels.size(); } }
|
||||
int getTunnelCount() { return size(); }
|
||||
|
||||
public TunnelPoolSettings getSettings() { return _settings; }
|
||||
|
||||
void setSettings(TunnelPoolSettings settings) {
|
||||
if (settings != null && _settings != null) {
|
||||
settings.getAliases().addAll(_settings.getAliases());
|
||||
settings.setAliasOf(_settings.getAliasOf());
|
||||
}
|
||||
_settings = settings;
|
||||
if (_settings != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -606,12 +607,18 @@ public class TunnelPool {
|
||||
if (_settings.isInbound() && !_settings.isExploratory()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(toString() + ": refreshing leaseSet on tunnel expiration (but prior to grace timeout)");
|
||||
LeaseSet ls = null;
|
||||
LeaseSet ls;
|
||||
synchronized (_tunnels) {
|
||||
ls = locked_buildNewLeaseSet();
|
||||
}
|
||||
if (ls != null) {
|
||||
_context.clientManager().requestLeaseSet(_settings.getDestination(), ls);
|
||||
Set<Hash> aliases = _settings.getAliases();
|
||||
if (aliases != null && !aliases.isEmpty()) {
|
||||
for (Hash h : aliases) {
|
||||
_context.clientManager().requestLeaseSet(h, ls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -710,7 +717,7 @@ public class TunnelPool {
|
||||
*
|
||||
* @return null on failure
|
||||
*/
|
||||
private LeaseSet locked_buildNewLeaseSet() {
|
||||
protected LeaseSet locked_buildNewLeaseSet() {
|
||||
if (!_alive)
|
||||
return null;
|
||||
|
||||
|
@ -97,7 +97,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
ctx.statManager().createRateStat("tunnel.testAborted", "Tunnel test could not occur, since there weren't any tunnels to test with", "Tunnels",
|
||||
RATES);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick a random inbound exploratory tunnel.
|
||||
* Warning - selectInboundExploratoryTunnel(Hash) is preferred.
|
||||
@ -113,7 +113,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick a random inbound tunnel from the given destination's pool.
|
||||
* Warning - selectOutboundTunnel(Hash, Hash) is preferred.
|
||||
@ -132,7 +132,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
" but there isn't a pool?");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick a random outbound exploratory tunnel.
|
||||
* Warning - selectOutboundExploratoryTunnel(Hash) is preferred.
|
||||
@ -148,7 +148,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick a random outbound tunnel from the given destination's pool.
|
||||
* Warning - selectOutboundTunnel(Hash, Hash) is preferred.
|
||||
@ -164,7 +164,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick the inbound exploratory tunnel with the gateway closest to the given hash.
|
||||
* By using this instead of the random selectTunnel(),
|
||||
@ -184,7 +184,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick the inbound tunnel with the gateway closest to the given hash
|
||||
* from the given destination's pool.
|
||||
@ -208,7 +208,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
" but there isn't a pool?");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick the outbound exploratory tunnel with the endpoint closest to the given hash.
|
||||
* By using this instead of the random selectTunnel(),
|
||||
@ -228,7 +228,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick the outbound tunnel with the endpoint closest to the given hash
|
||||
* from the given destination's pool.
|
||||
@ -249,7 +249,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Expensive (iterates through all tunnels of all pools) and unnecessary.
|
||||
* @deprecated unused
|
||||
@ -267,7 +267,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
if (info != null) return info;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/** @return number of inbound exploratory tunnels */
|
||||
public int getFreeTunnelCount() {
|
||||
return _inboundExploratory.size();
|
||||
@ -304,10 +304,11 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
return pool.getTunnelCount();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public int getParticipatingCount() { return _context.tunnelDispatcher().getParticipatingCount(); }
|
||||
|
||||
public long getLastParticipatingExpiration() { return _context.tunnelDispatcher().getLastParticipatingExpiration(); }
|
||||
|
||||
|
||||
/**
|
||||
* @return (number of part. tunnels) / (estimated total number of hops in our expl.+client tunnels)
|
||||
* 100 max.
|
||||
@ -330,7 +331,6 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
return Math.min(part / (double) count, 100d);
|
||||
}
|
||||
|
||||
|
||||
public boolean isValidTunnel(Hash client, TunnelInfo tunnel) {
|
||||
if (tunnel.getExpiration() < _context.clock().now())
|
||||
return false;
|
||||
@ -386,17 +386,18 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
pool.setSettings(settings);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public synchronized void restart() {
|
||||
_handler.restart();
|
||||
_executor.restart();
|
||||
shutdownExploratory();
|
||||
startup();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used only at session startup.
|
||||
* Do not use to change settings.
|
||||
* Do not use for aliased destinations; use addAlias().
|
||||
*/
|
||||
public void buildTunnels(Destination client, ClientTunnelSettings settings) {
|
||||
Hash dest = client.calculateHash();
|
||||
@ -434,8 +435,89 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
else
|
||||
outbound.startup();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add another destination to the same tunnels.
|
||||
* Must have same encryption key an a different signing key.
|
||||
* @throws IllegalArgumentException if not
|
||||
* @return success
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public boolean addAlias(Destination dest, ClientTunnelSettings settings, Destination existingClient) {
|
||||
if (dest.getSigningPublicKey().equals(existingClient.getSigningPublicKey()))
|
||||
throw new IllegalArgumentException("signing key must differ");
|
||||
if (!dest.getPublicKey().equals(existingClient.getPublicKey()))
|
||||
throw new IllegalArgumentException("encryption key mismatch");
|
||||
Hash h = dest.calculateHash();
|
||||
Hash e = existingClient.calculateHash();
|
||||
synchronized(this) {
|
||||
TunnelPool inbound = _clientInboundPools.get(h);
|
||||
TunnelPool outbound = _clientOutboundPools.get(h);
|
||||
if (inbound != null || outbound != null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("already have alias " + dest);
|
||||
return false;
|
||||
}
|
||||
TunnelPool eInbound = _clientInboundPools.get(e);
|
||||
TunnelPool eOutbound = _clientOutboundPools.get(e);
|
||||
if (eInbound == null || eOutbound == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("primary not found " + existingClient);
|
||||
return false;
|
||||
}
|
||||
eInbound.getSettings().getAliases().add(h);
|
||||
eOutbound.getSettings().getAliases().add(h);
|
||||
TunnelPoolSettings newIn = settings.getInboundSettings();
|
||||
TunnelPoolSettings newOut = settings.getOutboundSettings();
|
||||
newIn.setAliasOf(e);
|
||||
newOut.setAliasOf(e);
|
||||
inbound = new AliasedTunnelPool(_context, this, newIn, eInbound);
|
||||
outbound = new AliasedTunnelPool(_context, this, newOut, eOutbound);
|
||||
_clientInboundPools.put(h, inbound);
|
||||
_clientOutboundPools.put(h, outbound);
|
||||
inbound.startup();
|
||||
outbound.startup();
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Added " + h + " as alias for " + e + " with settings " + settings);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a destination for the same tunnels as another.
|
||||
* @since 0.9.19
|
||||
*/
|
||||
public void removeAlias(Destination dest) {
|
||||
Hash h = dest.calculateHash();
|
||||
synchronized(this) {
|
||||
TunnelPool inbound = _clientInboundPools.remove(h);
|
||||
if (inbound != null) {
|
||||
Hash p = inbound.getSettings().getAliasOf();
|
||||
if (p != null) {
|
||||
TunnelPool pri = _clientInboundPools.get(p);
|
||||
if (pri != null) {
|
||||
Set<Hash> aliases = pri.getSettings().getAliases();
|
||||
if (aliases != null)
|
||||
aliases.remove(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
TunnelPool outbound = _clientOutboundPools.remove(h);
|
||||
if (outbound != null) {
|
||||
Hash p = outbound.getSettings().getAliasOf();
|
||||
if (p != null) {
|
||||
TunnelPool pri = _clientOutboundPools.get(p);
|
||||
if (pri != null) {
|
||||
Set<Hash> aliases = pri.getSettings().getAliases();
|
||||
if (aliases != null)
|
||||
aliases.remove(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO if primary already vanished...
|
||||
}
|
||||
}
|
||||
|
||||
private static class DelayedStartup implements SimpleTimer.TimedEvent {
|
||||
private final TunnelPool pool;
|
||||
|
||||
@ -469,7 +551,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
if (outbound != null)
|
||||
outbound.shutdown();
|
||||
}
|
||||
|
||||
|
||||
/** queue a recurring test job if appropriate */
|
||||
void buildComplete(PooledTunnelCreatorConfig cfg) {
|
||||
if (cfg.getLength() > 1 &&
|
||||
@ -518,7 +600,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
_context.jobQueue().addJob(new BootstrapPool(_context, _inboundExploratory));
|
||||
_context.jobQueue().addJob(new BootstrapPool(_context, _outboundExploratory));
|
||||
}
|
||||
|
||||
|
||||
private static class BootstrapPool extends JobImpl {
|
||||
private TunnelPool _pool;
|
||||
public BootstrapPool(RouterContext ctx, TunnelPool pool) {
|
||||
@ -531,7 +613,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
_pool.buildFallback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cannot be restarted
|
||||
*/
|
||||
@ -546,7 +628,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
||||
_inboundExploratory.shutdown();
|
||||
_outboundExploratory.shutdown();
|
||||
}
|
||||
|
||||
|
||||
/** list of TunnelPool instances currently in play */
|
||||
public void listPools(List<TunnelPool> out) {
|
||||
out.addAll(_clientInboundPools.values());
|
||||
|
Reference in New Issue
Block a user