package net.i2p.i2ptunnel.irc; import java.util.Iterator; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.data.Base32; import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.i2ptunnel.Logging; import net.i2p.util.EventDispatcher; import net.i2p.util.Log; /** * Start, track, and expire the I2PTunnelDCCClients. * * <pre> * * direct conn * <---> I2PTunnelDCCServer <--------------->I2PTunnelDCCClient <----> * originating responding * chat client chat client * CHAT ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient -----> * SEND ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient -----> * RESUME <--- I2PTunnelIRCClient <-- IRC server <-- I2TunnelIRCClient <----- * ACCEPT ---> I2PTunnelIRCClient --> IRC server --> I2TunnelIRCClient -----> * * </pre> * * @since 0.8.9 */ public class DCCClientManager extends EventReceiver { private final I2PSocketManager sockMgr; private final EventDispatcher _dispatch; private final Logging l; private final I2PTunnel _tunnel; private final Log _log; /** key is the DCC client's local port */ private final ConcurrentHashMap<Integer, I2PTunnelDCCClient> _incoming; /** key is the DCC client's local port */ private final ConcurrentHashMap<Integer, I2PTunnelDCCClient> _active; /** key is the DCC client's local port */ private final ConcurrentHashMap<Integer, I2PTunnelDCCClient> _complete; // list of client tunnels? private static long _id; private static final int MAX_INCOMING_PENDING = 10; private static final int MAX_INCOMING_ACTIVE = 10; public DCCClientManager(I2PSocketManager sktMgr, Logging logging, EventDispatcher dispatch, I2PTunnel tunnel) { sockMgr = sktMgr; l = logging; _dispatch = dispatch; _tunnel = tunnel; _log = tunnel.getContext().logManager().getLog(DCCClientManager.class); _incoming = new ConcurrentHashMap<Integer, I2PTunnelDCCClient>(8); _active = new ConcurrentHashMap<Integer, I2PTunnelDCCClient>(8); _complete = new ConcurrentHashMap<Integer, I2PTunnelDCCClient>(8); } public boolean close(boolean forced) { for (I2PTunnelDCCClient c : _incoming.values()) { c.stop(); } _incoming.clear(); for (I2PTunnelDCCClient c : _active.values()) { c.stop(); } _active.clear(); _complete.clear(); return true; } /** * An incoming DCC request * * @param b32 remote dcc server b32 address * @param port remote dcc server I2P port * @param type ignored * @return local DCC client tunnel port or -1 on error */ public int newIncoming(String b32, int port, String type) { return newIncoming(b32, port, type, 0); } /** * @param localPort bind to port or 0; if nonzero it will be the rv */ private int newIncoming(String b32, int port, String type, int localPort) { b32 = b32.toLowerCase(Locale.US); // do some basic verification before starting the client if (b32.length() != 60 || !b32.endsWith(".b32.i2p")) return -1; byte[] dec = Base32.decode(b32.substring(0, 52)); if (dec == null || dec.length != 32) return -1; expireInbound(); if (_incoming.size() >= MAX_INCOMING_PENDING || _active.size() >= MAX_INCOMING_PENDING) { _log.error("Too many incoming DCC, max is " + MAX_INCOMING_PENDING + '/' + MAX_INCOMING_ACTIVE + " pending/active"); return -1; } try { // Transparent tunnel used for all types... // Do we need to do any filtering for chat? I2PTunnelDCCClient cTunnel = new I2PTunnelDCCClient(b32, localPort, port, l, sockMgr, _dispatch, _tunnel, ++_id); cTunnel.attachEventDispatcher(this); cTunnel.startRunning(); int lport = cTunnel.getLocalPort(); if (_log.shouldLog(Log.WARN)) _log.warn("Opened client tunnel at port " + lport + " pointing to " + b32 + ':' + port); _incoming.put(Integer.valueOf(lport), cTunnel); return lport; } catch (IllegalArgumentException uhe) { l.log("Could not find listen host to bind to [" + _tunnel.host + "]"); _log.error("Error finding host to bind", uhe); return -1; } } /** * An outgoing RESUME request * * @param port local DCC client tunnel port * @return remote DCC server i2p port or -1 on error */ public int resumeOutgoing(int port) { Integer lport = Integer.valueOf(port); I2PTunnelDCCClient tun = _complete.get(lport); if (tun == null) { tun = _active.get(lport); if (tun == null) // shouldn't happen tun = _incoming.get(lport); } if (tun != null) { tun.stop(); return tun.getLocalPort(); } return -1; } /** * An incoming ACCEPT response * * @param port remote dcc server I2P port * @return local DCC client tunnel port or -1 on error */ public int acceptIncoming(int port) { // do a reverse lookup for (I2PTunnelDCCClient tun : _complete.values()) { if (tun.getRemotePort() == port) return newIncoming(tun.getDest(), port, "ACCEPT", tun.getLocalPort()); } for (I2PTunnelDCCClient tun : _active.values()) { if (tun.getRemotePort() == port) return newIncoming(tun.getDest(), port, "ACCEPT", tun.getLocalPort()); } for (I2PTunnelDCCClient tun : _incoming.values()) { if (tun.getRemotePort() == port) { // shouldn't happen tun.stop(); return newIncoming(tun.getDest(), port, "ACCEPT", tun.getLocalPort()); } } return -1; } /** * The EventReceiver callback */ public void notifyEvent(String eventName, Object args) { if (eventName.equals(I2PTunnelDCCClient.CONNECT_START_EVENT)) { try { I2PTunnelDCCClient client = (I2PTunnelDCCClient) args; connStarted(client); } catch (ClassCastException cce) {} } else if (eventName.equals(I2PTunnelDCCClient.CONNECT_STOP_EVENT)) { try { Integer port = (Integer) args; connStopped(port); } catch (ClassCastException cce) {} } } private void connStarted(I2PTunnelDCCClient client) { Integer lport = Integer.valueOf(client.getLocalPort()); I2PTunnelDCCClient c = _incoming.remove(lport); if (c != null) { _active.put(lport, client); if (_log.shouldLog(Log.WARN)) _log.warn("Added client tunnel for port " + lport + " pending count now: " + _incoming.size() + " active count now: " + _active.size() + " complete count now: " + _complete.size()); } } private void connStopped(Integer lport) { I2PTunnelDCCClient tun = _incoming.remove(lport); if (tun != null) _complete.put(lport, tun); tun = _active.remove(lport); if (tun != null) _complete.put(lport, tun); if (_log.shouldLog(Log.WARN)) _log.warn("Removed client tunnel for port " + lport + " pending count now: " + _incoming.size() + " active count now: " + _active.size() + " complete count now: " + _complete.size()); } private void expireInbound() { for (Iterator<I2PTunnelDCCClient> iter = _incoming.values().iterator(); iter.hasNext(); ) { I2PTunnelDCCClient c = iter.next(); if (c.getExpires() < _tunnel.getContext().clock().now()) { iter.remove(); c.stop(); } } // shouldn't need to expire active for (Iterator<I2PTunnelDCCClient> iter = _complete.values().iterator(); iter.hasNext(); ) { I2PTunnelDCCClient c = iter.next(); if (c.getExpires() < _tunnel.getContext().clock().now()) { iter.remove(); c.stop(); } } } }