Skip to content

Commit

Permalink
Implementing BEP-7 for HTTP announce (solves issue #1659)
Browse files Browse the repository at this point in the history
  • Loading branch information
lvella committed Apr 9, 2021
1 parent 9c5e6f7 commit 555f1e7
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 35 deletions.
162 changes: 130 additions & 32 deletions libtransmission/announcer-http.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ static char const* get_event_string(tr_announce_request const* req)
tr_announce_event_get_string(req->event);
}

static char* announce_url_new(tr_session const* session, tr_announce_request const* req)
static char* announce_url_new(tr_session const* session, tr_announce_request const* req, unsigned char const* ipv6,
size_t* no_ipv6_cutoff)
{
char const* str;
unsigned char const* ipv6;
struct evbuffer* buf = evbuffer_new();
char escaped_info_hash[SHA_DIGEST_LENGTH * 3 + 1];

Expand Down Expand Up @@ -100,16 +100,7 @@ static char* announce_url_new(tr_session const* session, tr_announce_request con
evbuffer_add_printf(buf, "&trackerid=%s", str);
}

/* There are two incompatible techniques for announcing an IPv6 address.
BEP-7 suggests adding an "ipv6=" parameter to the announce URL,
while OpenTracker requires that peers announce twice, once over IPv4
and once over IPv6.
To be safe, we should do both: add the "ipv6=" parameter and
announce twice. At any rate, we're already computing our IPv6
address (for the LTEP handshake), so this comes for free. */

ipv6 = tr_globalIPv6();
*no_ipv6_cutoff = evbuffer_get_length(buf);

if (ipv6 != NULL)
{
Expand Down Expand Up @@ -176,36 +167,63 @@ static tr_pex* listToPex(tr_variant* peerList, size_t* setme_len)

struct announce_data
{
tr_announce_response response;
uint8_t info_hash[SHA_DIGEST_LENGTH];
tr_announce_response_func response_func;
void* response_func_user_data;
char log_name[128];
uint8_t requests_sent_count;
uint8_t requests_answered_count;
bool http_success;
};

struct response_data
{
tr_announce_response response;
tr_announce_response_func response_func;
void* response_func_user_data;
};

static struct response_data* create_response_data(struct announce_data* adata)
{
struct response_data* rd = tr_new0(struct response_data, 1);
rd->response.seeders = -1;
rd->response.leechers = -1;
rd->response.downloads = -1;
memcpy(rd->response.info_hash, adata->info_hash, SHA_DIGEST_LENGTH);
rd->response_func = adata->response_func;
rd->response_func_user_data = adata->response_func_user_data;

return rd;
}

static void free_response_data(struct response_data* rd)
{
tr_free(rd->response.pex6);
tr_free(rd->response.pex);
tr_free(rd->response.tracker_id_str);
tr_free(rd->response.warning);
tr_free(rd->response.errmsg);
tr_free(rd);
}

static void on_announce_done_eventthread(void* vdata)
{
struct announce_data* data = vdata;
struct response_data* data = vdata;

if (data->response_func != NULL)
{
data->response_func(&data->response, data->response_func_user_data);
}

tr_free(data->response.pex6);
tr_free(data->response.pex);
tr_free(data->response.tracker_id_str);
tr_free(data->response.warning);
tr_free(data->response.errmsg);
tr_free(data);
free_response_data(data);
}

static void on_announce_done(tr_session* session, bool did_connect, bool did_timeout, long response_code, void const* msg,
size_t msglen, void* vdata)
static bool handle_announce_response(bool did_connect, bool did_timeout, long response_code, void const* msg, size_t msglen,
void* vdata, tr_announce_response* response)
{
tr_announce_response* response;
struct announce_data* data = vdata;
bool success = false;

response = &data->response;
response->did_connect = did_connect;
response->did_timeout = did_timeout;
dbgmsg(data->log_name, "Got announce response");
Expand Down Expand Up @@ -307,6 +325,8 @@ static void on_announce_done(tr_session* session, bool did_connect, bool did_tim
response->pex = listToPex(tmp, &response->pex_count);
dbgmsg(data->log_name, "got a peers list with %zu entries", response->pex_count);
}

success = true;
}

if (variant_loaded)
Expand All @@ -315,26 +335,104 @@ static void on_announce_done(tr_session* session, bool did_connect, bool did_tim
}
}

tr_runInEventThread(session, on_announce_done_eventthread, data);
return success;
}

