В свободное время я работал над написанием BitTorrent-клиента на C. Однако - на первый взгляд, случайным образом - мой клиент не сможет проверить правильность полученного фрагмента торрента с сообщением:
Failed to verify piece: #4
Справочная информация: файл .torrent содержит соответствующие метаданные для файла, подлежащего загрузке, включая хэш SHA1 для каждой части (блока файла, подлежащего загрузке), поэтому - после загрузки кусок - я беру его хэш SHA1 и сравниваю его с хешем, предоставленным файлом метаданных торрента.
Вот код, который обрабатывает проверку:
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
, который является индексом часть, которая не прошла проверку. Затем он копирует неправильный фрагмент в файл с именем failed_piece.out.
Вы заметите, что перед вызовом exit()
, write_incorrect_piece()
вызывает функцию write_correct_piece()
. Это вторая функция, которую я написал, чтобы помочь с отладкой этой ошибки. Для этого требуется известная хорошая версия файла, которую торрент-клиент пытается загрузить (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 фрагмента отличается от того, который ожидается в торрент-файле? Возможно, я тогда подумал, что хеш SHA1 из файла метаданных был неверным. Если это так, я бы ожидал, что клиент всегда будет не в состоянии проверить часть # 4, поэтому я изменил планировщик торрент-клиента, чтобы попытаться загрузить только часть # 4, чтобы посмотреть, действительно ли он всегда проходит проверку.
Successfully downloaded piece: #4
Wtf? Сейчас я в тупике. Я полагаю, что это может быть вызвано несколькими причинами:
- На самом деле есть ошибка в сетевом коде клиента, но я ввел ошибку в
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