2016-06-01 20:01:23 +00:00
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
2016-05-28 13:16:12 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
|
|
|
module Protocols where
|
|
|
|
|
|
|
|
import ClassyPrelude
|
|
|
|
import Control.Concurrent (forkIO)
|
|
|
|
import qualified Data.HashMap.Strict as H
|
2019-10-24 11:46:58 +00:00
|
|
|
import System.IO hiding (hSetBuffering, hGetBuffering)
|
2016-05-28 13:16:12 +00:00
|
|
|
|
|
|
|
import qualified Data.ByteString.Char8 as BC
|
|
|
|
|
|
|
|
import qualified Data.Streaming.Network as N
|
|
|
|
|
|
|
|
import Network.Socket (HostName, PortNumber)
|
|
|
|
import qualified Network.Socket as N hiding (recv, recvFrom, send,
|
|
|
|
sendTo)
|
|
|
|
import qualified Network.Socket.ByteString as N
|
|
|
|
|
2016-08-27 16:31:32 +00:00
|
|
|
import Data.Binary (decode, encode)
|
2016-05-28 13:16:12 +00:00
|
|
|
|
2016-08-27 16:31:32 +00:00
|
|
|
import Logger
|
|
|
|
import qualified Socks5
|
|
|
|
import Types
|
2016-05-28 13:16:12 +00:00
|
|
|
|
2016-05-28 19:17:48 +00:00
|
|
|
|
2018-12-28 08:48:50 +00:00
|
|
|
runSTDIOServer :: (StdioAppData -> IO ()) -> IO ()
|
|
|
|
runSTDIOServer app = do
|
|
|
|
stdin_old_buffering <- hGetBuffering stdin
|
|
|
|
stdout_old_buffering <- hGetBuffering stdout
|
|
|
|
|
|
|
|
hSetBuffering stdin (BlockBuffering (Just 512))
|
|
|
|
hSetBuffering stdout NoBuffering
|
|
|
|
|
|
|
|
void $ forever $ app StdioAppData
|
|
|
|
|
|
|
|
hSetBuffering stdin stdin_old_buffering
|
|
|
|
hSetBuffering stdout stdout_old_buffering
|
|
|
|
info $ "CLOSE stdio server"
|
|
|
|
|
2016-05-28 13:16:12 +00:00
|
|
|
runTCPServer :: (HostName, PortNumber) -> (N.AppData -> IO ()) -> IO ()
|
2016-06-01 20:01:23 +00:00
|
|
|
runTCPServer endPoint@(host, port) app = do
|
|
|
|
info $ "WAIT for tcp connection on " <> toStr endPoint
|
2020-01-04 12:28:36 +00:00
|
|
|
let srvSet = N.setReadBufferSize defaultRecvBufferSize $ N.serverSettingsTCP (fromIntegral port) (fromString host)
|
|
|
|
void $ N.runTCPServer srvSet app
|
2016-06-01 20:01:23 +00:00
|
|
|
info $ "CLOSE tcp server on " <> toStr endPoint
|
2016-05-28 13:16:12 +00:00
|
|
|
|
|
|
|
runTCPClient :: (HostName, PortNumber) -> (N.AppData -> IO ()) -> IO ()
|
2016-06-01 20:01:23 +00:00
|
|
|
runTCPClient endPoint@(host, port) app = do
|
|
|
|
info $ "CONNECTING to " <> toStr endPoint
|
2020-01-04 12:28:36 +00:00
|
|
|
let srvSet = N.setReadBufferSize defaultRecvBufferSize $ N.clientSettingsTCP (fromIntegral port) (BC.pack host)
|
|
|
|
void $ N.runTCPClient srvSet app
|
2016-06-01 20:01:23 +00:00
|
|
|
info $ "CLOSE connection to " <> toStr endPoint
|
2016-05-28 13:16:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
runUDPClient :: (HostName, PortNumber) -> (UdpAppData -> IO ()) -> IO ()
|
2016-06-01 20:01:23 +00:00
|
|
|
runUDPClient endPoint@(host, port) app = do
|
|
|
|
info $ "SENDING datagrammes to " <> toStr endPoint
|
2016-05-28 13:16:12 +00:00
|
|
|
bracket (N.getSocketUDP host (fromIntegral port)) (N.close . fst) $ \(socket, addrInfo) -> do
|
|
|
|
sem <- newEmptyMVar
|
|
|
|
app UdpAppData { appAddr = N.addrAddress addrInfo
|
|
|
|
, appSem = sem
|
|
|
|
, appRead = fst <$> N.recvFrom socket 4096
|
2016-11-26 15:59:21 +00:00
|
|
|
, appWrite = \payload -> void $ N.sendAllTo socket payload (N.addrAddress addrInfo)
|
2016-05-28 13:16:12 +00:00
|
|
|
}
|
|
|
|
|
2016-06-01 20:01:23 +00:00
|
|
|
info $ "CLOSE udp connection to " <> toStr endPoint
|
2016-05-28 13:16:12 +00:00
|
|
|
|
|
|
|
|
2019-01-13 16:47:18 +00:00
|
|
|
runUDPServer :: (HostName, PortNumber) -> Int -> (UdpAppData -> IO ()) -> IO ()
|
|
|
|
runUDPServer endPoint@(host, port) cnxTimeout app = do
|
2016-06-01 20:01:23 +00:00
|
|
|
info $ "WAIT for datagrames on " <> toStr endPoint
|
2016-05-28 13:16:12 +00:00
|
|
|
clientsCtx <- newIORef mempty
|
|
|
|
void $ bracket (N.bindPortUDP (fromIntegral port) (fromString host)) N.close (runEventLoop clientsCtx)
|
2016-06-01 20:01:23 +00:00
|
|
|
info $ "CLOSE udp server" <> toStr endPoint
|
2016-05-28 13:16:12 +00:00
|
|
|
|
|
|
|
where
|
|
|
|
addNewClient :: IORef (H.HashMap N.SockAddr UdpAppData) -> N.Socket -> N.SockAddr -> ByteString -> IO UdpAppData
|
|
|
|
addNewClient clientsCtx socket addr payload = do
|
|
|
|
sem <- newMVar payload
|
|
|
|
let appData = UdpAppData { appAddr = addr
|
|
|
|
, appSem = sem
|
|
|
|
, appRead = takeMVar sem
|
2016-11-26 15:59:21 +00:00
|
|
|
, appWrite = \payload' -> void $ N.sendAllTo socket payload' addr
|
2016-05-28 13:16:12 +00:00
|
|
|
}
|
|
|
|
void $ atomicModifyIORef' clientsCtx (\clients -> (H.insert addr appData clients, ()))
|
|
|
|
return appData
|
|
|
|
|
|
|
|
removeClient :: IORef (H.HashMap N.SockAddr UdpAppData) -> UdpAppData -> IO ()
|
|
|
|
removeClient clientsCtx clientCtx = do
|
|
|
|
void $ atomicModifyIORef' clientsCtx (\clients -> (H.delete (appAddr clientCtx) clients, ()))
|
2016-06-01 20:01:23 +00:00
|
|
|
debug "TIMEOUT connection"
|
2016-05-28 13:16:12 +00:00
|
|
|
|
|
|
|
pushDataToClient :: UdpAppData -> ByteString -> IO ()
|
2016-11-26 15:59:21 +00:00
|
|
|
pushDataToClient clientCtx payload = putMVar (appSem clientCtx) payload
|
|
|
|
`catch` (\(_ :: SomeException) -> debug $ "DROP udp packet, client thread dead")
|
|
|
|
-- If we are unlucky the client's thread died before we had the time to push the data on a already full mutex
|
|
|
|
-- and will leave us waiting forever for the mutex to empty. So catch the exeception and drop the message.
|
|
|
|
-- Udp is not a reliable protocol so transmission failure should be handled by the application layer
|
2016-05-28 13:16:12 +00:00
|
|
|
|
|
|
|
runEventLoop :: IORef (H.HashMap N.SockAddr UdpAppData) -> N.Socket -> IO ()
|
|
|
|
runEventLoop clientsCtx socket = forever $ do
|
|
|
|
(payload, addr) <- N.recvFrom socket 4096
|
|
|
|
clientCtx <- H.lookup addr <$> readIORef clientsCtx
|
|
|
|
|
|
|
|
case clientCtx of
|
|
|
|
Just clientCtx' -> pushDataToClient clientCtx' payload
|
|
|
|
_ -> void . forkIO $ bracket
|
|
|
|
(addNewClient clientsCtx socket addr payload)
|
|
|
|
(removeClient clientsCtx)
|
2019-01-13 16:47:18 +00:00
|
|
|
(void . timeout cnxTimeout . app)
|
2016-08-27 16:31:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
runSocks5Server :: Socks5.ServerSettings -> TunnelSettings -> (TunnelSettings -> N.AppData -> IO()) -> IO ()
|
|
|
|
runSocks5Server socksSettings@Socks5.ServerSettings{..} cfg inner = do
|
|
|
|
info $ "Starting socks5 proxy " <> show socksSettings
|
|
|
|
|
|
|
|
N.runTCPServer (N.serverSettingsTCP (fromIntegral listenOn) (fromString bindOn)) $ \cnx -> do
|
|
|
|
-- Get the auth request and response with a no Auth
|
|
|
|
authRequest <- decode . fromStrict <$> N.appRead cnx :: IO Socks5.ResponseAuth
|
|
|
|
debug $ "Socks5 authentification request " <> show authRequest
|
|
|
|
let responseAuth = encode $ Socks5.ResponseAuth (fromIntegral Socks5.socksVersion) Socks5.NoAuth
|
|
|
|
N.appWrite cnx (toStrict responseAuth)
|
|
|
|
|
|
|
|
-- Get the request and update dynamically the tunnel config
|
|
|
|
request <- decode . fromStrict <$> N.appRead cnx :: IO Socks5.Request
|
|
|
|
debug $ "Socks5 forward request " <> show request
|
|
|
|
let responseRequest = encode $ Socks5.Response (fromIntegral Socks5.socksVersion) Socks5.SUCCEEDED (Socks5.addr request) (Socks5.port request)
|
|
|
|
let cfg' = cfg { destHost = Socks5.addr request, destPort = Socks5.port request }
|
|
|
|
N.appWrite cnx (toStrict responseRequest)
|
|
|
|
|
|
|
|
inner cfg' cnx
|
|
|
|
|
|
|
|
info $ "Closing socks5 proxy " <> show socksSettings
|