Skip to content

go-i2p/go-i2p-bt

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BT - Another Implementation For Golang Build Status GoDoc License

A pure golang implementation of BitTorrent library, which is inspired by dht and torrent.

Install

$ go get -u github.com/go-i2p/go-i2p-bt

Features

  • Requires Go 1.23.1+.
  • Support I2P, IPv4/IPv6.
  • Multi-BEPs implementation.
  • Pure Go implementation without CGO.
  • Transmission RPC-compatible HTTP server.
  • Upload/download handlers for file transfer.
  • Real-time torrent statistics and monitoring.
  • Minimal dependencies (only I2P-related packages). For command-line tools built on this library, see bttools.

The Implemented Specifications

RPC Server

This library includes a Transmission RPC-compatible HTTP server for remote torrent management. The server implements the Transmission RPC protocol with JSON-RPC 2.0 over HTTP.

Supported RPC Methods

  • torrent-add - Add new torrents from .torrent files or magnet links
  • torrent-get - Retrieve torrent information and statistics
  • torrent-start / torrent-start-now - Start torrent downloads
  • torrent-stop - Stop torrent downloads
  • torrent-verify - Verify torrent data integrity
  • torrent-remove - Remove torrents (with optional data deletion)
  • torrent-set - Modify torrent settings and priorities
  • session-get / session-set - Manage global session configuration
  • session-stats - Retrieve session statistics

RPC Server Usage

package main

import (
    "log"
    "github.com/go-i2p/go-i2p-bt/rpc"
)

func main() {
    // Create a simple server
    server, err := rpc.NewSimpleServer("/path/to/downloads")
    if err != nil {
        log.Fatal(err)
    }

    // Start the HTTP server
    log.Println("Starting RPC server on :9091")
    if err := rpc.StartServer(server, ":9091"); err != nil {
        log.Fatal(err)
    }
}

Authentication and Security

The server supports HTTP Basic Authentication and session token-based CSRF protection:

server, err := rpc.NewServerWithAuth("/downloads", "username", "password")

Example

See godoc or bttools.

Example 1: Download the file from the remote peer

package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"log"
	"net/url"
	"os"
	"time"

	"github.com/go-i2p/go-i2p-bt/downloader"
	"github.com/go-i2p/go-i2p-bt/downloadhandler"
	"github.com/go-i2p/go-i2p-bt/metainfo"
	pp "github.com/go-i2p/go-i2p-bt/peerprotocol"
	"github.com/go-i2p/go-i2p-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 := downloadhandler.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
}

About

Bittorrent library for use with I2P from Go. Orignally forked from xgfone/bt, now maintained as a separate module.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Go 96.3%
  • JavaScript 2.1%
  • Other 1.6%