transmission/libtransmission/peerutils.h

828 lines
24 KiB
C

/******************************************************************************
* Copyright (c) 2005 Eric Petit
*
* 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.
*****************************************************************************/
static void updateInterest( tr_torrent_t * tor, tr_peer_t * peer );
/***********************************************************************
* peerInit
***********************************************************************
* Returns NULL if we reached the maximum authorized number of peers.
* Otherwise, allocates a new tr_peer_t, add it to the peers list and
* returns a pointer to it.
**********************************************************************/
static tr_peer_t * peerInit( tr_torrent_t * tor )
{
tr_peer_t * peer;
if( tor->peerCount >= TR_MAX_PEER_COUNT )
{
return NULL;
}
peer = calloc( sizeof( tr_peer_t ), 1 );
peer->amChoking = 1;
peer->peerChoking = 1;
peer->date = tr_date();
peer->keepAlive = peer->date;
tor->peers[tor->peerCount++] = peer;
return peer;
}
static int peerCmp( tr_peer_t * peer1, tr_peer_t * peer2 )
{
/* Wait until we got the peers' ids */
if( peer1->status < PEER_STATUS_CONNECTED ||
peer2->status < PEER_STATUS_CONNECTED )
{
return 1;
}
return memcmp( peer1->id, peer2->id, 20 );
}
/***********************************************************************
* addWithAddr
***********************************************************************
* Does nothing if we already have a peer matching 'addr' and 'port'.
* Otherwise adds such a new peer.
**********************************************************************/
static void addWithAddr( tr_torrent_t * tor, struct in_addr addr,
in_port_t port )
{
int i;
tr_peer_t * peer;
for( i = 0; i < tor->peerCount; i++ )
{
peer = tor->peers[i];
if( peer->addr.s_addr == addr.s_addr &&
peer->port == port )
{
/* We are already connected to this peer */
return;
}
}
if( !( peer = peerInit( tor ) ) )
{
return;
}
peer->addr = addr;
peer->port = port;
peer->status = PEER_STATUS_IDLE;
}
static int checkPeer( tr_torrent_t * tor, int i )
{
tr_peer_t * peer = tor->peers[i];
if( peer->status < PEER_STATUS_CONNECTED &&
tr_date() > peer->date + 8000 )
{
/* If it has been too long, don't wait for the socket
to timeout - forget about it now */
peer_dbg( "connection timeout" );
return 1;
}
/* Drop peers who haven't even sent a keep-alive within the
last 3 minutes */
if( tr_date() > peer->date + 180000 )
{
peer_dbg( "read timeout" );
return 1;
}
/* Drop peers which are supposed to upload but actually
haven't sent anything within the last minute */
if( peer->inRequestCount && tr_date() > peer->date + 60000 )
{
peer_dbg( "bad uploader" );
return 1;
}
#if 0
/* Choke unchoked peers we are not sending anything to */
if( !peer->amChoking && tr_date() > peer->outDate + 10000 )
{
peer_dbg( "not worth the unchoke" );
if( sendChoke( peer, 1 ) )
{
goto dropPeer;
}
peer->outSlow = 1;
tr_uploadChoked( tor->upload );
}
#endif
if( peer->status & PEER_STATUS_CONNECTED )
{
/* Send keep-alive every 2 minutes */
if( tr_date() > peer->keepAlive + 120000 )
{
sendKeepAlive( peer );
peer->keepAlive = tr_date();
}
/* Choke or unchoke some people */
/* TODO: prefer people who upload to us */
if( !peer->amChoking && !peer->peerInterested )
{
/* He doesn't need us */
sendChoke( peer, 1 );
tr_uploadChoked( tor->upload );
}
if( peer->amChoking && peer->peerInterested &&
!peer->outSlow && tr_uploadCanUnchoke( tor->upload ) )
{
sendChoke( peer, 0 );
tr_uploadUnchoked( tor->upload );
}
}
return 0;
}
static int parseMessage( tr_torrent_t * tor, tr_peer_t * peer,
int newBytes )
{
tr_info_t * inf = &tor->info;
int i, j;
int len;
char id;
uint8_t * p = peer->buf;
uint8_t * end = &p[peer->pos];
for( ;; )
{
if( peer->pos < 4 )
{
break;
}
if( peer->status & PEER_STATUS_HANDSHAKE )
{
char * client;
if( p[0] != 19 || memcmp( &p[1], "Bit", 3 ) )
{
/* Don't wait until we get 68 bytes, this is wrong
already */
peer_dbg( "GET handshake, invalid" );
tr_netSend( peer->socket, (uint8_t *) "Nice try...\r\n", 13 );
return 1;
}
if( peer->pos < 68 )
{
break;
}
if( memcmp( &p[4], "Torrent protocol", 16 ) )
{
peer_dbg( "GET handshake, invalid" );
return 1;
}
if( memcmp( &p[28], inf->hash, 20 ) )
{
peer_dbg( "GET handshake, wrong torrent hash" );
return 1;
}
if( !memcmp( &p[48], tor->id, 20 ) )
{
/* We are connected to ourselves... */
peer_dbg( "GET handshake, that is us" );
return 1;
}
peer->status = PEER_STATUS_CONNECTED;
memcpy( peer->id, &p[48], 20 );
p += 68;
peer->pos -= 68;
for( i = 0; i < tor->peerCount; i++ )
{
if( tor->peers[i] == peer )
{
continue;
}
if( !peerCmp( peer, tor->peers[i] ) )
{
peer_dbg( "GET handshake, duplicate" );
return 1;
}
}
client = tr_clientForId( (uint8_t *) peer->id );
peer_dbg( "GET handshake, ok (%s)", client );
free( client );
sendBitfield( tor, peer );
continue;
}
/* Get payload size */
TR_NTOHL( p, len );
p += 4;
if( len > 9 + tor->blockSize )
{
/* This shouldn't happen. Forget about that peer */
peer_dbg( "message too large" );
return 1;
}
if( !len )
{
/* keep-alive */
peer_dbg( "GET keep-alive" );
peer->pos -= 4;
continue;
}
/* That's a piece coming */
if( p < end && *p == 7 )
{
/* XXX */
tor->downloaded[9] += newBytes;
peer->inTotal += newBytes;
newBytes = 0;
}
if( &p[len] > end )
{
/* We do not have the entire message */
p -= 4;
break;
}
/* Remaining data after this message */
peer->pos -= 4 + len;
/* Type of the message */
id = *(p++);
switch( id )
{
case 0: /* choke */
{
tr_request_t * r;
if( len != 1 )
{
peer_dbg( "GET choke, invalid" );
return 1;
}
peer_dbg( "GET choke" );
peer->peerChoking = 1;
for( i = 0; i < peer->inRequestCount; i++ )
{
r = &peer->inRequests[i];
if( tor->blockHave[tr_block(r->index,r->begin)] > 0 )
{
tor->blockHave[tr_block(r->index,r->begin)]--;
}
}
peer->inRequestCount = 0;
break;
}
case 1: /* unchoke */
if( len != 1 )
{
peer_dbg( "GET unchoke, invalid" );
return 1;
}
peer_dbg( "GET unchoke" );
peer->peerChoking = 0;
break;
case 2: /* interested */
if( len != 1 )
{
peer_dbg( "GET interested, invalid" );
return 1;
}
peer_dbg( "GET interested" );
peer->peerInterested = 1;
break;
case 3: /* uninterested */
if( len != 1 )
{
peer_dbg( "GET uninterested, invalid" );
return 1;
}
peer_dbg( "GET uninterested" );
peer->peerInterested = 0;
break;
case 4: /* have */
{
uint32_t piece;
if( len != 5 )
{
peer_dbg( "GET have, invalid" );
return 1;
}
TR_NTOHL( p, piece );
if( !peer->bitfield )
{
peer->bitfield = calloc( ( inf->pieceCount + 7 ) / 8, 1 );
}
tr_bitfieldAdd( peer->bitfield, piece );
updateInterest( tor, peer );
peer_dbg( "GET have %d", piece );
break;
}
case 5: /* bitfield */
{
int bitfieldSize;
bitfieldSize = ( inf->pieceCount + 7 ) / 8;
if( len != 1 + bitfieldSize )
{
peer_dbg( "GET bitfield, wrong size" );
return 1;
}
/* Make sure the spare bits are unset */
if( ( inf->pieceCount & 0x7 ) )
{
uint8_t lastByte;
lastByte = p[bitfieldSize-1];
lastByte <<= inf->pieceCount & 0x7;
lastByte &= 0xFF;
if( lastByte )
{
peer_dbg( "GET bitfield, spare bits set" );
return 1;
}
}
if( !peer->bitfield )
{
peer->bitfield = malloc( bitfieldSize );
}
memcpy( peer->bitfield, p, bitfieldSize );
updateInterest( tor, peer );
peer_dbg( "GET bitfield, ok" );
break;
}
case 6: /* request */
{
int index, begin, length;
if( peer->amChoking )
{
/* Didn't he get it? */
sendChoke( peer, 1 );
break;
}
TR_NTOHL( p, index );
TR_NTOHL( &p[4], begin );
TR_NTOHL( &p[8], length );
peer_dbg( "GET request %d/%d (%d bytes)",
index, begin, length );
/* TODO sanity checks (do we have the piece, etc) */
if( length > 16384 )
{
/* Sorry mate */
return 1;
}
if( peer->outRequestCount < MAX_REQUEST_COUNT )
{
tr_request_t * r;
r = &peer->outRequests[peer->outRequestCount];
r->index = index;
r->begin = begin;
r->length = length;
(peer->outRequestCount)++;
}
else
{
tr_err( "Too many requests" );
return 1;
}
break;
}
case 7: /* piece */
{
int index, begin;
int block;
tr_request_t * r;
TR_NTOHL( p, index );
TR_NTOHL( &p[4], begin );
peer_dbg( "GET piece %d/%d (%d bytes)",
index, begin, len - 9 );
if( peer->inRequestCount < 1 )
{
/* Our "cancel" was probably late */
peer_dbg( "not expecting a block" );
break;
}
r = &peer->inRequests[0];
if( index != r->index || begin != r->begin )
{
int suckyClient;
/* Either our "cancel" was late, or this is a sucky
client that cannot deal with multiple requests */
suckyClient = 0;
for( i = 0; i < peer->inRequestCount; i++ )
{
r = &peer->inRequests[i];
if( index != r->index || begin != r->begin )
{
continue;
}
/* Sucky client, he dropped the previous requests */
peer_dbg( "block was expected later" );
for( j = 0; j < i; j++ )
{
r = &peer->inRequests[j];
if( tor->blockHave[tr_block(r->index,r->begin)] > 0 )
{
tor->blockHave[tr_block(r->index,r->begin)]--;
}
}
suckyClient = 1;
peer->inRequestCount -= i;
memmove( &peer->inRequests[0], &peer->inRequests[i],
peer->inRequestCount * sizeof( tr_request_t ) );
r = &peer->inRequests[0];
break;
}
if( !suckyClient )
{
r = &peer->inRequests[0];
peer_dbg( "wrong block (expecting %d/%d)",
r->index, r->begin );
break;
}
}
if( len - 9 != r->length )
{
peer_dbg( "wrong size (expecting %d)", r->length );
return 1;
}
block = tr_block( r->index, r->begin );
if( tor->blockHave[block] < 0 )
{
peer_dbg( "have this block already" );
(peer->inRequestCount)--;
memmove( &peer->inRequests[0], &peer->inRequests[1],
peer->inRequestCount * sizeof( tr_request_t ) );
break;
}
tor->blockHave[block] = -1;
tor->blockHaveCount += 1;
tr_ioWrite( tor->io, index, begin, len - 9, &p[8] );
sendCancel( tor, block );
if( tr_bitfieldHas( tor->bitfield, index ) )
{
tr_peer_t * otherPeer;
for( i = 0; i < tor->peerCount; i++ )
{
otherPeer = tor->peers[i];
if( otherPeer->status < PEER_STATUS_CONNECTED )
{
continue;
}
sendHave( otherPeer, index );
updateInterest( tor, otherPeer );
}
}
(peer->inRequestCount)--;
memmove( &peer->inRequests[0], &peer->inRequests[1],
peer->inRequestCount * sizeof( tr_request_t ) );
break;
}
case 8: /* cancel */
{
int index, begin, length;
int i;
tr_request_t * r;
TR_NTOHL( p, index );
TR_NTOHL( &p[4], begin );
TR_NTOHL( &p[8], length );
peer_dbg( "GET cancel %d/%d (%d bytes)",
index, begin, length );
for( i = 0; i < peer->outRequestCount; i++ )
{
r = &peer->outRequests[i];
if( r->index == index && r->begin == begin &&
r->length == length )
{
(peer->outRequestCount)--;
memmove( &r[0], &r[1], sizeof( tr_request_t ) *
( peer->outRequestCount - i ) );
break;
}
}
break;
}
case 9:
{
in_port_t port;
if( len != 3 )
{
peer_dbg( "GET port, invalid" );
return 1;
}
port = *( (in_port_t *) p );
peer_dbg( "GET port %d", ntohs( port ) );
break;
}
default:
{
peer_dbg( "Unknown message '%d'", id );
return 1;
}
}
p += len - 1;
}
memmove( peer->buf, p, peer->pos );
return 0;
}
/***********************************************************************
* isInteresting
***********************************************************************
* Returns 1 if 'peer' has at least one piece that we haven't completed,
* or 0 otherwise.
**********************************************************************/
static int isInteresting( tr_torrent_t * tor, tr_peer_t * peer )
{
tr_info_t * inf = &tor->info;
int i;
int bitfieldSize = ( inf->pieceCount + 7 ) / 8;
if( !peer->bitfield )
{
/* We don't know what this peer has */
return 0;
}
for( i = 0; i < bitfieldSize; i++ )
{
if( ( peer->bitfield[i] & ~(tor->bitfield[i]) ) & 0xFF )
{
return 1;
}
}
return 0;
}
static void updateInterest( tr_torrent_t * tor, tr_peer_t * peer )
{
int interested = isInteresting( tor, peer );
if( interested && !peer->amInterested )
{
sendInterest( peer, 1 );
}
if( !interested && peer->amInterested )
{
sendInterest( peer, 0 );
}
}
/***********************************************************************
* chooseBlock
***********************************************************************
* At this point, we know the peer has at least one block we have an
* interest in. If he has more than one, we choose which one we are
* going to ask first.
* Our main goal is to complete pieces, so we look the pieces which are
* missing less blocks.
**********************************************************************/
static int chooseBlock( tr_torrent_t * tor, tr_peer_t * peer )
{
tr_info_t * inf = &tor->info;
int i, j;
int startBlock, endBlock, countBlocks;
int missingBlocks, minMissing;
int poolSize, * pool;
int block, minDownloading;
/* Choose a piece */
pool = malloc( inf->pieceCount * sizeof( int ) );
poolSize = 0;
minMissing = tor->blockCount + 1;
for( i = 0; i < inf->pieceCount; i++ )
{
if( !tr_bitfieldHas( peer->bitfield, i ) )
{
/* The peer doesn't have this piece */
continue;
}
if( tr_bitfieldHas( tor->bitfield, i ) )
{
/* We already have it */
continue;
}
/* Count how many blocks from this piece are missing */
startBlock = tr_pieceStartBlock( i );
countBlocks = tr_pieceCountBlocks( i );
endBlock = startBlock + countBlocks;
missingBlocks = countBlocks;
for( j = startBlock; j < endBlock; j++ )
{
/* TODO: optimize */
if( tor->blockHave[j] )
{
missingBlocks--;
}
if( missingBlocks > minMissing )
{
break;
}
}
if( missingBlocks < 1 )
{
/* We are already downloading all blocks */
continue;
}
/* We are interested in this piece, remember it */
if( missingBlocks < minMissing )
{
minMissing = missingBlocks;
poolSize = 0;
}
if( missingBlocks <= minMissing )
{
pool[poolSize++] = i;
}
}
if( poolSize )
{
/* All pieces in 'pool' have 'minMissing' missing blocks. Find
the rarest ones. */
uint8_t * bitfield;
int piece;
int min, foo, j;
int * pool2;
int pool2Size;
pool2 = malloc( poolSize * sizeof( int ) );
pool2Size = 0;
min = TR_MAX_PEER_COUNT + 1;
for( i = 0; i < poolSize; i++ )
{
foo = 0;
for( j = 0; j < tor->peerCount; j++ )
{
bitfield = tor->peers[j]->bitfield;
if( bitfield && tr_bitfieldHas( bitfield, pool[i] ) )
{
foo++;
}
}
if( foo < min )
{
min = foo;
pool2Size = 0;
}
if( foo <= min )
{
pool2[pool2Size++] = pool[i];
}
}
free( pool );
if( pool2Size < 1 )
{
/* Shouldn't happen */
free( pool2 );
return -1;
}
/* All pieces in pool2 have the same number of missing blocks,
and are availabme from the same number of peers. Pick a
random one */
piece = pool2[tr_rand(pool2Size)];
free( pool2 );
/* Pick a block in this piece */
startBlock = tr_pieceStartBlock( piece );
endBlock = startBlock + tr_pieceCountBlocks( piece );
for( i = startBlock; i < endBlock; i++ )
{
if( !tor->blockHave[i] )
{
block = i;
goto check;
}
}
/* Shouldn't happen */
return -1;
}
free( pool );
/* "End game" mode */
block = -1;
minDownloading = TR_MAX_PEER_COUNT + 1;
for( i = 0; i < tor->blockCount; i++ )
{
/* TODO: optimize */
if( tor->blockHave[i] > 0 && tor->blockHave[i] < minDownloading )
{
block = i;
minDownloading = tor->blockHave[i];
}
}
if( block < 0 )
{
/* Shouldn't happen */
return -1;
}
check:
for( i = 0; i < peer->inRequestCount; i++ )
{
tr_request_t * r;
r = &peer->inRequests[i];
if( tr_block( r->index, r->begin ) == block )
{
/* We are already asking this peer for this block */
return -1;
}
}
return block;
}