]> Sergey Matveev's repositories - btrtrc.git/blob - tracker/http.go
Switch to goimports import sorting
[btrtrc.git] / tracker / http.go
1 package tracker
2
3 import (
4         "bytes"
5         "crypto/tls"
6         "fmt"
7         "io"
8         "net"
9         "net/http"
10         "net/url"
11         "strconv"
12         "time"
13
14         "github.com/anacrolix/dht/krpc"
15         "github.com/anacrolix/missinggo/httptoo"
16         "github.com/anacrolix/torrent/bencode"
17 )
18
19 type httpResponse struct {
20         FailureReason string `bencode:"failure reason"`
21         Interval      int32  `bencode:"interval"`
22         TrackerId     string `bencode:"tracker id"`
23         Complete      int32  `bencode:"complete"`
24         Incomplete    int32  `bencode:"incomplete"`
25         Peers         Peers  `bencode:"peers"`
26         // BEP 7
27         Peers6 krpc.CompactIPv6NodeAddrs `bencode:"peers6"`
28 }
29
30 type Peers []Peer
31
32 func (me *Peers) UnmarshalBencode(b []byte) (err error) {
33         var _v interface{}
34         err = bencode.Unmarshal(b, &_v)
35         if err != nil {
36                 return
37         }
38         switch v := _v.(type) {
39         case string:
40                 vars.Add("http responses with string peers", 1)
41                 var cnas krpc.CompactIPv4NodeAddrs
42                 err = cnas.UnmarshalBinary([]byte(v))
43                 if err != nil {
44                         return
45                 }
46                 for _, cp := range cnas {
47                         *me = append(*me, Peer{
48                                 IP:   cp.IP[:],
49                                 Port: int(cp.Port),
50                         })
51                 }
52                 return
53         case []interface{}:
54                 vars.Add("http responses with list peers", 1)
55                 for _, i := range v {
56                         var p Peer
57                         p.fromDictInterface(i.(map[string]interface{}))
58                         *me = append(*me, p)
59                 }
60                 return
61         default:
62                 vars.Add("http responses with unhandled peers type", 1)
63                 err = fmt.Errorf("unsupported type: %T", _v)
64                 return
65         }
66 }
67
68 func setAnnounceParams(_url *url.URL, ar *AnnounceRequest, opts Announce) {
69         q := _url.Query()
70
71         q.Set("info_hash", string(ar.InfoHash[:]))
72         q.Set("peer_id", string(ar.PeerId[:]))
73         // AFAICT, port is mandatory, and there's no implied port key.
74         q.Set("port", fmt.Sprintf("%d", ar.Port))
75         q.Set("uploaded", strconv.FormatInt(ar.Uploaded, 10))
76         q.Set("downloaded", strconv.FormatInt(ar.Downloaded, 10))
77         q.Set("left", strconv.FormatUint(ar.Left, 10))
78         if ar.Event != None {
79                 q.Set("event", ar.Event.String())
80         }
81         // http://stackoverflow.com/questions/17418004/why-does-tracker-server-not-understand-my-request-bittorrent-protocol
82         q.Set("compact", "1")
83         // According to https://wiki.vuze.com/w/Message_Stream_Encryption. TODO:
84         // Take EncryptionPolicy or something like it as a parameter.
85         q.Set("supportcrypto", "1")
86         if opts.ClientIp4.IP != nil {
87                 q.Set("ipv4", opts.ClientIp4.String())
88         }
89         if opts.ClientIp6.IP != nil {
90                 q.Set("ipv6", opts.ClientIp6.String())
91         }
92         _url.RawQuery = q.Encode()
93 }
94
95 func announceHTTP(opt Announce, _url *url.URL) (ret AnnounceResponse, err error) {
96         _url = httptoo.CopyURL(_url)
97         setAnnounceParams(_url, &opt.Request, opt)
98         req, err := http.NewRequest("GET", _url.String(), nil)
99         req.Header.Set("User-Agent", opt.UserAgent)
100         req.Host = opt.HostHeader
101         resp, err := (&http.Client{
102                 Timeout: time.Second * 15,
103                 Transport: &http.Transport{
104                         Dial: (&net.Dialer{
105                                 Timeout: 15 * time.Second,
106                         }).Dial,
107                         Proxy:               opt.HTTPProxy,
108                         TLSHandshakeTimeout: 15 * time.Second,
109                         TLSClientConfig: &tls.Config{
110                                 InsecureSkipVerify: true,
111                                 ServerName:         opt.ServerName,
112                         },
113                 },
114         }).Do(req)
115         if err != nil {
116                 return
117         }
118         defer resp.Body.Close()
119         var buf bytes.Buffer
120         io.Copy(&buf, resp.Body)
121         if resp.StatusCode != 200 {
122                 err = fmt.Errorf("response from tracker: %s: %s", resp.Status, buf.String())
123                 return
124         }
125         var trackerResponse httpResponse
126         err = bencode.Unmarshal(buf.Bytes(), &trackerResponse)
127         if _, ok := err.(bencode.ErrUnusedTrailingBytes); ok {
128                 err = nil
129         } else if err != nil {
130                 err = fmt.Errorf("error decoding %q: %s", buf.Bytes(), err)
131                 return
132         }
133         if trackerResponse.FailureReason != "" {
134                 err = fmt.Errorf("tracker gave failure reason: %q", trackerResponse.FailureReason)
135                 return
136         }
137         vars.Add("successful http announces", 1)
138         ret.Interval = trackerResponse.Interval
139         ret.Leechers = trackerResponse.Incomplete
140         ret.Seeders = trackerResponse.Complete
141         if len(trackerResponse.Peers) != 0 {
142                 vars.Add("http responses with nonempty peers key", 1)
143         }
144         ret.Peers = trackerResponse.Peers
145         if len(trackerResponse.Peers6) != 0 {
146                 vars.Add("http responses with nonempty peers6 key", 1)
147         }
148         for _, na := range trackerResponse.Peers6 {
149                 ret.Peers = append(ret.Peers, Peer{
150                         IP:   na.IP,
151                         Port: na.Port,
152                 })
153         }
154         return
155 }