0

I want to insert a sleep (aka throttle, delay, tarpit, dwell) in an ASP.net application (imagine something like failed logon attempt escalating delay).

protected void Page_Load(object sender, EventArgs e)
{
    Int32 sleepyTime = GetSleepyTime(Request);

    if (sleepyTime > 0)
        System.Threading.Thread.Sleep(sleepyTime);


    //...Continue normal processing
}

I want all remaining processing to continue as normal; i just want the user agent to suffer.

The problem is that ASP.net uses a ThreadPool to handle requests. If i were to Sleep for 5, 10, 30 seconds, i would be eating up a valuable limited resource.

I assume it needs to be something like:

protected void Page_Load(object sender, EventArgs e)
{
    Int32 sleepyTime = GetSleepyTime(Request);

    if (sleepyTime > 0)
       ABetterKindOfSleep(sleepyTime);

    //...Continue normal processing
}

private void ABetterKindOfSleep(int milliseconds)
{
   await SleepAsync(milliseconds);
}

private async void SleepAsync(int milliseconds)
{
   System.Threading.Thread.Sleep(milliseconds);
}

But never having written any async/await code, and not understanding the logic behind where the async and await go, or why, or even if it can be used to run async code: i don't know if it can be used to run async code.

Bonus Reading

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • 1
    Your tag indicates that you are using .NET 2.0. How ever the `async`/`await` pattern is something that came along with .NET 4.0. Did you select the wrong tag or was this intentional? – Nitram Jul 29 '16 at 15:35
  • @Nitram The solution that works on older web-sites (e.g. if i asked this question in 2005) is, obviously, better. Especially since i have some code that is locked in 2.0. But if .NET 2 didn't support thread pools, i'll have to accept there's no way to throttle. But i'll take what i can get. – Ian Boyd Jul 29 '16 at 17:53

3 Answers3

1

The async equivalent of Thread.Sleep is await Task.Delay:

if (sleepyTime > 0)
  await Task.Delay(sleepyTime);

Note that this must be used in the context of an async method, and there are limitations (particularly on WebForms) on where async can be used. For more information, see my article on async ASP.NET and the official tutorial on async WebForms.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
1

It's easy enough.

First you create an IHttpModule class:

class TarpitHttpModule : IHttpModule
{
}

And then you let IIS know about this module by registering it in web.config:

<configuration>
   <system.webServer>
      <modules runAllManagedModulesForAllRequests="true">
         <add name="Tarpit" type="TarpitHttpModule"/>

If you are Cassini, add it to:

<configuration>
   <system.web>
      <httpModules>
         <add name="Tarpit" type="TarpitHttpModule"/>

Whenever an http request comes in, IIS will call your .Init method. This is where you will register your async event handler using:

Code:

public void Init(HttpApplication application)
{
    //This is the synchronous event handler; which we don't want
    //application.BeginRequest += new EventHandler(this.Application_BeginRequest);

    //EventHandlerTaskAsyncHelper requires .NET 4.5
    //https://brockallen.com/2013/07/27/implementing-async-http-modules-in-asp-net-using-tpls-task-api/ 
    //  Archive: http://archive.is/Cdvle
    //
    //Normally you'd have to write a pair of methods:
    //    application.AddOnBeginRequestAsync(OnBegin, OnEnd);
    //
    //and then we'd have to write an OnBegin which returns IAsyncResult, and then OnEnd which takes the IAsyncResult.
    //The modern way is to use Tasks, and use the IAsyncResult that a Task **is**.
    //Fortunately the .NET team wrote a handy class that wraps up the boilerplate catching faults, etc,
    //and created the EventHandlerTaskAsyncHelper class

    var beginTaskHelper = new EventHandlerTaskAsyncHelper(BeginRequestAsync);
    application.AddOnBeginRequestAsync(beginTaskHelper.BeginEventHandler, beginTaskHelper.EndEventHandler);
}

So now we have to supply the BeginRequestAsync asynchronous handler:

async Task BeginRequestAsync(object sender, EventArgs e)
{
    var application = (HttpApplication)sender;
    var context = application.Context;

    // In reality i would use the context.Request to come up with a unique key 
    // for this user agent e.g. 
    String key = SHA256(UserHostAddress+UserAgent+AcceptTypes+UserLanguages).ToBase64();
    // And use that as a cache key store information about this user agent
    Object tarpitInfo = context.Cache.Get(agentIdentity);
    if (ti == null)
        return;

    // But in this SO demo, i'm just going to unconditionally sleep
    Boolean waitPerformed = await PerformDelay(context, tarpitInfo);
    if (waitPerformed)
    {
        context.Response.StatusCode = 429;
        context.Response.StatusDescription = "Too Many Requests";
        context.Response.End();
        return;
    }
}

And then the work of sleeping:

async Task<Boolean> PerformDelay(HttpContext context, TarInfo ti)
{
    int delayMs = 3000;
    Task delay = Task.Delay(delayMs);
    await delay;
    return true;
}
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • What is the overhead of having lots of requests awaiting? It's not blocking a thread but it must use some memory. Couldn't it hit the default max concurrent request limit quite easily? I think it's 5000 requests. – Co-der Jun 12 '20 at 15:46
0

I also want to tarpit bot traffic attacking login endpoints, I'm worried that if I simply await that I will hit the max concurrent requests or run out of memory. I have yet to find a low overhead way of doing this with Windows and Asp.Net.

I like the way it is done here, where it changes the behaviour of the TCP/IP stack to shrink the window size and not ACK subsequent packets which makes the remote exponentially back off and only send a tiny amount of data.

I will likely add a few Linux VMs running HAProxy in-front to take advantage of its DDOS capabilities

Co-der
  • 363
  • 1
  • 14