(trunk) #3060 -- Local Peer Discovery patch from Eszet

This commit is contained in:
Charles Kerr 2010-05-01 16:04:00 +00:00
parent e141c10f71
commit 5a34347f4f
12 changed files with 804 additions and 6 deletions

View File

@ -85,6 +85,8 @@ static const struct tr_option options[] =
{ 800, "paused", "Pause all torrents on startup", NULL, 0, NULL },
{ 'o', "dht", "Enable distributed hash tables (DHT)", "o", 0, NULL },
{ 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", 0, NULL },
{ 'z', "lds", "Enable local peer discovery (LDS)", "z", 0, NULL },
{ 'Z', "no-lds", "Disable local peer discovery (LDS)", "Z", 0, NULL },
{ 'P', "peerport", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "P", 1, "<port>" },
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
{ 'M', "no-portmap", "Disable portmapping", "M", 0, NULL },
@ -406,6 +408,10 @@ main( int argc, char ** argv )
case 954:
tr_bencDictAddBool( &settings, TR_PREFS_KEY_RATIO_ENABLED, FALSE );
break;
case 'z': tr_bencDictAddBool( &settings, TR_PREFS_KEY_LDS_ENABLED, TRUE );
break;
case 'Z': tr_bencDictAddBool( &settings, TR_PREFS_KEY_LDS_ENABLED, FALSE );
break;
default: showUsage( );
break;
}

View File

@ -55,6 +55,7 @@ libtransmission_a_SOURCES = \
torrent-ctor.c \
torrent-magnet.c \
tr-dht.c \
tr-lds.c \
tr-getopt.c \
trevent.c \
upnp.c \
@ -107,6 +108,7 @@ noinst_HEADERS = \
tr-getopt.h \
transmission.h \
tr-dht.h \
tr-lds.h \
trevent.h \
upnp.h \
utils.h \

View File

@ -24,6 +24,7 @@
#include "publish.h"
#include "session.h"
#include "tr-dht.h"
#include "tr-lds.h"
#include "torrent.h"
#include "utils.h"
#include "web.h"
@ -65,7 +66,10 @@ enum
/* how long to put slow (nonresponsive) trackers in the penalty box */
SLOW_HOST_PENALTY_SECS = ( 60 * 10 ),
UPKEEP_INTERVAL_SECS = 1
UPKEEP_INTERVAL_SECS = 1,
/* this is an upper limit for the frequency of LDS announces */
LDS_HOUSEKEEPING_INTERVAL_SECS = 30
};
@ -202,6 +206,7 @@ typedef struct tr_announcer
tr_session * session;
struct event * upkeepTimer;
int slotsAvailable;
time_t ldsHouseKeepingAt;
}
tr_announcer;
@ -231,11 +236,26 @@ getHost( tr_announcer * announcer, const char * url )
static void
onUpkeepTimer( int foo UNUSED, short bar UNUSED, void * vannouncer );
static inline time_t
calcRescheduleWithJitter( const int minPeriod )
{
const double jitterFac = 0.1;
assert( minPeriod > 0 );
return tr_time()
+ minPeriod
+ tr_cryptoWeakRandInt( (int) ( minPeriod * jitterFac ) + 1 );
}
void
tr_announcerInit( tr_session * session )
{
tr_announcer * a;
const time_t relaxUntil =
calcRescheduleWithJitter( LDS_HOUSEKEEPING_INTERVAL_SECS / 3 );
assert( tr_isSession( session ) );
a = tr_new0( tr_announcer, 1 );
@ -243,6 +263,7 @@ tr_announcerInit( tr_session * session )
a->stops = TR_PTR_ARRAY_INIT;
a->session = session;
a->slotsAvailable = MAX_CONCURRENT_TASKS;
a->ldsHouseKeepingAt = relaxUntil;
a->upkeepTimer = tr_new0( struct event, 1 );
evtimer_set( a->upkeepTimer, onUpkeepTimer, a );
tr_timerAdd( a->upkeepTimer, UPKEEP_INTERVAL_SECS, 0 );
@ -1884,6 +1905,16 @@ fprintf( stderr, "[%s] announce.c has %d requests ready to send (announce: %d, s
}
}
}
/* Local Peer Discovery */
if( announcer->ldsHouseKeepingAt <= now )
{
tr_ldsAnnounceMore( now, LDS_HOUSEKEEPING_INTERVAL_SECS );
/* reschedule more LDS announces for ( the future + jitter ) */
announcer->ldsHouseKeepingAt =
calcRescheduleWithJitter( LDS_HOUSEKEEPING_INTERVAL_SECS );
}
}
static void

