| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- 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
- }
|