source: trunk/libtransmission/tracker.c @ 1

Last change on this file since 1 was 1, checked in by root, 15 years ago

Import from 2005-10-26

File size: 14.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
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  = (uint64_t) ( tor->blockCount - tor->blockHaveCount ) *
249            (uint64_t) tor->blockSize;
250    left  = MIN( left, inf->totalSize );
251
252    ret = snprintf( (char *) tc->buf, tc->size,
253              "GET %s?info_hash=%s&peer_id=%s&port=%d&uploaded=%lld&"
254              "downloaded=%lld&left=%lld&compact=1&numwant=50%s "
255              "HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n",
256              inf->trackerAnnounce, tor->hashString, tc->id,
257              tor->bindPort, tor->uploaded[9], tor->downloaded[9],
258              left, event, inf->trackerAddress );
259
260    ret = tr_netSend( tc->socket, tc->buf, ret );
261    if( ret & TR_NET_CLOSE )
262    {
263        tr_inf( "Tracker: connection failed" );
264        tr_netClose( tc->socket );
265        tr_fdSocketClosed( tor->fdlimit, 1 );
266        tc->status  = TC_STATUS_IDLE;
267        tc->dateTry = tr_date();
268    }
269    else if( !( ret & TR_NET_BLOCK ) )
270    {
271        // printf( "Tracker: sent %s", tc->buf );
272        tc->status = TC_STATUS_RECV;
273        tc->pos    = 0;
274    }
275}
276
277static void recvAnswer( tr_tracker_t * tc )
278{
279    tr_torrent_t * tor = tc->tor;
280    int ret;
281    int i;
282    benc_val_t   beAll;
283    benc_val_t * bePeers, * beFoo;
284
285    if( tc->pos == tc->size )
286    {
287        tc->size *= 2;
288        tc->buf   = realloc( tc->buf, tc->size );
289    }
290   
291    ret = tr_netRecv( tc->socket, &tc->buf[tc->pos],
292                    tc->size - tc->pos );
293
294    if( ret & TR_NET_BLOCK )
295    {
296        return;
297    }
298    if( !( ret & TR_NET_CLOSE ) )
299    {
300        // printf( "got %d bytes\n", ret );
301        tc->pos += ret;
302        return;
303    }
304
305    tr_netClose( tc->socket );
306    tr_fdSocketClosed( tor->fdlimit, 1 );
307    // printf( "connection closed, got total %d bytes\n", tc->pos );
308
309    tc->status  = TC_STATUS_IDLE;
310    tc->dateTry = tr_date();
311
312    if( tc->pos < 1 )
313    {
314        /* We got nothing */
315        return;
316    }
317
318    /* Find the beginning of the dictionary */
319    for( i = 0; i < tc->pos - 18; i++ )
320    {
321        /* Hem */
322        if( !memcmp( &tc->buf[i], "d8:interval", 11 ) ||
323            !memcmp( &tc->buf[i], "d8:complete", 11 ) ||
324            !memcmp( &tc->buf[i], "d14:failure reason", 18 ) )
325        {
326            break;
327        }
328    }
329
330    if( i >= tc->pos - 18 )
331    {
332        tr_err( "Tracker: no dictionary in answer" );
333        // printf( "%s\n", tc->buf );
334        return;
335    }
336
337    if( tr_bencLoad( &tc->buf[i], &beAll, NULL ) )
338    {
339        tr_err( "Tracker: error parsing bencoded data" );
340        return;
341    }
342
343    // tr_bencPrint( &beAll );
344
345    if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) )
346    {
347        tr_err( "Tracker: %s", bePeers->val.s.s );
348        tor->status |= TR_TRACKER_ERROR;
349        snprintf( tor->error, sizeof( tor->error ),
350                  bePeers->val.s.s );
351        goto cleanup;
352    }
353
354    tor->status &= ~TR_TRACKER_ERROR;
355
356    if( !tc->interval )
357    {
358        /* Get the tracker interval, ignore it if it is not between
359           10 sec and 5 mins */
360        if( !( beFoo = tr_bencDictFind( &beAll, "interval" ) ) ||
361            !( beFoo->type & TYPE_INT ) )
362        {
363            tr_err( "Tracker: no 'interval' field" );
364            goto cleanup;
365        }
366
367        tc->interval = beFoo->val.i;
368        tc->interval = MIN( tc->interval, 300 );
369        tc->interval = MAX( 10, tc->interval );
370
371        tr_inf( "Tracker: interval = %d seconds", tc->interval );
372    }
373
374    if( ( beFoo = tr_bencDictFind( &beAll, "complete" ) ) &&
375        ( beFoo->type & TYPE_INT ) )
376    {
377        tc->seeders = beFoo->val.i;
378    }
379    if( ( beFoo = tr_bencDictFind( &beAll, "incomplete" ) ) &&
380        ( beFoo->type & TYPE_INT ) )
381    {
382        tc->leechers = beFoo->val.i;
383    }
384    if( tc->seeders + tc->seeders >= 50 )
385    {
386        tc->hasManyPeers = 1;
387    }
388
389    if( !( bePeers = tr_bencDictFind( &beAll, "peers" ) ) )
390    {
391        tr_err( "Tracker: no \"peers\" field" );
392        goto cleanup;
393    }
394
395    if( bePeers->type & TYPE_LIST )
396    {
397        char * ip;
398        int    port;
399
400        /* Original protocol */
401        tr_inf( "Tracker: got %d peers", bePeers->val.l.count );
402
403        for( i = 0; i < bePeers->val.l.count; i++ )
404        {
405            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "ip" );
406            if( !beFoo )
407                continue;
408            ip = beFoo->val.s.s;
409            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "port" );
410            if( !beFoo )
411                continue;
412            port = beFoo->val.i;
413
414            tr_peerAddOld( tor, ip, port );
415        }
416
417        if( bePeers->val.l.count >= 50 )
418        {
419            tc->hasManyPeers = 1;
420        }
421    }
422    else if( bePeers->type & TYPE_STR )
423    {
424        struct in_addr addr;
425        in_port_t      port;
426
427        /* "Compact" extension */
428        if( bePeers->val.s.i % 6 )
429        {
430            tr_err( "Tracker: \"peers\" of size %d",
431                    bePeers->val.s.i );
432            tr_lockUnlock( tor->lock );
433            goto cleanup;
434        }
435
436        tr_inf( "Tracker: got %d peers", bePeers->val.s.i / 6 );
437        for( i = 0; i < bePeers->val.s.i / 6; i++ )
438        {
439            memcpy( &addr, &bePeers->val.s.s[6*i],   4 );
440            memcpy( &port, &bePeers->val.s.s[6*i+4], 2 );
441
442            tr_peerAddCompact( tor, addr, port, -1 );
443        }
444
445        if( bePeers->val.s.i / 6 >= 50 )
446        {
447            tc->hasManyPeers = 1;
448        }
449    }
450
451    /* Success */
452    tc->started   = 0;
453    tc->completed = 0;
454    tc->dateOk    = tr_date();
455
456    if( tc->stopped )
457    {
458        tor->status = TR_STATUS_STOPPED;
459        tc->stopped = 0;
460    }
461
462cleanup:
463    tr_bencFree( &beAll );
464}
465
466int tr_trackerScrape( tr_torrent_t * tor, int * seeders, int * leechers )
467{
468    tr_info_t * inf = &tor->info;
469
470    int s, i, ret;
471    uint8_t buf[1024];
472    benc_val_t scrape, * val1, * val2;
473    struct in_addr addr;
474    uint64_t date;
475    int pos, len;
476
477    if( !tor->scrape[0] )
478    {
479        /* scrape not supported */
480        return 1;
481    }
482
483    if( tr_netResolve( inf->trackerAddress, &addr ) )
484    {
485        return 0;
486    }
487    s = tr_netOpen( addr, htons( inf->trackerPort ) );
488    if( s < 0 )
489    {
490        return 1;
491    }
492
493    len = snprintf( (char *) buf, sizeof( buf ),
494              "GET %s?info_hash=%s HTTP/1.1\r\n"
495              "Host: %s\r\n"
496              "Connection: close\r\n\r\n",
497              tor->scrape, tor->hashString,
498              inf->trackerAddress );
499
500    for( date = tr_date();; )
501    {
502        ret = tr_netSend( s, buf, len );
503        if( ret & TR_NET_CLOSE )
504        {
505            fprintf( stderr, "Could not connect to tracker\n" );
506            tr_netClose( s );
507            return 1;
508        }
509        else if( ret & TR_NET_BLOCK )
510        {
511            if( tr_date() > date + 10000 )
512            {
513                fprintf( stderr, "Could not connect to tracker\n" );
514                tr_netClose( s );
515                return 1;
516            }
517        }
518        else
519        {
520            break;
521        }
522        tr_wait( 10 );
523    }
524
525    pos = 0;
526    for( date = tr_date();; )
527    {
528        ret = tr_netRecv( s, &buf[pos], sizeof( buf ) - pos );
529        if( ret & TR_NET_CLOSE )
530        {
531            break;
532        }
533        else if( ret & TR_NET_BLOCK )
534        {
535            if( tr_date() > date + 10000 )
536            {
537                fprintf( stderr, "Could not read from tracker\n" );
538                tr_netClose( s );
539                return 1;
540            }
541        }
542        else
543        {
544            pos += ret;
545        }
546        tr_wait( 10 );
547    }
548
549    if( pos < 1 )
550    {
551        fprintf( stderr, "Could not read from tracker\n" );
552        tr_netClose( s );
553        return 1;
554    }
555
556    for( i = 0; i < ret - 8; i++ )
557    {
558        if( !memcmp( &buf[i], "d5:files", 8 ) )
559        {
560            break;
561        }
562    }
563    if( i >= ret - 8 )
564    {
565        return 1;
566    }
567    if( tr_bencLoad( &buf[i], &scrape, NULL ) )
568    {
569        return 1;
570    }
571
572    val1 = tr_bencDictFind( &scrape, "files" );
573    if( !val1 )
574    {
575        return 1;
576    }
577    val1 = &val1->val.l.vals[1];
578    if( !val1 )
579    {
580        return 1;
581    }
582    val2 = tr_bencDictFind( val1, "complete" );
583    if( !val2 )
584    {
585        return 1;
586    }
587    *seeders = val2->val.i;
588    val2 = tr_bencDictFind( val1, "incomplete" );
589    if( !val2 )
590    {
591        return 1;
592    }
593    *leechers = val2->val.i;
594    tr_bencFree( &scrape );
595
596    return 0;
597}
Note: See TracBrowser for help on using the repository browser.