Giter Club home page Giter Club logo

mvcthrottle's Introduction

MvcThrottle

Build status NuGet

With MvcThrottle you can protect your site from aggressive crawlers, scraping tools or unwanted traffic spikes originated from the same location by limiting the rate of requests that a client from the same IP can make to your site or to specific routes.

You can set multiple limits for different scenarios like allowing an IP to make a maximum number of calls per second, per minute, per hour or per day. You can define these limits to address all requests made to your website or you can scope the limits to each Controller, Action or URL, with or without query string params.

Global throttling based on IP

The setup bellow will limit the number of requests originated from the same IP. If from the same IP, in same second, you'll make a call to home/index and home/about the last call will get blocked.

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        var throttleFilter = new ThrottlingFilter
        {
            Policy = new ThrottlePolicy(perSecond: 1, perMinute: 10, perHour: 60 * 10, perDay: 600 * 10)
            {
                IpThrottling = true
            },
            Repository = new CacheRepository()
        };

        filters.Add(throttleFilter);
    }
}

In order to enable throttling you'll have to decorate your Controller or Action with EnableThrottlingAttribute, if you want to exclude a certain Action you can apply DisableThrottlingAttribute.

[EnableThrottling]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [DisableThrottling]
    public ActionResult About()
    {
        return View();
    }
}

You can define custom limits using the EnableThrottling attribute, these limits will override the default ones.

[EnableThrottling(PerSecond = 2, PerMinute = 10, PerHour = 30, PerDay = 300)]
public ActionResult Index()
{
    return View();
}

Endpoint throttling based on IP

If, from the same IP, in the same second, you'll make two calls to home/index, the last call will get blocked. But if in the same second you call home/about too, the request will go through because it's a different route.

var throttleFilter = new ThrottlingFilter
{
    Policy = new ThrottlePolicy(perSecond: 1, perMinute: 10, perHour: 60 * 10, perDay: 600 * 10)
    {
        IpThrottling = true,
        EndpointThrottling = true,
        EndpointType = EndpointThrottlingType.ControllerAndAction
    },
    Repository = new CacheRepository()
};

Using the ThrottlePolicy.EndpointType property you can chose how the throttle key gets compose.

public enum EndpointThrottlingType
{
    AbsolutePath = 1,
    PathAndQuery,
    ControllerAndAction,
    Controller
}

Customizing the rate limit response

By default, when a client is rate limited a 429 HTTP status code is sent back along with Retry-After header. If you want to return a custom view instead of IIS error page you’ll need to implement your own ThrottlingFilter and override the QuotaExceededResult method.

public class MvcThrottleCustomFilter : MvcThrottle.ThrottlingFilter
{
    protected override ActionResult QuotaExceededResult(RequestContext context, string message, HttpStatusCode responseCode)
    {
        var rateLimitedView = new ViewResult
        {
            ViewName = "RateLimited"
        };
        rateLimitedView.ViewData["Message"] = message;

        return rateLimitedView;
    }
}

I’ve created a view named RateLimited.cshtml located in the Views/Shared folder and using ViewBag.Message I am sending the error message to this view. Take a look at MvcThrottle.Demo project for the full implementation.

IP, Endpoint and Client White-listing

If requests are initiated from a white-listed IP or to a white-listed URL, then the throttling policy will not be applied and the requests will not get stored. The IP white-list supports IP v4 and v6 ranges like "192.168.0.0/24", "fe80::/10" and "192.168.0.0-192.168.0.255" for more information check jsakamoto/ipaddressrange.

var throttleFilter = new ThrottlingFilter
{
	Policy = new ThrottlePolicy(perSecond: 2, perMinute: 60)
	{
		IpThrottling = true,
		IpWhitelist = new List<string> { "::1", "192.168.0.0/24" },
		
		EndpointThrottling = true,
		EndpointType = EndpointThrottlingType.ControllerAndAction,
		EndpointWhitelist = new List<string> { "Home/Index" },
		
		ClientThrottling = true,
		//white list authenticated users
		ClientWhitelist = new List<string> { "auth" }
	},
	Repository = new CacheRepository()
});

