on master help sign in
Better slowloris protection
5e3c9d1Michael Snoyman
warp/Network/Wai/Handler/Warp.hs+43−27
@@ -109,7 +109,7 @@ import qualified Timeout as T
109import Timeout (Manager, registerKillThread, pause, resume)
109import Timeout (Manager, registerKillThread, pause, resume)
110import Data.Word (Word8)
110import Data.Word (Word8)
111import Data.List (foldl')
111import Data.List (foldl')
112−import Control.Monad (forever)
112+import Control.Monad (forever, when)
113import qualified Network.HTTP.Types as H
113import qualified Network.HTTP.Types as H
114import qualified Data.CaseInsensitive as CI
114import qualified Data.CaseInsensitive as CI
115import System.IO (hPutStrLn, stderr)
115import System.IO (hPutStrLn, stderr)
@@ -126,13 +126,29 @@ import qualified Paths_warp
126warpVersion :: String
126warpVersion :: String
127warpVersion = showVersion Paths_warp.version
127warpVersion = showVersion Paths_warp.version
128
128
129+-- |
130+--
131+-- In order to provide slowloris protection, Warp provides timeout handlers. We
132+-- follow these rules:
133+--
134+-- * A timeout is created when a connection is opened.
135+--
136+-- * When all request headers are read, the timeout is tickled.
137+--
138+-- * Every time at least 2048 bytes of the request body are read, the timeout
139+-- is tickled.
140+--
141+-- * The timeout is paused while executing user code. This will apply to both
142+-- the application itself, and a ResponseSource response. The timeout is
143+-- resumed as soon as we return from user code.
144+--
145+-- * Every time data is successfully sent to the client, the timeout is tickled.
129data Connection = Connection
146data Connection = Connection
130 { connSendMany :: [B.ByteString] -> IO ()
147 { connSendMany :: [B.ByteString] -> IO ()
131 , connSendAll :: B.ByteString -> IO ()
148 , connSendAll :: B.ByteString -> IO ()
132 , connSendFile :: FilePath -> Integer -> Integer -> IO () -> IO () -- ^ offset, length
149 , connSendFile :: FilePath -> Integer -> Integer -> IO () -> IO () -- ^ offset, length
133− , connSink :: T.Handle -> C.Sink B.ByteString IO ()
134 , connClose :: IO ()
150 , connClose :: IO ()
135− , connSource :: T.Handle -> C.Source IO B.ByteString
151+ , connRecv :: IO B.ByteString
136 }
152 }
137
153
138socketConnection :: Socket -> Connection
154socketConnection :: Socket -> Connection
@@ -140,9 +156,8 @@ socketConnection s = Connection
140 { connSendMany = Sock.sendMany s
156 { connSendMany = Sock.sendMany s
141 , connSendAll = Sock.sendAll s
157 , connSendAll = Sock.sendAll s
142 , connSendFile = \fp off len act -> sendfile s fp (PartOfFile off len) act
158 , connSendFile = \fp off len act -> sendfile s fp (PartOfFile off len) act
143− , connSink = flip sinkSocket s
144 , connClose = sClose s
159 , connClose = sClose s
145− , connSource = \th -> C.Source $ return $ sourceSocket th bytesPerRead s
160+ , connRecv = Sock.recv s bytesPerRead
146 }
161 }
147
162
148
163
@@ -423,6 +438,8 @@ sendResponse th req conn r = sendResponse' r
423 connSendMany conn $ L.toChunks $ toLazyByteString $
438 connSendMany conn $ L.toChunks $ toLazyByteString $
424 headers version s lengthyHeaders False
439 headers version s lengthyHeaders False
425
440
441+ T.tickle th
442+
426 if not (hasBody s req) then return isPersist else do
443 if not (hasBody s req) then return isPersist else do
427 case mpart of
444 case mpart of
428 Nothing -> connSendFile conn fp 0 cl (T.tickle th)
445 Nothing -> connSendFile conn fp 0 cl (T.tickle th)
@@ -461,10 +478,11 @@ sendResponse th req conn r = sendResponse' r
461 -- FIXME perhaps alloca a buffer per thread and reuse that in all
478 -- FIXME perhaps alloca a buffer per thread and reuse that in all
462 -- functions below. Should lessen greatly the GC burden (I hope)
479 -- functions below. Should lessen greatly the GC burden (I hope)
463 response
480 response
464− | not (hasBody s req) = do
481+ | not (hasBody s req) = liftIO $ do
465− liftIO $ connSendMany conn
482+ connSendMany conn
466− $ L.toChunks $ toLazyByteString
483+ $ L.toChunks $ toLazyByteString
467− $ headers' False
484+ $ headers' False
485+ T.tickle th
468 return (checkPersist req)
486 return (checkPersist req)
469 | otherwise = do
487 | otherwise = do
470 let src =
488 let src =
@@ -492,34 +510,29 @@ parseHeaderNoAttr s =
492 else rest
510 else rest
493 in (CI.mk k, rest')
511 in (CI.mk k, rest')
494
512
495−sourceSocket :: T.Handle -> Int -> Socket -> C.PreparedSource IO ByteString
513+connSource :: Connection -> T.Handle -> C.PreparedSource IO ByteString
496−sourceSocket th len socket = C.PreparedSource
514+connSource Connection { connRecv = recv } th = C.PreparedSource
497 { C.sourcePull = do
515 { C.sourcePull = do
498− bs <- liftIO $ Sock.recv socket len
516+ bs <- liftIO recv
499− liftIO $ T.tickle th
500− return $ if S.null bs
517+ if S.null bs
501− then C.Closed
518+ then return C.Closed
519+ else do
520+ when (S.length bs >= 2048) $ liftIO $ T.tickle th
502− else C.Open bs
521+ return (C.Open bs)
503 , C.sourceClose = return ()
522 , C.sourceClose = return ()
504 }
523 }
505
524
506−------ The functions below are not warp-specific and could be split out into a
525+-- | Use 'connSendAll' to send this data while respecting timeout rules.
507−--separate package.
508−
509−sinkSocket :: T.Handle
510− -> Socket
511− -> C.Sink B.ByteString IO ()
526+connSink :: Connection -> T.Handle -> C.Sink B.ByteString IO ()
512−sinkSocket th sock = C.Sink $ return $ C.SinkData
527+connSink Connection { connSendAll = send } th = C.Sink $ return $ C.SinkData
513 { C.sinkPush = push
528 { C.sinkPush = push
514 , C.sinkClose = close
529 , C.sinkClose = close
515 }
530 }
516 where
531 where
517− close = do
518− liftIO (T.resume th)
532+ close = liftIO (T.resume th)
519− return ()
520 push x = do
533 push x = do
521 liftIO $ T.resume th
534 liftIO $ T.resume th
522− liftIO $ Sock.sendAll sock x
535+ liftIO $ send x
523 liftIO $ T.pause th
536 liftIO $ T.pause th
524 return $ C.Processing
537 return $ C.Processing
525 -- We pause timeouts before passing control back to user code. This ensures
538 -- We pause timeouts before passing control back to user code. This ensures
@@ -527,6 +540,9 @@ sinkSocket th sock = C.Sink $ return $ C.SinkData
527 -- also make sure to resume the timeout after the completion of user code
540 -- also make sure to resume the timeout after the completion of user code
528 -- so that we can kill idle connections.
541 -- so that we can kill idle connections.
529
542
543+------ The functions below are not warp-specific and could be split out into a
544+--separate package.
545+
530-- | Various Warp server settings. This is purposely kept as an abstract data
546-- | Various Warp server settings. This is purposely kept as an abstract data
531-- type so that new settings can be added without breaking backwards
547-- type so that new settings can be added without breaking backwards
532-- compatibility. In order to create a 'Settings' value, use 'defaultSettings'
548-- compatibility. In order to create a 'Settings' value, use 'defaultSettings'