source: trunk/libtransmission/tracker.c @ 5738

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

experimental speed tweaks:
(1) increase the per-peer read buffer to the size of a block message
(2) pulse the peers more often
(3) reduce the tracker delay on startup

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