2
0
mirror of synced 2025-02-24 06:38:14 +00:00

Tidy up the torrent and DHT APIs

This commit is contained in:
Matt Joiner 2014-08-21 18:07:06 +10:00
parent 96e44f8950
commit 40fd1d647c
10 changed files with 238 additions and 243 deletions

124
client.go
View File

@ -16,8 +16,6 @@ Simple example:
package torrent package torrent
import ( import (
"bitbucket.org/anacrolix/go.torrent/dht"
"bitbucket.org/anacrolix/go.torrent/util"
"bufio" "bufio"
"crypto/rand" "crypto/rand"
"crypto/sha1" "crypto/sha1"
@ -32,6 +30,9 @@ import (
"syscall" "syscall"
"time" "time"
"bitbucket.org/anacrolix/go.torrent/dht"
. "bitbucket.org/anacrolix/go.torrent/util"
"github.com/anacrolix/libtorgo/metainfo" "github.com/anacrolix/libtorgo/metainfo"
"github.com/nsf/libtorgo/bencode" "github.com/nsf/libtorgo/bencode"
@ -63,7 +64,7 @@ func (me *Client) PrioritizeDataRegion(ih InfoHash, off, len_ int64) error {
if !t.haveInfo() { if !t.haveInfo() {
return errors.New("missing metadata") return errors.New("missing metadata")
} }
me.DownloadStrategy.TorrentPrioritize(t, off, len_) me.downloadStrategy.TorrentPrioritize(t, off, len_)
for _, cn := range t.Conns { for _, cn := range t.Conns {
me.replenishConnRequests(t, cn) me.replenishConnRequests(t, cn)
} }
@ -76,13 +77,13 @@ type dataSpec struct {
} }
type Client struct { type Client struct {
DataDir string dataDir string
HalfOpenLimit int halfOpenLimit int
PeerId [20]byte peerID [20]byte
Listener net.Listener listener net.Listener
DisableTrackers bool disableTrackers bool
DownloadStrategy DownloadStrategy downloadStrategy DownloadStrategy
DHT *dht.Server dHT *dht.Server
mu sync.Mutex mu sync.Mutex
event sync.Cond event sync.Cond
@ -93,18 +94,23 @@ type Client struct {
dataWaiter chan struct{} dataWaiter chan struct{}
} }
func (me *Client) ListenAddr() net.Addr {
return me.listener.Addr()
}
func (cl *Client) WriteStatus(w io.Writer) { func (cl *Client) WriteStatus(w io.Writer) {
cl.mu.Lock() cl.mu.Lock()
defer cl.mu.Unlock() defer cl.mu.Unlock()
if cl.Listener != nil { if cl.listener != nil {
fmt.Fprintf(w, "Listening on %s\n", cl.Listener.Addr()) fmt.Fprintf(w, "Listening on %s\n", cl.listener.Addr())
} else { } else {
fmt.Fprintf(w, "No listening torrent port!\n") fmt.Fprintf(w, "No listening torrent port!\n")
} }
fmt.Fprintf(w, "Peer ID: %q\n", cl.PeerId) fmt.Fprintf(w, "Peer ID: %q\n", cl.peerID)
fmt.Fprintf(w, "Half open outgoing connections: %d\n", cl.halfOpen) fmt.Fprintf(w, "Half open outgoing connections: %d\n", cl.halfOpen)
if cl.DHT != nil { if cl.dHT != nil {
fmt.Fprintf(w, "DHT nodes: %d\n", cl.DHT.NumNodes()) fmt.Fprintf(w, "DHT nodes: %d\n", cl.dHT.NumNodes())
fmt.Fprintf(w, "DHT Server ID: %x\n", cl.dHT.IDString())
} }
fmt.Fprintln(w) fmt.Fprintln(w)
for _, t := range cl.torrents { for _, t := range cl.torrents {
@ -164,26 +170,50 @@ func (cl *Client) TorrentReadAt(ih InfoHash, off int64, p []byte) (n int, err er
return t.Data.ReadAt(p, off) return t.Data.ReadAt(p, off)
} }
// Starts the client. Defaults are applied. The client will begin accepting func NewClient(cfg *Config) (cl *Client, err error) {
// connections and tracking. if cfg == nil {
func (c *Client) Start() { cfg = &Config{}
c.event.L = &c.mu
c.torrents = make(map[InfoHash]*torrent)
if c.HalfOpenLimit == 0 {
c.HalfOpenLimit = 10
} }
o := copy(c.PeerId[:], BEP20)
_, err := rand.Read(c.PeerId[o:]) cl = &Client{
disableTrackers: cfg.DisableTrackers,
downloadStrategy: cfg.DownloadStrategy,
halfOpenLimit: 100,
dataDir: cfg.DataDir,
quit: make(chan struct{}),
torrents: make(map[InfoHash]*torrent),
}
cl.event.L = &cl.mu
o := copy(cl.peerID[:], BEP20)
_, err = rand.Read(cl.peerID[o:])
if err != nil { if err != nil {
panic("error generating peer id") panic("error generating peer id")
} }
c.quit = make(chan struct{})
if c.DownloadStrategy == nil { if cl.downloadStrategy == nil {
c.DownloadStrategy = &DefaultDownloadStrategy{} cl.downloadStrategy = &DefaultDownloadStrategy{}
} }
if c.Listener != nil {
go c.acceptConnections() cl.listener, err = net.Listen("tcp", cfg.ListenAddr)
if err != nil {
return
} }
if cl.listener != nil {
go cl.acceptConnections()
}
if !cfg.NoDHT {
cl.dHT, err = dht.NewServer(&dht.ServerConfig{
Addr: cfg.ListenAddr,
})
if err != nil {
return
}
}
return
} }
func (cl *Client) stopped() bool { func (cl *Client) stopped() bool {
@ -211,7 +241,7 @@ func (me *Client) Stop() {
func (cl *Client) acceptConnections() { func (cl *Client) acceptConnections() {
for { for {
conn, err := cl.Listener.Accept() conn, err := cl.listener.Accept()
select { select {
case <-cl.quit: case <-cl.quit:
if conn != nil { if conn != nil {
@ -245,7 +275,7 @@ func (me *Client) torrent(ih InfoHash) *torrent {
// Start the process of connecting to the given peer for the given torrent if // Start the process of connecting to the given peer for the given torrent if
// appropriate. // appropriate.
func (me *Client) initiateConn(peer Peer, torrent *torrent) { func (me *Client) initiateConn(peer Peer, torrent *torrent) {
if peer.Id == me.PeerId { if peer.Id == me.peerID {
return return
} }
me.halfOpen++ me.halfOpen++
@ -291,10 +321,10 @@ func (me *Client) initiateConn(peer Peer, torrent *torrent) {
} }
func (cl *Client) incomingPeerPort() int { func (cl *Client) incomingPeerPort() int {
if cl.Listener == nil { if cl.listener == nil {
return 0 return 0
} }
_, p, err := net.SplitHostPort(cl.Listener.Addr().String()) _, p, err := net.SplitHostPort(cl.listener.Addr().String())
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -452,7 +482,7 @@ func (me *Client) peerUnchoked(torrent *torrent, conn *connection) {
func (cl *Client) connCancel(t *torrent, cn *connection, r request) (ok bool) { func (cl *Client) connCancel(t *torrent, cn *connection, r request) (ok bool) {
ok = cn.Cancel(r) ok = cn.Cancel(r)
if ok { if ok {
cl.DownloadStrategy.DeleteRequest(t, r) cl.downloadStrategy.DeleteRequest(t, r)
} }
return return
} }
@ -461,7 +491,7 @@ func (cl *Client) connDeleteRequest(t *torrent, cn *connection, r request) {
if !cn.RequestPending(r) { if !cn.RequestPending(r) {
return return
} }
cl.DownloadStrategy.DeleteRequest(t, r) cl.downloadStrategy.DeleteRequest(t, r)
delete(cn.Requests, r) delete(cn.Requests, r)
} }
@ -788,7 +818,7 @@ func (me *Client) addConnection(t *torrent, c *connection) bool {
func (me *Client) openNewConns() { func (me *Client) openNewConns() {
for _, t := range me.torrents { for _, t := range me.torrents {
for len(t.Peers) != 0 { for len(t.Peers) != 0 {
if me.halfOpen >= me.HalfOpenLimit { if me.halfOpen >= me.halfOpenLimit {
return return
} }
p := t.Peers[0] p := t.Peers[0]
@ -812,7 +842,7 @@ func (me *Client) AddPeers(infoHash InfoHash, peers []Peer) error {
} }
func (cl *Client) setMetaData(t *torrent, md metainfo.Info, bytes []byte) (err error) { func (cl *Client) setMetaData(t *torrent, md metainfo.Info, bytes []byte) (err error) {
err = t.setMetadata(md, cl.DataDir, bytes) err = t.setMetadata(md, cl.dataDir, bytes)
if err != nil { if err != nil {
return return
} }
@ -827,7 +857,7 @@ func (cl *Client) setMetaData(t *torrent, md metainfo.Info, bytes []byte) (err e
} }
}() }()
cl.DownloadStrategy.TorrentStarted(t) cl.downloadStrategy.TorrentStarted(t)
return return
} }
@ -902,10 +932,10 @@ func (me *Client) addTorrent(t *torrent) (err error) {
return return
} }
me.torrents[t.InfoHash] = t me.torrents[t.InfoHash] = t
if !me.DisableTrackers { if !me.disableTrackers {
go me.announceTorrent(t) go me.announceTorrent(t)
} }
if me.DHT != nil { if me.dHT != nil {
go me.announceTorrentDHT(t) go me.announceTorrentDHT(t)
} }
return return
@ -940,7 +970,7 @@ func (me *Client) AddTorrentFromFile(name string) (err error) {
} }
func (cl *Client) listenerAnnouncePort() (port int16) { func (cl *Client) listenerAnnouncePort() (port int16) {
l := cl.Listener l := cl.listener
if l == nil { if l == nil {
return return
} }
@ -958,7 +988,7 @@ func (cl *Client) listenerAnnouncePort() (port int16) {
func (cl *Client) announceTorrentDHT(t *torrent) { func (cl *Client) announceTorrentDHT(t *torrent) {
for { for {
ps, err := cl.DHT.GetPeers(string(t.InfoHash[:])) ps, err := cl.dHT.GetPeers(string(t.InfoHash[:]))
if err != nil { if err != nil {
log.Printf("error getting peers from dht: %s", err) log.Printf("error getting peers from dht: %s", err)
return return
@ -1003,7 +1033,7 @@ func (cl *Client) announceTorrent(t *torrent) {
Event: tracker.Started, Event: tracker.Started,
NumWant: -1, NumWant: -1,
Port: cl.listenerAnnouncePort(), Port: cl.listenerAnnouncePort(),
PeerId: cl.PeerId, PeerId: cl.peerID,
InfoHash: t.InfoHash, InfoHash: t.InfoHash,
} }
newAnnounce: newAnnounce:
@ -1072,7 +1102,7 @@ func (me *Client) WaitAll() bool {
} }
func (cl *Client) assertRequestHeat() { func (cl *Client) assertRequestHeat() {
dds, ok := cl.DownloadStrategy.(*DefaultDownloadStrategy) dds, ok := cl.downloadStrategy.(*DefaultDownloadStrategy)
if !ok { if !ok {
return return
} }
@ -1095,7 +1125,7 @@ func (me *Client) replenishConnRequests(t *torrent, c *connection) {
if !t.haveInfo() { if !t.haveInfo() {
return return
} }
me.DownloadStrategy.FillRequests(t, c) me.downloadStrategy.FillRequests(t, c)
//me.assertRequestHeat() //me.assertRequestHeat()
if len(c.Requests) == 0 && !c.PeerChoked { if len(c.Requests) == 0 && !c.PeerChoked {
c.SetInterested(false) c.SetInterested(false)
@ -1130,7 +1160,7 @@ func (me *Client) downloadedChunk(t *torrent, c *connection, msg *pp.Message) er
} }
// Unprioritize the chunk. // Unprioritize the chunk.
me.DownloadStrategy.TorrentGotChunk(t, req) me.downloadStrategy.TorrentGotChunk(t, req)
// Cancel pending requests for this chunk. // Cancel pending requests for this chunk.
cancelled := false cancelled := false
@ -1171,7 +1201,7 @@ func (me *Client) pieceHashed(t *torrent, piece pp.Integer, correct bool) {
p.EverHashed = true p.EverHashed = true
if correct { if correct {
p.PendingChunkSpecs = nil p.PendingChunkSpecs = nil
me.DownloadStrategy.TorrentGotPiece(t, int(piece)) me.downloadStrategy.TorrentGotPiece(t, int(piece))
me.dataReady(dataSpec{ me.dataReady(dataSpec{
t.InfoHash, t.InfoHash,
request{ request{

View File

@ -7,6 +7,14 @@ import (
"testing" "testing"
) )
func TestClientDefault(t *testing.T) {
cl, err := NewClient(nil)
if err != nil {
t.Fatal(err)
}
cl.Stop()
}
func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) { func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
t.SkipNow() t.SkipNow()
} }

View File

@ -1,9 +1,6 @@
package main package main
import ( import (
"bitbucket.org/anacrolix/go.torrent/dht"
"bitbucket.org/anacrolix/go.torrent/tracker"
_ "bitbucket.org/anacrolix/go.torrent/util/profile"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -11,6 +8,11 @@ import (
"net" "net"
"os" "os"
"os/signal" "os/signal"
"time"
"bitbucket.org/anacrolix/go.torrent/dht"
"bitbucket.org/anacrolix/go.torrent/util"
_ "bitbucket.org/anacrolix/go.torrent/util/profile"
) )
type pingResponse struct { type pingResponse struct {
@ -23,7 +25,7 @@ var (
serveAddr = flag.String("serveAddr", ":0", "local UDP address") serveAddr = flag.String("serveAddr", ":0", "local UDP address")
infoHash = flag.String("infoHash", "", "torrent infohash") infoHash = flag.String("infoHash", "", "torrent infohash")
s dht.Server s *dht.Server
) )
func loadTable() error { func loadTable() error {
@ -74,22 +76,17 @@ func init() {
log.Fatal("require 20 byte infohash") log.Fatal("require 20 byte infohash")
} }
var err error var err error
s.Socket, err = net.ListenUDP("udp4", func() *net.UDPAddr { s, err = dht.NewServer(&dht.ServerConfig{
addr, err := net.ResolveUDPAddr("udp4", *serveAddr) Addr: *serveAddr,
if err != nil { })
log.Fatalf("error resolving serve addr: %s", err)
}
return addr
}())
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
s.Init()
err = loadTable() err = loadTable()
if err != nil { if err != nil {
log.Fatalf("error loading table: %s", err) log.Fatalf("error loading table: %s", err)
} }
log.Printf("dht server on %s, ID is %q", s.Socket.LocalAddr(), s.IDString()) log.Printf("dht server on %s, ID is %q", s.LocalAddr(), s.IDString())
setupSignals() setupSignals()
} }
@ -131,18 +128,13 @@ func setupSignals() {
} }
func main() { func main() {
go func() { seen := make(map[util.CompactPeer]struct{})
defer s.StopServing() for {
if err := s.Bootstrap(); err != nil {
log.Printf("error bootstrapping: %s", err)
return
}
saveTable()
ps, err := s.GetPeers(*infoHash) ps, err := s.GetPeers(*infoHash)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
seen := make(map[tracker.CompactPeer]struct{}) go func() {
for sl := range ps.Values { for sl := range ps.Values {
for _, p := range sl { for _, p := range sl {
if _, ok := seen[p]; ok { if _, ok := seen[p]; ok {
@ -156,11 +148,9 @@ func main() {
} }
} }
}() }()
err := s.Serve() time.Sleep(15 * time.Second)
}
if err := saveTable(); err != nil { if err := saveTable(); err != nil {
log.Printf("error saving node table: %s", err) log.Printf("error saving node table: %s", err)
} }
if err != nil {
log.Fatalf("error serving dht: %s", err)
}
} }

View File

@ -1,11 +1,12 @@
package main package main
import ( import (
"bitbucket.org/anacrolix/go.torrent/dht"
"flag" "flag"
"log" "log"
"net" "net"
"os" "os"
"bitbucket.org/anacrolix/go.torrent/dht"
) )
type pingResponse struct { type pingResponse struct {
@ -21,20 +22,11 @@ func main() {
os.Stderr.WriteString("u must specify addrs of nodes to ping e.g. router.bittorrent.com:6881\n") os.Stderr.WriteString("u must specify addrs of nodes to ping e.g. router.bittorrent.com:6881\n")
os.Exit(2) os.Exit(2)
} }
s := dht.Server{} s, err := dht.NewServer(nil)
var err error
s.Socket, err = net.ListenUDP("udp4", nil)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Printf("dht server on %s", s.Socket.LocalAddr()) log.Printf("dht server on %s", s.LocalAddr())
s.Init()
go func() {
err := s.Serve()
if err != nil {
log.Fatal(err)
}
}()
pingResponses := make(chan pingResponse) pingResponses := make(chan pingResponse)
for _, netloc := range pingStrAddrs { for _, netloc := range pingStrAddrs {
addr, err := net.ResolveUDPAddr("udp4", netloc) addr, err := net.ResolveUDPAddr("udp4", netloc)

View File

@ -1,14 +1,14 @@
package main package main
import ( import (
"bitbucket.org/anacrolix/go.torrent/dht"
"flag" "flag"
"fmt" "fmt"
"io" "io"
"log" "log"
"net"
"os" "os"
"os/signal" "os/signal"
"bitbucket.org/anacrolix/go.torrent/dht"
) )
type pingResponse struct { type pingResponse struct {
@ -20,7 +20,7 @@ var (
tableFileName = flag.String("tableFile", "", "name of file for storing node info") tableFileName = flag.String("tableFile", "", "name of file for storing node info")
serveAddr = flag.String("serveAddr", ":0", "local UDP address") serveAddr = flag.String("serveAddr", ":0", "local UDP address")
s dht.Server s *dht.Server
) )
func loadTable() error { func loadTable() error {
@ -61,22 +61,17 @@ func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile) log.SetFlags(log.LstdFlags | log.Lshortfile)
flag.Parse() flag.Parse()
var err error var err error
s.Socket, err = net.ListenUDP("udp4", func() *net.UDPAddr { s, err = dht.NewServer(&dht.ServerConfig{
addr, err := net.ResolveUDPAddr("udp4", *serveAddr) Addr: *serveAddr,
if err != nil { })
log.Fatalf("error resolving serve addr: %s", err)
}
return addr
}())
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
s.Init()
err = loadTable() err = loadTable()
if err != nil { if err != nil {
log.Fatalf("error loading table: %s", err) log.Fatalf("error loading table: %s", err)
} }
log.Printf("dht server on %s, ID is %q", s.Socket.LocalAddr(), s.IDString()) log.Printf("dht server on %s, ID is %q", s.LocalAddr(), s.IDString())
setupSignals() setupSignals()
} }
@ -118,20 +113,8 @@ func setupSignals() {
} }
func main() { func main() {
go func() { select {}
err := s.Bootstrap()
if err != nil {
log.Printf("error bootstrapping: %s", err)
s.StopServing()
} else {
log.Print("bootstrapping complete")
}
}()
err := s.Serve()
if err := saveTable(); err != nil { if err := saveTable(); err != nil {
log.Printf("error saving node table: %s", err) log.Printf("error saving node table: %s", err)
} }
if err != nil {
log.Fatalf("error serving dht: %s", err)
}
} }

View File

@ -1,8 +1,6 @@
package main package main
import ( import (
"bitbucket.org/anacrolix/go.torrent/dht"
"bitbucket.org/anacrolix/go.torrent/util"
"flag" "flag"
"fmt" "fmt"
"log" "log"
@ -12,6 +10,8 @@ import (
"os" "os"
"strings" "strings"
"bitbucket.org/anacrolix/go.torrent/util"
"github.com/anacrolix/libtorgo/metainfo" "github.com/anacrolix/libtorgo/metainfo"
"bitbucket.org/anacrolix/go.torrent" "bitbucket.org/anacrolix/go.torrent"
@ -32,57 +32,21 @@ func init() {
flag.Parse() flag.Parse()
} }
func makeListener() net.Listener {
l, err := net.Listen("tcp", *listenAddr)
if err != nil {
log.Fatal(err)
}
return l
}
func main() { func main() {
if *httpAddr != "" { if *httpAddr != "" {
util.LoggedHTTPServe(*httpAddr) util.LoggedHTTPServe(*httpAddr)
} }
dhtServer := &dht.Server{ client, err := torrent.NewClient(&torrent.Config{
Socket: func() *net.UDPConn {
addr, err := net.ResolveUDPAddr("udp4", *listenAddr)
if err != nil {
log.Fatalf("error resolving dht listen addr: %s", err)
}
s, err := net.ListenUDP("udp4", addr)
if err != nil {
log.Fatalf("error creating dht socket: %s", err)
}
return s
}(),
}
err := dhtServer.Init()
if err != nil {
log.Fatalf("error initing dht server: %s", err)
}
go func() {
err := dhtServer.Serve()
if err != nil {
log.Fatalf("error serving dht: %s", err)
}
}()
go func() {
err := dhtServer.Bootstrap()
if err != nil {
log.Printf("error bootstrapping dht server: %s", err)
}
}()
client := torrent.Client{
DataDir: *downloadDir, DataDir: *downloadDir,
Listener: makeListener(),
DisableTrackers: *disableTrackers, DisableTrackers: *disableTrackers,
DHT: dhtServer, ListenAddr: *listenAddr,
})
if err != nil {
log.Fatalf("error creating client: %s", err)
} }
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
client.WriteStatus(w) client.WriteStatus(w)
}) })
client.Start()
defer client.Stop() defer client.Stop()
if flag.NArg() == 0 { if flag.NArg() == 0 {
fmt.Fprintln(os.Stderr, "no torrents specified") fmt.Fprintln(os.Stderr, "no torrents specified")

View File

@ -45,14 +45,6 @@ func init() {
flag.StringVar(&mountDir, "mountDir", "", "location the torrent contents are made available") flag.StringVar(&mountDir, "mountDir", "", "location the torrent contents are made available")
} }
func makeListener() net.Listener {
l, err := net.Listen("tcp", *listenAddr)
if err != nil {
log.Fatal(err)
}
return l
}
func resolveTestPeerAddr() { func resolveTestPeerAddr() {
if *testPeer == "" { if *testPeer == "" {
return return
@ -113,16 +105,15 @@ func main() {
// TODO: Think about the ramifications of exiting not due to a signal. // TODO: Think about the ramifications of exiting not due to a signal.
setSignalHandlers() setSignalHandlers()
defer conn.Close() defer conn.Close()
client := &torrent.Client{ client, err := torrent.NewClient(&torrent.Config{
DataDir: downloadDir, DataDir: downloadDir,
DisableTrackers: *disableTrackers, DisableTrackers: *disableTrackers,
DownloadStrategy: torrent.NewResponsiveDownloadStrategy(*readaheadBytes), DownloadStrategy: torrent.NewResponsiveDownloadStrategy(*readaheadBytes),
Listener: makeListener(), ListenAddr: *listenAddr,
} })
http.DefaultServeMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { http.DefaultServeMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
client.WriteStatus(w) client.WriteStatus(w)
}) })
client.Start()
dw, err := dirwatch.New(torrentPath) dw, err := dirwatch.New(torrentPath)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

9
config.go Normal file
View File

@ -0,0 +1,9 @@
package torrent
type Config struct {
DataDir string
ListenAddr string
DisableTrackers bool
DownloadStrategy DownloadStrategy
NoDHT bool
}

View File

@ -1,25 +1,25 @@
package dht package dht
import ( import (
"bitbucket.org/anacrolix/go.torrent/tracker"
"bitbucket.org/anacrolix/go.torrent/util"
"crypto" "crypto"
_ "crypto/sha1" _ "crypto/sha1"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"github.com/nsf/libtorgo/bencode"
"io" "io"
"log" "log"
"net" "net"
"os" "os"
"sync" "sync"
"time" "time"
"bitbucket.org/anacrolix/go.torrent/util"
"github.com/nsf/libtorgo/bencode"
) )
type Server struct { type Server struct {
ID string id string
Socket *net.UDPConn socket *net.UDPConn
transactions []*transaction transactions []*transaction
transactionIDInt uint64 transactionIDInt uint64
nodes map[string]*Node // Keyed by *net.UDPAddr.String(). nodes map[string]*Node // Keyed by *net.UDPAddr.String().
@ -27,8 +27,47 @@ type Server struct {
closed chan struct{} closed chan struct{}
} }
type ServerConfig struct {
Addr string
}
func (s *Server) LocalAddr() net.Addr {
return s.socket.LocalAddr()
}
func makeSocket(addr string) (socket *net.UDPConn, err error) {
addr_, err := net.ResolveUDPAddr("", addr)
if err != nil {
return
}
socket, err = net.ListenUDP("udp", addr_)
return
}
func NewServer(c *ServerConfig) (s *Server, err error) {
s = &Server{}
s.socket, err = makeSocket(c.Addr)
if err != nil {
return
}
err = s.init()
if err != nil {
return
}
go func() {
panic(s.serve())
}()
go func() {
err := s.bootstrap()
if err != nil {
panic(err)
}
}()
return
}
func (s *Server) String() string { func (s *Server) String() string {
return fmt.Sprintf("dht server on %s", s.Socket.LocalAddr()) return fmt.Sprintf("dht server on %s", s.socket.LocalAddr())
} }
type Node struct { type Node struct {
@ -96,25 +135,14 @@ func (t *transaction) handleResponse(m Msg) {
} }
func (s *Server) setDefaults() (err error) { func (s *Server) setDefaults() (err error) {
if s.Socket == nil { if s.id == "" {
var addr *net.UDPAddr
addr, err = net.ResolveUDPAddr("", ":6882")
if err != nil {
return
}
s.Socket, err = net.ListenUDP("udp", addr)
if err != nil {
return
}
}
if s.ID == "" {
var id [20]byte var id [20]byte
h := crypto.SHA1.New() h := crypto.SHA1.New()
ss, err := os.Hostname() ss, err := os.Hostname()
if err != nil { if err != nil {
log.Print(err) log.Print(err)
} }
ss += s.Socket.LocalAddr().String() ss += s.socket.LocalAddr().String()
h.Write([]byte(ss)) h.Write([]byte(ss))
if b := h.Sum(id[:0:20]); len(b) != 20 { if b := h.Sum(id[:0:20]); len(b) != 20 {
panic(len(b)) panic(len(b))
@ -122,13 +150,13 @@ func (s *Server) setDefaults() (err error) {
if len(id) != 20 { if len(id) != 20 {
panic(len(id)) panic(len(id))
} }
s.ID = string(id[:]) s.id = string(id[:])
} }
s.nodes = make(map[string]*Node, 10000) s.nodes = make(map[string]*Node, 10000)
return return
} }
func (s *Server) Init() (err error) { func (s *Server) init() (err error) {
err = s.setDefaults() err = s.setDefaults()
if err != nil { if err != nil {
return return
@ -137,10 +165,10 @@ func (s *Server) Init() (err error) {
return return
} }
func (s *Server) Serve() error { func (s *Server) serve() error {
for { for {
var b [0x10000]byte var b [0x10000]byte
n, addr, err := s.Socket.ReadFromUDP(b[:]) n, addr, err := s.socket.ReadFromUDP(b[:])
if err != nil { if err != nil {
return err return err
} }
@ -296,7 +324,7 @@ func (s *Server) getNode(addr *net.UDPAddr) (n *Node) {
} }
func (s *Server) writeToNode(b []byte, node *net.UDPAddr) (err error) { func (s *Server) writeToNode(b []byte, node *net.UDPAddr) (err error) {
n, err := s.Socket.WriteTo(b, node) n, err := s.socket.WriteTo(b, node)
if err != nil { if err != nil {
err = fmt.Errorf("error writing %d bytes to %s: %s", len(b), node, err) err = fmt.Errorf("error writing %d bytes to %s: %s", len(b), node, err)
return return
@ -347,10 +375,10 @@ func (s *Server) addTransaction(t *transaction) {
} }
func (s *Server) IDString() string { func (s *Server) IDString() string {
if len(s.ID) != 20 { if len(s.id) != 20 {
panic("bad node id") panic("bad node id")
} }
return s.ID return s.id
} }
func (s *Server) timeoutTransaction(t *transaction) { func (s *Server) timeoutTransaction(t *transaction) {
@ -560,14 +588,9 @@ func (s *Server) findNode(addr *net.UDPAddr, targetID string) (t *transaction, e
return return
} }
type getPeersResponse struct {
Values []tracker.CompactPeer `bencode:"values"`
Nodes util.CompactPeers `bencode:"nodes"`
}
type peerStream struct { type peerStream struct {
mu sync.Mutex mu sync.Mutex
Values chan []tracker.CompactPeer Values chan []util.CompactPeer
stop chan struct{} stop chan struct{}
} }
@ -577,12 +600,11 @@ func (ps *peerStream) Close() {
case <-ps.stop: case <-ps.stop:
default: default:
close(ps.stop) close(ps.stop)
close(ps.Values)
} }
ps.mu.Unlock() ps.mu.Unlock()
} }
func extractValues(m Msg) (vs []tracker.CompactPeer) { func extractValues(m Msg) (vs []util.CompactPeer) {
r, ok := m["r"] r, ok := m["r"]
if !ok { if !ok {
return return
@ -595,19 +617,17 @@ func extractValues(m Msg) (vs []tracker.CompactPeer) {
if !ok { if !ok {
return return
} }
// log.Fatal(m)
vl, ok := v.([]interface{}) vl, ok := v.([]interface{})
if !ok { if !ok {
panic(v) panic(v)
} }
vs = make([]tracker.CompactPeer, 0, len(vl)) vs = make([]util.CompactPeer, 0, len(vl))
for _, i := range vl { for _, i := range vl {
// log.Printf("%T", i)
s, ok := i.(string) s, ok := i.(string)
if !ok { if !ok {
panic(i) panic(i)
} }
var cp tracker.CompactPeer var cp util.CompactPeer
err := cp.UnmarshalBinary([]byte(s)) err := cp.UnmarshalBinary([]byte(s))
if err != nil { if err != nil {
log.Printf("error decoding values list element: %s", err) log.Printf("error decoding values list element: %s", err)
@ -620,7 +640,7 @@ func extractValues(m Msg) (vs []tracker.CompactPeer) {
func (s *Server) GetPeers(infoHash string) (ps *peerStream, err error) { func (s *Server) GetPeers(infoHash string) (ps *peerStream, err error) {
ps = &peerStream{ ps = &peerStream{
Values: make(chan []tracker.CompactPeer), Values: make(chan []util.CompactPeer),
stop: make(chan struct{}), stop: make(chan struct{}),
} }
done := make(chan struct{}) done := make(chan struct{})
@ -657,7 +677,7 @@ func (s *Server) GetPeers(infoHash string) (ps *peerStream, err error) {
case <-s.closed: case <-s.closed:
} }
} }
ps.Close() close(ps.Values)
}() }()
return return
} }
@ -689,7 +709,7 @@ func (s *Server) addRootNode() error {
} }
// Populates the node table. // Populates the node table.
func (s *Server) Bootstrap() (err error) { func (s *Server) bootstrap() (err error) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if len(s.nodes) == 0 { if len(s.nodes) == 0 {
@ -702,7 +722,7 @@ func (s *Server) Bootstrap() (err error) {
var outstanding sync.WaitGroup var outstanding sync.WaitGroup
for _, node := range s.nodes { for _, node := range s.nodes {
var t *transaction var t *transaction
t, err = s.findNode(node.addr, s.ID) t, err = s.findNode(node.addr, s.id)
if err != nil { if err != nil {
return return
} }
@ -768,7 +788,7 @@ func (s *Server) Nodes() (nis []NodeInfo) {
} }
func (s *Server) StopServing() { func (s *Server) StopServing() {
s.Socket.Close() s.socket.Close()
s.mu.Lock() s.mu.Lock()
select { select {
case <-s.closed: case <-s.closed:

View File

@ -6,19 +6,26 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"net" "net"
"net/http"
_ "net/http/pprof"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
"bitbucket.org/anacrolix/go.torrent"
"bitbucket.org/anacrolix/go.torrent/testutil" "bitbucket.org/anacrolix/go.torrent/testutil"
"bitbucket.org/anacrolix/go.torrent/util"
"github.com/anacrolix/libtorgo/metainfo"
"bazil.org/fuse" "bazil.org/fuse"
fusefs "bazil.org/fuse/fs" fusefs "bazil.org/fuse/fs"
"bitbucket.org/anacrolix/go.torrent"
"github.com/anacrolix/libtorgo/metainfo"
) )
func init() {
go http.ListenAndServe(":6061", nil)
}
func TestTCPAddrString(t *testing.T) { func TestTCPAddrString(t *testing.T) {
ta := &net.TCPAddr{ ta := &net.TCPAddr{
IP: net.IPv4(127, 0, 0, 1), IP: net.IPv4(127, 0, 0, 1),
@ -65,6 +72,7 @@ func newGreetingLayout() (tl testLayout, err error) {
metaInfoBuf := &bytes.Buffer{} metaInfoBuf := &bytes.Buffer{}
testutil.CreateMetaInfo(name, metaInfoBuf) testutil.CreateMetaInfo(name, metaInfoBuf)
tl.Metainfo, err = metainfo.Load(metaInfoBuf) tl.Metainfo, err = metainfo.Load(metaInfoBuf)
log.Printf("%x", tl.Metainfo.Info.Pieces)
return return
} }
@ -79,14 +87,14 @@ func TestUnmountWedged(t *testing.T) {
t.Log(err) t.Log(err)
} }
}() }()
client := torrent.Client{ client, err := torrent.NewClient(&torrent.Config{
DataDir: filepath.Join(layout.BaseDir, "incomplete"), DataDir: filepath.Join(layout.BaseDir, "incomplete"),
DisableTrackers: true, DisableTrackers: true,
} NoDHT: true,
client.Start() })
log.Printf("%+v", *layout.Metainfo) log.Printf("%+v", *layout.Metainfo)
client.AddTorrent(layout.Metainfo) client.AddTorrent(layout.Metainfo)
fs := New(&client) fs := New(client)
fuseConn, err := fuse.Mount(layout.MountDir) fuseConn, err := fuse.Mount(layout.MountDir)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -125,40 +133,40 @@ func TestDownloadOnDemand(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
seeder := torrent.Client{ seeder, err := torrent.NewClient(&torrent.Config{
DataDir: layout.Completed, DataDir: layout.Completed,
Listener: func() net.Listener {
conn, err := net.Listen("tcp", ":0")
if err != nil {
panic(err)
}
return conn
}(),
DisableTrackers: true, DisableTrackers: true,
} NoDHT: true,
defer seeder.Listener.Close() })
seeder.Start() http.HandleFunc("/seeder", func(w http.ResponseWriter, req *http.Request) {
seeder.WriteStatus(w)
})
defer seeder.Stop() defer seeder.Stop()
err = seeder.AddMagnet(fmt.Sprintf("magnet:?xt=urn:btih:%x", layout.Metainfo.Info.Hash)) err = seeder.AddMagnet(fmt.Sprintf("magnet:?xt=urn:btih:%x", layout.Metainfo.Info.Hash))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
leecher := torrent.Client{ leecher, err := torrent.NewClient(&torrent.Config{
DataDir: filepath.Join(layout.BaseDir, "download"), DataDir: filepath.Join(layout.BaseDir, "download"),
DownloadStrategy: &torrent.ResponsiveDownloadStrategy{}, DownloadStrategy: torrent.NewResponsiveDownloadStrategy(0),
DisableTrackers: true, DisableTrackers: true,
} NoDHT: true,
leecher.Start() })
http.HandleFunc("/leecher", func(w http.ResponseWriter, req *http.Request) {
leecher.WriteStatus(w)
})
defer leecher.Stop() defer leecher.Stop()
leecher.AddTorrent(layout.Metainfo) leecher.AddTorrent(layout.Metainfo)
leecher.AddPeers(torrent.BytesInfoHash(layout.Metainfo.Info.Hash), []torrent.Peer{func() torrent.Peer { var ih torrent.InfoHash
tcpAddr := seeder.Listener.Addr().(*net.TCPAddr) util.CopyExact(ih[:], layout.Metainfo.Info.Hash)
leecher.AddPeers(ih, []torrent.Peer{func() torrent.Peer {
tcpAddr := seeder.ListenAddr().(*net.TCPAddr)
return torrent.Peer{ return torrent.Peer{
IP: tcpAddr.IP, IP: tcpAddr.IP,
Port: tcpAddr.Port, Port: tcpAddr.Port,
} }
}()}) }()})
fs := New(&leecher) fs := New(leecher)
mountDir := layout.MountDir mountDir := layout.MountDir
fuseConn, err := fuse.Mount(layout.MountDir) fuseConn, err := fuse.Mount(layout.MountDir)
if err != nil { if err != nil {