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