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 +0/−44
- examples/Relay.lhs +89/−0
- network-anonymous-tor.cabal +3/−3
- src/Network/Anonymous/Tor.hs +2/−1
- src/Network/Anonymous/Tor/Protocol.hs +35/−27
− 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