Initial torrentV2 hash support

This commit is contained in:
Širhoe Biazhkovič
2021-10-26 19:03:05 +03:00
committed by Lawrence, Rendall
parent 823b92fe83
commit 2f092bad45
9 changed files with 131 additions and 138 deletions

View File

@@ -5,6 +5,7 @@ package bittorrent
import (
"fmt"
"github.com/pkg/errors"
"net"
"time"
@@ -12,6 +13,7 @@ import (
)
// PeerID represents a peer ID.
// TODO: check if torrentV2 also changed this field size
type PeerID [20]byte
// PeerIDFromBytes creates a PeerID from a byte slice.
@@ -51,32 +53,52 @@ func PeerIDFromString(s string) PeerID {
}
// InfoHash represents an infohash.
type InfoHash [20]byte
type InfoHash []byte
const(
InfoHashV1Len = 20
InfoHashV2Len = 32
)
var invalidHashSize = errors.New("InfoHash must be either 20 (for torrent V1) or 32 (V2) bytes")
var isNotV1Hash = errors.New("InfoHash is not V1 (SHA1)")
// BytesV1 returns 20-bytes length array of the corresponding InfoHash.
// If InfoHash is not 20-bytes long (is torrent V2 hash) zeroed array and error returned
func (i InfoHash) BytesV1() ([InfoHashV1Len]byte, error){
var bb [InfoHashV1Len]byte
if len(i) != InfoHashV1Len {
return bb, isNotV1Hash
}
copy(bb[:], i)
return bb, nil
}
// ValidateInfoHash validates input bytes size and returns it
// if size one of InfoHashV1Len or InfoHashV2Len.
// In other case 0 and non-nil error returned
func ValidateInfoHash(b []byte) (int, error) {
l := len(b)
if l != InfoHashV1Len && l != InfoHashV2Len {
return 0, invalidHashSize
}
return l, nil
}
// InfoHashFromBytes creates an InfoHash from a byte slice.
//
// It panics if b is not 20 bytes long.
func InfoHashFromBytes(b []byte) InfoHash {
if len(b) != 20 {
panic("infohash must be 20 bytes")
func InfoHashFromBytes(b []byte) (InfoHash, error) {
if l, err := ValidateInfoHash(b); err != nil{
return nil, err
} else {
buf := make([]byte, l)
copy(buf[:], b)
return buf, nil
}
var buf [20]byte
copy(buf[:], b)
return buf
}
// InfoHashFromString creates an InfoHash from a string.
//
// It panics if s is not 20 bytes long.
func InfoHashFromString(s string) InfoHash {
if len(s) != 20 {
panic("infohash must be 20 bytes")
}
var buf [20]byte
copy(buf[:], s)
return buf
func InfoHashFromString(s string) (InfoHash, error) {
return InfoHashFromBytes([]byte(s))
}
// String implements fmt.Stringer, returning the base16 encoded InfoHash.
@@ -84,9 +106,9 @@ func (i InfoHash) String() string {
return fmt.Sprintf("%x", i[:])
}
// RawString returns a 20-byte string of the raw bytes of the InfoHash.
// RawString returns a string of the raw bytes of the InfoHash.
func (i InfoHash) RawString() string {
return string(i[:])
return string(i)
}
// AnnounceRequest represents the parsed parameters from an announce request.

View File

@@ -41,8 +41,9 @@ func TestPeerID_String(t *testing.T) {
}
func TestInfoHash_String(t *testing.T) {
s := InfoHashFromBytes(b).String()
require.Equal(t, expected, s)
ih, err := InfoHashFromBytes(b)
require.Nil(t, err)
require.Equal(t, expected, ih.String())
}
func TestPeer_String(t *testing.T) {

View File

@@ -168,10 +168,11 @@ func parseQuery(query string) (q *QueryParams, err error) {
}
if key == "info_hash" {
if len(value) != 20 {
return nil, ErrInvalidInfohash
if ih, err := InfoHashFromString(value); err == nil{
q.infoHashes = append(q.infoHashes, ih)
} else {
return nil, err
}
q.infoHashes = append(q.infoHashes, InfoHashFromString(value))
} else {
q.params[strings.ToLower(key)] = value
}

View File

@@ -2,7 +2,6 @@ package main
import (
"errors"
"io/ioutil"
"os"
yaml "gopkg.in/yaml.v2"
@@ -73,19 +72,10 @@ func ParseConfigFile(path string) (*ConfigFile, error) {
f, err := os.Open(os.ExpandEnv(path))
if err != nil {
return nil, err
} else {
defer f.Close()
cfgFile := new(ConfigFile)
err = yaml.NewDecoder(f).Decode(cfgFile)
return cfgFile, err
}
defer f.Close()
contents, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
var cfgFile ConfigFile
err = yaml.Unmarshal(contents, &cfgFile)
if err != nil {
return nil, err
}
return &cfgFile, nil
}

View File

@@ -1,8 +1,8 @@
package main
import (
"crypto/rand"
"fmt"
"math/rand"
"time"
"github.com/anacrolix/torrent/tracker"
@@ -54,28 +54,22 @@ func EndToEndRunCmdFunc(cmd *cobra.Command, args []string) error {
return nil
}
func generateInfohash() [20]byte {
func generateInfohash() bittorrent.InfoHash {
b := make([]byte, 20)
n, err := rand.Read(b)
if err != nil {
panic(err)
}
if n != 20 {
panic(fmt.Errorf("not enough randomness? Got %d bytes", n))
}
return bittorrent.InfoHashFromBytes(b)
rand.Read(b)
ih, _ := bittorrent.InfoHashFromBytes(b)
return ih
}
func test(addr string, delay time.Duration) error {
ih := generateInfohash()
ih, _ := generateInfohash().BytesV1()
return testWithInfohash(ih, addr, delay)
}
func testWithInfohash(infoHash [20]byte, url string, delay time.Duration) error {
var ih [20]byte
req := tracker.AnnounceRequest{
InfoHash: infoHash,
InfoHash: ih,
PeerId: [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
Downloaded: 50,
Left: 100,

View File

@@ -172,34 +172,21 @@ func NewFrontend(logic frontend.TrackerLogic, provided Config) (*Frontend, error
}
}
if cfg.HTTPSAddr != "" && f.tlsCfg == nil {
return nil, errors.New("must specify tls_cert_path and tls_key_path when using https_addr")
}
if cfg.HTTPSAddr == "" && f.tlsCfg != nil {
return nil, errors.New("must specify https_addr when using tls_cert_path and tls_key_path")
if cfg.HTTPSAddr == "" || f.tlsCfg == nil {
return nil, errors.New("must specify both https_addr, tls_cert_path and tls_key_path")
}
var listenerHTTP, listenerHTTPS net.Listener
var err error
if cfg.Addr != "" {
listenerHTTP, err = net.Listen("tcp", f.Addr)
if err != nil {
return nil, err
}
router := httprouter.New()
for _, route := range f.AnnounceRoutes {
router.GET(route, f.announceRoute)
}
if cfg.HTTPSAddr != "" {
listenerHTTPS, err = net.Listen("tcp", f.HTTPSAddr)
if err != nil {
if listenerHTTP != nil {
listenerHTTP.Close()
}
return nil, err
}
for _, route := range f.ScrapeRoutes {
router.GET(route, f.scrapeRoute)
}
if cfg.Addr != "" {
go func() {
if err := f.serveHTTP(listenerHTTP); err != nil {
if err := f.serveHTTP(router,false); err != nil {
log.Fatal("failed while serving http", log.Err(err))
}
}()
@@ -207,7 +194,7 @@ func NewFrontend(logic frontend.TrackerLogic, provided Config) (*Frontend, error
if cfg.HTTPSAddr != "" {
go func() {
if err := f.serveHTTPS(listenerHTTPS); err != nil {
if err := f.serveHTTP(router,true); err != nil {
log.Fatal("failed while serving https", log.Err(err))
}
}()
@@ -240,52 +227,31 @@ func (f *Frontend) makeStopFunc(stopSrv *http.Server) stop.Func {
}
}
func (f *Frontend) handler() http.Handler {
router := httprouter.New()
for _, route := range f.AnnounceRoutes {
router.GET(route, f.announceRoute)
}
for _, route := range f.ScrapeRoutes {
router.GET(route, f.scrapeRoute)
}
return router
}
// serveHTTP blocks while listening and serving non-TLS HTTP BitTorrent
// requests until Stop() is called or an error is returned.
func (f *Frontend) serveHTTP(l net.Listener) error {
f.srv = &http.Server{
Addr: f.Addr,
Handler: f.handler(),
func (f *Frontend) serveHTTP(handler http.Handler, tls bool) error {
srv := &http.Server{
Handler: handler,
ReadTimeout: f.ReadTimeout,
WriteTimeout: f.WriteTimeout,
IdleTimeout: f.IdleTimeout,
}
f.srv.SetKeepAlivesEnabled(f.EnableKeepAlive)
srv.SetKeepAlivesEnabled(f.EnableKeepAlive)
// Start the HTTP server.
if err := f.srv.Serve(l); err != http.ErrServerClosed {
return err
var err error
if tls {
srv.Addr = f.HTTPSAddr
srv.TLSConfig = f.tlsCfg
f.tlsSrv = srv
err = srv.ListenAndServe()
} else{
srv.Addr = f.Addr
f.srv = srv
err = f.tlsSrv.ListenAndServeTLS("", "")
}
return nil
}
// serveHTTPS blocks while listening and serving TLS HTTP BitTorrent
// requests until Stop() is called or an error is returned.
func (f *Frontend) serveHTTPS(l net.Listener) error {
f.tlsSrv = &http.Server{
Addr: f.HTTPSAddr,
TLSConfig: f.tlsCfg,
Handler: f.handler(),
ReadTimeout: f.ReadTimeout,
WriteTimeout: f.WriteTimeout,
}
f.tlsSrv.SetKeepAlivesEnabled(f.EnableKeepAlive)
// Start the HTTP server.
if err := f.tlsSrv.ServeTLS(l, "", ""); err != http.ErrServerClosed {
if err != http.ErrServerClosed {
return err
}
return nil

View File

@@ -12,7 +12,7 @@ import (
type strMap map[string]interface{}
// WriteError communicates an error to a BitTorrent client over HTTP.
func WriteError(w http.ResponseWriter, err error) error {
func WriteError(w http.ResponseWriter, err error) {
message := "internal server error"
if _, clientErr := err.(bittorrent.ClientError); clientErr {
message = err.Error()
@@ -21,9 +21,11 @@ func WriteError(w http.ResponseWriter, err error) error {
}
w.WriteHeader(http.StatusOK)
return bencode.NewEncoder(w).Encode(map[string]interface{}{
if err = bencode.NewEncoder(w).Encode(map[string]interface{}{
"failure reason": message,
})
}); err != nil{
log.Error("unable to encode string", log.Err(err))
}
}
// WriteAnnounceResponse communicates the results of an Announce to a
@@ -64,19 +66,18 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo
bdict["peers6"] = IPv6CompactDict
}
return bencode.NewEncoder(w).Encode(bdict)
} else {
// Add the peers to the dictionary.
var peers []strMap
for _, peer := range resp.IPv4Peers {
peers = append(peers, dict(peer))
}
for _, peer := range resp.IPv6Peers {
peers = append(peers, dict(peer))
}
bdict["peers"] = peers
}
// Add the peers to the dictionary.
var peers []strMap
for _, peer := range resp.IPv4Peers {
peers = append(peers, dict(peer))
}
for _, peer := range resp.IPv6Peers {
peers = append(peers, dict(peer))
}
bdict["peers"] = peers
return bencode.NewEncoder(w).Encode(bdict)
}
@@ -100,7 +101,7 @@ func compact4(peer bittorrent.Peer) (buf []byte) {
if ip := peer.IP.To4(); ip == nil {
panic("non-IPv4 IP for Peer in IPv4Peers")
} else {
buf = []byte(ip)
buf = ip
}
buf = append(buf, byte(peer.Port>>8))
buf = append(buf, byte(peer.Port&0xff))
@@ -111,7 +112,7 @@ func compact6(peer bittorrent.Peer) (buf []byte) {
if ip := peer.IP.To16(); ip == nil {
panic("non-IPv6 IP for Peer in IPv6Peers")
} else {
buf = []byte(ip)
buf = ip
}
buf = append(buf, byte(peer.Port>>8))
buf = append(buf, byte(peer.Port&0xff))

View File

@@ -112,10 +112,15 @@ func ParseAnnounce(r Request, v6Action bool, opts ParseOptions) (*bittorrent.Ann
return nil, err
}
ih, err := bittorrent.InfoHashFromBytes(infohash)
if err != nil{
return nil, err
}
request := &bittorrent.AnnounceRequest{
Event: eventIDs[eventID],
InfoHash: bittorrent.InfoHashFromBytes(infohash),
NumWant: uint32(numWant),
InfoHash: ih,
NumWant: numWant,
Left: left,
Downloaded: downloaded,
Uploaded: uploaded,
@@ -208,15 +213,26 @@ func ParseScrape(r Request, opts ParseOptions) (*bittorrent.ScrapeRequest, error
// Skip past the initial headers and check that the bytes left equal the
// length of a valid list of infohashes.
r.Packet = r.Packet[16:]
if len(r.Packet)%20 != 0 {
l := len(r.Packet)
isV1, isV2 := l%bittorrent.InfoHashV1Len == 0, l%bittorrent.InfoHashV2Len == 0
if !(isV1 || isV2) {
return nil, errMalformedPacket
}
// Allocate a list of infohashes and append it to the list until we're out.
var infohashes []bittorrent.InfoHash
for len(r.Packet) >= 20 {
infohashes = append(infohashes, bittorrent.InfoHashFromBytes(r.Packet[:20]))
r.Packet = r.Packet[20:]
pageSize := bittorrent.InfoHashV1Len
if isV2 {
pageSize = bittorrent.InfoHashV2Len
}
for len(r.Packet) >= pageSize {
if ih, err := bittorrent.InfoHashFromBytes(r.Packet[:pageSize]); err != nil{
return nil, err
} else {
infohashes = append(infohashes, ih)
r.Packet = r.Packet[pageSize:]
}
}
// Sanitize the request.

View File

@@ -17,16 +17,18 @@ var PeerEqualityFunc = func(p1, p2 bittorrent.Peer) bool { return p1.Equal(p2) }
// TestPeerStore tests a PeerStore implementation against the interface.
func TestPeerStore(t *testing.T, p PeerStore) {
ih0, _ := bittorrent.InfoHashFromString("00000000000000000001")
ih1, _ := bittorrent.InfoHashFromString("00000000000000000002")
testData := []struct {
ih bittorrent.InfoHash
peer bittorrent.Peer
}{
{
bittorrent.InfoHashFromString("00000000000000000001"),
ih0,
bittorrent.Peer{ID: bittorrent.PeerIDFromString("00000000000000000001"), Port: 1, IP: bittorrent.IP{IP: net.ParseIP("1.1.1.1").To4(), AddressFamily: bittorrent.IPv4}},
},
{
bittorrent.InfoHashFromString("00000000000000000002"),
ih1,
bittorrent.Peer{ID: bittorrent.PeerIDFromString("00000000000000000002"), Port: 2, IP: bittorrent.IP{IP: net.ParseIP("abab::0001"), AddressFamily: bittorrent.IPv6}},
},
}