View File

@ -1508,6 +1508,7 @@ getDefaultShelfLife( uint8_t from )
case TR_PEER_FROM_DHT : return 60 * 60 * 3;
case TR_PEER_FROM_PEX : return 60 * 60 * 2;
case TR_PEER_FROM_RESUME : return 60 * 60;
case TR_PEER_FROM_LDS : return 10 * 60;
default : return 60 * 60;
}
}

View File

@ -1214,6 +1214,8 @@ sessionSet( tr_session * session,
tr_sessionSetPexEnabled( session, boolVal );
if( tr_bencDictFindBool( args_in, TR_PREFS_KEY_DHT_ENABLED, &boolVal ) )
tr_sessionSetDHTEnabled( session, boolVal );
if( tr_bencDictFindBool( args_in, TR_PREFS_KEY_LDS_ENABLED, &boolVal ) )
tr_sessionSetLDSEnabled( session, boolVal );
if( tr_bencDictFindBool( args_in, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, &boolVal ) )
tr_sessionSetPeerPortRandomOnStart( session, boolVal );
if( tr_bencDictFindInt( args_in, TR_PREFS_KEY_PEER_PORT, &i ) )
@ -1326,6 +1328,7 @@ sessionGet( tr_session * s,
tr_bencDictAddBool( d, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, tr_sessionIsIncompleteDirEnabled( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_PEX_ENABLED, tr_sessionIsPexEnabled( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_DHT_ENABLED, tr_sessionIsDHTEnabled( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_LDS_ENABLED, tr_sessionIsLDSEnabled( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, tr_sessionGetPeerPortRandomOnStart( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, tr_sessionIsPortForwardingEnabled( s ) );

View File

@ -42,6 +42,7 @@
#include "stats.h"
#include "torrent.h"
#include "tr-dht.h"
#include "tr-lds.h"
#include "trevent.h"
#include "utils.h"
#include "verify.h"
@ -244,6 +245,7 @@ tr_sessionGetDefaultSettings( const char * configDir UNUSED, tr_benc * d )
tr_bencDictReserve( d, 35 );
tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, FALSE );
tr_bencDictAddBool( d, TR_PREFS_KEY_DHT_ENABLED, TRUE );
tr_bencDictAddBool( d, TR_PREFS_KEY_LDS_ENABLED, FALSE );
tr_bencDictAddStr ( d, TR_PREFS_KEY_DOWNLOAD_DIR, tr_getDefaultDownloadDir( ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_DSPEED, 100 );
tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, FALSE );
@ -306,6 +308,7 @@ tr_sessionGetSettings( tr_session * s, struct tr_benc * d )
tr_bencDictReserve( d, 30 );
tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, tr_blocklistIsEnabled( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_DHT_ENABLED, s->isDHTEnabled );
tr_bencDictAddBool( d, TR_PREFS_KEY_LDS_ENABLED, s->isLDSEnabled );
tr_bencDictAddStr ( d, TR_PREFS_KEY_DOWNLOAD_DIR, s->downloadDir );
tr_bencDictAddInt ( d, TR_PREFS_KEY_DSPEED, tr_sessionGetSpeedLimit( s, TR_DOWN ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, tr_sessionIsSpeedLimited( s, TR_DOWN ) );
@ -625,6 +628,14 @@ tr_sessionInitImpl( void * vdata )
tr_dhtInit( session, &session->public_ipv4->addr );
}
if( session->isLDSEnabled )
{
if( tr_ldsInit( session, &session->public_ipv4->addr ) )
tr_ninf( "LDS", "Local Peer Discovery active" );
}
else
tr_ndbg( "LDS", "Local Peer Discovery disabled" );
/* cleanup */
tr_bencFree( &settings );
data->done = TRUE;
@ -667,6 +678,8 @@ sessionSetImpl( void * vdata )
tr_sessionSetPexEnabled( session, boolVal );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_DHT_ENABLED, &boolVal ) )
tr_sessionSetDHTEnabled( session, boolVal );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_LDS_ENABLED, &boolVal ) )
tr_sessionSetLDSEnabled( session, boolVal );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_ENCRYPTION, &i ) )
tr_sessionSetEncryption( session, i );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_PEER_SOCKET_TOS, &i ) )
@ -1551,6 +1564,9 @@ sessionCloseImpl( void * vsession )
free_incoming_peer_port( session );
if( session->isLDSEnabled )
tr_ldsUninit( session );
if( session->isDHTEnabled )
tr_dhtUninit( session );
@ -1785,6 +1801,29 @@ tr_sessionSetDHTEnabled( tr_session * session, tr_bool enabled )
tr_runInEventThread( session, toggleDHTImpl, session );
}
void
tr_sessionSetLDSEnabled( tr_session * session,
tr_bool enabled )
{
assert( tr_isSession( session ) );
session->isLDSEnabled = ( enabled != 0 );
}
tr_bool
tr_sessionIsLDSEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->isLDSEnabled;
}
tr_bool
tr_sessionAllowsLDS( const tr_session * session )
{
return tr_sessionIsLDSEnabled( session );
}
/***
****
***/

