Skip to content

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

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
Tracked by #1181
josecelano opened this issue Feb 17, 2025 · 0 comments · Fixed by #1277
Closed
Tracked by #1181
Assignees
Labels
Code Cleanup / Refactoring Tidying and Making Neat

Comments

@josecelano
Copy link
Member

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))
}
@josecelano josecelano added the Code Cleanup / Refactoring Tidying and Making Neat label Feb 17, 2025
@josecelano josecelano self-assigned this Feb 17, 2025
@josecelano josecelano mentioned this issue Feb 17, 2025
26 tasks
josecelano added a commit to josecelano/torrust-tracker that referenced this issue Feb 17, 2025
josecelano added a commit to josecelano/torrust-tracker that referenced this issue Feb 17, 2025
josecelano added a commit to josecelano/torrust-tracker that referenced this issue Feb 18, 2025
josecelano added a commit to josecelano/torrust-tracker that referenced this issue Feb 18, 2025
josecelano added a commit to josecelano/torrust-tracker that referenced this issue Feb 18, 2025
josecelano added a commit that referenced this issue Feb 18, 2025
…ker-core` and `udp-tracker-core`

fdc2543 refactor: extract UDP connect service (Jose Celano)
4618f70 refactor: exatract response builders for UDP handlers (Jose Celano)
91525af refactor: [#1275] move authentication in udp tracker to core (Jose Celano)
4fd79b7 refactor: [#1275] move conenction cookie to udp_tracker_core package (Jose Celano)
694621b refactor: [#1275] extract ConnectionCookieError enum (Jose Celano)
ecc093f refactor: [#1275] move scrape authentication in http tracker to core (Jose Celano)
f6bf070 refactor: [#1275] move announce authentication in http tracker to core (Jose Celano)

Pull request description:

  Overhaul core Tracker: move authentication to `http-tracker-core` and `udp-tracker-core`.

  HTTP Tracker:
  - [x] Announce
  - [x] Scrape

  UDP Tracker:
  - [x] Announce
  - [x] Scrape

ACKs for top commit:
  josecelano:
    ACK fdc2543

Tree-SHA512: aad933f02307f6abb0a0afc9629f4b7282b2e57d2842f637f92310e28e3a82a53ffecc50ad46ba1f63b1660279c69e2b6af1b664218ab93f3cd7dce0e40ca339
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
Code Cleanup / Refactoring Tidying and Making Neat
Projects
None yet
1 participant