summary refs log tree commit diff
diff options
context:
space:
mode:
authorRichard Nyberg <rnyberg@murmeldjur.se>2009-02-07 21:13:07 +0100
committerRichard Nyberg <rnyberg@murmeldjur.se>2009-02-07 21:20:31 +0100
commit8b04af7e9104874a26fc511ca666bc88ff3e178d (patch)
treec4e0808c3e85ab763bbc7eacde8d4c9ec3fe18cb
parent89a95cbdf5b4b0b8c82ca937156ba0db1205f73a (diff)
downloadbtpd-8b04af7e9104874a26fc511ca666bc88ff3e178d.tar.gz
btpd-8b04af7e9104874a26fc511ca666bc88ff3e178d.zip
Find and ban peers with bad data.
Log which peer contributed what to a piece. Do not try to download the
same piece from the same peers. Don't download at all from peers implicated
in 3 bad pieces. When a previously bad piece has been downloaded successfully
the bad peer(s) can be found and banned.
-rw-r--r--btpd/btpd.h4
-rw-r--r--btpd/download.c37
-rw-r--r--btpd/download.h4
-rw-r--r--btpd/download_subr.c173
-rw-r--r--btpd/net.c10
-rw-r--r--btpd/net.h1
-rw-r--r--btpd/net_types.h18
-rw-r--r--btpd/peer.c76
-rw-r--r--btpd/peer.h8
-rw-r--r--btpd/util.c2
10 files changed, 285 insertions, 48 deletions
diff --git a/btpd/btpd.h b/btpd/btpd.h
index 70316a7..e20cf04 100644
--- a/btpd/btpd.h
+++ b/btpd/btpd.h
@@ -58,6 +58,7 @@
 #define BTPD_L_MSG      0x00000008
 #define BTPD_L_BTPD     0x00000010
 #define BTPD_L_POL      0x00000020
+#define BTPD_L_BAD      0x00000040
 
 extern long btpd_seconds;
 
@@ -90,6 +91,9 @@ uint32_t btpd_id_hash(const void *k);
 
 const uint8_t *btpd_get_peer_id(void);
 
+int btpd_id_eq(const void *id1, const void *id2);
+uint32_t btpd_id_hash(const void *id);
+
 void td_acquire_lock(void);
 void td_release_lock(void);
 
diff --git a/btpd/download.c b/btpd/download.c
index d24b0a2..ce3c4c6 100644
--- a/btpd/download.c
+++ b/btpd/download.c
@@ -17,18 +17,17 @@ dl_on_piece_ann(struct peer *p, uint32_t index)
     if (n->endgame) {
         assert(pc != NULL);
         peer_want(p, index);
-        if (!peer_chokes(p) && !peer_laden(p))
+        if (peer_leech_ok(p))
             dl_assign_requests_eg(p);
     } else if (pc == NULL) {
         peer_want(p, index);
-        if (!peer_chokes(p) && !peer_laden(p)) {
+        if (peer_leech_ok(p)) {
             pc = dl_new_piece(n, index);
-            if (pc != NULL)
-                dl_piece_assign_requests(pc, p);
+            dl_piece_assign_requests(pc, p);
         }
     } else if (!piece_full(pc)) {
         peer_want(p, index);
-        if (!peer_chokes(p) && !peer_laden(p))
+        if (peer_leech_ok(p))
             dl_piece_assign_requests(pc, p);
     }
 }
@@ -36,21 +35,17 @@ dl_on_piece_ann(struct peer *p, uint32_t index)
 void
 dl_on_download(struct peer *p)
 {
-    assert(peer_wanted(p));
     struct net *n = p->n;
-    if (n->endgame) {
+    if (n->endgame)
         dl_assign_requests_eg(p);
-    } else {
-        unsigned count = dl_assign_requests(p);
-        if (count == 0 && !p->n->endgame) // We may have entered end game.
-            assert(!peer_wanted(p) || peer_laden(p));
-    }
+    else
+        dl_assign_requests(p);
 }
 
 void
 dl_on_unchoke(struct peer *p)
 {
-    if (peer_wanted(p))
+    if (peer_leech_ok(p))
         dl_on_download(p);
 }
 
@@ -90,8 +85,9 @@ dl_on_ok_piece(struct net *n, uint32_t piece)
 
     if (n->endgame)
         BTPDQ_FOREACH(p, &n->peers, p_entry)
-            if (peer_has(p, pc->index))
-                peer_unwant(p, pc->index);
+            peer_unwant(p, pc->index);
+
+    piece_log_good(pc);
 
     assert(pc->nreqs == 0);
     piece_free(pc);
@@ -114,14 +110,16 @@ dl_on_bad_piece(struct net *n, uint32_t piece)
     pc->ngot = 0;
     pc->nbusy = 0;
 
+    piece_log_bad(pc);
+
     if (n->endgame) {
         struct peer *p;
         BTPDQ_FOREACH(p, &n->peers, p_entry) {
-            if (peer_has(p, pc->index) && peer_leech_ok(p) && !peer_laden(p))
+            if (peer_leech_ok(p) && peer_requestable(p, pc->index))
                 dl_assign_requests_eg(p);
         }
     } else
-        dl_on_piece_unfull(pc); // XXX: May get bad data again.
+        dl_on_piece_unfull(pc);
 }
 
 void
@@ -149,6 +147,7 @@ dl_on_block(struct peer *p, struct block_request *req,
     struct net *n = p->n;
     struct piece *pc = dl_find_piece(n, index);
 
+    piece_log_block(pc, p, begin);
     cm_put_bytes(p->n->tp, index, begin, data, length);
     pc->ngot++;
 
@@ -170,7 +169,7 @@ dl_on_block(struct peer *p, struct block_request *req,
                 continue;
             BTPDQ_REMOVE(&pc->reqs, req, blk_entry);
             nb_drop(req->msg);
-            if (peer_leech_ok(req->p) && !peer_laden(req->p))
+            if (peer_leech_ok(req->p))
                 dl_assign_requests_eg(req->p);
             free(req);
         }
@@ -186,7 +185,7 @@ dl_on_block(struct peer *p, struct block_request *req,
         pc->nbusy--;
         if (pc->ngot == pc->nblocks)
             cm_test_piece(pc->n->tp, pc->index);
-        if (peer_leech_ok(p) && !peer_laden(p))
+        if (peer_leech_ok(p))
             dl_assign_requests(p);
     }
 }
diff --git a/btpd/download.h b/btpd/download.h
index ed2ecc3..8a07945 100644
--- a/btpd/download.h
+++ b/btpd/download.h
@@ -6,6 +6,10 @@
 int piece_full(struct piece *pc);
 void piece_free(struct piece *pc);
 
+void piece_log_bad(struct piece *pc);
+void piece_log_good(struct piece *pc);
+void piece_log_block(struct piece *pc, struct peer *p, uint32_t begin);
+
 void dl_on_piece_unfull(struct piece *pc);
 
 struct piece *dl_new_piece(struct net *n, uint32_t index);
diff --git a/btpd/download_subr.c b/btpd/download_subr.c
index 2f57f07..0f91f8f 100644
--- a/btpd/download_subr.c
+++ b/btpd/download_subr.c
@@ -24,6 +24,135 @@
 #include <openssl/sha.h>
 #include <stream.h>
 
