(trunk) #7: DHT support. This is a work in progress... no gui/rpc support yet
This commit is contained in:
parent
f4d30c07ac
commit
3d7cc36424
1
AUTHORS
1
AUTHORS
|
@ -7,6 +7,7 @@ Lead Developers <dev@transmissionbt.com>
|
|||
|
||||
Project Contributors
|
||||
Tomas Carnecky (Profiling, patches, and detection of sneaky bugs)
|
||||
Juliusz Chroboczek (DHT)
|
||||
John Clay (Website maintenance and troubleshooting)
|
||||
Rashid Eissing (Mac OS X Transfers preferences icon)
|
||||
Hugo van Heuven, madebysofa (Main icon design)
|
||||
|
|
|
@ -20,6 +20,7 @@ transmissioncli_LDADD = \
|
|||
$(top_builddir)/third-party/libevent/libevent.la \
|
||||
$(top_builddir)/third-party/libnatpmp/libnatpmp.a \
|
||||
$(top_builddir)/third-party/miniupnp/libminiupnp.a \
|
||||
$(top_builddir)/third-party/dht/libdht.a \
|
||||
$(INTLLIBS) \
|
||||
$(LIBCURL_LIBS) \
|
||||
$(ZLIB_LIBS) \
|
||||
|
|
|
@ -263,7 +263,7 @@ fi
|
|||
use_nls=no
|
||||
if test "x$enable_nls" = "xyes" ; then
|
||||
use_nls=yes
|
||||
IT_PROG_INTLTOOL([0.40.0],[no-xml])
|
||||
IT_PROG_INTLTOOL([0.35],[no-xml])
|
||||
AC_CHECK_HEADERS([libintl.h])
|
||||
GETTEXT_PACKAGE=transmission
|
||||
AC_SUBST(GETTEXT_PACKAGE)
|
||||
|
@ -355,6 +355,7 @@ AC_CONFIG_FILES([Makefile
|
|||
third-party/Makefile
|
||||
third-party/miniupnp/Makefile
|
||||
third-party/libnatpmp/Makefile
|
||||
third-party/dht/Makefile
|
||||
macosx/Makefile
|
||||
gtk/Makefile
|
||||
gtk/icons/Makefile
|
||||
|
|
|
@ -22,6 +22,7 @@ LDADD = \
|
|||
$(top_builddir)/third-party/miniupnp/libminiupnp.a \
|
||||
$(top_builddir)/third-party/libnatpmp/libnatpmp.a \
|
||||
$(top_builddir)/third-party/libevent/libevent.la \
|
||||
$(top_builddir)/third-party/dht/libdht.a \
|
||||
$(INTLLIBS) \
|
||||
$(LIBCURL_LIBS) \
|
||||
$(ZLIB_LIBS) \
|
||||
|
|
|
@ -103,6 +103,7 @@ transmission_LDADD = \
|
|||
$(top_builddir)/third-party/libevent/libevent.la \
|
||||
$(top_builddir)/third-party/miniupnp/libminiupnp.a \
|
||||
$(top_builddir)/third-party/libnatpmp/libnatpmp.a \
|
||||
$(top_builddir)/third-party/dht/libdht.a \
|
||||
$(GTK_LIBS) \
|
||||
$(GIO_LIBS) \
|
||||
$(LIBNOTIFY_LIBS) \
|
||||
|
|
|
@ -941,7 +941,7 @@ refreshInfo( struct DetailsImpl * di, tr_torrent ** torrents, int n )
|
|||
if( i!=n )
|
||||
str = mixed;
|
||||
else if( baseline )
|
||||
str = _( "Private to this tracker -- PEX disabled" );
|
||||
str = _( "Private to this tracker -- DHT and PEX disabled" );
|
||||
else
|
||||
str = _( "Public torrent" );
|
||||
}
|
||||
|
@ -1503,6 +1503,7 @@ onPeerViewQueryTooltip( GtkWidget * widget,
|
|||
case '?': s = _( "We unchoked this peer, but they're not interested" ); break;
|
||||
case 'E': s = _( "Encrypted connection" ); break;
|
||||
case 'X': s = _( "Peer was discovered through Peer Exchange (PEX)" ); break;
|
||||
case 'H': s = _( "Peer was discovered through DHT" ); break;
|
||||
case 'I': s = _( "Peer is an incoming connection" ); break;
|
||||
}
|
||||
if( s )
|
||||
|
|
|
@ -49,6 +49,7 @@ libtransmission_a_SOURCES = \
|
|||
torrent-ctor.c \
|
||||
tr-getopt.c \
|
||||
tracker.c \
|
||||
tr-dht.c \
|
||||
trevent.c \
|
||||
upnp.c \
|
||||
utils.c \
|
||||
|
@ -96,6 +97,7 @@ noinst_HEADERS = \
|
|||
tracker.h \
|
||||
tr-getopt.h \
|
||||
transmission.h \
|
||||
tr-dht.h \
|
||||
trevent.h \
|
||||
upnp.h \
|
||||
utils.h \
|
||||
|
@ -124,6 +126,7 @@ apps_ldadd = \
|
|||
$(top_builddir)/third-party/miniupnp/libminiupnp.a \
|
||||
$(top_builddir)/third-party/libnatpmp/libnatpmp.a \
|
||||
$(top_builddir)/third-party/libevent/libevent.la \
|
||||
$(top_builddir)/third-party/dht/libdht.a \
|
||||
$(INTLLIBS) \
|
||||
$(LIBCURL_LIBS) \
|
||||
$(OPENSSL_LIBS) \
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "peer-io.h"
|
||||
#include "peer-mgr.h"
|
||||
#include "torrent.h"
|
||||
#include "tr-dht.h"
|
||||
#include "trevent.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
@ -35,6 +36,8 @@
|
|||
#define ENABLE_LTEP * /
|
||||
/* fast extensions */
|
||||
#define ENABLE_FAST * /
|
||||
/* DHT */
|
||||
#define ENABLE_DHT * /
|
||||
|
||||
/***
|
||||
****
|
||||
|
@ -82,6 +85,14 @@ enum
|
|||
#define HANDSHAKE_SET_FASTEXT( bits ) ( (void)0 )
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_DHT
|
||||
#define HANDSHAKE_HAS_DHT( bits ) ( ( ( bits )[7] & 0x01 ) ? 1 : 0 )
|
||||
#define HANDSHAKE_SET_DHT( bits ) ( ( bits )[7] |= 0x01 )
|
||||
#else
|
||||
#define HANDSHAKE_HAS_DHT( bits ) ( 0 )
|
||||
#define HANDSHAKE_SET_DHT( bits ) ( (void)0 )
|
||||
#endif
|
||||
|
||||
/* http://www.azureuswiki.com/index.php/Extension_negotiation_protocol
|
||||
these macros are to be used if both extended messaging and the
|
||||
azureus protocol is supported, they indicate which protocol is preferred */
|
||||
|
@ -219,6 +230,12 @@ buildHandshakeMessage( tr_handshake * handshake,
|
|||
HANDSHAKE_SET_LTEP( walk );
|
||||
HANDSHAKE_SET_FASTEXT( walk );
|
||||
|
||||
/* Note that this doesn't depend on whether the torrent is private. We
|
||||
don't accept DHT peers for a private torrent, but we participate in
|
||||
the DHT regardless. */
|
||||
if(tr_dhtEnabled(handshake->session))
|
||||
HANDSHAKE_SET_DHT( walk );
|
||||
|
||||
walk += HANDSHAKE_FLAGS_LEN;
|
||||
memcpy( walk, torrentHash, SHA_DIGEST_LENGTH );
|
||||
walk += SHA_DIGEST_LENGTH;
|
||||
|
@ -303,6 +320,10 @@ parseHandshake( tr_handshake * handshake,
|
|||
|
||||
tr_peerIoEnableFEXT( handshake->io, HANDSHAKE_HAS_FASTEXT( reserved ) );
|
||||
|
||||
/* This doesn't depend on whether the torrent is private. */
|
||||
if( tor->session->isDHTEnabled )
|
||||
tr_peerIoEnableDHT( handshake->io, HANDSHAKE_HAS_DHT( reserved ) );
|
||||
|
||||
return HANDSHAKE_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -617,6 +617,16 @@ tr_peerIoEnableLTEP( tr_peerIo * io,
|
|||
io->extendedProtocolSupported = flag;
|
||||
}
|
||||
|
||||
void
|
||||
tr_peerIoEnableDHT( tr_peerIo * io, tr_bool flag )
|
||||
{
|
||||
assert( tr_isPeerIo( io ) );
|
||||
assert( tr_isBool( flag ) );
|
||||
|
||||
dbgmsg( io, "setting DHT support flag to %d", (flag!=0) );
|
||||
io->dhtSupported = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
***
|
||||
**/
|
||||
|
|
|
@ -63,6 +63,7 @@ typedef struct tr_peerIo
|
|||
tr_bool peerIdIsSet;
|
||||
tr_bool extendedProtocolSupported;
|
||||
tr_bool fastExtensionSupported;
|
||||
tr_bool dhtSupported;
|
||||
|
||||
/* we create the socket in a nonblocking way, so this flag is initially
|
||||
* false and then set to true when libevent says that the socket is ready
|
||||
|
@ -159,6 +160,15 @@ static TR_INLINE tr_bool tr_peerIoSupportsFEXT( const tr_peerIo * io )
|
|||
return io->fastExtensionSupported;
|
||||
}
|
||||
|
||||
void tr_peerIoEnableDHT( tr_peerIo * io, tr_bool flag );
|
||||
|
||||
static TR_INLINE tr_bool tr_peerIoSupportsDHT( const tr_peerIo * io )
|
||||
{
|
||||
assert( tr_isPeerIo( io ) );
|
||||
|
||||
return io->dhtSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
***
|
||||
**/
|
||||
|
|
|
@ -1873,6 +1873,7 @@ tr_peerMgrPeerStats( const tr_torrent * tor,
|
|||
if( !stat->clientIsChoked && !stat->clientIsInterested ) *pch++ = 'K';
|
||||
if( !stat->peerIsChoked && !stat->peerIsInterested ) *pch++ = '?';
|
||||
if( stat->isEncrypted ) *pch++ = 'E';
|
||||
if( stat->from == TR_PEER_FROM_DHT ) *pch++ = 'H';
|
||||
if( stat->from == TR_PEER_FROM_PEX ) *pch++ = 'X';
|
||||
if( stat->isIncoming ) *pch++ = 'I';
|
||||
*pch = '\0';
|
||||
|
@ -2163,8 +2164,8 @@ compareCandidates( const void * va,
|
|||
if( a->time != b->time )
|
||||
return a->time < b->time ? -1 : 1;
|
||||
|
||||
/* all other things being equal, prefer peers whose
|
||||
* information comes from a more reliable source */
|
||||
/* In order to avoid fragmenting the swarm, peers from trackers and
|
||||
* from the DHT should be preferred to peers from PEX. */
|
||||
if( a->from != b->from )
|
||||
return a->from < b->from ? -1 : 1;
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "request-list.h"
|
||||
#include "stats.h"
|
||||
#include "torrent.h"
|
||||
#include "tr-dht.h"
|
||||
#include "trevent.h"
|
||||
#include "utils.h"
|
||||
#include "version.h"
|
||||
|
@ -306,6 +307,18 @@ protocolSendCancel( tr_peermsgs * msgs,
|
|||
pokeBatchPeriod( msgs, IMMEDIATE_PRIORITY_INTERVAL_SECS );
|
||||
}
|
||||
|
||||
static void
|
||||
protocolSendPort(tr_peermsgs *msgs, uint16_t port)
|
||||
{
|
||||
tr_peerIo * io = msgs->peer->io;
|
||||
struct evbuffer * out = msgs->outMessages;
|
||||
|
||||
dbgmsg( msgs, "sending Port %u", port);
|
||||
tr_peerIoWriteUint32( io, out, 3 );
|
||||
tr_peerIoWriteUint8 ( io, out, BT_PORT );
|
||||
tr_peerIoWriteUint16( io, out, port);
|
||||
}
|
||||
|
||||
static void
|
||||
protocolSendHave( tr_peermsgs * msgs,
|
||||
uint32_t index )
|
||||
|
@ -2123,6 +2136,9 @@ tr_peerMsgsNew( struct tr_torrent * torrent,
|
|||
if( tr_peerIoSupportsLTEP( peer->io ) )
|
||||
sendLtepHandshake( m );
|
||||
|
||||
if(tr_peerIoSupportsDHT(peer->io))
|
||||
protocolSendPort(m, tr_dhtPort(torrent->session));
|
||||
|
||||
tellPeerWhatWeHave( m );
|
||||
|
||||
tr_peerIoSetIOFuncs( m->peer->io, canRead, didWrite, gotError, m );
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "version.h"
|
||||
#include "verify.h"
|
||||
#include "web.h"
|
||||
#include "tr-dht.h"
|
||||
|
||||
#define dbgmsg( ... ) \
|
||||
do { \
|
||||
|
@ -621,6 +622,8 @@ tr_sessionInitImpl( void * vdata )
|
|||
found = tr_bencDictFindBool( &settings, TR_PREFS_KEY_PEX_ENABLED, &boolVal );
|
||||
assert( found );
|
||||
session->isPexEnabled = boolVal;
|
||||
/* This really ought to be a separate preference. */
|
||||
session->isDHTEnabled = boolVal;
|
||||
|
||||
found = tr_bencDictFindInt( &settings, TR_PREFS_KEY_ENCRYPTION, &i );
|
||||
assert( found );
|
||||
|
@ -830,6 +833,9 @@ tr_sessionInitImpl( void * vdata )
|
|||
metainfoLookupRescan( session );
|
||||
session->isWaiting = FALSE;
|
||||
dbgmsg( "returning session %p; session->tracker is %p", session, session->tracker );
|
||||
|
||||
if( session->isDHTEnabled )
|
||||
tr_dhtInit(session);
|
||||
}
|
||||
|
||||
/***
|
||||
|
@ -1392,7 +1398,7 @@ compareTorrentByCur( const void * va, const void * vb )
|
|||
}
|
||||
|
||||
static void
|
||||
tr_closeAllConnections( void * vsession )
|
||||
sessionCloseImpl( void * vsession )
|
||||
{
|
||||
tr_session * session = vsession;
|
||||
tr_torrent * tor;
|
||||
|
@ -1403,6 +1409,9 @@ tr_closeAllConnections( void * vsession )
|
|||
|
||||
free_incoming_peer_port( session );
|
||||
|
||||
if( session->isDHTEnabled )
|
||||
tr_dhtUninit( session );
|
||||
|
||||
evtimer_del( session->altTimer );
|
||||
tr_free( session->altTimer );
|
||||
session->altTimer = NULL;
|
||||
|
@ -1455,7 +1464,7 @@ tr_sessionClose( tr_session * session )
|
|||
dbgmsg( "shutting down transmission session %p", session );
|
||||
|
||||
/* close the session */
|
||||
tr_runInEventThread( session, tr_closeAllConnections, session );
|
||||
tr_runInEventThread( session, sessionCloseImpl, session );
|
||||
while( !session->isClosed && !deadlineReached( deadline ) )
|
||||
{
|
||||
dbgmsg(
|
||||
|
@ -1579,6 +1588,14 @@ tr_sessionIsPexEnabled( const tr_session * session )
|
|||
return session->isPexEnabled;
|
||||
}
|
||||
|
||||
tr_bool
|
||||
tr_sessionIsDHTEnabled( const tr_session * session )
|
||||
{
|
||||
assert( tr_isSession( session ) );
|
||||
|
||||
return session->isDHTEnabled;
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
|
|
@ -61,6 +61,7 @@ struct tr_session
|
|||
{
|
||||
tr_bool isPortRandom;
|
||||
tr_bool isPexEnabled;
|
||||
tr_bool isDHTEnabled;
|
||||
tr_bool isBlocklistEnabled;
|
||||
tr_bool isProxyEnabled;
|
||||
tr_bool isProxyAuthEnabled;
|
||||
|
|
|
@ -1271,20 +1271,23 @@ freeTorrent( tr_torrent * tor )
|
|||
static void
|
||||
checkAndStartImpl( void * vtor )
|
||||
{
|
||||
time_t now;
|
||||
tr_torrent * tor = vtor;
|
||||
|
||||
assert( tr_isTorrent( tor ) );
|
||||
|
||||
tr_globalLock( tor->session );
|
||||
|
||||
now = time( NULL );
|
||||
tor->isRunning = TRUE;
|
||||
tor->needsSeedRatioCheck = TRUE;
|
||||
*tor->errorString = '\0';
|
||||
tr_torrentResetTransferStats( tor );
|
||||
tor->completeness = tr_cpGetStatus( &tor->completion );
|
||||
tr_torrentSaveResume( tor );
|
||||
tor->startDate = tor->anyDate = time( NULL );
|
||||
tor->startDate = tor->anyDate = now;
|
||||
tr_trackerStart( tor->tracker );
|
||||
tor->dhtAnnounceAt = now + tr_cryptoWeakRandInt( 20 );
|
||||
tr_peerMgrStartTorrent( tor );
|
||||
|
||||
tr_globalUnlock( tor->session );
|
||||
|
|
|
@ -175,6 +175,9 @@ struct tr_torrent
|
|||
struct tr_tracker * tracker;
|
||||
struct tr_publisher_tag * trackerSubscription;
|
||||
|
||||
time_t dhtAnnounceAt;
|
||||
tr_bool dhtAnnounceInProgress;
|
||||
|
||||
uint64_t downloadedCur;
|
||||
uint64_t downloadedPrev;
|
||||
uint64_t uploadedCur;
|
||||
|
@ -288,6 +291,11 @@ static TR_INLINE tr_bool tr_torrentAllowsPex( const tr_torrent * tor )
|
|||
return ( tor != NULL ) && tor->session->isPexEnabled && !tr_torrentIsPrivate( tor );
|
||||
}
|
||||
|
||||
static TR_INLINE tr_bool tr_torrentAllowsDHT( const tr_torrent * tor )
|
||||
{
|
||||
return ( tor != NULL ) && tor->session->isDHTEnabled && !tr_torrentIsPrivate( tor );
|
||||
}
|
||||
|
||||
static TR_INLINE tr_bool tr_torrentIsPieceChecked( const tr_torrent * tor, tr_piece_index_t i )
|
||||
{
|
||||
return tr_bitfieldHasFast( &tor->checkedPieces, i );
|
||||
|
|
|
@ -0,0 +1,397 @@
|
|||
/*
|
||||
Copyright (c) 2009 by Juliusz Chroboczek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/signal.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include <event.h>
|
||||
|
||||
#include <dht/dht.h>
|
||||
|
||||
#include "transmission.h"
|
||||
#include "crypto.h"
|
||||
#include "peer-mgr.h"
|
||||
#include "platform.h"
|
||||
#include "session.h"
|
||||
#include "torrent.h"
|
||||
#include "trevent.h"
|
||||
#include "tr-dht.h"
|
||||
#include "utils.h"
|
||||
|
||||
static int dht_socket;
|
||||
static struct event dht_event;
|
||||
static tr_port dht_port;
|
||||
static unsigned char myid[20];
|
||||
static tr_session *session = NULL;
|
||||
|
||||
static void event_callback(int s, short type, void *ignore);
|
||||
|
||||
struct bootstrap_closure {
|
||||
tr_session *session;
|
||||
uint8_t *nodes;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
static void
|
||||
dht_bootstrap(void *closure)
|
||||
{
|
||||
struct bootstrap_closure *cl = closure;
|
||||
size_t i;
|
||||
|
||||
if(session != cl->session)
|
||||
return;
|
||||
|
||||
for(i = 0; i < cl->len; i += 6)
|
||||
{
|
||||
struct timeval tv;
|
||||
tr_port port;
|
||||
struct tr_address addr;
|
||||
int status;
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.type = TR_AF_INET;
|
||||
memcpy(&addr.addr.addr4, &cl->nodes[i], 4);
|
||||
memcpy(&port, &cl->nodes[i + 4], 2);
|
||||
port = ntohs(port);
|
||||
/* There's no race here -- if we uninit between the test and the
|
||||
AddNode, the AddNode will be ignored. */
|
||||
status = tr_dhtStatus(cl->session);
|
||||
if(status == TR_DHT_STOPPED || status >= TR_DHT_FIREWALLED)
|
||||
break;
|
||||
tr_dhtAddNode(cl->session, &addr, port, 1);
|
||||
tv.tv_sec = 2 + tr_cryptoWeakRandInt( 5 );
|
||||
tv.tv_usec = tr_cryptoWeakRandInt( 1000000 );
|
||||
select(0, NULL, NULL, NULL, &tv);
|
||||
}
|
||||
tr_free( cl->nodes );
|
||||
tr_free( closure );
|
||||
}
|
||||
|
||||
int
|
||||
tr_dhtInit(tr_session *ss)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
struct timeval tv;
|
||||
tr_benc benc;
|
||||
int rc;
|
||||
tr_bool have_id = FALSE;
|
||||
char * dat_file;
|
||||
uint8_t * nodes = NULL;
|
||||
const uint8_t * raw;
|
||||
size_t len;
|
||||
|
||||
if(session)
|
||||
return -1;
|
||||
|
||||
dht_socket = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if(dht_socket < 0)
|
||||
return -1;
|
||||
|
||||
dht_port = tr_sessionGetPeerPort(ss);
|
||||
if(dht_port <= 0)
|
||||
return -1;
|
||||
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htons(dht_port);
|
||||
rc = bind(dht_socket, (struct sockaddr*)&sin, sizeof(sin));
|
||||
if(rc < 0)
|
||||
goto fail;
|
||||
|
||||
#ifdef DEBUG_DHT
|
||||
dht_debug = stdout;
|
||||
#endif
|
||||
|
||||
dat_file = tr_buildPath( ss->configDir, "dht.dat", NULL );
|
||||
rc = tr_bencLoadFile(dat_file, &benc);
|
||||
tr_free( dat_file );
|
||||
if(rc == 0) {
|
||||
if(tr_bencDictFindRaw(&benc, "id", &raw, &len)) {
|
||||
if(raw && len == 20) {
|
||||
memcpy(myid, raw, len);
|
||||
have_id = TRUE;
|
||||
}
|
||||
}
|
||||
if(tr_bencDictFindRaw(&benc, "nodes", &raw, &len)) {
|
||||
if(len % 6 == 2) {
|
||||
/* This hack allows reading of uTorrent files, which I find
|
||||
convenient. */
|
||||
len -= 2;
|
||||
}
|
||||
nodes = tr_new( uint8_t, len );
|
||||
memcpy( nodes, raw, len );
|
||||
}
|
||||
tr_bencFree(&benc);
|
||||
}
|
||||
|
||||
if(!have_id) {
|
||||
/* Note that you cannot just use your BT id -- DHT ids need to be
|
||||
distributed uniformly, so it should either be the SHA-1 of
|
||||
something, or truly random. */
|
||||
tr_cryptoRandBuf( myid, 20 );
|
||||
have_id = TRUE;
|
||||
}
|
||||
|
||||
rc = dht_init(dht_socket, myid);
|
||||
if(rc < 0)
|
||||
goto fail;
|
||||
|
||||
session = ss;
|
||||
|
||||
if(nodes) {
|
||||
struct bootstrap_closure * cl = tr_new( struct bootstrap_closure, 1 );
|
||||
if( !cl )
|
||||
tr_free( nodes );
|
||||
else {
|
||||
cl->session = session;
|
||||
cl->nodes = nodes;
|
||||
cl->len = len;
|
||||
tr_threadNew( dht_bootstrap, cl );
|
||||
}
|
||||
}
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = tr_cryptoWeakRandInt( 1000000 );
|
||||
event_set( &dht_event, dht_socket, EV_READ, event_callback, NULL );
|
||||
event_add( &dht_event, &tv );
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
{
|
||||
const int save = errno;
|
||||
close(dht_socket);
|
||||
dht_socket = -1;
|
||||
session = NULL;
|
||||
errno = save;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
tr_dhtUninit(tr_session *ss)
|
||||
{
|
||||
if(session != ss)
|
||||
return;
|
||||
|
||||
event_del(&dht_event);
|
||||
|
||||
/* Since we only save known good nodes, avoid erasing older data if we
|
||||
don't know enough nodes. */
|
||||
if(tr_dhtStatus(ss) >= TR_DHT_FIREWALLED) {
|
||||
tr_benc benc;
|
||||
struct sockaddr_in sins[300];
|
||||
char compact[300 * 6];
|
||||
char *dat_file;
|
||||
int n, i, j;
|
||||
n = dht_get_nodes(sins, 300);
|
||||
j = 0;
|
||||
for(i = 0; i < n; i++) {
|
||||
memcpy(compact + j, &sins[i].sin_addr, 4);
|
||||
memcpy(compact + j + 4, &sins[i].sin_port, 2);
|
||||
j += 6;
|
||||
}
|
||||
tr_bencInitDict(&benc, 2);
|
||||
tr_bencDictAddRaw(&benc, "id", myid, 20);
|
||||
tr_bencDictAddRaw(&benc, "nodes", compact, j);
|
||||
dat_file = tr_buildPath( ss->configDir, "dht.dat", NULL );
|
||||
tr_bencSaveFile( dat_file, &benc );
|
||||
tr_free( dat_file );
|
||||
}
|
||||
|
||||
dht_uninit(dht_socket, 0);
|
||||
|
||||
session = NULL;
|
||||
}
|
||||
|
||||
tr_bool
|
||||
tr_dhtEnabled(tr_session *ss)
|
||||
{
|
||||
return (ss && session == ss);
|
||||
}
|
||||
|
||||
static void
|
||||
getstatus(void *closure)
|
||||
{
|
||||
sig_atomic_t *ret = (sig_atomic_t*)closure;
|
||||
int good, dubious, incoming;
|
||||
|
||||
dht_nodes(&good, &dubious, NULL, &incoming);
|
||||
if(good < 4 || good + dubious <= 8)
|
||||
*ret = TR_DHT_BROKEN;
|
||||
else if(good < 40)
|
||||
*ret = TR_DHT_POOR;
|
||||
else if(incoming < 8)
|
||||
*ret = TR_DHT_FIREWALLED;
|
||||
else
|
||||
*ret = TR_DHT_GOOD;
|
||||
}
|
||||
|
||||
int
|
||||
tr_dhtStatus(tr_session *ss)
|
||||
{
|
||||
sig_atomic_t ret = -1;
|
||||
|
||||
if(!tr_dhtEnabled(ss))
|
||||
return TR_DHT_STOPPED;
|
||||
|
||||
tr_runInEventThread(ss, getstatus, &ret);
|
||||
while( ret < 0 )
|
||||
tr_wait( 1 /* msec */ );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
tr_port
|
||||
tr_dhtPort(tr_session *ss)
|
||||
{
|
||||
return tr_dhtEnabled( ss ) ? dht_port : 0;
|
||||
}
|
||||
|
||||
int
|
||||
tr_dhtAddNode(tr_session *ss, tr_address *address, tr_port port, tr_bool bootstrap)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
|
||||
if(!tr_dhtEnabled(ss))
|
||||
return 0;
|
||||
|
||||
if(address->type != TR_AF_INET)
|
||||
return 0;
|
||||
|
||||
/* Since we don't want to abuse our bootstrap nodes, we don't ping them
|
||||
if the DHT is in a good state. */
|
||||
if(bootstrap) {
|
||||
if(tr_dhtStatus(ss) >= TR_DHT_FIREWALLED)
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
char buf[50];
|
||||
inet_ntop(AF_INET, &address->addr.addr4, buf, 50);
|
||||
}
|
||||
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
memcpy(&sin.sin_addr, &address->addr.addr4, 4);
|
||||
sin.sin_port = htons(port);
|
||||
dht_ping_node(dht_socket, &sin);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
callback(void *ignore, int event,
|
||||
unsigned char *info_hash, void *data, size_t data_len)
|
||||
{
|
||||
(void)ignore; /* sigh */
|
||||
if(event == DHT_EVENT_VALUES) {
|
||||
tr_torrent *tor;
|
||||
tr_pex *pex;
|
||||
size_t i, n;
|
||||
pex = tr_peerMgrCompactToPex(data, data_len, NULL, 0, &n);
|
||||
tr_globalLock(session);
|
||||
tor = tr_torrentFindFromHash(session, info_hash);
|
||||
if(tor && tr_torrentAllowsDHT(tor)) {
|
||||
for(i = 0; i < n; i++)
|
||||
tr_peerMgrAddPex(tor, TR_PEER_FROM_DHT, pex + i);
|
||||
}
|
||||
tr_globalUnlock(session);
|
||||
tr_free(pex);
|
||||
} else if(event == DHT_EVENT_SEARCH_DONE) {
|
||||
tr_torrent *tor;
|
||||
tor = tr_torrentFindFromHash(session, info_hash);
|
||||
if(tor)
|
||||
tor->dhtAnnounceInProgress = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
tr_dhtAnnounce(tr_torrent *tor, tr_bool announce)
|
||||
{
|
||||
if(!tr_torrentAllowsDHT(tor))
|
||||
return -1;
|
||||
|
||||
if(tr_dhtStatus(tor->session) < TR_DHT_POOR)
|
||||
return 0;
|
||||
|
||||
dht_search(dht_socket, tor->info.hash,
|
||||
announce ? tr_sessionGetPeerPort(session) : 0,
|
||||
callback, NULL);
|
||||
|
||||
tor->dhtAnnounceInProgress = 1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
event_callback(int s, short type, void *ignore)
|
||||
{
|
||||
int rc;
|
||||
time_t tosleep;
|
||||
struct timeval now, tv;
|
||||
(void)ignore;
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
|
||||
rc = dht_periodic(s, type == EV_READ, &tosleep, callback, NULL);
|
||||
if(rc < 0) {
|
||||
if(errno == EINTR) {
|
||||
tosleep = 0;
|
||||
} else {
|
||||
perror("dht_periodic");
|
||||
if(rc == EINVAL || rc == EFAULT)
|
||||
abort();
|
||||
tosleep = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Being slightly late is fine, and has the added benefit of adding
|
||||
some jitter. */
|
||||
tv.tv_sec = tosleep;
|
||||
tv.tv_usec = tr_cryptoWeakRandInt( 1000000 );
|
||||
event_add(&dht_event, &tv);
|
||||
}
|
||||
|
||||
void
|
||||
dht_hash(void *hash_return, int hash_size,
|
||||
const void *v1, int len1,
|
||||
const void *v2, int len2,
|
||||
const void *v3, int len3)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
tr_sha1(sha1, v1, len1, v2, len2, v3, len3, NULL);
|
||||
if(hash_size > 20) {
|
||||
memset((char*)hash_return + 20, 0, hash_size - 20);
|
||||
}
|
||||
memcpy(hash_return, sha1, hash_size > 20 ? 20 : hash_size);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Copyright (c) 2009 by Juliusz Chroboczek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#define TR_DHT_STOPPED 0
|
||||
#define TR_DHT_BROKEN 1
|
||||
#define TR_DHT_POOR 2
|
||||
#define TR_DHT_FIREWALLED 3
|
||||
#define TR_DHT_GOOD 4
|
||||
|
||||
int tr_dhtInit(tr_session *ss);
|
||||
void tr_dhtUninit(tr_session *ss);
|
||||
tr_bool tr_dhtEnabled(tr_session *ss);
|
||||
int tr_dhtStatus(tr_session *ss);
|
||||
tr_port tr_dhtPort(tr_session *ss);
|
||||
int tr_dhtAddNode(tr_session *ss, tr_address *address, tr_port port, tr_bool bootstrap);
|
||||
int tr_dhtAnnounce(tr_torrent *tor, tr_bool announce);
|
|
@ -26,6 +26,7 @@
|
|||
#include "resume.h"
|
||||
#include "torrent.h"
|
||||
#include "tracker.h"
|
||||
#include "tr-dht.h"
|
||||
#include "trevent.h"
|
||||
#include "utils.h"
|
||||
#include "web.h"
|
||||
|
@ -977,6 +978,18 @@ trackerPulse( void * vsession )
|
|||
t->manualAnnounceAllowedAt = TR_TRACKER_BUSY;
|
||||
enqueueRequest( session, t, TR_REQ_REANNOUNCE );
|
||||
}
|
||||
|
||||
if( tor->dhtAnnounceAt <= now ) {
|
||||
int rc = 1;
|
||||
if( tr_torrentAllowsDHT(tor) )
|
||||
rc = tr_dhtAnnounce(tor, 1);
|
||||
if(rc == 0)
|
||||
/* The DHT is not ready yet. Try again soon. */
|
||||
tor->dhtAnnounceAt = now + 5 + tr_cryptoWeakRandInt( 5 );
|
||||
else
|
||||
/* We should announce at least once every 30 minutes. */
|
||||
tor->dhtAnnounceAt = now + 25 * 60 + tr_cryptoWeakRandInt( 3 * 60 );
|
||||
}
|
||||
}
|
||||
|
||||
if( th->runningCount )
|
||||
|
|
|
@ -530,6 +530,8 @@ void tr_sessionSetPexEnabled( tr_session * session,
|
|||
|
||||
tr_bool tr_sessionIsPexEnabled( const tr_session * session );
|
||||
|
||||
tr_bool tr_sessionIsDHTEnabled( const tr_session * session );
|
||||
|
||||
void tr_sessionSetLazyBitfieldEnabled( tr_session * session,
|
||||
tr_bool enabled );
|
||||
|
||||
|
@ -1343,8 +1345,9 @@ enum
|
|||
{
|
||||
TR_PEER_FROM_INCOMING = 0, /* connections made to the listening port */
|
||||
TR_PEER_FROM_TRACKER = 1, /* peers received from a tracker */
|
||||
TR_PEER_FROM_CACHE = 2, /* peers read from the peer cache */
|
||||
TR_PEER_FROM_PEX = 3, /* peers discovered via PEX */
|
||||
TR_PEER_FROM_DHT = 2, /* peers learnt from the DHT */
|
||||
TR_PEER_FROM_CACHE = 3, /* peers read from the peer cache */
|
||||
TR_PEER_FROM_PEX = 4, /* peers discovered via PEX */
|
||||
TR_PEER_FROM__MAX
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
SUBDIRS = \
|
||||
libevent \
|
||||
libnatpmp \
|
||||
miniupnp
|
||||
miniupnp \
|
||||
dht
|
||||
|
||||
EXTRA_DIST = \
|
||||
macosx-libevent-config.h
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
18 May 2009: dht-0.4
|
||||
|
||||
* Fixed the handling of tokens in announce_peer messages.
|
||||
* Implemented backtracking during search when nodes turn out to be dead.
|
||||
|
||||
17 May 2009: dht-0.3
|
||||
|
||||
* Fixed a number of incorrectly formatted messages.
|
||||
* Changed reply to find_peers to spread the load more uniformly.
|
||||
* Fixed a bug that could cause premature splitting.
|
||||
* Implemented rate limiting.
|
||||
* Changed some time constants to be less chatty.
|
||||
* When determining if a bucket is fresh enough, we now only take replies
|
||||
into account.
|
||||
* dht_get_nodes now returns nodes starting with our own bucket.
|
||||
* Tweaked the memory allocation strategy for stored peers.
|
||||
|
||||
17 May 2009: dht-0.2
|
||||
|
||||
* Fixed a crash in dht_uninit.
|
||||
* Added support for saving the list of known-good nodes.
|
||||
* Changed the interface of dht_nodes to provide the number of nodes that
|
||||
recently sent incoming requests.
|
||||
|
||||
13 May 2009: dht-0.1
|
||||
|
||||
* Initial public release.
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2009 by Juliusz Chroboczek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,4 @@
|
|||
noinst_LIBRARIES = libdht.a
|
||||
libdht_a_SOURCES = dht.c
|
||||
noinst_HEADERS = dht.h
|
||||
extra_DIST = CHANGES dht-example.c LICENCE README
|
|
@ -0,0 +1,192 @@
|
|||
The files dht.c and dht.h implement the variant of the Kademlia Distributed
|
||||
Hash Table (DHT) used in the Bittorrent network (``mainline'' variant).
|
||||
|
||||
The file dht-example.c is a stand-alone program that participates in the
|
||||
DHT. Another example is a patch against Transmission, which you might or
|
||||
might not be able to find somewhere.
|
||||
|
||||
The code is designed to work well in both event-driven and threaded code.
|
||||
The caller, which is either an event-loop or a dedicated thread, must
|
||||
periodically call the function dht_periodic. In addition, it must call
|
||||
dht_periodic whenever any data has arrived from the network.
|
||||
|
||||
All functions return -1 in case of failure, in which case errno is set, or
|
||||
a positive value in case of success.
|
||||
|
||||
Initialisation
|
||||
**************
|
||||
|
||||
* dht_init
|
||||
|
||||
This must be called before using the library. You pass it a bound IPv4
|
||||
datagram socket, and your node id, a 20-octet array that should be globally
|
||||
unique.
|
||||
|
||||
Node ids must be well distributed, so you cannot just use your Bittorrent
|
||||
id; you should either generate a truly random value (using plenty of
|
||||
entropy), or at least take the SHA-1 of something. However, it is a good
|
||||
idea to keep the id stable, so you may want to store it in stable storage
|
||||
at client shutdown.
|
||||
|
||||
* dht_uninit
|
||||
|
||||
This may be called at the end of the session. If dofree is true, it frees
|
||||
all the memory allocated for the DHT. If dofree is false, this function
|
||||
currently does nothing.
|
||||
|
||||
Bootstrapping
|
||||
*************
|
||||
|
||||
The DHT needs to be taught a small number of contacts to begin functioning.
|
||||
You can hard-wire a small number of stable nodes in your application, but
|
||||
this obviously fails to scale. You may save the list of known good nodes
|
||||
at shutdown, and restore it at restart. You may also grab nodes from
|
||||
torrent files (the nodes field), and you may exchange contacts with other
|
||||
Bittorrent peers using the PORT extension.
|
||||
|
||||
* dht_ping
|
||||
|
||||
This is the main bootstrapping primitive. You pass it an address at which
|
||||
you believe that a DHT node may be living, and a query will be sent. If
|
||||
a node replies, and if there is space in the routing table, it will be
|
||||
inserted.
|
||||
|
||||
* dht_insert_node
|
||||
|
||||
This is a softer bootstrapping method, which doesn't actually send
|
||||
a query -- it only stores the node in the routing table for later use. It
|
||||
is a good idea to use that when e.g. restoring your routing table from
|
||||
disk.
|
||||
|
||||
Note that dht_insert_node requires that you supply a node id. If the id
|
||||
turns out to be wrong, the DHT will eventually recover; still, inserting
|
||||
massive amounts of incorrect information into your routing table is
|
||||
certainly not a good idea.
|
||||
|
||||
An additionaly difficulty with dht_insert_node is that, for various
|
||||
reasons, a Kademlia routing table cannot absorb nodes faster than a certain
|
||||
rate. Dumping a large number of nodes into a table using dht_insert_node
|
||||
will probably cause most of these nodes to be discarded straight away.
|
||||
(The tolerable rate is difficult to estimate; it is probably on the order
|
||||
of one node every few seconds per node already in the table divided by 8,
|
||||
for some suitable value of 8.)
|
||||
|
||||
Doing some work
|
||||
***************
|
||||
|
||||
* dht_periodic
|
||||
|
||||
This function should be called by your main loop periodically, and also
|
||||
whenever data is available on the socket. The time after which
|
||||
dht_periodic should be called if no data is available is returned in the
|
||||
parameter tosleep. (You do not need to be particularly accurate; actually,
|
||||
it is a good idea to be late by a random value.)
|
||||
|
||||
The parameter available indicates whether any data is available on the
|
||||
socket. If it is 0, dht_periodic will not try to read data; if it is 1, it
|
||||
will.
|
||||
|
||||
Dht_periodic also takes a callback, which will be called whenever something
|
||||
interesting happens (see below).
|
||||
|
||||
* dht_search
|
||||
|
||||
This schedules a search for information about the info-hash specified in
|
||||
id. If port is not 0, it specifies the TCP port on which the current peer
|
||||
is litening; in that case, when the search is complete it will be announced
|
||||
to the network. The port is in host order, beware if you got it from
|
||||
a struct sockaddr_in.
|
||||
|
||||
In either case, data is passed to the callback function as soon as it is
|
||||
available, possibly in multiple pieces. The callback function will
|
||||
additionally be called when the search is complete.
|
||||
|
||||
Up to DHT_MAX_SEARCHES (20) searches can be in progress at a given time;
|
||||
any more, and dht_search will return -1. If you specify a new search for
|
||||
the same info hash as a search still in progress, the previous search is
|
||||
combined with the new one -- you will only receive a completion indication
|
||||
once.
|
||||
|
||||
Information queries
|
||||
*******************
|
||||
|
||||
* dht_nodes
|
||||
|
||||
This returns the number of known good, dubious and cached nodes in our
|
||||
routing table. This can be used to decide whether it's reasonable to start
|
||||
a search; a search is likely to be successful as long as we have a few good
|
||||
nodes; however, in order to avoid overloading your bootstrap nodes, you may
|
||||
want to wait until good is at least 4 and good + doubtful is at least 30 or
|
||||
so.
|
||||
|
||||
It also includes the number of nodes that recently send us an unsolicited
|
||||
request; this can be used to determine if the UDP port used for the DHT is
|
||||
firewalled.
|
||||
|
||||
If you want to display a single figure to the user, you should display good
|
||||
+ doubtful, which is the total number of nodes in your routing table. Some
|
||||
clients try to estimate the total number of nodes, but this doesn't make
|
||||
much sense -- since the result is exponential in the number of nodes in the
|
||||
routing table, small variations in the latter cause huge jumps in the
|
||||
former.
|
||||
|
||||
* dht_get_nodes
|
||||
|
||||
This retrieves the list of known good nodes, starting with the nodes in our
|
||||
own bucket. It is a good idea to save the list of known good nodes at
|
||||
shutdown, and ping them at startup.
|
||||
|
||||
* dht_dump_tables
|
||||
* dht_debug
|
||||
|
||||
These are debugging aids.
|
||||
|
||||
Functions provided by you
|
||||
*************************
|
||||
|
||||
* The callback function
|
||||
|
||||
The callback function is called with 5 arguments. Closure is simply the
|
||||
value that you passed to dht_periodic. Event is one of DHT_EVENT_VALUES,
|
||||
which indicates that we have new values, or DHT_EVENT_SEARCH_DONE, which
|
||||
indicates that a search has completed. In either case, info_hash is set to
|
||||
the info-hash of the search.
|
||||
|
||||
In the case of DHT_EVENT_VALUES, data is a list of nodes in ``compact''
|
||||
format -- 6 bytes per node, 4 for the IP address and 2 for the port. It's
|
||||
length in bytes is in data_len.
|
||||
|
||||
* dht_hash
|
||||
|
||||
This should compute a reasonably strong cryptographic hash of the passed
|
||||
values. It should map cleanly to your favourite crypto toolkit's MD5 or
|
||||
SHA-1 function.
|
||||
|
||||
Final notes
|
||||
***********
|
||||
|
||||
* NAT
|
||||
|
||||
Nothing works well across NATs, but Kademlia is somewhat less impacted than
|
||||
many other protocols. The implementation takes care to distinguish between
|
||||
unidirectional and bidirectional reachability, and NATed nodes will
|
||||
eventually fall out from other nodes' routing tables.
|
||||
|
||||
While there is no periodic pinging in this implementation, maintaining
|
||||
a full routing table requires slightly more than one packet exchange per
|
||||
minute, even in a completely idle network; this should be sufficient to
|
||||
make most full cone NATs happy.
|
||||
|
||||
* Missing functionality
|
||||
|
||||
Some of the code has had very little testing. If it breaks, you get to
|
||||
keep both pieces.
|
||||
|
||||
There is currently no good way to save and restore your routing table.
|
||||
|
||||
IPv6 support is deliberately not included: designing a double-stack
|
||||
distributed hash table raises some tricky issues, and doing it naively may
|
||||
break connectivity for everyone.
|
||||
|
||||
Juliusz Chroboczek
|
||||
<jch@pps.jussieu.fr>
|
|
@ -0,0 +1,329 @@
|
|||
/* This example code was written by Juliusz Chroboczek.
|
||||
You are free to cut'n'paste from it to your heart's content. */
|
||||
|
||||
/* For crypt */
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/time.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/signal.h>
|
||||
|
||||
#include "dht.h"
|
||||
|
||||
#define MAX_BOOTSTRAP_NODES 20
|
||||
static struct sockaddr_in bootstrap_nodes[MAX_BOOTSTRAP_NODES];
|
||||
static int num_bootstrap_nodes = 0;
|
||||
|
||||
static volatile sig_atomic_t dumping = 0, searching = 0, exiting = 0;
|
||||
|
||||
static void
|
||||
sigdump(int signo)
|
||||
{
|
||||
dumping = 1;
|
||||
}
|
||||
|
||||
static void
|
||||
sigtest(int signo)
|
||||
{
|
||||
searching = 1;
|
||||
}
|
||||
|
||||
static void
|
||||
sigexit(int signo)
|
||||
{
|
||||
exiting = 1;
|
||||
}
|
||||
|
||||
static void
|
||||
init_signals(void)
|
||||
{
|
||||
struct sigaction sa;
|
||||
sigset_t ss;
|
||||
|
||||
sigemptyset(&ss);
|
||||
sa.sa_handler = sigdump;
|
||||
sa.sa_mask = ss;
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGUSR1, &sa, NULL);
|
||||
|
||||
sigemptyset(&ss);
|
||||
sa.sa_handler = sigtest;
|
||||
sa.sa_mask = ss;
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGUSR2, &sa, NULL);
|
||||
|
||||
sigemptyset(&ss);
|
||||
sa.sa_handler = sigexit;
|
||||
sa.sa_mask = ss;
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
}
|
||||
|
||||
const unsigned char hash[20] = {
|
||||
0x54, 0x57, 0x87, 0x89, 0xdf, 0xc4, 0x23, 0xee, 0xf6, 0x03,
|
||||
0x1f, 0x81, 0x94, 0xa9, 0x3a, 0x16, 0x98, 0x8b, 0x72, 0x7b
|
||||
};
|
||||
|
||||
/* The call-back function is called by the DHT whenever something
|
||||
interesting happens. Right now, it only happens when we get a new value or
|
||||
when a search completes, but this may be extended in future versions. */
|
||||
static void
|
||||
callback(void *closure,
|
||||
int event,
|
||||
unsigned char *info_hash,
|
||||
void *data, size_t data_len)
|
||||
{
|
||||
if(event == DHT_EVENT_SEARCH_DONE)
|
||||
printf("Search done.\n");
|
||||
else if(event == DHT_EVENT_VALUES)
|
||||
printf("Received %d values.\n", (int)(data_len / 6));
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int i, rc, fd;
|
||||
int s, port;
|
||||
int have_id = 0;
|
||||
unsigned char myid[20];
|
||||
time_t tosleep = 0;
|
||||
|
||||
/* Ids need to be distributed evenly, so you cannot just use your
|
||||
bittorrent id. Either generate it randomly, or take the SHA-1 of
|
||||
something. */
|
||||
fd = open("dht-example.id", O_RDONLY);
|
||||
if(fd >= 0) {
|
||||
rc = read(fd, myid, 20);
|
||||
if(rc == 20)
|
||||
have_id = 1;
|
||||
close(fd);
|
||||
}
|
||||
|
||||
if(!have_id) {
|
||||
fd = open("/dev/urandom", O_RDONLY);
|
||||
if(fd < 0) {
|
||||
perror("open(random)");
|
||||
exit(1);
|
||||
}
|
||||
rc = read(fd, myid, 20);
|
||||
if(rc < 0) {
|
||||
perror("read(random)");
|
||||
exit(1);
|
||||
}
|
||||
have_id = 1;
|
||||
close(fd);
|
||||
|
||||
fd = open("dht-example.id", O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if(fd >= 0) {
|
||||
rc = write(fd, myid, 20);
|
||||
if(rc < 20)
|
||||
unlink("dht-example.id");
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
if(argc < 2)
|
||||
goto usage;
|
||||
|
||||
i = 1;
|
||||
|
||||
if(argc < i + 1)
|
||||
goto usage;
|
||||
|
||||
port = atoi(argv[i++]);
|
||||
if(port <= 0 || port >= 0x10000)
|
||||
goto usage;
|
||||
|
||||
while(i < argc) {
|
||||
struct addrinfo hints, *info, *infop;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
rc = getaddrinfo(argv[i], NULL, &hints, &info);
|
||||
if(rc != 0) {
|
||||
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
infop = info;
|
||||
while(infop) {
|
||||
if(infop->ai_addr->sa_family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
memcpy(&sin, infop->ai_addr, infop->ai_addrlen);
|
||||
sin.sin_port = htons(atoi(argv[i]));
|
||||
bootstrap_nodes[num_bootstrap_nodes] = sin;
|
||||
num_bootstrap_nodes++;
|
||||
}
|
||||
infop = infop->ai_next;
|
||||
}
|
||||
freeaddrinfo(info);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if(i < argc)
|
||||
goto usage;
|
||||
|
||||
/* If you set dht_debug to a stream, every action taken by the DHT will
|
||||
be logged. */
|
||||
dht_debug = stdout;
|
||||
|
||||
/* We need an IPv4 socket, bound to a stable port. Rumour has it that
|
||||
uTorrent works better when it is the same as your Bittorrent port. */
|
||||
s = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if(s < 0) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htons(port);
|
||||
rc = bind(s, (struct sockaddr*)&sin, sizeof(sin));
|
||||
if(rc < 0) {
|
||||
perror("bind");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Init the dht. This sets the socket into non-blocking mode. */
|
||||
rc = dht_init(s, myid);
|
||||
if(rc < 0) {
|
||||
perror("dht_init");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
init_signals();
|
||||
|
||||
/* For bootstrapping, we need an initial list of nodes. This could be
|
||||
hard-wired, but can also be obtained from the nodes key of a torrent
|
||||
file, or from the PORT bittorrent message.
|
||||
|
||||
Dht_ping_node is the brutal way of bootstrapping -- it actually
|
||||
sends a message to the peer. If you're going to bootstrap from
|
||||
a massive number of nodes (for example because you're restoring from
|
||||
a dump) and you already know their ids, it's better to use
|
||||
dht_insert_node. If the ids are incorrect, the DHT will recover. */
|
||||
for(i = 0; i < num_bootstrap_nodes; i++) {
|
||||
dht_ping_node(s, &bootstrap_nodes[i]);
|
||||
usleep(random() % 100000);
|
||||
}
|
||||
|
||||
while(1) {
|
||||
struct timeval tv;
|
||||
fd_set readfds;
|
||||
tv.tv_sec = tosleep;
|
||||
tv.tv_usec = random() % 1000000;
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(s, &readfds);
|
||||
rc = select(s + 1, &readfds, NULL, NULL, &tv);
|
||||
if(rc < 0) {
|
||||
if(errno != EINTR) {
|
||||
perror("select");
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
if(exiting)
|
||||
break;
|
||||
|
||||
rc = dht_periodic(s, rc > 0, &tosleep, callback, NULL);
|
||||
if(rc < 0) {
|
||||
if(errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
perror("dht_periodic");
|
||||
if(rc == EINVAL || rc == EFAULT)
|
||||
abort();
|
||||
tosleep = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* This is how you trigger a search for a torrent hash. If port
|
||||
(the third argument) is non-zero, it also performs an announce.
|
||||
Since peers expire announced data after 30 minutes, it's a good
|
||||
idea to reannounce every 28 minutes or so. */
|
||||
if(searching) {
|
||||
dht_search(s, hash, 0, callback, NULL);
|
||||
searching = 0;
|
||||
}
|
||||
|
||||
/* For debugging, or idle curiosity. */
|
||||
if(dumping) {
|
||||
dht_dump_tables(stdout);
|
||||
dumping = 0;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
struct sockaddr_in sins[500];
|
||||
int i;
|
||||
i = dht_get_nodes(sins, 500);
|
||||
printf("Found %d good nodes.\n", i);
|
||||
}
|
||||
|
||||
dht_uninit(s, 1);
|
||||
return 0;
|
||||
|
||||
usage:
|
||||
fprintf(stderr, "Foo!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* We need to provide a reasonably strong cryptographic hashing function.
|
||||
Here's how we'd do it if we had RSA's MD5 code. */
|
||||
#if 0
|
||||
void
|
||||
dht_hash(void *hash_return, int hash_size,
|
||||
const void *v1, int len1,
|
||||
const void *v2, int len2,
|
||||
const void *v3, int len3)
|
||||
{
|
||||
static MD5_CTX ctx;
|
||||
MD5Init(&ctx);
|
||||
MD5Update(&ctx, v1, len1);
|
||||
MD5Update(&ctx, v2, len2);
|
||||
MD5Update(&ctx, v3, len3);
|
||||
MD5Final(&ctx);
|
||||
if(hash_size > 16)
|
||||
memset((char*)hash_return + 16, 0, hash_size - 16);
|
||||
memcpy(hash_return, ctx.digest, hash_size > 16 ? 16 : hash_size);
|
||||
}
|
||||
#else
|
||||
/* But for this example, we might as well use something weaker. */
|
||||
void
|
||||
dht_hash(void *hash_return, int hash_size,
|
||||
const void *v1, int len1,
|
||||
const void *v2, int len2,
|
||||
const void *v3, int len3)
|
||||
{
|
||||
const char *c1 = v1, *c2 = v2, *c3 = v3;
|
||||
char key[9]; /* crypt is limited to 8 characters */
|
||||
int i;
|
||||
|
||||
memset(key, 0, 9);
|
||||
#define CRYPT_HAPPY(c) ((c % 0x60) + 0x20)
|
||||
|
||||
for(i = 0; i < 2 && i < len1; i++)
|
||||
key[i] = CRYPT_HAPPY(c1[i]);
|
||||
for(i = 0; i < 4 && i < len1; i++)
|
||||
key[2 + i] = CRYPT_HAPPY(c2[i]);
|
||||
for(i = 0; i < 2 && i < len1; i++)
|
||||
key[6 + i] = CRYPT_HAPPY(c3[i]);
|
||||
strncpy(hash_return, crypt(key, "jc"), hash_size);
|
||||
}
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright (c) 2009 by Juliusz Chroboczek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
typedef void
|
||||
dht_callback(void *closure, int event,
|
||||
unsigned char *info_hash,
|
||||
void *data, size_t data_len);
|
||||
|
||||
#define DHT_EVENT_NONE 0
|
||||
#define DHT_EVENT_VALUES 1
|
||||
#define DHT_EVENT_SEARCH_DONE 2
|
||||
|
||||
extern FILE *dht_debug;
|
||||
|
||||
int dht_init(int s, const unsigned char *id);
|
||||
int dht_insert_node(int s, const unsigned char *id, struct sockaddr_in *sin);
|
||||
int dht_ping_node(int s, struct sockaddr_in *sin);
|
||||
int dht_periodic(int s, int available, time_t *tosleep,
|
||||
dht_callback *callback, void *closure);
|
||||
int dht_search(int s, const unsigned char *id, int port,
|
||||
dht_callback *callback, void *closure);
|
||||
int dht_nodes(int *good_return, int *dubious_return, int *cached_return,
|
||||
int *incoming_return);
|
||||
void dht_dump_tables(FILE *f);
|
||||
int dht_get_nodes(struct sockaddr_in *sins, int num);
|
||||
int dht_uninit(int s, int dofree);
|
||||
|
||||
/* This must be provided by the user. */
|
||||
void dht_hash(void *hash_return, int hash_size,
|
||||
const void *v1, int len1,
|
||||
const void *v2, int len2,
|
||||
const void *v3, int len3);
|
|
@ -0,0 +1,7 @@
|
|||
if gcc -DPACKAGE_NAME=\"transmission\" -DPACKAGE_TARNAME=\"transmission\" -DPACKAGE_VERSION=\"1.61+\" -DPACKAGE_STRING=\"transmission\ 1.61+\" -DPACKAGE_BUGREPORT=\"http://trac.transmissionbt.com/newticket\" -DPACKAGE=\"transmission\" -DVERSION=\"1.61+\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1 -DTR_NIGHTLY_RELEASE=1 -DSTDC_HEADERS=1 -DTIME_WITH_SYS_TIME=1 -DHAVE_DAEMON=1 -DHAVE_DIRNAME=1 -DHAVE_BASENAME=1 -DHAVE_DAEMON=1 -DHAVE_STRCASECMP=1 -DHAVE_LOCALTIME_R=1 -DHAVE_POSIX_FALLOCATE=1 -DHAVE_MEMMEM=1 -DHAVE_PTHREAD=1 -DHAVE__TMP_DUMMY1_ZLIB_H=1 -DHAVE_ZLIB=1 -DHAVE_DECL_POSIX_FADVISE=1 -DHAVE_POSIX_FADVISE=1 -DWITH_INOTIFY=1 -DHAVE_DBUS_GLIB=1 -DHAVE_LIBINTL_H=1 -DGETTEXT_PACKAGE=\"transmission\" -DHAVE_LOCALE_H=1 -DHAVE_LC_MESSAGES=1 -DHAVE_BIND_TEXTDOMAIN_CODESET=1 -DHAVE_GETTEXT=1 -DHAVE_DCGETTEXT=1 -DENABLE_NLS=1 -I. -I. -I/mnt/home/charles/opt/easytag/include -I/mnt/home/charles/wx/include -I/mnt/home/charles/wx/include -Os -Wall -W -ggdb3 -g -O0 -std=gnu99 -ggdb3 -Wall -W -Wpointer-arith -Wformat-security -Wcast-align -Wundef -Wcast-align -Wstrict-prototypes -Wmissing-declarations -Wmissing-format-attribute -Wredundant-decls -Wnested-externs -Wunused-parameter -Wwrite-strings -Wextra -Wdeclaration-after-statement -Winit-self -MT dht.o -MD -MP -MF ".deps/dht.Tpo" -c -o dht.o dht.c; \
|
||||
then mv -f ".deps/dht.Tpo" ".deps/dht.Po"; else rm -f ".deps/dht.Tpo"; exit 1; fi
|
||||
dht.c:1321: warning: unused parameter ‘s’
|
||||
dht.c:1952: warning: unused parameter ‘port’
|
||||
rm -f libdht.a
|
||||
ar cru libdht.a dht.o
|
||||
ranlib libdht.a
|
Loading…
Reference in New Issue