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 +12/−0
- network-simple.cabal +4/−4
- src/Network/Simple/Internal.hs +18/−5
- src/Network/Simple/TCP.hs +86/−6
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