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

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

(trunk) #2222: Make DHT support a compile-time option, enabled by default

File size: 10.9 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 ) { return TR_DHT_STOPPED; }
57  void tr_dhtUninit( tr_session * session UNUSED ) { }
58  tr_bool tr_dhtEnabled( const tr_session * session UNUSED ) { return FALSE; }
59  tr_port tr_dhtPort ( const tr_session * sesssion UNUSED ) { return 0; }
60  int tr_dhtStatus( tr_session * session     UNUSED,
61                    int        * setmeCount  UNUSED ) { return TR_DHT_STOPPED; }
62  int tr_dhtAddNode( tr_session * session    UNUSED,
63                     tr_address * addr       UNUSED,
64                     tr_port      port       UNUSED,
65                     tr_bool      bootstrap  UNUSED ) { return 0; }
66  int tr_dhtAnnounce( tr_torrent * session UNUSED,
67                      tr_bool announce UNUSED ) { return -1; }
68
69
70#else
71
72static int dht_socket;
73static struct event dht_event;
74static tr_port dht_port;
75static unsigned char myid[20];
76static tr_session *session = NULL;
77
78static void event_callback(int s, short type, void *ignore);
79
80struct bootstrap_closure {
81    tr_session *session;
82    uint8_t *nodes;
83    size_t len;
84};
85
86static void
87dht_bootstrap(void *closure)
88{
89    struct bootstrap_closure *cl = closure;
90    size_t i;
91
92    if(session != cl->session)
93        return;
94
95    for(i = 0; i < cl->len; i += 6)
96    {
97        struct timeval tv;
98        tr_port port;
99        struct tr_address addr;
100        int status;
101
102        memset(&addr, 0, sizeof(addr));
103        addr.type = TR_AF_INET;
104        memcpy(&addr.addr.addr4, &cl->nodes[i], 4);
105        memcpy(&port, &cl->nodes[i + 4], 2);
106        port = ntohs(port);
107        /* There's no race here -- if we uninit between the test and the
108           AddNode, the AddNode will be ignored. */
109        status = tr_dhtStatus(cl->session, NULL);
110        if(status == TR_DHT_STOPPED || status >= TR_DHT_FIREWALLED)
111            break;
112        tr_dhtAddNode(cl->session, &addr, port, 1);
113        tr_timevalSet( &tv, 2 + tr_cryptoWeakRandInt( 5 ), tr_cryptoWeakRandInt( 1000000 ) );
114        select( 0, NULL, NULL, NULL, &tv );
115    }
116    tr_free( cl->nodes );
117    tr_free( closure );
118}
119
120int
121tr_dhtInit(tr_session *ss)
122{
123    struct sockaddr_in sin;
124    tr_benc benc;
125    int rc;
126    tr_bool have_id = FALSE;
127    char * dat_file;
128    uint8_t * nodes = NULL;
129    const uint8_t * raw;
130    size_t len;
131    char v[5];
132
133    if( session ) /* already initialized */
134        return -1;
135
136    dht_socket = socket(PF_INET, SOCK_DGRAM, 0);
137    if(dht_socket < 0)
138        return -1;
139
140    dht_port = tr_sessionGetPeerPort(ss);
141    if(dht_port <= 0)
142        return -1;
143
144    memset(&sin, 0, sizeof(sin));
145    sin.sin_family = AF_INET;
146    sin.sin_port = htons(dht_port);
147    rc = bind(dht_socket, (struct sockaddr*)&sin, sizeof(sin));
148    if(rc < 0)
149        goto fail;
150
151    if( getenv( "TR_DHT_VERBOSE" ) != NULL )
152        dht_debug = stderr;
153
154    dat_file = tr_buildPath( ss->configDir, "dht.dat", NULL );
155    rc = tr_bencLoadFile( &benc, TR_FMT_BENC, dat_file );
156    tr_free( dat_file );
157    if(rc == 0) {
158        if(( have_id = tr_bencDictFindRaw( &benc, "id", &raw, &len ) && len==20 ))
159            memcpy( myid, raw, len );
160        if( tr_bencDictFindRaw( &benc, "nodes", &raw, &len ) && !(len%6) )
161            nodes = tr_memdup( raw, len );
162        tr_bencFree( &benc );
163    }
164
165    if(!have_id) {
166        /* Note that DHT ids need to be distributed uniformly,
167         * so it should be something truly random. */
168        tr_cryptoRandBuf( myid, 20 );
169    }
170
171    v[0] = 'T';
172    v[1] = 'R';
173    v[2] = (SVN_REVISION_NUM >> 8) & 0xFF; 
174    v[3] = SVN_REVISION_NUM & 0xFF; 
175    rc = dht_init( dht_socket, myid, (const unsigned char*)v );
176    if(rc < 0)
177        goto fail;
178
179    session = ss;
180
181    if(nodes) {
182        struct bootstrap_closure * cl = tr_new( struct bootstrap_closure, 1 );
183        cl->session = session;
184        cl->nodes = nodes;
185        cl->len = len;
186        tr_threadNew( dht_bootstrap, cl );
187    }
188
189    event_set( &dht_event, dht_socket, EV_READ, event_callback, NULL );
190    tr_timerAdd( &dht_event, 0, tr_cryptoWeakRandInt( 1000000 ) );
191
192    return 1;
193
194    fail:
195    {
196        const int save = errno;
197        close(dht_socket);
198        dht_socket = -1;
199        session = NULL;
200        errno = save;
201    }
202
203    return -1;
204}
205
206void
207tr_dhtUninit(tr_session *ss)
208{
209    if(session != ss)
210        return;
211
212    event_del(&dht_event);
213
214    /* Since we only save known good nodes, avoid erasing older data if we
215       don't know enough nodes. */
216    if(tr_dhtStatus(ss, NULL) >= TR_DHT_FIREWALLED) {
217        tr_benc benc;
218        struct sockaddr_in sins[300];
219        char compact[300 * 6];
220        char *dat_file;
221        int i;
222        int n = dht_get_nodes(sins, 300);
223        int j = 0;
224        for( i=0; i<n; ++i ) {
225            memcpy( compact + j, &sins[i].sin_addr, 4 );
226            memcpy( compact + j + 4, &sins[i].sin_port, 2 );
227            j += 6;
228        }
229        tr_bencInitDict( &benc, 2 );
230        tr_bencDictAddRaw( &benc, "id", myid, 20 );
231        tr_bencDictAddRaw( &benc, "nodes", compact, j );
232        dat_file = tr_buildPath( ss->configDir, "dht.dat", NULL );
233        tr_bencToFile( &benc, TR_FMT_BENC, dat_file );
234        tr_bencFree( &benc );
235        tr_free( dat_file );
236    }
237
238    dht_uninit( dht_socket, 0 );
239    EVUTIL_CLOSESOCKET( dht_socket );
240
241    session = NULL;
242}
243
244tr_bool
245tr_dhtEnabled( const tr_session * ss )
246{
247    return ss && ( ss == session );
248}
249
250struct getstatus_closure
251{
252    sig_atomic_t status;
253    sig_atomic_t count;
254};
255
256static void
257getstatus( void * closure )
258{
259    struct getstatus_closure * ret = closure;
260    int good, dubious, incoming;
261
262    dht_nodes( &good, &dubious, NULL, &incoming );
263
264    ret->count = good + dubious;
265
266    if( good < 4 || good + dubious <= 8 )
267        ret->status = TR_DHT_BROKEN;
268    else if( good < 40 )
269        ret->status = TR_DHT_POOR;
270    else if( incoming < 8 )
271        ret->status = TR_DHT_FIREWALLED;
272    else
273        ret->status = TR_DHT_GOOD;
274}
275
276int
277tr_dhtStatus( tr_session * ss, int * nodes_return )
278{
279    struct getstatus_closure ret = { -1, - 1 };
280
281    if( !tr_dhtEnabled( ss ) )
282        return TR_DHT_STOPPED;
283
284    tr_runInEventThread( ss, getstatus, &ret );
285    while( ret.status < 0 )
286        tr_wait( 10 /*msec*/ );
287
288    if( nodes_return )
289        *nodes_return = ret.count;
290
291    return ret.status;
292}
293
294tr_port
295tr_dhtPort( const tr_session *ss )
296{
297    return tr_dhtEnabled( ss ) ? dht_port : 0;
298}
299
300int
301tr_dhtAddNode(tr_session *ss, tr_address *address, tr_port port, tr_bool bootstrap)
302{
303    struct sockaddr_in sin;
304
305    if( !tr_dhtEnabled( ss ) )
306        return 0;
307
308    if( address->type != TR_AF_INET )
309        return 0;
310
311    /* Since we don't want to abuse our bootstrap nodes,
312     * we don't ping them if the DHT is in a good state. */
313    if(bootstrap) {
314        if(tr_dhtStatus(ss, NULL) >= TR_DHT_FIREWALLED)
315            return 0;
316    }
317
318    memset(&sin, 0, sizeof(sin));
319    sin.sin_family = AF_INET;
320    memcpy(&sin.sin_addr, &address->addr.addr4, 4);
321    sin.sin_port = htons(port);
322    dht_ping_node(dht_socket, &sin);
323
324    return 1;
325}
326
327static void
328callback( void *ignore UNUSED, int event,
329          unsigned char *info_hash, void *data, size_t data_len )
330{
331    if( event == DHT_EVENT_VALUES )
332    {
333        tr_torrent *tor;
334        tr_globalLock( session );
335        tor = tr_torrentFindFromHash( session, info_hash );
336        if( tor && tr_torrentAllowsDHT( tor ))
337        {
338            size_t i, n;
339            tr_pex * pex = tr_peerMgrCompactToPex(data, data_len, NULL, 0, &n);
340            for( i=0; i<n; ++i )
341                tr_peerMgrAddPex( tor, TR_PEER_FROM_DHT, pex+i );
342            tr_free(pex);
343        }
344        tr_globalUnlock( session );
345    }
346    else if( event == DHT_EVENT_SEARCH_DONE )
347    {
348        tr_torrent * tor = tr_torrentFindFromHash( session, info_hash );
349        if( tor )
350            tor->dhtAnnounceInProgress = 0;
351    }
352}
353
354int
355tr_dhtAnnounce(tr_torrent *tor, tr_bool announce)
356{
357    int rc;
358
359    if( !tr_torrentAllowsDHT( tor ) )
360        return -1;
361
362    if( tr_dhtStatus( tor->session, NULL ) < TR_DHT_POOR )
363        return 0;
364
365    rc = dht_search( dht_socket, tor->info.hash,
366                     announce ? tr_sessionGetPeerPort(session) : 0,
367                     callback, NULL);
368
369    if( rc >= 1 )
370        tor->dhtAnnounceInProgress = TRUE;
371
372    return 1;
373}
374
375static void
376event_callback(int s, short type, void *ignore UNUSED )
377{
378    time_t tosleep;
379
380    if( dht_periodic(s, type == EV_READ, &tosleep, callback, NULL) < 0 ) {
381        if(errno == EINTR) {
382            tosleep = 0;
383        } else {
384            perror("dht_periodic");
385            if(errno == EINVAL || errno == EFAULT)
386                    abort();
387            tosleep = 1;
388        }
389    }
390
391    /* Being slightly late is fine,
392       and has the added benefit of adding some jitter. */
393    tr_timerAdd( &dht_event, tosleep, tr_cryptoWeakRandInt( 1000000 ) );
394}
395
396void
397dht_hash(void *hash_return, int hash_size,
398         const void *v1, int len1,
399         const void *v2, int len2,
400         const void *v3, int len3)
401{
402    unsigned char sha1[SHA_DIGEST_LENGTH];
403    tr_sha1( sha1, v1, len1, v2, len2, v3, len3, NULL );
404    memset( hash_return, 0, hash_size );
405    memcpy( hash_return, sha1, MIN( hash_size, SHA_DIGEST_LENGTH ) );
406}
407
408int
409dht_random_bytes( void * buf, size_t size )
410{
411    tr_cryptoRandBuf( buf, size );
412    return size;
413}
414
415#endif
Note: See TracBrowser for help on using the repository browser.