packages feed

Dust-tools-1.2: Dust/Services/Replay/Replay.hs

module Dust.Services.Replay.Replay
(
    ReplayConfig(..),
    PacketMask(..),
    loadMask,
    replayStream,
    linger
)
where

import System.IO
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.ByteString.Char8 (pack)
import Data.Serialize
import Text.Printf (printf)
import Data.List as L
import Control.Concurrent
import Network.Socket hiding (recv, sendTo, Stream)
import Network.Socket.ByteString (recv, sendAll, sendTo)
import System.Entropy
import Control.Exception
import Data.Word (Word8, Word16, Word32)
import Data.String
import Data.List.Split
import Control.Monad (forever)

import Dust.Network.Util
import Dust.Model.TrafficModel
import Dust.Model.Packet

data ReplayConfig =
    TCPConfig PortNumber Bool
  | UDPConfig PortNumber Bool String Bool

data PacketMask = PacketMask [[(Int,Word8)]] deriving (Show) -- [[(Offset,Byte)]], list index is packet number

nextMask :: PacketMask -> PacketMask
nextMask (PacketMask []) = PacketMask []
nextMask (PacketMask (mask:masks)) = PacketMask masks

loadMask :: FilePath -> IO PacketMask
loadMask path = do
  file <- readFile path
  return $ parseMasks $ map words (lines file)

parseMasks :: [[String]] -> PacketMask
parseMasks sss = PacketMask $ map parseMask sss

parseMask :: [String] -> [(Int,Word8)]
parseMask ss = map parseByteMask ss

parseByteMask :: String -> (Int,Word8)
parseByteMask s =
  let (a:b:_) = splitOn "," s
      a' = (read a)::Int
      b' = (read b)::Word8
  in (a',b')

replayStream :: ReplayConfig -> Stream -> PacketMask -> Socket -> IO()
replayStream config stream@(Stream _ _ []) mask sock = do
  putStrLn "Done replaying"
  putStrLn "Closing socket"
  case config of
--   (TCPConfig _ _)     -> sClose sock
   (TCPConfig _ _)     -> linger sock
   (UDPConfig _ _ _ _) -> return()
replayStream config stream@(Stream protocol port (packet:packets)) mask sock = do
  replayPacket config packet mask sock
  replayStream (markNotFirst config) (Stream protocol port packets) (nextMask mask) sock

markNotFirst :: ReplayConfig -> ReplayConfig
markNotFirst config@(TCPConfig _ _) = config
markNotFirst (UDPConfig a b c _) = UDPConfig a b c False

-- True if packet direction is the same as config direction
-- False if packet direction is not the same as config direction
-- Config Packet Replay
--   T      T      T    -- Server, Packet is Client->Server, Read
--   T      F      F    -- Server, Packet is Client<-Server, Send
--   F      T      F    -- Client, Packet is Client->Server, Send
--   F      F      T    -- Client, Packet is Client<-Server, Read
replayDirection :: ReplayConfig -> Packet -> Bool
replayDirection config packet =
  case config of
    (TCPConfig port dir)     -> (packetDirection packet port) == dir
    (UDPConfig port dir _ _) -> (packetDirection packet port) == dir

-- True if the destination of the packet is the config port
--   Means packet is going to server
-- False if the destination of the packet is any other port
--   Means packet is going to client
packetDirection :: Packet -> PortNumber -> Bool
packetDirection (Packet _ _ tcp _) (PortNum port) = (destport tcp) == port

replayPacket :: ReplayConfig -> Packet -> PacketMask -> Socket -> IO()
replayPacket config packet@(Packet _ _ _ payload) mask sock = do
    case (replayDirection config packet) of
        True  -> getReplayedBytes (B.length payload) sock
        False -> sendReplayedBytes config packet mask sock

linger :: Socket -> IO()
linger = forever $ getReplayedBytes 1024

getReplayedBytes :: Int -> Socket -> IO()
getReplayedBytes count sock = do
    putStrLn $ "waiting for " ++ show count
    bsTry <- try (recv sock count) :: IO (Either IOError B.ByteString)
    case bsTry of
        Left error -> do
            putStrLn $ "Error: " ++ show error
            putStrLn "Closing socket"
--            sClose sock
            return ()
        Right bs -> do
            putStrLn $ "got " ++ (show (B.length bs))
            if (B.length bs) == 0
              then do
                putStrLn "0 length read"
                putStrLn "Closing socket"
--                sClose sock
                return ()
              else if (B.length bs) < count
                then getReplayedBytes (count - (B.length bs)) sock
                else return ()

sendReplayedBytes :: ReplayConfig -> Packet -> PacketMask -> Socket -> IO()
sendReplayedBytes config packet@(Packet _ _ transport payload) mask sock = do
    putStrLn $ "sending " ++ show (B.length payload)
    putStrLn $ show packet
    let maskedPayload = applyMask mask payload
    case config of
        (TCPConfig _ _) -> do
            sendTry <- try (sendAll sock maskedPayload) :: IO (Either IOError ())
            case sendTry of
                Left error -> do
                    putStrLn $ "Error: " ++ show error
                    putStrLn "Closing socket"
--                    sClose sock
                Right _ -> do
                    putStrLn $ "sent " ++ (show (B.length payload))
        (UDPConfig sport dir host first) -> do
            bindAddr <- inet_addr host
            let addr = SockAddrInet (PortNum (if dir then 2014 else 2013)) bindAddr
            sendTry <- try (sendTo sock maskedPayload addr) :: IO (Either IOError Int)
            case sendTry of
                Left error -> do
                    putStrLn $ "Error: " ++ show error
                Right _ -> do
                    putStrLn $ "sent " ++ (show (B.length payload)) ++ " to " ++ (show addr)
    return ()

applyMask :: PacketMask -> ByteString -> ByteString
applyMask (PacketMask []) bs = bs
applyMask (PacketMask (mask:_)) bs = applyByteMasks mask bs

applyByteMasks :: [(Int,Word8)] -> ByteString -> ByteString
applyByteMasks [] bs = bs
applyByteMasks (mask:masks) bs = applyByteMasks masks (applyByteMask mask bs)

applyByteMask :: (Int,Word8) -> ByteString -> ByteString
applyByteMask (offset,value) bs = 
  let front = B.take offset bs
      back = B.drop (offset+1) bs
  in front `B.append` (value `B.cons` back)