+static void
+piece_new_log(struct piece *pc)
+{
+    struct blog *log = btpd_calloc(1, sizeof(*log));
+    BTPDQ_INIT(&log->records);
+    BTPDQ_INSERT_HEAD(&pc->logs, log, entry);
+}
+
+static void
+piece_log_hashes(struct piece *pc)
+{
+    uint8_t *buf;
+    struct torrent *tp = pc->n->tp;
+    struct blog *log = BTPDQ_FIRST(&pc->logs);
+    log->hashes = btpd_malloc(20 * pc->nblocks);
+    for (unsigned i = 0; i < pc->nblocks; i++) {
+        uint32_t bsize = torrent_block_size(tp, pc->index, pc->nblocks, i);
+        cm_get_bytes(tp, pc->index, i * PIECE_BLOCKLEN, bsize, &buf);
+        SHA1(buf, bsize, &log->hashes[i * 20]);
+        free(buf);
+    }
+}
+
+static void
+piece_log_free(struct piece *pc, struct blog *log)
+{
+    struct blog_record *r, *rnext;
+    BTPDQ_FOREACH_MUTABLE(r, &log->records, entry, rnext) {
+        mp_drop(r->mp, pc->n);
+        free(r);
+    }
+    if (log->hashes != NULL)
+        free(log->hashes);
+    free(log);
+}
+
+static void
+piece_kill_logs(struct piece *pc)
+{
+    struct blog *log, *lnext;
+    BTPDQ_FOREACH_MUTABLE(log, &pc->logs, entry, lnext)
+        piece_log_free(pc, log);
+    BTPDQ_INIT(&pc->logs);
+}
+
+void
+piece_log_bad(struct piece *pc)
+{
+    struct blog *log = BTPDQ_FIRST(&pc->logs);
+    struct blog_record *r = BTPDQ_FIRST(&log->records);
+    struct meta_peer *culprit = NULL;
+
+    if (r == BTPDQ_LAST(&log->records, blog_record_tq)) {
+        unsigned i;
+        for (i = 0; i < pc->nblocks; i++)
+            if (!has_bit(r->down_field, i))
+                break;
+        if (i == pc->nblocks)
+            culprit = r->mp;
+    }
+    if (culprit != NULL) {
+        if (pc->n->endgame && culprit->p != NULL)
+            peer_unwant(culprit->p, pc->index);
+        net_ban_peer(pc->n, culprit);
+        BTPDQ_REMOVE(&pc->logs, log, entry);
+        piece_log_free(pc, log);
+    } else {
+        BTPDQ_FOREACH(r, &log->records, entry) {
+            if (r->mp->p != NULL) {
+                if (pc->n->endgame)
+                    peer_unwant(r->mp->p, pc->index);
+                peer_bad_piece(r->mp->p, pc->index);
+            }
+        }
+        piece_log_hashes(pc);
+    }
+    piece_new_log(pc);
+}
+
+void
+piece_log_good(struct piece *pc)
+{
+    struct blog_record *r;
+    struct blog *log = BTPDQ_FIRST(&pc->logs), *bad = BTPDQ_NEXT(log, entry);
+
+    BTPDQ_FOREACH(r, &log->records, entry)
+        if (r->mp->p != NULL)
+            peer_good_piece(r->mp->p, pc->index);
+
+    if (bad != NULL)
+        piece_log_hashes(pc);
+
+    while (bad != NULL) {
+        BTPDQ_FOREACH(r, &bad->records, entry) {
+            int culprit = 0;
+            for (unsigned i = 0; i < pc->nblocks && !culprit; i++)
+                if (has_bit(r->down_field, i) && (
+                        bcmp(&log->hashes[i*20], &bad->hashes[i*20], 20) != 0))
+                    culprit = 1;
+            if (culprit)
+                net_ban_peer(pc->n, r->mp);
+            else if (r->mp->p != NULL)
+                peer_good_piece(r->mp->p, pc->index);
+        }
+
+        bad = BTPDQ_NEXT(bad, entry);
+    }
+}
+
+void
+piece_log_block(struct piece *pc, struct peer *p, uint32_t begin)
+{
+    struct blog_record *r;
+    struct blog *log = BTPDQ_FIRST(&pc->logs);
+    BTPDQ_FOREACH(r, &log->records, entry)
+        if (r->mp == p->mp)
+            break;
+    if (r == NULL) {
+        r = btpd_calloc(1, sizeof(*r) + ceil(pc->nblocks / 8.0));
+        r->mp = p->mp;
+        mp_hold(r->mp);
+        BTPDQ_INSERT_HEAD(&log->records, r, entry);
+    } else {
+        BTPDQ_REMOVE(&log->records, r, entry);
+        BTPDQ_INSERT_HEAD(&log->records, r, entry);
+    }
+    set_bit(r->down_field, begin / PIECE_BLOCKLEN);
+}
+
 static struct piece *
 piece_alloc(struct net *n, uint32_t index)
 {
@@ -54,10 +183,13 @@ piece_alloc(struct net *n, uint32_t index)
     assert(pc->ngot < pc->nblocks);
 
     BTPDQ_INIT(&pc->reqs);
+    BTPDQ_INIT(&pc->logs);
+
+    piece_new_log(pc);
 
     n->npcs_busy++;
     set_bit(n->busy_field, index);
-    BTPDQ_INSERT_HEAD(&n->getlst, pc, entry);
+    BTPDQ_INSERT_TAIL(&n->getlst, pc, entry);
     return pc;
 }
 
