Skip to content

Overhaul core Tracker: move authentication to http-tracker-core and udp-tracker-core #1275

@josecelano

Description

@josecelano

Parent issue: #1270

In the HTTP and UDP trackers authentication is done at the delivery layer. Since it's not handled by external frameworks we can move it to the http-tracker-core and udp-tracker-core. That way that code can be decoupled from the framework. For example, if we migrate the HTTP tracker from Axum web framework to ActixWeb we don't need to implement authentication again for ActixcWeb.

NOTICE: The UDP tracker is always public. "Authentication" is the validation of the connection cookie.

There are some comment with the prefix // todo: move authentication indicating where to make the refactor.

HTTP Tracker

Announce

#[allow(clippy::too_many_arguments)]
async fn handle_announce(
    core_config: &Arc<Core>,
    announce_handler: &Arc<AnnounceHandler>,
    authentication_service: &Arc<AuthenticationService>,
    whitelist_authorization: &Arc<whitelist::authorization::WhitelistAuthorization>,
    opt_http_stats_event_sender: &Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
    announce_request: &Announce,
    client_ip_sources: &ClientIpSources,
    maybe_key: Option<Key>,
) -> Result<AnnounceData, responses::error::Error> {
    // todo: move authentication inside `http_tracker_core::services::announce::handle_announce`

    // Authentication
    if core_config.private {
        match maybe_key {
            Some(key) => match authentication_service.authenticate(&key).await {
                Ok(()) => (),
                Err(error) => return Err(map_auth_error_to_error_response(&error)),
            },
            None => {
                return Err(responses::error::Error::from(auth::Error::MissingAuthKey {
                    location: Location::caller(),
                }))
            }
        }
    }

    http_tracker_core::services::announce::handle_announce(
        &core_config.clone(),
        &announce_handler.clone(),
        &authentication_service.clone(),
        &whitelist_authorization.clone(),
        &opt_http_stats_event_sender.clone(),
        announce_request,
        client_ip_sources,
    )
    .await
}

Scrape

#[allow(clippy::too_many_arguments)]
async fn handle_scrape(
    core_config: &Arc<Core>,
    scrape_handler: &Arc<ScrapeHandler>,
    authentication_service: &Arc<AuthenticationService>,
    opt_http_stats_event_sender: &Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
    scrape_request: &Scrape,
    client_ip_sources: &ClientIpSources,
    maybe_key: Option<Key>,
) -> Result<ScrapeData, responses::error::Error> {
    // todo: move authentication inside `http_tracker_core::services::scrape::handle_scrape`

    // Authentication
    let return_fake_scrape_data = if core_config.private {
        match maybe_key {
            Some(key) => match authentication_service.authenticate(&key).await {
                Ok(()) => false,
                Err(_error) => true,
            },
            None => true,
        }
    } else {
        false
    };

    http_tracker_core::services::scrape::handle_scrape(
        core_config,
        scrape_handler,
        opt_http_stats_event_sender,
        scrape_request,
        client_ip_sources,
        return_fake_scrape_data,
    )
    .await
}

UDP Tracker

Announce

/// It handles the `Announce` request. Refer to [`Announce`](crate::servers::udp#announce)
/// request for more information.
///
/// # Errors
///
/// If a error happens in the `handle_announce` function, it will just return the  `ServerError`.
#[allow(clippy::too_many_arguments)]
#[instrument(fields(transaction_id, connection_id, info_hash), skip(announce_handler, whitelist_authorization, opt_udp_stats_event_sender), ret(level = Level::TRACE))]
pub async fn handle_announce(
    remote_addr: SocketAddr,
    request: &AnnounceRequest,
    core_config: &Arc<Core>,
    announce_handler: &Arc<AnnounceHandler>,
    whitelist_authorization: &Arc<whitelist::authorization::WhitelistAuthorization>,
    opt_udp_stats_event_sender: &Arc<Option<Box<dyn udp_tracker_core::statistics::event::sender::Sender>>>,
    cookie_valid_range: Range<f64>,
) -> Result<Response, (Error, TransactionId)> {
    tracing::Span::current()
        .record("transaction_id", request.transaction_id.0.to_string())
        .record("connection_id", request.connection_id.0.to_string())
        .record("info_hash", InfoHash::from_bytes(&request.info_hash.0).to_hex_string());

    tracing::trace!("handle announce");

    // todo: move authentication to `udp_tracker_core::services::announce::handle_announce`

    check(
        &request.connection_id,
        gen_remote_fingerprint(&remote_addr),
        cookie_valid_range,
    )
    .map_err(|e| (e, request.transaction_id))?;

    let response = udp_tracker_core::services::announce::handle_announce(
        remote_addr,
        request,
        announce_handler,
        whitelist_authorization,
        opt_udp_stats_event_sender,
    )
    .await
    .map_err(|e| Error::TrackerError {
        source: (Arc::new(e) as Arc<dyn std::error::Error + Send + Sync>).into(),
    })
    .map_err(|e| (e, request.transaction_id))?;

    // todo: extract `build_response` function.

    // ...
}

Scrape

/// It handles the `Scrape` request. Refer to [`Scrape`](crate::servers::udp#scrape)
/// request for more information.
///
/// # Errors
///
/// This function does not ever return an error.
#[instrument(fields(transaction_id, connection_id), skip(scrape_handler, opt_udp_stats_event_sender),  ret(level = Level::TRACE))]
pub async fn handle_scrape(
    remote_addr: SocketAddr,
    request: &ScrapeRequest,
    scrape_handler: &Arc<ScrapeHandler>,
    opt_udp_stats_event_sender: &Arc<Option<Box<dyn udp_tracker_core::statistics::event::sender::Sender>>>,
    cookie_valid_range: Range<f64>,
) -> Result<Response, (Error, TransactionId)> {
    tracing::Span::current()
        .record("transaction_id", request.transaction_id.0.to_string())
        .record("connection_id", request.connection_id.0.to_string());

    tracing::trace!("handle scrape");

    // todo: move authentication to `udp_tracker_core::services::scrape::handle_scrape`

    check(
        &request.connection_id,
        gen_remote_fingerprint(&remote_addr),
        cookie_valid_range,
    )
    .map_err(|e| (e, request.transaction_id))?;

    let scrape_data =
        udp_tracker_core::services::scrape::handle_scrape(remote_addr, request, scrape_handler, opt_udp_stats_event_sender)
            .await
            .map_err(|e| Error::TrackerError {
                source: (Arc::new(e) as Arc<dyn std::error::Error + Send + Sync>).into(),
            })
            .map_err(|e| (e, request.transaction_id))?;

    // todo: extract `build_response` function.

    let mut torrent_stats: Vec<TorrentScrapeStatistics> = Vec::new();

    for file in &scrape_data.files {
        let swarm_metadata = file.1;

        #[allow(clippy::cast_possible_truncation)]
        let scrape_entry = {
            TorrentScrapeStatistics {
                seeders: NumberOfPeers(I32::new(i64::from(swarm_metadata.complete) as i32)),
                completed: NumberOfDownloads(I32::new(i64::from(swarm_metadata.downloaded) as i32)),
                leechers: NumberOfPeers(I32::new(i64::from(swarm_metadata.incomplete) as i32)),
            }
        };

        torrent_stats.push(scrape_entry);
    }

    let response = ScrapeResponse {
        transaction_id: request.transaction_id,
        torrent_stats,
    };

    Ok(Response::from(response))
}

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions