source: trunk/libtransmission/tracker.c @ 8268

Last change on this file since 8268 was 8268, checked in by charles, 13 years ago

(trunk libT) tweak the bencode comments a bit

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