source: branches/oneport/libtransmission/tracker.c @ 42

Last change on this file since 42 was 42, checked in by joshe, 16 years ago

Whoops, use the empty stop response workaround when changing ports too.
Turn off the port debug code.

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