> 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 Nothing (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)