packages feed

network-anonymous-tor 0.9.1 → 0.9.2

raw patch · 5 files changed

+129/−75 lines, 5 filesdep +splicePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: splice

API changes (from Hackage documentation)

- Network.Anonymous.Tor: detectPort :: MonadIO m => [Integer] -> m [Integer]
- Network.Anonymous.Tor.Protocol: detectPort :: MonadIO m => [Integer] -> m [Integer]
+ Network.Anonymous.Tor: Available :: Availability
+ Network.Anonymous.Tor: ConnectionRefused :: Availability
+ Network.Anonymous.Tor: IncorrectPort :: Availability
+ Network.Anonymous.Tor: data Availability
+ Network.Anonymous.Tor: isAvailable :: MonadIO m => Integer -> m Availability
+ Network.Anonymous.Tor.Protocol: Available :: Availability
+ Network.Anonymous.Tor.Protocol: ConnectionRefused :: Availability
+ Network.Anonymous.Tor.Protocol: IncorrectPort :: Availability
+ Network.Anonymous.Tor.Protocol: data Availability
+ Network.Anonymous.Tor.Protocol: instance Eq Availability
+ Network.Anonymous.Tor.Protocol: instance Show Availability
+ Network.Anonymous.Tor.Protocol: isAvailable :: MonadIO m => Integer -> m Availability

Files

− examples/Relay.hs
@@ -1,44 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}
-
-import           System.Environment    (getArgs)
-
-import           Control.Concurrent    (threadDelay)
-import           Network               (withSocketsDo)
-import qualified Network.Anonymous.Tor as Tor
-import qualified Network.Socks5.Types  as SocksT
-
-getPortmap :: IO (Integer, Integer)
-getPortmap = do
-  [pub, priv] <- getArgs
-  return (read pub, read priv)
-
-whichControlPort :: IO Integer
-whichControlPort = do
-  ports <- Tor.detectPort [9051, 9151]
-  return (head ports)
-
-main = withSocketsDo $ do
-  torPort <- whichControlPort
-  portmap <- getPortmap
-
-  Tor.withSession torPort (withinSession portmap)
-
-  return ()
-
-  where
-    withinSession (publicPort, privatePort) controlSock = do
-      onion <- Tor.accept controlSock publicPort (newConnection privatePort)
-
-      putStrLn ("hidden service descriptor: " ++ show onion)
-
-      -- If we would leave this function at this point, our connection with
-      -- the Tor control service would be lost, which would cause Tor to clean
-      -- up any mappings and hidden services we have registered.
-      --
-      -- Since this is just an example, we will now wait for 5 minutes and then
-      -- exit.
-      threadDelay 300000000
-
-    newConnection privatePort sock = do
-      putStrLn "Got new connection!"
-      return ()
+ examples/Relay.lhs view
@@ -0,0 +1,89 @@+> import           System.Environment    (getArgs)
+> import           Control.Concurrent    (threadDelay, forkIO)
+> import           Control.Monad         (void)
+> import           System.IO             (IOMode (ReadWriteMode))
+
+> import           Network               (withSocketsDo)
+> import qualified Network.Simple.TCP    as NST
+> import           Network.Socket        (socketToHandle)
+> import qualified Network.Socket.Splice as Splice
+
+> import qualified Network.Anonymous.Tor as Tor
+
+Our main function is fairly simple: get our configuration data and start
+a new Tor Session. As soon as this session completes, exit the program.
+
+> main = withSocketsDo $ do
+>   torPort <- whichControlPort
+>   portmap <- getPortmap
+
+Once we got the configuration data, launch a Tor session and will continue
+execution in the 'withinSession' function.
+
+>   Tor.withSession torPort (withinSession portmap)
+>
+>   where
+
+We need a simple function to detect which control port Tor listens at. By
+default the Tor service uses port 9051, but the Tor Browser Bundle uses 9151,
+to allow Tor and the TBB to run next to each other.
+
+>     whichControlPort = do
+>       let ports = [9051, 9151]
+>
+>       availability <- mapM Tor.isAvailable ports
+
+At this point, the `ports` list describes the list of ports in which we think
+a Tor controller might be active, and `availability` has a list of the same length
+with the associated availability status.
+
+Since we're only interested in services that are available, we are going to
+combine these list, filter on the availability status, and return the first port
+that matches these constraints. This functionality is relatively unsafe, since
+it assumes at least one Tor service is running (otherwise 'head' will return an
+error).
+
+>       return . fst . head . filter ((== Tor.Available) . snd) $ zip ports availability
+
+Some boilerplate code: we need to set up a port mapping, and rather than hard-
+coding it, we allow the user to provide is as command line arguments. The first
+argument is the public port we will listen at, the second argument the private
+port we relay connections to.
+
+>     getPortmap :: IO (Integer, Integer)
+>     getPortmap = do
+>       [pub, priv] <- getArgs
+>       return (read pub, read priv)
+
+Once a Tor session has been created and we are authenticated with the Tor
+control service, let's set up a new onion service which redirects incoming
+connections to the 'newConnection' function.
+
+>     withinSession (publicPort, privatePort) controlSock = do
+>       onion <- Tor.accept controlSock publicPort (newConnection privatePort)
+>       putStrLn ("hidden service descriptor: " ++ show onion)
+
+If we would leave this function at this point, our connection with the Tor
+control service would be lost, which would cause Tor to clean up any mappings
+and hidden services we have registered.
+
+Since this is just an example, we will now wait for 5 minutes and then exit.
+
+>       threadDelay 300000000
+
+This function is called for all incoming connections. All it needs to do is
+establish a connection with the local service and relay the connections to the
+local, private server.
+
+>     newConnection privatePort sPublic =
+>       NST.connect "127.0.0.1" (show privatePort) $ \(sPrivate, addr) -> spliceSockets sPublic sPrivate
+
+And to demonstrate that the sockets we deal with are just regular, normal
+network sockets, we implement a function using the `splice` package that
+creates a bidirectional pipe between the public and private sockets.
+
+>     spliceSockets sLhs sRhs = do
+>       hLhs <- socketToHandle sLhs ReadWriteMode
+>       hRhs <- socketToHandle sRhs ReadWriteMode
+>       _ <- forkIO $ Splice.splice 1024 (sLhs, Just hLhs) (sRhs, Just hRhs)
+>       Splice.splice 1024 (sRhs, Just hRhs) (sLhs, Just hLhs)
network-anonymous-tor.cabal view
@@ -1,6 +1,6 @@ name: network-anonymous-tor
 category: Network
