source: trunk/libtransmission/tracker.c @ 931

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

Fix possible uint64_t underflow which could cause insanely huge (16,000,000 TB)

upload and/or download totals to be reported to the tracker.

  • Property svn:keywords set to Date Rev Author Id
File size: 14.9 KB
Line 
1/******************************************************************************
2 * $Id: tracker.c 931 2006-09-26 22:36: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
33    char           started;
34    char           completed;
35    char           stopped;
36
37    int            interval;
38    int            seeders;
39    int            leechers;
40    int            hasManyPeers;
41
42    uint64_t       dateTry;
43    uint64_t       dateOk;
44
45#define TC_ATTEMPT_NOREACH 1
46#define TC_ATTEMPT_ERROR   2
47#define TC_ATTEMPT_OK      4
48    char           lastAttempt;
49
50    tr_http_t    * http;
51
52    int            bindPort;
53    int            newPort;
54};
55
56static tr_http_t * getQuery   ( tr_tracker_t * tc );
57static void        readAnswer ( tr_tracker_t * tc, const char *, int );
58
59tr_tracker_t * tr_trackerInit( tr_torrent_t * tor )
60{
61    tr_tracker_t * tc;
62
63    tc           = calloc( 1, sizeof( tr_tracker_t ) );
64    tc->tor      = tor;
65    tc->id       = tor->id;
66
67    tc->started  = 1;
68
69    tc->interval = 300;
70    tc->seeders  = -1;
71    tc->leechers = -1;
72
73    tc->lastAttempt = TC_ATTEMPT_NOREACH;
74
75    tc->bindPort = *(tor->bindPort);
76    tc->newPort  = -1;
77
78    return tc;
79}
80
81static int shouldConnect( tr_tracker_t * tc )
82{
83    uint64_t now = tr_date();
84
85    /* Unreachable tracker, try 10 seconds before trying again */
86    if( tc->lastAttempt == TC_ATTEMPT_NOREACH &&
87        now < tc->dateTry + 10000 )
88    {
89        return 0;
90    }
91
92    /* The tracker rejected us (like 4XX code, unauthorized IP...),
93       don't hammer it - we'll probably get the same answer next time
94       anyway */
95    if( tc->lastAttempt == TC_ATTEMPT_ERROR &&
96        now < tc->dateTry + 1000 * tc->interval )
97    {
98        return 0;
99    }
100
101    /* Do we need to send an event? */
102    if( tc->started || tc->completed || tc->stopped || 0 < tc->newPort )
103    {
104        return 1;
105    }
106
107    /* Should we try and get more peers? */
108    if( now > tc->dateOk + 1000 * tc->interval )
109    {
110        return 1;
111    }
112
113    /* If there is quite a lot of people on this torrent, stress
114       the tracker a bit until we get a decent number of peers */
115    if( tc->hasManyPeers )
116    {
117        if( tc->tor->peerCount < 5 && now > tc->dateOk + 10000 )
118        {
119            return 1;
120        }
121        if( tc->tor->peerCount < 10 && now > tc->dateOk + 20000 )
122        {
123            return 1;
124        }
125        if( tc->tor->peerCount < 15 && now > tc->dateOk + 30000 )
126        {
127            return 1;
128        }
129    }
130
131    return 0;
132}
133
134void tr_trackerChangePort( tr_tracker_t * tc, int port )
135{
136    tc->newPort = port;
137}
138
139int tr_trackerPulse( tr_tracker_t * tc )
140{
141    tr_torrent_t * tor = tc->tor;
142    tr_info_t    * inf = &tor->info;
143    const char   * data;
144    int            len;
145
146    if( ( NULL == tc->http ) && shouldConnect( tc ) )
147    {
148        if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) )
149        {
150            return 0;
151        }
152        tc->dateTry = tr_date();
153        tc->http = getQuery( tc );
154
155        tr_inf( "Tracker: connecting to %s:%d (%s)",
156                inf->trackerAddress, inf->trackerPort,
157                tc->started ? "sending 'started'" :
158                ( tc->completed ? "sending 'completed'" :
159                  ( tc->stopped ? "sending 'stopped'" :
160                    ( 0 < tc->newPort ? "sending 'stopped' to change port" :
161                      "getting peers" ) ) ) );
162    }
163
164    if( NULL != tc->http )
165    {
166        switch( tr_httpPulse( tc->http, &data, &len ) )
167        {
168            case TR_WAIT:
169                return 0;
170
171            case TR_ERROR:
172                tr_httpClose( tc->http );
173                tr_fdSocketClosed( tor->fdlimit, 1 );
174                tc->http    = NULL;
175                tc->dateTry = tr_date();
176                return 0;
177
178            case TR_OK:
179                readAnswer( tc, data, len );
180                tr_httpClose( tc->http );
181                tc->http = NULL;
182                tr_fdSocketClosed( tor->fdlimit, 1 );
183                break;
184        }
185    }
186
187    return 0;
188}
189
190void tr_trackerCompleted( tr_tracker_t * tc )
191{
192    tc->started   = 0;
193    tc->completed = 1;
194    tc->stopped   = 0;
195}
196
197void tr_trackerStopped( tr_tracker_t * tc )
198{
199    tr_torrent_t * tor = tc->tor;
200
201    if( NULL != tc->http )
202    {
203        /* If we are already sendy a query at the moment, we need to
204           reconnect */
205        tr_httpClose( tc->http );
206        tc->http = NULL;
207        tr_fdSocketClosed( tor->fdlimit, 1 );
208    }
209
210    tc->started   = 0;
211    tc->completed = 0;
212    tc->stopped   = 1;
213
214    /* Even if we have connected recently, reconnect right now */
215    tc->dateTry = 0;
216}
217
218void tr_trackerClose( tr_tracker_t * tc )
219{
220    tr_torrent_t * tor = tc->tor;
221
222    if( NULL != tc->http )
223    {
224        tr_httpClose( tc->http );
225        tr_fdSocketClosed( tor->fdlimit, 1 );
226    }
227    free( tc );
228}
229
230static tr_http_t * getQuery( tr_tracker_t * tc )
231{
232    tr_torrent_t * tor = tc->tor;
233    tr_info_t    * inf = &tor->info;
234
235    char         * event;
236    uint64_t       left;
237    uint64_t       down;
238    uint64_t       up;
239
240    down = tor->downloadedCur;
241    up = tor->uploadedCur;
242    if( tc->started )
243    {
244        event = "&event=started";
245        down = up = 0;
246       
247        if( 0 < tc->newPort )
248        {
249            tc->bindPort = tc->newPort;
250            tc->newPort = -1;
251        }
252    }
253    else if( tc->completed )
254    {
255        event = "&event=completed";
256    }
257    else if( tc->stopped || 0 < tc->newPort )
258    {
259        event = "&event=stopped";
260    }
261    else
262    {
263        event = "";
264    }
265
266    left = tr_cpLeftBytes( tor->completion );
267
268    return tr_httpClient( TR_HTTP_GET, inf->trackerAddress,
269                          inf->trackerPort,
270                          "%s?"
271                          "info_hash=%s&"
272                          "peer_id=%s&"
273                          "port=%d&"
274                          "uploaded=%"PRIu64"&"
275                          "downloaded=%"PRIu64"&"
276                          "left=%"PRIu64"&"
277                          "compact=1&"
278                          "numwant=50&"
279                          "key=%s"
280                          "%s ",
281                          inf->trackerAnnounce, tor->hashString, tc->id,
282                          tc->bindPort, up, down, left, tor->key, event );
283}
284
285static void readAnswer( tr_tracker_t * tc, const char * data, int len )
286{
287    tr_torrent_t * tor = tc->tor;
288    int i;
289    int code;
290    benc_val_t   beAll;
291    benc_val_t * bePeers, * beFoo;
292    const uint8_t * body;
293    int bodylen;
294    int shouldfree;
295
296    tc->dateTry = tr_date();
297    code = tr_httpResponseCode( data, len );
298    if( 0 > code )
299    {
300        /* We don't have a valid HTTP status line */
301        tr_inf( "Tracker: invalid HTTP status line" );
302        tc->lastAttempt = TC_ATTEMPT_NOREACH;
303        return;
304    }
305
306    if( !TR_HTTP_STATUS_OK( code ) )
307    {
308        /* we didn't get a 2xx status code */
309        tr_err( "Tracker: invalid HTTP status code: %i", code );
310        tc->lastAttempt = TC_ATTEMPT_ERROR;
311        return;
312    }
313
314    /* find the end of the http headers */
315    body = (uint8_t *) tr_httpParse( data, len, NULL );
316    if( NULL == body )
317    {
318        tr_err( "Tracker: could not find end of HTTP headers" );
319        tc->lastAttempt = TC_ATTEMPT_NOREACH;
320        return;
321    }
322    bodylen = len - (body - (const uint8_t*)data);
323
324    /* Find and load the dictionary */
325    shouldfree = 0;
326    for( i = 0; i < bodylen; i++ )
327    {
328        if( !tr_bencLoad( &body[i], bodylen - i, &beAll, NULL ) )
329        {
330            shouldfree = 1;
331            break;
332        }
333    }
334
335    if( i >= bodylen )
336    {
337        if( tc->stopped || 0 < tc->newPort )
338        {
339            tc->lastAttempt = TC_ATTEMPT_OK;
340            goto nodict;
341        }
342        tr_err( "Tracker: no valid dictionary found in answer" );
343        tc->lastAttempt = TC_ATTEMPT_ERROR;
344        return;
345    }
346
347    // tr_bencPrint( &beAll );
348
349    if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) )
350    {
351        tr_err( "Tracker: %s", bePeers->val.s.s );
352        tor->error |= TR_ETRACKER;
353        snprintf( tor->trackerError, sizeof( tor->trackerError ),
354                  "%s", bePeers->val.s.s );
355        tc->lastAttempt = TC_ATTEMPT_ERROR;
356        goto cleanup;
357    }
358
359    tor->error &= ~TR_ETRACKER;
360    tc->lastAttempt = TC_ATTEMPT_OK;
361
362    if( !tc->interval )
363    {
364        /* Get the tracker interval, ignore it if it is not between
365           10 sec and 5 mins */
366        if( !( beFoo = tr_bencDictFind( &beAll, "interval" ) ) ||
367            !( beFoo->type & TYPE_INT ) )
368        {
369            tr_err( "Tracker: no 'interval' field" );
370            goto cleanup;
371        }
372
373        tc->interval = beFoo->val.i;
374        tc->interval = MIN( tc->interval, 300 );
375        tc->interval = MAX( 10, tc->interval );
376
377        tr_inf( "Tracker: interval = %d seconds", tc->interval );
378    }
379
380    if( ( beFoo = tr_bencDictFind( &beAll, "complete" ) ) &&
381        ( beFoo->type & TYPE_INT ) )
382    {
383        tc->seeders = beFoo->val.i;
384    }
385    if( ( beFoo = tr_bencDictFind( &beAll, "incomplete" ) ) &&
386        ( beFoo->type & TYPE_INT ) )
387    {
388        tc->leechers = beFoo->val.i;
389    }
390    if( tc->seeders + tc->leechers >= 50 )
391    {
392        tc->hasManyPeers = 1;
393    }
394
395    if( !( bePeers = tr_bencDictFind( &beAll, "peers" ) ) )
396    {
397        if( tc->stopped || 0 < tc->newPort )
398        {
399            goto nodict;
400        }
401        tr_err( "Tracker: no \"peers\" field" );
402        goto cleanup;
403    }
404
405    if( bePeers->type & TYPE_LIST )
406    {
407        char * ip;
408        int    port;
409
410        /* Original protocol */
411        tr_inf( "Tracker: got %d peers", bePeers->val.l.count );
412
413        for( i = 0; i < bePeers->val.l.count; i++ )
414        {
415            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "ip" );
416            if( !beFoo )
417                continue;
418            ip = beFoo->val.s.s;
419            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "port" );
420            if( !beFoo )
421                continue;
422            port = beFoo->val.i;
423
424            tr_peerAddOld( tor, ip, port );
425        }
426
427        if( bePeers->val.l.count >= 50 )
428        {
429            tc->hasManyPeers = 1;
430        }
431    }
432    else if( bePeers->type & TYPE_STR )
433    {
434        struct in_addr addr;
435        in_port_t      port;
436
437        /* "Compact" extension */
438        if( bePeers->val.s.i % 6 )
439        {
440            tr_err( "Tracker: \"peers\" of size %d",
441                    bePeers->val.s.i );
442            tr_lockUnlock( &tor->lock );
443            goto cleanup;
444        }
445
446        tr_inf( "Tracker: got %d peers", bePeers->val.s.i / 6 );
447        for( i = 0; i < bePeers->val.s.i / 6; i++ )
448        {
449            memcpy( &addr, &bePeers->val.s.s[6*i],   4 );
450            memcpy( &port, &bePeers->val.s.s[6*i+4], 2 );
451
452            tr_peerAddCompact( tor, addr, port );
453        }
454
455        if( bePeers->val.s.i / 6 >= 50 )
456        {
457            tc->hasManyPeers = 1;
458        }
459    }
460
461nodict:
462    /* Success */
463    tc->started   = 0;
464    tc->completed = 0;
465    tc->dateOk    = tr_date();
466
467    if( tc->stopped )
468    {
469        tor->status = TR_STATUS_STOPPED;
470        tc->stopped = 0;
471    }
472    else if( 0 < tc->newPort )
473    {
474        tc->started  = 1;
475    }
476
477cleanup:
478    if( shouldfree )
479    {
480        tr_bencFree( &beAll );
481    }
482}
483
484int tr_trackerScrape( tr_torrent_t * tor, int * seeders, int * leechers )
485{
486    tr_info_t * inf = &tor->info;
487
488    tr_http_t  * http;
489    const char * data, * body;
490    int          datalen, bodylen;
491    int          code, ii;
492    benc_val_t   scrape, * val1, * val2;
493
494    if( !tor->scrape[0] )
495    {
496        /* scrape not supported */
497        return 1;
498    }
499
500    http = tr_httpClient( TR_HTTP_GET, inf->trackerAddress, inf->trackerPort,
501                          "%s?info_hash=%s", tor->scrape, tor->hashString );
502
503    data = NULL;
504    while( NULL == data )
505    {
506        switch( tr_httpPulse( http, &data, &datalen ) )
507        {
508            case TR_WAIT:
509                break;
510
511            case TR_ERROR:
512                tr_httpClose( http );
513                return 1;
514
515            case TR_OK:
516                if( NULL == data || 0 >= datalen )
517                {
518                    tr_httpClose( http );
519                    return 1;
520                }
521                break;
522        }
523        tr_wait( 10 );
524    }
525
526    code = tr_httpResponseCode( data, datalen );
527    if( !TR_HTTP_STATUS_OK( code ) )
528    {
529        tr_httpClose( http );
530        return 1;
531    }
532
533    body = tr_httpParse( data, datalen , NULL );
534    if( NULL == body )
535    {
536        tr_httpClose( http );
537        return 1;
538    }
539    bodylen = datalen - ( body - data );
540
541    for( ii = 0; ii < bodylen - 8; ii++ )
542    {
543        if( !memcmp( body + ii, "d5:files", 8 ) )
544        {
545            break;
546        }
547    }
548    if( ii >= bodylen - 8 )
549    {
550        tr_httpClose( http );
551        return 1;
552    }
553    if( tr_bencLoad( body + ii, bodylen - ii, &scrape, NULL ) )
554    {
555        tr_httpClose( http );
556        return 1;
557    }
558
559    val1 = tr_bencDictFind( &scrape, "files" );
560    if( !val1 )
561    {
562        tr_bencFree( &scrape );
563        tr_httpClose( http );
564        return 1;
565    }
566    val1 = &val1->val.l.vals[1];
567    if( !val1 )
568    {
569        tr_bencFree( &scrape );
570        tr_httpClose( http );
571        return 1;
572    }
573    val2 = tr_bencDictFind( val1, "complete" );
574    if( !val2 )
575    {
576        tr_bencFree( &scrape );
577        tr_httpClose( http );
578        return 1;
579    }
580    *seeders = val2->val.i;
581    val2 = tr_bencDictFind( val1, "incomplete" );
582    if( !val2 )
583    {
584        tr_bencFree( &scrape );
585        tr_httpClose( http );
586        return 1;
587    }
588    *leechers = val2->val.i;
589    tr_bencFree( &scrape );
590    tr_httpClose( http );
591
592    return 0;
593}
594
595int tr_trackerSeeders( tr_tracker_t * tc )
596{
597    if( !tc )
598    {
599        return -1;
600    }
601    return tc->seeders;
602}
603
604int tr_trackerLeechers( tr_tracker_t * tc )
605{
606    if( !tc )
607    {
608        return -1;
609    }
610    return tc->leechers;
611}
Note: See TracBrowser for help on using the repository browser.