transmission/libtransmission/tracker.c

1101 lines
32 KiB
C

/*
* This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
*
* 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$
*/
#include <assert.h>
#include <ctype.h> /* isalnum */
#include <limits.h> /* INT_MAX */
#include <stdio.h> /* snprintf */
#include <stdlib.h>
#include <string.h> /* strcmp, strchr */
#include <sys/queue.h> /* for evhttp */
#include <sys/types.h> /* for evhttp */
#include <event.h>
#include <evhttp.h>
#include "transmission.h"
#include "bencode.h"
#include "completion.h"
#include "net.h"
#include "ptrarray.h"
#include "publish.h"
#include "timer.h"
#include "tracker.h"
#include "trevent.h"
#include "utils.h"
#define MINUTES_TO_MSEC(N) ((N) * 60 * 1000)
/* manual announces via "update tracker" are allowed this frequently */
#define MANUAL_ANNOUNCE_INTERVAL_MSEC (MINUTES_TO_MSEC(10))
/* unless the tracker tells us otherwise, rescrape this frequently */
#define DEFAULT_SCRAPE_INTERVAL_MSEC (MINUTES_TO_MSEC(15))
/* unless the tracker tells us otherwise, reannounce this frequently */
#define DEFAULT_ANNOUNCE_INTERVAL_MSEC (MINUTES_TO_MSEC(20))
/* this is how long we'll leave a scrape request hanging before timeout */
#define SCRAPE_TIMEOUT_INTERVAL_SEC 60
/* this is how long we'll leave a tracker request hanging before timeout */
#define REQ_TIMEOUT_INTERVAL_SEC 60
/* the value of the 'numwant' argument passed in tracker requests */
#define NUMWANT 75
/* the length of the 'key' argument passed in tracker requests */
#define TR_KEY_LEN 10
/**
***
**/
typedef struct
{
tr_handle_t * handle;
tr_ptrArray_t * torrents;
tr_ptrArray_t * scraping;
tr_ptrArray_t * scrapeQueue;
/* these are set from the latest scrape or tracker response */
int announceIntervalMsec;
int minAnnounceIntervalMsec;
int scrapeIntervalMsec;
/* calculated when we get fewer scrapes
back than we asked for */
int multiscrapeMax;
tr_tracker_info_t * redirect;
tr_tracker_info_t * addresses;
int addressIndex;
int addressCount;
int * tierFronts;
char * primaryAddress;
/* sent as the "key" argument in tracker requests
to verify us if our IP address changes.
This is immutable for the life of the tracker object. */
char key_param[TR_KEY_LEN+1];
tr_timer_tag scrapeTag;
}
Tracker;
/* this is the Torrent struct, but since it's the pointer
passed around in the public API of this tracker module,
its *public* name is tr_tracker_s... wheee */
typedef struct tr_tracker_s
{
tr_publisher_t * publisher;
/* torrent hash string */
uint8_t hash[SHA_DIGEST_LENGTH];
char escaped[SHA_DIGEST_LENGTH * 3 + 1];
/* corresponds to the peer_id sent as a tracker request parameter.
OiNK's op TooMuchTime says: "When the same torrent is opened and
closed and opened again without quitting Transmission ...
change the peerid. It would help sometimes if a stopped event
was missed to ensure that we didn't think someone was cheating. */
char peer_id[TR_ID_LEN + 1];
/* these are set from the latest scrape or tracker response...
-1 means 'unknown' */
int timesDownloaded;
int seeders;
int leechers;
char * trackerID;
/* the last tracker request we sent. (started, stopped, etc.)
automatic announces are an empty string;
NULL means no message has ever been sent */
char * lastRequest;
uint64_t manualAnnounceAllowedAt;
Tracker * tracker;
tr_timer_tag scrapeTag;
tr_timer_tag reannounceTag;
struct evhttp_request * httpReq;
tr_torrent_t * torrent;
}
Torrent;
static int
trackerCompare( const void * va, const void * vb )
{
const Tracker * a = ( const Tracker * ) va;
const Tracker * b = ( const Tracker * ) vb;
return strcmp( a->primaryAddress, b->primaryAddress );
}
static int
torrentCompare( const void * va, const void * vb )
{
const Torrent * a = (const Torrent*) va;
const Torrent * b = (const Torrent*) vb;
return memcmp( a->hash, b->hash, SHA_DIGEST_LENGTH );
}
/***
****
***/
typedef struct
{
char * address;
int port;
struct evhttp_connection * evconn;
}
connection_key_t;
static int
connectionCompare( const void * va, const void * vb )
{
const connection_key_t * a = (const connection_key_t *) va;
const connection_key_t * b = (const connection_key_t *) vb;
int ret = strcmp( a->address, b->address );
if( ret ) return ret;
return a->port - b->port;
}
static struct evhttp_connection*
getConnection( const char * address, int port )
{
connection_key_t *val, tmp;
static tr_ptrArray_t * connections = NULL;
if( !connections )
connections = tr_ptrArrayNew( );
tmp.address = (char*) address;
tmp.port = port;
val = tr_ptrArrayFindSorted( connections, &tmp, connectionCompare );
if( !val )
{
val = tr_new( connection_key_t, 1 );
val->address = tr_strdup( address );
val->port = port;
val->evconn = evhttp_connection_new( address, port );
tr_ptrArrayInsertSorted( connections, val, connectionCompare );
}
return val->evconn;
}
/***
**** PUBLISH
***/
static const tr_tracker_event_t emptyEvent = { 0, NULL, NULL, NULL, 0 };
static void
publishMessage( Torrent * tor, const char * msg, int type )
{
tr_tracker_event_t event = emptyEvent;
event.hash = tor->hash;
event.messageType = type;
event.text = msg;
tr_publisherPublish( tor->publisher, tor, &event );
}
static void
publishErrorMessage( Torrent * tor, const char * msg )
{
publishMessage( tor, msg, TR_TRACKER_ERROR );
}
static void
publishWarningMessage( Torrent * tor, const char * msg )
{
publishMessage( tor, msg, TR_TRACKER_WARNING );
}
static void
publishNewPeers( Torrent * tor, int count, uint8_t * peers )
{
tr_tracker_event_t event = emptyEvent;
event.hash = tor->hash;
event.messageType = TR_TRACKER_PEERS;
event.peerCount = count;
event.peerCompact = peers;
tr_inf( "Torrent \"%s\" got %d new peers", tor->torrent->info.name, count );
tr_publisherPublish( tor->publisher, tor, &event );
}
static void
publishStopped( Torrent * tor )
{
tr_tracker_event_t event = emptyEvent;
event.hash = tor->hash;
event.messageType = TR_TRACKER_STOPPED;
tr_publisherPublish( tor->publisher, tor, &event );
}
/***
**** LIFE CYCLE
***/
static tr_ptrArray_t *
getTrackerLookupTable( void )
{
static tr_ptrArray_t * myTrackers = NULL;
if( !myTrackers )
myTrackers = tr_ptrArrayNew( );
return myTrackers;
}
static void
generateKeyParam( char * msg, int len )
{
int i;
const char * pool = "abcdefghijklmnopqrstuvwxyz0123456789";
for( i=0; i<len; ++i )
*msg++ = pool[tr_rand(36)];
*msg = '\0';
}
static int onTrackerScrapeNow( void* );
static void
tr_trackerScrapeSoon( Tracker * t )
{
/* don't start more than one scrape at once for the same tracker... */
if( !tr_ptrArrayEmpty( t->scraping ) )
return;
if( !t->scrapeTag )
t->scrapeTag = tr_timerNew( t->handle,
onTrackerScrapeNow, t,
NULL, 1000 );
}
static Tracker*
tr_trackerGet( const tr_torrent_t * tor )
{
const tr_info_t * info = &tor->info;
tr_ptrArray_t * trackers = getTrackerLookupTable( );
Tracker *t, tmp;
assert( info != NULL );
assert( info->primaryAddress && *info->primaryAddress );
tmp.primaryAddress = info->primaryAddress;
t = tr_ptrArrayFindSorted( trackers, &tmp, trackerCompare );
assert( t==NULL || !strcmp(t->primaryAddress,info->primaryAddress) );
if( t == NULL ) /* no such tracker.... create one */
{
int i, j, sum, *iwalk;
tr_tracker_info_t * nwalk;
tr_dbg( "making a new tracker for \"%s\"", info->primaryAddress );
t = tr_new0( Tracker, 1 );
t->handle = tor->handle;
t->primaryAddress = tr_strdup( info->primaryAddress );
t->scrapeIntervalMsec = DEFAULT_SCRAPE_INTERVAL_MSEC;
t->announceIntervalMsec = DEFAULT_ANNOUNCE_INTERVAL_MSEC;
t->minAnnounceIntervalMsec = DEFAULT_ANNOUNCE_INTERVAL_MSEC;
t->multiscrapeMax = INT_MAX;
t->torrents = tr_ptrArrayNew( );
t->scraping = tr_ptrArrayNew( );
t->scrapeQueue = tr_ptrArrayNew( );
generateKeyParam( t->key_param, TR_KEY_LEN );
for( sum=i=0; i<info->trackerTiers; ++i )
sum += info->trackerList[i].count;
t->addresses = nwalk = tr_new0( tr_tracker_info_t, sum );
t->addressIndex = 0;
t->addressCount = sum;
t->tierFronts = iwalk = tr_new0( int, sum );
for( i=0; i<info->trackerTiers; ++i )
{
const int tierFront = nwalk - t->addresses;
for( j=0; j<info->trackerList[i].count; ++j )
{
const tr_tracker_info_t * src = &info->trackerList[i].list[j];
nwalk->address = tr_strdup( src->address );
nwalk->port = src->port;
nwalk->announce = tr_strdup( src->announce );
nwalk->scrape = tr_strdup( src->scrape );
++nwalk;
*iwalk++ = tierFront;
}
}
assert( nwalk - t->addresses == sum );
assert( iwalk - t->tierFronts == sum );
tr_ptrArrayInsertSorted( trackers, t, trackerCompare );
}
return t;
}
static Torrent *
getExistingTorrent( Tracker * t, const uint8_t hash[SHA_DIGEST_LENGTH] )
{
Torrent tmp;
memcpy( tmp.hash, hash, SHA_DIGEST_LENGTH );
return tr_ptrArrayFindSorted( t->torrents, &tmp, torrentCompare );
}
static void
escape( char * out, const uint8_t * in, int in_len ) /* rfc2396 */
{
const uint8_t *end = in + in_len;
while( in != end )
if( isalnum(*in) )
*out++ = (char) *in++;
else
out += snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
*out = '\0';
}
static int
onTorrentFreeNow( void * vtor )
{
Torrent * tor = (Torrent *) vtor;
Tracker * t = tor->tracker;
tr_ptrArrayRemoveSorted( t->torrents, tor, torrentCompare );
tr_ptrArrayRemoveSorted( t->scrapeQueue, tor, torrentCompare );
tr_ptrArrayRemoveSorted( t->scraping, tor, torrentCompare );
tr_timerFree( &tor->scrapeTag );
tr_timerFree( &tor->reannounceTag );
tr_publisherFree( tor->publisher );
tr_free( tor->trackerID );
tr_free( tor->lastRequest );
tr_free( tor );
if( tr_ptrArrayEmpty( t->torrents ) ) /* last one.. free the tracker too */
{
int i;
tr_ptrArrayRemoveSorted( getTrackerLookupTable( ), t, trackerCompare );
tr_ptrArrayFree( t->torrents );
tr_ptrArrayFree( t->scrapeQueue );
tr_ptrArrayFree( t->scraping );
for( i=0; i<t->addressCount; ++i )
tr_trackerInfoClear( &t->addresses[i] );
if( t->redirect ) {
tr_trackerInfoClear( t->redirect );
tr_free( t->redirect );
}
tr_timerFree( &t->scrapeTag );
tr_free( t->primaryAddress );
tr_free( t->addresses );
tr_free( t->tierFronts );
tr_free( t );
}
return FALSE;
}
void
tr_trackerFree( Torrent * tor )
{
tr_timerNew( tor->tracker->handle,
onTorrentFreeNow, tor, NULL, 0 );
}
Torrent*
tr_trackerNew( tr_torrent_t * torrent )
{
Torrent * tor;
Tracker * t = tr_trackerGet( torrent );
assert( getExistingTorrent( t, torrent->info.hash ) == NULL );
/* create a new Torrent and queue it for scraping */
tor = tr_new0( Torrent, 1 );
tor->publisher = tr_publisherNew( );
tor->tracker = t;
tor->torrent = torrent;
tor->timesDownloaded = -1;
tor->seeders = -1;
tor->leechers = -1;
tor->manualAnnounceAllowedAt = ~0;
memcpy( tor->hash, torrent->info.hash, SHA_DIGEST_LENGTH );
escape( tor->escaped, torrent->info.hash, SHA_DIGEST_LENGTH );
tr_ptrArrayInsertSorted( t->torrents, tor, torrentCompare );
tr_ptrArrayInsertSorted( t->scrapeQueue, tor, torrentCompare );
tr_trackerScrapeSoon( t );
return tor;
}
/***
**** UTIL
***/
static int
parseBencResponse( struct evhttp_request * req, benc_val_t * setme )
{
const unsigned char * body = EVBUFFER_DATA( req->input_buffer );
const int bodylen = EVBUFFER_LENGTH( req->input_buffer );
int ret = 1;
int i;
for( i=0; ret && i<bodylen; ++i )
if( !tr_bencLoad( body+i, bodylen-1, setme, NULL ) )
ret = 0;
return ret;
}
static char*
updateAddresses( Tracker * t, const struct evhttp_request * req )
{
char * ret = NULL;
int used = 0, max = 0;
int moveToNextAddress = FALSE;
if( !req )
{
tr_sprintf( &ret, &used, &max,
"Connecting to %s got a NULL response",
t->addresses[t->addressIndex].announce );
tr_inf( ret );
moveToNextAddress = TRUE;
}
else if( req->response_code == HTTP_OK )
{
if( t->redirect != NULL )
{
/* multitracker spec: "if a connection with a tracker is
successful, it will be moved to the front of the tier." */
const int i = t->addressIndex;
const int j = t->tierFronts[i];
const tr_tracker_info_t swap = t->addresses[i];
t->addresses[i] = t->addresses[j];
t->addresses[j] = swap;
}
}
else if( ( req->response_code == HTTP_MOVEPERM )
|| ( req->response_code == HTTP_MOVETEMP ) )
{
const char * loc = evhttp_find_header( req->input_headers, "Location" );
tr_tracker_info_t tmp;
if( tr_trackerInfoInit( &tmp, loc, -1 ) ) /* a bad redirect? */
{
moveToNextAddress = TRUE;
}
else if( req->response_code == HTTP_MOVEPERM )
{
tr_tracker_info_t * cur = &t->addresses[t->addressIndex];
tr_trackerInfoClear( cur );
*cur = tmp;
}
else if( req->response_code == HTTP_MOVETEMP )
{
if( t->redirect == NULL )
t->redirect = tr_new0( tr_tracker_info_t, 1 );
else
tr_trackerInfoClear( t->redirect );
*t->redirect = tmp;
}
}
else
{
tr_sprintf( &ret, &used, &max,
"Error connecting: %s",
t->addresses[t->addressIndex].announce,
req->response_code_line );
moveToNextAddress = TRUE;
}
if( moveToNextAddress )
if ( ++t->addressIndex >= t->addressCount )
t->addressIndex = 0;
return ret;
}
static tr_tracker_info_t *
getCurrentAddress( const Tracker * t )
{
assert( t->addresses != NULL );
assert( t->addressIndex >= 0 );
assert( t->addressIndex < t->addressCount );
return &t->addresses[t->addressIndex];
}
static int
trackerSupportsScrape( const Tracker * t )
{
const tr_tracker_info_t * info = getCurrentAddress( t );
return ( info != NULL )
&& ( info->scrape != NULL )
&& ( info->scrape[0] != '\0' );
}
static void
addCommonHeaders( const Tracker * t,
struct evhttp_request * req )
{
char buf[1024];
tr_tracker_info_t * address = getCurrentAddress( t );
snprintf( buf, sizeof(buf), "%s:%d", address->address, address->port );
evhttp_add_header( req->output_headers, "Host", buf );
evhttp_add_header( req->output_headers, "Connection", "close" );
evhttp_add_header( req->output_headers, "Content-Length", "0" );
evhttp_add_header( req->output_headers, "User-Agent",
TR_NAME "/" LONG_VERSION_STRING );
}
/***
****
**** SCRAPE
****
***/
static int
onTorrentScrapeNow( void * vtor )
{
Torrent * tor = (Torrent *) vtor;
if( trackerSupportsScrape( tor->tracker ) ) {
tr_ptrArrayInsertSorted( tor->tracker->scrapeQueue, tor, torrentCompare );
tr_trackerScrapeSoon( tor->tracker );
}
tor->scrapeTag = NULL;
return FALSE;
}
static void
onScrapeResponse( struct evhttp_request * req, void * vt )
{
char * errmsg;
Tracker * t = (Tracker*) vt;
tr_inf( "Got scrape response from '%s': %s",
t->primaryAddress,
(req ? req->response_code_line : "(null)") );
if( req && ( req->response_code == HTTP_OK ) )
{
int numResponses = 0;
benc_val_t benc, *files;
const int n_scraping = tr_ptrArraySize( t->scraping );
const int bencLoaded = !parseBencResponse( req, &benc );
if( bencLoaded
&& (( files = tr_bencDictFind( &benc, "files" ) ))
&& ( files->type == TYPE_DICT ) )
{
int i;
for( i=0; i<files->val.l.count; i+=2 )
{
const uint8_t* hash =
(const uint8_t*) files->val.l.vals[i].val.s.s;
benc_val_t *tmp, *flags;
benc_val_t *tordict = &files->val.l.vals[i+1];
Torrent * tor = getExistingTorrent( t, hash );
++numResponses;
if( !tor ) {
tr_err( "Got an unrequested scrape response!" );
continue;
}
if(( tmp = tr_bencDictFind( tordict, "complete" )))
tor->seeders = tmp->val.i;
if(( tmp = tr_bencDictFind( tordict, "incomplete" )))
tor->leechers = tmp->val.i;
if(( tmp = tr_bencDictFind( tordict, "downloaded" )))
tor->timesDownloaded = tmp->val.i;
if(( flags = tr_bencDictFind( tordict, "flags" )))
if(( tmp = tr_bencDictFind( flags, "min_request_interval")))
t->scrapeIntervalMsec = tmp->val.i * 1000;
assert( tr_ptrArrayFindSorted(t->scraping,tor,torrentCompare) );
tr_ptrArrayRemoveSorted( t->scraping, tor, torrentCompare );
assert( !tor->scrapeTag );
tor->scrapeTag = tr_timerNew( t->handle,
onTorrentScrapeNow,
tor, NULL,
t->scrapeIntervalMsec );
tr_dbg( "Torrent '%s' scrape successful."
" Rescraping in %d seconds",
tor->torrent->info.name, t->scrapeIntervalMsec/1000 );
}
if( !files->val.l.count )
{
/* got an empty files dictionary! This probably means the
torrents we're scraping have expired from the tracker,
so make sure they're stopped. It also means any previous
changes to multiscrapeMax are suspect, so reset that. */
int n;
Torrent ** torrents = (Torrent**)
tr_ptrArrayPeek( t->scraping, &n );
for( i=0; i<n; ++i )
tr_trackerStop( torrents[i] );
tr_ptrArrayClear( t->scraping );
t->multiscrapeMax = INT_MAX;
}
}
if( bencLoaded )
tr_bencFree( &benc );
/* if the tracker gave us back fewer torrents than we
thought we should get, maybe our multiscrape string
is too big... limit it based on how many we got back */
if( ( 0 < numResponses ) && ( numResponses < n_scraping ) )
t->multiscrapeMax = numResponses;
}
if (( errmsg = updateAddresses( t, req ) )) {
tr_err( errmsg );
tr_free( errmsg );
}
if( !tr_ptrArrayEmpty( t->scraping ) )
{
int i, n;
Torrent ** torrents =
(Torrent**) tr_ptrArrayPeek( t->scraping, &n );
for( i=0; i<n; ++i )
onTorrentScrapeNow( torrents[i] );
tr_ptrArrayClear( t->scraping );
}
if( !tr_ptrArrayEmpty( t->scrapeQueue ) )
tr_trackerScrapeSoon( t );
}
static int
onTrackerScrapeNow( void * vt )
{
Tracker * t = (Tracker*) vt;
const tr_tracker_info_t * address = getCurrentAddress( t );
assert( tr_ptrArrayEmpty( t->scraping ) );
if( trackerSupportsScrape( t ) && !tr_ptrArrayEmpty( t->scrapeQueue ) )
{
int i, n, len, addr_len, ask_n;
char *march, *uri;
Torrent ** torrents =
(Torrent**) tr_ptrArrayPeek( t->scrapeQueue, &n );
struct evhttp_connection *evcon = NULL;
struct evhttp_request *req = NULL;
ask_n = n;
if( ask_n > t->multiscrapeMax )
ask_n = t->multiscrapeMax;
/**
*** Build the scrape request
**/
len = addr_len = strlen( address->scrape );
for( i=0; i<ask_n; ++i )
len += strlen("&info_hash=") + strlen(torrents[i]->escaped);
++len; /* for nul */
uri = march = tr_new( char, len );
memcpy( march, address->scrape, addr_len ); march += addr_len;
for( i=0; i<ask_n; ++i ) {
const int elen = strlen( torrents[i]->escaped );
*march++ = i?'&':'?';
memcpy( march, "info_hash=", 10); march += 10;
memcpy( march, torrents[i]->escaped, elen ); march += elen;
}
*march++ = '\0';
assert( march - uri == len );
/* move the first n_ask torrents from scrapeQueue to scraping */
for( i=0; i<ask_n; ++i )
tr_ptrArrayInsertSorted( t->scraping, torrents[i], torrentCompare );
tr_ptrArrayErase( t->scrapeQueue, 0, ask_n );
/* ping the tracker */
tr_inf( "Sending scrape to tracker %s:%d: %s",
address->address, address->port, uri );
evcon = getConnection( address->address, address->port );
evhttp_connection_set_timeout( evcon, SCRAPE_TIMEOUT_INTERVAL_SEC );
req = evhttp_request_new( onScrapeResponse, t );
assert( req );
addCommonHeaders( t, req );
tr_evhttp_make_request( t->handle, evcon, req, EVHTTP_REQ_GET, uri );
}
t->scrapeTag = NULL;
return FALSE;
}
/***
****
**** TRACKER REQUESTS
****
***/
static int
torrentIsRunning( const Torrent * tor )
{
return ( tor != NULL )
&& ( tor->lastRequest != NULL )
&& ( strcmp( tor->lastRequest, "stopped" ) );
}
static char*
buildTrackerRequestURI( const Torrent * tor, const char * eventName )
{
const tr_torrent_t * torrent = tor->torrent;
const int stopping = !strcmp( eventName, "stopped" );
const int numwant = stopping ? 0 : NUMWANT;
char buf[4096];
snprintf( buf, sizeof(buf), "%s"
"?info_hash=%s"
"&peer_id=%s"
"&port=%d"
"&uploaded=%"PRIu64
"&downloaded=%"PRIu64
"&corrupt=%"PRIu64
"&left=%"PRIu64
"&compact=1"
"&numwant=%d"
"&key=%s"
"%s%s"
"%s%s",
getCurrentAddress(tor->tracker)->announce,
tor->escaped,
tor->peer_id,
torrent->publicPort,
torrent->uploadedCur,
torrent->downloadedCur,
torrent->corruptCur,
tr_cpLeftUntilComplete( torrent->completion ),
numwant,
tor->tracker->key_param,
( ( eventName && *eventName ) ? "&event=" : "" ),
( ( eventName && *eventName ) ? eventName : "" ),
( ( tor->trackerID && *tor->trackerID ) ? "&trackerid=" : "" ),
( ( tor->trackerID && *tor->trackerID ) ? tor->trackerID : "" ) );
return tr_strdup( buf );
}
/* Convert to compact form */
static uint8_t *
parseOldPeers( benc_val_t * bePeers, int * peerCount )
{
int i, count;
uint8_t * compact;
assert( bePeers->type == TYPE_LIST );
compact = tr_new( uint8_t, 6 * bePeers->val.l.count );
for( i=count=0; i<bePeers->val.l.count; ++i )
{
struct in_addr addr;
tr_port_t port;
benc_val_t * val;
benc_val_t * peer = &bePeers->val.l.vals[i];
val = tr_bencDictFind( peer, "ip" );
if( !val || val->type!=TYPE_STR || tr_netResolve(val->val.s.s, &addr) )
continue;
memcpy( &compact[6 * count], &addr, 4 );
val = tr_bencDictFind( peer, "port" );
if( !val || val->type!=TYPE_INT || val->val.i<0 || val->val.i>0xffff )
continue;
port = htons( val->val.i );
memcpy( &compact[6 * count + 4], &port, 2 );
++count;
}
*peerCount = count;
return compact;
}
/* handle braindead trackers whose minimums is higher
than the interval. */
static void
setAnnounceInterval( Tracker * t,
int minimum,
int interval )
{
assert( t != NULL );
if( minimum > 0 )
t->minAnnounceIntervalMsec = minimum;
if( interval > 0 )
t->announceIntervalMsec = interval;
if( t->announceIntervalMsec < t->minAnnounceIntervalMsec )
t->announceIntervalMsec = t->minAnnounceIntervalMsec;
}
static int onReannounceNow( void * vtor );
static void
onTrackerResponse( struct evhttp_request * req, void * vtor )
{
char * errmsg;
Torrent * tor = (Torrent *) vtor;
const int isStopped = !torrentIsRunning( tor );
int reannounceInterval;
tr_inf( "Torrent \"%s\" tracker response: %s",
tor->torrent->info.name,
( req ? req->response_code_line : "(null)") );
if( req && ( req->response_code == HTTP_OK ) )
{
benc_val_t benc;
const int bencLoaded = !parseBencResponse( req, &benc );
if( bencLoaded && benc.type==TYPE_DICT )
{
benc_val_t * tmp;
if(( tmp = tr_bencDictFind( &benc, "failure reason" )))
publishErrorMessage( tor, tmp->val.s.s );
if(( tmp = tr_bencDictFind( &benc, "warning message" )))
publishWarningMessage( tor, tmp->val.s.s );
if(( tmp = tr_bencDictFind( &benc, "interval" )))
setAnnounceInterval( tor->tracker, -1, tmp->val.i * 1000 );
if(( tmp = tr_bencDictFind( &benc, "min interval" )))
setAnnounceInterval( tor->tracker, tmp->val.i * 1000, -1 );
if(( tmp = tr_bencDictFind( &benc, "tracker id" )))
tor->trackerID = tr_strndup( tmp->val.s.s, tmp->val.s.i );
if(( tmp = tr_bencDictFind( &benc, "complete" )))
tor->seeders = tmp->val.i;
if(( tmp = tr_bencDictFind( &benc, "incomplete" )))
tor->leechers = tmp->val.i;
if(( tmp = tr_bencDictFind( &benc, "peers" )))
{
int peerCount = 0;
uint8_t * peerCompact = NULL;
if( tmp->type == TYPE_LIST ) /* original protocol */
{
if( tmp->val.l.count > 0 )
peerCompact = parseOldPeers( tmp, &peerCount );
}
else if( tmp->type == TYPE_STR ) /* "compact" extension */
{
if( tmp->val.s.i >= 6 )
{
peerCount = tmp->val.s.i / 6;
peerCompact = tr_new( uint8_t, tmp->val.s.i );
memcpy( peerCompact, tmp->val.s.s, tmp->val.s.i );
}
}
publishNewPeers( tor, peerCount, peerCompact );
tr_free( peerCompact );
}
}
reannounceInterval = isStopped
? -1
: tor->tracker->announceIntervalMsec;
if( bencLoaded )
tr_bencFree( &benc );
}
else
{
tr_inf( "Bad response from tracker '%s' on request '%s' "
"for torrent '%s'... trying again in 30 seconds",
tor->tracker->primaryAddress,
tor->lastRequest,
tor->torrent->info.name );
reannounceInterval = 30 * 1000;
}
if (( errmsg = updateAddresses( tor->tracker, req ) )) {
publishErrorMessage( tor, errmsg );
tr_err( errmsg );
tr_free( errmsg );
}
tor->httpReq = NULL;
if( isStopped )
publishStopped( tor );
else if( reannounceInterval > 0 ) {
tr_dbg( "torrent '%s' reannouncing in %d seconds",
tor->torrent->info.name, (reannounceInterval/1000) );
tor->reannounceTag = tr_timerNew( tor->tracker->handle,
onReannounceNow, tor, NULL,
reannounceInterval );
tor->manualAnnounceAllowedAt
= tr_date() + MANUAL_ANNOUNCE_INTERVAL_MSEC;
}
}
static int
sendTrackerRequest( void * vtor, const char * eventName )
{
Torrent * tor = (Torrent *) vtor;
const tr_tracker_info_t * address = getCurrentAddress( tor->tracker );
char * uri = buildTrackerRequestURI( tor, eventName );
struct evhttp_connection * evcon = NULL;
tr_inf( "Torrent \"%s\" sending '%s' to tracker %s:%d: %s",
tor->torrent->info.name,
(eventName ? eventName : "periodic announce"),
address->address, address->port,
uri );
/* kill any pending requests */
tr_timerFree( &tor->reannounceTag );
evcon = getConnection( address->address, address->port );
if ( !evcon ) {
tr_err( "Can't make a connection to %s:%d", address->address, address->port );
tr_free( uri );
} else {
tr_free( tor->lastRequest );
tor->lastRequest = tr_strdup( eventName );
evhttp_connection_set_timeout( evcon, REQ_TIMEOUT_INTERVAL_SEC );
tor->httpReq = evhttp_request_new( onTrackerResponse, tor );
addCommonHeaders( tor->tracker, tor->httpReq );
tr_evhttp_make_request( tor->tracker->handle, evcon,
tor->httpReq, EVHTTP_REQ_GET, uri );
}
return FALSE;
}
static int
onReannounceNow( void * vtor )
{
Torrent * tor = (Torrent *) vtor;
sendTrackerRequest( tor, "" );
tor->reannounceTag = NULL;
return FALSE;
}
/***
**** PUBLIC
***/
tr_publisher_tag
tr_trackerSubscribe( Torrent * tor,
tr_delivery_func func,
void * user_data )
{
return tr_publisherSubscribe( tor->publisher, func, user_data );
}
void
tr_trackerUnsubscribe( Torrent * tor,
tr_publisher_tag tag )
{
tr_publisherUnsubscribe( tor->publisher, tag );
}
const tr_tracker_info_t *
tr_trackerGetAddress( const Torrent * tor )
{
return getCurrentAddress( tor->tracker );
}
int
tr_trackerCanManualAnnounce ( const Torrent * tor )
{
/* return true if this torrent's currently running
and it's been long enough since the last announce */
return ( torrentIsRunning( tor ) )
&& ( tr_date() >= tor->manualAnnounceAllowedAt );
}
void
tr_trackerGetCounts( const Torrent * tor,
int * setme_completedCount,
int * setme_leecherCount,
int * setme_seederCount )
{
if( setme_completedCount )
*setme_completedCount = tor->timesDownloaded;
if( setme_leecherCount )
*setme_leecherCount = tor->leechers;
if( setme_seederCount )
*setme_seederCount = tor->seeders;
}
void
tr_trackerStart( Torrent * tor )
{
tr_peerIdNew( tor->peer_id, sizeof(tor->peer_id) );
if( !tor->reannounceTag && !tor->httpReq )
sendTrackerRequest( tor, "started" );
}
void
tr_trackerReannounce( Torrent * tor )
{
sendTrackerRequest( tor, "started" );
}
void
tr_trackerCompleted( Torrent * tor )
{
sendTrackerRequest( tor, "completed" );
}
void
tr_trackerStop( Torrent * tor )
{
sendTrackerRequest( tor, "stopped" );
}
void
tr_trackerChangeMyPort( Torrent * tor )
{
if( torrentIsRunning( tor ) )
tr_trackerReannounce( tor );
}