peerwire.c

来自「elinks下lynx是最重要的二个文本浏览器, 在linux下非常实用, el」· C语言 代码 · 共 961 行 · 第 1/2 页

C
961
字号
/* 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 = "\023BitTorrent protocol";/* 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_statedo_send_bittorrent_peer_message(struct bittorrent_peer_connection *peer,				struct bittorrent_peer_request *message);static enum bittorrent_handshake_statedo_read_bittorrent_peer_handshake(struct socket *socket, struct read_buffer *buffer);/* ************************************************************************** *//* Peer state managing: *//* ************************************************************************** *//* Queue requests up to the configured limit. */static voidqueue_bittorrent_peer_connection_requests(struct bittorrent_peer_connection *peer){	int size = get_opt_int("protocol.bittorrent.request_queue_size");	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. */voidupdate_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 doubleget_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);}voidupdate_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 voidsent_bittorrent_peer_message(struct socket *socket){	assert(!socket->write_buffer);	/* Check if there are pending messages or requests. */	update_bittorrent_peer_connection_state(socket->conn);}static inline voidadd_bittorrent_peer_integer(struct string *string, uint32_t integer){	uint32_t data = htonl(integer);	add_bytes_to_string(string, (unsigned char *) &data, sizeof(data));}/* Common lowlevel backend for composing a peer message and writing it to the * socket. */static enum bittorrent_statedo_send_bittorrent_peer_message(struct bittorrent_peer_connection *peer,				struct bittorrent_peer_request *message){	struct bittorrent_connection *bittorrent = peer->bittorrent;	struct string string;	unsigned char msgid_str[1] = { (unsigned 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, (unsigned 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, 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:	{		unsigned 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, (unsigned 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,			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. */voidsend_bittorrent_peer_message(struct bittorrent_peer_connection *peer,			     enum bittorrent_message_id 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 = 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_tget_bittorrent_peer_integer(struct read_buffer *buffer, int offset){	assert(offset + sizeof(uint32_t) <= buffer->length);	return ntohl(*((uint32_t *) (buffer->data + offset)));}static enum bittorrent_message_idcheck_bittorrent_peer_message(struct bittorrent_peer_connection *peer,			      struct read_buffer *buffer, uint32_t *length){	uint32_t message_length;	enum bittorrent_message_id 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_stateread_bittorrent_peer_message(struct bittorrent_peer_connection *peer,			     enum bittorrent_message_id 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;	unsigned 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. */

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?