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

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

(trunk libT) tweak tr-dht a little

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