mirror of
https://github.com/YGGverse/btracker-scrape.git
synced 2026-03-31 17:15:34 +00:00
initial commit
This commit is contained in:
parent
99c16e6d4c
commit
4a5b82bf39
7 changed files with 184 additions and 0 deletions
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
custom: https://yggverse.github.io/#donate
|
||||||
25
.github/workflows/build.yml
vendored
Normal file
25
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
RUSTFLAGS: -Dwarnings
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: rustup update
|
||||||
|
- run: cargo update
|
||||||
|
- run: cargo fmt --all -- --check
|
||||||
|
- run: cargo clippy --all-targets
|
||||||
|
- run: cargo build --verbose
|
||||||
|
- run: cargo test --verbose
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "btracker-scrape"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = "MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
description = "Shared BitTorrent scrape API for the βtracker project components"
|
||||||
|
keywords = ["btracker", "bittorrent", "scrape", "udp", "tcp"]
|
||||||
|
categories = ["network-programming"]
|
||||||
|
repository = "https://github.com/YGGverse/btracker-scrape"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.9.2"
|
||||||
13
README.md
Normal file
13
README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# btracker-scrape
|
||||||
|
|
||||||
|

|
||||||
|
[](https://deps.rs/repo/github/YGGverse/btracker-scrape)
|
||||||
|
[](https://crates.io/crates/btracker-scrape)
|
||||||
|
|
||||||
|
Shared BitTorrent scrape API for the βtracker project components
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
cargo add btracker-scrape
|
||||||
|
```
|
||||||
36
src/lib.rs
Normal file
36
src/lib.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
mod udp;
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use udp::Udp;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Result {
|
||||||
|
pub leechers: u32,
|
||||||
|
pub peers: u32,
|
||||||
|
pub seeders: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Scrape {
|
||||||
|
udp: Option<Udp>,
|
||||||
|
// tcp: @TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scrape {
|
||||||
|
pub fn init(udp: Option<(Vec<SocketAddr>, Vec<SocketAddr>)>) -> Self {
|
||||||
|
Self {
|
||||||
|
udp: udp.map(|(local, remote)| Udp::init(local, remote)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scrape(&self, info_hash: [u8; 20]) -> Option<Result> {
|
||||||
|
self.udp.as_ref()?;
|
||||||
|
let mut t = Result::default();
|
||||||
|
if let Some(ref u) = self.udp {
|
||||||
|
let r = u.scrape(info_hash).ok()?; // @TODO handle
|
||||||
|
t.leechers += r.leechers;
|
||||||
|
t.peers += r.peers;
|
||||||
|
t.seeders += r.seeders;
|
||||||
|
}
|
||||||
|
Some(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/udp.rs
Normal file
94
src/udp.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
use rand::Rng;
|
||||||
|
use std::{
|
||||||
|
io::Error,
|
||||||
|
net::{SocketAddr, UdpSocket},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Route {
|
||||||
|
socket: UdpSocket,
|
||||||
|
remote: Vec<SocketAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Udp(Vec<Route>);
|
||||||
|
|
||||||
|
impl Udp {
|
||||||
|
pub fn init(local: Vec<SocketAddr>, remote: Vec<SocketAddr>) -> Self {
|
||||||
|
Self(
|
||||||
|
local
|
||||||
|
.into_iter()
|
||||||
|
.map(|l| {
|
||||||
|
let socket = UdpSocket::bind(l).unwrap();
|
||||||
|
socket
|
||||||
|
.set_read_timeout(Some(Duration::from_secs(3)))
|
||||||
|
.unwrap();
|
||||||
|
Route {
|
||||||
|
socket,
|
||||||
|
remote: if l.is_ipv4() {
|
||||||
|
remote.iter().filter(|r| r.is_ipv4()).cloned().collect()
|
||||||
|
} else {
|
||||||
|
remote.iter().filter(|r| r.is_ipv6()).cloned().collect()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scrape(&self, info_hash: [u8; 20]) -> Result<super::Result, Error> {
|
||||||
|
let mut t = super::Result::default();
|
||||||
|
for route in &self.0 {
|
||||||
|
for remote in &route.remote {
|
||||||
|
route.socket.send_to(&connection_request(), remote)?;
|
||||||
|
|
||||||
|
let mut b = [0u8; 16];
|
||||||
|
if route.socket.recv(&mut b)? < 16 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
route.socket.send_to(
|
||||||
|
&scrape_request(
|
||||||
|
u64::from_be_bytes(b[8..16].try_into().unwrap()),
|
||||||
|
rand::rng().random::<u32>(),
|
||||||
|
&[info_hash],
|
||||||
|
),
|
||||||
|
remote,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut b = [0u8; 1024];
|
||||||
|
let l = route.socket.recv(&mut b)?;
|
||||||
|
if l < 20 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.seeders += u32::from_be_bytes(b[8..12].try_into().unwrap());
|
||||||
|
t.leechers += u32::from_be_bytes(b[12..16].try_into().unwrap());
|
||||||
|
t.peers += u32::from_be_bytes(b[16..20].try_into().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connection_request() -> Vec<u8> {
|
||||||
|
let mut b = Vec::new();
|
||||||
|
b.extend_from_slice(&0x41727101980u64.to_be_bytes());
|
||||||
|
b.extend_from_slice(&0u32.to_be_bytes());
|
||||||
|
b.extend_from_slice(&rand::rng().random::<u32>().to_be_bytes());
|
||||||
|
b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scrape_request(connection_id: u64, transaction_id: u32, info_hashes: &[[u8; 20]]) -> Vec<u8> {
|
||||||
|
let mut b = Vec::new();
|
||||||
|
b.extend_from_slice(&connection_id.to_be_bytes());
|
||||||
|
b.extend_from_slice(&2u32.to_be_bytes());
|
||||||
|
b.extend_from_slice(&transaction_id.to_be_bytes());
|
||||||
|
// * up to about 74 torrents can be scraped at once
|
||||||
|
// https://www.bittorrent.org/beps/bep_0015.html
|
||||||
|
if info_hashes.len() > 74 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
for hash in info_hashes {
|
||||||
|
b.extend_from_slice(hash);
|
||||||
|
}
|
||||||
|
b
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue