package net.i2p.client.streaming.impl; import net.i2p.client.streaming.I2PSocketOptions; import java.util.Collections; import java.util.HashSet; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import net.i2p.I2PAppContext; import net.i2p.data.Hash; import net.i2p.util.ConvertToHash; import net.i2p.util.Log; /** * Define the current options for the con (and allow custom tweaking midstream) * * TODO many of these are not per-connection options, and should be migrated * somewhere so they aren't copied for every connection */ class ConnectionOptions extends I2PSocketOptionsImpl { private int _connectDelay; private boolean _fullySigned; private boolean _answerPings; private boolean _enforceProto; private volatile int _windowSize; private int _receiveWindow; private int _profile; private int _rtt; private int _rttDev; private int _rto = INITIAL_RTO; private int _resendDelay; private int _sendAckDelay; private int _maxMessageSize; private int _maxResends; private int _inactivityTimeout; private int _inactivityAction; private int _inboundBufferSize; private int _maxWindowSize; private int _congestionAvoidanceGrowthRateFactor; private int _slowStartGrowthRateFactor; private boolean _accessListEnabled; private boolean _blackListEnabled; private Set<Hash> _accessList; private Set<Hash> _blackList; private int _maxConnsPerMinute; private int _maxConnsPerHour; private int _maxConnsPerDay; private int _maxTotalConnsPerMinute; private int _maxTotalConnsPerHour; private int _maxTotalConnsPerDay; private int _maxConns; private boolean _disableRejectLog; /** state of a connection */ private enum AckInit { INIT, // just created FIRST, // first received ack STEADY } /** LOCKING: this */ private AckInit _initState = AckInit.INIT; // NOTE - almost all the options are below, but see // I2PSocketOptions in ministreaming for a few more public static final int PROFILE_BULK = 1; public static final int PROFILE_INTERACTIVE = 2; /** on inactivity timeout, do nothing */ public static final int INACTIVITY_ACTION_NOOP = 0; /** on inactivity timeout, close the connection */ public static final int INACTIVITY_ACTION_DISCONNECT = 1; /** on inactivity timeout, send a payload message */ public static final int INACTIVITY_ACTION_SEND = 2; /* * These values are specified in RFC 6298 * Do not change unless you know what you're doing */ private static final double TCP_ALPHA = 1.0/8; private static final double TCP_BETA = 1.0/4; private static final double TCP_KAPPA = 4; private static final String PROP_INITIAL_RTO = "i2p.streaming.initialRTO"; private static final int INITIAL_RTO = 9000; public static final String PROP_CONNECT_DELAY = "i2p.streaming.connectDelay"; public static final String PROP_PROFILE = "i2p.streaming.profile"; public static final String PROP_MAX_MESSAGE_SIZE = "i2p.streaming.maxMessageSize"; public static final String PROP_MAX_RESENDS = "i2p.streaming.maxResends"; public static final String PROP_INITIAL_RESEND_DELAY = "i2p.streaming.initialResendDelay"; public static final String PROP_INITIAL_ACK_DELAY = "i2p.streaming.initialAckDelay"; public static final String PROP_INITIAL_WINDOW_SIZE = "i2p.streaming.initialWindowSize"; /** unused */ public static final String PROP_INITIAL_RECEIVE_WINDOW = "i2p.streaming.initialReceiveWindow"; public static final String PROP_INACTIVITY_TIMEOUT = "i2p.streaming.inactivityTimeout"; public static final String PROP_INACTIVITY_ACTION = "i2p.streaming.inactivityAction"; public static final String PROP_MAX_WINDOW_SIZE = "i2p.streaming.maxWindowSize"; public static final String PROP_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR = "i2p.streaming.congestionAvoidanceGrowthRateFactor"; public static final String PROP_SLOW_START_GROWTH_RATE_FACTOR = "i2p.streaming.slowStartGrowthRateFactor"; public static final String PROP_ANSWER_PINGS = "i2p.streaming.answerPings"; /** all of these are @since 0.7.13 */ public static final String PROP_ENABLE_ACCESS_LIST = "i2cp.enableAccessList"; public static final String PROP_ENABLE_BLACKLIST = "i2cp.enableBlackList"; public static final String PROP_ACCESS_LIST = "i2cp.accessList"; /** all of these are @since 0.7.14 */ public static final String PROP_MAX_CONNS_MIN = "i2p.streaming.maxConnsPerMinute"; public static final String PROP_MAX_CONNS_HOUR = "i2p.streaming.maxConnsPerHour"; public static final String PROP_MAX_CONNS_DAY = "i2p.streaming.maxConnsPerDay"; public static final String PROP_MAX_TOTAL_CONNS_MIN = "i2p.streaming.maxTotalConnsPerMinute"; public static final String PROP_MAX_TOTAL_CONNS_HOUR = "i2p.streaming.maxTotalConnsPerHour"; public static final String PROP_MAX_TOTAL_CONNS_DAY = "i2p.streaming.maxTotalConnsPerDay"; /** @since 0.9.1 */ public static final String PROP_ENFORCE_PROTO = "i2p.streaming.enforceProtocol"; /** * how many streams will we allow at once? * @since 0.9.3 moved from I2PSocketManagerFull */ public static final String PROP_MAX_STREAMS = "i2p.streaming.maxConcurrentStreams"; /** @since 0.9.4 default false */ public static final String PROP_DISABLE_REJ_LOG = "i2p.streaming.disableRejectLogging"; private static final int TREND_COUNT = 3; static final int INITIAL_WINDOW_SIZE = 6; static final int DEFAULT_MAX_SENDS = 8; public static final int DEFAULT_INITIAL_RTT = 8*1000; private static final int MAX_RTT = 60*1000; private static final int DEFAULT_INITIAL_ACK_DELAY = 750; static final int MIN_WINDOW_SIZE = 1; private static final boolean DEFAULT_ANSWER_PINGS = true; private static final int DEFAULT_INACTIVITY_TIMEOUT = 90*1000; private static final int DEFAULT_INACTIVITY_ACTION = INACTIVITY_ACTION_SEND; private static final int DEFAULT_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR = 1; private static final int DEFAULT_SLOW_START_GROWTH_RATE_FACTOR = 1; /** * If PROTO is enforced, we cannot communicate with destinations earlier than version 0.7.1. * @since 0.9.1 */ private static final boolean DEFAULT_ENFORCE_PROTO = false; private final int _trend[] = new int[TREND_COUNT]; /** * OK, here is the calculation on the message size to fit in a single * tunnel message without fragmentation. * This is based on documentation, the code, and logging, however there are still * some parts that could use more research. * *<pre> * 1024 Tunnel Message * - 21 Header (see router/tunnel/BatchedPreprocessor.java) * ----- * 1003 Tunnel Payload * - 39 Unfragmented instructions (see router/tunnel/TrivialPreprocessor.java) * ----- * 964 Unfragmented I2NP Message * - 20 ?? * ----- * 944 Garlic Message padded to 16 bytes * - 0 Pad to 16 bytes (why?) * ----- * 944 Garlic Message (assumes no bundled leaseSet or keys) * - 71 Garlic overhead * ----- * 873 Tunnel Data Message * - 84 ?? * ----- * 789 Gzipped I2NP message * - 23 Gzip 10 byte header, 5 byte block header, 8 byte trailer (yes we always use gzip, but it * probably isn't really compressing, just adding the headers and trailer, since * HTTP Server already compresses, and most P2P files aren't compressible. * (see client/I2PSessionImpl2.java, util/ReusableGZipOutputStream.java, and the gzip and deflate specs) * ----- * 766 * - 28 Streaming header (24 min, but leave room for a nack or other optional things) (See Packet.java) * ----- * 738 Streaming message size * * * FOR TWO TUNNEL MESSAGES: * * 2048 2 Tunnel Messages * - 42 2 Headers * ----- * 2006 Tunnel Payload * - 50 Fragmented instructions (43 for first + 7 for second) * ----- * 1956 Unfragmented I2NP Message * - 20 ?? * ----- * 1936 Garlic Message padded to 16 bytes * 1936 * - 0 Pad to 16 bytes * ----- * 1936 Garlic Message * - 71 Garlic overhead * ----- * 1865 Tunnel Data Message * - 84 ?? * ----- * 1781 Gzipped I2NP message * - 23 Gzip header * ----- * 1758 * - 28 Streaming header * ----- * 1730 Streaming message size to fit in 2 tunnel messages * * * Similarly: * 3 msgs: 2722 * 4 msgs: 3714 *</pre> * * Before release 0.6.1.14 this was 4096. * From release 0.6.1.14 through release 0.6.4, this was 960. * It was claimed in the comment that this fit in * a single tunnel message (and the checkin comment says the goal was to * increase reliability at the expense of throughput), * clearly from the math above that was not correct. * (Before 0.6.2, the reply leaseSet was bundled with every message, so it didn't even * fit in TWO tunnel messages - more like 2 1/3) * <p> * Now, it's not clear how often we will get the ideal situation (no reply leaseSet bundling, * no key bundling, and especially not having a small message ahead of you, which will then cause * fragmentation for all subsequent messages until the queue is emptied - BatchedPreprocessor * doesn't do reordering, and it isn't clear to me if it could). In particular the initial * messages in a new stream are much larger due to the leaseSet and key bundling. * But for long-lived streams (like with i2psnark) this should pay dividends. * The tunnel.batch* stats should provide some data for test comparisons. * <p> * As MTU and MRU are identical and are negotiated to the lowest value * for the two ends, you can't do widespread testing of a higher value. * Unless we change to allow MTU and MRU to be different, * which would be a pain because it would mess up our buffer scheme. * Both 738 and 1730 have been tested to verify that the math above is correct. * So let's try 1730 for release 0.6.5. This will allow for 738 testing as well, * with i2p.streaming.maxMessageSize=738 (in configadvanced.jsp, or in i2ptunnel, or * i2psnark, for example). * <p> * Not that an isolated single packet is very common, but * in this case, 960 was 113.3% total overhead. * Compared to 738 (38.8% overhead) and 1730 (18.4%). * */ public static final int DEFAULT_MAX_MESSAGE_SIZE = 1730; public static final int MIN_MESSAGE_SIZE = 512; /** * Sets max buffer size, connect timeout, read timeout, and write timeout * from System properties. Does not set local port or remote port. */ public ConnectionOptions() { super(); cinit(System.getProperties()); } /** * Sets max buffer size, connect timeout, read timeout, and write timeout * from properties. Does not set local port or remote port. * * As of 0.9.19, defaults in opts are honored. * * @param opts may be null */ public ConnectionOptions(Properties opts) { super(opts); cinit(opts); } /** * Initializes from System properties then copies over all options. * @param opts may be null */ public ConnectionOptions(I2PSocketOptions opts) { super(opts); cinit(System.getProperties()); } /** * Initializes from System properties then copies over all options. * @param opts may be null */ public ConnectionOptions(ConnectionOptions opts) { super(opts); cinit(System.getProperties()); if (opts != null) update(opts); } /** * Update everything by copying over from opts * @param opts non-null * @since 0.9.1 */ public void updateAll(ConnectionOptions opts) { // user is unlikely to change these 6 between buildOptions() and setDefaultOptions(), // since they may be updated directly, but just in case... setConnectTimeout(opts.getConnectTimeout()); setReadTimeout(opts.getReadTimeout()); setWriteTimeout(opts.getWriteTimeout()); setMaxBufferSize(opts.getMaxBufferSize()); setLocalPort(opts.getLocalPort()); setPort(opts.getPort()); update(opts); } /** * Update everything (except super) by copying over from opts * @param opts non-null * @since 0.9.1 */ private void update(ConnectionOptions opts) { setMaxWindowSize(opts.getMaxWindowSize()); setConnectDelay(opts.getConnectDelay()); setProfile(opts.getProfile()); setRTTDev(opts.getRTTDev()); setRTT(opts.getRTT()); setRequireFullySigned(opts.getRequireFullySigned()); setWindowSize(opts.getWindowSize()); setResendDelay(opts.getResendDelay()); setMaxMessageSize(opts.getMaxMessageSize()); setMaxResends(opts.getMaxResends()); setInactivityTimeout(opts.getInactivityTimeout()); setInactivityAction(opts.getInactivityAction()); setInboundBufferSize(opts.getInboundBufferSize()); setCongestionAvoidanceGrowthRateFactor(opts.getCongestionAvoidanceGrowthRateFactor()); setSlowStartGrowthRateFactor(opts.getSlowStartGrowthRateFactor()); // handled in super() // not clear why added by jr 12/22/2005 //setWriteTimeout(opts.getWriteTimeout()); //setReadTimeout(opts.getReadTimeout()); setAnswerPings(opts.getAnswerPings()); setEnforceProtocol(opts.getEnforceProtocol()); setDisableRejectLogging(opts.getDisableRejectLogging()); initLists(opts); _maxConnsPerMinute = opts.getMaxConnsPerMinute(); _maxConnsPerHour = opts.getMaxConnsPerHour(); _maxConnsPerDay = opts.getMaxConnsPerDay(); _maxTotalConnsPerMinute = opts.getMaxTotalConnsPerMinute(); _maxTotalConnsPerHour = opts.getMaxTotalConnsPerHour(); _maxTotalConnsPerDay = opts.getMaxTotalConnsPerDay(); _maxConns = opts.getMaxConns(); } /** * Initialization */ private void cinit(Properties opts) { setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE)); setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1)); setProfile(getInt(opts, PROP_PROFILE, PROFILE_BULK)); setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, DEFAULT_MAX_MESSAGE_SIZE)); setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1)); setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 1000)); setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY)); setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, INITIAL_WINDOW_SIZE)); setMaxResends(getInt(opts, PROP_MAX_RESENDS, DEFAULT_MAX_SENDS)); // handled in super() //setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1)); setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, DEFAULT_INACTIVITY_TIMEOUT)); setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, DEFAULT_INACTIVITY_ACTION)); setInboundBufferSize(getMaxMessageSize() * (Connection.MAX_WINDOW_SIZE + 2)); setCongestionAvoidanceGrowthRateFactor(getInt(opts, PROP_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR, DEFAULT_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR)); setSlowStartGrowthRateFactor(getInt(opts, PROP_SLOW_START_GROWTH_RATE_FACTOR, DEFAULT_SLOW_START_GROWTH_RATE_FACTOR)); // overrides default in super()... why? //setConnectTimeout(getInt(opts, PROP_CONNECT_TIMEOUT, Connection.DISCONNECT_TIMEOUT)); setAnswerPings(getBool(opts, PROP_ANSWER_PINGS, DEFAULT_ANSWER_PINGS)); setEnforceProtocol(getBool(opts, PROP_ENFORCE_PROTO, DEFAULT_ENFORCE_PROTO)); setDisableRejectLogging(getBool(opts, PROP_DISABLE_REJ_LOG, false)); initLists(opts); _maxConnsPerMinute = getInt(opts, PROP_MAX_CONNS_MIN, 0); _maxConnsPerHour = getInt(opts, PROP_MAX_CONNS_HOUR, 0); _maxConnsPerDay = getInt(opts, PROP_MAX_CONNS_DAY, 0); _maxTotalConnsPerMinute = getInt(opts, PROP_MAX_TOTAL_CONNS_MIN, 0); _maxTotalConnsPerHour = getInt(opts, PROP_MAX_TOTAL_CONNS_HOUR, 0); _maxTotalConnsPerDay = getInt(opts, PROP_MAX_TOTAL_CONNS_DAY, 0); _maxConns = getInt(opts, PROP_MAX_STREAMS, 0); _rto = getInt(opts, PROP_INITIAL_RTO, INITIAL_RTO); } /** * Note: NOT part of the interface * * As of 0.9.19, defaults in opts are honored. */ @Override public void setProperties(Properties opts) { super.setProperties(opts); if (opts == null) return; if (opts.getProperty(PROP_MAX_WINDOW_SIZE) != null) setMaxWindowSize(getInt(opts, PROP_MAX_WINDOW_SIZE, Connection.MAX_WINDOW_SIZE)); if (opts.getProperty(PROP_CONNECT_DELAY) != null) setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1)); if (opts.getProperty(PROP_PROFILE) != null) setProfile(getInt(opts, PROP_PROFILE, PROFILE_BULK)); if (opts.getProperty(PROP_MAX_MESSAGE_SIZE) != null) setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, Packet.MAX_PAYLOAD_SIZE)); if (opts.getProperty(PROP_INITIAL_RECEIVE_WINDOW) != null) setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1)); if (opts.getProperty(PROP_INITIAL_RESEND_DELAY) != null) setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 1000)); if (opts.getProperty(PROP_INITIAL_ACK_DELAY) != null) setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY)); if (opts.getProperty(PROP_INITIAL_WINDOW_SIZE) != null) setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, INITIAL_WINDOW_SIZE)); if (opts.getProperty(PROP_MAX_RESENDS) != null) setMaxResends(getInt(opts, PROP_MAX_RESENDS, DEFAULT_MAX_SENDS)); // handled in super() //if (opts.getProperty(PROP_WRITE_TIMEOUT)) // setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1)); if (opts.getProperty(PROP_INACTIVITY_TIMEOUT) != null) setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, DEFAULT_INACTIVITY_TIMEOUT)); if (opts.getProperty(PROP_INACTIVITY_ACTION) != null) setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, DEFAULT_INACTIVITY_ACTION)); setInboundBufferSize(getMaxMessageSize() * (Connection.MAX_WINDOW_SIZE + 2)); if (opts.getProperty(PROP_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR) != null) setCongestionAvoidanceGrowthRateFactor(getInt(opts, PROP_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR, DEFAULT_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR)); if (opts.getProperty(PROP_SLOW_START_GROWTH_RATE_FACTOR) != null) setSlowStartGrowthRateFactor(getInt(opts, PROP_SLOW_START_GROWTH_RATE_FACTOR, DEFAULT_SLOW_START_GROWTH_RATE_FACTOR)); if (opts.getProperty(PROP_CONNECT_TIMEOUT) != null) // overrides default in super() setConnectTimeout(getInt(opts, PROP_CONNECT_TIMEOUT, Connection.DEFAULT_CONNECT_TIMEOUT)); if (opts.getProperty(PROP_ANSWER_PINGS) != null) setAnswerPings(getBool(opts, PROP_ANSWER_PINGS, DEFAULT_ANSWER_PINGS)); if (opts.getProperty(PROP_ENFORCE_PROTO) != null) setEnforceProtocol(getBool(opts, PROP_ENFORCE_PROTO, DEFAULT_ENFORCE_PROTO)); if (opts.getProperty(PROP_DISABLE_REJ_LOG) != null) setDisableRejectLogging(getBool(opts, PROP_DISABLE_REJ_LOG, false)); initLists(opts); if (opts.getProperty(PROP_MAX_CONNS_MIN) != null) _maxConnsPerMinute = getInt(opts, PROP_MAX_CONNS_MIN, 0); if (opts.getProperty(PROP_MAX_CONNS_HOUR) != null) _maxConnsPerHour = getInt(opts, PROP_MAX_CONNS_HOUR, 0); if (opts.getProperty(PROP_MAX_CONNS_DAY) != null) _maxConnsPerDay = getInt(opts, PROP_MAX_CONNS_DAY, 0); if (opts.getProperty(PROP_MAX_TOTAL_CONNS_MIN) != null) _maxTotalConnsPerMinute = getInt(opts, PROP_MAX_TOTAL_CONNS_MIN, 0); if (opts.getProperty(PROP_MAX_TOTAL_CONNS_HOUR) != null) _maxTotalConnsPerHour = getInt(opts, PROP_MAX_TOTAL_CONNS_HOUR, 0); if (opts.getProperty(PROP_MAX_TOTAL_CONNS_DAY) != null) _maxTotalConnsPerDay = getInt(opts, PROP_MAX_TOTAL_CONNS_DAY, 0); if (opts.getProperty(PROP_MAX_STREAMS) != null) _maxConns = getInt(opts, PROP_MAX_STREAMS, 0); _rto = getInt(opts, PROP_INITIAL_RTO, INITIAL_RTO); } /** * how long will we wait after instantiating a new con * before actually attempting to connect. If this is * set to 0, connect ASAP. If it is greater than 0, wait * until the output stream is flushed, the buffer fills, * or that many milliseconds pass. * * @return how long to wait before actually attempting to connect */ public int getConnectDelay() { return _connectDelay; } public void setConnectDelay(int delayMs) { _connectDelay = delayMs; } /** * Do we want all packets in both directions to be signed, * or can we deal with signatures on the SYN and FIN packets * only? * * There is no property name defined for this, so it's safe to * say this is unused and always false. * * @return if we want signatures on all packets. */ public boolean getRequireFullySigned() { return _fullySigned; } /** unused, see above */ public void setRequireFullySigned(boolean sign) { _fullySigned = sign; } /** * Do we respond to a ping? * * @return if we do */ public boolean getAnswerPings() { return _answerPings; } public void setAnswerPings(boolean yes) { _answerPings = yes; } /** * Do we receive all traffic, or only traffic marked with I2PSession.PROTO_STREAMING (6) ? * Default false. * If PROTO is enforced, we cannot communicate with destinations earlier than version 0.7.1 * (released March 2009), which is when streaming started sending the PROTO_STREAMING indication. * Set to true if you are running multiple protocols on a single Destination. * * @return if we do * @since 0.9.1 */ public boolean getEnforceProtocol() { return _enforceProto; } public void setEnforceProtocol(boolean yes) { _enforceProto = yes; } /** * Do we disable connection rejected logging? Default false. * * @return if we do * @since 0.9.4 */ public boolean getDisableRejectLogging() { return _disableRejectLog; } public void setDisableRejectLogging(boolean yes) { _disableRejectLog = yes; } /** * How many messages will we send before waiting for an ACK? * * @return Maximum amount of messages that can be in-flight */ public int getWindowSize() { return _windowSize; } public void setWindowSize(int numMsgs) { if (numMsgs <= 0) numMsgs = 1; if (numMsgs < MIN_WINDOW_SIZE) numMsgs = MIN_WINDOW_SIZE; // the stream's max window size may be less than the min window size, for // instance, with interactive streams of cwin=1. This is why we test it here // after checking MIN_WINDOW_SIZE if (numMsgs > _maxWindowSize) numMsgs = _maxWindowSize; _windowSize = numMsgs; } /** after how many consecutive messages should we ack? * @deprecated This doesn't appear to be used. * @return receive window size. */ @Deprecated public int getReceiveWindow() { return _receiveWindow; } public void setReceiveWindow(int numMsgs) { _receiveWindow = numMsgs; } /** * What to set the round trip time estimate to (in milliseconds) * @return round trip time estimate in ms */ public synchronized int getRTT() { return _rtt; } /** * not public, use updateRTT() */ private void setRTT(int ms) { synchronized (_trend) { _trend[0] = _trend[1]; _trend[1] = _trend[2]; if (ms > _rtt) _trend[2] = 1; else if (ms < _rtt) _trend[2] = -1; else _trend[2] = 0; } synchronized(this) { _rtt = ms; if (_rtt > MAX_RTT) _rtt = MAX_RTT; } } public synchronized int getRTO() { return _rto; } /** used in TCB @since 0.9.8 */ synchronized int getRTTDev() { return _rttDev; } private synchronized void setRTTDev(int rttDev) { _rttDev = rttDev; } /** * Loads options from TCB cache. */ synchronized void loadFromCache(int rtt, int rttDev, int wdw) { _initState = AckInit.STEADY; setRTT(rtt); setRTTDev(rttDev); setWindowSize(wdw); computeRTO(); } /** * computes RTO based on formula in RFC */ private synchronized void computeRTO() { switch(_initState) { case INIT : throw new IllegalStateException(); case FIRST : _rto = _rtt + _rtt / 2; break; case STEADY : _rto = _rtt + (int) (_rttDev * TCP_KAPPA); break; } if (_rto < Connection.MIN_RESEND_DELAY) _rto = (int)Connection.MIN_RESEND_DELAY; else if (_rto > Connection.MAX_RESEND_DELAY) _rto = (int)Connection.MAX_RESEND_DELAY; } /** * If we have 3 consecutive rtt increases, we are trending upwards (1), or if we have * 3 consecutive rtt decreases, we are trending downwards (-1), else we're stable. * * @return positive/flat/negative trend in round trip time */ public int getRTTTrend() { synchronized (_trend) { for (int i = 0; i < TREND_COUNT - 1; i++) { if (_trend[i] != _trend[i+1]) return 0; } return _trend[0]; } } /** * @param measuredValue must be positive */ public synchronized void updateRTT(int measuredValue) { switch(_initState) { case INIT: _initState = AckInit.FIRST; setRTT(measuredValue); // no smoothing first sample _rttDev = _rtt / 2; break; case FIRST: _initState = AckInit.STEADY; // fall through case STEADY: // calculation matches that recommended in RFC 6298 _rttDev = (int) ((1-TCP_BETA) *_rttDev + TCP_BETA * Math.abs(measuredValue-_rtt)); int smoothed = (int)((1-TCP_ALPHA)*_rtt + TCP_ALPHA*measuredValue); setRTT(smoothed); } computeRTO(); } public synchronized boolean receivedAck() { return _initState != AckInit.INIT; } /** How long after sending a packet will we wait before resending? * @return delay for a retransmission in ms */ public int getResendDelay() { return _resendDelay; } public void setResendDelay(int ms) { _resendDelay = ms; } /** * if there are packets we haven't ACKed yet and we don't * receive _receiveWindow messages before * (_lastSendTime+_sendAckDelay), send an ACK of what * we have received so far. * * @return ACK delay in ms */ public int getSendAckDelay() { return _sendAckDelay; } /** * Unused except here, so expect the default initial delay of 2000 ms unless set by the user * to remain constant. */ public void setSendAckDelay(int delayMs) { _sendAckDelay = delayMs; } /** What is the largest message we want to send or receive? * @return Maximum message size (MTU/MRU) */ public int getMaxMessageSize() { return _maxMessageSize; } public void setMaxMessageSize(int bytes) { _maxMessageSize = Math.max(bytes, MIN_MESSAGE_SIZE); } /** * What profile do we want to use for this connection? * TODO: Only bulk is supported so far. * @return the profile of the connection. */ public int getProfile() { return _profile; } public void setProfile(int profile) { if (profile != PROFILE_BULK) throw new IllegalArgumentException("Only bulk is supported so far"); _profile = profile; } /** * How many times will we try to send a message before giving up? * * @return Maximum retrys before failing a sent message. */ public int getMaxResends() { return _maxResends; } public void setMaxResends(int numSends) { _maxResends = Math.max(numSends, 0); } /** * What period of inactivity qualifies as "too long"? * * @return period of inactivity qualifies as "too long" */ public int getInactivityTimeout() { return _inactivityTimeout; } public void setInactivityTimeout(int timeout) { _inactivityTimeout = timeout; } public int getInactivityAction() { return _inactivityAction; } public void setInactivityAction(int action) { _inactivityAction = action; } public int getMaxWindowSize() { return _maxWindowSize; } public void setMaxWindowSize(int msgs) { if (msgs > Connection.MAX_WINDOW_SIZE) _maxWindowSize = Connection.MAX_WINDOW_SIZE; else if (msgs < 1) _maxWindowSize = 1; else _maxWindowSize = msgs; } /** * how much data are we willing to accept in our buffer? * * @return size of the buffer used to accept data */ public int getInboundBufferSize() { return _inboundBufferSize; } public void setInboundBufferSize(int bytes) { _inboundBufferSize = bytes; } /** * When we're in congestion avoidance, we grow the window size at the rate * of 1/(windowSize*factor). In standard TCP, window sizes are in bytes, * while in I2P, window sizes are in messages, so setting factor=maxMessageSize * mimics TCP, but using a smaller factor helps grow a little more rapidly. * @return window size to grow by to attempt to avoid congestion. */ public int getCongestionAvoidanceGrowthRateFactor() { return _congestionAvoidanceGrowthRateFactor; } public void setCongestionAvoidanceGrowthRateFactor(int factor) { _congestionAvoidanceGrowthRateFactor = factor; } /** * When we're in slow start, we grow the window size at the rate * of 1/(factor). In standard TCP, window sizes are in bytes, * while in I2P, window sizes are in messages, so setting factor=maxMessageSize * mimics TCP, but using a smaller factor helps grow a little more rapidly. * @return slow start window size to grow by to attempt to avoid sending many small packets. */ public int getSlowStartGrowthRateFactor() { return _slowStartGrowthRateFactor; } public void setSlowStartGrowthRateFactor(int factor) { _slowStartGrowthRateFactor = factor; } /** all of these are @since 0.7.14; no public setters */ public int getMaxConnsPerMinute() { return _maxConnsPerMinute; } public int getMaxConnsPerHour() { return _maxConnsPerHour; } public int getMaxConnsPerDay() { return _maxConnsPerDay; } public int getMaxTotalConnsPerMinute() { return _maxTotalConnsPerMinute; } public int getMaxTotalConnsPerHour() { return _maxTotalConnsPerHour; } public int getMaxTotalConnsPerDay() { return _maxTotalConnsPerDay; } /** @since 0.9.3; no public setter */ public int getMaxConns() { return _maxConns; } public boolean isAccessListEnabled() { return _accessListEnabled; } public boolean isBlacklistEnabled() { return _blackListEnabled; } public Set<Hash> getAccessList() { return _accessList; } public Set<Hash> getBlacklist() { return _blackList; } private void initLists(ConnectionOptions opts) { _accessList = opts.getAccessList(); _blackList = opts.getBlacklist(); _accessListEnabled = opts.isAccessListEnabled(); _blackListEnabled = opts.isBlacklistEnabled(); } private void initLists(Properties opts) { boolean accessListEnabled = getBool(opts, PROP_ENABLE_ACCESS_LIST, false); boolean blackListEnabled = getBool(opts, PROP_ENABLE_BLACKLIST, false); // Don't think these would ever be accessed simultaneously, // but avoid concurrent modification just in case Set<Hash> accessList, blackList; if (accessListEnabled) accessList = new HashSet<Hash>(); else accessList = Collections.emptySet(); if (blackListEnabled) blackList = new HashSet<Hash>(); else blackList = Collections.emptySet(); if (accessListEnabled || blackListEnabled) { String hashes = opts.getProperty(PROP_ACCESS_LIST); if (hashes == null) return; StringTokenizer tok = new StringTokenizer(hashes, ",; "); while (tok.hasMoreTokens()) { String hashstr = tok.nextToken(); Hash h = ConvertToHash.getHash(hashstr); if (h == null) error("bad list hash: " + hashstr); else if (blackListEnabled) blackList.add(h); else accessList.add(h); } } _accessList = accessList; _blackList = blackList; _accessListEnabled = accessListEnabled; _blackListEnabled = blackListEnabled; if (_accessListEnabled && _accessList.isEmpty()) error("Connection access list enabled but no valid entries; no peers can connect"); else if (_blackListEnabled && _blackList.isEmpty()) error("Connection blacklist enabled but no valid entries; all peers can connect"); } private static void error(String s) { I2PAppContext ctx = I2PAppContext.getGlobalContext(); Log log = ctx.logManager().getLog(ConnectionOptions.class); log.error(s); } /** doesn't include everything */ @Override public String toString() { StringBuilder buf = new StringBuilder(256); buf.append("conDelay=").append(_connectDelay); buf.append(" maxSize=").append(_maxMessageSize); buf.append(" rtt=").append(_rtt); buf.append(" rwin=").append(_receiveWindow); buf.append(" resendDelay=").append(_resendDelay); buf.append(" ackDelay=").append(_sendAckDelay); buf.append(" cwin=").append(_windowSize); buf.append(" maxResends=").append(_maxResends); buf.append(" writeTimeout=").append(getWriteTimeout()); buf.append(" readTimeout=").append(getReadTimeout()); buf.append(" inactivityTimeout=").append(_inactivityTimeout); buf.append(" inboundBuffer=").append(_inboundBufferSize); buf.append(" maxWindowSize=").append(_maxWindowSize); buf.append(" blacklistSize=").append(_blackList.size()); buf.append(" whitelistSize=").append(_accessList.size()); buf.append(" maxConns=").append(_maxConnsPerMinute).append('/') .append(_maxConnsPerHour).append('/') .append(_maxConnsPerDay); buf.append(" maxTotalConns=").append(_maxTotalConnsPerMinute).append('/') .append(_maxTotalConnsPerHour).append('/') .append(_maxTotalConnsPerDay); return buf.toString(); } private static boolean getBool(Properties opts, String name, boolean defaultVal) { if (opts == null) return defaultVal; String val = opts.getProperty(name); if (val == null) return defaultVal; return Boolean.parseBoolean(val); } /**** public static void main(String args[]) { Properties p = new Properties(); p.setProperty(PROP_CONNECT_DELAY, "1000"); ConnectionOptions c = new ConnectionOptions(p); System.out.println("opts: " + c); c = new ConnectionOptions(new I2PSocketOptionsImpl(p)); System.out.println("opts: " + c); } ****/ }