mirror of
https://github.com/sot-tech/mochi.git
synced 2026-05-21 07:14:48 -07:00
Initial torrentV2 hash support
This commit is contained in:
committed by
Lawrence, Rendall
parent
823b92fe83
commit
2f092bad45
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user