Created
June 27, 2024 12:49
-
-
Save liamzebedee/4145b51f7cba2f9c24e516d8a778634c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://wiki.theory.org/BitTorrent_Tracker_Protocol | |
// https://www.bittorrent.org/beps/bep_0015.html | |
package core | |
import ( | |
"bytes" | |
"encoding/binary" | |
"fmt" | |
"math/rand" | |
"net" | |
"time" | |
) | |
const ( | |
protocolID = 0x41727101980 | |
actionConnect = 0 | |
actionAnnounce = 1 | |
actionScrape = 2 | |
) | |
type ConnectRequest struct { | |
ProtocolID int64 | |
Action int32 | |
TransactionID int32 | |
} | |
type ConnectResponse struct { | |
Action int32 | |
TransactionID int32 | |
ConnectionID int64 | |
} | |
type AnnounceRequest struct { | |
ConnectionID int64 | |
Action int32 | |
TransactionID int32 | |
InfoHash [20]byte | |
PeerID [20]byte | |
Downloaded int64 | |
Left int64 | |
Uploaded int64 | |
Event int32 | |
IPAddress int32 | |
Key int32 | |
NumWant int32 | |
Port int16 | |
} | |
type AnnounceResponse struct { | |
Action int32 | |
TransactionID int32 | |
Interval int32 | |
Leechers int32 | |
Seeders int32 | |
IPs []Peer | |
} | |
type ScrapeRequest struct { | |
ConnectionID int64 | |
Action int32 | |
TransactionID int32 | |
InfoHashes [][20]byte | |
} | |
type ScrapeResponse struct { | |
Action int32 | |
TransactionID int32 | |
Torrents []TorrentStats | |
} | |
type TorrentStats struct { | |
Seeders int32 | |
Completed int32 | |
Leechers int32 | |
} | |
type Peer struct { | |
IP net.IP | |
Port uint16 | |
} | |
func RunTrackerDemo(infohash [20]byte, peerId [20]byte) { | |
trackerAddr := "tracker.opentrackr.org:1337" | |
conn, err := net.Dial("udp", trackerAddr) | |
if err != nil { | |
fmt.Println("Error connecting to tracker:", err) | |
return | |
} | |
defer conn.Close() | |
// Step 1: Connect | |
transactionID := rand.Int31() | |
connectReq := ConnectRequest{ | |
ProtocolID: protocolID, | |
Action: actionConnect, | |
TransactionID: transactionID, | |
} | |
if err := sendConnectRequest(conn, connectReq); err != nil { | |
fmt.Println("Error sending connect request:", err) | |
return | |
} | |
connectResp, err := receiveConnectResponse(conn, transactionID) | |
if err != nil { | |
fmt.Println("Error receiving connect response:", err) | |
return | |
} | |
// Step 2: Announce | |
announceReq := AnnounceRequest{ | |
ConnectionID: connectResp.ConnectionID, | |
Action: actionAnnounce, | |
TransactionID: rand.Int31(), | |
InfoHash: infohash, | |
PeerID: peerId, | |
Downloaded: 0, | |
Left: 100, | |
Uploaded: 0, | |
Event: 0, | |
IPAddress: 0, | |
Key: rand.Int31(), | |
NumWant: -1, | |
Port: 6881, | |
} | |
if err := sendAnnounceRequest(conn, announceReq); err != nil { | |
fmt.Println("Error sending announce request:", err) | |
return | |
} | |
announceResp, err := receiveAnnounceResponse(conn, announceReq.TransactionID) | |
if err != nil { | |
fmt.Println("Error receiving announce response:", err) | |
return | |
} | |
fmt.Printf("Received Announce Response: %+v\n", announceResp) | |
} | |
func sendConnectRequest(conn net.Conn, req ConnectRequest) error { | |
buf := new(bytes.Buffer) | |
if err := binary.Write(buf, binary.BigEndian, req); err != nil { | |
return err | |
} | |
_, err := conn.Write(buf.Bytes()) | |
return err | |
} | |
func receiveConnectResponse(conn net.Conn, transactionID int32) (*ConnectResponse, error) { | |
buf := make([]byte, 16) | |
conn.SetReadDeadline(time.Now().Add(15 * time.Second)) | |
if _, err := conn.Read(buf); err != nil { | |
return nil, err | |
} | |
var resp ConnectResponse | |
if err := binary.Read(bytes.NewReader(buf), binary.BigEndian, &resp); err != nil { | |
return nil, err | |
} | |
if resp.TransactionID != transactionID { | |
return nil, fmt.Errorf("transaction ID mismatch") | |
} | |
return &resp, nil | |
} | |
func sendAnnounceRequest(conn net.Conn, req AnnounceRequest) error { | |
buf := new(bytes.Buffer) | |
if err := binary.Write(buf, binary.BigEndian, req); err != nil { | |
return err | |
} | |
_, err := conn.Write(buf.Bytes()) | |
return err | |
} | |
func receiveAnnounceResponse(conn net.Conn, transactionID int32) (*AnnounceResponse, error) { | |
buf := make([]byte, 1024) // Adjust buffer size as needed | |
conn.SetReadDeadline(time.Now().Add(15 * time.Second)) | |
n, err := conn.Read(buf) | |
if err != nil { | |
return nil, err | |
} | |
if n < 20 { | |
return nil, fmt.Errorf("announce response too short") | |
} | |
var action, txnID, interval, leechers, seeders int32 | |
r := bytes.NewReader(buf[:20]) | |
if err := binary.Read(r, binary.BigEndian, &action); err != nil { | |
return nil, err | |
} | |
if err := binary.Read(r, binary.BigEndian, &txnID); err != nil { | |
return nil, err | |
} | |
if err := binary.Read(r, binary.BigEndian, &interval); err != nil { | |
return nil, err | |
} | |
if err := binary.Read(r, binary.BigEndian, &leechers); err != nil { | |
return nil, err | |
} | |
if err := binary.Read(r, binary.BigEndian, &seeders); err != nil { | |
return nil, err | |
} | |
if txnID != transactionID { | |
return nil, fmt.Errorf("transaction ID mismatch") | |
} | |
numPeers := (n - 20) / 6 | |
peers := make([]Peer, numPeers) | |
for i := 0; i < numPeers; i++ { | |
ip := net.IPv4(buf[20+6*i], buf[21+6*i], buf[22+6*i], buf[23+6*i]) | |
port := binary.BigEndian.Uint16(buf[24+6*i : 26+6*i]) | |
peers[i] = Peer{IP: ip, Port: port} | |
} | |
return &AnnounceResponse{ | |
Action: action, | |
TransactionID: txnID, | |
Interval: interval, | |
Leechers: leechers, | |
Seeders: seeders, | |
IPs: peers, | |
}, nil | |
} | |
func sendScrapeRequest(conn net.Conn, req ScrapeRequest) error { | |
buf := new(bytes.Buffer) | |
if err := binary.Write(buf, binary.BigEndian, req.ConnectionID); err != nil { | |
return err | |
} | |
if err := binary.Write(buf, binary.BigEndian, req.Action); err != nil { | |
return err | |
} | |
if err := binary.Write(buf, binary.BigEndian, req.TransactionID); err != nil { | |
return err | |
} | |
for _, hash := range req.InfoHashes { | |
if err := binary.Write(buf, binary.BigEndian, hash); err != nil { | |
return err | |
} | |
} | |
_, err := conn.Write(buf.Bytes()) | |
return err | |
} | |
func receiveScrapeResponse(conn net.Conn, transactionID int32) (*ScrapeResponse, error) { | |
buf := make([]byte, 1024) // Adjust buffer size as needed | |
conn.SetReadDeadline(time.Now().Add(15 * time.Second)) | |
n, err := conn.Read(buf) | |
if err != nil { | |
return nil, err | |
} | |
if n < 8 { | |
return nil, fmt.Errorf("scrape response too short") | |
} | |
var action, txnID int32 | |
r := bytes.NewReader(buf[:8]) | |
if err := binary.Read(r, binary.BigEndian, &action); err != nil { | |
return nil, err | |
} | |
if err := binary.Read(r, binary.BigEndian, &txnID); err != nil { | |
return nil, err | |
} | |
if txnID != transactionID { | |
return nil, fmt.Errorf("transaction ID mismatch") | |
} | |
numTorrents := (n - 8) / 12 | |
torrents := make([]TorrentStats, numTorrents) | |
for i := 0; i < numTorrents; i++ { | |
offset := 8 + i*12 | |
torrents[i] = TorrentStats{ | |
Seeders: int32(binary.BigEndian.Uint32(buf[offset:])), | |
Completed: int32(binary.BigEndian.Uint32(buf[offset+4:])), | |
Leechers: int32(binary.BigEndian.Uint32(buf[offset+8:])), | |
} | |
} | |
return &ScrapeResponse{ | |
Action: action, | |
TransactionID: txnID, | |
Torrents: torrents, | |
}, nil | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment