http.go 4.5 KB

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