The Demo project comes with a white-list of Google and Bing bot IPs, take at look at FilterConfig.cs.

IP and/or Endpoint custom rate limits

You can define custom limits for known IPs and endpoint, these limits will override the default ones. Be aware that a custom limit will only work if you have defined a global counterpart. You can define endpoint rules by providing relative routes like Home/Index or just a URL segment like /About/. The endpoint throttling engine will search for the expression you've provided in the absolute URI, if the expression is contained in the request route then the rule will be applied. If two or more rules match the same URI then the lower limit will be applied.

var throttleFilter = new ThrottlingFilter
{
	Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200, perDay: 1500)
	{
		IpThrottling = true,
		IpRules = new Dictionary<string, RateLimits>
		{ 
			{ "192.168.1.1", new RateLimits { PerSecond = 2 } },
			{ "192.168.2.0/24", new RateLimits { PerMinute = 30, PerHour = 30*60, PerDay = 30*60*24 } }
		},
		
		EndpointThrottling = true,
		EndpointType = EndpointThrottlingType.ControllerAndAction,
		EndpointRules = new Dictionary<string, RateLimits>
		{ 
			{ "Home/Index", new RateLimits { PerMinute = 40, PerHour = 400 } },
			{ "Home/About", new RateLimits { PerDay = 2000 } }
		}
	},
	Repository = new CacheRepository()
});

User-Agent rate limiting

You can define custom limits for known User-Agents or event white-list them, these limits will override the default ones.

var throttleFilter = new ThrottlingFilter
{
	Policy = new ThrottlePolicy(perSecond: 5, perMinute: 20, perHour: 200, perDay: 1500)
	{
		IpThrottling = true,
		EndpointThrottling = true,
		EndpointType = EndpointThrottlingType.AbsolutePath,

		UserAgentThrottling = true,
		UserAgentWhitelist = new List<string>
		{
			"Googlebot",
			"Mediapartners-Google",
			"AdsBot-Google",
			"Bingbot",
			"YandexBot",
			"DuckDuckBot"
		},
		UserAgentRules = new Dictionary<string, RateLimits>
		{
			{"Slurp", new RateLimits { PerMinute = 1 }},
			{"Sogou", new RateLimits { PerHour = 1 } }
		}
	},
	Repository = new CacheRepository()
});

The above setup will allow the Sogou bot to crawl each URL once every hour while Google, Bing, Yandex and DuckDuck will not get rate limited at all. Any other bot that is not present in the setup will be rate limited based on the global rules defined in the ThrottlePolicy constuctor.

Stack rejected requests

By default, rejected calls are not added to the throttle counter. If a client makes 3 requests per second and you've set a limit of one call per second, the minute, hour and day counters will only record the first call, the one that wasn't blocked. If you want rejected requests to count towards the other limits, you'll have to set StackBlockedRequests to true.

var throttleFilter = new ThrottlingFilter
{
	Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30)
	{
		IpThrottling = true,
		EndpointThrottling = true,
		StackBlockedRequests = true
	},
	Repository = new CacheRepository()
});

Storing throttle metrics

MvcThrottle stores all request data in-memory using ASP.NET Cache. If you want to change the storage to Velocity, MemCache or Redis, all you have to do is create your own repository by implementing the IThrottleRepository interface.

public interface IThrottleRepository
{
	bool Any(string id);
	
	ThrottleCounter? FirstOrDefault(string id);
	
	void Save(string id, ThrottleCounter throttleCounter, TimeSpan expirationTime);
	
	void Remove(string id);
	
	void Clear();
}

Logging throttled requests

If you want to log throttled requests you'll have to implement IThrottleLogger interface and provide it to the ThrottlingFilter.

public interface IThrottleLogger
{
	void Log(ThrottleLogEntry entry);
}

Logging implementation example

