]> Sergey Matveev's repositories - btrtrc.git/blob - tracker/udp/udp_tracker.go
Rewrite import paths for migration from Bitbucket
[btrtrc.git] / tracker / udp / udp_tracker.go
1 package udp_tracker
2
3 import (
4         "bytes"
5         "encoding/binary"
6         "errors"
7         "fmt"
8         "io"
9         "math/rand"
10         "net"
11         "net/url"
12         "time"
13
14         "github.com/anacrolix/torrent/tracker"
15 )
16
17 type Action int32
18
19 const (
20         Connect Action = iota
21         Announce
22         Scrape
23         Error
24
25         // BEP 41
26         optionTypeEndOfOptions = 0
27         optionTypeNOP          = 1
28         optionTypeURLData      = 2
29 )
30
31 type ConnectionRequest struct {
32         ConnectionId int64
33         Action       int32
34         TransctionId int32
35 }
36
37 type ConnectionResponse struct {
38         ConnectionId int64
39 }
40
41 type ResponseHeader struct {
42         Action        Action
43         TransactionId int32
44 }
45
46 type RequestHeader struct {
47         ConnectionId  int64
48         Action        Action
49         TransactionId int32
50 } // 16 bytes
51
52 type AnnounceResponseHeader struct {
53         Interval int32
54         Leechers int32
55         Seeders  int32
56 }
57
58 type Peer struct {
59         IP   [4]byte
60         Port uint16
61 }
62
63 func init() {
64         tracker.RegisterClientScheme("udp", newClient)
65 }
66
67 func newClient(url *url.URL) tracker.Client {
68         return &client{
69                 url: url,
70         }
71 }
72
73 func newTransactionId() int32 {
74         return int32(rand.Uint32())
75 }
76
77 func timeout(contiguousTimeouts int) (d time.Duration) {
78         if contiguousTimeouts > 8 {
79                 contiguousTimeouts = 8
80         }
81         d = 15 * time.Second
82         for ; contiguousTimeouts > 0; contiguousTimeouts-- {
83                 d *= 2
84         }
85         return
86 }
87
88 type client struct {
89         contiguousTimeouts   int
90         connectionIdReceived time.Time
91         connectionId         int64
92         socket               net.Conn
93         url                  *url.URL
94 }
95
96 func (c *client) URL() string {
97         return c.url.String()
98 }
99
100 func (c *client) String() string {
101         return c.URL()
102 }
103
104 func (c *client) Announce(req *tracker.AnnounceRequest) (res tracker.AnnounceResponse, err error) {
105         if !c.connected() {
106                 err = tracker.ErrNotConnected
107                 return
108         }
109         reqURI := c.url.RequestURI()
110         // Clearly this limits the request URI to 255 bytes. BEP 41 supports
111         // longer but I'm not fussed.
112         options := append([]byte{optionTypeURLData, byte(len(reqURI))}, []byte(reqURI)...)
113         b, err := c.request(Announce, req, options)
114         if err != nil {
115                 return
116         }
117         var h AnnounceResponseHeader
118         err = readBody(b, &h)
119         if err != nil {
120                 if err == io.EOF {
121                         err = io.ErrUnexpectedEOF
122                 }
123                 err = fmt.Errorf("error parsing announce response: %s", err)
124                 return
125         }
126         res.Interval = h.Interval
127         res.Leechers = h.Leechers
128         res.Seeders = h.Seeders
129         for {
130                 var p Peer
131                 err = binary.Read(b, binary.BigEndian, &p)
132                 switch err {
133                 case nil:
134                 case io.EOF:
135                         err = nil
136                         fallthrough
137                 default:
138                         return
139                 }
140                 res.Peers = append(res.Peers, tracker.Peer{
141                         IP:   p.IP[:],
142                         Port: int(p.Port),
143                 })
144         }
145 }
146
147 // body is the binary serializable request body. trailer is optional data
148 // following it, such as for BEP 41.
149 func (c *client) write(h *RequestHeader, body interface{}, trailer []byte) (err error) {
150         buf := &bytes.Buffer{}
151         err = binary.Write(buf, binary.BigEndian, h)
152         if err != nil {
153                 panic(err)
154         }
155         if body != nil {
156                 err = binary.Write(buf, binary.BigEndian, body)
157                 if err != nil {
158                         panic(err)
159                 }
160         }
161         _, err = buf.Write(trailer)
162         if err != nil {
163                 return
164         }
165         n, err := c.socket.Write(buf.Bytes())
166         if err != nil {
167                 return
168         }
169         if n != buf.Len() {
170                 panic("write should send all or error")
171         }
172         return
173 }
174
175 func read(r io.Reader, data interface{}) error {
176         return binary.Read(r, binary.BigEndian, data)
177 }
178
179 func write(w io.Writer, data interface{}) error {
180         return binary.Write(w, binary.BigEndian, data)
181 }
182
183 // args is the binary serializable request body. trailer is optional data
184 // following it, such as for BEP 41.
185 func (c *client) request(action Action, args interface{}, options []byte) (responseBody *bytes.Reader, err error) {
186         tid := newTransactionId()
187         err = c.write(&RequestHeader{
188                 ConnectionId:  c.connectionId,
189                 Action:        action,
190                 TransactionId: tid,
191         }, args, options)
192         if err != nil {
193                 return
194         }
195         c.socket.SetReadDeadline(time.Now().Add(timeout(c.contiguousTimeouts)))
196         b := make([]byte, 0x10000) // IP limits packet size to 64KB
197         for {
198                 var n int
199                 n, err = c.socket.Read(b)
200                 if opE, ok := err.(*net.OpError); ok {
201                         if opE.Timeout() {
202                                 c.contiguousTimeouts++
203                                 return
204                         }
205                 }
206                 if err != nil {
207                         return
208                 }
209                 buf := bytes.NewBuffer(b[:n])
210                 var h ResponseHeader
211                 err = binary.Read(buf, binary.BigEndian, &h)
212                 switch err {
213                 case io.ErrUnexpectedEOF:
214                         continue
215                 case nil:
216                 default:
217                         return
218                 }
219                 if h.TransactionId != tid {
220                         continue
221                 }
222                 c.contiguousTimeouts = 0
223                 if h.Action == Error {
224                         err = errors.New(buf.String())
225                 }
226                 responseBody = bytes.NewReader(buf.Bytes())
227                 return
228         }
229 }
230
231 func readBody(r *bytes.Reader, data ...interface{}) (err error) {
232         for _, datum := range data {
233                 err = binary.Read(r, binary.BigEndian, datum)
234                 if err != nil {
235                         break
236                 }
237         }
238         return
239 }
240
241 func (c *client) connected() bool {
242         return !c.connectionIdReceived.IsZero() && time.Now().Before(c.connectionIdReceived.Add(time.Minute))
243 }
244
245 func (c *client) Connect() (err error) {
246         if c.connected() {
247                 return nil
248         }
249         c.connectionId = 0x41727101980
250         if c.socket == nil {
251                 c.socket, err = net.Dial("udp", c.url.Host)
252                 if err != nil {
253                         return
254                 }
255         }
256         b, err := c.request(Connect, nil, nil)
257         if err != nil {
258                 return
259         }
260         var res ConnectionResponse
261         err = readBody(b, &res)
262         if err != nil {
263                 return
264         }
265         c.connectionId = res.ConnectionId
266         c.connectionIdReceived = time.Now()
267         return
268 }