Add customHeaders flag

Former-commit-id: fec205291e26e8d64fca5ff753580b1a64592601
Former-commit-id: 6050a5408343c2d07a02cbcbf778ba3148a42126 [formerly 902076332615bb34f38b9c2ed51bcb165aa08fcd] [formerly 3c3c90b0bf53a73818cecf5a8ab8e2d649dad35e [formerly 0bdea96822211a4eb95a90435c66049965e7aff5 [formerly 0bdea96822211a4eb95a90435c66049965e7aff5 [formerly 0bdea96822211a4eb95a90435c66049965e7aff5 [formerly 9ce5c1a09c9945223df26609b9e725b3f93f014e]]]]]
Former-commit-id: 1c79ed2bd07cbf476f69b06ed9a134a63dd285cb [formerly 2cc27e84320fdc7245b64515b082f29bd24cf299]
Former-commit-id: c213a08c5adfe7857635db8d2b1c0d836a6e1207
Former-commit-id: 86624be4dfa00559808a1ee3d3568a455f99e5e2
Former-commit-id: 8f7937f2320beb2356c8cf8af7241d452cacd3cf
Former-commit-id: 4b30c019bfd2c847698cec7c2980e7139e813f24 [formerly ed0aa0b199a4e417c3edfb2764ae8b0a90f908ad]
Former-commit-id: 5473698ef4f0b331d8622af6a9131cf6a2674c59
This commit is contained in:
Romain GERARD 2022-01-30 16:39:13 +01:00
parent f851d9e361
commit 8bd805b0d3
6 changed files with 74 additions and 33 deletions

View file

