source: trunk/libtransmission/tracker.c @ 1001

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

Fix for trackers that include parameters in announce URL.

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