2005-09-15 09:58:31 -04:00
|
|
|
/* Internal "bittorrent" protocol implementation */
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2021-12-22 10:27:29 -05:00
|
|
|
#include <sys/types.h>
|
|
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
|
|
#include <sys/socket.h> /* OS/2 needs this after sys/types.h */
|
|
|
|
#endif
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
#include "elinks.h"
|
|
|
|
|
|
|
|
#include "cache/cache.h"
|
|
|
|
#include "config/options.h"
|
|
|
|
#include "main/timer.h"
|
|
|
|
#include "network/connection.h"
|
|
|
|
#include "network/progress.h"
|
|
|
|
#include "network/socket.h"
|
|
|
|
#include "protocol/bittorrent/bencoding.h"
|
|
|
|
#include "protocol/bittorrent/bittorrent.h"
|
|
|
|
#include "protocol/bittorrent/common.h"
|
|
|
|
#include "protocol/bittorrent/connection.h"
|
|
|
|
#include "protocol/bittorrent/tracker.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 "session/download.h"
|
|
|
|
#include "util/bitfield.h"
|
|
|
|
#include "util/conv.h"
|
|
|
|
#include "util/memory.h"
|
|
|
|
#include "util/string.h"
|
|
|
|
#include "util/time.h"
|
|
|
|
|
|
|
|
|
|
|
|
/* ************************************************************************** */
|
|
|
|
/* Peer selection and connection scheduling: */
|
|
|
|
/* ************************************************************************** */
|
|
|
|
|
|
|
|
/* Reschedule updating of the connection state. */
|
|
|
|
static void
|
|
|
|
set_bittorrent_connection_timer(struct connection *conn)
|
|
|
|
{
|
|
|
|
struct bittorrent_connection *bittorrent = conn->info;
|
2007-08-28 12:41:18 -04:00
|
|
|
milliseconds_T interval = sec_to_ms(get_opt_int("protocol.bittorrent.choke_interval", NULL));
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
install_timer(&bittorrent->timer, interval,
|
|
|
|
(void (*)(void *)) update_bittorrent_connection_state,
|
|
|
|
conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sort the peers based on the stats rate, bubbaly style! */
|
|
|
|
static void
|
|
|
|
sort_bittorrent_peer_connections(struct bittorrent_connection *bittorrent)
|
|
|
|
{
|
|
|
|
struct bittorrent_peer_connection *peer, *prev;
|
|
|
|
|
2005-12-13 10:52:08 -05:00
|
|
|
while (1) {
|
2005-09-15 09:58:31 -04:00
|
|
|
struct bittorrent_peer_connection *next;
|
2005-12-13 10:52:08 -05:00
|
|
|
int resort = 0;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
prev = NULL;
|
|
|
|
|
|
|
|
foreachsafe (peer, next, bittorrent->peers) {
|
|
|
|
if (prev && prev->stats.download_rate < peer->stats.download_rate) {
|
|
|
|
resort = 1;
|
|
|
|
del_from_list(prev);
|
|
|
|
add_at_pos(peer, prev);
|
|
|
|
}
|
|
|
|
|
|
|
|
prev = peer;
|
|
|
|
}
|
|
|
|
|
2005-12-13 10:52:08 -05:00
|
|
|
if (!resort) break;
|
|
|
|
};
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
#ifdef CONFIG_DEBUG
|
|
|
|
prev = NULL;
|
|
|
|
foreach (peer, bittorrent->peers) {
|
|
|
|
assert(!prev || prev->stats.download_rate >= peer->stats.download_rate);
|
|
|
|
prev = peer;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2006-12-02 11:35:03 -05:00
|
|
|
/* Timer callback for @bittorrent->timer. As explained in @install_timer,
|
|
|
|
* this function must erase the expired timer ID from all variables.
|
|
|
|
*
|
|
|
|
* This is basically the choke period handler. */
|
2005-09-15 09:58:31 -04:00
|
|
|
void
|
|
|
|
update_bittorrent_connection_state(struct connection *conn)
|
|
|
|
{
|
|
|
|
struct bittorrent_connection *bittorrent = conn->info;
|
|
|
|
struct bittorrent_peer_connection *peer, *next_peer;
|
|
|
|
int peer_conns, max_peer_conns;
|
2007-08-28 12:41:18 -04:00
|
|
|
int min_uploads = get_opt_int("protocol.bittorrent.min_uploads", NULL);
|
|
|
|
int max_uploads = get_opt_int("protocol.bittorrent.max_uploads", NULL);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
set_bittorrent_connection_timer(conn);
|
2006-12-02 11:35:03 -05:00
|
|
|
/* The expired timer ID has now been erased. */
|
2005-09-15 09:58:31 -04:00
|
|
|
set_connection_timeout(conn);
|
|
|
|
|
|
|
|
peer_conns = list_size(&bittorrent->peers);
|
2007-08-28 12:41:18 -04:00
|
|
|
max_peer_conns = get_opt_int("protocol.bittorrent.peerwire.connections", NULL);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* First ``age'' the peer rates _before_ the sorting. */
|
|
|
|
foreach (peer, bittorrent->peers)
|
|
|
|
update_bittorrent_peer_connection_stats(peer, 0, 0, 0);
|
|
|
|
|
|
|
|
/* Sort the peers so that the best peers are at the list start. */
|
|
|
|
sort_bittorrent_peer_connections(bittorrent);
|
|
|
|
|
|
|
|
/* Unchoke all the optimal peers. In good spirit, also unchoke all
|
|
|
|
* uninterested peers until the maximum number of interested peers have
|
|
|
|
* been unchoked. The rest is choked. */
|
|
|
|
foreachsafe (peer, next_peer, bittorrent->peers) {
|
|
|
|
if (!peer->remote.handshake)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (min_uploads < max_uploads) {
|
2006-05-20 08:11:27 -04:00
|
|
|
unchoke_bittorrent_peer(peer);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* Uninterested peers are not counted as uploads. */
|
|
|
|
if (peer->remote.interested)
|
|
|
|
max_uploads--;
|
|
|
|
|
|
|
|
} else {
|
2006-05-20 08:07:14 -04:00
|
|
|
choke_bittorrent_peer(peer);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Can remove the peer so we use foreachsafe(). */
|
|
|
|
update_bittorrent_peer_connection_state(peer);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: Find peer(s) to optimistically unchoke. */
|
|
|
|
|
|
|
|
update_bittorrent_piece_cache_state(bittorrent);
|
|
|
|
|
|
|
|
/* Close or open peers connections. */
|
|
|
|
if (peer_conns > max_peer_conns) {
|
|
|
|
struct bittorrent_peer_connection *prev;
|
|
|
|
|
|
|
|
foreachsafe (peer, prev, bittorrent->peers) {
|
|
|
|
done_bittorrent_peer_connection(peer);
|
|
|
|
if (--peer_conns <= max_peer_conns)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (peer_conns < max_peer_conns) {
|
|
|
|
struct bittorrent_peer *peer_info, *next_peer_info;
|
|
|
|
|
|
|
|
foreachsafe (peer_info, next_peer_info, bittorrent->peer_pool) {
|
|
|
|
enum bittorrent_state state;
|
|
|
|
|
|
|
|
state = make_bittorrent_peer_connection(bittorrent, peer_info);
|
|
|
|
if (state != BITTORRENT_STATE_OK)
|
|
|
|
break;
|
|
|
|
|
|
|
|
del_from_list(peer_info);
|
|
|
|
mem_free(peer_info);
|
|
|
|
if (++peer_conns >= max_peer_conns)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(peer_conns <= max_peer_conns);
|
|
|
|
|
|
|
|
/* Shrink the peer pool. */
|
|
|
|
if (!list_empty(bittorrent->peers)) {
|
|
|
|
struct bittorrent_peer *peer_info, *next_peer_info;
|
2007-08-28 12:41:18 -04:00
|
|
|
int pool_size = get_opt_int("protocol.bittorrent.peerwire.pool_size", NULL);
|
2005-09-15 09:58:31 -04:00
|
|
|
int pool_peers = 0;
|
|
|
|
|
|
|
|
foreachsafe (peer_info, next_peer_info, bittorrent->peer_pool) {
|
|
|
|
/* Unlimited. */
|
|
|
|
if (!pool_size) break;
|
|
|
|
|
|
|
|
if (pool_peers < pool_size) {
|
|
|
|
pool_peers++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
del_from_list(peer_info);
|
|
|
|
mem_free(peer_info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Erase progress.timer before calling progress.timer_func
Previously, each progress timer function registered with
start_update_progress() was directly used as the timer function of
progress.timer, so it was responsible of erasing the expired timer ID
from that member. Failing to do this could result in heap corruption.
The progress timer functions normally fulfilled the requirement by
calling update_progress(), but one such function upload_stat_timer()
had to erase the timer ID on its own too.
Now instead, there is a wrapper function progress_timeout(), which
progress.c sets as the timer function of progress.timer. This wrapper
erases the expired timer ID from progress.timer and then calls the
progress timer function registered with start_update_progress(). So
the progress timer function is no longer responsible of erasing the
timer ID and there's no risk that it could fail to do that in some
error situation.
This commit introduces a new risk though. Previously, if the struct
progress was freed while the timer was running, the (progress) timer
function would still be called, and it would be able to detect that
the progress pointer is NULL and recover from this situation. Now,
the timer function progress_timeout() has a pointer to the struct
progress and will dereference that pointer without being able to check
whether the structure has been freed. Fortunately, done_progress()
asserts that the timer is not running, so this should not occur.
2008-06-15 04:25:33 -04:00
|
|
|
/* Progress timer callback for @bittorrent->upload_progress. */
|
2005-09-15 09:58:31 -04:00
|
|
|
static void
|
|
|
|
update_bittorrent_connection_upload(void *data)
|
|
|
|
{
|
|
|
|
struct bittorrent_connection *bittorrent = data;
|
|
|
|
|
|
|
|
update_progress(&bittorrent->upload_progress,
|
|
|
|
bittorrent->uploaded,
|
|
|
|
bittorrent->downloaded,
|
|
|
|
bittorrent->uploaded);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
update_bittorrent_connection_stats(struct bittorrent_connection *bittorrent,
|
|
|
|
off_t downloaded, off_t uploaded,
|
|
|
|
off_t received)
|
|
|
|
{
|
|
|
|
struct bittorrent_meta *meta = &bittorrent->meta;
|
|
|
|
|
|
|
|
if (bittorrent->conn->est_length == -1) {
|
|
|
|
off_t length = (off_t) (meta->pieces - 1) * meta->piece_length
|
|
|
|
+ meta->last_piece_length;
|
|
|
|
|
|
|
|
bittorrent->conn->est_length = length;
|
|
|
|
bittorrent->left = length;
|
|
|
|
start_update_progress(&bittorrent->upload_progress,
|
|
|
|
update_bittorrent_connection_upload,
|
|
|
|
bittorrent);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bittorrent->upload_progress.timer == TIMER_ID_UNDEF
|
|
|
|
&& bittorrent->uploaded)
|
|
|
|
update_bittorrent_connection_upload(bittorrent);
|
|
|
|
|
|
|
|
bittorrent->conn->received += received;
|
|
|
|
bittorrent->conn->from += downloaded;
|
|
|
|
if (downloaded > 0)
|
|
|
|
bittorrent->downloaded += downloaded;
|
|
|
|
bittorrent->uploaded += uploaded;
|
|
|
|
bittorrent->left -= downloaded;
|
|
|
|
|
|
|
|
if (!bittorrent->downloaded) return;
|
|
|
|
|
|
|
|
bittorrent->sharing_rate = (double) bittorrent->uploaded
|
|
|
|
/ bittorrent->downloaded;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ************************************************************************** */
|
|
|
|
/* The ``main'' BitTorrent connection setup: */
|
|
|
|
/* ************************************************************************** */
|
|
|
|
|
|
|
|
/* Callback which is attached to the ELinks connection and invoked when the
|
|
|
|
* connection is shutdown. */
|
|
|
|
static void
|
|
|
|
done_bittorrent_connection(struct connection *conn)
|
|
|
|
{
|
|
|
|
struct bittorrent_connection *bittorrent = conn->info;
|
|
|
|
struct bittorrent_peer_connection *peer, *next;
|
|
|
|
|
|
|
|
assert(bittorrent);
|
2008-05-21 20:59:33 -04:00
|
|
|
assert(conn->done == done_bittorrent_connection);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* We don't want the tracker to see the fetch. */
|
|
|
|
if (bittorrent->fetch)
|
|
|
|
done_bittorrent_fetch(&bittorrent->fetch);
|
|
|
|
|
|
|
|
foreachsafe (peer, next, bittorrent->peers)
|
|
|
|
done_bittorrent_peer_connection(peer);
|
|
|
|
|
|
|
|
done_bittorrent_tracker_connection(conn);
|
|
|
|
done_bittorrent_listening_socket(conn);
|
|
|
|
if (bittorrent->cache)
|
|
|
|
done_bittorrent_piece_cache(bittorrent);
|
|
|
|
done_bittorrent_meta(&bittorrent->meta);
|
|
|
|
|
|
|
|
kill_timer(&bittorrent->timer);
|
|
|
|
kill_timer(&bittorrent->upload_progress.timer);
|
|
|
|
|
|
|
|
free_list(bittorrent->peer_pool);
|
|
|
|
|
|
|
|
mem_free_set(&conn->info, NULL);
|
2008-05-21 20:59:33 -04:00
|
|
|
conn->done = NULL;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct bittorrent_connection *
|
|
|
|
init_bittorrent_connection(struct connection *conn)
|
|
|
|
{
|
|
|
|
struct bittorrent_connection *bittorrent;
|
|
|
|
|
2008-05-21 20:59:33 -04:00
|
|
|
assert(conn->info == NULL);
|
|
|
|
assert(conn->done == NULL);
|
|
|
|
if_assert_failed return NULL;
|
|
|
|
|
2022-01-16 15:08:50 -05:00
|
|
|
bittorrent = (struct bittorrent_connection *)mem_calloc(1, sizeof(*bittorrent));
|
2005-09-15 09:58:31 -04:00
|
|
|
if (!bittorrent) return NULL;
|
|
|
|
|
|
|
|
init_list(bittorrent->peers);
|
|
|
|
init_list(bittorrent->peer_pool);
|
|
|
|
|
2008-05-21 20:59:33 -04:00
|
|
|
/* conn->info and conn->done were asserted as NULL above. */
|
2005-09-15 09:58:31 -04:00
|
|
|
conn->info = bittorrent;
|
|
|
|
conn->done = done_bittorrent_connection;
|
|
|
|
|
|
|
|
init_bittorrent_peer_id(bittorrent->peer_id);
|
|
|
|
|
|
|
|
bittorrent->conn = conn;
|
|
|
|
bittorrent->tracker.timer = TIMER_ID_UNDEF;
|
|
|
|
|
2006-11-06 10:23:20 -05:00
|
|
|
/* Initialize here so that error handling can safely call
|
|
|
|
* free_list on it. */
|
|
|
|
init_list(bittorrent->meta.files);
|
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
return bittorrent;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
bittorrent_resume_callback(struct bittorrent_connection *bittorrent)
|
|
|
|
{
|
2008-08-03 08:24:26 -04:00
|
|
|
struct connection_state state;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* Failing to create the listening socket is fatal. */
|
|
|
|
state = init_bittorrent_listening_socket(bittorrent->conn);
|
2008-08-03 08:24:26 -04:00
|
|
|
if (!is_in_state(state, S_OK)) {
|
2005-09-15 09:58:31 -04:00
|
|
|
retry_connection(bittorrent->conn, state);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-08-03 08:24:26 -04:00
|
|
|
set_connection_state(bittorrent->conn, connection_state(S_CONN_TRACKER));
|
2005-09-15 09:58:31 -04:00
|
|
|
send_bittorrent_tracker_request(bittorrent->conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Metainfo file download callback */
|
|
|
|
static void
|
2008-08-03 08:24:26 -04:00
|
|
|
bittorrent_metainfo_callback(void *data, struct connection_state state,
|
2008-01-26 08:29:13 -05:00
|
|
|
struct bittorrent_const_string *response)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
|
|
|
struct connection *conn = data;
|
|
|
|
struct bittorrent_connection *bittorrent = conn->info;
|
|
|
|
|
|
|
|
bittorrent->fetch = NULL;
|
|
|
|
|
2008-08-03 08:24:26 -04:00
|
|
|
if (!is_in_state(state, S_OK)) {
|
2005-09-15 09:58:31 -04:00
|
|
|
abort_connection(conn, state);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (parse_bittorrent_metafile(&bittorrent->meta, response)) {
|
|
|
|
case BITTORRENT_STATE_OK:
|
|
|
|
{
|
|
|
|
size_t size = list_size(&bittorrent->meta.files);
|
|
|
|
int *selection;
|
|
|
|
|
|
|
|
assert(bittorrent->tracker.event == BITTORRENT_EVENT_STARTED);
|
|
|
|
|
|
|
|
selection = get_bittorrent_selection(conn->uri, size);
|
|
|
|
if (selection) {
|
|
|
|
struct bittorrent_file *file;
|
|
|
|
int index = 0;
|
|
|
|
|
|
|
|
foreach (file, bittorrent->meta.files)
|
|
|
|
file->selected = selection[index++];
|
|
|
|
|
|
|
|
mem_free(selection);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (init_bittorrent_piece_cache(bittorrent, response)) {
|
|
|
|
case BITTORRENT_STATE_OK:
|
|
|
|
bittorrent_resume_callback(bittorrent);
|
|
|
|
return;
|
|
|
|
|
|
|
|
case BITTORRENT_STATE_CACHE_RESUME:
|
2008-08-03 08:24:26 -04:00
|
|
|
set_connection_state(bittorrent->conn,
|
|
|
|
connection_state(S_RESUME));
|
2005-09-15 09:58:31 -04:00
|
|
|
return;
|
|
|
|
|
|
|
|
case BITTORRENT_STATE_OUT_OF_MEM:
|
2008-08-03 08:24:26 -04:00
|
|
|
state = connection_state(S_OUT_OF_MEM);
|
2005-09-15 09:58:31 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2008-08-03 08:24:26 -04:00
|
|
|
state = connection_state(S_BITTORRENT_ERROR);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case BITTORRENT_STATE_OUT_OF_MEM:
|
2008-08-03 08:24:26 -04:00
|
|
|
state = connection_state(S_OUT_OF_MEM);
|
2005-09-15 09:58:31 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case BITTORRENT_STATE_ERROR:
|
|
|
|
default:
|
|
|
|
/* XXX: This can also happen when passing bittorrent:<uri> and
|
|
|
|
* <uri> gives an HTTP 404 response. It might be worth fixing by
|
|
|
|
* looking at the protocol header, however, direct usage of the
|
|
|
|
* internal bittorrent: is at your own risk ... at least for
|
|
|
|
* now. --jonas */
|
2008-08-03 08:24:26 -04:00
|
|
|
state = connection_state(S_BITTORRENT_METAINFO);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
abort_connection(conn, state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The entry point for BitTorrent downloads. */
|
|
|
|
void
|
|
|
|
bittorrent_protocol_handler(struct connection *conn)
|
|
|
|
{
|
2006-11-06 10:23:20 -05:00
|
|
|
struct uri *uri = NULL;
|
2005-09-15 09:58:31 -04:00
|
|
|
struct bittorrent_connection *bittorrent;
|
|
|
|
|
|
|
|
bittorrent = init_bittorrent_connection(conn);
|
|
|
|
if (!bittorrent) {
|
2008-08-03 08:24:26 -04:00
|
|
|
abort_connection(conn, connection_state(S_OUT_OF_MEM));
|
2005-09-15 09:58:31 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2006-11-06 10:23:20 -05:00
|
|
|
if (conn->uri->datalen)
|
2022-01-14 14:52:17 -05:00
|
|
|
uri = get_uri(conn->uri->data, URI_NONE);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
if (!uri) {
|
2008-08-03 08:24:26 -04:00
|
|
|
abort_connection(conn, connection_state(S_BITTORRENT_BAD_URL));
|
2005-09-15 09:58:31 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-08-03 08:24:26 -04:00
|
|
|
set_connection_state(conn, connection_state(S_CONN));
|
2005-09-15 09:58:31 -04:00
|
|
|
set_connection_timeout(conn);
|
|
|
|
conn->from = 0;
|
|
|
|
|
|
|
|
init_bittorrent_fetch(&bittorrent->fetch, uri,
|
|
|
|
bittorrent_metainfo_callback, conn, 0);
|
|
|
|
done_uri(uri);
|
|
|
|
}
|
2008-09-06 23:10:52 -04:00
|
|
|
|
|
|
|
void
|
|
|
|
bittorrent_peer_protocol_handler(struct connection *conn)
|
|
|
|
{
|
|
|
|
abort_connection(conn, connection_state(S_BITTORRENT_PEER_URL));
|
|
|
|
}
|