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

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

(trunk libT) #117 "UDP tracker protocol support (BEP #15)" -- refactor announcer.c so that alternate tracker protocols can be supported.

This commit adds a set of package-visible structs and functions to allow delegating announces and scrapes to different protocol handlers. (Examples: struct tr_announce_request, struct tr_announce_response, struct tr_scrape_request, struct tr_scrape_response.) HTTP is the only protocol handler currently implemented; however, this provides a clean API for other protocol handlers, and having this in trunk will help shake out any bugs in this refactoring.

In addition, logging via the TR_DEBUG_FD environment variable is vastly improved in the announcer module now.

File size: 14.3 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                              (int)tr_sessionGetPublicPeerPort( session ),
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    int torrent_id;
157    tr_session * session;
158    tr_announce_response response;
159    tr_announce_response_func * response_func;
160    void * response_func_user_data;
161    char log_name[128];
162};
163
164static void
165on_announce_done_eventthread( void * vdata )
166{
167    struct announce_data * data = vdata;
168
169    if( data->response_func != NULL )
170        data->response_func( data->session,
171                             &data->response,
172                             data->response_func_user_data );
173
174    tr_free( data->response.pex6 );
175    tr_free( data->response.pex );
176    tr_free( data->response.tracker_id_str );
177    tr_free( data->response.warning );
178    tr_free( data->response.errmsg );
179    tr_free( data );
180}
181
182
183static void
184on_announce_done( tr_session   * session,
185                  tr_bool        did_connect,
186                  tr_bool        did_timeout,
187                  long           response_code,
188                  const void   * msg,
189                  size_t         msglen,
190                  void         * vdata )
191{
192    tr_announce_response * response;
193    struct announce_data * data = vdata;
194
195    response = &data->response;
196    response->did_connect = did_connect;
197    response->did_timeout = did_timeout;
198    dbgmsg( data->log_name, "Got announce response" );
199
200    if( response_code != HTTP_OK )
201    {
202        const char * fmt = _( "Tracker gave HTTP response code %1$ld (%2$s)" );
203        const char * response_str = tr_webGetResponseStr( response_code );
204        response->errmsg = tr_strdup_printf( fmt, response_code, response_str );
205    }
206    else
207    {
208        tr_benc benc;
209        const int benc_loaded = !tr_bencLoad( msg, msglen, &benc, NULL );
210
211        if( getenv( "TR_CURL_VERBOSE" ) != NULL )
212        {
213            char * str = tr_bencToStr( &benc, TR_FMT_JSON, NULL );
214            fprintf( stderr, "Announce response:\n< %s\n", str );
215            tr_free( str );
216        }
217
218        if( benc_loaded && tr_bencIsDict( &benc ) )
219        {
220            int64_t i;
221            size_t rawlen;
222            tr_benc * tmp;
223            const char * str;
224            const uint8_t * raw;
225
226            if( tr_bencDictFindStr( &benc, "failure reason", &str ) )
227                response->errmsg = tr_strdup( str );
228
229            if( tr_bencDictFindStr( &benc, "warning message", &str ) )
230                response->warning = tr_strdup( str );
231
232            if( tr_bencDictFindInt( &benc, "interval", &i ) )
233                response->interval = i;
234
235            if( tr_bencDictFindInt( &benc, "min interval", &i ) )
236                response->min_interval = i;
237
238            if( tr_bencDictFindStr( &benc, "tracker id", &str ) )
239                response->tracker_id_str = tr_strdup( str );
240
241            if( tr_bencDictFindInt( &benc, "complete", &i ) )
242                response->seeders = i;
243
244            if( tr_bencDictFindInt( &benc, "incomplete", &i ) )
245                response->leechers = i;
246
247            if( tr_bencDictFindInt( &benc, "downloaded", &i ) )
248                response->downloads = i;
249
250            if( tr_bencDictFindRaw( &benc, "peers6", &raw, &rawlen ) ) {
251                dbgmsg( data->log_name, "got a peers6 length of %zu", rawlen );
252                response->pex6 = tr_peerMgrCompact6ToPex( raw, rawlen,
253                                              NULL, 0, &response->pex6_count );
254            }
255
256            if( tr_bencDictFindRaw( &benc, "peers", &raw, &rawlen ) ) {
257                dbgmsg( data->log_name, "got a compact peers length of %zu", rawlen );
258                response->pex = tr_peerMgrCompactToPex( raw, rawlen,
259                                               NULL, 0, &response->pex_count );
260            } else if( tr_bencDictFindList( &benc, "peers", &tmp ) ) {
261                response->pex = listToPex( tmp, &response->pex_count );
262                dbgmsg( data->log_name, "got a peers list with %zu entries",
263                        response->pex_count );
264            }
265        }
266
267        if( benc_loaded )
268            tr_bencFree( &benc );
269    }
270
271    tr_runInEventThread( session, on_announce_done_eventthread, data );
272}
273
274void
275tr_tracker_http_announce( tr_session                 * session,
276                          const tr_announce_request  * request,
277                          tr_announce_response_func    response_func,
278                          void                       * response_func_user_data )
279{
280    struct announce_data * d;
281    char * url = announce_url_new( session, request );
282
283    d = tr_new0( struct announce_data, 1 );
284    d->session = session;
285    d->response_func = response_func;
286    d->response_func_user_data = response_func_user_data;
287    memcpy( d->response.info_hash, request->info_hash, SHA_DIGEST_LENGTH );
288    tr_strlcpy( d->log_name, request->log_name, sizeof( d->log_name ) );
289
290    dbgmsg( request->log_name, "Sending announce to libcurl: \"%s\"", url );
291    tr_webRun( session, url, NULL, on_announce_done, d );
292    tr_free( url );
293}
294
295/****
296*****
297*****  SCRAPE
298*****
299****/
300
301struct scrape_data
302{
303    tr_session * session;
304    tr_scrape_response response;
305    tr_scrape_response_func * response_func;
306    void * response_func_user_data;
307    char log_name[128];
308};
309
310static void
311on_scrape_done_eventthread( void * vdata )
312{
313    struct scrape_data * data = vdata;
314
315    if( data->response_func != NULL )
316        data->response_func( data->session,
317                             &data->response,
318                             data->response_func_user_data );
319
320    tr_free( data->response.errmsg );
321    tr_free( data->response.url );
322    tr_free( data );
323}
324
325static void
326on_scrape_done( tr_session   * session,
327                tr_bool        did_connect,
328                tr_bool        did_timeout,
329                long           response_code,
330                const void   * msg,
331                size_t         msglen,
332                void         * vdata )
333{
334    tr_scrape_response * response; 
335    struct scrape_data * data = vdata;
336
337    response = &data->response;
338    response->did_connect = did_connect;
339    response->did_timeout = did_timeout;
340    dbgmsg( data->log_name, "Got scrape response for \"%s\"", response->url );
341
342    if( response_code != HTTP_OK )
343    {
344        const char * fmt = _( "Tracker gave HTTP response code %1$ld (%2$s)" );
345        const char * response_str = tr_webGetResponseStr( response_code );
346        response->errmsg = tr_strdup_printf( fmt, response_code, response_str );
347    }
348    else
349    {
350        tr_benc top;
351        int64_t intVal;
352        tr_benc * files;
353        const char * str;
354        const int benc_loaded = !tr_bencLoad( msg, msglen, &top, NULL );
355        if( benc_loaded )
356        {
357            if( tr_bencDictFindStr( &top, "failure reason", &str ) )
358                response->errmsg = tr_strdup( str );
359
360            if( tr_bencDictFindInt( &top, "min_request_interval", &intVal ) )
361                response->min_request_interval = intVal;
362
363            if( tr_bencDictFindDict( &top, "files", &files ) )
364            {
365                int i = 0;
366
367                for( ;; )
368                {
369                    int j;
370                    tr_benc * val;
371                    const char * key;
372
373                    /* get the next "file" */
374                    if( !tr_bencDictChild( files, i++, &key, &val ) )
375                        break;
376
377                    /* populate the corresponding row in our response array */
378                    for( j=0; j<response->row_count; ++j )
379                    {
380                        struct tr_scrape_response_row * row = &response->rows[j];
381                        if( !memcmp( key, row->info_hash, SHA_DIGEST_LENGTH ) )
382                        {
383                            if( tr_bencDictFindInt( val, "complete", &intVal ) )
384                                row->seeders = intVal;
385                            if( tr_bencDictFindInt( val, "incomplete", &intVal ) )
386                                row->leechers = intVal;
387                            if( tr_bencDictFindInt( val, "downloaded", &intVal ) )
388                                row->downloads = intVal;
389                            if( tr_bencDictFindInt( val, "downloaders", &intVal ) )
390                                row->downloaders = intVal;
391                            break;
392                        }
393                    }
394                }
395            }
396
397            tr_bencFree( &top );
398        }
399    }
400
401    tr_runInEventThread( session, on_scrape_done_eventthread, data );
402}
403
404static char*
405scrape_url_new( const tr_scrape_request * req )
406{
407    int i;
408    char delimiter;
409    struct evbuffer * buf = evbuffer_new( );
410
411    evbuffer_add_printf( buf, "%s", req->url );
412    delimiter = strchr( req->url, '?' ) ? '&' : '?';
413    for( i=0; i<req->info_hash_count; ++i )
414    {
415        char str[SHA_DIGEST_LENGTH*3 + 1];
416        tr_http_escape_sha1( str, req->info_hash[i] );
417        evbuffer_add_printf( buf, "%cinfo_hash=%s", delimiter, str );
418        delimiter = '&';
419    }
420
421    return evbuffer_free_to_str( buf );
422}
423
424void
425tr_tracker_http_scrape( tr_session               * session,
426                        const tr_scrape_request  * request,
427                        tr_scrape_response_func    response_func,
428                        void                     * response_func_user_data )
429{
430    int i;
431    struct scrape_data * d;
432    char * url = scrape_url_new( request );
433
434    d = tr_new0( struct scrape_data, 1 );
435    d->session = session;
436    d->response.url = tr_strdup( request->url );
437    d->response_func = response_func;
438    d->response_func_user_data = response_func_user_data;
439    d->response.row_count = request->info_hash_count;
440    for( i=0; i<d->response.row_count; ++i )
441        memcpy( d->response.rows[i].info_hash, request->info_hash[i], SHA_DIGEST_LENGTH );
442    tr_strlcpy( d->log_name, request->log_name, sizeof( d->log_name ) );
443
444    dbgmsg( request->log_name, "Sending scrape to libcurl: \"%s\"", url );
445    tr_webRun( session, url, NULL, on_scrape_done, d );
446    tr_free( url );
447}
Note: See TracBrowser for help on using the repository browser.