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