From 8b04af7e9104874a26fc511ca666bc88ff3e178d Mon Sep 17 00:00:00 2001 From: Richard Nyberg Date: Sat, 7 Feb 2009 21:13:07 +0100 Subject: [PATCH] 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. --- btpd/btpd.h | 4 + btpd/download.c | 37 +++++---- btpd/download.h | 4 + btpd/download_subr.c | 173 ++++++++++++++++++++++++++++++++++++------- btpd/net.c | 10 +++ btpd/net.h | 1 + btpd/net_types.h | 18 +++++ btpd/peer.c | 76 ++++++++++++++++++- btpd/peer.h | 8 ++ btpd/util.c | 2 + 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 #include +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) { @@ -602,6 +661,12 @@ peer_has(struct peer *p, uint32_t index) return has_bit(p->piece_field, 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) { @@ -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 ""; }