2022-12-05 17:50:39 +11:00
|
|
|
package httpTracker
|
2015-03-26 17:20:31 +11:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-06-22 23:28:26 +10:00
|
|
|
"context"
|
|
|
|
"expvar"
|
2015-03-26 17:20:31 +11:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2019-07-17 18:12:11 +10:00
|
|
|
"math"
|
2018-10-18 11:09:56 +11:00
|
|
|
"net"
|
2015-03-26 17:20:31 +11:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
2021-09-12 14:11:59 +10:00
|
|
|
"strings"
|
2015-03-26 17:20:31 +11:00
|
|
|
|
2016-05-20 00:48:46 +10:00
|
|
|
"github.com/anacrolix/missinggo/httptoo"
|
2022-11-15 23:22:10 +11:00
|
|
|
|
2015-04-27 14:55:01 +10:00
|
|
|
"github.com/anacrolix/torrent/bencode"
|
2021-06-22 23:28:26 +10:00
|
|
|
"github.com/anacrolix/torrent/tracker/shared"
|
|
|
|
"github.com/anacrolix/torrent/tracker/udp"
|
2021-06-25 11:29:59 +10:00
|
|
|
"github.com/anacrolix/torrent/version"
|
2015-03-26 17:20:31 +11:00
|
|
|
)
|
|
|
|
|
2021-06-22 23:28:26 +10:00
|
|
|
var vars = expvar.NewMap("tracker/http")
|
|
|
|
|
|
|
|
func setAnnounceParams(_url *url.URL, ar *AnnounceRequest, opts AnnounceOpt) {
|
2021-12-23 09:34:16 +08:00
|
|
|
q := url.Values{}
|
2016-02-14 21:15:51 +01:00
|
|
|
|
2020-10-01 10:45:29 +10:00
|
|
|
q.Set("key", strconv.FormatInt(int64(ar.Key), 10))
|
2015-03-26 17:20:31 +11:00
|
|
|
q.Set("info_hash", string(ar.InfoHash[:]))
|
|
|
|
q.Set("peer_id", string(ar.PeerId[:]))
|
2018-08-13 20:27:42 +10:00
|
|
|
// AFAICT, port is mandatory, and there's no implied port key.
|
2015-03-26 17:20:31 +11:00
|
|
|
q.Set("port", fmt.Sprintf("%d", ar.Port))
|
|
|
|
q.Set("uploaded", strconv.FormatInt(ar.Uploaded, 10))
|
|
|
|
q.Set("downloaded", strconv.FormatInt(ar.Downloaded, 10))
|
2019-07-17 18:12:11 +10:00
|
|
|
|
|
|
|
// 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))
|
|
|
|
|
2021-06-22 23:28:26 +10:00
|
|
|
if ar.Event != shared.None {
|
2015-03-26 17:20:31 +11:00
|
|
|
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")
|
2018-02-13 11:18:23 +11:00
|
|
|
// According to https://wiki.vuze.com/w/Message_Stream_Encryption. TODO:
|
|
|
|
// Take EncryptionPolicy or something like it as a parameter.
|
2015-03-27 17:22:42 +11:00
|
|
|
q.Set("supportcrypto", "1")
|
2020-10-01 10:46:27 +10:00
|
|
|
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)
|
2018-02-19 16:19:18 +11:00
|
|
|
}
|
2021-06-22 23:28:26 +10:00
|
|
|
doIp("ipv4", opts.ClientIp4)
|
|
|
|
doIp("ipv6", opts.ClientIp6)
|
2021-09-12 14:11:59 +10:00
|
|
|
// 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.
|
2021-12-23 09:34:16 +08:00
|
|
|
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
|
|
|
|
}
|
2016-05-20 00:48:46 +10:00
|
|
|
}
|
|
|
|
|
2021-06-22 23:28:26 +10:00
|
|
|
type AnnounceOpt struct {
|
2022-11-28 23:35:36 +00:00
|
|
|
UserAgent string
|
|
|
|
HostHeader string
|
|
|
|
ClientIp4 net.IP
|
|
|
|
ClientIp6 net.IP
|
2022-11-29 10:44:46 +11:00
|
|
|
HttpRequestDirector func(*http.Request) error
|
2021-06-22 23:28:26 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
type AnnounceRequest = udp.AnnounceRequest
|
|
|
|
|
2021-06-24 10:39:56 +10:00
|
|
|
func (cl Client) Announce(ctx context.Context, ar AnnounceRequest, opt AnnounceOpt) (ret AnnounceResponse, err error) {
|
|
|
|
_url := httptoo.CopyURL(cl.url_)
|
2021-06-22 23:28:26 +10:00
|
|
|
setAnnounceParams(_url, &ar, opt)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, _url.String(), nil)
|
2021-06-25 11:29:59 +10:00
|
|
|
userAgent := opt.UserAgent
|
|
|
|
if userAgent == "" {
|
|
|
|
userAgent = version.DefaultHttpUserAgent
|
|
|
|
}
|
|
|
|
if userAgent != "" {
|
|
|
|
req.Header.Set("User-Agent", userAgent)
|
|
|
|
}
|
2022-11-28 23:35:36 +00:00
|
|
|
|
2022-11-29 10:44:46 +11:00
|
|
|
if opt.HttpRequestDirector != nil {
|
|
|
|
err = opt.HttpRequestDirector(req)
|
2022-11-28 23:35:36 +00:00
|
|
|
if err != nil {
|
2022-11-29 10:42:32 +11:00
|
|
|
err = fmt.Errorf("error modifying HTTP request: %w", err)
|
2022-11-28 23:35:36 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-19 16:19:18 +11:00
|
|
|
req.Host = opt.HostHeader
|
2021-06-22 23:28:26 +10:00
|
|
|
resp, err := cl.hc.Do(req)
|
2015-03-26 17:20:31 +11:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
2016-05-20 00:48:46 +10:00
|
|
|
var buf bytes.Buffer
|
2015-03-26 17:20:31 +11:00
|
|
|
io.Copy(&buf, resp.Body)
|
2015-03-27 17:18:45 +11:00
|
|
|
if resp.StatusCode != 200 {
|
2022-03-02 10:49:55 +11:00
|
|
|
err = fmt.Errorf("response from tracker: %s: %q", resp.Status, buf.Bytes())
|
2015-03-27 17:18:45 +11:00
|
|
|
return
|
|
|
|
}
|
2019-11-20 06:31:54 +01:00
|
|
|
var trackerResponse HttpResponse
|
2015-03-27 17:18:45 +11:00
|
|
|
err = bencode.Unmarshal(buf.Bytes(), &trackerResponse)
|
2018-08-13 20:24:15 +10:00
|
|
|
if _, ok := err.(bencode.ErrUnusedTrailingBytes); ok {
|
|
|
|
err = nil
|
|
|
|
} else if err != nil {
|
2015-03-27 17:18:45 +11:00
|
|
|
err = fmt.Errorf("error decoding %q: %s", buf.Bytes(), err)
|
2015-03-26 17:20:31 +11:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if trackerResponse.FailureReason != "" {
|
2018-08-13 20:28:02 +10:00
|
|
|
err = fmt.Errorf("tracker gave failure reason: %q", trackerResponse.FailureReason)
|
2015-03-26 17:20:31 +11:00
|
|
|
return
|
|
|
|
}
|
2018-02-13 00:23:07 +11:00
|
|
|
vars.Add("successful http announces", 1)
|
2015-03-26 17:20:31 +11:00
|
|
|
ret.Interval = trackerResponse.Interval
|
|
|
|
ret.Leechers = trackerResponse.Incomplete
|
|
|
|
ret.Seeders = trackerResponse.Complete
|
2022-12-05 17:53:36 +11:00
|
|
|
if len(trackerResponse.Peers.List) != 0 {
|
2018-02-13 00:23:07 +11:00
|
|
|
vars.Add("http responses with nonempty peers key", 1)
|
|
|
|
}
|
2022-12-05 17:53:36 +11:00
|
|
|
ret.Peers = trackerResponse.Peers.List
|
2018-02-13 00:23:07 +11:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
2015-03-26 17:20:31 +11:00
|
|
|
return
|
|
|
|
}
|
2021-06-22 23:28:26 +10:00
|
|
|
|
|
|
|
type AnnounceResponse struct {
|
|
|
|
Interval int32 // Minimum seconds the local peer should wait before next announce.
|
|
|
|
Leechers int32
|
|
|
|
Seeders int32
|
|
|
|
Peers []Peer
|
|
|
|
}
|