]> Sergey Matveev's repositories - btrtrc.git/blob - tracker_scraper.go
Merge branch 'webtorrent'
[btrtrc.git] / tracker_scraper.go
1 package torrent
2
3 import (
4         "bytes"
5         "errors"
6         "fmt"
7         "net"
8         "net/url"
9         "time"
10
11         "github.com/anacrolix/dht/v2/krpc"
12
13         "github.com/anacrolix/torrent/tracker"
14 )
15
16 // Announces a torrent to a tracker at regular intervals, when peers are
17 // required.
18 type trackerScraper struct {
19         u            url.URL
20         t            *Torrent
21         lastAnnounce trackerAnnounceResult
22 }
23
24 type torrentTrackerAnnouncer interface {
25         statusLine() string
26         URL() url.URL
27 }
28
29 func (me trackerScraper) URL() url.URL {
30         return me.u
31 }
32
33 func (ts *trackerScraper) statusLine() string {
34         var w bytes.Buffer
35         fmt.Fprintf(&w, "%q\t%s\t%s",
36                 ts.u.String(),
37                 func() string {
38                         na := time.Until(ts.lastAnnounce.Completed.Add(ts.lastAnnounce.Interval))
39                         if na > 0 {
40                                 na /= time.Second
41                                 na *= time.Second
42                                 return na.String()
43                         } else {
44                                 return "anytime"
45                         }
46                 }(),
47                 func() string {
48                         if ts.lastAnnounce.Err != nil {
49                                 return ts.lastAnnounce.Err.Error()
50                         }
51                         if ts.lastAnnounce.Completed.IsZero() {
52                                 return "never"
53                         }
54                         return fmt.Sprintf("%d peers", ts.lastAnnounce.NumPeers)
55                 }(),
56         )
57         return w.String()
58 }
59
60 type trackerAnnounceResult struct {
61         Err       error
62         NumPeers  int
63         Interval  time.Duration
64         Completed time.Time
65 }
66
67 func (me *trackerScraper) getIp() (ip net.IP, err error) {
68         ips, err := net.LookupIP(me.u.Hostname())
69         if err != nil {
70                 return
71         }
72         if len(ips) == 0 {
73                 err = errors.New("no ips")
74                 return
75         }
76         for _, ip = range ips {
77                 if me.t.cl.ipIsBlocked(ip) {
78                         continue
79                 }
80                 switch me.u.Scheme {
81                 case "udp4":
82                         if ip.To4() == nil {
83                                 continue
84                         }
85                 case "udp6":
86                         if ip.To4() != nil {
87                                 continue
88                         }
89                 }
90                 return
91         }
92         err = errors.New("no acceptable ips")
93         return
94 }
95
96 func (me *trackerScraper) trackerUrl(ip net.IP) string {
97         u := me.u
98         if u.Port() != "" {
99                 u.Host = net.JoinHostPort(ip.String(), u.Port())
100         }
101         return u.String()
102 }
103
104 // Return how long to wait before trying again. For most errors, we return 5
105 // minutes, a relatively quick turn around for DNS changes.
106 func (me *trackerScraper) announce(event tracker.AnnounceEvent) (ret trackerAnnounceResult) {
107         defer func() {
108                 ret.Completed = time.Now()
109         }()
110         ret.Interval = time.Minute
111         ip, err := me.getIp()
112         if err != nil {
113                 ret.Err = fmt.Errorf("error getting ip: %s", err)
114                 return
115         }
116         me.t.cl.rLock()
117         req := me.t.announceRequest(event)
118         me.t.cl.rUnlock()
119         //log.Printf("announcing %s %s to %q", me.t, req.Event, me.u.String())
120         res, err := tracker.Announce{
121                 HTTPProxy:  me.t.cl.config.HTTPProxy,
122                 UserAgent:  me.t.cl.config.HTTPUserAgent,
123                 TrackerUrl: me.trackerUrl(ip),
124                 Request:    req,
125                 HostHeader: me.u.Host,
126                 ServerName: me.u.Hostname(),
127                 UdpNetwork: me.u.Scheme,
128                 ClientIp4:  krpc.NodeAddr{IP: me.t.cl.config.PublicIp4},
129                 ClientIp6:  krpc.NodeAddr{IP: me.t.cl.config.PublicIp6},
130         }.Do()
131         if err != nil {
132                 ret.Err = fmt.Errorf("error announcing: %s", err)
133                 return
134         }
135         me.t.AddPeers(Peers(nil).AppendFromTracker(res.Peers))
136         ret.NumPeers = len(res.Peers)
137         ret.Interval = time.Duration(res.Interval) * time.Second
138         return
139 }
140
141 func (me *trackerScraper) Run() {
142         defer me.announceStopped()
143         // make sure first announce is a "started"
144         e := tracker.Started
145         for {
146                 ar := me.announce(e)
147                 // after first announce, get back to regular "none"
148                 e = tracker.None
149                 me.t.cl.lock()
150                 me.lastAnnounce = ar
151                 me.t.cl.unlock()
152
153         wait:
154                 // Make sure we don't announce for at least a minute since the last one.
155                 interval := ar.Interval
156                 if interval < time.Minute {
157                         interval = time.Minute
158                 }
159
160                 me.t.cl.lock()
161                 wantPeers := me.t.wantPeersEvent.C()
162                 closed := me.t.closed.C()
163                 me.t.cl.unlock()
164
165                 // If we want peers, reduce the interval to the minimum.
166                 select {
167                 case <-wantPeers:
168                         if interval > time.Minute {
169                                 interval = time.Minute
170                         }
171                         // Now we're at the minimum, don't trigger on it anymore.
172                         wantPeers = nil
173                 default:
174                 }
175
176                 select {
177                 case <-closed:
178                         return
179                 case <-wantPeers:
180                         // Recalculate the interval.
181                         goto wait
182                 case <-time.After(time.Until(ar.Completed.Add(interval))):
183                 }
184         }
185 }
186
187 func (me *trackerScraper) announceStopped() {
188         me.announce(tracker.Stopped)
189 }