source: trunk/libtransmission/tracker.c @ 7266

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

(libT) #1557: handshake peer-id doesn't match the peer-id sent in the tracker announce

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