source: trunk/libtransmission/tracker.c @ 920

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

Merge nat-traversal branch to trunk.

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