Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
672864e
feat: print announce request in debug log
mickvandijke Mar 11, 2022
9637661
refactor: made all fields optional in http announce request except pe…
mickvandijke Mar 14, 2022
f2125f2
refactor: return http announce errors in bencoded format instead of json
mickvandijke Mar 14, 2022
f596f22
Added: Separate HTTP/HTTPS/IPv4/IPv6 binding of sockets, persistant s…
Power2All Mar 15, 2022
4cf58f9
Merge pull request #20 from Power2All/development
mickvandijke Mar 15, 2022
afe75a7
Changing the static binding options to a dynamical one, you can now b…
Power2All Mar 15, 2022
16f9d8a
Readme updated with latest changes so far
Power2All Mar 15, 2022
b91c5e4
Fixing a nasty peer_id bug
Power2All Mar 15, 2022
ca2d118
Fixing the peer timeout for both seed and peers, and put the config i…
Power2All Mar 15, 2022
e1ccb1b
Revert "Fixing a nasty peer_id bug"
Power2All Mar 15, 2022
5a07517
Applied the Peer_ID fix from master of torrust tracker to development
Power2All Mar 15, 2022
18e7057
Removed a unneeded function
Power2All Mar 15, 2022
74649d4
Trying to debug
Power2All Mar 15, 2022
d3e9688
Solving a bug
Power2All Mar 15, 2022
f478f3b
Trying to fix again a bug
Power2All Mar 15, 2022
b0417a3
Prevent error when no peers with udp
Power2All Mar 16, 2022
f6eb853
First version of stats tracking, but needs improvements
Power2All Mar 16, 2022
0dd225a
Merge pull request #21 from Power2All/development
mickvandijke Mar 16, 2022
25e9476
refactor: replace error message on empty peers with empty vec
mickvandijke Mar 16, 2022
5a07d89
refactor: updated to newest aquatic_udp_protocol
mickvandijke Mar 16, 2022
9d6db19
fix: no more panic on http scrape on_reverse_proxy but no forwarded_ip
mickvandijke Mar 16, 2022
b34f564
fix: now able to get ipv6 peers from ipv6 remote_addr
mickvandijke Mar 16, 2022
ab60545
refactor: refactored persistent torrent loading/saving
mickvandijke Mar 16, 2022
0e0df0e
Conflict fixed, added min interval to response, and a proper exiting …
Power2All Mar 17, 2022
37fe6fd
Updated README to reflect changes
Power2All Mar 17, 2022
65834bd
Version bump to 2.2.0
Power2All Mar 17, 2022
495a3b4
Merge pull request #22 from Power2All/development
mickvandijke Mar 17, 2022
cd0473c
refactor: optimized http (reverse proxy) ip determination
mickvandijke Mar 17, 2022
660a555
chore: updated readme with the latest example config
mickvandijke Mar 17, 2022
0bfc7b7
chore: updated readme with BEP's
mickvandijke Mar 17, 2022
9f601a5
Debugging a issue where API wouldn't bind...
Power2All Mar 17, 2022
e808826
Merge branch 'torrust:development' into development
Power2All Mar 17, 2022
2954380
Merge pull request #23 from Power2All/development
mickvandijke Mar 17, 2022
2e25d53
fix: (HTTP) getting the peer address from x-forwarded-for when it is …
mickvandijke Mar 17, 2022
a220478
Fixing a bug in return data
Power2All Mar 17, 2022
dd600c0
Merge branch 'torrust:development' into development
Power2All Mar 17, 2022
f0c0e95
Merge pull request #24 from Power2All/development
mickvandijke Mar 17, 2022
f46df38
feat: gracefully shutdown udp servers
mickvandijke Mar 17, 2022
f9eaa10
refactor: changed udp max packet size from 65535 to 1496
mickvandijke Mar 17, 2022
9988c64
Merge branch 'main' into development
mickvandijke Mar 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
460 changes: 25 additions & 435 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 3 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "torrust-tracker"
version = "2.1.2"
version = "2.2.0"
license = "AGPL-3.0"
authors = ["Mick van Dijke <mick@dutchbits.nl>"]
description = "A feature rich BitTorrent tracker."
Expand All @@ -24,13 +24,11 @@ log = {version = "0.4", features = ["release_max_level_info"]}
fern = "0.6"
chrono = "0.4"
byteorder = "1"
external-ip = "4.1.0"
r2d2_sqlite = "0.16.0"
r2d2 = "0.8.8"
rand = "0.8.4"
env_logger = "0.9.0"
config = "0.11"
derive_more = "0.99"
thiserror = "1.0"

