mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-04 08:10:55 +00:00
MSE support
This commit is contained in:
1 parent
03fbdcda60
commit
dbc58a209d
11 files changed
+309
-343
No files matched your search
@@ -1 +0,0 @@
|
||||
package bt
|
||||
@@ -0,0 +1,55 @@
|
||||
package bt
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
PeerID [20]byte
|
||||
|
||||
torrents []Torrent
|
||||
}
|
||||
|
||||
func NewClient() *Client {
|
||||
client := &Client{}
|
||||
|
||||
// 生成Peer ID,规则为前三位固定字母(-GP)+SemVer(xyz)+End(-),后面随机生成
|
||||
// 参考:https://wiki.theory.org/index.php/BitTorrentSpecification#peer_id
|
||||
peerID := [20]byte{'-', 'G', 'P', '0', '0', '1', '-'}
|
||||
_, err := rand.Read(peerID[7:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
client.PeerID = peerID
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func (client *Client) AddTorrent(rawurl string) error {
|
||||
parse, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var metaInfo *MetaInfo
|
||||
switch parse.Scheme {
|
||||
case "magnet":
|
||||
// TODO 磁力链接解析
|
||||
default:
|
||||
metaInfo, err = ParseFromFile(rawurl)
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
torrent := Torrent{
|
||||
client: client,
|
||||
MetaInfo: metaInfo,
|
||||
}
|
||||
client.torrents = append(client.torrents, torrent)
|
||||
|
||||
torrent.Download()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package bt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_AddTorrent(t *testing.T) {
|
||||
client := NewClient()
|
||||
client.AddTorrent("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent")
|
||||
}
|
||||
+25
-25
@@ -11,24 +11,18 @@ type MetaInfo struct {
|
||||
// tracker URL
|
||||
// http://bittorrent.org/beps/bep_0003.html#metainfo-files
|
||||
Announce string `json:"announce"`
|
||||
|
||||
// tracker URL
|
||||
// http://bittorrent.org/beps/bep_0012.html
|
||||
AnnounceList [][]string `json:"announce-list"`
|
||||
|
||||
Comment string `json:"comment"`
|
||||
|
||||
CreatedBy string `json:"created by"`
|
||||
|
||||
CreationDate int64 `json:"creation date"`
|
||||
|
||||
Encoding string `json:"encoding"`
|
||||
|
||||
Comment string `json:"comment"`
|
||||
CreatedBy string `json:"created by"`
|
||||
CreationDate int64 `json:"creation date"`
|
||||
Encoding string `json:"encoding"`
|
||||
// http://bittorrent.org/beps/bep_0019.html
|
||||
UrlList []string `json:"url-list"`
|
||||
Info *Info `json:"info"`
|
||||
|
||||
Info *Info `json:"info"`
|
||||
InfoHash [20]byte `json:"-"`
|
||||
ExtraInfo `json:"-"`
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
@@ -44,19 +38,9 @@ type File struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func (metaInfo *MetaInfo) GetTotalSize() uint64 {
|
||||
if metaInfo.Info != nil {
|
||||
if len(metaInfo.Info.Files) > 0 {
|
||||
var length uint64
|
||||
for _, f := range metaInfo.Info.Files {
|
||||
length += f.Length
|
||||
}
|
||||
return length
|
||||
} else {
|
||||
return metaInfo.Info.Length
|
||||
}
|
||||
}
|
||||
return 0
|
||||
type ExtraInfo struct {
|
||||
InfoHash [20]byte
|
||||
FileSize uint64
|
||||
}
|
||||
|
||||
func ParseFromFile(path string) (*MetaInfo, error) {
|
||||
@@ -91,5 +75,21 @@ func ParseFromFile(path string) (*MetaInfo, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
metaInfo.FileSize = getTotalSize(&metaInfo)
|
||||
return &metaInfo, nil
|
||||
}
|
||||
|
||||
func getTotalSize(metaInfo *MetaInfo) uint64 {
|
||||
if metaInfo.Info != nil {
|
||||
if len(metaInfo.Info.Files) > 0 {
|
||||
var length uint64
|
||||
for _, f := range metaInfo.Info.Files {
|
||||
length += f.Length
|
||||
}
|
||||
return length
|
||||
} else {
|
||||
return metaInfo.Info.Length
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
+28
-8
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/cenkalti/mse"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
@@ -30,6 +31,9 @@ const (
|
||||
type Peer struct {
|
||||
IP uint32
|
||||
Port uint16
|
||||
|
||||
Torrent *Torrent
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (peer *Peer) Address() string {
|
||||
@@ -82,16 +86,29 @@ func (handshake *Handshake) decode(buf []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (peer *Peer) DoDownload(metaInfo *MetaInfo, peerId [20]byte) error {
|
||||
func dialMse(address string, infoHash [20]byte) (net.Conn, error) {
|
||||
conn, err := net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mseConn := mse.WrapConn(conn)
|
||||
_, err = mseConn.HandshakeOutgoing(infoHash[:], mse.PlainText, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mseConn, nil
|
||||
}
|
||||
|
||||
func (peer *Peer) DoDownload() error {
|
||||
if peer.Port == 0 {
|
||||
return fmt.Errorf("error port %d", peer.Port)
|
||||
}
|
||||
conn, err := net.Dial("tcp", peer.Address())
|
||||
conn, err := dialMse(peer.Address(), peer.Torrent.MetaInfo.InfoHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = doHandshake(peer, metaInfo, peerId, conn)
|
||||
err = peer.doHandshake(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -204,12 +221,15 @@ func (peer *Peer) DoDownload(metaInfo *MetaInfo, peerId [20]byte) error {
|
||||
|
||||
// Handshake of Peer wire protocol
|
||||
// Per https://wiki.theory.org/index.php/BitTorrentSpecification#Handshake
|
||||
func doHandshake(peer *Peer, metaInfo *MetaInfo, peerId [20]byte, conn net.Conn) error {
|
||||
func (peer *Peer) doHandshake(conn net.Conn) error {
|
||||
metaInfo := peer.Torrent.MetaInfo
|
||||
peerID := peer.Torrent.client.PeerID
|
||||
|
||||
reserved := [8]byte{}
|
||||
reserved[5] = 0x10
|
||||
reserved[6] = 0x0
|
||||
reserved[7] = 0x5
|
||||
handshakeReq := NewHandshake(reserved, metaInfo.InfoHash, peerId)
|
||||
/* reserved[5] = 0x10
|
||||
reserved[6] = 0x0
|
||||
reserved[7] = 0x5*/
|
||||
handshakeReq := NewHandshake(reserved, metaInfo.InfoHash, peerID)
|
||||
buf, err := handshakeReq.encode()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
package bt
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDoDownload(t *testing.T) {
|
||||
metaInfo, err := ParseFromFile("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
peers, err := metaInfo.Tracker()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
testPeer := peers[0]
|
||||
fmt.Println(testPeer.Address())
|
||||
|
||||
peerId, err := GenPeerID()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
IP uint32
|
||||
Port uint16
|
||||
}
|
||||
type args struct {
|
||||
metaInfo *MetaInfo
|
||||
peerId [20]byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"GOTS08E05",
|
||||
fields{
|
||||
testPeer.IP,
|
||||
testPeer.Port,
|
||||
},
|
||||
args{
|
||||
metaInfo,
|
||||
peerId,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
peer := &Peer{
|
||||
IP: tt.fields.IP,
|
||||
Port: tt.fields.Port,
|
||||
}
|
||||
if err := peer.DoDownload(tt.args.metaInfo, tt.args.peerId); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Peer.DoDownload() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindPeer(t *testing.T) {
|
||||
metaInfo, err := ParseFromFile("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
peers, err := metaInfo.Tracker()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// peerId, err := GenPeerID()
|
||||
peerId, err := [20]byte{0x2d, 0x55, 0x54, 0x33, 0x35, 0x35, 0x53, 0x2d, 0xaf, 0xb0, 0xec, 0xdc, 0xbf, 0xd1, 0x02, 0x93, 0xd6, 0xf4, 0x9b, 0x24}, nil
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ch := make(chan Peer)
|
||||
for i, peer := range peers {
|
||||
go func(i int, peer Peer) {
|
||||
fmt.Println(peer.Address())
|
||||
err := peer.DoDownload(metaInfo, peerId)
|
||||
if err == nil {
|
||||
ch <- peer
|
||||
} else {
|
||||
fmt.Printf("%d:%v\n", i, err)
|
||||
}
|
||||
}(i, peer)
|
||||
}
|
||||
peer := <-ch
|
||||
fmt.Println(peer.Address())
|
||||
}
|
||||
|
||||
func TestDoDownloadSpecialPeer(t *testing.T) {
|
||||
metaInfo, err := ParseFromFile("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 101.188.129.156:14475
|
||||
testPeer := Peer{
|
||||
binary.BigEndian.Uint32([]byte{101, 188, 129, 156}),
|
||||
14475,
|
||||
}
|
||||
fmt.Println(testPeer.Address())
|
||||
|
||||
peerId, err := GenPeerID()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
IP uint32
|
||||
Port uint16
|
||||
}
|
||||
type args struct {
|
||||
metaInfo *MetaInfo
|
||||
peerId [20]byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"GOTS08E05",
|
||||
fields{
|
||||
testPeer.IP,
|
||||
testPeer.Port,
|
||||
},
|
||||
args{
|
||||
metaInfo,
|
||||
peerId,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
peer := &Peer{
|
||||
IP: tt.fields.IP,
|
||||
Port: tt.fields.Port,
|
||||
}
|
||||
if err := peer.DoDownload(tt.args.metaInfo, tt.args.peerId); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Peer.DoDownload() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package bt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Torrent struct {
|
||||
client *Client
|
||||
|
||||
MetaInfo *MetaInfo
|
||||
Peers []Peer
|
||||
}
|
||||
|
||||
func (torrent *Torrent) Download() {
|
||||
t := NewTracker(torrent)
|
||||
peers, err := t.Tracker()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
peers[0].Torrent = torrent
|
||||
peers[0].DoDownload()
|
||||
}
|
||||
+153
-151
@@ -1,7 +1,6 @@
|
||||
package bt
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -26,6 +25,14 @@ const (
|
||||
udpActionError = 3
|
||||
)
|
||||
|
||||
type Tracker struct {
|
||||
torrent *Torrent
|
||||
}
|
||||
|
||||
func NewTracker(torrent *Torrent) *Tracker {
|
||||
return &Tracker{torrent: torrent}
|
||||
}
|
||||
|
||||
/*
|
||||
Offset Size Name Value
|
||||
0 64-bit integer protocol_id 0x41727101980 // magic constant
|
||||
@@ -34,25 +41,25 @@ const (
|
||||
16
|
||||
Per https://www.libtorrent.org/udp_tracker_protocol.html#connecting
|
||||
*/
|
||||
type UdpConnectRequest struct {
|
||||
ProtocolId uint64
|
||||
Action uint32
|
||||
TransactionId uint32
|
||||
type udpConnectRequest struct {
|
||||
protocolId uint64
|
||||
action uint32
|
||||
transactionId uint32
|
||||
}
|
||||
|
||||
func NewUdpConnectRequest() *UdpConnectRequest {
|
||||
return &UdpConnectRequest{
|
||||
ProtocolId: udpConnectRequestMagic,
|
||||
Action: udpActionConnect,
|
||||
TransactionId: mrand.Uint32(),
|
||||
func newUdpConnectRequest() *udpConnectRequest {
|
||||
return &udpConnectRequest{
|
||||
protocolId: udpConnectRequestMagic,
|
||||
action: udpActionConnect,
|
||||
transactionId: mrand.Uint32(),
|
||||
}
|
||||
}
|
||||
|
||||
func (req *UdpConnectRequest) Encode() []byte {
|
||||
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)
|
||||
binary.BigEndian.PutUint64(buf[:8], req.protocolId)
|
||||
binary.BigEndian.PutUint32(buf[8:12], req.action)
|
||||
binary.BigEndian.PutUint32(buf[12:], req.transactionId)
|
||||
return buf
|
||||
}
|
||||
|
||||
@@ -63,17 +70,17 @@ func (req *UdpConnectRequest) Encode() []byte {
|
||||
8 64-bit integer connection_id
|
||||
16
|
||||
*/
|
||||
type UdpConnectResponse struct {
|
||||
Action uint32
|
||||
TransactionId uint32
|
||||
ConnectionId uint64
|
||||
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:]),
|
||||
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:]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,52 +95,52 @@ func NewUdpConnectResponse(buf []byte) *UdpConnectResponse {
|
||||
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
|
||||
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 int32
|
||||
Port uint16
|
||||
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 int32
|
||||
port uint16
|
||||
}
|
||||
|
||||
func NewUdpAnnounceRequest(connectionId uint64) *UdpAnnounceRequest {
|
||||
return &UdpAnnounceRequest{
|
||||
ConnectionId: connectionId,
|
||||
Action: udpActionAnnounce,
|
||||
TransactionId: mrand.Uint32(),
|
||||
func newUdpAnnounceRequest(connectionId uint64) *udpAnnounceRequest {
|
||||
return &udpAnnounceRequest{
|
||||
connectionId: connectionId,
|
||||
action: udpActionAnnounce,
|
||||
transactionId: mrand.Uint32(),
|
||||
}
|
||||
}
|
||||
|
||||
func (req *UdpAnnounceRequest) encode() []byte {
|
||||
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], uint32(req.NumWant))
|
||||
binary.BigEndian.PutUint16(buf[96:98], req.Port)
|
||||
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], uint32(req.numWant))
|
||||
binary.BigEndian.PutUint16(buf[96:98], req.port)
|
||||
return buf
|
||||
}
|
||||
|
||||
@@ -144,34 +151,34 @@ func (req *UdpAnnounceRequest) encode() []byte {
|
||||
8 32-bit integer interval
|
||||
12 32-bit integer leechers
|
||||
16 32-bit integer seeders
|
||||
20 + 6 * n 32-bit integer IP address
|
||||
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
|
||||
type udpAnnounceResponse struct {
|
||||
action uint32
|
||||
transactionId uint32
|
||||
interval uint32
|
||||
leechers uint32
|
||||
seeders uint32
|
||||
peers []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]),
|
||||
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, count, count)
|
||||
response.peers = make([]Peer, count, count)
|
||||
for i := 0; i < count; i++ {
|
||||
ipBegin := 20 + 6*i
|
||||
portBegin := ipBegin + 4
|
||||
response.Peers[i] = Peer{
|
||||
response.peers[i] = Peer{
|
||||
IP: binary.BigEndian.Uint32(buf[ipBegin:portBegin]),
|
||||
Port: binary.BigEndian.Uint16(buf[portBegin : portBegin+2]),
|
||||
}
|
||||
@@ -179,33 +186,8 @@ func NewUdpAnnounceResponse(buf []byte) *UdpAnnounceResponse {
|
||||
return response
|
||||
}
|
||||
|
||||
func dial(trackerUrl *url.URL) (*net.UDPConn, error) {
|
||||
host, portStr, err := net.SplitHostPort(trackerUrl.Host)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
port, err := strconv.ParseInt(portStr, 10, 32)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
ip, err := lookupIP(host, trackerUrl.Scheme)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
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 connect(conn *net.UDPConn, timeout int64) (response *UdpConnectResponse, err error) {
|
||||
request := NewUdpConnectRequest()
|
||||
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)
|
||||
@@ -217,27 +199,24 @@ func connect(conn *net.UDPConn, timeout int64) (response *UdpConnectResponse, er
|
||||
err = NewTrackerError(ErrResponse, errors.New("invalid response"))
|
||||
return
|
||||
}
|
||||
response = NewUdpConnectResponse(buf)
|
||||
response = newUdpConnectResponse(buf)
|
||||
return
|
||||
}
|
||||
|
||||
func announce(conn *net.UDPConn, timeout int64, connectionId uint64, metaInfo *MetaInfo) (response *UdpAnnounceResponse, err error) {
|
||||
request := NewUdpAnnounceRequest(connectionId)
|
||||
request.InfoHash = metaInfo.InfoHash
|
||||
peerID, err := GenPeerID()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
request.PeerID = peerID
|
||||
request.Downloaded = 0
|
||||
request.Uploaded = 0
|
||||
request.Left = metaInfo.GetTotalSize() - request.Downloaded
|
||||
request.Event = 0
|
||||
request.IP = 0
|
||||
request.Key = 0
|
||||
request.NumWant = 50
|
||||
request.Port = 6882
|
||||
func (tracker *Tracker) announce(conn *net.UDPConn, timeout int64, connectionId uint64) (response *udpAnnounceResponse, err error) {
|
||||
torrent := tracker.torrent
|
||||
|
||||
request := newUdpAnnounceRequest(connectionId)
|
||||
request.infoHash = torrent.MetaInfo.InfoHash
|
||||
request.peerID = torrent.client.PeerID
|
||||
request.downloaded = 0
|
||||
request.uploaded = 0
|
||||
request.left = torrent.MetaInfo.FileSize - request.downloaded
|
||||
request.event = 0
|
||||
request.ip = 0
|
||||
request.key = 0
|
||||
request.numWant = -1
|
||||
request.port = 6882
|
||||
encode := request.encode()
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout)))
|
||||
conn.Write(encode)
|
||||
@@ -247,16 +226,29 @@ func announce(conn *net.UDPConn, timeout int64, connectionId uint64, metaInfo *M
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
response = NewUdpAnnounceResponse(buf[:len])
|
||||
response = newUdpAnnounceResponse(buf[:len])
|
||||
return
|
||||
}
|
||||
|
||||
func (metaInfo *MetaInfo) Tracker() (peers []Peer, err error) {
|
||||
if len(metaInfo.AnnounceList) > 0 {
|
||||
func (tracker *Tracker) Tracker() (peers []Peer, err error) {
|
||||
metaInfo := tracker.torrent.MetaInfo
|
||||
|
||||
checkAnnounceList := func(announceList [][]string) bool {
|
||||
if len(announceList) > 0 {
|
||||
for _, l := range announceList {
|
||||
if len(l) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if checkAnnounceList(metaInfo.AnnounceList) {
|
||||
for _, announceArr := range metaInfo.AnnounceList {
|
||||
if len(announceArr) > 0 {
|
||||
for _, announce := range announceArr {
|
||||
peers, err = doTracker(announce, metaInfo)
|
||||
peers, err = tracker.doTracker(announce)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
@@ -264,7 +256,7 @@ func (metaInfo *MetaInfo) Tracker() (peers []Peer, err error) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
peers, err = doTracker(metaInfo.Announce, metaInfo)
|
||||
peers, err = tracker.doTracker(metaInfo.Announce)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
@@ -272,16 +264,16 @@ func (metaInfo *MetaInfo) Tracker() (peers []Peer, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func doTracker(announce string, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
func (tracker *Tracker) doTracker(announce string) (peers []Peer, err error) {
|
||||
if announce != "" {
|
||||
url, _ := url.Parse(announce)
|
||||
switch url.Scheme {
|
||||
case "http", "https":
|
||||
// TODO UDP test
|
||||
break
|
||||
return httpTracker(url, metaInfo)
|
||||
return tracker.httpTracker(url)
|
||||
case "udp", "udp4", "udp6":
|
||||
return udpTracker(url, metaInfo)
|
||||
return tracker.udpTracker(url)
|
||||
default:
|
||||
return nil, errors.New("unsupported protocol")
|
||||
}
|
||||
@@ -289,31 +281,18 @@ func doTracker(announce string, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
return nil, errors.New("empty announce")
|
||||
}
|
||||
|
||||
// 生成Peer ID,规则为前三位固定字母(-GP)+SemVer(xyz),后面随机生成
|
||||
// 参考:https://wiki.theory.org/index.php/BitTorrentSpecification#peer_id
|
||||
func GenPeerID() ([20]byte, error) {
|
||||
peerId := [20]byte{'-', 'G', 'P', '0', '0', '1'}
|
||||
_, err := rand.Read(peerId[6:])
|
||||
if err != nil {
|
||||
return peerId, err
|
||||
}
|
||||
return peerId, nil
|
||||
}
|
||||
|
||||
// http://bittorrent.org/beps/bep_0003.html#trackers
|
||||
func httpTracker(url *url.URL, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
func (tracker *Tracker) httpTracker(url *url.URL) (peers []Peer, err error) {
|
||||
metaInfo := tracker.torrent.MetaInfo
|
||||
peerID := tracker.torrent.client.PeerID
|
||||
|
||||
query := url.Query()
|
||||
query.Add("info_hash", string(metaInfo.InfoHash[:]))
|
||||
// Generate peer ID
|
||||
peerID, err := GenPeerID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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))
|
||||
query.Add("left", strconv.FormatInt(int64(metaInfo.FileSize), 10))
|
||||
url.RawQuery = query.Encode()
|
||||
request, err := http.NewRequest("GET", url.String(), nil)
|
||||
if err != nil {
|
||||
@@ -340,14 +319,15 @@ func httpTracker(url *url.URL, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
}
|
||||
|
||||
// http://bittorrent.org/beps/bep_0015.html
|
||||
func udpTracker(url *url.URL, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
func (tracker *Tracker) udpTracker(url *url.URL) (peers []Peer, err error) {
|
||||
conn, err := dial(url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
announceResponse, err := func() (announceResponse *UdpAnnounceResponse, err error) {
|
||||
var connectResponse *UdpConnectResponse
|
||||
announceResponse, err := func() (announceResponse *udpAnnounceResponse, err error) {
|
||||
var connectResponse *udpConnectResponse
|
||||
// 0:connect 1:announce
|
||||
state := 0
|
||||
// 访问超时最多重试8次,当有请求成功则将重试次数重置为0
|
||||
@@ -356,7 +336,7 @@ func udpTracker(url *url.URL, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
timeout := int64(udpConnectTimeout * math.Pow(2, float64(n)))
|
||||
switch state {
|
||||
case 0:
|
||||
connectResponse, err = connect(conn, timeout)
|
||||
connectResponse, err = tracker.connect(conn, timeout)
|
||||
if err != nil {
|
||||
if oe, ok := err.(*net.OpError); ok && oe.Timeout() {
|
||||
break
|
||||
@@ -368,7 +348,7 @@ func udpTracker(url *url.URL, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
state = 1
|
||||
break
|
||||
case 1:
|
||||
announceResponse, err = announce(conn, timeout, connectResponse.ConnectionId, metaInfo)
|
||||
announceResponse, err = tracker.announce(conn, timeout, connectResponse.connectionId)
|
||||
if err != nil {
|
||||
if oe, ok := err.(*net.OpError); ok && oe.Timeout() {
|
||||
break
|
||||
@@ -386,7 +366,29 @@ func udpTracker(url *url.URL, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
return announceResponse.Peers, nil
|
||||
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) {
|
||||
|
||||
@@ -122,6 +122,14 @@ func TestTemp(t *testing.T) {
|
||||
s = time.Now().UnixNano()
|
||||
f.Write(bts)
|
||||
fmt.Printf("write from 128M+9b use %d\n", time.Now().UnixNano()-s)
|
||||
s = time.Now().UnixNano()
|
||||
f.Seek(0, 0)
|
||||
f.Write(bts)
|
||||
fmt.Printf("write from 0 agein use %d\n", time.Now().UnixNano()-s)
|
||||
s = time.Now().UnixNano()
|
||||
f.Seek(1024*1024*64, 0)
|
||||
f.Write(bts)
|
||||
fmt.Printf("write from agein 64M use %d\n", time.Now().UnixNano()-s)
|
||||
}
|
||||
|
||||
func fileMd5(filePath string) string {
|
||||
|
||||
@@ -2,4 +2,7 @@ module gopeed
|
||||
|
||||
go 1.12
|
||||
|
||||
require github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e
|
||||
require (
|
||||
github.com/cenkalti/mse v0.0.0-20140930130441-6ef65f170972
|
||||
github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e
|
||||
)
|
||||
@@ -1,2 +1,4 @@
|
||||
github.com/cenkalti/mse v0.0.0-20140930130441-6ef65f170972 h1:FvAasVrPm/VUO8M0odLDqA9Y7VbALYq3PRAwmWGPw78=
|
||||
github.com/cenkalti/mse v0.0.0-20140930130441-6ef65f170972/go.mod h1:wGf99xKTJ8f/a8cC1inMqFz9cGEzISLLid5F6i5fI/g=
|
||||
github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e h1:KMs6SK8iDSR1+ZzOK10L5wGPpWDByyvOe5nrqk51g2U=
|
||||
github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e/go.mod h1:+AHfJo5+69p+fjvMJTmYajNP9rFBHQcaTDFmuXRRATI=
|
||||
Reference in new issue
Block a user