package net.i2p.router.client; /* * free (adj.): unencumbered; not under the control of others * Written by jrandom in 2003 and released into the public domain * with no warranty of any kind, either expressed or implied. * It probably won't make your computer catch on fire, or eat * your children, but it might. Use at your own risk. * */ import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import net.i2p.client.I2PClient; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.util.Log; import net.i2p.util.PortMapper; /** * Listen for connections on the specified port, and toss them onto the client manager's * set of connections once they are established. * * @author jrandom */ class ClientListenerRunner implements Runnable { protected final Log _log; protected final RouterContext _context; protected final ClientManager _manager; protected ServerSocket _socket; protected final int _port; protected final boolean _bindAllInterfaces; protected volatile boolean _running; protected volatile boolean _listening; public static final String BIND_ALL_INTERFACES = "i2cp.tcp.bindAllInterfaces"; public ClientListenerRunner(RouterContext context, ClientManager manager, int port) { _context = context; _log = _context.logManager().getLog(getClass()); _manager = manager; _port = port; _bindAllInterfaces = context.getBooleanProperty(BIND_ALL_INTERFACES); } public boolean isListening() { return _running && _listening; } /** * Get a ServerSocket. * Split out so it can be overridden for SSL. * @since 0.8.3 */ protected ServerSocket getServerSocket() throws IOException { if (_bindAllInterfaces) { if (_log.shouldLog(Log.INFO)) _log.info("Listening on port " + _port + " on all interfaces"); return new ServerSocket(_port); } else { String listenInterface = _context.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST, ClientManagerFacadeImpl.DEFAULT_HOST); if (_log.shouldLog(Log.INFO)) _log.info("Listening on port " + _port + " of the specific interface: " + listenInterface); return new ServerSocket(_port, 0, InetAddress.getByName(listenInterface)); } } public void run() { runServer(); } /** * Start up the socket listener, listens for connections, and * fires those connections off via {@link #runConnection runConnection}. * This only returns if the socket cannot be opened or there is a catastrophic * failure. * */ protected void runServer() { _running = true; int curDelay = 1000; final String portMapperService = (this instanceof SSLClientListenerRunner) ? PortMapper.SVC_I2CP_SSL : PortMapper.SVC_I2CP; while (_running) { try { _socket = getServerSocket(); if (_log.shouldLog(Log.DEBUG)) _log.debug("ServerSocket created, before accept: " + _socket); if (_port > 0) { // not for DomainClientListenerRunner _context.portMapper().register(portMapperService, _socket.getInetAddress().getHostAddress(), _port); } curDelay = 1000; _listening = true; while (_running) { try { Socket socket = _socket.accept(); if (validate(socket)) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Connection received"); socket.setKeepAlive(true); runConnection(socket); } else { if (_log.shouldLog(Log.WARN)) _log.warn("Refused connection from " + socket.getInetAddress()); try { socket.close(); } catch (IOException ioe) {} } } catch (IOException ioe) { if (isAlive()) _log.error("Server error accepting", ioe); } catch (Throwable t) { if (isAlive()) _log.error("Fatal error running client listener - killing the thread!", t); _listening = false; return; } } } catch (IOException ioe) { if (isAlive()) _log.error("Error listening on port " + _port, ioe); } finally { if (_port > 0) { // not for DomainClientListenerRunner _context.portMapper().unregister(portMapperService); } } _listening = false; if (_socket != null) { try { _socket.close(); } catch (IOException ioe) {} _socket = null; } if (!isAlive()) break; if (curDelay < 60*1000) _log.error("Error listening, waiting " + (curDelay/1000) + "s before we try again"); else _log.log(Log.CRIT, "I2CP error listening to port " + _port + " - is another I2P instance running? Resolve conflicts and restart"); try { Thread.sleep(curDelay); } catch (InterruptedException ie) {} curDelay = Math.min(curDelay*3, 60*1000); } if (isAlive()) _log.error("CANCELING I2CP LISTEN", new Exception("I2CP Listen cancelled!!!")); _running = false; } /** * Just so unit tests don't NPE, where router could be null. * @since 0.9.20 */ private boolean isAlive() { Router r = _context.router(); return r == null || r.isAlive(); } /** give the i2cp client 5 seconds to show that they're really i2cp clients */ protected final static int CONNECT_TIMEOUT = 5*1000; private final static int LOOP_DELAY = 250; /** * Verify the first byte. * The InternalSocket doesn't support SoTimeout, so use available() * instead to prevent hanging. */ protected boolean validate(Socket socket) { try { InputStream is = socket.getInputStream(); for (int i = 0; i < CONNECT_TIMEOUT / LOOP_DELAY; i++) { if (is.available() > 0) return is.read() == I2PClient.PROTOCOL_BYTE; try { Thread.sleep(LOOP_DELAY); } catch (InterruptedException ie) {} } } catch (IOException ioe) {} if (_log.shouldLog(Log.WARN)) _log.warn("Peer did not authenticate themselves as I2CP quickly enough, dropping"); return false; } /** * Handle the connection by passing it off to a {@link ClientConnectionRunner ClientConnectionRunner} * */ protected void runConnection(Socket socket) { ClientConnectionRunner runner = new ClientConnectionRunner(_context, _manager, socket); _manager.registerConnection(runner); } public void stopListening() { _running = false; if (_socket != null) try { _socket.close(); _socket = null; } catch (IOException ioe) {} } }