package httpTracker import ( "bytes" "context" "expvar" "fmt" "io" "math" "net" "net/http" "net/url" "strconv" "strings" "github.com/anacrolix/missinggo/httptoo" "github.com/anacrolix/torrent/bencode" "github.com/anacrolix/torrent/tracker/shared" "github.com/anacrolix/torrent/tracker/udp" "github.com/anacrolix/torrent/version" ) var vars = expvar.NewMap("tracker/http") func setAnnounceParams(_url *url.URL, ar *AnnounceRequest, opts AnnounceOpt) { q := url.Values{} q.Set("key", strconv.FormatInt(int64(ar.Key), 10)) q.Set("info_hash", string(ar.InfoHash[:])) q.Set("peer_id", string(ar.PeerId[:])) // AFAICT, port is mandatory, and there's no implied port key. q.Set("port", fmt.Sprintf("%d", ar.Port)) q.Set("uploaded", strconv.FormatInt(ar.Uploaded, 10)) q.Set("downloaded", strconv.FormatInt(ar.Downloaded, 10)) // The AWS S3 tracker returns "400 Bad Request: left(-1) was not in the valid range 0 - // 9223372036854775807" if left is out of range, or "500 Internal Server Error: Internal Server // Error" if omitted entirely. left := ar.Left if left < 0 { left = math.MaxInt64 } q.Set("left", strconv.FormatInt(left, 10)) if ar.Event != shared.None { q.Set("event", ar.Event.String()) } // http://stackoverflow.com/questions/17418004/why-does-tracker-server-not-understand-my-request-bittorrent-protocol q.Set("compact", "1") // According to https://wiki.vuze.com/w/Message_Stream_Encryption. TODO: // Take EncryptionPolicy or something like it as a parameter. q.Set("supportcrypto", "1") doIp := func(versionKey string, ip net.IP) { if ip == nil { return } ipString := ip.String() q.Set(versionKey, ipString) // Let's try listing them. BEP 3 mentions having an "ip" param, and BEP 7 says we can list // addresses for other address-families, although it's not encouraged. q.Add("ip", ipString) } doIp("ipv4", opts.ClientIp4) doIp("ipv6", opts.ClientIp6) // We're operating purely on query-escaped strings, where + would have already been encoded to // %2B, and + has no other special meaning. See https://github.com/anacrolix/torrent/issues/534. qstr := strings.ReplaceAll(q.Encode(), "+", "%20") // Some private trackers require the original query param to be in the first position. if _url.RawQuery != "" { _url.RawQuery += "&" + qstr } else { _url.RawQuery = qstr } } type AnnounceOpt struct { UserAgent string HostHeader string ClientIp4 net.IP ClientIp6 net.IP HttpRequestDirector func(*http.Request) error } type AnnounceRequest = udp.AnnounceRequest func (cl Client) Announce(ctx context.Context, ar AnnounceRequest, opt AnnounceOpt) (ret AnnounceResponse, err error) { _url := httptoo.CopyURL(cl.url_) setAnnounceParams(_url, &ar, opt) req, err := http.NewRequestWithContext(ctx, http.MethodGet, _url.String(), nil) userAgent := opt.UserAgent if userAgent == "" { userAgent = version.DefaultHttpUserAgent } if userAgent != "" { req.Header.Set("User-Agent", userAgent) } if opt.HttpRequestDirector != nil { err = opt.HttpRequestDirector(req) if err != nil { err = fmt.Errorf("error modifying HTTP request: %w", err) return } } req.Host = opt.HostHeader resp, err := cl.hc.Do(req) if err != nil { return } defer resp.Body.Close() var buf bytes.Buffer io.Copy(&buf, resp.Body) if resp.StatusCode != 200 { err = fmt.Errorf("response from tracker: %s: %q", resp.Status, buf.Bytes()) return } var trackerResponse HttpResponse err = bencode.Unmarshal(buf.Bytes(), &trackerResponse) if _, ok := err.(bencode.ErrUnusedTrailingBytes); ok { err = nil } else if err != nil { err = fmt.Errorf("error decoding %q: %s", buf.Bytes(), err) return } if trackerResponse.FailureReason != "" { err = fmt.Errorf("tracker gave failure reason: %q", trackerResponse.FailureReason) return } vars.Add("successful http announces", 1) ret.Interval = trackerResponse.Interval ret.Leechers = trackerResponse.Incomplete ret.Seeders = trackerResponse.Complete if len(trackerResponse.Peers.List) != 0 { vars.Add("http responses with nonempty peers key", 1) } ret.Peers = trackerResponse.Peers.List if len(trackerResponse.Peers6) != 0 { vars.Add("http responses with nonempty peers6 key", 1) } for _, na := range trackerResponse.Peers6 { ret.Peers = append(ret.Peers, Peer{ IP: na.IP, Port: na.Port, }) } return } type AnnounceResponse struct { Interval int32 // Minimum seconds the local peer should wait before next announce. Leechers int32 Seeders int32 Peers []Peer }