在我的业余时间,我一直在用C语言写一个BitTorrent客户端。然而 - 看似随意 - 我的客户将无法通过消息验证收到的一条洪流是否正确:
Failed to verify piece: #4
背景:.torrent文件包含要下载的文件的相关元数据,包括每个部分的SHA1哈希值(要下载的文件块),所以 - 下载后piece - 我将其SHA1哈希值与torrent的元数据文件提供的哈希值进行比较。
以下是处理验证的代码:
int verify_piece (void* addr, uint64_t piece_length, unsigned char* piece_sha)
{
SHA_CTX sha;
unsigned char sha1result[20];
SHA1_Init(&sha);
SHA1_Update(&sha, addr, piece_length);
SHA1_Final(sha1result, &sha);
if (memcmp(sha1result, piece_sha, 20)) {
printf("Expected: ");
print_sha1(piece_sha);
printf("Received: ");
print_sha1(sha1result);
return 0;
} else
return 1;
}
为了追踪错误,我操纵了两个功能:write_incorrect_piece()
和write_correct_piece()
。 write_incorrect_piece()
失败时会调用第一个函数verify_piece()
。它将验证失败的文件写入文件,以便我可以使用hexdump进行检查。
void write_incorrect_piece (void* piece, uint32_t piece_length, uint32_t index)
{
FILE* failed_piece = fopen("failed_piece.out", "w+");
write(fileno(failed_piece), piece, piece_length);
fclose(failed_piece);
printf("ERROR: Wrote piece #%d to failed_piece.out for inspection with hexdump.\n",
index);
write_correct_piece(piece_length, index);
exit(1);
}
正如您所看到的,write_incorrect_piece()
采用参数void* piece
,这是指向验证失败的作品的指针,uint32_t piece_length
,即作品的长度,uint32_t index
1}}这是验证失败的部分的索引。然后它将不正确的部分复制到名为failed_piece.out的文件中。
您会注意到,在致电exit()
之前,write_incorrect_piece()
会调用函数write_correct_piece()
。这是我写的第二个帮助调试此错误的函数。它需要torrent客户端尝试下载的文件的已知良好版本(known_good.iso),然后将相关部分复制到文件(correct_piece.out),以便我可以将其与包含不正确部分的文件进行比较。
void write_correct_piece (uint32_t piece_length, uint32_t index)
{
FILE* known_good = fopen("known_good.iso", "r+");
FILE* correct_piece = fopen("correct_piece.out", "w+");
/* get file size for MMAPing */
struct stat st;
stat("known_good.iso", &st);
long size = st.st_size;
void* addr = mmap(0,
size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fileno(known_good),
0);
write(fileno(correct_piece), addr + piece_length * index, piece_length);
munmap(addr, size);
fclose(known_good);
fclose(correct_piece);
}
然后我拿了两个写入的文件并用hexdump格式化它们,这样输出就是人类可读的,然后尝试通过diff比较它们,就像这样
$ hexdump failed_piece.out > failed_piece.dump
$ hexdump correct_piece.out > correct_piece.dump
$ diff correct_piece.dump failed_piece.dump
令我惊讶的是,diff没有输出任何东西。文件完全一样。那么,为什么这个片段的SHA1哈希值不同于torrent文件所期望的那个?也许,我当时认为,元数据文件中的SHA1哈希是不正确的。如果是这种情况,我希望客户端始终无法验证第4条,所以我修改了torrent客户端的调度程序,只是尝试下载第4条,看它是否确实,实际上总是验证失败。
Successfully downloaded piece: #4
跆拳道?现在我陷入了僵局。我认为有几件不同的事情可能导致这种情况:
write_incorrect_piece()
和write_correct_piece()
中引入了一个错误,因此无论输入的完整性如何,它都会生成两个相同的文件。verify_piece()
中存在一个错误,导致其偶尔失败。但是,对于我的生活,我发现任何上述代码都没有任何问题,所以我希望你们好的人可以指出我所缺少的东西。
客户的整个来源位于https://github.com/robertseaton/slug。
修改:将strncmp()
替换为memcmp()
中的verify_piece()
并清除了write_incorrect_piece()
和write_correct_piece()
。
编辑:以下是我手动对其进行区分时出现故障片段的SHA1哈希示例。如您所见,哈希完全不相似:
Expected: 239 66 216 164 16 120 73 24 1 236 116 173 144 85 243 152 160 165 64 231
Received: 214 94 49 185 54 159 255 201 214 137 102 23 223 76 102 138 89 94 154 69
Failed to verify piece: #13
答案 0 :(得分:1)
好的,如果我们看一下verify_piece()
的调用,我们就会看到:
if (verify_piece(p->torrent->mmap + index * p->torrent->piece_length,
p->torrent->piece_length, p->torrent->pieces[index].sha1)) {
现在,我们知道第一个和第二个参数是正确的,因为它稍后在程序调用{{1}}时重复使用,并且我们已经验证了它的输出是正确的。因此,我们可以专注于第三个参数write_incorrect_piece()
。
乍一看,参数看起来是正确的,因为我们在整个函数中使用p->torrent->pieces[index].sha1
。但是,考虑一下,如果index
- 虽然在正确排序的数组上完全有效 - 被用作不再包含假定顺序的数组的数组的索引,该怎么办?
Viola!这是罪魁祸首(在index
中):
__schedule()
注释掉对qsort(t->pieces, t->num_pieces, sizeof(struct Piece), rarest);
的调用,客户端按预期运行。