module Hans.IP4.Fragments (
FragTable(),
newFragTable, cleanupFragTable,
processFragment,
) where
import Hans.Config
import qualified Hans.HashTable as HT
import Hans.IP4.Packet
import Hans.Lens (view)
import Hans.Monad
import Hans.Network.Types (NetworkProtocol)
import Hans.Threads (forkNamed)
import Hans.Time (toUSeconds)
import Control.Concurrent (ThreadId,threadDelay,killThread)
import Control.Monad (forever)
import qualified Data.ByteString as S
import Data.Time.Clock
(UTCTime,getCurrentTime,NominalDiffTime,addUTCTime)
type Key = (IP4,IP4,NetworkProtocol,IP4Ident)
type Table = HT.HashTable Key Buffer
data FragTable = FragTable { ftEntries :: !Table
, ftDuration :: !NominalDiffTime
, ftPurgeThread :: !ThreadId
}
newFragTable :: Config -> IO FragTable
newFragTable Config { .. } =
do ftEntries <- HT.newHashTable 31
ftPurgeThread <- forkNamed "IP4 Fragment Purge Thread"
(purgeEntries cfgIP4FragTimeout ftEntries)
return FragTable { ftDuration = cfgIP4FragTimeout, .. }
cleanupFragTable :: FragTable -> IO ()
cleanupFragTable FragTable { .. } = killThread ftPurgeThread
processFragment :: FragTable -> IP4Header -> S.ByteString
-> Hans (IP4Header,S.ByteString)
processFragment FragTable { .. } hdr body
| not (view ip4MoreFragments hdr) && view ip4FragmentOffset hdr == 0 =
return (hdr,body)
| otherwise =
do mb <- io $ do now <- getCurrentTime
let expire = addUTCTime ftDuration now
frag = mkFragment hdr body
key = mkKey hdr
HT.alter (updateBuffer expire hdr frag) key ftEntries
case mb of
Nothing -> escape
Just (hdr',body') -> return (hdr',body')
purgeEntries :: NominalDiffTime -> Table -> IO ()
purgeEntries lifetime entries = forever $
do threadDelay halfLife
now <- getCurrentTime
HT.filterHashTable (\_ Buffer { .. } -> bufExpire < now) entries
where
halfLife = toUSeconds (lifetime / 2)
data Buffer = Buffer { bufExpire :: !UTCTime
, bufSize :: !(Maybe Int)
, bufHeader :: !(Maybe IP4Header)
, bufFragments :: ![Fragment]
}
data Fragment = Fragment { fragStart :: !Int
, fragEnd :: !Int
, fragPayload :: [S.ByteString]
} deriving (Show)
mkKey :: IP4Header -> Key
mkKey IP4Header { .. } = (ip4SourceAddr,ip4DestAddr,ip4Protocol,ip4Ident)
mkFragment :: IP4Header -> S.ByteString -> Fragment
mkFragment hdr body = Fragment { .. }
where
fragStart = fromIntegral (view ip4FragmentOffset hdr)
fragEnd = fragStart + S.length body
fragPayload = [body]
mkBuffer :: UTCTime -> IP4Header -> Fragment -> Buffer
mkBuffer bufExpire hdr frag =
addFragment hdr frag
Buffer { bufHeader = Nothing
, bufSize = Nothing
, bufFragments = []
, .. }
updateBuffer :: UTCTime -> IP4Header -> Fragment -> Maybe Buffer
-> (Maybe Buffer,Maybe (IP4Header,S.ByteString))
updateBuffer _ hdr frag (Just buf) =
let buf' = addFragment hdr frag buf
in case bufFull buf' of
Just res -> (Nothing, Just res)
Nothing -> (Just buf', Nothing)
updateBuffer expire hdr frag Nothing =
let buf = mkBuffer expire hdr frag
in buf `seq` (Just buf, Nothing)
bufFull :: Buffer -> Maybe (IP4Header,S.ByteString)
bufFull Buffer { .. }
| Just size <- bufSize
, Just hdr <- bufHeader
, [Fragment { .. }] <- bufFragments
, fragEnd == size =
Just (hdr, S.concat fragPayload)
| otherwise =
Nothing
addFragment :: IP4Header -> Fragment -> Buffer -> Buffer
addFragment hdr frag buf =
Buffer { bufExpire = bufExpire buf
, bufSize = size'
, bufHeader = case bufHeader buf of
Nothing | view ip4FragmentOffset hdr == 0 -> Just hdr
_ -> bufHeader buf
, bufFragments = insertFragment (bufFragments buf)
}
where
size' | view ip4MoreFragments hdr = bufSize buf
| otherwise = Just $! fragEnd frag
insertFragment frags@(f:fs)
| fragEnd frag == fragStart f = mergeFragment frag f : fs
| fragStart frag == fragEnd f = mergeFragment f frag : fs
| fragStart frag < fragStart f = frag : frags
| otherwise = f : insertFragment fs
insertFragment [] = [frag]
mergeFragment :: Fragment -> Fragment -> Fragment
mergeFragment a b =
Fragment { fragStart = fragStart a
, fragEnd = fragEnd b
, fragPayload = fragPayload a ++ fragPayload b
}