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

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

(trunk libT) #2576 "IPv6 support for DHT (BEP #32)" -- silence message "IPv6 DHT not ready." Reported by KyleK; fixed by jch

File size: 19.5 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 <netdb.h>
34#include <unistd.h> /* close() */
35
36/* third party */
37#include <event.h>
38#include <dht/dht.h>
39
40/* libT */
41#include "transmission.h"
42#include "bencode.h"
43#include "crypto.h"
44#include "net.h"
45#include "peer-mgr.h" /* tr_peerMgrCompactToPex() */
46#include "platform.h" /* tr_threadNew() */
47#include "session.h"
48#include "torrent.h" /* tr_torrentFindFromHash() */
49#include "tr-dht.h"
50#include "trevent.h" /* tr_runInEventThread() */
51#include "utils.h"
52#include "version.h"
53
54static int dht_socket = -1, dht6_socket = -1;
55static struct event dht_event, dht6_event;
56static tr_port dht_port;
57static unsigned char myid[20];
58static tr_session *session = NULL;
59
60static void event_callback(int s, short type, void *ignore);
61
62struct bootstrap_closure {
63    tr_session *session;
64    uint8_t *nodes;
65    uint8_t *nodes6;
66    size_t len, len6;
67};
68
69static int
70bootstrap_done( tr_session *session, int af )
71{
72    int status;
73
74    if(af == 0)
75        return
76            bootstrap_done(session, AF_INET) &&
77            bootstrap_done(session, AF_INET6);
78
79    status = tr_dhtStatus(session, af, NULL);
80    return status == TR_DHT_STOPPED || status >= TR_DHT_FIREWALLED;
81}
82
83static void
84nap( int roughly )
85{
86    struct timeval tv;
87    tr_timevalSet( &tv, roughly / 2 + tr_cryptoWeakRandInt( roughly ),
88                   tr_cryptoWeakRandInt( 1000000 ) );
89    select( 0, NULL, NULL, NULL, &tv );
90}
91
92static int
93bootstrap_af(tr_session *session)
94{
95    if( bootstrap_done(session, AF_INET6) )
96        return AF_INET;
97    else if ( bootstrap_done(session, AF_INET) )
98        return AF_INET6;
99    else
100        return 0;
101}
102
103static void
104bootstrap_from_name( const char *name, short int port, int af )
105{
106    struct addrinfo hints, *info, *infop;
107    char pp[10];
108    int rc;
109
110    memset(&hints, 0, sizeof(hints));
111    hints.ai_socktype = SOCK_DGRAM;
112    hints.ai_family = af;
113    /* No, just passing p + 1 to gai won't work. */
114    snprintf(pp, 10, "%d", port);
115
116    rc = getaddrinfo(name, pp, &hints, &info);
117    if(rc != 0) {
118        tr_nerr("DHT", "%s:%s: %s", name, pp, gai_strerror(rc));
119        return;
120    }
121
122    infop = info;
123    while(infop) {
124        dht_ping_node(infop->ai_addr, infop->ai_addrlen);
125
126        nap(15);
127
128        if(bootstrap_done(session, af))
129            break;
130        infop = infop->ai_next;
131    }
132    freeaddrinfo(info);
133}
134
135static void
136dht_bootstrap(void *closure)
137{
138    struct bootstrap_closure *cl = closure;
139    int i;
140    int num = cl->len / 6, num6 = cl->len6 / 18;
141
142    if(session != cl->session)
143        return;
144
145    if(cl->len > 0)
146        tr_ninf( "DHT", "Bootstrapping from %d nodes", num );
147
148    if(cl->len6 > 0)
149        tr_ninf( "DHT", "Bootstrapping from %d IPv6 nodes", num6 );
150
151
152    for(i = 0; i < MAX(num, num6); i++) {
153        if( i < num && !bootstrap_done(cl->session, AF_INET) ) {
154            tr_port port;
155            struct tr_address addr;
156
157            memset(&addr, 0, sizeof(addr));
158            addr.type = TR_AF_INET;
159            memcpy(&addr.addr.addr4, &cl->nodes[i * 6], 4);
160            memcpy(&port, &cl->nodes[i * 6 + 4], 2);
161            port = ntohs(port);
162            tr_dhtAddNode(cl->session, &addr, port, 1);
163        }
164        if( i < num6 && !bootstrap_done(cl->session, AF_INET6) ) {
165            tr_port port;
166            struct tr_address addr;
167
168            memset(&addr, 0, sizeof(addr));
169            addr.type = TR_AF_INET6;
170            memcpy(&addr.addr.addr6, &cl->nodes6[i * 18], 16);
171            memcpy(&port, &cl->nodes6[i * 18 + 16], 2);
172            port = ntohs(port);
173            tr_dhtAddNode(cl->session, &addr, port, 1);
174        }
175
176        /* Our DHT code is able to take up to 9 nodes in a row without
177           dropping any.  After that, it takes some time to split buckets.
178           So ping the first 8 nodes quickly, then slow down. */
179        if(i < 8)
180            nap(2);
181        else
182            nap(15);
183
184        if(bootstrap_done( session, 0 ))
185            break;
186    }
187
188    if(!bootstrap_done(cl->session, 0)) {
189        char *bootstrap_file;
190        FILE *f = NULL;
191
192        bootstrap_file =
193            tr_buildPath(cl->session->configDir, "dht.bootstrap", NULL);
194
195        if(bootstrap_file)
196            f = fopen(bootstrap_file, "r");
197        if(f) {
198            tr_ninf("DHT", "Attempting manual bootstrap");
199            while(1) {
200                char buf[201];
201                char *p;
202                int port = 0;
203
204                p = fgets(buf, 200, f);
205                if( p == NULL )
206                    break;
207
208                p = memchr(buf, ' ', strlen(buf));
209                if(p != NULL)
210                    port = atoi(p + 1);
211                if(p == NULL || port <= 0 || port >= 0x10000) {
212                    tr_nerr("DHT", "Couldn't parse %s", buf);
213                    continue;
214                }
215
216                *p = '\0';
217
218                bootstrap_from_name( buf, port, bootstrap_af(session) );
219
220                if(bootstrap_done(cl->session, 0))
221                    break;
222            }
223        }
224    }
225
226    /* We really don't want to abuse our bootstrap nodes.
227       Be glacially slow. */
228    if(!bootstrap_done(cl->session, 0))
229        nap(30);
230
231    if(!bootstrap_done(cl->session, 0)) {
232        tr_ninf("DHT", "Attempting bootstrap from dht.transmissionbt.com");
233        bootstrap_from_name( "dht.transmissionbt.com", 6881,
234                             bootstrap_af(session) );
235    }
236
237    if( cl->nodes )
238        tr_free( cl->nodes );
239    if( cl->nodes6 )
240        tr_free( cl->nodes6 );
241    tr_free( closure );
242    tr_ndbg( "DHT", "Finished bootstrapping" );
243}
244
245/* BEP-32 has a rather nice explanation of why we need to bind to one
246   IPv6 address, if I may say so myself. */
247
248static int
249rebind_ipv6(int force)
250{
251    struct sockaddr_in6 sin6;
252    const unsigned char *ipv6 = tr_globalIPv6();
253    static unsigned char *last_bound = NULL;
254    int rc;
255
256    if(dht6_socket < 0)
257        return 0;
258
259    if(!force &&
260       ((ipv6 == NULL && last_bound == NULL) ||
261        (ipv6 != NULL && last_bound != NULL &&
262         memcmp(ipv6, last_bound, 16) == 0)))
263        return 0;
264
265    memset(&sin6, 0, sizeof(sin6));
266    sin6.sin6_family = AF_INET6;
267    if(ipv6)
268        memcpy(&sin6.sin6_addr, ipv6, 16);
269    sin6.sin6_port = htons(dht_port);
270    rc = bind(dht6_socket, (struct sockaddr*)&sin6, sizeof(sin6));
271
272    if(last_bound)
273        free(last_bound);
274    last_bound = NULL;
275
276    if(rc >= 0 && ipv6) {
277        last_bound = malloc(16);
278        if(last_bound)
279            memcpy(last_bound, ipv6, 16);
280    }
281    return rc;
282}
283
284int
285tr_dhtInit(tr_session *ss, const tr_address * tr_addr)
286{
287    struct sockaddr_in sin;
288    tr_benc benc;
289    int rc;
290    tr_bool have_id = FALSE;
291    char * dat_file;
292    uint8_t * nodes = NULL, * nodes6 = NULL;
293    const uint8_t * raw;
294    size_t len, len6;
295    char v[5];
296    struct bootstrap_closure * cl;
297
298    if( session ) /* already initialized */
299        return -1;
300
301    dht_port = tr_sessionGetPeerPort(ss);
302    if(dht_port <= 0)
303        return -1;
304
305    tr_ndbg( "DHT", "Initializing DHT" );
306
307    dht_socket = socket(PF_INET, SOCK_DGRAM, 0);
308    if(dht_socket < 0)
309        goto fail;
310
311    memset(&sin, 0, sizeof(sin));
312    sin.sin_family = AF_INET;
313    memcpy(&sin.sin_addr, &tr_addr->addr.addr4, sizeof (struct in_addr));
314    sin.sin_port = htons(dht_port);
315    rc = bind(dht_socket, (struct sockaddr*)&sin, sizeof(sin));
316    if(rc < 0)
317        goto fail;
318
319    if(tr_globalIPv6()) {
320        dht6_socket = socket(PF_INET6, SOCK_DGRAM, 0);
321        if(dht6_socket < 0)
322            goto fail;
323
324        rebind_ipv6(1);
325
326    }
327
328    if( getenv( "TR_DHT_VERBOSE" ) != NULL )
329        dht_debug = stderr;
330
331    dat_file = tr_buildPath( ss->configDir, "dht.dat", NULL );
332    rc = tr_bencLoadFile( &benc, TR_FMT_BENC, dat_file );
333    tr_free( dat_file );
334    if(rc == 0) {
335        have_id = tr_bencDictFindRaw(&benc, "id", &raw, &len);
336        if( have_id && len==20 )
337            memcpy( myid, raw, len );
338        if( dht_socket >= 0 &&
339            tr_bencDictFindRaw( &benc, "nodes", &raw, &len ) && !(len%6) ) {
340                nodes = tr_memdup( raw, len );
341        }
342        if( dht6_socket > 0 &&
343            tr_bencDictFindRaw( &benc, "nodes6", &raw, &len6 ) && !(len6%18) ) {
344            nodes6 = tr_memdup( raw, len6 );
345        }
346        tr_bencFree( &benc );
347    }
348
349    if(nodes == NULL)
350        len = 0;
351    if(nodes6 == NULL)
352        len6 = 0;
353
354    if( have_id )
355        tr_ninf( "DHT", "Reusing old id" );
356    else {
357        /* Note that DHT ids need to be distributed uniformly,
358         * so it should be something truly random. */
359        tr_ninf( "DHT", "Generating new id" );
360        tr_cryptoRandBuf( myid, 20 );
361    }
362
363    v[0] = 'T';
364    v[1] = 'R';
365    v[2] = (SVN_REVISION_NUM >> 8) & 0xFF;
366    v[3] = SVN_REVISION_NUM & 0xFF;
367    rc = dht_init( dht_socket, dht6_socket, myid, (const unsigned char*)v );
368    if(rc < 0)
369        goto fail;
370
371    session = ss;
372
373    cl = tr_new( struct bootstrap_closure, 1 );
374    cl->session = session;
375    cl->nodes = nodes;
376    cl->nodes6 = nodes6;
377    cl->len = len;
378    cl->len6 = len6;
379    tr_threadNew( dht_bootstrap, cl );
380
381    event_set( &dht_event, dht_socket, EV_READ, event_callback, NULL );
382    tr_timerAdd( &dht_event, 0, tr_cryptoWeakRandInt( 1000000 ) );
383
384    if( dht6_socket >= 0 )
385    {
386        event_set( &dht6_event, dht6_socket, EV_READ, event_callback, NULL );
387        tr_timerAdd( &dht6_event, 0, tr_cryptoWeakRandInt( 1000000 ) );
388    }
389
390    tr_ndbg( "DHT", "DHT initialized" );
391
392    return 1;
393
394    fail:
395    {
396        const int save = errno;
397        close(dht_socket);
398        if( dht6_socket >= 0 )
399            close(dht6_socket);
400        dht_socket = dht6_socket = -1;
401        session = NULL;
402        tr_ndbg( "DHT", "DHT initialization failed (errno = %d)", save );
403        errno = save;
404    }
405
406    return -1;
407}
408
409void
410tr_dhtUninit(tr_session *ss)
411{
412    if(session != ss)
413        return;
414
415    tr_ndbg( "DHT", "Uninitializing DHT" );
416
417    event_del( &dht_event );
418
419    if( dht6_socket >= 0 )
420        event_del( &dht6_event );
421
422    /* Since we only save known good nodes, avoid erasing older data if we
423       don't know enough nodes. */
424    if(tr_dhtStatus(ss, AF_INET, NULL) < TR_DHT_FIREWALLED)
425        tr_ninf( "DHT", "Not saving nodes, DHT not ready" );
426    else {
427        tr_benc benc;
428        struct sockaddr_in sins[300];
429        struct sockaddr_in6 sins6[300];
430        char compact[300 * 6], compact6[300 * 18];
431        char *dat_file;
432        int i, j, num = 300, num6 = 300;
433        int n = dht_get_nodes(sins, &num, sins6, &num6);
434
435        tr_ninf( "DHT", "Saving %d (%d + %d) nodes", n, num, num6 );
436
437        j = 0;
438        for( i=0; i<num; ++i ) {
439            memcpy( compact + j, &sins[i].sin_addr, 4 );
440            memcpy( compact + j + 4, &sins[i].sin_port, 2 );
441            j += 6;
442        }
443        j = 0;
444        for( i=0; i<num6; ++i ) {
445            memcpy( compact6 + j, &sins6[i].sin6_addr, 16 );
446            memcpy( compact6 + j + 16, &sins6[i].sin6_port, 2 );
447            j += 18;
448        }
449        tr_bencInitDict( &benc, 3 );
450        tr_bencDictAddRaw( &benc, "id", myid, 20 );
451        if(num > 0)
452            tr_bencDictAddRaw( &benc, "nodes", compact, num * 6 );
453        if(num6 > 0)
454            tr_bencDictAddRaw( &benc, "nodes6", compact6, num6 * 18 );
455        dat_file = tr_buildPath( ss->configDir, "dht.dat", NULL );
456        tr_bencToFile( &benc, TR_FMT_BENC, dat_file );
457        tr_bencFree( &benc );
458        tr_free( dat_file );
459    }
460
461    dht_uninit( 1 );
462    tr_netCloseSocket( dht_socket );
463    if(dht6_socket > 0)
464        tr_netCloseSocket( dht6_socket );
465
466    tr_ndbg("DHT", "Done uninitializing DHT");
467
468    session = NULL;
469}
470
471tr_bool
472tr_dhtEnabled( tr_session * ss )
473{
474    return ss && ( ss == session );
475}
476
477struct getstatus_closure
478{
479    int af;
480    sig_atomic_t status;
481    sig_atomic_t count;
482};
483
484static void
485getstatus( void * cl )
486{
487    struct getstatus_closure * closure = cl;
488    int good, dubious, incoming;
489
490    dht_nodes( closure->af, &good, &dubious, NULL, &incoming );
491
492    closure->count = good + dubious;
493
494    if( good < 4 || good + dubious <= 8 )
495        closure->status = TR_DHT_BROKEN;
496    else if( good < 40 )
497        closure->status = TR_DHT_POOR;
498    else if( incoming < 8 )
499        closure->status = TR_DHT_FIREWALLED;
500    else
501        closure->status = TR_DHT_GOOD;
502}
503
504int
505tr_dhtStatus( tr_session * ss, int af, int * nodes_return )
506{
507    struct getstatus_closure closure = { af, -1, -1 };
508
509    if( !tr_dhtEnabled( ss ) ||
510        (af == AF_INET && dht_socket < 0) ||
511        (af == AF_INET6 && dht6_socket < 0) ) {
512        if( nodes_return )
513            *nodes_return = 0;
514        return TR_DHT_STOPPED;
515    }
516
517    tr_runInEventThread( ss, getstatus, &closure );
518    while( closure.status < 0 )
519        tr_wait( 10 /*msec*/ );
520
521    if( nodes_return )
522        *nodes_return = closure.count;
523
524    return closure.status;
525}
526
527tr_port
528tr_dhtPort( tr_session *ss )
529{
530    return tr_dhtEnabled( ss ) ? dht_port : 0;
531}
532
533int
534tr_dhtAddNode( tr_session       * ss,
535               const tr_address * address,
536               tr_port            port,
537               tr_bool            bootstrap )
538{
539    int af = address->type == TR_AF_INET ? AF_INET : AF_INET6;
540
541    if( !tr_dhtEnabled( ss ) )
542        return 0;
543
544    /* Since we don't want to abuse our bootstrap nodes,
545     * we don't ping them if the DHT is in a good state. */
546
547    if(bootstrap) {
548        if(tr_dhtStatus(ss, af, NULL) >= TR_DHT_FIREWALLED)
549            return 0;
550    }
551
552    if( address->type == TR_AF_INET ) {
553        struct sockaddr_in sin;
554        memset(&sin, 0, sizeof(sin));
555        sin.sin_family = AF_INET;
556        memcpy(&sin.sin_addr, &address->addr.addr4, 4);
557        sin.sin_port = htons(port);
558        dht_ping_node((struct sockaddr*)&sin, sizeof(sin));
559        return 1;
560    } else if( address->type == TR_AF_INET6 ) {
561        struct sockaddr_in6 sin6;
562        memset(&sin6, 0, sizeof(sin6));
563        sin6.sin6_family = AF_INET6;
564        memcpy(&sin6.sin6_addr, &address->addr.addr6, 16);
565        sin6.sin6_port = htons(port);
566        dht_ping_node((struct sockaddr*)&sin6, sizeof(sin6));
567        return 1;
568    }
569
570    return 0;
571}
572
573const char *
574tr_dhtPrintableStatus(int status)
575{
576    switch(status) {
577    case TR_DHT_STOPPED: return "stopped";
578    case TR_DHT_BROKEN: return "broken";
579    case TR_DHT_POOR: return "poor";
580    case TR_DHT_FIREWALLED: return "firewalled";
581    case TR_DHT_GOOD: return "good";
582    default: return "???";
583    }
584}
585
586static void
587callback( void *ignore UNUSED, int event,
588          unsigned char *info_hash, void *data, size_t data_len )
589{
590    if( event == DHT_EVENT_VALUES || event == DHT_EVENT_VALUES6 ) {
591        tr_torrent *tor;
592        tr_globalLock( session );
593        tor = tr_torrentFindFromHash( session, info_hash );
594        if( tor && tr_torrentAllowsDHT( tor ))
595        {
596            size_t i, n;
597            tr_pex * pex;
598            if( event == DHT_EVENT_VALUES )
599                pex = tr_peerMgrCompactToPex(data, data_len, NULL, 0, &n);
600            else
601                pex = tr_peerMgrCompact6ToPex(data, data_len, NULL, 0, &n);
602            for( i=0; i<n; ++i )
603                tr_peerMgrAddPex( tor, TR_PEER_FROM_DHT, pex+i );
604            tr_free(pex);
605            tr_torinf(tor, "Learned %d%s peers from DHT",
606                      (int)n,
607                      event == DHT_EVENT_VALUES6 ? " IPv6" : "");
608        }
609        tr_globalUnlock( session );
610    } else if( event == DHT_EVENT_SEARCH_DONE ||
611               event == DHT_EVENT_SEARCH_DONE6) {
612        tr_torrent * tor = tr_torrentFindFromHash( session, info_hash );
613        if( tor ) {
614            if( event == DHT_EVENT_SEARCH_DONE ) {
615                tr_torinf(tor, "DHT announce done");
616                tor->dhtAnnounceInProgress = 0;
617            } else {
618                tr_torinf(tor, "IPv6 DHT announce done");
619                tor->dhtAnnounce6InProgress = 0;
620            }
621        }
622    }
623}
624
625int
626tr_dhtAnnounce(tr_torrent *tor, int af, tr_bool announce)
627{
628    int rc, status, numnodes, ret = 0;
629
630    if( !tr_torrentAllowsDHT( tor ) )
631        return -1;
632
633    status = tr_dhtStatus( tor->session, af, &numnodes );
634
635    if( status == TR_DHT_STOPPED ) {
636        /* Let the caller believe everything is all right. */
637        return 1;
638    }
639
640    if(status >= TR_DHT_POOR ) {
641        rc = dht_search( tor->info.hash,
642                         announce ? tr_sessionGetPeerPort(session) : 0,
643                         af, callback, NULL);
644        if( rc >= 1 ) {
645            tr_torinf(tor, "Starting%s DHT announce (%s, %d nodes)",
646                      af == AF_INET6 ? " IPv6" : "",
647                      tr_dhtPrintableStatus(status), numnodes);
648            if(af == AF_INET)
649                tor->dhtAnnounceInProgress = TRUE;
650            else
651                tor->dhtAnnounce6InProgress = TRUE;
652            ret = 1;
653        } else {
654            tr_torerr(tor, "%sDHT announce failed, errno = %d (%s, %d nodes)",
655                      af == AF_INET6 ? "IPv6 " : "",
656                      errno, tr_dhtPrintableStatus(status), numnodes);
657        }
658    } else {
659        tr_tordbg(tor, "%sDHT not ready (%s, %d nodes)",
660                  af == AF_INET6 ? "IPv6 " : "",
661                  tr_dhtPrintableStatus(status), numnodes);
662    }
663
664    return ret;
665}
666
667static void
668event_callback(int s, short type, void *ignore UNUSED )
669{
670    struct event *event = (s == dht_socket) ? &dht_event : &dht6_event;
671    time_t tosleep;
672    static int count = 0;
673
674    if( dht_periodic( type == EV_READ, &tosleep, callback, NULL) < 0 ) {
675        if(errno == EINTR) {
676            tosleep = 0;
677        } else {
678            tr_nerr("DHT", "dht_periodic failed (errno = %d)", errno);
679            if(errno == EINVAL || errno == EFAULT)
680                    abort();
681            tosleep = 1;
682        }
683    }
684
685    /* Only do this once in a while.  Counting rather than measuring time
686       avoids a system call. */
687    count++;
688    if(count >= 128) {
689        rebind_ipv6(0);
690        count = 0;
691    }
692
693    /* Being slightly late is fine,
694       and has the added benefit of adding some jitter. */
695    tr_timerAdd( event, tosleep, tr_cryptoWeakRandInt( 1000000 ) );
696}
697
698void
699dht_hash(void *hash_return, int hash_size,
700         const void *v1, int len1,
701         const void *v2, int len2,
702         const void *v3, int len3)
703{
704    unsigned char sha1[SHA_DIGEST_LENGTH];
705    tr_sha1( sha1, v1, len1, v2, len2, v3, len3, NULL );
706    memset( hash_return, 0, hash_size );
707    memcpy( hash_return, sha1, MIN( hash_size, SHA_DIGEST_LENGTH ) );
708}
709
710int
711dht_random_bytes( void * buf, size_t size )
712{
713    tr_cryptoRandBuf( buf, size );
714    return size;
715}
Note: See TracBrowser for help on using the repository browser.