Changeset cabf1aa


Ignore:
Timestamp:
Feb 16, 2018 7:49:49 PM (3 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
48027fe6
Parents:
a1ed4cf
Message:

Streaming: configurable response when over connection limits (ticket #2145)
Drop when way over limits.
Default to HTTP 429 for HTTP Server tunnels
Increase recently-closed cache size

Files:
6 edited

Legend:

Unmodified
Added
Removed
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java

    ra1ed4cf rcabf1aa  
    106106    public static final int DEFAULT_MAX_STREAMS = 20;
    107107
     108    /** @since 0.9.34 */
     109    public static final String PROP_LIMIT_ACTION = "i2p.streaming.limitAction";
     110
    108111    /** @since 0.9.14 */
    109112    public static final String PFX_OPTION = "option.";
     
    130133    public static final String OPT_POST_MAX = PFX_OPTION + I2PTunnelHTTPServer.OPT_POST_MAX;
    131134    public static final String OPT_POST_TOTAL_MAX = PFX_OPTION + I2PTunnelHTTPServer.OPT_POST_TOTAL_MAX;
     135
     136    /** @since 0.9.34 */
     137    private static final String OPT_LIMIT_ACTION = PFX_OPTION + PROP_LIMIT_ACTION;
    132138
    133139    /** all of these @since 0.9.14 */
     
    801807        String type = getType();
    802808        if (type != null) {
     809            if (type.equals(TYPE_HTTP_SERVER)) {
     810                if (!_config.containsKey(OPT_LIMIT_ACTION))
     811                    _config.setProperty(OPT_LIMIT_ACTION, "http");
     812            }
    803813            if (type.equals(TYPE_HTTP_SERVER) || type.equals(TYPE_STREAMR_SERVER)) {
    804814                if (!_config.containsKey(OPT_BUNDLE_REPLY))
  • apps/streaming/java/src/net/i2p/client/streaming/impl/ConnThrottler.java

    ra1ed4cf rcabf1aa  
    7171    }
    7272
     73    /**
     74     *  Checks if individual count is over the limit by this much. Does not increment.
     75     *  @since 0.9.34
     76     */
     77    boolean isOverBy(Hash h, int over) {
     78        if (_max > 0)
     79            return this.counter.count(h) >  _max + over;
     80        return false;
     81    }
     82
    7383    private class Cleaner implements SimpleTimer.TimedEvent {
    7484        public void timeReached() {
  • apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionManager.java

    ra1ed4cf rcabf1aa  
    1313import net.i2p.client.I2PSession;
    1414import net.i2p.data.ByteArray;
     15import net.i2p.data.DataHelper;
    1516import net.i2p.data.Destination;
    1617import net.i2p.data.Hash;
     
    6263    private static final long MAX_PING_TIMEOUT = 5*60*1000;
    6364    private static final int MAX_PONG_PAYLOAD = 32;
     65    /** once over throttle limits, respond this many times before just dropping */
     66    private static final int DROP_OVER_LIMIT = 3;
     67
     68    // TODO https://stackoverflow.com/questions/16022624/examples-of-http-api-rate-limiting-http-response-headers
     69    private static final String LIMIT_HTTP_RESPONSE =
     70         "HTTP/1.1 429 Denied\r\n"+
     71         "Content-Type: text/html; charset=iso-8859-1\r\n"+
     72         "Cache-Control: no-cache\r\n"+
     73         "Connection: close\r\n"+
     74         "Proxy-Connection: close\r\n"+
     75         "\r\n"+
     76         "<html><head><title>429 Denied</title></head>"+
     77         "<body><h3>429 Denied</h3>" +
     78         "<p>Denied due to excessive requests. Please try again later." +
     79         "</body></html>";
    6480
    6581    /**
     
    90106        _session.addMuxedSessionListener(_messageHandler, protocol, defaultOptions.getLocalPort());
    91107        _outboundQueue = new PacketQueue(_context, _timer);
    92         _recentlyClosed = new LHMCache<Long, Object>(32);
     108        _recentlyClosed = new LHMCache<Long, Object>(64);
    93109        /** Socket timeout for accept() */
    94110        _soTimeout = -1;
     
    218234        opts.setPort(synPacket.getRemotePort());
    219235        opts.setLocalPort(synPacket.getLocalPort());
    220         Connection con = new Connection(_context, this, synPacket.getSession(), _schedulerChooser,
    221                                         _timer, _outboundQueue, _conPacketHandler, opts, true);
    222         _tcbShare.updateOptsFromShare(con);
    223236        boolean reject = false;
    224237        int active = 0;
     
    244257                           (synPacket.getOptionalFrom() == null ? "" : ": " + synPacket.getOptionalFrom().toBase32()));
    245258                    reject = true;
    246                 } else {
    247                     assignReceiveStreamId(con);
    248259                }
    249260            }
     
    255266            if (from == null)
    256267                return null;
    257             if (_dayThrottler != null || _hourThrottler != null) {
    258                 Hash h = from.calculateHash();
    259                 if ((_hourThrottler != null && _hourThrottler.isThrottled(h)) ||
    260                     (_dayThrottler != null && _dayThrottler.isThrottled(h)) ||
    261                     _globalBlacklist.contains(h) ||
    262                     (_defaultOptions.isAccessListEnabled() && !_defaultOptions.getAccessList().contains(h)) ||
    263                     (_defaultOptions.isBlacklistEnabled() && _defaultOptions.getBlacklist().contains(h))) {
    264                     // A signed RST packet + ElGamal + session tags is fairly expensive, so
    265                     // once the hour/day limit is hit for a particular peer, don't even send it.
    266                     // Ditto for blacklist / whitelist
    267                     // This is a tradeoff, because it will keep retransmitting the SYN for a while,
    268                     // thus more inbound, but let's not spend several KB on the outbound.
    269                     if (!Boolean.valueOf(_context.getProperty("i2p.streaming.sendResetOnBlock"))) {
    270                         // this is the default. Set property to send reset for debugging.
    271                         if (_log.shouldLog(Log.INFO))
    272                             _log.info("Dropping RST to " + h);
    273                         return null;
    274                     }
    275                 }
    276             }
     268            String resp = _defaultOptions.getLimitAction();
     269            if ("drop".equals(resp)) {
     270                // always drop
     271                return null;
     272            }
     273            Hash h = from.calculateHash();
     274            if (_globalBlacklist.contains(h) ||
     275                (_defaultOptions.isAccessListEnabled() && !_defaultOptions.getAccessList().contains(h)) ||
     276                (_defaultOptions.isBlacklistEnabled() && _defaultOptions.getBlacklist().contains(h))) {
     277                // always drop these regardless of setting
     278                return null;
     279            }
     280
     281            if ((_minuteThrottler != null && _minuteThrottler.isOverBy(h, DROP_OVER_LIMIT)) ||
     282                (_hourThrottler != null && _hourThrottler.isOverBy(h, DROP_OVER_LIMIT)) ||
     283                (_dayThrottler != null && _dayThrottler.isOverBy(h, DROP_OVER_LIMIT))) {
     284                // A signed RST/close packet + ElGamal + session tags is fairly expensive, so
     285                // once a limit is significantly exceeded for a particular peer, don't even send it.
     286                // This is a tradeoff, because it will keep retransmitting the SYN for a while,
     287                // thus more inbound, but let's not spend several KB on the outbound.
     288                if (_log.shouldLog(Log.INFO))
     289                    _log.info("Dropping limit response to " + from.toBase32());
     290                return null;
     291            }
     292
     293            boolean reset = resp == null || resp.equals("reset") || resp.length() <= 0;
     294            boolean http = !reset && "http".equals(resp);
     295            boolean custom = !(reset || http);
     296            String sendResponse;
     297            if (http) {
     298                sendResponse = LIMIT_HTTP_RESPONSE;
     299            } else if (custom) {
     300                sendResponse = resp.replace("\\r", "\r").replace("\\n", "\n");
     301            } else {
     302                sendResponse = null;
     303            }
     304
    277305            PacketLocal reply = new PacketLocal(_context, from, synPacket.getSession());
    278             reply.setFlag(Packet.FLAG_RESET);
    279             reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
     306            if (sendResponse != null) {
     307                reply.setFlag(Packet.FLAG_SYNCHRONIZE | Packet.FLAG_CLOSE | Packet.FLAG_SIGNATURE_INCLUDED);
     308                reply.setSequenceNum(0);
     309                ByteArray payload = new ByteArray(DataHelper.getUTF8(sendResponse));
     310                reply.setPayload(payload);
     311            } else {
     312                reply.setFlag(Packet.FLAG_RESET | Packet.FLAG_SIGNATURE_INCLUDED);
     313            }
    280314            reply.setAckThrough(synPacket.getSequenceNum());
    281315            reply.setSendStreamId(synPacket.getReceiveStreamId());
    282             reply.setReceiveStreamId(0);
     316            long rcvStreamId = assignRejectId();
     317            reply.setReceiveStreamId(rcvStreamId);
    283318            reply.setOptionalFrom();
    284319            reply.setLocalPort(synPacket.getLocalPort());
    285320            reply.setRemotePort(synPacket.getRemotePort());
     321            if (_log.shouldInfo())
     322                //_log.info("Over limit, sending " + (sendResponse != null ? "configured response" : "reset") + " to " + from.toBase32());
     323                _log.info("Over limit, sending " + reply + " to " + from.toBase32());
    286324            // this just sends the packet - no retries or whatnot
    287325            _outboundQueue.enqueue(reply);
     
    289327        }
    290328       
     329        Connection con = new Connection(_context, this, synPacket.getSession(), _schedulerChooser,
     330                                        _timer, _outboundQueue, _conPacketHandler, opts, true);
     331        _tcbShare.updateOptsFromShare(con);
     332        assignReceiveStreamId(con);
     333
    291334        // finally, we know enough that we can log the packet with the conn filled in
    292335        if (I2PSocketManagerFull.pcapWriter != null &&
     
    386429                     _connectionByInboundId.containsKey(rcvID) ||
    387430                     _pendingPings.putIfAbsent(rcvID, req) != null);
     431        }
     432        return receiveId;
     433    }
     434   
     435    /**
     436     *  Pick a new random stream ID that we are rejecting,
     437     *  taking care to avoid duplicates, and return it.
     438     *
     439     *  @since 0.9.34
     440     */
     441    private long assignRejectId() {
     442        long receiveId;
     443        synchronized(_recentlyClosed) {
     444            Long rcvID;
     445            do {
     446                receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1;
     447                rcvID = Long.valueOf(receiveId);
     448            } while (_recentlyClosed.containsKey(rcvID) ||
     449                     _connectionByInboundId.containsKey(rcvID));
     450            _recentlyClosed.put(rcvID, DUMMY);
    388451        }
    389452        return receiveId;
  • apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionOptions.java

    ra1ed4cf rcabf1aa  
    5353    private int _maxConns;
    5454    private boolean _disableRejectLog;
     55    private String _limitAction;
    5556   
    5657    /** state of a connection */
     
    123124    /** @since 0.9.4  default false */
    124125    public static final String PROP_DISABLE_REJ_LOG = "i2p.streaming.disableRejectLogging";
     126    /** @since 0.9.34 reset,drop,http, or custom string,  default reset */
     127    public static final String PROP_LIMIT_ACTION = "i2p.streaming.limitAction";
    125128   
    126129   
     
    137140    private static final int DEFAULT_CONGESTION_AVOIDANCE_GROWTH_RATE_FACTOR = 1;
    138141    private static final int DEFAULT_SLOW_START_GROWTH_RATE_FACTOR = 1;
     142    private static final String DEFAULT_LIMIT_ACTION = "reset";
    139143
    140144
     
    348352            _maxTotalConnsPerDay = opts.getMaxTotalConnsPerDay();
    349353            _maxConns = opts.getMaxConns();
     354            _limitAction = opts.getLimitAction();
    350355    }
    351356   
     
    385390        _maxTotalConnsPerDay = getInt(opts, PROP_MAX_TOTAL_CONNS_DAY, 0);
    386391        _maxConns = getInt(opts, PROP_MAX_STREAMS, 0);
     392        if (opts != null)
     393            _limitAction = opts.getProperty(PROP_LIMIT_ACTION, DEFAULT_LIMIT_ACTION);
     394        else
     395            _limitAction = DEFAULT_LIMIT_ACTION;
    387396       
    388397        _rto = getInt(opts, PROP_INITIAL_RTO, INITIAL_RTO);
     
    454463        if (opts.getProperty(PROP_MAX_STREAMS) != null)
    455464            _maxConns = getInt(opts, PROP_MAX_STREAMS, 0);
     465        if (opts.getProperty(PROP_LIMIT_ACTION) != null)
     466            _limitAction = opts.getProperty(PROP_LIMIT_ACTION);
    456467       
    457468        _rto = getInt(opts, PROP_INITIAL_RTO, INITIAL_RTO);
     
    772783    public Set<Hash> getAccessList() { return _accessList; }
    773784    public Set<Hash> getBlacklist() { return _blackList; }
     785
     786    /**
     787     * "reset", "drop", "http", or custom string.
     788     * Default "reset".
     789     *
     790     * @since 0.9.34
     791     */
     792    public String getLimitAction() { return _limitAction; }
    774793
    775794    private void initLists(ConnectionOptions opts) {
  • history.txt

    ra1ed4cf rcabf1aa  
     12018-02-16 zzz
     2 * i2psnark: Fix NPE on torrent not found (ticket #2167)
     3 * i2ptunnel: Change POST throttle response to 429
     4 * Streaming: Configurable response when over conn limits (ticket #2145)
     5
    162018-02-12 zzz
     7 * i2ptunnel: Close sockets
     8 * Proxy: Update clearnet user-agent (ticket #2163)
    29 * SusiMail:
    310   - Background email checking (ticket #2087)
  • router/java/src/net/i2p/router/RouterVersion.java

    ra1ed4cf rcabf1aa  
    1919    public final static String ID = "Monotone";
    2020    public final static String VERSION = CoreVersion.VERSION;
    21     public final static long BUILD = 5;
     21    public final static long BUILD = 6;
    2222
    2323    /** for example "-test" */
Note: See TracChangeset for help on using the changeset viewer.