public class MvcThrottleLogger : IThrottleLogger
{
    public void Log(ThrottleLogEntry entry)
    {
        Debug.WriteLine("{0} Request {1} from {2} has been blocked, quota {3}/{4} exceeded by {5}",
            entry.LogDate, entry.RequestId, entry.ClientIp, entry.RateLimit, entry.RateLimitPeriod, entry.TotalRequests);
    }
}

Logging usage example

var throttleFilter = new ThrottlingFilter
{
	Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30)
	{
		IpThrottling = true,
		EndpointThrottling = true
	},
	Repository = new CacheRepository(),
	Logger = new DebugThrottleLogger()
});

mvcthrottle's People

Contributors

georgehemmings avatar maultasche avatar natelowry avatar nicholashead avatar stefanprodan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mvcthrottle's Issues

globally enable throttling

Is it possible to globally set the EnableThrottling attribute?

I wouldn't want to decorate each of the controllers with the attribute but just apply the same rules for each controller/action.

System.FormatException thrown by poorly formatted HTTP_X_FORWARDED_FOR

If a client doesn't properly format the HTTP_X_FORWARDED_FOR request header MvcThrottle.IpAddressParser.ParseIp will throw a System.FormatException.

In these cases, we would rather it just use the IP address of the request. While it's true this is an error on the client's part, if the request is otherwise valid we'd still like to serve something to the user.

I'd suggest returning IPAddress.None:

            IPAddress parsedIP;
            if (IPAddress.TryParse(ipAddress, out parsedIP))
            {
                return parsedIP;
            } else
            {
                return IPAddress.None;
            }

DisableThrottling on Class

Me again 😺

The DisableThrottlingAttribute is checked at the Action level, but not on the Class. Is there a reason for this? We have some base classes that have it enabled and want to disable it on specific inherited classes. If not, maybe mark that attribute as Method only? Edit: can't do that with ActionFilterAttribute apparently

I think the fix would be to ThrottlingFilter.cs:352 here:

            if (filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(DisableThrottlingAttribute), true))
            {
                applyThrottling = false;
            }

I'd be happy to PR that if it helps.

We just pushed this into production and are much <3

IP usage flawed for some scenarios

Just a recommendation... I would not limit throttling to the to the IP address. In scenarios where there are multiple clients sending in a single key, this scenario does not work at all. For our product we issue a key and then people stick a tracking script on their website, so the requests end up coming from numerous ip addresses but all using a single key.

How to use it to API

Can't convert MvcThrottle.ThrottlingFilter” to "System.Web.Http.Filters.IFilter"

Issue 429 instead of 409.

RFC 6585 has added the HTTP status code 429 Too Many Requests, which seems more appropriate as a default than 409.

From the spec:

The 429 status code indicates that the user has sent too many
requests in a given amount of time ("rate limiting").

The response representations SHOULD include details explaining the
condition, and MAY include a Retry-After header indicating how long
to wait before making a new request.

I'd be awesome if MvcThrottle supported the Retry-After header as well.

Defaults vs custom values in attribute

I don't understand how the default limits are used.

I always throttle specific actions, using the attribute. I don't do this globally. I want to define custom limits every time I use the attribute e.g. [EnableThrottling(PerFoo=123)].

But there are also defaults set in the global filter. I wish I didn't need to specify defaults, but the constructor wants them. Because of this my limits often conflict and I get unexpected results.

So is it safe to do something like this:

