2016-05-16 21:55:06 +00:00
|
|
|
{-# LANGUAGE BangPatterns #-}
|
2016-05-14 23:50:16 +00:00
|
|
|
{-# LANGUAGE DeriveDataTypeable #-}
|
|
|
|
{-# OPTIONS_GHC -fno-cse #-}
|
|
|
|
|
2016-05-11 21:39:02 +00:00
|
|
|
module Main where
|
|
|
|
|
2016-05-28 13:14:55 +00:00
|
|
|
import Tunnel
|
2016-05-14 23:50:16 +00:00
|
|
|
|
2016-05-16 21:55:06 +00:00
|
|
|
import ClassyPrelude (ByteString, guard, readMay)
|
2016-05-14 23:50:16 +00:00
|
|
|
import qualified Data.ByteString.Char8 as BC
|
2016-05-16 21:33:00 +00:00
|
|
|
import Data.Maybe (fromMaybe)
|
2016-05-14 23:50:16 +00:00
|
|
|
import System.Console.CmdArgs
|
|
|
|
import System.Environment (getArgs, withArgs)
|
2016-05-31 16:35:04 +00:00
|
|
|
import qualified System.Log.Logger as LOG
|
2016-05-14 23:50:16 +00:00
|
|
|
|
|
|
|
data WsTunnel = WsTunnel
|
|
|
|
{ localToRemote :: String
|
2016-05-28 20:32:52 +00:00
|
|
|
-- , remoteToLocal :: String
|
2016-05-14 23:50:16 +00:00
|
|
|
, wsTunnelServer :: String
|
|
|
|
, udpMode :: Bool
|
2016-05-28 20:32:52 +00:00
|
|
|
, proxy :: String
|
2016-05-14 23:50:16 +00:00
|
|
|
, serverMode :: Bool
|
|
|
|
, restrictTo :: String
|
|
|
|
, _last :: Bool
|
|
|
|
} deriving (Show, Data, Typeable)
|
|
|
|
|
2016-05-15 00:09:18 +00:00
|
|
|
data WsServerInfo = WsServerInfo
|
|
|
|
{ useTls :: !Bool
|
|
|
|
, host :: !String
|
|
|
|
, port :: !Int
|
|
|
|
} deriving (Show)
|
|
|
|
|
|
|
|
data TunnelInfo = TunnelInfo
|
|
|
|
{ localHost :: !String
|
|
|
|
, localPort :: !Int
|
|
|
|
, remoteHost :: !String
|
|
|
|
, remotePort :: !Int
|
|
|
|
} deriving (Show)
|
2016-05-14 23:50:16 +00:00
|
|
|
|
2016-05-15 23:09:56 +00:00
|
|
|
|
2016-05-14 23:50:16 +00:00
|
|
|
cmdLine :: WsTunnel
|
|
|
|
cmdLine = WsTunnel
|
2016-05-15 00:09:18 +00:00
|
|
|
{ localToRemote = def &= explicit &= name "L" &= name "localToRemote" &= typ "[BIND:]PORT:HOST:PORT"
|
|
|
|
&= help "Listen on local and forward traffic from remote" &= groupname "Client options"
|
2016-05-28 20:32:52 +00:00
|
|
|
-- , remoteToLocal = def &= explicit &= name "R" &= name "RemoteToLocal" &= typ "[BIND:]PORT:HOST:PORT"
|
|
|
|
-- &= help "Listen on remote and forward traffic from local"
|
2016-05-15 00:09:18 +00:00
|
|
|
, udpMode = def &= explicit &= name "u" &= name "udp" &= help "forward UDP traffic instead of TCP"
|
2016-05-28 19:17:48 +00:00
|
|
|
, proxy = def &= explicit &= name "p" &= name "httpProxy"
|
|
|
|
&= help "If set, will use this proxy to connect to the server" &= typ "HOST:PORT"
|
2016-05-28 20:32:52 +00:00
|
|
|
, wsTunnelServer = def &= argPos 0 &= typ "ws[s]://wstunnelServer[:port]"
|
2016-05-14 23:50:16 +00:00
|
|
|
|
2016-05-15 00:09:18 +00:00
|
|
|
, serverMode = def &= explicit &= name "server"
|
|
|
|
&= help "Start a server that will forward traffic for you" &= groupname "Server options"
|
|
|
|
, restrictTo = def &= explicit &= name "r" &= name "restrictTo"
|
|
|
|
&= help "Accept traffic to be forwarded only to this service" &= typ "HOST:PORT"
|
|
|
|
, _last = def &= explicit &= name "ツ" &= groupname "Common options"
|
|
|
|
} &= summary ( "Use the websockets protocol to tunnel {TCP,UDP} traffic\n"
|
2016-05-14 23:50:16 +00:00
|
|
|
++ "wsTunnelClient <---> wsTunnelServer <---> RemoteHost\n"
|
|
|
|
++ "Use secure connection (wss://) to bypass proxies"
|
|
|
|
)
|
|
|
|
&= helpArg [explicit, name "help", name "h"]
|
|
|
|
|
|
|
|
|
|
|
|
toPort :: String -> Int
|
|
|
|
toPort str = case readMay str of
|
|
|
|
Just por -> por
|
|
|
|
Nothing -> error $ "Invalid port number `" ++ str ++ "`"
|
|
|
|
|
|
|
|
parseServerInfo :: WsServerInfo -> String -> WsServerInfo
|
2016-05-15 00:09:18 +00:00
|
|
|
parseServerInfo server [] = server
|
2016-05-28 13:14:55 +00:00
|
|
|
parseServerInfo server ('w':'s':':':'/':'/':xs) = parseServerInfo (server {Main.useTls = False, port = 80}) xs
|
|
|
|
parseServerInfo server ('w':'s':'s':':':'/':'/':xs) = parseServerInfo (server {Main.useTls = True, port = 443}) xs
|
2016-05-15 00:09:18 +00:00
|
|
|
parseServerInfo server (':':prt) = server {port = toPort prt}
|
|
|
|
parseServerInfo server hostPath = parseServerInfo (server {host = takeWhile (/= ':') hostPath}) (dropWhile (/= ':') hostPath)
|
2016-05-14 23:50:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
parseTunnelInfo :: String -> TunnelInfo
|
|
|
|
parseTunnelInfo str = mk $ BC.unpack <$> BC.split ':' (BC.pack str)
|
|
|
|
where
|
2016-05-28 13:14:55 +00:00
|
|
|
mk [lPort, host, rPort] = TunnelInfo {localHost = "127.0.0.1", Main.localPort = toPort lPort, remoteHost = host, remotePort = toPort rPort}
|
|
|
|
mk [bind,lPort, host,rPort] = TunnelInfo {localHost = bind, Main.localPort = toPort lPort, remoteHost = host, remotePort = toPort rPort}
|
2016-05-15 00:09:18 +00:00
|
|
|
mk _ = error $ "Invalid tunneling information `" ++ str ++ "`, please use format [BIND:]PORT:HOST:PORT"
|
2016-05-14 23:50:16 +00:00
|
|
|
|
|
|
|
|
2016-05-28 19:17:48 +00:00
|
|
|
parseRestrictTo :: String -> ((ByteString, Int) -> Bool)
|
2016-05-16 21:33:00 +00:00
|
|
|
parseRestrictTo "" = const True
|
2016-05-16 21:55:06 +00:00
|
|
|
parseRestrictTo str = let (!h, !p) = fromMaybe (error "Invalid Parameter restart") parse
|
|
|
|
in (\(!hst, !port) -> hst == h && port == p)
|
2016-05-16 21:33:00 +00:00
|
|
|
where
|
|
|
|
parse = do
|
|
|
|
let ret = BC.unpack <$> BC.split ':' (BC.pack str)
|
|
|
|
guard (length ret == 2)
|
|
|
|
portNumber <- readMay $ ret !! 1 :: Maybe Int
|
2016-05-16 21:55:06 +00:00
|
|
|
return (BC.pack (head ret), portNumber)
|
2016-05-16 21:33:00 +00:00
|
|
|
|
2016-05-28 19:17:48 +00:00
|
|
|
parseProxyInfo :: String -> Maybe (String, Int)
|
|
|
|
parseProxyInfo str = do
|
|
|
|
let ret = BC.unpack <$> BC.split ':' (BC.pack str)
|
|
|
|
guard (length ret == 2)
|
|
|
|
portNumber <- readMay $ ret !! 1 :: Maybe Int
|
|
|
|
return (head ret, portNumber)
|
|
|
|
|
2016-05-11 21:39:02 +00:00
|
|
|
main :: IO ()
|
2016-05-14 23:50:16 +00:00
|
|
|
main = do
|
|
|
|
args <- getArgs
|
|
|
|
cfg <- if null args then withArgs ["--help"] (cmdArgs cmdLine) else cmdArgs cmdLine
|
|
|
|
|
|
|
|
let serverInfo = parseServerInfo (WsServerInfo False "" 0) (wsTunnelServer cfg)
|
2016-05-31 16:35:04 +00:00
|
|
|
LOG.updateGlobalLogger "wstunnel" (LOG.setLevel LOG.INFO)
|
|
|
|
|
2016-05-14 23:50:16 +00:00
|
|
|
|
|
|
|
if serverMode cfg
|
|
|
|
then putStrLn ("Starting server with opts " ++ show serverInfo )
|
2016-05-28 13:14:55 +00:00
|
|
|
>> runServer (Main.useTls serverInfo) (host serverInfo, fromIntegral $ port serverInfo) (parseRestrictTo $ restrictTo cfg)
|
2016-05-14 23:50:16 +00:00
|
|
|
else if not $ null (localToRemote cfg)
|
2016-05-15 00:09:18 +00:00
|
|
|
then let (TunnelInfo lHost lPort rHost rPort) = parseTunnelInfo (localToRemote cfg)
|
2016-05-28 13:14:55 +00:00
|
|
|
in runClient TunnelSettings { localBind = lHost
|
|
|
|
, Tunnel.localPort = fromIntegral lPort
|
|
|
|
, serverHost = host serverInfo
|
|
|
|
, serverPort = fromIntegral $ port serverInfo
|
|
|
|
, destHost = rHost
|
|
|
|
, destPort = fromIntegral rPort
|
|
|
|
, Tunnel.useTls = Main.useTls serverInfo
|
|
|
|
, protocol = if udpMode cfg then UDP else TCP
|
2016-05-28 19:17:48 +00:00
|
|
|
, proxySetting = (\(h, p) -> (h, fromIntegral p)) <$> parseProxyInfo (proxy cfg)
|
2016-05-28 13:14:55 +00:00
|
|
|
}
|
2016-05-14 23:50:16 +00:00
|
|
|
else return ()
|
|
|
|
|
|
|
|
|
|
|
|
putStrLn "Goodbye !"
|
|
|
|
return ()
|