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