diff options
author | joe <joe@jerkface.net> | 2018-06-19 23:41:17 -0400 |
---|---|---|
committer | joe <joe@jerkface.net> | 2018-06-19 23:41:17 -0400 |
commit | b7ca0fee54bc0f5a169972d559e7ca8c4c2b479f (patch) | |
tree | 65203fdce365b4658a425eea4b186e9078ebed1d /OnionRouter.hs | |
parent | bf9d8626bfa0758c3374c1e329f94a6b520a4b3b (diff) |
Remember which version of an onion route timed out.
Diffstat (limited to 'OnionRouter.hs')
-rw-r--r-- | OnionRouter.hs | 106 |
1 files changed, 69 insertions, 37 deletions
diff --git a/OnionRouter.hs b/OnionRouter.hs index 428b6d8e..17cea8e8 100644 --- a/OnionRouter.hs +++ b/OnionRouter.hs | |||
@@ -46,10 +46,8 @@ import System.Timeout | |||
46 | -- only-announcing. Instead, we maintain 12 multi-purpose routes. | 46 | -- only-announcing. Instead, we maintain 12 multi-purpose routes. |
47 | data OnionRouter = OnionRouter | 47 | data OnionRouter = OnionRouter |
48 | { -- | For every query, we remember the destination and source keys | 48 | { -- | For every query, we remember the destination and source keys |
49 | -- so we can decrypt the response. Note, the RouteId field is not | 49 | -- so we can decrypt the response. |
50 | -- currently stored here. It is inferred from the destination NodeId. | 50 | pendingQueries :: TVar (Word64Map PendingQuery) |
51 | -- Instead, a 'Nothing' is stored. | ||
52 | pendingQueries :: TVar (Word64Map (OnionDestination RouteId)) | ||
53 | -- | The current 12 routes that may be assigned to outgoing packets. | 51 | -- | The current 12 routes that may be assigned to outgoing packets. |
54 | , routeMap :: TVar (IntMap RouteRecord) | 52 | , routeMap :: TVar (IntMap RouteRecord) |
55 | -- | A set of nodes used to query for random route nodes. These aren't | 53 | -- | A set of nodes used to query for random route nodes. These aren't |
@@ -71,9 +69,10 @@ data OnionRouter = OnionRouter | |||
71 | , onionDRG :: TVar ChaChaDRG | 69 | , onionDRG :: TVar ChaChaDRG |
72 | -- | Building onion routes happens in a dedicated thread. See 'forkRouteBuilder'. | 70 | -- | Building onion routes happens in a dedicated thread. See 'forkRouteBuilder'. |
73 | , routeThread :: ThreadId | 71 | , routeThread :: ThreadId |
74 | -- | Each of the 12 routes has a flag here that is set True when the | 72 | -- | Each of the 12 routes has a version number here that is set larger |
75 | -- route should be discarded and replaced with a fresh one. | 73 | -- than the 'routeVersion' set in 'routeMap' when the route should be |
76 | , pendingRoutes :: IntMap (TVar Bool) | 74 | -- discarded and replaced with a fresh one. |
75 | , pendingRoutes :: IntMap (TVar Int) | ||
77 | -- | Debug prints are written to this channel which is then flushed to | 76 | -- | Debug prints are written to this channel which is then flushed to |
78 | -- 'routeLogger'. | 77 | -- 'routeLogger'. |
79 | , routeLog :: TChan String | 78 | , routeLog :: TChan String |
@@ -81,10 +80,17 @@ data OnionRouter = OnionRouter | |||
81 | , routeLogger :: String -> IO () | 80 | , routeLogger :: String -> IO () |
82 | } | 81 | } |
83 | 82 | ||
83 | data PendingQuery = PendingQuery | ||
84 | { pendingVersion :: !Int -- ^ Remembered version number so timeouts can signal a rebuild. | ||
85 | , pendingDestination :: OnionDestination RouteId | ||
86 | } | ||
87 | deriving Show | ||
88 | |||
84 | data RouteRecord = RouteRecord | 89 | data RouteRecord = RouteRecord |
85 | { storedRoute :: OnionRoute | 90 | { storedRoute :: OnionRoute |
86 | , responseCount :: !Int | 91 | , responseCount :: !Int |
87 | , timeoutCount :: !Int | 92 | , timeoutCount :: !Int |
93 | , routeVersion :: !Int | ||
88 | } | 94 | } |
89 | 95 | ||
90 | -- Onion paths have different timeouts depending on whether the path is | 96 | -- Onion paths have different timeouts depending on whether the path is |
@@ -101,11 +107,12 @@ timeoutForRoute :: RouteRecord -> Int | |||
101 | timeoutForRoute RouteRecord{ responseCount = 0 } = 4000000 | 107 | timeoutForRoute RouteRecord{ responseCount = 0 } = 4000000 |
102 | timeoutForRoute RouteRecord{ responseCount = _ } = 10000000 | 108 | timeoutForRoute RouteRecord{ responseCount = _ } = 10000000 |
103 | 109 | ||
104 | freshRoute :: OnionRoute -> RouteRecord | 110 | freshRoute :: OnionRoute -> Maybe RouteRecord -> Maybe RouteRecord |
105 | freshRoute r = RouteRecord | 111 | freshRoute r mrec = Just $ RouteRecord |
106 | { storedRoute = r | 112 | { storedRoute = r |
107 | , responseCount = 0 | 113 | , responseCount = 0 |
108 | , timeoutCount = 0 | 114 | , timeoutCount = 0 |
115 | , routeVersion = maybe 0 succ $ routeVersion <$> mrec | ||
109 | } | 116 | } |
110 | 117 | ||
111 | gotResponse :: RouteRecord -> RouteRecord | 118 | gotResponse :: RouteRecord -> RouteRecord |
@@ -125,15 +132,15 @@ newOnionRouter :: (String -> IO ()) -> IO OnionRouter | |||
125 | newOnionRouter perror = do | 132 | newOnionRouter perror = do |
126 | drg0 <- drgNew | 133 | drg0 <- drgNew |
127 | or <- atomically $ do | 134 | or <- atomically $ do |
128 | chan <- newTChan | 135 | -- chan <- newTChan |
129 | drg <- newTVar drg0 | 136 | drg <- newTVar drg0 |
130 | forM_ [0..11] $ \n -> writeTChan chan $ BuildRoute (RouteId n) | 137 | -- forM_ [0..11] $ \n -> writeTChan chan $ BuildRoute (RouteId n) |
131 | pq <- newTVar W64.empty | 138 | pq <- newTVar W64.empty |
132 | rm <- newTVar IntMap.empty | 139 | rm <- newTVar IntMap.empty |
133 | tn <- newTVar IntMap.empty | 140 | tn <- newTVar IntMap.empty |
134 | ti <- newTVar HashMap.empty | 141 | ti <- newTVar HashMap.empty |
135 | tc <- newTVar 0 | 142 | tc <- newTVar 0 |
136 | vs <- sequence $ replicate 12 (newTVar True) | 143 | vs <- sequence $ replicate 12 (newTVar 0) |
137 | rlog <- newTChan | 144 | rlog <- newTChan |
138 | return OnionRouter | 145 | return OnionRouter |
139 | { pendingRoutes = IntMap.fromList $ zip [0..11] vs | 146 | { pendingRoutes = IntMap.fromList $ zip [0..11] vs |
@@ -156,9 +163,10 @@ forkRouteBuilder or getnodes = do | |||
156 | labelThread me "OnionRouter" | 163 | labelThread me "OnionRouter" |
157 | forever $ do | 164 | forever $ do |
158 | let checkRebuild rid want_build stm = flip orElse stm $ do | 165 | let checkRebuild rid want_build stm = flip orElse stm $ do |
159 | readTVar want_build >>= check | 166 | wanted_ver <- readTVar want_build |
160 | -- This was moved to handleEvent to allow retry on fail. | 167 | current_ver <- fmap routeVersion . IntMap.lookup rid <$> readTVar (routeMap or) |
161 | -- writeTVar want_build False -- Prevent redundant BuildRoute events. | 168 | writeTChan (routeLog or) $ "ONION checkRebuild "++show(current_ver,wanted_ver) |
169 | check $ maybe True (< wanted_ver) current_ver | ||
162 | return $ BuildRoute $ RouteId rid | 170 | return $ BuildRoute $ RouteId rid |
163 | io <- atomically $ | 171 | io <- atomically $ |
164 | (readTChan (routeLog or) >>= return . routeLogger or) | 172 | (readTChan (routeLog or) >>= return . routeLogger or) |
@@ -325,10 +333,11 @@ handleEvent getnodes or e@(BuildRoute (RouteId rid)) = do | |||
325 | _ -> return Nothing | 333 | _ -> return Nothing |
326 | writeTVar (onionDRG or) drg' | 334 | writeTVar (onionDRG or) drg' |
327 | return $ getr | 335 | return $ getr |
328 | atomically $ maybe (writeTVar (pendingRoutes or IntMap.! rid) True) | 336 | atomically $ maybe (return ()) -- writeTVar (pendingRoutes or IntMap.! rid) True) |
329 | (\r -> do modifyTVar' (routeMap or) | 337 | (\r -> do modifyTVar' (routeMap or) |
330 | (IntMap.insert rid $ freshRoute r) | 338 | (IntMap.alter (freshRoute r) rid) |
331 | writeTVar (pendingRoutes or IntMap.! rid) False | 339 | v <- routeVersion . (IntMap.! rid) <$> readTVar (routeMap or) |
340 | writeTVar (pendingRoutes or IntMap.! rid) v | ||
332 | ) | 341 | ) |
333 | mb | 342 | mb |
334 | case mb of | 343 | case mb of |
@@ -344,9 +353,9 @@ lookupSender or saddr (Nonce8 w8) = do | |||
344 | return r | 353 | return r |
345 | return $ do | 354 | return $ do |
346 | od <- result | 355 | od <- result |
347 | let nid = nodeId $ onionNodeInfo od | 356 | let nid = nodeId $ onionNodeInfo $ pendingDestination od |
348 | ni <- either (const Nothing) Just $ nodeInfo nid saddr | 357 | ni <- either (const Nothing) Just $ nodeInfo nid saddr |
349 | Just (OnionDestination (onionAliasSelector od) | 358 | Just (OnionDestination (onionAliasSelector $ pendingDestination od) |
350 | ni | 359 | ni |
351 | (Just $ routeId nid)) | 360 | (Just $ routeId nid)) |
352 | 361 | ||
@@ -365,42 +374,65 @@ lookupTimeout or n8 (OnionDestination asel ni Nothing) = do | |||
365 | Nothing -> return ( OnionDestination asel ni Nothing , 0 ) | 374 | Nothing -> return ( OnionDestination asel ni Nothing , 0 ) |
366 | 375 | ||
367 | hookQueries :: OnionRouter -> (tid -> Nonce8) | 376 | hookQueries :: OnionRouter -> (tid -> Nonce8) |
368 | -> TransactionMethods d tid (OnionDestination r) x | 377 | -> TransactionMethods d tid (OnionDestination RouteId) x |
369 | -> TransactionMethods d tid (OnionDestination r) x | 378 | -> TransactionMethods d tid (OnionDestination RouteId) x |
370 | hookQueries or t8 tmethods = TransactionMethods | 379 | hookQueries or t8 tmethods = TransactionMethods |
371 | { dispatchRegister = \mvar od d -> do -- :: MVar x -> d -> STM (tid, d) | 380 | { dispatchRegister = \mvar od d -> do -- :: MVar x -> d -> STM (tid, d) |
372 | (tid,d') <- dispatchRegister tmethods mvar od d | 381 | (tid,d') <- dispatchRegister tmethods mvar od d |
373 | let Nonce8 w8 = t8 tid | 382 | let Nonce8 w8 = t8 tid |
374 | od' = case od of OnionDestination {} -> od { onionRouteSpec = Nothing } | ||
375 | OnionToOwner a b -> OnionToOwner a b -- Type cast. | ||
376 | ni = onionNodeInfo od | 383 | ni = onionNodeInfo od |
377 | modifyTVar' (pendingQueries or) (W64.insert w8 od') | 384 | rid@(RouteId ridn) = fromMaybe (routeId (nodeId ni)) $ onionRouteSpec od |
378 | writeTChan (routeLog or) $ "ONION query add " ++ unwords [ show w8, ":=", show ni ] | 385 | od' = case od of |
386 | OnionDestination {} -> od { onionRouteSpec = Just rid } | ||
387 | OnionToOwner a b -> OnionToOwner a b -- Type cast. | ||
388 | wanted <- readTVar (pendingRoutes or IntMap.! ridn) | ||
389 | mr <- IntMap.lookup ridn <$> readTVar (routeMap or) | ||
390 | -- Block query until a route is ready. | ||
391 | check $ fromMaybe False $ do | ||
392 | RouteRecord{routeVersion=rv} <- mr | ||
393 | return $ wanted <= rv | ||
394 | let pq = PendingQuery { pendingDestination = od' | ||
395 | , pendingVersion = maybe 0 routeVersion mr | ||
396 | } | ||
397 | pqs <- readTVar (pendingQueries or) | ||
398 | -- check $ W64.size pqs < 20 | ||
399 | modifyTVar' (pendingQueries or) (W64.insert w8 pq) | ||
400 | writeTChan (routeLog or) $ "ONION query add " ++ unwords [ show (Just $ pendingVersion pq,w8), ":=", show ni ] | ||
379 | return (tid,d') | 401 | return (tid,d') |
380 | , dispatchResponse = \tid x d -> do -- :: tid -> x -> d -> STM (d, IO ()) | 402 | , dispatchResponse = \tid x d -> do -- :: tid -> x -> d -> STM (d, IO ()) |
381 | let Nonce8 w8 = t8 tid | 403 | let Nonce8 w8 = t8 tid |
382 | mb <- W64.lookup w8 <$> readTVar (pendingQueries or) | 404 | mb <- W64.lookup w8 <$> readTVar (pendingQueries or) |
383 | modifyTVar' (pendingQueries or) (W64.delete w8) | 405 | modifyTVar' (pendingQueries or) (W64.delete w8) |
384 | forM_ mb $ \od -> do | 406 | forM_ mb $ \pq -> do |
385 | let RouteId rid = routeId (nodeId (onionNodeInfo od)) | 407 | let od = pendingDestination pq |
408 | RouteId rid = fromMaybe (routeId (nodeId (onionNodeInfo od))) | ||
409 | $ onionRouteSpec od | ||
386 | modifyTVar' (routeMap or) (IntMap.adjust gotResponse rid) | 410 | modifyTVar' (routeMap or) (IntMap.adjust gotResponse rid) |
387 | writeTChan (routeLog or) $ "ONION query del " ++ show w8 | 411 | writeTChan (routeLog or) $ "ONION query del " ++ show (fmap pendingVersion mb, w8) |
388 | dispatchResponse tmethods tid x d | 412 | dispatchResponse tmethods tid x d |
389 | , dispatchCancel = \tid d -> do -- :: tid -> d -> STM d | 413 | , dispatchCancel = \tid d -> do -- :: tid -> d -> STM d |
390 | let Nonce8 w8 = t8 tid | 414 | let Nonce8 w8 = t8 tid |
391 | mb <- W64.lookup w8 <$> readTVar (pendingQueries or) | 415 | mb <- W64.lookup w8 <$> readTVar (pendingQueries or) |
392 | modifyTVar' (pendingQueries or) (W64.delete w8) | 416 | modifyTVar' (pendingQueries or) (W64.delete w8) |
393 | forM_ mb $ \od -> do | 417 | forM_ mb $ \pq -> do |
394 | let RouteId rid = routeId (nodeId (onionNodeInfo od)) | 418 | let od = pendingDestination pq |
419 | RouteId rid = fromMaybe (routeId (nodeId (onionNodeInfo od))) | ||
420 | $ onionRouteSpec od | ||
421 | mrr <- IntMap.lookup rid <$> readTVar (routeMap or) | ||
422 | forM_ mrr $ \rr -> do | ||
423 | when (routeVersion rr == pendingVersion pq) $ do | ||
424 | let expireRoute = modifyTVar' (pendingRoutes or IntMap.! rid) expire | ||
425 | expire ver | ver <= succ (pendingVersion pq) = succ (pendingVersion pq) | ||
426 | | otherwise = ver | ||
395 | modifyTVar' (routeMap or) (IntMap.adjust gotTimeout rid) | 427 | modifyTVar' (routeMap or) (IntMap.adjust gotTimeout rid) |
396 | let expireRoute = writeTVar (pendingRoutes or IntMap.! rid) True | ||
397 | rr <- IntMap.lookup rid <$> readTVar (routeMap or) | ||
398 | case rr of | 428 | case rr of |
399 | Just RouteRecord{ responseCount=0 | 429 | RouteRecord{ responseCount = 0 |
400 | , timeoutCount = c } | c >= 2 -> expireRoute | 430 | , timeoutCount = c |
401 | Just RouteRecord{ timeoutCount = c } | c >= 4 -> expireRoute | 431 | , routeVersion = v } | c >= 2 -> expireRoute |
432 | RouteRecord{ timeoutCount = c | ||
433 | , routeVersion = v } | c >= 5 -> expireRoute | ||
402 | _ -> return () | 434 | _ -> return () |
403 | writeTChan (routeLog or) $ "ONION query can " ++ show w8 | 435 | writeTChan (routeLog or) $ "ONION query can " ++ show (fmap pendingVersion mb, w8) |
404 | dispatchCancel tmethods tid d | 436 | dispatchCancel tmethods tid d |
405 | } | 437 | } |
406 | 438 | ||