Files
gopeed/protocol/bt/tracker/tracker.go
T
2020-03-01 23:35:51 +08:00

431 lines
11 KiB
Go

package tracker
import (
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
mrand "math/rand"
"net"
"net/http"
"net/url"
"strconv"
"sync"
"time"
"github.com/monkeyWie/gopeed/protocol/bt/metainfo"
"github.com/monkeyWie/gopeed/protocol/bt/peer"
)
const (
udpConnectRequestMagic = 0x41727101980
udpConnectTimeout = 15
udpConnectRetries = 3
udpActionConnect = 0
udpActionAnnounce = 1
udpActionScrape = 2
udpActionError = 3
)
/*
Offset size Name Value
0 64-bit integer protocol_id 0x41727101980 // magic constant
8 32-bit integer action 0 // connect
12 32-bit integer transaction_id
16
Per https://www.libtorrent.org/udp_tracker_protocol.html#connecting
*/
type udpConnectRequest struct {
protocolId uint64
action uint32
transactionId uint32
}
func newUdpConnectRequest() *udpConnectRequest {
return &udpConnectRequest{
protocolId: udpConnectRequestMagic,
action: udpActionConnect,
transactionId: mrand.Uint32(),
}
}
func (req *udpConnectRequest) encode() []byte {
buf := make([]byte, 16)
binary.BigEndian.PutUint64(buf[:8], req.protocolId)
binary.BigEndian.PutUint32(buf[8:12], req.action)
binary.BigEndian.PutUint32(buf[12:], req.transactionId)
return buf
}
/*
Offset size Name Value
0 32-bit integer action 0 // connect
4 32-bit integer transaction_id
8 64-bit integer connection_id
16
*/
type udpConnectResponse struct {
action uint32
transactionId uint32
connectionId uint64
}
func newUdpConnectResponse(buf []byte) *udpConnectResponse {
return &udpConnectResponse{
action: binary.BigEndian.Uint32(buf[:4]),
transactionId: binary.BigEndian.Uint32(buf[4:8]),
connectionId: binary.BigEndian.Uint64(buf[8:]),
}
}
/*
Offset size Name Value
0 64-bit integer connection_id
8 32-bit integer action 1 // announce
12 32-bit integer transaction_id
16 20-byte string info_hash
36 20-byte string peer_id
56 64-bit integer downloaded
64 64-bit integer left
72 64-bit integer uploaded
80 32-bit integer event 0 // 0: none; 1: completed; 2: started; 3: stopped
84 32-bit integer ip address 0 // default
88 32-bit integer key
92 32-bit integer num_want -1 // default
96 16-bit integer port
98
Per https://www.libtorrent.org/udp_tracker_protocol.html#announcing
*/
type udpAnnounceRequest struct {
connectionId uint64
action uint32
transactionId uint32
infoHash [20]byte
peerID [20]byte
downloaded uint64
left uint64
uploaded uint64
event uint32
ip uint32
key uint32
numWant uint32
port uint16
}
func newUdpAnnounceRequest(connectionId uint64) *udpAnnounceRequest {
return &udpAnnounceRequest{
connectionId: connectionId,
action: udpActionAnnounce,
transactionId: mrand.Uint32(),
}
}
func (req *udpAnnounceRequest) encode() []byte {
buf := make([]byte, 98, 98)
binary.BigEndian.PutUint64(buf[:8], req.connectionId)
binary.BigEndian.PutUint32(buf[8:12], req.action)
binary.BigEndian.PutUint32(buf[12:16], req.transactionId)
copy(buf[16:36], req.infoHash[:])
copy(buf[36:56], req.peerID[:])
binary.BigEndian.PutUint64(buf[56:64], req.downloaded)
binary.BigEndian.PutUint64(buf[64:72], req.left)
binary.BigEndian.PutUint64(buf[72:80], req.uploaded)
binary.BigEndian.PutUint32(buf[80:84], req.event)
binary.BigEndian.PutUint32(buf[84:88], req.ip)
binary.BigEndian.PutUint32(buf[88:92], req.key)
binary.BigEndian.PutUint32(buf[92:96], req.numWant)
binary.BigEndian.PutUint16(buf[96:98], req.port)
return buf
}
/*
Offset size Name Value
0 32-bit integer action 1 // announce
4 32-bit integer transaction_id
8 32-bit integer interval
12 32-bit integer leechers
16 32-bit integer seeders
20 + 6 * n 32-bit integer ip address
24 + 6 * n 16-bit integer TCP port
20 + 6 * N
Per https://www.libtorrent.org/udp_tracker_protocol.html#announcing
*/
type udpAnnounceResponse struct {
action uint32
transactionId uint32
interval uint32
leechers uint32
seeders uint32
peers []peer.Peer
}
func newUdpAnnounceResponse(buf []byte) *udpAnnounceResponse {
response := &udpAnnounceResponse{
action: binary.BigEndian.Uint32(buf[:4]),
transactionId: binary.BigEndian.Uint32(buf[4:8]),
interval: binary.BigEndian.Uint32(buf[8:12]),
leechers: binary.BigEndian.Uint32(buf[12:16]),
seeders: binary.BigEndian.Uint32(buf[16:20]),
}
count := (len(buf) - 20) / 6
response.peers = make([]peer.Peer, count, count)
for i := 0; i < count; i++ {
ipBegin := 20 + 6*i
portBegin := ipBegin + 4
response.peers[i] = peer.Peer{
IP: binary.BigEndian.Uint32(buf[ipBegin:portBegin]),
Port: binary.BigEndian.Uint16(buf[portBegin : portBegin+2]),
}
}
return response
}
type Tracker struct {
PeerID [20]byte
MetaInfo *metainfo.MetaInfo
}
func (tracker *Tracker) connect(conn *net.UDPConn, timeout int64) (response *udpConnectResponse, err error) {
request := newUdpConnectRequest()
buf := request.encode()
conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout)))
conn.Write(buf)
len, err := conn.Read(buf)
if err != nil {
return
}
if len != 16 {
err = NewTrackerError(ErrResponse, errors.New("invalid response"))
return
}
response = newUdpConnectResponse(buf)
return
}
func (tracker *Tracker) announce(conn *net.UDPConn, timeout int64, connectionId uint64) (response *udpAnnounceResponse, err error) {
request := newUdpAnnounceRequest(connectionId)
request.infoHash = tracker.MetaInfo.GetInfoHash()
request.peerID = tracker.PeerID
request.downloaded = 0
request.uploaded = 0
request.left = tracker.MetaInfo.GetTotalSize() - request.downloaded
request.event = 0
request.ip = 0
request.key = 0
request.numWant = 200
request.port = 6882
encode := request.encode()
conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout)))
conn.Write(encode)
buf := make([]byte, 20+request.numWant*6)
len, err := conn.Read(buf)
if err != nil {
return
}
if len < 20 {
err = errors.New("announce fail")
return
}
response = newUdpAnnounceResponse(buf[:len])
return
}
// 通过tracker服务器获取peer信息
func (tracker *Tracker) Tracker() <-chan []peer.Peer {
peersCh := make(chan []peer.Peer)
metaInfo := tracker.MetaInfo
checkAnnounceList := func(announceList [][]string) bool {
if len(announceList) > 0 {
for _, l := range announceList {
if len(l) > 0 {
return true
}
}
}
return false
}
// 只要获取到了peers就直接返回
if checkAnnounceList(metaInfo.AnnounceList) {
wg := sync.WaitGroup{}
for _, announceArr := range metaInfo.AnnounceList {
if len(announceArr) > 0 {
for _, announce := range announceArr {
wg.Add(1)
go func(announce string) {
peers, err := tracker.DoTracker(announce)
if err == nil {
peersCh <- peers
}
wg.Done()
}(announce)
}
}
}
go func() {
wg.Wait()
close(peersCh)
}()
} else {
go func() {
peers, err := tracker.DoTracker(metaInfo.Announce)
if err == nil {
peersCh <- peers
}
close(peersCh)
}()
}
return peersCh
}
func (tracker *Tracker) DoTracker(announce string) (peers []peer.Peer, err error) {
if announce != "" {
url, _ := url.Parse(announce)
switch url.Scheme {
case "http", "https":
return tracker.httpTracker(url)
case "udp", "udp4", "udp6":
return tracker.udpTracker(url)
default:
return nil, errors.New("unsupported protocol")
}
}
return nil, errors.New("empty announce")
}
// http://bittorrent.org/beps/bep_0003.html#trackers
func (tracker *Tracker) httpTracker(url *url.URL) (peers []peer.Peer, err error) {
metaInfo := tracker.MetaInfo
peerID := tracker.PeerID
query := url.Query()
infoHash := metaInfo.GetInfoHash()
query.Add("info_hash", string(infoHash[:]))
query.Add("peer_id", string(peerID[:]))
query.Add("port", "6882")
query.Add("uploaded", "0")
query.Add("downloaded", "0")
query.Add("left", strconv.FormatInt(int64(metaInfo.GetTotalSize()), 10))
url.RawQuery = query.Encode()
request, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
return
}
var httpClient = &http.Client{
Timeout: time.Second * time.Duration(15),
}
request.Header.Set("User-Agent", "gopeed/0.0.1")
response, err := httpClient.Do(request)
if err != nil {
return
}
defer response.Body.Close()
resp, err := ioutil.ReadAll(response.Body)
if err != nil {
if err != io.EOF {
return
}
}
// TODO Parse HTTP tracker response
fmt.Println(resp)
return nil, nil
}
// http://bittorrent.org/beps/bep_0015.html
func (tracker *Tracker) udpTracker(url *url.URL) (peers []peer.Peer, err error) {
conn, err := dial(url)
if err != nil {
return
}
defer conn.Close()
announceResponse, err := func() (announceResponse *udpAnnounceResponse, err error) {
var connectResponse *udpConnectResponse
// 0:connect 1:announce
state := udpActionConnect
// 访问超时最多重试8次,当有请求成功则将重试次数重置为0
for n := 0; n < udpConnectRetries; n++ {
// 15 * 2 ^ n seconds
timeout := int64(udpConnectTimeout * math.Pow(2, float64(n)))
switch state {
case udpActionConnect:
connectResponse, err = tracker.connect(conn, timeout)
if err != nil {
if oe, ok := err.(*net.OpError); ok && oe.Timeout() {
break
} else {
return
}
}
n = -1
state = udpActionAnnounce
break
case udpActionAnnounce:
announceResponse, err = tracker.announce(conn, timeout, connectResponse.connectionId)
if err != nil {
if oe, ok := err.(*net.OpError); ok && oe.Timeout() {
break
} else {
return
}
}
return
}
}
return
}()
if err != nil {
return
}
return announceResponse.peers, nil
}
func dial(trackerUrl *url.URL) (*net.UDPConn, error) {
host, portStr, err := net.SplitHostPort(trackerUrl.Host)
if err != nil {
return nil, err
}
port, err := strconv.ParseInt(portStr, 10, 32)
if err != nil {
return nil, err
}
ip, err := lookupIP(host, trackerUrl.Scheme)
if err != nil {
return nil, err
}
srcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
dstAddr := &net.UDPAddr{
IP: ip,
Port: int(port),
}
return net.DialUDP(trackerUrl.Scheme, srcAddr, dstAddr)
}
func lookupIP(host string, scheme string) (ip net.IP, err error) {
ips, _ := net.LookupIP(host)
if len(ips) == 0 {
err = errors.New("no ips")
return
}
for _, ip = range ips {
switch scheme {
case "udp", "udp4":
if ip.To4() != nil {
return
}
case "udp6":
if ip.To4() == nil {
return
}
}
}
err = errors.New("no available ips ")
return
}