View File

@ -84,6 +84,7 @@ struct tr_session
tr_bool isPortRandom;
tr_bool isPexEnabled;
tr_bool isDHTEnabled;
tr_bool isLDSEnabled;
tr_bool isBlocklistEnabled;
tr_bool isProxyEnabled;
tr_bool isProxyAuthEnabled;
@ -179,6 +180,8 @@ struct tr_session
tr_bool tr_sessionAllowsDHT( const tr_session * session );
tr_bool tr_sessionAllowsLDS( const tr_session * session );
const char * tr_sessionFindTorrentFile( const tr_session * session,
const char * hashString );

View File

@ -940,7 +940,7 @@ tr_torrentSetVerifyState( tr_torrent * tor, tr_verify_state state )
tor->anyDate = tr_time( );
}
static tr_torrent_activity
tr_torrent_activity
tr_torrentGetActivity( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
@ -1400,6 +1400,7 @@ checkAndStartImpl( void * vtor )
tr_announcerTorrentStarted( tor );
tor->dhtAnnounceAt = now + tr_cryptoWeakRandInt( 20 );
tor->dhtAnnounce6At = now + tr_cryptoWeakRandInt( 20 );
tor->ldsAnnounceAt = now;
tr_peerMgrStartTorrent( tor );
}

View File

