2 - Copyright (C) 2015-2016 Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
4 - This file is part of FuncTorrent.
6 - FuncTorrent is free software; you can redistribute it and/or modify
7 - it under the terms of the GNU General Public License as published by
8 - the Free Software Foundation; either version 3 of the License, or
9 - (at your option) any later version.
11 - FuncTorrent is distributed in the hope that it will be useful,
12 - but WITHOUT ANY WARRANTY; without even the implied warranty of
13 - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 - GNU General Public License for more details.
16 - You should have received a copy of the GNU General Public License
17 - along with FuncTorrent; if not, see <http://www.gnu.org/licenses/>
20 {-# LANGUAGE OverloadedStrings #-}
21 module FuncTorrent.Tracker.Udp
25 import Control.Applicative (liftA2)
26 import Control.Monad (liftM, forever, void)
27 import Control.Concurrent (threadDelay)
28 import Control.Concurrent.MVar (readMVar, putMVar, isEmptyMVar, swapMVar)
29 import Control.Monad.Reader (ReaderT, runReaderT, ask, liftIO)
30 import Data.Binary (Binary(..), encode, decode)
31 import Data.Binary.Get (Get, isEmpty, getWord32be, getWord64be, getByteString)
32 import Data.Binary.Put (putWord16be, putWord64be, putWord32be, putByteString)
33 import Data.ByteString (ByteString)
34 import qualified Data.ByteString.Char8 as BC
35 import Data.ByteString.Lazy (fromStrict, toStrict)
36 import Data.Word (Word16, Word32, Word64)
37 import Network.Socket (Socket, Family( AF_INET ), SocketType( Datagram ), defaultProtocol, SockAddr(..), socket, close, getAddrInfo, addrAddress, SockAddr(..))
38 import Network.Socket.ByteString (sendTo, recvFrom)
39 import System.Random (randomIO)
40 import System.Timeout (timeout)
42 import FuncTorrent.PeerMsgs (Peer(..))
43 import FuncTorrent.Tracker.Types (TrackerEventState(..), TState(..), UdpTrackerResponse(..))
44 import FuncTorrent.Utils (IP, Port, toIP, toPort, getHostname, getPort)
45 import qualified FuncTorrent.FileSystem as FS (MsgChannel, Stats(..), getStats)
47 -- UDP tracker: http://bittorrent.org/beps/bep_0015.html
53 data UDPRequest = ConnectReq Word32
54 | AnnounceReq Word64 Word32 ByteString String Word64 Word64 Word64 TrackerEventState Word16
55 | ScrapeReq Integer Integer ByteString
58 data UDPResponse = ConnectResp Word32 Word64 -- transaction_id connection_id
59 | AnnounceResp Word32 Word32 Word32 Word32 [Peer] -- transaction_id interval leechers seeders [(ip, port)]
60 | ScrapeResp Integer Integer Integer Integer
61 | ErrorResp Integer String
64 data UDPTrackerHandle = UDPTrackerHandle { sock :: Socket
69 actionToInteger :: Action -> Integer
70 actionToInteger Connect = 0
71 actionToInteger Announce = 1
72 actionToInteger Scrape = 2
74 intToAction :: Integer -> Action
75 intToAction 0 = Connect
76 intToAction 1 = Announce
77 intToAction 2 = Scrape
79 eventToInteger :: TrackerEventState -> Integer
80 eventToInteger None = 0
81 eventToInteger Completed = 1
82 eventToInteger Started = 2
83 eventToInteger Stopped = 3
85 instance Binary UDPRequest where
86 put (ConnectReq transId) = do
87 putWord64be 0x41727101980
88 putWord32be $ fromIntegral (actionToInteger Connect)
89 putWord32be (fromIntegral transId)
90 put (AnnounceReq connId transId infohash peerId down left up event port) = do
91 putWord64be $ fromIntegral connId
92 putWord32be $ fromIntegral (actionToInteger Announce)
93 putWord32be $ fromIntegral transId
94 putByteString infohash
95 putByteString (BC.pack peerId)
96 putWord64be (fromIntegral down)
97 putWord64be (fromIntegral left)
98 putWord64be (fromIntegral up)
99 putWord32be $ fromIntegral (eventToInteger event)
103 putWord16be $ fromIntegral port
104 put (ScrapeReq _ _ _) = undefined
107 instance Binary UDPResponse where
110 a <- getWord32be -- action
112 0 -> liftA2 ConnectResp (fromIntegral <$> getWord32be) (fromIntegral <$> getWord64be)
114 tid <- fromIntegral <$> getWord32be
115 interval' <- fromIntegral <$> getWord32be
116 l <- getWord32be -- leechers
117 s <- getWord32be -- seeders
118 ipportpairs <- getIPPortPairs -- [(ip, port)]
119 return $ AnnounceResp tid interval' l s ipportpairs
121 tid <- fromIntegral <$> getWord32be
125 return $ ScrapeResp tid 0 0 0
126 3 -> do -- error response
127 tid <- fromIntegral <$> getWord32be
128 bs <- getByteString 4
129 return $ ErrorResp tid $ BC.unpack bs
130 _ -> error ("unknown response action type: " ++ show a)
132 sendRequest :: UDPTrackerHandle -> ByteString -> IO ()
133 sendRequest h req = do
134 n <- sendTo (sock h) req (addr h)
135 -- sanity check with n?
138 recvResponse :: UDPTrackerHandle -> IO UDPResponse
140 (bs, saddr) <- recvFrom (sock h) (16*1024)
141 return $ decode $ fromStrict bs
143 connectRequest :: ReaderT UDPTrackerHandle IO Word32
146 tidi <- liftIO randomIO
147 let pkt = encode $ ConnectReq tidi
148 liftIO $ sendRequest h (toStrict pkt)
151 connectResponse :: Word32 -> ReaderT UDPTrackerHandle IO Word64
152 connectResponse tid = do
154 resp <- liftIO $ recvResponse h
155 -- check if nbytes is at least 16 bytes long
157 (ConnectResp tidr cid) ->
160 liftIO $ putStrLn "connect succeeded"
166 announceRequest :: Word64 -> ByteString -> String -> Word64 -> Word64 -> Word64 -> Word16 -> ReaderT UDPTrackerHandle IO Word32
167 announceRequest cid infohash peerId up down left port = do
169 tidi <- liftIO randomIO
170 let pkt = encode $ AnnounceReq cid tidi infohash peerId down left up None port
171 liftIO $ sendRequest h (toStrict pkt)
174 announceResponse :: Word32 -> ReaderT UDPTrackerHandle IO UdpTrackerResponse
175 announceResponse tid = do
177 resp <- liftIO $ recvResponse h
179 (AnnounceResp tidr interval ss ls xs) ->
182 liftIO $ putStrLn "announce succeeded"
183 return $ UdpTrackerResponse ls ss interval xs
185 return $ UdpTrackerResponse 0 0 0 []
186 _ -> return $ UdpTrackerResponse 0 0 0 []
188 getIPPortPairs :: Get [Peer]
194 ip <- toIP <$> getByteString 4
195 port <- toPort <$> getByteString 2
196 ipportpairs <- getIPPortPairs
197 return $ (Peer ip port) : ipportpairs
199 startSession :: String -> Port -> IO UDPTrackerHandle
200 startSession host port = do
201 s <- socket AF_INET Datagram defaultProtocol
202 addrinfos <- getAddrInfo Nothing (Just host) (Just (show port))
203 let (SockAddrInet p ip) = addrAddress $ head addrinfos
204 putStrLn "connected to tracker"
205 return UDPTrackerHandle { sock = s
206 , addr = (SockAddrInet (fromIntegral port) ip) }
208 closeSession :: UDPTrackerHandle -> IO ()
209 closeSession (UDPTrackerHandle s _ _) = close s
211 trackerLoop :: String -> Port -> String -> ByteString -> FS.MsgChannel -> TState -> IO ()
212 trackerLoop url sport peerId infohash fschan tstate = forever $ do
213 st <- readMVar <$> FS.getStats fschan
214 up <- fmap FS.bytesRead st
215 down <- fmap FS.bytesWritten st
216 handle <- startSession host port
217 stats <- timeout (15*(10^6)) $ worker handle up down
219 Nothing -> closeSession handle
221 ps <- isEmptyMVar $ connectedPeers tstate
224 putMVar (connectedPeers tstate) (peers stats')
226 void $ swapMVar (connectedPeers tstate) (peers stats')
227 threadDelay $ fromIntegral (interval stats') * (10^6)
231 host = getHostname url
232 worker handle up down = flip runReaderT handle $ do
234 cid <- connectResponse t1
235 t2 <- announceRequest cid infohash peerId (fromIntegral up) (fromIntegral down) (fromIntegral (left tstate)) (fromIntegral sport)
236 stats <- announceResponse t2