Created
June 27, 2024 12:49
-
-
Save liamzebedee/4145b51f7cba2f9c24e516d8a778634c to your computer and use it in GitHub Desktop.
Revisions
-
liamzebedee created this gist
Jun 27, 2024 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,291 @@ // 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 }