source: trunk/libtransmission/announcer-http.c @ 12141

Last change on this file since 12141 was 12141, checked in by jordan, 11 years ago

(trunk libT) #117 "UDP tracker protocol support" -- working implementation; needs testing and valgrinding and review.

File size: 14.2 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id:$
11 */
12
13#include <event2/buffer.h>
14#include <event2/http.h> /* for HTTP_OK */
15
16#define __LIBTRANSMISSION_ANNOUNCER_MODULE___
17
18#include "transmission.h"
19#include "announcer-common.h"
20#include "limits.h"
21#include "net.h" /* tr_globalIPv6() */
22#include "peer-mgr.h" /* pex */
23#include "torrent.h"
24#include "trevent.h" /* tr_runInEventThread() */
25#include "web.h" /* tr_http_escape() */
26
27#define dbgmsg( name, ... ) \
28if( tr_deepLoggingIsActive( ) ) do { \
29  tr_deepLog( __FILE__, __LINE__, name, __VA_ARGS__ ); \
30} while( 0 )
31
32/****
33*****
34*****  ANNOUNCE
35*****
36****/
37
38static const char*
39get_event_string( const tr_announce_request * req )
40{
41    if( req->partial_seed )
42        if( req->event != TR_ANNOUNCE_EVENT_STOPPED )
43            return "paused";
44
45    return tr_announce_event_get_string( req->event );
46}
47
48static char*
49announce_url_new( const tr_session * session, const tr_announce_request * req )
50{
51    const char * str;
52    const unsigned char * ipv6;
53    struct evbuffer * buf = evbuffer_new( );
54    char escaped_info_hash[SHA_DIGEST_LENGTH*3 + 1];
55
56    tr_http_escape_sha1( escaped_info_hash, req->info_hash );
57
58    evbuffer_expand( buf, 1024 );
59
60    evbuffer_add_printf( buf, "%s"
61                              "%c"
62                              "info_hash=%s"
63                              "&peer_id=%*.*s"
64                              "&port=%d"
65                              "&uploaded=%" PRIu64
66                              "&downloaded=%" PRIu64
67                              "&left=%" PRIu64
68                              "&numwant=%d"
69                              "&key=%x"
70                              "&compact=1"
71                              "&supportcrypto=1",
72                              req->url,
73                              strchr( req->url, '?' ) ? '&' : '?',
74                              escaped_info_hash,
75                              PEER_ID_LEN, PEER_ID_LEN, req->peer_id,
76                              req->port,
77                              req->up,
78                              req->down,
79                              req->left,
80                              req->numwant,
81                              req->key );
82
83    if( session->encryptionMode == TR_ENCRYPTION_REQUIRED )
84        evbuffer_add_printf( buf, "&requirecrypto=1" );
85
86    if( req->corrupt )
87        evbuffer_add_printf( buf, "&corrupt=%" PRIu64, req->corrupt );
88
89    str = get_event_string( req );
90    if( str && *str )
91        evbuffer_add_printf( buf, "&event=%s", str );
92
93    str = req->tracker_id_str;
94    if( str && *str )
95        evbuffer_add_printf( buf, "&trackerid=%s", str );
96
97    /* There are two incompatible techniques for announcing an IPv6 address.
98       BEP-7 suggests adding an "ipv6=" parameter to the announce URL,
99       while OpenTracker requires that peers announce twice, once over IPv4
100       and once over IPv6.
101
102       To be safe, we should do both: add the "ipv6=" parameter and
103       announce twice. At any rate, we're already computing our IPv6
104       address (for the LTEP handshake), so this comes for free. */
105
106    ipv6 = tr_globalIPv6( );
107    if( ipv6 ) {
108        char ipv6_readable[INET6_ADDRSTRLEN];
109        inet_ntop( AF_INET6, ipv6, ipv6_readable, INET6_ADDRSTRLEN );
110        evbuffer_add_printf( buf, "&ipv6=");
111        tr_http_escape( buf, ipv6_readable, -1, TRUE );
112    }
113
114    return evbuffer_free_to_str( buf );
115}
116
117static tr_pex*
118listToPex( tr_benc * peerList, size_t * setme_len )
119{
120    size_t i;
121    size_t n;
122    const size_t len = tr_bencListSize( peerList );
123    tr_pex * pex = tr_new0( tr_pex, len );
124
125    for( i=n=0; i<len; ++i )
126    {
127        int64_t port;
128        const char * ip;
129        tr_address addr;
130        tr_benc * peer = tr_bencListChild( peerList, i );
131
132        if( peer == NULL )
133            continue;
134        if( !tr_bencDictFindStr( peer, "ip", &ip ) )
135            continue;
136        if( tr_pton( ip, &addr ) == NULL )
137            continue;
138        if( !tr_bencDictFindInt( peer, "port", &port ) )
139            continue;
140        if( ( port < 0 ) || ( port > USHRT_MAX ) )
141            continue;
142        if( !tr_isValidPeerAddress( &addr, port ) )
143            continue;
144
145        pex[n].addr = addr;
146        pex[n].port = htons( (uint16_t)port );
147        ++n;
148    }
149
150    *setme_len = n;
151    return pex;
152}
153
154struct announce_data
155{
156    tr_session * session;
157    tr_announce_response response;
158    tr_announce_response_func * response_func;
159    void * response_func_user_data;
160    char log_name[128];
161};
162
163static void
164on_announce_done_eventthread( void * vdata )
165{
166    struct announce_data * data = vdata;
167
168    if( data->response_func != NULL )
169        data->response_func( data->session,
170                             &data->response,
171                             data->response_func_user_data );
172
173    tr_free( data->response.pex6 );
174    tr_free( data->response.pex );
175    tr_free( data->response.tracker_id_str );
176    tr_free( data->response.warning );
177    tr_free( data->response.errmsg );
178    tr_free( data );
179}
180
181
182static void
183on_announce_done( tr_session   * session,
184                  tr_bool        did_connect,
185                  tr_bool        did_timeout,
186                  long           response_code,
187                  const void   * msg,
188                  size_t         msglen,
189                  void         * vdata )
190{
191    tr_announce_response * response;
192    struct announce_data * data = vdata;
193
194    response = &data->response;
195    response->did_connect = did_connect;
196    response->did_timeout = did_timeout;
197    dbgmsg( data->log_name, "Got announce response" );
198
199    if( response_code != HTTP_OK )
200    {
201        const char * fmt = _( "Tracker gave HTTP response code %1$ld (%2$s)" );
202        const char * response_str = tr_webGetResponseStr( response_code );
203        response->errmsg = tr_strdup_printf( fmt, response_code, response_str );
204    }
205    else
206    {
207        tr_benc benc;
208        const int benc_loaded = !tr_bencLoad( msg, msglen, &benc, NULL );
209
210        if( getenv( "TR_CURL_VERBOSE" ) != NULL )
211        {
212            char * str = tr_bencToStr( &benc, TR_FMT_JSON, NULL );
213            fprintf( stderr, "Announce response:\n< %s\n", str );
214            tr_free( str );
215        }
216
217        if( benc_loaded && tr_bencIsDict( &benc ) )
218        {
219            int64_t i;
220            size_t rawlen;
221            tr_benc * tmp;
222            const char * str;
223            const uint8_t * raw;
224
225            if( tr_bencDictFindStr( &benc, "failure reason", &str ) )
226                response->errmsg = tr_strdup( str );
227
228            if( tr_bencDictFindStr( &benc, "warning message", &str ) )
229                response->warning = tr_strdup( str );
230
231            if( tr_bencDictFindInt( &benc, "interval", &i ) )
232                response->interval = i;
233
234            if( tr_bencDictFindInt( &benc, "min interval", &i ) )
235                response->min_interval = i;
236
237            if( tr_bencDictFindStr( &benc, "tracker id", &str ) )
238                response->tracker_id_str = tr_strdup( str );
239
240            if( tr_bencDictFindInt( &benc, "complete", &i ) )
241                response->seeders = i;
242
243            if( tr_bencDictFindInt( &benc, "incomplete", &i ) )
244                response->leechers = i;
245
246            if( tr_bencDictFindInt( &benc, "downloaded", &i ) )
247                response->downloads = i;
248
249            if( tr_bencDictFindRaw( &benc, "peers6", &raw, &rawlen ) ) {
250                dbgmsg( data->log_name, "got a peers6 length of %zu", rawlen );
251                response->pex6 = tr_peerMgrCompact6ToPex( raw, rawlen,
252                                              NULL, 0, &response->pex6_count );
253            }
254
255            if( tr_bencDictFindRaw( &benc, "peers", &raw, &rawlen ) ) {
256                dbgmsg( data->log_name, "got a compact peers length of %zu", rawlen );
257                response->pex = tr_peerMgrCompactToPex( raw, rawlen,
258                                               NULL, 0, &response->pex_count );
259            } else if( tr_bencDictFindList( &benc, "peers", &tmp ) ) {
260                response->pex = listToPex( tmp, &response->pex_count );
261                dbgmsg( data->log_name, "got a peers list with %zu entries",
262                        response->pex_count );
263            }
264        }
265
266        if( benc_loaded )
267            tr_bencFree( &benc );
268    }
269
270    tr_runInEventThread( session, on_announce_done_eventthread, data );
271}
272
273void
274tr_tracker_http_announce( tr_session                 * session,
275                          const tr_announce_request  * request,
276                          tr_announce_response_func    response_func,
277                          void                       * response_func_user_data )
278{
279    struct announce_data * d;
280    char * url = announce_url_new( session, request );
281
282    d = tr_new0( struct announce_data, 1 );
283    d->session = session;
284    d->response_func = response_func;
285    d->response_func_user_data = response_func_user_data;
286    memcpy( d->response.info_hash, request->info_hash, SHA_DIGEST_LENGTH );
287    tr_strlcpy( d->log_name, request->log_name, sizeof( d->log_name ) );
288
289    dbgmsg( request->log_name, "Sending announce to libcurl: \"%s\"", url );
290    tr_webRun( session, url, NULL, on_announce_done, d );
291    tr_free( url );
292}
293
294/****
295*****
296*****  SCRAPE
297*****
298****/
299
300struct scrape_data
301{
302    tr_session * session;
303    tr_scrape_response response;
304    tr_scrape_response_func * response_func;
305    void * response_func_user_data;
306    char log_name[128];
307};
308
309static void
310on_scrape_done_eventthread( void * vdata )
311{
312    struct scrape_data * data = vdata;
313
314    if( data->response_func != NULL )
315        data->response_func( data->session,
316                             &data->response,
317                             data->response_func_user_data );
318
319    tr_free( data->response.errmsg );
320    tr_free( data->response.url );
321    tr_free( data );
322}
323
324static void
325on_scrape_done( tr_session   * session,
326                tr_bool        did_connect,
327                tr_bool        did_timeout,
328                long           response_code,
329                const void   * msg,
330                size_t         msglen,
331                void         * vdata )
332{
333    tr_scrape_response * response;
334    struct scrape_data * data = vdata;
335
336    response = &data->response;
337    response->did_connect = did_connect;
338    response->did_timeout = did_timeout;
339    dbgmsg( data->log_name, "Got scrape response for \"%s\"", response->url );
340
341    if( response_code != HTTP_OK )
342    {
343        const char * fmt = _( "Tracker gave HTTP response code %1$ld (%2$s)" );
344        const char * response_str = tr_webGetResponseStr( response_code );
345        response->errmsg = tr_strdup_printf( fmt, response_code, response_str );
346    }
347    else
348    {
349        tr_benc top;
350        int64_t intVal;
351        tr_benc * files;
352        const char * str;
353        const int benc_loaded = !tr_bencLoad( msg, msglen, &top, NULL );
354        if( benc_loaded )
355        {
356            if( tr_bencDictFindStr( &top, "failure reason", &str ) )
357                response->errmsg = tr_strdup( str );
358
359            if( tr_bencDictFindInt( &top, "min_request_interval", &intVal ) )
360                response->min_request_interval = intVal;
361
362            if( tr_bencDictFindDict( &top, "files", &files ) )
363            {
364                int i = 0;
365
366                for( ;; )
367                {
368                    int j;
369                    tr_benc * val;
370                    const char * key;
371
372                    /* get the next "file" */
373                    if( !tr_bencDictChild( files, i++, &key, &val ) )
374                        break;
375
376                    /* populate the corresponding row in our response array */
377                    for( j=0; j<response->row_count; ++j )
378                    {
379                        struct tr_scrape_response_row * row = &response->rows[j];
380                        if( !memcmp( key, row->info_hash, SHA_DIGEST_LENGTH ) )
381                        {
382                            if( tr_bencDictFindInt( val, "complete", &intVal ) )
383                                row->seeders = intVal;
384                            if( tr_bencDictFindInt( val, "incomplete", &intVal ) )
385                                row->leechers = intVal;
386                            if( tr_bencDictFindInt( val, "downloaded", &intVal ) )
387                                row->downloads = intVal;
388                            if( tr_bencDictFindInt( val, "downloaders", &intVal ) )
389                                row->downloaders = intVal;
390                            break;
391                        }
392                    }
393                }
394            }
395
396            tr_bencFree( &top );
397        }
398    }
399
400    tr_runInEventThread( session, on_scrape_done_eventthread, data );
401}
402
403static char*
404scrape_url_new( const tr_scrape_request * req )
405{
406    int i;
407    char delimiter;
408    struct evbuffer * buf = evbuffer_new( );
409
410    evbuffer_add_printf( buf, "%s", req->url );
411    delimiter = strchr( req->url, '?' ) ? '&' : '?';
412    for( i=0; i<req->info_hash_count; ++i )
413    {
414        char str[SHA_DIGEST_LENGTH*3 + 1];
415        tr_http_escape_sha1( str, req->info_hash[i] );
416        evbuffer_add_printf( buf, "%cinfo_hash=%s", delimiter, str );
417        delimiter = '&';
418    }
419
420    return evbuffer_free_to_str( buf );
421}
422
423void
424tr_tracker_http_scrape( tr_session               * session,
425                        const tr_scrape_request  * request,
426                        tr_scrape_response_func    response_func,
427                        void                     * response_func_user_data )
428{
429    int i;
430    struct scrape_data * d;
431    char * url = scrape_url_new( request );
432
433    d = tr_new0( struct scrape_data, 1 );
434    d->session = session;
435    d->response.url = tr_strdup( request->url );
436    d->response_func = response_func;
437    d->response_func_user_data = response_func_user_data;
438    d->response.row_count = request->info_hash_count;
439    for( i=0; i<d->response.row_count; ++i )
440        memcpy( d->response.rows[i].info_hash, request->info_hash[i], SHA_DIGEST_LENGTH );
441    tr_strlcpy( d->log_name, request->log_name, sizeof( d->log_name ) );
442
443    dbgmsg( request->log_name, "Sending scrape to libcurl: \"%s\"", url );
444    tr_webRun( session, url, NULL, on_scrape_done, d );
445    tr_free( url );
446}
Note: See TracBrowser for help on using the repository browser.