1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2025-02-07 15:04:13 +00:00
transmission/libtransmission/tracker.c
Josh Elsasser 9809dcb05d Send stopped and started events to trackers when the listening port is changed,
but without disrupting existing peer connections.
In the GTK prefs dialog, change the port immediately instead of on next invocation.
2006-01-25 17:20:21 +00:00

648 lines
15 KiB
C

/******************************************************************************
* Copyright (c) 2005 Eric Petit
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#include "transmission.h"
struct tr_tracker_s
{
tr_torrent_t * tor;
char * id;
char started;
char completed;
char stopped;
int interval;
int seeders;
int leechers;
int hasManyPeers;
uint64_t dateTry;
uint64_t dateOk;
#define TC_STATUS_IDLE 1
#define TC_STATUS_CONNECT 2
#define TC_STATUS_RECV 4
char status;
int socket;
uint8_t * buf;
int size;
int pos;
int bindPort;
int newPort;
};
static void sendQuery ( tr_tracker_t * tc );
static void recvAnswer ( tr_tracker_t * tc );
tr_tracker_t * tr_trackerInit( tr_handle_t * h, tr_torrent_t * tor )
{
tr_tracker_t * tc;
tc = calloc( 1, sizeof( tr_tracker_t ) );
tc->tor = tor;
tc->id = h->id;
tc->started = 1;
tc->seeders = -1;
tc->leechers = -1;
tc->status = TC_STATUS_IDLE;
tc->size = 1024;
tc->buf = malloc( tc->size );
tc->bindPort = h->bindPort;
tc->newPort = -1;
return tc;
}
static int shouldConnect( tr_tracker_t * tc )
{
uint64_t now = tr_date();
/* In any case, always wait 5 seconds between two requests */
if( now < tc->dateTry + 5000 )
{
return 0;
}
/* Do we need to send an event? */
if( tc->started || tc->completed || tc->stopped || 0 < tc->newPort )
{
return 1;
}
/* Should we try and get more peers? */
if( now > tc->dateOk + 1000 * tc->interval )
{
return 1;
}
/* If there is quite a lot of people on this torrent, stress
the tracker a bit until we get a decent number of peers */
if( tc->hasManyPeers )
{
if( tc->tor->peerCount < 5 && now > tc->dateOk + 10000 )
{
return 1;
}
if( tc->tor->peerCount < 10 && now > tc->dateOk + 20000 )
{
return 1;
}
if( tc->tor->peerCount < 15 && now > tc->dateOk + 30000 )
{
return 1;
}
}
return 0;
}
void tr_trackerChangePort( tr_tracker_t * tc, int port )
{
tc->newPort = port;
}
int tr_trackerPulse( tr_tracker_t * tc )
{
tr_torrent_t * tor = tc->tor;
tr_info_t * inf = &tor->info;
uint64_t now = tr_date();
if( ( tc->status & TC_STATUS_IDLE ) && shouldConnect( tc ) )
{
struct in_addr addr;
if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) )
{
return 0;
}
if( tr_netResolve( inf->trackerAddress, &addr ) )
{
tr_fdSocketClosed( tor->fdlimit, 1 );
return 0;
}
tc->socket = tr_netOpen( addr, htons( inf->trackerPort ) );
if( tc->socket < 0 )
{
tr_fdSocketClosed( tor->fdlimit, 1 );
return 0;
}
tr_inf( "Tracker: connecting to %s:%d (%s)",
inf->trackerAddress, inf->trackerPort,
tc->started ? "sending 'started'" :
( tc->completed ? "sending 'completed'" :
( tc->stopped ? "sending 'stopped'" :
( 0 < tc->newPort ? "sending 'stopped' to change port" :
"getting peers" ) ) ) );
tc->status = TC_STATUS_CONNECT;
tc->dateTry = tr_date();
}
if( tc->status & TC_STATUS_CONNECT )
{
/* We are connecting to the tracker. Try to send the query */
sendQuery( tc );
}
if( tc->status & TC_STATUS_RECV )
{
/* Try to get something */
recvAnswer( tc );
}
if( tc->status > TC_STATUS_IDLE && now > tc->dateTry + 60000 )
{
/* Give up if the request wasn't successful within 60 seconds */
tr_inf( "Tracker: timeout reached (60 s)" );
tr_netClose( tc->socket );
tr_fdSocketClosed( tor->fdlimit, 1 );
tc->status = TC_STATUS_IDLE;
tc->dateTry = tr_date();
}
return 0;
}
void tr_trackerCompleted( tr_tracker_t * tc )
{
tc->started = 0;
tc->completed = 1;
tc->stopped = 0;
}
void tr_trackerStopped( tr_tracker_t * tc )
{
tr_torrent_t * tor = tc->tor;
if( tc->status > TC_STATUS_CONNECT )
{
/* If we are already sendy a query at the moment, we need to
reconnect */
tr_netClose( tc->socket );
tr_fdSocketClosed( tor->fdlimit, 1 );
tc->status = TC_STATUS_IDLE;
}
tc->started = 0;
tc->completed = 0;
tc->stopped = 1;
/* Even if we have connected recently, reconnect right now */
if( tc->status & TC_STATUS_IDLE )
{
tc->dateTry = 0;
}
}
void tr_trackerClose( tr_tracker_t * tc )
{
tr_torrent_t * tor = tc->tor;
if( tc->status > TC_STATUS_IDLE )
{
tr_netClose( tc->socket );
tr_fdSocketClosed( tor->fdlimit, 1 );
}
free( tc->buf );
free( tc );
}
static void sendQuery( tr_tracker_t * tc )
{
tr_torrent_t * tor = tc->tor;
tr_info_t * inf = &tor->info;
char * event;
uint64_t left;
int ret;
if( tc->started && 0 < tc->newPort )
{
tc->bindPort = tc->newPort;
tc->newPort = -1;
}
if( tc->started )
event = "&event=started";
else if( tc->completed )
event = "&event=completed";
else if( tc->stopped || 0 < tc->newPort )
event = "&event=stopped";
else
event = "";
left = tr_cpLeftBytes( tor->completion );
ret = snprintf( (char *) tc->buf, tc->size,
"GET %s?"
"info_hash=%s&"
"peer_id=%s&"
"port=%d&"
"uploaded=%lld&"
"downloaded=%lld&"
"left=%lld&"
"compact=1&"
"numwant=50&"
"key=%s"
"%s "
"HTTP/1.1\r\n"
"Host: %s\r\n"
"User-Agent: Transmission/%d.%d\r\n"
"Connection: close\r\n\r\n",
inf->trackerAnnounce, tor->hashString, tc->id,
tc->bindPort, tor->uploaded[9], tor->downloaded[9],
left, tor->key, event, inf->trackerAddress,
VERSION_MAJOR, VERSION_MINOR );
ret = tr_netSend( tc->socket, tc->buf, ret );
if( ret & TR_NET_CLOSE )
{
tr_inf( "Tracker: connection failed" );
tr_netClose( tc->socket );
tr_fdSocketClosed( tor->fdlimit, 1 );
tc->status = TC_STATUS_IDLE;
tc->dateTry = tr_date();
}
else if( !( ret & TR_NET_BLOCK ) )
{
// printf( "Tracker: sent %s", tc->buf );
tc->status = TC_STATUS_RECV;
tc->pos = 0;
}
}
static void recvAnswer( tr_tracker_t * tc )
{
tr_torrent_t * tor = tc->tor;
int ret;
int i;
benc_val_t beAll;
benc_val_t * bePeers, * beFoo;
if( tc->pos == tc->size )
{
tc->size *= 2;
tc->buf = realloc( tc->buf, tc->size );
}
ret = tr_netRecv( tc->socket, &tc->buf[tc->pos],
tc->size - tc->pos );
if( ret & TR_NET_BLOCK )
{
return;
}
if( !( ret & TR_NET_CLOSE ) )
{
// printf( "got %d bytes\n", ret );
tc->pos += ret;
return;
}
tr_netClose( tc->socket );
tr_fdSocketClosed( tor->fdlimit, 1 );
// printf( "connection closed, got total %d bytes\n", tc->pos );
tc->status = TC_STATUS_IDLE;
tc->dateTry = tr_date();
if( tc->pos < 1 )
{
/* We got nothing */
return;
}
/* Find the beginning of the dictionary */
for( i = 0; i < tc->pos - 18; i++ )
{
/* Hem */
if( !memcmp( &tc->buf[i], "d8:interval", 11 ) ||
!memcmp( &tc->buf[i], "d8:complete", 11 ) ||
!memcmp( &tc->buf[i], "d14:failure reason", 18 ) )
{
break;
}
}
if( i >= tc->pos - 18 )
{
tr_err( "Tracker: no dictionary in answer" );
// printf( "%s\n", tc->buf );
return;
}
if( tr_bencLoad( &tc->buf[i], &beAll, NULL ) )
{
tr_err( "Tracker: error parsing bencoded data" );
return;
}
// tr_bencPrint( &beAll );
if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) )
{
tr_err( "Tracker: %s", bePeers->val.s.s );
tor->status |= TR_TRACKER_ERROR;
snprintf( tor->error, sizeof( tor->error ),
"%s", bePeers->val.s.s );
goto cleanup;
}
tor->status &= ~TR_TRACKER_ERROR;
if( !tc->interval )
{
/* Get the tracker interval, ignore it if it is not between
10 sec and 5 mins */
if( !( beFoo = tr_bencDictFind( &beAll, "interval" ) ) ||
!( beFoo->type & TYPE_INT ) )
{
tr_err( "Tracker: no 'interval' field" );
goto cleanup;
}
tc->interval = beFoo->val.i;
tc->interval = MIN( tc->interval, 300 );
tc->interval = MAX( 10, tc->interval );
tr_inf( "Tracker: interval = %d seconds", tc->interval );
}
if( ( beFoo = tr_bencDictFind( &beAll, "complete" ) ) &&
( beFoo->type & TYPE_INT ) )
{
tc->seeders = beFoo->val.i;
}
if( ( beFoo = tr_bencDictFind( &beAll, "incomplete" ) ) &&
( beFoo->type & TYPE_INT ) )
{
tc->leechers = beFoo->val.i;
}
if( tc->seeders + tc->leechers >= 50 )
{
tc->hasManyPeers = 1;
}
if( !( bePeers = tr_bencDictFind( &beAll, "peers" ) ) )
{
tr_err( "Tracker: no \"peers\" field" );
goto cleanup;
}
if( bePeers->type & TYPE_LIST )
{
char * ip;
int port;
/* Original protocol */
tr_inf( "Tracker: got %d peers", bePeers->val.l.count );
for( i = 0; i < bePeers->val.l.count; i++ )
{
beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "ip" );
if( !beFoo )
continue;
ip = beFoo->val.s.s;
beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "port" );
if( !beFoo )
continue;
port = beFoo->val.i;
tr_peerAddOld( tor, ip, port );
}
if( bePeers->val.l.count >= 50 )
{
tc->hasManyPeers = 1;
}
}
else if( bePeers->type & TYPE_STR )
{
struct in_addr addr;
in_port_t port;
/* "Compact" extension */
if( bePeers->val.s.i % 6 )
{
tr_err( "Tracker: \"peers\" of size %d",
bePeers->val.s.i );
tr_lockUnlock( &tor->lock );
goto cleanup;
}
tr_inf( "Tracker: got %d peers", bePeers->val.s.i / 6 );
for( i = 0; i < bePeers->val.s.i / 6; i++ )
{
memcpy( &addr, &bePeers->val.s.s[6*i], 4 );
memcpy( &port, &bePeers->val.s.s[6*i+4], 2 );
tr_peerAddCompact( tor, addr, port );
}
if( bePeers->val.s.i / 6 >= 50 )
{
tc->hasManyPeers = 1;
}
}
/* Success */
tc->started = 0;
tc->completed = 0;
tc->dateOk = tr_date();
if( tc->stopped )
{
tor->status = TR_STATUS_STOPPED;
tc->stopped = 0;
}
else if( 0 < tc->newPort )
{
tc->started = 1;
}
cleanup:
tr_bencFree( &beAll );
}
int tr_trackerScrape( tr_torrent_t * tor, int * seeders, int * leechers )
{
tr_info_t * inf = &tor->info;
int s, i, ret;
uint8_t buf[1024];
benc_val_t scrape, * val1, * val2;
struct in_addr addr;
uint64_t date;
int pos, len;
if( !tor->scrape[0] )
{
/* scrape not supported */
return 1;
}
if( tr_netResolve( inf->trackerAddress, &addr ) )
{
return 0;
}
s = tr_netOpen( addr, htons( inf->trackerPort ) );
if( s < 0 )
{
return 1;
}
len = snprintf( (char *) buf, sizeof( buf ),
"GET %s?info_hash=%s HTTP/1.1\r\n"
"Host: %s\r\n"
"Connection: close\r\n\r\n",
tor->scrape, tor->hashString,
inf->trackerAddress );
for( date = tr_date();; )
{
ret = tr_netSend( s, buf, len );
if( ret & TR_NET_CLOSE )
{
fprintf( stderr, "Could not connect to tracker\n" );
tr_netClose( s );
return 1;
}
else if( ret & TR_NET_BLOCK )
{
if( tr_date() > date + 10000 )
{
fprintf( stderr, "Could not connect to tracker\n" );
tr_netClose( s );
return 1;
}
}
else
{
break;
}
tr_wait( 10 );
}
pos = 0;
for( date = tr_date();; )
{
ret = tr_netRecv( s, &buf[pos], sizeof( buf ) - pos );
if( ret & TR_NET_CLOSE )
{
break;
}
else if( ret & TR_NET_BLOCK )
{
if( tr_date() > date + 10000 )
{
fprintf( stderr, "Could not read from tracker\n" );
tr_netClose( s );
return 1;
}
}
else
{
pos += ret;
}
tr_wait( 10 );
}
if( pos < 1 )
{
fprintf( stderr, "Could not read from tracker\n" );
tr_netClose( s );
return 1;
}
for( i = 0; i < pos - 8; i++ )
{
if( !memcmp( &buf[i], "d5:files", 8 ) )
{
break;
}
}
if( i >= pos - 8 )
{
return 1;
}
if( tr_bencLoad( &buf[i], &scrape, NULL ) )
{
return 1;
}
val1 = tr_bencDictFind( &scrape, "files" );
if( !val1 )
{
return 1;
}
val1 = &val1->val.l.vals[1];
if( !val1 )
{
return 1;
}
val2 = tr_bencDictFind( val1, "complete" );
if( !val2 )
{
return 1;
}
*seeders = val2->val.i;
val2 = tr_bencDictFind( val1, "incomplete" );
if( !val2 )
{
return 1;
}
*leechers = val2->val.i;
tr_bencFree( &scrape );
return 0;
}
int tr_trackerSeeders( tr_torrent_t * tor)
{
if (tor->status != TR_STATUS_PAUSE)
{
return (tor->tracker)->seeders;
}
return 0;
}
int tr_trackerLeechers( tr_torrent_t * tor)
{
if (tor->status != TR_STATUS_PAUSE)
{
return (tor->tracker)->leechers;
}
return 0;
}