source: trunk/libtransmission/tracker.c @ 7476

Last change on this file since 7476 was 7476, checked in by charles, 12 years ago

(trunk libT) #include "session.h" cleanup from wereHamster

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