static void on_announce_done(tr_session* session, bool did_connect, bool did_timeout, long response_code, void const* msg,
size_t msglen, void* vdata)
{
struct announce_data* data = vdata;

++data->requests_answered_count;

// If another request already succeeded, skip processing this new request:
if (!data->http_success)
{
struct response_data* rd = create_response_data(data);

data->http_success = handle_announce_response(
did_connect, did_timeout, response_code, msg, msglen, vdata, &rd->response);

// Process response on success or when both requests have been answered.
if (data->http_success || data->requests_answered_count == 2)
{
tr_runInEventThread(session, on_announce_done_eventthread, rd);
}
else
{
free_response_data(rd);
}
}
else
{
dbgmsg(data->log_name, "Ignoring redundant announce response");
}

// Free data if no more responses are expected:
if (data->requests_answered_count == data->requests_sent_count)
{
tr_free(data);
}
}

void tr_tracker_http_announce(tr_session* session, tr_announce_request const* request, tr_announce_response_func response_func,
void* response_func_user_data)
{
struct announce_data* d;
char* url = announce_url_new(session, request);
unsigned char const* ipv6;
size_t no_ipv6_cutoff;
char* url;

d = tr_new0(struct announce_data, 1);
d->response.seeders = -1;
d->response.leechers = -1;
d->response.downloads = -1;
d->response_func = response_func;
d->response_func_user_data = response_func_user_data;
memcpy(d->response.info_hash, request->info_hash, SHA_DIGEST_LENGTH);
d->requests_answered_count = 0;
d->http_success = false;
memcpy(d->info_hash, request->info_hash, SHA_DIGEST_LENGTH);
tr_strlcpy(d->log_name, request->log_name, sizeof(d->log_name));

dbgmsg(request->log_name, "Sending announce to libcurl: \"%s\"", url);
tr_webRun(session, url, on_announce_done, d);
/* There are two alternative techniques for announcing both IPv4 and
IPv6 addresses. Previous version of BEP-7 suggests adding "ipv4="
and "ipv6=" parameters to the announce URL, while OpenTracker and
newer version of BEP-7 requires that peers announce once per each
public address they want to use.
We should ensure that we send the announce both via IPv6 and IPv4,
and to be safe we also add the "ipv6=" parameter, because we're
already computing our global IPv6 address (for the LTEP handshake),
so this comes for free.
We don't set the "ipv4=" parameter because it is much harder to
figure the public IPv4 address of the machine due to pervasive use
of NAT, so it is probably not worth it.
TODO: Use miniUPnPc to figure out the public IPv4 address and set
"ipv4=" parameter.
*/
ipv6 = tr_globalIPv6();
url = announce_url_new(session, request, ipv6, &no_ipv6_cutoff);

if (ipv6)
{
d->requests_sent_count = 2;

// First try to send the announce via IPv4:
dbgmsg(request->log_name, "Sending IPv4 announce to libcurl: \"%s\"", url);
tr_webRunWithIpProto(session, url, on_announce_done, d, TR_WEB_IP_PROTO_V4);

// Then strip the "ipv6=..." part and try to send via IPv6:
url[no_ipv6_cutoff] = '\0';
dbgmsg(request->log_name, "Sending IPv6 announce to libcurl: \"%s\"", url);
tr_webRunWithIpProto(session, url, on_announce_done, d, TR_WEB_IP_PROTO_V6);
}
else
{
d->requests_sent_count = 1;

// Don't care about IP version when announcing
dbgmsg(request->log_name, "Sending announce to libcurl: \"%s\"", url);
tr_webRun(session, url, on_announce_done, d);
}

tr_free(url);
}
Expand Down
37 changes: 34 additions & 3 deletions libtransmission/web.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ struct tr_web_task
char* cookies;
tr_session* session;
tr_web_done_func done_func;
tr_web_ip_proto ip_proto;
void* done_func_user_data;
CURL* curl_easy;
struct tr_web_task* next;
Expand Down Expand Up @@ -240,6 +241,18 @@ static long getTimeoutFromURL(struct tr_web_task const* task)
return timeout;
}

static int const IP_VERSION_TO_CURL[] =
{
CURL_IPRESOLVE_WHATEVER,
#ifdef CURL_IPRESOLVE_V4_STRICT
CURL_IPRESOLVE_V4_STRICT,
CURL_IPRESOLVE_V6_STRICT,
#else
CURL_IPRESOLVE_V4,
CURL_IPRESOLVE_V6,
#endif
};

