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