client.go | 5 +++-- client_test.go | 14 ++++++-------- cmd/torrent/main.go | 2 +- cmd/torrentfs/main.go | 7 ++++--- config.go | 13 ++++++++++++- fs/torrentfs_test.go | 12 +++++------- listen.go | 10 ++++++++++ socket.go | 31 ++++++++++++++++++------------- torrent.go | 10 +++++++++- diff --git a/client.go b/client.go index 43c79f5c958874e8baebfe1c01270963e2ce78e7..eec8ad6338bcc18c92d8a0dbfc05b80485b5c118 100644 --- a/client.go +++ b/client.go @@ -104,7 +104,7 @@ func writeDhtServerStatus(w io.Writer, s *dht.Server) { dhtStats := s.Stats() fmt.Fprintf(w, "\t# Nodes: %d (%d good, %d banned)\n", dhtStats.Nodes, dhtStats.GoodNodes, dhtStats.BadNodes) fmt.Fprintf(w, "\tServer ID: %x\n", s.ID()) - fmt.Fprintf(w, "\tAnnounces: %d\n", dhtStats.ConfirmedAnnounces) + fmt.Fprintf(w, "\tAnnounces: %d\n", dhtStats.SuccessfulOutboundAnnouncePeerQueries) fmt.Fprintf(w, "\tOutstanding transactions: %d\n", dhtStats.OutstandingTransactions) } @@ -224,10 +224,11 @@ panic("error generating peer id") } } - cl.conns, err = listenAll(cl.enabledPeerNetworks(), cl.config.ListenAddr) + cl.conns, err = listenAll(cl.enabledPeerNetworks(), cl.config.ListenHost, cl.config.ListenPort) if err != nil { return } + // Check for panics. cl.LocalPort() for _, s := range cl.conns { diff --git a/client_test.go b/client_test.go index abeb953aafaecf4cbf6a1acdf17b3b08e173bd78..1b07f9c5abe69b02944ae27ed95f4043e14a02f1 100644 --- a/client_test.go +++ b/client_test.go @@ -31,7 +31,7 @@ ) func TestingConfig() *Config { return &Config{ - ListenAddr: "localhost:0", + ListenHost: LoopbackListenHost, NoDHT: true, DataDir: tempDir(), DisableTrackers: true, @@ -540,7 +540,7 @@ func BenchmarkAddLargeTorrent(b *testing.B) { cfg := TestingConfig() cfg.DisableTCP = true cfg.DisableUTP = true - cfg.ListenAddr = "redonk" + cfg.ListenHost = func(string) string { return "redonk" } cl, err := NewClient(cfg) require.NoError(b, err) defer cl.Close() @@ -756,7 +756,7 @@ } func TestAddMetainfoWithNodes(t *testing.T) { cfg := TestingConfig() - cfg.ListenAddr = ":0" + cfg.ListenHost = func(string) string { return "" } cfg.NoDHT = false cfg.DhtStartingNodes = func() ([]dht.Addr, error) { return nil, nil } // For now, we want to just jam the nodes into the table, without @@ -766,10 +766,9 @@ // cfg.DHTConfig.NoSecurity = true cl, err := NewClient(cfg) require.NoError(t, err) defer cl.Close() - sum := func() (ret int) { + sum := func() (ret int64) { cl.eachDhtServer(func(s *dht.Server) { - ret += s.NumNodes() - ret += s.Stats().OutstandingTransactions + ret += s.Stats().OutboundQueriesAttempted }) return } @@ -1065,8 +1064,7 @@ s, _ := NewUtpSocket("udp", ":50007") if s != nil { defer s.Close() } - cfg := TestingConfig() - cfg.ListenAddr = ":50007" + cfg := TestingConfig().SetListenAddr(":50007") cl, err := NewClient(cfg) require.Error(t, err) require.Nil(t, cl) diff --git a/cmd/torrent/main.go b/cmd/torrent/main.go index 52547590c9aab3280db4b320bcc443919ef1a876..2421412b73fa2a1294d9a6c64f74ba4198d4a48c 100644 --- a/cmd/torrent/main.go +++ b/cmd/torrent/main.go @@ -162,7 +162,7 @@ if flags.Mmap { clientConfig.DefaultStorage = storage.NewMMap("") } if flags.Addr != nil { - clientConfig.ListenAddr = flags.Addr.String() + clientConfig.SetListenAddr(flags.Addr.String()) } if flags.UploadRate != -1 { clientConfig.UploadRateLimiter = rate.NewLimiter(rate.Limit(flags.UploadRate), 256<<10) diff --git a/cmd/torrentfs/main.go b/cmd/torrentfs/main.go index 4d9119da970347bb8e3bf772eeaa552672590bab..3cc778124e11bf7575ede99c00071adf2f6ece3e 100644 --- a/cmd/torrentfs/main.go +++ b/cmd/torrentfs/main.go @@ -86,12 +86,13 @@ } defer fuse.Unmount(args.MountDir) // TODO: Think about the ramifications of exiting not due to a signal. defer conn.Close() - client, err := torrent.NewClient(&torrent.Config{ + cfg := torrent.Config{ DataDir: args.DownloadDir, DisableTrackers: args.DisableTrackers, - ListenAddr: args.ListenAddr.String(), NoUpload: true, // Ensure that downloads are responsive. - }) + } + cfg.SetListenAddr(args.ListenAddr.String()) + client, err := torrent.NewClient(&cfg) if err != nil { log.Print(err) return 1 diff --git a/config.go b/config.go index c2aec9e36ba7299d76f1df64bc1e728709cb89d2..8b679c15c20a0709e2c665378d2c4d3662518a22 100644 --- a/config.go +++ b/config.go @@ -9,6 +9,8 @@ "golang.org/x/time/rate" "github.com/anacrolix/dht" + "github.com/anacrolix/missinggo" + "github.com/anacrolix/missinggo/expect" "github.com/anacrolix/torrent/iplist" "github.com/anacrolix/torrent/storage" ) @@ -33,7 +35,8 @@ DataDir string `long:"data-dir" description:"directory to store downloaded torrent data"` // The address to listen for new uTP and TCP bittorrent protocol // connections. DHT shares a UDP socket with uTP unless configured // otherwise. - ListenAddr string `long:"listen-addr" value-name:"HOST:PORT"` + ListenHost func(network string) string + ListenPort int NoDefaultPortForwarding bool // Don't announce to trackers. This only leaves DHT to discover peers. DisableTrackers bool `long:"disable-trackers"` @@ -110,6 +113,14 @@ HandshakesTimeout time.Duration // default 20 * time.Second PublicIp4 net.IP PublicIp6 net.IP +} + +func (cfg *Config) SetListenAddr(addr string) *Config { + host, port, err := missinggo.ParseHostPort(addr) + expect.Nil(err) + cfg.ListenHost = func(string) string { return host } + cfg.ListenPort = port + return cfg } func (cfg *Config) setDefaults() { diff --git a/fs/torrentfs_test.go b/fs/torrentfs_test.go index 164a8feca23d3ed178672bf6731e4be06b08b512..c8cacc5d37b105d378a6907cb7145cc272e92053 100644 --- a/fs/torrentfs_test.go +++ b/fs/torrentfs_test.go @@ -165,13 +165,14 @@ func TestDownloadOnDemand(t *testing.T) { layout, err := newGreetingLayout() require.NoError(t, err) defer layout.Destroy() - seeder, err := torrent.NewClient(&torrent.Config{ + cfg := torrent.Config{ DataDir: layout.Completed, DisableTrackers: true, NoDHT: true, - ListenAddr: "localhost:0", Seed: true, - }) + ListenHost: torrent.LoopbackListenHost, + } + seeder, err := torrent.NewClient(&cfg) require.NoError(t, err) defer seeder.Close() testutil.ExportStatusWriter(seeder, "s") @@ -187,12 +188,9 @@ }() leecher, err := torrent.NewClient(&torrent.Config{ DisableTrackers: true, NoDHT: true, - ListenAddr: "localhost:0", DisableTCP: true, DefaultStorage: storage.NewMMap(filepath.Join(layout.BaseDir, "download")), - // This can be used to check if clients can connect to other clients - // with the same ID. - // PeerID: seeder.PeerID(), + ListenHost: torrent.LoopbackListenHost, }) require.NoError(t, err) testutil.ExportStatusWriter(leecher, "l") diff --git a/listen.go b/listen.go index 68c4b7685533136d39db23b744de73d8ad14821a..0e1b5c0d309b88e30498007673f2590f68f77614 100644 --- a/listen.go +++ b/listen.go @@ -1,5 +1,7 @@ package torrent +import "strings" + type peerNetworks struct { tcp4, tcp6 bool utp4, utp6 bool @@ -15,3 +17,11 @@ } } return nil } + +func LoopbackListenHost(network string) string { + if strings.Contains(network, "4") { + return "127.0.0.1" + } else { + return "::1" + } +} diff --git a/socket.go b/socket.go index 7c14e3fd23deb25a7d845790d8787a868e1e178d..fb70b280f5ff7222c728c463ef7ba11de340147a 100644 --- a/socket.go +++ b/socket.go @@ -61,26 +61,31 @@ } return net.JoinHostPort(host, strconv.FormatInt(int64(port), 10)) } -func listenAll(networks []string, addr string) ([]socket, error) { +func listenAll(networks []string, getHost func(string) string, port int) ([]socket, error) { if len(networks) == 0 { return nil, nil } + var nahs []networkAndHost + for _, n := range networks { + nahs = append(nahs, networkAndHost{n, getHost(n)}) + } for { - ss, retry, err := listenAllRetry(networks, addr) + ss, retry, err := listenAllRetry(nahs, port) if !retry { return ss, err } } } -func listenAllRetry(networks []string, addr string) (ss []socket, retry bool, err error) { - _, port, err := missinggo.ParseHostPort(addr) - if err != nil { - err = fmt.Errorf("error parsing addr: %s", err) - return - } - ss = make([]socket, 1, len(networks)) - ss[0], err = listen(networks[0], addr) +type networkAndHost struct { + Network string + Host string +} + +func listenAllRetry(nahs []networkAndHost, port int) (ss []socket, retry bool, err error) { + ss = make([]socket, 1, len(nahs)) + portStr := strconv.FormatInt(int64(port), 10) + ss[0], err = listen(nahs[0].Network, net.JoinHostPort(nahs[0].Host, portStr)) if err != nil { return nil, false, fmt.Errorf("first listen: %s", err) } @@ -92,9 +97,9 @@ } ss = nil } }() - restAddr := setPort(addr, missinggo.AddrPort(ss[0].Addr())) - for _, n := range networks[1:] { - s, err := listen(n, restAddr) + portStr = strconv.FormatInt(int64(missinggo.AddrPort(ss[0].Addr())), 10) + for _, nah := range nahs[1:] { + s, err := listen(nah.Network, net.JoinHostPort(nah.Host, portStr)) if err != nil { return ss, missinggo.IsAddrInUse(err) && port == 0, diff --git a/torrent.go b/torrent.go index 83b6546b7a3f4f168f695a419959069c0a886e8d..bc3f2a082c1717ff33a1dde90e17cd5bb702635a 100644 --- a/torrent.go +++ b/torrent.go @@ -1325,14 +1325,22 @@ // Returns an AnnounceRequest with fields filled out to defaults and current // values. func (t *Torrent) announceRequest() tracker.AnnounceRequest { + // Note that IPAddress is not set. It's set for UDP inside the tracker + // code, since it's dependent on the network in use. return tracker.AnnounceRequest{ Event: tracker.None, NumWant: -1, Port: uint16(t.cl.incomingPeerPort()), PeerId: t.cl.peerID, InfoHash: t.infoHash, - Left: t.bytesLeftAnnounce(), Key: t.cl.announceKey(), + + // The following are vaguely described in BEP 3. + + Left: t.bytesLeftAnnounce(), + Uploaded: t.stats.BytesWrittenData, + // There's no mention of wasted or unwanted download in the BEP. + Downloaded: t.stats.BytesReadUsefulData, } }