source: trunk/libtransmission/tracker.c @ 7231

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

(libT) re-apply jhujhiti's IPv6 patch. This merges in my tr_port cleanup, so any new bugs are mine :/

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