1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-06-24 00:56:14 +00:00
elinks/src/protocol/bittorrent/peerwire.c
2022-06-23 21:39:11 +02:00

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);
}