},
}
}
+ if cfg == nil {
+ cfg = &Config{}
+ }
+ cfg.setDefaults()
defer func() {
if err != nil {
}
}()
cl = &Client{
- halfOpenLimit: defaultHalfOpenConnsPerTorrent,
+ halfOpenLimit: cfg.HalfOpenConnsPerTorrent,
config: *cfg,
dopplegangerAddrs: make(map[string]struct{}),
torrents: make(map[metainfo.Hash]*Torrent),
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")
}
}
-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
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 {
}
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)
}
}
return
}(),
- "v": extendedHandshakeClientVersion,
+ "v": cl.config.ExtendedHandshakeClientVersion,
// No upload queue is implemented yet.
"reqq": 64,
}
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,
}
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)
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
"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
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 {
import (
"crypto"
"expvar"
- "time"
)
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):
// 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
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}
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()
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):
import (
"bytes"
- "crypto/tls"
"errors"
"fmt"
"io"
- "net"
"net/http"
"net/url"
"strconv"
- "time"
"github.com/anacrolix/missinggo/httptoo"
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:
_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
}
import (
"errors"
"net"
+ "net/http"
"net/url"
)
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:
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)
}
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))
}
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)
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
}
defer conn.Close()
go func() {
- _, err := Announce((&url.URL{
+ _, err := Announce(defaultClient, (&url.URL{
Scheme: "udp",
Host: conn.LocalAddr().String(),
Path: "/announce",
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