transmission/libtransmission/tracker.c

1153 lines
33 KiB
C

/*
* This file Copyright (C) 2007-2008 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 <stdlib.h>
#include <string.h> /* strcmp, strchr */
#include <event.h>
#include "transmission.h"
#include "bencode.h"
#include "crypto.h"
#include "completion.h"
#include "net.h"
#include "publish.h"
#include "resume.h"
#include "torrent.h"
#include "tracker.h"
#include "trevent.h"
#include "utils.h"
#include "web.h"
enum
{
/* the announceAt fields are set to this when the action is disabled */
TR_TRACKER_STOPPED = 0,
/* the announceAt fields are set to this when the action is in progress */
TR_TRACKER_BUSY = 1,
HTTP_OK = 200,
/* seconds between tracker pulses */
PULSE_INTERVAL_MSEC = 1000,
/* unless the tracker says otherwise, rescrape this frequently */
DEFAULT_SCRAPE_INTERVAL_SEC = ( 60 * 15 ),
/* unless the tracker says otherwise, this is the announce interval */
DEFAULT_ANNOUNCE_INTERVAL_SEC = ( 60 * 4 ),
/* unless the tracker says otherwise, this is the announce min_interval */
DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC = ( 60 * 2 ),
/* how long to wait before a rescrape the first time we get an error */
FIRST_SCRAPE_RETRY_INTERVAL_SEC = 30,
/* how long to wait before a reannounce the first time we get an error */
FIRST_ANNOUNCE_RETRY_INTERVAL_SEC = 30,
/* the value of the 'numwant' argument passed in tracker requests. */
NUMWANT = 80,
/* the length of the 'key' argument passed in tracker requests */
KEYLEN = 10
};
/**
***
**/
struct tr_tracker
{
unsigned int isRunning : 1;
uint8_t randOffset;
/* 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[KEYLEN + 1];
/* these are set from the latest scrape or tracker response */
int announceIntervalSec;
int announceMinIntervalSec;
int scrapeIntervalSec;
int retryScrapeIntervalSec;
int retryAnnounceIntervalSec;
/* index into the torrent's tr_info.trackers array */
int trackerIndex;
tr_session * session;
tr_publisher_t * publisher;
/* torrent hash string */
uint8_t hash[SHA_DIGEST_LENGTH];
char escaped[SHA_DIGEST_LENGTH * 3 + 1];
char * name;
/* corresponds to the peer_id sent as a tracker request parameter.
one tracker admin 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. */
uint8_t * peer_id;
/* these are set from the latest tracker response... -1 is 'unknown' */
int timesDownloaded;
int seederCount;
int leecherCount;
char * trackerID;
time_t manualAnnounceAllowedAt;
time_t reannounceAt;
/* 0==never, 1==in progress, other values==when to scrape */
time_t scrapeAt;
time_t lastScrapeTime;
long lastScrapeResponse;
time_t lastAnnounceTime;
long lastAnnounceResponse;
};
#define dbgmsg( name, ... ) \
do { \
if( tr_deepLoggingIsActive( ) ) \
tr_deepLog( __FILE__, __LINE__, name, __VA_ARGS__ ); \
} while( 0 )
/***
****
***/
static const tr_tracker_info *
getCurrentAddressFromTorrent( tr_tracker * t,
const tr_torrent * tor )
{
/* user might have removed trackers,
* so check to make sure our current index is in-bounds */
if( t->trackerIndex >= tor->info.trackerCount )
t->trackerIndex = 0;
assert( t->trackerIndex >= 0 );
assert( t->trackerIndex < tor->info.trackerCount );
return tor->info.trackers + t->trackerIndex;
}
static const tr_tracker_info *
getCurrentAddress( tr_tracker * t )
{
const tr_torrent * torrent;
if( ( torrent = tr_torrentFindFromHash( t->session, t->hash ) ) )
return getCurrentAddressFromTorrent( t, torrent );
return NULL;
}
static int
trackerSupportsScrape( tr_tracker * t,
const tr_torrent * tor )
{
const tr_tracker_info * info = getCurrentAddressFromTorrent( t, tor );
return info && info->scrape;
}
/***
****
***/
static tr_tracker *
findTracker( tr_session * session,
const uint8_t * hash )
{
tr_torrent * torrent = tr_torrentFindFromHash( session, hash );
return torrent ? torrent->tracker : NULL;
}
/***
**** PUBLISH
***/
static const tr_tracker_event emptyEvent = { 0, NULL, NULL, NULL, 0, 0 };
static void
publishMessage( tr_tracker * t,
const char * msg,
int type )
{
if( t )
{
tr_tracker_event event = emptyEvent;
event.hash = t->hash;
event.messageType = type;
event.text = msg;
tr_publisherPublish( t->publisher, t, &event );
}
}
static void
publishErrorClear( tr_tracker * t )
{
publishMessage( t, NULL, TR_TRACKER_ERROR_CLEAR );
}
static void
publishErrorMessageAndStop( tr_tracker * t,
const char * msg )
{
t->isRunning = 0;
publishMessage( t, msg, TR_TRACKER_ERROR );
}
static void
publishWarning( tr_tracker * t,
const char * msg )
{
publishMessage( t, msg, TR_TRACKER_WARNING );
}
static void
publishNewPeers( tr_tracker * t,
int allAreSeeds,
void * compact,
int compactLen )
{
tr_tracker_event event = emptyEvent;
event.hash = t->hash;
event.messageType = TR_TRACKER_PEERS;
event.allAreSeeds = allAreSeeds;
event.compact = compact;
event.compactLen = compactLen;
if( compactLen )
tr_publisherPublish( t->publisher, t, &event );
}
/***
****
***/
static void onReqDone( tr_session * session );
static int
updateAddresses( tr_tracker * t,
int success )
{
int retry;
tr_torrent * torrent = tr_torrentFindFromHash( t->session, t->hash );
if( success )
{
/* multitracker spec: "if a connection with a tracker is
successful, it will be moved to the front of the tier." */
t->trackerIndex = tr_torrentPromoteTracker( torrent,
t->trackerIndex );
retry = FALSE; /* we succeeded; no need to retry */
}
else if( ++t->trackerIndex >= torrent->info.trackerCount )
{
t->trackerIndex = 0;
retry = FALSE; /* we've tried them all */
}
else
{
const tr_tracker_info * n = getCurrentAddressFromTorrent( t,
torrent );
tr_ninf( t->name, _( "Trying tracker \"%s\"" ), n->announce );
retry = TRUE;
}
return retry;
}
/* Convert to compact form */
static uint8_t *
parseOldPeers( tr_benc * bePeers,
size_t * byteCount )
{
int i;
uint8_t * compact, *walk;
const int peerCount = bePeers->val.l.count;
assert( bePeers->type == TYPE_LIST );
compact = tr_new( uint8_t, peerCount * 6 );
for( i = 0, walk = compact; i < peerCount; ++i )
{
const char * s;
int64_t itmp;
struct in_addr addr;
tr_port_t port;
tr_benc * peer = &bePeers->val.l.vals[i];
if( !tr_bencDictFindStr( peer, "ip",
&s ) || tr_netResolve( s, &addr ) )
continue;
memcpy( walk, &addr, 4 );
walk += 4;
if( !tr_bencDictFindInt( peer, "port",
&itmp ) || itmp < 0 || itmp > 0xffff )
continue;
port = htons( itmp );
memcpy( walk, &port, 2 );
walk += 2;
}
*byteCount = peerCount * 6;
return compact;
}
static void
onStoppedResponse( tr_session * session,
long responseCode UNUSED,
const void * response UNUSED,
size_t responseLen UNUSED,
void * torrent_hash )
{
tr_tracker * t = findTracker( session, torrent_hash );
if( t )
{
const time_t now = time( NULL );
t->reannounceAt = TR_TRACKER_STOPPED;
t->manualAnnounceAllowedAt = TR_TRACKER_STOPPED;
if( t->scrapeAt <= now )
t->scrapeAt = now + t->scrapeIntervalSec + t->randOffset;
}
dbgmsg( NULL, "got a response to some `stop' message" );
onReqDone( session );
tr_free( torrent_hash );
}
static void
onTrackerResponse( tr_session * session,
long responseCode,
const void * response,
size_t responseLen,
void * torrent_hash )
{
int retry;
int success = FALSE;
int scrapeFields = 0;
tr_tracker * t;
onReqDone( session );
t = findTracker( session, torrent_hash );
tr_free( torrent_hash );
if( !t ) /* tracker's been closed */
return;
dbgmsg( t->name, "tracker response: %ld", responseCode );
tr_ndbg( t->name, "tracker response: %ld", responseCode );
t->lastAnnounceResponse = responseCode;
if( responseCode == HTTP_OK )
{
tr_benc benc;
const int bencLoaded = !tr_bencLoad( response, responseLen,
&benc, NULL );
publishErrorClear( t );
if( bencLoaded && tr_bencIsDict( &benc ) )
{
tr_benc * tmp;
int64_t i;
int incomplete = -1;
const char * str;
success = TRUE;
t->retryAnnounceIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
if( ( tr_bencDictFindStr( &benc, "failure reason", &str ) ) )
{
publishMessage( t, str, TR_TRACKER_ERROR );
success = FALSE;
}
if( ( tr_bencDictFindStr( &benc, "warning message", &str ) ) )
publishWarning( t, str );
if( ( tr_bencDictFindInt( &benc, "interval", &i ) ) )
{
dbgmsg( t->name, "setting interval to %d", (int)i );
t->announceIntervalSec = i;
}
if( ( tr_bencDictFindInt( &benc, "min interval", &i ) ) )
{
dbgmsg( t->name, "setting min interval to %d", (int)i );
t->announceMinIntervalSec = i;
}
if( ( tr_bencDictFindStr( &benc, "tracker id", &str ) ) )
t->trackerID = tr_strdup( str );
if( ( tr_bencDictFindInt( &benc, "complete", &i ) ) )
{
++scrapeFields;
t->seederCount = i;
}
if( ( tr_bencDictFindInt( &benc, "incomplete", &i ) ) )
{
++scrapeFields;
t->leecherCount = incomplete = i;
}
if( ( tr_bencDictFindInt( &benc, "downloaded", &i ) ) )
{
++scrapeFields;
t->timesDownloaded = i;
}
if( ( tmp = tr_bencDictFind( &benc, "peers" ) ) )
{
const int allAreSeeds = incomplete == 0;
if( tmp->type == TYPE_STR ) /* "compact" extension */
{
publishNewPeers( t, allAreSeeds, tmp->val.s.s,
tmp->val.s.i );
}
else if( tmp->type == TYPE_LIST ) /* original protocol */
{
size_t byteCount = 0;
uint8_t * compact = parseOldPeers( tmp, &byteCount );
publishNewPeers( t, allAreSeeds, compact, byteCount );
tr_free( compact );
}
}
}
if( bencLoaded )
tr_bencFree( &benc );
}
else
{
/* %1$ld - http status code, such as 404
* %2$s - human-readable explanation of the http status code */
char * buf = tr_strdup_printf( _( "Tracker request failed. Got HTTP Status Code %1$ld (%2$s)" ),
responseCode,
tr_webGetResponseStr( responseCode ) );
publishWarning( t, buf );
tr_free( buf );
}
retry = updateAddresses( t, success );
/**
***
**/
if( retry )
responseCode = 300;
if( 200 <= responseCode && responseCode <= 299 )
{
const int interval = t->announceIntervalSec + t->randOffset;
const time_t now = time ( NULL );
dbgmsg( t->name, "request succeeded. reannouncing in %d seconds",
interval );
/* if the announce response was a superset of the scrape response,
treat this as both a successful announce AND scrape. */
if( scrapeFields >= 3 ) {
t->lastScrapeResponse = responseCode;
t->lastScrapeTime = now;
t->scrapeAt = now + t->scrapeIntervalSec + t->randOffset;
}
/* most trackers don't provide all the scrape responses, but do
provide most of them, so don't scrape too soon anyway */
if( ( scrapeFields == 2 ) && ( t->scrapeAt <= ( now + 120 ) ) ) {
t->scrapeAt = now + t->scrapeIntervalSec + t->randOffset;
}
t->reannounceAt = now + interval;
t->manualAnnounceAllowedAt = now + t->announceMinIntervalSec;
/* #319: save the .resume file after an announce so that, in case
* of a crash, our stats still match up with the tracker's stats */
tr_torrentSaveResume( tr_torrentFindFromHash( t->session, t->hash ) );
}
else if( 300 <= responseCode && responseCode <= 399 )
{
/* it's a redirect... updateAddresses() has already
* parsed the redirect, all that's left is to retry */
const int interval = 5;
dbgmsg( t->name, "got a redirect. retrying in %d seconds", interval );
t->reannounceAt = time( NULL ) + interval;
t->manualAnnounceAllowedAt = time( NULL ) +
t->announceMinIntervalSec;
}
else if( 400 <= responseCode && responseCode <= 499 )
{
/* The request could not be understood by the server due to
* malformed syntax. The client SHOULD NOT repeat the
* request without modifications. */
publishErrorMessageAndStop( t, _( "Tracker returned a 4xx message" ) );
t->manualAnnounceAllowedAt = ~(time_t)0;
t->reannounceAt = TR_TRACKER_STOPPED;
}
else if( 500 <= responseCode && responseCode <= 599 )
{
/* Response status codes beginning with the digit "5" indicate
* cases in which the server is aware that it has erred or is
* incapable of performing the request. So we pause a bit and
* try again. */
t->manualAnnounceAllowedAt = ~(time_t)0;
t->reannounceAt = time( NULL ) + t->retryAnnounceIntervalSec;
t->retryAnnounceIntervalSec *= 2;
}
else
{
/* WTF did we get?? */
dbgmsg(
t->name,
"Invalid response from tracker... retrying in two minutes." );
t->manualAnnounceAllowedAt = ~(time_t)0;
t->reannounceAt = time( NULL ) + t->randOffset + 120;
}
}
static void
onScrapeResponse( tr_session * session,
long responseCode,
const void * response,
size_t responseLen,
void * torrent_hash )
{
int success = FALSE;
int retry;
tr_tracker * t;
onReqDone( session );
t = findTracker( session, torrent_hash );
tr_free( torrent_hash );
if( !t ) /* tracker's been closed... */
return;
dbgmsg( t->name, "scrape response: %ld\n", responseCode );
tr_ndbg( t->name, "scrape response: %ld", responseCode );
t->lastScrapeResponse = responseCode;
if( responseCode == HTTP_OK )
{
tr_benc benc, *files;
const int bencLoaded = !tr_bencLoad( response, responseLen,
&benc, NULL );
if( bencLoaded && tr_bencDictFindDict( &benc, "files", &files ) )
{
size_t i;
for( i = 0; i < files->val.l.count; i += 2 )
{
int64_t itmp;
const uint8_t* hash =
(const uint8_t*) files->val.l.vals[i].val.s.s;
tr_benc * flags;
tr_benc * tordict = &files->val.l.vals[i + 1];
if( memcmp( t->hash, hash, SHA_DIGEST_LENGTH ) )
continue;
publishErrorClear( t );
if( ( tr_bencDictFindInt( tordict, "complete", &itmp ) ) )
t->seederCount = itmp;
if( ( tr_bencDictFindInt( tordict, "incomplete", &itmp ) ) )
t->leecherCount = itmp;
if( ( tr_bencDictFindInt( tordict, "downloaded", &itmp ) ) )
t->timesDownloaded = itmp;
if( tr_bencDictFindDict( tordict, "flags", &flags ) )
if( ( tr_bencDictFindInt( flags, "min_request_interval",
&itmp ) ) )
t->scrapeIntervalSec = i;
/* as per ticket #1045, safeguard against trackers returning
* a very low min_request_interval... */
if( t->scrapeIntervalSec < DEFAULT_SCRAPE_INTERVAL_SEC )
t->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC;
tr_ndbg( t->name,
"Scrape successful. Rescraping in %d seconds.",
t->scrapeIntervalSec );
success = TRUE;
t->retryScrapeIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
}
}
if( bencLoaded )
tr_bencFree( &benc );
}
retry = updateAddresses( t, success );
/**
***
**/
if( retry )
responseCode = 300;
if( 200 <= responseCode && responseCode <= 299 )
{
const int interval = t->scrapeIntervalSec + t->randOffset;
dbgmsg( t->name, "request succeeded. rescraping in %d seconds",
interval );
tr_ndbg( t->name, "request succeeded. rescraping in %d seconds",
interval );
t->scrapeAt = time( NULL ) + interval;
}
else if( 300 <= responseCode && responseCode <= 399 )
{
const int interval = 5;
dbgmsg( t->name, "got a redirect. retrying in %d seconds", interval );
t->scrapeAt = time( NULL ) + interval;
}
else
{
const int interval = t->retryScrapeIntervalSec + t->randOffset;
dbgmsg(
t->name,
"Tracker responded to scrape with %ld. Retrying in %d seconds.",
responseCode, interval );
t->retryScrapeIntervalSec *= 2;
t->scrapeAt = time( NULL ) + interval;
}
}
/***
****
***/
enum
{
TR_REQ_STARTED,
TR_REQ_COMPLETED,
TR_REQ_STOPPED,
TR_REQ_REANNOUNCE,
TR_REQ_SCRAPE,
TR_REQ_COUNT
};
struct tr_tracker_request
{
uint8_t torrent_hash[SHA_DIGEST_LENGTH];
int reqtype; /* TR_REQ_* */
char * url;
tr_web_done_func * done_func;
tr_session * session;
};
static void
freeRequest( struct tr_tracker_request * req )
{
tr_free( req->url );
tr_free( req );
}
static void
buildTrackerRequestURI( tr_tracker * t,
const tr_torrent * torrent,
const char * eventName,
struct evbuffer * buf )
{
const int isStopping = !strcmp( eventName, "stopped" );
const int numwant = isStopping ? 0 : NUMWANT;
const char * ann = getCurrentAddressFromTorrent( t, torrent )->announce;
evbuffer_add_printf( buf, "%cinfo_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",
strchr( ann, '?' ) ? '&' : '?',
t->escaped,
t->peer_id,
tr_sessionGetPeerPort( t->session ),
torrent->uploadedCur,
torrent->downloadedCur,
torrent->corruptCur,
tr_cpLeftUntilComplete( torrent->completion ),
numwant,
t->key_param,
( ( eventName && *eventName ) ? "&event=" : "" ),
( ( eventName && *eventName ) ? eventName : "" ),
( ( t->trackerID
&& *t->trackerID ) ? "&trackerid=" : "" ),
( ( t->trackerID
&& *t->trackerID ) ? t->trackerID : "" ) );
}
static struct tr_tracker_request*
createRequest( tr_session * session,
tr_tracker * tracker,
int reqtype )
{
static const char* strings[] =
{ "started", "completed", "stopped", "", "err" };
const tr_torrent * torrent = tr_torrentFindFromHash(
session, tracker->hash );
const tr_tracker_info * address = getCurrentAddressFromTorrent(
tracker, torrent );
const int isStopping = reqtype == TR_REQ_STOPPED;
struct tr_tracker_request * req;
struct evbuffer * url;
url = evbuffer_new( );
evbuffer_add_printf( url, "%s", address->announce );
buildTrackerRequestURI( tracker, torrent, strings[reqtype], url );
req = tr_new0( struct tr_tracker_request, 1 );
req->session = session;
req->reqtype = reqtype;
req->done_func = isStopping ? onStoppedResponse : onTrackerResponse;
req->url = tr_strdup( EVBUFFER_DATA( url ) );
memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH );
evbuffer_free( url );
return req;
}
static struct tr_tracker_request*
createScrape( tr_session * session,
tr_tracker * tracker )
{
const tr_tracker_info * a = getCurrentAddress( tracker );
struct tr_tracker_request * req;
struct evbuffer * url = evbuffer_new( );
evbuffer_add_printf( url, "%s%cinfo_hash=%s",
a->scrape, strchr( a->scrape, '?' ) ? '&' : '?',
tracker->escaped );
req = tr_new0( struct tr_tracker_request, 1 );
req->session = session;
req->reqtype = TR_REQ_SCRAPE;
req->url = tr_strdup( EVBUFFER_DATA( url ) );
req->done_func = onScrapeResponse;
memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH );
evbuffer_free( url );
return req;
}
struct tr_tracker_handle
{
int runningCount;
tr_timer * pulseTimer;
};
static int trackerPulse( void * vsession );
static void
ensureGlobalsExist( tr_session * session )
{
if( session->tracker == NULL )
{
session->tracker = tr_new0( struct tr_tracker_handle, 1 );
session->tracker->pulseTimer =
tr_timerNew( session, trackerPulse, session,
PULSE_INTERVAL_MSEC );
dbgmsg( NULL, "creating tracker timer" );
}
}
void
tr_trackerSessionClose( tr_session * session )
{
if( session && session->tracker )
{
dbgmsg( NULL, "freeing tracker timer" );
tr_timerFree( &session->tracker->pulseTimer );
tr_free( session->tracker );
session->tracker = NULL;
}
}
/***
****
***/
static void
invokeRequest( void * vreq )
{
struct tr_tracker_request * req = vreq;
tr_tracker * t = findTracker( req->session,
req->torrent_hash );
if( t )
{
const time_t now = time( NULL );
if( req->reqtype == TR_REQ_SCRAPE )
{
t->lastScrapeTime = now;
t->scrapeAt = TR_TRACKER_BUSY;
}
else
{
t->lastAnnounceTime = now;
t->reannounceAt = TR_TRACKER_BUSY;
t->manualAnnounceAllowedAt = TR_TRACKER_BUSY;
}
}
++req->session->tracker->runningCount;
tr_webRun( req->session, req->url, NULL, req->done_func,
tr_memdup( req->torrent_hash, SHA_DIGEST_LENGTH ) );
freeRequest( req );
}
static void
enqueueScrape( tr_session * session,
tr_tracker * tracker )
{
struct tr_tracker_request * req;
req = createScrape( session, tracker );
tr_runInEventThread( session, invokeRequest, req );
}
static void
enqueueRequest( tr_session * session,
tr_tracker * tracker,
int reqtype )
{
struct tr_tracker_request * req;
req = createRequest( session, tracker, reqtype );
tr_runInEventThread( session, invokeRequest, req );
}
static int
trackerPulse( void * vsession )
{
tr_session * session = vsession;
struct tr_tracker_handle * th = session->tracker;
tr_torrent * tor;
const time_t now = time( NULL );
if( !session->tracker )
return FALSE;
if( th->runningCount )
dbgmsg( NULL, "tracker pulse... %d running", th->runningCount );
/* upkeep: queue periodic rescrape / reannounce */
for( tor = session->torrentList; tor; tor = tor->next )
{
tr_tracker * t = tor->tracker;
if( ( t->scrapeAt > 1 )
&& ( t->scrapeAt <= now )
&& ( trackerSupportsScrape( t, tor ) ) )
{
t->scrapeAt = TR_TRACKER_BUSY;
enqueueScrape( session, t );
}
if( ( t->reannounceAt > 1 )
&& ( t->reannounceAt <= now )
&& ( t->isRunning ) )
{
t->reannounceAt = TR_TRACKER_BUSY;
t->manualAnnounceAllowedAt = TR_TRACKER_BUSY;
enqueueRequest( session, t, TR_REQ_REANNOUNCE );
}
}
if( th->runningCount )
dbgmsg( NULL, "tracker pulse after upkeep... %d running",
th->runningCount );
/* free the tracker manager if no torrents are left */
if( ( session->tracker )
&& ( session->tracker->runningCount < 1 )
&& ( session->torrentList == NULL ) )
{
tr_trackerSessionClose( session );
}
/* if there are still running torrents (as indicated by
* the existence of the tracker manager) then keep the
* trackerPulse() timer alive */
return session->tracker != NULL;
}
static void
onReqDone( tr_session * session )
{
if( session->tracker )
{
--session->tracker->runningCount;
dbgmsg( NULL, "decrementing runningCount to %d",
session->tracker->runningCount );
trackerPulse( session );
}
}
/***
**** LIFE CYCLE
***/
static void
generateKeyParam( char * msg,
int len )
{
int i;
const char * pool = "abcdefghijklmnopqrstuvwxyz0123456789";
const int poolSize = strlen( pool );
for( i = 0; i < len; ++i )
*msg++ = pool[tr_cryptoRandInt( poolSize )];
*msg = '\0';
}
static int
is_rfc2396_alnum( char ch )
{
return ( '0' <= ch && ch <= '9' )
|| ( 'A' <= ch && ch <= 'Z' )
|| ( 'a' <= ch && ch <= 'z' );
}
static void
escape( char * out,
const uint8_t * in,
int in_len ) /* rfc2396 */
{
const uint8_t *end = in + in_len;
while( in != end )
if( is_rfc2396_alnum( *in ) )
*out++ = (char) *in++;
else
out += tr_snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
*out = '\0';
}
tr_tracker *
tr_trackerNew( const tr_torrent * torrent )
{
const tr_info * info = &torrent->info;
tr_tracker * t;
ensureGlobalsExist( torrent->session );
t = tr_new0( tr_tracker, 1 );
t->publisher = tr_publisherNew( );
t->session = torrent->session;
t->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC;
t->retryScrapeIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
t->retryAnnounceIntervalSec = FIRST_ANNOUNCE_RETRY_INTERVAL_SEC;
t->announceIntervalSec = DEFAULT_ANNOUNCE_INTERVAL_SEC;
t->announceMinIntervalSec = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
t->timesDownloaded = -1;
t->seederCount = -1;
t->leecherCount = -1;
t->lastAnnounceResponse = -1;
t->lastScrapeResponse = -1;
t->manualAnnounceAllowedAt = ~(time_t)0;
t->name = tr_strdup( info->name );
t->randOffset = tr_cryptoRandInt( 30 );
memcpy( t->hash, info->hash, SHA_DIGEST_LENGTH );
escape( t->escaped, info->hash, SHA_DIGEST_LENGTH );
generateKeyParam( t->key_param, KEYLEN );
t->trackerIndex = 0;
if( trackerSupportsScrape( t, torrent ) )
t->scrapeAt = time( NULL ) + t->randOffset;
return t;
}
static void
onTrackerFreeNow( void * vt )
{
tr_tracker * t = vt;
tr_publisherFree( &t->publisher );
tr_free( t->name );
tr_free( t->trackerID );
tr_free( t->peer_id );
tr_free( t );
}
/***
**** PUBLIC
***/
void
tr_trackerFree( tr_tracker * t )
{
if( t )
tr_runInEventThread( t->session, onTrackerFreeNow, t );
}
tr_publisher_tag
tr_trackerSubscribe( tr_tracker * t,
tr_delivery_func func,
void * user_data )
{
return tr_publisherSubscribe( t->publisher, func, user_data );
}
void
tr_trackerUnsubscribe( tr_tracker * t,
tr_publisher_tag tag )
{
if( t )
tr_publisherUnsubscribe( t->publisher, tag );
}
const tr_tracker_info *
tr_trackerGetAddress( tr_tracker * t )
{
return getCurrentAddress( t );
}
time_t
tr_trackerGetManualAnnounceTime( const struct tr_tracker * t )
{
return t->isRunning ? t->manualAnnounceAllowedAt : 0;
}
int
tr_trackerCanManualAnnounce( const tr_tracker * t )
{
const time_t allow = tr_trackerGetManualAnnounceTime( t );
return allow && ( allow <= time( NULL ) );
}
void
tr_trackerGetCounts( const tr_tracker * t,
int * setme_completedCount,
int * setme_leecherCount,
int * setme_seederCount )
{
if( setme_completedCount )
*setme_completedCount = t->timesDownloaded;
if( setme_leecherCount )
*setme_leecherCount = t->leecherCount;
if( setme_seederCount )
*setme_seederCount = t->seederCount;
}
void
tr_trackerStart( tr_tracker * t )
{
if( t && !t->isRunning )
{
tr_free( t->peer_id );
t->peer_id = tr_peerIdNew( );
t->isRunning = 1;
enqueueRequest( t->session, t, TR_REQ_STARTED );
}
}
void
tr_trackerReannounce( tr_tracker * t )
{
enqueueRequest( t->session, t, TR_REQ_REANNOUNCE );
}
void
tr_trackerCompleted( tr_tracker * t )
{
enqueueRequest( t->session, t, TR_REQ_COMPLETED );
}
void
tr_trackerStop( tr_tracker * t )
{
if( t && t->isRunning )
{
t->isRunning = 0;
t->reannounceAt = TR_TRACKER_STOPPED;
t->manualAnnounceAllowedAt = TR_TRACKER_STOPPED;
enqueueRequest( t->session, t, TR_REQ_STOPPED );
}
}
void
tr_trackerChangeMyPort( tr_tracker * t )
{
if( t->isRunning )
tr_trackerReannounce( t );
}
void
tr_trackerStat( const tr_tracker * t,
struct tr_stat * setme )
{
assert( t );
assert( setme );
setme->lastScrapeTime = t->lastScrapeTime;
setme->nextScrapeTime = t->scrapeAt;
setme->lastAnnounceTime = t->lastAnnounceTime;
setme->nextAnnounceTime = t->reannounceAt;
setme->manualAnnounceTime = t->manualAnnounceAllowedAt;
if( t->lastScrapeResponse == -1 ) /* never been scraped */
*setme->scrapeResponse = '\0';
else
tr_snprintf( setme->scrapeResponse,
sizeof( setme->scrapeResponse ),
"%s (%ld)",
tr_webGetResponseStr( t->lastScrapeResponse ),
t->lastScrapeResponse );
if( t->lastAnnounceResponse == -1 ) /* never been announced */
*setme->announceResponse = '\0';
else
tr_snprintf( setme->announceResponse,
sizeof( setme->announceResponse ),
"%s (%ld)",
tr_webGetResponseStr( t->lastAnnounceResponse ),
t->lastAnnounceResponse );
}