From 0d411de4d49a16f3d34026c0701eb670021a407c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Adamski?= Date: Wed, 29 Jan 2025 23:44:48 +0100 Subject: [PATCH] removed dependency on fiber and solved memory leaks --- Makefile | 2 +- announce.go | 94 +++++++++++++++++++++++++++--------------------- announce_test.go | 17 +++++++++ go.mod | 14 +------- go.sum | 27 -------------- main.go | 93 ++++++++++++++--------------------------------- middleware.go | 67 ++++++++++++++++++++++++++++++++++ scrape.go | 26 +++++++------- storage.go | 78 ++++++++++++++++++++-------------------- 9 files changed, 216 insertions(+), 202 deletions(-) create mode 100644 announce_test.go create mode 100644 middleware.go diff --git a/Makefile b/Makefile index 7e42a54..929743d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ build: - CGO_ENABLED=0 go build -ldflags="-s -w" -trimpath + CGO_ENABLED=0 go build -ldflags="-s -w -buildid=" -trimpath deploy: build rsync -avzL --exclude '*.fiber.gz' docs privtracker privtracker:web/ diff --git a/announce.go b/announce.go index 50b8ade..68fc5dd 100644 --- a/announce.go +++ b/announce.go @@ -2,32 +2,16 @@ package main import ( "crypto/sha1" + "fmt" "net" + "net/http" + "strconv" + "strings" "time" - "github.com/gofiber/fiber/v2" "github.com/jackpal/bencode-go" ) -type AnnounceRequest struct { - InfoHash string `query:"info_hash"` - PeerID string `query:"peer_id"` - IP string `query:"ip"` - Port uint16 `query:"port"` - Uploaded uint `query:"uploaded"` - Downloaded uint `query:"downloaded"` - Left uint `query:"left"` - Numwant uint `query:"numwant"` - Key string `query:"key"` - Compact bool `query:"compact"` - SupportCrypto bool `query:"supportcrypto"` - Event string `query:"event"` -} - -func (req *AnnounceRequest) IsSeeding() bool { - return req.Left == 0 -} - type AnnounceResponse struct { Interval int `bencode:"interval"` Complete int `bencode:"complete"` @@ -36,38 +20,44 @@ type AnnounceResponse struct { PeersIPv6 []byte `bencode:"peers_ipv6"` } -func announce(c *fiber.Ctx) error { - var req AnnounceRequest - err := c.QueryParser(&req) +func announce(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + port, err := strconv.Atoi(query.Get("port")) if err != nil { - return err + http.Error(w, "port missing", 400) + return } - ip := net.ParseIP(c.IP()) + ip := getRemoteIP(r) if ip == nil { - ip = c.Context().RemoteIP() + http.Error(w, "can't parse IP", 400) + return } - if req.Numwant < 1 { - req.Numwant = 30 + numwant, err := strconv.Atoi(query.Get("numwant")) + if err != nil || numwant < 1 { + numwant = 30 } - swarmHash := sha1.Sum([]byte(c.Params("room") + req.InfoHash)) - peer := NewPeer(ip, req.Port) - switch req.Event { + swarmHash := sha1.Sum([]byte(r.PathValue("room") + query.Get("info_hash"))) + peer := NewPeer(ip, uint16(port)) + isSeeding := query.Get("left") == "0" + switch query.Get("event") { case "stopped": DeletePeer(swarmHash, peer) case "completed": GraduateLeecher(swarmHash, peer) default: - PutPeer(swarmHash, peer, req.IsSeeding()) + PutPeer(swarmHash, peer, isSeeding) } - peersIPv4, peersIPv6, numSeeders, numLeechers := GetPeers(swarmHash, peer, req.IsSeeding(), req.Numwant) - interval := int(time.Now().Unix()+int64(swarmHash[0]))%256 + 60 + peersIPv4, peersIPv6, numSeeders, numLeechers := GetPeers(swarmHash, peer, isSeeding, numwant) + interval := 480 // must be smaller than cleanup interval switch { - // case numSeeders == 0: - // interval -= 30 - case numLeechers == 0: - interval += 240 - case numSeeders+numLeechers > 10: - interval += 480 + case numSeeders+numLeechers < 10: + // try to synchronize peer requests. Maybe it will help µTP with UDP port punching... not sure + interval = 240 - int(time.Now().Unix()+int64(swarmHash[0]))%240 + if interval < 60 { + interval += 240 + } + case numSeeders+numLeechers > 30: + interval = 900 } resp := AnnounceResponse{ Interval: interval, @@ -76,5 +66,27 @@ func announce(c *fiber.Ctx) error { Peers: peersIPv4, PeersIPv6: peersIPv6, } - return bencode.Marshal(c, resp) + w.Header().Add("X-PrivTracker", fmt.Sprintf("s:%d l:%d", numSeeders, numLeechers)) + if err := bencode.Marshal(w, resp); err != nil { + http.Error(w, err.Error(), 400) + } +} + +func getRemoteIP(r *http.Request) net.IP { + addr := r.RemoteAddr + if colonIndex := strings.LastIndex(addr, ":"); colonIndex != -1 { + addr = addr[:colonIndex] + } + addr = strings.Trim(addr, "[]") + ip := net.ParseIP(addr) + if ip.IsPrivate() { + ips := strings.Split(r.Header.Get("X-Forwarded-For"), ",") + if len(ips) > 0 { + ipForwarded := net.ParseIP(strings.TrimSpace(ips[0])) + if ipForwarded != nil { + ip = ipForwarded + } + } + } + return ip } diff --git a/announce_test.go b/announce_test.go new file mode 100644 index 0000000..1a34a2a --- /dev/null +++ b/announce_test.go @@ -0,0 +1,17 @@ +package main + +import ( + "net/http/httptest" + "testing" +) + +func BenchmarkAnnounce(b *testing.B) { + server := httptest.NewServer(router()) + client := server.Client() + for i := 0; i < b.N; i++ { + _, err := client.Get(server.URL + "/test/announce?port=1234") + if err != nil { + b.Fatal(err) + } + } +} diff --git a/go.mod b/go.mod index dbd1ccf..db7cec2 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,13 @@ module github.com/meehow/privtracker -go 1.21.4 +go 1.22 require ( - github.com/gofiber/fiber/v2 v2.52.5 github.com/jackpal/bencode-go v1.0.2 golang.org/x/crypto v0.32.0 ) require ( - github.com/andybalholm/brotli v1.1.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.51.0 // indirect - github.com/valyala/tcplisten v1.0.0 // indirect golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index fb0e0f9..aba7d1d 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,8 @@ -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= -github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackpal/bencode-go v1.0.2 h1:LcCNfZ344u0LpBPOZNjpCLps/wUOuN4r87Fy9+5yU8g= github.com/jackpal/bencode-go v1.0.2/go.mod h1:6jI9mUjO3GQbZti3JizEfxTzRfWOM8oBBcwbwlTfceI= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= -github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= diff --git a/main.go b/main.go index 7a0f997..f4558ac 100644 --- a/main.go +++ b/main.go @@ -5,64 +5,37 @@ import ( "fmt" "log" "net" + "net/http" "os" "path/filepath" - "time" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" - "github.com/gofiber/fiber/v2/middleware/monitor" - "github.com/gofiber/fiber/v2/middleware/recover" "golang.org/x/crypto/acme/autocert" ) func main() { port := os.Getenv("PORT") - tlsEnabled := port == "443" if port == "" { port = "1337" } - config := fiber.Config{ - AppName: "PrivTracker", - ServerHeader: "PrivTracker", - ReadTimeout: time.Second * 245, - WriteTimeout: time.Second * 30, - Network: fiber.NetworkTCP, - GETOnly: true, - DisableKeepalive: true, - Immutable: true, - } - // if you disable TLS, then I guess you want to use existing proxy - if !tlsEnabled { - config.EnableTrustedProxyCheck = true - config.TrustedProxies = []string{"127.0.0.1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"} - config.ProxyHeader = fiber.HeaderXForwardedFor - } - - go Cleanup(time.Minute * 16) - - app := fiber.New(config) - app.Use(recover.New()) - // app.Use(pprof.New()) - app.Use(myLogger()) - app.Use(hsts) - app.Static("/", "docs", fiber.Static{ - MaxAge: 3600 * 24 * 7, - Compress: true, - CacheDuration: time.Hour, - }) - app.Get("/dashboard", monitor.New()) - app.Get("/:room/announce", announce) - app.Get("/:room/scrape", scrape) - app.Server().LogAllErrors = true - if tlsEnabled { - go redirect80(config) - log.Fatal(app.Listener(autocertListener())) + handler := router(recoveryMiddleware, headersMiddleware, logRequestMiddleware) + if port == "443" { + go redirect80() + fmt.Println("PrivTracker listening on https://0.0.0.0/") + log.Fatal(http.Serve(autocertListener(), handler)) } else { - log.Fatal(app.Listen(":" + port)) + fmt.Printf("PrivTracker listening on http://0.0.0.0:%s/\n", port) + log.Fatal(http.ListenAndServe(":"+port, handler)) } } +func router(middlewares ...Middleware) http.Handler { + mux := http.NewServeMux() + mux.Handle("/", http.FileServer(http.Dir("docs"))) + mux.HandleFunc("GET /{room}/announce", announce) + mux.HandleFunc("GET /{room}/scrape", scrape) + return chainMiddleware(mux, middlewares...) +} + func autocertListener() net.Listener { homeDir, err := os.UserHomeDir() if err != nil { @@ -75,33 +48,21 @@ func autocertListener() net.Listener { } cfg := &tls.Config{ GetCertificate: m.GetCertificate, - NextProtos: []string{ - "http/1.1", "acme-tls/1", - }, + NextProtos: []string{"h2", "http/1.1", "acme-tls/1"}, } - ln, err := tls.Listen("tcp", ":443", cfg) + listener, err := tls.Listen("tcp", ":443", cfg) if err != nil { log.Fatal(err) } - return ln + return listener } -func redirect80(config fiber.Config) { - config.DisableStartupMessage = true - app := fiber.New(config) - app.Use(func(c *fiber.Ctx) error { - return c.Redirect(fmt.Sprintf("https://%s/", c.Hostname()), fiber.StatusMovedPermanently) - }) - log.Print(app.Listen(":80")) -} - -func myLogger() fiber.Handler { - loggerConfig := logger.ConfigDefault - loggerConfig.Format = "${status} - ${latency} ${ip} ${method} ${path} ${bytesSent} - ${referer} - ${ua}\n" - return logger.New(loggerConfig) -} - -func hsts(c *fiber.Ctx) error { - c.Set("Strict-Transport-Security", "max-age=31536000") - return c.Next() +func redirect80() { + err := http.ListenAndServe(":80", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + url := fmt.Sprintf("https://%s/", r.Host) + http.Redirect(w, r, url, http.StatusMovedPermanently) + })) + if err != nil { + fmt.Println(err) + } } diff --git a/middleware.go b/middleware.go new file mode 100644 index 0000000..f72e121 --- /dev/null +++ b/middleware.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "net/http" + "runtime/debug" + "time" +) + +type Middleware func(http.Handler) http.Handler + +type ResponseWriterWrapper struct { + http.ResponseWriter + StatusCode int + BytesSent int +} + +func (rw *ResponseWriterWrapper) WriteHeader(code int) { + rw.StatusCode = code + rw.ResponseWriter.WriteHeader(code) +} + +func (rw *ResponseWriterWrapper) Write(data []byte) (int, error) { + bytesWritten, err := rw.ResponseWriter.Write(data) + rw.BytesSent += bytesWritten + return bytesWritten, err +} + +func logRequestMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + wrapper := &ResponseWriterWrapper{ResponseWriter: w, StatusCode: http.StatusOK} + timer := time.Now() + next.ServeHTTP(wrapper, r) + fmt.Printf("%d - %5dµs %47s %4s %42s %6d %11s - %s - %s\n", + wrapper.StatusCode, time.Since(timer).Microseconds(), r.RemoteAddr, + r.Method, r.URL.Path, wrapper.BytesSent, + wrapper.Header().Get("X-PrivTracker"), r.Referer(), r.UserAgent()) + }) +} + +func headersMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Strict-Transport-Security", "max-age=31536000") // hsts + w.Header().Set("Server", "PrivTracker") + next.ServeHTTP(w, r) + }) +} + +func recoveryMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("Recovered from panic: %v\n%s\n", err, debug.Stack()) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + }() + next.ServeHTTP(w, r) + }) +} + +func chainMiddleware(handler http.Handler, middlewares ...Middleware) http.Handler { + // Apply middlewares in reverse order (outermost to innermost) + for i := len(middlewares) - 1; i >= 0; i-- { + handler = middlewares[i](handler) + } + return handler +} diff --git a/scrape.go b/scrape.go index db360c1..ee032e3 100644 --- a/scrape.go +++ b/scrape.go @@ -2,15 +2,12 @@ package main import ( "crypto/sha1" + "fmt" + "net/http" - "github.com/gofiber/fiber/v2" "github.com/jackpal/bencode-go" ) -type ScrapeRequest struct { - InfoHash string `query:"info_hash"` -} - type ScrapeResponse struct { Files map[string]Stat `bencode:"files"` } @@ -21,21 +18,22 @@ type Stat struct { // Downloaded uint `bencode:"downloaded"` } -func scrape(c *fiber.Ctx) error { - var req ScrapeRequest - err := c.QueryParser(&req) - if err != nil { - return err - } - swarmHash := sha1.Sum([]byte(c.Params("room") + req.InfoHash)) +func scrape(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + infoHash := query.Get("info_hash") + swarmHash := sha1.Sum([]byte(r.PathValue("room") + infoHash)) + numSeeders, numLeechers := GetStats(swarmHash) resp := ScrapeResponse{ Files: map[string]Stat{ - req.InfoHash: { + infoHash: { Complete: numSeeders, Incomplete: numLeechers, }, }, } - return bencode.Marshal(c, resp) + w.Header().Add("X-PrivTracker", fmt.Sprintf("s:%d l:%d", numSeeders, numLeechers)) + if err := bencode.Marshal(w, resp); err != nil { + http.Error(w, err.Error(), 400) + } } diff --git a/storage.go b/storage.go index 4d644b2..4cf969a 100644 --- a/storage.go +++ b/storage.go @@ -5,9 +5,7 @@ import ( "crypto/sha1" "encoding/binary" "fmt" - "log" "net" - "runtime" "sync" "time" ) @@ -15,11 +13,11 @@ import ( type Hash [sha1.Size]byte // we use sha1 and we are not affraid of hash collisions type Peer [18]byte // 16 bytes for IP and 2 bytes for port number -var shards = NewShards(256) var v4InV6Prefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff} +var shards [256]*shard type shard struct { - swarms map[Hash]Swarm + swarms map[Hash]*Swarm sync.RWMutex } @@ -28,8 +26,17 @@ type Swarm struct { leechers map[Peer]int64 } -func NewSwarm() Swarm { - return Swarm{ +func init() { + for i := range shards { + shards[i] = &shard{ + swarms: make(map[Hash]*Swarm), + } + } + go Cleanup(time.Minute * 16) // needs to be bigget than biggest interval in announce.go +} + +func NewSwarm() *Swarm { + return &Swarm{ seeders: make(map[Peer]int64), leechers: make(map[Peer]int64), } @@ -47,23 +54,15 @@ func (peer Peer) String() string { return fmt.Sprintf("%s:%d", ip, port) } -func shardIndex(hash Hash) int { - return int(binary.BigEndian.Uint16(hash[:2])) % len(shards) -} - -func NewShards(size int) []*shard { - shards := make([]*shard, size) - for i := 0; i < size; i++ { - shards[i] = &shard{ - swarms: make(map[Hash]Swarm), - } - } - return shards +func shardIndex(hash Hash) uint8 { + // return int(binary.BigEndian.Uint16(hash[:2])) % len(shards) + return hash[0] // works only with 256 shards } func PutPeer(h Hash, peer Peer, seeding bool) { shard := shards[shardIndex(h)] shard.Lock() + defer shard.Unlock() if _, ok := shard.swarms[h]; !ok { shard.swarms[h] = NewSwarm() } @@ -72,34 +71,40 @@ func PutPeer(h Hash, peer Peer, seeding bool) { } else { shard.swarms[h].leechers[peer] = time.Now().Unix() } - shard.Unlock() } func DeletePeer(h Hash, peer Peer) { shard := shards[shardIndex(h)] shard.Lock() + defer shard.Unlock() if _, ok := shard.swarms[h]; !ok { return } delete(shard.swarms[h].seeders, peer) delete(shard.swarms[h].leechers, peer) - shard.Unlock() } func GraduateLeecher(h Hash, peer Peer) { shard := shards[shardIndex(h)] shard.Lock() + defer shard.Unlock() if _, ok := shard.swarms[h]; !ok { shard.swarms[h] = NewSwarm() } shard.swarms[h].seeders[peer] = time.Now().Unix() delete(shard.swarms[h].leechers, peer) - shard.Unlock() } -func GetPeers(h Hash, client Peer, seeding bool, numWant uint) (peersIPv4, peersIPv6 []byte, numSeeders, numLeechers int) { +func GetPeers(h Hash, client Peer, seeding bool, numWant int) (peersIPv4, peersIPv6 []byte, numSeeders, numLeechers int) { shard := shards[shardIndex(h)] shard.RLock() + defer shard.RUnlock() + if _, ok := shard.swarms[h]; !ok { + return + } + + numSeeders = len(shard.swarms[h].seeders) + numLeechers = len(shard.swarms[h].leechers) // seeders don't need other seeders if !seeding { @@ -107,6 +112,9 @@ func GetPeers(h Hash, client Peer, seeding bool, numWant uint) (peersIPv4, peers if numWant == 0 { break } + if peer == client { + continue + } if bytes.HasPrefix(peer[:], v4InV6Prefix) { peersIPv4 = append(peersIPv4, peer[len(v4InV6Prefix):]...) } else { @@ -116,66 +124,56 @@ func GetPeers(h Hash, client Peer, seeding bool, numWant uint) (peersIPv4, peers } } for peer := range shard.swarms[h].leechers { - if peer == client { - continue - } if numWant == 0 { break } + if peer == client { + continue + } if bytes.HasPrefix(peer[:], v4InV6Prefix) { - peersIPv4 = append(peersIPv4, peer[12:]...) + peersIPv4 = append(peersIPv4, peer[len(v4InV6Prefix):]...) } else { peersIPv6 = append(peersIPv6, peer[:]...) } numWant-- } - numSeeders = len(shard.swarms[h].seeders) - numLeechers = len(shard.swarms[h].leechers) - shard.RUnlock() return } func GetStats(h Hash) (numSeeders, numLeechers int) { shard := shards[shardIndex(h)] shard.RLock() + defer shard.RUnlock() + if _, ok := shard.swarms[h]; !ok { + return + } numSeeders = len(shard.swarms[h].seeders) numLeechers = len(shard.swarms[h].leechers) - shard.RUnlock() return } func Cleanup(duration time.Duration) { ticker := time.NewTicker(duration) for range ticker.C { - var seeders, leechers, swarms, seedersDeleted, leechersDeleted, swarmsDeleted int expiration := time.Now().Unix() - int64(duration.Seconds()) for _, shard := range shards { shard.Lock() - swarms += len(shard.swarms) for h, swarm := range shard.swarms { - seeders += len(swarm.seeders) - leechers += len(swarm.leechers) for peer, lastSeen := range swarm.seeders { if lastSeen < expiration { - seedersDeleted++ delete(swarm.seeders, peer) } } for peer, lastSeen := range swarm.leechers { if lastSeen < expiration { - leechersDeleted++ delete(swarm.leechers, peer) } } if len(swarm.leechers) == 0 && len(swarm.seeders) == 0 { - swarmsDeleted++ delete(shard.swarms, h) } } shard.Unlock() } - log.Printf("seeders: %d (%d deleted), leechers: %d (%d deleted), swarms: %d (%d deleted)", - seeders, seedersDeleted, leechers, leechersDeleted, swarms, swarmsDeleted) - runtime.GC() } }