mirror of
https://github.com/rkd77/elinks.git
synced 2024-06-24 00:56:14 +00:00
967 lines
29 KiB
C
967 lines
29 KiB
C
/* BitTorrent peer-wire protocol implementation */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
#include <sys/socket.h> /* OS/2 needs this after sys/types.h */
|
|
#endif
|
|
#ifdef HAVE_NETINET_IN_H
|
|
#include <netinet/in.h>
|
|
#endif
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
|
|
#include "elinks.h"
|
|
|
|
#include "config/options.h"
|
|
#include "main/select.h"
|
|
#include "main/timer.h"
|
|
#include "network/connection.h"
|
|
#include "network/socket.h"
|
|
#include "osdep/osdep.h"
|
|
#include "protocol/bittorrent/bittorrent.h"
|
|
#include "protocol/bittorrent/common.h"
|
|
#include "protocol/bittorrent/connection.h"
|
|
#include "protocol/bittorrent/peerconnect.h"
|
|
#include "protocol/bittorrent/peerwire.h"
|
|
#include "protocol/bittorrent/piececache.h"
|
|
#include "protocol/protocol.h"
|
|
#include "protocol/uri.h"
|
|
#include "util/bitfield.h"
|
|
#include "util/memory.h"
|
|
#include "util/string.h"
|
|
|
|
|
|
/* I give to you my sweet surrender. */
|
|
|
|
enum bittorrent_handshake_state {
|
|
BITTORRENT_PEER_HANDSHAKE_OK, /* Completely read and valid. */
|
|
BITTORRENT_PEER_HANDSHAKE_ERROR, /* The handshake was invalid. */
|
|
BITTORRENT_PEER_HANDSHAKE_INFO_HASH, /* All up to the hash was ok. */
|
|
BITTORRENT_PEER_HANDSHAKE_INCOMPLETE, /* All up to now was correct. */
|
|
};
|
|
|
|
/* The size of the handshake for version 1.0 is:
|
|
* <1:protocolstrlen> <19:protocolstr> <8:reserved> <20:info-hash> <20:peer-id>
|
|
* In total 68 bytes.*/
|
|
#define BITTORRENT_PEER_HANDSHAKE_SIZE (1 + 19 + 8 + 20 + 20)
|
|
|
|
/* Storing the version identification part of the handshake as one entity
|
|
* (length prefix and string) makes it much easier to verify and write. */
|
|
static const bittorrent_id_T BITTORRENT_ID = {'\023','B','i','t','T','o','r','r','e','n','t',' ','p','r','o','t','o','c','o','l'};
|
|
|
|
/* Has the last message written to the peer socket been sent or not? */
|
|
#define bittorrent_peer_is_sending(peer) ((peer)->socket->write_buffer)
|
|
|
|
|
|
static enum bittorrent_state
|
|
do_send_bittorrent_peer_message(struct bittorrent_peer_connection *peer,
|
|
struct bittorrent_peer_request *message);
|
|
|
|
static enum bittorrent_handshake_state
|
|
do_read_bittorrent_peer_handshake(struct socket *socket, struct read_buffer *buffer);
|
|
|
|
|
|
/* ************************************************************************** */
|
|
/* Peer state managing: */
|
|
/* ************************************************************************** */
|
|
|
|
/* Queue requests up to the configured limit. */
|
|
static void
|
|
queue_bittorrent_peer_connection_requests(struct bittorrent_peer_connection *peer)
|
|
{
|
|
int size = get_opt_int("protocol.bittorrent.request_queue_size", NULL);
|
|
int queue_size = list_size(&peer->local.requests);
|
|
|
|
for ( ; queue_size < size; queue_size++) {
|
|
struct bittorrent_peer_request *request;
|
|
|
|
request = find_bittorrent_peer_request(peer);
|
|
if (!request) break;
|
|
|
|
/* TODO: Insert rarest first? --jonas */
|
|
add_to_list_end(peer->local.requests, request);
|
|
}
|
|
|
|
/* Interest in the peer was lost if no request could be queued. */
|
|
if (list_empty(peer->local.requests))
|
|
set_bittorrent_peer_not_interested(peer);
|
|
}
|
|
|
|
/* Called both from the choke period handler of the main BitTorrent connection
|
|
* and upon completion of message sending this handles allocation of peer
|
|
* requests and sending of anything pending. */
|
|
/* XXX: Calling this function can cause the peer struct to disappear. */
|
|
void
|
|
update_bittorrent_peer_connection_state(struct bittorrent_peer_connection *peer)
|
|
{
|
|
struct bittorrent_connection *bittorrent = peer->bittorrent;
|
|
struct bittorrent_piece_cache *cache = bittorrent->cache;
|
|
struct bittorrent_peer_request *request, *next_request;
|
|
enum bittorrent_state state;
|
|
|
|
/* Drop connections to other seeders or when a partial download has been
|
|
* completed. */
|
|
if ((peer->remote.seeder && bittorrent->mode == BITTORRENT_MODE_SEEDER)
|
|
|| (cache->partial && cache->partial_pieces == cache->completed_pieces)) {
|
|
done_bittorrent_peer_connection(peer);
|
|
return;
|
|
}
|
|
|
|
if (peer->local.interested && !peer->local.choked)
|
|
queue_bittorrent_peer_connection_requests(peer);
|
|
|
|
/* Is there a write in progress? */
|
|
if (bittorrent_peer_is_sending(peer)) {
|
|
assert(get_handler(peer->socket->fd, SELECT_HANDLER_WRITE));
|
|
return;
|
|
}
|
|
|
|
/* Send the shorter state messages first ... */
|
|
|
|
/* Send any peer state oriented messages. */
|
|
foreachsafe (request, next_request, peer->queue) {
|
|
state = do_send_bittorrent_peer_message(peer, request);
|
|
if (state == BITTORRENT_STATE_OK)
|
|
return;
|
|
}
|
|
|
|
/* Send local piece requests which has not already been requested. */
|
|
foreachsafe (request, next_request, peer->local.requests) {
|
|
if (request->requested)
|
|
continue;
|
|
|
|
request->id = BITTORRENT_MESSAGE_REQUEST;
|
|
|
|
state = do_send_bittorrent_peer_message(peer, request);
|
|
if (state == BITTORRENT_STATE_OK)
|
|
return;
|
|
}
|
|
|
|
/* Ship the longer piece data messages. */
|
|
foreachsafe (request, next_request, peer->remote.requests) {
|
|
request->id = BITTORRENT_MESSAGE_PIECE;
|
|
|
|
state = do_send_bittorrent_peer_message(peer, request);
|
|
if (state == BITTORRENT_STATE_OK)
|
|
return;
|
|
}
|
|
}
|
|
|
|
static inline double
|
|
get_bittorrent_rate(struct bittorrent_peer_stats *stats, time_t now,
|
|
double rate, uint32_t loaded)
|
|
{
|
|
return (rate * (stats->last_time - stats->age) + loaded)
|
|
/ (now - stats->age);
|
|
}
|
|
|
|
void
|
|
update_bittorrent_peer_connection_stats(struct bittorrent_peer_connection *peer,
|
|
uint32_t downloaded, uint32_t have_piece,
|
|
uint32_t uploaded)
|
|
{
|
|
struct bittorrent_peer_stats *stats = &peer->stats;
|
|
time_t now = time(NULL);
|
|
|
|
stats->download_rate = get_bittorrent_rate(stats, now, stats->download_rate, downloaded);
|
|
stats->have_rate = get_bittorrent_rate(stats, now, stats->have_rate, have_piece);
|
|
|
|
stats->downloaded += downloaded;
|
|
stats->uploaded += uploaded;
|
|
stats->last_time = now;
|
|
|
|
/* Push the age along, so it will be no older than the requested number
|
|
* of seconds. */
|
|
if (stats->age < now - 20)
|
|
stats->age = stats->age ? now - 20 : now - 1;
|
|
}
|
|
|
|
|
|
/* ************************************************************************** */
|
|
/* Peer message handling: */
|
|
/* ************************************************************************** */
|
|
|
|
/* The layout of the length prefixed peer messages.
|
|
*
|
|
* - All indexes and offsets are encoded as 4-bytes big-endian.
|
|
* - Indexes are zero-based.
|
|
* - Variable length fields depends on the message length.
|
|
* - All messages begin with <4:message-length> <1:message-id>
|
|
* The only exception is the keep-alive message which has length set to zero
|
|
* and doesn't carry any message ID or payload.
|
|
*
|
|
* Message without payload:
|
|
* ------------------------
|
|
*
|
|
* - choke
|
|
* - unchoke
|
|
* - interested
|
|
* - not-interested
|
|
*
|
|
* Messages with payload:
|
|
* ----------------------
|
|
*
|
|
* - have: <4:piece-index>
|
|
* - bitfield: <x:bitfield-data>
|
|
* - request and cancel: <4:piece-index> <4:piece-offset> <4:block-length>
|
|
* - piece: <4:piece-index> <4:piece-offset> <x:block-data>
|
|
*/
|
|
|
|
|
|
/* ************************************************************************** */
|
|
/* Peer message sending: */
|
|
/* ************************************************************************** */
|
|
|
|
/* Meesage write completion callback. */
|
|
static void
|
|
sent_bittorrent_peer_message(struct socket *socket)
|
|
{
|
|
assert(!socket->write_buffer);
|
|
/* Check if there are pending messages or requests. */
|
|
update_bittorrent_peer_connection_state((struct bittorrent_peer_connection *)socket->conn);
|
|
}
|
|
|
|
static inline void
|
|
add_bittorrent_peer_integer(struct string *string, uint32_t integer)
|
|
{
|
|
uint32_t data = htonl(integer);
|
|
|
|
add_bytes_to_string(string, (char *) &data, sizeof(data));
|
|
}
|
|
|
|
/* Common lowlevel backend for composing a peer message and writing it to the
|
|
* socket. */
|
|
static enum bittorrent_state
|
|
do_send_bittorrent_peer_message(struct bittorrent_peer_connection *peer,
|
|
struct bittorrent_peer_request *message)
|
|
{
|
|
struct bittorrent_connection *bittorrent = peer->bittorrent;
|
|
struct string string;
|
|
char msgid_str[1] = { (char) message->id };
|
|
uint32_t msglen = 0;
|
|
|
|
assert(!bittorrent_peer_is_sending(peer));
|
|
assert(!get_handler(peer->socket->fd, SELECT_HANDLER_WRITE));
|
|
|
|
if (!init_string(&string))
|
|
return BITTORRENT_STATE_OUT_OF_MEM;
|
|
|
|
/* Reserve 4 bytes to the message length and add the message ID byte. */
|
|
add_bytes_to_string(&string, (char *) &msglen, sizeof(msglen));
|
|
|
|
/* XXX: Can't use add_char_to_string() here because the message ID
|
|
* can be zero. */
|
|
if (message->id != BITTORRENT_MESSAGE_KEEP_ALIVE)
|
|
add_bytes_to_string(&string, msgid_str, 1);
|
|
|
|
switch (message->id) {
|
|
case BITTORRENT_MESSAGE_HAVE:
|
|
{
|
|
add_bittorrent_peer_integer(&string, message->piece);
|
|
assert(string.length == 9);
|
|
break;
|
|
}
|
|
case BITTORRENT_MESSAGE_BITFIELD:
|
|
{
|
|
struct bitfield *bitfield = bittorrent->cache->bitfield;
|
|
size_t bytes = get_bitfield_byte_size(bitfield->bitsize);
|
|
|
|
/* Are bitfield messages allowed at this point? */
|
|
assert(!peer->local.bitfield);
|
|
|
|
add_bytes_to_string(&string, (char *)bitfield->bits, bytes);
|
|
|
|
assert(string.length == 5 + bytes);
|
|
break;
|
|
}
|
|
case BITTORRENT_MESSAGE_REQUEST:
|
|
case BITTORRENT_MESSAGE_CANCEL:
|
|
{
|
|
assert(!peer->local.choked);
|
|
|
|
add_bittorrent_peer_integer(&string, message->piece);
|
|
add_bittorrent_peer_integer(&string, message->offset);
|
|
add_bittorrent_peer_integer(&string, message->length);
|
|
|
|
message->requested = 1;
|
|
|
|
assert(string.length == 17);
|
|
break;
|
|
}
|
|
case BITTORRENT_MESSAGE_PIECE:
|
|
{
|
|
char *data;
|
|
|
|
assert(!peer->remote.choked);
|
|
assert(test_bitfield_bit(bittorrent->cache->bitfield, message->piece));
|
|
|
|
data = get_bittorrent_piece_cache_data(bittorrent, message->piece);
|
|
if (!data) {
|
|
done_string(&string);
|
|
return BITTORRENT_STATE_CACHE_FAILURE;
|
|
}
|
|
|
|
data += message->offset;
|
|
|
|
add_bittorrent_peer_integer(&string, message->piece);
|
|
add_bittorrent_peer_integer(&string, message->offset);
|
|
add_bytes_to_string(&string, data, message->length);
|
|
|
|
update_bittorrent_peer_connection_stats(peer, 0, 0,
|
|
message->length);
|
|
update_bittorrent_connection_stats(peer->bittorrent,
|
|
0, message->length, 0);
|
|
assert(string.length == 13 + message->length);
|
|
break;
|
|
}
|
|
case BITTORRENT_MESSAGE_KEEP_ALIVE:
|
|
assert(string.length == 4);
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_CHOKE:
|
|
case BITTORRENT_MESSAGE_UNCHOKE:
|
|
case BITTORRENT_MESSAGE_INTERESTED:
|
|
case BITTORRENT_MESSAGE_NOT_INTERESTED:
|
|
assert(string.length == 5);
|
|
break;
|
|
|
|
default:
|
|
INTERNAL("Bad message ID");
|
|
}
|
|
|
|
/* Insert the real message length. */
|
|
msglen = string.length - sizeof(uint32_t);
|
|
msglen = htonl(msglen);
|
|
memcpy(string.source, (char *) &msglen, sizeof(msglen));
|
|
|
|
/* Any message will cause bitfield messages to become invalid. */
|
|
peer->local.bitfield = 1;
|
|
|
|
if (message->id != BITTORRENT_MESSAGE_REQUEST) {
|
|
del_from_list(message);
|
|
mem_free(message);
|
|
}
|
|
|
|
write_to_socket(peer->socket, string.source, string.length,
|
|
connection_state(S_TRANS),
|
|
sent_bittorrent_peer_message);
|
|
|
|
done_string(&string);
|
|
|
|
return BITTORRENT_STATE_OK;
|
|
}
|
|
|
|
/* Highlevel backend for sending peer messages. It handles queuing of messages.
|
|
* In order to make this function safe to call from any contexts, messages are
|
|
* NEVER directly written to the peer socket, since that could cause the peer
|
|
* connection struct to disappear from under us. */
|
|
void
|
|
send_bittorrent_peer_message(struct bittorrent_peer_connection *peer,
|
|
bittorrent_message_id_TT message_id, ...)
|
|
{
|
|
struct bittorrent_peer_request message_store, *message = &message_store;
|
|
va_list args;
|
|
|
|
memset(message, 0, sizeof(*message));
|
|
message->id = message_id;
|
|
|
|
va_start(args, message_id);
|
|
|
|
switch (message_id) {
|
|
case BITTORRENT_MESSAGE_CANCEL:
|
|
message->piece = va_arg(args, uint32_t);
|
|
message->offset = va_arg(args, uint32_t);
|
|
message->length = va_arg(args, uint32_t);
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_HAVE:
|
|
message->piece = va_arg(args, uint32_t);
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_BITFIELD:
|
|
case BITTORRENT_MESSAGE_CHOKE:
|
|
case BITTORRENT_MESSAGE_INTERESTED:
|
|
case BITTORRENT_MESSAGE_KEEP_ALIVE:
|
|
case BITTORRENT_MESSAGE_NOT_INTERESTED:
|
|
case BITTORRENT_MESSAGE_UNCHOKE:
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_REQUEST:
|
|
case BITTORRENT_MESSAGE_PIECE:
|
|
/* Piece and piece request messages are generated automaticalle
|
|
* from the request queue the local and remote peer status. */
|
|
default:
|
|
INTERNAL("Bad message ID");
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
message = (struct bittorrent_peer_request *)mem_alloc(sizeof(*message));
|
|
if (!message) return;
|
|
|
|
memcpy(message, &message_store, sizeof(*message));
|
|
|
|
/* Prioritize bitfield cancel messages by putting them in the start of
|
|
* the queue so that bandwidth is not wasted. This way bitfield messages
|
|
* will always be sent before anything else and cancel messages will
|
|
* always arrive before have messages, which our client prefers. */
|
|
if (message->id == BITTORRENT_MESSAGE_BITFIELD
|
|
|| message->id == BITTORRENT_MESSAGE_CANCEL)
|
|
add_to_list(peer->queue, message);
|
|
else
|
|
add_to_list_end(peer->queue, message);
|
|
}
|
|
|
|
|
|
/* ************************************************************************** */
|
|
/* Peer message receiving: */
|
|
/* ************************************************************************** */
|
|
|
|
static inline uint32_t
|
|
get_bittorrent_peer_integer(struct read_buffer *buffer, int offset)
|
|
{
|
|
assert(offset + sizeof(uint32_t) <= buffer->length);
|
|
return ntohl(*((uint32_t *) (buffer->data + offset)));
|
|
}
|
|
|
|
static bittorrent_message_id_T
|
|
check_bittorrent_peer_message(struct bittorrent_peer_connection *peer,
|
|
struct read_buffer *buffer, uint32_t *length)
|
|
{
|
|
uint32_t message_length;
|
|
bittorrent_message_id_T message_id;
|
|
|
|
*length = 0;
|
|
|
|
assert(peer->remote.handshake);
|
|
|
|
if (buffer->length < sizeof(message_length))
|
|
return BITTORRENT_MESSAGE_INCOMPLETE;
|
|
|
|
message_length = get_bittorrent_peer_integer(buffer, 0);
|
|
|
|
if (message_length > get_bittorrent_peerwire_max_message_length())
|
|
return BITTORRENT_MESSAGE_ERROR;
|
|
|
|
if (buffer->length - sizeof(message_length) < message_length)
|
|
return BITTORRENT_MESSAGE_INCOMPLETE;
|
|
|
|
if (message_length == 0)
|
|
return BITTORRENT_MESSAGE_KEEP_ALIVE;
|
|
|
|
message_id = buffer->data[sizeof(message_length)];
|
|
|
|
*length = message_length;
|
|
|
|
return message_id;
|
|
}
|
|
|
|
static enum bittorrent_state
|
|
read_bittorrent_peer_message(struct bittorrent_peer_connection *peer,
|
|
bittorrent_message_id_T message_id,
|
|
struct read_buffer *buffer, uint32_t message_length,
|
|
int *write_errno)
|
|
{
|
|
struct bittorrent_connection *bittorrent = peer->bittorrent;
|
|
enum bittorrent_state state;
|
|
uint32_t piece, offset, length;
|
|
char *data;
|
|
|
|
assert(message_id != BITTORRENT_MESSAGE_INCOMPLETE);
|
|
|
|
*write_errno = 0;
|
|
|
|
switch (message_id) {
|
|
case BITTORRENT_MESSAGE_CHOKE:
|
|
/* Return all pending requests to the free list. */
|
|
peer->local.choked = 1;
|
|
add_requests_to_bittorrent_piece_cache(peer, &peer->local);
|
|
{
|
|
struct bittorrent_peer_request *message, *next;
|
|
|
|
foreachsafe (message, next, peer->queue) {
|
|
if (message->id == BITTORRENT_MESSAGE_CANCEL) {
|
|
del_from_list(message);
|
|
mem_free(message);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_UNCHOKE:
|
|
peer->local.choked = 0;
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_INTERESTED:
|
|
peer->remote.interested = 1;
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_NOT_INTERESTED:
|
|
peer->remote.interested = 0;
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_HAVE:
|
|
if (message_length < sizeof(uint32_t))
|
|
return BITTORRENT_STATE_ERROR;
|
|
|
|
piece = get_bittorrent_peer_integer(buffer, 5);
|
|
/* Is piece out of bound? */
|
|
if (piece >= bittorrent->meta.pieces)
|
|
break;
|
|
|
|
/* Is the piece already recorded? */
|
|
if (test_bitfield_bit(peer->bitfield, piece))
|
|
break;
|
|
|
|
length = get_bittorrent_piece_length(&bittorrent->meta, piece);
|
|
update_bittorrent_peer_connection_stats(peer, 0, length, 0);
|
|
|
|
set_bitfield_bit(peer->bitfield, piece);
|
|
peer->remote.seeder = bitfield_is_set(peer->bitfield);
|
|
|
|
update_bittorrent_piece_cache(peer, piece);
|
|
|
|
if (peer->local.interested
|
|
|| test_bitfield_bit(bittorrent->cache->bitfield, piece))
|
|
break;
|
|
|
|
set_bittorrent_peer_interested(peer);
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_BITFIELD:
|
|
data = buffer->data + 5;
|
|
length = message_length - 1; /* Message ID byte. */
|
|
|
|
if (length > get_bitfield_byte_size(peer->bitfield->bitsize))
|
|
break;
|
|
|
|
/* Are bitfield messages allowed at this point? */
|
|
if (peer->remote.bitfield)
|
|
break;
|
|
|
|
/* XXX: The function tail will set the bitfield flag ... */
|
|
copy_bitfield(peer->bitfield, data, length);
|
|
peer->remote.seeder = bitfield_is_set(peer->bitfield);
|
|
|
|
update_bittorrent_piece_cache_from_bitfield(peer);
|
|
|
|
/* Force checking of the interested flag. */
|
|
foreach_bitfield_set (piece, peer->bitfield) {
|
|
if (test_bitfield_bit(bittorrent->cache->bitfield, piece))
|
|
continue;
|
|
|
|
set_bittorrent_peer_interested(peer);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_REQUEST:
|
|
case BITTORRENT_MESSAGE_CANCEL:
|
|
if (message_length < sizeof(uint32_t) * 3)
|
|
return BITTORRENT_STATE_ERROR;
|
|
|
|
piece = get_bittorrent_peer_integer(buffer, 5);
|
|
offset = get_bittorrent_peer_integer(buffer, 9);
|
|
length = get_bittorrent_peer_integer(buffer, 13);
|
|
|
|
/* FIXME: Should requests be allowed to overlap pieces? */
|
|
if (peer->remote.choked
|
|
|| piece >= bittorrent->meta.pieces
|
|
|| offset + length > get_bittorrent_piece_length(&bittorrent->meta, piece)
|
|
|| !test_bitfield_bit(bittorrent->cache->bitfield, piece))
|
|
break;
|
|
|
|
if (length > get_bittorrent_peerwire_max_request_length())
|
|
return BITTORRENT_STATE_ERROR;
|
|
|
|
if (message_id == BITTORRENT_MESSAGE_REQUEST) {
|
|
add_bittorrent_peer_request(&peer->remote, piece, offset, length);
|
|
} else {
|
|
del_bittorrent_peer_request(&peer->remote, piece, offset, length);
|
|
}
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_PIECE:
|
|
if (message_length < sizeof(uint32_t) * 2)
|
|
return BITTORRENT_STATE_ERROR;
|
|
|
|
piece = get_bittorrent_peer_integer(buffer, 5);
|
|
offset = get_bittorrent_peer_integer(buffer, 9);
|
|
length = message_length - 9; /* Msg ID byte + 2 ints */
|
|
data = buffer->data + 13; /* Offset includes msg len */
|
|
|
|
if (peer->local.choked
|
|
|| piece >= bittorrent->meta.pieces
|
|
|| offset + length > get_bittorrent_piece_length(&bittorrent->meta, piece)
|
|
|| length == 0)
|
|
break;
|
|
|
|
update_bittorrent_peer_connection_stats(peer, length, 0, 0);
|
|
state = add_to_bittorrent_piece_cache(peer, piece, offset, data, length, write_errno);
|
|
if (state != BITTORRENT_STATE_OK)
|
|
return state;
|
|
break;
|
|
|
|
case BITTORRENT_MESSAGE_KEEP_ALIVE:
|
|
default:
|
|
/* Keep-alive messages doesn't require any special handling.
|
|
* Unknown peer messages are simply dropped. */
|
|
break;
|
|
}
|
|
|
|
/* Any message will cause bitfield messages to become invalid. */
|
|
peer->remote.bitfield = 1;
|
|
|
|
return BITTORRENT_STATE_OK;
|
|
}
|
|
|
|
static void
|
|
read_bittorrent_peer_data(struct socket *socket, struct read_buffer *buffer)
|
|
{
|
|
struct bittorrent_peer_connection *peer = (struct bittorrent_peer_connection *)socket->conn;
|
|
|
|
if (!peer->remote.handshake) {
|
|
enum bittorrent_handshake_state state;
|
|
|
|
/* We still needs to read the peer ID from the handshake. */
|
|
state = do_read_bittorrent_peer_handshake(socket, buffer);
|
|
if (state != BITTORRENT_PEER_HANDSHAKE_OK)
|
|
return;
|
|
|
|
/* If the handshake was ok'ed see if there are any messages to
|
|
* read. */
|
|
assert(peer->remote.handshake);
|
|
}
|
|
|
|
/* All messages atleast contains an integer prefix. */
|
|
while (buffer->length > sizeof(uint32_t)) {
|
|
bittorrent_message_id_T message_id;
|
|
uint32_t length;
|
|
int write_errno = 0;
|
|
|
|
message_id = check_bittorrent_peer_message(peer, buffer, &length);
|
|
if (message_id == BITTORRENT_MESSAGE_INCOMPLETE)
|
|
break;
|
|
|
|
if (message_id == BITTORRENT_MESSAGE_ERROR) {
|
|
done_bittorrent_peer_connection(peer);
|
|
return;
|
|
}
|
|
|
|
switch (read_bittorrent_peer_message(peer, message_id, buffer, length, &write_errno)) {
|
|
case BITTORRENT_STATE_OK:
|
|
break;
|
|
|
|
case BITTORRENT_STATE_OUT_OF_MEM:
|
|
abort_connection(peer->bittorrent->conn,
|
|
connection_state(S_OUT_OF_MEM));
|
|
return;
|
|
|
|
case BITTORRENT_STATE_ERROR:
|
|
default:
|
|
if (!write_errno) {
|
|
done_bittorrent_peer_connection(peer);
|
|
return;
|
|
}
|
|
|
|
/* Shutdown on fatal errors! */
|
|
abort_connection(peer->bittorrent->conn,
|
|
connection_state_for_errno(write_errno));
|
|
return;
|
|
}
|
|
|
|
/* Remove the processed data from the input buffer. */
|
|
kill_buffer_data(buffer, length + sizeof(length));
|
|
update_bittorrent_connection_stats(peer->bittorrent, 0, 0,
|
|
length + sizeof(length));
|
|
}
|
|
|
|
update_bittorrent_peer_connection_state(peer);
|
|
}
|
|
|
|
|
|
/* ************************************************************************** */
|
|
/* Peer handshake exchanging: */
|
|
/* ************************************************************************** */
|
|
|
|
/* XXX: Note, handshake messages are also handled above in the function
|
|
* read_bittorrnt_peer_data() if it is incomplete when reading it below.
|
|
* Often, peers will only send up to and including the info hash, so that the
|
|
* peer ID arrives later. */
|
|
|
|
/* The handshake was sent, so notify the remote peer about completed piece in a
|
|
* bitfield message and start reading any remaining bits of the handshake or any
|
|
* other message. */
|
|
static void
|
|
sent_bittorrent_peer_handshake(struct socket *socket)
|
|
{
|
|
struct bittorrent_peer_connection *peer = (struct bittorrent_peer_connection *)socket->conn;
|
|
struct read_buffer *buffer = peer->socket->read_buffer;
|
|
|
|
assert(buffer);
|
|
|
|
/* Only send the bitfield message if there is anything interesting to
|
|
* report. */
|
|
if (peer->bittorrent->cache->completed_pieces) {
|
|
assert(list_empty(peer->queue));
|
|
send_bittorrent_peer_message(peer, BITTORRENT_MESSAGE_BITFIELD);
|
|
}
|
|
|
|
read_from_socket(peer->socket, buffer, connection_state(S_TRANS),
|
|
read_bittorrent_peer_data);
|
|
}
|
|
|
|
/* This function is called when a handhake has been read from an incoming
|
|
* connection and is used as a callback for when a new peer connection has
|
|
* successfully been established. */
|
|
void
|
|
send_bittorrent_peer_handshake(struct socket *socket)
|
|
{
|
|
struct bittorrent_peer_connection *peer = (struct bittorrent_peer_connection *)socket->conn;
|
|
struct bittorrent_connection *bittorrent = peer->bittorrent;
|
|
struct bittorrent_meta *meta = &bittorrent->meta;
|
|
char reserved[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
char handshake[BITTORRENT_PEER_HANDSHAKE_SIZE];
|
|
int i = 0;
|
|
|
|
#define add_to_handshake(handshake, i, data) \
|
|
do { \
|
|
memcpy((handshake) + (i), data, sizeof(data)); \
|
|
i += sizeof(data); \
|
|
} while (0)
|
|
|
|
add_to_handshake(handshake, i, BITTORRENT_ID);
|
|
add_to_handshake(handshake, i, reserved);
|
|
add_to_handshake(handshake, i, meta->info_hash);
|
|
add_to_handshake(handshake, i, bittorrent->peer_id);
|
|
|
|
#undef add_to_handshake
|
|
|
|
assert(handshake[0] == 19);
|
|
|
|
if (!peer->socket->read_buffer) {
|
|
/* Just return. Failure is handled by alloc_read_buffer(). */
|
|
peer->socket->read_buffer = alloc_read_buffer(peer->socket);
|
|
if (!peer->socket->read_buffer) {
|
|
done_bittorrent_peer_connection(peer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
peer->local.handshake = 1;
|
|
|
|
/* Temporarily stop processing of all incoming messages while we send
|
|
* the handshake so that reading of especially the bitfield message
|
|
* won't cause any interested message to be queued _before_ we have a
|
|
* chance to send the bitfield message which MUST be first. */
|
|
clear_handlers(peer->socket->fd);
|
|
|
|
/* Can't use request_.. version because it will create a new read buffer
|
|
* and we might want to hold on to the old buffer if the peer ID of the
|
|
* handshake was not read. */
|
|
write_to_socket(peer->socket, handshake, sizeof(handshake),
|
|
connection_state(S_TRANS),
|
|
sent_bittorrent_peer_handshake);
|
|
}
|
|
|
|
#if 0
|
|
/* DHT is not supported, so commented out. */
|
|
/* Checks for the DHT flags used by atleast Brams client to indicate it supports
|
|
* trackerless BitTorrent. */
|
|
static inline int
|
|
bittorrent_peer_supports_dht(char flags[8])
|
|
{
|
|
return !!(flags[7] & 1);
|
|
}
|
|
#endif
|
|
|
|
/* This function is called each time there is something from the handshake
|
|
* message to read. */
|
|
static enum bittorrent_handshake_state
|
|
check_bittorrent_peer_handshake(struct bittorrent_peer_connection *peer,
|
|
struct read_buffer *buffer)
|
|
{
|
|
bittorrent_id_T info_hash;
|
|
struct bittorrent_peer *peer_info;
|
|
enum bittorrent_handshake_state state;
|
|
|
|
if (buffer->length < 20)
|
|
return BITTORRENT_PEER_HANDSHAKE_INCOMPLETE;
|
|
|
|
if (memcmp(buffer->data, BITTORRENT_ID, sizeof(BITTORRENT_ID)))
|
|
return BITTORRENT_PEER_HANDSHAKE_ERROR;
|
|
|
|
if (buffer->length < 28)
|
|
return BITTORRENT_PEER_HANDSHAKE_INCOMPLETE;
|
|
|
|
#if 0
|
|
/* DHT is not supported, so commented out. */
|
|
/* Check the reserved flags */
|
|
peer->remote.dht = bittorrent_peer_supports_dht(&buffer->data[20]);
|
|
#endif
|
|
|
|
if (buffer->length < 48)
|
|
return BITTORRENT_PEER_HANDSHAKE_INCOMPLETE;
|
|
|
|
memcpy(info_hash, &buffer->data[28], sizeof(info_hash));
|
|
|
|
if (peer->local.initiater) {
|
|
struct bittorrent_meta *meta = &peer->bittorrent->meta;
|
|
|
|
assert(peer->bittorrent);
|
|
|
|
/* Check if the info_hash matches the one in the associated
|
|
* bittorrent connection. */
|
|
if (memcmp(meta->info_hash, info_hash, sizeof(info_hash)))
|
|
return BITTORRENT_PEER_HANDSHAKE_ERROR;
|
|
|
|
if (buffer->length < BITTORRENT_PEER_HANDSHAKE_SIZE)
|
|
return BITTORRENT_PEER_HANDSHAKE_INFO_HASH;
|
|
|
|
/* If we got the peer info using the compact tracker flag there
|
|
* is no peer ID, so set it. Else check if the peer has sent the
|
|
* expected ID. */
|
|
if (bittorrent_id_is_empty(peer->id))
|
|
memcpy(peer->id, &buffer->data[48], sizeof(peer->id));
|
|
|
|
else if (memcmp(peer->id, &buffer->data[48], sizeof(peer->id)))
|
|
return BITTORRENT_PEER_HANDSHAKE_ERROR;
|
|
|
|
} else {
|
|
struct bittorrent_connection *bittorrent = peer->bittorrent;
|
|
|
|
/* If the peer ID is empty we didn't establish the connection. */
|
|
assert(bittorrent_id_is_empty(peer->id));
|
|
|
|
/* Look-up the bittorrent connection and drop peer connections
|
|
* to unknown torrents. */
|
|
if (!bittorrent) {
|
|
bittorrent = find_bittorrent_connection(info_hash);
|
|
if (!bittorrent)
|
|
return BITTORRENT_PEER_HANDSHAKE_ERROR;
|
|
|
|
peer->bittorrent = bittorrent;
|
|
|
|
/* Don't know if this is the right place to do this
|
|
* initialization, but it is the first time we know how
|
|
* many bits there should be room for. Also, it feels
|
|
* safer to have it created before the peer connection
|
|
* is moved to a bittorrent connection so all other code
|
|
* can assume it is always there. */
|
|
peer->bitfield = init_bitfield(bittorrent->meta.pieces);
|
|
if (!peer->bitfield)
|
|
return BITTORRENT_PEER_HANDSHAKE_ERROR;
|
|
}
|
|
|
|
/* FIXME: It would be possible to already add the peer to the
|
|
* neighbor list here. Should we? --jonas */
|
|
|
|
if (buffer->length < BITTORRENT_PEER_HANDSHAKE_SIZE)
|
|
return BITTORRENT_PEER_HANDSHAKE_INFO_HASH;
|
|
|
|
memcpy(peer->id, &buffer->data[48], sizeof(peer->id));
|
|
}
|
|
|
|
assert(peer->bittorrent);
|
|
|
|
/* Remove any recorded peer from the peer info list. */
|
|
/* XXX: This has to be done before checking if the peer is known. Since
|
|
* known in this case means whether the peer already has a connection
|
|
* associated. */
|
|
peer_info = get_peer_from_bittorrent_pool(peer->bittorrent, peer->id);
|
|
if (peer_info) {
|
|
del_from_list(peer_info);
|
|
mem_free(peer_info);
|
|
}
|
|
|
|
/* Even if the peer is already associated with a connection we still
|
|
* needs to check if the ID is known since we might have just gotten it.
|
|
* Removing it first makes that possible. */
|
|
del_from_list(peer);
|
|
|
|
/* Check if the peer is already connected to us. */
|
|
state = bittorrent_id_is_known(peer->bittorrent, peer->id)
|
|
? BITTORRENT_PEER_HANDSHAKE_ERROR : BITTORRENT_PEER_HANDSHAKE_OK;
|
|
|
|
/* For unassociated connections; move the peer connection from the list
|
|
* of unknown pending peer connections to a bittorrent connection's list
|
|
* of active peers. */
|
|
add_to_list(peer->bittorrent->peers, peer);
|
|
|
|
return state;
|
|
}
|
|
|
|
/* Common backend for reading handshakes. */
|
|
static enum bittorrent_handshake_state
|
|
do_read_bittorrent_peer_handshake(struct socket *socket, struct read_buffer *buffer)
|
|
{
|
|
struct bittorrent_peer_connection *peer = (struct bittorrent_peer_connection *)socket->conn;
|
|
enum bittorrent_handshake_state state;
|
|
|
|
state = check_bittorrent_peer_handshake(peer, buffer);
|
|
|
|
switch (state) {
|
|
case BITTORRENT_PEER_HANDSHAKE_OK:
|
|
/* The whole handshake was successfully read. */
|
|
peer->remote.handshake = 1;
|
|
kill_buffer_data(buffer, BITTORRENT_PEER_HANDSHAKE_SIZE);
|
|
update_bittorrent_connection_stats(peer->bittorrent, 0, 0,
|
|
BITTORRENT_PEER_HANDSHAKE_SIZE);
|
|
|
|
switch (get_bittorrent_blacklist_flags(peer->id)) {
|
|
case BITTORRENT_BLACKLIST_PEER_POOL:
|
|
case BITTORRENT_BLACKLIST_NONE:
|
|
break;
|
|
|
|
case BITTORRENT_BLACKLIST_MALICIOUS:
|
|
case BITTORRENT_BLACKLIST_BEHAVIOUR:
|
|
default:
|
|
done_bittorrent_peer_connection(peer);
|
|
return BITTORRENT_PEER_HANDSHAKE_ERROR;
|
|
}
|
|
|
|
if (!peer->local.handshake)
|
|
send_bittorrent_peer_handshake(socket);
|
|
break;
|
|
|
|
case BITTORRENT_PEER_HANDSHAKE_INFO_HASH:
|
|
if (!peer->local.handshake) {
|
|
send_bittorrent_peer_handshake(socket);
|
|
/* XXX: The peer connection might have disappear from
|
|
* under us at this point so do not reregister the
|
|
* socket for reading. */
|
|
break;
|
|
}
|
|
|
|
read_from_socket(peer->socket, buffer,
|
|
connection_state(S_TRANS),
|
|
read_bittorrent_peer_handshake);
|
|
break;
|
|
|
|
case BITTORRENT_PEER_HANDSHAKE_ERROR:
|
|
done_bittorrent_peer_connection(peer);
|
|
break;
|
|
|
|
case BITTORRENT_PEER_HANDSHAKE_INCOMPLETE:
|
|
/* The whole handshake was not read so wait for more. */
|
|
read_from_socket(peer->socket, buffer,
|
|
connection_state(S_TRANS),
|
|
read_bittorrent_peer_handshake);
|
|
break;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
void
|
|
read_bittorrent_peer_handshake(struct socket *socket, struct read_buffer *buffer)
|
|
{
|
|
do_read_bittorrent_peer_handshake(socket, buffer);
|
|
}
|