{-
Copyright 2016 Markus Ongyerth
This file is part of Monky.
Monky is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Monky is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with Monky. If not, see .
-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE LambdaCase #-}
{-|
Module : Monky.Connectivity
Description : Allows the user to get a connected state
Maintainer : ongy
Stability : experimental
Portability : Linux
This module checks periodically if the current system
can establish a network connection (TCP) to a given host
on a given port. It does not care about reject, it is
intended to test whether a firewall drops packages or
a (tethered) connection is stable.
-}
module Monky.Connectivity
( ConnHandle
, getConnH
, hasConn
)
where
import Data.Bits ((.|.))
import Control.Concurrent (threadWaitWrite, threadDelay, forkIO)
import Data.Word (Word16)
import Foreign.C.Error (getErrno, Errno(..), eINPROGRESS)
import Foreign.Marshal.Utils (with)
import Foreign.Ptr (Ptr)
import Foreign.C.Types (CInt(..), CLong(..))
import Foreign.Storable (Storable(..))
import System.Posix.Types (Fd(..))
import System.Timeout (timeout)
import Data.IORef (IORef, newIORef, writeIORef, readIORef)
import Monky.IP.Raw
#include
#include
#include
-- |The Haskell type for the C struct sockaddr
newtype Port = Port Word16 deriving (Eq, Show)
data Sockaddr = Socka Int Port IP4 deriving (Eq, Show)
-- |Raw socket calls, we node those
foreign import ccall "socket" c_socket :: CInt -> CInt -> CInt -> IO CInt
foreign import ccall "close" c_close :: CInt -> IO ()
foreign import ccall "connect" c_connect :: CInt -> Ptr Sockaddr -> CInt -> IO CInt
-- This isn't really IO, since it is deterministic and doesn't have sideeffects
foreign import ccall "htons" htons :: Word16 -> Word16
instance Storable Sockaddr where
sizeOf _ = #{size struct sockaddr_in}
alignment _ = alignment (undefined :: CLong)
peek p = do
fam <- #{peek struct sockaddr_in, sin_family} p
port <- #{peek struct sockaddr_in, sin_port} p
ip <- #{peek struct sockaddr_in, sin_addr} p
return (Socka fam (Port port) ip)
poke p (Socka fam (Port port) ip) = do
#{poke struct sockaddr_in, sin_family} p fam
#{poke struct sockaddr_in, sin_port} p port
#{poke struct sockaddr_in, sin_addr} p ip
-- TODO maybe create an echo service so we don't have to do the socket call all the time
tryConn :: String -> Int -> IO Bool
tryConn ip port = do
socket <- c_socket #{const AF_INET} (#{const SOCK_STREAM} .|. #{const SOCK_NONBLOCK}) 0
-- This will always be -1 because of how unblocking sockets work
_ <- with (Socka #{const AF_INET} (Port . htons $fromIntegral port) (parseIP ip))
(\ptr ->c_connect socket ptr (fromIntegral $sizeOf (undefined :: Sockaddr)))
(Errno con) <- getErrno
ret <- if (Errno con) == eINPROGRESS
then timeout (500 * 1000) (threadWaitWrite (Fd socket)) >>=
\case
Nothing -> return True
Just _ -> return False
else return False
c_close socket
return ret
-- |The handle exposed by this module
data ConnHandle = ConnH String Int (IORef Bool)
-- |Get the current connected state from the handle
hasConn :: ConnHandle -> IO Bool
hasConn (ConnH _ _ r) = readIORef r
updateLoop :: ConnHandle -> IO ()
updateLoop h@(ConnH ip port ref) = do
writeIORef ref =<< tryConn ip port
-- Sleep 1 second
threadDelay (1000*1000)
updateLoop h
-- |Get a handle to check for connectivity
getConnH
:: String -- ^The Host to use for connectivity probing
-> Int -- ^Which port to use for connecivity probing (drop is bad)
-> IO ConnHandle
getConnH ip port = do
ref <- newIORef False
let h = ConnH ip port ref
_ <- forkIO (updateLoop h)
return h