@@ -74,6 +206,7 @@ piece_free(struct piece *pc)
         nb_drop(req->msg);
         free(req);
     }
+    piece_kill_logs(pc);
     if (pc->eg_reqs != NULL) {
         for (uint32_t i = 0; i < pc->nblocks; i++)
             if (pc->eg_reqs[i] != NULL)
@@ -170,11 +303,9 @@ dl_enter_endgame(struct net *n)
     }
     BTPDQ_FOREACH(p, &n->peers, p_entry) {
         assert(p->nwant == 0);
-        BTPDQ_FOREACH(pc, &n->getlst, entry) {
-            if (peer_has(p, pc->index))
-                peer_want(p, pc->index);
-        }
-        if (p->nwant > 0 && peer_leech_ok(p) && !peer_laden(p))
+        BTPDQ_FOREACH(pc, &n->getlst, entry)
+            peer_want(p, pc->index);
+        if (peer_leech_ok(p))
             dl_assign_requests_eg(p);
     }
 }
@@ -192,7 +323,7 @@ dl_find_piece(struct net *n, uint32_t index)
 static int
 dl_piece_startable(struct peer *p, uint32_t index)
 {
-    return peer_has(p, index) && !cm_has_piece(p->n->tp, index)
+    return peer_requestable(p, index) && !cm_has_piece(p->n->tp, index)
         && !has_bit(p->n->busy_field, index);
 }
 
@@ -252,23 +383,12 @@ static void
 dl_on_piece_full(struct piece *pc)
 {
     struct peer *p;
-    BTPDQ_FOREACH(p, &pc->n->peers, p_entry) {
-        if (peer_has(p, pc->index))
-            peer_unwant(p, pc->index);
-    }
+    BTPDQ_FOREACH(p, &pc->n->peers, p_entry)
+        peer_unwant(p, pc->index);
     if (dl_should_enter_endgame(pc->n))
         dl_enter_endgame(pc->n);
 }
 