static CURL* createEasy(tr_session* s, struct tr_web* web, struct tr_web_task* task)
{
bool is_default_value;
Expand All @@ -256,6 +269,16 @@ static CURL* createEasy(tr_session* s, struct tr_web* web, struct tr_web_task* t
curl_easy_setopt(e, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(e, CURLOPT_PRIVATE, task);

#ifndef CURL_IPRESOLVE_V4_STRICT
if (task->ip_proto != TR_WEB_IP_PROTO_ANY)
{
curl_easy_setopt(e, CURLOPT_DNS_CACHE_TIMEOUT, 0L);
curl_easy_setopt(e, CURLOPT_FORBID_REUSE, 1L);
}

#endif
curl_easy_setopt(e, CURLOPT_IPRESOLVE, IP_VERSION_TO_CURL[task->ip_proto]);

#ifdef USE_LIBCURL_SOCKOPT
curl_easy_setopt(e, CURLOPT_SOCKOPTFUNCTION, sockoptfunction);
curl_easy_setopt(e, CURLOPT_SOCKOPTDATA, task);
Expand Down Expand Up @@ -340,7 +363,7 @@ static void tr_webThreadFunc(void* vsession);

static struct tr_web_task* tr_webRunImpl(tr_session* session, int torrentId, char const* url, char const* range,
char const* cookies, tr_web_done_func done_func, void* done_func_user_data,
struct evbuffer* buffer)
struct evbuffer* buffer, tr_web_ip_proto ip_proto)
{
struct tr_web_task* task = NULL;

Expand All @@ -363,6 +386,7 @@ static struct tr_web_task* tr_webRunImpl(tr_session* session, int torrentId, cha
task->range = tr_strdup(range);
task->cookies = tr_strdup(cookies);
task->done_func = done_func;
task->ip_proto = ip_proto;
task->done_func_user_data = done_func_user_data;
task->response = buffer != NULL ? buffer : evbuffer_new();
task->freebuf = buffer != NULL ? NULL : task->response;
Expand All @@ -379,7 +403,13 @@ static struct tr_web_task* tr_webRunImpl(tr_session* session, int torrentId, cha
struct tr_web_task* tr_webRunWithCookies(tr_session* session, char const* url, char const* cookies, tr_web_done_func done_func,
void* done_func_user_data)
{
return tr_webRunImpl(session, -1, url, NULL, cookies, done_func, done_func_user_data, NULL);
return tr_webRunImpl(session, -1, url, NULL, cookies, done_func, done_func_user_data, NULL, TR_WEB_IP_PROTO_ANY);
}

struct tr_web_task* tr_webRunWithIpProto(tr_session* session, char const* url, tr_web_done_func done_func,
void* done_func_user_data, tr_web_ip_proto ip_proto)
{
return tr_webRunImpl(session, -1, url, NULL, NULL, done_func, done_func_user_data, NULL, ip_proto);
}

struct tr_web_task* tr_webRun(tr_session* session, char const* url, tr_web_done_func done_func, void* done_func_user_data)
Expand All @@ -390,7 +420,8 @@ struct tr_web_task* tr_webRun(tr_session* session, char const* url, tr_web_done_
struct tr_web_task* tr_webRunWebseed(tr_torrent* tor, char const* url, char const* range, tr_web_done_func done_func,
void* done_func_user_data, struct evbuffer* buffer)
{
return tr_webRunImpl(tor->session, tr_torrentId(tor), url, range, NULL, done_func, done_func_user_data, buffer);
return tr_webRunImpl(tor->session, tr_torrentId(
tor), url, range, NULL, done_func, done_func_user_data, buffer, TR_WEB_IP_PROTO_ANY);
}

static void tr_webThreadFunc(void* vsession)
Expand Down
12 changes: 12 additions & 0 deletions libtransmission/web.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#pragma once

#include <curl/curl.h>
#include "tr-macros.h"

TR_BEGIN_DECLS
Expand All @@ -22,6 +23,14 @@ typedef enum
}
tr_web_close_mode;

typedef enum
{
TR_WEB_IP_PROTO_ANY,
TR_WEB_IP_PROTO_V4,
TR_WEB_IP_PROTO_V6
}
tr_web_ip_proto;

void tr_webClose(tr_session* session, tr_web_close_mode close_mode);

typedef void (* tr_web_done_func)(tr_session* session, bool did_connect_flag, bool timeout_flag, long response_code,
Expand All @@ -31,6 +40,9 @@ char const* tr_webGetResponseStr(long response_code);

struct tr_web_task* tr_webRun(tr_session* session, char const* url, tr_web_done_func done_func, void* done_func_user_data);

struct tr_web_task* tr_webRunWithIpProto(tr_session* session, char const* url, tr_web_done_func done_func,
void* done_func_user_data, tr_web_ip_proto ip_proto);

struct tr_web_task* tr_webRunWithCookies(tr_session* session, char const* url, char const* cookies, tr_web_done_func done_func,
void* done_func_user_data);

Expand Down

0 comments on commit 555f1e7

Please sign in to comment.