]> granicus.if.org Git - transmission/commitdiff
(trunk) #7: DHT support. This is a work in progress... no gui/rpc support yet
authorCharles Kerr <charles@transmissionbt.com>
Tue, 19 May 2009 18:38:26 +0000 (18:38 +0000)
committerCharles Kerr <charles@transmissionbt.com>
Tue, 19 May 2009 18:38:26 +0000 (18:38 +0000)
29 files changed:
AUTHORS
cli/Makefile.am
configure.ac
daemon/Makefile.am
gtk/Makefile.am
gtk/details.c
libtransmission/Makefile.am
libtransmission/handshake.c
libtransmission/peer-io.c
libtransmission/peer-io.h
libtransmission/peer-mgr.c
libtransmission/peer-msgs.c
libtransmission/session.c
libtransmission/session.h
libtransmission/torrent.c
libtransmission/torrent.h
libtransmission/tr-dht.c [new file with mode: 0644]
libtransmission/tr-dht.h [new file with mode: 0644]
libtransmission/tracker.c
libtransmission/transmission.h
third-party/Makefile.am
third-party/dht/CHANGES [new file with mode: 0644]
third-party/dht/LICENCE [new file with mode: 0644]
third-party/dht/Makefile.am [new file with mode: 0644]
third-party/dht/README [new file with mode: 0644]
third-party/dht/dht-example.c [new file with mode: 0644]
third-party/dht/dht.c [new file with mode: 0644]
third-party/dht/dht.h [new file with mode: 0644]
third-party/dht/makelog [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
index aef0c50afacc0b7db046fedbc2c4cb1beaaa61ec..3f65fe54cd432d31a721c834f512a79394347984 100644 (file)
--- a/AUTHORS
+++ b/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)
index 3244936f690c036d711ddaf885701afd0d7cd61f..ddc398b5d2d827da01f92cb74ef10aa8d9a6d4a0 100644 (file)
@@ -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) \
index 6c5937ba26bc8133189db5bf1d3c10eeb324ef1c..ff342ca061b54df097de2fa4607f59d589571ade 100644 (file)
@@ -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
index ea22fac7c170ec4ceb36c4e41785d1f22bf6a444..caa8a7241ba684bb6e6685db6fdf8edf5837c351 100644 (file)
@@ -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) \
index f40a2c37256f124db85868b5e10980c488e033d4..51a1a29fa403bec7b295371e570e5f3006435cdb 100644 (file)
@@ -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) \
index 0a7911d8ba445ac34e076eae29fc1a325a3ea736..b5e35d73313f598c1fc909759c461d1a6608fbd2 100644 (file)
@@ -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 )
index 4f869d3549e30f4b8b432b08d2d5bd888eda42ac..fc43e3a2acb1311b1292dc1af8afd0d78baff3ed 100644 (file)
@@ -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) \
index 6684b9bbde64b53aa3346857d63642c68ef544ab..90df9f3c023f61def0a21d5357404d864231019c 100644 (file)
@@ -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;
 }
 
index 5e4b73b26a6c3b9c0aa4e9586944d46c7d270001..992b8ca1f7203286332ad3906b2ca7b5d0bc3ecf 100644 (file)
@@ -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;
+}
+
 /**
 ***
 **/
index adb9afcb670a57148d1a07cf54455e0bb3157afd..b0a34d3526ac32ec5a616f56da898c99d8ff1684 100644 (file)
@@ -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;
+}
+
 /**
 ***
 **/
index c5adfb94b5920c347a1c09b2b62e39980a784e16..314da67e79e5be93d01e42d6925a370029aef1e7 100644 (file)
@@ -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;
 
index 2b903121a5dd236cc6573eee1126a8c367c95f1e..7fa22453b8a86802a81727d441c99e28fc9eb22c 100644 (file)
@@ -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 );
index a8a09e17637b44990fa5e02e613e25c37b9e5de0..174bd4bb4c75349729d462e852d1542b17ce489d 100644 (file)
@@ -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;
+}
+
 /***
 ****
 ***/
index 1c885d9d4456c0c129ad434bec08f53cab73e588..8751e0b618cbf1dd2809c7aa00358c444f858e2a 100644 (file)
@@ -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;
index ab871f19ef6559afcf2643a156c7ef4ecbc67655..a48b184d7966d59f97899ea3e09465b0aa8747b5 100644 (file)
@@ -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 );
index 2f86a879eaf3bd721e26b1cb282e0778fb4c7c00..3c32caad331cdda8f7bbab75e89a10e429ff122a 100644 (file)
@@ -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 );
diff --git a/libtransmission/tr-dht.c b/libtransmission/tr-dht.c
new file mode 100644 (file)
index 0000000..e41e19f
--- /dev/null
@@ -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);
+}
diff --git a/libtransmission/tr-dht.h b/libtransmission/tr-dht.h
new file mode 100644 (file)
index 0000000..9bf6d84
--- /dev/null
@@ -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);
index fadb91e2dcaf502800889d58a9cdbc44f579c25b..18dd76379438cb3b0d9a5415673b01033f58fe0e 100644 (file)
@@ -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 )
index 51bd0d15d6c9043cc5300d31a7a1b41ed93bd551..0cb6bf27e2e800ed906c5f1005c4273cc687f3ef 100644 (file)
@@ -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
 };
 
index 4d9e661c39db2f7a98bba5e0a16b5fe939821cb3..8ed3f226842331c0c2520800969effd40fa0eba9 100644 (file)
@@ -1,7 +1,8 @@
 SUBDIRS = \
     libevent \
     libnatpmp \
-    miniupnp
+    miniupnp \
+    dht
 
 EXTRA_DIST = \
     macosx-libevent-config.h
