package net.i2p.router.transport.udp; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import net.i2p.data.Hash; import net.i2p.data.SessionKey; import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; import net.i2p.router.util.CachedIteratorArrayList; import net.i2p.router.util.CoDelPriorityBlockingQueue; import net.i2p.router.util.PriBlockingQueue; import net.i2p.util.Log; import net.i2p.util.ConcurrentHashSet; /** * Contain all of the state about a UDP connection to a peer. * This is instantiated only after a connection is fully established. */ class PeerState { private final RouterContext _context; private final Log _log; /** * The peer are we talking to. This should be set as soon as this * state is created if we are initiating a connection, but if we are * receiving the connection this will be set only after the connection * is established. */ private final Hash _remotePeer; /** * The AES key used to verify packets, set only after the connection is * established. */ private SessionKey _currentMACKey; /** * The AES key used to encrypt/decrypt packets, set only after the * connection is established. */ private SessionKey _currentCipherKey; /** * The pending AES key for verifying packets if we are rekeying the * connection, or null if we are not in the process of rekeying. */ private SessionKey _nextMACKey; /** * The pending AES key for encrypting/decrypting packets if we are * rekeying the connection, or null if we are not in the process * of rekeying. */ private SessionKey _nextCipherKey; /** * The keying material used for the rekeying, or null if we are not in * the process of rekeying. */ //private byte[] _nextKeyingMaterial; /** true if we began the current rekeying, false otherwise */ //private boolean _rekeyBeganLocally; /** when were the current cipher and MAC keys established/rekeyed? */ private long _keyEstablishedTime; /** * How far off is the remote peer from our clock, in milliseconds? * A positive number means our clock is ahead of theirs. */ private long _clockSkew; private final Object _clockSkewLock = new Object(); /** what is the current receive second, for congestion control? */ private long _currentReceiveSecond; /** when did we last send them a packet? */ private long _lastSendTime; /** when did we last send them a message that was ACKed */ private long _lastSendFullyTime; /** when did we last send them a ping? */ private long _lastPingTime; /** when did we last receive a packet from them? */ private long _lastReceiveTime; /** how many consecutive messages have we sent and not received an ACK to */ private int _consecutiveFailedSends; /** when did we last have a failed send (beginning of period) */ // private long _lastFailedSendPeriod; /** * Set of messageIds (Long) that we have received but not yet sent * Since even with the smallest MTU we can fit 131 acks in a message, * we are unlikely to get backed up on acks, so we don't keep * them in any particular order. */ private final Set<Long> _currentACKs; /** * list of the most recent messageIds (Long) that we have received and sent * an ACK for. We keep a few of these around to retransmit with _currentACKs, * hopefully saving some spurious retransmissions */ private final Queue<ResendACK> _currentACKsResend; /** when did we last send ACKs to the peer? */ private volatile long _lastACKSend; /** when did we decide we need to ACK to this peer? */ private volatile long _wantACKSendSince; /** have we received a packet with the ECN bit set in the current second? */ private boolean _currentSecondECNReceived; /** * have all of the packets received in the current second requested that * the previous second's ACKs be sent? */ //private boolean _remoteWantsPreviousACKs; /** how many bytes should we send to the peer in a second */ private int _sendWindowBytes; /** how many bytes can we send to the peer in the current second */ private int _sendWindowBytesRemaining; private long _lastSendRefill; private int _sendBps; private int _sendBytes; private int _receiveBps; private int _receiveBytes; //private int _sendACKBps; //private int _sendZACKBytes; //private int _receiveACKBps; //private int _receiveACKBytes; private long _receivePeriodBegin; private volatile long _lastCongestionOccurred; /** * when sendWindowBytes is below this, grow the window size quickly, * but after we reach it, grow it slowly * */ private volatile int _slowStartThreshold; /** what IP is the peer sending and receiving packets on? */ private final byte[] _remoteIP; /** cached IP address */ private volatile InetAddress _remoteIPAddress; /** what port is the peer sending and receiving packets on? */ private volatile int _remotePort; /** cached RemoteHostId, used to find the peerState by remote info */ private volatile RemoteHostId _remoteHostId; /** if we need to contact them, do we need to talk to an introducer? */ //private boolean _remoteRequiresIntroduction; /** * if we are serving as an introducer to them, this is the the tag that * they can publish that, when presented to us, will cause us to send * a relay introduction to the current peer */ private long _weRelayToThemAs; /** * If they have offered to serve as an introducer to us, this is the tag * we can use to publish that fact. */ private long _theyRelayToUsAs; /** what is the largest packet we can currently send to the peer? */ private int _mtu; private int _mtuReceive; /** what is the largest packet we will ever send to the peer? */ private int _largeMTU; /* how many consecutive packets at or under the min MTU have been received */ private long _consecutiveSmall; /** when did we last check the MTU? */ //private long _mtuLastChecked; private int _mtuIncreases; private int _mtuDecreases; /** current round trip time estimate */ private int _rtt; /** smoothed mean deviation in the rtt */ private int _rttDeviation; /** current retransmission timeout */ private int _rto; /** how many packets will be considered within the retransmission rate calculation */ static final long RETRANSMISSION_PERIOD_WIDTH = 100; private int _messagesReceived; private int _messagesSent; private int _packetsTransmitted; /** how many packets were retransmitted within the last RETRANSMISSION_PERIOD_WIDTH packets */ private int _packetsRetransmitted; /** how many packets were transmitted within the last RETRANSMISSION_PERIOD_WIDTH packets */ //private long _packetsPeriodTransmitted; //private int _packetsPeriodRetransmitted; //private int _packetRetransmissionRate; /** how many dup packets were received within the last RETRANSMISSION_PERIOD_WIDTH packets */ private int _packetsReceivedDuplicate; private int _packetsReceived; private boolean _mayDisconnect; /** list of InboundMessageState for active message */ private final Map<Long, InboundMessageState> _inboundMessages; /** * Mostly messages that have been transmitted and are awaiting acknowledgement, * although there could be some that have not been sent yet. */ private final List<OutboundMessageState> _outboundMessages; /** * Priority queue of messages that have not yet been sent. * They are taken from here and put in _outboundMessages. */ //private final CoDelPriorityBlockingQueue<OutboundMessageState> _outboundQueue; private final PriBlockingQueue<OutboundMessageState> _outboundQueue; /** which outbound message is currently being retransmitted */ private OutboundMessageState _retransmitter; private final UDPTransport _transport; /** have we migrated away from this peer to another newer one? */ private volatile boolean _dead; /** Make sure a 4229 byte TunnelBuildMessage can be sent in one volley with small MTU */ private static final int MIN_CONCURRENT_MSGS = 8; /** how many concurrent outbound messages do we allow throws OutboundMessageFragments to send */ private int _concurrentMessagesAllowed = MIN_CONCURRENT_MSGS; /** * how many outbound messages are currently being transmitted. Not thread safe, as we're not strict */ private int _concurrentMessagesActive; /** how many concurrency rejections have we had in a row */ private int _consecutiveRejections; /** is it inbound? **/ private final boolean _isInbound; /** Last time it was made an introducer **/ private long _lastIntroducerTime; private static final int DEFAULT_SEND_WINDOW_BYTES = 8*1024; private static final int MINIMUM_WINDOW_BYTES = DEFAULT_SEND_WINDOW_BYTES; private static final int MAX_SEND_WINDOW_BYTES = 1024*1024; /** max number of msgs returned from allocateSend() */ private static final int MAX_ALLOCATE_SEND = 2; /** * Was 32 before 0.9.2, but since the streaming lib goes up to 128, * we would just drop our own msgs right away during slow start. * May need to adjust based on memory. */ private static final int MAX_SEND_MSGS_PENDING = 128; /** * IPv4 Min MTU * * 596 gives us 588 IP byes, 568 UDP bytes, and with an SSU data message, * 522 fragment bytes, which is enough to send a tunnel data message in 2 * packets. A tunnel data message sent over the wire is 1044 bytes, meaning * we need 522 fragment bytes to fit it in 2 packets - add 46 for SSU, 20 * for UDP, and 8 for IP, giving us 596. round up to mod 16, giving a total * of 608 * * Well, we really need to count the acks as well, especially * 1 + (4 * MAX_RESEND_ACKS_SMALL) which can take up a significant amount of space. * We reduce the max acks when using the small MTU but it may not be enough... * * Goal: VTBM msg fragments 2646 / (620 - 87) fits nicely. * * Assuming that we can enforce an MTU correctly, this % 16 should be 12, * as the IP/UDP header is 28 bytes and data max should be mulitple of 16 for padding efficiency, * and so PacketBuilder.buildPacket() works correctly. */ public static final int MIN_MTU = 620; /** * IPv6/UDP header is 48 bytes, so we want MTU % 16 == 0. */ public static final int MIN_IPV6_MTU = 1280; public static final int MAX_IPV6_MTU = 1488; private static final int DEFAULT_MTU = MIN_MTU; /** * IPv4 Max MTU * * based on measurements, 1350 fits nearly all reasonably small I2NP messages * (larger I2NP messages may be up to 1900B-4500B, which isn't going to fit * into a live network MTU anyway) * * TODO * VTBM is 2646, it would be nice to fit in two large * 2646 / 2 = 1323 * 1323 + 74 + 46 + 1 + (4 * 9) = 1480 * So why not make it 1492 (old ethernet is 1492, new is 1500) * Changed to 1492 in 0.8.9 * * BUT through 0.8.11, * size estimate was bad, actual packet was up to 48 bytes bigger * To be figured out. Curse the ACKs. * Assuming that we can enforce an MTU correctly, this % 16 should be 12, * as the IP/UDP header is 28 bytes and data max should be mulitple of 16 for padding efficiency, * and so PacketBuilder.buildPacket() works correctly. */ public static final int LARGE_MTU = 1484; /** * Max of IPv4 and IPv6 max MTUs * @since 0.9.28 */ public static final int MAX_MTU = Math.max(LARGE_MTU, MAX_IPV6_MTU); private static final int MIN_RTO = 100 + ACKSender.ACK_FREQUENCY; private static final int INIT_RTO = 3*1000; public static final int INIT_RTT = INIT_RTO / 2; private static final int MAX_RTO = 15*1000; private static final int CLOCK_SKEW_FUDGE = (ACKSender.ACK_FREQUENCY * 2) / 3; /** * The max number of acks we save to send as duplicates */ private static final int MAX_RESEND_ACKS = 64; /** * The max number of duplicate acks sent in each ack-only messge. * Doesn't really matter, we have plenty of room... * @since 0.7.13 */ private static final int MAX_RESEND_ACKS_LARGE = MAX_RESEND_ACKS / 3; /** for small MTU */ private static final int MAX_RESEND_ACKS_SMALL = MAX_RESEND_ACKS / 5; private static final long RESEND_ACK_TIMEOUT = 5*60*1000; public PeerState(RouterContext ctx, UDPTransport transport, byte[] remoteIP, int remotePort, Hash remotePeer, boolean isInbound) { _context = ctx; _log = ctx.logManager().getLog(PeerState.class); _transport = transport; long now = ctx.clock().now(); _keyEstablishedTime = now; _currentReceiveSecond = now - (now % 1000); _lastSendTime = now; _lastReceiveTime = now; _currentACKs = new ConcurrentHashSet<Long>(); _currentACKsResend = new LinkedBlockingQueue<ResendACK>(); _sendWindowBytes = DEFAULT_SEND_WINDOW_BYTES; _sendWindowBytesRemaining = DEFAULT_SEND_WINDOW_BYTES; _slowStartThreshold = MAX_SEND_WINDOW_BYTES/2; _lastSendRefill = now; _receivePeriodBegin = now; _lastCongestionOccurred = -1; _remotePort = remotePort; if (remoteIP.length == 4) { _mtu = DEFAULT_MTU; _mtuReceive = DEFAULT_MTU; _largeMTU = transport.getMTU(false); } else { _mtu = MIN_IPV6_MTU; _mtuReceive = MIN_IPV6_MTU; _largeMTU = transport.getMTU(true); } //_mtuLastChecked = -1; _lastACKSend = -1; _rto = INIT_RTO; _rtt = INIT_RTT; _rttDeviation = _rtt; _inboundMessages = new HashMap<Long, InboundMessageState>(8); _outboundMessages = new CachedIteratorArrayList<OutboundMessageState>(32); //_outboundQueue = new CoDelPriorityBlockingQueue(ctx, "UDP-PeerState", 32); _outboundQueue = new PriBlockingQueue<OutboundMessageState>(ctx, "UDP-PeerState", 32); // all createRateStat() moved to EstablishmentManager _remoteIP = remoteIP; _remotePeer = remotePeer; _isInbound = isInbound; _remoteHostId = new RemoteHostId(remoteIP, remotePort); } /** * Caller should sync; UDPTransport must remove and add to peersByRemoteHost map * @since 0.9.3 */ public void changePort(int newPort) { if (newPort != _remotePort) { _remoteHostId = new RemoteHostId(_remoteIP, newPort); _remotePort = newPort; } } /** * The peer are we talking to. This should be set as soon as this * state is created if we are initiating a connection, but if we are * receiving the connection this will be set only after the connection * is established. */ public Hash getRemotePeer() { return _remotePeer; } /** * The AES key used to verify packets, set only after the connection is * established. */ public SessionKey getCurrentMACKey() { return _currentMACKey; } /** * The AES key used to encrypt/decrypt packets, set only after the * connection is established. */ public SessionKey getCurrentCipherKey() { return _currentCipherKey; } /** * The pending AES key for verifying packets if we are rekeying the * connection, or null if we are not in the process of rekeying. * * @return null always, rekeying unimplemented */ public SessionKey getNextMACKey() { return _nextMACKey; } /** * The pending AES key for encrypting/decrypting packets if we are * rekeying the connection, or null if we are not in the process * of rekeying. * * @return null always, rekeying unimplemented */ public SessionKey getNextCipherKey() { return _nextCipherKey; } /** * The keying material used for the rekeying, or null if we are not in * the process of rekeying. * deprecated unused */ //public byte[] getNextKeyingMaterial() { return _nextKeyingMaterial; } /** true if we began the current rekeying, false otherwise */ //public boolean getRekeyBeganLocally() { return _rekeyBeganLocally; } /** when were the current cipher and MAC keys established/rekeyed? */ public long getKeyEstablishedTime() { return _keyEstablishedTime; } /** * How far off is the remote peer from our clock, in milliseconds? * A positive number means our clock is ahead of theirs. */ public long getClockSkew() { return _clockSkew ; } /** what is the current receive second, for congestion control? */ public long getCurrentReceiveSecond() { return _currentReceiveSecond; } /** when did we last send them a packet? */ public long getLastSendTime() { return _lastSendTime; } /** when did we last send them a message that was ACKed? */ public long getLastSendFullyTime() { return _lastSendFullyTime; } /** when did we last receive a packet from them? */ public long getLastReceiveTime() { return _lastReceiveTime; } /** how many seconds have we sent packets without any ACKs received? */ public int getConsecutiveFailedSends() { return _consecutiveFailedSends; } /** * have we received a packet with the ECN bit set in the current second? * @return false always * @deprecated unused, ECNs are never sent, always returns false */ @Deprecated public boolean getCurrentSecondECNReceived() { return _currentSecondECNReceived; } /** * have all of the packets received in the current second requested that * the previous second's ACKs be sent? */ //public boolean getRemoteWantsPreviousACKs() { return _remoteWantsPreviousACKs; } /** how many bytes should we send to the peer in a second */ public int getSendWindowBytes() { synchronized(_outboundMessages) { return _sendWindowBytes; } } /** how many bytes can we send to the peer in the current second */ public int getSendWindowBytesRemaining() { synchronized(_outboundMessages) { return _sendWindowBytesRemaining; } } /** what IP is the peer sending and receiving packets on? */ public byte[] getRemoteIP() { return _remoteIP; } /** * @return may be null if IP is invalid */ public InetAddress getRemoteIPAddress() { if (_remoteIPAddress == null) { try { _remoteIPAddress = InetAddress.getByAddress(_remoteIP); } catch (UnknownHostException uhe) { if (_log.shouldLog(Log.ERROR)) _log.error("Invalid IP? ", uhe); } } return _remoteIPAddress; } /** what port is the peer sending and receiving packets on? */ public int getRemotePort() { return _remotePort; } /** if we need to contact them, do we need to talk to an introducer? */ //public boolean getRemoteRequiresIntroduction() { return _remoteRequiresIntroduction; } /** * if we are serving as an introducer to them, this is the the tag that * they can publish that, when presented to us, will cause us to send * a relay introduction to the current peer * @return 0 (no relay) if unset previously */ public long getWeRelayToThemAs() { return _weRelayToThemAs; } /** * If they have offered to serve as an introducer to us, this is the tag * we can use to publish that fact. * @return 0 (no relay) if unset previously */ public long getTheyRelayToUsAs() { return _theyRelayToUsAs; } /** what is the largest packet we can send to the peer? */ public int getMTU() { return _mtu; } /** * Estimate how large the other side's MTU is. * This could be wrong. * It is used only for the HTML status. */ public int getReceiveMTU() { return _mtuReceive; } /** when did we last check the MTU? */ /**** public long getMTULastChecked() { return _mtuLastChecked; } public long getMTUIncreases() { return _mtuIncreases; } public long getMTUDecreases() { return _mtuDecreases; } ****/ /** * The AES key used to verify packets, set only after the connection is * established. */ public void setCurrentMACKey(SessionKey key) { _currentMACKey = key; } /** * The AES key used to encrypt/decrypt packets, set only after the * connection is established. */ public void setCurrentCipherKey(SessionKey key) { _currentCipherKey = key; } /** * The pending AES key for verifying packets if we are rekeying the * connection, or null if we are not in the process of rekeying. * @deprecated unused */ @Deprecated public void setNextMACKey(SessionKey key) { _nextMACKey = key; } /** * The pending AES key for encrypting/decrypting packets if we are * rekeying the connection, or null if we are not in the process * of rekeying. * @deprecated unused */ @Deprecated public void setNextCipherKey(SessionKey key) { _nextCipherKey = key; } /** * The keying material used for the rekeying, or null if we are not in * the process of rekeying. * @deprecated unused */ //public void setNextKeyingMaterial(byte data[]) { _nextKeyingMaterial = data; } /** * @param local true if we began the current rekeying, false otherwise * @deprecated unused */ //public void setRekeyBeganLocally(boolean local) { _rekeyBeganLocally = local; } /** * when were the current cipher and MAC keys established/rekeyed? * @deprecated unused */ @Deprecated public void setKeyEstablishedTime(long when) { _keyEstablishedTime = when; } /** * Update the moving-average clock skew based on the current difference. * The raw skew will be adjusted for RTT/2 here. * A positive number means our clock is ahead of theirs. * @param skew milliseconds, NOT adjusted for RTT. */ public void adjustClockSkew(long skew) { // the real one-way delay is much less than RTT / 2, due to ack delays, // so add a fudge factor long actualSkew = skew + CLOCK_SKEW_FUDGE - (_rtt / 2); //_log.error("Skew " + skew + " actualSkew " + actualSkew + " rtt " + _rtt + " pktsRcvd " + _packetsReceived); // First time... // This is important because we need accurate // skews right from the beginning, since the median is taken // and fed to the timestamper. Lots of connections only send a few packets. if (_packetsReceived <= 1) { synchronized(_clockSkewLock) { _clockSkew = actualSkew; } return; } double adj = 0.1 * actualSkew; synchronized(_clockSkewLock) { _clockSkew = (long) (0.9*_clockSkew + adj); } } /** what is the current receive second, for congestion control? */ public void setCurrentReceiveSecond(long sec) { _currentReceiveSecond = sec; } /** when did we last send them a packet? */ public void setLastSendTime(long when) { _lastSendTime = when; } /** when did we last receive a packet from them? */ public void setLastReceiveTime(long when) { _lastReceiveTime = when; } /** * Note ping sent. Does not update last send time. * @since 0.9.3 */ public void setLastPingTime(long when) { _lastPingTime = when; } /** * Latest of last sent, last ACK, last ping * @since 0.9.3 */ public long getLastSendOrPingTime() { return Math.max(Math.max(_lastSendTime, _lastACKSend), _lastPingTime); } /** return the smoothed send transfer rate */ public int getSendBps() { return _sendBps; } public synchronized int getReceiveBps() { return _receiveBps; } public int incrementConsecutiveFailedSends() { synchronized(_outboundMessages) { _concurrentMessagesActive--; if (_concurrentMessagesActive < 0) _concurrentMessagesActive = 0; //long now = _context.clock().now()/(10*1000); //if (_lastFailedSendPeriod >= now) { // // ignore... too fast //} else { // _lastFailedSendPeriod = now; _consecutiveFailedSends++; //} return _consecutiveFailedSends; } } public long getInactivityTime() { long now = _context.clock().now(); long lastActivity = Math.max(_lastReceiveTime, _lastSendFullyTime); return now - lastActivity; } /** how fast we are sending *ack* packets */ //public int getSendACKBps() { return _sendACKBps; } //public int getReceiveACKBps() { return _receiveACKBps; } /** * have all of the packets received in the current second requested that * the previous second's ACKs be sent? */ //public void remoteDoesNotWantPreviousACKs() { _remoteWantsPreviousACKs = false; } /** should we ignore the peer state's congestion window, and let anything through? */ private static final boolean IGNORE_CWIN = false; /** should we ignore the congestion window on the first push of every message? */ private static final boolean ALWAYS_ALLOW_FIRST_PUSH = false; /** * Decrement the remaining bytes in the current period's window, * returning true if the full size can be decremented, false if it * cannot. If it is not decremented, the window size remaining is * not adjusted at all. * * Caller should synch */ private boolean allocateSendingBytes(int size, int messagePushCount) { return allocateSendingBytes(size, false, messagePushCount); } //private boolean allocateSendingBytes(int size, boolean isForACK) { return allocateSendingBytes(size, isForACK, -1); } /** * Caller should synch */ private boolean allocateSendingBytes(int size, boolean isForACK, int messagePushCount) { long now = _context.clock().now(); long duration = now - _lastSendRefill; if (duration >= 1000) { _sendWindowBytesRemaining = _sendWindowBytes; _sendBytes += size; _sendBps = (int)(0.9f*_sendBps + 0.1f*(_sendBytes * (1000f/duration))); //if (isForACK) { // _sendACKBytes += size; // _sendACKBps = (int)(0.9f*(float)_sendACKBps + 0.1f*((float)_sendACKBytes * (1000f/(float)duration))); //} _sendBytes = 0; //_sendACKBytes = 0; _lastSendRefill = now; } //if (true) return true; if (IGNORE_CWIN || size <= _sendWindowBytesRemaining || (ALWAYS_ALLOW_FIRST_PUSH && messagePushCount == 0)) { if ( (messagePushCount == 0) && (_concurrentMessagesActive > _concurrentMessagesAllowed) ) { _consecutiveRejections++; _context.statManager().addRateData("udp.rejectConcurrentActive", _concurrentMessagesActive, _consecutiveRejections); return false; } else if (messagePushCount == 0) { _context.statManager().addRateData("udp.allowConcurrentActive", _concurrentMessagesActive, _concurrentMessagesAllowed); _concurrentMessagesActive++; if (_consecutiveRejections > 0) _context.statManager().addRateData("udp.rejectConcurrentSequence", _consecutiveRejections, _concurrentMessagesActive); _consecutiveRejections = 0; } _sendWindowBytesRemaining -= size; _sendBytes += size; _lastSendTime = now; //if (isForACK) // _sendACKBytes += size; return true; } else { return false; } } /** if we need to contact them, do we need to talk to an introducer? */ //public void setRemoteRequiresIntroduction(boolean required) { _remoteRequiresIntroduction = required; } /** * if we are serving as an introducer to them, this is the the tag that * they can publish that, when presented to us, will cause us to send * a relay introduction to the current peer * @param tag 1 to Integer.MAX_VALUE, or 0 if relaying disabled */ public void setWeRelayToThemAs(long tag) { _weRelayToThemAs = tag; } /** * If they have offered to serve as an introducer to us, this is the tag * we can use to publish that fact. * @param tag 1 to Integer.MAX_VALUE, or 0 if relaying disabled */ public void setTheyRelayToUsAs(long tag) { _theyRelayToUsAs = tag; } /** what is the largest packet we can send to the peer? */ /**** public void setMTU(int mtu) { _mtu = mtu; _mtuLastChecked = _context.clock().now(); } ****/ public int getSlowStartThreshold() { return _slowStartThreshold; } public int getConcurrentSends() { synchronized(_outboundMessages) { return _concurrentMessagesActive; } } public int getConcurrentSendWindow() { synchronized(_outboundMessages) { return _concurrentMessagesAllowed; } } public int getConsecutiveSendRejections() { synchronized(_outboundMessages) { return _consecutiveRejections; } } public boolean isInbound() { return _isInbound; } /** @since IPv6 */ public boolean isIPv6() { return _remoteIP.length == 16; } /** the last time we used them as an introducer, or 0 */ public long getIntroducerTime() { return _lastIntroducerTime; } /** set the last time we used them as an introducer to now */ public void setIntroducerTime() { _lastIntroducerTime = _context.clock().now(); } /** * We received the message specified completely. * @param bytes if less than or equal to zero, message is a duplicate. */ public void messageFullyReceived(Long messageId, int bytes) { messageFullyReceived(messageId, bytes, false); } /** * We received the message specified completely. * @param isForACK unused * @param bytes if less than or equal to zero, message is a duplicate. */ private synchronized void messageFullyReceived(Long messageId, int bytes, boolean isForACK) { if (bytes > 0) { _receiveBytes += bytes; //if (isForACK) // _receiveACKBytes += bytes; _messagesReceived++; } else { //if (true || _retransmissionPeriodStart + 1000 < _context.clock().now()) { _packetsReceivedDuplicate++; //} else { // _retransmissionPeriodStart = _context.clock().now(); // _packetsReceivedDuplicate = 1; //} } long now = _context.clock().now(); long duration = now - _receivePeriodBegin; if (duration >= 1000) { _receiveBps = (int)(0.9f*_receiveBps + 0.1f*(_receiveBytes * (1000f/duration))); //if (isForACK) // _receiveACKBps = (int)(0.9f*(float)_receiveACKBps + 0.1f*((float)_receiveACKBytes * (1000f/(float)duration))); //_receiveACKBytes = 0; _receiveBytes = 0; _receivePeriodBegin = now; _context.statManager().addRateData("udp.receiveBps", _receiveBps); } if (_wantACKSendSince <= 0) _wantACKSendSince = now; _currentACKs.add(messageId); } public void messagePartiallyReceived() { if (_wantACKSendSince <= 0) _wantACKSendSince = _context.clock().now(); } /** * Fetch the internal id (Long) to InboundMessageState for incomplete inbound messages. * Access to this map must be synchronized explicitly! */ public Map<Long, InboundMessageState> getInboundMessages() { return _inboundMessages; } /** * Expire partially received inbound messages, returning how many are still pending. * This should probably be fired periodically, in case a peer goes silent and we don't * try to send them any messages (and don't receive any messages from them either) * */ public int expireInboundMessages() { int rv = 0; synchronized (_inboundMessages) { for (Iterator<InboundMessageState> iter = _inboundMessages.values().iterator(); iter.hasNext(); ) { InboundMessageState state = iter.next(); if (state.isExpired() || _dead) { iter.remove(); // state.releaseResources() ?? } else { if (state.isComplete()) { _log.error("inbound message is complete, but wasn't handled inline? " + state + " with " + this); iter.remove(); // state.releaseResources() ?? } else { rv++; } } } } return rv; } /** * either they told us to back off, or we had to resend to get * the data through. * Caller should synch on this * @return true if window shrunk, but nobody uses the return value */ private boolean congestionOccurred() { long now = _context.clock().now(); if (_lastCongestionOccurred + _rto > now) return false; // only shrink once every few seconds _lastCongestionOccurred = now; int congestionAt = _sendWindowBytes; //if (true) // _sendWindowBytes -= 10000; //else _sendWindowBytes = _sendWindowBytes/2; //(_sendWindowBytes*2) / 3; if (_sendWindowBytes < MINIMUM_WINDOW_BYTES) _sendWindowBytes = MINIMUM_WINDOW_BYTES; //if (congestionAt/2 < _slowStartThreshold) _slowStartThreshold = congestionAt/2; return true; } /** * Grab a list of message ids (Long) that we want to send to the remote * peer, regardless of the packet size, but don't remove it from our * "want to send" list. If the message id is transmitted to the peer, * removeACKMessage(Long) should be called. * * The returned list contains acks not yet sent only. * The caller should NOT transmit all of them all the time, * even if there is room, * or the packets will have way too much overhead. * * @return a new list, do as you like with it */ public List<Long> getCurrentFullACKs() { // no such element exception seen here List<Long> rv = new ArrayList<Long>(_currentACKs); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Returning " + _currentACKs.size() + " current acks"); return rv; } /** * Grab a list of message ids (Long) that we want to send to the remote * peer, regardless of the packet size, but don't remove it from our * "want to send" list. * * The returned list contains * a random assortment of acks already sent. * The caller should NOT transmit all of them all the time, * even if there is room, * or the packets will have way too much overhead. * * @return a new list, do as you like with it * @since 0.8.12 was included in getCurrentFullACKs() */ public List<Long> getCurrentResendACKs() { int sz = _currentACKsResend.size(); List<Long> randomResends = new ArrayList<Long>(sz); if (sz > 0) { long cutoff = _context.clock().now() - RESEND_ACK_TIMEOUT; int i = 0; for (Iterator<ResendACK> iter = _currentACKsResend.iterator(); iter.hasNext(); ) { ResendACK rack = iter.next(); if (rack.time > cutoff && i++ < MAX_RESEND_ACKS) { randomResends.add(rack.id); } else { iter.remove(); if (_log.shouldLog(Log.INFO)) _log.info("Expired ack " + rack.id + " sent " + (cutoff + RESEND_ACK_TIMEOUT - rack.time) + " ago, now " + i + " resend acks"); } } if (i > 1) Collections.shuffle(randomResends, _context.random()); } return randomResends; } /** * The ack was sent. * Side effect - sets _lastACKSend */ public void removeACKMessage(Long messageId) { boolean removed = _currentACKs.remove(messageId); if (removed) { // only add if removed from current, as this may be called for // acks already in _currentACKsResend. _currentACKsResend.offer(new ResendACK(messageId, _context.clock().now())); // trim happens in getCurrentResendACKs above if (_log.shouldLog(Log.INFO)) _log.info("Sent ack " + messageId + " now " + _currentACKs.size() + " current and " + _currentACKsResend.size() + " resend acks"); } // should we only do this if removed? _lastACKSend = _context.clock().now(); } /** * grab a list of ACKBitfield instances, some of which may fully * ACK a message while others may only partially ACK a message. * the values returned are limited in size so that they will fit within * the peer's current MTU as an ACK - as such, not all messages may be * ACKed with this call. Be sure to check getWantedACKSendSince() which * will be unchanged if there are ACKs remaining. * * @return non-null, possibly empty * @deprecated unused */ @Deprecated public List<ACKBitfield> retrieveACKBitfields() { return retrieveACKBitfields(true); } /** * See above. Only called by ACKSender with alwaysIncludeRetransmissions = false. * So this is only for ACK-only packets, so all the size limiting is useless. * FIXME. * Side effect - sets _lastACKSend if rv is non-empty * * @return non-null, possibly empty */ public List<ACKBitfield> retrieveACKBitfields(boolean alwaysIncludeRetransmissions) { int bytesRemaining = countMaxACKData(); // Limit the overhead of all the resent acks when using small MTU // 64 bytes in a 608-byte packet is too much... // Send a random subset of all the queued resend acks. int resendSize = _currentACKsResend.size(); int maxResendAcks; if (bytesRemaining < MIN_MTU) maxResendAcks = MAX_RESEND_ACKS_SMALL; else maxResendAcks = MAX_RESEND_ACKS_LARGE; List<ACKBitfield> rv = new ArrayList<ACKBitfield>(maxResendAcks); // save to add to currentACKsResend later so we don't include twice List<Long> currentACKsRemoved = new ArrayList<Long>(_currentACKs.size()); // As explained above, we include the acks in any order // since we are unlikely to get backed up - // just take them using the Set iterator. Iterator<Long> iter = _currentACKs.iterator(); while (bytesRemaining >= 4 && iter.hasNext()) { Long val = iter.next(); iter.remove(); long id = val.longValue(); rv.add(new FullACKBitfield(id)); currentACKsRemoved.add(val); bytesRemaining -= 4; } if (_currentACKs.isEmpty()) _wantACKSendSince = -1; if (alwaysIncludeRetransmissions || !rv.isEmpty()) { List<Long> randomResends = getCurrentResendACKs(); // now repeat by putting in some old ACKs // randomly selected from the Resend queue. // Maybe we should only resend each one a certain number of times... int oldIndex = Math.min(resendSize, maxResendAcks); iter = randomResends.iterator(); while (bytesRemaining >= 4 && oldIndex-- > 0 && iter.hasNext()) { Long cur = iter.next(); long c = cur.longValue(); FullACKBitfield bf = new FullACKBitfield(c); // try to avoid duplicates ?? // ACKsResend is not checked for dups at add time //if (rv.contains(bf)) { // iter.remove(); //} else { rv.add(bf); bytesRemaining -= 4; //} } if (!currentACKsRemoved.isEmpty()) { long now = _context.clock().now(); for (Long val : currentACKsRemoved) { _currentACKsResend.offer(new ResendACK(val, now)); } // trim happens in getCurrentResendACKs above } } int partialIncluded = 0; if (bytesRemaining > 4) { // ok, there's room to *try* to fit in some partial ACKs, so // we should try to find some packets to partially ACK // (preferably the ones which have the most received fragments) List<ACKBitfield> partial = new ArrayList<ACKBitfield>(); fetchPartialACKs(partial); // we may not be able to use them all, but lets try... for (int i = 0; (bytesRemaining > 4) && (i < partial.size()); i++) { ACKBitfield bitfield = partial.get(i); int bytes = (bitfield.fragmentCount() / 7) + 1; if (bytesRemaining > bytes + 4) { // msgId + bitfields rv.add(bitfield); bytesRemaining -= bytes + 4; partialIncluded++; } else { // continue on to another partial, in case there's a // smaller one that will fit } } } if (!rv.isEmpty()) _lastACKSend = _context.clock().now(); //if (rv == null) // rv = Collections.EMPTY_LIST; if (partialIncluded > 0) _context.statManager().addRateData("udp.sendACKPartial", partialIncluded, rv.size() - partialIncluded); return rv; } /** * @param rv out parameter, populated with true partial ACKBitfields. * no full bitfields are included. */ void fetchPartialACKs(List<ACKBitfield> rv) { List<InboundMessageState> states = null; int curState = 0; synchronized (_inboundMessages) { int numMessages = _inboundMessages.size(); if (numMessages <= 0) return; // todo: make this a list instead of a map, so we can iterate faster w/out the memory overhead? for (Iterator<InboundMessageState> iter = _inboundMessages.values().iterator(); iter.hasNext(); ) { InboundMessageState state = iter.next(); if (state.isExpired()) { //if (_context instanceof RouterContext) // ((RouterContext)_context).messageHistory().droppedInboundMessage(state.getMessageId(), state.getFrom(), "expired partially received: " + state.toString()); iter.remove(); // state.releaseResources() ?? } else { if (!state.isComplete()) { if (states == null) states = new ArrayList<InboundMessageState>(numMessages); states.add(state); } } } } if (states != null) { for (InboundMessageState ims : states) { ACKBitfield abf = ims.createACKBitfield(); if (!abf.receivedComplete()) rv.add(abf); } } } /** * A dummy "partial" ack which represents a full ACK of a message */ private static class FullACKBitfield implements ACKBitfield { private final long _msgId; public FullACKBitfield(long id) { _msgId = id; } public int fragmentCount() { return 1; } public int ackCount() { return 1; } public int highestReceived() { return 0; } public long getMessageId() { return _msgId; } public boolean received(int fragmentNum) { return true; } public boolean receivedComplete() { return true; } @Override public int hashCode() { return (int) _msgId; } @Override public boolean equals(Object o) { if (!(o instanceof FullACKBitfield)) return false; return _msgId == ((ACKBitfield)o).getMessageId(); } @Override public String toString() { return "Full ACK " + _msgId; } } /** * We sent a message which was ACKed containing the given # of bytes. * Caller should synch on this */ private void locked_messageACKed(int bytesACKed, long lifetime, int numSends) { _concurrentMessagesActive--; if (_concurrentMessagesActive < 0) _concurrentMessagesActive = 0; _consecutiveFailedSends = 0; // _lastFailedSendPeriod = -1; if (numSends < 2) { if (_context.random().nextInt(_concurrentMessagesAllowed) <= 0) _concurrentMessagesAllowed++; if (_sendWindowBytes <= _slowStartThreshold) { _sendWindowBytes += bytesACKed; } else { //if (false) { // _sendWindowBytes += 16; // why 16? //} else { float prob = ((float)bytesACKed) / ((float)(_sendWindowBytes<<1)); float v = _context.random().nextFloat(); if (v < 0) v = 0-v; if (v <= prob) _sendWindowBytes += bytesACKed; //512; // bytesACKed; //} } } else { int allow = _concurrentMessagesAllowed - 1; if (allow < MIN_CONCURRENT_MSGS) allow = MIN_CONCURRENT_MSGS; _concurrentMessagesAllowed = allow; } if (_sendWindowBytes > MAX_SEND_WINDOW_BYTES) _sendWindowBytes = MAX_SEND_WINDOW_BYTES; _lastReceiveTime = _context.clock().now(); _lastSendFullyTime = _lastReceiveTime; //if (true) { if (_sendWindowBytesRemaining + bytesACKed <= _sendWindowBytes) _sendWindowBytesRemaining += bytesACKed; else _sendWindowBytesRemaining = _sendWindowBytes; //} if (numSends < 2) { // caller synchs //synchronized (this) { recalculateTimeouts(lifetime); adjustMTU(); //} } } /** * We sent a message which was ACKed containing the given # of bytes. */ private void messageACKed(int bytesACKed, long lifetime, int numSends) { synchronized(this) { locked_messageACKed(bytesACKed, lifetime, numSends); } if (numSends >= 2 && _log.shouldLog(Log.INFO)) _log.info("acked after numSends=" + numSends + " w/ lifetime=" + lifetime + " and size=" + bytesACKed); _context.statManager().addRateData("udp.sendBps", _sendBps); } /** This is the value specified in RFC 2988 */ private static final float RTT_DAMPENING = 0.125f; /** * Adjust the tcp-esque timeouts. * Caller should synch on this */ private void recalculateTimeouts(long lifetime) { // the rttDev calculation matches that recommended in RFC 2988 (beta = 1/4) _rttDeviation = _rttDeviation + (int)(0.25d*(Math.abs(lifetime-_rtt)-_rttDeviation)); float scale = RTT_DAMPENING; // the faster we are going, the slower we want to reduce the rtt //if (_sendBps > 0) // scale = lifetime / ((float)lifetime + (float)_sendBps); //if (scale < 0.001f) scale = 0.001f; _rtt = (int)(_rtt*(1.0f-scale) + (scale)*lifetime); // K = 4 _rto = Math.min(MAX_RTO, Math.max(minRTO(), _rtt + (_rttDeviation<<2))); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Recalculating timeouts w/ lifetime=" + lifetime + ": rtt=" + _rtt // + " rttDev=" + _rttDeviation + " rto=" + _rto); } /** * Caller should synch on this */ private void adjustMTU() { double retransPct = 0; if (_packetsTransmitted > 10) { retransPct = (double)_packetsRetransmitted/(double)_packetsTransmitted; boolean wantLarge = retransPct < .30d; // heuristic to allow fairly lossy links to use large MTUs if (wantLarge && _mtu != _largeMTU) { if (_context.random().nextLong(_mtuDecreases) <= 0) { _mtu = _largeMTU; _mtuIncreases++; _context.statManager().addRateData("udp.mtuIncrease", _mtuIncreases); } } else if (!wantLarge && _mtu == _largeMTU) { _mtu = _remoteIP.length == 4 ? MIN_MTU : MIN_IPV6_MTU; _mtuDecreases++; _context.statManager().addRateData("udp.mtuDecrease", _mtuDecreases); } } else { _mtu = _remoteIP.length == 4 ? DEFAULT_MTU : MIN_IPV6_MTU; } } /** * @since 0.9.2 */ public synchronized void setHisMTU(int mtu) { if (mtu <= MIN_MTU || mtu >= _largeMTU || (_remoteIP.length == 16 && mtu <= MIN_IPV6_MTU)) return; _largeMTU = mtu; if (mtu < _mtu) _mtu = mtu; } /** we are resending a packet, so lets jack up the rto */ public synchronized void messageRetransmitted(int packets) { _context.statManager().addRateData("udp.congestionOccurred", _sendWindowBytes); _context.statManager().addRateData("udp.congestedRTO", _rto, _rttDeviation); _packetsRetransmitted += packets; congestionOccurred(); adjustMTU(); } public synchronized void packetsTransmitted(int packets) { _packetsTransmitted += packets; } /** how long does it usually take to get a message ACKed? */ public synchronized int getRTT() { return _rtt; } /** how soon should we retransmit an unacked packet? */ public synchronized int getRTO() { return _rto; } /** how skewed are the measured RTTs? */ public synchronized int getRTTDeviation() { return _rttDeviation; } /** * I2NP messages sent. * Does not include duplicates. * As of 0.9.24, incremented when bandwidth is allocated just before sending, not when acked. */ public int getMessagesSent() { synchronized (_outboundMessages) { return _messagesSent; } } /** * I2NP messages received. * As of 0.9.24, does not include duplicates. */ public synchronized int getMessagesReceived() { return _messagesReceived; } public synchronized int getPacketsTransmitted() { return _packetsTransmitted; } public synchronized int getPacketsRetransmitted() { return _packetsRetransmitted; } //public long getPacketsPeriodTransmitted() { return _packetsPeriodTransmitted; } //public int getPacketsPeriodRetransmitted() { return _packetsPeriodRetransmitted; } /** avg number of packets retransmitted for every 100 packets */ //public long getPacketRetransmissionRate() { return _packetRetransmissionRate; } public synchronized int getPacketsReceived() { return _packetsReceived; } public synchronized int getPacketsReceivedDuplicate() { return _packetsReceivedDuplicate; } private static final int MTU_RCV_DISPLAY_THRESHOLD = 20; /** 60 */ private static final int OVERHEAD_SIZE = PacketBuilder.IP_HEADER_SIZE + PacketBuilder.UDP_HEADER_SIZE + UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; /** 80 */ private static final int IPV6_OVERHEAD_SIZE = PacketBuilder.IPV6_HEADER_SIZE + PacketBuilder.UDP_HEADER_SIZE + UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE; /** * @param size not including IP header, UDP header, MAC or IV */ public synchronized void packetReceived(int size) { _packetsReceived++; int minMTU; if (_remoteIP.length == 4) { size += OVERHEAD_SIZE; minMTU = MIN_MTU; } else { size += IPV6_OVERHEAD_SIZE; minMTU = MIN_IPV6_MTU; } if (size <= minMTU) { _consecutiveSmall++; if (_consecutiveSmall >= MTU_RCV_DISPLAY_THRESHOLD) _mtuReceive = minMTU; } else { _consecutiveSmall = 0; if (size > _mtuReceive) _mtuReceive = size; } } /** * We received a backoff request, so cut our send window. * NOTE: ECN sending is unimplemented, this is never called. */ public void ECNReceived() { synchronized(this) { congestionOccurred(); } _context.statManager().addRateData("udp.congestionOccurred", _sendWindowBytes); _currentSecondECNReceived = true; _lastReceiveTime = _context.clock().now(); } public void dataReceived() { _lastReceiveTime = _context.clock().now(); } /** when did we last send an ACK to the peer? */ public long getLastACKSend() { return _lastACKSend; } /** @deprecated unused */ @Deprecated public void setLastACKSend(long when) { _lastACKSend = when; } public long getWantedACKSendSince() { return _wantACKSendSince; } /** * Are we out of room to send all the current unsent acks in a single packet? * This is a huge threshold (134 for small MTU and 255 for large MTU) * that is rarely if ever exceeded in practice. * So just use a fixed threshold of half the resend acks, so that if the * packet is lost the acks have a decent chance of getting retransmitted. * Used only by ACKSender. */ public boolean unsentACKThresholdReached() { //int threshold = countMaxACKData() / 4; //return _currentACKs.size() >= threshold; return _currentACKs.size() >= MAX_RESEND_ACKS / 2; } /** * @return how many bytes available for acks in an ack-only packet, == MTU - 83 * Max of 1020 */ private int countMaxACKData() { return Math.min(PacketBuilder.ABSOLUTE_MAX_ACKS * 4, _mtu - (_remoteIP.length == 4 ? PacketBuilder.IP_HEADER_SIZE : PacketBuilder.IPV6_HEADER_SIZE) - PacketBuilder.UDP_HEADER_SIZE - UDPPacket.IV_SIZE - UDPPacket.MAC_SIZE - 1 // type flag - 4 // timestamp - 1 // data flag - 1 // # ACKs - 16); // padding safety } private int minRTO() { //if (_packetRetransmissionRate < 10) return MIN_RTO; //else if (_packetRetransmissionRate < 50) // return 2*MIN_RTO; //else // return MAX_RTO; } /** @return non-null */ RemoteHostId getRemoteHostId() { return _remoteHostId; } /** * TODO should this use a queue, separate from the list of msgs pending an ack? * TODO bring back tail drop? * TODO priority queue? (we don't implement priorities in SSU now) * TODO backlog / pushback / block instead of dropping? Can't really block here. * TODO SSU does not support isBacklogged() now */ public void add(OutboundMessageState state) { if (_dead) { _transport.failed(state, false); return; } if (state.getPeer() != this) { if (_log.shouldLog(Log.WARN)) _log.warn("Not for me!", new Exception("I did it")); _transport.failed(state, false); return; } if (_log.shouldLog(Log.DEBUG)) _log.debug("Adding to " + _remotePeer + ": " + state.getMessageId()); int rv = 0; // will never fail for CDPQ boolean fail = !_outboundQueue.offer(state); /**** synchronized (_outboundMessages) { rv = _outboundMessages.size() + 1; if (rv > MAX_SEND_MSGS_PENDING) { // too many queued messages to one peer? nuh uh. fail = true; rv--; ****/ /******* proactive tail drop disabled by jr 2006-04-19 so all this is pointless } else if (_retransmitter != null) { long lifetime = _retransmitter.getLifetime(); long totalLifetime = lifetime; for (int i = 1; i < msgs.size(); i++) { // skip the first, as thats the retransmitter OutboundMessageState cur = msgs.get(i); totalLifetime += cur.getLifetime(); } long remaining = -1; OutNetMessage omsg = state.getMessage(); if (omsg != null) remaining = omsg.getExpiration() - _context.clock().now(); else remaining = 10*1000 - state.getLifetime(); if (remaining <= 0) remaining = 1; // total lifetime will exceed it anyway, guaranteeing failure float pDrop = totalLifetime / (float)remaining; pDrop = pDrop * pDrop * pDrop; if (false && (pDrop >= _context.random().nextFloat())) { if (_log.shouldLog(Log.WARN)) _log.warn("Proactively tail dropping for " + _remotePeer.toBase64() + " (messages=" + msgs.size() + " headLifetime=" + lifetime + " totalLifetime=" + totalLifetime + " curLifetime=" + state.getLifetime() + " remaining=" + remaining + " pDrop=" + pDrop + ")"); _context.statManager().addRateData("udp.queueDropSize", msgs.size(), totalLifetime); fail = true; } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Probabalistically allowing for " + _remotePeer.toBase64() + " (messages=" + msgs.size() + " headLifetime=" + lifetime + " totalLifetime=" + totalLifetime + " curLifetime=" + state.getLifetime() + " remaining=" + remaining + " pDrop=" + pDrop + ")"); _context.statManager().addRateData("udp.queueAllowTotalLifetime", totalLifetime, lifetime); msgs.add(state); } *******/ /**** } else { _outboundMessages.add(state); } } ****/ if (fail) { if (_log.shouldLog(Log.WARN)) _log.warn("Dropping msg, OB queue full for " + toString()); _transport.failed(state, false); } } /** drop all outbound messages */ public void dropOutbound() { //if (_dead) return; _dead = true; //_outboundMessages = null; List<OutboundMessageState> tempList; synchronized (_outboundMessages) { _retransmitter = null; tempList = new ArrayList<OutboundMessageState>(_outboundMessages); _outboundMessages.clear(); } //_outboundQueue.drainAllTo(tempList); _outboundQueue.drainTo(tempList); for (OutboundMessageState oms : tempList) { _transport.failed(oms, false); } // so the ACKSender will drop this peer from its queue _wantACKSendSince = -1; } /** * @return number of active outbound messages remaining (unsynchronized) */ public int getOutboundMessageCount() { if (_dead) return 0; return _outboundMessages.size() + _outboundQueue.size(); } /** * Sets to true. * @since 0.9.24 */ public void setMayDisconnect() { _mayDisconnect = true; } /** * @since 0.9.24 */ public boolean getMayDisconnect() { return _mayDisconnect; } /** * Expire / complete any outbound messages * High usage - * OutboundMessageFragments.getNextVolley() calls this 1st. * TODO combine finishMessages(), allocateSend(), and getNextDelay() so we don't iterate 3 times. * * @return number of active outbound messages remaining */ public int finishMessages() { // short circuit, unsynchronized if (_outboundMessages.isEmpty()) return _outboundQueue.size(); if (_dead) { dropOutbound(); return 0; } int rv = 0; List<OutboundMessageState> succeeded = null; List<OutboundMessageState> failed = null; synchronized (_outboundMessages) { for (Iterator<OutboundMessageState> iter = _outboundMessages.iterator(); iter.hasNext(); ) { OutboundMessageState state = iter.next(); if (state.isComplete()) { iter.remove(); if (_retransmitter == state) _retransmitter = null; if (succeeded == null) succeeded = new ArrayList<OutboundMessageState>(4); succeeded.add(state); } else if (state.isExpired()) { iter.remove(); if (_retransmitter == state) _retransmitter = null; _context.statManager().addRateData("udp.sendFailed", state.getPushCount()); if (failed == null) failed = new ArrayList<OutboundMessageState>(4); failed.add(state); } else if (state.getPushCount() > OutboundMessageFragments.MAX_VOLLEYS) { iter.remove(); if (state == _retransmitter) _retransmitter = null; _context.statManager().addRateData("udp.sendAggressiveFailed", state.getPushCount()); if (failed == null) failed = new ArrayList<OutboundMessageState>(4); failed.add(state); } // end (pushCount > maxVolleys) } // end iterating over outbound messages rv = _outboundMessages.size(); } for (int i = 0; succeeded != null && i < succeeded.size(); i++) { OutboundMessageState state = succeeded.get(i); _transport.succeeded(state); OutNetMessage msg = state.getMessage(); if (msg != null) msg.timestamp("sending complete"); } for (int i = 0; failed != null && i < failed.size(); i++) { OutboundMessageState state = failed.get(i); OutNetMessage msg = state.getMessage(); if (msg != null) { msg.timestamp("expired in the active pool"); _transport.failed(state); } else { // it can not have an OutNetMessage if the source is the // final after establishment message if (_log.shouldLog(Log.WARN)) _log.warn("Unable to send a direct message: " + state); } } return rv + _outboundQueue.size(); } /** * Pick one or more messages we want to send and allocate them out of our window * High usage - * OutboundMessageFragments.getNextVolley() calls this 2nd, if finishMessages() returned > 0. * TODO combine finishMessages(), allocateSend(), and getNextDelay() so we don't iterate 3 times. * * @return allocated messages to send (never empty), or null if no messages or no resources */ public List<OutboundMessageState> allocateSend() { if (_dead) return null; List<OutboundMessageState> rv = null; synchronized (_outboundMessages) { for (OutboundMessageState state : _outboundMessages) { // We have 3 return values, because if allocateSendingBytes() returns false, // then we can stop iterating. ShouldSend should = locked_shouldSend(state); if (should == ShouldSend.YES) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Allocate sending (OLD) to " + _remotePeer + ": " + state.getMessageId()); /* while (iter.hasNext()) { OutboundMessageState later = (OutboundMessageState)iter.next(); OutNetMessage msg = later.getMessage(); if (msg != null) msg.timestamp("not reached for allocation " + msgs.size() + " other peers"); } */ if (rv == null) rv = new ArrayList<OutboundMessageState>(MAX_ALLOCATE_SEND); rv.add(state); if (rv.size() >= MAX_ALLOCATE_SEND) return rv; } else if (should == ShouldSend.NO_BW) { // no more bandwidth available // we don't bother looking for a smaller msg that would fit. // By not looking further, we keep strict sending order, and that allows // some efficiency in acked() below. if (rv == null && _log.shouldLog(Log.DEBUG)) _log.debug("Nothing to send (BW) to " + _remotePeer + ", with " + _outboundMessages.size() + " / " + _outboundQueue.size() + " remaining"); return rv; } /* else { OutNetMessage msg = state.getMessage(); if (msg != null) msg.timestamp("passed over for allocation with " + msgs.size() + " peers"); } */ } // Peek at head of _outboundQueue and see if we can send it. // If so, pull it off, put it in _outbundMessages, test // again for bandwidth if necessary, and return it. OutboundMessageState state; while ((state = _outboundQueue.peek()) != null && ShouldSend.YES == locked_shouldSend(state)) { // we could get a different state, or null, when we poll, // due to AQM drops, so we test again if necessary OutboundMessageState dequeuedState = _outboundQueue.poll(); if (dequeuedState != null) { _outboundMessages.add(dequeuedState); if (dequeuedState == state || ShouldSend.YES == locked_shouldSend(state)) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Allocate sending (NEW) to " + _remotePeer + ": " + dequeuedState.getMessageId()); if (rv == null) rv = new ArrayList<OutboundMessageState>(MAX_ALLOCATE_SEND); rv.add(state); if (rv.size() >= MAX_ALLOCATE_SEND) return rv; } } } } if ( rv == null && _log.shouldLog(Log.DEBUG)) _log.debug("Nothing to send to " + _remotePeer + ", with " + _outboundMessages.size() + " / " + _outboundQueue.size() + " remaining"); return rv; } /** * High usage - * OutboundMessageFragments.getNextVolley() calls this 3rd, if allocateSend() returned null. * TODO combine finishMessages(), allocateSend(), and getNextDelay() so we don't iterate 3 times. * * @return how long to wait before sending, or Integer.MAX_VALUE if we have nothing to send. * If ready now, will return 0 or a negative value. */ public int getNextDelay() { int rv = Integer.MAX_VALUE; if (_dead) return rv; long now = _context.clock().now(); synchronized (_outboundMessages) { if (_retransmitter != null) { rv = (int)(_retransmitter.getNextSendTime() - now); return rv; } for (OutboundMessageState state : _outboundMessages) { int delay = (int)(state.getNextSendTime() - now); // short circuit once we hit something ready to go if (delay <= 0) return delay; if (delay < rv) rv = delay; } } // failsafe... is this OK? if (rv > 100 && !_outboundQueue.isEmpty()) rv = 100; return rv; } /** * @since 0.9.3 */ public boolean isBacklogged() { return _dead || _outboundQueue.isBacklogged(); } /** * If set to true, we should throttle retransmissions of all but the first message in * flight to a peer. If set to false, we will only throttle the initial flight of a * message to a peer while a retransmission is going on. */ private static final boolean THROTTLE_RESENDS = true; /** * if true, throttle the initial volley of a message if there is a resend in progress. * if false, always send the first volley, regardless of retransmissions (but keeping in * mind bw/cwin throttle, etc) * */ private static final boolean THROTTLE_INITIAL_SEND = true; /** * Always leave room for this many explicit acks. * Only for data packets. Does not affect ack-only packets. * This directly affects data packet overhead, adjust with care. */ private static final int MIN_EXPLICIT_ACKS = 3; /** this is room for three explicit acks or two partial acks or one of each = 13 */ private static final int MIN_ACK_SIZE = 1 + (4 * MIN_EXPLICIT_ACKS); /** * how much payload data can we shove in there? * @return MTU - 87, i.e. 533 or 1397 (IPv4), MTU - 107 (IPv6) */ public int fragmentSize() { // 46 + 20 + 8 + 13 = 74 + 13 = 87 (IPv4) // 46 + 40 + 8 + 13 = 94 + 13 = 107 (IPv6) return _mtu - (_remoteIP.length == 4 ? PacketBuilder.MIN_DATA_PACKET_OVERHEAD : PacketBuilder.MIN_IPV6_DATA_PACKET_OVERHEAD) - MIN_ACK_SIZE; } private enum ShouldSend { YES, NO, NO_BW }; /** * Have 3 return values, because if allocateSendingBytes() returns false, * then allocateSend() can stop iterating * * Caller should synch */ private ShouldSend locked_shouldSend(OutboundMessageState state) { long now = _context.clock().now(); if (state.getNextSendTime() <= now) { OutboundMessageState retrans = _retransmitter; if ( (retrans != null) && ( (retrans.isExpired() || retrans.isComplete()) ) ) { _retransmitter = null; retrans = null; } if ( (retrans != null) && (retrans != state) ) { // choke it, since there's already another message retransmitting to this // peer. _context.statManager().addRateData("udp.blockedRetransmissions", _packetsRetransmitted); int max = state.getMaxSends(); if ( (max <= 0) && (!THROTTLE_INITIAL_SEND) ) { //if (state.getMessage() != null) // state.getMessage().timestamp("another message is retransmitting, but we want to send our first volley..."); } else if ( (max <= 0) || (THROTTLE_RESENDS) ) { //if (state.getMessage() != null) // state.getMessage().timestamp("choked, with another message retransmitting"); return ShouldSend.NO; } else { //if (state.getMessage() != null) // state.getMessage().timestamp("another message is retransmitting, but since we've already begun sending..."); } } int size = state.getUnackedSize(); if (allocateSendingBytes(size, state.getPushCount())) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Allocation of " + size + " allowed with " + getSendWindowBytesRemaining() + "/" + getSendWindowBytes() + " remaining" + " for message " + state.getMessageId() + ": " + state); if (state.getPushCount() > 0) _retransmitter = state; if (state.push()) _messagesSent++; int rto = getRTO(); state.setNextSendTime(now + rto); //if (peer.getSendWindowBytesRemaining() > 0) // _throttle.unchoke(peer.getRemotePeer()); return ShouldSend.YES; } else { _context.statManager().addRateData("udp.sendRejected", state.getPushCount()); //if (state.getMessage() != null) // state.getMessage().timestamp("send rejected, available=" + getSendWindowBytesRemaining()); if (_log.shouldLog(Log.INFO)) _log.info("Allocation of " + size + " rejected w/ wsize=" + getSendWindowBytes() + " available=" + getSendWindowBytesRemaining() + " for message " + state.getMessageId() + ": " + state); state.setNextSendTime(now + (ACKSender.ACK_FREQUENCY / 2) + _context.random().nextInt(ACKSender.ACK_FREQUENCY)); //(now + 1024) & ~SECOND_MASK); if (_log.shouldLog(Log.INFO)) _log.info("Retransmit after choke for next send time in " + (state.getNextSendTime()-now) + "ms"); //_throttle.choke(peer.getRemotePeer()); //if (state.getMessage() != null) // state.getMessage().timestamp("choked, not enough available, wsize=" // + getSendWindowBytes() + " available=" // + getSendWindowBytesRemaining()); return ShouldSend.NO_BW; } } // nextTime <= now return ShouldSend.NO; } /** * A full ACK was received. * TODO if messages awaiting ack were a HashMap<Long, OutboundMessageState> this would be faster. * * @return true if the message was acked for the first time */ public boolean acked(long messageId) { if (_dead) return false; OutboundMessageState state = null; synchronized (_outboundMessages) { for (Iterator<OutboundMessageState> iter = _outboundMessages.iterator(); iter.hasNext(); ) { state = iter.next(); if (state.getMessageId() == messageId) { iter.remove(); break; } else if (state.getPushCount() <= 0) { // _outboundMessages is ordered, so once we get to a msg that // hasn't been transmitted yet, we can stop state = null; break; } else { state = null; } } if ( (state != null) && (state == _retransmitter) ) _retransmitter = null; } if (state != null) { int numSends = state.getMaxSends(); //if (state.getMessage() != null) { // state.getMessage().timestamp("acked after " + numSends // + " lastReceived: " // + (_context.clock().now() - getLastReceiveTime()) // + " lastSentFully: " // + (_context.clock().now() - getLastSendFullyTime())); //} if (_log.shouldLog(Log.INFO)) _log.info("Received ack of " + messageId + " by " + _remotePeer + " after " + state.getLifetime() + " and " + numSends + " sends"); _context.statManager().addRateData("udp.sendConfirmTime", state.getLifetime()); if (state.getFragmentCount() > 1) _context.statManager().addRateData("udp.sendConfirmFragments", state.getFragmentCount()); _context.statManager().addRateData("udp.sendConfirmVolley", numSends); _transport.succeeded(state); int numFragments = state.getFragmentCount(); // this adjusts the rtt/rto/window/etc messageACKed(state.getMessageSize(), state.getLifetime(), numSends); //if (getSendWindowBytesRemaining() > 0) // _throttle.unchoke(peer.getRemotePeer()); } else { // dupack, likely //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Received an ACK for a message not pending: " + messageId); } return state != null; } /** * A partial ACK was received. This is much less common than full ACKs. * * @return true if the message was completely acked for the first time */ public boolean acked(ACKBitfield bitfield) { if (_dead) return false; if (bitfield.receivedComplete()) { return acked(bitfield.getMessageId()); } OutboundMessageState state = null; boolean isComplete = false; synchronized (_outboundMessages) { for (Iterator<OutboundMessageState> iter = _outboundMessages.iterator(); iter.hasNext(); ) { state = iter.next(); if (state.getMessageId() == bitfield.getMessageId()) { boolean complete = state.acked(bitfield); if (complete) { isComplete = true; iter.remove(); if (state == _retransmitter) _retransmitter = null; } break; } else if (state.getPushCount() <= 0) { // _outboundMessages is ordered, so once we get to a msg that // hasn't been transmitted yet, we can stop state = null; break; } else { state = null; } } } if (state != null) { int numSends = state.getMaxSends(); int numACKed = bitfield.ackCount(); _context.statManager().addRateData("udp.partialACKReceived", numACKed); if (_log.shouldLog(Log.INFO)) _log.info("Received partial ack of " + state.getMessageId() + " by " + _remotePeer + " after " + state.getLifetime() + " and " + numSends + " sends: " + bitfield + ": completely removed? " + isComplete + ": " + state); if (isComplete) { _context.statManager().addRateData("udp.sendConfirmTime", state.getLifetime()); if (state.getFragmentCount() > 1) _context.statManager().addRateData("udp.sendConfirmFragments", state.getFragmentCount()); _context.statManager().addRateData("udp.sendConfirmVolley", numSends); //if (state.getMessage() != null) // state.getMessage().timestamp("partial ack to complete after " + numSends); _transport.succeeded(state); // this adjusts the rtt/rto/window/etc messageACKed(state.getMessageSize(), state.getLifetime(), numSends); //if (state.getPeer().getSendWindowBytesRemaining() > 0) // _throttle.unchoke(state.getPeer().getRemotePeer()); } else { //if (state.getMessage() != null) // state.getMessage().timestamp("partial ack after " + numSends + ": " + bitfield.toString()); } return isComplete; } else { // dupack if (_log.shouldLog(Log.DEBUG)) _log.debug("Received an ACK for a message not pending: " + bitfield); return false; } } /** * Transfer the basic activity/state from the old peer to the current peer * * @param oldPeer non-null */ public void loadFrom(PeerState oldPeer) { _rto = oldPeer._rto; _rtt = oldPeer._rtt; _rttDeviation = oldPeer._rttDeviation; _slowStartThreshold = oldPeer._slowStartThreshold; _sendWindowBytes = oldPeer._sendWindowBytes; oldPeer._dead = true; List<Long> tmp = new ArrayList<Long>(); // AIOOBE from concurrent access //tmp.addAll(oldPeer._currentACKs); for (Long l : oldPeer._currentACKs) { tmp.add(l); } oldPeer._currentACKs.clear(); if (!_dead) { _currentACKs.addAll(tmp); } List<ResendACK> tmp3 = new ArrayList<ResendACK>(); tmp3.addAll(oldPeer._currentACKsResend); oldPeer._currentACKsResend.clear(); if (!_dead) { _currentACKsResend.addAll(tmp3); } Map<Long, InboundMessageState> msgs = new HashMap<Long, InboundMessageState>(); synchronized (oldPeer._inboundMessages) { msgs.putAll(oldPeer._inboundMessages); oldPeer._inboundMessages.clear(); } if (!_dead) { synchronized (_inboundMessages) { _inboundMessages.putAll(msgs); } } msgs.clear(); List<OutboundMessageState> tmp2 = new ArrayList<OutboundMessageState>(); OutboundMessageState retransmitter = null; synchronized (oldPeer._outboundMessages) { tmp2.addAll(oldPeer._outboundMessages); oldPeer._outboundMessages.clear(); retransmitter = oldPeer._retransmitter; oldPeer._retransmitter = null; } if (!_dead) { synchronized (_outboundMessages) { _outboundMessages.addAll(tmp2); _retransmitter = retransmitter; } } } /** * Convenience for OutboundMessageState so it can fail itself * @since 0.9.3 */ public UDPTransport getTransport() { return _transport; } /** * A message ID and a timestamp. Used for the resend ACKS. * @since 0.9.17 */ private static class ResendACK { public final Long id; public final long time; public ResendACK(Long id, long time) { this.id = id; this.time = time; } } // why removed? Some risk of dups in OutboundMessageFragments._activePeers ??? /* public int hashCode() { if (_remotePeer != null) return _remotePeer.hashCode(); else return super.hashCode(); } public boolean equals(Object o) { if (o == this) return true; if (o == null) return false; if (o instanceof PeerState) { PeerState s = (PeerState)o; if (_remotePeer == null) return o == this; else return _remotePeer.equals(s.getRemotePeer()); } else { return false; } } */ @Override public String toString() { StringBuilder buf = new StringBuilder(256); buf.append(_remoteHostId.toString()); if (_remotePeer != null) buf.append(" ").append(_remotePeer.toBase64().substring(0,6)); buf.append(_isInbound? " IB " : " OB "); long now = _context.clock().now(); buf.append(" recvAge: ").append(now-_lastReceiveTime); buf.append(" sendAge: ").append(now-_lastSendFullyTime); buf.append(" sendAttemptAge: ").append(now-_lastSendTime); buf.append(" sendACKAge: ").append(now-_lastACKSend); buf.append(" lifetime: ").append(now-_keyEstablishedTime); buf.append(" cwin: ").append(_sendWindowBytes); buf.append(" acwin: ").append(_sendWindowBytesRemaining); buf.append(" consecFail: ").append(_consecutiveFailedSends); buf.append(" msgs rcvd: ").append(_messagesReceived); buf.append(" msgs sent: ").append(_messagesSent); buf.append(" pkts rcvd OK/Dup: ").append(_packetsReceived).append('/').append(_packetsReceivedDuplicate); buf.append(" pkts sent OK/Dup: ").append(_packetsTransmitted).append('/').append(_packetsRetransmitted); buf.append(" IBM: ").append(_inboundMessages.size()); buf.append(" OBQ: ").append(_outboundQueue.size()); buf.append(" OBL: ").append(_outboundMessages.size()); return buf.toString(); } }