source: trunk/libtransmission/tracker.c @ 20

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

Update 2005-12-25

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