(trunk) #3060 -- Local Peer Discovery patch from Eszet
This commit is contained in:
parent
e141c10f71
commit
5a34347f4f
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ) );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
|
|
@ -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 );
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
@ -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" );
|
||||
}
|
||||
}
|
||||
|
|
@ -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 */
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue