about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--btpd/cli_if.c3
-rw-r--r--btpd/http_tr_if.c126
-rw-r--r--btpd/torrent.c32
-rw-r--r--btpd/torrent.h2
-rw-r--r--btpd/tracker_req.c378
-rw-r--r--btpd/tracker_req.h31
6 files changed, 320 insertions, 252 deletions
diff --git a/btpd/cli_if.c b/btpd/cli_if.c
index a026b9e..e129b88 100644
--- a/btpd/cli_if.c
+++ b/btpd/cli_if.c
@@ -148,8 +148,7 @@ write_ans(struct iobuf *iob, struct tlib *tl, enum ipc_tval val)
             (tl->tp == NULL ? 0 : tl->tp->net->uploaded));
         return;
     case IPC_TVAL_TRERR:
-        iobuf_print(iob, "i%dei%ue", IPC_TYPE_NUM,
-            tl->tp == NULL ? 0 : tr_errors(tl->tp));
+        iobuf_print(iob, "i%dei%ue", IPC_TYPE_NUM, 0);
         return;
     case IPC_TVALCOUNT:
         break;
diff --git a/btpd/http_tr_if.c b/btpd/http_tr_if.c
index 376bb02..beb29c0 100644
--- a/btpd/http_tr_if.c
+++ b/btpd/http_tr_if.c
@@ -7,23 +7,26 @@
 
 static const char *m_tr_events[] = { "started", "stopped", "completed", "" };
 
-struct http_tr_req {
+struct httptr_req {
     struct torrent *tp;
+    struct tr_tier *tr;
     struct http_req *req;
     struct iobuf buf;
     struct fdev ioev;
+    struct timeout timer;
     nameconn_t nc;
     int sd;
     enum tr_event event;
 };
 
 static void
-http_tr_free(struct http_tr_req *treq)
+httptr_free(struct httptr_req *treq)
 {
     if (treq->sd != -1) {
         btpd_ev_del(&treq->ioev);
         close(treq->sd);
     }
+    btpd_timer_del(&treq->timer);
     iobuf_free(&treq->buf);
     free(treq);
 }
@@ -55,9 +58,9 @@ maybe_connect_to(struct torrent *tp, const char *pinfo)
         free(ip);
 }
 
