module Cardano.Wallet.Network.Ports
(
PortNumber
, getRandomPort
, isPortOpen
, simpleSockAddr
, portFromURL
, randomUnusedTCPPorts
) where
import Prelude
import Control.Monad
( filterM )
import Control.Monad.IO.Class
( liftIO )
import Data.List
( isInfixOf, sort )
import Data.Maybe
( fromMaybe )
import Data.Streaming.Network
( bindRandomPortTCP )
import Data.Word
( Word8 )
import Foreign.C.Error
( Errno (..), eCONNREFUSED )
import GHC.IO.Exception
( IOException (..) )
import Network.Socket
( Family (AF_INET)
, PortNumber
, SockAddr (..)
, SocketType (Stream)
, close'
, connect
, socket
, tupleToHostAddress
)
import Network.URI
( URI (..), URIAuth (..) )
import Safe
( readMay )
import System.Random.Shuffle
( shuffleM )
import UnliftIO.Exception
( bracket, throwIO, try )
getRandomPort :: IO PortNumber
getRandomPort :: IO PortNumber
getRandomPort = do
let hostPreference :: HostPreference
hostPreference = HostPreference
"127.0.0.1"
(Int
port, Socket
sock) <- HostPreference -> IO (Int, Socket)
bindRandomPortTCP HostPreference
hostPreference
IO () -> IO ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ Socket -> IO ()
close' Socket
sock
PortNumber -> IO PortNumber
forall (m :: * -> *) a. Monad m => a -> m a
return (PortNumber -> IO PortNumber) -> PortNumber -> IO PortNumber
forall a b. (a -> b) -> a -> b
$ Int -> PortNumber
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
port
isPortOpen :: SockAddr -> IO Bool
isPortOpen :: SockAddr -> IO Bool
isPortOpen SockAddr
sockAddr = do
IO Socket -> (Socket -> IO ()) -> (Socket -> IO Bool) -> IO Bool
forall (m :: * -> *) a b c.
MonadUnliftIO m =>
m a -> (a -> m b) -> (a -> m c) -> m c
bracket (Family -> SocketType -> ProtocolNumber -> IO Socket
socket Family
AF_INET SocketType
Stream ProtocolNumber
6 ) Socket -> IO ()
close' ((Socket -> IO Bool) -> IO Bool) -> (Socket -> IO Bool) -> IO Bool
forall a b. (a -> b) -> a -> b
$ \Socket
sock -> do
Either IOException ()
res <- IO () -> IO (Either IOException ())
forall (m :: * -> *) e a.
(MonadUnliftIO m, Exception e) =>
m a -> m (Either e a)
try (IO () -> IO (Either IOException ()))
-> IO () -> IO (Either IOException ())
forall a b. (a -> b) -> a -> b
$ Socket -> SockAddr -> IO ()
connect Socket
sock SockAddr
sockAddr
case Either IOException ()
res of
Right () -> Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
Left IOException
e
| (ProtocolNumber -> Errno
Errno (ProtocolNumber -> Errno) -> Maybe ProtocolNumber -> Maybe Errno
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IOException -> Maybe ProtocolNumber
ioe_errno IOException
e) Maybe Errno -> Maybe Errno -> Bool
forall a. Eq a => a -> a -> Bool
== Errno -> Maybe Errno
forall a. a -> Maybe a
Just Errno
eCONNREFUSED -> Bool -> IO Bool
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False
| [Char]
"WSAECONNREFUSED" [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` IOException -> [Char]
forall a. Show a => a -> [Char]
show IOException
e -> Bool -> IO Bool
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False
| Bool
otherwise -> IOException -> IO Bool
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO IOException
e
simpleSockAddr :: (Word8, Word8, Word8, Word8) -> PortNumber -> SockAddr
simpleSockAddr :: (Word8, Word8, Word8, Word8) -> PortNumber -> SockAddr
simpleSockAddr (Word8, Word8, Word8, Word8)
addr PortNumber
port = PortNumber -> HostAddress -> SockAddr
SockAddrInet PortNumber
port ((Word8, Word8, Word8, Word8) -> HostAddress
tupleToHostAddress (Word8, Word8, Word8, Word8)
addr)
portFromURL :: URI -> PortNumber
portFromURL :: URI -> PortNumber
portFromURL URI
uri = PortNumber -> Maybe PortNumber -> PortNumber
forall a. a -> Maybe a -> a
fromMaybe PortNumber
fallback
(URI -> Maybe URIAuth
uriAuthority URI
uri Maybe URIAuth -> (URIAuth -> Maybe PortNumber) -> Maybe PortNumber
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [Char] -> Maybe PortNumber
forall a. Read a => [Char] -> Maybe a
readMay ([Char] -> Maybe PortNumber)
-> (URIAuth -> [Char]) -> URIAuth -> Maybe PortNumber
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
':')) ([Char] -> [Char]) -> (URIAuth -> [Char]) -> URIAuth -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. URIAuth -> [Char]
uriPort)
where
fallback :: PortNumber
fallback = if URI -> [Char]
uriScheme URI
uri [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
"https:" then PortNumber
443 else PortNumber
80
randomUnusedTCPPorts :: Int -> IO [Int]
randomUnusedTCPPorts :: Int -> IO [Int]
randomUnusedTCPPorts Int
count = do
[Int]
usablePorts <- [Int] -> IO [Int]
forall (m :: * -> *) a. MonadRandom m => [a] -> m [a]
shuffleM [Int
1024..Int
49151]
[Int] -> [Int]
forall a. Ord a => [a] -> [a]
sort ([Int] -> [Int]) -> IO [Int] -> IO [Int]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Int -> IO Bool) -> [Int] -> IO [Int]
forall (m :: * -> *) a.
Applicative m =>
(a -> m Bool) -> [a] -> m [a]
filterM Int -> IO Bool
unused (Int -> [Int] -> [Int]
forall a. Int -> [a] -> [a]
take Int
count [Int]
usablePorts)
where
unused :: Int -> IO Bool
unused = (Bool -> Bool) -> IO Bool -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Bool -> Bool
not (IO Bool -> IO Bool) -> (Int -> IO Bool) -> Int -> IO Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SockAddr -> IO Bool
isPortOpen (SockAddr -> IO Bool) -> (Int -> SockAddr) -> Int -> IO Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word8, Word8, Word8, Word8) -> PortNumber -> SockAddr
simpleSockAddr (Word8
127,Word8
0,Word8
0,Word8
1) (PortNumber -> SockAddr) -> (Int -> PortNumber) -> Int -> SockAddr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> PortNumber
forall a b. (Integral a, Num b) => a -> b
fromIntegral