mirror of
https://github.com/transmission/transmission
synced 2024-12-25 09:13:06 +00:00
968 lines
29 KiB
C
968 lines
29 KiB
C
/*
|
|
* This file Copyright (C) Mnemosyne LLC
|
|
*
|
|
* This file is licensed by the GPL version 2. Works owned by the
|
|
* Transmission project are granted a special exemption to clause 2(b)
|
|
* so that the bulk of its code can remain under the MIT license.
|
|
* This exemption does not extend to derived works not owned by
|
|
* the Transmission project.
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#define __LIBTRANSMISSION_ANNOUNCER_MODULE___
|
|
|
|
#include <string.h> /* memcpy(), memset() */
|
|
|
|
#include <event2/buffer.h>
|
|
#include <event2/dns.h>
|
|
#include <event2/util.h>
|
|
|
|
#include "transmission.h"
|
|
#include "announcer.h"
|
|
#include "announcer-common.h"
|
|
#include "crypto.h" /* tr_cryptoRandBuf() */
|
|
#include "peer-io.h"
|
|
#include "peer-mgr.h" /* tr_peerMgrCompactToPex() */
|
|
#include "ptrarray.h"
|
|
#include "tr-udp.h"
|
|
#include "utils.h"
|
|
|
|
#define dbgmsg( name, ... ) \
|
|
if( tr_deepLoggingIsActive( ) ) do { \
|
|
tr_deepLog( __FILE__, __LINE__, name, __VA_ARGS__ ); \
|
|
} while( 0 )
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
static void
|
|
tau_sockaddr_setport( struct sockaddr * sa, tr_port port )
|
|
{
|
|
if( sa->sa_family == AF_INET )
|
|
((struct sockaddr_in *)sa)->sin_port = htons(port);
|
|
else if (sa->sa_family == AF_INET6)
|
|
((struct sockaddr_in6 *)sa)->sin6_port = htons(port);
|
|
}
|
|
|
|
static int
|
|
tau_sendto( tr_session * session,
|
|
struct evutil_addrinfo * ai, tr_port port,
|
|
const void * buf, size_t buflen )
|
|
{
|
|
int sockfd;
|
|
|
|
if( ai->ai_addr->sa_family == AF_INET )
|
|
sockfd = session->udp_socket;
|
|
else if( ai->ai_addr->sa_family == AF_INET6 )
|
|
sockfd = session->udp6_socket;
|
|
else
|
|
sockfd = -1;
|
|
|
|
if( sockfd < 0 ) {
|
|
errno = EAFNOSUPPORT;
|
|
return -1;
|
|
}
|
|
|
|
tau_sockaddr_setport( ai->ai_addr, port );
|
|
return sendto( sockfd, buf, buflen, 0, ai->ai_addr, ai->ai_addrlen );
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
static uint32_t
|
|
evbuffer_read_ntoh_32( struct evbuffer * buf )
|
|
{
|
|
uint32_t val;
|
|
evbuffer_remove( buf, &val, sizeof( uint32_t ) );
|
|
return ntohl( val );
|
|
}
|
|
|
|
static uint64_t
|
|
evbuffer_read_ntoh_64( struct evbuffer * buf )
|
|
{
|
|
uint64_t val;
|
|
evbuffer_remove( buf, &val, sizeof( uint64_t ) );
|
|
return tr_ntohll( val );
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
typedef uint64_t tau_connection_t;
|
|
|
|
enum
|
|
{
|
|
TAU_CONNECTION_TTL_SECS = 60
|
|
};
|
|
|
|
typedef uint32_t tau_transaction_t;
|
|
|
|
static tau_transaction_t
|
|
tau_transaction_new( void )
|
|
{
|
|
tau_transaction_t tmp;
|
|
tr_cryptoRandBuf( &tmp, sizeof( tau_transaction_t ) );
|
|
return tmp;
|
|
}
|
|
|
|
/* used in the "action" field of a request */
|
|
typedef enum
|
|
{
|
|
TAU_ACTION_CONNECT = 0,
|
|
TAU_ACTION_ANNOUNCE = 1,
|
|
TAU_ACTION_SCRAPE = 2,
|
|
TAU_ACTION_ERROR = 3
|
|
}
|
|
tau_action_t;
|
|
|
|
static bool
|
|
is_tau_response_message( int action, int msglen )
|
|
{
|
|
if( action == TAU_ACTION_CONNECT ) return msglen == 16;
|
|
if( action == TAU_ACTION_ANNOUNCE ) return msglen >= 20;
|
|
if( action == TAU_ACTION_SCRAPE ) return msglen >= 20;
|
|
if( action == TAU_ACTION_ERROR ) return msglen >= 8;
|
|
return false;
|
|
}
|
|
|
|
enum
|
|
{
|
|
TAU_REQUEST_TTL = 60
|
|
};
|
|
|
|
/****
|
|
*****
|
|
***** SCRAPE
|
|
*****
|
|
****/
|
|
|
|
struct tau_scrape_request
|
|
{
|
|
void * payload;
|
|
size_t payload_len;
|
|
|
|
time_t sent_at;
|
|
time_t created_at;
|
|
tau_transaction_t transaction_id;
|
|
|
|
tr_scrape_response response;
|
|
tr_scrape_response_func * callback;
|
|
void * user_data;
|
|
};
|
|
|
|
static struct tau_scrape_request *
|
|
tau_scrape_request_new( const tr_scrape_request * in,
|
|
tr_scrape_response_func callback,
|
|
void * user_data )
|
|
{
|
|
int i;
|
|
struct evbuffer * buf;
|
|
struct tau_scrape_request * req;
|
|
const tau_transaction_t transaction_id = tau_transaction_new( );
|
|
|
|
/* build the payload */
|
|
buf = evbuffer_new( );
|
|
evbuffer_add_hton_32( buf, TAU_ACTION_SCRAPE );
|
|
evbuffer_add_hton_32( buf, transaction_id );
|
|
for( i=0; i<in->info_hash_count; ++i )
|
|
evbuffer_add( buf, in->info_hash[i], SHA_DIGEST_LENGTH );
|
|
|
|
/* build the tau_scrape_request */
|
|
req = tr_new0( struct tau_scrape_request, 1 );
|
|
req->created_at = tr_time( );
|
|
req->transaction_id = transaction_id;
|
|
req->callback = callback;
|
|
req->user_data = user_data;
|
|
req->response.url = tr_strdup( in->url );
|
|
req->response.row_count = in->info_hash_count;
|
|
req->payload_len = evbuffer_get_length( buf );
|
|
req->payload = tr_memdup( evbuffer_pullup( buf, -1 ), req->payload_len );
|
|
for( i=0; i<req->response.row_count; ++i )
|
|
{
|
|
req->response.rows[i].seeders = -1;
|
|
req->response.rows[i].leechers = -1;
|
|
req->response.rows[i].downloads = -1;
|
|
memcpy( req->response.rows[i].info_hash,
|
|
in->info_hash[i], SHA_DIGEST_LENGTH );
|
|
}
|
|
|
|
/* cleanup */
|
|
evbuffer_free( buf );
|
|
return req;
|
|
}
|
|
|
|
static void
|
|
tau_scrape_request_free( struct tau_scrape_request * req )
|
|
{
|
|
tr_free( req->response.errmsg );
|
|
tr_free( req->response.url );
|
|
tr_free( req->payload );
|
|
tr_free( req );
|
|
}
|
|
|
|
static void
|
|
tau_scrape_request_finished( const struct tau_scrape_request * request )
|
|
{
|
|
if( request->callback != NULL )
|
|
request->callback( &request->response, request->user_data );
|
|
}
|
|
|
|
static void
|
|
tau_scrape_request_fail( struct tau_scrape_request * request,
|
|
bool did_connect,
|
|
bool did_timeout,
|
|
const char * errmsg )
|
|
{
|
|
request->response.did_connect = did_connect;
|
|
request->response.did_timeout = did_timeout;
|
|
request->response.errmsg = tr_strdup( errmsg );
|
|
tau_scrape_request_finished( request );
|
|
}
|
|
|
|
static void
|
|
on_scrape_response( struct tau_scrape_request * request,
|
|
tau_action_t action,
|
|
struct evbuffer * buf )
|
|
{
|
|
request->response.did_connect = true;
|
|
request->response.did_timeout = false;
|
|
|
|
if( action == TAU_ACTION_SCRAPE )
|
|
{
|
|
int i;
|
|
for( i=0; i<request->response.row_count; ++i )
|
|
{
|
|
struct tr_scrape_response_row * row;
|
|
|
|
if( evbuffer_get_length( buf ) < ( sizeof( uint32_t ) * 3 ) )
|
|
break;
|
|
|
|
row = &request->response.rows[i];
|
|
row->seeders = evbuffer_read_ntoh_32( buf );
|
|
row->downloads = evbuffer_read_ntoh_32( buf );
|
|
row->leechers = evbuffer_read_ntoh_32( buf );
|
|
}
|
|
|
|
tau_scrape_request_finished( request );
|
|
}
|
|
else
|
|
{
|
|
char * errmsg;
|
|
const size_t buflen = evbuffer_get_length( buf );
|
|
|
|
if( ( action == TAU_ACTION_ERROR ) && ( buflen > 0 ) )
|
|
errmsg = tr_strndup( evbuffer_pullup( buf, -1 ), buflen );
|
|
else
|
|
errmsg = tr_strdup( _( "Unknown error" ) );
|
|
|
|
tau_scrape_request_fail( request, true, false, errmsg );
|
|
tr_free( errmsg );
|
|
}
|
|
}
|
|
|
|
/****
|
|
*****
|
|
***** ANNOUNCE
|
|
*****
|
|
****/
|
|
|
|
struct tau_announce_request
|
|
{
|
|
void * payload;
|
|
size_t payload_len;
|
|
|
|
time_t created_at;
|
|
time_t sent_at;
|
|
tau_transaction_t transaction_id;
|
|
|
|
tr_announce_response response;
|
|
tr_announce_response_func * callback;
|
|
void * user_data;
|
|
};
|
|
|
|
typedef enum
|
|
{
|
|
/* used in the "event" field of an announce request */
|
|
TAU_ANNOUNCE_EVENT_NONE = 0,
|
|
TAU_ANNOUNCE_EVENT_COMPLETED = 1,
|
|
TAU_ANNOUNCE_EVENT_STARTED = 2,
|
|
TAU_ANNOUNCE_EVENT_STOPPED = 3
|
|
}
|
|
tau_announce_event;
|
|
|
|
static tau_announce_event
|
|
get_tau_announce_event( tr_announce_event e )
|
|
{
|
|
switch( e )
|
|
{
|
|
case TR_ANNOUNCE_EVENT_COMPLETED: return TAU_ANNOUNCE_EVENT_COMPLETED;
|
|
case TR_ANNOUNCE_EVENT_STARTED: return TAU_ANNOUNCE_EVENT_STARTED;
|
|
case TR_ANNOUNCE_EVENT_STOPPED: return TAU_ANNOUNCE_EVENT_STOPPED;
|
|
default: return TAU_ANNOUNCE_EVENT_NONE;
|
|
}
|
|
}
|
|
|
|
static struct tau_announce_request *
|
|
tau_announce_request_new( const tr_announce_request * in,
|
|
tr_announce_response_func callback,
|
|
void * user_data )
|
|
{
|
|
struct evbuffer * buf;
|
|
struct tau_announce_request * req;
|
|
const tau_transaction_t transaction_id = tau_transaction_new( );
|
|
|
|
/* build the payload */
|
|
buf = evbuffer_new( );
|
|
evbuffer_add_hton_32( buf, TAU_ACTION_ANNOUNCE );
|
|
evbuffer_add_hton_32( buf, transaction_id );
|
|
evbuffer_add ( buf, in->info_hash, SHA_DIGEST_LENGTH );
|
|
evbuffer_add ( buf, in->peer_id, PEER_ID_LEN );
|
|
evbuffer_add_hton_64( buf, in->down );
|
|
evbuffer_add_hton_64( buf, in->leftUntilComplete );
|
|
evbuffer_add_hton_64( buf, in->up );
|
|
evbuffer_add_hton_32( buf, get_tau_announce_event( in->event ) );
|
|
evbuffer_add_hton_32( buf, 0 );
|
|
evbuffer_add_hton_32( buf, in->key );
|
|
evbuffer_add_hton_32( buf, in->numwant );
|
|
evbuffer_add_hton_16( buf, in->port );
|
|
|
|
/* build the tau_announce_request */
|
|
req = tr_new0( struct tau_announce_request, 1 );
|
|
req->created_at = tr_time( );
|
|
req->transaction_id = transaction_id;
|
|
req->callback = callback;
|
|
req->user_data = user_data;
|
|
req->payload_len = evbuffer_get_length( buf );
|
|
req->payload = tr_memdup( evbuffer_pullup( buf, -1 ), req->payload_len );
|
|
req->response.seeders = -1;
|
|
req->response.leechers = -1;
|
|
req->response.downloads = -1;
|
|
memcpy( req->response.info_hash, in->info_hash, SHA_DIGEST_LENGTH );
|
|
|
|
evbuffer_free( buf );
|
|
return req;
|
|
}
|
|
|
|
static void
|
|
tau_announce_request_free( struct tau_announce_request * req )
|
|
{
|
|
tr_free( req->response.tracker_id_str );
|
|
tr_free( req->response.warning );
|
|
tr_free( req->response.errmsg );
|
|
tr_free( req->response.pex6 );
|
|
tr_free( req->response.pex );
|
|
tr_free( req->payload );
|
|
tr_free( req );
|
|
}
|
|
|
|
static void
|
|
tau_announce_request_finished( const struct tau_announce_request * request )
|
|
{
|
|
if( request->callback != NULL )
|
|
request->callback( &request->response, request->user_data );
|
|
}
|
|
|
|
static void
|
|
tau_announce_request_fail( struct tau_announce_request * request,
|
|
bool did_connect,
|
|
bool did_timeout,
|
|
const char * errmsg )
|
|
{
|
|
request->response.did_connect = did_connect;
|
|
request->response.did_timeout = did_timeout;
|
|
request->response.errmsg = tr_strdup( errmsg );
|
|
tau_announce_request_finished( request );
|
|
}
|
|
|
|
static void
|
|
on_announce_response( struct tau_announce_request * request,
|
|
tau_action_t action,
|
|
struct evbuffer * buf )
|
|
{
|
|
const size_t buflen = evbuffer_get_length( buf );
|
|
|
|
request->response.did_connect = true;
|
|
request->response.did_timeout = false;
|
|
|
|
if( ( action == TAU_ACTION_ANNOUNCE ) && ( buflen >= 3*sizeof(uint32_t) ) )
|
|
{
|
|
tr_announce_response * resp = &request->response;
|
|
resp->interval = evbuffer_read_ntoh_32( buf );
|
|
resp->leechers = evbuffer_read_ntoh_32( buf );
|
|
resp->seeders = evbuffer_read_ntoh_32( buf );
|
|
resp->pex = tr_peerMgrCompactToPex( evbuffer_pullup( buf, -1 ),
|
|
evbuffer_get_length( buf ),
|
|
NULL, 0,
|
|
&request->response.pex_count );
|
|
tau_announce_request_finished( request );
|
|
}
|
|
else
|
|
{
|
|
char * errmsg;
|
|
|
|
if( ( action == TAU_ACTION_ERROR ) && ( buflen > 0 ) )
|
|
errmsg = tr_strndup( evbuffer_pullup( buf, -1 ), buflen );
|
|
else
|
|
errmsg = tr_strdup( _( "Unknown error" ) );
|
|
|
|
tau_announce_request_fail( request, true, false, errmsg );
|
|
tr_free( errmsg );
|
|
}
|
|
}
|
|
|
|
/****
|
|
*****
|
|
***** TRACKERS
|
|
*****
|
|
****/
|
|
|
|
struct tau_tracker
|
|
{
|
|
tr_session * session;
|
|
|
|
char * key;
|
|
char * host;
|
|
int port;
|
|
|
|
bool is_asking_dns;
|
|
struct evutil_addrinfo * addr;
|
|
time_t addr_expiration_time;
|
|
|
|
time_t connecting_at;
|
|
time_t connection_expiration_time;
|
|
tau_connection_t connection_id;
|
|
tau_transaction_t connection_transaction_id;
|
|
|
|
time_t close_at;
|
|
|
|
tr_ptrArray announces;
|
|
tr_ptrArray scrapes;
|
|
};
|
|
|
|
static void tau_tracker_upkeep( struct tau_tracker * );
|
|
|
|
static void
|
|
tau_tracker_free( struct tau_tracker * t )
|
|
{
|
|
if( t->addr )
|
|
evutil_freeaddrinfo( t->addr );
|
|
tr_ptrArrayDestruct( &t->announces, (PtrArrayForeachFunc)tau_announce_request_free );
|
|
tr_ptrArrayDestruct( &t->scrapes, (PtrArrayForeachFunc)tau_scrape_request_free );
|
|
tr_free( t->host );
|
|
tr_free( t->key );
|
|
tr_free( t );
|
|
}
|
|
|
|
static void
|
|
tau_tracker_fail_all( struct tau_tracker * tracker,
|
|
bool did_connect,
|
|
bool did_timeout,
|
|
const char * errmsg )
|
|
{
|
|
int i;
|
|
int n;
|
|
tr_ptrArray * reqs;
|
|
|
|
/* fail all the scrapes */
|
|
reqs = &tracker->scrapes;
|
|
for( i=0, n=tr_ptrArraySize(reqs); i<n; ++i )
|
|
tau_scrape_request_fail( tr_ptrArrayNth( reqs, i ),
|
|
did_connect, did_timeout, errmsg );
|
|
tr_ptrArrayDestruct( reqs, (PtrArrayForeachFunc)tau_scrape_request_free );
|
|
*reqs = TR_PTR_ARRAY_INIT;
|
|
|
|
/* fail all the announces */
|
|
reqs = &tracker->announces;
|
|
for( i=0, n=tr_ptrArraySize(reqs); i<n; ++i )
|
|
tau_announce_request_fail( tr_ptrArrayNth( reqs, i ),
|
|
did_connect, did_timeout, errmsg );
|
|
tr_ptrArrayDestruct( reqs, (PtrArrayForeachFunc)tau_announce_request_free );
|
|
*reqs = TR_PTR_ARRAY_INIT;
|
|
|
|
}
|
|
|
|
static void
|
|
tau_tracker_on_dns( int errcode, struct evutil_addrinfo *addr, void * vtracker )
|
|
{
|
|
struct tau_tracker * tracker = vtracker;
|
|
|
|
tracker->is_asking_dns = false;
|
|
|
|
if ( errcode )
|
|
{
|
|
char * errmsg = tr_strdup_printf( _( "DNS Lookup failed: %s" ),
|
|
evdns_err_to_string( errcode ) );
|
|
dbgmsg( tracker->key, "%s", errmsg );
|
|
tau_tracker_fail_all( tracker, false, false, errmsg );
|
|
tr_free( errmsg );
|
|
}
|
|
else
|
|
{
|
|
dbgmsg( tracker->key, "DNS lookup succeeded" );
|
|
tracker->addr = addr;
|
|
tracker->addr_expiration_time = tr_time() + (60*60); /* one hour */
|
|
tau_tracker_upkeep( tracker );
|
|
}
|
|
}
|
|
|
|
static void
|
|
tau_tracker_send_request( struct tau_tracker * tracker,
|
|
const void * payload,
|
|
size_t payload_len )
|
|
{
|
|
struct evbuffer * buf = evbuffer_new( );
|
|
dbgmsg( tracker->key, "sending request w/connection id %"PRIu64"\n",
|
|
tracker->connection_id );
|
|
evbuffer_add_hton_64( buf, tracker->connection_id );
|
|
evbuffer_add_reference( buf, payload, payload_len, NULL, NULL );
|
|
tau_sendto( tracker->session, tracker->addr, tracker->port,
|
|
evbuffer_pullup( buf, -1 ),
|
|
evbuffer_get_length( buf ) );
|
|
evbuffer_free( buf );
|
|
}
|
|
|
|
static void
|
|
tau_tracker_send_reqs( struct tau_tracker * tracker )
|
|
{
|
|
int i, n;
|
|
tr_ptrArray * reqs;
|
|
const time_t now = tr_time( );
|
|
|
|
assert( tracker->is_asking_dns == false );
|
|
assert( tracker->connecting_at == 0 );
|
|
assert( tracker->addr != NULL );
|
|
assert( tracker->connection_expiration_time > now );
|
|
|
|
reqs = &tracker->announces;
|
|
for( i=0, n=tr_ptrArraySize(reqs); i<n; ++i ) {
|
|
struct tau_announce_request * req = tr_ptrArrayNth( reqs, i );
|
|
if( !req->sent_at ) {
|
|
dbgmsg( tracker->key, "sending announce req %p", req );
|
|
req->sent_at = now;
|
|
tau_tracker_send_request( tracker, req->payload, req->payload_len );
|
|
if( req->callback == NULL ) {
|
|
tau_announce_request_free( req );
|
|
tr_ptrArrayRemove( reqs, i );
|
|
--i;
|
|
--n;
|
|
}
|
|
}
|
|
}
|
|
|
|
reqs = &tracker->scrapes;
|
|
for( i=0, n=tr_ptrArraySize(reqs); i<n; ++i ) {
|
|
struct tau_scrape_request * req = tr_ptrArrayNth( reqs, i );
|
|
if( !req->sent_at ) {
|
|
dbgmsg( tracker->key, "sending scrape req %p", req );
|
|
req->sent_at = now;
|
|
tau_tracker_send_request( tracker, req->payload, req->payload_len );
|
|
if( req->callback == NULL ) {
|
|
tau_scrape_request_free( req );
|
|
tr_ptrArrayRemove( reqs, i );
|
|
--i;
|
|
--n;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_tracker_connection_response( struct tau_tracker * tracker,
|
|
tau_action_t action,
|
|
struct evbuffer * buf )
|
|
{
|
|
const time_t now = tr_time( );
|
|
|
|
tracker->connecting_at = 0;
|
|
tracker->connection_transaction_id = 0;
|
|
|
|
if( action == TAU_ACTION_CONNECT )
|
|
{
|
|
tracker->connection_id = evbuffer_read_ntoh_64( buf );
|
|
tracker->connection_expiration_time = now + TAU_CONNECTION_TTL_SECS;
|
|
dbgmsg( tracker->key, "Got a new connection ID from tracker: %"PRIu64,
|
|
tracker->connection_id );
|
|
}
|
|
else
|
|
{
|
|
char * errmsg;
|
|
const size_t buflen = buf ? evbuffer_get_length( buf ) : 0;
|
|
|
|
if( ( action == TAU_ACTION_ERROR ) && ( buflen > 0 ) )
|
|
errmsg = tr_strndup( evbuffer_pullup( buf, -1 ), buflen );
|
|
else
|
|
errmsg = tr_strdup( _( "Connection failed" ) );
|
|
|
|
dbgmsg( tracker->key, "%s", errmsg );
|
|
tau_tracker_fail_all( tracker, true, false, errmsg );
|
|
tr_free( errmsg );
|
|
}
|
|
|
|
tau_tracker_upkeep( tracker );
|
|
}
|
|
|
|
static void
|
|
tau_tracker_timeout_reqs( struct tau_tracker * tracker )
|
|
{
|
|
int i, n;
|
|
tr_ptrArray * reqs;
|
|
const time_t now = time( NULL );
|
|
const bool cancel_all = tracker->close_at && ( tracker->close_at <= now );
|
|
|
|
|
|
if( tracker->connecting_at && ( tracker->connecting_at + TAU_REQUEST_TTL < now ) ) {
|
|
on_tracker_connection_response( tracker, TAU_ACTION_ERROR, NULL );
|
|
}
|
|
|
|
reqs = &tracker->announces;
|
|
for( i=0, n=tr_ptrArraySize(reqs); i<n; ++i ) {
|
|
struct tau_announce_request * req = tr_ptrArrayNth( reqs, i );
|
|
if( cancel_all || ( req->created_at + TAU_REQUEST_TTL < now ) ) {
|
|
dbgmsg( tracker->key, "timeout announce req %p", req );
|
|
tau_announce_request_fail( req, false, true, NULL );
|
|
tau_announce_request_free( req );
|
|
tr_ptrArrayRemove( reqs, i );
|
|
--i;
|
|
--n;
|
|
}
|
|
}
|
|
|
|
reqs = &tracker->scrapes;
|
|
for( i=0, n=tr_ptrArraySize(reqs); i<n; ++i ) {
|
|
struct tau_scrape_request * req = tr_ptrArrayNth( reqs, i );
|
|
if( cancel_all || ( req->created_at + TAU_REQUEST_TTL < now ) ) {
|
|
dbgmsg( tracker->key, "timeout scrape req %p", req );
|
|
tau_scrape_request_fail( req, false, true, NULL );
|
|
tau_scrape_request_free( req );
|
|
tr_ptrArrayRemove( reqs, i );
|
|
--i;
|
|
--n;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool
|
|
tau_tracker_is_idle( const struct tau_tracker * tracker )
|
|
{
|
|
return tr_ptrArrayEmpty( &tracker->announces )
|
|
&& tr_ptrArrayEmpty( &tracker->scrapes );
|
|
}
|
|
|
|
static void
|
|
tau_tracker_upkeep( struct tau_tracker * tracker )
|
|
{
|
|
const time_t now = tr_time( );
|
|
|
|
/* if the address info is too old, expire it */
|
|
if( tracker->addr && ( tracker->addr_expiration_time <= now ) ) {
|
|
dbgmsg( tracker->host, "Expiring old DNS result" );
|
|
evutil_freeaddrinfo( tracker->addr );
|
|
tracker->addr = NULL;
|
|
}
|
|
|
|
/* are there any requests pending? */
|
|
if( tau_tracker_is_idle( tracker ) )
|
|
return;
|
|
|
|
/* if we don't have an address yet, try & get one now. */
|
|
if( !tracker->addr && !tracker->is_asking_dns )
|
|
{
|
|
struct evutil_addrinfo hints;
|
|
memset( &hints, 0, sizeof( hints ) );
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_flags = EVUTIL_AI_CANONNAME;
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
hints.ai_protocol = IPPROTO_UDP;
|
|
tracker->is_asking_dns = true;
|
|
dbgmsg( tracker->host, "Trying a new DNS lookup" );
|
|
evdns_getaddrinfo( tracker->session->evdns_base,
|
|
tracker->host, NULL, &hints,
|
|
tau_tracker_on_dns, tracker );
|
|
return;
|
|
}
|
|
|
|
dbgmsg( tracker->key, "addr %p -- connected %d (%zu %zu) -- connecting_at %zu",
|
|
tracker->addr,
|
|
(int)(tracker->connection_expiration_time > now), (size_t)tracker->connection_expiration_time, (size_t)now,
|
|
(size_t)tracker->connecting_at );
|
|
|
|
/* also need a valid connection ID... */
|
|
if( tracker->addr
|
|
&& ( tracker->connection_expiration_time <= now )
|
|
&& ( !tracker->connecting_at ) )
|
|
{
|
|
struct evbuffer * buf = evbuffer_new( );
|
|
tracker->connecting_at = now;
|
|
tracker->connection_transaction_id = tau_transaction_new( );
|
|
dbgmsg( tracker->key, "Trying to connect. Transaction ID is %u",
|
|
tracker->connection_transaction_id );
|
|
evbuffer_add_hton_64( buf, 0x41727101980LL );
|
|
evbuffer_add_hton_32( buf, TAU_ACTION_CONNECT );
|
|
evbuffer_add_hton_32( buf, tracker->connection_transaction_id );
|
|
tau_sendto( tracker->session, tracker->addr, tracker->port,
|
|
evbuffer_pullup( buf, -1 ),
|
|
evbuffer_get_length( buf ) );
|
|
evbuffer_free( buf );
|
|
return;
|
|
}
|
|
|
|
tau_tracker_timeout_reqs( tracker );
|
|
|
|
if( ( tracker->addr != NULL ) && ( tracker->connection_expiration_time > now ) )
|
|
tau_tracker_send_reqs( tracker );
|
|
}
|
|
|
|
/****
|
|
*****
|
|
***** SESSION
|
|
*****
|
|
****/
|
|
|
|
struct tr_announcer_udp
|
|
{
|
|
/* tau_tracker */
|
|
tr_ptrArray trackers;
|
|
|
|
tr_session * session;
|
|
};
|
|
|
|
static struct tr_announcer_udp*
|
|
announcer_udp_get( tr_session * session )
|
|
{
|
|
struct tr_announcer_udp * tau;
|
|
|
|
if( session->announcer_udp != NULL )
|
|
return session->announcer_udp;
|
|
|
|
tau = tr_new0( struct tr_announcer_udp, 1 );
|
|
tau->trackers = TR_PTR_ARRAY_INIT;
|
|
tau->session = session;
|
|
session->announcer_udp = tau;
|
|
return tau;
|
|
}
|
|
|
|
/* Finds the tau_tracker struct that corresponds to this url.
|
|
If it doesn't exist yet, create one. */
|
|
static struct tau_tracker *
|
|
tau_session_get_tracker( struct tr_announcer_udp * tau, const char * url )
|
|
{
|
|
int i;
|
|
int n;
|
|
int port;
|
|
char * host;
|
|
char * key;
|
|
struct tau_tracker * tracker = NULL;
|
|
|
|
/* see if we've already got a tracker that matches this host + port */
|
|
tr_urlParse( url, -1, NULL, &host, &port, NULL );
|
|
key = tr_strdup_printf( "%s:%d", host, port );
|
|
for( i=0, n=tr_ptrArraySize( &tau->trackers ); !tracker && i<n; ++i ) {
|
|
struct tau_tracker * tmp = tr_ptrArrayNth( &tau->trackers, i );
|
|
if( !tr_strcmp0( tmp->key, key ) )
|
|
tracker = tmp;
|
|
}
|
|
|
|
/* if we don't have a match, build a new tracker */
|
|
if( tracker == NULL )
|
|
{
|
|
tracker = tr_new0( struct tau_tracker, 1 );
|
|
tracker->session = tau->session;
|
|
tracker->key = key;
|
|
tracker->host = host;
|
|
tracker->port = port;
|
|
tracker->scrapes = TR_PTR_ARRAY_INIT;
|
|
tracker->announces = TR_PTR_ARRAY_INIT;
|
|
tr_ptrArrayAppend( &tau->trackers, tracker );
|
|
dbgmsg( tracker->key, "New tau_tracker created" );
|
|
}
|
|
else
|
|
{
|
|
tr_free( key );
|
|
tr_free( host );
|
|
}
|
|
|
|
return tracker;
|
|
}
|
|
|
|
/****
|
|
*****
|
|
***** PUBLIC API
|
|
*****
|
|
****/
|
|
|
|
void
|
|
tr_tracker_udp_upkeep( tr_session * session )
|
|
{
|
|
struct tr_announcer_udp * tau = session->announcer_udp;
|
|
|
|
if( tau != NULL )
|
|
tr_ptrArrayForeach( &tau->trackers,
|
|
(PtrArrayForeachFunc)tau_tracker_upkeep );
|
|
}
|
|
|
|
bool
|
|
tr_tracker_udp_is_idle( const tr_session * session )
|
|
{
|
|
int i;
|
|
int n;
|
|
struct tr_announcer_udp * tau = session->announcer_udp;
|
|
|
|
if( tau != NULL )
|
|
for( i=0, n=tr_ptrArraySize(&tau->trackers); i<n; ++i )
|
|
if( !tau_tracker_is_idle( tr_ptrArrayNth( &tau->trackers, i ) ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* drop dead now. */
|
|
void
|
|
tr_tracker_udp_close( tr_session * session )
|
|
{
|
|
struct tr_announcer_udp * tau = session->announcer_udp;
|
|
|
|
if( tau != NULL )
|
|
{
|
|
session->announcer_udp = NULL;
|
|
tr_ptrArrayDestruct( &tau->trackers, (PtrArrayForeachFunc)tau_tracker_free );
|
|
tr_free( tau );
|
|
}
|
|
}
|
|
|
|
/* start shutting down.
|
|
This doesn't destroy everything if there are requests,
|
|
but sets a deadline on how much longer to wait for the remaining ones */
|
|
void
|
|
tr_tracker_udp_start_shutdown( tr_session * session )
|
|
{
|
|
const time_t now = time( NULL );
|
|
struct tr_announcer_udp * tau = session->announcer_udp;
|
|
|
|
if( tau != NULL )
|
|
{
|
|
int i, n;
|
|
for( i=0, n=tr_ptrArraySize(&tau->trackers); i<n; ++i )
|
|
{
|
|
struct tau_tracker * tracker = tr_ptrArrayNth( &tau->trackers, i );
|
|
tracker->close_at = now + 3;
|
|
tau_tracker_upkeep( tracker );
|
|
}
|
|
}
|
|
}
|
|
|
|
/* @brief process an incoming udp message if it's a tracker response.
|
|
* @return true if msg was a tracker response; false otherwise */
|
|
bool
|
|
tau_handle_message( tr_session * session, const uint8_t * msg, size_t msglen )
|
|
{
|
|
int i;
|
|
int n;
|
|
struct tr_announcer_udp * tau;
|
|
tau_action_t action_id;
|
|
tau_transaction_t transaction_id;
|
|
struct evbuffer * buf;
|
|
|
|
/*fprintf( stderr, "got an incoming udp message w/len %zu\n", msglen );*/
|
|
|
|
if( !session || !session->announcer_udp )
|
|
return false;
|
|
if( msglen < (sizeof(uint32_t)*2) )
|
|
return false;
|
|
|
|
/* extract the action_id and see if it makes sense */
|
|
buf = evbuffer_new( );
|
|
evbuffer_add_reference( buf, msg, msglen, NULL, NULL );
|
|
action_id = evbuffer_read_ntoh_32( buf );
|
|
if( !is_tau_response_message( action_id, msglen ) ) {
|
|
evbuffer_free( buf );
|
|
return false;
|
|
}
|
|
|
|
/* extract the transaction_id and look for a match */
|
|
tau = session->announcer_udp;
|
|
transaction_id = evbuffer_read_ntoh_32( buf );
|
|
/*fprintf( stderr, "UDP got a transaction_id %u...\n", transaction_id );*/
|
|
for( i=0, n=tr_ptrArraySize( &tau->trackers ); i<n; ++i )
|
|
{
|
|
int j, jn;
|
|
tr_ptrArray * reqs;
|
|
struct tau_tracker * tracker = tr_ptrArrayNth( &tau->trackers, i );
|
|
|
|
/* is it a connection response? */
|
|
if( tracker->connecting_at
|
|
&& ( transaction_id == tracker->connection_transaction_id ) )
|
|
{
|
|
dbgmsg( tracker->key, "%"PRIu32" is my connection request!", transaction_id );
|
|
on_tracker_connection_response( tracker, action_id, buf );
|
|
evbuffer_free( buf );
|
|
return true;
|
|
}
|
|
|
|
/* is it a response to one of this tracker's announces? */
|
|
reqs = &tracker->announces;
|
|
for( j=0, jn=tr_ptrArraySize(reqs); j<jn; ++j ) {
|
|
struct tau_announce_request * req = tr_ptrArrayNth( reqs, j );
|
|
if( req->sent_at && ( transaction_id == req->transaction_id ) ) {
|
|
dbgmsg( tracker->key, "%"PRIu32" is an announce request!", transaction_id );
|
|
tr_ptrArrayRemove( reqs, j );
|
|
on_announce_response( req, action_id, buf );
|
|
tau_announce_request_free( req );
|
|
evbuffer_free( buf );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* is it a response to one of this tracker's scrapes? */
|
|
reqs = &tracker->scrapes;
|
|
for( j=0, jn=tr_ptrArraySize(reqs); j<jn; ++j ) {
|
|
struct tau_scrape_request * req = tr_ptrArrayNth( reqs, j );
|
|
if( req->sent_at && ( transaction_id == req->transaction_id ) ) {
|
|
dbgmsg( tracker->key, "%"PRIu32" is a scrape request!", transaction_id );
|
|
tr_ptrArrayRemove( reqs, j );
|
|
on_scrape_response( req, action_id, buf );
|
|
tau_scrape_request_free( req );
|
|
evbuffer_free( buf );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* no match... */
|
|
evbuffer_free( buf );
|
|
return false;
|
|
}
|
|
|
|
void
|
|
tr_tracker_udp_announce( tr_session * session,
|
|
const tr_announce_request * request,
|
|
tr_announce_response_func response_func,
|
|
void * user_data )
|
|
{
|
|
struct tr_announcer_udp * tau = announcer_udp_get( session );
|
|
struct tau_tracker * tracker = tau_session_get_tracker( tau, request->url );
|
|
struct tau_announce_request * r = tau_announce_request_new( request,
|
|
response_func,
|
|
user_data );
|
|
tr_ptrArrayAppend( &tracker->announces, r );
|
|
tau_tracker_upkeep( tracker );
|
|
}
|
|
|
|
void
|
|
tr_tracker_udp_scrape( tr_session * session,
|
|
const tr_scrape_request * request,
|
|
tr_scrape_response_func response_func,
|
|
void * user_data )
|
|
{
|
|
struct tr_announcer_udp * tau = announcer_udp_get( session );
|
|
struct tau_tracker * tracker = tau_session_get_tracker( tau, request->url );
|
|
struct tau_scrape_request * r = tau_scrape_request_new( request,
|
|
response_func,
|
|
user_data );
|
|
tr_ptrArrayAppend( &tracker->scrapes, r );
|
|
tau_tracker_upkeep( tracker );
|
|
}
|