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

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

(trunk libT) fix event_callback() error in tr-dht.c

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