-/*
- * Allocate the piece indicated by the index for download.
- * There's a small possibility that a piece is fully downloaded
- * but haven't been tested. If such is the case the piece will
- * be tested and NULL will be returned. Also, we might then enter
- * end game.
- *
- * Return the piece or NULL.
- */
 struct piece *
 dl_new_piece(struct net *n, uint32_t index)
 {
@@ -291,11 +411,10 @@ dl_on_piece_unfull(struct piece *pc)
     struct peer *p;
     assert(!piece_full(pc) && n->endgame == 0);
     BTPDQ_FOREACH(p, &n->peers, p_entry)
-        if (peer_has(p, pc->index))
-            peer_want(p, pc->index);
+        peer_want(p, pc->index);
     p = BTPDQ_FIRST(&n->peers);
     while (p != NULL && !piece_full(pc)) {
-        if (peer_leech_ok(p) && !peer_laden(p))
+        if (peer_leech_ok(p) && peer_requestable(p, pc->index))
             dl_piece_assign_requests(pc, p); // Cannot provoke end game here.
         p = BTPDQ_NEXT(p, p_entry);
     }
@@ -368,12 +487,12 @@ dl_piece_assign_requests(struct piece *pc, struct peer *p)
 unsigned
 dl_assign_requests(struct peer *p)
 {
-    assert(!p->n->endgame && !peer_laden(p));
+    assert(!p->n->endgame && peer_leech_ok(p));
     struct piece *pc;
     struct net *n = p->n;
     unsigned count = 0;
     BTPDQ_FOREACH(pc, &n->getlst, entry) {
-        if (piece_full(pc) || !peer_has(p, pc->index))
+        if (piece_full(pc) || !peer_requestable(p, pc->index))
             continue;
         count += dl_piece_assign_requests(pc, p);
         if (n->endgame)
@@ -455,7 +574,7 @@ dl_piece_assign_requests_eg(struct piece *pc, struct peer *p)
 void
 dl_assign_requests_eg(struct peer *p)
 {
-    assert(!peer_laden(p));
+    assert(peer_leech_ok(p));
     struct net *n = p->n;
     struct piece_tq tmp;
     BTPDQ_INIT(&tmp);
@@ -463,7 +582,7 @@ dl_assign_requests_eg(struct peer *p)
     struct piece *pc = BTPDQ_FIRST(&n->getlst);
     while (!peer_laden(p) && pc != NULL) {
         struct piece *next = BTPDQ_NEXT(pc, entry);
-        if (peer_has(p, pc->index) && pc->nblocks != pc->ngot) {
+        if (peer_requestable(p, pc->index) && pc->nblocks != pc->ngot) {
             dl_piece_assign_requests_eg(pc, p);
             BTPDQ_REMOVE(&n->getlst, pc, entry);
             BTPDQ_INSERT_HEAD(&tmp, pc, entry);
diff --git a/btpd/net.c b/btpd/net.c
index d5ef575..0c64489 100644
--- a/btpd/net.c
+++ b/btpd/net.c
@@ -23,6 +23,16 @@ struct peer_tq net_bw_readq = BTPDQ_HEAD_INITIALIZER(net_bw_readq);
 struct peer_tq net_bw_writeq = BTPDQ_HEAD_INITIALIZER(net_bw_writeq);
 struct peer_tq net_unattached = BTPDQ_HEAD_INITIALIZER(net_unattached);
 
+void
+net_ban_peer(struct net *n, struct meta_peer *mp)
+{
+    if (mp->flags & PF_BANNED)
+        return;
+    mp_hold(mp); // Keep the meta peer alive
+    mp->flags |= PF_BANNED;
+    btpd_log(BTPD_L_BAD, "banned peer %p.\n", mp);
+}
+
 int
 net_torrent_has_peer(struct net *n, const uint8_t *id)
 {
diff --git a/btpd/net.h b/btpd/net.h
index 11b4506..c0aa24a 100644
--- a/btpd/net.h
+++ b/btpd/net.h
@@ -29,6 +29,7 @@ void net_start(struct torrent *tp);
 void net_stop(struct torrent *tp);
 int net_active(struct torrent *tp);
 
+void net_ban_peer(struct net *n, struct meta_peer *mp);
 int net_torrent_has_peer(struct net *n, const uint8_t *id);
 
 void net_io_cb(int sd, short type, void *arg);
diff --git a/btpd/net_types.h b/btpd/net_types.h
index 8b7438e..71b43e2 100644
--- a/btpd/net_types.h
+++ b/btpd/net_types.h
@@ -4,6 +4,8 @@
 BTPDQ_HEAD(peer_tq, peer);
 BTPDQ_HEAD(piece_tq, piece);
 BTPDQ_HEAD(block_request_tq, block_request);
+BTPDQ_HEAD(blog_tq, blog);
+BTPDQ_HEAD(blog_record_tq, blog_record);
 
 struct net {
     struct torrent *tp;
@@ -47,8 +49,11 @@ HTBL_TYPE(mptbl, meta_peer, uint8_t, id, chain);
 struct peer {
     int sd;
     uint8_t *piece_field;
+    uint8_t *bad_field;
     uint32_t npieces;
     uint32_t nwant;
+    uint32_t npcs_bad;
+    int suspicion;
 
     struct net *n;
     struct meta_peer *mp;
@@ -102,6 +107,7 @@ struct piece {
 
     struct net_buf **eg_reqs;
     struct block_request_tq reqs;
+    struct blog_tq logs;
 
     const uint8_t *have_field;
     uint8_t *down_field;
@@ -109,6 +115,18 @@ struct piece {
     BTPDQ_ENTRY(piece) entry;
 };
 
+struct blog {
+    BTPDQ_ENTRY(blog) entry;
+    struct blog_record_tq records;
+    uint8_t *hashes;
+};
+
+struct blog_record {
+    BTPDQ_ENTRY(blog_record) entry;
+    struct meta_peer *mp;
+    uint8_t down_field[];
+};
+
 struct block_request {
     struct peer *p;
     struct net_buf *msg;
diff --git a/btpd/peer.c b/btpd/peer.c
index 3f35773..2d31dd7 100644
--- a/btpd/peer.c
+++ b/btpd/peer.c
@@ -26,6 +26,7 @@ mp_drop(struct meta_peer *mp, struct net *n)
     assert(mp->refs > 0);
     mp->refs--;
     if (mp->refs == 0) {
+        assert(mp->p == NULL);
         if (mp->flags & PF_ATTACHED)
             assert(mptbl_remove(n->mptbl, mp->id) == mp);
         mp_kill(mp);
@@ -70,6 +71,8 @@ peer_kill(struct peer *p)
         free(p->in.buf);
     if (p->piece_field != NULL)
         free(p->piece_field);
+    if (p->bad_field != NULL)
+        free(p->bad_field);
     free(p);
     net_npeers--;
 }
@@ -257,9 +260,14 @@ peer_choke(struct peer *p)
 void
 peer_want(struct peer *p, uint32_t index)
 {
+    if (!has_bit(p->piece_field, index) || peer_has_bad(p, index))
+        return;
     assert(p->nwant < p->npieces);
     p->nwant++;
     if (p->nwant == 1) {
+        p->mp->flags |= PF_I_WANT;
+        if (p->mp->flags & PF_SUSPECT)
+            return;
         if (p->nreqs_out == 0) {
             assert((p->mp->flags & PF_DO_UNWANT) == 0);
             int unsent = 0;
@@ -272,17 +280,20 @@ peer_want(struct peer *p, uint32_t index)
             assert((p->mp->flags & PF_DO_UNWANT) != 0);
             p->mp->flags &= ~PF_DO_UNWANT;
         }
-        p->mp->flags |= PF_I_WANT;
     }
 }
 
 void
 peer_unwant(struct peer *p, uint32_t index)
 {
+    if (!has_bit(p->piece_field, index) || peer_has_bad(p, index))
+        return;
     assert(p->nwant > 0);
     p->nwant--;
     if (p->nwant == 0) {
         p->mp->flags &= ~PF_I_WANT;
+        if (p->mp->flags & PF_SUSPECT)
+            return;
         p->t_nointerest = btpd_seconds;
         if (p->nreqs_out == 0)
             peer_send(p, nb_create_uninterest());
@@ -568,6 +579,8 @@ peer_on_cancel(struct peer *p, uint32_t index, uint32_t begin,
 void
 peer_on_tick(struct peer *p)
 {
+    if (p->mp->flags & PF_BANNED)
+        goto kill;
     if (p->mp->flags & PF_ATTACHED) {
         if (BTPDQ_EMPTY(&p->outq)) {
             if (btpd_seconds - p->t_lastwrite >= 120)
@@ -590,6 +603,52 @@ kill:
     peer_kill(p);
 }
 
+void
+peer_bad_piece(struct peer *p, uint32_t index)
+{
+    if (p->npcs_bad == 0) {
+        assert(p->bad_field == NULL);
+        p->bad_field = btpd_calloc(ceil(p->n->tp->npieces / 8.0), 1);
+    }
+    assert(!has_bit(p->bad_field, index));
+    set_bit(p->bad_field, index);
+    p->npcs_bad++;
+    p->suspicion++;
+    if (p->suspicion == 3) {
+        btpd_log(BTPD_L_BAD, "suspect peer %p.\n", p);
+        p->mp->flags |= PF_SUSPECT;
+        if (p->nwant > 0) {
+            p->mp->flags &= ~PF_DO_UNWANT;
+            peer_send(p, nb_create_uninterest());
+        }
+    }
+}
+
+void
+peer_good_piece(struct peer *p, uint32_t index)
+{
+    if (peer_has_bad(p, index)) {
+        assert(p->npcs_bad > 0);
+        p->npcs_bad--;
+        if (p->npcs_bad == 0) {
+            free(p->bad_field);
+            p->bad_field = NULL;
+        } else
+            clear_bit(p->bad_field, index);
+    }
+    p->suspicion = 0;
+    if (p->mp->flags & PF_SUSPECT) {
+        btpd_log(BTPD_L_BAD, "unsuspect peer %p.\n", p);
+        p->mp->flags &= ~PF_SUSPECT;
+        if (p->nwant > 0) {
+            assert(p->mp->flags & PF_I_WANT);
+            peer_send(p, nb_create_interest());
+        }
+        if (peer_leech_ok(p))
+            dl_on_download(p);
+    }
+}
+
 int
 peer_chokes(struct peer *p)
 {
@@ -603,6 +662,12 @@ peer_has(struct peer *p, uint32_t index)
 }
 
 int
+peer_has_bad(struct peer *p, uint32_t index)
+{
+    return p->bad_field != NULL && has_bit(p->bad_field, index);
+}
+
+int
 peer_laden(struct peer *p)
 {
     return p->nreqs_out >= MAXPIPEDREQUESTS;
@@ -617,7 +682,8 @@ peer_wanted(struct peer *p)
 int
 peer_leech_ok(struct peer *p)
 {
-    return (p->mp->flags & (PF_I_WANT|PF_P_CHOKE)) == PF_I_WANT;
+    return (p->mp->flags & (PF_BANNED|PF_SUSPECT|PF_I_WANT|PF_P_CHOKE))
+        == PF_I_WANT && !peer_laden(p);
 }
 
 int
@@ -638,3 +704,9 @@ peer_full(struct peer *p)
 {
     return p->npieces == p->n->tp->npieces;
 }
+
+int
+peer_requestable(struct peer *p, uint32_t index)
+{
+    return peer_has(p, index) && !peer_has_bad(p, index);
+}
diff --git a/btpd/peer.h b/btpd/peer.h
index 0dc38bb..dfe1a3d 100644
--- a/btpd/peer.h
+++ b/btpd/peer.h
@@ -11,6 +11,8 @@
 #define PF_NO_REQUESTS   0x80
 #define PF_INCOMING     0x100
 #define PF_DO_UNWANT    0x200
+#define PF_SUSPECT      0x400
+#define PF_BANNED       0x800
 
 #define MAXPIECEMSGS 128
 #define MAXPIPEDREQUESTS 10
@@ -61,9 +63,15 @@ int peer_chokes(struct peer *p);
 int peer_wanted(struct peer *p);
 int peer_laden(struct peer *p);
 int peer_has(struct peer *p, uint32_t index);
+int peer_has_bad(struct peer *p, uint32_t index);
 int peer_leech_ok(struct peer *p);
 int peer_full(struct peer *p);
+void peer_bad_piece(struct peer *p, uint32_t index);
+void peer_good_piece(struct peer *p, uint32_t index);
+int peer_requestable(struct peer *p, uint32_t index);
 
+void mp_hold(struct meta_peer *mp);
+void mp_drop(struct meta_peer *mp, struct net *n);
 void mp_kill(struct meta_peer *mp);
 
 #endif
diff --git a/btpd/util.c b/btpd/util.c
index a3ce23e..edf935d 100644
--- a/btpd/util.c
+++ b/btpd/util.c
@@ -83,6 +83,8 @@ logtype_str(uint32_t type)
     case BTPD_L_CONN:  return "conn";
     case BTPD_L_TR:    return "tracker";
     case BTPD_L_MSG:   return "msg";
+    case BTPD_L_POL:   return "policy";
+    case BTPD_L_BAD:   return "bad";
     }
     return "";
 }