source: trunk/libtransmission/tracker.c @ 5086

Last change on this file since 5086 was 5086, checked in by charles, 14 years ago

libT: add tracker information to tr_stat. gtk: add `tracker' tab to inspector

  • Property svn:keywords set to Date Rev Author Id
File size: 35.4 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: tracker.c 5086 2008-02-21 07:29:39Z charles $
11 */
12
13#include <assert.h>
14#include <stdio.h> /* snprintf */
15#include <stdlib.h>
16#include <string.h> /* strcmp, strchr */
17#include <libgen.h> /* basename */
18
19#include <event.h>
20#include <evhttp.h>
21
22#include "transmission.h"
23#include "bencode.h"
24#include "completion.h"
25#include "list.h"
26#include "net.h"
27#include "publish.h"
28#include "shared.h"
29#include "torrent.h"
30#include "tracker.h"
31#include "trcompat.h" /* strlcpy */
32#include "trevent.h"
33#include "utils.h"
34
35enum
36{
37    /* seconds between tracker pulses */
38    PULSE_INTERVAL_MSEC = 1000,
39
40    /* maximum number of concurrent tracker socket connections */
41    MAX_TRACKER_SOCKETS = 16,
42
43    /* maximum number of concurrent tracker socket connections during shutdown.
44     * all the peer connections should be gone by now, so we can hog more
45     * connections to send `stop' messages to the trackers */
46    MAX_TRACKER_SOCKETS_DURING_SHUTDOWN = 64,
47
48    /* unless the tracker says otherwise, rescrape this frequently */
49    DEFAULT_SCRAPE_INTERVAL_SEC = (60 * 15),
50
51    /* unless the tracker says otherwise, this is the announce interval */
52    DEFAULT_ANNOUNCE_INTERVAL_SEC = (60 * 4),
53
54    /* unless the tracker says otherwise, this is the announce min_interval */
55    DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC = (60 * 2),
56
57    /* this is how long we'll leave a request hanging before timeout */
58    TIMEOUT_INTERVAL_SEC = 30,
59
60    /* this is how long we'll leave a 'stop' request hanging before timeout.
61       we wait less time for this so it doesn't slow down shutdowns */
62    STOP_TIMEOUT_INTERVAL_SEC = 5,
63
64    /* the value of the 'numwant' argument passed in tracker requests. */
65    NUMWANT = 200,
66
67    /* the length of the 'key' argument passed in tracker requests */
68    KEYLEN = 10
69};
70
71/**
72***
73**/
74
75struct tr_tracker
76{
77    tr_handle * handle;
78
79    /* these are set from the latest scrape or tracker response */
80    int announceIntervalSec;
81    int announceMinIntervalSec;
82    int scrapeIntervalSec;
83
84    tr_tracker_info * redirect;
85    tr_tracker_info * addresses;
86    int addressIndex;
87    int addressCount;
88    int * tierFronts;
89
90    /* sent as the "key" argument in tracker requests
91       to verify us if our IP address changes.
92       This is immutable for the life of the tracker object. */
93    char key_param[KEYLEN+1];
94
95    tr_publisher_t * publisher;
96
97    /* torrent hash string */
98    uint8_t hash[SHA_DIGEST_LENGTH];
99    char escaped[SHA_DIGEST_LENGTH * 3 + 1];
100    char * name;
101
102    /* corresponds to the peer_id sent as a tracker request parameter.
103       one tracker admin says: "When the same torrent is opened and
104       closed and opened again without quitting Transmission ...
105       change the peerid. It would help sometimes if a stopped event
106       was missed to ensure that we didn't think someone was cheating. */
107    uint8_t * peer_id;
108
109    /* these are set from the latest tracker response... -1 is 'unknown' */
110    int timesDownloaded;
111    int seederCount;
112    int leecherCount;
113    char * trackerID;
114
115    time_t manualAnnounceAllowedAt;
116    time_t reannounceAt;
117    time_t scrapeAt;
118
119    time_t lastScrapeTime;
120    char lastScrapeResponse[512];
121
122    time_t lastAnnounceTime;
123    char lastAnnounceResponse[512];
124
125    int randOffset;
126
127    unsigned int isRunning     : 1;
128};
129
130/**
131***
132**/
133
134static void
135myDebug( const char * file, int line, const tr_tracker * t, const char * fmt, ... )
136{   
137    FILE * fp = tr_getLog( );
138    if( fp != NULL )
139    {
140        va_list args;
141        char timestr[64];
142        struct evbuffer * buf = evbuffer_new( );
143        char * myfile = tr_strdup( file );
144
145        evbuffer_add_printf( buf, "[%s] ", tr_getLogTimeStr( timestr, sizeof(timestr) ) );
146        if( t != NULL )
147            evbuffer_add_printf( buf, "%s ", t->name );
148        va_start( args, fmt );
149        evbuffer_add_vprintf( buf, fmt, args );
150        va_end( args );
151        evbuffer_add_printf( buf, " (%s:%d)\n", basename(myfile), line );
152        fwrite( EVBUFFER_DATA(buf), 1, EVBUFFER_LENGTH(buf), fp );
153
154        tr_free( myfile );
155        evbuffer_free( buf );
156    }
157}
158
159#define dbgmsg(t, fmt...) myDebug(__FILE__, __LINE__, t, ##fmt )
160
161/***
162****
163***/
164
165static tr_tracker_info *
166getCurrentAddress( const tr_tracker * t )
167{
168    assert( t->addresses != NULL );
169    assert( t->addressIndex >= 0 );
170    assert( t->addressIndex < t->addressCount );
171
172    return t->redirect ? t->redirect
173                       : t->addresses + t->addressIndex;
174}
175
176static int
177trackerSupportsScrape( const tr_tracker * t )
178{
179    const tr_tracker_info * info = getCurrentAddress( t );
180
181    return ( info != NULL )
182        && ( info->scrape != NULL )
183        && ( info->scrape[0] != '\0' );
184}
185
186/***
187****
188***/
189
190struct torrent_hash
191{
192    tr_handle * handle;
193    uint8_t hash[SHA_DIGEST_LENGTH];
194};
195
196static struct torrent_hash*
197torrentHashNew( tr_handle * handle, const tr_tracker * t )
198{
199    struct torrent_hash * data = tr_new( struct torrent_hash, 1 );
200    data->handle = handle;
201    memcpy( data->hash, t->hash, SHA_DIGEST_LENGTH );
202    return data;
203}
204
205tr_tracker *
206findTrackerFromHash( struct torrent_hash * data )
207{
208    tr_torrent * torrent = tr_torrentFindFromHash( data->handle, data->hash );
209    return torrent ? torrent->tracker : NULL;
210}
211
212tr_tracker *
213findTracker( tr_handle * handle, const uint8_t * hash )
214{
215    tr_torrent * torrent = tr_torrentFindFromHash( handle, hash );
216    return torrent ? torrent->tracker : NULL;
217}
218
219/***
220****  PUBLISH
221***/
222
223static const tr_tracker_event emptyEvent = { 0, NULL, NULL, NULL, 0 };
224
225static void
226publishMessage( tr_tracker * t, const char * msg, int type )
227{
228    if( t != NULL )
229    {
230        tr_tracker_event event = emptyEvent;
231        event.hash = t->hash;
232        event.messageType = type;
233        event.text = msg;
234        tr_publisherPublish( t->publisher, t, &event );
235    }
236}
237
238static void
239publishErrorClear( tr_tracker * t )
240{
241    publishMessage( t, NULL, TR_TRACKER_ERROR_CLEAR );
242}
243
244static void
245publishErrorMessageAndStop( tr_tracker * t, const char * msg )
246{
247    t->isRunning = 0;
248    publishMessage( t, msg, TR_TRACKER_ERROR );
249}
250
251static void
252publishWarning( tr_tracker * t, const char * msg )
253{
254    publishMessage( t, msg, TR_TRACKER_WARNING );
255}
256
257static void
258publishNewPeers( tr_tracker * t, int count, uint8_t * peers )
259{
260    tr_tracker_event event = emptyEvent;
261    event.hash = t->hash;
262    event.messageType = TR_TRACKER_PEERS;
263    event.peerCount = count;
264    event.peerCompact = peers;
265    tr_dbg( "Torrent \"%s\" got %d new peers", t->name, count );
266    if( count )
267        tr_publisherPublish( t->publisher, t, &event );
268}
269
270/***
271****
272***/
273
274static void onReqDone( tr_handle * handle );
275
276static void
277onStoppedResponse( struct evhttp_request * req UNUSED, void * handle )
278{
279    dbgmsg( NULL, "got a response to some `stop' message" );
280    onReqDone( handle );
281}
282
283static int
284parseBencResponse( struct evhttp_request * req, benc_val_t * setme )
285{
286    const unsigned char * body = EVBUFFER_DATA( req->input_buffer );
287    const int bodylen = EVBUFFER_LENGTH( req->input_buffer );
288    return tr_bencLoad( body, bodylen, setme, NULL );
289}
290
291static const char*
292updateAddresses( tr_tracker * t, const struct evhttp_request * req, int * tryAgain )
293{
294    const char * ret = NULL;
295    int moveToNextAddress = FALSE;
296
297    if( !req )
298    {
299        ret = "Tracker hasn't responded yet.  Retrying...";
300        tr_inf( ret );
301
302        moveToNextAddress = TRUE;
303    }
304    else if( req->response_code == HTTP_OK )
305    {
306        if( t->redirect != NULL )
307        {
308            /* multitracker spec: "if a connection with a tracker is
309               successful, it will be moved to the front of the tier." */
310            const int i = t->addressIndex;
311            const int j = t->tierFronts[i];
312            const tr_tracker_info swap = t->addresses[i];
313            t->addresses[i] = t->addresses[j];
314            t->addresses[j] = swap;
315        }
316    }
317    else if(    ( req->response_code == HTTP_MOVEPERM )
318             || ( req->response_code == HTTP_MOVETEMP ) )
319    {
320        const char * loc = evhttp_find_header( req->input_headers, "Location" );
321        tr_tracker_info tmp;
322        if( tr_trackerInfoInit( &tmp, loc, -1 ) ) /* a bad redirect? */
323        {
324            moveToNextAddress = TRUE;
325        }
326        else if( req->response_code == HTTP_MOVEPERM )
327        {
328            tr_tracker_info * cur = &t->addresses[t->addressIndex];
329            tr_trackerInfoClear( cur );
330            *cur = tmp;
331        }
332        else if( req->response_code == HTTP_MOVETEMP )
333        {
334            if( t->redirect == NULL )
335                t->redirect = tr_new0( tr_tracker_info, 1 );
336            else
337                tr_trackerInfoClear( t->redirect );
338            *t->redirect = tmp;
339        }
340    }
341    else 
342    {
343        moveToNextAddress = TRUE;
344    }
345
346    *tryAgain = moveToNextAddress;
347
348    if( moveToNextAddress )
349    {
350        if ( ++t->addressIndex >= t->addressCount ) /* we've tried them all */
351        {
352            *tryAgain = FALSE;
353            t->addressIndex = 0;
354            ret = "Tracker hasn't responded yet.  Retrying...";
355            tr_inf( ret );
356        }
357    }
358
359
360    return ret;
361}
362
363/* Convert to compact form */
364static uint8_t *
365parseOldPeers( benc_val_t * bePeers, int * setmePeerCount )
366{
367    int i;
368    uint8_t *compact, *walk;
369    const int peerCount = bePeers->val.l.count;
370
371    assert( bePeers->type == TYPE_LIST );
372
373    compact = tr_new( uint8_t, peerCount*6 );
374
375    for( i=0, walk=compact; i<peerCount; ++i )
376    {
377        struct in_addr addr;
378        tr_port_t port;
379        benc_val_t * val;
380        benc_val_t * peer = &bePeers->val.l.vals[i];
381
382        val = tr_bencDictFind( peer, "ip" );
383        if( !val || val->type!=TYPE_STR || tr_netResolve(val->val.s.s, &addr) )
384            continue;
385
386        memcpy( walk, &addr, 4 );
387        walk += 4;
388
389        val = tr_bencDictFind( peer, "port" );
390        if( !val || val->type!=TYPE_INT || val->val.i<0 || val->val.i>0xffff )
391            continue;
392
393        port = htons( val->val.i );
394        memcpy( walk, &port, 2 );
395        walk += 2;
396    }
397
398    *setmePeerCount = peerCount;
399    return compact;
400}
401
402static void
403onTrackerResponse( struct evhttp_request * req, void * vhash )
404{
405    const char * warning;
406    int tryAgain;
407    int responseCode;
408    struct torrent_hash * torrent_hash = (struct torrent_hash*) vhash;
409    tr_tracker * t = findTrackerFromHash( torrent_hash );
410
411    onReqDone( torrent_hash->handle );
412    tr_free( torrent_hash );
413
414    if( t == NULL ) /* tracker has been closed */
415        return;
416
417    dbgmsg( t, "got response from tracker: \"%s\"",
418            ( req && req->response_code_line ) ?  req->response_code_line
419                                               : "(null)" );
420
421    *t->lastAnnounceResponse = '\0';
422    if( req && req->response_code_line )
423        strlcpy( t->lastAnnounceResponse, req->response_code_line, sizeof( t->lastAnnounceResponse ) );
424
425    tr_dbg( "Torrent \"%s\" tracker response: %s",
426            t->name,
427            ( req ? req->response_code_line : "(null)") );
428
429    if( req && ( req->response_code == HTTP_OK ) )
430    {
431        benc_val_t benc;
432        const int bencLoaded = !parseBencResponse( req, &benc );
433
434        publishErrorClear( t );
435
436        if( bencLoaded && benc.type==TYPE_DICT )
437        {
438            benc_val_t * tmp;
439
440            if(( tmp = tr_bencDictFind( &benc, "failure reason" ))) {
441                dbgmsg( t, "got failure message [%s]", tmp->val.s.s );
442                publishErrorMessageAndStop( t, tmp->val.s.s );
443            }
444
445            if(( tmp = tr_bencDictFind( &benc, "warning message" ))) {
446                dbgmsg( t, "got warning message [%s]", tmp->val.s.s );
447                publishWarning( t, tmp->val.s.s );
448            }
449
450            if(( tmp = tr_bencDictFind( &benc, "interval" ))) {
451                dbgmsg( t, "setting interval to %d", tmp->val.i );
452                t->announceIntervalSec = tmp->val.i;
453            }
454
455            if(( tmp = tr_bencDictFind( &benc, "min interval" ))) {
456                dbgmsg( t, "setting min interval to %d", tmp->val.i );
457                t->announceMinIntervalSec = tmp->val.i;
458            }
459
460            if(( tmp = tr_bencDictFind( &benc, "tracker id" )))
461                t->trackerID = tr_strndup( tmp->val.s.s, tmp->val.s.i );
462
463            if(( tmp = tr_bencDictFind( &benc, "complete" )))
464                t->seederCount = tmp->val.i;
465
466            if(( tmp = tr_bencDictFind( &benc, "incomplete" )))
467                t->leecherCount = tmp->val.i;
468
469            if(( tmp = tr_bencDictFind( &benc, "peers" )))
470            {
471                int peerCount = 0;
472                uint8_t * peerCompact = NULL;
473
474                if( tmp->type == TYPE_LIST ) /* original protocol */
475                {
476                    if( tmp->val.l.count > 0 )
477                        peerCompact = parseOldPeers( tmp, &peerCount );
478                }
479                else if( tmp->type == TYPE_STR ) /* "compact" extension */
480                {
481                    if( tmp->val.s.i >= 6 )
482                    {
483                        peerCount = tmp->val.s.i / 6;
484                        peerCompact = tr_new( uint8_t, tmp->val.s.i );
485                        memcpy( peerCompact, tmp->val.s.s, tmp->val.s.i );
486                    }
487                }
488
489                publishNewPeers( t, peerCount, peerCompact );
490                tr_free( peerCompact );
491            }
492        }
493
494        if( bencLoaded )
495            tr_bencFree( &benc );
496    }
497
498    if (( warning = updateAddresses( t, req, &tryAgain ) )) {
499        publishWarning( t, warning );
500        tr_err( warning );
501    }
502
503    /**
504    ***
505    **/
506
507    if( tryAgain )
508        responseCode = 300;
509    else if( req )
510        responseCode = req->response_code;
511    else
512        responseCode = 503;
513
514    if( 200<=responseCode && responseCode<=299 )
515    {
516        dbgmsg( t, "request succeeded. reannouncing in %d seconds",
517                   t->announceIntervalSec );
518        t->reannounceAt = time( NULL ) + t->randOffset + t->announceIntervalSec;
519        t->manualAnnounceAllowedAt = time( NULL ) + t->announceMinIntervalSec;
520    }
521    else if( 300<=responseCode && responseCode<=399 )
522    {
523        dbgmsg( t, "got a redirect; retrying immediately" );
524
525        /* it's a redirect... updateAddresses() has already
526         * parsed the redirect, all that's left is to retry */
527        t->reannounceAt = time( NULL );
528        t->manualAnnounceAllowedAt = time( NULL ) + t->announceMinIntervalSec;
529    }
530    else if( 400<=responseCode && responseCode<=499 )
531    {
532        const char * err = req && req->response_code_line
533            ? req->response_code_line
534            : "Unspecified 4xx error from tracker.";
535        dbgmsg( t, err );
536
537        /* The request could not be understood by the server due to
538         * malformed syntax. The client SHOULD NOT repeat the
539         * request without modifications. */
540        publishErrorMessageAndStop( t, err );
541        t->manualAnnounceAllowedAt = ~(time_t)0;
542        t->reannounceAt = 0;
543    }
544    else if( 500<=responseCode && responseCode<=599 )
545    {
546        dbgmsg( t, "Got a 5xx error... retrying in one minute." );
547
548        /* Response status codes beginning with the digit "5" indicate
549         * cases in which the server is aware that it has erred or is
550         * incapable of performing the request.  So we pause a bit and
551         * try again. */
552        if( req && req->response_code_line )
553            publishWarning( t, req->response_code_line );
554        t->manualAnnounceAllowedAt = ~(time_t)0;
555        t->reannounceAt = time( NULL ) + 60;
556    }
557    else
558    {
559        dbgmsg( t, "Invalid response from tracker... retrying in two minutes." );
560
561        /* WTF did we get?? */
562        if( req && req->response_code_line )
563            publishWarning( t, req->response_code_line );
564        t->manualAnnounceAllowedAt = ~(time_t)0;
565        t->reannounceAt = time( NULL ) + t->randOffset + 120;
566    }
567}
568
569static void
570onScrapeResponse( struct evhttp_request * req, void * vhash )
571{
572    const char * warning;
573    int tryAgain;
574    time_t nextScrapeSec = 60;
575    struct torrent_hash * torrent_hash = (struct torrent_hash*) vhash;
576    tr_tracker * t = findTrackerFromHash( torrent_hash );
577
578    onReqDone( torrent_hash->handle );
579    tr_free( torrent_hash );
580
581    dbgmsg( t, "Got scrape response for '%s': %s (%d)", (t ? t->name : "(null)"), (req ? req->response_code_line : "(no line)"), (req ? req->response_code : -1) );
582
583    if( t == NULL ) /* tracker's been closed... */
584        return;
585
586    *t->lastScrapeResponse = '\0';
587    if( req && req->response_code_line )
588        strlcpy( t->lastScrapeResponse, req->response_code_line, sizeof( t->lastScrapeResponse ) );
589
590    tr_dbg( "Got scrape response for  '%s': %s",
591            t->name,
592            ( ( req && req->response_code_line ) ? req->response_code_line
593                                                 : "(null)") );
594
595    if( req && ( req->response_code == HTTP_OK ) )
596    {
597        benc_val_t benc, *files;
598        const int bencLoaded = !parseBencResponse( req, &benc );
599
600        if( bencLoaded
601            && (( files = tr_bencDictFind( &benc, "files" ) ))
602            && ( files->type == TYPE_DICT ) )
603        {
604            int i;
605            for( i=0; i<files->val.l.count; i+=2 )
606            {
607                const uint8_t* hash =
608                        (const uint8_t*) files->val.l.vals[i].val.s.s;
609                benc_val_t *tmp, *flags;
610                benc_val_t *tordict = &files->val.l.vals[i+1];
611                if( memcmp( t->hash, hash, SHA_DIGEST_LENGTH ) )
612                    continue;
613
614                publishErrorClear( t );
615
616                if(( tmp = tr_bencDictFind( tordict, "complete" )))
617                    t->seederCount = tmp->val.i;
618
619                if(( tmp = tr_bencDictFind( tordict, "incomplete" )))
620                    t->leecherCount = tmp->val.i;
621
622                if(( tmp = tr_bencDictFind( tordict, "downloaded" )))
623                    t->timesDownloaded = tmp->val.i;
624
625                if(( flags = tr_bencDictFind( tordict, "flags" )))
626                    if(( tmp = tr_bencDictFind( flags, "min_request_interval")))
627                        t->scrapeIntervalSec = tmp->val.i;
628
629                tr_dbg( "Torrent '%s' scrape successful."
630                        "  Rescraping in %d seconds",
631                        t->name, t->scrapeIntervalSec );
632
633                nextScrapeSec = t->scrapeIntervalSec;
634            }
635        }
636
637        if( bencLoaded )
638            tr_bencFree( &benc );
639    }
640
641    if (( warning = updateAddresses( t, req, &tryAgain ) ))
642    {
643        tr_err( warning );
644        publishWarning( t, warning );
645    }
646
647    if( tryAgain ) 
648        t->scrapeAt = time( NULL );
649    else
650        t->scrapeAt = time( NULL ) + t->randOffset + nextScrapeSec;
651}
652
653/***
654****
655***/
656
657enum
658{
659    TR_REQ_STARTED,
660    TR_REQ_COMPLETED,
661    TR_REQ_STOPPED,
662    TR_REQ_REANNOUNCE,
663    TR_REQ_SCRAPE,
664    TR_REQ_COUNT
665};
666
667struct tr_tracker_request
668{
669    int port;
670    int timeout;
671    int reqtype; /* TR_REQ_* */
672    char * address;
673    char * uri;
674    struct evhttp_request * req;
675    uint8_t torrent_hash[SHA_DIGEST_LENGTH];
676};
677
678static void
679freeRequest( struct tr_tracker_request * req )
680{
681    tr_free( req->address );
682    tr_free( req->uri );
683    tr_free( req );
684}
685
686static void
687addCommonHeaders( const tr_tracker * t,
688                  struct evhttp_request * req )
689{
690    char buf[1024];
691    const tr_tracker_info * address = getCurrentAddress( t );
692    snprintf( buf, sizeof(buf), "%s:%d", address->address, address->port );
693    evhttp_add_header( req->output_headers, "Host", buf );
694    evhttp_add_header( req->output_headers, "Connection", "close" );
695    evhttp_add_header( req->output_headers, "User-Agent",
696                                         TR_NAME "/" LONG_VERSION_STRING );
697}
698
699static char*
700buildTrackerRequestURI( const tr_tracker  * t,
701                        const tr_torrent  * torrent,
702                        const char        * eventName )
703{
704    const int isStopping = !strcmp( eventName, "stopped" );
705    const int numwant = isStopping ? 0 : NUMWANT;
706    struct evbuffer * buf = evbuffer_new( );
707    char * ret;
708
709    char * ann = getCurrentAddress(t)->announce;
710   
711    evbuffer_add_printf( buf, "%s"
712                              "%cinfo_hash=%s"
713                              "&peer_id=%s"
714                              "&port=%d"
715                              "&uploaded=%"PRIu64
716                              "&downloaded=%"PRIu64
717                              "&corrupt=%"PRIu64
718                              "&left=%"PRIu64
719                              "&compact=1"
720                              "&numwant=%d"
721                              "&key=%s"
722                              "&supportcrypto=1"
723                              "&requirecrypto=%d"
724                              "%s%s"
725                              "%s%s",
726        ann,
727        strchr(ann, '?') ? '&' : '?',
728        t->escaped,
729        t->peer_id,
730        tr_sharedGetPublicPort( t->handle->shared ),
731        torrent->uploadedCur,
732        torrent->downloadedCur,
733        torrent->corruptCur,
734        tr_cpLeftUntilComplete( torrent->completion ),
735        numwant,
736        t->key_param,
737        ( t->handle->encryptionMode==TR_ENCRYPTION_REQUIRED ? 1 : 0 ),
738        ( ( eventName && *eventName ) ? "&event=" : "" ),
739        ( ( eventName && *eventName ) ? eventName : "" ),
740        ( ( t->trackerID && *t->trackerID ) ? "&trackerid=" : "" ),
741        ( ( t->trackerID && *t->trackerID ) ? t->trackerID : "" ) );
742
743    ret = tr_strdup( (char*) EVBUFFER_DATA( buf ) );
744    evbuffer_free( buf );
745    return ret;
746}
747
748static struct tr_tracker_request*
749createRequest( tr_handle * handle, const tr_tracker * tracker, int reqtype )
750{
751    static const char* strings[TR_REQ_COUNT] = { "started", "completed", "stopped", "", "err" };
752    const tr_torrent * torrent = tr_torrentFindFromHash( handle, tracker->hash );
753    const tr_tracker_info * address = getCurrentAddress( tracker );
754    const int isStopping = reqtype == TR_REQ_STOPPED;
755    const char * eventName = strings[reqtype];
756    struct tr_tracker_request * req;
757
758    req = tr_new0( struct tr_tracker_request, 1 );
759    req->address = tr_strdup( address->address );
760    req->port = address->port;
761    req->uri = buildTrackerRequestURI( tracker, torrent, eventName );
762    req->timeout = isStopping ? STOP_TIMEOUT_INTERVAL_SEC : TIMEOUT_INTERVAL_SEC;
763    req->reqtype = reqtype;
764    req->req = isStopping
765        ? evhttp_request_new( onStoppedResponse, handle )
766        : evhttp_request_new( onTrackerResponse, torrentHashNew(handle, tracker) );
767    memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH );
768    addCommonHeaders( tracker, req->req );
769
770    return req;
771}
772
773static struct tr_tracker_request*
774createScrape( tr_handle * handle, const tr_tracker * tracker )
775{
776    const tr_tracker_info * a = getCurrentAddress( tracker );
777    struct tr_tracker_request * req;
778
779    req = tr_new0( struct tr_tracker_request, 1 );
780    req->address = tr_strdup( a->address );
781    req->port = a->port;
782    req->timeout = TIMEOUT_INTERVAL_SEC;
783    req->req = evhttp_request_new( onScrapeResponse, torrentHashNew( handle, tracker ) );
784    req->reqtype = TR_REQ_SCRAPE;
785    tr_asprintf( &req->uri, "%s%cinfo_hash=%s", a->scrape, strchr(a->scrape,'?')?'&':'?', tracker->escaped );
786    memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH );
787    addCommonHeaders( tracker, req->req );
788
789    return req;
790}
791
792struct tr_tracker_handle
793{
794    int socketCount;
795    unsigned int isShuttingDown : 1;
796    tr_timer * pulseTimer;
797    tr_list * requestQueue;
798    tr_list * scrapeQueue;
799};
800
801static int pulse( void * vhandle );
802
803static void
804ensureGlobalsExist( tr_handle * handle )
805{
806    if( handle->tracker == NULL )
807    {
808        handle->tracker = tr_new0( struct tr_tracker_handle, 1 );
809        handle->tracker->pulseTimer = tr_timerNew( handle, pulse, handle, PULSE_INTERVAL_MSEC );
810        dbgmsg( NULL, "creating tracker timer" );
811    }
812}
813
814static void
815freeRequest2( void * req )
816{
817    freeRequest( req );
818}
819
820void
821tr_trackerShuttingDown( tr_handle * handle )
822{
823    if( handle->tracker )
824    {
825        /* since we're shutting down, we don't need to scrape anymore... */
826        tr_list_free( &handle->tracker->scrapeQueue, freeRequest2 );
827
828        handle->tracker->isShuttingDown = 1;
829    }
830}
831
832static int
833maybeFreeGlobals( tr_handle * handle )
834{
835    int globalsExist = handle->tracker != NULL;
836
837    if( globalsExist
838        && ( handle->tracker->socketCount < 1 )
839        && ( handle->tracker->requestQueue == NULL )
840        && ( handle->tracker->scrapeQueue == NULL )
841        && ( handle->torrentList== NULL ) )
842    {
843        dbgmsg( NULL, "freeing tracker timer" );
844        tr_timerFree( &handle->tracker->pulseTimer );
845        tr_free( handle->tracker );
846        handle->tracker = NULL;
847        globalsExist = FALSE;
848    }
849
850    return globalsExist;
851}
852
853/***
854****
855***/
856
857static int
858freeConnection( void * evcon )
859{
860    evhttp_connection_free( evcon );
861    return FALSE;
862}
863static void
864connectionClosedCB( struct evhttp_connection * evcon, void * vhandle )
865{
866    tr_handle * handle = vhandle;
867
868    /* libevent references evcon right after calling this function,
869       so we can't free it yet... defer it to after this call chain
870       has played out */
871    tr_timerNew( handle, freeConnection, evcon, 100 );
872}
873
874static struct evhttp_connection*
875getConnection( tr_handle * handle, const char * address, int port )
876{
877    struct evhttp_connection * c = evhttp_connection_new( address, port );
878    evhttp_connection_set_closecb( c, connectionClosedCB, handle );
879    return c;
880}
881
882static void
883invokeRequest( tr_handle * handle, const struct tr_tracker_request * req )
884{
885    const time_t now = time( NULL );
886    struct evhttp_connection * evcon = getConnection( handle, req->address, req->port );
887    tr_tracker * t = findTracker( handle, req->torrent_hash );
888    dbgmsg( t, "sending '%s' to tracker %s:%d, timeout is %d", req->uri, req->address, req->port, (int)req->timeout );
889    evhttp_connection_set_timeout( evcon, req->timeout );
890    ++handle->tracker->socketCount;
891
892    if( t != NULL )
893    {
894        if( req->reqtype == TR_REQ_SCRAPE )
895        {
896            t->lastScrapeTime = now;
897            t->scrapeAt = 0;
898        }
899        else
900        {
901            t->lastAnnounceTime = now;
902            t->reannounceAt = 0;
903            t->manualAnnounceAllowedAt = 0;
904        }
905    }
906
907    if( evhttp_make_request( evcon, req->req, EVHTTP_REQ_GET, req->uri ))
908        (*req->req->cb)(req->req, req->req->cb_arg);
909    else
910        dbgmsg( t, "incremented socket count to %d", handle->tracker->socketCount );
911}
912
913static void
914invokeNextInQueue( tr_handle * handle, tr_list ** list )
915{
916    struct tr_tracker_request * req = tr_list_pop_front( list );
917    invokeRequest( handle, req );
918    freeRequest( req );
919}
920
921static int
922socketIsAvailable( tr_handle * handle )
923{
924    const int max = handle->tracker->isShuttingDown
925                      ? MAX_TRACKER_SOCKETS_DURING_SHUTDOWN
926                      : MAX_TRACKER_SOCKETS;
927    return handle->tracker->socketCount < max;
928}
929
930static void ensureGlobalsExist( tr_handle * );
931
932static void
933enqueueScrape( tr_handle * handle, const tr_tracker * tracker )
934{
935    struct tr_tracker_request * req;
936    ensureGlobalsExist( handle );
937    req = createScrape( handle, tracker );
938    tr_list_append( &handle->tracker->scrapeQueue, req );
939}
940
941static void
942enqueueRequest( tr_handle * handle, const tr_tracker * tracker, int reqtype )
943{
944    struct tr_tracker_request * req;
945    ensureGlobalsExist( handle );
946    req = createRequest( handle, tracker, reqtype );
947    tr_list_append( &handle->tracker->requestQueue, req );
948}
949
950static void
951scrapeSoon( tr_tracker * t )
952{
953    if( trackerSupportsScrape( t ) )
954        t->scrapeAt = time( NULL ) + t->randOffset;
955}
956
957static int
958pulse( void * vhandle )
959{
960    tr_handle * handle = vhandle;
961    struct tr_tracker_handle * th = handle->tracker;
962    tr_torrent * tor;
963    const time_t now = time( NULL );
964
965    if( handle->tracker == NULL )
966        return FALSE;
967
968    if( handle->tracker->socketCount || tr_list_size(th->requestQueue) || tr_list_size(th->scrapeQueue) )
969        dbgmsg( NULL, "tracker pulse... %d sockets, %d reqs left, %d scrapes left", handle->tracker->socketCount, tr_list_size(th->requestQueue), tr_list_size(th->scrapeQueue) );
970
971    /* upkeep: queue periodic rescrape / reannounce */
972    for( tor=handle->torrentList; tor; tor=tor->next )
973    {
974        tr_tracker * t = tor->tracker;
975
976        if( t->scrapeAt && trackerSupportsScrape( t ) && ( now >= t->scrapeAt ) ) {
977            t->scrapeAt = 0;
978            enqueueScrape( handle, t );
979        }
980
981        if( t->reannounceAt && t->isRunning && ( now >= t->reannounceAt ) ) {
982            t->reannounceAt = 0;
983            enqueueRequest( handle, t, TR_REQ_REANNOUNCE );
984        }
985    }
986
987    if( handle->tracker->socketCount || tr_list_size(th->requestQueue) || tr_list_size(th->scrapeQueue) )
988        dbgmsg( NULL, "tracker pulse after upkeep... %d sockets, %d reqs left, %d scrapes left", handle->tracker->socketCount, tr_list_size(th->requestQueue), tr_list_size(th->scrapeQueue) );
989
990    /* look for things to do... process all the requests, then process all the scrapes */
991    while( th->requestQueue && socketIsAvailable( handle ) )
992        invokeNextInQueue( handle, &th->requestQueue );
993    while( th->scrapeQueue && socketIsAvailable( handle ) )
994        invokeNextInQueue( handle, &th->scrapeQueue );
995
996    if( handle->tracker->socketCount || tr_list_size(th->requestQueue) || tr_list_size(th->scrapeQueue) )
997        dbgmsg( NULL, "tracker pulse done... %d sockets, %d reqs left, %d scrapes left", handle->tracker->socketCount, tr_list_size(th->requestQueue), tr_list_size(th->scrapeQueue) );
998
999    return maybeFreeGlobals( handle );
1000}
1001
1002static void
1003onReqDone( tr_handle * handle )
1004{
1005    if( handle->tracker )
1006    {
1007        pulse( handle );
1008        --handle->tracker->socketCount;
1009        dbgmsg( NULL, "decrementing socket count to %d", handle->tracker->socketCount );
1010    }
1011}
1012
1013/***
1014****  LIFE CYCLE
1015***/
1016
1017static void
1018generateKeyParam( char * msg, int len )
1019{
1020    int i;
1021    const char * pool = "abcdefghijklmnopqrstuvwxyz0123456789";
1022    const int poolSize = strlen( pool );
1023    for( i=0; i<len; ++i )
1024        *msg++ = pool[tr_rand(poolSize)];
1025    *msg = '\0';
1026}
1027
1028static int
1029is_rfc2396_alnum( char ch )
1030{
1031    return ( (ch >= 'a' && ch <= 'z' )
1032            || (ch >= 'A' && ch <= 'Z' )
1033            || (ch >= '0' && ch <= '9' ) );
1034}
1035
1036static void
1037escape( char * out, const uint8_t * in, int in_len ) /* rfc2396 */
1038{
1039    const uint8_t *end = in + in_len;
1040    while( in != end )
1041        if( is_rfc2396_alnum(*in) )
1042            *out++ = (char) *in++;
1043        else 
1044            out += snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
1045    *out = '\0';
1046}
1047
1048tr_tracker *
1049tr_trackerNew( const tr_torrent * torrent )
1050{
1051    const tr_info * info = &torrent->info;
1052    int i, j, sum, *iwalk;
1053    tr_tracker_info * nwalk;
1054    tr_tracker * t;
1055
1056    t = tr_new0( tr_tracker, 1 );
1057    t->handle = torrent->handle;
1058    t->scrapeIntervalSec       = DEFAULT_SCRAPE_INTERVAL_SEC;
1059    t->announceIntervalSec     = DEFAULT_ANNOUNCE_INTERVAL_SEC;
1060    t->announceMinIntervalSec  = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
1061    generateKeyParam( t->key_param, KEYLEN );
1062
1063    t->publisher = tr_publisherNew( );
1064    t->timesDownloaded = -1;
1065    t->seederCount = -1;
1066    t->leecherCount = -1;
1067    t->manualAnnounceAllowedAt = ~(time_t)0;
1068    t->name = tr_strdup( info->name );
1069    t->randOffset = tr_rand( 60 );
1070    memcpy( t->hash, info->hash, SHA_DIGEST_LENGTH );
1071    escape( t->escaped, info->hash, SHA_DIGEST_LENGTH );
1072
1073    for( sum=i=0; i<info->trackerTiers; ++i )
1074         sum += info->trackerList[i].count;
1075    t->addresses = nwalk = tr_new0( tr_tracker_info, sum );
1076    t->addressIndex = 0;
1077    t->addressCount = sum;
1078    t->tierFronts = iwalk = tr_new0( int, sum );
1079
1080    for( i=0; i<info->trackerTiers; ++i )
1081    {
1082        const int tierFront = nwalk - t->addresses;
1083
1084        for( j=0; j<info->trackerList[i].count; ++j )
1085        {
1086            const tr_tracker_info * src = &info->trackerList[i].list[j];
1087            nwalk->address = tr_strdup( src->address );
1088            nwalk->port = src->port;
1089            nwalk->announce = tr_strdup( src->announce );
1090            nwalk->scrape = tr_strdup( src->scrape );
1091            ++nwalk;
1092
1093            *iwalk++ = tierFront;
1094        }
1095    }
1096
1097    assert( nwalk - t->addresses == sum );
1098    assert( iwalk - t->tierFronts == sum );
1099
1100    scrapeSoon( t );
1101
1102    return t;
1103}
1104
1105static void
1106onTrackerFreeNow( void * vt )
1107{
1108    int i;
1109    tr_tracker * t = vt;
1110
1111    tr_publisherFree( &t->publisher );
1112    tr_free( t->name );
1113    tr_free( t->trackerID );
1114    tr_free( t->peer_id );
1115
1116    /* addresses... */
1117    for( i=0; i<t->addressCount; ++i )
1118        tr_trackerInfoClear( &t->addresses[i] );
1119    tr_free( t->addresses );
1120    tr_free( t->tierFronts );
1121
1122    /* redirect... */
1123    if( t->redirect ) {
1124        tr_trackerInfoClear( t->redirect );
1125        tr_free( t->redirect );
1126    }
1127
1128    tr_free( t );
1129}
1130
1131void
1132tr_trackerFree( tr_tracker * t )
1133{
1134    tr_runInEventThread( t->handle, onTrackerFreeNow, t );
1135}
1136
1137
1138/***
1139****  PUBLIC
1140***/
1141
1142tr_publisher_tag
1143tr_trackerSubscribe( tr_tracker          * t,
1144                     tr_delivery_func      func,
1145                     void                * user_data )
1146{
1147    return tr_publisherSubscribe( t->publisher, func, user_data );
1148}
1149
1150void
1151tr_trackerUnsubscribe( tr_tracker        * t,
1152                       tr_publisher_tag    tag )
1153{
1154    tr_publisherUnsubscribe( t->publisher, tag );
1155}
1156
1157const tr_tracker_info *
1158tr_trackerGetAddress( const tr_tracker   * t )
1159{
1160    return getCurrentAddress( t );
1161}
1162
1163time_t
1164tr_trackerGetManualAnnounceTime( const struct tr_tracker * t )
1165{
1166    return t->isRunning ? t->manualAnnounceAllowedAt : 0;
1167}
1168
1169int
1170tr_trackerCanManualAnnounce ( const tr_tracker * t)
1171{
1172    const time_t allow = tr_trackerGetManualAnnounceTime( t );
1173    return allow && ( allow <= time( NULL ) );
1174}
1175
1176void
1177tr_trackerGetCounts( const tr_tracker  * t,
1178                     int               * setme_completedCount,
1179                     int               * setme_leecherCount,
1180                     int               * setme_seederCount )
1181{
1182    if( setme_completedCount )
1183       *setme_completedCount = t->timesDownloaded;
1184
1185    if( setme_leecherCount )
1186       *setme_leecherCount = t->leecherCount;
1187
1188    if( setme_seederCount )
1189       *setme_seederCount = t->seederCount;
1190}
1191
1192
1193void
1194tr_trackerStart( tr_tracker * t )
1195{
1196    tr_free( t->peer_id );
1197    t->peer_id = tr_peerIdNew( );
1198
1199    if( t->isRunning == 0 ) {
1200        t->isRunning = 1;
1201        enqueueRequest( t->handle, t, TR_REQ_STARTED );
1202    }
1203}
1204
1205void
1206tr_trackerReannounce( tr_tracker * t )
1207{
1208    enqueueRequest( t->handle, t, TR_REQ_REANNOUNCE );
1209}
1210
1211void
1212tr_trackerCompleted( tr_tracker * t )
1213{
1214    enqueueRequest( t->handle, t, TR_REQ_COMPLETED );
1215}
1216
1217void
1218tr_trackerStop( tr_tracker * t )
1219{
1220    if( t->isRunning ) {
1221        t->isRunning = 0;
1222        t->reannounceAt = t->manualAnnounceAllowedAt = 0;
1223        enqueueRequest( t->handle, t, TR_REQ_STOPPED );
1224    }
1225}
1226
1227void
1228tr_trackerChangeMyPort( tr_tracker * t )
1229{
1230    if( t->isRunning )
1231        tr_trackerReannounce( t );
1232}
1233
1234void
1235tr_trackerStat( const tr_tracker * t,
1236                struct tr_tracker_stat * setme)
1237{
1238    assert( t != NULL );
1239    assert( setme != NULL );
1240
1241    strlcpy( setme->scrapeResponse,
1242             t->lastScrapeResponse,
1243             sizeof( setme->scrapeResponse ) );
1244    setme->lastScrapeTime = t->lastScrapeTime;
1245    setme->nextScrapeTime = t->scrapeAt;
1246
1247    strlcpy( setme->announceResponse,
1248             t->lastAnnounceResponse,
1249             sizeof( setme->announceResponse ) );
1250    setme->lastAnnounceTime = t->lastAnnounceTime;
1251    setme->nextAnnounceTime = t->reannounceAt;
1252    setme->nextManualAnnounceTime = t->manualAnnounceAllowedAt;
1253}
Note: See TracBrowser for help on using the repository browser.