-static int
-parse_reply(struct torrent *tp, const char *content, size_t size, int parse,
-    int *interval)
+static void
+parse_reply(struct torrent *tp, struct tr_response *res, const char *content,
+    size_t size)
 {
     const char *buf;
     size_t len;
@@ -67,25 +70,20 @@ parse_reply(struct torrent *tp, const char *content, size_t size, int parse,
     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) {
-        *interval = -1;
-        return 0;
+    if ((buf = benc_dget_any(content, "failure reason")) != NULL) {
+        if (!benc_isstr(buf))
+            goto bad_data;
+        res->type = TR_RES_FAIL;
+        res->mi_failure = buf;
+        return;
     }
 
-    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;
+    buf = benc_dget_any(content, "interval");
+    if (buf != NULL && benc_isint(buf))
+        res->interval = benc_int(buf, NULL);
 
-    peers = benc_dget_any(content, "peers");
+    if ((peers = benc_dget_any(content, "peers")) == NULL)
+        goto after_peers;
 
     if (benc_islst(peers)) {
         for (peers = benc_first(peers);
@@ -101,8 +99,9 @@ parse_reply(struct torrent *tp, const char *content, size_t size, int parse,
     } else
         goto bad_data;
 
+after_peers:
     if (!net_ipv6)
-        return 0;
+        goto after_peers6;
     for (int k = 0; k < 2; k++) {
         peers = benc_dget_any(content, v6key[k]);
         if (peers != NULL && benc_isstr(peers)) {
@@ -111,42 +110,44 @@ parse_reply(struct torrent *tp, const char *content, size_t size, int parse,
                 peer_create_out_compact(tp->net, AF_INET6, peers + i);
         }
     }
-    return 0;
+after_peers6:
+    res->type = TR_RES_OK;
+    return;
 
 bad_data:
-    btpd_log(BTPD_L_ERROR, "Bad data from tracker for '%s'.\n",
-        torrent_name(tp));
-    return 1;
+    res->type = TR_RES_BAD;
 }
 
 static void
 http_cb(struct http_req *req, struct http_response *res, void *arg)
 {
-    int interval;
-    struct http_tr_req *treq = arg;
+    struct httptr_req *treq = arg;
+    struct tr_response tres = {0, NULL, -1 };
     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);
+        tres.type = TR_RES_BAD;
+        tr_result(treq->tr, &tres);
+        httptr_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);
+            tres.type = TR_RES_BAD;
+            tr_result(treq->tr, &tres);
+            httptr_cancel(treq);
             break;
         }
         if (!iobuf_write(&treq->buf, res->v.data.p, res->v.data.l))
             btpd_err("Out of memory.\n");
         break;
     case HTTP_T_DONE:
-        if (parse_reply(treq->tp, treq->buf.buf, 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);
+        if (treq->event == TR_EV_STOPPED) {
+            tres.type = TR_RES_OK;
+            tr_result(treq->tr, &tres);
+        } else {
+            parse_reply(treq->tp, &tres, treq->buf.buf, treq->buf.off);
+            tr_result(treq->tr, &tres);
+        }
+        httptr_free(treq);
         break;
     default:
         break;
@@ -154,9 +155,10 @@ http_cb(struct http_req *req, struct http_response *res, void *arg)
 }
 
 static void
-sd_io_cb(int sd, short type, void *arg)
+httptr_io_cb(int sd, short type, void *arg)
 {
-    struct http_tr_req *treq = arg;
+    struct tr_response res;
+    struct httptr_req *treq = arg;
     switch (type) {
     case EV_READ:
         if (http_read(treq->req, sd) && !http_want_read(treq->req))
@@ -166,30 +168,39 @@ sd_io_cb(int sd, short type, void *arg)
         if (http_write(treq->req, sd) && !http_want_write(treq->req))
             btpd_ev_disable(&treq->ioev, EV_WRITE);
         break;
+    case EV_TIMEOUT:
+        res.type = TR_RES_CONN;
+        tr_result(treq->tr, &res);
+        httptr_cancel(treq);
+        break;
     default:
         abort();
     }
 }
 
 static void
-nc_cb(void *arg, int error, int sd)
+httptr_nc_cb(void *arg, int error, int sd)
 {
-    struct http_tr_req *treq = arg;
+    struct tr_response res;
+    struct httptr_req *treq = arg;
     if (error) {
-        tr_result(treq->tp, TR_RES_FAIL, -1);
+        res.type = TR_RES_CONN;
+        tr_result(treq->tr, &res);
         http_cancel(treq->req);
-        http_tr_free(treq);
+        httptr_free(treq);
     } else {
         treq->sd = sd;
         uint16_t flags =
             (http_want_read(treq->req) ? EV_READ : 0) |
             (http_want_write(treq->req) ? EV_WRITE : 0);
-        btpd_ev_new(&treq->ioev, sd, flags, sd_io_cb, treq);
+        btpd_ev_new(&treq->ioev, sd, flags, httptr_io_cb, treq);
+        btpd_timer_add(&treq->timer, (& (struct timespec) { 30, 0 }));
     }
 }
 
-struct http_tr_req *
-http_tr_req(struct torrent *tp, enum tr_event event, const char *aurl)
+struct httptr_req *
+httptr_req(struct torrent *tp, struct tr_tier *tr, const char *aurl,
+    enum tr_event event)
 {
     char e_hash[61], e_id[61], url[512], qc;
     const uint8_t *peer_id = btpd_get_peer_id();
@@ -211,28 +222,33 @@ http_tr_req(struct torrent *tp, enum tr_event event, const char *aurl)
         (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));
+    struct httptr_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;
     }
+    treq->tp = tp;
+    treq->tr = tr;
+    treq->event = event;
     treq->buf = iobuf_init(4096);
     if (treq->buf.error)
         btpd_err("Out of memory.\n");
-    treq->tp = tp;
-    treq->event = event;
+    treq->tr = tr;
     treq->sd = -1;
     http_url = http_url_get(treq->req);
-    treq->nc = btpd_name_connect(http_url->host, http_url->port, nc_cb, treq);
+    treq->nc = btpd_name_connect(http_url->host, http_url->port,
+        httptr_nc_cb, treq);
+    timer_init(&treq->timer, httptr_io_cb, treq);
+    btpd_timer_add(&treq->timer, (& (struct timespec) { 60, 0 }));
     return treq;
 }
 
 void
