moesocks 0.1.0.17 → 0.1.0.18
raw patch · 11 files changed
+265/−142 lines, 11 files
Files
- CHANGELOG.md +5/−1
- README.md +33/−12
- config.json +5/−5
- moesocks.cabal +15/−15
- src/Network/MoeSocks/App.hs +54/−43
- src/Network/MoeSocks/Config.hs +7/−7
- src/Network/MoeSocks/Constant.hs +4/−7
- src/Network/MoeSocks/Helper.hs +15/−9
- src/Network/MoeSocks/Options.hs +112/−29
- src/Network/MoeSocks/TCP.hs +12/−12
- src/Network/MoeSocks/Type.hs +3/−2
CHANGELOG.md view
@@ -1,6 +1,10 @@+0.1.0.18+--------+* Add complete command line arguments+ 0.1.0.17 ---------* Add `--no-socks5` flag to switch off the socks5 service on local+* Add `--disable-socks5` flag to switch off the socks5 service on local 0.1.0.16 --------
README.md view
@@ -1,7 +1,6 @@ MoeSocks ======== - A socks5 proxy using the client / server architecture. MoeSocks is greatly inspired by [shadowsocks].@@ -11,40 +10,62 @@ type `moesocks --help` for help. +Usage+-----+* Edit `config.json` to fit your setup (at least the `remote` and `password`+ fields)+* Start a remote node outside the firewall: + + moesocks --role remote -c config.json+* Start a local node inside the firewall: + + moesocks --role local -c config.json+* Now you have a socks5 proxy running inside the firewall using port + `localPort`.+ Features ---------* TCP port forwarding -* UDP port forwarding+* Socks5 proxy service, obviously+* TCP port forwarding+* UDP port forwarding, for example `-U 5300:8.8.8.8:53` * TCP per connection throttling (as a side effect of trying to find a bug in the remote)+* Socks5 service on local can be turned off+* Understand shadowsocks' configuration file Not working ------------* Remote is flaky +* Remote is flaky, particularly when trying to send data through the Great+ Firewall of China (GFW). * UDP over Socks5 is not implemented-* 2.5 times slower then the original Python implementation (measured at 17.6M/s- vs 43.6M/s on an Intel P8800, using the AES-256-CFB encryption)+* More then 2 times slower then the original Python implementation (measured at+ 20M/s vs 43M/s on an Intel P8800, using the AES-256-CFB method) Planning features ------------------ * None -Note+Notes ------ -There's a bug that prevents remote from working correctly.+There's a bug that prevents remote from working correctly with GFW.+Thanks, Prof Fang! You should use the original Python implementation of [shadowsocks] on the remote-server.+server if your primary goal is to bypass internet censorship in China. -There is an earlier implementation of [shadowsocks in Haskell] by rnons that+From the author's limited testing, [shadowsocks-go] also works reasonably+well, though it might not run as fast as the Python's version.++There is an earlier implementation of [shadowsocks-haskell] by rnons that makes MoeSocks possible. The original goal of MoeSocks is to provide extra configurability to standard-shadowsocks, but it has been discarded since the remote is too flaky. +shadowsocks, but it has been discarded since remote is too flaky. [shadowsocks]:https://github.com/shadowsocks/shadowsocks -[shadowsocks in Haskell]:https://github.com/rnons/shadowsocks-haskell+[shadowsocks-go]:https://github.com/shadowsocks/shadowsocks-go+[shadowsocks-haskell]:https://github.com/rnons/shadowsocks-haskell
config.json view
@@ -1,9 +1,9 @@ {- "server":"localhost",- "server_port":1190,- "local_address":"localhost",- "local_port":1090,+ "remote":"0.0.0.0",+ "remotePort":1190,+ "local":"localhost",+ "localPort":1090, "password":"birthday!",- "method":"aes-256-cfb"+ "method":"aes-256-cfb" }
moesocks.cabal view
@@ -1,6 +1,6 @@ name: moesocks category: Network-version: 0.1.0.17+version: 0.1.0.18 license: Apache-2.0 synopsis: A functional firewall killer description: A socks5 proxy using the client / server architecture.@@ -11,7 +11,7 @@ build-type: Simple cabal-version: >=1.10 copyright: Copyright (C) 2015 Jinjing Wang-tested-with: GHC == 7.10.1+tested-with: GHC == 7.10.2 extra-source-files: config.json , README.md@@ -26,27 +26,27 @@ ghc-options: -Wall -fno-warn-unused-do-bind -threaded -rtsopts "-with-rtsopts=-N" build-depends: base > 4 && <= 5- , lens - , network+ , HsOpenSSL+ , aeson+ , async+ , attoparsec , binary , bytestring- , unix- , attoparsec- , text- , random- , optparse-applicative- , aeson- , unordered-containers- , HsOpenSSL , containers , cryptohash- , safe , hslogger- , transformers+ , lens , mtl+ , network+ , optparse-applicative+ , random+ , safe , stm- , async+ , text , time+ , transformers+ , unix+ , unordered-containers hs-source-dirs: src default-language: Haskell2010
src/Network/MoeSocks/App.hs view
@@ -35,10 +35,35 @@ import qualified System.Log.Handler as LogHandler -parseConfig :: Text -> MoeMonadT MoeConfig-parseConfig aFilePath = do- _configFile <- io - TIO.readFile - aFilePath ^. _Text+parseConfig :: MoeOptions -> MoeMonadT MoeConfig+parseConfig aOption = do+ let+ formatConfig :: Value -> Value+ formatConfig (Object _obj) =+ Object -+ _obj+ & H.toList + & over (mapped . _1) T.tail + & H.fromList+ formatConfig _ = Null + showConfig :: MoeConfig -> Text+ showConfig = view utf8 + . toStrict + . encode + . formatConfig + . toJSON ++ let _maybeFilePath = aOption ^. configFile ++ _v <- case _maybeFilePath of+ Nothing -> pure - Just - Object mempty+ Just _filePath -> do+ _data <- io - TIO.readFile - _filePath ^. _Text+ pure -+ (decodeStrict - review utf8 _data + :: Maybe Value)+ let fromShadowSocksConfig :: [(Text, Value)] -> [(Text, Value)] fromShadowSocksConfig _configList = @@ -57,8 +82,7 @@ fromSS = fromShadowSocksConfig - let _v = decodeStrict - review utf8 _configFile :: Maybe Value-+ let fixConfig :: Value -> Value fixConfig (Object _obj) = Object - @@ -72,25 +96,13 @@ - formatConfig :: Value -> Value- formatConfig (Object _obj) =- Object -- _obj- & H.toList - & over (mapped . _1) T.tail - & H.fromList- formatConfig _ = Null filterEssentialConfig :: Value -> Value filterEssentialConfig (Object _obj) = Object - foldl (flip H.delete) _obj - [- "_remote"- , "_remotePort"- , "_local"- , "_localPort"- , "_password"+ "_password" ] filterEssentialConfig _ = Null@@ -100,41 +112,42 @@ Object - _x `H.union` _y mergeConfigObject _ _ = Null + mergeParams :: [(Text, Value)] -> Value -> Value+ mergeParams xs (Object _x) =+ Object - H.fromList xs `H.union` _x+ mergeParams _ _ = Null + optionalConfig = filterEssentialConfig - toJSON defaultMoeConfig - _maybeConfig = _v + _maybeConfig = _v >>= decode . encode . flip mergeConfigObject optionalConfig+ . mergeParams (aOption ^. params) . fixConfig - let - showConfig :: MoeConfig -> ByteString- showConfig = - toStrict - . encode - . formatConfig - . toJSON -- case _maybeConfig of Nothing -> do let _r = execWriter - do tell "\n\n"- tell "Failed to parse configuration file\n"- tell "Example: \n"-- let configBS :: ByteString - configBS = showConfig defaultMoeConfig- - tell - configBS ^. utf8 <> "\n"+ case _maybeFilePath of+ Just _filePath -> do+ tell "Failed to parse configuration file"+ tell _filePath+ tell "\n"+ tell "Example: \n"+ tell - showConfig defaultMoeConfig <> "\n"+ Nothing -> do+ tell "The password argument '-k' is required.\n"+ tell "Alternatively, use '-c' to provide a "+ tell "configuration file.\n" throwError - _r ^. _Text Just _config -> do- let configStr = showConfig _config ^. utf8 . _Text :: String+ let configStr = showConfig _config ^. _Text :: String io - puts - "Using config: " <> configStr pure - _config @@ -162,7 +175,7 @@ io - puts - show _options - _config <- parseConfig - _options ^. configFile+ _config <- parseConfig - _options let _c = _config let _method = _config ^. method . _Text@@ -173,8 +186,6 @@ case _cipher of Nothing -> throwError - "Invalid method '" <> _method- <> "' in "- <> _options ^. configFile . _Text Just _ -> pure () let localAppBuilder :: AppType -> String -> @@ -195,7 +206,7 @@ _s@(_newSocket, _newSockAddr) <- accept _socket setSocketCloseOnExec _newSocket -- send immediately!- setSocketOption _socket NoDelay 1 + {-setSocketOption _socket NoDelay 1 -} forkIO - catchExceptAsyncLog "L TCP thread" - logSA "L TCP client socket" (pure _s) -@@ -206,7 +217,7 @@ UDP_App -> do let handleLocal = do (_msg, _sockAddr) <- - recvFrom _localSocket _ReceiveLength+ recvFrom _localSocket _PacketLength puts - "L UDP: " <> show _msg @@ -263,7 +274,7 @@ (_newSocket, _) <- accept _socket setSocketCloseOnExec _newSocket -- send immediately!- setSocketOption _socket NoDelay 1 + {-setSocketOption _socket NoDelay 1 -} forkIO - catchExceptAsyncLog "R thread" - logSocket "R remote socket" (pure _newSocket) -@@ -280,7 +291,7 @@ bindSocket _remoteSocket _remoteAddr let handleRemote = do- (_msg, _sockAddr) <- recvFrom _remoteSocket _ReceiveLength+ (_msg, _sockAddr) <- recvFrom _remoteSocket _PacketLength puts - "R UDP: " <> show _msg
src/Network/MoeSocks/Config.hs view
@@ -2,21 +2,20 @@ module Network.MoeSocks.Config where -import Network.MoeSocks.Constant import Network.MoeSocks.Type import System.Log.Logger defaultMoeConfig :: MoeConfig defaultMoeConfig = MoeConfig {- _remote= "127.0.0.1"- , _remotePort = 1190+ _remote= "0.0.0.0"+ , _remotePort = 8388 , _local = "127.0.0.1"- , _localPort = 1090+ , _localPort = 1080 , _password = "moesocks"- , _method = _DefaultMethod+ , _method = "aes-256-cfb" , _timeout = 300- , _tcpBufferSizeInPacket = 256+ , _tcpBufferSize = 256 , _throttle = False , _throttleSpeed = 8000 -- in Kilobytes }@@ -26,9 +25,10 @@ defaultMoeOptions = MoeOptions { _runningMode = DebugMode- , _configFile = ""+ , _configFile = Nothing , _verbosity = DEBUG , _forwardTCP = [] , _forwardUDP = [] , _disableSocks5 = False+ , _params = [] }
src/Network/MoeSocks/Constant.hs view
@@ -3,7 +3,6 @@ module Network.MoeSocks.Constant where import Data.Word-import Data.Text (Text) _No_authentication :: Word8 _No_authentication = 0@@ -17,11 +16,9 @@ _SpaceCode :: Word8 _SpaceCode = 32 --_DefaultMethod :: Text-_DefaultMethod = "aes-256-cfb"+_PacketLength :: Int+_PacketLength = 4096 -_ReceiveLength :: Int-_ReceiveLength = 4096-+_NoThrottle :: Maybe a+_NoThrottle = Nothing
src/Network/MoeSocks/Helper.hs view
@@ -79,8 +79,8 @@ showBytes :: ByteString -> String showBytes = show . S.unpack -sleep :: Int -> IO ()-sleep x = threadDelay - x * 1000 * 1000+sleep :: Double -> IO ()+sleep x = threadDelay - floor - x * 1000 * 1000 foreverRun :: IO a -> IO () foreverRun _io = do@@ -204,8 +204,8 @@ setSocketCloseOnExec _socket -- send immediately!- when (aSocketType == Stream) -- setSocketOption _socket NoDelay 1 + {-when (aSocketType == Stream) --}+ {-setSocketOption _socket NoDelay 1 -} puts - "Getting socket: " <> show addrInfo {-puts - "Socket family: " <> show family-}@@ -304,6 +304,7 @@ _produce :: Int -> IO () _produce _bytesReceived = do _r <- timeoutFor aID aTimeout - recv_ aSocket+ {-pute - "Get chunk: " <> (show - S.length _r) -- <> " " <> aID-} if (_r & isn't _Empty) then do forM_ aThrottle - \_throttle -> do@@ -348,13 +349,13 @@ {-tryIO aID - close aSocket-} _consume :: Int -> IO ()- _consume _bytesSent = do+ _consume _allBytesSent = do forM_ aThrottle - \_throttle -> do _currentTime <- getCurrentTime let _timeDiff = realToFrac (diffUTCTime _currentTime _startTime) :: Double - _bytesK = fromIntegral _bytesSent / 1000 :: Double+ _bytesK = fromIntegral _allBytesSent / 1000 :: Double let _speed = _bytesK / _timeDiff @@ -368,13 +369,18 @@ <> " miliseconds." threadDelay - floor - _sleepTime - (atomically - readTBQueue aTBQueue) >>= \case + + + _newPacket <- atomically - readTBQueue aTBQueue++ case _newPacket of Nothing -> () <$ _shutdown Just _data -> do- timeoutFor aID aTimeout - sendAll aSocket _data+ timeoutFor aID aTimeout - + sendAll aSocket _data yield _consume - - _bytesSent + S.length _data+ _allBytesSent + S.length _data _consume 0 `onException` _shutdown pure ()
src/Network/MoeSocks/Options.hs view
@@ -4,8 +4,12 @@ import Control.Lens import Data.Text.Lens+import Data.Aeson+import Data.Maybe+import Data.Text (Text) import Network.MoeSocks.Helper import Network.MoeSocks.Type+import Network.MoeSocks.Config import Options.Applicative hiding (Parser) import Prelude hiding ((-), takeWhile) import System.Log.Logger@@ -13,51 +17,114 @@ import Data.Attoparsec.Text (Parser, takeWhile, char, decimal, skipSpace, parseOnly, many', choice) +textOption :: O.Mod O.OptionFields String -> O.Parser Text+textOption x = strOption x <&> view (from _Text)++defaultHelp :: Text -> Text -> Mod f a+defaultHelp val x = help - x <> ", default: " <> val & view _Text++textParam :: O.Mod O.OptionFields String -> O.Parser (Maybe Value)+textParam = optional . fmap toJSON . textOption++intParam :: O.Mod O.OptionFields Int -> O.Parser (Maybe Value)+intParam = optional . fmap toJSON . option auto+ optionParser :: O.Parser MoeOptions optionParser = - let _mode = ( strOption -- short 'm'- <> long "mode"- <> metavar "MODE"- <> help "Tell moesocks to runs in local or remote mode"+ let _c = defaultMoeConfig+ _mode = ( textOption -+ short 'r'+ <> long "role"+ <> metavar "ROLE"+ <> defaultHelp "local" + "Tell moesocks to run as local or remote" ) <|> pure "local" - parseMode :: String -> RunningMode+ parseMode :: Text -> RunningMode parseMode x | x `elem` ["server", "remote"] = RemoteMode | x `elem` ["client", "local"] = LocalMode | x == "debug" = DebugMode | otherwise = DebugMode- in - let _disableSocks5 :: O.Parser Bool+ _disableSocks5 :: O.Parser Bool _disableSocks5 = switch - long "disable-socks5" <> help ("Do not start a socks5 server on local. It can be " <> "useful to run moesocks only as a secure tunnel") - in- + _tcpBufferSize = intParam -+ long "tcp-buffer-size"+ <> metavar "SIZE"+ <> defaultHelp (_c ^. tcpBufferSize+ & show+ & view (from _Text))+ ("The number of packets used as a "+ <> "buffer. A packet can hold "+ <> "at most 4K of data.") - let _config = ( strOption -+ _config = optional - textOption - short 'c' <> long "config" <> metavar "CONFIG" <> help "Point to the path of the configuration file"- ) <|> pure "config.json" - in+ _remote = textParam -+ short 's'+ <> metavar "REMOTE"+ <> defaultHelp (_c ^. remote)+ "remote address"+ + _remotePort = intParam -+ short 'p'+ <> metavar "REMOTE_PORT"+ <> defaultHelp (_c ^. remotePort + & show + & view (from _Text))+ "remote port" - let _verbosity :: O.Parser Priority + _local = textParam -+ short 'b'+ <> metavar "LOCAL"+ <> defaultHelp (_c ^. local)+ "local address"++ _localPort = intParam -+ short 'l'+ <> metavar "LOCAL PORT"+ <> defaultHelp (_c ^. localPort+ & show+ & view (from _Text))+ "local port"++ _password = textParam -+ short 'k'+ <> metavar "PASSWORD"+ <> help "password"++ _method = textParam -+ short 'm'+ <> metavar "METHOD"+ <> defaultHelp (_c ^. method)+ "encryption method"++ _timeout = intParam - + short 't'+ <> metavar "TIMEOUT"+ <> defaultHelp (_c ^. timeout+ & show+ & view (from _Text))+ "timeout connection in seconds"+ ++ _verbosity :: O.Parser Priority _verbosity = flag INFO DEBUG - short 'v' <> long "verbose" <> help "Turn on logging"- in -- let _forwardTCP :: O.Parser String- _forwardTCP = ( strOption -+ _forwardTCP :: O.Parser (Maybe Text)+ _forwardTCP = optional - textOption - short 'T' <> long "tcp" <> metavar "port:host:hostport"@@ -67,13 +134,11 @@ <> "(client) host is to be forwarded to the " <> "given host and port on the remote side." )- ) <|> pure ""- - _forwardUDP :: O.Parser String- _forwardUDP = ( strOption -+ _forwardUDP :: O.Parser (Maybe Text)+ _forwardUDP = optional - textOption - short 'U' <> long "udp" <> metavar "port:host:hostport"@@ -83,8 +148,6 @@ <> "(client) host is to be forwarded to the " <> "given host and port on the remote side." )- ) <|> pure ""- forwardParser :: Parser Forward forwardParser = do@@ -112,26 +175,46 @@ forwardListParser :: Parser [Forward] forwardListParser = many' forwardParser - parseForwarding :: String -> [Forward]- parseForwarding x = - x ^. from _Text + parseForwarding :: Maybe Text -> [Forward]+ parseForwarding Nothing = []+ parseForwarding (Just x ) = + x & parseOnly forwardListParser & toListOf (traverse . traverse)++ tag :: a -> O.Parser (Maybe b) -> O.Parser (Maybe (a, b))+ tag x = fmap . fmap - ((,) x)+++ _params :: O.Parser [(Text, Value)]+ _params = + [ tag "_remote" _remote + , tag "_remotePort" _remotePort+ , tag "_local" _local + , tag "_localPort" _localPort + , tag "_password" _password+ , tag "_method" _method + , tag "_timeout" _timeout+ , tag "_tcpBufferSize" _tcpBufferSize+ ]+ & sequenceA+ & fmap catMaybes+ in MoeOptions <$> fmap parseMode _mode - <*> fmap (view packed) _config+ <*> _config <*> _verbosity <*> fmap parseForwarding _forwardTCP <*> fmap parseForwarding _forwardUDP <*> _disableSocks5+ <*> _params opts :: ParserInfo MoeOptions opts = info (helper <*> optionParser) - fullDesc <> progDesc "A socks5 proxy using the client / server architecture" <> header "A functional firewall killer"-
src/Network/MoeSocks/TCP.hs view
@@ -90,8 +90,8 @@ let _header = shadowSocksRequestBuilder _clientRequest - sendChannel <- newTBQueueIO - aConfig ^. tcpBufferSizeInPacket- receiveChannel <- newTBQueueIO - aConfig ^. tcpBufferSizeInPacket+ sendChannel <- newTBQueueIO - aConfig ^. tcpBufferSize+ receiveChannel <- newTBQueueIO - aConfig ^. tcpBufferSize let _logId x = x <> " " <> _msg _timeout = aConfig ^. timeout * 1000 * 1000@@ -112,7 +112,7 @@ let _produce = do produceLoop (_logId "L --> + Loop") _timeout- Nothing+ _NoThrottle aSocket sendChannel _encrypt@@ -133,7 +133,7 @@ let receiveThread = do let _produce = produceLoop (_logId "L <-- + Loop") _timeout- Nothing+ _NoThrottle __remoteSocket receiveChannel _decrypt@@ -141,7 +141,7 @@ let _consume = do consumeLoop (_logId "L <-- - Loop") _timeout- Nothing+ _NoThrottle aSocket receiveChannel finally @@ -190,8 +190,8 @@ let handleTarget __leftOverBytes __targetSocket = do- sendChannel <- newTBQueueIO - aConfig ^. tcpBufferSizeInPacket- receiveChannel <- newTBQueueIO - aConfig ^. tcpBufferSizeInPacket+ sendChannel <- newTBQueueIO - aConfig ^. tcpBufferSize+ receiveChannel <- newTBQueueIO - aConfig ^. tcpBufferSize let _logId x = x <> " " <> _msg _timeout = aConfig ^. timeout * 1000 * 1000@@ -208,14 +208,14 @@ let _produce = do produceLoop (_logId "R --> + Loop") _timeout- Nothing+ _NoThrottle aSocket sendChannel _decrypt let _consume = consumeLoop (_logId "R --> - Loop") _timeout- Nothing+ _NoThrottle __targetSocket sendChannel @@ -228,16 +228,16 @@ let receiveThread = do let _produce = do- produceLoop (_logId "R --> + Loop")+ produceLoop (_logId "R <-- + Loop") _timeout- Nothing+ _NoThrottle __targetSocket receiveChannel _encrypt let _consume = do- consumeLoop (_logId "R --> - Loop")+ consumeLoop (_logId "R <-- - Loop") _timeout _throttle aSocket
src/Network/MoeSocks/Type.hs view
@@ -55,7 +55,7 @@ , _password :: Text , _method :: Text , _timeout :: Int- , _tcpBufferSizeInPacket :: Int+ , _tcpBufferSize :: Int -- in packets , _throttle :: Bool , _throttleSpeed :: Double }@@ -86,11 +86,12 @@ data MoeOptions = MoeOptions { _runningMode :: RunningMode- , _configFile :: Text+ , _configFile :: Maybe Text , _verbosity :: Priority , _forwardTCP :: [Forward] , _forwardUDP :: [Forward] , _disableSocks5 :: Bool+ , _params :: [(Text, Value)] } deriving (Show, Eq)