{- | This library implements efficient socket to socket data transfer loops for proxy servers. On Linux, it uses the zero-copy splice() system call: . On all other operating systems, it currently falls back to a portable Haskell implementation that allocates a constant-sized memory buffer before it enters an inner loop which then uses hGetBufSome and hPutBuf; this avoids lots of tiny allocations as would otherwise be caused by recv and sendAll functions from Network.Socket.ByteString. -} -- -- Module : Network.Socket.Splice -- Copyright : (c) Cetin Sert 2012 -- License : BSD-style -- Maintainer : fusion@corsis.eu -- Stability : stable -- Portability : GHC-only, works on all OSes #ifdef LINUX_SPLICE #include {-# LANGUAGE CPP #-} {-# LANGUAGE ForeignFunctionInterface #-} #endif module Network.Socket.Splice ( -- * Cross-platform API for Socket to Socket Data Transfer Loops {- | 'splice' is the cross-platform API for continous, uni-directional data transfer between two network sockets. It is an /infinite loop/ that is intended to be used with 'Control.Concurrent.forkIO': > void . forkIO . try_ $ splice 1024 sourceSocket targetSocket > void . forkIO . try_ $ splice 1024 targetSocket sourceSocket -} splice , ChunkSize , zeroCopy -- * Combinators for Exception Handling , try_ -- * Linux splice() Components {- | These are available only on Linux and will be moved to a different namespace in later releases. Their names will stay the same. -} #ifdef LINUX_SPLICE , c_splice , sPLICE_F_MOVE , sPLICE_F_MORE , sPLICE_F_NONBLOCK #endif ) where import Data.Word import Foreign.Ptr import Network.Socket import Control.Monad import Control.Exception import System.IO import System.Posix.Types import System.Posix.Internals import GHC.IO.Handle.FD #ifdef LINUX_SPLICE import Data.Int import Data.Bits import Unsafe.Coerce import Foreign.C.Types import Foreign.C.Error import System.Posix.IO #else import Foreign.Marshal.Alloc #endif -- | Indicates whether 'splice' uses zero-copy system calls -- or the portable user mode Haskell substitue implementation. zeroCopy :: Bool -- ^ True: uses zero-copy system calls; otherwise: portable. zeroCopy = #ifdef LINUX_SPLICE True #else False #endif -------------------------------------------------------------------------------- -- | The numeric type used to recommend chunk sizes for moving -- data between sockets used by both the Linux 'splice' and -- the portable implementation of 'splice'. type ChunkSize = #ifdef LINUX_SPLICE (#type size_t) #else Int #endif -- | Pipes data from one socket to another in an -- **infinite loop**. -- -- On Linux this happens in kernel space with -- zero copying between kernel and user spaces. -- -- On other operating systems, a portable -- implementation utilizes a user space buffer -- and works on handles instead of file descriptors. splice :: ChunkSize -- ^ Chunk size. -> Socket -- ^ Source socket. -> Socket -- ^ Target socket. -> IO () -- ^ Infinite loop. splice len sIn sOut = do let throwRecv0 = error "Network.Socket.Splice.splice ended" let fdIn = fdSocket sIn let fdOut = fdSocket sOut #ifdef LINUX_SPLICE print "LINUX-SPLICE" (r,w) <- createPipe -- r: read end of pipe print ('+',r,w) -- w: write end of pipe let s = Fd fdIn -- s: source socket let t = Fd fdOut -- t: target socket let n = nullPtr let u = unsafeCoerce :: (#type ssize_t) -> (#type size_t) let check = throwErrnoIfMinus1 "Network.Socket.Splice.splice" let flags = sPLICE_F_MOVE .|. sPLICE_F_MORE let setNonBlockingMode v = do setNonBlockingFD fdIn v setNonBlockingFD fdOut v setNonBlockingMode False finally (forever $ do bytes <- check $ c_splice s n w n len flags if bytes > 0 then c_splice r n t n (u bytes) flags else throwRecv0) (do closeFd r closeFd w try_ $ setNonBlockingMode True print ('-',r,w)) #else s <- fdToHandle fdIn t <- fdToHandle fdOut hSetBuffering s NoBuffering hSetBuffering t NoBuffering a <- mallocBytes len :: IO (Ptr Word8) print "PORTABLE-SPLICE" finally (forever $ do bytes <- hGetBufSome s a len if bytes > 0 then hPutBuf t a bytes else throwRecv0) (do free a try_ $ hClose s try_ $ hClose t) #endif -- | Similar to 'Control.Exception.Base.try' but used when an -- obvious exception is expected whose type can be safely -- ignored. try_ :: IO () -- ^ The action to run which can throw any exception. -> IO () -- ^ The new action where exceptions are silenced. try_ a = (try a :: IO (Either SomeException ())) >> return () -------------------------------------------------------------------------------- #ifdef LINUX_SPLICE -- SPLICE -- fcntl.h -- ssize_t splice( -- int fd_in, -- loff_t* off_in, -- int fd_out, -- loff_t* off_out, -- size_t len, -- unsigned int flags -- ); -- | Moves data between two file descriptors without -- copying between kernel address space and user -- address space. It transfers up to 'len' bytes of -- data from the file descriptor 'fd_in' to the file -- file descriptor 'fd_out', where one of the -- descriptors must refer to a pipe. -- -- 'c_splice' is NOT a loop and needs to called repeatedly. -- For an example, see the source code of 'splice'. foreign import ccall "splice" c_splice :: Fd -- ^ fd_in -> Ptr (#type loff_t) -- ^ off_in -> Fd -- ^ fd_out -> Ptr (#type loff_t) -- ^ off_out -> (#type size_t) -- ^ len -> Word -- ^ flags -> IO (#type ssize_t) -- ^ number of bytes moved or -1 on error -- | Attempt to move pages instead of copying. This is -- only a hint to the kernel: pages may stil be copied -- if the kernel cannot move the pages from the pipe, -- or if the pipe buffers don't refer to full pages. sPLICE_F_MOVE :: Word sPLICE_F_MOVE = (#const "SPLICE_F_MOVE") -- | More data will be coming in a subsequent splice. -- This is a helpful hint when 'fd_out' refers to a -- socket. sPLICE_F_MORE :: Word sPLICE_F_MORE = (#const "SPLICE_F_MORE") -- | Do not block on I/O. This makes the splice pipe -- operations nonblocking, but splice() may nevertheless -- block because the file descriptors that are spliced -- to/from may block (unless they have the O_NONBLOCK flag -- set). sPLICE_F_NONBLOCK :: Word sPLICE_F_NONBLOCK = (#const "SPLICE_F_NONBLOCK") #endif