socks5-0.6.0.0: Network/SOCKS5/Server.hs
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Functions to run a SOCKS5 server.
module Network.SOCKS5.Server
( ServerConfig (..),
-- * SOCKS5 Server
runSOCKS5Server,
-- * SOCKS5 Server with TLS
runSOCKS5ServerTLS,
)
where
import Control.Concurrent.Async
import Control.Exception
import Control.Monad
import Control.Monad.State
import Data.Bifunctor
import Data.Binary (Binary, decode)
import Data.ByteString qualified as B
import Data.IORef
import Data.List.NonEmpty qualified as NE
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
data ServerConfig = ServerConfig
{ -- | Hostname or IP address to bind the server to
serverHost :: HostName,
-- | Port number to listen on
serverPort :: ServiceName,
-- | List of User/Password pairs for UserPass authentication
users :: [(LT.Text, LT.Text)]
}
type StateIO a = StateT B.ByteString IO a
-- | Run a SOCKS5 server with the given configuration.
runSOCKS5Server :: ServerConfig -> IO ()
runSOCKS5Server config = do
let host = serverHost config
port = serverPort config
putStrLn $ "Starting SOCKS5 server on " ++ host ++ ":" ++ port
runTCPServer (Just host) port $ \sockTCP -> do
clientAddr <- getPeerName sockTCP
putStrLn $ "Accepted new client connection from " ++ show clientAddr
handle (\(e :: SomeException) -> putStrLn $ show clientAddr ++ ": " ++ show e) $
newClient config sockTCP sockTCP
-- | Run a SOCKS5 server with TLS support.
runSOCKS5ServerTLS :: ServerConfig -> ServerParams -> IO ()
runSOCKS5ServerTLS config params = do
let host = serverHost config
port = serverPort config
putStrLn $ "Starting SOCKS5 server on " ++ host ++ ":" ++ port ++ " (TLS: on)"
runTCPServer (Just host) port $ \sockTCP -> do
clientAddr <- getPeerName sockTCP
putStrLn $ "Accepted new client connection from " ++ show clientAddr
handle (\(e :: SomeException) -> putStrLn $ show clientAddr ++ ": " ++ show e) $ do
ctx <- contextNew sockTCP params
handshake ctx
newClient config ctx sockTCP
bye ctx
newClient :: (Connection c) => ServerConfig -> c -> Socket -> IO ()
newClient config conn clientSock = do
void $ flip runStateT B.empty $ do
handleAuthentication config conn
req <- recv' conn
liftIO $ putStrLn $ "Received SOCKS5 Request: " ++ show req
case command req of
Connect -> liftIO $ handleConnect req conn clientSock
Bind -> liftIO $ handleBind req conn clientSock
UDPAssociate -> liftIO $ handleUDPAssociate req conn clientSock
handleAuthentication :: (Connection c) => ServerConfig -> c -> StateIO ()
handleAuthentication config conn = do
auths <- methods <$> recv' conn
let noUsers = null $ users config
selectedMethod <-
if
| NoAuth `elem` auths && noUsers -> do
encodeAndSend conn $ MethodSelection NoAuth
return NoAuth
| UserPass `elem` auths && not noUsers -> do
encodeAndSend conn $ MethodSelection UserPass
return UserPass
| otherwise -> liftIO $ throwIO NoAcceptableAuthMethods
when (selectedMethod == UserPass) $ do
UserPassRequest uname passwd <- recv' conn
let credentials = (uname, passwd)
if credentials `elem` users config
then encodeAndSend conn $ UserPassResponse Success
else do
encodeAndSend conn $ UserPassResponse Failure
liftIO $ throwIO $ AuthFailed Failure
handleConnect :: (Connection c) => Request -> c -> Socket -> IO ()
handleConnect (Request _ addr port) conn clientSock = do
handle (replyError conn) $
runTCPClient (show addr) (show port) $ \destSock -> do
putStrLn "Successfully connected to destination server."
(selfAddr, selfPort) <- fromSockAddr_ <$> getSocketName clientSock
encodeAndSend conn $ Reply Succeeded selfAddr selfPort
putStrLn $ "Sent SOCKS5 Reply: Succeeded, bound to " ++ show selfAddr ++ ":" ++ show selfPort
let forwardToDest = do
clientData <- connRecv conn
unless (B.null clientData) $ do
SB.sendAll destSock clientData
forwardToDest
let forwardToClient = do
serverData <- SB.recv destSock 4096
unless (B.null serverData) $ do
connSend conn $ B.fromStrict serverData
forwardToClient
concurrently_ forwardToDest forwardToClient
putStrLn "Data forwarding complete. Closing dest connection."
handleBind :: (Connection c) => Request -> c -> Socket -> IO ()
handleBind (Request _ _destAddr _destPort) conn clientSock =
handle (replyError conn) $
bracket (newTCPSocket "0.0.0.0") close $ \listenSock -> do
(selfAddr, _) <- fromSockAddr_ <$> getSocketName clientSock
(_, listenPort) <- fromSockAddr_ <$> getSocketName listenSock
putStrLn $ "Listening for connections on " ++ show selfAddr ++ ":" ++ show listenPort
encodeAndSend conn $ Reply Succeeded selfAddr listenPort
bracket (accept listenSock) (\(destSock, _) -> close destSock) $ \(destSock, destSockAddr) -> do
let (destAddr, destPort) = fromSockAddr_ destSockAddr
putStrLn $ "Accepted connection from " ++ show destAddr ++ ":" ++ show destPort
encodeAndSend conn $ Reply Succeeded destAddr destPort
let forwardToDest = do
clientData <- connRecv conn
unless (B.null clientData) $ do
SB.sendAll destSock clientData
forwardToDest
let forwardToClient = do
serverData <- SB.recv destSock 4096
unless (B.null serverData) $ do
connSend conn $ B.fromStrict serverData
forwardToClient
concurrently_ forwardToDest forwardToClient
putStrLn "Data forwarding complete. Closing dest connection."
where
newTCPSocket host = do
addrInfo <- head <$> getAddrInfo (Just defaultHints {addrFlags = [AI_PASSIVE], addrSocketType = Stream}) (Just host) (Just "0")
sock <- openSocket addrInfo
bind sock (addrAddress addrInfo)
listen sock 1
return sock
-- The UDP relay server MUST acquire from the SOCKS server the expected
-- IP address of the client that will send datagrams to the BND.PORT
-- given in the reply to UDP ASSOCIATE. It MUST drop any datagrams
-- arriving from any source IP address other than the one recorded for
-- the particular association.
handleUDPAssociate :: (Connection c) => Request -> c -> Socket -> IO ()
handleUDPAssociate (Request _ destAddr destPort) conn clientSock =
handle (replyError clientSock) $
bracket (newUDPSocket "0.0.0.0") close $ \relaySock -> do
(selfAddr, _) <- fromSockAddr_ <$> getSocketName clientSock
(_, relayPort) <- fromSockAddr_ <$> getSocketName relaySock
encodeAndSend conn $ Reply Succeeded selfAddr relayPort
(expectedClient, _) <- fromSockAddr_ <$> getPeerName clientSock
putStrLn $ "UDP association created for " ++ show expectedClient ++ ". Relaying on port " ++ show relayPort ++ "."
-- If the client is not in possesion of the information at the time of the UDP
-- ASSOCIATE, the client MUST use a port number and address of all zeros.
let expectedDest = case (destAddr, destPort) of
(AddressIPv4 "0.0.0.0", 0) -> Nothing
(AddressIPv6 "::", 0) -> Nothing
(AddressDomain _, _) -> Nothing
_ -> Just destAddr
-- A UDP association terminates when the TCP connection that the UDP ASSOCIATE request arrived on terminates.
withAsync (forwardUDP expectedClient expectedDest relaySock) $ \_ -> do
putStrLn "UDP association active. Waiting for TCP control connection to close."
waitForTCPClose clientSock
putStrLn "TCP control connection closed. Terminating UDP association."
where
newUDPSocket host = do
addrInfo <- head <$> getAddrInfo (Just defaultHints {addrFlags = [AI_PASSIVE], addrSocketType = Datagram}) (Just host) (Just "0")
sock <- openSocket addrInfo
bind sock (addrAddress addrInfo)
return sock
waitForTCPClose sock = do
chunk <- SB.recv sock 1024
unless (B.null chunk) $ waitForTCPClose sock
forwardUDP :: Address -> Maybe Address -> Socket -> IO ()
forwardUDP expectedClientAddr expectedDestAddr relaySock = do
putStrLn $ "Waiting UDP packets from client: " ++ show expectedClientAddr ++ " and dest: " ++ show expectedDestAddr
-- Use an IORef to store the client address once received, we don't know port yet
clientUDPSockAddr <- newIORef Nothing
forever $ do
(datagram, sourceUDPSockAddr) <- first B.fromStrict <$> SB.recvFrom relaySock 8192
let (sourceUDPAddr, _) = fromSockAddr_ sourceUDPSockAddr
-- sourceUDPAddr is used to compare only the address, not the port
mClientSockAddr <- readIORef clientUDPSockAddr
case mClientSockAddr of
Nothing ->
when (sourceUDPAddr == expectedClientAddr) $ do
putStrLn $ "Accepted first UDP packet from client at " ++ show sourceUDPSockAddr
writeIORef clientUDPSockAddr (Just sourceUDPSockAddr)
handleClientPacket datagram
Just clientSockAddr ->
if
| sourceUDPSockAddr == clientSockAddr -> handleClientPacket datagram
| isExpectedDest sourceUDPAddr -> handleRemotePacket datagram sourceUDPSockAddr clientSockAddr
| otherwise -> putStrLn $ "Received packet from unexpected source: " ++ show sourceUDPSockAddr
where
handleClientPacket datagram = do
let (UDPRequest _ addr port load) = decode datagram :: UDPRequest
destAddr <- NE.head <$> getAddrInfo (Just defaultHints {addrSocketType = Datagram}) (Just $ show addr) (Just $ show port)
SB.sendAllTo relaySock (B.toStrict load) (addrAddress destAddr)
isExpectedDest addr = case expectedDestAddr of
Nothing -> True
Just destAddr -> addr == destAddr
handleRemotePacket datagram sourceSockAddr clientSockAddr = do
let (remoteAddr, remotePort) = fromSockAddr_ sourceSockAddr
sendUDPRequestTo relaySock 0 remoteAddr remotePort datagram clientSockAddr
recv' :: (Binary b, Connection c) => c -> StateIO b
recv' conn = do
buffer <- get
(val, left) <- liftIO $ recvAndDecode conn buffer
put left
return val
replyError :: (Connection c) => c -> SomeException -> IO ()
replyError conn e = do
let rep = case fromException e of
Just (ioe :: IOException) ->
if
| isDoesNotExistError ioe -> HostUnreachable
| isPermissionError ioe -> ConnectionNotAllowedByRuleset
| otherwise -> GeneralSOCKSFailure
_ -> GeneralSOCKSFailure
putStrLn $ "Replying with error: " ++ show rep
encodeAndSend conn $ Reply rep (AddressIPv4 "0.0.0.0") 0
throwIO e