Network.hs (5069B)
1 {-# LANGUAGE ScopedTypeVariables #-} 2 -- TCP transports: numeric IPv4/IPv6 endpoints, no implicit resolver/retry policy. 3 module IO.Network (listenTCP, connectTCP, duplexBackend, backlogLimit) where 4 import qualified IO.Resources as R 5 import qualified Network.Socket as N 6 import qualified Network.Socket.ByteString as B 7 import qualified Data.ByteString as BS 8 import Control.Exception 9 import Control.Monad (when, unless) 10 import Data.IORef 11 import GHC.IO.Exception (ioe_errno) 12 import Foreign.C.Error 13 14 backlogLimit :: Int 15 backlogLimit = 4096 16 address (R.IPv4 host port) = N.SockAddrInet (fromIntegral port) (N.tupleToHostAddress host) 17 address (R.IPv6 host scope port) = N.SockAddrInet6 (fromIntegral port) 0 (N.tupleToHostAddress6 host) scope 18 family (R.IPv4 _ _) = N.AF_INET 19 family (R.IPv6 _ _ _) = N.AF_INET6 20 port (R.IPv4 _ p) = p 21 port (R.IPv6 _ _ p) = p 22 endpoint (N.SockAddrInet p h) = pure (R.IPv4 (N.hostAddressToTuple h) (fromIntegral p)) 23 endpoint (N.SockAddrInet6 p _ h scope) = pure (R.IPv6 (N.hostAddress6ToTuple h) scope (fromIntegral p)) 24 endpoint _ = ioError (userError "unexpected TCP address family") 25 26 networkIO :: String -> IO a -> IO a 27 networkIO op action = action `catch` \(e :: IOException) -> do 28 let generic = R.ioProblem op e 29 category = case ioe_errno e of 30 Just code -> maybe (R.category generic) id (lookup (Errno code) 31 [(eCONNREFUSED,"connection-refused"),(eCONNRESET,"connection-reset"), 32 (eCONNABORTED,"connection-aborted"),(eADDRINUSE,"address-in-use"), 33 (eADDRNOTAVAIL,"address-unavailable"),(eNETUNREACH,"network-unreachable"), 34 (eHOSTUNREACH,"host-unreachable"),(ePIPE,"broken-pipe"),(eTIMEDOUT,"timed-out")]) 35 _ -> R.category generic 36 R.resourceFault (generic {R.category=category, 37 R.progress=if op `elem` ["local-address","peer-address"] then "none" else "unknown"}) 38 39 listenTCP :: R.Runtime -> R.Scope -> R.Endpoint -> Int -> IO (Either R.Problem R.Token) 40 listenTCP runtime owner target backlog 41 | backlog <= 0 || backlog > backlogLimit = pure (Left (R.Problem "invalid-bound" "listen" "" "none")) 42 | otherwise = R.acquire runtime owner $ networkIO "listen" $ 43 bracketOnError (N.socket (family target) N.Stream N.defaultProtocol) N.close $ \socket -> do 44 N.setSocketOption socket N.ReuseAddr 1 45 when (family target == N.AF_INET6) (N.setSocketOption socket N.IPv6Only 1) 46 N.bind socket (address target) 47 N.listen socket backlog 48 let accept = networkIO "accept" $ 49 bracketOnError (fst <$> N.accept socket) N.close duplexBackend 50 pure (R.Listener (R.ListenerOps accept 51 (networkIO "local-address" (N.getSocketName socket >>= endpoint)) (N.close socket))) 52 53 connectTCP :: R.Runtime -> R.Scope -> R.Endpoint -> IO (Either R.Problem R.Token) 54 connectTCP runtime owner target 55 | port target == 0 = pure (Left (R.Problem "invalid-bound" "connect" "zero peer port" "none")) 56 | otherwise = R.acquire runtime owner $ networkIO "connect" $ 57 bracketOnError (N.socket (family target) N.Stream N.defaultProtocol) N.close $ \socket -> do 58 N.connect socket (address target) 59 duplexBackend socket 60 61 data Direction = Open | Ended | Poisoned 62 -- The registry supplies separate read/write leases; direction state is not 63 -- synchronized across unrelated sockets, aliases or host IO outside this runtime. 64 duplexBackend :: N.Socket -> IO R.Backend 65 duplexBackend socket = do 66 reading <- newIORef Open 67 writing <- newIORef Open 68 let invalid op = R.resourceFault (R.Problem "invalid-state" op "socket direction failed; close explicitly" "unknown") 69 receive count = mask $ \restore -> do 70 phase <- readIORef reading 71 case phase of 72 Ended -> pure BS.empty 73 Poisoned -> invalid "read" 74 Open -> (do 75 bytes <- restore (networkIO "read" (B.recv socket count)) 76 when (BS.null bytes) (writeIORef reading Ended) 77 pure bytes) `onException` writeIORef reading Poisoned 78 send bytes = mask $ \restore -> do 79 phase <- readIORef writing 80 case phase of 81 Ended -> R.resourceFault (R.Problem "invalid-state" "write" "write direction shut down" "none") 82 Poisoned -> invalid "write" 83 Open -> (do 84 count <- restore (networkIO "write" (B.send socket bytes)) 85 unless (count > 0 && count <= BS.length bytes) 86 (ioError (userError "invalid socket write progress")) 87 pure count) `onException` writeIORef writing Poisoned 88 shutdown = mask $ \restore -> do 89 phase <- readIORef writing 90 case phase of 91 Ended -> pure () 92 Poisoned -> invalid "shutdown-write" 93 Open -> (restore (networkIO "shutdown-write" (N.shutdown socket N.ShutdownSend)) >> 94 writeIORef writing Ended) `onException` writeIORef writing Poisoned 95 pure (R.Duplex (R.DuplexOps receive send shutdown 96 (networkIO "local-address" (N.getSocketName socket >>= endpoint)) 97 (networkIO "peer-address" (N.getPeerName socket >>= endpoint)) (N.close socket)))