{-# LANGUAGE OverloadedStrings #-}
module System.Linux.Proc.Tcp
( TcpSocket (..)
, TcpState (..)
, readProcTcpSockets
)
where
import Control.Error (runExceptT, throwE)
import Control.Monad (replicateM, void)
import Data.Attoparsec.ByteString.Char8 (Parser)
import qualified Data.Attoparsec.ByteString.Char8 as Atto
import Data.Bits ((.|.), shiftL)
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as BS
import qualified Data.List as List
import qualified Data.Text as Text
import System.Linux.Proc.Errors (ProcError (..))
import System.Linux.Proc.Process (ProcessId (..))
import System.Linux.Proc.IO (readProcFile)
data TcpState
= TcpEstablished
| TcpSynSent
| TcpSynReceive
| TcpFinWait1
| TcpFinWait2
| TcpTimeWait
| TcpClose
| TcpCloseWait
| TcpLastAck
| TcpListen
| TcpClosing
| TcpNewSynReceive
deriving (Show, Eq)
data TcpSocket = TcpSocket
{ tcpLocalAddr :: !(ByteString, Int)
, tcpRemoteAddr :: !(ByteString, Int)
, tcpTcpState :: !TcpState
, tcpUid :: !Int
, tcpInode :: !Int
} deriving (Show)
readProcTcpSockets :: ProcessId -> IO (Either ProcError [TcpSocket])
readProcTcpSockets pid =
runExceptT $ do
let fpath = mkNetTcpPath pid
bs <- readProcFile fpath
case Atto.parseOnly (pTcpSocketList <* Atto.endOfInput) bs of
Left e -> throwE $ ProcParseError fpath (Text.pack e)
Right ss -> pure ss
mkNetTcpPath :: ProcessId -> FilePath
mkNetTcpPath (ProcessId pid) = "/proc/" ++ show pid ++ "/net/tcp"
pTcpSocketList :: Parser [TcpSocket]
pTcpSocketList = pHeaders *> Atto.many' pTcpSocket
pSpace :: Parser Char
pSpace = Atto.char ' '
pMany1Space :: Parser ()
pMany1Space = void $ Atto.many1 pSpace
pStringSpace :: ByteString -> Parser ()
pStringSpace s =
Atto.string s *> pMany1Space
pHeaders :: Parser ()
pHeaders =
pMany1Space
*> pStringSpace "sl"
*> pStringSpace "local_address"
*> pStringSpace "rem_address"
*> pStringSpace "st"
*> pStringSpace "tx_queue"
*> pStringSpace "rx_queue"
*> pStringSpace "tr"
*> pStringSpace "tm->when"
*> pStringSpace "retrnsmt"
*> pStringSpace "uid"
*> pStringSpace "timeout inode"
<* Atto.endOfLine
pTcpSocket :: Parser TcpSocket
pTcpSocket = do
_ <- pMany1Space
_ <- (Atto.many1 Atto.digit *> Atto.char ':') <* pMany1Space
localAddr <- pAddressPort <* pMany1Space
remoteAddr <- pAddressPort <* pMany1Space
tcpState <- pTcpState <* pMany1Space
_ <- pInternalData
uid <- Atto.decimal <* pMany1Space
_ <- Atto.hexadecimal <* pMany1Space :: Parser Int
inode <- Atto.decimal <* pMany1Space :: Parser Int
_ <- Atto.many1 (Atto.satisfy (/= '\n'))
_ <- Atto.endOfLine
pure $ TcpSocket localAddr remoteAddr tcpState uid inode
pInternalData :: Parser ()
pInternalData = do
_ <- Atto.hexadecimal :: Parser Int
_ <- Atto.char ':'
_ <- Atto.hexadecimal :: Parser Int
_ <- Atto.many1 pSpace
_ <- Atto.hexadecimal :: Parser Int
_ <- Atto.char ':'
_ <- Atto.hexadecimal :: Parser Int
_ <- Atto.many1 pSpace
_ <- Atto.hexadecimal :: Parser Int
_ <- Atto.many1 pSpace
pure ()
pAddressPort :: Parser (ByteString, Int)
pAddressPort = do
addrParts <- replicateM 4 $ pHexadecimalOfLength 2
_ <- Atto.char ':'
port <- pHexadecimalOfLength 4
let addr' =
BS.concat . List.intersperse "." . fmap (BS.pack . show) $ reverse addrParts
pure (addr', port)
pTcpState :: Parser TcpState
pTcpState =
lookupState <$> (Atto.char '0' *> Atto.anyChar)
where
lookupState :: Char -> TcpState
lookupState '1' = TcpEstablished
lookupState '2' = TcpSynSent
lookupState '3' = TcpSynReceive
lookupState '4' = TcpFinWait1
lookupState '5' = TcpFinWait2
lookupState '6' = TcpTimeWait
lookupState '7' = TcpClose
lookupState '8' = TcpCloseWait
lookupState '9' = TcpLastAck
lookupState 'A' = TcpListen
lookupState 'B' = TcpClosing
lookupState 'C' = TcpNewSynReceive
lookupState c = error $ "System.Linux.Proc.Tcp.pTcpState: " ++ show c
pHexadecimalOfLength :: Int -> Parser Int
pHexadecimalOfLength n = do
ds <- Atto.count n (Atto.satisfy (isHexDigit . fromEnum))
return $ foldl step 0 (fmap (fromEnum :: Char -> Int) ds)
where
isHexDigit :: Int -> Bool
isHexDigit w =
(w >= 48 && w <= 57) || (w >= 97 && w <= 102) || (w >= 65 && w <= 70)
step :: Int -> Int -> Int
step a w | w >= 48 && w <= 57 = (a `shiftL` 4) .|. (w - 48)
| w >= 97 = (a `shiftL` 4) .|. (w - 87)
| otherwise = (a `shiftL` 4) .|. (w - 55)