packages feed

socks5 0.6.0.1 → 0.8.0.0

raw patch · 8 files changed

+297/−272 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Network.SOCKS5.Types: AddressDomain :: Text -> Address
- Network.SOCKS5.Types: AddressIPv4 :: IPv4 -> Address
- Network.SOCKS5.Types: AddressIPv6 :: IPv6 -> Address
- Network.SOCKS5.Types: GSSAPI :: Method
- Network.SOCKS5.Types: IANAAssigned :: Word8 -> Method
- Network.SOCKS5.Types: NoAcceptableMethods :: Method
- Network.SOCKS5.Types: NoAuth :: Method
- Network.SOCKS5.Types: PrivateMethod :: Word8 -> Method
- Network.SOCKS5.Types: UnknownMethod :: Word8 -> Method
- Network.SOCKS5.Types: UserPass :: Method
- Network.SOCKS5.Types: data Address
- Network.SOCKS5.Types: data Method
- Network.SOCKS5.Types: fromSockAddr_ :: SockAddr -> (Address, PortNumber)
- Network.SOCKS5.Types: instance Data.Binary.Class.Binary Network.SOCKS5.Types.Address
- Network.SOCKS5.Types: instance Data.Binary.Class.Binary Network.SOCKS5.Types.Method
- Network.SOCKS5.Types: instance GHC.Classes.Eq Network.SOCKS5.Types.Address
- Network.SOCKS5.Types: instance GHC.Classes.Eq Network.SOCKS5.Types.Method
- Network.SOCKS5.Types: instance GHC.Show.Show Network.SOCKS5.Types.Address
- Network.SOCKS5.Types: instance GHC.Show.Show Network.SOCKS5.Types.Method
- Network.SOCKS5.Types: toSockAddr_ :: Address -> PortNumber -> SockAddr
+ Network.SOCKS5.Client: execBind :: Connection c => HostName -> PortNumber -> ClientConfig -> c -> (HostName -> PortNumber -> IO ()) -> IO (HostName, PortNumber)
+ Network.SOCKS5.Client: execConnect :: Connection c => HostName -> PortNumber -> ClientConfig -> c -> IO ()
+ Network.SOCKS5.Client: execUDPAssociate :: Connection c => ClientConfig -> c -> IO (HostName, PortNumber)
+ Network.SOCKS5.Internal: AddressDomain :: Text -> Address
+ Network.SOCKS5.Internal: AddressIPv4 :: IPv4 -> Address
+ Network.SOCKS5.Internal: AddressIPv6 :: IPv6 -> Address
+ Network.SOCKS5.Internal: GSSAPI :: Method
+ Network.SOCKS5.Internal: IANAAssigned :: Word8 -> Method
+ Network.SOCKS5.Internal: NoAcceptableMethods :: Method
+ Network.SOCKS5.Internal: NoAuth :: Method
+ Network.SOCKS5.Internal: PrivateMethod :: Word8 -> Method
+ Network.SOCKS5.Internal: UnknownMethod :: Word8 -> Method
+ Network.SOCKS5.Internal: UserPass :: Method
+ Network.SOCKS5.Internal: data Address
+ Network.SOCKS5.Internal: data Method
+ Network.SOCKS5.Internal: fromSockAddr_ :: SockAddr -> (Address, PortNumber)
+ Network.SOCKS5.Internal: instance Data.Binary.Class.Binary Network.SOCKS5.Internal.Address
+ Network.SOCKS5.Internal: instance Data.Binary.Class.Binary Network.SOCKS5.Internal.Method
+ Network.SOCKS5.Internal: instance GHC.Classes.Eq Network.SOCKS5.Internal.Address
+ Network.SOCKS5.Internal: instance GHC.Classes.Eq Network.SOCKS5.Internal.Method
+ Network.SOCKS5.Internal: instance GHC.Show.Show Network.SOCKS5.Internal.Address
+ Network.SOCKS5.Internal: instance GHC.Show.Show Network.SOCKS5.Internal.Method
+ Network.SOCKS5.Internal: toSockAddr_ :: Address -> PortNumber -> SockAddr
- Network.SOCKS5.Client: runTCPBind :: Address -> ServiceName -> ClientConfig -> (Address -> PortNumber -> IO ()) -> (Address -> PortNumber -> Socket -> IO a) -> IO a
+ Network.SOCKS5.Client: runTCPBind :: HostName -> PortNumber -> ClientConfig -> (HostName -> PortNumber -> IO ()) -> (HostName -> PortNumber -> Socket -> IO a) -> IO a
- Network.SOCKS5.Client: runTCPBindTLS :: Address -> ServiceName -> ClientConfig -> ClientParams -> (Address -> PortNumber -> IO ()) -> (Address -> PortNumber -> Context -> IO a) -> IO a
+ Network.SOCKS5.Client: runTCPBindTLS :: HostName -> PortNumber -> ClientConfig -> ClientParams -> (HostName -> PortNumber -> IO ()) -> (HostName -> PortNumber -> Context -> IO a) -> IO a
- Network.SOCKS5.Client: runTCPConnect :: Address -> ServiceName -> ClientConfig -> (Socket -> IO a) -> IO a
+ Network.SOCKS5.Client: runTCPConnect :: HostName -> PortNumber -> ClientConfig -> (Socket -> IO a) -> IO a
- Network.SOCKS5.Client: runTCPConnectTLS :: Address -> ServiceName -> ClientConfig -> ClientParams -> (Context -> IO a) -> IO a
+ Network.SOCKS5.Client: runTCPConnectTLS :: HostName -> PortNumber -> ClientConfig -> ClientParams -> (Context -> IO a) -> IO a

