source: branches/1.3x/libtransmission/tracker.c @ 7282

Last change on this file since 7282 was 7282, checked in by charles, 14 years ago

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