github.com/anacrolix/torrent@v1.61.0/tracker/http/http.go (about) 1 package httpTracker 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 "strings" 15 16 "github.com/anacrolix/missinggo/httptoo" 17 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" 22 ) 23 24 var vars = expvar.NewMap("tracker/http") 25 26 func setAnnounceParams(_url *url.URL, ar *AnnounceRequest, opts AnnounceOpt) { 27 q := url.Values{} 28 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)) 36 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. 40 left := ar.Left 41 if left < 0 { 42 left = math.MaxInt64 43 } 44 q.Set("left", strconv.FormatInt(left, 10)) 45 46 if ar.Event != shared.None { 47 q.Set("event", ar.Event.String()) 48 } 49 // http://stackoverflow.com/questions/17418004/why-does-tracker-server-not-understand-my-request-bittorrent-protocol 50 q.Set("compact", "1") 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) { 55 if ip == nil { 56 return 57 } 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. 62 q.Add("ip", ipString) 63 } 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") 69 70 // Some private trackers require the original query param to be in the first position. 71 if _url.RawQuery != "" { 72 _url.RawQuery += "&" + qstr 73 } else { 74 _url.RawQuery = qstr 75 } 76 } 77 78 type AnnounceOpt struct { 79 UserAgent string 80 HostHeader string 81 ClientIp4 net.IP 82 ClientIp6 net.IP 83 HttpRequestDirector func(*http.Request) error 84 } 85 86 type AnnounceRequest = udp.AnnounceRequest 87 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 93 if userAgent == "" { 94 userAgent = version.DefaultHttpUserAgent 95 } 96 if userAgent != "" { 97 req.Header.Set("User-Agent", userAgent) 98 } 99 100 if opt.HttpRequestDirector != nil { 101 err = opt.HttpRequestDirector(req) 102 if err != nil { 103 err = fmt.Errorf("error modifying HTTP request: %w", err) 104 return 105 } 106 } 107 108 req.Host = opt.HostHeader 109 resp, err := cl.hc.Do(req) 110 if err != nil { 111 return 112 } 113 defer resp.Body.Close() 114 var buf bytes.Buffer 115 io.Copy(&buf, resp.Body) 116 if resp.StatusCode != 200 { 117 err = fmt.Errorf("response from tracker: %s: %q", resp.Status, buf.Bytes()) 118 return 119 } 120 var trackerResponse HttpResponse 121 err = bencode.Unmarshal(buf.Bytes(), &trackerResponse) 122 if _, ok := err.(bencode.ErrUnusedTrailingBytes); ok { 123 err = nil 124 } else if err != nil { 125 err = fmt.Errorf("error decoding %q: %s", buf.Bytes(), err) 126 return 127 } 128 if trackerResponse.FailureReason != "" { 129 err = fmt.Errorf("tracker gave failure reason: %q", trackerResponse.FailureReason) 130 return 131 } 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) 138 } 139 ret.Peers = trackerResponse.Peers.List 140 if len(trackerResponse.Peers6) != 0 { 141 vars.Add("http responses with nonempty peers6 key", 1) 142 } 143 for _, na := range trackerResponse.Peers6 { 144 ret.Peers = append(ret.Peers, Peer{ 145 IP: na.IP, 146 Port: na.Port, 147 }) 148 } 149 return 150 } 151 152 type AnnounceResponse struct { 153 Interval int32 // Minimum seconds the local peer should wait before next announce. 154 Leechers int32 155 Seeders int32 156 Peers []Peer 157 }