jetty的org.eclipse.jetty.servlets.DoSFilter类是用来实现Dos攻击预防的filter,里面涉及到一些变量,先了解下变量的含义:
protected long _delayMs;超过最大处理请求数当前请求的等待时间,-1立即拒绝,0,无限等待,正数表达等待的毫秒数
protected long _throttleMs;异步等待获取信号量的时间
protected long _maxWaitMs;阻塞等待获取信号量的时间
protected long _maxRequestMs;请求处理最大时间限制
protected long _maxIdleTrackerMs;跟踪连接是否断开的最大等待时间
protected int _throttledRequests;允许在等待队列中等待获取信号量的请求数
protected int _maxRequestsPerSec; 每秒允许处理最多的请求数,超过将延迟,异步等待。
protected boolean _insertHeaders; 是否往response写入dosfilter信息,默认true
protected boolean _trackSessions;是否根据session来检测dos攻击,默认true
protected boolean _remotePort;是否根据ip+port来检测dos攻击,默认false
protected String _whitelistStr; 白名单 ip白名单列表,这些通过都通过配置servlet的init-p
aram可以来重新设置。
首先看看init方法的初始化设置:
public void init(FilterConfig filterConfig)
{
_context = filterConfig.getServletContext();
_queue = new Queue[getMaxPriority() + 1];
_listener = new ContinuationListener[getMaxPriority() + 1];
for (int p = 0; p < _queue.length; p++)
{
_queue[p] = new ConcurrentLinkedQueue<Continuation>();
final int priority=p;
_listener[p] = new ContinuationListener()
{
public void onComplete(Continuation continuation)
{
}
public void onTimeout(Continuation continuation)
{
_queue[priority].remove(continuation);
}
};
}
_rateTrackers.clear();
int baseRateLimit = __DEFAULT_MAX_REQUESTS_PER_SEC;
if (filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM) != null)
baseRateLimit = Integer.parseInt(filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM));
_maxRequestsPerSec = baseRateLimit;
long delay = __DEFAULT_DELAY_MS;
if (filterConfig.getInitParameter(DELAY_MS_INIT_PARAM) != null)
delay = Integer.parseInt(filterConfig.getInitParameter(DELAY_MS_INIT_PARAM));
_delayMs = delay;
int throttledRequests = __DEFAULT_THROTTLE;
if (filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM) != null)
throttledRequests = Integer.parseInt(filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM));
_passes = new Semaphore(throttledRequests,true);
_throttledRequests = throttledRequests;
long wait = __DEFAULT_WAIT_MS;
if (filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM) != null)
wait = Integer.parseInt(filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM));
_maxWaitMs = wait;
long suspend = __DEFAULT_THROTTLE_MS;
if (filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM) != null)
suspend = Integer.parseInt(filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM));
_throttleMs = suspend;
long maxRequestMs = __DEFAULT_MAX_REQUEST_MS_INIT_PARAM;
if (filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM) != null )
maxRequestMs = Long.parseLong(filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM));
_maxRequestMs = maxRequestMs;
long maxIdleTrackerMs = __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM;
if (filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM) != null )
maxIdleTrackerMs = Long.parseLong(filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM));
_maxIdleTrackerMs = maxIdleTrackerMs;
_whitelistStr = "";
if (filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM) !=null )
_whitelistStr = filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM);
initWhitelist();
String tmp = filterConfig.getInitParameter(INSERT_HEADERS_INIT_PARAM);
_insertHeaders = tmp==null || Boolean.parseBoolean(tmp);
tmp = filterConfig.getInitParameter(TRACK_SESSIONS_INIT_PARAM);
_trackSessions = tmp==null || Boolean.parseBoolean(tmp);
tmp = filterConfig.getInitParameter(REMOTE_PORT_INIT_PARAM);
_remotePort = tmp!=null&& Boolean.parseBoolean(tmp);
_requestTimeoutQ.setNow();
_requestTimeoutQ.setDuration(_maxRequestMs);
_trackerTimeoutQ.setNow();
_trackerTimeoutQ.setDuration(_maxIdleTrackerMs);
_running=true;
_timerThread = (new Thread()
{
public void run()
{
try
{
while (_running)
{
long now;
synchronized (_requestTimeoutQ)
{
now = _requestTimeoutQ.setNow();
_requestTimeoutQ.tick();
}
synchronized (_trackerTimeoutQ)
{
_trackerTimeoutQ.setNow(now);
_trackerTimeoutQ.tick();
}
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
Log.ignore(e);
}
}
}
finally
{
Log.info("DoSFilter timer exited");
}
}
});
_timerThread.start();
if (_context!=null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
_context.setAttribute(filterConfig.getFilterName(),this);
}
_queue是用来保存当前当前通过header,session,ip等检测类型的请求队列,然后就是一堆参数的初始化设置,最后
又有两个queue _requestTimeoutQ是来保存每个请求的处理时间超时检测的队列;_trackerTimeoutQ是来检测请求对应连接是否已经关闭超时的检测队列;最后启动一个_timerThread线程来进行这两个队列的超时检测。
然后再看下dofilter的处理
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterchain) throws IOException, ServletException
{
final HttpServletRequest srequest = (HttpServletRequest)request;
final HttpServletResponse sresponse = (HttpServletResponse)response;
final long now=_requestTimeoutQ.getNow();
// Look for the rate tracker for this request
RateTracker tracker = (RateTracker)request.getAttribute(__TRACKER);
if (tracker==null) //如果request没有进行过dosfilter的处理
{
// This is the first time we have seen this request.
// get a rate tracker associated with this request, and record one hit
tracker = getRateTracker(request); //根据request生成dos跟踪的对象,看下面方法
// Calculate the rate and check it is over the allowed limit
final boolean overRateLimit = tracker.isRateExceeded(now);//判断是否已经超过每秒最大处理请求数
// pass it through if we are not currently over the rate limit
if (!overRateLimit)//如果没有超过,则正常处理
{
doFilterChain(filterchain,srequest,sresponse);
return;
}
// We are over the limit.
Log.warn("DOS ALERT: ip="+srequest.getRemoteAddr()+",session="+srequest.getRequestedSessionId()+",user="+srequest.getUserPrincipal());
// So either reject it, delay it or throttle it
switch((int)_delayMs) //根据当前配置的延时时间
{
case -1: //如果为-1,则直接拒绝
{
// Reject this request
if (_insertHeaders) //是否把dosfilter处理插入response的header
((HttpServletResponse)response).addHeader("DoSFilter","unavailable");
((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return;
}
case 0: //如果为0,则当前线程继续等待处理,
{
// fall through to throttle code
request.setAttribute(__TRACKER,tracker);
break;
}
default://其他字段,则设置request的timeout时间为_delayMs,并且挂起当前线程,返回
{
// insert a delay before throttling the request
if (_insertHeaders)
((HttpServletResponse)response).addHeader("DoSFilter","delayed");
Continuation continuation = ContinuationSupport.getContinuation(request);
request.setAttribute(__TRACKER,tracker);
if (_delayMs > 0)
continuation.setTimeout(_delayMs);
continuation.suspend();
return;
}
}
}
//_delayMs为0是,当前请求继续等待
// Throttle the request
boolean accepted = false;
try
{
// check if we can afford to accept another request at this time
accepted = _passes.tryAcquire(_maxWaitMs,TimeUnit.MILLISECONDS);//判断当前处理请求队列是否已经有处理完的请求,处理完的会释放信号量,则当前请求线程可以获取信号量
if (!accepted) //如果不能获取,则把当前请求设置为异步等待,异步等待的时间为_throttleMs,
{
// we were not accepted, so either we suspend to wait,or if we were woken up we insist or we fail
final Continuation continuation = ContinuationSupport.getContinuation(request);
Boolean throttled = (Boolean)request.getAttribute(__THROTTLED);
if (throttled!=Boolean.TRUE && _throttleMs>0)
{
int priority = getPriority(request,tracker);
request.setAttribute(__THROTTLED,Boolean.TRUE);
if (_insertHeaders)
((HttpServletResponse)response).addHeader("DoSFilter","throttled");
if (_throttleMs > 0)
continuation.setTimeout(_throttleMs);
continuation.suspend();
continuation.addContinuationListener(_listener[priority]);
_queue[priority].add(continuation);
return;
}
// else were we resumed?
else if (request.getAttribute("javax.servlet.resumed")==Boolean.TRUE)
{//如果线程中心被唤醒,则可以获取到信号量,
// we were resumed and somebody stole our pass, so we wait for the next one.
_passes.acquire();
accepted = true;
}
}
// if we were accepted (either immediately or after throttle)
if (accepted) //获取到了,继续执行,
// call the chain
doFilterChain(filterchain,srequest,sresponse);
else
{
// fail the request
if (_insertHeaders)
((HttpServletResponse)response).addHeader("DoSFilter","unavailable");
((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
}
catch (InterruptedException e)
{
_context.log("DoS",e);
((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
finally
{
if (accepted) //执行完了之后释放当前的信号量,唤醒等待队列中的第一个请求进行处理
{
// wake up the next highest priority request.
for (int p = _queue.length; p-- > 0;)
{
Continuation continuation = _queue[p].poll();
if (continuation != null && continuation.isSuspended())
{
continuation.resume();
break;
}
}
_passes.release();
}
}
}
public RateTracker getRateTracker(ServletRequest request)
{
HttpServletRequest srequest = (HttpServletRequest)request;
HttpSession session=srequest.getSession(false);
String loadId = extractUserId(request);
final int type;
if (loadId != null)
{
type = USER_AUTH;
}
else
{
if (_trackSessions && session!=null && !session.isNew())
{
loadId=session.getId();
type = USER_SESSION;
}
else
{
loadId = _remotePort?(request.getRemoteAddr()+request.getRemotePort()):request.getRemoteAddr();
type = USER_IP;
}
}
RateTracker tracker=_rateTrackers.get(loadId);
if (tracker==null)
{
RateTracker t;
if (_whitelist.contains(request.getRemoteAddr())) //如果在白名单中,则isRateExceeded一直返回false
{
t = new FixedRateTracker(loadId,type,_maxRequestsPerSec);
}
else
{
t = new RateTracker(loadId,type,_maxRequestsPerSec);
}
tracker=_rateTrackers.putIfAbsent(loadId,t);
if (tracker==null)
tracker=t;
if (type == USER_IP)
{
// USER_IP expiration from _rateTrackers is handled by the _trackerTimeoutQ
synchronized (_trackerTimeoutQ)
{
_trackerTimeoutQ.schedule(tracker);
}
}
else if (session!=null)
// USER_SESSION expiration from _rateTrackers are handled by the HttpSessionBindingListener
session.setAttribute(__TRACKER,tracker);
}
return tracker;
}