packages feed

moesocks-1.0.1.0: src/Network/MoeSocks/Runtime.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

module Network.MoeSocks.Runtime where

import           Control.Lens
import           Control.Monad.Except (ExceptT, throwError)
import           Data.IP (IPRange)
import           Data.Monoid ((<>))
import qualified Data.Text as T
import           Data.Text.IO as T
import           Data.Text.Lens (_Text)
import qualified Network.MoeSocks.Type.Bootstrap.Config as C
import qualified Network.MoeSocks.Type.Bootstrap.Option as O
import qualified System.IO as IO
import           System.Log.Formatter (simpleLogFormatter)
import qualified System.Log.Handler as LogHandler
import           System.Log.Handler.Simple (streamHandler)
import           System.Log.Logger (Priority(DEBUG), removeHandler, addHandler, setLevel)
import           System.Log.Logger (updateGlobalLogger, rootLoggerName)

import           Network.MoeSocks.Bootstrap (loadConfig)
import           Network.MoeSocks.Default (defaultRuntime, parseIPRangeList, defaultEnv)
import           Network.MoeSocks.Encrypt (initCipherBox)
import           Network.MoeSocks.Type

import           Prelude hiding ((-), take)
import           Network.MoeSocks.Helper ((-), debug_, io, is)

initLogger :: Priority -> IO ()
initLogger aLevel = do
  stdoutHandler <- streamHandler IO.stdout DEBUG
  let formattedHandler =
          LogHandler.setFormatter stdoutHandler -
            --"[$time : $loggername : $prio]
            simpleLogFormatter "$time $prio\t $msg"

  updateGlobalLogger rootLoggerName removeHandler

  updateGlobalLogger "moe" removeHandler
  updateGlobalLogger "moe" - addHandler formattedHandler
  updateGlobalLogger "moe" - setLevel aLevel



loadJobs :: C.Config -> O.Options -> [Job]
loadJobs aConfig someOptions =
  let _c = aConfig

      _remote_TCP_Relay =
          RemoteRelay
            Remote_TCP_Relay
            (_c ^. C.remoteHost)
            (_c ^. C.remotePort)

      _remote_UDP_Relay =
          RemoteRelay
            Remote_UDP_Relay
            (_c ^. C.remoteHost)
            (_c ^. C.remotePort)

      _localService :: LocalServiceType -> LocalService
      _localService = LocalService
                        (_c ^. C.localHost)
                        (_c ^. C.remoteHost)
                        (_c ^. C.remotePort)

      _localService_TCP_Forwards =
          someOptions ^. O.forward_TCPs
            & map (_localService . LocalService_TCP_Forward)

      _localService_UDP_Forwards =
          someOptions ^. O.forward_UDPs
            & map (_localService . LocalService_UDP_Forward)

      _localService_SOCKS5 =
          LocalService_SOCKS5 (_c ^. C.localPort)
            & _localService

      _remoteRelays = [_remote_TCP_Relay, _remote_UDP_Relay]
      _localServices =
        let
          _localService_SOCKS5s =
            if someOptions ^. O.disable_SOCKS5
              then []
              else pure _localService_SOCKS5
        in

        _localService_TCP_Forwards
        <> _localService_UDP_Forwards
        <> _localService_SOCKS5s
  in

  map RemoteRelayJob _remoteRelays
  <> map LocalServiceJob _localServices

filterJobs :: O.RunningMode -> [Job] -> [Job]
filterJobs = \case
  O.DebugMode -> id
  O.RemoteMode -> filter - is _RemoteRelayJob
  O.LocalMode -> filter - is _LocalServiceJob


initEnv :: C.Config -> O.Options -> ExceptT String IO Env
initEnv aConfig someOptions = do
  let _o = someOptions
      _c = aConfig

  io - initLogger - _o ^. O.verbosity
  io - debug_ - show _o

  _config <- loadConfig - _o

  let _method = _c ^. C.method

  _cipherBox <- (io - initCipherBox _method (_c ^. C.password)) >>= \case
    Nothing -> throwError - "Invalid method '"
                            <> _method ^. _Text
    Just (a, b, c, d) -> pure - CipherBox a b c d

  let _readDenyList :: IO [IPRange]
      _readDenyList =
        case someOptions ^. O.denyList of
          Nothing -> pure []
          Just _denyListPath ->
            T.readFile (_denyListPath ^. _Text)
              <&> T.lines
              <&> parseIPRangeList


  _denyList <- io - _readDenyList

  io - debug_ - "denyList: " <> show (length _denyList) <> " items"

  let _env = defaultEnv
              & cipherBox .~ _cipherBox
              & denyList .~ _denyList

  pure - _env

initRuntime :: C.Config -> O.Options -> ExceptT String IO (Runtime)
initRuntime aConfig someOptions = do
  let _c = aConfig
      _o = someOptions
      _s = _c ^. C.socketOption_TCP_NOTSENT_LOWAT

  _env <- initEnv aConfig someOptions

  let _env' = _env
        & timeout                        .~ _c ^. C.timeout
        & throttle                       .~ _c ^. C.throttle
        & throttleSpeed                  .~ _c ^. C.throttleSpeed
        & obfuscationFlushBound          .~ _c ^. C.obfuscationFlushBound
        & fastOpen                       .~ _c ^. C.fastOpen
        & socketOption_TCP_NOTSENT_LOWAT .~ _s
        & obfuscation                    .~ _o ^. O.obfuscation
        & forbidden_IPs                  .~ (_c ^. C.forbidden_IPs
                                                & parseIPRangeList)



  let _jobs = loadJobs _c _o & filterJobs (_o ^. O.runningMode)

  pure -  ( defaultRuntime
              & env .~ _env'
              & jobs .~ _jobs
          )