-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | High-level network sockets -- -- This library provides a high-level abstraction for network sockets. It -- uses Haskell2010 (along with GADTs) without typeclasses to ensure that -- consumers of the API can only call appropriate functions on a socket. -- Exceptions are tracked in the types of functions and returned to the -- caller with Either. The caller is free to handle these -- gracefully or to throw them. This library only throws exceptions when -- it detects that it has misused the operating system's sockets API -- (open an issue for this) or when the caller asks for a -- negatively-sized slice of a buffer (such exceptions are unrecoverable -- and indicate a mistake in the code consuming this API). @package sockets @version 0.3.0.0 -- | Internet datagram sockets without a fixed destination. module Socket.Datagram.IPv4.Undestined -- | A connectionless datagram socket that may communicate with many -- different endpoints on a datagram-by-datagram basis. newtype Socket Socket :: Fd -> Socket -- | An endpoint for an IPv4 socket, connection, or listener. Everything is -- in host byte order, and the user is not responisble for performing any -- conversions. data Endpoint Endpoint :: !IPv4 -> !Word16 -> Endpoint [$sel:address:Endpoint] :: Endpoint -> !IPv4 [$sel:port:Endpoint] :: Endpoint -> !Word16 data Message Message :: {-# UNPACK #-} !Endpoint -> !ByteArray -> Message [remote] :: Message -> {-# UNPACK #-} !Endpoint [payload] :: Message -> !ByteArray -- | Open a socket and run the supplied callback on it. This closes the -- socket when the callback finishes or when an exception is thrown. Do -- not return the socket from the callback. This leads to undefined -- behavior. If the address 0.0.0.0 is used, the socket receives -- on all network interfaces. If the port 0 is used, an unused port is -- chosen by the operating system. The callback provides the chosen port -- (or if the user specified a non-zero port, the chosen port will be -- that value). withSocket :: Endpoint -> (Socket -> Word16 -> IO a) -> IO (Either SocketException a) -- | Send a slice of a bytearray to the specified endpoint. send :: Socket -> Endpoint -> ByteArray -> Int -> Int -> IO (Either SocketException ()) -- | Send a slice of a bytearray to the specified endpoint. sendMutableByteArraySlice :: Socket -> Endpoint -> MutableByteArray RealWorld -> Int -> Int -> IO (Either (SendException 'Uninterruptible) ()) -- | Receive a datagram into a freshly allocated bytearray. receiveByteArray :: Socket -> Int -> IO (Either (ReceiveException 'Uninterruptible) Message) -- | Receive a datagram into a mutable byte array, ignoring information -- about the remote endpoint. Returns the actual number of bytes present -- in the datagram. Precondition: buffer_length - offset >= -- max_datagram_length. receiveMutableByteArraySlice_ :: Socket -> MutableByteArray RealWorld -> Int -> Int -> IO (Either SocketException Int) -- | Receive up to the specified number of datagrams into freshly allocated -- byte arrays. When there are many datagrams present on the receive -- buffer, this is more efficient than calling receive -- repeatedly. The array is guaranteed to have at least one message. -- -- The byte arrays in the resulting messages are always pinned. receiveMany :: Socket -> Int -> Int -> IO (Either SocketException (Array Message)) -- | This has the same behavior as receiveMany. However, it also -- takes an STM action that it attempts to run while the event -- manager is waiting for the socket to be ready for a reads. If the -- supplied action finishes first, this abandons the attempt to receive -- datagrams and returns Left ReceptionAbandoned. receiveManyUnless :: STM () -> Socket -> Int -> Int -> IO (Either SocketException (Array Message)) -- | Represents any unexpected behaviors that a function working on a -- socket, connection, or listener can exhibit. data SocketException -- | The datagram did not fit in the buffer. This can happen while sending. -- The field is the size of the number of bytes in the datagram that were -- successfully copied into the send buffer. SentMessageTruncated :: !Int -> SocketException -- | The datagram did not fit in the buffer. This can happen while -- receiving. The field is the original size of the datagram that was was -- truncated while copying it into the buffer. ReceivedMessageTruncated :: !Int -> SocketException -- | The socket address was not the expected size. This exception indicates -- a bug in this library or (less likely) in the operating system. SocketAddressSize :: SocketException -- | The socket address had an unexpected family. This exception indicates -- a bug in this library or (less likely) in the operating system. The -- int argument is the actual family found in the socket address. SocketAddressFamily :: !CInt -> SocketException -- | The option value was not the expected size. This exception indicates a -- bug in this library or (less likely) in the operating system. OptionValueSize :: SocketException -- | The user requested a negative number of bytes in a call to a receive -- function. NegativeBytesRequested :: SocketException -- | This happens when the Unless variant of a function is used -- and the STM action completes before the socket is ready for a -- read. ReceptionAbandoned :: SocketException -- | The remote end sent more data when it was expected to send a shutdown. RemoteNotShutdown :: SocketException -- | The remote end has shutdown its side of the full-duplex connection. -- This can happen receive is called on a stream socket. This is -- not necessarily a bad thing. Many protocols use shutdown to indicate -- that no more data is available. These protocols can be contrasted with -- protocols that send a length representing a number of expected bytes. RemoteShutdown :: SocketException -- | Any error code from the operating system that this library does not -- expect or recognize. Consult your operating system manual for details -- about the error code. ErrorCode :: !CInt -> SocketException -- | Internet datagram sockets without a fixed destination. The user may -- spoof the source address and may specify the packet ID. An application -- must have CAP_NET_RAW or be running as root to use the -- functions in this module. module Socket.Datagram.IPv4.Spoof -- | A socket that send datagrams with spoofed source IP addresses. It -- cannot receive datagrams. newtype Socket Socket :: Fd -> Socket -- | An endpoint for an IPv4 socket, connection, or listener. Everything is -- in host byte order, and the user is not responisble for performing any -- conversions. data Endpoint Endpoint :: !IPv4 -> !Word16 -> Endpoint [$sel:address:Endpoint] :: Endpoint -> !IPv4 [$sel:port:Endpoint] :: Endpoint -> !Word16 data Message Message :: {-# UNPACK #-} !Endpoint -> !ByteArray -> Message [remote] :: Message -> {-# UNPACK #-} !Endpoint [payload] :: Message -> !ByteArray -- | Open a socket and run the supplied callback on it. This closes the -- socket when the callback finishes or when an exception is thrown. Do -- not return the socket from the callback. This leads to undefined -- behavior. The user cannot specify an endpoint since the socket cannot -- receive traffic. withSocket :: (Socket -> IO a) -> IO (Either SocketException a) -- | Send a slice of a bytearray to the specified endpoint. sendMutableByteArray :: Socket -> Endpoint -> Endpoint -> MutableByteArray RealWorld -> Int -> Int -> IO (Either (SendException 'Uninterruptible) ()) data SocketException :: Type -- | Permission to create a raw socket was denied. The process needs the -- capability CAP_NET_RAW, or it must be run as root. [SocketPermissionDenied] :: SocketException -- | A limit on the number of open file descriptors has been reached. This -- could be the per-process limit or the system limit. (EMFILE -- and ENFILE) [SocketFileDescriptorLimit] :: SocketException data SendException :: Interruptibility -> Type -- | The datagram did not fit in the buffer. The field is the number of -- bytes that were successfully copied into the send buffer. The datagram -- does still get sent when this happens. [SendTruncated] :: !Int -> SendException i -- | Attempted to send to a broadcast address. [SendBroadcasted] :: SendException i -- | STM-style interrupt (much safer than C-style interrupt) [SendInterrupted] :: SendException 'Interruptible instance GHC.Show.Show Socket.Datagram.IPv4.Spoof.Socket instance GHC.Classes.Ord Socket.Datagram.IPv4.Spoof.Socket instance GHC.Classes.Eq Socket.Datagram.IPv4.Spoof.Socket instance GHC.Show.Show Socket.Datagram.IPv4.Spoof.SocketException instance GHC.Exception.Type.Exception Socket.Datagram.IPv4.Spoof.SocketException module Socket.Stream.IPv4 -- | A socket that listens for incomming connections. data Listener -- | A connection-oriented stream socket. data Connection -- | An endpoint for an IPv4 socket, connection, or listener. Everything is -- in host byte order, and the user is not responisble for performing any -- conversions. data Endpoint Endpoint :: !IPv4 -> !Word16 -> Endpoint [$sel:address:Endpoint] :: Endpoint -> !IPv4 [$sel:port:Endpoint] :: Endpoint -> !Word16 withListener :: Endpoint -> (Listener -> Word16 -> IO a) -> IO (Either SocketException a) -- | Accept a connection on the listener and run the supplied callback on -- it. This closes the connection when the callback finishes or if an -- exception is thrown. Since this function blocks the thread until the -- callback finishes, it is only suitable for stream socket clients that -- handle one connection at a time. The variant -- forkAcceptedUnmasked is preferrable for servers that need to -- handle connections concurrently (most use cases). withAccepted :: Listener -> (Either CloseException () -> a -> IO b) -> (Connection -> Endpoint -> IO a) -> IO (Either (AcceptException 'Uninterruptible) b) -- | Establish a connection to a server. withConnection :: Endpoint -> (Either CloseException () -> a -> IO b) -> (Connection -> IO a) -> IO (Either (ConnectException 'Uninterruptible) b) -- | Accept a connection on the listener and run the supplied callback in a -- new thread. Prefer forkAcceptedUnmasked unless the masking -- state needs to be preserved for the callback. Such a situation seems -- unlikely to the author. forkAccepted :: Listener -> (Either CloseException () -> a -> IO ()) -> (Connection -> Endpoint -> IO a) -> IO (Either (AcceptException 'Uninterruptible) ThreadId) -- | Accept a connection on the listener and run the supplied callback in a -- new thread. The masking state is set to Unmasked when running -- the callback. Typically, a is instantiated to (). forkAcceptedUnmasked :: Listener -> (Either CloseException () -> a -> IO ()) -> (Connection -> Endpoint -> IO a) -> IO (Either (AcceptException 'Uninterruptible) ThreadId) -- | Accept a connection on the listener and run the supplied callback in a -- new thread. The masking state is set to Unmasked when running -- the callback. Typically, a is instantiated to (). -- --

Discussion

-- -- Why is the counter argument present? At first, it seems like -- this is something that the API consumer should implement on top of -- this library. The argument for the inclusion of the counter is has two -- parts: (1) clients supporting graceful termination always need these -- semantics and (2) these semantics cannot be provided without building -- in counter as a TVar. -- --
    --
  1. Clients supporting graceful termination always need these -- semantics. To gracefully bring down a server that has been accepting -- connections with a forking function, an application must wait for all -- active connections to finish. Since all connections run on separate -- threads, this can only be accomplished by a concurrency primitive. The -- straightforward solution is to wrap a counter with either -- MVar or TVar. To complete graceful termination, the -- application must block until the counter reaches zero.
  2. --
  3. These semantics cannot be provided without building in -- counter as a TVar. When abandon becomes -- True, graceful termination begins. From this point onward, if -- at any point the counter reaches zero, the application consuming this -- API will complete termination. Consequently, we need the guarantee -- that the counter does not increment after the abandon -- transaction completes. If it did increment in this forbidden way (e.g. -- if it was incremented some unspecified amount of time after a -- connection was accepted), there would be a race condition in which the -- application may terminate without giving the newly accepted connection -- a chance to finish. Fortunately, STM gives us the composable -- transaction we need to get this guarantee. To wait for an inbound -- connection, we use:
  4. --
-- --
--   (isReady,deregister) <- threadWaitReadSTM fd
--   shouldReceive <- atomically $ do
--     readTVar abandon >>= \case
--       True -> do
--         isReady
--         modifyTVar' counter (+1)
--         pure True
--       False -> pure False
--   
-- -- This eliminates the window for the race condition. If a connection is -- accepted, the counter is guaranteed to be incremented _before_ -- abandon becomes True. However, this code would be -- more simple and would perform better if GHC's event manager used TVar -- instead of STM. interruptibleForkAcceptedUnmasked :: TVar Int -> TVar Bool -> Listener -> (Either CloseException () -> a -> IO ()) -> (Connection -> Endpoint -> IO a) -> IO (Either (AcceptException 'Interruptible) ThreadId) sendByteArray :: Connection -> ByteArray -> IO (Either (SendException 'Uninterruptible) ()) sendByteArraySlice :: Connection -> ByteArray -> Int -> Int -> IO (Either (SendException 'Uninterruptible) ()) sendMutableByteArray :: Connection -> MutableByteArray RealWorld -> IO (Either (SendException 'Uninterruptible) ()) sendMutableByteArraySlice :: Connection -> MutableByteArray RealWorld -> Int -> Int -> IO (Either (SendException 'Uninterruptible) ()) interruptibleSendByteArray :: TVar Bool -> Connection -> ByteArray -> IO (Either (SendException 'Interruptible) ()) interruptibleSendByteArraySlice :: TVar Bool -> Connection -> ByteArray -> Int -> Int -> IO (Either (SendException 'Interruptible) ()) interruptibleSendMutableByteArraySlice :: TVar Bool -> Connection -> MutableByteArray RealWorld -> Int -> Int -> IO (Either (SendException 'Interruptible) ()) -- | Receive exactly the given number of bytes. If the remote application -- shuts down its end of the connection before sending the required -- number of bytes, this returns Left -- ReceiveShutdown. receiveByteArray :: Connection -> Int -> IO (Either (ReceiveException 'Uninterruptible) ByteArray) -- | Receive up to the given number of bytes. If the remote application -- shuts down its end of the connection instead of sending any bytes, -- this returns Left (SocketException Receive -- RemoteShutdown). receiveBoundedByteArray :: Connection -> Int -> IO (Either (ReceiveException 'Uninterruptible) ByteArray) -- | Receive up to the given number of bytes, using the given array and -- starting at the given offset. receiveBoundedMutableByteArraySlice :: Connection -> Int -> MutableByteArray RealWorld -> Int -> IO (Either (ReceiveException 'Uninterruptible) Int) -- | Receive a number of bytes exactly equal to the size of the mutable -- byte array. If the remote application shuts down its end of the -- connection before sending the required number of bytes, this returns -- Left (SocketException Receive -- RemoteShutdown). receiveMutableByteArray :: Connection -> MutableByteArray RealWorld -> IO (Either (ReceiveException 'Uninterruptible) ()) -- | Variant of receiveByteArray that support STM-style interrupts. interruptibleReceiveByteArray :: TVar Bool -> Connection -> Int -> IO (Either (ReceiveException 'Interruptible) ByteArray) -- | Receive up to the given number of bytes, using the given array and -- starting at the given offset. This can be interrupted by the -- completion of an STM transaction. interruptibleReceiveBoundedMutableByteArraySlice :: TVar Bool -> Connection -> Int -> MutableByteArray RealWorld -> Int -> IO (Either (ReceiveException 'Interruptible) Int) data SendException :: Interruptibility -> Type -- | The local socket has already shutdown its writing channel. -- Consequently, sending is no longer possible. This can happen even if -- the process does not shutdown the socket. If the peer decides -- to close the connection, the local operating system will -- shutdown both the reading and writing channels. (EPIPE) [SendShutdown] :: SendException i -- | The peer reset the connection. [SendReset] :: SendException i -- | STM-style interrupt (much safer than C-style interrupt) [SendInterrupted] :: SendException 'Interruptible -- | Recoverable exceptions that can occur while receiving data on a stream -- socket. -- --

Discussion

-- -- The recv man page explicitly documents these: -- -- -- -- The man page includes a disclaimer: "Additional errors may be -- generated and returned from the underlying protocol modules". One such -- error when dealing with stream sockets in ECONNRESET. One -- scenario where this happens is when the process running on the peer -- terminates ungracefully and the operating system on the peer cleans up -- by sending a reset. data ReceiveException :: Interruptibility -> Type -- | The peer shutdown its writing channel. (zero-length chunk) [ReceiveShutdown] :: ReceiveException i -- | The peer reset the connection. (ECONNRESET) [ReceiveReset] :: ReceiveException i -- | STM-style interrupt (much safer than C-style interrupt) [ReceiveInterrupted] :: ReceiveException 'Interruptible -- | Recoverable exceptions that can occur while connecting to a peer. This -- includes both failures while opening the socket and failures while -- connecting to the peer. -- --

Discussion

-- -- In its API for connecting to a peer, this library combines the step of -- creating a socket with the step of connecting to the peer. In other -- words, the end user never gets access to an unconnected stream socket. -- Consequently, the connection exceptions correspond to the -- socket errors EMFILE and ENFILE as well as -- the connect errors ECONNREFUSED, -- EACCES/EPERM, ETIMEDOUT, -- ENETUNREACH, and EADDRNOTAVAIL. -- -- Somewhat surprisingly, EADDRINUSE is not included in the list -- of connect error codes we recognize as recoverable. The -- accept man page describes EADDRINUSE as "Local address -- is already in use". However, it is unclear what this means. The caller -- of connect does not provide an internet socket address. If -- ephemeral ports are exhausted, connect will error with -- EADDRNOTAVAIL. An unresolved Stack Overflow question -- calls into question whether or not it is actually possible for this -- error to happen with an internet domain socket. The author has decided -- to omit any checks for it. This means that, if it does ever happen, it -- will cause a SocketUnrecoverableException to be thrown. The -- Linux cognoscenti are encouraged to open an issue if they have more -- information about the circumstances under which this exception can -- occur. data ConnectException :: Interruptibility -> Type -- | Either the connection was blocked by a local firewall rule or it was -- blocked because it was to a broadcast address. Sadly, these two errors -- are not distinguished by the Linux sockets API. -- (EACCES/EPERM) [ConnectFirewalled] :: ConnectException i -- | A limit on the number of open file descriptors has been reached. This -- could be the per-process limit or the system limit. (EMFILE -- and ENFILE) [ConnectFileDescriptorLimit] :: ConnectException i -- | The network is unreachable. (ENETUNREACH) [ConnectNetworkUnreachable] :: ConnectException i -- | All port numbers numbers in the ephemeral port range are currently in -- use. (EADDRNOTAVAIL) [ConnectEphemeralPortsExhausted] :: ConnectException i -- | No one is listening on the remote address. (ECONNREFUSED) [ConnectRefused] :: ConnectException i -- | Timeout while attempting connection. The server may be too busy to -- accept new connections. Note that stock Linux configuration has -- timeout at appropriately 20 seconds. Users interested in timing -- out more quickly are encouraged to use registerDelay with the -- interruptible variants of the connection functions in this -- library. (ETIMEDOUT) [ConnectTimeout] :: ConnectException i -- | STM-style interrupt (much safer than C-style interrupt) [ConnectInterrupted] :: ConnectException 'Interruptible -- | Recoverable exceptions that happen when establishing an -- internet-domain stream listener or datagram socket. -- --

Discussion

-- -- The recoverable exceptions that we from stream sockets (established -- with socket-bind-listen) and datagram -- sockets (established with socket-bind) are the exact -- same exceptions. Consequently, we reuse the same type in both case. It -- is a little unfortunate since the name ListenException would -- be more appropriate for stream sockets. But the code reuse is worth -- the naming quibble. data SocketException :: Type -- | The address is protected, and the user is not the superuser. This most -- commonly happens when trying to bind to a port below 1024. On Linux, -- When it is necessary to bind to such a port on Linux, consider using -- the CAP_NET_BIND_SERVICE capability instead of running the -- process as root. (EACCES) [SocketPermissionDenied] :: SocketException -- | The given address is already in use. (EADDRINUSE with -- specified port) [SocketAddressInUse] :: SocketException -- | The port number was specified as zero, but upon attempting to bind to -- an ephemeral port, it was determined that all port numbers numbers in -- the ephemeral port range are currently in use. (EADDRINUSE -- with unspecified port) [SocketEphemeralPortsExhausted] :: SocketException -- | A limit on the number of open file descriptors has been reached. This -- could be the per-process limit or the system limit. (EMFILE -- and ENFILE) [SocketFileDescriptorLimit] :: SocketException -- | Recoverable exceptions that can occur while accepting an inbound -- connection. data AcceptException :: Interruptibility -> Type -- | The peer reset the connection before the running process accepted it. -- This is not typically treated as fatal. The process may continue -- accepting connections. (ECONNABORTED) [AcceptConnectionAborted] :: AcceptException i -- | A limit on the number of open file descriptors has been reached. This -- could be the per-process limit or the system limit. (EMFILE -- and ENFILE) [AcceptFileDescriptorLimit] :: AcceptException i -- | Firewall rules forbid connection. (EPERM) [AcceptFirewalled] :: AcceptException i -- | STM-style interrupt (much safer than C-style interrupt) [AcceptInterrupted] :: AcceptException 'Interruptible data CloseException :: Type -- | After the local process shut down the writing channel, it was -- expecting the peer to do the same. However, the peer sent more data -- instead. If this happens, the local process does still close the -- socket. However, it must send a TCP reset to accomplish this since -- there is still unread data in the receive buffer. -- -- This can happen if the peer is misbehaving or if the consumer of the -- sockets API has incorrectly implemented a protocol living -- above layer 4 of the OSI model. [ClosePeerContinuedSending] :: CloseException data Interruptibility Interruptible :: Interruptibility Uninterruptible :: Interruptibility -- | Open a socket that can be used to listen for inbound connections. -- Requirements: -- -- -- -- Noncompliant use of this function leads to undefined behavior. Prefer -- withListener unless you are writing an integration with a -- resource-management library. listen :: Endpoint -> IO (Either SocketException (Listener, Word16)) -- | Close a listener. This throws an unrecoverable exception if the socket -- cannot be closed. unlisten :: Listener -> IO () -- | Close a listener. This does not check to see whether or not the -- operating system successfully closed the socket. It never throws -- exceptions of any kind. This should only be preferred to -- unlistener in exception-cleanup contexts where there is -- already an exception that will be rethrown. See the implementation of -- withListener for an example of appropriate use of both -- unlistener and unlistener_. unlisten_ :: Listener -> IO () -- | Open a socket and connect to a peer. Requirements: -- -- -- -- Noncompliant use of this function leads to undefined behavior. Prefer -- withConnection unless you are writing an integration with a -- resource-management library. connect :: Endpoint -> IO (Either (ConnectException 'Uninterruptible) Connection) -- | Close a connection gracefully, reporting a CloseException when -- the connection has to be terminated by sending a TCP reset. This uses -- a combination of shutdown, recv, close to -- detect when resets need to be sent. disconnect :: Connection -> IO (Either CloseException ()) -- | Close a connection. This does not check to see whether or not the -- connection was brought down gracefully. It just calls close -- and is likely to cause a TCP reset to be sent. It never throws -- exceptions of any kind (even if close fails). This should -- only be preferred to disconnect in exception-cleanup contexts -- where there is already an exception that will be rethrown. See the -- implementation of withConnection for an example of appropriate -- use of both disconnect and disconnect_. disconnect_ :: Connection -> IO () -- | Listen for an inbound connection. accept :: Listener -> IO (Either (AcceptException 'Uninterruptible) (Connection, Endpoint)) -- | Listen for an inbound connection. Can be interrupted by an STM-style -- interrupt. interruptibleAccept :: TVar Bool -> Listener -> IO (Either (AcceptException 'Interruptible) (Connection, Endpoint))