📄 peerconnect.c
字号:
/* BitTorrent peer-wire connection management */#ifdef HAVE_CONFIG_H#include "config.h"#endif#include <errno.h>#include <stdio.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 "network/state.h"#include "osdep/osdep.h"#include "protocol/bittorrent/common.h"#include "protocol/bittorrent/peerwire.h"#include "protocol/bittorrent/peerconnect.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"#include "util/time.h"/* Only one port is opened and shared between all running BitTorrent * connections. This holds the descripter of the listening socket. */static int bittorrent_socket = -1;/* The active BitTorrent connections sharing the above listening port. */static INIT_LIST_HEAD(bittorrent_connections);/* The incoming (and pending anonymous) peer connections which has not yet been * assigned to a BitTorrent connection because the info hash has not been read * from the handshake. */static INIT_LIST_HEAD(bittorrent_peer_connections);/* Loop the bittorrent connection list and return matching connection * or NULL. */struct bittorrent_connection *find_bittorrent_connection(bittorrent_id_T info_hash){ struct bittorrent_connection *bittorrent; foreach (bittorrent, bittorrent_connections) if (!memcmp(bittorrent->meta.info_hash, info_hash, sizeof(info_hash))) return bittorrent; return NULL;}static voidcheck_bittorrent_peer_blacklisting(struct bittorrent_peer_connection *peer, enum connection_state state){ enum bittorrent_blacklist_flags flags = BITTORRENT_BLACKLIST_NONE; if (bittorrent_id_is_empty(peer->id) || !get_opt_bool("protocol.http.bugs.allow_blacklist")) return; switch (state) { case -ECONNREFUSED: case -ENETUNREACH: flags |= BITTORRENT_BLACKLIST_PEER_POOL; break; case S_CANT_WRITE: case S_CANT_READ: if (!peer->local.handshake || !peer->remote.handshake) flags |= BITTORRENT_BLACKLIST_PEER_POOL; break; default: break; } if (flags != BITTORRENT_BLACKLIST_NONE) { add_bittorrent_blacklist_flags(peer->id, flags); }}/* ************************************************************************** *//* Timeout scheduling: *//* ************************************************************************** */static voidbittorrent_peer_connection_timeout(struct bittorrent_peer_connection *peer){ /* Unset the timer so it won't get stopped when removing the peer * connection. */ peer->timer = TIMER_ID_UNDEF; done_bittorrent_peer_connection(peer);}/* The timeout mechanism is used for both inactive incoming peer connections * and peer connections attached to a BitTorrent (master) connection. */voidset_bittorrent_peer_connection_timeout(struct bittorrent_peer_connection *peer){ milliseconds_T timeout = sec_to_ms(get_opt_int("protocol.bittorrent.peerwire.timeout")); kill_timer(&peer->timer); install_timer(&peer->timer, timeout, (void (*)(void *)) bittorrent_peer_connection_timeout, peer);}/* ************************************************************************** *//* Socket callback implementation: *//* ************************************************************************** *//* Called when the connection changes state. Usually state starts out being * S_DMS (while looking up the host) then moves to S_CONN (while connecting), * and should hopefully become S_TRANS (while transfering). Note, state can hold * both internally defined connection states as described above and errno * values, such as ECONNREFUSED. The errno values are passed negative so in the * previous example the errno would be passed as -ECONNREFUSED. */static voidset_bittorrent_socket_state(struct socket *socket, enum connection_state state){ struct bittorrent_peer_connection *peer = socket->conn; if (state == S_TRANS && peer->bittorrent) set_connection_state(peer->bittorrent->conn, S_TRANS);}/* Called when progress is made such as when the select() loop detects and * schedules reads and writes. The state variable must be ignored. */static voidset_bittorrent_socket_timeout(struct socket *socket, enum connection_state state){ assert(state == 0); set_bittorrent_peer_connection_timeout(socket->conn);}/* Called when a non-fatal error condition has appeared, i.e. the condition is * caused by some internal or local system error or simply a timeout. */static voidretry_bittorrent_socket(struct socket *socket, enum connection_state state){ struct bittorrent_peer_connection *peer = socket->conn; check_bittorrent_peer_blacklisting(peer, state); /* FIXME: Maybe we should try to reconnect (or simply add connect info * to the peer info list) , but only if we initiated the connection, * i.e. if peer->local.initiater == 1, since it could be just the * tracker probing us. */ done_bittorrent_peer_connection(peer);}/* Called when a fatal and unrecoverable error condition has appeared, such as a * DNS query failed. */static voiddone_bittorrent_socket(struct socket *socket, enum connection_state state){ struct bittorrent_peer_connection *peer = socket->conn; check_bittorrent_peer_blacklisting(peer, state); done_bittorrent_peer_connection(peer);}/* All the above socket handlers are attached to the socket via this table. */static struct socket_operations bittorrent_socket_operations = { set_bittorrent_socket_state, set_bittorrent_socket_timeout, retry_bittorrent_socket, done_bittorrent_socket,};/* ************************************************************************** *//* Peer connection management: *//* ************************************************************************** *//* Allocate and initialize a basic peer connection either for incoming or * outgoing connection. */static struct bittorrent_peer_connection *init_bittorrent_peer_connection(int socket){ struct bittorrent_peer_connection *peer; peer = mem_calloc(1, sizeof(*peer)); if (!peer) return NULL; peer->socket = init_socket(peer, &bittorrent_socket_operations); if (!peer->socket) { mem_free(peer); return NULL; } /* We want simultaneous reads and writes. */ peer->socket->duplex = 1; peer->socket->fd = socket; init_list(peer->local.requests); init_list(peer->remote.requests); init_list(peer->queue); /* Peers start out being choked and not being interested. */ peer->local.choked = 1; peer->remote.choked = 1; return peer;}/* Shutdown an remove a peer connection from what ever context it * is currently attached, be it the. */voiddone_bittorrent_peer_connection(struct bittorrent_peer_connection *peer){ del_from_list(peer); /* The peer might not have been associated with a BitTorrent connection, * yet. */ if (peer->bittorrent) { add_requests_to_bittorrent_piece_cache(peer, &peer->local); if (peer->bitfield) { remove_bittorrent_peer_from_piece_cache(peer); mem_free(peer->bitfield); } } free_list(peer->remote.requests); free_list(peer->queue); kill_timer(&peer->timer); /* Unregister the socket from the select() loop mechanism. */ done_socket(peer->socket); mem_free(peer->socket); mem_free(peer);}/* Establish connection to a peer. As a backend, it uses the internal and more * generic connection creater which takes care of DNS querying etc. */enum bittorrent_statemake_bittorrent_peer_connection(struct bittorrent_connection *bittorrent, struct bittorrent_peer *peer_info){ struct uri uri; struct bittorrent_peer_connection *peer; unsigned char port[5]; peer = init_bittorrent_peer_connection(-1); if (!peer) return BITTORRENT_STATE_OUT_OF_MEM; peer->local.initiater = 1; add_to_list(bittorrent->peers, peer); peer->bittorrent = bittorrent; peer->bitfield = init_bitfield(bittorrent->meta.pieces); if (!peer->bitfield) { done_bittorrent_peer_connection(peer); return BITTORRENT_STATE_OUT_OF_MEM; } memcpy(peer->id, peer_info->id, sizeof(peer->id)); /* XXX: Very hacky; construct a fake URI from which make_connection() * can extract the IP address and port number. */ /* FIXME: Rather change the make_connection() interface. This is an ugly * hack. */ /* FIXME: Set the ipv6 flag iff ... */ memset(&uri, 0, sizeof(uri)); uri.protocol = PROTOCOL_BITTORRENT; uri.host = peer_info->ip; uri.hostlen = strlen(peer_info->ip); uri.port = port; uri.portlen = snprintf(port, sizeof(port), "%u", peer_info->port); make_connection(peer->socket, &uri, send_bittorrent_peer_handshake, 1); return BITTORRENT_STATE_OK;}/* ************************************************************************** *//* Listening socket management: *//* ************************************************************************** *//* Number of connections to keep in the listening backlog before dropping new * ones. */#define LISTEN_BACKLOG \ get_opt_int("protocol.bittorrent.peerwire.connections")/* Called when we receive a connection on the listening socket. */static voidaccept_bittorrent_peer_connection(void *____){ struct sockaddr_in addr; int peer_sock; int addrlen = sizeof(addr); struct bittorrent_peer_connection *peer; struct read_buffer *buffer; peer_sock = accept(bittorrent_socket, (struct sockaddr *) &addr, &addrlen); if (peer_sock < 0) return; if (set_nonblocking_fd(peer_sock) < 0) { close(peer_sock); return; } peer = init_bittorrent_peer_connection(peer_sock); if (!peer) { close(peer_sock); return; } peer->remote.initiater = 1; /* Just return. Failure is handled by alloc_read_buffer(). */ buffer = alloc_read_buffer(peer->socket); if (!buffer) return; read_from_socket(peer->socket, buffer, S_TRANS, read_bittorrent_peer_handshake); add_to_list(bittorrent_peer_connections, peer);}/* Based on network/socket.c:get_pasv_socket() but modified to try and bind to a * port range instead of any port. */enum connection_stateinit_bittorrent_listening_socket(struct connection *conn){ struct bittorrent_connection *bittorrent = conn->info; struct sockaddr_in addr, addr2; uint16_t port, max_port; int len; /* XXX: Always add the connection to the list even if we fail so we can * safely assume it is in done_bittorrent_listening_socket(). */ add_to_list(bittorrent_connections, bittorrent); /* Has the socket already been initialized? */ if (!list_is_singleton(bittorrent_connections)) return S_OK; /* We could have bailed out from an earlier attempt. */ if (bittorrent_socket != -1) close(bittorrent_socket); bittorrent_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (bittorrent_socket < 0) return -errno; /* Set it non-blocking */ if (set_nonblocking_fd(bittorrent_socket) < 0) return -errno; /* Bind it to some port */ port = get_opt_int("protocol.bittorrent.ports.min"); max_port = get_opt_int("protocol.bittorrent.ports.max"); memset(&addr, 0, sizeof(addr)); addr.sin_port = htons(port); /* Repeatedly try the configured port range. */ while (bind(bittorrent_socket, (struct sockaddr *) &addr, sizeof(addr))) { if (errno != EADDRINUSE) return -errno; /* If all ports was in use fail with EADDRINUSE. */ if (++port > max_port) return -errno; memset(&addr, 0, sizeof(addr)); addr.sin_port = htons(port); } /* Get the endpoint info about the new socket and save it */ memset(&addr2, 0, sizeof(addr2)); len = sizeof(addr2); if (getsockname(bittorrent_socket, (struct sockaddr *) &addr2, &len)) return -errno; bittorrent->port = ntohs(addr2.sin_port); /* Go listen */ if (listen(bittorrent_socket, LISTEN_BACKLOG)) return -errno; set_ip_tos_throughput(bittorrent_socket); set_handlers(bittorrent_socket, accept_bittorrent_peer_connection, NULL, NULL, NULL); return S_OK;}voiddone_bittorrent_listening_socket(struct connection *conn){ struct bittorrent_connection *connection, *bittorrent = conn->info; /* The bittorrent connection might not even have been added if the * request for the metainfo file failed so carefully look it up. */ foreach (connection, bittorrent_connections) if (connection == bittorrent) { del_from_list(bittorrent); break; } /* If there are no more connections left remove all pending peer * connections. */ if (list_empty(bittorrent_connections)) { struct bittorrent_peer_connection *peer, *next; foreachsafe (peer, next, bittorrent_peer_connections) done_bittorrent_peer_connection(peer); } /* Close the listening socket. */ if (bittorrent_socket != -1) { /* Unregister the socket from the select() loop mechanism. */ clear_handlers(bittorrent_socket); close(bittorrent_socket); bittorrent_socket = -1; }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -