Search in sources :

Example 6 with RouterAddress

use of net.i2p.data.router.RouterAddress in project i2p.i2p by i2p.

the class EstablishmentManager method handleCompletelyEstablished.

/**
 * ok, fully received, add it to the established cons and queue up a
 * netDb store to them
 */
private void handleCompletelyEstablished(InboundEstablishState state) {
    if (state.isComplete())
        return;
    RouterIdentity remote = state.getConfirmedIdentity();
    PeerState peer = new PeerState(_context, _transport, state.getSentIP(), state.getSentPort(), remote.calculateHash(), true);
    peer.setCurrentCipherKey(state.getCipherKey());
    peer.setCurrentMACKey(state.getMACKey());
    peer.setWeRelayToThemAs(state.getSentRelayTag());
    // Lookup the peer's MTU from the netdb, since it isn't included in the protocol setup (yet)
    // TODO if we don't have RI then we will get it shortly, but too late.
    // Perhaps netdb should notify transport when it gets a new RI...
    RouterInfo info = _context.netDb().lookupRouterInfoLocally(remote.calculateHash());
    if (info != null) {
        RouterAddress addr = _transport.getTargetAddress(info);
        if (addr != null) {
            String smtu = addr.getOption(UDPAddress.PROP_MTU);
            if (smtu != null) {
                try {
                    boolean isIPv6 = state.getSentIP().length == 16;
                    int mtu = MTU.rectify(isIPv6, Integer.parseInt(smtu));
                    peer.setHisMTU(mtu);
                } catch (NumberFormatException nfe) {
                }
            }
        }
    }
    if (_log.shouldLog(Log.DEBUG))
        _log.debug("Handle completely established (inbound): " + state + " - " + peer.getRemotePeer());
    // if (true) // for now, only support direct
    // peer.setRemoteRequiresIntroduction(false);
    _transport.addRemotePeerState(peer);
    boolean isIPv6 = state.getSentIP().length == 16;
    _transport.inboundConnectionReceived(isIPv6);
    _transport.setIP(remote.calculateHash(), state.getSentIP());
    _context.statManager().addRateData("udp.inboundEstablishTime", state.getLifetime());
    sendInboundComplete(peer);
    OutNetMessage msg;
    while ((msg = state.getNextQueuedMessage()) != null) {
        if (_context.clock().now() - Router.CLOCK_FUDGE_FACTOR > msg.getExpiration()) {
            msg.timestamp("took too long but established...");
            _transport.failed(msg, "Took too long to establish, but it was established");
        } else {
            msg.timestamp("session fully established and sent");
            _transport.send(msg);
        }
    }
    state.complete();
}
Also used : OutNetMessage(net.i2p.router.OutNetMessage) RouterIdentity(net.i2p.data.router.RouterIdentity) RouterInfo(net.i2p.data.router.RouterInfo) RouterAddress(net.i2p.data.router.RouterAddress)

Example 7 with RouterAddress

use of net.i2p.data.router.RouterAddress in project i2p.i2p by i2p.

the class EstablishmentManager method establish.

/**
 *  @param queueIfMaxExceeded true normally, false if called from locked_admit so we don't loop
 *  @since 0.9.2
 */
private void establish(OutNetMessage msg, boolean queueIfMaxExceeded) {
    RouterInfo toRouterInfo = msg.getTarget();
    RouterAddress ra = _transport.getTargetAddress(toRouterInfo);
    if (ra == null) {
        _transport.failed(msg, "Remote peer has no address, cannot establish");
        return;
    }
    RouterIdentity toIdentity = toRouterInfo.getIdentity();
    Hash toHash = toIdentity.calculateHash();
    if (toRouterInfo.getNetworkId() != _networkID) {
        _context.banlist().banlistRouter(toHash);
        _transport.markUnreachable(toHash);
        _transport.failed(msg, "Remote peer is on the wrong network, cannot establish");
        return;
    }
    UDPAddress addr = new UDPAddress(ra);
    RemoteHostId maybeTo = null;
    InetAddress remAddr = addr.getHostAddress();
    int port = addr.getPort();
    // claimed address (which we won't be using if indirect)
    if (remAddr != null && port > 0 && port <= 65535) {
        maybeTo = new RemoteHostId(remAddr.getAddress(), port);
        if ((!_transport.isValid(maybeTo.getIP())) || (Arrays.equals(maybeTo.getIP(), _transport.getExternalIP()) && !_transport.allowLocal())) {
            _transport.failed(msg, "Remote peer's IP isn't valid");
            _transport.markUnreachable(toHash);
            // _context.banlist().banlistRouter(msg.getTarget().getIdentity().calculateHash(), "Invalid SSU address", UDPTransport.STYLE);
            _context.statManager().addRateData("udp.establishBadIP", 1);
            return;
        }
        InboundEstablishState inState = _inboundStates.get(maybeTo);
        if (inState != null) {
            // we have an inbound establishment in progress, queue it there instead
            synchronized (inState) {
                switch(inState.getState()) {
                    case IB_STATE_UNKNOWN:
                    case IB_STATE_REQUEST_RECEIVED:
                    case IB_STATE_CREATED_SENT:
                    case IB_STATE_CONFIRMED_PARTIALLY:
                    case IB_STATE_CONFIRMED_COMPLETELY:
                        // queue it
                        inState.addMessage(msg);
                        if (_log.shouldLog(Log.WARN))
                            _log.debug("OB msg queued to IES");
                        break;
                    case IB_STATE_COMPLETE:
                        // race, send it out (but don't call _transport.send() again and risk a loop)
                        _transport.sendIfEstablished(msg);
                        break;
                    case IB_STATE_FAILED:
                        // race, failed
                        _transport.failed(msg, "OB msg failed during IB establish");
                        break;
                }
            }
            return;
        }
    }
    RemoteHostId to;
    boolean isIndirect = addr.getIntroducerCount() > 0 || maybeTo == null;
    if (isIndirect) {
        to = new RemoteHostId(toHash);
    } else {
        to = maybeTo;
    }
    OutboundEstablishState state = null;
    int deferred = 0;
    boolean rejected = false;
    int queueCount = 0;
    state = _outboundStates.get(to);
    if (state == null) {
        state = _outboundByHash.get(toHash);
        if (state != null && _log.shouldLog(Log.INFO))
            _log.info("Found by hash: " + state);
    }
    if (state == null) {
        if (queueIfMaxExceeded && _outboundStates.size() >= getMaxConcurrentEstablish()) {
            if (_queuedOutbound.size() >= MAX_QUEUED_OUTBOUND && !_queuedOutbound.containsKey(to)) {
                rejected = true;
            } else {
                List<OutNetMessage> newQueued = new ArrayList<OutNetMessage>(MAX_QUEUED_PER_PEER);
                List<OutNetMessage> queued = _queuedOutbound.putIfAbsent(to, newQueued);
                if (queued == null) {
                    queued = newQueued;
                    if (_log.shouldLog(Log.WARN))
                        _log.warn("Queueing outbound establish to " + to + ", increase " + PROP_MAX_CONCURRENT_ESTABLISH);
                }
                // There are still races possible but this should prevent AIOOBE and NPE
                synchronized (queued) {
                    queueCount = queued.size();
                    if (queueCount < MAX_QUEUED_PER_PEER) {
                        queued.add(msg);
                        // increment for the stat below
                        queueCount++;
                    } else {
                        rejected = true;
                    }
                    deferred = _queuedOutbound.size();
                }
            }
        } else {
            // must have a valid session key
            byte[] keyBytes = addr.getIntroKey();
            if (keyBytes == null) {
                _transport.markUnreachable(toHash);
                _transport.failed(msg, "Peer has no key, cannot establish");
                return;
            }
            SessionKey sessionKey;
            try {
                sessionKey = new SessionKey(keyBytes);
            } catch (IllegalArgumentException iae) {
                _transport.markUnreachable(toHash);
                _transport.failed(msg, "Peer has bad key, cannot establish");
                return;
            }
            boolean allowExtendedOptions = VersionComparator.comp(toRouterInfo.getVersion(), VERSION_ALLOW_EXTENDED_OPTIONS) >= 0 && !_context.getBooleanProperty(PROP_DISABLE_EXT_OPTS);
            // w/o ext options, it's always 'requested', no need to set
            // don't ask if they are indirect
            boolean requestIntroduction = allowExtendedOptions && !isIndirect && _transport.introducersMaybeRequired();
            state = new OutboundEstablishState(_context, maybeTo, to, toIdentity, allowExtendedOptions, requestIntroduction, sessionKey, addr, _transport.getDHFactory());
            OutboundEstablishState oldState = _outboundStates.putIfAbsent(to, state);
            boolean isNew = oldState == null;
            if (isNew) {
                if (isIndirect && maybeTo != null)
                    _outboundByClaimedAddress.put(maybeTo, state);
                if (_log.shouldLog(Log.DEBUG))
                    _log.debug("Adding new " + state);
            } else {
                // whoops, somebody beat us to it, throw out the state we just created
                state = oldState;
            }
        }
    }
    if (state != null) {
        state.addMessage(msg);
        List<OutNetMessage> queued = _queuedOutbound.remove(to);
        if (queued != null) {
            // see comments above
            synchronized (queued) {
                for (OutNetMessage m : queued) {
                    state.addMessage(m);
                }
            }
        }
    }
    if (rejected) {
        if (_log.shouldLog(Log.WARN))
            _log.warn("Too many pending, rejecting outbound establish to " + to);
        _transport.failed(msg, "Too many pending outbound connections");
        _context.statManager().addRateData("udp.establishRejected", deferred);
        return;
    }
    if (queueCount >= MAX_QUEUED_PER_PEER) {
        _transport.failed(msg, "Too many pending messages for the given peer");
        _context.statManager().addRateData("udp.establishOverflow", queueCount, deferred);
        return;
    }
    if (deferred > 0)
        msg.timestamp("too many deferred establishers");
    else if (state != null)
        msg.timestamp("establish state already waiting");
    notifyActivity();
}
Also used : RouterInfo(net.i2p.data.router.RouterInfo) RouterIdentity(net.i2p.data.router.RouterIdentity) ArrayList(java.util.ArrayList) RouterAddress(net.i2p.data.router.RouterAddress) Hash(net.i2p.data.Hash) OutNetMessage(net.i2p.router.OutNetMessage) SessionKey(net.i2p.data.SessionKey) InetAddress(java.net.InetAddress)

Example 8 with RouterAddress

use of net.i2p.data.router.RouterAddress in project i2p.i2p by i2p.

the class IntroductionManager method pickInbound.

/**
 * Grab a bunch of peers who are willing to be introducers for us that
 * are locally known (duh) and have published their own SSU address (duh^2).
 * The picked peers have their info tacked on to the ssuOptions parameter for
 * use in the SSU RouterAddress.
 *
 * Try to use "good" peers (i.e. reachable, active)
 *
 * Also, ping all idle peers that were introducers in the last 2 hours,
 * to keep the connection up, since the netDb can have quite stale information,
 * and we want to keep our introducers valid.
 *
 * @param current current router address, may be null
 * @param ssuOptions out parameter, options are added
 * @return number of introducers added
 */
public int pickInbound(RouterAddress current, Properties ssuOptions, int howMany) {
    int start = _context.random().nextInt();
    if (_log.shouldLog(Log.DEBUG))
        _log.debug("Picking inbound out of " + _inbound.size());
    if (_inbound.isEmpty())
        return 0;
    List<PeerState> peers = new ArrayList<PeerState>(_inbound);
    int sz = peers.size();
    start = start % sz;
    int found = 0;
    long now = _context.clock().now();
    // 15 min
    long inactivityCutoff = now - (UDPTransport.EXPIRE_TIMEOUT / 2);
    // if not too many to choose from, be less picky
    if (sz <= howMany + 2)
        inactivityCutoff -= UDPTransport.EXPIRE_TIMEOUT / 4;
    List<Introducer> introducers = new ArrayList<Introducer>(howMany);
    for (int i = 0; i < sz && found < howMany; i++) {
        PeerState cur = peers.get((start + i) % sz);
        RouterInfo ri = _context.netDb().lookupRouterInfoLocally(cur.getRemotePeer());
        if (ri == null) {
            if (_log.shouldLog(Log.INFO))
                _log.info("Picked peer has no local routerInfo: " + cur);
            continue;
        }
        // FIXME we can include all his addresses including IPv6 even if we don't support IPv6 (isValid() is false)
        // but requires RelayRequest support, see below
        RouterAddress ra = _transport.getTargetAddress(ri);
        if (ra == null) {
            if (_log.shouldLog(Log.INFO))
                _log.info("Picked peer has no SSU address: " + ri);
            continue;
        }
        if (/* _context.profileOrganizer().isFailing(cur.getRemotePeer()) || */
        _context.banlist().isBanlisted(cur.getRemotePeer()) || _transport.wasUnreachable(cur.getRemotePeer())) {
            if (_log.shouldLog(Log.INFO))
                _log.info("Peer is failing, shistlisted or was unreachable: " + cur);
            continue;
        }
        // FIXED, was ||, is this OK now?
        if (cur.getLastReceiveTime() < inactivityCutoff && cur.getLastSendTime() < inactivityCutoff) {
            if (_log.shouldLog(Log.INFO))
                _log.info("Peer is idle too long: " + cur);
            continue;
        }
        // FIXME we can include all his addresses including IPv6 even if we don't support IPv6 (isValid() is false)
        // but requires RelayRequest support, see below
        byte[] ip = cur.getRemoteIP();
        int port = cur.getRemotePort();
        if (!isValid(ip, port))
            continue;
        if (_log.shouldLog(Log.INFO))
            _log.info("Picking introducer: " + cur);
        cur.setIntroducerTime();
        UDPAddress ura = new UDPAddress(ra);
        byte[] ikey = ura.getIntroKey();
        if (ikey == null)
            continue;
        introducers.add(new Introducer(ip, port, ikey, cur.getTheyRelayToUsAs()));
        found++;
    }
    // we sort them so a change in order only won't happen, and won't cause a republish
    Collections.sort(introducers);
    String exp = Long.toString((now + INTRODUCER_EXPIRATION) / 1000);
    for (int i = 0; i < found; i++) {
        Introducer in = introducers.get(i);
        ssuOptions.setProperty(UDPAddress.PROP_INTRO_HOST_PREFIX + i, in.sip);
        ssuOptions.setProperty(UDPAddress.PROP_INTRO_PORT_PREFIX + i, in.sport);
        ssuOptions.setProperty(UDPAddress.PROP_INTRO_KEY_PREFIX + i, in.skey);
        ssuOptions.setProperty(UDPAddress.PROP_INTRO_TAG_PREFIX + i, in.stag);
        String sexp = exp;
        // and reuse if still recent enough, so deepEquals() won't fail in UDPT.rEA
        if (current != null) {
            for (int j = 0; j < UDPTransport.PUBLIC_RELAY_COUNT; j++) {
                if (in.sip.equals(current.getOption(UDPAddress.PROP_INTRO_HOST_PREFIX + j)) && in.sport.equals(current.getOption(UDPAddress.PROP_INTRO_PORT_PREFIX + j)) && in.skey.equals(current.getOption(UDPAddress.PROP_INTRO_KEY_PREFIX + j)) && in.stag.equals(current.getOption(UDPAddress.PROP_INTRO_TAG_PREFIX + j))) {
                    // found old one
                    String oexp = current.getOption(UDPAddress.PROP_INTRO_EXP_PREFIX + j);
                    if (oexp != null) {
                        try {
                            long oex = Long.parseLong(oexp) * 1000;
                            if (oex > now + UDPTransport.INTRODUCER_EXPIRATION_MARGIN) {
                                // still good, use old expiration time
                                sexp = oexp;
                            }
                        } catch (NumberFormatException nfe) {
                        }
                    }
                    break;
                }
            }
        }
        ssuOptions.setProperty(UDPAddress.PROP_INTRO_EXP_PREFIX + i, sexp);
    }
    // FIXME failsafe if found == 0, relax inactivityCutoff and try again?
    pingIntroducers();
    return found;
}
Also used : RouterInfo(net.i2p.data.router.RouterInfo) ArrayList(java.util.ArrayList) RouterAddress(net.i2p.data.router.RouterAddress)

Example 9 with RouterAddress

use of net.i2p.data.router.RouterAddress in project i2p.i2p by i2p.

the class EventPumper method runDelayedEvents.

/**
 *  Pull off the 4 _wants* queues and update the interest ops,
 *  which may, according to the javadocs, be a "naive" implementation and block.
 *  High-frequency path in thread.
 */
private void runDelayedEvents() {
    NTCPConnection con;
    while ((con = _wantsRead.poll()) != null) {
        SelectionKey key = con.getKey();
        try {
            key.interestOps(key.interestOps() | SelectionKey.OP_READ);
        } catch (CancelledKeyException cke) {
            // ignore, we remove/etc elsewhere
            if (_log.shouldLog(Log.WARN))
                _log.warn("RDE CKE 1", cke);
        } catch (IllegalArgumentException iae) {
            // ...4 more
            if (_log.shouldLog(Log.WARN))
                _log.warn("gnu?", iae);
        }
    }
    // check before instantiating iterator for speed
    if (!_wantsWrite.isEmpty()) {
        for (Iterator<NTCPConnection> iter = _wantsWrite.iterator(); iter.hasNext(); ) {
            con = iter.next();
            SelectionKey key = con.getKey();
            if (key == null)
                continue;
            iter.remove();
            try {
                key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
            } catch (CancelledKeyException cke) {
                if (_log.shouldLog(Log.WARN))
                    _log.warn("RDE CKE 2", cke);
            // ignore
            } catch (IllegalArgumentException iae) {
                // see above
                if (_log.shouldLog(Log.WARN))
                    _log.warn("gnu?", iae);
            }
        }
    }
    // only when address changes
    ServerSocketChannel chan;
    while ((chan = _wantsRegister.poll()) != null) {
        try {
            SelectionKey key = chan.register(_selector, SelectionKey.OP_ACCEPT);
            key.attach(chan);
        } catch (ClosedChannelException cce) {
            if (_log.shouldLog(Log.WARN))
                _log.warn("Error registering", cce);
        }
    }
    while ((con = _wantsConRegister.poll()) != null) {
        try {
            SelectionKey key = con.getChannel().register(_selector, SelectionKey.OP_CONNECT);
            key.attach(con);
            con.setKey(key);
            RouterAddress naddr = con.getRemoteAddress();
            try {
                // no DNS lookups, do not use host names
                int port = naddr.getPort();
                byte[] ip = naddr.getIP();
                if (port <= 0 || ip == null)
                    throw new IOException("Invalid NTCP address: " + naddr);
                InetSocketAddress saddr = new InetSocketAddress(InetAddress.getByAddress(ip), port);
                boolean connected = con.getChannel().connect(saddr);
                if (connected) {
                    // Never happens, we use nonblocking
                    // _context.statManager().addRateData("ntcp.connectImmediate", 1);
                    key.interestOps(SelectionKey.OP_READ);
                    processConnect(key);
                }
            } catch (IOException ioe) {
                if (_log.shouldLog(Log.WARN))
                    _log.warn("error connecting to " + Addresses.toString(naddr.getIP(), naddr.getPort()), ioe);
                _context.statManager().addRateData("ntcp.connectFailedIOE", 1);
                _transport.markUnreachable(con.getRemotePeer().calculateHash());
                // if (ntcpOnly(con)) {
                // _context.banlist().banlistRouter(con.getRemotePeer().calculateHash(), "unable to connect: " + ioe.getMessage());
                // con.close(false);
                // } else {
                // _context.banlist().banlistRouter(con.getRemotePeer().calculateHash(), "unable to connect: " + ioe.getMessage(), NTCPTransport.STYLE);
                con.close(true);
            // }
            } catch (UnresolvedAddressException uae) {
                if (_log.shouldLog(Log.WARN))
                    _log.warn("unresolved address connecting", uae);
                _context.statManager().addRateData("ntcp.connectFailedUnresolved", 1);
                _transport.markUnreachable(con.getRemotePeer().calculateHash());
                // if (ntcpOnly(con)) {
                // _context.banlist().banlistRouter(con.getRemotePeer().calculateHash(), "unable to connect/resolve: " + uae.getMessage());
                // con.close(false);
                // } else {
                // _context.banlist().banlistRouter(con.getRemotePeer().calculateHash(), "unable to connect/resolve: " + uae.getMessage(), NTCPTransport.STYLE);
                con.close(true);
            // }
            } catch (CancelledKeyException cke) {
                con.close(false);
            }
        } catch (ClosedChannelException cce) {
            if (_log.shouldLog(Log.WARN))
                _log.warn("Error registering", cce);
        }
    }
    long now = System.currentTimeMillis();
    if (_lastExpired + 1000 <= now) {
        expireTimedOut();
        _lastExpired = now;
    }
}
Also used : SelectionKey(java.nio.channels.SelectionKey) ClosedChannelException(java.nio.channels.ClosedChannelException) CancelledKeyException(java.nio.channels.CancelledKeyException) InetSocketAddress(java.net.InetSocketAddress) RouterAddress(net.i2p.data.router.RouterAddress) IOException(java.io.IOException) UnresolvedAddressException(java.nio.channels.UnresolvedAddressException) ServerSocketChannel(java.nio.channels.ServerSocketChannel)

