diff options
Diffstat (limited to 'src/Network/Tox/DHT/Handlers.hs')
-rw-r--r-- | src/Network/Tox/DHT/Handlers.hs | 306 |
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 #-} | ||
4 | module Network.Tox.DHT.Handlers where | ||
5 | |||
6 | import Network.Tox.DHT.Transport as DHTTransport | ||
7 | import Network.QueryResponse as QR hiding (Client) | ||
8 | import qualified Network.QueryResponse as QR (Client) | ||
9 | import Crypto.Tox | ||
10 | import Network.BitTorrent.DHT.Search | ||
11 | import qualified Data.Wrapper.PSQInt as Int | ||
12 | import Network.Kademlia | ||
13 | import Network.Address (WantIP (..), ipFamily, testIdBit,fromSockAddr, sockAddrPort) | ||
14 | import qualified Network.DHT.Routing as R | ||
15 | import Control.TriadCommittee | ||
16 | import System.Global6 | ||
17 | |||
18 | import qualified Data.ByteArray as BA | ||
19 | import qualified Data.ByteString.Char8 as C8 | ||
20 | import qualified Data.ByteString.Base16 as Base16 | ||
21 | import Control.Arrow | ||
22 | import Control.Monad | ||
23 | import Control.Concurrent.STM | ||
24 | import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime) | ||
25 | import Network.Socket | ||
26 | import Data.Hashable | ||
27 | import Data.IP | ||
28 | import Data.Ord | ||
29 | import Data.Maybe | ||
30 | import Data.Bits | ||
31 | import Data.Serialize (Serialize) | ||
32 | import Data.Word | ||
33 | import System.IO | ||
34 | |||
35 | data 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 | |||
41 | newtype PacketKind = PacketKind Word8 | ||
42 | deriving (Eq, Ord, Serialize) | ||
43 | |||
44 | pattern OnionRequest0Type = PacketKind 128 -- 0x80 Onion Request 0 | ||
45 | pattern OnionRequest1Type = PacketKind 129 -- 0x81 Onion Request 1 | ||
46 | pattern OnionRequest2Type = PacketKind 130 -- 0x82 Onion Request 2 | ||
47 | pattern AnnounceType = PacketKind 131 -- 0x83 Announce Request | ||
48 | pattern AnnounceResponseType = PacketKind 132 -- 0x84 Announce Response | ||
49 | |||
50 | pattern DataRequestType = PacketKind 133 -- 0x85 Onion Data Request (data to route request packet) | ||
51 | pattern DataResponseType = PacketKind 134 -- 0x86 Onion Data Response (data to route response packet) | ||
52 | -- 0x8c Onion Response 3 | ||
53 | -- 0x8d Onion Response 2 | ||
54 | pattern OnionResponse3Type = PacketKind 140 -- 0x8c Onion Response 3 | ||
55 | pattern OnionResponse2Type = PacketKind 141 -- 0x8d Onion Response 2 | ||
56 | pattern OnionResponse1Type = PacketKind 142 -- 0x8e Onion Response 1 | ||
57 | -- 0xf0 Bootstrap Info | ||
58 | |||
59 | pattern DHTRequestType = PacketKind 32 -- 0x20 DHT Request | ||
60 | |||
61 | pattern CookieRequestType = PacketKind 0x18 | ||
62 | pattern CookieResponseType = PacketKind 0x19 | ||
63 | |||
64 | pattern PingType = PacketKind 0 -- 0x00 Ping Request | ||
65 | pattern PongType = PacketKind 1 -- 0x01 Ping Response | ||
66 | pattern GetNodesType = PacketKind 2 -- 0x02 Nodes Request | ||
67 | pattern SendNodesType = PacketKind 4 -- 0x04 Nodes Response | ||
68 | |||
69 | |||
70 | instance 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 | |||
88 | classify :: Message -> MessageClass String PacketKind TransactionId | ||
89 | classify 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 | |||
99 | data 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 | |||
109 | newRouting :: 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 | ||
113 | newRouting 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 | ||
138 | isLocal :: IP -> Bool | ||
139 | isLocal (IPv6 ip6) = (ip6 == toEnum 0) | ||
140 | isLocal (IPv4 ip4) = (ip4 == toEnum 0) | ||
141 | |||
142 | isGlobal :: IP -> Bool | ||
143 | isGlobal = not . isLocal | ||
144 | |||
145 | prefer4or6 :: NodeInfo -> Maybe WantIP -> WantIP | ||
146 | prefer4or6 addr iptyp = fromMaybe (ipFamily $ nodeIP addr) iptyp | ||
147 | |||
148 | toxSpace :: R.KademliaSpace NodeId NodeInfo | ||
149 | toxSpace = R.KademliaSpace | ||
150 | { R.kademliaLocation = nodeId | ||
151 | , R.kademliaTestBit = testNodeIdBit | ||
152 | , R.kademliaXor = xorNodeId | ||
153 | , R.kademliaSample = sampleNodeId | ||
154 | } | ||
155 | |||
156 | |||
157 | pingH :: NodeInfo -> Ping -> IO Pong | ||
158 | pingH _ Ping = return Pong | ||
159 | |||
160 | getNodesH :: Routing -> NodeInfo -> GetNodes -> IO SendNodes | ||
161 | getNodesH 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 | |||
183 | type Message = DHTMessage ((,) Nonce8) | ||
184 | |||
185 | type Client = QR.Client String PacketKind TransactionId NodeInfo Message | ||
186 | |||
187 | |||
188 | wrapAssym :: TransactionId -> NodeInfo -> NodeInfo -> (Nonce8 -> dta) -> Assym dta | ||
189 | wrapAssym (TransactionId n8 n24) src dst dta = Assym | ||
190 | { senderKey = id2key $ nodeId src | ||
191 | , assymNonce = n24 | ||
192 | , assymData = dta n8 | ||
193 | } | ||
194 | |||
195 | serializer :: PacketKind | ||
196 | -> (Assym (Nonce8,ping) -> Message) | ||
197 | -> (Message -> Maybe (Assym (Nonce8,pong))) | ||
198 | -> MethodSerializer TransactionId NodeInfo Message PacketKind ping (Maybe pong) | ||
199 | serializer 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 | |||
209 | unpong :: Message -> Maybe (Assym (Nonce8,Pong)) | ||
210 | unpong (DHTPong assym) = Just assym | ||
211 | unpong _ = Nothing | ||
212 | |||
213 | showHex :: BA.ByteArrayAccess ba => ba -> String | ||
214 | showHex bs = C8.unpack $ Base16.encode $ BA.convert bs | ||
215 | |||
216 | ping :: Client -> NodeInfo -> IO Bool | ||
217 | ping 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 | |||
223 | unsendNodes :: Message -> Maybe (Assym (Nonce8,SendNodes)) | ||
224 | unsendNodes (DHTSendNodes assym) = Just assym | ||
225 | unsendNodes _ = Nothing | ||
226 | |||
227 | unwrapNodes :: SendNodes -> ( [NodeInfo], [NodeInfo], () ) | ||
228 | unwrapNodes (SendNodes ns) = (ns,ns,()) | ||
229 | |||
230 | getNodes :: Client -> NodeId -> NodeInfo -> IO (Maybe ([NodeInfo],[NodeInfo],())) | ||
231 | getNodes 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 | |||
237 | updateRouting :: Client -> Routing -> NodeInfo -> Message -> IO () | ||
238 | updateRouting 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 | |||
247 | updateTable :: Client -> NodeInfo -> TVar (R.BucketList NodeInfo) -> TriadCommittee NodeId SockAddr -> TVar (Int.PSQ POSIXTime) -> IO () | ||
248 | updateTable 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 | |||
254 | toxKademlia :: Client -> TriadCommittee NodeId SockAddr -> TVar (R.BucketList NodeInfo) -> TVar (Int.PSQ POSIXTime) -> Kademlia NodeId NodeInfo | ||
255 | toxKademlia 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 | |||
273 | transitionCommittee :: TriadCommittee NodeId SockAddr -> RoutingTransition NodeInfo -> STM (IO ()) | ||
274 | transitionCommittee committee (RoutingTransition ni Stranger) = do | ||
275 | delVote committee (nodeId ni) | ||
276 | return $ do | ||
277 | -- hPutStrLn stderr $ "delVote "++show (nodeId ni) | ||
278 | return () | ||
279 | transitionCommittee committee _ = return $ return () | ||
280 | |||
281 | type Handler = MethodHandler String TransactionId NodeInfo Message | ||
282 | |||
283 | isPing :: (f Ping -> Ping) -> DHTMessage f -> Either String Ping | ||
284 | isPing unpack (DHTPing a) = Right $ unpack $ assymData a | ||
285 | isPing _ _ = Left "Bad ping" | ||
286 | |||
287 | mkPong :: TransactionId -> NodeInfo -> NodeInfo -> Pong -> DHTMessage ((,) Nonce8) | ||
288 | mkPong tid src dst pong = DHTPong $ wrapAssym tid src dst (, pong) | ||
289 | |||
290 | isGetNodes :: (f GetNodes -> GetNodes) -> DHTMessage f -> Either String GetNodes | ||
291 | isGetNodes unpack (DHTGetNodes a) = Right $ unpack $ assymData a | ||
292 | isGetNodes _ _ = Left "Bad GetNodes" | ||
293 | |||
294 | mkSendNodes :: TransactionId -> NodeInfo -> NodeInfo -> SendNodes -> DHTMessage ((,) Nonce8) | ||
295 | mkSendNodes tid src dst sendnodes = DHTSendNodes $ wrapAssym tid src dst (, sendnodes) | ||
296 | |||
297 | handlers :: Routing -> PacketKind -> Maybe Handler | ||
298 | handlers routing PingType = Just $ MethodHandler (isPing snd) mkPong pingH | ||
299 | handlers routing GetNodesType = Just $ MethodHandler (isGetNodes snd) mkSendNodes $ getNodesH routing | ||
300 | |||
301 | nodeSearch :: Client -> Search NodeId (IP,PortNumber) () NodeInfo NodeInfo | ||
302 | nodeSearch client = Search | ||
303 | { searchSpace = toxSpace | ||
304 | , searchNodeAddress = nodeIP &&& nodePort | ||
305 | , searchQuery = getNodes client | ||
306 | } | ||