@ -33,40 +33,58 @@ wsTunnelClient <---> wsTunnelServer <---> RemoteHost
Use secure connection (wss://) to bypass proxies Use secure connection (wss://) to bypass proxies
wstunnel [OPTIONS] ws[s]://wstunnelServer[:port] wstunnel [OPTIONS] ws[s]://wstunnelServer[:port]
Client options: Client options:
-L --localToRemote=[BIND:]PORT:HOST:PORT Listen on local and forwards -L --localToRemote=[BIND:]PORT:HOST:PORT Listen on local and forwards
traffic from remote traffic from remote. Can be
-D --dynamicToRemote=[BIND:]PORT Listen on local and dynamically used multiple time
(with socks5 proxy) forwards -D --dynamicToRemote=[BIND:]PORT Listen on local and
traffic from remote dynamically (with socks5 proxy)
-u --udp forward UDP traffic instead of forwards traffic from remote
TCP -u --udp forward UDP traffic instead
of TCP
--udpTimeoutSec=INT When using udp forwarding, --udpTimeoutSec=INT When using udp forwarding,
timeout in seconds after when the timeout in seconds after when
tunnel connection is closed. the tunnel connection is
Default 30sec, -1 means no timeout closed. Default 30sec, -1 means
-p --httpProxy=USER:PASS@HOST:PORT If set, will use this proxy to no timeout
connect to the server -p --httpProxy=USER:PASS@HOST:PORT If set, will use this proxy
--soMark=int (linux only) Mark network packet to connect to the server
with SO_MARK sockoption with the --soMark=int (linux only) Mark network
specified value. You need to use packet with SO_MARK sockoption
{root, sudo, capabilities} to run with the specified value. You
wstunnel when using this option need to use {root, sudo,
--upgradePathPrefix=String Use a specific prefix that will capabilities} to run wstunnel
show up in the http path in the when using this option
upgrade request. Useful if you need --upgradePathPrefix=String Use a specific prefix that
to route requests server side but will show up in the http path
don't have vhosts in the upgrade request. Useful
if you need to route requests
server side but don't have
vhosts
--hostHeader=String If set, add the custom string
as host http header
--tlsSNI=String If set, use custom string in
the SNI during TLS handshake
--websocketPingFrequencySec=int do a hearthbeat ping every x
seconds to maintain websocket
connection
--upgradeCredentials=USER[:PASS] Credentials for the Basic
HTTP authorization type sent
with the upgrade request.
-H --customHeaders="HeaderName: HeaderValue" Send custom headers in the
upgrade request. Can be used
multiple time
-h --help Display help message
-V --version Print version information
Server options: Server options:
--server Start a server that will forward --server Start a server that will
traffic for you forward traffic for you
-r --restrictTo=HOST:PORT Accept traffic to be forwarded -r --restrictTo=HOST:PORT Accept traffic to be
only to this service forwarded only to this service
Common options: Common options:
-v --verbose Print debug information -v --verbose Print debug information
-q --quiet Print only errors -q --quiet Print only errors
-h --help Display help message
-V --version Print version information
``` ```
## Examples ## Examples

View file

@ -6,6 +6,8 @@
module Main where module Main where
import ClassyPrelude hiding (getArgs, head) import ClassyPrelude hiding (getArgs, head)
import Data.CaseInsensitive ( CI )
import qualified Data.CaseInsensitive as CI
import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Char8 as BC
import Data.List (head, (!!)) import Data.List (head, (!!))
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
@ -35,6 +37,7 @@ data WsTunnel = WsTunnel
, tlsSNI :: String , tlsSNI :: String
, websocketPingFrequencySec :: Int , websocketPingFrequencySec :: Int
, wsTunnelCredentials :: String , wsTunnelCredentials :: String
, customHeaders :: [String]
} deriving (Show, Data, Typeable) } deriving (Show, Data, Typeable)
data WsServerInfo = WsServerInfo data WsServerInfo = WsServerInfo
@ -62,6 +65,8 @@ cmdLine = WsTunnel
, udpMode = def &= explicit &= name "u" &= name "udp" &= help "forward UDP traffic instead of TCP" &= groupname "Client options" , udpMode = def &= explicit &= name "u" &= name "udp" &= help "forward UDP traffic instead of TCP" &= groupname "Client options"
, udpTimeout = def &= explicit &= name "udpTimeoutSec" &= help "When using udp forwarding, timeout in seconds after when the tunnel connection is closed. Default 30sec, -1 means no timeout" , udpTimeout = def &= explicit &= name "udpTimeoutSec" &= help "When using udp forwarding, timeout in seconds after when the tunnel connection is closed. Default 30sec, -1 means no timeout"
&= groupname "Client options" &= groupname "Client options"
, customHeaders = def &= explicit &= name "H" &= name "customHeaders" &= help "Send custom headers in the upgrade request. Can be used multiple time"
&= typ "\"HeaderName: HeaderValue\"" &= groupname "Client options"
, pathPrefix = def &= explicit &= name "upgradePathPrefix" , pathPrefix = def &= explicit &= name "upgradePathPrefix"
&= help "Use a specific prefix that will show up in the http path in the upgrade request. Useful if you need to route requests server side but don't have vhosts" &= help "Use a specific prefix that will show up in the http path in the upgrade request. Useful if you need to route requests server side but don't have vhosts"
&= typ "String" &= groupname "Client options" &= typ "String" &= groupname "Client options"
@ -173,6 +178,9 @@ parseProxyInfo str = do
return $ ProxySettings (BC.unpack $ head ret) (fromIntegral portNumber) Nothing return $ ProxySettings (BC.unpack $ head ret) (fromIntegral portNumber) Nothing
else Nothing else Nothing
parseCustomHeader :: String -> (CI ByteString, ByteString)
parseCustomHeader header = (CI.mk . BC.pack $ takeWhile (/= ':') header, BC.pack . dropWhile (\c -> c == ' ' || c == ':') $ (dropWhile (/= ':') header))
main :: IO () main :: IO ()
main = do main = do
@ -242,6 +250,7 @@ runApp cfg serverInfo
, tlsSNI = BC.pack $ Main.tlsSNI cfg , tlsSNI = BC.pack $ Main.tlsSNI cfg
, hostHeader = BC.pack $ Main.hostHeader cfg , hostHeader = BC.pack $ Main.hostHeader cfg
, websocketPingFrequencySec = Main.websocketPingFrequencySec cfg , websocketPingFrequencySec = Main.websocketPingFrequencySec cfg
, customHeaders = parseCustomHeader <$> Main.customHeaders cfg
} }
toTcpLocalToRemoteTunnelSetting cfg serverInfo (TunnelInfo lHost lPort rHost rPort) = toTcpLocalToRemoteTunnelSetting cfg serverInfo (TunnelInfo lHost lPort rHost rPort) =
@ -262,6 +271,7 @@ runApp cfg serverInfo
, tlsSNI = BC.pack $ Main.tlsSNI cfg , tlsSNI = BC.pack $ Main.tlsSNI cfg
, hostHeader = BC.pack $ Main.hostHeader cfg , hostHeader = BC.pack $ Main.hostHeader cfg
, websocketPingFrequencySec = Main.websocketPingFrequencySec cfg , websocketPingFrequencySec = Main.websocketPingFrequencySec cfg
, customHeaders = parseCustomHeader <$> Main.customHeaders cfg
} }
toUdpLocalToRemoteTunnelSetting cfg serverInfo (TunnelInfo lHost lPort rHost rPort) = toUdpLocalToRemoteTunnelSetting cfg serverInfo (TunnelInfo lHost lPort rHost rPort) =
@ -282,6 +292,7 @@ runApp cfg serverInfo
, tlsSNI = BC.pack $ Main.tlsSNI cfg , tlsSNI = BC.pack $ Main.tlsSNI cfg
, hostHeader = BC.pack $ Main.hostHeader cfg , hostHeader = BC.pack $ Main.hostHeader cfg
, websocketPingFrequencySec = Main.websocketPingFrequencySec cfg , websocketPingFrequencySec = Main.websocketPingFrequencySec cfg
, customHeaders = parseCustomHeader <$> Main.customHeaders cfg
} }
toDynamicTunnelSetting cfg serverInfo (TunnelInfo lHost lPort _ _) = toDynamicTunnelSetting cfg serverInfo (TunnelInfo lHost lPort _ _) =
@ -302,4 +313,5 @@ runApp cfg serverInfo
, tlsSNI = BC.pack $ Main.tlsSNI cfg , tlsSNI = BC.pack $ Main.tlsSNI cfg
, hostHeader = BC.pack $ Main.hostHeader cfg , hostHeader = BC.pack $ Main.hostHeader cfg
, websocketPingFrequencySec = Main.websocketPingFrequencySec cfg , websocketPingFrequencySec = Main.websocketPingFrequencySec cfg
, customHeaders = parseCustomHeader <$> Main.customHeaders cfg
} }

View file

@ -63,7 +63,8 @@ tunnelingClientP cfg@TunnelSettings{..} app conn = onError $ do
debug "Opening Websocket stream" debug "Opening Websocket stream"
stream <- connectionToStream conn stream <- connectionToStream conn
let headers = if not (null upgradeCredentials) then [("Authorization", "Basic " <> B64.encode upgradeCredentials)] else [] let authorization = if not (null upgradeCredentials) then [("Authorization", "Basic " <> B64.encode upgradeCredentials)] else []
let headers = authorization <> customHeaders
let hostname = if not (null hostHeader) then (BC.unpack hostHeader) else serverHost let hostname = if not (null hostHeader) then (BC.unpack hostHeader) else serverHost
ret <- WS.runClientWithStream stream hostname (toPath cfg) WS.defaultConnectionOptions headers run ret <- WS.runClientWithStream stream hostname (toPath cfg) WS.defaultConnectionOptions headers run

View file

@ -11,6 +11,7 @@ import Data.Maybe
import System.IO (stdin, stdout) import System.IO (stdin, stdout)
import Data.ByteString (hGetSome, hPutStr) import Data.ByteString (hGetSome, hPutStr)
import Data.CaseInsensitive ( CI )
import qualified Data.Streaming.Network as N import qualified Data.Streaming.Network as N
import qualified Network.Connection as NC import qualified Network.Connection as NC
import Network.Socket (HostName, PortNumber) import Network.Socket (HostName, PortNumber)
@ -80,6 +81,7 @@ data TunnelSettings = TunnelSettings
, hostHeader :: ByteString , hostHeader :: ByteString
, udpTimeout :: Int , udpTimeout :: Int
, websocketPingFrequencySec :: Int , websocketPingFrequencySec :: Int
, customHeaders :: [(CI ByteString, ByteString)]
} }
instance Show TunnelSettings where instance Show TunnelSettings where

View file

@ -9,6 +9,8 @@ import qualified Network.Socket.ByteString as N
import qualified Data.Conduit.Network.TLS as N import qualified Data.Conduit.Network.TLS as N
import qualified Data.Streaming.Network as N import qualified Data.Streaming.Network as N
import Data.CaseInsensitive ( CI )
import qualified Data.CaseInsensitive as CI
import Control.Concurrent.Async as Async import Control.Concurrent.Async as Async
import Data.ByteString (hPutStr) import Data.ByteString (hPutStr)
import Control.Concurrent (threadDelay) import Control.Concurrent (threadDelay)
@ -51,6 +53,7 @@ testTCPLocalToRemote useTLS = do
, hostHeader = "toto.com" , hostHeader = "toto.com"
, tlsSNI = "toto.com" , tlsSNI = "toto.com"
, websocketPingFrequencySec = 30 , websocketPingFrequencySec = 30
, customHeaders = [(CI.mk "toto", "tata"), (CI.mk "titi", "tutu")]
} }
let client = runClient tunnelSetting let client = runClient tunnelSetting
@ -112,6 +115,7 @@ testUDPLocalToRemote useTLS = do
, hostHeader = "toto.com" , hostHeader = "toto.com"
, tlsSNI = "toto.com" , tlsSNI = "toto.com"
, websocketPingFrequencySec = 30 , websocketPingFrequencySec = 30
, customHeaders = [(CI.mk "toto", "tata"), (CI.mk "titi", "tutu")]
} }
let client = runClient tunnelSetting let client = runClient tunnelSetting
@ -172,6 +176,7 @@ testSocks5Tunneling useTLS = do
, hostHeader = "toto.com" , hostHeader = "toto.com"
, tlsSNI = "toto.com" , tlsSNI = "toto.com"
, websocketPingFrequencySec = 30 , websocketPingFrequencySec = 30
, customHeaders = [(CI.mk "toto", "tata"), (CI.mk "titi", "tutu")]
} }
let client = runClient tunnelSetting let client = runClient tunnelSetting

View file

@ -33,6 +33,7 @@ library
, unordered-containers , unordered-containers
, websockets >= 0.12.4.0 , websockets >= 0.12.4.0
, iproute , iproute
, case-insensitive
default-language: Haskell2010 default-language: Haskell2010
@ -52,6 +53,7 @@ test-suite wstunnel-test
, wstunnel , wstunnel
, hspec , hspec
, binary , binary
, case-insensitive
ghc-options: -threaded -rtsopts -with-rtsopts=-N ghc-options: -threaded -rtsopts -with-rtsopts=-N
default-language: Haskell2010 default-language: Haskell2010
@ -74,5 +76,6 @@ executable wstunnel
, text >= 1.2.2.1 , text >= 1.2.2.1
, async , async
, wstunnel , wstunnel
, case-insensitive
default-language: Haskell2010 default-language: Haskell2010