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