]> Sergey Matveev's repositories - btrtrc.git/blob - tracker/http/http.go
Update and expose default client identifiers
[btrtrc.git] / tracker / http / http.go
1 package http
2
3 import (
4         "bytes"
5         "context"
6         "expvar"
7         "fmt"
8         "io"
9         "math"
10         "net"
11         "net/http"
12         "net/url"
13         "strconv"
14
15         "github.com/anacrolix/missinggo/httptoo"
16         "github.com/anacrolix/torrent/bencode"
17         "github.com/anacrolix/torrent/tracker/shared"
18         "github.com/anacrolix/torrent/tracker/udp"
19         "github.com/anacrolix/torrent/version"
20 )
21
22 var vars = expvar.NewMap("tracker/http")
23
24 func setAnnounceParams(_url *url.URL, ar *AnnounceRequest, opts AnnounceOpt) {
25         q := _url.Query()
26
27         q.Set("key", strconv.FormatInt(int64(ar.Key), 10))
28         q.Set("info_hash", string(ar.InfoHash[:]))
29         q.Set("peer_id", string(ar.PeerId[:]))
30         // AFAICT, port is mandatory, and there's no implied port key.
31         q.Set("port", fmt.Sprintf("%d", ar.Port))
32         q.Set("uploaded", strconv.FormatInt(ar.Uploaded, 10))
33         q.Set("downloaded", strconv.FormatInt(ar.Downloaded, 10))
34
35         // The AWS S3 tracker returns "400 Bad Request: left(-1) was not in the valid range 0 -
36         // 9223372036854775807" if left is out of range, or "500 Internal Server Error: Internal Server
37         // Error" if omitted entirely.
38         left := ar.Left
39         if left < 0 {
40                 left = math.MaxInt64
41         }
42         q.Set("left", strconv.FormatInt(left, 10))
43
44         if ar.Event != shared.None {
45                 q.Set("event", ar.Event.String())
46         }
47         // http://stackoverflow.com/questions/17418004/why-does-tracker-server-not-understand-my-request-bittorrent-protocol
48         q.Set("compact", "1")
49         // According to https://wiki.vuze.com/w/Message_Stream_Encryption. TODO:
50         // Take EncryptionPolicy or something like it as a parameter.
51         q.Set("supportcrypto", "1")
52         doIp := func(versionKey string, ip net.IP) {
53                 if ip == nil {
54                         return
55                 }
56                 ipString := ip.String()
57                 q.Set(versionKey, ipString)
58                 // Let's try listing them. BEP 3 mentions having an "ip" param, and BEP 7 says we can list
59                 // addresses for other address-families, although it's not encouraged.
60                 q.Add("ip", ipString)
61         }
62         doIp("ipv4", opts.ClientIp4)
63         doIp("ipv6", opts.ClientIp6)
64         _url.RawQuery = q.Encode()
65 }
66
67 type AnnounceOpt struct {
68         UserAgent  string
69         HostHeader string
70         ClientIp4  net.IP
71         ClientIp6  net.IP
72 }
73
74 type AnnounceRequest = udp.AnnounceRequest
75
76 func (cl Client) Announce(ctx context.Context, ar AnnounceRequest, opt AnnounceOpt) (ret AnnounceResponse, err error) {
77         _url := httptoo.CopyURL(cl.url_)
78         setAnnounceParams(_url, &ar, opt)
79         req, err := http.NewRequestWithContext(ctx, http.MethodGet, _url.String(), nil)
80         userAgent := opt.UserAgent
81         if userAgent == "" {
82                 userAgent = version.DefaultHttpUserAgent
83         }
84         if userAgent != "" {
85                 req.Header.Set("User-Agent", userAgent)
86         }
87         req.Host = opt.HostHeader
88         resp, err := cl.hc.Do(req)
89         if err != nil {
90                 return
91         }
92         defer resp.Body.Close()
93         var buf bytes.Buffer
94         io.Copy(&buf, resp.Body)
95         if resp.StatusCode != 200 {
96                 err = fmt.Errorf("response from tracker: %s: %s", resp.Status, buf.String())
97                 return
98         }
99         var trackerResponse HttpResponse
100         err = bencode.Unmarshal(buf.Bytes(), &trackerResponse)
101         if _, ok := err.(bencode.ErrUnusedTrailingBytes); ok {
102                 err = nil
103         } else if err != nil {
104                 err = fmt.Errorf("error decoding %q: %s", buf.Bytes(), err)
105                 return
106         }
107         if trackerResponse.FailureReason != "" {
108                 err = fmt.Errorf("tracker gave failure reason: %q", trackerResponse.FailureReason)
109                 return
110         }
111         vars.Add("successful http announces", 1)
112         ret.Interval = trackerResponse.Interval
113         ret.Leechers = trackerResponse.Incomplete
114         ret.Seeders = trackerResponse.Complete
115         if len(trackerResponse.Peers) != 0 {
116                 vars.Add("http responses with nonempty peers key", 1)
117         }
118         ret.Peers = trackerResponse.Peers
119         if len(trackerResponse.Peers6) != 0 {
120                 vars.Add("http responses with nonempty peers6 key", 1)
121         }
122         for _, na := range trackerResponse.Peers6 {
123                 ret.Peers = append(ret.Peers, Peer{
124                         IP:   na.IP,
125                         Port: na.Port,
126                 })
127         }
128         return
129 }
130
131 type AnnounceResponse struct {
132         Interval int32 // Minimum seconds the local peer should wait before next announce.
133         Leechers int32
134         Seeders  int32
135         Peers    []Peer
136 }