mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-07 00:50:27 +00:00
BitTorrent peer write test
This commit is contained in:
1 parent
41ff983b73
commit
03fbdcda60
15 files changed
+649
-188
No files matched your search
@@ -1,2 +1,5 @@
|
||||
## gopeed
|
||||
A fast download client,support HTTP&P2P.
|
||||
A fast download client,support HTTP&P2P.
|
||||
|
||||
## 参考
|
||||
[https://wiki.theory.org/index.php/BitTorrentSpecification#Peer_wire_protocol_.28TCP.29](https://wiki.theory.org/index.php/BitTorrentSpecification#Peer_wire_protocol_.28TCP.29)
|
||||
@@ -6,16 +6,5 @@ import (
|
||||
|
||||
func main() {
|
||||
metaInfo, _ := bt.ParseFromFile("e:/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent")
|
||||
// metaInfo, _ := down.ParseFromFile("e:/test.torrent")
|
||||
if len(metaInfo.AnnounceList) > 0 {
|
||||
for _, announceArr := range metaInfo.AnnounceList {
|
||||
if len(announceArr) > 0 {
|
||||
for _, announce := range announceArr {
|
||||
bt.DoTracker(announce, metaInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bt.DoTracker(metaInfo.Announce, metaInfo)
|
||||
}
|
||||
metaInfo.Tracker()
|
||||
}
|
||||
+19
-4
@@ -1,9 +1,24 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var b = -1
|
||||
var n = uint32(b)
|
||||
fmt.Println(n)
|
||||
data := [68]byte{0x13, 0x42, 0x69, 0x74, 0x54, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x05, 0x2c, 0x37, 0xb2, 0x23, 0xe3, 0xaf, 0x73, 0xe7, 0xb0, 0xe7, 0xa0, 0x05, 0x03, 0x07, 0xe6, 0x61, 0xcf, 0x94, 0xe7, 0x0d, 0x2d, 0x55, 0x54, 0x33, 0x35, 0x35, 0x53, 0x2d, 0xaf, 0xb0, 0xec, 0xdc, 0xbf, 0xd1, 0x02, 0x93, 0xd6, 0xf4, 0x9b, 0x24}
|
||||
conn, err := net.Dial("tcp", "58.179.218.77:47474")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
conn.Write(data[:])
|
||||
|
||||
var read [68]byte
|
||||
_, err = io.ReadFull(conn, read[:])
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(read)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ip := net.ParseIP("127.0.0.1")
|
||||
srcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||
dstAddr := &net.UDPAddr{IP: ip, Port: 9981}
|
||||
conn, err := net.DialUDP("udp", srcAddr, dstAddr)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
conn.Write([]byte("hello"))
|
||||
conn.Write([]byte("test"))
|
||||
conn.Read([]byte{})
|
||||
fmt.Printf("<%s>\n", conn.RemoteAddr())
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9981})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Local: <%s> \n", conn.LocalAddr().String())
|
||||
data := make([]byte, 1024)
|
||||
for {
|
||||
n, remoteAddr, err := conn.ReadFromUDP(data)
|
||||
if err != nil {
|
||||
fmt.Printf("error during read: %s", err)
|
||||
}
|
||||
fmt.Printf("<%s> %s\n", remoteAddr, data[:n])
|
||||
_, err = conn.WriteToUDP([]byte("world"), remoteAddr)
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Age uint32
|
||||
Sex byte
|
||||
Name [20]byte
|
||||
}
|
||||
|
||||
type Buffer struct {
|
||||
buf []byte
|
||||
limit int
|
||||
}
|
||||
|
||||
func NewBuffer(buf []byte) *Buffer {
|
||||
return &Buffer{
|
||||
buf: buf,
|
||||
limit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
user := &User{1, 2, [20]byte{}}
|
||||
buf := new(bytes.Buffer)
|
||||
binary.Write(buf, binary.LittleEndian, user)
|
||||
fmt.Println(buf.Bytes())
|
||||
}
|
||||
|
||||
/*func (buffer Buffer) ReadByte() (byte, error) {
|
||||
|
||||
}
|
||||
|
||||
func (buffer Buffer) ReadByte() (byte, error)() {
|
||||
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,25 @@
|
||||
package bt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ErrorCode int
|
||||
|
||||
const (
|
||||
ErrTimeout ErrorCode = -1
|
||||
ErrResponse ErrorCode = -2
|
||||
)
|
||||
|
||||
type TrackerError struct {
|
||||
Code ErrorCode
|
||||
Err error
|
||||
}
|
||||
|
||||
func (te *TrackerError) Error() string {
|
||||
return fmt.Sprintf("%s:%d", te.Err.Error(), te.Code)
|
||||
}
|
||||
|
||||
func NewTrackerError(code ErrorCode, err error) *TrackerError {
|
||||
return &TrackerError{code, err}
|
||||
}
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
package bt
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type MessageType byte
|
||||
|
||||
const (
|
||||
ProtocolIdentifier = "BitTorrent protocol"
|
||||
ProtocolIdentifierLength = 0x13
|
||||
|
||||
Choke MessageType = iota
|
||||
Unchoke
|
||||
Interested
|
||||
NotInterested
|
||||
Have
|
||||
Bitfield
|
||||
Request
|
||||
Piece
|
||||
Cancel
|
||||
)
|
||||
|
||||
type Peer struct {
|
||||
IP uint32
|
||||
Port uint16
|
||||
}
|
||||
|
||||
func (peer *Peer) Address() string {
|
||||
bts := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(bts, peer.IP)
|
||||
return net.IP(bts).String() + ":" + strconv.Itoa(int(peer.Port))
|
||||
}
|
||||
|
||||
type Handshake struct {
|
||||
Pstrlen byte
|
||||
Pstr [19]byte
|
||||
Reserved [8]byte
|
||||
InfoHash [20]byte
|
||||
PeerID [20]byte
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Length uint32
|
||||
Type byte
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
func NewHandshake(reserved [8]byte, infoHash [20]byte, peerID [20]byte) *Handshake {
|
||||
var arr [ProtocolIdentifierLength]byte
|
||||
copy(arr[:], ProtocolIdentifier)
|
||||
return &Handshake{
|
||||
Pstrlen: ProtocolIdentifierLength,
|
||||
Pstr: arr,
|
||||
Reserved: reserved,
|
||||
InfoHash: infoHash,
|
||||
PeerID: peerID,
|
||||
}
|
||||
}
|
||||
|
||||
func (handshake *Handshake) encode() ([]byte, error) {
|
||||
writer := new(bytes.Buffer)
|
||||
err := binary.Write(writer, binary.BigEndian, handshake)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (handshake *Handshake) decode(buf []byte) error {
|
||||
reader := bytes.NewReader(buf)
|
||||
err := binary.Read(reader, binary.BigEndian, handshake)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (peer *Peer) DoDownload(metaInfo *MetaInfo, peerId [20]byte) error {
|
||||
if peer.Port == 0 {
|
||||
return fmt.Errorf("error port %d", peer.Port)
|
||||
}
|
||||
conn, err := net.Dial("tcp", peer.Address())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = doHandshake(peer, metaInfo, peerId, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(conn)
|
||||
scanner.Split(splitMessage)
|
||||
for scanner.Scan() {
|
||||
buf := scanner.Bytes()
|
||||
message := decodeMessage(buf)
|
||||
// keep-alive message
|
||||
if message.Length == 0 {
|
||||
|
||||
} else {
|
||||
switch MessageType(buf[4]) {
|
||||
case Choke:
|
||||
break
|
||||
case Unchoke:
|
||||
break
|
||||
case Interested:
|
||||
break
|
||||
case NotInterested:
|
||||
break
|
||||
case Have:
|
||||
break
|
||||
case Bitfield:
|
||||
break
|
||||
case Request:
|
||||
break
|
||||
case Piece:
|
||||
break
|
||||
case Cancel:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*var (
|
||||
// This client is choking the peer
|
||||
amChocking = true
|
||||
// This client is interested in the peer
|
||||
amInterested = false
|
||||
// Peer is choking this client
|
||||
peerChocking = true
|
||||
// Peer is interested in this client
|
||||
peerInterested = false
|
||||
)
|
||||
|
||||
// Receive message
|
||||
var cacheBuf []byte
|
||||
buf := make([]byte, 8192)
|
||||
for {
|
||||
scanner := bufio.NewScanner(conn)
|
||||
n, err := conn.Read(buf)
|
||||
if n > 0 {
|
||||
if len(cacheBuf) > 0 {
|
||||
buf = append(cacheBuf, buf...)
|
||||
}
|
||||
msg, err := decodeMessage(buf, n)
|
||||
if err != nil {
|
||||
if err == NMD {
|
||||
cacheBuf = make([]byte, n)
|
||||
copy(cacheBuf, buf[0:n])
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
msgLength := binary.BigEndian.Uint32(buf)
|
||||
// keep-alive message
|
||||
if msgLength == 0 {
|
||||
|
||||
} else {
|
||||
switch MessageType(buf[4]) {
|
||||
case Choke:
|
||||
peerChocking = true
|
||||
break
|
||||
case Unchoke:
|
||||
peerChocking = false
|
||||
break
|
||||
case Interested:
|
||||
peerInterested = true
|
||||
break
|
||||
case NotInterested:
|
||||
peerInterested = false
|
||||
break
|
||||
case Have:
|
||||
break
|
||||
case Bitfield:
|
||||
break
|
||||
case Request:
|
||||
break
|
||||
case Piece:
|
||||
break
|
||||
case Cancel:
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
reserved := [8]byte{}
|
||||
reserved[5] = 0x10
|
||||
reserved[6] = 0x0
|
||||
reserved[7] = 0x5
|
||||
handshakeReq := NewHandshake(reserved, metaInfo.InfoHash, peerId)
|
||||
buf, err := handshakeReq.encode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.Write(buf)
|
||||
|
||||
var read [68]byte
|
||||
_, err = io.ReadFull(conn, read[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
handshakeRes := new(Handshake)
|
||||
err = handshakeRes.decode(read[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if handshakeRes.InfoHash != handshakeReq.InfoHash {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitMessage(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if !atEOF && len(data) > 4 {
|
||||
length := int(binary.BigEndian.Uint32(data))
|
||||
if len(data)-4 >= length {
|
||||
return length + 4, data[:length+4], nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func decodeMessage(buf []byte) *Message {
|
||||
msg := &Message{}
|
||||
msg.Length = binary.BigEndian.Uint32(buf)
|
||||
// keep-alive message
|
||||
if msg.Length > 0 {
|
||||
msg.Type = buf[4]
|
||||
msg.Payload = buf[5:]
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func doBitfield(peer *Peer, metaInfo *MetaInfo, conn net.Conn) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Binary file not shown.
+128
-95
@@ -32,7 +32,7 @@ const (
|
||||
8 32-bit integer action 0 // connect
|
||||
12 32-bit integer transaction_id
|
||||
16
|
||||
Detail see https://www.libtorrent.org/udp_tracker_protocol.html#connecting
|
||||
Per https://www.libtorrent.org/udp_tracker_protocol.html#connecting
|
||||
*/
|
||||
type UdpConnectRequest struct {
|
||||
ProtocolId uint64
|
||||
@@ -93,7 +93,7 @@ func NewUdpConnectResponse(buf []byte) *UdpConnectResponse {
|
||||
92 32-bit integer num_want -1 // default
|
||||
96 16-bit integer port
|
||||
98
|
||||
Detail see https://www.libtorrent.org/udp_tracker_protocol.html#announcing
|
||||
Per https://www.libtorrent.org/udp_tracker_protocol.html#announcing
|
||||
*/
|
||||
type UdpAnnounceRequest struct {
|
||||
ConnectionId uint64
|
||||
@@ -147,6 +147,7 @@ func (req *UdpAnnounceRequest) encode() []byte {
|
||||
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
|
||||
@@ -154,8 +155,7 @@ type UdpAnnounceResponse struct {
|
||||
Interval uint32
|
||||
Leechers uint32
|
||||
Seeders uint32
|
||||
IP []uint32
|
||||
Port []uint16
|
||||
Peers []Peer
|
||||
}
|
||||
|
||||
func NewUdpAnnounceResponse(buf []byte) *UdpAnnounceResponse {
|
||||
@@ -167,48 +167,33 @@ func NewUdpAnnounceResponse(buf []byte) *UdpAnnounceResponse {
|
||||
Seeders: binary.BigEndian.Uint32(buf[16:20]),
|
||||
}
|
||||
count := (len(buf) - 20) / 6
|
||||
response.IP = make([]uint32, count, count)
|
||||
response.Port = make([]uint16, count, count)
|
||||
response.Peers = make([]Peer, count, count)
|
||||
for i := 0; i < count; i++ {
|
||||
begin := 20 + 6*i
|
||||
response.IP[i] = binary.BigEndian.Uint32(buf[begin : begin+4])
|
||||
response.Port[i] = binary.BigEndian.Uint16(buf[begin+4 : begin+6])
|
||||
ipBegin := 20 + 6*i
|
||||
portBegin := ipBegin + 4
|
||||
response.Peers[i] = Peer{
|
||||
IP: binary.BigEndian.Uint32(buf[ipBegin:portBegin]),
|
||||
Port: binary.BigEndian.Uint16(buf[portBegin : portBegin+2]),
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
type UdpTrackerContent struct {
|
||||
URL *url.URL
|
||||
MetaInfo *MetaInfo
|
||||
Conn *net.UDPConn
|
||||
ConnectRequest *UdpConnectRequest
|
||||
ConnectResponse *UdpConnectResponse
|
||||
AnnounceRequest *UdpAnnounceRequest
|
||||
AnnounceResponse *UdpAnnounceResponse
|
||||
}
|
||||
|
||||
func NewUdpTrackerContent(url *url.URL, metaInfo *MetaInfo) *UdpTrackerContent {
|
||||
return &UdpTrackerContent{
|
||||
URL: url,
|
||||
MetaInfo: metaInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func (content *UdpTrackerContent) connect() error {
|
||||
host, portStr, err := net.SplitHostPort(content.URL.Host)
|
||||
func dial(trackerUrl *url.URL) (*net.UDPConn, error) {
|
||||
host, portStr, err := net.SplitHostPort(trackerUrl.Host)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
port, err := strconv.ParseInt(portStr, 10, 32)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
ip, err := lookupIP(host, content.URL.Scheme)
|
||||
ip, err := lookupIP(host, trackerUrl.Scheme)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||
@@ -216,87 +201,99 @@ func (content *UdpTrackerContent) connect() error {
|
||||
IP: ip,
|
||||
Port: int(port),
|
||||
}
|
||||
conn, err := net.DialUDP(content.URL.Scheme, srcAddr, dstAddr)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
// Connect
|
||||
request := NewUdpConnectRequest()
|
||||
var response *UdpConnectResponse
|
||||
for n := 0; n < udpConnectRetries; n++ {
|
||||
buf := request.Encode()
|
||||
conn.Write(buf)
|
||||
// 15 * 2 ^ n seconds
|
||||
timeout := udpConnectTimeout * math.Pow(2, float64(n))
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout)))
|
||||
len, err := conn.Read(buf)
|
||||
if len == 16 && err == nil {
|
||||
response = NewUdpConnectResponse(buf)
|
||||
break
|
||||
}
|
||||
}
|
||||
if response == nil {
|
||||
conn.Close()
|
||||
return errors.New("UDP tracker connect error")
|
||||
}
|
||||
if response.Action != udpActionConnect || request.TransactionId != response.TransactionId {
|
||||
conn.Close()
|
||||
return errors.New("UDP tracker connect response error")
|
||||
}
|
||||
content.Conn = conn
|
||||
content.ConnectRequest = request
|
||||
content.ConnectResponse = response
|
||||
return nil
|
||||
return net.DialUDP(trackerUrl.Scheme, srcAddr, dstAddr)
|
||||
}
|
||||
|
||||
func (content *UdpTrackerContent) announce() error {
|
||||
request := NewUdpAnnounceRequest(content.ConnectResponse.ConnectionId)
|
||||
request.InfoHash = content.MetaInfo.InfoHash
|
||||
peerID, err := genPeerID()
|
||||
func 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 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 err
|
||||
return
|
||||
}
|
||||
request.PeerID = peerID
|
||||
request.Downloaded = 0
|
||||
request.Uploaded = 0
|
||||
request.Left = content.MetaInfo.GetTotalSize() - request.Downloaded
|
||||
request.Left = metaInfo.GetTotalSize() - request.Downloaded
|
||||
request.Event = 0
|
||||
request.IP = 0
|
||||
request.Key = 0
|
||||
request.NumWant = 50
|
||||
request.Port = 6882
|
||||
encode := request.encode()
|
||||
content.Conn.Write(encode)
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout)))
|
||||
conn.Write(encode)
|
||||
buf := make([]byte, 512)
|
||||
len, err := content.Conn.Read(buf)
|
||||
len, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
return
|
||||
}
|
||||
response := NewUdpAnnounceResponse(buf[:len])
|
||||
fmt.Println(response)
|
||||
return nil
|
||||
response = NewUdpAnnounceResponse(buf[:len])
|
||||
return
|
||||
}
|
||||
|
||||
func DoTracker(announce string, metaInfo *MetaInfo) {
|
||||
func (metaInfo *MetaInfo) Tracker() (peers []Peer, err error) {
|
||||
if len(metaInfo.AnnounceList) > 0 {
|
||||
for _, announceArr := range metaInfo.AnnounceList {
|
||||
if len(announceArr) > 0 {
|
||||
for _, announce := range announceArr {
|
||||
peers, err = doTracker(announce, metaInfo)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
peers, err = doTracker(metaInfo.Announce, metaInfo)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func doTracker(announce string, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
if announce != "" {
|
||||
url, _ := url.Parse(announce)
|
||||
switch url.Scheme {
|
||||
case "http", "https":
|
||||
// TODO UDP test
|
||||
break
|
||||
httpTracker(url, metaInfo)
|
||||
return httpTracker(url, metaInfo)
|
||||
case "udp", "udp4", "udp6":
|
||||
udpTracker(url, metaInfo)
|
||||
return udpTracker(url, metaInfo)
|
||||
default:
|
||||
return nil, errors.New("unsupported protocol")
|
||||
}
|
||||
}
|
||||
return nil, errors.New("empty announce")
|
||||
}
|
||||
|
||||
func genPeerID() ([20]byte, error) {
|
||||
peerId := [20]byte{'G', 'P', '0', '0', '1'}
|
||||
_, err := rand.Read(peerId[5:])
|
||||
// 生成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
|
||||
}
|
||||
@@ -304,13 +301,12 @@ func genPeerID() ([20]byte, error) {
|
||||
}
|
||||
|
||||
// http://bittorrent.org/beps/bep_0003.html#trackers
|
||||
func httpTracker(url *url.URL, metaInfo *MetaInfo) {
|
||||
func httpTracker(url *url.URL, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
query := url.Query()
|
||||
query.Add("info_hash", string(metaInfo.InfoHash[:]))
|
||||
// Generate peer ID
|
||||
peerID, err := genPeerID()
|
||||
peerID, err := GenPeerID()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
query.Add("peer_id", string(peerID[:]))
|
||||
@@ -321,39 +317,76 @@ func httpTracker(url *url.URL, metaInfo *MetaInfo) {
|
||||
url.RawQuery = query.Encode()
|
||||
request, err := http.NewRequest("GET", url.String(), nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
var httpClient = &http.Client{
|
||||
Timeout: time.Second * time.Duration(15),
|
||||
}
|
||||
request.Header.Set("User-Agent", "gopeed/0.0.1")
|
||||
fmt.Println("request:" + url.String())
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
resp, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Println(string(resp))
|
||||
// TODO Parse HTTP tracker response
|
||||
fmt.Println(resp)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// http://bittorrent.org/beps/bep_0015.html
|
||||
func udpTracker(url *url.URL, metaInfo *MetaInfo) {
|
||||
content := NewUdpTrackerContent(url, metaInfo)
|
||||
err := content.connect()
|
||||
func udpTracker(url *url.URL, metaInfo *MetaInfo) (peers []Peer, err error) {
|
||||
conn, err := dial(url)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
content.announce()
|
||||
|
||||
announceResponse, err := func() (announceResponse *UdpAnnounceResponse, err error) {
|
||||
var connectResponse *UdpConnectResponse
|
||||
// 0:connect 1:announce
|
||||
state := 0
|
||||
// 访问超时最多重试8次,当有请求成功则将重试次数重置为0
|
||||
for n := 0; n < udpConnectRetries; n++ {
|
||||
// 15 * 2 ^ n seconds
|
||||
timeout := int64(udpConnectTimeout * math.Pow(2, float64(n)))
|
||||
switch state {
|
||||
case 0:
|
||||
connectResponse, err = connect(conn, timeout)
|
||||
if err != nil {
|
||||
if oe, ok := err.(*net.OpError); ok && oe.Timeout() {
|
||||
break
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
n = -1
|
||||
state = 1
|
||||
break
|
||||
case 1:
|
||||
announceResponse, err = announce(conn, timeout, connectResponse.ConnectionId, metaInfo)
|
||||
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 lookupIP(host string, scheme string) (ip net.IP, err error) {
|
||||
|
||||
+4
-4
@@ -33,7 +33,7 @@ func Resolve(request *Request) (*Response, error) {
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != 200 && response.StatusCode != 206 {
|
||||
return nil, fmt.Errorf("Response status error:%d", response.StatusCode)
|
||||
return nil, fmt.Errorf("response status error:%d", response.StatusCode)
|
||||
}
|
||||
ret := &Response{}
|
||||
// Get file name by "Content-Disposition"
|
||||
@@ -165,10 +165,10 @@ func downChunk(request *Request, file *os.File, start int64, end int64, waitGrou
|
||||
buf := make([]byte, 8192)
|
||||
writeIndex := int64(start)
|
||||
for {
|
||||
len, err := httpResponse.Body.Read(buf)
|
||||
if len > 0 {
|
||||
n, err := httpResponse.Body.Read(buf)
|
||||
if n > 0 {
|
||||
fileLock.Lock()
|
||||
writeSize, err := file.WriteAt(buf[0:len], writeIndex)
|
||||
writeSize, err := file.WriteAt(buf[0:n], writeIndex)
|
||||
fileLock.Unlock()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
||||
+11
-15
@@ -104,28 +104,24 @@ func TestResolve(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTemp(t *testing.T) {
|
||||
f, err := os.Create("e:/data/test.data")
|
||||
f, err := os.Create("e:/testbt/test.data")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
s := time.Now().UnixNano()
|
||||
if err := f.Truncate(1024 * 1024 * 1024 * 1); err != nil {
|
||||
if err := f.Truncate(1024 * 1024 * 512); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("use %d\n", time.Now().UnixNano()-s)
|
||||
bts := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
s := time.Now().UnixNano()
|
||||
f.Write(bts)
|
||||
fmt.Printf("write from 0 use: %d\n", time.Now().UnixNano()-s)
|
||||
s = time.Now().UnixNano()
|
||||
f.Write([]byte{1})
|
||||
fmt.Printf("use %d\n", time.Now().UnixNano()-s)
|
||||
s = time.Now().UnixNano()
|
||||
f.Seek(1024*1024*512, 0)
|
||||
fmt.Printf("use %d\n", time.Now().UnixNano()-s)
|
||||
s = time.Now().UnixNano()
|
||||
f.Write([]byte{2})
|
||||
fmt.Printf("use %d\n", time.Now().UnixNano()-s)
|
||||
s = time.Now().UnixNano()
|
||||
f.Write([]byte{3})
|
||||
fmt.Printf("use %d\n", time.Now().UnixNano()-s)
|
||||
f.Seek(1024*1024*128, 0)
|
||||
f.Write(bts)
|
||||
fmt.Printf("write from 128M use %d\n", time.Now().UnixNano()-s)
|
||||
s = time.Now().UnixNano()
|
||||
f.Write(bts)
|
||||
fmt.Printf("write from 128M+9b use %d\n", time.Now().UnixNano()-s)
|
||||
}
|
||||
|
||||
func fileMd5(filePath string) string {
|
||||
|
||||
@@ -2,7 +2,4 @@ module gopeed
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09
|
||||
)
|
||||
require github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e
|
||||
@@ -1,7 +1,2 @@
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYRuq8JQ1aa7LJt8EXVyo=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
Reference in new issue
Block a user