summaryrefslogtreecommitdiff
path: root/src/Network/Tox/DHT
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/Tox/DHT')
-rw-r--r--src/Network/Tox/DHT/Handlers.hs306
1 files changed, 306 insertions, 0 deletions
diff --git a/src/Network/Tox/DHT/Handlers.hs b/src/Network/Tox/DHT/Handlers.hs
new file mode 100644
index 00000000..901da99e
--- /dev/null
+++ b/src/Network/Tox/DHT/Handlers.hs
@@ -0,0 +1,306 @@
1{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2{-# LANGUAGE PatternSynonyms #-}
3{-# LANGUAGE TupleSections #-}
4module Network.Tox.DHT.Handlers where
5
6import Network.Tox.DHT.Transport as DHTTransport
7import Network.QueryResponse as QR hiding (Client)
8import qualified Network.QueryResponse as QR (Client)
9import Crypto.Tox
10import Network.BitTorrent.DHT.Search
11import qualified Data.Wrapper.PSQInt as Int
12import Network.Kademlia
13import Network.Address (WantIP (..), ipFamily, testIdBit,fromSockAddr, sockAddrPort)
14import qualified Network.DHT.Routing as R
15import Control.TriadCommittee
16import System.Global6
17
18import qualified Data.ByteArray as BA
19import qualified Data.ByteString.Char8 as C8
20import qualified Data.ByteString.Base16 as Base16
21import Control.Arrow
22import Control.Monad
23import Control.Concurrent.STM
24import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime)
25import Network.Socket
26import Data.Hashable
27import Data.IP
28import Data.Ord
29import Data.Maybe
30import Data.Bits
31import Data.Serialize (Serialize)
32import Data.Word
33import System.IO
34
35data TransactionId = TransactionId
36 { transactionKey :: Nonce8 -- ^ Used to lookup pending query.
37 , cryptoNonce :: Nonce24 -- ^ Used during the encryption layer.
38 }
39 deriving (Eq,Ord,Show)
40
41newtype PacketKind = PacketKind Word8
42 deriving (Eq, Ord, Serialize)
43
44pattern OnionRequest0Type = PacketKind 128 -- 0x80 Onion Request 0
45pattern OnionRequest1Type = PacketKind 129 -- 0x81 Onion Request 1
46pattern OnionRequest2Type = PacketKind 130 -- 0x82 Onion Request 2
47pattern AnnounceType = PacketKind 131 -- 0x83 Announce Request
48pattern AnnounceResponseType = PacketKind 132 -- 0x84 Announce Response
49
50pattern DataRequestType = PacketKind 133 -- 0x85 Onion Data Request (data to route request packet)
51pattern DataResponseType = PacketKind 134 -- 0x86 Onion Data Response (data to route response packet)
52-- 0x8c Onion Response 3
53-- 0x8d Onion Response 2
54pattern OnionResponse3Type = PacketKind 140 -- 0x8c Onion Response 3
55pattern OnionResponse2Type = PacketKind 141 -- 0x8d Onion Response 2
56pattern OnionResponse1Type = PacketKind 142 -- 0x8e Onion Response 1
57-- 0xf0 Bootstrap Info
58
59pattern DHTRequestType = PacketKind 32 -- 0x20 DHT Request
60
61pattern CookieRequestType = PacketKind 0x18
62pattern CookieResponseType = PacketKind 0x19
63
64pattern PingType = PacketKind 0 -- 0x00 Ping Request
65pattern PongType = PacketKind 1 -- 0x01 Ping Response
66pattern GetNodesType = PacketKind 2 -- 0x02 Nodes Request
67pattern SendNodesType = PacketKind 4 -- 0x04 Nodes Response
68
69
70instance Show PacketKind where
71 showsPrec d PingType = mappend "PingType"
72 showsPrec d PongType = mappend "PongType"
73 showsPrec d GetNodesType = mappend "GetNodesType"
74 showsPrec d SendNodesType = mappend "SendNodesType"
75 showsPrec d DHTRequestType = mappend "DHTRequestType"
76 showsPrec d OnionRequest0Type = mappend "OnionRequest0Type"
77 showsPrec d OnionResponse1Type = mappend "OnionResponse1Type"
78 showsPrec d OnionResponse3Type = mappend "OnionResponse3Type"
79 showsPrec d AnnounceType = mappend "AnnounceType"
80 showsPrec d AnnounceResponseType = mappend "AnnounceResponseType"
81 showsPrec d DataRequestType = mappend "DataRequestType"
82 showsPrec d DataResponseType = mappend "DataResponseType"
83 showsPrec d CookieRequestType = mappend "CookieRequestType"
84 showsPrec d CookieResponseType = mappend "CookieResponseType"
85 showsPrec d (PacketKind x) = mappend "PacketKind " . showsPrec (d+1) x
86
87
88classify :: Message -> MessageClass String PacketKind TransactionId
89classify msg = mapMessage (\nonce24 (nonce8,_) -> go msg (TransactionId nonce8 nonce24)) msg
90 where
91 go (DHTPing {}) = IsQuery PingType
92 go (DHTGetNodes {}) = IsQuery GetNodesType
93 go (DHTPong {}) = IsResponse
94 go (DHTSendNodes {}) = IsResponse
95 go (DHTCookieRequest {}) = IsQuery (PacketKind 0x18)
96 go (DHTCookie {}) = IsResponse
97 go (DHTDHTRequest {}) = IsQuery DHTRequestType
98
99data Routing = Routing
100 { tentativeId :: NodeInfo
101 , sched4 :: !( TVar (Int.PSQ POSIXTime) )
102 , routing4 :: !( TVar (R.BucketList NodeInfo) )
103 , committee4 :: TriadCommittee NodeId SockAddr
104 , sched6 :: !( TVar (Int.PSQ POSIXTime) )
105 , routing6 :: !( TVar (R.BucketList NodeInfo) )
106 , committee6 :: TriadCommittee NodeId SockAddr
107 }
108
109newRouting :: SockAddr -> TransportCrypto
110 -> (TVar (R.BucketList NodeInfo) -> SockAddr -> STM ()) -- ^ invoked on IPv4 change
111 -> (TVar (R.BucketList NodeInfo) -> SockAddr -> STM ()) -- ^ invoked on IPv6 change
112 -> IO Routing
113newRouting addr crypto update4 update6 = do
114 let tentative_ip4 = fromMaybe (IPv4 $ toEnum 0) (IPv4 <$> fromSockAddr addr)
115 tentative_ip6 = fromMaybe (IPv6 $ toEnum 0) (IPv6 <$> fromSockAddr addr)
116 tentative_info = NodeInfo
117 { nodeId = key2id $ transportPublic crypto
118 , nodeIP = fromMaybe (toEnum 0) (fromSockAddr addr)
119 , nodePort = fromMaybe 0 $ sockAddrPort addr
120 }
121 tentative_info4 = tentative_info { nodeIP = tentative_ip4 }
122 tentative_info6 <-
123 maybe (tentative_info { nodeIP = tentative_ip6 })
124 (\ip6 -> tentative_info { nodeIP = IPv6 ip6 })
125 <$> global6
126 atomically $ do
127 let nobkts = R.defaultBucketCount :: Int
128 tbl4 <- newTVar $ R.nullTable (comparing nodeId) (\s -> hashWithSalt s . nodeId) tentative_info4 nobkts
129 tbl6 <- newTVar $ R.nullTable (comparing nodeId) (\s -> hashWithSalt s . nodeId) tentative_info6 nobkts
130 committee4 <- newTriadCommittee (update4 tbl4) -- $ updateIPVote tbl4 addr4
131 committee6 <- newTriadCommittee (update6 tbl6) -- $ updateIPVote tbl6 addr6
132 sched4 <- newTVar Int.empty
133 sched6 <- newTVar Int.empty
134 return $ Routing tentative_info sched4 tbl4 committee4 sched6 tbl6 committee6
135
136
137-- TODO: This should cover more cases
138isLocal :: IP -> Bool
139isLocal (IPv6 ip6) = (ip6 == toEnum 0)
140isLocal (IPv4 ip4) = (ip4 == toEnum 0)
141
142isGlobal :: IP -> Bool
143isGlobal = not . isLocal
144
145prefer4or6 :: NodeInfo -> Maybe WantIP -> WantIP
146prefer4or6 addr iptyp = fromMaybe (ipFamily $ nodeIP addr) iptyp
147
148toxSpace :: R.KademliaSpace NodeId NodeInfo
149toxSpace = R.KademliaSpace
150 { R.kademliaLocation = nodeId
151 , R.kademliaTestBit = testNodeIdBit
152 , R.kademliaXor = xorNodeId
153 , R.kademliaSample = sampleNodeId
154 }
155
156
157pingH :: NodeInfo -> Ping -> IO Pong
158pingH _ Ping = return Pong
159
160getNodesH :: Routing -> NodeInfo -> GetNodes -> IO SendNodes
161getNodesH routing addr (GetNodes nid) = do
162 let preferred = prefer4or6 addr Nothing
163
164 (append4,append6) <- atomically $ do
165 ni4 <- R.thisNode <$> readTVar (routing4 routing)
166 ni6 <- R.thisNode <$> readTVar (routing6 routing)
167 return $ case ipFamily (nodeIP addr) of
168 Want_IP4 | isGlobal (nodeIP ni6) -> (id, (++ [ni6]))
169 Want_IP6 | isGlobal (nodeIP ni4) -> ((++ [ni4]), id)
170 _ -> (id, id)
171 ks <- go append4 $ routing4 routing
172 ks6 <- go append6 $ routing6 routing
173 let (ns1,ns2) = case preferred of Want_IP6 -> (ks6,ks)
174 Want_IP4 -> (ks,ks6)
175 return $ SendNodes
176 $ if null ns2 then ns1
177 else take 4 (take 3 ns1 ++ ns2)
178 where
179 go f var = f . R.kclosest toxSpace k nid <$> atomically (readTVar var)
180
181 k = 4
182
183type Message = DHTMessage ((,) Nonce8)
184
185type Client = QR.Client String PacketKind TransactionId NodeInfo Message
186
187
188wrapAssym :: TransactionId -> NodeInfo -> NodeInfo -> (Nonce8 -> dta) -> Assym dta
189wrapAssym (TransactionId n8 n24) src dst dta = Assym
190 { senderKey = id2key $ nodeId src
191 , assymNonce = n24
192 , assymData = dta n8
193 }
194
195serializer :: PacketKind
196 -> (Assym (Nonce8,ping) -> Message)
197 -> (Message -> Maybe (Assym (Nonce8,pong)))
198 -> MethodSerializer TransactionId NodeInfo Message PacketKind ping (Maybe pong)
199serializer pktkind mkping mkpong = MethodSerializer
200 { methodTimeout = 5
201 , method = pktkind
202 -- wrapQuery :: tid -> addr -> addr -> qry -> x
203 , wrapQuery = \tid src dst ping -> mkping $ wrapAssym tid src dst (, ping)
204 -- unwrapResponse :: x -> b
205 , unwrapResponse = fmap (snd . assymData) . mkpong
206 }
207
208
209unpong :: Message -> Maybe (Assym (Nonce8,Pong))
210unpong (DHTPong assym) = Just assym
211unpong _ = Nothing
212
213showHex :: BA.ByteArrayAccess ba => ba -> String
214showHex bs = C8.unpack $ Base16.encode $ BA.convert bs
215
216ping :: Client -> NodeInfo -> IO Bool
217ping client addr = do
218 hPutStrLn stderr $ show addr ++ " <-- ping"
219 reply <- QR.sendQuery client (serializer PingType DHTPing unpong) Ping addr
220 hPutStrLn stderr $ show addr ++ " -pong-> " ++ show reply
221 maybe (return False) (\Pong -> return True) $ join reply
222
223unsendNodes :: Message -> Maybe (Assym (Nonce8,SendNodes))
224unsendNodes (DHTSendNodes assym) = Just assym
225unsendNodes _ = Nothing
226
227unwrapNodes :: SendNodes -> ( [NodeInfo], [NodeInfo], () )
228unwrapNodes (SendNodes ns) = (ns,ns,())
229
230getNodes :: Client -> NodeId -> NodeInfo -> IO (Maybe ([NodeInfo],[NodeInfo],()))
231getNodes client nid addr = do
232 hPutStrLn stderr $ show addr ++ " <-- getnodes " ++ show nid
233 reply <- QR.sendQuery client (serializer GetNodesType DHTGetNodes unsendNodes) (GetNodes nid) addr
234 hPutStrLn stderr $ show addr ++ " -sendnodes-> " ++ show reply
235 return $ fmap unwrapNodes $ join reply
236
237updateRouting :: Client -> Routing -> NodeInfo -> Message -> IO ()
238updateRouting client routing naddr msg = do
239 let typ = fst $ dhtMessageType $ fst $ DHTTransport.encrypt (error "updateRouting") msg naddr
240 tid = mapMessage (\n24 (n8,_) -> TransactionId n8 n24) msg
241 hPutStrLn stderr $ "updateRouting "++show (typ,tid)
242 -- TODO: check msg type
243 case prefer4or6 naddr Nothing of
244 Want_IP4 -> updateTable client naddr (routing4 routing) (committee4 routing) (sched4 routing)
245 Want_IP6 -> updateTable client naddr (routing6 routing) (committee6 routing) (sched6 routing)
246
247updateTable :: Client -> NodeInfo -> TVar (R.BucketList NodeInfo) -> TriadCommittee NodeId SockAddr -> TVar (Int.PSQ POSIXTime) -> IO ()
248updateTable client naddr tbl committee sched = do
249 self <- atomically $ R.thisNode <$> readTVar tbl
250 when (nodeIP self /= nodeIP naddr) $ do
251 -- TODO: IP address vote?
252 insertNode (toxKademlia client committee tbl sched) naddr
253
254toxKademlia :: Client -> TriadCommittee NodeId SockAddr -> TVar (R.BucketList NodeInfo) -> TVar (Int.PSQ POSIXTime) -> Kademlia NodeId NodeInfo
255toxKademlia client committee var sched
256 = Kademlia quietInsertions
257 toxSpace
258 (vanillaIO var $ ping client)
259 { tblTransition = \tr -> do
260 io1 <- transitionCommittee committee tr
261 io2 <- touchBucket toxSpace (15*60) var sched tr
262 return $ do
263 io1 >> io2
264 {-
265 hPutStrLn stderr $ unwords
266 [ show (transitionedTo tr)
267 , show (transitioningNode tr)
268 ]
269 -}
270 return ()
271 }
272
273transitionCommittee :: TriadCommittee NodeId SockAddr -> RoutingTransition NodeInfo -> STM (IO ())
274transitionCommittee committee (RoutingTransition ni Stranger) = do
275 delVote committee (nodeId ni)
276 return $ do
277 -- hPutStrLn stderr $ "delVote "++show (nodeId ni)
278 return ()
279transitionCommittee committee _ = return $ return ()
280
281type Handler = MethodHandler String TransactionId NodeInfo Message
282
283isPing :: (f Ping -> Ping) -> DHTMessage f -> Either String Ping
284isPing unpack (DHTPing a) = Right $ unpack $ assymData a
285isPing _ _ = Left "Bad ping"
286
287mkPong :: TransactionId -> NodeInfo -> NodeInfo -> Pong -> DHTMessage ((,) Nonce8)
288mkPong tid src dst pong = DHTPong $ wrapAssym tid src dst (, pong)
289
290isGetNodes :: (f GetNodes -> GetNodes) -> DHTMessage f -> Either String GetNodes
291isGetNodes unpack (DHTGetNodes a) = Right $ unpack $ assymData a
292isGetNodes _ _ = Left "Bad GetNodes"
293
294mkSendNodes :: TransactionId -> NodeInfo -> NodeInfo -> SendNodes -> DHTMessage ((,) Nonce8)
295mkSendNodes tid src dst sendnodes = DHTSendNodes $ wrapAssym tid src dst (, sendnodes)
296
297handlers :: Routing -> PacketKind -> Maybe Handler
298handlers routing PingType = Just $ MethodHandler (isPing snd) mkPong pingH
299handlers routing GetNodesType = Just $ MethodHandler (isGetNodes snd) mkSendNodes $ getNodesH routing
300
301nodeSearch :: Client -> Search NodeId (IP,PortNumber) () NodeInfo NodeInfo
302nodeSearch client = Search
303 { searchSpace = toxSpace
304 , searchNodeAddress = nodeIP &&& nodePort
305 , searchQuery = getNodes client
306 }