Skip to content

Commit 60a9f29

Browse files
committed
feat: [torrust#1096] ban client IP when exxecds connection ID erros limit
If the client does not send the rigth conenction ID more than 10 times it's banned. In this first implementation after sending 10 times a wrong connection ID. They are only unabnned when the tracker is restarted.
1 parent adfc9d4 commit 60a9f29

File tree

4 files changed

+36
-7
lines changed

4 files changed

+36
-7
lines changed

cSpell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"alekitto",
66
"appuser",
77
"Arvid",
8+
"ASMS",
89
"asyn",
910
"autoclean",
1011
"AUTOINCREMENT",

console/tracker-client/src/console/clients/udp/app.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ use std::net::{SocketAddr, ToSocketAddrs};
6060
use std::str::FromStr;
6161

6262
use anyhow::Context;
63-
use aquatic_udp_protocol::{Response, TransactionId};
63+
use aquatic_udp_protocol::{ConnectionId, Response, TransactionId};
6464
use bittorrent_primitives::info_hash::InfoHash as TorrustInfoHash;
6565
use clap::{Parser, Subcommand};
6666
use torrust_tracker_configuration::DEFAULT_TIMEOUT;
@@ -136,9 +136,11 @@ async fn handle_announce(remote_addr: SocketAddr, info_hash: &TorrustInfoHash) -
136136

137137
let client = checker::Client::new(remote_addr, DEFAULT_TIMEOUT).await?;
138138

139-
let connection_id = client.send_connection_request(transaction_id).await?;
139+
let _connection_id = client.send_connection_request(transaction_id).await?;
140+
141+
let new_connection_id = ConnectionId::new(100);
140142

141-
client.send_announce_request(transaction_id, connection_id, *info_hash).await
143+
client.send_announce_request(transaction_id, new_connection_id, *info_hash).await
142144
}
143145

144146
async fn handle_scrape(remote_addr: SocketAddr, info_hashes: &[TorrustInfoHash]) -> Result<Response, Error> {

src/servers/udp/server/launcher.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use std::sync::Arc;
33
use std::time::Duration;
44

55
use bittorrent_tracker_client::udp::client::check;
6+
use bloom::CountingBloomFilter;
67
use derive_more::Constructor;
78
use futures_util::StreamExt;
89
use tokio::select;
9-
use tokio::sync::oneshot;
10+
use tokio::sync::{oneshot, RwLock};
1011
use tracing::instrument;
1112

1213
use super::request_buffer::ActiveRequests;
@@ -20,6 +21,10 @@ use crate::servers::udp::server::processor::Processor;
2021
use crate::servers::udp::server::receiver::Receiver;
2122
use crate::servers::udp::UDP_TRACKER_LOG_TARGET;
2223

24+
/// The maximum number of connection id errors per ip. Clients will be banned if
25+
/// they exceed this limit.
26+
const MAX_CONNECTION_ID_ERRORS_PER_IP: u32 = 10;
27+
2328
/// A UDP server instance launcher.
2429
#[derive(Constructor)]
2530
pub struct Launcher;
@@ -114,6 +119,10 @@ impl Launcher {
114119
async fn run_udp_server_main(mut receiver: Receiver, tracker: Arc<Tracker>, cookie_lifetime: Duration) {
115120
let active_requests = &mut ActiveRequests::default();
116121

122+
// Create a counting bloom filter that uses 4 bits per element and has a
123+
// false positive rate of 0.01 when 100 items have been inserted
124+
let cbf = Arc::new(RwLock::new(CountingBloomFilter::with_rate(4, 0.01, 100)));
125+
117126
let addr = receiver.bound_socket_address();
118127
let local_addr = format!("udp://{addr}");
119128

@@ -140,6 +149,13 @@ impl Launcher {
140149
}
141150
};
142151

152+
let connection_id_errors_from_ip = cbf.read().await.estimate_count(&req.from.ip().to_string());
153+
154+
if connection_id_errors_from_ip > MAX_CONNECTION_ID_ERRORS_PER_IP {
155+
tracing::debug!(target: UDP_TRACKER_LOG_TARGET, local_addr, "Udp::run_udp_server::loop continue: (banned ip)");
156+
continue;
157+
}
158+
143159
// We spawn the new task even if there active requests buffer is
144160
// full. This could seem counterintuitive because we are accepting
145161
// more request and consuming more memory even if the server is
@@ -151,7 +167,8 @@ impl Launcher {
151167
// are only adding and removing tasks without given them the
152168
// chance to finish. However, the buffer is yielding before
153169
// aborting one tasks, giving it the chance to finish.
154-
let abort_handle: tokio::task::AbortHandle = tokio::task::spawn(processor.process_request(req)).abort_handle();
170+
let abort_handle: tokio::task::AbortHandle =
171+
tokio::task::spawn(processor.process_request(req, cbf.clone())).abort_handle();
155172

156173
if abort_handle.is_finished() {
157174
continue;

src/servers/udp/server/processor.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::net::SocketAddr;
33
use std::sync::Arc;
44

55
use aquatic_udp_protocol::Response;
6+
use bloom::{CountingBloomFilter, ASMS};
7+
use tokio::sync::RwLock;
68
use tracing::{instrument, Level};
79

810
use super::bound_socket::BoundSocket;
@@ -25,8 +27,8 @@ impl Processor {
2527
}
2628
}
2729

28-
#[instrument(skip(self, request))]
29-
pub async fn process_request(self, request: RawRequest) {
30+
#[instrument(skip(self, request, cbf))]
31+
pub async fn process_request(self, request: RawRequest, cbf: Arc<RwLock<CountingBloomFilter>>) {
3032
let from = request.from;
3133
let response = handlers::handle_packet(
3234
request,
@@ -35,6 +37,13 @@ impl Processor {
3537
CookieTimeValues::new(self.cookie_lifetime),
3638
)
3739
.await;
40+
41+
if let Response::Error(err) = &response {
42+
if err.message.contains("cookie value is expired") || err.message.contains("cookie value is from future") {
43+
cbf.write().await.insert(&from.ip().to_string());
44+
}
45+
}
46+
3847
self.send_response(from, response).await;
3948
}
4049

0 commit comments

Comments
 (0)