# Packages
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
# README
BT - Another Implementation For Golang

A pure golang implementation of BitTorrent library, which is inspired by dht and torrent.
Install
$ go get -u github.com/xgfone/bt
Features
- Support
Go1.9+
. - Support IPv4/IPv6.
- Multi-BEPs implementation.
- Pure Go implementation without
CGO
. - Only library without any denpendencies. For the command tools, see bttools.
The Implemented Specifications
- BEP 03: The BitTorrent Protocol Specification
- BEP 05: DHT Protocol
- BEP 06: Fast Extension
- BEP 07: IPv6 Tracker Extension
- BEP 09: Extension for Peers to Send Metadata Files
- BEP 10: Extension Protocol
- BEP 11: Peer Exchange (PEX)
- BEP 12: Multitracker Metadata Extension
- BEP 15: UDP Tracker Protocol for BitTorrent
- BEP 19: WebSeed - HTTP/FTP Seeding (GetRight style) (Only
url-list
in metainfo) - BEP 23: Tracker Returns Compact Peer Lists
- BEP 32: IPv6 extension for DHT
- BEP 33: DHT scrape
- BEP 41: UDP Tracker Protocol Extensions
- BEP 43: Read-only DHT Nodes
- BEP 44: Storing arbitrary data in the DHT
- BEP 48: Tracker Protocol Extension: Scrape
Example
Example 1: Download the file from the remote peer
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"net/url"
"os"
"time"
"github.com/xgfone/bt/downloader"
"github.com/xgfone/bt/metainfo"
pp "github.com/xgfone/bt/peerprotocol"
"github.com/xgfone/bt/tracker"
)
var peeraddr string
func init() {
flag.StringVar(&peeraddr, "peeraddr", "", "The address of the peer storing the file.")
}
func getPeersFromTrackers(id, infohash metainfo.Hash, trackers []string) (peers []string) {
c, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
resp := tracker.GetPeers(c, id, infohash, trackers)
for r := range resp {
for _, addr := range r.Resp.Addresses {
addrs := addr.String()
nonexist := true
for _, peer := range peers {
if peer == addrs {
nonexist = false
break
}
}
if nonexist {
peers = append(peers, addrs)
}
}
}
return
}
func main() {
flag.Parse()
torrentfile := os.Args[1]
mi, err := metainfo.LoadFromFile(torrentfile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
id := metainfo.NewRandomHash()
infohash := mi.InfoHash()
info, err := mi.Info()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
var peers []string
if peeraddr != "" {
peers = []string{peeraddr}
} else {
// Get the peers from the trackers in the torrent file.
trackers := mi.Announces().Unique()
if len(trackers) == 0 {
fmt.Println("no trackers")
return
}
peers = getPeersFromTrackers(id, infohash, trackers)
if len(peers) == 0 {
fmt.Println("no peers")
return
}
}
// We save the downloaded file to the current directory.
w := metainfo.NewWriter("", info, 0)
defer w.Close()
// We don't request the blocks from the remote peers concurrently,
// and it is only an example. But you can do it concurrently.
dm := newDownloadManager(w, info)
for peerslen := len(peers); peerslen > 0 && !dm.IsFinished(); {
peerslen--
peer := peers[peerslen]
peers = peers[:peerslen]
downloadFileFromPeer(peer, id, infohash, dm)
}
}
func downloadFileFromPeer(peer string, id, infohash metainfo.Hash, dm *downloadManager) {
pc, err := pp.NewPeerConnByDial(peer, id, infohash, time.Second*3)
if err != nil {
log.Printf("fail to dial '%s'", peer)
return
}
defer pc.Close()
dm.doing = false
pc.Timeout = time.Second * 10
if err = pc.Handshake(); err != nil {
log.Printf("fail to handshake with '%s': %s", peer, err)
return
}
info := dm.writer.Info()
bdh := downloader.NewBlockDownloadHandler(info, dm.OnBlock, dm.RequestBlock)
if err = bdh.OnHandShake(pc); err != nil {
log.Printf("handshake error with '%s': %s", peer, err)
return
}
var msg pp.Message
for !dm.IsFinished() {
switch msg, err = pc.ReadMsg(); err {
case nil:
switch err = pc.HandleMessage(msg, bdh); err {
case nil, pp.ErrChoked:
default:
log.Printf("fail to handle the msg from '%s': %s", peer, err)
return
}
case io.EOF:
log.Printf("got EOF from '%s'", peer)
return
default:
log.Printf("fail to read the msg from '%s': %s", peer, err)
return
}
}
}
func newDownloadManager(w metainfo.Writer, info metainfo.Info) *downloadManager {
length := info.Piece(0).Length()
return &downloadManager{writer: w, plength: length}
}
type downloadManager struct {
writer metainfo.Writer
pindex uint32
poffset uint32
plength int64
doing bool
}
func (dm *downloadManager) IsFinished() bool {
if dm.pindex >= uint32(dm.writer.Info().CountPieces()) {
return true
}
return false
}
func (dm *downloadManager) OnBlock(index, offset uint32, b []byte) (err error) {
if dm.pindex != index {
return fmt.Errorf("inconsistent piece: old=%d, new=%d", dm.pindex, index)
} else if dm.poffset != offset {
return fmt.Errorf("inconsistent offset for piece '%d': old=%d, new=%d",
index, dm.poffset, offset)
}
dm.doing = false
n, err := dm.writer.WriteBlock(index, offset, b)
if err == nil {
dm.poffset = offset + uint32(n)
dm.plength -= int64(n)
}
return
}
func (dm *downloadManager) RequestBlock(pc *pp.PeerConn) (err error) {
if dm.doing {
return
}
if dm.plength <= 0 {
dm.pindex++
if dm.IsFinished() {
return
}
dm.poffset = 0
dm.plength = dm.writer.Info().Piece(int(dm.pindex)).Length()
}
index := dm.pindex
begin := dm.poffset
length := uint32(downloader.BlockSize)
if length > uint32(dm.plength) {
length = uint32(dm.plength)
}
log.Printf("Request Block from '%s': index=%d, offset=%d, length=%d",
pc.RemoteAddr().String(), index, begin, length)
if err = pc.SendRequest(index, begin, length); err == nil {
dm.doing = true
}
return
}