package net.i2p.router.transport.udp; import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.SocketException; import java.util.concurrent.atomic.AtomicInteger; import net.i2p.router.RouterContext; import net.i2p.router.transport.TransportUtil; import net.i2p.util.Log; /** * Coordinate the low-level datagram socket, creating and managing the UDPSender and * UDPReceiver. */ class UDPEndpoint implements SocketListener { private final RouterContext _context; private final Log _log; private int _listenPort; private final UDPTransport _transport; private UDPSender _sender; private UDPReceiver _receiver; private DatagramSocket _socket; private final InetAddress _bindAddress; private final boolean _isIPv4, _isIPv6; private static final AtomicInteger _counter = new AtomicInteger(); /** * @param transport may be null for unit testing ONLY * @param listenPort -1 or the requested port, may not be honored * @param bindAddress null ok */ public UDPEndpoint(RouterContext ctx, UDPTransport transport, int listenPort, InetAddress bindAddress) { _context = ctx; _log = ctx.logManager().getLog(UDPEndpoint.class); _transport = transport; _bindAddress = bindAddress; _listenPort = listenPort; _isIPv4 = bindAddress == null || bindAddress instanceof Inet4Address; _isIPv6 = bindAddress == null || bindAddress instanceof Inet6Address; } /** * Caller should call getListenPort() after this to get the actual bound port and determine success . * * Can be restarted. */ public synchronized void startup() throws SocketException { if (_log.shouldLog(Log.DEBUG)) _log.debug("Starting up the UDP endpoint"); shutdown(); _socket = getSocket(); if (_socket == null) { _log.log(Log.CRIT, "UDP Unable to open a port"); throw new SocketException("SSU Unable to bind to a port on " + _bindAddress); } int count = _counter.incrementAndGet(); _sender = new UDPSender(_context, _socket, "UDPSender " + count, this); _sender.startup(); if (_transport != null) { _receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver " + count, this); _receiver.startup(); } } public synchronized void shutdown() { if (_sender != null) { _sender.shutdown(); _receiver.shutdown(); } if (_socket != null) { _socket.close(); } } public void setListenPort(int newPort) { _listenPort = newPort; } /******* public void updateListenPort(int newPort) { if (newPort == _listenPort) return; try { if (_bindAddress == null) _socket = new DatagramSocket(_listenPort); else _socket = new DatagramSocket(_listenPort, _bindAddress); _sender.updateListeningPort(_socket, newPort); // note: this closes the old socket, so call this after the sender! _receiver.updateListeningPort(_socket, newPort); _listenPort = newPort; } catch (SocketException se) { if (_log.shouldLog(Log.ERROR)) _log.error("Unable to bind on " + _listenPort); } } ********/ /** 8998 is monotone, and 31000 is the wrapper outbound, so let's stay between those */ public static final String PROP_MIN_PORT = "i2np.udp.minPort"; public static final String PROP_MAX_PORT = "i2np.udp.maxPort"; private static final int MIN_RANDOM_PORT = 9111; private static final int MAX_RANDOM_PORT = 30777; private static final int MAX_PORT_RETRIES = 20; /** * Open socket using requested port in _listenPort and bind host in _bindAddress. * If _listenPort <= 0, or requested port is busy, repeatedly try a new random port. * @return null on failure * Sets _listenPort to actual port or -1 on failure */ private DatagramSocket getSocket() { DatagramSocket socket = null; int port = _listenPort; if (port > 0 && !TransportUtil.isValidPort(port)) { _log.error("Specified UDP port " + port + " is not valid, selecting a new port"); // See isValidPort() for list _log.error("Invalid ports are: 0-1023, 1900, 2827, 4444, 4445, 6668, 7650-7664, 8998, 31000, 32000, 65536+"); port = -1; } for (int i = 0; i < MAX_PORT_RETRIES; i++) { if (port <= 0) { // try random ports rather than just do new DatagramSocket() // so we stay out of the way of other I2P stuff port = selectRandomPort(_context); } try { if (_bindAddress == null) socket = new DatagramSocket(port); else socket = new DatagramSocket(port, _bindAddress); break; } catch (SocketException se) { if (_log.shouldLog(Log.WARN)) _log.warn("Binding to port " + port + " failed", se); } port = -1; } if (socket == null) { _log.log(Log.CRIT, "SSU Unable to bind to a port on " + _bindAddress); } else if (port != _listenPort) { if (_listenPort > 0) _log.error("SSU Unable to bind to requested port " + _listenPort + ", using random port " + port); else _log.logAlways(Log.INFO, "UDP selected random port " + port); } _listenPort = port; return socket; } /** * Pick a random port between the configured boundaries * @since IPv6 */ public static int selectRandomPort(RouterContext ctx) { int minPort = Math.min(65535, Math.max(1, ctx.getProperty(PROP_MIN_PORT, MIN_RANDOM_PORT))); int maxPort = Math.min(65535, Math.max(minPort, ctx.getProperty(PROP_MAX_PORT, MAX_RANDOM_PORT))); return minPort + ctx.random().nextInt(1 + maxPort - minPort); } /** call after startup() to get actual port or -1 on startup failure */ public int getListenPort() { return _listenPort; } public UDPSender getSender() { return _sender; } /** * Add the packet to the outobund queue to be sent ASAP (as allowed by * the bandwidth limiter) * BLOCKING if queue is full. */ public void send(UDPPacket packet) { _sender.add(packet); } /** * Blocking call to receive the next inbound UDP packet from any peer. * * UNIT TESTING ONLY. Direct from the socket. * In normal operation, UDPReceiver thread injects to PacketHandler queue. * * @return null if we have shut down, or on failure */ public UDPPacket receive() { UDPPacket packet = UDPPacket.acquire(_context, true); try { _socket.receive(packet.getPacket()); return packet; } catch (IOException ioe) { packet.release(); return null; } } /** * Clear outbound queue, probably in preparation for sending destroy() to everybody. * @since 0.9.2 */ public void clearOutbound() { if (_sender != null) _sender.clear(); } /** * @return true for wildcard too * @since IPv6 */ public boolean isIPv4() { return _isIPv4; } /** * @return true for wildcard too * @since IPv6 */ public boolean isIPv6() { return _isIPv6; } /** * @since 0.9.16 */ public void fail() { shutdown(); _transport.fail(this); } /** * @since 0.9.16 */ @Override public String toString() { StringBuilder buf = new StringBuilder(64); buf.append("UDP Socket "); if (_bindAddress != null) buf.append(_bindAddress.toString()).append(' '); buf.append("port ").append(_listenPort); return buf.toString(); } }