propagate from branch 'i2p.i2p' (head 1de143fff53bb56e6eac926d6293d62200f0c392)

to branch 'i2p.i2p.zzz.multisess' (head 70fc07857232668b93ca6ba02c433dffc7639132)
This commit is contained in:
zzz
2015-06-08 21:50:42 +00:00
51 changed files with 2036 additions and 263 deletions

View File

@ -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();

View File

@ -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.

View File

@ -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 {

View File

@ -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");
}
}

View File

@ -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);

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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; }

View File

@ -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);
}

View File

@ -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);

View File

@ -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());

View File

@ -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; }

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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());