source: branches/nat-traversal/libtransmission/tracker.c @ 864

Last change on this file since 864 was 864, checked in by joshe, 16 years ago

Add nat traversal support (ie: NAT-PMP and UPnP)

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