aquatic_udp_protocol = "0.1.0"
aquatic_udp_protocol = { git = "https://github.com/greatest-ape/aquatic" }
futures = "0.3.21"
32 changes: 22 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ Torrust Tracker is a lightweight but incredibly powerful and feature-rich BitTor


### Features
* [X] UDP server
* [X] HTTP (optional SSL) server
* [X] Multiple UDP server and HTTP(S) server blocks for socket binding possible
* [X] Full IPv4 and IPv6 support for both UDP and HTTP(S)
* [X] Private & Whitelisted mode
* [X] Built-in API
* [X] Torrent whitelisting
* [X] Peer authentication using time-bound keys
* [X] newTrackon check supported for both HTTP, UDP, where IPv4 and IPv6 is properly handled
* [X] SQLite3 Persistent loading and saving of the torrent hashes and completed count

### Implemented BEPs
* [BEP 3](https://www.bittorrent.org/beps/bep_0003.html): The BitTorrent Protocol
Expand Down Expand Up @@ -46,22 +48,31 @@ cargo build --release

* Edit the newly created config.toml file according to your liking, see [configuration documentation](https://torrust.github.io/torrust-documentation/torrust-tracker/config/). Eg:
```toml
log_level = "trace"
log_level = "info"
mode = "public"
db_path = "data.db"
persistence = false
cleanup_interval = 600
external_ip = "YOUR_EXTERNAL_IP"
cleanup_peerless = true
external_ip = "0.0.0.0"
announce_interval = 120
announce_interval_min = 900
peer_timeout = 900
on_reverse_proxy = false

[udp_tracker]
[[udp_trackers]]
enabled = false
bind_address = "0.0.0.0:6969"
announce_interval = 120

[http_tracker]
[[udp_trackers]]
enabled = true
bind_address = "[::]:6969"

[[http_trackers]]
enabled = true
bind_address = "0.0.0.0:6969"
on_reverse_proxy = false
announce_interval = 120
ssl_enabled = false
ssl_bind_address = "0.0.0.0:6868"
ssl_cert_path = ""
ssl_key_path = ""

Expand All @@ -80,7 +91,7 @@ admin = "MyAccessToken"
```

### Tracker URL
Your tracker announce URL will be **udp://{tracker-ip:port}** or **https://{tracker-ip:port}/announce** depending on your tracker mode.
Your tracker announce URL will be **udp://{tracker-ip:port}** and/or **http://{tracker-ip:port}/announce** and/or **https://{tracker-ip:port}/announce** depending on your bindings.
In private & private_listed mode, tracker keys are added after the tracker URL like: **https://{tracker-ip:port}/announce/{key}**.

### Built-in API
Expand All @@ -89,3 +100,4 @@ Read the API documentation [here](https://torrust.github.io/torrust-documentatio
### Credits
This project was a joint effort by [Nautilus Cyberneering GmbH](https://nautilus-cyberneering.de/) and [Dutch Bits](https://dutchbits.nl).
Also thanks to [Naim A.](https://github.com/naim94a/udpt) and [greatest-ape](https://github.com/greatest-ape/aquatic) for some parts of the code.
Further added features and functions thanks to [Power2All](https://github.com/power2all).
9 changes: 6 additions & 3 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ impl serde::ser::Serialize for InfoHash {
let mut buffer = [0u8; 40];
let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok().unwrap();
let str_out = std::str::from_utf8(bytes_out).unwrap();

serializer.serialize_str(str_out)
}
}
Expand Down Expand Up @@ -131,8 +130,12 @@ pub struct PeerId(pub [u8; 20]);
impl PeerId {
pub fn to_string(&self) -> String {
let mut buffer = [0u8; 20];
let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok().unwrap();
String::from(std::str::from_utf8(bytes_out).unwrap())
let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok();
return if let Some(bytes_out) = bytes_out {
String::from(std::str::from_utf8(bytes_out).unwrap())
} else {
"".to_string()
}
}
}

Expand Down
67 changes: 38 additions & 29 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@ pub enum TrackerServer {

#[derive(Serialize, Deserialize)]
pub struct UdpTrackerConfig {
pub enabled: bool,
pub bind_address: String,
pub announce_interval: u32,
}

#[derive(Serialize, Deserialize)]
pub struct HttpTrackerConfig {
pub enabled: bool,
pub bind_address: String,
pub on_reverse_proxy: bool,
pub announce_interval: u32,
pub ssl_enabled: bool,
pub ssl_bind_address: String,
#[serde(serialize_with = "none_as_empty_string")]
pub ssl_cert_path: Option<String>,
#[serde(serialize_with = "none_as_empty_string")]
Expand All @@ -52,10 +51,16 @@ pub struct Configuration {
pub log_level: Option<String>,
pub mode: TrackerMode,
pub db_path: String,
pub persistence: bool,
pub cleanup_interval: Option<u64>,
pub cleanup_peerless: bool,
pub external_ip: Option<String>,
pub udp_tracker: UdpTrackerConfig,
pub http_tracker: HttpTrackerConfig,
pub announce_interval: u32,
pub announce_interval_min: u32,
pub peer_timeout: u32,
pub on_reverse_proxy: bool,
pub udp_trackers: Vec<UdpTrackerConfig>,
pub http_trackers: Vec<HttpTrackerConfig>,
pub http_api: HttpApiConfig,
}

Expand Down Expand Up @@ -124,36 +129,48 @@ impl Configuration {

impl Configuration {
pub fn default() -> Configuration {
Configuration {
let mut configuration = Configuration {
log_level: Option::from(String::from("info")),
mode: TrackerMode::PublicMode,
db_path: String::from("data.db"),
persistence: false,
cleanup_interval: Some(600),
cleanup_peerless: true,
external_ip: Some(String::from("0.0.0.0")),
udp_tracker: UdpTrackerConfig {
bind_address: String::from("0.0.0.0:6969"),
announce_interval: 120,
},
http_tracker: HttpTrackerConfig {
announce_interval: 120,
announce_interval_min: 120,
peer_timeout: 900,
on_reverse_proxy: false,
udp_trackers: Vec::new(),
http_trackers: Vec::new(),
http_api: HttpApiConfig {
enabled: true,
bind_address: String::from("127.0.0.1:1212"),
access_tokens: [(String::from("admin"), String::from("MyAccessToken"))].iter().cloned().collect(),
}
};
configuration.udp_trackers.push(
UdpTrackerConfig{
enabled: false,
bind_address: String::from("0.0.0.0:6969")
}
);
configuration.http_trackers.push(
HttpTrackerConfig{
enabled: false,
bind_address: String::from("0.0.0.0:6969"),
on_reverse_proxy: false,
announce_interval: 120,
ssl_enabled: false,
ssl_bind_address: String::from("0.0.0.0:6868"),
ssl_cert_path: None,
ssl_key_path: None
},
http_api: HttpApiConfig {
enabled: true,
bind_address: String::from("127.0.0.1:1212"),
access_tokens: [(String::from("admin"), String::from("MyAccessToken"))].iter().cloned().collect(),
},
}
}
);
configuration
}

pub fn verify(&self) -> Result<(), ConfigurationError> {
// UDP is not secure for sending private keys
if (self.mode == TrackerMode::PrivateMode || self.mode == TrackerMode::PrivateListedMode) && self.get_tracker_server() == TrackerServer::UDP {
if self.mode == TrackerMode::PrivateMode || self.mode == TrackerMode::PrivateListedMode {
return Err(ConfigurationError::TrackerModeIncompatible)
}

Expand Down Expand Up @@ -188,12 +205,4 @@ impl Configuration {
fs::write("config.toml", toml_string).expect("Could not write to file!");
Ok(())
}

pub fn get_tracker_server(&self) -> TrackerServer {
if self.http_tracker.enabled {
TrackerServer::HTTP
} else {
TrackerServer::UDP
}
}
}
50 changes: 48 additions & 2 deletions src/database.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{InfoHash, AUTH_KEY_LENGTH};
use std::collections::BTreeMap;
use crate::{InfoHash, AUTH_KEY_LENGTH, TorrentEntry};
use log::debug;
use r2d2_sqlite::{SqliteConnectionManager, rusqlite};
use r2d2::{Pool};
Expand Down Expand Up @@ -32,6 +33,13 @@ impl SqliteDatabase {
info_hash VARCHAR(20) NOT NULL UNIQUE
);".to_string();

let create_torrents_table = "
CREATE TABLE IF NOT EXISTS torrents (
id integer PRIMARY KEY AUTOINCREMENT,
info_hash VARCHAR(20) NOT NULL UNIQUE,
completed INTEGER DEFAULT 0 NOT NULL
);".to_string();

let create_keys_table = format!("
CREATE TABLE IF NOT EXISTS keys (
id integer PRIMARY KEY AUTOINCREMENT,
Expand All @@ -43,7 +51,15 @@ impl SqliteDatabase {
match conn.execute(&create_whitelist_table, NO_PARAMS) {
Ok(updated) => {
match conn.execute(&create_keys_table, NO_PARAMS) {
Ok(updated2) => Ok(updated + updated2),
Ok(updated2) => {
match conn.execute(&create_torrents_table, NO_PARAMS) {
Ok(updated3) => Ok(updated + updated2 + updated3),
Err(e) => {
debug!("{:?}", e);
Err(e)
}
}
}
Err(e) => {
debug!("{:?}", e);
Err(e)
Expand All @@ -57,6 +73,36 @@ impl SqliteDatabase {
}
}

pub async fn load_persistent_torrent_data(&self) -> Result<Vec<(InfoHash, u32)>, rusqlite::Error> {
let conn = self.pool.get().unwrap();
let mut stmt = conn.prepare("SELECT info_hash, completed FROM torrents")?;

let torrent_iter = stmt.query_map(NO_PARAMS, |row| {
let info_hash_string: String = row.get(0)?;
let info_hash = InfoHash::from_str(&info_hash_string).unwrap();
let completed: u32 = row.get(1)?;
Ok((info_hash, completed))
})?;

let torrents: Vec<(InfoHash, u32)> = torrent_iter.filter_map(|x| x.ok() ).collect();

Ok(torrents)
}

pub async fn save_persistent_torrent_data(&self, torrents: &BTreeMap<InfoHash, TorrentEntry>) -> Result<(), rusqlite::Error> {
let mut conn = self.pool.get().unwrap();
let db_transaction = conn.transaction()?;

for (info_hash, torrent_entry) in torrents {
let (_seeders, completed, _leechers) = torrent_entry.get_stats();
let _ = db_transaction.execute("INSERT OR REPLACE INTO torrents (info_hash, completed) VALUES (?, ?)", &[info_hash.to_string(), completed.to_string()]);
}

let _ = db_transaction.commit();

Ok(())
}

pub async fn get_info_hash_from_whitelist(&self, info_hash: &str) -> Result<InfoHash, rusqlite::Error> {
let conn = self.pool.get().unwrap();
let mut stmt = conn.prepare("SELECT info_hash FROM whitelist WHERE info_hash = ?")?;
Expand Down
Loading