]> Sergey Matveev's repositories - btrtrc.git/commitdiff
feat(config): Allows the torrent client to customise the client identity and connecti...
authorthemihai <mihai@epek.com>
Tue, 7 Nov 2017 18:14:13 +0000 (20:14 +0200)
committerthemihai <mihai@epek.com>
Tue, 7 Nov 2017 18:14:13 +0000 (20:14 +0200)
client.go
client_test.go
cmd/tracker-announce/main.go
config.go
global.go
torrent.go
tracker/http.go
tracker/tracker.go
tracker/tracker_test.go
tracker/udp_test.go
tracker_scraper.go

index 47d3b54bdfe957ef0096461766227d03ab37e0cc..ed0f4a5935313c2c10b92740be39610a5c633469 100644 (file)
--- a/client.go
+++ b/client.go
@@ -232,6 +232,10 @@ func NewClient(cfg *Config) (cl *Client, err error) {
                        },
                }
        }
+       if cfg == nil {
+               cfg = &Config{}
+       }
+       cfg.setDefaults()
 
        defer func() {
                if err != nil {
@@ -239,7 +243,7 @@ func NewClient(cfg *Config) (cl *Client, err error) {
                }
        }()
        cl = &Client{
-               halfOpenLimit:     defaultHalfOpenConnsPerTorrent,
+               halfOpenLimit:     cfg.HalfOpenConnsPerTorrent,
                config:            *cfg,
                dopplegangerAddrs: make(map[string]struct{}),
                torrents:          make(map[metainfo.Hash]*Torrent),
@@ -280,7 +284,7 @@ func NewClient(cfg *Config) (cl *Client, err error) {
        if cfg.PeerID != "" {
                missinggo.CopyExact(&cl.peerID, cfg.PeerID)
        } else {
-               o := copy(cl.peerID[:], bep20)
+               o := copy(cl.peerID[:], cfg.Bep20)
                _, err = rand.Read(cl.peerID[o:])
                if err != nil {
                        panic("error generating peer id")
@@ -486,7 +490,7 @@ func countDialResult(err error) {
        }
 }
 
-func reducedDialTimeout(max time.Duration, halfOpenLimit int, pendingPeers int) (ret time.Duration) {
+func reducedDialTimeout(minDialTimeout, max time.Duration, halfOpenLimit int, pendingPeers int) (ret time.Duration) {
        ret = max / time.Duration((pendingPeers+halfOpenLimit)/halfOpenLimit)
        if ret < minDialTimeout {
                ret = minDialTimeout
@@ -604,7 +608,7 @@ func (cl *Client) handshakesConnection(ctx context.Context, nc net.Conn, t *Torr
        c = cl.newConnection(nc)
        c.headerEncrypted = encryptHeader
        c.uTP = utp
-       ctx, cancel := context.WithTimeout(ctx, handshakesTimeout)
+       ctx, cancel := context.WithTimeout(ctx, cl.config.HandshakesTimeout)
        defer cancel()
        dl, ok := ctx.Deadline()
        if !ok {
@@ -989,7 +993,7 @@ func (cl *Client) runInitiatedHandshookConn(c *connection, t *Torrent) {
 }
 
 func (cl *Client) runReceivedConn(c *connection) {
-       err := c.conn.SetDeadline(time.Now().Add(handshakesTimeout))
+       err := c.conn.SetDeadline(time.Now().Add(cl.config.HandshakesTimeout))
        if err != nil {
                panic(err)
        }
@@ -1046,7 +1050,7 @@ func (cl *Client) sendInitialMessages(conn *connection, torrent *Torrent) {
                                                }
                                                return
                                        }(),
-                                       "v": extendedHandshakeClientVersion,
+                                       "v": cl.config.ExtendedHandshakeClientVersion,
                                        // No upload queue is implemented yet.
                                        "reqq": 64,
                                }
@@ -1204,13 +1208,13 @@ func (cl *Client) newTorrent(ih metainfo.Hash, specStorage storage.ClientImpl) (
                cl:       cl,
                infoHash: ih,
                peers:    make(map[peersKey]Peer),
-               conns:    make(map[*connection]struct{}, 2*defaultEstablishedConnsPerTorrent),
+               conns:    make(map[*connection]struct{}, 2*cl.config.EstablishedConnsPerTorrent),
 
                halfOpen:          make(map[string]Peer),
                pieceStateChanges: pubsub.NewPubSub(),
 
                storageOpener:       storageClient,
-               maxEstablishedConns: defaultEstablishedConnsPerTorrent,
+               maxEstablishedConns: cl.config.EstablishedConnsPerTorrent,
 
                networkingEnabled: true,
                requestStrategy:   2,
index f589186c89dacc8e8ffc9b1426986067b598ae13..e06601f416011baa8beecc5d8bb102751bb6f9a6 100644 (file)
@@ -132,23 +132,25 @@ func TestUnmarshalPEXMsg(t *testing.T) {
 }
 
 func TestReducedDialTimeout(t *testing.T) {
+       cfg := &Config{}
+       cfg.setDefaults()
        for _, _case := range []struct {
                Max             time.Duration
                HalfOpenLimit   int
                PendingPeers    int
                ExpectedReduced time.Duration
        }{
-               {nominalDialTimeout, 40, 0, nominalDialTimeout},
-               {nominalDialTimeout, 40, 1, nominalDialTimeout},
-               {nominalDialTimeout, 40, 39, nominalDialTimeout},
-               {nominalDialTimeout, 40, 40, nominalDialTimeout / 2},
-               {nominalDialTimeout, 40, 80, nominalDialTimeout / 3},
-               {nominalDialTimeout, 40, 4000, nominalDialTimeout / 101},
+               {cfg.NominalDialTimeout, 40, 0, cfg.NominalDialTimeout},
+               {cfg.NominalDialTimeout, 40, 1, cfg.NominalDialTimeout},
+               {cfg.NominalDialTimeout, 40, 39, cfg.NominalDialTimeout},
+               {cfg.NominalDialTimeout, 40, 40, cfg.NominalDialTimeout / 2},
+               {cfg.NominalDialTimeout, 40, 80, cfg.NominalDialTimeout / 3},
+               {cfg.NominalDialTimeout, 40, 4000, cfg.NominalDialTimeout / 101},
        } {
-               reduced := reducedDialTimeout(_case.Max, _case.HalfOpenLimit, _case.PendingPeers)
+               reduced := reducedDialTimeout(cfg.MinDialTimeout, _case.Max, _case.HalfOpenLimit, _case.PendingPeers)
                expected := _case.ExpectedReduced
-               if expected < minDialTimeout {
-                       expected = minDialTimeout
+               if expected < cfg.MinDialTimeout {
+                       expected = cfg.MinDialTimeout
                }
                if reduced != expected {
                        t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
index 1e2f86a8f674bf3ec40c482cb8ee61cb7ee98c49..9c99a3b1a0a3f441f25e7ead23d127e4993aa60a 100644 (file)
@@ -39,7 +39,7 @@ func main() {
                ar.InfoHash = ts.InfoHash
                for _, tier := range ts.Trackers {
                        for _, tURI := range tier {
-                               resp, err := tracker.Announce(tURI, &ar)
+                               resp, err := tracker.Announce(torrent.DefaultHTTPClient, tURI, &ar)
                                if err != nil {
                                        log.Print(err)
                                        continue
index c0abb23ed908ff1607d44e6136826ec2945e9acd..e70c9b80a9cd7b7e6aefcbb4fa1fda08edab7340 100644 (file)
--- a/config.go
+++ b/config.go
@@ -6,8 +6,24 @@ import (
 
        "github.com/anacrolix/torrent/iplist"
        "github.com/anacrolix/torrent/storage"
+
+       "crypto/tls"
+       "net"
+       "net/http"
+       "time"
 )
 
+var DefaultHTTPClient = &http.Client{
+       Timeout: time.Second * 15,
+       Transport: &http.Transport{
+               Dial: (&net.Dialer{
+                       Timeout: 15 * time.Second,
+               }).Dial,
+               TLSHandshakeTimeout: 15 * time.Second,
+               TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
+       },
+}
+
 // Override Client defaults.
 type Config struct {
        // Store torrent file data in this directory unless TorrentDataOpener is
@@ -59,6 +75,62 @@ type Config struct {
        DisableIPv6 bool `long:"disable-ipv6"`
        // Perform logging and any other behaviour that will help debug.
        Debug bool `help:"enable debug logging"`
+
+       // HTTP client used to query the tracker endpoint. Default is DefaultHTTPClient
+       HTTP *http.Client
+       // Updated occasionally to when there's been some changes to client
+       // behaviour in case other clients are assuming anything of us. See also
+       // `bep20`.
+       ExtendedHandshakeClientVersion string // default  "go.torrent dev 20150624"
+       // Peer ID client identifier prefix. We'll update this occasionally to
+       // reflect changes to client behaviour that other clients may depend on.
+       // Also see `extendedHandshakeClientVersion`.
+       Bep20 string // default "-GT0001-"
+
+       NominalDialTimeout         time.Duration // default  time.Second * 30
+       MinDialTimeout             time.Duration // default  5 * time.Second
+       EstablishedConnsPerTorrent int           // default 80
+       HalfOpenConnsPerTorrent    int           // default  80
+       TorrentPeersHighWater      int           // default 200
+       TorrentPeersLowWater       int           // default 50
+
+       // Limit how long handshake can take. This is to reduce the lingering
+       // impact of a few bad apples. 4s loses 1% of successful handshakes that
+       // are obtained with 60s timeout, and 5% of unsuccessful handshakes.
+       HandshakesTimeout time.Duration // default  20 * time.Second
+}
+
+func (cfg *Config) setDefaults() {
+       if cfg.HTTP == nil {
+               cfg.HTTP = DefaultHTTPClient
+       }
+       if cfg.ExtendedHandshakeClientVersion == "" {
+               cfg.ExtendedHandshakeClientVersion = "go.torrent dev 20150624"
+       }
+       if cfg.Bep20 == "" {
+               cfg.Bep20 = "-GT0001-"
+       }
+       if cfg.NominalDialTimeout == 0 {
+               cfg.NominalDialTimeout = 30 * time.Second
+       }
+       if cfg.MinDialTimeout == 0 {
+               cfg.MinDialTimeout = 5 * time.Second
+       }
+       if cfg.EstablishedConnsPerTorrent == 0 {
+               cfg.EstablishedConnsPerTorrent = 80
+       }
+       if cfg.HalfOpenConnsPerTorrent == 0 {
+               cfg.HalfOpenConnsPerTorrent = 80
+       }
+       if cfg.TorrentPeersHighWater == 0 {
+               cfg.TorrentPeersHighWater = 200
+       }
+       if cfg.TorrentPeersLowWater == 0 {
+               cfg.TorrentPeersLowWater = 50
+       }
+       if cfg.HandshakesTimeout == 0 {
+               cfg.HandshakesTimeout = 20 * time.Second
+       }
 }
 
 type EncryptionPolicy struct {
index ed06fccc223de234cee03898ae4abe319333d537..ffc30f493348c455c360187964c935d66a0899b8 100644 (file)
--- a/global.go
+++ b/global.go
@@ -3,7 +3,6 @@ package torrent
 import (
        "crypto"
        "expvar"
-       "time"
 )
 
 const (
@@ -11,18 +10,6 @@ const (
        maxRequests      = 250    // Maximum pending requests we allow peers to send us.
        defaultChunkSize = 0x4000 // 16KiB
 
-       // Updated occasionally to when there's been some changes to client
-       // behaviour in case other clients are assuming anything of us. See also
-       // `bep20`.
-       extendedHandshakeClientVersion = "go.torrent dev 20150624"
-       // Peer ID client identifier prefix. We'll update this occasionally to
-       // reflect changes to client behaviour that other clients may depend on.
-       // Also see `extendedHandshakeClientVersion`.
-       bep20 = "-GT0001-"
-
-       nominalDialTimeout = time.Second * 30
-       minDialTimeout     = 5 * time.Second
-
        // Justification for set bits follows.
        //
        // Extension protocol ([5]|=0x10):
@@ -36,16 +23,6 @@ const (
        // http://www.bittorrent.org/beps/bep_0005.html
        defaultExtensionBytes = "\x00\x00\x00\x00\x00\x10\x00\x01"
 
-       defaultEstablishedConnsPerTorrent = 80
-       defaultHalfOpenConnsPerTorrent    = 80
-       torrentPeersHighWater             = 200
-       torrentPeersLowWater              = 50
-
-       // Limit how long handshake can take. This is to reduce the lingering
-       // impact of a few bad apples. 4s loses 1% of successful handshakes that
-       // are obtained with 60s timeout, and 5% of unsuccessful handshakes.
-       handshakesTimeout = 20 * time.Second
-
        // These are our extended message IDs. Peers will use these values to
        // select which extension a message is intended for.
        metadataExtendedId = iota + 1 // 0 is reserved for deleting keys
index 0060cff13c62561711d17e6d5746af4bae037f79..926e6316fabb155c914137caa04ea9ab275870aa 100644 (file)
@@ -223,7 +223,7 @@ func (t *Torrent) unclosedConnsAsSlice() (ret []*connection) {
 func (t *Torrent) addPeer(p Peer) {
        cl := t.cl
        cl.openNewConns(t)
-       if len(t.peers) >= torrentPeersHighWater {
+       if len(t.peers) >= cl.config.TorrentPeersHighWater {
                return
        }
        key := peersKey{string(p.IP), p.Port}
@@ -1151,7 +1151,7 @@ func (t *Torrent) wantPeers() bool {
        if t.closed.IsSet() {
                return false
        }
-       if len(t.peers) > torrentPeersLowWater {
+       if len(t.peers) > t.cl.config.TorrentPeersLowWater {
                return false
        }
        return t.needData() || t.seeding()
@@ -1261,7 +1261,7 @@ func (t *Torrent) consumeDHTAnnounce(pvs <-chan dht.PeersValues) {
                        t.addPeers(addPeers)
                        numPeers := len(t.peers)
                        cl.mu.Unlock()
-                       if numPeers >= torrentPeersHighWater {
+                       if numPeers >= cl.config.TorrentPeersHighWater {
                                return
                        }
                case <-t.closed.LockedChan(&cl.mu):
index 4ca80f8fa81cd599fc4dcaa222ed46a52f2682a5..b0ab3f95bee9f7985842992da703456d05aaaa64 100644 (file)
@@ -2,15 +2,12 @@ package tracker
 
 import (
        "bytes"
-       "crypto/tls"
        "errors"
        "fmt"
        "io"
-       "net"
        "net/http"
        "net/url"
        "strconv"
-       "time"
 
        "github.com/anacrolix/missinggo/httptoo"
 
@@ -27,17 +24,6 @@ type httpResponse struct {
        Peers         interface{} `bencode:"peers"`
 }
 
-var netClient = &http.Client{
-       Timeout: time.Second * 15,
-       Transport: &http.Transport{
-               Dial: (&net.Dialer{
-                       Timeout: 15 * time.Second,
-               }).Dial,
-               TLSHandshakeTimeout: 15 * time.Second,
-               TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
-       },
-}
-
 func (r *httpResponse) UnmarshalPeers() (ret []Peer, err error) {
        switch v := r.Peers.(type) {
        case string:
@@ -87,12 +73,12 @@ func setAnnounceParams(_url *url.URL, ar *AnnounceRequest) {
        _url.RawQuery = q.Encode()
 }
 
-func announceHTTP(ar *AnnounceRequest, _url *url.URL, host string) (ret AnnounceResponse, err error) {
+func announceHTTP(cl *http.Client, ar *AnnounceRequest, _url *url.URL, host string) (ret AnnounceResponse, err error) {
        _url = httptoo.CopyURL(_url)
        setAnnounceParams(_url, ar)
        req, err := http.NewRequest("GET", _url.String(), nil)
        req.Host = host
-       resp, err := netClient.Do(req)
+       resp, err := cl.Do(req)
        if err != nil {
                return
        }
index 9f14041e72e236c5eb8af1cfbd2c2992f65d7f05..4ed76f11f4115ccb39965171fc2aecf33c7b2740 100644 (file)
@@ -3,6 +3,7 @@ package tracker
 import (
        "errors"
        "net"
+       "net/http"
        "net/url"
 )
 
@@ -53,18 +54,18 @@ var (
        ErrBadScheme = errors.New("unknown scheme")
 )
 
-func Announce(urlStr string, req *AnnounceRequest) (res AnnounceResponse, err error) {
-       return AnnounceHost(urlStr, req, "")
+func Announce(cl *http.Client, urlStr string, req *AnnounceRequest) (res AnnounceResponse, err error) {
+       return AnnounceHost(cl, urlStr, req, "")
 }
 
-func AnnounceHost(urlStr string, req *AnnounceRequest, host string) (res AnnounceResponse, err error) {
+func AnnounceHost(cl *http.Client, urlStr string, req *AnnounceRequest, host string) (res AnnounceResponse, err error) {
        _url, err := url.Parse(urlStr)
        if err != nil {
                return
        }
        switch _url.Scheme {
        case "http", "https":
-               return announceHTTP(req, _url, host)
+               return announceHTTP(cl, req, _url, host)
        case "udp":
                return announceUDP(req, _url)
        default:
index d095af5a73b5215c242aadbd0bcea81557f1a36b..bf397e9d0af90231594a637b7be08a1e94b9c5a5 100644 (file)
@@ -1,12 +1,27 @@
 package tracker
 
 import (
+       "crypto/tls"
+       "net"
+       "net/http"
        "testing"
+       "time"
 )
 
+var defaultClient = &http.Client{
+       Timeout: time.Second * 15,
+       Transport: &http.Transport{
+               Dial: (&net.Dialer{
+                       Timeout: 15 * time.Second,
+               }).Dial,
+               TLSHandshakeTimeout: 15 * time.Second,
+               TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
+       },
+}
+
 func TestUnsupportedTrackerScheme(t *testing.T) {
        t.Parallel()
-       _, err := Announce("lol://tracker.openbittorrent.com:80/announce", nil)
+       _, err := Announce(defaultClient, "lol://tracker.openbittorrent.com:80/announce", nil)
        if err != ErrBadScheme {
                t.Fatal(err)
        }
index 55946aae220dc1fe2cb7408f339c3e7db74a87a6..78434d328dd73e40d19ffbf4f54242920f3b3da7 100644 (file)
@@ -114,7 +114,7 @@ func TestAnnounceLocalhost(t *testing.T) {
        go func() {
                require.NoError(t, srv.serveOne())
        }()
-       ar, err := Announce(fmt.Sprintf("udp://%s/announce", srv.pc.LocalAddr().String()), &req)
+       ar, err := Announce(defaultClient, fmt.Sprintf("udp://%s/announce", srv.pc.LocalAddr().String()), &req)
        require.NoError(t, err)
        assert.EqualValues(t, 1, ar.Seeders)
        assert.EqualValues(t, 2, len(ar.Peers))
@@ -130,7 +130,7 @@ func TestUDPTracker(t *testing.T) {
        }
        rand.Read(req.PeerId[:])
        copy(req.InfoHash[:], []uint8{0xa3, 0x56, 0x41, 0x43, 0x74, 0x23, 0xe6, 0x26, 0xd9, 0x38, 0x25, 0x4a, 0x6b, 0x80, 0x49, 0x10, 0xa6, 0x67, 0xa, 0xc1})
-       ar, err := Announce("udp://tracker.openbittorrent.com:80/announce", &req)
+       ar, err := Announce(defaultClient, "udp://tracker.openbittorrent.com:80/announce", &req)
        // Skip any net errors as we don't control the server.
        if _, ok := err.(net.Error); ok {
                t.Skip(err)
@@ -166,7 +166,7 @@ func TestAnnounceRandomInfoHashThirdParty(t *testing.T) {
                wg.Add(1)
                go func(url string) {
                        defer wg.Done()
-                       resp, err := Announce(url, &req)
+                       resp, err := Announce(defaultClient, url, &req)
                        if err != nil {
                                t.Logf("error announcing to %s: %s", url, err)
                                return
@@ -202,7 +202,7 @@ func TestURLPathOption(t *testing.T) {
        }
        defer conn.Close()
        go func() {
-               _, err := Announce((&url.URL{
+               _, err := Announce(defaultClient, (&url.URL{
                        Scheme: "udp",
                        Host:   conn.LocalAddr().String(),
                        Path:   "/announce",
index 3463133c931ea6d4982bf1aeb15f5629eeb8119d..a090bc1d7603a7b57b1315d76a5135404a01f84b 100644 (file)
@@ -85,7 +85,7 @@ func (me *trackerScraper) announce() (ret trackerAnnounceResult) {
        me.t.cl.mu.Lock()
        req := me.t.announceRequest()
        me.t.cl.mu.Unlock()
-       res, err := tracker.AnnounceHost(urlToUse, &req, host)
+       res, err := tracker.AnnounceHost(me.t.cl.config.HTTP, urlToUse, &req, host)
        if err != nil {
                ret.Err = err
                return