source: trunk/libtransmission/tr-dht.c @ 8996

Last change on this file since 8996 was 8996, checked in by charles, 13 years ago

fix --disable-dht error in 1.74 reported by DimStar?

File size: 12.6 KB
Line 
1/*
2Copyright (c) 2009 by Juliusz Chroboczek
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"), to deal
6in the Software without restriction, including without limitation the rights
7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8copies of the Software, and to permit persons to whom the Software is
9furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in
12all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20THE SOFTWARE.
21*/
22
23/* ansi */
24#include <errno.h>
25#include <stdio.h>
26
27/* posix */
28#include <netinet/in.h> /* sockaddr_in */
29#include <signal.h> /* sig_atomic_t */
30#include <sys/time.h>
31#include <sys/types.h>
32#include <sys/socket.h> /* socket(), bind() */
33#include <unistd.h> /* close() */
34
35/* third party */
36#include <event.h>
37#include <dht/dht.h>
38
39/* libT */
40#include "transmission.h"
41#include "bencode.h"
42#include "crypto.h"
43#include "net.h"
44#include "peer-mgr.h" /* tr_peerMgrCompactToPex() */
45#include "platform.h" /* tr_threadNew() */
46#include "session.h"
47#include "torrent.h" /* tr_torrentFindFromHash() */
48#include "tr-dht.h"
49#include "trevent.h" /* tr_runInEventThread() */
50#include "utils.h"
51#include "version.h"
52
53#ifdef WITHOUT_DHT
54
55  /* These are the stubs for when we're building without DHT support */
56  int tr_dhtInit( tr_session * session UNUSED,
57                  tr_address * address UNUSED ) { return TR_DHT_STOPPED; }
58  void tr_dhtUninit( tr_session * session UNUSED ) { }
59  tr_bool tr_dhtEnabled( const tr_session * session UNUSED ) { return FALSE; }
60  tr_port tr_dhtPort ( const tr_session * sesssion UNUSED ) { return 0; }
61  int tr_dhtStatus( tr_session * session     UNUSED,
62                    int        * setmeCount  UNUSED ) { return TR_DHT_STOPPED; }
63  int tr_dhtAddNode( tr_session * session    UNUSED,
64                     tr_address * addr       UNUSED,
65                     tr_port      port       UNUSED,
66                     tr_bool      bootstrap  UNUSED ) { return 0; }
67  int tr_dhtAnnounce( tr_torrent * session UNUSED,
68                      tr_bool announce UNUSED ) { return -1; }
69
70
71#else
72
73static int dht_socket;
74static struct event dht_event;
75static tr_port dht_port;
76static unsigned char myid[20];
77static tr_session *session = NULL;
78
79static void event_callback(int s, short type, void *ignore);
80
81struct bootstrap_closure {
82    tr_session *session;
83    uint8_t *nodes;
84    size_t len;
85};
86
87static void
88dht_bootstrap(void *closure)
89{
90    struct bootstrap_closure *cl = closure;
91    size_t i;
92
93    if(session != cl->session)
94        return;
95
96    for(i = 0; i < cl->len; i += 6)
97    {
98        struct timeval tv;
99        tr_port port;
100        struct tr_address addr;
101        int status;
102
103        memset(&addr, 0, sizeof(addr));
104        addr.type = TR_AF_INET;
105        memcpy(&addr.addr.addr4, &cl->nodes[i], 4);
106        memcpy(&port, &cl->nodes[i + 4], 2);
107        port = ntohs(port);
108        /* There's no race here -- if we uninit between the test and the
109           AddNode, the AddNode will be ignored. */
110        status = tr_dhtStatus(cl->session, NULL);
111        if(status == TR_DHT_STOPPED || status >= TR_DHT_FIREWALLED)
112            break;
113        tr_dhtAddNode(cl->session, &addr, port, 1);
114        tr_timevalSet( &tv, 2 + tr_cryptoWeakRandInt( 5 ), tr_cryptoWeakRandInt( 1000000 ) );
115        select( 0, NULL, NULL, NULL, &tv );
116    }
117    tr_free( cl->nodes );
118    tr_free( closure );
119    tr_ndbg( "DHT", "Finished bootstrapping" );
120}
121
122int
123tr_dhtInit(tr_session *ss, tr_address * tr_addr)
124{
125    struct sockaddr_in sin;
126    tr_benc benc;
127    int rc;
128    tr_bool have_id = FALSE;
129    char * dat_file;
130    uint8_t * nodes = NULL;
131    const uint8_t * raw;
132    size_t len;
133    char v[5];
134
135    if( session ) /* already initialized */
136        return -1;
137
138    dht_port = tr_sessionGetPeerPort(ss);
139    if(dht_port <= 0)
140        return -1;
141
142    tr_ndbg( "DHT", "Initialising DHT" );
143
144    dht_socket = socket(PF_INET, SOCK_DGRAM, 0);
145    if(dht_socket < 0)
146        goto fail;
147
148    memset(&sin, 0, sizeof(sin));
149    sin.sin_family = AF_INET;
150    memcpy(&(sin.sin_addr), &(tr_addr->addr.addr4), sizeof (struct in_addr));
151    sin.sin_port = htons(dht_port);
152    rc = bind(dht_socket, (struct sockaddr*)&sin, sizeof(sin));
153    if(rc < 0)
154        goto fail;
155
156    if( getenv( "TR_DHT_VERBOSE" ) != NULL )
157        dht_debug = stderr;
158
159    dat_file = tr_buildPath( ss->configDir, "dht.dat", NULL );
160    rc = tr_bencLoadFile( &benc, TR_FMT_BENC, dat_file );
161    tr_free( dat_file );
162    if(rc == 0) {
163        if(( have_id = tr_bencDictFindRaw( &benc, "id", &raw, &len ) && len==20 ))
164            memcpy( myid, raw, len );
165        if( tr_bencDictFindRaw( &benc, "nodes", &raw, &len ) && !(len%6) ) {
166            nodes = tr_memdup( raw, len );
167            tr_ninf( "DHT", "Bootstrapping from %d old nodes", (int)(len/6) );
168        }
169        tr_bencFree( &benc );
170    }
171
172    if( have_id )
173        tr_ninf( "DHT", "Reusing old id" );
174    else {
175        /* Note that DHT ids need to be distributed uniformly,
176         * so it should be something truly random. */
177        tr_ninf( "DHT", "Generating new id" );
178        tr_cryptoRandBuf( myid, 20 );
179    }
180
181    v[0] = 'T';
182    v[1] = 'R';
183    v[2] = (SVN_REVISION_NUM >> 8) & 0xFF;
184    v[3] = SVN_REVISION_NUM & 0xFF;
185    rc = dht_init( dht_socket, myid, (const unsigned char*)v );
186    if(rc < 0)
187        goto fail;
188
189    session = ss;
190
191    if(nodes) {
192        struct bootstrap_closure * cl = tr_new( struct bootstrap_closure, 1 );
193        cl->session = session;
194        cl->nodes = nodes;
195        cl->len = len;
196        tr_threadNew( dht_bootstrap, cl );
197    }
198
199    event_set( &dht_event, dht_socket, EV_READ, event_callback, NULL );
200    tr_timerAdd( &dht_event, 0, tr_cryptoWeakRandInt( 1000000 ) );
201
202    tr_ndbg( "DHT", "DHT initialised" );
203
204    return 1;
205
206    fail:
207    {
208        const int save = errno;
209        close(dht_socket);
210        dht_socket = -1;
211        session = NULL;
212        tr_ndbg( "DHT", "DHT initialisation failed (errno = %d)", save );
213        errno = save;
214    }
215
216    return -1;
217}
218
219void
220tr_dhtUninit(tr_session *ss)
221{
222    if(session != ss)
223        return;
224
225    tr_ndbg( "DHT", "Uninitialising DHT" );
226
227    event_del(&dht_event);
228
229    /* Since we only save known good nodes, avoid erasing older data if we
230       don't know enough nodes. */
231    if(tr_dhtStatus(ss, NULL) < TR_DHT_FIREWALLED)
232        tr_ninf( "DHT", "Not saving nodes, DHT not ready" );
233    else {
234        tr_benc benc;
235        struct sockaddr_in sins[300];
236        char compact[300 * 6];
237        char *dat_file;
238        int i;
239        int n = dht_get_nodes(sins, 300);
240        int j = 0;
241
242        tr_ninf( "DHT", "Saving %d nodes", n );
243        for( i=0; i<n; ++i ) {
244            memcpy( compact + j, &sins[i].sin_addr, 4 );
245            memcpy( compact + j + 4, &sins[i].sin_port, 2 );
246            j += 6;
247        }
248        tr_bencInitDict( &benc, 2 );
249        tr_bencDictAddRaw( &benc, "id", myid, 20 );
250        tr_bencDictAddRaw( &benc, "nodes", compact, j );
251        dat_file = tr_buildPath( ss->configDir, "dht.dat", NULL );
252        tr_bencToFile( &benc, TR_FMT_BENC, dat_file );
253        tr_bencFree( &benc );
254        tr_free( dat_file );
255    }
256
257    dht_uninit( dht_socket, 0 );
258    EVUTIL_CLOSESOCKET( dht_socket );
259
260    tr_ndbg("DHT", "Done uninitialising DHT");
261
262    session = NULL;
263}
264
265tr_bool
266tr_dhtEnabled( const tr_session * ss )
267{
268    return ss && ( ss == session );
269}
270
271struct getstatus_closure
272{
273    sig_atomic_t status;
274    sig_atomic_t count;
275};
276
277static void
278getstatus( void * closure )
279{
280    struct getstatus_closure * ret = closure;
281    int good, dubious, incoming;
282
283    dht_nodes( &good, &dubious, NULL, &incoming );
284
285    ret->count = good + dubious;
286
287    if( good < 4 || good + dubious <= 8 )
288        ret->status = TR_DHT_BROKEN;
289    else if( good < 40 )
290        ret->status = TR_DHT_POOR;
291    else if( incoming < 8 )
292        ret->status = TR_DHT_FIREWALLED;
293    else
294        ret->status = TR_DHT_GOOD;
295}
296
297int
298tr_dhtStatus( tr_session * ss, int * nodes_return )
299{
300    struct getstatus_closure ret = { -1, - 1 };
301
302    if( !tr_dhtEnabled( ss ) )
303        return TR_DHT_STOPPED;
304
305    tr_runInEventThread( ss, getstatus, &ret );
306    while( ret.status < 0 )
307        tr_wait( 10 /*msec*/ );
308
309    if( nodes_return )
310        *nodes_return = ret.count;
311
312    return ret.status;
313}
314
315tr_port
316tr_dhtPort( const tr_session *ss )
317{
318    return tr_dhtEnabled( ss ) ? dht_port : 0;
319}
320
321int
322tr_dhtAddNode(tr_session *ss, tr_address *address, tr_port port,
323              tr_bool bootstrap)
324{
325    struct sockaddr_in sin;
326
327    if( !tr_dhtEnabled( ss ) )
328        return 0;
329
330    if( address->type != TR_AF_INET )
331        return 0;
332
333    /* Since we don't want to abuse our bootstrap nodes,
334     * we don't ping them if the DHT is in a good state. */
335    if(bootstrap) {
336        if(tr_dhtStatus(ss, NULL) >= TR_DHT_FIREWALLED)
337            return 0;
338    }
339
340    memset(&sin, 0, sizeof(sin));
341    sin.sin_family = AF_INET;
342    memcpy(&sin.sin_addr, &address->addr.addr4, 4);
343    sin.sin_port = htons(port);
344    dht_ping_node(dht_socket, &sin);
345
346    return 1;
347}
348
349const char *
350tr_dhtPrintableStatus(int status)
351{
352    switch(status) {
353    case TR_DHT_STOPPED: return "stopped";
354    case TR_DHT_BROKEN: return "broken";
355    case TR_DHT_POOR: return "poor";
356    case TR_DHT_FIREWALLED: return "firewalled";
357    case TR_DHT_GOOD: return "good";
358    default: return "???";
359    }
360}
361
362static void
363callback( void *ignore UNUSED, int event,
364          unsigned char *info_hash, void *data, size_t data_len )
365{
366    if( event == DHT_EVENT_VALUES )
367    {
368        tr_torrent *tor;
369        tr_globalLock( session );
370        tor = tr_torrentFindFromHash( session, info_hash );
371        if( tor && tr_torrentAllowsDHT( tor ))
372        {
373            size_t i, n;
374            tr_pex * pex = tr_peerMgrCompactToPex(data, data_len, NULL, 0, &n);
375            for( i=0; i<n; ++i )
376                tr_peerMgrAddPex( tor, TR_PEER_FROM_DHT, pex+i );
377            tr_free(pex);
378            tr_torinf(tor, "Learned %d peers from DHT", (int)n);
379        }
380        tr_globalUnlock( session );
381    }
382    else if( event == DHT_EVENT_SEARCH_DONE )
383    {
384        tr_torrent * tor = tr_torrentFindFromHash( session, info_hash );
385        if( tor ) {
386            tr_torinf(tor, "DHT announce done");
387            tor->dhtAnnounceInProgress = 0;
388        }
389    }
390}
391
392int
393tr_dhtAnnounce(tr_torrent *tor, tr_bool announce)
394{
395    int rc, status, numnodes;
396
397    if( !tr_torrentAllowsDHT( tor ) )
398        return -1;
399
400    status = tr_dhtStatus( tor->session, &numnodes );
401    if(status < TR_DHT_POOR ) {
402        tr_tordbg(tor, "DHT not ready (%s, %d nodes)",
403                  tr_dhtPrintableStatus(status), numnodes);
404        return 0;
405    }
406
407    rc = dht_search( dht_socket, tor->info.hash,
408                     announce ? tr_sessionGetPeerPort(session) : 0,
409                     callback, NULL);
410
411    if( rc >= 1 ) {
412        tr_torinf(tor, "Starting DHT announce (%s, %d nodes)",
413                  tr_dhtPrintableStatus(status), numnodes);
414        tor->dhtAnnounceInProgress = TRUE;
415    } else {
416        tr_torerr(tor, "DHT announce failed, errno = %d (%s, %d nodes)",
417                  errno, tr_dhtPrintableStatus(status), numnodes);
418    }
419
420    return 1;
421}
422
423static void
424event_callback(int s, short type, void *ignore UNUSED )
425{
426    time_t tosleep;
427
428    if( dht_periodic(s, type == EV_READ, &tosleep, callback, NULL) < 0 ) {
429        if(errno == EINTR) {
430            tosleep = 0;
431        } else {
432            tr_nerr("DHT", "dht_periodic failed (errno = %d)", errno);
433            if(errno == EINVAL || errno == EFAULT)
434                    abort();
435            tosleep = 1;
436        }
437    }
438
439    /* Being slightly late is fine,
440       and has the added benefit of adding some jitter. */
441    tr_timerAdd( &dht_event, tosleep, tr_cryptoWeakRandInt( 1000000 ) );
442}
443
444void
445dht_hash(void *hash_return, int hash_size,
446         const void *v1, int len1,
447         const void *v2, int len2,
448         const void *v3, int len3)
449{
450    unsigned char sha1[SHA_DIGEST_LENGTH];
451    tr_sha1( sha1, v1, len1, v2, len2, v3, len3, NULL );
452    memset( hash_return, 0, hash_size );
453    memcpy( hash_return, sha1, MIN( hash_size, SHA_DIGEST_LENGTH ) );
454}
455
456int
457dht_random_bytes( void * buf, size_t size )
458{
459    tr_cryptoRandBuf( buf, size );
460    return size;
461}
462
463#endif
Note: See TracBrowser for help on using the repository browser.