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 ( import (
"fmt" "fmt"
"github.com/pkg/errors"
"net" "net"
"time" "time"
@@ -12,6 +13,7 @@ import (
) )
// PeerID represents a peer ID. // PeerID represents a peer ID.
// TODO: check if torrentV2 also changed this field size
type PeerID [20]byte type PeerID [20]byte
// PeerIDFromBytes creates a PeerID from a byte slice. // PeerIDFromBytes creates a PeerID from a byte slice.
@@ -51,32 +53,52 @@ func PeerIDFromString(s string) PeerID {
} }
// InfoHash represents an infohash. // 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. // InfoHashFromBytes creates an InfoHash from a byte slice.
// func InfoHashFromBytes(b []byte) (InfoHash, error) {
// It panics if b is not 20 bytes long. if l, err := ValidateInfoHash(b); err != nil{
func InfoHashFromBytes(b []byte) InfoHash { return nil, err
if len(b) != 20 { } else {
panic("infohash must be 20 bytes") 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. // InfoHashFromString creates an InfoHash from a string.
// func InfoHashFromString(s string) (InfoHash, error) {
// It panics if s is not 20 bytes long. return InfoHashFromBytes([]byte(s))
func InfoHashFromString(s string) InfoHash {
if len(s) != 20 {
panic("infohash must be 20 bytes")
}
var buf [20]byte
copy(buf[:], s)
return buf
} }
// String implements fmt.Stringer, returning the base16 encoded InfoHash. // String implements fmt.Stringer, returning the base16 encoded InfoHash.
@@ -84,9 +106,9 @@ func (i InfoHash) String() string {
return fmt.Sprintf("%x", i[:]) 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 { func (i InfoHash) RawString() string {
return string(i[:]) return string(i)
} }
// AnnounceRequest represents the parsed parameters from an announce request. // 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) { func TestInfoHash_String(t *testing.T) {
s := InfoHashFromBytes(b).String() ih, err := InfoHashFromBytes(b)
require.Equal(t, expected, s) require.Nil(t, err)
require.Equal(t, expected, ih.String())
} }
func TestPeer_String(t *testing.T) { 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 key == "info_hash" {
if len(value) != 20 { if ih, err := InfoHashFromString(value); err == nil{
return nil, ErrInvalidInfohash q.infoHashes = append(q.infoHashes, ih)
} else {
return nil, err
} }
q.infoHashes = append(q.infoHashes, InfoHashFromString(value))
} else { } else {
q.params[strings.ToLower(key)] = value q.params[strings.ToLower(key)] = value
} }

View File

