forked from I2P_Developers/i2p.i2p
* I2PTunnel HTTPServer:
New POST limiter
This commit is contained in:
203
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ConnThrottler.java
Normal file
203
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ConnThrottler.java
Normal file
@ -0,0 +1,203 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
* Count how often something happens with a particular peer and all peers.
|
||||
* This offers basic DOS protection but is not a complete solution.
|
||||
*
|
||||
* This is a little different from the one in streaming, in that the
|
||||
* ban time is different from the check time, and we keep a separate
|
||||
* map of throttled peers with individual time stamps.
|
||||
* The streaming version is lightweight but "sloppy" since it
|
||||
* uses a single time bucket for all.
|
||||
*
|
||||
* @since 0.9.9
|
||||
*/
|
||||
class ConnThrottler {
|
||||
private int _max;
|
||||
private int _totalMax;
|
||||
private long _checkPeriod;
|
||||
private long _throttlePeriod;
|
||||
private long _totalThrottlePeriod;
|
||||
private int _currentTotal;
|
||||
private final Map<Hash, Record> _peers;
|
||||
private long _totalThrottleUntil;
|
||||
private final String _action;
|
||||
private final Log _log;
|
||||
private final DateFormat _fmt;
|
||||
|
||||
/*
|
||||
* @param max per-peer, 0 for unlimited
|
||||
* @param totalMax for all peers, 0 for unlimited
|
||||
* @param period check window (ms)
|
||||
* @param throttlePeriod how long to ban a peer (ms)
|
||||
* @param totalThrottlePeriod how long to ban all peers (ms)
|
||||
* @param action just a name to note in the log
|
||||
*/
|
||||
public ConnThrottler(int max, int totalMax, long period,
|
||||
long throttlePeriod, long totalThrottlePeriod, String action, Log log) {
|
||||
updateLimits(max, totalMax, period, throttlePeriod, totalThrottlePeriod);
|
||||
_peers = new HashMap(4);
|
||||
_action = action;
|
||||
_log = log;
|
||||
// for logging
|
||||
_fmt = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
|
||||
String systemTimeZone = I2PAppContext.getGlobalContext().getProperty("i2p.systemTimeZone");
|
||||
if (systemTimeZone != null)
|
||||
_fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone));
|
||||
new Cleaner();
|
||||
}
|
||||
|
||||
/*
|
||||
* All periods in ms
|
||||
* @param max per-peer, 0 for unlimited
|
||||
* @param totalMax for all peers, 0 for unlimited
|
||||
* @since 0.9.3
|
||||
*/
|
||||
public synchronized void updateLimits(int max, int totalMax, long checkPeriod, long throttlePeriod, long totalThrottlePeriod) {
|
||||
_max = max;
|
||||
_totalMax = totalMax;
|
||||
_checkPeriod = Math.max(checkPeriod, 10*1000);
|
||||
_throttlePeriod = Math.max(throttlePeriod, 10*1000);
|
||||
_totalThrottlePeriod = Math.max(totalThrottlePeriod, 10*1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks both individual and total. Increments before checking.
|
||||
*/
|
||||
public synchronized boolean shouldThrottle(Hash h) {
|
||||
// all throttled already?
|
||||
if (_totalMax > 0) {
|
||||
if (_totalThrottleUntil > 0) {
|
||||
if (_totalThrottleUntil > Clock.getInstance().now())
|
||||
return true;
|
||||
_totalThrottleUntil = 0;
|
||||
}
|
||||
}
|
||||
// do this first, so we don't increment total if individual throttled
|
||||
if (_max > 0) {
|
||||
Record rec = _peers.get(h);
|
||||
if (rec != null) {
|
||||
// peer throttled already?
|
||||
if (rec.getUntil() > 0)
|
||||
return true;
|
||||
rec.increment();
|
||||
long now = Clock.getInstance().now();
|
||||
if (rec.countSince(now - _checkPeriod) > _max) {
|
||||
long until = now + _throttlePeriod;
|
||||
String date = _fmt.format(new Date(until));
|
||||
_log.logAlways(Log.WARN, "Throttling " + _action + " until " + date +
|
||||
" after exceeding max of " + _max +
|
||||
" in " + DataHelper.formatDuration(_checkPeriod) +
|
||||
": " + h.toBase64());
|
||||
rec.ban(until);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
_peers.put(h, new Record());
|
||||
}
|
||||
}
|
||||
if (_totalMax > 0 && ++_currentTotal > _totalMax) {
|
||||
if (_totalThrottleUntil == 0) {
|
||||
_totalThrottleUntil = Clock.getInstance().now() + _totalThrottlePeriod;
|
||||
String date = _fmt.format(new Date(_totalThrottleUntil));
|
||||
_log.logAlways(Log.WARN, "*** Throttling " + _action + " from ALL peers until " + date +
|
||||
" after exceeding max of " + _max +
|
||||
" in " + DataHelper.formatDuration(_checkPeriod));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* start over
|
||||
*/
|
||||
public synchronized void clear() {
|
||||
_currentTotal = 0;
|
||||
_totalThrottleUntil = 0;
|
||||
_peers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a list of seen times, and a ban-until time.
|
||||
* Caller must sync all methods.
|
||||
*/
|
||||
private static class Record {
|
||||
private final List<Long> times;
|
||||
private long until;
|
||||
|
||||
public Record() {
|
||||
times = new ArrayList(8);
|
||||
increment();
|
||||
}
|
||||
|
||||
/** Caller must synch */
|
||||
public int countSince(long time) {
|
||||
for (Iterator<Long> iter = times.iterator(); iter.hasNext(); ) {
|
||||
if (iter.next().longValue() < time)
|
||||
iter.remove();
|
||||
else
|
||||
break;
|
||||
}
|
||||
return times.size();
|
||||
}
|
||||
|
||||
/** Caller must synch */
|
||||
public void increment() {
|
||||
times.add(Long.valueOf(Clock.getInstance().now()));
|
||||
}
|
||||
|
||||
/** Caller must synch */
|
||||
public void ban(long untilTime) {
|
||||
until = untilTime;
|
||||
// don't need to save times if banned
|
||||
times.clear();
|
||||
}
|
||||
|
||||
/** Caller must synch */
|
||||
public long getUntil() {
|
||||
if (until < Clock.getInstance().now())
|
||||
until = 0;
|
||||
return until;
|
||||
}
|
||||
}
|
||||
|
||||
private class Cleaner extends SimpleTimer2.TimedEvent {
|
||||
/** schedules itself */
|
||||
public Cleaner() {
|
||||
super(SimpleTimer2.getInstance(), _checkPeriod);
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
synchronized(ConnThrottler.this) {
|
||||
if (_totalMax > 0)
|
||||
_currentTotal = 0;
|
||||
if (_max > 0 && !_peers.isEmpty()) {
|
||||
long then = Clock.getInstance().now() - _checkPeriod;
|
||||
for (Iterator<Record> iter = _peers.values().iterator(); iter.hasNext(); ) {
|
||||
Record rec = iter.next();
|
||||
if (rec.getUntil() <= 0 && rec.countSince(then) <= 0)
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
schedule(_checkPeriod);
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
@ -41,6 +42,18 @@ import net.i2p.data.Base32;
|
||||
*/
|
||||
public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
|
||||
/** all of these in SECONDS */
|
||||
public static final String OPT_POST_WINDOW = "postCheckTime";
|
||||
public static final String OPT_POST_BAN_TIME = "postBanTime";
|
||||
public static final String OPT_POST_TOTAL_BAN_TIME = "postTotalBanTime";
|
||||
public static final String OPT_POST_MAX = "maxPosts";
|
||||
public static final String OPT_POST_TOTAL_MAX = "maxTotalPosts";
|
||||
public static final int DEFAULT_POST_WINDOW = 5*60;
|
||||
public static final int DEFAULT_POST_BAN_TIME = 30*60;
|
||||
public static final int DEFAULT_POST_TOTAL_BAN_TIME = 10*60;
|
||||
public static final int DEFAULT_POST_MAX = 3;
|
||||
public static final int DEFAULT_POST_TOTAL_MAX = 10;
|
||||
|
||||
/** what Host: should we seem to be to the webserver? */
|
||||
private String _spoofHost;
|
||||
private static final String HASH_HEADER = "X-I2P-DestHash";
|
||||
@ -53,6 +66,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
private static final long TOTAL_HEADER_TIMEOUT = 2 * HEADER_TIMEOUT;
|
||||
private static final long START_INTERVAL = (60 * 1000) * 3;
|
||||
private long _startedOn = 0L;
|
||||
private ConnThrottler _postThrottler;
|
||||
|
||||
private final static byte[] ERR_UNAVAILABLE =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
@ -67,6 +81,19 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
"</body></html>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_DENIED =
|
||||
("HTTP/1.1 403 Denied\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Proxy-Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"<html><head><title>403 Denied</title></head>\n"+
|
||||
"<body><h2>403 Denied</h2>\n" +
|
||||
"<p>Denied due to excessive requests. Please try again later.</p>\n" +
|
||||
"</body></html>")
|
||||
.getBytes();
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, l, notifyThis, tunnel);
|
||||
setupI2PTunnelHTTPServer(spoofHost);
|
||||
@ -91,8 +118,57 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
@Override
|
||||
public void startRunning() {
|
||||
super.startRunning();
|
||||
_startedOn = getTunnel().getContext().clock().now();
|
||||
// Would be better if this was set when the inbound tunnel becomes alive.
|
||||
_startedOn = getTunnel().getContext().clock().now();
|
||||
setupPostThrottle();
|
||||
}
|
||||
|
||||
/** @since 0.9.9 */
|
||||
private void setupPostThrottle() {
|
||||
int pp = getIntOption(OPT_POST_MAX, 0);
|
||||
int pt = getIntOption(OPT_POST_TOTAL_MAX, 0);
|
||||
synchronized(this) {
|
||||
if (pp != 0 || pp != 0 || _postThrottler != null) {
|
||||
long pw = 1000L * getIntOption(OPT_POST_WINDOW, DEFAULT_POST_WINDOW);
|
||||
long pb = 1000L * getIntOption(OPT_POST_BAN_TIME, DEFAULT_POST_BAN_TIME);
|
||||
long px = 1000L * getIntOption(OPT_POST_TOTAL_BAN_TIME, DEFAULT_POST_TOTAL_BAN_TIME);
|
||||
if (_postThrottler == null)
|
||||
_postThrottler = new ConnThrottler(pp, pt, pw, pb, px, "POST", _log);
|
||||
else
|
||||
_postThrottler.updateLimits(pp, pt, pw, pb, px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.9.9 */
|
||||
private int getIntOption(String opt, int dflt) {
|
||||
Properties opts = getTunnel().getClientOptions();
|
||||
String o = opts.getProperty(opt);
|
||||
if (o != null) {
|
||||
try {
|
||||
return Integer.parseInt(o);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
return dflt;
|
||||
}
|
||||
|
||||
/** @since 0.9.9 */
|
||||
@Override
|
||||
public boolean close(boolean forced) {
|
||||
synchronized(this) {
|
||||
if (_postThrottler != null)
|
||||
_postThrottler.clear();
|
||||
}
|
||||
return super.close(forced);
|
||||
}
|
||||
|
||||
/** @since 0.9.9 */
|
||||
@Override
|
||||
public void optionsUpdated(I2PTunnel tunnel) {
|
||||
if (getTunnel() != tunnel)
|
||||
return;
|
||||
setupPostThrottle();
|
||||
super.optionsUpdated(tunnel);
|
||||
}
|
||||
|
||||
|
||||
@ -102,9 +178,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
*/
|
||||
@Override
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
Hash peerHash = socket.getPeerDestination().calculateHash();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Incoming connection to '" + toString() + "' port " + socket.getLocalPort() +
|
||||
" from: " + socket.getPeerDestination().calculateHash() + " port " + socket.getPort());
|
||||
" from: " + peerHash + " port " + socket.getPort());
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
@ -119,9 +196,27 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
Map<String, List<String>> headers = readHeaders(in, command,
|
||||
CLIENT_SKIPHEADERS, getTunnel().getContext());
|
||||
long afterHeaders = getTunnel().getContext().clock().now();
|
||||
|
||||
if (_postThrottler != null &&
|
||||
command.length() >= 5 &&
|
||||
command.substring(0, 5).toUpperCase(Locale.US).equals("POST ")) {
|
||||
if (_postThrottler.shouldThrottle(peerHash)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Refusing POST since peer is throttled: " + peerHash.toBase64());
|
||||
try {
|
||||
// Send a 503, so the user doesn't get an HTTP Proxy error message
|
||||
// and blame his router or the network.
|
||||
socket.getOutputStream().write(ERR_DENIED);
|
||||
} catch (IOException ioe) {}
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addEntry(headers, HASH_HEADER, socket.getPeerDestination().calculateHash().toBase64());
|
||||
addEntry(headers, DEST32_HEADER, Base32.encode(socket.getPeerDestination().calculateHash().getData()) + ".b32.i2p");
|
||||
addEntry(headers, HASH_HEADER, peerHash.toBase64());
|
||||
addEntry(headers, DEST32_HEADER, Base32.encode(peerHash.getData()) + ".b32.i2p");
|
||||
addEntry(headers, DEST64_HEADER, socket.getPeerDestination().toBase64());
|
||||
|
||||
// Port-specific spoofhost
|
||||
|
@ -21,6 +21,7 @@ import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPServer;
|
||||
import net.i2p.i2ptunnel.I2PTunnelIRCClient;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
@ -274,6 +275,30 @@ public class EditBean extends IndexBean {
|
||||
return getProperty(tunnel, PROP_MAX_STREAMS, "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* POST limits
|
||||
* @since 0.9.9
|
||||
*/
|
||||
public String getPostMax(int tunnel) {
|
||||
return getProperty(tunnel, I2PTunnelHTTPServer.OPT_POST_MAX, "0");
|
||||
}
|
||||
|
||||
public String getPostTotalMax(int tunnel) {
|
||||
return getProperty(tunnel, I2PTunnelHTTPServer.OPT_POST_TOTAL_MAX, "0");
|
||||
}
|
||||
|
||||
public int getPostCheckTime(int tunnel) {
|
||||
return getProperty(tunnel, I2PTunnelHTTPServer.OPT_POST_WINDOW, I2PTunnelHTTPServer.DEFAULT_POST_WINDOW) / 60;
|
||||
}
|
||||
|
||||
public int getPostBanTime(int tunnel) {
|
||||
return getProperty(tunnel, I2PTunnelHTTPServer.OPT_POST_BAN_TIME, I2PTunnelHTTPServer.DEFAULT_POST_BAN_TIME) / 60;
|
||||
}
|
||||
|
||||
public int getPostTotalBanTime(int tunnel) {
|
||||
return getProperty(tunnel, I2PTunnelHTTPServer.OPT_POST_TOTAL_BAN_TIME, I2PTunnelHTTPServer.DEFAULT_POST_TOTAL_BAN_TIME) / 60;
|
||||
}
|
||||
|
||||
private int getProperty(int tunnel, String prop, int def) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
|
@ -30,6 +30,7 @@ import net.i2p.data.SessionKey;
|
||||
import net.i2p.i2ptunnel.I2PTunnelConnectClient;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPServer;
|
||||
import net.i2p.i2ptunnel.I2PTunnelIRCClient;
|
||||
import net.i2p.i2ptunnel.I2PTunnelServer;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
@ -809,7 +810,7 @@ public class IndexBean {
|
||||
public void setReduceTime(String val) {
|
||||
if (val != null) {
|
||||
try {
|
||||
_otherOptions.put("i2cp.reduceIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
|
||||
_otherOptions.put("i2cp.reduceIdleTime", Integer.toString(Integer.parseInt(val.trim()) * 60*1000));
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
}
|
||||
@ -835,7 +836,7 @@ public class IndexBean {
|
||||
public void setCloseTime(String val) {
|
||||
if (val != null) {
|
||||
try {
|
||||
_otherOptions.put("i2cp.closeIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
|
||||
_otherOptions.put("i2cp.closeIdleTime", Integer.toString(Integer.parseInt(val.trim()) * 60*1000));
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
}
|
||||
@ -914,6 +915,35 @@ public class IndexBean {
|
||||
_otherOptions.put(PROP_MAX_STREAMS, s.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* POST limits
|
||||
* @since 0.9.9
|
||||
*/
|
||||
public void setPostMax(String s) {
|
||||
if (s != null)
|
||||
_otherOptions.put(I2PTunnelHTTPServer.OPT_POST_MAX, s.trim());
|
||||
}
|
||||
|
||||
public void setPostTotalMax(String s) {
|
||||
if (s != null)
|
||||
_otherOptions.put(I2PTunnelHTTPServer.OPT_POST_TOTAL_MAX, s.trim());
|
||||
}
|
||||
|
||||
public void setPostCheckTime(String s) {
|
||||
if (s != null)
|
||||
_otherOptions.put(I2PTunnelHTTPServer.OPT_POST_WINDOW, Integer.toString(Integer.parseInt(s.trim()) * 60));
|
||||
}
|
||||
|
||||
public void setPostBanTime(String s) {
|
||||
if (s != null)
|
||||
_otherOptions.put(I2PTunnelHTTPServer.OPT_POST_BAN_TIME, Integer.toString(Integer.parseInt(s.trim()) * 60));
|
||||
}
|
||||
|
||||
public void setPostTotalBanTime(String s) {
|
||||
if (s != null)
|
||||
_otherOptions.put(I2PTunnelHTTPServer.OPT_POST_TOTAL_BAN_TIME, Integer.toString(Integer.parseInt(s.trim()) * 60));
|
||||
}
|
||||
|
||||
/** params needed for hashcash and dest modification */
|
||||
public void setEffort(String val) {
|
||||
if (val != null) {
|
||||
@ -1124,6 +1154,9 @@ public class IndexBean {
|
||||
} else if ("httpserver".equals(_type) || "httpbidirserver".equals(_type)) {
|
||||
if (_spoofedHost != null)
|
||||
config.setProperty("spoofedHost", _spoofedHost);
|
||||
for (String p : _httpServerOpts)
|
||||
if (_otherOptions.containsKey(p))
|
||||
config.setProperty("option." + p, _otherOptions.get(p));
|
||||
}
|
||||
if ("httpbidirserver".equals(_type)) {
|
||||
if (_port != null)
|
||||
@ -1180,6 +1213,13 @@ public class IndexBean {
|
||||
PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY,
|
||||
PROP_MAX_STREAMS
|
||||
};
|
||||
private static final String _httpServerOpts[] = {
|
||||
I2PTunnelHTTPServer.OPT_POST_WINDOW,
|
||||
I2PTunnelHTTPServer.OPT_POST_BAN_TIME,
|
||||
I2PTunnelHTTPServer.OPT_POST_TOTAL_BAN_TIME,
|
||||
I2PTunnelHTTPServer.OPT_POST_MAX,
|
||||
I2PTunnelHTTPServer.OPT_POST_TOTAL_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* do NOT add these to noShoOpts, we must leave them in for HTTPClient and ConnectCLient
|
||||
@ -1190,7 +1230,7 @@ public class IndexBean {
|
||||
"proxyUsername", "proxyPassword"
|
||||
};
|
||||
|
||||
protected static final Set _noShowSet = new HashSet(64);
|
||||
protected static final Set _noShowSet = new HashSet(128);
|
||||
protected static final Set _nonProxyNoShowSet = new HashSet(4);
|
||||
static {
|
||||
_noShowSet.addAll(Arrays.asList(_noShowOpts));
|
||||
@ -1199,6 +1239,7 @@ public class IndexBean {
|
||||
_noShowSet.addAll(Arrays.asList(_booleanServerOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_otherClientOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_otherServerOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_httpServerOpts));
|
||||
_nonProxyNoShowSet.addAll(Arrays.asList(_otherProxyOpts));
|
||||
}
|
||||
|
||||
|
@ -425,7 +425,41 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
<% if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) {
|
||||
%><div class="rowItem">
|
||||
<div id="optionsField" class="rowItem">
|
||||
<label><%=intl._("POST limits (0=unlimited)")%><br /><%=intl._("Per client")%>:</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label><%=intl._("Per period")%>:</label>
|
||||
<input type="text" id="port" name="postMax" value="<%=editBean.getPostMax(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label><%=intl._("Ban minutes")%>:</label>
|
||||
<input type="text" id="port" name="postBanTime" value="<%=editBean.getPostBanTime(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<div id="optionsField" class="rowItem">
|
||||
<label><%=intl._("Total")%>:</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<input type="text" id="port" name="postTotalMax" value="<%=editBean.getPostTotalMax(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<input type="text" id="port" name="postTotalBanTime" value="<%=editBean.getPostTotalBanTime(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<div id="optionsField" class="rowItem">
|
||||
<label><%=intl._("POST limit period (minutes)")%>:</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<input type="text" id="port" name="postCheckTime" value="<%=editBean.getPostCheckTime(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
</div>
|
||||
<% } // httpserver
|
||||
%><div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
@ -1,4 +1,8 @@
|
||||
2013-10-26 zzz
|
||||
* I2PTunnel HTTP server: New POST limiter
|
||||
|
||||
2013-10-25 zzz
|
||||
* Router: Only log ping file error once (ticket #1086)
|
||||
* Streaming:
|
||||
- Check blacklist/whitelist before connection limits, so
|
||||
a blacklisted peer does not increment the counters
|
||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
||||
/** deprecated */
|
||||
public final static String ID = "Monotone";
|
||||
public final static String VERSION = CoreVersion.VERSION;
|
||||
public final static long BUILD = 10;
|
||||
public final static long BUILD = 11;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
|
Reference in New Issue
Block a user