@ -130,6 +130,8 @@ tr_verify_state;
void tr_torrentSetVerifyState( tr_torrent * tor,
tr_verify_state state );
tr_torrent_activity tr_torrentGetActivity( tr_torrent * tor );
struct tr_incomplete_metadata;
/** @brief Torrent object */
@ -197,6 +199,8 @@ struct tr_torrent
time_t dhtAnnounce6At;
tr_bool dhtAnnounceInProgress;
tr_bool dhtAnnounce6InProgress;
time_t ldsAnnounceAt;
uint64_t downloadedCur;
uint64_t downloadedPrev;
@ -330,6 +334,13 @@ static inline tr_bool tr_torrentAllowsDHT( const tr_torrent * tor )
&& ( !tr_torrentIsPrivate( tor ) );
}
static inline tr_bool tr_torrentAllowsLDS( const tr_torrent * tor )
{
return ( tor != NULL )
&& ( tr_sessionAllowsLDS( tor->session ) )
&& ( !tr_torrentIsPrivate( tor ) );
}
static inline tr_bool tr_torrentIsPieceChecked( const tr_torrent * tor,
tr_piece_index_t i )
{

638
libtransmission/tr-lds.c Normal file
View File

@ -0,0 +1,638 @@
/*
Copyright (c) 2010 by Johannes Lieder
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.
*/
/* ansi */
#include <errno.h>
#include <stdio.h>
/* posix */
#include <netinet/in.h> /* sockaddr_in */
#include <signal.h> /* sig_atomic_t */
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h> /* socket(), bind() */
#include <unistd.h> /* close() */
#include <fcntl.h> /* fcntl(), O_NONBLOCK */
#include <ctype.h>
/* third party */
#include <event.h>
/* libT */
#include "transmission.h"
#include "crypto.h"
#include "net.h"
#include "peer-mgr.h" /* tr_peerMgrAddPex() */
#include "session.h"
#include "torrent.h" /* tr_torrentFindFromHash() */
#include "tr-lds.h"
#include "utils.h"
#include "version.h"
/**
* @brief Local Peer Discovery
* @file tr-lds.c
*
* This module implements the Local Peer Discovery (LDS) protocol as supported by the
* uTorrent client application. A typical LDS datagram is 119 bytes long.
*
* $Id$
*/
static void event_callback( int, short, void* );
static int lds_socket; /**<separate multicast receive socket */
static int lds_socket2; /**<and multicast send socket */
static struct event lds_event;
static tr_port lds_port;
static tr_torrent* lds_torStaticType UNUSED; /* just a helper for static type analysis */
static tr_session* session;
enum { lds_maxDatagramLength = 200 }; /**<the size an LDS datagram must not exceed */
const char lds_mcastGroup[] = "239.192.152.143"; /**<LDS multicast group */
const int lds_mcastPort = 6771; /**<LDS source and destination UPD port */
static struct sockaddr_in lds_mcastAddr; /**<initialized from the above constants in tr_ldsInit */
/**
* @brief Protocol-related information carried by a Local Peer Discovery packet */
struct lds_protocolVersion
{
int major, minor;
};
enum lds_enumTimeToLive {
lds_ttlSameSubnet = 1,
lds_ttlSameSite = 32,
lds_ttlSameRegion = 64,
lds_ttlSameContinent = 128,
lds_ttlUnrestricted = 255
};
enum {
lds_announceInterval = 4 * 60, /**<4 min announce interval per torrent */
lds_announceScope = lds_ttlSameSubnet /**<the maximum scope for LDS datagrams */
};
/**
* @defgroup DoS Message Flood Protection
* @{
* We want to have a means to protect the libtransmission backend against message
* flooding: the strategy is to cap event processing once more than ten messages
* per second (that is, taking the average over one of our housekeeping intervals)
* got into our processing handler.
* If we'd really hit the limit and start discarding events, we either joined an
* extremely crowded multicast group or a malevolent host is sending bogus data to
* our socket. In this situation, we rather miss some announcements than blocking
* the actual task.
* @}
*/
/**
* @ingroup DoS
* @brief allow at most ten messages per second (interval average)
* @note this constraint is only enforced once per housekeeping interval */
enum { lds_announceCapFactor = 10 };
/**
* @ingroup DoS
* @brief number of unsolicited messages during the last HK interval
* @remark counts downwards */
static int lds_unsolicitedMsgCounter;
/**
* @def CRLF
* @brief a line-feed, as understood by the LDS protocol */
#define CRLF "\r\n"
/**
* @defgroup HttpReqProc HTTP-style request handling
* @{
*/
/**
* @brief Checks for BT-SEARCH method and separates the parameter section
* @param[in] s The request string
* @param[out] ver If non-NULL, gets filled with protocol info from the request
* @return Returns a relative pointer to the beginning of the parameter section;
* if result is NULL, s was invalid and no information will be returned
* @remark Note that the returned pointer is only usable as long as the given
* pointer s is valid; that is, return storage is temporary.
*
* Determines whether the given string checks out to be a valid BT-SEARCH message.
* If so, the return value points to the beginning of the parameter section (note:
* in this case the function returns a character sequence beginning with CRLF).
* If parameter is not NULL, the declared protocol version is returned as part of
* the lds_protocolVersion structure.
*/
static const char* lds_extractHeader( const char* s, struct lds_protocolVersion* const ver )
{
int major = -1, minor = -1;
size_t len;
assert( s != NULL );
len = strlen( s );
/* something might be rotten with this chunk of data */
if( len == 0 || len > lds_maxDatagramLength )
return NULL;
/* now we can attempt to look up the BT-SEARCH header */
if( sscanf( s, "BT-SEARCH * HTTP/%d.%d" CRLF, &major, &minor ) != 2 )
return NULL;
if( major < 0 || minor < 0 )
return NULL;
{
/* a pair of blank lines at the end of the string, no place else */
const char* const two_blank = CRLF CRLF CRLF;
const char* const end = strstr( s, two_blank );
if( end == NULL || strlen( end ) > strlen( two_blank ) )
return NULL;
}
if( ver != NULL )
{
ver->major = major;
ver->minor = minor;
}
/* separate the header, begins with CRLF */
return strstr( s, CRLF );
}
/**
* @brief Return the value of a named parameter
*
* @param[in] str Input string of "\r\nName: Value" pairs without HTTP-style method part
* @param[in] name Name of parameter to extract
* @param[in] n Maximum available storage for value to return
* @param[out] val Output parameter for the actual value
* @return Returns 1 if value could be copied successfully
*
* Extracts the associated value of a named parameter from a HTTP-style header by
* performing the following steps:
* - assemble search string "\r\nName: " and locate position
* - copy back value from end to next "\r\n"
*/
static int lds_extractParam( const char* const str, const char* const name, int n, char* const val )
{
/* configure maximum length of search string here */
enum { maxLength = 30 };
char sstr[maxLength] = { };
const char* pos;
assert( str != NULL && name != NULL );
assert( val != NULL );
if( strlen( name ) > maxLength - strlen( CRLF ": " ) )
return 0;
/* compose the string token to search for */
snprintf( sstr, maxLength, CRLF "%s: ", name );
pos = strstr( str, sstr );
if( pos == NULL )
return 0; /* search was not successful */
{
const char* const beg = pos + strlen( sstr );
const char* const new_line = strstr( beg, CRLF );
/* the value is delimited by the next CRLF */
int len = new_line - beg;
/* if value string hits the length limit n,
* leave space for a trailing '\0' character */
if( len < n-- )
n = len;
strncpy( val, beg, n );
val[n] = 0;
}
/* we successfully returned the value string */
return 1;
}
/**
* @} */
/**
* @brief Configures additional capabilities for a socket */
static inline int lds_configureSocket( int sock, int add )
{
/* read-modify-write socket flags */
int flags = fcntl( sock, F_GETFL );
if( flags < 0 )
return -1;
if( fcntl( sock, F_SETFL, add | flags ) == -1 )
return -1;
return add;
}
/**
* @brief Initializes Local Peer Discovery for this node
*
* For the most part, this means setting up an appropriately configured multicast socket
* and event-based message handling.
*
* @remark Since the LDS service does not use another protocol family yet, this code is
* IPv4 only for the time being.
*/
int tr_ldsInit( tr_session* ss, tr_address* tr_addr UNUSED )
{
struct ip_mreq mcastReq;
const int opt_on = 1, opt_off = 0;
if( session ) /* already initialized */
return -1;
assert( lds_announceInterval > 0 );
assert( lds_announceScope > 0 );
lds_port = tr_sessionGetPeerPort( ss );
if( lds_port <= 0 )
return -1;
tr_ndbg( "LDS", "Initialising Local Peer Discovery" );
/* setup datagram socket (receive) */
{
lds_socket = socket( PF_INET, SOCK_DGRAM, 0 );
if( lds_socket < 0 )
goto fail;
/* enable non-blocking operation */
if( lds_configureSocket( lds_socket, O_NONBLOCK ) < 0 )
goto fail;
if( setsockopt( lds_socket, SOL_SOCKET, SO_REUSEADDR,
&opt_on, sizeof opt_on ) < 0 )
goto fail;
memset( &lds_mcastAddr, 0, sizeof lds_mcastAddr );
lds_mcastAddr.sin_family = AF_INET;
lds_mcastAddr.sin_port = htons( lds_mcastPort );
if( inet_pton( lds_mcastAddr.sin_family, lds_mcastGroup,
&lds_mcastAddr.sin_addr ) < 0 )
goto fail;
if( bind( lds_socket, (struct sockaddr*) &lds_mcastAddr,
sizeof lds_mcastAddr ) < 0 )
goto fail;
/* we want to join that LDS multicast group */
memset( &mcastReq, 0, sizeof mcastReq );
mcastReq.imr_multiaddr = lds_mcastAddr.sin_addr;
mcastReq.imr_interface.s_addr = htonl( INADDR_ANY );
if( setsockopt( lds_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mcastReq, sizeof mcastReq ) < 0 )
goto fail;
if( setsockopt( lds_socket, IPPROTO_IP, IP_MULTICAST_LOOP,
&opt_off, sizeof opt_off ) < 0 )
goto fail;
}
/* setup datagram socket (send) */
{
const unsigned char scope = lds_announceScope;
lds_socket2 = socket( PF_INET, SOCK_DGRAM, 0 );
if( lds_socket2 < 0 )
goto fail;
/* enable non-blocking operation */
if( lds_configureSocket( lds_socket2, O_NONBLOCK ) < 0 )
goto fail;
/* configure outbound multicast TTL */
if( setsockopt( lds_socket2, IPPROTO_IP, IP_MULTICAST_TTL,
&scope, sizeof scope ) < 0 )
goto fail;
if( setsockopt( lds_socket2, IPPROTO_IP, IP_MULTICAST_LOOP,
&opt_off, sizeof opt_off ) < 0 )
goto fail;
}
session = ss;
/* Note: lds_unsolicitedMsgCounter remains 0 until the first timeout event, thus
* any announcement received during the initial interval will be discarded. */
event_set( &lds_event, lds_socket, EV_READ | EV_PERSIST, event_callback, NULL );
event_add( &lds_event, NULL );
tr_ndbg( "LDS", "Local Peer Discovery initialised" );
return 1;
fail:
{
const int save = errno;
close( lds_socket );
close( lds_socket2 );
lds_socket = lds_socket2 = -1;
session = NULL;
tr_ndbg( "LDS", "LDS initialisation failed (errno = %d)", save );
errno = save;
}
return -1;
}
void tr_ldsUninit( tr_session* ss )
{
if( session != ss )
return;
tr_ndbg( "LDS", "Uninitialising Local Peer Discovery" );
event_del( &lds_event );
/* just shut down, we won't remember any former nodes */
EVUTIL_CLOSESOCKET( lds_socket );
EVUTIL_CLOSESOCKET( lds_socket2 );
tr_ndbg( "LDS", "Done uninitialising Local Peer Discovery" );
session = NULL;
}
tr_bool tr_ldsEnabled( const tr_session* ss )
{
return ss && ( ss == session );
}
/**
* @cond
* @brief Performs some (internal) software consistency checks at compile time.
* @remark Declared inline for the compiler not to allege us of feeding unused
* functions. In any other respect, lds_consistencyCheck is an orphaned function.
*/
static inline void lds_consistencyCheck( void )
{
/* if the following check fails, the definition of a hash string has changed
* without our knowledge; revise string handling in functions tr_ldsSendAnnounce
* and tr_ldsConsiderAnnounce. However, the code is designed to function as long
* as interfaces to the rest of the lib remain compatible with char* strings. */
STATIC_ASSERT( sizeof(lds_torStaticType->info.hashString[0]) == sizeof(char) );
}
/**
* @endcond */
/**
* @defgroup LdsProto LDS announcement processing
* @{
*/
/**
* @brief Announce the given torrent on the local network
*
* @param[in] t Torrent to announce
* @return Returns TRUE on success
*
* Send a query for torrent t out to the LDS multicast group (or the LAN, for that
* matter). A listening client on the same network might react by adding us to his
* peer pool for torrent t.
*/
tr_bool tr_ldsSendAnnounce( const tr_torrent* t )
{
const char fmt[] =
"BT-SEARCH * HTTP/%u.%u" CRLF
"Host: %s:%u" CRLF
"Port: %u" CRLF
"Infohash: %s" CRLF
CRLF
CRLF;
char hashString[lengthof( t->info.hashString )];
char query[lds_maxDatagramLength + 1] = { };
if( t == NULL )
return FALSE;
/* make sure the hash string is normalized, just in case */
for( size_t i = 0; i < sizeof hashString; i++ )
hashString[i] = toupper( t->info.hashString[i] );
/* prepare a zero-terminated announce message */
snprintf( query, lds_maxDatagramLength + 1, fmt, 1, 1,
lds_mcastGroup, lds_mcastPort, lds_port, hashString );
/* actually send the query out using [lds_socket2] */
{
const int len = strlen( query );
/* destination address info has already been set up in tr_ldsInit(),
* so we refrain from preparing another sockaddr_in here */
int res = sendto( lds_socket2, query, len, 0,
(const struct sockaddr*) &lds_mcastAddr, sizeof lds_mcastAddr );
if( res != len )
return FALSE;
}
tr_tordbg( t, "LDS announce message away" );
return TRUE;
}
/**
* @brief Process incoming unsolicited messages and add the peer to the announced
* torrent if all checks are passed.
*
* @param[in,out] peer Adress information of the peer to add
* @param[in] msg The announcement message to consider
* @return Returns 0 if any input parameter or the announce was invalid, 1 if the peer
* was successfully added, -1 if not; a non-null return value indicates a side-effect to
* the peer in/out parameter.
*
* @note The port information gets added to the peer structure if tr_ldsConsiderAnnounce
* is able to extract the necessary information from the announce message. That is, if
* return != 0, the caller may retrieve the value from the passed structure.
*/
static int tr_ldsConsiderAnnounce( tr_pex* peer, const char* const msg )
{
enum
{
maxValueLen = 25,
maxHashLen = lengthof(lds_torStaticType->info.hashString)
};
struct lds_protocolVersion ver = { -1, -1 };
char value[maxValueLen] = { };
char hashString[maxHashLen] = { };
int res = 0, peerPort = 0;
if( peer != NULL && msg != NULL )
{
tr_torrent* tor = NULL;
const char* params = lds_extractHeader( msg, &ver );
if( params == NULL || ver.major != 1 ) /* allow messages of protocol v1 */
return 0;
/* save the effort to check Host, which seems to be optional anyway */
if( lds_extractParam( params, "Port", maxValueLen, value ) == 0 )
return 0;
/* determine announced peer port, refuse if value too large */
if( sscanf( value, "%u", &peerPort ) != 1 || peerPort > (in_port_t)-1 )
return 0;
peer->port = htons( peerPort );
res = -1; /* signal caller side-effect to peer->port via return != 0 */
if( lds_extractParam( params, "Infohash", maxHashLen, hashString ) == 0 )
return res;
tor = tr_torrentFindFromHashString( session, hashString );
if( tr_isTorrent( tor ) && tr_torrentAllowsLDS( tor ) )
{
/* we found a suitable peer, add it to the torrent */
tr_peerMgrAddPex( tor, TR_PEER_FROM_LDS, peer, -1 );
tr_tordbg( tor, "Learned %d local peer from LDS (%s:%u)",
1, inet_ntoa( peer->addr.addr.addr4 ), peerPort );
/* periodic reconnectPulse() deals with the rest... */
return 1;
}
else
tr_ndbg( "LDS", "Cannot serve torrent #%s", hashString );
}
return res;
}
/**
* @} */
/**
* @note Since it possible for tr_ldsAnnounceMore to get called from outside the LDS module,
* the function needs to be informed of the externally employed housekeeping interval.
* Further, by setting interval to zero (or negative) the caller may actually disable LDS
* announces on a per-interval basis.
*/
int tr_ldsAnnounceMore( const time_t now, const int interval )
{
tr_torrent* tor = NULL;
int announcesSent = 0;
if( !tr_isSession( session ) )
return -1;
while(( tor = tr_torrentNext( session, tor ) )
&& tr_sessionAllowsLDS( session ) )
{
if( tr_isTorrent( tor ) )
{
if( !tr_torrentAllowsLDS( tor ) || (
( tr_torrentGetActivity( tor ) != TR_STATUS_DOWNLOAD ) &&
( tr_torrentGetActivity( tor ) != TR_STATUS_SEED ) ) )
continue;
if( tor->ldsAnnounceAt <= now )
{
if( tr_ldsSendAnnounce( tor ) )
announcesSent++;
tor->ldsAnnounceAt = now + lds_announceInterval;
break; /* that's enough; for this interval */
}
}
}
/* perform housekeeping for the flood protection mechanism */
{
const int maxAnnounceCap = interval * lds_announceCapFactor;
if( lds_unsolicitedMsgCounter < 0 )
tr_ninf( "LDS", "Dropped %d announces in the last interval (max. %d "
"allowed)", -lds_unsolicitedMsgCounter, maxAnnounceCap );
lds_unsolicitedMsgCounter = maxAnnounceCap;
}
return announcesSent;
}
/**
* @brief Processing of timeout notifications and incoming data on the socket
* @note maximum rate of read events is limited according to @a lds_maxAnnounceCap
* @see DoS */
static void event_callback( int s UNUSED, short type, void* ignore UNUSED )
{
assert( tr_isSession( session ) );
/* do not allow announces to be processed if LDS is disabled */
if( !tr_sessionAllowsLDS( session ) )
return;
if( ( type & EV_READ ) != 0 )
{
struct sockaddr_in foreignAddr;
int addrLen = sizeof foreignAddr;
/* be paranoid enough about zero terminating the foreign string */
char foreignMsg[lds_maxDatagramLength + 1] = { };
/* process local announcement from foreign peer */
int res = recvfrom( lds_socket, foreignMsg, lds_maxDatagramLength,
0, (struct sockaddr*) &foreignAddr, (socklen_t*) &addrLen );
/* besides, do we get flooded? then bail out! */
if( --lds_unsolicitedMsgCounter < 0 )
return;
if( res > 0 && res <= lds_maxDatagramLength )
{
struct tr_pex foreignPeer =
{
.port = 0, /* the peer-to-peer port is yet unknown */
.flags = 0
};
foreignPeer.addr.addr.addr4 = foreignAddr.sin_addr;
if( tr_ldsConsiderAnnounce( &foreignPeer, foreignMsg ) != 0 )
return; /* OK so far, no log message */
}
tr_ndbg( "LDS", "Discarded invalid multicast message" );
}
}

57
libtransmission/tr-lds.h Normal file
View File

@ -0,0 +1,57 @@
/*
Copyright (c) 2010 by Johannes Lieder
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.
*/
#ifndef _TR_LDS_H
#define _TR_LDS_H
/* $Id$ */
int tr_ldsInit( tr_session*, tr_address* );
void tr_ldsUninit( tr_session* );
tr_bool tr_ldsEnabled( const tr_session* );
tr_bool tr_ldsSendAnnounce( const tr_torrent* );
int tr_ldsAnnounceMore( const time_t, const int );
/**
* @defgroup Preproc Helper macros
* @{
*
* @def lengthof
* @brief returns the static length of a C array type
* @note A lower case macro name is tolerable here since this definition of lengthof()
* is intimately related to sizeof semantics.
* Meaningful return values are only guaranteed for true array types. */
#define lengthof( arr ) ( sizeof( *(arr) ) > 0 ? sizeof( arr ) / sizeof( *(arr) ) : 0 )
/**
* @def STATIC_ASSERT
* @brief This helper allows to perform static checks at compile time */
#define STATIC_ASSERT( x ) { const char static_check[( (x) ? 1 : -1 )] UNUSED; }
/**
* @} */
#endif /* _TR_LDS_H */

View File

@ -164,6 +164,7 @@ const char* tr_getDefaultDownloadDir( void );
#define TR_PREFS_KEY_BIND_ADDRESS_IPV6 "bind-address-ipv6"
#define TR_PREFS_KEY_BLOCKLIST_ENABLED "blocklist-enabled"
#define TR_PREFS_KEY_DHT_ENABLED "dht-enabled"
#define TR_PREFS_KEY_LDS_ENABLED "lds-enabled"
#define TR_PREFS_KEY_DOWNLOAD_DIR "download-dir"
#define TR_PREFS_KEY_ENCRYPTION "encryption"
#define TR_PREFS_KEY_INCOMPLETE_DIR "incomplete-dir"
@ -591,6 +592,10 @@ tr_bool tr_sessionIsDHTEnabled( const tr_session * session );
void tr_sessionSetDHTEnabled( tr_session * session, tr_bool );
tr_bool tr_sessionIsLDSEnabled( const tr_session * session );
void tr_sessionSetLDSEnabled( tr_session * session, tr_bool enabled );
void tr_sessionSetLazyBitfieldEnabled( tr_session * session,
tr_bool enabled );
@ -1649,10 +1654,11 @@ 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_DHT = 2, /* peers learnt from the DHT */
TR_PEER_FROM_RESUME = 3, /* peers read from the .resume file */
TR_PEER_FROM_PEX = 4, /* peers discovered via PEX */
TR_PEER_FROM_LTEP = 5, /* peer address provided in an LTEP handshake */
TR_PEER_FROM_LDS = 2, /* peers discovered by local announcements */
TR_PEER_FROM_DHT = 3, /* peers learnt from the DHT */
TR_PEER_FROM_RESUME = 4, /* peers read from the .resume file */
TR_PEER_FROM_PEX = 5, /* peers discovered via PEX */
TR_PEER_FROM_LTEP = 6, /* peer address provided in an LTEP handshake */
TR_PEER_FROM__MAX
};