Ian Boyd
Ian Boyd

Reputation: 256999

Thread-pool friendly approach to Sleep?

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

Upvotes: 0

Views: 682

Answers (3)

Co-der
Co-der

Reputation: 383

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

Upvotes: 0

Ian Boyd
Ian Boyd

Reputation: 256999

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;
}

Upvotes: 1

Stephen Cleary
Stephen Cleary

Reputation: 457147

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.

Upvotes: 1

Related Questions