packages feed

network-simple 0.4.2 → 0.4.3

raw patch · 4 files changed

+120/−15 lines, 4 filesdep +socksdep −exceptionsdep ~bytestringdep ~networkdep ~transformers

Dependencies added: socks

Dependencies removed: exceptions

Dependency ranges changed: bytestring, network, transformers

Files

changelog.md view
@@ -1,3 +1,15 @@+# Version 0.4.3++* Added SOCKS5 proxy support using the `socks` library. See functions+  `connectSOCKS5` and `connectSockSOCKS5`.++* Increased connection timeout.++* Client side sockets now have `SO_KEEPALIVE` and `TCP_NODELAY` on by default.++* Server side sockets now have `SO_KEEPALIVE` on by default.++ # Version 0.4.2  * Deprecate `sendMany` in favor of `sendLazy`.
network-simple.cabal view
@@ -1,5 +1,5 @@ name:                network-simple-version:             0.4.2+version:             0.4.3 homepage:            https://github.com/k0001/network-simple bug-reports:         https://github.com/k0001/network-simple/issues license:             BSD3@@ -35,7 +35,7 @@   other-modules:     Network.Simple.Internal   build-depends:     base         (>=4.7 && < 5)                    , network      (>=2.3)-                   , bytestring   (>=0.9.2.1)-                   , transformers (>=0.2)-                   , exceptions   (>=0.6)+                   , bytestring+                   , transformers                    , safe-exceptions+                   , socks
src/Network/Simple/Internal.hs view
@@ -13,13 +13,16 @@   , isIPv6addr   , prioritize   , happyEyeballSort+  , getServicePortNumber'   ) where -import           Data.Bits                     (shiftR, (.&.))-import qualified Data.List                     as List-import           Data.String                   (IsString (fromString))-import           Data.Word                     (byteSwap32)-import qualified Network.Socket as             NS+import Data.Bits (shiftR, (.&.))+import qualified Data.List as List+import Data.String (IsString (fromString))+import Data.Word (byteSwap16)+import qualified Network.Socket as NS+import qualified Network.BSD as NS (getServicePortNumber)+import Text.Read (readMaybe)  -- | Preferred host to bind. data HostPreference@@ -75,3 +78,13 @@ -- Sorting is stable. prioritize :: (a -> Bool) -> [a] -> [a] prioritize p = uncurry (++) . List.partition p++-- | Like 'NS.getServicePortNumber', but it will accept numeric values such as+-- @\"80\"@ as input.+getServicePortNumber' :: NS.ServiceName -> IO NS.PortNumber+getServicePortNumber' sn = case readMaybe sn of+  Just w16 -> pure (fromIntegral (byteSwap16 w16))+  Nothing -> NS.getServicePortNumber sn+    -- the @socks5@ library is broken and uses port numbers in the+    -- wrong byte order. here we work around that.+
src/Network/Simple/TCP.hs view
@@ -17,6 +17,10 @@ -- Some code in this file was adapted from the @network-conduit@ library by -- Michael Snoyman. Copyright (c) 2011. See its licensing terms (BSD3) at: --   https://github.com/snoyberg/conduit/blob/master/network-conduit/LICENSE+--+-- Some code in this file was adapted from the @socks@ library by+-- Vincent Hanquez. Copyright (c) 2010-2011. See its licensing terms (BSD3) at:+--   https://github.com/vincenthz/hs-socks/blob/master/LICENSE  module Network.Simple.TCP (   -- * Introduction to TCP networking@@ -25,6 +29,7 @@   -- * Client side   -- $client-side     connect+  , connectSOCKS5    -- * Server side   -- $server-side@@ -45,6 +50,7 @@   , bindSock   , listenSock   , connectSock+  , connectSockSOCKS5   , closeSock    -- * Note to Windows users@@ -65,9 +71,13 @@ import           Control.Monad import           Control.Monad.IO.Class         (MonadIO(liftIO)) import qualified Data.ByteString                as BS+import qualified Data.ByteString.Char8          as B8 import qualified Data.ByteString.Lazy           as BSL import           Data.List                      (transpose, partition)+import           Data.Word                      (byteSwap16) import qualified Network.Socket                 as NS+import qualified Network.Socks5.Lowlevel        as NS5+import qualified Network.Socks5.Types           as NS5 import qualified Network.Socket.ByteString      as NSB import qualified Network.Socket.ByteString.Lazy as NSBL import qualified System.IO                      as IO@@ -75,7 +85,7 @@  import Network.Simple.Internal   (HostPreference(..), hpHostName, isIPv4addr, isIPv6addr, ipv4mapped_to_ipv4,-   prioritize, happyEyeballSort)+   prioritize, happyEyeballSort, getServicePortNumber')  -------------------------------------------------------------------------------- -- $tcp-101@@ -128,6 +138,8 @@ -- -- If you prefer to acquire and close the socket yourself, then use -- 'connectSock' and 'closeSock'.+--+-- Note: The 'NS.NoDelay' and 'NS.KeepAlive' options are set on the socket. connect   :: (MonadIO m, Ex.MonadMask m)   => NS.HostName -- ^ Server hostname or IP address.@@ -137,6 +149,29 @@   -> m r connect host port = Ex.bracket (connectSock host port) (closeSock . fst) +-- | Like 'connect', but connects to the destination server through a SOCKS5+-- proxy.+connectSOCKS5+  :: (MonadIO m, Ex.MonadMask m)+  => NS.HostName -- ^ SOCKS5 proxy server hostname or IP address.+  -> NS.ServiceName -- ^ SOCKS5 proxy server service port name or number.+  -> NS.HostName+  -- ^ Destination server hostname or IP address. We connect to this host+  -- /through/ the SOCKS5 proxy specified in the previous arguments.+  --+  -- Note that if hostname resolution on this 'NS.HostName' is necessary, it+  -- will happen on the proxy side for security reasons, not locally.+  -> NS.ServiceName -- ^ Destination server service port name or number.+  -> ((NS.Socket, NS.SockAddr, NS.SockAddr) -> m r)+  -- ^ Computation taking 'NS.Socket' connected to the SOCKS5 server (through+  -- which we can interact with the destination server), the address of that+  -- SOCKS5 server, and the address of the destination server, in that order.+  -> m r+connectSOCKS5 phn psn dhn dsn k =+  connect phn psn $ \(psock, paddr) -> do+     daddr <- connectSockSOCKS5 psock dhn dsn+     k (psock, paddr, daddr)+ --------------------------------------------------------------------------------  -- $server-side@@ -191,8 +226,8 @@ -- If you prefer to acquire and close the socket yourself, then use 'bindSock' -- and 'closeSock', as well as 'listenSock' function. ----- Note: The 'NS.NoDelay' and 'NS.ReuseAddr' options are set on the socket. The--- maximum number of incoming queued connections is 2048.+-- Note: The 'NS.NoDelay', 'NS.KeepAlive' and 'NS.ReuseAddr' options are set on+-- the socket. The maximum number of incoming queued connections is 2048. listen   :: (MonadIO m, Ex.MonadMask m)   => HostPreference -- ^ Host to bind.@@ -266,6 +301,8 @@ -- Prefer to use 'connect' if you will be using the socket within a limited -- scope and would like it to be closed immediately after its usage or in case -- of exceptions.+--+-- Note: The 'NS.NoDelay' and 'NS.KeepAlive' options are set on the socket. connectSock   :: MonadIO m   => NS.HostName -- ^ Server hostname or IP address.@@ -286,9 +323,11 @@       (x:xs) -> Ex.catch (useAddr x) (\(_ :: IOError) -> tryAddrs xs)     useAddr :: NS.AddrInfo -> IO (NS.Socket, NS.SockAddr)     useAddr addr = do-       yx <- timeout 1000000 $ do -- 1 second+       yx <- timeout 3000000 $ do -- 3 seconds           Ex.bracketOnError (newSocket addr) closeSock $ \sock -> do              let sockAddr = NS.addrAddress addr+             NS.setSocketOption sock NS.NoDelay 1+             NS.setSocketOption sock NS.KeepAlive 1              NS.connect sock sockAddr              pure (sock, sockAddr)        case yx of@@ -304,7 +343,8 @@ -- within a limited scope, and would like it to be closed immediately after its -- usage or in case of exceptions. ----- Note: The 'NS.NoDelay' and 'NS.ReuseAddr' options are set on the socket.+-- Note: The 'NS.NoDelay', 'NS.KeepAlive' and 'NS.ReuseAddr' options are set on+-- the socket. bindSock   :: MonadIO m   => HostPreference -- ^ Host to bind.@@ -324,7 +364,7 @@       , NS.addrSocketType = NS.Stream }     tryAddrs :: [NS.AddrInfo] -> IO (NS.Socket, NS.SockAddr)     tryAddrs = \case-      [] -> fail "bindSock: No addresses available"+      [] -> fail "Network.Simple.TCP.bindSock: No addresses available"       [x] -> useAddr x       (x:xs) -> Ex.catch (useAddr x) (\(_ :: IOError) -> tryAddrs xs)     useAddr :: NS.AddrInfo -> IO (NS.Socket, NS.SockAddr)@@ -332,6 +372,7 @@       let sockAddr = NS.addrAddress addr       NS.setSocketOption sock NS.NoDelay 1       NS.setSocketOption sock NS.ReuseAddr 1+      NS.setSocketOption sock NS.KeepAlive 1       when (isIPv6addr addr) $ do          NS.setSocketOption sock NS.IPv6Only (if hp == HostIPv6 then 1 else 0)       NS.bind sock sockAddr@@ -344,6 +385,45 @@   Ex.catch (Ex.finally (NS.shutdown s NS.ShutdownBoth)                        (NS.close s))            (\(_ :: Ex.SomeException) -> pure ())++--------------------------------------------------------------------------------++-- | Given a 'NS.Socket' connected to a SOCKS5 proxy server, establish a+-- connection to the specified destination server through that proxy.+connectSockSOCKS5+  :: MonadIO m+  => NS.Socket+  -- ^ Socket connected to the SOCKS5 proxy server.+  --+  -- After a successful use of 'connectSockSOCKS5', all traffic exchanged+  -- through this socket will be between ourselves and the destination server.+  -> NS.HostName+  -- ^ Destination server hostname or IP address. We connect to this host+  -- /through/ the SOCKS5 proxy specified in the previous arguments.+  --+  -- Note that if hostname resolution on this 'NS.HostName' is necessary, it+  -- will happen on the proxy side for security reasons, not locally.+  -> NS.ServiceName -- ^ Destination server service port name or number.+  -> m NS.SockAddr -- ^ Address of the destination server.+connectSockSOCKS5 psock dhn dsn = liftIO $ do+   dpn :: NS.PortNumber <- do+     pn <- getServicePortNumber' dsn+     -- The @socks5@ library seems to be broken and use port numbers+     -- in the wrong byte order. Here we work around that.+     pure (fromIntegral (byteSwap16 (fromInteger (toInteger pn))))+   let dsa = NS5.SocksAddress (NS5.SocksAddrDomainName (B8.pack dhn)) dpn+   NS5.establish psock [NS5.SocksMethodNone] >>= \case+     NS5.SocksMethodNone -> do+       NS5.rpc_ psock (NS5.Connect dsa) >>= \case+         (NS5.SocksAddrIPV4 ha, p) -> pure (NS.SockAddrInet p ha)+         (NS5.SocksAddrIPV6 ha, p) -> pure (NS.SockAddrInet6 p 0 ha 0)+         _ -> err "Impossible reply from SOCKS5 server"+     r -> err ("Unsupported reply from SOCKS5 server: " ++ show r)+ where+   err :: String -> IO a+   err s = fail ("Network.Simple.TCP.connectSockSOCKS5: " ++ s)++  -------------------------------------------------------------------------------- -- Utils