2010-05-01 16:04:00 +00:00
|
|
|
/*
|
|
|
|
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,
|
2010-12-27 19:18:17 +00:00
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
2010-05-01 16:04:00 +00:00
|
|
|
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 <signal.h> /* sig_atomic_t */
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <unistd.h> /* close() */
|
2010-05-31 17:03:51 +00:00
|
|
|
#include <ctype.h> /* toupper() */
|
2010-06-30 21:24:36 +00:00
|
|
|
#ifdef WIN32
|
|
|
|
#include <w32api.h>
|
|
|
|
#define WINDOWS WindowsXP /* freeaddrinfo(),getaddrinfo(),getnameinfo() */
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <ws2tcpip.h>
|
|
|
|
typedef uint16_t in_port_t; /* all missing */
|
|
|
|
#else
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/socket.h> /* socket(), bind() */
|
|
|
|
#include <netinet/in.h> /* sockaddr_in */
|
|
|
|
#endif
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
/* third party */
|
2010-12-20 02:07:51 +00:00
|
|
|
#include <event2/event.h>
|
|
|
|
#include <event2/util.h>
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
/* libT */
|
|
|
|
#include "transmission.h"
|
|
|
|
#include "crypto.h"
|
|
|
|
#include "net.h"
|
|
|
|
#include "peer-mgr.h" /* tr_peerMgrAddPex() */
|
|
|
|
#include "session.h"
|
|
|
|
#include "torrent.h" /* tr_torrentFindFromHash() */
|
2010-05-08 08:42:45 +00:00
|
|
|
#include "tr-lpd.h"
|
2010-05-01 16:04:00 +00:00
|
|
|
#include "utils.h"
|
|
|
|
#include "version.h"
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Local Peer Discovery
|
2010-05-08 08:42:45 +00:00
|
|
|
* @file tr-lpd.c
|
2010-05-01 16:04:00 +00:00
|
|
|
*
|
2010-05-08 08:42:45 +00:00
|
|
|
* This module implements the Local Peer Discovery (LPD) protocol as supported by the
|
2010-12-27 19:18:17 +00:00
|
|
|
* uTorrent client application. A typical LPD datagram is 119 bytes long.
|
2010-05-01 16:04:00 +00:00
|
|
|
*
|
|
|
|
* $Id$
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void event_callback( int, short, void* );
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
static int lpd_socket; /**<separate multicast receive socket */
|
|
|
|
static int lpd_socket2; /**<and multicast send socket */
|
2010-12-20 02:07:51 +00:00
|
|
|
static struct event * lpd_event = NULL;
|
2010-05-08 08:42:45 +00:00
|
|
|
static tr_port lpd_port;
|
2010-05-01 16:04:00 +00:00
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
static tr_torrent* lpd_torStaticType UNUSED; /* just a helper for static type analysis */
|
2010-05-01 16:04:00 +00:00
|
|
|
static tr_session* session;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
enum { lpd_maxDatagramLength = 200 }; /**<the size an LPD datagram must not exceed */
|
|
|
|
const char lpd_mcastGroup[] = "239.192.152.143"; /**<LPD multicast group */
|
|
|
|
const int lpd_mcastPort = 6771; /**<LPD source and destination UPD port */
|
|
|
|
static struct sockaddr_in lpd_mcastAddr; /**<initialized from the above constants in tr_lpdInit */
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Protocol-related information carried by a Local Peer Discovery packet */
|
2010-05-08 08:42:45 +00:00
|
|
|
struct lpd_protocolVersion
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
int major, minor;
|
|
|
|
};
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
enum lpd_enumTimeToLive {
|
|
|
|
lpd_ttlSameSubnet = 1,
|
|
|
|
lpd_ttlSameSite = 32,
|
|
|
|
lpd_ttlSameRegion = 64,
|
|
|
|
lpd_ttlSameContinent = 128,
|
|
|
|
lpd_ttlUnrestricted = 255
|
2010-05-01 16:04:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
2010-05-08 08:42:45 +00:00
|
|
|
lpd_announceInterval = 4 * 60, /**<4 min announce interval per torrent */
|
|
|
|
lpd_announceScope = lpd_ttlSameSubnet /**<the maximum scope for LPD datagrams */
|
2010-05-01 16:04:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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 */
|
2010-05-08 08:42:45 +00:00
|
|
|
enum { lpd_announceCapFactor = 10 };
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @ingroup DoS
|
|
|
|
* @brief number of unsolicited messages during the last HK interval
|
|
|
|
* @remark counts downwards */
|
2010-05-08 08:42:45 +00:00
|
|
|
static int lpd_unsolicitedMsgCounter;
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @def CRLF
|
2010-05-08 08:42:45 +00:00
|
|
|
* @brief a line-feed, as understood by the LPD protocol */
|
2010-05-01 16:04:00 +00:00
|
|
|
#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
|
2010-05-08 08:42:45 +00:00
|
|
|
* the lpd_protocolVersion structure.
|
2010-05-01 16:04:00 +00:00
|
|
|
*/
|
2010-05-08 08:42:45 +00:00
|
|
|
static const char* lpd_extractHeader( const char* s, struct lpd_protocolVersion* const ver )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
int major = -1, minor = -1;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
assert( s != NULL );
|
|
|
|
len = strlen( s );
|
|
|
|
|
|
|
|
/* something might be rotten with this chunk of data */
|
2010-05-08 08:42:45 +00:00
|
|
|
if( len == 0 || len > lpd_maxDatagramLength )
|
2010-05-01 16:04:00 +00:00
|
|
|
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"
|
|
|
|
*/
|
2010-05-08 08:42:45 +00:00
|
|
|
static int lpd_extractParam( const char* const str, const char* const name, int n, char* const val )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
/* 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 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.
|
|
|
|
*
|
2010-05-08 08:42:45 +00:00
|
|
|
* @remark Since the LPD service does not use another protocol family yet, this code is
|
2010-05-01 16:04:00 +00:00
|
|
|
* IPv4 only for the time being.
|
|
|
|
*/
|
2010-05-08 08:42:45 +00:00
|
|
|
int tr_lpdInit( tr_session* ss, tr_address* tr_addr UNUSED )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
struct ip_mreq mcastReq;
|
|
|
|
const int opt_on = 1, opt_off = 0;
|
|
|
|
|
|
|
|
if( session ) /* already initialized */
|
|
|
|
return -1;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
assert( lpd_announceInterval > 0 );
|
|
|
|
assert( lpd_announceScope > 0 );
|
2010-05-01 16:04:00 +00:00
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
lpd_port = tr_sessionGetPeerPort( ss );
|
|
|
|
if( lpd_port <= 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
return -1;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
tr_ndbg( "LPD", "Initialising Local Peer Discovery" );
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
/* setup datagram socket (receive) */
|
|
|
|
{
|
2010-05-08 08:42:45 +00:00
|
|
|
lpd_socket = socket( PF_INET, SOCK_DGRAM, 0 );
|
|
|
|
if( lpd_socket < 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
goto fail;
|
|
|
|
|
2010-10-28 23:19:53 +00:00
|
|
|
if( evutil_make_socket_nonblocking( lpd_socket ) < 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
goto fail;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
if( setsockopt( lpd_socket, SOL_SOCKET, SO_REUSEADDR,
|
2010-05-01 16:04:00 +00:00
|
|
|
&opt_on, sizeof opt_on ) < 0 )
|
|
|
|
goto fail;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
memset( &lpd_mcastAddr, 0, sizeof lpd_mcastAddr );
|
|
|
|
lpd_mcastAddr.sin_family = AF_INET;
|
|
|
|
lpd_mcastAddr.sin_port = htons( lpd_mcastPort );
|
|
|
|
if( inet_pton( lpd_mcastAddr.sin_family, lpd_mcastGroup,
|
|
|
|
&lpd_mcastAddr.sin_addr ) < 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
goto fail;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
if( bind( lpd_socket, (struct sockaddr*) &lpd_mcastAddr,
|
|
|
|
sizeof lpd_mcastAddr ) < 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
goto fail;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
/* we want to join that LPD multicast group */
|
2010-05-01 16:04:00 +00:00
|
|
|
memset( &mcastReq, 0, sizeof mcastReq );
|
2010-05-08 08:42:45 +00:00
|
|
|
mcastReq.imr_multiaddr = lpd_mcastAddr.sin_addr;
|
2010-05-01 16:04:00 +00:00
|
|
|
mcastReq.imr_interface.s_addr = htonl( INADDR_ANY );
|
2010-05-08 08:42:45 +00:00
|
|
|
if( setsockopt( lpd_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP,
|
2010-05-01 16:04:00 +00:00
|
|
|
&mcastReq, sizeof mcastReq ) < 0 )
|
|
|
|
goto fail;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
if( setsockopt( lpd_socket, IPPROTO_IP, IP_MULTICAST_LOOP,
|
2010-05-01 16:04:00 +00:00
|
|
|
&opt_off, sizeof opt_off ) < 0 )
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* setup datagram socket (send) */
|
|
|
|
{
|
2010-05-08 08:42:45 +00:00
|
|
|
const unsigned char scope = lpd_announceScope;
|
2010-05-01 16:04:00 +00:00
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
lpd_socket2 = socket( PF_INET, SOCK_DGRAM, 0 );
|
|
|
|
if( lpd_socket2 < 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
goto fail;
|
|
|
|
|
2010-10-28 23:19:53 +00:00
|
|
|
if( evutil_make_socket_nonblocking( lpd_socket2 ) < 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* configure outbound multicast TTL */
|
2010-05-08 08:42:45 +00:00
|
|
|
if( setsockopt( lpd_socket2, IPPROTO_IP, IP_MULTICAST_TTL,
|
2010-05-01 16:04:00 +00:00
|
|
|
&scope, sizeof scope ) < 0 )
|
|
|
|
goto fail;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
if( setsockopt( lpd_socket2, IPPROTO_IP, IP_MULTICAST_LOOP,
|
2010-05-01 16:04:00 +00:00
|
|
|
&opt_off, sizeof opt_off ) < 0 )
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
session = ss;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
/* Note: lpd_unsolicitedMsgCounter remains 0 until the first timeout event, thus
|
2010-05-01 16:04:00 +00:00
|
|
|
* any announcement received during the initial interval will be discarded. */
|
|
|
|
|
2010-12-24 08:58:41 +00:00
|
|
|
lpd_event = event_new( ss->event_base, lpd_socket, EV_READ | EV_PERSIST, event_callback, NULL );
|
2010-12-20 02:07:51 +00:00
|
|
|
event_add( lpd_event, NULL );
|
2010-05-01 16:04:00 +00:00
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
tr_ndbg( "LPD", "Local Peer Discovery initialised" );
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
{
|
|
|
|
const int save = errno;
|
2010-05-08 08:42:45 +00:00
|
|
|
close( lpd_socket );
|
|
|
|
close( lpd_socket2 );
|
|
|
|
lpd_socket = lpd_socket2 = -1;
|
2010-05-01 16:04:00 +00:00
|
|
|
session = NULL;
|
2010-05-08 08:42:45 +00:00
|
|
|
tr_ndbg( "LPD", "LPD initialisation failed (errno = %d)", save );
|
2010-05-01 16:04:00 +00:00
|
|
|
errno = save;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
void tr_lpdUninit( tr_session* ss )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
if( session != ss )
|
|
|
|
return;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
tr_ndbg( "LPD", "Uninitialising Local Peer Discovery" );
|
2010-05-01 16:04:00 +00:00
|
|
|
|
2010-12-20 02:07:51 +00:00
|
|
|
event_free( lpd_event );
|
|
|
|
lpd_event = NULL;
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
/* just shut down, we won't remember any former nodes */
|
2010-05-08 08:42:45 +00:00
|
|
|
EVUTIL_CLOSESOCKET( lpd_socket );
|
|
|
|
EVUTIL_CLOSESOCKET( lpd_socket2 );
|
|
|
|
tr_ndbg( "LPD", "Done uninitialising Local Peer Discovery" );
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
session = NULL;
|
|
|
|
}
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
tr_bool tr_lpdEnabled( const tr_session* ss )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
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
|
2010-05-08 08:42:45 +00:00
|
|
|
* functions. In any other respect, lpd_consistencyCheck is an orphaned function.
|
2010-05-01 16:04:00 +00:00
|
|
|
*/
|
2010-05-08 08:42:45 +00:00
|
|
|
static inline void lpd_consistencyCheck( void )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
/* if the following check fails, the definition of a hash string has changed
|
2010-05-08 08:42:45 +00:00
|
|
|
* without our knowledge; revise string handling in functions tr_lpdSendAnnounce
|
|
|
|
* and tr_lpdConsiderAnnounce. However, the code is designed to function as long
|
2010-05-01 16:04:00 +00:00
|
|
|
* as interfaces to the rest of the lib remain compatible with char* strings. */
|
2010-05-08 08:42:45 +00:00
|
|
|
STATIC_ASSERT( sizeof(lpd_torStaticType->info.hashString[0]) == sizeof(char) );
|
2010-05-01 16:04:00 +00:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @endcond */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2010-05-08 08:42:45 +00:00
|
|
|
* @defgroup LdsProto LPD announcement processing
|
2010-05-01 16:04:00 +00:00
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Announce the given torrent on the local network
|
|
|
|
*
|
|
|
|
* @param[in] t Torrent to announce
|
|
|
|
* @return Returns TRUE on success
|
|
|
|
*
|
2010-05-08 08:42:45 +00:00
|
|
|
* Send a query for torrent t out to the LPD multicast group (or the LAN, for that
|
2010-12-27 19:18:17 +00:00
|
|
|
* matter). A listening client on the same network might react by adding us to his
|
2010-05-01 16:04:00 +00:00
|
|
|
* peer pool for torrent t.
|
|
|
|
*/
|
2010-05-08 08:42:45 +00:00
|
|
|
tr_bool tr_lpdSendAnnounce( const tr_torrent* t )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
2010-05-31 17:03:51 +00:00
|
|
|
size_t i;
|
2010-05-01 16:04:00 +00:00
|
|
|
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 )];
|
2010-05-08 08:42:45 +00:00
|
|
|
char query[lpd_maxDatagramLength + 1] = { };
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
if( t == NULL )
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
/* make sure the hash string is normalized, just in case */
|
2010-05-31 17:03:51 +00:00
|
|
|
for( i = 0; i < sizeof hashString; i++ )
|
2010-05-01 16:04:00 +00:00
|
|
|
hashString[i] = toupper( t->info.hashString[i] );
|
|
|
|
|
|
|
|
/* prepare a zero-terminated announce message */
|
2010-05-08 08:42:45 +00:00
|
|
|
snprintf( query, lpd_maxDatagramLength + 1, fmt, 1, 1,
|
|
|
|
lpd_mcastGroup, lpd_mcastPort, lpd_port, hashString );
|
2010-05-01 16:04:00 +00:00
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
/* actually send the query out using [lpd_socket2] */
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
const int len = strlen( query );
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
/* destination address info has already been set up in tr_lpdInit(),
|
2010-05-01 16:04:00 +00:00
|
|
|
* so we refrain from preparing another sockaddr_in here */
|
2010-05-08 08:42:45 +00:00
|
|
|
int res = sendto( lpd_socket2, query, len, 0,
|
|
|
|
(const struct sockaddr*) &lpd_mcastAddr, sizeof lpd_mcastAddr );
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
if( res != len )
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
tr_tordbg( t, "LPD announce message away" );
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*
|
2010-05-08 08:42:45 +00:00
|
|
|
* @note The port information gets added to the peer structure if tr_lpdConsiderAnnounce
|
2010-12-27 19:18:17 +00:00
|
|
|
* is able to extract the necessary information from the announce message. That is, if
|
2010-05-01 16:04:00 +00:00
|
|
|
* return != 0, the caller may retrieve the value from the passed structure.
|
|
|
|
*/
|
2010-05-08 08:42:45 +00:00
|
|
|
static int tr_lpdConsiderAnnounce( tr_pex* peer, const char* const msg )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
maxValueLen = 25,
|
2010-05-08 08:42:45 +00:00
|
|
|
maxHashLen = lengthof(lpd_torStaticType->info.hashString)
|
2010-05-01 16:04:00 +00:00
|
|
|
};
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
struct lpd_protocolVersion ver = { -1, -1 };
|
2010-05-01 16:04:00 +00:00
|
|
|
char value[maxValueLen] = { };
|
|
|
|
char hashString[maxHashLen] = { };
|
|
|
|
int res = 0, peerPort = 0;
|
|
|
|
|
|
|
|
if( peer != NULL && msg != NULL )
|
|
|
|
{
|
|
|
|
tr_torrent* tor = NULL;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
const char* params = lpd_extractHeader( msg, &ver );
|
2010-05-01 16:04:00 +00:00
|
|
|
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 */
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
if( lpd_extractParam( params, "Port", maxValueLen, value ) == 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* determine announced peer port, refuse if value too large */
|
2010-06-30 16:40:19 +00:00
|
|
|
if( sscanf( value, "%d", &peerPort ) != 1 || peerPort > (in_port_t)-1 )
|
2010-05-01 16:04:00 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
peer->port = htons( peerPort );
|
|
|
|
res = -1; /* signal caller side-effect to peer->port via return != 0 */
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
if( lpd_extractParam( params, "Infohash", maxHashLen, hashString ) == 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
return res;
|
|
|
|
|
|
|
|
tor = tr_torrentFindFromHashString( session, hashString );
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
if( tr_isTorrent( tor ) && tr_torrentAllowsLPD( tor ) )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
/* we found a suitable peer, add it to the torrent */
|
2010-05-08 08:42:45 +00:00
|
|
|
tr_peerMgrAddPex( tor, TR_PEER_FROM_LPD, peer, -1 );
|
|
|
|
tr_tordbg( tor, "Learned %d local peer from LPD (%s:%u)",
|
2010-05-01 16:04:00 +00:00
|
|
|
1, inet_ntoa( peer->addr.addr.addr4 ), peerPort );
|
|
|
|
|
|
|
|
/* periodic reconnectPulse() deals with the rest... */
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
2010-05-08 08:42:45 +00:00
|
|
|
tr_ndbg( "LPD", "Cannot serve torrent #%s", hashString );
|
2010-05-01 16:04:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @} */
|
|
|
|
|
|
|
|
/**
|
2010-05-08 08:42:45 +00:00
|
|
|
* @note Since it possible for tr_lpdAnnounceMore to get called from outside the LPD module,
|
2010-05-01 16:04:00 +00:00
|
|
|
* the function needs to be informed of the externally employed housekeeping interval.
|
2010-05-08 08:42:45 +00:00
|
|
|
* Further, by setting interval to zero (or negative) the caller may actually disable LPD
|
2010-05-01 16:04:00 +00:00
|
|
|
* announces on a per-interval basis.
|
|
|
|
*/
|
2010-05-08 08:42:45 +00:00
|
|
|
int tr_lpdAnnounceMore( const time_t now, const int interval )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
tr_torrent* tor = NULL;
|
|
|
|
int announcesSent = 0;
|
|
|
|
|
|
|
|
if( !tr_isSession( session ) )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
while(( tor = tr_torrentNext( session, tor ) )
|
2010-05-08 08:42:45 +00:00
|
|
|
&& tr_sessionAllowsLPD( session ) )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
if( tr_isTorrent( tor ) )
|
|
|
|
{
|
2010-05-08 08:42:45 +00:00
|
|
|
if( !tr_torrentAllowsLPD( tor ) || (
|
2010-05-01 16:04:00 +00:00
|
|
|
( tr_torrentGetActivity( tor ) != TR_STATUS_DOWNLOAD ) &&
|
|
|
|
( tr_torrentGetActivity( tor ) != TR_STATUS_SEED ) ) )
|
|
|
|
continue;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
if( tor->lpdAnnounceAt <= now )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
2010-05-08 08:42:45 +00:00
|
|
|
if( tr_lpdSendAnnounce( tor ) )
|
2010-05-01 16:04:00 +00:00
|
|
|
announcesSent++;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
tor->lpdAnnounceAt = now + lpd_announceInterval;
|
2010-05-01 16:04:00 +00:00
|
|
|
break; /* that's enough; for this interval */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* perform housekeeping for the flood protection mechanism */
|
|
|
|
{
|
2010-05-08 08:42:45 +00:00
|
|
|
const int maxAnnounceCap = interval * lpd_announceCapFactor;
|
2010-05-01 16:04:00 +00:00
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
if( lpd_unsolicitedMsgCounter < 0 )
|
|
|
|
tr_ninf( "LPD", "Dropped %d announces in the last interval (max. %d "
|
|
|
|
"allowed)", -lpd_unsolicitedMsgCounter, maxAnnounceCap );
|
2010-05-01 16:04:00 +00:00
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
lpd_unsolicitedMsgCounter = maxAnnounceCap;
|
2010-05-01 16:04:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return announcesSent;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Processing of timeout notifications and incoming data on the socket
|
2010-05-08 08:42:45 +00:00
|
|
|
* @note maximum rate of read events is limited according to @a lpd_maxAnnounceCap
|
2010-05-01 16:04:00 +00:00
|
|
|
* @see DoS */
|
|
|
|
static void event_callback( int s UNUSED, short type, void* ignore UNUSED )
|
|
|
|
{
|
|
|
|
assert( tr_isSession( session ) );
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
/* do not allow announces to be processed if LPD is disabled */
|
|
|
|
if( !tr_sessionAllowsLPD( session ) )
|
2010-05-01 16:04:00 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
if( ( type & EV_READ ) != 0 )
|
|
|
|
{
|
|
|
|
struct sockaddr_in foreignAddr;
|
|
|
|
int addrLen = sizeof foreignAddr;
|
|
|
|
|
|
|
|
/* be paranoid enough about zero terminating the foreign string */
|
2010-05-08 08:42:45 +00:00
|
|
|
char foreignMsg[lpd_maxDatagramLength + 1] = { };
|
2010-05-01 16:04:00 +00:00
|
|
|
|
|
|
|
/* process local announcement from foreign peer */
|
2010-05-08 08:42:45 +00:00
|
|
|
int res = recvfrom( lpd_socket, foreignMsg, lpd_maxDatagramLength,
|
2010-05-01 16:04:00 +00:00
|
|
|
0, (struct sockaddr*) &foreignAddr, (socklen_t*) &addrLen );
|
|
|
|
|
|
|
|
/* besides, do we get flooded? then bail out! */
|
2010-05-08 08:42:45 +00:00
|
|
|
if( --lpd_unsolicitedMsgCounter < 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
return;
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
if( res > 0 && res <= lpd_maxDatagramLength )
|
2010-05-01 16:04:00 +00:00
|
|
|
{
|
|
|
|
struct tr_pex foreignPeer =
|
|
|
|
{
|
|
|
|
.port = 0, /* the peer-to-peer port is yet unknown */
|
|
|
|
.flags = 0
|
|
|
|
};
|
|
|
|
|
|
|
|
foreignPeer.addr.addr.addr4 = foreignAddr.sin_addr;
|
2010-05-08 08:42:45 +00:00
|
|
|
if( tr_lpdConsiderAnnounce( &foreignPeer, foreignMsg ) != 0 )
|
2010-05-01 16:04:00 +00:00
|
|
|
return; /* OK so far, no log message */
|
|
|
|
}
|
|
|
|
|
2010-05-08 08:42:45 +00:00
|
|
|
tr_ndbg( "LPD", "Discarded invalid multicast message" );
|
2010-05-01 16:04:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|