source: trunk/libtransmission/tracker.c @ 228

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

Add missing tracker stats reset when changing our listening port.

This should have been included in rev 216.

File size: 16.3 KB
Line 
1/******************************************************************************
2 * Copyright (c) 2005 Eric Petit
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 *****************************************************************************/
22
23#include "transmission.h"
24
25struct tr_tracker_s
26{
27    tr_torrent_t * tor;
28
29    char         * id;
30
31    char           started;
32    char           completed;
33    char           stopped;
34
35    int            interval;
36    int            seeders;
37    int            leechers;
38    int            hasManyPeers;
39
40    uint64_t       dateTry;
41    uint64_t       dateOk;
42
43#define TC_STATUS_IDLE    1
44#define TC_STATUS_RESOLVE 2
45#define TC_STATUS_CONNECT 4
46#define TC_STATUS_RECV    8
47    char           status;
48
49    tr_resolve_t * resolve;
50    int            socket;
51    uint8_t      * buf;
52    int            size;
53    int            pos;
54
55    int            bindPort;
56    int            newPort;
57
58    uint64_t       download;
59    uint64_t       upload;
60};
61
62static void sendQuery  ( tr_tracker_t * tc );
63static void recvAnswer ( tr_tracker_t * tc );
64
65tr_tracker_t * tr_trackerInit( tr_torrent_t * tor )
66{
67    tr_tracker_t * tc;
68
69    tc           = calloc( 1, sizeof( tr_tracker_t ) );
70    tc->tor      = tor;
71    tc->id       = tor->id;
72
73    tc->started  = 1;
74
75    tc->seeders  = -1;
76    tc->leechers = -1;
77
78    tc->status   = TC_STATUS_IDLE;
79    tc->size     = 1024;
80    tc->buf      = malloc( tc->size );
81
82    tc->bindPort = *(tor->bindPort);
83    tc->newPort  = -1;
84
85    tc->download = tor->downloaded;
86    tc->upload   = tor->uploaded;
87
88    return tc;
89}
90
91static int shouldConnect( tr_tracker_t * tc )
92{
93    uint64_t now = tr_date();
94
95    /* In any case, always wait 5 seconds between two requests */
96    if( now < tc->dateTry + 5000 )
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    uint64_t       now = tr_date();
144
145    if( ( tc->status & TC_STATUS_IDLE ) && shouldConnect( tc ) )
146    {
147        tc->resolve = tr_netResolveInit( inf->trackerAddress );
148
149        tr_inf( "Tracker: connecting to %s:%d (%s)",
150                inf->trackerAddress, inf->trackerPort,
151                tc->started ? "sending 'started'" :
152                ( tc->completed ? "sending 'completed'" :
153                  ( tc->stopped ? "sending 'stopped'" :
154                    ( 0 < tc->newPort ? "sending 'stopped' to change port" :
155                      "getting peers" ) ) ) );
156
157        tc->status  = TC_STATUS_RESOLVE;
158        tc->dateTry = tr_date();
159    }
160
161    if( tc->status & TC_STATUS_RESOLVE )
162    {
163        int ret;
164        struct in_addr addr;
165
166        ret = tr_netResolvePulse( tc->resolve, &addr );
167        if( ret == TR_RESOLVE_WAIT )
168        {
169            return 0;
170        }
171        else
172        {
173            tr_netResolveClose( tc->resolve );
174        }
175       
176        if( ret == TR_RESOLVE_ERROR )
177        {
178            tc->status = TC_STATUS_IDLE;
179            return 0;
180        }
181
182        if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) )
183        {
184            tc->status = TC_STATUS_IDLE;
185            return 0;
186        }
187
188        tc->socket = tr_netOpen( addr, htons( inf->trackerPort ) );
189        if( tc->socket < 0 )
190        {
191            tr_fdSocketClosed( tor->fdlimit, 1 );
192            tc->status = TC_STATUS_IDLE;
193            return 0;
194        }
195
196        tc->status = TC_STATUS_CONNECT;
197    }
198
199    if( tc->status & TC_STATUS_CONNECT )
200    {
201        /* We are connecting to the tracker. Try to send the query */
202        sendQuery( tc );
203    }
204
205    if( tc->status & TC_STATUS_RECV )
206    {
207        /* Try to get something */
208        recvAnswer( tc );
209    }
210
211    if( tc->status > TC_STATUS_IDLE && now > tc->dateTry + 60000 )
212    {
213        /* Give up if the request wasn't successful within 60 seconds */
214        tr_inf( "Tracker: timeout reached (60 s)" );
215
216        tr_netClose( tc->socket );
217        tr_fdSocketClosed( tor->fdlimit, 1 );
218
219        tc->status  = TC_STATUS_IDLE;
220        tc->dateTry = tr_date();
221    }
222
223    return 0;
224}
225
226void tr_trackerCompleted( tr_tracker_t * tc )
227{
228    tc->started   = 0;
229    tc->completed = 1;
230    tc->stopped   = 0;
231}
232
233void tr_trackerStopped( tr_tracker_t * tc )
234{
235    tr_torrent_t * tor = tc->tor;
236
237    if( tc->status > TC_STATUS_CONNECT )
238    {
239        /* If we are already sendy a query at the moment, we need to
240           reconnect */
241        tr_netClose( tc->socket );
242        tr_fdSocketClosed( tor->fdlimit, 1 );
243        tc->status = TC_STATUS_IDLE;
244    }
245
246    tc->started   = 0;
247    tc->completed = 0;
248    tc->stopped   = 1;
249
250    /* Even if we have connected recently, reconnect right now */
251    if( tc->status & TC_STATUS_IDLE )
252    {
253        tc->dateTry = 0;
254    }
255}
256
257void tr_trackerClose( tr_tracker_t * tc )
258{
259    tr_torrent_t * tor = tc->tor;
260
261    if( tc->status == TC_STATUS_RESOLVE )
262    {
263        tr_netResolveClose( tc->resolve );
264    }
265    else if( tc->status > TC_STATUS_RESOLVE )
266    {
267        tr_netClose( tc->socket );
268        tr_fdSocketClosed( tor->fdlimit, 1 );
269    }
270    free( tc->buf );
271    free( tc );
272}
273
274static void sendQuery( tr_tracker_t * tc )
275{
276    tr_torrent_t * tor = tc->tor;
277    tr_info_t    * inf = &tor->info;
278
279    char     * event;
280    uint64_t   left;
281    int        ret;
282    uint64_t   down;
283    uint64_t   up;
284
285    down = tor->downloaded - tc->download;
286    up = tor->uploaded - tc->upload;
287    if( tc->started )
288    {
289        event = "&event=started";
290        down = up = 0;
291       
292        if( 0 < tc->newPort )
293        {
294            tc->bindPort = tc->newPort;
295            tc->newPort = -1;
296        }
297    }
298    else if( tc->completed )
299    {
300        event = "&event=completed";
301    }
302    else if( tc->stopped || 0 < tc->newPort )
303    {
304        event = "&event=stopped";
305    }
306    else
307    {
308        event = "";
309    }
310
311    left = tr_cpLeftBytes( tor->completion );
312
313    ret = snprintf( (char *) tc->buf, tc->size,
314            "GET %s?"
315            "info_hash=%s&"
316            "peer_id=%s&"
317            "port=%d&"
318            "uploaded=%lld&"
319            "downloaded=%lld&"
320            "left=%lld&"
321            "compact=1&"
322            "numwant=50&"
323            "key=%s"
324            "%s "
325            "HTTP/1.1\r\n"
326            "Host: %s\r\n"
327            "User-Agent: Transmission/%d.%d\r\n"
328            "Connection: close\r\n\r\n",
329            inf->trackerAnnounce, tor->hashString, tc->id,
330            tc->bindPort, up, down,
331            left, tor->key, event, inf->trackerAddress,
332            VERSION_MAJOR, VERSION_MINOR );
333
334    ret = tr_netSend( tc->socket, tc->buf, ret );
335    if( ret & TR_NET_CLOSE )
336    {
337        tr_inf( "Tracker: connection failed" );
338        tr_netClose( tc->socket );
339        tr_fdSocketClosed( tor->fdlimit, 1 );
340        tc->status  = TC_STATUS_IDLE;
341        tc->dateTry = tr_date();
342    }
343    else if( !( ret & TR_NET_BLOCK ) )
344    {
345        // printf( "Tracker: sent %s", tc->buf );
346        tc->status = TC_STATUS_RECV;
347        tc->pos    = 0;
348    }
349}
350
351static void recvAnswer( tr_tracker_t * tc )
352{
353    tr_torrent_t * tor = tc->tor;
354    int ret;
355    int i;
356    benc_val_t   beAll;
357    benc_val_t * bePeers, * beFoo;
358
359    if( tc->pos == tc->size )
360    {
361        tc->size *= 2;
362        tc->buf   = realloc( tc->buf, tc->size );
363    }
364   
365    ret = tr_netRecv( tc->socket, &tc->buf[tc->pos],
366                    tc->size - tc->pos );
367
368    if( ret & TR_NET_BLOCK )
369    {
370        return;
371    }
372    if( !( ret & TR_NET_CLOSE ) )
373    {
374        // printf( "got %d bytes\n", ret );
375        tc->pos += ret;
376        return;
377    }
378
379    tr_netClose( tc->socket );
380    tr_fdSocketClosed( tor->fdlimit, 1 );
381    // printf( "connection closed, got total %d bytes\n", tc->pos );
382
383    tc->status  = TC_STATUS_IDLE;
384    tc->dateTry = tr_date();
385
386    if( tc->pos < 1 )
387    {
388        /* We got nothing */
389        return;
390    }
391
392    /* Find the beginning of the dictionary */
393    for( i = 0; i < tc->pos - 18; i++ )
394    {
395        /* Hem */
396        if( !memcmp( &tc->buf[i], "d8:interval", 11 ) ||
397            !memcmp( &tc->buf[i], "d8:complete", 11 ) ||
398            !memcmp( &tc->buf[i], "d14:failure reason", 18 ) )
399        {
400            break;
401        }
402    }
403
404    if( i >= tc->pos - 18 )
405    {
406        tr_err( "Tracker: no dictionary in answer" );
407        // printf( "%s\n", tc->buf );
408        return;
409    }
410
411    if( tr_bencLoad( &tc->buf[i], &beAll, NULL ) )
412    {
413        tr_err( "Tracker: error parsing bencoded data" );
414        return;
415    }
416
417    // tr_bencPrint( &beAll );
418
419    if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) )
420    {
421        tr_err( "Tracker: %s", bePeers->val.s.s );
422        tor->error |= TR_ETRACKER;
423        snprintf( tor->trackerError, sizeof( tor->trackerError ),
424                  "%s", bePeers->val.s.s );
425        goto cleanup;
426    }
427    tor->error &= ~TR_ETRACKER;
428
429    if( !tc->interval )
430    {
431        /* Get the tracker interval, ignore it if it is not between
432           10 sec and 5 mins */
433        if( !( beFoo = tr_bencDictFind( &beAll, "interval" ) ) ||
434            !( beFoo->type & TYPE_INT ) )
435        {
436            tr_err( "Tracker: no 'interval' field" );
437            goto cleanup;
438        }
439
440        tc->interval = beFoo->val.i;
441        tc->interval = MIN( tc->interval, 300 );
442        tc->interval = MAX( 10, tc->interval );
443
444        tr_inf( "Tracker: interval = %d seconds", tc->interval );
445    }
446
447    if( ( beFoo = tr_bencDictFind( &beAll, "complete" ) ) &&
448        ( beFoo->type & TYPE_INT ) )
449    {
450        tc->seeders = beFoo->val.i;
451    }
452    if( ( beFoo = tr_bencDictFind( &beAll, "incomplete" ) ) &&
453        ( beFoo->type & TYPE_INT ) )
454    {
455        tc->leechers = beFoo->val.i;
456    }
457    if( tc->seeders + tc->leechers >= 50 )
458    {
459        tc->hasManyPeers = 1;
460    }
461
462    if( !( bePeers = tr_bencDictFind( &beAll, "peers" ) ) )
463    {
464        tr_err( "Tracker: no \"peers\" field" );
465        goto cleanup;
466    }
467
468    if( bePeers->type & TYPE_LIST )
469    {
470        char * ip;
471        int    port;
472
473        /* Original protocol */
474        tr_inf( "Tracker: got %d peers", bePeers->val.l.count );
475
476        for( i = 0; i < bePeers->val.l.count; i++ )
477        {
478            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "ip" );
479            if( !beFoo )
480                continue;
481            ip = beFoo->val.s.s;
482            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "port" );
483            if( !beFoo )
484                continue;
485            port = beFoo->val.i;
486
487            tr_peerAddOld( tor, ip, port );
488        }
489
490        if( bePeers->val.l.count >= 50 )
491        {
492            tc->hasManyPeers = 1;
493        }
494    }
495    else if( bePeers->type & TYPE_STR )
496    {
497        struct in_addr addr;
498        in_port_t      port;
499
500        /* "Compact" extension */
501        if( bePeers->val.s.i % 6 )
502        {
503            tr_err( "Tracker: \"peers\" of size %d",
504                    bePeers->val.s.i );
505            tr_lockUnlock( &tor->lock );
506            goto cleanup;
507        }
508
509        tr_inf( "Tracker: got %d peers", bePeers->val.s.i / 6 );
510        for( i = 0; i < bePeers->val.s.i / 6; i++ )
511        {
512            memcpy( &addr, &bePeers->val.s.s[6*i],   4 );
513            memcpy( &port, &bePeers->val.s.s[6*i+4], 2 );
514
515            tr_peerAddCompact( tor, addr, port );
516        }
517
518        if( bePeers->val.s.i / 6 >= 50 )
519        {
520            tc->hasManyPeers = 1;
521        }
522    }
523
524    /* Success */
525    tc->started   = 0;
526    tc->completed = 0;
527    tc->dateOk    = tr_date();
528
529    if( tc->stopped )
530    {
531        tor->status = TR_STATUS_STOPPED;
532        tc->stopped = 0;
533    }
534    else if( 0 < tc->newPort )
535    {
536        tc->started  = 1;
537        tc->download = tor->downloaded;
538        tc->upload   = tor->uploaded;
539    }
540
541cleanup:
542    tr_bencFree( &beAll );
543}
544
545int tr_trackerScrape( tr_torrent_t * tor, int * seeders, int * leechers )
546{
547    tr_info_t * inf = &tor->info;
548
549    int s, i, ret;
550    uint8_t buf[1024];
551    benc_val_t scrape, * val1, * val2;
552    struct in_addr addr;
553    uint64_t date;
554    int pos, len;
555
556    if( !tor->scrape[0] )
557    {
558        /* scrape not supported */
559        return 1;
560    }
561
562    if( tr_netResolve( inf->trackerAddress, &addr ) )
563    {
564        return 0;
565    }
566    s = tr_netOpen( addr, htons( inf->trackerPort ) );
567    if( s < 0 )
568    {
569        return 1;
570    }
571
572    len = snprintf( (char *) buf, sizeof( buf ),
573              "GET %s?info_hash=%s HTTP/1.1\r\n"
574              "Host: %s\r\n"
575              "Connection: close\r\n\r\n",
576              tor->scrape, tor->hashString,
577              inf->trackerAddress );
578
579    for( date = tr_date();; )
580    {
581        ret = tr_netSend( s, buf, len );
582        if( ret & TR_NET_CLOSE )
583        {
584            fprintf( stderr, "Could not connect to tracker\n" );
585            tr_netClose( s );
586            return 1;
587        }
588        else if( ret & TR_NET_BLOCK )
589        {
590            if( tr_date() > date + 10000 )
591            {
592                fprintf( stderr, "Could not connect to tracker\n" );
593                tr_netClose( s );
594                return 1;
595            }
596        }
597        else
598        {
599            break;
600        }
601        tr_wait( 10 );
602    }
603
604    pos = 0;
605    for( date = tr_date();; )
606    {
607        ret = tr_netRecv( s, &buf[pos], sizeof( buf ) - pos );
608        if( ret & TR_NET_CLOSE )
609        {
610            break;
611        }
612        else if( ret & TR_NET_BLOCK )
613        {
614            if( tr_date() > date + 10000 )
615            {
616                fprintf( stderr, "Could not read from tracker\n" );
617                tr_netClose( s );
618                return 1;
619            }
620        }
621        else
622        {
623            pos += ret;
624        }
625        tr_wait( 10 );
626    }
627
628    if( pos < 1 )
629    {
630        fprintf( stderr, "Could not read from tracker\n" );
631        tr_netClose( s );
632        return 1;
633    }
634
635    for( i = 0; i < pos - 8; i++ )
636    {
637        if( !memcmp( &buf[i], "d5:files", 8 ) )
638        {
639            break;
640        }
641    }
642    if( i >= pos - 8 )
643    {
644        return 1;
645    }
646    if( tr_bencLoad( &buf[i], &scrape, NULL ) )
647    {
648        return 1;
649    }
650
651    val1 = tr_bencDictFind( &scrape, "files" );
652    if( !val1 )
653    {
654        return 1;
655    }
656    val1 = &val1->val.l.vals[1];
657    if( !val1 )
658    {
659        return 1;
660    }
661    val2 = tr_bencDictFind( val1, "complete" );
662    if( !val2 )
663    {
664        return 1;
665    }
666    *seeders = val2->val.i;
667    val2 = tr_bencDictFind( val1, "incomplete" );
668    if( !val2 )
669    {
670        return 1;
671    }
672    *leechers = val2->val.i;
673    tr_bencFree( &scrape );
674
675    return 0;
676}
677
678int tr_trackerSeeders( tr_tracker_t * tc )
679{
680    if( !tc )
681    {
682        return -1;
683    }
684    return tc->seeders;
685}
686
687int tr_trackerLeechers( tr_tracker_t * tc )
688{
689    if( !tc )
690    {
691        return -1;
692    }
693    return tc->leechers;
694}
Note: See TracBrowser for help on using the repository browser.