Files

CHANGELOG.md view
@@ -1,29 +1,33 @@ # Revision history for socks5 -## 0.1.0.0 -- 2025-06-30+## 0.8.0.0 -- 2025-07-09 -- First version. Released on an unsuspecting world.+- Adjusted the argument of client API. Low-level API for created connections added. -## 0.2.0.0 -- 2025-07-06+## 0.6.0.1 -- 2025-07-08 -- TLS version API and server executable's CLI args added.+- Changed base requirement for example and tests -## 0.3.0.0 -- 2025-07-07+## 0.6.0.0 -- 2025-07-08 -- Adjusted the order of argument for client side API.+- Removed a record from client config. Rename test directory to tests. Added tests for TLS and UserPass authentication. +## 0.5.0.0 -- 2025-07-07++- Moved some types to separate file. Haddock documentation added. Default server port changed to 1080.+ ## 0.4.0.0 -- 2025-07-07  - UserPass authentication added, adjusted server API and CLI. -## 0.5.0.0 -- 2025-07-07+## 0.3.0.0 -- 2025-07-07 -- Moved some types to separate file. Haddock documentation added. Default server port changed to 1080.+- Adjusted the order of argument for client side API. -## 0.6.0.0 -- 2025-07-08+## 0.2.0.0 -- 2025-07-06 -- Removed a record from client config. Rename test directory to tests. Added tests for TLS and UserPass authentication.+- TLS version API and server executable's CLI args added. -## 0.6.0.1 -- 2025-07-08+## 0.1.0.0 -- 2025-06-30 -- Changed base requirement for example and tests+- First version. Released on an unsuspecting world.
Network/SOCKS5/Client.hs view
@@ -1,14 +1,24 @@ {-# LANGUAGE OverloadedStrings #-} --- | Functions to start connection using SOCKS5 proxy.+-- |+-- This module provides a client for the SOCKS5 proxy protocol+-- It offers two tiers of functions: ----- Here is an minimal example of how to use the library to connect to a remote server through a SOCKS5 proxy:+-- * High-level 'run...' functions: These are "all-in-one" helpers that+--     manage the entire connection, from connecting to the proxy to running+--     your client code. --+-- * Low-level 'exec...' functions: These functions execute a single SOCKS5+--     command on an already established connection. They provide more control+--     for users who need to manage the connection to the proxy manually.+--+-- = Example Usage+-- Here is a minimal example of how to use the library to connect to a remote server through a SOCKS5 proxy:+-- -- > {-# LANGUAGE OverloadedStrings #-} -- > -- > import qualified Data.ByteString.Char8 as C8 -- > import Network.SOCKS5.Client--- > import Network.SOCKS5.Types -- > import Network.Socket.ByteString (recv, sendAll) -- > -- > main :: IO ()@@ -19,10 +29,8 @@ -- >             proxyPort = "11451", -- >             userPass = Nothing -- >           }--- >   let destAddr = AddressDomain "example.com"--- >   let destPort = "80" -- >--- >   runTCPConnect destAddr destPort proxyConfig $ \sock -> do+-- >   runTCPConnect "example.com" 80 proxyConfig $ \sock -> do -- >     putStrLn "Connected to example.com through SOCKS5 proxy!" -- >     sendAll sock "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" -- >     response <- recv sock 4096@@ -30,15 +38,20 @@ module Network.SOCKS5.Client   ( ClientConfig (..), -    -- * SOCKS5+    -- * High-level functions     runTCPConnect,     runTCPBind,     runUDPAssociate, -    -- * SOCKS5 with TLS+    -- * High-level functions with TLS     runTCPConnectTLS,     runTCPBindTLS,     runUDPAssociateTLS,++    -- * Low-level functions+    execConnect,+    execBind,+    execUDPAssociate,   ) where @@ -46,199 +59,222 @@ import Control.Monad.State import Data.Binary (Binary, decode) import Data.ByteString qualified as B+import Data.IP import Data.Text.Lazy qualified as LT import Network.Run.TCP import Network.Run.UDP import Network.SOCKS5.Internal-import Network.SOCKS5.Types import Network.Socket import Network.Socket.ByteString qualified as SB import Network.TLS import Text.Read (readMaybe) +-- | Configuration for connecting to the SOCKS5 proxy server. data ClientConfig = ClientConfig-  { -- | Hostname or IP address of the SOCKS5 proxy server+  { -- | Hostname or IP address of the SOCKS5 proxy server.     proxyHost :: HostName,-    -- | Port number of the SOCKS5 proxy server+    -- | Port number of the SOCKS5 proxy server.     proxyPort :: ServiceName,-    -- | Username and password for UserPass authentication, `Nothing` will then use `NoAuth`+    -- | Credentials for authentication. 'Nothing' defaults to the 'NoAuth' method.+    -- Otherwise 'UserPass' will be used with the provided value.     userPass :: Maybe (LT.Text, LT.Text)   }  type StateIO a = StateT B.ByteString IO a --- | Run a TCP connection to target address and port through proxy.+-- | Establishes a TCP connection to a target through a SOCKS5 proxy using the CONNECT command. runTCPConnect ::-  -- | Destination address-  Address ->-  -- | Destination port-  ServiceName ->-  -- | Proxy configuration+  -- | Target hostname or IP address.+  HostName ->+  -- | Target port.+  PortNumber ->+  -- | SOCKS5 proxy configuration.   ClientConfig ->-  -- | Client function to run with the connected socket+  -- | The action to run with the connected 'Socket'.   (Socket -> IO a) ->   IO a-runTCPConnect destAddr destPort config client = do+runTCPConnect destHost destPort config client = do   runTCPClient (proxyHost config) (proxyPort config) $ \sock -> do-    startConnect destAddr destPort config sock client+    execConnect destHost destPort config sock+    client sock --- | Run a TCP connection to target address and port through proxy with TLS.+-- | Establishes a TLS-secured TCP connection to a target through a SOCKS5 proxy using the CONNECT command. runTCPConnectTLS ::-  -- | Destination address-  Address ->-  -- | Destination port-  ServiceName ->-  -- | Proxy configuration+  HostName ->+  PortNumber ->   ClientConfig ->-  -- | TLS parameters for the connection+  -- | TLS parameters for the connection.   ClientParams ->-  -- | Client function to run with the connected TLS context+  -- | The action to run with the TLS 'Context'.   (Context -> IO a) ->   IO a-runTCPConnectTLS destAddr destPort config params client = do+runTCPConnectTLS destHost destPort config params client = do   runTCPClient (proxyHost config) (proxyPort config) $ \sock -> do     ctx <- contextNew sock params     handshake ctx-    res <- startConnect destAddr destPort config ctx client+    execConnect destHost destPort config ctx+    res <- client ctx     bye ctx     return res -startConnect ::+-- | Executes the CONNECT command on an established connection to the proxy.+-- This function only performs the SOCKS5 negotiation.+execConnect ::   (Connection c) =>-  Address ->-  ServiceName ->+  -- | Target hostname or IP address.+  HostName ->+  -- | Target port.+  PortNumber ->+  -- | SOCKS5 proxy configuration.   ClientConfig ->+  -- | An existing connection to the proxy (e.g., a 'Socket' or 'Context').   c ->-  (c -> IO a) ->-  IO a-startConnect destAddr destPort config conn client =+  IO ()+execConnect destHost destPort config conn =   flip evalStateT B.empty $ do     performAuth config conn-    portNum <- resolvePort destPort-    encodeAndSend conn $ Request Connect destAddr portNum+    let destAddr = hostToAddress destHost+    encodeAndSend conn $ Request Connect destAddr destPort     reply <- recv' conn     case reply of-      Reply Succeeded _ _ -> liftIO $ client conn+      Reply Succeeded _ _ -> return ()       _ -> liftIO $ throwIO $ HandshakeFail reply --- | run a TCP connection to target by making proxy listen on a port and bind to it.+-- | Asks the SOCKS5 proxy to BIND to a port and waits for an incoming connection.+-- This is for protocols that require the client to accept a connection, like FTP. runTCPBind ::-  -- | Destination address-  Address ->-  -- | Destination port-  ServiceName ->-  -- | Proxy configuration+  -- | The hostname or IP address the proxy should connect to (often ignored).+  HostName ->+  -- | The port the proxy should connect to (often ignored).+  PortNumber ->   ClientConfig ->-  -- | Function to call when the bind is successful, receives the proxy's listening address and port-  (Address -> PortNumber -> IO ()) ->-  -- | Client function to run with the bound socket, receives the connected peer's address and port-  (Address -> PortNumber -> Socket -> IO a) ->+  -- | An action called after the proxy successfully binds, providing the listening address and port.+  (HostName -> PortNumber -> IO ()) ->+  -- | The action to run with the 'Socket' once a peer connects, providing the peer's address and port.+  (HostName -> PortNumber -> Socket -> IO a) ->   IO a-runTCPBind destAddr destPort config notify client = do+runTCPBind destHost destPort config notify client = do   runTCPClient (proxyHost config) (proxyPort config) $ \sock -> do-    startBind destAddr destPort config sock notify client+    (hostAddr, hostPort) <- execBind destHost destPort config sock notify+    client hostAddr hostPort sock --- | run a TCP connection to target by making proxy listen on a port and bind to it with TLS.+-- | Asks the SOCKS5 proxy to BIND over a TLS-secured connection. runTCPBindTLS ::-  -- | Destination address-  Address ->-  -- | Destination port-  ServiceName ->-  -- | Proxy configuration+  HostName ->+  PortNumber ->   ClientConfig ->-  -- | TLS parameters for the connection   ClientParams ->-  -- | Function to call when the bind is successful, receives the proxy's listening address and port-  (Address -> PortNumber -> IO ()) ->-  -- | Client function to run with the bound TLS context, receives the connected peer's address and port-  (Address -> PortNumber -> Context -> IO a) ->+  -- | An action called after the proxy successfully binds, providing the listening address and port.+  (HostName -> PortNumber -> IO ()) ->+  -- | The action to run with the 'Context' once a peer connects, providing the peer's address and port.+  (HostName -> PortNumber -> Context -> IO a) ->   IO a-runTCPBindTLS destAddr destPort config params notify client = do+runTCPBindTLS destHost destPort config params notify client = do   runTCPClient (proxyHost config) (proxyPort config) $ \sock -> do     ctx <- contextNew sock params     handshake ctx-    res <- startBind destAddr destPort config ctx notify client+    (hostAddr, hostPort) <- execBind destHost destPort config ctx notify+    res <- client hostAddr hostPort ctx     bye ctx     return res -startBind ::+-- | Executes the BIND command on an established connection.+-- This function handles the two-stage reply from the proxy.+execBind ::   (Connection c) =>-  Address ->-  ServiceName ->+  -- | The hostname or IP address the proxy should connect to (often ignored).+  HostName ->+  -- | The port the proxy should connect to (often ignored).+  PortNumber ->+  -- | SOCKS5 proxy configuration.   ClientConfig ->+  -- | An existing connection to the proxy (e.g., a 'Socket' or 'Context').   c ->-  (Address -> PortNumber -> IO ()) ->-  (Address -> PortNumber -> c -> IO a) ->-  IO a-startBind destAddr destPort config conn notify client =+  -- | An action called after the proxy successfully binds.+  (HostName -> PortNumber -> IO ()) ->+  -- | Returns the hostname/IP and port of the connecting peer.+  IO (HostName, PortNumber)+execBind destHost destPort config conn notify =   flip evalStateT B.empty $ do     performAuth config conn-    portNum <- resolvePort destPort-    encodeAndSend conn $ Request Bind destAddr portNum+    let destAddr = hostToAddress destHost+    encodeAndSend conn $ Request Bind destAddr destPort     reply1 <- recv' conn     case reply1 of       Reply Succeeded listenAddr listenPort -> do-        liftIO $ notify listenAddr listenPort+        liftIO $ notify (show listenAddr) listenPort         reply2 <- recv' conn         case reply2 of           Reply Succeeded hostAddr hostPort ->-            liftIO $ client hostAddr hostPort conn+            return (show hostAddr, hostPort)           _ -> liftIO $ throwIO $ HandshakeFail reply2       _ -> liftIO $ throwIO $ HandshakeFail reply1 --- | Start a UDP relay on proxy server and run a client function with the connected UDP socket.+-- | Establishes a UDP ASSOCIATION with the SOCKS5 proxy, enabling UDP traffic to be relayed. runUDPAssociate ::-  -- | Proxy configuration   ClientConfig ->-  -- | Client function to run with the connected UDP socket, receives a function to send data and a function to receive data+  -- | Action to run with the connected UDP socket, providing function to send and receive datagrams.   ((B.ByteString -> SockAddr -> IO ()) -> IO (B.ByteString, SockAddr) -> IO a) ->   IO a runUDPAssociate config client = do   runTCPClient (proxyHost config) (proxyPort config) $ \sockTCP -> do-    startUDPAssociate config sockTCP client+    (relayHost, relayPort) <- execUDPAssociate config sockTCP+    runUDPClient relayHost (show relayPort) $ \sockUDP proxySockAddr -> do+      let sendDataTo :: B.ByteString -> SockAddr -> IO ()+          sendDataTo userData destination = do+            let (dstAddr, dstPort) = fromSockAddr_ destination+            sendUDPRequestTo sockUDP 0 dstAddr dstPort (B.fromStrict userData) proxySockAddr+          recvDataFrom :: IO (B.ByteString, SockAddr)+          recvDataFrom = do+            (datagram, _) <- SB.recvFrom sockUDP 8192+            let udpReply = decode (B.fromStrict datagram) :: UDPRequest+                originalSender = toSockAddr_ (address udpReply) (port udpReply)+            return (B.toStrict (payload udpReply), originalSender)+      client sendDataTo recvDataFrom --- | Start a UDP relay on proxy server with TLS and run a client function with the connected UDP socket. Note that the UDP socket is not encrypted, only the initial connection to the proxy is secured.+-- | Establishes a UDP association over a TLS-secured control connection.+-- Note: The control connection to the proxy is secured with TLS, but the resulting+-- UDP traffic is not encrypted. runUDPAssociateTLS ::-  -- | Proxy configuration   ClientConfig ->-  -- | TLS parameters for the connection   ClientParams ->-  -- | Client function to run with the connected UDP socket, receives a function to send data and a function to receive data+  -- | Action to run with the connected UDP socket, providing function to send and receive datagrams.   ((B.ByteString -> SockAddr -> IO ()) -> IO (B.ByteString, SockAddr) -> IO a) ->   IO a runUDPAssociateTLS config params client = do   runTCPClient (proxyHost config) (proxyPort config) $ \sockTCP -> do     ctx <- contextNew sockTCP params     handshake ctx-    res <- startUDPAssociate config ctx client+    (relayHost, relayPort) <- execUDPAssociate config ctx+    res <- runUDPClient relayHost (show relayPort) $ \sockUDP proxySockAddr -> do+      let sendDataTo :: B.ByteString -> SockAddr -> IO ()+          sendDataTo userData destination = do+            let (dstAddr, dstPort) = fromSockAddr_ destination+            sendUDPRequestTo sockUDP 0 dstAddr dstPort (B.fromStrict userData) proxySockAddr+          recvDataFrom :: IO (B.ByteString, SockAddr)+          recvDataFrom = do+            (datagram, _) <- SB.recvFrom sockUDP 8192+            let udpReply = decode (B.fromStrict datagram) :: UDPRequest+                originalSender = toSockAddr_ (address udpReply) (port udpReply)+            return (B.toStrict (payload udpReply), originalSender)+      client sendDataTo recvDataFrom     bye ctx     return res -startUDPAssociate ::+-- | Executes the UDP ASSOCIATE command on an established connection.+execUDPAssociate ::   (Connection c) =>   ClientConfig ->   c ->-  ((B.ByteString -> SockAddr -> IO ()) -> IO (B.ByteString, SockAddr) -> IO a) ->-  IO a-startUDPAssociate config conn client =+  -- | Returns the address and port of the UDP relay on the proxy server.+  IO (HostName, PortNumber)+execUDPAssociate config conn =   flip evalStateT B.empty $ do     performAuth config conn     encodeAndSend conn $ Request UDPAssociate (AddressIPv4 "0.0.0.0") 0     reply <- recv' conn     case reply of-      Reply Succeeded relayAddr relayPort -> liftIO $ do-        runUDPClient (show relayAddr) (show relayPort) $ \sockUDP proxySockAddr -> do-          let sendDataTo :: B.ByteString -> SockAddr -> IO ()-              sendDataTo userData destination = do-                let (dstAddr, dstPort) = fromSockAddr_ destination-                sendUDPRequestTo sockUDP 0 dstAddr dstPort (B.fromStrict userData) proxySockAddr-              recvDataFrom :: IO (B.ByteString, SockAddr)-              recvDataFrom = do-                (datagram, _) <- SB.recvFrom sockUDP 8192-                let udpReply = decode (B.fromStrict datagram) :: UDPRequest-                    originalSender = toSockAddr_ (address udpReply) (port udpReply)-                return (B.toStrict (payload udpReply), originalSender)-          client sendDataTo recvDataFrom+      Reply Succeeded relayAddr relayPort -> return (show relayAddr, relayPort)       _ -> liftIO $ throwIO $ HandshakeFail reply  performAuth :: (Connection c) => ClientConfig -> c -> StateIO ()@@ -260,11 +296,11 @@         _ -> liftIO $ throwIO AuthMissingCredentials     _ -> liftIO $ throwIO $ AuthUnsupported (method selectedMethod) -resolvePort :: ServiceName -> StateIO PortNumber-resolvePort port = liftIO $-  case readMaybe port of-    Just p -> return p-    Nothing -> throwIO $ PortStringInvalid port+hostToAddress :: HostName -> Address+hostToAddress host = case readMaybe host :: Maybe IP of+  Just (IPv4 ipv4) -> AddressIPv4 ipv4+  Just (IPv6 ipv6) -> AddressIPv6 ipv6+  Nothing -> AddressDomain (LT.pack host)  recv' :: (Binary b, Connection c) => c -> StateIO b recv' conn = do
Network/SOCKS5/Internal.hs view
@@ -1,17 +1,21 @@ module Network.SOCKS5.Internal-  ( Hello (..),+  ( Method (..),+    Hello (..),     MethodSelection (..),     Command (..),+    Address (..),     Request (..),     Rep (..),     Reply (..),     UDPRequest (..),     sendUDPRequestTo,     UserPassRequest (..),-    UserPassResponse (..),     Status (..),+    UserPassResponse (..),     SOCKSException (..),     Connection (..),+    fromSockAddr_,+    toSockAddr_,     recvAndDecode,     encodeAndSend,   )@@ -25,9 +29,9 @@ import Data.Binary.Put import Data.ByteString qualified as B import Data.ByteString.Lazy qualified as LB+import Data.IP import Data.Text.Lazy qualified as LT import Data.Text.Lazy.Encoding qualified as LTE-import Network.SOCKS5.Types import Network.Socket import Network.Socket.ByteString qualified as SB import Network.Socket.ByteString.Lazy qualified as LSB@@ -42,6 +46,46 @@ --    |  1  |    1     | 1 to 255 | --    +-----+----------+----------+ +--  The values currently defined for METHOD are:+--    X'00': NO AUTHENTICATION REQUIRED+--    X'01': GSSAPI+--    X'02': USERNAME/PASSWORD+--    X'03': to X'7F' IANA ASSIGNED+--    X'80': to X'FE' RESERVED FOR PRIVATE METHODS+--    X'FF': NO ACCEPTABLE METHODS++data Method+  = NoAuth+  | GSSAPI+  | UserPass+  | IANAAssigned Word8+  | PrivateMethod Word8+  | NoAcceptableMethods+  | UnknownMethod Word8+  deriving (Eq, Show)++instance Binary Method where+  put :: Method -> Put+  put NoAuth = putWord8 0x00+  put GSSAPI = putWord8 0x01+  put UserPass = putWord8 0x02+  put (IANAAssigned m) = putWord8 m+  put (PrivateMethod m) = putWord8 m+  put NoAcceptableMethods = putWord8 0xFF+  put (UnknownMethod m) = putWord8 m++  get :: Get Method+  get = do+    m <- getWord8+    case m of+      0x00 -> return NoAuth+      0x01 -> return GSSAPI+      0x02 -> return UserPass+      _ | m >= 0x03 && m <= 0x7F -> return $ IANAAssigned m+      _ | m >= 0x80 && m <= 0xFE -> return $ PrivateMethod m+      0xFF -> return NoAcceptableMethods+      _ -> return $ UnknownMethod m+ newtype Hello = Hello   { methods :: [Method]   }@@ -126,6 +170,62 @@       0x03 -> return UDPAssociate       _ -> fail $ "Unknown command: " ++ show cmd +-- | Address type used for destination addresses in SOCKS5 protocol.+data Address+  = AddressIPv4 IPv4+  | AddressIPv6 IPv6+  | AddressDomain LT.Text+  deriving (Eq)++instance Show Address where+  show :: Address -> String+  show (AddressIPv4 ip) = show ip+  show (AddressIPv6 ip) = show ip+  show (AddressDomain name) = LT.unpack name++instance Binary Address where+  put :: Address -> Put+  put (AddressIPv4 ip) = do+    putWord8 0x01+    putWord32be $ fromIPv4w ip+  put (AddressIPv6 ip) = do+    putWord8 0x04+    let (a1, a2, a3, a4) = fromIPv6w ip+     in mapM_ putWord32be [a1, a2, a3, a4]+  put (AddressDomain name) = do+    putWord8 0x03+    let encodedName = LTE.encodeUtf8 name+        byteLength = LB.length encodedName+    if byteLength > fromIntegral (maxBound :: Word8)+      then+        error $+          "DomainName (UTF-8 encoded) has length of "+            ++ show byteLength+            ++ " bytes. Max allowed is "+            ++ show (maxBound :: Word8)+      else putWord8 $ fromIntegral byteLength+    putLazyByteString encodedName++  get :: Get Address+  get = do+    addrType <- getWord8+    case addrType of+      0x01 -> do+        AddressIPv4 . toIPv4w <$> getWord32be+      0x04 -> do+        a1 <- getWord32be+        a2 <- getWord32be+        a3 <- getWord32be+        a4 <- getWord32be+        return $ AddressIPv6 $ toIPv6w (a1, a2, a3, a4)+      0x03 -> do+        len <- getWord8+        encodedName <- getLazyByteString (fromIntegral len)+        case LTE.decodeUtf8' encodedName of+          Left err -> fail $ "Failed to decode UTF-8 domain name: " ++ show err+          Right name -> return $ AddressDomain name+      _ -> fail $ "Unknown address type: " ++ show addrType+ data Request = Request   { command :: Command,     destinationAddress :: Address,@@ -405,6 +505,16 @@    connSend :: Context -> LB.ByteString -> IO ()   connSend = sendData++fromSockAddr_ :: SockAddr -> (Address, PortNumber)+fromSockAddr_ (SockAddrInet port host) = (AddressIPv4 (fromHostAddress host), fromIntegral port)+fromSockAddr_ (SockAddrInet6 port _ host _) = (AddressIPv6 (toIPv6w host), fromIntegral port)+fromSockAddr_ path = error $ "Unexpected Unix socket address: " ++ show path++toSockAddr_ :: Address -> PortNumber -> SockAddr+toSockAddr_ (AddressIPv4 host) port = SockAddrInet (fromIntegral port) $ toHostAddress host+toSockAddr_ (AddressIPv6 host) port = SockAddrInet6 (fromIntegral port) 0 (toHostAddress6 host) 0+toSockAddr_ (AddressDomain host) _ = error $ "Address conversion for DomainName not implemented: " ++ show host  recvAndDecode :: (Binary a, Connection c, MonadIO m) => c -> B.ByteString -> m (a, B.ByteString) recvAndDecode conn buffer = liftIO $ go $ pushChunk (runGetIncremental get) buffer
Network/SOCKS5/Server.hs view
@@ -1,14 +1,11 @@ {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} --- | Functions to run a SOCKS5 server.+-- |+-- This module provides a server implementation for the SOCKS5 proxy protocol. module Network.SOCKS5.Server   ( ServerConfig (..),--    -- * SOCKS5 Server     runSOCKS5Server,--    -- * SOCKS5 Server with TLS     runSOCKS5ServerTLS,   ) where@@ -25,18 +22,20 @@ import Data.Text.Lazy qualified as LT import Network.Run.TCP import Network.SOCKS5.Internal-import Network.SOCKS5.Types import Network.Socket import Network.Socket.ByteString qualified as SB import Network.TLS import System.IO.Error +-- | Configuration for the SOCKS5 server. data ServerConfig = ServerConfig-  { -- | Hostname or IP address to bind the server to+  { -- | Hostname or IP address to bind the server to.     serverHost :: HostName,-    -- | Port number to listen on+    -- | Port number to listen on.     serverPort :: ServiceName,     -- | List of User/Password pairs for UserPass authentication+    --  If empty, only 'NoAuth' method will be available.+    -- Otherwise, 'UserPass' authentication is required.     users :: [(LT.Text, LT.Text)]   } @@ -54,8 +53,12 @@     handle (\(e :: SomeException) -> putStrLn $ show clientAddr ++ ": " ++ show e) $       newClient config sockTCP sockTCP --- | Run a SOCKS5 server with TLS support.-runSOCKS5ServerTLS :: ServerConfig -> ServerParams -> IO ()+-- | Run a SOCKS5 server with the control channel secured by TLS.+runSOCKS5ServerTLS ::+  ServerConfig ->+  -- | TLS parameters for the server (e.g., certificate and key).+  ServerParams ->+  IO () runSOCKS5ServerTLS config params = do   let host = serverHost config       port = serverPort config
− Network/SOCKS5/Types.hs
@@ -1,123 +0,0 @@--- Types you may want to import.-module Network.SOCKS5.Types-  ( Method (..),-    Address (..),-    fromSockAddr_,-    toSockAddr_,-  )-where--import Data.Binary-import Data.Binary.Get-import Data.Binary.Put-import Data.ByteString.Lazy qualified as LB-import Data.IP-import Data.Text.Lazy qualified as LT-import Data.Text.Lazy.Encoding qualified as LTE-import Network.Socket----  The values currently defined for METHOD are:---    X'00': NO AUTHENTICATION REQUIRED---    X'01': GSSAPI---    X'02': USERNAME/PASSWORD---    X'03': to X'7F' IANA ASSIGNED---    X'80': to X'FE' RESERVED FOR PRIVATE METHODS---    X'FF': NO ACCEPTABLE METHODS--data Method-  = NoAuth-  | GSSAPI-  | UserPass-  | IANAAssigned Word8-  | PrivateMethod Word8-  | NoAcceptableMethods-  | UnknownMethod Word8-  deriving (Eq, Show)--instance Binary Method where-  put :: Method -> Put-  put NoAuth = putWord8 0x00-  put GSSAPI = putWord8 0x01-  put UserPass = putWord8 0x02-  put (IANAAssigned m) = putWord8 m-  put (PrivateMethod m) = putWord8 m-  put NoAcceptableMethods = putWord8 0xFF-  put (UnknownMethod m) = putWord8 m--  get :: Get Method-  get = do-    m <- getWord8-    case m of-      0x00 -> return NoAuth-      0x01 -> return GSSAPI-      0x02 -> return UserPass-      _ | m >= 0x03 && m <= 0x7F -> return $ IANAAssigned m-      _ | m >= 0x80 && m <= 0xFE -> return $ PrivateMethod m-      0xFF -> return NoAcceptableMethods-      _ -> return $ UnknownMethod m---- | Address type used for destination addresses in SOCKS5 protocol.-data Address-  = AddressIPv4 IPv4-  | AddressIPv6 IPv6-  | AddressDomain LT.Text-  deriving (Eq)--instance Show Address where-  show :: Address -> String-  show (AddressIPv4 ip) = show ip-  show (AddressIPv6 ip) = show ip-  show (AddressDomain name) = LT.unpack name--instance Binary Address where-  put :: Address -> Put-  put (AddressIPv4 ip) = do-    putWord8 0x01-    putWord32be $ fromIPv4w ip-  put (AddressIPv6 ip) = do-    putWord8 0x04-    let (a1, a2, a3, a4) = fromIPv6w ip-     in mapM_ putWord32be [a1, a2, a3, a4]-  put (AddressDomain name) = do-    putWord8 0x03-    let encodedName = LTE.encodeUtf8 name-        byteLength = LB.length encodedName-    if byteLength > fromIntegral (maxBound :: Word8)-      then-        error $-          "DomainName (UTF-8 encoded) has length of "-            ++ show byteLength-            ++ " bytes. Max allowed is "-            ++ show (maxBound :: Word8)-      else putWord8 $ fromIntegral byteLength-    putLazyByteString encodedName--  get :: Get Address-  get = do-    addrType <- getWord8-    case addrType of-      0x01 -> do-        AddressIPv4 . toIPv4w <$> getWord32be-      0x04 -> do-        a1 <- getWord32be-        a2 <- getWord32be-        a3 <- getWord32be-        a4 <- getWord32be-        return $ AddressIPv6 $ toIPv6w (a1, a2, a3, a4)-      0x03 -> do-        len <- getWord8-        encodedName <- getLazyByteString (fromIntegral len)-        case LTE.decodeUtf8' encodedName of-          Left err -> fail $ "Failed to decode UTF-8 domain name: " ++ show err-          Right name -> return $ AddressDomain name-      _ -> fail $ "Unknown address type: " ++ show addrType--fromSockAddr_ :: SockAddr -> (Address, PortNumber)-fromSockAddr_ (SockAddrInet port host) = (AddressIPv4 (fromHostAddress host), fromIntegral port)-fromSockAddr_ (SockAddrInet6 port _ host _) = (AddressIPv6 (toIPv6w host), fromIntegral port)-fromSockAddr_ path = error $ "Unexpected Unix socket address: " ++ show path--toSockAddr_ :: Address -> PortNumber -> SockAddr-toSockAddr_ (AddressIPv4 host) port = SockAddrInet (fromIntegral port) $ toHostAddress host-toSockAddr_ (AddressIPv6 host) port = SockAddrInet6 (fromIntegral port) 0 (toHostAddress6 host) 0-toSockAddr_ (AddressDomain host) _ = error $ "Address conversion for DomainName not implemented: " ++ show host
example/Main.hs view
@@ -2,7 +2,6 @@  import qualified Data.ByteString.Char8 as C8 import Network.SOCKS5.Client-import Network.SOCKS5.Types import Network.Socket.ByteString (recv, sendAll)  main :: IO ()@@ -13,10 +12,8 @@             proxyPort = "11451",             userPass = Nothing           }-  let destAddr = AddressDomain "example.com"-  let destPort = "80" -  runTCPConnect destAddr destPort proxyConfig $ \sock -> do+  runTCPConnect "example.com" 80 proxyConfig $ \sock -> do     putStrLn "Connected to example.com through SOCKS5 proxy!"     sendAll sock "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"     response <- recv sock 4096
socks5.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: socks5-version: 0.6.0.1+version: 0.8.0.0 synopsis: A SOCKS5 (RFC 1928) implementation description:   This package provides a library with simple functions to create SOCKS5 clients and servers, along with a standalone server executable.@@ -30,7 +30,6 @@     Network.SOCKS5.Client     Network.SOCKS5.Internal     Network.SOCKS5.Server-    Network.SOCKS5.Types    build-depends:     async ^>=2.2,
tests/Spec.hs view
@@ -10,7 +10,6 @@ import Network.Run.TCP import Network.SOCKS5.Client import Network.SOCKS5.Server-import Network.SOCKS5.Types import Network.Socket import Network.Socket.ByteString import Network.TLS@@ -22,8 +21,8 @@   aroundAll withSOCKS5Server $ do     describe "Network.SOCKS5.Client" $ do       it "should run TCP connect successfully to ipinfo.io" $ \proxyConfig -> do-        let destAddr = AddressDomain "ipinfo.io"-            destPort = "80"+        let destAddr = "ipinfo.io"+            destPort = 80         runTCPConnect destAddr destPort proxyConfig $ \sock -> do           let request = C8.pack "GET / HTTP/1.1\r\nHost: ipinfo.io\r\nConnection: close\r\n\r\n"           putStrLn "Sending HTTP GET request..."@@ -37,8 +36,8 @@           response `shouldSatisfy` ("HTTP/1.1 200 OK" `B.isPrefixOf`)        it "should run BIND command successfully" $ \proxyConfig -> do-        let destAddr = AddressIPv4 "8.8.8.8"-            destPort = "80"+        let destAddr = "8.8.8.8"+            destPort = 80         runTCPBind           destAddr           destPort@@ -47,7 +46,7 @@               void $ async $ do                 threadDelay 200000                 putStrLn $ "Third-party connector attempting to connect to " ++ show boundAddr ++ ":" ++ show boundPort-                runTCPClient (show boundAddr) (show boundPort) $ \remoteSock -> do+                runTCPClient boundAddr (show boundPort) $ \remoteSock -> do                   putStrLn "Third-party connector: connection successful."                   forever $ sendAll remoteSock "PING"           )@@ -79,8 +78,8 @@   aroundAll withSOCKS5ServerTLS $ do     describe "Network.SOCKS5.Client" $ do       it "should run TCP connect successfully to ipinfo.io with TLS" $ \(proxyConfig, clientParams) -> do-        let destAddr = AddressDomain "ipinfo.io"-            destPort = "80"+        let destAddr = "ipinfo.io"+            destPort = 80         runTCPConnectTLS destAddr destPort proxyConfig clientParams $ \ctx -> do           let request = C8.pack "GET / HTTP/1.1\r\nHost: ipinfo.io\r\nConnection: close\r\n\r\n"           putStrLn "Sending HTTP GET request..."@@ -94,8 +93,8 @@           response `shouldSatisfy` ("HTTP/1.1 200 OK" `B.isPrefixOf`)        it "should run BIND command successfully with TLS" $ \(proxyConfig, clientParams) -> do-        let destAddr = AddressIPv4 "8.8.8.8"-            destPort = "80"+        let destAddr = "8.8.8.8"+            destPort = 80         runTCPBindTLS           destAddr           destPort@@ -105,7 +104,7 @@               void $ async $ do                 threadDelay 200000                 putStrLn $ "Third-party connector attempting to connect to " ++ show boundAddr ++ ":" ++ show boundPort-                runTCPClient (show boundAddr) (show boundPort) $ \remoteSock -> do+                runTCPClient boundAddr (show boundPort) $ \remoteSock -> do                   putStrLn "Third-party connector: connection successful."                   sendAll remoteSock "PING"           )