source: branches/1.4x/libtransmission/tracker.c @ 7100

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

(libT) #1447: Tracker request failed. Got HTTP Status Code: 0 (No Response)

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