source: trunk/libtransmission/tracker.c @ 8672

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

(trunk libT) #2199: DHT should not announce stopped torrents

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