From dbc58a209df8e933572f464e697564817cfd95d6 Mon Sep 17 00:00:00 2001 From: liwei Date: Fri, 31 May 2019 18:29:04 +0800 Subject: [PATCH] MSE support --- down/bt/bittorrent.go | 1 - down/bt/client.go | 55 ++++++++ down/bt/client_test.go | 10 ++ down/bt/metainfo.go | 50 +++---- down/bt/peer.go | 36 +++-- down/bt/peer_test.go | 157 --------------------- down/bt/torrent.go | 24 ++++ down/bt/tracker.go | 304 +++++++++++++++++++++-------------------- down/http/down_test.go | 8 ++ go.mod | 5 +- go.sum | 2 + 11 files changed, 309 insertions(+), 343 deletions(-) delete mode 100644 down/bt/bittorrent.go create mode 100644 down/bt/client.go create mode 100644 down/bt/client_test.go delete mode 100644 down/bt/peer_test.go create mode 100644 down/bt/torrent.go diff --git a/down/bt/bittorrent.go b/down/bt/bittorrent.go deleted file mode 100644 index 96f76e27..00000000 --- a/down/bt/bittorrent.go +++ /dev/null @@ -1 +0,0 @@ -package bt diff --git a/down/bt/client.go b/down/bt/client.go new file mode 100644 index 00000000..06e7847e --- /dev/null +++ b/down/bt/client.go @@ -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 +} diff --git a/down/bt/client_test.go b/down/bt/client_test.go new file mode 100644 index 00000000..5b5e1708 --- /dev/null +++ b/down/bt/client_test.go @@ -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") +} diff --git a/down/bt/metainfo.go b/down/bt/metainfo.go index 895faba8..9917f6d6 100644 --- a/down/bt/metainfo.go +++ b/down/bt/metainfo.go @@ -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 +} diff --git a/down/bt/peer.go b/down/bt/peer.go index 329e6adf..4f6d36b3 100644 --- a/down/bt/peer.go +++ b/down/bt/peer.go @@ -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 diff --git a/down/bt/peer_test.go b/down/bt/peer_test.go deleted file mode 100644 index 75e13a6a..00000000 --- a/down/bt/peer_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/down/bt/torrent.go b/down/bt/torrent.go new file mode 100644 index 00000000..65dd9408 --- /dev/null +++ b/down/bt/torrent.go @@ -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() +} diff --git a/down/bt/tracker.go b/down/bt/tracker.go index 21b098dc..23939b75 100644 --- a/down/bt/tracker.go +++ b/down/bt/tracker.go @@ -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) { diff --git a/down/http/down_test.go b/down/http/down_test.go index 5654f8c8..f644e1c6 100644 --- a/down/http/down_test.go +++ b/down/http/down_test.go @@ -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 { diff --git a/go.mod b/go.mod index eb0b6446..3e0cbf02 100644 --- a/go.mod +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum index 7aa90f02..4fb0ab47 100644 --- a/go.sum +++ b/go.sum @@ -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=