source: branches/oneport/libtransmission/tracker.c @ 44

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

Do some basic HTTP header parsing to check for 2xx response code.
Start looking for dictionary in the body instead of the entire response.
Add a memmem() implementation.

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