mptcpanalyzer-0.0.2.0: src/Tshark/Live.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-|
Module: Tshark.Live
Description : Load incrementally a PCAP into a frame
Maintainer : matt
Portability : Linux
-}
module Tshark.Live (
showLiveStatsTcp
, showLiveStatsMptcp
, LiveStats(..)
, LiveStatsTcp
, LiveStatsConfig(..)
, LiveStatsMptcp(..)
, lsmMaster, lsmSubflows, lsmStats
, mkLiveStatsMptcp
, genLiveStatsMptcp
, genLiveStatsTcp
-- , CaptureSettingsMptcp
)
where
import Tshark.Main (csvDelimiter, defaultTsharkPrefs)
import Net.Stream
import Net.Mptcp.Stats
import Data.Text as T
import qualified Data.Map.Strict as Map
import Control.Lens
import GHC.IO.Handle
import Pipes ((>->))
import Pipes hiding (Proxy)
-- import Control.Monad.Primitive
import Control.Exception (IOException, try)
import Control.Monad (liftM, unless, when)
import Data.Maybe (isNothing)
import qualified Data.Text.Encoding as T
import qualified Data.Text.IO as T
import qualified Data.Map.Strict as Map
import Data.Vinyl.Functor (Compose(..), (:.))
import Debug.Trace (trace, traceShow, traceShowId)
import Frames
import Frames.CSV
( ParserOptions
, ReadRec
, columnSeparator
, defaultParser
, headerOverride
, pipeTableEitherOpt
, pipeTableMaybeOpt
, readRecEither
, readRow
, tokenizeRow
)
import Frames.Exploration
import MptcpAnalyzer.Types (HostCols, Packet, PacketWithTcpDest)
import qualified Pipes as P
import qualified Pipes.Parse as P
import qualified Pipes.Prelude as P
import qualified Pipes.Safe as P
import Control.Monad.State (MonadState(get), StateT, gets, modify')
import Control.Monad.State.Lazy (execStateT)
import Data.Text.IO (hPutStrLn)
import MptcpAnalyzer (FrameFiltered(ffFrame))
import MptcpAnalyzer.ArtificialFields
import MptcpAnalyzer.Pcap (addTcpDestinationsToAFrame)
import MptcpAnalyzer.Types (FrameFiltered(FrameTcp))
import Net.Mptcp.Stats (MptcpUnidirectionalStats)
import Net.Mptcp.Connection (MptcpConnection(MptcpConnection))
import Net.Mptcp.Stats (MptcpUnidirectionalStats, showMptcpUnidirectionalStats, TcpSubflowUnidirectionalStats)
import Net.Tcp (TcpConnection)
import Net.Tcp.Stats
(TcpUnidirectionalStats, getTcpStatsFromAFrame, showTcpUnidirectionalStats)
import System.Console.ANSI
import System.IO (stdout)
import Net.Mptcp.Connection
import qualified Data.Set as Set
import GHC.Word (Word64, Word32)
import MptcpAnalyzer.Utils.Text
-- -- +--------+-- A 'Producer' that yields 'String's
-- -- | |
-- -- | | +-- Every monad transformer has a base monad.
-- -- | | | This time the base monad is 'IO'.
-- -- | | |
-- -- | | | +-- Every monadic action has a return value.
-- -- | | | | This action returns '()' when finished
-- -- v v v v
-- stdinLn :: Producer String IO ()
-- stdinLn = do
-- eof <- lift isEOF -- 'lift' an 'IO' action from the base monad
-- unless eof $ do
-- res <- lift getLine
-- yield res -- 'yield' the 'String'
-- stdinLn -- Loop
-- loop :: Effect IO ()
-- loop = for stdinLn $ \x -> do -- Read this like: "for str in stdinLn"
-- lift $ putStrLn x
-- | Opens a file (in 'P.MonadSafe') and repeatedly applies the given
-- function to the 'Handle' to obtain lines to yield. Adapted from the
-- moribund pipes-text package.
pipeLines :: P.MonadSafe m
=> (Handle -> IO (Either IOException T.Text))
-> Handle
-> P.Producer T.Text m ()
pipeLines pgetLine h =
let loop = do txt <- P.liftIO (pgetLine h)
case txt of
Left _e -> return ()
Right y -> P.yield y >> loop
in loop
-- | Produce lines of 'T.Text'.
-- produceTextLines :: P.MonadSafe m => FilePath -> P.Producer T.Text m ()
-- produceTextLines = pipeLines (try . T.hGetLine)
-- | Show live stats TCP
-- showLiveStatsTcp :: LiveStatsTcp -> Text
-- showLiveStatsTcp stats = T.unlines [
-- showLiveStats (SomeStats stats)
-- , showTcpUnidirectionalStats (lsStats stats)
-- ]
data SomeStats where
SomeStats :: LiveStats a b -> SomeStats
showLiveStatsTcp :: LiveStatsTcp -> Text
showLiveStatsTcp liveStats =
T.unlines (
[ "Completed ?: " <> tshow (lsHasFinished liveStats) ]
-- ++ if lsDestination liveStats == RoleServer then else []
++ ["Showing towards server: ", showTcpUnidirectionalStats (lsForwardStats liveStats)]
-- ++ if lsDestination liveStats == RoleClient then else []
++ ["Showing towards client: ", showTcpUnidirectionalStats (lsBackwardStats liveStats)]
)
-- type UpdateFrameFunc a b = Frame a -> Frame a -> (Frame a, b)
-- | Hold information on a connection
-- data LiveStats = LiveStats {
-- -- lsCon :: MptcpConnection,
-- lsStats :: MptcpUnidirectionalStats
-- , lsPackets :: Int
-- -- , lsFrame :: FrameFiltered TcpConnection Packet
-- , lsFrame :: FrameRec HostCols
-- }
-- TODO rename to liveplotConfig ?
data LiveStatsConfig = LiveStatsConfig {
lsConnection :: TcpConnection
, lsDestination :: ConnectionRole
}
-- TODO should be instance of a Monoid !
-- | for now unidirectional ?
data LiveStats stats packet = LiveStats {
-- lsCon :: MptcpConnection,
lsForwardStats :: stats
, lsBackwardStats :: stats
-- keep to check everything worked fine? else we can retreive the count from lsFrame
, lsPackets :: Int
-- , lsConnection :: TcpConnection
, lsFrame :: Frame packet
-- , lsFrame :: FrameFiltered con packet
, lsHasFinished :: Bool
-- ^ True once it sees a FIN
-- , lsFrame :: FrameRec HostCols
}
instance Semigroup stats => Semigroup (LiveStats stats packets) where
(<>) a b = LiveStats {
lsForwardStats = lsForwardStats a <> lsForwardStats b
, lsBackwardStats = lsBackwardStats a <> lsBackwardStats b
, lsPackets = lsPackets a + lsPackets b
, lsFrame = lsFrame a <> lsFrame b
, lsHasFinished = (lsHasFinished a) || (lsHasFinished b)
}
instance Monoid stats => Monoid (LiveStats stats packets) where
mempty = LiveStats {
lsForwardStats = mempty
, lsBackwardStats = mempty
-- keep to check everything worked fine? else we can retreive the count from lsFrame
, lsPackets = 0
-- , lsConnection :: TcpConnection
, lsFrame = mempty
-- , lsFrame :: FrameFiltered con packet
, lsHasFinished = False
}
type LiveStatsTcp = LiveStats TcpUnidirectionalStats Packet
-- data LiveStatsTcp = LiveStatsTcp {
-- _lstConn :: Maybe TcpConnection
-- , _lstStats :: LiveStats TcpUnidirectionalStats Packet
-- }
-- type LiveStatsMptcp = LiveStats MptcpUnidirectionalStats MptcpConnection Packet
-- should be richer
data LiveStatsMptcp = LiveStatsMptcp {
-- tcpStreamId
_lsmMaster :: Maybe MptcpConnection
, _lsmClient :: Maybe MptcpEndpointConfiguration
-- ^ Key / Token
, _lsmServer :: Maybe MptcpEndpointConfiguration
-- ^ (Key, Token)
, _lsmSubflows :: Map.Map StreamIdTcp LiveStatsTcp
-- (TcpSubflowUnidirectionalStats, TcpSubflowUnidirectionalStats)
-- ^ TODO these should be subflow stats (dss/dsn)
, _lsmStats :: LiveStats MptcpUnidirectionalStats Packet
}
makeLenses ''LiveStatsMptcp
-- |Search for the master subflow
-- TODO could
getMasterSubflow :: [MptcpSubflow] -> Maybe MptcpSubflow
getMasterSubflow l = case Prelude.filter (isNothing . sfJoinToken) l of
[] -> Nothing
(x:_) -> Just x
-- helper to create LiveStatsMptcp
mkLiveStatsMptcp :: LiveStatsMptcp
mkLiveStatsMptcp = LiveStatsMptcp {
_lsmMaster = Nothing
, _lsmClient = Nothing
, _lsmServer = Nothing
, _lsmSubflows = mempty
, _lsmStats = mempty
}
-- type CaptureSettingsMptcp = LiveStatsMptcp
-- TODO
genLiveStatsTcp :: FrameFiltered TcpConnection PacketWithTcpDest -> LiveStatsTcp
genLiveStatsTcp frameWithDest@(FrameTcp _ frame) = let
forwardFrameWithDest = getTcpStatsFromAFrame frameWithDest RoleServer
backwardFrameWithDest = getTcpStatsFromAFrame frameWithDest RoleClient
in (mempty :: LiveStatsTcp) {
lsPackets = frameLength frame
-- , lsFrame = frame
, lsForwardStats = let
merged =
-- trace ("FRAMEWITH DEST\n" ++ showFrame [csvDelimiter defaultTsharkPrefs] (ffFrame frameWithDest) ++ "\n " ++ show forwardFrameWithDest)
forwardFrameWithDest
in merged
, lsBackwardStats = backwardFrameWithDest
}
genLiveStatsMptcp :: FrameFiltered MptcpConnection Packet -> LiveStats MptcpUnidirectionalStats Packet
genLiveStatsMptcp mptcpAframe = (mempty :: LiveStats MptcpUnidirectionalStats Packet) {
lsPackets = frameLength $ ffFrame mptcpAframe
, lsFrame = ffFrame mptcpAframe
, lsForwardStats = getMptcpStats mptcpAframe RoleServer
, lsBackwardStats = getMptcpStats mptcpAframe RoleClient
}
showLiveStatsMptcp :: LiveStatsMptcp -> Text
showLiveStatsMptcp stats = T.unlines [
"Forward: " <> showMptcpUnidirectionalStats (lsForwardStats $ stats ^. lsmStats)
, "Backward: " <> showMptcpUnidirectionalStats (lsBackwardStats $ stats ^. lsmStats)
]
showLiveStats :: SomeStats -> Text
showLiveStats (SomeStats liveStats) =
T.unlines [
"Number of packets: " <> tshow (lsPackets liveStats)
]
-- tsharkProducer :: Handle -> Producer Text TsharkMonad ()
-- tsharkProducer hout = do
-- liftIO $ trace ("show hout " ++ show hout) hSetBuffering hout NoBuffering
-- output <- liftIO $ trace "hgetline" hGetLine hout
-- trace "yield" yield (T.pack output)
-- tsharkProducer hout
---- Accept as input the different handles
--readTsharkOutputAndPlotIt :: Handle -> Handle -> IO ()
--readTsharkOutputAndPlotIt hout herr = do
-- -- use pipeTableEitherOpt to parse
-- output <- hGetContents hout
-- putStrLn output