source: trunk/libtransmission/tracker.c @ 1059

Last change on this file since 1059 was 1059, checked in by livings124, 15 years ago

Don't request more peers if stopping (or changing ports).

  • Property svn:keywords set to Date Rev Author Id
File size: 15.1 KB
Line 
1/******************************************************************************
2 * $Id: tracker.c 1059 2006-11-09 04:38:32Z livings124 $
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    char         * start;
240    int            numwant = 50;
241
242    down = tor->downloadedCur;
243    up = tor->uploadedCur;
244    if( tc->started )
245    {
246        event = "&event=started";
247        down = up = 0;
248       
249        if( 0 < tc->newPort )
250        {
251            tc->bindPort = tc->newPort;
252            tc->newPort = -1;
253        }
254    }
255    else if( tc->completed )
256    {
257        event = "&event=completed";
258    }
259    else if( tc->stopped || 0 < tc->newPort )
260    {
261        event = "&event=stopped";
262        numwant = 0;
263    }
264    else
265    {
266        event = "";
267    }
268
269    if( NULL == strchr( inf->trackerAnnounce, '?' ) )
270    {
271        start = "?";
272    }
273    else
274    {
275        start = "&";
276    }
277
278    left = tr_cpLeftBytes( tor->completion );
279
280    return tr_httpClient( TR_HTTP_GET, inf->trackerAddress,
281                          inf->trackerPort,
282                          "%s%s"
283                          "info_hash=%s&"
284                          "peer_id=%s&"
285                          "port=%d&"
286                          "uploaded=%"PRIu64"&"
287                          "downloaded=%"PRIu64"&"
288                          "left=%"PRIu64"&"
289                          "compact=1&"
290                          "numwant=%d&"
291                          "key=%s"
292                          "%s",
293                          inf->trackerAnnounce, start, tor->hashString, tc->id,
294                          tc->bindPort, up, down, left, numwant, tor->key, event );
295}
296
297static void readAnswer( tr_tracker_t * tc, const char * data, int len )
298{
299    tr_torrent_t * tor = tc->tor;
300    int i;
301    int code;
302    benc_val_t   beAll;
303    benc_val_t * bePeers, * beFoo;
304    const uint8_t * body;
305    int bodylen;
306    int shouldfree;
307
308    tc->dateTry = tr_date();
309    code = tr_httpResponseCode( data, len );
310    if( 0 > code )
311    {
312        /* We don't have a valid HTTP status line */
313        tr_inf( "Tracker: invalid HTTP status line" );
314        tc->lastAttempt = TC_ATTEMPT_NOREACH;
315        return;
316    }
317
318    if( !TR_HTTP_STATUS_OK( code ) )
319    {
320        /* we didn't get a 2xx status code */
321        tr_err( "Tracker: invalid HTTP status code: %i", code );
322        tc->lastAttempt = TC_ATTEMPT_ERROR;
323        return;
324    }
325
326    /* find the end of the http headers */
327    body = (uint8_t *) tr_httpParse( data, len, NULL );
328    if( NULL == body )
329    {
330        tr_err( "Tracker: could not find end of HTTP headers" );
331        tc->lastAttempt = TC_ATTEMPT_NOREACH;
332        return;
333    }
334    bodylen = len - (body - (const uint8_t*)data);
335
336    /* Find and load the dictionary */
337    shouldfree = 0;
338    for( i = 0; i < bodylen; i++ )
339    {
340        if( !tr_bencLoad( &body[i], bodylen - i, &beAll, NULL ) )
341        {
342            shouldfree = 1;
343            break;
344        }
345    }
346
347    if( i >= bodylen )
348    {
349        if( tc->stopped || 0 < tc->newPort )
350        {
351            tc->lastAttempt = TC_ATTEMPT_OK;
352            goto nodict;
353        }
354        tr_err( "Tracker: no valid dictionary found in answer" );
355        tc->lastAttempt = TC_ATTEMPT_ERROR;
356        return;
357    }
358
359    // tr_bencPrint( &beAll );
360
361    if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) )
362    {
363        tr_err( "Tracker: %s", bePeers->val.s.s );
364        tor->error |= TR_ETRACKER;
365        snprintf( tor->trackerError, sizeof( tor->trackerError ),
366                  "%s", bePeers->val.s.s );
367        tc->lastAttempt = TC_ATTEMPT_ERROR;
368        goto cleanup;
369    }
370
371    tor->error &= ~TR_ETRACKER;
372    tc->lastAttempt = TC_ATTEMPT_OK;
373
374    if( !tc->interval )
375    {
376        /* Get the tracker interval, ignore it if it is not between
377           10 sec and 5 mins */
378        if( !( beFoo = tr_bencDictFind( &beAll, "interval" ) ) ||
379            !( beFoo->type & TYPE_INT ) )
380        {
381            tr_err( "Tracker: no 'interval' field" );
382            goto cleanup;
383        }
384
385        tc->interval = beFoo->val.i;
386        tc->interval = MIN( tc->interval, 300 );
387        tc->interval = MAX( 10, tc->interval );
388
389        tr_inf( "Tracker: interval = %d seconds", tc->interval );
390    }
391
392    if( ( beFoo = tr_bencDictFind( &beAll, "complete" ) ) &&
393        ( beFoo->type & TYPE_INT ) )
394    {
395        tc->seeders = beFoo->val.i;
396    }
397    if( ( beFoo = tr_bencDictFind( &beAll, "incomplete" ) ) &&
398        ( beFoo->type & TYPE_INT ) )
399    {
400        tc->leechers = beFoo->val.i;
401    }
402    if( tc->seeders + tc->leechers >= 50 )
403    {
404        tc->hasManyPeers = 1;
405    }
406
407    if( !( bePeers = tr_bencDictFind( &beAll, "peers" ) ) )
408    {
409        if( tc->stopped || 0 < tc->newPort )
410        {
411            goto nodict;
412        }
413        tr_err( "Tracker: no \"peers\" field" );
414        goto cleanup;
415    }
416
417    if( bePeers->type & TYPE_LIST )
418    {
419        char * ip;
420        int    port;
421
422        /* Original protocol */
423        tr_inf( "Tracker: got %d peers", bePeers->val.l.count );
424
425        for( i = 0; i < bePeers->val.l.count; i++ )
426        {
427            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "ip" );
428            if( !beFoo )
429                continue;
430            ip = beFoo->val.s.s;
431            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "port" );
432            if( !beFoo )
433                continue;
434            port = beFoo->val.i;
435
436            tr_peerAddOld( tor, ip, port );
437        }
438
439        if( bePeers->val.l.count >= 50 )
440        {
441            tc->hasManyPeers = 1;
442        }
443    }
444    else if( bePeers->type & TYPE_STR )
445    {
446        struct in_addr addr;
447        in_port_t      port;
448
449        /* "Compact" extension */
450        if( bePeers->val.s.i % 6 )
451        {
452            tr_err( "Tracker: \"peers\" of size %d",
453                    bePeers->val.s.i );
454            tr_lockUnlock( &tor->lock );
455            goto cleanup;
456        }
457
458        tr_inf( "Tracker: got %d peers", bePeers->val.s.i / 6 );
459        for( i = 0; i < bePeers->val.s.i / 6; i++ )
460        {
461            memcpy( &addr, &bePeers->val.s.s[6*i],   4 );
462            memcpy( &port, &bePeers->val.s.s[6*i+4], 2 );
463
464            tr_peerAddCompact( tor, addr, port );
465        }
466
467        if( bePeers->val.s.i / 6 >= 50 )
468        {
469            tc->hasManyPeers = 1;
470        }
471    }
472
473nodict:
474    /* Success */
475    tc->started   = 0;
476    tc->completed = 0;
477    tc->dateOk    = tr_date();
478
479    if( tc->stopped )
480    {
481        tor->status = TR_STATUS_STOPPED;
482        tc->stopped = 0;
483    }
484    else if( 0 < tc->newPort )
485    {
486        tc->started  = 1;
487    }
488
489cleanup:
490    if( shouldfree )
491    {
492        tr_bencFree( &beAll );
493    }
494}
495
496int tr_trackerScrape( tr_torrent_t * tor, int * seeders, int * leechers )
497{
498    tr_info_t * inf = &tor->info;
499
500    tr_http_t  * http;
501    const char * data, * body;
502    int          datalen, bodylen;
503    int          code, ii;
504    benc_val_t   scrape, * val1, * val2;
505
506    if( !tor->scrape[0] )
507    {
508        /* scrape not supported */
509        return 1;
510    }
511
512    http = tr_httpClient( TR_HTTP_GET, inf->trackerAddress, inf->trackerPort,
513                          "%s?info_hash=%s", tor->scrape, tor->hashString );
514
515    data = NULL;
516    while( NULL == data )
517    {
518        switch( tr_httpPulse( http, &data, &datalen ) )
519        {
520            case TR_WAIT:
521                break;
522
523            case TR_ERROR:
524                tr_httpClose( http );
525                return 1;
526
527            case TR_OK:
528                if( NULL == data || 0 >= datalen )
529                {
530                    tr_httpClose( http );
531                    return 1;
532                }
533                break;
534        }
535        tr_wait( 10 );
536    }
537
538    code = tr_httpResponseCode( data, datalen );
539    if( !TR_HTTP_STATUS_OK( code ) )
540    {
541        tr_httpClose( http );
542        return 1;
543    }
544
545    body = tr_httpParse( data, datalen , NULL );
546    if( NULL == body )
547    {
548        tr_httpClose( http );
549        return 1;
550    }
551    bodylen = datalen - ( body - data );
552
553    for( ii = 0; ii < bodylen - 8; ii++ )
554    {
555        if( !memcmp( body + ii, "d5:files", 8 ) )
556        {
557            break;
558        }
559    }
560    if( ii >= bodylen - 8 )
561    {
562        tr_httpClose( http );
563        return 1;
564    }
565    if( tr_bencLoad( body + ii, bodylen - ii, &scrape, NULL ) )
566    {
567        tr_httpClose( http );
568        return 1;
569    }
570
571    val1 = tr_bencDictFind( &scrape, "files" );
572    if( !val1 )
573    {
574        tr_bencFree( &scrape );
575        tr_httpClose( http );
576        return 1;
577    }
578    val1 = &val1->val.l.vals[1];
579    if( !val1 )
580    {
581        tr_bencFree( &scrape );
582        tr_httpClose( http );
583        return 1;
584    }
585    val2 = tr_bencDictFind( val1, "complete" );
586    if( !val2 )
587    {
588        tr_bencFree( &scrape );
589        tr_httpClose( http );
590        return 1;
591    }
592    *seeders = val2->val.i;
593    val2 = tr_bencDictFind( val1, "incomplete" );
594    if( !val2 )
595    {
596        tr_bencFree( &scrape );
597        tr_httpClose( http );
598        return 1;
599    }
600    *leechers = val2->val.i;
601    tr_bencFree( &scrape );
602    tr_httpClose( http );
603
604    return 0;
605}
606
607int tr_trackerSeeders( tr_tracker_t * tc )
608{
609    if( !tc )
610    {
611        return -1;
612    }
613    return tc->seeders;
614}
615
616int tr_trackerLeechers( tr_tracker_t * tc )
617{
618    if( !tc )
619    {
620        return -1;
621    }
622    return tc->leechers;
623}
Note: See TracBrowser for help on using the repository browser.