diff --git a/third-party/dht/CHANGES b/third-party/dht/CHANGES
new file mode 100644 (file)
index 0000000..d915a26
--- /dev/null
@@ -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.
diff --git a/third-party/dht/LICENCE b/third-party/dht/LICENCE
new file mode 100644 (file)
index 0000000..e5b0995
--- /dev/null
@@ -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.
diff --git a/third-party/dht/Makefile.am b/third-party/dht/Makefile.am
new file mode 100644 (file)
index 0000000..0adadf5
--- /dev/null
@@ -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
diff --git a/third-party/dht/README b/third-party/dht/README
new file mode 100644 (file)
index 0000000..96b33ea
--- /dev/null
@@ -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>
diff --git a/third-party/dht/dht-example.c b/third-party/dht/dht-example.c
new file mode 100644 (file)
index 0000000..086585e
--- /dev/null
@@ -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
diff --git a/third-party/dht/dht.c b/third-party/dht/dht.c
new file mode 100644 (file)
index 0000000..9b0a6c3
--- /dev/null
@@ -0,0 +1,2183 @@
+/*
+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.
+*/
+
+/* Please, please, please.
+
+   You are welcome to integrate this code in your favourite Bittorrent
+   client.  Please remember, however, that it is meant to be usable by
+   others, including myself.  This means no C++, no relicensing, and no
+   gratuitious changes to the coding style.  And please send back any
+   improvements to the author. */
+
+/* For memmem. */
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "dht.h"
+
+#ifndef MSG_CONFIRM
+#define MSG_CONFIRM 0
+#endif
+
+/* We set sin_family to 0 to mark unused slots. */
+#if AF_INET == 0
+#error You lose
+#endif
+
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+/* nothing */
+#elif defined(__GNUC__)
+#define inline __inline
+#if  (__GNUC__ >= 3)
+#define restrict __restrict
+#else
+#define restrict /**/
+#endif
+#else
+#define inline /**/
+#define restrict /**/
+#endif
+
+#define MAX(x, y) ((x) >= (y) ? (x) : (y))
+#define MIN(x, y) ((x) <= (y) ? (x) : (y))
+
+struct node {
+    unsigned char id[20];
+    struct sockaddr_in sin;
+    time_t time;                /* time of last message received */
+    time_t reply_time;          /* time of last correct reply received */
+    time_t pinged_time;         /* time of last request */
+    int pinged;                 /* how many requests we sent since last reply */
+    struct node *next;
+};
+
+struct bucket {
+    unsigned char first[20];
+    int count;                  /* number of nodes */
+    int time;                   /* time of last reply in this bucket */
+    struct node *nodes;
+    struct sockaddr_in cached;  /* the address of a likely candidate */
+    struct bucket *next;
+};
+
+struct search_node {
+    unsigned char id[20];
+    struct sockaddr_in sin;
+    time_t request_time;        /* the time of the last unanswered request */
+    time_t reply_time;          /* the time of the last reply */
+    int pinged;
+    unsigned char token[40];
+    int token_len;
+    int replied;                /* whether we have received a reply */
+    int acked;                  /* whether they acked our announcement */
+};
+
+/* When performing a search, we search for up to SEARCH_NODES closest nodes
+   to the destinatin, and use the additional ones to backtrack if any of
+   the target 8 turn out to be dead. */
+#define SEARCH_NODES 14
+
+struct search {
+    unsigned short tid;
+    time_t step_time;           /* the time of the last search_step */
+    unsigned char id[20];
+    unsigned short port;        /* 0 for pure searches */
+    int done;
+    struct search_node nodes[SEARCH_NODES];
+    int numnodes;
+};
+
+struct peer {
+    time_t time;
+    unsigned char ip[4];
+    unsigned short port;
+};
+
+/* The maximum number of peers we store for a given hash. */
+#ifndef DHT_MAX_PEERS
+#define DHT_MAX_PEERS 2048
+#endif
+
+struct storage {
+    unsigned char id[20];
+    int numpeers;
+    int maxpeers;
+    struct peer *peers;
+    struct storage *next;
+};
+
+static int send_ping(int s, struct sockaddr *sa, int salen,
+                     const unsigned char *tid, int tid_len);
+static int send_pong(int s, struct sockaddr *sa, int salen,
+                     const unsigned char *tid, int tid_len);
+static int send_find_node(int s, struct sockaddr *sa, int salen,
+                          const unsigned char *tid, int tid_len,
+                          const unsigned char *target, int confirm);
+static int send_found_nodes(int s, struct sockaddr *sa, int salen,
+                            const unsigned char *tid, int tid_len,
+                            const unsigned char *nodes, int nodes_len,
+                            const unsigned char *token, int token_len);
+static int send_bucket_nodes(int s, struct sockaddr *sa, int salen,
+                             const unsigned char *tid, int tid_len,
+                             struct bucket *b,
+                             const unsigned char *token, int token_len);
+static int send_get_peers(int s, struct sockaddr *sa, int salen,
+                          unsigned char *tid, int tid_len,
+                          unsigned char *infohash, unsigned short port,
+                          int confirm);
+static int send_announce_peer(int s, struct sockaddr *sa, int salen,
+                              unsigned char *tid, int tid_len,
+                              unsigned char *infohas, unsigned short port,
+                              unsigned char *token, int token_len, int confirm);
+int send_peers_found(int s, struct sockaddr *sa, int salen,
+                     unsigned char *tid, int tid_len,
+                     struct peer *peers1, int numpeers1,
+                     struct peer *peers2, int numpeers2,
+                     unsigned char *token, int token_len);
+int send_peer_announced(int s, struct sockaddr *sa, int salen,
+                        unsigned char *tid, int tid_len);
+
+#define REPLY 0
+#define PING 1
+#define FIND_NODE 2
+#define GET_PEERS 3
+#define ANNOUNCE_PEER 4
+static int parse_message(const unsigned char *buf, int buflen,
+                         unsigned char *tid_return, int *tid_len,
+                         unsigned char *id_return,
+                         unsigned char *info_hash_return,
+                         unsigned char *target_return,
+                         unsigned short *port_return,
+                         unsigned char *token_return, int *token_len,
+                         unsigned char *nodes_return, int *nodes_len,
+                         const unsigned char *values_return, int *values_len);
+
+static const unsigned char zeroes[20] = {0};
+static const unsigned char ones[20] = {
+    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+    0xFF, 0xFF, 0xFF, 0xFF
+};
+static time_t search_time;
+static time_t confirm_nodes_time;
+static time_t rotate_secrets_time;
+
+static unsigned char myid[20];
+static unsigned char secret[8];
+static unsigned char oldsecret[8];
+
+static struct bucket *buckets = NULL;
+static struct storage *storage;
+
+/* The maximum number of concurrent searches. */
+#ifndef DHT_MAX_SEARCHES
+#define DHT_MAX_SEARCHES 20
+#endif
+
+static struct search searches[DHT_MAX_SEARCHES];
+static int numsearches;
+static unsigned short search_id;
+
+/* The maximum number of nodes that we snub.  There is probably little
+   reason to increase this value. */
+#ifndef DHT_MAX_BLACKLISTED
+#define DHT_MAX_BLACKLISTED 10
+#endif
+static struct sockaddr_in blacklist[DHT_MAX_BLACKLISTED];
+int next_blacklisted;
+
+static struct timeval now;
+static time_t mybucket_grow_time;
+static time_t expire_stuff_time;
+
+#define MAX_LEAKY_BUCKET_TOKENS 40
+static time_t leaky_bucket_time;
+static int leaky_bucket_tokens;
+
+FILE *dht_debug = NULL;
+
+#ifdef __GNUC__
+    __attribute__ ((format (printf, 1, 2)))
+#endif
+static void
+debugf(const char *format, ...)
+{
+    va_list args;
+    va_start(args, format);
+    if(dht_debug)
+        vfprintf(dht_debug, format, args);
+    va_end(args);
+    fflush(dht_debug);
+}
+
+static void
+debug_printable(const unsigned char *buf, int buflen)
+{
+    int i;
+    if(dht_debug) {
+        for(i = 0; i < buflen; i++)
+            putc(buf[i] >= 32 && buf[i] <= 126 ? buf[i] : '.', dht_debug);
+    }
+}
+
+static void
+print_hex(FILE *f, const unsigned char *buf, int buflen)
+{
+    int i;
+    for(i = 0; i < buflen; i++)
+        fprintf(f, "%02x", buf[i]);
+}
+
+/* Forget about the ``XOR-metric''.  An id is just a path from the
+   root of the tree, so bits are numbered from the start. */
+
+static inline int
+id_cmp(const unsigned char *restrict id1, const unsigned char *restrict id2)
+{
+    /* Memcmp is guaranteed to perform an unsigned comparison. */
+    return memcmp(id1, id2, 20);
+}
+
+/* Find the lowest 1 bit in an id. */
+static int
+lowbit(const unsigned char *id)
+{
+    int i, j;
+    for(i = 19; i >= 0; i--)
+        if(id[i] != 0)
+            break;
+
+    if(i < 0)
+        return -1;
+
+    for(j = 7; j >= 0; j--)
+        if((id[i] & (0x80 >> j)) != 0)
+            break;
+
+    return 8 * i + j;
+}
+
+/* Find how many bits two ids have in common. */
+static int
+common_bits(const unsigned char *id1, const unsigned char *id2)
+{
+    int i, j;
+    unsigned char xor;
+    for(i = 0; i < 20; i++) {
+        if(id1[i] != id2[i])
+            break;
+    }
+
+    if(i == 20)
+        return 160;
+
+    xor = id1[i] ^ id2[i];
+
+    j = 0;
+    while((xor & 0x80) == 0) {
+        xor <<= 1;
+        j++;
+    }
+
+    return 8 * i + j;
+}
+
+/* We keep buckets in a sorted linked list.  A bucket b ranges from
+   b->first inclusive up to b->next->first exclusive. */
+static int
+in_bucket(const unsigned char *id, struct bucket *b)
+{
+    return id_cmp(b->first, id) <= 0 &&
+        (b->next == NULL || id_cmp(id, b->next->first) < 0);
+}
+
+static struct bucket *
+find_bucket(unsigned const char *id)
+{
+    struct bucket *b = buckets;
+
+    while(1) {
+        if(b->next == NULL)
+            return b;
+        if(id_cmp(id, b->next->first) < 0)
+            return b;
+        b = b->next;
+    }
+}
+
+static struct bucket *
+previous_bucket(struct bucket *b)
+{
+    struct bucket *p = buckets;
+
+    if(b == p)
+        return NULL;
+
+    while(1) {
+        if(p->next == NULL)
+            return NULL;
+        if(p->next == b)
+            return p;
+        p = p->next;
+    }
+}
+
+/* Every bucket contains an unordered list of nodes. */
+static struct node *
+find_node(const unsigned char *id)
+{
+    struct bucket *b = find_bucket(id);
+    struct node *n;
+
+    if(b == NULL)
+        return NULL;
+    n = b->nodes;
+    while(n) {
+        if(id_cmp(n->id, id) == 0)
+            return n;
+        n = n->next;
+    }
+    return NULL;
+}
+
+/* Return a random node in a bucket. */
+static struct node *
+random_node(struct bucket *b)
+{
+    struct node *n;
+    int nn;
+
+    if(b->count == 0)
+        return NULL;
+
+    nn = random() % b->count;
+    n = b->nodes;
+    while(nn > 0 && n) {
+        n = n->next;
+        nn--;
+    }
+    return n;
+}
+
+/* Return the middle id of a bucket. */
+static int
+bucket_middle(struct bucket *b, unsigned char *id_return)
+{
+    int bit1 = lowbit(b->first);
+    int bit2 = b->next ? lowbit(b->next->first) : -1;
+    int bit = MAX(bit1, bit2) + 1;
+
+    if(bit >= 160)
+        return -1;
+
+    memcpy(id_return, b->first, 20);
+    id_return[bit / 8] |= (0x80 >> (bit % 8));
+    return 1;
+}
+
+/* Return a random id within a bucket. */
+static int
+bucket_random(struct bucket *b, unsigned char *id_return)
+{
+    int bit1 = lowbit(b->first);
+    int bit2 = b->next ? lowbit(b->next->first) : -1;
+    int bit = MAX(bit1, bit2) + 1;
+    int i;
+
+    if(bit >= 160) {
+        memcpy(id_return, b->first, 20);
+        return 1;
+    }
+
+    memcpy(id_return, b->first, bit / 8);
+    id_return[bit / 8] = b->first[bit / 8] & (0xFF00 >> (bit % 8));
+    id_return[bit / 8] |= random() & 0xFF >> (bit % 8);
+    for(i = bit / 8 + 1; i < 20; i++)
+        id_return[i] = random() & 0xFF;
+    return 1;
+}    
+
+/* Insert a new node into a bucket. */
+static struct node *
+insert_node(struct node *node)
+{
+    struct bucket *b = find_bucket(node->id);
+
+    node->next = b->nodes;
+    b->nodes = node;
+    b->count++;
+    return node;
+}
+
+/* This is our definition of a known-good node. */
+static int
+node_good(struct node *node)
+{
+    return
+        node->pinged <= 2 &&
+        node->reply_time >= now.tv_sec - 7200 &&
+        node->time >= now.tv_sec - 900;
+}
+
+/* Our transaction-ids are 4-bytes long, with the first two bytes identi-
+   fying the kind of request, and the remaining two a sequence number in
+   host order. */
+
+static void
+make_tid(unsigned char *tid_return, const char *prefix, unsigned short seqno)
+{
+    tid_return[0] = prefix[0] & 0xFF;
+    tid_return[1] = prefix[1] & 0xFF;
+    memcpy(tid_return + 2, &seqno, 2);
+}
+
+static int
+tid_match(const unsigned char *tid, const char *prefix,
+          unsigned short *seqno_return)
+{
+    if(tid[0] == (prefix[0] & 0xFF) && tid[1] == (prefix[1] & 0xFF)) {
+        if(seqno_return)
+            memcpy(seqno_return, tid + 2, 2);
+        return 1;
+    } else
+        return 0;
+}
+
+/* Every bucket caches the address of a likely node.  Ping it. */
+static int
+send_cached_ping(int s, struct bucket *b)
+{
+    int rc;
+    /* We set family to 0 when there's no cached node. */
+    if(b->cached.sin_family == AF_INET) {
+        unsigned char tid[4];
+        debugf("Sending ping to cached node.\n");
+        make_tid(tid, "pn", 0);
+        rc = send_ping(s, (struct sockaddr*)&b->cached,
+                       sizeof(struct sockaddr_in),
+                       tid, 4);
+        b->cached.sin_family = 0;
+        return rc;
+    }
+    return 0;
+}
+
+/* Split a bucket into two equal parts. */
+static struct bucket *
+split_bucket(int s, struct bucket *b)
+{
+    struct bucket *new;
+    struct node *nodes;
+    int rc;
+    unsigned char new_id[20];
+
+    rc = bucket_middle(b, new_id);
+    if(rc < 0)
+        return NULL;
+
+    new = calloc(1, sizeof(struct bucket));
+    if(new == NULL)
+        return NULL;
+
+    send_cached_ping(s, b);
+
+    memcpy(new->first, new_id, 20);
+    new->time = b->time;
+
+    nodes = b->nodes;
+    b->nodes = NULL;
+    b->count = 0;
+    new->next = b->next;
+    b->next = new;
+    while(nodes) {
+        struct node *n;
+        n = nodes;
+        nodes = nodes->next;
+        insert_node(n);
+    }
+    return b;
+}
+
+/* Called whenever we send a request to a node. */
+static void
+pinged(int s, struct node *n, struct bucket *b)
+{
+    n->pinged++;
+    n->pinged_time = now.tv_sec;
+    if(n->pinged >= 3)
+        send_cached_ping(s, b ? b : find_bucket(n->id));
+}
+
+/* We just learnt about a node, not necessarily a new one.  Confirm is 1 if
+   the node sent a message, 2 if it sent us a reply. */
+static struct node *
+new_node(int s, const unsigned char *id, struct sockaddr_in *sin,
+         int confirm)
+{
+    struct bucket *b = find_bucket(id);
+    struct node *n;
+    int mybucket = in_bucket(myid, b);
+
+    if(id_cmp(id, myid) == 0)
+        return NULL;
+
+    if(confirm == 2)
+        b->time = now.tv_sec;
+
+    n = b->nodes;
+    while(n) {
+        if(id_cmp(n->id, id) == 0) {
+            if(confirm || n->time < now.tv_sec - 15 * 60) {
+                /* Known node.  Update stuff. */
+                n->sin = *sin;
+                if(confirm)
+                    n->time = now.tv_sec;
+                if(confirm >= 2) {
+                    n->reply_time = now.tv_sec;
+                    n->pinged = 0;
+                    n->pinged_time = 0;
+                }
+            }
+            return n;
+        }
+        n = n->next;
+    }
+
+    /* New node.  First, try to get rid of a known-bad node. */
+    n = b->nodes;
+    while(n) {
+        if(n->pinged >= 3) {
+            memcpy(n->id, id, 20);
+            n->sin = *sin;
+            n->time = confirm ? now.tv_sec : 0;
+            n->reply_time = confirm >= 2 ? now.tv_sec : 0;
+            n->pinged_time = 0;
+            n->pinged = 0;
+            if(mybucket)
+                mybucket_grow_time = now.tv_sec;
+            return n;
+        }
+        n = n->next;
+    }
+
+    if(b->count >= 8) {
+        /* Bucket full.  Ping a dubious node */
+        int dubious = 0;
+        n = b->nodes;
+        while(n) {
+            /* Pick the first dubious node that we haven't pinged in the
+               last 15 seconds.  This gives nodes the time to reply, but
+               tends to concentrate on the same nodes. */
+            if(!node_good(n)) {
+                dubious = 1;
+                if(n->pinged_time < now.tv_sec - 15) {
+                    unsigned char tid[4];
+                    debugf("Sending ping to dubious node.\n");
+                    make_tid(tid, "pn", 0);
+                    send_ping(s,
+                              (struct sockaddr*)&n->sin,
+                              sizeof(struct sockaddr_in),
+                              tid, 4);
+                    n->pinged++;
+                    n->pinged_time = now.tv_sec;
+                    break;
+                }
+            }
+            n = n->next;
+        }
+        
+        if(!dubious && mybucket) {
+            debugf("Splitting.\n");
+            b = split_bucket(s, b);
+            mybucket_grow_time = now.tv_sec;
+            return new_node(s, id, sin, confirm);
+        }
+
+        /* No space for this node.  Cache it away for later. */
+        if(confirm || b->cached.sin_family == 0)
+            b->cached = *sin;
+
+        return NULL;
+    }
+
+    /* Create a new node. */
+    n = calloc(1, sizeof(struct node));
+    if(n == NULL)
+        return NULL;
+    memcpy(n->id, id, 20);
+    n->sin = *sin;
+    n->time = confirm ? now.tv_sec : 0;
+    n->reply_time = confirm >= 2 ? now.tv_sec : 0;
+    n->next = b->nodes;
+    b->nodes = n;
+    b->count++;
+    if(mybucket)
+        mybucket_grow_time = now.tv_sec;
+    return n;
+}
+
+/* Called periodically to purge known-bad nodes.  Note that we're very
+   conservative here: broken nodes in the table don't do much harm, we'll
+   recover as soon as we find better ones. */
+static int
+expire_buckets(int s)
+{
+    struct bucket *b = buckets;
+
+    while(b) {
+        struct node *n, *p;
+        int changed = 0;
+
+        while(b->nodes && b->nodes->pinged >= 4) {
+            n = b->nodes;
+            b->nodes = n->next;
+            b->count--;
+            changed = 1;
+            free(n);
+        }
+
+        p = b->nodes;
+        while(p) {
+            while(p->next && p->next->pinged >= 4) {
+                n = p->next;
+                p->next = n->next;
+                b->count--;
+                changed = 1;
+                free(n);
+            }
+            p = p->next;
+        }
+
+        if(changed)
+            send_cached_ping(s, b);
+
+        b = b->next;
+    }
+    expire_stuff_time = now.tv_sec + 120 + random() % 240;
+    return 1;
+}
+
+/* While a search is in progress, we don't necessarily keep the nodes being
+   walked in the main bucket table.  A search in progress is identified by
+   a unique transaction id, a short (and hence small enough to fit in the
+   transaction id of the protocol packets). */
+
+static struct search *
+find_search(unsigned short tid)
+{
+    int i;
+    for(i = 0; i < numsearches; i++) {
+        if(searches[i].tid == tid)
+            return &searches[i];
+    }
+    return NULL;
+}
+
+/* A search contains a list of nodes, sorted by decreasing distance to the
+   target.  We just got a new candidate, insert it at the right spot or
+   discard it. */
+
+static int
+insert_search_node(unsigned char *id, struct sockaddr_in *sin,
+                   struct search *sr, int replied,
+                   unsigned char *token, int token_len)
+{
+    int bits = common_bits(id, sr->id);
+    struct search_node *n;
+    int i, j;
+
+    for(i = 0; i < sr->numnodes; i++) {
+        if(id_cmp(id, sr->nodes[i].id) == 0) {
+            n = &sr->nodes[i];
+            goto found;
+        }
+        if(common_bits(sr->id, sr->nodes[i].id) < bits)
+            break;
+    }
+
+    if(i == SEARCH_NODES)
+        return 0;
+
+    if(sr->numnodes < SEARCH_NODES)
+        sr->numnodes++;
+
+    for(j = sr->numnodes - 1; j > i; j--) {
+        sr->nodes[j] = sr->nodes[j - 1];
+    }
+
+    n = &sr->nodes[i];
+
+    memset(n, 0, sizeof(struct search_node));
+    memcpy(n->id, id, 20);
+
+found:
+    n->sin = *sin;
+
+    if(replied) {
+        n->replied = 1;
+        n->reply_time = now.tv_sec;
+        n->request_time = 0;
+        n->pinged = 0;
+    }
+    if(token) {
+        if(token_len >= 40) {
+            debugf("Eek!  Overlong token.\n");
+        } else {
+            memcpy(n->token, token, token_len);
+            n->token_len = token_len;
+        }
+    }
+
+    return 1;
+}
+
+static void
+flush_search_node(struct search_node *n, struct search *sr)
+{
+    int i = n - sr->nodes, j;
+    for(j = i; j < sr->numnodes - 1; j++)
+        sr->nodes[j] = sr->nodes[j + 1];
+    sr->numnodes--;
+}
+
+/* This must always return 0 or 1, never -1, not even on failure (see below). */
+static int
+search_send_get_peers(int s, struct search *sr, struct search_node *n)
+{
+    struct node *node;
+    unsigned char tid[4];
+
+    if(n == NULL) {
+        int i;
+        for(i = 0; i < sr->numnodes; i++) {
+            if(sr->nodes[i].pinged < 3 && !sr->nodes[i].replied &&
+               sr->nodes[i].request_time < now.tv_sec - 15)
+                n = &sr->nodes[i];
+        }
+    }
+
+    if(!n || n->pinged >= 3 || n->replied ||
+       n->request_time >= now.tv_sec - 15)
+        return 0;
+
+    debugf("Sending get_peers.\n");
+    make_tid(tid, "gp", sr->tid);
+    send_get_peers(s, (struct sockaddr*)&n->sin,
+                   sizeof(struct sockaddr_in),
+                   tid, 4, sr->id, sr->tid,
+                   n->reply_time >= now.tv_sec - 15);
+    n->pinged++;
+    n->request_time = now.tv_sec;
+    /* If the node happens to be in our main routing table, mark it
+       as pinged. */
+    node = find_node(n->id);
+    if(node) pinged(s, node, NULL);
+    return 1;
+}
+
+/* When a search is in progress, we periodically call search_step to send
+   further requests. */
+static void
+search_step(int s, struct search *sr, dht_callback *callback, void *closure)
+{
+    int i, j;
+    int all_done = 1;
+
+    /* Check if the first 8 live nodes have replied. */
+    j = 0;
+    for(i = 0; i < sr->numnodes && j < 8; i++) {
+        struct search_node *n = &sr->nodes[i];
+        if(n->pinged >= 3)
+            continue;
+        if(!n->replied) {
+            all_done = 0;
+            break;
+        }
+        j++;
+    }
+
+    if(all_done) {
+        if(sr->port == 0) {
+            goto done;
+        } else {
+            int all_acked = 1;
+            j = 0;
+            for(i = 0; i < sr->numnodes && j < 8; i++) {
+                struct search_node *n = &sr->nodes[i];
+                struct node *node;
+                unsigned char tid[4];
+                if(n->pinged >= 3)
+                    continue;
+                if(!n->acked) {
+                    all_acked = 0;
+                    debugf("Sending announce_peer.\n");
+                    make_tid(tid, "ap", sr->tid);
+                    send_announce_peer(s,
+                                       (struct sockaddr*)&n->sin,
+                                       sizeof(struct sockaddr_in),
+                                       tid, 4, sr->id, sr->port,
+                                       n->token, n->token_len,
+                                       n->reply_time >= now.tv_sec - 15);
+                    n->pinged++;
+                    n->request_time = now.tv_sec;
+                    node = find_node(n->id);
+                    if(node) pinged(s, node, NULL);
+                }
+                j++;
+            }
+            if(all_acked)
+                goto done;
+        }
+        sr->step_time = now.tv_sec;
+        return;
+    }
+
+    if(sr->step_time + 15 >= now.tv_sec)
+        return;
+
+    j = 0;
+    for(i = 0; i < sr->numnodes; i++) {
+        j += search_send_get_peers(s, sr, &sr->nodes[i]);
+        if(j >= 3)
+            break;
+    }
+    sr->step_time = now.tv_sec;
+    return;
+
+ done:
+    sr->done = 1;
+    if(callback)
+        (*callback)(closure, DHT_EVENT_SEARCH_DONE, sr->id, NULL, 0);
+    sr->step_time = now.tv_sec;
+}
+
+static struct search *
+find_free_search_slot(void)
+{
+    int i;
+    struct search *sr = NULL;
+
+    if(numsearches < DHT_MAX_SEARCHES)
+        return &searches[numsearches++];
+
+    for(i = 0; i < numsearches; i++) {
+        if(searches[i].done &&
+           (sr == NULL || searches[i].step_time < sr->step_time))
+            sr = &searches[i];
+    }
+    return sr;
+}
+
+/* Insert the contents of a bucket into a search structure. */
+static void
+insert_search_bucket(struct bucket *b, struct search *sr)
+{
+    struct node *n;
+    n = b->nodes;
+    while(n) {
+        insert_search_node(n->id, &n->sin, sr, 0, NULL, 0);
+        n = n->next;
+    }
+}
+
+/* Start a search.  If port is non-zero, perform an announce when the
+   search is complete. */
+int 
+dht_search(int s, const unsigned char *id, int port,
+           dht_callback *callback, void *closure)
+{
+    struct search *sr;
+    struct bucket *b;
+    int i;
+
+    for(i = 0; i < numsearches; i++) {
+        if(id_cmp(searches[i].id, id) == 0)
+            break;
+    }
+
+    if(i < numsearches) {
+        /* We're reusing data from an old search.  Reusing the same tid
+           means that we can merge replies for both searches. */
+        int j;
+        sr = searches + i;
+        sr->done = 0;
+    again:
+        for(j = 0; j < sr->numnodes; j++) {
+            struct search_node *n;
+            n = &sr->nodes[j];
+            /* Discard any doubtful nodes. */
+            if(n->pinged >= 3 || n->reply_time < now.tv_sec - 7200) {
+                flush_search_node(n, sr);
+                goto again;
+            }
+            n->pinged = 0;
+            n->token_len = 0;
+            n->replied = 0;
+            n->acked = 0;
+        }
+    } else {
+        sr = find_free_search_slot();
+        if(sr == NULL) {
+            errno = ENOSPC;
+            return -1;
+        }
+        memset(sr, 0, sizeof(struct search));
+        sr->tid = search_id++;
+        memcpy(sr->id, id, 20);
+        sr->numnodes = 0;
+    }
+
+    sr->port = port;
+
+    b = find_bucket(id);
+    insert_search_bucket(b, sr);
+
+    if(sr->numnodes < 8) {
+        struct bucket *p = previous_bucket(b);
+        if(b->next)
+            insert_search_bucket(b->next, sr);
+        if(p)
+            insert_search_bucket(p, sr);
+    }
+    if(sr->numnodes < SEARCH_NODES)
+        insert_search_bucket(find_bucket(myid), sr);
+
+    search_step(s, sr, callback, closure);
+    search_time = now.tv_sec;
+    return 1;
+}
+
+/* A struct storage stores all the stored peer addresses for a given info
+   hash. */
+
+static struct storage *
+find_storage(const unsigned char *id)
+{
+    struct storage *st = storage;
+
+    while(st) {
+        if(id_cmp(id, st->id) == 0)
+            break;
+        st = st->next;
+    }
+    return st;
+}
+
+static int
+storage_store(const unsigned char *id, const unsigned char *ip,
+              unsigned short port)
+{
+    int i;
+    struct storage *st = storage;
+
+    st = find_storage(id);
+
+    if(st == NULL) {
+        st = calloc(1, sizeof(struct storage));
+        if(st == NULL) return -1;
+        memcpy(st->id, id, 20);
+        st->next = storage;
+        storage = st;
+    }
+
+    for(i = 0; i < st->numpeers; i++) {
+        if(st->peers[i].port == port && memcmp(st->peers[i].ip, ip, 4) == 0)
+            break;
+    }
+    if(i < st->numpeers) {
+        /* Already there, only need to refresh */
+        st->peers[i].time = now.tv_sec;
+        return 0;
+    } else {
+        struct peer *p;
+        if(i >= st->maxpeers) {
+            /* Need to expand the array. */
+            int n;
+            struct peer *new_peers;
+            if(st->maxpeers > DHT_MAX_PEERS / 2)
+                return 0;
+            n = st->maxpeers == 0 ? 2 : 2 * st->maxpeers;
+            new_peers = realloc(st->peers, n * sizeof(struct peer));
+            if(new_peers == NULL)
+                return -1;
+            st->peers = new_peers;
+            st->maxpeers = n;
+        }
+        p = &st->peers[st->numpeers++];
+        p->time = now.tv_sec;
+        memcpy(p->ip, ip, 4);
+        p->port = port;
+        return 1;
+    }
+}
+
+static int
+expire_storage( void )
+{
+    struct storage *st = storage, *previous = NULL;
+    while(st) {
+        int i = 0;
+        while(i < st->numpeers) {
+            if(st->peers[i].time < now.tv_sec - 32 * 60) {
+                if(i != st->numpeers - 1)
+                    st->peers[i] = st->peers[st->numpeers - 1];
+                st->numpeers--;
+            } else {
+                i++;
+            }
+        }
+
+        if(st->numpeers == 0) {
+            free(st->peers);
+            if(previous)
+                previous->next = st->next;
+            else
+                storage = st->next;
+            free(st);
+            if(previous)
+                st = previous->next;
+            else
+                st = storage;
+        } else {
+            previous = st;
+            st = st->next;
+        }
+    }
+    return 1;
+}
+
+/* We've just found out that a node is buggy. */
+static void
+broken_node(int s, const unsigned char *id, struct sockaddr_in *sin)
+{
+    int i, j;
+
+    debugf("Blacklisting broken node.\n");
+
+    if(id) {
+        /* Make the node easy to discard. */
+        struct node *n;
+        n = find_node(id);
+        if(n) {
+            n->pinged = 3;
+            pinged(s, n, NULL);
+        }
+        /* Discard it from any searches in progress. */
+        for(i = 0; i < numsearches; i++) {
+            for(j = 0; j < searches[i].numnodes; j++)
+                if(id_cmp(searches[i].nodes[j].id, id) == 0)
+                    flush_search_node(&searches[i].nodes[j],
+                                      &searches[i]);
+        }
+    }
+    /* And make sure we don't hear from it again. */
+    blacklist[next_blacklisted] = *sin;
+    next_blacklisted = (next_blacklisted + 1) % DHT_MAX_BLACKLISTED;
+}
+
+static int
+rotate_secrets( void )
+{
+    int fd;
+    unsigned seed;
+
+    fd = open("/dev/urandom", O_RDONLY);
+    if(fd < 0)
+        return -1;
+
+    memcpy(oldsecret, secret, sizeof(secret));
+    read(fd, secret, sizeof(secret));
+
+    read(fd, &seed, sizeof(seed));
+    srandom(seed);
+
+    close(fd);
+    rotate_secrets_time = now.tv_sec + 900 + random() % 1800;
+    return 1;
+}
+
+#ifndef TOKEN_SIZE
+#define TOKEN_SIZE 8
+#endif
+
+static void
+make_token(const unsigned char *ipv4, unsigned short port, int old,
+           unsigned char *token_return)
+{
+    dht_hash(token_return, TOKEN_SIZE,
+             old ? oldsecret : secret, sizeof(secret),
+             ipv4, 4,
+             (unsigned char*)&port, 2);
+}
+static int
+token_match(unsigned char *token, int token_len,
+            const unsigned char *ipv4, unsigned short port)
+{
+    unsigned char t[TOKEN_SIZE];
+    if(token_len != TOKEN_SIZE)
+        return 0;
+    make_token(ipv4, port, 0, t);
+    if(memcmp(t, token, TOKEN_SIZE) == 0)
+        return 1;
+    make_token(ipv4, port, 1, t);
+    if(memcmp(t, token, TOKEN_SIZE) == 0)
+        return 1;
+    return 0;
+}
+
+int
+dht_nodes(int *good_return, int *dubious_return, int *cached_return,
+          int *incoming_return)
+{
+    int good = 0, dubious = 0, cached = 0, incoming = 0;
+    struct bucket *b = buckets;
+    while(b) {
+        struct node *n = b->nodes;
+        while(n) {
+            if(node_good(n)) {
+                good++;
+                if(n->time > n->reply_time)
+                    incoming++;
+            } else {
+                dubious++;
+            }
+            n = n->next;
+        }
+        if(b->cached.sin_family == AF_INET)
+            cached++;
+        b = b->next;
+    }
+    if(good_return)
+        *good_return = good;
+    if(dubious_return)
+        *dubious_return = dubious;
+    if(cached_return)
+        *cached_return = cached;
+    if(incoming_return)
+        *incoming_return = cached;
+    return good + dubious;
+}
+                
+
+void
+dht_dump_tables(FILE *f)
+{
+    int i, j;
+    struct bucket *b = buckets;
+    struct storage *st = storage;
+
+    fprintf(f, "My id ");
+    print_hex(f, myid, 20);
+    fprintf(f, "\n");
+    while(b) {
+        struct node *n = b->nodes;
+        fprintf(f, "Bucket ");
+        print_hex(f, b->first, 20);
+        fprintf(f, " count %d age %d%s%s:\n",
+               b->count, (int)(now.tv_sec - b->time),
+               in_bucket(myid, b) ? " (mine)" : "",
+               b->cached.sin_family ? " (cached)" : "");
+        while(n) {
+            char buf[512];
+            fprintf(f, "    Node ");
+            print_hex(f, n->id, 20);
+            inet_ntop(AF_INET, &n->sin.sin_addr, buf, 512);
+            fprintf(f, " %s:%d ", buf, ntohs(n->sin.sin_port));
+            if(n->time != n->reply_time)
+                fprintf(f, "age %ld, %ld",
+                       (long)(now.tv_sec - n->time),
+                       (long)(now.tv_sec - n->reply_time));
+            else
+                fprintf(f, "age %ld", (long)(now.tv_sec - n->time));
+            if(n->pinged)
+                fprintf(f, " (%d)", n->pinged);
+            if(node_good(n))
+                fprintf(f, " (good)");
+            fprintf(f, "\n");
+            n = n->next;
+        }
+        b = b->next;
+    }
+    for(i = 0; i < numsearches; i++) {
+        struct search *sr = &searches[i];
+        fprintf(f, "\nSearch %d id ", i);
+        print_hex(f, sr->id, 20);
+        fprintf(f, " age %d%s\n", (int)(now.tv_sec - sr->step_time),
+               sr->done ? " (done)" : "");
+        for(j = 0; j < sr->numnodes; j++) {
+            struct search_node *n = &sr->nodes[j];
+            fprintf(f, "Node %d id ", j);
+            print_hex(f, n->id, 20);
+            fprintf(f, " bits %d age ", common_bits(sr->id, n->id));
+            if(n->request_time)
+                fprintf(f, "%d, ", (int)(now.tv_sec - n->request_time));
+            fprintf(f, "%d", (int)(now.tv_sec - n->reply_time));
+            if(n->pinged)
+                fprintf(f, " (%d)", n->pinged);
+            fprintf(f, "%s%s.\n",
+                   find_node(n->id) ? " (known)" : "",
+                   n->replied ? " (replied)" : "");
+        }
+    }
+
+    
+    while(st) {
+        fprintf(f, "\nStorage ");
+        print_hex(f, st->id, 20);
+        fprintf(f, " %d/%d nodes:", st->numpeers, st->maxpeers);
+        for(i = 0; i < st->numpeers; i++) {
+            char buf[20];
+            inet_ntop(AF_INET, st->peers[i].ip, buf, 20);
+            fprintf(f, " %s:%u (%ld)",
+                    buf, st->peers[i].port,
+                    (long)(now.tv_sec - st->peers[i].time));
+        }
+        st = st->next;
+    }
+    
+    fprintf(f, "\n\n");
+    fflush(f);
+}
+
+int
+dht_init(int s, const unsigned char *id)
+{
+    int rc;
+
+    if(buckets) {
+        errno = EBUSY;
+        return -1;
+    }
+
+    buckets = calloc(sizeof(struct bucket), 1);
+    if(buckets == NULL)
+        return -1;
+
+    numsearches = 0;
+
+    storage = NULL;
+
+    rc = fcntl(s, F_GETFL, 0);
+    if(rc < 0)
+        return -1;
+
+    rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK));
+    if(rc < 0)
+        return -1;
+
+    memcpy(myid, id, 20);
+
+    gettimeofday(&now, NULL);
+
+    mybucket_grow_time = now.tv_sec;
+    confirm_nodes_time = now.tv_sec + random() % 3;
+
+    search_id = random() & 0xFFFF;
+    search_time = 0;
+
+    next_blacklisted = 0;
+
+    leaky_bucket_time = now.tv_sec;
+    leaky_bucket_tokens = MAX_LEAKY_BUCKET_TOKENS;
+
+    memset(secret, 0, sizeof(secret));
+    rotate_secrets();
+    expire_buckets(s);
+
+    return 1;
+}
+
+int
+dht_uninit(int s, int dofree)
+{
+    if(!dofree)
+        return 1;
+
+    while(buckets) {
+        struct bucket *b = buckets;
+        buckets = b->next;
+        while(b->nodes) {
+            struct node *n = b->nodes;
+            b->nodes = n->next;
+            free(n);
+        }
+        free(b);
+    }
+
+    while(storage) {
+        struct storage *st = storage;
+        storage = storage->next;
+        free(st->peers);
+        free(st);
+    }
+    
+    return 1;
+}
+
+/* Rate control for requests we receive. */
+
+static int
+leaky_bucket( void )
+{
+    if(leaky_bucket_tokens == 0) {
+        leaky_bucket_tokens = MIN(MAX_LEAKY_BUCKET_TOKENS,
+                                  2 * (now.tv_sec - leaky_bucket_time));
+        leaky_bucket_time = now.tv_sec;
+    }
+
+    if(leaky_bucket_tokens == 0)
+        return 0;
+
+    leaky_bucket_tokens--;
+    return 1;
+}
+
+int
+dht_periodic(int s, int available, time_t *tosleep,
+             dht_callback *callback, void *closure)
+{
+    gettimeofday(&now, NULL);
+
+    if(available) {
+        int rc, i, message;
+        unsigned char tid[16], id[20], info_hash[20], target[20];
+        unsigned char buf[1024], nodes[256], token[128];
+        int tid_len = 16, token_len = 128;
+        int nodes_len = 256;
+        unsigned short port;
+        unsigned char values[2048];
+        int values_len = 2048;
+        struct sockaddr_in source;
+        socklen_t source_len = sizeof(struct sockaddr_in);
+        unsigned short ttid;
+
+        rc = recvfrom(s, buf, 1024, 0,
+                      (struct sockaddr*)&source, &source_len);
+        if(rc < 0) {
+            if(errno == EAGAIN)
+                goto dontread;
+            else
+                return rc;
+        }
+
+        if(source_len != sizeof(struct sockaddr_in)) {
+            /* Hmm... somebody gave us an IPv6 socket. */
+            errno = EINVAL;
+            return -1;
+        }
+
+        for(i = 0; i < DHT_MAX_BLACKLISTED; i++) {
+            if(blacklist[i].sin_family == AF_INET &&
+               blacklist[i].sin_port == source.sin_port &&
+               memcmp(&blacklist[i].sin_addr, &source.sin_addr, 4) == 0) {
+                debugf("Received packet from blacklisted node.\n");
+                goto dontread;
+            }
+        }
+
+        message = parse_message(buf, rc, tid, &tid_len, id, info_hash,
+                                target, &port, token, &token_len,
+                                nodes, &nodes_len, values, &values_len);
+        if(id_cmp(id, zeroes) == 0) {
+            debugf("Message with no id: ");
+            debug_printable(buf, rc);
+            debugf("\n");
+            goto dontread;
+        }
+
+        if(id_cmp(id, myid) == 0) {
+            debugf("Received message from self.\n");
+            goto dontread;
+        }
+
+        if(message > REPLY) {
+            /* Rate limit requests. */
+            if(!leaky_bucket()) {
+                debugf("Dropping request due to rate limiting.\n");
+                goto dontread;
+            }
+        }
+
+        switch(message) {
+        case REPLY:
+            if(tid_len != 4) {
+                debugf("Broken node truncates transaction ids: ");
+                debug_printable(buf, rc);
+                printf("\n");
+                /* This is really annoying, as it means that we will
+                   time-out all our searches that go through this node.
+                   Kill it. */
+                broken_node(s, id, &source);
+                goto dontread;
+            }
+            if(tid_match(tid, "pn", NULL)) {
+                debugf("Pong!\n");
+                new_node(s, id, &source, 2);
+            } else if(tid_match(tid, "fn", NULL) ||
+                      tid_match(tid, "gp", NULL)) {
+                int gp = 0;
+                struct search *sr = NULL;
+                if(tid_match(tid, "gp", &ttid)) {
+                    gp = 1;
+                    sr = find_search(ttid);
+                }
+                debugf("Nodes found (%d)%s!\n", nodes_len / 26,
+                       gp ? " for get_peers" : "");
+                if(nodes_len % 26 != 0) {
+                    debugf("Unexpected length for node info!\n");
+                    broken_node(s, id, &source);
+                } else if(gp && sr == NULL) {
+                    debugf("Unknown search!\n");
+                    new_node(s, id, &source, 1);
+                } else {
+                    int i;
+                    new_node(s, id, &source, 2);
+                    for(i = 0; i < nodes_len / 26; i++) {
+                        unsigned char *ni = nodes + i * 26;
+                        struct sockaddr_in sin;
+                        if(id_cmp(ni, myid) == 0)
+                            continue;
+                        memset(&sin, 0, sizeof(sin));
+                        sin.sin_family = AF_INET;
+                        memcpy(&sin.sin_addr, ni + 20, 4);
+                        memcpy(&sin.sin_port, ni + 24, 2);
+                        new_node(s, ni, &sin, 0);
+                        if(sr) {
+                            insert_search_node(ni, &sin, sr, 0, NULL, 0);
+                        }
+                    }
+                    if(sr)
+                        /* Since we received a reply, the number of
+                           requests in flight has decreased.  Let's push
+                           another request. */
+                        search_send_get_peers(s, sr, NULL);
+                }
+                if(sr) {
+                    insert_search_node(id, &source, sr,
+                                       1, token, token_len);
+                    if(values_len > 0) {
+                        debugf("Got values (%d)!\n", values_len / 6);
+                        if(callback) {
+                            (*callback)(closure, DHT_EVENT_VALUES,
+                                        sr->id, (void*)values, values_len);
+                        }
+                    }
+                }
+            } else if(tid_match(tid, "ap", &ttid)) {
+                struct search *sr;
+                debugf("Got reply to announce_peer.\n");
+                sr = find_search(ttid);
+                if(!sr) {
+                    debugf("Unknown search!");
+                    new_node(s, id, &source, 1);
+                } else {
+                    int i;
+                    new_node(s, id, &source, 2);
+                    for(i = 0; i < sr->numnodes; i++)
+                        if(id_cmp(sr->nodes[i].id, id) == 0) {
+                            sr->nodes[i].request_time = 0;
+                            sr->nodes[i].reply_time = now.tv_sec;
+                            sr->nodes[i].acked = 1;
+                            sr->nodes[i].pinged = 0;
+                            break;
+                        }
+                    /* See comment for gp above. */
+                    search_send_get_peers(s, sr, NULL);
+                }
+            } else {
+                debugf("Unexpected reply: ");
+                debug_printable(buf, rc);
+                debugf("\n");
+            }
+            break;
+        case PING:
+            debugf("Ping (%d)!\n", tid_len);
+            new_node(s, id, &source, 1);
+            debugf("Sending pong!\n");
+            send_pong(s, (struct sockaddr*)&source, sizeof(source),
+                      tid, tid_len);
+            break;
+        case FIND_NODE:
+            debugf("Find node!\n");
+            new_node(s, id, &source, 1);
+            {
+                struct bucket *b = find_bucket(target);
+                if(b) {
+                    debugf("Sending nodes from bucket.\n");
+                    send_bucket_nodes(s,
+                                      (struct sockaddr*)&source,
+                                      sizeof(source),
+                                      tid, tid_len, b, NULL, 0);
+                }
+            }
+            break;
+        case GET_PEERS:
+            debugf("Get_peers!\n");
+            new_node(s, id, &source, 1);
+            if(id_cmp(info_hash, zeroes) == 0) {
+                debugf("Eek!  Got get_peers with no info_hash.\n");
+                break;
+            } else {
+                struct storage *st = find_storage(info_hash);
+                if(st && st->numpeers > 0) {
+                    int i0, n0, n1;
+                    unsigned char token[TOKEN_SIZE];
+                    make_token((unsigned char*)&source.sin_addr,
+                               ntohs(source.sin_port),
+                               0, token);
+                    i0 = random() % st->numpeers;
+                    /* We treat peers as a circular list, and choose 50
+                       peers starting at i0. */
+                    n0 = MIN(st->numpeers - i0, 50);
+                    n1 = n0 >= 50 ? 0 : MIN(50, i0);
+
+                    debugf("Sending found peers (%d).\n", n0 + n1);
+                    send_peers_found(s, (struct sockaddr*)&source,
+                                     sizeof(source), tid, tid_len,
+                                     st->peers + i0, n0,
+                                     st->peers, n1,
+                                     token, TOKEN_SIZE);
+
+                } else {
+                    struct bucket *b = find_bucket(info_hash);
+                    if(b) {
+                        unsigned char token[TOKEN_SIZE];
+                        make_token((unsigned char*)&source.sin_addr,
+                                   ntohs(source.sin_port),
+                                   0, token);
+                        debugf("Sending nodes for get_peers.\n");
+                        send_bucket_nodes(s, (struct sockaddr*)&source,
+                                          sizeof(source),
+                                          tid, tid_len, b,
+                                          token, TOKEN_SIZE);
+                    }
+                }
+            }
+            break;
+        case ANNOUNCE_PEER:
+            debugf("Announce peer!\n");
+            new_node(s, id, &source, 1);
+            if(id_cmp(info_hash, zeroes) == 0) {
+                debugf("Announce_peer with no info_hash.\n");
+                break;
+            }
+            if(!token_match(token, token_len,
+                            (unsigned char*)&source.sin_addr,
+                            ntohs(source.sin_port))) {
+                debugf("Incorrect token for announce_peer.\n");
+                break;
+            }
+            if(port == 0) {
+                debugf("Announce_peer with forbidden port %d.\n", port);
+                break;
+            }
+            storage_store(info_hash,
+                          (unsigned char*)&source.sin_addr, port);
+            debugf("Sending peer announced.\n");
+            send_peer_announced(s, (struct sockaddr*)&source,
+                                sizeof(source), tid, tid_len);
+        }
+    }
+
+ dontread:
+    if(now.tv_sec >= rotate_secrets_time)
+        rotate_secrets();
+
+    if(now.tv_sec >= expire_stuff_time) {
+        expire_buckets(s);
+        expire_storage();
+    }
+
+    if(search_time > 0 && now.tv_sec >= search_time) {
+        int i;
+        for(i = 0; i < numsearches; i++) {
+            struct search *sr = &searches[i];
+            if(!sr->done && sr->step_time + 5 <= now.tv_sec) {
+                search_step(s, sr, callback, closure);
+            }
+        }
+                   
+        search_time = 0;
+        
+        for(i = 0; i < numsearches; i++) {
+            struct search *sr = &searches[i];
+            if(!sr->done) {
+                time_t tm = sr->step_time + 15 + random() % 10;
+                if(search_time == 0 || search_time > tm)
+                    search_time = tm;
+            }
+        }
+    }
+
+    if(now.tv_sec >= confirm_nodes_time) {
+        struct bucket *b;
+        int soon = 0;
+        b = buckets;
+        while(!soon && b) {
+            struct bucket *q;
+            if(b->time < now.tv_sec - 900) {
+                /* This bucket hasn't seen any activity for a long
+                   time.  Pick a random id in this bucket's range, and
+                   send a request to a random node. */
+                unsigned char id[20];
+                struct node *n;
+                int rc;
+                
+                rc = bucket_random(b, id);
+                if(rc < 0)
+                    memcpy(id, b->first, 20);
+                
+                q = b;
+                /* If the bucket is empty, we try to fill it from
+                   a neighbour.  We also sometimes do it gratuitiously
+                   to recover from buckets full of broken nodes. */
+                if(q->next && (q->count == 0 || random() % 7 == 0))
+                    q = b->next;
+                if(q->count == 0 || random() % 7 == 0) {
+                    struct bucket *r;
+                    r = previous_bucket(b);
+                    if(r && r->count > 0)
+                        q = r;
+                }
+
+                if(q) {
+                    n = random_node(q);
+                    if(n) {
+                        unsigned char tid[4];
+                        debugf("Sending find_node "
+                               "for bucket maintenance.\n");
+                        make_tid(tid, "fn", 0);
+                        send_find_node(s, (struct sockaddr*)&n->sin,
+                                       sizeof(struct sockaddr_in),
+                                       tid, 4, id,
+                                       n->reply_time >= now.tv_sec - 15);
+                        pinged(s, n, q);
+                        /* In order to avoid sending queries back-to-back,
+                           give up for now and reschedule us soon. */
+                        soon = 1;
+                    }
+                }
+            }
+            b = b->next;
+        }
+
+        if(!soon && mybucket_grow_time >= now.tv_sec - 150) {
+            /* We've seen updates to our own bucket recently.  Try to
+               improve our neighbourship. */
+            unsigned char id[20];
+            struct bucket *b, *q;
+            struct node *n;
+            
+            memcpy(id, myid, 20);
+            id[19] = random() % 0xFF;
+            b = find_bucket(myid);
+            q = b;
+            if(q->next && (q->count == 0 || random() % 7 == 0))
+                q = b->next;
+            if(q->count == 0 || random() % 7 == 0) {
+                struct bucket *r;
+                r = previous_bucket(b);
+                if(r && r->count > 0)
+                    q = r;
+            }
+
+            if(q) {
+                n = random_node(q);
+                if(n) {
+                    unsigned char tid[4];
+                    debugf("Sending find_node "
+                           "for neighborhood maintenance.\n");
+                    make_tid(tid, "fn", 0);
+                    send_find_node(s, (struct sockaddr*)&n->sin,
+                                   sizeof(struct sockaddr_in),
+                                   tid, 4, id,
+                                   n->reply_time >= now.tv_sec - 15);
+                    pinged(s, n, q);
+                }
+            }
+            soon = 1;
+        }
+
+        /* In order to maintain all buckets' age within 900 seconds, worst
+           case is roughly 40 seconds, assuming the table is 22 bits deep.
+           We want to keep a margin for neighborhood maintenance, so keep
+           this within 30 seconds. */
+        if(soon)
+            confirm_nodes_time = now.tv_sec + 10 + random() % 20;
+        else
+            confirm_nodes_time = now.tv_sec + 60 + random() % 120;
+    }
+
+    if(confirm_nodes_time > now.tv_sec)
+        *tosleep = confirm_nodes_time - now.tv_sec;
+    else
+        *tosleep = 0;
+
+    if(search_time > 0) {
+        if(search_time <= now.tv_sec)
+            *tosleep = 0;
+        else if(*tosleep > search_time - now.tv_sec)
+            *tosleep = search_time - now.tv_sec;
+    }
+    
+    return find_bucket(myid)->count > 2;
+}
+
+int
+dht_get_nodes(struct sockaddr_in *sins, int num)
+{
+    int i;
+    struct bucket *b;
+    struct node *n;
+
+    i = 0;
+
+    /* For restoring to work without discarding too many nodes, the list
+       must start with the contents of our bucket. */
+    b = find_bucket(myid);
+    n = b->nodes;
+    while(n && i < num) {
+        if(node_good(n)) {
+            sins[i] = n->sin;
+            i++;
+        }
+        n = n->next;
+    }
+
+    b = buckets;
+    while(b && i < num) {
+        if(!in_bucket(myid, b)) {
+            n = b->nodes;
+            while(n && i < num) {
+                if(node_good(n)) {
+                    sins[i] = n->sin;
+                    i++;
+                }
+                n = n->next;
+            }
+        }
+        b = b->next;
+    }
+    return i;
+}
+
+int
+dht_insert_node(int s, const unsigned char *id, struct sockaddr_in *sin)
+{
+    struct node *n;
+    n = new_node(s, id, sin, 0);
+    return !!n;
+}
+
+int
+dht_ping_node(int s, struct sockaddr_in *sin)
+{
+    unsigned char tid[4];
+    debugf("Sending ping.\n");
+    make_tid(tid, "pn", 0);
+    return send_ping(s, (struct sockaddr*)sin, sizeof(struct sockaddr_in),
+                     tid, 4);
+}
+
+/* We could use a proper bencoding printer and parser, but the format of
+   DHT messages is fairly stylised, so this seemed simpler. */
+
+#define CHECK(offset, delta, size)                      \
+    if(delta < 0 || offset + delta > size) goto fail
+
+#define INC(offset, delta, size)                        \
+    CHECK(offset, delta, size);                         \
+    offset += delta
+
+#define COPY(buf, offset, src, delta, size)             \
+    CHECK(offset, delta, size);                         \
+    memcpy(buf + offset, src, delta);                   \
+    i += delta;
+
+int
+send_ping(int s, struct sockaddr *sa, int salen,
+          const unsigned char *tid, int tid_len)
+{
+    char buf[512];
+    int i = 0, rc;
+    rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512);
+    COPY(buf, i, myid, 20, 512);
+    rc = snprintf(buf + i, 512 - i, "e1:q4:ping1:t%d:", tid_len);
+    INC(i, rc, 512);
+    COPY(buf, i, tid, tid_len, 512);
+    rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512);
+    return sendto(s, buf, i, 0, sa, salen);
+
+ fail:
+    errno = ENOSPC;
+    return -1;
+}
+
+int
+send_pong(int s, struct sockaddr *sa, int salen,
+          const unsigned char *tid, int tid_len)
+{
+    char buf[512];
+    int i = 0, rc;
+    rc = snprintf(buf + i, 512 - i, "d1:rd2:id20:"); INC(i, rc, 512);
+    COPY(buf, i, myid, 20, 512);
+    rc = snprintf(buf + i, 512 - i, "e1:t%d:", tid_len); INC(i, rc, 512);
+    COPY(buf, i, tid, tid_len, 512);
+    rc = snprintf(buf + i, 512 - i, "1:y1:re"); INC(i, rc, 512);
+    return sendto(s, buf, i, 0, sa, salen);
+
+ fail:
+    errno = ENOSPC;
+    return -1;
+}
+
+int
+send_find_node(int s, struct sockaddr *sa, int salen,
+               const unsigned char *tid, int tid_len,
+               const unsigned char *target, int confirm)
+{
+    char buf[512];
+    int i = 0, rc;
+    rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512);
+    COPY(buf, i, myid, 20, 512);
+    rc = snprintf(buf + i, 512 - i, "6:target20:"); INC(i, rc, 512);
+    COPY(buf, i, target, 20, 512);
+    rc = snprintf(buf + i, 512 - i, "e1:q9:find_node1:t%d:", tid_len);
+    INC(i, rc, 512);
+    COPY(buf, i, tid, tid_len, 512);
+    rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512);
+    return sendto(s, buf, i, confirm ? MSG_CONFIRM : 0, sa, salen);
+
+ fail:
+    errno = ENOSPC;
+    return -1;
+}
+
+int
+send_found_nodes(int s, struct sockaddr *sa, int salen,
+                 const unsigned char *tid, int tid_len,
+                 const unsigned char *nodes, int nodes_len,
+                 const unsigned char *token, int token_len)
+{
+    char buf[2048];
+    int i = 0, rc;
+    rc = snprintf(buf + i, 2048 - i, "d1:rd2:id20:"); INC(i, rc, 2048);
+    COPY(buf, i, myid, 20, 2048);
+    if(nodes) {
+        rc = snprintf(buf + i, 2048 - i, "5:nodes%d:", nodes_len);
+        INC(i, rc, 2048);
+        COPY(buf, i, nodes, nodes_len, 2048);
+    }
+    if(token) {
+        rc = snprintf(buf + i, 2048 - i, "5:token%d:", token_len);
+        INC(i, rc, 2048);
+        COPY(buf, i, token, token_len, 2048);
+    }
+    rc = snprintf(buf + i, 2048 - i, "e1:t%d:", tid_len); INC(i, rc, 2048);
+    COPY(buf, i, tid, tid_len, 2048);
+    rc = snprintf(buf + i, 2048 - i, "1:y1:re"); INC(i, rc, 2048);
+
+    return sendto(s, buf, i, 0, sa, salen);
+
+ fail:
+    errno = ENOSPC;
+    return -1;
+}
+
+static int
+buffer_bucket(unsigned char *buf, int bufsize, struct bucket *b)
+{
+    int i = 0;
+    struct node *n = b->nodes;
+    while(n && i < bufsize - 26) {
+        if(node_good(n)) {
+            memcpy(buf + i, n->id, 20);
+            memcpy(buf + i + 20, &n->sin.sin_addr, 4);
+            memcpy(buf + i + 24, &n->sin.sin_port, 2);
+            i += 26;
+        }
+        n = n->next;
+    }
+    return i;
+}
+
+int
+send_bucket_nodes(int s, struct sockaddr *sa, int salen,
+                  const unsigned char *tid, int tid_len,
+                  struct bucket *b,
+                  const unsigned char *token, int token_len)
+{
+    unsigned char nodes[8 * 26];
+    int nodeslen = 0;
+
+    nodeslen = buffer_bucket(nodes, 8 * 26, b);
+    return send_found_nodes(s, sa, salen, tid, tid_len,
+                            nodes, nodeslen,
+                            token, token_len);
+}
+
+int
+send_get_peers(int s, struct sockaddr *sa, int salen,
+               unsigned char *tid, int tid_len,
+               unsigned char *infohash, unsigned short port, int confirm)
+{
+    char buf[512];
+    int i = 0, rc;
+
+    rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512);
+    COPY(buf, i, myid, 20, 512);
+    rc = snprintf(buf + i, 512 - i, "9:info_hash20:"); INC(i, rc, 512);
+    COPY(buf, i, infohash, 20, 512);
+    rc = snprintf(buf + i, 512 - i, "e1:q9:get_peers1:t%d:", tid_len);
+    INC(i, rc, 512);
+    COPY(buf, i, tid, tid_len, 512);
+    rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512);
+    return sendto(s, buf, i, confirm ? MSG_CONFIRM : 0, sa, salen);
+
+ fail:
+    errno = ENOSPC;
+    return -1;
+}
+
+int
+send_announce_peer(int s, struct sockaddr *sa, int salen,
+                   unsigned char *tid, int tid_len,
+                   unsigned char *infohash, unsigned short port,
+                   unsigned char *token, int token_len, int confirm)
+{
+    char buf[512];
+    int i = 0, rc;
+
+    rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512);
+    COPY(buf, i, myid, 20, 512);
+    rc = snprintf(buf + i, 512 - i, "9:info_hash20:"); INC(i, rc, 512);
+    COPY(buf, i, infohash, 20, 512);
+    rc = snprintf(buf + i, 512 - i, "4:porti%ue5:token%d:", (unsigned)port,
+                  token_len);
+    INC(i, rc, 512);
+    COPY(buf, i, token, token_len, 512);
+    rc = snprintf(buf + i, 512 - i, "e1:q13:announce_peer1:t%d:", tid_len);
+    INC(i, rc, 512);
+    COPY(buf, i, tid, tid_len, 512);
+    rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512);
+
+    return sendto(s, buf, i, confirm ? 0 : MSG_CONFIRM, sa, salen);
+
+ fail:
+    errno = ENOSPC;
+    return -1;
+}
+
+int
+send_peers_found(int s, struct sockaddr *sa, int salen,
+                 unsigned char *tid, int tid_len,
+                 struct peer *peers1, int numpeers1,
+                 struct peer *peers2, int numpeers2,
+                 unsigned char *token, int token_len)
+{
+    char buf[1400];
+    int i = 0, rc, j;
+
+    rc = snprintf(buf + i, 1400 - i, "d1:rd2:id20:"); INC(i, rc, 1400);
+    COPY(buf, i, myid, 20, 1400);
+    rc = snprintf(buf + i, 1400 - i, "5:token%d:", token_len); INC(i, rc, 1400);
+    COPY(buf, i, token, token_len, 1400);
+    rc = snprintf(buf + i, 1400 - i, "6:valuesl"); INC(i, rc, 1400);
+    for(j = 0; j < numpeers1; j++) {
+        unsigned short swapped = htons(peers1[j].port);
+        rc = snprintf(buf + i, 1400 - i, "6:"); INC(i, rc, 1400);
+        COPY(buf, i, peers1[j].ip, 4, 1400);
+        COPY(buf, i, &swapped, 2, 1400);
+    }
+    for(j = 0; j < numpeers2; j++) {
+        unsigned short swapped = htons(peers2[j].port);
+        rc = snprintf(buf + i, 1400 - i, "6:"); INC(i, rc, 1400);
+        COPY(buf, i, peers2[j].ip, 4, 1400);
+        COPY(buf, i, &swapped, 2, 1400);
+    }
+    rc = snprintf(buf + i, 1400 - i, "ee1:t%d:", tid_len);
+    INC(i, rc, 1400);
+    COPY(buf, i, tid, tid_len, 1400);
+    rc = snprintf(buf + i, 2048 - i, "1:y1:re"); INC(i, rc, 2048);
+    return sendto(s, buf, i, 0, sa, salen);
+
+ fail:
+    errno = ENOSPC;
+    return -1;
+}
+
+int
+send_peer_announced(int s, struct sockaddr *sa, int salen,
+                    unsigned char *tid, int tid_len)
+{
+    char buf[512];
+    int i = 0, rc;
+
+    rc = snprintf(buf + i, 512 - i, "d1:rd2:id20:"); INC(i, rc, 512);
+    COPY(buf, i, myid, 20, 512);
+    rc = snprintf(buf + i, 512 - i, "e1:t%d:", tid_len);
+    INC(i, rc, 512);
+    COPY(buf, i, tid, tid_len, 512);
+    rc = snprintf(buf + i, 2048 - i, "1:y1:re"); INC(i, rc, 2048);
+    return sendto(s, buf, i, 0, sa, salen);
+
+ fail:
+    errno = ENOSPC;
+    return -1;
+}
+
+static int
+parse_message(const unsigned char *buf, int buflen,
+              unsigned char *tid_return, int *tid_len,
+              unsigned char *id_return, unsigned char *info_hash_return,
+              unsigned char *target_return, unsigned short *port_return,
+              unsigned char *token_return, int *token_len,
+              unsigned char *nodes_return, int *nodes_len,
+              const unsigned char *values_return, int *values_len)
+{
+    const unsigned char *p;
+
+    if(tid_return) {
+        p = memmem(buf, buflen, "1:t", 3);
+        if(p) {
+            long l;
+            char *q;
+            l = strtol((char*)p + 3, &q, 10);
+            if(q && *q == ':' && l > 0 && l < *tid_len) {
+                memcpy(tid_return, q + 1, l);
+                *tid_len = l;
+            } else
+                *tid_len = 0;
+        }
+    }
+    if(id_return) {
+        p = memmem(buf, buflen, "2:id20:", 7);
+        if(p) {
+            memcpy(id_return, p + 7, 20);
+        } else {
+            memset(id_return, 0, 20);
+        }
+    }
+    if(info_hash_return) {
+        p = memmem(buf, buflen, "9:info_hash20:", 14);
+        if(p) {
+            memcpy(info_hash_return, p + 14, 20);
+        } else {
+            memset(info_hash_return, 0, 20);
+        }
+    }
+    if(port_return) {
+        p = memmem(buf, buflen, "porti", 5);
+        if(p) {
+            long l;
+            char *q;
+            l = strtol((char*)p + 5, &q, 10);
+            if(q && *q == 'e' && l > 0 && l < 0x10000)
+                *port_return = l;
+            else
+                *port_return = 0;
+        } else
+            *port_return = 0;
+    }
+    if(target_return) {
+        p = memmem(buf, buflen, "6:target20:", 11);
+        if(p) {
+            memcpy(target_return, p + 11, 20);
+        } else {
+            memset(target_return, 0, 20);
+        }
+    }
+    if(token_return) {
+        p = memmem(buf, buflen, "5:token", 7);
+        if(p) {
+            long l;
+            char *q;
+            l = strtol((char*)p + 7, &q, 10);
+            if(q && *q == ':' && l > 0 && l < *token_len) {
+                memcpy(token_return, q + 1, l);
+                *token_len = l;
+            } else
+                *token_len = 0;
+        } else
+            *token_len = 0;
+    }
+        
+    if(nodes_return) {
+        p = memmem(buf, buflen, "5:nodes", 7);
+        if(p) {
+            long l;
+            char *q;
+            l = strtol((char*)p + 7, &q, 10);
+            if(q && *q == ':' && l > 0 && l < *nodes_len) {
+                memcpy(nodes_return, q + 1, l);
+                *nodes_len = l;
+            } else
+                *nodes_len = 0;
+        } else
+            *nodes_len = 0;
+    }
+
+    if(values_return) {
+        p = memmem(buf, buflen, "6:valuesl", 9);
+        if(p) {
+            int i = p - buf + 9;
+            int j = 0;
+            while(buf[i] == '6' && buf[i + 1] == ':' && i + 8 < buflen) {
+                if(j + 6 > *values_len)
+                    break;
+                memcpy((char*)values_return + j, buf + i + 2, 6);
+                i += 8;
+                j += 6;
+            }
+            if(buf[i] != 'e')
+                debugf("Eek... unexpected end for values.\n");
+            *values_len = j;
+        } else {
+            *values_len = 0;
+        }
+    }
+                
+    if(memmem(buf, buflen, "1:y1:r", 6))
+        return REPLY;
+    if(!memmem(buf, buflen, "1:y1:q", 6))
+        return -1;
+    if(memmem(buf, buflen, "1:q4:ping", 9))
+        return PING;
+    if(memmem(buf, buflen, "1:q9:find_node", 14))
+       return FIND_NODE;
+    if(memmem(buf, buflen, "1:q9:get_peers", 14))
+        return GET_PEERS;
+    if(memmem(buf, buflen, "1:q13:announce_peer", 19))
+       return ANNOUNCE_PEER;
+    return -1;
+}
diff --git a/third-party/dht/dht.h b/third-party/dht/dht.h
new file mode 100644 (file)
index 0000000..2d9cc35
--- /dev/null
@@ -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);
diff --git a/third-party/dht/makelog b/third-party/dht/makelog
new file mode 100644 (file)
index 0000000..9075906
--- /dev/null
@@ -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