source: trunk/libtransmission/tracker.c @ 241

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

Make sure not to go past the end of the buffer when loading bencoded data.
Add code to encode using bencoding.

File size: 18.1 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#define TC_ATTEMPT_NOREACH 1
50#define TC_ATTEMPT_ERROR   2
51#define TC_ATTEMPT_OK      4
52    char           lastAttempt;
53
54    tr_resolve_t * resolve;
55    int            socket;
56    uint8_t      * buf;
57    int            size;
58    int            pos;
59
60    int            bindPort;
61    int            newPort;
62
63    uint64_t       download;
64    uint64_t       upload;
65};
66
67static void sendQuery  ( tr_tracker_t * tc );
68static void recvAnswer ( tr_tracker_t * tc );
69
70tr_tracker_t * tr_trackerInit( tr_torrent_t * tor )
71{
72    tr_tracker_t * tc;
73
74    tc           = calloc( 1, sizeof( tr_tracker_t ) );
75    tc->tor      = tor;
76    tc->id       = tor->id;
77
78    tc->started  = 1;
79
80    tc->interval = 300;
81    tc->seeders  = -1;
82    tc->leechers = -1;
83
84    tc->status   = TC_STATUS_IDLE;
85    tc->lastAttempt = TC_ATTEMPT_NOREACH;
86    tc->size     = 1024;
87    tc->buf      = malloc( tc->size );
88
89    tc->bindPort = *(tor->bindPort);
90    tc->newPort  = -1;
91
92    tc->download = tor->downloaded;
93    tc->upload   = tor->uploaded;
94
95    return tc;
96}
97
98static int shouldConnect( tr_tracker_t * tc )
99{
100    uint64_t now = tr_date();
101
102    /* Unreachable tracker, try 10 seconds before trying again */
103    if( tc->lastAttempt == TC_ATTEMPT_NOREACH &&
104        now < tc->dateTry + 10000 )
105    {
106        return 0;
107    }
108
109    /* The tracker rejected us (like 4XX code, unauthorized IP...),
110       don't hammer it - we'll probably get the same answer next time
111       anyway */
112    if( tc->lastAttempt == TC_ATTEMPT_ERROR &&
113        now < tc->dateTry + 1000 * tc->interval )
114    {
115        return 0;
116    }
117
118    /* Do we need to send an event? */
119    if( tc->started || tc->completed || tc->stopped || 0 < tc->newPort )
120    {
121        return 1;
122    }
123
124    /* Should we try and get more peers? */
125    if( now > tc->dateOk + 1000 * tc->interval )
126    {
127        return 1;
128    }
129
130    /* If there is quite a lot of people on this torrent, stress
131       the tracker a bit until we get a decent number of peers */
132    if( tc->hasManyPeers )
133    {
134        if( tc->tor->peerCount < 5 && now > tc->dateOk + 10000 )
135        {
136            return 1;
137        }
138        if( tc->tor->peerCount < 10 && now > tc->dateOk + 20000 )
139        {
140            return 1;
141        }
142        if( tc->tor->peerCount < 15 && now > tc->dateOk + 30000 )
143        {
144            return 1;
145        }
146    }
147
148    return 0;
149}
150
151void tr_trackerChangePort( tr_tracker_t * tc, int port )
152{
153    tc->newPort = port;
154}
155
156int tr_trackerPulse( tr_tracker_t * tc )
157{
158    tr_torrent_t * tor = tc->tor;
159    tr_info_t    * inf = &tor->info;
160    uint64_t       now = tr_date();
161
162    if( ( tc->status & TC_STATUS_IDLE ) && shouldConnect( tc ) )
163    {
164        tc->resolve = tr_netResolveInit( inf->trackerAddress );
165
166        tr_inf( "Tracker: connecting to %s:%d (%s)",
167                inf->trackerAddress, inf->trackerPort,
168                tc->started ? "sending 'started'" :
169                ( tc->completed ? "sending 'completed'" :
170                  ( tc->stopped ? "sending 'stopped'" :
171                    ( 0 < tc->newPort ? "sending 'stopped' to change port" :
172                      "getting peers" ) ) ) );
173
174        tc->status  = TC_STATUS_RESOLVE;
175        tc->dateTry = tr_date();
176    }
177
178    if( tc->status & TC_STATUS_RESOLVE )
179    {
180        int ret;
181        struct in_addr addr;
182
183        ret = tr_netResolvePulse( tc->resolve, &addr );
184        if( ret == TR_RESOLVE_WAIT )
185        {
186            return 0;
187        }
188        else
189        {
190            tr_netResolveClose( tc->resolve );
191        }
192       
193        if( ret == TR_RESOLVE_ERROR )
194        {
195            tc->status = TC_STATUS_IDLE;
196            return 0;
197        }
198
199        if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) )
200        {
201            tc->status = TC_STATUS_IDLE;
202            return 0;
203        }
204
205        tc->socket = tr_netOpen( addr, htons( inf->trackerPort ) );
206        if( tc->socket < 0 )
207        {
208            tr_fdSocketClosed( tor->fdlimit, 1 );
209            tc->status = TC_STATUS_IDLE;
210            return 0;
211        }
212
213        tc->status = TC_STATUS_CONNECT;
214    }
215
216    if( tc->status & TC_STATUS_CONNECT )
217    {
218        /* We are connecting to the tracker. Try to send the query */
219        sendQuery( tc );
220    }
221
222    if( tc->status & TC_STATUS_RECV )
223    {
224        /* Try to get something */
225        recvAnswer( tc );
226    }
227
228    if( tc->status > TC_STATUS_IDLE && now > tc->dateTry + 60000 )
229    {
230        /* Give up if the request wasn't successful within 60 seconds */
231        tr_inf( "Tracker: timeout reached (60 s)" );
232
233        tr_netClose( tc->socket );
234        tr_fdSocketClosed( tor->fdlimit, 1 );
235
236        tc->status  = TC_STATUS_IDLE;
237        tc->dateTry = tr_date();
238    }
239
240    return 0;
241}
242
243void tr_trackerCompleted( tr_tracker_t * tc )
244{
245    tc->started   = 0;
246    tc->completed = 1;
247    tc->stopped   = 0;
248}
249
250void tr_trackerStopped( tr_tracker_t * tc )
251{
252    tr_torrent_t * tor = tc->tor;
253
254    if( tc->status > TC_STATUS_CONNECT )
255    {
256        /* If we are already sendy a query at the moment, we need to
257           reconnect */
258        tr_netClose( tc->socket );
259        tr_fdSocketClosed( tor->fdlimit, 1 );
260        tc->status = TC_STATUS_IDLE;
261    }
262
263    tc->started   = 0;
264    tc->completed = 0;
265    tc->stopped   = 1;
266
267    /* Even if we have connected recently, reconnect right now */
268    if( tc->status & TC_STATUS_IDLE )
269    {
270        tc->dateTry = 0;
271    }
272}
273
274void tr_trackerClose( tr_tracker_t * tc )
275{
276    tr_torrent_t * tor = tc->tor;
277
278    if( tc->status == TC_STATUS_RESOLVE )
279    {
280        tr_netResolveClose( tc->resolve );
281    }
282    else if( tc->status > TC_STATUS_RESOLVE )
283    {
284        tr_netClose( tc->socket );
285        tr_fdSocketClosed( tor->fdlimit, 1 );
286    }
287    free( tc->buf );
288    free( tc );
289}
290
291static void sendQuery( tr_tracker_t * tc )
292{
293    tr_torrent_t * tor = tc->tor;
294    tr_info_t    * inf = &tor->info;
295
296    char     * event;
297    uint64_t   left;
298    int        ret;
299    uint64_t   down;
300    uint64_t   up;
301
302    down = tor->downloaded - tc->download;
303    up = tor->uploaded - tc->upload;
304    if( tc->started )
305    {
306        event = "&event=started";
307        down = up = 0;
308       
309        if( 0 < tc->newPort )
310        {
311            tc->bindPort = tc->newPort;
312            tc->newPort = -1;
313        }
314    }
315    else if( tc->completed )
316    {
317        event = "&event=completed";
318    }
319    else if( tc->stopped || 0 < tc->newPort )
320    {
321        event = "&event=stopped";
322    }
323    else
324    {
325        event = "";
326    }
327
328    left = tr_cpLeftBytes( tor->completion );
329
330    ret = snprintf( (char *) tc->buf, tc->size,
331            "GET %s?"
332            "info_hash=%s&"
333            "peer_id=%s&"
334            "port=%d&"
335            "uploaded=%lld&"
336            "downloaded=%lld&"
337            "left=%lld&"
338            "compact=1&"
339            "numwant=50&"
340            "key=%s"
341            "%s "
342            "HTTP/1.1\r\n"
343            "Host: %s\r\n"
344            "User-Agent: Transmission/%d.%d\r\n"
345            "Connection: close\r\n\r\n",
346            inf->trackerAnnounce, tor->hashString, tc->id,
347            tc->bindPort, up, down,
348            left, tor->key, event, inf->trackerAddress,
349            VERSION_MAJOR, VERSION_MINOR );
350
351    ret = tr_netSend( tc->socket, tc->buf, ret );
352    if( ret & TR_NET_CLOSE )
353    {
354        tr_inf( "Tracker: connection failed" );
355        tr_netClose( tc->socket );
356        tr_fdSocketClosed( tor->fdlimit, 1 );
357        tc->status  = TC_STATUS_IDLE;
358        tc->dateTry = tr_date();
359    }
360    else if( !( ret & TR_NET_BLOCK ) )
361    {
362        // printf( "Tracker: sent %s", tc->buf );
363        tc->status = TC_STATUS_RECV;
364        tc->pos    = 0;
365    }
366}
367
368static void recvAnswer( tr_tracker_t * tc )
369{
370    tr_torrent_t * tor = tc->tor;
371    int ret;
372    int i;
373    benc_val_t   beAll;
374    benc_val_t * bePeers, * beFoo;
375    uint8_t * body;
376    int bodylen;
377
378    if( tc->pos == tc->size )
379    {
380        tc->size *= 2;
381        tc->buf   = realloc( tc->buf, tc->size );
382    }
383   
384    ret = tr_netRecv( tc->socket, &tc->buf[tc->pos],
385                    tc->size - tc->pos );
386
387    if( ret & TR_NET_BLOCK )
388    {
389        return;
390    }
391    if( !( ret & TR_NET_CLOSE ) )
392    {
393        // printf( "got %d bytes\n", ret );
394        tc->pos += ret;
395        return;
396    }
397
398    tr_netClose( tc->socket );
399    tr_fdSocketClosed( tor->fdlimit, 1 );
400    // printf( "connection closed, got total %d bytes\n", tc->pos );
401
402    tc->status  = TC_STATUS_IDLE;
403    tc->dateTry = tr_date();
404
405    if( tc->pos < 12 || ( 0 != memcmp( tc->buf, "HTTP/1.0 ", 9 ) &&
406                          0 != memcmp( tc->buf, "HTTP/1.1 ", 9 ) ) )
407    {
408        /* We don't have a complete HTTP status line */
409        tr_inf( "Tracker: incomplete HTTP status line" );
410        tc->lastAttempt = TC_ATTEMPT_NOREACH;
411        return;
412    }
413
414    if( '2' != tc->buf[9] )
415    {
416        /* we didn't get a 2xx status code */
417        tr_err( "Tracker: invalid HTTP status code: %c%c%c",
418                tc->buf[9], tc->buf[10], tc->buf[11] );
419        tc->lastAttempt = TC_ATTEMPT_ERROR;
420        return;
421    }
422
423    /* find the end of the http headers */
424    body = tr_memmem( tc->buf, tc->pos, "\015\012\015\012", 4 );
425    if( NULL != body )
426    {
427        body += 4;
428    }
429    /* hooray for trackers that violate the HTTP spec */
430    else if( NULL != ( body = tr_memmem( tc->buf, tc->pos, "\015\015", 2 ) ) ||
431             NULL != ( body = tr_memmem( tc->buf, tc->pos, "\012\012", 2 ) ) )
432    {
433        body += 2;
434    }
435    else
436    {
437        tr_err( "Tracker: could not find end of HTTP headers" );
438        tc->lastAttempt = TC_ATTEMPT_NOREACH;
439        return;
440    }
441    bodylen = tc->pos - (body - tc->buf);
442
443    /* Find the beginning of the dictionary */
444    for( i = 0; i < bodylen; i++ )
445    {
446        if( body[i] == 'd' )
447        {
448            /* This must be it */
449            break;
450        }
451    }
452
453    if( i >= bodylen )
454    {
455        if( tc->stopped || 0 < tc->newPort )
456        {
457            tc->lastAttempt = TC_ATTEMPT_OK;
458            goto nodict;
459        }
460        tr_err( "Tracker: no dictionary in answer" );
461        tc->lastAttempt = TC_ATTEMPT_ERROR;
462        return;
463    }
464
465    if( tr_bencLoad( &body[i], bodylen - i, &beAll, NULL ) )
466    {
467        tr_err( "Tracker: error parsing bencoded data" );
468        tc->lastAttempt = TC_ATTEMPT_ERROR;
469        return;
470    }
471
472    // tr_bencPrint( &beAll );
473
474    if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) )
475    {
476        tr_err( "Tracker: %s", bePeers->val.s.s );
477        tor->error |= TR_ETRACKER;
478        snprintf( tor->trackerError, sizeof( tor->trackerError ),
479                  "%s", bePeers->val.s.s );
480        tc->lastAttempt = TC_ATTEMPT_ERROR;
481        goto cleanup;
482    }
483
484    tor->error &= ~TR_ETRACKER;
485    tc->lastAttempt = TC_ATTEMPT_OK;
486
487    if( !tc->interval )
488    {
489        /* Get the tracker interval, ignore it if it is not between
490           10 sec and 5 mins */
491        if( !( beFoo = tr_bencDictFind( &beAll, "interval" ) ) ||
492            !( beFoo->type & TYPE_INT ) )
493        {
494            tr_err( "Tracker: no 'interval' field" );
495            goto cleanup;
496        }
497
498        tc->interval = beFoo->val.i;
499        tc->interval = MIN( tc->interval, 300 );
500        tc->interval = MAX( 10, tc->interval );
501
502        tr_inf( "Tracker: interval = %d seconds", tc->interval );
503    }
504
505    if( ( beFoo = tr_bencDictFind( &beAll, "complete" ) ) &&
506        ( beFoo->type & TYPE_INT ) )
507    {
508        tc->seeders = beFoo->val.i;
509    }
510    if( ( beFoo = tr_bencDictFind( &beAll, "incomplete" ) ) &&
511        ( beFoo->type & TYPE_INT ) )
512    {
513        tc->leechers = beFoo->val.i;
514    }
515    if( tc->seeders + tc->leechers >= 50 )
516    {
517        tc->hasManyPeers = 1;
518    }
519
520    if( !( bePeers = tr_bencDictFind( &beAll, "peers" ) ) )
521    {
522        if( tc->stopped || 0 < tc->newPort )
523        {
524            goto nodict;
525        }
526        tr_err( "Tracker: no \"peers\" field" );
527        goto cleanup;
528    }
529
530    if( bePeers->type & TYPE_LIST )
531    {
532        char * ip;
533        int    port;
534
535        /* Original protocol */
536        tr_inf( "Tracker: got %d peers", bePeers->val.l.count );
537
538        for( i = 0; i < bePeers->val.l.count; i++ )
539        {
540            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "ip" );
541            if( !beFoo )
542                continue;
543            ip = beFoo->val.s.s;
544            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "port" );
545            if( !beFoo )
546                continue;
547            port = beFoo->val.i;
548
549            tr_peerAddOld( tor, ip, port );
550        }
551
552        if( bePeers->val.l.count >= 50 )
553        {
554            tc->hasManyPeers = 1;
555        }
556    }
557    else if( bePeers->type & TYPE_STR )
558    {
559        struct in_addr addr;
560        in_port_t      port;
561
562        /* "Compact" extension */
563        if( bePeers->val.s.i % 6 )
564        {
565            tr_err( "Tracker: \"peers\" of size %d",
566                    bePeers->val.s.i );
567            tr_lockUnlock( &tor->lock );
568            goto cleanup;
569        }
570
571        tr_inf( "Tracker: got %d peers", bePeers->val.s.i / 6 );
572        for( i = 0; i < bePeers->val.s.i / 6; i++ )
573        {
574            memcpy( &addr, &bePeers->val.s.s[6*i],   4 );
575            memcpy( &port, &bePeers->val.s.s[6*i+4], 2 );
576
577            tr_peerAddCompact( tor, addr, port );
578        }
579
580        if( bePeers->val.s.i / 6 >= 50 )
581        {
582            tc->hasManyPeers = 1;
583        }
584    }
585
586nodict:
587    /* Success */
588    tc->started   = 0;
589    tc->completed = 0;
590    tc->dateOk    = tr_date();
591
592    if( tc->stopped )
593    {
594        tor->status = TR_STATUS_STOPPED;
595        tc->stopped = 0;
596    }
597    else if( 0 < tc->newPort )
598    {
599        tc->started  = 1;
600        tc->download = tor->downloaded;
601        tc->upload   = tor->uploaded;
602    }
603
604cleanup:
605    tr_bencFree( &beAll );
606}
607
608int tr_trackerScrape( tr_torrent_t * tor, int * seeders, int * leechers )
609{
610    tr_info_t * inf = &tor->info;
611
612    int s, i, ret;
613    uint8_t buf[1024];
614    benc_val_t scrape, * val1, * val2;
615    struct in_addr addr;
616    uint64_t date;
617    int pos, len;
618
619    if( !tor->scrape[0] )
620    {
621        /* scrape not supported */
622        return 1;
623    }
624
625    if( tr_netResolve( inf->trackerAddress, &addr ) )
626    {
627        return 0;
628    }
629    s = tr_netOpen( addr, htons( inf->trackerPort ) );
630    if( s < 0 )
631    {
632        return 1;
633    }
634
635    len = snprintf( (char *) buf, sizeof( buf ),
636              "GET %s?info_hash=%s HTTP/1.1\r\n"
637              "Host: %s\r\n"
638              "Connection: close\r\n\r\n",
639              tor->scrape, tor->hashString,
640              inf->trackerAddress );
641
642    for( date = tr_date();; )
643    {
644        ret = tr_netSend( s, buf, len );
645        if( ret & TR_NET_CLOSE )
646        {
647            fprintf( stderr, "Could not connect to tracker\n" );
648            tr_netClose( s );
649            return 1;
650        }
651        else if( ret & TR_NET_BLOCK )
652        {
653            if( tr_date() > date + 10000 )
654            {
655                fprintf( stderr, "Could not connect to tracker\n" );
656                tr_netClose( s );
657                return 1;
658            }
659        }
660        else
661        {
662            break;
663        }
664        tr_wait( 10 );
665    }
666
667    pos = 0;
668    for( date = tr_date();; )
669    {
670        ret = tr_netRecv( s, &buf[pos], sizeof( buf ) - pos );
671        if( ret & TR_NET_CLOSE )
672        {
673            break;
674        }
675        else if( ret & TR_NET_BLOCK )
676        {
677            if( tr_date() > date + 10000 )
678            {
679                fprintf( stderr, "Could not read from tracker\n" );
680                tr_netClose( s );
681                return 1;
682            }
683        }
684        else
685        {
686            pos += ret;
687        }
688        tr_wait( 10 );
689    }
690
691    if( pos < 1 )
692    {
693        fprintf( stderr, "Could not read from tracker\n" );
694        tr_netClose( s );
695        return 1;
696    }
697
698    for( i = 0; i < pos - 8; i++ )
699    {
700        if( !memcmp( &buf[i], "d5:files", 8 ) )
701        {
702            break;
703        }
704    }
705    if( i >= pos - 8 )
706    {
707        return 1;
708    }
709    if( tr_bencLoad( &buf[i], pos - i, &scrape, NULL ) )
710    {
711        return 1;
712    }
713
714    val1 = tr_bencDictFind( &scrape, "files" );
715    if( !val1 )
716    {
717        return 1;
718    }
719    val1 = &val1->val.l.vals[1];
720    if( !val1 )
721    {
722        return 1;
723    }
724    val2 = tr_bencDictFind( val1, "complete" );
725    if( !val2 )
726    {
727        return 1;
728    }
729    *seeders = val2->val.i;
730    val2 = tr_bencDictFind( val1, "incomplete" );
731    if( !val2 )
732    {
733        return 1;
734    }
735    *leechers = val2->val.i;
736    tr_bencFree( &scrape );
737
738    return 0;
739}
740
741int tr_trackerSeeders( tr_tracker_t * tc )
742{
743    if( !tc )
744    {
745        return -1;
746    }
747    return tc->seeders;
748}
749
750int tr_trackerLeechers( tr_tracker_t * tc )
751{
752    if( !tc )
753    {
754        return -1;
755    }
756    return tc->leechers;
757}
Note: See TracBrowser for help on using the repository browser.