module Tor.State.CircuitManager(
CircuitManager
, newCircuitManager
, openCircuit
, closeCircuit
)
where
import Control.Concurrent
import Control.Concurrent.Async(Async,async,wait,waitCatch)
import Control.Exception
import Control.Monad
import Crypto.Random
import Network.TLS(HasBackend)
import System.Mem.Weak
import Tor.Circuit
import Tor.DataFormat.TorCell
import Tor.Link
import Tor.NetworkStack
import Tor.Options
import Tor.RNG
import Tor.RouterDesc
import Tor.State.Credentials
import Tor.State.LinkManager
import Tor.State.Routers
data HasBackend s => CircuitManager ls s
= NoCircuitManager
| CircuitManager {
cmCircuitLength :: Int
, cmRouterDB :: RouterDB
, cmOptions :: TorOptions
, cmLinkManager :: LinkManager ls s
, cmRNG :: MVar TorRNG
, cmOpenCircuits :: MVar [CircuitEntry s]
}
data CircuitEntry s = Pending {
ceExitNode :: RouterDesc
, _cePendingEntrance :: Async OriginatedCircuit
}
| Entry {
ceExitNode :: RouterDesc
, _ceWeakEntrance :: Weak OriginatedCircuit
}
| Transverse {
_ceIncomingLink :: TorLink
, _ceCircuit :: Weak (TransverseCircuit s)
}
newCircuitManager :: HasBackend s =>
TorOptions -> TorNetworkStack ls s ->
Credentials -> RouterDB -> LinkManager ls s ->
IO (CircuitManager ls s)
newCircuitManager opts ns creds rdb lm =
case torEntranceOptions opts of
Nothing -> return NoCircuitManager
Just entOpts ->
do let circLen = torInternalCircuitLength entOpts
rngMV <- newMVar =<< drgNew
circMV <- newMVar []
let cm = CircuitManager circLen rdb opts lm rngMV circMV
setIncomingLinkHandler lm $ \ link ->
handle logException $
do me <- getRouterDesc creds
mcircuit <- acceptCircuit ns opts me creds rdb link rngMV
case mcircuit of
Nothing ->
torLog opts ("Failed to build transverse circuit.")
Just circuit ->
do wkCircuit <- mkWeakPtr circuit Nothing
let circ = Transverse link wkCircuit
modifyMVar_ circMV $ \ circs -> return (circ : circs)
return cm
where
logException e = torLog opts ("Exception creating incoming circuit: " ++
show (e :: SomeException))
openCircuit :: HasBackend s =>
CircuitManager ls s -> [RouterRestriction] ->
IO OriginatedCircuit
openCircuit NoCircuitManager _ = fail "This node doesn't support entrance."
openCircuit cm restricts =
join $ modifyMVar (cmOpenCircuits cm) $ \ circs ->
case findApplicable circs of
Nothing ->
do exitNode <- modifyMVar (cmRNG cm) $ \ rng ->
getRouter (cmRouterDB cm) restricts rng
pendRes <- async (buildNewCircuit cm exitNode (cmCircuitLength cm))
return (snoc circs (Pending exitNode pendRes),
waitAndUpdate exitNode pendRes)
Just (pend@(Pending _ entrance), rest) ->
return (snoc rest pend, wait entrance)
Just (ent@(Entry _ wkEnt), rest) ->
do ment <- deRefWeak wkEnt
case ment of
Nothing ->
return (rest, openCircuit cm restricts)
Just res ->
return (snoc rest ent, return res)
_ ->
fail "Serious internal error (openCircuit)"
where
findApplicable ls = loop ls []
where
loop [] _ = Nothing
loop (x : rest) acc
| ceExitNode x `meetsRestrictions` restricts = Just (x, rest ++ acc)
| otherwise = loop rest (snoc acc x)
waitAndUpdate exitNode pendRes =
do eres <- waitCatch pendRes
case eres of
Left err ->
do modifyMVar_ (cmOpenCircuits cm)
(return . removeEntry exitNode)
throwIO err
Right res ->
do weak <- mkWeakPtr res (Just (destroyCircuit res RequestedDestroy))
let newent = Entry exitNode weak
modifyMVar_ (cmOpenCircuits cm)
(return . replaceEntry exitNode newent)
return res
removeEntry _ [] = []
removeEntry exitNode (x : rest)
| exitNode == ceExitNode x = removeEntry exitNode rest
| otherwise = x : removeEntry exitNode rest
replaceEntry _ _ [] = []
replaceEntry exitNode new (x : rest)
| exitNode == ceExitNode x = new : replaceEntry exitNode new rest
| otherwise = x : replaceEntry exitNode new rest
closeCircuit :: HasBackend s => CircuitManager ls s -> OriginatedCircuit -> IO ()
closeCircuit = error "closeCircuit"
buildNewCircuit :: HasBackend s =>
CircuitManager ls s -> RouterDesc -> Int ->
IO OriginatedCircuit
buildNewCircuit cm exitNode len =
do let notExit = [NotRouter exitNode]
(link, desc, circId) <- newLinkCircuit (cmLinkManager cm) notExit
cmLog cm ("Built initial link to " ++ show (routerIPv4Address desc) ++
" with circuit ID " ++ show circId)
circ <- createCircuit (cmRNG cm) (cmOptions cm) link desc circId
loop circ (NotRouter desc : notExit) len
where
loop circ _ 0 =
do cmLog cm ("Extending circuit to exit node " ++
show (routerIPv4Address exitNode))
extendCircuit circ exitNode
return circ
loop circ restricts x =
do next <- modifyMVar (cmRNG cm) (getRouter (cmRouterDB cm) restricts)
cmLog cm ("Extending circuit to " ++ show (routerIPv4Address next))
extendCircuit circ next
loop circ (NotRouter next : restricts) (x 1)
snoc :: [a] -> a -> [a]
snoc [] x = [x]
snoc (x:rest) y = x : snoc rest y
cmLog :: HasBackend s => CircuitManager ls s -> (String -> IO ())
cmLog = torLog . cmOptions