source: branches/scrape/libtransmission/tracker.c @ 1148

Last change on this file since 1148 was 1148, checked in by joshe, 15 years ago

Revert the 'many peer' thresholds and times.
Fix trackerid (still untested).
Misc cleanups.

  • Property svn:keywords set to Date Rev Author Id
File size: 19.9 KB
Line 
1/******************************************************************************
2 * $Id: tracker.c 1148 2006-12-02 01:29:04Z joshe $
3 *
4 * Copyright (c) 2005-2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include "transmission.h"
26
27struct tr_tracker_s
28{
29    tr_torrent_t * tor;
30
31    char         * id;
32    char         * trackerid;
33
34    char           started;
35    char           completed;
36    char           stopped;
37
38    int            interval;
39    int            minInterval;
40    int            scrapeInterval;
41    int            seeders;
42    int            leechers;
43    int            hasManyPeers;
44    int            complete;
45
46    uint64_t       dateTry;
47    uint64_t       dateOk;
48    uint64_t       dateScrape;
49    int            lastScrapeFailed;
50
51#define TC_ATTEMPT_NOREACH 1
52#define TC_ATTEMPT_ERROR   2
53#define TC_ATTEMPT_OK      4
54    char           lastAttempt;
55    int            scrapeNeeded;
56
57    tr_http_t    * http;
58    tr_http_t    * httpScrape;
59
60    int            bindPort;
61    int            newPort;
62};
63
64static tr_http_t * getQuery         ( tr_tracker_t * tc );
65static tr_http_t * getScrapeQuery   ( tr_tracker_t * tc );
66static void        readAnswer       ( tr_tracker_t * tc, const char *, int );
67static void        readScrapeAnswer ( tr_tracker_t * tc, const char *, int );
68static void        killHttp         ( tr_http_t ** http, tr_fd_t * fdlimit );
69
70tr_tracker_t * tr_trackerInit( tr_torrent_t * tor )
71{
72    tr_tracker_t * tc;
73
74    tc                 = calloc( 1, sizeof( tr_tracker_t ) );
75    tc->tor            = tor;
76    tc->id             = tor->id;
77
78    tc->started        = 1;
79
80    tc->interval       = 300;
81    tc->scrapeInterval = 600;
82    tc->seeders        = -1;
83    tc->leechers       = -1;
84    tc->complete       = -1;
85
86    tc->lastAttempt    = TC_ATTEMPT_NOREACH;
87
88    tc->bindPort       = *(tor->bindPort);
89    tc->newPort        = -1;
90
91    return tc;
92}
93
94static int shouldConnect( tr_tracker_t * tc )
95{
96    tr_torrent_t * tor = tc->tor;
97    uint64_t       now;
98
99    now = tr_date();
100
101    /* Unreachable tracker, try 10 seconds before trying again */
102    if( tc->lastAttempt == TC_ATTEMPT_NOREACH &&
103        now < tc->dateTry + 10000 )
104    {
105        return 0;
106    }
107
108    /* The tracker rejected us (like 4XX code, unauthorized IP...),
109       don't hammer it - we'll probably get the same answer next time
110       anyway */
111    if( tc->lastAttempt == TC_ATTEMPT_ERROR &&
112        now < tc->dateTry + 1000 * tc->interval )
113    {
114        return 0;
115    }
116
117    /* Do we need to send an event? */
118    if( tc->started || tc->completed || tc->stopped || 0 < tc->newPort )
119    {
120        return 1;
121    }
122
123    /* Should we try and get more peers? */
124    if( now > tc->dateOk + 1000 * tc->interval )
125    {
126        return 1;
127    }
128
129    /* If there is quite a lot of people on this torrent, stress
130       the tracker a bit until we get a decent number of peers */
131    if( tc->hasManyPeers && !tr_cpIsSeeding( tor->completion ) )
132    {
133        /* reannounce in 10 seconds if we have less than 5 peers */
134        if( tor->peerCount < 5 )
135        {
136            if( now > tc->dateOk + 1000 * MAX( 10, tc->minInterval ) )
137            {
138                return 1;
139            }
140        }
141        /* reannounce in 20 seconds if we have less than 10 peers */
142        else if( tor->peerCount < 10 )
143        {
144            if( now > tc->dateOk + 1000 * MAX( 20, tc->minInterval ) )
145            {
146                return 1;
147            }
148        }
149        /* reannounce in 30 seconds if we have less than 15 peers */
150        else if( tor->peerCount < 15 )
151        {
152            if( now > tc->dateOk + 1000 * MAX( 30, tc->minInterval ) )
153            {
154                return 1;
155            }
156        }
157    }
158
159    return 0;
160}
161
162static int shouldScrape( tr_tracker_t * tc )
163{
164    uint64_t now, interval;
165
166    /* scrape not supported */
167    if( !tc->tor->scrape[0] )
168    {
169        return 0;
170    }
171
172    now      = tr_date();
173    interval = 1000 * MAX( tc->scrapeInterval, 60 );
174
175    /* scrape half as often if there is no need to */
176    if( !tc->scrapeNeeded && !tc->lastScrapeFailed )
177    {
178        interval *= 2;
179    }
180
181    return now > tc->dateScrape + interval;
182}
183
184void tr_trackerChangePort( tr_tracker_t * tc, int port )
185{
186    tc->newPort = port;
187}
188
189int tr_trackerPulse( tr_tracker_t * tc )
190{
191    tr_torrent_t * tor = tc->tor;
192    tr_info_t    * inf = &tor->info;
193    const char   * data;
194    int            len;
195
196    if( ( NULL == tc->http ) && shouldConnect( tc ) )
197    {
198        if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) )
199        {
200            return 0;
201        }
202        tc->dateTry = tr_date();
203        tc->http = getQuery( tc );
204
205        tr_inf( "Tracker: connecting to %s:%d (%s)",
206                inf->trackerAddress, inf->trackerPort,
207                tc->started ? "sending 'started'" :
208                ( tc->completed ? "sending 'completed'" :
209                  ( tc->stopped ? "sending 'stopped'" :
210                    ( 0 < tc->newPort ? "sending 'stopped' to change port" :
211                      "getting peers" ) ) ) );
212    }
213
214    if( NULL != tc->http )
215    {
216        switch( tr_httpPulse( tc->http, &data, &len ) )
217        {
218            case TR_WAIT:
219                break;
220
221            case TR_ERROR:
222                killHttp( &tc->http, tor->fdlimit );
223                tc->dateTry = tr_date();
224                break;
225
226            case TR_OK:
227                readAnswer( tc, data, len );
228                killHttp( &tc->http, tor->fdlimit );
229                break;
230        }
231    }
232   
233    if( ( NULL == tc->httpScrape ) && shouldScrape( tc ) )
234    {
235        if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) )
236        {
237            return 0;
238        }
239        tc->dateScrape = tr_date();
240        tc->httpScrape = getScrapeQuery( tc );
241        tr_inf( "Scrape: sent http request to %s:%d",
242                    inf->trackerAddress, inf->trackerPort );
243    }
244
245    if( NULL != tc->httpScrape )
246    {
247        switch( tr_httpPulse( tc->httpScrape, &data, &len ) )
248        {
249            case TR_WAIT:
250                break;
251
252            case TR_ERROR:
253                killHttp( &tc->httpScrape, tor->fdlimit );
254                tc->lastScrapeFailed = 1;
255                break;
256
257            case TR_OK:
258                readScrapeAnswer( tc, data, len );
259                killHttp( &tc->httpScrape, tor->fdlimit );
260                break;
261        }
262    }
263
264    return 0;
265}
266
267void tr_trackerCompleted( tr_tracker_t * tc )
268{
269    tc->started   = 0;
270    tc->completed = 1;
271    tc->stopped   = 0;
272}
273
274void tr_trackerStopped( tr_tracker_t * tc )
275{
276    tr_torrent_t * tor = tc->tor;
277
278    /* If we are already sending a query at the moment, we need to
279       reconnect */
280    killHttp( &tc->http, tor->fdlimit );
281
282    tc->started   = 0;
283    tc->completed = 0;
284    tc->stopped   = 1;
285
286    /* Even if we have connected recently, reconnect right now */
287    tc->dateTry = 0;
288}
289
290void tr_trackerClose( tr_tracker_t * tc )
291{
292    tr_torrent_t * tor = tc->tor;
293
294    killHttp( &tc->http, tor->fdlimit );
295    killHttp( &tc->httpScrape, tor->fdlimit );
296    free( tc->trackerid );
297    free( tc );
298}
299
300static tr_http_t * getQuery( tr_tracker_t * tc )
301{
302    tr_torrent_t * tor = tc->tor;
303    tr_info_t    * inf = &tor->info;
304
305    char         * event, * trackerid, * idparam;
306    uint64_t       left;
307    uint64_t       down;
308    uint64_t       up;
309    char           start;
310    int            numwant = 50;
311
312    down = tor->downloadedCur;
313    up   = tor->uploadedCur;
314    if( tc->started )
315    {
316        event = "&event=started";
317        down  = 0;
318        up    = 0;
319
320        if( 0 < tc->newPort )
321        {
322            tc->bindPort = tc->newPort;
323            tc->newPort  = -1;
324        }
325    }
326    else if( tc->completed )
327    {
328        event = "&event=completed";
329    }
330    else if( tc->stopped || 0 < tc->newPort )
331    {
332        event = "&event=stopped";
333        numwant = 0;
334    }
335    else
336    {
337        event = "";
338    }
339
340    if( NULL == tc->trackerid )
341    {
342        trackerid = "";
343        idparam   = "";
344    }
345    else
346    {
347        trackerid = tc->trackerid;
348        idparam   = "&trackerid=";
349    }
350
351    start = ( strchr( inf->trackerAnnounce, '?' ) ? '&' : '?' );
352    left  = tr_cpLeftBytes( tor->completion );
353
354    return tr_httpClient( TR_HTTP_GET, inf->trackerAddress, inf->trackerPort,
355                          "%s%c"
356                          "info_hash=%s&"
357                          "peer_id=%s&"
358                          "port=%d&"
359                          "uploaded=%"PRIu64"&"
360                          "downloaded=%"PRIu64"&"
361                          "left=%"PRIu64"&"
362                          "compact=1&"
363                          "numwant=%d&"
364                          "key=%s"
365                          "%s%s"
366                          "%s",
367                          inf->trackerAnnounce, start, tor->escapedHashString,
368                          tc->id, tc->bindPort, up, down, left, numwant,
369                          tor->key, idparam, trackerid, event );
370}
371
372static tr_http_t * getScrapeQuery( tr_tracker_t * tc )
373{
374    tr_torrent_t * tor = tc->tor;
375    tr_info_t    * inf = &tor->info;
376
377    char           start;
378
379    start = ( strchr( tor->scrape, '?' ) ? '&' : '?' );
380
381    return tr_httpClient( TR_HTTP_GET, inf->trackerAddress, inf->trackerPort,
382                          "%s%c"
383                          "info_hash=%s",
384                          tor->scrape, start, tor->escapedHashString );
385}
386
387static void readAnswer( tr_tracker_t * tc, const char * data, int len )
388{
389    tr_torrent_t * tor = tc->tor;
390    int i;
391    int code;
392    benc_val_t   beAll;
393    benc_val_t * bePeers, * beFoo;
394    const uint8_t * body;
395    int bodylen, shouldfree, scrapeNeeded;
396
397    tc->dateTry = tr_date();
398    code = tr_httpResponseCode( data, len );
399    if( 0 > code )
400    {
401        /* We don't have a valid HTTP status line */
402        tr_inf( "Tracker: invalid HTTP status line" );
403        tc->lastAttempt = TC_ATTEMPT_NOREACH;
404        return;
405    }
406
407    if( !TR_HTTP_STATUS_OK( code ) )
408    {
409        /* we didn't get a 2xx status code */
410        tr_err( "Tracker: invalid HTTP status code: %i", code );
411        tc->lastAttempt = TC_ATTEMPT_ERROR;
412        return;
413    }
414
415    /* find the end of the http headers */
416    body = (uint8_t *) tr_httpParse( data, len, NULL );
417    if( NULL == body )
418    {
419        tr_err( "Tracker: could not find end of HTTP headers" );
420        tc->lastAttempt = TC_ATTEMPT_NOREACH;
421        return;
422    }
423    bodylen = len - ( body - (const uint8_t*)data );
424
425    /* Find and load the dictionary */
426    shouldfree = 0;
427    for( i = 0; i < bodylen; i++ )
428    {
429        if( !tr_bencLoad( &body[i], bodylen - i, &beAll, NULL ) )
430        {
431            shouldfree = 1;
432            break;
433        }
434    }
435
436    if( i >= bodylen )
437    {
438        if( tc->stopped || 0 < tc->newPort )
439        {
440            tc->lastAttempt = TC_ATTEMPT_OK;
441            goto nodict;
442        }
443        tr_err( "Tracker: no valid dictionary found in answer" );
444        tc->lastAttempt = TC_ATTEMPT_ERROR;
445        return;
446    }
447
448    // tr_bencPrint( &beAll );
449
450    if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) )
451    {
452        tr_err( "Tracker: %s", bePeers->val.s.s );
453        tor->error |= TR_ETRACKER;
454        snprintf( tor->trackerError, sizeof( tor->trackerError ),
455                  "%s", bePeers->val.s.s );
456        tc->lastAttempt = TC_ATTEMPT_ERROR;
457        goto cleanup;
458    }
459
460    tor->error &= ~TR_ETRACKER;
461    tc->lastAttempt = TC_ATTEMPT_OK;
462
463    /* Get the tracker interval, force to between
464       10 sec and 5 mins */
465    beFoo = tr_bencDictFind( &beAll, "interval" );
466    if( !beFoo || TYPE_INT != beFoo->type )
467    {
468        tr_err( "Tracker: no 'interval' field" );
469        goto cleanup;
470    }
471
472    tc->interval = beFoo->val.i;
473    tr_inf( "Tracker: interval = %d seconds", tc->interval );
474
475    tc->interval = MIN( tc->interval, 300 );
476    tc->interval = MAX( 10, tc->interval );
477
478    /* Get the tracker minimum interval, force to between
479       10 sec and 5 mins  */
480    beFoo = tr_bencDictFind( &beAll, "min interval" );
481    if( beFoo && TYPE_INT == beFoo->type )
482    {
483        tc->minInterval = beFoo->val.i;
484        tr_inf( "Tracker: min interval = %d seconds", tc->minInterval );
485
486        tc->minInterval = MIN( tc->minInterval, 300 );
487        tc->minInterval = MAX( 10, tc->minInterval );
488
489        if( tc->interval < tc->minInterval )
490        {
491            tc->interval = tc->minInterval;
492            tr_inf( "Tracker: 'interval' less than 'min interval', "
493                    "using 'min interval'" );
494        }
495    }
496    else
497    {
498        tc->minInterval = 0;
499        tr_inf( "Tracker: no 'min interval' field" );
500    }
501
502    scrapeNeeded = 0;
503    beFoo = tr_bencDictFind( &beAll, "complete" );
504    if( beFoo && TYPE_INT == beFoo->type )
505    {
506        tc->seeders = beFoo->val.i;
507    }
508    else
509    {
510        scrapeNeeded = 1;
511    }
512
513    beFoo = tr_bencDictFind( &beAll, "incomplete" );
514    if( beFoo && TYPE_INT == beFoo->type )
515    {
516        tc->leechers = beFoo->val.i;
517    }
518    else
519    {
520        scrapeNeeded = 1;
521    }
522
523    tc->scrapeNeeded = scrapeNeeded;
524    if( !scrapeNeeded )
525    {
526        tc->hasManyPeers = ( tc->seeders + tc->leechers >= 50 );
527    }
528
529    beFoo = tr_bencDictFind( &beAll, "tracker id" );
530    if( beFoo )
531    {
532        free( tc->trackerid );
533        tc->trackerid = strdup( beFoo->val.s.s );
534        tr_inf( "Tracker: tracker id = %s", tc->trackerid );
535    }
536
537    bePeers = tr_bencDictFind( &beAll, "peers" );
538    if( !bePeers )
539    {
540        if( tc->stopped || 0 < tc->newPort )
541        {
542            goto nodict;
543        }
544        tr_err( "Tracker: no \"peers\" field" );
545        goto cleanup;
546    }
547
548    if( bePeers->type & TYPE_LIST )
549    {
550        char * ip;
551        int    port;
552
553        /* Original protocol */
554        tr_inf( "Tracker: got %d peers", bePeers->val.l.count );
555
556        for( i = 0; i < bePeers->val.l.count; i++ )
557        {
558            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "ip" );
559            if( !beFoo )
560                continue;
561            ip = beFoo->val.s.s;
562            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "port" );
563            if( !beFoo )
564                continue;
565            port = beFoo->val.i;
566
567            tr_peerAddOld( tor, ip, port );
568        }
569
570        if( bePeers->val.l.count >= 50 )
571        {
572            tc->hasManyPeers = 1;
573        }
574    }
575    else if( bePeers->type & TYPE_STR )
576    {
577        struct in_addr addr;
578        in_port_t      port;
579
580        /* "Compact" extension */
581        if( bePeers->val.s.i % 6 )
582        {
583            tr_err( "Tracker: \"peers\" of size %d",
584                    bePeers->val.s.i );
585            tr_lockUnlock( &tor->lock );
586            goto cleanup;
587        }
588
589        tr_inf( "Tracker: got %d peers", bePeers->val.s.i / 6 );
590        for( i = 0; i < bePeers->val.s.i / 6; i++ )
591        {
592            memcpy( &addr, &bePeers->val.s.s[6*i],   4 );
593            memcpy( &port, &bePeers->val.s.s[6*i+4], 2 );
594
595            tr_peerAddCompact( tor, addr, port );
596        }
597
598        if( bePeers->val.s.i / 6 >= 50 )
599        {
600            tc->hasManyPeers = 1;
601        }
602    }
603
604nodict:
605    /* Success */
606    tc->started   = 0;
607    tc->completed = 0;
608    tc->dateOk    = tr_date();
609
610    if( tc->stopped )
611    {
612        tor->status = TR_STATUS_STOPPED;
613        tc->stopped = 0;
614    }
615    else if( 0 < tc->newPort )
616    {
617        tc->started  = 1;
618    }
619
620cleanup:
621    if( shouldfree )
622    {
623        tr_bencFree( &beAll );
624    }
625}
626
627static void readScrapeAnswer( tr_tracker_t * tc, const char * data, int len )
628{
629    int code;
630    const uint8_t * body;
631    int bodylen, ii;
632    benc_val_t scrape, * val1, * val2;
633
634    code = tr_httpResponseCode( data, len );
635    if( 0 > code )
636    {
637        /* We don't have a valid HTTP status line */
638        tr_inf( "Scrape: invalid HTTP status line" );
639        tc->lastScrapeFailed = 1;
640        return;
641    }
642
643    if( !TR_HTTP_STATUS_OK( code ) )
644    {
645        /* we didn't get a 2xx status code */
646        tr_err( "Scrape: invalid HTTP status code: %i", code );
647        tc->lastScrapeFailed = 1;
648        return;
649    }
650
651    /* find the end of the http headers */
652    body = (uint8_t *) tr_httpParse( data, len, NULL );
653    if( NULL == body )
654    {
655        tr_err( "Scrape: could not find end of HTTP headers" );
656        tc->lastScrapeFailed = 1;
657        return;
658    }
659
660    tc->lastScrapeFailed = 0;
661    bodylen = len - ( body - (const uint8_t*)data );
662
663    for( ii = 0; ii < bodylen; ii++ )
664    {
665        if( !tr_bencLoad( body + ii, bodylen - ii, &scrape, NULL ) )
666        {
667            break;
668        }
669    }
670    if( ii >= bodylen )
671    {
672        return;
673    }
674
675    val1 = tr_bencDictFind( &scrape, "files" );
676    if( !val1 )
677    {
678        tr_bencFree( &scrape );
679        return;
680    }
681    val1 = &val1->val.l.vals[1];
682    if( !val1 )
683    {
684        tr_bencFree( &scrape );
685        return;
686    }
687   
688    val2 = tr_bencDictFind( val1, "complete" );
689    if( !val2 )
690    {
691        tr_bencFree( &scrape );
692        return;
693    }
694    tc->seeders = val2->val.i;
695   
696    val2 = tr_bencDictFind( val1, "incomplete" );
697    if( !val2 )
698    {
699        tr_bencFree( &scrape );
700        return;
701    }
702    tc->leechers = val2->val.i;
703   
704    val2 = tr_bencDictFind( val1, "downloaded" );
705    if( !val2 )
706    {
707        tr_bencFree( &scrape );
708        return;
709    }
710    tc->complete = val2->val.i;
711   
712    val2 = tr_bencDictFind( val1, "flags" );
713    if( val2 )
714    {
715        val2 = tr_bencDictFind( val2, "min_request_interval" );
716        if( val2 )
717        {
718            tc->scrapeInterval = val2->val.i;
719        }
720    }
721   
722    tc->hasManyPeers = ( tc->seeders + tc->leechers >= 50 );
723   
724    tr_bencFree( &scrape );
725}
726
727int tr_trackerSeeders( tr_tracker_t * tc )
728{
729    if( !tc )
730    {
731        return -1;
732    }
733    return tc->seeders;
734}
735
736int tr_trackerLeechers( tr_tracker_t * tc )
737{
738    if( !tc )
739    {
740        return -1;
741    }
742    return tc->leechers;
743}
744
745int tr_trackerDownloaded( tr_tracker_t * tc )
746{
747    if( !tc )
748    {
749        return -1;
750    }
751    return tc->complete;
752}
753
754/* Blocking version */
755int tr_trackerScrape( tr_torrent_t * tor, int * s, int * l, int * d )
756{
757    tr_tracker_t * tc;
758    tr_http_t    * http;
759    const char   * data;
760    int            len;
761    int            ret;
762
763    if( !tor->scrape[0] )
764    {
765        return 1;
766    }
767
768    tc = tr_trackerInit( tor );
769    http = getScrapeQuery( tc );
770
771    for( data = NULL; !data; tr_wait( 10 ) )
772    {
773        switch( tr_httpPulse( http, &data, &len ) )
774        {
775            case TR_WAIT:
776                break;
777
778            case TR_ERROR:
779                goto scrapeDone;
780
781            case TR_OK:
782                readScrapeAnswer( tc, data, len );
783                goto scrapeDone;
784        }
785    }
786
787scrapeDone:
788    tr_httpClose( http );
789
790    ret = 1;
791    if( tc->seeders > -1 && tc->leechers > -1 && tc->complete > -1 )
792    {
793        *s = tc->seeders;
794        *l = tc->leechers;
795        *d = tc->complete;
796        ret = 0;
797    }
798
799    tr_trackerClose( tc );
800    return ret;
801}
802
803static void killHttp( tr_http_t ** http, tr_fd_t * fdlimit )
804{
805    if( NULL != *http )
806    {
807        tr_httpClose( *http );
808        tr_fdSocketClosed( fdlimit, 1 );
809        *http = NULL;
810    }
811}
Note: See TracBrowser for help on using the repository browser.