-http_tr_cancel(struct http_tr_req *treq)
+httptr_cancel(struct httptr_req *treq)
 {
     if (treq->sd == -1)
         btpd_name_connect_cancel(treq->nc);
     http_cancel(treq->req);
-    http_tr_free(treq);
+    httptr_free(treq);
 }
diff --git a/btpd/torrent.c b/btpd/torrent.c
index c77e9da..c48e844 100644
--- a/btpd/torrent.c
+++ b/btpd/torrent.c
@@ -96,25 +96,19 @@ torrent_start(struct tlib *tl)
         benc_dget_mem(benc_dget_dct(mi, "info"), "pieces", NULL) - mi;
 
     btpd_log(BTPD_L_BTPD, "Starting torrent '%s'.\n", torrent_name(tp));
-    if (tr_create(tp, mi) == 0) {
-        tl->tp = tp;
-        net_create(tp);
-        cm_create(tp, mi);
-        BTPDQ_INSERT_TAIL(&m_torrents, tp, entry);
-        m_ntorrents++;
-        cm_start(tp, 0);
-        free(mi);
-        if (m_ntorrents == 1) {
-            m_tsave = btpd_seconds + SAVE_INTERVAL;
-            m_savetp = tp;
-        }
-        return IPC_OK;
-    } else {
-        mi_free_files(tp->nfiles, tp->files);
-        free(tp);
-        free(mi);
-        return IPC_EBADTRACKER;
+    tr_create(tp, mi);
+    tl->tp = tp;
+    net_create(tp);
+    cm_create(tp, mi);
+    BTPDQ_INSERT_TAIL(&m_torrents, tp, entry);
+    m_ntorrents++;
+    cm_start(tp, 0);
+    free(mi);
+    if (m_ntorrents == 1) {
+        m_tsave = btpd_seconds + SAVE_INTERVAL;
+        m_savetp = tp;
     }
+    return IPC_OK;
 }
 
 static void
@@ -158,8 +152,6 @@ torrent_stop(struct torrent *tp, int delete)
             tlib_update_info(tp->tl, 0);
         break;
     case T_STOPPING:
-        if (tr_active(tp))
-            tr_stop(tp);
         break;
     }
 }
