Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions bep27_private_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// BEP 27 private-torrent semantics.
// http://www.bittorrent.org/beps/bep_0027.html
package torrent

import (
"testing"

"github.com/anacrolix/torrent/metainfo"
)

// makeTorrentWithPrivate is a focused helper for the BEP 27 tests below.
// It builds a *Torrent shell carrying just the info needed by isPrivate(),
// without touching the network or storage. The Torrent is not registered
// with a Client and must not be used for any other operations.
func makeTorrentWithPrivate(p *bool) *Torrent {
return &Torrent{
info: &metainfo.Info{Private: p},
}
}

func TestIsPrivate_NoInfo(t *testing.T) {
// Before metadata is loaded (e.g. magnet still fetching), isPrivate
// returns false. The downstream loops re-check on every iteration,
// so once info arrives the privacy flag takes effect immediately.
tor := &Torrent{}
if tor.isPrivate() {
t.Fatal("torrent with no info should not report private")
}
}

func TestIsPrivate_FlagAbsent(t *testing.T) {
// Most public torrents omit the field entirely (Info.Private == nil).
tor := makeTorrentWithPrivate(nil)
if tor.isPrivate() {
t.Fatal("info without private field should not report private")
}
}

func TestIsPrivate_FlagFalse(t *testing.T) {
// Explicit `private=0` must behave the same as omitted.
f := false
tor := makeTorrentWithPrivate(&f)
if tor.isPrivate() {
t.Fatal("private=0 should not report private")
}
}

func TestIsPrivate_FlagTrue(t *testing.T) {
// The actual private case.
tt := true
tor := makeTorrentWithPrivate(&tt)
if !tor.isPrivate() {
t.Fatal("private=1 should report private")
}
}
18 changes: 14 additions & 4 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,15 @@ func (cl *Client) LocalPort() (port int) {

// OnLPDAnnouncement implements lpdClient. It adds addr to any torrent matching
// an announced infohash, and also to all other active torrents (LPD is the
// only source of local IPs).
// only source of local IPs). Private torrents (BEP 27) are skipped on both
// paths — they must not receive peers via local discovery.
func (cl *Client) OnLPDAnnouncement(addr string, infohashes []string) {
announced := make(map[*Torrent]struct{}, len(infohashes))
for _, ih := range infohashes {
if t, ok := cl.Torrent(metainfo.NewHashFromHex(ih)); ok {
if t.isPrivate() {
continue
}
lpdPeer(t, addr)
announced[t] = struct{}{}
}
Expand All @@ -182,7 +186,7 @@ func (cl *Client) OnLPDAnnouncement(addr string, infohashes []string) {
cl.rLock()
var rest []*Torrent
for t := range cl.torrents {
if _, ok := announced[t]; !ok {
if _, ok := announced[t]; !ok && !t.isPrivate() {
rest = append(rest, t)
}
}
Expand All @@ -194,12 +198,17 @@ func (cl *Client) OnLPDAnnouncement(addr string, infohashes []string) {
}

// TorrentInfohashesAndPort implements lpdClient. It returns a snapshot of
// active torrent infohash hex strings and the listen port.
// active torrent infohash hex strings and the listen port. Private torrents
// (BEP 27) are excluded — they must not be announced via Local Peer
// Discovery (BEP 14).
func (cl *Client) TorrentInfohashesAndPort() (port int, infohashes []string) {
cl.rLock()
defer cl.rUnlock()
port = cl.LocalPort()
for t := range cl.torrents {
if t.isPrivate() {
continue
}
infohashes = append(infohashes, t.InfoHash().HexString())
}
return
Expand Down Expand Up @@ -1583,7 +1592,8 @@ func (cl *Client) AddTorrentOpt(opts AddTorrentOpts) (t *Torrent, new bool) {

cl.unlock()

if cl.lpd != nil {
// BEP 27: private torrents must not receive or announce via Local Peer Discovery.
if cl.lpd != nil && !t.isPrivate() {
cl.lpd.lpdPeers(t)
cl.lpd.lpdForce()
}
Expand Down
4 changes: 4 additions & 0 deletions pexconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func (s *pexConnState) Init(c *PeerConn) {
if !ok || xid == 0 || c.t.cl.config.DisablePEX {
return
}
// BEP 27: private torrents must not exchange peers via PEX.
if c.t.isPrivate() {
return
}
s.xid = xid
s.last = nil
s.torrent = c.t
Expand Down
18 changes: 18 additions & 0 deletions torrent.go
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,19 @@ func (t *Torrent) haveInfo() bool {
return t.info != nil
}

// isPrivate reports whether the torrent's metainfo carries BEP 27's
// `private=1` flag. Private torrents must not be announced via DHT, must
// not participate in PEX, and must not be discovered or announced via
// Local Peer Discovery (BEP 14). Returns false when info has not yet been
// loaded (e.g. magnet that hasn't fetched metadata yet) — callers that need
// to be conservative should also check haveInfo().
func (t *Torrent) isPrivate() bool {
if t.info == nil {
return false
}
return t.info.Private != nil && *t.info.Private
}

// Returns a run-time generated MetaInfo that includes the info bytes and
// announce-list as currently known to the client.
func (t *Torrent) newMetaInfo() metainfo.MetaInfo {
Expand Down Expand Up @@ -2387,6 +2400,11 @@ func (t *Torrent) dhtAnnouncer(s DhtServer) {
if t.closed.IsSet() {
return
}
// BEP 27: private torrents must not be announced via DHT. We re-check
// every loop because info may be loaded later (e.g. via magnet).
if t.isPrivate() {
goto wait
}
// We're also announcing ourselves as a listener, so we don't just want peer addresses.
// TODO: We can include the announce_peer step depending on whether we can receive
// inbound connections. We should probably only announce once every 15 mins too.
Expand Down
Loading