mirror of
https://github.com/meehow/privtracker.git
synced 2026-09-02 22:56:07 +08:00
change map key type to fixed size array (Peer)
This commit is contained in:
17
annnounce.go
17
annnounce.go
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/jackpal/bencode-go"
|
||||
)
|
||||
@@ -38,19 +40,24 @@ func announce(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.IP = c.IP()
|
||||
room := c.Params("room")
|
||||
ip := net.ParseIP(c.IP())
|
||||
if ip == nil {
|
||||
ip = c.Context().RemoteIP()
|
||||
}
|
||||
if req.Numwant == 0 {
|
||||
req.Numwant = 30
|
||||
}
|
||||
peer := NewPeer(ip, req.Port)
|
||||
switch req.Event {
|
||||
case "stopped":
|
||||
DeletePeer(c.Params("room"), req.InfoHash, req.IP, req.Port)
|
||||
DeletePeer(room, req.InfoHash, peer)
|
||||
case "completed":
|
||||
GraduateLeecher(c.Params("room"), req.InfoHash, req.IP, req.Port)
|
||||
GraduateLeecher(room, req.InfoHash, peer)
|
||||
default:
|
||||
PutPeer(c.Params("room"), req.InfoHash, req.IP, req.Port, req.IsSeeding())
|
||||
PutPeer(room, req.InfoHash, peer, req.IsSeeding())
|
||||
}
|
||||
peersIPv4, peersIPv6, numSeeders, numLeechers := GetPeers(c.Params("room"), req.InfoHash, req.IP, req.Port, req.IsSeeding(), req.Numwant)
|
||||
peersIPv4, peersIPv6, numSeeders, numLeechers := GetPeers(room, req.InfoHash, peer, req.IsSeeding(), req.Numwant)
|
||||
interval := 120
|
||||
if numSeeders == 0 {
|
||||
interval /= 2
|
||||
|
||||
@@ -4,30 +4,32 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
|
||||
<link rel="canonical" href="https://privtracker.com/" />
|
||||
<link rel="stylesheet" href="water.css">
|
||||
<link rel="canonical" href="https://privtracker.com/">
|
||||
<title>PrivTracker - Private BitTorrent tracker for everyone</title>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll(".url").forEach(url => {
|
||||
url.innerText = url.innerText.replace('{{hostname}}', window.location.host);
|
||||
url.innerText = url.innerText.replace('{{origin}}', window.location.origin);
|
||||
})
|
||||
})
|
||||
function makeRoom(length) {
|
||||
let roomID = '';
|
||||
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const charactersLength = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
roomID += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
function makeRoom() {
|
||||
const randomValues = new Uint8Array(16);
|
||||
crypto.getRandomValues(randomValues);
|
||||
const roomID = Array.from(randomValues).map(byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
document.querySelectorAll(".url").forEach(url => {
|
||||
url.innerText = url.innerText.replace('{{room}}', roomID);
|
||||
})
|
||||
});
|
||||
|
||||
document.querySelector('#hidden').style.display = 'block';
|
||||
}
|
||||
function copyToClipboard() {
|
||||
navigator.clipboard.writeText(document.querySelector('.url').innerText);
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(document.querySelector('.url').innerText);
|
||||
} else {
|
||||
window.alert('"Copy to clipboard" works only on https websites.');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
@@ -45,33 +47,21 @@
|
||||
It really works like a private tracker, but can be generated with one click of a button.
|
||||
</p>
|
||||
|
||||
<button onclick="makeRoom(32)">I'm in. Generate private tracker for me</button>
|
||||
<button onclick="makeRoom()">I'm in. Generate private tracker for me</button>
|
||||
|
||||
<div id="hidden" style="display:none">
|
||||
<h3>Your announce URL:</h3>
|
||||
<code class="url">https://{{hostname}}/{{room}}/announce</code>
|
||||
<code class="url">{{origin}}/{{room}}/announce</code>
|
||||
<button onclick="copyToClipboard()">Copy to clipboard</button>
|
||||
<p>(You can also use any random string as the Room ID. We don't store it anywhere.)</p>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<h2>The Problem PrivTracker Solves</h2>
|
||||
<div>
|
||||
Sharing large files has always been difficult without compromising privacy or simplicity.
|
||||
Centralized services require full uploads before sharing, often with account registration and fees for large files.
|
||||
Hosting a file server demands technical knowledge, like opening firewall ports, and requires your computer to stay online.
|
||||
PrivTracker solves this by enabling private torrent sharing within a trusted group.
|
||||
Using UPnP, most torrent clients can automatically handle port opening, and only one person in the group needs an open port for everyone to download.
|
||||
Unlike public trackers, PrivTracker shares peers' IPs only within the group and keeps files off public networks like DHT, ensuring privacy and efficient sharing.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>How to create private torrent?</h2>
|
||||
<h3>Using <a href="https://transmissionbt.com/" target="_blank">transmission</a> on Linux:</h3>
|
||||
<ul>
|
||||
<li>File <b>→</b> New…</li>
|
||||
<li>Select file to share</li>
|
||||
<li>In <b>Trackers</b> field enter <code class="url">https://{{hostname}}/{{room}}/announce</code></li>
|
||||
<li>In <b>Trackers</b> field enter <code class="url">{{origin}}/{{room}}/announce</code></li>
|
||||
<li>Select <b>Private torrent</b></li>
|
||||
<li>Click <b>New</b></li>
|
||||
<li>Click <b>Add</b> in next window</li>
|
||||
@@ -84,7 +74,7 @@
|
||||
<li>File <b>→</b> Create Torrent File…</li>
|
||||
<li>Select file to share</li>
|
||||
<li>Click <b>+</b> under <b>Trackers</b> field and enter <code
|
||||
class="url">https://{{hostname}}/{{room}}/announce</code></li>
|
||||
class="url">{{origin}}/{{room}}/announce</code></li>
|
||||
<li>Select <b>Private</b>
|
||||
<li>Select <b>Open when created</b></li>
|
||||
<li>Click <b>Create</b></li>
|
||||
@@ -93,7 +83,20 @@
|
||||
</ul>
|
||||
<img src="new_torrent_mac.png" alt="New Torrent screenshot">
|
||||
|
||||
<h2>The Problem PrivTracker solves</h2>
|
||||
<p>
|
||||
Sharing large files has always been difficult without compromising privacy or simplicity.
|
||||
Centralized services require full uploads before sharing, often with account registration and fees for
|
||||
large files.
|
||||
Hosting a file server demands technical knowledge, like opening firewall ports, and requires your
|
||||
computer to stay online.
|
||||
PrivTracker solves this by enabling private torrent sharing within a trusted group.
|
||||
Using UPnP, most torrent clients can automatically handle port opening, and only one person in the group
|
||||
needs an open port for everyone to download.
|
||||
Unlike public trackers, PrivTracker shares peers' IPs only within the group and keeps files off public
|
||||
networks like DHT, ensuring privacy and efficient sharing.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
1689
docs/water.css
Normal file
1689
docs/water.css
Normal file
File diff suppressed because it is too large
Load Diff
6
main.go
6
main.go
@@ -24,8 +24,8 @@ func main() {
|
||||
port = "1337"
|
||||
}
|
||||
config := fiber.Config{
|
||||
AppName: "PrivTracker.com",
|
||||
ServerHeader: "PrivTracker.com",
|
||||
AppName: "PrivTracker",
|
||||
ServerHeader: "PrivTracker",
|
||||
ReadTimeout: time.Second * 245,
|
||||
WriteTimeout: time.Second * 30,
|
||||
Network: fiber.NetworkTCP,
|
||||
@@ -38,7 +38,7 @@ func main() {
|
||||
config.ProxyHeader = fiber.HeaderXForwardedFor
|
||||
}
|
||||
|
||||
go Cleanup()
|
||||
go Cleanup(time.Second * 600)
|
||||
|
||||
app := fiber.New(config)
|
||||
app.Use(recover.New())
|
||||
|
||||
90
storage.go
90
storage.go
@@ -4,109 +4,122 @@ import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type serializedPeer string
|
||||
type hash [20]byte
|
||||
type Hash [20]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
|
||||
|
||||
func NewPeer(ip net.IP, port uint16) (peer Peer) {
|
||||
copy(peer[:], ip)
|
||||
peer[16] = byte(port >> 8)
|
||||
peer[17] = byte(port)
|
||||
return
|
||||
}
|
||||
|
||||
func (peer Peer) String() string {
|
||||
return fmt.Sprintf("%s:%d", net.IP(peer[:16]), binary.BigEndian.Uint16(peer[16:]))
|
||||
}
|
||||
|
||||
// type serializedPeer [18]byte
|
||||
|
||||
type shard struct {
|
||||
swarms map[hash]swarm
|
||||
swarms map[Hash]swarm
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type swarm struct {
|
||||
seeders map[serializedPeer]int64
|
||||
leechers map[serializedPeer]int64
|
||||
seeders map[Peer]int64
|
||||
leechers map[Peer]int64
|
||||
}
|
||||
|
||||
var shards = NewShards(512)
|
||||
var v4InV6Prefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}
|
||||
|
||||
func shardIndex(hash [20]byte) int {
|
||||
return int(binary.BigEndian.Uint32(hash[:4])) % len(shards)
|
||||
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),
|
||||
swarms: make(map[Hash]swarm),
|
||||
}
|
||||
}
|
||||
return shards
|
||||
}
|
||||
|
||||
func serialize(ip string, port uint16) serializedPeer {
|
||||
return serializedPeer(append(net.ParseIP(ip), byte(port>>8), byte(port)))
|
||||
}
|
||||
// func serialize(ip net.IP, port uint16) serializedPeer {
|
||||
// var sp serializedPeer
|
||||
// copy(sp[:], ip)
|
||||
// sp[16] = byte(port >> 8)
|
||||
// sp[17] = byte(port)
|
||||
// return sp
|
||||
// }
|
||||
|
||||
func PutPeer(room, infoHash, ip string, port uint16, seeding bool) {
|
||||
func PutPeer(room, infoHash string, peer Peer, seeding bool) {
|
||||
h := sha1.Sum([]byte(room + infoHash))
|
||||
shard := shards[shardIndex(h)]
|
||||
shard.Lock()
|
||||
if _, ok := shard.swarms[h]; !ok {
|
||||
shard.swarms[h] = swarm{
|
||||
seeders: make(map[serializedPeer]int64),
|
||||
leechers: make(map[serializedPeer]int64),
|
||||
seeders: make(map[Peer]int64),
|
||||
leechers: make(map[Peer]int64),
|
||||
}
|
||||
}
|
||||
client := serialize(ip, port)
|
||||
if seeding {
|
||||
shard.swarms[h].seeders[client] = time.Now().Unix()
|
||||
shard.swarms[h].seeders[peer] = time.Now().Unix()
|
||||
} else {
|
||||
shard.swarms[h].leechers[client] = time.Now().Unix()
|
||||
shard.swarms[h].leechers[peer] = time.Now().Unix()
|
||||
}
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
func DeletePeer(room, infoHash, ip string, port uint16) {
|
||||
func DeletePeer(room, infoHash string, peer Peer) {
|
||||
h := sha1.Sum([]byte(room + infoHash))
|
||||
shard := shards[shardIndex(h)]
|
||||
shard.Lock()
|
||||
if _, ok := shard.swarms[h]; !ok {
|
||||
return
|
||||
}
|
||||
client := serialize(ip, port)
|
||||
delete(shard.swarms[h].seeders, client)
|
||||
delete(shard.swarms[h].leechers, client)
|
||||
delete(shard.swarms[h].seeders, peer)
|
||||
delete(shard.swarms[h].leechers, peer)
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
func GraduateLeecher(room, infoHash, ip string, port uint16) {
|
||||
func GraduateLeecher(room, infoHash string, peer Peer) {
|
||||
h := sha1.Sum([]byte(room + infoHash))
|
||||
shard := shards[shardIndex(h)]
|
||||
shard.Lock()
|
||||
if _, ok := shard.swarms[h]; !ok {
|
||||
shard.swarms[h] = swarm{
|
||||
seeders: make(map[serializedPeer]int64),
|
||||
leechers: make(map[serializedPeer]int64),
|
||||
seeders: make(map[Peer]int64),
|
||||
leechers: make(map[Peer]int64),
|
||||
}
|
||||
}
|
||||
client := serialize(ip, port)
|
||||
shard.swarms[h].seeders[client] = time.Now().Unix()
|
||||
delete(shard.swarms[h].leechers, client)
|
||||
shard.swarms[h].seeders[peer] = time.Now().Unix()
|
||||
delete(shard.swarms[h].leechers, peer)
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
func GetPeers(room, infoHash, ip string, port uint16, seeding bool, numWant uint) (peersIPv4, peersIPv6 []byte, numSeeders, numLeechers int) {
|
||||
func GetPeers(room, infoHash string, client Peer, seeding bool, numWant uint) (peersIPv4, peersIPv6 []byte, numSeeders, numLeechers int) {
|
||||
h := sha1.Sum([]byte(room + infoHash))
|
||||
shard := shards[shardIndex(h)]
|
||||
shard.RLock()
|
||||
client := serialize(ip, port)
|
||||
// seeders don't need other seeders
|
||||
if !seeding {
|
||||
for peer := range shard.swarms[h].seeders {
|
||||
if numWant == 0 {
|
||||
break
|
||||
}
|
||||
if bytes.HasPrefix([]byte(peer), v4InV6Prefix) {
|
||||
peersIPv4 = append(peersIPv4, peer[12:]...)
|
||||
if bytes.HasPrefix(peer[:], v4InV6Prefix) {
|
||||
peersIPv4 = append(peersIPv4, peer[len(v4InV6Prefix):]...)
|
||||
} else {
|
||||
peersIPv6 = append(peersIPv6, peer...)
|
||||
peersIPv6 = append(peersIPv6, peer[:]...)
|
||||
}
|
||||
numWant--
|
||||
}
|
||||
@@ -118,10 +131,10 @@ func GetPeers(room, infoHash, ip string, port uint16, seeding bool, numWant uint
|
||||
if numWant == 0 {
|
||||
break
|
||||
}
|
||||
if bytes.HasPrefix([]byte(peer), v4InV6Prefix) {
|
||||
if bytes.HasPrefix(peer[:], v4InV6Prefix) {
|
||||
peersIPv4 = append(peersIPv4, peer[12:]...)
|
||||
} else {
|
||||
peersIPv6 = append(peersIPv6, peer...)
|
||||
peersIPv6 = append(peersIPv6, peer[:]...)
|
||||
}
|
||||
numWant--
|
||||
}
|
||||
@@ -141,10 +154,10 @@ func GetStats(room, infoHash string) (numSeeders, numLeechers int) {
|
||||
return
|
||||
}
|
||||
|
||||
func Cleanup() {
|
||||
for {
|
||||
time.Sleep(time.Second * 600)
|
||||
expiration := time.Now().Unix() - 600
|
||||
func Cleanup(duration time.Duration) {
|
||||
ticker := time.NewTicker(duration)
|
||||
for range ticker.C {
|
||||
expiration := time.Now().Unix() - int64(duration.Seconds())
|
||||
for _, shard := range shards {
|
||||
shard.Lock()
|
||||
for h, swarm := range shard.swarms {
|
||||
@@ -164,6 +177,5 @@ func Cleanup() {
|
||||
}
|
||||
shard.Unlock()
|
||||
}
|
||||
runtime.GC()
|
||||
}
|
||||
}
|
||||
|
||||
14
storage_test.go
Normal file
14
storage_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkPutPeerGetPeers(b *testing.B) {
|
||||
peer := NewPeer(net.ParseIP("127.0.0.1"), 6881)
|
||||
for i := 0; i < b.N; i++ {
|
||||
PutPeer("room", "infoHash", peer, true)
|
||||
GetPeers("room", "infoHash", peer, true, 99)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user