16 "github.com/anacrolix/missinggo/httptoo"
18 "github.com/anacrolix/torrent/bencode"
19 "github.com/anacrolix/torrent/tracker/shared"
20 "github.com/anacrolix/torrent/tracker/udp"
21 "github.com/anacrolix/torrent/version"
24 var vars = expvar.NewMap("tracker/http")
26 func setAnnounceParams(_url *url.URL, ar *AnnounceRequest, opts AnnounceOpt) {
29 q.Set("key", strconv.FormatInt(int64(ar.Key), 10))
30 q.Set("info_hash", string(ar.InfoHash[:]))
31 q.Set("peer_id", string(ar.PeerId[:]))
32 // AFAICT, port is mandatory, and there's no implied port key.
33 q.Set("port", fmt.Sprintf("%d", ar.Port))
34 q.Set("uploaded", strconv.FormatInt(ar.Uploaded, 10))
35 q.Set("downloaded", strconv.FormatInt(ar.Downloaded, 10))
37 // The AWS S3 tracker returns "400 Bad Request: left(-1) was not in the valid range 0 -
38 // 9223372036854775807" if left is out of range, or "500 Internal Server Error: Internal Server
39 // Error" if omitted entirely.
44 q.Set("left", strconv.FormatInt(left, 10))
46 if ar.Event != shared.None {
47 q.Set("event", ar.Event.String())
49 // http://stackoverflow.com/questions/17418004/why-does-tracker-server-not-understand-my-request-bittorrent-protocol
51 // According to https://wiki.vuze.com/w/Message_Stream_Encryption. TODO:
52 // Take EncryptionPolicy or something like it as a parameter.
53 q.Set("supportcrypto", "1")
54 doIp := func(versionKey string, ip net.IP) {
58 ipString := ip.String()
59 q.Set(versionKey, ipString)
60 // Let's try listing them. BEP 3 mentions having an "ip" param, and BEP 7 says we can list
61 // addresses for other address-families, although it's not encouraged.
64 doIp("ipv4", opts.ClientIp4)
65 doIp("ipv6", opts.ClientIp6)
66 // We're operating purely on query-escaped strings, where + would have already been encoded to
67 // %2B, and + has no other special meaning. See https://github.com/anacrolix/torrent/issues/534.
68 qstr := strings.ReplaceAll(q.Encode(), "+", "%20")
70 // Some private trackers require the original query param to be in the first position.
71 if _url.RawQuery != "" {
72 _url.RawQuery += "&" + qstr
78 type AnnounceOpt struct {
83 HttpRequestDirector func(*http.Request) error
86 type AnnounceRequest = udp.AnnounceRequest
88 func (cl Client) Announce(ctx context.Context, ar AnnounceRequest, opt AnnounceOpt) (ret AnnounceResponse, err error) {
89 _url := httptoo.CopyURL(cl.url_)
90 setAnnounceParams(_url, &ar, opt)
91 req, err := http.NewRequestWithContext(ctx, http.MethodGet, _url.String(), nil)
92 userAgent := opt.UserAgent
94 userAgent = version.DefaultHttpUserAgent
97 req.Header.Set("User-Agent", userAgent)
100 if opt.HttpRequestDirector != nil {
101 err = opt.HttpRequestDirector(req)
103 err = fmt.Errorf("error modifying HTTP request: %w", err)
108 req.Host = opt.HostHeader
109 resp, err := cl.hc.Do(req)
113 defer resp.Body.Close()
115 io.Copy(&buf, resp.Body)
116 if resp.StatusCode != 200 {
117 err = fmt.Errorf("response from tracker: %s: %q", resp.Status, buf.Bytes())
120 var trackerResponse HttpResponse
121 err = bencode.Unmarshal(buf.Bytes(), &trackerResponse)
122 if _, ok := err.(bencode.ErrUnusedTrailingBytes); ok {
124 } else if err != nil {
125 err = fmt.Errorf("error decoding %q: %s", buf.Bytes(), err)
128 if trackerResponse.FailureReason != "" {
129 err = fmt.Errorf("tracker gave failure reason: %q", trackerResponse.FailureReason)
132 vars.Add("successful http announces", 1)
133 ret.Interval = trackerResponse.Interval
134 ret.Leechers = trackerResponse.Incomplete
135 ret.Seeders = trackerResponse.Complete
136 if len(trackerResponse.Peers.List) != 0 {
137 vars.Add("http responses with nonempty peers key", 1)
139 ret.Peers = trackerResponse.Peers.List
140 if len(trackerResponse.Peers6) != 0 {
141 vars.Add("http responses with nonempty peers6 key", 1)
143 for _, na := range trackerResponse.Peers6 {
144 ret.Peers = append(ret.Peers, Peer{
152 type AnnounceResponse struct {
153 Interval int32 // Minimum seconds the local peer should wait before next announce.