source: branches/1.5x/libtransmission/tracker.c @ 8204

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

(1.5x libT) various backports for 1.52:
(1) recognize Aria2 as a client
(2) remove jhujhiti's tr_suspectAddress(), since he removed it from trunka
(3) on Mac, better detection of where the Web UI files are located
(4) reintroduce the web task queue
(5) various minor formatting changes to reduce the diffs between 1.52 and trunk

  • 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 8204 2009-04-10 17:34:25Z 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.