source: trunk/libtransmission/tracker.c @ 26

Last change on this file since 26 was 26, checked in by root, 16 years ago

Update 2006-01-11

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