diff --git a/btpd/torrent.h b/btpd/torrent.h
index e2a0218..e8eee4c 100644
--- a/btpd/torrent.h
+++ b/btpd/torrent.h
@@ -18,7 +18,7 @@ struct torrent {
     int delete;
 
     struct content *cm;
-    struct tracker *tr;
+    struct trackers *tr;
     struct net *net;
 
     off_t total_length;
diff --git a/btpd/tracker_req.c b/btpd/tracker_req.c
index 06e281d..32c56e9 100644
--- a/btpd/tracker_req.c
+++ b/btpd/tracker_req.c
@@ -1,251 +1,309 @@
 #include "btpd.h"
+#include "http_client.h"
 
 #define REQ_DELAY 1
-#define STOP_ERRORS 5
-#define REQ_TIMEOUT (& (struct timespec) { 120, 0 })
-#define RETRY_WAIT (& (struct timespec) { rand_between(35, 70), 0 })
+#define DEFAULT_INTERVAL rand_between(25 * 60, 30 * 60)
+#define RETRY1_TIMEOUT (& (struct timespec) {240 + rand_between(0, 120), 0})
+#define RETRY2_TIMEOUT (& (struct timespec) {900 + rand_between(0, 300), 0})
 
 long tr_key;
 
 static long m_tlast_req, m_tnext_req;
 
-enum timer_type {
-    TIMER_NONE,
-    TIMER_TIMEOUT,
-    TIMER_INTERVAL,
-    TIMER_RETRY
+struct tr_entry {
+    BTPDQ_ENTRY(tr_entry) entry;
+    char *url;
+    enum tr_type type;
 };
 
-struct tracker {
-    enum timer_type ttype;
-    enum tr_event event;
-    int interval;
-    unsigned nerrors;
-    int tier, url;
-    struct mi_announce *ann;
-    void *req;
-    struct timeout timer;
-};
-
-typedef struct _dummy *(*request_fun_t)(struct torrent *, enum tr_event,
-    const char *);
-typedef void (*cancel_fun_t)(struct _dummy *);
+BTPDQ_HEAD(tr_entry_tq, tr_entry);
 
-struct tr_op {
-    int len;
-    const char *scheme;
-    request_fun_t request;
-    cancel_fun_t cancel;
+struct tr_tier {
+    struct torrent *tp;
+    struct tr_entry *cur;
+    struct tr_entry_tq trackers;
+    struct timeout timer;
+    BTPDQ_ENTRY(tr_tier) entry;
+    void *req;
+    char *failure;
+    int interval;
+    int bad_conns;
+    int active;
+    int has_responded;
+    enum tr_event event;
 };
 
-static struct tr_op m_http_op = {
-    7, "http://", (request_fun_t)http_tr_req, (cancel_fun_t)http_tr_cancel
-};
+BTPDQ_HEAD(tr_tier_tq, tr_tier);
 
-static struct tr_op *m_tr_ops[] = {
-    &m_http_op, NULL
+struct trackers {
+    struct tr_tier_tq trackers;
 };
 
-static char *
-get_url(struct tracker *tr)
+static void *
+req_send(struct tr_tier *t)
 {
-    return tr->ann->tiers[tr->tier].urls[tr->url];
+    switch (t->cur->type) {
+    case TR_HTTP:
+        return httptr_req(t->tp, t, t->cur->url, t->event);
+    default:
+        abort();
+    }
 }
 
 static void
-good_url(struct tracker *tr)
+req_cancel(struct tr_tier *t)
 {
-    char *set = tr->ann->tiers[tr->tier].urls[tr->url], *hold;
-    for (int i = 0; i <= tr->url; i++) {
-        hold = tr->ann->tiers[tr->tier].urls[i];
-        tr->ann->tiers[tr->tier].urls[i] = set;
-        set = hold;
+    switch (t->cur->type) {
+    case TR_HTTP:
+        httptr_cancel(t->req);
+        break;
+    default:
+        abort();
     }
-    tr->tier = 0;
-    tr->url = 0;
+    t->req = NULL;
 }
 
 static void
-next_url(struct tracker *tr)
+entry_send(struct tr_tier *t, struct tr_entry *e, enum tr_event event)
 {
-    tr->url = (tr->url + 1) % tr->ann->tiers[tr->tier].nurls;
-    if (tr->url == 0)
-        tr->tier = (tr->tier + 1) % tr->ann->ntiers;
+    if (t->req != NULL)
+        req_cancel(t);
+    t->event = event;
+    t->cur = e;
+    if (m_tlast_req > btpd_seconds - REQ_DELAY) {
+        m_tnext_req = max(m_tnext_req, m_tlast_req) + REQ_DELAY;
+        btpd_timer_add(&t->timer,
+            (& (struct timespec) { m_tnext_req - btpd_seconds, 0 }));
+        return;
+    }
+    btpd_timer_del(&t->timer);
+    if ((t->req = req_send(t)) == NULL) {
+        asprintf(&t->failure, "failed to create tracker message to '%s' (%s).",
+            e->url, strerror(errno));
+        t->active = 0;
+        return;
+    }
+    m_tlast_req = btpd_seconds;
 }
 
-struct tr_op *
-get_op(struct tracker *tr)
+static int
+tier_active(struct tr_tier *t)
 {
-    struct tr_op **opp;
-    char *url = get_url(tr);
-    for (opp = m_tr_ops; *opp != NULL; opp++)
-        if (strncasecmp((*opp)->scheme, url, (*opp)->len) == 0)
-            return *opp;
-    return NULL;
+    return t->active;
 }
 
 static void
-tr_cancel(struct tracker *tr)
+tier_timer_cb(int fd, short type, void *arg)
 {
-    struct tr_op *op = get_op(tr);
-    assert(op != NULL);
-    op->cancel(tr->req);
-    tr->req = NULL;
+    struct tr_tier *t = arg;
+    assert(tier_active(t));
+    entry_send(t, BTPDQ_FIRST(&t->trackers), t->event);
 }
 
 static void
-tr_set_stopped(struct torrent *tp)
+tier_start(struct tr_tier *t)
 {
-    struct tracker *tr = tp->tr;
-    btpd_timer_del(&tr->timer);
-    tr->ttype = TIMER_NONE;
-    if (tr->req != NULL)
-        tr_cancel(tr);
+    assert(!tier_active(t) || t->event == TR_EV_STOPPED);
+    if (t->failure != NULL) {
+        free(t->failure);
+        t->failure = NULL;
+    }
+    t->has_responded = 0;
+    t->bad_conns = 0;
+    t->active = 1;
+    entry_send(t, BTPDQ_FIRST(&t->trackers), TR_EV_STARTED);
 }
 
 static void
-tr_send(struct torrent *tp, enum tr_event event)
+tier_stop(struct tr_tier *t)
 {
-    struct tracker *tr = tp->tr;
-    struct tr_op *op = get_op(tr);
+    if (!tier_active(t) || t->event == TR_EV_STOPPED)
+        return;
 
-    tr->event = event;
-    if (tr->req != NULL)
-        tr_cancel(tr);
+    if (!t->has_responded && t->bad_conns > 1) {
+        btpd_timer_del(&t->timer);
+        if (t->req != NULL)
+            req_cancel(t);
+        t->active = 0;
+    } else
+        entry_send(t, BTPDQ_FIRST(&t->trackers), TR_EV_STOPPED);
+}
 
-    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_timer_add(&tr->timer,
-            (& (struct timespec) { m_tnext_req - btpd_seconds, 0 }));
-        return;
-    }
+static void
+tier_complete(struct tr_tier *t)
+{
+    if (tier_active(t) && t->event == TR_EV_EMPTY)
+        entry_send(t, BTPDQ_FIRST(&t->trackers), TR_EV_COMPLETED);
+}
 
-    if ((op == NULL ||
-            (tr->req = op->request(tp, event, get_url(tr))) == NULL)) {
-        tr->nerrors++;
-        if (tr->event == TR_EV_STOPPED && tr->nerrors >= STOP_ERRORS) {
-            tr_set_stopped(tp);
-            return;
-        }
-        next_url(tr);
-        tr->ttype = TIMER_RETRY;
-        btpd_timer_add(&tr->timer, (& (struct timespec) { 5, 0 }));
-    } else {
-        m_tlast_req = btpd_seconds;
-        tr->ttype = TIMER_TIMEOUT;
-        btpd_timer_add(&tr->timer, REQ_TIMEOUT);
-    }
+static void
+add_tracker(struct tr_tier *t, const char *url)
+{
+    struct tr_entry *e;
+    struct http_url *hu;
+    if ((hu = http_url_parse(url)) != NULL) {
+        http_url_free(hu);
+        e = btpd_calloc(1, sizeof(*e));
+        if ((e->url = strdup(url)) == NULL)
+            btpd_err("Out of memory.\n");
+        e->type = TR_HTTP;
+    } else
+        return;
+    BTPDQ_INSERT_TAIL(&t->trackers, e, entry);
 }
 
-void
-tr_result(struct torrent *tp, enum tr_res res, int interval)
-{
-    struct tracker *tr = tp->tr;
-    tr->req = NULL;
-    if (tr->event == TR_EV_STOPPED &&
-            (res == TR_RES_OK || tr->nerrors >= STOP_ERRORS - 1))
-        tr_set_stopped(tp);
-    else if (res == TR_RES_OK) {
-        good_url(tr);
-        tr->interval = interval;
-        tr->nerrors = 0;
-        tr->ttype = TIMER_INTERVAL;
-        btpd_timer_add(&tr->timer, (& (struct timespec) { tr->interval, 0}));
+static struct tr_tier *
+tier_create(struct torrent *tp, struct mi_tier *tier)
+{
+    struct tr_tier *t = btpd_calloc(1, sizeof(*t));
+    BTPDQ_INIT(&t->trackers);
+    for (int i = 0; i < tier->nurls; i++)
+        add_tracker(t, tier->urls[i]);
+    if (!BTPDQ_EMPTY(&t->trackers)) {
+        t->tp = tp;
+        t->interval = -1;
+        t->event = TR_EV_STOPPED;
+        timer_init(&t->timer, tier_timer_cb, t);
+        return t;
     } else {
-        tr->nerrors++;
-        tr->ttype = TIMER_RETRY;
-        btpd_timer_add(&tr->timer, RETRY_WAIT);
-        next_url(tr);
+        free(t);
+        return NULL;
     }
 }
 
 static void
-timer_cb(int fd, short type, void *arg)
-{
-    struct torrent *tp = arg;
-    struct tracker *tr = tp->tr;
-    switch (tr->ttype) {
-    case TIMER_TIMEOUT:
-        btpd_log(BTPD_L_ERROR, "Tracker request timed out for '%s'.\n",
-            torrent_name(tp));
-        tr->nerrors++;
-        if (tr->event == TR_EV_STOPPED && tr->nerrors >= STOP_ERRORS) {
-            tr_set_stopped(tp);
-            break;
-        }
-        tr_cancel(tr);
-        next_url(tr);
-    case TIMER_RETRY:
-        tr_send(tp, tr->event);
-        break;
-    case TIMER_INTERVAL:
-        tr_send(tp, TR_EV_EMPTY);
-        break;
-    default:
-        abort();
+tier_kill(struct tr_tier *t)
+{
+    struct tr_entry *e, *next;
+    if (t->failure != NULL)
+        free(t->failure);
+    btpd_timer_del(&t->timer);
+    if (t->req != NULL)
+        req_cancel(t);
+    BTPDQ_FOREACH_MUTABLE(e, &t->trackers, entry , next) {
+        free(e->url);
+        free(e);
     }
+    free(t);
 }
 
-int
+void
 tr_create(struct torrent *tp, const char *mi)
 {
+    int i;
+    struct tr_tier *t;
+    struct mi_announce *ann;
     tp->tr = btpd_calloc(1, sizeof(*tp->tr));
-    if ((tp->tr->ann = mi_announce(mi)) == NULL)
+    BTPDQ_INIT(&tp->tr->trackers);
+    if ((ann = mi_announce(mi)) == NULL)
         btpd_err("Out of memory.\n");
-    timer_init(&tp->tr->timer, timer_cb, tp);
-    return 0;
+    for (i = 0; i < ann->ntiers; i++)
+        if ((t = tier_create(tp, &ann->tiers[i])) != NULL)
+            BTPDQ_INSERT_TAIL(&tp->tr->trackers, t, entry);
+    mi_free_announce(ann);
 }
 
 void
 tr_kill(struct torrent *tp)
 {
-    struct tracker *tr = tp->tr;
+    struct tr_tier *t, *next;
+    BTPDQ_FOREACH_MUTABLE(t, &tp->tr->trackers, entry, next)
+        tier_kill(t);
+    free(tp->tr);
     tp->tr = NULL;
-    btpd_timer_del(&tr->timer);
-    if (tr->req != NULL)
-        tr_cancel(tr);
-    mi_free_announce(tr->ann);
-    free(tr);
 }
 
 void
 tr_start(struct torrent *tp)
 {
-    tr_send(tp, TR_EV_STARTED);
+    struct tr_tier *t;
+    BTPDQ_FOREACH(t, &tp->tr->trackers, entry)
+        tier_start(t);
 }
 
 void
-tr_refresh(struct torrent *tp)
+tr_stop(struct torrent *tp)
 {
-    tr_send(tp, TR_EV_EMPTY);
+    struct tr_tier *t;
+    BTPDQ_FOREACH(t, &tp->tr->trackers, entry)
+        tier_stop(t);
 }
 
 void
 tr_complete(struct torrent *tp)
 {
-    tr_send(tp, TR_EV_COMPLETED);
+    struct tr_tier *t;
+    BTPDQ_FOREACH(t, &tp->tr->trackers, entry)
+        tier_complete(t);
 }
 
-void
-tr_stop(struct torrent *tp)
+int
+tr_active(struct torrent *tp)
 {
-    if (tp->tr->event == TR_EV_STOPPED)
-        tr_set_stopped(tp);
-    else
-        tr_send(tp, TR_EV_STOPPED);
+    struct tr_tier *t;
+    BTPDQ_FOREACH(t, &tp->tr->trackers, entry)
+        if (tier_active(t))
+            return 1;
+    return 0;
 }
 
 int
-tr_active(struct torrent *tp)
+tr_good_count(struct torrent *tp)
 {
-    return tp->tr->ttype != TIMER_NONE;
+    int count = 0;
+    struct tr_tier *t;
+    BTPDQ_FOREACH(t, &tp->tr->trackers, entry)
+        if (tier_active(t) && t->bad_conns == 0)
+            count++;
+    return count;
 }
 
-unsigned
-tr_errors(struct torrent *tp)
+void
+tr_result(struct tr_tier *t, struct tr_response *res)
 {
-    return tp->tr->nerrors;
+    struct tr_entry *e;
+    t->req = NULL;
+    switch (res->type) {
+    case TR_RES_FAIL:
+        t->active = 0;
+        t->failure = benc_str(res->mi_failure, NULL, NULL);
+        btpd_log(BTPD_L_ERROR, "tracker at '%s' failed (%s).\n",
+            t->cur->url, t->failure);
+        break;
+    case TR_RES_CONN:
+        if ((e = BTPDQ_NEXT(t->cur, entry)) != NULL) {
+            entry_send(t, e, t->event);
+            break;
+        }
+        t->bad_conns++;
+        if (t->event == TR_EV_STOPPED && t->bad_conns > 1)
+            t->active = 0;
+        else if (t->bad_conns == 1)
+            entry_send(t, BTPDQ_FIRST(&t->trackers), t->event);
+        else if (t->bad_conns == 2)
+            btpd_timer_add(&t->timer, RETRY1_TIMEOUT);
+        else
+            btpd_timer_add(&t->timer, RETRY2_TIMEOUT);
+        break;
+    case TR_RES_BAD:
+    case TR_RES_OK:
+        if (t->event == TR_EV_STOPPED)
+            t->active = 0;
+        else {
+            t->event = TR_EV_EMPTY;
+            if (res->interval > 0)
+                t->interval = res->interval;
+            btpd_timer_add(&t->timer, (& (struct timespec) {
+                t->interval > 0 ? t->interval : DEFAULT_INTERVAL, 0 }));
+        }
+        t->bad_conns = 0;
+        t->has_responded = 1;
+        BTPDQ_REMOVE(&t->trackers, t->cur, entry);
+        BTPDQ_INSERT_HEAD(&t->trackers, t->cur, entry);
+        break;
+    default:
+        abort();
+    }
 }
 
 void
diff --git a/btpd/tracker_req.h b/btpd/tracker_req.h
index caedeb9..3ebd7a2 100644
--- a/btpd/tracker_req.h
+++ b/btpd/tracker_req.h
@@ -8,28 +8,31 @@ enum tr_event {
     TR_EV_EMPTY
 };
 
-enum tr_res {
-    TR_RES_OK,
-    TR_RES_FAIL
+extern long tr_key;
+
+enum tr_type { TR_HTTP };
+
+struct tr_response {
+    enum {
+        TR_RES_FAIL, TR_RES_CONN, TR_RES_BAD, TR_RES_OK
+    } type;
+    const char *mi_failure;
+    int interval;
 };
 
-extern long tr_key;
+struct tr_tier;
 
-int tr_create(struct torrent *tp, const char *mi);
+void tr_create(struct torrent *tp, const char *mi);
 void tr_kill(struct torrent *tp);
 void tr_start(struct torrent *tp);
 void tr_stop(struct torrent *tp);
-void tr_refresh(struct torrent *tp);
 void tr_complete(struct torrent *tp);
-unsigned tr_errors(struct torrent *tp);
 int tr_active(struct torrent *tp);
+void tr_result(struct tr_tier *t, struct tr_response *res);
+int tr_good_count(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);
+struct httptr_req *httptr_req(struct torrent *tp, struct tr_tier *tr,
+    const char *url, enum tr_event event);
+void httptr_cancel(struct httptr_req *req);
 
 #endif