forked from jackpal/Taipei-Torrent
/
trackerClient.go
148 lines (131 loc) · 3.78 KB
/
trackerClient.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package main
import (
"fmt"
"log"
"math/rand"
"net"
"net/url"
"strconv"
)
// Code to talk to trackers.
// Implements BEP 12 Multitracker Metadata Extension
type ClientStatusReport struct {
Event string
InfoHash string
PeerId string
Port int
Uploaded int64
Downloaded int64
Left int64
}
func startTrackerClient(announce string, announceList [][]string, trackerInfoChan chan *TrackerResponse, reports chan ClientStatusReport) {
if announce != "" && announceList == nil {
// Convert the plain announce into an announceList to simplify logic
announceList = [][]string{[]string{announce}}
}
if announceList != nil {
announceList = shuffleAnnounceList(announceList)
}
go func() {
for report := range reports {
tr := queryTrackers(announceList, report)
if tr != nil {
trackerInfoChan <- tr
}
}
}()
}
// Deep copy announcelist and shuffle each level.
func shuffleAnnounceList(announceList [][]string) (result [][]string) {
result = make([][]string, len(announceList))
for i, level := range announceList {
result[i] = shuffleAnnounceListLevel(level)
}
return
}
func shuffleAnnounceListLevel(level []string) (shuffled []string) {
items := len(level)
shuffled = make([]string, items)
perm := rand.Perm(items)
for i, v := range perm {
shuffled[v] = level[i]
}
return
}
func queryTrackers(announceList [][]string, report ClientStatusReport) (tr *TrackerResponse) {
for _, level := range announceList {
for i, tracker := range level {
var err error
tr, err = queryTracker(report, tracker)
if err == nil {
// Move successful tracker to front of slice for next announcement
// cycle.
copy(level[1:i+1], level[0:i])
level[0] = tracker
return
}
}
}
log.Println("Error: Did not successfully contact a tracker:", announceList)
return
}
func queryTracker(report ClientStatusReport, trackerUrl string) (tr *TrackerResponse, err error) {
u, err := url.Parse(trackerUrl)
if err != nil {
log.Println("Error: Invalid announce URL(", trackerUrl, "):", err)
return
}
uq := u.Query()
uq.Add("info_hash", report.InfoHash)
uq.Add("peer_id", report.PeerId)
uq.Add("port", strconv.Itoa(report.Port))
uq.Add("uploaded", strconv.FormatInt(report.Uploaded, 10))
uq.Add("downloaded", strconv.FormatInt(report.Downloaded, 10))
uq.Add("left", strconv.FormatInt(report.Left, 10))
uq.Add("compact", "1")
// Don't report IPv6 address, the user might prefer to keep
// that information private when communicating with IPv4 hosts.
if false {
ipv6Address, err := findLocalIPV6AddressFor(u.Host)
if err == nil {
log.Println("our ipv6", ipv6Address)
uq.Add("ipv6", ipv6Address)
}
}
if report.Event != "" {
uq.Add("event", report.Event)
}
// This might reorder the existing query string in the Announce url
// This might break some broken trackers that don't parse URLs properly.
u.RawQuery = uq.Encode()
tr, err = getTrackerInfo(u.String())
if tr == nil || err != nil {
log.Println("Error: Could not fetch tracker info:", err)
} else if tr.FailureReason != "" {
log.Println("Error: Tracker returned failure reason:", tr.FailureReason)
err = fmt.Errorf("tracker failure %s", tr.FailureReason)
}
return
}
func findLocalIPV6AddressFor(hostAddr string) (local string, err error) {
// Figure out our IPv6 address to talk to a given host.
host, hostPort, err := net.SplitHostPort(hostAddr)
if err != nil {
host = hostAddr
hostPort = "1234"
}
dummyAddr := net.JoinHostPort(host, hostPort)
log.Println("Looking for host ", dummyAddr)
conn, err := net.Dial("udp6", dummyAddr)
if err != nil {
log.Println("No IPV6 for host ", host, err)
return "", err
}
defer conn.Close()
localAddr := conn.LocalAddr()
local, _, err = net.SplitHostPort(localAddr.String())
if err != nil {
local = localAddr.String()
}
return
}