source: trunk/libtransmission/tracker.c @ 164

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

Adds non-blocking (threaded) DNS resolution

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