source: trunk/libtransmission/tracker.c @ 7397

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

(trunk libT) add ipv6 support by jhujhiti. I think this is the largest user-contributed patch we've ever used... thanks jhujhiti :)

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