Где ошибка в процессе проверки кусочков этого клиента BitTorrent? - PullRequest
1 голос
/ 27 сентября 2011

В свободное время я работал над написанием 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

1 Ответ

1 голос
/ 28 сентября 2011

Хорошо, поэтому, если мы посмотрим на вызов verify_piece(), мы увидим это:

 if (verify_piece(p->torrent->mmap + index * p->torrent->piece_length,
     p->torrent->piece_length, p->torrent->pieces[index].sha1)) {

Теперь, тривиально, мы знаем, что первый и второй параметры верны, так как это позжеиспользуется повторно, когда программа вызывает write_incorrect_piece(), и мы уже убедились, что ее вывод верен.Итак, мы можем сосредоточиться на третьем параметре, p->torrent->pieces[index].sha1.

На первый взгляд, параметр выглядит корректно, поскольку мы используем index во всей функции.Тем не менее, подумайте, что если index - в то же время идеально допустимый для правильно отсортированного массива - используется в качестве индекса для массива, который больше не содержит фрагменты в предполагаемом порядке?

Альт! Вот виновник (в __schedule()):

qsort(t->pieces, t->num_pieces, sizeof(struct Piece), rarest);

Закомментируйте вызов на qsort(), и клиент будет работать как положено.

...