Changeset 099515a


Ignore:
Timestamp:
Jun 8, 2015 9:50:42 PM (5 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
cbc2f89
Parents:
e8f4e19 (diff), 657f13a (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

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

to branch 'i2p.i2p.zzz.multisess' (head 70fc07857232668b93ca6ba02c433dffc7639132)

Files:
2 added
49 edited

Legend:

Unmodified
Added
Removed
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java

    re8f4e19 r099515a  
    2626import net.i2p.I2PAppContext;
    2727import net.i2p.I2PException;
     28import net.i2p.client.I2PClient;
    2829import net.i2p.client.I2PSession;
    2930import net.i2p.client.I2PSessionException;
     
    3233import net.i2p.client.streaming.I2PSocketManagerFactory;
    3334import net.i2p.client.streaming.I2PSocketOptions;
     35import net.i2p.crypto.SigType;
    3436import net.i2p.data.Destination;
    3537import net.i2p.util.EventDispatcher;
     
    288290                // We could be here a LONG time, holding the lock
    289291                socketManager = buildSocketManager(tunnel, pkf);
     292                // FIXME may not be the right place for this
     293                I2PSession sub = addSubsession(tunnel);
     294                if (sub != null && _log.shouldLog(Log.WARN))
     295                    _log.warn("Added subsession " + sub);
    290296            } else {
    291297                if (_log.shouldLog(Log.INFO))
     
    300306                _log.info(tunnel.getClientOptions().getProperty("inbound.nickname") + ": Building a new socket manager since there is no other one");
    301307            socketManager = buildSocketManager(tunnel, pkf);
     308            I2PSession sub = addSubsession(tunnel);
     309            if (sub != null && _log.shouldLog(Log.WARN))
     310                _log.warn("Added subsession " + sub);
    302311        }
    303312        return socketManager;
     313    }
     314
     315    /**
     316     *  Add a subsession to a shared client if necessary.
     317     *
     318     *  @since 0.9.20
     319     */
     320    protected static synchronized I2PSession addSubsession(I2PTunnel tunnel) {
     321        I2PSession sess = socketManager.getSession();
     322        if (sess.getMyDestination().getSigType() == SigType.DSA_SHA1)
     323            return null;
     324        Properties props = new Properties();
     325        props.putAll(tunnel.getClientOptions());
     326        String name = props.getProperty("inbound.nickname");
     327        if (name != null)
     328            props.setProperty("inbound.nickname", name + " (DSA)");
     329        name = props.getProperty("outbound.nickname");
     330        if (name != null)
     331            props.setProperty("outbound.nickname", name + " (DSA)");
     332        props.setProperty(I2PClient.PROP_SIGTYPE, "DSA_SHA1");
     333        try {
     334            return socketManager.addSubsession(null, props);
     335        } catch (I2PSessionException ise) {
     336            Log log = tunnel.getContext().logManager().getLog(I2PTunnelClientBase.class);
     337            if (log.shouldLog(Log.WARN))
     338                log.warn("Failed to add subssession", ise);
     339            return null;
     340        }
    304341    }
    305342
  • apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManager.java

    re8f4e19 r099515a  
    66
    77import java.io.IOException;
     8import java.io.InputStream;
    89import java.io.InterruptedIOException;
    910import java.net.ConnectException;
     
    1112import java.net.ServerSocket;
    1213import java.net.Socket;
     14import java.util.List;
    1315import java.util.Properties;
    1416import java.util.Set;
     
    1719import net.i2p.I2PException;
    1820import net.i2p.client.I2PSession;
     21import net.i2p.client.I2PSessionException;
    1922import net.i2p.data.Destination;
    2023
     
    3639   
    3740    /**
     41     *  @return a new subsession, non-null
     42     *  @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session
     43     *                          and different signing keys
     44     *  @param opts subsession options if any, may be null
     45     *  @since 0.9.19
     46     */
     47    public I2PSession addSubsession(InputStream privateKeyStream, Properties opts) throws I2PSessionException;
     48   
     49    /**
     50     *  @since 0.9.19
     51     */
     52    public void removeSubsession(I2PSession session);
     53   
     54    /**
     55     *  @return a list of subsessions, non-null, does not include the primary session
     56     *  @since 0.9.19
     57     */
     58    public List<I2PSession> getSubsessions();
     59
     60    /**
    3861     * How long should we wait for the client to .accept() a socket before
    3962     * sending back a NACK/Close? 
  • apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java

    re8f4e19 r099515a  
    453453                buf.append("</td><td align=\"left\"><b><a href=\"tunnels#").append(h.toBase64().substring(0,4));
    454454                buf.append("\" target=\"_top\" title=\"").append(_("Show tunnels")).append("\">");
    455                 if (name.length() < 18)
     455                if (name.length() <= 20)
    456456                    buf.append(DataHelper.escapeHTML(name));
    457457                else
    458                     buf.append(DataHelper.escapeHTML(name.substring(0,15))).append("&hellip;");
     458                    buf.append(DataHelper.escapeHTML(name.substring(0,18))).append("&hellip;");
    459459                buf.append("</a></b></td>\n");
    460460                LeaseSet ls = _context.netDb().lookupLeaseSetLocally(h);
  • apps/streaming/java/src/net/i2p/client/streaming/impl/Connection.java

    re8f4e19 r099515a  
    875875    public void setOptions(ConnectionOptions opts) { _options = opts; }
    876876       
     877    /** @since 0.9.21 */
     878    public ConnectionManager getConnectionManager() { return _connectionManager; }
     879
    877880    public I2PSession getSession() { return _connectionManager.getSession(); }
    878881    public I2PSocketFull getSocket() { return _socket; }
  • apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionManager.java

    re8f4e19 r099515a  
    8989        int protocol = defaultOptions.getEnforceProtocol() ? I2PSession.PROTO_STREAMING : I2PSession.PROTO_ANY;
    9090        _session.addMuxedSessionListener(_messageHandler, protocol, defaultOptions.getLocalPort());
    91         _outboundQueue = new PacketQueue(_context, _session, this);
     91        _outboundQueue = new PacketQueue(_context, _session);
    9292        _recentlyClosed = new LHMCache<Long, Object>(32);
    9393        /** Socket timeout for accept() */
     
    430430                    try { Thread.sleep(remaining/4); } catch (InterruptedException ie) {}
    431431                } else {
    432                     con = new Connection(_context, this, _schedulerChooser, _timer, _outboundQueue, _conPacketHandler, opts, false);
     432                    con = new Connection(_context, this, _schedulerChooser, _timer,
     433                                         _outboundQueue, _conPacketHandler, opts, false);
    433434                    con.setRemotePeer(peer);
    434435                    assignReceiveStreamId(con);
     
    891892            req.pong(payload);
    892893    }
     894
     895    /**
     896     *  @since 0.9.20
     897     */
     898    @Override
     899    public String toString() {
     900        return "ConnectionManager for " + _session;
     901    }
    893902}
  • apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java

    re8f4e19 r099515a  
    11package net.i2p.client.streaming.impl;
    22
     3import java.io.ByteArrayInputStream;
     4import java.io.ByteArrayOutputStream;
    35import java.io.IOException;
     6import java.io.InputStream;
    47import java.net.ConnectException;
    58import java.net.NoRouteToHostException;
     
    811import java.net.SocketTimeoutException;
    912import java.util.HashSet;
     13import java.util.List;
     14import java.util.Map;
    1015import java.util.Properties;
    1116import java.util.Set;
     17import java.util.concurrent.ConcurrentHashMap;
    1218import java.util.concurrent.atomic.AtomicBoolean;
    1319import java.util.concurrent.atomic.AtomicInteger;
     
    1521import net.i2p.I2PAppContext;
    1622import net.i2p.I2PException;
     23import net.i2p.client.I2PClient;
    1724import net.i2p.client.I2PSession;
    1825import net.i2p.client.I2PSessionException;
     
    2128import net.i2p.client.streaming.I2PSocketManager;
    2229import net.i2p.client.streaming.I2PSocketOptions;
     30import net.i2p.crypto.SigType;
     31import net.i2p.data.Certificate;
    2332import net.i2p.data.Destination;
     33import net.i2p.data.Hash;
     34import net.i2p.data.PrivateKey;
     35import net.i2p.data.PublicKey;
     36import net.i2p.data.SimpleDataStructure;
     37import net.i2p.util.ConvertToHash;
    2438import net.i2p.util.Log;
    2539
     
    3852    private final Log _log;
    3953    private final I2PSession _session;
     54    private final ConcurrentHashMap<I2PSession, ConnectionManager> _subsessions;
    4055    private final I2PServerSocketFull _serverSocket;
    4156    private StandardServerSocket _realServerSocket;
     
    4661    private final ConnectionManager _connectionManager;
    4762    private final AtomicBoolean _isDestroyed = new AtomicBoolean();
    48    
     63
     64    /** @since 0.9.20 */
     65    private static final Set<Hash> _dsaOnly = new HashSet<Hash>(16);
     66    private static final String[] DSA_ONLY_HASHES = {
     67        // list from http://zzz.i2p/topics/1682?page=1#p8414
     68        // bzr.welterde.i2p
     69        "Cvs1gCZTTkgD2Z2byh2J9atPmh5~I8~L7BNQnQl0hUE=",
     70        // docs.i2p2.i2p
     71        "WCXV87RdrF6j-mnn6qt7kVSBifHTlPL0PmVMFWwaolo=",
     72        // flibusta.i2p
     73        "yy2hYtqqfl84N9skwdRkeM7baFMXHKyDWU3XRShlEo8=",
     74        // forum.i2p
     75        "3t5Ar2NCTIOId70uzX2bZyJljR0aBogxMEzNyHirB7A=",
     76        // i2jump.i2p
     77        "9vaoGZbOaeqdRK2qEunlwRM9mUSW-I9R4OON35TDKK4=",
     78        // irc.welterde.i2p
     79        "5rjezx4McFk3bNhoJV-NTLlQW1AR~jiUcN6DOWMCCVc=",
     80        // lists.i2p2.i2p
     81        "qwtgoFoMSK0TOtbT4ovBX1jHUzCoZCPzrJVxjKD7RCg=",
     82        // mtn.i2p2.i2p
     83        "X5VDzYaoX9-P6bAWnrVSR5seGLkOeORP2l3Mh4drXPo=",
     84        // nntp.welterde.i2p
     85        "VXwmNIwMy1BcUVmut0oZ72jbWoqFzvxJukmS-G8kAAE=",
     86        // paste.i2p2.i2p
     87        "DoyMyUUgOSTddvRpqYfKHFPPjkkX~iQmResyfjjBYWs=",
     88        // syndie.wetlerde.i2p
     89        "xMxC54BFgyp-~zzrQI3F8m2CK--9XMcNmSAep6RH4Kk=",
     90        // ugha.i2p
     91        "zsu3WF~QLBxZXH-gHq9MuZE6y8ROZmMF7dA2MbMMKkY=",
     92        // tracker.welterde.i2p
     93        "EVkFgKkrDKyGfI7TIuDmlHoAmvHC~FbnY946DfujR0A=",
     94        // www.i2p2.i2p
     95        "im9gytzKT15mT1sB5LC9bHXCcwytQ4EPcrGQhoam-4w="
     96    };
     97   
     98    static {
     99        for (int i = 0; i < DSA_ONLY_HASHES.length; i++) {
     100            String s = DSA_ONLY_HASHES[i];
     101            Hash h = ConvertToHash.getHash(s);
     102            if (h != null)
     103                _dsaOnly.add(h);
     104            else
     105                System.out.println("Bad hash " + s);
     106        }
     107    }
     108
    49109    /**
    50110     * How long to wait for the client app to accept() before sending back CLOSE?
     
    81141        _context = context;
    82142        _session = session;
     143        _subsessions = new ConcurrentHashMap<I2PSession, ConnectionManager>(4);
    83144        _log = _context.logManager().getLog(I2PSocketManagerFull.class);
    84145       
     
    121182    }
    122183   
     184    /**
     185     *  @return a new subsession, non-null
     186     *  @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session
     187     *                          and different signing keys
     188     *  @param opts subsession options if any, may be null
     189     *  @since 0.9.19
     190     */
     191    public I2PSession addSubsession(InputStream privateKeyStream, Properties opts) throws I2PSessionException {
     192        if (privateKeyStream == null) {
     193            // We don't actually need the same pubkey in the dest, just in the LS.
     194            // The dest one is unused. But this is how we find the LS keys
     195            // to reuse in RequestLeaseSetMessageHandler.
     196            ByteArrayOutputStream keyStream = new ByteArrayOutputStream(1024);
     197            try {
     198                SigType type = getSigType(opts);
     199                if (type != SigType.DSA_SHA1) {
     200                    // hassle, have to set up the padding and cert, see I2PClientImpl
     201                    throw new I2PSessionException("type " + type + " unsupported");
     202                }
     203                PublicKey pub = _session.getMyDestination().getPublicKey();
     204                PrivateKey priv = _session.getDecryptionKey();
     205                SimpleDataStructure[] keys = _context.keyGenerator().generateSigningKeys(type);
     206                pub.writeBytes(keyStream);
     207                keys[0].writeBytes(keyStream); // signing pub
     208                Certificate.NULL_CERT.writeBytes(keyStream);
     209                priv.writeBytes(keyStream);
     210                keys[1].writeBytes(keyStream); // signing priv
     211            } catch (Exception e) {
     212                throw new I2PSessionException("Error creating keys", e);
     213            }
     214            privateKeyStream = new ByteArrayInputStream(keyStream.toByteArray());
     215        }
     216        I2PSession rv = _session.addSubsession(privateKeyStream, opts);
     217        ConnectionOptions defaultOptions = new ConnectionOptions(opts);
     218        ConnectionManager connectionManager = new ConnectionManager(_context, rv, defaultOptions);
     219        ConnectionManager old = _subsessions.putIfAbsent(rv, connectionManager);
     220        if (old != null) {
     221            // shouldn't happen
     222            _session.removeSubsession(rv);
     223            connectionManager.shutdown();
     224            throw new I2PSessionException("dup");
     225        }
     226        if (_log.shouldLog(Log.WARN))
     227            _log.warn("Added subsession " + rv);
     228        return rv;
     229    }
     230
     231    /**
     232     *  @param opts may be null
     233     *  @since 0.9.20 copied from I2PSocketManagerFactory
     234     */
     235    private SigType getSigType(Properties opts) {
     236        if (opts != null) {
     237            String st = opts.getProperty(I2PClient.PROP_SIGTYPE);
     238            if (st != null) {
     239                SigType rv = SigType.parseSigType(st);
     240                if (rv != null && rv.isAvailable())
     241                    return rv;
     242                if (rv != null)
     243                    st = rv.toString();
     244                _log.logAlways(Log.WARN, "Unsupported sig type " + st +
     245                                         ", reverting to " + I2PClient.DEFAULT_SIGTYPE);
     246                // TODO throw instead?
     247            }
     248        }
     249        return I2PClient.DEFAULT_SIGTYPE;
     250    }
     251   
     252    /**
     253     *  Remove the subsession
     254     *
     255     *  @since 0.9.19
     256     */
     257    public void removeSubsession(I2PSession session) {
     258        _session.removeSubsession(session);
     259        ConnectionManager cm = _subsessions.remove(session);
     260        if (cm != null) {
     261            cm.shutdown();
     262            if (_log.shouldLog(Log.WARN))
     263                _log.warn("Removeed subsession " + session);
     264        } else {
     265            if (_log.shouldLog(Log.WARN))
     266                _log.warn("Subsession not found to remove " + session);
     267        }
     268    }
     269   
     270    /**
     271     *  @return a list of subsessions, non-null, does not include the primary session
     272     *  @since 0.9.19
     273     */
     274    public List<I2PSession> getSubsessions() {
     275        return _session.getSubsessions();
     276    }
     277   
    123278    public ConnectionManager getConnectionManager() {
    124279        return _connectionManager;
     
    263418
    264419    private void verifySession() throws I2PException {
     420        verifySession(_connectionManager);
     421    }
     422
     423    /** @since 0.9.20 */
     424    private void verifySession(ConnectionManager cm) throws I2PException {
    265425        if (_isDestroyed.get())
    266426            throw new I2PException("Session was closed");
    267         if (!_connectionManager.getSession().isClosed())
     427        if (!cm.getSession().isClosed())
    268428            return;
    269         _connectionManager.getSession().connect();
     429        cm.getSession().connect();
    270430    }
    271431   
     
    286446    public I2PSocket connect(Destination peer, I2PSocketOptions options)
    287447                             throws I2PException, NoRouteToHostException {
    288         verifySession();
    289448        if (options == null)
    290449            options = _defaultOptions;
     
    298457            _log.info("Connecting to " + peer.calculateHash().toBase64().substring(0,6)
    299458                      + " with options: " + opts);
     459        // pick the subsession here
     460        ConnectionManager cm = _connectionManager;
     461        if (!_subsessions.isEmpty()) {
     462            Hash h = peer.calculateHash();
     463            if (_dsaOnly.contains(h)) {
     464                // FIXME just taking the first one for now
     465                for (Map.Entry<I2PSession, ConnectionManager> e : _subsessions.entrySet()) {
     466                    if (e.getKey().getMyDestination().getSigType() == SigType.DSA_SHA1) {
     467                        cm = e.getValue();
     468                        break;
     469                    }
     470                }
     471            }
     472        }
     473        verifySession(cm);
    300474        // the following blocks unless connect delay > 0
    301         Connection con = _connectionManager.connect(peer, opts);
     475        Connection con = cm.connect(peer, opts);
    302476        if (con == null)
    303477            throw new TooManyStreamsException("Too many streams, max " + _defaultOptions.getMaxConns());
     
    382556        _connectionManager.setAllowIncomingConnections(false);
    383557        _connectionManager.shutdown();
     558        if (!_subsessions.isEmpty()) {
     559            for (I2PSession sess : _subsessions.keySet()) {
     560                 removeSubsession(sess);
     561            }
     562        }
     563
    384564        // should we destroy the _session too?
    385565        // yes, since the old lib did (and SAM wants it to, and i dont know why not)
  • apps/streaming/java/src/net/i2p/client/streaming/impl/MessageHandler.java

    re8f4e19 r099515a  
    5151     */
    5252    public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromPort, int toPort) {
    53         byte data[] = null;
     53        byte data[];
    5454        try {
    5555            data = session.receiveMessage(msgId);
     
    6060            return;
    6161        }
    62         if (data == null) return;
     62        if (data == null) {
     63            if (_log.shouldLog(Log.WARN))
     64                _log.warn("Received null data on " + session + " proto: " + proto +
     65                          " fromPort: " + fromPort + " toPort: " + toPort);
     66            return;
     67        }
     68        if (_log.shouldLog(Log.DEBUG))
     69            _log.debug("Received " + data.length + " bytes on " + session +
     70                       " (" + _manager + ')' +
     71                       " proto: " + proto +
     72                       " fromPort: " + fromPort + " toPort: " + toPort);
    6373        Packet packet = new Packet();
    6474        try {
  • apps/streaming/java/src/net/i2p/client/streaming/impl/MessageInputStream.java

    re8f4e19 r099515a  
    569569    public void close() {
    570570        synchronized (_dataLock) {
     571            if (_log.shouldLog(Log.DEBUG)) {
     572                StringBuilder buf = new StringBuilder(128);
     573                buf.append("close(), ready bytes: ");
     574                long available = 0;
     575                for (int i = 0; i < _readyDataBlocks.size(); i++)
     576                    available += _readyDataBlocks.get(i).getValid();
     577                available -= _readyDataBlockIndex;
     578                buf.append(available);
     579                buf.append(" blocks: ").append(_readyDataBlocks.size());
     580                buf.append(" not ready blocks: ");
     581                long notAvailable = 0;
     582                for (Long id : _notYetReadyBlocks.keySet()) {
     583                    ByteArray ba = _notYetReadyBlocks.get(id);
     584                    buf.append(id).append(" ");
     585                    if (ba != null)
     586                        notAvailable += ba.getValid();
     587                }
     588                buf.append("not ready bytes: ").append(notAvailable);
     589                buf.append(" highest ready block: ").append(_highestReadyBlockId);
     590                _log.debug(buf.toString());
     591            }
    571592            //while (_readyDataBlocks.size() > 0)
    572593            //    _cache.release((ByteArray)_readyDataBlocks.remove(0));
  • apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java

    re8f4e19 r099515a  
    767767        if (isFlagSet(FLAG_PROFILE_INTERACTIVE)) buf.append(" INTERACTIVE");
    768768        if (isFlagSet(FLAG_RESET)) buf.append(" RESET");
    769         if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) buf.append(" SIG");
     769        if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) buf.append(" SIG ").append(_optionSignature.length());
    770770        if (isFlagSet(FLAG_SIGNATURE_REQUESTED)) buf.append(" SIGREQ");
    771771        if (isFlagSet(FLAG_SYNCHRONIZE)) buf.append(" SYN");
  • apps/streaming/java/src/net/i2p/client/streaming/impl/PacketQueue.java

    re8f4e19 r099515a  
    3030    private final Log _log;
    3131    private final I2PSession _session;
    32     private final ConnectionManager _connectionManager;
    3332    private final ByteCache _cache = ByteCache.getInstance(64, 36*1024);
    3433    private final Map<Long, Connection> _messageStatusMap;
     
    4746    private static final boolean ENABLE_STATUS_LISTEN = true;
    4847
    49     public PacketQueue(I2PAppContext context, I2PSession session, ConnectionManager mgr) {
     48    public PacketQueue(I2PAppContext context, I2PSession session) {
    5049        _context = context;
    5150        _session = session;
    52         _connectionManager = mgr;
    5351        _log = context.logManager().getLog(PacketQueue.class);
    5452        _messageStatusMap = new ConcurrentHashMap<Long, Connection>(16);
     
    200198            packet.incrementSends();
    201199            Connection c = packet.getConnection();
    202             String suffix = (c != null ? "wsize " + c.getOptions().getWindowSize() + " rto " + c.getOptions().getRTO() : null);
    203             if (_log.shouldDebug())
    204                 _connectionManager.getPacketHandler().displayPacket(packet, "SEND", suffix);
     200            if (c != null) {
     201                String suffix = "wsize " + c.getOptions().getWindowSize() + " rto " + c.getOptions().getRTO();
     202                c.getConnectionManager().getPacketHandler().displayPacket(packet, "SEND", suffix);
     203            }
    205204            if (I2PSocketManagerFull.pcapWriter != null &&
    206205                _context.getBooleanProperty(I2PSocketManagerFull.PROP_PCAP))
  • core/java/src/net/i2p/client/I2PSession.java

    re8f4e19 r099515a  
    1010 */
    1111
     12import java.io.InputStream;
     13import java.util.List;
    1214import java.util.Properties;
    1315import java.util.Set;
     
    2224 * <p>Define the standard means of sending and receiving messages on the
    2325 * I2P network by using the I2CP (the client protocol).  This is done over a
    24  * bidirectional TCP socket and never sends any private keys.
     26 * bidirectional TCP socket.
    2527 *
    2628 * End to end encryption in I2PSession was disabled in release 0.6.
     
    248250     */
    249251    public void destroySession() throws I2PSessionException;
     252   
     253    /**
     254     *  @return a new subsession, non-null
     255     *  @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session
     256     *                          and different signing keys
     257     *  @param opts subsession options if any, may be null
     258     *  @since 0.9.19
     259     */
     260    public I2PSession addSubsession(InputStream privateKeyStream, Properties opts) throws I2PSessionException;
     261   
     262    /**
     263     *  @return a list of subsessions, non-null, does not include the primary session
     264     *  @since 0.9.19
     265     */
     266    public void removeSubsession(I2PSession session);
     267   
     268    /**
     269     *  @return a list of subsessions, non-null, does not include the primary session
     270     *  @since 0.9.19
     271     */
     272    public List<I2PSession> getSubsessions();
    250273
    251274    /**
  • core/java/src/net/i2p/client/I2PSessionDemultiplexer.java

    re8f4e19 r099515a  
    7575     */
    7676    public void addListener(I2PSessionListener l, int proto, int port) {
    77         _listeners.put(key(proto, port), new NoPortsListener(l));
     77        I2PSessionListener old = _listeners.put(key(proto, port), new NoPortsListener(l));
     78        if (old != null && _log.shouldLog(Log.WARN))
     79            _log.warn("Listener " + l + " replaces " + old + " for proto: " + proto + " port: " + port);
    7880    }
    7981
     
    8385     */
    8486    public void addMuxedListener(I2PSessionMuxedListener l, int proto, int port) {
    85         _listeners.put(key(proto, port), l);
     87        I2PSessionListener old = _listeners.put(key(proto, port), l);
     88        if (old != null && _log.shouldLog(Log.WARN))
     89            _log.warn("Listener " + l + " replaces " + old + " for proto: " + proto + " port: " + port);
    8690    }
    8791
  • core/java/src/net/i2p/client/I2PSessionImpl.java

    re8f4e19 r099515a  
    2424import java.util.Properties;
    2525import java.util.concurrent.ConcurrentHashMap;
     26import java.util.concurrent.CopyOnWriteArrayList;
    2627import java.util.concurrent.LinkedBlockingQueue;
    2728import java.util.concurrent.atomic.AtomicInteger;
     
    4445import net.i2p.data.i2cp.MessagePayloadMessage;
    4546import net.i2p.data.i2cp.SessionId;
     47import net.i2p.data.i2cp.SessionStatusMessage;
    4648import net.i2p.internal.I2CPMessageQueue;
    4749import net.i2p.internal.InternalClientManager;
     
    8284    private volatile LeaseSet _leaseSet;
    8385
     86    // subsession stuff
     87    // registered subsessions
     88    private final List<SubSession> _subsessions;
     89    // established subsessions
     90    private final ConcurrentHashMap<SessionId, SubSession> _subsessionMap;
     91    private final Object _subsessionLock = new Object();
     92    private static final String MIN_SUBSESSION_VERSION = "0.9.19";
     93    private volatile boolean _routerSupportsSubsessions;
     94
    8495    /** hostname of router - will be null if in RouterContext */
    8596    protected final String _hostname;
     
    187198                                     (routerVersion != null && routerVersion.length() > 0 &&
    188199                                      VersionComparator.comp(routerVersion, MIN_HOST_LOOKUP_VERSION) >= 0);
     200        _routerSupportsSubsessions = _context.isRouterContext() ||
     201                                     (routerVersion != null && routerVersion.length() > 0 &&
     202                                      VersionComparator.comp(routerVersion, MIN_SUBSESSION_VERSION) >= 0);
    189203        synchronized (_stateLock) {
    190204            if (_state == State.OPENING) {
     
    204218    protected I2PSessionImpl(I2PAppContext context, Properties options,
    205219                             I2PClientMessageHandlerMap handlerMap) {
    206         this(context, options, handlerMap, false);
    207     }
    208    
     220        this(context, options, handlerMap, null, false);
     221    }
     222
     223    /*
     224     * For extension by SubSession via I2PSessionMuxedImpl and I2PSessionImpl2
     225     *
     226     * @param destKeyStream stream containing the private key data,
     227     *                             format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
     228     * @param options set of options to configure the router with, if null will use System properties
     229     * @since 0.9.19
     230     */
     231    protected I2PSessionImpl(I2PSessionImpl primary, InputStream destKeyStream, Properties options) throws I2PSessionException {
     232        this(primary.getContext(), options, primary.getHandlerMap(), primary.getProducer(), true);
     233        _availabilityNotifier = new AvailabilityNotifier();
     234        try {
     235            readDestination(destKeyStream);
     236        } catch (DataFormatException dfe) {
     237            throw new I2PSessionException("Error reading the destination key stream", dfe);
     238        } catch (IOException ioe) {
     239            throw new I2PSessionException("Error reading the destination key stream", ioe);
     240        }
     241    }
     242
    209243    /**
    210244     * Basic setup of finals
     
    212246     */
    213247    private I2PSessionImpl(I2PAppContext context, Properties options,
    214                            I2PClientMessageHandlerMap handlerMap, boolean hasDest) {
     248                           I2PClientMessageHandlerMap handlerMap,
     249                           I2CPMessageProducer producer,
     250                           boolean hasDest) {
    215251        _context = context;
    216252        _handlerMap = handlerMap;
    217253        _log = context.logManager().getLog(getClass());
     254        _subsessions = new CopyOnWriteArrayList<SubSession>();
     255        _subsessionMap = new ConcurrentHashMap<SessionId, SubSession>(4);
    218256        if (options == null)
    219257            options = (Properties) System.getProperties().clone();
     
    223261        _fastReceive = Boolean.parseBoolean(_options.getProperty(I2PClient.PROP_FAST_RECEIVE));
    224262        if (hasDest) {
    225             _producer = new I2CPMessageProducer(context);
     263            _producer = producer;
    226264            _availableMessages = new ConcurrentHashMap<Long, MessagePayloadMessage>();
    227265            _myDestination = new Destination();
     
    237275        _routerSupportsFastReceive = _context.isRouterContext();
    238276        _routerSupportsHostLookup = _context.isRouterContext();
     277        _routerSupportsSubsessions = _context.isRouterContext();
    239278    }
    240279
     
    248287     *                             format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
    249288     * @param options set of options to configure the router with, if null will use System properties
    250      * @throws I2PSessionException if there is a problem loading the private keys or
     289     * @throws I2PSessionException if there is a problem loading the private keys
    251290     */
    252291    public I2PSessionImpl(I2PAppContext context, InputStream destKeyStream, Properties options) throws I2PSessionException {
    253         this(context, options, new I2PClientMessageHandlerMap(context), true);
     292        this(context, options, new I2PClientMessageHandlerMap(context), new I2CPMessageProducer(context), true);
    254293        _availabilityNotifier = new AvailabilityNotifier();
    255294        try {
     
    259298        } catch (IOException ioe) {
    260299            throw new I2PSessionException("Error reading the destination key stream", ioe);
     300        }
     301    }
     302   
     303    /**
     304     *  Router must be connected or was connected... for now.
     305     *
     306     *  @return a new subsession, non-null
     307     *  @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session
     308     *                          and different signing keys
     309     *  @param opts subsession options if any, may be null
     310     *  @since 0.9.19
     311     */
     312    public I2PSession addSubsession(InputStream privateKeyStream, Properties opts) throws I2PSessionException {
     313        if (!_routerSupportsSubsessions)
     314            throw new I2PSessionException("Router does not support subsessions");
     315        SubSession sub;
     316        synchronized(_subsessionLock) {
     317            if (_subsessions.size() > _subsessionMap.size())
     318                throw new I2PSessionException("Subsession request already pending");
     319            sub = new SubSession(this, privateKeyStream, opts);
     320            for (SubSession ss : _subsessions) {
     321                 if (ss.getDecryptionKey().equals(sub.getDecryptionKey()) &&
     322                     ss.getPrivateKey().equals(sub.getPrivateKey())) {
     323                    throw new I2PSessionException("Dup subsession");
     324                }
     325            }
     326            _subsessions.add(sub);
     327        }
     328
     329        synchronized (_stateLock) {
     330            if (_state == State.OPEN) {
     331                _producer.connect(sub);
     332            } // else will be called in connect()
     333        }
     334        return sub;
     335    }
     336   
     337    /**
     338     *  @since 0.9.19
     339     */
     340    public void removeSubsession(I2PSession session) {
     341        if (!(session instanceof SubSession))
     342            return;
     343        synchronized(_subsessionLock) {
     344            _subsessions.remove(session);
     345            SessionId id = ((SubSession) session).getSessionId();
     346            if (id != null)
     347                _subsessionMap.remove(id);
     348            /// tell the subsession
     349            try {
     350                // doesn't really throw
     351                session.destroySession();
     352            } catch (I2PSessionException ise) {}
     353        }
     354    }
     355   
     356    /**
     357     *  @return a list of subsessions, non-null, does not include the primary session
     358     *  @since 0.9.19
     359     */
     360    public List<I2PSession> getSubsessions() {
     361        synchronized(_subsessionLock) {
     362            return new ArrayList<I2PSession>(_subsessions);
    261363        }
    262364    }
     
    554656            startVerifyUsage();
    555657            success = true;
     658
     659            // now send CreateSessionMessages for all subsessions, one at a time, must wait for each response
     660            synchronized(_subsessionLock) {
     661                for (SubSession ss : _subsessions) {
     662                   if (_log.shouldLog(Log.INFO))
     663                       _log.info(getPrefix() + "Connecting subsession " + ss);
     664                    _producer.connect(ss);
     665                }
     666            }
     667
    556668        } catch (InterruptedException ie) {
    557669            throw new I2PSessionException("Interrupted", ie);
     
    764876     * The I2CPMessageEventListener callback.
    765877     * Recieve notification of some I2CP message and handle it if possible.
     878     *
     879     * We route the message based on message type AND session ID.
     880     *
     881     * The following types never contain a session ID and are not routable to
     882     * a subsession:
     883     *     BandwidthLimitsMessage, DestReplyMessage
     884     *
     885     * The following types may not ontain a valid session ID
     886     * even when intended for a subsession, so we must take special care:
     887     *     SessionStatusMessage
     888     *
    766889     * @param reader unused
    767890     */
    768891    public void messageReceived(I2CPMessageReader reader, I2CPMessage message) {
    769         I2CPMessageHandler handler = _handlerMap.getHandler(message.getType());
    770         if (handler == null) {
    771             if (_log.shouldLog(Log.WARN))
    772                 _log.warn(getPrefix() + "Unknown message or unhandleable message received: type = "
    773                           + message.getType());
     892        int type = message.getType();
     893        SessionId id = message.sessionId();
     894        if (id == null || id.equals(_sessionId) ||
     895            (_sessionId == null && id != null && type == SessionStatusMessage.MESSAGE_TYPE)) {
     896            // it's for us
     897            I2CPMessageHandler handler = _handlerMap.getHandler(type);
     898            if (handler != null) {
     899                if (_log.shouldLog(Log.DEBUG))
     900                    _log.debug(getPrefix() + "Message received of type " + type
     901                               + " to be handled by " + handler.getClass().getSimpleName());
     902                handler.handleMessage(message, this);
     903            } else {
     904                if (_log.shouldLog(Log.WARN))
     905                    _log.warn(getPrefix() + "Unknown message or unhandleable message received: type = "
     906                              + type);
     907            }
    774908        } else {
    775             if (_log.shouldLog(Log.DEBUG))
    776                 _log.debug(getPrefix() + "Message received of type " + message.getType()
    777                            + " to be handled by " + handler.getClass().getSimpleName());
    778             handler.handleMessage(message, this);
     909            SubSession sub = _subsessionMap.get(id);
     910            if (sub != null) {
     911                // it's for a subsession
     912                if (_log.shouldLog(Log.DEBUG))
     913                    _log.debug(getPrefix() + "Message received of type " + type
     914                               + " to be handled by " + sub);
     915                sub.messageReceived(reader, message);
     916            } else if (id != null && type == SessionStatusMessage.MESSAGE_TYPE) {
     917                // look for a subsession without a session
     918                synchronized (_subsessionLock) {
     919                    for (SubSession sess : _subsessions) {
     920                        if (sess.getSessionId() == null) {
     921                            sess.messageReceived(reader, message);
     922                            id = sess.getSessionId();
     923                            if (id != null) {
     924                                if (id.equals(_sessionId)) {
     925                                    // shouldnt happen
     926                                    sess.setSessionId(null);
     927                                    if (_log.shouldLog(Log.WARN))
     928                                        _log.warn("Dup or our session id " + id);
     929                                } else {
     930                                    SubSession old = _subsessionMap.putIfAbsent(id, sess);
     931                                    if (old != null) {
     932                                        // shouldnt happen
     933                                        sess.setSessionId(null);
     934                                        if (_log.shouldLog(Log.WARN))
     935                                            _log.warn("Dup session id " + id);
     936                                    }
     937                                }
     938                            }
     939                            return;
     940                        }
     941                        if (_log.shouldLog(Log.WARN))
     942                            _log.warn(getPrefix() + "No session " + id + " to handle message: type = "
     943                                      + type);
     944                    }
     945                }
     946            } else {
     947                // it's for nobody
     948                if (_log.shouldLog(Log.WARN))
     949                    _log.warn(getPrefix() + "No session " + id + " to handle message: type = "
     950                              + type);
     951            }
    779952        }
    780953    }
     
    810983     */
    811984    I2CPMessageProducer getProducer() { return _producer; }
     985
     986    /**
     987     *  For Subsessions
     988     *  @since 0.9.19
     989     */
     990    I2PClientMessageHandlerMap getHandlerMap() { return _handlerMap; }
     991
     992    /**
     993     *  For Subsessions
     994     *  @since 0.9.19
     995     */
     996    I2PAppContext getContext() { return _context; }
    812997
    813998    /**
     
    9241109            _availabilityNotifier.stopNotifying();
    9251110        closeSocket();
     1111        _subsessionMap.clear();
    9261112        if (_sessionListener != null) _sessionListener.disconnected(this);
    9271113    }
  • core/java/src/net/i2p/client/I2PSessionImpl2.java

    re8f4e19 r099515a  
    5151    private static final long REMOVE_EXPIRED_TIME = 63*1000;
    5252
    53      /**
    54       * for extension by SimpleSession (no dest)
    55       */
     53    /**
     54     * for extension by SimpleSession (no dest)
     55     */
    5656    protected I2PSessionImpl2(I2PAppContext context, Properties options,
    5757                              I2PClientMessageHandlerMap handlerMap) {
     
    6262
    6363    /**
     64     * for extension by I2PSessionMuxedImpl
     65     *
    6466     * Create a new session, reading the Destination, PrivateKey, and SigningPrivateKey
    6567     * from the destKeyStream, and using the specified options to connect to the router
     
    6870     *                             format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
    6971     * @param options set of options to configure the router with, if null will use System properties
    70      * @throws I2PSessionException if there is a problem loading the private keys or
    71      */
    72     public I2PSessionImpl2(I2PAppContext ctx, InputStream destKeyStream, Properties options) throws I2PSessionException {
     72     * @throws I2PSessionException if there is a problem loading the private keys
     73     */
     74    protected I2PSessionImpl2(I2PAppContext ctx, InputStream destKeyStream, Properties options) throws I2PSessionException {
    7375        super(ctx, destKeyStream, options);
    7476        _sendingStates = new ConcurrentHashMap<Long, MessageState>(32);
     
    9193        _context.statManager().createRateStat("i2cp.receiveStatusTime.5", "How long it took to get status=5 back", "i2cp", new long[] { 10*60*1000 });
    9294        //_context.statManager().createRateStat("i2cp.receiveStatusTime", "How long it took to get any status", "i2cp", new long[] { 10*60*1000 });
     95        _context.statManager().createRateStat("i2cp.tx.msgCompressed", "compressed size transferred", "i2cp", new long[] { 30*60*1000 });
     96        _context.statManager().createRateStat("i2cp.tx.msgExpanded", "size before compression", "i2cp", new long[] { 30*60*1000 });
     97    }
     98
     99    /*
     100     * For extension by SubSession via I2PSessionMuxedImpl
     101     *
     102     * @param destKeyStream stream containing the private key data,
     103     *                             format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
     104     * @param options set of options to configure the router with, if null will use System properties
     105     * @since 0.9.19
     106     */
     107    protected I2PSessionImpl2(I2PSessionImpl primary, InputStream destKeyStream, Properties options) throws I2PSessionException {
     108        super(primary, destKeyStream, options);
     109        _sendingStates = new ConcurrentHashMap<Long, MessageState>(32);
     110        _sendMessageNonce = new AtomicLong();
     111        _noEffort = "none".equals(getOptions().getProperty(I2PClient.PROP_RELIABILITY, "").toLowerCase(Locale.US));
     112        _context.statManager().createRateStat("i2cp.receiveStatusTime.1", "How long it took to get status=1 back", "i2cp", new long[] { 10*60*1000 });
     113        _context.statManager().createRateStat("i2cp.receiveStatusTime.4", "How long it took to get status=4 back", "i2cp", new long[] { 10*60*1000 });
     114        _context.statManager().createRateStat("i2cp.receiveStatusTime.5", "How long it took to get status=5 back", "i2cp", new long[] { 10*60*1000 });
    93115        _context.statManager().createRateStat("i2cp.tx.msgCompressed", "compressed size transferred", "i2cp", new long[] { 30*60*1000 });
    94116        _context.statManager().createRateStat("i2cp.tx.msgExpanded", "size before compression", "i2cp", new long[] { 30*60*1000 });
  • core/java/src/net/i2p/client/I2PSessionMuxedImpl.java

    re8f4e19 r099515a  
    7979        // as well so we don't have to keep casting
    8080        _demultiplexer =  new I2PSessionDemultiplexer(ctx);
     81        super.setSessionListener(_demultiplexer);
     82        // discards the one in super(), sorry about that... (no it wasn't started yet)
     83        _availabilityNotifier = new MuxedAvailabilityNotifier();
     84    }
     85
     86    /*
     87     * For extension by SubSession
     88     *
     89     * @param destKeyStream stream containing the private key data,
     90     *                             format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
     91     * @param options set of options to configure the router with, if null will use System properties
     92     * @since 0.9.19
     93     */
     94    protected I2PSessionMuxedImpl(I2PSessionImpl primary, InputStream destKeyStream, Properties options) throws I2PSessionException {
     95        super(primary, destKeyStream, options);
     96        // also stored in _sessionListener but we keep it in _demultipexer
     97        // as well so we don't have to keep casting
     98        _demultiplexer =  new I2PSessionDemultiplexer(primary.getContext());
    8199        super.setSessionListener(_demultiplexer);
    82100        // discards the one in super(), sorry about that... (no it wasn't started yet)
     
    316334    protected class MuxedAvailabilityNotifier extends AvailabilityNotifier {
    317335        private final LinkedBlockingQueue<MsgData> _msgs;
    318         private volatile boolean _alive = false;
     336        private volatile boolean _alive;
    319337        private static final int POISON_SIZE = -99999;
    320         private final AtomicBoolean stopping = new AtomicBoolean(false);
     338        private final AtomicBoolean stopping = new AtomicBoolean();
    321339
    322340        public MuxedAvailabilityNotifier() {
     
    326344        @Override
    327345        public void stopNotifying() {
    328             boolean again = true;
    329346            synchronized (stopping) {
    330347                if( !stopping.getAndSet(true)) {
    331                     if (_alive == true) {
     348                    _msgs.clear();
     349                    if (_alive) {
    332350                        // System.out.println("I2PSessionMuxedImpl.stopNotifying()");
    333                         _msgs.clear();
     351                        boolean again = true;
    334352                        while(again) {
    335353                            try {
     
    341359                            }
    342360                        }
     361                        _alive = false;
    343362                    }
    344                     _alive = false;
    345363                    stopping.set(false);
    346364                }
     
    356374                _msgs.put(new MsgData((int)(msgId & 0xffffffff), size, proto, fromPort, toPort));
    357375            } catch (InterruptedException ie) {}
     376            if (!_alive && _log.shouldLog(Log.WARN))
     377                _log.warn(getPrefix() + "message available but notifier not running");
    358378        }
    359379
    360380        @Override
    361381        public void run() {
    362             MsgData msg;
     382            if (_log.shouldLog(Log.DEBUG))
     383                _log.debug(getPrefix() + "starting muxed availability notifier");
     384            _msgs.clear();
    363385            _alive=true;
    364386            while (_alive) {
     387                MsgData msg;
    365388                try {
    366389                    msg = _msgs.take();
    367390                } catch (InterruptedException ie) {
    368                     _log.debug("I2PSessionMuxedImpl.run() InterruptedException " + String.valueOf(_msgs.size()) + " Messages, Alive " + _alive);
     391                    if (_log.shouldLog(Log.DEBUG))
     392                        _log.debug("I2PSessionMuxedImpl.run() InterruptedException " +
     393                                    String.valueOf(_msgs.size()) + " Messages, Alive " + _alive);
    369394                    continue;
    370395                }
  • core/java/src/net/i2p/client/MessagePayloadMessageHandler.java

    re8f4e19 r099515a  
    3434    public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
    3535        if (_log.shouldLog(Log.DEBUG))
    36             _log.debug("Handle message " + message);
     36            _log.debug("Handle message " + message + " for session " + session);
    3737        try {
    3838            MessagePayloadMessage msg = (MessagePayloadMessage) message;
  • core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java

    re8f4e19 r099515a  
    8989            PrivateKey privKey = null;
    9090            SigningPrivateKey signingPrivKey = null;
    91             boolean useOldKeys;
    9291            if (spk != null && sspk != null) {
    93                 useOldKeys = true;
     92                boolean useOldKeys = true;
    9493                int colon = sspk.indexOf(':');
    9594                SigType type = dest.getSigType();
     
    112111                    } catch (DataFormatException iae) {
    113112                        useOldKeys = false;
     113                        signingPrivKey = null;
    114114                    }
    115115                }
     
    119119                        privKey.fromBase64(spk);
    120120                    } catch (DataFormatException iae) {
    121                         useOldKeys = false;
     121                        privKey = null;
    122122                    }
    123123                }
     124            }
     125            if (privKey == null && !_existingLeaseSets.isEmpty()) {
     126                // look for keypair from another dest using same pubkey
     127                PublicKey pk = dest.getPublicKey();
     128                for (Map.Entry<Destination, LeaseInfo> e : _existingLeaseSets.entrySet()) {
     129                    if (pk.equals(e.getKey().getPublicKey())) {
     130                        privKey = e.getValue().getPrivateKey();
     131                        if (_log.shouldLog(Log.DEBUG))
     132                            _log.debug("Creating new leaseInfo keys for " + dest + " with private key from " + e.getKey());
     133                        break;
     134                    }
     135                }
     136            }
     137            if (privKey != null) {
     138                if (signingPrivKey != null) {
     139                    li = new LeaseInfo(privKey, signingPrivKey);
     140                    if (_log.shouldLog(Log.DEBUG))
     141                        _log.debug("Creating new leaseInfo keys for " + dest + " WITH configured private keys");
     142                } else {
     143                    li = new LeaseInfo(privKey, dest);
     144                }
    124145            } else {
    125                 useOldKeys = false;
    126             }
    127             if (useOldKeys)
    128                 li = new LeaseInfo(privKey, signingPrivKey);
    129             else
    130146                li = new LeaseInfo(dest);
     147                if (_log.shouldLog(Log.DEBUG))
     148                    _log.debug("Creating new leaseInfo keys for " + dest + " without configured private keys");
     149            }
    131150            _existingLeaseSets.put(dest, li);
    132             if (_log.shouldLog(Log.DEBUG))
    133                 _log.debug("Creating new leaseInfo keys for " 
    134                            + dest + " using configured private keys? " + useOldKeys);
    135151        } else {
    136152            if (_log.shouldLog(Log.DEBUG))
     
    179195        private final SigningPrivateKey _signingPrivKey;
    180196
     197        /**
     198         *  New keys
     199         */
    181200        public LeaseInfo(Destination dest) {
    182201            SimpleDataStructure encKeys[] = KeyGenerator.getInstance().generatePKIKeys();
     
    195214
    196215        /**
     216         *  Existing keys
    197217         *  @since 0.9.18
    198218         */
     
    202222            _signingPubKey = KeyGenerator.getSigningPublicKey(signingPrivKey);
    203223            _signingPrivKey = signingPrivKey;
     224        }
     225
     226        /**
     227         *  Existing crypto key, new signing key
     228         *  @since 0.9.20
     229         */
     230        public LeaseInfo(PrivateKey privKey, Destination dest) {
     231            SimpleDataStructure signKeys[];
     232            try {
     233                signKeys = KeyGenerator.getInstance().generateSigningKeys(dest.getSigningPublicKey().getType());
     234            } catch (GeneralSecurityException gse) {
     235                throw new IllegalStateException(gse);
     236            }
     237            _pubKey = KeyGenerator.getPublicKey(privKey);
     238            _privKey = privKey;
     239            _signingPubKey = (SigningPublicKey) signKeys[0];
     240            _signingPrivKey = (SigningPrivateKey) signKeys[1];
    204241        }
    205242
  • core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java

    re8f4e19 r099515a  
    3636
    3737    public SessionId getSessionId() {
     38        return _sessionId;
     39    }
     40
     41    @Override
     42    public SessionId sessionId() {
    3843        return _sessionId;
    3944    }
  • core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java

    re8f4e19 r099515a  
    3030
    3131    public SessionId getSessionId() {
     32        return _sessionId;
     33    }
     34
     35    /**
     36     * Return the SessionId for this message.
     37     *
     38     * @since 0.9.19
     39     */
     40    @Override
     41    public SessionId sessionId() {
    3242        return _sessionId;
    3343    }
  • core/java/src/net/i2p/data/i2cp/HostLookupMessage.java

    re8f4e19 r099515a  
    7474
    7575    public SessionId getSessionId() {
     76        return _sessionId;
     77    }
     78
     79    /**
     80     * Return the SessionId for this message.
     81     *
     82     * @since 0.9.19
     83     */
     84    @Override
     85    public SessionId sessionId() {
    7686        return _sessionId;
    7787    }
  • core/java/src/net/i2p/data/i2cp/HostReplyMessage.java

    re8f4e19 r099515a  
    7575
    7676    /**
     77     * Return the SessionId for this message.
     78     *
     79     * @since 0.9.19
     80     */
     81    @Override
     82    public SessionId sessionId() {
     83        return _sessionId;
     84    }
     85
     86    /**
    7787     *  @return 0 to 2**32 - 1
    7888     */
  • core/java/src/net/i2p/data/i2cp/I2CPMessage.java

    re8f4e19 r099515a  
    6161
    6262    /**
    63      * Return the unique identifier for this type of APIMessage, as specified in the
     63     * Return the unique identifier for this type of message, as specified in the
    6464     * network specification document under #ClientAccessLayerMessages
    65      * @return unique identifier for this type of APIMessage
     65     * @return unique identifier for this type of message
    6666     */
    6767    public int getType();
     68
     69    /**
     70     * Return the SessionId for this type of message.
     71     * Most but not all message types include a SessionId.
     72     * The ones that do already define getSessionId(), but some return a SessionId and
     73     * some return a long, so we define a new method here.
     74     *
     75     * @return SessionId or null if this message type does not include a SessionId
     76     * @since 0.9.19
     77     */
     78    public SessionId sessionId();
    6879}
  • core/java/src/net/i2p/data/i2cp/I2CPMessageException.java

    re8f4e19 r099515a  
    1313
    1414/**
    15  * Represent an error serializing or deserializing an APIMessage
     15 * Represent an error serializing or deserializing a message
    1616 *
    1717 * @author jrandom
  • core/java/src/net/i2p/data/i2cp/I2CPMessageImpl.java

    re8f4e19 r099515a  
    128128        }
    129129    }
     130
     131    /**
     132     * Return the SessionId for this type of message.
     133     * Most but not all message types include a SessionId.
     134     * The ones that do already define getSessionId(), but some return a SessionId and
     135     * some return a long, so we define a new method here.
     136     *
     137     * @return null always. Extending classes with a SessionId must override.
     138     * @since 0.9.19
     139     */
     140    public SessionId sessionId() { return null; }
    130141}
  • core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java

    re8f4e19 r099515a  
    3636    public long getSessionId() {
    3737        return _sessionId;
     38    }
     39
     40    /**
     41     * Return the SessionId for this message.
     42     *
     43     * @since 0.9.19
     44     */
     45    @Override
     46    public SessionId sessionId() {
     47        return _sessionId >= 0 ? new SessionId(_sessionId) : null;
    3848    }
    3949
  • core/java/src/net/i2p/data/i2cp/MessageStatusMessage.java

    re8f4e19 r099515a  
    194194    }
    195195
     196    /**
     197     * Return the SessionId for this message.
     198     *
     199     * @since 0.9.19
     200     */
     201    @Override
     202    public SessionId sessionId() {
     203        return _sessionId >= 0 ? new SessionId(_sessionId) : null;
     204    }
     205
    196206    /** @param id 0-65535 */
    197207    public void setSessionId(long id) {
     
    276286        case STATUS_SEND_SUCCESS_LOCAL:
    277287            return "LOCAL SUCCESS      ";
     288        case STATUS_SEND_BEST_EFFORT_FAILURE:
     289            return "PROBABLE FAILURE   ";
     290        case STATUS_SEND_FAILURE_NO_TUNNELS:
     291            return "NO LOCAL TUNNELS   ";
     292        case STATUS_SEND_FAILURE_NO_LEASESET:
     293            return "LEASESET NOT FOUND ";
    278294        default:
    279295            return "SEND FAILURE CODE: " + status;
  • core/java/src/net/i2p/data/i2cp/ReceiveMessageBeginMessage.java

    re8f4e19 r099515a  
    3535    public long getSessionId() {
    3636        return _sessionId;
     37    }
     38
     39    /**
     40     * Return the SessionId for this message.
     41     *
     42     * @since 0.9.19
     43     */
     44    @Override
     45    public SessionId sessionId() {
     46        return _sessionId >= 0 ? new SessionId(_sessionId) : null;
    3747    }
    3848
  • core/java/src/net/i2p/data/i2cp/ReceiveMessageEndMessage.java

    re8f4e19 r099515a  
    3434    public long getSessionId() {
    3535        return _sessionId;
     36    }
     37
     38    /**
     39     * Return the SessionId for this message.
     40     *
     41     * @since 0.9.19
     42     */
     43    @Override
     44    public SessionId sessionId() {
     45        return _sessionId >= 0 ? new SessionId(_sessionId) : null;
    3646    }
    3747
  • core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java

    re8f4e19 r099515a  
    3131
    3232    public SessionId getSessionId() {
     33        return _sessionId;
     34    }
     35
     36    /**
     37     * Return the SessionId for this message.
     38     *
     39     * @since 0.9.19
     40     */
     41    @Override
     42    public SessionId sessionId() {
    3343        return _sessionId;
    3444    }
  • core/java/src/net/i2p/data/i2cp/ReportAbuseMessage.java

    re8f4e19 r099515a  
    3333
    3434    public SessionId getSessionId() {
     35        return _sessionId;
     36    }
     37
     38    /**
     39     * Return the SessionId for this message.
     40     *
     41     * @since 0.9.19
     42     */
     43    @Override
     44    public SessionId sessionId() {
    3545        return _sessionId;
    3646    }
  • core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java

    re8f4e19 r099515a  
    4343
    4444    public SessionId getSessionId() {
     45        return _sessionId;
     46    }
     47
     48    /**
     49     * Return the SessionId for this message.
     50     *
     51     * @since 0.9.19
     52     */
     53    @Override
     54    public SessionId sessionId() {
    4555        return _sessionId;
    4656    }
  • core/java/src/net/i2p/data/i2cp/RequestVariableLeaseSetMessage.java

    re8f4e19 r099515a  
    5353
    5454    public SessionId getSessionId() {
     55        return _sessionId;
     56    }
     57
     58    /**
     59     * Return the SessionId for this message.
     60     *
     61     * @since 0.9.19
     62     */
     63    @Override
     64    public SessionId sessionId() {
    5565        return _sessionId;
    5666    }
  • core/java/src/net/i2p/data/i2cp/SendMessageMessage.java

    re8f4e19 r099515a  
    3636
    3737    public SessionId getSessionId() {
     38        return _sessionId;
     39    }
     40
     41    /**
     42     * Return the SessionId for this message.
     43     *
     44     * @since 0.9.19
     45     */
     46    @Override
     47    public SessionId sessionId() {
    3848        return _sessionId;
    3949    }
  • core/java/src/net/i2p/data/i2cp/SessionStatusMessage.java

    re8f4e19 r099515a  
    4040
    4141    public SessionId getSessionId() {
     42        return _sessionId;
     43    }
     44
     45    /**
     46     * Return the SessionId for this message.
     47     *
     48     * @since 0.9.19
     49     */
     50    @Override
     51    public SessionId sessionId() {
    4252        return _sessionId;
    4353    }
  • router/java/src/net/i2p/router/TunnelManagerFacade.java

    re8f4e19 r099515a  
    147147     */
    148148    public void buildTunnels(Destination client, ClientTunnelSettings settings);
     149
     150    /**
     151     *  Add another destination to the same tunnels.
     152     *  Must have same encryption key an a different signing key.
     153     *  @throws IllegalArgumentException if not
     154     *  @return success
     155     *  @since 0.9.19
     156     */
     157    public boolean addAlias(Destination dest, ClientTunnelSettings settings, Destination existingClient);
     158
     159    /**
     160     *  Remove another destination to the same tunnels.
     161     *  @since 0.9.19
     162     */
     163    public void removeAlias(Destination dest);
    149164   
    150165    public TunnelPoolSettings getInboundSettings();
  • router/java/src/net/i2p/router/TunnelPoolSettings.java

    re8f4e19 r099515a  
    11package net.i2p.router;
    22
     3import java.util.Set;
    34import java.util.Locale;
    45import java.util.Map;
     
    78import net.i2p.data.Base64;
    89import net.i2p.data.Hash;
     10import net.i2p.util.ConcurrentHashSet;
    911import net.i2p.util.NativeBigInteger;
    1012import net.i2p.util.RandomSource;
     
    3234    private Hash _randomKey;
    3335    private int _priority;
     36    private final Set<Hash> _aliases;
     37    private Hash _aliasOf;
    3438   
    3539    /** prefix used to override the router's defaults for clients */
     
    120124        if (_isExploratory && !_isInbound)
    121125            _priority = EXPLORATORY_PRIORITY;
     126        if (!_isExploratory)
     127            _aliases = new ConcurrentHashSet<Hash>(4);
     128        else
     129            _aliases = null;
    122130    }
    123131   
     
    207215    /** what destination is this a client tunnel for (or null if exploratory) */
    208216    public Hash getDestination() { return _destination; }
     217   
     218    /**
     219     *  Other destinations that use the same tunnel (or null if exploratory)
     220     *  Modifiable, concurrent, not a copy
     221     *  @since 0.9.19
     222     */
     223    public Set<Hash> getAliases() {
     224        return _aliases;
     225    }
     226
     227    /**
     228     *  Other destination that this is an alias of (or null).
     229     *  If non-null, don't build tunnels.
     230     *  @since 0.9.19
     231     */
     232    public Hash getAliasOf() {
     233        return _aliasOf;
     234    }
     235
     236
     237    /**
     238     *  Set other destination that this is an alias of (or null).
     239     *  If non-null, don't build tunnels.
     240     *  @since 0.9.19
     241     */
     242    public void setAliasOf(Hash h) {
     243        _aliasOf = h;
     244    }
    209245
    210246    /**
     
    236272
    237273    public Properties getUnknownOptions() { return _unknownOptions; }
    238    
     274
    239275    /**
    240276     *  Defaults in props are NOT honored.
  • router/java/src/net/i2p/router/client/ClientConnectionRunner.java

    re8f4e19 r099515a  
    1717import java.util.concurrent.ConcurrentHashMap;
    1818import java.util.ArrayList;
     19import java.util.Iterator;
    1920import java.util.List;
    2021import java.util.Locale;
     
    4041import net.i2p.data.i2cp.SessionConfig;
    4142import net.i2p.data.i2cp.SessionId;
     43import net.i2p.data.i2cp.SessionStatusMessage;
    4244import net.i2p.router.Job;
    4345import net.i2p.router.JobImpl;
     
    5153/**
    5254 * Bridge the router and the client - managing state for a client.
     55 *
     56 * As of release 0.9.19, multiple sessions are supported on a single
     57 * I2CP connection. These sessions share tunnels and some configuration.
    5358 *
    5459 * @author jrandom
     
    6267    /** output stream of the socket that I2CP messages bound to the client should be written to */
    6368    private OutputStream _out;
    64     /** session ID of the current client */
    65     private SessionId _sessionId;
    66     /** user's config */
    67     private SessionConfig _config;
     69
     70    private final ConcurrentHashMap<Hash, SessionParams> _sessions;
     71
    6872    private String _clientVersion;
    6973    /**
     
    7276     */
    7377    private final Map<MessageId, Payload> _messages;
    74     /** lease set request state, or null if there is no request pending on at the moment */
    75     private LeaseRequestState _leaseRequest;
    7678    private int _consecutiveLeaseRequestFails;
    77     /** currently allocated leaseSet, or null if none is allocated */
    78     private LeaseSet _currentLeaseSet;
    7979    /**
    8080     *  Set of messageIds created but not yet ACCEPTED.
     
    8484    /** thingy that does stuff */
    8585    protected I2CPMessageReader _reader;
    86     /** just for this destination */
     86    /** Used for all sessions, which must all have the same crypto keys */
    8787    private SessionKeyManager _sessionKeyManager;
    8888    /**
     
    9292    private final List<MessageId> _alreadyProcessed;
    9393    private ClientWriterRunner _writer;
    94     private Hash _destHashCache;
    9594    /** are we, uh, dead */
    9695    private volatile boolean _dead;
     
    109108    private static final int MAX_LEASE_FAILS = 5;
    110109    private static final int BUF_SIZE = 32*1024;
     110    private static final int MAX_SESSIONS = 4;
    111111
    112112    /** @since 0.9.2 */
    113113    private static final String PROP_TAGS = "crypto.tagsToSend";
    114114    private static final String PROP_THRESH = "crypto.lowTagThreshold";
     115
     116    /**
     117     *  For multisession
     118     *  @since 0.9.19
     119     */
     120    private static class SessionParams {
     121        final Destination dest;
     122        final boolean isPrimary;
     123        SessionId sessionId;
     124        SessionConfig config;
     125        LeaseRequestState leaseRequest;
     126        LeaseSet currentLeaseSet;
     127
     128        SessionParams(Destination d, boolean isPrimary) {
     129            dest = d;
     130            this.isPrimary = isPrimary;
     131        }
     132    }
    115133
    116134    /**
     
    125143        // unused for fastReceive
    126144        _messages = new ConcurrentHashMap<MessageId, Payload>();
     145        _sessions = new ConcurrentHashMap<Hash, SessionParams>(4);
    127146        _alreadyProcessed = new ArrayList<MessageId>();
    128147        _acceptedPending = new ConcurrentHashSet<MessageId>();
     
    169188        if ((_context.router() == null || _context.router().isAlive()) &&
    170189            _log.shouldWarn())
    171             _log.warn("Stop the I2CP connection!  current leaseSet: "
    172                       + _currentLeaseSet, new Exception("Stop client connection"));
     190            _log.warn("Stop the I2CP connection!", new Exception("Stop client connection"));
    173191        _dead = true;
    174192        // we need these keys to unpublish the leaseSet
     
    181199            _sessionKeyManager.shutdown();
    182200        _manager.unregisterConnection(this);
    183         if (_currentLeaseSet != null)
    184             _context.netDb().unpublish(_currentLeaseSet);
    185         _leaseRequest = null;
     201        for (SessionParams sp : _sessions.values()) {
     202            LeaseSet ls = sp.currentLeaseSet;
     203            if (ls != null)
     204                _context.netDb().unpublish(ls);
     205        }
    186206        synchronized (_alreadyProcessed) {
    187207            _alreadyProcessed.clear();
    188208        }
    189         //_config = null;
    190         //_manager = null;
     209        _sessions.clear();
    191210    }
    192211   
    193212    /**
    194213     *  Current client's config,
    195      *  will be null before session is established
    196      */
    197     public SessionConfig getConfig() { return _config; }
     214     *  will be null if session not found
     215     *  IS subsession aware.
     216     *  @since 0.9.19 added hash param
     217     */
     218    public SessionConfig getConfig(Hash h) {
     219        SessionParams sp  = _sessions.get(h);
     220        if (sp == null)
     221            return null;
     222        return sp.config;
     223    }
     224   
     225    /**
     226     *  Current client's config,
     227     *  will be null if session not found
     228     *  IS subsession aware.
     229     *  @since 0.9.19 added id param
     230     */
     231    public SessionConfig getConfig(SessionId id) {
     232        for (SessionParams sp : _sessions.values()) {
     233            if (id.equals(sp.sessionId))
     234                return sp.config;
     235        }
     236        return null;
     237    }
     238   
     239    /**
     240     *  Primary client's config,
     241     *  will be null if session not set up
     242     *  @since 0.9.19
     243     */
     244    public SessionConfig getPrimaryConfig() {
     245        for (SessionParams sp : _sessions.values()) {
     246            if (sp.isPrimary)
     247                return sp.config;
     248        }
     249        return null;
     250    }
    198251
    199252    /**
     
    217270    public SessionKeyManager getSessionKeyManager() { return _sessionKeyManager; }
    218271
    219     /** currently allocated leaseSet */
    220     public LeaseSet getLeaseSet() { return _currentLeaseSet; }
    221     void setLeaseSet(LeaseSet ls) { _currentLeaseSet = ls; }
     272    /**
     273     *  Currently allocated leaseSet.
     274     *  IS subsession aware. Returns primary leaseset only.
     275     *  @return leaseSet or null if not yet set or unknown hash
     276     *  @since 0.9.19 added hash parameter
     277     */
     278    public LeaseSet getLeaseSet(Hash h) {
     279        SessionParams sp = _sessions.get(h);
     280        if (sp == null)
     281            return null;
     282        return sp.currentLeaseSet;
     283    }
     284
     285    /**
     286     *  Currently allocated leaseSet.
     287     *  IS subsession aware.
     288     */
     289    void setLeaseSet(LeaseSet ls) {
     290        Hash h = ls.getDestination().calculateHash();
     291        SessionParams sp = _sessions.get(h);
     292        if (sp == null)
     293            return;
     294        sp.currentLeaseSet = ls;
     295    }
    222296
    223297    /**
    224298     *  Equivalent to getConfig().getDestination().calculateHash();
    225299     *  will be null before session is established
    226      */
    227     public Hash getDestHash() { return _destHashCache; }
    228    
    229     /**
    230      * @return current client's sessionId or null if not yet set
    231      */
    232     SessionId getSessionId() { return _sessionId; }
     300     *  Not subsession aware. Returns random hash from the sessions.
     301     *  Don't use if you can help it.
     302     *
     303     *  @return primary hash or null if not yet set
     304     */
     305    public Hash getDestHash() {
     306        for (Hash h : _sessions.keySet()) {
     307            return h;
     308        }
     309        return null;
     310    }
     311
     312    /**
     313     *  Return the hash for the given ID
     314     *  @return hash or null if unknown
     315     *  @since 0.9.19
     316     */
     317    public Hash getDestHash(SessionId id) {
     318        for (Map.Entry<Hash, SessionParams> e : _sessions.entrySet()) {
     319            if (id.equals(e.getValue().sessionId))
     320                return e.getKey();
     321        }
     322        return null;
     323    }
     324
     325    /**
     326     *  Return the dest for the given ID
     327     *  @return dest or null if unknown
     328     *  @since 0.9.19
     329     */
     330    public Destination getDestination(SessionId id) {
     331        for (SessionParams sp : _sessions.values()) {
     332            if (id.equals(sp.sessionId))
     333                return sp.dest;
     334        }
     335        return null;
     336    }
     337   
     338    /**
     339     *  Subsession aware.
     340     *
     341     *  @param h the local target
     342     *  @return current client's sessionId or null if not yet set or not a valid hash
     343     *  @since 0.9.19
     344     */
     345    SessionId getSessionId(Hash h) {
     346        SessionParams sp = _sessions.get(h);
     347        if (sp == null)
     348            return null;
     349        return sp.sessionId;
     350    }
     351   
     352    /**
     353     *  Subsession aware.
     354     *
     355     *  @return all current client's sessionIds, non-null
     356     *  @since 0.9.19
     357     */
     358    List<SessionId> getSessionIds() {
     359        List<SessionId> rv = new ArrayList<SessionId>(_sessions.size());
     360        for (SessionParams sp : _sessions.values()) {
     361            SessionId id = sp.sessionId;
     362            if (id != null)
     363                rv.add(id);
     364        }
     365        return rv;
     366    }
     367   
     368    /**
     369     *  Subsession aware.
     370     *
     371     *  @return all current client's destinations, non-null
     372     *  @since 0.9.19
     373     */
     374    List<Destination> getDestinations() {
     375        List<Destination> rv = new ArrayList<Destination>(_sessions.size());
     376        for (SessionParams sp : _sessions.values()) {
     377            rv.add(sp.dest);
     378        }
     379        return rv;
     380    }
    233381
    234382    /**
    235383     *  To be called only by ClientManager.
    236384     *
     385     *  @param hash for the session
    237386     *  @throws IllegalStateException if already set
    238      */
    239     void setSessionId(SessionId id) {
    240         if (_sessionId != null)
     387     *  @since 0.9.19 added hash param
     388     */
     389    void setSessionId(Hash hash, SessionId id) {
     390        if (hash == null)
    241391            throw new IllegalStateException();
    242         _sessionId = id;
    243     }
    244 
    245     /** data for the current leaseRequest, or null if there is no active leaseSet request */
    246     LeaseRequestState getLeaseRequest() { return _leaseRequest; }
     392        SessionParams sp = _sessions.get(hash);
     393        if (sp == null || sp.sessionId != null)
     394            throw new IllegalStateException();
     395        sp.sessionId = id;
     396     }
     397   
     398    /**
     399     *  Kill the session. Caller must kill runner if none left.
     400     *
     401     *  @since 0.9.19
     402     */
     403    void removeSession(SessionId id) {
     404        boolean isPrimary = false;
     405        for (Iterator<SessionParams> iter = _sessions.values().iterator(); iter.hasNext(); ) {
     406            SessionParams sp = iter.next();
     407            if (id.equals(sp.sessionId)) {
     408                if (_log.shouldLog(Log.INFO))
     409                    _log.info("Destroying client session " + id);
     410                iter.remove();
     411                // Tell client manger
     412                _manager.unregisterSession(id, sp.dest);
     413                LeaseSet ls = sp.currentLeaseSet;
     414                if (ls != null)
     415                    _context.netDb().unpublish(ls);
     416                isPrimary = sp.isPrimary;
     417            }
     418        }
     419        if (isPrimary) {
     420            // kill all the others also
     421            for (SessionParams sp : _sessions.values()) {
     422                _manager.unregisterSession(id, sp.dest);
     423                LeaseSet ls = sp.currentLeaseSet;
     424                if (ls != null)
     425                    _context.netDb().unpublish(ls);
     426            }
     427        }
     428    }
     429
     430    /**
     431     *  Data for the current leaseRequest, or null if there is no active leaseSet request.
     432     *  Not subsession aware. Returns primary ID only.
     433     *  @since 0.9.19 added hash param
     434     */
     435    LeaseRequestState getLeaseRequest(Hash h) {
     436        SessionParams sp = _sessions.get(h);
     437        if (sp == null)
     438            return null;
     439        return sp.leaseRequest;
     440    }
    247441
    248442    /** @param req non-null */
    249443    public void failLeaseRequest(LeaseRequestState req) {
    250444        boolean disconnect = false;
     445        Hash h = req.getRequested().getDestination().calculateHash();
     446        SessionParams sp = _sessions.get(h);
     447        if (sp == null)
     448            return;
    251449        synchronized (this) {
    252             if (_leaseRequest == req) {
    253                 _leaseRequest = null;
     450            if (sp.leaseRequest == req) {
     451                sp.leaseRequest = null;
    254452                disconnect = ++_consecutiveLeaseRequestFails > MAX_LEASE_FAILS;
    255453            }
     
    292490     */
    293491    public int sessionEstablished(SessionConfig config) {
    294         _destHashCache = config.getDestination().calculateHash();
     492        Destination dest = config.getDestination();
     493        Hash destHash = dest.calculateHash();
    295494        if (_log.shouldLog(Log.DEBUG))
    296             _log.debug("SessionEstablished called for destination " + _destHashCache.toBase64());
    297         _config = config;
     495            _log.debug("SessionEstablished called for destination " + destHash);
     496        if (_sessions.size() > MAX_SESSIONS)
     497            return SessionStatusMessage.STATUS_REFUSED;
     498        boolean isPrimary = _sessions.isEmpty();
     499        if (!isPrimary) {
     500            // all encryption keys must be the same
     501            for (SessionParams sp : _sessions.values()) {
     502                if (!dest.getPublicKey().equals(sp.dest.getPublicKey()))
     503                    return SessionStatusMessage.STATUS_INVALID;
     504            }
     505        }
     506        SessionParams sp = new SessionParams(dest, isPrimary);
     507        sp.config = config;
     508        SessionParams old = _sessions.putIfAbsent(destHash, sp);
     509        if (old != null)
     510            return SessionStatusMessage.STATUS_INVALID;
    298511        // We process a few options here, but most are handled by the tunnel manager.
    299512        // The ones here can't be changed later.
    300513        Properties opts = config.getOptions();
    301         if (opts != null) {
     514        if (isPrimary && opts != null) {
    302515            _dontSendMSM = "none".equals(opts.getProperty(I2PClient.PROP_RELIABILITY, "").toLowerCase(Locale.US));
    303516            _dontSendMSMOnReceive = Boolean.parseBoolean(opts.getProperty(I2PClient.PROP_FAST_RECEIVE));
    304517        }
    305518        // per-destination session key manager to prevent rather easy correlation
    306         if (_sessionKeyManager == null) {
     519        if (isPrimary && _sessionKeyManager == null) {
    307520            int tags = TransientSessionKeyManager.DEFAULT_TAGS;
    308521            int thresh = TransientSessionKeyManager.LOW_THRESHOLD;
     
    318531            }
    319532            _sessionKeyManager = new TransientSessionKeyManager(_context, tags, thresh);
    320         } else {
    321             _log.error("SessionEstablished called for twice for destination " + _destHashCache.toBase64().substring(0,4));
    322         }
    323         return _manager.destinationEstablished(this);
     533        }
     534        return _manager.destinationEstablished(this, dest);
    324535    }
    325536   
     
    332543     *  Do not use for status = STATUS_SEND_ACCEPTED; use ackSendMessage() for that.
    333544     *
     545     *  @param dest the client
    334546     *  @param id the router's ID for this message
    335547     *  @param messageNonce the client's ID for this message
    336548     *  @param status see I2CP MessageStatusMessage for success/failure codes
    337549     */
    338     void updateMessageDeliveryStatus(MessageId id, long messageNonce, int status) {
     550    void updateMessageDeliveryStatus(Destination dest, MessageId id, long messageNonce, int status) {
    339551        if (_dead || messageNonce <= 0)
    340552            return;
    341         _context.jobQueue().addJob(new MessageDeliveryStatusUpdate(id, messageNonce, status));
     553        SessionParams sp = _sessions.get(dest.calculateHash());
     554        if (sp == null)
     555            return;
     556        SessionId sid = sp.sessionId;
     557        if (sid == null)
     558            return;  // sid = new SessionId(foo) ???
     559        _context.jobQueue().addJob(new MessageDeliveryStatusUpdate(sid, id, messageNonce, status));
    342560    }
    343561
     
    347565     */
    348566    void leaseSetCreated(LeaseSet ls) {
    349         LeaseRequestState state = null;
     567        Hash h = ls.getDestination().calculateHash();
     568        SessionParams sp = _sessions.get(h);
     569        if (sp == null)
     570            return;
     571        LeaseRequestState state;
    350572        synchronized (this) {
    351             state = _leaseRequest;
     573            state = sp.leaseRequest;
    352574            if (state == null) {
    353575                if (_log.shouldLog(Log.WARN))
     
    356578            } else {
    357579                state.setIsSuccessful(true);
    358                 _currentLeaseSet = ls;
     580                setLeaseSet(ls);
    359581                if (_log.shouldLog(Log.DEBUG))
    360582                    _log.debug("LeaseSet created fully: " + state + " / " + ls);
    361                 _leaseRequest = null;
     583                sp.leaseRequest = null;
    362584                _consecutiveLeaseRequestFails = 0;
    363585            }
     
    428650        if (_log.shouldLog(Log.DEBUG))
    429651            _log.debug("** Receiving message " + id.getMessageId() + " with payload of size "
    430                        + payload.getSize() + " for session " + _sessionId.getSessionId());
     652                       + payload.getSize() + " for session " + message.getSessionId());
    431653        //long beforeDistribute = _context.clock().now();
    432654        // the following blocks as described above
    433         SessionConfig cfg = _config;
    434         if (cfg != null)
    435             _manager.distributeMessage(cfg.getDestination(), dest, payload,
     655        Destination fromDest = getDestination(message.getSessionId());
     656        if (fromDest != null)
     657            _manager.distributeMessage(fromDest, dest, payload,
    436658                                       id, message.getNonce(), expiration, flags);
    437659        // else log error?
     
    453675     * @param nonce HIS id for the message
    454676     */
    455     void ackSendMessage(MessageId id, long nonce) {
     677    void ackSendMessage(SessionId sid, MessageId id, long nonce) {
    456678        if (_dontSendMSM || nonce == 0)
    457679            return;
    458         SessionId sid = _sessionId;
    459         if (sid == null) return;
    460680        if (_log.shouldLog(Log.DEBUG))
    461681            _log.debug("Acking message send [accepted]" + id + " / " + nonce + " for sessionId "
     
    481701     * Note that no failure indication is available.
    482702     * Fails silently on e.g. queue overflow to client, client dead, etc.
     703     *
     704     * @param toDest non-null
     705     * @param fromDest generally null when from remote, non-null if from local
    483706     */
    484707    void receiveMessage(Destination toDest, Destination fromDest, Payload payload) {
     
    491714   
    492715    /**
     716     * Asynchronously deliver the message to the current runner
     717     *
     718     * Note that no failure indication is available.
     719     * Fails silently on e.g. queue overflow to client, client dead, etc.
     720     *
     721     * @param toHash non-null
     722     * @param fromDest generally null when from remote, non-null if from local
     723     * @since 0.9.20
     724     */
     725    void receiveMessage(Hash toHash, Destination fromDest, Payload payload) {
     726        SessionParams sp = _sessions.get(toHash);
     727        if (sp == null) {
     728            if (_log.shouldLog(Log.WARN))
     729                _log.warn("No session found for receiveMessage()");
     730            return;
     731        }
     732        receiveMessage(sp.dest, fromDest, payload);
     733    }
     734   
     735    /**
    493736     * Send async abuse message to the client
    494737     *
    495738     */
    496     public void reportAbuse(String reason, int severity) {
     739    public void reportAbuse(Destination dest, String reason, int severity) {
    497740        if (_dead) return;
    498         _context.jobQueue().addJob(new ReportAbuseJob(_context, this, reason, severity));
     741        _context.jobQueue().addJob(new ReportAbuseJob(_context, this, dest, reason, severity));
    499742    }
    500743       
     
    505748     * block.
    506749     *
     750     * @param h the Destination's hash
    507751     * @param set LeaseSet with requested leases - this object must be updated to contain the
    508752     *            signed version (as well as any changed/added/removed Leases)
     753     *            The LeaseSet contains Leases and destination only, it is unsigned.
    509754     * @param expirationTime ms to wait before failing
    510755     * @param onCreateJob Job to run after the LeaseSet is authorized, null OK
    511756     * @param onFailedJob Job to run after the timeout passes without receiving authorization, null OK
    512757     */
    513     void requestLeaseSet(LeaseSet set, long expirationTime, Job onCreateJob, Job onFailedJob) {
     758    void requestLeaseSet(Hash h, LeaseSet set, long expirationTime, Job onCreateJob, Job onFailedJob) {
    514759        if (_dead) {
    515760            if (_log.shouldLog(Log.WARN))
     
    517762            if (onFailedJob != null)
    518763                _context.jobQueue().addJob(onFailedJob);
     764            return;
     765        }
     766        SessionParams sp = _sessions.get(h);
     767        if (sp == null) {
     768            if (_log.shouldLog(Log.WARN))
     769                _log.warn("Requesting leaseSet for an unknown sesssion");
    519770            return;
    520771        }
     
    529780        int leases = set.getLeaseCount();
    530781        // synch so _currentLeaseSet isn't changed out from under us
     782        LeaseSet current = null;
     783        Destination dest = sp.dest;
    531784        synchronized (this) {
    532             if (_currentLeaseSet != null && _currentLeaseSet.getLeaseCount() == leases) {
     785            current = sp.currentLeaseSet;
     786            if (current != null && current.getLeaseCount() == leases) {
    533787                for (int i = 0; i < leases; i++) {
    534                     if (! _currentLeaseSet.getLease(i).getTunnelId().equals(set.getLease(i).getTunnelId()))
     788                    if (! current.getLease(i).getTunnelId().equals(set.getLease(i).getTunnelId()))
    535789                        break;
    536                     if (! _currentLeaseSet.getLease(i).getGateway().equals(set.getLease(i).getGateway()))
     790                    if (! current.getLease(i).getGateway().equals(set.getLease(i).getGateway()))
    537791                        break;
    538792                    if (i == leases - 1) {
     
    547801        }
    548802        if (_log.shouldLog(Log.INFO))
    549             _log.info("Current leaseSet " + _currentLeaseSet + "\nNew leaseSet " + set);
    550         LeaseRequestState state = null;
     803            _log.info("Current leaseSet " + current + "\nNew leaseSet " + set);
     804        LeaseRequestState state;
    551805        synchronized (this) {
    552             state = _leaseRequest;
     806            state = sp.leaseRequest;
    553807            if (state != null) {
    554808                if (_log.shouldLog(Log.DEBUG))
     
    562816                } else {
    563817                    // ours is newer, so wait a few secs and retry
     818                    set.setDestination(dest);
    564819                    _context.simpleTimer2().addEvent(new Rerequest(set, expirationTime, onCreateJob, onFailedJob), 3*1000);
    565820                }
     
    567822                return; // already requesting
    568823            } else {
    569                 _leaseRequest = state = new LeaseRequestState(onCreateJob, onFailedJob, _context.clock().now() + expirationTime, set);
     824                set.setDestination(dest);
     825                sp.leaseRequest = state = new LeaseRequestState(onCreateJob, onFailedJob,
     826                                                                _context.clock().now() + expirationTime, set);
    570827                if (_log.shouldLog(Log.DEBUG))
    571828                    _log.debug("New request: " + state);
     
    581838        private final Job _onFailed;
    582839
     840        /** @param ls dest must be set */
    583841        public Rerequest(LeaseSet ls, long expirationTime, Job onCreate, Job onFailed) {
    584842            _ls = ls;
     
    589847
    590848        public void timeReached() {
    591             requestLeaseSet(_ls, _expirationTime, _onCreate, _onFailed);
     849            requestLeaseSet(_ls.getDestination().calculateHash(), _ls, _expirationTime, _onCreate, _onFailed);
    592850        }
    593851    }
     
    698956   
    699957    private class MessageDeliveryStatusUpdate extends JobImpl {
     958        private final SessionId _sessId;
    700959        private final MessageId _messageId;
    701960        private final long _messageNonce;
     
    711970         *  @param status see I2CP MessageStatusMessage for success/failure codes
    712971         */
    713         public MessageDeliveryStatusUpdate(MessageId id, long messageNonce, int status) {
     972        public MessageDeliveryStatusUpdate(SessionId sid, MessageId id, long messageNonce, int status) {
    714973            super(ClientConnectionRunner.this._context);
     974            _sessId = sid;
    715975            _messageId = id;
    716976            _messageNonce = messageNonce;
     
    728988            MessageStatusMessage msg = new MessageStatusMessage();
    729989            msg.setMessageId(_messageId.getMessageId());
    730             msg.setSessionId(_sessionId.getSessionId());
     990            msg.setSessionId(_sessId.getSessionId());
    731991            // has to be >= 0, it is initialized to -1
    732992            msg.setNonce(_messageNonce);
     
    739999                    _log.error("Abandon update for message " + _messageId + " to "
    7401000                          + MessageStatusMessage.getStatusString(msg.getStatus())
    741                           + " for session " + _sessionId.getSessionId());
     1001                          + " for " + _sessId);
    7421002                } else {
    7431003                    if (_log.shouldLog(Log.WARN))
    7441004                        _log.warn("Almost send an update for message " + _messageId + " to "
    7451005                          + MessageStatusMessage.getStatusString(msg.getStatus())
    746                           + " for session " + _sessionId.getSessionId()
     1006                          + " for " + _sessId
    7471007                          + " before they knew the messageId!  delaying .5s");
    7481008                    _lastTried = _context.clock().now();
     
    7791039                    _log.info("Updating message status for message " + _messageId + " to "
    7801040                              + MessageStatusMessage.getStatusString(msg.getStatus())
    781                               + " for session " + _sessionId.getSessionId()
     1041                              + " for " + _sessId
    7821042                              + " (with nonce=2), retrying after "
    7831043                              + (_context.clock().now() - _lastTried));
     
    7861046                    _log.debug("Updating message status for message " + _messageId + " to "
    7871047                               + MessageStatusMessage.getStatusString(msg.getStatus())
    788                                + " for session " + _sessionId.getSessionId() + " (with nonce=2)");
     1048                               + " for " + _sessId + " (with nonce=2)");
    7891049            }
    7901050
  • router/java/src/net/i2p/router/client/ClientManager.java

    re8f4e19 r099515a  
    5656    // Destination --> ClientConnectionRunner
    5757    // Locked for adds/removes but not lookups
     58    // If a runner has multiple sessions it will be in here multiple times, one for each dest
    5859    private final Map<Destination, ClientConnectionRunner>  _runners;
    5960    // Same as what's in _runners, but for fast lookup by Hash
    6061    // Locked for adds/removes but not lookups
     62    // If a runner has multiple sessions it will be in here multiple times, one for each dest
    6163    private final Map<Hash, ClientConnectionRunner>  _runnersByHash;
    6264    // ClientConnectionRunner for clients w/out a Dest yet
     
    215217    }
    216218   
     219    /**
     220     *  Remove all sessions for this runner.
     221     */
    217222    public void unregisterConnection(ClientConnectionRunner runner) {
    218         _log.warn("Unregistering (dropping) a client connection");
     223        if (_log.shouldLog(Log.WARN))
     224            _log.warn("Unregistering (dropping) a client connection");
    219225        synchronized (_pendingRunners) {
    220226            _pendingRunners.remove(runner);
    221227        }
    222         if ( (runner.getConfig() != null) && (runner.getConfig().getDestination() != null) ) {
    223             // after connection establishment
    224             Destination dest = runner.getConfig().getDestination();
    225             synchronized (_runners) {
    226                 SessionId id = runner.getSessionId();
    227                 if (id != null)
    228                     _runnerSessionIds.remove(id);
     228
     229        List<SessionId> ids = runner.getSessionIds();
     230        List<Destination> dests = runner.getDestinations();
     231        synchronized (_runners) {
     232            for (SessionId id : ids) {
     233                _runnerSessionIds.remove(id);
     234            }
     235            for (Destination dest : dests) {
    229236                _runners.remove(dest);
    230237                _runnersByHash.remove(dest.calculateHash());
    231238            }
     239        }
     240    }
     241   
     242    /**
     243     *  Remove only the following session. Does not remove the runner if it has more.
     244     *
     245     *  @since 0.9.19
     246     */
     247    public void unregisterSession(SessionId id, Destination dest) {
     248        if (_log.shouldLog(Log.WARN))
     249            _log.warn("Unregistering client session "  + id);
     250        synchronized (_runners) {
     251            _runnerSessionIds.remove(id);
     252            _runners.remove(dest);
     253            _runnersByHash.remove(dest.calculateHash());
    232254        }
    233255    }
     
    240262     *  @return SessionStatusMessage return code, 1 for success, != 1 for failure
    241263     */
    242     public int destinationEstablished(ClientConnectionRunner runner) {
    243         Destination dest = runner.getConfig().getDestination();
     264    public int destinationEstablished(ClientConnectionRunner runner, Destination dest) {
    244265        if (_log.shouldLog(Log.DEBUG))
    245266            _log.debug("DestinationEstablished called for destination " + dest.calculateHash().toBase64());
     
    256277                SessionId id = locked_getNextSessionId();
    257278                if (id != null) {
    258                     runner.setSessionId(id);
     279                    Hash h = dest.calculateHash();
     280                    runner.setSessionId(h, id);
    259281                    _runners.put(dest, runner);
    260                     _runnersByHash.put(dest.calculateHash(), runner);
     282                    _runnersByHash.put(h, runner);
    261283                    rv = SessionStatusMessage.STATUS_CREATED;
    262284                } else {
     
    324346                return;
    325347            }
    326             ClientMessage msg = new ClientMessage(toDest, payload, runner.getConfig(),
    327                                                   runner.getConfig().getDestination(), msgId,
     348            SessionConfig config = runner.getConfig(fromDest.calculateHash());
     349            if (config == null)
     350                return;
     351            ClientMessage msg = new ClientMessage(toDest, payload, config,
     352                                                  fromDest, msgId,
    328353                                                  messageNonce, expiration, flags);
    329354            _ctx.clientMessagePool().add(msg, true);
     
    363388            // so a queue overflow is not recognized. we always return success.
    364389            if (_from != null) {
    365                 _from.updateMessageDeliveryStatus(_msgId, _messageNonce, MessageStatusMessage.STATUS_SEND_SUCCESS_LOCAL);
     390                _from.updateMessageDeliveryStatus(_fromDest, _msgId, _messageNonce, MessageStatusMessage.STATUS_SEND_SUCCESS_LOCAL);
    366391            }
    367392        }
     
    379404     * @param dest Destination from which the LeaseSet's authorization should be requested
    380405     * @param set LeaseSet with requested leases - this object must be updated to contain the
    381      *            signed version (as well as any changed/added/removed Leases)
     406     *            signed version (as well as any changed/added/removed Leases).
     407     *            The LeaseSet contains Leases only; it is unsigned and does not have the destination set.
    382408     * @param timeout ms to wait before failing
    383409     * @param onCreateJob Job to run after the LeaseSet is authorized
     
    387413        ClientConnectionRunner runner = getRunner(dest);
    388414        if (runner == null) {
    389             if (_log.shouldLog(Log.ERROR))
     415            if (_log.shouldLog(Log.WARN))
    390416                _log.warn("Cannot request the lease set, as we can't find a client runner for "
    391417                          + dest.calculateHash().toBase64() + ".  disconnected?");
    392418            _ctx.jobQueue().addJob(onFailedJob);
    393419        } else {
    394             runner.requestLeaseSet(set, timeout, onCreateJob, onFailedJob);
    395         }
    396     }
    397 
     420            runner.requestLeaseSet(dest.calculateHash(), set, timeout, onCreateJob, onFailedJob);
     421        }
     422    }
     423
     424    /**
     425     * Request that a particular client authorize the Leases contained in the
     426     * LeaseSet.
     427     *
     428     * @param dest Destination from which the LeaseSet's authorization should be requested
     429     * @param ls  LeaseSet with requested leases - this object must be updated to contain the
     430     *            signed version (as well as any changed/added/removed Leases).
     431     *            The LeaseSet contains Leases only; it is unsigned and does not have the destination set.
     432     */
    398433    public void requestLeaseSet(Hash dest, LeaseSet ls) {
    399434        ClientConnectionRunner runner = getRunner(dest);
    400435        if (runner != null)  {
    401436            // no need to fire off any jobs...
    402             runner.requestLeaseSet(ls, REQUEST_LEASESET_TIMEOUT, null, null);
     437            runner.requestLeaseSet(dest, ls, REQUEST_LEASESET_TIMEOUT, null, null);
     438        } else {
     439            if (_log.shouldLog(Log.WARN))
     440                _log.warn("Cannot request the lease set, as we can't find a client runner for "
     441                          + dest + ".  disconnected?");
    403442        }
    404443    }
     
    426465        ClientConnectionRunner runner = getRunner(destHash);
    427466        if (runner == null) return true;
    428         return !Boolean.parseBoolean(runner.getConfig().getOptions().getProperty(ClientManagerFacade.PROP_CLIENT_ONLY));
     467        SessionConfig config = runner.getConfig(destHash);
     468        if (config == null) return true;
     469        return !Boolean.parseBoolean(config.getOptions().getProperty(ClientManagerFacade.PROP_CLIENT_ONLY));
    429470    }
    430471
     
    453494        ClientConnectionRunner runner = getRunner(dest);
    454495        if (runner != null)
    455             return runner.getConfig();
     496            return runner.getConfig(dest.calculateHash());
    456497        else
    457498            return null;
     
    491532                _log.debug("Delivering status " + status + " to "
    492533                           + fromDest.calculateHash() + " for message " + id);
    493             runner.updateMessageDeliveryStatus(id, messageNonce, status);
     534            runner.updateMessageDeliveryStatus(fromDest, id, messageNonce, status);
    494535        } else {
    495536            if (_log.shouldLog(Log.WARN))
     
    515556            ClientConnectionRunner runner = getRunner(dest);
    516557            if (runner != null) {
    517                 runner.reportAbuse(reason, severity);
     558                runner.reportAbuse(dest, reason, severity);
    518559            }
    519560        } else {
     
    593634        public void runJob() {
    594635            ClientConnectionRunner runner;
    595             if (_msg.getDestination() != null)
    596                 runner = getRunner(_msg.getDestination());
     636            Destination dest = _msg.getDestination();
     637            if (dest != null)
     638                runner = getRunner(dest);
    597639            else
    598640                runner = getRunner(_msg.getDestinationHash());
     
    601643                //_ctx.statManager().addRateData("client.receiveMessageSize",
    602644                //                                   _msg.getPayload().getSize(), 0);
    603                 runner.receiveMessage(_msg.getDestination(), null, _msg.getPayload());
     645                if (dest != null)
     646                    runner.receiveMessage(dest, null, _msg.getPayload());
     647                else
     648                    runner.receiveMessage(_msg.getDestinationHash(), null, _msg.getPayload());
    604649            } else {
    605650                // no client connection...
     
    607652                if (_log.shouldLog(Log.WARN))
    608653                    _log.warn("Message received but we don't have a connection to "
    609                               + _msg.getDestination() + "/" + _msg.getDestinationHash()
     654                              + dest + "/" + _msg.getDestinationHash()
    610655                              + " currently.  DROPPED");
    611656            }
  • router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java

    re8f4e19 r099515a  
    9191            ClientConnectionRunner runner = _manager.getRunner(dest);
    9292            if ( (runner == null) || (runner.getIsDead())) continue;
    93             LeaseSet ls = runner.getLeaseSet();
     93            LeaseSet ls = runner.getLeaseSet(dest.calculateHash());
    9494            if (ls == null)
    9595                continue; // still building
     
    116116     * @param set LeaseSet with requested leases - this object must be updated to contain the
    117117     *            signed version (as well as any changed/added/removed Leases)
     118     *            The LeaseSet contains Leases only; it is unsigned and does not have the destination set.
    118119     * @param timeout ms to wait before failing
    119120     * @param onCreateJob Job to run after the LeaseSet is authorized
     
    127128    }
    128129   
     130    /**
     131     * Request that a particular client authorize the Leases contained in the
     132     * LeaseSet.
     133     *
     134     * @param dest Destination from which the LeaseSet's authorization should be requested
     135     * @param ls  LeaseSet with requested leases - this object must be updated to contain the
     136     *            signed version (as well as any changed/added/removed Leases).
     137     *            The LeaseSet contains Leases only; it is unsigned and does not have the destination set.
     138     */
    129139    public void requestLeaseSet(Hash dest, LeaseSet set) {
    130140        if (_manager != null)
  • router/java/src/net/i2p/router/client/ClientMessageEventListener.java

    re8f4e19 r099515a  
    205205    private void handleCreateSession(CreateSessionMessage message) {
    206206        SessionConfig in = message.getSessionConfig();
     207        Destination dest = in.getDestination();
    207208        if (in.verifySignature()) {
    208209            if (_log.shouldLog(Log.DEBUG))
     
    210211        } else {
    211212            // For now, we do NOT send a SessionStatusMessage - see javadoc above
    212             int itype = in.getDestination().getCertificate().getCertificateType();
     213            int itype = dest.getCertificate().getCertificateType();
    213214            SigType stype = SigType.getByCode(itype);
    214215            if (stype == null || !stype.isAvailable()) {
     
    236237            return;
    237238
    238         SessionId id = _runner.getSessionId();
     239        SessionId id = _runner.getSessionId(dest.calculateHash());
    239240        if (id != null) {
    240241            _runner.disconnectClient("Already have session " + id);
     
    245246        // the client side if we change settings or later get a
    246247        // ReconfigureSessionMessage
    247         SessionConfig cfg = new SessionConfig(in.getDestination());
     248        SessionConfig cfg = new SessionConfig(dest);
    248249        cfg.setSignature(in.getSignature());
    249250        Properties props = new Properties();
    250         props.putAll(in.getOptions());
     251        boolean isPrimary = _runner.getSessionIds().isEmpty();
     252        if (!isPrimary) {
     253            // all the primary options, then the overrides from the alias
     254            SessionConfig pcfg = _runner.getPrimaryConfig();
     255            if (pcfg != null) {
     256                props.putAll(pcfg.getOptions());
     257            } else {
     258                _log.error("no primary config?");
     259            }
     260        }
     261        props.putAll(inProps);
    251262        cfg.setOptions(props);
     263        // this sets the session id
    252264        int status = _runner.sessionEstablished(cfg);
    253265        if (status != SessionStatusMessage.STATUS_CREATED) {
     
    265277            return;
    266278        }
    267         sendStatusMessage(status);
     279        // get the new session ID
     280        id = _runner.getSessionId(dest.calculateHash());
    268281
    269282        if (_log.shouldLog(Log.INFO))
    270             _log.info("Session " + _runner.getSessionId() + " established for " + _runner.getDestHash());
    271         startCreateSessionJob();
     283            _log.info("Session " + id + " established for " + dest.calculateHash());
     284        if (isPrimary) {
     285            sendStatusMessage(id, status);
     286            startCreateSessionJob(cfg);
     287        } else {
     288            SessionConfig pcfg = _runner.getPrimaryConfig();
     289            if (pcfg != null) {
     290                ClientTunnelSettings settings = new ClientTunnelSettings(dest.calculateHash());
     291                settings.readFromProperties(props);
     292                // addAlias() sends the create lease set msg, so we have to send the SMS first
     293                sendStatusMessage(id, status);
     294                boolean ok = _context.tunnelManager().addAlias(dest, settings, pcfg.getDestination());
     295                if (!ok) {
     296                    _log.error("Add alias failed");
     297                    // FIXME cleanup
     298                }
     299            } else {
     300                _log.error("no primary config?");
     301                status = SessionStatusMessage.STATUS_INVALID;
     302                sendStatusMessage(id, status);
     303                // FIXME cleanup
     304            }
     305        }
    272306    }
    273307   
     
    315349     *
    316350     */
    317     protected void startCreateSessionJob() {
    318         _context.jobQueue().addJob(new CreateSessionJob(_context, _runner));
     351    protected void startCreateSessionJob(SessionConfig config) {
     352        _context.jobQueue().addJob(new CreateSessionJob(_context, config));
    319353    }
    320354   
     
    325359     */
    326360    private void handleSendMessage(SendMessageMessage message) {
    327         SessionConfig cfg = _runner.getConfig();
     361        SessionId sid = message.getSessionId();
     362        SessionConfig cfg = _runner.getConfig(sid);
    328363        if (cfg == null) {
    329364            if (_log.shouldLog(Log.ERROR))
     
    337372        MessageId id = _runner.distributeMessage(message);
    338373        long timeToDistribute = _context.clock().now() - beforeDistribute;
    339         _runner.ackSendMessage(id, message.getNonce());
     374        // TODO validate session id
     375        _runner.ackSendMessage(message.getSessionId(), id, message.getNonce());
    340376        _context.statManager().addRateData("client.distributeTime", timeToDistribute);
    341377        if ( (timeToDistribute > 50) && (_log.shouldLog(Log.INFO)) )
     
    354390        MessagePayloadMessage msg = new MessagePayloadMessage();
    355391        msg.setMessageId(message.getMessageId());
    356         msg.setSessionId(_runner.getSessionId().getSessionId());
     392        // TODO validate session id
     393        msg.setSessionId(message.getSessionId());
    357394        Payload payload = _runner.getPayload(new MessageId(message.getMessageId()));
    358395        if (payload == null) {
     
    383420   
    384421    private void handleDestroySession(DestroySessionMessage message) {
    385         if (_log.shouldLog(Log.INFO))
    386             _log.info("Destroying client session " + _runner.getSessionId());
    387         _runner.stopRunning();
     422        SessionId id = message.getSessionId();
     423        SessionConfig cfg = _runner.getConfig(id);
     424        _runner.removeSession(id);
     425        int left = _runner.getSessionIds().size();
     426        if (left <= 0) {
     427            _runner.stopRunning();
     428        } else {
     429            if (cfg != null)
     430                _context.tunnelManager().removeAlias(cfg.getDestination());
     431            if (_log.shouldLog(Log.INFO))
     432                _log.info("Still " + left + " sessions left");
     433        }
    388434    }
    389435   
     
    396442            return;
    397443        }
    398         SessionConfig cfg = _runner.getConfig();
     444        SessionId id = message.getSessionId();
     445        SessionConfig cfg = _runner.getConfig(id);
    399446        if (cfg == null) {
    400447            if (_log.shouldLog(Log.ERROR))
     
    447494        }
    448495        if (_log.shouldLog(Log.INFO))
    449             _log.info("New lease set granted for destination "
    450                       + _runner.getDestHash());
     496            _log.info("New lease set granted for destination " + dest);
    451497
    452498        // leaseSetCreated takes care of all the LeaseRequestState stuff (including firing any jobs)
     
    456502    /** override for testing */
    457503    protected void handleDestLookup(DestLookupMessage message) {
     504        // no session id in DLM
    458505        _context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getHash(),
    459506                                                     _runner.getDestHash()));
     
    465512     */
    466513    protected void handleHostLookup(HostLookupMessage message) {
     514        Hash h = _runner.getDestHash(message.getSessionId());
     515        if (h == null)
     516            return;  // ok?
    467517        _context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getReqID(),
    468518                                                     message.getTimeout(), message.getSessionId(),
    469                                                      message.getHash(), message.getHostname(),
    470                                                      _runner.getDestHash()));
     519                                                     message.getHash(), message.getHostname(), h));
    471520    }
    472521
     
    483532     */
    484533    private void handleReconfigureSession(ReconfigureSessionMessage message) {
    485         SessionConfig cfg = _runner.getConfig();
     534        SessionId id = message.getSessionId();
     535        SessionConfig cfg = _runner.getConfig(id);
    486536        if (cfg == null) {
    487537            if (_log.shouldLog(Log.ERROR))
    488538                _log.error("ReconfigureSession w/o session");
     539            //sendStatusMessage(id, SessionStatusMessage.STATUS_INVALID);
    489540            _runner.disconnectClient("ReconfigureSession w/o session");
    490541            return;
     
    494545        if (!message.getSessionConfig().getDestination().equals(cfg.getDestination())) {
    495546            _log.error("Dest mismatch");
    496             sendStatusMessage(SessionStatusMessage.STATUS_INVALID);
     547            sendStatusMessage(id, SessionStatusMessage.STATUS_INVALID);
    497548            _runner.stopRunning();
    498549            return;
    499550        }
     551        Hash dest = cfg.getDestination().calculateHash();
    500552        cfg.getOptions().putAll(message.getSessionConfig().getOptions());
    501         Hash dest = _runner.getDestHash();
    502553        ClientTunnelSettings settings = new ClientTunnelSettings(dest);
    503554        Properties props = new Properties();
     
    508559        _context.tunnelManager().setOutboundSettings(dest,
    509560                                                     settings.getOutboundSettings());
    510         sendStatusMessage(SessionStatusMessage.STATUS_UPDATED);
    511     }
    512    
    513     private void sendStatusMessage(int status) {
     561        sendStatusMessage(id, SessionStatusMessage.STATUS_UPDATED);
     562    }
     563   
     564    private void sendStatusMessage(SessionId id, int status) {
    514565        SessionStatusMessage msg = new SessionStatusMessage();
    515         SessionId id = _runner.getSessionId();
    516         if (id == null)
    517             id = ClientManager.UNKNOWN_SESSION_ID;
    518566        msg.setSessionId(id);
    519567        msg.setStatus(status);
  • router/java/src/net/i2p/router/client/CreateSessionJob.java

    re8f4e19 r099515a  
    2727class CreateSessionJob extends JobImpl {
    2828    private final Log _log;
    29     private final ClientConnectionRunner _runner;
     29    private final SessionConfig _config;
    3030   
    31     public CreateSessionJob(RouterContext context, ClientConnectionRunner runner) {
     31    public CreateSessionJob(RouterContext context, SessionConfig config) {
    3232        super(context);
    3333        _log = context.logManager().getLog(CreateSessionJob.class);
    34         _runner = runner;
     34        _config = config;
    3535        if (_log.shouldLog(Log.DEBUG))
    36             _log.debug("CreateSessionJob for runner " + _runner + " / config: " + _runner.getConfig());
     36            _log.debug("CreateSessionJob for config: " + config);
    3737    }
    3838   
    3939    public String getName() { return "Request tunnels for a new client"; }
     40
    4041    public void runJob() {
    41         SessionConfig cfg = _runner.getConfig();
    42         if ( (cfg == null) || (cfg.getDestination() == null) ) {
    43             if (_log.shouldLog(Log.ERROR))
    44                 _log.error("No session config on runner " + _runner);
    45             return;
    46         }
    47         Hash dest = cfg.getDestination().calculateHash();
     42        Hash dest = _config.getDestination().calculateHash();
    4843        if (_log.shouldLog(Log.INFO))
    4944            _log.info("Requesting lease set for destination " + dest);
     
    6257       
    6358        // override them by the client's settings
    64         props.putAll(cfg.getOptions());
     59        props.putAll(_config.getOptions());
    6560       
    6661        // and load 'em up (using anything not yet set as the software defaults)
    6762        settings.readFromProperties(props);
    68         getContext().tunnelManager().buildTunnels(cfg.getDestination(), settings);
     63        getContext().tunnelManager().buildTunnels(_config.getDestination(), settings);
    6964    }
    7065}
  • router/java/src/net/i2p/router/client/LeaseRequestState.java

    re8f4e19 r099515a  
    3131    /**
    3232     *  @param expiration absolute time, when the request expires (not when the LS expires)
     33     *  @param requested LeaseSet with requested leases - this object must be updated to contain the
     34     *             signed version (as well as any changed/added/removed Leases)
     35     *             The LeaseSet contains Leases and destination only, it is unsigned.
    3336     */
    3437    public LeaseRequestState(Job onGranted, Job onFailed, long expiration, LeaseSet requested) {
     
    4144    /** created lease set from client - FIXME always null */
    4245    public LeaseSet getGranted() { return _grantedLeaseSet; }
     46
    4347    /** FIXME unused - why? */
    4448    public void setGranted(LeaseSet ls) { _grantedLeaseSet = ls; }
  • router/java/src/net/i2p/router/client/MessageReceivedJob.java

    re8f4e19 r099515a  
    1515import net.i2p.data.i2cp.MessagePayloadMessage;
    1616import net.i2p.data.i2cp.MessageStatusMessage;
     17import net.i2p.data.i2cp.SessionId;
    1718import net.i2p.router.JobImpl;
    1819import net.i2p.router.RouterContext;
     
    2728    private final Log _log;
    2829    private final ClientConnectionRunner _runner;
     30    private final Destination _toDest;
    2931    private final Payload _payload;
    3032    private final boolean _sendDirect;
    3133
     34    /**
     35     *  @param toDest non-null, required to pick session
     36     *  @param fromDest ignored, generally null
     37     */
    3238    public MessageReceivedJob(RouterContext ctx, ClientConnectionRunner runner, Destination toDest,
    3339                              Destination fromDest, Payload payload, boolean sendDirect) {
     
    3541        _log = ctx.logManager().getLog(MessageReceivedJob.class);
    3642        _runner = runner;
     43        _toDest = toDest;
    3744        _payload = payload;
    3845        _sendDirect = sendDirect;
     
    4451        if (_runner.isDead()) return;
    4552        MessageId id = null;
    46         long nextID = _runner.getNextMessageId();
    4753        try {
     54            long nextID = _runner.getNextMessageId();
    4855            if (_sendDirect) {
    4956                sendMessage(nextID);
     
    5663            if (_log.shouldLog(Log.WARN))
    5764                _log.warn("Error writing out the message", ime);
    58             if (!_sendDirect)
     65            if (id != null && !_sendDirect)
    5966                _runner.removePayload(id);
    6067        }
     
    7077        MessageStatusMessage msg = new MessageStatusMessage();
    7178        msg.setMessageId(id.getMessageId());
    72         msg.setSessionId(_runner.getSessionId().getSessionId());
     79        SessionId sid = _runner.getSessionId(_toDest.calculateHash());
     80        if (sid == null) {
     81            if (_log.shouldLog(Log.WARN))
     82                _log.warn("No session for " + _toDest.calculateHash());
     83            return;
     84        }
     85        msg.setSessionId(sid.getSessionId());
    7386        msg.setSize(size);
    7487        // has to be >= 0, it is initialized to -1
     
    8598        MessagePayloadMessage msg = new MessagePayloadMessage();
    8699        msg.setMessageId(id);
    87         msg.setSessionId(_runner.getSessionId().getSessionId());
     100        SessionId sid = _runner.getSessionId(_toDest.calculateHash());
     101        if (sid == null) {
     102            if (_log.shouldLog(Log.WARN))
     103                _log.warn("No session for " + _toDest.calculateHash());
     104            return;
     105        }
     106        msg.setSessionId(sid.getSessionId());
    88107        msg.setPayload(_payload);
    89108        _runner.doSend(msg);
  • router/java/src/net/i2p/router/client/ReportAbuseJob.java

    re8f4e19 r099515a  
    99 */
    1010
     11import net.i2p.data.Destination;
    1112import net.i2p.data.i2cp.AbuseReason;
    1213import net.i2p.data.i2cp.AbuseSeverity;
    1314import net.i2p.data.i2cp.I2CPMessageException;
    1415import net.i2p.data.i2cp.ReportAbuseMessage;
     16import net.i2p.data.i2cp.SessionId;
    1517import net.i2p.router.JobImpl;
    1618import net.i2p.router.RouterContext;
     
    2426    private final Log _log;
    2527    private final ClientConnectionRunner _runner;
     28    private final Destination _dest;
    2629    private final String _reason;
    2730    private final int _severity;
    28     public ReportAbuseJob(RouterContext context, ClientConnectionRunner runner, String reason, int severity) {
     31
     32    public ReportAbuseJob(RouterContext context, ClientConnectionRunner runner,
     33                          Destination dest, String reason, int severity) {
    2934        super(context);
    3035        _log = context.logManager().getLog(ReportAbuseJob.class);
    3136        _runner = runner;
     37        _dest = dest;
    3238        _reason = reason;
    3339        _severity = severity;
     
    3541   
    3642    public String getName() { return "Report Abuse"; }
     43
    3744    public void runJob() {
    3845        if (_runner.isDead()) return;
     
    4249        sev.setSeverity(_severity);
    4350        ReportAbuseMessage msg = new ReportAbuseMessage();
    44         msg.setMessageId(null);
    4551        msg.setReason(res);
    46         msg.setSessionId(_runner.getSessionId());
     52        SessionId id = _runner.getSessionId(_dest.calculateHash());
     53        if (id == null)
     54            return;
     55        msg.setSessionId(id);
    4756        msg.setSeverity(sev);
    4857        try {
  • router/java/src/net/i2p/router/client/RequestLeaseSetJob.java

    re8f4e19 r099515a  
    1717import net.i2p.data.i2cp.RequestLeaseSetMessage;
    1818import net.i2p.data.i2cp.RequestVariableLeaseSetMessage;
     19import net.i2p.data.i2cp.SessionId;
    1920import net.i2p.router.JobImpl;
    2021import net.i2p.router.RouterContext;
     
    6465        endTime += fudge;
    6566
     67        SessionId id = _runner.getSessionId(_requestState.getRequested().getDestination().calculateHash());
     68        if (id == null)
     69            return;
    6670        I2CPMessage msg;
    6771        if (getContext().getProperty(PROP_VARIABLE, DFLT_VARIABLE) &&
     
    7074            // new style - leases will have individual expirations
    7175            RequestVariableLeaseSetMessage rmsg = new RequestVariableLeaseSetMessage();
    72             rmsg.setSessionId(_runner.getSessionId());
     76            rmsg.setSessionId(id);
    7377            for (int i = 0; i < requested.getLeaseCount(); i++) {
    7478                Lease lease = requested.getLease(i);
     
    9195            Date end = new Date(endTime);
    9296            rmsg.setEndDate(end);
    93             rmsg.setSessionId(_runner.getSessionId());
     97            rmsg.setSessionId(id);
    9498            for (int i = 0; i < requested.getLeaseCount(); i++) {
    9599                Lease lease = requested.getLease(i);
     
    145149                if (_log.shouldLog(Log.ERROR)) {
    146150                    long waited = System.currentTimeMillis() - _start;
    147                     _log.error("Failed to receive a leaseSet in the time allotted (" + waited + "): " + _requestState + " for "
    148                              + _runner.getConfig().getDestination().calculateHash().toBase64());
     151                    _log.error("Failed to receive a leaseSet in the time allotted (" + waited + "): " + _requestState);
    149152                }
    150153                if (_requestState.getOnFailed() != null)
  • router/java/src/net/i2p/router/dummy/DummyTunnelManagerFacade.java

    re8f4e19 r099515a  
    5151    public long getLastParticipatingExpiration() { return -1; }
    5252    public void buildTunnels(Destination client, ClientTunnelSettings settings) {}
     53    public boolean addAlias(Destination dest, ClientTunnelSettings settings, Destination existingClient) { return false; }
     54    public void removeAlias(Destination dest) {}
    5355    public TunnelPoolSettings getInboundSettings() { return null; }
    5456    public TunnelPoolSettings getOutboundSettings() { return null; }
  • router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java

    re8f4e19 r099515a  
    1919import net.i2p.router.RouterContext;
    2020import net.i2p.router.TunnelInfo;
     21import net.i2p.router.TunnelPoolSettings;
    2122import net.i2p.router.message.GarlicMessageReceiver;
    2223import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
     
    205206     */
    206207    public void handleClove(DeliveryInstructions instructions, I2NPMessage data) {
     208        int type = data.getType();
    207209        switch (instructions.getDeliveryMode()) {
    208210            case DeliveryInstructions.DELIVERY_MODE_LOCAL:
    209211                if (_log.shouldLog(Log.DEBUG))
    210212                    _log.debug("local delivery instructions for clove: " + data.getClass().getSimpleName());
    211                 int type = data.getType();
    212213                if (type == GarlicMessage.MESSAGE_TYPE) {
    213214                    _receiver.receive((GarlicMessage)data);
     
    297298                }
    298299                return;
     300
    299301            case DeliveryInstructions.DELIVERY_MODE_DESTINATION:
     302                Hash to = instructions.getDestination();
    300303                // Can we route UnknownI2NPMessages to a destination too?
    301                 if (!(data instanceof DataMessage)) {
     304                if (type != DataMessage.MESSAGE_TYPE) {
    302305                    if (_log.shouldLog(Log.ERROR))
    303306                        _log.error("cant send a " + data.getClass().getSimpleName() + " to a destination");
    304                 } else if ( (_client != null) && (_client.equals(instructions.getDestination())) ) {
     307                } else if (_client != null && _client.equals(to)) {
    305308                    if (_log.shouldLog(Log.DEBUG))
    306                         _log.debug("data message came down a tunnel for "
    307                                    + _client);
     309                        _log.debug("data message came down a tunnel for " + _client);
    308310                    DataMessage dm = (DataMessage)data;
    309311                    Payload payload = new Payload();
     
    311313                    ClientMessage m = new ClientMessage(_client, payload);
    312314                    _context.clientManager().messageReceived(m);
     315                } else if (_client != null) {
     316                    // Shared tunnel?
     317                    TunnelPoolSettings tgt = _context.tunnelManager().getInboundSettings(to);
     318                    if (tgt != null && _client.equals(tgt.getAliasOf())) {
     319                        // same as above, just different log
     320                        if (_log.shouldLog(Log.DEBUG))
     321                            _log.debug("data message came down a tunnel for "
     322                                       + _client + " targeting shared " + to);
     323                        DataMessage dm = (DataMessage)data;
     324                        Payload payload = new Payload();
     325                        payload.setEncryptedData(dm.getData());
     326                        ClientMessage m = new ClientMessage(to, payload);
     327                        _context.clientManager().messageReceived(m);
     328                    } else {
     329                        if (_log.shouldLog(Log.ERROR))
     330                            _log.error("Data message came down a tunnel for "
     331                                   +  _client + " but targetted " + to);
     332                    }
    313333                } else {
    314334                    if (_log.shouldLog(Log.ERROR))
    315                         _log.error("this data message came down a tunnel for "
    316                                    + (_client == null ? "no one" : _client)
    317                                    + " but targetted "
    318                                    + instructions.getDestination());
     335                        _log.error("Data message came down an exploratory tunnel targeting " + to);
    319336                }
    320337                return;
     338
    321339            case DeliveryInstructions.DELIVERY_MODE_ROUTER: // fall through
    322340            case DeliveryInstructions.DELIVERY_MODE_TUNNEL:
     
    326344                distribute(data, instructions.getRouter(), instructions.getTunnelId());
    327345                return;
     346
    328347            default:
    329348                if (_log.shouldLog(Log.ERROR))
  • router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java

    re8f4e19 r099515a  
    99import java.util.List;
    1010import java.util.Properties;
     11import java.util.Set;
    1112import java.util.TreeSet;
    1213
     
    3132public class TunnelPool {
    3233    private final List<PooledTunnelCreatorConfig> _inProgress = new ArrayList<PooledTunnelCreatorConfig>();
    33     private final RouterContext _context;
    34     private final Log _log;
     34    protected final RouterContext _context;
     35    protected final Log _log;
    3536    private TunnelPoolSettings _settings;
    3637    private final List<TunnelInfo> _tunnels;
    3738    private final TunnelPeerSelector _peerSelector;
    3839    private final TunnelPoolManager _manager;
    39     private volatile boolean _alive;
     40    protected volatile boolean _alive;
    4041    private long _lifetimeProcessed;
    4142    private TunnelInfo _lastSelected;
     
    119120    }
    120121
    121     void refreshSettings() {
    122         if (!_settings.isExploratory()) {
     122    private void refreshSettings() {
     123        if (!_settings.isExploratory())
    123124            return; // don't override client specified settings
    124         } else {
    125             if (_settings.isExploratory()) {
    126                 Properties props = new Properties();
    127                 props.putAll(_context.router().getConfigMap());
    128                 if (_settings.isInbound())
    129                     _settings.readFromProperties(TunnelPoolSettings.PREFIX_INBOUND_EXPLORATORY, props);
    130                 else
    131                     _settings.readFromProperties(TunnelPoolSettings.PREFIX_OUTBOUND_EXPLORATORY, props);
    132             }
    133         }
     125        Properties props = new Properties();
     126        props.putAll(_context.router().getConfigMap());
     127        if (_settings.isInbound())
     128            _settings.readFromProperties(TunnelPoolSettings.PREFIX_INBOUND_EXPLORATORY, props);
     129        else
     130            _settings.readFromProperties(TunnelPoolSettings.PREFIX_OUTBOUND_EXPLORATORY, props);
    134131    }
    135132   
     
    413410   
    414411    /** duplicate of size(), let's pick one */
    415     int getTunnelCount() { synchronized (_tunnels) { return _tunnels.size(); } }
     412    int getTunnelCount() { return size(); }
    416413   
    417414    public TunnelPoolSettings getSettings() { return _settings; }
    418415
    419416    void setSettings(TunnelPoolSettings settings) {
     417        if (settings != null && _settings != null) {
     418            settings.getAliases().addAll(_settings.getAliases());
     419            settings.setAliasOf(_settings.getAliasOf());
     420        }
    420421        _settings = settings;
    421422        if (_settings != null) {
     
    607608            if (_log.shouldLog(Log.DEBUG))
    608609                _log.debug(toString() + ": refreshing leaseSet on tunnel expiration (but prior to grace timeout)");
    609             LeaseSet ls = null;
     610            LeaseSet ls;
    610611            synchronized (_tunnels) {
    611612                ls = locked_buildNewLeaseSet();
     
    613614            if (ls != null) {
    614615                _context.clientManager().requestLeaseSet(_settings.getDestination(), ls);
     616                Set<Hash> aliases = _settings.getAliases();
     617                if (aliases != null && !aliases.isEmpty()) {
     618                    for (Hash h : aliases) {
     619                        _context.clientManager().requestLeaseSet(h, ls);
     620                    }
     621                }
    615622            }
    616623        }
     
    711718     * @return null on failure
    712719     */
    713     private LeaseSet locked_buildNewLeaseSet() {
     720    protected LeaseSet locked_buildNewLeaseSet() {
    714721        if (!_alive)
    715722            return null;
  • router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java

    re8f4e19 r099515a  
    9898                                         RATES);
    9999    }
    100    
     100
    101101    /**
    102102     * Pick a random inbound exploratory tunnel.
     
    114114        return info;
    115115    }
    116    
     116
    117117    /**
    118118     * Pick a random inbound tunnel from the given destination's pool.
     
    133133        return null;
    134134    }
    135    
     135
    136136    /**
    137137     * Pick a random outbound exploratory tunnel.
     
    149149        return info;
    150150    }
    151    
     151
    152152    /**
    153153     * Pick a random outbound tunnel from the given destination's pool.
     
    165165        return null;
    166166    }
    167    
     167
    168168    /**
    169169     * Pick the inbound exploratory tunnel with the gateway closest to the given hash.
     
    185185        return info;
    186186    }
    187    
     187
    188188    /**
    189189     * Pick the inbound tunnel with the gateway closest to the given hash
     
    209209        return null;
    210210    }
    211    
     211
    212212    /**
    213213     * Pick the outbound exploratory tunnel with the endpoint closest to the given hash.
     
    229229        return info;
    230230    }
    231    
     231
    232232    /**
    233233     * Pick the outbound tunnel with the endpoint closest to the given hash
     
    250250        return null;
    251251    }
    252    
     252
    253253    /**
    254254     *  Expensive (iterates through all tunnels of all pools) and unnecessary.
     
    268268        return null;
    269269    }
    270    
     270
    271271    /** @return number of inbound exploratory tunnels */
    272272    public int getFreeTunnelCount() {
     
    305305        return 0;
    306306    }
    307    
     307
    308308    public int getParticipatingCount() { return _context.tunnelDispatcher().getParticipatingCount(); }
     309
    309310    public long getLastParticipatingExpiration() { return _context.tunnelDispatcher().getLastParticipatingExpiration(); }
    310    
     311
    311312    /**
    312313     *  @return (number of part. tunnels) / (estimated total number of hops in our expl.+client tunnels)
     
    330331        return Math.min(part / (double) count, 100d);
    331332    }
    332 
    333333
    334334    public boolean isValidTunnel(Hash client, TunnelInfo tunnel) {
     
    387387        }
    388388    }
    389    
     389
    390390    public synchronized void restart() {
    391391        _handler.restart();
     
    394394        startup();
    395395    }
    396        
     396
    397397    /**
    398398     *  Used only at session startup.
    399399     *  Do not use to change settings.
     400     *  Do not use for aliased destinations; use addAlias().
    400401     */
    401402    public void buildTunnels(Destination client, ClientTunnelSettings settings) {
     
    435436            outbound.startup();
    436437    }
    437    
    438    
     438
     439    /**
     440     *  Add another destination to the same tunnels.
     441     *  Must have same encryption key an a different signing key.
     442     *  @throws IllegalArgumentException if not
     443     *  @return success
     444     *  @since 0.9.19
     445     */
     446    public boolean addAlias(Destination dest, ClientTunnelSettings settings, Destination existingClient) {
     447        if (dest.getSigningPublicKey().equals(existingClient.getSigningPublicKey()))
     448            throw new IllegalArgumentException("signing key must differ");
     449        if (!dest.getPublicKey().equals(existingClient.getPublicKey()))
     450            throw new IllegalArgumentException("encryption key mismatch");
     451        Hash h = dest.calculateHash();
     452        Hash e = existingClient.calculateHash();
     453        synchronized(this) {
     454            TunnelPool inbound = _clientInboundPools.get(h);
     455            TunnelPool outbound = _clientOutboundPools.get(h);
     456            if (inbound != null || outbound != null) {
     457                if (_log.shouldLog(Log.WARN))
     458                    _log.warn("already have alias " + dest);
     459                return false;
     460            }
     461            TunnelPool eInbound = _clientInboundPools.get(e);
     462            TunnelPool eOutbound = _clientOutboundPools.get(e);
     463            if (eInbound == null || eOutbound == null) {
     464                if (_log.shouldLog(Log.WARN))
     465                    _log.warn("primary not found " + existingClient);
     466                return false;
     467            }
     468            eInbound.getSettings().getAliases().add(h);
     469            eOutbound.getSettings().getAliases().add(h);
     470            TunnelPoolSettings newIn = settings.getInboundSettings();
     471            TunnelPoolSettings newOut = settings.getOutboundSettings();
     472            newIn.setAliasOf(e);
     473            newOut.setAliasOf(e);
     474            inbound = new AliasedTunnelPool(_context, this, newIn, eInbound);
     475            outbound = new AliasedTunnelPool(_context, this, newOut, eOutbound);
     476            _clientInboundPools.put(h, inbound);
     477            _clientOutboundPools.put(h, outbound);
     478            inbound.startup();
     479            outbound.startup();
     480        }
     481        if (_log.shouldLog(Log.WARN))
     482            _log.warn("Added " + h + " as alias for " + e + " with settings " + settings);
     483        return true;
     484    }
     485
     486    /**
     487     *  Remove a destination for the same tunnels as another.
     488     *  @since 0.9.19
     489     */
     490    public void removeAlias(Destination dest) {
     491        Hash h = dest.calculateHash();
     492        synchronized(this) {
     493            TunnelPool inbound = _clientInboundPools.remove(h);
     494            if (inbound != null) {
     495                Hash p = inbound.getSettings().getAliasOf();
     496                if (p != null) {
     497                    TunnelPool pri = _clientInboundPools.get(p);
     498                    if (pri != null) {
     499                        Set<Hash> aliases = pri.getSettings().getAliases();
     500                        if (aliases != null)
     501                            aliases.remove(h);
     502                    }
     503                }
     504            }
     505            TunnelPool outbound = _clientOutboundPools.remove(h);
     506            if (outbound != null) {
     507                Hash p = outbound.getSettings().getAliasOf();
     508                if (p != null) {
     509                    TunnelPool pri = _clientOutboundPools.get(p);
     510                    if (pri != null) {
     511                        Set<Hash> aliases = pri.getSettings().getAliases();
     512                        if (aliases != null)
     513                            aliases.remove(h);
     514                    }
     515                }
     516            }
     517            // TODO if primary already vanished...
     518        }
     519    }
     520
    439521    private static class DelayedStartup implements SimpleTimer.TimedEvent {
    440522        private final TunnelPool pool;
     
    470552            outbound.shutdown();
    471553    }
    472    
     554
    473555    /** queue a recurring test job if appropriate */
    474556    void buildComplete(PooledTunnelCreatorConfig cfg) {
     
    519601        _context.jobQueue().addJob(new BootstrapPool(_context, _outboundExploratory));
    520602    }
    521    
     603
    522604    private static class BootstrapPool extends JobImpl {
    523605        private TunnelPool _pool;
     
    532614        }
    533615    }
    534    
     616
    535617    /**
    536618     *  Cannot be restarted
     
    547629            _outboundExploratory.shutdown();
    548630    }
    549    
     631
    550632    /** list of TunnelPool instances currently in play */
    551633    public void listPools(List<TunnelPool> out) {
Note: See TracChangeset for help on using the changeset viewer.