... new ThrottlingFilter( ...
    Policy = new ThrottlePolicy(perSecond:0, perMinute:0, perHour:0, perDay:0, perWeek:0) {
    ...

...and then set my real values on the attribute? I noticed that some of these override the others.

Identity ClientIP Including Port

It seems the SetIdentity method includes the port that the client has made the request from. So the ClientIP would look like this 192.168.1.1:32456. This port will change with different requests, especially when requests are coming from scrapers/spiders. This means the request gets a different identity key so previous requests are ignored, meaning the rate limit isn’t applied.

There may be a more obvious way to fix this issue? But my solution was to add the following to my custom filter implementation.

protected override RequestIdentity SetIdentity(HttpRequestBase request)
 {
            var identity = base.SetIdentity(request);
            identity.ClientIp = identity.ClientIp.Substring(0, identity.ClientIp.LastIndexOf(":"));

            return identity;
 }

Requests always stacking

The readme describes the default stacking policy "If a client makes 3 requests per second and you've set a limit of one call per second, the minute, hour and day counters will only record the first call, the one that wasn't blocked."

This isn't the behaviour I'm seeing.

I think this commit may have broken this feature: 0f60a5a

As an exception is no longer thrown once a rate has been exceeded, it goes on to evaluate all the others rates also.

Should it just return after filterContext.Result is set?

Null user agent exceptions

With user agent throttling there is one more place where a null user agent check should be made to prevent null reference exceptions.

In ThrottlingFilter.cs:

//apply custom rate limit for user agent
if (Policy.UserAgentRules != null)

can be changed to:

//apply custom rate limit for user agent
if (Policy.UserAgentRules != null && identity.UserAgent != null)

to prevent the null reference exceptions

IndexOutOfRangeException when using slash to define IP range

Exception is thrown in BeginExecute method. Relevant stack trace:

[IndexOutOfRangeException: Index was outside the bounds of the array.]
   MvcThrottle.Bits.GetBitMask(Int32 sizeOfBuff, Int32 bitLen) +165
   MvcThrottle.IPAddressRange..ctor(String ipRangeString) +385
   MvcThrottle.IpAddressParser.ContainsIp(List`1 ipRules, String clientIp) +256
   MvcThrottle.ThrottlingFilter.IsWhitelisted(RequestIdentity requestIdentity) +333
   MvcThrottle.ThrottlingFilter.OnActionExecuting(ActionExecutingContext filterContext) +327
   System.Web.Mvc.Async.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex) +176
   System.Web.Mvc.Async.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex) +651
   System.Web.Mvc.Async.<>c__DisplayClass33.<BeginInvokeActionMethodWithFilters>b__31(AsyncCallback asyncCallback, Object asyncState) +58
   System.Web.Mvc.Async.WrappedAsyncResult`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +14
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters, AsyncCallback callback, Object state) +197
   System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__19(AsyncCallback asyncCallback, Object asyncState) +743
   System.Web.Mvc.Async.WrappedAsyncResult`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +14
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state) +343
   System.Web.Mvc.Controller.<BeginExecuteCore>b__1c(AsyncCallback asyncCallback, Object asyncState, ExecuteCoreState innerState) +25
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +30
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +465
   System.Web.Mvc.Controller.<BeginExecute>b__14(AsyncCallback asyncCallback, Object callbackState, Controller controller) +18
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +20
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +374

Range seems to work fine with - delimiter.

Adding honeyPot and reCaptcha as attributes

Hello, great project ⚙ 👍

I wanted to suggest if I may

  • add, the honeyPot list , sample project
  • add, google recpatcha as a filter, and also update the build to target 4.5.2 and 4.6.2
[EnableRecaptcha(ifThrottlePerSecondexceeds = 2, PerMinute = 10, PerHour = 30, PerDay = 300)]
public ActionResult login()
{
    return View();
}

Thanks

Use with ServiceStack?

I really like this - do you have any idea if I can use this to decorate my ServiceStack classes and/or services to leverage this?

Get correct client IP address from X-Forwarded-For list

https://github.com/stefanprodan/MvcThrottle/blob/master/MvcThrottle/ThrottlingFilter.cs#L359

ip = request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (!string.IsNullOrEmpty(ip))
{
    if (ip.IndexOf(",") > 0)
    {
        ip = ip.Split(',').Last(); // <--  HERE
    }
}

I think the spec says (and our LBs say 😺) that the client address should be the first IP in the list.

Wikipedia

RFC 7239

In a chain of proxy servers where this is fully utilized, the first
"for" parameter will disclose the client where the request was first
made, followed by any subsequent proxy identifiers. The last proxy
in the chain is not part of the list of "for" parameters.

I'll get a PR for fixing that shortly. Awesome library!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.