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

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

(1.5x) sync with trunk's libtransmission bugfixes & enhancements

  • Property svn:keywords set to Date Rev Author Id
File size: 35.6 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 7722 2009-01-16 17:31:10Z 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        tr_suspectAddress( &addr, "compact" );
263       
264        memcpy( walk, &addr, sizeof( addr ) );
265        memcpy( walk + sizeof( addr ), &port, 2 );
266       
267        walk += sizeof( tr_address ) + 2;
268        compactWalk += 6;
269    }
270    publishNewPeers( t, allAreSeeds, array, arrayLen );
271    tr_free( array );
272}
273
274static void
275publishNewPeersCompact6( tr_tracker * t,
276                         int          allAreSeeds,
277                         void       * compact,
278                         int          compactLen )
279{
280    int i;
281    uint8_t *array, *walk, *compactWalk;
282    const int peerCount = compactLen / 18;
283    const int arrayLen = peerCount * ( sizeof( tr_address ) + 2 );
284    tr_address addr;
285    tr_port port;
286   
287    addr.type = TR_AF_INET6;
288    memset( &addr.addr, 0x00, sizeof( addr.addr ) );
289    array = tr_new( uint8_t, arrayLen );
290    for ( i = 0, walk = array, compactWalk = compact ; i < peerCount ; i++ )
291    {
292        memcpy( &addr.addr.addr6, compactWalk, 16 );
293        memcpy( &port, compactWalk + 16, 2 );
294        compactWalk += 18;
295        tr_suspectAddress( &addr, "compact6" );
296       
297        memcpy( walk, &addr, sizeof( addr ) );
298        memcpy( walk + sizeof( addr ), &port, 2 );
299        walk += sizeof( tr_address ) + 2;
300    }
301    publishNewPeers( t, allAreSeeds, array, arrayLen );
302    tr_free( array );
303}
304
305/***
306****
307***/
308
309static void onReqDone( tr_session * session );
310
311static int
312updateAddresses( tr_tracker * t,
313                 int          success )
314{
315    int          retry;
316
317    tr_torrent * torrent = tr_torrentFindFromHash( t->session, t->hash );
318
319    if( success )
320    {
321        /* multitracker spec: "if a connection with a tracker is
322           successful, it will be moved to the front of the tier." */
323        t->trackerIndex = tr_torrentPromoteTracker( torrent, t->trackerIndex );
324        retry = FALSE; /* we succeeded; no need to retry */
325    }
326    else if( ++t->trackerIndex >= torrent->info.trackerCount )
327    {
328        t->trackerIndex = 0;
329        retry = FALSE; /* we've tried them all */
330    }
331    else
332    {
333        const tr_tracker_info * n = getCurrentAddressFromTorrent( t, torrent );
334        tr_ninf( t->name, _( "Trying tracker \"%s\"" ), n->announce );
335        retry = TRUE;
336    }
337
338    return retry;
339}
340
341static uint8_t *
342parseOldPeers( tr_benc * bePeers,
343               size_t *  byteCount )
344{
345    int       i;
346    uint8_t * array, *walk;
347    const int peerCount = bePeers->val.l.count;
348
349    assert( bePeers->type == TYPE_LIST );
350
351    array = tr_new( uint8_t, peerCount * ( sizeof( tr_address ) + 2 ) );
352
353    for( i = 0, walk = array; i < peerCount; ++i )
354    {
355        const char * s;
356        int64_t      itmp;
357        tr_address   addr;
358        tr_port      port;
359        tr_benc    * peer = &bePeers->val.l.vals[i];
360
361        if( tr_bencDictFindStr( peer, "ip", &s ) )
362        {
363            if( tr_pton( s, &addr ) == NULL )
364                continue;
365        }
366        if( !tr_bencDictFindInt( peer, "port",
367                                 &itmp ) || itmp < 0 || itmp > 0xffff )
368            continue;
369
370        memcpy( walk, &addr, sizeof( tr_address ) );
371        tr_suspectAddress( &addr, "old tracker" );
372        port = htons( itmp );
373        memcpy( walk + sizeof( tr_address ), &port, 2 );
374        walk += sizeof( tr_address ) + 2;
375    }
376
377    *byteCount = peerCount * sizeof( tr_address ) + 2;
378    return array;
379}
380
381static void
382onStoppedResponse( tr_session    * session,
383                   long            responseCode UNUSED,
384                   const void    * response     UNUSED,
385                   size_t          responseLen  UNUSED,
386                   void          * torrentId )
387{
388    tr_tracker * t = findTracker( session, tr_ptr2int( torrentId ) );
389    if( t )
390    {
391        const time_t now = time( NULL );
392
393        t->reannounceAt = TR_TRACKER_STOPPED;
394        t->manualAnnounceAllowedAt = TR_TRACKER_STOPPED;
395
396        if( t->scrapeAt <= now )
397            t->scrapeAt = now + t->scrapeIntervalSec + t->randOffset;
398    }
399
400    dbgmsg( NULL, "got a response to some `stop' message" );
401    onReqDone( session );
402}
403
404static void
405onTrackerResponse( tr_session * session,
406                   long         responseCode,
407                   const void * response,
408                   size_t       responseLen,
409                   void       * torrentId )
410{
411    int retry;
412    int success = FALSE;
413    int scrapeFields = 0;
414    tr_tracker * t;
415
416    onReqDone( session );
417    t = findTracker( session, tr_ptr2int( torrentId ) );
418    if( !t ) /* tracker's been closed */
419        return;
420
421    dbgmsg( t->name, "tracker response: %ld", responseCode );
422    tr_ndbg( t->name, "tracker response: %ld", responseCode );
423    t->lastAnnounceResponse = responseCode;
424
425    if( responseCode == HTTP_OK )
426    {
427        tr_benc   benc;
428        const int bencLoaded = !tr_bencLoad( response, responseLen,
429                                             &benc, NULL );
430        publishErrorClear( t );
431        if( bencLoaded && tr_bencIsDict( &benc ) )
432        {
433            tr_benc *    tmp;
434            int64_t      i;
435            int          incomplete = -1;
436            const char * str;
437
438            success = TRUE;
439            t->retryAnnounceIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
440
441            if( ( tr_bencDictFindStr( &benc, "failure reason", &str ) ) )
442            {
443                publishMessage( t, str, TR_TRACKER_ERROR );
444                success = FALSE;
445            }
446
447            if( ( tr_bencDictFindStr( &benc, "warning message", &str ) ) )
448                publishWarning( t, str );
449
450            if( ( tr_bencDictFindInt( &benc, "interval", &i ) ) )
451            {
452                dbgmsg( t->name, "setting interval to %d", (int)i );
453                t->announceIntervalSec = i;
454            }
455
456            if( ( tr_bencDictFindInt( &benc, "min interval", &i ) ) )
457            {
458                dbgmsg( t->name, "setting min interval to %d", (int)i );
459                t->announceMinIntervalSec = i;
460            }
461
462            if( ( tr_bencDictFindStr( &benc, "tracker id", &str ) ) )
463                t->trackerID = tr_strdup( str );
464
465            if( ( tr_bencDictFindInt( &benc, "complete", &i ) ) )
466            {
467                ++scrapeFields;
468                t->seederCount = i;
469            }
470
471            if( ( tr_bencDictFindInt( &benc, "incomplete", &i ) ) )
472            {
473                ++scrapeFields;
474                t->leecherCount = incomplete = i;
475            }
476
477            if( ( tr_bencDictFindInt( &benc, "downloaded", &i ) ) )
478            {
479                ++scrapeFields;
480                t->timesDownloaded = i;
481            }
482
483            if( ( tmp = tr_bencDictFind( &benc, "peers" ) ) )
484            {
485                const int allAreSeeds = incomplete == 0;
486
487                if( tmp->type == TYPE_STR ) /* "compact" extension */
488                {
489                    publishNewPeersCompact( t, allAreSeeds, tmp->val.s.s,
490                                            tmp->val.s.i );
491                }
492                else if( tmp->type == TYPE_LIST ) /* original protocol */
493                {
494                    size_t    byteCount = 0;
495                    uint8_t * array = parseOldPeers( tmp, &byteCount );
496                    publishNewPeers( t, allAreSeeds, array, byteCount );
497                    tr_free( array );
498                }
499            }
500           
501            if( ( tmp = tr_bencDictFind( &benc, "peers6" ) ) )
502            {
503                const int allAreSeeds = incomplete == 0;
504               
505                if( tmp->type == TYPE_STR ) /* "compact" extension */
506                {
507                    publishNewPeersCompact6( t, allAreSeeds, tmp->val.s.s,
508                                             tmp->val.s.i );
509                }
510            }
511        }
512
513        if( bencLoaded )
514            tr_bencFree( &benc );
515    }
516    else if( responseCode )
517    {
518        /* %1$ld - http status code, such as 404
519         * %2$s - human-readable explanation of the http status code */
520        char * buf = tr_strdup_printf( _( "Tracker request failed.  Got HTTP Status Code %1$ld (%2$s)" ),
521                                      responseCode,
522                                      tr_webGetResponseStr( responseCode ) );
523        publishWarning( t, buf );
524        tr_free( buf );
525    }
526
527    retry = updateAddresses( t, success );
528
529    if( responseCode && retry )
530        responseCode = 300;
531
532    if( responseCode == 0 )
533    {
534        dbgmsg( t->name, "No response from tracker... retrying in two minutes." );
535        t->manualAnnounceAllowedAt = ~(time_t)0;
536        t->reannounceAt = time( NULL ) + t->randOffset + 120;
537    }
538    else if( 200 <= responseCode && responseCode <= 299 )
539    {
540        const int    interval = t->announceIntervalSec + t->randOffset;
541        const time_t now = time ( NULL );
542        dbgmsg( t->name, "request succeeded. reannouncing in %d seconds", interval );
543
544        /* if the announce response was a superset of the scrape response,
545           treat this as both a successful announce AND scrape. */
546        if( scrapeFields >= 3 ) {
547            t->lastScrapeResponse = responseCode;
548            t->lastScrapeTime = now;
549            t->scrapeAt = now + t->scrapeIntervalSec + t->randOffset;
550        }
551
552        /* most trackers don't provide all the scrape responses, but do
553           provide most of them, so don't scrape too soon anyway */
554        if( ( scrapeFields == 2 ) && ( t->scrapeAt <= ( now + 120 ) ) ) {
555            t->scrapeAt = now + t->scrapeIntervalSec + t->randOffset;
556        }
557
558        t->reannounceAt = now + interval;
559        t->manualAnnounceAllowedAt = now + t->announceMinIntervalSec;
560
561        /* #319: save the .resume file after an announce so that, in case
562         * of a crash, our stats still match up with the tracker's stats */
563        tr_torrentSaveResume( tr_torrentFindFromHash( t->session, t->hash ) );
564    }
565    else if( 300 <= responseCode && responseCode <= 399 )
566    {
567        /* it's a redirect... updateAddresses() has already
568         * parsed the redirect, all that's left is to retry */
569        const int interval = 5;
570        dbgmsg( t->name, "got a redirect. retrying in %d seconds", interval );
571        t->reannounceAt = time( NULL ) + interval;
572        t->manualAnnounceAllowedAt = time( NULL ) + t->announceMinIntervalSec;
573    }
574    else if( 400 <= responseCode && responseCode <= 499 )
575    {
576        /* The request could not be understood by the server due to
577         * malformed syntax. The client SHOULD NOT repeat the
578         * request without modifications. */
579        publishErrorMessageAndStop( t, _( "Tracker returned a 4xx message" ) );
580        t->manualAnnounceAllowedAt = ~(time_t)0;
581        t->reannounceAt = TR_TRACKER_STOPPED;
582    }
583    else if( 500 <= responseCode && responseCode <= 599 )
584    {
585        /* Response status codes beginning with the digit "5" indicate
586         * cases in which the server is aware that it has erred or is
587         * incapable of performing the request.  So we pause a bit and
588         * try again. */
589        t->manualAnnounceAllowedAt = ~(time_t)0;
590        t->reannounceAt = time( NULL ) + t->retryAnnounceIntervalSec;
591        t->retryAnnounceIntervalSec *= 2;
592    }
593    else
594    {
595        /* WTF did we get?? */
596        dbgmsg( t->name, "Invalid response from tracker... retrying in two minutes." );
597        t->manualAnnounceAllowedAt = ~(time_t)0;
598        t->reannounceAt = time( NULL ) + t->randOffset + 120;
599    }
600}
601
602static void
603onScrapeResponse( tr_session * session,
604                  long         responseCode,
605                  const void * response,
606                  size_t       responseLen,
607                  void       * torrentId )
608{
609    int          success = FALSE;
610    int          retry;
611    tr_tracker * t;
612
613    onReqDone( session );
614    t = findTracker( session, tr_ptr2int( torrentId ) );
615    if( !t ) /* tracker's been closed... */
616        return;
617
618    dbgmsg( t->name, "scrape response: %ld\n", responseCode );
619    tr_ndbg( t->name, "scrape response: %ld", responseCode );
620    t->lastScrapeResponse = responseCode;
621
622    if( responseCode == HTTP_OK )
623    {
624        tr_benc   benc, *files;
625        const int bencLoaded = !tr_bencLoad( response, responseLen,
626                                             &benc, NULL );
627        if( bencLoaded && tr_bencDictFindDict( &benc, "files", &files ) )
628        {
629            size_t i;
630            for( i = 0; i < files->val.l.count; i += 2 )
631            {
632                int64_t        itmp;
633                const uint8_t* hash =
634                    (const uint8_t*) files->val.l.vals[i].val.s.s;
635                tr_benc *      flags;
636                tr_benc *      tordict = &files->val.l.vals[i + 1];
637                if( memcmp( t->hash, hash, SHA_DIGEST_LENGTH ) )
638                    continue;
639
640                publishErrorClear( t );
641
642                if( ( tr_bencDictFindInt( tordict, "complete", &itmp ) ) )
643                    t->seederCount = itmp;
644
645                if( ( tr_bencDictFindInt( tordict, "incomplete", &itmp ) ) )
646                    t->leecherCount = itmp;
647
648                if( ( tr_bencDictFindInt( tordict, "downloaded", &itmp ) ) )
649                    t->timesDownloaded = itmp;
650
651                if( ( tr_bencDictFindInt( tordict, "downloaders", &itmp ) ) )
652                    t->downloaderCount = itmp;
653
654                if( tr_bencDictFindDict( tordict, "flags", &flags ) )
655                    if( ( tr_bencDictFindInt( flags, "min_request_interval", &itmp ) ) )
656                        t->scrapeIntervalSec = i;
657
658                /* as per ticket #1045, safeguard against trackers returning
659                 * a very low min_request_interval... */
660                if( t->scrapeIntervalSec < DEFAULT_SCRAPE_INTERVAL_SEC )
661                    t->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC;
662
663                tr_ndbg( t->name,
664                         "Scrape successful.  Rescraping in %d seconds.",
665                         t->scrapeIntervalSec );
666
667                success = TRUE;
668                t->retryScrapeIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
669            }
670        }
671
672        if( bencLoaded )
673            tr_bencFree( &benc );
674    }
675
676    retry = updateAddresses( t, success );
677
678    /**
679    ***
680    **/
681
682    if( retry )
683        responseCode = 300;
684
685    if( 200 <= responseCode && responseCode <= 299 )
686    {
687        const int interval = t->scrapeIntervalSec + t->randOffset;
688        dbgmsg( t->name, "request succeeded. rescraping in %d seconds",
689                interval );
690        tr_ndbg( t->name, "request succeeded. rescraping in %d seconds",
691                 interval );
692        t->scrapeAt = time( NULL ) + interval;
693    }
694    else if( 300 <= responseCode && responseCode <= 399 )
695    {
696        const int interval = 5;
697        dbgmsg( t->name, "got a redirect. retrying in %d seconds", interval );
698        t->scrapeAt = time( NULL ) + interval;
699    }
700    else
701    {
702        const int interval = t->retryScrapeIntervalSec + t->randOffset;
703        dbgmsg(
704            t->name,
705            "Tracker responded to scrape with %ld.  Retrying in %d seconds.",
706            responseCode,  interval );
707        t->retryScrapeIntervalSec *= 2;
708        t->scrapeAt = time( NULL ) + interval;
709    }
710}
711
712/***
713****
714***/
715
716enum
717{
718    TR_REQ_STARTED,
719    TR_REQ_COMPLETED,
720    TR_REQ_STOPPED,
721    TR_REQ_PAUSED,     /* BEP 21 */
722    TR_REQ_REANNOUNCE,
723    TR_REQ_SCRAPE
724};
725
726struct tr_tracker_request
727{
728    int                 reqtype; /* TR_REQ_* */
729    int                 torrentId;
730    struct evbuffer   * url;
731    tr_web_done_func  * done_func;
732    tr_session *        session;
733};
734
735static void
736freeRequest( struct tr_tracker_request * req )
737{
738    evbuffer_free( req->url );
739    tr_free( req );
740}
741
742static void
743buildTrackerRequestURI( tr_tracker *       t,
744                        const tr_torrent * torrent,
745                        const char *       eventName,
746                        struct evbuffer *  buf )
747{
748    const int    isStopping = !strcmp( eventName, "stopped" );
749    const int    numwant = isStopping ? 0 : NUMWANT;
750    const char * ann = getCurrentAddressFromTorrent( t, torrent )->announce;
751
752    evbuffer_add_printf( buf, "%cinfo_hash=%s"
753                              "&peer_id=%s"
754                              "&port=%d"
755                              "&uploaded=%" PRIu64
756                              "&downloaded=%" PRIu64
757                              "&corrupt=%" PRIu64
758                              "&left=%" PRIu64
759                              "&compact=1"
760                              "&numwant=%d"
761                              "&key=%s",
762                              strchr( ann, '?' ) ? '&' : '?',
763                              t->escaped,
764                              t->peer_id,
765                              tr_sessionGetPeerPort( t->session ),
766                              torrent->uploadedCur,
767                              torrent->downloadedCur,
768                              torrent->corruptCur,
769                              tr_cpLeftUntilComplete( &torrent->completion ),
770                              numwant,
771                              t->key_param );
772
773    if( eventName && *eventName )
774        evbuffer_add_printf( buf, "&event=%s", eventName );
775
776    if( t->trackerID && *t->trackerID )
777        evbuffer_add_printf( buf, "&trackerid=%s", t->trackerID );
778
779}
780
781static struct tr_tracker_request*
782createRequest( tr_session * session,
783               tr_tracker * tracker,
784               int          reqtype )
785{
786    static const char* strings[] = { "started", "completed", "stopped", "paused", "", "err" };
787    const tr_torrent * torrent = tr_torrentFindFromHash( session, tracker->hash );
788    const tr_tracker_info * address = getCurrentAddressFromTorrent( tracker, torrent );
789    int isStopping;
790    struct tr_tracker_request * req;
791    struct evbuffer * url;
792
793    /* BEP 21: In order to tell the tracker that a peer is a partial seed, it MUST send
794     * an event=paused parameter in every announce while it is a partial seed. */
795    if( tr_cpGetStatus( &torrent->completion ) == TR_PARTIAL_SEED )
796        reqtype = TR_REQ_PAUSED;
797
798    isStopping = reqtype == TR_REQ_STOPPED;
799
800    url = evbuffer_new( );
801    evbuffer_add_printf( url, "%s", address->announce );
802    buildTrackerRequestURI( tracker, torrent, strings[reqtype], url );
803
804    req = tr_new0( struct tr_tracker_request, 1 );
805    req->session = session;
806    req->reqtype = reqtype;
807    req->done_func =  isStopping ? onStoppedResponse : onTrackerResponse;
808    req->url = url;
809    req->torrentId = tracker->torrentId;
810
811    return req;
812}
813
814static struct tr_tracker_request*
815createScrape( tr_session * session,
816              tr_tracker * tracker )
817{
818    const tr_tracker_info *     a = getCurrentAddress( tracker );
819    struct tr_tracker_request * req;
820    struct evbuffer *           url = evbuffer_new( );
821
822    evbuffer_add_printf( url, "%s%cinfo_hash=%s",
823                         a->scrape, strchr( a->scrape, '?' ) ? '&' : '?',
824                         tracker->escaped );
825
826    req = tr_new0( struct tr_tracker_request, 1 );
827    req->session = session;
828    req->reqtype = TR_REQ_SCRAPE;
829    req->url = url;
830    req->done_func = onScrapeResponse;
831    req->torrentId = tracker->torrentId;
832
833    return req;
834}
835
836struct tr_tracker_handle
837{
838    int         runningCount;
839    tr_timer *  pulseTimer;
840};
841
842static int trackerPulse( void * vsession );
843
844static void
845ensureGlobalsExist( tr_session * session )
846{
847    if( session->tracker == NULL )
848    {
849        session->tracker = tr_new0( struct tr_tracker_handle, 1 );
850        session->tracker->pulseTimer =
851            tr_timerNew( session, trackerPulse, session,
852                         PULSE_INTERVAL_MSEC );
853        dbgmsg( NULL, "creating tracker timer" );
854    }
855}
856
857void
858tr_trackerSessionClose( tr_session * session )
859{
860    if( session && session->tracker )
861    {
862        dbgmsg( NULL, "freeing tracker timer" );
863        tr_timerFree( &session->tracker->pulseTimer );
864        tr_free( session->tracker );
865        session->tracker = NULL;
866    }
867}
868
869/***
870****
871***/
872
873static void
874invokeRequest( void * vreq )
875{
876    struct tr_tracker_request * req = vreq;
877    tr_tracker * t = findTracker( req->session, req->torrentId );
878
879    if( t )
880    {
881        const time_t now = time( NULL );
882
883        if( req->reqtype == TR_REQ_SCRAPE )
884        {
885            t->lastScrapeTime = now;
886            t->scrapeAt = TR_TRACKER_BUSY;
887        }
888        else
889        {
890            t->lastAnnounceTime = now;
891            t->reannounceAt = TR_TRACKER_BUSY;
892            t->manualAnnounceAllowedAt = TR_TRACKER_BUSY;
893        }
894    }
895
896    ++req->session->tracker->runningCount;
897
898    tr_webRun( req->session,
899               (char*)EVBUFFER_DATA(req->url),
900               NULL,
901               req->done_func, tr_int2ptr( req->torrentId ) );
902
903    freeRequest( req );
904}
905
906static void
907enqueueScrape( tr_session * session,
908               tr_tracker * tracker )
909{
910    struct tr_tracker_request * req = createScrape( session, tracker );
911    tr_runInEventThread( session, invokeRequest, req );
912}
913
914static void
915enqueueRequest( tr_session * session,
916                tr_tracker * tracker,
917                int          reqtype )
918{
919    struct tr_tracker_request * req = createRequest( session, tracker, reqtype );
920    tr_runInEventThread( session, invokeRequest, req );
921}
922
923static int
924trackerPulse( void * vsession )
925{
926    tr_session *               session = vsession;
927    struct tr_tracker_handle * th = session->tracker;
928    tr_torrent *               tor;
929    const time_t               now = time( NULL );
930
931    if( !session->tracker )
932        return FALSE;
933
934    if( th->runningCount )
935        dbgmsg( NULL, "tracker pulse... %d running", th->runningCount );
936
937    /* upkeep: queue periodic rescrape / reannounce */
938    tor = NULL;
939    while(( tor = tr_torrentNext( session, tor )))
940    {
941        tr_tracker * t = tor->tracker;
942
943        if( ( t->scrapeAt > 1 )
944          && ( t->scrapeAt <= now )
945          && ( trackerSupportsScrape( t, tor ) ) )
946        {
947            t->scrapeAt = TR_TRACKER_BUSY;
948            enqueueScrape( session, t );
949        }
950
951        if( ( t->reannounceAt > 1 )
952          && ( t->reannounceAt <= now )
953          && ( t->isRunning ) )
954        {
955            t->reannounceAt = TR_TRACKER_BUSY;
956            t->manualAnnounceAllowedAt = TR_TRACKER_BUSY;
957            enqueueRequest( session, t, TR_REQ_REANNOUNCE );
958        }
959    }
960
961    if( th->runningCount )
962        dbgmsg( NULL, "tracker pulse after upkeep... %d running",
963                th->runningCount );
964
965    /* free the tracker manager if no torrents are left */
966    if( ( session->tracker )
967      && ( session->tracker->runningCount < 1 )
968      && ( tr_sessionCountTorrents( session ) == 0 ) )
969    {
970        tr_trackerSessionClose( session );
971    }
972
973    /* if there are still running torrents (as indicated by
974     * the existence of the tracker manager) then keep the
975     * trackerPulse() timer alive */
976    return session->tracker != NULL;
977}
978
979static void
980onReqDone( tr_session * session )
981{
982    if( session->tracker )
983    {
984        --session->tracker->runningCount;
985        dbgmsg( NULL, "decrementing runningCount to %d",
986                session->tracker->runningCount );
987        trackerPulse( session );
988    }
989}
990
991/***
992****  LIFE CYCLE
993***/
994
995static void
996generateKeyParam( char * msg,
997                  int    len )
998{
999    int          i;
1000    const char * pool = "abcdefghijklmnopqrstuvwxyz0123456789";
1001    const int    poolSize = strlen( pool );
1002
1003    for( i = 0; i < len; ++i )
1004        *msg++ = pool[tr_cryptoRandInt( poolSize )];
1005    *msg = '\0';
1006}
1007
1008static int
1009is_rfc2396_alnum( char ch )
1010{
1011    return ( '0' <= ch && ch <= '9' )
1012           || ( 'A' <= ch && ch <= 'Z' )
1013           || ( 'a' <= ch && ch <= 'z' );
1014}
1015
1016static void
1017escape( char *          out,
1018        const uint8_t * in,
1019        int             in_len )                     /* rfc2396 */
1020{
1021    const uint8_t *end = in + in_len;
1022
1023    while( in != end )
1024        if( is_rfc2396_alnum( *in ) )
1025            *out++ = (char) *in++;
1026        else
1027            out += tr_snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
1028
1029    *out = '\0';
1030}
1031
1032tr_tracker *
1033tr_trackerNew( const tr_torrent * torrent )
1034{
1035    const tr_info * info = &torrent->info;
1036    tr_tracker *    t;
1037
1038    ensureGlobalsExist( torrent->session );
1039
1040    t = tr_new0( tr_tracker, 1 );
1041    t->publisher                = TR_PUBLISHER_INIT;
1042    t->session                  = torrent->session;
1043    t->scrapeIntervalSec        = DEFAULT_SCRAPE_INTERVAL_SEC;
1044    t->retryScrapeIntervalSec   = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
1045    t->retryAnnounceIntervalSec = FIRST_ANNOUNCE_RETRY_INTERVAL_SEC;
1046    t->announceIntervalSec      = DEFAULT_ANNOUNCE_INTERVAL_SEC;
1047    t->announceMinIntervalSec   = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
1048    t->timesDownloaded          = -1;
1049    t->seederCount              = -1;
1050    t->downloaderCount          = -1;
1051    t->leecherCount             = -1;
1052    t->lastAnnounceResponse     = -1;
1053    t->lastScrapeResponse       = -1;
1054    t->manualAnnounceAllowedAt  = ~(time_t)0;
1055    t->name                     = tr_strdup( info->name );
1056    t->torrentId                = torrent->uniqueId;
1057    t->randOffset               = tr_cryptoRandInt( 30 );
1058    memcpy( t->hash, info->hash, SHA_DIGEST_LENGTH );
1059    escape( t->escaped, info->hash, SHA_DIGEST_LENGTH );
1060    generateKeyParam( t->key_param, KEYLEN );
1061
1062    t->trackerIndex = 0;
1063
1064    if( trackerSupportsScrape( t, torrent ) )
1065        t->scrapeAt = time( NULL ) + t->randOffset;
1066
1067    return t;
1068}
1069
1070static void
1071onTrackerFreeNow( void * vt )
1072{
1073    tr_tracker * t = vt;
1074
1075    tr_publisherDestruct( &t->publisher );
1076    tr_free( t->name );
1077    tr_free( t->trackerID );
1078    tr_free( t->peer_id );
1079
1080    tr_free( t );
1081}
1082
1083/***
1084****  PUBLIC
1085***/
1086
1087void
1088tr_trackerFree( tr_tracker * t )
1089{
1090    if( t )
1091        tr_runInEventThread( t->session, onTrackerFreeNow, t );
1092}
1093
1094tr_publisher_tag
1095tr_trackerSubscribe( tr_tracker *     t,
1096                     tr_delivery_func func,
1097                     void *           user_data )
1098{
1099    return tr_publisherSubscribe( &t->publisher, func, user_data );
1100}
1101
1102void
1103tr_trackerUnsubscribe( tr_tracker *     t,
1104                       tr_publisher_tag tag )
1105{
1106    if( t )
1107        tr_publisherUnsubscribe( &t->publisher, tag );
1108}
1109
1110const tr_tracker_info *
1111tr_trackerGetAddress( tr_tracker * t, const tr_torrent * torrent )
1112{
1113    return getCurrentAddressFromTorrent( t, torrent );
1114}
1115
1116time_t
1117tr_trackerGetManualAnnounceTime( const struct tr_tracker * t )
1118{
1119    return t->isRunning ? t->manualAnnounceAllowedAt : 0;
1120}
1121
1122int
1123tr_trackerCanManualAnnounce( const tr_tracker * t )
1124{
1125    const time_t allow = tr_trackerGetManualAnnounceTime( t );
1126
1127    return allow && ( allow <= time( NULL ) );
1128}
1129
1130void
1131tr_trackerGetCounts( const tr_tracker * t,
1132                     int              * setme_completedCount,
1133                     int              * setme_leecherCount,
1134                     int              * setme_seederCount,
1135                     int              * setme_downloaderCount )
1136{
1137    if( setme_completedCount )
1138        *setme_completedCount = t->timesDownloaded;
1139
1140    if( setme_leecherCount )
1141        *setme_leecherCount = t->leecherCount;
1142
1143    if( setme_seederCount )
1144        *setme_seederCount = t->seederCount;
1145
1146    if( setme_downloaderCount )
1147        *setme_downloaderCount = t->downloaderCount;
1148}
1149
1150void
1151tr_trackerStart( tr_tracker * t )
1152{
1153    if( t && !t->isRunning )
1154    {
1155        tr_torrent * tor;
1156
1157        /* change the peer-id */
1158        tr_free( t->peer_id );
1159        t->peer_id = tr_peerIdNew( );
1160        if(( tor = tr_torrentFindFromHash( t->session, t->hash ))) {
1161            tr_free( tor->peer_id );
1162            tor->peer_id = (uint8_t*) tr_strdup( t->peer_id );
1163        }
1164
1165        t->isRunning = 1;
1166        enqueueRequest( t->session, t, TR_REQ_STARTED );
1167    }
1168}
1169
1170void
1171tr_trackerReannounce( tr_tracker * t )
1172{
1173    enqueueRequest( t->session, t, TR_REQ_REANNOUNCE );
1174}
1175
1176void
1177tr_trackerCompleted( tr_tracker * t )
1178{
1179    enqueueRequest( t->session, t, TR_REQ_COMPLETED );
1180}
1181
1182void
1183tr_trackerStop( tr_tracker * t )
1184{
1185    if( t && t->isRunning )
1186    {
1187        t->isRunning = 0;
1188        t->reannounceAt = TR_TRACKER_STOPPED;
1189        t->manualAnnounceAllowedAt = TR_TRACKER_STOPPED;
1190        enqueueRequest( t->session, t, TR_REQ_STOPPED );
1191    }
1192}
1193
1194void
1195tr_trackerChangeMyPort( tr_tracker * t )
1196{
1197    if( t->isRunning )
1198        tr_trackerReannounce( t );
1199}
1200
1201void
1202tr_trackerStat( const tr_tracker * t,
1203                struct tr_stat *   setme )
1204{
1205    assert( t );
1206    assert( setme );
1207
1208    setme->lastScrapeTime = t->lastScrapeTime;
1209    setme->nextScrapeTime = t->scrapeAt;
1210    setme->lastAnnounceTime = t->lastAnnounceTime;
1211    setme->nextAnnounceTime = t->reannounceAt;
1212    setme->manualAnnounceTime = t->manualAnnounceAllowedAt;
1213
1214    if( t->lastScrapeResponse == -1 ) /* never been scraped */
1215        *setme->scrapeResponse = '\0';
1216    else
1217        tr_snprintf( setme->scrapeResponse,
1218                     sizeof( setme->scrapeResponse ),
1219                     "%s (%ld)",
1220                     tr_webGetResponseStr( t->lastScrapeResponse ),
1221                     t->lastScrapeResponse );
1222
1223    if( t->lastAnnounceResponse == -1 ) /* never been announced */
1224        *setme->announceResponse = '\0';
1225    else
1226        tr_snprintf( setme->announceResponse,
1227                     sizeof( setme->announceResponse ),
1228                     "%s (%ld)",
1229                     tr_webGetResponseStr( t->lastAnnounceResponse ),
1230                     t->lastAnnounceResponse );
1231}
1232
Note: See TracBrowser for help on using the repository browser.