packages feed

neko-obfs-0.1.0.0: src/Main.hs

{-# LANGUAGE OverloadedStrings #-}

module Main where

import           Helper ((-), log)
import           Prelude hiding ((-), log)

import           Control.Concurrent.Async (async, waitAnyCatchCancel, waitEitherCancel)
import           Control.Lens
import           Control.Monad (replicateM_, join)
import           Control.Monad.IO.Class (MonadIO(..))
import           Control.Monad.Trans.State.Strict (runStateT)
import           Data.Attoparsec.ByteString (parse, maybeResult)
import           Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Builder as Builder
import qualified Data.ByteString.Char8 as BC
import           Data.Monoid ((<>))
import qualified Data.Text as T
import           Data.Text.Lens
import           Data.Text.Strict.Lens (utf8)
import           Network.Simple.TCP (send)
import qualified Network.Socket as NS
import           Pipes (runEffect, (>->), Pipe, for, yield, cat, Producer, Consumer)
import qualified Pipes.Attoparsec as PA
import           Pipes.Network.TCP.Safe
import qualified Pipes.Prelude as P
import           Pipes.Safe
import           System.Random (randomIO)
import           Options.Generic

import           Options (toConfig)
import           Parser
import           Type


obfsEncode :: (MonadIO m) => Integer -> Integer -> Pipe ByteString ByteString m a
obfsEncode bound randomness = do
  for cat - \x -> do
    let payloadLen = (fromIntegral - B.length x)

    randomLen <-
      if (payloadLen > fromIntegral bound)
        then pure 0
        else liftIO - randomIO <&> (`mod` fromIntegral randomness)

    yield - view strict - Builder.toLazyByteString - Builder.word32BE randomLen
    replicateM_ (fromIntegral randomLen) - yield - BC.singleton 'a'

    yield - view strict - Builder.toLazyByteString - Builder.word32BE payloadLen
    yield x


obfsDecode :: (MonadIO m) => Producer ByteString m a -> (ByteString -> m ()) -> m ()
obfsDecode pull sink = do
  (r, next) <- runStateT (PA.parse obfsParser) pull
  case r of
    Just (Right result@(_,_,_,payload)) -> do
      -- log - view packed - show result
      sink payload
      obfsDecode next sink
    _ -> pure ()

main :: IO ()
main = do
  config <- getRecord "a TCP tunnel with packet length obfuscation" <&> toConfig

  log - T.pack - show config

  let mtu = 4096
      timeout = fromIntegral - config ^. timeoutInSeconds * 1000000

  let _localHost = config ^. localHost . re packed
      _localPort = show - config ^. localPort

      _remoteHost = config ^. remoteHost . re packed
      _remotePort = show - config ^. remotePort

      _fromSocket = fromSocketTimeout timeout
      _toSocket = toSocketTimeout timeout

  localThread <- pure -
    runSafeT . runEffect - serve (Host _localHost) _localPort - \(localSock, localSockAddr) -> do
      log - "local accepted: " <> view packed (show localSockAddr)

      runSafeT . runEffect -
        connect _remoteHost _remotePort - \(remoteSock, remoteSockAddr) -> do

          let
              localPull = _fromSocket localSock mtu

              remotePull = _fromSocket remoteSock mtu
              remotePush = _toSocket remoteSock


          sendThread <- liftIO - async - runEffect - do
            localPull >-> obfsEncode (config ^. bound) (config ^. randomness)
                      >-> remotePush
            liftIO - NS.shutdown remoteSock NS.ShutdownSend

          recvThread <- liftIO - async - do
            obfsDecode remotePull - send localSock
            liftIO - NS.shutdown localSock NS.ShutdownSend

          liftIO - waitEitherCancel sendThread recvThread

          pure ()

  remoteThread <- pure -
    runSafeT . runEffect - serve (Host _remoteHost) _remotePort - \(remoteSock, remoteSockAddr) -> do
      log - "remote accepted: " <> view packed (show remoteSockAddr)

      let _forwardHost = config ^. forwardHost . re packed
          _forwardPort = show - config ^. forwardPort

      runSafeT . runEffect -
        connect _forwardHost _forwardPort - \(forwardSock, forwardSockAddr) -> do

          let
              remotePull = _fromSocket remoteSock mtu
              remotePush = _toSocket remoteSock

              forwardPull = _fromSocket forwardSock mtu

          sendThread <- liftIO - async - do
            obfsDecode remotePull - send forwardSock
            liftIO - NS.shutdown forwardSock NS.ShutdownSend

          recvThread <- liftIO - async - runEffect - do
            forwardPull >-> obfsEncode (config ^. bound) (config ^. randomness)
                        >-> remotePush
            liftIO - NS.shutdown remoteSock NS.ShutdownSend

          liftIO - waitEitherCancel sendThread recvThread

          pure ()


  case config ^. role of
    Local -> do
      _ <- localThread
      pure ()
    Remote -> do
      _ <- remoteThread
      pure ()

  pure ()