@@ -2,7 +2,6 @@ package main
import ( import (
"errors" "errors"
"io/ioutil"
"os" "os"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
@@ -73,19 +72,10 @@ func ParseConfigFile(path string) (*ConfigFile, error) {
f, err := os.Open(os.ExpandEnv(path)) f, err := os.Open(os.ExpandEnv(path))
if err != nil { if err != nil {
return nil, err 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 package main
import ( import (
"crypto/rand"
"fmt" "fmt"
"math/rand"
"time" "time"
"github.com/anacrolix/torrent/tracker" "github.com/anacrolix/torrent/tracker"
@@ -54,28 +54,22 @@ func EndToEndRunCmdFunc(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func generateInfohash() [20]byte { func generateInfohash() bittorrent.InfoHash {
b := make([]byte, 20) b := make([]byte, 20)
rand.Read(b)
n, err := rand.Read(b) ih, _ := bittorrent.InfoHashFromBytes(b)
if err != nil { return ih
panic(err)
}
if n != 20 {
panic(fmt.Errorf("not enough randomness? Got %d bytes", n))
}
return bittorrent.InfoHashFromBytes(b)
} }
func test(addr string, delay time.Duration) error { func test(addr string, delay time.Duration) error {
ih := generateInfohash() ih, _ := generateInfohash().BytesV1()
return testWithInfohash(ih, addr, delay) return testWithInfohash(ih, addr, delay)
} }
func testWithInfohash(infoHash [20]byte, url string, delay time.Duration) error { func testWithInfohash(infoHash [20]byte, url string, delay time.Duration) error {
var ih [20]byte
req := tracker.AnnounceRequest{ 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}, 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, Downloaded: 50,
Left: 100, Left: 100,

View File

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

View File

@@ -12,7 +12,7 @@ import (
type strMap map[string]interface{} type strMap map[string]interface{}
// WriteError communicates an error to a BitTorrent client over HTTP. // 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" message := "internal server error"
if _, clientErr := err.(bittorrent.ClientError); clientErr { if _, clientErr := err.(bittorrent.ClientError); clientErr {
message = err.Error() message = err.Error()
@@ -21,9 +21,11 @@ func WriteError(w http.ResponseWriter, err error) error {
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
return bencode.NewEncoder(w).Encode(map[string]interface{}{ if err = bencode.NewEncoder(w).Encode(map[string]interface{}{
"failure reason": message, "failure reason": message,
}) }); err != nil{
log.Error("unable to encode string", log.Err(err))
}
} }
// WriteAnnounceResponse communicates the results of an Announce to a // WriteAnnounceResponse communicates the results of an Announce to a
@@ -64,19 +66,18 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo
bdict["peers6"] = IPv6CompactDict 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) return bencode.NewEncoder(w).Encode(bdict)
} }
@@ -100,7 +101,7 @@ func compact4(peer bittorrent.Peer) (buf []byte) {
if ip := peer.IP.To4(); ip == nil { if ip := peer.IP.To4(); ip == nil {
panic("non-IPv4 IP for Peer in IPv4Peers") panic("non-IPv4 IP for Peer in IPv4Peers")
} else { } else {
buf = []byte(ip) buf = ip
} }
buf = append(buf, byte(peer.Port>>8)) buf = append(buf, byte(peer.Port>>8))
buf = append(buf, byte(peer.Port&0xff)) 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 { if ip := peer.IP.To16(); ip == nil {
panic("non-IPv6 IP for Peer in IPv6Peers") panic("non-IPv6 IP for Peer in IPv6Peers")
} else { } else {
buf = []byte(ip) buf = ip
} }
buf = append(buf, byte(peer.Port>>8)) buf = append(buf, byte(peer.Port>>8))
buf = append(buf, byte(peer.Port&0xff)) 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 return nil, err
} }
ih, err := bittorrent.InfoHashFromBytes(infohash)
if err != nil{
return nil, err
}
request := &bittorrent.AnnounceRequest{ request := &bittorrent.AnnounceRequest{
Event: eventIDs[eventID], Event: eventIDs[eventID],
InfoHash: bittorrent.InfoHashFromBytes(infohash), InfoHash: ih,
NumWant: uint32(numWant), NumWant: numWant,
Left: left, Left: left,
Downloaded: downloaded, Downloaded: downloaded,
Uploaded: uploaded, 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 // Skip past the initial headers and check that the bytes left equal the
// length of a valid list of infohashes. // length of a valid list of infohashes.
r.Packet = r.Packet[16:] 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 return nil, errMalformedPacket
} }
// Allocate a list of infohashes and append it to the list until we're out. // Allocate a list of infohashes and append it to the list until we're out.
var infohashes []bittorrent.InfoHash var infohashes []bittorrent.InfoHash
for len(r.Packet) >= 20 { pageSize := bittorrent.InfoHashV1Len
infohashes = append(infohashes, bittorrent.InfoHashFromBytes(r.Packet[:20])) if isV2 {
r.Packet = r.Packet[20:] 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. // 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. // TestPeerStore tests a PeerStore implementation against the interface.
func TestPeerStore(t *testing.T, p PeerStore) { func TestPeerStore(t *testing.T, p PeerStore) {
ih0, _ := bittorrent.InfoHashFromString("00000000000000000001")
ih1, _ := bittorrent.InfoHashFromString("00000000000000000002")
testData := []struct { testData := []struct {
ih bittorrent.InfoHash ih bittorrent.InfoHash
peer bittorrent.Peer 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.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}}, bittorrent.Peer{ID: bittorrent.PeerIDFromString("00000000000000000002"), Port: 2, IP: bittorrent.IP{IP: net.ParseIP("abab::0001"), AddressFamily: bittorrent.IPv6}},
}, },
} }