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 + -
显示快捷键?