diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Network/BitTorrent/DHT/Query.hs | 73 | ||||
-rw-r--r-- | src/Network/BitTorrent/DHT/Session.hs | 102 |
2 files changed, 109 insertions, 66 deletions
diff --git a/src/Network/BitTorrent/DHT/Query.hs b/src/Network/BitTorrent/DHT/Query.hs index 68c67900..254b347c 100644 --- a/src/Network/BitTorrent/DHT/Query.hs +++ b/src/Network/BitTorrent/DHT/Query.hs | |||
@@ -80,6 +80,7 @@ import Text.PrettyPrint as PP hiding ((<>), ($$)) | |||
80 | import Text.PrettyPrint.HughesPJClass hiding ((<>),($$)) | 80 | import Text.PrettyPrint.HughesPJClass hiding ((<>),($$)) |
81 | import Data.Time | 81 | import Data.Time |
82 | import Data.Time.Clock.POSIX | 82 | import Data.Time.Clock.POSIX |
83 | import Data.Hashable (Hashable) | ||
83 | 84 | ||
84 | import Network.DatagramServer as KRPC hiding (Options, def) | 85 | import Network.DatagramServer as KRPC hiding (Options, def) |
85 | import Network.KRPC.Method as KRPC | 86 | import Network.KRPC.Method as KRPC |
@@ -109,13 +110,9 @@ import Control.Monad.Trans.Control | |||
109 | 110 | ||
110 | nodeHandler :: ( Address ip | 111 | nodeHandler :: ( Address ip |
111 | , KRPC (Query a) (Response b) | 112 | , KRPC (Query a) (Response b) |
112 | #ifdef VERSION_bencoding | 113 | ) |
113 | , KRPC.Envelope (Query a) (Response b) ~ BValue ) | 114 | => (NodeInfo KMessageOf ip () -> Maybe ReflectedIP -> IO ()) -> (NodeAddr ip -> IO (NodeId KMessageOf)) -> (Char -> String -> Text -> IO ()) -> QueryMethod KMessageOf -> (NodeAddr ip -> a -> IO b) -> NodeHandler |
114 | #else | 115 | nodeHandler insertNode myNodeIdAccordingTo logm method action = handler method $ \ sockAddr qry -> do |
115 | , KPRC.Envelope (Query a) (Response b) ~ ByteString ) | ||
116 | #endif | ||
117 | => QueryMethod KMessageOf -> (NodeAddr ip -> a -> DHT ip b) -> NodeHandler ip | ||
118 | nodeHandler method action = handler method $ \ sockAddr qry -> do | ||
119 | #ifdef VERSION_bencoding | 116 | #ifdef VERSION_bencoding |
120 | let remoteId = queringNodeId qry | 117 | let remoteId = queringNodeId qry |
121 | read_only = queryIsReadOnly qry | 118 | read_only = queryIsReadOnly qry |
@@ -131,53 +128,55 @@ nodeHandler method action = handler method $ \ sockAddr qry -> do | |||
131 | let ni = NodeInfo remoteId naddr () | 128 | let ni = NodeInfo remoteId naddr () |
132 | -- Do not route read-only nodes. (bep 43) | 129 | -- Do not route read-only nodes. (bep 43) |
133 | if read_only | 130 | if read_only |
134 | then $(logWarnS) "nodeHandler" $ "READ-ONLY " <> T.pack (show $ pPrint ni) | 131 | then logm 'W' "nodeHandler" $ "READ-ONLY " <> T.pack (show $ pPrint ni) |
135 | else insertNode ni Nothing >> return () -- TODO need to block. why? | 132 | else insertNode ni Nothing >> return () -- TODO need to block. why? |
136 | Response | 133 | Response |
137 | <$> myNodeIdAccordingTo naddr | 134 | <$> myNodeIdAccordingTo naddr |
138 | <*> action naddr q | 135 | <*> action naddr q |
139 | 136 | ||
140 | -- | Default 'Ping' handler. | 137 | -- | Default 'Ping' handler. |
141 | pingH :: Address ip => NodeHandler ip | 138 | pingH :: NodeAddr ip -> Ping -> IO Ping |
142 | #ifdef VERSION_bencoding | 139 | pingH _ Ping = return Ping |
143 | pingH = nodeHandler "ping" $ \ _ Ping -> return Ping | 140 | -- pingH = nodeHandler $ \ _ p@PingPayload{} -> return p { isPong = True } |
144 | #else | ||
145 | pingH = nodeHandler $ \ _ p@PingPayload{} -> return p { isPong = True } | ||
146 | #endif | ||
147 | 141 | ||
148 | -- | Default 'FindNode' handler. | 142 | -- | Default 'FindNode' handler. |
149 | findNodeH :: Address ip => NodeHandler ip | 143 | findNodeH :: (NodeId KMessageOf -> IO [NodeInfo KMessageOf ip ()]) -> NodeAddr ip -> FindNode ip -> IO (NodeFound ip) |
150 | findNodeH = nodeHandler "find-nodes" $ \ _ (FindNode nid) -> do | 144 | findNodeH getclosest _ (FindNode nid) = NodeFound <$> getclosest nid |
151 | NodeFound <$> getClosest nid | ||
152 | 145 | ||
153 | #ifdef VERSION_bencoding | ||
154 | -- | Default 'GetPeers' handler. | 146 | -- | Default 'GetPeers' handler. |
155 | getPeersH :: Ord ip => Address ip => NodeHandler ip | 147 | getPeersH :: Hashable ip => (InfoHash -> IO (PeerList ip)) -> TVar SessionTokens -> NodeAddr ip -> GetPeers ip -> IO (GotPeers ip) |
156 | getPeersH = nodeHandler "get_peers" $ \ naddr (GetPeers ih) -> do | 148 | getPeersH getPeerList toks naddr (GetPeers ih) = do |
157 | ps <- getPeerList ih | 149 | ps <- getPeerList ih |
158 | tok <- grantToken naddr | 150 | tok <- grantToken toks naddr |
159 | return $ GotPeers ps tok | 151 | return $ GotPeers ps tok |
160 | 152 | ||
161 | -- | Default 'Announce' handler. | 153 | -- | Default 'Announce' handler. |
162 | announceH :: Ord ip => Address ip => NodeHandler ip | 154 | announceH :: ( Ord ip, Hashable ip ) => TVar (PeerStore ip) -> TVar SessionTokens -> NodeAddr ip -> Announce -> IO Announced |
163 | announceH = nodeHandler "announce_peer" $ \ naddr @ NodeAddr {..} (Announce {..}) -> do | 155 | announceH peers toks naddr @ NodeAddr {..} (Announce {..}) = do |
164 | valid <- checkToken naddr sessionToken | 156 | valid <- checkToken toks naddr sessionToken |
165 | unless valid $ do | 157 | unless valid $ do |
166 | throwIO $ InvalidParameter "token" | 158 | throwIO $ InvalidParameter "token" |
167 | 159 | ||
168 | let annPort = if impliedPort then nodePort else port | 160 | let annPort = if impliedPort then nodePort else port |
169 | peerAddr = PeerAddr Nothing nodeHost annPort | 161 | peerAddr = PeerAddr Nothing nodeHost annPort |
170 | insertPeer topic announcedName peerAddr | 162 | insertPeer peers topic announcedName peerAddr |
171 | return Announced | 163 | return Announced |
172 | 164 | ||
173 | -- | Includes all default query handlers. | 165 | -- | Includes all default query handlers. |
174 | defaultHandlers :: Ord ip => Address ip => [NodeHandler ip] | 166 | defaultHandlers :: forall ip. (Eq ip, Ord ip, Address ip) => LogFun -> DHT ip [NodeHandler] |
175 | defaultHandlers = [pingH, findNodeH, getPeersH, announceH] | 167 | defaultHandlers logger = do |
176 | #else | 168 | groknode <- insertNode1 |
177 | -- | Includes all default query handlers. | 169 | toks <- asks sessionTokens |
178 | defaultHandlers :: Ord ip => Address ip => [NodeHandler ip] | 170 | getclosest <- getClosest1 |
179 | defaultHandlers = [pingH, findNodeH] | 171 | mynid <- myNodeIdAccordingTo1 |
180 | #endif | 172 | peers <- asks contactInfo |
173 | getpeers <- getPeerList1 | ||
174 | let handler :: KRPC (Query a) (Response b) => QueryMethod KMessageOf -> (NodeAddr ip -> a -> IO b) -> NodeHandler | ||
175 | handler = nodeHandler groknode mynid (logt logger) | ||
176 | return [ handler "ping" $ pingH | ||
177 | , handler "find-nodes" $ findNodeH getclosest | ||
178 | , handler "get_peers" $ getPeersH getpeers toks | ||
179 | , handler "announce_peer" $ announceH peers toks ] | ||
181 | 180 | ||
182 | {----------------------------------------------------------------------- | 181 | {----------------------------------------------------------------------- |
183 | -- Basic queries | 182 | -- Basic queries |
@@ -324,6 +323,11 @@ logc c = $(logInfoS) "insertNode" . T.pack . (c :) . (':' :) | |||
324 | -- routing table. | 323 | -- routing table. |
325 | insertNode :: forall ip. Address ip => NodeInfo KMessageOf ip () -> Maybe ReflectedIP -> DHT ip () | 324 | insertNode :: forall ip. Address ip => NodeInfo KMessageOf ip () -> Maybe ReflectedIP -> DHT ip () |
326 | insertNode info witnessed_ip0 = do | 325 | insertNode info witnessed_ip0 = do |
326 | f <- insertNode1 | ||
327 | liftIO $ f info witnessed_ip0 | ||
328 | |||
329 | insertNode1 :: forall ip. Address ip => DHT ip (NodeInfo KMessageOf ip () -> Maybe ReflectedIP -> IO ()) | ||
330 | insertNode1 = do | ||
327 | bc <- optBucketCount <$> asks options | 331 | bc <- optBucketCount <$> asks options |
328 | nid <- asks tentativeNodeId | 332 | nid <- asks tentativeNodeId |
329 | logm0 <- embed_ (uncurry logc) | 333 | logm0 <- embed_ (uncurry logc) |
@@ -349,7 +353,7 @@ insertNode info witnessed_ip0 = do | |||
349 | , grokNode = DHT.insertNode params state | 353 | , grokNode = DHT.insertNode params state |
350 | , grokAddress = \_ _ -> return () -- :: Maybe SockAddr -> ReflectedIP -> IO () | 354 | , grokAddress = \_ _ -> return () -- :: Maybe SockAddr -> ReflectedIP -> IO () |
351 | } | 355 | } |
352 | liftIO $ DHT.insertNode params state info witnessed_ip0 | 356 | return $ \info witnessed_ip0 -> DHT.insertNode params state info witnessed_ip0 |
353 | 357 | ||
354 | -- | Throws exception if node is not responding. | 358 | -- | Throws exception if node is not responding. |
355 | queryNode :: forall a b ip. Address ip => KRPC (Query a) (Response b) | 359 | queryNode :: forall a b ip. Address ip => KRPC (Query a) (Response b) |
@@ -362,7 +366,8 @@ queryNode' addr q = do | |||
362 | nid <- myNodeIdAccordingTo addr | 366 | nid <- myNodeIdAccordingTo addr |
363 | let read_only = False -- TODO: check for NAT issues. (BEP 43) | 367 | let read_only = False -- TODO: check for NAT issues. (BEP 43) |
364 | let KRPC.Method name = KRPC.method :: KRPC.Method (Query a) (Response b) | 368 | let KRPC.Method name = KRPC.method :: KRPC.Method (Query a) (Response b) |
365 | (Response remoteId r, witnessed_ip) <- query' name (toSockAddr addr) (Query nid read_only q) | 369 | mgr <- asks manager |
370 | (Response remoteId r, witnessed_ip) <- liftIO $ query' mgr name (toSockAddr addr) (Query nid read_only q) | ||
366 | -- \$(logDebugS) "queryNode" $ "Witnessed address: " <> T.pack (show witnessed_ip) | 371 | -- \$(logDebugS) "queryNode" $ "Witnessed address: " <> T.pack (show witnessed_ip) |
367 | -- <> " by " <> T.pack (show (toSockAddr addr)) | 372 | -- <> " by " <> T.pack (show (toSockAddr addr)) |
368 | _ <- insertNode (NodeInfo remoteId addr ()) witnessed_ip | 373 | _ <- insertNode (NodeInfo remoteId addr ()) witnessed_ip |
diff --git a/src/Network/BitTorrent/DHT/Session.hs b/src/Network/BitTorrent/DHT/Session.hs index 7e87df6c..d8665773 100644 --- a/src/Network/BitTorrent/DHT/Session.hs +++ b/src/Network/BitTorrent/DHT/Session.hs | |||
@@ -31,12 +31,19 @@ module Network.BitTorrent.DHT.Session | |||
31 | , options | 31 | , options |
32 | , tentativeNodeId | 32 | , tentativeNodeId |
33 | , myNodeIdAccordingTo | 33 | , myNodeIdAccordingTo |
34 | , myNodeIdAccordingTo1 | ||
34 | , routingInfo | 35 | , routingInfo |
35 | , routableAddress | 36 | , routableAddress |
36 | , getTimestamp | 37 | , getTimestamp |
38 | , SessionTokens | ||
39 | , sessionTokens | ||
40 | , contactInfo | ||
41 | , PeerStore | ||
42 | , manager | ||
37 | 43 | ||
38 | -- ** Initialization | 44 | -- ** Initialization |
39 | , LogFun | 45 | , LogFun |
46 | , logt | ||
40 | , NodeHandler | 47 | , NodeHandler |
41 | , newNode | 48 | , newNode |
42 | , closeNode | 49 | , closeNode |
@@ -54,11 +61,13 @@ module Network.BitTorrent.DHT.Session | |||
54 | -- ** Routing table | 61 | -- ** Routing table |
55 | , getTable | 62 | , getTable |
56 | , getClosest | 63 | , getClosest |
64 | , getClosest1 | ||
57 | 65 | ||
58 | #ifdef VERSION_bencoding | 66 | #ifdef VERSION_bencoding |
59 | -- ** Peer storage | 67 | -- ** Peer storage |
60 | , insertPeer | 68 | , insertPeer |
61 | , getPeerList | 69 | , getPeerList |
70 | , getPeerList1 | ||
62 | , insertTopic | 71 | , insertTopic |
63 | , deleteTopic | 72 | , deleteTopic |
64 | , getSwarms | 73 | , getSwarms |
@@ -333,7 +342,7 @@ instance MonadLogger (DHT ip) where | |||
333 | liftIO $ logger loc src lvl (toLogStr msg) | 342 | liftIO $ logger loc src lvl (toLogStr msg) |
334 | 343 | ||
335 | #ifdef VERSION_bencoding | 344 | #ifdef VERSION_bencoding |
336 | type NodeHandler ip = Handler IO KMessageOf BValue | 345 | type NodeHandler = Handler IO KMessageOf BValue |
337 | #else | 346 | #else |
338 | type NodeHandler ip = Handler (DHT ip) Tox.Message ByteString | 347 | type NodeHandler ip = Handler (DHT ip) Tox.Message ByteString |
339 | #endif | 348 | #endif |
@@ -368,10 +377,10 @@ locFromCS cs = case getCallStack cs of | |||
368 | -- 'closeNode' function, otherwise socket or other scarce resources may | 377 | -- 'closeNode' function, otherwise socket or other scarce resources may |
369 | -- leak. | 378 | -- leak. |
370 | newNode :: Address ip | 379 | newNode :: Address ip |
371 | => [NodeHandler ip] -- ^ handlers to run on accepted queries; | 380 | => [NodeHandler] -- ^ handlers to run on accepted queries; |
372 | -> Options -- ^ various dht options; | 381 | -> Options -- ^ various dht options; |
373 | -> NodeAddr ip -- ^ node address to bind; | 382 | -> NodeAddr ip -- ^ node address to bind; |
374 | -> LogFun -- ^ | 383 | -> LogFun -- ^ invoked on log messages; |
375 | #ifdef VERSION_bencoding | 384 | #ifdef VERSION_bencoding |
376 | -> Maybe (NodeId KMessageOf) -- ^ use this NodeId, if not given a new one is generated. | 385 | -> Maybe (NodeId KMessageOf) -- ^ use this NodeId, if not given a new one is generated. |
377 | #else | 386 | #else |
@@ -420,24 +429,23 @@ runDHT node action = runReaderT (unDHT action) node | |||
420 | -- Tokens | 429 | -- Tokens |
421 | -----------------------------------------------------------------------} | 430 | -----------------------------------------------------------------------} |
422 | 431 | ||
423 | tryUpdateSecret :: DHT ip () | 432 | tryUpdateSecret :: TVar SessionTokens -> IO () |
424 | tryUpdateSecret = do | 433 | tryUpdateSecret toks = do |
425 | curTime <- liftIO getCurrentTime | 434 | curTime <- liftIO getCurrentTime |
426 | toks <- asks sessionTokens | ||
427 | liftIO $ atomically $ modifyTVar' toks (invalidateTokens curTime) | 435 | liftIO $ atomically $ modifyTVar' toks (invalidateTokens curTime) |
428 | 436 | ||
429 | grantToken :: Hashable a => NodeAddr a -> DHT ip Token | 437 | grantToken :: Hashable a => TVar SessionTokens -> NodeAddr a -> IO Token |
430 | grantToken addr = do | 438 | grantToken sessionTokens addr = do |
431 | tryUpdateSecret | 439 | tryUpdateSecret sessionTokens |
432 | toks <- asks sessionTokens >>= liftIO . readTVarIO | 440 | toks <- readTVarIO sessionTokens |
433 | return $ T.lookup addr $ tokenMap toks | 441 | return $ T.lookup addr $ tokenMap toks |
434 | 442 | ||
435 | -- | Throws 'HandlerError' if the token is invalid or already | 443 | -- | Throws 'HandlerError' if the token is invalid or already |
436 | -- expired. See 'TokenMap' for details. | 444 | -- expired. See 'TokenMap' for details. |
437 | checkToken :: Hashable a => NodeAddr a -> Token -> DHT ip Bool | 445 | checkToken :: Hashable a => TVar SessionTokens -> NodeAddr a -> Token -> IO Bool |
438 | checkToken addr questionableToken = do | 446 | checkToken sessionTokens addr questionableToken = do |
439 | tryUpdateSecret | 447 | tryUpdateSecret sessionTokens |
440 | toks <- asks sessionTokens >>= liftIO . readTVarIO | 448 | toks <- readTVarIO sessionTokens |
441 | return $ T.member addr questionableToken (tokenMap toks) | 449 | return $ T.member addr questionableToken (tokenMap toks) |
442 | 450 | ||
443 | 451 | ||
@@ -463,6 +471,14 @@ myNodeIdAccordingTo _ = do | |||
463 | (return . myNodeId) | 471 | (return . myNodeId) |
464 | info | 472 | info |
465 | 473 | ||
474 | myNodeIdAccordingTo1 :: DHT ip ( NodeAddr ip -> IO (NodeId KMessageOf) ) | ||
475 | myNodeIdAccordingTo1 = do | ||
476 | var <- asks routingInfo | ||
477 | tid <- asks tentativeNodeId | ||
478 | return $ \ _ -> do | ||
479 | info <- atomically $ readTVar var | ||
480 | return $ maybe tid myNodeId info | ||
481 | |||
466 | -- | Get current routing table. Normally you don't need to use this | 482 | -- | Get current routing table. Normally you don't need to use this |
467 | -- function, but it can be usefull for debugging and profiling purposes. | 483 | -- function, but it can be usefull for debugging and profiling purposes. |
468 | #ifdef VERSION_bencoding | 484 | #ifdef VERSION_bencoding |
@@ -515,38 +531,48 @@ getClosest node = do | |||
515 | k <- asks (optK . options) | 531 | k <- asks (optK . options) |
516 | kclosest k node <$> getTable | 532 | kclosest k node <$> getTable |
517 | 533 | ||
534 | getClosest1 :: ( Eq ip | ||
535 | , TableKey KMessageOf k | ||
536 | ) => DHT ip (k -> IO [NodeInfo KMessageOf ip ()]) | ||
537 | getClosest1 = do | ||
538 | k <- asks (optK . options) | ||
539 | nobkts <- asks (optBucketCount . options) | ||
540 | myid <- asks tentativeNodeId | ||
541 | var <- asks routingInfo | ||
542 | return $ \node -> do nfo <- atomically $ readTVar var | ||
543 | let tbl = maybe (nullTable myid nobkts) R.myBuckets nfo | ||
544 | return $ kclosest k node tbl | ||
545 | |||
518 | {----------------------------------------------------------------------- | 546 | {----------------------------------------------------------------------- |
519 | -- Peer storage | 547 | -- Peer storage |
520 | -----------------------------------------------------------------------} | 548 | -----------------------------------------------------------------------} |
521 | 549 | ||
522 | refreshContacts :: DHT ip () | 550 | refreshContacts :: Ord ip => TVar (PeerStore ip) -> IO () |
523 | refreshContacts = | 551 | refreshContacts var = |
524 | -- TODO limit dht peer store in size (probably by removing oldest peers) | 552 | -- TODO limit dht peer store in size (probably by removing oldest peers) |
525 | return () | 553 | return () |
526 | 554 | ||
527 | 555 | ||
528 | -- | Insert peer to peer store. Used to handle announce requests. | 556 | -- | Insert peer to peer store. Used to handle announce requests. |
529 | insertPeer :: Ord ip => InfoHash -> Maybe ByteString -> PeerAddr ip -> DHT ip () | 557 | insertPeer :: Ord ip => TVar (PeerStore ip) -> InfoHash -> Maybe ByteString -> PeerAddr ip -> IO () |
530 | insertPeer ih name addr = do | 558 | insertPeer var ih name addr = do |
531 | refreshContacts | 559 | refreshContacts var |
532 | var <- asks contactInfo | 560 | atomically $ modifyTVar' var (P.insertPeer ih name addr) |
533 | liftIO $ atomically $ modifyTVar' var (P.insertPeer ih name addr) | ||
534 | 561 | ||
535 | -- | Get peer set for specific swarm. | 562 | -- | Get peer set for specific swarm. |
536 | lookupPeers :: Ord ip => InfoHash -> DHT ip [PeerAddr ip] | 563 | lookupPeers :: Ord ip => TVar (PeerStore ip) -> InfoHash -> IO [PeerAddr ip] |
537 | lookupPeers ih = do | 564 | lookupPeers var ih = do |
538 | refreshContacts | 565 | refreshContacts var |
539 | var <- asks contactInfo | ||
540 | tm <- getTimestamp | 566 | tm <- getTimestamp |
541 | liftIO $ atomically $ do | 567 | atomically $ do |
542 | (ps,store') <- P.freshPeers ih tm <$> readTVar var | 568 | (ps,store') <- P.freshPeers ih tm <$> readTVar var |
543 | writeTVar var store' | 569 | writeTVar var store' |
544 | return ps | 570 | return ps |
545 | 571 | ||
546 | getTimestamp :: DHT ip Timestamp | 572 | getTimestamp :: IO Timestamp |
547 | getTimestamp = do | 573 | getTimestamp = do |
548 | utcTime <- liftIO $ getCurrentTime | 574 | utcTime <- getCurrentTime |
549 | $(logDebugS) "routing.make_timestamp" (Text.pack (render (pPrint utcTime))) | 575 | -- $(logDebugS) "routing.make_timestamp" (Text.pack (render (pPrint utcTime))) |
550 | return $ utcTimeToPOSIXSeconds utcTime | 576 | return $ utcTimeToPOSIXSeconds utcTime |
551 | 577 | ||
552 | 578 | ||
@@ -557,11 +583,23 @@ getTimestamp = do | |||
557 | -- | 583 | -- |
558 | getPeerList :: Ord ip => InfoHash -> DHT ip (PeerList ip) | 584 | getPeerList :: Ord ip => InfoHash -> DHT ip (PeerList ip) |
559 | getPeerList ih = do | 585 | getPeerList ih = do |
560 | ps <- lookupPeers ih | 586 | var <- asks contactInfo |
587 | ps <- liftIO $ lookupPeers var ih | ||
561 | if L.null ps | 588 | if L.null ps |
562 | then Left <$> getClosest ih | 589 | then Left <$> getClosest ih |
563 | else return (Right ps) | 590 | else return (Right ps) |
564 | 591 | ||
592 | getPeerList1 :: Ord ip => DHT ip (InfoHash -> IO (PeerList ip)) | ||
593 | getPeerList1 = do | ||
594 | var <- asks contactInfo | ||
595 | getclosest <- getClosest1 | ||
596 | return $ \ih -> do | ||
597 | ps <- lookupPeers var ih | ||
598 | if L.null ps | ||
599 | then Left <$> getclosest ih | ||
600 | else return (Right ps) | ||
601 | |||
602 | |||
565 | insertTopic :: InfoHash -> PortNumber -> DHT ip () | 603 | insertTopic :: InfoHash -> PortNumber -> DHT ip () |
566 | insertTopic ih p = do | 604 | insertTopic ih p = do |
567 | var <- asks announceInfo | 605 | var <- asks announceInfo |