transmission/libtransmission/peeraz.h

518 lines
14 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 AZ_EXT_VERSION 1
#define AZ_MSG_BT_HANDSHAKE -1
#define AZ_MSG_BT_KEEP_ALIVE -2
#define AZ_MSG_AZ_HANDSHAKE -3
#define AZ_MSG_AZ_PEER_EXCHANGE -4
#define AZ_MSG_INVALID -5
#define AZ_MSG_IS_OPTIONAL( id ) \
( AZ_MSG_AZ_PEER_EXCHANGE == (id) || AZ_MSG_IS_UNUSED( id ) )
#define AZ_MSG_IS_UNUSED( id ) \
( AZ_MSG_AZ_HANDSHAKE == (id) || AZ_MSG_BT_HANDSHAKE == (id) )
static const struct
{
char * label;
const int len;
const int id;
}
az_msgs[] = {
{ "AZ_PEER_EXCHANGE", 16, AZ_MSG_AZ_PEER_EXCHANGE },
{ "AZ_HANDSHAKE", 12, AZ_MSG_AZ_HANDSHAKE },
{ "BT_HANDSHAKE", 12, AZ_MSG_BT_HANDSHAKE },
{ "BT_KEEP_ALIVE", 13, AZ_MSG_BT_KEEP_ALIVE },
{ "BT_CHOKE", 8, PEER_MSG_CHOKE },
{ "BT_UNCHOKE", 10, PEER_MSG_UNCHOKE },
{ "BT_INTERESTED", 13, PEER_MSG_INTERESTED },
{ "BT_UNINTERESTED", 15, PEER_MSG_UNINTERESTED },
{ "BT_HAVE", 7, PEER_MSG_HAVE },
{ "BT_BITFIELD", 11, PEER_MSG_BITFIELD },
{ "BT_REQUEST", 10, PEER_MSG_REQUEST },
{ "BT_PIECE", 8, PEER_MSG_PIECE },
{ "BT_CANCEL", 9, PEER_MSG_CANCEL },
};
#define azmsgStr( idx ) ( az_msgs[(idx)].label )
#define azmsgLen( idx ) ( az_msgs[(idx)].len )
#define azmsgId( idx ) ( az_msgs[(idx)].id )
#define azmsgCount() ( (int)(sizeof( az_msgs ) / sizeof( az_msgs[0] ) ) )
static int
azmsgIdIndex( int id )
{
int ii;
for( ii = 0; azmsgCount() > ii; ii++ )
{
if( id == azmsgId( ii ) )
{
return ii;
}
}
assert( 0 );
return 0;
}
static int
azmsgNameIndex( const char * name, int len )
{
int ii;
for( ii = 0; azmsgCount() > ii; ii++ )
{
if( azmsgLen( ii ) == len &&
0 == memcmp( azmsgStr( ii ), name, len ) )
{
return ii;
}
}
return -1;
}
static uint8_t *
makeAZHandshake( tr_torrent_t * tor, tr_peer_t * peer, int * buflen )
{
char * buf;
benc_val_t val, * msgsval, * msgdictval;
int len, max, idx;
uint8_t vers;
*buflen = 0;
idx = azmsgIdIndex( AZ_MSG_AZ_HANDSHAKE );
len = 4 + 4 + azmsgLen( idx ) + 1;
buf = malloc( len );
if( NULL == buf )
{
return NULL;
}
/* set length to zero for now, we won't know it until after bencoding */
tr_htonl( 0, buf );
/* set name length, name, and version */
tr_htonl( azmsgLen( idx ), buf + 4 );
memcpy( buf + 8, azmsgStr( idx ), azmsgLen( idx ) );
buf[8 + azmsgLen( idx )] = AZ_EXT_VERSION;
/* start building a dictionary for handshake data */
tr_bencInit( &val, TYPE_DICT );
if( tr_bencDictReserve( &val, 5 ) )
{
free( buf );
tr_bencFree( &val );
return NULL;
}
/* fill in the dictionary values */
tr_bencInitStr( tr_bencDictAdd( &val, "identity" ),
tor->azId, TR_AZ_ID_LEN, 1 );
tr_bencInitStr( tr_bencDictAdd( &val, "client" ), TR_NAME, 0, 1 );
tr_bencInitStr( tr_bencDictAdd( &val, "version" ), SHORT_VERSION_STRING, 0, 1 );
if( 0 < tor->publicPort )
{
tr_bencInitInt( tr_bencDictAdd( &val, "tcp_port" ), tor->publicPort );
}
/* initialize supported message list */
msgsval = tr_bencDictAdd( &val, "messages" );
tr_bencInit( msgsval, TYPE_LIST );
if( tr_bencListReserve( msgsval, azmsgCount() ) )
{
tr_bencFree( &val );
free( buf );
return NULL;
}
/* fill in the message list */
vers = AZ_EXT_VERSION;
for( idx = 0; azmsgCount() > idx; idx++ )
{
if( AZ_MSG_IS_UNUSED( azmsgId( idx ) ) )
{
continue;
}
if( AZ_MSG_AZ_PEER_EXCHANGE == azmsgId( idx ) && peer->private )
{
/* no point in saying we can do pex if the torrent is private */
continue;
}
/* each item in the list is a dict with id and ver keys */
msgdictval = tr_bencListAdd( msgsval );
tr_bencInit( msgdictval, TYPE_DICT );
if( tr_bencDictReserve( msgdictval, 2 ) )
{
tr_bencFree( &val );
free( buf );
return NULL;
}
tr_bencInitStr( tr_bencDictAdd( msgdictval, "id" ),
azmsgStr( idx ), azmsgLen( idx ), 1 );
tr_bencInitStr( tr_bencDictAdd( msgdictval, "ver" ), &vers, 1, 1 );
}
/* bencode the dictionary and append it to the buffer */
max = len;
if( tr_bencSave( &val, &buf, &len, &max ) )
{
tr_bencFree( &val );
free( buf );
return NULL;
}
tr_bencFree( &val );
/* we know the length now, fill it in */
tr_htonl( len - 4, buf );
/* XXX is there a way to tell azureus that the public port has changed? */
peer->advertisedPort = tor->publicPort;
*buflen = len;
return ( uint8_t * )buf;
}
static int
peertreeToBencAZ( tr_peertree_t * tree, benc_val_t * val )
{
int count;
tr_peertree_entry_t * ii;
tr_bencInit( val, TYPE_LIST );
count = peertreeCount( tree );
if( 0 == count )
{
return 0;
}
if( tr_bencListReserve( val, count ) )
{
return 1;
}
ii = peertreeFirst( tree );
while( NULL != ii )
{
tr_bencInitStr( tr_bencListAdd( val ), ii->peer, 6, 1 );
ii = peertreeNext( tree, ii );
}
return 0;
}
static int
makeAZPex( tr_torrent_t * tor, tr_peer_t * peer, char ** buf, int * len )
{
benc_val_t val;
assert( !peer->private );
tr_bencInitStr( &val, tor->info.hash, sizeof( tor->info.hash ), 1 );
return makeCommonPex( tor, peer, peertreeToBencAZ, "infohash", &val,
buf, len);
}
static int
sendAZHandshake( tr_torrent_t * tor, tr_peer_t * peer )
{
uint8_t * buf;
int len;
/* XXX this is kind of evil to use this buffer like this */
if( NULL == peer->outMessages )
{
buf = makeAZHandshake( tor, peer, &len );
if( NULL == buf )
{
return TR_NET_CLOSE;
}
peer->outMessages = buf;
peer->outMessagesSize = len;
peer->outMessagesPos = 0;
}
len = tr_netSend( peer->socket, peer->outMessages + peer->outMessagesPos,
peer->outMessagesSize - peer->outMessagesPos );
if( peer->outMessagesPos + len < peer->outMessagesSize )
{
peer->outMessagesPos += len;
return TR_NET_BLOCK;
}
peer_dbg( "SEND azureus-handshake" );
len = peer->outMessagesSize;
free( peer->outMessages );
peer->outMessages = NULL;
peer->outMessagesSize = 0;
peer->outMessagesPos = 0;
return len;
}
static int
parseAZMessageHeader( tr_peer_t * peer, uint8_t * buf, int len,
int * msgidret, int * msglenret )
{
uint8_t * name, vers;
int off, namelen, msglen, index, msgid;
if( 8 > len )
{
return TR_NET_BLOCK;
}
/* message length */
msglen = tr_ntohl( buf );
msglen += 4;
off = 4;
if( msglen > len )
{
return TR_NET_BLOCK;
}
if( 9 > msglen )
{
peer_dbg( "azureus peer message is too short to make sense" );
return TR_NET_CLOSE;
}
/* name length */
namelen = tr_ntohl( buf + off );
off += 4;
if( off + namelen + 1 > msglen )
{
peer_dbg( "azureus peer message name is too long to make sense" );
return TR_NET_CLOSE;
}
/* message name */
name = buf + off;
off += namelen;
/* message version */
vers = buf[off];
off++;
/* get payload length from message length */
msglen -= off;
index = azmsgNameIndex( ( char * )name, namelen );
if( AZ_EXT_VERSION != vers )
{
/* XXX should we close the connection here? */
peer_dbg( "GET unsupported azureus message version %hhu", vers );
msgid = AZ_MSG_INVALID;
}
else if( 0 > index )
{
name[namelen] = '\0';
peer_dbg( "GET unknown azureus message: \"%s\"", name );
name[namelen] = vers;
msgid = AZ_MSG_INVALID;
}
else
{
msgid = azmsgId( index );
}
*msgidret = msgid;
*msglenret = msglen;
return off;
}
static int
parseAZHandshake( tr_peer_t * peer, uint8_t * buf, int len )
{
benc_val_t val, * sub, * dict, * subsub;
tr_bitfield_t * msgs;
int ii, idx;
if( tr_bencLoad( buf, len, &val, NULL ) )
{
peer_dbg( "GET azureus-handshake, invalid bencoding" );
return TR_ERROR;
}
if( TYPE_DICT != val.type )
{
peer_dbg( "GET azureus-handshake, data not a dictionary" );
tr_bencFree( &val );
return TR_ERROR;
}
#if 0 /* ugh, we have to deal with encoding if we do this */
/* get peer's client name */
sub = tr_bencDictFind( &val, "client" );
sub2 = tr_bencDictFind( &val, "version" );
if( NULL != sub && TYPE_STR == sub->type &&
NULL != sub2 && TYPE_STR == sub->type )
{
if( NULL == peer->client ||
( 0 != strncmp( peer->client, sub->val.s.s, sub->val.s.i ) ||
' ' != peer->client[sub->val.s.i] ||
0 != strcmp( peer->client + sub->val.s.i + 1, sub2->val.s.s ) ) )
{
client = NULL;
asprintf( &client, "%s %s", sub->val.s.s, sub2->val.s.s );
if( NULL != client )
{
free( peer->client );
peer->client = client;
}
}
}
#endif
/* get the peer's listening port */
sub = tr_bencDictFind( &val, "tcp_port" );
if( NULL != sub )
{
if( TYPE_INT == sub->type && 0x0 < sub->val.i && 0xffff >= sub->val.i )
{
peer->port = htons( sub->val.i );
}
}
/* find the supported message list */
sub = tr_bencDictFind( &val, "messages" );
if( !sub || sub->type != TYPE_LIST )
{
tr_bencFree( &val );
peer_dbg( "GET azureus-handshake, missing 'messages'" );
return TR_ERROR;
}
peer_dbg( "GET azureus-handshake, ok" );
/* fill bitmask with supported message info */
msgs = tr_bitfieldNew( azmsgCount() );
for( ii = 0; ii < sub->val.l.count; ii++ )
{
dict = &sub->val.l.vals[ii];
if( TYPE_DICT != dict->type )
{
continue;
}
subsub = tr_bencDictFind( dict, "id" );
if( NULL == subsub || TYPE_STR != subsub->type )
{
continue;
}
idx = azmsgNameIndex( subsub->val.s.s, subsub->val.s.i );
if( 0 > idx )
{
continue;
}
subsub = tr_bencDictFind( dict, "ver" );
if( NULL == subsub || TYPE_STR != subsub->type ||
1 != subsub->val.s.i || AZ_EXT_VERSION > subsub->val.s.s[0] )
{
continue;
}
tr_bitfieldAdd( msgs, idx );
}
tr_bencFree( &val );
/* check bitmask to see if we're missing any messages */
for( ii = 0; azmsgCount() > ii; ii++ )
{
if( AZ_MSG_AZ_PEER_EXCHANGE == azmsgId( ii ) )
{
peer->pexStatus = tr_bitfieldHas( msgs, ii );
}
if( !AZ_MSG_IS_OPTIONAL( azmsgId( ii ) ) &&
!tr_bitfieldHas( msgs, ii ) )
{
peer_dbg( "azureus message %s not supported by peer",
azmsgStr( ii ) );
tr_bitfieldFree( msgs );
return TR_ERROR;
}
}
tr_bitfieldFree( msgs );
return TR_OK;
}
static int
parseAZPex( tr_torrent_t * tor, tr_peer_t * peer, uint8_t * buf, int len )
{
tr_info_t * info = &tor->info;
benc_val_t val, * list, * pair;
int ii, used;
if( peer->private || PEX_PEER_CUTOFF <= tor->peerCount )
{
peer_dbg( "GET azureus-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 azureus-pex, invalid bencoding" );
return TR_ERROR;
}
if( TYPE_DICT != val.type )
{
tr_bencFree( &val );
peer_dbg( "GET azureus-pex, data not a dictionary" );
return TR_ERROR;
}
list = tr_bencDictFind( &val, "infohash" );
if( NULL == list || TYPE_STR != list->type ||
sizeof( info->hash ) != list->val.s.i ||
0 != memcmp( info->hash, list->val.s.s, sizeof( info->hash ) ) )
{
tr_bencFree( &val );
peer_dbg( "GET azureus-pex, bad infohash" );
return TR_ERROR;
}
list = tr_bencDictFind( &val, "added" );
if( NULL == list || TYPE_LIST != list->type )
{
peer_dbg( "GET azureus-pex, no peers" );
tr_bencFree( &val );
return TR_OK;
}
used = 0;
for( ii = 0; ii < list->val.l.count; ii++ )
{
pair = &list->val.l.vals[ii];
if( TYPE_STR == pair->type && 6 == pair->val.s.i )
{
used += tr_torrentAddCompact( tor, TR_PEER_FROM_PEX,
( uint8_t * )pair->val.s.s, 1 );
}
}
peer_dbg( "GET azureus-pex, found %i peers, using %i",
list->val.l.count, used );
tr_bencFree( &val );
return TR_OK;
}