Skip to content

Commit

Permalink
Add customHeaders flag
Browse files Browse the repository at this point in the history
  • Loading branch information
erebe committed Jan 30, 2022
1 parent 11a4214 commit fec2052
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 33 deletions.
80 changes: 49 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,40 +33,58 @@ wsTunnelClient <---> wsTunnelServer <---> RemoteHost
Use secure connection (wss://) to bypass proxies
wstunnel [OPTIONS] ws[s]://wstunnelServer[:port]
Client options:
-L --localToRemote=[BIND:]PORT:HOST:PORT Listen on local and forwards
traffic from remote
-D --dynamicToRemote=[BIND:]PORT Listen on local and dynamically
(with socks5 proxy) forwards
traffic from remote
-u --udp forward UDP traffic instead of
TCP
--udpTimeoutSec=INT When using udp forwarding,
timeout in seconds after when the
tunnel connection is closed.
Default 30sec, -1 means no timeout
-p --httpProxy=USER:PASS@HOST:PORT If set, will use this proxy to
connect to the server
--soMark=int (linux only) Mark network packet
with SO_MARK sockoption with the
specified value. You need to use
{root, sudo, capabilities} to run
wstunnel when using this option
--upgradePathPrefix=String 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
-L --localToRemote=[BIND:]PORT:HOST:PORT Listen on local and forwards
traffic from remote. Can be
used multiple time
-D --dynamicToRemote=[BIND:]PORT Listen on local and
dynamically (with socks5 proxy)
forwards traffic from remote
-u --udp forward UDP traffic instead
of TCP
--udpTimeoutSec=INT When using udp forwarding,
timeout in seconds after when
the tunnel connection is
closed. Default 30sec, -1 means
no timeout
-p --httpProxy=USER:PASS@HOST:PORT If set, will use this proxy
to connect to the server
--soMark=int (linux only) Mark network
packet with SO_MARK sockoption
with the specified value. You
need to use {root, sudo,
capabilities} to run wstunnel
when using this option
--upgradePathPrefix=String 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
--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 Start a server that will forward
traffic for you
-r --restrictTo=HOST:PORT Accept traffic to be forwarded
only to this service
--server Start a server that will
forward traffic for you
-r --restrictTo=HOST:PORT Accept traffic to be
forwarded only to this service
Common options:
-v --verbose Print debug information
-q --quiet Print only errors
-h --help Display help message
-V --version Print version information
-v --verbose Print debug information
-q --quiet Print only errors
```

## Examples
Expand Down
12 changes: 12 additions & 0 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
module Main where

import ClassyPrelude hiding (getArgs, head)
import Data.CaseInsensitive ( CI )
import qualified Data.CaseInsensitive as CI
import qualified Data.ByteString.Char8 as BC
import Data.List (head, (!!))
import Data.Maybe (fromMaybe)
Expand Down Expand Up @@ -35,6 +37,7 @@ data WsTunnel = WsTunnel
, tlsSNI :: String
, websocketPingFrequencySec :: Int
, wsTunnelCredentials :: String
, customHeaders :: [String]
} deriving (Show, Data, Typeable)

data WsServerInfo = WsServerInfo
Expand Down Expand Up @@ -62,6 +65,8 @@ cmdLine = WsTunnel
, 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"
&= 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"
&= 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"
Expand Down Expand Up @@ -173,6 +178,9 @@ parseProxyInfo str = do
return $ ProxySettings (BC.unpack $ head ret) (fromIntegral portNumber) 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 = do
Expand Down Expand Up @@ -242,6 +250,7 @@ runApp cfg serverInfo
, tlsSNI = BC.pack $ Main.tlsSNI cfg
, hostHeader = BC.pack $ Main.hostHeader cfg
, websocketPingFrequencySec = Main.websocketPingFrequencySec cfg
, customHeaders = parseCustomHeader <$> Main.customHeaders cfg
}

toTcpLocalToRemoteTunnelSetting cfg serverInfo (TunnelInfo lHost lPort rHost rPort) =
Expand All @@ -262,6 +271,7 @@ runApp cfg serverInfo
, tlsSNI = BC.pack $ Main.tlsSNI cfg
, hostHeader = BC.pack $ Main.hostHeader cfg
, websocketPingFrequencySec = Main.websocketPingFrequencySec cfg
, customHeaders = parseCustomHeader <$> Main.customHeaders cfg
}

toUdpLocalToRemoteTunnelSetting cfg serverInfo (TunnelInfo lHost lPort rHost rPort) =
Expand All @@ -282,6 +292,7 @@ runApp cfg serverInfo
, tlsSNI = BC.pack $ Main.tlsSNI cfg
, hostHeader = BC.pack $ Main.hostHeader cfg
, websocketPingFrequencySec = Main.websocketPingFrequencySec cfg
, customHeaders = parseCustomHeader <$> Main.customHeaders cfg
}

toDynamicTunnelSetting cfg serverInfo (TunnelInfo lHost lPort _ _) =
Expand All @@ -302,4 +313,5 @@ runApp cfg serverInfo
, tlsSNI = BC.pack $ Main.tlsSNI cfg
, hostHeader = BC.pack $ Main.hostHeader cfg
, websocketPingFrequencySec = Main.websocketPingFrequencySec cfg
, customHeaders = parseCustomHeader <$> Main.customHeaders cfg
}
3 changes: 2 additions & 1 deletion src/Tunnel.hs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ tunnelingClientP cfg@TunnelSettings{..} app conn = onError $ do
debug "Opening Websocket stream"

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

ret <- WS.runClientWithStream stream hostname (toPath cfg) WS.defaultConnectionOptions headers run
Expand Down
2 changes: 2 additions & 0 deletions src/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Data.Maybe
import System.IO (stdin, stdout)
import Data.ByteString (hGetSome, hPutStr)

import Data.CaseInsensitive ( CI )
import qualified Data.Streaming.Network as N
import qualified Network.Connection as NC
import Network.Socket (HostName, PortNumber)
Expand Down Expand Up @@ -80,6 +81,7 @@ data TunnelSettings = TunnelSettings
, hostHeader :: ByteString
, udpTimeout :: Int
, websocketPingFrequencySec :: Int
, customHeaders :: [(CI ByteString, ByteString)]
}

instance Show TunnelSettings where
Expand Down
5 changes: 5 additions & 0 deletions test/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import qualified Network.Socket.ByteString as N
import qualified Data.Conduit.Network.TLS 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 Data.ByteString (hPutStr)
import Control.Concurrent (threadDelay)
Expand Down Expand Up @@ -51,6 +53,7 @@ testTCPLocalToRemote useTLS = do
, hostHeader = "toto.com"
, tlsSNI = "toto.com"
, websocketPingFrequencySec = 30
, customHeaders = [(CI.mk "toto", "tata"), (CI.mk "titi", "tutu")]
}
let client = runClient tunnelSetting

Expand Down Expand Up @@ -112,6 +115,7 @@ testUDPLocalToRemote useTLS = do
, hostHeader = "toto.com"
, tlsSNI = "toto.com"
, websocketPingFrequencySec = 30
, customHeaders = [(CI.mk "toto", "tata"), (CI.mk "titi", "tutu")]
}
let client = runClient tunnelSetting

Expand Down Expand Up @@ -172,6 +176,7 @@ testSocks5Tunneling useTLS = do
, hostHeader = "toto.com"
, tlsSNI = "toto.com"
, websocketPingFrequencySec = 30
, customHeaders = [(CI.mk "toto", "tata"), (CI.mk "titi", "tutu")]
}
let client = runClient tunnelSetting

Expand Down
5 changes: 4 additions & 1 deletion wstunnel.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ library
, unordered-containers
, websockets >= 0.12.4.0
, iproute
, case-insensitive

default-language: Haskell2010

Expand All @@ -42,7 +43,7 @@ test-suite wstunnel-test
main-is: Spec.hs
default-extensions: NoImplicitPrelude, ScopedTypeVariables, BangPatterns, RecordWildCards
build-depends: base >= 4.5 && < 5
, async
, async
, text >= 1.2.2.1
, classy-prelude
, bytestring
Expand All @@ -52,6 +53,7 @@ test-suite wstunnel-test
, wstunnel
, hspec
, binary
, case-insensitive
ghc-options: -threaded -rtsopts -with-rtsopts=-N
default-language: Haskell2010

Expand All @@ -74,5 +76,6 @@ executable wstunnel
, text >= 1.2.2.1
, async
, wstunnel
, case-insensitive

default-language: Haskell2010

0 comments on commit fec2052

Please sign in to comment.