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)
27 import Control.Concurrent.MVar (readMVar)
28 import Control.Monad.Reader (ReaderT, runReaderT, ask, liftIO)
29 import Data.Binary (Binary(..), encode, decode)
30 import Data.Binary.Get (Get, isEmpty, getWord32be, getByteString)
31 import Data.Binary.Put (putWord16be, putWord64be, putWord32be, putByteString)
32 import Data.ByteString (ByteString)
33 import qualified Data.ByteString.Char8 as BC
34 import Data.ByteString.Lazy (fromStrict, toStrict)
35 import Data.Word (Word16, Word32, Word64)
36 import Network.Socket (Socket, Family( AF_INET ), SocketType( Datagram ), defaultProtocol, SockAddr(..), socket, close, getAddrInfo, addrAddress, SockAddr(..))
37 import Network.Socket.ByteString (sendTo, recvFrom)
38 import System.Random (randomIO)
40 import FuncTorrent.Tracker.Types (TrackerEventState(..), TState(..))
41 import FuncTorrent.Utils (IP, Port, toIP, toPort, getHostname, getPort)
42 import qualified FuncTorrent.FileSystem as FS (MsgChannel, Stats(..), getStats)
44 -- UDP tracker: http://bittorrent.org/beps/bep_0015.html
50 data UDPRequest = ConnectReq Word32
51 | AnnounceReq Word64 Word32 ByteString String Word64 Word64 Word64 TrackerEventState Word16
52 | ScrapeReq Integer Integer ByteString
55 data UDPResponse = ConnectResp Word32 Word64 -- transaction_id connection_id
56 | AnnounceResp Word32 Word32 Word32 Word32 [(IP, Port)] -- transaction_id interval leechers seeders [(ip, port)]
57 | ScrapeResp Integer Integer Integer Integer
58 | ErrorResp Integer String
61 data UDPTrackerHandle = UDPTrackerHandle { sock :: Socket
66 actionToInteger :: Action -> Integer
67 actionToInteger Connect = 0
68 actionToInteger Announce = 1
69 actionToInteger Scrape = 2
71 intToAction :: Integer -> Action
72 intToAction 0 = Connect
73 intToAction 1 = Announce
74 intToAction 2 = Scrape
76 eventToInteger :: TrackerEventState -> Integer
77 eventToInteger None = 0
78 eventToInteger Completed = 1
79 eventToInteger Started = 2
80 eventToInteger Stopped = 3
82 instance Binary UDPRequest where
83 put (ConnectReq transId) = do
84 putWord64be 0x41727101980
85 putWord32be $ fromIntegral (actionToInteger Connect)
86 putWord32be (fromIntegral transId)
87 put (AnnounceReq connId transId infohash peerId down left up event port) = do
88 putWord64be $ fromIntegral connId
89 putWord32be $ fromIntegral (actionToInteger Announce)
90 putWord32be $ fromIntegral transId
91 putByteString infohash
92 putByteString (BC.pack peerId)
93 putWord64be (fromIntegral down)
94 putWord64be (fromIntegral left)
95 putWord64be (fromIntegral up)
96 putWord32be $ fromIntegral (eventToInteger None)
99 putWord32be $ fromIntegral (-1)
100 putWord16be $ fromIntegral port
101 put (ScrapeReq _ _ _) = undefined
104 instance Binary UDPResponse where
107 a <- getWord32be -- action
109 0 -> liftA2 ConnectResp (fromIntegral <$> getWord32be) (fromIntegral <$> getWord32be)
111 tid <- fromIntegral <$> getWord32be
112 interval' <- fromIntegral <$> getWord32be
113 l <- getWord32be -- leechers
114 s <- getWord32be -- seeders
115 ipportpairs <- getIPPortPairs -- [(ip, port)]
116 return $ AnnounceResp tid interval' l s ipportpairs
118 tid <- fromIntegral <$> getWord32be
122 return $ ScrapeResp tid 0 0 0
123 3 -> do -- error response
124 tid <- fromIntegral <$> getWord32be
125 bs <- getByteString 4
126 return $ ErrorResp tid $ BC.unpack bs
127 _ -> error ("unknown response action type: " ++ show a)
129 sendRequest :: UDPTrackerHandle -> ByteString -> IO ()
130 sendRequest h req = do
131 n <- sendTo (sock h) req (addr h)
132 print $ BC.length req
133 -- sanity check with n?
136 recvResponse :: UDPTrackerHandle -> IO UDPResponse
138 (bs, saddr) <- recvFrom (sock h) (16*1024)
139 return $ decode $ fromStrict bs
141 connectRequest :: ReaderT UDPTrackerHandle IO Word32
144 tidi <- liftIO randomIO
145 let pkt = encode $ ConnectReq tidi
146 liftIO $ sendRequest h (toStrict pkt)
149 connectResponse :: Word32 -> ReaderT UDPTrackerHandle IO Word64
150 connectResponse tid = do
152 resp <- liftIO $ recvResponse h
154 -- check if nbytes is at least 16 bytes long
156 (ConnectResp tidr cid) ->
159 liftIO $ putStrLn "connect succeeded"
165 announceRequest :: Word64 -> ByteString -> String -> Word64 -> Word64 -> Word64 -> Word16 -> ReaderT UDPTrackerHandle IO Word32
166 announceRequest cid infohash peerId up down left port = do
168 tidi <- liftIO randomIO
169 -- connId transId infohash peerId down left up event port)
170 let pkt = encode $ AnnounceReq cid tidi infohash peerId down left up None port
171 liftIO $ sendRequest h (toStrict pkt)
174 data PeerStats = PeerStats { leechers :: Word32
176 , peers :: [(IP, Port)]
179 announceResponse :: Word32 -> ReaderT UDPTrackerHandle IO PeerStats
180 announceResponse tid = do
182 resp <- liftIO $ recvResponse h
184 (AnnounceResp tidr interval ss ls xs) ->
187 liftIO $ putStrLn "announce succeeded"
188 return $ PeerStats ls ss xs
190 return $ PeerStats 0 0 []
191 _ -> return $ PeerStats 0 0 []
193 getIPPortPairs :: Get [(IP, Port)]
199 ip <- toIP <$> getByteString 6
200 port <- toPort <$> getByteString 2
201 ipportpairs <- getIPPortPairs
202 return $ (ip, port) : ipportpairs
204 startSession :: String -> Port -> IO UDPTrackerHandle
205 startSession host port = do
206 s <- socket AF_INET Datagram defaultProtocol
207 addrinfos <- getAddrInfo Nothing (Just host) (Just (show port))
208 let (SockAddrInet p ip) = addrAddress $ head addrinfos
209 putStrLn "connected to tracker"
210 return UDPTrackerHandle { sock = s
211 , addr = (SockAddrInet (fromIntegral port) ip) }
213 closeSession :: UDPTrackerHandle -> IO ()
214 closeSession (UDPTrackerHandle s _ _) = close s
216 trackerLoop :: String -> Port -> String -> ByteString -> FS.MsgChannel -> TState -> IO ()
217 trackerLoop url sport peerId infohash fschan tstate = do
218 st' <- FS.getStats fschan
220 let up = FS.bytesRead st
221 down = FS.bytesWritten st
223 host = getHostname url
224 putStrLn $ "host = " ++ (show host) ++ " port= " ++ (show port)
225 handle <- startSession host port
226 flip runReaderT handle $ do
228 cid <- connectResponse t1
229 liftIO $ print "connect response"
231 t2 <- announceRequest cid infohash peerId (fromIntegral up) (fromIntegral down) (fromIntegral (left tstate)) (fromIntegral sport)
232 liftIO $ print "announce request"
234 stats <- announceResponse t2