transmission/libtransmission/peerext.h

329 lines
9.3 KiB
C

/******************************************************************************
* $Id$
*
* Copyright (c) 2006-2007 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#define EXTENDED_HANDSHAKE_ID 0
#define EXTENDED_PEX_ID 1
static int
makeCommonPex( tr_torrent_t * tor, tr_peer_t * peer,
int ( *peerfunc )( tr_peertree_t *, benc_val_t * ),
const char * extrakey, benc_val_t * extraval,
char ** retbuf, int * retlen )
{
tr_peertree_t * sent, added, common;
int ii;
tr_peer_t * pp;
tr_peertree_entry_t * found;
benc_val_t val, * addval, * delval, * extra;
*retbuf = NULL;
*retlen = 0;
sent = &peer->sentPeers;
peertreeInit( &added );
peertreeInit( &common );
/* build trees of peers added and deleted since the last pex */
for( ii = 0; ii < tor->peerCount; ii++ )
{
pp = tor->peers[ii];
if( 0 == pp->port || 0 == tr_addrcmp( &peer->addr, &pp->addr ) )
{
continue;
}
found = peertreeGet( sent, &pp->addr, pp->port );
if( NULL != found )
{
peertreeMove( &common, sent, found );
}
else if( NULL == peertreeAdd( &added, &pp->addr, pp->port ) )
{
peertreeMerge( sent, &common );
peertreeFree( &added );
tr_bencFree( extraval );
return 1;
}
}
/* check if there were any added or deleted peers */
if( peertreeEmpty( &added ) && peertreeEmpty( sent ) )
{
peertreeMerge( sent, &common );
peertreeFree( &added );
tr_bencFree( extraval );
return 0;
}
/* build the dictionaries */
tr_bencInit( &val, TYPE_DICT );
if( tr_bencDictReserve( &val, 3 ) )
{
tr_bencFree( &val );
peertreeMerge( sent, &common );
peertreeFree( &added );
tr_bencFree( extraval );
return 1;
}
extra = tr_bencDictAdd( &val, extrakey );
addval = tr_bencDictAdd( &val, "added" );
delval = tr_bencDictAdd( &val, "dropped" );
assert( NULL != extra && NULL != addval && NULL != delval );
if( (*peerfunc)( &added, addval ) || (*peerfunc)( sent, delval ) )
{
tr_bencFree( &val );
peertreeMerge( sent, &common );
peertreeFree( &added );
tr_bencFree( extraval );
return 1;
}
*extra = *extraval;
memset( extraval, 0, sizeof( extraval ) );
/* bencode it */
*retbuf = tr_bencSaveMalloc( &val, retlen );
tr_bencFree( &val );
if( NULL == *retbuf )
{
peertreeMerge( sent, &common );
peertreeFree( &added );
return 1;
}
peertreeSwap( sent, &common );
peertreeMerge( sent, &added );
peertreeFree( &common );
return 0;
}
static char *
makeExtendedHandshake( tr_torrent_t * tor, tr_peer_t * peer, int * len )
{
benc_val_t val, * msgsval;
char * buf, * vers;
/* get human-readable version string */
vers = NULL;
asprintf( &vers, "%s %s", TR_NAME, VERSION_STRING );
if( NULL == vers )
{
return NULL;
}
/* reserve space in toplevel dictionary for v, m, and possibly p */
tr_bencInit( &val, TYPE_DICT );
if( tr_bencDictReserve( &val, ( 0 < tor->publicPort ? 3 : 2 ) ) )
{
free( vers );
tr_bencFree( &val );
return NULL;
}
/* human readable version string */
tr_bencInitStr( tr_bencDictAdd( &val, "v" ), vers, 0, 0 );
/* create dict of supported extended messages */
msgsval = tr_bencDictAdd( &val, "m" );
tr_bencInit( msgsval, TYPE_DICT );
if( tr_bencDictReserve( msgsval, 1 ) )
{
tr_bencFree( &val );
return NULL;
}
/* for public torrents advertise utorrent pex message */
tr_bencInitInt( tr_bencDictAdd( msgsval, "ut_pex" ),
( peer->private ? 0 : EXTENDED_PEX_ID ) );
/* our listening port */
if( 0 < tor->publicPort )
{
/* XXX should inform peer when we no longer have a valid port */
tr_bencInitInt( tr_bencDictAdd( &val, "p" ), tor->publicPort );
}
/* bencode it */
buf = tr_bencSaveMalloc( &val, len );
tr_bencFree( &val );
if( NULL == buf )
{
return NULL;
}
peer->advertisedPort = tor->publicPort;
return buf;
}
static int
peertreeToBencUT( tr_peertree_t * tree, benc_val_t * val )
{
char * buf;
tr_peertree_entry_t * ii;
int count;
count = peertreeCount( tree );
if( 0 == count )
{
tr_bencInitStr( val, NULL, 0, 1 );
return 0;
}
buf = malloc( 6 * count );
if( NULL == buf )
{
return 1;
}
tr_bencInitStr( val, buf, 6 * count, 0 );
for( ii = peertreeFirst( tree ); NULL != ii;
ii = peertreeNext( tree, ii ) )
{
assert( 0 < count );
count--;
memcpy( buf + 6 * count, ii->peer, 6 );
}
assert( 0 == count );
return 0;
}
static int
makeUTPex( tr_torrent_t * tor, tr_peer_t * peer, char ** buf, int * len )
{
benc_val_t val;
assert( !peer->private );
tr_bencInitStr( &val, NULL, 0, 1 );
return makeCommonPex( tor, peer, peertreeToBencUT, "added.f", &val,
buf, len );
}
static inline int
parseExtendedHandshake( tr_peer_t * peer, uint8_t * buf, int len )
{
benc_val_t val, * sub;
int dbgport, dbgpex;
if( tr_bencLoad( buf, len, &val, NULL ) )
{
peer_dbg( "GET extended-handshake, invalid bencoding" );
return TR_ERROR;
}
if( TYPE_DICT != val.type )
{
peer_dbg( "GET extended-handshake, not a dictionary" );
tr_bencFree( &val );
return TR_ERROR;
}
/* check supported messages for utorrent pex */
sub = tr_bencDictFind( &val, "m" );
dbgpex = -1;
if( NULL != sub && TYPE_DICT == sub->type )
{
sub = tr_bencDictFind( sub, "ut_pex" );
if( NULL != sub && TYPE_INT == sub->type )
{
peer->pexStatus = 0;
if( 0x0 < sub->val.i && 0xff >= sub->val.i )
{
peer->pexStatus = sub->val.i;
dbgpex = sub->val.i;
}
}
}
#if 0 /* ugh, we have to deal with encoding if we do this */
/* get peer's client name */
sub = tr_bencDictFind( &val, "v" );
if( NULL != sub && TYPE_STR == sub->type &&
( NULL == peer->client || 0 != strcmp( sub->val.s.s, peer->client ) ) )
{
client = tr_bencStealStr( sub );
if( NULL != client )
{
free( peer->client );
peer->client = client;
}
}
#endif
/* get peer's listening port */
sub = tr_bencDictFind( &val, "p" );
dbgport = -1;
if( NULL != sub && TYPE_INT == sub->type &&
0x0 < sub->val.i && 0xffff >= sub->val.i )
{
peer->port = htons( (uint16_t) sub->val.i );
dbgport = sub->val.i;
}
peer_dbg( "GET extended-handshake, ok port=%i pex=%i", dbgport, dbgpex );
tr_bencFree( &val );
return TR_OK;
}
static inline int
parseUTPex( tr_torrent_t * tor, tr_peer_t * peer, uint8_t * buf, int len )
{
benc_val_t val, * sub;
int used;
if( peer->private || PEX_PEER_CUTOFF <= tor->peerCount )
{
peer_dbg( "GET extended-pex, ignoring p=%i c=(%i<=%i)",
peer->private, PEX_PEER_CUTOFF, tor->peerCount );
return TR_OK;
}
if( tr_bencLoad( buf, len, &val, NULL ) )
{
peer_dbg( "GET extended-pex, invalid bencoding" );
return TR_ERROR;
}
if( TYPE_DICT != val.type )
{
tr_bencFree( &val );
peer_dbg( "GET extended-pex, not a dictionary" );
return TR_ERROR;
}
sub = tr_bencDictFind( &val, "added" );
if( NULL != sub && TYPE_STR == sub->type && 0 == sub->val.s.i % 6 )
{
used = tr_torrentAddCompact( tor, TR_PEER_FROM_PEX,
( uint8_t * )sub->val.s.s,
sub->val.s.i / 6 );
peer_dbg( "GET extended-pex, got %i peers, used %i",
sub->val.s.i / 6, used );
}
else
{
peer_dbg( "GET extended-pex, no peers" );
}
tr_bencFree( &val );
return TR_OK;
}