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

Last change on this file since 14336 was 14336, checked in by jordan, 8 years ago

(trunk) #4160: mike.dld patch: 4160-07-env.patch

  • Property svn:keywords set to Date Rev Author Id
File size: 19.8 KB
Line 
1/*
2 * Copyright (c) 2009-2010 by Juliusz Chroboczek
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 *
22 * $Id: tr-dht.c 14336 2014-09-21 18:05:14Z jordan $
23 *
24 */
25
26/* ansi */
27#include <errno.h>
28#include <stdio.h>
29#include <string.h> /* memcpy (), memset (), memchr (), strlen () */
30#include <stdlib.h> /* atoi () */
31
32/* posix */
33#include <signal.h> /* sig_atomic_t */
34#include <sys/time.h>
35#ifdef _WIN32
36  #include <inttypes.h>
37  #define _WIN32_WINNT  0x0501  /* freeaddrinfo (),getaddrinfo (),getnameinfo () */
38  #include <ws2tcpip.h>
39#else
40  #include <sys/types.h>
41  #include <sys/socket.h> /* socket (), bind () */
42  #include <netdb.h>
43  #include <netinet/in.h> /* sockaddr_in */
44#endif
45
46/* third party */
47#include <event2/event.h>
48#include <dht/dht.h>
49
50/* libT */
51#include "transmission.h"
52#include "crypto.h"
53#include "file.h"
54#include "log.h"
55#include "net.h"
56#include "peer-mgr.h" /* tr_peerMgrCompactToPex () */
57#include "platform.h" /* tr_threadNew () */
58#include "session.h"
59#include "torrent.h" /* tr_torrentFindFromHash () */
60#include "tr-dht.h"
61#include "trevent.h" /* tr_runInEventThread () */
62#include "utils.h"
63#include "variant.h"
64
65static struct event *dht_timer = NULL;
66static unsigned char myid[20];
67static tr_session *session = NULL;
68
69static void timer_callback (evutil_socket_t s, short type, void *ignore);
70
71struct bootstrap_closure {
72    tr_session *session;
73    uint8_t *nodes;
74    uint8_t *nodes6;
75    size_t len, len6;
76};
77
78static int
79bootstrap_done (tr_session *session, int af)
80{
81    int status;
82
83    if (af == 0)
84        return
85            bootstrap_done (session, AF_INET) &&
86            bootstrap_done (session, AF_INET6);
87
88    status = tr_dhtStatus (session, af, NULL);
89    return status == TR_DHT_STOPPED || status >= TR_DHT_FIREWALLED;
90}
91
92static void
93nap (int roughly_sec)
94{
95    const int roughly_msec = roughly_sec * 1000;
96    const int msec = roughly_msec/2 + tr_cryptoWeakRandInt (roughly_msec);
97    tr_wait_msec (msec);
98}
99
100static int
101bootstrap_af (tr_session *session)
102{
103    if (bootstrap_done (session, AF_INET6))
104        return AF_INET;
105    else if (bootstrap_done (session, AF_INET))
106        return AF_INET6;
107    else
108        return 0;
109}
110
111static void
112bootstrap_from_name (const char *name, tr_port port, int af)
113{
114    struct addrinfo hints, *info, *infop;
115    char pp[10];
116    int rc;
117
118    memset (&hints, 0, sizeof (hints));
119    hints.ai_socktype = SOCK_DGRAM;
120    hints.ai_family = af;
121    /* No, just passing p + 1 to gai won't work. */
122    tr_snprintf (pp, sizeof (pp), "%d", (int)port);
123
124    rc = getaddrinfo (name, pp, &hints, &info);
125    if (rc != 0) {
126        tr_logAddNamedError ("DHT", "%s:%s: %s", name, pp, gai_strerror (rc));
127        return;
128    }
129
130    infop = info;
131    while (infop) {
132        dht_ping_node (infop->ai_addr, infop->ai_addrlen);
133
134        nap (15);
135
136        if (bootstrap_done (session, af))
137            break;
138        infop = infop->ai_next;
139    }
140    freeaddrinfo (info);
141}
142
143static void
144dht_bootstrap (void *closure)
145{
146    struct bootstrap_closure *cl = closure;
147    int i;
148    int num = cl->len / 6, num6 = cl->len6 / 18;
149
150    if (session != cl->session)
151        return;
152
153    if (cl->len > 0)
154        tr_logAddNamedInfo ("DHT", "Bootstrapping from %d IPv4 nodes", num);
155
156    if (cl->len6 > 0)
157        tr_logAddNamedInfo ("DHT", "Bootstrapping from %d IPv6 nodes", num6);
158
159
160    for (i = 0; i < MAX (num, num6); i++) {
161        if (i < num && !bootstrap_done (cl->session, AF_INET)) {
162            tr_port port;
163            struct tr_address addr;
164
165            memset (&addr, 0, sizeof (addr));
166            addr.type = TR_AF_INET;
167            memcpy (&addr.addr.addr4, &cl->nodes[i * 6], 4);
168            memcpy (&port, &cl->nodes[i * 6 + 4], 2);
169            port = ntohs (port);
170            tr_dhtAddNode (cl->session, &addr, port, 1);
171        }
172        if (i < num6 && !bootstrap_done (cl->session, AF_INET6)) {
173            tr_port port;
174            struct tr_address addr;
175
176            memset (&addr, 0, sizeof (addr));
177            addr.type = TR_AF_INET6;
178            memcpy (&addr.addr.addr6, &cl->nodes6[i * 18], 16);
179            memcpy (&port, &cl->nodes6[i * 18 + 16], 2);
180            port = ntohs (port);
181            tr_dhtAddNode (cl->session, &addr, port, 1);
182        }
183
184        /* Our DHT code is able to take up to 9 nodes in a row without
185           dropping any. After that, it takes some time to split buckets.
186           So ping the first 8 nodes quickly, then slow down. */
187        if (i < 8)
188            nap (2);
189        else
190            nap (15);
191
192        if (bootstrap_done (session, 0))
193            break;
194    }
195
196    if (!bootstrap_done (cl->session, 0)) {
197        char *bootstrap_file;
198        tr_sys_file_t f = TR_BAD_SYS_FILE;
199
200        bootstrap_file =
201            tr_buildPath (cl->session->configDir, "dht.bootstrap", NULL);
202
203        if (bootstrap_file)
204            f = tr_sys_file_open (bootstrap_file, TR_SYS_FILE_READ, 0, NULL);
205        if (f != TR_BAD_SYS_FILE) {
206            tr_logAddNamedInfo ("DHT", "Attempting manual bootstrap");
207            for (;;) {
208                char buf[201];
209                char *p;
210                int port = 0;
211
212                if (!tr_sys_file_read_line (f, buf, 200, NULL))
213                    break;
214
215                p = memchr (buf, ' ', strlen (buf));
216                if (p != NULL)
217                    port = atoi (p + 1);
218                if (p == NULL || port <= 0 || port >= 0x10000) {
219                    tr_logAddNamedError ("DHT", "Couldn't parse %s", buf);
220                    continue;
221                }
222
223                *p = '\0';
224
225                bootstrap_from_name (buf, port, bootstrap_af (session));
226
227                if (bootstrap_done (cl->session, 0))
228                    break;
229            }
230            tr_sys_file_close (f, NULL);
231        }
232
233        tr_free (bootstrap_file);
234    }
235
236    if (!bootstrap_done (cl->session, 0)) {
237        for (i = 0; i < 6; i++) {
238            /* We don't want to abuse our bootstrap nodes, so be very
239               slow.  The initial wait is to give other nodes a chance
240               to contact us before we attempt to contact a bootstrap
241               node, for example because we've just been restarted. */
242            nap (40);
243            if (bootstrap_done (cl->session, 0))
244                break;
245            if (i == 0)
246                tr_logAddNamedInfo ("DHT",
247                        "Attempting bootstrap from dht.transmissionbt.com");
248            bootstrap_from_name ("dht.transmissionbt.com", 6881,
249                                 bootstrap_af (session));
250        }
251    }
252
253    if (cl->nodes)
254        tr_free (cl->nodes);
255    if (cl->nodes6)
256        tr_free (cl->nodes6);
257    tr_free (closure);
258    tr_logAddNamedDbg ("DHT", "Finished bootstrapping");
259}
260
261int
262tr_dhtInit (tr_session *ss)
263{
264    tr_variant benc;
265    int rc;
266    bool have_id = false;
267    char * dat_file;
268    uint8_t * nodes = NULL, * nodes6 = NULL;
269    const uint8_t * raw;
270    size_t len, len6;
271    struct bootstrap_closure * cl;
272
273    if (session) /* already initialized */
274        return -1;
275
276    tr_logAddNamedDbg ("DHT", "Initializing DHT");
277
278    if (tr_env_key_exists ("TR_DHT_VERBOSE"))
279        dht_debug = stderr;
280
281    dat_file = tr_buildPath (ss->configDir, "dht.dat", NULL);
282    rc = tr_variantFromFile (&benc, TR_VARIANT_FMT_BENC, dat_file);
283    tr_free (dat_file);
284    if (rc == 0) {
285        have_id = tr_variantDictFindRaw (&benc, TR_KEY_id, &raw, &len);
286        if (have_id && len==20)
287            memcpy (myid, raw, len);
288        if (ss->udp_socket >= 0 &&
289            tr_variantDictFindRaw (&benc, TR_KEY_nodes, &raw, &len) && ! (len%6)) {
290                nodes = tr_memdup (raw, len);
291        }
292        if (ss->udp6_socket > 0 &&
293            tr_variantDictFindRaw (&benc, TR_KEY_nodes6, &raw, &len6) && ! (len6%18)) {
294            nodes6 = tr_memdup (raw, len6);
295        }
296        tr_variantFree (&benc);
297    }
298
299    if (nodes == NULL)
300        len = 0;
301    if (nodes6 == NULL)
302        len6 = 0;
303
304    if (have_id)
305        tr_logAddNamedInfo ("DHT", "Reusing old id");
306    else {
307        /* Note that DHT ids need to be distributed uniformly,
308         * so it should be something truly random. */
309        tr_logAddNamedInfo ("DHT", "Generating new id");
310        tr_cryptoRandBuf (myid, 20);
311    }
312
313    rc = dht_init (ss->udp_socket, ss->udp6_socket, myid, NULL);
314    if (rc < 0)
315        goto fail;
316
317    session = ss;
318
319    cl = tr_new (struct bootstrap_closure, 1);
320    cl->session = session;
321    cl->nodes = nodes;
322    cl->nodes6 = nodes6;
323    cl->len = len;
324    cl->len6 = len6;
325    tr_threadNew (dht_bootstrap, cl);
326
327    dht_timer = evtimer_new (session->event_base, timer_callback, session);
328    tr_timerAdd (dht_timer, 0, tr_cryptoWeakRandInt (1000000));
329
330    tr_logAddNamedDbg ("DHT", "DHT initialized");
331
332    return 1;
333
334 fail:
335    tr_logAddNamedDbg ("DHT", "DHT initialization failed (errno = %d)", errno);
336    session = NULL;
337    return -1;
338}
339
340void
341tr_dhtUninit (tr_session *ss)
342{
343    if (session != ss)
344        return;
345
346    tr_logAddNamedDbg ("DHT", "Uninitializing DHT");
347
348    if (dht_timer != NULL) {
349        event_free (dht_timer);
350        dht_timer = NULL;
351    }
352
353    /* Since we only save known good nodes, avoid erasing older data if we
354       don't know enough nodes. */
355    if ((tr_dhtStatus (ss, AF_INET, NULL) < TR_DHT_FIREWALLED) &&
356      (tr_dhtStatus (ss, AF_INET6, NULL) < TR_DHT_FIREWALLED)) {
357        tr_logAddNamedInfo ("DHT", "Not saving nodes, DHT not ready");
358    } else {
359        tr_variant benc;
360        struct sockaddr_in sins[300];
361        struct sockaddr_in6 sins6[300];
362        char compact[300 * 6], compact6[300 * 18];
363        char *dat_file;
364        int i, j, num = 300, num6 = 300;
365        int n = dht_get_nodes (sins, &num, sins6, &num6);
366
367        tr_logAddNamedInfo ("DHT", "Saving %d (%d + %d) nodes", n, num, num6);
368
369        j = 0;
370        for (i=0; i<num; ++i) {
371            memcpy (compact + j, &sins[i].sin_addr, 4);
372            memcpy (compact + j + 4, &sins[i].sin_port, 2);
373            j += 6;
374        }
375        j = 0;
376        for (i=0; i<num6; ++i) {
377            memcpy (compact6 + j, &sins6[i].sin6_addr, 16);
378            memcpy (compact6 + j + 16, &sins6[i].sin6_port, 2);
379            j += 18;
380        }
381        tr_variantInitDict (&benc, 3);
382        tr_variantDictAddRaw (&benc, TR_KEY_id, myid, 20);
383        if (num > 0)
384            tr_variantDictAddRaw (&benc, TR_KEY_nodes, compact, num * 6);
385        if (num6 > 0)
386            tr_variantDictAddRaw (&benc, TR_KEY_nodes6, compact6, num6 * 18);
387        dat_file = tr_buildPath (ss->configDir, "dht.dat", NULL);
388        tr_variantToFile (&benc, TR_VARIANT_FMT_BENC, dat_file);
389        tr_variantFree (&benc);
390        tr_free (dat_file);
391    }
392
393    dht_uninit ();
394    tr_logAddNamedDbg ("DHT", "Done uninitializing DHT");
395
396    session = NULL;
397}
398
399bool
400tr_dhtEnabled (const tr_session * ss)
401{
402    return ss && (ss == session);
403}
404
405struct getstatus_closure
406{
407    int af;
408    sig_atomic_t status;
409    sig_atomic_t count;
410};
411
412static void
413getstatus (void * cl)
414{
415    struct getstatus_closure * closure = cl;
416    int good, dubious, incoming;
417
418    dht_nodes (closure->af, &good, &dubious, NULL, &incoming);
419
420    closure->count = good + dubious;
421
422    if (good < 4 || good + dubious <= 8)
423        closure->status = TR_DHT_BROKEN;
424    else if (good < 40)
425        closure->status = TR_DHT_POOR;
426    else if (incoming < 8)
427        closure->status = TR_DHT_FIREWALLED;
428    else
429        closure->status = TR_DHT_GOOD;
430}
431
432int
433tr_dhtStatus (tr_session * session, int af, int * nodes_return)
434{
435    struct getstatus_closure closure = { af, -1, -1 };
436
437    if (!tr_dhtEnabled (session) ||
438      (af == AF_INET && session->udp_socket < 0) ||
439      (af == AF_INET6 && session->udp6_socket < 0)) {
440        if (nodes_return)
441            *nodes_return = 0;
442        return TR_DHT_STOPPED;
443    }
444
445    tr_runInEventThread (session, getstatus, &closure);
446    while (closure.status < 0)
447        tr_wait_msec (50 /*msec*/);
448
449    if (nodes_return)
450        *nodes_return = closure.count;
451
452    return closure.status;
453}
454
455tr_port
456tr_dhtPort (tr_session *ss)
457{
458    return tr_dhtEnabled (ss) ? ss->udp_port : 0;
459}
460
461int
462tr_dhtAddNode (tr_session       * ss,
463               const tr_address * address,
464               tr_port            port,
465               bool            bootstrap)
466{
467    int af = address->type == TR_AF_INET ? AF_INET : AF_INET6;
468
469    if (!tr_dhtEnabled (ss))
470        return 0;
471
472    /* Since we don't want to abuse our bootstrap nodes,
473     * we don't ping them if the DHT is in a good state. */
474
475    if (bootstrap) {
476        if (tr_dhtStatus (ss, af, NULL) >= TR_DHT_FIREWALLED)
477            return 0;
478    }
479
480    if (address->type == TR_AF_INET) {
481        struct sockaddr_in sin;
482        memset (&sin, 0, sizeof (sin));
483        sin.sin_family = AF_INET;
484        memcpy (&sin.sin_addr, &address->addr.addr4, 4);
485        sin.sin_port = htons (port);
486        dht_ping_node ((struct sockaddr*)&sin, sizeof (sin));
487        return 1;
488    } else if (address->type == TR_AF_INET6) {
489        struct sockaddr_in6 sin6;
490        memset (&sin6, 0, sizeof (sin6));
491        sin6.sin6_family = AF_INET6;
492        memcpy (&sin6.sin6_addr, &address->addr.addr6, 16);
493        sin6.sin6_port = htons (port);
494        dht_ping_node ((struct sockaddr*)&sin6, sizeof (sin6));
495        return 1;
496    }
497
498    return 0;
499}
500
501const char *
502tr_dhtPrintableStatus (int status)
503{
504    switch (status) {
505    case TR_DHT_STOPPED: return "stopped";
506    case TR_DHT_BROKEN: return "broken";
507    case TR_DHT_POOR: return "poor";
508    case TR_DHT_FIREWALLED: return "firewalled";
509    case TR_DHT_GOOD: return "good";
510    default: return "???";
511    }
512}
513
514static void
515callback (void *ignore UNUSED, int event,
516          const unsigned char *info_hash, const void *data, size_t data_len)
517{
518    if (event == DHT_EVENT_VALUES || event == DHT_EVENT_VALUES6) {
519        tr_torrent *tor;
520        tr_sessionLock (session);
521        tor = tr_torrentFindFromHash (session, info_hash);
522        if (tor && tr_torrentAllowsDHT (tor))
523        {
524            size_t i, n;
525            tr_pex * pex;
526            if (event == DHT_EVENT_VALUES)
527                pex = tr_peerMgrCompactToPex (data, data_len, NULL, 0, &n);
528            else
529                pex = tr_peerMgrCompact6ToPex (data, data_len, NULL, 0, &n);
530            for (i=0; i<n; ++i)
531                tr_peerMgrAddPex (tor, TR_PEER_FROM_DHT, pex+i, -1);
532            tr_free (pex);
533            tr_logAddTorDbg (tor, "Learned %d %s peers from DHT",
534                    (int)n,
535                      event == DHT_EVENT_VALUES6 ? "IPv6" : "IPv4");
536        }
537        tr_sessionUnlock (session);
538    } else if (event == DHT_EVENT_SEARCH_DONE ||
539               event == DHT_EVENT_SEARCH_DONE6) {
540        tr_torrent * tor = tr_torrentFindFromHash (session, info_hash);
541        if (tor) {
542            if (event == DHT_EVENT_SEARCH_DONE) {
543                tr_logAddTorInfo (tor, "%s", "IPv4 DHT announce done");
544                tor->dhtAnnounceInProgress = 0;
545            } else {
546                tr_logAddTorInfo (tor, "%s", "IPv6 DHT announce done");
547                tor->dhtAnnounce6InProgress = 0;
548            }
549        }
550    }
551}
552
553static int
554tr_dhtAnnounce (tr_torrent *tor, int af, bool announce)
555{
556    int rc, status, numnodes, ret = 0;
557
558    if (!tr_torrentAllowsDHT (tor))
559        return -1;
560
561    status = tr_dhtStatus (tor->session, af, &numnodes);
562
563    if (status == TR_DHT_STOPPED) {
564        /* Let the caller believe everything is all right. */
565        return 1;
566    }
567
568    if (status >= TR_DHT_POOR) {
569        rc = dht_search (tor->info.hash,
570                         announce ? tr_sessionGetPeerPort (session) : 0,
571                         af, callback, NULL);
572        if (rc >= 1) {
573            tr_logAddTorInfo (tor, "Starting %s DHT announce (%s, %d nodes)",
574                      af == AF_INET6 ? "IPv6" : "IPv4",
575                      tr_dhtPrintableStatus (status), numnodes);
576            if (af == AF_INET)
577                tor->dhtAnnounceInProgress = true;
578            else
579                tor->dhtAnnounce6InProgress = true;
580            ret = 1;
581        } else {
582            tr_logAddTorErr (tor, "%s DHT announce failed (%s, %d nodes): %s",
583                      af == AF_INET6 ? "IPv6" : "IPv4",
584                      tr_dhtPrintableStatus (status), numnodes,
585                      tr_strerror (errno));
586        }
587    } else {
588        tr_logAddTorDbg (tor, "%s DHT not ready (%s, %d nodes)",
589                  af == AF_INET6 ? "IPv6" : "IPv4",
590                  tr_dhtPrintableStatus (status), numnodes);
591    }
592
593    return ret;
594}
595
596void
597tr_dhtUpkeep (tr_session * session)
598{
599    tr_torrent * tor = NULL;
600    const time_t now = tr_time ();
601
602    while ((tor = tr_torrentNext (session, tor)))
603    {
604        if (!tor->isRunning || !tr_torrentAllowsDHT (tor))
605            continue;
606
607        if (tor->dhtAnnounceAt <= now)
608        {
609            const int rc = tr_dhtAnnounce (tor, AF_INET, 1);
610
611            tor->dhtAnnounceAt = now + ((rc == 0)
612                                     ? 5 + tr_cryptoWeakRandInt (5)
613                                     : 25 * 60 + tr_cryptoWeakRandInt (3*60));
614        }
615
616        if (tor->dhtAnnounce6At <= now)
617        {
618            const int rc = tr_dhtAnnounce (tor, AF_INET6, 1);
619
620            tor->dhtAnnounce6At = now + ((rc == 0)
621                                      ? 5 + tr_cryptoWeakRandInt (5)
622                                      : 25 * 60 + tr_cryptoWeakRandInt (3*60));
623        }
624    }
625}
626
627void
628tr_dhtCallback (unsigned char *buf, int buflen,
629               struct sockaddr *from, socklen_t fromlen,
630               void *sv)
631{
632    time_t tosleep;
633    int rc;
634
635    assert (tr_isSession (sv));
636
637    if (sv != session)
638        return;
639
640    rc = dht_periodic (buf, buflen, from, fromlen,
641                       &tosleep, callback, NULL);
642    if (rc < 0) {
643        if (errno == EINTR) {
644            tosleep = 0;
645        } else {
646            tr_logAddNamedError ("DHT", "dht_periodic failed: %s", tr_strerror (errno));
647            if (errno == EINVAL || errno == EFAULT)
648                    abort ();
649            tosleep = 1;
650        }
651    }
652
653    /* Being slightly late is fine,
654       and has the added benefit of adding some jitter. */
655    tr_timerAdd (dht_timer, tosleep, tr_cryptoWeakRandInt (1000000));
656}
657
658static void
659timer_callback (evutil_socket_t s UNUSED, short type UNUSED, void *session)
660{
661    tr_dhtCallback (NULL, 0, NULL, 0, session);
662}
663
664/* This function should return true when a node is blacklisted.  We do
665   not support using a blacklist with the DHT in Transmission, since
666   massive (ab)use of this feature could harm the DHT.  However, feel
667   free to add support to your private copy as long as you don't
668   redistribute it. */
669
670int
671dht_blacklisted (const struct sockaddr *sa UNUSED, int salen UNUSED)
672{
673    return 0;
674}
675
676void
677dht_hash (void *hash_return, int hash_size,
678         const void *v1, int len1,
679         const void *v2, int len2,
680         const void *v3, int len3)
681{
682    unsigned char sha1[SHA_DIGEST_LENGTH];
683    tr_sha1 (sha1, v1, len1, v2, len2, v3, len3, NULL);
684    memset (hash_return, 0, hash_size);
685    memcpy (hash_return, sha1, MIN (hash_size, SHA_DIGEST_LENGTH));
686}
687
688int
689dht_random_bytes (void * buf, size_t size)
690{
691    tr_cryptoRandBuf (buf, size);
692    return size;
693}
Note: See TracBrowser for help on using the repository browser.