package net.i2p.router.transport.udp; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.util.concurrent.BlockingQueue; import net.i2p.router.RouterContext; import net.i2p.router.transport.FIFOBandwidthLimiter; import net.i2p.router.util.CoDelBlockingQueue; import net.i2p.util.I2PThread; import net.i2p.util.Log; import net.i2p.util.SystemVersion; /** * Lowest level packet sender, pushes anything on its queue ASAP. * * There is a UDPSender for each UDPEndpoint. * It contains a thread and a queue. Packet to be sent are queued * by the PacketPusher. */ class UDPSender { private final RouterContext _context; private final Log _log; private final DatagramSocket _socket; private String _name; private final BlockingQueue<UDPPacket> _outboundQueue; private volatile boolean _keepRunning; private final Runner _runner; private final boolean _dummy; private final SocketListener _endpoint; private static final int TYPE_POISON = 99999; // Queue needs to be big enough that we can compete with NTCP for // bandwidth requests, and so CoDel can work well. // When full, packets back up into the PacketPusher thread, pre-CoDel. private static final int MIN_QUEUE_SIZE = 64; private static final int MAX_QUEUE_SIZE = 384; public UDPSender(RouterContext ctx, DatagramSocket socket, String name, SocketListener lsnr) { _context = ctx; _dummy = false; // ctx.commSystem().isDummy(); _log = ctx.logManager().getLog(UDPSender.class); long maxMemory = SystemVersion.getMaxMemory(); int qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (1024*1024))); _outboundQueue = new CoDelBlockingQueue<UDPPacket>(ctx, "UDP-Sender", qsize); _socket = socket; _runner = new Runner(); _name = name; _endpoint = lsnr; _context.statManager().createRateStat("udp.pushTime", "How long a UDP packet takes to get pushed out", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendQueueSize", "How many packets are queued on the UDP sender", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendQueueFailed", "How often it was unable to add a new packet to the queue", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendQueueTrimmed", "How many packets were removed from the queue for being too old (duration == remaining)", "udp", UDPTransport.RATES); _context.statManager().createRequiredRateStat("udp.sendPacketSize", "Size of sent packets (bytes)", "udp", UDPTransport.RATES); //_context.statManager().createRateStat("udp.socketSendTime", "How long the actual socket.send took", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendBWThrottleTime", "How long the send is blocked by the bandwidth throttle", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendACKTime", "How long an ACK packet is blocked for (duration == lifetime)", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendFailsafe", "limiter stuck?", "udp", new long[] { 24*60*60*1000L }); // used in RouterWatchdog _context.statManager().createRequiredRateStat("udp.sendException", "Send fails (Windows exception?)", "udp", new long[] { 60*1000, 10*60*1000 }); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_ACK, "ack-only packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_PUNCH, "hole punch packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_RESP, "relay response packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_INTRO, "relay intro packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_RREQ, "relay request packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_TCB, "peer test charlie to bob packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_TBC, "peer test bob to charlie packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_TTA, "peer test to alice packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_TFA, "peer test from alice packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_CONF, "session confirmed packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_SREQ, "session request packet size", "udp", UDPTransport.RATES); _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_CREAT, "session created packet size", "udp", UDPTransport.RATES); } /** * Cannot be restarted (socket is final) */ public synchronized void startup() { if (_log.shouldLog(Log.DEBUG)) _log.debug("Starting the runner: " + _name); _keepRunning = true; I2PThread t = new I2PThread(_runner, _name, true); t.start(); } public synchronized void shutdown() { if (!_keepRunning) return; _keepRunning = false; _outboundQueue.clear(); UDPPacket poison = UDPPacket.acquire(_context, false); poison.setMessageType(TYPE_POISON); _outboundQueue.offer(poison); for (int i = 1; i <= 5 && !_outboundQueue.isEmpty(); i++) { try { Thread.sleep(i * 50); } catch (InterruptedException ie) {} } _outboundQueue.clear(); } /** * Clear outbound queue, probably in preparation for sending destroy() to everybody. * @since 0.9.2 */ public void clear() { _outboundQueue.clear(); } /********* public DatagramSocket updateListeningPort(DatagramSocket socket, int newPort) { return _runner.updateListeningPort(socket, newPort); } **********/ /** * Add the packet to the queue. This may block until there is space * available, if requested, otherwise it returns immediately * * @param blockTime how long to block IGNORED * @deprecated use add(packet) */ @Deprecated public void add(UDPPacket packet, int blockTime) { /******** //long expiration = _context.clock().now() + blockTime; int remaining = -1; long lifetime = -1; boolean added = false; int removed = 0; while ( (_keepRunning) && (remaining < 0) ) { //try { synchronized (_outboundQueue) { // clear out any too-old packets UDPPacket head = null; if (!_outboundQueue.isEmpty()) { head = (UDPPacket)_outboundQueue.get(0); while (head.getLifetime() > MAX_HEAD_LIFETIME) { _outboundQueue.remove(0); removed++; if (!_outboundQueue.isEmpty()) head = (UDPPacket)_outboundQueue.get(0); else break; } } if (true || (_outboundQueue.size() < MAX_QUEUED)) { lifetime = packet.getLifetime(); _outboundQueue.add(packet); added = true; remaining = _outboundQueue.size(); _outboundQueue.notifyAll(); } else { long remainingTime = expiration - _context.clock().now(); if (remainingTime > 0) { _outboundQueue.wait(remainingTime); } else { remaining = _outboundQueue.size(); _outboundQueue.notifyAll(); } lifetime = packet.getLifetime(); } } //} catch (InterruptedException ie) {} } _context.statManager().addRateData("udp.sendQueueSize", remaining, lifetime); if (!added) _context.statManager().addRateData("udp.sendQueueFailed", remaining, lifetime); if (removed > 0) _context.statManager().addRateData("udp.sendQueueTrimmed", removed, remaining); if (_log.shouldLog(Log.DEBUG)) _log.debug("Added the packet onto the queue with " + remaining + " remaining and a lifetime of " + lifetime); return remaining; ********/ add(packet); } private static final int MAX_HEAD_LIFETIME = 3*1000; /** * Put it on the queue. * BLOCKING if queue is full (backs up PacketPusher thread) */ public void add(UDPPacket packet) { if (packet == null || !_keepRunning) return; int psz = packet.getPacket().getLength(); if (psz > PeerState.MAX_MTU) { _log.error("Dropping large UDP packet " + psz + " bytes: " + packet); return; } if (_dummy) { // testing // back to the cache packet.release(); return; } packet.requestOutboundBandwidth(); try { _outboundQueue.put(packet); } catch (InterruptedException ie) { packet.release(); return; } //size = _outboundQueue.size(); //_context.statManager().addRateData("udp.sendQueueSize", size, lifetime); if (_log.shouldLog(Log.DEBUG)) { _log.debug("Added the packet onto the queue with a lifetime of " + packet.getLifetime()); } } private class Runner implements Runnable { //private volatile boolean _socketChanged; public void run() { if (_log.shouldLog(Log.DEBUG)) _log.debug("Running the UDP sender"); //_socketChanged = false; while (_keepRunning) { //if (_socketChanged) { // Thread.currentThread().setName(_name); // _socketChanged = false; //} UDPPacket packet = getNextPacket(); if (packet != null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Packet to send known: " + packet); long acquireTime = _context.clock().now(); int size = packet.getPacket().getLength(); // ?? int size2 = packet.getPacket().getLength(); if (size > 0) { //_context.bandwidthLimiter().requestOutbound(req, size, "UDP sender"); FIFOBandwidthLimiter.Request req = packet.getBandwidthRequest(); if (req != null) { // failsafe, don't wait forever int waitCount = 0; while (req.getPendingRequested() > 0 && waitCount++ < 5) { req.waitForNextAllocation(); } if (waitCount >= 5) { // tell FBL we didn't send it, but send it anyway req.abort(); _context.statManager().addRateData("udp.sendFailsafe", 1); } } } long afterBW = _context.clock().now(); //if (_log.shouldLog(Log.DEBUG)) { //if (len > 128) // len = 128; //_log.debug("Sending packet: (size="+size + "/"+size2 +")\nraw: " + Base64.encode(packet.getPacket().getData(), 0, size)); //} if (packet.getMessageType() >= PacketBuilder.TYPE_FIRST) _context.statManager().addRateData("udp.sendPacketSize." + packet.getMessageType(), size, packet.getFragmentCount()); //packet.getPacket().setLength(size); try { //long before = _context.clock().now(); //synchronized (Runner.this) { // synchronization lets us update safely //_log.debug("Break out datagram for " + packet); DatagramPacket dp = packet.getPacket(); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Just before socket.send of " + packet); _socket.send(dp); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Just after socket.send of " + packet); //} //long sendTime = _context.clock().now() - before; // less than 50 microsec //_context.statManager().addRateData("udp.socketSendTime", sendTime, packet.getLifetime()); if (_log.shouldLog(Log.DEBUG)) _log.debug("Sent the packet " + packet); long throttleTime = afterBW - acquireTime; if (throttleTime > 10) _context.statManager().addRateData("udp.sendBWThrottleTime", throttleTime, acquireTime - packet.getBegin()); if (packet.getMarkedType() == 1) _context.statManager().addRateData("udp.sendACKTime", throttleTime, packet.getLifetime()); _context.statManager().addRateData("udp.pushTime", packet.getLifetime(), packet.getLifetime()); _context.statManager().addRateData("udp.sendPacketSize", size, packet.getLifetime()); } catch (IOException ioe) { if (_log.shouldLog(Log.WARN)) _log.warn("Error sending to " + packet.getPacket().getAddress(), ioe); _context.statManager().addRateData("udp.sendException", 1, packet.getLifetime()); if (_socket.isClosed()) { if (_keepRunning) { _keepRunning = false; _endpoint.fail(); } } } // back to the cache packet.release(); } } if (_log.shouldLog(Log.WARN)) _log.warn("Stop sending on " + _endpoint); _outboundQueue.clear(); } /** @return next packet in queue. Will discard any packet older than MAX_HEAD_LIFETIME */ private UDPPacket getNextPacket() { UDPPacket packet = null; while ( (_keepRunning) && (packet == null || packet.getLifetime() > MAX_HEAD_LIFETIME) ) { if (packet != null) { _context.statManager().addRateData("udp.sendQueueTrimmed", 1); packet.release(); } try { packet = _outboundQueue.take(); } catch (InterruptedException ie) {} if (packet != null && packet.getMessageType() == TYPE_POISON) return null; } return packet; } /****** public DatagramSocket updateListeningPort(DatagramSocket socket, int newPort) { _name = "UDPSend on " + newPort; DatagramSocket old = null; synchronized (Runner.this) { old = _socket; _socket = socket; } _socketChanged = true; return old; } *****/ } }