source: trunk/libtransmission/tracker.c @ 216

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

Fixed bug where incorrect stats were being reported to the tracker.
This was introduced when download and upload totals became persistent.

File size: 16.0 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            return 0;
185        }
186
187        tc->socket = tr_netOpen( addr, htons( inf->trackerPort ) );
188        if( tc->socket < 0 )
189        {
190            tr_fdSocketClosed( tor->fdlimit, 1 );
191            return 0;
192        }
193
194        tc->status = TC_STATUS_CONNECT;
195    }
196
197    if( tc->status & TC_STATUS_CONNECT )
198    {
199        /* We are connecting to the tracker. Try to send the query */
200        sendQuery( tc );
201    }
202
203    if( tc->status & TC_STATUS_RECV )
204    {
205        /* Try to get something */
206        recvAnswer( tc );
207    }
208
209    if( tc->status > TC_STATUS_IDLE && now > tc->dateTry + 60000 )
210    {
211        /* Give up if the request wasn't successful within 60 seconds */
212        tr_inf( "Tracker: timeout reached (60 s)" );
213
214        tr_netClose( tc->socket );
215        tr_fdSocketClosed( tor->fdlimit, 1 );
216
217        tc->status  = TC_STATUS_IDLE;
218        tc->dateTry = tr_date();
219    }
220
221    return 0;
222}
223
224void tr_trackerCompleted( tr_tracker_t * tc )
225{
226    tc->started   = 0;
227    tc->completed = 1;
228    tc->stopped   = 0;
229}
230
231void tr_trackerStopped( tr_tracker_t * tc )
232{
233    tr_torrent_t * tor = tc->tor;
234
235    if( tc->status > TC_STATUS_CONNECT )
236    {
237        /* If we are already sendy a query at the moment, we need to
238           reconnect */
239        tr_netClose( tc->socket );
240        tr_fdSocketClosed( tor->fdlimit, 1 );
241        tc->status = TC_STATUS_IDLE;
242    }
243
244    tc->started   = 0;
245    tc->completed = 0;
246    tc->stopped   = 1;
247
248    /* Even if we have connected recently, reconnect right now */
249    if( tc->status & TC_STATUS_IDLE )
250    {
251        tc->dateTry = 0;
252    }
253}
254
255void tr_trackerClose( tr_tracker_t * tc )
256{
257    tr_torrent_t * tor = tc->tor;
258
259    if( tc->status > TC_STATUS_IDLE )
260    {
261        tr_netClose( tc->socket );
262        tr_fdSocketClosed( tor->fdlimit, 1 );
263    }
264    free( tc->buf );
265    free( tc );
266}
267
268static void sendQuery( tr_tracker_t * tc )
269{
270    tr_torrent_t * tor = tc->tor;
271    tr_info_t    * inf = &tor->info;
272
273    char     * event;
274    uint64_t   left;
275    int        ret;
276    uint64_t   down;
277    uint64_t   up;
278
279    down = tor->downloaded - tc->download;
280    up = tor->uploaded - tc->upload;
281    if( tc->started )
282    {
283        event = "&event=started";
284        down = up = 0;
285       
286        if( 0 < tc->newPort )
287        {
288            tc->bindPort = tc->newPort;
289            tc->newPort = -1;
290        }
291    }
292    else if( tc->completed )
293    {
294        event = "&event=completed";
295    }
296    else if( tc->stopped || 0 < tc->newPort )
297    {
298        event = "&event=stopped";
299    }
300    else
301    {
302        event = "";
303    }
304
305    left = tr_cpLeftBytes( tor->completion );
306
307    ret = snprintf( (char *) tc->buf, tc->size,
308            "GET %s?"
309            "info_hash=%s&"
310            "peer_id=%s&"
311            "port=%d&"
312            "uploaded=%lld&"
313            "downloaded=%lld&"
314            "left=%lld&"
315            "compact=1&"
316            "numwant=50&"
317            "key=%s"
318            "%s "
319            "HTTP/1.1\r\n"
320            "Host: %s\r\n"
321            "User-Agent: Transmission/%d.%d\r\n"
322            "Connection: close\r\n\r\n",
323            inf->trackerAnnounce, tor->hashString, tc->id,
324            tc->bindPort, up, down,
325            left, tor->key, event, inf->trackerAddress,
326            VERSION_MAJOR, VERSION_MINOR );
327
328    ret = tr_netSend( tc->socket, tc->buf, ret );
329    if( ret & TR_NET_CLOSE )
330    {
331        tr_inf( "Tracker: connection failed" );
332        tr_netClose( tc->socket );
333        tr_fdSocketClosed( tor->fdlimit, 1 );
334        tc->status  = TC_STATUS_IDLE;
335        tc->dateTry = tr_date();
336    }
337    else if( !( ret & TR_NET_BLOCK ) )
338    {
339        // printf( "Tracker: sent %s", tc->buf );
340        tc->status = TC_STATUS_RECV;
341        tc->pos    = 0;
342    }
343}
344
345static void recvAnswer( tr_tracker_t * tc )
346{
347    tr_torrent_t * tor = tc->tor;
348    int ret;
349    int i;
350    benc_val_t   beAll;
351    benc_val_t * bePeers, * beFoo;
352
353    if( tc->pos == tc->size )
354    {
355        tc->size *= 2;
356        tc->buf   = realloc( tc->buf, tc->size );
357    }
358   
359    ret = tr_netRecv( tc->socket, &tc->buf[tc->pos],
360                    tc->size - tc->pos );
361
362    if( ret & TR_NET_BLOCK )
363    {
364        return;
365    }
366    if( !( ret & TR_NET_CLOSE ) )
367    {
368        // printf( "got %d bytes\n", ret );
369        tc->pos += ret;
370        return;
371    }
372
373    tr_netClose( tc->socket );
374    tr_fdSocketClosed( tor->fdlimit, 1 );
375    // printf( "connection closed, got total %d bytes\n", tc->pos );
376
377    tc->status  = TC_STATUS_IDLE;
378    tc->dateTry = tr_date();
379
380    if( tc->pos < 1 )
381    {
382        /* We got nothing */
383        return;
384    }
385
386    /* Find the beginning of the dictionary */
387    for( i = 0; i < tc->pos - 18; i++ )
388    {
389        /* Hem */
390        if( !memcmp( &tc->buf[i], "d8:interval", 11 ) ||
391            !memcmp( &tc->buf[i], "d8:complete", 11 ) ||
392            !memcmp( &tc->buf[i], "d14:failure reason", 18 ) )
393        {
394            break;
395        }
396    }
397
398    if( i >= tc->pos - 18 )
399    {
400        tr_err( "Tracker: no dictionary in answer" );
401        // printf( "%s\n", tc->buf );
402        return;
403    }
404
405    if( tr_bencLoad( &tc->buf[i], &beAll, NULL ) )
406    {
407        tr_err( "Tracker: error parsing bencoded data" );
408        return;
409    }
410
411    // tr_bencPrint( &beAll );
412
413    if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) )
414    {
415        tr_err( "Tracker: %s", bePeers->val.s.s );
416        tor->error |= TR_ETRACKER;
417        snprintf( tor->trackerError, sizeof( tor->trackerError ),
418                  "%s", bePeers->val.s.s );
419        goto cleanup;
420    }
421    tor->error &= ~TR_ETRACKER;
422
423    if( !tc->interval )
424    {
425        /* Get the tracker interval, ignore it if it is not between
426           10 sec and 5 mins */
427        if( !( beFoo = tr_bencDictFind( &beAll, "interval" ) ) ||
428            !( beFoo->type & TYPE_INT ) )
429        {
430            tr_err( "Tracker: no 'interval' field" );
431            goto cleanup;
432        }
433
434        tc->interval = beFoo->val.i;
435        tc->interval = MIN( tc->interval, 300 );
436        tc->interval = MAX( 10, tc->interval );
437
438        tr_inf( "Tracker: interval = %d seconds", tc->interval );
439    }
440
441    if( ( beFoo = tr_bencDictFind( &beAll, "complete" ) ) &&
442        ( beFoo->type & TYPE_INT ) )
443    {
444        tc->seeders = beFoo->val.i;
445    }
446    if( ( beFoo = tr_bencDictFind( &beAll, "incomplete" ) ) &&
447        ( beFoo->type & TYPE_INT ) )
448    {
449        tc->leechers = beFoo->val.i;
450    }
451    if( tc->seeders + tc->leechers >= 50 )
452    {
453        tc->hasManyPeers = 1;
454    }
455
456    if( !( bePeers = tr_bencDictFind( &beAll, "peers" ) ) )
457    {
458        tr_err( "Tracker: no \"peers\" field" );
459        goto cleanup;
460    }
461
462    if( bePeers->type & TYPE_LIST )
463    {
464        char * ip;
465        int    port;
466
467        /* Original protocol */
468        tr_inf( "Tracker: got %d peers", bePeers->val.l.count );
469
470        for( i = 0; i < bePeers->val.l.count; i++ )
471        {
472            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "ip" );
473            if( !beFoo )
474                continue;
475            ip = beFoo->val.s.s;
476            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "port" );
477            if( !beFoo )
478                continue;
479            port = beFoo->val.i;
480
481            tr_peerAddOld( tor, ip, port );
482        }
483
484        if( bePeers->val.l.count >= 50 )
485        {
486            tc->hasManyPeers = 1;
487        }
488    }
489    else if( bePeers->type & TYPE_STR )
490    {
491        struct in_addr addr;
492        in_port_t      port;
493
494        /* "Compact" extension */
495        if( bePeers->val.s.i % 6 )
496        {
497            tr_err( "Tracker: \"peers\" of size %d",
498                    bePeers->val.s.i );
499            tr_lockUnlock( &tor->lock );
500            goto cleanup;
501        }
502
503        tr_inf( "Tracker: got %d peers", bePeers->val.s.i / 6 );
504        for( i = 0; i < bePeers->val.s.i / 6; i++ )
505        {
506            memcpy( &addr, &bePeers->val.s.s[6*i],   4 );
507            memcpy( &port, &bePeers->val.s.s[6*i+4], 2 );
508
509            tr_peerAddCompact( tor, addr, port );
510        }
511
512        if( bePeers->val.s.i / 6 >= 50 )
513        {
514            tc->hasManyPeers = 1;
515        }
516    }
517
518    /* Success */
519    tc->started   = 0;
520    tc->completed = 0;
521    tc->dateOk    = tr_date();
522
523    if( tc->stopped )
524    {
525        tor->status = TR_STATUS_STOPPED;
526        tc->stopped = 0;
527    }
528    else if( 0 < tc->newPort )
529    {
530        tc->started = 1;
531    }
532
533cleanup:
534    tr_bencFree( &beAll );
535}
536
537int tr_trackerScrape( tr_torrent_t * tor, int * seeders, int * leechers )
538{
539    tr_info_t * inf = &tor->info;
540
541    int s, i, ret;
542    uint8_t buf[1024];
543    benc_val_t scrape, * val1, * val2;
544    struct in_addr addr;
545    uint64_t date;
546    int pos, len;
547
548    if( !tor->scrape[0] )
549    {
550        /* scrape not supported */
551        return 1;
552    }
553
554    if( tr_netResolve( inf->trackerAddress, &addr ) )
555    {
556        return 0;
557    }
558    s = tr_netOpen( addr, htons( inf->trackerPort ) );
559    if( s < 0 )
560    {
561        return 1;
562    }
563
564    len = snprintf( (char *) buf, sizeof( buf ),
565              "GET %s?info_hash=%s HTTP/1.1\r\n"
566              "Host: %s\r\n"
567              "Connection: close\r\n\r\n",
568              tor->scrape, tor->hashString,
569              inf->trackerAddress );
570
571    for( date = tr_date();; )
572    {
573        ret = tr_netSend( s, buf, len );
574        if( ret & TR_NET_CLOSE )
575        {
576            fprintf( stderr, "Could not connect to tracker\n" );
577            tr_netClose( s );
578            return 1;
579        }
580        else if( ret & TR_NET_BLOCK )
581        {
582            if( tr_date() > date + 10000 )
583            {
584                fprintf( stderr, "Could not connect to tracker\n" );
585                tr_netClose( s );
586                return 1;
587            }
588        }
589        else
590        {
591            break;
592        }
593        tr_wait( 10 );
594    }
595
596    pos = 0;
597    for( date = tr_date();; )
598    {
599        ret = tr_netRecv( s, &buf[pos], sizeof( buf ) - pos );
600        if( ret & TR_NET_CLOSE )
601        {
602            break;
603        }
604        else if( ret & TR_NET_BLOCK )
605        {
606            if( tr_date() > date + 10000 )
607            {
608                fprintf( stderr, "Could not read from tracker\n" );
609                tr_netClose( s );
610                return 1;
611            }
612        }
613        else
614        {
615            pos += ret;
616        }
617        tr_wait( 10 );
618    }
619
620    if( pos < 1 )
621    {
622        fprintf( stderr, "Could not read from tracker\n" );
623        tr_netClose( s );
624        return 1;
625    }
626
627    for( i = 0; i < pos - 8; i++ )
628    {
629        if( !memcmp( &buf[i], "d5:files", 8 ) )
630        {
631            break;
632        }
633    }
634    if( i >= pos - 8 )
635    {
636        return 1;
637    }
638    if( tr_bencLoad( &buf[i], &scrape, NULL ) )
639    {
640        return 1;
641    }
642
643    val1 = tr_bencDictFind( &scrape, "files" );
644    if( !val1 )
645    {
646        return 1;
647    }
648    val1 = &val1->val.l.vals[1];
649    if( !val1 )
650    {
651        return 1;
652    }
653    val2 = tr_bencDictFind( val1, "complete" );
654    if( !val2 )
655    {
656        return 1;
657    }
658    *seeders = val2->val.i;
659    val2 = tr_bencDictFind( val1, "incomplete" );
660    if( !val2 )
661    {
662        return 1;
663    }
664    *leechers = val2->val.i;
665    tr_bencFree( &scrape );
666
667    return 0;
668}
669
670int tr_trackerSeeders( tr_torrent_t * tor)
671{
672        if (tor->status != TR_STATUS_PAUSE)
673        {
674                return (tor->tracker)->seeders;
675        }
676        return 0;
677}
678
679int tr_trackerLeechers( tr_torrent_t * tor)
680{
681        if (tor->status != TR_STATUS_PAUSE) 
682        {
683                return (tor->tracker)->leechers;
684        }
685        return 0;
686}
Note: See TracBrowser for help on using the repository browser.