diff --git a/btpd/http.c b/btpd/http.c deleted file mode 100644 index f1e6b7e..0000000 --- a/btpd/http.c +++ /dev/null @@ -1,251 +0,0 @@ -#include -#include -#include -#include -#include - -#include "btpd.h" -#include "http.h" - -#define MAX_DOWNLOAD (1 << 18) // 256kB -#define CURL_SELECT_TIME (& (struct timeval) { 1, 0 }) - -enum http_state { - HS_ADD, - HS_ACTIVE, - HS_DONE, - HS_NOADD, - HS_CANCEL -}; - -struct http { - long t_created; - enum http_state state; - char *url; - CURL *curlh; - struct http_res res; - BTPDQ_ENTRY(http) entry; - void (*cb)(struct http *, struct http_res *, void *); - void *cb_arg; -}; - -BTPDQ_HEAD(http_tq, http); - -static struct http_tq m_httpq = BTPDQ_HEAD_INITIALIZER(m_httpq); -static pthread_mutex_t m_httpq_lock; -static pthread_cond_t m_httpq_cond; -static CURLM *m_curlh; - -static size_t -http_write_cb(void *ptr, size_t size, size_t nmemb, void *arg) -{ - char *mem; - struct http_res *res = arg; - size_t nbytes = size * nmemb; - size_t nlength = res->length + nbytes; - if (nlength > MAX_DOWNLOAD) - return 0; - if ((mem = realloc(res->content, nlength)) == NULL) - return 0; - res->content = mem; - bcopy(ptr, res->content + res->length, nbytes); - res->length = nlength; - return nbytes; -} - -int -http_get(struct http **ret, - void (*cb)(struct http *, struct http_res *, void *), void *arg, - const char *fmt, ...) -{ - struct http *h = btpd_calloc(1, sizeof(*h)); - - h->state = HS_ADD; - h->cb = cb; - h->cb_arg = arg; - h->t_created = btpd_seconds; - if ((h->curlh = curl_easy_init()) == NULL) - btpd_err("Fatal error in curl.\n"); - - va_list ap; - va_start(ap, fmt); - if (vasprintf(&h->url, fmt, ap) == -1) - btpd_err("Out of memory.\n"); - va_end(ap); - - curl_easy_setopt(h->curlh, CURLOPT_URL, h->url); - curl_easy_setopt(h->curlh, CURLOPT_USERAGENT, BTPD_VERSION); - curl_easy_setopt(h->curlh, CURLOPT_WRITEFUNCTION, http_write_cb); - curl_easy_setopt(h->curlh, CURLOPT_WRITEDATA, &h->res); - curl_easy_setopt(h->curlh, CURLOPT_FOLLOWLOCATION, 1); - - pthread_mutex_lock(&m_httpq_lock); - BTPDQ_INSERT_TAIL(&m_httpq, h, entry); - pthread_mutex_unlock(&m_httpq_lock); - pthread_cond_signal(&m_httpq_cond); - - if (ret != NULL) - *ret = h; - - return 0; -} - -long -http_server_busy_time(const char *url, long s) -{ - struct http *h; - size_t len = strlen(url); - - pthread_mutex_lock(&m_httpq_lock); - h = BTPDQ_LAST(&m_httpq, http_tq); - while (h != NULL && - !((h->state == HS_ACTIVE || h->state == HS_ADD) && - strncmp(url, h->url, len) == 0)) - h = BTPDQ_PREV(h, http_tq, entry); - pthread_mutex_unlock(&m_httpq_lock); - - if (h == NULL || btpd_seconds - h->t_created >= s) - return 0; - else - return s - (btpd_seconds - h->t_created); -} - -void -http_cancel(struct http *http) -{ - pthread_mutex_lock(&m_httpq_lock); - if (http->state == HS_ADD) - http->state = HS_NOADD; - else - http->state = HS_CANCEL; - pthread_mutex_unlock(&m_httpq_lock); -} - -int -http_succeeded(struct http_res *res) -{ - return res->res == HRES_OK && res->code >= 200 && res->code < 300; -} - -static void -http_td_cb(void *arg) -{ - struct http *h = arg; - if (h->res.res == HRES_OK) - curl_easy_getinfo(h->curlh, CURLINFO_RESPONSE_CODE, &h->res.code); - if (h->res.res == HRES_FAIL) - h->res.errmsg = curl_easy_strerror(h->res.code); - if (h->state != HS_CANCEL) - h->cb(h, &h->res, h->cb_arg); - curl_easy_cleanup(h->curlh); - if (h->res.content != NULL) - free(h->res.content); - free(h->url); - free(h); -} - -static void -http_td_actions(void) -{ - int nmsgs; - struct http *http, *next; - struct http_tq postq; - CURLMsg *cmsg; - - pthread_mutex_lock(&m_httpq_lock); - do { - while (BTPDQ_EMPTY(&m_httpq)) - pthread_cond_wait(&m_httpq_cond, &m_httpq_lock); - - BTPDQ_INIT(&postq); - - BTPDQ_FOREACH_MUTABLE(http, &m_httpq, entry, next) { - switch (http->state) { - case HS_ADD: - curl_multi_add_handle(m_curlh, http->curlh); - http->state = HS_ACTIVE; - break; - case HS_CANCEL: - curl_multi_remove_handle(m_curlh, http->curlh); - case HS_NOADD: - BTPDQ_REMOVE(&m_httpq, http, entry); - BTPDQ_INSERT_TAIL(&postq, http, entry); - http->state = HS_CANCEL; - http->res.res = HRES_CANCEL; - break; - case HS_DONE: - abort(); - default: - break; - } - } - - while ((cmsg = curl_multi_info_read(m_curlh, &nmsgs)) != NULL) { - BTPDQ_FOREACH(http, &m_httpq, entry) { - if (http->curlh == cmsg->easy_handle) - break; - } - assert(http != NULL); - BTPDQ_REMOVE(&m_httpq, http, entry); - BTPDQ_INSERT_TAIL(&postq, http, entry); - http->state = HS_DONE; - if (cmsg->data.result == 0) - http->res.res = HRES_OK; - else { - http->res.res = HRES_FAIL; - http->res.code = cmsg->data.result; - } - curl_multi_remove_handle(m_curlh, http->curlh); - } - - if (!BTPDQ_EMPTY(&postq)) { - pthread_mutex_unlock(&m_httpq_lock); - td_post_begin(); - BTPDQ_FOREACH(http, &postq, entry) - td_post(http_td_cb, http); - td_post_end(); - pthread_mutex_lock(&m_httpq_lock); - } - } while (BTPDQ_EMPTY(&m_httpq)); - pthread_mutex_unlock(&m_httpq_lock); -} - -static void -http_td(void *arg) -{ - fd_set rset, wset, eset; - int maxfd, nbusy; - - for (;;) { - http_td_actions(); - - while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(m_curlh, &nbusy)) - ; - - if (nbusy > 0) { - FD_ZERO(&rset); - FD_ZERO(&wset); - FD_ZERO(&eset); - curl_multi_fdset(m_curlh, &rset, &wset, &eset, &maxfd); - select(maxfd + 1, &rset, &wset, &eset, CURL_SELECT_TIME); - } - } -} - -static void -errdie(int err) -{ - if (err != 0) - btpd_err("Fatal error in http_init.\n"); -} - -void -http_init(void) -{ - pthread_t ret; - errdie(curl_global_init(0)); - errdie((m_curlh = curl_multi_init()) == NULL); - errdie(pthread_mutex_init(&m_httpq_lock, NULL)); - errdie(pthread_cond_init(&m_httpq_cond, NULL)); - errdie(pthread_create(&ret, NULL, (void *(*)(void *))http_td, NULL)); -} diff --git a/btpd/http.h b/btpd/http.h deleted file mode 100644 index 9f03614..0000000 --- a/btpd/http.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef BTPD_HTTP_H -#define BTPD_HTTP_H - -struct http; - -enum http_status { - HRES_OK, - HRES_FAIL, - HRES_CANCEL -}; - -struct http_res { - enum http_status res; - long code; - const char *errmsg; - char *content; - size_t length; -}; - -__attribute__((format (printf, 4, 5))) -int http_get(struct http **ret, - void (*cb)(struct http *, struct http_res *, void *), - void *arg, - const char *fmt, ...); -void http_cancel(struct http *http); -int http_succeeded(struct http_res *res); - -long http_server_busy_time(const char *url, long s); - -void http_init(void); - -#endif diff --git a/btpd/http_tr_if.c b/btpd/http_tr_if.c new file mode 100644 index 0000000..568b93c --- /dev/null +++ b/btpd/http_tr_if.c @@ -0,0 +1,174 @@ +#include + +#include "btpd.h" +#include "tracker_req.h" +#include "http_client.h" + +#define MAX_DOWNLOAD (1 << 18) // 256kB + +static const char *m_tr_events[] = { "started", "stopped", "completed", "" }; + +struct http_tr_req { + struct torrent *tp; + struct http_req *req; + struct evbuffer *buf; + enum tr_event event; +}; + +static void +http_tr_free(struct http_tr_req *treq) +{ + evbuffer_free(treq->buf); + free(treq); +} + +static void +maybe_connect_to(struct torrent *tp, const char *pinfo) +{ + const char *pid; + char *ip; + int port; + size_t len; + + if ((pid = benc_dget_mem(pinfo, "peer id", &len)) == NULL || len != 20) + return; + + if (bcmp(btpd_get_peer_id(), pid, 20) == 0) + return; + + if (net_torrent_has_peer(tp->net, pid)) + return; + + if ((ip = benc_dget_str(pinfo, "ip", NULL)) == NULL) + return; + + port = benc_dget_int(pinfo, "port"); + peer_create_out(tp->net, pid, ip, port); + + if (ip != NULL) + free(ip); +} + +static int +parse_reply(struct torrent *tp, const char *content, size_t size, int parse, + int *interval) +{ + const char *buf; + size_t len; + const char *peers; + + if (benc_validate(content, size) != 0) + goto bad_data; + + if ((buf = benc_dget_mem(content, "failure reason", &len)) != NULL) { + btpd_log(BTPD_L_ERROR, "Tracker failure: '%.*s' for '%s'.\n", + (int)len, buf, torrent_name(tp)); + return 1; + } + + if (!parse) + return 0; + + if (!benc_dct_chk(content, 2, BE_INT, 1, "interval", BE_ANY, 1, "peers")) + goto bad_data; + + *interval = benc_dget_int(content, "interval"); + if (*interval < 1) + goto bad_data; + + peers = benc_dget_any(content, "peers"); + + if (benc_islst(peers)) { + for (peers = benc_first(peers); + peers != NULL && net_npeers < net_max_peers; + peers = benc_next(peers)) + maybe_connect_to(tp, peers); + } else if (benc_isstr(peers)) { + peers = benc_dget_mem(content, "peers", &len); + for (size_t i = 0; i < len && net_npeers < net_max_peers; i += 6) + peer_create_out_compact(tp->net, peers + i); + } else + goto bad_data; + + return 0; + +bad_data: + btpd_log(BTPD_L_ERROR, "Bad data from tracker for '%s'.\n", + torrent_name(tp)); + return 1; +} + +static void +http_cb(struct http_req *req, struct http_response *res, void *arg) +{ + int interval; + struct http_tr_req *treq = arg; + switch (res->type) { + case HTTP_T_ERR: + btpd_log(BTPD_L_ERROR, "http request failed for '%s'.\n", + torrent_name(treq->tp)); + tr_result(treq->tp, TR_RES_FAIL, -1); + http_tr_free(treq); + break; + case HTTP_T_DATA: + if (treq->buf->off + res->v.data.l > MAX_DOWNLOAD) { + tr_result(treq->tp, TR_RES_FAIL, -1); + http_tr_cancel(treq); + break; + } + if (evbuffer_add(treq->buf, res->v.data.p, res->v.data.l) != 0) + btpd_err("Out of memory.\n"); + break; + case HTTP_T_DONE: + if (parse_reply(treq->tp, treq->buf->buffer, treq->buf->off, + treq->event != TR_EV_STOPPED, &interval) == 0) + tr_result(treq->tp, TR_RES_OK, interval); + else + tr_result(treq->tp, TR_RES_FAIL, -1); + http_tr_free(treq); + break; + default: + break; + } +} + +struct http_tr_req * +http_tr_req(struct torrent *tp, enum tr_event event, const char *aurl) +{ + char e_hash[61], e_id[61], url[512], qc;; + const uint8_t *peer_id = btpd_get_peer_id(); + + qc = (strchr(aurl, '?') == NULL) ? '?' : '&'; + + for (int i = 0; i < 20; i++) + snprintf(e_hash + i * 3, 4, "%%%.2x", tp->tl->hash[i]); + for (int i = 0; i < 20; i++) + snprintf(e_id + i * 3, 4, "%%%.2x", peer_id[i]); + + snprintf(url, sizeof(url), + "%s%cinfo_hash=%s&peer_id=%s&key=%ld&port=%d&uploaded=%llu" + "&downloaded=%llu&left=%llu&compact=1%s%s", + aurl, qc, e_hash, e_id, tr_key, net_port, + tp->net->uploaded, tp->net->downloaded, + (long long)tp->total_length - cm_content(tp), + event == TR_EV_EMPTY ? "" : "&event=", m_tr_events[event]); + + struct http_tr_req *treq = btpd_calloc(1, sizeof(*treq)); + if (!http_get(&treq->req, url, "User-Agent: " BTPD_VERSION "\r\n", + http_cb, treq)) { + free(treq); + return NULL; + } + if ((treq->buf = evbuffer_new()) == NULL) + btpd_err("Out of memory.\n"); + treq->tp = tp; + treq->event = event; + return treq; +} + +void +http_tr_cancel(struct http_tr_req *treq) +{ + http_cancel(treq->req); + http_tr_free(treq); +} diff --git a/btpd/tracker_req.c b/btpd/tracker_req.c index fe31b2e..add1462 100644 --- a/btpd/tracker_req.c +++ b/btpd/tracker_req.c @@ -1,24 +1,16 @@ -#include #include #include "btpd.h" -#include "benc.h" #include "subr.h" -#include "http.h" #include "tracker_req.h" +#define REQ_DELAY 1 #define REQ_TIMEOUT (& (struct timeval) { 120, 0 }) #define RETRY_WAIT (& (struct timeval) { rand_between(35, 70), 0 }) -enum tr_event { - TR_EV_STARTED, - TR_EV_STOPPED, - TR_EV_COMPLETED, - TR_EV_EMPTY -}; +long tr_key; -static long m_key; -static const char *m_events[] = { "started", "stopped", "completed", "" }; +static long m_tlast_req, m_tnext_req; enum timer_type { TIMER_NONE, @@ -34,102 +26,28 @@ struct tracker { unsigned nerrors; int tier, url; struct mi_announce *ann; - struct http *req; + void *req; struct event timer; }; -static void tr_send(struct torrent *tp, enum tr_event event); - -static void -maybe_connect_to(struct torrent *tp, const char *pinfo) -{ - const char *pid; - char *ip; - int port; - size_t len; - - if ((pid = benc_dget_mem(pinfo, "peer id", &len)) == NULL || len != 20) - return; - - if (bcmp(btpd_get_peer_id(), pid, 20) == 0) - return; - - if (net_torrent_has_peer(tp->net, pid)) - return; - - if ((ip = benc_dget_str(pinfo, "ip", NULL)) == NULL) - return; - - port = benc_dget_int(pinfo, "port"); - peer_create_out(tp->net, pid, ip, port); - - if (ip != NULL) - free(ip); -} - - -static int -parse_reply(struct torrent *tp, const char *content, size_t size, int parse) -{ - const char *buf; - size_t len; - const char *peers; - int interval; - - if (benc_validate(content, size) != 0) - goto bad_data; - - if ((buf = benc_dget_mem(content, "failure reason", &len)) != NULL) { - btpd_log(BTPD_L_ERROR, "Tracker failure: '%.*s' for '%s'.\n", - (int)len, buf, torrent_name(tp)); - return 1; - } - - if (!parse) - return 0; - - if (!benc_dct_chk(content, 2, BE_INT, 1, "interval", BE_ANY, 1, "peers")) - goto bad_data; +typedef struct _dummy *(*request_fun_t)(struct torrent *, enum tr_event, + const char *); +typedef void (*cancel_fun_t)(struct _dummy *); - interval = benc_dget_int(content, "interval"); - if (interval < 1) - goto bad_data; - - tp->tr->interval = interval; - peers = benc_dget_any(content, "peers"); - - if (benc_islst(peers)) { - for (peers = benc_first(peers); - peers != NULL && net_npeers < net_max_peers; - peers = benc_next(peers)) - maybe_connect_to(tp, peers); - } else if (benc_isstr(peers)) { - peers = benc_dget_mem(content, "peers", &len); - for (size_t i = 0; i < len && net_npeers < net_max_peers; i += 6) - peer_create_out_compact(tp->net, peers + i); - } else - goto bad_data; - - return 0; +struct tr_op { + int len; + const char *scheme; + request_fun_t request; + cancel_fun_t cancel; +}; -bad_data: - btpd_log(BTPD_L_ERROR, "Bad data from tracker for '%s'.\n", - torrent_name(tp)); - return 1; -} +static struct tr_op m_http_op = { + 7, "http://", (request_fun_t)http_tr_req, (cancel_fun_t)http_tr_cancel +}; -static void -tr_set_stopped(struct torrent *tp) -{ - struct tracker *tr = tp->tr; - btpd_ev_del(&tr->timer); - tr->ttype = TIMER_NONE; - if (tr->req != NULL) { - http_cancel(tr->req); - tr->req = NULL; - } - torrent_on_tr_stopped(tp); -} +static struct tr_op *m_tr_ops[] = { + &m_http_op, NULL +}; static char * get_url(struct tracker *tr) @@ -158,26 +76,82 @@ next_url(struct tracker *tr) tr->tier = (tr->tier + 1) % tr->ann->ntiers; } +struct tr_op * +get_op(struct tracker *tr) +{ + struct tr_op *op; + char *url = get_url(tr); + for (op = m_tr_ops[0]; op != NULL; op++) + if (strncasecmp(op->scheme, url, op->len) == 0) + return op; + return NULL; +} + static void -http_cb(struct http *req, struct http_res *res, void *arg) +tr_cancel(struct tracker *tr) +{ + struct tr_op *op = get_op(tr); + assert(op != NULL); + op->cancel(tr->req); + tr->req = NULL; +} + +static void +tr_send(struct torrent *tp, enum tr_event event) +{ + struct tracker *tr = tp->tr; + struct tr_op *op = get_op(tr); + + tr->event = event; + if (tr->req != NULL) + tr_cancel(tr); + + if (m_tlast_req > btpd_seconds - REQ_DELAY) { + m_tnext_req = max(m_tnext_req, m_tlast_req) + REQ_DELAY; + tr->ttype = TIMER_RETRY; + btpd_ev_add(&tr->timer, + (& (struct timeval) { m_tnext_req - btpd_seconds, 0 })); + return; + } + + if ((op == NULL || + (tr->req = op->request(tp, event, get_url(tr))) == NULL)) { + tr->ttype = TIMER_RETRY; + btpd_ev_add(&tr->timer, (& (struct timeval) { 20, 0 })); + } else { + m_tlast_req = btpd_seconds; + tr->ttype = TIMER_TIMEOUT; + btpd_ev_add(&tr->timer, REQ_TIMEOUT); + } +} + +static void +tr_set_stopped(struct torrent *tp) +{ + struct tracker *tr = tp->tr; + btpd_ev_del(&tr->timer); + tr->ttype = TIMER_NONE; + if (tr->req != NULL) + tr_cancel(tr); + torrent_on_tr_stopped(tp); +} + +void +tr_result(struct torrent *tp, enum tr_res res, int interval) { - struct torrent *tp = arg; struct tracker *tr = tp->tr; - assert(tr->ttype == TIMER_TIMEOUT); tr->req = NULL; - if (res->res == HRES_OK && parse_reply(tp, res->content, res->length, - tr->event != TR_EV_STOPPED) == 0) { + if (res == TR_RES_OK) { good_url(tr); + tr->interval = interval; tr->nerrors = 0; tr->ttype = TIMER_INTERVAL; - btpd_ev_add(&tr->timer, (& (struct timeval) { tr->interval, 0 })); + btpd_ev_add(&tr->timer, (& (struct timeval) { tr->interval, 0})); } else { - if (res->res == HRES_FAIL) - btpd_log(BTPD_L_BTPD, "Tracker request for '%s' failed (%s).\n", - torrent_name(tp), res->errmsg); tr->nerrors++; tr->ttype = TIMER_RETRY; btpd_ev_add(&tr->timer, RETRY_WAIT); + next_url(tr); } if (tr->event == TR_EV_STOPPED && (tr->nerrors == 0 || tr->nerrors >= 5)) tr_set_stopped(tp); @@ -197,8 +171,9 @@ timer_cb(int fd, short type, void *arg) tr_set_stopped(tp); break; } - case TIMER_RETRY: + tr_cancel(tr); next_url(tr); + case TIMER_RETRY: tr_send(tp, tr->event); break; case TIMER_INTERVAL: @@ -209,43 +184,6 @@ timer_cb(int fd, short type, void *arg) } } -static void -tr_send(struct torrent *tp, enum tr_event event) -{ - long busy_secs; - char e_hash[61], e_id[61], qc;; - const uint8_t *peer_id = btpd_get_peer_id(); - - struct tracker *tr = tp->tr; - tr->event = event; - if (tr->ttype == TIMER_TIMEOUT) - http_cancel(tr->req); - - if ((busy_secs = http_server_busy_time(get_url(tr), 3)) > 0) { - tr->ttype = TIMER_RETRY; - btpd_ev_add(&tr->timer, (& (struct timeval) { busy_secs, 0 })); - return; - } - - tr->ttype = TIMER_TIMEOUT; - btpd_ev_add(&tr->timer, REQ_TIMEOUT); - - qc = (strchr(get_url(tr), '?') == NULL) ? '?' : '&'; - - for (int i = 0; i < 20; i++) - snprintf(e_hash + i * 3, 4, "%%%.2x", tp->tl->hash[i]); - for (int i = 0; i < 20; i++) - snprintf(e_id + i * 3, 4, "%%%.2x", peer_id[i]); - - http_get(&tr->req, http_cb, tp, - "%s%cinfo_hash=%s&peer_id=%s&key=%ld&port=%d&uploaded=%llu" - "&downloaded=%llu&left=%llu&compact=1%s%s", - get_url(tr), qc, e_hash, e_id, m_key, net_port, - tp->net->uploaded, tp->net->downloaded, - (long long)tp->total_length - cm_content(tp), - event == TR_EV_EMPTY ? "" : "&event=", m_events[event]); -} - int tr_create(struct torrent *tp, const char *mi) { @@ -263,7 +201,7 @@ tr_kill(struct torrent *tp) tp->tr = NULL; btpd_ev_del(&tr->timer); if (tr->req != NULL) - http_cancel(tr->req); + tr_cancel(tr); mi_free_announce(tr->ann); free(tr); } @@ -310,5 +248,5 @@ tr_errors(struct torrent *tp) void tr_init(void) { - m_key = random(); + tr_key = random(); } diff --git a/btpd/tracker_req.h b/btpd/tracker_req.h index 23cbeaa..caedeb9 100644 --- a/btpd/tracker_req.h +++ b/btpd/tracker_req.h @@ -1,6 +1,20 @@ #ifndef TRACKER_REQ_H #define TRACKER_REQ_H +enum tr_event { + TR_EV_STARTED, + TR_EV_STOPPED, + TR_EV_COMPLETED, + TR_EV_EMPTY +}; + +enum tr_res { + TR_RES_OK, + TR_RES_FAIL +}; + +extern long tr_key; + int tr_create(struct torrent *tp, const char *mi); void tr_kill(struct torrent *tp); void tr_start(struct torrent *tp); @@ -10,4 +24,12 @@ void tr_complete(struct torrent *tp); unsigned tr_errors(struct torrent *tp); int tr_active(struct torrent *tp); +void tr_result(struct torrent *tp, enum tr_res res, int interval); + +struct http_tr_req; + +struct http_tr_req *http_tr_req(struct torrent *tp, enum tr_event event, + const char *aurl); +void http_tr_cancel(struct http_tr_req *treq); + #endif