source: branches/new_api/libtransmission/tracker.c @ 137

Last change on this file since 137 was 137, checked in by titer, 16 years ago

Adds a permanent handle for every torrent.
Removed the limit on the number of open torrents.

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