-version: 0.9.1
+version: 0.9.2
 license: MIT
 license-file: LICENSE
 copyright: (c) 2014 Leon Mergen
@@ -77,14 +77,14 @@ executable tor-relay
   default-language:    Haskell2010
   hs-source-dirs:      examples
-  main-is:             Relay.hs
+  main-is:             Relay.lhs
   
   build-depends:       base                     >= 4.3          && < 5
                      , exceptions
 
                      , network
                      , network-simple
-                     , socks
+                     , splice
                      
                      , network-anonymous-tor
                      
src/Network/Anonymous/Tor.hs view
@@ -21,7 +21,8 @@   , accept
 
   -- * Probing Tor configuration information
-  , P.detectPort
+  , P.Availability (..)
+  , P.isAvailable
   , P.socksPort
 
   -- ** Setting up the context
src/Network/Anonymous/Tor/Protocol.hs view
@@ -9,7 +9,8 @@ --                interface of these functions might change at any time without
 --                prior notice.
 --
-module Network.Anonymous.Tor.Protocol ( detectPort
+module Network.Anonymous.Tor.Protocol ( Availability (..)
+                                      , isAvailable
                                       , socksPort
                                       , connect
                                       , connect'
@@ -20,15 +21,19 @@ import           Control.Concurrent.MVar
 
 import           Control.Monad                             (unless, void, when)
-import           Control.Monad.Catch                       (handleAll)
+import           Control.Monad.Catch                       ( handle
+                                                           , handleIOError )
 import           Control.Monad.IO.Class
 
+import qualified System.IO.Error as E
+import qualified GHC.IO.Exception as E hiding (ProtocolError)
+
 import qualified Data.Attoparsec.ByteString                as Atto
 import qualified Data.Base32String.Default                 as B32
 import qualified Data.ByteString                           as BS
 import qualified Data.ByteString.Char8                     as BS8
 import qualified Data.HexString                            as HS
-import           Data.Maybe                                (catMaybes, fromJust)
+import           Data.Maybe                                (fromJust)
 
 import qualified Data.Text.Encoding                        as TE
 
@@ -67,35 +72,38 @@ 
   return res
 
--- | Probes several default ports to see if there is a service at the remote
---   that behaves like the Tor controller daemon. Will return a list of all
---   the probed ports that have a Tor service, since in some scenarios there
---   will be multiple Tor services (specifically, when a user has both the
---   Tor browser bundle and a separate Tor relay running).
-detectPort :: MonadIO m
-           => [Integer]   -- ^ The ports we wish to probe
-           -> m [Integer] -- ^ The ports which respond like a Tor service
-detectPort possible = do
-
-  ports <- liftIO $ mapM hasTor possible
+-- | Represents the availability status of Tor for a specific port.
+data Availability =
+  Available |         -- ^ There is a Tor control service listening at the port
+  ConnectionRefused | -- ^ There is no service listening at the port
+  IncorrectPort       -- ^ There is a non-Tor control service listening at the port
+  deriving (Show, Eq)
 
-  return (catMaybes ports)
+-- | Probes a port to see if there is a service at the remote that behaves
+--   like the Tor controller daemon. Will return the status of the probed
+--   port.
+isAvailable :: MonadIO m
+            => Integer        -- ^ The ports we wish to probe
+            -> m Availability -- ^ The status of all the ports
+isAvailable port = liftIO $ do
 
-  where
-    -- Returns the port if the remote service is available and responds in
-    -- an expected fashion to 'protocolInfo', returns Nothing otherwise.
-    hasTor :: Integer -> IO (Maybe Integer)
-    hasTor port = liftIO $ do
-      result <- newEmptyMVar
+  result <- newEmptyMVar
 
-      handleAll
-        (\_ -> putMVar result Nothing)
-        (NST.connect "127.0.0.1" (show port) (\(sock, _) -> do
-                                                   _ <- protocolInfo sock
-                                                   putMVar result (Just port)))
+  handle (\(E.TorError E.ProtocolError) -> putMVar result IncorrectPort)
+    $ handleIOError (\e  ->
+                      -- The error raised for a Connection Refused is a very descriptive OtherError
+                      if   E.ioeGetErrorType e == E.OtherError || E.ioeGetErrorType e == E.NoSuchThing
+                      then putMVar result ConnectionRefused
+                      else E.ioError e)
+    (performTest port result)
 
-      takeMVar result
+  takeMVar result
 
+  where
+    performTest port result =
+      NST.connect "127.0.0.1" (show port) (\(sock, _) -> do
+                                                _ <- protocolInfo sock
+                                                putMVar result Available)
 -- | Returns the configured SOCKS proxy port
 socksPort :: MonadIO m
           => Network.Socket