Example 10 with RouterAddress

use of net.i2p.data.router.RouterAddress in project i2p.i2p by i2p.

the class NTCPTransport method bid.

public TransportBid bid(RouterInfo toAddress, long dataSize) {
    if (!isAlive())
        return null;
    if (dataSize > NTCPConnection.MAX_MSG_SIZE) {
        // let SSU deal with it
        _context.statManager().addRateData("ntcp.noBidTooLargeI2NP", dataSize);
        return null;
    }
    Hash peer = toAddress.getIdentity().calculateHash();
    if (_context.banlist().isBanlisted(peer, STYLE)) {
        // we aren't banlisted in general (since we are trying to get a bid), but we have
        // recently banlisted the peer on the NTCP transport, so don't try it
        _context.statManager().addRateData("ntcp.attemptBanlistedPeer", 1);
        return null;
    } else if (isUnreachable(peer)) {
        _context.statManager().addRateData("ntcp.attemptUnreachablePeer", 1);
        return null;
    }
    boolean established = isEstablished(toAddress.getIdentity());
    if (established) {
        // _log.debug("fast bid when trying to send to " + peer + " as its already established");
        return _fastBid;
    }
    RouterAddress addr = getTargetAddress(toAddress);
    if (addr == null) {
        markUnreachable(peer);
        return null;
    }
    // Check for supported sig type
    SigType type = toAddress.getIdentity().getSigType();
    if (type == null || !type.isAvailable()) {
        markUnreachable(peer);
        return null;
    }
    // Can we connect to them if we are not DSA?
    RouterInfo us = _context.router().getRouterInfo();
    if (us != null) {
        RouterIdentity id = us.getIdentity();
        if (id.getSigType() != SigType.DSA_SHA1) {
            String v = toAddress.getVersion();
            if (VersionComparator.comp(v, MIN_SIGTYPE_VERSION) < 0) {
                markUnreachable(peer);
                return null;
            }
        }
    }
    if (!allowConnection()) {
        // _log.warn("no bid when trying to send to " + peer + ", max connection limit reached");
        return _transientFail;
    }
    // _log.debug("slow bid when trying to send to " + peer);
    if (haveCapacity()) {
        if (addr.getCost() > DEFAULT_COST)
            return _slowCostBid;
        else
            return _slowBid;
    } else {
        if (addr.getCost() > DEFAULT_COST)
            return _nearCapacityCostBid;
        else
            return _nearCapacityBid;
    }
}
Also used : RouterInfo(net.i2p.data.router.RouterInfo) RouterIdentity(net.i2p.data.router.RouterIdentity) RouterAddress(net.i2p.data.router.RouterAddress) Hash(net.i2p.data.Hash) SigType(net.i2p.crypto.SigType)

Aggregations

RouterAddress (net.i2p.data.router.RouterAddress)42 RouterInfo (net.i2p.data.router.RouterInfo)17 Hash (net.i2p.data.Hash)11 IOException (java.io.IOException)9 ArrayList (java.util.ArrayList)9 OrderedProperties (net.i2p.util.OrderedProperties)6 InetAddress (java.net.InetAddress)5 Map (java.util.Map)5 RouterIdentity (net.i2p.data.router.RouterIdentity)5 UnknownHostException (java.net.UnknownHostException)4 HashMap (java.util.HashMap)4 DataFormatException (net.i2p.data.DataFormatException)4 ServerSocketChannel (java.nio.channels.ServerSocketChannel)3 Date (java.util.Date)3 Status (net.i2p.router.CommSystemFacade.Status)3 OutNetMessage (net.i2p.router.OutNetMessage)3 File (java.io.File)2 InetSocketAddress (java.net.InetSocketAddress)2 Properties (java.util.Properties)2 CopyOnWriteArrayList (java.util.concurrent.CopyOnWriteArrayList)2