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

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

Minor style cleanups, no functional changes.

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